gearheads
/
mastodon
Archived
2
0
Fork 0

Compare commits

...

331 Commits

Author SHA1 Message Date
Ducky 41e76b7902 Gearheads: v4.2.7-gh24050 2024-02-19 18:10:57 +00:00
Claire 0e4e98fad1 Bump version to v4.2.7 2024-02-16 11:57:02 +01:00
Claire 15de520201
Merge pull request from GHSA-jhrq-qvrm-qr36
* Fix insufficient Content-Type checking of fetched ActivityStreams objects

* Allow JSON-LD documents with multiple profiles
2024-02-16 11:56:12 +01:00
Claire 684f99908f Update dependency pg to 1.5.5 2024-02-16 09:19:35 +01:00
Claire e4ec4ce217
Update `nsa` gem to version 0.3.0 (#29065) (#29206)
Co-authored-by: Matt Jankowski <matt@jankowski.online>
2024-02-14 23:27:02 +01:00
Claire 870ee80fd3 Fix user creation failure handling in OAuth paths (#29207) 2024-02-14 22:55:31 +01:00
Claire 76a37bd040 Fix OmniAuth tests (#29201) 2024-02-14 16:06:38 +01:00
Claire 7c8ca0c6d6 Bump version to v4.2.6 2024-02-14 15:16:34 +01:00
Claire f1700523f1
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
2024-02-14 15:16:07 +01:00
Claire 0b0c7af2c1
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: Emelia Smith <ThisIsMissEm@users.noreply.github.com>
2024-02-14 15:15:34 +01:00
Claire 1a33d348d0 Add `sidekiq_unique_jobs:delete_all_locks` task and disable `sidekiq-unique-jobs` UI by default (#29199) 2024-02-14 13:17:45 +01:00
Emelia Smith 6d43b63275 Disable administrative doorkeeper routes (#29187) 2024-02-14 11:03:21 +01:00
Claire ae2dce813a Update dependency sidekiq-unique-jobs to 7.1.33 2024-02-14 11:02:55 +01:00
Claire b7230cd759 Update dependency nokogiri to 1.16.2 2024-02-14 11:02:11 +01:00
Claire a6641f828b
Merge pull request from GHSA-3fjr-858r-92rw
* Fix insufficient origin validation

* Bump version to v4.2.5
2024-02-01 15:56:46 +01:00
Claire 4633bb8ce0 Bump version to v4.2.4 2024-01-24 15:31:13 +01:00
Claire 1ab050eb52 Change PostgreSQL version check to check for PostgreSQL 10+ 2024-01-24 15:31:13 +01:00
Claire 4eb98ef755 Ignore the devise-two-factor advisory as we have rate limits in place (#28733) 2024-01-24 15:31:13 +01:00
Claire 7a22999f92 Bump ruby version to 3.2.3 2024-01-24 15:31:13 +01:00
Claire c5c464804d Update dependency puma to v6.4.2 2024-01-24 15:31:13 +01:00
Claire 779237f054 Fix error when processing remote files with unusually long names (#28823) 2024-01-24 15:31:13 +01:00
Claire b377f82b1d Fix processing of compacted single-item JSON-LD collections (#28816) 2024-01-24 15:31:13 +01:00
Claire 6fe2a47357 Add rate-limit of TOTP authentication attempts at controller level (#28801) 2024-01-24 15:31:13 +01:00
Jonathan de Jong 2dbf176d23 Retry 401 errors on replies fetching (#28788)
Co-authored-by: Claire <claire.github-309c@sitedethib.com>
2024-01-24 15:31:13 +01:00
Jeong Arm 499bc716a5 Ignore RecordNotUnique errors in LinkCrawlWorker (#28748) 2024-01-24 15:31:13 +01:00
Claire 3837ec2227 Fix Mastodon not correctly processing HTTP Signatures with query strings (#28476) 2024-01-24 15:31:13 +01:00
Claire 1998c561b2 Convert signature verification specs to request specs (#28443) 2024-01-24 15:31:13 +01:00
Claire c0a9db3611 Fix potential redirection loop of streaming endpoint (#28665) 2024-01-24 15:31:13 +01:00
Claire 01caa18e5b Fix streaming API redirection ignoring the port of `streaming_api_base_url` (#28558) 2024-01-24 15:31:13 +01:00
Claire c609b726cb Fix error when processing link preview with an array as `inLanguage` (#28252) 2024-01-24 15:31:13 +01:00
Eugen Rochko 4d96d716c4 Fix unsupported time zone or locale preventing sign-up (#28035)
Co-authored-by: Claire <claire.github-309c@sitedethib.com>
2024-01-24 15:31:13 +01:00
Brian Holley 3ecc991f63 Fix "Hide these posts from home" list setting not refreshing when switching lists (#27763) 2024-01-24 15:31:13 +01:00
Eugen Rochko 8f2dac0567 Fix missing background behind dismissable banner in web UI (#27479) 2024-01-24 15:31:13 +01:00
Claire dfc8fcc6f0 Fix width of large text icon buttons (#27127) 2024-01-24 15:31:13 +01:00
gunchleoc e8c5754142 Fix line wrapping of language selection button with long locale codes (#27100) 2024-01-24 15:31:13 +01:00
MitarashiDango 0a01bc01d2 Fix Undo Announce activity is not sent, when not followed by the reblogged post author (#18482)
Co-authored-by: Claire <claire.github-309c@sitedethib.com>
2024-01-24 15:31:13 +01:00
Claire a12b7551cf Fix N+1s because of association preloaders not actually getting called (#28339) 2024-01-24 15:31:13 +01:00
Claire 7abc61887f Fix empty column explainer getting cropped under certain conditions (#28337) 2024-01-24 15:31:13 +01:00
Claire 279be07679 Fix `LinkCrawlWorker` error when encountering empty OEmbed response (#28268) 2024-01-24 15:31:13 +01:00
Claire d7875adad2
Fix call to inefficient `delete_matched` cache method in domain blocks (#28367) 2023-12-19 11:27:37 +01:00
Claire 90371a4fc4 Bump version to v4.2.3 2023-12-05 15:35:05 +01:00
Claire 71b60b09f4 Update dependency json-ld to v3.3.1 2023-12-05 15:35:05 +01:00
Claire 4b8fe9df73 Bump version to v4.2.2 2023-12-04 15:28:15 +01:00
Claire 7b9496322f Change dismissed banners to be stored server-side (#27055) 2023-12-04 15:28:15 +01:00
Claire 09115731d6 Change GIF max matrix size error to explicitly mention GIF files (#27927) 2023-12-04 15:28:15 +01:00
Claire e11100d782 Clamp dates when serializing to Elasticsearch API (#28081) 2023-12-04 15:28:15 +01:00
Jonathan de Jong 252ea2fc67 Have `Follow` activities bypass availability (#27586)
Co-authored-by: Claire <claire.github-309c@sitedethib.com>
2023-12-04 15:28:15 +01:00
Claire 8d02e58ff4 Fix upper border radius of onboarding columns (#27890) 2023-12-04 15:28:15 +01:00
Claire 1076a6cd62 Fix incoming status creation date not being restricted to standard ISO8601 (#27655) 2023-12-04 15:28:15 +01:00
Claire 54a07731d1 Fix posts from threads received out-of-order sometimes not being inserted into timelines (#27653) 2023-12-04 15:28:15 +01:00
Claire 81d7cfd544 Fix posts from force-sensitized accounts being able to trend (#27620) 2023-12-04 15:28:15 +01:00
Claire e6f4c91c5c Fix hashtag matching pattern matching some URLs (#27584) 2023-12-04 15:28:15 +01:00
Claire de86e822f4 Fix error when trying to delete already-deleted file with OpenStack Swift (#27569) 2023-12-04 15:28:15 +01:00
Claire 4c38706474 Fix batch attachment deletion when using OpenStack Swift (#27554) 2023-12-04 15:28:15 +01:00
Renaud Chaput 4fc2523546 Do not display the navigation banner in the logo container (#27476) 2023-12-04 15:28:15 +01:00
Renaud Chaput d5bc10b711 The `class` props should be `className` (#27462) 2023-12-04 15:28:15 +01:00
Claire c66ade7de8 Fix processing LDSigned activities from actors with unknown public keys (#27474) 2023-12-04 15:28:15 +01:00
Claire bece853e3c Fix error and incorrect URLs in `/api/v1/accounts/:id/featured_tags` for remote accounts (#27459) 2023-12-04 15:28:15 +01:00
Claire 700ae1f918 Fix report processing notice not mentioning the report number when performing a custom action (#27442) 2023-12-04 15:28:15 +01:00
Claire 13205b54fd Fix handling of `inLanguage` attribute in preview card processing (#27423) 2023-12-04 15:28:15 +01:00
KMY(雪あすか) 8be33d4316 Fix when unfollow a tag, my post also disappears from the home timeline (#27391) 2023-12-04 15:28:15 +01:00
Claire cdedae6d63 Fix some link anchors being recognized as hashtags (#27271) 2023-12-04 15:28:15 +01:00
Claire aa69ca74ed Fix incorrect serialization of regional languages in `contentMap` (#27207) 2023-12-04 15:28:15 +01:00
gunchleoc 156d32689b Only strip country code when language not listed in SUPPORTED_LOCALES (#27099) 2023-12-04 15:28:15 +01:00
Claire ef149674f0 Change Content-Security-Policy to be tighter on media paths (#26889) 2023-12-04 15:28:15 +01:00
Claire eea2654236
Fix format-dependent redirects being cached regardless of requested format (#27634) 2023-11-13 17:58:00 +01:00
Claire 74dd325112
Fix duplicate reports being sent when reporting some remote posts (port to v4.2.1) (#27356) 2023-10-10 18:23:31 +02:00
Claire 790fd1374f Bump version to v4.2.1 2023-10-10 13:52:41 +02:00
Claire a1f7d2d19a Fix scroll position in thread view reseting when closing a modal (#27350) 2023-10-10 13:52:41 +02:00
github-actions[bot] 4262cfbe41 New Crowdin Translations (automated) (#27347)
Co-authored-by: GitHub Actions <noreply@github.com>
2023-10-10 13:52:41 +02:00
Claire bcfc3b3f65 Fix clicking on already-loaded thread scrolling to the top of the thread (#27338) 2023-10-10 13:52:41 +02:00
Claire 6dcccd325f Fix clicking on already-opened thread post scrolling to the top of the thread (#27331) 2023-10-10 13:52:41 +02:00
github-actions[bot] 5a33b81479 New Crowdin Translations (automated) (#27321)
Co-authored-by: GitHub Actions <noreply@github.com>
2023-10-10 13:52:41 +02:00
Andy Piper 8f55224307 Add 4.2.x to supported versions in SECURITY.md (#27317) 2023-10-10 13:52:41 +02:00
Claire f71b7943f9 Fix some remote posts getting truncated (#27307) 2023-10-10 13:52:41 +02:00
github-actions[bot] 2e2936eb64 New Crowdin Translations (automated) (#27304)
Co-authored-by: GitHub Actions <noreply@github.com>
2023-10-10 13:52:41 +02:00
renovate[bot] f4b0a10490 Update dependency sidekiq to v6.5.10 (#27287)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-10-10 13:52:41 +02:00
Claire b9b8eafc98 Fix auto-loading-more when not scrolled (#27286) 2023-10-10 13:52:41 +02:00
github-actions[bot] 88fc73dbbc New Crowdin Translations (automated) (#27277)
Co-authored-by: GitHub Actions <noreply@github.com>
2023-10-10 13:52:41 +02:00
github-actions[bot] aba0c5abd9 New Crowdin Translations (automated) (#27270)
Co-authored-by: GitHub Actions <noreply@github.com>
2023-10-10 13:52:41 +02:00
Claire ffcf2c691e Fix Vary headers not being set on some redirects (#27272) 2023-10-10 13:52:41 +02:00
Matt Jankowski a9588065b2 Dont match mention in url query string (#25656)
Co-authored-by: Claire <claire.github-309c@sitedethib.com>
2023-10-10 13:52:41 +02:00
Claire 3e21780cf1 Fix double scroll bars in some columns in advanced interface (#27187) 2023-10-10 13:52:41 +02:00
Claire 0619ec1592 Fix boosts of local users being filtered in account timelines (#27204) 2023-10-10 13:52:41 +02:00
Claire 451884a36b Add a short-lived lock to trend refresh scheduler (#27253) 2023-10-10 13:52:41 +02:00
Michael Stanclift aa4c4f5737 Keep version string displayed without breakpoints in UI (#26986) 2023-10-10 13:52:41 +02:00
David Aaron 82502f54ac Change min age of backup policy from 1 week to 6 days (#27200) 2023-10-10 13:52:41 +02:00
Jakob Gillich 16dcdfcb4e Fix importer returning negative row estimates (#27258) 2023-10-10 13:52:41 +02:00
github-actions[bot] 7c6f41039d New Crowdin Translations (automated) (#27260)
Co-authored-by: GitHub Actions <noreply@github.com>
2023-10-10 13:52:41 +02:00
Claire 6ba4b208b8 Change some worker lock TTLs (#27246) 2023-10-10 13:52:41 +02:00
Claire 8a6fa34040 Fix incorrectly keeping outdated update notices absent from the API endpoint (#27021) 2023-10-10 13:52:41 +02:00
Claire cfd2c6e28d Fix import progress not updating on certain failures (#27247) 2023-10-10 13:52:41 +02:00
github-actions[bot] 7b86708980 New Crowdin Translations (automated) (#27220)
Co-authored-by: GitHub Actions <noreply@github.com>
2023-10-10 13:52:41 +02:00
Emelia Smith ccb980beac Fix websocket connections being incorrectly decremented twice on errors (#27238) 2023-10-10 13:52:41 +02:00
Claire ac32f4b3c3 Fix explore prompt appearing because of posts being received out of order (#27211) 2023-10-10 13:52:41 +02:00
github-actions[bot] 2cd969cca7 New Crowdin Translations (automated) (#27202)
Co-authored-by: GitHub Actions <noreply@github.com>
2023-10-10 13:52:41 +02:00
Claire 4e420d8459 Fix link handling of mentions in user profiles when logged out (#27185) 2023-10-10 13:52:41 +02:00
Claire 8bc5fe204e Fix filtering audit log for entries about disabling 2FA (#27186) 2023-10-10 13:52:41 +02:00
github-actions[bot] 4e5791bba1 New Crowdin Translations (automated) (#27168)
Co-authored-by: GitHub Actions <noreply@github.com>
2023-10-10 13:52:41 +02:00
Christian Schmidt 11f0b6bc7e Make notification respect reduce-motion (#27178) 2023-10-10 13:52:41 +02:00
Michael Stanclift 91047c36b5 Fix retention dashboard not displaying correct month (#27180) 2023-10-10 13:52:41 +02:00
Claire 6a3d09dde2 Update `tootctl maintenance fix-duplicates` to Mastodon v4.2.0 (#27147) 2023-10-10 13:52:41 +02:00
github-actions[bot] aed930b629 New Crowdin Translations (automated) (#27144)
Co-authored-by: GitHub Actions <noreply@github.com>
2023-10-10 13:52:41 +02:00
Essem 2191858cff Properly remove tIME chunk from PNG uploads (#27111) 2023-10-10 13:52:41 +02:00
Claire 916b5bd4ad Fix division by zero in video in bitrate computation code (#27129) 2023-10-10 13:52:41 +02:00
Claire 12bbccbe82 Fix explore prompt sometimes showing up when the home TL is loading (#27062) 2023-10-10 13:52:41 +02:00
Claire 6c25730024 Add redirection on `/deck` URLs for logged-out users (#27128) 2023-10-10 13:52:41 +02:00
Claire fa98c9b077 Fix crash when filtering for “dormant” relationships (#27306) 2023-10-10 13:52:41 +02:00
Claire 58477a6163 Fix inefficient queries in “Follows and followers” as well as several admin pages (#27116) 2023-10-10 13:52:41 +02:00
github-actions[bot] 9cb7fa57f6 New Crowdin Translations (automated) (#27080)
Co-authored-by: GitHub Actions <noreply@github.com>
2023-10-10 13:52:41 +02:00
Claire 8b382b8df7 Fix ActiveRecord using two connection pools when no replica is defined (#27061) 2023-10-10 13:52:41 +02:00
github-actions[bot] 40702a81fa New Crowdin Translations (automated) (#27052)
Co-authored-by: GitHub Actions <noreply@github.com>
2023-10-10 13:52:41 +02:00
Renaud Chaput 238a17b145 Fix the search documentation URL in system checks (#27036) 2023-10-10 13:52:41 +02:00
Claire 4fcc026f0f
Bump version to v4.2.0 (#27017) 2023-09-21 16:51:32 +02:00
Eugen Rochko 0fbefb6f67
Update AUTHORS.md (#27018) 2023-09-21 14:42:15 +02:00
Claire 8acc75435b
Change S3 checksum mode to be disabled by default (#27007) 2023-09-21 14:00:51 +02:00
aaaaalbert bb6c59a399
Fix #27010: update the copyright year in README (#27016) 2023-09-21 13:57:24 +02:00
Claire 5356ddbcca
Fix search results temporarily disappearing while scrolling and loading more results (#27014) 2023-09-21 13:51:21 +02:00
Claire 890e334703
Add migration tests for negative settings (#27012) 2023-09-21 12:33:30 +02:00
renovate[bot] c74670b4d3
Update dependency glob to v10.3.5 (#27002)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-09-21 11:06:16 +02:00
github-actions[bot] effe4728cf
New Crowdin Translations (automated) (#27005)
Co-authored-by: GitHub Actions <noreply@github.com>
2023-09-21 11:06:04 +02:00
renovate[bot] 4ed9d9ca6f
Update dependency autoprefixer to v10.4.16 (#27000)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-09-21 11:00:15 +02:00
Claire 828eebad48
Add `hide_collections`, `discoverable` and `indexable` attributes to credentials API (#26998) 2023-09-20 18:25:16 +02:00
github-actions[bot] 61fe25fe74
New Crowdin Translations (automated) (#26988)
Co-authored-by: GitHub Actions <noreply@github.com>
2023-09-20 13:05:56 +02:00
Claire 889c4d4bbb
Update changelog (#26992) 2023-09-20 12:12:19 +02:00
Claire abcc0b38fa
Fix incorrect PostgreSQL version check (#26979) 2023-09-19 17:31:58 +02:00
Claire f4b780ba22
Bump version to v4.2.0-rc2 (#26974) 2023-09-19 17:04:23 +02:00
Claire eeab3560fc
Merge pull request from GHSA-v3xf-c9qf-j667 2023-09-19 16:53:58 +02:00
Claire ff32475f5f
Merge pull request from GHSA-2693-xr3m-jhqr 2023-09-19 16:53:21 +02:00
Claire 94893cf24f
Merge pull request from GHSA-hcqf-fw2r-52g4
* Revert "Fix request URL normalisation for bare domain and 8-bit characters (#26285)"

This reverts commit 8891d8945d.

* Revert "Do not normalize URL before fetching it (#26219)"

This reverts commit fd284311e7.
2023-09-19 16:52:52 +02:00
github-actions[bot] 73ecc4de6e
New Crowdin Translations (automated) (#26978)
Co-authored-by: GitHub Actions <noreply@github.com>
2023-09-19 16:26:51 +02:00
renovate[bot] a83615edc9
Update eslint (non-major) (#26954)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-09-19 12:51:13 +02:00
Eugen Rochko 0f3f9b611f
Change video bitrate to always fit within size limit (#26970) 2023-09-19 12:25:58 +02:00
Claire 70cae19b6b
Fix hashtag bar being sometimes incorrectly hidden (#26960) 2023-09-19 12:25:39 +02:00
Emelia Smith fbfceb9c77
Add additional metrics for streaming (#26945) 2023-09-19 12:25:30 +02:00
renovate[bot] 6eb6209d02
Update DefinitelyTyped types (non-major) (#26953)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-09-19 12:24:26 +02:00
renovate[bot] c6a535a197
Update dependency node to 20.7 (#26963)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-09-19 11:16:45 +02:00
renovate[bot] 6f9f901153
Update dependency postcss to v8.4.30 (#26964)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-09-19 11:03:42 +02:00
github-actions[bot] 67eaaa4b90
New Crowdin Translations (automated) (#26966)
Co-authored-by: GitHub Actions <noreply@github.com>
2023-09-19 10:41:48 +02:00
Rob Thomas 200312e8be
Fix crash when viewing a moderation appeal and the moderator account has been deleted (#25900)
Co-authored-by: Claire <claire.github-309c@sitedethib.com>
2023-09-18 17:41:32 +02:00
github-actions[bot] 1b4902fabf
New Crowdin Translations (automated) (#26913)
Co-authored-by: GitHub Actions <noreply@github.com>
2023-09-18 13:46:07 +02:00
Claire 4c1518a6f3
Fix Web UI making duplicate search queries when scrolling (#26959) 2023-09-18 11:20:09 +02:00
Claire 893755f4cb
Fix error in Web UI when server rules cannot be fetched (#26957) 2023-09-18 09:51:52 +02:00
Claire 392c07f2bf
Fix `Setting.authorized_fetch` not being properly taken into consideration (#26958) 2023-09-18 09:37:29 +02:00
renovate[bot] 8b5c61ae3a
Update formatjs monorepo (#26955)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-09-18 08:52:49 +02:00
renovate[bot] 04623e2f34
Update babel monorepo to v7.22.20 (#26946)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-09-18 08:34:04 +02:00
Claire 6273416292
Fix post edits not being forwarded as expected (#26936) 2023-09-15 19:54:32 +02:00
Claire 2a4fcc51fd
Fix dismiss button overlapping with text in dismissable banners (#26935) 2023-09-15 19:53:28 +02:00
Claire e4f5114aaf
Fix obsolete cache key in status cache invalidation logic (#26934) 2023-09-15 19:52:28 +02:00
Renaud Chaput 5d93e98da4
Fix container build when cache is disabled (#26932) 2023-09-15 10:44:01 +02:00
Claire 9693c271f1
Fix processing of `min_id` and `max_id` parameters in `/api/v2/search` (#26927) 2023-09-15 10:23:24 +02:00
renovate[bot] 82015dbab6
Update dependency sass to v1.67.0 (#26914)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-09-15 10:03:55 +02:00
renovate[bot] 3103415364
Update dependency @babel/core to v7.22.19 (#26920)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-09-15 09:09:22 +02:00
Renaud Chaput ef8ca2fd72
Change Dockerfile to upgrade packages when building (#26922) 2023-09-14 23:11:36 +02:00
Claire b90383d073
Bump version to v4.2.0-rc1 (#26916) 2023-09-14 18:46:00 +02:00
Eugen Rochko fc6825055b
Change score half-life for trending posts from 2 hours to 1 hour (#26915) 2023-09-14 09:20:14 +02:00
Claire a04ae16201
Fix CSP when using `ONE_CLICK_SSO_LOGIN` (#26901) 2023-09-13 19:54:04 +02:00
github-actions[bot] 921c6fe654
New Crowdin Translations (automated) (#26498)
Co-authored-by: GitHub Actions <noreply@github.com>
2023-09-13 15:10:41 +02:00
Robert R George 20666482ef
Added admin api for managing tags (#26872) 2023-09-13 11:22:53 +02:00
renovate[bot] 97e4011c3c
Update dependency rails to v7.0.8 (#26907)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-09-13 10:01:31 +02:00
Claire 520b570474
Fix notification permissions being requested immediately after login (#26472) 2023-09-12 18:27:01 +02:00
Claire 287520453c
Fix front-end bug when processing relationship-related account actions (#26902) 2023-09-12 16:30:03 +02:00
CSDUMMI 9a70cac9de
Fix #26849 by adding the domain of the current SSO provider to the form-action CSP (#26857) 2023-09-12 13:04:51 +02:00
renovate[bot] 93223633fc
Update dependency uuid to v9.0.1 (#26900)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-09-12 12:38:52 +02:00
Renaud Chaput 0712cc2b99
Fix Redux types (#26899) 2023-09-12 12:18:19 +02:00
renovate[bot] 9ac9aca142
Update dependency test-prof to v1.2.3 (#26894)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-09-12 10:45:54 +02:00
Jeong Arm 59af3c1310
Add PublicStatuses to tootctl search deploy --only option (#26896) 2023-09-12 09:59:59 +02:00
Santiago Kozak d2cfcdd09e
Fix recent search items not handling overflows correctly (#26893) 2023-09-12 04:20:57 +02:00
Claire 20ac5be1c1
Fix error on `status` type notification (bell icon) (#26884) 2023-09-11 20:23:13 +02:00
Renaud Chaput bd06c13204
Convert `actions/account_notes` into Typescript (#26601) 2023-09-11 16:09:22 +02:00
renovate[bot] 7730083611
Update dependency kt-paperclip to v7.2.1 (#26869)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-09-11 11:51:42 +02:00
renovate[bot] f3be4eb0dc
Update dependency @rails/ujs to v7.0.8 (#26871)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-09-11 11:32:30 +02:00
renovate[bot] 3679e67fad
Update dependency @babel/core to v7.22.17 (#26855)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-09-11 11:12:22 +02:00
renovate[bot] c10142ac3c
Update formatjs monorepo (#26874)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-09-11 10:54:59 +02:00
renovate[bot] 3d0331fc7a
Update actions/checkout action to v4 (#26882)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-09-11 10:41:26 +02:00
renovate[bot] b1d8907138
Update dependency @types/lodash to v4.14.198 (#26878)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-09-11 10:26:23 +02:00
renovate[bot] f333d1822b
Update docker.elastic.co/elasticsearch/elasticsearch Docker tag to v7.17.13 (#26879)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-09-11 10:26:06 +02:00
renovate[bot] 959ccf5682
Update eslint (non-major) (#26881)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-09-11 10:25:54 +02:00
renovate[bot] b98edfa4ba
Update dependency rubocop to v1.56.3 (#26883)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-09-11 10:11:59 +02:00
renovate[bot] d5155cbc21
Update dependency ws to v8.14.1 (#26862)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-09-11 09:59:18 +02:00
Claire 91040da871
Fix confusing behavior of mute button and volume slider in web UI (#26860) 2023-09-08 20:39:29 +02:00
Claire 33c8708a1a
Change `GET /api/v1/directory` to use database replica rather than primary (#26856) 2023-09-08 17:01:02 +02:00
jsgoldstein 4d9186a48c
Add search tests (#26703) 2023-09-08 16:17:55 +02:00
Eugen Rochko 3a679844e4
Fix `account_id`, `max_id` and `min_id` params not working in search (#26847) 2023-09-08 14:25:00 +02:00
Renaud Chaput e9b528eaee
Use NodeJS v20 by default (#26830) 2023-09-08 13:45:34 +02:00
renovate[bot] 712d96b207
Update dependency ws to v8.14.0 (#26821)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-09-08 12:43:21 +02:00
renovate[bot] d0f7d879a6
Update formatjs monorepo (#26844)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-09-08 12:42:55 +02:00
Claire 81caafbe84
Fix performances of profile directory (#26842) 2023-09-07 18:55:25 +02:00
renovate[bot] 858ad1f363
Update dependency core-js to v3.32.2 (#26841)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-09-07 16:49:31 +02:00
Claire 355e3fb529
Simplify `Account.by_recent_status` and `Account.by_recent_sign_in` scopes (#26840) 2023-09-07 15:38:11 +02:00
renovate[bot] b9e2eb5184
Update dependency @material-design-icons/svg to v0.14.12 (#26832)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-09-07 14:59:42 +02:00
Eugen Rochko 9b2bc3d1de
Add recent searches in web UI (#26834) 2023-09-07 14:56:19 +02:00
Eugen Rochko a90b0056cc
Fix `#hashtag` matching on individual components in search (#26835) 2023-09-07 12:53:41 +02:00
Colette Kerr 8a9d7aeb1e
Change DCT method used for JPEG encoding to float (#26675) 2023-09-07 05:26:25 +02:00
Eugen Rochko f3a2e15f8e
Fix mute button and volume slider feeling disconnected in web UI (#26827) 2023-09-07 04:24:24 +02:00
Eugen Rochko 1f141f656d
Change onboarding prompt to use full width of banner in web UI (#26829) 2023-09-07 04:24:11 +02:00
Eugen Rochko 398635c0c4
Fix paragraph margins resulting in irregular read-more cut-off in web UI (#26828) 2023-09-07 04:24:01 +02:00
renovate[bot] 9e3567bfbe
Update dependency webpack to v4.47.0 (#26825)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-09-06 22:25:03 +02:00
Claire 1f99d86287
Fix blocked domain appears from account feed (#26823)
Co-authored-by: Jeong Arm <kjwonmail@gmail.com>
Co-authored-by: Eugen Rochko <eugen@zeonfederated.com>
2023-09-06 21:04:48 +02:00
gunchleoc 14f6798836
Link to hashtag timelines from the Trending hashtags moderation interface (#26724)
Co-authored-by: Claire <claire.github-309c@sitedethib.com>
2023-09-06 17:49:52 +02:00
Claire 475783d567
Add timezone to datetimes in e-mails (#26822) 2023-09-06 17:25:39 +02:00
Claire 9c1ef8302a
Add some information about Full-text search being disabled in search popout (#26756) 2023-09-06 17:23:58 +02:00
Claire b83e487502
Fix moderator rights inconsistencies (#26729) 2023-09-06 16:40:19 +02:00
Claire 93d051e47d
Update FEDERATION.md (#26819) 2023-09-06 16:28:31 +02:00
Claire 223f9ca665
Fix crash when encountering invalid URL (#26814) 2023-09-06 12:17:51 +02:00
Claire 09ec9c6aa5
Downgrade signature verification debug logging from `warn` to `debug` (#26812) 2023-09-06 12:17:22 +02:00
Claire d881988372
Update supported versions in SECURITY.md (#26811) 2023-09-06 10:00:25 +02:00
renovate[bot] 8c321b8c3a
Update dependency pghero to v3.3.4 (#26808)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-09-06 09:19:43 +02:00
Claire ec48bc3610
Do not truncate backtrace when the `BACKTRACE` env variable is set (#26794) 2023-09-06 09:18:10 +02:00
Eugen Rochko d8bdba2f9f
Add `in:` to search popout in web UI (#26807) 2023-09-06 06:46:26 +02:00
Eugen Rochko 9d290c23d2
Remove obfuscation of reply count in web UI (#26768) 2023-09-05 23:57:03 +02:00
Eugen Rochko 5d20733d8d
Add infinite scrolling for search results in web UI (#26784) 2023-09-05 23:54:24 +02:00
Claire 548c032dbb
Improve interaction modal error handling (#26795) 2023-09-05 23:49:48 +02:00
Claire ea7de25de0
Fix video player not being displayed in reports interface (#26801) 2023-09-05 20:05:58 +02:00
Michael Stanclift b749de766f
Migrate Dockerfile to Bookworm (#26802) 2023-09-05 18:19:59 +02:00
Claire cab4cbfa5c
Fix “Scoped order is ignored, it's forced to be batch order.” warnings (#26793) 2023-09-05 15:37:23 +02:00
Claire f80f426c57
Bump version to v4.2.0-beta3 (#26753) 2023-09-04 21:20:20 +02:00
renovate[bot] ddeca3b37b
Update babel monorepo to v7.22.15 (#26790)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-09-04 17:56:47 +02:00
Claire cddef4c485
Fix language settings for users having selected the `kmr` language (#26787) 2023-09-04 17:56:31 +02:00
Eugen Rochko ece1ff77d6
Add `in:library` syntax to search (#26760)
Co-authored-by: Claire <claire.github-309c@sitedethib.com>
2023-09-04 17:20:35 +02:00
gunchleoc ac3f310f4b
Remove kmr from language selection (#26014) 2023-09-04 13:21:43 +02:00
renovate[bot] 3e6a6439b5
Update dependency stylelint-config-standard-scss to v11 (#26780)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-09-04 11:02:36 +02:00
renovate[bot] 59361dfde2
Update dependency rubocop to v1.56.2 (#26568)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-09-04 11:00:55 +02:00
Eugen Rochko 68b4e36c82
Fix `#hashtag` matching non-hashtagged posts in search (#26781) 2023-09-04 10:18:45 +02:00
Stanislas Signoud a106c46478
Fix light mode colors for advanced interface banner (#26759) 2023-09-04 10:14:01 +02:00
Christian Schmidt ea31929776
Fix invalid Content-Type header for WebP images (#26773) 2023-09-04 09:46:33 +02:00
renovate[bot] 1f92436745
Update DefinitelyTyped types (non-major) (#26785)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-09-04 09:26:33 +02:00
Eugen Rochko e52d0494ee
Fix `before:`, `after:` and `during:` failing when time zone not set (#26782) 2023-09-04 09:09:25 +02:00
renovate[bot] 173041f02c
Update dependency typescript to v5.2.2 (#26786)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-09-04 09:08:53 +02:00
Eugen Rochko 728eb6a153
Fix wrong color on active icons with counters in web UI (#26767) 2023-09-02 14:46:26 +02:00
Eugen Rochko 05093266e6
Fix some video encoding failing due to uneven dimensions (#26766) 2023-09-02 09:02:44 +02:00
Claire 16681e0f20
Add admin notifications for new Mastodon versions (#26582) 2023-09-01 17:47:07 +02:00
Gabriel Simmer be991f1d18
Move to ioredis for streaming (#26581)
Co-authored-by: Emelia Smith <ThisIsMissEm@users.noreply.github.com>
2023-09-01 17:44:28 +02:00
Claire 9e26cd5503
Add `authorized_fetch` server setting in addition to env var (#25798) 2023-09-01 15:41:10 +02:00
Claire 6c4c72497a
Fix search popout including full-text search instructions when full-text search is disabled (#26755) 2023-09-01 15:13:27 +02:00
Claire 5c0a9aac3b
Revert to using primary database in IndexingScheduler (#26754) 2023-09-01 15:09:44 +02:00
renovate[bot] bb0edb178f
Update dependency pg to v1.5.4 (#26750)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-09-01 10:48:49 +02:00
renovate[bot] 630e558677
Update dependency oj to v3.16.1 (#26749)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-09-01 10:17:08 +02:00
Eugen Rochko e754083e8a
Fix unmatched quotes and prefixes causing search to fail (#26701) 2023-09-01 09:43:12 +02:00
Eugen Rochko 872145d1c2
Fix not being able to invoke phrase search using unicode quotation marks (#26687) 2023-09-01 09:27:03 +02:00
Eugen Rochko 0008458128
Fix search queries with slash causing or-condition (#26699) 2023-08-31 19:04:44 +02:00
Claire 9bb2fb6b14
Change importers to avoid a few inefficiencies (#26721) 2023-08-31 19:04:27 +02:00
Eugen Rochko ecd76fa413
Fix videos not playing in some browsers due to unsupported color space (#26745) 2023-08-31 17:21:06 +02:00
Claire 1471be8225
Fix `AddUniqueIndexOnPreviewCardsStatuses` migration requiring PostgreSQL 12+ in some cases (#26737) 2023-08-31 15:47:30 +02:00
Claire 6b58cfd8dd
Fix searching by username by reverting account verbatim tokenizer to `standard` (#26739) 2023-08-31 15:35:58 +02:00
Renaud Chaput cffc5d2b01
Fix comment in build image workflow (#26740) 2023-08-31 14:59:50 +02:00
Santiago Kozak f1d250135c
Allow filter form in profiles directory to wrap (#26682) 2023-08-31 14:15:58 +02:00
Daniel M Brasil ccca542db1
Fix `/api/v1/timelines/tag/:hashtag` allowing for unauthenticated access when public preview is disabled (#26237) 2023-08-31 13:53:24 +02:00
Tyler Deitz 336ec503c2
Add avatar image to webfinger responses (#26558) 2023-08-31 13:46:27 +02:00
Stanislas Signoud 40b69cc1cd
Add an explanation banner on switching to single column mode (#26019) 2023-08-31 12:18:46 +02:00
gunchleoc cb9f96036c
Add suggestion for secure cyphers to nginx.conf (#26349) 2023-08-31 12:17:10 +02:00
renovate[bot] 430eac3eb1
Update dependency @babel/preset-env to v7.22.14 (#26732)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-08-31 10:23:24 +02:00
renovate[bot] ef9a85a2d8
Update dependency webpack-bundle-analyzer to v4.9.1 (#26733)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-08-31 09:52:23 +02:00
renovate[bot] 15949e42c2
Update dependency glob to v10.3.4 (#26734)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-08-31 09:49:34 +02:00
Claire 5c38c3a9a1
Change text extraction in `PlainTextFormatter` to be faster (#26727) 2023-08-30 17:36:16 +02:00
Claire 21ec596dab
Fix sign up steps progress layout in right-to-left locales (#26728) 2023-08-30 16:29:52 +02:00
Eugen Rochko bba76e7267
Change `language:` to attempt to match to a known value in search (#26663) 2023-08-30 16:07:26 +02:00
renovate[bot] 0e1bff178e
Update dependency chewy to v7.3.4 (#26717)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-08-30 09:45:13 +02:00
renovate[bot] 24deaf2e4a
Update dependency postcss to v8.4.29 (#26720)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-08-30 09:44:14 +02:00
renovate[bot] 168688ef1c
Update dependency webmock to v3.19.1 (#26722)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-08-30 09:34:07 +02:00
Claire 9e77ab7db2
Change private statuses index to index without crutches (#26713) 2023-08-29 17:51:13 +02:00
Tim Rogers 74eb7dbf2d
Fix bug with reblogged view on Toots only showing latest reblogging accounts (#26574) 2023-08-29 14:42:20 +02:00
Claire c0605747ad
Fix N+1 in `tootctl search deploy` (#26710) 2023-08-29 14:06:22 +02:00
Tim Rogers ae6cf33321
Fix bug with favourited view on Toots only showing latest favouriting accounts (#26577) 2023-08-29 10:56:19 +02:00
renovate[bot] 4ad1c5aa71
Update dependency aws-sdk-s3 to v1.133.0 (#26616)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-08-29 10:53:01 +02:00
renovate[bot] a67cf439ee
Update dependency axios to v1.5.0 (#26680)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-08-29 10:50:27 +02:00
Claire 25bf640629
Add debug logging on signature verification failure (#26637) 2023-08-29 10:29:07 +02:00
Christian Schmidt 075cc8e8a6
Improve Codespaces port forwarding (#26400) 2023-08-29 10:20:36 +02:00
Christian Schmidt 286a21afdc
Support webpacker live-reloading on Docker (#26419) 2023-08-29 10:17:57 +02:00
Christian Schmidt 0719216368
Remove dead code from public.jsx (#26547) 2023-08-29 10:16:18 +02:00
Lukas Martini a7d96e6aff
Improve error messages when DeepL quota is exceeded (#26704) 2023-08-29 09:14:44 +02:00
Eugen Rochko 10b06436d1
Fix colors and typography on hashtag bar in web UI (#26666) 2023-08-28 19:49:38 +02:00
Eugen Rochko 01b87a1632
Change video compression parameters (#26631) 2023-08-28 19:40:08 +02:00
renovate[bot] dd72a8d28b
Update dependency stoplight to v3.0.2 (#26698)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-08-28 17:10:58 +02:00
Eugen Rochko 2304cc6456
Fix bad search type heuristic (#26673) 2023-08-28 17:08:37 +02:00
Claire 0cce7fb617
Fix incorrect call to `PublicStatusesIndex.import` (#26697) 2023-08-28 15:04:57 +02:00
Eugen Rochko bceb893159
Add search options to search popout in web UI (#26662) 2023-08-28 13:18:39 +02:00
Eugen Rochko 67166de865
Add `from:me` syntax to search (#26660) 2023-08-28 12:43:00 +02:00
renovate[bot] 9d9de8d219
Update dependency @testing-library/jest-dom to v6 (#26479)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Renaud Chaput <renchap@gmail.com>
2023-08-28 12:19:42 +02:00
Eugen Rochko 7bd5ebb0c5
Fix multiple issues with status index mappings (#26686) 2023-08-28 11:36:17 +02:00
renovate[bot] 4ea5db90da
Update DefinitelyTyped types (non-major) (#26693)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-08-28 11:04:06 +02:00
renovate[bot] b42fe5e338
Update dependency haml_lint to v0.50.0 (#26665)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-08-28 10:32:13 +02:00
Eugen Rochko 822a35b9d5
Fix not being able to negate prefix clauses in search (#26672) 2023-08-28 10:31:51 +02:00
renovate[bot] fc14d1f3b0
Update eslint (non-major) (#26694)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-08-28 10:20:02 +02:00
Eugen Rochko e263db276f
Change indexing jobs to use database replica (#26692) 2023-08-27 22:38:01 +02:00
Eugen Rochko 5694e24bbf
Fix unnecessary condition causing seqscan when indexing (#26689) 2023-08-27 22:37:35 +02:00
Eugen Rochko f8d2fea2e6
Change queue of job when opting into search from `default` to `pull` (#26688) 2023-08-27 18:58:20 +02:00
Claire 925c16adea
Fix nightly build version (#26676) 2023-08-26 17:34:36 +02:00
Eugen Rochko 71641766f2
Fix toast saying "published" instead of "saved" after editing post in web UI (#26664) 2023-08-25 22:03:04 +02:00
Claire f39847476c
Change the pre-release versioning scheme and associated environment variables (#26653) 2023-08-25 18:26:44 +02:00
Claire 072112867b
Fix dashboard check for Elasticsearch suggested command including incorrect names (#26658) 2023-08-25 18:17:22 +02:00
jsgoldstein 8e8747c564
Update ordering to use `id` from body of document instead of deprecated `_id` (#26659) 2023-08-25 18:16:08 +02:00
renovate[bot] e4c0ce18a3
Update dependency immutable to v4.3.4 (#26655)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-08-25 17:02:33 +02:00
Claire 82ec6f162b
Fix statuses search Elasticsearch query (#26657) 2023-08-25 17:01:56 +02:00
Claire f2ec2876a4
Add Elasticsearch/OpenSearch version to “Software” in admin dashboard (#26652) 2023-08-25 12:02:09 +02:00
Claire e3825a13c1
Add PublicStatusesCheck to Elasticsearch index check on admin dashboard (#26650) 2023-08-25 12:01:55 +02:00
Claire c3a42e1280
Add `data-nosnippet` attribute to remote posts and local posts with `noindex` (#26648) 2023-08-25 11:31:20 +02:00
renovate[bot] c66f756522
Update babel monorepo to v7.22.11 (#26640)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-08-25 10:08:44 +02:00
Claire 389b7d23db
Fix changelog wording and missing items (#26638) 2023-08-24 21:07:39 +02:00
jsgoldstein 30c191aaa0
Add new public status index (#26344)
Co-authored-by: Eugen Rochko <eugen@zeonfederated.com>
Co-authored-by: Claire <claire.github-309c@sitedethib.com>
2023-08-24 16:40:04 +02:00
Claire 96bcee66fb
Change nightlies versioning from `v4.2.0+2023-08-23` to `v4.2.0-nightly.2023-08-23` (#26626) 2023-08-24 14:43:44 +02:00
Claire 163b004bb1
Change admin e-mail notification settins to be their own settings group (#26596) 2023-08-24 14:43:00 +02:00
Claire e90649b064
Fix clicking “Explore” or “Live feeds” column headers to scroll in advanced mode (#26633)
Co-authored-by: Plastikmensch <Plastikmensch@users.noreply.github.com>
2023-08-24 14:10:48 +02:00
Claire b2ac93dd73
Fix selecting domains to forward reports to not passing the information correctly (#26636) 2023-08-24 14:06:27 +02:00
Jaehong Kang 2dbbeedc94
Fix: Workaround to load MASTODON_VERSION_* in Docker. (#26591)
Co-authored-by: Claire <claire.github-309c@sitedethib.com>
2023-08-24 13:07:27 +02:00
renovate[bot] 24ea6f851f
Update dependency redis to v4.6.8 (#26630)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-08-24 09:59:13 +02:00
Matt Jankowski 9974163776
Consolidate inclusion of `admin` js pack link (#26628) 2023-08-24 09:56:23 +02:00
renovate[bot] e3fd071973
Update dependency rspec-sidekiq to v4 (#26627)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-08-24 09:03:40 +02:00
Antonin Del Fabbro 9a8190da4a
Change opacity of the delete icon in the search field to be more visible (#26449) 2023-08-23 15:51:07 +02:00
Daniel M Brasil f337008819
Fix timeout on invalid set of exclusionary parameters in `/api/v1/timelines/public` (#26239) 2023-08-23 15:50:23 +02:00
jsgoldstein b91724fb9d
Add elastic search installation into Vagrantfile (#26512) 2023-08-23 15:46:14 +02:00
renovate[bot] 34f5b90dc7
Update dependency sass to v1.66.1 (#26534)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-08-23 15:45:56 +02:00
renovate[bot] 060b554a9d
Update dependency oj to v3.16.0 (#26520)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-08-23 15:45:38 +02:00
renovate[bot] de8c2427a5
Update dependency immutable to v4.3.3 (#26622)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-08-23 15:45:21 +02:00
Claire 613cfd625c
Change hashtag bar tags to be de-emphasized (#26606) 2023-08-23 15:44:52 +02:00
Christian Schmidt 152b10b624
Fix some React warnings (#26609) 2023-08-23 15:43:41 +02:00
Renaud Chaput 44ba785242
Change the hashtag bar to be hidden when there is a CW and the post is not expanded (#26615) 2023-08-23 15:40:31 +02:00
jsgoldstein 85057865b4
Update Account Search to prioritize username over display name (#26623) 2023-08-23 15:40:09 +02:00
yufushiro 3aac12981c
Fix unexpected audio stream transcoding when uploaded video is eligible to passthrough (#26608)
Co-authored-by: Claire <claire.github-309c@sitedethib.com>
2023-08-23 08:44:56 +02:00
Robert R George cf6f70799b
Add support for federating `memorial` attribute (#26583) 2023-08-23 08:27:24 +02:00
renovate[bot] ea1a221e2d
Update dependency react-textarea-autosize to v8.5.3 (#26607)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-08-23 08:26:30 +02:00
renovate[bot] 74b8b8ea14
Update dependency rails to v7.0.7.2 (#26612)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-08-23 08:22:48 +02:00
Renaud Chaput 58acaa9ae6
Better hashtag normalization when processing a post (#26614) 2023-08-23 08:18:07 +02:00
Claire bd023a2637
Fix admin dashboard check when using Elasticsearch with `ES_PREFIX` (#26605) 2023-08-22 18:51:32 +02:00
gunchleoc 25dc01660d
Add Kalmyk to languages dropdown (#26013) 2023-08-22 17:50:04 +02:00
gunchleoc c01ecd0879
Add cherokee to languages dropdown (#26012) 2023-08-22 16:53:27 +02:00
renovate[bot] dc09c10fa8
Update dependency mime-types to v3.5.1 (#26595)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-08-22 16:47:40 +02:00
Renaud Chaput 3249c06c73
Update `SECURITY.md` to indicate issues can be reported on Github (#26599) 2023-08-22 16:47:19 +02:00
Renaud Chaput bb2db2aec0
Add `circular-dependency-plugin` to detect any circular deps issues (#26600) 2023-08-22 13:24:16 +02:00
Nick Schonning b970ed6098
Update rubocop and rubocop-rspec (#26329) 2023-08-22 09:31:40 +02:00
Claire fe31571965
Fix layout of the closed registrations modal (#26593) 2023-08-22 09:11:59 +02:00
729 changed files with 19416 additions and 7622 deletions

View File

@ -0,0 +1,6 @@
---
ignore:
# devise-two-factor advisory about brute-forcing TOTP
# We have rate-limits on authentication endpoints in place (including second
# factor verification) since Mastodon v3.2.0
- CVE-2024-0227

View File

@ -4,10 +4,6 @@ FROM mcr.microsoft.com/devcontainers/ruby:1-3.2-bullseye
# Install Rails # Install Rails
# RUN gem install rails webdrivers # RUN gem install rails webdrivers
# Default value to allow debug server to serve content over GitHub Codespace's port forwarding service
# The value is a comma-separated list of allowed domains
ENV RAILS_DEVELOPMENT_HOSTS=".githubpreview.dev,.preview.app.github.dev,.app.github.dev"
ARG NODE_VERSION="16" ARG NODE_VERSION="16"
RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1" RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1"

View File

@ -0,0 +1,49 @@
{
"name": "Mastodon on GitHub Codespaces",
"dockerComposeFile": "../docker-compose.yml",
"service": "app",
"workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}",
"features": {
"ghcr.io/devcontainers/features/sshd:1": {}
},
"runServices": ["app", "db", "redis"],
"forwardPorts": [3000, 4000],
"portsAttributes": {
"3000": {
"label": "web",
"onAutoForward": "notify"
},
"4000": {
"label": "stream",
"onAutoForward": "silent"
}
},
"otherPortsAttributes": {
"onAutoForward": "silent"
},
"remoteEnv": {
"LOCAL_DOMAIN": "${localEnv:CODESPACE_NAME}-3000.app.github.dev",
"LOCAL_HTTPS": "true",
"STREAMING_API_BASE_URL": "https://${localEnv:CODESPACE_NAME}-4000.app.github.dev",
"DISABLE_FORGERY_REQUEST_PROTECTION": "true",
"ES_ENABLED": "",
"LIBRE_TRANSLATE_ENDPOINT": ""
},
"onCreateCommand": "git config --global --add safe.directory ${containerWorkspaceFolder}",
"postCreateCommand": ".devcontainer/post-create.sh",
"waitFor": "postCreateCommand",
"customizations": {
"vscode": {
"settings": {},
"extensions": ["EditorConfig.EditorConfig", "webben.browserslist"]
}
}
}

View File

@ -1,5 +1,5 @@
{ {
"name": "Mastodon", "name": "Mastodon on local machine",
"dockerComposeFile": "docker-compose.yml", "dockerComposeFile": "docker-compose.yml",
"service": "app", "service": "app",
"workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}", "workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}",
@ -8,13 +8,23 @@
"ghcr.io/devcontainers/features/sshd:1": {} "ghcr.io/devcontainers/features/sshd:1": {}
}, },
"runServices": ["app", "db", "redis"],
"forwardPorts": [3000, 4000], "forwardPorts": [3000, 4000],
"containerEnv": { "portsAttributes": {
"ES_ENABLED": "", "3000": {
"LIBRE_TRANSLATE_ENDPOINT": "" "label": "web",
"onAutoForward": "notify",
"requireLocalPort": true
},
"4000": {
"label": "stream",
"onAutoForward": "silent",
"requireLocalPort": true
}
},
"otherPortsAttributes": {
"onAutoForward": "silent"
}, },
"onCreateCommand": "git config --global --add safe.directory ${containerWorkspaceFolder}", "onCreateCommand": "git config --global --add safe.directory ${containerWorkspaceFolder}",

View File

@ -25,6 +25,7 @@ services:
command: sleep infinity command: sleep infinity
ports: ports:
- '127.0.0.1:3000:3000' - '127.0.0.1:3000:3000'
- '127.0.0.1:3035:3035'
- '127.0.0.1:4000:4000' - '127.0.0.1:4000:4000'
networks: networks:
- external_network - external_network

View File

@ -2,3 +2,7 @@ VAGRANT=true
LOCAL_DOMAIN=mastodon.local LOCAL_DOMAIN=mastodon.local
BIND=0.0.0.0 BIND=0.0.0.0
DB_HOST=/var/run/postgresql/ DB_HOST=/var/run/postgresql/
ES_ENABLED=true
ES_HOST=localhost
ES_PORT=9200

View File

@ -4,11 +4,16 @@ on:
platforms: platforms:
required: true required: true
type: string type: string
cache:
type: boolean
default: true
use_native_arm64_builder: use_native_arm64_builder:
type: boolean type: boolean
push_to_images: push_to_images:
type: string type: string
version_suffix: version_prerelease:
type: string
version_metadata:
type: string type: string
flavor: flavor:
type: string type: string
@ -22,7 +27,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- uses: docker/setup-qemu-action@v2 - uses: docker/setup-qemu-action@v2
if: contains(inputs.platforms, 'linux/arm64') && !inputs.use_native_arm64_builder if: contains(inputs.platforms, 'linux/arm64') && !inputs.use_native_arm64_builder
@ -74,8 +79,6 @@ jobs:
if: ${{ inputs.push_to_images != '' }} if: ${{ inputs.push_to_images != '' }}
with: with:
images: ${{ inputs.push_to_images }} images: ${{ inputs.push_to_images }}
# Only tag with latest when ran against the latest stable branch
# This needs to be updated after each minor version release
flavor: ${{ inputs.flavor }} flavor: ${{ inputs.flavor }}
tags: ${{ inputs.tags }} tags: ${{ inputs.tags }}
labels: ${{ inputs.labels }} labels: ${{ inputs.labels }}
@ -83,12 +86,14 @@ jobs:
- uses: docker/build-push-action@v4 - uses: docker/build-push-action@v4
with: with:
context: . context: .
build-args: MASTODON_VERSION_SUFFIX=${{ inputs.version_suffix }} build-args: |
MASTODON_VERSION_PRERELEASE=${{ inputs.version_prerelease }}
MASTODON_VERSION_METADATA=${{ inputs.version_metadata }}
platforms: ${{ inputs.platforms }} platforms: ${{ inputs.platforms }}
provenance: false provenance: false
builder: ${{ steps.buildx.outputs.name || steps.buildx-native.outputs.name }} builder: ${{ steps.buildx.outputs.name || steps.buildx-native.outputs.name }}
push: ${{ inputs.push_to_images != '' }} push: ${{ inputs.push_to_images != '' }}
tags: ${{ steps.meta.outputs.tags }} tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }} labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha cache-from: ${{ inputs.cache && 'type=gha' || '' }}
cache-to: type=gha,mode=max cache-to: ${{ inputs.cache && 'type=gha,mode=max' || '' }}

View File

@ -16,9 +16,9 @@ jobs:
env: env:
TZ: Etc/UTC TZ: Etc/UTC
run: | run: |
echo mastodon_version_suffix=nightly-$(date +'%Y-%m-%d')>> $GITHUB_OUTPUT echo mastodon_version_prerelease=nightly.$(date +'%Y-%m-%d')>> $GITHUB_OUTPUT
outputs: outputs:
suffix: ${{ steps.version_vars.outputs.mastodon_version_suffix }} prerelease: ${{ steps.version_vars.outputs.mastodon_version_prerelease }}
build-image: build-image:
needs: compute-suffix needs: compute-suffix
@ -26,11 +26,11 @@ jobs:
with: with:
platforms: linux/amd64,linux/arm64 platforms: linux/amd64,linux/arm64
use_native_arm64_builder: true use_native_arm64_builder: true
cache: false
push_to_images: | push_to_images: |
tootsuite/mastodon tootsuite/mastodon
ghcr.io/mastodon/mastodon ghcr.io/mastodon/mastodon
# The `+` is important here, result will be v4.1.2+nightly-2022-03-05 version_prerelease: ${{ needs.compute-suffix.outputs.prerelease }}
version_suffix: +${{ needs.compute-suffix.outputs.suffix }}
labels: | labels: |
org.opencontainers.image.description=Nightly build image used for testing purposes org.opencontainers.image.description=Nightly build image used for testing purposes
flavor: | flavor: |
@ -38,5 +38,5 @@ jobs:
tags: | tags: |
type=raw,value=edge type=raw,value=edge
type=raw,value=nightly type=raw,value=nightly
type=schedule,pattern=${{ needs.compute-suffix.outputs.suffix }} type=schedule,pattern=${{ needs.compute-suffix.outputs.prerelease }}
secrets: inherit secrets: inherit

View File

@ -18,12 +18,12 @@ jobs:
steps: steps:
# Repository needs to be cloned so `git rev-parse` below works # Repository needs to be cloned so `git rev-parse` below works
- name: Clone repository - name: Clone repository
uses: actions/checkout@v3 uses: actions/checkout@v4
- id: version_vars - id: version_vars
run: | run: |
echo mastodon_version_suffix=+pr-${{ github.event.pull_request.number }}-$(git rev-parse --short HEAD) >> $GITHUB_OUTPUT echo mastodon_version_metadata=pr-${{ github.event.pull_request.number }}-$(git rev-parse --short HEAD) >> $GITHUB_OUTPUT
outputs: outputs:
suffix: ${{ steps.version_vars.outputs.mastodon_version_suffix }} metadata: ${{ steps.version_vars.outputs.mastodon_version_metadata }}
build-image: build-image:
needs: compute-suffix needs: compute-suffix
@ -33,7 +33,7 @@ jobs:
use_native_arm64_builder: true use_native_arm64_builder: true
push_to_images: | push_to_images: |
ghcr.io/mastodon/mastodon ghcr.io/mastodon/mastodon
version_suffix: ${{ needs.compute-suffix.outputs.suffix }} version_metadata: ${{ needs.compute-suffix.outputs.metadata }}
flavor: | flavor: |
latest=auto latest=auto
tags: | tags: |

View File

@ -17,8 +17,12 @@ jobs:
push_to_images: | push_to_images: |
tootsuite/mastodon tootsuite/mastodon
ghcr.io/mastodon/mastodon ghcr.io/mastodon/mastodon
# Do not use cache when building releases, so apt update is always ran and the release always contain the latest packages
cache: false
# Only tag with latest when ran against the latest stable branch
# This needs to be updated after each minor version release
flavor: | flavor: |
latest=${{ startsWith(github.ref, 'refs/tags/v4.1.') }} latest=${{ startsWith(github.ref, 'refs/tags/v4.2.') }}
tags: | tags: |
type=pep440,pattern={{raw}} type=pep440,pattern={{raw}}
type=pep440,pattern=v{{major}}.{{minor}} type=pep440,pattern=v{{major}}.{{minor}}

View File

@ -25,7 +25,7 @@ jobs:
steps: steps:
- name: Clone repository - name: Clone repository
uses: actions/checkout@v3 uses: actions/checkout@v4
- name: Install native Ruby dependencies - name: Install native Ruby dependencies
run: sudo apt-get install -y libicu-dev libidn11-dev run: sudo apt-get install -y libicu-dev libidn11-dev

View File

@ -17,7 +17,7 @@ jobs:
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: Install system dependencies - name: Install system dependencies
run: | run: |

View File

@ -27,7 +27,7 @@ jobs:
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v3 uses: actions/checkout@v4
# Initializes the CodeQL tools for scanning. # Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL - name: Initialize CodeQL

View File

@ -14,7 +14,7 @@ jobs:
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v4
- name: Increase Git http.postBuffer - name: Increase Git http.postBuffer
# This is needed due to a bug in Ubuntu's cURL version? # This is needed due to a bug in Ubuntu's cURL version?

View File

@ -20,7 +20,7 @@ jobs:
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v4
- name: crowdin action - name: crowdin action
uses: crowdin/github-action@v1 uses: crowdin/github-action@v1

View File

@ -33,7 +33,7 @@ jobs:
steps: steps:
- name: Clone repository - name: Clone repository
uses: actions/checkout@v3 uses: actions/checkout@v4
- name: Set up Node.js - name: Set up Node.js
uses: actions/setup-node@v3 uses: actions/setup-node@v3

View File

@ -28,7 +28,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Clone repository - name: Clone repository
uses: actions/checkout@v3 uses: actions/checkout@v4
- name: Install native Ruby dependencies - name: Install native Ruby dependencies
run: | run: |

View File

@ -37,7 +37,7 @@ jobs:
steps: steps:
- name: Clone repository - name: Clone repository
uses: actions/checkout@v3 uses: actions/checkout@v4
- name: Set up Node.js - name: Set up Node.js
uses: actions/setup-node@v3 uses: actions/setup-node@v3

View File

@ -29,7 +29,7 @@ jobs:
steps: steps:
- name: Clone repository - name: Clone repository
uses: actions/checkout@v3 uses: actions/checkout@v4
- name: Set up Node.js - name: Set up Node.js
uses: actions/setup-node@v3 uses: actions/setup-node@v3

View File

@ -29,7 +29,7 @@ jobs:
steps: steps:
- name: Clone repository - name: Clone repository
uses: actions/checkout@v3 uses: actions/checkout@v4
- name: Set up Node.js - name: Set up Node.js
uses: actions/setup-node@v3 uses: actions/setup-node@v3

View File

@ -29,7 +29,7 @@ jobs:
steps: steps:
- name: Clone repository - name: Clone repository
uses: actions/checkout@v3 uses: actions/checkout@v4
- name: Install native Ruby dependencies - name: Install native Ruby dependencies
run: sudo apt-get install -y libicu-dev libidn11-dev run: sudo apt-get install -y libicu-dev libidn11-dev

View File

@ -31,7 +31,7 @@ jobs:
steps: steps:
- name: Clone repository - name: Clone repository
uses: actions/checkout@v3 uses: actions/checkout@v4
- name: Set up Node.js - name: Set up Node.js
uses: actions/setup-node@v3 uses: actions/setup-node@v3

View File

@ -33,7 +33,7 @@ jobs:
steps: steps:
- name: Clone repository - name: Clone repository
uses: actions/checkout@v3 uses: actions/checkout@v4
- name: Set up Node.js - name: Set up Node.js
uses: actions/setup-node@v3 uses: actions/setup-node@v3

View File

@ -70,7 +70,7 @@ jobs:
BUNDLE_RETRY: 3 BUNDLE_RETRY: 3
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: Install native Ruby dependencies - name: Install native Ruby dependencies
run: | run: |

View File

@ -69,7 +69,7 @@ jobs:
BUNDLE_RETRY: 3 BUNDLE_RETRY: 3
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: Install native Ruby dependencies - name: Install native Ruby dependencies
run: | run: |

View File

@ -32,7 +32,7 @@ jobs:
SECRET_KEY_BASE: precompile_placeholder SECRET_KEY_BASE: precompile_placeholder
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: Set up Node.js - name: Set up Node.js
uses: actions/setup-node@v3 uses: actions/setup-node@v3
@ -127,7 +127,7 @@ jobs:
- 3 - 3
- 4 - 4
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- uses: actions/download-artifact@v3 - uses: actions/download-artifact@v3
with: with:
@ -202,7 +202,7 @@ jobs:
- '.ruby-version' - '.ruby-version'
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- uses: actions/download-artifact@v3 - uses: actions/download-artifact@v3
with: with:
@ -250,3 +250,116 @@ jobs:
with: with:
name: e2e-screenshots name: e2e-screenshots
path: tmp/screenshots/ path: tmp/screenshots/
test-search:
name: Testing search
runs-on: ubuntu-latest
needs:
- build
services:
postgres:
image: postgres:14-alpine
env:
POSTGRES_PASSWORD: postgres
POSTGRES_USER: postgres
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 5432:5432
redis:
image: redis:7-alpine
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 6379:6379
elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:7.17.13
env:
discovery.type: single-node
xpack.security.enabled: false
options: >-
--health-cmd "curl http://localhost:9200/_cluster/health"
--health-interval 10s
--health-timeout 5s
--health-retries 10
ports:
- 9200:9200
env:
DB_HOST: localhost
DB_USER: postgres
DB_PASS: postgres
DISABLE_SIMPLECOV: true
RAILS_ENV: test
BUNDLE_WITH: test
ES_ENABLED: true
ES_HOST: localhost
ES_PORT: 9200
strategy:
fail-fast: false
matrix:
ruby-version:
- '3.0'
- '3.1'
- '.ruby-version'
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v3
with:
path: './public'
name: ${{ github.sha }}
- name: Update package index
run: sudo apt-get update
- name: Set up Node.js
uses: actions/setup-node@v3
with:
cache: yarn
node-version-file: '.nvmrc'
- name: Install native Ruby dependencies
run: sudo apt-get install -y libicu-dev libidn11-dev
- name: Install additional system dependencies
run: sudo apt-get install -y ffmpeg imagemagick
- name: Set up bundler cache
uses: ruby/setup-ruby@v1
with:
ruby-version: ${{ matrix.ruby-version}}
bundler-cache: true
- run: yarn --frozen-lockfile
- name: Load database schema
run: './bin/rails db:create db:schema:load db:seed'
- run: bundle exec rake spec:search
- name: Archive logs
uses: actions/upload-artifact@v3
if: failure()
with:
name: test-search-logs-${{ matrix.ruby-version }}
path: log/
- name: Archive test screenshots
uses: actions/upload-artifact@v3
if: failure()
with:
name: test-search-screenshots
path: tmp/screenshots/

2
.nvmrc
View File

@ -1 +1 @@
16.20 20.7

View File

@ -1,6 +1,6 @@
# This configuration was generated by # This configuration was generated by
# `rubocop --auto-gen-config --auto-gen-only-exclude --no-exclude-limit --no-offense-counts --no-auto-gen-timestamp` # `rubocop --auto-gen-config --auto-gen-only-exclude --no-exclude-limit --no-offense-counts --no-auto-gen-timestamp`
# using RuboCop version 1.54.2. # using RuboCop version 1.56.1.
# The point is for the user to remove these configuration records # The point is for the user to remove these configuration records
# one by one as the offenses are removed from the code base. # one by one as the offenses are removed from the code base.
# Note that changes in the inspected code, or installation of new # Note that changes in the inspected code, or installation of new
@ -37,7 +37,7 @@ Layout/HashAlignment:
Layout/LeadingCommentSpace: Layout/LeadingCommentSpace:
Exclude: Exclude:
- 'config/application.rb' - 'config/application.rb'
- 'config/initializers/omniauth.rb' - 'config/initializers/3_omniauth.rb'
# This cop supports safe autocorrection (--autocorrect). # This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: Max, AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, AllowedPatterns. # Configuration parameters: Max, AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, AllowedPatterns.
@ -61,38 +61,8 @@ Lint/EmptyBlock:
- 'spec/fabricators/access_token_fabricator.rb' - 'spec/fabricators/access_token_fabricator.rb'
- 'spec/fabricators/conversation_fabricator.rb' - 'spec/fabricators/conversation_fabricator.rb'
- 'spec/fabricators/system_key_fabricator.rb' - 'spec/fabricators/system_key_fabricator.rb'
- 'spec/helpers/admin/action_logs_helper_spec.rb'
- 'spec/lib/activitypub/adapter_spec.rb' - 'spec/lib/activitypub/adapter_spec.rb'
- 'spec/models/account_alias_spec.rb'
- 'spec/models/account_deletion_request_spec.rb'
- 'spec/models/account_moderation_note_spec.rb'
- 'spec/models/announcement_mute_spec.rb'
- 'spec/models/announcement_reaction_spec.rb'
- 'spec/models/announcement_spec.rb'
- 'spec/models/backup_spec.rb'
- 'spec/models/conversation_mute_spec.rb'
- 'spec/models/custom_filter_keyword_spec.rb'
- 'spec/models/custom_filter_spec.rb'
- 'spec/models/device_spec.rb'
- 'spec/models/encrypted_message_spec.rb'
- 'spec/models/featured_tag_spec.rb'
- 'spec/models/follow_recommendation_suppression_spec.rb'
- 'spec/models/list_account_spec.rb'
- 'spec/models/list_spec.rb'
- 'spec/models/login_activity_spec.rb'
- 'spec/models/mute_spec.rb'
- 'spec/models/preview_card_spec.rb'
- 'spec/models/preview_card_trend_spec.rb'
- 'spec/models/relay_spec.rb'
- 'spec/models/scheduled_status_spec.rb'
- 'spec/models/status_stat_spec.rb'
- 'spec/models/status_trend_spec.rb'
- 'spec/models/system_key_spec.rb'
- 'spec/models/tag_follow_spec.rb'
- 'spec/models/unavailable_domain_spec.rb'
- 'spec/models/user_invite_request_spec.rb'
- 'spec/models/user_role_spec.rb' - 'spec/models/user_role_spec.rb'
- 'spec/models/web/setting_spec.rb'
Lint/NonLocalExitFromIterator: Lint/NonLocalExitFromIterator:
Exclude: Exclude:
@ -116,7 +86,7 @@ Lint/UnusedBlockArgument:
Lint/UselessAssignment: Lint/UselessAssignment:
Exclude: Exclude:
- 'app/services/activitypub/process_status_update_service.rb' - 'app/services/activitypub/process_status_update_service.rb'
- 'config/initializers/omniauth.rb' - 'config/initializers/3_omniauth.rb'
- 'db/migrate/20190511134027_add_silenced_at_suspended_at_to_accounts.rb' - 'db/migrate/20190511134027_add_silenced_at_suspended_at_to_accounts.rb'
- 'db/post_migrate/20190511152737_remove_suspended_silenced_account_fields.rb' - 'db/post_migrate/20190511152737_remove_suspended_silenced_account_fields.rb'
- 'spec/controllers/api/v1/favourites_controller_spec.rb' - 'spec/controllers/api/v1/favourites_controller_spec.rb'
@ -135,7 +105,7 @@ Lint/UselessAssignment:
# Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes. # Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes.
Metrics/AbcSize: Metrics/AbcSize:
Max: 146 Max: 144
# Configuration parameters: CountBlocks, Max. # Configuration parameters: CountBlocks, Max.
Metrics/BlockNesting: Metrics/BlockNesting:
@ -164,6 +134,19 @@ Naming/VariableNumber:
- 'spec/models/domain_block_spec.rb' - 'spec/models/domain_block_spec.rb'
- 'spec/models/user_spec.rb' - 'spec/models/user_spec.rb'
# This cop supports unsafe autocorrection (--autocorrect-all).
# Configuration parameters: SafeMultiline.
Performance/DeletePrefix:
Exclude:
- 'app/models/featured_tag.rb'
Performance/MapMethodChain:
Exclude:
- 'app/models/feed.rb'
- 'lib/mastodon/cli/maintenance.rb'
- 'spec/services/bulk_import_service_spec.rb'
- 'spec/services/import_service_spec.rb'
RSpec/AnyInstance: RSpec/AnyInstance:
Exclude: Exclude:
- 'spec/controllers/activitypub/inboxes_controller_spec.rb' - 'spec/controllers/activitypub/inboxes_controller_spec.rb'
@ -306,10 +289,6 @@ RSpec/MultipleMemoizedHelpers:
RSpec/NestedGroups: RSpec/NestedGroups:
Max: 6 Max: 6
RSpec/PendingWithoutReason:
Exclude:
- 'spec/models/account_spec.rb'
# This cop supports unsafe autocorrection (--autocorrect-all). # This cop supports unsafe autocorrection (--autocorrect-all).
Rails/ApplicationController: Rails/ApplicationController:
Exclude: Exclude:
@ -590,11 +569,11 @@ Style/FetchEnvVar:
- 'config/environments/development.rb' - 'config/environments/development.rb'
- 'config/environments/production.rb' - 'config/environments/production.rb'
- 'config/initializers/2_limited_federation_mode.rb' - 'config/initializers/2_limited_federation_mode.rb'
- 'config/initializers/3_omniauth.rb'
- 'config/initializers/blacklists.rb' - 'config/initializers/blacklists.rb'
- 'config/initializers/cache_buster.rb' - 'config/initializers/cache_buster.rb'
- 'config/initializers/content_security_policy.rb' - 'config/initializers/content_security_policy.rb'
- 'config/initializers/devise.rb' - 'config/initializers/devise.rb'
- 'config/initializers/omniauth.rb'
- 'config/initializers/paperclip.rb' - 'config/initializers/paperclip.rb'
- 'config/initializers/vapid.rb' - 'config/initializers/vapid.rb'
- 'lib/mastodon/premailer_webpack_strategy.rb' - 'lib/mastodon/premailer_webpack_strategy.rb'
@ -762,6 +741,15 @@ Style/RedundantFetchBlock:
- 'config/initializers/paperclip.rb' - 'config/initializers/paperclip.rb'
- 'config/puma.rb' - 'config/puma.rb'
# This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: AllowMultipleReturnValues.
Style/RedundantReturn:
Exclude:
- 'app/controllers/api/v1/directories_controller.rb'
- 'app/controllers/auth/confirmations_controller.rb'
- 'app/lib/ostatus/tag_manager.rb'
- 'app/models/form/import.rb'
# This cop supports unsafe autocorrection (--autocorrect-all). # This cop supports unsafe autocorrection (--autocorrect-all).
# Configuration parameters: ConvertCodeThatCanStartToReturnNil, AllowedMethods, MaxChainLength. # Configuration parameters: ConvertCodeThatCanStartToReturnNil, AllowedMethods, MaxChainLength.
# AllowedMethods: present?, blank?, presence, try, try! # AllowedMethods: present?, blank?, presence, try, try!
@ -819,7 +807,7 @@ Style/StringLiterals:
# AllowedMethods: define_method, mail, respond_to # AllowedMethods: define_method, mail, respond_to
Style/SymbolProc: Style/SymbolProc:
Exclude: Exclude:
- 'config/initializers/omniauth.rb' - 'config/initializers/3_omniauth.rb'
# This cop supports safe autocorrection (--autocorrect). # This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: EnforcedStyle, AllowSafeAssignment. # Configuration parameters: EnforcedStyle, AllowSafeAssignment.

View File

@ -1 +1 @@
3.2.2 3.2.3

1680
AUTHORS.md

File diff suppressed because it is too large Load Diff

View File

@ -2,55 +2,207 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
## [4.2.0] - UNRELEASED ## [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
- Add redirection on `/deck` URLs for logged-out users ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27128))
- Add support for v4.2.0 migrations to `tootctl maintenance fix-duplicates` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27147))
### Changed
- Change some worker lock TTLs to be shorter-lived ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27246))
- Change user archive export allowed period from 7 days to 6 days ([suddjian](https://github.com/mastodon/mastodon/pull/27200))
### Fixed
- Fix duplicate reports being sent when reporting some remote posts ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27355))
- Fix clicking on already-opened thread post scrolling to the top of the thread ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27331), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/27338), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/27350))
- Fix some remote posts getting truncated ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27307))
- Fix some cases of infinite scroll code trying to fetch inaccessible posts in a loop ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27286))
- Fix `Vary` headers not being set on some redirects ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27272))
- Fix mentions being matched in some URL query strings ([mjankowski](https://github.com/mastodon/mastodon/pull/25656))
- Fix unexpected linebreak in version string in the Web UI ([vmstan](https://github.com/mastodon/mastodon/pull/26986))
- Fix double scroll bars in some columns in advanced interface ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27187))
- Fix boosts of local users being filtered in account timelines ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27204))
- Fix multiple instances of the trend refresh scheduler sometimes running at once ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27253))
- Fix importer returning negative row estimates ([jgillich](https://github.com/mastodon/mastodon/pull/27258))
- Fix incorrectly keeping outdated update notices absent from the API endpoint ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27021))
- Fix import progress not updating on certain failures ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27247))
- Fix websocket connections being incorrectly decremented twice on errors ([ThisIsMissEm](https://github.com/mastodon/mastodon/pull/27238))
- Fix explore prompt appearing because of posts being received out of order ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27211))
- Fix explore prompt sometimes showing up when the home TL is loading ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27062))
- Fix link handling of mentions in user profiles when logged out ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27185))
- Fix filtering audit log for entries about disabling 2FA ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27186))
- Fix notification toasts not respecting reduce-motion ([c960657](https://github.com/mastodon/mastodon/pull/27178))
- Fix retention dashboard not displaying correct month ([vmstan](https://github.com/mastodon/mastodon/pull/27180))
- Fix tIME chunk not being properly removed from PNG uploads ([TheEssem](https://github.com/mastodon/mastodon/pull/27111))
- Fix division by zero in video in bitrate computation code ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27129))
- Fix inefficient queries in “Follows and followers” as well as several admin pages ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27116), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/27306))
- Fix ActiveRecord using two connection pools when no replica is defined ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27061))
- Fix the search documentation URL in system checks ([renchap](https://github.com/mastodon/mastodon/pull/27036))
## [4.2.0] - 2023-09-21
The following changelog entries focus on changes visible to users, administrators, client developers or federated software developers, but there has also been a lot of code modernization, refactoring, and tooling work, in particular by [@danielmbrasil](https://github.com/danielmbrasil), [@mjankowski](https://github.com/mjankowski), [@nschonni](https://github.com/nschonni), [@renchap](https://github.com/renchap), and [@takayamaki](https://github.com/takayamaki). The following changelog entries focus on changes visible to users, administrators, client developers or federated software developers, but there has also been a lot of code modernization, refactoring, and tooling work, in particular by [@danielmbrasil](https://github.com/danielmbrasil), [@mjankowski](https://github.com/mjankowski), [@nschonni](https://github.com/nschonni), [@renchap](https://github.com/renchap), and [@takayamaki](https://github.com/takayamaki).
### Added ### Added
- **Add full-text search of opted-in public posts and rework search operators** ([Gargron](https://github.com/mastodon/mastodon/pull/26485), [jsgoldstein](https://github.com/mastodon/mastodon/pull/26344), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26657), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26650), [jsgoldstein](https://github.com/mastodon/mastodon/pull/26659), [Gargron](https://github.com/mastodon/mastodon/pull/26660), [Gargron](https://github.com/mastodon/mastodon/pull/26663), [Gargron](https://github.com/mastodon/mastodon/pull/26688), [Gargron](https://github.com/mastodon/mastodon/pull/26689), [Gargron](https://github.com/mastodon/mastodon/pull/26686), [Gargron](https://github.com/mastodon/mastodon/pull/26687), [Gargron](https://github.com/mastodon/mastodon/pull/26692), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26697), [Gargron](https://github.com/mastodon/mastodon/pull/26699), [Gargron](https://github.com/mastodon/mastodon/pull/26701), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26710), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26739), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26754), [Gargron](https://github.com/mastodon/mastodon/pull/26662), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26755), [Gargron](https://github.com/mastodon/mastodon/pull/26781), [Gargron](https://github.com/mastodon/mastodon/pull/26782), [Gargron](https://github.com/mastodon/mastodon/pull/26760), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26756), [Gargron](https://github.com/mastodon/mastodon/pull/26784), [Gargron](https://github.com/mastodon/mastodon/pull/26807), [Gargron](https://github.com/mastodon/mastodon/pull/26835), [Gargron](https://github.com/mastodon/mastodon/pull/26847), [Gargron](https://github.com/mastodon/mastodon/pull/26834), [arbolitoloco1](https://github.com/mastodon/mastodon/pull/26893), [tribela](https://github.com/mastodon/mastodon/pull/26896), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26927), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26959), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/27014))
This introduces a new `public_statuses` Elasticsearch index for public posts by users who have opted in to their posts being searchable (`toot#indexable` flag).
This also revisits the other indexes to provide more useful indexing, and adds new search operators such as `from:me`, `before:2022-11-01`, `after:2022-11-01`, `during:2022-11-01`, `language:fr`, `has:poll`, or `in:library` (for searching only in posts you have written or interacted with).
Results are now ordered chronologically.
- **Add admin notifications for new Mastodon versions** ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/26582))
This is done by querying `https://api.joinmastodon.org/update-check` every 30 minutes in a background job.
That URL can be changed using the `UPDATE_CHECK_URL` environment variable, and the feature outright disabled by setting that variable to an empty string (`UPDATE_CHECK_URL=`).
- **Add “Privacy and reach” tab in profile settings** ([Gargron](https://github.com/mastodon/mastodon/pull/26484), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26508)) - **Add “Privacy and reach” tab in profile settings** ([Gargron](https://github.com/mastodon/mastodon/pull/26484), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26508))
This reorganized scattered privacy and reach settings to a single place, as well as improve their wording. This reorganized scattered privacy and reach settings to a single place, as well as improve their wording.
- **Add display of out-of-band hashtags in the web interface** ([Gargron](https://github.com/mastodon/mastodon/pull/26492), [arbolitoloco1](https://github.com/mastodon/mastodon/pull/26497), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26506), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26525)) - **Add display of out-of-band hashtags in the web interface** ([Gargron](https://github.com/mastodon/mastodon/pull/26492), [arbolitoloco1](https://github.com/mastodon/mastodon/pull/26497), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26506), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26525), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26606), [Gargron](https://github.com/mastodon/mastodon/pull/26666), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26960))
- **Add role badges to the web interface** ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25649), [Gargron](https://github.com/mastodon/mastodon/pull/26281)) - **Add role badges to the web interface** ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25649), [Gargron](https://github.com/mastodon/mastodon/pull/26281))
- **Add ability to pick domains to forward reports to using the `forward_to_domains` parameter in `POST /api/v1/reports`** ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25866)) - **Add ability to pick domains to forward reports to using the `forward_to_domains` parameter in `POST /api/v1/reports`** ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25866), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26636))
The `forward_to_domains` REST API parameter is a list of strings. If it is empty or omitted, the previous behavior is maintained. The `forward_to_domains` REST API parameter is a list of strings. If it is empty or omitted, the previous behavior is maintained.
The `forward` parameter still needs to be set for `forward_to_domains` to be taken into account. The `forward` parameter still needs to be set for `forward_to_domains` to be taken into account.
The forwarded-to domains can only include that of the original author and people being replied to. The forwarded-to domains can only include that of the original author and people being replied to.
- **Add forwarding of reported replies to servers being replied to** ([Gargron](https://github.com/mastodon/mastodon/pull/25341), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26189)) - **Add forwarding of reported replies to servers being replied to** ([Gargron](https://github.com/mastodon/mastodon/pull/25341), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26189))
- Add direct link to the Single-Sign On provider if there is only one sign up method available ([CSDUMMI](https://github.com/mastodon/mastodon/pull/26083), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26368)) - Add `ONE_CLICK_SSO_LOGIN` environment variable to directly link to the Single-Sign On provider if there is only one sign up method available ([CSDUMMI](https://github.com/mastodon/mastodon/pull/26083), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26368), [CSDUMMI](https://github.com/mastodon/mastodon/pull/26857), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26901))
- **Add webhook templating** ([Gargron](https://github.com/mastodon/mastodon/pull/23289)) - **Add webhook templating** ([Gargron](https://github.com/mastodon/mastodon/pull/23289))
- **Add webhooks for local `status.created`, `status.updated`, `account.updated` and `report.updated`** ([VyrCossont](https://github.com/mastodon/mastodon/pull/24133), [VyrCossont](https://github.com/mastodon/mastodon/pull/24243), [VyrCossont](https://github.com/mastodon/mastodon/pull/24211)) - **Add webhooks for local `status.created`, `status.updated`, `account.updated` and `report.updated`** ([VyrCossont](https://github.com/mastodon/mastodon/pull/24133), [VyrCossont](https://github.com/mastodon/mastodon/pull/24243), [VyrCossont](https://github.com/mastodon/mastodon/pull/24211))
- **Add exclusive lists** ([dariusk](https://github.com/mastodon/mastodon/pull/22048), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/25324)) - **Add exclusive lists** ([dariusk, necropolina](https://github.com/mastodon/mastodon/pull/22048), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/25324))
- **Add a confirmation screen when suspending a domain** ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25144), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/25603)) - **Add a confirmation screen when suspending a domain** ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25144), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/25603))
- **Add support for importing lists** ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25203), [mgmn](https://github.com/mastodon/mastodon/pull/26120), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26372)) - **Add support for importing lists** ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25203), [mgmn](https://github.com/mastodon/mastodon/pull/26120), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26372))
- **Add optional hCaptcha support** ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25019), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/25057), [Gargron](https://github.com/mastodon/mastodon/pull/25395), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26388)) - **Add optional hCaptcha support** ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25019), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/25057), [Gargron](https://github.com/mastodon/mastodon/pull/25395), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26388))
- **Add lines to threads in web UI** ([Gargron](https://github.com/mastodon/mastodon/pull/24549), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/24677), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/24696), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/24711), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/24714), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/24713), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/24715), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/24800), [teeerevor](https://github.com/mastodon/mastodon/pull/25706), [renchap](https://github.com/mastodon/mastodon/pull/25807)) - **Add lines to threads in web UI** ([Gargron](https://github.com/mastodon/mastodon/pull/24549), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/24677), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/24696), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/24711), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/24714), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/24713), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/24715), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/24800), [teeerevor](https://github.com/mastodon/mastodon/pull/25706), [renchap](https://github.com/mastodon/mastodon/pull/25807))
- **Add new onboarding flow to web UI** ([Gargron](https://github.com/mastodon/mastodon/pull/24619), [Gargron](https://github.com/mastodon/mastodon/pull/24646), [Gargron](https://github.com/mastodon/mastodon/pull/24705), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/24872), [ThisIsMissEm](https://github.com/mastodon/mastodon/pull/24883), [Gargron](https://github.com/mastodon/mastodon/pull/24954), [stevenjlm](https://github.com/mastodon/mastodon/pull/24959), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/25010), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/25275), [Gargron](https://github.com/mastodon/mastodon/pull/25559), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/25561)) - **Add new onboarding flow to web UI** ([Gargron](https://github.com/mastodon/mastodon/pull/24619), [Gargron](https://github.com/mastodon/mastodon/pull/24646), [Gargron](https://github.com/mastodon/mastodon/pull/24705), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/24872), [ThisIsMissEm](https://github.com/mastodon/mastodon/pull/24883), [Gargron](https://github.com/mastodon/mastodon/pull/24954), [stevenjlm](https://github.com/mastodon/mastodon/pull/24959), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/25010), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/25275), [Gargron](https://github.com/mastodon/mastodon/pull/25559), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/25561))
- **Add `S3_DISABLE_CHECKSUM_MODE` environment variable for compatibility with some S3-compatible providers** ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/26435))
- **Add auto-refresh of accounts we get new messages/edits of** ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/26510)) - **Add auto-refresh of accounts we get new messages/edits of** ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/26510))
- **Add Elasticsearch cluster health check and indexes mismatch check to dashboard** ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/26448)) - **Add Elasticsearch cluster health check and indexes mismatch check to dashboard** ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/26448), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26605), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26658))
- Add support for `indexable` attribute on remote actors ([Gargron](https://github.com/mastodon/mastodon/pull/26485)) - Add `hide_collections`, `discoverable` and `indexable` attributes to credentials API ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/26998))
- Add `S3_ENABLE_CHECKSUM_MODE` environment variable to enable checksum verification on compatible S3-providers ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/26435))
- Add admin API for managing tags ([rrgeorge](https://github.com/mastodon/mastodon/pull/26872))
- Add a link to hashtag timelines from the Trending hashtags moderation interface ([gunchleoc](https://github.com/mastodon/mastodon/pull/26724))
- Add timezone to datetimes in e-mails ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/26822))
- Add `authorized_fetch` server setting in addition to env var ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25798), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26958))
- Add avatar image to webfinger responses ([tvler](https://github.com/mastodon/mastodon/pull/26558))
- Add debug logging on signature verification failure ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/26637), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26812))
- Add explicit error messages when DeepL quota is exceeded ([lutoma](https://github.com/mastodon/mastodon/pull/26704))
- Add Elasticsearch/OpenSearch version to “Software” in admin dashboard ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/26652))
- Add `data-nosnippet` attribute to remote posts and local posts with `noindex` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/26648))
- Add support for federating `memorial` attribute ([rrgeorge](https://github.com/mastodon/mastodon/pull/26583))
- Add Cherokee and Kalmyk to languages dropdown ([gunchleoc](https://github.com/mastodon/mastodon/pull/26012), [gunchleoc](https://github.com/mastodon/mastodon/pull/26013))
- Add `DELETE /api/v1/profile/avatar` and `DELETE /api/v1/profile/header` to the REST API ([danielmbrasil](https://github.com/mastodon/mastodon/pull/25124), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26573)) - Add `DELETE /api/v1/profile/avatar` and `DELETE /api/v1/profile/header` to the REST API ([danielmbrasil](https://github.com/mastodon/mastodon/pull/25124), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26573))
- Add `ES_PRESET` option to customize numbers of shards and replicas ([Gargron](https://github.com/mastodon/mastodon/pull/26483), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26489)) - Add `ES_PRESET` option to customize numbers of shards and replicas ([Gargron](https://github.com/mastodon/mastodon/pull/26483), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26489))
This can have a value of `single_node_cluster` (default), `small_cluster` (uses one replica) or `large_cluster` (uses one replica and a higher number of shards). This can have a value of `single_node_cluster` (default), `small_cluster` (uses one replica) or `large_cluster` (uses one replica and a higher number of shards).
- Add missing `instances` option to `tootctl search deploy` ([tribela](https://github.com/mastodon/mastodon/pull/26461))
- Add `CACHE_BUSTER_HTTP_METHOD` environment variable ([renchap](https://github.com/mastodon/mastodon/pull/26528), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26542)) - Add `CACHE_BUSTER_HTTP_METHOD` environment variable ([renchap](https://github.com/mastodon/mastodon/pull/26528), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26542))
- Add support for `DB_PASS` when using `DATABASE_URL` ([ThisIsMissEm](https://github.com/mastodon/mastodon/pull/26295)) - Add support for `DB_PASS` when using `DATABASE_URL` ([ThisIsMissEm](https://github.com/mastodon/mastodon/pull/26295))
- Add `GET /api/v1/instance/languages` to REST API ([danielmbrasil](https://github.com/mastodon/mastodon/pull/24443)) - Add `GET /api/v1/instance/languages` to REST API ([danielmbrasil](https://github.com/mastodon/mastodon/pull/24443))
- Add primary key to `preview_cards_statuses` join table ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25243), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26384), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26447)) - Add primary key to `preview_cards_statuses` join table ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25243), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26384), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26447), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26737), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26979))
- Add client-side timeout on resend confirmation button ([Gargron](https://github.com/mastodon/mastodon/pull/26300)) - Add client-side timeout on resend confirmation button ([Gargron](https://github.com/mastodon/mastodon/pull/26300))
- Add published date and author to news on the explore screen in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/26155)) - Add published date and author to news on the explore screen in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/26155))
- Add `lang` attribute to various UI components ([c960657](https://github.com/mastodon/mastodon/pull/23869), [c960657](https://github.com/mastodon/mastodon/pull/23891), [c960657](https://github.com/mastodon/mastodon/pull/26111), [c960657](https://github.com/mastodon/mastodon/pull/26149)) - Add `lang` attribute to various UI components ([c960657](https://github.com/mastodon/mastodon/pull/23869), [c960657](https://github.com/mastodon/mastodon/pull/23891), [c960657](https://github.com/mastodon/mastodon/pull/26111), [c960657](https://github.com/mastodon/mastodon/pull/26149))
- Add stricter protocol fields validation for accounts ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25937)) - Add stricter protocol fields validation for accounts ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25937))
- Add support for Azure blob storage ([mistydemeo](https://github.com/mastodon/mastodon/pull/23607), [mistydemeo](https://github.com/mastodon/mastodon/pull/26080)) - Add support for Azure blob storage ([mistydemeo](https://github.com/mastodon/mastodon/pull/23607), [mistydemeo](https://github.com/mastodon/mastodon/pull/26080))
- Add toast with option to open post after publishing in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/25564), [Signez](https://github.com/mastodon/mastodon/pull/25919)) - Add toast with option to open post after publishing in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/25564), [Signez](https://github.com/mastodon/mastodon/pull/25919), [Gargron](https://github.com/mastodon/mastodon/pull/26664))
- Add canonical link tags in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/25715)) - Add canonical link tags in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/25715))
- Add button to see results for polls in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/25726)) - Add button to see results for polls in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/25726))
- Add at-symbol prepended to mention span title ([forsamori](https://github.com/mastodon/mastodon/pull/25684)) - Add at-symbol prepended to mention span title ([forsamori](https://github.com/mastodon/mastodon/pull/25684))
- Add users index on `unconfirmed_email` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25672), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/25702)) - Add users index on `unconfirmed_email` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25672), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/25702))
- Add superapp index on `oauth_applications` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25670)) - Add superapp index on `oauth_applications` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25670))
- Add index to backups on `user_id` column ([mjankowski](https://github.com/mastodon/mastodon/pull/25647)) - Add index to backups on `user_id` column ([mjankowski](https://github.com/mastodon/mastodon/pull/25647))
- Add onboarding prompt when home feed too slow in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/25267), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/25556), [Gargron](https://github.com/mastodon/mastodon/pull/25579), [renchap](https://github.com/mastodon/mastodon/pull/25580), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/25581), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/25617), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/25917)) - Add onboarding prompt when home feed too slow in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/25267), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/25556), [Gargron](https://github.com/mastodon/mastodon/pull/25579), [renchap](https://github.com/mastodon/mastodon/pull/25580), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/25581), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/25617), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/25917), [Gargron](https://github.com/mastodon/mastodon/pull/26829), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26935))
- Add `POST /api/v1/conversations/:id/unread` API endpoint to mark a conversation as unread ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25509)) - Add `POST /api/v1/conversations/:id/unread` API endpoint to mark a conversation as unread ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25509))
- Add `translate="no"` to outgoing mentions and links ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25524)) - Add `translate="no"` to outgoing mentions and links ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25524))
- Add unsubscribe link and headers to e-mails ([Gargron](https://github.com/mastodon/mastodon/pull/25378), [c960657](https://github.com/mastodon/mastodon/pull/26085)) - Add unsubscribe link and headers to e-mails ([Gargron](https://github.com/mastodon/mastodon/pull/25378), [c960657](https://github.com/mastodon/mastodon/pull/26085))
@ -88,41 +240,48 @@ The following changelog entries focus on changes visible to users, administrator
- Add support for streaming server to connect to postgres with self-signed certs through the `sslmode` URL parameter ([ramuuns](https://github.com/mastodon/mastodon/pull/21431)) - Add support for streaming server to connect to postgres with self-signed certs through the `sslmode` URL parameter ([ramuuns](https://github.com/mastodon/mastodon/pull/21431))
- Add support for specifying S3 storage classes through the `S3_STORAGE_CLASS` environment variable ([hyl](https://github.com/mastodon/mastodon/pull/22480)) - Add support for specifying S3 storage classes through the `S3_STORAGE_CLASS` environment variable ([hyl](https://github.com/mastodon/mastodon/pull/22480))
- Add support for incoming rich text ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23913)) - Add support for incoming rich text ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23913))
- Add support for Ruby 3.2 ([tenderlove](https://github.com/mastodon/mastodon/pull/22928), [casperisfine](https://github.com/mastodon/mastodon/pull/24142), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/24202)) - Add support for Ruby 3.2 ([tenderlove](https://github.com/mastodon/mastodon/pull/22928), [casperisfine](https://github.com/mastodon/mastodon/pull/24142), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/24202), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26934))
- Add API parameter to safeguard unexpected mentions in new posts ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/18350)) - Add API parameter to safeguard unexpected mentions in new posts ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/18350))
### Changed ### Changed
- **Change hashtags to be displayed separately when they are the last line of a post** ([renchap](https://github.com/mastodon/mastodon/pull/26499)) - **Change hashtags to be displayed separately when they are the last line of a post** ([renchap](https://github.com/mastodon/mastodon/pull/26499), [renchap](https://github.com/mastodon/mastodon/pull/26614), [renchap](https://github.com/mastodon/mastodon/pull/26615))
- **Change reblogs to be excluded from "Posts and replies" tab in web UI** ([Gargron](https://github.com/mastodon/mastodon/pull/26302)) - **Change reblogs to be excluded from "Posts and replies" tab in web UI** ([Gargron](https://github.com/mastodon/mastodon/pull/26302))
- **Change interaction modal in web interface** ([Gargron, ClearlyClaire](https://github.com/mastodon/mastodon/pull/26075), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26269), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26268), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26267), [mgmn](https://github.com/mastodon/mastodon/pull/26459)) - **Change interaction modal in web interface** ([Gargron, ClearlyClaire](https://github.com/mastodon/mastodon/pull/26075), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26269), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26268), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26267), [mgmn](https://github.com/mastodon/mastodon/pull/26459), [tribela](https://github.com/mastodon/mastodon/pull/26461), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26593), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26795))
- **Change design of link previews in web UI** ([Gargron](https://github.com/mastodon/mastodon/pull/26136), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26151), [Gargron](https://github.com/mastodon/mastodon/pull/26153), [Gargron](https://github.com/mastodon/mastodon/pull/26250), [Gargron](https://github.com/mastodon/mastodon/pull/26287), [Gargron](https://github.com/mastodon/mastodon/pull/26286), [c960657](https://github.com/mastodon/mastodon/pull/26184)) - **Change design of link previews in web UI** ([Gargron](https://github.com/mastodon/mastodon/pull/26136), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26151), [Gargron](https://github.com/mastodon/mastodon/pull/26153), [Gargron](https://github.com/mastodon/mastodon/pull/26250), [Gargron](https://github.com/mastodon/mastodon/pull/26287), [Gargron](https://github.com/mastodon/mastodon/pull/26286), [c960657](https://github.com/mastodon/mastodon/pull/26184))
- **Change "direct message" nomenclature to "private mention" in web UI** ([Gargron](https://github.com/mastodon/mastodon/pull/24248)) - **Change "direct message" nomenclature to "private mention" in web UI** ([Gargron](https://github.com/mastodon/mastodon/pull/24248))
- **Change translation feature to cover Content Warnings, poll options and media descriptions** ([c960657](https://github.com/mastodon/mastodon/pull/24175), [S-H-GAMELINKS](https://github.com/mastodon/mastodon/pull/25251), [c960657](https://github.com/mastodon/mastodon/pull/26168), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26452)) - **Change translation feature to cover Content Warnings, poll options and media descriptions** ([c960657](https://github.com/mastodon/mastodon/pull/24175), [S-H-GAMELINKS](https://github.com/mastodon/mastodon/pull/25251), [c960657](https://github.com/mastodon/mastodon/pull/26168), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26452))
- **Change account search to match by text when opted-in** ([jsgoldstein](https://github.com/mastodon/mastodon/pull/25599), [Gargron](https://github.com/mastodon/mastodon/pull/26378)) - **Change account search to match by text when opted-in** ([jsgoldstein](https://github.com/mastodon/mastodon/pull/25599), [Gargron](https://github.com/mastodon/mastodon/pull/26378))
- **Change import feature to be clearer, less error-prone and more reliable** ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/21054), [mgmn](https://github.com/mastodon/mastodon/pull/24874)) - **Change import feature to be clearer, less error-prone and more reliable** ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/21054), [mgmn](https://github.com/mastodon/mastodon/pull/24874))
- **Change local and federated timelines to be in a single “Live feeds” column** ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25641), [Gargron](https://github.com/mastodon/mastodon/pull/25683), [mgmn](https://github.com/mastodon/mastodon/pull/25694), [Plastikmensch](https://github.com/mastodon/mastodon/pull/26247)) - **Change local and federated timelines to be tabs of a single “Live feeds” column** ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25641), [Gargron](https://github.com/mastodon/mastodon/pull/25683), [mgmn](https://github.com/mastodon/mastodon/pull/25694), [Plastikmensch](https://github.com/mastodon/mastodon/pull/26247), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26633))
- **Change user archive export to be faster and more reliable, and export `.zip` archives instead of `.tar.gz` ones** ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23360), [TheEssem](https://github.com/mastodon/mastodon/pull/25034)) - **Change user archive export to be faster and more reliable, and export `.zip` archives instead of `.tar.gz` ones** ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23360), [TheEssem](https://github.com/mastodon/mastodon/pull/25034))
- **Change `mastodon-streaming` systemd unit files to be templated** ([e-nomem](https://github.com/mastodon/mastodon/pull/24751)) - **Change `mastodon-streaming` systemd unit files to be templated** ([e-nomem](https://github.com/mastodon/mastodon/pull/24751))
- **Change `statsd` integration to disable sidekiq metrics by default** ([mjankowski](https://github.com/mastodon/mastodon/pull/25265), [mjankowski](https://github.com/mastodon/mastodon/pull/25336), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26310)) - **Change `statsd` integration to disable sidekiq metrics by default** ([mjankowski](https://github.com/mastodon/mastodon/pull/25265), [mjankowski](https://github.com/mastodon/mastodon/pull/25336), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26310))
This deprecates `statsd` support and disables the sidekiq integration unless `STATSD_SIDEKIQ` is set to `true`. This deprecates `statsd` support and disables the sidekiq integration unless `STATSD_SIDEKIQ` is set to `true`.
This is because the `nsa` gem is unmaintained, and its sidekiq integration is known to add very significant overhead. This is because the `nsa` gem is unmaintained, and its sidekiq integration is known to add very significant overhead.
Later versions of Mastodon will have other ways to get the same metrics. Later versions of Mastodon will have other ways to get the same metrics.
- **Change replica support to native Rails adapter** ([krainboltgreene](https://github.com/mastodon/mastodon/pull/25693), [Gargron](https://github.com/mastodon/mastodon/pull/25849), [Gargron](https://github.com/mastodon/mastodon/pull/25874), [Gargron](https://github.com/mastodon/mastodon/pull/25851), [Gargron](https://github.com/mastodon/mastodon/pull/25977), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26074), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26326), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26386)) - **Change replica support to native Rails adapter** ([krainboltgreene](https://github.com/mastodon/mastodon/pull/25693), [Gargron](https://github.com/mastodon/mastodon/pull/25849), [Gargron](https://github.com/mastodon/mastodon/pull/25874), [Gargron](https://github.com/mastodon/mastodon/pull/25851), [Gargron](https://github.com/mastodon/mastodon/pull/25977), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26074), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26326), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26386), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26856))
This is a breaking change, dropping `makara` support, and requiring you to update your database configuration if you are using replicas. This is a breaking change, dropping `makara` support, and requiring you to update your database configuration if you are using replicas.
To tell Mastodon to use a read replica, you can either set the `REPLICA_DB_NAME` environment variable (along with `REPLICA_DB_USER`, `REPLICA_DB_PASS`, `REPLICA_DB_HOST`, and `REPLICA_DB_PORT`, if they differ from the primary database), or the `REPLICA_DATABASE_URL` environment variable if your configuration is based on `DATABASE_URL`. To tell Mastodon to use a read replica, you can either set the `REPLICA_DB_NAME` environment variable (along with `REPLICA_DB_USER`, `REPLICA_DB_PASS`, `REPLICA_DB_HOST`, and `REPLICA_DB_PORT`, if they differ from the primary database), or the `REPLICA_DATABASE_URL` environment variable if your configuration is based on `DATABASE_URL`.
- Change DCT method used for JPEG encoding to float ([electroCutie](https://github.com/mastodon/mastodon/pull/26675))
- Change from `node-redis` to `ioredis` for streaming ([gmemstr](https://github.com/mastodon/mastodon/pull/26581))
- Change private statuses index to index without crutches ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/26713))
- Change video compression parameters ([Gargron](https://github.com/mastodon/mastodon/pull/26631), [Gargron](https://github.com/mastodon/mastodon/pull/26745), [Gargron](https://github.com/mastodon/mastodon/pull/26766), [Gargron](https://github.com/mastodon/mastodon/pull/26970))
- Change admin e-mail notification settings to be their own settings group ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/26596))
- Change opacity of the delete icon in the search field to be more visible ([AntoninDelFabbro](https://github.com/mastodon/mastodon/pull/26449))
- Change Account Search to prioritize username over display name ([jsgoldstein](https://github.com/mastodon/mastodon/pull/26623))
- Change follow recommendation materialized view to be faster in most cases ([renchap, ClearlyClaire](https://github.com/mastodon/mastodon/pull/26545)) - Change follow recommendation materialized view to be faster in most cases ([renchap, ClearlyClaire](https://github.com/mastodon/mastodon/pull/26545))
- Change `robots.txt` to block GPTBot ([Foritus](https://github.com/mastodon/mastodon/pull/26396)) - Change `robots.txt` to block GPTBot ([Foritus](https://github.com/mastodon/mastodon/pull/26396))
- Change header of hashtag timelines in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/26362), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26416)) - Change header of hashtag timelines in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/26362), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26416))
- Change streaming `/metrics` to include additional metrics ([ThisIsMissEm](https://github.com/mastodon/mastodon/pull/26299)) - Change streaming `/metrics` to include additional metrics ([ThisIsMissEm](https://github.com/mastodon/mastodon/pull/26299), [ThisIsMissEm](https://github.com/mastodon/mastodon/pull/26945))
- Change indexing frequency from 5 minutes to 1 minute, add locks to schedulers ([Gargron](https://github.com/mastodon/mastodon/pull/26304)) - Change indexing frequency from 5 minutes to 1 minute, add locks to schedulers ([Gargron](https://github.com/mastodon/mastodon/pull/26304))
- Change column link to add a better keyboard focus indicator ([teeerevor](https://github.com/mastodon/mastodon/pull/26278)) - Change column link to add a better keyboard focus indicator ([teeerevor](https://github.com/mastodon/mastodon/pull/26278))
- Change poll form element colors to fit with the rest of the ui ([teeerevor](https://github.com/mastodon/mastodon/pull/26139), [teeerevor](https://github.com/mastodon/mastodon/pull/26162), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26164)) - Change poll form element colors to fit with the rest of the ui ([teeerevor](https://github.com/mastodon/mastodon/pull/26139), [teeerevor](https://github.com/mastodon/mastodon/pull/26162), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26164))
- Change 'favourite' to 'favorite' for American English ([marekr](https://github.com/mastodon/mastodon/pull/24667), [gunchleoc](https://github.com/mastodon/mastodon/pull/26009), [nabijaczleweli](https://github.com/mastodon/mastodon/pull/26109)) - Change 'favourite' to 'favorite' for American English ([marekr](https://github.com/mastodon/mastodon/pull/24667), [gunchleoc](https://github.com/mastodon/mastodon/pull/26009), [nabijaczleweli](https://github.com/mastodon/mastodon/pull/26109))
- Change ActivityStreams representation of suspended accounts to not use a blank `name` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25276)) - Change ActivityStreams representation of suspended accounts to not use a blank `name` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25276))
- Change focus UI for keyboard only input ([teeerevor](https://github.com/mastodon/mastodon/pull/25935), [Gargron](https://github.com/mastodon/mastodon/pull/26125)) - Change focus UI for keyboard only input ([teeerevor](https://github.com/mastodon/mastodon/pull/25935), [Gargron](https://github.com/mastodon/mastodon/pull/26125), [Gargron](https://github.com/mastodon/mastodon/pull/26767))
- Change thread view to scroll to the selected post rather than the post being replied to ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/24685)) - Change thread view to scroll to the selected post rather than the post being replied to ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/24685))
- Change links in multi-column mode so tabs are open in single-column mode ([Signez](https://github.com/mastodon/mastodon/pull/25893), [Signez](https://github.com/mastodon/mastodon/pull/26070), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/25973)) - Change links in multi-column mode so tabs are open in single-column mode ([Signez](https://github.com/mastodon/mastodon/pull/25893), [Signez](https://github.com/mastodon/mastodon/pull/26070), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/25973), [Signez](https://github.com/mastodon/mastodon/pull/26019), [Signez](https://github.com/mastodon/mastodon/pull/26759))
- Change searching with `#` to include account index ([jsgoldstein](https://github.com/mastodon/mastodon/pull/25638)) - Change searching with `#` to include account index ([jsgoldstein](https://github.com/mastodon/mastodon/pull/25638))
- Change label and design of sensitive and unavailable media in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/25712), [Gargron](https://github.com/mastodon/mastodon/pull/26135), [Gargron](https://github.com/mastodon/mastodon/pull/26330)) - Change label and design of sensitive and unavailable media in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/25712), [Gargron](https://github.com/mastodon/mastodon/pull/26135), [Gargron](https://github.com/mastodon/mastodon/pull/26330))
- Change button colors to increase hover/focus contrast and consistency ([teeerevor](https://github.com/mastodon/mastodon/pull/25677), [Gargron](https://github.com/mastodon/mastodon/pull/25679)) - Change button colors to increase hover/focus contrast and consistency ([teeerevor](https://github.com/mastodon/mastodon/pull/25677), [Gargron](https://github.com/mastodon/mastodon/pull/25679))
@ -136,12 +295,11 @@ The following changelog entries focus on changes visible to users, administrator
- Change wording of “Content cache retention period” setting to highlight destructive implications ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23261)) - Change wording of “Content cache retention period” setting to highlight destructive implications ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23261))
- Change autolinking to allow carets in URL search params ([renchap](https://github.com/mastodon/mastodon/pull/25216)) - Change autolinking to allow carets in URL search params ([renchap](https://github.com/mastodon/mastodon/pull/25216))
- Change share action from being in action bar to being in dropdown in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/25105)) - Change share action from being in action bar to being in dropdown in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/25105))
- Change remote report processing to accept reports with long comments, but truncate them ([ThisIsMissEm](https://github.com/mastodon/mastodon/pull/25028))
- Change sessions to be ordered from most-recent to least-recently updated ([frankieroberto](https://github.com/mastodon/mastodon/pull/25005)) - Change sessions to be ordered from most-recent to least-recently updated ([frankieroberto](https://github.com/mastodon/mastodon/pull/25005))
- Change vacuum scheduler to also delete expired tokens and unused application records ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/24868), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/24871)) - Change vacuum scheduler to also delete expired tokens and unused application records ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/24868), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/24871))
- Change "Sign in" to "Login" ([Gargron](https://github.com/mastodon/mastodon/pull/24942)) - Change "Sign in" to "Login" ([Gargron](https://github.com/mastodon/mastodon/pull/24942))
- Change domain suspensions to also be checked before trying to fetch unknown remote resources ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/24535)) - Change domain suspensions to also be checked before trying to fetch unknown remote resources ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/24535))
- Change media components to use aspect-ratio rather than compute height themselves ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/24686), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/24943)) - Change media components to use aspect-ratio rather than compute height themselves ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/24686), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/24943), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26801))
- Change logo version in header based on screen size in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/24707)) - Change logo version in header based on screen size in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/24707))
- Change label from "For you" to "People" on explore screen in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/24706)) - Change label from "For you" to "People" on explore screen in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/24706))
- Change logged-out WebUI HTML pages to be cached for a few seconds ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/24708)) - Change logged-out WebUI HTML pages to be cached for a few seconds ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/24708))
@ -152,7 +310,7 @@ The following changelog entries focus on changes visible to users, administrator
- Change account search in moderation interface to allow searching by username including the leading `@` ([HeitorMC](https://github.com/mastodon/mastodon/pull/24242)) - Change account search in moderation interface to allow searching by username including the leading `@` ([HeitorMC](https://github.com/mastodon/mastodon/pull/24242))
- Change all components to use the same error page in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/24512)) - Change all components to use the same error page in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/24512))
- Change search pop-out in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/24305)) - Change search pop-out in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/24305))
- Change user settings to be stored in a more optimal way ([Gargron](https://github.com/mastodon/mastodon/pull/23630), [c960657](https://github.com/mastodon/mastodon/pull/24321), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/24453), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/24460), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/24558), [Gargron](https://github.com/mastodon/mastodon/pull/24761), [Gargron](https://github.com/mastodon/mastodon/pull/24783), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/25508), [jsgoldstein](https://github.com/mastodon/mastodon/pull/25340)) - Change user settings to be stored in a more optimal way ([Gargron](https://github.com/mastodon/mastodon/pull/23630), [c960657](https://github.com/mastodon/mastodon/pull/24321), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/24453), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/24460), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/24558), [Gargron](https://github.com/mastodon/mastodon/pull/24761), [Gargron](https://github.com/mastodon/mastodon/pull/24783), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/25508), [jsgoldstein](https://github.com/mastodon/mastodon/pull/25340), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26884), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/27012))
- Change media upload limits and remove client-side resizing ([Gargron](https://github.com/mastodon/mastodon/pull/23726)) - Change media upload limits and remove client-side resizing ([Gargron](https://github.com/mastodon/mastodon/pull/23726))
- Change design of account rows in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/24247), [Gargron](https://github.com/mastodon/mastodon/pull/24343), [Gargron](https://github.com/mastodon/mastodon/pull/24956), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/25131)) - Change design of account rows in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/24247), [Gargron](https://github.com/mastodon/mastodon/pull/24343), [Gargron](https://github.com/mastodon/mastodon/pull/24956), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/25131))
- Change log-out to use Single Logout when using external log-in through OIDC ([CSDUMMI](https://github.com/mastodon/mastodon/pull/24020)) - Change log-out to use Single Logout when using external log-in through OIDC ([CSDUMMI](https://github.com/mastodon/mastodon/pull/24020))
@ -175,6 +333,8 @@ The following changelog entries focus on changes visible to users, administrator
- **Remove support for Ruby 2.7** ([nschonni](https://github.com/mastodon/mastodon/pull/24237)) - **Remove support for Ruby 2.7** ([nschonni](https://github.com/mastodon/mastodon/pull/24237))
- **Remove clustering from streaming API** ([ThisIsMissEm](https://github.com/mastodon/mastodon/pull/24655)) - **Remove clustering from streaming API** ([ThisIsMissEm](https://github.com/mastodon/mastodon/pull/24655))
- **Remove anonymous access to the streaming API** ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23989)) - **Remove anonymous access to the streaming API** ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23989))
- Remove obfuscation of reply count in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/26768))
- Remove `kmr` from language selection, as it was a duplicate for `ku` ([gunchleoc](https://github.com/mastodon/mastodon/pull/26014), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26787))
- Remove 16:9 cropping from web UI ([Gargron](https://github.com/mastodon/mastodon/pull/26132)) - Remove 16:9 cropping from web UI ([Gargron](https://github.com/mastodon/mastodon/pull/26132))
- Remove back button from bookmarks, favourites and lists screens in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/26126)) - Remove back button from bookmarks, favourites and lists screens in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/26126))
- Remove display name input from sign-up form ([Gargron](https://github.com/mastodon/mastodon/pull/24704)) - Remove display name input from sign-up form ([Gargron](https://github.com/mastodon/mastodon/pull/24704))
@ -188,9 +348,23 @@ The following changelog entries focus on changes visible to users, administrator
- **Fix being unable to load past a full page of filtered posts in Home timeline** ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/24930)) - **Fix being unable to load past a full page of filtered posts in Home timeline** ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/24930))
- **Fix log-in flow when involving both OAuth and external authentication** ([CSDUMMI](https://github.com/mastodon/mastodon/pull/24073)) - **Fix log-in flow when involving both OAuth and external authentication** ([CSDUMMI](https://github.com/mastodon/mastodon/pull/24073))
- **Fix broken links in account gallery** ([c960657](https://github.com/mastodon/mastodon/pull/24218)) - **Fix broken links in account gallery** ([c960657](https://github.com/mastodon/mastodon/pull/24218))
- **Fix blocking subdomains of an already-blocked domain** ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/26392)) - **Fix migration handler not updating lists** ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/24808))
- Fix uploading of video files for which `ffprobe` reports `0/0` average framerate ([NicolaiSoeborg](https://github.com/mastodon/mastodon/pull/26500)) - Fix crash when viewing a moderation appeal and the moderator account has been deleted ([xrobau](https://github.com/mastodon/mastodon/pull/25900))
- Fix cached posts including stale stats ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/26409)) - Fix error in Web UI when server rules cannot be fetched ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/26957))
- Fix paragraph margins resulting in irregular read-more cut-off in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/26828))
- Fix notification permissions being requested immediately after login ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/26472))
- Fix performances of profile directory ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/26840), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26842))
- Fix mute button and volume slider feeling disconnected in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/26827), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26860))
- Fix “Scoped order is ignored, it's forced to be batch order.” warnings ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/26793))
- Fix blocked domain appearing in account feeds ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/26823))
- Fix invalid `Content-Type` header for WebP images ([c960657](https://github.com/mastodon/mastodon/pull/26773))
- Fix minor inefficiencies in `tootctl search deploy` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/26721))
- Fix filter form in profiles directory overflowing instead of wrapping ([arbolitoloco1](https://github.com/mastodon/mastodon/pull/26682))
- Fix sign up steps progress layout in right-to-left locales ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/26728))
- Fix bug with “favorited by” and “reblogged by“ view on posts only showing up to 40 items ([timothyjrogers](https://github.com/mastodon/mastodon/pull/26577), [timothyjrogers](https://github.com/mastodon/mastodon/pull/26574))
- Fix bad search type heuristic ([Gargron](https://github.com/mastodon/mastodon/pull/26673))
- Fix not being able to negate prefix clauses in search ([Gargron](https://github.com/mastodon/mastodon/pull/26672))
- Fix timeout on invalid set of exclusionary parameters in `/api/v1/timelines/public` ([danielmbrasil](https://github.com/mastodon/mastodon/pull/26239))
- Fix adding column with default value taking longer on Postgres >= 11 ([Gargron](https://github.com/mastodon/mastodon/pull/26375)) - Fix adding column with default value taking longer on Postgres >= 11 ([Gargron](https://github.com/mastodon/mastodon/pull/26375))
- Fix light theme select option for hashtags ([teeerevor](https://github.com/mastodon/mastodon/pull/26311)) - Fix light theme select option for hashtags ([teeerevor](https://github.com/mastodon/mastodon/pull/26311))
- Fix AVIF attachments ([c960657](https://github.com/mastodon/mastodon/pull/26264)) - Fix AVIF attachments ([c960657](https://github.com/mastodon/mastodon/pull/26264))
@ -255,6 +429,34 @@ The following changelog entries focus on changes visible to users, administrator
- Fix streaming API not being usable without `DATABASE_URL` ([Gargron](https://github.com/mastodon/mastodon/pull/23960)) - Fix streaming API not being usable without `DATABASE_URL` ([Gargron](https://github.com/mastodon/mastodon/pull/23960))
- Fix external authentication not running onboarding code for new users ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23458)) - Fix external authentication not running onboarding code for new users ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23458))
## [4.1.8] - 2023-09-19
### Fixed
- Fix post edits not being forwarded as expected ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/26936))
- Fix moderator rights inconsistencies ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/26729))
- Fix crash when encountering invalid URL ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/26814))
- Fix cached posts including stale stats ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/26409))
- Fix uploading of video files for which `ffprobe` reports `0/0` average framerate ([NicolaiSoeborg](https://github.com/mastodon/mastodon/pull/26500))
- Fix unexpected audio stream transcoding when uploaded video is eligible to passthrough ([yufushiro](https://github.com/mastodon/mastodon/pull/26608))
### Security
- Fix missing HTML sanitization in translation API (CVE-2023-42452, [GHSA-2693-xr3m-jhqr](https://github.com/mastodon/mastodon/security/advisories/GHSA-2693-xr3m-jhqr))
- Fix incorrect domain name normalization (CVE-2023-42451, [GHSA-v3xf-c9qf-j667](https://github.com/mastodon/mastodon/security/advisories/GHSA-v3xf-c9qf-j667))
## [4.1.7] - 2023-09-05
### Changed
- Change remote report processing to accept reports with long comments, but truncate them ([ThisIsMissEm](https://github.com/mastodon/mastodon/pull/25028))
### Fixed
- **Fix blocking subdomains of an already-blocked domain** ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/26392))
- Fix `/api/v1/timelines/tag/:hashtag` allowing for unauthenticated access when public preview is disabled ([danielmbrasil](https://github.com/mastodon/mastodon/pull/26237))
- Fix inefficiencies in `PlainTextFormatter` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/26727))
## [4.1.6] - 2023-07-31 ## [4.1.6] - 2023-07-31
### Fixed ### Fixed

View File

@ -1,8 +1,8 @@
# syntax=docker/dockerfile:1.4 # syntax=docker/dockerfile:1.4
# This needs to be bullseye-slim because the Ruby image is built on bullseye-slim # This needs to be bookworm-slim because the Ruby image is built on bookworm-slim
ARG NODE_VERSION="16.20-bullseye-slim" ARG NODE_VERSION="20.6-bookworm-slim"
FROM ghcr.io/moritzheiber/ruby-jemalloc:3.2.2-slim as ruby FROM ghcr.io/moritzheiber/ruby-jemalloc:3.2.3-slim as ruby
FROM node:${NODE_VERSION} as build FROM node:${NODE_VERSION} as build
COPY --link --from=ruby /opt/ruby /opt/ruby COPY --link --from=ruby /opt/ruby /opt/ruby
@ -17,10 +17,11 @@ COPY Gemfile* package.json yarn.lock /opt/mastodon/
# hadolint ignore=DL3008 # hadolint ignore=DL3008
RUN apt-get update && \ RUN apt-get update && \
apt-get -yq dist-upgrade && \
apt-get install -y --no-install-recommends build-essential \ apt-get install -y --no-install-recommends build-essential \
git \ git \
libicu-dev \ libicu-dev \
libidn11-dev \ libidn-dev \
libpq-dev \ libpq-dev \
libjemalloc-dev \ libjemalloc-dev \
zlib1g-dev \ zlib1g-dev \
@ -42,8 +43,8 @@ RUN apt-get update && \
FROM node:${NODE_VERSION} FROM node:${NODE_VERSION}
# Use those args to specify your own version flags & suffixes # Use those args to specify your own version flags & suffixes
ARG MASTODON_VERSION_FLAGS="" ARG MASTODON_VERSION_PRERELEASE=""
ARG MASTODON_VERSION_SUFFIX="" ARG MASTODON_VERSION_METADATA=""
ARG UID="991" ARG UID="991"
ARG GID="991" ARG GID="991"
@ -64,13 +65,13 @@ RUN apt-get update && \
apt-get -y --no-install-recommends install whois \ apt-get -y --no-install-recommends install whois \
wget \ wget \
procps \ procps \
libssl1.1 \ libssl3 \
libpq5 \ libpq5 \
imagemagick \ imagemagick \
ffmpeg \ ffmpeg \
libjemalloc2 \ libjemalloc2 \
libicu67 \ libicu72 \
libidn11 \ libidn12 \
libyaml-0-2 \ libyaml-0-2 \
file \ file \
ca-certificates \ ca-certificates \
@ -89,8 +90,8 @@ ENV RAILS_ENV="production" \
NODE_ENV="production" \ NODE_ENV="production" \
RAILS_SERVE_STATIC_FILES="true" \ RAILS_SERVE_STATIC_FILES="true" \
BIND="0.0.0.0" \ BIND="0.0.0.0" \
MASTODON_VERSION_FLAGS="${MASTODON_VERSION_FLAGS}" \ MASTODON_VERSION_PRERELEASE="${MASTODON_VERSION_PRERELEASE}" \
MASTODON_VERSION_SUFFIX="${MASTODON_VERSION_SUFFIX}" MASTODON_VERSION_METADATA="${MASTODON_VERSION_METADATA}"
# Set the run user # Set the run user
USER mastodon USER mastodon

View File

@ -27,4 +27,5 @@ More information on HTTP Signatures, as well as examples, can be found here: htt
- Linked-Data Signatures: https://docs.joinmastodon.org/spec/security/#ld - Linked-Data Signatures: https://docs.joinmastodon.org/spec/security/#ld
- Bearcaps: https://docs.joinmastodon.org/spec/bearcaps/ - Bearcaps: https://docs.joinmastodon.org/spec/bearcaps/
- Followers collection synchronization: https://git.activitypub.dev/ActivityPubDev/Fediverse-Enhancement-Proposals/src/branch/main/feps/fep-8fcf.md - Followers collection synchronization: https://codeberg.org/fediverse/fep/src/branch/main/fep/8fcf/fep-8fcf.md
- Search indexing consent for actors: https://codeberg.org/fediverse/fep/src/branch/main/fep/5feb/fep-5feb.md

View File

@ -61,7 +61,7 @@ gem 'kaminari', '~> 1.2'
gem 'link_header', '~> 0.0' gem 'link_header', '~> 0.0'
gem 'mime-types', '~> 3.5.0', require: 'mime/types/columnar' gem 'mime-types', '~> 3.5.0', require: 'mime/types/columnar'
gem 'nokogiri', '~> 1.15' gem 'nokogiri', '~> 1.15'
gem 'nsa', github: 'jhawthorn/nsa', ref: 'e020fcc3a54d993ab45b7194d89ab720296c111b' gem 'nsa'
gem 'oj', '~> 3.14' gem 'oj', '~> 3.14'
gem 'ox', '~> 2.14' gem 'ox', '~> 2.14'
gem 'parslet' gem 'parslet'
@ -110,7 +110,7 @@ group :test do
gem 'fuubar', '~> 2.5' gem 'fuubar', '~> 2.5'
# Extra RSpec extenion methods and helpers for sidekiq # Extra RSpec extenion methods and helpers for sidekiq
gem 'rspec-sidekiq', '~> 3.1' gem 'rspec-sidekiq', '~> 4.0'
# Browser integration testing # Browser integration testing
gem 'capybara', '~> 3.39' gem 'capybara', '~> 3.39'

View File

@ -7,17 +7,6 @@ GIT
hkdf (~> 0.2) hkdf (~> 0.2)
jwt (~> 2.0) 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)
GIT GIT
remote: https://github.com/mastodon/rails-settings-cached.git remote: https://github.com/mastodon/rails-settings-cached.git
revision: 86328ef0bd04ce21cc0504ff5e334591e8c2ccab revision: 86328ef0bd04ce21cc0504ff5e334591e8c2ccab
@ -39,47 +28,47 @@ GIT
GEM GEM
remote: https://rubygems.org/ remote: https://rubygems.org/
specs: specs:
actioncable (7.0.7) actioncable (7.0.8)
actionpack (= 7.0.7) actionpack (= 7.0.8)
activesupport (= 7.0.7) activesupport (= 7.0.8)
nio4r (~> 2.0) nio4r (~> 2.0)
websocket-driver (>= 0.6.1) websocket-driver (>= 0.6.1)
actionmailbox (7.0.7) actionmailbox (7.0.8)
actionpack (= 7.0.7) actionpack (= 7.0.8)
activejob (= 7.0.7) activejob (= 7.0.8)
activerecord (= 7.0.7) activerecord (= 7.0.8)
activestorage (= 7.0.7) activestorage (= 7.0.8)
activesupport (= 7.0.7) activesupport (= 7.0.8)
mail (>= 2.7.1) mail (>= 2.7.1)
net-imap net-imap
net-pop net-pop
net-smtp net-smtp
actionmailer (7.0.7) actionmailer (7.0.8)
actionpack (= 7.0.7) actionpack (= 7.0.8)
actionview (= 7.0.7) actionview (= 7.0.8)
activejob (= 7.0.7) activejob (= 7.0.8)
activesupport (= 7.0.7) activesupport (= 7.0.8)
mail (~> 2.5, >= 2.5.4) mail (~> 2.5, >= 2.5.4)
net-imap net-imap
net-pop net-pop
net-smtp net-smtp
rails-dom-testing (~> 2.0) rails-dom-testing (~> 2.0)
actionpack (7.0.7) actionpack (7.0.8)
actionview (= 7.0.7) actionview (= 7.0.8)
activesupport (= 7.0.7) activesupport (= 7.0.8)
rack (~> 2.0, >= 2.2.4) rack (~> 2.0, >= 2.2.4)
rack-test (>= 0.6.3) rack-test (>= 0.6.3)
rails-dom-testing (~> 2.0) rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.2.0) rails-html-sanitizer (~> 1.0, >= 1.2.0)
actiontext (7.0.7) actiontext (7.0.8)
actionpack (= 7.0.7) actionpack (= 7.0.8)
activerecord (= 7.0.7) activerecord (= 7.0.8)
activestorage (= 7.0.7) activestorage (= 7.0.8)
activesupport (= 7.0.7) activesupport (= 7.0.8)
globalid (>= 0.6.0) globalid (>= 0.6.0)
nokogiri (>= 1.8.5) nokogiri (>= 1.8.5)
actionview (7.0.7) actionview (7.0.8)
activesupport (= 7.0.7) activesupport (= 7.0.8)
builder (~> 3.1) builder (~> 3.1)
erubi (~> 1.4) erubi (~> 1.4)
rails-dom-testing (~> 2.0) rails-dom-testing (~> 2.0)
@ -89,27 +78,27 @@ GEM
activemodel (>= 4.1, < 7.1) activemodel (>= 4.1, < 7.1)
case_transform (>= 0.2) case_transform (>= 0.2)
jsonapi-renderer (>= 0.1.1.beta1, < 0.3) jsonapi-renderer (>= 0.1.1.beta1, < 0.3)
activejob (7.0.7) activejob (7.0.8)
activesupport (= 7.0.7) activesupport (= 7.0.8)
globalid (>= 0.3.6) globalid (>= 0.3.6)
activemodel (7.0.7) activemodel (7.0.8)
activesupport (= 7.0.7) activesupport (= 7.0.8)
activerecord (7.0.7) activerecord (7.0.8)
activemodel (= 7.0.7) activemodel (= 7.0.8)
activesupport (= 7.0.7) activesupport (= 7.0.8)
activestorage (7.0.7) activestorage (7.0.8)
actionpack (= 7.0.7) actionpack (= 7.0.8)
activejob (= 7.0.7) activejob (= 7.0.8)
activerecord (= 7.0.7) activerecord (= 7.0.8)
activesupport (= 7.0.7) activesupport (= 7.0.8)
marcel (~> 1.0) marcel (~> 1.0)
mini_mime (>= 1.1.0) mini_mime (>= 1.1.0)
activesupport (7.0.7) activesupport (7.0.8)
concurrent-ruby (~> 1.0, >= 1.0.2) concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 1.6, < 2) i18n (>= 1.6, < 2)
minitest (>= 5.1) minitest (>= 5.1)
tzinfo (~> 2.0) tzinfo (~> 2.0)
addressable (2.8.4) addressable (2.8.5)
public_suffix (>= 2.0.2, < 6.0) public_suffix (>= 2.0.2, < 6.0)
aes_key_wrap (1.1.0) aes_key_wrap (1.1.0)
airbrussh (1.4.1) airbrussh (1.4.1)
@ -124,8 +113,8 @@ GEM
attr_required (1.0.1) attr_required (1.0.1)
awrence (1.2.1) awrence (1.2.1)
aws-eventstream (1.2.0) aws-eventstream (1.2.0)
aws-partitions (1.793.0) aws-partitions (1.809.0)
aws-sdk-core (3.180.3) aws-sdk-core (3.181.0)
aws-eventstream (~> 1, >= 1.0.2) aws-eventstream (~> 1, >= 1.0.2)
aws-partitions (~> 1, >= 1.651.0) aws-partitions (~> 1, >= 1.651.0)
aws-sigv4 (~> 1.5) aws-sigv4 (~> 1.5)
@ -133,8 +122,8 @@ GEM
aws-sdk-kms (1.71.0) aws-sdk-kms (1.71.0)
aws-sdk-core (~> 3, >= 3.177.0) aws-sdk-core (~> 3, >= 3.177.0)
aws-sigv4 (~> 1.1) aws-sigv4 (~> 1.1)
aws-sdk-s3 (1.132.1) aws-sdk-s3 (1.133.0)
aws-sdk-core (~> 3, >= 3.179.0) aws-sdk-core (~> 3, >= 3.181.0)
aws-sdk-kms (~> 1) aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.6) aws-sigv4 (~> 1.6)
aws-sigv4 (1.6.0) aws-sigv4 (1.6.0)
@ -147,6 +136,8 @@ GEM
faraday_middleware (~> 1.0, >= 1.0.0.rc1) faraday_middleware (~> 1.0, >= 1.0.0.rc1)
net-http-persistent (~> 4.0) net-http-persistent (~> 4.0)
nokogiri (~> 1, >= 1.10.8) nokogiri (~> 1, >= 1.10.8)
base64 (0.1.1)
bcp47_spec (0.2.1)
bcrypt (3.1.18) bcrypt (3.1.18)
better_errors (2.10.1) better_errors (2.10.1)
erubi (>= 1.0.0) erubi (>= 1.0.0)
@ -202,7 +193,7 @@ GEM
activesupport activesupport
cbor (0.5.9.6) cbor (0.5.9.6)
charlock_holmes (0.7.7) charlock_holmes (0.7.7)
chewy (7.3.3) chewy (7.3.4)
activesupport (>= 5.2) activesupport (>= 5.2)
elasticsearch (>= 7.12.0, < 7.14.0) elasticsearch (>= 7.12.0, < 7.14.0)
elasticsearch-dsl elasticsearch-dsl
@ -210,7 +201,7 @@ GEM
climate_control (0.2.0) climate_control (0.2.0)
cocoon (1.2.15) cocoon (1.2.15)
color_diff (0.1) color_diff (0.1)
concurrent-ruby (1.2.2) concurrent-ruby (1.2.3)
connection_pool (2.4.1) connection_pool (2.4.1)
cose (1.3.0) cose (1.3.0)
cbor (~> 0.5.9) cbor (~> 0.5.9)
@ -323,7 +314,7 @@ GEM
ruby-progressbar (~> 1.4) ruby-progressbar (~> 1.4)
globalid (1.1.0) globalid (1.1.0)
activesupport (>= 5.0) activesupport (>= 5.0)
haml (6.1.1) haml (6.1.2)
temple (>= 0.8.2) temple (>= 0.8.2)
thor thor
tilt tilt
@ -332,7 +323,7 @@ GEM
activesupport (>= 5.1) activesupport (>= 5.1)
haml (>= 4.0.6) haml (>= 4.0.6)
railties (>= 5.1) railties (>= 5.1)
haml_lint (0.49.3) haml_lint (0.50.0)
haml (>= 4.0, < 6.2) haml (>= 4.0, < 6.2)
parallel (~> 1.10) parallel (~> 1.10)
rainbow rainbow
@ -376,19 +367,19 @@ GEM
ipaddress (0.8.3) ipaddress (0.8.3)
jmespath (1.6.2) jmespath (1.6.2)
json (2.6.3) json (2.6.3)
json-canonicalization (0.3.2) json-canonicalization (1.0.0)
json-jwt (1.15.3) json-jwt (1.15.3)
activesupport (>= 4.2) activesupport (>= 4.2)
aes_key_wrap aes_key_wrap
bindata bindata
httpclient httpclient
json-ld (3.2.5) json-ld (3.3.1)
htmlentities (~> 4.3) htmlentities (~> 4.3)
json-canonicalization (~> 0.3, >= 0.3.2) json-canonicalization (~> 1.0)
link_header (~> 0.0, >= 0.0.8) link_header (~> 0.0, >= 0.0.8)
multi_json (~> 1.15) multi_json (~> 1.15)
rack (>= 2.2, < 4) rack (>= 2.2, < 4)
rdf (~> 3.2, >= 3.2.10) rdf (~> 3.3)
json-ld-preloaded (3.2.2) json-ld-preloaded (3.2.2)
json-ld (~> 3.2) json-ld (~> 3.2)
rdf (~> 3.2) rdf (~> 3.2)
@ -408,7 +399,7 @@ GEM
activerecord activerecord
kaminari-core (= 1.2.2) kaminari-core (= 1.2.2)
kaminari-core (1.2.2) kaminari-core (1.2.2)
kt-paperclip (7.2.0) kt-paperclip (7.2.1)
activemodel (>= 4.2.0) activemodel (>= 4.2.0)
activesupport (>= 4.2.0) activesupport (>= 4.2.0)
marcel (~> 1.0.1) marcel (~> 1.0.1)
@ -451,11 +442,11 @@ GEM
hashie (~> 5.0) hashie (~> 5.0)
memory_profiler (1.0.1) memory_profiler (1.0.1)
method_source (1.0.0) method_source (1.0.0)
mime-types (3.5.0) mime-types (3.5.1)
mime-types-data (~> 3.2015) mime-types-data (~> 3.2015)
mime-types-data (3.2023.0808) mime-types-data (3.2023.0808)
mini_mime (1.1.5) mini_mime (1.1.5)
mini_portile2 (2.8.4) mini_portile2 (2.8.5)
minitest (5.19.0) minitest (5.19.0)
msgpack (1.7.1) msgpack (1.7.1)
multi_json (1.15.0) multi_json (1.15.0)
@ -477,11 +468,16 @@ GEM
net-smtp (0.3.3) net-smtp (0.3.3)
net-protocol net-protocol
net-ssh (7.1.0) net-ssh (7.1.0)
nio4r (2.5.9) nio4r (2.7.0)
nokogiri (1.15.4) nokogiri (1.16.2)
mini_portile2 (~> 2.8.2) mini_portile2 (~> 2.8.2)
racc (~> 1.4) racc (~> 1.4)
oj (3.15.0) 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.1)
omniauth (2.1.1) omniauth (2.1.1)
hashie (>= 3.4.6) hashie (>= 3.4.6)
rack (>= 2.2.3) rack (>= 2.2.3)
@ -518,8 +514,8 @@ GEM
parslet (2.0.0) parslet (2.0.0)
pastel (0.8.0) pastel (0.8.0)
tty-color (~> 0.5) tty-color (~> 0.5)
pg (1.5.3) pg (1.5.5)
pghero (3.3.3) pghero (3.3.4)
activerecord (>= 6) activerecord (>= 6)
posix-spawn (0.3.15) posix-spawn (0.3.15)
premailer (1.21.0) premailer (1.21.0)
@ -532,12 +528,12 @@ GEM
premailer (~> 1.7, >= 1.7.9) premailer (~> 1.7, >= 1.7.9)
private_address_check (0.5.0) private_address_check (0.5.0)
public_suffix (5.0.3) public_suffix (5.0.3)
puma (6.3.1) puma (6.4.2)
nio4r (~> 2.0) nio4r (~> 2.0)
pundit (2.3.0) pundit (2.3.0)
activesupport (>= 3.0.0) activesupport (>= 3.0.0)
raabro (1.4.0) raabro (1.4.0)
racc (1.7.1) racc (1.7.3)
rack (2.2.8) rack (2.2.8)
rack-attack (6.7.0) rack-attack (6.7.0)
rack (>= 1.0, < 4) rack (>= 1.0, < 4)
@ -555,20 +551,20 @@ GEM
rack rack
rack-test (2.1.0) rack-test (2.1.0)
rack (>= 1.3) rack (>= 1.3)
rails (7.0.7) rails (7.0.8)
actioncable (= 7.0.7) actioncable (= 7.0.8)
actionmailbox (= 7.0.7) actionmailbox (= 7.0.8)
actionmailer (= 7.0.7) actionmailer (= 7.0.8)
actionpack (= 7.0.7) actionpack (= 7.0.8)
actiontext (= 7.0.7) actiontext (= 7.0.8)
actionview (= 7.0.7) actionview (= 7.0.8)
activejob (= 7.0.7) activejob (= 7.0.8)
activemodel (= 7.0.7) activemodel (= 7.0.8)
activerecord (= 7.0.7) activerecord (= 7.0.8)
activestorage (= 7.0.7) activestorage (= 7.0.8)
activesupport (= 7.0.7) activesupport (= 7.0.8)
bundler (>= 1.15.0) bundler (>= 1.15.0)
railties (= 7.0.7) railties (= 7.0.8)
rails-controller-testing (1.0.5) rails-controller-testing (1.0.5)
actionpack (>= 5.0.1.rc1) actionpack (>= 5.0.1.rc1)
actionview (>= 5.0.1.rc1) actionview (>= 5.0.1.rc1)
@ -583,16 +579,17 @@ GEM
rails-i18n (7.0.7) rails-i18n (7.0.7)
i18n (>= 0.7, < 2) i18n (>= 0.7, < 2)
railties (>= 6.0.0, < 8) railties (>= 6.0.0, < 8)
railties (7.0.7) railties (7.0.8)
actionpack (= 7.0.7) actionpack (= 7.0.8)
activesupport (= 7.0.7) activesupport (= 7.0.8)
method_source method_source
rake (>= 12.2) rake (>= 12.2)
thor (~> 1.0) thor (~> 1.0)
zeitwerk (~> 2.5) zeitwerk (~> 2.5)
rainbow (3.1.1) rainbow (3.1.1)
rake (13.0.6) rake (13.0.6)
rdf (3.2.11) rdf (3.3.1)
bcp47_spec (~> 0.2)
link_header (~> 0.0, >= 0.0.8) link_header (~> 0.0, >= 0.0.8)
rdf-normalize (0.6.1) rdf-normalize (0.6.1)
rdf (~> 3.2) rdf (~> 3.2)
@ -632,12 +629,15 @@ GEM
rspec-expectations (~> 3.12) rspec-expectations (~> 3.12)
rspec-mocks (~> 3.12) rspec-mocks (~> 3.12)
rspec-support (~> 3.12) rspec-support (~> 3.12)
rspec-sidekiq (3.1.0) rspec-sidekiq (4.0.1)
rspec-core (~> 3.0, >= 3.0.0) rspec-core (~> 3.0)
sidekiq (>= 2.4.0) rspec-expectations (~> 3.0)
rspec-support (3.12.0) rspec-mocks (~> 3.0)
sidekiq (>= 5, < 8)
rspec-support (3.12.1)
rspec_chunked (0.6) rspec_chunked (0.6)
rubocop (1.54.2) rubocop (1.56.3)
base64 (~> 0.1.1)
json (~> 2.3) json (~> 2.3)
language_server-protocol (>= 3.17.0) language_server-protocol (>= 3.17.0)
parallel (~> 1.10) parallel (~> 1.10)
@ -645,7 +645,7 @@ GEM
rainbow (>= 2.2.2, < 4.0) rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 1.8, < 3.0) regexp_parser (>= 1.8, < 3.0)
rexml (>= 3.2.5, < 4.0) rexml (>= 3.2.5, < 4.0)
rubocop-ast (>= 1.28.0, < 2.0) rubocop-ast (>= 1.28.1, < 2.0)
ruby-progressbar (~> 1.7) ruby-progressbar (~> 1.7)
unicode-display_width (>= 2.4.0, < 3.0) unicode-display_width (>= 2.4.0, < 3.0)
rubocop-ast (1.29.0) rubocop-ast (1.29.0)
@ -654,14 +654,14 @@ GEM
rubocop (~> 1.41) rubocop (~> 1.41)
rubocop-factory_bot (2.23.1) rubocop-factory_bot (2.23.1)
rubocop (~> 1.33) rubocop (~> 1.33)
rubocop-performance (1.18.0) rubocop-performance (1.19.0)
rubocop (>= 1.7.0, < 2.0) rubocop (>= 1.7.0, < 2.0)
rubocop-ast (>= 0.4.0) rubocop-ast (>= 0.4.0)
rubocop-rails (2.20.2) rubocop-rails (2.20.2)
activesupport (>= 4.2.0) activesupport (>= 4.2.0)
rack (>= 1.1) rack (>= 1.1)
rubocop (>= 1.33.0, < 2.0) rubocop (>= 1.33.0, < 2.0)
rubocop-rspec (2.22.0) rubocop-rspec (2.23.2)
rubocop (~> 1.33) rubocop (~> 1.33)
rubocop-capybara (~> 2.17) rubocop-capybara (~> 2.17)
rubocop-factory_bot (~> 2.22) rubocop-factory_bot (~> 2.22)
@ -687,7 +687,7 @@ GEM
rubyzip (>= 1.2.2, < 3.0) rubyzip (>= 1.2.2, < 3.0)
websocket (~> 1.0) websocket (~> 1.0)
semantic_range (3.0.0) semantic_range (3.0.0)
sidekiq (6.5.9) sidekiq (6.5.12)
connection_pool (>= 2.2.5, < 3) connection_pool (>= 2.2.5, < 3)
rack (~> 2.0) rack (~> 2.0)
redis (>= 4.5.0, < 5) redis (>= 4.5.0, < 5)
@ -697,7 +697,7 @@ GEM
rufus-scheduler (~> 3.2) rufus-scheduler (~> 3.2)
sidekiq (>= 6, < 8) sidekiq (>= 6, < 8)
tilt (>= 1.4.0) tilt (>= 1.4.0)
sidekiq-unique-jobs (7.1.29) sidekiq-unique-jobs (7.1.33)
brpoplpush-redis_script (> 0.1.1, <= 2.0.0) brpoplpush-redis_script (> 0.1.1, <= 2.0.0)
concurrent-ruby (~> 1.0, >= 1.0.5) concurrent-ruby (~> 1.0, >= 1.0.5)
redis (< 5.0) redis (< 5.0)
@ -727,7 +727,7 @@ GEM
net-ssh (>= 2.8.0) net-ssh (>= 2.8.0)
stackprof (0.2.25) stackprof (0.2.25)
statsd-ruby (1.5.0) statsd-ruby (1.5.0)
stoplight (3.0.1) stoplight (3.0.2)
redlock (~> 1.0) redlock (~> 1.0)
strong_migrations (0.8.0) strong_migrations (0.8.0)
activerecord (>= 5.2) activerecord (>= 5.2)
@ -741,8 +741,8 @@ GEM
unicode-display_width (>= 1.1.1, < 3) unicode-display_width (>= 1.1.1, < 3)
terrapin (0.6.0) terrapin (0.6.0)
climate_control (>= 0.0.3, < 1.0) climate_control (>= 0.0.3, < 1.0)
test-prof (1.2.2) test-prof (1.2.3)
thor (1.2.2) thor (1.3.0)
tilt (2.2.0) tilt (2.2.0)
timeout (0.4.0) timeout (0.4.0)
tpm-key_attestation (0.12.0) tpm-key_attestation (0.12.0)
@ -791,7 +791,7 @@ GEM
webfinger (1.2.0) webfinger (1.2.0)
activesupport activesupport
httpclient (>= 2.4) httpclient (>= 2.4)
webmock (3.18.1) webmock (3.19.1)
addressable (>= 2.8.0) addressable (>= 2.8.0)
crack (>= 0.3.2) crack (>= 0.3.2)
hashdiff (>= 0.4.0, < 2.0.0) hashdiff (>= 0.4.0, < 2.0.0)
@ -878,7 +878,7 @@ DEPENDENCIES
net-http (~> 0.3.2) net-http (~> 0.3.2)
net-ldap (~> 0.18) net-ldap (~> 0.18)
nokogiri (~> 1.15) nokogiri (~> 1.15)
nsa! nsa
oj (~> 3.14) oj (~> 3.14)
omniauth (~> 2.0) omniauth (~> 2.0)
omniauth-cas! omniauth-cas!
@ -909,7 +909,7 @@ DEPENDENCIES
redis-namespace (~> 1.10) redis-namespace (~> 1.10)
rqrcode (~> 2.2) rqrcode (~> 2.2)
rspec-rails (~> 6.0) rspec-rails (~> 6.0)
rspec-sidekiq (~> 3.1) rspec-sidekiq (~> 4.0)
rspec_chunked (~> 0.6) rspec_chunked (~> 0.6)
rubocop rubocop
rubocop-capybara rubocop-capybara

View File

@ -1,4 +1,4 @@
web: env PORT=3000 RAILS_ENV=development bundle exec puma -C config/puma.rb web: env PORT=3000 RAILS_ENV=development bundle exec puma -C config/puma.rb
sidekiq: env PORT=3000 RAILS_ENV=development bundle exec sidekiq sidekiq: env PORT=3000 RAILS_ENV=development bundle exec sidekiq
stream: env PORT=4000 yarn run start stream: env PORT=4000 yarn run start
webpack: ./bin/webpack-dev-server --listen-host 0.0.0.0 webpack: bin/webpack-dev-server

View File

@ -59,13 +59,13 @@ Mastodon acts as an OAuth2 provider, so 3rd party apps can use the REST and Stre
## Deployment ## Deployment
### Tech stack: ### Tech stack
- **Ruby on Rails** powers the REST API and other web pages - **Ruby on Rails** powers the REST API and other web pages
- **React.js** and Redux are used for the dynamic parts of the interface - **React.js** and Redux are used for the dynamic parts of the interface
- **Node.js** powers the streaming API - **Node.js** powers the streaming API
### Requirements: ### Requirements
- **PostgreSQL** 9.5+ - **PostgreSQL** 9.5+
- **Redis** 4+ - **Redis** 4+
@ -74,6 +74,10 @@ Mastodon acts as an OAuth2 provider, so 3rd party apps can use the REST and Stre
The repository includes deployment configurations for **Docker and docker-compose** as well as specific platforms like **Heroku**, **Scalingo**, and **Nanobox**. For Helm charts, reference the [mastodon/chart repository](https://github.com/mastodon/chart). The [**standalone** installation guide](https://docs.joinmastodon.org/admin/install/) is available in the documentation. The repository includes deployment configurations for **Docker and docker-compose** as well as specific platforms like **Heroku**, **Scalingo**, and **Nanobox**. For Helm charts, reference the [mastodon/chart repository](https://github.com/mastodon/chart). The [**standalone** installation guide](https://docs.joinmastodon.org/admin/install/) is available in the documentation.
## Development
### Vagrant
A **Vagrant** configuration is included for development purposes. To use it, complete the following steps: A **Vagrant** configuration is included for development purposes. To use it, complete the following steps:
- Install Vagrant and Virtualbox - Install Vagrant and Virtualbox
@ -82,9 +86,11 @@ A **Vagrant** configuration is included for development purposes. To use it, com
- Run `vagrant ssh -c "cd /vagrant && foreman start"` - Run `vagrant ssh -c "cd /vagrant && foreman start"`
- Open `http://mastodon.local` in your browser - Open `http://mastodon.local` in your browser
### MacOS
To set up **MacOS** for native development, complete the following steps: To set up **MacOS** for native development, complete the following steps:
- Install the latest stable Ruby version (use a ruby version manager for easy installation and management of ruby versions) - Install the latest stable Ruby version (use a Ruby version manager for easy installation and management of Ruby versions)
- Run `brew install postgresql@14` - Run `brew install postgresql@14`
- Run `brew install redis` - Run `brew install redis`
- Run `brew install imagemagick` - Run `brew install imagemagick`
@ -94,15 +100,27 @@ To set up **MacOS** for native development, complete the following steps:
- Run `bundle exec rails db:setup` (optionally prepend `RAILS_ENV=development` to target the dev environment) - Run `bundle exec rails db:setup` (optionally prepend `RAILS_ENV=development` to target the dev environment)
- Finally, run `overmind start -f Procfile.dev` - Finally, run `overmind start -f Procfile.dev`
### Getting Started with GitHub Codespaces ### Docker
To get started, create a codespace for this repository by clicking this 👇 For development with **Docker**, complete the following steps:
[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://github.com/codespaces/new?hide_repo_select=true&ref=main&repo=52281283) - Install Docker Desktop
- Run `docker compose -f .devcontainer/docker-compose.yml up -d`
- Run `docker compose -f .devcontainer/docker-compose.yml exec app .devcontainer/post-create.sh`
- Finally, run `docker compose -f .devcontainer/docker-compose.yml exec app foreman start -f Procfile.dev`
A codespace will open in a web-based version of Visual Studio Code. The [dev container](.devcontainer/devcontainer.json) is fully configured with the software needed for this project. If you are using an IDE with [support for the Development Container specification](https://containers.dev/supporting), it will run the above `docker compose` commands automatically. For **Visual Studio Code** this requires the [Dev Container extension](https://containers.dev/supporting#dev-containers).
**Note**: Dev containers are an open spec that is supported by [GitHub Codespaces](https://github.com/codespaces) and [other tools](https://containers.dev/supporting). ### GitHub Codespaces
To get you coding in just a few minutes, GitHub Codespaces provides a web-based version of Visual Studio Code and a cloud-hosted development environment fully configured with the software needed for this project..
- Click this button to create a new codespace:<br>
[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://github.com/codespaces/new?hide_repo_select=true&ref=main&repo=52281283&devcontainer_path=.devcontainer%2Fcodespaces%2Fdevcontainer.json)
- Wait for the environment to build. This will take a few minutes.
- When the editor is ready, run `foreman start -f Procfile.dev` in the terminal.
- After a few seconds, a popup will appear with a button labeled _Open in Browser_. This will open Mastodon.
- On the _Ports_ tab, right click on the “stream” row and select _Port visibility__Public_.
## Contributing ## Contributing
@ -114,7 +132,7 @@ You can open issues for bugs you've found or features you think are missing. You
## License ## License
Copyright (C) 2016-2022 Eugen Rochko & other Mastodon contributors (see [AUTHORS.md](AUTHORS.md)) Copyright (C) 2016-2023 Eugen Rochko & other Mastodon contributors (see [AUTHORS.md](AUTHORS.md))
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.

View File

@ -1,8 +1,11 @@
# Security Policy # Security Policy
If you believe you've identified a security vulnerability in Mastodon (a bug that allows something to happen that shouldn't be possible), you can reach us at <security@joinmastodon.org>. If you believe you've identified a security vulnerability in Mastodon (a bug that allows something to happen that shouldn't be possible), you can either:
You should _not_ report such issues on GitHub or in other public spaces to give us time to publish a fix for the issue without exposing Mastodon's users to increased risk. - open a [Github security issue on the Mastodon project](https://github.com/mastodon/mastodon/security/advisories/new)
- reach us at <security@joinmastodon.org>
You should _not_ report such issues on public GitHub issues or in other public spaces to give us time to publish a fix for the issue without exposing Mastodon's users to increased risk.
## Scope ## Scope
@ -11,8 +14,7 @@ A "vulnerability in Mastodon" is a vulnerability in the code distributed through
## Supported Versions ## Supported Versions
| Version | Supported | | Version | Supported |
| ------- | --------- | | ------- | ---------------- |
| 4.2.x | Yes |
| 4.1.x | Yes | | 4.1.x | Yes |
| 4.0.x | Yes | | < 4.1 | No |
| 3.5.x | Yes |
| < 3.5 | No |

44
Vagrantfile vendored
View File

@ -60,6 +60,38 @@ sudo usermod -a -G rvm $USER
SCRIPT SCRIPT
$provisionElasticsearch = <<SCRIPT
# Install Elastic Search
sudo apt install openjdk-17-jre-headless -y
sudo wget -O /usr/share/keyrings/elasticsearch.asc https://artifacts.elastic.co/GPG-KEY-elasticsearch
sudo sh -c 'echo "deb [signed-by=/usr/share/keyrings/elasticsearch.asc] https://artifacts.elastic.co/packages/7.x/apt stable main" > /etc/apt/sources.list.d/elastic-7.x.list'
sudo apt update
sudo apt install elasticsearch -y
sudo systemctl daemon-reload
sudo systemctl enable --now elasticsearch
echo 'path.data: /var/lib/elasticsearch
path.logs: /var/log/elasticsearch
network.host: 0.0.0.0
http.port: 9200
discovery.seed_hosts: ["localhost"]
cluster.initial_master_nodes: ["node-1"]
xpack.security.enabled: false' > /etc/elasticsearch/elasticsearch.yml
sudo systemctl restart elasticsearch
# Install Kibana
sudo apt install kibana -y
sudo systemctl enable --now kibana
echo 'server.host: "0.0.0.0"
elasticsearch.hosts: ["http://localhost:9200"]' > /etc/kibana/kibana.yml
sudo systemctl restart kibana
SCRIPT
$provisionB = <<SCRIPT $provisionB = <<SCRIPT
source "/etc/profile.d/rvm.sh" source "/etc/profile.d/rvm.sh"
@ -102,10 +134,8 @@ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
config.vm.provider :virtualbox do |vb| config.vm.provider :virtualbox do |vb|
vb.name = "mastodon" vb.name = "mastodon"
vb.customize ["modifyvm", :id, "--memory", "2048"] vb.customize ["modifyvm", :id, "--memory", "8192"]
# Increase the number of CPUs. Uncomment and adjust to vb.customize ["modifyvm", :id, "--cpus", "3"]
# increase performance
# vb.customize ["modifyvm", :id, "--cpus", "3"]
# Disable VirtualBox DNS proxy to skip long-delay IPv6 resolutions. # Disable VirtualBox DNS proxy to skip long-delay IPv6 resolutions.
# https://github.com/mitchellh/vagrant/issues/1172 # https://github.com/mitchellh/vagrant/issues/1172
@ -141,9 +171,15 @@ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
config.vm.network :forwarded_port, guest: 3000, host: 3000 config.vm.network :forwarded_port, guest: 3000, host: 3000
config.vm.network :forwarded_port, guest: 4000, host: 4000 config.vm.network :forwarded_port, guest: 4000, host: 4000
config.vm.network :forwarded_port, guest: 8080, host: 8080 config.vm.network :forwarded_port, guest: 8080, host: 8080
config.vm.network :forwarded_port, guest: 9200, host: 9200
config.vm.network :forwarded_port, guest: 9300, host: 9300
config.vm.network :forwarded_port, guest: 9243, host: 9243
config.vm.network :forwarded_port, guest: 5601, host: 5601
# Full provisioning script, only runs on first 'vagrant up' or with 'vagrant provision' # Full provisioning script, only runs on first 'vagrant up' or with 'vagrant provision'
config.vm.provision :shell, inline: $provisionA, privileged: false, reset: true config.vm.provision :shell, inline: $provisionA, privileged: false, reset: true
# Run with elevated privileges for Elasticsearch installation
config.vm.provision :shell, inline: $provisionElasticsearch, privileged: true
config.vm.provision :shell, inline: $provisionB, privileged: false config.vm.provision :shell, inline: $provisionB, privileged: false
config.vm.post_up_message = <<MESSAGE config.vm.post_up_message = <<MESSAGE

View File

@ -1,6 +1,8 @@
# frozen_string_literal: true # frozen_string_literal: true
class AccountsIndex < Chewy::Index class AccountsIndex < Chewy::Index
include DatetimeClampingConcern
settings index: index_preset(refresh_interval: '30s'), analysis: { settings index: index_preset(refresh_interval: '30s'), analysis: {
filter: { filter: {
english_stop: { english_stop: {
@ -21,12 +23,13 @@ class AccountsIndex < Chewy::Index
analyzer: { analyzer: {
natural: { natural: {
tokenizer: 'uax_url_email', tokenizer: 'standard',
filter: %w( filter: %w(
english_possessive_stemmer
lowercase lowercase
asciifolding asciifolding
cjk_width cjk_width
elision
english_possessive_stemmer
english_stop english_stop
english_stemmer english_stemmer
), ),
@ -59,9 +62,9 @@ class AccountsIndex < Chewy::Index
field(:following_count, type: 'long') field(:following_count, type: 'long')
field(:followers_count, type: 'long') field(:followers_count, type: 'long')
field(:properties, type: 'keyword', value: ->(account) { account.searchable_properties }) field(:properties, type: 'keyword', value: ->(account) { account.searchable_properties })
field(:last_status_at, type: 'date', value: ->(account) { account.last_status_at || account.created_at }) field(:last_status_at, type: 'date', value: ->(account) { clamp_date(account.last_status_at || account.created_at) })
field(:display_name, type: 'text', analyzer: 'verbatim') { field :edge_ngram, type: 'text', analyzer: 'edge_ngram', search_analyzer: 'verbatim' } field(:display_name, type: 'text', analyzer: 'verbatim') { field :edge_ngram, type: 'text', analyzer: 'edge_ngram', search_analyzer: 'verbatim' }
field(:username, type: 'text', analyzer: 'verbatim', value: ->(account) { [account.username, account.domain].compact.join('@') }) { field :edge_ngram, type: 'text', analyzer: 'edge_ngram', search_analyzer: 'verbatim' } field(:username, type: 'text', analyzer: 'verbatim', value: ->(account) { [account.username, account.domain].compact.join('@') }) { field :edge_ngram, type: 'text', analyzer: 'edge_ngram', search_analyzer: 'verbatim' }
field(:text, type: 'text', value: ->(account) { account.searchable_text }) { field :stemmed, type: 'text', analyzer: 'natural' } field(:text, type: 'text', analyzer: 'verbatim', value: ->(account) { account.searchable_text }) { field :stemmed, type: 'text', analyzer: 'natural' }
end end
end end

View File

@ -0,0 +1,14 @@
# frozen_string_literal: true
module DatetimeClampingConcern
extend ActiveSupport::Concern
MIN_ISO8601_DATETIME = '0000-01-01T00:00:00Z'.to_datetime.freeze
MAX_ISO8601_DATETIME = '9999-12-31T23:59:59Z'.to_datetime.freeze
class_methods do
def clamp_date(datetime)
datetime.clamp(MIN_ISO8601_DATETIME, MAX_ISO8601_DATETIME)
end
end
end

View File

@ -0,0 +1,69 @@
# frozen_string_literal: true
class PublicStatusesIndex < Chewy::Index
include DatetimeClampingConcern
settings index: index_preset(refresh_interval: '30s', number_of_shards: 5), analysis: {
filter: {
english_stop: {
type: 'stop',
stopwords: '_english_',
},
english_stemmer: {
type: 'stemmer',
language: 'english',
},
english_possessive_stemmer: {
type: 'stemmer',
language: 'possessive_english',
},
},
analyzer: {
verbatim: {
tokenizer: 'uax_url_email',
filter: %w(lowercase),
},
content: {
tokenizer: 'standard',
filter: %w(
lowercase
asciifolding
cjk_width
elision
english_possessive_stemmer
english_stop
english_stemmer
),
},
hashtag: {
tokenizer: 'keyword',
filter: %w(
word_delimiter_graph
lowercase
asciifolding
cjk_width
),
},
},
}
index_scope ::Status.unscoped
.kept
.indexable
.includes(:media_attachments, :preloadable_poll, :preview_cards, :tags)
root date_detection: false do
field(:id, type: 'long')
field(:account_id, type: 'long')
field(:text, type: 'text', analyzer: 'verbatim', value: ->(status) { status.searchable_text }) { field(:stemmed, type: 'text', analyzer: 'content') }
field(:tags, type: 'text', analyzer: 'hashtag', value: ->(status) { status.tags.map(&:display_name) })
field(:language, type: 'keyword')
field(:properties, type: 'keyword', value: ->(status) { status.searchable_properties })
field(:created_at, type: 'date', value: ->(status) { clamp_date(status.created_at) })
end
end

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
class StatusesIndex < Chewy::Index class StatusesIndex < Chewy::Index
include FormattingHelper include DatetimeClampingConcern
settings index: index_preset(refresh_interval: '30s', number_of_shards: 5), analysis: { settings index: index_preset(refresh_interval: '30s', number_of_shards: 5), analysis: {
filter: { filter: {
@ -9,67 +9,59 @@ class StatusesIndex < Chewy::Index
type: 'stop', type: 'stop',
stopwords: '_english_', stopwords: '_english_',
}, },
english_stemmer: { english_stemmer: {
type: 'stemmer', type: 'stemmer',
language: 'english', language: 'english',
}, },
english_possessive_stemmer: { english_possessive_stemmer: {
type: 'stemmer', type: 'stemmer',
language: 'possessive_english', language: 'possessive_english',
}, },
}, },
analyzer: { analyzer: {
content: { verbatim: {
tokenizer: 'uax_url_email', tokenizer: 'uax_url_email',
filter: %w(lowercase),
},
content: {
tokenizer: 'standard',
filter: %w( filter: %w(
english_possessive_stemmer
lowercase lowercase
asciifolding asciifolding
cjk_width cjk_width
elision
english_possessive_stemmer
english_stop english_stop
english_stemmer english_stemmer
), ),
}, },
hashtag: {
tokenizer: 'keyword',
filter: %w(
word_delimiter_graph
lowercase
asciifolding
cjk_width
),
},
}, },
} }
# We do not use delete_if option here because it would call a method that we index_scope ::Status.unscoped.kept.without_reblogs.includes(:media_attachments, :preview_cards, :local_mentioned, :local_favorited, :local_reblogged, :local_bookmarked, :tags, preloadable_poll: :local_voters), delete_if: ->(status) { status.searchable_by.empty? }
# expect to be called with crutches without crutches, causing n+1 queries
index_scope ::Status.unscoped.kept.without_reblogs.includes(:media_attachments, :preloadable_poll)
crutch :mentions do |collection|
data = ::Mention.where(status_id: collection.map(&:id)).where(account: Account.local, silent: false).pluck(:status_id, :account_id)
data.each.with_object({}) { |(id, name), result| (result[id] ||= []).push(name) }
end
crutch :favourites do |collection|
data = ::Favourite.where(status_id: collection.map(&:id)).where(account: Account.local).pluck(:status_id, :account_id)
data.each.with_object({}) { |(id, name), result| (result[id] ||= []).push(name) }
end
crutch :reblogs do |collection|
data = ::Status.where(reblog_of_id: collection.map(&:id)).where(account: Account.local).pluck(:reblog_of_id, :account_id)
data.each.with_object({}) { |(id, name), result| (result[id] ||= []).push(name) }
end
crutch :bookmarks do |collection|
data = ::Bookmark.where(status_id: collection.map(&:id)).where(account: Account.local).pluck(:status_id, :account_id)
data.each.with_object({}) { |(id, name), result| (result[id] ||= []).push(name) }
end
crutch :votes do |collection|
data = ::PollVote.joins(:poll).where(poll: { status_id: collection.map(&:id) }).where(account: Account.local).pluck(:status_id, :account_id)
data.each.with_object({}) { |(id, name), result| (result[id] ||= []).push(name) }
end
root date_detection: false do root date_detection: false do
field :id, type: 'long' field(:id, type: 'long')
field :account_id, type: 'long' field(:account_id, type: 'long')
field(:text, type: 'text', analyzer: 'verbatim', value: ->(status) { status.searchable_text }) { field(:stemmed, type: 'text', analyzer: 'content') }
field :text, type: 'text', value: ->(status) { status.searchable_text } do field(:tags, type: 'text', analyzer: 'hashtag', value: ->(status) { status.tags.map(&:display_name) })
field :stemmed, type: 'text', analyzer: 'content' field(:searchable_by, type: 'long', value: ->(status) { status.searchable_by })
end field(:language, type: 'keyword')
field(:properties, type: 'keyword', value: ->(status) { status.searchable_properties })
field :searchable_by, type: 'long', value: ->(status, crutches) { status.searchable_by(crutches) } field(:created_at, type: 'date', value: ->(status) { clamp_date(status.created_at) })
end end
end end

View File

@ -1,16 +1,27 @@
# frozen_string_literal: true # frozen_string_literal: true
class TagsIndex < Chewy::Index class TagsIndex < Chewy::Index
include DatetimeClampingConcern
settings index: index_preset(refresh_interval: '30s'), analysis: { settings index: index_preset(refresh_interval: '30s'), analysis: {
analyzer: { analyzer: {
content: { content: {
tokenizer: 'keyword', tokenizer: 'keyword',
filter: %w(lowercase asciifolding cjk_width), filter: %w(
word_delimiter_graph
lowercase
asciifolding
cjk_width
),
}, },
edge_ngram: { edge_ngram: {
tokenizer: 'edge_ngram', tokenizer: 'edge_ngram',
filter: %w(lowercase asciifolding cjk_width), filter: %w(
lowercase
asciifolding
cjk_width
),
}, },
}, },
@ -30,12 +41,9 @@ class TagsIndex < Chewy::Index
end end
root date_detection: false do root date_detection: false do
field :name, type: 'text', analyzer: 'content' do field(:name, type: 'text', analyzer: 'content', value: :display_name) { field(:edge_ngram, type: 'text', analyzer: 'edge_ngram', search_analyzer: 'content') }
field :edge_ngram, type: 'text', analyzer: 'edge_ngram', search_analyzer: 'content' field(:reviewed, type: 'boolean', value: ->(tag) { tag.reviewed? })
end field(:usage, type: 'long', value: ->(tag, crutches) { tag.history.aggregate(crutches.time_period).accounts })
field(:last_status_at, type: 'date', value: ->(tag) { clamp_date(tag.last_status_at || tag.created_at) })
field :reviewed, type: 'boolean', value: ->(tag) { tag.reviewed? }
field :usage, type: 'long', value: ->(tag, crutches) { tag.history.aggregate(crutches.time_period).accounts }
field :last_status_at, type: 'date', value: ->(tag) { tag.last_status_at || tag.created_at }
end end
end end

View File

@ -21,7 +21,7 @@ module Admin
account_action.save! account_action.save!
if account_action.with_report? if account_action.with_report?
redirect_to admin_reports_path, notice: I18n.t('admin.reports.processed_msg', id: params[:report_id]) redirect_to admin_reports_path, notice: I18n.t('admin.reports.processed_msg', id: resource_params[:report_id])
else else
redirect_to admin_account_path(@account.id) redirect_to admin_account_path(@account.id)
end end

View File

@ -0,0 +1,18 @@
# frozen_string_literal: true
module Admin
class SoftwareUpdatesController < BaseController
before_action :check_enabled!
def index
authorize :software_update, :index?
@software_updates = SoftwareUpdate.all.sort_by(&:gem_version)
end
private
def check_enabled!
not_found unless SoftwareUpdate.check_enabled?
end
end
end

View File

@ -30,6 +30,7 @@ class Api::V1::Accounts::CredentialsController < Api::BaseController
:bot, :bot,
:discoverable, :discoverable,
:hide_collections, :hide_collections,
:indexable,
fields_attributes: [:name, :value] fields_attributes: [:name, :value]
) )
end end

View File

@ -25,6 +25,6 @@ class Api::V1::Accounts::NotesController < Api::BaseController
end end
def relationships_presenter def relationships_presenter
AccountRelationshipsPresenter.new([@account.id], current_user.account_id) AccountRelationshipsPresenter.new([@account], current_user.account_id)
end end
end end

View File

@ -25,6 +25,6 @@ class Api::V1::Accounts::PinsController < Api::BaseController
end end
def relationships_presenter def relationships_presenter
AccountRelationshipsPresenter.new([@account.id], current_user.account_id) AccountRelationshipsPresenter.new([@account], current_user.account_id)
end end
end end

View File

@ -5,11 +5,10 @@ class Api::V1::Accounts::RelationshipsController < Api::BaseController
before_action :require_user! before_action :require_user!
def index def index
accounts = Account.without_suspended.where(id: account_ids).select('id') @accounts = Account.without_suspended.where(id: account_ids).select(:id, :domain).to_a
# .where doesn't guarantee that our results are in the same order # .where doesn't guarantee that our results are in the same order
# we requested them, so return the "right" order to the requestor. # we requested them, so return the "right" order to the requestor.
@accounts = accounts.index_by(&:id).values_at(*account_ids).compact render json: @accounts.index_by(&:id).values_at(*account_ids).compact, each_serializer: REST::RelationshipSerializer, relationships: relationships
render json: @accounts, each_serializer: REST::RelationshipSerializer, relationships: relationships
end end
private private

View File

@ -86,7 +86,7 @@ class Api::V1::AccountsController < Api::BaseController
end end
def relationships(**options) def relationships(**options)
AccountRelationshipsPresenter.new([@account.id], current_user.account_id, **options) AccountRelationshipsPresenter.new([@account], current_user.account_id, **options)
end end
def account_params def account_params

View File

@ -0,0 +1,74 @@
# frozen_string_literal: true
class Api::V1::Admin::TagsController < Api::BaseController
include Authorization
before_action -> { authorize_if_got_token! :'admin:read' }, only: [:index, :show]
before_action -> { authorize_if_got_token! :'admin:write' }, only: :update
before_action :set_tags, only: :index
before_action :set_tag, except: :index
after_action :insert_pagination_headers, only: :index
after_action :verify_authorized
LIMIT = 100
PAGINATION_PARAMS = %i(limit).freeze
def index
authorize :tag, :index?
render json: @tags, each_serializer: REST::Admin::TagSerializer
end
def show
authorize @tag, :show?
render json: @tag, serializer: REST::Admin::TagSerializer
end
def update
authorize @tag, :update?
@tag.update!(tag_params.merge(reviewed_at: Time.now.utc))
render json: @tag, serializer: REST::Admin::TagSerializer
end
private
def set_tag
@tag = Tag.find(params[:id])
end
def set_tags
@tags = Tag.all.to_a_paginated_by_id(limit_param(LIMIT), params_slice(:max_id, :since_id, :min_id))
end
def tag_params
params.permit(:display_name, :trendable, :usable, :listable)
end
def insert_pagination_headers
set_pagination_headers(next_path, prev_path)
end
def next_path
api_v1_admin_tags_url(pagination_params(max_id: pagination_max_id)) if records_continue?
end
def prev_path
api_v1_admin_tags_url(pagination_params(min_id: pagination_since_id)) unless @tags.empty?
end
def pagination_max_id
@tags.last.id
end
def pagination_since_id
@tags.first.id
end
def records_continue?
@tags.size == limit_param(LIMIT)
end
def pagination_params(core_params)
params.slice(*PAGINATION_PARAMS).permit(*PAGINATION_PARAMS).merge(core_params)
end
end

View File

@ -16,8 +16,10 @@ class Api::V1::DirectoriesController < Api::BaseController
end end
def set_accounts def set_accounts
with_read_replica do
@accounts = accounts_scope.offset(params[:offset]).limit(limit_param(DEFAULT_ACCOUNTS_LIMIT)) @accounts = accounts_scope.offset(params[:offset]).limit(limit_param(DEFAULT_ACCOUNTS_LIMIT))
end end
end
def accounts_scope def accounts_scope
Account.discoverable.tap do |scope| Account.discoverable.tap do |scope|

View File

@ -25,11 +25,11 @@ class Api::V1::FollowRequestsController < Api::BaseController
private private
def account def account
Account.find(params[:id]) @account ||= Account.find(params[:id])
end end
def relationships(**options) def relationships(**options)
AccountRelationshipsPresenter.new([params[:id]], current_user.account_id, **options) AccountRelationshipsPresenter.new([account], current_user.account_id, **options)
end end
def load_accounts def load_accounts

View File

@ -41,5 +41,7 @@ class Api::V1::Peers::SearchController < Api::BaseController
domain = TagManager.instance.normalize_domain(domain) domain = TagManager.instance.normalize_domain(domain)
@domains = Instance.searchable.where(Instance.arel_table[:domain].matches("#{Instance.sanitize_sql_like(domain)}%", false, true)).limit(10).pluck(:domain) @domains = Instance.searchable.where(Instance.arel_table[:domain].matches("#{Instance.sanitize_sql_like(domain)}%", false, true)).limit(10).pluck(:domain)
end end
rescue Addressable::URI::InvalidURIError
@domains = []
end end
end end

View File

@ -8,7 +8,15 @@ class Api::V1::Statuses::TranslationsController < Api::BaseController
before_action :set_translation before_action :set_translation
rescue_from TranslationService::NotConfiguredError, with: :not_found rescue_from TranslationService::NotConfiguredError, with: :not_found
rescue_from TranslationService::UnexpectedResponseError, TranslationService::QuotaExceededError, TranslationService::TooManyRequestsError, with: :service_unavailable rescue_from TranslationService::UnexpectedResponseError, with: :service_unavailable
rescue_from TranslationService::QuotaExceededError do
render json: { error: I18n.t('translation.errors.quota_exceeded') }, status: 503
end
rescue_from TranslationService::TooManyRequestsError do
render json: { error: I18n.t('translation.errors.too_many_requests') }, status: 503
end
def create def create
render json: @translation, serializer: REST::TranslationSerializer render json: @translation, serializer: REST::TranslationSerializer

View File

@ -2,7 +2,7 @@
class Api::V1::StreamingController < Api::BaseController class Api::V1::StreamingController < Api::BaseController
def index def index
if Rails.configuration.x.streaming_api_base_url == request.host if same_host?
not_found not_found
else else
redirect_to streaming_api_url, status: 301, allow_other_host: true redirect_to streaming_api_url, status: 301, allow_other_host: true
@ -11,9 +11,16 @@ class Api::V1::StreamingController < Api::BaseController
private private
def same_host?
base_url = Addressable::URI.parse(Rails.configuration.x.streaming_api_base_url)
request.host == base_url.host && request.port == (base_url.port || 80)
end
def streaming_api_url def streaming_api_url
Addressable::URI.parse(request.url).tap do |uri| Addressable::URI.parse(request.url).tap do |uri|
uri.host = Addressable::URI.parse(Rails.configuration.x.streaming_api_base_url).host base_url = Addressable::URI.parse(Rails.configuration.x.streaming_api_base_url)
uri.host = base_url.host
uri.port = base_url.port
end.to_s end.to_s
end end
end end

View File

@ -1,6 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
class Api::V1::Timelines::TagController < Api::BaseController class Api::V1::Timelines::TagController < Api::BaseController
before_action -> { doorkeeper_authorize! :read, :'read:statuses' }, only: :show, if: :require_auth?
before_action :load_tag before_action :load_tag
after_action :insert_pagination_headers, unless: -> { @statuses.empty? } after_action :insert_pagination_headers, unless: -> { @statuses.empty? }
@ -12,6 +13,10 @@ class Api::V1::Timelines::TagController < Api::BaseController
private private
def require_auth?
!Setting.timeline_preview
end
def load_tag def load_tag
@tag = Tag.find_normalized(params[:id]) @tag = Tag.find_normalized(params[:id])
end end

View File

@ -11,6 +11,7 @@ class ApplicationController < ActionController::Base
include CacheConcern include CacheConcern
include DomainControlHelper include DomainControlHelper
include DatabaseHelper include DatabaseHelper
include AuthorizedFetchHelper
helper_method :current_account helper_method :current_account
helper_method :current_session helper_method :current_session
@ -51,10 +52,6 @@ class ApplicationController < ActionController::Base
private private
def authorized_fetch_mode?
ENV['AUTHORIZED_FETCH'] == 'true' || Rails.configuration.x.limited_federation_mode
end
def public_fetch_mode? def public_fetch_mode?
!authorized_fetch_mode? !authorized_fetch_mode?
end end

View File

@ -6,7 +6,7 @@ class Auth::OmniauthCallbacksController < Devise::OmniauthCallbacksController
def self.provides_callback_for(provider) def self.provides_callback_for(provider)
define_method provider do define_method provider do
@provider = provider @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? if @user.persisted?
record_login_activity record_login_activity
@ -16,6 +16,9 @@ class Auth::OmniauthCallbacksController < Devise::OmniauthCallbacksController
session["devise.#{provider}_data"] = request.env['omniauth.auth'] session["devise.#{provider}_data"] = request.env['omniauth.auth']
redirect_to new_user_registration_url redirect_to new_user_registration_url
end 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
end end

View File

@ -1,6 +1,10 @@
# frozen_string_literal: true # frozen_string_literal: true
class Auth::SessionsController < Devise::SessionsController class Auth::SessionsController < Devise::SessionsController
include Redisable
MAX_2FA_ATTEMPTS_PER_HOUR = 10
layout 'auth' layout 'auth'
skip_before_action :require_no_authentication, only: [:create] skip_before_action :require_no_authentication, only: [:create]
@ -134,9 +138,23 @@ class Auth::SessionsController < Devise::SessionsController
session.delete(:attempt_user_updated_at) session.delete(:attempt_user_updated_at)
end end
def clear_2fa_attempt_from_user(user)
redis.del(second_factor_attempts_key(user))
end
def check_second_factor_rate_limits(user)
attempts, = redis.multi do |multi|
multi.incr(second_factor_attempts_key(user))
multi.expire(second_factor_attempts_key(user), 1.hour)
end
attempts >= MAX_2FA_ATTEMPTS_PER_HOUR
end
def on_authentication_success(user, security_measure) def on_authentication_success(user, security_measure)
@on_authentication_success_called = true @on_authentication_success_called = true
clear_2fa_attempt_from_user(user)
clear_attempt_from_session clear_attempt_from_session
user.update_sign_in!(new_sign_in: true) user.update_sign_in!(new_sign_in: true)
@ -168,4 +186,8 @@ class Auth::SessionsController < Devise::SessionsController
user_agent: request.user_agent user_agent: request.user_agent
) )
end end
def second_factor_attempts_key(user)
"2fa_auth_attempts:#{user.id}:#{Time.now.utc.hour}"
end
end end

View File

@ -91,14 +91,23 @@ module SignatureVerification
raise SignatureVerificationError, "Public key not found for key #{signature_params['keyId']}" if actor.nil? raise SignatureVerificationError, "Public key not found for key #{signature_params['keyId']}" if actor.nil?
signature = Base64.decode64(signature_params['signature']) signature = Base64.decode64(signature_params['signature'])
compare_signed_string = build_signed_string compare_signed_string = build_signed_string(include_query_string: true)
return actor unless verify_signature(actor, signature, compare_signed_string).nil? return actor unless verify_signature(actor, signature, compare_signed_string).nil?
# Compatibility quirk with older Mastodon versions
compare_signed_string = build_signed_string(include_query_string: false)
return actor unless verify_signature(actor, signature, compare_signed_string).nil?
actor = stoplight_wrap_request { actor_refresh_key!(actor) } actor = stoplight_wrap_request { actor_refresh_key!(actor) }
raise SignatureVerificationError, "Could not refresh public key #{signature_params['keyId']}" if actor.nil? raise SignatureVerificationError, "Could not refresh public key #{signature_params['keyId']}" if actor.nil?
compare_signed_string = build_signed_string(include_query_string: true)
return actor unless verify_signature(actor, signature, compare_signed_string).nil?
# Compatibility quirk with older Mastodon versions
compare_signed_string = build_signed_string(include_query_string: false)
return actor unless verify_signature(actor, signature, compare_signed_string).nil? return actor unless verify_signature(actor, signature, compare_signed_string).nil?
fail_with! "Verification failed for #{actor.to_log_human_identifier} #{actor.uri} using rsa-sha256 (RSASSA-PKCS1-v1_5 with SHA-256)", signed_string: compare_signed_string, signature: signature_params['signature'] fail_with! "Verification failed for #{actor.to_log_human_identifier} #{actor.uri} using rsa-sha256 (RSASSA-PKCS1-v1_5 with SHA-256)", signed_string: compare_signed_string, signature: signature_params['signature']
@ -119,6 +128,8 @@ module SignatureVerification
private private
def fail_with!(message, **options) def fail_with!(message, **options)
Rails.logger.debug { "Signature verification failed: #{message}" }
@signature_verification_failure_reason = { error: message }.merge(options) @signature_verification_failure_reason = { error: message }.merge(options)
@signed_request_actor = nil @signed_request_actor = nil
end end
@ -178,11 +189,18 @@ module SignatureVerification
nil nil
end end
def build_signed_string def build_signed_string(include_query_string: true)
signed_headers.map do |signed_header| signed_headers.map do |signed_header|
case signed_header case signed_header
when Request::REQUEST_TARGET when Request::REQUEST_TARGET
if include_query_string
"#{Request::REQUEST_TARGET}: #{request.method.downcase} #{request.original_fullpath}"
else
# Current versions of Mastodon incorrectly omit the query string from the (request-target) pseudo-header.
# Therefore, temporarily support such incorrect signatures for compatibility.
# TODO: remove eventually some time after release of the fixed version
"#{Request::REQUEST_TARGET}: #{request.method.downcase} #{request.path}" "#{Request::REQUEST_TARGET}: #{request.method.downcase} #{request.path}"
end
when '(created)' when '(created)'
raise SignatureVerificationError, 'Invalid pseudo-header (created) for rsa-sha256' unless signature_algorithm == 'hs2019' raise SignatureVerificationError, 'Invalid pseudo-header (created) for rsa-sha256' unless signature_algorithm == 'hs2019'
raise SignatureVerificationError, 'Pseudo-header (created) used but corresponding argument missing' if signature_params['created'].blank? raise SignatureVerificationError, 'Pseudo-header (created) used but corresponding argument missing' if signature_params['created'].blank?
@ -248,7 +266,7 @@ module SignatureVerification
stoplight_wrap_request { ResolveAccountService.new.call(key_id.delete_prefix('acct:'), suppress_errors: false) } stoplight_wrap_request { ResolveAccountService.new.call(key_id.delete_prefix('acct:'), suppress_errors: false) }
elsif !ActivityPub::TagManager.instance.local_uri?(key_id) elsif !ActivityPub::TagManager.instance.local_uri?(key_id)
account = ActivityPub::TagManager.instance.uri_to_actor(key_id) account = ActivityPub::TagManager.instance.uri_to_actor(key_id)
account ||= stoplight_wrap_request { ActivityPub::FetchRemoteKeyService.new.call(key_id, id: false, suppress_errors: false) } account ||= stoplight_wrap_request { ActivityPub::FetchRemoteKeyService.new.call(key_id, suppress_errors: false) }
account account
end end
rescue Mastodon::PrivateNetworkAddressError => e rescue Mastodon::PrivateNetworkAddressError => e

View File

@ -65,6 +65,11 @@ module TwoFactorAuthenticationConcern
end end
def authenticate_with_two_factor_via_otp(user) def authenticate_with_two_factor_via_otp(user)
if check_second_factor_rate_limits(user)
flash.now[:alert] = I18n.t('users.rate_limited')
return prompt_for_two_factor(user)
end
if valid_otp_attempt?(user) if valid_otp_attempt?(user)
on_authentication_success(user, :otp) on_authentication_success(user, :otp)
else else

View File

@ -4,14 +4,14 @@ module WebAppControllerConcern
extend ActiveSupport::Concern extend ActiveSupport::Concern
included do included do
prepend_before_action :redirect_unauthenticated_to_permalinks!
before_action :set_app_body_class
vary_by 'Accept, Accept-Language, Cookie' vary_by 'Accept, Accept-Language, Cookie'
before_action :redirect_unauthenticated_to_permalinks!
before_action :set_app_body_class
end end
def skip_csrf_meta_tags? def skip_csrf_meta_tags?
!(ENV['OMNIAUTH_ONLY'] == 'true' && Devise.omniauth_providers.length == 1) && current_user.nil? !(ENV['ONE_CLICK_SSO_LOGIN'] == 'true' && ENV['OMNIAUTH_ONLY'] == 'true' && Devise.omniauth_providers.length == 1) && current_user.nil?
end end
def set_app_body_class def set_app_body_class
@ -22,7 +22,9 @@ module WebAppControllerConcern
return if user_signed_in? && current_account.moved_to_account_id.nil? return if user_signed_in? && current_account.moved_to_account_id.nil?
redirect_path = PermalinkRedirector.new(request.path).redirect_path redirect_path = PermalinkRedirector.new(request.path).redirect_path
return if redirect_path.blank?
redirect_to(redirect_path) if redirect_path.present? expires_in(15.seconds, public: true, stale_while_revalidate: 30.seconds, stale_if_error: 1.day) unless user_signed_in?
redirect_to(redirect_path)
end end
end end

View File

@ -3,7 +3,6 @@
class FollowerAccountsController < ApplicationController class FollowerAccountsController < ApplicationController
include AccountControllerConcern include AccountControllerConcern
include SignatureVerification include SignatureVerification
include WebAppControllerConcern
vary_by -> { public_fetch_mode? ? 'Accept, Accept-Language, Cookie' : 'Accept, Accept-Language, Cookie, Signature' } vary_by -> { public_fetch_mode? ? 'Accept, Accept-Language, Cookie' : 'Accept, Accept-Language, Cookie, Signature' }

View File

@ -3,7 +3,6 @@
class FollowingAccountsController < ApplicationController class FollowingAccountsController < ApplicationController
include AccountControllerConcern include AccountControllerConcern
include SignatureVerification include SignatureVerification
include WebAppControllerConcern
vary_by -> { public_fetch_mode? ? 'Accept, Accept-Language, Cookie' : 'Accept, Accept-Language, Cookie, Signature' } vary_by -> { public_fetch_mode? ? 'Accept, Accept-Language, Cookie' : 'Accept, Accept-Language, Cookie, Signature' }

View File

@ -33,7 +33,7 @@ class RelationshipsController < ApplicationController
end end
def set_relationships def set_relationships
@relationships = AccountRelationshipsPresenter.new(@accounts.pluck(:id), current_user.account_id) @relationships = AccountRelationshipsPresenter.new(@accounts, current_user.account_id)
end end
def form_account_batch_params def form_account_batch_params

View File

@ -18,7 +18,7 @@ class Settings::PrivacyController < Settings::BaseController
private private
def account_params def account_params
params.require(:account).permit(:discoverable, :unlocked, :show_collections, settings: UserSettings.keys) params.require(:account).permit(:discoverable, :unlocked, :indexable, :show_collections, settings: UserSettings.keys)
end end
def set_account def set_account

View File

@ -0,0 +1,11 @@
# frozen_string_literal: true
module AuthorizedFetchHelper
def authorized_fetch_mode?
ENV.fetch('AUTHORIZED_FETCH') { Setting.authorized_fetch && 'true' } == 'true' || Rails.configuration.x.limited_federation_mode
end
def authorized_fetch_overridden?
ENV.key?('AUTHORIZED_FETCH') || Rails.configuration.x.limited_federation_mode
end
end

View File

@ -21,6 +21,7 @@ module ContextHelper
blurhash: { 'toot' => 'http://joinmastodon.org/ns#', 'blurhash' => 'toot:blurhash' }, blurhash: { 'toot' => 'http://joinmastodon.org/ns#', 'blurhash' => 'toot:blurhash' },
discoverable: { 'toot' => 'http://joinmastodon.org/ns#', 'discoverable' => 'toot:discoverable' }, discoverable: { 'toot' => 'http://joinmastodon.org/ns#', 'discoverable' => 'toot:discoverable' },
indexable: { 'toot' => 'http://joinmastodon.org/ns#', 'indexable' => 'toot:indexable' }, indexable: { 'toot' => 'http://joinmastodon.org/ns#', 'indexable' => 'toot:indexable' },
memorial: { 'toot' => 'http://joinmastodon.org/ns#', 'memorial' => 'toot:memorial' },
voters_count: { 'toot' => 'http://joinmastodon.org/ns#', 'votersCount' => 'toot:votersCount' }, voters_count: { 'toot' => 'http://joinmastodon.org/ns#', 'votersCount' => 'toot:votersCount' },
olm: { olm: {
'toot' => 'http://joinmastodon.org/ns#', 'Device' => 'toot:Device', 'Ed25519Signature' => 'toot:Ed25519Signature', 'Ed25519Key' => 'toot:Ed25519Key', 'Curve25519Key' => 'toot:Curve25519Key', 'EncryptedMessage' => 'toot:EncryptedMessage', 'publicKeyBase64' => 'toot:publicKeyBase64', 'deviceId' => 'toot:deviceId', 'toot' => 'http://joinmastodon.org/ns#', 'Device' => 'toot:Device', 'Ed25519Signature' => 'toot:Ed25519Signature', 'Ed25519Key' => 'toot:Ed25519Key', 'Curve25519Key' => 'toot:Curve25519Key', 'EncryptedMessage' => 'toot:EncryptedMessage', 'publicKeyBase64' => 'toot:publicKeyBase64', 'deviceId' => 'toot:deviceId',

View File

@ -1,11 +1,24 @@
# frozen_string_literal: true # frozen_string_literal: true
module DatabaseHelper module DatabaseHelper
def replica_enabled?
ENV['REPLICA_DB_NAME'] || ENV.fetch('REPLICA_DATABASE_URL', nil)
end
module_function :replica_enabled?
def with_read_replica(&block) def with_read_replica(&block)
if replica_enabled?
ApplicationRecord.connected_to(role: :reading, prevent_writes: true, &block) ApplicationRecord.connected_to(role: :reading, prevent_writes: true, &block)
else
yield
end
end end
def with_primary(&block) def with_primary(&block)
if replica_enabled?
ApplicationRecord.connected_to(role: :writing, &block) ApplicationRecord.connected_to(role: :writing, &block)
else
yield
end
end end
end end

View File

@ -155,8 +155,8 @@ module JsonLdHelper
end end
end end
def fetch_resource(uri, id, on_behalf_of = nil) def fetch_resource(uri, id_is_known, on_behalf_of = nil, request_options: {})
unless id unless id_is_known
json = fetch_resource_without_id_validation(uri, on_behalf_of) json = fetch_resource_without_id_validation(uri, on_behalf_of)
return if !json.is_a?(Hash) || unsupported_uri_scheme?(json['id']) return if !json.is_a?(Hash) || unsupported_uri_scheme?(json['id'])
@ -164,17 +164,29 @@ module JsonLdHelper
uri = json['id'] uri = json['id']
end end
json = fetch_resource_without_id_validation(uri, on_behalf_of) json = fetch_resource_without_id_validation(uri, on_behalf_of, request_options: request_options)
json.present? && json['id'] == uri ? json : nil json.present? && json['id'] == uri ? json : nil
end end
def fetch_resource_without_id_validation(uri, on_behalf_of = nil, raise_on_temporary_error = false) def fetch_resource_without_id_validation(uri, on_behalf_of = nil, raise_on_temporary_error = false, request_options: {})
on_behalf_of ||= Account.representative on_behalf_of ||= Account.representative
build_request(uri, on_behalf_of).perform do |response| 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 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
end end
@ -204,8 +216,8 @@ module JsonLdHelper
response.code == 501 || ((400...500).cover?(response.code) && ![401, 408, 429].include?(response.code)) response.code == 501 || ((400...500).cover?(response.code) && ![401, 408, 429].include?(response.code))
end end
def build_request(uri, on_behalf_of = nil) def build_request(uri, on_behalf_of = nil, options: {})
Request.new(:get, uri).tap do |request| Request.new(:get, uri, **options).tap do |request|
request.on_behalf_of(on_behalf_of) if on_behalf_of request.on_behalf_of(on_behalf_of) if on_behalf_of
request.add_headers('Accept' => 'application/activity+json, application/ld+json') request.add_headers('Accept' => 'application/activity+json, application/ld+json')
end end

View File

@ -188,11 +188,11 @@ module LanguagesHelper
ISO_639_3 = { ISO_639_3 = {
ast: ['Asturian', 'Asturianu'].freeze, ast: ['Asturian', 'Asturianu'].freeze,
chr: ['Cherokee', 'ᏣᎳᎩ ᎦᏬᏂᎯᏍᏗ'].freeze,
ckb: ['Sorani (Kurdish)', 'سۆرانی'].freeze, ckb: ['Sorani (Kurdish)', 'سۆرانی'].freeze,
cnr: ['Montenegrin', 'crnogorski'].freeze, cnr: ['Montenegrin', 'crnogorski'].freeze,
jbo: ['Lojban', 'la .lojban.'].freeze, jbo: ['Lojban', 'la .lojban.'].freeze,
kab: ['Kabyle', 'Taqbaylit'].freeze, kab: ['Kabyle', 'Taqbaylit'].freeze,
kmr: ['Kurmanji (Kurdish)', 'Kurmancî'].freeze,
ldn: ['Láadan', 'Láadan'].freeze, ldn: ['Láadan', 'Láadan'].freeze,
lfn: ['Lingua Franca Nova', 'lingua franca nova'].freeze, lfn: ['Lingua Franca Nova', 'lingua franca nova'].freeze,
sco: ['Scots', 'Scots'].freeze, sco: ['Scots', 'Scots'].freeze,
@ -200,6 +200,7 @@ module LanguagesHelper
smj: ['Lule Sami', 'Julevsámegiella'].freeze, smj: ['Lule Sami', 'Julevsámegiella'].freeze,
szl: ['Silesian', 'ślůnsko godka'].freeze, szl: ['Silesian', 'ślůnsko godka'].freeze,
tok: ['Toki Pona', 'toki pona'].freeze, tok: ['Toki Pona', 'toki pona'].freeze,
xal: ['Kalmyk', 'Хальмг келн'].freeze,
zba: ['Balaibalan', 'باليبلن'].freeze, zba: ['Balaibalan', 'باليبلن'].freeze,
zgh: ['Standard Moroccan Tamazight', 'ⵜⴰⵎⴰⵣⵉⵖⵜ'].freeze, zgh: ['Standard Moroccan Tamazight', 'ⵜⴰⵎⴰⵣⵉⵖⵜ'].freeze,
}.freeze }.freeze
@ -253,6 +254,7 @@ module LanguagesHelper
def valid_locale_or_nil(str) def valid_locale_or_nil(str)
return if str.blank? return if str.blank?
return str if valid_locale?(str)
code, = str.to_s.split(/[_-]/) # Strip out the region from e.g. en_US or ja-JP code, = str.to_s.split(/[_-]/) # Strip out the region from e.g. en_US or ja-JP

View File

@ -14,6 +14,7 @@ module MediaComponentHelper
blurhash: video.blurhash, blurhash: video.blurhash,
frameRate: meta.dig('original', 'frame_rate'), frameRate: meta.dig('original', 'frame_rate'),
inline: true, inline: true,
aspectRatio: "#{meta.dig('original', 'width')} / #{meta.dig('original', 'height')}",
media: [ media: [
serialize_media_attachment(video), serialize_media_attachment(video),
].as_json, ].as_json,

View File

@ -1,37 +0,0 @@
import api from '../api';
export const ACCOUNT_NOTE_SUBMIT_REQUEST = 'ACCOUNT_NOTE_SUBMIT_REQUEST';
export const ACCOUNT_NOTE_SUBMIT_SUCCESS = 'ACCOUNT_NOTE_SUBMIT_SUCCESS';
export const ACCOUNT_NOTE_SUBMIT_FAIL = 'ACCOUNT_NOTE_SUBMIT_FAIL';
export function submitAccountNote(id, value) {
return (dispatch, getState) => {
dispatch(submitAccountNoteRequest());
api(getState).post(`/api/v1/accounts/${id}/note`, {
comment: value,
}).then(response => {
dispatch(submitAccountNoteSuccess(response.data));
}).catch(error => dispatch(submitAccountNoteFail(error)));
};
}
export function submitAccountNoteRequest() {
return {
type: ACCOUNT_NOTE_SUBMIT_REQUEST,
};
}
export function submitAccountNoteSuccess(relationship) {
return {
type: ACCOUNT_NOTE_SUBMIT_SUCCESS,
relationship,
};
}
export function submitAccountNoteFail(error) {
return {
type: ACCOUNT_NOTE_SUBMIT_FAIL,
error,
};
}

View File

@ -0,0 +1,18 @@
import { createAppAsyncThunk } from 'mastodon/store/typed_functions';
import api from '../api';
export const submitAccountNote = createAppAsyncThunk(
'account_note/submit',
async (args: { id: string; value: string }, { getState }) => {
// TODO: replace `unknown` with `ApiRelationshipJSON` when it is merged
const response = await api(getState).post<unknown>(
`/api/v1/accounts/${args.id}/note`,
{
comment: args.value,
},
);
return { relationship: response.data };
},
);

View File

@ -84,6 +84,7 @@ const messages = defineMessages({
uploadErrorPoll: { id: 'upload_error.poll', defaultMessage: 'File upload not allowed with polls.' }, uploadErrorPoll: { id: 'upload_error.poll', defaultMessage: 'File upload not allowed with polls.' },
open: { id: 'compose.published.open', defaultMessage: 'Open' }, open: { id: 'compose.published.open', defaultMessage: 'Open' },
published: { id: 'compose.published.body', defaultMessage: 'Post published.' }, published: { id: 'compose.published.body', defaultMessage: 'Post published.' },
saved: { id: 'compose.saved.body', defaultMessage: 'Post saved.' },
}); });
export const ensureComposeIsVisible = (getState, routerHistory) => { export const ensureComposeIsVisible = (getState, routerHistory) => {
@ -244,7 +245,7 @@ export function submitCompose(routerHistory) {
} }
dispatch(showAlert({ dispatch(showAlert({
message: messages.published, message: statusId === null ? messages.published : messages.saved,
action: messages.open, action: messages.open,
dismissAfter: 10000, dismissAfter: 10000,
onClick: () => routerHistory.push(`/@${response.data.account.username}/${response.data.id}`), onClick: () => routerHistory.push(`/@${response.data.account.username}/${response.data.id}`),

View File

@ -1,11 +1,16 @@
import api from '../api'; import api, { getLinks } from '../api';
import { fetchRelationships } from './accounts';
import { importFetchedAccounts, importFetchedStatus } from './importer'; import { importFetchedAccounts, importFetchedStatus } from './importer';
export const REBLOG_REQUEST = 'REBLOG_REQUEST'; export const REBLOG_REQUEST = 'REBLOG_REQUEST';
export const REBLOG_SUCCESS = 'REBLOG_SUCCESS'; export const REBLOG_SUCCESS = 'REBLOG_SUCCESS';
export const REBLOG_FAIL = 'REBLOG_FAIL'; export const REBLOG_FAIL = 'REBLOG_FAIL';
export const REBLOGS_EXPAND_REQUEST = 'REBLOGS_EXPAND_REQUEST';
export const REBLOGS_EXPAND_SUCCESS = 'REBLOGS_EXPAND_SUCCESS';
export const REBLOGS_EXPAND_FAIL = 'REBLOGS_EXPAND_FAIL';
export const FAVOURITE_REQUEST = 'FAVOURITE_REQUEST'; export const FAVOURITE_REQUEST = 'FAVOURITE_REQUEST';
export const FAVOURITE_SUCCESS = 'FAVOURITE_SUCCESS'; export const FAVOURITE_SUCCESS = 'FAVOURITE_SUCCESS';
export const FAVOURITE_FAIL = 'FAVOURITE_FAIL'; export const FAVOURITE_FAIL = 'FAVOURITE_FAIL';
@ -26,6 +31,10 @@ export const FAVOURITES_FETCH_REQUEST = 'FAVOURITES_FETCH_REQUEST';
export const FAVOURITES_FETCH_SUCCESS = 'FAVOURITES_FETCH_SUCCESS'; export const FAVOURITES_FETCH_SUCCESS = 'FAVOURITES_FETCH_SUCCESS';
export const FAVOURITES_FETCH_FAIL = 'FAVOURITES_FETCH_FAIL'; export const FAVOURITES_FETCH_FAIL = 'FAVOURITES_FETCH_FAIL';
export const FAVOURITES_EXPAND_REQUEST = 'FAVOURITES_EXPAND_REQUEST';
export const FAVOURITES_EXPAND_SUCCESS = 'FAVOURITES_EXPAND_SUCCESS';
export const FAVOURITES_EXPAND_FAIL = 'FAVOURITES_EXPAND_FAIL';
export const PIN_REQUEST = 'PIN_REQUEST'; export const PIN_REQUEST = 'PIN_REQUEST';
export const PIN_SUCCESS = 'PIN_SUCCESS'; export const PIN_SUCCESS = 'PIN_SUCCESS';
export const PIN_FAIL = 'PIN_FAIL'; export const PIN_FAIL = 'PIN_FAIL';
@ -273,8 +282,10 @@ export function fetchReblogs(id) {
dispatch(fetchReblogsRequest(id)); dispatch(fetchReblogsRequest(id));
api(getState).get(`/api/v1/statuses/${id}/reblogged_by`).then(response => { api(getState).get(`/api/v1/statuses/${id}/reblogged_by`).then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next');
dispatch(importFetchedAccounts(response.data)); dispatch(importFetchedAccounts(response.data));
dispatch(fetchReblogsSuccess(id, response.data)); dispatch(fetchReblogsSuccess(id, response.data, next ? next.uri : null));
dispatch(fetchRelationships(response.data.map(item => item.id)));
}).catch(error => { }).catch(error => {
dispatch(fetchReblogsFail(id, error)); dispatch(fetchReblogsFail(id, error));
}); });
@ -288,17 +299,62 @@ export function fetchReblogsRequest(id) {
}; };
} }
export function fetchReblogsSuccess(id, accounts) { export function fetchReblogsSuccess(id, accounts, next) {
return { return {
type: REBLOGS_FETCH_SUCCESS, type: REBLOGS_FETCH_SUCCESS,
id, id,
accounts, accounts,
next,
}; };
} }
export function fetchReblogsFail(id, error) { export function fetchReblogsFail(id, error) {
return { return {
type: REBLOGS_FETCH_FAIL, type: REBLOGS_FETCH_FAIL,
id,
error,
};
}
export function expandReblogs(id) {
return (dispatch, getState) => {
const url = getState().getIn(['user_lists', 'reblogged_by', id, 'next']);
if (url === null) {
return;
}
dispatch(expandReblogsRequest(id));
api(getState).get(url).then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next');
dispatch(importFetchedAccounts(response.data));
dispatch(expandReblogsSuccess(id, response.data, next ? next.uri : null));
dispatch(fetchRelationships(response.data.map(item => item.id)));
}).catch(error => dispatch(expandReblogsFail(id, error)));
};
}
export function expandReblogsRequest(id) {
return {
type: REBLOGS_EXPAND_REQUEST,
id,
};
}
export function expandReblogsSuccess(id, accounts, next) {
return {
type: REBLOGS_EXPAND_SUCCESS,
id,
accounts,
next,
};
}
export function expandReblogsFail(id, error) {
return {
type: REBLOGS_EXPAND_FAIL,
id,
error, error,
}; };
} }
@ -308,8 +364,10 @@ export function fetchFavourites(id) {
dispatch(fetchFavouritesRequest(id)); dispatch(fetchFavouritesRequest(id));
api(getState).get(`/api/v1/statuses/${id}/favourited_by`).then(response => { api(getState).get(`/api/v1/statuses/${id}/favourited_by`).then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next');
dispatch(importFetchedAccounts(response.data)); dispatch(importFetchedAccounts(response.data));
dispatch(fetchFavouritesSuccess(id, response.data)); dispatch(fetchFavouritesSuccess(id, response.data, next ? next.uri : null));
dispatch(fetchRelationships(response.data.map(item => item.id)));
}).catch(error => { }).catch(error => {
dispatch(fetchFavouritesFail(id, error)); dispatch(fetchFavouritesFail(id, error));
}); });
@ -323,17 +381,62 @@ export function fetchFavouritesRequest(id) {
}; };
} }
export function fetchFavouritesSuccess(id, accounts) { export function fetchFavouritesSuccess(id, accounts, next) {
return { return {
type: FAVOURITES_FETCH_SUCCESS, type: FAVOURITES_FETCH_SUCCESS,
id, id,
accounts, accounts,
next,
}; };
} }
export function fetchFavouritesFail(id, error) { export function fetchFavouritesFail(id, error) {
return { return {
type: FAVOURITES_FETCH_FAIL, type: FAVOURITES_FETCH_FAIL,
id,
error,
};
}
export function expandFavourites(id) {
return (dispatch, getState) => {
const url = getState().getIn(['user_lists', 'favourited_by', id, 'next']);
if (url === null) {
return;
}
dispatch(expandFavouritesRequest(id));
api(getState).get(url).then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next');
dispatch(importFetchedAccounts(response.data));
dispatch(expandFavouritesSuccess(id, response.data, next ? next.uri : null));
dispatch(fetchRelationships(response.data.map(item => item.id)));
}).catch(error => dispatch(expandFavouritesFail(id, error)));
};
}
export function expandFavouritesRequest(id) {
return {
type: FAVOURITES_EXPAND_REQUEST,
id,
};
}
export function expandFavouritesSuccess(id, accounts, next) {
return {
type: FAVOURITES_EXPAND_SUCCESS,
id,
accounts,
next,
};
}
export function expandFavouritesFail(id, error) {
return {
type: FAVOURITES_EXPAND_FAIL,
id,
error, error,
}; };
} }

View File

@ -18,6 +18,7 @@ import {
importFetchedStatuses, importFetchedStatuses,
} from './importer'; } from './importer';
import { submitMarkers } from './markers'; import { submitMarkers } from './markers';
import { register as registerPushNotifications } from './push_notifications';
import { saveSettings } from './settings'; import { saveSettings } from './settings';
export const NOTIFICATIONS_UPDATE = 'NOTIFICATIONS_UPDATE'; export const NOTIFICATIONS_UPDATE = 'NOTIFICATIONS_UPDATE';
@ -293,6 +294,10 @@ export function requestBrowserPermission(callback = noOp) {
requestNotificationPermission((permission) => { requestNotificationPermission((permission) => {
dispatch(setBrowserPermission(permission)); dispatch(setBrowserPermission(permission));
callback(permission); callback(permission);
if (permission === 'granted') {
dispatch(registerPushNotifications());
}
}); });
}; };
} }

View File

@ -1,3 +1,7 @@
import { fromJS } from 'immutable';
import { searchHistory } from 'mastodon/settings';
import api from '../api'; import api from '../api';
import { fetchRelationships } from './accounts'; import { fetchRelationships } from './accounts';
@ -15,8 +19,7 @@ export const SEARCH_EXPAND_REQUEST = 'SEARCH_EXPAND_REQUEST';
export const SEARCH_EXPAND_SUCCESS = 'SEARCH_EXPAND_SUCCESS'; export const SEARCH_EXPAND_SUCCESS = 'SEARCH_EXPAND_SUCCESS';
export const SEARCH_EXPAND_FAIL = 'SEARCH_EXPAND_FAIL'; export const SEARCH_EXPAND_FAIL = 'SEARCH_EXPAND_FAIL';
export const SEARCH_RESULT_CLICK = 'SEARCH_RESULT_CLICK'; export const SEARCH_HISTORY_UPDATE = 'SEARCH_HISTORY_UPDATE';
export const SEARCH_RESULT_FORGET = 'SEARCH_RESULT_FORGET';
export function changeSearch(value) { export function changeSearch(value) {
return { return {
@ -37,17 +40,17 @@ export function submitSearch(type) {
const signedIn = !!getState().getIn(['meta', 'me']); const signedIn = !!getState().getIn(['meta', 'me']);
if (value.length === 0) { if (value.length === 0) {
dispatch(fetchSearchSuccess({ accounts: [], statuses: [], hashtags: [] }, '')); dispatch(fetchSearchSuccess({ accounts: [], statuses: [], hashtags: [] }, '', type));
return; return;
} }
dispatch(fetchSearchRequest()); dispatch(fetchSearchRequest(type));
api(getState).get('/api/v2/search', { api(getState).get('/api/v2/search', {
params: { params: {
q: value, q: value,
resolve: signedIn, resolve: signedIn,
limit: 5, limit: 11,
type, type,
}, },
}).then(response => { }).then(response => {
@ -59,7 +62,7 @@ export function submitSearch(type) {
dispatch(importFetchedStatuses(response.data.statuses)); dispatch(importFetchedStatuses(response.data.statuses));
} }
dispatch(fetchSearchSuccess(response.data, value)); dispatch(fetchSearchSuccess(response.data, value, type));
dispatch(fetchRelationships(response.data.accounts.map(item => item.id))); dispatch(fetchRelationships(response.data.accounts.map(item => item.id)));
}).catch(error => { }).catch(error => {
dispatch(fetchSearchFail(error)); dispatch(fetchSearchFail(error));
@ -67,16 +70,18 @@ export function submitSearch(type) {
}; };
} }
export function fetchSearchRequest() { export function fetchSearchRequest(searchType) {
return { return {
type: SEARCH_FETCH_REQUEST, type: SEARCH_FETCH_REQUEST,
searchType,
}; };
} }
export function fetchSearchSuccess(results, searchTerm) { export function fetchSearchSuccess(results, searchTerm, searchType) {
return { return {
type: SEARCH_FETCH_SUCCESS, type: SEARCH_FETCH_SUCCESS,
results, results,
searchType,
searchTerm, searchTerm,
}; };
} }
@ -90,15 +95,16 @@ export function fetchSearchFail(error) {
export const expandSearch = type => (dispatch, getState) => { export const expandSearch = type => (dispatch, getState) => {
const value = getState().getIn(['search', 'value']); const value = getState().getIn(['search', 'value']);
const offset = getState().getIn(['search', 'results', type]).size; const offset = getState().getIn(['search', 'results', type]).size - 1;
dispatch(expandSearchRequest()); dispatch(expandSearchRequest(type));
api(getState).get('/api/v2/search', { api(getState).get('/api/v2/search', {
params: { params: {
q: value, q: value,
type, type,
offset, offset,
limit: 11,
}, },
}).then(({ data }) => { }).then(({ data }) => {
if (data.accounts) { if (data.accounts) {
@ -116,8 +122,9 @@ export const expandSearch = type => (dispatch, getState) => {
}); });
}; };
export const expandSearchRequest = () => ({ export const expandSearchRequest = (searchType) => ({
type: SEARCH_EXPAND_REQUEST, type: SEARCH_EXPAND_REQUEST,
searchType,
}); });
export const expandSearchSuccess = (results, searchTerm, searchType) => ({ export const expandSearchSuccess = (results, searchTerm, searchType) => ({
@ -140,6 +147,10 @@ export const openURL = (value, history, onFailure) => (dispatch, getState) => {
const signedIn = !!getState().getIn(['meta', 'me']); const signedIn = !!getState().getIn(['meta', 'me']);
if (!signedIn) { if (!signedIn) {
if (onFailure) {
onFailure();
}
return; return;
} }
@ -166,16 +177,34 @@ export const openURL = (value, history, onFailure) => (dispatch, getState) => {
}); });
}; };
export const clickSearchResult = (q, type) => ({ export const clickSearchResult = (q, type) => (dispatch, getState) => {
type: SEARCH_RESULT_CLICK, const previous = getState().getIn(['search', 'recent']);
const me = getState().getIn(['meta', 'me']);
const current = previous.add(fromJS({ type, q })).takeLast(4);
result: { searchHistory.set(me, current.toJS());
type, dispatch(updateSearchHistory(current));
q, };
},
export const forgetSearchResult = q => (dispatch, getState) => {
const previous = getState().getIn(['search', 'recent']);
const me = getState().getIn(['meta', 'me']);
const current = previous.filterNot(result => result.get('q') === q);
searchHistory.set(me, current.toJS());
dispatch(updateSearchHistory(current));
};
export const updateSearchHistory = recent => ({
type: SEARCH_HISTORY_UPDATE,
recent,
}); });
export const forgetSearchResult = q => ({ export const hydrateSearch = () => (dispatch, getState) => {
type: SEARCH_RESULT_FORGET, const me = getState().getIn(['meta', 'me']);
q, const history = searchHistory.get(me);
});
if (history !== null) {
dispatch(updateSearchHistory(history));
}
};

View File

@ -2,6 +2,7 @@ import { Iterable, fromJS } from 'immutable';
import { hydrateCompose } from './compose'; import { hydrateCompose } from './compose';
import { importFetchedAccounts } from './importer'; import { importFetchedAccounts } from './importer';
import { hydrateSearch } from './search';
export const STORE_HYDRATE = 'STORE_HYDRATE'; export const STORE_HYDRATE = 'STORE_HYDRATE';
export const STORE_HYDRATE_LAZY = 'STORE_HYDRATE_LAZY'; export const STORE_HYDRATE_LAZY = 'STORE_HYDRATE_LAZY';
@ -20,6 +21,7 @@ export function hydrateStore(rawState) {
}); });
dispatch(hydrateCompose()); dispatch(hydrateCompose());
dispatch(hydrateSearch());
dispatch(importFetchedAccounts(Object.values(rawState.accounts))); dispatch(importFetchedAccounts(Object.values(rawState.accounts)));
}; };
} }

View File

@ -1,76 +0,0 @@
// @ts-check
import axios from 'axios';
import LinkHeader from 'http-link-header';
import ready from './ready';
/**
* @param {import('axios').AxiosResponse} response
* @returns {LinkHeader}
*/
export const getLinks = response => {
const value = response.headers.link;
if (!value) {
return new LinkHeader();
}
return LinkHeader.parse(value);
};
/** @type {import('axios').RawAxiosRequestHeaders} */
const csrfHeader = {};
/**
* @returns {void}
*/
const setCSRFHeader = () => {
/** @type {HTMLMetaElement | null} */
const csrfToken = document.querySelector('meta[name=csrf-token]');
if (csrfToken) {
csrfHeader['X-CSRF-Token'] = csrfToken.content;
}
};
ready(setCSRFHeader);
/**
* @param {() => import('immutable').Map<string,any>} getState
* @returns {import('axios').RawAxiosRequestHeaders}
*/
const authorizationHeaderFromState = getState => {
const accessToken = getState && getState().getIn(['meta', 'access_token'], '');
if (!accessToken) {
return {};
}
return {
'Authorization': `Bearer ${accessToken}`,
};
};
/**
* @param {() => import('immutable').Map<string,any>} getState
* @returns {import('axios').AxiosInstance}
*/
export default function api(getState) {
return axios.create({
headers: {
...csrfHeader,
...authorizationHeaderFromState(getState),
},
transformResponse: [
function (data) {
try {
return JSON.parse(data);
} catch {
return data;
}
},
],
});
}

View File

@ -0,0 +1,63 @@
import type { AxiosResponse, RawAxiosRequestHeaders } from 'axios';
import axios from 'axios';
import LinkHeader from 'http-link-header';
import ready from './ready';
import type { GetState } from './store';
export const getLinks = (response: AxiosResponse) => {
const value = response.headers.link as string | undefined;
if (!value) {
return new LinkHeader();
}
return LinkHeader.parse(value);
};
const csrfHeader: RawAxiosRequestHeaders = {};
const setCSRFHeader = () => {
const csrfToken = document.querySelector<HTMLMetaElement>(
'meta[name=csrf-token]',
);
if (csrfToken) {
csrfHeader['X-CSRF-Token'] = csrfToken.content;
}
};
void ready(setCSRFHeader);
const authorizationHeaderFromState = (getState?: GetState) => {
const accessToken =
getState && (getState().meta.get('access_token', '') as string);
if (!accessToken) {
return {};
}
return {
Authorization: `Bearer ${accessToken}`,
} as RawAxiosRequestHeaders;
};
// eslint-disable-next-line import/no-default-export
export default function api(getState: GetState) {
return axios.create({
headers: {
...csrfHeader,
...authorizationHeaderFromState(getState),
},
transformResponse: [
function (data: unknown) {
try {
return JSON.parse(data as string) as unknown;
} catch {
return data;
}
},
],
});
}

View File

@ -45,6 +45,21 @@ describe('computeHashtagBarForStatus', () => {
); );
}); });
it('does not truncate the contents when the last child is a text node', () => {
const status = createStatus(
'this is a #<a class="zrl" href="https://example.com/search?tag=test">test</a>. Some more text',
['test'],
);
const { hashtagsInBar, statusContentProps } =
computeHashtagBarForStatus(status);
expect(hashtagsInBar).toEqual([]);
expect(statusContentProps.statusContent).toMatchInlineSnapshot(
`"this is a #<a class="zrl" href="https://example.com/search?tag=test">test</a>. Some more text"`,
);
});
it('extract tags from the last line', () => { it('extract tags from the last line', () => {
const status = createStatus( const status = createStatus(
'<p>Simple text</p><p><a href="test">#hashtag</a></p>', '<p>Simple text</p><p><a href="test">#hashtag</a></p>',
@ -105,6 +120,21 @@ describe('computeHashtagBarForStatus', () => {
); );
}); });
it('handles server-side normalized tags with accentuated characters', () => {
const status = createStatus(
'<p>Text</p><p><a href="test">#éaa</a> <a href="test">#Éaa</a></p>',
['eaa'], // The server may normalize the hashtags in the `tags` attribute
);
const { hashtagsInBar, statusContentProps } =
computeHashtagBarForStatus(status);
expect(hashtagsInBar).toEqual(['Éaa']);
expect(statusContentProps.statusContent).toMatchInlineSnapshot(
`"<p>Text</p>"`,
);
});
it('does not display in bar a hashtag in content with a case difference', () => { it('does not display in bar a hashtag in content with a case difference', () => {
const status = createStatus( const status = createStatus(
'<p>Text <a href="test">#Éaa</a></p><p><a href="test">#éaa</a></p>', '<p>Text <a href="test">#Éaa</a></p><p><a href="test">#éaa</a></p>',

View File

@ -9,11 +9,12 @@ import api from 'mastodon/api';
import { roundTo10 } from 'mastodon/utils/numbers'; import { roundTo10 } from 'mastodon/utils/numbers';
const dateForCohort = cohort => { const dateForCohort = cohort => {
const timeZone = 'UTC';
switch(cohort.frequency) { switch(cohort.frequency) {
case 'day': case 'day':
return <FormattedDate value={cohort.period} month='long' day='2-digit' />; return <FormattedDate value={cohort.period} month='long' day='2-digit' timeZone={timeZone} />;
default: default:
return <FormattedDate value={cohort.period} month='long' year='numeric' />; return <FormattedDate value={cohort.period} month='long' year='numeric' timeZone={timeZone} />;
} }
}; };

View File

@ -6,21 +6,10 @@ import { reduceMotion } from '../initial_state';
import { ShortNumber } from './short_number'; import { ShortNumber } from './short_number';
const obfuscatedCount = (count: number) => {
if (count < 0) {
return 0;
} else if (count <= 1) {
return count;
} else {
return '1+';
}
};
interface Props { interface Props {
value: number; value: number;
obfuscate?: boolean;
} }
export const AnimatedNumber: React.FC<Props> = ({ value, obfuscate }) => { export const AnimatedNumber: React.FC<Props> = ({ value }) => {
const [previousValue, setPreviousValue] = useState(value); const [previousValue, setPreviousValue] = useState(value);
const [direction, setDirection] = useState<1 | -1>(1); const [direction, setDirection] = useState<1 | -1>(1);
@ -36,11 +25,7 @@ export const AnimatedNumber: React.FC<Props> = ({ value, obfuscate }) => {
); );
if (reduceMotion) { if (reduceMotion) {
return obfuscate ? ( return <ShortNumber value={value} />;
<>{obfuscatedCount(value)}</>
) : (
<ShortNumber value={value} />
);
} }
const styles = [ const styles = [
@ -67,11 +52,7 @@ export const AnimatedNumber: React.FC<Props> = ({ value, obfuscate }) => {
transform: `translateY(${style.y * 100}%)`, transform: `translateY(${style.y * 100}%)`,
}} }}
> >
{obfuscate ? (
obfuscatedCount(data as number)
) : (
<ShortNumber value={data as number} /> <ShortNumber value={data as number} />
)}
</span> </span>
))} ))}
</span> </span>

View File

@ -16,7 +16,13 @@ export default class Column extends PureComponent {
}; };
scrollTop () { scrollTop () {
const scrollable = this.props.bindToDocument ? document.scrollingElement : this.node.querySelector('.scrollable'); let scrollable = null;
if (this.props.bindToDocument) {
scrollable = document.scrollingElement;
} else {
scrollable = this.node.querySelector('.scrollable');
}
if (!scrollable) { if (!scrollable) {
return; return;

View File

@ -1,9 +1,16 @@
/* eslint-disable @typescript-eslint/no-unsafe-call,
@typescript-eslint/no-unsafe-return,
@typescript-eslint/no-unsafe-assignment,
@typescript-eslint/no-unsafe-member-access
-- the settings store is not yet typed */
import type { PropsWithChildren } from 'react'; import type { PropsWithChildren } from 'react';
import { useCallback, useState } from 'react'; import { useCallback, useState, useEffect } from 'react';
import { defineMessages, useIntl } from 'react-intl'; import { defineMessages, useIntl } from 'react-intl';
import { changeSetting } from 'mastodon/actions/settings';
import { bannerSettings } from 'mastodon/settings'; import { bannerSettings } from 'mastodon/settings';
import { useAppSelector, useAppDispatch } from 'mastodon/store';
import { IconButton } from './icon_button'; import { IconButton } from './icon_button';
@ -19,13 +26,25 @@ export const DismissableBanner: React.FC<PropsWithChildren<Props>> = ({
id, id,
children, children,
}) => { }) => {
const [visible, setVisible] = useState(!bannerSettings.get(id)); const dismissed = useAppSelector((state) =>
state.settings.getIn(['dismissed_banners', id], false),
);
const dispatch = useAppDispatch();
const [visible, setVisible] = useState(!bannerSettings.get(id) && !dismissed);
const intl = useIntl(); const intl = useIntl();
const handleDismiss = useCallback(() => { const handleDismiss = useCallback(() => {
setVisible(false); setVisible(false);
bannerSettings.set(id, true); bannerSettings.set(id, true);
}, [id]); dispatch(changeSetting(['dismissed_banners', id], true));
}, [id, dispatch]);
useEffect(() => {
if (!visible && !dismissed) {
dispatch(changeSetting(['dismissed_banners', id], true));
}
}, [id, dispatch, visible, dismissed]);
if (!visible) { if (!visible) {
return null; return null;
@ -33,8 +52,6 @@ export const DismissableBanner: React.FC<PropsWithChildren<Props>> = ({
return ( return (
<div className='dismissable-banner'> <div className='dismissable-banner'>
<div className='dismissable-banner__message'>{children}</div>
<div className='dismissable-banner__action'> <div className='dismissable-banner__action'>
<IconButton <IconButton
icon='times' icon='times'
@ -42,6 +59,8 @@ export const DismissableBanner: React.FC<PropsWithChildren<Props>> = ({
onClick={handleDismiss} onClick={handleDismiss}
/> />
</div> </div>
<div className='dismissable-banner__message'>{children}</div>
</div> </div>
); );
}; };

View File

@ -10,8 +10,8 @@ import { groupBy, minBy } from 'lodash';
import { getStatusContent } from './status_content'; import { getStatusContent } from './status_content';
// About two lines on desktop // Fit on a single line on desktop
const VISIBLE_HASHTAGS = 7; const VISIBLE_HASHTAGS = 3;
// Those types are not correct, they need to be replaced once this part of the state is typed // Those types are not correct, they need to be replaced once this part of the state is typed
export type TagLike = Record<{ name: string }>; export type TagLike = Record<{ name: string }>;
@ -23,8 +23,9 @@ export type StatusLike = Record<{
}>; }>;
function normalizeHashtag(hashtag: string) { function normalizeHashtag(hashtag: string) {
if (hashtag && hashtag.startsWith('#')) return hashtag.slice(1); return (
else return hashtag; hashtag && hashtag.startsWith('#') ? hashtag.slice(1) : hashtag
).normalize('NFKC');
} }
function isNodeLinkHashtag(element: Node): element is HTMLLinkElement { function isNodeLinkHashtag(element: Node): element is HTMLLinkElement {
@ -70,9 +71,16 @@ function uniqueHashtagsWithCaseHandling(hashtags: string[]) {
} }
// Create the collator once, this is much more efficient // Create the collator once, this is much more efficient
const collator = new Intl.Collator(undefined, { sensitivity: 'accent' }); const collator = new Intl.Collator(undefined, {
sensitivity: 'base', // we use this to emulate the ASCII folding done on the server-side, hopefuly more efficiently
});
function localeAwareInclude(collection: string[], value: string) { function localeAwareInclude(collection: string[], value: string) {
return collection.find((item) => collator.compare(item, value) === 0); const normalizedValue = value.normalize('NFKC');
return !!collection.find(
(item) => collator.compare(item.normalize('NFKC'), normalizedValue) === 0,
);
} }
// We use an intermediate function here to make it easier to test // We use an intermediate function here to make it easier to test
@ -101,7 +109,7 @@ export function computeHashtagBarForStatus(status: StatusLike): {
const lastChild = template.content.lastChild; const lastChild = template.content.lastChild;
if (!lastChild) return defaultResult; if (!lastChild || lastChild.nodeType === Node.TEXT_NODE) return defaultResult;
template.content.removeChild(lastChild); template.content.removeChild(lastChild);
const contentWithoutLastLine = template; const contentWithoutLastLine = template;
@ -121,11 +129,13 @@ export function computeHashtagBarForStatus(status: StatusLike): {
// try to see if the last line is only hashtags // try to see if the last line is only hashtags
let onlyHashtags = true; let onlyHashtags = true;
const normalizedTagNames = tagNames.map((tag) => tag.normalize('NFKC'));
Array.from(lastChild.childNodes).forEach((node) => { Array.from(lastChild.childNodes).forEach((node) => {
if (isNodeLinkHashtag(node) && node.textContent) { if (isNodeLinkHashtag(node) && node.textContent) {
const normalized = normalizeHashtag(node.textContent); const normalized = normalizeHashtag(node.textContent);
if (!localeAwareInclude(tagNames, normalized)) { if (!localeAwareInclude(normalizedTagNames, normalized)) {
// stop here, this is not a real hashtag, so consider it as text // stop here, this is not a real hashtag, so consider it as text
onlyHashtags = false; onlyHashtags = false;
return; return;
@ -140,12 +150,14 @@ export function computeHashtagBarForStatus(status: StatusLike): {
} }
}); });
const hashtagsInBar = tagNames.filter( const hashtagsInBar = tagNames.filter((tag) => {
(tag) => const normalizedTag = tag.normalize('NFKC');
// the tag does not appear at all in the status content, it is an out-of-band tag // the tag does not appear at all in the status content, it is an out-of-band tag
!localeAwareInclude(contentHashtags, tag) && return (
!localeAwareInclude(lastLineHashtags, tag), !localeAwareInclude(contentHashtags, normalizedTag) &&
!localeAwareInclude(lastLineHashtags, normalizedTag)
); );
});
const isOnlyOneLine = contentWithoutLastLine.content.childElementCount === 0; const isOnlyOneLine = contentWithoutLastLine.content.childElementCount === 0;
const hasMedia = status.get('media_attachments').size > 0; const hasMedia = status.get('media_attachments').size > 0;
@ -198,13 +210,13 @@ const HashtagBar: React.FC<{
const revealedHashtags = expanded const revealedHashtags = expanded
? hashtags ? hashtags
: hashtags.slice(0, VISIBLE_HASHTAGS - 1); : hashtags.slice(0, VISIBLE_HASHTAGS);
return ( return (
<div className='hashtag-bar'> <div className='hashtag-bar'>
{revealedHashtags.map((hashtag) => ( {revealedHashtags.map((hashtag) => (
<Link key={hashtag} to={`/tags/${hashtag}`}> <Link key={hashtag} to={`/tags/${hashtag}`}>
#{hashtag} #<span>{hashtag}</span>
</Link> </Link>
))} ))}

View File

@ -24,7 +24,6 @@ interface Props {
overlay: boolean; overlay: boolean;
tabIndex: number; tabIndex: number;
counter?: number; counter?: number;
obfuscateCount?: boolean;
href?: string; href?: string;
ariaHidden: boolean; ariaHidden: boolean;
} }
@ -105,7 +104,6 @@ export class IconButton extends PureComponent<Props, States> {
tabIndex, tabIndex,
title, title,
counter, counter,
obfuscateCount,
href, href,
ariaHidden, ariaHidden,
} = this.props; } = this.props;
@ -131,7 +129,7 @@ export class IconButton extends PureComponent<Props, States> {
<Icon id={icon} fixedWidth aria-hidden='true' />{' '} <Icon id={icon} fixedWidth aria-hidden='true' />{' '}
{typeof counter !== 'undefined' && ( {typeof counter !== 'undefined' && (
<span className='icon-button__counter'> <span className='icon-button__counter'>
<AnimatedNumber value={counter} obfuscate={obfuscateCount} /> <AnimatedNumber value={counter} />
</span> </span>
)} )}
</> </>

View File

@ -73,7 +73,7 @@ class ScrollableList extends PureComponent {
const clientHeight = this.getClientHeight(); const clientHeight = this.getClientHeight();
const offset = scrollHeight - scrollTop - clientHeight; const offset = scrollHeight - scrollTop - clientHeight;
if (400 > offset && this.props.onLoadMore && this.props.hasMore && !this.props.isLoading) { if (scrollTop > 0 && offset < 400 && this.props.onLoadMore && this.props.hasMore && !this.props.isLoading) {
this.props.onLoadMore(); this.props.onLoadMore();
} }

View File

@ -546,10 +546,11 @@ class Status extends ImmutablePureComponent {
const visibilityIcon = visibilityIconInfo[status.get('visibility')]; const visibilityIcon = visibilityIconInfo[status.get('visibility')];
const {statusContentProps, hashtagBar} = getHashtagBarForStatus(status); const {statusContentProps, hashtagBar} = getHashtagBarForStatus(status);
const expanded = !status.get('hidden') || status.get('spoiler_text').length === 0;
return ( return (
<HotKeys handlers={handlers}> <HotKeys handlers={handlers}>
<div className={classNames('status__wrapper', `status__wrapper-${status.get('visibility')}`, { 'status__wrapper-reply': !!status.get('in_reply_to_id'), unread, focusable: !this.props.muted })} tabIndex={this.props.muted ? null : 0} data-featured={featured ? 'true' : null} aria-label={textForScreenReader(intl, status, rebloggedByText)} ref={this.handleRef}> <div className={classNames('status__wrapper', `status__wrapper-${status.get('visibility')}`, { 'status__wrapper-reply': !!status.get('in_reply_to_id'), unread, focusable: !this.props.muted })} tabIndex={this.props.muted ? null : 0} data-featured={featured ? 'true' : null} aria-label={textForScreenReader(intl, status, rebloggedByText)} ref={this.handleRef} data-nosnippet={status.getIn(['account', 'noindex'], true) || undefined}>
{prepend} {prepend}
<div className={classNames('status', `status-${status.get('visibility')}`, { 'status-reply': !!status.get('in_reply_to_id'), 'status--in-thread': !!rootId, 'status--first-in-thread': previousId && (!connectUp || connectToRoot), muted: this.props.muted })} data-id={status.get('id')}> <div className={classNames('status', `status-${status.get('visibility')}`, { 'status-reply': !!status.get('in_reply_to_id'), 'status--in-thread': !!rootId, 'status--first-in-thread': previousId && (!connectUp || connectToRoot), muted: this.props.muted })} data-id={status.get('id')}>
@ -574,7 +575,7 @@ class Status extends ImmutablePureComponent {
<StatusContent <StatusContent
status={status} status={status}
onClick={this.handleClick} onClick={this.handleClick}
expanded={!status.get('hidden')} expanded={expanded}
onExpandedToggle={this.handleExpandedToggle} onExpandedToggle={this.handleExpandedToggle}
onTranslate={this.handleTranslate} onTranslate={this.handleTranslate}
collapsible collapsible
@ -584,7 +585,7 @@ class Status extends ImmutablePureComponent {
{media} {media}
{hashtagBar} {expanded && hashtagBar}
<StatusActionBar scrollKey={scrollKey} status={status} account={account} onFilter={matchedFilters ? this.handleFilterClick : null} {...other} /> <StatusActionBar scrollKey={scrollKey} status={status} account={account} onFilter={matchedFilters ? this.handleFilterClick : null} {...other} />
</div> </div>

View File

@ -362,7 +362,7 @@ class StatusActionBar extends ImmutablePureComponent {
return ( return (
<div className='status__action-bar'> <div className='status__action-bar'>
<IconButton className='status__action-bar__button' title={replyTitle} icon={status.get('in_reply_to_account_id') === status.getIn(['account', 'id']) ? 'reply' : replyIcon} onClick={this.handleReplyClick} counter={status.get('replies_count')} obfuscateCount /> <IconButton className='status__action-bar__button' title={replyTitle} icon={status.get('in_reply_to_account_id') === status.getIn(['account', 'id']) ? 'reply' : replyIcon} onClick={this.handleReplyClick} counter={status.get('replies_count')} />
<IconButton className={classNames('status__action-bar__button', { reblogPrivate })} disabled={!publicStatus && !reblogPrivate} active={status.get('reblogged')} title={reblogTitle} icon='retweet' onClick={this.handleReblogClick} counter={withCounters ? status.get('reblogs_count') : undefined} /> <IconButton className={classNames('status__action-bar__button', { reblogPrivate })} disabled={!publicStatus && !reblogPrivate} active={status.get('reblogged')} title={reblogTitle} icon='retweet' onClick={this.handleReblogClick} counter={withCounters ? status.get('reblogs_count') : undefined} />
<IconButton className='status__action-bar__button star-icon' animate active={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} counter={withCounters ? status.get('favourites_count') : undefined} /> <IconButton className='status__action-bar__button star-icon' animate active={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} counter={withCounters ? status.get('favourites_count') : undefined} />
<IconButton className='status__action-bar__button bookmark-icon' disabled={!signedIn} active={status.get('bookmarked')} title={intl.formatMessage(messages.bookmark)} icon='bookmark' onClick={this.handleBookmarkClick} /> <IconButton className='status__action-bar__button bookmark-icon' disabled={!signedIn} active={status.get('bookmarked')} title={intl.formatMessage(messages.bookmark)} icon='bookmark' onClick={this.handleBookmarkClick} />

View File

@ -6,6 +6,7 @@ import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import classNames from 'classnames'; import classNames from 'classnames';
import { Helmet } from 'react-helmet'; import { Helmet } from 'react-helmet';
import { List as ImmutableList } from 'immutable';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
@ -161,7 +162,7 @@ class About extends PureComponent {
</Section> </Section>
<Section title={intl.formatMessage(messages.rules)}> <Section title={intl.formatMessage(messages.rules)}>
{!isLoading && (server.get('rules', []).isEmpty() ? ( {!isLoading && (server.get('rules', ImmutableList()).isEmpty() ? (
<p><FormattedMessage id='about.not_available' defaultMessage='This information has not been made available on this server.' /></p> <p><FormattedMessage id='about.not_available' defaultMessage='This information has not been made available on this server.' /></p>
) : ( ) : (
<ol className='rules-list'> <ol className='rules-list'>

View File

@ -11,7 +11,7 @@ const mapStateToProps = (state, { account }) => ({
const mapDispatchToProps = (dispatch, { account }) => ({ const mapDispatchToProps = (dispatch, { account }) => ({
onSave (value) { onSave (value) {
dispatch(submitAccountNote(account.get('id'), value)); dispatch(submitAccountNote({ id: account.get('id'), value}));
}, },
}); });

View File

@ -205,11 +205,11 @@ class Audio extends PureComponent {
}; };
toggleMute = () => { toggleMute = () => {
const muted = !this.state.muted; const muted = !(this.state.muted || this.state.volume === 0);
this.setState({ muted }, () => { this.setState((state) => ({ muted, volume: Math.max(state.volume || 0.5, 0.05) }), () => {
if (this.gainNode) { if (this.gainNode) {
this.gainNode.gain.value = muted ? 0 : this.state.volume; this.gainNode.gain.value = this.state.muted ? 0 : this.state.volume;
} }
}); });
}; };
@ -287,7 +287,7 @@ class Audio extends PureComponent {
const { x } = getPointerPosition(this.volume, e); const { x } = getPointerPosition(this.volume, e);
if(!isNaN(x)) { if(!isNaN(x)) {
this.setState({ volume: x }, () => { this.setState((state) => ({ volume: x, muted: state.muted && x === 0 }), () => {
if (this.gainNode) { if (this.gainNode) {
this.gainNode.gain.value = this.state.muted ? 0 : x; this.gainNode.gain.value = this.state.muted ? 0 : x;
} }
@ -466,8 +466,9 @@ class Audio extends PureComponent {
render () { render () {
const { src, intl, alt, lang, editable, autoPlay, sensitive, blurhash } = this.props; const { src, intl, alt, lang, editable, autoPlay, sensitive, blurhash } = this.props;
const { paused, muted, volume, currentTime, duration, buffer, dragging, revealed } = this.state; const { paused, volume, currentTime, duration, buffer, dragging, revealed } = this.state;
const progress = Math.min((currentTime / duration) * 100, 100); const progress = Math.min((currentTime / duration) * 100, 100);
const muted = this.state.muted || volume === 0;
let warning; let warning;
@ -557,12 +558,12 @@ class Audio extends PureComponent {
<button type='button' title={intl.formatMessage(muted ? messages.unmute : messages.mute)} aria-label={intl.formatMessage(muted ? messages.unmute : messages.mute)} className='player-button' onClick={this.toggleMute}><Icon id={muted ? 'volume-off' : 'volume-up'} fixedWidth /></button> <button type='button' title={intl.formatMessage(muted ? messages.unmute : messages.mute)} aria-label={intl.formatMessage(muted ? messages.unmute : messages.mute)} className='player-button' onClick={this.toggleMute}><Icon id={muted ? 'volume-off' : 'volume-up'} fixedWidth /></button>
<div className={classNames('video-player__volume', { active: this.state.hovered })} ref={this.setVolumeRef} onMouseDown={this.handleVolumeMouseDown}> <div className={classNames('video-player__volume', { active: this.state.hovered })} ref={this.setVolumeRef} onMouseDown={this.handleVolumeMouseDown}>
<div className='video-player__volume__current' style={{ width: `${volume * 100}%`, backgroundColor: this._getAccentColor() }} /> <div className='video-player__volume__current' style={{ width: `${muted ? 0 : volume * 100}%`, backgroundColor: this._getAccentColor() }} />
<span <span
className='video-player__volume__handle' className='video-player__volume__handle'
tabIndex={0} tabIndex={0}
style={{ left: `${volume * 100}%`, backgroundColor: this._getAccentColor() }} style={{ left: `${muted ? 0 : volume * 100}%`, backgroundColor: this._getAccentColor() }}
/> />
</div> </div>

View File

@ -1,14 +1,14 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { PureComponent } from 'react'; import { PureComponent } from 'react';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import { defineMessages, injectIntl, FormattedMessage, FormattedList } from 'react-intl';
import classNames from 'classnames'; import classNames from 'classnames';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import { Icon } from 'mastodon/components/icon'; import { Icon } from 'mastodon/components/icon';
import { searchEnabled } from 'mastodon/initial_state'; import { domain, searchEnabled } from 'mastodon/initial_state';
import { HASHTAG_REGEX } from 'mastodon/utils/hashtags'; import { HASHTAG_REGEX } from 'mastodon/utils/hashtags';
const messages = defineMessages({ const messages = defineMessages({
@ -16,6 +16,17 @@ const messages = defineMessages({
placeholderSignedIn: { id: 'search.search_or_paste', defaultMessage: 'Search or paste URL' }, placeholderSignedIn: { id: 'search.search_or_paste', defaultMessage: 'Search or paste URL' },
}); });
const labelForRecentSearch = search => {
switch(search.get('type')) {
case 'account':
return `@${search.get('q')}`;
case 'hashtag':
return `#${search.get('q')}`;
default:
return search.get('q');
}
};
class Search extends PureComponent { class Search extends PureComponent {
static contextTypes = { static contextTypes = {
@ -45,6 +56,17 @@ class Search extends PureComponent {
options: [], options: [],
}; };
defaultOptions = [
{ label: <><mark>has:</mark> <FormattedList type='disjunction' value={['media', 'poll', 'embed']} /></>, action: e => { e.preventDefault(); this._insertText('has:') } },
{ label: <><mark>is:</mark> <FormattedList type='disjunction' value={['reply', 'sensitive']} /></>, action: e => { e.preventDefault(); this._insertText('is:') } },
{ label: <><mark>language:</mark> <FormattedMessage id='search_popout.language_code' defaultMessage='ISO language code' /></>, action: e => { e.preventDefault(); this._insertText('language:') } },
{ label: <><mark>from:</mark> <FormattedMessage id='search_popout.user' defaultMessage='user' /></>, action: e => { e.preventDefault(); this._insertText('from:') } },
{ label: <><mark>before:</mark> <FormattedMessage id='search_popout.specific_date' defaultMessage='specific date' /></>, action: e => { e.preventDefault(); this._insertText('before:') } },
{ label: <><mark>during:</mark> <FormattedMessage id='search_popout.specific_date' defaultMessage='specific date' /></>, action: e => { e.preventDefault(); this._insertText('during:') } },
{ label: <><mark>after:</mark> <FormattedMessage id='search_popout.specific_date' defaultMessage='specific date' /></>, action: e => { e.preventDefault(); this._insertText('after:') } },
{ label: <><mark>in:</mark> <FormattedList type='disjunction' value={['all', 'library']} /></>, action: e => { e.preventDefault(); this._insertText('in:') } }
];
setRef = c => { setRef = c => {
this.searchForm = c; this.searchForm = c;
}; };
@ -70,7 +92,7 @@ class Search extends PureComponent {
handleKeyDown = (e) => { handleKeyDown = (e) => {
const { selectedOption } = this.state; const { selectedOption } = this.state;
const options = this._getOptions(); const options = searchEnabled ? this._getOptions().concat(this.defaultOptions) : this._getOptions();
switch(e.key) { switch(e.key) {
case 'Escape': case 'Escape':
@ -100,11 +122,9 @@ class Search extends PureComponent {
if (selectedOption === -1) { if (selectedOption === -1) {
this._submit(); this._submit();
} else if (options.length > 0) { } else if (options.length > 0) {
options[selectedOption].action(); options[selectedOption].action(e);
} }
this._unfocus();
break; break;
case 'Delete': case 'Delete':
if (selectedOption > -1 && options.length > 0) { if (selectedOption > -1 && options.length > 0) {
@ -147,6 +167,7 @@ class Search extends PureComponent {
router.history.push(`/tags/${query}`); router.history.push(`/tags/${query}`);
onClickSearchResult(query, 'hashtag'); onClickSearchResult(query, 'hashtag');
this._unfocus();
}; };
handleAccountClick = () => { handleAccountClick = () => {
@ -157,6 +178,7 @@ class Search extends PureComponent {
router.history.push(`/@${query}`); router.history.push(`/@${query}`);
onClickSearchResult(query, 'account'); onClickSearchResult(query, 'account');
this._unfocus();
}; };
handleURLClick = () => { handleURLClick = () => {
@ -164,6 +186,7 @@ class Search extends PureComponent {
const { value, onOpenURL } = this.props; const { value, onOpenURL } = this.props;
onOpenURL(value, router.history); onOpenURL(value, router.history);
this._unfocus();
}; };
handleStatusSearch = () => { handleStatusSearch = () => {
@ -175,13 +198,19 @@ class Search extends PureComponent {
}; };
handleRecentSearchClick = search => { handleRecentSearchClick = search => {
const { onChange } = this.props;
const { router } = this.context; const { router } = this.context;
if (search.get('type') === 'account') { if (search.get('type') === 'account') {
router.history.push(`/@${search.get('q')}`); router.history.push(`/@${search.get('q')}`);
} else if (search.get('type') === 'hashtag') { } else if (search.get('type') === 'hashtag') {
router.history.push(`/tags/${search.get('q')}`); router.history.push(`/tags/${search.get('q')}`);
} else {
onChange(search.get('q'));
this._submit(search.get('type'));
} }
this._unfocus();
}; };
handleForgetRecentSearchClick = search => { handleForgetRecentSearchClick = search => {
@ -194,15 +223,33 @@ class Search extends PureComponent {
document.querySelector('.ui').parentElement.focus(); document.querySelector('.ui').parentElement.focus();
} }
_insertText (text) {
const { value, onChange } = this.props;
if (value === '') {
onChange(text);
} else if (value[value.length - 1] === ' ') {
onChange(`${value}${text}`);
} else {
onChange(`${value} ${text}`);
}
}
_submit (type) { _submit (type) {
const { onSubmit, openInRoute } = this.props; const { onSubmit, openInRoute, value, onClickSearchResult } = this.props;
const { router } = this.context; const { router } = this.context;
onSubmit(type); onSubmit(type);
if (value) {
onClickSearchResult(value, type);
}
if (openInRoute) { if (openInRoute) {
router.history.push('/search'); router.history.push('/search');
} }
this._unfocus();
} }
_getOptions () { _getOptions () {
@ -215,7 +262,7 @@ class Search extends PureComponent {
const { recent } = this.props; const { recent } = this.props;
return recent.toArray().map(search => ({ return recent.toArray().map(search => ({
label: search.get('type') === 'account' ? `@${search.get('q')}` : `#${search.get('q')}`, label: labelForRecentSearch(search),
action: () => this.handleRecentSearchClick(search), action: () => this.handleRecentSearchClick(search),
@ -325,6 +372,22 @@ class Search extends PureComponent {
</div> </div>
</> </>
)} )}
<h4><FormattedMessage id='search_popout.options' defaultMessage='Search options' /></h4>
{searchEnabled ? (
<div className='search__popout__menu'>
{this.defaultOptions.map(({ key, label, action }, i) => (
<button key={key} onMouseDown={action} className={classNames('search__popout__menu__item', { selected: selectedOption === ((options.length || recent.size) + i) })}>
{label}
</button>
))}
</div>
) : (
<div className='search__popout__menu__message'>
<FormattedMessage id='search_popout.full_text_search_disabled_message' defaultMessage='Not available on {domain}.' values={{ domain }} />
</div>
)}
</div> </div>
</div> </div>
); );

View File

@ -1,46 +1,36 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { FormattedMessage, defineMessages, injectIntl } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import { Icon } from 'mastodon/components/icon'; import { Icon } from 'mastodon/components/icon';
import { LoadMore } from 'mastodon/components/load_more'; import { LoadMore } from 'mastodon/components/load_more';
import { SearchSection } from 'mastodon/features/explore/components/search_section';
import { ImmutableHashtag as Hashtag } from '../../../components/hashtag'; import { ImmutableHashtag as Hashtag } from '../../../components/hashtag';
import AccountContainer from '../../../containers/account_container'; import AccountContainer from '../../../containers/account_container';
import StatusContainer from '../../../containers/status_container'; import StatusContainer from '../../../containers/status_container';
import { searchEnabled } from '../../../initial_state';
const messages = defineMessages({ const INITIAL_PAGE_LIMIT = 10;
dismissSuggestion: { id: 'suggestions.dismiss', defaultMessage: 'Dismiss suggestion' },
}); const withoutLastResult = list => {
if (list.size > INITIAL_PAGE_LIMIT && list.size % INITIAL_PAGE_LIMIT === 1) {
return list.skipLast(1);
} else {
return list;
}
};
class SearchResults extends ImmutablePureComponent { class SearchResults extends ImmutablePureComponent {
static propTypes = { static propTypes = {
results: ImmutablePropTypes.map.isRequired, results: ImmutablePropTypes.map.isRequired,
suggestions: ImmutablePropTypes.list.isRequired,
fetchSuggestions: PropTypes.func.isRequired,
expandSearch: PropTypes.func.isRequired, expandSearch: PropTypes.func.isRequired,
dismissSuggestion: PropTypes.func.isRequired,
searchTerm: PropTypes.string, searchTerm: PropTypes.string,
intl: PropTypes.object.isRequired,
}; };
componentDidMount () {
if (this.props.searchTerm === '') {
this.props.fetchSuggestions();
}
}
componentDidUpdate () {
if (this.props.searchTerm === '') {
this.props.fetchSuggestions();
}
}
handleLoadMoreAccounts = () => this.props.expandSearch('accounts'); handleLoadMoreAccounts = () => this.props.expandSearch('accounts');
handleLoadMoreStatuses = () => this.props.expandSearch('statuses'); handleLoadMoreStatuses = () => this.props.expandSearch('statuses');
@ -48,97 +38,52 @@ class SearchResults extends ImmutablePureComponent {
handleLoadMoreHashtags = () => this.props.expandSearch('hashtags'); handleLoadMoreHashtags = () => this.props.expandSearch('hashtags');
render () { render () {
const { intl, results, suggestions, dismissSuggestion, searchTerm } = this.props; const { results } = this.props;
if (searchTerm === '' && !suggestions.isEmpty()) {
return (
<div className='search-results'>
<div className='trends'>
<div className='trends__header'>
<Icon id='user-plus' fixedWidth />
<FormattedMessage id='suggestions.header' defaultMessage='You might be interested in…' />
</div>
{suggestions && suggestions.map(suggestion => (
<AccountContainer
key={suggestion.get('account')}
id={suggestion.get('account')}
actionIcon={suggestion.get('source') === 'past_interactions' ? 'times' : null}
actionTitle={suggestion.get('source') === 'past_interactions' ? intl.formatMessage(messages.dismissSuggestion) : null}
onActionClick={dismissSuggestion}
/>
))}
</div>
</div>
);
}
let accounts, statuses, hashtags; let accounts, statuses, hashtags;
let count = 0;
if (results.get('accounts') && results.get('accounts').size > 0) { if (results.get('accounts') && results.get('accounts').size > 0) {
count += results.get('accounts').size;
accounts = ( accounts = (
<div className='search-results__section'> <SearchSection title={<><Icon id='users' fixedWidth /><FormattedMessage id='search_results.accounts' defaultMessage='Profiles' /></>}>
<h5><Icon id='users' fixedWidth /><FormattedMessage id='search_results.accounts' defaultMessage='Profiles' /></h5> {withoutLastResult(results.get('accounts')).map(accountId => <AccountContainer key={accountId} id={accountId} />)}
{(results.get('accounts').size > INITIAL_PAGE_LIMIT && results.get('accounts').size % INITIAL_PAGE_LIMIT === 1) && <LoadMore visible onClick={this.handleLoadMoreAccounts} />}
{results.get('accounts').map(accountId => <AccountContainer key={accountId} id={accountId} />)} </SearchSection>
{results.get('accounts').size >= 5 && <LoadMore visible onClick={this.handleLoadMoreAccounts} />}
</div>
);
}
if (results.get('statuses') && results.get('statuses').size > 0) {
count += results.get('statuses').size;
statuses = (
<div className='search-results__section'>
<h5><Icon id='quote-right' fixedWidth /><FormattedMessage id='search_results.statuses' defaultMessage='Posts' /></h5>
{results.get('statuses').map(statusId => <StatusContainer key={statusId} id={statusId} />)}
{results.get('statuses').size >= 5 && <LoadMore visible onClick={this.handleLoadMoreStatuses} />}
</div>
);
} else if(results.get('statuses') && results.get('statuses').size === 0 && !searchEnabled && !(searchTerm.startsWith('@') || searchTerm.startsWith('#') || searchTerm.includes(' '))) {
statuses = (
<div className='search-results__section'>
<h5><Icon id='quote-right' fixedWidth /><FormattedMessage id='search_results.statuses' defaultMessage='Posts' /></h5>
<div className='search-results__info'>
<FormattedMessage id='search_results.statuses_fts_disabled' defaultMessage='Searching posts by their content is not enabled on this Mastodon server.' />
</div>
</div>
); );
} }
if (results.get('hashtags') && results.get('hashtags').size > 0) { if (results.get('hashtags') && results.get('hashtags').size > 0) {
count += results.get('hashtags').size;
hashtags = ( hashtags = (
<div className='search-results__section'> <SearchSection title={<><Icon id='hashtag' fixedWidth /><FormattedMessage id='search_results.hashtags' defaultMessage='Hashtags' /></>}>
<h5><Icon id='hashtag' fixedWidth /><FormattedMessage id='search_results.hashtags' defaultMessage='Hashtags' /></h5> {withoutLastResult(results.get('hashtags')).map(hashtag => <Hashtag key={hashtag.get('name')} hashtag={hashtag} />)}
{(results.get('hashtags').size > INITIAL_PAGE_LIMIT && results.get('hashtags').size % INITIAL_PAGE_LIMIT === 1) && <LoadMore visible onClick={this.handleLoadMoreHashtags} />}
{results.get('hashtags').map(hashtag => <Hashtag key={hashtag.get('name')} hashtag={hashtag} />)} </SearchSection>
{results.get('hashtags').size >= 5 && <LoadMore visible onClick={this.handleLoadMoreHashtags} />}
</div>
); );
} }
if (results.get('statuses') && results.get('statuses').size > 0) {
statuses = (
<SearchSection title={<><Icon id='quote-right' fixedWidth /><FormattedMessage id='search_results.statuses' defaultMessage='Posts' /></>}>
{withoutLastResult(results.get('statuses')).map(statusId => <StatusContainer key={statusId} id={statusId} />)}
{(results.get('statuses').size > INITIAL_PAGE_LIMIT && results.get('statuses').size % INITIAL_PAGE_LIMIT === 1) && <LoadMore visible onClick={this.handleLoadMoreStatuses} />}
</SearchSection>
);
}
return ( return (
<div className='search-results'> <div className='search-results'>
<div className='search-results__header'> <div className='search-results__header'>
<Icon id='search' fixedWidth /> <Icon id='search' fixedWidth />
<FormattedMessage id='search_results.total' defaultMessage='{count, plural, one {# result} other {# results}}' values={{ count }} /> <FormattedMessage id='explore.search_results' defaultMessage='Search results' />
</div> </div>
{accounts} {accounts}
{statuses}
{hashtags} {hashtags}
{statuses}
</div> </div>
); );
} }
} }
export default injectIntl(SearchResults); export default SearchResults;

View File

@ -4,7 +4,7 @@ import { PureComponent } from 'react';
const iconStyle = { const iconStyle = {
height: null, height: null,
lineHeight: '27px', lineHeight: '27px',
width: `${18 * 1.28571429}px`, minWidth: `${18 * 1.28571429}px`,
}; };
export default class TextIconButton extends PureComponent { export default class TextIconButton extends PureComponent {

View File

@ -15,7 +15,7 @@ import Search from '../components/search';
const mapStateToProps = state => ({ const mapStateToProps = state => ({
value: state.getIn(['search', 'value']), value: state.getIn(['search', 'value']),
submitted: state.getIn(['search', 'submitted']), submitted: state.getIn(['search', 'submitted']),
recent: state.getIn(['search', 'recent']), recent: state.getIn(['search', 'recent']).reverse(),
}); });
const mapDispatchToProps = dispatch => ({ const mapDispatchToProps = dispatch => ({

Some files were not shown because too many files have changed in this diff Show More