Merge commit '4a7395d989e40c3bd4ef18173aa5ea850c8ddcdd' into glitch-soc/merge-upstream

Conflicts:
- `README.md`:
  Upstream modified its README, we have a completely different one.
  Kept our README file.
th-downstream
Claire 11 months ago
commit 6e56f95285

@ -50,6 +50,10 @@ You can contribute in the following ways:
If your contributions are accepted into Mastodon, you can request to be paid through [our OpenCollective](https://opencollective.com/mastodon). If your contributions are accepted into Mastodon, you can request to be paid through [our OpenCollective](https://opencollective.com/mastodon).
## API Changes and Additions
Please note that any changes or additions made to the API should have an accompanying pull request on [our documentation repository](https://github.com/mastodon/documentation).
## Bug reports ## Bug reports
Bug reports and feature suggestions must use descriptive and concise titles and be submitted to [GitHub Issues](https://github.com/mastodon/mastodon/issues). Please use the search function to make sure that you are not submitting duplicates, and that a similar report or request has not already been resolved or rejected. Bug reports and feature suggestions must use descriptive and concise titles and be submitted to [GitHub Issues](https://github.com/mastodon/mastodon/issues). Please use the search function to make sure that you are not submitting duplicates, and that a similar report or request has not already been resolved or rejected.

@ -118,7 +118,7 @@ GEM
minitest (>= 5.1) minitest (>= 5.1)
mutex_m mutex_m
tzinfo (~> 2.0) tzinfo (~> 2.0)
addressable (2.8.5) addressable (2.8.6)
public_suffix (>= 2.0.2, < 6.0) public_suffix (>= 2.0.2, < 6.0)
aes_key_wrap (1.1.0) aes_key_wrap (1.1.0)
android_key_attestation (0.3.0) android_key_attestation (0.3.0)
@ -220,9 +220,9 @@ GEM
database_cleaner-core (~> 2.0.0) database_cleaner-core (~> 2.0.0)
database_cleaner-core (2.0.1) database_cleaner-core (2.0.1)
date (3.3.4) date (3.3.4)
debug (1.8.0) debug (1.9.0)
irb (>= 1.5.0) irb (~> 1.10)
reline (>= 0.3.1) reline (>= 0.3.8)
debug_inspector (1.1.0) debug_inspector (1.1.0)
devise (4.9.3) devise (4.9.3)
bcrypt (~> 3.0) bcrypt (~> 3.0)
@ -484,8 +484,8 @@ GEM
nokogiri (1.15.5) nokogiri (1.15.5)
mini_portile2 (~> 2.8.2) mini_portile2 (~> 2.8.2)
racc (~> 1.4) racc (~> 1.4)
oj (3.16.2) oj (3.16.3)
bigdecimal (~> 3.1) bigdecimal (>= 3.0)
omniauth (2.1.1) omniauth (2.1.1)
hashie (>= 3.4.6) hashie (>= 3.4.6)
rack (>= 2.2.3) rack (>= 2.2.3)

@ -114,7 +114,7 @@ module ApplicationHelper
end end
def fa_icon(icon, attributes = {}) def fa_icon(icon, attributes = {})
class_names = attributes[:class]&.split(' ') || [] class_names = attributes[:class]&.split || []
class_names << 'fa' class_names << 'fa'
class_names += icon.split.map { |cl| "fa-#{cl}" } class_names += icon.split.map { |cl| "fa-#{cl}" }

@ -18,6 +18,6 @@ export const notificationsUpdate = createAction(
playSound: boolean; playSound: boolean;
}) => ({ }) => ({
payload: args, payload: args,
meta: { playSound: playSound ? { sound: 'boop' } : undefined }, meta: { sound: playSound ? 'boop' : undefined },
}), }),
); );

@ -21,6 +21,7 @@
"account.blocked": "Blocked", "account.blocked": "Blocked",
"account.browse_more_on_origin_server": "Browse more on the original profile", "account.browse_more_on_origin_server": "Browse more on the original profile",
"account.cancel_follow_request": "Cancel follow", "account.cancel_follow_request": "Cancel follow",
"account.copy": "Copy link to profile",
"account.direct": "Privately mention @{name}", "account.direct": "Privately mention @{name}",
"account.disable_notifications": "Stop notifying me when @{name} posts", "account.disable_notifications": "Stop notifying me when @{name} posts",
"account.domain_blocked": "Domain blocked", "account.domain_blocked": "Domain blocked",
@ -191,6 +192,7 @@
"conversation.mark_as_read": "Mark as read", "conversation.mark_as_read": "Mark as read",
"conversation.open": "View conversation", "conversation.open": "View conversation",
"conversation.with": "With {names}", "conversation.with": "With {names}",
"copy_icon_button.copied": "Copied to clipboard",
"copypaste.copied": "Copied", "copypaste.copied": "Copied",
"copypaste.copy_to_clipboard": "Copy to clipboard", "copypaste.copy_to_clipboard": "Copy to clipboard",
"directory.federated": "From known fediverse", "directory.federated": "From known fediverse",
@ -222,6 +224,7 @@
"emoji_button.search_results": "Search results", "emoji_button.search_results": "Search results",
"emoji_button.symbols": "Symbols", "emoji_button.symbols": "Symbols",
"emoji_button.travel": "Travel & Places", "emoji_button.travel": "Travel & Places",
"empty_column.account_hides_collections": "This user has chosen to not make this information available",
"empty_column.account_suspended": "Account suspended", "empty_column.account_suspended": "Account suspended",
"empty_column.account_timeline": "No posts here!", "empty_column.account_timeline": "No posts here!",
"empty_column.account_unavailable": "Profile unavailable", "empty_column.account_unavailable": "Profile unavailable",
@ -478,6 +481,8 @@
"onboarding.follows.empty": "Unfortunately, no results can be shown right now. You can try using search or browsing the explore page to find people to follow, or try again later.", "onboarding.follows.empty": "Unfortunately, no results can be shown right now. You can try using search or browsing the explore page to find people to follow, or try again later.",
"onboarding.follows.lead": "You curate your own home feed. The more people you follow, the more active and interesting it will be. These profiles may be a good starting point—you can always unfollow them later!", "onboarding.follows.lead": "You curate your own home feed. The more people you follow, the more active and interesting it will be. These profiles may be a good starting point—you can always unfollow them later!",
"onboarding.follows.title": "Personalize your home feed", "onboarding.follows.title": "Personalize your home feed",
"onboarding.profile.discoverable": "Make my profile discoverable",
"onboarding.profile.discoverable_hint": "When you opt in to discoverability on Mastodon, your posts may appear in search results and trending, and your profile may be suggested to people with similar interests to you.",
"onboarding.profile.display_name": "Display name", "onboarding.profile.display_name": "Display name",
"onboarding.profile.display_name_hint": "Your full name or your fun name…", "onboarding.profile.display_name_hint": "Your full name or your fun name…",
"onboarding.profile.lead": "You can always complete this later in the settings, where even more customisation options are available.", "onboarding.profile.lead": "You can always complete this later in the settings, where even more customisation options are available.",
@ -530,6 +535,7 @@
"privacy.unlisted.short": "Unlisted", "privacy.unlisted.short": "Unlisted",
"privacy_policy.last_updated": "Last updated {date}", "privacy_policy.last_updated": "Last updated {date}",
"privacy_policy.title": "Privacy Policy", "privacy_policy.title": "Privacy Policy",
"recommended": "Recommended",
"refresh": "Refresh", "refresh": "Refresh",
"regeneration_indicator.label": "Loading…", "regeneration_indicator.label": "Loading…",
"regeneration_indicator.sublabel": "Your home feed is being prepared!", "regeneration_indicator.sublabel": "Your home feed is being prepared!",
@ -600,6 +606,7 @@
"search.quick_action.status_search": "Posts matching {x}", "search.quick_action.status_search": "Posts matching {x}",
"search.search_or_paste": "Search or paste URL", "search.search_or_paste": "Search or paste URL",
"search_popout.full_text_search_disabled_message": "Unavailable on {domain}.", "search_popout.full_text_search_disabled_message": "Unavailable on {domain}.",
"search_popout.full_text_search_logged_out_message": "Only available when logged in.",
"search_popout.language_code": "ISO language code", "search_popout.language_code": "ISO language code",
"search_popout.options": "Search options", "search_popout.options": "Search options",
"search_popout.quick_actions": "Quick actions", "search_popout.quick_actions": "Quick actions",

@ -21,6 +21,7 @@
"account.blocked": "Blokita", "account.blocked": "Blokita",
"account.browse_more_on_origin_server": "Foliumi pli ĉe la originala profilo", "account.browse_more_on_origin_server": "Foliumi pli ĉe la originala profilo",
"account.cancel_follow_request": "Nuligi peton por sekvado", "account.cancel_follow_request": "Nuligi peton por sekvado",
"account.copy": "Kopii ligilon al profilo",
"account.direct": "Private mencii @{name}", "account.direct": "Private mencii @{name}",
"account.disable_notifications": "Ne plu sciigi min, kiam @{name} mesaĝas", "account.disable_notifications": "Ne plu sciigi min, kiam @{name} mesaĝas",
"account.domain_blocked": "Domajno blokita", "account.domain_blocked": "Domajno blokita",
@ -191,6 +192,7 @@
"conversation.mark_as_read": "Marki legita", "conversation.mark_as_read": "Marki legita",
"conversation.open": "Vidi konversacion", "conversation.open": "Vidi konversacion",
"conversation.with": "Kun {names}", "conversation.with": "Kun {names}",
"copy_icon_button.copied": "Kopiis al kliptabulo",
"copypaste.copied": "Kopiita", "copypaste.copied": "Kopiita",
"copypaste.copy_to_clipboard": "Kopii al dosierujo", "copypaste.copy_to_clipboard": "Kopii al dosierujo",
"directory.federated": "El konata fediverso", "directory.federated": "El konata fediverso",
@ -202,7 +204,9 @@
"dismissable_banner.community_timeline": "Jen la plej novaj publikaj afiŝoj de uzantoj, kies kontojn gastigas {domain}.", "dismissable_banner.community_timeline": "Jen la plej novaj publikaj afiŝoj de uzantoj, kies kontojn gastigas {domain}.",
"dismissable_banner.dismiss": "Eksigi", "dismissable_banner.dismiss": "Eksigi",
"dismissable_banner.explore_links": "Tiuj novaĵoj estas aktuale priparolataj de uzantoj en tiu ĉi kaj aliaj serviloj, sur la malcentrigita reto.", "dismissable_banner.explore_links": "Tiuj novaĵoj estas aktuale priparolataj de uzantoj en tiu ĉi kaj aliaj serviloj, sur la malcentrigita reto.",
"dismissable_banner.explore_statuses": "Ĉi tioj estas afiŝoj de socia reto kiu populariĝas hodiau.",
"dismissable_banner.explore_tags": "Ĉi tiuj kradvostoj populariĝas en ĉi tiu kaj aliaj serviloj en la malcentraliza reto nun.", "dismissable_banner.explore_tags": "Ĉi tiuj kradvostoj populariĝas en ĉi tiu kaj aliaj serviloj en la malcentraliza reto nun.",
"dismissable_banner.public_timeline": "Ĉi tioj estas plej lastaj publikaj afiŝoj de personoj ĉe socia reto kiu personoj ĉe {domain} sekvas.",
"embed.instructions": "Enkorpigu ĉi tiun afiŝon en vian retejon per kopio de la suba kodo.", "embed.instructions": "Enkorpigu ĉi tiun afiŝon en vian retejon per kopio de la suba kodo.",
"embed.preview": "Ĝi aperos tiel:", "embed.preview": "Ĝi aperos tiel:",
"emoji_button.activity": "Agadoj", "emoji_button.activity": "Agadoj",
@ -220,6 +224,7 @@
"emoji_button.search_results": "Serĉaj rezultoj", "emoji_button.search_results": "Serĉaj rezultoj",
"emoji_button.symbols": "Simboloj", "emoji_button.symbols": "Simboloj",
"emoji_button.travel": "Vojaĝoj kaj lokoj", "emoji_button.travel": "Vojaĝoj kaj lokoj",
"empty_column.account_hides_collections": "Ĉi tiu uzanto elektis ne disponebligi ĉi tiu informon",
"empty_column.account_suspended": "Konto suspendita", "empty_column.account_suspended": "Konto suspendita",
"empty_column.account_timeline": "Neniu afiŝo ĉi tie!", "empty_column.account_timeline": "Neniu afiŝo ĉi tie!",
"empty_column.account_unavailable": "Profilo ne disponebla", "empty_column.account_unavailable": "Profilo ne disponebla",
@ -229,6 +234,8 @@
"empty_column.direct": "Vi ankoraŭ ne havas privatan mencion. Kiam vi sendos aŭ ricevos iun, tiu aperos ĉi tie.", "empty_column.direct": "Vi ankoraŭ ne havas privatan mencion. Kiam vi sendos aŭ ricevos iun, tiu aperos ĉi tie.",
"empty_column.domain_blocks": "Ankoraŭ neniu domajno estas blokita.", "empty_column.domain_blocks": "Ankoraŭ neniu domajno estas blokita.",
"empty_column.explore_statuses": "Nenio tendencas nun. Rekontrolu poste!", "empty_column.explore_statuses": "Nenio tendencas nun. Rekontrolu poste!",
"empty_column.favourited_statuses": "Vi ankoraŭ ne havas stelumitan afiŝon.",
"empty_column.favourites": "Ankoraŭ neniu stelumis tiun afiŝon.",
"empty_column.follow_requests": "Vi ne ankoraŭ havas iun peton de sekvado. Kiam vi ricevos unu, ĝi aperos ĉi tie.", "empty_column.follow_requests": "Vi ne ankoraŭ havas iun peton de sekvado. Kiam vi ricevos unu, ĝi aperos ĉi tie.",
"empty_column.followed_tags": "Vi ankoraŭ ne sekvas iujn kradvortojn. Kiam vi faras, ili aperos ĉi tie.", "empty_column.followed_tags": "Vi ankoraŭ ne sekvas iujn kradvortojn. Kiam vi faras, ili aperos ĉi tie.",
"empty_column.hashtag": "Ankoraŭ estas nenio per ĉi tiu kradvorto.", "empty_column.hashtag": "Ankoraŭ estas nenio per ĉi tiu kradvorto.",
@ -292,19 +299,36 @@
"hashtag.column_settings.tag_mode.any": "Iu ajn", "hashtag.column_settings.tag_mode.any": "Iu ajn",
"hashtag.column_settings.tag_mode.none": "Neniu", "hashtag.column_settings.tag_mode.none": "Neniu",
"hashtag.column_settings.tag_toggle": "Aldoni pliajn etikedojn por ĉi tiu kolumno", "hashtag.column_settings.tag_toggle": "Aldoni pliajn etikedojn por ĉi tiu kolumno",
"hashtag.counter_by_accounts": "{count, plural,one {{counter} partoprenanto} other {{counter} partoprenantoj}}",
"hashtag.counter_by_uses": "{count, plural,one {{counter} afiŝo} other {{counter} afiŝoj}}",
"hashtag.counter_by_uses_today": "{count, plural,one {{counter} afiŝo} other {{counter} afiŝoj}} hodiau",
"hashtag.follow": "Sekvi la kradvorton", "hashtag.follow": "Sekvi la kradvorton",
"hashtag.unfollow": "Ne plu sekvi la kradvorton", "hashtag.unfollow": "Ne plu sekvi la kradvorton",
"hashtags.and_other": "…kaj {count, plural,other {# pli}}",
"home.actions.go_to_explore": "Vidi kio populariĝas",
"home.actions.go_to_suggestions": "Trovi homojn por sekvi", "home.actions.go_to_suggestions": "Trovi homojn por sekvi",
"home.column_settings.basic": "Bazaj agordoj", "home.column_settings.basic": "Bazaj agordoj",
"home.column_settings.show_reblogs": "Montri diskonigojn", "home.column_settings.show_reblogs": "Montri diskonigojn",
"home.column_settings.show_replies": "Montri respondojn", "home.column_settings.show_replies": "Montri respondojn",
"home.explore_prompt.body": "Via hejmafiŝaro havos miksitajn afiŝojn de kradvortoj kiujn vi elektis sekvi, personoj kiujn vi elektis sekvi, kaj afiŝoj kiujn ili suprenigis.",
"home.explore_prompt.title": "Ĉi tio estas via hejma paĝo en Mastodon.",
"home.hide_announcements": "Kaŝi la anoncojn", "home.hide_announcements": "Kaŝi la anoncojn",
"home.pending_critical_update.body": "Ĝisdatigu vian servilon de Mastodon kiel eble plej baldau!",
"home.pending_critical_update.link": "Vidi ĝisdatigojn",
"home.pending_critical_update.title": "Kritika sekurĝisdatigo estas disponebla!",
"home.show_announcements": "Montri anoncojn", "home.show_announcements": "Montri anoncojn",
"interaction_modal.description.favourite": "Per konto ĉe Mastodon, vi povas stelumiti ĉi tiun afiŝon por sciigi la afiŝanton ke vi aprezigas ŝin kaj konservas por la estonteco.",
"interaction_modal.description.follow": "Kun konto ĉe Mastodon, vi povos sekvi {name} por vidi ties mesaĝojn en via hejmo.", "interaction_modal.description.follow": "Kun konto ĉe Mastodon, vi povos sekvi {name} por vidi ties mesaĝojn en via hejmo.",
"interaction_modal.description.reblog": "Kun konto ĉe Mastodon, vi povas diskonigi ĉi tiun afiŝon, por ke viaj propraj sekvantoj vidu ĝin.", "interaction_modal.description.reblog": "Kun konto ĉe Mastodon, vi povas diskonigi ĉi tiun afiŝon, por ke viaj propraj sekvantoj vidu ĝin.",
"interaction_modal.description.reply": "Kun konto ĉe Mastodon, vi povos respondi al ĉi tiu mesaĝo.", "interaction_modal.description.reply": "Kun konto ĉe Mastodon, vi povos respondi al ĉi tiu mesaĝo.",
"interaction_modal.login.action": "Prenu min hejmen",
"interaction_modal.login.prompt": "Domajno de via hejma servilo, ekz. mastodon.social",
"interaction_modal.no_account_yet": "Ĉu ne estas ĉe Mastodon?",
"interaction_modal.on_another_server": "En alia servilo", "interaction_modal.on_another_server": "En alia servilo",
"interaction_modal.on_this_server": "En ĉi tiu servilo", "interaction_modal.on_this_server": "En ĉi tiu servilo",
"interaction_modal.sign_in": "Vi ne estas ensalutita al ĉi tiu servilo.",
"interaction_modal.sign_in_hint": "Gvideto: Tio estas la retejo kie vi registris. Vi ankau povas tajpi vian plenan uzantonomon!",
"interaction_modal.title.favourite": "Stelumi la afiŝon de {name}",
"interaction_modal.title.follow": "Sekvi {name}", "interaction_modal.title.follow": "Sekvi {name}",
"interaction_modal.title.reblog": "Akceli la afiŝon de {name}", "interaction_modal.title.reblog": "Akceli la afiŝon de {name}",
"interaction_modal.title.reply": "Respondi al la afiŝo de {name}", "interaction_modal.title.reply": "Respondi al la afiŝo de {name}",
@ -320,6 +344,8 @@
"keyboard_shortcuts.direct": "por malfermi la kolumnon pri privataj mencioj", "keyboard_shortcuts.direct": "por malfermi la kolumnon pri privataj mencioj",
"keyboard_shortcuts.down": "iri suben en la listo", "keyboard_shortcuts.down": "iri suben en la listo",
"keyboard_shortcuts.enter": "malfermi mesaĝon", "keyboard_shortcuts.enter": "malfermi mesaĝon",
"keyboard_shortcuts.favourite": "Stelumi afiŝon",
"keyboard_shortcuts.favourites": "Malfermi la liston de la stelumoj",
"keyboard_shortcuts.federated": "Malfermi la frataran templinion", "keyboard_shortcuts.federated": "Malfermi la frataran templinion",
"keyboard_shortcuts.heading": "Klavaraj mallongigoj", "keyboard_shortcuts.heading": "Klavaraj mallongigoj",
"keyboard_shortcuts.home": "Malfermi la hejman templinion", "keyboard_shortcuts.home": "Malfermi la hejman templinion",
@ -366,6 +392,7 @@
"lists.search": "Serĉi inter la homoj, kiujn vi sekvas", "lists.search": "Serĉi inter la homoj, kiujn vi sekvas",
"lists.subheading": "Viaj listoj", "lists.subheading": "Viaj listoj",
"load_pending": "{count,plural, one {# nova elemento} other {# novaj elementoj}}", "load_pending": "{count,plural, one {# nova elemento} other {# novaj elementoj}}",
"loading_indicator.label": "Ŝargado…",
"media_gallery.toggle_visible": "{number, plural, one {Kaŝi la bildon} other {Kaŝi la bildojn}}", "media_gallery.toggle_visible": "{number, plural, one {Kaŝi la bildon} other {Kaŝi la bildojn}}",
"moved_to_account_banner.text": "Via konto {disabledAccount} estas malvalidigita ĉar vi movis ĝin al {movedToAccount}.", "moved_to_account_banner.text": "Via konto {disabledAccount} estas malvalidigita ĉar vi movis ĝin al {movedToAccount}.",
"mute_modal.duration": "Daŭro", "mute_modal.duration": "Daŭro",
@ -382,6 +409,7 @@
"navigation_bar.domain_blocks": "Blokitaj domajnoj", "navigation_bar.domain_blocks": "Blokitaj domajnoj",
"navigation_bar.edit_profile": "Redakti profilon", "navigation_bar.edit_profile": "Redakti profilon",
"navigation_bar.explore": "Esplori", "navigation_bar.explore": "Esplori",
"navigation_bar.favourites": "Stelumoj",
"navigation_bar.filters": "Silentigitaj vortoj", "navigation_bar.filters": "Silentigitaj vortoj",
"navigation_bar.follow_requests": "Petoj de sekvado", "navigation_bar.follow_requests": "Petoj de sekvado",
"navigation_bar.followed_tags": "Sekvataj kradvortoj", "navigation_bar.followed_tags": "Sekvataj kradvortoj",
@ -389,6 +417,7 @@
"navigation_bar.lists": "Listoj", "navigation_bar.lists": "Listoj",
"navigation_bar.logout": "Adiaŭi", "navigation_bar.logout": "Adiaŭi",
"navigation_bar.mutes": "Silentigitaj uzantoj", "navigation_bar.mutes": "Silentigitaj uzantoj",
"navigation_bar.opened_in_classic_interface": "Afiŝoj, kontoj, kaj aliaj specifaj paĝoj kiuj estas malfermititaj defaulta en la klasika reta interfaco.",
"navigation_bar.personal": "Persone", "navigation_bar.personal": "Persone",
"navigation_bar.pins": "Alpinglitaj mesaĝoj", "navigation_bar.pins": "Alpinglitaj mesaĝoj",
"navigation_bar.preferences": "Preferoj", "navigation_bar.preferences": "Preferoj",
@ -398,6 +427,7 @@
"not_signed_in_indicator.not_signed_in": "Necesas saluti por aliri tiun rimedon.", "not_signed_in_indicator.not_signed_in": "Necesas saluti por aliri tiun rimedon.",
"notification.admin.report": "{name} raportis {target}", "notification.admin.report": "{name} raportis {target}",
"notification.admin.sign_up": "{name} kreis konton", "notification.admin.sign_up": "{name} kreis konton",
"notification.favourite": "{name} stelumis vian afiŝon",
"notification.follow": "{name} eksekvis vin", "notification.follow": "{name} eksekvis vin",
"notification.follow_request": "{name} petis sekvi vin", "notification.follow_request": "{name} petis sekvi vin",
"notification.mention": "{name} menciis vin", "notification.mention": "{name} menciis vin",
@ -411,6 +441,7 @@
"notifications.column_settings.admin.report": "Novaj raportoj:", "notifications.column_settings.admin.report": "Novaj raportoj:",
"notifications.column_settings.admin.sign_up": "Novaj registriĝoj:", "notifications.column_settings.admin.sign_up": "Novaj registriĝoj:",
"notifications.column_settings.alert": "Sciigoj de la retumilo", "notifications.column_settings.alert": "Sciigoj de la retumilo",
"notifications.column_settings.favourite": "Stelumoj:",
"notifications.column_settings.filter_bar.advanced": "Montri ĉiujn kategoriojn", "notifications.column_settings.filter_bar.advanced": "Montri ĉiujn kategoriojn",
"notifications.column_settings.filter_bar.category": "Rapida filtra breto", "notifications.column_settings.filter_bar.category": "Rapida filtra breto",
"notifications.column_settings.filter_bar.show_bar": "Montri la breton de filtrilo", "notifications.column_settings.filter_bar.show_bar": "Montri la breton de filtrilo",
@ -428,6 +459,7 @@
"notifications.column_settings.update": "Redaktoj:", "notifications.column_settings.update": "Redaktoj:",
"notifications.filter.all": "Ĉiuj", "notifications.filter.all": "Ĉiuj",
"notifications.filter.boosts": "Diskonigoj", "notifications.filter.boosts": "Diskonigoj",
"notifications.filter.favourites": "Stelumoj",
"notifications.filter.follows": "Sekvoj", "notifications.filter.follows": "Sekvoj",
"notifications.filter.mentions": "Mencioj", "notifications.filter.mentions": "Mencioj",
"notifications.filter.polls": "Balotenketaj rezultoj", "notifications.filter.polls": "Balotenketaj rezultoj",
@ -441,14 +473,29 @@
"notifications_permission_banner.enable": "Ŝalti retumilajn sciigojn", "notifications_permission_banner.enable": "Ŝalti retumilajn sciigojn",
"notifications_permission_banner.how_to_control": "Por ricevi sciigojn kiam Mastodon ne estas malfermita, ebligu labortablajn sciigojn. Vi povas regi precize kiuj specoj de interagoj generas labortablajn sciigojn per la supra butono {icon} post kiam ili estas ebligitaj.", "notifications_permission_banner.how_to_control": "Por ricevi sciigojn kiam Mastodon ne estas malfermita, ebligu labortablajn sciigojn. Vi povas regi precize kiuj specoj de interagoj generas labortablajn sciigojn per la supra butono {icon} post kiam ili estas ebligitaj.",
"notifications_permission_banner.title": "Neniam preterlasas iun ajn", "notifications_permission_banner.title": "Neniam preterlasas iun ajn",
"onboarding.action.back": "Prenu min reen",
"onboarding.actions.back": "Prenu min reen",
"onboarding.actions.go_to_explore": "See what's trending", "onboarding.actions.go_to_explore": "See what's trending",
"onboarding.actions.go_to_home": "Go to your home feed", "onboarding.actions.go_to_home": "Go to your home feed",
"onboarding.compose.template": "Saluton #Mastodon!", "onboarding.compose.template": "Saluton #Mastodon!",
"onboarding.follows.empty": "Bedaŭrinde, neniu rezulto estas montrebla nuntempe. Vi povas provi serĉi aŭ foliumi la esploran paĝon por trovi kontojn por sekvi, aŭ retrovi baldaŭ.", "onboarding.follows.empty": "Bedaŭrinde, neniu rezulto estas montrebla nuntempe. Vi povas provi serĉi aŭ foliumi la esploran paĝon por trovi kontojn por sekvi, aŭ retrovi baldaŭ.",
"onboarding.follows.lead": "You curate your own home feed. The more people you follow, the more active and interesting it will be. These profiles may be a good starting point—you can always unfollow them later!", "onboarding.follows.lead": "You curate your own home feed. The more people you follow, the more active and interesting it will be. These profiles may be a good starting point—you can always unfollow them later!",
"onboarding.follows.title": "Popular on Mastodon", "onboarding.follows.title": "Popular on Mastodon",
"onboarding.profile.discoverable": "Trovebligi mian profilon",
"onboarding.profile.discoverable_hint": "Kiam vi aliĝi al trovebleco ĉe Mastodon, viaj afiŝoj eble aperos en serĉaj rezultoj kaj populariĝoj, kaj via profilo eble estas sugestota al personoj kun similaj intereseoj al vi.",
"onboarding.profile.display_name": "Publika nomo",
"onboarding.profile.display_name_hint": "Via plena nomo aŭ via kromnomo…",
"onboarding.profile.lead": "Vi ĉiam povas plenigi ĉi tion poste en la agordoj, kie eĉ pli da personecigagordoj estas disponeblaj.",
"onboarding.profile.note": "Sinprezento",
"onboarding.profile.note_hint": "Vi povas @mencii aliajn homojn aŭ #kradvortojn…",
"onboarding.profile.save_and_continue": "Konservi kaj daŭrigi", "onboarding.profile.save_and_continue": "Konservi kaj daŭrigi",
"onboarding.profile.title": "Profila fikso",
"onboarding.profile.upload_avatar": "Alŝuti profilbildon",
"onboarding.profile.upload_header": "Alŝuti profilkapbildon",
"onboarding.share.lead": "Sciigi personojn pri kiel ili povas trovi vin ĉe Mastodon!",
"onboarding.share.message": "Mi estas {username} en #Mastodon! Sekvu min ĉe {url}", "onboarding.share.message": "Mi estas {username} en #Mastodon! Sekvu min ĉe {url}",
"onboarding.share.next_steps": "Eblaj malantauaj paŝoj:",
"onboarding.share.title": "Disvastigi vian profilon",
"onboarding.start.lead": "Your new Mastodon account is ready to go. Here's how you can make the most of it:", "onboarding.start.lead": "Your new Mastodon account is ready to go. Here's how you can make the most of it:",
"onboarding.start.skip": "Want to skip right ahead?", "onboarding.start.skip": "Want to skip right ahead?",
"onboarding.start.title": "Vi atingas ĝin!", "onboarding.start.title": "Vi atingas ĝin!",
@ -460,6 +507,9 @@
"onboarding.steps.setup_profile.title": "Customize your profile", "onboarding.steps.setup_profile.title": "Customize your profile",
"onboarding.steps.share_profile.body": "Let your friends know how to find you on Mastodon!", "onboarding.steps.share_profile.body": "Let your friends know how to find you on Mastodon!",
"onboarding.steps.share_profile.title": "Share your profile", "onboarding.steps.share_profile.title": "Share your profile",
"onboarding.tips.2fa": "<strong>Ĉu vi scias?</strong> Vi povas sekurigi vian konton per efektivigi dufaktora autentigo en via kontoagordoj.",
"onboarding.tips.accounts_from_other_servers": "<strong>Ĉu vi scias?</strong> Ĉar Mastodon estas sencentra, kelkaj profiloj kiujn vi trovi estas gastigitaj ĉe aliaj serviloj kiuj ne estas via.",
"onboarding.tips.migration": "<strong>Ĉu vi scias?</strong> Se vi sentas ke {domain} ne estas bona servilelekto por vi en la estonteco, vi povas translokiĝi al alia servilo de Mastodon sen malgajni viajn sekvantojn.",
"password_confirmation.mismatching": "Pasvorto konfirmo ne kongruas", "password_confirmation.mismatching": "Pasvorto konfirmo ne kongruas",
"picture_in_picture.restore": "Remetu ĝin", "picture_in_picture.restore": "Remetu ĝin",
"poll.closed": "Finita", "poll.closed": "Finita",

