Compare commits

...

260 Commits

Author SHA1 Message Date
Claire
9b31a5fc4c [Glitch] Fix code style issue
Port 3bbf3e9709 to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2025-05-06 17:21:02 +02:00
Claire
b6aa0b4990 [Glitch] Merge commit from fork
Port 79931bf3ae to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2025-05-06 17:21:02 +02:00
Claire
7868b545ed Merge pull request #3064 from glitch-soc/glitch-soc/merge-4.3
Merge upstream changes up to e6591bf322
2025-05-06 15:48:23 +02:00
Claire
bd8d96e699 Merge commit 'e6591bf322c7e47a8420a588d52a44a585c10b54' into glitch-soc/merge-4.3
Conflicts:
- `docker-compose.yml`:
  Conflict because of different repo names. Updated version.
2025-05-06 15:28:55 +02:00
Claire
e6591bf322 Fix code style issue 2025-05-06 15:08:57 +02:00
Claire
30e25ff7fc Bump version to v4.3.8 2025-05-06 15:04:34 +02:00
Claire
5ef82d7937 Update dependency net-imap 2025-05-06 15:04:34 +02:00
Claire
e14bf631b5 Update dependency nokogiri 2025-05-06 15:04:34 +02:00
Claire
6d46225718 Merge commit from fork
* Check scheme in account and post links

* Harden media attachments

* Client-side mitigation

