diff --git a/.devcontainer/codespaces/devcontainer.json b/.devcontainer/codespaces/devcontainer.json index ca9156fdaa..b32e4026d2 100644 --- a/.devcontainer/codespaces/devcontainer.json +++ b/.devcontainer/codespaces/devcontainer.json @@ -5,7 +5,7 @@ "workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}", "features": { - "ghcr.io/devcontainers/features/sshd:1": {} + "ghcr.io/devcontainers/features/sshd:1": {}, }, "runServices": ["app", "db", "redis"], @@ -15,16 +15,16 @@ "portsAttributes": { "3000": { "label": "web", - "onAutoForward": "notify" + "onAutoForward": "notify", }, "4000": { "label": "stream", - "onAutoForward": "silent" - } + "onAutoForward": "silent", + }, }, "otherPortsAttributes": { - "onAutoForward": "silent" + "onAutoForward": "silent", }, "remoteEnv": { @@ -33,7 +33,7 @@ "STREAMING_API_BASE_URL": "https://${localEnv:CODESPACE_NAME}-4000.app.github.dev", "DISABLE_FORGERY_REQUEST_PROTECTION": "true", "ES_ENABLED": "", - "LIBRE_TRANSLATE_ENDPOINT": "" + "LIBRE_TRANSLATE_ENDPOINT": "", }, "onCreateCommand": "git config --global --add safe.directory ${containerWorkspaceFolder}", @@ -43,7 +43,7 @@ "customizations": { "vscode": { "settings": {}, - "extensions": ["EditorConfig.EditorConfig", "webben.browserslist"] - } - } + "extensions": ["EditorConfig.EditorConfig", "webben.browserslist"], + }, + }, } diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index fa8d6542c1..ed71235b3b 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -5,7 +5,7 @@ "workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}", "features": { - "ghcr.io/devcontainers/features/sshd:1": {} + "ghcr.io/devcontainers/features/sshd:1": {}, }, "forwardPorts": [3000, 4000], @@ -14,17 +14,17 @@ "3000": { "label": "web", "onAutoForward": "notify", - "requireLocalPort": true + "requireLocalPort": true, }, "4000": { "label": "stream", "onAutoForward": "silent", - "requireLocalPort": true - } + "requireLocalPort": true, + }, }, "otherPortsAttributes": { - "onAutoForward": "silent" + "onAutoForward": "silent", }, "onCreateCommand": "git config --global --add safe.directory ${containerWorkspaceFolder}", @@ -34,7 +34,7 @@ "customizations": { "vscode": { "settings": {}, - "extensions": ["EditorConfig.EditorConfig", "webben.browserslist"] - } - } + "extensions": ["EditorConfig.EditorConfig", "webben.browserslist"], + }, + }, } diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 2f21564325..d707578cae 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -81,17 +81,9 @@ Rails/WhereExists: - 'app/lib/delivery_failure_tracker.rb' - 'app/lib/feed_manager.rb' - 'app/lib/suspicious_sign_in_detector.rb' - - 'app/models/poll.rb' - - 'app/models/session_activation.rb' - - 'app/models/status.rb' - 'app/policies/status_policy.rb' - 'app/serializers/rest/announcement_serializer.rb' - - 'app/services/activitypub/fetch_remote_status_service.rb' - - 'app/services/vote_service.rb' - - 'app/validators/reaction_validator.rb' - - 'app/validators/vote_validator.rb' - 'app/workers/move_worker.rb' - - 'lib/tasks/tests.rake' - 'spec/models/account_spec.rb' - 'spec/services/activitypub/process_collection_service_spec.rb' - 'spec/services/purge_domain_service_spec.rb' diff --git a/app/controllers/api/v1/accounts/follower_accounts_controller.rb b/app/controllers/api/v1/accounts/follower_accounts_controller.rb index d6a5a7176d..f60181f1eb 100644 --- a/app/controllers/api/v1/accounts/follower_accounts_controller.rb +++ b/app/controllers/api/v1/accounts/follower_accounts_controller.rb @@ -30,7 +30,7 @@ class Api::V1::Accounts::FollowerAccountsController < Api::BaseController end def default_accounts - Account.includes(:active_relationships, :account_stat).references(:active_relationships) + Account.includes(:active_relationships, :account_stat, :user).references(:active_relationships) end def paginated_follows diff --git a/app/controllers/api/v1/accounts/following_accounts_controller.rb b/app/controllers/api/v1/accounts/following_accounts_controller.rb index b8578ef539..3ab8c1efd6 100644 --- a/app/controllers/api/v1/accounts/following_accounts_controller.rb +++ b/app/controllers/api/v1/accounts/following_accounts_controller.rb @@ -30,7 +30,7 @@ class Api::V1::Accounts::FollowingAccountsController < Api::BaseController end def default_accounts - Account.includes(:passive_relationships, :account_stat).references(:passive_relationships) + Account.includes(:passive_relationships, :account_stat, :user).references(:passive_relationships) end def paginated_follows diff --git a/app/controllers/api/v1/annual_reports_controller.rb b/app/controllers/api/v1/annual_reports_controller.rb new file mode 100644 index 0000000000..9bc8e68ac2 --- /dev/null +++ b/app/controllers/api/v1/annual_reports_controller.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +class Api::V1::AnnualReportsController < Api::BaseController + before_action -> { doorkeeper_authorize! :read, :'read:accounts' }, only: :index + before_action -> { doorkeeper_authorize! :write, :'write:accounts' }, except: :index + before_action :require_user! + before_action :set_annual_report, except: :index + + def index + with_read_replica do + @presenter = AnnualReportsPresenter.new(GeneratedAnnualReport.where(account_id: current_account.id).pending) + @relationships = StatusRelationshipsPresenter.new(@presenter.statuses, current_account.id) + end + + render json: @presenter, + serializer: REST::AnnualReportsSerializer, + relationships: @relationships + end + + def read + @annual_report.view! + render_empty + end + + private + + def set_annual_report + @annual_report = GeneratedAnnualReport.find_by!(account_id: current_account.id, year: params[:id]) + end +end diff --git a/app/controllers/api/v1/blocks_controller.rb b/app/controllers/api/v1/blocks_controller.rb index 06a8bfa891..0934622f88 100644 --- a/app/controllers/api/v1/blocks_controller.rb +++ b/app/controllers/api/v1/blocks_controller.rb @@ -17,7 +17,7 @@ class Api::V1::BlocksController < Api::BaseController end def paginated_blocks - @paginated_blocks ||= Block.eager_load(target_account: :account_stat) + @paginated_blocks ||= Block.eager_load(target_account: [:account_stat, :user]) .joins(:target_account) .merge(Account.without_suspended) .where(account: current_account) diff --git a/app/controllers/api/v1/directories_controller.rb b/app/controllers/api/v1/directories_controller.rb index e79b20ce42..6c540404ea 100644 --- a/app/controllers/api/v1/directories_controller.rb +++ b/app/controllers/api/v1/directories_controller.rb @@ -27,7 +27,7 @@ class Api::V1::DirectoriesController < Api::BaseController scope.merge!(local_account_scope) if local_accounts? scope.merge!(account_exclusion_scope) if current_account scope.merge!(account_domain_block_scope) if current_account && !local_accounts? - end + end.includes(:account_stat, user: :role) end def local_accounts? diff --git a/app/controllers/api/v1/endorsements_controller.rb b/app/controllers/api/v1/endorsements_controller.rb index 46e3fcd647..2216a9860d 100644 --- a/app/controllers/api/v1/endorsements_controller.rb +++ b/app/controllers/api/v1/endorsements_controller.rb @@ -25,7 +25,7 @@ class Api::V1::EndorsementsController < Api::BaseController end def endorsed_accounts - current_account.endorsed_accounts.includes(:account_stat).without_suspended + current_account.endorsed_accounts.includes(:account_stat, :user).without_suspended end def insert_pagination_headers diff --git a/app/controllers/api/v1/follow_requests_controller.rb b/app/controllers/api/v1/follow_requests_controller.rb index ee717ebbcc..87f6df5f94 100644 --- a/app/controllers/api/v1/follow_requests_controller.rb +++ b/app/controllers/api/v1/follow_requests_controller.rb @@ -37,7 +37,7 @@ class Api::V1::FollowRequestsController < Api::BaseController end def default_accounts - Account.without_suspended.includes(:follow_requests, :account_stat).references(:follow_requests) + Account.without_suspended.includes(:follow_requests, :account_stat, :user).references(:follow_requests) end def paginated_follow_requests diff --git a/app/controllers/api/v1/lists/accounts_controller.rb b/app/controllers/api/v1/lists/accounts_controller.rb index 8e12cb7b65..0604ad60fc 100644 --- a/app/controllers/api/v1/lists/accounts_controller.rb +++ b/app/controllers/api/v1/lists/accounts_controller.rb @@ -37,9 +37,9 @@ class Api::V1::Lists::AccountsController < Api::BaseController def load_accounts if unlimited? - @list.accounts.without_suspended.includes(:account_stat).all + @list.accounts.without_suspended.includes(:account_stat, :user).all else - @list.accounts.without_suspended.includes(:account_stat).paginate_by_max_id(limit_param(DEFAULT_ACCOUNTS_LIMIT), params[:max_id], params[:since_id]) + @list.accounts.without_suspended.includes(:account_stat, :user).paginate_by_max_id(limit_param(DEFAULT_ACCOUNTS_LIMIT), params[:max_id], params[:since_id]) end end diff --git a/app/controllers/api/v1/mutes_controller.rb b/app/controllers/api/v1/mutes_controller.rb index 555485823c..2fb685ac39 100644 --- a/app/controllers/api/v1/mutes_controller.rb +++ b/app/controllers/api/v1/mutes_controller.rb @@ -17,7 +17,7 @@ class Api::V1::MutesController < Api::BaseController end def paginated_mutes - @paginated_mutes ||= Mute.eager_load(:target_account) + @paginated_mutes ||= Mute.eager_load(target_account: [:account_stat, :user]) .joins(:target_account) .merge(Account.without_suspended) .where(account: current_account) diff --git a/app/controllers/api/v1/statuses/favourited_by_accounts_controller.rb b/app/controllers/api/v1/statuses/favourited_by_accounts_controller.rb index 98b69c347f..069ad37cb2 100644 --- a/app/controllers/api/v1/statuses/favourited_by_accounts_controller.rb +++ b/app/controllers/api/v1/statuses/favourited_by_accounts_controller.rb @@ -21,7 +21,7 @@ class Api::V1::Statuses::FavouritedByAccountsController < Api::V1::Statuses::Bas def default_accounts Account .without_suspended - .includes(:favourites, :account_stat) + .includes(:favourites, :account_stat, :user) .references(:favourites) .where(favourites: { status_id: @status.id }) end diff --git a/app/controllers/api/v1/statuses/reblogged_by_accounts_controller.rb b/app/controllers/api/v1/statuses/reblogged_by_accounts_controller.rb index aacab5f8f4..b8a997518d 100644 --- a/app/controllers/api/v1/statuses/reblogged_by_accounts_controller.rb +++ b/app/controllers/api/v1/statuses/reblogged_by_accounts_controller.rb @@ -19,7 +19,7 @@ class Api::V1::Statuses::RebloggedByAccountsController < Api::V1::Statuses::Base end def default_accounts - Account.without_suspended.includes(:statuses, :account_stat).references(:statuses) + Account.without_suspended.includes(:statuses, :account_stat, :user).references(:statuses) end def paginated_statuses diff --git a/app/controllers/api/v2/filters_controller.rb b/app/controllers/api/v2/filters_controller.rb index 2fcdeeae45..09d4813f34 100644 --- a/app/controllers/api/v2/filters_controller.rb +++ b/app/controllers/api/v2/filters_controller.rb @@ -35,7 +35,7 @@ class Api::V2::FiltersController < Api::BaseController private def set_filters - @filters = current_account.custom_filters.includes(:keywords) + @filters = current_account.custom_filters.includes(:keywords, :statuses) end def set_filter diff --git a/app/javascript/flavours/glitch/features/compose/containers/search_container.js b/app/javascript/flavours/glitch/features/compose/containers/search_container.js index 17be30edcc..93b8645ac6 100644 --- a/app/javascript/flavours/glitch/features/compose/containers/search_container.js +++ b/app/javascript/flavours/glitch/features/compose/containers/search_container.js @@ -1,3 +1,4 @@ +import { createSelector } from '@reduxjs/toolkit'; import { connect } from 'react-redux'; import { @@ -12,10 +13,15 @@ import { import Search from '../components/search'; +const getRecentSearches = createSelector( + state => state.getIn(['search', 'recent']), + recent => recent.reverse(), +); + const mapStateToProps = state => ({ value: state.getIn(['search', 'value']), submitted: state.getIn(['search', 'submitted']), - recent: state.getIn(['search', 'recent']).reverse(), + recent: getRecentSearches(state), }); const mapDispatchToProps = dispatch => ({ diff --git a/app/javascript/flavours/glitch/locales/ar.json b/app/javascript/flavours/glitch/locales/ar.json index 20ca870f7c..a259146328 100644 --- a/app/javascript/flavours/glitch/locales/ar.json +++ b/app/javascript/flavours/glitch/locales/ar.json @@ -32,11 +32,13 @@ "compose_form.spoiler": "إخفاء النص خلف تحذير", "confirmation_modal.do_not_ask_again": "لا تطلب التأكيد مرة أخرى", "confirmations.deprecated_settings.confirm": "استخدام تفضيلات ماستدون", + "confirmations.deprecated_settings.message": "تم استبدال بعض من الجهاز الخاص بالماستدون {preferences} الذي تستخدمه {app_settings} الخاص بجهاز ماستدون سيتم تجاوزه:", "confirmations.missing_media_description.confirm": "أرسل على أيّة حال", "confirmations.missing_media_description.edit": "تعديل الوسائط", "confirmations.unfilter.author": "المؤلف", "confirmations.unfilter.confirm": "عرض", "confirmations.unfilter.edit_filter": "تعديل عامل التصفية", + "confirmations.unfilter.filters": "مطابقة {count, plural, zero {}one {فلتر} two {فلاتر} few {فلاتر} many {فلاتر} other {فلاتر}}", "content-type.change": "نوع المحتوى", "direct.group_by_conversations": "تجميع حسب المحادثة", "endorsed_accounts_editor.endorsed_accounts": "الحسابات المميزة", @@ -61,6 +63,10 @@ "notification_purge.start": "أدخل وضع تنظيف الإشعارات", "notifications.marked_clear": "مسح الإشعارات المحددة", "notifications.marked_clear_confirmation": "هل أنت متأكد من أنك تريد مسح جميع الإشعارات المحددة نهائياً؟", + "settings.always_show_spoilers_field": "تمكين دائما حقل تحذير المحتوى", + "settings.auto_collapse_height": "الارتفاع (بالبكسل) لاعتبار التبويق طويل", + "settings.auto_collapse_reblogs": "دفع", + "settings.auto_collapse_replies": "ردود {{count}}", "settings.close": "إغلاق", "settings.content_warnings": "Content warnings", "settings.preferences": "Preferences" diff --git a/app/javascript/flavours/glitch/locales/de.json b/app/javascript/flavours/glitch/locales/de.json index 11b6a98c0d..7420917fda 100644 --- a/app/javascript/flavours/glitch/locales/de.json +++ b/app/javascript/flavours/glitch/locales/de.json @@ -2,6 +2,7 @@ "about.fork_disclaimer": "Glitch-soc ist freie, quelloffene Software geforkt von Mastodon.", "account.disclaimer_full": "Die folgenden Informationen könnten das Profil des Nutzers unvollständig wiedergeben.", "account.follows": "Folgt", + "account.follows_you": "Folgt dir", "account.joined": "Beigetreten am {date}", "account.suspended_disclaimer_full": "Dieser Nutzer wurde durch einen Moderator gesperrt.", "account.view_full_profile": "Vollständiges Profil anzeigen", diff --git a/app/javascript/flavours/glitch/locales/es-AR.json b/app/javascript/flavours/glitch/locales/es-AR.json index 860c2c0beb..48ae0f0683 100644 --- a/app/javascript/flavours/glitch/locales/es-AR.json +++ b/app/javascript/flavours/glitch/locales/es-AR.json @@ -2,6 +2,7 @@ "about.fork_disclaimer": "Glitch-soc es software gratuito, de código abierto, bifurcado de Mastodon.", "account.disclaimer_full": "La información aquí presentada puede reflejar de manera incompleta el perfil del usuario.", "account.follows": "Sigue", + "account.follows_you": "Te sigue", "account.joined": "Unido el {date}", "account.suspended_disclaimer_full": "Este usuario ha sido suspendido por un moderador.", "account.view_full_profile": "Ver perfil completo", diff --git a/app/javascript/flavours/glitch/locales/es-MX.json b/app/javascript/flavours/glitch/locales/es-MX.json index 00b87e9c12..bb0a5ab74f 100644 --- a/app/javascript/flavours/glitch/locales/es-MX.json +++ b/app/javascript/flavours/glitch/locales/es-MX.json @@ -2,6 +2,7 @@ "about.fork_disclaimer": "Glitch-soc es software gratuito, de código abierto, bifurcado de Mastodon.", "account.disclaimer_full": "La información aquí presentada puede reflejar de manera incompleta el perfil del usuario.", "account.follows": "Seguir", + "account.follows_you": "Te sigue", "account.joined": "Unido {date}", "account.suspended_disclaimer_full": "Este usuario ha sido suspendido por un moderador.", "account.view_full_profile": "Ver perfil completo", diff --git a/app/javascript/flavours/glitch/locales/es.json b/app/javascript/flavours/glitch/locales/es.json index b7f266aa3e..35f0fa8deb 100644 --- a/app/javascript/flavours/glitch/locales/es.json +++ b/app/javascript/flavours/glitch/locales/es.json @@ -2,6 +2,7 @@ "about.fork_disclaimer": "Glitch-soc es software gratuito, de código abierto, bifurcado de Mastodon.", "account.disclaimer_full": "La información que figura a continuación puede reflejar el perfil de la cuenta de forma incompleta.", "account.follows": "Sigue", + "account.follows_you": "Te sigue", "account.joined": "Se unió el {date}", "account.suspended_disclaimer_full": "Este usuario ha sido suspendido por un moderador.", "account.view_full_profile": "Ver perfil completo", diff --git a/app/javascript/flavours/glitch/locales/fr-CA.json b/app/javascript/flavours/glitch/locales/fr-CA.json new file mode 100644 index 0000000000..6015f4097d --- /dev/null +++ b/app/javascript/flavours/glitch/locales/fr-CA.json @@ -0,0 +1,159 @@ +{ + "about.fork_disclaimer": "Glitch-soc est un logiciel gratuit et open source, fork de Mastodon.", + "account.disclaimer_full": "Les informations ci-dessous peuvent être incomplètes.", + "account.follows": "Abonnements", + "account.follows_you": "Vous suit", + "account.joined": "Ici depuis {date}", + "account.suspended_disclaimer_full": "Cet utilisateur a été suspendu par un modérateur.", + "account.view_full_profile": "Voir le profil complet", + "advanced_options.icon_title": "Options avancées", + "advanced_options.local-only.long": "Ne pas envoyer aux autres instances", + "advanced_options.local-only.short": "Uniquement en local", + "advanced_options.local-only.tooltip": "Ce post est uniquement local", + "advanced_options.threaded_mode.long": "Ouvre automatiquement une réponse lors de la publication", + "advanced_options.threaded_mode.short": "Mode thread", + "advanced_options.threaded_mode.tooltip": "Mode thread activé", + "boost_modal.missing_description": "Ce post contient des médias sans description", + "column.favourited_by": "Ajouté en favori par", + "column.heading": "Divers", + "column.reblogged_by": "Partagé par", + "column.subheading": "Autres options", + "column_header.profile": "Profil", + "column_subheading.lists": "Listes", + "column_subheading.navigation": "Navigation", + "community.column_settings.allow_local_only": "Afficher seulement les posts locaux", + "compose.attach": "Joindre…", + "compose.attach.doodle": "Dessiner quelque chose", + "compose.attach.upload": "Téléverser un fichier", + "compose.content-type.html": "HTML", + "compose.content-type.markdown": "Markdown", + "compose.content-type.plain": "Text brut", + "compose_form.poll.multiple_choices": "Choix multiples", + "compose_form.poll.single_choice": "Choix unique", + "compose_form.spoiler": "Cacher le texte derrière un avertissement", + "confirmation_modal.do_not_ask_again": "Ne plus demander confirmation", + "confirmations.deprecated_settings.confirm": "Utiliser les préférences de Mastodon", + "confirmations.deprecated_settings.message": "Certaines {app_settings} de glitch-soc que vous utilisez ont été remplacées par les {preferences} de Mastodon et seront remplacées :", + "confirmations.missing_media_description.confirm": "Envoyer quand même", + "confirmations.missing_media_description.edit": "Modifier le média", + "confirmations.missing_media_description.message": "Au moins un média joint manque d'une description. Pensez à décrire tous les médias attachés pour les malvoyant·e·s avant de publier votre post.", + "confirmations.unfilter.author": "Auteur", + "confirmations.unfilter.confirm": "Afficher", + "confirmations.unfilter.edit_filter": "Modifier le filtre", + "confirmations.unfilter.filters": "Correspondance avec {count, plural, one {un filtre} other {plusieurs filtres}}", + "content-type.change": "Type de contenu", + "direct.group_by_conversations": "Grouper par conversation", + "endorsed_accounts_editor.endorsed_accounts": "Comptes mis en avant", + "favourite_modal.combo": "Vous pouvez appuyer sur {combo} pour passer ceci la prochaine fois", + "firehose.column_settings.allow_local_only": "Afficher les messages locaux dans \"Tous\"", + "home.column_settings.advanced": "Avancé", + "home.column_settings.filter_regex": "Filtrer par expression régulière", + "home.column_settings.show_direct": "Afficher les MPs", + "home.settings": "Paramètres de la colonne", + "keyboard_shortcuts.bookmark": "ajouter aux marque-pages", + "keyboard_shortcuts.secondary_toot": "Envoyer le post en utilisant les paramètres secondaires de confidentialité", + "keyboard_shortcuts.toggle_collapse": "Plier/déplier les posts", + "media_gallery.sensitive": "Sensible", + "moved_to_warning": "Ce compte a déménagé vers {moved_to_link} et ne peut donc plus accepter de nouveaux abonné·e·s.", + "navigation_bar.app_settings": "Paramètres de l'application", + "navigation_bar.featured_users": "Utilisateurs mis en avant", + "navigation_bar.keyboard_shortcuts": "Raccourcis clavier", + "navigation_bar.misc": "Autres", + "notification.markForDeletion": "Ajouter aux éléments à supprimer", + "notification_purge.btn_all": "Sélectionner\ntout", + "notification_purge.btn_apply": "Effacer\nla sélection", + "notification_purge.btn_invert": "Inverser\nla sélection", + "notification_purge.btn_none": "Annuler\nla sélection", + "notification_purge.start": "Activer le mode de nettoyage des notifications", + "notifications.marked_clear": "Effacer les notifications sélectionnées", + "notifications.marked_clear_confirmation": "Voulez-vous vraiment effacer de manière permanente toutes les notifications sélectionnées ?", + "settings.always_show_spoilers_field": "Toujours activer le champ de rédaction de l'avertissement de contenu", + "settings.auto_collapse": "Repliage automatique", + "settings.auto_collapse_all": "Tout", + "settings.auto_collapse_height": "Hauteur (en pixels) pour qu'un pouet soit considéré comme long", + "settings.auto_collapse_lengthy": "Posts longs", + "settings.auto_collapse_media": "Posts avec média", + "settings.auto_collapse_notifications": "Notifications", + "settings.auto_collapse_reblogs": "Boosts", + "settings.auto_collapse_replies": "Réponses", + "settings.close": "Fermer", + "settings.collapsed_statuses": "Posts repliés", + "settings.compose_box_opts": "Zone de rédaction", + "settings.confirm_before_clearing_draft": "Afficher une fenêtre de confirmation avant d'écraser le message en cours de rédaction", + "settings.confirm_boost_missing_media_description": "Afficher une fenêtre de confirmation avant de partager des posts manquant de description des médias", + "settings.confirm_missing_media_description": "Afficher une fenêtre de confirmation avant de publier des posts manquant de description de média", + "settings.content_warnings": "Content warnings", + "settings.content_warnings.regexp": "Expression rationnelle", + "settings.content_warnings_filter": "Avertissement de contenu à ne pas automatiquement déplier :", + "settings.content_warnings_media_outside": "Afficher les médias en dehors des avertissements de contenu", + "settings.content_warnings_media_outside_hint": "Reproduit le comportement par défaut de Mastodon, les médias attachés ne sont plus affectés par le bouton d'affichage d'un post avec avertissement", + "settings.content_warnings_shared_state": "Affiche/cache le contenu de toutes les copies à la fois", + "settings.content_warnings_shared_state_hint": "Reproduit le comportement par défaut de Mastodon, le bouton d'avertissement de contenu affecte toutes les copies d'un post à la fois. Cela empêchera le repliement automatique de n'importe quelle copie d'un post avec un avertissement déplié", + "settings.content_warnings_unfold_opts": "Options de dépliement automatique", + "settings.deprecated_setting": "Cette option est maintenant définie par les {settings_page_link} de Mastodon", + "settings.enable_collapsed": "Activer le repliement des posts", + "settings.enable_collapsed_hint": "Les posts repliés ont une partie de leur contenu caché pour libérer de l'espace sur l'écran. C'est une option différente de l'avertissement de contenu", + "settings.enable_content_warnings_auto_unfold": "Déplier automatiquement les avertissements de contenu", + "settings.general": "Général", + "settings.hicolor_privacy_icons": "Indicateurs de confidentialité en couleurs", + "settings.hicolor_privacy_icons.hint": "Affiche les indicateurs de confidentialité dans des couleurs facilement distinguables", + "settings.image_backgrounds": "Images en arrière-plan", + "settings.image_backgrounds_media": "Prévisualiser les médias d'un post replié", + "settings.image_backgrounds_media_hint": "Si le post a un média attaché, utiliser le premier comme arrière-plan du post", + "settings.image_backgrounds_users": "Donner aux posts repliés une image en arrière-plan", + "settings.inline_preview_cards": "Cartes d'aperçu pour les liens externes", + "settings.layout_opts": "Mise en page", + "settings.media": "Média", + "settings.media_fullwidth": "Utiliser toute la largeur pour les aperçus", + "settings.media_letterbox": "Afficher les médias en Letterbox", + "settings.media_letterbox_hint": "Réduit le média et utilise une letterbox pour afficher l'image entière plutôt que de l'étirer et de la rogner", + "settings.media_reveal_behind_cw": "Toujours afficher les médias sensibles avec avertissement", + "settings.notifications.favicon_badge": "Badge de notifications non lues dans la favicon", + "settings.notifications.favicon_badge.hint": "Ajoute un badge dans la favicon pour alerter d'une notification non lue", + "settings.notifications.tab_badge": "Badge de notifications non lues", + "settings.notifications.tab_badge.hint": "Affiche un badge de notifications non lues dans les icônes des colonnes quand la colonne n'est pas ouverte", + "settings.notifications_opts": "Options des notifications", + "settings.pop_in_left": "Gauche", + "settings.pop_in_player": "Activer le lecteur pop-in", + "settings.pop_in_position": "Position du lecteur pop-in :", + "settings.pop_in_right": "Droite", + "settings.preferences": "Preferences", + "settings.prepend_cw_re": "Préfixer les avertissements avec \"re: \" lors d'une réponse", + "settings.preselect_on_reply": "Présélectionner les noms d’utilisateur·rices lors de la réponse", + "settings.preselect_on_reply_hint": "Présélectionner les noms d'utilisateurs après le premier lors d'une réponse à une conversation à plusieurs participants", + "settings.rewrite_mentions": "Réécrire les mentions dans les posts affichés", + "settings.rewrite_mentions_acct": "Réécrire avec le nom d'utilisateur·rice et le domaine (lorsque le compte est distant)", + "settings.rewrite_mentions_no": "Ne pas réécrire les mentions", + "settings.rewrite_mentions_username": "Réécrire avec le nom d’utilisateur·rice", + "settings.shared_settings_link": "préférences de l'utilisateur", + "settings.show_action_bar": "Afficher les boutons d'action dans les posts repliés", + "settings.show_content_type_choice": "Afficher le choix du type de contenu lors de la création des posts", + "settings.show_reply_counter": "Afficher une estimation du nombre de réponses", + "settings.side_arm": "Bouton secondaire de publication :", + "settings.side_arm.none": "Aucun", + "settings.side_arm_reply_mode": "Quand vous répondez à un post, le bouton secondaire de publication devrait :", + "settings.side_arm_reply_mode.copy": "Copier la confidentialité du post auquel vous répondez", + "settings.side_arm_reply_mode.keep": "Garder la confidentialité établie", + "settings.side_arm_reply_mode.restrict": "Restreindre la confidentialité de la réponse à celle du post auquel vous répondez", + "settings.status_icons": "Icônes des posts", + "settings.status_icons_language": "Indicateur de langue", + "settings.status_icons_local_only": "Indicateur de post local", + "settings.status_icons_media": "Indicateur de médias et sondage", + "settings.status_icons_reply": "Indicateur de réponses", + "settings.status_icons_visibility": "Indicateur de la confidentialité du post", + "settings.swipe_to_change_columns": "Glissement latéral pour changer de colonne (mobile uniquement)", + "settings.tag_misleading_links": "Étiqueter les liens trompeurs", + "settings.tag_misleading_links.hint": "Ajouter une indication visuelle avec l'hôte cible du lien à chaque lien ne le mentionnant pas explicitement", + "settings.wide_view": "Vue élargie (mode ordinateur uniquement)", + "settings.wide_view_hint": "Étire les colonnes pour mieux remplir l'espace disponible.", + "status.collapse": "Replier", + "status.has_audio": "Contient des fichiers audio attachés", + "status.has_pictures": "Contient des images attachées", + "status.has_preview_card": "Contient une carte de prévisualisation attachée", + "status.has_video": "Contient des vidéos attachées", + "status.in_reply_to": "Ce post est une réponse", + "status.is_poll": "Ce post est un sondage", + "status.local_only": "Visible uniquement depuis votre instance", + "status.sensitive_toggle": "Cliquer pour voir", + "status.uncollapse": "Déplier" +} diff --git a/app/javascript/flavours/glitch/locales/fr.json b/app/javascript/flavours/glitch/locales/fr.json index 7131ede819..92be2d2f8f 100644 --- a/app/javascript/flavours/glitch/locales/fr.json +++ b/app/javascript/flavours/glitch/locales/fr.json @@ -2,6 +2,7 @@ "about.fork_disclaimer": "Glitch-soc est un logiciel gratuit et open source, fork de Mastodon.", "account.disclaimer_full": "Les informations ci-dessous peuvent être incomplètes.", "account.follows": "Abonnements", + "account.follows_you": "Vous suit", "account.joined": "Ici depuis {date}", "account.suspended_disclaimer_full": "Cet utilisateur a été suspendu par un modérateur.", "account.view_full_profile": "Voir le profil complet", diff --git a/app/javascript/flavours/glitch/locales/ko.json b/app/javascript/flavours/glitch/locales/ko.json index 96f0b7aa97..49fcb0b46a 100644 --- a/app/javascript/flavours/glitch/locales/ko.json +++ b/app/javascript/flavours/glitch/locales/ko.json @@ -2,6 +2,7 @@ "about.fork_disclaimer": "글리치는 마스토돈에서 포크한 자유 오픈소스 소프트웨어입니다.", "account.disclaimer_full": "아래에 있는 정보들은 사용자의 프로필을 완벽하게 나타내지 못하고 있을 수도 있습니다.", "account.follows": "팔로우", + "account.follows_you": "날 팔로우합니다", "account.joined": "{date}에 가입함", "account.suspended_disclaimer_full": "이 사용자는 중재자에 의해 정지되었습니다.", "account.view_full_profile": "전체 프로필 보기", @@ -44,6 +45,7 @@ "direct.group_by_conversations": "대화별로 묶기", "endorsed_accounts_editor.endorsed_accounts": "추천하는 계정들", "favourite_modal.combo": "다음엔 {combo}를 눌러 건너뛸 수 있습니다", + "firehose.column_settings.allow_local_only": "\"모두\" 탭에서 로컬 전용 글 보여주기", "home.column_settings.advanced": "고급", "home.column_settings.filter_regex": "정규표현식으로 필터", "home.column_settings.show_direct": "DM 보여주기", diff --git a/app/javascript/flavours/glitch/locales/vi.json b/app/javascript/flavours/glitch/locales/vi.json index d360fed722..0967ef424b 100644 --- a/app/javascript/flavours/glitch/locales/vi.json +++ b/app/javascript/flavours/glitch/locales/vi.json @@ -1,4 +1 @@ -{ - "settings.content_warnings": "Content warnings", - "settings.preferences": "Preferences" -} +{} diff --git a/app/javascript/flavours/glitch/locales/zh-CN.json b/app/javascript/flavours/glitch/locales/zh-CN.json index 5a620c9346..742624108c 100644 --- a/app/javascript/flavours/glitch/locales/zh-CN.json +++ b/app/javascript/flavours/glitch/locales/zh-CN.json @@ -1,7 +1,8 @@ { - "about.fork_disclaimer": "Glitch-soc是从Mastodon派生的自由开源软件。", - "account.disclaimer_full": "以下信息可能无法完整代表你的个人资料。", + "about.fork_disclaimer": "Glitch-soc是从Mastodon生成的免费开源软件。", + "account.disclaimer_full": "下面的信息可能不完全反映用户的个人资料。", "account.follows": "正在关注", + "account.follows_you": "关注了你", "account.joined": "加入于 {date}", "account.suspended_disclaimer_full": "该用户已被管理员封禁。", "account.view_full_profile": "查看完整资料", diff --git a/app/javascript/flavours/glitch/locales/zh-TW.json b/app/javascript/flavours/glitch/locales/zh-TW.json index fcd4f4a3a6..414bd44f78 100644 --- a/app/javascript/flavours/glitch/locales/zh-TW.json +++ b/app/javascript/flavours/glitch/locales/zh-TW.json @@ -2,6 +2,7 @@ "about.fork_disclaimer": "Glitch-soc 是從 Mastodon 分支出來的自由開源軟體。", "account.disclaimer_full": "下面的資訊可能不完全反映使用者的個人資料。", "account.follows": "跟隨", + "account.follows_you": "跟隨了您", "account.joined": "加入於 {date}", "account.suspended_disclaimer_full": "使用者已被管理者停權。", "account.view_full_profile": "查看完整個人資料", diff --git a/app/javascript/mastodon/features/compose/containers/search_container.js b/app/javascript/mastodon/features/compose/containers/search_container.js index 758b6b07db..616b91369c 100644 --- a/app/javascript/mastodon/features/compose/containers/search_container.js +++ b/app/javascript/mastodon/features/compose/containers/search_container.js @@ -1,3 +1,4 @@ +import { createSelector } from '@reduxjs/toolkit'; import { connect } from 'react-redux'; import { @@ -12,10 +13,15 @@ import { import Search from '../components/search'; +const getRecentSearches = createSelector( + state => state.getIn(['search', 'recent']), + recent => recent.reverse(), +); + const mapStateToProps = state => ({ value: state.getIn(['search', 'value']), submitted: state.getIn(['search', 'submitted']), - recent: state.getIn(['search', 'recent']).reverse(), + recent: getRecentSearches(state), }); const mapDispatchToProps = dispatch => ({ diff --git a/app/javascript/mastodon/locales/ca.json b/app/javascript/mastodon/locales/ca.json index 290b364a52..7d1049a30f 100644 --- a/app/javascript/mastodon/locales/ca.json +++ b/app/javascript/mastodon/locales/ca.json @@ -150,7 +150,7 @@ "compose_form.poll.duration": "Durada de l'enquesta", "compose_form.poll.option_placeholder": "Opció {number}", "compose_form.poll.remove_option": "Elimina aquesta opció", - "compose_form.poll.switch_to_multiple": "Canvia l’enquesta per a permetre diverses opcions", + "compose_form.poll.switch_to_multiple": "Canvia l’enquesta per a permetre múltiples opcions", "compose_form.poll.switch_to_single": "Canvia l’enquesta per a permetre una única opció", "compose_form.publish": "Tut", "compose_form.publish_form": "Nou tut", @@ -607,7 +607,7 @@ "search.quick_action.status_search": "Tuts coincidint amb {x}", "search.search_or_paste": "Cerca o escriu l'URL", "search_popout.full_text_search_disabled_message": "No disponible a {domain}.", - "search_popout.full_text_search_logged_out_message": "Només disponible en iniciar la sessió.", + "search_popout.full_text_search_logged_out_message": "Només disponible amb la sessió iniciada.", "search_popout.language_code": "Codi de llengua ISO", "search_popout.options": "Opcions de cerca", "search_popout.quick_actions": "Accions ràpides", diff --git a/app/javascript/mastodon/locales/pt-BR.json b/app/javascript/mastodon/locales/pt-BR.json index 482cc8ee73..b8e18e1229 100644 --- a/app/javascript/mastodon/locales/pt-BR.json +++ b/app/javascript/mastodon/locales/pt-BR.json @@ -32,6 +32,7 @@ "account.featured_tags.last_status_never": "Sem publicações", "account.featured_tags.title": "Hashtags em destaque de {name}", "account.follow": "Seguir", + "account.follow_back": "Seguir de volta", "account.followers": "Seguidores", "account.followers.empty": "Nada aqui.", "account.followers_counter": "{count, plural, one {{counter} seguidor} other {{counter} seguidores}}", @@ -52,6 +53,7 @@ "account.mute_notifications_short": "Silenciar notificações", "account.mute_short": "Silenciar", "account.muted": "Silenciado", + "account.mutual": "Mútuo", "account.no_bio": "Nenhuma descrição fornecida.", "account.open_original_page": "Abrir a página original", "account.posts": "Toots", diff --git a/app/lib/annual_report.rb b/app/lib/annual_report.rb new file mode 100644 index 0000000000..cf4297f2a4 --- /dev/null +++ b/app/lib/annual_report.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +class AnnualReport + include DatabaseHelper + + SOURCES = [ + AnnualReport::Archetype, + AnnualReport::TypeDistribution, + AnnualReport::TopStatuses, + AnnualReport::MostUsedApps, + AnnualReport::CommonlyInteractedWithAccounts, + AnnualReport::TimeSeries, + AnnualReport::TopHashtags, + AnnualReport::MostRebloggedAccounts, + AnnualReport::Percentiles, + ].freeze + + SCHEMA = 1 + + def initialize(account, year) + @account = account + @year = year + end + + def generate + return if GeneratedAnnualReport.exists?(account: @account, year: @year) + + GeneratedAnnualReport.create( + account: @account, + year: @year, + schema_version: SCHEMA, + data: data + ) + end + + private + + def data + with_read_replica do + SOURCES.each_with_object({}) { |klass, hsh| hsh.merge!(klass.new(@account, @year).generate) } + end + end +end diff --git a/app/lib/annual_report/archetype.rb b/app/lib/annual_report/archetype.rb new file mode 100644 index 0000000000..ea9ef366df --- /dev/null +++ b/app/lib/annual_report/archetype.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +class AnnualReport::Archetype < AnnualReport::Source + # Average number of posts (including replies and reblogs) made by + # each active user in a single year (2023) + AVERAGE_PER_YEAR = 113 + + def generate + { + archetype: archetype, + } + end + + private + + def archetype + if (standalone_count + replies_count + reblogs_count) < AVERAGE_PER_YEAR + :lurker + elsif reblogs_count > (standalone_count * 2) + :booster + elsif polls_count > (standalone_count * 0.1) # standalone_count includes posts with polls + :pollster + elsif replies_count > (standalone_count * 2) + :replier + else + :oracle + end + end + + def polls_count + @polls_count ||= base_scope.where.not(poll_id: nil).count + end + + def reblogs_count + @reblogs_count ||= base_scope.where.not(reblog_of_id: nil).count + end + + def replies_count + @replies_count ||= base_scope.where.not(in_reply_to_id: nil).where.not(in_reply_to_account_id: @account.id).count + end + + def standalone_count + @standalone_count ||= base_scope.without_replies.without_reblogs.count + end + + def base_scope + @account.statuses.where(id: year_as_snowflake_range) + end +end diff --git a/app/lib/annual_report/commonly_interacted_with_accounts.rb b/app/lib/annual_report/commonly_interacted_with_accounts.rb new file mode 100644 index 0000000000..af5e854c22 --- /dev/null +++ b/app/lib/annual_report/commonly_interacted_with_accounts.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +class AnnualReport::CommonlyInteractedWithAccounts < AnnualReport::Source + SET_SIZE = 40 + + def generate + { + commonly_interacted_with_accounts: commonly_interacted_with_accounts.map do |(account_id, count)| + { + account_id: account_id, + count: count, + } + end, + } + end + + private + + def commonly_interacted_with_accounts + @account.statuses.reorder(nil).where(id: year_as_snowflake_range).where.not(in_reply_to_account_id: @account.id).group(:in_reply_to_account_id).having('count(*) > 1').order(total: :desc).limit(SET_SIZE).pluck(Arel.sql('in_reply_to_account_id, count(*) AS total')) + end +end diff --git a/app/lib/annual_report/most_reblogged_accounts.rb b/app/lib/annual_report/most_reblogged_accounts.rb new file mode 100644 index 0000000000..e3e8a7c90b --- /dev/null +++ b/app/lib/annual_report/most_reblogged_accounts.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +class AnnualReport::MostRebloggedAccounts < AnnualReport::Source + SET_SIZE = 10 + + def generate + { + most_reblogged_accounts: most_reblogged_accounts.map do |(account_id, count)| + { + account_id: account_id, + count: count, + } + end, + } + end + + private + + def most_reblogged_accounts + @account.statuses.reorder(nil).where(id: year_as_snowflake_range).where.not(reblog_of_id: nil).joins(reblog: :account).group('accounts.id').having('count(*) > 1').order(total: :desc).limit(SET_SIZE).pluck(Arel.sql('accounts.id, count(*) as total')) + end +end diff --git a/app/lib/annual_report/most_used_apps.rb b/app/lib/annual_report/most_used_apps.rb new file mode 100644 index 0000000000..85ff1ff86e --- /dev/null +++ b/app/lib/annual_report/most_used_apps.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +class AnnualReport::MostUsedApps < AnnualReport::Source + SET_SIZE = 10 + + def generate + { + most_used_apps: most_used_apps.map do |(name, count)| + { + name: name, + count: count, + } + end, + } + end + + private + + def most_used_apps + @account.statuses.reorder(nil).where(id: year_as_snowflake_range).joins(:application).group('oauth_applications.name').order(total: :desc).limit(SET_SIZE).pluck(Arel.sql('oauth_applications.name, count(*) as total')) + end +end diff --git a/app/lib/annual_report/percentiles.rb b/app/lib/annual_report/percentiles.rb new file mode 100644 index 0000000000..9fe4698ee5 --- /dev/null +++ b/app/lib/annual_report/percentiles.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true + +class AnnualReport::Percentiles < AnnualReport::Source + def generate + { + percentiles: { + followers: (total_with_fewer_followers / (total_with_any_followers + 1.0)) * 100, + statuses: (total_with_fewer_statuses / (total_with_any_statuses + 1.0)) * 100, + }, + } + end + + private + + def followers_gained + @followers_gained ||= @account.passive_relationships.where("date_part('year', follows.created_at) = ?", @year).count + end + + def statuses_created + @statuses_created ||= @account.statuses.where(id: year_as_snowflake_range).count + end + + def total_with_fewer_followers + @total_with_fewer_followers ||= Follow.find_by_sql([<<~SQL.squish, { year: @year, comparison: followers_gained }]).first.total + WITH tmp0 AS ( + SELECT follows.target_account_id + FROM follows + INNER JOIN accounts ON accounts.id = follows.target_account_id + WHERE date_part('year', follows.created_at) = :year + AND accounts.domain IS NULL + GROUP BY follows.target_account_id + HAVING COUNT(*) < :comparison + ) + SELECT count(*) AS total + FROM tmp0 + SQL + end + + def total_with_fewer_statuses + @total_with_fewer_statuses ||= Status.find_by_sql([<<~SQL.squish, { comparison: statuses_created, min_id: year_as_snowflake_range.first, max_id: year_as_snowflake_range.last }]).first.total + WITH tmp0 AS ( + SELECT statuses.account_id + FROM statuses + INNER JOIN accounts ON accounts.id = statuses.account_id + WHERE statuses.id BETWEEN :min_id AND :max_id + AND accounts.domain IS NULL + GROUP BY statuses.account_id + HAVING count(*) < :comparison + ) + SELECT count(*) AS total + FROM tmp0 + SQL + end + + def total_with_any_followers + @total_with_any_followers ||= Follow.where("date_part('year', follows.created_at) = ?", @year).joins(:target_account).merge(Account.local).count('distinct follows.target_account_id') + end + + def total_with_any_statuses + @total_with_any_statuses ||= Status.where(id: year_as_snowflake_range).joins(:account).merge(Account.local).count('distinct statuses.account_id') + end +end diff --git a/app/lib/annual_report/source.rb b/app/lib/annual_report/source.rb new file mode 100644 index 0000000000..1ccb622676 --- /dev/null +++ b/app/lib/annual_report/source.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +class AnnualReport::Source + attr_reader :account, :year + + def initialize(account, year) + @account = account + @year = year + end + + protected + + def year_as_snowflake_range + (Mastodon::Snowflake.id_at(DateTime.new(year, 1, 1))..Mastodon::Snowflake.id_at(DateTime.new(year, 12, 31))) + end +end diff --git a/app/lib/annual_report/time_series.rb b/app/lib/annual_report/time_series.rb new file mode 100644 index 0000000000..a144bac0d1 --- /dev/null +++ b/app/lib/annual_report/time_series.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +class AnnualReport::TimeSeries < AnnualReport::Source + def generate + { + time_series: (1..12).map do |month| + { + month: month, + statuses: statuses_per_month[month] || 0, + following: following_per_month[month] || 0, + followers: followers_per_month[month] || 0, + } + end, + } + end + + private + + def statuses_per_month + @statuses_per_month ||= @account.statuses.reorder(nil).where(id: year_as_snowflake_range).group(:period).pluck(Arel.sql("date_part('month', created_at)::int AS period, count(*)")).to_h + end + + def following_per_month + @following_per_month ||= @account.active_relationships.where("date_part('year', created_at) = ?", @year).group(:period).pluck(Arel.sql("date_part('month', created_at)::int AS period, count(*)")).to_h + end + + def followers_per_month + @followers_per_month ||= @account.passive_relationships.where("date_part('year', created_at) = ?", @year).group(:period).pluck(Arel.sql("date_part('month', created_at)::int AS period, count(*)")).to_h + end +end diff --git a/app/lib/annual_report/top_hashtags.rb b/app/lib/annual_report/top_hashtags.rb new file mode 100644 index 0000000000..488dacb1b4 --- /dev/null +++ b/app/lib/annual_report/top_hashtags.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +class AnnualReport::TopHashtags < AnnualReport::Source + SET_SIZE = 40 + + def generate + { + top_hashtags: top_hashtags.map do |(name, count)| + { + name: name, + count: count, + } + end, + } + end + + private + + def top_hashtags + Tag.joins(:statuses).where(statuses: { id: @account.statuses.where(id: year_as_snowflake_range).reorder(nil).select(:id) }).group(:id).having('count(*) > 1').order(total: :desc).limit(SET_SIZE).pluck(Arel.sql('COALESCE(tags.display_name, tags.name), count(*) AS total')) + end +end diff --git a/app/lib/annual_report/top_statuses.rb b/app/lib/annual_report/top_statuses.rb new file mode 100644 index 0000000000..112e5591ce --- /dev/null +++ b/app/lib/annual_report/top_statuses.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +class AnnualReport::TopStatuses < AnnualReport::Source + def generate + top_reblogs = base_scope.order(reblogs_count: :desc).first&.id + top_favourites = base_scope.where.not(id: top_reblogs).order(favourites_count: :desc).first&.id + top_replies = base_scope.where.not(id: [top_reblogs, top_favourites]).order(replies_count: :desc).first&.id + + { + top_statuses: { + by_reblogs: top_reblogs, + by_favourites: top_favourites, + by_replies: top_replies, + }, + } + end + + def base_scope + @account.statuses.with_public_visibility.joins(:status_stat).where(id: year_as_snowflake_range).reorder(nil) + end +end diff --git a/app/lib/annual_report/type_distribution.rb b/app/lib/annual_report/type_distribution.rb new file mode 100644 index 0000000000..fc12a6f1f4 --- /dev/null +++ b/app/lib/annual_report/type_distribution.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +class AnnualReport::TypeDistribution < AnnualReport::Source + def generate + { + type_distribution: { + total: base_scope.count, + reblogs: base_scope.where.not(reblog_of_id: nil).count, + replies: base_scope.where.not(in_reply_to_id: nil).where.not(in_reply_to_account_id: @account.id).count, + standalone: base_scope.without_replies.without_reblogs.count, + }, + } + end + + private + + def base_scope + @account.statuses.where(id: year_as_snowflake_range) + end +end diff --git a/app/lib/vacuum/media_attachments_vacuum.rb b/app/lib/vacuum/media_attachments_vacuum.rb index ab7ea4092f..e558195290 100644 --- a/app/lib/vacuum/media_attachments_vacuum.rb +++ b/app/lib/vacuum/media_attachments_vacuum.rb @@ -27,11 +27,17 @@ class Vacuum::MediaAttachmentsVacuum end def media_attachments_past_retention_period - MediaAttachment.remote.cached.where(MediaAttachment.arel_table[:created_at].lt(@retention_period.ago)).where(MediaAttachment.arel_table[:updated_at].lt(@retention_period.ago)) + MediaAttachment + .remote + .cached + .created_before(@retention_period.ago) + .updated_before(@retention_period.ago) end def orphaned_media_attachments - MediaAttachment.unattached.where(MediaAttachment.arel_table[:created_at].lt(TTL.ago)) + MediaAttachment + .unattached + .created_before(TTL.ago) end def retention_period? diff --git a/app/models/account_suggestions.rb b/app/models/account_suggestions.rb index d62176c7ca..25c8b04d50 100644 --- a/app/models/account_suggestions.rb +++ b/app/models/account_suggestions.rb @@ -29,7 +29,7 @@ class AccountSuggestions # a complicated query on this end. account_ids = account_ids_with_sources[offset, limit] - accounts_map = Account.where(id: account_ids.map(&:first)).includes(:account_stat).index_by(&:id) + accounts_map = Account.where(id: account_ids.map(&:first)).includes(:account_stat, :user).index_by(&:id) account_ids.filter_map do |(account_id, source)| next unless accounts_map.key?(account_id) diff --git a/app/models/account_summary.rb b/app/models/account_summary.rb index 0d8835b83c..2a21d09a8b 100644 --- a/app/models/account_summary.rb +++ b/app/models/account_summary.rb @@ -12,9 +12,11 @@ class AccountSummary < ApplicationRecord self.primary_key = :account_id + has_many :follow_recommendation_suppressions, primary_key: :account_id, foreign_key: :account_id, inverse_of: false + scope :safe, -> { where(sensitive: false) } scope :localized, ->(locale) { where(language: locale) } - scope :filtered, -> { joins(arel_table.join(FollowRecommendationSuppression.arel_table, Arel::Nodes::OuterJoin).on(arel_table[:account_id].eq(FollowRecommendationSuppression.arel_table[:account_id])).join_sources).where(FollowRecommendationSuppression.arel_table[:id].eq(nil)) } + scope :filtered, -> { where.missing(:follow_recommendation_suppressions) } def self.refresh Scenic.database.refresh_materialized_view(table_name, concurrently: false, cascade: false) diff --git a/app/models/generated_annual_report.rb b/app/models/generated_annual_report.rb new file mode 100644 index 0000000000..43c97d7108 --- /dev/null +++ b/app/models/generated_annual_report.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +# == Schema Information +# +# Table name: generated_annual_reports +# +# id :bigint(8) not null, primary key +# account_id :bigint(8) not null +# year :integer not null +# data :jsonb not null +# schema_version :integer not null +# viewed_at :datetime +# created_at :datetime not null +# updated_at :datetime not null +# + +class GeneratedAnnualReport < ApplicationRecord + belongs_to :account + + scope :pending, -> { where(viewed_at: nil) } + + def viewed? + viewed_at.present? + end + + def view! + update!(viewed_at: Time.now.utc) + end + + def account_ids + data['most_reblogged_accounts'].pluck('account_id') + data['commonly_interacted_with_accounts'].pluck('account_id') + end + + def status_ids + data['top_statuses'].values + end +end diff --git a/app/models/media_attachment.rb b/app/models/media_attachment.rb index dea9409256..d50d4a7b42 100644 --- a/app/models/media_attachment.rb +++ b/app/models/media_attachment.rb @@ -204,12 +204,14 @@ class MediaAttachment < ApplicationRecord validates :file, presence: true, if: :local? validates :thumbnail, absence: true, if: -> { local? && !audio_or_video? } - scope :attached, -> { where.not(status_id: nil).or(where.not(scheduled_status_id: nil)) } - scope :cached, -> { remote.where.not(file_file_name: nil) } - scope :local, -> { where(remote_url: '') } - scope :ordered, -> { order(id: :asc) } - scope :remote, -> { where.not(remote_url: '') } + scope :attached, -> { where.not(status_id: nil).or(where.not(scheduled_status_id: nil)) } + scope :cached, -> { remote.where.not(file_file_name: nil) } + scope :created_before, ->(value) { where(arel_table[:created_at].lt(value)) } + scope :local, -> { where(remote_url: '') } + scope :ordered, -> { order(id: :asc) } + scope :remote, -> { where.not(remote_url: '') } scope :unattached, -> { where(status_id: nil, scheduled_status_id: nil) } + scope :updated_before, ->(value) { where(arel_table[:updated_at].lt(value)) } attr_accessor :skip_download diff --git a/app/models/poll.rb b/app/models/poll.rb index 37149c3d86..cc4184f80a 100644 --- a/app/models/poll.rb +++ b/app/models/poll.rb @@ -57,7 +57,7 @@ class Poll < ApplicationRecord end def voted?(account) - account.id == account_id || votes.where(account: account).exists? + account.id == account_id || votes.exists?(account: account) end def own_votes(account) diff --git a/app/models/report.rb b/app/models/report.rb index 126701b3d6..38da26d7b7 100644 --- a/app/models/report.rb +++ b/app/models/report.rb @@ -41,7 +41,7 @@ class Report < ApplicationRecord scope :unresolved, -> { where(action_taken_at: nil) } scope :resolved, -> { where.not(action_taken_at: nil) } - scope :with_accounts, -> { includes([:account, :target_account, :action_taken_by_account, :assigned_account].index_with({ user: [:invite_request, :invite] })) } + scope :with_accounts, -> { includes([:account, :target_account, :action_taken_by_account, :assigned_account].index_with([:account_stat, { user: [:invite_request, :invite, :ips] }])) } # A report is considered local if the reporter is local delegate :local?, to: :account diff --git a/app/models/session_activation.rb b/app/models/session_activation.rb index 7f5f0d9a9a..c67180d3ba 100644 --- a/app/models/session_activation.rb +++ b/app/models/session_activation.rb @@ -41,7 +41,7 @@ class SessionActivation < ApplicationRecord class << self def active?(id) - id && where(session_id: id).exists? + id && exists?(session_id: id) end def activate(**options) diff --git a/app/models/status.rb b/app/models/status.rb index 676e9732b5..33998a2b87 100644 --- a/app/models/status.rb +++ b/app/models/status.rb @@ -271,7 +271,7 @@ class Status < ApplicationRecord end def reported? - @reported ||= Report.where(target_account: account).unresolved.where('? = ANY(status_ids)', id).exists? + @reported ||= Report.where(target_account: account).unresolved.exists?(['? = ANY(status_ids)', id]) end def emojis diff --git a/app/models/tag.rb b/app/models/tag.rb index 46e55d74f9..f2168ae904 100644 --- a/app/models/tag.rb +++ b/app/models/tag.rb @@ -39,6 +39,8 @@ class Tag < ApplicationRecord HASHTAG_NAME_RE = /\A(#{HASHTAG_NAME_PAT})\z/i HASHTAG_INVALID_CHARS_RE = /[^[:alnum:]\u0E47-\u0E4E#{HASHTAG_SEPARATORS}]/ + RECENT_STATUS_LIMIT = 1000 + validates :name, presence: true, format: { with: HASHTAG_NAME_RE } validates :display_name, format: { with: HASHTAG_NAME_RE } validate :validate_name_change, if: -> { !new_record? && name_changed? } @@ -53,7 +55,7 @@ class Tag < ApplicationRecord scope :not_trendable, -> { where(trendable: false) } scope :recently_used, lambda { |account| joins(:statuses) - .where(statuses: { id: account.statuses.select(:id).limit(1000) }) + .where(statuses: { id: account.statuses.select(:id).limit(RECENT_STATUS_LIMIT) }) .group(:id).order(Arel.sql('count(*) desc')) } scope :matches_name, ->(term) { where(arel_table[:name].lower.matches(arel_table.lower("#{sanitize_sql_like(Tag.normalize(term))}%"), nil, true)) } # Search with case-sensitive to use B-tree index diff --git a/app/presenters/annual_reports_presenter.rb b/app/presenters/annual_reports_presenter.rb new file mode 100644 index 0000000000..001e1d37b0 --- /dev/null +++ b/app/presenters/annual_reports_presenter.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +class AnnualReportsPresenter + alias read_attribute_for_serialization send + + attr_reader :annual_reports + + def initialize(annual_reports) + @annual_reports = annual_reports + end + + def accounts + @accounts ||= Account.where(id: @annual_reports.flat_map(&:account_ids)).includes(:account_stat, :moved_to_account, user: :role) + end + + def statuses + @statuses ||= Status.where(id: @annual_reports.flat_map(&:status_ids)).with_includes + end + + def self.model_name + @model_name ||= ActiveModel::Name.new(self) + end +end diff --git a/app/serializers/rest/annual_report_serializer.rb b/app/serializers/rest/annual_report_serializer.rb new file mode 100644 index 0000000000..1fb5ddb5c1 --- /dev/null +++ b/app/serializers/rest/annual_report_serializer.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +class REST::AnnualReportSerializer < ActiveModel::Serializer + attributes :year, :data, :schema_version +end diff --git a/app/serializers/rest/annual_reports_serializer.rb b/app/serializers/rest/annual_reports_serializer.rb new file mode 100644 index 0000000000..ea9572be1b --- /dev/null +++ b/app/serializers/rest/annual_reports_serializer.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class REST::AnnualReportsSerializer < ActiveModel::Serializer + has_many :annual_reports, serializer: REST::AnnualReportSerializer + has_many :accounts, serializer: REST::AccountSerializer + has_many :statuses, serializer: REST::StatusSerializer +end diff --git a/app/services/activitypub/fetch_remote_status_service.rb b/app/services/activitypub/fetch_remote_status_service.rb index a491b32b26..e3a9b60b56 100644 --- a/app/services/activitypub/fetch_remote_status_service.rb +++ b/app/services/activitypub/fetch_remote_status_service.rb @@ -44,7 +44,7 @@ class ActivityPub::FetchRemoteStatusService < BaseService # If we fetched a status that already exists, then we need to treat the # activity as an update rather than create - activity_json['type'] = 'Update' if equals_or_includes_any?(activity_json['type'], %w(Create)) && Status.where(uri: object_uri, account_id: actor.id).exists? + activity_json['type'] = 'Update' if equals_or_includes_any?(activity_json['type'], %w(Create)) && Status.exists?(uri: object_uri, account_id: actor.id) with_redis do |redis| discoveries = redis.incr("status_discovery_per_request:#{@request_id}") diff --git a/app/services/vote_service.rb b/app/services/vote_service.rb index 3e92a1690a..878350388b 100644 --- a/app/services/vote_service.rb +++ b/app/services/vote_service.rb @@ -19,7 +19,7 @@ class VoteService < BaseService already_voted = true with_redis_lock("vote:#{@poll.id}:#{@account.id}") do - already_voted = @poll.votes.where(account: @account).exists? + already_voted = @poll.votes.exists?(account: @account) ApplicationRecord.transaction do @choices.each do |choice| diff --git a/app/validators/reaction_validator.rb b/app/validators/reaction_validator.rb index 4ed3376e8b..89d83de5a2 100644 --- a/app/validators/reaction_validator.rb +++ b/app/validators/reaction_validator.rb @@ -19,7 +19,7 @@ class ReactionValidator < ActiveModel::Validator end def new_reaction?(reaction) - !reaction.announcement.announcement_reactions.where(name: reaction.name).exists? + !reaction.announcement.announcement_reactions.exists?(name: reaction.name) end def limit_reached?(reaction) diff --git a/app/validators/vote_validator.rb b/app/validators/vote_validator.rb index fa2bd223dc..e725b4c0b8 100644 --- a/app/validators/vote_validator.rb +++ b/app/validators/vote_validator.rb @@ -35,7 +35,7 @@ class VoteValidator < ActiveModel::Validator if vote.persisted? account_votes_on_same_poll(vote).where(choice: vote.choice).where.not(poll_votes: { id: vote }).exists? else - account_votes_on_same_poll(vote).where(choice: vote.choice).exists? + account_votes_on_same_poll(vote).exists?(choice: vote.choice) end end diff --git a/app/workers/generate_annual_report_worker.rb b/app/workers/generate_annual_report_worker.rb new file mode 100644 index 0000000000..7094c1ab9c --- /dev/null +++ b/app/workers/generate_annual_report_worker.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +class GenerateAnnualReportWorker + include Sidekiq::Worker + + def perform(account_id, year) + AnnualReport.new(Account.find(account_id), year).generate + rescue ActiveRecord::RecordNotFound, ActiveRecord::RecordNotUnique + true + end +end diff --git a/app/workers/scheduler/indexing_scheduler.rb b/app/workers/scheduler/indexing_scheduler.rb index 5c985e25a0..f52d0141d4 100644 --- a/app/workers/scheduler/indexing_scheduler.rb +++ b/app/workers/scheduler/indexing_scheduler.rb @@ -24,6 +24,8 @@ class Scheduler::IndexingScheduler end end + private + def indexes [AccountsIndex, TagsIndex, PublicStatusesIndex, StatusesIndex] end diff --git a/config/locales-glitch/fr-CA.yml b/config/locales-glitch/fr-CA.yml new file mode 100644 index 0000000000..2fbf0ffd71 --- /dev/null +++ b/config/locales-glitch/fr-CA.yml @@ -0,0 +1 @@ +--- {} diff --git a/config/locales-glitch/simple_form.fr-CA.yml b/config/locales-glitch/simple_form.fr-CA.yml new file mode 100644 index 0000000000..2fbf0ffd71 --- /dev/null +++ b/config/locales-glitch/simple_form.fr-CA.yml @@ -0,0 +1 @@ +--- {} diff --git a/config/locales/ar.yml b/config/locales/ar.yml index 3c8c643fe7..e6d653c674 100644 --- a/config/locales/ar.yml +++ b/config/locales/ar.yml @@ -1934,6 +1934,7 @@ ar: go_to_sso_account_settings: انتقل إلى إعدادات حساب مزود الهوية الخاص بك invalid_otp_token: رمز المصادقة بخطوتين غير صالح otp_lost_help_html: إن فقدتَهُما ، يمكنك الاتصال بـ %{email} + rate_limited: عدد محاولات التحقق كثير جدًا، يرجى المحاولة مرة أخرى لاحقًا. seamless_external_login: لقد قمت بتسجيل الدخول عبر خدمة خارجية، إنّ إعدادات الكلمة السرية و البريد الإلكتروني غير متوفرة. signed_in_as: 'تم تسجيل دخولك بصفة:' verification: diff --git a/config/locales/bg.yml b/config/locales/bg.yml index 58a5cae2fd..b9a3135448 100644 --- a/config/locales/bg.yml +++ b/config/locales/bg.yml @@ -1790,6 +1790,12 @@ bg: extra: Вече е готово за теглене! subject: Вашият архив е готов за изтегляне title: Сваляне на архива + failed_2fa: + details: 'Ето подробности на опита за влизане:' + explanation: Някой се опита да влезе в акаунта ви, но предостави невалиден втори фактор за удостоверяване. + further_actions_html: Ако не бяхте вие, то препоръчваме да направите %{action} незабавно, тъй като може да се злепостави. + subject: Неуспешен втори фактор за удостоверяване + title: Провал на втория фактор за удостоверяване suspicious_sign_in: change_password: промяна на паролата ви details: 'Ето подробности при вход:' diff --git a/config/locales/ca.yml b/config/locales/ca.yml index 36ebb9785b..38ef976b83 100644 --- a/config/locales/ca.yml +++ b/config/locales/ca.yml @@ -425,7 +425,7 @@ ca: view: Veure el bloqueig del domini email_domain_blocks: add_new: Afegir nou - allow_registrations_with_approval: Registre permès amb validació + allow_registrations_with_approval: Permet els registres amb validació attempts_over_week: one: "%{count} intent en la darrera setmana" other: "%{count} intents de registre en la darrera setmana" @@ -1046,6 +1046,7 @@ ca: clicking_this_link: en clicar aquest enllaç login_link: inici de sessió proceed_to_login_html: Ara pots passar a %{login_link}. + redirect_to_app_html: Se us hauria d'haver redirigit a l'app %{app_name}. Si això no ha passat, intenteu %{clicking_this_link} o torneu manualment a l'app. registration_complete: La teva inscripció a %{domain} ja és completa. welcome_title: Hola, %{name}! wrong_email_hint: Si aquesta adreça de correu electrònic no és correcte, pots canviar-la en els ajustos del compte. @@ -1109,6 +1110,7 @@ ca: functional: El teu compte està completament operatiu. pending: La vostra sol·licitud està pendent de revisió pel nostre personal. Això pot trigar una mica. Rebreu un correu electrònic quan sigui aprovada. redirecting_to: El teu compte és inactiu perquè actualment està redirigint a %{acct}. + self_destruct: Com que %{domain} tanca, només tindreu accés limitat al vostre compte. view_strikes: Veure accions del passat contra el teu compte too_fast: Formulari enviat massa ràpid, torna a provar-ho. use_security_key: Usa clau de seguretat @@ -1580,6 +1582,7 @@ ca: over_total_limit: Has superat el límit de %{limit} tuts programats too_soon: La data programada ha de ser futura self_destruct: + lead_html: Lamentablement, %{domain} tanca de forma definitiva. Si hi teníeu un compte, no el podreu continuar utilitzant, però podeu demanar una còpia de les vostres dades. title: Aquest servidor tancarà sessions: activity: Última activitat @@ -1784,9 +1787,15 @@ ca: title: Apel·lació rebutjada backup_ready: explanation: Heu demanat una còpia completa de les dades del vostre compte de Mastodon. - extra: Ja us ho podeu baixar + extra: Ja la podeu baixar subject: L'arxiu està preparat per a descàrrega title: Recollida de l'arxiu + failed_2fa: + details: 'Aquests són els detalls de l''intent d''accés:' + explanation: Algú ha intentat accedir al vostre compte però no ha proporcionat un factor de doble autenticació correcte. + further_actions_html: Si no heu estat vosaltres, us recomanem que %{action} immediatament perquè pot estar compromès. + subject: Ha fallat el factor de doble autenticació + title: Ha fallat l'autenticació de doble factor suspicious_sign_in: change_password: canvia la teva contrasenya details: 'Aquest són els detalls de l''inici de sessió:' @@ -1840,7 +1849,7 @@ ca: go_to_sso_account_settings: Ves a la configuració del compte del teu proveïdor d'identitat invalid_otp_token: El codi de dos factors no és correcte otp_lost_help_html: Si has perdut l'accés a tots dos pots contactar per %{email} - rate_limited: Excessius intents d'autenticació, torneu-ho a provar més tard. + rate_limited: Excessius intents d'autenticació, torneu-hi més tard. seamless_external_login: Has iniciat sessió via un servei extern per tant els ajustos de contrasenya i correu electrònic no estan disponibles. signed_in_as: 'Sessió iniciada com a:' verification: diff --git a/config/locales/da.yml b/config/locales/da.yml index 58fd723aef..d92d001905 100644 --- a/config/locales/da.yml +++ b/config/locales/da.yml @@ -1790,6 +1790,12 @@ da: extra: Sikkerhedskopien kan nu downloades! subject: Dit arkiv er klar til download title: Arkiv download + failed_2fa: + details: 'Her er detaljerne om login-forsøget:' + explanation: Nogen har forsøgt at logge ind på kontoen, men har angivet en ugyldig anden godkendelsesfaktor. + further_actions_html: Var dette ikke dig, anbefales det straks at %{action}, da den kan være kompromitteret. + subject: Anden faktor godkendelsesfejl + title: Fejlede på anden faktor godkendelse suspicious_sign_in: change_password: ændrer din adgangskode details: 'Her er nogle detaljer om login-forsøget:' diff --git a/config/locales/de.yml b/config/locales/de.yml index e177c6d2d1..9568f698d1 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -1790,8 +1790,14 @@ de: extra: Sie ist jetzt zum Herunterladen bereit! subject: Dein persönliches Archiv kann heruntergeladen werden title: Archiv-Download + failed_2fa: + details: 'Details zum Anmeldeversuch:' + explanation: Jemand hat versucht, sich bei deinem Konto anzumelden, aber die Zwei-Faktor-Authentisierung schlug fehl. + further_actions_html: Solltest du das nicht gewesen sein, empfehlen wir dir, sofort %{action}, da dein Konto möglicherweise kompromittiert ist. + subject: Zwei-Faktor-Authentisierung fehlgeschlagen + title: Zwei-Faktor-Authentisierung fehlgeschlagen suspicious_sign_in: - change_password: dein Passwort ändern + change_password: dein Passwort zu ändern details: 'Hier sind die Details zu den Anmeldeversuchen:' explanation: Wir haben eine Anmeldung zu deinem Konto von einer neuen IP-Adresse festgestellt. further_actions_html: Wenn du das nicht warst, empfehlen wir dir schnellstmöglich, %{action} und die Zwei-Faktor-Authentisierung (2FA) für dein Konto zu aktivieren, um es abzusichern. diff --git a/config/locales/devise.ca.yml b/config/locales/devise.ca.yml index 2bf741ee40..3720d3c5f7 100644 --- a/config/locales/devise.ca.yml +++ b/config/locales/devise.ca.yml @@ -49,19 +49,19 @@ ca: subject: 'Mastodon: Instruccions per a reiniciar contrasenya' title: Contrasenya restablerta two_factor_disabled: - explanation: Només es pot accedir amb compte de correu i contrasenya. + explanation: Ara es pot accedir amb només compte de correu i contrasenya. subject: 'Mastodon: Autenticació de doble factor desactivada' subtitle: S'ha deshabilitat l'autenticació de doble factor al vostre compte. title: A2F desactivada two_factor_enabled: - explanation: Per accedir fa falta un token generat per l'aplicació TOTP aparellada. + explanation: Per accedir cal un token generat per l'aplicació TOTP aparellada. subject: 'Mastodon: Autenticació de doble factor activada' subtitle: S'ha habilitat l'autenticació de doble factor al vostre compte. title: A2F activada two_factor_recovery_codes_changed: explanation: Els codis de recuperació anteriors ja no són vàlids i se n'han generat de nous. subject: 'Mastodon: codis de recuperació de doble factor regenerats' - subtitle: S'han invalidat els codis de recuperació anteriors i se n'ha generat de nous. + subtitle: S'han invalidat els codis de recuperació anteriors i se n'han generat de nous. title: Codis de recuperació A2F canviats unlock_instructions: subject: 'Mastodon: Instruccions per a desblocar' @@ -76,7 +76,7 @@ ca: title: Una de les teves claus de seguretat ha estat esborrada webauthn_disabled: explanation: S'ha deshabilitat l'autenticació amb claus de seguretat al vostre compte. - extra: Ara només podeu accedir amb el token generat amb l'aplicació TOTP aparellada. + extra: Ara es pot accedir amb només el token generat amb l'aplicació TOTP aparellada. subject: 'Mastodon: S''ha desactivat l''autenticació amb claus de seguretat' title: Claus de seguretat desactivades webauthn_enabled: diff --git a/config/locales/devise.ru.yml b/config/locales/devise.ru.yml index ccbd13438d..9dd418f2cd 100644 --- a/config/locales/devise.ru.yml +++ b/config/locales/devise.ru.yml @@ -47,14 +47,19 @@ ru: subject: 'Mastodon: Инструкция по сбросу пароля' title: Сброс пароля two_factor_disabled: + explanation: Вход в систему теперь возможен только с использованием адреса электронной почты и пароля. subject: 'Mastodon: Двухфакторная авторизация отключена' + subtitle: Двухфакторная аутентификация для вашей учетной записи была отключена. title: 2ФА отключена two_factor_enabled: + explanation: Для входа в систему потребуется токен, сгенерированный сопряженным приложением TOTP. subject: 'Mastodon: Настроена двухфакторная авторизация' + subtitle: Для вашей учетной записи была включена двухфакторная аутентификация. title: 2ФА включена two_factor_recovery_codes_changed: explanation: Предыдущие резервные коды были аннулированы и созданы новые. subject: 'Mastodon: Резервные коды двуфакторной авторизации обновлены' + subtitle: Предыдущие коды восстановления были аннулированы и сгенерированы новые. title: Коды восстановления 2FA изменены unlock_instructions: subject: 'Mastodon: Инструкция по разблокировке' @@ -68,9 +73,13 @@ ru: subject: 'Мастодон: Ключ Безопасности удален' title: Один из ваших защитных ключей был удален webauthn_disabled: + explanation: Аутентификация с помощью ключей безопасности была отключена для вашей учетной записи. + extra: Теперь вход в систему возможен только с использованием токена, сгенерированного сопряженным приложением TOTP. subject: 'Мастодон: Аутентификация с ключами безопасности отключена' title: Ключи безопасности отключены webauthn_enabled: + explanation: Для вашей учетной записи включена аутентификация по ключу безопасности. + extra: Теперь ваш ключ безопасности можно использовать для входа в систему. subject: 'Мастодон: Включена аутентификация по ключу безопасности' title: Ключи безопасности включены omniauth_callbacks: diff --git a/config/locales/devise.sq.yml b/config/locales/devise.sq.yml index 7cea2f8e2e..32136a0baa 100644 --- a/config/locales/devise.sq.yml +++ b/config/locales/devise.sq.yml @@ -47,14 +47,19 @@ sq: subject: 'Mastodon: Udhëzime ricaktimi fjalëkalimi' title: Ricaktim fjalëkalimi two_factor_disabled: + explanation: Hyrja tanimë është e mundshme duke përdorur vetëm adresë email dhe fjalëkalim. subject: 'Mastodon: U çaktivizua mirëfilltësimi dyfaktorësh' + subtitle: Mirëfilltësimi dyfaktorësh për llogarinë tuaj është çaktivizuar. title: 2FA u çaktivizua two_factor_enabled: + explanation: Për të kryer hyrjen do të kërkohet doemos një token i prodhuar nga aplikacioni TOTP i çiftuar. subject: 'Mastodon: U aktivizua mirëfilltësimi dyfaktorësh' + subtitle: Për llogarinë tuaj është aktivizuar mirëfilltësmi dyfaktorësh. title: 2FA u aktivizua two_factor_recovery_codes_changed: explanation: Kodet e dikurshëm të rikthimit janë bërë të pavlefshëm dhe janë prodhuar të rinj. subject: 'Mastodon: U riprodhuan kode rikthimi dyfaktorësh' + subtitle: Kodet e dikurshëm të rikthimit janë bërë të pavlefshëm dhe janë prodhuar të rinj. title: Kodet e rikthimit 2FA u ndryshuan unlock_instructions: subject: 'Mastodon: Udhëzime shkyçjeje' @@ -68,9 +73,13 @@ sq: subject: 'Mastodon: Fshirje kyçi sigurie' title: Një nga kyçet tuaj të sigurisë është fshirë webauthn_disabled: + explanation: Mirëfilltësimi me kyçe sigurie është çaktivizuar për llogarinë tuaj. + extra: Hyrjet tani janë të mundshme vetëm duke përdorur token-in e prodhuar nga aplikacioni TOTP i çiftuar. subject: 'Mastodon: U çaktivizua mirëfilltësimi me kyçe sigurie' title: U çaktivizuan kyçe sigurie webauthn_enabled: + explanation: Mirëfilltësimi me kyçe sigurie është aktivizuar për këtë llogari. + extra: Kyçi juaj i sigurisë tanimë mund të përdoret për hyrje. subject: 'Mastodon: U aktivizua mirëfilltësim me kyçe sigurie' title: U aktivizuan kyçe sigurie omniauth_callbacks: diff --git a/config/locales/es-AR.yml b/config/locales/es-AR.yml index 0b6e58db59..cc55d3d3ff 100644 --- a/config/locales/es-AR.yml +++ b/config/locales/es-AR.yml @@ -1790,6 +1790,12 @@ es-AR: extra: "¡Ya está lista para descargar!" subject: Tu archivo historial está listo para descargar title: Descargar archivo historial + failed_2fa: + details: 'Estos son los detalles del intento de inicio de sesión:' + explanation: Alguien intentó iniciar sesión en tu cuenta pero proporcionó un segundo factor de autenticación no válido. + further_actions_html: Si vos no fuiste, te recomendamos que %{action} inmediatamente, ya que la seguridad de tu cuenta podría estar comprometida. + subject: Fallo de autenticación del segundo factor + title: Fallo en la autenticación del segundo factor suspicious_sign_in: change_password: cambiés tu contraseña details: 'Acá están los detalles del inicio de sesión:' diff --git a/config/locales/es-MX.yml b/config/locales/es-MX.yml index 11c327bcca..b84fb7cf96 100644 --- a/config/locales/es-MX.yml +++ b/config/locales/es-MX.yml @@ -1790,6 +1790,12 @@ es-MX: extra: "¡Ya está listo para descargar!" subject: Tu archivo está preparado para descargar title: Descargar archivo + failed_2fa: + details: 'Estos son los detalles del intento de inicio de sesión:' + explanation: Alguien ha intentado iniciar sesión en tu cuenta pero proporcionó un segundo factor de autenticación inválido. + further_actions_html: Si no fuiste tú, se recomienda %{action} inmediatamente ya que puede estar comprometido. + subject: Fallo de autenticación de segundo factor + title: Falló la autenticación de segundo factor suspicious_sign_in: change_password: cambies tu contraseña details: 'Aquí están los detalles del inicio de sesión:' diff --git a/config/locales/es.yml b/config/locales/es.yml index 4dbb76c526..95816d6bcb 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -1790,6 +1790,12 @@ es: extra: "¡Ya está listo para descargar!" subject: Tu archivo está preparado para descargar title: Descargar archivo + failed_2fa: + details: 'Estos son los detalles del intento de inicio de sesión:' + explanation: Alguien ha intentado iniciar sesión en tu cuenta pero proporcionó un segundo factor de autenticación inválido. + further_actions_html: Si no fuiste tú, se recomienda %{action} inmediatamente ya que puede estar comprometida. + subject: Fallo de autenticación del segundo factor + title: Fallo en la autenticación del segundo factor suspicious_sign_in: change_password: cambies tu contraseña details: 'Aquí están los detalles del inicio de sesión:' diff --git a/config/locales/et.yml b/config/locales/et.yml index 71f49e1abb..f82ee6cb8f 100644 --- a/config/locales/et.yml +++ b/config/locales/et.yml @@ -1792,6 +1792,12 @@ et: extra: See on nüüd allalaadimiseks valmis! subject: Arhiiv on allalaadimiseks valmis title: Arhiivi väljavõte + failed_2fa: + details: 'Sisenemise üksikasjad:' + explanation: Keegi püüdis Su kontole siseneda, ent sisestas vale teisese autentimisfaktori. + further_actions_html: Kui see polnud Sina, siis soovitame viivitamata %{action}, kuna see võib olla lekkinud. + subject: Kaheastmelise autentimise nurjumine + title: Kaheastmeline autentimine nurjus suspicious_sign_in: change_password: muuta oma salasõna details: 'Sisenemise üksikasjad:' @@ -1848,6 +1854,7 @@ et: go_to_sso_account_settings: Mine oma idenditeedipakkuja kontosätetesse invalid_otp_token: Vale kaheastmeline võti otp_lost_help_html: Kui kaotasid ligipääsu mõlemale, saad võtta ühendust %{email}-iga + rate_limited: Liiga palju autentimise katseid, proovi hiljem uuesti. seamless_external_login: Välise teenuse kaudu sisse logides pole salasõna ja e-posti sätted saadaval. signed_in_as: 'Sisse logitud kasutajana:' verification: diff --git a/config/locales/eu.yml b/config/locales/eu.yml index bfa1f829b6..bd6ea8c832 100644 --- a/config/locales/eu.yml +++ b/config/locales/eu.yml @@ -1794,6 +1794,12 @@ eu: extra: Deskargatzeko prest! subject: Zure artxiboa deskargatzeko prest dago title: Artxiboa jasotzea + failed_2fa: + details: 'Hemen dituzu saio-hasieraren saiakeraren xehetasunak:' + explanation: Norbait zure kontuan saioa hasten saiatu da, baina bigarren autentifikazioaren faktore baliogabea eman du. + further_actions_html: Ez bazara zu izan, "%{action}" ekintza berehala egitea gomendatzen dugu, kontua arriskarazi daiteke eta. + subject: Autentifikazioaren bigarren faktoreak huts egin du + title: Huts egin duen autentifikazioaren bigarren faktorea suspicious_sign_in: change_password: aldatu pasahitza details: 'Hemen daude saio hasieraren xehetasunak:' diff --git a/config/locales/fi.yml b/config/locales/fi.yml index 9d8974392f..8e61c7b2a0 100644 --- a/config/locales/fi.yml +++ b/config/locales/fi.yml @@ -1790,6 +1790,12 @@ fi: extra: Se on nyt valmis ladattavaksi! subject: Arkisto on valmiina ladattavaksi title: Arkiston tallennus + failed_2fa: + details: 'Tässä on tiedot kirjautumisyrityksestä:' + explanation: Joku on yrittänyt kirjautua tilillesi, mutta antanut virheellisen kaksivaiheisen todennuksen. + further_actions_html: Jos se et ollut sinä, suosittelemme, että %{action} välittömästi, sillä se on saattanut vaarantua. + subject: Kaksivaiheisen todennuksen virhe + title: Epäonnistunut kaksivaiheinen todennus suspicious_sign_in: change_password: vaihda salasanasi details: 'Tässä on tiedot kirjautumisesta:' diff --git a/config/locales/fo.yml b/config/locales/fo.yml index dabaf24ba7..8e34265313 100644 --- a/config/locales/fo.yml +++ b/config/locales/fo.yml @@ -1790,6 +1790,12 @@ fo: extra: Tað er nú klárt at taka niður! subject: Savnið hjá tær er tøkt at taka niður title: Tak savn niður + failed_2fa: + details: 'Her eru smálutirnir í innritanarroyndini:' + explanation: Onkur hevur roynt at rita inn á tína kontu, men gav eitt ógildugt seinna samgildi. + further_actions_html: Um hetta ikki var tú, so skjóta vit upp, at tú %{action} beinan vegin, tí tað kann vera sett í vanda. + subject: Seinna samgildi miseydnaðist + title: Miseydnað seinna samgildi suspicious_sign_in: change_password: broyt loyniorðið hjá tær details: 'Her eru smálutirnir í innritanini:' diff --git a/config/locales/fy.yml b/config/locales/fy.yml index 1d648f4790..c59ad72725 100644 --- a/config/locales/fy.yml +++ b/config/locales/fy.yml @@ -1790,6 +1790,12 @@ fy: extra: It stiet no klear om download te wurden! subject: Jo argyf stiet klear om download te wurden title: Argyf ophelje + failed_2fa: + details: 'Hjir binne de details fan de oanmeldbesykjen:' + explanation: Ien hat probearre om oan te melden op jo account, mar hat in ûnjildige twaddeferifikaasjefaktor opjûn. + further_actions_html: As jo dit net wiene, rekommandearje wy jo oan daliks %{action}, omdat it kompromitearre wêze kin. + subject: Twaddefaktorautentikaasjeflater + title: Twastapsferifikaasje mislearre suspicious_sign_in: change_password: wizigje jo wachtwurd details: 'Hjir binne de details fan oanmeldbesykjen:' @@ -1843,6 +1849,7 @@ fy: go_to_sso_account_settings: Gean nei de accountynstellingen fan jo identiteitsprovider invalid_otp_token: Unjildige twa-stapstagongskoade otp_lost_help_html: As jo tagong ta beide kwytrekke binne, nim dan kontakt op fia %{email} + rate_limited: Te folle autentikaasjebesykjen, probearje it letter opnij. seamless_external_login: Jo binne oanmeld fia in eksterne tsjinst, dêrom binne wachtwurden en e-mailynstellingen net beskikber. signed_in_as: 'Oanmeld as:' verification: diff --git a/config/locales/gl.yml b/config/locales/gl.yml index 3c43a4e23d..087ed2ec76 100644 --- a/config/locales/gl.yml +++ b/config/locales/gl.yml @@ -1790,6 +1790,12 @@ gl: extra: Está preparada para descargala! subject: O teu ficheiro xa está preparado para descargar title: Leve o ficheiro + failed_2fa: + details: 'Detalles do intento de acceso:' + explanation: Alguén intentou acceder á túa conta mais fíxoo cun segundo factor de autenticación non válido. + further_actions_html: Se non foches ti, recomendámosche %{action} inmediatamente xa que a conta podería estar en risco. + subject: Fallo co segundo factor de autenticación + title: Fallou o segundo factor de autenticación suspicious_sign_in: change_password: cambia o teu contrasinal details: 'Estos son os detalles do acceso:' diff --git a/config/locales/he.yml b/config/locales/he.yml index db57912d89..1f5fd096ac 100644 --- a/config/locales/he.yml +++ b/config/locales/he.yml @@ -1854,6 +1854,12 @@ he: extra: הגיבוי מוכן להורדה! subject: הארכיון שלך מוכן להורדה title: הוצאת ארכיון + failed_2fa: + details: 'הנה פרטי נסיון ההתחברות:' + explanation: פולני אלמוני ניסה להתחבר לחשבונך אך האימות המשני נכשל. + further_actions_html: אם הנסיון לא היה שלך, אנו ממליצים על %{action} באופן מיידי כדי שהחשבון לא יפול קורבן. + subject: נכשל אימות בגורם שני + title: אימות בגורם שני נכשל suspicious_sign_in: change_password: שינוי הסיסמא שלך details: 'הנה פרטי ההתחברות:' diff --git a/config/locales/hu.yml b/config/locales/hu.yml index 8fce206e9e..2870435ea7 100644 --- a/config/locales/hu.yml +++ b/config/locales/hu.yml @@ -1790,6 +1790,12 @@ hu: extra: Már letöltésre kész! subject: Az adataidról készült archív letöltésre kész title: Archiválás + failed_2fa: + details: 'Itt vannak a bejelentkezési kísérlet részletei:' + explanation: Valaki megpróbált bejelentkezni a fiókodba, de a második hitelesítési lépése érvénytelen volt. + further_actions_html: Ha ez nem te voltál, azt javasoljuk, hogy azonnal %{action}, mivel lehetséges, hogy az rossz kezekbe került. + subject: Második körös hitelesítés sikertelen + title: Sikertelen a második körös hitelesítés suspicious_sign_in: change_password: módosítsd a jelszavad details: 'Itt vannak a bejelentkezés részletei:' diff --git a/config/locales/is.yml b/config/locales/is.yml index b048d5cb00..191383f56c 100644 --- a/config/locales/is.yml +++ b/config/locales/is.yml @@ -1794,6 +1794,12 @@ is: extra: Það er núna tilbúið til niðurhals! subject: Safnskráin þín er tilbúin til niðurhals title: Taka út í safnskrá + failed_2fa: + details: 'Hér eru nánari upplýsingar um innskráningartilraunina:' + explanation: Einhver reyndi að skrá sig inn á aðganginn þinn en gaf upp ógild gögn seinna þrepi auðkenningar. + further_actions_html: Ef þetta varst ekki þú, þá mælum við eindregið með því að þú %{action} samstundis, þar sem það gæti verið berskjaldað. + subject: Bilun í seinna þrepi auðkenningar + title: Seinna þrep auðkenningar brást suspicious_sign_in: change_password: breytir lykilorðinu þínu details: 'Hér eru nánari upplýsingar um innskráninguna:' diff --git a/config/locales/it.yml b/config/locales/it.yml index adcef9559f..89ff071f36 100644 --- a/config/locales/it.yml +++ b/config/locales/it.yml @@ -1792,6 +1792,12 @@ it: extra: Ora è pronto per il download! subject: Il tuo archivio è pronto per essere scaricato title: Esportazione archivio + failed_2fa: + details: 'Questi sono i dettagli del tentativo di accesso:' + explanation: Qualcuno ha tentato di accedere al tuo account ma ha fornito un secondo fattore di autenticazione non valido. + further_actions_html: Se non eri tu, ti consigliamo di %{action} immediatamente poiché potrebbe essere compromesso. + subject: Errore di autenticazione del secondo fattore + title: Autenticazione del secondo fattore non riuscita suspicious_sign_in: change_password: cambiare la tua password details: 'Questi sono i dettagli del tentativo di accesso:' diff --git a/config/locales/ko.yml b/config/locales/ko.yml index 946aa35657..b3c786e265 100644 --- a/config/locales/ko.yml +++ b/config/locales/ko.yml @@ -1760,6 +1760,8 @@ ko: extra: 다운로드 할 준비가 되었습니다! subject: 아카이브를 다운로드할 수 있습니다 title: 아카이브 테이크아웃 + failed_2fa: + details: '로그인 시도에 대한 상세 정보입니다:' suspicious_sign_in: change_password: 암호 변경 details: '로그인에 대한 상세 정보입니다:' diff --git a/config/locales/lad.yml b/config/locales/lad.yml index 5a09c4c609..be5d2d21bd 100644 --- a/config/locales/lad.yml +++ b/config/locales/lad.yml @@ -1757,6 +1757,8 @@ lad: extra: Agora esta pronto para abashar! subject: Tu dosya esta pronta para abashar title: Abasha dosya + failed_2fa: + details: 'Aki estan los peratim de las provas de koneksyon kon tu kuento:' suspicious_sign_in: change_password: troka tu kod details: 'Aki estan los peratim de la koneksyon kon tu kuento:' diff --git a/config/locales/lt.yml b/config/locales/lt.yml index f3715fd2ee..ba8b53fdc9 100644 --- a/config/locales/lt.yml +++ b/config/locales/lt.yml @@ -559,6 +559,12 @@ lt: extra: Jį jau galima atsisiųsti! subject: Jūsų archyvas paruoštas parsisiuntimui title: Archyvas išimtas + failed_2fa: + details: 'Štai išsami informacija apie bandymą prisijungti:' + explanation: Kažkas bandė prisijungti prie tavo paskyros, bet nurodė netinkamą antrąjį tapatybės nustatymo veiksnį. + further_actions_html: Jei tai buvo ne tu, rekomenduojame nedelsiant imtis %{action}, nes jis gali būti pažeistas. + subject: Antrojo veiksnio tapatybės nustatymas nesėkmingai + title: Nepavyko atlikti antrojo veiksnio tapatybės nustatymo warning: subject: disable: Jūsų paskyra %{acct} buvo užšaldyta @@ -584,6 +590,7 @@ lt: go_to_sso_account_settings: Eik į savo tapatybės teikėjo paskyros nustatymus invalid_otp_token: Netinkamas dviejų veiksnių kodas otp_lost_help_html: Jei praradai prieigą prie abiejų, gali susisiek su %{email} + rate_limited: Per daug tapatybės nustatymo bandymų. Bandyk dar kartą vėliau. seamless_external_login: Esi prisijungęs (-usi) per išorinę paslaugą, todėl slaptažodžio ir el. pašto nustatymai nepasiekiami. signed_in_as: 'Prisijungta kaip:' verification: diff --git a/config/locales/nl.yml b/config/locales/nl.yml index 5ffa788a8c..2d27f9165d 100644 --- a/config/locales/nl.yml +++ b/config/locales/nl.yml @@ -1790,6 +1790,11 @@ nl: extra: Het staat nu klaar om te worden gedownload! subject: Jouw archief staat klaar om te worden gedownload title: Archief ophalen + failed_2fa: + details: 'Hier zijn details van de aanmeldpoging:' + explanation: Iemand heeft geprobeerd om in te loggen op uw account maar heeft een ongeldige tweede verificatiefactor opgegeven. + subject: Tweede factor authenticatiefout + title: Tweestapsverificatie mislukt suspicious_sign_in: change_password: je wachtwoord te wijzigen details: 'Hier zijn de details van inlogpoging:' diff --git a/config/locales/nn.yml b/config/locales/nn.yml index 626252be00..95eed49785 100644 --- a/config/locales/nn.yml +++ b/config/locales/nn.yml @@ -1790,6 +1790,12 @@ nn: extra: Den er nå klar for nedlasting! subject: Arkivet ditt er klart til å lastes ned title: Nedlasting av arkiv + failed_2fa: + details: 'Her er detaljane om innloggingsforsøket:' + explanation: Nokon har prøvd å logge inn på kontoen din, men brukte ein ugyldig andre-autentiseringsfaktor. + further_actions_html: Om dette ikkje var deg, rår me deg til å %{action} med éin gong, då det kan vere kompomittert. + subject: To-faktor-autentiseringsfeil + title: Mislukka to-faktor-autentisering suspicious_sign_in: change_password: endre passord details: 'Her er påloggingsdetaljane:' diff --git a/config/locales/no.yml b/config/locales/no.yml index d90aa5bab5..7ece8564fc 100644 --- a/config/locales/no.yml +++ b/config/locales/no.yml @@ -1790,6 +1790,12 @@ extra: Den er nå klar for nedlasting! subject: Arkivet ditt er klart til å lastes ned title: Nedlasting av arkiv + failed_2fa: + details: 'Her er detaljer om påloggingsforsøket:' + explanation: Noen har prøvd å logge på kontoen din, men ga en ugyldig andre-autentiseringsfaktor. + further_actions_html: Hvis dette ikke var deg, anbefaler vi at du %{action} umiddelbart fordi det kan ha blitt kompromittert. + subject: Andre-autentiseringsfaktorfeil + title: Mislykket andre-autentiseringsfaktor suspicious_sign_in: change_password: endre passord details: 'Her er detaljer om påloggingen:' diff --git a/config/locales/pl.yml b/config/locales/pl.yml index 4d8fde8f4f..6718f1994b 100644 --- a/config/locales/pl.yml +++ b/config/locales/pl.yml @@ -1854,6 +1854,12 @@ pl: extra: Gotowe do pobrania! subject: Twoje archiwum jest gotowe do pobrania title: Odbiór archiwum + failed_2fa: + details: 'Oto szczegóły próby logowania:' + explanation: Ktoś próbował zalogować się na twoje konto, ale nie przeszedł drugiego etapu autoryzacji. + further_actions_html: Jeśli to nie ty, polecamy natychmiastowo %{action}, bo może ono być narażone. + subject: Błąd drugiego etapu uwierzytelniania + title: Nieudane uwierzytelnienie w drugim etapie suspicious_sign_in: change_password: zmień hasło details: 'Oto szczegóły logowania:' diff --git a/config/locales/pt-BR.yml b/config/locales/pt-BR.yml index 47ad0ac448..c1a47c0161 100644 --- a/config/locales/pt-BR.yml +++ b/config/locales/pt-BR.yml @@ -1789,6 +1789,12 @@ pt-BR: extra: Agora está pronto para baixar! subject: Seu arquivo está pronto para ser baixado title: Baixar arquivo + failed_2fa: + details: 'Aqui estão os detalhes da tentativa de acesso:' + explanation: Alguém tentou entrar em sua conta, mas forneceu um segundo fator de autenticação inválido. + further_actions_html: Se não foi você, recomendamos que %{action} imediatamente, pois ela pode ser comprometida. + subject: Falha na autenticação do segundo fator + title: Falha na autenticação do segundo fator suspicious_sign_in: change_password: Altere sua senha details: 'Aqui estão os detalhes do acesso:' @@ -1842,6 +1848,7 @@ pt-BR: go_to_sso_account_settings: Vá para as configurações de conta do seu provedor de identidade invalid_otp_token: Código de dois fatores inválido otp_lost_help_html: Se você perder o acesso à ambos, você pode entrar em contato com %{email} + rate_limited: Muitas tentativas de autenticação; tente novamente mais tarde. seamless_external_login: Você entrou usando um serviço externo, então configurações de e-mail e senha não estão disponíveis. signed_in_as: 'Entrou como:' verification: diff --git a/config/locales/pt-PT.yml b/config/locales/pt-PT.yml index 2e077b37a8..268531718d 100644 --- a/config/locales/pt-PT.yml +++ b/config/locales/pt-PT.yml @@ -1790,6 +1790,12 @@ pt-PT: extra: Está pronta para transferir! subject: O seu arquivo está pronto para descarregar title: Arquivo de ficheiros + failed_2fa: + details: 'Aqui estão os detalhes da tentativa de entrada:' + explanation: Alguém tentou entrar em sua conta mas forneceu um segundo fator de autenticação inválido. + further_actions_html: Se não foi você, recomendamos que %{action} imediatamente, pois pode ter sido comprometido. + subject: Falha na autenticação do segundo fator + title: Falha na autenticação do segundo fator suspicious_sign_in: change_password: alterar a sua palavra-passe details: 'Eis os pormenores do início de sessão:' diff --git a/config/locales/ru.yml b/config/locales/ru.yml index 2644275c37..24edbdc75e 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -439,6 +439,7 @@ ru: view: Посмотреть доменные блокировки email_domain_blocks: add_new: Добавить новую + allow_registrations_with_approval: Разрешить регистрацию с одобрением attempts_over_week: few: "%{count} попытки за последнюю неделю" many: "%{count} попыток за последнюю неделю" @@ -1659,6 +1660,7 @@ ru: unknown_browser: Неизвестный браузер weibo: Weibo current_session: Текущая сессия + date: Дата description: "%{browser} на %{platform}" explanation: Здесь отображаются все браузеры, с которых выполнен вход в вашу учётную запись. Авторизованные приложения находятся в секции «Приложения». ip: IP @@ -1837,16 +1839,27 @@ ru: webauthn: Ключи безопасности user_mailer: appeal_approved: + action: Настройки аккаунта explanation: Апелляция на разблокировку против вашей учетной записи %{strike_date}, которую вы подали на %{appeal_date}, была одобрена. Ваша учетная запись снова на хорошем счету. subject: Ваше обжалование от %{date} была одобрено + subtitle: Ваш аккаунт снова с хорошей репутацией. title: Обжалование одобрено appeal_rejected: explanation: Апелляция на разблокировку против вашей учетной записи %{strike_date}, которую вы подали на %{appeal_date}, была одобрена. Ваша учетная запись восстановлена. subject: Ваше обжалование от %{date} отклонено + subtitle: Ваша апелляция отклонена. title: Обжалование отклонено backup_ready: + explanation: Вы запросили полное резервное копирование вашей учетной записи Mastodon. + extra: Теперь он готов к загрузке! subject: Ваш архив готов к загрузке title: Архив ваших данных готов + failed_2fa: + details: 'Вот подробности попытки регистрации:' + explanation: Кто-то пытался войти в вашу учетную запись, но указал неверный второй фактор аутентификации. + further_actions_html: Если это не вы, мы рекомендуем %{action} немедленно принять меры, так как он может быть скомпрометирован. + subject: Сбой двухфакторной аутентификации + title: Сбой двухфакторной аутентификации suspicious_sign_in: change_password: сменить пароль details: 'Подробности о новом входе:' @@ -1900,6 +1913,7 @@ ru: go_to_sso_account_settings: Перейти к настройкам сторонних аккаунтов учетной записи invalid_otp_token: Введен неверный код двухфакторной аутентификации otp_lost_help_html: Если Вы потеряли доступ к обоим, свяжитесь с %{email} + rate_limited: Слишком много попыток аутентификации, повторите попытку позже. seamless_external_login: Вы залогинены через сторонний сервис, поэтому настройки e-mail и пароля недоступны. signed_in_as: 'Выполнен вход под именем:' verification: diff --git a/config/locales/simple_form.sk.yml b/config/locales/simple_form.sk.yml index e13a05835f..614812a3a9 100644 --- a/config/locales/simple_form.sk.yml +++ b/config/locales/simple_form.sk.yml @@ -60,6 +60,7 @@ sk: fields: name: Označenie value: Obsah + unlocked: Automaticky prijímaj nových nasledovateľov account_alias: acct: Adresa starého účtu account_migration: diff --git a/config/locales/sk.yml b/config/locales/sk.yml index c639bbe1a6..e83ae348f6 100644 --- a/config/locales/sk.yml +++ b/config/locales/sk.yml @@ -430,6 +430,7 @@ sk: dashboard: instance_accounts_dimension: Najsledovanejšie účty instance_accounts_measure: uložené účty + instance_followers_measure: naši nasledovatelia tam instance_follows_measure: ich sledovatelia tu instance_languages_dimension: Najpopulárnejšie jazyky instance_media_attachments_measure: uložené mediálne prílohy @@ -1257,6 +1258,8 @@ sk: extra: Teraz je pripravená na stiahnutie! subject: Tvoj archív je pripravený na stiahnutie title: Odber archívu + failed_2fa: + details: 'Tu sú podrobnosti o pokuse o prihlásenie:' warning: subject: disable: Tvoj účet %{acct} bol zamrazený diff --git a/config/locales/sq.yml b/config/locales/sq.yml index 1693db7f31..d6e6925c70 100644 --- a/config/locales/sq.yml +++ b/config/locales/sq.yml @@ -1604,6 +1604,7 @@ sq: unknown_browser: Shfletues i Panjohur weibo: Weibo current_session: Sesioni i tanishëm + date: Datë description: "%{browser} në %{platform}" explanation: Këta janë shfletuesit e përdorur tani për hyrje te llogaria juaj Mastodon. ip: IP @@ -1770,16 +1771,27 @@ sq: webauthn: Kyçe sigurie user_mailer: appeal_approved: + action: Rregullime Llogarie explanation: Apelimi i paralajmërimit kundër llogarisë tuaj më %{strike_date}, të cilin e parashtruar më %{appeal_date} është miratuar. Llogaria juaj është sërish në pozita të mira. subject: Apelimi juaj i datës %{date} u miratua + subtitle: Llogaria juaj edhe një herë është e shëndetshme. title: Apelimi u miratua appeal_rejected: explanation: Apelimi i paralajmërimit kundër llogarisë tuaj më %{strike_date}, të cilin e parashtruar më %{appeal_date}, u hodh poshtë. subject: Apelimi juaj prej %{date} është hedhur poshtë + subtitle: Apelimi juaj është hedhur poshtë. title: Apelimi u hodh poshtë backup_ready: + explanation: Kërkuat një kopjeruajtje të plotë të llogarisë tuaj Mastodon. + extra: Tani është gati për shkarkim! subject: Arkivi juaj është gati për shkarkim title: Marrje arkivi me vete + failed_2fa: + details: 'Ja hollësitë e përpjekjes për hyrje:' + explanation: Dikush ka provuar të hyjë në llogarinë tuaj, por dha faktor të dytë mirëfilltësimi. + further_actions_html: Nëse s’qetë ju, rekomandojmë të %{action} menjëherë, ngaqë mund të jetë komprometua. + subject: Dështim faktori të dytë mirëfilltësimesh + title: Dështoi mirëfilltësimi me faktor të dytë suspicious_sign_in: change_password: ndryshoni fjalëkalimin tuaj details: 'Ja hollësitë për hyrjen:' @@ -1833,6 +1845,7 @@ sq: go_to_sso_account_settings: Kaloni te rregullime llogarie te shërbimi juaj i identitetit invalid_otp_token: Kod dyfaktorësh i pavlefshëm otp_lost_help_html: Nëse humbët hyrjen te të dy, mund të lidheni me %{email} + rate_limited: Shumë përpjekje mirëfilltësimi, riprovoni më vonë. seamless_external_login: Jeni futur përmes një shërbimi të jashtëm, ndaj s’ka rregullime fjalëkalimi dhe email. signed_in_as: 'I futur si:' verification: diff --git a/config/locales/sr-Latn.yml b/config/locales/sr-Latn.yml index 39c9f2f873..9cb555c943 100644 --- a/config/locales/sr-Latn.yml +++ b/config/locales/sr-Latn.yml @@ -1822,6 +1822,12 @@ sr-Latn: extra: Sada je spremno za preuzimanje! subject: Vaša arhiva je spremna za preuzimanje title: Izvoz arhive + failed_2fa: + details: 'Evo detalja o pokušaju prijavljivanja:' + explanation: Neko je pokušao da se prijavi na vaš nalog ali je dao nevažeći drugi faktor autentifikacije. + further_actions_html: Ako to niste bili vi, preporučujemo vam da odmah %{action} jer može biti ugrožena. + subject: Neuspeh drugog faktora autentifikacije + title: Nije uspeo drugi faktor autentifikacije suspicious_sign_in: change_password: promenite svoju lozinku details: 'Evo detalja o prijavi:' diff --git a/config/locales/sr.yml b/config/locales/sr.yml index 0cf35c14cc..e1c2e992ed 100644 --- a/config/locales/sr.yml +++ b/config/locales/sr.yml @@ -1822,6 +1822,12 @@ sr: extra: Сада је спремно за преузимање! subject: Ваша архива је спремна за преузимање title: Извоз архиве + failed_2fa: + details: 'Ево детаља о покушају пријављивања:' + explanation: Неко је покушао да се пријави на ваш налог али је дао неважећи други фактор аутентификације. + further_actions_html: Ако то нисте били ви, препоручујемо вам да одмах %{action} јер може бити угрожена. + subject: Неуспех другог фактора аутентификације + title: Није успео други фактор аутентификације suspicious_sign_in: change_password: промените своју лозинку details: 'Ево детаља о пријави:' diff --git a/config/locales/sv.yml b/config/locales/sv.yml index 3a82f29d2f..c9000d50fc 100644 --- a/config/locales/sv.yml +++ b/config/locales/sv.yml @@ -1789,6 +1789,9 @@ sv: extra: Nu redo för nedladdning! subject: Ditt arkiv är klart för nedladdning title: Arkivuttagning + failed_2fa: + further_actions_html: Om detta inte var du, rekommenderar vi att du %{action} omedelbart eftersom ditt konto kan ha äventyrats. + title: Misslyckad tvåfaktorsautentisering suspicious_sign_in: change_password: Ändra ditt lösenord details: 'Här är inloggningsdetaljerna:' diff --git a/config/locales/tr.yml b/config/locales/tr.yml index 3b74c4eaa1..2b5b5ad45b 100644 --- a/config/locales/tr.yml +++ b/config/locales/tr.yml @@ -1790,6 +1790,12 @@ tr: extra: Şimdi indirebilirsiniz! subject: Arşiviniz indirilmeye hazır title: Arşiv paketlemesi + failed_2fa: + details: 'İşte oturum açma girişiminin ayrıntıları:' + explanation: Birisi hesabınızda oturum açmaya çalıştı ancak hatalı bir iki aşamalı doğrulama kodu kullandı. + further_actions_html: Eğer bu kişi siz değilseniz, hemen %{action} yapmanızı öneriyoruz çünkü hesabınız ifşa olmuş olabilir. + subject: İki aşamalı doğrulama başarısızlığı + title: Başarısız iki aşamalı kimlik doğrulama suspicious_sign_in: change_password: parolanızı değiştirin details: 'Oturum açma ayrıntıları şöyledir:' diff --git a/config/locales/vi.yml b/config/locales/vi.yml index 3817b18f07..1ece72e154 100644 --- a/config/locales/vi.yml +++ b/config/locales/vi.yml @@ -1758,6 +1758,12 @@ vi: extra: Hiện nó đã sẵn sàng tải xuống! subject: Dữ liệu cá nhân của bạn đã sẵn sàng để tải về title: Nhận dữ liệu cá nhân + failed_2fa: + details: 'Chi tiết thông tin đăng nhập:' + explanation: Ai đó đã cố đăng nhập vào tài khoản của bạn nhưng cung cấp yếu tố xác thực thứ hai không hợp lệ. + further_actions_html: Nếu không phải bạn, hãy lập tức %{action} vì có thể có rủi ro. + subject: Xác minh hai bước thất bại + title: Xác minh hai bước thất bại suspicious_sign_in: change_password: đổi mật khẩu của bạn details: 'Chi tiết thông tin đăng nhập:' diff --git a/config/locales/zh-CN.yml b/config/locales/zh-CN.yml index 80bb5653ca..272787ce25 100644 --- a/config/locales/zh-CN.yml +++ b/config/locales/zh-CN.yml @@ -1758,6 +1758,12 @@ zh-CN: extra: 现在它可以下载了! subject: 你的存档已经准备完毕 title: 存档导出 + failed_2fa: + details: 以下是该次登录尝试的详情: + explanation: 有人试图登录到您的账户,但提供了无效的辅助认证因子。 + further_actions_html: 如果这不是您所为,您的密码可能已经泄露,建议您立即 %{action} 。 + subject: 辅助认证失败 + title: 辅助认证失败 suspicious_sign_in: change_password: 更改密码 details: 以下是该次登录的详细信息: diff --git a/config/locales/zh-HK.yml b/config/locales/zh-HK.yml index ac32c03e95..0c39aa8c0b 100644 --- a/config/locales/zh-HK.yml +++ b/config/locales/zh-HK.yml @@ -1758,6 +1758,12 @@ zh-HK: extra: 現在可以下載了! subject: 你的備份檔已可供下載 title: 檔案匯出 + failed_2fa: + details: 以下是嘗試登入的細節: + explanation: 有人嘗試登入你的帳號,但沒有通過雙重認證。 + further_actions_html: 如果這不是你,我們建議你立刻%{action},因為你的帳號或已遭到侵害。 + subject: 雙重認證失敗 + title: 雙重認證失敗 suspicious_sign_in: change_password: 更改你的密碼 details: 以下是登入的細節: diff --git a/config/locales/zh-TW.yml b/config/locales/zh-TW.yml index 6662e44cdc..8726ea72a4 100644 --- a/config/locales/zh-TW.yml +++ b/config/locales/zh-TW.yml @@ -1760,6 +1760,12 @@ zh-TW: extra: 準備好下載了! subject: 您的備份檔已可供下載 title: 檔案匯出 + failed_2fa: + details: 以下是該登入嘗試之詳細資訊: + explanation: 有人嘗試登入您的帳號,但提供了無效的第二個驗證因子。 + further_actions_html: 若這並非您所為,我們建議您立刻 %{action},因為其可能已被入侵。 + subject: 第二因子驗證失敗 + title: 第二因子身份驗證失敗 suspicious_sign_in: change_password: 變更密碼 details: 以下是該登入之詳細資訊: diff --git a/config/routes/api.rb b/config/routes/api.rb index c86d019b95..4335036d59 100644 --- a/config/routes/api.rb +++ b/config/routes/api.rb @@ -57,6 +57,12 @@ namespace :api, format: false do resources :scheduled_statuses, only: [:index, :show, :update, :destroy] resources :preferences, only: [:index] + resources :annual_reports, only: [:index] do + member do + post :read + end + end + resources :announcements, only: [:index] do scope module: :announcements do resources :reactions, only: [:update, :destroy] diff --git a/db/migrate/20240111033014_create_generated_annual_reports.rb b/db/migrate/20240111033014_create_generated_annual_reports.rb new file mode 100644 index 0000000000..2a755fb14e --- /dev/null +++ b/db/migrate/20240111033014_create_generated_annual_reports.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +class CreateGeneratedAnnualReports < ActiveRecord::Migration[7.1] + def change + create_table :generated_annual_reports do |t| + t.belongs_to :account, null: false, foreign_key: { on_cascade: :delete }, index: false + t.integer :year, null: false + t.jsonb :data, null: false + t.integer :schema_version, null: false + t.datetime :viewed_at + + t.timestamps + end + + add_index :generated_annual_reports, [:account_id, :year], unique: true + end +end diff --git a/db/schema.rb b/db/schema.rb index da2be71318..34cbf5a57b 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.1].define(version: 2024_01_09_103012) do +ActiveRecord::Schema[7.1].define(version: 2024_01_11_033014) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -516,6 +516,17 @@ ActiveRecord::Schema[7.1].define(version: 2024_01_09_103012) do t.index ["target_account_id"], name: "index_follows_on_target_account_id" end + create_table "generated_annual_reports", force: :cascade do |t| + t.bigint "account_id", null: false + t.integer "year", null: false + t.jsonb "data", null: false + t.integer "schema_version", null: false + t.datetime "viewed_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["account_id", "year"], name: "index_generated_annual_reports_on_account_id_and_year", unique: true + end + create_table "identities", force: :cascade do |t| t.string "provider", default: "", null: false t.string "uid", default: "", null: false @@ -1241,6 +1252,7 @@ ActiveRecord::Schema[7.1].define(version: 2024_01_09_103012) do add_foreign_key "follow_requests", "accounts", name: "fk_76d644b0e7", on_delete: :cascade add_foreign_key "follows", "accounts", column: "target_account_id", name: "fk_745ca29eac", on_delete: :cascade add_foreign_key "follows", "accounts", name: "fk_32ed1b5560", on_delete: :cascade + add_foreign_key "generated_annual_reports", "accounts" add_foreign_key "identities", "users", name: "fk_bea040f377", on_delete: :cascade add_foreign_key "imports", "accounts", name: "fk_6db1b6e408", on_delete: :cascade add_foreign_key "invites", "users", on_delete: :cascade diff --git a/jsconfig.json b/jsconfig.json index d52816a98b..7b710de83c 100644 --- a/jsconfig.json +++ b/jsconfig.json @@ -11,7 +11,7 @@ "noEmit": true, "resolveJsonModule": true, "strict": false, - "target": "ES2022" + "target": "ES2022", }, - "exclude": ["**/build/*", "**/node_modules/*", "**/public/*", "**/vendor/*"] + "exclude": ["**/build/*", "**/node_modules/*", "**/public/*", "**/vendor/*"], } diff --git a/lib/mastodon/cli/maintenance.rb b/lib/mastodon/cli/maintenance.rb index e2ea866152..73012812fd 100644 --- a/lib/mastodon/cli/maintenance.rb +++ b/lib/mastodon/cli/maintenance.rb @@ -275,7 +275,7 @@ module Mastodon::CLI def deduplicate_users_process_email ActiveRecord::Base.connection.select_all("SELECT string_agg(id::text, ',') AS ids FROM users GROUP BY email HAVING count(*) > 1").each do |row| - users = User.where(id: row['ids'].split(',')).order(updated_at: :desc).to_a + users = User.where(id: row['ids'].split(',')).order(updated_at: :desc).includes(:account).to_a ref_user = users.shift say "Multiple users registered with e-mail address #{ref_user.email}.", :yellow say "e-mail will be disabled for the following accounts: #{users.map { |user| user.account.acct }.join(', ')}", :yellow @@ -289,7 +289,7 @@ module Mastodon::CLI def deduplicate_users_process_confirmation_token ActiveRecord::Base.connection.select_all("SELECT string_agg(id::text, ',') AS ids FROM users WHERE confirmation_token IS NOT NULL GROUP BY confirmation_token HAVING count(*) > 1").each do |row| - users = User.where(id: row['ids'].split(',')).order(created_at: :desc).to_a.drop(1) + users = User.where(id: row['ids'].split(',')).order(created_at: :desc).includes(:account).to_a.drop(1) say "Unsetting confirmation token for those accounts: #{users.map { |user| user.account.acct }.join(', ')}", :yellow users.each do |user| @@ -313,7 +313,7 @@ module Mastodon::CLI def deduplicate_users_process_password_token ActiveRecord::Base.connection.select_all("SELECT string_agg(id::text, ',') AS ids FROM users WHERE reset_password_token IS NOT NULL GROUP BY reset_password_token HAVING count(*) > 1").each do |row| - users = User.where(id: row['ids'].split(',')).order(updated_at: :desc).to_a.drop(1) + users = User.where(id: row['ids'].split(',')).order(updated_at: :desc).includes(:account).to_a.drop(1) say "Unsetting password reset token for those accounts: #{users.map { |user| user.account.acct }.join(', ')}", :yellow users.each do |user| @@ -591,7 +591,7 @@ module Mastodon::CLI end def deduplicate_local_accounts!(scope) - accounts = scope.order(id: :desc).to_a + accounts = scope.order(id: :desc).includes(:account_stat, :user).to_a say "Multiple local accounts were found for username '#{accounts.first.username}'.", :yellow say 'All those accounts are distinct accounts but only the most recently-created one is fully-functional.', :yellow diff --git a/lib/mastodon/cli/statuses.rb b/lib/mastodon/cli/statuses.rb index 7acf3f9b77..48d76e0288 100644 --- a/lib/mastodon/cli/statuses.rb +++ b/lib/mastodon/cli/statuses.rb @@ -120,7 +120,7 @@ module Mastodon::CLI say('Beginning removal of now-orphaned media attachments to free up disk space...') - scope = MediaAttachment.unattached.where('created_at < ?', options[:days].pred.days.ago) + scope = MediaAttachment.unattached.created_before(options[:days].pred.days.ago) processed = 0 removed = 0 progress = create_progress_bar(scope.count) diff --git a/lib/tasks/tests.rake b/lib/tasks/tests.rake index c3a9dbfd73..45f055e218 100644 --- a/lib/tasks/tests.rake +++ b/lib/tasks/tests.rake @@ -24,7 +24,7 @@ namespace :tests do exit(1) end - if Account.where(domain: Rails.configuration.x.local_domain).exists? + if Account.exists?(domain: Rails.configuration.x.local_domain) puts 'Faux remote accounts not properly cleaned up' exit(1) end diff --git a/spec/controllers/api/base_controller_spec.rb b/spec/controllers/api/base_controller_spec.rb index db1e8777f7..f8e014be2f 100644 --- a/spec/controllers/api/base_controller_spec.rb +++ b/spec/controllers/api/base_controller_spec.rb @@ -12,7 +12,7 @@ describe Api::BaseController do head 200 end - def error + def failure FakeService.new end end @@ -30,7 +30,7 @@ describe Api::BaseController do it 'does not protect from forgery' do ActionController::Base.allow_forgery_protection = true - post 'success' + post :success expect(response).to have_http_status(200) end end @@ -50,47 +50,55 @@ describe Api::BaseController do it 'returns http forbidden for unconfirmed accounts' do user.update(confirmed_at: nil) - post 'success' + post :success expect(response).to have_http_status(403) end it 'returns http forbidden for pending accounts' do user.update(approved: false) - post 'success' + post :success expect(response).to have_http_status(403) end it 'returns http forbidden for disabled accounts' do user.update(disabled: true) - post 'success' + post :success expect(response).to have_http_status(403) end it 'returns http forbidden for suspended accounts' do user.account.suspend! - post 'success' + post :success expect(response).to have_http_status(403) end end describe 'error handling' do before do - routes.draw { get 'error' => 'api/base#error' } + routes.draw { get 'failure' => 'api/base#failure' } end { ActiveRecord::RecordInvalid => 422, - Mastodon::ValidationError => 422, ActiveRecord::RecordNotFound => 404, - Mastodon::UnexpectedResponseError => 503, + ActiveRecord::RecordNotUnique => 422, + Date::Error => 422, HTTP::Error => 503, - OpenSSL::SSL::SSLError => 503, + Mastodon::InvalidParameterError => 400, Mastodon::NotPermittedError => 403, + Mastodon::RaceConditionError => 503, + Mastodon::RateLimitExceededError => 429, + Mastodon::UnexpectedResponseError => 503, + Mastodon::ValidationError => 422, + OpenSSL::SSL::SSLError => 503, + Seahorse::Client::NetworkingError => 503, + Stoplight::Error::RedLight => 503, }.each do |error, code| it "Handles error class of #{error}" do allow(FakeService).to receive(:new).and_raise(error) - get 'error' + get :failure + expect(response).to have_http_status(code) expect(FakeService).to have_received(:new) end diff --git a/spec/models/tag_spec.rb b/spec/models/tag_spec.rb index 6177b7a25a..69aaeed0af 100644 --- a/spec/models/tag_spec.rb +++ b/spec/models/tag_spec.rb @@ -100,6 +100,38 @@ RSpec.describe Tag do end end + describe '.recently_used' do + let(:account) { Fabricate(:account) } + let(:other_person_status) { Fabricate(:status) } + let(:out_of_range) { Fabricate(:status, account: account) } + let(:older_in_range) { Fabricate(:status, account: account) } + let(:newer_in_range) { Fabricate(:status, account: account) } + let(:unused_tag) { Fabricate(:tag) } + let(:used_tag_one) { Fabricate(:tag) } + let(:used_tag_two) { Fabricate(:tag) } + let(:used_tag_on_out_of_range) { Fabricate(:tag) } + + before do + stub_const 'Tag::RECENT_STATUS_LIMIT', 2 + + other_person_status.tags << used_tag_one + + out_of_range.tags << used_tag_on_out_of_range + + older_in_range.tags << used_tag_one + older_in_range.tags << used_tag_two + + newer_in_range.tags << used_tag_one + end + + it 'returns tags used by account within last X statuses ordered most used first' do + results = described_class.recently_used(account) + + expect(results) + .to eq([used_tag_one, used_tag_two]) + end + end + describe '.find_normalized' do it 'returns tag for a multibyte case-insensitive name' do upcase_string = 'abcABCabcABCやゆよ' diff --git a/streaming/tsconfig.json b/streaming/tsconfig.json index f7bb711b9b..a0cf68ef90 100644 --- a/streaming/tsconfig.json +++ b/streaming/tsconfig.json @@ -6,7 +6,7 @@ "moduleResolution": "node", "noUnusedParameters": false, "tsBuildInfoFile": "../tmp/cache/streaming/tsconfig.tsbuildinfo", - "paths": {} + "paths": {}, }, - "include": ["./*.js", "./.eslintrc.js"] + "include": ["./*.js", "./.eslintrc.js"], } diff --git a/tsconfig.json b/tsconfig.json index 5f5db44226..1e00f24f48 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -20,14 +20,14 @@ "flavours/glitch/*": ["app/javascript/flavours/glitch/*"], "mastodon": ["app/javascript/mastodon"], "mastodon/*": ["app/javascript/mastodon/*"], - "@/*": ["app/javascript/*"] - } + "@/*": ["app/javascript/*"], + }, }, "include": [ "app/javascript/mastodon", "app/javascript/packs", "app/javascript/types", "app/javascript/flavours/glitch", - "app/javascript/core" - ] + "app/javascript/core", + ], } diff --git a/yarn.lock b/yarn.lock index 0802a4d4b4..08bfdd6c48 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1538,7 +1538,7 @@ __metadata: languageName: node linkType: hard -"@csstools/css-parser-algorithms@npm:^2.4.0": +"@csstools/css-parser-algorithms@npm:^2.5.0": version: 2.5.0 resolution: "@csstools/css-parser-algorithms@npm:2.5.0" peerDependencies: @@ -1547,14 +1547,14 @@ __metadata: languageName: node linkType: hard -"@csstools/css-tokenizer@npm:^2.2.2": +"@csstools/css-tokenizer@npm:^2.2.3": version: 2.2.3 resolution: "@csstools/css-tokenizer@npm:2.2.3" checksum: 557266ec52e8b36c19008a5bbd7151effba085cdd6d68270c01afebf914981caac698eda754b2a530a8a9947a3dd70e3f3a39a5e037c4170bb2a055a92754acb languageName: node linkType: hard -"@csstools/media-query-list-parser@npm:^2.1.6": +"@csstools/media-query-list-parser@npm:^2.1.7": version: 2.1.7 resolution: "@csstools/media-query-list-parser@npm:2.1.7" peerDependencies: @@ -1777,8 +1777,8 @@ __metadata: linkType: hard "@formatjs/cli@npm:^6.1.1": - version: 6.2.4 - resolution: "@formatjs/cli@npm:6.2.4" + version: 6.2.6 + resolution: "@formatjs/cli@npm:6.2.6" peerDependencies: vue: ^3.3.4 peerDependenciesMeta: @@ -1786,7 +1786,7 @@ __metadata: optional: true bin: formatjs: bin/formatjs - checksum: 3f6bbbc633a3a6ebd4e6fcfc3a9f889bc044043452cbc8f81abcaee97aaef991a778ae785d3b9d21ecc5f55b147eb0009b44520bb895fe244b4c14a36d9b05bd + checksum: f8b0bc45c72b83437f0dc91a2d3ea513852c11bfd8eedbc2f255b19552f153bccb4d38fcd281f897ca60d0dfddf2b99de22e5a87cb1e173ca11df88c61cde2e4 languageName: node linkType: hard @@ -11355,10 +11355,10 @@ __metadata: languageName: node linkType: hard -"meow@npm:^13.0.0": - version: 13.0.0 - resolution: "meow@npm:13.0.0" - checksum: fab0f91578154c048e792a81704f3f28099ffff900f364df8a85f6e770a57e1c124859a25e186186e149dad30692c7893af0dfd71517bea343bfe5d749b1fa04 +"meow@npm:^13.1.0": + version: 13.1.0 + resolution: "meow@npm:13.1.0" + checksum: 2dac9dbf99a17ce29618fe5919072a9b28e2aedb9547f9b1f15d046d5501dd6c14fe1f35f7a5665d0ee7111c98c4d359fcf3f985463ec5896dd50177363f442d languageName: node linkType: hard @@ -13225,7 +13225,7 @@ __metadata: languageName: node linkType: hard -"postcss@npm:^8.2.15, postcss@npm:^8.4.24, postcss@npm:^8.4.32": +"postcss@npm:^8.2.15, postcss@npm:^8.4.24, postcss@npm:^8.4.33": version: 8.4.33 resolution: "postcss@npm:8.4.33" dependencies: @@ -13320,11 +13320,11 @@ __metadata: linkType: hard "prettier@npm:^3.0.0": - version: 3.2.2 - resolution: "prettier@npm:3.2.2" + version: 3.2.4 + resolution: "prettier@npm:3.2.4" bin: prettier: bin/prettier.cjs - checksum: e84d0d2a4ce2b88ee1636904effbdf68b59da63d9f887128f2ed5382206454185432e7c0a9578bc4308bc25d099cfef47fd0b9c211066777854e23e65e34044d + checksum: 88dfeb78ac6096522c9a5b81f1413d875f568420d9bb6a5e5103527912519b993f2bcdcac311fcff5718d5869671d44e4f85827d3626f3a6ce32b9abc65d88e0 languageName: node linkType: hard @@ -15820,12 +15820,12 @@ __metadata: linkType: hard "stylelint@npm:^16.0.2": - version: 16.1.0 - resolution: "stylelint@npm:16.1.0" + version: 16.2.0 + resolution: "stylelint@npm:16.2.0" dependencies: - "@csstools/css-parser-algorithms": "npm:^2.4.0" - "@csstools/css-tokenizer": "npm:^2.2.2" - "@csstools/media-query-list-parser": "npm:^2.1.6" + "@csstools/css-parser-algorithms": "npm:^2.5.0" + "@csstools/css-tokenizer": "npm:^2.2.3" + "@csstools/media-query-list-parser": "npm:^2.1.7" "@csstools/selector-specificity": "npm:^3.0.1" balanced-match: "npm:^2.0.0" colord: "npm:^2.9.3" @@ -15845,14 +15845,14 @@ __metadata: is-plain-object: "npm:^5.0.0" known-css-properties: "npm:^0.29.0" mathml-tag-names: "npm:^2.1.3" - meow: "npm:^13.0.0" + meow: "npm:^13.1.0" micromatch: "npm:^4.0.5" normalize-path: "npm:^3.0.0" picocolors: "npm:^1.0.0" - postcss: "npm:^8.4.32" + postcss: "npm:^8.4.33" postcss-resolve-nested-selector: "npm:^0.1.1" postcss-safe-parser: "npm:^7.0.0" - postcss-selector-parser: "npm:^6.0.13" + postcss-selector-parser: "npm:^6.0.15" postcss-value-parser: "npm:^4.2.0" resolve-from: "npm:^5.0.0" string-width: "npm:^4.2.3" @@ -15863,7 +15863,7 @@ __metadata: write-file-atomic: "npm:^5.0.1" bin: stylelint: bin/stylelint.mjs - checksum: 765eea0b07319d1e7989502c07b8b5794938e5a8542bec00990b09ec10c3f7006891689930099e948d06c9ef9982066edb98b1ea64a435138a6b0f0905eb2b87 + checksum: 6fdf0451833c11b18c9aa502f687febd6881a912ac94f39d509b894b0f74ccb636f3dac2991c69cc82dc6190731cc2fa48e307fed477d2a0fce57067cd22b572 languageName: node linkType: hard