@ -702,7 +702,7 @@
"timeline_hint.resources.followers": "Les abonnés", "timeline_hint.resources.followers": "Les abonnés",
"timeline_hint.resources.follows": "Les abonnements", "timeline_hint.resources.follows": "Les abonnements",
"timeline_hint.resources.statuses": "Messages plus anciens", "timeline_hint.resources.statuses": "Messages plus anciens",
"trends.counter_by_accounts": "{count, plural, one {{counter} personne} other {{counter} personnes}} au cours {days, plural, one {des dernières 24h} other {des {days} derniers jours}}", "trends.counter_by_accounts": "{count, plural, one {{counter} pers.} other {{counter} pers.}} sur {days, plural, one {les dernières 24h} other {les {days} derniers jours}}",
"trends.trending_now": "Tendance en ce moment", "trends.trending_now": "Tendance en ce moment",
"ui.beforeunload": "Votre brouillon sera perdu si vous quittez Mastodon.", "ui.beforeunload": "Votre brouillon sera perdu si vous quittez Mastodon.",
"units.short.billion": "{count}Md", "units.short.billion": "{count}Md",

@ -479,7 +479,7 @@
"onboarding.actions.go_to_home": "Ugrás a saját hírfolyamra", "onboarding.actions.go_to_home": "Ugrás a saját hírfolyamra",
"onboarding.compose.template": "Üdvözlet, #Mastodon!", "onboarding.compose.template": "Üdvözlet, #Mastodon!",
"onboarding.follows.empty": "Sajnos jelenleg nem jeleníthető meg eredmény. Kipróbálhatod a keresést vagy böngészheted a felfedező oldalon a követni kívánt személyeket, vagy próbáld meg később.", "onboarding.follows.empty": "Sajnos jelenleg nem jeleníthető meg eredmény. Kipróbálhatod a keresést vagy böngészheted a felfedező oldalon a követni kívánt személyeket, vagy próbáld meg később.",
"onboarding.follows.lead": "A saját hírfolyamod az elsődleges tapasztalás a Mastodonon. Minél több embert követsz, annál aktívabb és érdekesebb a dolog. Az induláshoz itt van néhány javaslat:", "onboarding.follows.lead": "A kezdőlapod a Mastodon használatának elsődleges módja. Minél több embert követsz, annál aktívabbak és érdekesebbek lesznek a dolgok. Az induláshoz itt van néhány javaslat:",
"onboarding.follows.title": "Szabd személyre a kezdőlapodat", "onboarding.follows.title": "Szabd személyre a kezdőlapodat",
"onboarding.profile.discoverable": "Saját profil beállítása felfedezhetőként", "onboarding.profile.discoverable": "Saját profil beállítása felfedezhetőként",
"onboarding.profile.discoverable_hint": "A Mastodonon a felfedezhetőség választása esetén a saját bejegyzéseid megjelenhetnek a keresési eredmények és a felkapott tartalmak között, valamint a profilod a hozzád hasonló érdeklődési körrel rendelkező embereknél is ajánlásra kerülhet.", "onboarding.profile.discoverable_hint": "A Mastodonon a felfedezhetőség választása esetén a saját bejegyzéseid megjelenhetnek a keresési eredmények és a felkapott tartalmak között, valamint a profilod a hozzád hasonló érdeklődési körrel rendelkező embereknél is ajánlásra kerülhet.",
@ -720,7 +720,7 @@
"upload_form.undo": "Törlés", "upload_form.undo": "Törlés",
"upload_form.video_description": "Leírás siket, hallássérült, vak vagy gyengénlátó emberek számára", "upload_form.video_description": "Leírás siket, hallássérült, vak vagy gyengénlátó emberek számára",
"upload_modal.analyzing_picture": "Kép elemzése…", "upload_modal.analyzing_picture": "Kép elemzése…",
"upload_modal.apply": "Alkalmazás", "upload_modal.apply": "Alkalmaz",
"upload_modal.applying": "Alkalmazás…", "upload_modal.applying": "Alkalmazás…",
"upload_modal.choose_image": "Kép kiválasztása", "upload_modal.choose_image": "Kép kiválasztása",
"upload_modal.description_placeholder": "A gyors, barna róka átugrik a lusta kutya fölött", "upload_modal.description_placeholder": "A gyors, barna róka átugrik a lusta kutya fölött",

@ -239,7 +239,7 @@
"empty_column.follow_requests": "아직 팔로우 요청이 없습니다. 요청을 받았을 때 여기에 나타납니다.", "empty_column.follow_requests": "아직 팔로우 요청이 없습니다. 요청을 받았을 때 여기에 나타납니다.",
"empty_column.followed_tags": "아직 아무 해시태그도 팔로우하고 있지 않습니다. 해시태그를 팔로우하면, 여기에 표시됩니다.", "empty_column.followed_tags": "아직 아무 해시태그도 팔로우하고 있지 않습니다. 해시태그를 팔로우하면, 여기에 표시됩니다.",
"empty_column.hashtag": "이 해시태그는 아직 사용되지 않았습니다.", "empty_column.hashtag": "이 해시태그는 아직 사용되지 않았습니다.",
"empty_column.home": "당신의 홈 타임라인은 비어있습니다! 더 많은 사람을 팔로우 하여 채워보세요. {suggestions}", "empty_column.home": "당신의 홈 타임라인은 비어있습니다! 더 많은 사람을 팔로우하여 채워보세요. {suggestions}",
"empty_column.list": "리스트에 아직 아무것도 없습니다. 리스트의 누군가가 게시물을 올리면 여기에 나타납니다.", "empty_column.list": "리스트에 아직 아무것도 없습니다. 리스트의 누군가가 게시물을 올리면 여기에 나타납니다.",
"empty_column.lists": "아직 리스트가 없습니다. 리스트를 만들면 여기에 나타납니다.", "empty_column.lists": "아직 리스트가 없습니다. 리스트를 만들면 여기에 나타납니다.",
"empty_column.mutes": "아직 아무도 뮤트하지 않았습니다.", "empty_column.mutes": "아직 아무도 뮤트하지 않았습니다.",

@ -1,6 +1,6 @@
{ {
"about.contact": "Ratio:", "about.contact": "Ratio:",
"about.domain_blocks.no_reason_available": "ratio abdere est", "about.domain_blocks.no_reason_available": "Ratio abdere est",
"account.account_note_header": "Annotatio", "account.account_note_header": "Annotatio",
"account.badges.bot": "Robotum", "account.badges.bot": "Robotum",
"account.badges.group": "Congregatio", "account.badges.group": "Congregatio",
@ -49,7 +49,7 @@
"dismissable_banner.explore_links": "These news stories are being talked about by people on this and other servers of the decentralized network right now.", "dismissable_banner.explore_links": "These news stories are being talked about by people on this and other servers of the decentralized network right now.",
"dismissable_banner.explore_tags": "These hashtags are gaining traction among people on this and other servers of the decentralized network right now.", "dismissable_banner.explore_tags": "These hashtags are gaining traction among people on this and other servers of the decentralized network right now.",
"embed.instructions": "Embed this status on your website by copying the code below.", "embed.instructions": "Embed this status on your website by copying the code below.",
"emoji_button.food": "cibus et potus", "emoji_button.food": "Cibus et potus",
"emoji_button.people": "Homines", "emoji_button.people": "Homines",
"emoji_button.search": "Quaerere...", "emoji_button.search": "Quaerere...",
"empty_column.account_timeline": "Hic nulla contributa!", "empty_column.account_timeline": "Hic nulla contributa!",
@ -57,13 +57,13 @@
"empty_column.home": "Your home timeline is empty! Follow more people to fill it up. {suggestions}", "empty_column.home": "Your home timeline is empty! Follow more people to fill it up. {suggestions}",
"empty_column.list": "There is nothing in this list yet. When members of this list post new statuses, they will appear here.", "empty_column.list": "There is nothing in this list yet. When members of this list post new statuses, they will appear here.",
"explore.trending_statuses": "Contributa", "explore.trending_statuses": "Contributa",
"generic.saved": "servavit", "generic.saved": "Servavit",
"hashtag.column_settings.tag_toggle": "Include additional tags in this column", "hashtag.column_settings.tag_toggle": "Include additional tags in this column",
"keyboard_shortcuts.back": "to navigate back", "keyboard_shortcuts.back": "Re navigare",
"keyboard_shortcuts.blocked": "to open blocked users list", "keyboard_shortcuts.blocked": "Aperire listam usorum obstructorum",
"keyboard_shortcuts.boost": "to boost", "keyboard_shortcuts.boost": "Inlustrare publicatio",
"keyboard_shortcuts.column": "to focus a status in one of the columns", "keyboard_shortcuts.column": "Columnam dirigere",
"keyboard_shortcuts.compose": "to focus the compose textarea", "keyboard_shortcuts.compose": "TextArea Compositi Attendere",
"keyboard_shortcuts.description": "Descriptio", "keyboard_shortcuts.description": "Descriptio",
"keyboard_shortcuts.direct": "to open direct messages column", "keyboard_shortcuts.direct": "to open direct messages column",
"keyboard_shortcuts.down": "to move down in the list", "keyboard_shortcuts.down": "to move down in the list",

@ -21,6 +21,7 @@
"account.blocked": "ဘလော့ထားသည်", "account.blocked": "ဘလော့ထားသည်",
"account.browse_more_on_origin_server": "မူရင်းပရိုဖိုင်တွင် ပိုမိုကြည့်ရှုပါ။", "account.browse_more_on_origin_server": "မူရင်းပရိုဖိုင်တွင် ပိုမိုကြည့်ရှုပါ။",
"account.cancel_follow_request": "စောင့်ကြည့်မှု ပယ်ဖျက်ခြင်း", "account.cancel_follow_request": "စောင့်ကြည့်မှု ပယ်ဖျက်ခြင်း",
"account.copy": "လင့်ခ်ကို ပရိုဖိုင်သို့ ကူးယူပါ",
"account.direct": "@{name} သီးသန့် သိရှိနိုင်အောင် မန်းရှင်းခေါ်မည်", "account.direct": "@{name} သီးသန့် သိရှိနိုင်အောင် မန်းရှင်းခေါ်မည်",
"account.disable_notifications": "@{name} ပို့စ်တင်သည့်အခါ ကျွန်ုပ်ထံ အသိပေးခြင်း မပြုလုပ်ရန်။", "account.disable_notifications": "@{name} ပို့စ်တင်သည့်အခါ ကျွန်ုပ်ထံ အသိပေးခြင်း မပြုလုပ်ရန်။",
"account.domain_blocked": "ဒိုမိန်း ပိတ်ပင်ထားခဲ့သည်", "account.domain_blocked": "ဒိုမိန်း ပိတ်ပင်ထားခဲ့သည်",
@ -191,6 +192,7 @@
"conversation.mark_as_read": "ဖတ်ပြီးသားအဖြစ်မှတ်ထားပါ", "conversation.mark_as_read": "ဖတ်ပြီးသားအဖြစ်မှတ်ထားပါ",
"conversation.open": "Conversation ကိုကြည့်မည်", "conversation.open": "Conversation ကိုကြည့်မည်",
"conversation.with": "{အမည်များ} ဖြင့်", "conversation.with": "{အမည်များ} ဖြင့်",
"copy_icon_button.copied": "ကလစ်ဘုတ်သို့ ကူးပါ",
"copypaste.copied": "ကူယူပြီးပါပြီ", "copypaste.copied": "ကူယူပြီးပါပြီ",
"copypaste.copy_to_clipboard": "ကလစ်ဘုတ်သို့ ကူးပါ", "copypaste.copy_to_clipboard": "ကလစ်ဘုတ်သို့ ကူးပါ",
"directory.federated": "သင် သိသော ဖက်ဒီမှ", "directory.federated": "သင် သိသော ဖက်ဒီမှ",
@ -389,6 +391,7 @@
"lists.search": "မိမိဖောလိုးထားသူများမှရှာဖွေမည်", "lists.search": "မိမိဖောလိုးထားသူများမှရှာဖွေမည်",
"lists.subheading": "သင့်၏စာရင်းများ", "lists.subheading": "သင့်၏စာရင်းများ",
"load_pending": "{count, plural, one {# new item} other {# new items}}", "load_pending": "{count, plural, one {# new item} other {# new items}}",
"loading_indicator.label": "လုပ်ဆောင်နေသည်…",
"media_gallery.toggle_visible": "{number, plural, one {Hide image} other {Hide images}}", "media_gallery.toggle_visible": "{number, plural, one {Hide image} other {Hide images}}",
"moved_to_account_banner.text": "{movedToAccount} အကောင့်သို့ပြောင်းလဲထားသဖြင့် {disabledAccount} အကောင့်မှာပိတ်ထားသည်", "moved_to_account_banner.text": "{movedToAccount} အကောင့်သို့ပြောင်းလဲထားသဖြင့် {disabledAccount} အကောင့်မှာပိတ်ထားသည်",
"mute_modal.duration": "ကြာချိန်", "mute_modal.duration": "ကြာချိန်",
@ -477,6 +480,13 @@
"onboarding.follows.empty": "ယခုအချိန် မည်သည့်ရလဒ်ကိုမျှ မပြသနိုင်ပါ။ လူများကိုစောင့်ကြည့်ရန်အတွက် Explore စာမျက်နှာကို အသုံးပြု၍ စမ်းကြည့်နိုင်သည် သို့မဟုတ် နောက်မှ ထပ်စမ်းကြည့်ပါ။", "onboarding.follows.empty": "ယခုအချိန် မည်သည့်ရလဒ်ကိုမျှ မပြသနိုင်ပါ။ လူများကိုစောင့်ကြည့်ရန်အတွက် Explore စာမျက်နှာကို အသုံးပြု၍ စမ်းကြည့်နိုင်သည် သို့မဟုတ် နောက်မှ ထပ်စမ်းကြည့်ပါ။",
"onboarding.follows.lead": "သင့်ကိုယ်ပိုင်ပို့စ်များ တင်နိုင်သည်။ သင်စောင့်ကြည့်သူ များလေလေ၊ စိတ်ဝင်စားစရာကောင်းသောပို့စ်များ တွေ့ရလေဖြစ်သည်။ ဤပရိုဖိုင်များမှာ ကောင်းမွန်သောအစပြုမှုတစ်ခုဖြစ်ပြီး ၎င်းတို့ကိုစောင့်ကြည့်ခြင်းမှလည်း အချိန်မရွေး ပယ်ဖျက်နိုင်ပါသည်။", "onboarding.follows.lead": "သင့်ကိုယ်ပိုင်ပို့စ်များ တင်နိုင်သည်။ သင်စောင့်ကြည့်သူ များလေလေ၊ စိတ်ဝင်စားစရာကောင်းသောပို့စ်များ တွေ့ရလေဖြစ်သည်။ ဤပရိုဖိုင်များမှာ ကောင်းမွန်သောအစပြုမှုတစ်ခုဖြစ်ပြီး ၎င်းတို့ကိုစောင့်ကြည့်ခြင်းမှလည်း အချိန်မရွေး ပယ်ဖျက်နိုင်ပါသည်။",
"onboarding.follows.title": "Mastodon တွင် ရေပန်းစားခြင်း", "onboarding.follows.title": "Mastodon တွင် ရေပန်းစားခြင်း",
"onboarding.profile.discoverable": "ပရိုဖိုင် ရှာဖွေနိုင်ပါမည်",
"onboarding.profile.display_name": "ဖော်ပြမည့်အမည်",
"onboarding.profile.display_name_hint": "သင့်အမည်အပြည့်အစုံ သို့မဟုတ် သင့်အမည်ပြောင်။",
"onboarding.profile.note": "ကိုယ်ရေးအကျဉ်း",
"onboarding.profile.save_and_continue": "သိမ်းပြီး ဆက်လုပ်ပါ",
"onboarding.profile.title": "ပရိုဖိုင်စနစ် ထည့်သွင်းခြင်း",
"onboarding.profile.upload_avatar": "ပရိုဖိုင်ပုံ အပ်လုဒ်လုပ်ပါ",
"onboarding.share.lead": "Mastodon တွင် သင့်အား မည်သို့ရှာတွေ့နိုင်သည်ကို အသိပေးပါ။", "onboarding.share.lead": "Mastodon တွင် သင့်အား မည်သို့ရှာတွေ့နိုင်သည်ကို အသိပေးပါ။",
"onboarding.share.message": "Mastodon ရှိ ကျွန်ုပ်၏အမည်မှာ {username} ဖြစ်သည်။ ကျွန်ုပ်ကို {url} တွင် စောင့်ကြည့်နိုင်ပါသည်", "onboarding.share.message": "Mastodon ရှိ ကျွန်ုပ်၏အမည်မှာ {username} ဖြစ်သည်။ ကျွန်ုပ်ကို {url} တွင် စောင့်ကြည့်နိုင်ပါသည်",
"onboarding.share.next_steps": "ဖြစ်နိုင်ချေရှိသော နောက်အဆင့်များ -", "onboarding.share.next_steps": "ဖြစ်နိုင်ချေရှိသော နောက်အဆင့်များ -",
@ -520,6 +530,7 @@
"privacy.unlisted.short": "စာရင်းမသွင်းထားပါ", "privacy.unlisted.short": "စာရင်းမသွင်းထားပါ",
"privacy_policy.last_updated": "နောက်ဆုံး ပြင်ဆင်ခဲ့သည့်ရက်စွဲ {date}", "privacy_policy.last_updated": "နောက်ဆုံး ပြင်ဆင်ခဲ့သည့်ရက်စွဲ {date}",
"privacy_policy.title": "ကိုယ်ရေးအချက်အလက်မူဝါဒ", "privacy_policy.title": "ကိုယ်ရေးအချက်အလက်မူဝါဒ",
"recommended": "အကြံပြုသည်",
"refresh": "ပြန်လည်စတင်ပါ", "refresh": "ပြန်လည်စတင်ပါ",
"regeneration_indicator.label": "လုပ်ဆောင်နေသည်…", "regeneration_indicator.label": "လုပ်ဆောင်နေသည်…",
"regeneration_indicator.sublabel": "သင့်ပင်မစာမျက်နှာကို ပြင်ဆင်နေပါသည်။", "regeneration_indicator.sublabel": "သင့်ပင်မစာမျက်နှာကို ပြင်ဆင်နေပါသည်။",
@ -590,6 +601,7 @@
"search.quick_action.status_search": "{x} နှင့် ကိုက်ညီသော ပို့စ်များ", "search.quick_action.status_search": "{x} နှင့် ကိုက်ညီသော ပို့စ်များ",
"search.search_or_paste": "URL ရိုက်ထည့်ပါ သို့မဟုတ် ရှာဖွေပါ", "search.search_or_paste": "URL ရိုက်ထည့်ပါ သို့မဟုတ် ရှာဖွေပါ",
"search_popout.full_text_search_disabled_message": "{domain} တွင် မရနိုင်ပါ။", "search_popout.full_text_search_disabled_message": "{domain} တွင် မရနိုင်ပါ။",
"search_popout.full_text_search_logged_out_message": "အကောင့်ဝင်ထားမှသာ ရနိုင်သည်။",
"search_popout.language_code": "ISO ဘာသာစကားကုဒ်", "search_popout.language_code": "ISO ဘာသာစကားကုဒ်",
"search_popout.options": "ရွေးချယ်ထားသည်များ ရှာဖွေရန်", "search_popout.options": "ရွေးချယ်ထားသည်များ ရှာဖွေရန်",
"search_popout.quick_actions": "အမြန်လုပ်ဆောင်မှုများ", "search_popout.quick_actions": "အမြန်လုပ်ဆောင်မှုများ",

@ -1 +1,53 @@
{} {
"about.contact": "सम्पर्क:",
"about.disclaimer": "Mastodon नि:शुल्क, खुला स्रोत सफ्टवेयर, र Mastodon gGmbH को ट्रेडमार्क हो।",
"about.domain_blocks.no_reason_available": "कारण उपलब्ध छैन",
"about.domain_blocks.preamble": "Mastodon ले तपाइँलाई सामान्यतया फेडिभर्समा कुनै पनि अन्य सर्भरका सामग्री हेर्न र प्रयोगकर्ताहरूसँग अन्तरक्रिया गर्न दिन्छ। यी अपवादहरू हुन् जुन यस विशेष सर्भरमा बनाइएका छन्।",
"about.domain_blocks.silenced.title": "सीमित",
"about.domain_blocks.suspended.explanation": "यस सर्भरबाट कुनै पनि डेटा प्रशोधन, भण्डारण वा आदानप्रदान गरिने छैन, जसले यस सर्भरका प्रयोगकर्ताहरूसँग कुनै पनि अन्तरक्रिया वा सञ्चारलाई असम्भव बनाउँछ।",
"about.domain_blocks.suspended.title": "निलम्बित",
"about.not_available": "यो जानकारी यस सर्भरमा उपलब्ध गराइएको छैन।",
"about.powered_by": "{mastodon} द्वारा संचालित विकेन्द्रीकृत सामाजिक मिडिया",
"about.rules": "सर्भर नियमहरू",
"account.add_or_remove_from_list": "सूचीबाट थप्नुहोस् वा हटाउनुहोस्",
"account.badges.group": "समूह",
"account.block": "@{name} लाई ब्लक गर्नुहोस्",
"account.block_domain": "{domain} डोमेनलाई ब्लक गर्नुहोस्",
"account.block_short": "ब्लक",
"account.blocked": "ब्लक गरिएको",
"account.browse_more_on_origin_server": "मूल प्रोफाइलमा थप ब्राउज गर्नुहोस्",
"account.cancel_follow_request": "फलो अनुरोध रद्द गर्नुहोस",
"account.copy": "प्रोफाइलको लिङ्क प्रतिलिपि गर्नुहोस्",
"account.direct": "@{name} लाई निजी रूपमा उल्लेख गर्नुहोस्",
"account.disable_notifications": "@{name} ले पोस्ट गर्दा मलाई सूचित नगर्नुहोस्",
"account.domain_blocked": "डोमेन ब्लक गरिएको छ",
"account.edit_profile": "प्रोफाइल सम्पादन गर्नुहोस्",
"account.enable_notifications": "@{name} ले पोस्ट गर्दा मलाई सूचित गर्नुहोस्",
"account.endorse": "प्रोफाइलमा फिचर गर्नुहोस्",
"account.featured_tags.last_status_never": "कुनै पोस्ट छैन",
"account.follow": "फलो गर्नुहोस",
"account.followers.empty": "यस प्रयोगकर्तालाई अहिलेसम्म कसैले फलो गर्दैन।",
"account.follows.empty": "यो प्रयोगकर्ताले अहिलेसम्म कसैलाई फलो गरेको छैन।",
"account.go_to_profile": "प्रोफाइलमा जानुहोस्",
"account.hide_reblogs": "@{name} को बूस्टहरू लुकाउनुहोस्",
"account.link_verified_on": "यस लिङ्कको स्वामित्व {date} मा जाँच गरिएको थियो",
"account.media": "मिडिया",
"account.mention": "@{name} लाई उल्लेख गर्नुहोस्",
"account.no_bio": "कुनै विवरण प्रदान गरिएको छैन।",
"account.posts": "पोस्टहरू",
"account.requested": "स्वीकृतिको पर्खाइमा। फलो अनुरोध रद्द गर्न क्लिक गर्नुहोस्",
"account.requested_follow": "{name} ले तपाईंलाई फलो गर्न अनुरोध गर्नुभएको छ",
"account.share": "@{name} को प्रोफाइल सेयर गर्नुहोस्",
"account.show_reblogs": "@{name} को बूस्टहरू देखाउनुहोस्",
"account.statuses_counter": "{count, plural, one {{counter} पोस्ट} other {{counter} पोस्टहरू}}",
"account.unblock": "@{name} लाई अनब्लक गर्नुहोस्",
"account.unblock_domain": "{domain} डोमेनलाई अनब्लक गर्नुहोस्",
"account.unblock_short": "अनब्लक गर्नुहोस्",
"account.unendorse": "प्रोफाइलमा फिचर नगर्नुहोस्",
"account.unfollow": "अनफलो गर्नुहोस्",
"account_note.placeholder": "नोट लेख्न क्लिक गर्नुहोस्",
"admin.dashboard.retention.average": "औसत",
"admin.dashboard.retention.cohort_size": "नयाँ प्रयोगकर्ताहरू",
"alert.rate_limited.message": "कृपया {retry_time, time, medium} पछि पुन: प्रयास गर्नुहोस्।",
"alert.unexpected.message": "एउटा अनपेक्षित त्रुटि भयो।"
}

@ -7,7 +7,7 @@
"about.domain_blocks.silenced.explanation": "Normalmente não verá perfis e conteúdo deste servidor, a menos que os procure explicitamente ou opte por os seguir.", "about.domain_blocks.silenced.explanation": "Normalmente não verá perfis e conteúdo deste servidor, a menos que os procure explicitamente ou opte por os seguir.",
"about.domain_blocks.silenced.title": "Limitados", "about.domain_blocks.silenced.title": "Limitados",
"about.domain_blocks.suspended.explanation": "Nenhum dado deste servidor será processado, armazenado ou trocado, impossibilitando qualquer interação ou comunicação com os utilizadores dessas instâncias.", "about.domain_blocks.suspended.explanation": "Nenhum dado deste servidor será processado, armazenado ou trocado, impossibilitando qualquer interação ou comunicação com os utilizadores dessas instâncias.",
"about.domain_blocks.suspended.title": "Supensos", "about.domain_blocks.suspended.title": "Suspensos",
"about.not_available": "Esta informação não foi disponibilizada neste servidor.", "about.not_available": "Esta informação não foi disponibilizada neste servidor.",
"about.powered_by": "Rede social descentralizada baseada no {mastodon}", "about.powered_by": "Rede social descentralizada baseada no {mastodon}",
"about.rules": "Regras do servidor", "about.rules": "Regras do servidor",

@ -224,6 +224,7 @@
"emoji_button.search_results": "Výsledky hľadania", "emoji_button.search_results": "Výsledky hľadania",
"emoji_button.symbols": "Symboly", "emoji_button.symbols": "Symboly",
"emoji_button.travel": "Cestovanie a miesta", "emoji_button.travel": "Cestovanie a miesta",
"empty_column.account_hides_collections": "Tento užívateľ si zvolil nesprístupniť túto informáciu",
"empty_column.account_suspended": "Účet bol pozastavený", "empty_column.account_suspended": "Účet bol pozastavený",
"empty_column.account_timeline": "Nie sú tu žiadne príspevky!", "empty_column.account_timeline": "Nie sú tu žiadne príspevky!",
"empty_column.account_unavailable": "Profil nedostupný", "empty_column.account_unavailable": "Profil nedostupný",

@ -8,7 +8,7 @@
"about.domain_blocks.silenced.title": "已受限", "about.domain_blocks.silenced.title": "已受限",
"about.domain_blocks.suspended.explanation": "來自此伺服器的資料都不會被處理、儲存或交換,也無法與此伺服器上的使用者互動或交流。", "about.domain_blocks.suspended.explanation": "來自此伺服器的資料都不會被處理、儲存或交換,也無法與此伺服器上的使用者互動或交流。",
"about.domain_blocks.suspended.title": "已停權", "about.domain_blocks.suspended.title": "已停權",
"about.not_available": "無法於伺服器上使用此資訊。", "about.not_available": "無法於伺服器上使用此資訊。",
"about.powered_by": "由 {mastodon} 提供的去中心化社群媒體", "about.powered_by": "由 {mastodon} 提供的去中心化社群媒體",
"about.rules": "伺服器規則", "about.rules": "伺服器規則",
"account.account_note_header": "備註", "account.account_note_header": "備註",
@ -34,9 +34,9 @@
"account.follow": "跟隨", "account.follow": "跟隨",
"account.followers": "跟隨者", "account.followers": "跟隨者",
"account.followers.empty": "尚未有人跟隨這位使用者。", "account.followers.empty": "尚未有人跟隨這位使用者。",
"account.followers_counter": "被 {count, plural,one {{counter} 人}other {{counter} 人}}跟隨", "account.followers_counter": "被 {count, plural, other {{counter} 人}}跟隨",
"account.following": "跟隨中", "account.following": "跟隨中",
"account.following_counter": "正在跟隨 {count, plural, one {{counter} 人} other {{counter} 人}}", "account.following_counter": "正在跟隨 {count,plural,other {{counter} 人}}",
"account.follows.empty": "這位使用者尚未跟隨任何人。", "account.follows.empty": "這位使用者尚未跟隨任何人。",
"account.follows_you": "跟隨了您", "account.follows_you": "跟隨了您",
"account.go_to_profile": "前往個人檔案", "account.go_to_profile": "前往個人檔案",
@ -72,8 +72,8 @@
"account.unmute_notifications_short": "取消靜音推播通知", "account.unmute_notifications_short": "取消靜音推播通知",
"account.unmute_short": "解除靜音", "account.unmute_short": "解除靜音",
"account_note.placeholder": "按此新增備註", "account_note.placeholder": "按此新增備註",
"admin.dashboard.daily_retention": "註冊後使用者存留率(日)", "admin.dashboard.daily_retention": "註冊後使用者存留率(日)",
"admin.dashboard.monthly_retention": "註冊後使用者存留率(月)", "admin.dashboard.monthly_retention": "註冊後使用者存留率(月)",
"admin.dashboard.retention.average": "平均", "admin.dashboard.retention.average": "平均",
"admin.dashboard.retention.cohort": "註冊月份", "admin.dashboard.retention.cohort": "註冊月份",
"admin.dashboard.retention.cohort_size": "新使用者", "admin.dashboard.retention.cohort_size": "新使用者",
@ -105,7 +105,7 @@
"closed_registrations.other_server_instructions": "因為 Mastodon 是去中心化的,所以您也能於其他伺服器上建立帳號,並仍然與這個伺服器互動。", "closed_registrations.other_server_instructions": "因為 Mastodon 是去中心化的,所以您也能於其他伺服器上建立帳號,並仍然與這個伺服器互動。",
"closed_registrations_modal.description": "目前無法於 {domain} 建立新帳號,但也請別忘了,您並不一定需要有 {domain} 伺服器的帳號,也能使用 Mastodon。", "closed_registrations_modal.description": "目前無法於 {domain} 建立新帳號,但也請別忘了,您並不一定需要有 {domain} 伺服器的帳號,也能使用 Mastodon。",
"closed_registrations_modal.find_another_server": "尋找另一個伺服器", "closed_registrations_modal.find_another_server": "尋找另一個伺服器",
"closed_registrations_modal.preamble": "Mastodon 是去中心化的,所以無論您於哪個伺服器新增帳號,都可以與此伺服器上的任何人跟隨及互動。您甚至能自行架一個自己的伺服器!", "closed_registrations_modal.preamble": "Mastodon 是去中心化的,所以無論您於哪個伺服器新增帳號,都可以與此伺服器上的任何人跟隨及互動。您甚至能自行架一個自己的伺服器!",
"closed_registrations_modal.title": "註冊 Mastodon", "closed_registrations_modal.title": "註冊 Mastodon",
"column.about": "關於", "column.about": "關於",
"column.blocks": "已封鎖的使用者", "column.blocks": "已封鎖的使用者",
@ -155,7 +155,7 @@
"compose_form.publish_form": "嘟出去", "compose_form.publish_form": "嘟出去",
"compose_form.publish_loud": "{publish}", "compose_form.publish_loud": "{publish}",
"compose_form.save_changes": "儲存變更", "compose_form.save_changes": "儲存變更",
"compose_form.sensitive.hide": "標記媒體為敏感內容", "compose_form.sensitive.hide": "{count, plural, other {將媒體標記為敏感內容}}",
"compose_form.sensitive.marked": "此媒體被標記為敏感內容", "compose_form.sensitive.marked": "此媒體被標記為敏感內容",
"compose_form.sensitive.unmarked": "此媒體未被標記為敏感內容", "compose_form.sensitive.unmarked": "此媒體未被標記為敏感內容",
"compose_form.spoiler.marked": "移除內容警告", "compose_form.spoiler.marked": "移除內容警告",
@ -207,14 +207,14 @@
"dismissable_banner.explore_statuses": "這些於此伺服器以及去中心化網路中其他伺服器發出的嘟文正在被此伺服器上的人們熱烈討論著。越多不同人轉嘟及最愛排名更高。", "dismissable_banner.explore_statuses": "這些於此伺服器以及去中心化網路中其他伺服器發出的嘟文正在被此伺服器上的人們熱烈討論著。越多不同人轉嘟及最愛排名更高。",
"dismissable_banner.explore_tags": "這些主題標籤正在被此伺服器以及去中心化網路上的人們熱烈討論著。越多不同人所嘟出的主題標籤排名更高。", "dismissable_banner.explore_tags": "這些主題標籤正在被此伺服器以及去中心化網路上的人們熱烈討論著。越多不同人所嘟出的主題標籤排名更高。",
"dismissable_banner.public_timeline": "這些是來自 {domain} 使用者們跟隨中帳號所發表之最新公開嘟文。", "dismissable_banner.public_timeline": "這些是來自 {domain} 使用者們跟隨中帳號所發表之最新公開嘟文。",
"embed.instructions": "若您欲於您的網站嵌入此嘟文,請複製以下程式碼。", "embed.instructions": "如要將此嘟文嵌入您的網站,請複製以下程式碼。",
"embed.preview": "它將顯示成這樣:", "embed.preview": "它將顯示成這樣:",
"emoji_button.activity": "活動", "emoji_button.activity": "活動",
"emoji_button.clear": "清除", "emoji_button.clear": "清除",
"emoji_button.custom": "自訂", "emoji_button.custom": "自訂",
"emoji_button.flags": "旗幟", "emoji_button.flags": "旗幟",
"emoji_button.food": "食物 & 飲料", "emoji_button.food": "食物 & 飲料",
"emoji_button.label": "插入表情符號", "emoji_button.label": "插入表情圖案",
"emoji_button.nature": "自然", "emoji_button.nature": "自然",
"emoji_button.not_found": "啊就沒這表情符號吼!! (╯°□°)╯︵ ┻━┻", "emoji_button.not_found": "啊就沒這表情符號吼!! (╯°□°)╯︵ ┻━┻",
"emoji_button.objects": "物件", "emoji_button.objects": "物件",
@ -353,11 +353,11 @@
"keyboard_shortcuts.legend": "顯示此說明選單", "keyboard_shortcuts.legend": "顯示此說明選單",
"keyboard_shortcuts.local": "開啟本站時間軸", "keyboard_shortcuts.local": "開啟本站時間軸",
"keyboard_shortcuts.mention": "提及作者", "keyboard_shortcuts.mention": "提及作者",
"keyboard_shortcuts.muted": "開啟靜音使用者列表", "keyboard_shortcuts.muted": "開啟靜音使用者清單",
"keyboard_shortcuts.my_profile": "開啟個人檔案頁面", "keyboard_shortcuts.my_profile": "開啟個人檔案頁面",
"keyboard_shortcuts.notifications": "開啟通知欄", "keyboard_shortcuts.notifications": "開啟通知欄",
"keyboard_shortcuts.open_media": "開啟媒體", "keyboard_shortcuts.open_media": "開啟媒體",
"keyboard_shortcuts.pinned": "開啟釘選的嘟文列表", "keyboard_shortcuts.pinned": "開啟釘選的嘟文清單",
"keyboard_shortcuts.profile": "開啟作者的個人檔案頁面", "keyboard_shortcuts.profile": "開啟作者的個人檔案頁面",
"keyboard_shortcuts.reply": "回應嘟文", "keyboard_shortcuts.reply": "回應嘟文",
"keyboard_shortcuts.requests": "開啟跟隨請求列表", "keyboard_shortcuts.requests": "開啟跟隨請求列表",
@ -386,7 +386,7 @@
"lists.new.create": "新增列表", "lists.new.create": "新增列表",
"lists.new.title_placeholder": "新列表標題", "lists.new.title_placeholder": "新列表標題",
"lists.replies_policy.followed": "任何跟隨的使用者", "lists.replies_policy.followed": "任何跟隨的使用者",
"lists.replies_policy.list": "列表成員", "lists.replies_policy.list": "成員清單",
"lists.replies_policy.none": "沒有人", "lists.replies_policy.none": "沒有人",
"lists.replies_policy.title": "顯示回覆:", "lists.replies_policy.title": "顯示回覆:",
"lists.search": "搜尋您跟隨的使用者", "lists.search": "搜尋您跟隨的使用者",
@ -452,7 +452,7 @@
"notifications.column_settings.push": "推播通知", "notifications.column_settings.push": "推播通知",
"notifications.column_settings.reblog": "轉嘟:", "notifications.column_settings.reblog": "轉嘟:",
"notifications.column_settings.show": "於欄位中顯示", "notifications.column_settings.show": "於欄位中顯示",
"notifications.column_settings.sound": "播放音", "notifications.column_settings.sound": "播放",
"notifications.column_settings.status": "新嘟文:", "notifications.column_settings.status": "新嘟文:",
"notifications.column_settings.unread_notifications.category": "未讀通知", "notifications.column_settings.unread_notifications.category": "未讀通知",
"notifications.column_settings.unread_notifications.highlight": "突顯未讀通知", "notifications.column_settings.unread_notifications.highlight": "突顯未讀通知",
@ -477,7 +477,7 @@
"onboarding.actions.back": "返回", "onboarding.actions.back": "返回",
"onboarding.actions.go_to_explore": "看看發生什麼新鮮事", "onboarding.actions.go_to_explore": "看看發生什麼新鮮事",
"onboarding.actions.go_to_home": "前往您的首頁時間軸", "onboarding.actions.go_to_home": "前往您的首頁時間軸",
"onboarding.compose.template": "哈囉 #Mastodon", "onboarding.compose.template": "你好 #Mastodon",
"onboarding.follows.empty": "很遺憾,目前未能顯示任何結果。您可以嘗試使用搜尋、瀏覽探索頁面以找尋人們跟隨、或稍候再試。", "onboarding.follows.empty": "很遺憾,目前未能顯示任何結果。您可以嘗試使用搜尋、瀏覽探索頁面以找尋人們跟隨、或稍候再試。",
"onboarding.follows.lead": "您的首頁時間軸是 Mastodon 的核心體驗。若您跟隨更多人的話,它將會變得更活躍有趣。這些個人檔案也許是個好起點,您可以隨時取消跟隨他們!", "onboarding.follows.lead": "您的首頁時間軸是 Mastodon 的核心體驗。若您跟隨更多人的話,它將會變得更活躍有趣。這些個人檔案也許是個好起點,您可以隨時取消跟隨他們!",
"onboarding.follows.title": "客製化您的首頁時間軸", "onboarding.follows.title": "客製化您的首頁時間軸",
@ -540,7 +540,7 @@
"regeneration_indicator.label": "載入中…", "regeneration_indicator.label": "載入中…",
"regeneration_indicator.sublabel": "您的首頁時間軸正在準備中!", "regeneration_indicator.sublabel": "您的首頁時間軸正在準備中!",
"relative_time.days": "{number} 天", "relative_time.days": "{number} 天",
"relative_time.full.days": "{number, plural, one {# 天} other {# 天}}前", "relative_time.full.days": "{number, plural, other {# 天}}前",
"relative_time.full.hours": "{number, plural, one {# 小時} other {# 小時}}前", "relative_time.full.hours": "{number, plural, one {# 小時} other {# 小時}}前",
"relative_time.full.just_now": "剛剛", "relative_time.full.just_now": "剛剛",
"relative_time.full.minutes": "{number, plural, one {# 分鐘} other {# 分鐘}}前", "relative_time.full.minutes": "{number, plural, one {# 分鐘} other {# 分鐘}}前",
@ -620,7 +620,7 @@
"search_results.see_all": "檢視全部", "search_results.see_all": "檢視全部",
"search_results.statuses": "嘟文", "search_results.statuses": "嘟文",
"search_results.title": "搜尋:{q}", "search_results.title": "搜尋:{q}",
"server_banner.about_active_users": "最近三十日內使用此伺服器的人 (月活躍使用者)", "server_banner.about_active_users": "最近三十日內使用此伺服器的人(月活躍使用者)",
"server_banner.active_users": "活躍使用者", "server_banner.active_users": "活躍使用者",
"server_banner.administered_by": "管理者:", "server_banner.administered_by": "管理者:",
"server_banner.introduction": "{domain} 是由 {mastodon} 提供之去中心化社群網路一部分。", "server_banner.introduction": "{domain} 是由 {mastodon} 提供之去中心化社群網路一部分。",
@ -687,7 +687,7 @@
"status.translated_from_with": "透過 {provider} 翻譯 {lang}", "status.translated_from_with": "透過 {provider} 翻譯 {lang}",
"status.uncached_media_warning": "無法預覽", "status.uncached_media_warning": "無法預覽",
"status.unmute_conversation": "解除此對話的靜音", "status.unmute_conversation": "解除此對話的靜音",
"status.unpin": "個人檔案頁面取消釘選", "status.unpin": "個人檔案頁面取消釘選",
"subscribed_languages.lead": "僅選定語言的嘟文才會出現於您的首頁上,並於變更後列出時間軸。選取「無」以接收所有語言的嘟文。", "subscribed_languages.lead": "僅選定語言的嘟文才會出現於您的首頁上,並於變更後列出時間軸。選取「無」以接收所有語言的嘟文。",
"subscribed_languages.save": "儲存變更", "subscribed_languages.save": "儲存變更",
"subscribed_languages.target": "變更 {target} 的訂閱語言", "subscribed_languages.target": "變更 {target} 的訂閱語言",

@ -4,7 +4,8 @@ class AttachmentBatch
# Maximum amount of objects you can delete in an S3 API call. It's # Maximum amount of objects you can delete in an S3 API call. It's
# important to remember that this does not correspond to the number # important to remember that this does not correspond to the number
# of records in the batch, since records can have multiple attachments # of records in the batch, since records can have multiple attachments
LIMIT = 1_000 LIMIT = ENV.fetch('S3_BATCH_DELETE_LIMIT', 1000).to_i
MAX_RETRY = ENV.fetch('S3_BATCH_DELETE_RETRY', 3).to_i
# Attributes generated and maintained by Paperclip (not all of them # Attributes generated and maintained by Paperclip (not all of them
# are always used on every class, however) # are always used on every class, however)
@ -95,6 +96,7 @@ class AttachmentBatch
# objects can be processed at once, so we have to potentially # objects can be processed at once, so we have to potentially
# separate them into multiple calls. # separate them into multiple calls.
retries = 0
keys.each_slice(LIMIT) do |keys_slice| keys.each_slice(LIMIT) do |keys_slice|
logger.debug { "Deleting #{keys_slice.size} objects" } logger.debug { "Deleting #{keys_slice.size} objects" }
@ -102,6 +104,17 @@ class AttachmentBatch
objects: keys_slice.map { |key| { key: key } }, objects: keys_slice.map { |key| { key: key } },
quiet: true, quiet: true,
}) })
rescue => e
retries += 1
if retries < MAX_RETRY
logger.debug "Retry #{retries}/#{MAX_RETRY} after #{e.message}"
sleep 2**retries
retry
else
logger.error "Batch deletion from S3 failed after #{e.message}"
raise e
end
end end
end end