* Client-side mitigation for media attachments
2025-05-06 15:02:13 +02:00
Claire
022af54ea2 Merge pull request #3061 from ClearlyClaire/glitch-soc/backports-4.3
Merge upstream changes to stable-4.3 up to ec2023233d
2025-05-06 08:03:24 +02:00
Claire
bcf788dad7 [Glitch] Fix sign-up e-mail confirmation page reloading on error or redirect
Port 698e4fdef2 to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2025-05-05 20:39:23 +02:00
Claire
7917b495d2 Merge commit 'ec2023233d3e7cae1aba5aa1bdce0e6d72437101' into glitch-soc/backports-4.3 2025-05-05 20:35:05 +02:00
Claire
ec2023233d Add warning for REDIS_NAMESPACE deprecation at startup (#34581) 2025-05-05 18:48:39 +02:00
Claire
e6a6c26c36 Remove double-query for signed query strings (#34610) 2025-05-05 18:48:39 +02:00
Claire
86a8aa5e5c Add built-in context for interaction policies (#34574) 2025-05-05 18:48:39 +02:00
Claire
a9f8b1ad96 Fix incorrect redirect in response to unauthenticated API requests in limited federation mode (#34549) 2025-05-05 18:48:39 +02:00
Claire
698e4fdef2 Fix sign-up e-mail confirmation page reloading on error or redirect (#34548) 2025-05-05 18:48:39 +02:00
Claire
72b1af137e Change activity distribution error handling to skip retrying for deleted accounts (#33617) 2025-05-05 18:48:39 +02:00
David Roetzel
8291afae35 [Glitch] Merge commit from fork
Port d8f9db547a to glitch-soc

Co-authored-by: Eugen Rochko <eugen@zeonfederated.com>
Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2025-05-03 18:39:30 +02:00
Claire
1ce0733cac Add option to stretch columns to available width (#3040) (#3042) 2025-04-15 13:57:49 +02:00
Claire
8bfbf2abaf Switch to glitch-soc docker images in docker-compose (#3038)
Fixes #3032
2025-04-12 12:42:07 +02:00
Claire
a63511425f Merge pull request #3026 from glitch-soc/glitch-soc/merge-4.3
Merge upstream changes up to 6f16011c5a
2025-04-02 14:26:10 +02:00
Claire
459e3451b6 Merge commit '6f16011c5a46bfa131cc1d6be89347dcff1b0fc1' into glitch-soc/merge-4.3 2025-04-02 12:10:24 +02:00
Jeong Arm
58d2c7b481 Merge pull request #2972 from tribela/fix-secondary-button
Fix secondary post button alignment
2025-04-02 12:09:17 +02:00
Claire
ae43b6bb09 Merge MoveGlitchUserSettings migration into MoveUserSettings (#2925) 2025-04-02 12:08:44 +02:00
Claire
6f16011c5a Bump version to v4.3.7 (#34328) 2025-04-02 09:14:21 +02:00
Claire
f79810313c Merge pull request #3024 from glitch-soc/glitch-soc/merge-4.3
Merge upstream changes up to 6d53e8c6c5 to stable-4.3
2025-04-02 08:34:39 +02:00
Claire
65a6840f71 Fix static version of animated PNG emojis not being properly extracted (#34337) 2025-04-01 16:01:14 +02:00
github-actions[bot]
527d9200d0 New Crowdin Translations for stable-4.3 (automated) (#34336)
Co-authored-by: GitHub Actions <noreply@github.com>
2025-04-01 11:10:20 +02:00
Claire
cbb9b83160 [Glitch] Fix bookmarks and favourites not being filtered
Port 2eb6d815d6 to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2025-03-31 20:17:32 +02:00
Claire
51fcb9ca99 [Glitch] Fix filters not applying in detailed view
Port 8c3eeb4d29 to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2025-03-31 20:15:13 +02:00
Claire
4a271072f5 Merge commit '6d53e8c6c5273bc8405ed8cf10ec6455ad7cc677' into glitch-soc/merge-4.3 2025-03-31 19:55:39 +02:00
Claire
6d53e8c6c5 Add delay to profile updates to debounce them (#34137) 2025-03-31 15:38:00 +02:00
Claire
d9fb61f305 Change account suspensions to be federated to recently-followed accounts as well (#34294) 2025-03-31 15:38:00 +02:00
Claire
6af733d1d8 Change AccountReachFinder to consider statuses based on suspension date (#34291) 2025-03-31 15:38:00 +02:00
Matt Jankowski
29eae75ca0 Define constants for sampling sizes in AccountReachFinder (#32805) 2025-03-31 15:38:00 +02:00
David Roetzel
8a3f25a4fa Use fixed order in flaky spec (#34279) 2025-03-31 15:38:00 +02:00
Claire
0615febd84 Add support for paginating partial collections in SynchronizeFollowersService (#34277) 2025-03-31 15:38:00 +02:00
Claire
86d8df0c03 Fix follower synchronization mechanism erroneously removing followers from multi-page collections (#34272) 2025-03-31 15:38:00 +02:00
Claire
105e5b1d76 Fix bookmarks and favourites not being filtered (#34260) 2025-03-31 15:38:00 +02:00
Claire
d6442b5455 Fix filters not applying in detailed view (#34259) 2025-03-31 15:38:00 +02:00
Claire
653868bb0c Change user archive signed URL TTL from 10 seconds to 1 hour (#34254) 2025-03-31 15:38:00 +02:00
Claire
4cb3fe35be Fix handling of malformed/unusual HTML (#34201) 2025-03-31 15:38:00 +02:00
Claire
8197e65cb3 Fix CacheBuster being queued for missing media attachments (#34253) 2025-03-31 15:38:00 +02:00
Claire
c48413ad4c Fix incorrect URL being used when cache busting (#34189) 2025-03-31 15:38:00 +02:00
Claire
9be391514b Fix streaming server refusing unix socket path in DATABASE_URL (#34091) 2025-03-31 15:38:00 +02:00
Claire
2340f4df81 Fix “x” hotkey not working on boosted filtered posts (#33758) 2025-03-31 15:38:00 +02:00
Claire
db86ec3d62 Merge pull request #2995 from glitch-soc/glitch-soc/merge-4.3
Merge upstream changes up to cdcd77ebff to stable-4.3
2025-03-13 15:44:43 +01:00
Claire
da6e667123 Merge commit 'cdcd77ebff3ff2093d47dbd622df763e88eaa731' into glitch-soc/merge-4.3 2025-03-13 15:28:46 +01:00
David Roetzel
cdcd77ebff Bump version to v4.3.6 2025-03-13 13:32:38 +01:00
Claire
c79c9e8c42 Update dependency omniauth-saml 2025-03-13 10:20:47 +01:00
Claire
e84031ea97 Update dependency rack 2025-03-13 10:20:47 +01:00
Claire
d01e407177 Fix Stoplight errors when using REDIS_NAMESPACE (#34126) 2025-03-13 10:20:47 +01:00
Claire
e1ff48978d Merge pull request #2990 from glitch-soc/glitch-soc/merge-4.3
Merge upstream changes up to a8613b7cda in stable-4.3
2025-03-10 12:46:02 +01:00
Claire
24304fbbe6 [Glitch] Change hashtag suggestion to prefer personal history capitalization
Port 62f019252a to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2025-03-10 12:25:32 +01:00
Claire
644caeb156 [Glitch] Fix preview cards under Content Warnings not being shown in detailed statuses
Port 1ed1cdba1b to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2025-03-10 12:25:06 +01:00
Claire
ad92660de6 [Glitch] Fix username and display name being hidden on narrow screens in moderation interface
Port b73e968641 to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2025-03-10 12:23:53 +01:00
Claire
c5d17a3997 Merge commit 'a8613b7cda61e46209cace4379a9dea81f45529e' into glitch-soc/merge-4.3 2025-03-10 12:20:09 +01:00
Claire
a8613b7cda Bump version to v4.3.5 2025-03-10 10:14:17 +01:00
Noel De Martin
0c2fa2aab4 Comment sidekiq build in docker compose (#33483) 2025-03-10 10:14:17 +01:00
Claire
62f019252a Change hashtag suggestion to prefer personal history capitalization (#34070) 2025-03-10 10:14:17 +01:00
Renaud Chaput
4228ca614c Fix processing errors for some HEIF images from iOS 18 (#34086) 2025-03-10 10:14:17 +01:00
Claire
7e20ee7695 Fix streaming server not filtering unknown-language posts from public timelines (#33774) 2025-03-10 10:14:17 +01:00
Claire
1ed1cdba1b Fix preview cards under Content Warnings not being shown in detailed statuses (#34068) 2025-03-10 10:14:17 +01:00
Claire
b73e968641 Fix username and display name being hidden on narrow screens in moderation interface (#33064) 2025-03-10 10:14:17 +01:00
Claire
559f7a8e61 [Glitch] Fix media preview height in compose form when 3 or more images are attached (#2988)
Port 50449ae7ac to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2025-03-08 16:13:49 +01:00
Claire
bcfd6ab3e4 Add Ruby 3.4 to Mastodon 4.3 test matrix (#34028) 2025-02-28 11:17:18 +01:00
Claire
1704a7d858 Merge pull request #2980 from glitch-soc/glitch-soc/merge-4.3
Merge upstream changes up to c1f398ae93
2025-02-27 16:31:19 +01:00
Claire
97fd14e141 Merge commit 'c1f398ae93d23ebb1ff5c7df5a32bc161a632980' into glitch-soc/merge-4.3 2025-02-27 16:21:44 +01:00
Claire
c1f398ae93 Bump version to v4.3.4 2025-02-27 16:09:48 +01:00
Claire
19b3469c29 Change HTML sanitization to remove unusable and unused embed tag (#34021) 2025-02-27 16:09:48 +01:00
Claire
57e4232b3e Update dependency uri 2025-02-27 16:09:48 +01:00
Claire
c6b501c42d Merge commit from fork
* Fix domain blocks/rationales being visible to unapproved/unconfirmed users

* Fix domain blocks/rationales being visible to suspended users

Co-authored-by: Claire <claire.github-309c@sitedethib.com>

* Allow moved users to view domain blocks

* Add authorization specs for `/api/v1/instance/domain_blocks` spec

* Fix tests

* Fix incorrect test setup

---------

Co-authored-by: Jeremy Kescher <jeremy@kescher.at>
2025-02-27 15:49:57 +01:00
Claire
5140f31cbb Merge commit from fork 2025-02-27 15:44:35 +01:00
Claire
adee65ad1b Merge pull request #2976 from glitch-soc/glitch-soc/merge-4.3
Merge upstream changes up to b1a584d252
2025-02-25 21:20:39 +01:00
Claire
fba7e85b9b [Glitch] Fix emoji rewrite adding unnecessary curft to the DOM for most emoji
Port 44f5f1f0a5 to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2025-02-25 18:40:30 +01:00
Claire
bc95675236 [Glitch] Fix preview card sizing in “Author attribution” in profile settings
Port 82e046ea06 to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2025-02-25 18:39:00 +01:00
Eugen Rochko
ccc4fcbdb8 [Glitch] Fix notification polling showing a loading bar in web UI
Port e856838e0c to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2025-02-25 18:38:35 +01:00
Oliver Geer
e3afbab115 [Glitch] Fix accounts table long display name
Port 0ad5c212c1 to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2025-02-25 18:38:12 +01:00
Claire
baac429103 Merge commit 'b1a584d252f4df4c2a1a9400d6588b4f36768216' into glitch-soc/merge-4.3
Conflicts:
- `app/lib/feed_manager.rb`:
  Not a real conflict, but glitch-soc has an extra `populate_direct_feed` method.
  Added upstream's code.
  In addition, upstream changed how notifications from follow subscriptions were
  handled, refactoring this file in the process.
  Ported upstream's changes.
- `app/services/precompute_feed_service.rb`:
  Not a real conflict, glitch-soc has extra code for the direct feed.
  Added upstream's new code for populating lists.
- `app/validators/poll_options_validator.rb`:
  Upstream split `PollValidator` in two, and glitch-soc had local changes to
  make the options configurable.
  Refactored as upstream did, keeping glitch-soc's configurable limits.
- `app/workers/feed_insert_worker.rb`:
  Upstream changed how notifications from follow subscriptions were handled,
  refactoring this file in the process.
  Conflict is due to glitch-soc having an extra timeline type (direct).
  Ported upstream's changes.
2025-02-25 18:33:54 +01:00
github-actions[bot]
b1a584d252 New Crowdin Translations for stable-4.3 (automated) (#33999)
Co-authored-by: GitHub Actions <noreply@github.com>
2025-02-25 17:12:07 +01:00
Claire
8787077462 Fix GET /api/v2/notifications/:id and POST /api/v2/notifications/:id/dismiss for ungrouped notifications (#33990) 2025-02-25 17:11:09 +01:00
Claire
10bcbf15af Update dependency nokogiri 2025-02-25 17:11:09 +01:00
Claire
fb29ac0f5f Update dependency rack 2025-02-25 17:11:09 +01:00
Claire
b0f88be86f Update dependencies net-imap, net-smtp and timeout 2025-02-25 17:11:09 +01:00
Claire
018b85e767 Update dependency ruby-vips 2025-02-25 17:11:09 +01:00
Claire
08d2250ad2 Fix handling of duplicate mentions in incoming status Update (#33911) 2025-02-25 17:11:09 +01:00
Claire
679e7555ee Fix filtering for lists (#33842) 2025-02-25 17:11:09 +01:00
Claire
452153d55d Optimize timeline generation (#33839) 2025-02-25 17:11:09 +01:00
Claire
2954c2facb Change preview cards to be shown when Content Warnings are expanded (#33827) 2025-02-25 17:11:09 +01:00
Claire
44e38b79de Fix emoji rewrite adding unnecessary curft to the DOM for most emoji (#33818) 2025-02-25 17:11:09 +01:00
Claire
b32a67ff74 Fix tootctl feeds build not building list timelines (#33783) 2025-02-25 17:11:09 +01:00
Claire
4f33b041f0 Fix flaky test in /api/v2/notifications tests (#33773) 2025-02-25 17:11:09 +01:00
Claire
6e906884cf Fix missing timeout options in Request class (#33769) 2025-02-25 17:11:09 +01:00
Claire
317715254f Fix incorrect signature after HTTP redirect (#33757) 2025-02-25 17:11:09 +01:00
Claire
2b148d3e88 Fix polls not being validated on edition (#33755) 2025-02-25 17:11:09 +01:00
Claire
227d48dbd5 Fix LDSignature tests (#33705) 2025-02-25 17:11:09 +01:00
Claire
94fed6e140 Change mastodon:setup to prevent overwriting already-configured servers (#33684) 2025-02-25 17:11:09 +01:00
Matt Jankowski
37b029d400 Move clear environment portion of mastodon:setup to private method (#33616) 2025-02-25 17:11:09 +01:00
Matt Jankowski
11baa26db2 Collect errors in setup rake task (#33603) 2025-02-25 17:11:09 +01:00
Claire
c7172b54fe Change notifications from moderators to not be filtered (#33654) 2025-02-25 17:11:09 +01:00
Matt Jankowski
74496838e7 Add UserRole#bypass_block? method for notification check (#32974) 2025-02-25 17:11:09 +01:00
Claire
ca39069433 Further harden the warnings against changing encryption secrets (#33631) 2025-02-25 17:11:09 +01:00
Claire
7ad9581940 Fix media preview height in compose form when 3 or more images are attached (#33571) 2025-02-25 17:11:09 +01:00
Claire
e4f2a054c9 Fix preview card sizing in “Author attribution” in profile settings (#33482) 2025-02-25 17:11:09 +01:00
Claire
68eb62f4a9 Fix processing of incoming notifications for unfilterable types (#33429) 2025-02-25 17:11:09 +01:00
Claire
e63d0cfe85 Fix intermittent failure on ap/activity/update spec timestamp check (#33425) 2025-02-25 17:11:09 +01:00
Matt Jankowski
4da31b8263 Fix intermittent failure on ap/activity/create spec timestamp check (#33406) 2025-02-25 17:11:09 +01:00
Claire
17695ace33 Fix featured tags for remote accounts not being kept up to date (#33372) 2025-02-25 17:11:09 +01:00
Eugen Rochko
fa2625a0d9 Fix notification polling showing a loading bar in web UI (#32960) 2025-02-25 17:11:09 +01:00
Oliver Geer
1005b2f7b2 Fix accounts table long display name (#29316) 2025-02-25 17:11:09 +01:00
Claire
f24b0e9505 Fix exclusive lists interfering with notifications (#28162) 2025-02-25 17:11:09 +01:00
Claire
4db64491ee Merge pull request #2969 from glitch-soc/glitch-soc/merge-4.3
Merge upstream changes up to 96455304bc
2025-02-12 20:53:05 +01:00
Claire
fd79e2417d Merge commit '96455304bc0d7157e9db13dba838a641ba42e907' into glitch-soc/merge-4.3
- `.github/workflows/build-nightly.yml`:
  We had modified the file to disable the custom ARM64 builder.
  Upstream has removed it, using github's runners.
  Took upstream's changes.
- `.github/workflows/build-push-pr.yml`:
  We had modified the file to disable the custom ARM64 builder.
  Upstream has removed it, using github's runners.
  Took upstream's changes.
- `.github/workflows/build-releases.yml`:
  We had modified the file to disable the custom ARM64 builder.
  Upstream has removed it, using github's runners.
  Took upstream's changes.
- `.github/workflows/build-security.yml`:
  We had modified the file to disable the custom ARM64 builder.
  Upstream has removed it, using github's runners.
  Took upstream's changes.
2025-02-12 20:33:05 +01:00
Claire
96455304bc Use github's native arm64 runners for docker builds (#33886) 2025-02-12 11:02:44 +01:00
Claire
63f4e2070c Merge commit 'faed9bf9f14f077443374f5eb3075b9878e24214' into glitch-soc/stable-4.3 2025-01-16 11:43:10 +01:00
Claire
faed9bf9f1 Bump version to v4.3.3 2025-01-16 11:42:36 +01:00
Claire
10f10844ff Update dependencies rails and rails-html-sanitizer 2025-01-16 11:42:36 +01:00
Michael Stanclift
5c8d2be23b Fix libyaml missing from Dockerfile build stage (#33591) 2025-01-16 11:42:36 +01:00
Claire
90072f4367 Fix incorrect relationship_severance_event attribute name in changelog (#33443) 2025-01-16 11:42:36 +01:00
Claire
512bfc0a54 Fix incorrect notification settings migration for non-followers (#33348) 2025-01-16 11:42:36 +01:00
Jesse Karmani
d764ae017d Fix down clause for notification policy v2 migrations (#33340) 2025-01-16 11:42:36 +01:00
Claire
757aed3290 Fix error decrementing status count when FeaturedTags#last_status_at is nil (#33320) 2025-01-16 11:42:36 +01:00
Claire
3cff7caffd Fix last paginated notification group only including data on a single notification (#33271) 2025-01-16 11:42:36 +01:00
Claire
533477e77c Fix processing of mentions for post edits with an existing corresponding silent mention (#33227) 2025-01-16 11:42:36 +01:00
Claire
afcfc64007 Fix deletion of unconfirmed users with Webauthn set (#33186) 2025-01-16 11:42:36 +01:00
Claire
734f0dd182 Fix fediverse:creator metadata not showing up in REST API (#33466) 2025-01-16 11:42:36 +01:00
Matt Jankowski
bcc798d6a7 Fix empty authors preview card serialization (#33151) 2025-01-16 11:42:36 +01:00
Claire
3a4242ce01 Merge commit from fork 2025-01-16 11:10:08 +01:00
Claire
23376cb691 Fix NameError in status update processing (#33161) 2024-12-04 08:41:21 +01:00
Claire
c2d65f7142 Merge commit '13ab4b54e2b9cb9ddfcbe9dd3d820a7ba9164412' into glitch-soc/stable-4.3 2024-12-03 15:17:19 +01:00
Claire
13ab4b54e2 Bump version to v4.3.2 (#33136) 2024-12-03 15:16:28 +01:00
Claire
df0b641914 [Glitch] Fix duplicate notifications in notification groups when using slow mode
Port 4bfb8887bf to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2024-12-02 21:58:22 +01:00
Claire
624b942c2e [Glitch] Redesign Content Warning and filters
Port 393f0a0159 to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2024-12-02 21:57:14 +01:00
Claire
cfa2e0503a [Glitch] Fix alt-text pop-in not using the translated description
Port 0a1b5df202 to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2024-12-02 21:57:00 +01:00
Renato "Lond" Cerqueira
de945eef63 [Glitch] Fix 'unknown' media attachment rendering in detailed view
Port 01e25af2e3 to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2024-12-02 21:56:45 +01:00
Claire
9a7030fb69 [Glitch] Fix preview cards with long titles erroneously causing layout changes
Port 742eb549ab to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2024-12-02 21:56:24 +01:00
Nathan Sparrow
221da1ba04 [Glitch] Embed modal mobile fix
Port de1d8dc63a

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2024-12-02 21:56:05 +01:00
David Roetzel
ff85540904 [Glitch] Do not change follow counters when already following
Port 029c99bd7b to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2024-12-02 21:55:41 +01:00
Emelia Smith
c2862049a2 [Glitch] Fix 'unknown' media attachment type rendering
Port 346cdb998c to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2024-12-02 21:55:26 +01:00
Claire
0d69cc068c Merge commit '26f25ef4bafd5ad84d03d8cb7ad0d868360175e5' into glitch-soc/stable-4.3
Conflicts:
- `app/javascript/styles/mastodon/components.scss`:
  Conflict caused by glitch-soc changing the path to images, and upstream
  removing styling using such an image.
  Removed the styling as upstream did.
- `app/models/trends/statuses.rb`:
  Upstream added a date restriction to trendable posts, while glitch-soc had
  slightly different conditions.
  Added the date restriction to glitch-soc's conditions.
2024-12-02 21:49:12 +01:00
github-actions[bot]
26f25ef4ba New Crowdin Translations for stable-4.3 (automated) (#33135)
Co-authored-by: GitHub Actions <noreply@github.com>
2024-12-02 16:53:28 +01:00
Claire
3b4070cfcc Prepare changelog 2024-12-02 16:20:32 +01:00
Claire
eb997c9f0e Fix processing incoming post edits with mentions to unresolvable accounts (#33129) 2024-12-02 16:20:32 +01:00
Yann
4239baa1f4 Remove constant definition from global scope in embed.js (#33107) 2024-12-02 16:20:32 +01:00
Claire
5532d1c2cb Add tootctl feeds vacuum (#33065) 2024-12-02 16:20:32 +01:00
Claire
3f0d90f019 Fix inactive users' timelines being backfilled on follow and unsuspend (#33094) 2024-12-02 16:20:32 +01:00
Claire
15e1a63e4a Fix direct inbox delivery pushing posts into inactive followers' timelines (#33067) 2024-12-02 16:20:32 +01:00
Claire
6b8ff1cf6e Fix TagFollow records not being correctly handled in account operations (#33063) 2024-12-02 16:20:32 +01:00
Eugen Rochko
6cbd217055 Fix pushing hashtag-followed posts to feeds of inactive users (#33018) 2024-12-02 16:20:32 +01:00
Claire
90c7c1bf7d Fix duplicate notifications in notification groups when using slow mode (#33014) 2024-12-02 16:20:32 +01:00
Claire
e06448e652 Fix posts made in the future being allowed to trend (#32996) 2024-12-02 16:20:32 +01:00
Claire
3752db3c9a Update dependency rexml 2024-12-02 16:20:32 +01:00
Claire
cc5c125cc7 Fix uploading higher-than-wide GIF profile picture with libvips enabled (#32911) 2024-12-02 16:20:32 +01:00
Claire
f65523c5b6 Fix domain attribution field having autocorrect and autocapitalize enabled (#32903) 2024-12-02 16:20:32 +01:00
Claire
5b6b23eeef Fix titles being escaped twice (#32889) 2024-12-02 16:20:32 +01:00
Claire
0cbf03efa7 Fix list creation limit check (#32869) 2024-12-02 16:20:32 +01:00
Matt Jankowski
90f2c7a1e9 Fix error in CLI EmailDomainBlocks when supplying --with-dns-records (#32863) 2024-12-02 16:20:32 +01:00
Matt Jankowski
f0d734cc6e Add DomainHelpers spec support module for DNS/MX stub (#32690) 2024-12-02 16:20:32 +01:00
Eugen Rochko
0720ef5f62 Fix min_id and max_id causing error in search API (#32857) 2024-12-02 16:20:32 +01:00
Claire
dc9a106d4c Avoid latest featured tag use on post removal unless necessary (#32787) 2024-12-02 16:20:32 +01:00
Claire
c634da32cf Redesign Content Warning and filters (#32543) 2024-12-02 16:20:32 +01:00
Claire
2d8ce9e19a Fix alt-text pop-in not using the translated description (#32766) 2024-12-02 16:20:32 +01:00
Renato "Lond" Cerqueira
1ddf1aedf1 Fix 'unknown' media attachment rendering in detailed view (#32713) 2024-12-02 16:20:32 +01:00
Claire
931870ca34 Fix preview cards with long titles erroneously causing layout changes (#32678) 2024-12-02 16:20:32 +01:00
Nathan Sparrow
7f9b0f36ba Embed modal mobile fix (#32641) 2024-12-02 16:20:32 +01:00
Hugo Gameiro
dd0992b25d Fix and improve batch attachment deletion handling when using OpenStack Swift (#32637) 2024-12-02 16:20:32 +01:00
Jeong Arm
9b677f099e Fix that blocking was not working on link timeline (#32625) 2024-12-02 16:20:32 +01:00
David Roetzel
c13b8026f0 Do not change follow counters when already following (#32622) 2024-12-02 16:20:32 +01:00
Emelia Smith
bf1375ae37 Fix 'unknown' media attachment type rendering (#32613) 2024-12-02 16:20:32 +01:00
Eugene Alvin Villar
b06161dba3 Fix tl language native name (#32606) 2024-12-02 16:20:32 +01:00
Matt Jankowski
a089109b77 Use async_count in more view locations (#32086) 2024-12-02 16:20:32 +01:00
Leni Kadali
74f9f7c600 Add error message when user tries to follow their own account (#31910) 2024-12-02 16:20:32 +01:00
Emelia Smith
ea1b598246 Add client_secret_expires_at to OAuth Applications (#30317) 2024-12-02 16:20:32 +01:00
Matt Jankowski
dbedd021f5 Move account suspension-related methods to concern (#28351) 2024-12-02 16:20:32 +01:00
Claire
5d79af928c Fix collapse icon opening the post (#2899) 2024-11-24 18:36:54 +01:00
Claire
a62be22cb1 Fix clicking on avatar/display opening status instead of profile (#2897)
Fix regression from #2895
2024-11-24 18:36:54 +01:00
Claire
96ffbc05c0 Fix status clickable area (#2895) 2024-11-24 18:36:54 +01:00
Claire
39fb314421 Merge pull request #2888 from ClearlyClaire/glitch-soc/backports-4.3
Backports upstream changes to glitch-soc (stable 4.3)
2024-10-21 11:13:51 +02:00
Claire
90f6984ff1 Merge tag 'v4.3.1' into glitch-soc/backports-4.3 2024-10-21 11:06:25 +02:00
Claire
9adb96f3a1 Bump version to v4.3.1 (#32582)
Co-authored-by: David Roetzel <david@roetzel.de>
2024-10-21 10:58:01 +02:00
Claire
d5a3478864 Merge pull request #2886 from ClearlyClaire/glitch-soc/backports-4.3
Merge upstream changes up to f7aab0cc2f (stable-4.3)
2024-10-19 19:14:05 +02:00
Claire
9877a053f6 [Glitch] Remove ability to get embed code for remote posts
Port de5f522cc0 to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2024-10-18 17:30:47 +02:00
hota
605ed50603 [Glitch] Fix column-settings spacing in local timeline in advanced view
Port 044dd3f788 to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2024-10-18 17:30:18 +02:00
Claire
e1609c6813 [Glitch] Add more explicit explanations about author attribution and fediverse:creator
Port 7388a6ce9a to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2024-10-18 17:27:57 +02:00
Renaud Chaput
de5d6e98ae [Glitch] Add ability to group follow notifications in WebUI
Port e507b4f884 to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2024-10-18 17:27:12 +02:00
Claire
2b0410f903 Merge commit 'f7aab0cc2ff47337021d50ed52428abcb7a9b518' into glitch-soc/backports-4.3
Conflicts:
- `app/helpers/application_helper.rb`:
  Upstream added a helper, while glitch-soc had extra helpers.
  Added upstream's helper.
2024-10-18 17:19:57 +02:00
Claire
f7aab0cc2f Update changelog 2024-10-18 15:49:26 +02:00
Claire
de5f522cc0 Remove ability to get embed code for remote posts (#32578) 2024-10-18 15:49:26 +02:00
Claire
d728fa9991 Fix follow recommendation moderation page default language when using regional variant (#32580) 2024-10-18 15:49:26 +02:00
hota
044dd3f788 Fix column-settings spacing in local timeline in advanced view (#32567) 2024-10-18 15:49:26 +02:00
Matt Jankowski
afc440435c Fix broken i18n in text welcome mailer tags area (#32571) 2024-10-18 15:49:26 +02:00
github-actions[bot]
d0fb7939bb New Crowdin Translations for stable-4.3 (automated) (#32576)
Co-authored-by: GitHub Actions <noreply@github.com>
2024-10-18 10:11:59 +02:00
Claire
7388a6ce9a Add more explicit explanations about author attribution and fediverse:creator (#32383) 2024-10-18 09:14:13 +02:00
Emelia Smith
cd2a3bac79 Fix missing or incorrect cache-control headers for Streaming server (#32551) 2024-10-18 09:14:13 +02:00
Matt Jankowski
f0e011fbc9 Fix trailing slash newline in changelog (#32545) 2024-10-18 09:14:13 +02:00
Matt Jankowski
acbc273d6e Update rails to version 7.1.4.1 (#32542) 2024-10-18 09:14:13 +02:00
Claire
1f0c84749d Change Active Record Encryption variable check to check for emptiness (#32537) 2024-10-18 09:14:13 +02:00
Renaud Chaput
e507b4f884 Add ability to group follow notifications in WebUI (#32520) 2024-10-18 09:14:13 +02:00
github-actions[bot]
93348136a5 New Crowdin Translations for stable-4.3 (automated) (#32555)
Co-authored-by: GitHub Actions <noreply@github.com>
2024-10-17 10:21:47 +02:00
Claire
3a5e83b91a Merge pull request #2885 from ClearlyClaire/glitch-soc/backports-4.3
Merge upstream changes (stable-4.3)
2024-10-16 19:56:45 +02:00
Claire
8d37565c19 [Glitch] Fix only the first paragraph being displayed in some notifications
Port 82dd6cd96ef42dc9fdf6f68398d46344ba0e9884 to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2024-10-16 12:45:06 +02:00
Renaud Chaput
177e8fe972 [Glitch] Add back a 6 hours mute duration option
Port d73b5e2ced6c50f2410fbd724394254c792172ad to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2024-10-16 12:44:43 +02:00
Claire
198283a188 Merge commit '36452845d78f6c3501af1e39391d06ab88a45a5a' into glitch-soc/backports-4.3
Conflicts:
- `.env.production.sample`:
  Upstream added a block of three environment variables, while
  glitch-soc has a different version of the file overall.
  Added upstream's changes.
2024-10-16 12:42:12 +02:00
Claire
36452845d7 Explicitly install ImageMagick in CI (except for libvips tests) (#32534) 2024-10-16 12:40:58 +02:00
Christian Winther
5c4bcd2f08 Run migration tests against postgres 16 and 17 as well (#32416) 2024-10-16 12:40:58 +02:00
Claire
a20f38c930 Fix only the first paragraph being displayed in some notifications (#32348) 2024-10-16 12:40:58 +02:00
Renaud Chaput
b01bd74698 Add back a 6 hours mute duration option (#32522) 2024-10-16 12:40:58 +02:00
Matt Jankowski
41e342a88f Convert admin/invites controller specs to system specs (#32450) 2024-10-16 12:40:58 +02:00
Matt Jankowski
9258ee8847 Improve app/policies coverage (#32426) 2024-10-16 12:40:58 +02:00
Matt Jankowski
6d72c13a4d Convert status embed controller to request spec (#32448) 2024-10-16 12:40:58 +02:00
Matt Jankowski
ad4be12473 Add mention of encryption secrets to production sample (#32512) 2024-10-16 12:40:58 +02:00
Matt Jankowski
527d1253bf Reduce factory creation (14 -> 8) in ActivityPub::Activity::Block spec (#32488) 2024-10-16 12:40:58 +02:00
Matt Jankowski
ae676edc2b Expand coverage for User#token_for_app (#32434) 2024-10-16 12:40:58 +02:00
Matt Jankowski
63df649fe5 Expand coverage for Block model (#32480) 2024-10-16 12:40:58 +02:00
Christian Schmidt
0ff427fab3 Translate to regional language variant (e.g. pt-BR) (#32428) 2024-10-16 12:40:58 +02:00
Matt Jankowski
dc2f9eef77 Reduce factories (36 > 12) in AccountReachFinder spec (#32482) 2024-10-16 12:40:58 +02:00
Matt Jankowski
ff1247ad16 Use context for repeated scenarios in AccountStatusCleanupPolicy spec (#32489) 2024-10-16 12:40:58 +02:00
Matt Jankowski
fbe55a4545 Reduce factory creation (73 -> 64) in PublicFeed spec (#32491) 2024-10-16 12:40:58 +02:00
Matt Jankowski
a72819660a Reduce factory creation (48 -> 8) in AP::Note serializer spec (#32492) 2024-10-16 12:40:58 +02:00
Matt Jankowski
c292ed07fe Expand coverage for Scheduler::IpCleanupScheduler worker (#32499) 2024-10-16 12:40:58 +02:00
Matt Jankowski
2d008108a4 Reduce factory creation (132 -> 40) in lib/vacuum/* specs (#32498) 2024-10-16 12:40:58 +02:00
Matt Jankowski
0c59ef44b1 Extend spec coverage for Poll model (#32500) 2024-10-16 12:40:58 +02:00
Jeong Arm
12297faa1d [Glitch] Fix reblog icons on account media view
Port 49b3d5692e to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2024-10-15 17:49:40 +02:00
Claire
9b6f92e47f [Glitch] Fix follow recommendation carrousel scrolling on RTL layouts, for real
Port 70472de726 to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2024-10-15 17:49:40 +02:00
Renaud Chaput
8b6247ca44 [Glitch] Fix back arrow pointing to the incorrect direction in RTL languages
Port ca68a3cacb

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2024-10-15 17:49:40 +02:00
Claire
836cbca469 [Glitch] Fix follow recommendation carrousel scrolling on RTL layouts
Port a2e24ee2de to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2024-10-15 17:49:20 +02:00
Claire
b091e531a5 Merge commit '49b3d5692e6f217e6506674ad8a623a4ba8d0c5f' into glitch-soc/backports-4.3 2024-10-15 17:42:22 +02:00
Jeong Arm
49b3d5692e Fix reblog icons on account media view (#32506) 2024-10-15 17:37:14 +02:00
Claire
70472de726 Fix follow recommendation carrousel scrolling on RTL layouts, for real (#32505) 2024-10-15 17:37:14 +02:00
kenkiku1021
304e440f88 add SWIFT object storage uri to CSP media hosts (#32439)
Co-authored-by: Claire <claire.github-309c@sitedethib.com>
2024-10-15 17:37:14 +02:00
Renaud Chaput
ca68a3cacb Fix back arrow pointing to the incorrect direction in RTL languages (#32485) 2024-10-15 17:37:14 +02:00
Emelia Smith
066efc2d3f Fix: Use consistent REDIS_USER environment variable in streaming (#32493) 2024-10-15 17:37:14 +02:00
Claire
a2e24ee2de Fix follow recommendation carrousel scrolling on RTL layouts (#32462) 2024-10-15 11:57:59 +02:00
Claire
ee61f7772a Add further warnings about encryption secrets (#32476) 2024-10-15 11:57:59 +02:00
Matt Jankowski
5ee72f0e2d Convert admin/tags controller specs to system specs (#32447) 2024-10-15 11:57:59 +02:00
Claire
192e9d16eb Fix follow recommendation suppressions not applying immediately (#32392) 2024-10-15 11:57:59 +02:00
Claire
a3f40309fb Merge pull request #2883 from ClearlyClaire/glitch-soc/backports-4.3
Port changes from upstream to stable-4.3
2024-10-14 21:47:27 +02:00
Claire
782a785893 [Glitch] Fix mute duration not being shown in list of muted accounts in web UI
Port a295832960 to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2024-10-14 19:24:11 +02:00
Claire
9f165436d2 [Glitch] Fix “Mark every notification as read” not updating the read marker if scrolled down
Port e018e6321f to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2024-10-14 19:23:38 +02:00
Claire
592945e498 [Glitch] Fix “Mention” appearing for otherwise filtered posts
Port f75eb1a8b0 to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2024-10-14 19:23:18 +02:00
Michael Stanclift
bfb610922d [Glitch] Restore list column border
Port de4f7859b4 to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2024-10-14 19:22:54 +02:00
Claire
480dcecc11 [Glitch] Fix list edition modal styling
Port 45a520603b to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2024-10-14 19:22:35 +02:00
Matt Jankowski
2647606a15 [Glitch] Bring icon vertical middle to applications list style
Port fa4a82326d to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2024-10-14 19:22:02 +02:00
Claire
b40adb4a89 Merge commit 'f99da81ef8b14a851347503d4177f83322c16d9a' into glitch-soc/stable-4.3 2024-10-14 19:18:08 +02:00
Claire
f99da81ef8 Add tag with commit hash to PR image builds (#32418) 2024-10-14 13:14:38 +02:00
Claire
799f507dce Fix language of push notifications (#32415) 2024-10-14 13:14:38 +02:00
Claire
81472396bc Add note about not changing ActiveRecord encryption secrets once they are set (#32413) 2024-10-14 13:14:38 +02:00
Claire
a295832960 Fix mute duration not being shown in list of muted accounts in web UI (#32388) 2024-10-14 13:14:38 +02:00
Claire
e018e6321f Fix “Mark every notification as read” not updating the read marker if scrolled down (#32385) 2024-10-14 13:14:38 +02:00
Claire
f75eb1a8b0 Fix “Mention” appearing for otherwise filtered posts (#32356) 2024-10-14 13:14:38 +02:00
Michael Stanclift
de4f7859b4 Restore list column border (#32367) 2024-10-14 13:14:38 +02:00
Claire
e5e0144957 Fix notification requests from suspended accounts still being listed (#32354) 2024-10-14 13:14:38 +02:00
Claire
45a520603b Fix list edition modal styling (#32358) 2024-10-14 13:14:38 +02:00
Claire
6ac78ead52 Fix 4 columns barely not fitting on 1920px screen (#32361) 2024-10-14 13:14:38 +02:00
Claire
c0d3b3de10 Fix latest tag for 4.3 docker image builds (#32350) 2024-10-14 13:14:38 +02:00
Matt Jankowski
9e04e46521 Reference IpBlock.severities keys from CLI option check (#32291) 2024-10-14 13:14:38 +02:00
Matt Jankowski
fa4a82326d Bring icon vertical middle to applications list style (#32293) 2024-10-14 13:14:38 +02:00
Claire
93fa102f9a Fix setting to hide the quick filter bar (#2882)
Fixes #2881
2024-10-11 17:45:31 +02:00
Claire
9ee86a738e Fix the favicon notification badge not using the correct notification count (#2880)
Fixes #2879
2024-10-10 19:35:55 +02:00
593 changed files with 12991 additions and 6423 deletions

View File

@@ -73,6 +73,16 @@ DB_PORT=5432
SECRET_KEY_BASE=
OTP_SECRET=
# Encryption secrets
# ------------------
# Must be available (and set to same values) for all server processes
# These are private/secret values, do not share outside hosting environment
# Use `bin/rails db:encryption:init` to generate fresh secrets
# Do NOT change these secrets once in use, as this would cause data loss and other issues
# ------------------
# ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY=
# ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT=
# ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY=
# Web Push
# --------

View File

@@ -21,3 +21,4 @@ runs:
with:
ruby-version: ${{ inputs.ruby-version }}
bundler-cache: true
cache-version: 4.3

View File

@@ -1,14 +1,9 @@
on:
workflow_call:
inputs:
platforms:
required: true
type: string
cache:
type: boolean
default: true
use_native_arm64_builder:
type: boolean
push_to_images:
type: string
version_prerelease:
@@ -24,42 +19,36 @@ on:
file_to_build:
type: string
# This builds multiple images with one runner each, allowing us to build for multiple architectures
# using Github's runners.
# The two-step process is adapted form:
# https://docs.docker.com/build/ci/github-actions/multi-platform/#distribute-build-across-multiple-runners
jobs:
# Build each (amd64 and arm64) image separately
build-image:
runs-on: ubuntu-latest
runs-on: ${{ startsWith(matrix.platform, 'linux/arm') && 'ubuntu-24.04-arm' || 'ubuntu-24.04' }}
strategy:
fail-fast: false
matrix:
platform:
- linux/amd64
- linux/arm64
steps:
- uses: actions/checkout@v4
- uses: docker/setup-qemu-action@v3
if: contains(inputs.platforms, 'linux/arm64') && !inputs.use_native_arm64_builder
- name: Prepare
env:
PUSH_TO_IMAGES: ${{ inputs.push_to_images }}
run: |
platform=${{ matrix.platform }}
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
# Transform multi-line variable into comma-separated variable
image_names=${PUSH_TO_IMAGES//$'\n'/,}
echo "IMAGE_NAMES=${image_names%,}" >> $GITHUB_ENV
- uses: docker/setup-buildx-action@v3
id: buildx
if: ${{ !(inputs.use_native_arm64_builder && contains(inputs.platforms, 'linux/arm64')) }}
- name: Start a local Docker Builder
if: inputs.use_native_arm64_builder && contains(inputs.platforms, 'linux/arm64')
run: |
docker run --rm -d --name buildkitd -p 1234:1234 --privileged moby/buildkit:latest --addr tcp://0.0.0.0:1234
- uses: docker/setup-buildx-action@v3
id: buildx-native
if: inputs.use_native_arm64_builder && contains(inputs.platforms, 'linux/arm64')
with:
driver: remote
endpoint: tcp://localhost:1234
platforms: linux/amd64
append: |
- endpoint: tcp://${{ vars.DOCKER_BUILDER_HETZNER_ARM64_01_HOST }}:13865
platforms: linux/arm64
name: mastodon-docker-builder-arm64-01
driver-opts:
- servername=mastodon-docker-builder-arm64-01
env:
BUILDER_NODE_1_AUTH_TLS_CACERT: ${{ secrets.DOCKER_BUILDER_HETZNER_ARM64_01_CACERT }}
BUILDER_NODE_1_AUTH_TLS_CERT: ${{ secrets.DOCKER_BUILDER_HETZNER_ARM64_01_CERT }}
BUILDER_NODE_1_AUTH_TLS_KEY: ${{ secrets.DOCKER_BUILDER_HETZNER_ARM64_01_KEY }}
- name: Log in to Docker Hub
if: contains(inputs.push_to_images, 'tootsuite')
@@ -76,8 +65,91 @@ jobs:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/metadata-action@v5
- name: Docker meta
id: meta
uses: docker/metadata-action@v5
if: ${{ inputs.push_to_images != '' }}
with:
images: ${{ inputs.push_to_images }}
flavor: ${{ inputs.flavor }}
labels: ${{ inputs.labels }}
- name: Build and push by digest
id: build
uses: docker/build-push-action@v6
with:
context: .
file: ${{ inputs.file_to_build }}
build-args: |
MASTODON_VERSION_PRERELEASE=${{ inputs.version_prerelease }}
MASTODON_VERSION_METADATA=${{ inputs.version_metadata }}
SOURCE_COMMIT=${{ github.sha }}
platforms: ${{ matrix.platform }}
provenance: false
push: ${{ inputs.push_to_images != '' }}
cache-from: ${{ inputs.cache && 'type=gha' || '' }}
cache-to: ${{ inputs.cache && 'type=gha,mode=max' || '' }}
outputs: type=image,"name=${{ env.IMAGE_NAMES }}",push-by-digest=true,name-canonical=true,push=${{ inputs.push_to_images != '' }}
- name: Export digest
if: ${{ inputs.push_to_images != '' }}
run: |
mkdir -p "${{ runner.temp }}/digests"
digest="${{ steps.build.outputs.digest }}"
touch "${{ runner.temp }}/digests/${digest#sha256:}"
- name: Upload digest
if: ${{ inputs.push_to_images != '' }}
uses: actions/upload-artifact@v4
with:
# `hashFiles` is used to disambiguate between streaming and non-streaming images
name: digests-${{ hashFiles(inputs.file_to_build) }}-${{ env.PLATFORM_PAIR }}
path: ${{ runner.temp }}/digests/*
if-no-files-found: error
retention-days: 1
# Then merge the docker images into a single one
merge-images:
if: ${{ inputs.push_to_images != '' }}
runs-on: ubuntu-24.04
needs:
- build-image
env:
PUSH_TO_IMAGES: ${{ inputs.push_to_images }}
steps:
- uses: actions/checkout@v4
- name: Download digests
uses: actions/download-artifact@v4
with:
path: ${{ runner.temp }}/digests
# `hashFiles` is used to disambiguate between streaming and non-streaming images
pattern: digests-${{ hashFiles(inputs.file_to_build) }}-*
merge-multiple: true
- name: Log in to Docker Hub
if: contains(inputs.push_to_images, 'tootsuite')
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Log in to the GitHub Container registry
if: contains(inputs.push_to_images, 'ghcr.io')
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Docker meta
id: meta
uses: docker/metadata-action@v5
if: ${{ inputs.push_to_images != '' }}
with:
images: ${{ inputs.push_to_images }}
@@ -85,18 +157,14 @@ jobs:
tags: ${{ inputs.tags }}
labels: ${{ inputs.labels }}
- uses: docker/build-push-action@v6
with:
context: .
file: ${{ inputs.file_to_build }}
build-args: |
MASTODON_VERSION_PRERELEASE=${{ inputs.version_prerelease }}
MASTODON_VERSION_METADATA=${{ inputs.version_metadata }}
platforms: ${{ inputs.platforms }}
provenance: false
builder: ${{ steps.buildx.outputs.name || steps.buildx-native.outputs.name }}
push: ${{ inputs.push_to_images != '' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: ${{ inputs.cache && 'type=gha' || '' }}
cache-to: ${{ inputs.cache && 'type=gha,mode=max' || '' }}
- name: Create manifest list and push
working-directory: ${{ runner.temp }}/digests
run: |
echo "$PUSH_TO_IMAGES" | xargs -I{} \
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
$(printf '{}@sha256:%s ' *)
- name: Inspect image
run: |
echo "$PUSH_TO_IMAGES" | xargs -i{} \
docker buildx imagetools inspect {}:${{ steps.meta.outputs.version }}

View File

@@ -26,8 +26,6 @@ jobs:
uses: ./.github/workflows/build-container-image.yml
with:
file_to_build: Dockerfile
platforms: linux/amd64,linux/arm64
use_native_arm64_builder: false
cache: false
push_to_images: |
ghcr.io/${{ github.repository_owner }}/mastodon
@@ -47,8 +45,6 @@ jobs:
uses: ./.github/workflows/build-container-image.yml
with:
file_to_build: streaming/Dockerfile
platforms: linux/amd64,linux/arm64
use_native_arm64_builder: false
cache: false
push_to_images: |
ghcr.io/${{ github.repository_owner }}/mastodon-streaming

View File

@@ -21,17 +21,17 @@ jobs:
uses: actions/checkout@v4
- id: version_vars
run: |
echo mastodon_version_metadata=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 ${{github.event.pull_request.head.sha}}) >> $GITHUB_OUTPUT
echo mastodon_short_sha=$(git rev-parse --short ${{github.event.pull_request.head.sha}}) >> $GITHUB_OUTPUT
outputs:
metadata: ${{ steps.version_vars.outputs.mastodon_version_metadata }}
short_sha: ${{ steps.version_vars.outputs.mastodon_short_sha }}
build-image:
needs: compute-suffix
uses: ./.github/workflows/build-container-image.yml
with:
file_to_build: Dockerfile
platforms: linux/amd64,linux/arm64
use_native_arm64_builder: false
push_to_images: |
ghcr.io/${{ github.repository_owner }}/mastodon
version_metadata: ${{ needs.compute-suffix.outputs.metadata }}
@@ -39,6 +39,7 @@ jobs:
latest=auto
tags: |
type=ref,event=pr
type=ref,event=pr,suffix=-${{ needs.compute-suffix.outputs.short_sha }}
secrets: inherit
build-image-streaming:
@@ -46,8 +47,6 @@ jobs:
uses: ./.github/workflows/build-container-image.yml
with:
file_to_build: streaming/Dockerfile
platforms: linux/amd64,linux/arm64
use_native_arm64_builder: false
push_to_images: |
ghcr.io/${{ github.repository_owner }}/mastodon-streaming
version_metadata: ${{ needs.compute-suffix.outputs.metadata }}
@@ -55,4 +54,5 @@ jobs:
latest=auto
tags: |
type=ref,event=pr
type=ref,event=pr,suffix=-${{ needs.compute-suffix.outputs.short_sha }}
secrets: inherit

View File

@@ -13,8 +13,6 @@ jobs:
uses: ./.github/workflows/build-container-image.yml
with:
file_to_build: Dockerfile
platforms: linux/amd64,linux/arm64
use_native_arm64_builder: false
push_to_images: |
ghcr.io/${{ github.repository_owner }}/mastodon
# Do not use cache when building releases, so apt update is always ran and the release always contain the latest packages
@@ -22,7 +20,7 @@ jobs:
# Only tag with latest when ran against the latest stable branch
# This needs to be updated after each minor version release
flavor: |
latest=${{ startsWith(github.ref, 'refs/tags/v4.2.') }}
latest=${{ startsWith(github.ref, 'refs/tags/v4.3.') }}
tags: |
type=pep440,pattern={{raw}}
type=pep440,pattern=v{{major}}.{{minor}}
@@ -33,8 +31,6 @@ jobs:
uses: ./.github/workflows/build-container-image.yml
with:
file_to_build: streaming/Dockerfile
platforms: linux/amd64,linux/arm64
use_native_arm64_builder: false
push_to_images: |
ghcr.io/${{ github.repository_owner }}/mastodon-streaming
# Do not use cache when building releases, so apt update is always ran and the release always contain the latest packages

View File

@@ -23,8 +23,6 @@ jobs:
uses: ./.github/workflows/build-container-image.yml
with:
file_to_build: Dockerfile
platforms: linux/amd64,linux/arm64
use_native_arm64_builder: false
cache: false
push_to_images: |
ghcr.io/${{ github.repository_owner }}/mastodon
@@ -44,8 +42,6 @@ jobs:
uses: ./.github/workflows/build-container-image.yml
with:
file_to_build: streaming/Dockerfile
platforms: linux/amd64,linux/arm64
use_native_arm64_builder: false
cache: false
push_to_images: |
ghcr.io/${{ github.repository_owner }}/mastodon-streaming

View File

@@ -20,7 +20,6 @@ jobs:
uses: ./.github/workflows/build-container-image.yml
with:
file_to_build: Dockerfile
platforms: linux/amd64 # Testing only on native platform so it is performant
cache: true
build-image-streaming:
@@ -31,5 +30,4 @@ jobs:
uses: ./.github/workflows/build-container-image.yml
with:
file_to_build: streaming/Dockerfile
platforms: linux/amd64 # Testing only on native platform so it is performant
cache: true

View File

@@ -32,6 +32,8 @@ jobs:
postgres:
- 14-alpine
- 15-alpine
- 16-alpine
- 17-alpine
services:
postgres:

View File

@@ -127,6 +127,7 @@ jobs:
- '3.1'
- '3.2'
- '.ruby-version'
- '3.4'
steps:
- uses: actions/checkout@v4
@@ -143,7 +144,7 @@ jobs:
uses: ./.github/actions/setup-ruby
with:
ruby-version: ${{ matrix.ruby-version}}
additional-system-dependencies: ffmpeg libpam-dev
additional-system-dependencies: ffmpeg imagemagick libpam-dev
- name: Load database schema
run: |
@@ -229,6 +230,7 @@ jobs:
- '3.1'
- '3.2'
- '.ruby-version'
- '3.4'
steps:
- uses: actions/checkout@v4
@@ -245,7 +247,7 @@ jobs:
uses: ./.github/actions/setup-ruby
with:
ruby-version: ${{ matrix.ruby-version}}
additional-system-dependencies: ffmpeg libpam-dev libyaml-dev
additional-system-dependencies: ffmpeg libpam-dev
- name: Load database schema
run: './bin/rails db:create db:schema:load db:seed'
@@ -308,6 +310,7 @@ jobs:
- '3.1'
- '3.2'
- '.ruby-version'
- '3.4'
steps:
- uses: actions/checkout@v4
@@ -325,7 +328,7 @@ jobs:
uses: ./.github/actions/setup-ruby
with:
ruby-version: ${{ matrix.ruby-version}}
additional-system-dependencies: ffmpeg
additional-system-dependencies: ffmpeg imagemagick
- name: Set up Javascript environment
uses: ./.github/actions/setup-javascript
@@ -425,6 +428,7 @@ jobs:
- '3.1'
- '3.2'
- '.ruby-version'
- '3.4'
search-image:
- docker.elastic.co/elasticsearch/elasticsearch:7.17.13
include:
@@ -445,7 +449,7 @@ jobs:
uses: ./.github/actions/setup-ruby
with:
ruby-version: ${{ matrix.ruby-version}}
additional-system-dependencies: ffmpeg
additional-system-dependencies: ffmpeg imagemagick
- name: Set up Javascript environment
uses: ./.github/actions/setup-javascript

View File

@@ -2,6 +2,215 @@
All notable changes to this project will be documented in this file.
## [4.3.8] - 2025-05-06
### Security
- Update dependencies
- Check scheme on account, profile, and media URLs ([GHSA-x2rc-v5wx-g3m5](https://github.com/mastodon/mastodon/security/advisories/GHSA-x2rc-v5wx-g3m5))
### Added
- Add warning for REDIS_NAMESPACE deprecation at startup (#34581 by @ClearlyClaire)
- Add built-in context for interaction policies (#34574 by @ClearlyClaire)
### Changed
- Change activity distribution error handling to skip retrying for deleted accounts (#33617 by @ClearlyClaire)
### Removed
- Remove double-query for signed query strings (#34610 by @ClearlyClaire)
### Fixed
- Fix incorrect redirect in response to unauthenticated API requests in limited federation mode (#34549 by @ClearlyClaire)
- Fix sign-up e-mail confirmation page reloading on error or redirect (#34548 by @ClearlyClaire)
## [4.3.7] - 2025-04-02
### Added
- Add delay to profile updates to debounce them (#34137 by @ClearlyClaire)
- Add support for paginating partial collections in `SynchronizeFollowersService` (#34272 and #34277 by @ClearlyClaire)
### Changed
- Change account suspensions to be federated to recently-followed accounts as well (#34294 by @ClearlyClaire)
- Change `AccountReachFinder` to consider statuses based on suspension date (#32805 and #34291 by @ClearlyClaire and @mjankowski)
- Change user archive signed URL TTL from 10 seconds to 1 hour (#34254 by @ClearlyClaire)
### Fixed
- Fix static version of animated PNG emojis not being properly extracted (#34337 by @ClearlyClaire)
- Fix filters not applying in detailed view, favourites and bookmarks (#34259 and #34260 by @ClearlyClaire)
- Fix handling of malformed/unusual HTML (#34201 by @ClearlyClaire)
- Fix `CacheBuster` being queued for missing media attachments (#34253 by @ClearlyClaire)
- Fix incorrect URL being used when cache busting (#34189 by @ClearlyClaire)
- Fix streaming server refusing unix socket path in `DATABASE_URL` (#34091 by @ClearlyClaire)
- Fix “x” hotkey not working on boosted filtered posts (#33758 by @ClearlyClaire)
## [4.3.6] - 2025-03-13
### Security
- Update dependency `omniauth-saml`
- Update dependency `rack`
### Fixed
- Fix Stoplight errors when using `REDIS_NAMESPACE` (#34126 by @ClearlyClaire)
## [4.3.5] - 2025-03-10
### Changed
- Change hashtag suggestion to prefer personal history capitalization (#34070 by @ClearlyClaire)
### Fixed
- Fix processing errors for some HEIF images from iOS 18 (#34086 by @renchap)
- Fix streaming server not filtering unknown-language posts from public timelines (#33774 by @ClearlyClaire)
- Fix preview cards under Content Warnings not being shown in detailed statuses (#34068 by @ClearlyClaire)
- Fix username and display name being hidden on narrow screens in moderation interface (#33064 by @ClearlyClaire)
## [4.3.4] - 2025-02-27
### Security
- Update dependencies
- Change HTML sanitization to remove unusable and unused `embed` tag (#34021 by @ClearlyClaire, [GHSA-mq2m-hr29-8gqf](https://github.com/mastodon/mastodon/security/advisories/GHSA-mq2m-hr29-8gqf))
- Fix rate-limit on sign-up email verification ([GHSA-v39f-c9jj-8w7h](https://github.com/mastodon/mastodon/security/advisories/GHSA-v39f-c9jj-8w7h))
- Fix improper disclosure of domain blocks to unverified users ([GHSA-94h4-fj37-c825](https://github.com/mastodon/mastodon/security/advisories/GHSA-94h4-fj37-c825))
### Changed
- Change preview cards to be shown when Content Warnings are expanded (#33827 by @ClearlyClaire)
- Change warnings against changing encryption secrets to be even more noticeable (#33631 by @ClearlyClaire)
- Change `mastodon:setup` to prevent overwriting already-configured servers (#33603, #33616, and #33684 by @ClearlyClaire and @mjankowski)
- Change notifications from moderators to not be filtered (#32974 and #33654 by @ClearlyClaire and @mjankowski)
### Fixed
- Fix `GET /api/v2/notifications/:id` and `POST /api/v2/notifications/:id/dismiss` for ungrouped notifications (#33990 by @ClearlyClaire)
- Fix issue with some versions of libvips on some systems (#33853 by @kleisauke)
- Fix handling of duplicate mentions in incoming status `Update` (#33911 by @ClearlyClaire)
- Fix inefficiencies in timeline generation (#33839 and #33842 by @ClearlyClaire)
- Fix emoji rewrite adding unnecessary curft to the DOM for most emoji (#33818 by @ClearlyClaire)
- Fix `tootctl feeds build` not building list timelines (#33783 by @ClearlyClaire)
- Fix flaky test in `/api/v2/notifications` tests (#33773 by @ClearlyClaire)
- Fix incorrect signature after HTTP redirect (#33757 and #33769 by @ClearlyClaire)
- Fix polls not being validated on edition (#33755 by @ClearlyClaire)
- Fix media preview height in compose form when 3 or more images are attached (#33571 by @ClearlyClaire)
- Fix preview card sizing in “Author attribution” in profile settings (#33482 by @ClearlyClaire)
- Fix processing of incoming notifications for unfilterable types (#33429 by @ClearlyClaire)
- Fix featured tags for remote accounts not being kept up to date (#33372, #33406, and #33425 by @ClearlyClaire and @mjankowski)
- Fix notification polling showing a loading bar in web UI (#32960 by @Gargron)
- Fix accounts table long display name (#29316 by @WebCoder49)
- Fix exclusive lists interfering with notifications (#28162 by @ShadowJonathan)
## [4.3.3] - 2025-01-16
### Security
- Fix insufficient validation of account URIs ([GHSA-5wxh-3p65-r4g6](https://github.com/mastodon/mastodon/security/advisories/GHSA-5wxh-3p65-r4g6))
- Update dependencies
### Fixed
- Fix `libyaml` missing from `Dockerfile` build stage (#33591 by @vmstan)
- Fix incorrect notification settings migration for non-followers (#33348 by @ClearlyClaire)
- Fix down clause for notification policy v2 migrations (#33340 by @jesseplusplus)
- Fix error decrementing status count when `FeaturedTags#last_status_at` is `nil` (#33320 by @ClearlyClaire)
- Fix last paginated notification group only including data on a single notification (#33271 by @ClearlyClaire)
- Fix processing of mentions for post edits with an existing corresponding silent mention (#33227 by @ClearlyClaire)
- Fix deletion of unconfirmed users with Webauthn set (#33186 by @ClearlyClaire)
- Fix empty authors preview card serialization (#33151, #33466 by @mjankowski and @ClearlyClaire)
## [4.3.2] - 2024-12-03
### Added
- Add `tootctl feeds vacuum` (#33065 by @ClearlyClaire)
- Add error message when user tries to follow their own account (#31910 by @lenikadali)
- Add client_secret_expires_at to OAuth Applications (#30317 by @ThisIsMissEm)
### Changed
- Change design of Content Warnings and filters (#32543 by @ClearlyClaire)
### Fixed
- Fix processing incoming post edits with mentions to unresolvable accounts (#33129 by @ClearlyClaire)
- Fix error when including multiple instances of `embed.js` (#33107 by @YKWeyer)
- Fix inactive users' timelines being backfilled on follow and unsuspend (#33094 by @ClearlyClaire)
- Fix direct inbox delivery pushing posts into inactive followers' timelines (#33067 by @ClearlyClaire)
- Fix `TagFollow` records not being correctly handled in account operations (#33063 by @ClearlyClaire)
- Fix pushing hashtag-followed posts to feeds of inactive users (#33018 by @Gargron)
- Fix duplicate notifications in notification groups when using slow mode (#33014 by @ClearlyClaire)
- Fix posts made in the future being allowed to trend (#32996 by @ClearlyClaire)
- Fix uploading higher-than-wide GIF profile picture with libvips enabled (#32911 by @ClearlyClaire)
- Fix domain attribution field having autocorrect and autocapitalize enabled (#32903 by @ClearlyClaire)
- Fix titles being escaped twice (#32889 by @ClearlyClaire)
- Fix list creation limit check (#32869 by @ClearlyClaire)
- Fix error in `tootctl email_domain_blocks` when supplying `--with-dns-records` (#32863 by @mjankowski)
- Fix `min_id` and `max_id` causing error in search API (#32857 by @Gargron)
- Fix inefficiencies when processing removal of posts that use featured tags (#32787 by @ClearlyClaire)
- Fix alt-text pop-in not using the translated description (#32766 by @ClearlyClaire)
- Fix preview cards with long titles erroneously causing layout changes (#32678 by @ClearlyClaire)
- Fix embed modal layout on mobile (#32641 by @DismalShadowX)
- Fix and improve batch attachment deletion handling when using OpenStack Swift (#32637 by @hugogameiro)
- Fix blocks not being applied on link timeline (#32625 by @tribela)
- Fix follow counters being incorrectly changed (#32622 by @oneiros)
- Fix 'unknown' media attachment type rendering (#32613 and #32713 by @ThisIsMissEm and @renatolond)
- Fix tl language native name (#32606 by @seav)
### Security
- Update dependencies
## [4.3.1] - 2024-10-21
### Added
- Add more explicit explanations about author attribution and `fediverse:creator` (#32383 by @ClearlyClaire)
- Add ability to group follow notifications in WebUI, can be disabled in the column settings (#32520 by @renchap)
- Add back a 6 hours mute duration option (#32522 by @renchap)
- Add note about not changing ActiveRecord encryption secrets once they are set (#32413, #32476, #32512, and #32537 by @ClearlyClaire and @mjankowski)
### Changed
- Change translation feature to translate to selected regional variant (e.g. pt-BR) if available (#32428 by @c960657)
### Removed
- Remove ability to get embed code for remote posts (#32578 by @ClearlyClaire)\
Getting the embed code is only reliable for local posts.\
It never worked for non-Mastodon servers, and stopped working correctly with the changes made in 4.3.0.\
We have therefore decided to remove the menu entry while we investigate solutions.
### Fixed
- Fix follow recommendation moderation page default language when using regional variant (#32580 by @ClearlyClaire)
- Fix column-settings spacing in local timeline in advanced view (#32567 by @lindwurm)
- Fix broken i18n in text welcome mailer tags area (#32571 by @mjankowski)
- Fix missing or incorrect cache-control headers for Streaming server (#32551 by @ThisIsMissEm)
- Fix only the first paragraph being displayed in some notifications (#32348 by @ClearlyClaire)
- Fix reblog icons on account media view (#32506 by @tribela)
- Fix Content-Security-Policy not allowing OpenStack SWIFT object storage URI (#32439 by @kenkiku1021)
- Fix back arrow pointing to the incorrect direction in RTL languages (#32485 by @renchap)
- Fix streaming server using `REDIS_USERNAME` instead of `REDIS_USER` (#32493 by @ThisIsMissEm)
- Fix follow recommendation carrousel scrolling on RTL layouts (#32462 and #32505 by @ClearlyClaire)
- Fix follow recommendation suppressions not applying immediately (#32392 by @ClearlyClaire)
- Fix language of push notifications (#32415 by @ClearlyClaire)
- Fix mute duration not being shown in list of muted accounts in web UI (#32388 by @ClearlyClaire)
- Fix “Mark every notification as read” not updating the read marker if scrolled down (#32385 by @ClearlyClaire)
- Fix “Mention” appearing for otherwise filtered posts (#32356 by @ClearlyClaire)
- Fix notification requests from suspended accounts still being listed (#32354 by @ClearlyClaire)
- Fix list edition modal styling (#32358 and #32367 by @ClearlyClaire and @vmstan)
- Fix 4 columns barely not fitting on 1920px screen (#32361 by @ClearlyClaire)
- Fix icon alignment in applications list (#32293 by @mjankowski)
## [4.3.0] - 2024-10-08
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 @mjankowski.
@@ -51,7 +260,7 @@ The following changelog entries focus on changes visible to users, administrator
- **Add notifications of severed relationships** (#27511, #29665, #29668, #29670, #29700, #29714, #29712, and #29731 by @ClearlyClaire and @Gargron)\
Notify local users when they lose relationships as a result of a local moderator blocking a remote account or server, allowing the affected user to retrieve the list of broken relationships.\
Note that this does not notify remote users.\
This adds the `severed_relationships` notification type to the REST API and streaming, with a new [`relationship_severance_event` attribute](https://docs.joinmastodon.org/entities/Notification/#relationship_severance_event).
This adds the `severed_relationships` notification type to the REST API and streaming, with a new [`event` attribute](https://docs.joinmastodon.org/entities/Notification/#relationship_severance_event).
- **Add hover cards in web UI** (#30754, #30864, #30850, #30879, #30928, #30949, #30948, #30931, and #31300 by @ClearlyClaire, @Gargron, and @renchap)\
Hovering over an avatar or username will now display a hover card with the first two lines of the user's description and their first two profile fields.\
This can be disabled in the “Animations and accessibility” section of the preferences.
@@ -67,7 +276,7 @@ The following changelog entries focus on changes visible to users, administrator
```html
<meta name="fediverse:creator" content="username@domain" />
```
On the API side, this is represented by a new `authors` attribute to the `PreviewCard` entity: https://docs.joinmastodon.org/entities/PreviewCard/#authors\
On the API side, this is represented by a new `authors` attribute to the `PreviewCard` entity: https://docs.joinmastodon.org/entities/PreviewCard/#authors \
Users can allow arbitrary domains to use `fediverse:creator` to credit them by visiting `/settings/verification`.\
This is federated as a new `attributionDomains` property in the `http://joinmastodon.org/ns` namespace, containing an array of domain names: https://docs.joinmastodon.org/spec/activitypub/#properties-used-1
- **Add in-app notifications for moderation actions and warnings** (#30065, #30082, and #30081 by @ClearlyClaire)\

View File

@@ -92,6 +92,9 @@ RUN \
# Set /opt/mastodon as working directory
WORKDIR /opt/mastodon
# Add backport repository for some specific packages where we need the latest version
RUN echo 'deb http://deb.debian.org/debian bookworm-backports main' >> /etc/apt/sources.list
# hadolint ignore=DL3008,DL3005
RUN \
# Mount Apt cache and lib directories from Docker buildx caches
@@ -150,6 +153,7 @@ RUN \
libpq-dev \
libssl-dev \
libtool \
libyaml-dev \
meson \
nasm \
pkg-config \
@@ -160,7 +164,7 @@ RUN \
libexif-dev \
libexpat1-dev \
libgirepository1.0-dev \
libheif-dev \
libheif-dev/bookworm-backports \
libimagequant-dev \
libjpeg62-turbo-dev \
liblcms2-dev \
@@ -343,7 +347,7 @@ RUN \
# libvips components
libcgif0 \
libexif12 \
libheif1 \
libheif1/bookworm-backports \
libimagequant0 \
libjpeg62-turbo \
liblcms2-2 \

View File

@@ -10,35 +10,35 @@ GIT
GEM
remote: https://rubygems.org/
specs:
actioncable (7.1.4)
actionpack (= 7.1.4)
activesupport (= 7.1.4)
actioncable (7.1.5.1)
actionpack (= 7.1.5.1)
activesupport (= 7.1.5.1)
nio4r (~> 2.0)
websocket-driver (>= 0.6.1)
zeitwerk (~> 2.6)
actionmailbox (7.1.4)
actionpack (= 7.1.4)
activejob (= 7.1.4)
activerecord (= 7.1.4)
activestorage (= 7.1.4)
activesupport (= 7.1.4)
actionmailbox (7.1.5.1)
actionpack (= 7.1.5.1)
activejob (= 7.1.5.1)
activerecord (= 7.1.5.1)
activestorage (= 7.1.5.1)
activesupport (= 7.1.5.1)
mail (>= 2.7.1)
net-imap
net-pop
net-smtp
actionmailer (7.1.4)
actionpack (= 7.1.4)
actionview (= 7.1.4)
activejob (= 7.1.4)
activesupport (= 7.1.4)
actionmailer (7.1.5.1)
actionpack (= 7.1.5.1)
actionview (= 7.1.5.1)
activejob (= 7.1.5.1)
activesupport (= 7.1.5.1)
mail (~> 2.5, >= 2.5.4)
net-imap
net-pop
net-smtp
rails-dom-testing (~> 2.2)
actionpack (7.1.4)
actionview (= 7.1.4)
activesupport (= 7.1.4)
actionpack (7.1.5.1)
actionview (= 7.1.5.1)
activesupport (= 7.1.5.1)
nokogiri (>= 1.8.5)
racc
rack (>= 2.2.4)
@@ -46,15 +46,15 @@ GEM
rack-test (>= 0.6.3)
rails-dom-testing (~> 2.2)
rails-html-sanitizer (~> 1.6)
actiontext (7.1.4)
actionpack (= 7.1.4)
activerecord (= 7.1.4)
activestorage (= 7.1.4)
activesupport (= 7.1.4)
actiontext (7.1.5.1)
actionpack (= 7.1.5.1)
activerecord (= 7.1.5.1)
activestorage (= 7.1.5.1)
activesupport (= 7.1.5.1)
globalid (>= 0.6.0)
nokogiri (>= 1.8.5)
actionview (7.1.4)
activesupport (= 7.1.4)
actionview (7.1.5.1)
activesupport (= 7.1.5.1)
builder (~> 3.1)
erubi (~> 1.11)
rails-dom-testing (~> 2.2)
@@ -64,30 +64,33 @@ GEM
activemodel (>= 4.1)
case_transform (>= 0.2)
jsonapi-renderer (>= 0.1.1.beta1, < 0.3)
activejob (7.1.4)
activesupport (= 7.1.4)
activejob (7.1.5.1)
activesupport (= 7.1.5.1)
globalid (>= 0.3.6)
activemodel (7.1.4)
activesupport (= 7.1.4)
activerecord (7.1.4)
activemodel (= 7.1.4)
activesupport (= 7.1.4)
activemodel (7.1.5.1)
activesupport (= 7.1.5.1)
activerecord (7.1.5.1)
activemodel (= 7.1.5.1)
activesupport (= 7.1.5.1)
timeout (>= 0.4.0)
activestorage (7.1.4)
actionpack (= 7.1.4)
activejob (= 7.1.4)
activerecord (= 7.1.4)
activesupport (= 7.1.4)
activestorage (7.1.5.1)
actionpack (= 7.1.5.1)
activejob (= 7.1.5.1)
activerecord (= 7.1.5.1)
activesupport (= 7.1.5.1)
marcel (~> 1.0)
activesupport (7.1.4)
activesupport (7.1.5.1)
base64
benchmark (>= 0.3)
bigdecimal
concurrent-ruby (~> 1.0, >= 1.0.2)
connection_pool (>= 2.2.5)
drb
i18n (>= 1.6, < 2)
logger (>= 1.4.2)
minitest (>= 5.1)
mutex_m
securerandom (>= 0.3)
tzinfo (~> 2.0)
addressable (2.8.7)
public_suffix (>= 2.0.2, < 7.0)
@@ -126,6 +129,7 @@ GEM
base64 (0.2.0)
bcp47_spec (0.2.1)
bcrypt (3.1.20)
benchmark (0.4.0)
better_errors (2.10.1)
erubi (>= 1.0.0)
rack (>= 0.9.0)
@@ -186,7 +190,7 @@ GEM
activerecord (>= 5.a)
database_cleaner-core (~> 2.0.0)
database_cleaner-core (2.0.1)
date (3.3.4)
date (3.4.1)
debug (1.9.2)
irb (~> 1.10)
reline (>= 0.3.8)
@@ -262,15 +266,15 @@ GEM
faraday (~> 1.0)
fast_blank (1.0.1)
fastimage (2.3.1)
ffi (1.16.3)
ffi (1.17.1)
ffi-compiler (1.3.2)
ffi (>= 1.15.5)
rake
flatware (2.3.3)
flatware (2.3.4)
drb
thor (< 2.0)
flatware-rspec (2.3.3)
flatware (= 2.3.3)
flatware-rspec (2.3.4)
flatware (= 2.3.4)
rspec (>= 3.6)
fog-core (2.5.0)
builder
@@ -406,7 +410,7 @@ GEM
llhttp-ffi (0.5.0)
ffi-compiler (~> 1.0)
rake (~> 13.0)
logger (1.6.1)
logger (1.6.6)
lograge (0.14.0)
actionpack (>= 4)
activesupport (>= 4)
@@ -433,7 +437,7 @@ GEM
mime-types-data (~> 3.2015)
mime-types-data (3.2024.0820)
mini_mime (1.1.5)
mini_portile2 (2.8.7)
mini_portile2 (2.8.8)
minitest (5.25.1)
msgpack (1.7.2)
multi_json (1.15.0)
@@ -443,7 +447,7 @@ GEM
uri
net-http-persistent (4.0.2)
connection_pool (~> 2.2)
net-imap (0.4.15)
net-imap (0.5.8)
date
net-protocol
net-ldap (0.19.0)
@@ -451,16 +455,16 @@ GEM
net-protocol
net-protocol (0.2.2)
timeout
net-smtp (0.5.0)
net-smtp (0.5.1)
net-protocol
nio4r (2.7.3)
nokogiri (1.16.7)
nokogiri (1.18.8)
mini_portile2 (~> 2.8.2)
racc (~> 1.4)
oj (3.16.6)
bigdecimal (>= 3.0)
ostruct (>= 0.2)
omniauth (2.1.2)
omniauth (2.1.3)
hashie (>= 3.4.6)
rack (>= 2.2.3)
rack-protection
@@ -471,9 +475,9 @@ GEM
omniauth-rails_csrf_protection (1.0.2)
actionpack (>= 4.2)
omniauth (~> 2.0)
omniauth-saml (2.2.1)
omniauth-saml (2.2.3)
omniauth (~> 2.1)
ruby-saml (~> 1.17)
ruby-saml (~> 1.18)
omniauth_openid_connect (0.6.1)
omniauth (>= 1.9, < 3)
openid_connect (~> 1.1)
@@ -615,7 +619,7 @@ GEM
activesupport (>= 3.0.0)
raabro (1.4.0)
racc (1.8.1)
rack (2.2.9)
rack (2.2.13)
rack-attack (6.7.0)
rack (>= 1.0, < 4)
rack-cors (2.0.2)
@@ -638,20 +642,20 @@ GEM
rackup (1.0.0)
rack (< 3)
webrick
rails (7.1.4)
actioncable (= 7.1.4)
actionmailbox (= 7.1.4)
actionmailer (= 7.1.4)
actionpack (= 7.1.4)
actiontext (= 7.1.4)
actionview (= 7.1.4)
activejob (= 7.1.4)
activemodel (= 7.1.4)
activerecord (= 7.1.4)
activestorage (= 7.1.4)
activesupport (= 7.1.4)
rails (7.1.5.1)
actioncable (= 7.1.5.1)
actionmailbox (= 7.1.5.1)
actionmailer (= 7.1.5.1)
actionpack (= 7.1.5.1)
actiontext (= 7.1.5.1)
actionview (= 7.1.5.1)
activejob (= 7.1.5.1)
activemodel (= 7.1.5.1)
activerecord (= 7.1.5.1)
activestorage (= 7.1.5.1)
activesupport (= 7.1.5.1)
bundler (>= 1.15.0)
railties (= 7.1.4)
railties (= 7.1.5.1)
rails-controller-testing (1.0.5)
actionpack (>= 5.0.1.rc1)
actionview (>= 5.0.1.rc1)
@@ -660,15 +664,15 @@ GEM
activesupport (>= 5.0.0)
minitest
nokogiri (>= 1.6)
rails-html-sanitizer (1.6.0)
rails-html-sanitizer (1.6.2)
loofah (~> 2.21)
nokogiri (~> 1.14)
nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0)
rails-i18n (7.0.9)
i18n (>= 0.7, < 2)
railties (>= 6.0.0, < 8)
railties (7.1.4)
actionpack (= 7.1.4)
activesupport (= 7.1.4)
railties (7.1.5.1)
actionpack (= 7.1.5.1)
activesupport (= 7.1.5.1)
irb
rackup (>= 1.0.0)
rake (>= 12.2)
@@ -698,7 +702,7 @@ GEM
responders (3.1.1)
actionpack (>= 5.2)
railties (>= 5.2)
rexml (3.3.8)
rexml (3.3.9)
rotp (6.3.0)
rouge (4.3.0)
rpam2 (4.0.2)
@@ -763,10 +767,10 @@ GEM
rubocop-rspec (~> 3, >= 3.0.1)
ruby-prof (1.7.0)
ruby-progressbar (1.13.0)
ruby-saml (1.17.0)
ruby-saml (1.18.0)
nokogiri (>= 1.13.10)
rexml
ruby-vips (2.2.2)
ruby-vips (2.2.3)
ffi (~> 1.12)
logger
ruby2_keywords (0.0.5)
@@ -781,6 +785,7 @@ GEM
scenic (1.8.0)
activerecord (>= 4.0.0)
railties (>= 4.0.0)
securerandom (0.4.1)
selenium-webdriver (4.25.0)
base64 (~> 0.2)
logger (~> 1.4)
@@ -837,7 +842,7 @@ GEM
test-prof (1.4.2)
thor (1.3.2)
tilt (2.4.0)
timeout (0.4.1)
timeout (0.4.3)
tpm-key_attestation (0.12.1)
bindata (~> 2.4)
openssl (> 2.0)
@@ -863,7 +868,7 @@ GEM
unf_ext
unf_ext (0.0.9.1)
unicode-display_width (2.5.0)
uri (0.13.1)
uri (0.13.2)
validate_email (0.1.6)
activemodel (>= 3.0)
mail (>= 2.2.5)
@@ -1060,4 +1065,4 @@ RUBY VERSION
ruby 3.3.4p94
BUNDLED WITH
2.5.18
2.6.5

View File

@@ -6,6 +6,7 @@ class Admin::AnnouncementsController < Admin::BaseController
def index
authorize :announcement, :index?
@published_announcements_count = Announcement.published.async_count
end
def new

View File

@@ -6,6 +6,7 @@ class Admin::Disputes::AppealsController < Admin::BaseController
def index
authorize :appeal, :index?
@pending_appeals_count = Appeal.pending.async_count
@appeals = filtered_appeals.page(params[:page])
end

View File

@@ -4,6 +4,7 @@ class Admin::Trends::Links::PreviewCardProvidersController < Admin::BaseControll
def index
authorize :preview_card_provider, :review?
@pending_preview_card_providers_count = PreviewCardProvider.unreviewed.async_count
@preview_card_providers = filtered_preview_card_providers.page(params[:page])
@form = Trends::PreviewCardProviderBatch.new
end

View File

@@ -4,6 +4,7 @@ class Admin::Trends::TagsController < Admin::BaseController
def index
authorize :tag, :review?
@pending_tags_count = Tag.pending_review.async_count
@tags = filtered_tags.page(params[:page])
@form = Trends::TagBatch.new
end

View File

@@ -72,6 +72,13 @@ class Api::BaseController < ApplicationController
end
end
# Redefine `require_functional!` to properly output JSON instead of HTML redirects
def require_functional!
return if current_user.functional?
require_user!
end
def render_empty
render json: {}, status: 200
end

View File

@@ -14,7 +14,7 @@ class Api::V1::Accounts::CredentialsController < Api::BaseController
@account = current_account
UpdateAccountService.new.call(@account, account_params, raise_error: true)
current_user.update(user_params) if user_params
ActivityPub::UpdateDistributionWorker.perform_async(@account.id)
ActivityPub::UpdateDistributionWorker.perform_in(ActivityPub::UpdateDistributionWorker::DEBOUNCE_DELAY, @account.id)
render json: @account, serializer: REST::CredentialAccountSerializer
rescue ActiveRecord::RecordInvalid => e
render json: ValidationErrorFormatter.new(e).as_json, status: 422

View File

@@ -16,6 +16,7 @@ class Api::V1::AccountsController < Api::BaseController
before_action :check_account_confirmation, except: [:index, :create]
before_action :check_enabled_registrations, only: [:create]
before_action :check_accounts_limit, only: [:index]
before_action :check_following_self, only: [:follow]
skip_before_action :require_authenticated_user!, only: :create
@@ -101,6 +102,10 @@ class Api::V1::AccountsController < Api::BaseController
raise(Mastodon::ValidationError) if account_ids.size > DEFAULT_ACCOUNTS_LIMIT
end
def check_following_self
render json: { error: I18n.t('accounts.self_follow_error') }, status: 403 if current_user.account.id == @account.id
end
def relationships(**options)
AccountRelationshipsPresenter.new([@account], current_user.account_id, **options)
end

View File

@@ -31,7 +31,7 @@ class Api::V1::Instances::DomainBlocksController < Api::V1::Instances::BaseContr
end
def show_domain_blocks_to_user?
Setting.show_domain_blocks == 'users' && user_signed_in?
Setting.show_domain_blocks == 'users' && user_signed_in? && current_user.functional_or_moved?
end
def set_domain_blocks
@@ -47,6 +47,6 @@ class Api::V1::Instances::DomainBlocksController < Api::V1::Instances::BaseContr
end
def show_rationale_for_user?
Setting.show_domain_blocks_rationale == 'users' && user_signed_in?
Setting.show_domain_blocks_rationale == 'users' && user_signed_in? && current_user.functional_or_moved?
end
end

View File

@@ -52,7 +52,7 @@ class Api::V1::Notifications::RequestsController < Api::BaseController
private
def load_requests
requests = NotificationRequest.where(account: current_account).includes(:last_status, from_account: [:account_stat, :user]).to_a_paginated_by_id(
requests = NotificationRequest.where(account: current_account).without_suspended.includes(:last_status, from_account: [:account_stat, :user]).to_a_paginated_by_id(
limit_param(DEFAULT_ACCOUNTS_LIMIT),
params_slice(:max_id, :since_id, :min_id)
)

View File

@@ -7,7 +7,7 @@ class Api::V1::Profile::AvatarsController < Api::BaseController
def destroy
@account = current_account
UpdateAccountService.new.call(@account, { avatar: nil }, raise_error: true)
ActivityPub::UpdateDistributionWorker.perform_async(@account.id)
ActivityPub::UpdateDistributionWorker.perform_in(ActivityPub::UpdateDistributionWorker::DEBOUNCE_DELAY, @account.id)
render json: @account, serializer: REST::CredentialAccountSerializer
end
end

View File

@@ -7,7 +7,7 @@ class Api::V1::Profile::HeadersController < Api::BaseController
def destroy
@account = current_account
UpdateAccountService.new.call(@account, { header: nil }, raise_error: true)
ActivityPub::UpdateDistributionWorker.perform_async(@account.id)
ActivityPub::UpdateDistributionWorker.perform_in(ActivityPub::UpdateDistributionWorker::DEBOUNCE_DELAY, @account.id)
render json: @account, serializer: REST::CredentialAccountSerializer
end
end

View File

@@ -23,6 +23,6 @@ class Api::V1::Statuses::TranslationsController < Api::V1::Statuses::BaseControl
private
def set_translation
@translation = TranslateStatusService.new.call(@status, content_locale)
@translation = TranslateStatusService.new.call(@status, I18n.locale.to_s)
end
end

View File

@@ -46,7 +46,7 @@ class Api::V2::NotificationsController < Api::BaseController
end
def show
@notification = current_account.notifications.without_suspended.find_by!(group_key: params[:group_key])
@notification = current_account.notifications.without_suspended.by_group_key(params[:group_key]).take!
presenter = GroupedNotificationsPresenter.new(NotificationGroup.from_notifications([@notification]))
render json: presenter, serializer: REST::DedupNotificationGroupSerializer
end
@@ -57,7 +57,7 @@ class Api::V2::NotificationsController < Api::BaseController
end
def dismiss
current_account.notifications.where(group_key: params[:group_key]).destroy_all
current_account.notifications.by_group_key(params[:group_key]).destroy_all
render_empty
end
@@ -80,10 +80,31 @@ class Api::V2::NotificationsController < Api::BaseController
return [] if @notifications.empty?
MastodonOTELTracer.in_span('Api::V2::NotificationsController#load_grouped_notifications') do
NotificationGroup.from_notifications(@notifications, pagination_range: (@notifications.last.id)..(@notifications.first.id), grouped_types: params[:grouped_types])
pagination_range = (@notifications.last.id)..@notifications.first.id
# If the page is incomplete, we know we are on the last page
if incomplete_page?
if paginating_up?
pagination_range = @notifications.last.id...(params[:max_id]&.to_i)
else
range_start = params[:since_id]&.to_i
range_start += 1 unless range_start.nil?
pagination_range = range_start..(@notifications.first.id)
end
end
NotificationGroup.from_notifications(@notifications, pagination_range: pagination_range, grouped_types: params[:grouped_types])
end
end
def incomplete_page?
@notifications.size < limit_param(DEFAULT_NOTIFICATIONS_LIMIT)
end
def paginating_up?
params[:min_id].present?
end
def browserable_account_notifications
current_account.notifications.without_suspended.browserable(
types: Array(browserable_params[:types]),

View File

@@ -74,7 +74,23 @@ class ApplicationController < ActionController::Base
end
def require_functional!
redirect_to edit_user_registration_path unless current_user.functional?
return if current_user.functional?
respond_to do |format|
format.any do
redirect_to edit_user_registration_path
end
format.json do
if !current_user.confirmed?
render json: { error: 'Your login is missing a confirmed e-mail address' }, status: 403
elsif !current_user.approved?
render json: { error: 'Your login is currently pending approval' }, status: 403
elsif !current_user.functional?
render json: { error: 'Your login is currently disabled' }, status: 403
end
end
end
end
def skip_csrf_meta_tags?

View File

@@ -9,13 +9,15 @@ class BackupsController < ApplicationController
before_action :authenticate_user!
before_action :set_backup
BACKUP_LINK_TIMEOUT = 1.hour.freeze
def download
case Paperclip::Attachment.default_options[:storage]
when :s3, :azure
redirect_to @backup.dump.expiring_url(10), allow_other_host: true
redirect_to @backup.dump.expiring_url(BACKUP_LINK_TIMEOUT.to_i), allow_other_host: true
when :fog
if Paperclip::Attachment.default_options.dig(:fog_credentials, :openstack_temp_url_key).present?
redirect_to @backup.dump.expiring_url(Time.now.utc + 10), allow_other_host: true
redirect_to @backup.dump.expiring_url(BACKUP_LINK_TIMEOUT.from_now), allow_other_host: true
else
redirect_to full_asset_url(@backup.dump.url), allow_other_host: true
end

View File

@@ -117,7 +117,7 @@ module SignatureVerification
def verify_signature_strength!
raise SignatureVerificationError, 'Mastodon requires the Date header or (created) pseudo-header to be signed' unless signed_headers.include?('date') || signed_headers.include?('(created)')
raise SignatureVerificationError, 'Mastodon requires the Digest header or (request-target) pseudo-header to be signed' unless signed_headers.include?(Request::REQUEST_TARGET) || signed_headers.include?('digest')
raise SignatureVerificationError, 'Mastodon requires the Digest header or (request-target) pseudo-header to be signed' unless signed_headers.include?(HttpSignatureDraft::REQUEST_TARGET) || signed_headers.include?('digest')
raise SignatureVerificationError, 'Mastodon requires the Host header to be signed when doing a GET request' if request.get? && !signed_headers.include?('host')
raise SignatureVerificationError, 'Mastodon requires the Digest header to be signed when doing a POST request' if request.post? && !signed_headers.include?('digest')
end
@@ -155,14 +155,14 @@ module SignatureVerification
def build_signed_string(include_query_string: true)
signed_headers.map do |signed_header|
case signed_header
when Request::REQUEST_TARGET
when HttpSignatureDraft::REQUEST_TARGET
if include_query_string
"#{Request::REQUEST_TARGET}: #{request.method.downcase} #{request.original_fullpath}"
"#{HttpSignatureDraft::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}"
"#{HttpSignatureDraft::REQUEST_TARGET}: #{request.method.downcase} #{request.path}"
end
when '(created)'
raise SignatureVerificationError, 'Invalid pseudo-header (created) for rsa-sha256' unless signature_algorithm == 'hs2019'

View File

@@ -8,7 +8,7 @@ module Settings
def destroy
if valid_picture?
if UpdateAccountService.new.call(@account, { @picture => nil, "#{@picture}_remote_url" => '' })
ActivityPub::UpdateDistributionWorker.perform_async(@account.id)
ActivityPub::UpdateDistributionWorker.perform_in(ActivityPub::UpdateDistributionWorker::DEBOUNCE_DELAY, @account.id)
redirect_to settings_profile_path, notice: I18n.t('generic.changes_saved_msg'), status: 303
else
redirect_to settings_profile_path

View File

@@ -8,7 +8,7 @@ class Settings::PrivacyController < Settings::BaseController
def update
if UpdateAccountService.new.call(@account, account_params.except(:settings))
current_user.update!(settings_attributes: account_params[:settings])
ActivityPub::UpdateDistributionWorker.perform_async(@account.id)
ActivityPub::UpdateDistributionWorker.perform_in(ActivityPub::UpdateDistributionWorker::DEBOUNCE_DELAY, @account.id)
redirect_to settings_privacy_path, notice: I18n.t('generic.changes_saved_msg')
else
render :show

View File

@@ -9,7 +9,7 @@ class Settings::ProfilesController < Settings::BaseController
def update
if UpdateAccountService.new.call(@account, account_params)
ActivityPub::UpdateDistributionWorker.perform_async(@account.id)
ActivityPub::UpdateDistributionWorker.perform_in(ActivityPub::UpdateDistributionWorker::DEBOUNCE_DELAY, @account.id)
redirect_to settings_profile_path, notice: I18n.t('generic.changes_saved_msg')
else
@account.build_fields

View File

@@ -8,7 +8,7 @@ class Settings::VerificationsController < Settings::BaseController
def update
if UpdateAccountService.new.call(@account, account_params)
ActivityPub::UpdateDistributionWorker.perform_async(@account.id)
ActivityPub::UpdateDistributionWorker.perform_in(ActivityPub::UpdateDistributionWorker::DEBOUNCE_DELAY, @account.id)
redirect_to settings_verification_path, notice: I18n.t('generic.changes_saved_msg')
else
render :show

View File

@@ -2,11 +2,18 @@
module Admin::Trends::StatusesHelper
def one_line_preview(status)
text = if status.local?
status.text.split("\n").first
else
Nokogiri::HTML5(status.text).css('html > body > *').first&.text
end
text = begin
if status.local?
status.text.split("\n").first
else
Nokogiri::HTML5(status.text).css('html > body > *').first&.text
end
rescue ArgumentError
# This can happen if one of the Nokogumbo limits is encountered
# Unfortunately, it does not use a more precise error class
# nor allows more graceful handling
''
end
return '' if text.blank?

View File

@@ -79,7 +79,7 @@ module ApplicationHelper
def html_title
safe_join(
[content_for(:page_title).to_s.chomp, title]
[content_for(:page_title), title]
.compact_blank,
' - '
)
@@ -245,6 +245,11 @@ module ApplicationHelper
tag.input(type: :text, maxlength: 999, spellcheck: false, readonly: true, **options)
end
def recent_tag_usage(tag)
people = tag.history.aggregate(2.days.ago.to_date..Time.zone.today).accounts
I18n.t 'user_mailer.welcome.hashtags_recent_count', people: number_with_delimiter(people), count: people
end
# glitch-soc addition to handle the multiple flavors
def preload_locale_pack
supported_locales = Themes.instance.flavour(current_flavour)['locales']

View File

@@ -26,6 +26,13 @@ module ContextHelper
voters_count: { 'toot' => 'http://joinmastodon.org/ns#', 'votersCount' => 'toot:votersCount' },
suspended: { 'toot' => 'http://joinmastodon.org/ns#', 'suspended' => 'toot:suspended' },
attribution_domains: { 'toot' => 'http://joinmastodon.org/ns#', 'attributionDomains' => { '@id' => 'toot:attributionDomains', '@type' => '@id' } },
interaction_policies: {
'gts' => 'https://gotosocial.org/ns#',
'interactionPolicy' => { '@id' => 'gts:interactionPolicy', '@type' => '@id' },
'canQuote' => { '@id' => 'gts:canQuote', '@type' => '@id' },
'automaticApproval' => { '@id' => 'gts:automaticApproval', '@type' => '@id' },
'manualApproval' => { '@id' => 'gts:manualApproval', '@type' => '@id' },
},
}.freeze
def full_context

View File

@@ -162,7 +162,7 @@ module LanguagesHelper
th: ['Thai', 'ไทย'].freeze,
ti: ['Tigrinya', 'ትግርኛ'].freeze,
tk: ['Turkmen', 'Türkmen'].freeze,
tl: ['Tagalog', 'Wikang Tagalog'].freeze,
tl: ['Tagalog', 'Tagalog'].freeze,
tn: ['Tswana', 'Setswana'].freeze,
to: ['Tonga', 'faka Tonga'].freeze,
tr: ['Turkish', 'Türkçe'].freeze,

View File

@@ -4,9 +4,12 @@ import axios from 'axios';
import ready from '../mastodon/ready';
async function checkConfirmation() {
const response = await axios.get('/api/v1/emails/check_confirmation');
const response = await axios.get('/api/v1/emails/check_confirmation', {
headers: { Accept: 'application/json' },
withCredentials: true,
});
if (response.data) {
if (response.status === 200 && response.data === true) {
window.location.href = '/start';
}
}

View File

@@ -72,6 +72,17 @@ export function normalizeStatus(status, normalOldStatus, settings) {
normalStatus.contentHtml = emojify(normalStatus.content, emojiMap);
normalStatus.spoilerHtml = emojify(escapeTextContentForBrowser(spoilerText), emojiMap);
normalStatus.hidden = (spoilerText.length > 0 || normalStatus.sensitive) && autoHideCW(settings, spoilerText);
if (normalStatus.url && !(normalStatus.url.startsWith('http://') || normalStatus.url.startsWith('https://'))) {
normalStatus.url = null;
}
normalStatus.url ||= normalStatus.uri;
normalStatus.media_attachments.forEach(item => {
if (item.remote_url && !(item.remote_url.startsWith('http://') || item.remote_url.startsWith('https://')))
item.remote_url = null;
});
}
if (normalOldStatus) {

View File

@@ -8,6 +8,7 @@ import type { ApiAccountJSON } from 'flavours/glitch/api_types/accounts';
import type {
ApiNotificationGroupJSON,
ApiNotificationJSON,
NotificationType,
} from 'flavours/glitch/api_types/notifications';
import { allNotificationTypes } from 'flavours/glitch/api_types/notifications';
import type { ApiStatusJSON } from 'flavours/glitch/api_types/statuses';
@@ -15,6 +16,7 @@ import { usePendingItems } from 'flavours/glitch/initial_state';
import type { NotificationGap } from 'flavours/glitch/reducers/notification_groups';
import {
selectSettingsNotificationsExcludedTypes,
selectSettingsNotificationsGroupFollows,
selectSettingsNotificationsQuickFilterActive,
selectSettingsNotificationsShows,
} from 'flavours/glitch/selectors/settings';
@@ -68,17 +70,19 @@ function dispatchAssociatedRecords(
dispatch(importFetchedStatuses(fetchedStatuses));
}
const supportedGroupedNotificationTypes = ['favourite', 'reblog'];
function selectNotificationGroupedTypes(state: RootState) {
const types: NotificationType[] = ['favourite', 'reblog'];
export function shouldGroupNotificationType(type: string) {
return supportedGroupedNotificationTypes.includes(type);
if (selectSettingsNotificationsGroupFollows(state)) types.push('follow');
return types;
}
export const fetchNotifications = createDataLoadingThunk(
'notificationGroups/fetch',
async (_params, { getState }) =>
apiFetchNotificationGroups({
grouped_types: supportedGroupedNotificationTypes,
grouped_types: selectNotificationGroupedTypes(getState()),
exclude_types: getExcludedTypes(getState()),
}),
({ notifications, accounts, statuses }, { dispatch }) => {
@@ -102,7 +106,7 @@ export const fetchNotificationsGap = createDataLoadingThunk(
'notificationGroups/fetchGap',
async (params: { gap: NotificationGap }, { getState }) =>
apiFetchNotificationGroups({
grouped_types: supportedGroupedNotificationTypes,
grouped_types: selectNotificationGroupedTypes(getState()),
max_id: params.gap.maxId,
exclude_types: getExcludedTypes(getState()),
}),
@@ -119,7 +123,7 @@ export const pollRecentNotifications = createDataLoadingThunk(
'notificationGroups/pollRecentNotifications',
async (_params, { getState }) => {
return apiFetchNotificationGroups({
grouped_types: supportedGroupedNotificationTypes,
grouped_types: selectNotificationGroupedTypes(getState()),
max_id: undefined,
exclude_types: getExcludedTypes(getState()),
// In slow mode, we don't want to include notifications that duplicate the already-displayed ones
@@ -137,6 +141,9 @@ export const pollRecentNotifications = createDataLoadingThunk(
return { notifications };
},
{
useLoadingBar: false,
},
);
export const processNewNotificationForGroups = createAppAsyncThunk(
@@ -168,7 +175,10 @@ export const processNewNotificationForGroups = createAppAsyncThunk(
dispatchAssociatedRecords(dispatch, [notification]);
return notification;
return {
notification,
groupedTypes: selectNotificationGroupedTypes(state),
};
},
);

View File

@@ -13,7 +13,7 @@ export interface ApiAccountRoleJSON {
}
// See app/serializers/rest/account_serializer.rb
export interface ApiAccountJSON {
export interface BaseApiAccountJSON {
acct: string;
avatar: string;
avatar_static: string;
@@ -45,3 +45,12 @@ export interface ApiAccountJSON {
memorial?: boolean;
hide_collections: boolean;
}
// See app/serializers/rest/muted_account_serializer.rb
export interface ApiMutedAccountJSON extends BaseApiAccountJSON {
mute_expires_at?: string | null;
}
// For now, we have the same type representing both `Account` and `MutedAccount`
// objects, but we should refactor this in the future.
export type ApiAccountJSON = ApiMutedAccountJSON;

View File

@@ -19,6 +19,7 @@ export const CollapseButton = ({ collapsed, setCollapsed }) => {
if (e.button === 0) {
setCollapsed(!collapsed);
e.preventDefault();
e.stopPropagation();
}
}, [collapsed, setCollapsed]);

View File

@@ -17,9 +17,15 @@ export const ContentWarning: React.FC<{
aria-expanded={expanded}
>
{expanded ? (
<FormattedMessage id='status.show_less' defaultMessage='Show less' />
<FormattedMessage
id='content_warning.hide'
defaultMessage='Hide post'
/>
) : (
<FormattedMessage id='status.show_more' defaultMessage='Show more' />
<FormattedMessage
id='content_warning.show_more'
defaultMessage='Show more'
/>
)}
{icons}
</button>

View File

@@ -10,13 +10,16 @@ export const FilterWarning: React.FC<{
<StatusBanner
expanded={expanded}
onClick={onClick}
variant={BannerVariant.Blue}
variant={BannerVariant.Filter}
>
<p>
<FormattedMessage
id='filter_warning.matches_filter'
defaultMessage='Matches filter “{title}”'
values={{ title }}
defaultMessage='Matches filter “<span>{title}</span>”'
values={{
title,
span: (chunks) => <span className='filter-name'>{chunks}</span>,
}}
/>
</p>
</StatusBanner>

View File

@@ -98,12 +98,12 @@ class Item extends PureComponent {
height = 50;
}
if (attachment.get('description')?.length > 0) {
badges.push(<AltTextBadge key='alt' description={attachment.get('description')} />);
}
const description = attachment.getIn(['translation', 'description']) || attachment.get('description');
if (description?.length > 0) {
badges.push(<AltTextBadge key='alt' description={description} />);
}
if (attachment.get('type') === 'unknown') {
return (
<div className={classNames('media-gallery__item', { standalone, 'media-gallery__item--tall': height === 100, 'media-gallery__item--wide': width === 100 })} key={attachment.get('id')}>

View File

@@ -372,26 +372,29 @@ class Status extends ImmutablePureComponent {
const { isCollapsed } = this.state;
if (!history) return;
if (e.button === 0 && !(e.ctrlKey || e.altKey || e.metaKey)) {
if (isCollapsed) this.setCollapsed(false);
else if (e.shiftKey) {
this.setCollapsed(true);
document.getSelection().removeAllRanges();
} else if (this.props.onClick) {
this.props.onClick();
return;
} else {
if (destination === undefined) {
destination = `/@${
status.getIn(['reblog', 'account', 'acct'], status.getIn(['account', 'acct']))
}/${
status.getIn(['reblog', 'id'], status.get('id'))
}`;
}
history.push(destination);
}
e.preventDefault();
if (e.button !== 0 || e.ctrlKey || e.altKey || e.metaKey) {
return;
}
if (isCollapsed) this.setCollapsed(false);
else if (e.shiftKey) {
this.setCollapsed(true);
document.getSelection().removeAllRanges();
} else if (this.props.onClick) {
this.props.onClick();
return;
} else {
if (destination === undefined) {
destination = `/@${
status.getIn(['reblog', 'account', 'acct'], status.getIn(['account', 'acct']))
}/${
status.getIn(['reblog', 'id'], status.get('id'))
}`;
}
history.push(destination);
}
e.preventDefault();
};
handleToggleMediaVisibility = () => {
@@ -648,7 +651,7 @@ class Status extends ImmutablePureComponent {
media={status.get('media_attachments')}
/>,
);
} else if (['image', 'gifv'].includes(status.getIn(['media_attachments', 0, 'type'])) || status.get('media_attachments').size > 1) {
} else if (['image', 'gifv', 'unknown'].includes(status.getIn(['media_attachments', 0, 'type'])) || status.get('media_attachments').size > 1) {
media.push(
<Bundle fetchComponent={MediaGallery} loading={this.renderLoadingMediaGallery}>
{Component => (
@@ -806,7 +809,8 @@ class Status extends ImmutablePureComponent {
{(connectReply || connectUp || connectToRoot) && <div className={classNames('status__line', { 'status__line--full': connectReply, 'status__line--first': !status.get('in_reply_to_id') && !connectToRoot })} />}
{(!muted || !isCollapsed) && (
<header className='status__info'>
/* eslint-disable-next-line jsx-a11y/no-static-element-interactions */
<header onClick={this.parseClick} className='status__info'>
<StatusHeader
status={status}
friend={account}

View File

@@ -233,7 +233,7 @@ class StatusActionBar extends ImmutablePureComponent {
menu.push({ text: intl.formatMessage(messages.share), action: this.handleShareClick });
}
if (publicStatus && (signedIn || !isRemote)) {
if (publicStatus && !isRemote) {
menu.push({ text: intl.formatMessage(messages.embed), action: this.handleEmbed });
}

View File

@@ -1,8 +1,8 @@
import { FormattedMessage } from 'react-intl';
export enum BannerVariant {
Yellow = 'yellow',
Blue = 'blue',
Warning = 'warning',
Filter = 'filter',
}
export const StatusBanner: React.FC<{
@@ -11,9 +11,9 @@ export const StatusBanner: React.FC<{
expanded?: boolean;
onClick?: () => void;
}> = ({ children, variant, expanded, onClick }) => (
<div
<label
className={
variant === BannerVariant.Yellow
variant === BannerVariant.Warning
? 'content-warning'
: 'content-warning content-warning--filter'
}
@@ -26,6 +26,11 @@ export const StatusBanner: React.FC<{
id='content_warning.hide'
defaultMessage='Hide post'
/>
) : variant === BannerVariant.Warning ? (
<FormattedMessage
id='content_warning.show_more'
defaultMessage='Show more'
/>
) : (
<FormattedMessage
id='content_warning.show'
@@ -33,5 +38,5 @@ export const StatusBanner: React.FC<{
/>
)}
</button>
</div>
</label>
);

View File

@@ -18,15 +18,10 @@ export default class StatusHeader extends PureComponent {
parseClick: PropTypes.func.isRequired,
};
// Handles clicks on account name/image
handleClick = (acct, e) => {
const { parseClick } = this.props;
parseClick(e, `/@${acct}`);
};
handleAccountClick = (e) => {
const { status } = this.props;
this.handleClick(status.getIn(['account', 'acct']), e);
const { status, parseClick } = this.props;
parseClick(e, `/@${status.getIn(['account', 'acct'])}`);
e.stopPropagation();
};
// Rendering.

View File

@@ -4,9 +4,12 @@ import axios from 'axios';
import ready from 'flavours/glitch/ready';
async function checkConfirmation() {
const response = await axios.get('/api/v1/emails/check_confirmation');
const response = await axios.get('/api/v1/emails/check_confirmation', {
headers: { Accept: 'application/json' },
withCredentials: true,
});
if (response.data) {
if (response.status === 200 && response.data === true) {
window.location.href = '/start';
}
}

View File

@@ -100,6 +100,7 @@ class Bookmarks extends ImmutablePureComponent {
onLoadMore={this.handleLoadMore}
emptyMessage={emptyMessage}
bindToDocument={!multiColumn}
timelineId='bookmarks'
/>
<Helmet>

View File

@@ -27,15 +27,19 @@ class ColumnSettings extends PureComponent {
return (
<div className='column-settings'>
<div className='column-settings__row'>
<SettingToggle settings={settings} settingPath={['other', 'onlyMedia']} onChange={onChange} label={<FormattedMessage id='community.column_settings.media_only' defaultMessage='Media only' />} />
</div>
<section>
<div className='column-settings__row'>
<SettingToggle settings={settings} settingPath={['other', 'onlyMedia']} onChange={onChange} label={<FormattedMessage id='community.column_settings.media_only' defaultMessage='Media only' />} />
</div>
</section>
<span className='column-settings__section'><FormattedMessage id='home.column_settings.advanced' defaultMessage='Advanced' /></span>
<section>
<span className='column-settings__section'><FormattedMessage id='home.column_settings.advanced' defaultMessage='Advanced' /></span>
<div className='column-settings__row'>
<SettingText settings={settings} settingPath={['regex', 'body']} onChange={onChange} label={intl.formatMessage(messages.filter_regex)} />
</div>
<div className='column-settings__row'>
<SettingText settings={settings} settingPath={['regex', 'body']} onChange={onChange} label={intl.formatMessage(messages.filter_regex)} />
</div>
</section>
</div>
);
}

View File

@@ -97,30 +97,30 @@ const emojifyTextNode = (node, customEmojis) => {
const { filename, shortCode } = unicodeMapping[unicode_emoji];
const title = shortCode ? `:${shortCode}:` : '';
replacement = document.createElement('picture');
const isSystemTheme = !!document.body?.classList.contains('theme-system');
if(isSystemTheme) {
let source = document.createElement('source');
source.setAttribute('media', '(prefers-color-scheme: dark)');
source.setAttribute('srcset', `${assetHost}/emoji/${emojiFilename(filename, "dark")}.svg`);
replacement.appendChild(source);
}
const theme = (isSystemTheme || document.body?.classList.contains('theme-mastodon-light')) ? 'light' : 'dark';
let img = document.createElement('img');
const imageFilename = emojiFilename(filename, theme);
const img = document.createElement('img');
img.setAttribute('draggable', 'false');
img.setAttribute('class', 'emojione');
img.setAttribute('alt', unicode_emoji);
img.setAttribute('title', title);
img.setAttribute('src', `${assetHost}/emoji/${imageFilename}.svg`);
let theme = "light";
if (isSystemTheme && imageFilename !== emojiFilename(filename, 'dark')) {
replacement = document.createElement('picture');
if(!isSystemTheme && !document.body?.classList.contains('skin-mastodon-light'))
theme = "dark";
img.setAttribute('src', `${assetHost}/emoji/${emojiFilename(filename, theme)}.svg`);
replacement.appendChild(img);
const source = document.createElement('source');
source.setAttribute('media', '(prefers-color-scheme: dark)');
source.setAttribute('srcset', `${assetHost}/emoji/${emojiFilename(filename, 'dark')}.svg`);
replacement.appendChild(source);
replacement.appendChild(img);
} else {
replacement = img;
}
}
// Add the processed-up-to-now string and the emoji replacement
@@ -135,7 +135,7 @@ const emojifyTextNode = (node, customEmojis) => {
};
const emojifyNode = (node, customEmojis) => {
for (const child of node.childNodes) {
for (const child of Array.from(node.childNodes)) {
switch(child.nodeType) {
case Node.TEXT_NODE:
emojifyTextNode(child, customEmojis);

View File

@@ -100,6 +100,7 @@ class Favourites extends ImmutablePureComponent {
onLoadMore={this.handleLoadMore}
emptyMessage={emptyMessage}
bindToDocument={!multiColumn}
timelineId='favourites'
/>
<Helmet>

View File

@@ -129,8 +129,13 @@ export const InlineFollowSuggestions = ({ hidden }) => {
return;
}
setCanScrollLeft(bodyRef.current.scrollLeft > 0);
setCanScrollRight((bodyRef.current.scrollLeft + bodyRef.current.clientWidth) < bodyRef.current.scrollWidth);
if (getComputedStyle(bodyRef.current).direction === 'rtl') {
setCanScrollLeft((bodyRef.current.clientWidth - bodyRef.current.scrollLeft) < bodyRef.current.scrollWidth);
setCanScrollRight(bodyRef.current.scrollLeft < 0);
} else {
setCanScrollLeft(bodyRef.current.scrollLeft > 0);
setCanScrollRight((bodyRef.current.scrollLeft + bodyRef.current.clientWidth) < bodyRef.current.scrollWidth);
}
}, [setCanScrollRight, setCanScrollLeft, bodyRef, suggestions]);
const handleLeftNav = useCallback(() => {
@@ -146,8 +151,13 @@ export const InlineFollowSuggestions = ({ hidden }) => {
return;
}
setCanScrollLeft(bodyRef.current.scrollLeft > 0);
setCanScrollRight((bodyRef.current.scrollLeft + bodyRef.current.clientWidth) < bodyRef.current.scrollWidth);
if (getComputedStyle(bodyRef.current).direction === 'rtl') {
setCanScrollLeft((bodyRef.current.clientWidth - bodyRef.current.scrollLeft) < bodyRef.current.scrollWidth);
setCanScrollRight(bodyRef.current.scrollLeft < 0);
} else {
setCanScrollLeft(bodyRef.current.scrollLeft > 0);
setCanScrollRight((bodyRef.current.scrollLeft + bodyRef.current.clientWidth) < bodyRef.current.scrollWidth);
}
}, [setCanScrollRight, setCanScrollLeft, bodyRef]);
const handleDismiss = useCallback(() => {

View File

@@ -169,6 +169,15 @@ class LocalSettingsPage extends PureComponent {
<FormattedMessage id='settings.wide_view' defaultMessage='Wide view (Desktop mode only)' />
<span className='hint'><FormattedMessage id='settings.wide_view_hint' defaultMessage='Stretches columns to better fill the available space.' /></span>
</LocalSettingsPageItem>
<LocalSettingsPageItem
settings={settings}
item={['fullwidth_columns']}
id='mastodon-settings--fullwidth_columns'
onChange={onChange}
>
<FormattedMessage id='settings.fullwidth_view' defaultMessage='Stretch columns to full width (Desktop mode only)' />
<span className='hint'><FormattedMessage id='settings.fullwidth_view_hint' defaultMessage='Stretches columns to fill all the available space.' /></span>
</LocalSettingsPageItem>
</section>
</div>
),

View File

@@ -40,6 +40,7 @@ class ColumnSettings extends PureComponent {
const alertStr = <FormattedMessage id='notifications.column_settings.alert' defaultMessage='Desktop notifications' />;
const showStr = <FormattedMessage id='notifications.column_settings.show' defaultMessage='Show in column' />;
const soundStr = <FormattedMessage id='notifications.column_settings.sound' defaultMessage='Play sound' />;
const groupStr = <FormattedMessage id='notifications.column_settings.group' defaultMessage='Group' />;
const showPushSettings = pushSettings.get('browserSupport') && pushSettings.get('isSubscribed');
const pushStr = showPushSettings && <FormattedMessage id='notifications.column_settings.push' defaultMessage='Push notifications' />;
@@ -96,6 +97,10 @@ class ColumnSettings extends PureComponent {
<PillBarButton prefix='notifications' settings={settings} settingPath={['shows', 'follow']} onChange={onChange} label={showStr} />
<PillBarButton prefix='notifications' settings={settings} settingPath={['sounds', 'follow']} onChange={onChange} label={soundStr} />
</div>
<div className='column-settings__row'>
<SettingToggle prefix='notifications' settings={settings} settingPath={['group', 'follow']} onChange={onChange} label={groupStr} />
</div>
</section>
<section role='group' aria-labelledby='notifications-follow-request'>

View File

@@ -56,11 +56,12 @@ const mapDispatchToProps = (dispatch) => ({
} else {
dispatch(changeSetting(['notifications', ...path], checked));
}
} else if(path[0] === 'groupingBeta') {
dispatch(changeSetting(['notifications', ...path], checked));
dispatch(initializeNotifications());
} else {
dispatch(changeSetting(['notifications', ...path], checked));
if(path[0] === 'group' && path[1] === 'follow') {
dispatch(initializeNotifications());
}
}
},

View File

@@ -1,16 +1,19 @@
import { FormattedMessage } from 'react-intl';
import { Link } from 'react-router-dom';
import PersonAddIcon from '@/material-icons/400-24px/person_add-fill.svg?react';
import { FollowersCounter } from 'flavours/glitch/components/counters';
import { FollowButton } from 'flavours/glitch/components/follow_button';
import { ShortNumber } from 'flavours/glitch/components/short_number';
import { me } from 'flavours/glitch/initial_state';
import type { NotificationGroupFollow } from 'flavours/glitch/models/notification_group';
import { useAppSelector } from 'flavours/glitch/store';
import type { LabelRenderer } from './notification_group_with_status';
import { NotificationGroupWithStatus } from './notification_group_with_status';
const labelRenderer: LabelRenderer = (displayedName, total) => {
const labelRenderer: LabelRenderer = (displayedName, total, seeMoreHref) => {
if (total === 1)
return (
<FormattedMessage
@@ -23,10 +26,12 @@ const labelRenderer: LabelRenderer = (displayedName, total) => {
return (
<FormattedMessage
id='notification.follow.name_and_others'
defaultMessage='{name} and {count, plural, one {# other} other {# others}} followed you'
defaultMessage='{name} and <a>{count, plural, one {# other} other {# others}}</a> followed you'
values={{
name: displayedName,
count: total - 1,
a: (chunks) =>
seeMoreHref ? <Link to={seeMoreHref}>{chunks}</Link> : chunks,
}}
/>
);
@@ -46,6 +51,10 @@ export const NotificationFollow: React.FC<{
notification: NotificationGroupFollow;
unread: boolean;
}> = ({ notification, unread }) => {
const username = useAppSelector(
(state) => state.accounts.getIn([me, 'username']) as string,
);
let actions: JSX.Element | undefined;
let additionalContent: JSX.Element | undefined;
@@ -68,6 +77,7 @@ export const NotificationFollow: React.FC<{
timestamp={notification.latest_page_notification_at}
count={notification.notifications_count}
labelRenderer={labelRenderer}
labelSeeMoreHref={`/@${username}/followers`}
unread={unread}
actions={actions}
additionalContent={additionalContent}

View File

@@ -16,6 +16,7 @@ import {
import type { IconProp } from 'flavours/glitch/components/icon';
import { Icon } from 'flavours/glitch/components/icon';
import Status from 'flavours/glitch/containers/status_container';
import { getStatusHidden } from 'flavours/glitch/selectors/filters';
import { useAppSelector, useAppDispatch } from 'flavours/glitch/store';
import { DisplayedName } from './displayed_name';
@@ -51,6 +52,12 @@ export const NotificationWithStatus: React.FC<{
(state) => state.statuses.getIn([statusId, 'visibility']) === 'direct',
);
const isFiltered = useAppSelector(
(state) =>
statusId &&
getStatusHidden(state, { id: statusId, contextType: 'notifications' }),
);
const handlers = useMemo(
() => ({
open: () => {
@@ -77,7 +84,7 @@ export const NotificationWithStatus: React.FC<{
[dispatch, statusId],
);
if (!statusId) return null;
if (!statusId || isFiltered) return null;
return (
<HotKeys handlers={handlers}>

View File

@@ -14,6 +14,7 @@ import { Icon } from 'flavours/glitch/components/icon';
import {
selectSettingsNotificationsQuickFilterActive,
selectSettingsNotificationsQuickFilterAdvanced,
selectSettingsNotificationsQuickFilterShow,
} from 'flavours/glitch/selectors/settings';
import { useAppDispatch, useAppSelector } from 'flavours/glitch/store';
@@ -65,6 +66,11 @@ export const FilterBar: React.FC = () => {
const advancedMode = useAppSelector(
selectSettingsNotificationsQuickFilterAdvanced,
);
const useFilterBar = useAppSelector(
selectSettingsNotificationsQuickFilterShow,
);
if (!useFilterBar) return null;
if (advancedMode)
return (

View File

@@ -14,6 +14,8 @@ import RepeatIcon from '@/material-icons/400-24px/repeat.svg?react';
import ReplyIcon from '@/material-icons/400-24px/reply.svg?react';
import ReplyAllIcon from '@/material-icons/400-24px/reply_all.svg?react';
import StarIcon from '@/material-icons/400-24px/star.svg?react';
import RepeatDisabledIcon from '@/svg-icons/repeat_disabled.svg?react';
import RepeatPrivateIcon from '@/svg-icons/repeat_private.svg?react';
import { replyCompose } from 'flavours/glitch/actions/compose';
import { toggleReblog, toggleFavourite } from 'flavours/glitch/actions/interactions';
import { openModal } from 'flavours/glitch/actions/modal';
@@ -161,16 +163,20 @@ class Footer extends ImmutablePureComponent {
replyTitle = intl.formatMessage(messages.replyAll);
}
let reblogTitle = '';
let reblogTitle, reblogIconComponent;
if (status.get('reblogged')) {
reblogTitle = intl.formatMessage(messages.cancel_reblog_private);
reblogIconComponent = publicStatus ? RepeatIcon : RepeatPrivateIcon;
} else if (publicStatus) {
reblogTitle = intl.formatMessage(messages.reblog);
reblogIconComponent = RepeatIcon;
} else if (reblogPrivate) {
reblogTitle = intl.formatMessage(messages.reblog_private);
reblogIconComponent = RepeatPrivateIcon;
} else {
reblogTitle = intl.formatMessage(messages.cannot_reblog);
reblogIconComponent = RepeatDisabledIcon;
}
let replyButton = null;
@@ -201,7 +207,7 @@ class Footer extends ImmutablePureComponent {
return (
<div className='picture-in-picture__footer'>
{replyButton}
<IconButton className={classNames('status__action-bar-button', { reblogPrivate })} disabled={!publicStatus && !reblogPrivate} active={status.get('reblogged')} title={reblogTitle} icon='retweet' iconComponent={RepeatIcon} onClick={this.handleReblogClick} counter={status.get('reblogs_count')} />
<IconButton className={classNames('status__action-bar-button', { reblogPrivate })} disabled={!publicStatus && !reblogPrivate} active={status.get('reblogged')} title={reblogTitle} icon='retweet' iconComponent={reblogIconComponent} onClick={this.handleReblogClick} counter={status.get('reblogs_count')} />
<IconButton className='status__action-bar-button star-icon' animate active={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' iconComponent={StarIcon} onClick={this.handleFavouriteClick} counter={status.get('favourites_count')} />
{withOpenButton && <IconButton className='status__action-bar-button' title={intl.formatMessage(messages.open)} icon='external-link' iconComponent={OpenInNewIcon} onClick={this.handleOpenClick} href={status.get('url')} />}
</div>

View File

@@ -14,6 +14,7 @@ import { Link } from 'react-router-dom';
import { AnimatedNumber } from 'flavours/glitch/components/animated_number';
import AttachmentList from 'flavours/glitch/components/attachment_list';
import EditedTimestamp from 'flavours/glitch/components/edited_timestamp';
import { FilterWarning } from 'flavours/glitch/components/filter_warning';
import type { StatusLike } from 'flavours/glitch/components/hashtag_bar';
import { getHashtagBarForStatus } from 'flavours/glitch/components/hashtag_bar';
import { IconLogo } from 'flavours/glitch/components/logo';
@@ -72,6 +73,7 @@ export const DetailedStatus: React.FC<{
}) => {
const properStatus = status?.get('reblog') ?? status;
const [height, setHeight] = useState(0);
const [showDespiteFilter, setShowDespiteFilter] = useState(false);
const nodeRef = useRef<HTMLDivElement>();
const history = useAppHistory();
@@ -108,6 +110,10 @@ export const DetailedStatus: React.FC<{
[onOpenVideo, status],
);
const handleFilterToggle = useCallback(() => {
setShowDespiteFilter(!showDespiteFilter);
}, [showDespiteFilter, setShowDespiteFilter]);
const _measureHeight = useCallback(
(heightJustChanged?: boolean) => {
if (measureHeight && nodeRef.current) {
@@ -196,7 +202,7 @@ export const DetailedStatus: React.FC<{
) {
media.push(<AttachmentList media={status.get('media_attachments')} />);
} else if (
['image', 'gifv'].includes(
['image', 'gifv', 'unknown'].includes(
status.getIn(['media_attachments', 0, 'type']) as string,
) ||
status.get('media_attachments').size > 1
@@ -273,12 +279,12 @@ export const DetailedStatus: React.FC<{
);
mediaIcons.push('video-camera');
}
} else if (status.get('spoiler_text').length === 0) {
} else if (status.get('card')) {
media.push(
<Card
sensitive={status.get('sensitive')}
onOpenMedia={onOpenMedia}
card={status.get('card', null)}
card={status.get('card')}
/>,
);
mediaIcons.push('link');
@@ -358,6 +364,8 @@ export const DetailedStatus: React.FC<{
);
contentMedia.push(hashtagBar);
const matchedFilters = status.get('matched_filters');
return (
<div style={outerStyle}>
<div
@@ -386,22 +394,32 @@ export const DetailedStatus: React.FC<{
)}
</Permalink>
<StatusContent
status={status}
media={contentMedia}
extraMedia={extraMedia}
mediaIcons={contentMediaIcons}
expanded={expanded}
collapsed={false}
onExpandedToggle={onToggleHidden}
onTranslate={handleTranslate}
onUpdate={handleChildUpdate}
tagLinks={tagMisleadingLinks}
rewriteMentions={rewriteMentions}
parseClick={parseClick}
disabled
{...(statusContentProps as any)}
/>
{matchedFilters && (
<FilterWarning
title={matchedFilters.join(', ')}
expanded={showDespiteFilter}
onClick={handleFilterToggle}
/>
)}
{(!matchedFilters || showDespiteFilter) && (
<StatusContent
status={status}
media={contentMedia}
extraMedia={extraMedia}
mediaIcons={contentMediaIcons}
expanded={expanded}
collapsed={false}
onExpandedToggle={onToggleHidden}
onTranslate={handleTranslate}
onUpdate={handleChildUpdate}
tagLinks={tagMisleadingLinks}
rewriteMentions={rewriteMentions}
parseClick={parseClick}
disabled
{...(statusContentProps as any)}
/>
)}
<div className='detailed-status__meta'>
<div className='detailed-status__meta__line'>

View File

@@ -133,7 +133,7 @@ const makeMapStateToProps = () => {
});
const mapStateToProps = (state, props) => {
const status = getStatus(state, { id: props.params.statusId });
const status = getStatus(state, { id: props.params.statusId, contextType: 'detailed' });
let ancestorsIds = Immutable.List();
let descendantsIds = Immutable.List();

View File

@@ -116,6 +116,7 @@ export const MuteModal = ({ accountId, acct }) => {
<div className='safety-action-modal__bottom__collapsible'>
<div className='safety-action-modal__field-group'>
<RadioButtonLabel name='duration' value='0' label={intl.formatMessage(messages.indefinite)} currentValue={muteDuration} onChange={handleChangeMuteDuration} />
<RadioButtonLabel name='duration' value='21600' label={intl.formatMessage(messages.hours, { number: 6 })} currentValue={muteDuration} onChange={handleChangeMuteDuration} />
<RadioButtonLabel name='duration' value='86400' label={intl.formatMessage(messages.hours, { number: 24 })} currentValue={muteDuration} onChange={handleChangeMuteDuration} />
<RadioButtonLabel name='duration' value='604800' label={intl.formatMessage(messages.days, { number: 7 })} currentValue={muteDuration} onChange={handleChangeMuteDuration} />
<RadioButtonLabel name='duration' value='2592000' label={intl.formatMessage(messages.days, { number: 30 })} currentValue={muteDuration} onChange={handleChangeMuteDuration} />

View File

@@ -21,6 +21,7 @@ import { Permalink } from 'flavours/glitch/components/permalink';
import { PictureInPicture } from 'flavours/glitch/features/picture_in_picture';
import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context';
import { layoutFromWindow } from 'flavours/glitch/is_mobile';
import { selectUnreadNotificationGroupsCount } from 'flavours/glitch/selectors/notifications';
import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router';
import { uploadCompose, resetCompose, changeComposeSpoilerness } from '../../actions/compose';
@@ -90,7 +91,8 @@ const mapStateToProps = state => ({
hasMediaAttachments: state.getIn(['compose', 'media_attachments']).size > 0,
canUploadMore: !state.getIn(['compose', 'media_attachments']).some(x => ['audio', 'video'].includes(x.get('type'))) && state.getIn(['compose', 'media_attachments']).size < 4,
isWide: state.getIn(['local_settings', 'stretch']),
unreadNotifications: state.getIn(['notifications', 'unread']),
fullWidthColumns: state.getIn(['local_settings', 'fullwidth_columns']),
unreadNotifications: selectUnreadNotificationGroupsCount(state),
showFaviconBadge: state.getIn(['local_settings', 'notifications', 'favicon_badge']),
hicolorPrivacyIcons: state.getIn(['local_settings', 'hicolor_privacy_icons']),
moved: state.getIn(['accounts', me, 'moved']) && state.getIn(['accounts', state.getIn(['accounts', me, 'moved'])]),
@@ -269,6 +271,7 @@ class UI extends PureComponent {
dispatch: PropTypes.func.isRequired,
children: PropTypes.node,
isWide: PropTypes.bool,
fullWidthColumns: PropTypes.bool,
systemFontUi: PropTypes.bool,
isComposing: PropTypes.bool,
hasComposingText: PropTypes.bool,
@@ -607,6 +610,7 @@ class UI extends PureComponent {
const className = classNames('ui', {
'wide': isWide,
'fullwidth-columns': this.props.fullWidthColumns,
'system-font': this.props.systemFontUi,
'hicolor-privacy-icons': this.props.hicolorPrivacyIcons,
});

View File

@@ -90,6 +90,8 @@
"settings.enable_collapsed": "Enable collapsed toots",
"settings.enable_collapsed_hint": "Collapsed posts have parts of their contents hidden to take up less screen space. This is distinct from the Content Warning feature",
"settings.enable_content_warnings_auto_unfold": "Automatically unfold content-warnings",
"settings.fullwidth_view": "Stretch columns to full width (Desktop mode only)",
"settings.fullwidth_view_hint": "Stretches columns to fill all the available space.",
"settings.general": "General",
"settings.hicolor_privacy_icons": "High color privacy icons",
"settings.hicolor_privacy_icons.hint": "Display privacy icons in bright and easily distinguishable colors",
@@ -154,7 +156,5 @@
"status.is_poll": "This toot is a poll",
"status.local_only": "Only visible from your instance",
"status.show_filter_reason": "Show anyway",
"status.show_less": "Show less",
"status.show_more": "Show more",
"status.uncollapse": "Uncollapse"
}

View File

@@ -95,6 +95,9 @@ export const accountDefaultValues: AccountShape = {
limited: false,
moved: null,
hide_collections: false,
// This comes from `ApiMutedAccountJSON`, but we should eventually
// store that in a different object.
mute_expires_at: null,
};
const AccountFactory = ImmutableRecord<AccountShape>(accountDefaultValues);
@@ -147,5 +150,10 @@ export function createAccountFromServerJSON(serverJSON: ApiAccountJSON) {
),
note_emojified: emojify(accountJSON.note, emojiMap),
note_plain: unescapeHTML(accountJSON.note),
url:
accountJSON.url.startsWith('http://') ||
accountJSON.url.startsWith('https://')
? accountJSON.url
: accountJSON.uri,
});
}

View File

@@ -16,6 +16,7 @@ export const NOTIFICATIONS_GROUP_MAX_AVATARS = 8;
interface BaseNotificationGroup
extends Omit<BaseNotificationGroupJSON, 'sample_account_ids'> {
sampleAccountIds: string[];
partial: boolean;
}
interface BaseNotificationWithStatus<Type extends NotificationWithStatusType>
@@ -128,6 +129,7 @@ export function createNotificationGroupFromJSON(
return {
statusId: statusId ?? undefined,
sampleAccountIds,
partial: false,
...groupWithoutStatus,
};
}
@@ -136,12 +138,14 @@ export function createNotificationGroupFromJSON(
return {
report: createReportFromJSON(report),
sampleAccountIds,
partial: false,
...groupWithoutTargetAccount,
};
}
case 'severed_relationships':
return {
...group,
partial: false,
event: createAccountRelationshipSeveranceEventFromJSON(group.event),
sampleAccountIds,
};
@@ -150,13 +154,16 @@ export function createNotificationGroupFromJSON(
const { moderation_warning, ...groupWithoutModerationWarning } = group;
return {
...groupWithoutModerationWarning,
partial: false,
moderationWarning: createAccountWarningFromJSON(moderation_warning),
sampleAccountIds,
};
}
default:
return {
sampleAccountIds,
partial: false,
...group,
};
}
@@ -164,17 +171,17 @@ export function createNotificationGroupFromJSON(
export function createNotificationGroupFromNotificationJSON(
notification: ApiNotificationJSON,
) {
): NotificationGroup {
const group = {
sampleAccountIds: [notification.account.id],
group_key: notification.group_key,
notifications_count: 1,
type: notification.type,
most_recent_notification_id: notification.id,
page_min_id: notification.id,
page_max_id: notification.id,
latest_page_notification_at: notification.created_at,
} as NotificationGroup;
partial: true,
};
switch (notification.type) {
case 'favourite':
@@ -183,12 +190,21 @@ export function createNotificationGroupFromNotificationJSON(
case 'mention':
case 'poll':
case 'update':
return { ...group, statusId: notification.status?.id };
return {
...group,
type: notification.type,
statusId: notification.status?.id,
};
case 'admin.report':
return { ...group, report: createReportFromJSON(notification.report) };
return {
...group,
type: notification.type,
report: createReportFromJSON(notification.report),
};
case 'severed_relationships':
return {
...group,
type: notification.type,
event: createAccountRelationshipSeveranceEventFromJSON(
notification.event,
),
@@ -196,11 +212,15 @@ export function createNotificationGroupFromNotificationJSON(
case 'moderation_warning':
return {
...group,
type: notification.type,
moderationWarning: createAccountWarningFromJSON(
notification.moderation_warning,
),
};
default:
return group;
return {
...group,
type: notification.type,
};
}
}

View File

@@ -57,7 +57,10 @@ export const accountsReducer: Reducer<typeof initialState> = (
return state.setIn([action.payload.id, 'hidden'], false);
else if (importAccounts.match(action))
return normalizeAccounts(state, action.payload.accounts);
else if (followAccountSuccess.match(action)) {
else if (
followAccountSuccess.match(action) &&
!action.payload.alreadyFollowing
) {
return state
.update(action.payload.relationship.id, (account) =>
account?.update('followers_count', (n) => n + 1),

View File

@@ -4,11 +4,10 @@ import { ACCOUNT_LOOKUP_FAIL } from '../actions/accounts';
import { importAccounts } from '../actions/accounts_typed';
import { domain } from '../initial_state';
export const normalizeForLookup = str => {
str = str.toLowerCase();
const trailingIndex = str.indexOf(`@${domain.toLowerCase()}`);
return (trailingIndex > 0) ? str.slice(0, trailingIndex) : str;
};
const pattern = new RegExp(`@${domain}$`, 'gi');
export const normalizeForLookup = str =>
str.toLowerCase().replace(pattern, '');
const initialState = ImmutableMap();

View File

@@ -330,12 +330,26 @@ const expiresInFromExpiresAt = expires_at => {
const mergeLocalHashtagResults = (suggestions, prefix, tagHistory) => {
prefix = prefix.toLowerCase();
if (suggestions.length < 4) {
const localTags = tagHistory.filter(tag => tag.toLowerCase().startsWith(prefix) && !suggestions.some(suggestion => suggestion.type === 'hashtag' && suggestion.name.toLowerCase() === tag.toLowerCase()));
return suggestions.concat(localTags.slice(0, 4 - suggestions.length).toJS().map(tag => ({ type: 'hashtag', name: tag })));
} else {
return suggestions;
suggestions = suggestions.concat(localTags.slice(0, 4 - suggestions.length).toJS().map(tag => ({ type: 'hashtag', name: tag })));
}
// Prefer capitalization from personal history, unless personal history is all lower-case
const fixSuggestionCapitalization = (suggestion) => {
if (suggestion.type !== 'hashtag')
return suggestion;
const tagFromHistory = tagHistory.find((tag) => tag.localeCompare(suggestion.name, undefined, { sensitivity: 'accent' }) === 0);
if (!tagFromHistory || tagFromHistory.toLowerCase() === tagFromHistory)
return suggestion;
return { ...suggestion, name: tagFromHistory };
};
return suggestions.map(fixSuggestionCapitalization);
};
const normalizeSuggestions = (state, { accounts, emojis, tags, token }) => {

View File

@@ -6,6 +6,7 @@ import { LOCAL_SETTING_CHANGE, LOCAL_SETTING_DELETE } from 'flavours/glitch/acti
import { STORE_HYDRATE } from 'flavours/glitch/actions/store';
const initialState = ImmutableMap({
fullwidth_columns: false,
stretch : true,
side_arm : 'none',
side_arm_reply_mode : 'keep',

View File

@@ -21,7 +21,6 @@ import {
unmountNotifications,
refreshStaleNotificationGroups,
pollRecentNotifications,
shouldGroupNotificationType,
} from 'flavours/glitch/actions/notification_groups';
import {
disconnectTimeline,
@@ -30,6 +29,7 @@ import {
import type {
ApiNotificationJSON,
ApiNotificationGroupJSON,
NotificationType,
} from 'flavours/glitch/api_types/notifications';
import { compareId } from 'flavours/glitch/compare_id';
import { usePendingItems } from 'flavours/glitch/initial_state';
@@ -205,8 +205,9 @@ function mergeGapsAround(
function processNewNotification(
groups: NotificationGroupsState['groups'],
notification: ApiNotificationJSON,
groupedTypes: NotificationType[],
) {
if (!shouldGroupNotificationType(notification.type)) {
if (!groupedTypes.includes(notification.type)) {
notification = {
...notification,
group_key: `ungrouped-${notification.id}`,
@@ -476,11 +477,13 @@ export const notificationGroupsReducer = createReducer<NotificationGroupsState>(
trimNotifications(state);
})
.addCase(processNewNotificationForGroups.fulfilled, (state, action) => {
const notification = action.payload;
if (notification) {
if (action.payload) {
const { notification, groupedTypes } = action.payload;
processNewNotification(
usePendingItems ? state.pendingGroups : state.groups,
notification,
groupedTypes,
);
updateLastReadId(state);
trimNotifications(state);
@@ -531,10 +534,13 @@ export const notificationGroupsReducer = createReducer<NotificationGroupsState>(
if (existingGroupIndex > -1) {
const existingGroup = state.groups[existingGroupIndex];
if (existingGroup && existingGroup.type !== 'gap') {
group.notifications_count += existingGroup.notifications_count;
group.sampleAccountIds = group.sampleAccountIds
.concat(existingGroup.sampleAccountIds)
.slice(0, NOTIFICATIONS_GROUP_MAX_AVATARS);
if (group.partial) {
group.notifications_count +=
existingGroup.notifications_count;
group.sampleAccountIds = group.sampleAccountIds
.concat(existingGroup.sampleAccountIds)
.slice(0, NOTIFICATIONS_GROUP_MAX_AVATARS);
}
state.groups.splice(existingGroupIndex, 1);
}
}
@@ -559,7 +565,10 @@ export const notificationGroupsReducer = createReducer<NotificationGroupsState>(
compareId(state.lastReadId, mostRecentGroup.page_max_id) < 0
)
state.lastReadId = mostRecentGroup.page_max_id;
commitLastReadId(state);
// We don't call `commitLastReadId`, because that is conditional
// and we want to unconditionally update the state instead.
state.readMarkerId = state.lastReadId;
})
.addCase(fetchMarkers.fulfilled, (state, action) => {
if (

View File

@@ -79,6 +79,10 @@ const initialState = ImmutableMap({
'admin.sign_up': true,
'admin.report': true,
}),
group: ImmutableMap({
follow: true
}),
}),
firehose: ImmutableMap({

View File

@@ -0,0 +1,50 @@
import { createSelector } from '@reduxjs/toolkit';
import type { RootState } from 'flavours/glitch/store';
import { toServerSideType } from 'flavours/glitch/utils/filters';
// TODO: move to `app/javascript/flavours/glitch/models` and use more globally
type Filter = Immutable.Map<string, unknown>;
// TODO: move to `app/javascript/flavours/glitch/models` and use more globally
type FilterResult = Immutable.Map<string, unknown>;
export const getFilters = createSelector(
[
(state: RootState) => state.filters as Immutable.Map<string, Filter>,
(_, { contextType }: { contextType: string }) => contextType,
],
(filters, contextType) => {
if (!contextType) {
return null;
}
const now = new Date();
const serverSideType = toServerSideType(contextType);
return filters.filter((filter) => {
const context = filter.get('context') as Immutable.List<string>;
const expiration = filter.get('expires_at') as Date | null;
return (
context.includes(serverSideType) &&
(expiration === null || expiration > now)
);
});
},
);
export const getStatusHidden = (
state: RootState,
{ id, contextType }: { id: string; contextType: string },
) => {
const filters = getFilters(state, { contextType });
if (filters === null) return false;
const filtered = state.statuses.getIn([id, 'filtered']) as
| Immutable.List<FilterResult>
| undefined;
return filtered?.some(
(result) =>
filters.getIn([result.get('filter'), 'filter_action']) === 'hide',
);
};

View File

@@ -1,23 +1,12 @@
import { createSelector } from '@reduxjs/toolkit';
import { List as ImmutableList, Map as ImmutableMap } from 'immutable';
import { toServerSideType } from 'flavours/glitch/utils/filters';
import { me } from '../initial_state';
import { getFilters } from './filters';
export { makeGetAccount } from "./accounts";
const getFilters = createSelector([state => state.get('filters'), (_, { contextType }) => contextType], (filters, contextType) => {
if (!contextType) {
return null;
}
const now = new Date();
const serverSideType = toServerSideType(contextType);
return filters.filter(filter => filter.get('context').includes(serverSideType) && (filter.get('expires_at') === null || filter.get('expires_at') > now));
});
export const makeGetStatus = () => {
return createSelector(
[
@@ -26,9 +15,10 @@ export const makeGetStatus = () => {
(state, { id }) => state.getIn(['accounts', state.getIn(['statuses', id, 'account'])]),
(state, { id }) => state.getIn(['accounts', state.getIn(['statuses', state.getIn(['statuses', id, 'reblog']), 'account'])]),
getFilters,
(_, { contextType }) => ['detailed', 'bookmarks', 'favourites'].includes(contextType),
],
(statusBase, statusReblog, accountBase, accountReblog, filters) => {
(statusBase, statusReblog, accountBase, accountReblog, filters, warnInsteadOfHide) => {
if (!statusBase || statusBase.get('isLoading')) {
return null;
}
@@ -36,7 +26,7 @@ export const makeGetStatus = () => {
let filtered = false;
if ((accountReblog || accountBase).get('id') !== me && filters) {
let filterResults = statusReblog?.get('filtered') || statusBase.get('filtered') || ImmutableList();
if (filterResults.some((result) => filters.getIn([result.get('filter'), 'filter_action']) === 'hide')) {
if (!warnInsteadOfHide && filterResults.some((result) => filters.getIn([result.get('filter'), 'filter_action']) === 'hide')) {
return null;
}
filterResults = filterResults.filter(result => filters.has(result.get('filter')));

View File

@@ -52,4 +52,7 @@ export const selectSettingsNotificationsMinimizeFilteredBanner = (
) =>
state.settings.getIn(['notifications', 'minimizeFilteredBanner']) as boolean;
export const selectSettingsNotificationsGroupFollows = (state: RootState) =>
state.settings.getIn(['notifications', 'group', 'follow']) as boolean;
/* eslint-enable @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access */

View File

@@ -1050,6 +1050,12 @@ a.name-tag,
color: var(--user-role-accent);
}
.applications-list {
.icon {
vertical-align: middle;
}
}
.announcements-list,
.filters-list {
border: 1px solid var(--background-border-color);

View File

@@ -817,6 +817,10 @@ body > [data-popper-placement] {
flex: 0 1 auto;
padding-top: 0;
padding-bottom: 0;
.icon {
vertical-align: middle;
}
}
}
@@ -2941,6 +2945,7 @@ a.account__display-name {
flex: 0 1 auto;
display: flex;
flex-direction: column;
contain: inline-size layout paint style;
@media screen and (min-width: $no-gap-breakpoint) {
max-width: 600px;
@@ -4237,6 +4242,7 @@ input.glitch-setting-text {
overflow: hidden;
border: 1px solid var(--background-border-color);
border-radius: 8px;
contain: inline-size layout paint style;
&.bottomless {
border-radius: 8px 8px 0 0;
@@ -6275,6 +6281,7 @@ a.status-card {
pointer-events: auto;
user-select: text;
display: flex;
max-width: 100vw;
@media screen and (width <= $mobile-breakpoint) {
margin-top: auto;
@@ -7470,6 +7477,13 @@ img.modal-warning {
}
}
.fullwidth-columns .columns-area:not(.columns-area--mobile) {
.column {
flex: auto;
max-width: unset;
}
}
.media-gallery__actions {
position: absolute;
top: 6px;
@@ -7637,6 +7651,8 @@ img.modal-warning {
}
&--layout-3 {
min-height: calc(64px * 2 + 8px);
& > .media-gallery__item:nth-child(1) {
border-end-end-radius: 0;
border-start-end-radius: 0;
@@ -7656,6 +7672,8 @@ img.modal-warning {
}
&--layout-4 {
min-height: calc(64px * 2 + 8px);
& > .media-gallery__item:nth-child(1) {
border-end-end-radius: 0;
border-start-end-radius: 0;
@@ -8525,79 +8543,23 @@ noscript {
background: rgba($base-overlay-background, 0.5);
}
.list-adder,
.list-editor {
background: $ui-base-color;
backdrop-filter: var(--background-filter);
background: var(--modal-background-color);
border: 1px solid var(--modal-border-color);
flex-direction: column;
border-radius: 8px;
box-shadow: 2px 4px 15px rgba($base-shadow-color, 0.4);
width: 380px;
overflow: hidden;
@media screen and (width <= 420px) {
width: 90%;
}
h4 {
padding: 15px 0;
background: lighten($ui-base-color, 13%);
font-weight: 500;
font-size: 16px;
text-align: center;
border-radius: 8px 8px 0 0;
}
.drawer__pager {
height: 50vh;
border-radius: 4px;
}
.drawer__inner {
border-radius: 0 0 8px 8px;
&.backdrop {
width: calc(100% - 60px);
box-shadow: 2px 4px 15px rgba($base-shadow-color, 0.4);
border-radius: 0 0 0 8px;
}
}
&__accounts {
overflow-y: auto;
}
.account__display-name {
&:hover strong {
text-decoration: none;
}
}
.account__avatar {
cursor: default;
}
.search {
margin-bottom: 0;
}
}
.list-adder {
background: $ui-base-color;
flex-direction: column;
border-radius: 8px;
box-shadow: 2px 4px 15px rgba($base-shadow-color, 0.4);
width: 380px;
overflow: hidden;
@media screen and (width <= 420px) {
width: 90%;
}
&__account {
background: lighten($ui-base-color, 13%);
}
&__lists {
background: lighten($ui-base-color, 13%);
height: 50vh;
border-radius: 0 0 8px 8px;
overflow-y: auto;
@@ -8618,6 +8580,52 @@ noscript {
text-decoration: none;
font-size: 16px;
padding: 10px;
display: flex;
align-items: center;
gap: 4px;
}
}
.list-editor {
h4 {
padding: 15px 0;
background: lighten($ui-base-color, 13%);
font-weight: 500;
font-size: 16px;
text-align: center;
border-radius: 8px 8px 0 0;
}
.drawer__pager {
height: 50vh;
border: 0;
}
.drawer__inner {
&.backdrop {
width: calc(100% - 60px);
box-shadow: 2px 4px 15px rgba($base-shadow-color, 0.4);
border-radius: 0 0 0 8px;
}
}
&__accounts {
background: unset;
overflow-y: auto;
}
.account__display-name {
&:hover strong {
text-decoration: none;
}
}
.account__avatar {
cursor: default;
}
.search {
margin-bottom: 0;
}
}
@@ -11364,21 +11372,17 @@ noscript {
color: $darker-text-color;
-webkit-line-clamp: 4;
-webkit-box-orient: vertical;
max-height: 4 * 22px;
max-height: none;
overflow: hidden;
p {
display: none;
&:first-child {
display: initial;
}
}
p,
a {
color: inherit;
}
p {
margin-bottom: 8px;
}
}
.reply-indicator__attachments {
@@ -11662,19 +11666,21 @@ noscript {
}
.content-warning {
display: block;
box-sizing: border-box;
background: rgba($ui-highlight-color, 0.05);
color: $secondary-text-color;
border-top: 1px solid;
border-bottom: 1px solid;
border-color: rgba($ui-highlight-color, 0.15);
border: 1px solid rgba($ui-highlight-color, 0.15);
border-radius: 8px;
padding: 8px (5px + 8px);
position: relative;
font-size: 15px;
line-height: 22px;
cursor: pointer;
p {
margin-bottom: 8px;
font-weight: 500;
}
.link-button {
@@ -11683,31 +11689,16 @@ noscript {
font-weight: 500;
}
&::before,
&::after {
content: '';
display: block;
position: absolute;
height: 100%;
background: url('~images/warning-stripes.svg') repeat-y;
width: 5px;
top: 0;
}
&--filter {
color: $darker-text-color;
&::before {
border-start-start-radius: 4px;
border-end-start-radius: 4px;
inset-inline-start: 0;
}
p {
font-weight: normal;
}
&::after {
border-start-end-radius: 4px;
border-end-end-radius: 4px;
inset-inline-end: 0;
}
&--filter::before,
&--filter::after {
background-image: url('~images/filter-stripes.svg');
.filter-name {
font-weight: 500;
color: $secondary-text-color;
}
}
}

View File

@@ -23,6 +23,8 @@ code {
position: relative;
overflow: hidden;
height: 160px;
max-width: 566px;
margin-inline: auto;
&::after {
content: '';
@@ -666,6 +668,10 @@ code {
}
}
}
.status-card {
contain: unset;
}
}
.block-icon {

View File

@@ -90,6 +90,10 @@ body.rtl {
direction: rtl;
}
.column-back-button__icon {
transform: scale(-1, 1);
}
.simple_form select {
background: $ui-base-color
url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14.933 18.467' height='19.698' width='15.929'><path d='M3.467 14.967l-3.393-3.5H14.86l-3.392 3.5c-1.866 1.925-3.666 3.5-4 3.5-.335 0-2.135-1.575-4-3.5zm.266-11.234L7.467 0 11.2 3.733l3.733 3.734H0l3.733-3.734z' fill='#{hex-color(lighten($ui-base-color, 12%))}'/></svg>")

View File

@@ -84,6 +84,7 @@
width: 100%;
.account {
max-width: calc(56px + 30ch);
padding: 0;
border: 0;
}

View File

@@ -6,6 +6,11 @@ export const toServerSideType = (columnType: string) => {
case 'thread':
case 'account':
return columnType;
case 'detailed':
return 'thread';
case 'bookmarks':
case 'favourites':
return 'home';
default:
if (columnType.includes('list:')) {
return 'home';

View File

@@ -80,6 +80,17 @@ export function normalizeStatus(status, normalOldStatus) {
normalStatus.contentHtml = emojify(normalStatus.content, emojiMap);
normalStatus.spoilerHtml = emojify(escapeTextContentForBrowser(spoilerText), emojiMap);
normalStatus.hidden = expandSpoilers ? false : spoilerText.length > 0 || normalStatus.sensitive;
if (normalStatus.url && !(normalStatus.url.startsWith('http://') || normalStatus.url.startsWith('https://'))) {
normalStatus.url = null;
}
normalStatus.url ||= normalStatus.uri;
normalStatus.media_attachments.forEach(item => {
if (item.remote_url && !(item.remote_url.startsWith('http://') || item.remote_url.startsWith('https://')))
item.remote_url = null;
});
}
if (normalOldStatus) {

View File

@@ -8,6 +8,7 @@ import type { ApiAccountJSON } from 'mastodon/api_types/accounts';
import type {
ApiNotificationGroupJSON,
ApiNotificationJSON,
NotificationType,
} from 'mastodon/api_types/notifications';
import { allNotificationTypes } from 'mastodon/api_types/notifications';
import type { ApiStatusJSON } from 'mastodon/api_types/statuses';
@@ -15,6 +16,7 @@ import { usePendingItems } from 'mastodon/initial_state';
import type { NotificationGap } from 'mastodon/reducers/notification_groups';
import {
selectSettingsNotificationsExcludedTypes,
selectSettingsNotificationsGroupFollows,
selectSettingsNotificationsQuickFilterActive,
selectSettingsNotificationsShows,
} from 'mastodon/selectors/settings';
@@ -68,17 +70,19 @@ function dispatchAssociatedRecords(
dispatch(importFetchedStatuses(fetchedStatuses));
}
const supportedGroupedNotificationTypes = ['favourite', 'reblog'];
function selectNotificationGroupedTypes(state: RootState) {
const types: NotificationType[] = ['favourite', 'reblog'];
export function shouldGroupNotificationType(type: string) {
return supportedGroupedNotificationTypes.includes(type);
if (selectSettingsNotificationsGroupFollows(state)) types.push('follow');
return types;
}
export const fetchNotifications = createDataLoadingThunk(
'notificationGroups/fetch',
async (_params, { getState }) =>
apiFetchNotificationGroups({
grouped_types: supportedGroupedNotificationTypes,
grouped_types: selectNotificationGroupedTypes(getState()),
exclude_types: getExcludedTypes(getState()),
}),
({ notifications, accounts, statuses }, { dispatch }) => {
@@ -102,7 +106,7 @@ export const fetchNotificationsGap = createDataLoadingThunk(
'notificationGroups/fetchGap',
async (params: { gap: NotificationGap }, { getState }) =>
apiFetchNotificationGroups({
grouped_types: supportedGroupedNotificationTypes,
grouped_types: selectNotificationGroupedTypes(getState()),
max_id: params.gap.maxId,
exclude_types: getExcludedTypes(getState()),
}),
@@ -119,7 +123,7 @@ export const pollRecentNotifications = createDataLoadingThunk(
'notificationGroups/pollRecentNotifications',
async (_params, { getState }) => {
return apiFetchNotificationGroups({
grouped_types: supportedGroupedNotificationTypes,
grouped_types: selectNotificationGroupedTypes(getState()),
max_id: undefined,
exclude_types: getExcludedTypes(getState()),
// In slow mode, we don't want to include notifications that duplicate the already-displayed ones
@@ -137,6 +141,9 @@ export const pollRecentNotifications = createDataLoadingThunk(
return { notifications };
},
{
useLoadingBar: false,
},
);
export const processNewNotificationForGroups = createAppAsyncThunk(
@@ -148,7 +155,7 @@ export const processNewNotificationForGroups = createAppAsyncThunk(
const showInColumn =
activeFilter === 'all'
? notificationShows[notification.type]
? notificationShows[notification.type] !== false
: activeFilter === notification.type;
if (!showInColumn) return;
@@ -168,7 +175,10 @@ export const processNewNotificationForGroups = createAppAsyncThunk(
dispatchAssociatedRecords(dispatch, [notification]);
return notification;
return {
notification,
groupedTypes: selectNotificationGroupedTypes(state),
};
},
);

View File

@@ -13,7 +13,7 @@ export interface ApiAccountRoleJSON {
}
// See app/serializers/rest/account_serializer.rb
export interface ApiAccountJSON {
export interface BaseApiAccountJSON {
acct: string;
avatar: string;
avatar_static: string;
@@ -45,3 +45,12 @@ export interface ApiAccountJSON {
memorial?: boolean;
hide_collections: boolean;
}
// See app/serializers/rest/muted_account_serializer.rb
export interface ApiMutedAccountJSON extends BaseApiAccountJSON {
mute_expires_at?: string | null;
}
// For now, we have the same type representing both `Account` and `MutedAccount`
// objects, but we should refactor this in the future.
export type ApiAccountJSON = ApiMutedAccountJSON;

View File

@@ -8,7 +8,7 @@ export const ContentWarning: React.FC<{
<StatusBanner
expanded={expanded}
onClick={onClick}
variant={BannerVariant.Yellow}
variant={BannerVariant.Warning}
>
<p dangerouslySetInnerHTML={{ __html: text }} />
</StatusBanner>

View File

@@ -10,13 +10,16 @@ export const FilterWarning: React.FC<{
<StatusBanner
expanded={expanded}
onClick={onClick}
variant={BannerVariant.Blue}
variant={BannerVariant.Filter}
>
<p>
<FormattedMessage
id='filter_warning.matches_filter'
defaultMessage='Matches filter “{title}”'
values={{ title }}
defaultMessage='Matches filter “<span>{title}</span>”'
values={{
title,
span: (chunks) => <span className='filter-name'>{chunks}</span>,
}}
/>
</p>
</StatusBanner>

View File

@@ -97,12 +97,12 @@ class Item extends PureComponent {
height = 50;
}
if (attachment.get('description')?.length > 0) {
badges.push(<AltTextBadge key='alt' description={attachment.get('description')} />);
}
const description = attachment.getIn(['translation', 'description']) || attachment.get('description');
if (description?.length > 0) {
badges.push(<AltTextBadge key='alt' description={description} />);
}
if (attachment.get('type') === 'unknown') {
return (
<div className={classNames('media-gallery__item', { standalone, 'media-gallery__item--tall': height === 100, 'media-gallery__item--wide': width === 100 })} key={attachment.get('id')}>

View File

@@ -330,7 +330,7 @@ class Status extends ImmutablePureComponent {
const { onToggleHidden } = this.props;
const status = this._properStatus();
if (status.get('matched_filters')) {
if (this.props.status.get('matched_filters')) {
const expandedBecauseOfCW = !status.get('hidden') || status.get('spoiler_text').length === 0;
const expandedBecauseOfFilter = this.state.showDespiteFilter;
@@ -449,7 +449,7 @@ class Status extends ImmutablePureComponent {
} else if (status.get('media_attachments').size > 0) {
const language = status.getIn(['translation', 'language']) || status.get('language');
if (['image', 'gifv'].includes(status.getIn(['media_attachments', 0, 'type'])) || status.get('media_attachments').size > 1) {
if (['image', 'gifv', 'unknown'].includes(status.getIn(['media_attachments', 0, 'type'])) || status.get('media_attachments').size > 1) {
media = (
<Bundle fetchComponent={MediaGallery} loading={this.renderLoadingMediaGallery}>
{Component => (
@@ -520,7 +520,7 @@ class Status extends ImmutablePureComponent {
</Bundle>
);
}
} else if (status.get('spoiler_text').length === 0 && status.get('card')) {
} else if (status.get('card')) {
media = (
<Card
onOpenMedia={this.handleOpenMedia}

View File

@@ -264,7 +264,7 @@ class StatusActionBar extends ImmutablePureComponent {
menu.push({ text: intl.formatMessage(messages.share), action: this.handleShareClick });
}
if (publicStatus && (signedIn || !isRemote)) {
if (publicStatus && !isRemote) {
menu.push({ text: intl.formatMessage(messages.embed), action: this.handleEmbed });
}

View File

@@ -1,8 +1,8 @@
import { FormattedMessage } from 'react-intl';
export enum BannerVariant {
Yellow = 'yellow',
Blue = 'blue',
Warning = 'warning',
Filter = 'filter',
}
export const StatusBanner: React.FC<{
@@ -11,9 +11,9 @@ export const StatusBanner: React.FC<{
expanded?: boolean;
onClick?: () => void;
}> = ({ children, variant, expanded, onClick }) => (
<div
<label
className={
variant === BannerVariant.Yellow
variant === BannerVariant.Warning
? 'content-warning'
: 'content-warning content-warning--filter'
}
@@ -26,6 +26,11 @@ export const StatusBanner: React.FC<{
id='content_warning.hide'
defaultMessage='Hide post'
/>
) : variant === BannerVariant.Warning ? (
<FormattedMessage
id='content_warning.show_more'
defaultMessage='Show more'
/>
) : (
<FormattedMessage
id='content_warning.show'
@@ -33,5 +38,5 @@ export const StatusBanner: React.FC<{
/>
)}
</button>
</div>
</label>
);

View File

@@ -99,6 +99,7 @@ class Bookmarks extends ImmutablePureComponent {
onLoadMore={this.handleLoadMore}
emptyMessage={emptyMessage}
bindToDocument={!multiColumn}
timelineId='bookmarks'
/>
<Helmet>

View File

@@ -21,9 +21,11 @@ class ColumnSettings extends PureComponent {
return (
<div className='column-settings'>
<div className='column-settings__row'>
<SettingToggle settings={settings} settingPath={['other', 'onlyMedia']} onChange={onChange} label={<FormattedMessage id='community.column_settings.media_only' defaultMessage='Media only' />} />
</div>
<section>
<div className='column-settings__row'>
<SettingToggle settings={settings} settingPath={['other', 'onlyMedia']} onChange={onChange} label={<FormattedMessage id='community.column_settings.media_only' defaultMessage='Media only' />} />
</div>
</section>
</div>
);
}

View File

@@ -22,23 +22,23 @@ describe('emoji', () => {
it('does unicode', () => {
expect(emojify('\uD83D\uDC69\u200D\uD83D\uDC69\u200D\uD83D\uDC66\u200D\uD83D\uDC66')).toEqual(
'<picture><img draggable="false" class="emojione" alt="👩‍👩‍👦‍👦" title=":woman-woman-boy-boy:" src="/emoji/1f469-200d-1f469-200d-1f466-200d-1f466.svg"></picture>');
'<img draggable="false" class="emojione" alt="👩‍👩‍👦‍👦" title=":woman-woman-boy-boy:" src="/emoji/1f469-200d-1f469-200d-1f466-200d-1f466.svg">');
expect(emojify('👨‍👩‍👧‍👧')).toEqual(
'<picture><img draggable="false" class="emojione" alt="👨‍👩‍👧‍👧" title=":man-woman-girl-girl:" src="/emoji/1f468-200d-1f469-200d-1f467-200d-1f467.svg"></picture>');
expect(emojify('👩‍👩‍👦')).toEqual('<picture><img draggable="false" class="emojione" alt="👩‍👩‍👦" title=":woman-woman-boy:" src="/emoji/1f469-200d-1f469-200d-1f466.svg"></picture>');
'<img draggable="false" class="emojione" alt="👨‍👩‍👧‍👧" title=":man-woman-girl-girl:" src="/emoji/1f468-200d-1f469-200d-1f467-200d-1f467.svg">');
expect(emojify('👩‍👩‍👦')).toEqual('<img draggable="false" class="emojione" alt="👩‍👩‍👦" title=":woman-woman-boy:" src="/emoji/1f469-200d-1f469-200d-1f466.svg">');
expect(emojify('\u2757')).toEqual(
'<picture><img draggable="false" class="emojione" alt="❗" title=":exclamation:" src="/emoji/2757.svg"></picture>');
'<img draggable="false" class="emojione" alt="❗" title=":exclamation:" src="/emoji/2757.svg">');
});
it('does multiple unicode', () => {
expect(emojify('\u2757 #\uFE0F\u20E3')).toEqual(
'<picture><img draggable="false" class="emojione" alt="❗" title=":exclamation:" src="/emoji/2757.svg"></picture> <picture><img draggable="false" class="emojione" alt="#️⃣" title=":hash:" src="/emoji/23-20e3.svg"></picture>');
'<img draggable="false" class="emojione" alt="❗" title=":exclamation:" src="/emoji/2757.svg"> <img draggable="false" class="emojione" alt="#️⃣" title=":hash:" src="/emoji/23-20e3.svg">');
expect(emojify('\u2757#\uFE0F\u20E3')).toEqual(
'<picture><img draggable="false" class="emojione" alt="❗" title=":exclamation:" src="/emoji/2757.svg"></picture><picture><img draggable="false" class="emojione" alt="#️⃣" title=":hash:" src="/emoji/23-20e3.svg"></picture>');
'<img draggable="false" class="emojione" alt="❗" title=":exclamation:" src="/emoji/2757.svg"><img draggable="false" class="emojione" alt="#️⃣" title=":hash:" src="/emoji/23-20e3.svg">');
expect(emojify('\u2757 #\uFE0F\u20E3 \u2757')).toEqual(
'<picture><img draggable="false" class="emojione" alt="❗" title=":exclamation:" src="/emoji/2757.svg"></picture> <picture><img draggable="false" class="emojione" alt="#️⃣" title=":hash:" src="/emoji/23-20e3.svg"></picture> <picture><img draggable="false" class="emojione" alt="❗" title=":exclamation:" src="/emoji/2757.svg"></picture>');
'<img draggable="false" class="emojione" alt="❗" title=":exclamation:" src="/emoji/2757.svg"> <img draggable="false" class="emojione" alt="#️⃣" title=":hash:" src="/emoji/23-20e3.svg"> <img draggable="false" class="emojione" alt="❗" title=":exclamation:" src="/emoji/2757.svg">');
expect(emojify('foo \u2757 #\uFE0F\u20E3 bar')).toEqual(
'foo <picture><img draggable="false" class="emojione" alt="❗" title=":exclamation:" src="/emoji/2757.svg"></picture> <picture><img draggable="false" class="emojione" alt="#️⃣" title=":hash:" src="/emoji/23-20e3.svg"></picture> bar');
'foo <img draggable="false" class="emojione" alt="❗" title=":exclamation:" src="/emoji/2757.svg"> <img draggable="false" class="emojione" alt="#️⃣" title=":hash:" src="/emoji/23-20e3.svg"> bar');
});
it('ignores unicode inside of tags', () => {
@@ -46,16 +46,16 @@ describe('emoji', () => {
});
it('does multiple emoji properly (issue 5188)', () => {
expect(emojify('👌🌈💕')).toEqual('<picture><img draggable="false" class="emojione" alt="👌" title=":ok_hand:" src="/emoji/1f44c.svg"></picture><picture><img draggable="false" class="emojione" alt="🌈" title=":rainbow:" src="/emoji/1f308.svg"></picture><picture><img draggable="false" class="emojione" alt="💕" title=":two_hearts:" src="/emoji/1f495.svg"></picture>');
expect(emojify('👌 🌈 💕')).toEqual('<picture><img draggable="false" class="emojione" alt="👌" title=":ok_hand:" src="/emoji/1f44c.svg"></picture> <picture><img draggable="false" class="emojione" alt="🌈" title=":rainbow:" src="/emoji/1f308.svg"></picture> <picture><img draggable="false" class="emojione" alt="💕" title=":two_hearts:" src="/emoji/1f495.svg"></picture>');
expect(emojify('👌🌈💕')).toEqual('<img draggable="false" class="emojione" alt="👌" title=":ok_hand:" src="/emoji/1f44c.svg"><img draggable="false" class="emojione" alt="🌈" title=":rainbow:" src="/emoji/1f308.svg"><img draggable="false" class="emojione" alt="💕" title=":two_hearts:" src="/emoji/1f495.svg">');
expect(emojify('👌 🌈 💕')).toEqual('<img draggable="false" class="emojione" alt="👌" title=":ok_hand:" src="/emoji/1f44c.svg"> <img draggable="false" class="emojione" alt="🌈" title=":rainbow:" src="/emoji/1f308.svg"> <img draggable="false" class="emojione" alt="💕" title=":two_hearts:" src="/emoji/1f495.svg">');
});
it('does an emoji that has no shortcode', () => {
expect(emojify('👁‍🗨')).toEqual('<picture><img draggable="false" class="emojione" alt="👁‍🗨" title="" src="/emoji/1f441-200d-1f5e8.svg"></picture>');
expect(emojify('👁‍🗨')).toEqual('<img draggable="false" class="emojione" alt="👁‍🗨" title="" src="/emoji/1f441-200d-1f5e8.svg">');
});
it('does an emoji whose filename is irregular', () => {
expect(emojify('↙️')).toEqual('<picture><img draggable="false" class="emojione" alt="↙️" title=":arrow_lower_left:" src="/emoji/2199.svg"></picture>');
expect(emojify('↙️')).toEqual('<img draggable="false" class="emojione" alt="↙️" title=":arrow_lower_left:" src="/emoji/2199.svg">');
});
it('avoid emojifying on invisible text', () => {
@@ -67,11 +67,11 @@ describe('emoji', () => {
it('avoid emojifying on invisible text with nested tags', () => {
expect(emojify('<span class="invisible">😄<span class="foo">bar</span>😴</span>😇'))
.toEqual('<span class="invisible">😄<span class="foo">bar</span>😴</span><picture><img draggable="false" class="emojione" alt="😇" title=":innocent:" src="/emoji/1f607.svg"></picture>');
.toEqual('<span class="invisible">😄<span class="foo">bar</span>😴</span><img draggable="false" class="emojione" alt="😇" title=":innocent:" src="/emoji/1f607.svg">');
expect(emojify('<span class="invisible">😄<span class="invisible">😕</span>😴</span>😇'))
.toEqual('<span class="invisible">😄<span class="invisible">😕</span>😴</span><picture><img draggable="false" class="emojione" alt="😇" title=":innocent:" src="/emoji/1f607.svg"></picture>');
.toEqual('<span class="invisible">😄<span class="invisible">😕</span>😴</span><img draggable="false" class="emojione" alt="😇" title=":innocent:" src="/emoji/1f607.svg">');
expect(emojify('<span class="invisible">😄<br>😴</span>😇'))
.toEqual('<span class="invisible">😄<br>😴</span><picture><img draggable="false" class="emojione" alt="😇" title=":innocent:" src="/emoji/1f607.svg"></picture>');
.toEqual('<span class="invisible">😄<br>😴</span><img draggable="false" class="emojione" alt="😇" title=":innocent:" src="/emoji/1f607.svg">');
});
it('does not emojify emojis with textual presentation VS15 character', () => {
@@ -81,17 +81,17 @@ describe('emoji', () => {
it('does a simple emoji properly', () => {
expect(emojify('♀♂'))
.toEqual('<picture><img draggable="false" class="emojione" alt="♀" title=":female_sign:" src="/emoji/2640.svg"></picture><picture><img draggable="false" class="emojione" alt="♂" title=":male_sign:" src="/emoji/2642.svg"></picture>');
.toEqual('<img draggable="false" class="emojione" alt="♀" title=":female_sign:" src="/emoji/2640.svg"><img draggable="false" class="emojione" alt="♂" title=":male_sign:" src="/emoji/2642.svg">');
});
it('does an emoji containing ZWJ properly', () => {
expect(emojify('💂‍♀️💂‍♂️'))
.toEqual('<picture><img draggable="false" class="emojione" alt="💂\u200D♀" title=":female-guard:" src="/emoji/1f482-200d-2640-fe0f_border.svg"></picture><picture><img draggable="false" class="emojione" alt="💂\u200D♂" title=":male-guard:" src="/emoji/1f482-200d-2642-fe0f_border.svg"></picture>');
.toEqual('<img draggable="false" class="emojione" alt="💂\u200D♀" title=":female-guard:" src="/emoji/1f482-200d-2640-fe0f_border.svg"><img draggable="false" class="emojione" alt="💂\u200D♂" title=":male-guard:" src="/emoji/1f482-200d-2642-fe0f_border.svg">');
});
it('keeps ordering as expected (issue fixed by PR 20677)', () => {
expect(emojify('<p>💕 <a class="hashtag" href="https://example.com/tags/foo" rel="nofollow noopener noreferrer" target="_blank">#<span>foo</span></a> test: foo.</p>'))
.toEqual('<p><picture><img draggable="false" class="emojione" alt="💕" title=":two_hearts:" src="/emoji/1f495.svg"></picture> <a class="hashtag" href="https://example.com/tags/foo" rel="nofollow noopener noreferrer" target="_blank">#<span>foo</span></a> test: foo.</p>');
expect(emojify('<p>💕 <a class="hashtag" href="https://example.com/tags/foo" rel="nofollow noopener" target="_blank">#<span>foo</span></a> test: foo.</p>'))
.toEqual('<p><img draggable="false" class="emojione" alt="💕" title=":two_hearts:" src="/emoji/1f495.svg"> <a class="hashtag" href="https://example.com/tags/foo" rel="nofollow noopener" target="_blank">#<span>foo</span></a> test: foo.</p>');
});
});
});

View File

@@ -97,30 +97,30 @@ const emojifyTextNode = (node, customEmojis) => {
const { filename, shortCode } = unicodeMapping[unicode_emoji];
const title = shortCode ? `:${shortCode}:` : '';
replacement = document.createElement('picture');
const isSystemTheme = !!document.body?.classList.contains('theme-system');
if(isSystemTheme) {
let source = document.createElement('source');
source.setAttribute('media', '(prefers-color-scheme: dark)');
source.setAttribute('srcset', `${assetHost}/emoji/${emojiFilename(filename, "dark")}.svg`);
replacement.appendChild(source);
}
const theme = (isSystemTheme || document.body?.classList.contains('theme-mastodon-light')) ? 'light' : 'dark';
let img = document.createElement('img');
const imageFilename = emojiFilename(filename, theme);
const img = document.createElement('img');
img.setAttribute('draggable', 'false');
img.setAttribute('class', 'emojione');
img.setAttribute('alt', unicode_emoji);
img.setAttribute('title', title);
img.setAttribute('src', `${assetHost}/emoji/${imageFilename}.svg`);
let theme = "light";
if (isSystemTheme && imageFilename !== emojiFilename(filename, 'dark')) {
replacement = document.createElement('picture');
if(!isSystemTheme && !document.body?.classList.contains('theme-mastodon-light'))
theme = "dark";
img.setAttribute('src', `${assetHost}/emoji/${emojiFilename(filename, theme)}.svg`);
replacement.appendChild(img);
const source = document.createElement('source');
source.setAttribute('media', '(prefers-color-scheme: dark)');
source.setAttribute('srcset', `${assetHost}/emoji/${emojiFilename(filename, 'dark')}.svg`);
replacement.appendChild(source);
replacement.appendChild(img);
} else {
replacement = img;
}
}
// Add the processed-up-to-now string and the emoji replacement
@@ -135,7 +135,7 @@ const emojifyTextNode = (node, customEmojis) => {
};
const emojifyNode = (node, customEmojis) => {
for (const child of node.childNodes) {
for (const child of Array.from(node.childNodes)) {
switch(child.nodeType) {
case Node.TEXT_NODE:
emojifyTextNode(child, customEmojis);

View File

@@ -99,6 +99,7 @@ class Favourites extends ImmutablePureComponent {
onLoadMore={this.handleLoadMore}
emptyMessage={emptyMessage}
bindToDocument={!multiColumn}
timelineId='favourites'
/>
<Helmet>

View File

@@ -129,8 +129,13 @@ export const InlineFollowSuggestions = ({ hidden }) => {
return;
}
setCanScrollLeft(bodyRef.current.scrollLeft > 0);
setCanScrollRight((bodyRef.current.scrollLeft + bodyRef.current.clientWidth) < bodyRef.current.scrollWidth);
if (getComputedStyle(bodyRef.current).direction === 'rtl') {
setCanScrollLeft((bodyRef.current.clientWidth - bodyRef.current.scrollLeft) < bodyRef.current.scrollWidth);
setCanScrollRight(bodyRef.current.scrollLeft < 0);
} else {
setCanScrollLeft(bodyRef.current.scrollLeft > 0);
setCanScrollRight((bodyRef.current.scrollLeft + bodyRef.current.clientWidth) < bodyRef.current.scrollWidth);
}
}, [setCanScrollRight, setCanScrollLeft, bodyRef, suggestions]);
const handleLeftNav = useCallback(() => {
@@ -146,8 +151,13 @@ export const InlineFollowSuggestions = ({ hidden }) => {
return;
}
setCanScrollLeft(bodyRef.current.scrollLeft > 0);
setCanScrollRight((bodyRef.current.scrollLeft + bodyRef.current.clientWidth) < bodyRef.current.scrollWidth);
if (getComputedStyle(bodyRef.current).direction === 'rtl') {
setCanScrollLeft((bodyRef.current.clientWidth - bodyRef.current.scrollLeft) < bodyRef.current.scrollWidth);
setCanScrollRight(bodyRef.current.scrollLeft < 0);
} else {
setCanScrollLeft(bodyRef.current.scrollLeft > 0);
setCanScrollRight((bodyRef.current.scrollLeft + bodyRef.current.clientWidth) < bodyRef.current.scrollWidth);
}
}, [setCanScrollRight, setCanScrollLeft, bodyRef]);
const handleDismiss = useCallback(() => {

View File

@@ -38,6 +38,7 @@ class ColumnSettings extends PureComponent {
const alertStr = <FormattedMessage id='notifications.column_settings.alert' defaultMessage='Desktop notifications' />;
const showStr = <FormattedMessage id='notifications.column_settings.show' defaultMessage='Show in column' />;
const soundStr = <FormattedMessage id='notifications.column_settings.sound' defaultMessage='Play sound' />;
const groupStr = <FormattedMessage id='notifications.column_settings.group' defaultMessage='Group' />;
const showPushSettings = pushSettings.get('browserSupport') && pushSettings.get('isSubscribed');
const pushStr = showPushSettings && <FormattedMessage id='notifications.column_settings.push' defaultMessage='Push notifications' />;
@@ -94,6 +95,7 @@ class ColumnSettings extends PureComponent {
{showPushSettings && <SettingToggle prefix='notifications_push' settings={pushSettings} settingPath={['alerts', 'follow']} onChange={this.onPushChange} label={pushStr} />}
<SettingToggle prefix='notifications' settings={settings} settingPath={['shows', 'follow']} onChange={onChange} label={showStr} />
<SettingToggle prefix='notifications' settings={settings} settingPath={['sounds', 'follow']} onChange={onChange} label={soundStr} />
<SettingToggle prefix='notifications' settings={settings} settingPath={['group', 'follow']} onChange={onChange} label={groupStr} />
</div>
</section>

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