Merge commit 'b902c825054b953599cd0341acf428332fca5044' into glitch-soc/merge-upstream

This commit is contained in:
Claire 2026-06-12 18:37:02 +02:00
commit 08027edb10
76 changed files with 914 additions and 690 deletions

View File

@ -6,14 +6,14 @@ All notable changes to this project will be documented in this file.
### Added
- **Add collections** (#37992, #37005, #37049, #37020, #37053, #37110, #37117, #37122, #37154, #37157, #37176, #37192, #37222, #37225, #37254, #37277, #37298, #37322, #37434, #37468, #37514, #37512, #37549, #37556, #37560, #37580, #37591, #37552, #37618, #37643, #37658, #37731, #37678, #37741, #37762, #37790, #37805, #37823, #37837, #37842, #37850, #37848, #37812, #37950, #37898, #37916, #37920, #37927, #37928, #37961, #37967, #37974, #37989, #37986, #38004, #38026, #38027, #38030, #38038, #38065, #38081, #38082, #38096, #38106, #38113, #38124, #38133, #38144, #38153, #38166, #38167, #38169, #38170, #38177, #38193, #38213, #38251, #38255, #38256, #38282, #38298, #38292, #38307, #38306, #38316, #38115, #38329, #38334, #38337, #38351, #38368, #38370, #38356, #38383, #38386, #38385, #38394, #38393, #38399, #38402, #38409, #38414, #38413, #38424, #38425, #38450, #38508, #38528, #38534, #38536, #38540, #38543, #38491, #38586, #38611, #38588, #38612, #38628, #38626, #38630, #38633, #38629, #38638, #38645, #38644, #38636, #38660, #38657, #38688, #38690, #38672, #38698, #38697, #38708, #38712, #38713, #38709, #38719, #38728, #38730, #38732, #38739, #38749, #38751, #38750, #38767, #38769, #38783, #38785, #38959, #38786, #38794, #38776, #38817, #38792, #38822, #38827, #38831, #38830, #38844, #38843, #38852, #38850, #38847, #38865, #38897, #38900, #38919, #38933, #38934, #38935, #38942, #38941, #38954, #38961, #38957, #38962, #38991, #39009, #39062, #39029, #39069, #39020, #39073, #39082, #39096, #39080, #39182, #39143, #39127, #37929, #38029, #39194, #39198, #39210, #39211, #39202, #39214, #39215, #39220, #39234, #39260, and #39251 by @ChaosExAnima, @ClearlyClaire, @Gargron, @arte7, @diondiondion, @mjankowski, @oneiros, and @shleeable)
- **Add collections** (#37992, #37005, #37049, #37020, #37053, #37110, #37117, #37122, #37154, #37157, #37176, #37192, #37222, #37225, #37254, #37277, #37298, #37322, #37434, #37468, #37514, #37512, #37549, #37556, #37560, #37580, #37591, #37552, #37618, #37643, #37658, #37731, #37678, #37741, #37762, #37790, #37805, #37823, #37837, #37842, #37850, #37848, #37812, #37950, #37898, #37916, #37920, #37927, #37928, #37961, #37967, #37974, #37989, #37986, #38004, #38026, #38027, #38030, #38038, #38065, #38081, #38082, #38096, #38106, #38113, #38124, #38133, #38144, #38153, #38166, #38167, #38169, #38170, #38177, #38193, #38213, #38251, #38255, #38256, #38282, #38298, #38292, #38307, #38306, #38316, #38115, #38329, #38334, #38337, #38351, #38368, #38370, #38356, #38383, #38386, #38385, #38394, #38393, #38399, #38402, #38409, #38414, #38413, #38424, #38425, #38450, #38508, #38528, #38534, #38536, #38540, #38543, #38491, #38586, #38611, #38588, #38612, #38628, #38626, #38630, #38633, #38629, #38638, #38645, #38644, #38636, #38660, #38657, #38688, #38690, #38672, #38698, #38697, #38708, #38712, #38713, #38709, #38719, #38728, #38730, #38732, #38739, #38749, #38751, #38750, #38767, #38769, #38783, #38785, #38959, #38786, #38794, #38776, #38817, #38792, #38822, #38827, #38831, #38830, #38844, #38843, #38852, #38850, #38847, #38865, #38897, #38900, #38919, #38933, #38934, #38935, #38942, #38941, #38954, #38961, #38957, #38962, #38991, #39009, #39062, #39029, #39069, #39020, #39073, #39082, #39096, #39080, #39182, #39143, #39127, #37929, #38029, #39194, #39198, #39210, #39211, #39202, #39214, #39215, #39220, #39234, #39260, #39251, #39361, #39357, #39349, #39287, #39376, #39289, #39342, #38711, #39379, #39282, #39286, #39296, #39047, #39346, #39373, #39372 by @ChaosExAnima, @ClearlyClaire, @Gargron, @arte7, @diondiondion, @mjankowski, @oneiros, and @shleeable)
- Create collections with up to 25 accounts each, then share them with others. You can read more about this feature [on our blog](https://blog.joinmastodon.org/2026/04/designing-collections/). This is based on FEP-7aa9 (Featured Collections) to be interoperable with the wider Fediverse. All the new API methods [are documented here](https://docs.joinmastodon.org/client/collections/).
- **Add email subscriptions** (#38163, #38507, #38502, #38487, #38527, #38582, #38741, #38907, and #39162 by @ClearlyClaire and @Gargron)
- **Add email subscriptions** (#38163, #38507, #38502, #38487, #38527, #38582, #38741, #38907, #39162, #39271 by @ClearlyClaire and @Gargron)
- Admins can allow specific roles to enable email subscriptions on their profile, allowing anonymous visitors to subscribe to their posts via email.
- **Add new overview landing page setting** (#39074, #39170, #39163, and #39138 by @Gargron, @diondiondion, and @zunda)
- Admins can choose a new frontpage for anonymous visitors, which combines the about page and most recent posts from local profiles.
- **Add ability to require 2FA for specific roles** (including Everybody) (#37701, #37846, and #38906 by @ClearlyClaire and @mjankowski)
- Add export for custom filters (#39085 by @arte7)
- Add import and export for custom filters (#39085, #39256, #39386 by @arte7)
- Add ability to search email blocks by domain in admin UI (#38923 by @arte7)
- Add new endpoints for profile editing in REST API (#37912, #37934, #37932, #38221, and #38339 by @ClearlyClaire)
- Add `GET /api/v1/profile` and `PATCH /api/v1/profile` to replace the existing `update_credentials` endpoint. See [the documentation](https://docs.joinmastodon.org/methods/profile/) for more information.
@ -32,27 +32,32 @@ All notable changes to this project will be documented in this file.
- Add `Cmd`/`Ctrl`+`Enter` for form submissions in more text areas in web UI (#37821 by @diondiondion)
- Add support for quoting by dragging a link into the compose form in web UI (#36859 and #36896 by @ClearlyClaire and @tribela)
- Add `text-autospace` to posts to improve rendering of mixed script posts in web UI (#37694 by @ahxxm)
- Add `nan-TW` to supported locales (#37650, #34923, #37822, and #37721 by @ClearlyClaire and @Yoxem)
- Add Taiwanese (Minnan), Lazuri, Mingrelian and Ottoman Turkish to supported locales (#37650, #34923, #37822, #37721, #38648 by @ClearlyClaire and @Yoxem)
- Add ability to filter notifications from bots (#38809 and #39377 by @evanp and @shleeable)
- Add support for `hosts` resolver in request socket DNS lookup (#38699, #38866, and #39030 by @ClearlyClaire and @mjankowski)
- Add support for FEP-2c59 (Webfinger Backlink) (#38239, #38538, and #38639 by @ClearlyClaire and @shleeable)
- Add support for FEP-3b86 (Activity Intents) (#38120 and #38130 by @ClearlyClaire and @Gargron)
- Add support for alt text for profile pictures and headers (#37634, #37641, and #38000 by @ClearlyClaire and @Doxterpepper)
- Add support for alt text for profile pictures and headers (#37634, #37641, #38000, #39352 by @ClearlyClaire and @Doxterpepper)
- Add support for multiple keypairs for remote accounts (#38279, #38407, #38419, #38511, #38516, #38515, #38555 and #39235 by @ClearlyClaire)
- Add duration to ActivityPub representation of media attachments (#38061 by @ClearlyClaire)
- Add Stoplight circuit-breaker on Elasticsearch endpoints to better handle some Elasticsearch failures (#39323 and #39375 by @ClearlyClaire and @shleeable)
- Add support for the “require approval” feature for email domain blocks to `tootctl email_domain_blocks` (#34579 and #38107 by @ClearlyClaire and @e-nomem)
- Add `--keep-interacted` flag to `tootctl media remove` to preserve cached media on cleanup (#36200 by @northerner)
- Add systemd service file for prometheus exporter (#35130 by @ThisIsMissEm)
### Changed
- **Change design of profiles in web UI** (#37472, #37490, #37479, #37513, #37527, #37550, #37538, #37632, #37627, #37593, #37638, #37626, #37645, #37653, #37683, #37707, #37682, #37742, #37747, #37760, #37761, #37831, #37766, #37811, #37813, #37825, #37854, #37851, #37876, #37885, #37892, #37890, #37907, #37922, #37952, #37958, #37996, #37990, #37994, #38005, #38012, #38040, #38052, #38066, #38083, #38147, #38148, #38152, #38168, #38156, #38175, #38191, #38189, #38235, #38283, #38310, #38309, #38315, #38314, #38365, #38366, #38363, #38346, #38382, #38384, #38400, #38404, #38417, #38426, #38440, #38442, #38443, #38445, #38446, #38451, #38456, #38509, #38510, #38512, #38513, #38517, #38529, #38531, #38535, #38532, #38544, #38549, #38575, #38579, #38580, #38581, #38585, #38584, #38604, #38605, #38606, #38607, #38622, #38616, #38625, #38632, #38640, #38663, #38667, #38646, #38691, #38692, #38766, #38791, #38687, #38826, #38828, #38863, #38845, #38870, #38872, #38932, #38945, #38963, #38964, #39055, #39042, #38893, #39079, #39084, #39160, #39070, and #39217 by @ChaosExAnima, @ClearlyClaire, @Coro365, @diondiondion, and @shleeable)
- **Change design of profiles in web UI** (#37472, #37490, #37479, #37513, #37527, #37550, #37538, #37632, #37627, #37593, #37638, #37626, #37645, #37653, #37683, #37707, #37682, #37742, #37747, #37760, #37761, #37831, #37766, #37811, #37813, #37825, #37854, #37851, #37876, #37885, #37892, #37890, #37907, #37922, #37952, #37958, #37996, #37990, #37994, #38005, #38012, #38040, #38052, #38066, #38083, #38147, #38148, #38152, #38168, #38156, #38175, #38191, #38189, #38235, #38283, #38310, #38309, #38315, #38314, #38365, #38366, #38363, #38346, #38382, #38384, #38400, #38404, #38417, #38426, #38440, #38442, #38443, #38445, #38446, #38451, #38456, #38509, #38510, #38512, #38513, #38517, #38529, #38531, #38535, #38532, #38544, #38549, #38575, #38579, #38580, #38581, #38585, #38584, #38604, #38605, #38606, #38607, #38622, #38616, #38625, #38632, #38640, #38663, #38667, #38646, #38691, #38692, #38766, #38791, #38687, #38826, #38828, #38863, #38845, #38870, #38872, #38932, #38945, #38963, #38964, #39055, #39042, #38893, #39079, #39084, #39160, #39070, #39217, #39309, #39354, #39324, #39387 by @ChaosExAnima, @ClearlyClaire, @Coro365, @diondiondion, and @shleeable)
- The profile screen has been entirely redesigned, has new features, and allows you to update your own profile directly without going into the preferences panel. You can read more about it [on our blog](https://blog.joinmastodon.org/2026/03/a-redesign-for-profiles/).
- **Change how #Wrapstodon reports are generated and displayed** (#37033, #37045, #37093, #37055, #37096, #37047, #37103, #37104, #37106, #37109, #37121, #37138, #37134, #37177, #37182, #37169, #37186, #37187, #37188, #37189, #37190, #37193, #37198, #37201, #37203, #37205, #37206, #37207, #37209, #37202, #37216, #37219, #37224, #37226, #37229, #37249, #37251, #37256, #37261, #37269, #37270, #37273, and #37289 by @ChaosExAnima, @ClearlyClaire, @channyeintun, and @diondiondion)
- This finishes up work started in 2024 by completely revamping how Wrapstodon reports are generated and displayed, reducing the amount of data collected and generating reports when active users ask for them.
- Instead of requiring manual generation from a server administrator, this is now offered between the 10th of December and the end of each year if enabled in the server settings.
- The design of the Wrapstodon report has also been fully reworked to be more delightful and easier to share!
- The relevant API endpoints are documented at https://docs.joinmastodon.org/methods/annual_reports/
- Change limitation to allow posts with both media and a poll to be created (#39203, #39368, #39388 by @ClearlyClaire and @Gargron)
- Change alt text limit for media attachments to 10,000 characters (#39306 by @ClearlyClaire)
- Change pending user notification email to link directly to the pending account (#39206 by @vmstan)
- Changed emoji processing in web UI to make it less resource intensive and more robust (#39077, #39008, #39088, #38892, #38885, #38965, #38854, #38825, #38784, #38541, #37442, #37300, #37306, #37271, #37255, #37284, #37272, #37178, #37084, #37080, #37418, #39167, and #39126 by @ChaosExAnima, @ClearlyClaire, @diondiondion, and @gomasy)
- Changed emoji processing in web UI to make it less resource intensive and more robust (#39077, #39008, #39088, #38892, #38885, #38965, #38854, #38825, #38784, #38541, #37442, #37300, #37306, #37271, #37255, #37284, #37272, #37178, #37084, #37080, #37418, #39167, #39126, #39353, #39378, #39382 by @ChaosExAnima, @ClearlyClaire, @diondiondion, and @gomasy)
- Change composer textarea to have a limited height to prevent column scrolling (#39268 by @diondiondion)
- Change mentions of “Mastodon gGmbH” to “Mastodon GmbH” (#39261 by @renchap)
- Change the limited profile message to be less misleading (#39231 by @mortie)
@ -72,10 +77,12 @@ All notable changes to this project will be documented in this file.
- Change wording and ordering of account migration warnings (#20387 by @jsoref)
- Change wording of “Automatic post deletion” settings (#37286 by @mjankowski)
- Change wording of language filter settings to clarify they do not impact home/lists (#38490 by @mjankowski)
- Change wording of `tootctl preview_cards remove` command description to clearly state it only removes media (#39348 by @mjankowski)
- Change invitations to only bypass sign-up approval setting when the issuer of the invitation has the `invite_bypass_approval` permission (#38278 by @ClearlyClaire)
- This splits the “Invite Users” permission into a new “Invite Users without review” permission.
- Existing roles will be updated to have the new permission if they have the old one, but default permissions will not include the new `invite_bypass_approval` permission.
- Change followers synchronization mechanism on followers-only posts to be skipped for accounts with 25k followers or more (#37302 by @ClearlyClaire)
- Change "Accept" link on sign-up page to a form to prevent some crawling behavior (#39283 and #39345 by @ClearlyClaire and @mjankowski)
- Change “dark”, “light” and “high contrast” themes to be separate “Color scheme” and “Contrast” settings handled by a single theme (#37095, #37120, #37288, #37459, #37470, #37477, #37519, #37520, #37523, #37524, #37526, #37612, #37824, #37807, #37810, #37819, #37906, and #38261 by @ClearlyClaire, @diondiondion, and @mjankowski)
- Existing settings should be migrated automatically from user settings, and using browser defaults otherwise.
- This also allows third-party theme authors to make use of the same browser defaults and user settings. Learn more about this in [our new Theming docs](https://docs.joinmastodon.org/dev/frontend/theming/).
@ -91,18 +98,25 @@ All notable changes to this project will be documented in this file.
- Remove support for Ruby 3.2 (#37476 by @mjankowski)
- Remove support for `ENABLE_SIDEKIQ_UNIQUE_JOBS_UI` (#38340 by @ClearlyClaire)
- Remove support for ImageMagick (#37488 by @mjankowski)
- Remove outdated hint for "Use system scrollbar" preference (#39297 by @diondiondion)
### Fixed
- Fix accessibility issues in web UI (#37250, #38006, #38033, #38188, #38230, #38252, #38257, #38285, #38293, #38362, #38387, #38459, #38796, #38801, #39098, #39111, #39120, #39129, #39133, #39134, #39144, #39145, #39149, #39164, #39165, #39169, and #39181 by @ChaosExAnima and @diondiondion)
- Fix accessibility issues in web UI (#37250, #38006, #38033, #38188, #38230, #38252, #38257, #38285, #38293, #38362, #38387, #38459, #38796, #38801, #39098, #39111, #39120, #39129, #39133, #39134, #39144, #39145, #39149, #39164, #39165, #39169, #39181, #39335, #39305, #39331, #39356, #39350, #39358, #39360, #39325, #39270 by @ChaosExAnima and @diondiondion)
- Fix tiny checkboxes and radio buttons in Safari (#39332 by @diondiondion)
- Fix handling of offset in timezone list in settings (#39334 by @mjankowski)
- Fix being unable to unmark media as sensitive when "always mark media as sensitive" is enabled in web UI (#39339 by @matrix07012)
- Fix display of sensitive media cards in web UI according to settings (#39366 by @nshki)
- Fix some inputs incorrectly having resize handles in Firefox (#39274 by @diondiondion)
- Fix processing some link previews where text is language-tagged (#39190 by @zunda)
- Fix error when “New trends” email is sent at the same time trends are recomputed (#39122 by @arte7)
- Fix hover card opening even when not preceded by mouse movement in web UI (#39166 by @diondiondion)
- Fix hover card opening even when not preceded by mouse movement in web UI (#39166, #39381 by @diondiondion)
- Fix [ominous](https://mastodon.social/@mcc/116404362104299129) "Moments remaining" timestamp in web UI (#38488 and #38689 by @ChaosExAnima and @MitarashiDango)
- Fix filters not being applied to search results in web UI (#36346 by @ClearlyClaire)
- Fix error when visiting non-public hashtag timelines (#36961 by @diondiondion)
- Fix duplicate favourite/boost counters in some languages (#36844 by @ChaosExAnima)
- Fix unblocking domain from blocked domains column not updating the list in web UI (#38882 by @tribela)
- Fix "change thumbnail" button being visible when it shouldn't in web UI (#38467 by @dpbento)
- Fix profile dropdown menu sometimes ending with a separator in web UI (#38481 by @mkljczk)
- Fix short numbers rounding up instead of truncating in web UI (#38114 by @serranodfm)
- Fix directory showing load more button when no more profiles exist in web UI (#37465 by @heathdutton)
@ -115,10 +129,13 @@ All notable changes to this project will be documented in this file.
- Fix HTML `lang` attribute being stripped out of remote posts (#39114 by @artemist)
- Fix remote posts with large media descriptions being rejected (#39135 by @ClearlyClaire)
- Fix some occurrence of PostgreSQL log pollution when processing new hashtags (#35792 by @oelison)
- Fix blocked domains not being removed from the Instance search index (#39109 by @shleeable)
- Fix Elasticsearch connections not being cleaned up properly in Sidekiq middleware (#39359 by @ClearlyClaire)
- Fix replica database not being used when `REPLICA_DB_HOST` is used but neither `REPLICA_DB_NAME` nor `REPLICA_DATABASE_URL` (#37240 by @smiba)
- Fix remote media attachment thumbnails not being stored in the `cache/` directory (#36911 by @shugo)
- Fix race condition when processing posts twice with the same idempotency key (#37879 by @ClearlyClaire)
- Fix various missing translation strings (#37671, #37838, #37078, #37371, and #37827 by @ClearlyClaire, @mjankowski, and @valtlai)
- Fix `expire_at` instead of `expires_at` in muted words CSV exports (#39304 by @arte7)
- Fix various missing translation strings (#37671, #37838, #37078, #37371, #37827, #39328 by @ClearlyClaire, @mjankowski, and @valtlai)
- Fix last post time for remote accounts not being accurately tracked (#37619 by @ClearlyClaire)
- Fix filtering of mentions from filtered-on-their-origin-server accounts (#37583 by @ClearlyClaire)
- Fix irrelevant remote accounts being passed through to local fan-out worker (#37589 by @ClearlyClaire)
@ -897,566 +914,4 @@ All notable changes to this project will be documented in this file.
- Fix use of deprecated `execCommand` for copying text by using the `clipboard` API (#32598 by @renchap)
- Fix some translation strings not being properly pluralized (#27094 by @gunchleoc)
## [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.
### Security
- **Add confirmation interstitial instead of silently redirecting logged-out visitors to remote resources** (#27792, #28902, and #30651 by @ClearlyClaire and @Gargron)\
This fixes a longstanding open redirect in Mastodon, at the cost of added friction when local links to remote resources are shared.
- Fix ReDoS vulnerability on some Ruby versions ([GHSA-jpxp-r43f-rhvx](https://github.com/mastodon/mastodon/security/advisories/GHSA-jpxp-r43f-rhvx))
- Change `form-action` Content-Security-Policy directive to be more restrictive (#26897 and #32241 by @ClearlyClaire)
- Update dependencies
### Added
- **Add server-side notification grouping** (#29889, #30576, #30685, #30688, #30707, #30776, #30779, #30781, #30440, #31062, #31098, #31076, #31111, #31123, #31223, #31214, #31224, #31299, #31325, #31347, #31304, #31326, #31384, #31403, #31433, #31509, #31486, #31513, #31592, #31594, #31638, #31746, #31652, #31709, #31725, #31745, #31613, #31657, #31840, #31610, #31929, #32089, #32085, #32243, #32179 and #32254 by @ClearlyClaire, @Gargron, @mgmn, and @renchap)\
Group notifications of the same type for the same target, so that your notifications no longer get cluttered by boost and favorite notifications as soon as a couple of your posts get traction.\
This is done server-side so that clients can efficiently get relevant groups without having to go through numerous pages of individual notifications.\
As part of this, the visual design of the entire notifications feature has been revamped.\
This feature is intended to eventually replace the existing notifications column, but for this first beta, users will have to enable it in the “Experimental features” section of the notifications column settings.\
The API is not final yet, but it consists of:
- a new `group_key` attribute to `Notification` entities
- `GET /api/v2/notifications`: https://docs.joinmastodon.org/methods/grouped_notifications/#get-grouped
- `GET /api/v2/notifications/:group_key`: https://docs.joinmastodon.org/methods/grouped_notifications/#get-notification-group
- `GET /api/v2/notifications/:group_key/accounts`: https://docs.joinmastodon.org/methods/grouped_notifications/#get-group-accounts
- `POST /api/v2/notifications/:group_key/dismiss`: https://docs.joinmastodon.org/methods/grouped_notifications/#dismiss-group
- `GET /api/v2/notifications/:unread_count`: https://docs.joinmastodon.org/methods/grouped_notifications/#unread-group-count
- **Add notification policies, filtered notifications and notification requests** (#29366, #29529, #29433, #29565, #29567, #29572, #29575, #29588, #29646, #29652, #29658, #29666, #29693, #29699, #29737, #29706, #29570, #29752, #29810, #29826, #30114, #30251, #30559, #29868, #31008, #31011, #30996, #31149, #31220, #31222, #31225, #31242, #31262, #31250, #31273, #31310, #31316, #31322, #31329, #31324, #31331, #31343, #31342, #31309, #31358, #31378, #31406, #31256, #31456, #31419, #31457, #31508, #31540, #31541, #31723, #32062 and #32281 by @ClearlyClaire, @Gargron, @TheEssem, @mgmn, @oneiros, and @renchap)\
The old “Block notifications from non-followers”, “Block notifications from people you don't follow” and “Block direct messages from people you don't follow” notification settings have been replaced by a new set of settings found directly in the notification column.\
You can now separately filter or drop notifications from people you don't follow, people who don't follow you, accounts created within the past 30 days, as well as unsolicited private mentions, and accounts limited by the moderation.\
Instead of being outright dropped, notifications that you chose to filter are put in a separate “Filtered notifications” box that you can review separately without it clogging your main notifications.\
This adds the following REST API endpoints:
- `GET /api/v2/notifications/policy`: https://docs.joinmastodon.org/methods/notifications/#get-policy
- `PATCH /api/v2/notifications/policy`: https://docs.joinmastodon.org/methods/notifications/#update-the-filtering-policy-for-notifications
- `GET /api/v1/notifications/requests`: https://docs.joinmastodon.org/methods/notifications/#get-requests
- `GET /api/v1/notifications/requests/:id`: https://docs.joinmastodon.org/methods/notifications/#get-one-request
- `POST /api/v1/notifications/requests/:id/accept`: https://docs.joinmastodon.org/methods/notifications/#accept-request
- `POST /api/v1/notifications/requests/:id/dismiss`: https://docs.joinmastodon.org/methods/notifications/#dismiss-request
- `POST /api/v1/notifications/requests/accept`: https://docs.joinmastodon.org/methods/notifications/#accept-multiple-requests
- `POST /api/v1/notifications/requests/dismiss`: https://docs.joinmastodon.org/methods/notifications/#dismiss-multiple-requests
- `GET /api/v1/notifications/requests/merged`: https://docs.joinmastodon.org/methods/notifications/#requests-merged
In addition, accepting one or more notification requests generates a new streaming event:
- `notifications_merged`: an event of this type indicates accepted notification requests have finished merging, and the notifications list should be refreshed
- **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 [`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.
- **Add "system" theme setting (light/dark theme depending on user system preference)** (#29748, #29553, #29795, #29918, #30839, and #30861 by @nshki, @ErikUden, @mjankowski, @renchap, and @vmstan)\
Add a “system” theme that automatically switch between default dark and light themes depending on the user's system preferences.\
Also changes the default server theme to this new “system” theme so that automatic theme selection happens even when logged out.
- **Add timeline of public posts about a trending link** (#30381 and #30840 by @Gargron)\
You can now see public posts mentioning currently-trending articles from people who have opted into discovery features.\
This adds a new REST API endpoint: https://docs.joinmastodon.org/methods/timelines/#link
- **Add author highlight for news articles whose authors are on the fediverse** (#30398, #30670, #30521, #30846, #31819, #31900 and #32188 by @Gargron, @mjankowski and @oneiros)\
This adds a mechanism to [highlight the author of news articles](https://blog.joinmastodon.org/2024/07/highlighting-journalism-on-mastodon/) shared on Mastodon.\
Articles hosted outside the fediverse can indicate a fediverse author with a meta tag:
```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 \
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)\
In addition to email notifications, also notify users of moderation actions or warnings against them directly within the app, so they are less likely to miss important communication from their moderators.\
This adds the `moderation_warning` notification type to the REST API and streaming, with a new [`moderation_warning` attribute](https://docs.joinmastodon.org/entities/Notification/#moderation_warning).
- **Add domain information to profiles in web UI** (#29602 by @Gargron)\
Clicking the domain of a user in their profile will now open a tooltip with a short explanation about servers and federation.
- **Add support for Redis sentinel** (#31694, #31623, #31744, #31767, and #31768 by @ThisIsMissEm and @oneiros)\
See https://docs.joinmastodon.org/admin/scaling/#redis-sentinel
- **Add ability to reorder uploaded media before posting in web UI** (#28456 and #32093 by @Gargron)
- Add “A Mastodon update is available.” message on admin dashboard for non-bugfix updates (#32106 by @ClearlyClaire)
- Add ability to view alt text by clicking the ALT badge in web UI (#32058 by @Gargron)
- Add preview of followers removed in domain block modal in web UI (#32032 and #32105 by @ClearlyClaire and @Gargron)
- Add reblogs and favourites counts to statuses in ActivityPub (#32007 by @Gargron)
- Add moderation interface for searching hashtags (#30880 by @ThisIsMissEm)
- Add ability for admins to configure instance favicon and logo (#30040, #30208, #30259, #30375, #30734, #31016, and #30205 by @ClearlyClaire, @FawazFarid, @JasonPunyon, @mgmn, and @renchap)\
This is also exposed through the REST API: https://docs.joinmastodon.org/entities/Instance/#icon
- Add `api_versions` to `/api/v2/instance` (#31354 by @ClearlyClaire)\
Add API version number to make it easier for clients to detect compatible features going forward.\
See API documentation at https://docs.joinmastodon.org/entities/Instance/#api-versions
- Add quick links to Administration and Moderation Reports from Web UI (#24838 by @ThisIsMissEm)
- Add link to `/admin/roles` in moderation interface when changing someone's role (#31791 by @ClearlyClaire)
- Add recent audit log entries in federation moderation interface (#27386 by @ThisIsMissEm)
- Add profile setup to onboarding in web UI (#27829, #27876, and #28453 by @Gargron)
- Add prominent share/copy button on profiles in web UI (#27865 and #27889 by @ClearlyClaire and @Gargron)
- Add optional hints for server rules (#29539 and #29758 by @ClearlyClaire and @Gargron)\
Server rules can now be broken into a short rule name and a longer explanation of the rule.\
This adds a new [`hint` attribute](https://docs.joinmastodon.org/entities/Rule/#hint) to `Rule` entities in the REST API.
- Add support for PKCE in OAuth flow (#31129 by @ThisIsMissEm)
- Add CDN cache busting on media deletion (#31353 and #31414 by @ClearlyClaire and @tribela)
- Add the OAuth application used in local reports (#30539 by @ThisIsMissEm)
- Add hint to user that other remote statuses may be missing (#26910, #31387, and #31516 by @Gargron, @audiodude, and @renchap)
- Add lang attribute on preview card title (#31303 by @c960657)
- Add check for `Content-Length` in `ResponseWithLimitAdapter` (#31285 by @c960657)
- Add `Accept-Language` header to fetch preview cards in the server's default language (#31232 by @c960657)
- Add support for PKCE Extension in OmniAuth OIDC through the `OIDC_USE_PKCE` environment variable (#31131 by @ThisIsMissEm)
- Add API endpoints for unread notifications count (#31191 by @ClearlyClaire)\
This adds the following REST API endpoints:
- `GET /api/v1/notifications/unread_count`: https://docs.joinmastodon.org/methods/notifications/#unread-count
- Add `/` keyboard shortcut to focus the search field (#29921 by @ClearlyClaire)
- Add button to view the Hashtag on the instance from Hashtags in Moderation UI (#31533 by @ThisIsMissEm)
- Add list of pending releases directly in mail notifications for version updates (#29436 and #30035 by @ClearlyClaire)
- Add “Appeals” link under “Moderation” navigation category in moderation interface (#31071 by @ThisIsMissEm)
- Add badge on account card in report moderation interface when account is already suspended (#29592 by @ClearlyClaire)
- Add admin comments directly to the `admin/instances` page (#29240 by @tribela)
- Add ability to require approval when users sign up using specific email domains (#28468, #28732, #28607, and #28608 by @ClearlyClaire)
- Add banner for forwarded reports made by remote users about remote content (#27549 by @ClearlyClaire)
- Add support HTML ruby tags in remote posts for east-asian languages (#30897 by @ThisIsMissEm)
- Add link to manage warning presets in admin navigation (#26199 by @vmstan)
- Add volume saving/reuse to video player (#27488 by @thehydrogen)
- Add Elasticsearch index size, ffmpeg and ImageMagick versions to the admin dashboard (#27301, #30710, #31130, and #30845 by @vmstan)
- Add `MASTODON_SIDEKIQ_READY_FILENAME` environment variable to use a file for Sidekiq to signal it is ready to process jobs (#30971 and #30988 by @renchap)\
In the official Docker image, this is set to `sidekiq_process_has_started_and_will_begin_processing_jobs` so that Sidekiq will touch `tmp/sidekiq_process_has_started_and_will_begin_processing_jobs` to signal readiness.
- Add `S3_RETRY_LIMIT` environment variable to make S3 retries configurable (#23215 by @smiba)
- Add `S3_KEY_PREFIX` environment variable (#30181 by @S0yKaf)
- Add support for multiple `redirect_uris` when creating OAuth 2.0 Applications (#29192 by @ThisIsMissEm)
- Add Interlingue and Interlingua to interface languages (#28630 and #30828 by @Dhghomon and @renchap)
- Add Kashubian, Pennsylvania Dutch, Vai, Jawi Malay, Mohawk and Low German to posting languages (#26024, #26634, #27136, #29098, #27115, and #27434 by @EngineerDali, @HelgeKrueger, and @gunchleoc)
- Add option to use native Ruby driver for Redis through `REDIS_DRIVER=ruby` (#30717 by @vmstan)
- Add support for libvips in addition to ImageMagick (#30090, #30590, #30597, #30632, #30857, #30869, #30858 and #32104 by @ClearlyClaire, @Gargron, and @mjankowski)\
Server admins can now use libvips as a faster and lighter alternative to ImageMagick for processing user-uploaded images.\
This requires libvips 8.13 or newer, and needs to be enabled with `MASTODON_USE_LIBVIPS=true`.\
This is enabled by default in the official Docker images, and is intended to completely replace ImageMagick in the future.
- Add validations to `Web::PushSubscription` (#30540 and #30542 by @ThisIsMissEm)
- Add anchors to each authorized application in `/oauth/authorized_applications` (#31677 by @fowl2)
- Add active animation to header settings button (#30221, #30307, and #30388 by @daudix)
- Add OpenTelemetry instrumentation (#30130, #30322, #30353, #30350 and #31998 by @julianocosta89, @renchap, @robbkidd and @timetinytim)\
See https://docs.joinmastodon.org/admin/config/#otel for documentation
- Add API to get multiple accounts and statuses (#27871 and #30465 by @ClearlyClaire)\
This adds `GET /api/v1/accounts` and `GET /api/v1/statuses` to the REST API, see https://docs.joinmastodon.org/methods/accounts/#index and https://docs.joinmastodon.org/methods/statuses/#index
- Add support for CORS to `POST /oauth/revoke` (#31743 by @ClearlyClaire)
- Add redirection back to previous page after site upload deletion (#30141 by @FawazFarid)
- Add RFC8414 OAuth 2.0 server metadata (#29191 by @ThisIsMissEm)
- Add loading indicator and empty result message to advanced interface search (#30085 by @ClearlyClaire)
- Add `profile` OAuth 2.0 scope, allowing more limited access to user data (#29087 and #30357 by @ThisIsMissEm)
- Add the role ID to the badge component (#29707 by @renchap)
- Add diagnostic message for failure during CLI search deploy (#29462 by @mjankowski)
- Add pagination `Link` headers on API accounts/statuses when pinned true (#29442 by @mjankowski)
- Add support for specifying custom CA cert for Elasticsearch through `ES_CA_FILE` (#29122 and #29147 by @ClearlyClaire)
- Add groundwork for annual reports for accounts (#28693 by @Gargron)\
This lays the groundwork for a “year-in-review”/“wrapped” style report for local users, but is currently not in use.
- Add notification email on invalid second authenticator (#28822 by @ClearlyClaire)
- Add date of account deletion in list of accounts in the admin interface (#25640 by @tribela)
- Add new emojis from `jdecked/twemoji` 15.0 (#28404 by @TheEssem)
- Add configurable error handling in attachment batch deletion (#28184 by @vmstan)\
This makes the S3 batch size configurable through the `S3_BATCH_DELETE_LIMIT` environment variable (defaults to 1000), and adds some retry logic, configurable through the `S3_BATCH_DELETE_RETRY` environment variable (defaults to 3).
- Add VAPID public key to instance serializer (#28006 by @ThisIsMissEm)
- Add support for serving JRD `/.well-known/host-meta.json` in addition to XRD host-meta (#32206 by @c960657)
- Add `nodeName` and `nodeDescription` to nodeinfo `metadata` (#28079 by @6543)
- Add Thai diacritics and tone marks in `HASHTAG_INVALID_CHARS_RE` (#26576 by @ppnplus)
- Add variable delay before link verification of remote account links (#27774 by @ClearlyClaire)
- Add support for invite codes in the registration API (#27805 by @ClearlyClaire)
- Add HTML lang attribute to preview card descriptions (#27503 by @srapilly)
- Add display of relevant account warnings to report action logs (#27425 by @ClearlyClaire)
- Add validation of allowed schemes on preview card URLs (#27485 by @mjankowski)
- Add token introspection without read scope to `/api/v1/apps/verify_credentials` (#27142 by @ThisIsMissEm)
- Add support for cross-origin request to `/nodeinfo/2.0` (#27413 by @palant)
- Add variable delay before link verification of remote account links (#27351 by @ClearlyClaire)
- Add PWA shortcut to `/explore` page (#27235 by @jake-anto)
### Changed
- **Change icons throughout the web interface** (#27385, #27539, #27555, #27579, #27700, #27817, #28519, #28709, #28064, #28775, #28780, #27924, #29294, #29395, #29537, #29569, #29610, #29612, #29649, #29844, #27780, #30974, #30963, #30962, #30961, #31362, #31363, #31359, #31371, #31360, #31512, #31511, #31525, #32153, and #32201 by @ClearlyClaire, @Gargron, @arbolitoloco1, @mjankowski, @nclm, @renchap, @ronilaukkarinen, and @zunda)\
This changes all the interface icons from FontAwesome to Material Symbols for a more modern look, consistent with the official Mastodon Android app.\
In addition, better care is given to pixel alignment, and icon variants are used to better highlight active/inactive state.
- **Change design of compose form in web UI** (#28119, #29059, #29248, #29372, #29384, #29417, #29456, #29406, #29651, #29659, #31889 and #32033 by @ClearlyClaire, @Gargron, @eai04191, @hinaloe, and @ronilaukkarinen)\
The compose form has been completely redesigned for a more modern and consistent look, as well as spelling out the chosen privacy setting and language name at all times.\
As part of this, the “Unlisted” privacy setting has been renamed to “Quiet public”.
- **Change design of modals in the web UI** (#29576, #29614, #29640, #29644, #30131, #30884, #31399, #31555, #31752, #31801, #31883, #31844, #31864, and #31943 by @ClearlyClaire, @Gargron, @tribela and @vmstan)\
The mute, block, and domain block confirmation modals have been completely redesigned to be clearer and include more detailed information on the action to be performed.\
They also have a more modern and consistent design, along with other confirmation modals in the application.
- **Change colors throughout the web UI** (#29522, #29584, #29653, #29779, #29803, #29809, #29808, #29828, #31034, #31168, #31266, #31348, #31349, #31361, #31510 and #32128 by @ClearlyClaire, @Gargron, @mjankowski, @renchap, and @vmstan)
- **Change onboarding prompt to follow suggestions carousel in web UI** (#28878, #29272, and #31912 by @Gargron)
- **Change email templates** (#28416, #28755, #28814, #29064, #28883, #29470, #29607, #29761, #29760, #29879, #32073 and #32132 by @c960657, @ClearlyClaire, @Gargron, @hteumeuleu, and @mjankowski)\
All emails to end-users have been completely redesigned with a fresh new look, providing more information while making them easier to read and keeping maximum compatibility across mail clients.
- **Change follow recommendations algorithm** (#28314, #28433, #29017, #29108, #29306, #29550, #29619, and #31474 by @ClearlyClaire, @Gargron, @kernal053, @mjankowski, and @wheatear-dev)\
This replaces the “past interactions” recommendation algorithm with a “friends of friends” algorithm that suggests accounts followed by people you follow, and a “similar profiles” algorithm that suggests accounts with a profile similar to your most recent follows.\
In addition, the implementation has been significantly reworked, and all follow recommendations are now dismissable.\
This change deprecates the `source` attribute in `Suggestion` entities in the REST API, and replaces it with the new [`sources` attribute](https://docs.joinmastodon.org/entities/Suggestion/#sources).
- Change account search algorithm (#30803 by @Gargron)
- **Change streaming server to use its own dependencies and its own docker image** (#24702, #27967, #26850, #28112, #28115, #28137, #28138, #28497, #28548, #30795, #31612, and #31615 by @TheEssem, @ThisIsMissEm, @jippi, @renchap, @timetinytim, and @vmstan)\
In order to reduce the amount of runtime dependencies, the streaming server has been moved into a separate package and Docker image.\
The `mastodon` image does not contain the streaming server anymore, as it has been moved to its own `mastodon-streaming` image.\
Administrators may need to update their setup accordingly.
- Change how content warnings and filters are displayed in web UI (#31365, and #31761 by @Gargron)
- Change preview card processing to ignore `undefined` as canonical url (#31882 by @oneiros)
- Change embedded posts to use web UI (#31766, #32135 and #32271 by @Gargron)
- Change inner borders in media galleries in web UI (#31852 by @Gargron)
- Change design of media attachments and profile media tab in web UI (#31807, #32048, #31967, #32217, #32224 and #32257 by @ClearlyClaire and @Gargron)
- Change labels on thread indicators in web UI (#31806 by @Gargron)
- Change label of "Data export" menu item in settings interface (#32099 by @c960657)
- Change responsive break points on navigation panel in web UI (#32034 by @Gargron)
- Change cursor to `not-allowed` on disabled buttons (#32076 by @mjankowski)
- Change OAuth authorization prompt to not refer to apps as “third-party” (#32005 by @Gargron)
- Change Mastodon to issue correct HTTP signatures by default (#31994 by @ClearlyClaire)
- Change zoom icon in web UI (#29683 by @Gargron)
- Change directory page to use URL query strings for options (#31980, #31977 and #31984 by @ClearlyClaire and @renchap)
- Change report action buttons to be disabled when action has already been taken (#31773, #31822, and #31899 by @ClearlyClaire and @ThisIsMissEm)
- Change width of columns in advanced web UI (#31762 by @Gargron)
- Change design of unread conversations in web UI (#31763 by @Gargron)
- Change Web UI to allow viewing and severing relationships with suspended accounts (#27667 by @ClearlyClaire)\
This also adds a `with_suspended` parameter to `GET /api/v1/accounts/relationships` in the REST API.
- Change preview card image size limit from 2MB to 8MB when using libvips (#31904 by @ClearlyClaire)
- Change avatars border radius (#31390 by @renchap)
- Change counters to be displayed on profile timelines in web UI (#30525 by @Gargron)
- Change disabled buttons color in light mode to make the difference more visible (#30998 by @renchap)
- Change design of people tab on explore in web UI (#30059 by @Gargron)
- Change sidebar text in web UI (#30696 by @Gargron)
- Change "Follow" to "Follow back" and "Mutual" when appropriate in web UI (#28452, #28465, and #31934 by @ClearlyClaire, @Gargron and @renchap)
- Change media to be hidden/blurred by default in report modal (#28522 by @ClearlyClaire)
- Change order of the "muting" and "blocking" list options in “Data Exports” (#26088 by @fixermark)
- Change admin and moderation notes character limit from 500 to 2000 characters (#30288 by @ThisIsMissEm)
- Change mute options to be in dropdown on muted users list in web UI (#30049 and #31315 by @ClearlyClaire and @Gargron)
- Change out-of-band hashtags design in web UI (#29732 by @Gargron)
- Change design of metadata underneath detailed posts in web UI (#29585, #29605, and #29648 by @ClearlyClaire and @Gargron)
- Change action button to be last on profiles in web UI (#29533 and #29923 by @ClearlyClaire and @Gargron)
- Change confirmation prompts in trending moderation interface to be more specific (#19626 by @tribela)
- Change “Trends” moderation menu to “Recommendations & Trends” and move follow recommendations there (#31292 by @ThisIsMissEm)
- Change irrelevant fields in account cleanup settings to be disabled unless automatic cleanup is enabled (#26562 by @c960657)
- Change dropdown menu icon to not be replaced by close icon when open in web UI (#29532 by @Gargron)
- Change back button to always appear in advanced web UI (#29551 and #29669 by @Gargron)
- Change border of active compose field search inputs (#29832 and #29839 by @vmstan)
- Change instances of Nokogiri HTML4 parsing to HTML5 (#31812, #31815, #31813, and #31814 by @flavorjones)
- Change link detection to allow `@` at the end of an URL (#31124 by @adamniedzielski)
- Change User-Agent to use Mastodon as the product, and http.rb as platform details (#31192 by @ClearlyClaire)
- Change layout and wording of the Content Retention server settings page (#27733 by @vmstan)
- Change unconfirmed users to be kept for one week instead of two days (#30285 by @renchap)
- Change maximum page size for Admin Domain Management APIs from 200 to 500 (#31253 by @ThisIsMissEm)
- Change database pool size to default to Sidekiq concurrency settings in Sidekiq processes (#26488 by @sinoru)
- Change alt text to empty string for avatars (#21875 by @jasminjohal)
- Change Docker images to use custom-built libvips and ffmpeg (#30571, #30569, and #31498 by @vmstan)
- Change external links in the admin audit log to plain text or local administration pages (#27139 and #27150 by @ClearlyClaire and @ThisIsMissEm)
- Change YJIT to be enabled when available (#30310 and #27283 by @ClearlyClaire and @mjankowski)\
Enable Ruby's built-in just-in-time compiler. This improves performances substantially, at the cost of a slightly increased memory usage.
- Change `.env` file loading from deprecated `dotenv-rails` gem to `dotenv` gem (#29173 and #30121 by @mjankowski)\
This should have no effect except in the unlikely case an environment variable included a newline.
- Change “Panjabi” language name to the more common spelling “Punjabi” (#27117 by @gunchleoc)
- Change encryption of OTP secrets to use ActiveRecord Encryption (#29831, #28325, #30151, #30202, #30340, and #30344 by @ClearlyClaire and @mjankowski)\
This requires a manual step from administrators of existing servers. Indeed, they need to generate new secrets, which can be done using `bundle exec rails db:encryption:init`.\
Furthermore, there is a risk that the introduced migration fails if the server was misconfigured in the past. If that happens, the migration error will include the relevant information.
- Change `/api/v1/announcements` to return regular `Status` entities (#26736 by @ClearlyClaire)
- Change imports to convert case-insensitive fields to lowercase (#29739 and #29740 by @ThisIsMissEm)
- Change stats in the admin interface to be inclusive of the full selected range, from beginning of day to end of day (#29416 and #29841 by @mjankowski)
- Change materialized views to be refreshed concurrently to avoid locks (#29015 by @Gargron)
- Change compose form to use server-provided post character and poll options limits (#28928 and #29490 by @ClearlyClaire and @renchap)
- Change streaming server logging from `npmlog` to `pino` and `pino-http` (#27828 by @ThisIsMissEm)\
This changes the Mastodon streaming server log format, so this might be considered a breaking change if you were parsing the logs.
- Change media “ALT” label to use a specific CSS class (#28777 by @ClearlyClaire)
- Change streaming API host to not be overridden to localhost in development mode (#28557 by @ClearlyClaire)
- Change cookie rotator to use SHA1 digest for new cookies (#27392 by @ClearlyClaire)\
Note that this requires that no pre-4.2.0 Mastodon web server is running when this code is deployed, as those would not understand the new cookies.\
Therefore, zero-downtime updates are only supported if you're coming from 4.2.0 or newer. If you want to skip Mastodon 4.2, you will need to completely stop Mastodon services before updating.
- Change preview card deletes to be done using batch method (#28183 by @vmstan)
- Change `img-src` and `media-src` CSP directives to not include `https:` (#28025 and #28561 by @ClearlyClaire)
- Change self-destruct procedure (#26439, #29049, and #29420 by @ClearlyClaire and @zunda)\
Instead of enqueuing deletion jobs immediately, `tootctl self-destruct` now outputs a value for the `SELF_DESTRUCT` environment variable, which puts a server in self-destruct mode, processing deletions in the background, while giving users access to their export archives.
### Removed
- Remove unused E2EE messaging code and related `crypto` OAuth scope (#31193, #31945, #31963, and #31964 by @ClearlyClaire and @mjankowski)
- Remove StatsD integration (replaced by OpenTelemetry) (#30240 by @mjankowski)
- Remove `CacheBuster` default options (#30718 by @mjankowski)
- Remove home marker updates from the Web UI (#22721 by @davbeck)\
The web interface was unconditionally updating the home marker to the most recent received post, discarding any value set by other clients, thus making the feature unreliable.
- Remove support for Ruby 3.0 (reaching EOL) (#29702 by @mjankowski)
- Remove setting for unfollow confirmation modal (#29373 by @ClearlyClaire)\
Instead, the unfollow confirmation modal will always be displayed.
- Remove support for Capistrano (#27295 and #30009 by @mjankowski and @renchap)
### Fixed
- **Fix link preview cards not always preserving the original URL from the status** (#27312 by @Gargron)
- Fix log out from user menu not working on Safari (#31402 by @renchap)
- Fix various issues when in link preview card generation (#28748, #30017, #30362, #30173, #30853, #30929, #30933, #30957, #30987, and #31144 by @adamniedzielski, @oneiros, @phocks, @timothyjrogers, and @tribela)
- Fix handling of missing links in Webfinger responses (#31030 by @adamniedzielski)
- Fix error when accepting an appeal for sensitive posts deleted in the meantime (#32037 by @ClearlyClaire)
- Fix error when encountering reblog of deleted post in feed rebuild (#32001 by @ClearlyClaire)
- Fix Safari browser glitch related to horizontal scrolling (#31960 by @Gargron)
- Fix unresolvable mentions sometimes preventing processing incoming posts (#29215 by @tribela and @ClearlyClaire)
- Fix too many requests caused by relationship look-ups in web UI (#32042 by @Gargron)
- Fix links for reblogs in moderation interface (#31979 by @ClearlyClaire)
- Fix the appearance of avatars when they do not load (#31966 and #32270 by @Gargron and @renchap)
- Fix spurious error notifications for aborted requests in web UI (#31952 by @c960657)
- Fix HTTP 500 error in `/api/v1/polls/:id/votes` when required `choices` parameter is missing (#25598 by @danielmbrasil)
- Fix security context sometimes not being added in LD-Signed activities (#31871 by @ClearlyClaire)
- Fix cross-origin loading of `inert.css` polyfill (#30687 by @louis77)
- Fix wrapping in dashboard quick access buttons (#32043 by @renchap)
- Fix recently used tags hint being displayed in profile edition page when there is none (#32120 by @mjankowski)
- Fix checkbox lists on narrow screens in the settings interface (#32112 by @mjankowski)
- Fix the position of status action buttons being affected by interaction counters (#32084 by @renchap)
- Fix the summary of converted ActivityPub object types to be treated as HTML (#28629 by @Menrath)
- Fix cutoff of instance name in sign-up form (#30598 by @oneiros)
- Fix invalid date searches returning 503 errors (#31526 by @notchairmk)
- Fix invalid `visibility` values in `POST /api/v1/statuses` returning 500 errors (#31571 by @c960657)
- Fix some components re-rendering spuriously in web UI (#31879 and #31881 by @ClearlyClaire and @Gargron)
- Fix sort order of moderation notes on Reports and Accounts (#31528 by @ThisIsMissEm)
- Fix email language when recipient has no selected locale (#31747 by @ClearlyClaire)
- Fix frequently-used languages not correctly updating in the web UI (#31386 by @c960657)
- Fix `POST /api/v1/statuses` silently ignoring invalid `media_ids` parameter (#31681 by @c960657)
- Fix handling of the `BIND` environment variable in the streaming server (#31624 by @ThisIsMissEm)
- Fix empty `aria-hidden` attribute value in logo resources area (#30570 by @mjankowski)
- Fix “Redirect URI” field not being marked as required in “New application” form (#30311 by @ThisIsMissEm)
- Fix right-to-left text in preview cards (#30930 by @ClearlyClaire)
- Fix rack attack `match_type` value typo in logging config (#30514 by @mjankowski)
- Fix various cases of duplicate, missing, or inconsistent borders or scrollbar styles (#31068, #31286, #31268, #31275, #31284, #31305, #31346, #31372, #31373, #31389, #31432, #31391, #31445, #32091, #32147 and #32137 by @ClearlyClaire, @mjankowski, @valtlai and @vmstan)
- Fix editing description of media uploads with custom thumbnails (#32221 by @ClearlyClaire)
- Fix race condition in `POST /api/v1/push/subscription` (#30166 by @ClearlyClaire)
- Fix post deletion not being delayed when those are part of an account warning (#30163 by @ClearlyClaire)
- Fix rendering error on `/start` when not logged in (#30023 by @timothyjrogers)
- Fix unneeded requests to blocked domains when receiving relayed signed activities from them (#31161 by @ClearlyClaire)
- Fix logo pushing header buttons out of view on certain conditions in mobile layout (#29787 by @ClearlyClaire)
- Fix notification-related records not being reattributed when merging accounts (#29694 by @ClearlyClaire)
- Fix results/query in `api/v1/featured_tags/suggestions` (#29597 by @mjankowski)
- Fix distracting and confusing always-showing scrollbar track in boost confirmation modal (#31524 by @ClearlyClaire)
- Fix being able to upload more than 4 media attachments in some cases (#29183 by @mashirozx)
- Fix preview card player getting embedded when clicking on the external link button (#29457 by @ClearlyClaire)
- Fix full date display not respecting the locale 12/24h format (#29448 by @renchap)
- Fix filters title and keywords overflow (#29396 by @GeopJr)
- Fix incorrect date format in “Follows and followers” (#29390 by @JasonPunyon)
- Fix navigation item active highlight for some paths (#32159 by @mjankowski)
- Fix “Edit media” modal sizing and layout when space-constrained (#27095 by @ronilaukkarinen)
- Fix modal container bounds (#29185 by @nico3333fr)
- Fix inefficient HTTP signature parsing using regexps and `StringScanner` (#29133 by @ClearlyClaire)
- Fix moderation report updates through `PUT /api/v1/admin/reports/:id` not being logged in the audit log (#29044, #30342, and #31033 by @mjankowski, @tribela, and @vmstan)
- Fix moderation interface allowing to select rule violation when there are no server rules (#31458 by @ThisIsMissEm)
- Fix redirection from paths with url-encoded `@` to their decoded form (#31184 by @timothyjrogers)
- Fix Trending Tags pending review having an unstable sort order (#31473 by @ThisIsMissEm)
- Fix the emoji dropdown button always opening the dropdown instead of behaving like a toggle (#29012 by @jh97uk)
- Fix processing of incoming posts with bearcaps (#26527 by @kmycode)
- Fix support for IPv6 redis connections in streaming (#31229 by @ThisIsMissEm)
- Fix search form re-rendering spuriously in web UI (#28876 by @Gargron)
- Fix `RedownloadMediaWorker` not being called on transient S3 failure (#28714 by @ClearlyClaire)
- Fix ISO code for Canadian French from incorrect `fr-QC` to `fr-CA` (#26015 by @gunchleoc)
- Fix `.opus` file uploads being misidentified by Paperclip (#28580 by @vmstan)
- Fix loading local accounts with extraneous domain part in WebUI (#28559 by @ClearlyClaire)
- Fix destructive actions in dropdowns not using error color in light theme (#28484 by @logicalmoody)
- Fix call to inefficient `delete_matched` cache method in domain blocks (#28374 by @ClearlyClaire)
- Fix status edits not always being streamed to mentioned users (#28324 by @ClearlyClaire)
- Fix onboarding step descriptions being truncated on narrow screens (#28021 by @ClearlyClaire)
- Fix duplicate IDs in relationships and familiar_followers APIs (#27982 by @KevinBongart)
- Fix modal content not being selectable (#27813 by @pajowu)
- Fix Web UI not displaying appropriate explanation when a user hides their follows/followers (#27791 by @ClearlyClaire)
- Fix format-dependent redirects being cached regardless of requested format (#27632 by @ClearlyClaire)
- Fix confusing screen when visiting a confirmation link for an already-confirmed email (#27368 by @ClearlyClaire)
- Fix explore page reloading when you navigate back to it in web UI (#27489 by @Gargron)
- Fix missing redirection from `/home` to `/deck/home` in the advanced interface (#27378 by @Signez)
- Fix empty environment variables not using default nil value (#27400 by @renchap)
- Fix language sorting in settings (#27158 by @gunchleoc)
_For previous changes, review the [stable-4.2 branch](https://github.com/mastodon/mastodon/blob/stable-4.2/CHANGELOG.md)_
_For previous changes, review the [stable-4.3 branch](https://github.com/mastodon/mastodon/blob/stable-4.3/CHANGELOG.md)_

View File

@ -99,8 +99,8 @@ GEM
ast (2.4.3)
attr_required (1.0.2)
aws-eventstream (1.4.0)
aws-partitions (1.1257.0)
aws-sdk-core (3.251.0)
aws-partitions (1.1259.0)
aws-sdk-core (3.252.0)
aws-eventstream (~> 1, >= 1.3.0)
aws-partitions (~> 1, >= 1.992.0)
aws-sigv4 (~> 1.9)
@ -111,7 +111,7 @@ GEM
aws-sdk-kms (1.129.0)
aws-sdk-core (~> 3, >= 3.248.0)
aws-sigv4 (~> 1.5)
aws-sdk-s3 (1.225.0)
aws-sdk-s3 (1.225.1)
aws-sdk-core (~> 3, >= 3.248.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.5)

View File

@ -13,9 +13,8 @@ A "vulnerability in Mastodon" is a vulnerability in the code distributed through
## Supported Versions
| Version | Supported |
| ------- | ---------------- |
| 4.5.x | Yes |
| 4.4.x | Yes |
| 4.3.x | Until 2026-05-06 |
| < 4.3 | No |
| Version | Supported |
| ------- | --------- |
| 4.5.x | Yes |
| 4.4.x | Yes |
| < 4.4 | No |

View File

@ -13,7 +13,7 @@ class AuthorizeInteractionsController < ApplicationController
when Status
redirect_to web_url("@#{@resource.account.pretty_acct}/#{@resource.id}")
when Collection
redirect_to web_url("collections/#{resource.id}")
redirect_to web_url("collections/#{@resource.id}")
else
not_found
end

View File

@ -13,6 +13,7 @@ class Settings::ImportsController < Settings::BaseController
domain_blocking: 'blocked_domains_failures.csv',
bookmarks: 'bookmarks_failures.csv',
lists: 'lists_failures.csv',
custom_filters: 'custom_filters_failures.json',
}.freeze
TYPE_TO_HEADERS_MAP = {
@ -61,6 +62,21 @@ class Settings::ImportsController < Settings::BaseController
send_data export_data, filename: filename
end
format.json do
filename = TYPE_TO_FILENAME_MAP[@bulk_import.type.to_sym]
data_collection = { custom_filters: [] }
@bulk_import.rows.find_each do |row|
case @bulk_import.type.to_sym
when :custom_filters
data_collection[:custom_filters] << row.data
end
end
export_data = JSON.generate(data_collection)
send_data export_data, filename: filename
end
end
end

View File

@ -30,8 +30,8 @@ export const HoverCardController: React.FC = () => {
const cardRef = useRef<HTMLDivElement | null>(null);
const [setLeaveTimeout, cancelLeaveTimeout] = useTimeout();
const [setEnterTimeout, cancelEnterTimeout, delayEnterTimeout] = useTimeout();
const [setMoveTimeout, cancelMoveTimeout] = useTimeout();
const [setScrollTimeout] = useTimeout();
const lastMouseMoveTime = useRef<number>(0);
const handleClose = useCallback(() => {
cancelEnterTimeout();
@ -50,7 +50,6 @@ export const HoverCardController: React.FC = () => {
useEffect(() => {
let isScrolling = false;
let isUsingTouch = false;
let isActiveMouseMovement = false;
let currentAnchor: HTMLElement | null = null;
let currentTitle: string | null = null;
@ -84,31 +83,40 @@ export const HoverCardController: React.FC = () => {
return;
}
// Bail out if we're scrolling, a touch is active,
// or if there was no active mouse movement
if (isScrolling || !isActiveMouseMovement || isUsingTouch) {
return;
}
// This 0ms timeout is needed to push processing of this code
// until after the mousemove event was run, in order to be able
// to track the most recent value of lastMouseMoveTime.current
setTimeout(() => {
// Check if mouse moved within the active movement threshold
const timeSinceLastMove = Date.now() - lastMouseMoveTime.current;
const hasRecentMovement = timeSinceLastMove < activeMovementThreshold;
// We've entered an anchor
if (isHoverCardAnchor(target)) {
cancelLeaveTimeout();
// Bail out if we're scrolling, a touch is active,
// or if there was no active mouse movement
if (isScrolling || !hasRecentMovement || isUsingTouch) {
return;
}
currentAnchor?.removeAttribute('aria-describedby');
currentAnchor = target;
// We've entered an anchor
if (isHoverCardAnchor(target)) {
cancelLeaveTimeout();
currentTitle = target.getAttribute('title');
target.removeAttribute('title');
currentAnchor?.removeAttribute('aria-describedby');
currentAnchor = target;
setEnterTimeout(() => {
open(target);
}, enterDelay);
}
currentTitle = target.getAttribute('title');
target.removeAttribute('title');
// We've entered the hover card
if (target === currentAnchor || target === cardRef.current) {
cancelLeaveTimeout();
}
setEnterTimeout(() => {
open(target);
}, enterDelay);
}
// We've entered the hover card
if (target === currentAnchor || target === cardRef.current) {
cancelLeaveTimeout();
}
}, 0);
};
const handleMouseLeave = (e: MouseEvent) => {
@ -149,18 +157,15 @@ export const HoverCardController: React.FC = () => {
isUsingTouch = false;
}
const hasMoved = Math.max(e.movementX, e.movementY) > 0;
const hasMoved =
Math.max(Math.abs(e.movementX), Math.abs(e.movementY)) > 0;
if (!hasMoved) {
return;
}
delayEnterTimeout(enterDelay);
cancelMoveTimeout();
isActiveMouseMovement = true;
setMoveTimeout(() => {
isActiveMouseMovement = false;
}, activeMovementThreshold);
lastMouseMoveTime.current = Date.now();
};
document.body.addEventListener('touchstart', handleTouchStart, {
@ -204,8 +209,6 @@ export const HoverCardController: React.FC = () => {
setOpen,
setAccountId,
setAnchor,
setMoveTimeout,
cancelMoveTimeout,
]);
return (

View File

@ -3,6 +3,7 @@ import { useCallback, useId, useState } from 'react';
import { FormattedMessage, useIntl, defineMessages } from 'react-intl';
import type { ApiCollectionJSON } from '@/mastodon/api_types/collections';
import { EmptyState } from '@/mastodon/components/empty_state';
import { LoadingIndicator } from '@/mastodon/components/loading_indicator';
import { NavigationFocusTarget } from '@/mastodon/components/navigation_focus_target';
import { useCurrentAccountId } from '@/mastodon/hooks/useAccountId';
@ -16,7 +17,10 @@ import { IconButton } from 'mastodon/components/icon_button';
import { useAppDispatch, useAppSelector } from 'mastodon/store';
import { MAX_COLLECTION_ACCOUNT_COUNT } from '../collections/editor/accounts';
import { useCollectionsCreatedBy } from '../collections/overview/created_by_account';
import {
NewCollectionButton,
useCollectionsCreatedBy,
} from '../collections/overview/created_by_account';
import { CollectionToggle } from './collection_toggle';
@ -136,6 +140,23 @@ export const CollectionAdder: React.FC<{
>
{status === 'loading' || !account ? (
<LoadingIndicator />
) : collections.length === 0 ? (
<EmptyState
title={
<FormattedMessage
id='empty_column.collections_self'
defaultMessage='You have not created any collections yet.'
/>
}
message={
<FormattedMessage
id='empty_column.account_featured_self.showcase_accounts_desc'
defaultMessage='Collections are curated lists of accounts to help others discover more of the Fediverse.'
/>
}
>
<NewCollectionButton onClick={onClose} />
</EmptyState>
) : (
collections.map((item) => (
<ListItem key={item.id} collection={item} account={account} />

View File

@ -19,7 +19,7 @@ const messages = defineMessages({
revokeCollectionInclusionMessage: {
id: 'confirmations.revoke_collection_inclusion.message',
defaultMessage:
"This action is permanent, and the curator won't be able to re-add you to the collection later on.",
"The curator won't be able to re-add you to this collection for 24 hours. To prevent them from adding you to collections permanently, you can block them.",
},
revokeCollectionInclusionConfirm: {
id: 'confirmations.revoke_collection_inclusion.confirm',

View File

@ -26,8 +26,14 @@ import {
} from '../editor';
import classes from '../styles.module.scss';
const CreateButton: React.FC = () => (
<Link to='/collections/new' className='button button--compact'>
export const NewCollectionButton: React.FC<{ onClick?: () => void }> = ({
onClick,
}) => (
<Link
to='/collections/new'
className='button button--compact'
onClick={onClick}
>
<Icon id='plus' icon={AddIcon} />
<FormattedMessage {...editorMessages.newCollection} />
</Link>
@ -97,7 +103,7 @@ export const CollectionsCreatedByAccount: React.FC = () => {
/>
}
>
<CreateButton />
<NewCollectionButton />
</EmptyState>
);
} else {
@ -129,7 +135,7 @@ export const CollectionsCreatedByAccount: React.FC = () => {
}}
/>
</h2>
{showCreateButton && <CreateButton />}
{showCreateButton && <NewCollectionButton />}
</div>
<ItemList>
{isOwnCollectionPage && !canCreateMoreCollections && (

View File

@ -316,8 +316,8 @@ class ComposeForm extends ImmutablePureComponent {
className='compose-form__input'
/>
<UploadForm />
<PollForm />
<UploadForm />
<ComposeQuotedStatus />
<div className='compose-form__footer'>

View File

@ -5,6 +5,7 @@ import { customEmojiFactory, unicodeEmojiFactory } from '@/testing/factories';
import {
putEmojiData,
search,
loadEmojiByHexcode,
testClear,
testGet,
@ -31,6 +32,108 @@ describe('emoji database', () => {
indexedDB = new IDBFactory();
});
describe('search', () => {
beforeEach(async () => {
await putEmojiData([], 'en');
});
test('test no query tokens', async () => {
await putEmojiData([rawEmojiFactory()], 'en');
await expect(search({ query: ' ', locale: 'en' })).resolves.toEqual([]);
});
test('unicode results', async () => {
await putEmojiData(
[
rawEmojiFactory({
hexcode: 'unicode_hex',
label: 'Party Popper',
shortcodes: ['party_popper'],
unicode: '🎉',
}),
],
'en',
);
await expect(
search({ query: 'party', locale: 'en' }),
).resolves.toContainEqual(
expect.objectContaining({
hexcode: 'unicode_hex',
}),
);
});
test('custom results', async () => {
await putCustomEmojiData({
emojis: [customEmojiFactory({ shortcode: 'party_custom' })],
});
await expect(
search({ query: 'party', locale: 'en' }),
).resolves.toContainEqual(
expect.objectContaining({
shortcode: 'party_custom',
}),
);
});
test('shortcode results', async () => {
await putEmojiData([rawEmojiFactory()], 'en');
await putLegacyShortcodes({
test: ['legacy_smile'],
});
await expect(
search({ query: 'legacy', locale: 'en' }),
).resolves.toContainEqual(
expect.objectContaining({
hexcode: 'test',
}),
);
});
test('full custom emoji search', async () => {
await putCustomEmojiData({
emojis: [
customEmojiFactory({ shortcode: 'arrow' }),
customEmojiFactory({ shortcode: 'party_parrot' }),
],
});
const result = await search({ query: 'arro', locale: 'en' });
expect(result).toContainEqual(
// Test for ordinary IDB search
expect.objectContaining({
shortcode: 'arrow',
}),
);
expect(result).toContainEqual(
// Test for manual iteration search
expect.objectContaining({
shortcode: 'party_parrot',
}),
);
});
test('limit test', async () => {
await putCustomEmojiData({
emojis: [
customEmojiFactory({ shortcode: 'limit' }),
customEmojiFactory({ shortcode: 'limit_extra' }),
],
});
await expect(
search({ query: 'limit', locale: 'en', limit: 1 }),
).resolves.toEqual([
expect.objectContaining({
shortcode: 'limit',
}),
]);
});
});
describe('putEmojiData', () => {
test('adds to loaded locales', async () => {
const { loadedLocales } = await testGet();

View File

@ -12,7 +12,7 @@ import {
transformCustomEmojiData,
transformEmojiData,
} from './normalize';
import type { AnyEmojiData, CacheKey } from './types';
import type { AnyEmojiData, CacheKey, CustomEmojiData } from './types';
import { emojiLogger } from './utils';
const loadedLocales = new Set<Locale>();
@ -78,6 +78,8 @@ export async function search({
// Create an array of emoji results
const db = await loadDB();
const resultArrays: ScoreMap[] = [];
const existingCustomShortcodes = new Set<string>();
for (let i = 0; i < queryTokens.length; i++) {
const token = queryTokens[i];
if (!token) continue;
@ -96,6 +98,7 @@ export async function search({
],
);
const resultMap: ScoreMap = new Map();
for (const emoji of unicodeResults) {
const score = getScoreForEmoji(emoji, token);
if (score === null) {
@ -103,11 +106,13 @@ export async function search({
}
resultMap.set(emoji.hexcode, { ...emoji, score });
}
for (const emoji of customResults) {
const score = getScoreForEmoji(emoji, token);
if (score === null) {
continue;
}
existingCustomShortcodes.add(emoji.shortcode);
resultMap.set(emoji.shortcode, { ...emoji, score });
}
@ -119,7 +124,14 @@ export async function search({
if (!emoji) {
continue;
}
const score = getScoreForEmoji(emoji, token);
// Score the emoji with the legacy shortcode, even though it's not part of the emoji.
const score = getScoreForEmoji(
{
...emoji,
shortcodes: [...shortcodeResult.shortcodes, ...emoji.shortcodes],
},
token,
);
if (score === null) {
continue;
}
@ -146,21 +158,19 @@ export async function search({
);
// If there are no results, try a cursor-based custom emoji search instead.
if (results.length === 0) {
const trx = db.transaction('custom', 'readonly');
const foundEmojis = new Set<string>();
for await (const cursor of trx.store) {
const emoji = cursor.value;
const score = getScoreForEmoji(emoji, query, false);
if (score === null || foundEmojis.has(emoji.shortcode)) {
continue;
}
results.push({ ...emoji, score });
foundEmojis.add(emoji.shortcode);
if (results.length === 0 || results.length < limit) {
const customEmojisFound = await fullCustomSearch(
query,
existingCustomShortcodes,
);
if (customEmojisFound.length > 0) {
log(
'cursor search found %d results for "%s"',
customEmojisFound.length,
query,
);
results.push(...customEmojisFound);
}
log('cursor search found %d results for "%s"', foundEmojis.size, query);
await trx.done;
}
// Sort by score, descending.
@ -191,7 +201,14 @@ function getScoreForEmoji(
}
let index = 1;
const searchTokens = checkTokens ? [id, ...emoji.tokens] : [id];
const searchTokens = [id];
if (checkTokens) {
// Check shortcodes before tokens as they are more important.
if ('shortcodes' in emoji) {
searchTokens.push(...emoji.shortcodes);
}
searchTokens.push(...emoji.tokens);
}
for (const token of searchTokens) {
const tokenIndex = token.indexOf(query);
if (tokenIndex !== -1) {
@ -203,6 +220,51 @@ function getScoreForEmoji(
return null;
}
async function fullCustomSearch(query: string, existing = new Set<string>()) {
const db = await loadDB();
const trx = db.transaction('custom', 'readonly');
const foundEmojis = new Set<string>();
// First iterate over chunks of 1,000 custom emoji keys and find any matches.
const chunkSize = 1_000;
let lastKey: string | null = null;
let keys: string[] = [];
do {
const keyRange = lastKey ? IDBKeyRange.lowerBound(lastKey, true) : null;
keys = await trx.store.getAllKeys(keyRange, chunkSize);
if (keys.length === 0) {
break;
}
log('cursor search got batch of %d emojis', keys.length);
lastKey = keys.at(-1) ?? null;
for (const key of keys) {
if (!foundEmojis.has(key) && !existing.has(key) && key.includes(query)) {
foundEmojis.add(key);
}
}
} while (keys.length === chunkSize);
// Next get the full emojis for all matches.
const emojis = await Promise.all(
foundEmojis.keys().map((key) => trx.store.get(key)),
);
const results: (CustomEmojiData & { score: number })[] = [];
for (const emoji of emojis) {
if (emoji) {
const score = getScoreForEmoji(emoji, query, false);
if (score && score > 0) {
results.push({
score,
...emoji,
});
}
}
}
return results;
}
export async function putEmojiData(emojis: CompactEmoji[], locale: Locale) {
loadedLocales.add(locale);
const db = await loadDB();

View File

@ -649,6 +649,7 @@
"empty_column.collections": "{acct} пакуль не стварыў(-ла) аніводнай калекцыі.",
"empty_column.collections.featured_in": "Вас пакуль не дадалі ў ніякія калекцыі.",
"empty_column.collections.featured_in_undiscoverable": "Каб людзі маглі дадаваць Вас у калекцыі, Вам трэба даць ім дазвол знаходзіць Вас у <link>Налады > Прыватнасць і пошук</link>",
"empty_column.collections_self": "Вы пакуль не стварылі ніводнай калекцыі.",
"empty_column.community": "Мясцовая стужка пустая. Напішыце нешта публічнае, каб разварушыць справу!",
"empty_column.direct": "Пакуль у Вас няма асабістых згадванняў. Калі Вы дашляце або атрымаеце штосьці, яно з’явіцца тут.",
"empty_column.disabled_feed": "Гэта стужка была адключаная Вашымі адміністратарамі сервера.",

View File

@ -649,6 +649,7 @@
"empty_column.collections": "{acct} har endnu ikke oprettet nogen samlinger.",
"empty_column.collections.featured_in": "Du er ikke blevet tilføjet til nogen samlinger endnu.",
"empty_column.collections.featured_in_undiscoverable": "For at andre kan føje dig til samlinger, skal du give tilladelse til at blive vist i opdagelsesfunktioner under <link>Præferencer > Fortrolighed og rækkevidde</link>",
"empty_column.collections_self": "Du har endnu ikke oprettet nogen samlinger.",
"empty_column.community": "Den lokale tidslinje er tom. Skriv noget offentligt for at sætte tingene i gang!",
"empty_column.direct": "Du har ikke nogen private omtaler endnu. Når du sender eller modtager en, vil den blive vist her.",
"empty_column.disabled_feed": "Dette feed er blevet deaktiveret af dine serveradministratorer.",

View File

@ -649,6 +649,7 @@
"empty_column.collections": "{acct} hat noch keine Sammlungen erstellt.",
"empty_column.collections.featured_in": "Du wurdest noch keiner Sammlung hinzugefügt.",
"empty_column.collections.featured_in_undiscoverable": "Damit du zu Sammlungen hinzugefügt werden kannst, muss „Mich beim Entdecken berücksichtigen“ unter <link>Einstellungen > Datenschutz und Reichweite</link> aktiviert werden",
"empty_column.collections_self": "Du hast noch keine Sammlungen erstellt.",
"empty_column.community": "Die lokale Timeline ist leer. Schreibe einen öffentlichen Beitrag, um den Stein ins Rollen zu bringen!",
"empty_column.direct": "Du hast noch keine privaten Erwähnungen. Sobald du eine sendest oder erhältst, wird sie hier erscheinen.",
"empty_column.disabled_feed": "Diesen Feed haben deine Server-Administrator*innen deaktiviert.",

View File

@ -649,6 +649,7 @@
"empty_column.collections": "Ο χρήστης {acct} δεν έχει δημιουργήσει ακόμη καμία συλλογή.",
"empty_column.collections.featured_in": "Δεν έχετε προστεθεί ακόμη σε καμία συλλογή.",
"empty_column.collections.featured_in_undiscoverable": "Προκειμένου ο κόσμος να σας προσθέσει σε συλλογές, πρέπει να επιτρέψετε την ανάδειξή σας σε εμπειρίες ανακάλυψης από τις <link>Προτιμήσεις > Ιδιωτικότητα και προσιτότητα</link>",
"empty_column.collections_self": "Δεν έχετε δημιουργήσει ακόμη καμία συλλογή.",
"empty_column.community": "Η τοπική ροή είναι κενή. Γράψε κάτι δημόσια για να αρχίσει να κυλά η μπάλα!",
"empty_column.direct": "Δεν έχεις καμία ιδιωτική επισήμανση ακόμη. Όταν στείλεις ή λάβεις μία, θα εμφανιστεί εδώ.",
"empty_column.disabled_feed": "Αυτή η ροή έχει απενεργοποιηθεί από τους διαχειριστές του διακομιστή σας.",

View File

@ -561,7 +561,7 @@
"confirmations.remove_from_followers.message": "{name} will stop following you. Are you sure you want to proceed?",
"confirmations.remove_from_followers.title": "Remove follower?",
"confirmations.revoke_collection_inclusion.confirm": "Remove me",
"confirmations.revoke_collection_inclusion.message": "This action is permanent, and the curator won't be able to re-add you to the collection later on.",
"confirmations.revoke_collection_inclusion.message": "The curator won't be able to re-add you to this collection for 24 hours. To prevent them from adding you to collections permanently, you can block them.",
"confirmations.revoke_collection_inclusion.title": "Remove yourself from this collection?",
"confirmations.revoke_quote.confirm": "Remove post",
"confirmations.revoke_quote.message": "This action cannot be undone.",
@ -649,6 +649,7 @@
"empty_column.collections": "{acct} has not created any collections yet.",
"empty_column.collections.featured_in": "You have not been added to any collections yet.",
"empty_column.collections.featured_in_undiscoverable": "In order for people to add you to collections, you need to allow featuring in discovery experiences from <link>Preferences > Privacy and reach</link>",
"empty_column.collections_self": "You have not created any collections yet.",
"empty_column.community": "The local timeline is empty. Write something publicly to get the ball rolling!",
"empty_column.direct": "You don't have any private mentions yet. When you send or receive one, it will show up here.",
"empty_column.disabled_feed": "This feed has been disabled by your server administrators.",

View File

@ -649,6 +649,7 @@
"empty_column.collections": "{acct} todavía no creó ninguna colección.",
"empty_column.collections.featured_in": "Todavía no te agregaron a ninguna colección.",
"empty_column.collections.featured_in_undiscoverable": "Para que la gente pueda agregarte a colecciones, tenés que permitir que te destacen en las experiencias de descubrimiento en <link>Configuración > Privacidad y alcance</link>",
"empty_column.collections_self": "Aún no has creado ninguna colección.",
"empty_column.community": "La línea temporal local está vacía. ¡Escribí algo en modo público para que se empiece a correr la bola!",
"empty_column.direct": "Todavía no tenés ninguna mención privada. Cuando enviés o recibás una, se mostrará acá.",
"empty_column.disabled_feed": "Esta línea temporal fue deshabilitada por los administradores de tu servidor.",

View File

@ -649,6 +649,7 @@
"empty_column.collections": "{acct} todavía no ha creado ninguna colección.",
"empty_column.collections.featured_in": "Aún no te han añadido a ninguna colección.",
"empty_column.collections.featured_in_undiscoverable": "Para que los usuarios puedan añadirte a sus colecciones, debes habilitar la opción de aparecer en las experiencias de descubrimiento desde <link>Preferencias > Privacidad y alcance</link>",
"empty_column.collections_self": "Aún no has creado ninguna colección.",
"empty_column.community": "La cronología local está vacía. ¡Escribe algo públicamente para ponerla en marcha!",
"empty_column.direct": "Aún no tienes ninguna mención privada. Cuando envíes o recibas una, aparecerá aquí.",
"empty_column.disabled_feed": "Esta cronología fue desactivada por los administradores de tu servidor.",
@ -780,7 +781,7 @@
"home.pending_critical_update.link": "Ver actualizaciones",
"home.pending_critical_update.title": "¡Actualización de seguridad crítica disponible!",
"home.show_announcements": "Mostrar anuncios",
"ignore_notifications_modal.bots_title": "¿Ignorar notificaciones de bots?",
"ignore_notifications_modal.bots_title": "¿Ignorar las notificaciones de los bots?",
"ignore_notifications_modal.disclaimer": "Mastodon no puede informar a los usuarios que has ignorado sus notificaciones. Ignorar notificaciones no impedirá que se sigan enviando los mensajes.",
"ignore_notifications_modal.filter_instead": "Filtrar en su lugar",
"ignore_notifications_modal.filter_to_act_users": "Aún podrás aceptar, rechazar o reportar usuarios",

View File

@ -649,6 +649,7 @@
"empty_column.collections": "{acct} aún no ha creado ninguna colección.",
"empty_column.collections.featured_in": "Aún no te han añadido a ninguna colección.",
"empty_column.collections.featured_in_undiscoverable": "Para que la gente pueda añadirte a colecciones, debes permitir ser destacado en algoritmos de descubrimiento desde <link>Preferencias > Privacidad y alcance</link>",
"empty_column.collections_self": "Aún no has creado ninguna colección.",
"empty_column.community": "La línea de tiempo local está vacía. ¡Escribe algo para empezar la fiesta!",
"empty_column.direct": "Aún no tienes menciones privadas. Cuando envíes o recibas una, aparecerán aquí.",
"empty_column.disabled_feed": "Esta cronología ha sido desactivada por los administradores del servidor.",

View File

@ -649,6 +649,7 @@
"empty_column.collections": "{acct} ei ole luonut vielä yhtään kokoelmaa.",
"empty_column.collections.featured_in": "Sinua ei ole vielä lisätty mihinkään kokoelmaan.",
"empty_column.collections.featured_in_undiscoverable": "Jotta sinut voi lisätä kokoelmiin, esittely löydettävyyskokemuksissa on sallittava kohdassa <link>Asetukset > Yksityisyys ja tavoittavuus</link>",
"empty_column.collections_self": "Et ole luonut vielä yhtään kokoelmaa.",
"empty_column.community": "Paikallinen aikajana on tyhjä. Kirjoita jotain julkista, niin homma lähtee käyntiin!",
"empty_column.direct": "Yksityismainintoja ei vielä ole. Jos lähetät tai sinulle lähetetään sellaisia, näet ne täällä.",
"empty_column.disabled_feed": "Palvelimen ylläpito on poistanut käytöstä tämän syötteen.",

View File

@ -649,6 +649,7 @@
"empty_column.collections": "{acct} n'a pas encore créé de collection.",
"empty_column.collections.featured_in": "Vous n'avez pas encore été ajouté·e à une collection.",
"empty_column.collections.featured_in_undiscoverable": "Afin que l'on puisse vous ajouter à des collections, vous devez autoriser d'apparaître dans les expériences de découverte depuis <link>Vie privée et visibilité</link>",
"empty_column.collections_self": "Vous n'avez pas encore créé de collection.",
"empty_column.community": "Le fil local est vide. Écrivez donc quelque chose pour le remplir!",
"empty_column.direct": "Vous n'avez pas encore de mentions privées. Quand vous en envoyez ou en recevez, elles apparaîtront ici.",
"empty_column.disabled_feed": "Ce flux a été désactivé par les administrateur·rice·s de votre serveur.",

View File

@ -649,6 +649,7 @@
"empty_column.collections": "{acct} n'a pas encore créé de collection.",
"empty_column.collections.featured_in": "Vous n'avez pas encore été ajouté·e à une collection.",
"empty_column.collections.featured_in_undiscoverable": "Afin que l'on puisse vous ajouter à des collections, vous devez autoriser d'apparaître dans les expériences de découverte depuis <link>Vie privée et visibilité</link>",
"empty_column.collections_self": "Vous n'avez pas encore créé de collection.",
"empty_column.community": "Le fil public local est vide. Écrivez donc quelque chose pour le remplir!",
"empty_column.direct": "Vous n'avez pas encore de mentions privées. Quand vous en enverrez ou recevrez, elles apparaîtront ici.",
"empty_column.disabled_feed": "Ce flux a été désactivé par les administrateur·rice·s de votre serveur.",

View File

@ -649,6 +649,7 @@
"empty_column.collections": "Níl aon bhailiúcháin cruthaithe ag {acct} go fóill.",
"empty_column.collections.featured_in": "Níor cuireadh le haon bhailiúchán thú go fóill.",
"empty_column.collections.featured_in_undiscoverable": "Chun go mbeidh daoine in ann tú a chur le bailiúcháin, ní mór duit cead a thabhairt duit a bheith le feiceáil i dtaithí fionnachtana ó <link>Sainroghanna > Príobháideacht agus raon feidhme</link>",
"empty_column.collections_self": "Níl aon bhailiúcháin cruthaithe agat go fóill.",
"empty_column.community": "Tá an amlíne áitiúil folamh. Foilsigh rud éigin go poiblí le tús a chur le cúrsaí!",
"empty_column.direct": "Níl aon tagairtí príobháideacha agat fós. Nuair a sheolann tú nó a gheobhaidh tú ceann, beidh sé le feiceáil anseo.",
"empty_column.disabled_feed": "Tá an fotha seo díchumasaithe ag riarthóirí do fhreastalaí.",

View File

@ -649,6 +649,7 @@
"empty_column.collections": "{acct} non ha ancora creato alcuna collezione.",
"empty_column.collections.featured_in": "Non sei ancora stato/a aggiunto/a ad alcuna collezione.",
"empty_column.collections.featured_in_undiscoverable": "Affinché le persone possano aggiungerti alle collezioni, è necessario consentire che il profilo sia visibile nelle esperienze di scoperta tramite <link>Preferenze > Privacy e visibilità</link>",
"empty_column.collections_self": "Non hai ancora creato alcuna collezione.",
"empty_column.community": "La cronologia locale è vuota. Scrivi qualcosa pubblicamente per dare inizio alla festa!",
"empty_column.direct": "Non hai ancora alcuna menzione privata. Quando ne invierai o riceverai una, apparirà qui.",
"empty_column.disabled_feed": "Questo feed è stato disabilitato dagli amministratori del tuo server.",

View File

@ -78,7 +78,7 @@
"account.media": "Multivide",
"account.mention": "Pieminēt @{name}",
"account.menu.add_to_list": "Pievienot sarakstam…",
"account.menu.block": "Bloķēt kontu",
"account.menu.block": "Liegt kontu",
"account.menu.block_domain": "Liegt {domain}",
"account.menu.copy": "Kopēt saiti",
"account.menu.mute": "Apklusināt kontu",
@ -112,7 +112,7 @@
"account.unblock": "Atcelt liegšanu @{name}",
"account.unblock_domain": "Atcelt domēna {domain} liegšanu",
"account.unblock_domain_short": "Atcelt liegšanu",
"account.unblock_short": "Atbloķēt",
"account.unblock_short": "Atcelt liegšanu",
"account.unendorse": "Neizcelt profilā",
"account.unfollow": "Pārstāt sekot",
"account.unmute": "Noņemt apklusinājumu @{name}",
@ -178,11 +178,11 @@
"annual_report.summary.percentile.we_wont_tell_bernie": "Mēs neteiksim Bērnijam.",
"attachments_list.unprocessed": "(neapstrādāti)",
"audio.hide": "Slēpt audio",
"block_modal.remote_users_caveat": "Mēs lūgsim serverim {domain} ņemt vērā Tavu lēmumu. Tomēr sadarbība nav garantēta, jo atsevišķi serveri bloķēšanu var apstrādāt citādi. Publiski ieraksti, iespējams, joprojām būs redzami lietotājiem, kuri nav pieteikušies.",
"block_modal.remote_users_caveat": "Mēs vaicāsim serverim {domain} ņemt vērā Tavu lēmumu. Tomēr atbilstība nav nodrošināta, jo atsevišķi serveri liegšanu var apstrādāt citādi. Publiski ieraksti joprojām var būt redzami lietotājiem, kuri nav pieteikušies.",
"block_modal.show_less": "Rādīt mazāk",
"block_modal.show_more": "Rādīt vairāk",
"block_modal.they_will_know": "Lietotājs varēs redzēt, ka ir bloķēts.",
"block_modal.title": "Bloķēt lietotāju?",
"block_modal.they_will_know": "Lietotājs varēs redzēt, ka ir liegts.",
"block_modal.title": "Liegt lietotāju?",
"boost_modal.combo": "Nospied {combo}, lai nākamreiz šo izlaistu",
"boost_modal.reblog": "Pastiprināt ierakstu?",
"boost_modal.undo_reblog": "Atcelt ieraksta pastiprināšanu?",
@ -204,7 +204,7 @@
"closed_registrations_modal.preamble": "Mastodon ir decentralizēts, tāpēc neatkarīgi no tā, kur Tu izveido savu kontu, varēsi sekot un mijiedarboties ar ikvienu šajā serverī. Tu pat vari to pašizvietot!",
"closed_registrations_modal.title": "Reģistrēšanās Mastodon",
"collections.accounts.empty_title": "Šī kolekcija ir tukša",
"collections.block_collection_owner": "Bloķēt kontu",
"collections.block_collection_owner": "Liegt kontu",
"collections.collection_description": "Apraksts",
"collections.collection_language": "Valoda",
"collections.collection_language_none": "Nav",
@ -316,8 +316,8 @@
"confirmations.revoke_quote.confirm": "Noņemt ierakstu",
"confirmations.revoke_quote.message": "Šo darbību nevar atsaukt.",
"confirmations.revoke_quote.title": "Noņemt ierakstu?",
"confirmations.unblock.confirm": "Atbloķēt",
"confirmations.unblock.title": "Atbloķēt {name}?",
"confirmations.unblock.confirm": "Atcelt liegšanu",
"confirmations.unblock.title": "Atcelt {name} liegumu?",
"confirmations.unfollow.confirm": "Pārstāt sekot",
"confirmations.unfollow.title": "Pārtraukt sekot {name}?",
"confirmations.withdraw_request.confirm": "Atsaukt pieprasījumu",
@ -341,7 +341,7 @@
"dismissable_banner.dismiss": "Atcelt",
"dismissable_banner.public_timeline": "Šie ir jaunākie Fediverse lietotāju publiskie ieraksti, kuriem {domain} seko cilvēki.",
"domain_block_modal.block": "Liegt serveri",
"domain_block_modal.block_account_instead": "Tā vietā bloķēt @{name}",
"domain_block_modal.block_account_instead": "Tā vietā liegt @{name}",
"domain_block_modal.they_cant_follow": "Neviens no šī servera nevarēs Tev sekot.",
"domain_block_modal.they_wont_know": "Viņi nezinās, ka tikuši liegti.",
"domain_block_modal.title": "Liegt domēnu?",

View File

@ -649,6 +649,7 @@
"empty_column.collections": "{acct} não criou nenhuma coleção ainda.",
"empty_column.collections.featured_in": "Você ainda não foi adicionado a uma coleção.",
"empty_column.collections.featured_in_undiscoverable": "Para as pessoas adicionarem você às coleções, você precisa permitir destaque de experiências de descoberta através de <link>Preferências > Privacidade e alcance</link>",
"empty_column.collections_self": "Você ainda não criou nenhuma coleção.",
"empty_column.community": "A linha local está vazia. Publique algo para começar!",
"empty_column.direct": "Você ainda não possui menções privadas. Caso você enviar ou receber uma, ela aparecerá aqui.",
"empty_column.disabled_feed": "Este feed foi desativado pelos administradores de seu servidor.",

View File

@ -644,6 +644,7 @@
"empty_column.collections": "{acct} ska krijuar ende ndonjë koleksion.",
"empty_column.collections.featured_in": "Sjeni shtuar ende te ndonjë koleksion.",
"empty_column.collections.featured_in_undiscoverable": "Që njerëzit tju shtojnë te koleksione, lypset të lejoni shfaqjen si i zgjedhur në raste zbulimi, që nga <link>Parapëlqime > Privatësi dhe shtrirje</link>",
"empty_column.collections_self": "Skeni krijuar ende ndonjë koleksion.",
"empty_column.community": "Rrjedha kohore vendore është e zbrazët. Shkruani diçka publikisht që ti hyhet valles!",
"empty_column.direct": "Skeni ende ndonjë përmendje private. Kur dërgoni ose merrni një të tillë, do të shfaqet këtu.",
"empty_column.disabled_feed": "Kjo prurje është çaktivizuar nga përgjegjësit e shërbyesit tuaj.",

View File

@ -649,6 +649,7 @@
"empty_column.collections": "{acct} har ännu inte skapat några samlingar.",
"empty_column.collections.featured_in": "Du har inte lagts till i några samlingar än.",
"empty_column.collections.featured_in_undiscoverable": "För att personer ska kunna lägga till dig i samlingar, måste du tillåta att inkluderas i upptäcktsupplevelser från <link>Inställningar> Sekretess och räckvidd</link>",
"empty_column.collections_self": "Du har inte skapat några samlingar än.",
"empty_column.community": "Den lokala tidslinjen är tom. Skriv något offentligt för att sätta bollen i rullning!",
"empty_column.direct": "Du har inga privata omnämnanden. När du skickar eller tar emot ett direktmeddelande kommer det att visas här.",
"empty_column.disabled_feed": "Detta flöde har inaktiverats av dina serveradministratörer.",
@ -780,6 +781,7 @@
"home.pending_critical_update.link": "Se uppdateringar",
"home.pending_critical_update.title": "En kritisk säkerhetsuppdatering är tillgänglig!",
"home.show_announcements": "Visa notiser",
"ignore_notifications_modal.bots_title": "Ignorera aviseringar från botar?",
"ignore_notifications_modal.disclaimer": "Mastodon kan inte informera användarna om att du har ignorerat deras meddelanden. Ignorering av aviseringar kommer inte att stoppa själva meddelandena från att skickas.",
"ignore_notifications_modal.filter_instead": "Filtrera istället",
"ignore_notifications_modal.filter_to_act_users": "Du kommer fortfarande att kunna acceptera, avvisa eller rapportera användare",
@ -846,6 +848,7 @@
"keyboard_shortcuts.toggle_sensitivity": "Visa/gömma media",
"keyboard_shortcuts.toot": "Starta nytt inlägg",
"keyboard_shortcuts.top": "Flytta till början av listan",
"keyboard_shortcuts.translate": "Översätt ett inlägg",
"keyboard_shortcuts.unfocus": "Avfokusera skrivfält/sökfält",
"keyboard_shortcuts.up": "Flytta uppåt i listan",
"learn_more_link.got_it": "Jag förstår",
@ -1041,6 +1044,8 @@
"notifications.policy.drop": "Ignorera",
"notifications.policy.drop_hint": "Skicka ut i tomrummet för aldrig synas till igen",
"notifications.policy.filter": "Filtrera",
"notifications.policy.filter_bots_hint": "Konton markerade som automatiserade",
"notifications.policy.filter_bots_title": "Botar",
"notifications.policy.filter_hint": "Skicka till inkorgen för filtrerade aviserings",
"notifications.policy.filter_limited_accounts_hint": "Begränsade av servermoderatorer",
"notifications.policy.filter_limited_accounts_title": "Modererade konton",

View File

@ -649,6 +649,7 @@
"empty_column.collections": "{acct} henüz bir koleksiyon oluşturmadı.",
"empty_column.collections.featured_in": "Henüz herhangi bir koleksiyona eklenmediniz.",
"empty_column.collections.featured_in_undiscoverable": "Kullanıcıların sizi koleksiyonlarına ekleyebilmesi için, <link>Tercihler > Gizlilik ve erişim</link> bölümünden keşif deneyimlerinde öne çıkarılmaya izin vermeniz gerekir",
"empty_column.collections_self": "Henüz bir koleksiyon oluşturmadınız.",
"empty_column.community": "Yerel zaman çizelgesi boş. Daha fazla eğlence için herkese açık bir gönderi paylaşın!",
"empty_column.direct": "Henüz doğrudan değinmeniz yok. Bir tane gönderdiğinizde veya aldığınızda burada listelenecek.",
"empty_column.disabled_feed": "Bu akış sunucu yöneticileri tarafından devre dışı bırakılmıştır.",

View File

@ -649,6 +649,7 @@
"empty_column.collections": "{acct} chưa tạo gói khởi đầu nào.",
"empty_column.collections.featured_in": "Bạn chưa được thêm vào gói khởi đầu nào.",
"empty_column.collections.featured_in_undiscoverable": "Để mọi người có thể thêm bạn vào gói khởi đầu, bạn cần cho phép hiển thị trong trải nghiệm khám phá ở <link>Thiết lập > Riêng tư và tiếp cận</link>",
"empty_column.collections_self": "Bạn chưa tạo gói khởi đầu nào.",
"empty_column.community": "Máy chủ của bạn chưa có tút nào công khai. Bạn hãy thử viết gì đó đi!",
"empty_column.direct": "Bạn chưa có tin nhắn riêng nào. Khi bạn gửi hoặc nhận một tin nhắn riêng, nó sẽ xuất hiện ở đây.",
"empty_column.disabled_feed": "Bảng tin này bị vô hiệu hóa bởi quản trị viên máy chủ của bạn.",

View File

@ -649,6 +649,7 @@
"empty_column.collections": "{acct} 尚未创建任何收藏列表。",
"empty_column.collections.featured_in": "你还没有被添加到收藏列表过。",
"empty_column.collections.featured_in_undiscoverable": "如果想让其他人将你添加到收藏列表,请前往<link>偏好设置 > 隐私与可达性</link>在发现功能中允许推荐",
"empty_column.collections_self": "你尚未创建任何收藏列表。",
"empty_column.community": "本站时间线还没有内容,写点什么并公开发布,让它活跃起来吧!",
"empty_column.direct": "你还未使用过私下提及。当你发出或者收到私下提及时,它将显示在此。",
"empty_column.disabled_feed": "此动态已被你的服务器管理员禁用。",

View File

@ -649,6 +649,7 @@
"empty_column.collections": "{acct} 尚未建立任何收藏名單。",
"empty_column.collections.featured_in": "您尚未被加入至任何收藏名單。",
"empty_column.collections.featured_in_undiscoverable": "如欲被其他人將您加入至收藏名單,您必須於 <link>偏好設定 > 隱私權與觸及</link> 啟用探索相關功能",
"empty_column.collections_self": "您尚未建立任何收藏名單。",
"empty_column.community": "本站時間軸是空的。快公開嘟些文搶頭香啊!",
"empty_column.direct": "您還沒有收到任何私訊。當您私訊別人或收到私訊時,它將於此顯示。",
"empty_column.disabled_feed": "此內容已被您的伺服器管理員停用。",

View File

@ -2498,7 +2498,7 @@ a.account__display-name {
overflow: hidden;
strong,
span {
span:not(.emojione) {
display: block;
text-overflow: ellipsis;
overflow: hidden;

View File

@ -8,6 +8,7 @@
# finished_at :datetime
# imported_items :integer default(0), not null
# likely_mismatched :boolean default(FALSE), not null
# missing_status :boolean default(FALSE), not null
# original_filename :string default(""), not null
# overwrite :boolean default(FALSE), not null
# processed_items :integer default(0), not null
@ -34,6 +35,7 @@ class BulkImport < ApplicationRecord
domain_blocking: 3,
bookmarks: 4,
lists: 5,
custom_filters: 6,
}
enum :state, {

View File

@ -58,18 +58,31 @@ class Form::Import
end
end
def guessed_type_json
:custom_filters if parse_json.keys.any?('custom_filters')
end
# Whether the uploaded CSV file seems to correspond to a different import type than the one selected
def likely_mismatched?
guessed_type.present? && guessed_type != type.to_sym
end
def likely_mismatched_json?
guessed_type_json.present? && guessed_type_json != type.to_sym
end
def save
return false unless valid?
ApplicationRecord.transaction do
now = Time.now.utc
@bulk_import = current_account.bulk_imports.create(type: type, overwrite: overwrite || false, state: :unconfirmed, original_filename: data.original_filename, likely_mismatched: likely_mismatched?)
nb_items = BulkImportRow.insert_all(parsed_rows.map { |row| { bulk_import_id: bulk_import.id, data: row, created_at: now, updated_at: now } }).length
if content_type_is_json?
@bulk_import = current_account.bulk_imports.create(type: type, overwrite: overwrite || false, state: :unconfirmed, original_filename: data.original_filename, likely_mismatched: likely_mismatched_json?, missing_status: missing_status?)
nb_items = BulkImportRow.insert_all(json_data.map { |row| { bulk_import_id: bulk_import.id, data: row, created_at: now, updated_at: now } }).length
else
@bulk_import = current_account.bulk_imports.create(type: type, overwrite: overwrite || false, state: :unconfirmed, original_filename: data.original_filename, likely_mismatched: likely_mismatched?)
nb_items = BulkImportRow.insert_all(parsed_rows.map { |row| { bulk_import_id: bulk_import.id, data: row, created_at: now, updated_at: now } }).length
end
@bulk_import.update(total_items: nb_items)
end
end
@ -82,6 +95,14 @@ class Form::Import
self.overwrite = str.to_sym == :overwrite
end
def missing_status?
return false unless content_type_is_json?
import_statuses = json_data.pluck(:statuses).flatten.uniq
db_statuses = Status.where(uri: import_statuses)
import_statuses.count != db_statuses.count
end
private
def file_name_matches?(string)
@ -153,6 +174,21 @@ class Form::Import
def validate_data
return if data.nil?
return errors.add(:data, I18n.t('imports.errors.too_large')) if data.size > FILE_SIZE_LIMIT
if content_type_is_json?
validate_json_data
else
validate_csv_data
end
rescue CSV::MalformedCSVError => e
errors.add(:data, I18n.t('imports.errors.invalid_csv_file', error: e.message))
rescue JSON::ParserError => e
errors.add(:data, I18n.t('imports.errors.invalid_json_file', error: e.message))
rescue EmptyFileError
errors.add(:data, I18n.t('imports.errors.empty'))
end
def validate_csv_data
return errors.add(:data, I18n.t('imports.errors.incompatible_type')) unless default_csv_headers.all? { |header| csv_data.headers.include?(header) }
errors.add(:data, I18n.t('imports.errors.over_rows_processing_limit', count: ROWS_PROCESSING_LIMIT)) if csv_row_count > ROWS_PROCESSING_LIMIT
@ -163,9 +199,26 @@ class Form::Import
limit -= current_account.following_count unless overwrite
errors.add(:data, I18n.t('users.follow_limit_reached', limit: base_limit)) if csv_row_count > limit
end
rescue CSV::MalformedCSVError => e
errors.add(:data, I18n.t('imports.errors.invalid_csv_file', error: e.message))
rescue EmptyFileError
errors.add(:data, I18n.t('imports.errors.empty'))
end
def validate_json_data
errors.add(:data, I18n.t('imports.errors.over_rows_processing_limit', count: ROWS_PROCESSING_LIMIT)) if json_data.count > ROWS_PROCESSING_LIMIT
errors.add(:data, I18n.t('imports.errors.incompatible_type')) unless allowed_type_for_json?
end
def content_type_is_json?
data.content_type == 'application/json'
end
def json_data
parse_json['custom_filters'].map(&:deep_symbolize_keys)
end
def parse_json
@parse_json ||= JSON.parse(data.read)
end
def allowed_type_for_json?
type.to_sym.in?(%i(custom_filters))
end
end

View File

@ -42,6 +42,14 @@ class BulkImportRowService
FollowService.new.call(@account, @target_account) unless @account.id == @target_account.id
list.accounts << @target_account
when :custom_filters
filter = @account.custom_filters.create!(title: @data['title'], context: @data['context'])
filter.keywords = @data['keywords_attributes'].map { |keyword| CustomFilterKeyword.new(keyword: keyword['keyword'], whole_word: keyword['whole_word']) }
filter.action = @data['action'].to_sym
filter.expires_at = @data['expires_at']
status_ids = Status.where(uri: @data['statuses']).ids
filter.statuses = status_ids.map { |status| CustomFilterStatus.new(status_id: status) } if status_ids.any?
filter.save!
end
true

View File

@ -18,6 +18,8 @@ class BulkImportService < BaseService
import_bookmarks!
when :lists
import_lists!
when :custom_filters
import_custom_filters!
end
@import.update!(state: :finished, finished_at: Time.now.utc) if @import.processing_complete?
@ -182,4 +184,14 @@ class BulkImportService < BaseService
[row.id]
end
end
def import_custom_filters!
rows = @import.rows.to_a
@account.custom_filters.destroy_all if @import.overwrite?
Import::RowWorker.push_bulk(rows) do |row|
[row.id]
end
end
end

View File

@ -167,7 +167,7 @@ class NotifyService < BaseService
end
def blocked_by_bots_policy?
@policy.drop_bots? && from_bot?
@policy.drop_bots? && from_bot? && not_following?
end
end
@ -208,7 +208,7 @@ class NotifyService < BaseService
end
def filtered_by_bots_policy?
@policy.filter_bots? && from_bot?
@policy.filter_bots? && from_bot? && not_following?
end
end

View File

@ -23,14 +23,14 @@ class ProcessLinksService < BaseService
def scan_text!
urls = @status.text.scan(FetchLinkCardService::URL_PATTERN).map { |array| Addressable::URI.parse(array[1]).normalize }
domains = urls.map(&:domain).uniq
domains = urls.map(&:host).uniq
valid_domains = Instance.searchable.where(domain: domains).pluck(:domain)
urls.each do |url|
# We only support `FeaturedCollection` at this time
object = ActivityPub::TagManager.instance.uri_to_resource(url.to_s, Collection)
object ||= ResolveURLService.new.call(url.to_s) if valid_domains.include?(url.domain)
object ||= ResolveURLService.new.call(url.to_s) if valid_domains.include?(url.host)
next unless object.is_a?(Collection)
tagged_object = @previous_objects.find { |x| x.object == object || x.uri == url }

View File

@ -1,6 +1,8 @@
# frozen_string_literal: true
class TagSearchService < BaseService
include SearchStoplight
def call(query, options = {})
MastodonOTELTracer.in_span('TagSearchService#call') do |span|
@query = query.strip.delete_prefix('#')
@ -29,8 +31,8 @@ class TagSearchService < BaseService
definition = TagsIndex.query(elastic_search_query)
definition = definition.filter(elastic_search_filter) if @options[:exclude_unreviewed]
ensure_exact_match(definition.limit(@limit).offset(@offset).objects.compact)
rescue Faraday::ConnectionFailed, Parslet::ParseFailed, Errno::ENETUNREACH
elastic_stoplight_wrapper.run { ensure_exact_match(definition.limit(@limit).offset(@offset).objects.compact) }
rescue Stoplight::Error::RedLight, Faraday::ConnectionFailed, Parslet::ParseFailed, Errno::ENETUNREACH, OpenSSL::SSL::SSLError, Elastic::Transport::Transport::Error
nil
end

View File

@ -1,4 +1,4 @@
%h3= t 'sessions.title'
%h3.heading-medium= t 'sessions.title'
%p.muted-hint
= t 'sessions.explanation'
= link_to t('sessions.view_authentication_history'), settings_login_activities_path

View File

@ -2,10 +2,10 @@
= t('settings.import')
= simple_form_for @import, url: settings_imports_path do |f|
.field-group
.fields-group
= f.input :type,
as: :grouped_select,
collection: { constructive: %i(following bookmarks lists), destructive: %i(muting blocking domain_blocking) },
collection: { constructive: %i(following bookmarks lists), destructive: %i(muting blocking domain_blocking custom_filters) },
group_label_method: ->(group) { I18n.t("imports.type_groups.#{group.first}") },
group_method: :last,
hint: t('imports.preface'),
@ -33,7 +33,7 @@
- unless @recent_imports.empty?
%hr.spacer/
%h3= t('imports.recent_imports')
%h3.heading-medium= t('imports.recent_imports')
.table-wrapper
%table.table
@ -61,5 +61,9 @@
%td
- if import.failure_count.positive?
= link_to_if import.state_finished?, import.failure_count, failures_settings_import_path(import, format: :csv) do
= import.failure_count
- if import.type == 'custom_filters'
= link_to_if import.state_finished?, import.failure_count, failures_settings_import_path(import, format: :json) do
= import.failure_count
- else
= link_to_if import.state_finished?, import.failure_count, failures_settings_import_path(import, format: :csv) do
= import.failure_count

View File

@ -4,6 +4,9 @@
- if @bulk_import.likely_mismatched?
.flash-message.warning= t('imports.mismatched_types_warning')
- if @bulk_import&.missing_status == true
.flash-message.warning= I18n.t('imports.errors.status_not_found_warning')
- if @bulk_import.overwrite?
%p.hint= t("imports.overwrite_preambles.#{@bulk_import.type}_html", filename: @bulk_import.original_filename, count: @bulk_import.total_items)
- else

View File

@ -19,7 +19,7 @@
%hr.spacer/
%h3= t('auth.migrate_account')
%h3.heading-medium= t('auth.migrate_account')
= simple_form_for @migration, url: settings_migration_path do |f|
- if on_cooldown?
@ -58,7 +58,7 @@
- unless @migrations.empty?
%hr.spacer/
%h3= t 'migrations.past_migrations'
%h3.heading-medium= t 'migrations.past_migrations'
%hr.spacer/
@ -85,5 +85,5 @@
%hr.spacer/
%h3= t 'migrations.incoming_migrations'
%h3.heading-medium= t 'migrations.incoming_migrations'
%p.muted-hint= t('migrations.incoming_migrations_html', path: settings_aliases_path)

View File

@ -35,7 +35,7 @@
%hr.spacer/
%h3= t('two_factor_authentication.recovery_codes')
%h3.heading-medium= t('two_factor_authentication.recovery_codes')
%p.muted-hint= t('two_factor_authentication.lost_recovery_codes')
%hr.spacer/

View File

@ -75,8 +75,8 @@ ignore_unused:
- 'user_mailer.*.subject'
- 'email_subscription_mailer.*'
- 'notification_mailer.*'
- 'imports.overwrite_preambles.{following,blocking,muting,domain_blocking,bookmarks,lists}_html.*'
- 'imports.preambles.{following,blocking,muting,domain_blocking,bookmarks,lists}_html.*'
- 'imports.overwrite_preambles.{following,blocking,muting,domain_blocking,bookmarks,lists,custom_filters}_html.*'
- 'imports.preambles.{following,blocking,muting,domain_blocking,bookmarks,lists,custom_filters}_html.*'
- 'mail_subscriptions.unsubscribe.emails.*'
- 'preferences.other' # some locales are missing other keys, therefore leading i18n-tasks to detect `preferences` as plural and not finding use
- 'edit_profile.other' # some locales are missing other keys, therefore leading i18n-tasks to detect `preferences` as plural and not finding use

View File

@ -1723,7 +1723,9 @@ be:
empty: Пусты CSV файл
incompatible_type: Несумяшчальны з выбраным тыпам імпарту
invalid_csv_file: 'Несапраўдны файл CSV. Памылка: %{error}'
invalid_json_file: 'Няспраўны файл JSON. Памылка: %{error}'
over_rows_processing_limit: колькасць радкоў большая за %{count}
status_not_found_warning: Некаторыя з Вашых фільтраў хаваюць адмысловыя допісы, якія невядомыя гэтаму серверу. Гэтыя допісы будуць аўтаматычна адфільтраваныя, калі яны будуць пазней знойдзеныя на серверы.
too_large: Файл занадта вялікі
failures: Няўдала
imported: Імпартавана
@ -1744,6 +1746,11 @@ be:
many: Вы збіраецеся <strong>замяніць свае закладкі</strong> <strong>%{count} допісамі</strong> з <strong>%{filename}</strong>.
one: Вы збіраецеся <strong>замяніць свае закладкі</strong> <strong>%{count} допісам</strong> з <strong>%{filename}</strong>.
other: Вы збіраецеся <strong>замяніць свае закладкі</strong> <strong>%{count} допісамі</strong> з <strong>%{filename}</strong>.
custom_filters_html:
few: Вы збіраецеся <strong>замяніць свае фільтры</strong> змесцівам з <strong>%{filename}</strong>. У новыя фільтры будуць дададзеныя <strong>%{count} фільтры</strong>.
many: Вы збіраецеся <strong>замяніць свае фільтры</strong> змесцівам з <strong>%{filename}</strong>. У новыя фільтры будуць дададзеныя <strong>%{count} фільтраў</strong>.
one: Вы збіраецеся <strong>замяніць свае фільтры</strong> змесцівам з <strong>%{filename}</strong>. У новыя фільтры будзе дададзены <strong>%{count} фільтр</strong>.
other: Вы збіраецеся <strong>замяніць свае фільтры</strong> змесцівам з <strong>%{filename}</strong>. У новыя фільтры будуць дададзеныя <strong>%{count} фільтры</strong>.
domain_blocking_html:
few: Вы збіраецеся <strong>замяніць свой спіс заблакіраваных даменаў</strong> <strong>%{count} даменамі</strong> з <strong>%{filename}</strong>.
many: Вы збіраецеся <strong>замяніць свой спіс заблакіраваных даменаў</strong> <strong>%{count} даменамі</strong> з <strong>%{filename}</strong>.
@ -1775,6 +1782,11 @@ be:
many: Вы збіраецеся дадаць <strong>%{count} допісаў</strong> з <strong>%{filename}</strong> у Вашыя <strong>закладкі</strong>.
one: Вы збіраецеся дадаць <strong>%{count} допіс</strong> з <strong>%{filename}</strong> у Вашыя <strong>закладкі</strong>.
other: Вы збіраецеся дадаць <strong>%{count} допісаў</strong> з <strong>%{filename}</strong> у Вашыя <strong>закладкі</strong>.
custom_filters_html:
few: Вы збіраецеся дадаць <strong>%{count} фільтры </strong> з <strong>%{filename}</strong> у свае <strong>фільтры</strong>.
many: Вы збіраецеся дадаць <strong>%{count} фільтраў </strong> з <strong>%{filename}</strong> у свае <strong>фільтры</strong>.
one: Вы збіраецеся дадаць <strong>%{count} фільтр </strong> з <strong>%{filename}</strong> у свае <strong>фільтры</strong>.
other: Вы збіраецеся дадаць <strong>%{count} фільтры </strong> з <strong>%{filename}</strong> у свае <strong>фільтры</strong>.
domain_blocking_html:
few: Вы збіраецеся <strong>заблакіраваць</strong> <strong>%{count} дамены</strong> з <strong>%{filename}</strong>.
many: Вы збіраецеся <strong>заблакіраваць</strong> <strong>%{count} даменаў</strong> з <strong>%{filename}</strong>.

View File

@ -1665,7 +1665,9 @@ da:
empty: CSV-fil tom
incompatible_type: Inkompatibel med valgte importtype
invalid_csv_file: 'Ugyldig CSV-fil. Fejl: %{error}'
invalid_json_file: 'Ugyldig JSON-fil. Fejl: %{error}'
over_rows_processing_limit: indeholder mere end %{count} rækker
status_not_found_warning: Nogle af dine filtre skjuler bestemte indlæg, som denne server ikke kender til. Disse indlæg bliver ikke automatisk filtreret, hvis serveren finder dem senere.
too_large: Filen for stor
failures: Mislykket
imported: Importeret
@ -1682,6 +1684,9 @@ da:
bookmarks_html:
one: Du er ved at <strong>erstatte dine bogmærker</strong> med <strong>%{count} indlæg</strong> fra <strong>%{filename}</strong>.
other: Du er ved at <strong>erstatte dine bogmærker</strong> med op til <strong>%{count} indlæg</strong> fra <strong>%{filename}</strong>.
custom_filters_html:
one: Du er ved at <strong>udskifte dine filtre</strong> med indholdet af <strong>%{filename}</strong>. Op til <strong>%{count} filter</strong> vil blive føjet til nye filtre.
other: Du er ved at <strong>udskifte dine filtre</strong> med indholdet af <strong>%{filename}</strong>. Op til <strong>%{count} filtre</strong> vil blive føjet til nye filtre.
domain_blocking_html:
one: Du er ved at <strong>erstatte din domænesortliste</strong> med <strong>%{count} domæne</strong> fra <strong>%{filename}</strong>.
other: Du er ved at <strong>erstatte din domænesortliste</strong> med op til <strong>%{count} domæner</strong> fra <strong>%{filename}</strong>.
@ -1701,6 +1706,9 @@ da:
bookmarks_html:
one: Du er ved at tilføje <strong>%{count} indlæg</strong> fra <strong>%{filename}</strong> til dine <strong>bogmærker</strong>.
other: Du er ved at tilføje <strong>%{count} indlæg</strong> fra <strong>%{filename}</strong> til dine <strong>bogmærker</strong>.
custom_filters_html:
one: Du er ved at tilføje op til <strong>%{count} filter</strong> fra <strong>%{filename}</strong> til dine <strong>filtre</strong>.
other: Du er ved at tilføje op til <strong>%{count} filtre</strong> fra <strong>%{filename}</strong> til dine <strong>filtre</strong>.
domain_blocking_html:
one: Du er ved at <strong>blokere</strong> <strong>%{count} domæne</strong> fra <strong>%{filename}</strong>.
other: Man er ved at <strong>blokere</strong> op til <strong>%{count} domæner</strong> fra <strong>%{filename}</strong>.

View File

@ -1648,7 +1648,9 @@ de:
empty: Leere CSV-Datei
incompatible_type: Inkompatibel mit dem ausgewählten Importtyp
invalid_csv_file: 'Ungültige CSV-Datei. Fehler: %{error}'
invalid_json_file: 'Ungültige JSON-Datei. Fehler: %{error}'
over_rows_processing_limit: enthält mehr als %{count} Zeilen
status_not_found_warning: Einige deiner Filter blenden bestimmte Beiträge aus, die diesem Server nicht bekannt sind. Diese Beiträge werden nicht automatisch gefiltert, wenn sie dem Server später bekannt werden.
too_large: Datei ist zu groß
failures: Fehler
imported: Importiert
@ -1665,6 +1667,9 @@ de:
bookmarks_html:
one: Du bist dabei, <strong>deine Lesezeichen</strong> aus <strong>%{filename}</strong> durch bis zu <strong>%{count} Beitrag</strong> zu ersetzen.
other: Du bist dabei, <strong>deine Lesezeichen</strong> aus <strong>%{filename}</strong> durch bis zu <strong>%{count} Beiträge</strong> zu ersetzen.
custom_filters_html:
one: Du bist dabei, <strong>deine Filter</strong> mit dem Inhalt aus <strong>%{filename}</strong> zu <strong>ersetzen</strong>. Bis zu <strong>%{count} Filter</strong> wird zu neuen Filtern hinzugefügt.
other: Du bist dabei, <strong>deine Filter</strong> mit dem Inhalt aus <strong>%{filename}</strong> zu <strong>ersetzen</strong>. Bis zu <strong>%{count} Filter</strong> werden zu neuen Filtern hinzugefügt.
domain_blocking_html:
one: Du bist dabei, <strong>deine Liste blockierter Domains</strong> aus <strong>%{filename}</strong> durch bis zu <strong>%{count} Domain</strong> zu ersetzen.
other: Du bist dabei, <strong>deine Liste blockierter Domains</strong> aus <strong>%{filename}</strong> durch bis zu <strong>%{count} Domains</strong> zu ersetzen.
@ -1684,6 +1689,9 @@ de:
bookmarks_html:
one: Du bist dabei, bis zu <strong>%{count} Beitrag</strong> aus <strong>%{filename}</strong> zu deinen <strong>Lesezeichen hinzuzufügen</strong>.
other: Du bist dabei, bis zu <strong>%{count} Beiträge</strong> aus <strong>%{filename}</strong> zu deinen <strong>Lesezeichen hinzuzufügen</strong>.
custom_filters_html:
one: Du bist dabei, bis zu <strong>%{count} Filter</strong> aus <strong>%{filename}</strong> zu deinen <strong>Filtern hinzuzufügen</strong>.
other: Du bist dabei, bis zu <strong>%{count} Filter</strong> aus <strong>%{filename}</strong> zu deinen <strong>Filtern hinzuzufügen</strong>.
domain_blocking_html:
one: Du bist dabei, bis zu <strong>%{count} Domain</strong> aus <strong>%{filename}</strong> zu <strong>blockieren</strong>.
other: Du bist dabei, bis zu <strong>%{count} Domains</strong> aus <strong>%{filename}</strong> zu <strong>blockieren</strong>.

View File

@ -96,7 +96,7 @@ lv:
revoked: Piekļuves pilnvara tika atsaukta
unknown: Piekļuves pilnvara nav derīga
resource_owner_authenticator_not_configured: Resursa īpašnieka atrašana neizdevās, jo Doorkeeper.configure.resource_owner_authenticator nav savienots.
server_error: Pilnvarošanas serverim radās neparedzēti apstākļi, kas tam neļāva izpildīt pieprasījumu.
server_error: Pilnvarošanas serverī radās neparedzēti apstākļi, kas tam neļāva izpildīt pieprasījumu.
temporarily_unavailable: Pilnvarošanas serveris šobrīd nevar apstrādāt pieprasījumu servera īslaicīgas pārslodzes vai uzturēšanas darbu dēļ.
unauthorized_client: Klients nav pilnvarots izpildīt šo pieprasījumu, izmantojot šo metodi.
unsupported_grant_type: Pilnvarošanas serveris neatbalsta atļaujas piešķiršanas veidu.

View File

@ -1665,7 +1665,9 @@ el:
empty: Κενό αρχείο CSV
incompatible_type: Μη συμβατό με τον επιλεγμένο τύπο εισαγωγής
invalid_csv_file: 'Μη έγκυρο αρχείο CSV. Σφάλμα: %{error}'
invalid_json_file: 'Μη έγκυρο αρχείο JSON. Σφάλμα: %{error}'
over_rows_processing_limit: περιέχει περισσότερες από %{count} γραμμές
status_not_found_warning: Μερικά από τα φίλτρα σας κρύβουν συγκεκριμένες αναρτήσεις που δεν είναι γνωστές από αυτόν τον διακομιστή. Αυτές οι αναρτήσεις δεν θα φιλτραριστούν αυτόματα εάν ανακαλυφθούν αργότερα από τον διακομιστή.
too_large: Το αρχείο είναι πολύ μεγάλο
failures: Αποτυχίες
imported: Εισήχθη
@ -1682,6 +1684,9 @@ el:
bookmarks_html:
one: Πρόκειται να <strong>αντικαταστήσεις τους σελιδοδείκτες σου</strong> με έως και <strong>%{count} ανάρτηση</strong> από το <strong>%{filename}</strong>.
other: Πρόκειται να <strong>αντικαταστήσεις τους σελιδοδείκτες σου</strong> με έως και <strong>%{count} αναρτήσεις</strong> από το <strong>%{filename}</strong>.
custom_filters_html:
one: Πρόκειται να <strong>αντικαταστήσεις τα φίλτρα σου</strong> με το περιεχόμενο του <strong>%{filename}</strong>. Μέχρι <strong>%{count} φίλτρο</strong> θα προστεθεί στα νέα φίλτρα.
other: Πρόκειται να <strong>αντικαταστήσεις τα φίλτρα σου</strong> με το περιεχόμενο του <strong>%{filename}</strong>. Μέχρι <strong>%{count} φίλτρα</strong> θα προστεθούν στα νέα φίλτρα.
domain_blocking_html:
one: Πρόκειται να <strong>αντικαταστήσεις τη λίστα αποκλεισμών τομέων</strong> με έως και <strong>%{count} τομέα</strong> από το <strong>%{filename}</strong>.
other: Πρόκειται να <strong>αντικαταστήσεις τη λίστα αποκλεισμών τομέων</strong> με έως και <strong>%{count} τομείς</strong> από το <strong>%{filename}</strong>.
@ -1689,8 +1694,8 @@ el:
one: Πρόκειται να <strong>ακολουθήσεις</strong> μέχρι <strong>%{count} λογαριασμό</strong> από το <strong>%{filename}</strong> και <strong>να σταματήσεις να ακολουθείς οποιονδήποτε άλλο</strong>.
other: Πρόκειται να <strong>ακολουθήσεις</strong> μέχρι <strong>%{count} λογαριασμούς</strong> από το <strong>%{filename}</strong> και <strong>να σταματήσεις να ακολουθείς οποιονδήποτε άλλο</strong>.
lists_html:
one: Πρόκειται να <strong>αντικαταστήσεις τις λίστες σου</strong> με περιεχόμενο του <strong>%{filename}</strong>. Μέχρι <strong>%{count} λογαριασμός</strong> θα προστεθεί σε νέες λίστες.
other: Πρόκειται να <strong>αντικαταστήσεις τις λίστες σου</strong> με περιεχόμενο του <strong>%{filename}</strong>. Μέχρι <strong>%{count} λογαριασμοί</strong> θα προστεθούν σε νέες λίστες.
one: Πρόκειται να <strong>αντικαταστήσεις τις λίστες σου</strong> με το περιεχόμενο του <strong>%{filename}</strong>. Μέχρι <strong>%{count} λογαριασμός</strong> θα προστεθεί στις νέες λίστες.
other: Πρόκειται να <strong>αντικαταστήσεις τις λίστες σου</strong> με το περιεχόμενο του <strong>%{filename}</strong>. Μέχρι <strong>%{count} λογαριασμοί</strong> θα προστεθούν στις νέες λίστες.
muting_html:
one: Πρόκειται να <strong>αντικαταστήσεις τη λίστα λογαριασμών σε σίγαση</strong> με έως και <strong>%{count} λογαριασμό</strong> από το <strong>%{filename}</strong>.
other: Πρόκειται να <strong>αντικαταστήσεις τη λίστα λογαριασμών σε σίγαση</strong> με έως και <strong>%{count} λογαριασμούς</strong> από το <strong>%{filename}</strong>.
@ -1701,6 +1706,9 @@ el:
bookmarks_html:
one: Πρόκειται να προσθέσεις μέχρι και <strong>%{count} ανάρτηση</strong> από το <strong>%{filename}</strong> στους <strong>σελιδοδείκτες</strong> σου.
other: Πρόκειται να προσθέσεις μέχρι και <strong>%{count} αναρτήσεις</strong> από το <strong>%{filename}</strong> στους <strong>σελιδοδείκτες</strong> σου.
custom_filters_html:
one: Πρόκειται να προσθέσεις μέχρι και <strong>%{count} φίλτρο</strong> από το <strong>%{filename}</strong> στα <strong>φίλτρα</strong> σου.
other: Πρόκειται να προσθέσεις μέχρι και <strong>%{count} φίλτρα</strong> από το <strong>%{filename}</strong> στα <strong>φίλτρα</strong> σου.
domain_blocking_html:
one: Πρόκειται να <strong>αποκλείσεις</strong> έως και <strong>%{count} τόμεα</strong> από το <strong>%{filename}</strong>.
other: Πρόκειται να <strong>αποκλείσεις</strong> έως και <strong>%{count} τομείς</strong> από το <strong>%{filename}</strong>.

View File

@ -1665,7 +1665,9 @@ en:
empty: Empty CSV file
incompatible_type: Incompatible with the selected import type
invalid_csv_file: 'Invalid CSV file. Error: %{error}'
invalid_json_file: 'Invalid JSON file. Error: %{error}'
over_rows_processing_limit: contains more than %{count} rows
status_not_found_warning: Some of your filters hide specific posts that are not known by this server. These posts will not be automatically filtered if they are discovered later by the server.
too_large: File is too large
failures: Failures
imported: Imported
@ -1682,6 +1684,9 @@ en:
bookmarks_html:
one: You are about to <strong>replace your bookmarks</strong> with up to <strong>%{count} post</strong> from <strong>%{filename}</strong>.
other: You are about to <strong>replace your bookmarks</strong> with up to <strong>%{count} posts</strong> from <strong>%{filename}</strong>.
custom_filters_html:
one: You are about to <strong>replace your filters</strong> with contents of <strong>%{filename}</strong>. Up to <strong>%{count} filter</strong> will be added to new filters.
other: You are about to <strong>replace your filters</strong> with contents of <strong>%{filename}</strong>. Up to <strong>%{count} filters</strong> will be added to new filters.
domain_blocking_html:
one: You are about to <strong>replace your domain block list</strong> with up to <strong>%{count} domain</strong> from <strong>%{filename}</strong>.
other: You are about to <strong>replace your domain block list</strong> with up to <strong>%{count} domains</strong> from <strong>%{filename}</strong>.
@ -1701,6 +1706,9 @@ en:
bookmarks_html:
one: You are about to add up to <strong>%{count} post</strong> from <strong>%{filename}</strong> to your <strong>bookmarks</strong>.
other: You are about to add up to <strong>%{count} posts</strong> from <strong>%{filename}</strong> to your <strong>bookmarks</strong>.
custom_filters_html:
one: You are about to add up to <strong>%{count} filter</strong> from <strong>%{filename}</strong> to your <strong>filters</strong>.
other: You are about to add up to <strong>%{count} filters</strong> from <strong>%{filename}</strong> to your <strong>filters</strong>.
domain_blocking_html:
one: You are about to <strong>block</strong> up to <strong>%{count} domain</strong> from <strong>%{filename}</strong>.
other: You are about to <strong>block</strong> up to <strong>%{count} domains</strong> from <strong>%{filename}</strong>.
@ -1737,6 +1745,7 @@ en:
types:
blocking: Blocking list
bookmarks: Bookmarks
custom_filters: Filters
domain_blocking: Domain blocking list
following: Following list
lists: Lists

View File

@ -1665,7 +1665,9 @@ es-AR:
empty: Archivo CSV vacío
incompatible_type: Incompatible con el tipo de importación seleccionado
invalid_csv_file: 'Archivo CSV no válido. Error: %{error}'
invalid_json_file: 'Archivo JSON no válido. Error: %{error}'
over_rows_processing_limit: contiene más de %{count} filas
status_not_found_warning: Algunos de tus filtros ocultan mensajes específicos que no son conocidos por este servidor. Estos mensajes no serán filtrados automáticamente si son descubiertos más tarde por el servidor.
too_large: El archivo es demasiado grande
failures: Fallos
imported: Importadas
@ -1682,6 +1684,9 @@ es-AR:
bookmarks_html:
one: Estás a punto de <strong>reemplazar tus marcadores</strong> por hasta <strong>%{count} mensaje</strong> provenientes de <strong>%{filename}</strong>.
other: Estás a punto de <strong>reemplazar tus marcadores</strong> por hasta <strong>%{count} mensajes</strong> provenientes de <strong>%{filename}</strong>.
custom_filters_html:
one: Estás a punto de <strong>reemplazar tus filtros</strong> con el contenido de <strong>%{filename}</strong>. Se agregarán hasta <strong>%{count} filtro</strong> a filtros nuevos.
other: Estás a punto de <strong>reemplazar tus filtros</strong> con el contenido de <strong>%{filename}</strong>. Se agregarán hasta <strong>%{count} filtros</strong> a filtros nuevos.
domain_blocking_html:
one: Estás a punto de <strong>reemplazar tu lista de bloqueos de dominio</strong> por hasta <strong>%{count} dominio</strong> provenientes de <strong>%{filename}</strong>.
other: Estás a punto de <strong>reemplazar tu lista de bloqueos de dominio</strong> por hasta <strong>%{count} dominios</strong> provenientes de <strong>%{filename}</strong>.
@ -1701,6 +1706,9 @@ es-AR:
bookmarks_html:
one: Está a punto de agregar hasta <strong>%{count} mensaje</strong> provenientes de <strong>%{filename}</strong> a tus <strong>marcadores</strong>.
other: Está a punto de agregar hasta <strong>%{count} mensajes</strong> provenientes de <strong>%{filename}</strong> a tus <strong>marcadores</strong>.
custom_filters_html:
one: Está a punto de agregar hasta <strong>%{count} filtro</strong> proveniente de <strong>%{filename}</strong> a tus <strong>filtros</strong>.
other: Está a punto de agregar hasta <strong>%{count} filtros</strong> provenientes de <strong>%{filename}</strong> a tus <strong>filtros</strong>.
domain_blocking_html:
one: Estás a punto de <strong>bloquear</strong> hasta <strong>%{count} dominio</strong> provenientes de <strong>%{filename}</strong>.
other: Estás a punto de <strong>bloquear</strong> hasta <strong>%{count} dominios</strong> provenientes de <strong>%{filename}</strong>.

View File

@ -506,21 +506,19 @@ es-MX:
last_email: Último correo electrónico
lead: A continuación se mostrarán las cuentas que hayan activado la función y tengan suscriptores.
show:
confirm_disable_feature: |-
¿Desactivar boletines de correo electrónico de %{name}? No se enviarán actualizaciones por correo electrónico para esta cuenta. El usuario aún podrá reactivar esta función en los ajustes de su cuenta.
Para eliminar el acceso a esta función permanentemente, modifica los permisos de la cuenta en Roles.
confirm_remove_subscriber: "%{email} no recibirá más correos electrónicos de %{name}. Esta acción no se puede deshacer."
consent: Los subscriptores solo han autorizado recibir publicaciones por email. No utilices esta lista para otros propósitos.
confirm_disable_feature: "¿Deseas desactivar los boletines informativos por correo electrónico para %{name}? Ya no se enviarán actualizaciones por correo electrónico para esta cuenta. El usuario podrá volver a activar esta función en la configuración de su cuenta. Para eliminar de forma permanente el acceso a esta función, edita los permisos de la cuenta en «Roles»."
confirm_remove_subscriber: "%{email} ya no recibirá correos electrónicos de %{name}. Esta acción no se puede deshacer."
consent: Los suscriptores solo han dado su consentimiento para recibir mensajes por correo electrónico. No utilices esta lista para otros fines.
date: Fecha de suscripción
disable_feature: Desactivar función
disabled: La función ha sido desactivada y ya no se enviarán correos electrónicos a esta lista.
disabled: La función se ha desactivado y ya no se envían correos electrónicos a esta lista.
email: Dirección de correo electrónico
empty:
hint: Nadie se ha suscrito a esta cuenta todavía.
no_subscribers_yet: Sin suscriptores todavía
enable_feature: Activar función
no_access_html: Esta cuenta ya no tiene los permisos necesarios para activar la función. Modifica esto en <a href="%{roles_path}">Roles</a>.
title: Boletines de correo electrónico de %{name}
no_access_html: Esta cuenta ya no tiene los permisos necesarios para habilitar la función. Modifica esta configuración en <a href="%{roles_path}">Roles</a>.
title: Boletines informativos por correo electrónico de %{name}
view_account: Ver cuenta
status: Estado
subscribers: Suscriptores

View File

@ -1659,6 +1659,7 @@ fi:
empty: Tyhjä CSV-tiedosto
incompatible_type: Yhteensopimaton valitun tuontityypin kanssa
invalid_csv_file: 'Epäkelpo CSV-tiedosto. Virhe: %{error}'
invalid_json_file: 'Epäkelpo JSON-tiedosto. Virhe: %{error}'
over_rows_processing_limit: sisältää yli %{count} riviä
too_large: Tiedosto on liian suuri
failures: Virheet

View File

@ -1665,7 +1665,9 @@ it:
empty: File CSV vuoto
incompatible_type: Incompatibile con il tipo di importazione selezionato
invalid_csv_file: 'File CSV non valido. Errore: %{error}'
invalid_json_file: 'File JSON non valido. Errore: %{error}'
over_rows_processing_limit: contiene più di %{count} righe
status_not_found_warning: Alcuni dei tuoi filtri nascondono post specifici che non sono noti a questo server. Questi post non verranno filtrati automaticamente se vengono scoperti in seguito dal server.
too_large: Il file è troppo grande
failures: Fallimenti
imported: Importato
@ -1682,6 +1684,9 @@ it:
bookmarks_html:
one: Stai per <strong>sostituire i tuoi segnalibri</strong> con <strong>%{count} post</strong> da <strong>%{filename}</strong>.
other: Stai per <strong>sostituire i tuoi segnalibri</strong> con <strong>%{count} post</strong> da <strong>%{filename}</strong>.
custom_filters_html:
one: Stai per <strong>sostituire i tuoi filtri</strong> con i contenuti di <strong>%{filename}</strong>. Ai nuovi filtri sarà aggiunto fino a un massimo di <strong>%{count} filtro</strong>.
other: Stai per <strong>sostituire i tuoi filtri</strong> con i contenuti di <strong>%{filename}</strong>. Ai nuovi filtri saranno aggiunti fino a un massimo di <strong>%{count} filtri</strong>.
domain_blocking_html:
one: Stai per <strong>sostituire la tua lista di domini bloccati</strong> con <strong>%{count} dominio</strong> da <strong>%{filename}</strong>.
other: Stai per <strong>sostituire la tua lista di domini bloccati</strong> con <strong>%{count} domini</strong> da <strong>%{filename}</strong>.
@ -1701,6 +1706,9 @@ it:
bookmarks_html:
one: Stai per aggiungere <strong>%{count} post</strong> da <strong>%{filename}</strong> ai tuoi <strong>segnalibri</strong>.
other: Stai per aggiungere <strong>%{count} post</strong> da <strong>%{filename}</strong> ai tuoi <strong>segnalibri</strong>.
custom_filters_html:
one: Stai per aggiungere fino a un massimo di <strong>%{count} filtro</strong> da <strong>%{filename}</strong> ai tuoi <strong>filtri</strong>.
other: Stai per aggiungere fino a un massimo di <strong>%{count} filtri</strong> da <strong>%{filename}</strong> ai tuoi <strong>filtri</strong>.
domain_blocking_html:
one: Stai per <strong>bloccare</strong> <strong>%{count} dominio</strong> da <strong>%{filename}</strong>.
other: Stai per <strong>bloccare</strong> <strong>%{count} domini</strong> da <strong>%{filename}</strong>.

View File

@ -418,7 +418,7 @@ lv:
domain: Domēns
edit: Labot domēna liegšanu
existing_domain_block: Tu jau esi noteicis stingrākus ierobežojumus %{name}.
existing_domain_block_html: Tu jau esi noteicis stingrākus ierobežojumus %{name}, vispirms tev <a href="%{unblock_url}">jāatbloķē</a>.
existing_domain_block_html: Tu jau esi noteicis stingrākus ierobežojumus %{name}, vispirms <a href="%{unblock_url}">jāatceļ liegums</a>.
export: Eksportēt
import: Importēt
new:

View File

@ -504,6 +504,9 @@ pl:
action: Wyczyść dane
title: Usuń wszystkie dane
index:
disabled:
description: Ta funkcja pozwala określonym kontom dodawać widżet do ich profili, umożliwiając odwiedzającym bez konta Mastodon odbieranie ich postów za pośrednictwem poczty e-mail.
get_started: Rozpocznij
lead: Pozwól odwiedzającym otrzymywać wpisy pocztą e-mail z dedykowanych kont na tym serwerze.
title: Newslettery e-mail
roles:

View File

@ -1665,7 +1665,9 @@ pt-BR:
empty: Arquivo CSV vazio
incompatible_type: Incompatível com o tipo de importação selecionado
invalid_csv_file: 'Arquivo CSV inválido. Erro: %{error}'
invalid_json_file: 'Arquivo JSON inválido. Erro: %{error}'
over_rows_processing_limit: contém mais de %{count} linhas
status_not_found_warning: Alguns filtros seus ocultam publicações específicas que não são conhecidas neste servidor. Estas publicações serão filtradas automaticamente se forem descobertas depois pelo servidor.
too_large: O arquivo é muito grande
failures: Falhas
imported: Importado
@ -1682,6 +1684,9 @@ pt-BR:
bookmarks_html:
one: Você está prestes a <strong>substituir seus salvos</strong> por até <strong>%{count} publicação</strong> de <strong>%{filename}</strong>.
other: Você está prestes a <strong>substituir seus salvos</strong> por até <strong>%{count} publicações</strong> de <strong>%{filename}</strong>.
custom_filters_html:
one: Você está prestes a <strong>substituir seus filtros</strong> com conteúdos de <strong>%{filename}</strong>. Até <strong>%{count} filtro</strong> será adicionado aos novos filtros.
other: Você está prestes a <strong>substituir seus filtros</strong> com conteúdos de <strong>%{filename}</strong>. Até <strong>%{count} filtros</strong> será adicionado aos novos filtros.
domain_blocking_html:
one: Você está prestes a <strong>substituir seu bloco de lista do domínio</strong> com <strong>%{count} domínio</strong> de <strong>%{filename}</strong>.
other: Você está prestes a <strong>substituir sua lista de bloqueio de domínio</strong> com <strong>domínios%{count}</strong> de <strong>%{filename}</strong>.
@ -1701,6 +1706,9 @@ pt-BR:
bookmarks_html:
one: Você está prestes a adicionar até <strong>%{count} post</strong> de <strong>%{filename}</strong> aos seus <strong>favoritos</strong>.
other: Você está prestes a adicionar até <strong>%{count} posts</strong> de <strong>%{filename}</strong> aos seus <strong>favoritos</strong>.
custom_filters_html:
one: Você está prestes a adicionar até <strong>%{count} filtro</strong> de <strong>%{filename}</strong> para seus <strong>filtros</strong>.
other: Você está prestes a adicionar até <strong>%{count} filtros</strong> de <strong>%{filename}</strong> para seus <strong>filtros</strong>.
domain_blocking_html:
one: Você está prestes a <strong>bloquear</strong> até <strong>%{count} domínio</strong> de <strong>%{filename}</strong>.
other: Você está prestes a <strong>bloquear</strong> até <strong>%{count} domínios</strong> de <strong>%{filename}</strong>.

View File

@ -1650,7 +1650,9 @@ sq:
empty: Kartelë CSV e zbrazët
incompatible_type: E papërputhshme me llojin e përzgjedhur për importim
invalid_csv_file: 'Kartelë CSV e pavlefshme. Gabim: %{error}'
invalid_json_file: 'Kartelë JSON e pavlefshme. Gabim: %{error}'
over_rows_processing_limit: përmban më shumë se %{count} rreshta
status_not_found_warning: Disa nga filtrat tuaj fshehin postime specifike që snjihen nga ky shërbyes. Këto postime sdo të filtrohen automatikisht, nëse zbulohen më vonë nga shërbyesi.
too_large: Kartela është shumë e madhe
failures: Dështime
imported: Të importuar

View File

@ -505,6 +505,21 @@ sv:
no_lists_yet: Inga listor ännu
last_email: Senaste e-post
lead: Konton som har aktiverat funktionen och har prenumeranter kommer att visas nedan.
show:
confirm_disable_feature: Inaktivera nyhetsbrev för %{name}? E-postuppdateringar kommer inte längre att skickas för detta konto. Användaren kommer fortfarande att kunna återaktivera funktionen i sina kontoinställningar. Redigera behörigheten för kontot i Roller för att permanent ta bort åtkomst till den här funktionen.
confirm_remove_subscriber: "%{email} kommer inte längre att ta emot e-post från %{name}. Denna åtgärd kan inte ångras."
consent: Prenumeranter har endast samtyckt till att ta emot inlägg via e-post. Använd inte denna lista för andra ändamål.
date: Datum för registrering
disable_feature: Inaktivera funktion
disabled: Funktionen har inaktiverats och e-postmeddelanden skickas inte längre till denna lista.
email: E-postadress
empty:
hint: Ingen prenumererar på detta konto än.
no_subscribers_yet: Inga prenumeranter än
enable_feature: Aktivera funktion
no_access_html: Detta konto har inte längre de behörigheter som krävs för att aktivera funktionen. Ändra detta i <a href="%{roles_path}">Roller</a>.
title: E-postnyhetsbrev för %{name}
view_account: Visa konto
status: Status
subscribers: Prenumeranter
title: E-postlistor
@ -1532,7 +1547,9 @@ sv:
success_html: Du kommer nu att börja få e-postmeddelanden när %{name} publicerar nya inlägg. Lägg till %{sender} till dina kontakter så att dessa inlägg inte hamnar i din skräppostmapp.
title: Du är registrerad
unsubscribe: Avprenumerera
disabled: Inaktiverad
inactive: Inaktiv
no_access: Ingen åtkomst
status: Status
subscribers: Prenumeranter
emoji_styles:

View File

@ -1665,7 +1665,9 @@ tr:
empty: Boş CSV Dosyası
incompatible_type: Seçilen içe aktarma türüyle uyumsuz
invalid_csv_file: 'Geçersiz CSV dosyası. Hata: %{error}'
invalid_json_file: 'Geçersiz JSON dosyası. Hata: %{error}'
over_rows_processing_limit: "%{count} satırdan fazlasını içeriyor"
status_not_found_warning: Filtrelerinizden bazıları, bu sunucu tarafından tanınmayan belirli gönderileri gizler. Bu gönderiler, daha sonra sunucu tarafından tespit edildiklerinde otomatik olarak filtrelenmeyecektir.
too_large: Dosya çok büyük
failures: Başarısızlıklar
imported: İçe aktarılan
@ -1682,6 +1684,9 @@ tr:
bookmarks_html:
one: "<strong>Yerimlerinizi</strong>, <strong>%{filename}</strong> dosyasından <strong>%{count} gönderi</strong> ile değiştirmek üzeresiniz."
other: "<strong>Yerimlerinizi</strong>, <strong>%{filename}</strong> dosyasından <strong>%{count} gönderi</strong> ile değiştirmek üzeresiniz."
custom_filters_html:
one: "<strong>Filtrelerinizi</strong> <strong>%{filename}</strong> dosyasının içeriğiyle değiştirmek üzeresiniz. Yeni filtrelere en fazla <strong>%{count} filtre</strong> eklenecektir."
other: "<strong>Filtrelerinizi</strong> <strong>%{filename}</strong> dosyasının içeriğiyle değiştirmek üzeresiniz. Yeni filtrelere en fazla <strong>%{count} filtre</strong> eklenecektir."
domain_blocking_html:
one: "<strong>Alan adı engelleme listenizi</strong>, <strong>%{filename}</strong> dosyasından <strong>%{count} alan adı</strong> ile değiştirmek üzeresiniz."
other: "<strong>Alan adı engelleme listenizi</strong>, <strong>%{filename}</strong> dosyasından <strong>%{count} alan adı</strong> ile değiştirmek üzeresiniz."
@ -1701,6 +1706,9 @@ tr:
bookmarks_html:
one: "<strong>Yerimlerinize</strong>, <strong>%{filename}</strong> dosyasından <strong>%{count} gönderi</strong> eklemek üzeresiniz."
other: "<strong>Yerimlerinize</strong>, <strong>%{filename}</strong> dosyasından <strong>%{count} gönderi</strong> eklemek üzeresiniz."
custom_filters_html:
one: "<strong>%{filename}</strong> dosyasındaki <strong>%{count} adet </strong> filtresini <strong>filters</strong> listenize eklemek üzeresiniz."
other: "<strong>%{filename}</strong> dosyasındaki <strong>%{count} adet </strong> filtresini <strong>filters</strong> listenize eklemek üzeresiniz."
domain_blocking_html:
one: "<strong>%{filename}</strong> dosyasından <strong>%{count} alan adı</strong> <strong>engellemek</strong> üzeresiniz."
other: "<strong>%{filename}</strong> dosyasından <strong>%{count} alan adı</strong> <strong>engellemek</strong> üzeresiniz."

View File

@ -1636,7 +1636,9 @@ vi:
empty: File CSV trống
incompatible_type: Không tương thích với loại nhập đã chọn
invalid_csv_file: 'Tệp CSV không hợp lệ. Lỗi: %{error}'
invalid_json_file: 'Tệp JSON không hợp lệ. Lỗi: %{error}'
over_rows_processing_limit: chứa nhiều hơn %{count} hàng
status_not_found_warning: Một số bộ lọc của bạn ẩn các tút cụ thể mà máy chủ này không biết đến. Những tút này sẽ không tự động bị lọc nếu máy chủ phát hiện ra chúng sau này.
too_large: Tệp quá lớn
failures: Thất bại
imported: Đã nhập
@ -1651,6 +1653,8 @@ vi:
other: Bạn sắp <strong>thay thế danh sách chặn</strong> với <strong>%{count} người</strong> từ <strong>%{filename}</strong>.
bookmarks_html:
other: Bạn sắp <strong>thay thế danh sách tút đã lưu</strong> với <strong>%{count} tút</strong> từ <strong>%{filename}</strong>.
custom_filters_html:
other: Bạn sắp <strong>thay thế bộ lọc</strong> với nội dung của <strong>%{filename}</strong>. Có <strong>%{count} tài khoản</strong> sẽ được thêm vào những bộ lọc mới.
domain_blocking_html:
other: Bạn sắp <strong>thay thế danh sách máy chủ chặn</strong> với <strong>%{count} máy chủ</strong> từ <strong>%{filename}</strong>.
following_html:
@ -1664,6 +1668,8 @@ vi:
other: Bạn sắp <strong>chặn</strong> tới <strong>%{count} người</strong> từ <strong>%{filename}</strong>.
bookmarks_html:
other: Bạn sắp thêm tới <strong>%{count} tút</strong> từ <strong>%{filename}</strong> vào <strong>danh sách tút đã lưu</strong>.
custom_filters_html:
other: Bạn sắp thêm tới <strong>%{count} bộ lọc</strong> từ <strong>%{filename}</strong> vào <strong>bộ lọc</strong>.
domain_blocking_html:
other: Bạn sắp <strong>chặn</strong> tới <strong>%{count} máy chủ</strong> từ <strong>%{filename}</strong>.
following_html:

View File

@ -1635,8 +1635,10 @@ zh-CN:
errors:
empty: CSV 文件为空
incompatible_type: 与所选导入类型不兼容
invalid_csv_file: '无效的 CSV 文件。错误: %{error}'
invalid_csv_file: 无效的 CSV 文件。错误:%{error}
invalid_json_file: 无效的 JSON 文件。错误:%{error}
over_rows_processing_limit: 包含行数超过了 %{count}
status_not_found_warning: 你的部分过滤器将会过滤此服务器尚未知晓的嘟文。如果这些嘟文之后被服务器发现,则将不会被自动过滤。
too_large: 文件过大
failures: 失败
imported: 已导入
@ -1651,6 +1653,8 @@ zh-CN:
other: 你即将使用来自 <strong>%{filename}</strong> 的最多 <strong>%{count} 个</strong>账号<strong>替换你的屏蔽列表</strong>。
bookmarks_html:
other: 你即将使用来自 <strong>%{filename}</strong> 的最多 <strong>%{count} 条</strong>嘟文<strong>替换你的收藏列表</strong>。
custom_filters_html:
other: 你即将使用来自 <strong>%{filename}</strong> 的内容<strong>替换你的过滤器</strong>。最多将会有 <strong>%{count} 个过滤器</strong> 被添加为新过滤器。
domain_blocking_html:
other: 你即将使用来自 <strong>%{filename}</strong> 的最多 <strong>%{count} 个</strong>站点域名<strong>替换你的站点屏蔽列表</strong>。
following_html:
@ -1664,6 +1668,8 @@ zh-CN:
other: 你即将<strong>屏蔽</strong>来自 <strong>%{filename}</strong> 的最多 <strong>%{count} 个账号</strong>。
bookmarks_html:
other: 你即将把来自<strong> %{filename} </strong>的<strong> %{count} 篇嘟文</strong>添加到你的<strong>收藏夹</strong>中。
custom_filters_html:
other: 你即将把来自<strong> %{filename} </strong>的<strong> %{count} 个过滤器</strong>添加到你的<strong>过滤器</strong>中。
domain_blocking_html:
other: 你即将<strong>屏蔽</strong>来自 <strong>%{filename}</strong> 的最多 <strong>%{count} 个站点</strong>。
following_html:

View File

@ -1640,7 +1640,9 @@ zh-TW:
empty: 空的 CSV 檔案
incompatible_type: 與所選的匯入類型不相容
invalid_csv_file: 無效的 CSV 檔案。錯誤訊息:%{error}
invalid_json_file: 無效的 JSON 檔案。錯誤訊息:%{error}
over_rows_processing_limit: 含有超過 %{count} 行
status_not_found_warning: 某些您的過濾器隱藏此伺服器仍未知之特定嘟文。若這些嘟文日後被伺服器發現,它們將不被自動過濾。
too_large: 檔案大小過大
failures: 失敗
imported: 已匯入
@ -1655,6 +1657,8 @@ zh-TW:
other: 您將要自 <strong>%{filename}</strong> 中之 <strong>%{count} 個帳號</strong> 以 <strong>取代您的封鎖帳號列表</strong>。
bookmarks_html:
other: 您將要自 <strong>%{filename}</strong> 中之 <strong>%{count} 個嘟文</strong> 以 <strong>取代您的書籤</strong>。
custom_filters_html:
other: 您將以 <strong>%{filename}</strong> 之內容<strong>取代您的過濾器</strong>。這將會新增 <strong>%{count} 個過濾器</strong>至新過濾器。
domain_blocking_html:
other: 您將要自 <strong>%{filename}</strong> 中之 <strong>%{count} 個網域</strong> 以 <strong>取代您的封鎖網域列表</strong>。
following_html:
@ -1668,6 +1672,8 @@ zh-TW:
other: 您將要 <strong>封鎖</strong> 自 <strong>%{filename}</strong> 中之 <strong>%{count} 個帳號</strong>。
bookmarks_html:
other: 您將要自 <strong>%{filename}</strong> 中之 <strong>%{count} 個嘟文</strong> 以 <strong>加入至您的書籤</strong>。
custom_filters_html:
other: 您將要自 <strong>%{filename}</strong> 中之 <strong>%{count} 個過濾器</strong> 以 <strong>加入至您的過濾器</strong>。
domain_blocking_html:
other: 您將要 <strong>封鎖</strong> 自 <strong>%{filename}</strong> 中之 <strong>%{count} 個網域</strong>。
following_html:

View File

@ -0,0 +1,7 @@
# frozen_string_literal: true
class AddMissingStatusToBulkImport < ActiveRecord::Migration[8.1]
def change
add_column :bulk_imports, :missing_status, :boolean, null: false, default: false
end
end

View File

@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[8.1].define(version: 2026_05_05_155103) do
ActiveRecord::Schema[8.1].define(version: 2026_06_11_150940) do
# These are extensions that must be enabled in order to support this database
enable_extension "pg_catalog.plpgsql"
@ -338,6 +338,7 @@ ActiveRecord::Schema[8.1].define(version: 2026_05_05_155103) do
t.datetime "finished_at", precision: nil
t.integer "imported_items", default: 0, null: false
t.boolean "likely_mismatched", default: false, null: false
t.boolean "missing_status", default: false, null: false
t.string "original_filename", default: "", null: false
t.boolean "overwrite", default: false, null: false
t.integer "processed_items", default: 0, null: false

View File

@ -17,7 +17,7 @@ module Mastodon
end
def default_prerelease
'beta.1'
'rc.1'
end
def prerelease

View File

@ -169,6 +169,39 @@ RSpec.describe Settings::ImportsController do
it_behaves_like 'export failed rows', 'following_accounts_failures.csv', "Account address,Show boosts,Notify on new posts,Languages\nfoo@bar,true,false,\nuser@bar,false,true,\"fr, de\"\n"
end
context 'with custom filters' do
subject { get :failures, params: { id: bulk_import.id }, format: :json }
let(:import_type) { 'custom_filters' }
let(:rows) do
[
{
'title' => 'random title',
'expires_at' => nil,
'context' => ['public', 'account'],
'action' => 'warn',
'keywords_attributes' => [{
'keyword' => 'all them keywords',
'whole_word' => true,
}, {
'keyword' => 'more keywords even',
'whole_word' => true,
}],
'statuses' => ['status'],
},
]
end
let(:bulk_import) { Fabricate(:bulk_import, account: user.account, type: import_type, state: :finished) }
before do
rows.each { |data| Fabricate(:bulk_import_row, bulk_import: bulk_import, data: data) }
bulk_import.update(total_items: bulk_import.rows.count, processed_items: bulk_import.rows.count, imported_items: 0)
end
it_behaves_like 'export failed rows', 'custom_filters_failures.json',
'{"custom_filters":[{"title":"random title","action":"warn","context":["public","account"],"statuses":["status"],"expires_at":null,"keywords_attributes":[{"keyword":"all them keywords","whole_word":true},{"keyword":"more keywords even","whole_word":true}]},{"title":"random title","action":"warn","context":["public","account"],"statuses":["status"],"expires_at":null,"keywords_attributes":[{"keyword":"all them keywords","whole_word":true},{"keyword":"more keywords even","whole_word":true}]}]}' # rubocop:disable Layout/LineLength
end
context 'with blocks' do
let(:import_type) { 'blocking' }
@ -291,5 +324,29 @@ RSpec.describe Settings::ImportsController do
it_behaves_like 'unsuccessful import', 'following', 'empty.csv', 'merge'
it_behaves_like 'unsuccessful import', 'following', 'empty.csv', 'overwrite'
context 'with custom filter' do
subject { post :create, params: { form_import: { type: 'custom_filters', mode: mode, data: data } } }
describe 'successful import' do
let(:data) { fixture_file_upload('custom_filters.json', 'application/json') }
let(:mode) { 'merge' }
it 'creates an unconfirmed bulk_import with expected type and redirects', :aggregate_failures do
expect { subject }.to change { user.account.bulk_imports.pluck(:state, :type) }.from([]).to([['unconfirmed', 'custom_filters']])
expect(response).to redirect_to(settings_import_path(user.account.bulk_imports.first))
end
end
describe 'failing import' do
let(:mode) { 'merge' }
let(:data) { fixture_file_upload('empty.json', 'application/json') }
it 'does not creates an unconfirmed bulk_import', :aggregate_failures do
expect { subject }.to_not(change { user.account.bulk_imports.count })
expect(response.body).to include('field_with_errors')
end
end
end
end
end

32
spec/fixtures/files/custom_filters.json vendored Normal file
View File

@ -0,0 +1,32 @@
{
"custom_filters": [
{
"title": "dfjswa",
"expires_at": null,
"context": ["home"],
"action": "warn",
"keywords_attributes": [{ "keyword": "dvshja", "whole_word": true }],
"statuses": []
},
{
"title": "filter with a phrase as title",
"expires_at": null,
"context": ["home", "notifications", "public"],
"action": "warn",
"keywords_attributes": [
{ "keyword": "more words let's see ", "whole_word": true }
],
"statuses": []
},
{
"title": "how do I add a status to a filter?",
"expires_at": null,
"context": ["public", "account"],
"action": "warn",
"keywords_attributes": [
{ "keyword": "something something", "whole_word": true }
],
"statuses": []
}
]
}

0
spec/fixtures/files/empty.json vendored Normal file
View File

View File

@ -43,6 +43,17 @@ RSpec.describe Form::Import do
end
end
describe 'when the import type is custom_filters' do
let(:data) { fixture_file_upload(import_file, content_type) }
let(:import_file) { File.open('spec/fixtures/files/custom_filters.json') }
let(:import_type) { 'custom_filters' }
let(:content_type) { 'application/json' }
it 'passes validation' do
expect(subject).to be_valid
end
end
context 'when the file too large' do
let(:import_type) { 'following' }
let(:import_file) { 'imports.txt' }
@ -258,6 +269,69 @@ RSpec.describe Form::Import do
end
end
describe 'when importing json' do
let(:import_type) { 'custom_filters' }
let(:data) { fixture_file_upload('custom_filters.json', 'application/json') }
let(:import_mode) { 'merge' }
let(:expected_rows) do
[
{
'title' => 'dfjswa',
'expires_at' => nil,
'context' => ['home'],
'action' => 'warn',
'keywords_attributes' => [{ 'keyword' => 'dvshja', 'whole_word' => true }],
'statuses' => [],
},
{
'title' => 'filter with a phrase as title',
'expires_at' => nil,
'context' => %w(home notifications public),
'action' => 'warn',
'keywords_attributes' => [
{ 'keyword' => "more words let's see ", 'whole_word' => true },
],
'statuses' => [],
},
{
'title' => 'how do I add a status to a filter?',
'expires_at' => nil,
'context' => ['public', 'account'],
'action' => 'warn',
'keywords_attributes' => [
{ 'keyword' => 'something something', 'whole_word' => true },
],
'statuses' => [],
},
]
end
before do
subject.save
end
context 'with a BulkImport' do
let(:bulk_import) { account.bulk_imports.first }
it 'creates a bulk import with correct values' do
expect(bulk_import)
.to be_present
.and have_attributes(
type: eq(subject.type),
original_filename: eq(subject.data.original_filename),
likely_mismatched?: eq(subject.likely_mismatched_json?),
overwrite?: eq(!!subject.overwrite), # rubocop:disable Style/DoubleNegation
processed_items: eq(0),
imported_items: eq(0),
total_items: eq(bulk_import.rows.count),
state_unconfirmed?: be(true)
)
expect(bulk_import.rows.pluck(:data))
.to match_array(expected_rows)
end
end
end
it_behaves_like('on successful import', 'following', 'merge', 'imports.txt', %w(user@example.com user@test.com).map { |acct| { 'acct' => acct } })
it_behaves_like('on successful import', 'following', 'overwrite', 'imports.txt', %w(user@example.com user@test.com).map { |acct| { 'acct' => acct } })
it_behaves_like('on successful import', 'blocking', 'merge', 'imports.txt', %w(user@example.com user@test.com).map { |acct| { 'acct' => acct } })

View File

@ -4,7 +4,10 @@ require 'rails_helper'
RSpec.describe 'Settings Imports' do
describe 'POST /settings/imports' do
before { sign_in Fabricate(:user) }
let(:user) { Fabricate(:user) }
let(:account) { user.account }
before { sign_in user }
it 'gracefully handles invalid nested params' do
post settings_imports_path(form_import: 'invalid')
@ -12,5 +15,22 @@ RSpec.describe 'Settings Imports' do
expect(response)
.to have_http_status(400)
end
describe 'with JSON' do
subject { post settings_imports_path, params: { form_import: { type: 'custom_filters', mode: 'merge', data: data } } }
let!(:data) { fixture_file_upload('custom_filters.json', 'application/json') }
let(:confirm) { post confirm_settings_import_path(id: user.account.bulk_imports.last.id) }
it 'redirects to confirm_settings_import_path' do
subject
expect(response).to have_http_status(302)
.and redirect_to(settings_import_path(id: user.account.bulk_imports.last.id))
expect(user.account.bulk_imports.last.state).to eq('unconfirmed')
confirm
expect(response).to have_http_status(302)
expect(user.account.bulk_imports.last.state).to eq('scheduled')
end
end
end
end

View File

@ -329,6 +329,106 @@ RSpec.describe BulkImportService do
end
end
context 'when importing custom_filters' do
let(:import_type) { 'custom_filters' }
let!(:rows) do
[{
'title' => 'baz',
'expires_at' => nil,
'context' => ['home', 'notifications'],
'action' => 'warn',
'keywords_attributes' => [{
'keyword' => 'discourse',
'whole_word' => true,
}, {
'keyword' => 'something',
'whole_word' => false,
}],
'statuses' => ['http://localhost:3000/ap/users/116646814515254858/statuses/116681350935935708'],
}, {
'title' => 'buzz',
'expires_at' => nil,
'context' => ['notifications'],
'action' => 'warn',
'keywords_attributes' => [{
'keyword' => 'discourse',
'whole_word' => true,
}, {
'keyword' => 'something',
'whole_word' => false,
}],
'statuses' => [ActivityPub::TagManager.instance.uri_for(status)],
}].map { |data| import.rows.create!(data: data) }
end
let(:overwrite) { false }
let(:status) { Fabricate(:status, account: account, text: 'something something') }
let(:filter) { Fabricate(:custom_filter, account: account, title: 'a mazing title') }
let(:status_filter) { Fabricate(:custom_filter_status, custom_filter: filter, status: status) }
before do
status_filter
end
it 'enqueues workers for the expected rows and updates filters, keywords and statuses after worker run' do
subject.call(import)
expect(row_worker_job_args).to match_array(rows.map(&:id))
stub_fetch_remote_and_drain_workers
expect(account.custom_filters.count).to eq(3)
expect(account.custom_filters.order(:phrase).last.statuses.count).to eq(1)
expect(account.custom_filters.last.keywords.count).to eq(2)
end
end
context 'when importing custom_filters with overwrite' do
let(:import_type) { 'custom_filters' }
let!(:rows) do
[{
'title' => 'baz',
'expires_at' => nil,
'context' => ['home', 'notifications'],
'action' => 'warn',
'keywords_attributes' => [{
'keyword' => 'discourse',
'whole_word' => true,
}, {
'keyword' => 'something',
'whole_word' => false,
}],
'statuses' => ['http://localhost:3000/ap/users/116646814515254858/statuses/116681350935935708'],
}, {
'title' => 'buzz',
'expires_at' => nil,
'context' => ['notifications'],
'action' => 'warn',
'keywords_attributes' => [{
'keyword' => 'discourse',
'whole_word' => true,
}, {
'keyword' => 'something',
'whole_word' => false,
}],
'statuses' => [ActivityPub::TagManager.instance.uri_for(status)],
}].map { |data| import.rows.create!(data: data) }
end
let(:overwrite) { true }
let(:status) { Fabricate(:status, text: 'something something') }
let(:filter) { Fabricate(:custom_filter, account: account, title: 'a mazing title') }
let(:status_filter) { Fabricate(:custom_filter_status, custom_filter: filter, status: status) }
before do
status_filter
end
it 'enqueues workers for the expected rows and updates filters, keywords and statuses after worker run' do
subject.call(import)
expect(row_worker_job_args).to match_array(rows.map(&:id))
stub_fetch_remote_and_drain_workers
expect(account.custom_filters.count).to eq(2)
expect(account.custom_filters.order(:phrase).last.statuses.count).to eq(1)
expect(account.custom_filters.order(:phrase).last.keywords.count).to eq(2)
end
end
def row_worker_job_args
Import::RowWorker
.jobs

View File

@ -0,0 +1,44 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe MuteWorker do
let(:worker) { described_class.new }
describe '#perform' do
subject { worker.perform(account_id, target_account_id) }
let(:account) { Fabricate :account }
let(:target_account) { Fabricate :account }
let(:account_id) { account.id }
let(:target_account_id) { target_account.id }
context 'when account is invalid' do
let(:account_id) { nil }
it { is_expected.to be(true) }
end
context 'when target account is invalid' do
let(:target_account_id) { nil }
it { is_expected.to be(true) }
end
context 'with feed contents' do
let(:manager_service) { instance_double(FeedManager, clear_from_home: nil, clear_from_lists: nil) }
before { allow(FeedManager).to receive(:instance).and_return manager_service }
it 'clears feeds' do
subject
expect(manager_service)
.to have_received(:clear_from_home).with(account, target_account)
expect(manager_service)
.to have_received(:clear_from_lists).with(account, target_account)
end
end
end
end