@ -14,9 +14,8 @@ class Vacuum::PreviewCardsVacuum
private private
def vacuum_cached_images! def vacuum_cached_images!
preview_cards_past_retention_period.find_each do |preview_card| preview_cards_past_retention_period.find_in_batches do |preview_card|
preview_card.image.destroy AttachmentBatch.new(PreviewCard, preview_card).clear
preview_card.save
end end
end end

@ -9,9 +9,7 @@
# url :string # url :string
# #
class PreviewCardsStatus < ApplicationRecord class PreviewCardsStatus < ApplicationRecord
# Composite primary keys are not properly supported in Rails. However, self.primary_key = [:preview_card_id, :status_id]
# we shouldn't need this anyway...
self.primary_key = nil
belongs_to :preview_card belongs_to :preview_card
belongs_to :status belongs_to :status

@ -84,8 +84,7 @@ class Status < ApplicationRecord
has_and_belongs_to_many :tags has_and_belongs_to_many :tags
# Because of a composite primary key, the `dependent` option cannot be used on this association has_one :preview_cards_status, inverse_of: :status, dependent: :delete
has_one :preview_cards_status, inverse_of: :status # rubocop:disable Rails/HasManyOrHasOneDependent
has_one :notification, as: :activity, dependent: :destroy has_one :notification, as: :activity, dependent: :destroy
has_one :status_stat, inverse_of: :status, dependent: nil has_one :status_stat, inverse_of: :status, dependent: nil
@ -153,7 +152,6 @@ class Status < ApplicationRecord
# The `prepend: true` option below ensures this runs before # The `prepend: true` option below ensures this runs before
# the `dependent: destroy` callbacks remove relevant records # the `dependent: destroy` callbacks remove relevant records
before_destroy :unlink_from_conversations!, prepend: true before_destroy :unlink_from_conversations!, prepend: true
before_destroy :reset_preview_card!
cache_associated :application, cache_associated :application,
:media_attachments, :media_attachments,

@ -76,6 +76,15 @@ class FanOutOnWriteService < BaseService
LocalNotificationWorker.push_bulk(mentions) do |mention| LocalNotificationWorker.push_bulk(mentions) do |mention|
[mention.account_id, mention.id, 'Mention', 'mention'] [mention.account_id, mention.id, 'Mention', 'mention']
end end
next unless update?
# This may result in duplicate update payloads, but this ensures clients
# are aware of edits to posts only appearing in mention notifications
# (e.g. private mentions or mentions by people they do not follow)
PushUpdateWorker.push_bulk(mentions.filter { |mention| subscribed_to_streaming_api?(mention.account_id) }) do |mention|
[mention.account_id, @status.id, "timeline:#{mention.account_id}:notifications", { 'update' => true }]
end
end end
end end
@ -170,4 +179,8 @@ class FanOutOnWriteService < BaseService
def broadcastable? def broadcastable?
@status.public_visibility? && !@account.silenced? && (!@status.reblog? || Setting.show_reblogs_in_public_timelines) @status.public_visibility? && !@account.silenced? && (!@status.reblog? || Setting.show_reblogs_in_public_timelines)
end end
def subscribed_to_streaming_api?(account_id)
redis.exists?("subscribed:timeline:#{account_id}") || redis.exists?("subscribed:timeline:#{account_id}:notifications")
end
end end

@ -100,7 +100,7 @@ class FetchOEmbedService
end end
def validate(oembed) def validate(oembed)
oembed if oembed[:version].to_s == '1.0' && oembed[:type].present? oembed if oembed.present? && oembed[:version].to_s == '1.0' && oembed[:type].present?
end end
def html def html

@ -6,7 +6,9 @@ hr:
expires_at: Krajnji rok expires_at: Krajnji rok
options: Opcije options: Opcije
user: user:
agreement: Ugovor o uslugama
email: E-mail adresa email: E-mail adresa
locale: Lokalitet
password: Lozinka password: Lozinka
user/account: user/account:
username: Korisničko ime username: Korisničko ime
@ -18,3 +20,16 @@ hr:
attributes: attributes:
username: username:
invalid: mora sadržavati samo slova, brojeve i _ invalid: mora sadržavati samo slova, brojeve i _
reserved: je rezervisano
admin/webhook:
attributes:
url:
invalid: nije validan URL
doorkeeper/application:
attributes:
website:
invalid: nije validan URL
import:
attributes:
data:
malformed: je neispravan

@ -36,7 +36,7 @@ ru:
status: status:
attributes: attributes:
reblog: reblog:
taken: поста уже существует taken: пост уже существует
user: user:
attributes: attributes:
email: email:

@ -28,7 +28,7 @@ zh-TW:
doorkeeper/application: doorkeeper/application:
attributes: attributes:
website: website:
invalid: 不是有效的 URL invalid: 不是有效的網址
import: import:
attributes: attributes:
data: data:

@ -29,7 +29,7 @@ zh-TW:
edit: edit:
title: 編輯應用程式 title: 編輯應用程式
form: form:
error: 唉呦!請看看表單以排查錯誤 error: 糟糕!請檢查表單以排查錯誤
help: help:
native_redirect_uri: 請使用 %{native_redirect_uri} 作本站測試 native_redirect_uri: 請使用 %{native_redirect_uri} 作本站測試
redirect_uri: 每行輸入一個 URI redirect_uri: 每行輸入一個 URI

@ -534,6 +534,7 @@ en-GB:
total_reported: Reports about them total_reported: Reports about them
total_storage: Media attachments total_storage: Media attachments
totals_time_period_hint_html: The totals displayed below include data for all time. totals_time_period_hint_html: The totals displayed below include data for all time.
unknown_instance: There is currently no record of this domain on this server.
invites: invites:
deactivate_all: Deactivate all deactivate_all: Deactivate all
filter: filter:
@ -610,6 +611,7 @@ en-GB:
created_at: Reported created_at: Reported
delete_and_resolve: Delete posts delete_and_resolve: Delete posts
forwarded: Forwarded forwarded: Forwarded
forwarded_replies_explanation: This report is from a remote user and about remote content. It has been forwarded to you because the reported content is in reply to one of your users.
forwarded_to: Forwarded to %{domain} forwarded_to: Forwarded to %{domain}
mark_as_resolved: Mark as resolved mark_as_resolved: Mark as resolved
mark_as_sensitive: Mark as sensitive mark_as_sensitive: Mark as sensitive
@ -1038,6 +1040,14 @@ en-GB:
hint_html: Just one more thing! We need to confirm you're a human (this is so we can keep the spam out!). Solve the CAPTCHA below and click "Continue". hint_html: Just one more thing! We need to confirm you're a human (this is so we can keep the spam out!). Solve the CAPTCHA below and click "Continue".
title: Security check title: Security check
confirmations: confirmations:
awaiting_review: Your e-mail address is confirmed! The %{domain} staff is now reviewing your registration. You will receive an e-mail if they approve your account!
awaiting_review_title: Your registration is being reviewed
clicking_this_link: clicking this link
login_link: log in
proceed_to_login_html: You can now proceed to %{login_link}.
redirect_to_app_html: You should have been redirected to the <strong>%{app_name}</strong> app. If that did not happen, try %{clicking_this_link} or manually return to the app.
registration_complete: Your registration on %{domain} is now complete!
welcome_title: Welcome, %{name}!
wrong_email_hint: If that e-mail address is not correct, you can change it in account settings. wrong_email_hint: If that e-mail address is not correct, you can change it in account settings.
delete_account: Delete account delete_account: Delete account
delete_account_html: If you wish to delete your account, you can <a href="%{path}">proceed here</a>. You will be asked for confirmation. delete_account_html: If you wish to delete your account, you can <a href="%{path}">proceed here</a>. You will be asked for confirmation.
@ -1099,6 +1109,7 @@ en-GB:
functional: Your account is fully operational. functional: Your account is fully operational.
pending: Your application is pending review by our staff. This may take some time. You will receive an e-mail if your application is approved. pending: Your application is pending review by our staff. This may take some time. You will receive an e-mail if your application is approved.
redirecting_to: Your account is inactive because it is currently redirecting to %{acct}. redirecting_to: Your account is inactive because it is currently redirecting to %{acct}.
self_destruct: As %{domain} is closing down, you will only get limited access to your account.
view_strikes: View past strikes against your account view_strikes: View past strikes against your account
too_fast: Form submitted too fast, try again. too_fast: Form submitted too fast, try again.
use_security_key: Use security key use_security_key: Use security key
@ -1356,6 +1367,7 @@ en-GB:
'86400': 1 day '86400': 1 day
expires_in_prompt: Never expires_in_prompt: Never
generate: Generate invite link generate: Generate invite link
invalid: This invite is not valid
invited_by: 'You were invited by:' invited_by: 'You were invited by:'
max_uses: max_uses:
one: 1 use one: 1 use
@ -1568,6 +1580,9 @@ en-GB:
over_daily_limit: You have exceeded the limit of %{limit} scheduled posts for today over_daily_limit: You have exceeded the limit of %{limit} scheduled posts for today
over_total_limit: You have exceeded the limit of %{limit} scheduled posts over_total_limit: You have exceeded the limit of %{limit} scheduled posts
too_soon: The scheduled date must be in the future too_soon: The scheduled date must be in the future
self_destruct:
lead_html: Unfortunately, <strong>%{domain}</strong> is permanently closing down. If you had an account there, you will not be able to continue using it, but you can still request a backup of your data.
title: This server is closing down
sessions: sessions:
activity: Last activity activity: Last activity
browser: Browser browser: Browser
@ -1736,6 +1751,7 @@ en-GB:
default: "%b %d, %Y, %H:%M" default: "%b %d, %Y, %H:%M"
month: "%b %Y" month: "%b %Y"
time: "%H:%M" time: "%H:%M"
with_time_zone: "%b %d, %Y, %H:%M %Z"
translation: translation:
errors: errors:
quota_exceeded: The server-wide usage quota for the translation service has been exceeded. quota_exceeded: The server-wide usage quota for the translation service has been exceeded.

@ -1324,6 +1324,7 @@ my:
'86400': ၁ ရက် '86400': ၁ ရက်
expires_in_prompt: ဘယ်တော့မှ expires_in_prompt: ဘယ်တော့မှ
generate: ဖိတ်ကြားချက်လင့်ခ် ဖန်တီးပါ generate: ဖိတ်ကြားချက်လင့်ခ် ဖန်တီးပါ
invalid: ဤဖိတ်ကြားချက်မှာ မမှန်ကန်ပါ
invited_by: သင့်ကို ဖိတ်ခေါ်ထားသည် - invited_by: သင့်ကို ဖိတ်ခေါ်ထားသည် -
max_uses: max_uses:
other: "%{count} အသုံးပြုမှုများ" other: "%{count} အသုံးပြုမှုများ"

@ -16,7 +16,7 @@ zh-TW:
acct: 指定要移動至的帳號的「使用者名稱@網域名稱」 acct: 指定要移動至的帳號的「使用者名稱@網域名稱」
account_warning_preset: account_warning_preset:
text: 您可使用嘟文語法,例如網址、「#」標籤與提及功能 text: 您可使用嘟文語法,例如網址、「#」標籤與提及功能
title: 可選。不會向收件者顯示 title: 可選。不會向收件者顯示
admin_account_action: admin_account_action:
include_statuses: 使用者可看到導致檢舉或警告的嘟文 include_statuses: 使用者可看到導致檢舉或警告的嘟文
send_email_notification: 使用者將收到帳號發生之事情的解釋 send_email_notification: 使用者將收到帳號發生之事情的解釋

@ -656,6 +656,10 @@ sk:
rules_check: rules_check:
action: Spravuj serverové pravidlá action: Spravuj serverové pravidlá
message_html: Neurčil/a si žiadne serverové pravidlá. message_html: Neurčil/a si žiadne serverové pravidlá.
software_version_critical_check:
action: Pozri dostupné aktualizácie
software_version_patch_check:
action: Pozri dostupné aktualizácie
upload_check_privacy_error: upload_check_privacy_error:
action: Pozri tu pre viac informácií action: Pozri tu pre viac informácií
upload_check_privacy_error_object_storage: upload_check_privacy_error_object_storage:

@ -467,7 +467,7 @@ zh-TW:
other: 錯誤嘗試於 %{count} 天。 other: 錯誤嘗試於 %{count} 天。
no_failures_recorded: 報告中沒有錯誤。 no_failures_recorded: 報告中沒有錯誤。
title: 可用狀態 title: 可用狀態
warning: 上一次嘗試連線至伺服器失敗 warning: 上一次嘗試連線至伺服器失敗
back_to_all: 所有 back_to_all: 所有
back_to_limited: 受限制的 back_to_limited: 受限制的
back_to_warning: 警告 back_to_warning: 警告
@ -876,7 +876,7 @@ zh-TW:
publishers: publishers:
no_publisher_selected: 因未選取任何發行者,所以什麼事都沒發生 no_publisher_selected: 因未選取任何發行者,所以什麼事都沒發生
shared_by_over_week: shared_by_over_week:
other: 上週被 %{count} 使用者分享 other: 上週被 %{count} 使用者分享
title: 熱門連結 title: 熱門連結
usage_comparison: 於今日被 %{today} 人分享,相較於昨日 %{yesterday} 人 usage_comparison: 於今日被 %{today} 人分享,相較於昨日 %{yesterday} 人
not_allowed_to_trend: 不允許登上熱門 not_allowed_to_trend: 不允許登上熱門
@ -1273,7 +1273,7 @@ zh-TW:
other: 選取 %{count} 個符合您搜尋的項目。 other: 選取 %{count} 個符合您搜尋的項目。
today: 今天 today: 今天
validation_errors: validation_errors:
other: 恩...似乎不太對勁耶?請檢查以下 %{count} 項錯誤 other: 恩...似乎發生了點錯誤?請檢查以下 %{count} 項錯誤
imports: imports:
errors: errors:
empty: 空的 CSV 檔案 empty: 空的 CSV 檔案
@ -1796,7 +1796,7 @@ zh-TW:
welcome: welcome:
edit_profile_action: 設定個人檔案 edit_profile_action: 設定個人檔案
edit_profile_step: 您可以設定您的個人檔案,包括上傳大頭貼、變更顯示名稱等等。您也可以選擇於新的跟隨者跟隨前,先對他們進行審核。 edit_profile_step: 您可以設定您的個人檔案,包括上傳大頭貼、變更顯示名稱等等。您也可以選擇於新的跟隨者跟隨前,先對他們進行審核。
explanation: 下面是幾個小幫助,希望它們能幫到您 explanation: 以下是幾個小技巧,希望它們能幫到您
final_action: 開始嘟嘟 final_action: 開始嘟嘟
final_step: '開始嘟嘟吧!即使您現在沒有跟隨者,其他人仍然能於本站時間軸、主題標籤等地方,看到您的公開嘟文。試著用 #introductions 這個主題標籤介紹一下自己吧。' final_step: '開始嘟嘟吧!即使您現在沒有跟隨者,其他人仍然能於本站時間軸、主題標籤等地方,看到您的公開嘟文。試著用 #introductions 這個主題標籤介紹一下自己吧。'
full_handle: 您的完整帳號名稱 full_handle: 您的完整帳號名稱
@ -1805,7 +1805,7 @@ zh-TW:
title: "%{name} 誠摯歡迎您的加入!" title: "%{name} 誠摯歡迎您的加入!"
users: users:
follow_limit_reached: 您無法跟隨多於 %{limit} 個人 follow_limit_reached: 您無法跟隨多於 %{limit} 個人
go_to_sso_account_settings: 前往您的身分提供商 (identity provider) 之帳號設定 go_to_sso_account_settings: 前往您的身分識別提供者IdP之帳號設定
invalid_otp_token: 兩階段認證碼不正確 invalid_otp_token: 兩階段認證碼不正確
otp_lost_help_html: 如果您無法存取這兩者,您可以透過 %{email} 與我們聯繫 otp_lost_help_html: 如果您無法存取這兩者,您可以透過 %{email} 與我們聯繫
seamless_external_login: 由於您是由外部系統登入,所以不能設定密碼與電子郵件。 seamless_external_login: 由於您是由外部系統登入,所以不能設定密碼與電子郵件。

@ -97,6 +97,8 @@ module Mastodon::CLI
say("Removed #{custom_emojis_count} custom emojis#{dry_run_mode_suffix}", :green) say("Removed #{custom_emojis_count} custom emojis#{dry_run_mode_suffix}", :green)
end end
CRAWL_SLEEP_TIME = 20
option :concurrency, type: :numeric, default: 50, aliases: [:c] option :concurrency, type: :numeric, default: 50, aliases: [:c]
option :format, type: :string, default: 'summary', aliases: [:f] option :format, type: :string, default: 'summary', aliases: [:f]
option :exclude_suspended, type: :boolean, default: false, aliases: [:x] option :exclude_suspended, type: :boolean, default: false, aliases: [:x]
@ -168,8 +170,8 @@ module Mastodon::CLI
pool.post(domain, &work_unit) pool.post(domain, &work_unit)
end end
sleep 20 sleep CRAWL_SLEEP_TIME
sleep 20 until pool.queue_length.zero? sleep CRAWL_SLEEP_TIME until pool.queue_length.zero?
pool.shutdown pool.shutdown
pool.wait_for_termination(20) pool.wait_for_termination(20)

@ -185,15 +185,15 @@ module Mastodon::CLI
end end
def schema_has_instances_view? def schema_has_instances_view?
ActiveRecord::Migrator.current_version >= 2020_12_06_004238 migrator_version >= 2020_12_06_004238
end end
def verify_schema_version! def verify_schema_version!
if ActiveRecord::Migrator.current_version < MIN_SUPPORTED_VERSION if migrator_version < MIN_SUPPORTED_VERSION
say 'Your version of the database schema is too old and is not supported by this script.', :red say 'Your version of the database schema is too old and is not supported by this script.', :red
say 'Please update to at least Mastodon 3.0.0 before running this script.', :red say 'Please update to at least Mastodon 3.0.0 before running this script.', :red
exit(1) exit(1)
elsif ActiveRecord::Migrator.current_version > MAX_SUPPORTED_VERSION elsif migrator_version > MAX_SUPPORTED_VERSION
say 'Your version of the database schema is more recent than this script, this may cause unexpected errors.', :yellow say 'Your version of the database schema is more recent than this script, this may cause unexpected errors.', :yellow
exit(1) unless yes?('Continue anyway? (Yes/No)') exit(1) unless yes?('Continue anyway? (Yes/No)')
end end
@ -228,7 +228,7 @@ module Mastodon::CLI
end end
say 'Restoring index_accounts_on_username_and_domain_lower…' say 'Restoring index_accounts_on_username_and_domain_lower…'
if ActiveRecord::Migrator.current_version < 2020_06_20_164023 if migrator_version < 2020_06_20_164023
ActiveRecord::Base.connection.add_index :accounts, 'lower (username), lower(domain)', name: 'index_accounts_on_username_and_domain_lower', unique: true ActiveRecord::Base.connection.add_index :accounts, 'lower (username), lower(domain)', name: 'index_accounts_on_username_and_domain_lower', unique: true
else else
ActiveRecord::Base.connection.add_index :accounts, "lower (username), COALESCE(lower(domain), '')", name: 'index_accounts_on_username_and_domain_lower', unique: true ActiveRecord::Base.connection.add_index :accounts, "lower (username), COALESCE(lower(domain), '')", name: 'index_accounts_on_username_and_domain_lower', unique: true
@ -238,7 +238,7 @@ module Mastodon::CLI
ActiveRecord::Base.connection.execute('REINDEX INDEX search_index;') ActiveRecord::Base.connection.execute('REINDEX INDEX search_index;')
ActiveRecord::Base.connection.execute('REINDEX INDEX index_accounts_on_uri;') ActiveRecord::Base.connection.execute('REINDEX INDEX index_accounts_on_uri;')
ActiveRecord::Base.connection.execute('REINDEX INDEX index_accounts_on_url;') ActiveRecord::Base.connection.execute('REINDEX INDEX index_accounts_on_url;')
ActiveRecord::Base.connection.execute('REINDEX INDEX index_accounts_on_domain_and_id;') if ActiveRecord::Migrator.current_version >= 2023_05_24_190515 ActiveRecord::Base.connection.execute('REINDEX INDEX index_accounts_on_domain_and_id;') if migrator_version >= 2023_05_24_190515
end end
def deduplicate_users! def deduplicate_users!
@ -269,15 +269,15 @@ module Mastodon::CLI
say 'Restoring users indexes…' say 'Restoring users indexes…'
ActiveRecord::Base.connection.add_index :users, ['confirmation_token'], name: 'index_users_on_confirmation_token', unique: true ActiveRecord::Base.connection.add_index :users, ['confirmation_token'], name: 'index_users_on_confirmation_token', unique: true
ActiveRecord::Base.connection.add_index :users, ['email'], name: 'index_users_on_email', unique: true ActiveRecord::Base.connection.add_index :users, ['email'], name: 'index_users_on_email', unique: true
ActiveRecord::Base.connection.add_index :users, ['remember_token'], name: 'index_users_on_remember_token', unique: true if ActiveRecord::Migrator.current_version < 2022_01_18_183010 ActiveRecord::Base.connection.add_index :users, ['remember_token'], name: 'index_users_on_remember_token', unique: true if migrator_version < 2022_01_18_183010
if ActiveRecord::Migrator.current_version < 2022_03_10_060641 if migrator_version < 2022_03_10_060641
ActiveRecord::Base.connection.add_index :users, ['reset_password_token'], name: 'index_users_on_reset_password_token', unique: true ActiveRecord::Base.connection.add_index :users, ['reset_password_token'], name: 'index_users_on_reset_password_token', unique: true
else else
ActiveRecord::Base.connection.add_index :users, ['reset_password_token'], name: 'index_users_on_reset_password_token', unique: true, where: 'reset_password_token IS NOT NULL', opclass: :text_pattern_ops ActiveRecord::Base.connection.add_index :users, ['reset_password_token'], name: 'index_users_on_reset_password_token', unique: true, where: 'reset_password_token IS NOT NULL', opclass: :text_pattern_ops
end end
ActiveRecord::Base.connection.execute('REINDEX INDEX index_users_on_unconfirmed_email;') if ActiveRecord::Migrator.current_version >= 2023_07_02_151753 ActiveRecord::Base.connection.execute('REINDEX INDEX index_users_on_unconfirmed_email;') if migrator_version >= 2023_07_02_151753
end end
def deduplicate_users_process_confirmation_token def deduplicate_users_process_confirmation_token
@ -292,7 +292,7 @@ module Mastodon::CLI
end end
def deduplicate_users_process_remember_token def deduplicate_users_process_remember_token
if ActiveRecord::Migrator.current_version < 2022_01_18_183010 if migrator_version < 2022_01_18_183010
ActiveRecord::Base.connection.select_all("SELECT string_agg(id::text, ',') AS ids FROM users WHERE remember_token IS NOT NULL GROUP BY remember_token HAVING count(*) > 1").each do |row| ActiveRecord::Base.connection.select_all("SELECT string_agg(id::text, ',') AS ids FROM users WHERE remember_token IS NOT NULL GROUP BY remember_token HAVING count(*) > 1").each do |row|
users = User.where(id: row['ids'].split(',')).sort_by(&:updated_at).reverse.drop(1) users = User.where(id: row['ids'].split(',')).sort_by(&:updated_at).reverse.drop(1)
say "Unsetting remember token for those accounts: #{users.map { |user| user.account.acct }.join(', ')}", :yellow say "Unsetting remember token for those accounts: #{users.map { |user| user.account.acct }.join(', ')}", :yellow
@ -346,7 +346,7 @@ module Mastodon::CLI
remove_index_if_exists!(:announcement_reactions, 'index_announcement_reactions_on_account_id_and_announcement_id') remove_index_if_exists!(:announcement_reactions, 'index_announcement_reactions_on_account_id_and_announcement_id')
say 'Removing duplicate account identity proofs…' say 'Removing duplicate announcement reactions…'
ActiveRecord::Base.connection.select_all("SELECT string_agg(id::text, ',') AS ids FROM announcement_reactions GROUP BY account_id, announcement_id, name HAVING count(*) > 1").each do |row| ActiveRecord::Base.connection.select_all("SELECT string_agg(id::text, ',') AS ids FROM announcement_reactions GROUP BY account_id, announcement_id, name HAVING count(*) > 1").each do |row|
AnnouncementReaction.where(id: row['ids'].split(',')).sort_by(&:id).reverse.drop(1).each(&:destroy) AnnouncementReaction.where(id: row['ids'].split(',')).sort_by(&:id).reverse.drop(1).each(&:destroy)
end end
@ -371,7 +371,7 @@ module Mastodon::CLI
end end
say 'Restoring conversations indexes…' say 'Restoring conversations indexes…'
if ActiveRecord::Migrator.current_version < 2022_03_07_083603 if migrator_version < 2022_03_07_083603
ActiveRecord::Base.connection.add_index :conversations, ['uri'], name: 'index_conversations_on_uri', unique: true ActiveRecord::Base.connection.add_index :conversations, ['uri'], name: 'index_conversations_on_uri', unique: true
else else
ActiveRecord::Base.connection.add_index :conversations, ['uri'], name: 'index_conversations_on_uri', unique: true, where: 'uri IS NOT NULL', opclass: :text_pattern_ops ActiveRecord::Base.connection.add_index :conversations, ['uri'], name: 'index_conversations_on_uri', unique: true, where: 'uri IS NOT NULL', opclass: :text_pattern_ops
@ -431,7 +431,7 @@ module Mastodon::CLI
def deduplicate_domain_blocks! def deduplicate_domain_blocks!
remove_index_if_exists!(:domain_blocks, 'index_domain_blocks_on_domain') remove_index_if_exists!(:domain_blocks, 'index_domain_blocks_on_domain')
say 'Deduplicating domain_allows…' say 'Deduplicating domain_blocks…'
ActiveRecord::Base.connection.select_all("SELECT string_agg(id::text, ',') AS ids FROM domain_blocks GROUP BY domain HAVING count(*) > 1").each do |row| ActiveRecord::Base.connection.select_all("SELECT string_agg(id::text, ',') AS ids FROM domain_blocks GROUP BY domain HAVING count(*) > 1").each do |row|
domain_blocks = DomainBlock.where(id: row['ids'].split(',')).by_severity.reverse.to_a domain_blocks = DomainBlock.where(id: row['ids'].split(',')).by_severity.reverse.to_a
@ -462,7 +462,7 @@ module Mastodon::CLI
UnavailableDomain.where(id: row['ids'].split(',')).sort_by(&:id).reverse.drop(1).each(&:destroy) UnavailableDomain.where(id: row['ids'].split(',')).sort_by(&:id).reverse.drop(1).each(&:destroy)
end end
say 'Restoring domain_allows indexes…' say 'Restoring unavailable_domains indexes…'
ActiveRecord::Base.connection.add_index :unavailable_domains, ['domain'], name: 'index_unavailable_domains_on_domain', unique: true ActiveRecord::Base.connection.add_index :unavailable_domains, ['domain'], name: 'index_unavailable_domains_on_domain', unique: true
end end
@ -488,7 +488,7 @@ module Mastodon::CLI
end end
say 'Restoring media_attachments indexes…' say 'Restoring media_attachments indexes…'
if ActiveRecord::Migrator.current_version < 2022_03_10_060626 if migrator_version < 2022_03_10_060626
ActiveRecord::Base.connection.add_index :media_attachments, ['shortcode'], name: 'index_media_attachments_on_shortcode', unique: true ActiveRecord::Base.connection.add_index :media_attachments, ['shortcode'], name: 'index_media_attachments_on_shortcode', unique: true
else else
ActiveRecord::Base.connection.add_index :media_attachments, ['shortcode'], name: 'index_media_attachments_on_shortcode', unique: true, where: 'shortcode IS NOT NULL', opclass: :text_pattern_ops ActiveRecord::Base.connection.add_index :media_attachments, ['shortcode'], name: 'index_media_attachments_on_shortcode', unique: true, where: 'shortcode IS NOT NULL', opclass: :text_pattern_ops
@ -521,7 +521,7 @@ module Mastodon::CLI
end end
say 'Restoring statuses indexes…' say 'Restoring statuses indexes…'
if ActiveRecord::Migrator.current_version < 2022_03_10_060706 if migrator_version < 2022_03_10_060706
ActiveRecord::Base.connection.add_index :statuses, ['uri'], name: 'index_statuses_on_uri', unique: true ActiveRecord::Base.connection.add_index :statuses, ['uri'], name: 'index_statuses_on_uri', unique: true
else else
ActiveRecord::Base.connection.add_index :statuses, ['uri'], name: 'index_statuses_on_uri', unique: true, where: 'uri IS NOT NULL', opclass: :text_pattern_ops ActiveRecord::Base.connection.add_index :statuses, ['uri'], name: 'index_statuses_on_uri', unique: true, where: 'uri IS NOT NULL', opclass: :text_pattern_ops
@ -543,7 +543,7 @@ module Mastodon::CLI
end end
say 'Restoring tags indexes…' say 'Restoring tags indexes…'
if ActiveRecord::Migrator.current_version < 2021_04_21_121431 if migrator_version < 2021_04_21_121431
ActiveRecord::Base.connection.add_index :tags, 'lower((name)::text)', name: 'index_tags_on_name_lower', unique: true ActiveRecord::Base.connection.add_index :tags, 'lower((name)::text)', name: 'index_tags_on_name_lower', unique: true
else else
ActiveRecord::Base.connection.execute 'CREATE UNIQUE INDEX CONCURRENTLY index_tags_on_name_lower_btree ON tags (lower(name) text_pattern_ops)' ActiveRecord::Base.connection.execute 'CREATE UNIQUE INDEX CONCURRENTLY index_tags_on_name_lower_btree ON tags (lower(name) text_pattern_ops)'
@ -707,12 +707,16 @@ module Mastodon::CLI
end end
end end
def migrator_version
ActiveRecord::Migrator.current_version
end
def find_duplicate_accounts def find_duplicate_accounts
ActiveRecord::Base.connection.select_all("SELECT string_agg(id::text, ',') AS ids FROM accounts GROUP BY lower(username), COALESCE(lower(domain), '') HAVING count(*) > 1") ActiveRecord::Base.connection.select_all("SELECT string_agg(id::text, ',') AS ids FROM accounts GROUP BY lower(username), COALESCE(lower(domain), '') HAVING count(*) > 1")
end end
def remove_index_if_exists!(table, name) def remove_index_if_exists!(table, name)
ActiveRecord::Base.connection.remove_index(table, name: name) ActiveRecord::Base.connection.remove_index(table, name: name) if ActiveRecord::Base.connection.index_name_exists?(table, name)
rescue ArgumentError, ActiveRecord::StatementInvalid rescue ArgumentError, ActiveRecord::StatementInvalid
nil nil
end end

@ -205,7 +205,7 @@
"prettier": "^3.0.0", "prettier": "^3.0.0",
"react-test-renderer": "^18.2.0", "react-test-renderer": "^18.2.0",
"stylelint": "^15.10.1", "stylelint": "^15.10.1",
"stylelint-config-standard-scss": "^11.0.0", "stylelint-config-standard-scss": "^12.0.0",
"typescript": "^5.0.4", "typescript": "^5.0.4",
"webpack-dev-server": "^3.11.3", "webpack-dev-server": "^3.11.3",
"yargs": "^17.7.2" "yargs": "^17.7.2"

@ -1,23 +0,0 @@
# frozen_string_literal: true
require 'rails_helper'
describe Api::V1::Accounts::IdentityProofsController do
render_views
let(:user) { Fabricate(:user) }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read:accounts') }
let(:account) { Fabricate(:account) }
before do
allow(controller).to receive(:doorkeeper_token) { token }
end
describe 'GET #index' do
it 'returns http success' do
get :index, params: { account_id: account.id, limit: 2 }
expect(response).to have_http_status(200)
end
end
end

@ -1,25 +0,0 @@
# frozen_string_literal: true
require 'rails_helper'
describe Api::V1::Accounts::ListsController do
render_views
let(:user) { Fabricate(:user) }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read:lists') }
let(:account) { Fabricate(:account) }
let(:list) { Fabricate(:list, account: user.account) }
before do
allow(controller).to receive(:doorkeeper_token) { token }
user.account.follow!(account)
list.accounts << account
end
describe 'GET #index' do
it 'returns http success' do
get :index, params: { account_id: account.id }
expect(response).to have_http_status(200)
end
end
end

@ -1,23 +0,0 @@
# frozen_string_literal: true
require 'rails_helper'
describe Api::V1::Accounts::LookupController do
render_views
let(:user) { Fabricate(:user) }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read:accounts') }
let(:account) { Fabricate(:account) }
before do
allow(controller).to receive(:doorkeeper_token) { token }
end
describe 'GET #show' do
it 'returns http success' do
get :show, params: { account_id: account.id, acct: account.acct }
expect(response).to have_http_status(200)
end
end
end

@ -1,40 +0,0 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe Api::V1::Accounts::PinsController do
let(:john) { Fabricate(:user) }
let(:kevin) { Fabricate(:user) }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: john.id, scopes: 'write:accounts') }
before do
kevin.account.followers << john.account
allow(controller).to receive(:doorkeeper_token) { token }
end
describe 'POST #create' do
subject { post :create, params: { account_id: kevin.account.id } }
it 'creates account_pin', :aggregate_failures do
expect do
subject
end.to change { AccountPin.where(account: john.account, target_account: kevin.account).count }.by(1)
expect(response).to have_http_status(200)
end
end
describe 'DELETE #destroy' do
subject { delete :destroy, params: { account_id: kevin.account.id } }
before do
Fabricate(:account_pin, account: john.account, target_account: kevin.account)
end
it 'destroys account_pin', :aggregate_failures do
expect do
subject
end.to change { AccountPin.where(account: john.account, target_account: kevin.account).count }.by(-1)
expect(response).to have_http_status(200)
end
end
end

@ -1,22 +0,0 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe Api::V1::Accounts::SearchController do
render_views
let(:user) { Fabricate(:user) }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read:accounts') }
before do
allow(controller).to receive(:doorkeeper_token) { token }
end
describe 'GET #show' do
it 'returns http success' do
get :show, params: { q: 'query' }
expect(response).to have_http_status(200)
end
end
end

@ -1,23 +0,0 @@
# frozen_string_literal: true
require 'rails_helper'
describe Api::V1::FeaturedTags::SuggestionsController do
render_views
let(:user) { Fabricate(:user) }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read:accounts') }
let(:account) { Fabricate(:account) }
before do
allow(controller).to receive(:doorkeeper_token) { token }
end
describe 'GET #index' do
it 'returns http success' do
get :index, params: { account_id: account.id, limit: 2 }
expect(response).to have_http_status(200)
end
end
end

@ -1,22 +0,0 @@
# frozen_string_literal: true
require 'rails_helper'
describe Api::V2::SuggestionsController do
render_views
let(:user) { Fabricate(:user) }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read') }
before do
allow(controller).to receive(:doorkeeper_token) { token }
end
describe 'GET #index' do
it 'returns http success' do
get :index
expect(response).to have_http_status(200)
end
end
end

File diff suppressed because it is too large Load Diff

@ -4,22 +4,29 @@ require 'rails_helper'
require 'mastodon/cli/cache' require 'mastodon/cli/cache'
describe Mastodon::CLI::Cache do describe Mastodon::CLI::Cache do
subject { cli.invoke(action, arguments, options) }
let(:cli) { described_class.new } let(:cli) { described_class.new }
let(:arguments) { [] }
let(:options) { {} }
it_behaves_like 'CLI Command' it_behaves_like 'CLI Command'
describe '#clear' do describe '#clear' do
let(:action) { :clear }
before { allow(Rails.cache).to receive(:clear) } before { allow(Rails.cache).to receive(:clear) }
it 'clears the Rails cache' do it 'clears the Rails cache' do
expect { cli.invoke(:clear) }.to output( expect { subject }
a_string_including('OK') .to output_results('OK')
).to_stdout
expect(Rails.cache).to have_received(:clear) expect(Rails.cache).to have_received(:clear)
end end
end end
describe '#recount' do describe '#recount' do
let(:action) { :recount }
context 'with the `accounts` argument' do context 'with the `accounts` argument' do
let(:arguments) { ['accounts'] } let(:arguments) { ['accounts'] }
let(:account_stat) { Fabricate(:account_stat) } let(:account_stat) { Fabricate(:account_stat) }
@ -29,9 +36,8 @@ describe Mastodon::CLI::Cache do
end end
it 're-calculates account records in the cache' do it 're-calculates account records in the cache' do
expect { cli.invoke(:recount, arguments) }.to output( expect { subject }
a_string_including('OK') .to output_results('OK')
).to_stdout
expect(account_stat.reload.statuses_count).to be_zero expect(account_stat.reload.statuses_count).to be_zero
end end
@ -46,9 +52,8 @@ describe Mastodon::CLI::Cache do
end end
it 're-calculates account records in the cache' do it 're-calculates account records in the cache' do
expect { cli.invoke(:recount, arguments) }.to output( expect { subject }
a_string_including('OK') .to output_results('OK')
).to_stdout
expect(status_stat.reload.replies_count).to be_zero expect(status_stat.reload.replies_count).to be_zero
end end
@ -58,9 +63,9 @@ describe Mastodon::CLI::Cache do
let(:arguments) { ['other-type'] } let(:arguments) { ['other-type'] }
it 'Exits with an error message' do it 'Exits with an error message' do
expect { cli.invoke(:recount, arguments) }.to output( expect { subject }
a_string_including('Unknown') .to output_results('Unknown')
).to_stdout.and raise_error(SystemExit) .and raise_error(SystemExit)
end end
end end
end end

@ -4,42 +4,45 @@ require 'rails_helper'
require 'mastodon/cli/canonical_email_blocks' require 'mastodon/cli/canonical_email_blocks'
describe Mastodon::CLI::CanonicalEmailBlocks do describe Mastodon::CLI::CanonicalEmailBlocks do
subject { cli.invoke(action, arguments, options) }
let(:cli) { described_class.new } let(:cli) { described_class.new }
let(:arguments) { [] }
let(:options) { {} }
it_behaves_like 'CLI Command' it_behaves_like 'CLI Command'
describe '#find' do describe '#find' do
let(:action) { :find }
let(:arguments) { ['user@example.com'] } let(:arguments) { ['user@example.com'] }
context 'when a block is present' do context 'when a block is present' do
before { Fabricate(:canonical_email_block, email: 'user@example.com') } before { Fabricate(:canonical_email_block, email: 'user@example.com') }
it 'announces the presence of the block' do it 'announces the presence of the block' do
expect { cli.invoke(:find, arguments) }.to output( expect { subject }
a_string_including('user@example.com is blocked') .to output_results('user@example.com is blocked')
).to_stdout
end end
end end
context 'when a block is not present' do context 'when a block is not present' do
it 'announces the absence of the block' do it 'announces the absence of the block' do
expect { cli.invoke(:find, arguments) }.to output( expect { subject }
a_string_including('user@example.com is not blocked') .to output_results('user@example.com is not blocked')
).to_stdout
end end
end end
end end
describe '#remove' do describe '#remove' do
let(:action) { :remove }
let(:arguments) { ['user@example.com'] } let(:arguments) { ['user@example.com'] }
context 'when a block is present' do context 'when a block is present' do
before { Fabricate(:canonical_email_block, email: 'user@example.com') } before { Fabricate(:canonical_email_block, email: 'user@example.com') }
it 'removes the block' do it 'removes the block' do
expect { cli.invoke(:remove, arguments) }.to output( expect { subject }
a_string_including('Unblocked user@example.com') .to output_results('Unblocked user@example.com')
).to_stdout
expect(CanonicalEmailBlock.matching_email('user@example.com')).to be_empty expect(CanonicalEmailBlock.matching_email('user@example.com')).to be_empty
end end
@ -47,9 +50,8 @@ describe Mastodon::CLI::CanonicalEmailBlocks do
context 'when a block is not present' do context 'when a block is not present' do
it 'announces the absence of the block' do it 'announces the absence of the block' do
expect { cli.invoke(:remove, arguments) }.to output( expect { subject }
a_string_including('user@example.com is not blocked') .to output_results('user@example.com is not blocked')
).to_stdout
end end
end end
end end

@ -4,22 +4,75 @@ require 'rails_helper'
require 'mastodon/cli/domains' require 'mastodon/cli/domains'
describe Mastodon::CLI::Domains do describe Mastodon::CLI::Domains do
subject { cli.invoke(action, arguments, options) }
let(:cli) { described_class.new } let(:cli) { described_class.new }
let(:arguments) { [] }
let(:options) { {} }
it_behaves_like 'CLI Command' it_behaves_like 'CLI Command'
describe '#purge' do describe '#purge' do
let(:action) { :purge }
context 'with accounts from the domain' do context 'with accounts from the domain' do
let(:options) { {} }
let(:domain) { 'host.example' } let(:domain) { 'host.example' }
let!(:account) { Fabricate(:account, domain: domain) } let!(:account) { Fabricate(:account, domain: domain) }
let(:arguments) { [domain] }
it 'removes the account' do it 'removes the account' do
expect { cli.invoke(:purge, [domain], options) }.to output( expect { subject }
a_string_including('Removed 1 accounts') .to output_results('Removed 1 accounts')
).to_stdout
expect { account.reload }.to raise_error(ActiveRecord::RecordNotFound) expect { account.reload }.to raise_error(ActiveRecord::RecordNotFound)
end end
end end
end end
describe '#crawl' do
let(:action) { :crawl }
context 'with accounts from the domain' do
let(:domain) { 'host.example' }
before do
Fabricate(:account, domain: domain)
stub_request(:get, 'https://host.example/api/v1/instance').to_return(status: 200, body: {}.to_json)
stub_request(:get, 'https://host.example/api/v1/instance/peers').to_return(status: 200, body: {}.to_json)
stub_request(:get, 'https://host.example/api/v1/instance/activity').to_return(status: 200, body: {}.to_json)
stub_const('Mastodon::CLI::Domains::CRAWL_SLEEP_TIME', 0)
end
context 'with --format of summary' do
let(:options) { { format: 'summary' } }
it 'crawls the domains and summarizes results' do
expect { subject }
.to output_results('Visited 1 domains, 0 failed')
end
end
context 'with --format of domains' do
let(:options) { { format: 'domains' } }
it 'crawls the domains and summarizes results' do
expect { subject }
.to output_results(domain)
end
end
context 'with --format of json' do
let(:options) { { format: 'json' } }
it 'crawls the domains and summarizes results' do
expect { subject }
.to output_results(json_summary)
end
def json_summary
Oj.dump('host.example': { activity: {} })
end
end
end
end
end end

@ -4,96 +4,99 @@ require 'rails_helper'
require 'mastodon/cli/email_domain_blocks' require 'mastodon/cli/email_domain_blocks'
describe Mastodon::CLI::EmailDomainBlocks do describe Mastodon::CLI::EmailDomainBlocks do
subject { cli.invoke(action, arguments, options) }
let(:cli) { described_class.new } let(:cli) { described_class.new }
let(:arguments) { [] }
let(:options) { {} }
it_behaves_like 'CLI Command' it_behaves_like 'CLI Command'
describe '#list' do describe '#list' do
let(:action) { :list }
context 'with email domain block records' do context 'with email domain block records' do
let!(:parent_block) { Fabricate(:email_domain_block) } let!(:parent_block) { Fabricate(:email_domain_block) }
let!(:child_block) { Fabricate(:email_domain_block, parent: parent_block) } let!(:child_block) { Fabricate(:email_domain_block, parent: parent_block) }
let(:options) { {} }
it 'lists the blocks' do it 'lists the blocks' do
expect { cli.invoke(:list, [], options) }.to output( expect { subject }
a_string_including(parent_block.domain) .to output_results(
.and(a_string_including(child_block.domain)) parent_block.domain,
).to_stdout child_block.domain
)
end end
end end
end end
describe '#add' do describe '#add' do
context 'without any options' do let(:action) { :add }
let(:options) { {} }
context 'without any options' do
it 'warns about usage and exits' do it 'warns about usage and exits' do
expect { cli.invoke(:add, [], options) }.to output( expect { subject }
a_string_including('No domain(s) given') .to output_results('No domain(s) given')
).to_stdout.and raise_error(SystemExit) .and raise_error(SystemExit)
end end
end end
context 'when blocks exist' do context 'when blocks exist' do
let(:options) { {} } let(:options) { {} }
let(:domain) { 'host.example' } let(:domain) { 'host.example' }
let(:arguments) { [domain] }
before { Fabricate(:email_domain_block, domain: domain) } before { Fabricate(:email_domain_block, domain: domain) }
it 'does not add a new block' do it 'does not add a new block' do
expect { cli.invoke(:add, [domain], options) }.to output( expect { subject }
a_string_including('is already blocked') .to output_results('is already blocked')
).to_stdout
.and(not_change(EmailDomainBlock, :count)) .and(not_change(EmailDomainBlock, :count))
end end
end end
context 'when no blocks exist' do context 'when no blocks exist' do
let(:options) { {} }
let(:domain) { 'host.example' } let(:domain) { 'host.example' }
let(:arguments) { [domain] }
it 'adds a new block' do it 'adds a new block' do
expect { cli.invoke(:add, [domain], options) }.to output( expect { subject }
a_string_including('Added 1') .to output_results('Added 1')
).to_stdout
.and(change(EmailDomainBlock, :count).by(1)) .and(change(EmailDomainBlock, :count).by(1))
end end
end end
end end
describe '#remove' do describe '#remove' do
context 'without any options' do let(:action) { :remove }
let(:options) { {} }
context 'without any options' do
it 'warns about usage and exits' do it 'warns about usage and exits' do
expect { cli.invoke(:remove, [], options) }.to output( expect { subject }
a_string_including('No domain(s) given') .to output_results('No domain(s) given')
).to_stdout.and raise_error(SystemExit) .and raise_error(SystemExit)
end end
end end
context 'when blocks exist' do context 'when blocks exist' do
let(:options) { {} }
let(:domain) { 'host.example' } let(:domain) { 'host.example' }
let(:arguments) { [domain] }
before { Fabricate(:email_domain_block, domain: domain) } before { Fabricate(:email_domain_block, domain: domain) }
it 'removes the block' do it 'removes the block' do
expect { cli.invoke(:remove, [domain], options) }.to output( expect { subject }
a_string_including('Removed 1') .to output_results('Removed 1')
).to_stdout
.and(change(EmailDomainBlock, :count).by(-1)) .and(change(EmailDomainBlock, :count).by(-1))
end end
end end
context 'when no blocks exist' do context 'when no blocks exist' do
let(:options) { {} }
let(:domain) { 'host.example' } let(:domain) { 'host.example' }
let(:arguments) { [domain] }
it 'does not remove a block' do it 'does not remove a block' do
expect { cli.invoke(:remove, [domain], options) }.to output( expect { subject }
a_string_including('is not yet blocked') .to output_results('is not yet blocked')
).to_stdout
.and(not_change(EmailDomainBlock, :count)) .and(not_change(EmailDomainBlock, :count))
end end
end end

@ -4,10 +4,10 @@ require 'rails_helper'
require 'mastodon/cli/emoji' require 'mastodon/cli/emoji'
describe Mastodon::CLI::Emoji do describe Mastodon::CLI::Emoji do
subject { cli.invoke(action, args, options) } subject { cli.invoke(action, arguments, options) }
let(:cli) { described_class.new } let(:cli) { described_class.new }
let(:args) { [] } let(:arguments) { [] }
let(:options) { {} } let(:options) { {} }
it_behaves_like 'CLI Command' it_behaves_like 'CLI Command'
@ -29,7 +29,7 @@ describe Mastodon::CLI::Emoji do
context 'with existing custom emoji' do context 'with existing custom emoji' do
let(:import_path) { Rails.root.join('spec', 'fixtures', 'files', 'elite-assets.tar.gz') } let(:import_path) { Rails.root.join('spec', 'fixtures', 'files', 'elite-assets.tar.gz') }
let(:action) { :import } let(:action) { :import }
let(:args) { [import_path] } let(:arguments) { [import_path] }
it 'reports about imported emoji' do it 'reports about imported emoji' do
expect { subject } expect { subject }
@ -51,7 +51,7 @@ describe Mastodon::CLI::Emoji do
after { FileUtils.rm_rf(export_path.dirname) } after { FileUtils.rm_rf(export_path.dirname) }
let(:export_path) { Rails.root.join('tmp', 'cli-tests', 'export.tar.gz') } let(:export_path) { Rails.root.join('tmp', 'cli-tests', 'export.tar.gz') }
let(:args) { [export_path.dirname.to_s] } let(:arguments) { [export_path.dirname.to_s] }
let(:action) { :export } let(:action) { :export }
it 'reports about exported emoji' do it 'reports about exported emoji' do
@ -61,8 +61,4 @@ describe Mastodon::CLI::Emoji do
end end
end end
end end
def output_results(string)
output(a_string_including(string)).to_stdout
end
end end

@ -4,20 +4,25 @@ require 'rails_helper'
require 'mastodon/cli/feeds' require 'mastodon/cli/feeds'
describe Mastodon::CLI::Feeds do describe Mastodon::CLI::Feeds do
subject { cli.invoke(action, arguments, options) }
let(:cli) { described_class.new } let(:cli) { described_class.new }
let(:arguments) { [] }
let(:options) { {} }
it_behaves_like 'CLI Command' it_behaves_like 'CLI Command'
describe '#build' do describe '#build' do
let(:action) { :build }
before { Fabricate(:account) } before { Fabricate(:account) }
context 'with --all option' do context 'with --all option' do
let(:options) { { all: true } } let(:options) { { all: true } }
it 'regenerates feeds for all accounts' do it 'regenerates feeds for all accounts' do
expect { cli.invoke(:build, [], options) }.to output( expect { subject }
a_string_including('Regenerated feeds') .to output_results('Regenerated feeds')
).to_stdout
end end
end end
@ -27,9 +32,8 @@ describe Mastodon::CLI::Feeds do
let(:arguments) { ['alice'] } let(:arguments) { ['alice'] }
it 'regenerates feeds for the account' do it 'regenerates feeds for the account' do
expect { cli.invoke(:build, arguments) }.to output( expect { subject }
a_string_including('OK') .to output_results('OK')
).to_stdout
end end
end end
@ -37,22 +41,23 @@ describe Mastodon::CLI::Feeds do
let(:arguments) { ['invalid-username'] } let(:arguments) { ['invalid-username'] }
it 'displays an error and exits' do it 'displays an error and exits' do
expect { cli.invoke(:build, arguments) }.to output( expect { subject }
a_string_including('No such account') .to output_results('No such account')
).to_stdout.and raise_error(SystemExit) .and raise_error(SystemExit)
end end
end end
end end
describe '#clear' do describe '#clear' do
let(:action) { :clear }
before do before do
allow(redis).to receive(:del).with(key_namespace) allow(redis).to receive(:del).with(key_namespace)
end end
it 'clears the redis `feed:*` namespace' do it 'clears the redis `feed:*` namespace' do
expect { cli.invoke(:clear) }.to output( expect { subject }
a_string_including('OK') .to output_results('OK')
).to_stdout
expect(redis).to have_received(:del).with(key_namespace).once expect(redis).to have_received(:del).with(key_namespace).once
end end

@ -4,11 +4,16 @@ require 'rails_helper'
require 'mastodon/cli/ip_blocks' require 'mastodon/cli/ip_blocks'
describe Mastodon::CLI::IpBlocks do describe Mastodon::CLI::IpBlocks do
subject { cli.invoke(action, arguments, options) }
let(:cli) { described_class.new } let(:cli) { described_class.new }
let(:arguments) { [] }
let(:options) { {} }
it_behaves_like 'CLI Command' it_behaves_like 'CLI Command'
describe '#add' do describe '#add' do
let(:action) { :add }
let(:ip_list) do let(:ip_list) do
[ [
'192.0.2.1', '192.0.2.1',
@ -25,29 +30,28 @@ describe Mastodon::CLI::IpBlocks do
] ]
end end
let(:options) { { severity: 'no_access' } } let(:options) { { severity: 'no_access' } }
let(:arguments) { ip_list }
shared_examples 'ip address blocking' do shared_examples 'ip address blocking' do
it 'blocks all specified IP addresses' do def blocked_ip_addresses
cli.invoke(:add, ip_list, options) IpBlock.where(ip: ip_list).pluck(:ip)
blocked_ip_addresses = IpBlock.where(ip: ip_list).pluck(:ip)
expected_ip_addresses = ip_list.map { |ip| IPAddr.new(ip) }
expect(blocked_ip_addresses).to match_array(expected_ip_addresses)
end end
it 'sets the severity for all blocked IP addresses' do def expected_ip_addresses
cli.invoke(:add, ip_list, options) ip_list.map { |ip| IPAddr.new(ip) }
end
blocked_ips_severity = IpBlock.where(ip: ip_list).pluck(:severity).all?(options[:severity])
expect(blocked_ips_severity).to be(true) def blocked_ips_severity
IpBlock.where(ip: ip_list).pluck(:severity).all?(options[:severity])
end end
it 'displays a success message with a summary' do it 'blocks and sets severity for ip address and displays summary' do
expect { cli.invoke(:add, ip_list, options) }.to output( expect { subject }
a_string_including("Added #{ip_list.size}, skipped 0, failed 0") .to output_results("Added #{ip_list.size}, skipped 0, failed 0")
).to_stdout expect(blocked_ip_addresses)
.to match_array(expected_ip_addresses)
expect(blocked_ips_severity)
.to be(true)
end end
end end
@ -57,27 +61,23 @@ describe Mastodon::CLI::IpBlocks do
context 'when a specified IP address is already blocked' do context 'when a specified IP address is already blocked' do
let!(:blocked_ip) { IpBlock.create(ip: ip_list.last, severity: options[:severity]) } let!(:blocked_ip) { IpBlock.create(ip: ip_list.last, severity: options[:severity]) }
let(:arguments) { ip_list }
it 'skips the already blocked IP address' do before { allow(IpBlock).to receive(:new).and_call_original }
allow(IpBlock).to receive(:new).and_call_original
cli.invoke(:add, ip_list, options) it 'skips already block ip and displays the correct summary' do
expect { subject }
.to output_results("#{ip_list.last} is already blocked\nAdded #{ip_list.size - 1}, skipped 1, failed 0")
expect(IpBlock).to_not have_received(:new).with(ip: ip_list.last) expect(IpBlock).to_not have_received(:new).with(ip: ip_list.last)
end end
it 'displays the correct summary' do
expect { cli.invoke(:add, ip_list, options) }.to output(
a_string_including("#{ip_list.last} is already blocked\nAdded #{ip_list.size - 1}, skipped 1, failed 0")
).to_stdout
end
context 'with --force option' do context 'with --force option' do
let!(:blocked_ip) { IpBlock.create(ip: ip_list.last, severity: 'no_access') } let!(:blocked_ip) { IpBlock.create(ip: ip_list.last, severity: 'no_access') }
let(:options) { { severity: 'sign_up_requires_approval', force: true } } let(:options) { { severity: 'sign_up_requires_approval', force: true } }
it 'overwrites the existing IP block record' do it 'overwrites the existing IP block record' do
expect { cli.invoke(:add, ip_list, options) } expect { subject }
.to change { blocked_ip.reload.severity } .to change { blocked_ip.reload.severity }
.from('no_access') .from('no_access')
.to('sign_up_requires_approval') .to('sign_up_requires_approval')
@ -89,11 +89,11 @@ describe Mastodon::CLI::IpBlocks do
context 'when a specified IP address is invalid' do context 'when a specified IP address is invalid' do
let(:ip_list) { ['320.15.175.0', '9.5.105.255', '0.0.0.0'] } let(:ip_list) { ['320.15.175.0', '9.5.105.255', '0.0.0.0'] }
let(:arguments) { ip_list }
it 'displays the correct summary' do it 'displays the correct summary' do
expect { cli.invoke(:add, ip_list, options) }.to output( expect { subject }
a_string_including("#{ip_list.first} is invalid\nAdded #{ip_list.size - 1}, skipped 0, failed 1") .to output_results("#{ip_list.first} is invalid\nAdded #{ip_list.size - 1}, skipped 0, failed 1")
).to_stdout
end end
end end
@ -124,6 +124,7 @@ describe Mastodon::CLI::IpBlocks do
context 'when a specified IP address fails to be blocked' do context 'when a specified IP address fails to be blocked' do
let(:ip_address) { '127.0.0.1' } let(:ip_address) { '127.0.0.1' }
let(:ip_block) { instance_double(IpBlock, ip: ip_address, save: false) } let(:ip_block) { instance_double(IpBlock, ip: ip_address, save: false) }
let(:arguments) { [ip_address] }
before do before do
allow(IpBlock).to receive(:new).and_return(ip_block) allow(IpBlock).to receive(:new).and_return(ip_block)
@ -132,24 +133,25 @@ describe Mastodon::CLI::IpBlocks do
end end
it 'displays an error message' do it 'displays an error message' do
expect { cli.invoke(:add, [ip_address], options) } expect { subject }
.to output( .to output_results("#{ip_address} could not be saved")
a_string_including("#{ip_address} could not be saved")
).to_stdout
end end
end end
context 'when no IP address is provided' do context 'when no IP address is provided' do
let(:arguments) { [] }
it 'exits with an error message' do it 'exits with an error message' do
expect { cli.add }.to output( expect { subject }
a_string_including('No IP(s) given') .to output_results('No IP(s) given')
).to_stdout
.and raise_error(SystemExit) .and raise_error(SystemExit)
end end
end end
end end
describe '#remove' do describe '#remove' do
let(:action) { :remove }
context 'when removing exact matches' do context 'when removing exact matches' do
let(:ip_list) do let(:ip_list) do
[ [
@ -166,22 +168,17 @@ describe Mastodon::CLI::IpBlocks do
'::/128', '::/128',
] ]
end end
let(:arguments) { ip_list }
before do before do
ip_list.each { |ip| IpBlock.create(ip: ip, severity: :no_access) } ip_list.each { |ip| IpBlock.create(ip: ip, severity: :no_access) }
end end
it 'removes exact IP blocks' do it 'removes exact ip blocks and displays success message with a summary' do
cli.invoke(:remove, ip_list) expect { subject }
.to output_results("Removed #{ip_list.size}, skipped 0")
expect(IpBlock.where(ip: ip_list)).to_not exist expect(IpBlock.where(ip: ip_list)).to_not exist
end end
it 'displays success message with a summary' do
expect { cli.invoke(:remove, ip_list) }.to output(
a_string_including("Removed #{ip_list.size}, skipped 0")
).to_stdout
end
end end
context 'with --force option' do context 'with --force option' do
@ -191,62 +188,60 @@ describe Mastodon::CLI::IpBlocks do
let(:arguments) { ['192.168.0.5', '10.0.1.50'] } let(:arguments) { ['192.168.0.5', '10.0.1.50'] }
let(:options) { { force: true } } let(:options) { { force: true } }
it 'removes blocks for IP ranges that cover given IP(s)' do it 'removes blocks for IP ranges that cover given IP(s) and keeps other ranges' do
cli.invoke(:remove, arguments, options) subject
expect(IpBlock.where(id: [first_ip_range_block.id, second_ip_range_block.id])).to_not exist expect(covered_ranges).to_not exist
expect(other_ranges).to exist
end end
it 'does not remove other IP ranges' do def covered_ranges
cli.invoke(:remove, arguments, options) IpBlock.where(id: [first_ip_range_block.id, second_ip_range_block.id])
end
expect(IpBlock.where(id: third_ip_range_block.id)).to exist def other_ranges
IpBlock.where(id: third_ip_range_block.id)
end end
end end
context 'when a specified IP address is not blocked' do context 'when a specified IP address is not blocked' do
let(:unblocked_ip) { '192.0.2.1' } let(:unblocked_ip) { '192.0.2.1' }
let(:arguments) { [unblocked_ip] }
it 'skips the IP address' do it 'skips the IP address and displays summary' do
expect { cli.invoke(:remove, [unblocked_ip]) }.to output( expect { subject }
a_string_including("#{unblocked_ip} is not yet blocked") .to output_results(
).to_stdout "#{unblocked_ip} is not yet blocked",
end 'Removed 0, skipped 1'
)
it 'displays the summary correctly' do
expect { cli.invoke(:remove, [unblocked_ip]) }.to output(
a_string_including('Removed 0, skipped 1')
).to_stdout
end end
end end
context 'when a specified IP address is invalid' do context 'when a specified IP address is invalid' do
let(:invalid_ip) { '320.15.175.0' } let(:invalid_ip) { '320.15.175.0' }
let(:arguments) { [invalid_ip] }
it 'skips the invalid IP address' do it 'skips the invalid IP address and displays summary' do
expect { cli.invoke(:remove, [invalid_ip]) }.to output( expect { subject }
a_string_including("#{invalid_ip} is invalid") .to output_results(
).to_stdout "#{invalid_ip} is invalid",
end 'Removed 0, skipped 1'
)
it 'displays the summary correctly' do
expect { cli.invoke(:remove, [invalid_ip]) }.to output(
a_string_including('Removed 0, skipped 1')
).to_stdout
end end
end end
context 'when no IP address is provided' do context 'when no IP address is provided' do
it 'exits with an error message' do it 'exits with an error message' do
expect { cli.remove }.to output( expect { subject }
a_string_including('No IP(s) given') .to output_results('No IP(s) given')
).to_stdout
.and raise_error(SystemExit) .and raise_error(SystemExit)
end end
end end
end end
describe '#export' do describe '#export' do
let(:action) { :export }
let(:first_ip_range_block) { IpBlock.create(ip: '192.168.0.0/24', severity: :no_access) } let(:first_ip_range_block) { IpBlock.create(ip: '192.168.0.0/24', severity: :no_access) }
let(:second_ip_range_block) { IpBlock.create(ip: '10.0.0.0/16', severity: :no_access) } let(:second_ip_range_block) { IpBlock.create(ip: '10.0.0.0/16', severity: :no_access) }
let(:third_ip_range_block) { IpBlock.create(ip: '127.0.0.1', severity: :sign_up_block) } let(:third_ip_range_block) { IpBlock.create(ip: '127.0.0.1', severity: :sign_up_block) }
@ -255,15 +250,13 @@ describe Mastodon::CLI::IpBlocks do
let(:options) { { format: 'plain' } } let(:options) { { format: 'plain' } }
it 'exports blocked IPs with "no_access" severity in plain format' do it 'exports blocked IPs with "no_access" severity in plain format' do
expect { cli.invoke(:export, nil, options) }.to output( expect { subject }
a_string_including("#{first_ip_range_block.ip}/#{first_ip_range_block.ip.prefix}\n#{second_ip_range_block.ip}/#{second_ip_range_block.ip.prefix}") .to output_results("#{first_ip_range_block.ip}/#{first_ip_range_block.ip.prefix}\n#{second_ip_range_block.ip}/#{second_ip_range_block.ip.prefix}")
).to_stdout
end end
it 'does not export bloked IPs with different severities' do it 'does not export blocked IPs with different severities' do
expect { cli.invoke(:export, nil, options) }.to_not output( expect { subject }
a_string_including("#{third_ip_range_block.ip}/#{first_ip_range_block.ip.prefix}") .to_not output_results("#{third_ip_range_block.ip}/#{first_ip_range_block.ip.prefix}")
).to_stdout
end end
end end
@ -271,23 +264,20 @@ describe Mastodon::CLI::IpBlocks do
let(:options) { { format: 'nginx' } } let(:options) { { format: 'nginx' } }
it 'exports blocked IPs with "no_access" severity in plain format' do it 'exports blocked IPs with "no_access" severity in plain format' do
expect { cli.invoke(:export, nil, options) }.to output( expect { subject }
a_string_including("deny #{first_ip_range_block.ip}/#{first_ip_range_block.ip.prefix};\ndeny #{second_ip_range_block.ip}/#{second_ip_range_block.ip.prefix};") .to output_results("deny #{first_ip_range_block.ip}/#{first_ip_range_block.ip.prefix};\ndeny #{second_ip_range_block.ip}/#{second_ip_range_block.ip.prefix};")
).to_stdout
end end
it 'does not export bloked IPs with different severities' do it 'does not export blocked IPs with different severities' do
expect { cli.invoke(:export, nil, options) }.to_not output( expect { subject }
a_string_including("deny #{third_ip_range_block.ip}/#{first_ip_range_block.ip.prefix};") .to_not output_results("deny #{third_ip_range_block.ip}/#{first_ip_range_block.ip.prefix};")
).to_stdout
end end
end end
context 'when --format option is not provided' do context 'when --format option is not provided' do
it 'exports blocked IPs in plain format by default' do it 'exports blocked IPs in plain format by default' do
expect { cli.export }.to output( expect { subject }
a_string_including("#{first_ip_range_block.ip}/#{first_ip_range_block.ip.prefix}\n#{second_ip_range_block.ip}/#{second_ip_range_block.ip.prefix}") .to output_results("#{first_ip_range_block.ip}/#{first_ip_range_block.ip.prefix}\n#{second_ip_range_block.ip}/#{second_ip_range_block.ip.prefix}")
).to_stdout
end end
end end
end end

@ -4,13 +4,20 @@ require 'rails_helper'
require 'mastodon/cli/main' require 'mastodon/cli/main'
describe Mastodon::CLI::Main do describe Mastodon::CLI::Main do
subject { cli.invoke(action, arguments, options) }
let(:cli) { described_class.new }
let(:arguments) { [] }
let(:options) { {} }
it_behaves_like 'CLI Command' it_behaves_like 'CLI Command'
describe 'version' do describe '#version' do
let(:action) { :version }
it 'returns the Mastodon version' do it 'returns the Mastodon version' do
expect { described_class.new.invoke(:version) }.to output( expect { subject }
a_string_including(Mastodon::Version.to_s) .to output_results(Mastodon::Version.to_s)
).to_stdout
end end
end end
end end

@ -4,20 +4,26 @@ require 'rails_helper'
require 'mastodon/cli/maintenance' require 'mastodon/cli/maintenance'
describe Mastodon::CLI::Maintenance do describe Mastodon::CLI::Maintenance do
subject { cli.invoke(action, arguments, options) }
let(:cli) { described_class.new } let(:cli) { described_class.new }
let(:arguments) { [] }
let(:options) { {} }
it_behaves_like 'CLI Command' it_behaves_like 'CLI Command'
describe '#fix_duplicates' do describe '#fix_duplicates' do
let(:action) { :fix_duplicates }
context 'when the database version is too old' do context 'when the database version is too old' do
before do before do
allow(ActiveRecord::Migrator).to receive(:current_version).and_return(2000_01_01_000000) # Earlier than minimum allow(ActiveRecord::Migrator).to receive(:current_version).and_return(2000_01_01_000000) # Earlier than minimum
end end
it 'Exits with error message' do it 'Exits with error message' do
expect { cli.invoke :fix_duplicates }.to output( expect { subject }
a_string_including('is too old') .to output_results('is too old')
).to_stdout.and raise_error(SystemExit) .and raise_error(SystemExit)
end end
end end
@ -28,9 +34,9 @@ describe Mastodon::CLI::Maintenance do
end end
it 'Exits with error message' do it 'Exits with error message' do
expect { cli.invoke :fix_duplicates }.to output( expect { subject }
a_string_including('more recent') .to output_results('more recent')
).to_stdout.and raise_error(SystemExit) .and raise_error(SystemExit)
end end
end end
@ -41,9 +47,9 @@ describe Mastodon::CLI::Maintenance do
end end
it 'Exits with error message' do it 'Exits with error message' do
expect { cli.invoke :fix_duplicates }.to output( expect { subject }
a_string_including('Sidekiq is running') .to output_results('Sidekiq is running')
).to_stdout.and raise_error(SystemExit) .and raise_error(SystemExit)
end end
end end
end end

@ -4,18 +4,24 @@ require 'rails_helper'
require 'mastodon/cli/media' require 'mastodon/cli/media'
describe Mastodon::CLI::Media do describe Mastodon::CLI::Media do
subject { cli.invoke(action, arguments, options) }
let(:cli) { described_class.new } let(:cli) { described_class.new }
let(:arguments) { [] }
let(:options) { {} }
it_behaves_like 'CLI Command' it_behaves_like 'CLI Command'
describe '#remove' do describe '#remove' do
let(:action) { :remove }
context 'with --prune-profiles and --remove-headers' do context 'with --prune-profiles and --remove-headers' do
let(:options) { { prune_profiles: true, remove_headers: true } } let(:options) { { prune_profiles: true, remove_headers: true } }
it 'warns about usage and exits' do it 'warns about usage and exits' do
expect { cli.invoke(:remove, [], options) }.to output( expect { subject }
a_string_including('--prune-profiles and --remove-headers should not be specified simultaneously') .to output_results('--prune-profiles and --remove-headers should not be specified simultaneously')
).to_stdout.and raise_error(SystemExit) .and raise_error(SystemExit)
end end
end end
@ -23,9 +29,9 @@ describe Mastodon::CLI::Media do
let(:options) { { include_follows: true } } let(:options) { { include_follows: true } }
it 'warns about usage and exits' do it 'warns about usage and exits' do
expect { cli.invoke(:remove, [], options) }.to output( expect { subject }
a_string_including('--include-follows can only be used with --prune-profiles or --remove-headers') .to output_results('--include-follows can only be used with --prune-profiles or --remove-headers')
).to_stdout.and raise_error(SystemExit) .and raise_error(SystemExit)
end end
end end
@ -38,9 +44,8 @@ describe Mastodon::CLI::Media do
let(:options) { { prune_profiles: true } } let(:options) { { prune_profiles: true } }
it 'removes account avatars' do it 'removes account avatars' do
expect { cli.invoke(:remove, [], options) }.to output( expect { subject }
a_string_including('Visited 1') .to output_results('Visited 1')
).to_stdout
expect(account.reload.avatar).to be_blank expect(account.reload.avatar).to be_blank
end end
@ -50,9 +55,8 @@ describe Mastodon::CLI::Media do
let(:options) { { remove_headers: true } } let(:options) { { remove_headers: true } }
it 'removes account header' do it 'removes account header' do
expect { cli.invoke(:remove, [], options) }.to output( expect { subject }
a_string_including('Visited 1') .to output_results('Visited 1')
).to_stdout
expect(account.reload.header).to be_blank expect(account.reload.header).to be_blank
end end
@ -64,9 +68,8 @@ describe Mastodon::CLI::Media do
context 'without options' do context 'without options' do
it 'removes account avatars' do it 'removes account avatars' do
expect { cli.invoke(:remove) }.to output( expect { subject }
a_string_including('Removed 1') .to output_results('Removed 1')
).to_stdout
expect(media_attachment.reload.file).to be_blank expect(media_attachment.reload.file).to be_blank
expect(media_attachment.reload.thumbnail).to be_blank expect(media_attachment.reload.thumbnail).to be_blank
@ -76,25 +79,50 @@ describe Mastodon::CLI::Media do
end end
describe '#usage' do describe '#usage' do
context 'without options' do let(:action) { :usage }
let(:options) { {} }
context 'without options' do
it 'reports about storage size' do it 'reports about storage size' do
expect { cli.invoke(:usage, [], options) }.to output( expect { subject }
a_string_including('0 Bytes') .to output_results('0 Bytes')
).to_stdout end
end
end
describe '#lookup' do
let(:action) { :lookup }
let(:arguments) { [url] }
context 'with valid url not connected to a record' do
let(:url) { 'https://example.host/assets/1' }
it 'warns about url and exits' do
expect { subject }
.to output_results('Not a media URL')
.and raise_error(SystemExit)
end
end
context 'with a valid media url' do
let(:status) { Fabricate(:status) }
let(:media_attachment) { Fabricate(:media_attachment, status: status) }
let(:url) { media_attachment.file.url(:original) }
it 'displays the url of a connected status' do
expect { subject }
.to output_results(status.id.to_s)
end end
end end
end end
describe '#refresh' do describe '#refresh' do
context 'without any options' do let(:action) { :refresh }
let(:options) { {} }
context 'without any options' do
it 'warns about usage and exits' do it 'warns about usage and exits' do
expect { cli.invoke(:refresh, [], options) }.to output( expect { subject }
a_string_including('Specify the source') .to output_results('Specify the source')
).to_stdout.and raise_error(SystemExit) .and raise_error(SystemExit)
end end
end end
@ -108,9 +136,8 @@ describe Mastodon::CLI::Media do
let(:status) { Fabricate(:status) } let(:status) { Fabricate(:status) }
it 'redownloads the attachment file' do it 'redownloads the attachment file' do
expect { cli.invoke(:refresh, [], options) }.to output( expect { subject }
a_string_including('Downloaded 1 media') .to output_results('Downloaded 1 media')
).to_stdout
end end
end end
@ -119,9 +146,9 @@ describe Mastodon::CLI::Media do
let(:options) { { account: 'not-real-user@example.host' } } let(:options) { { account: 'not-real-user@example.host' } }
it 'warns about usage and exits' do it 'warns about usage and exits' do
expect { cli.invoke(:refresh, [], options) }.to output( expect { subject }
a_string_including('No such account') .to output_results('No such account')
).to_stdout.and raise_error(SystemExit) .and raise_error(SystemExit)
end end
end end
@ -135,9 +162,8 @@ describe Mastodon::CLI::Media do
let(:account) { Fabricate(:account) } let(:account) { Fabricate(:account) }
it 'redownloads the attachment file' do it 'redownloads the attachment file' do
expect { cli.invoke(:refresh, [], options) }.to output( expect { subject }
a_string_including('Downloaded 1 media') .to output_results('Downloaded 1 media')
).to_stdout
end end
end end
end end
@ -153,9 +179,8 @@ describe Mastodon::CLI::Media do
let(:account) { Fabricate(:account, domain: domain) } let(:account) { Fabricate(:account, domain: domain) }
it 'redownloads the attachment file' do it 'redownloads the attachment file' do
expect { cli.invoke(:refresh, [], options) }.to output( expect { subject }
a_string_including('Downloaded 1 media') .to output_results('Downloaded 1 media')
).to_stdout
end end
end end
end end

@ -4,11 +4,17 @@ require 'rails_helper'
require 'mastodon/cli/preview_cards' require 'mastodon/cli/preview_cards'
describe Mastodon::CLI::PreviewCards do describe Mastodon::CLI::PreviewCards do
subject { cli.invoke(action, arguments, options) }
let(:cli) { described_class.new } let(:cli) { described_class.new }
let(:arguments) { [] }
let(:options) { {} }
it_behaves_like 'CLI Command' it_behaves_like 'CLI Command'
describe '#remove' do describe '#remove' do
let(:action) { :remove }
context 'with relevant preview cards' do context 'with relevant preview cards' do
before do before do
Fabricate(:preview_card, updated_at: 10.years.ago, type: :link) Fabricate(:preview_card, updated_at: 10.years.ago, type: :link)
@ -18,10 +24,11 @@ describe Mastodon::CLI::PreviewCards do
context 'with no arguments' do context 'with no arguments' do
it 'deletes thumbnails for local preview cards' do it 'deletes thumbnails for local preview cards' do
expect { cli.invoke(:remove) }.to output( expect { subject }
a_string_including('Removed 2 preview cards') .to output_results(
.and(a_string_including('approx. 119 KB')) 'Removed 2 preview cards',
).to_stdout 'approx. 119 KB'
)
end end
end end
@ -29,10 +36,11 @@ describe Mastodon::CLI::PreviewCards do
let(:options) { { link: true } } let(:options) { { link: true } }
it 'deletes thumbnails for local preview cards' do it 'deletes thumbnails for local preview cards' do
expect { cli.invoke(:remove, [], options) }.to output( expect { subject }
a_string_including('Removed 1 link-type preview cards') .to output_results(
.and(a_string_including('approx. 59.6 KB')) 'Removed 1 link-type preview cards',
).to_stdout 'approx. 59.6 KB'
)
end end
end end
@ -40,10 +48,11 @@ describe Mastodon::CLI::PreviewCards do
let(:options) { { days: 365 } } let(:options) { { days: 365 } }
it 'deletes thumbnails for local preview cards' do it 'deletes thumbnails for local preview cards' do
expect { cli.invoke(:remove, [], options) }.to output( expect { subject }
a_string_including('Removed 1 preview cards') .to output_results(
.and(a_string_including('approx. 59.6 KB')) 'Removed 1 preview cards',
).to_stdout 'approx. 59.6 KB'
)
end end
end end
end end

@ -4,5 +4,79 @@ require 'rails_helper'
require 'mastodon/cli/search' require 'mastodon/cli/search'
describe Mastodon::CLI::Search do describe Mastodon::CLI::Search do
subject { cli.invoke(action, arguments, options) }
let(:cli) { described_class.new }
let(:arguments) { [] }
let(:options) { {} }
it_behaves_like 'CLI Command' it_behaves_like 'CLI Command'
describe '#deploy' do
let(:action) { :deploy }
context 'with concurrency out of range' do
let(:options) { { concurrency: -100 } }
it 'Exits with error message' do
expect { subject }
.to output_results('this concurrency setting')
.and raise_error(SystemExit)
end
end
context 'with batch size out of range' do
let(:options) { { batch_size: -100_000 } }
it 'Exits with error message' do
expect { subject }
.to output_results('this batch_size setting')
.and raise_error(SystemExit)
end
end
context 'without options' do
before { stub_search_indexes }
let(:indexed_count) { 1 }
let(:deleted_count) { 2 }
it 'reports about storage size' do
expect { subject }
.to output_results(
"Indexed #{described_class::INDICES.size * indexed_count} records",
"de-indexed #{described_class::INDICES.size * deleted_count}"
)
end
end
def stub_search_indexes
described_class::INDICES.each do |index|
allow(index)
.to receive_messages(
specification: instance_double(Chewy::Index::Specification, changed?: true, lock!: nil),
purge: nil
)
importer_double = importer_double_for(index)
allow(importer_double).to receive(:on_progress).and_yield([indexed_count, deleted_count])
allow("Importer::#{index}Importer".constantize)
.to receive(:new)
.and_return(importer_double)
end
end
def importer_double_for(index)
instance_double(
"Importer::#{index}Importer".constantize,
clean_up!: nil,
estimate!: 100,
import!: nil,
on_failure: nil,
# on_progress: nil,
optimize_for_import!: nil,
optimize_for_search!: nil
)
end
end
end end

@ -7,59 +7,53 @@ describe Mastodon::CLI::Settings do
it_behaves_like 'CLI Command' it_behaves_like 'CLI Command'
describe 'subcommand "registrations"' do describe 'subcommand "registrations"' do
subject { cli.invoke(action, arguments, options) }
let(:cli) { Mastodon::CLI::Registrations.new } let(:cli) { Mastodon::CLI::Registrations.new }
let(:arguments) { [] }
let(:options) { {} }
before do before do
Setting.registrations_mode = nil Setting.registrations_mode = nil
end end
describe '#open' do describe '#open' do
it 'changes "registrations_mode" to "open"' do let(:action) { :open }
expect { cli.open }.to change(Setting, :registrations_mode).from(nil).to('open')
end
it 'displays success message' do it 'changes "registrations_mode" to "open" and displays success' do
expect { cli.open }.to output( expect { subject }
a_string_including('OK') .to change(Setting, :registrations_mode).from(nil).to('open')
).to_stdout .and output_results('OK')
end end
end end
describe '#approved' do describe '#approved' do
it 'changes "registrations_mode" to "approved"' do let(:action) { :approved }
expect { cli.approved }.to change(Setting, :registrations_mode).from(nil).to('approved')
end
it 'displays success message' do it 'changes "registrations_mode" to "approved" and displays success' do
expect { cli.approved }.to output( expect { subject }
a_string_including('OK') .to change(Setting, :registrations_mode).from(nil).to('approved')
).to_stdout .and output_results('OK')
end end
context 'with --require-reason' do context 'with --require-reason' do
before do let(:options) { { require_reason: true } }
cli.options = { require_reason: true }
end
it 'changes "registrations_mode" to "approved"' do
expect { cli.approved }.to change(Setting, :registrations_mode).from(nil).to('approved')
end
it 'sets "require_invite_text" to "true"' do it 'changes registrations_mode and require_invite_text' do
expect { cli.approved }.to change(Setting, :require_invite_text).from(false).to(true) expect { subject }
.to change(Setting, :registrations_mode).from(nil).to('approved')
.and change(Setting, :require_invite_text).from(false).to(true)
end end
end end
end end
describe '#close' do describe '#close' do
it 'changes "registrations_mode" to "none"' do let(:action) { :close }
expect { cli.close }.to change(Setting, :registrations_mode).from(nil).to('none')
end
it 'displays success message' do it 'changes "registrations_mode" to "none" and displays success' do
expect { cli.close }.to output( expect { subject }
a_string_including('OK') .to change(Setting, :registrations_mode).from(nil).to('none')
).to_stdout .and output_results('OK')
end end
end end
end end

@ -4,26 +4,31 @@ require 'rails_helper'
require 'mastodon/cli/statuses' require 'mastodon/cli/statuses'
describe Mastodon::CLI::Statuses do describe Mastodon::CLI::Statuses do
subject { cli.invoke(action, arguments, options) }
let(:cli) { described_class.new } let(:cli) { described_class.new }
let(:arguments) { [] }
let(:options) { {} }
it_behaves_like 'CLI Command' it_behaves_like 'CLI Command'
describe '#remove', use_transactional_tests: false do describe '#remove', use_transactional_tests: false do
let(:action) { :remove }
context 'with small batch size' do context 'with small batch size' do
let(:options) { { batch_size: 0 } } let(:options) { { batch_size: 0 } }
it 'exits with error message' do it 'exits with error message' do
expect { cli.invoke :remove, [], options }.to output( expect { subject }
a_string_including('Cannot run') .to output_results('Cannot run')
).to_stdout.and raise_error(SystemExit) .and raise_error(SystemExit)
end end
end end
context 'with default batch size' do context 'with default batch size' do
it 'removes unreferenced statuses' do it 'removes unreferenced statuses' do
expect { cli.invoke :remove }.to output( expect { subject }
a_string_including('Done after') .to output_results('Done after')
).to_stdout
end end
end end
end end

@ -4,23 +4,26 @@ require 'rails_helper'
require 'mastodon/cli/upgrade' require 'mastodon/cli/upgrade'
describe Mastodon::CLI::Upgrade do describe Mastodon::CLI::Upgrade do
subject { cli.invoke(action, arguments, options) }
let(:cli) { described_class.new } let(:cli) { described_class.new }
let(:arguments) { [] }
let(:options) { {} }
it_behaves_like 'CLI Command' it_behaves_like 'CLI Command'
describe '#storage_schema' do describe '#storage_schema' do
context 'with records that dont need upgrading' do let(:action) { :storage_schema }
let(:options) { {} }
context 'with records that dont need upgrading' do
before do before do
Fabricate(:account) Fabricate(:account)
Fabricate(:media_attachment) Fabricate(:media_attachment)
end end
it 'does not upgrade storage for the attachments' do it 'does not upgrade storage for the attachments' do
expect { cli.invoke(:storage_schema, [], options) }.to output( expect { subject }
a_string_including('Upgraded storage schema of 0 records') .to output_results('Upgraded storage schema of 0 records')
).to_stdout
end end
end end
end end

@ -88,6 +88,7 @@ RSpec.configure do |config|
config.include Chewy::Rspec::Helpers config.include Chewy::Rspec::Helpers
config.include Redisable config.include Redisable
config.include SignedRequestHelpers, type: :request config.include SignedRequestHelpers, type: :request
config.include CommandLineHelpers, type: :cli
config.around(:each, use_transactional_tests: false) do |example| config.around(:each, use_transactional_tests: false) do |example|
self.use_transactional_tests = false self.use_transactional_tests = false

@ -2,20 +2,16 @@
require 'rails_helper' require 'rails_helper'
describe Api::V1::Accounts::FamiliarFollowersController do describe 'Accounts Familiar Followers API' do
render_views
let(:user) { Fabricate(:user) } let(:user) { Fabricate(:user) }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read:follows') } let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
let(:scopes) { 'read:follows' }
let(:headers) { { 'Authorization' => "Bearer #{token.token}" } }
let(:account) { Fabricate(:account) } let(:account) { Fabricate(:account) }
before do describe 'GET /api/v1/accounts/familiar_followers' do
allow(controller).to receive(:doorkeeper_token) { token }
end
describe 'GET #index' do
it 'returns http success' do it 'returns http success' do
get :index, params: { account_id: account.id, limit: 2 } get '/api/v1/accounts/familiar_followers', params: { account_id: account.id, limit: 2 }, headers: headers
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
end end
@ -26,7 +22,7 @@ describe Api::V1::Accounts::FamiliarFollowersController do
it 'removes duplicate account IDs from params' do it 'removes duplicate account IDs from params' do
account_ids = [account_a, account_b, account_b, account_a, account_a].map { |a| a.id.to_s } account_ids = [account_a, account_b, account_b, account_a, account_a].map { |a| a.id.to_s }
get :index, params: { id: account_ids } get '/api/v1/accounts/familiar_followers', params: { id: account_ids }, headers: headers
expect(body_as_json.pluck(:id)).to contain_exactly(account_a.id.to_s, account_b.id.to_s) expect(body_as_json.pluck(:id)).to contain_exactly(account_a.id.to_s, account_b.id.to_s)
end end

@ -0,0 +1,19 @@
# frozen_string_literal: true
require 'rails_helper'
describe 'Accounts Identity Proofs API' do
let(:user) { Fabricate(:user) }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
let(:scopes) { 'read:accounts' }
let(:headers) { { 'Authorization' => "Bearer #{token.token}" } }
let(:account) { Fabricate(:account) }
describe 'GET /api/v1/accounts/identity_proofs' do
it 'returns http success' do
get "/api/v1/accounts/#{account.id}/identity_proofs", params: { limit: 2 }, headers: headers
expect(response).to have_http_status(200)
end
end
end

@ -0,0 +1,25 @@
# frozen_string_literal: true
require 'rails_helper'
describe 'Accounts Lists API' do
let(:user) { Fabricate(:user) }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
let(:scopes) { 'read:lists' }
let(:headers) { { 'Authorization' => "Bearer #{token.token}" } }
let(:account) { Fabricate(:account) }
let(:list) { Fabricate(:list, account: user.account) }
before do
user.account.follow!(account)
list.accounts << account
end
describe 'GET /api/v1/accounts/lists' do
it 'returns http success' do
get "/api/v1/accounts/#{account.id}/lists", headers: headers
expect(response).to have_http_status(200)
end
end
end

@ -0,0 +1,19 @@
# frozen_string_literal: true
require 'rails_helper'
describe 'Accounts Lookup API' do
let(:user) { Fabricate(:user) }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
let(:scopes) { 'read:accounts' }
let(:headers) { { 'Authorization' => "Bearer #{token.token}" } }
let(:account) { Fabricate(:account) }
describe 'GET /api/v1/accounts/lookup' do
it 'returns http success' do
get '/api/v1/accounts/lookup', params: { account_id: account.id, acct: account.acct }, headers: headers
expect(response).to have_http_status(200)
end
end
end

@ -2,21 +2,17 @@
require 'rails_helper' require 'rails_helper'
describe Api::V1::Accounts::NotesController do describe 'Accounts Notes API' do
render_views
let(:user) { Fabricate(:user) } let(:user) { Fabricate(:user) }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'write:accounts') } let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
let(:scopes) { 'write:accounts' }
let(:headers) { { 'Authorization' => "Bearer #{token.token}" } }
let(:account) { Fabricate(:account) } let(:account) { Fabricate(:account) }
let(:comment) { 'foo' } let(:comment) { 'foo' }
before do describe 'POST /api/v1/accounts/:account_id/note' do
allow(controller).to receive(:doorkeeper_token) { token }
end
describe 'POST #create' do
subject do subject do
post :create, params: { account_id: account.id, comment: comment } post "/api/v1/accounts/#{account.id}/note", params: { comment: comment }, headers: headers
end end
context 'when account note has reasonable length', :aggregate_failures do context 'when account note has reasonable length', :aggregate_failures do

@ -0,0 +1,41 @@
# frozen_string_literal: true
require 'rails_helper'
describe 'Accounts Pins API' do
let(:user) { Fabricate(:user) }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
let(:scopes) { 'write:accounts' }
let(:headers) { { 'Authorization' => "Bearer #{token.token}" } }
let(:kevin) { Fabricate(:user) }
before do
kevin.account.followers << user.account
end
describe 'POST /api/v1/accounts/:account_id/pin' do
subject { post "/api/v1/accounts/#{kevin.account.id}/pin", headers: headers }
it 'creates account_pin', :aggregate_failures do
expect do
subject
end.to change { AccountPin.where(account: user.account, target_account: kevin.account).count }.by(1)
expect(response).to have_http_status(200)
end
end
describe 'POST /api/v1/accounts/:account_id/unpin' do
subject { post "/api/v1/accounts/#{kevin.account.id}/unpin", headers: headers }
before do
Fabricate(:account_pin, account: user.account, target_account: kevin.account)
end
it 'destroys account_pin', :aggregate_failures do
expect do
subject
end.to change { AccountPin.where(account: user.account, target_account: kevin.account).count }.by(-1)
expect(response).to have_http_status(200)
end
end
end

@ -31,8 +31,8 @@ describe 'GET /api/v1/accounts/relationships' do
.to have_http_status(200) .to have_http_status(200)
expect(body_as_json) expect(body_as_json)
.to be_an(Enumerable) .to be_an(Enumerable)
.and have_attributes( .and contain_exactly(
first: include( include(
following: true, following: true,
followed_by: false followed_by: false
) )
@ -53,9 +53,11 @@ describe 'GET /api/v1/accounts/relationships' do
expect(body_as_json) expect(body_as_json)
.to be_an(Enumerable) .to be_an(Enumerable)
.and have_attributes( .and have_attributes(
size: 2, size: 2
first: include(simon_item), )
second: include(lewis_item) .and contain_exactly(
include(simon_item),
include(lewis_item)
) )
end end
end end
@ -71,10 +73,12 @@ describe 'GET /api/v1/accounts/relationships' do
expect(body_as_json) expect(body_as_json)
.to be_an(Enumerable) .to be_an(Enumerable)
.and have_attributes( .and have_attributes(
size: 3, size: 3
first: include(simon_item), )
second: include(lewis_item), .and contain_exactly(
third: include(bob_item) include(simon_item),
include(lewis_item),
include(bob_item)
) )
end end
end end
@ -88,9 +92,11 @@ describe 'GET /api/v1/accounts/relationships' do
expect(body_as_json) expect(body_as_json)
.to be_an(Enumerable) .to be_an(Enumerable)
.and have_attributes( .and have_attributes(
size: 2, size: 2
first: include(simon_item), )
second: include(lewis_item) .and contain_exactly(
include(simon_item),
include(lewis_item)
) )
end end
end end
@ -116,7 +122,6 @@ describe 'GET /api/v1/accounts/relationships' do
muting: false, muting: false,
requested: false, requested: false,
domain_blocking: false, domain_blocking: false,
} }
end end
@ -129,7 +134,6 @@ describe 'GET /api/v1/accounts/relationships' do
muting: false, muting: false,
requested: false, requested: false,
domain_blocking: false, domain_blocking: false,
} }
end end
end end
@ -149,8 +153,10 @@ describe 'GET /api/v1/accounts/relationships' do
expect(body_as_json) expect(body_as_json)
.to be_an(Enumerable) .to be_an(Enumerable)
.and have_attributes( .and have_attributes(
size: 1, size: 1
first: include( )
.and contain_exactly(
include(
following: true, following: true,
showing_reblogs: true showing_reblogs: true
) )
@ -168,8 +174,8 @@ describe 'GET /api/v1/accounts/relationships' do
expect(body_as_json) expect(body_as_json)
.to be_an(Enumerable) .to be_an(Enumerable)
.and have_attributes( .and contain_exactly(
first: include( include(
following: false, following: false,
showing_reblogs: false showing_reblogs: false
) )

@ -0,0 +1,18 @@
# frozen_string_literal: true
require 'rails_helper'
describe 'Accounts Search API' do
let(:user) { Fabricate(:user) }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
let(:scopes) { 'read:accounts' }
let(:headers) { { 'Authorization' => "Bearer #{token.token}" } }
describe 'GET /api/v1/accounts/search' do
it 'returns http success' do
get '/api/v1/accounts/search', params: { q: 'query' }, headers: headers
expect(response).to have_http_status(200)
end
end
end

@ -0,0 +1,19 @@
# frozen_string_literal: true
require 'rails_helper'
describe 'Featured Tags Suggestions API' do
let(:user) { Fabricate(:user) }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
let(:scopes) { 'read:accounts' }
let(:headers) { { 'Authorization' => "Bearer #{token.token}" } }
let(:account) { Fabricate(:account) }
describe 'GET /api/v1/featured_tags/suggestions' do
it 'returns http success' do
get '/api/v1/featured_tags/suggestions', params: { account_id: account.id, limit: 2 }, headers: headers
expect(response).to have_http_status(200)
end
end
end

@ -2,25 +2,21 @@
require 'rails_helper' require 'rails_helper'
RSpec.describe Api::V2::SearchController do describe 'Search API' do
render_views
context 'with token' do context 'with token' do
let(:user) { Fabricate(:user) } let(:user) { Fabricate(:user) }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read:search') } let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
let(:scopes) { 'read:search' }
before do let(:headers) { { 'Authorization' => "Bearer #{token.token}" } }
allow(controller).to receive(:doorkeeper_token) { token }
end
describe 'GET #index' do describe 'GET /api/v2/search' do
let!(:bob) { Fabricate(:account, username: 'bob_test') } let!(:bob) { Fabricate(:account, username: 'bob_test') }
let!(:ana) { Fabricate(:account, username: 'ana_test') } let!(:ana) { Fabricate(:account, username: 'ana_test') }
let!(:tom) { Fabricate(:account, username: 'tom_test') } let!(:tom) { Fabricate(:account, username: 'tom_test') }
let(:params) { { q: 'test' } } let(:params) { { q: 'test' } }
it 'returns http success' do it 'returns http success' do
get :index, params: params get '/api/v2/search', headers: headers, params: params
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
end end
@ -29,7 +25,7 @@ RSpec.describe Api::V2::SearchController do
let(:params) { { q: 'test', type: 'accounts' } } let(:params) { { q: 'test', type: 'accounts' } }
it 'returns all matching accounts' do it 'returns all matching accounts' do
get :index, params: params get '/api/v2/search', headers: headers, params: params
expect(body_as_json[:accounts].pluck(:id)).to contain_exactly(bob.id.to_s, ana.id.to_s, tom.id.to_s) expect(body_as_json[:accounts].pluck(:id)).to contain_exactly(bob.id.to_s, ana.id.to_s, tom.id.to_s)
end end
@ -38,7 +34,7 @@ RSpec.describe Api::V2::SearchController do
let(:params) { { q: 'test1', resolve: '1' } } let(:params) { { q: 'test1', resolve: '1' } }
it 'returns http unauthorized' do it 'returns http unauthorized' do
get :index, params: params get '/api/v2/search', headers: headers, params: params
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
end end
@ -48,7 +44,7 @@ RSpec.describe Api::V2::SearchController do
let(:params) { { q: 'test1', offset: 1 } } let(:params) { { q: 'test1', offset: 1 } }
it 'returns http unauthorized' do it 'returns http unauthorized' do
get :index, params: params get '/api/v2/search', headers: headers, params: params
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
end end
@ -62,7 +58,7 @@ RSpec.describe Api::V2::SearchController do
end end
it 'returns only the followed accounts' do it 'returns only the followed accounts' do
get :index, params: params get '/api/v2/search', headers: headers, params: params
expect(body_as_json[:accounts].pluck(:id)).to contain_exactly(ana.id.to_s) expect(body_as_json[:accounts].pluck(:id)).to contain_exactly(ana.id.to_s)
end end
@ -73,7 +69,7 @@ RSpec.describe Api::V2::SearchController do
before { allow(Search).to receive(:new).and_raise(Mastodon::SyntaxError) } before { allow(Search).to receive(:new).and_raise(Mastodon::SyntaxError) }
it 'returns http unprocessable_entity' do it 'returns http unprocessable_entity' do
get :index, params: params get '/api/v2/search', headers: headers, params: params
expect(response).to have_http_status(422) expect(response).to have_http_status(422)
end end
@ -83,7 +79,7 @@ RSpec.describe Api::V2::SearchController do
before { allow(Search).to receive(:new).and_raise(ActiveRecord::RecordNotFound) } before { allow(Search).to receive(:new).and_raise(ActiveRecord::RecordNotFound) }
it 'returns http not_found' do it 'returns http not_found' do
get :index, params: params get '/api/v2/search', headers: headers, params: params
expect(response).to have_http_status(404) expect(response).to have_http_status(404)
end end
@ -92,11 +88,11 @@ RSpec.describe Api::V2::SearchController do
end end
context 'without token' do context 'without token' do
describe 'GET #index' do describe 'GET /api/v2/search' do
let(:search_params) { nil } let(:search_params) { nil }
before do before do
get :index, params: search_params get '/api/v2/search', params: search_params
end end
context 'without a `q` param' do context 'without a `q` param' do

@ -0,0 +1,18 @@
# frozen_string_literal: true
require 'rails_helper'
describe 'Suggestions API' do
let(:user) { Fabricate(:user) }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
let(:scopes) { 'read' }
let(:headers) { { 'Authorization' => "Bearer #{token.token}" } }
describe 'GET /api/v2/suggestions' do
it 'returns http success' do
get '/api/v2/suggestions', headers: headers
expect(response).to have_http_status(200)
end
end
end

@ -6,11 +6,12 @@ RSpec.describe FanOutOnWriteService, type: :service do
subject { described_class.new } subject { described_class.new }
let(:last_active_at) { Time.now.utc } let(:last_active_at) { Time.now.utc }
let(:status) { Fabricate(:status, account: alice, visibility: visibility, text: 'Hello @bob #hoge') } let(:status) { Fabricate(:status, account: alice, visibility: visibility, text: 'Hello @bob @eve #hoge') }
let!(:alice) { Fabricate(:user, current_sign_in_at: last_active_at).account } let!(:alice) { Fabricate(:user, current_sign_in_at: last_active_at).account }
let!(:bob) { Fabricate(:user, current_sign_in_at: last_active_at, account_attributes: { username: 'bob' }).account } let!(:bob) { Fabricate(:user, current_sign_in_at: last_active_at, account_attributes: { username: 'bob' }).account }
let!(:tom) { Fabricate(:user, current_sign_in_at: last_active_at).account } let!(:tom) { Fabricate(:user, current_sign_in_at: last_active_at).account }
let!(:eve) { Fabricate(:user, current_sign_in_at: last_active_at, account_attributes: { username: 'eve' }).account }
before do before do
bob.follow!(alice) bob.follow!(alice)
@ -109,5 +110,24 @@ RSpec.describe FanOutOnWriteService, type: :service do
expect(redis).to_not have_received(:publish).with('timeline:hashtag:hoge', anything) expect(redis).to_not have_received(:publish).with('timeline:hashtag:hoge', anything)
expect(redis).to_not have_received(:publish).with('timeline:public', anything) expect(redis).to_not have_received(:publish).with('timeline:public', anything)
end end
context 'when handling status updates', :sidekiq_fake do
before do
subject.call(status)
status.snapshot!(at_time: status.created_at, rate_limit: false)
status.update!(text: 'Hello @bob @eve #hoge (edited)')
status.snapshot!(account_id: status.account_id)
redis.set("subscribed:timeline:#{eve.id}:notifications", '1')
Sidekiq::Worker.clear_all
end
it 'pushes the update to mentioned users through the notifications streaming channel' do
subject.call(status, update: true)
expect(PushUpdateWorker).to have_enqueued_sidekiq_job(anything, status.id, "timeline:#{eve.id}:notifications", { 'update' => true })
end
end
end end
end end

@ -0,0 +1,9 @@
# frozen_string_literal: true
module CommandLineHelpers
def output_results(*args)
output(
include(*args)
).to_stdout
end
end

@ -52,7 +52,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@babel/compat-data@npm:^7.22.6, @babel/compat-data@npm:^7.22.9, @babel/compat-data@npm:^7.23.3, @babel/compat-data@npm:^7.23.5": "@babel/compat-data@npm:^7.22.6, @babel/compat-data@npm:^7.23.3, @babel/compat-data@npm:^7.23.5":
version: 7.23.5 version: 7.23.5
resolution: "@babel/compat-data@npm:7.23.5" resolution: "@babel/compat-data@npm:7.23.5"
checksum: 081278ed46131a890ad566a59c61600a5f9557bd8ee5e535890c8548192532ea92590742fd74bd9db83d74c669ef8a04a7e1c85cdea27f960233e3b83c3a957c checksum: 081278ed46131a890ad566a59c61600a5f9557bd8ee5e535890c8548192532ea92590742fd74bd9db83d74c669ef8a04a7e1c85cdea27f960233e3b83c3a957c
@ -60,37 +60,37 @@ __metadata:
linkType: hard linkType: hard
"@babel/core@npm:^7.10.4, @babel/core@npm:^7.11.1, @babel/core@npm:^7.11.6, @babel/core@npm:^7.12.3, @babel/core@npm:^7.22.1": "@babel/core@npm:^7.10.4, @babel/core@npm:^7.11.1, @babel/core@npm:^7.11.6, @babel/core@npm:^7.12.3, @babel/core@npm:^7.22.1":
version: 7.23.5 version: 7.23.6
resolution: "@babel/core@npm:7.23.5" resolution: "@babel/core@npm:7.23.6"
dependencies: dependencies:
"@ampproject/remapping": "npm:^2.2.0" "@ampproject/remapping": "npm:^2.2.0"
"@babel/code-frame": "npm:^7.23.5" "@babel/code-frame": "npm:^7.23.5"
"@babel/generator": "npm:^7.23.5" "@babel/generator": "npm:^7.23.6"
"@babel/helper-compilation-targets": "npm:^7.22.15" "@babel/helper-compilation-targets": "npm:^7.23.6"
"@babel/helper-module-transforms": "npm:^7.23.3" "@babel/helper-module-transforms": "npm:^7.23.3"
"@babel/helpers": "npm:^7.23.5" "@babel/helpers": "npm:^7.23.6"
"@babel/parser": "npm:^7.23.5" "@babel/parser": "npm:^7.23.6"
"@babel/template": "npm:^7.22.15" "@babel/template": "npm:^7.22.15"
"@babel/traverse": "npm:^7.23.5" "@babel/traverse": "npm:^7.23.6"
"@babel/types": "npm:^7.23.5" "@babel/types": "npm:^7.23.6"
convert-source-map: "npm:^2.0.0" convert-source-map: "npm:^2.0.0"
debug: "npm:^4.1.0" debug: "npm:^4.1.0"
gensync: "npm:^1.0.0-beta.2" gensync: "npm:^1.0.0-beta.2"
json5: "npm:^2.2.3" json5: "npm:^2.2.3"
semver: "npm:^6.3.1" semver: "npm:^6.3.1"
checksum: 311a512a870ee330a3f9a7ea89e5df790b2b5af0b1bd98b10b4edc0de2ac440f0df4d69ea2c0ee38a4b89041b9a495802741d93603be7d4fd834ec8bb6970bd2 checksum: a02bae7d916029b70706dc301535e1b31e5d216f55d4ee6f64a15825c6b69ee2c14c52a213d1497ec414e925ed4e9d897d41fb0d75df9fea28ed2c0008790e31
languageName: node languageName: node
linkType: hard linkType: hard
"@babel/generator@npm:^7.23.5, @babel/generator@npm:^7.7.2": "@babel/generator@npm:^7.23.6, @babel/generator@npm:^7.7.2":
version: 7.23.5 version: 7.23.6
resolution: "@babel/generator@npm:7.23.5" resolution: "@babel/generator@npm:7.23.6"
dependencies: dependencies:
"@babel/types": "npm:^7.23.5" "@babel/types": "npm:^7.23.6"
"@jridgewell/gen-mapping": "npm:^0.3.2" "@jridgewell/gen-mapping": "npm:^0.3.2"
"@jridgewell/trace-mapping": "npm:^0.3.17" "@jridgewell/trace-mapping": "npm:^0.3.17"
jsesc: "npm:^2.5.1" jsesc: "npm:^2.5.1"
checksum: 14c6e874f796c4368e919bed6003bb0adc3ce837760b08f9e646d20aeb5ae7d309723ce6e4f06bcb4a2b5753145446c8e4425851380f695e40e71e1760f49e7b checksum: 53540e905cd10db05d9aee0a5304e36927f455ce66f95d1253bb8a179f286b88fa7062ea0db354c566fe27f8bb96567566084ffd259f8feaae1de5eccc8afbda
languageName: node languageName: node
linkType: hard linkType: hard
@ -122,16 +122,16 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@babel/helper-compilation-targets@npm:^7.22.15, @babel/helper-compilation-targets@npm:^7.22.6": "@babel/helper-compilation-targets@npm:^7.22.15, @babel/helper-compilation-targets@npm:^7.22.6, @babel/helper-compilation-targets@npm:^7.23.6":
version: 7.22.15 version: 7.23.6
resolution: "@babel/helper-compilation-targets@npm:7.22.15" resolution: "@babel/helper-compilation-targets@npm:7.23.6"
dependencies: dependencies:
"@babel/compat-data": "npm:^7.22.9" "@babel/compat-data": "npm:^7.23.5"
"@babel/helper-validator-option": "npm:^7.22.15" "@babel/helper-validator-option": "npm:^7.23.5"
browserslist: "npm:^4.21.9" browserslist: "npm:^4.22.2"
lru-cache: "npm:^5.1.1" lru-cache: "npm:^5.1.1"
semver: "npm:^6.3.1" semver: "npm:^6.3.1"
checksum: 45b9286861296e890f674a3abb199efea14a962a27d9b8adeb44970a9fd5c54e73a9e342e8414d2851cf4f98d5994537352fbce7b05ade32e9849bbd327f9ff1 checksum: ba38506d11185f48b79abf439462ece271d3eead1673dd8814519c8c903c708523428806f05f2ec5efd0c56e4e278698fac967e5a4b5ee842c32415da54bc6fa
languageName: node languageName: node
linkType: hard linkType: hard
@ -342,14 +342,14 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@babel/helpers@npm:^7.23.5": "@babel/helpers@npm:^7.23.6":
version: 7.23.5 version: 7.23.6
resolution: "@babel/helpers@npm:7.23.5" resolution: "@babel/helpers@npm:7.23.6"
dependencies: dependencies:
"@babel/template": "npm:^7.22.15" "@babel/template": "npm:^7.22.15"
"@babel/traverse": "npm:^7.23.5" "@babel/traverse": "npm:^7.23.6"
"@babel/types": "npm:^7.23.5" "@babel/types": "npm:^7.23.6"
checksum: a37e2728eb4378a4888e5d614e28de7dd79b55ac8acbecd0e5c761273e2a02a8f33b34b1932d9069db55417ace2937cbf8ec37c42f1030ce6d228857d7ccaa4f checksum: df1cf6607676ad36f52f652ec03536f2732d70aef5e76dba5c964e34d49f3c2d3dcf9fb3740db359f53071d74b64606a833d5ba156f79f437f71bfe06e2e7e19
languageName: node languageName: node
linkType: hard linkType: hard
@ -364,12 +364,12 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.14.7, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.22.15, @babel/parser@npm:^7.23.5": "@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.14.7, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.22.15, @babel/parser@npm:^7.23.6":
version: 7.23.5 version: 7.23.6
resolution: "@babel/parser@npm:7.23.5" resolution: "@babel/parser@npm:7.23.6"
bin: bin:
parser: ./bin/babel-parser.js parser: ./bin/babel-parser.js
checksum: 3356aa90d7bafb4e2c7310e7c2c3d443c4be4db74913f088d3d577a1eb914ea4188e05fd50a47ce907a27b755c4400c4e3cbeee73dbeb37761f6ca85954f5a20 checksum: 6f76cd5ccae1fa9bcab3525b0865c6222e9c1d22f87abc69f28c5c7b2c8816a13361f5bd06bddbd5faf903f7320a8feba02545c981468acec45d12a03db7755e
languageName: node languageName: node
linkType: hard linkType: hard
@ -836,14 +836,15 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@babel/plugin-transform-for-of@npm:^7.23.3": "@babel/plugin-transform-for-of@npm:^7.23.6":
version: 7.23.3 version: 7.23.6
resolution: "@babel/plugin-transform-for-of@npm:7.23.3" resolution: "@babel/plugin-transform-for-of@npm:7.23.6"
dependencies: dependencies:
"@babel/helper-plugin-utils": "npm:^7.22.5" "@babel/helper-plugin-utils": "npm:^7.22.5"
"@babel/helper-skip-transparent-expression-wrappers": "npm:^7.22.5"
peerDependencies: peerDependencies:
"@babel/core": ^7.0.0-0 "@babel/core": ^7.0.0-0
checksum: 8a36202cfee312ba80e509c7c2131e6773524e572b4dc64a8ee95bd912634fdeb5ea91c6c7747ee30e03562d0f0d333f88ed7dbb929b36b60b8d74189189e12f checksum: 46681b6ab10f3ca2d961f50d4096b62ab5d551e1adad84e64be1ee23e72eb2f26a1e30e617e853c74f1349fffe4af68d33921a128543b6f24b6d46c09a3e2aec
languageName: node languageName: node
linkType: hard linkType: hard
@ -1200,8 +1201,8 @@ __metadata:
linkType: hard linkType: hard
"@babel/plugin-transform-runtime@npm:^7.22.4": "@babel/plugin-transform-runtime@npm:^7.22.4":
version: 7.23.4 version: 7.23.6
resolution: "@babel/plugin-transform-runtime@npm:7.23.4" resolution: "@babel/plugin-transform-runtime@npm:7.23.6"
dependencies: dependencies:
"@babel/helper-module-imports": "npm:^7.22.15" "@babel/helper-module-imports": "npm:^7.22.15"
"@babel/helper-plugin-utils": "npm:^7.22.5" "@babel/helper-plugin-utils": "npm:^7.22.5"
@ -1211,7 +1212,7 @@ __metadata:
semver: "npm:^6.3.1" semver: "npm:^6.3.1"
peerDependencies: peerDependencies:
"@babel/core": ^7.0.0-0 "@babel/core": ^7.0.0-0
checksum: 6ac29012550cdd10b65ec43fef0c7f43904ec458c43d597f627d8f52807413e57ea94e3986dbace576d734e67c2d09be5e43e77c72567d18f8c4ac5e19844625 checksum: 94a7ee92f073df53fd8bebf9ed391a95553716077da1c6c3a57f10f042358c938495d55e6b09b4b50544c01f03560c4770c17698e1c24817a15d3668e8231249
languageName: node languageName: node
linkType: hard linkType: hard
@ -1333,11 +1334,11 @@ __metadata:
linkType: hard linkType: hard
"@babel/preset-env@npm:^7.11.0, @babel/preset-env@npm:^7.12.1, @babel/preset-env@npm:^7.22.4": "@babel/preset-env@npm:^7.11.0, @babel/preset-env@npm:^7.12.1, @babel/preset-env@npm:^7.22.4":
version: 7.23.5 version: 7.23.6
resolution: "@babel/preset-env@npm:7.23.5" resolution: "@babel/preset-env@npm:7.23.6"
dependencies: dependencies:
"@babel/compat-data": "npm:^7.23.5" "@babel/compat-data": "npm:^7.23.5"
"@babel/helper-compilation-targets": "npm:^7.22.15" "@babel/helper-compilation-targets": "npm:^7.23.6"
"@babel/helper-plugin-utils": "npm:^7.22.5" "@babel/helper-plugin-utils": "npm:^7.22.5"
"@babel/helper-validator-option": "npm:^7.23.5" "@babel/helper-validator-option": "npm:^7.23.5"
"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "npm:^7.23.3" "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "npm:^7.23.3"
@ -1377,7 +1378,7 @@ __metadata:
"@babel/plugin-transform-dynamic-import": "npm:^7.23.4" "@babel/plugin-transform-dynamic-import": "npm:^7.23.4"
"@babel/plugin-transform-exponentiation-operator": "npm:^7.23.3" "@babel/plugin-transform-exponentiation-operator": "npm:^7.23.3"
"@babel/plugin-transform-export-namespace-from": "npm:^7.23.4" "@babel/plugin-transform-export-namespace-from": "npm:^7.23.4"
"@babel/plugin-transform-for-of": "npm:^7.23.3" "@babel/plugin-transform-for-of": "npm:^7.23.6"
"@babel/plugin-transform-function-name": "npm:^7.23.3" "@babel/plugin-transform-function-name": "npm:^7.23.3"
"@babel/plugin-transform-json-strings": "npm:^7.23.4" "@babel/plugin-transform-json-strings": "npm:^7.23.4"
"@babel/plugin-transform-literals": "npm:^7.23.3" "@babel/plugin-transform-literals": "npm:^7.23.3"
@ -1418,7 +1419,7 @@ __metadata:
semver: "npm:^6.3.1" semver: "npm:^6.3.1"
peerDependencies: peerDependencies:
"@babel/core": ^7.0.0-0 "@babel/core": ^7.0.0-0
checksum: 2a0e1274dec045186e131c6433659b75492583290e8d41633c616f6bff829cb2e4b2f9a57f556283a54db3bd6aa697911e56a36f607911a29b731c445a5b5a06 checksum: 5b24d179af52f082d04b9b98cc4777e37bf31a97cef5a91d8917e996dbd75f2f743c88c40f80744cb8529355bb674619d150c0260c32d834aa4067e21d0c8962
languageName: node languageName: node
linkType: hard linkType: hard
@ -1483,11 +1484,11 @@ __metadata:
linkType: hard linkType: hard
"@babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.1.2, @babel/runtime@npm:^7.11.2, @babel/runtime@npm:^7.12.0, @babel/runtime@npm:^7.12.1, @babel/runtime@npm:^7.12.13, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.13.8, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.2.0, @babel/runtime@npm:^7.20.13, @babel/runtime@npm:^7.22.3, @babel/runtime@npm:^7.23.2, @babel/runtime@npm:^7.3.1, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.6.3, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.8.7, @babel/runtime@npm:^7.9.2": "@babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.1.2, @babel/runtime@npm:^7.11.2, @babel/runtime@npm:^7.12.0, @babel/runtime@npm:^7.12.1, @babel/runtime@npm:^7.12.13, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.13.8, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.2.0, @babel/runtime@npm:^7.20.13, @babel/runtime@npm:^7.22.3, @babel/runtime@npm:^7.23.2, @babel/runtime@npm:^7.3.1, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.6.3, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.8.7, @babel/runtime@npm:^7.9.2":
version: 7.23.5 version: 7.23.6
resolution: "@babel/runtime@npm:7.23.5" resolution: "@babel/runtime@npm:7.23.6"
dependencies: dependencies:
regenerator-runtime: "npm:^0.14.0" regenerator-runtime: "npm:^0.14.0"
checksum: ca679cc91bb7e424bc2db87bb58cc3b06ade916b9adb21fbbdc43e54cdaacb3eea201ceba2a0464b11d2eb65b9fe6a6ffcf4d7521fa52994f19be96f1af14788 checksum: d886954e985ef8e421222f7a2848884d96a752e0020d3078b920dd104e672fdf23bcc6f51a44313a048796319f1ac9d09c2c88ec8cbb4e1f09174bcd3335b9ff
languageName: node languageName: node
linkType: hard linkType: hard
@ -1502,32 +1503,32 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@babel/traverse@npm:7, @babel/traverse@npm:^7.23.5": "@babel/traverse@npm:7, @babel/traverse@npm:^7.23.6":
version: 7.23.5 version: 7.23.6
resolution: "@babel/traverse@npm:7.23.5" resolution: "@babel/traverse@npm:7.23.6"
dependencies: dependencies:
"@babel/code-frame": "npm:^7.23.5" "@babel/code-frame": "npm:^7.23.5"
"@babel/generator": "npm:^7.23.5" "@babel/generator": "npm:^7.23.6"
"@babel/helper-environment-visitor": "npm:^7.22.20" "@babel/helper-environment-visitor": "npm:^7.22.20"
"@babel/helper-function-name": "npm:^7.23.0" "@babel/helper-function-name": "npm:^7.23.0"
"@babel/helper-hoist-variables": "npm:^7.22.5" "@babel/helper-hoist-variables": "npm:^7.22.5"
"@babel/helper-split-export-declaration": "npm:^7.22.6" "@babel/helper-split-export-declaration": "npm:^7.22.6"
"@babel/parser": "npm:^7.23.5" "@babel/parser": "npm:^7.23.6"
"@babel/types": "npm:^7.23.5" "@babel/types": "npm:^7.23.6"
debug: "npm:^4.1.0" debug: "npm:^4.3.1"
globals: "npm:^11.1.0" globals: "npm:^11.1.0"
checksum: c5ea793080ca6719b0a1612198fd25e361cee1f3c14142d7a518d2a1eeb5c1d21f7eec1b26c20ea6e1ddd8ed12ab50b960ff95ffd25be353b6b46e1b54d6f825 checksum: 5b4ebb94a00a7e1daf111e4b0b45a7998d5b7598637a14e75e855e88cc1b702789e09a958726b5d599a003be1e9032dbdfde4b88ea6061332228738950d5582d
languageName: node languageName: node
linkType: hard linkType: hard
"@babel/types@npm:^7.0.0, @babel/types@npm:^7.0.0-beta.49, @babel/types@npm:^7.12.11, @babel/types@npm:^7.12.6, @babel/types@npm:^7.20.7, @babel/types@npm:^7.22.10, @babel/types@npm:^7.22.15, @babel/types@npm:^7.22.19, @babel/types@npm:^7.22.5, @babel/types@npm:^7.23.0, @babel/types@npm:^7.23.5, @babel/types@npm:^7.3.3, @babel/types@npm:^7.4.4, @babel/types@npm:^7.8.3": "@babel/types@npm:^7.0.0, @babel/types@npm:^7.0.0-beta.49, @babel/types@npm:^7.12.11, @babel/types@npm:^7.12.6, @babel/types@npm:^7.20.7, @babel/types@npm:^7.22.10, @babel/types@npm:^7.22.15, @babel/types@npm:^7.22.19, @babel/types@npm:^7.22.5, @babel/types@npm:^7.23.0, @babel/types@npm:^7.23.6, @babel/types@npm:^7.3.3, @babel/types@npm:^7.4.4, @babel/types@npm:^7.8.3":
version: 7.23.5 version: 7.23.6
resolution: "@babel/types@npm:7.23.5" resolution: "@babel/types@npm:7.23.6"
dependencies: dependencies:
"@babel/helper-string-parser": "npm:^7.23.4" "@babel/helper-string-parser": "npm:^7.23.4"
"@babel/helper-validator-identifier": "npm:^7.22.20" "@babel/helper-validator-identifier": "npm:^7.22.20"
to-fast-properties: "npm:^2.0.0" to-fast-properties: "npm:^2.0.0"
checksum: 7dd5e2f59828ed046ad0b06b039df2524a8b728d204affb4fc08da2502b9dd3140b1356b5166515d229dc811539a8b70dcd4bc507e06d62a89f4091a38d0b0fb checksum: 42cefce8a68bd09bb5828b4764aa5586c53c60128ac2ac012e23858e1c179347a4aac9c66fc577994fbf57595227611c5ec8270bf0cfc94ff033bbfac0550b70
languageName: node languageName: node
linkType: hard linkType: hard
@ -1726,9 +1727,9 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@eslint/eslintrc@npm:^2.1.3": "@eslint/eslintrc@npm:^2.1.4":
version: 2.1.3 version: 2.1.4
resolution: "@eslint/eslintrc@npm:2.1.3" resolution: "@eslint/eslintrc@npm:2.1.4"
dependencies: dependencies:
ajv: "npm:^6.12.4" ajv: "npm:^6.12.4"
debug: "npm:^4.3.2" debug: "npm:^4.3.2"
@ -1739,14 +1740,14 @@ __metadata:
js-yaml: "npm:^4.1.0" js-yaml: "npm:^4.1.0"
minimatch: "npm:^3.1.2" minimatch: "npm:^3.1.2"
strip-json-comments: "npm:^3.1.1" strip-json-comments: "npm:^3.1.1"
checksum: f4103f4346126292eb15581c5a1d12bef03410fd3719dedbdb92e1f7031d46a5a2d60de8566790445d5d4b70b75ba050876799a11f5fff8265a91ee3fa77dab0 checksum: 32f67052b81768ae876c84569ffd562491ec5a5091b0c1e1ca1e0f3c24fb42f804952fdd0a137873bc64303ba368a71ba079a6f691cee25beee9722d94cc8573
languageName: node languageName: node
linkType: hard linkType: hard
"@eslint/js@npm:8.54.0": "@eslint/js@npm:8.55.0":
version: 8.54.0 version: 8.55.0
resolution: "@eslint/js@npm:8.54.0" resolution: "@eslint/js@npm:8.55.0"
checksum: d61fb4a0be6af2d8cb290121c329697664a75d6255a29926d5454fb02aeb02b87112f67fdf218d10abac42f90c570ac366126751baefc5405d0e017ed0c946c5 checksum: 88ab9fc57a651becd2b32ec40a3958db27fae133b1ae77bebd733aa5bbd00a92f325bb02f20ad680d31c731fa49b22f060a4777dd52eb3e27da013d940bd978d
languageName: node languageName: node
linkType: hard linkType: hard
@ -2435,7 +2436,7 @@ __metadata:
stacktrace-js: "npm:^2.0.2" stacktrace-js: "npm:^2.0.2"
stringz: "npm:^2.1.0" stringz: "npm:^2.1.0"
stylelint: "npm:^15.10.1" stylelint: "npm:^15.10.1"
stylelint-config-standard-scss: "npm:^11.0.0" stylelint-config-standard-scss: "npm:^12.0.0"
substring-trie: "npm:^1.0.2" substring-trie: "npm:^1.0.2"
terser-webpack-plugin: "npm:^4.2.3" terser-webpack-plugin: "npm:^4.2.3"
tesseract.js: "npm:^2.1.5" tesseract.js: "npm:^2.1.5"
@ -3030,11 +3031,11 @@ __metadata:
linkType: hard linkType: hard
"@types/emoji-mart@npm:^3.0.9": "@types/emoji-mart@npm:^3.0.9":
version: 3.0.13 version: 3.0.14
resolution: "@types/emoji-mart@npm:3.0.13" resolution: "@types/emoji-mart@npm:3.0.14"
dependencies: dependencies:
"@types/react": "npm:*" "@types/react": "npm:*"
checksum: 840f920c3242e1d274f0102e67cb2d00434e1fd370e0bcc8983b43b6b62322b01e3ddcd5fb078c60883e613530a7c70b8c40060624897543cd4da9441ca81486 checksum: 23ded65fce9b3355fbe903d3971cb67cc827a5d587464bb7e3f349615527ef4a9197b3bb59fa84c4391d1b901e7f200f686a7fc83f649ae2a51a0fb948cbadfb
languageName: node languageName: node
linkType: hard linkType: hard
@ -3178,12 +3179,12 @@ __metadata:
linkType: hard linkType: hard
"@types/jest@npm:^29.5.2": "@types/jest@npm:^29.5.2":
version: 29.5.10 version: 29.5.11
resolution: "@types/jest@npm:29.5.10" resolution: "@types/jest@npm:29.5.11"
dependencies: dependencies:
expect: "npm:^29.0.0" expect: "npm:^29.0.0"
pretty-format: "npm:^29.0.0" pretty-format: "npm:^29.0.0"
checksum: b46171d59d12a5f69bbe710f65eaf59a8073337c6b4a67dff8158575caec53f1c61f8a7d645b34d6ac3c4ea398acd30f0c5d1c4a131c0c918798019264a3397d checksum: 524a3394845214581278bf4d75055927261fbeac7e1a89cd621bd0636da37d265fe0a85eac58b5778758faad1cbd7c7c361dfc190c78ebde03a91cce33463261
languageName: node languageName: node
linkType: hard linkType: hard
@ -3377,11 +3378,11 @@ __metadata:
linkType: hard linkType: hard
"@types/react-helmet@npm:^6.1.6": "@types/react-helmet@npm:^6.1.6":
version: 6.1.9 version: 6.1.11
resolution: "@types/react-helmet@npm:6.1.9" resolution: "@types/react-helmet@npm:6.1.11"
dependencies: dependencies:
"@types/react": "npm:*" "@types/react": "npm:*"
checksum: d1823582903d6e70f1f447c7bec9e844b6f85f5de84cbcde5c8bbeecc064db1394c786ed9b9ded30544afe5c91e57c7e8105171df1643998f64c0aeab9f7f2aa checksum: f7b3bb2151d992a108ae46fed876fb9c8119108397d9a01d150c5642782997542c8b3c52e742b56e8689b7dbfa62ca9cfc76aa7e05dec4e60c652f7ef53fa783
languageName: node languageName: node
linkType: hard linkType: hard
@ -3498,13 +3499,13 @@ __metadata:
linkType: hard linkType: hard
"@types/react@npm:*, @types/react@npm:16 || 17 || 18, @types/react@npm:>=16.9.11, @types/react@npm:^18.2.7": "@types/react@npm:*, @types/react@npm:16 || 17 || 18, @types/react@npm:>=16.9.11, @types/react@npm:^18.2.7":
version: 18.2.41 version: 18.2.43
resolution: "@types/react@npm:18.2.41" resolution: "@types/react@npm:18.2.43"
dependencies: dependencies:
"@types/prop-types": "npm:*" "@types/prop-types": "npm:*"
"@types/scheduler": "npm:*" "@types/scheduler": "npm:*"
csstype: "npm:^3.0.2" csstype: "npm:^3.0.2"
checksum: 5cc72491ce8be95e7bbedd8bf039ca971772ecd22d989feb045af7e73247c7e6cff25a2f1c2200be461fb2f6b5aacef739e1ba9fd83c744209dfd3ce8aa75afe checksum: 10477a50fbd3c0cc5b8a2ade679f442717f68fb27c8460b2aa1d3256cd18c48f742bbe5b9ee37a8c4c5f832ffa37b3a23c09fd96dd880a8e3182d8929c05e803
languageName: node languageName: node
linkType: hard linkType: hard
@ -3685,14 +3686,14 @@ __metadata:
linkType: hard linkType: hard
"@typescript-eslint/eslint-plugin@npm:^6.0.0": "@typescript-eslint/eslint-plugin@npm:^6.0.0":
version: 6.11.0 version: 6.13.2
resolution: "@typescript-eslint/eslint-plugin@npm:6.11.0" resolution: "@typescript-eslint/eslint-plugin@npm:6.13.2"
dependencies: dependencies:
"@eslint-community/regexpp": "npm:^4.5.1" "@eslint-community/regexpp": "npm:^4.5.1"
"@typescript-eslint/scope-manager": "npm:6.11.0" "@typescript-eslint/scope-manager": "npm:6.13.2"
"@typescript-eslint/type-utils": "npm:6.11.0" "@typescript-eslint/type-utils": "npm:6.13.2"
"@typescript-eslint/utils": "npm:6.11.0" "@typescript-eslint/utils": "npm:6.13.2"
"@typescript-eslint/visitor-keys": "npm:6.11.0" "@typescript-eslint/visitor-keys": "npm:6.13.2"
debug: "npm:^4.3.4" debug: "npm:^4.3.4"
graphemer: "npm:^1.4.0" graphemer: "npm:^1.4.0"
ignore: "npm:^5.2.4" ignore: "npm:^5.2.4"
@ -3705,44 +3706,44 @@ __metadata:
peerDependenciesMeta: peerDependenciesMeta:
typescript: typescript:
optional: true optional: true
checksum: 6645aa09b9d51c5e3ea781eaf74da75b94f83f3e2d7b3dd988d5ce7eb82dd87e3509471cf2ee8c6b2428d907df5f1b02f29dbd04f54c2653f9566c8c4ce98009 checksum: 531a4406d872738d165c6a66cb26e976523c94053b022a8210dc9fd10e91b79b705bc0fcc77145e9744e4108b53bdba55e02a10dc17757b22be92aff57849384
languageName: node languageName: node
linkType: hard linkType: hard
"@typescript-eslint/parser@npm:^6.0.0": "@typescript-eslint/parser@npm:^6.0.0":
version: 6.11.0 version: 6.13.2
resolution: "@typescript-eslint/parser@npm:6.11.0" resolution: "@typescript-eslint/parser@npm:6.13.2"
dependencies: dependencies:
"@typescript-eslint/scope-manager": "npm:6.11.0" "@typescript-eslint/scope-manager": "npm:6.13.2"
"@typescript-eslint/types": "npm:6.11.0" "@typescript-eslint/types": "npm:6.13.2"
"@typescript-eslint/typescript-estree": "npm:6.11.0" "@typescript-eslint/typescript-estree": "npm:6.13.2"
"@typescript-eslint/visitor-keys": "npm:6.11.0" "@typescript-eslint/visitor-keys": "npm:6.13.2"
debug: "npm:^4.3.4" debug: "npm:^4.3.4"
peerDependencies: peerDependencies:
eslint: ^7.0.0 || ^8.0.0 eslint: ^7.0.0 || ^8.0.0
peerDependenciesMeta: peerDependenciesMeta:
typescript: typescript:
optional: true optional: true
checksum: e7caeb20069102e21f468fc0dbe7ff6fb6b1efa9e72f4c9f39d4a865ed0633f39130b593ef9ae8f394ca1d70563e15410faf30a482a97809951eaac6ed3a67da checksum: 2c62b8cd8a37eb2ea59cd00e559f51a9f57af746e2040e872af3c58ddd3f4071ad7b7009789bdeb0e0d4ee0343bfe96ee77288020f3ae22d08e1674203f5e156
languageName: node languageName: node
linkType: hard linkType: hard
"@typescript-eslint/scope-manager@npm:6.11.0": "@typescript-eslint/scope-manager@npm:6.13.2":
version: 6.11.0 version: 6.13.2
resolution: "@typescript-eslint/scope-manager@npm:6.11.0" resolution: "@typescript-eslint/scope-manager@npm:6.13.2"
dependencies: dependencies:
"@typescript-eslint/types": "npm:6.11.0" "@typescript-eslint/types": "npm:6.13.2"
"@typescript-eslint/visitor-keys": "npm:6.11.0" "@typescript-eslint/visitor-keys": "npm:6.13.2"
checksum: d8999e2d1a4cbde8a79df5e3ec416f0e3db9532d39f2f4bb5a0ebdf954ae75c183d3277579ba05268fe2c88e88ef87f0fa12f02bb8d95d9e67d92e411241f3a3 checksum: 9b159e5bb10dfb5953e71488200b4126378fc7e987ce7d90946aea9ec40cd66c7ada92399657c5d9794189b764ca6f4eb38a8dcb9e4c5aa50ab6000a39636b9c
languageName: node languageName: node
linkType: hard linkType: hard
"@typescript-eslint/type-utils@npm:6.11.0": "@typescript-eslint/type-utils@npm:6.13.2":
version: 6.11.0 version: 6.13.2
resolution: "@typescript-eslint/type-utils@npm:6.11.0" resolution: "@typescript-eslint/type-utils@npm:6.13.2"
dependencies: dependencies:
"@typescript-eslint/typescript-estree": "npm:6.11.0" "@typescript-eslint/typescript-estree": "npm:6.13.2"
"@typescript-eslint/utils": "npm:6.11.0" "@typescript-eslint/utils": "npm:6.13.2"
debug: "npm:^4.3.4" debug: "npm:^4.3.4"
ts-api-utils: "npm:^1.0.1" ts-api-utils: "npm:^1.0.1"
peerDependencies: peerDependencies:
@ -3750,23 +3751,23 @@ __metadata:
peerDependenciesMeta: peerDependenciesMeta:
typescript: typescript:
optional: true optional: true
checksum: ff68f2e052b8d688f1dc1a0050746704c8e0ab6263b47f1f52da73a7d251678e4950af23a95e1cd8e3fcea2457e6e5294ddbe01d29dafa2fdfb5b11ed9452a3f checksum: 1ca97c78abdf479aea0c54e869fda2ae2f69de1974cc063062ce7b5b16c7fdf497ea15c50a29dd5941ea1b6b77e8f1213a5c272a747e334ac69ede083f327468
languageName: node languageName: node
linkType: hard linkType: hard
"@typescript-eslint/types@npm:6.11.0": "@typescript-eslint/types@npm:6.13.2":
version: 6.11.0 version: 6.13.2
resolution: "@typescript-eslint/types@npm:6.11.0" resolution: "@typescript-eslint/types@npm:6.13.2"
checksum: 23182813db39a5e9b9bcc1e85306c953f7b8b22d3885e41fcac0bd725c170fbcb70f4ce55633678cc5921dcf062fa0e55635eb39480c118a4411a00354820223 checksum: 029918ca5b1442bb4bc435773504ce32191e2c3e2fde8d4176bb6513f03e3dfa2aa9724b2d22b1640656d666b97f7a7ebfeaf67b881d5e07250828fa83e3ebe8
languageName: node languageName: node
linkType: hard linkType: hard
"@typescript-eslint/typescript-estree@npm:6.11.0": "@typescript-eslint/typescript-estree@npm:6.13.2":
version: 6.11.0 version: 6.13.2
resolution: "@typescript-eslint/typescript-estree@npm:6.11.0" resolution: "@typescript-eslint/typescript-estree@npm:6.13.2"
dependencies: dependencies:
"@typescript-eslint/types": "npm:6.11.0" "@typescript-eslint/types": "npm:6.13.2"
"@typescript-eslint/visitor-keys": "npm:6.11.0" "@typescript-eslint/visitor-keys": "npm:6.13.2"
debug: "npm:^4.3.4" debug: "npm:^4.3.4"
globby: "npm:^11.1.0" globby: "npm:^11.1.0"
is-glob: "npm:^4.0.3" is-glob: "npm:^4.0.3"
@ -3775,34 +3776,34 @@ __metadata:
peerDependenciesMeta: peerDependenciesMeta:
typescript: typescript:
optional: true optional: true
checksum: 3e183e554e1bc74f065da3015f7137eb40c262f989c547701b1e3f4f20134e574e56b749288cd00d77b9d1ddb705546613c2457661ffc63b6060ffa97ba3aac8 checksum: 1c4c59dce0c51fdfee34d9f418e64fe28e3ec1a97661efc8a3d2780bdff36aff38de9090d356a968f394fa6d4e9c058936ce9cd260d4c44a52761ecd74915bce
languageName: node languageName: node
linkType: hard linkType: hard
"@typescript-eslint/utils@npm:6.11.0, @typescript-eslint/utils@npm:^6.5.0": "@typescript-eslint/utils@npm:6.13.2, @typescript-eslint/utils@npm:^6.5.0":
version: 6.11.0 version: 6.13.2
resolution: "@typescript-eslint/utils@npm:6.11.0" resolution: "@typescript-eslint/utils@npm:6.13.2"
dependencies: dependencies:
"@eslint-community/eslint-utils": "npm:^4.4.0" "@eslint-community/eslint-utils": "npm:^4.4.0"
"@types/json-schema": "npm:^7.0.12" "@types/json-schema": "npm:^7.0.12"
"@types/semver": "npm:^7.5.0" "@types/semver": "npm:^7.5.0"
"@typescript-eslint/scope-manager": "npm:6.11.0" "@typescript-eslint/scope-manager": "npm:6.13.2"
"@typescript-eslint/types": "npm:6.11.0" "@typescript-eslint/types": "npm:6.13.2"
"@typescript-eslint/typescript-estree": "npm:6.11.0" "@typescript-eslint/typescript-estree": "npm:6.13.2"
semver: "npm:^7.5.4" semver: "npm:^7.5.4"
peerDependencies: peerDependencies:
eslint: ^7.0.0 || ^8.0.0 eslint: ^7.0.0 || ^8.0.0
checksum: c91eb4578607959acc2b43ddc791571682e45601a19b25d5d120786ed4af607656f83c5c1fa71972e549ddfb5542acf2f7d443ae93b32ee28192c22c106b8883 checksum: 84969be91e7949868eaaa289288c9d71927f0e427b572501b0991d8d62b40a4234f7287c35b35d276ccbb53e9ea5457b8250fcf4941e60e6b9ba4065fbfba416
languageName: node languageName: node
linkType: hard linkType: hard
"@typescript-eslint/visitor-keys@npm:6.11.0": "@typescript-eslint/visitor-keys@npm:6.13.2":
version: 6.11.0 version: 6.13.2
resolution: "@typescript-eslint/visitor-keys@npm:6.11.0" resolution: "@typescript-eslint/visitor-keys@npm:6.13.2"
dependencies: dependencies:
"@typescript-eslint/types": "npm:6.11.0" "@typescript-eslint/types": "npm:6.13.2"
eslint-visitor-keys: "npm:^3.4.1" eslint-visitor-keys: "npm:^3.4.1"
checksum: 5f48329422b7f286196661d39e93e9defd7c5cf80e6c84c8d03459853f5d9f86a5e91c5e80ea572dcdb907ebbe503bbcc77aeb8b468c294b2aa7b3ccfc81cb88 checksum: c173bc1fcc42c3075a5ee094e7f3bf0279d98315c25ff49e20d02d79022b1d0402accfa113b070afb4d52a6f6d180594b67baa8b6a784eabdf82b54dd1ff454c
languageName: node languageName: node
linkType: hard linkType: hard
@ -5180,17 +5181,17 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"browserslist@npm:^4.0.0, browserslist@npm:^4.21.10, browserslist@npm:^4.21.4, browserslist@npm:^4.21.9, browserslist@npm:^4.22.1": "browserslist@npm:^4.0.0, browserslist@npm:^4.21.10, browserslist@npm:^4.21.4, browserslist@npm:^4.22.1, browserslist@npm:^4.22.2":
version: 4.22.1 version: 4.22.2
resolution: "browserslist@npm:4.22.1" resolution: "browserslist@npm:4.22.2"
dependencies: dependencies:
caniuse-lite: "npm:^1.0.30001541" caniuse-lite: "npm:^1.0.30001565"
electron-to-chromium: "npm:^1.4.535" electron-to-chromium: "npm:^1.4.601"
node-releases: "npm:^2.0.13" node-releases: "npm:^2.0.14"
update-browserslist-db: "npm:^1.0.13" update-browserslist-db: "npm:^1.0.13"
bin: bin:
browserslist: cli.js browserslist: cli.js
checksum: 6810f2d63f171d0b7b8d38cf091708e00cb31525501810a507839607839320d66e657293b0aa3d7f051ecbc025cb07390a90c037682c1d05d12604991e41050b checksum: 2a331aab90503130043ca41dd5d281fa1e89d5e076d07a2d75e76bf4d693bd56e73d5abcd8c4f39119da6328d450578c216cf1cd5c99b82d8a90a2ae6271b465
languageName: node languageName: node
linkType: hard linkType: hard
@ -5418,10 +5419,10 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"caniuse-lite@npm:^1.0.0, caniuse-lite@npm:^1.0.30001538, caniuse-lite@npm:^1.0.30001541": "caniuse-lite@npm:^1.0.0, caniuse-lite@npm:^1.0.30001538, caniuse-lite@npm:^1.0.30001565":
version: 1.0.30001561 version: 1.0.30001568
resolution: "caniuse-lite@npm:1.0.30001561" resolution: "caniuse-lite@npm:1.0.30001568"
checksum: 6e84c84026fee53edbdbb5aded7a04a036aae4c2e367cf6bdc90c6783a591e2fdcfcdebcc4e774aca61092e542a61200c8c16b06659396492426033c4dbcc618 checksum: 13f01e5a2481134bd61cf565ce9fecbd8e107902927a0dcf534230a92191a81f1715792170f5f39719c767c3a96aa6df9917a8d5601f15bbd5e4041a8cfecc99
languageName: node languageName: node
linkType: hard linkType: hard
@ -6423,7 +6424,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"debug@npm:4, debug@npm:4.3.4, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.2, debug@npm:^4.3.4": "debug@npm:4, debug@npm:4.3.4, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.4":
version: 4.3.4 version: 4.3.4
resolution: "debug@npm:4.3.4" resolution: "debug@npm:4.3.4"
dependencies: dependencies:
@ -6965,10 +6966,10 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"electron-to-chromium@npm:^1.4.535": "electron-to-chromium@npm:^1.4.601":
version: 1.4.576 version: 1.4.609
resolution: "electron-to-chromium@npm:1.4.576" resolution: "electron-to-chromium@npm:1.4.609"
checksum: b0b9e7ba803bf93ffac9cb830ed2b0e0eb07f20066127065f9ab9e08e4e6a5812040e03d76f6ee9bc59e03fb938fd414e83d4883b29111303e9e88633cf2dce4 checksum: 9675a79388acbaff5953a4c61589af7da93e0d1f9d6a3b284c7630f10126eb0998557b07448514214d5a3d19025310039b55f405ab701b1253130fc94907f743
languageName: node languageName: node
linkType: hard linkType: hard
@ -7323,13 +7324,13 @@ __metadata:
linkType: hard linkType: hard
"eslint-config-prettier@npm:^9.0.0": "eslint-config-prettier@npm:^9.0.0":
version: 9.0.0 version: 9.1.0
resolution: "eslint-config-prettier@npm:9.0.0" resolution: "eslint-config-prettier@npm:9.1.0"
peerDependencies: peerDependencies:
eslint: ">=7.0.0" eslint: ">=7.0.0"
bin: bin:
eslint-config-prettier: bin/cli.js eslint-config-prettier: bin/cli.js
checksum: bc1f661915845c631824178942e5d02f858fe6d0ea796f0050d63e0f681927b92696e81139dd04714c08c3e7de580fd079c66162e40070155ba79eaee78ab5d0 checksum: 6d332694b36bc9ac6fdb18d3ca2f6ac42afa2ad61f0493e89226950a7091e38981b66bac2b47ba39d15b73fff2cd32c78b850a9cf9eed9ca9a96bfb2f3a2f10d
languageName: node languageName: node
linkType: hard linkType: hard
@ -7565,13 +7566,13 @@ __metadata:
linkType: hard linkType: hard
"eslint@npm:^8.41.0": "eslint@npm:^8.41.0":
version: 8.54.0 version: 8.55.0
resolution: "eslint@npm:8.54.0" resolution: "eslint@npm:8.55.0"
dependencies: dependencies:
"@eslint-community/eslint-utils": "npm:^4.2.0" "@eslint-community/eslint-utils": "npm:^4.2.0"
"@eslint-community/regexpp": "npm:^4.6.1" "@eslint-community/regexpp": "npm:^4.6.1"
"@eslint/eslintrc": "npm:^2.1.3" "@eslint/eslintrc": "npm:^2.1.4"
"@eslint/js": "npm:8.54.0" "@eslint/js": "npm:8.55.0"
"@humanwhocodes/config-array": "npm:^0.11.13" "@humanwhocodes/config-array": "npm:^0.11.13"
"@humanwhocodes/module-importer": "npm:^1.0.1" "@humanwhocodes/module-importer": "npm:^1.0.1"
"@nodelib/fs.walk": "npm:^1.2.8" "@nodelib/fs.walk": "npm:^1.2.8"
@ -7608,7 +7609,7 @@ __metadata:
text-table: "npm:^0.2.0" text-table: "npm:^0.2.0"
bin: bin:
eslint: bin/eslint.js eslint: bin/eslint.js
checksum: 4f205f832bdbd0218cde374b067791f4f76d7abe8de86b2dc849c273899051126d912ebf71531ee49b8eeaa22cad77febdc8f2876698dc2a76e84a8cb976af22 checksum: d28c0b60f19bb7d355cb8393e77b018c8f548dba3f820b799c89bb2e0c436ee26084e700c5e57e1e97e7972ec93065277849141b82e7b0c0d02c2dc1e553a2a1
languageName: node languageName: node
linkType: hard linkType: hard
@ -11954,10 +11955,10 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"node-releases@npm:^2.0.13": "node-releases@npm:^2.0.14":
version: 2.0.13 version: 2.0.14
resolution: "node-releases@npm:2.0.13" resolution: "node-releases@npm:2.0.14"
checksum: 2fb44bf70fc949d27f3a48a7fd1a9d1d603ddad4ccd091f26b3fb8b1da976605d919330d7388ccd55ca2ade0dc8b2e12841ba19ef249c8bb29bf82532d401af7 checksum: 199fc93773ae70ec9969bc6d5ac5b2bbd6eb986ed1907d751f411fef3ede0e4bfdb45ceb43711f8078bea237b6036db8b1bf208f6ff2b70c7d615afd157f3ab9
languageName: node languageName: node
linkType: hard linkType: hard
@ -13336,11 +13337,11 @@ __metadata:
linkType: hard linkType: hard
"prettier@npm:^3.0.0": "prettier@npm:^3.0.0":
version: 3.1.0 version: 3.1.1
resolution: "prettier@npm:3.1.0" resolution: "prettier@npm:3.1.1"
bin: bin:
prettier: bin/prettier.cjs prettier: bin/prettier.cjs
checksum: a45ea70aa97fde162ea4c4aba3dfc7859aa6a732a1db34458d9535dc3c2c16d3bc3fb5689e6cd76aa835562555303b02d9449fd2e15af3b73c8053557e25c5b6 checksum: facc944ba20e194ff4db765e830ffbcb642803381f0d2033ed397e79904fa4ccc877dc25ad68f42d36985c01d051c990ca1b905fb83d2d7d65fe69e4386fa1a3
languageName: node languageName: node
linkType: hard linkType: hard
@ -15781,62 +15782,62 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"stylelint-config-recommended-scss@npm:^13.1.0": "stylelint-config-recommended-scss@npm:^14.0.0":
version: 13.1.0 version: 14.0.0
resolution: "stylelint-config-recommended-scss@npm:13.1.0" resolution: "stylelint-config-recommended-scss@npm:14.0.0"
dependencies: dependencies:
postcss-scss: "npm:^4.0.9" postcss-scss: "npm:^4.0.9"
stylelint-config-recommended: "npm:^13.0.0" stylelint-config-recommended: "npm:^14.0.0"
stylelint-scss: "npm:^5.3.0" stylelint-scss: "npm:^6.0.0"
peerDependencies: peerDependencies:
postcss: ^8.3.3 postcss: ^8.3.3
stylelint: ^15.10.0 stylelint: ^16.0.2
peerDependenciesMeta: peerDependenciesMeta:
postcss: postcss:
optional: true optional: true
checksum: e07d0172c7936b4f644138e4129df2f187d297f1f96ce5865ab21ccd1c22caf94220f7caf9d6985e93e515de4c0356f6cb9c924d00df2eee5b3bc237f7e5bb48 checksum: 9ddc92e7a5fa131b41cee1ab1f69251934ca35c0e2803dc613329cdead7b8b27d8457048a63db29f61a1442e7cdef14207f88a3abce00ec53fdefe0d604f7de3
languageName: node languageName: node
linkType: hard linkType: hard
"stylelint-config-recommended@npm:^13.0.0": "stylelint-config-recommended@npm:^14.0.0":
version: 13.0.0 version: 14.0.0
resolution: "stylelint-config-recommended@npm:13.0.0" resolution: "stylelint-config-recommended@npm:14.0.0"
peerDependencies: peerDependencies:
stylelint: ^15.10.0 stylelint: ^16.0.0
checksum: 80420a1ab616e8637b66223f88c597388990d9991cd6a28b8372049b83329d893412f83029bb253a82b52387e497b62e042bc898064a2f22574b0d8921f01dd2 checksum: 4ad15c36e8c03291aa7bbe4b672ebfb0f46ab698e7580a0da8d29644046d102d7f31dbf00a2a6eab94b565c390c6fb0d5d528737b83ac3acf6dc2ef085a90b11
languageName: node languageName: node
linkType: hard linkType: hard
"stylelint-config-standard-scss@npm:^11.0.0": "stylelint-config-standard-scss@npm:^12.0.0":
version: 11.1.0 version: 12.0.0
resolution: "stylelint-config-standard-scss@npm:11.1.0" resolution: "stylelint-config-standard-scss@npm:12.0.0"
dependencies: dependencies:
stylelint-config-recommended-scss: "npm:^13.1.0" stylelint-config-recommended-scss: "npm:^14.0.0"
stylelint-config-standard: "npm:^34.0.0" stylelint-config-standard: "npm:^35.0.0"
peerDependencies: peerDependencies:
postcss: ^8.3.3 postcss: ^8.3.3
stylelint: ^15.10.0 stylelint: ^16.0.2
peerDependenciesMeta: peerDependenciesMeta:
postcss: postcss:
optional: true optional: true
checksum: 22d00e75c1eacce9883fd48c3d67b1107b0e39d7d86e9f73deaa332b11c39a9678c947ae2c34cd5159a452ec9a857694ed58b5a851087480d3c9a66dab629415 checksum: 7f3ccfb4175f9c50b69d30ca35a97887008c5ba493dbe7d5bce0b57b1eafd21b268177b82404368e7780600077cba784f98e1046671724be3b29a00c6a7913a4
languageName: node languageName: node
linkType: hard linkType: hard
"stylelint-config-standard@npm:^34.0.0": "stylelint-config-standard@npm:^35.0.0":
version: 34.0.0 version: 35.0.0
resolution: "stylelint-config-standard@npm:34.0.0" resolution: "stylelint-config-standard@npm:35.0.0"
dependencies: dependencies:
stylelint-config-recommended: "npm:^13.0.0" stylelint-config-recommended: "npm:^14.0.0"
peerDependencies: peerDependencies:
stylelint: ^15.10.0 stylelint: ^16.0.0
checksum: 2494468af2359490b6ebb9723d9653f9e31db3a0772b8d9f0e081018b0079ef84ae6f90dcf94c879a3c374f299e334941e3dcff1afb603c2284d3103085b71fb checksum: 791fbc26cc3029ce3c2423a643e903545b5e4cd605251b18f0ce790bac6fbaaf380469845c1ff45f4e320126af9f8a9dc1ca85d0df9274277ae60da91e81895b
languageName: node languageName: node
linkType: hard linkType: hard
"stylelint-scss@npm:^5.3.0": "stylelint-scss@npm:^6.0.0":
version: 5.3.1 version: 6.0.0
resolution: "stylelint-scss@npm:5.3.1" resolution: "stylelint-scss@npm:6.0.0"
dependencies: dependencies:
known-css-properties: "npm:^0.29.0" known-css-properties: "npm:^0.29.0"
postcss-media-query-parser: "npm:^0.2.3" postcss-media-query-parser: "npm:^0.2.3"
@ -15844,8 +15845,8 @@ __metadata:
postcss-selector-parser: "npm:^6.0.13" postcss-selector-parser: "npm:^6.0.13"
postcss-value-parser: "npm:^4.2.0" postcss-value-parser: "npm:^4.2.0"
peerDependencies: peerDependencies:
stylelint: ^14.5.1 || ^15.0.0 stylelint: ^16.0.2
checksum: 5dfed5f9ac9812cd2ac6ef0272c720dee0326aaaee2998315a23bdcd71b8f04427f29cad634793eea2b45984182e20f03e90d43501e8e4d55bc956f80e2de477 checksum: f5e971d19ef6879ae5c18cb8fba8033fe7928f241178e6afd80357cc080d2feddfd6f7fe564aaa696008aa10345df5885d9a4471c926b3e266088e015927782e
languageName: node languageName: node
linkType: hard linkType: hard
@ -16519,22 +16520,22 @@ __metadata:
linkType: hard linkType: hard
"typescript@npm:5, typescript@npm:^5.0.4": "typescript@npm:5, typescript@npm:^5.0.4":
version: 5.3.2 version: 5.3.3
resolution: "typescript@npm:5.3.2" resolution: "typescript@npm:5.3.3"
bin: bin:
tsc: bin/tsc tsc: bin/tsc
tsserver: bin/tsserver tsserver: bin/tsserver
checksum: d7dbe1fbe19039e36a65468ea64b5d338c976550394ba576b7af9c68ed40c0bc5d12ecce390e4b94b287a09a71bd3229f19c2d5680611f35b7c53a3898791159 checksum: e33cef99d82573624fc0f854a2980322714986bc35b9cb4d1ce736ed182aeab78e2cb32b385efa493b2a976ef52c53e20d6c6918312353a91850e2b76f1ea44f
languageName: node languageName: node
linkType: hard linkType: hard
"typescript@patch:typescript@npm%3A5#optional!builtin<compat/typescript>, typescript@patch:typescript@npm%3A^5.0.4#optional!builtin<compat/typescript>": "typescript@patch:typescript@npm%3A5#optional!builtin<compat/typescript>, typescript@patch:typescript@npm%3A^5.0.4#optional!builtin<compat/typescript>":
version: 5.3.2 version: 5.3.3
resolution: "typescript@patch:typescript@npm%3A5.3.2#optional!builtin<compat/typescript>::version=5.3.2&hash=e012d7" resolution: "typescript@patch:typescript@npm%3A5.3.3#optional!builtin<compat/typescript>::version=5.3.3&hash=e012d7"
bin: bin:
tsc: bin/tsc tsc: bin/tsc
tsserver: bin/tsserver tsserver: bin/tsserver
checksum: 73c8bad74e732d93211c9d77f28b03307e2f5fc6a0afc73f4b783261ab567686a16d6ae958bdaef383a00be1b0b8c8b6741dd6ca3d13af4963fa7e47456d49c7 checksum: 1d0a5f4ce496c42caa9a30e659c467c5686eae15d54b027ee7866744952547f1be1262f2d40de911618c242b510029d51d43ff605dba8fb740ec85ca2d3f9500
languageName: node languageName: node
linkType: hard linkType: hard
@ -17762,8 +17763,8 @@ __metadata:
linkType: hard linkType: hard
"ws@npm:^8.11.0, ws@npm:^8.12.1, ws@npm:^8.14.2": "ws@npm:^8.11.0, ws@npm:^8.12.1, ws@npm:^8.14.2":
version: 8.14.2 version: 8.15.0
resolution: "ws@npm:8.14.2" resolution: "ws@npm:8.15.0"
peerDependencies: peerDependencies:
bufferutil: ^4.0.1 bufferutil: ^4.0.1
utf-8-validate: ">=5.0.2" utf-8-validate: ">=5.0.2"
@ -17772,7 +17773,7 @@ __metadata:
optional: true optional: true
utf-8-validate: utf-8-validate:
optional: true optional: true
checksum: 35b4c2da048b8015c797fd14bcb5a5766216ce65c8a5965616a5440ca7b6c3681ee3cbd0ea0c184a59975556e9d58f2002abf8485a14d11d3371770811050a16 checksum: b778a405b2589ffbf549323e2f404f1f72e372a049d332d2f0b1f33057e9fbb14a05aa474cb156e4584b418cd95edf4297c0ca5263d6519e8009064bf8e0b80d
languageName: node languageName: node
linkType: hard linkType: hard

Loading…
Cancel
Save