From f3f06dafe34de5cb8562bc203f3c4557821217e5 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 18 Sep 2024 10:05:06 +0200 Subject: [PATCH 01/51] Update dependency babel-loader to v8.4.1 (#31931) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/yarn.lock b/yarn.lock index a0c1bb7bae233e..49b6e241f87c40 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5169,17 +5169,17 @@ __metadata: linkType: hard "babel-loader@npm:^8.3.0": - version: 8.3.0 - resolution: "babel-loader@npm:8.3.0" + version: 8.4.1 + resolution: "babel-loader@npm:8.4.1" dependencies: find-cache-dir: "npm:^3.3.1" - loader-utils: "npm:^2.0.0" + loader-utils: "npm:^2.0.4" make-dir: "npm:^3.1.0" schema-utils: "npm:^2.6.5" peerDependencies: "@babel/core": ^7.0.0 webpack: ">=2" - checksum: 10c0/7b83bae35a12fbc5cdf250e2d36a288305fe5b6d20ab044ab7c09bbf456c8895b80af7a4f1e8b64b5c07a4fd48d4b5144dab40b4bc72a4fed532dc000362f38f + checksum: 10c0/efdca9c3ef502af58b923a32123d660c54fd0be125b7b64562c8a43bda0a3a55dac0db32331674104e7e5184061b75c3a0e395b2c5ccdc7cb2125dd9ec7108d2 languageName: node linkType: hard @@ -11421,7 +11421,7 @@ __metadata: languageName: node linkType: hard -"loader-utils@npm:^2.0.0": +"loader-utils@npm:^2.0.0, loader-utils@npm:^2.0.4": version: 2.0.4 resolution: "loader-utils@npm:2.0.4" dependencies: From a397141d78b073246059b36489eb28f27485f9f8 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Wed, 18 Sep 2024 04:05:25 -0400 Subject: [PATCH 02/51] Move non-action public method controller callback to private methods (#31933) --- app/controllers/auth/sessions_controller.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/controllers/auth/sessions_controller.rb b/app/controllers/auth/sessions_controller.rb index a2fed644fe7f10..ecac4c5ba8e517 100644 --- a/app/controllers/auth/sessions_controller.rb +++ b/app/controllers/auth/sessions_controller.rb @@ -20,11 +20,6 @@ class Auth::SessionsController < Devise::SessionsController p.form_action(false) end - def check_suspicious! - user = find_user - @login_is_suspicious = suspicious_sign_in?(user) unless user.nil? - end - def create super do |resource| # We only need to call this if this hasn't already been @@ -101,6 +96,11 @@ def require_no_authentication private + def check_suspicious! + user = find_user + @login_is_suspicious = suspicious_sign_in?(user) unless user.nil? + end + def home_paths(resource) paths = [about_path, '/explore'] From b7548dbf290b3eb9adebeac0ff1b0735ffec2872 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 18 Sep 2024 08:05:59 +0000 Subject: [PATCH 03/51] Update dependency memory_profiler to v1.1.0 (#31947) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 4a139155f5bb14..12f1b5db2c81ed 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -429,7 +429,7 @@ GEM addressable (~> 2.5) azure-storage-blob (~> 2.0.1) hashie (~> 5.0) - memory_profiler (1.0.2) + memory_profiler (1.1.0) mime-types (3.5.2) mime-types-data (~> 3.2015) mime-types-data (3.2024.0820) From bd86c692cf08bb4c5b853f5b843adf0a0853d16d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 18 Sep 2024 08:06:44 +0000 Subject: [PATCH 04/51] New Crowdin Translations (automated) (#31959) Co-authored-by: GitHub Actions --- app/javascript/mastodon/locales/ar.json | 7 +- app/javascript/mastodon/locales/eo.json | 30 +++++ app/javascript/mastodon/locales/es-MX.json | 144 ++++++++++----------- app/javascript/mastodon/locales/fr-CA.json | 2 +- app/javascript/mastodon/locales/fr.json | 2 +- app/javascript/mastodon/locales/gd.json | 2 +- app/javascript/mastodon/locales/he.json | 1 + app/javascript/mastodon/locales/kab.json | 7 + config/locales/es-MX.yml | 12 +- config/locales/kab.yml | 2 + config/locales/lv.yml | 26 +++- config/locales/simple_form.es-MX.yml | 26 ++-- config/locales/simple_form.kab.yml | 6 +- config/locales/simple_form.lv.yml | 2 +- 14 files changed, 168 insertions(+), 101 deletions(-) diff --git a/app/javascript/mastodon/locales/ar.json b/app/javascript/mastodon/locales/ar.json index 43256506cdc38f..d50ca8dbb59bc9 100644 --- a/app/javascript/mastodon/locales/ar.json +++ b/app/javascript/mastodon/locales/ar.json @@ -36,6 +36,7 @@ "account.followers.empty": "لا أحدَ يُتابع هذا المُستخدم إلى حد الآن.", "account.followers_counter": "{count, plural, zero{لا مُتابع} one {مُتابعٌ واحِد} two {مُتابعانِ اِثنان} few {{counter} مُتابِعين} many {{counter} مُتابِعًا} other {{counter} مُتابع}}", "account.following": "الاشتراكات", + "account.following_counter": "{count, plural, zero{لا يُتابِع أحدًا} one {يُتابِعُ واحد} two{يُتابِعُ اِثنان} few{يُتابِعُ {counter}} many{يُتابِعُ {counter}} other {يُتابِعُ {counter}}}", "account.follows.empty": "لا يُتابع هذا المُستخدمُ أيَّ أحدٍ حتى الآن.", "account.go_to_profile": "اذهب إلى الملف الشخصي", "account.hide_reblogs": "إخفاء المعاد نشرها مِن @{name}", @@ -309,7 +310,7 @@ "follow_request.authorize": "ترخيص", "follow_request.reject": "رفض", "follow_requests.unlocked_explanation": "حتى وإن كان حسابك غير مقفل، يعتقد فريق {domain} أنك قد ترغب في مراجعة طلبات المتابعة من هذه الحسابات يدوياً.", - "follow_suggestions.curated_suggestion": "اختيار الموظفين", + "follow_suggestions.curated_suggestion": "انتقاه الفريق", "follow_suggestions.dismiss": "لا تُظهرها مجدّدًا", "follow_suggestions.featured_longer": "مختار يدوياً من قِبل فريق {domain}", "follow_suggestions.friends_of_friends_longer": "مشهور بين الأشخاص الذين تتابعهم", @@ -752,7 +753,7 @@ "status.edit": "تعديل", "status.edited": "آخر تعديل يوم {date}", "status.edited_x_times": "عُدّل {count, plural, zero {} one {مرةً واحدة} two {مرّتان} few {{count} مرات} many {{count} مرة} other {{count} مرة}}", - "status.embed": "الحصول على شفرة الإدماج", + "status.embed": "الحصول على شيفرة الدمج", "status.favourite": "فضّل", "status.favourites": "{count, plural, zero {}one {مفضلة واحدة} two {مفضلتان} few {# مفضلات} many {# مفضلات} other {# مفضلات}}", "status.filter": "تصفية هذا المنشور", @@ -773,7 +774,7 @@ "status.reblog": "إعادة النشر", "status.reblog_private": "إعادة النشر إلى الجمهور الأصلي", "status.reblogged_by": "شارَكَه {name}", - "status.reblogs": "{count, plural, one {تعزيز واحد} two {تعزيزتان} few {# تعزيزات} many {# تعزيزات} other {# تعزيزات}}", + "status.reblogs": "{count, plural, one {إعادة نشر واحدة} two {معاد نشرها مرتان} few {# إعادات نشر} many {# إعادات نشر} other {# إعادة نشر}}", "status.reblogs.empty": "لم يقم أي أحد بمشاركة هذا المنشور بعد. عندما يقوم أحدهم بذلك سوف يظهر هنا.", "status.redraft": "إزالة وإعادة الصياغة", "status.remove_bookmark": "احذفه مِن الفواصل المرجعية", diff --git a/app/javascript/mastodon/locales/eo.json b/app/javascript/mastodon/locales/eo.json index ce7201dacfbe98..e162e732d07dab 100644 --- a/app/javascript/mastodon/locales/eo.json +++ b/app/javascript/mastodon/locales/eo.json @@ -97,6 +97,8 @@ "block_modal.title": "Ĉu bloki uzanton?", "block_modal.you_wont_see_mentions": "Vi ne vidos afiŝojn, ke mencii ilin.", "boost_modal.combo": "Vi povas premi {combo} por preterpasi sekvafoje", + "boost_modal.reblog": "Ĉu diskonigi afiŝon?", + "boost_modal.undo_reblog": "Ĉu ĉesi diskonigi afiŝon?", "bundle_column_error.copy_stacktrace": "Kopii la eraran raporton", "bundle_column_error.error.body": "La petita paĝo ne povas redonitis. Eble estas eraro.", "bundle_column_error.error.title": "Ho, ve!", @@ -188,8 +190,12 @@ "confirmations.redraft.title": "Ĉu forigi kaj redakcii afiŝon?", "confirmations.reply.confirm": "Respondi", "confirmations.reply.message": "Respondi nun anstataŭigos la skribatan afiŝon. Ĉu vi certas, ke vi volas daŭrigi?", + "confirmations.reply.title": "Ĉu superskribi afiŝon?", "confirmations.unfollow.confirm": "Ne plu sekvi", "confirmations.unfollow.message": "Ĉu vi certas, ke vi volas ĉesi sekvi {name}?", + "confirmations.unfollow.title": "Ĉu ĉesi sekvi uzanton?", + "content_warning.hide": "Kaŝi afiŝon", + "content_warning.show": "Montri ĉiukaze", "conversation.delete": "Forigi konversacion", "conversation.mark_as_read": "Marki legita", "conversation.open": "Vidi konversacion", @@ -209,6 +215,8 @@ "dismissable_banner.explore_statuses": "Ĉi tioj estas afiŝoj de socia reto kiu populariĝas hodiau.", "dismissable_banner.explore_tags": "Ĉi tiuj kradvostoj populariĝas en ĉi tiu kaj aliaj serviloj en la malcentraliza reto nun.", "dismissable_banner.public_timeline": "Ĉi tioj estas plej lastaj publikaj afiŝoj de personoj ĉe socia reto kiu personoj ĉe {domain} sekvas.", + "domain_block_modal.they_cant_follow": "Neniu el ĉi tiu servilo povas sekvi vin.", + "domain_pill.username": "Uzantnomo", "embed.instructions": "Enkorpigu ĉi tiun afiŝon en vian retejon per kopio de la suba kodo.", "embed.preview": "Ĝi aperos tiel:", "emoji_button.activity": "Agadoj", @@ -281,6 +289,12 @@ "follow_request.authorize": "Rajtigi", "follow_request.reject": "Rifuzi", "follow_requests.unlocked_explanation": "Kvankam via konto ne estas ŝlosita, la dungitaro de {domain} opinias, ke vi eble volas revizii petojn pri sekvado de ĉi tiuj kontoj permane.", + "follow_suggestions.dismiss": "Ne montri denove", + "follow_suggestions.hints.friends_of_friends": "Ĉi tiu profilo estas populara inter la homoj, kiujn vi sekvas.", + "follow_suggestions.hints.most_followed": "Ĉi tiu profilo estas unu el la plej sekvataj en {domain}.", + "follow_suggestions.popular_suggestion_longer": "Populara en {domain}", + "follow_suggestions.view_all": "Vidi ĉiujn", + "follow_suggestions.who_to_follow": "Kiun sekvi", "followed_tags": "Sekvataj kradvortoj", "footer.about": "Pri", "footer.directory": "Profilujo", @@ -374,6 +388,7 @@ "limited_account_hint.action": "Montru profilon ĉiukaze", "limited_account_hint.title": "La profilo estas kaŝita de la moderigantoj de {domain}.", "link_preview.author": "De {name}", + "link_preview.shares": "{count, plural, one {{counter} afiŝo} other {{counter} afiŝoj}}", "lists.account.add": "Aldoni al la listo", "lists.account.remove": "Forigi de la listo", "lists.delete": "Forigi la liston", @@ -390,8 +405,12 @@ "lists.subheading": "Viaj listoj", "load_pending": "{count,plural, one {# nova elemento} other {# novaj elementoj}}", "loading_indicator.label": "Ŝargado…", + "media_gallery.hide": "Kaŝi", "moved_to_account_banner.text": "Via konto {disabledAccount} estas malvalidigita ĉar vi movis ĝin al {movedToAccount}.", + "mute_modal.show_options": "Montri agordojn", + "mute_modal.they_can_mention_and_follow": "Ili povas mencii kaj sekvi vin, sed vi ne vidos ilin.", "navigation_bar.about": "Pri", + "navigation_bar.administration": "Administrado", "navigation_bar.advanced_interface": "Malfermi altnivelan retpaĝan interfacon", "navigation_bar.blocks": "Blokitaj uzantoj", "navigation_bar.bookmarks": "Legosignoj", @@ -422,10 +441,18 @@ "notification.favourite": "{name} stelumis vian afiŝon", "notification.follow": "{name} eksekvis vin", "notification.follow_request": "{name} petis sekvi vin", + "notification.label.mention": "Mencii", + "notification.label.private_mention": "Privata mencio", + "notification.label.private_reply": "Privata respondo", + "notification.label.reply": "Respondi", + "notification.mention": "Mencii", + "notification.moderation-warning.learn_more": "Lerni pli", "notification.own_poll": "Via enketo finiĝis", "notification.reblog": "{name} diskonigis vian afiŝon", + "notification.relationships_severance_event.learn_more": "Lerni pli", "notification.status": "{name} ĵus afiŝis", "notification.update": "{name} redaktis afiŝon", + "notification_requests.accept": "Akcepti", "notifications.clear": "Forviŝi sciigojn", "notifications.clear_confirmation": "Ĉu vi certas, ke vi volas porĉiame forviŝi ĉiujn viajn sciigojn?", "notifications.column_settings.admin.report": "Novaj raportoj:", @@ -457,6 +484,8 @@ "notifications.permission_denied": "Labortablaj sciigoj ne disponeblas pro peto antaŭe rifuzita de retumiloj", "notifications.permission_denied_alert": "Labortablaj sciigoj ne povas esti ebligitaj, ĉar retumilpermeso antaŭe estis rifuzita", "notifications.permission_required": "Labortablaj sciigoj ne disponeblas ĉar la bezonata permeso ne estis donita.", + "notifications.policy.accept": "Akcepti", + "notifications.policy.filter_new_accounts_title": "Novaj kontoj", "notifications_permission_banner.enable": "Ŝalti retumilajn sciigojn", "notifications_permission_banner.how_to_control": "Por ricevi sciigojn kiam Mastodon ne estas malfermita, ebligu labortablajn sciigojn. Vi povas regi precize kiuj specoj de interagoj generas labortablajn sciigojn per la supra butono {icon} post kiam ili estas ebligitaj.", "notifications_permission_banner.title": "Neniam preterlasas iun ajn", @@ -581,6 +610,7 @@ "report_notification.attached_statuses": "{count, plural, one {{count} afiŝo almetita} other {{count} afiŝoj almetitaj}}", "report_notification.categories.legal": "Laŭleĝa", "report_notification.categories.other": "Alia", + "report_notification.categories.other_sentence": "alia", "report_notification.categories.spam": "Trudmesaĝo", "report_notification.categories.violation": "Malobservo de la regulo", "report_notification.open": "Malfermi la raporton", diff --git a/app/javascript/mastodon/locales/es-MX.json b/app/javascript/mastodon/locales/es-MX.json index d1abc392bf0950..7b09ada9f828e7 100644 --- a/app/javascript/mastodon/locales/es-MX.json +++ b/app/javascript/mastodon/locales/es-MX.json @@ -39,11 +39,11 @@ "account.following_counter": "{count, plural, one {{counter} siguiendo} other {{counter} siguiendo}}", "account.follows.empty": "Este usuario todavía no sigue a nadie.", "account.go_to_profile": "Ir al perfil", - "account.hide_reblogs": "Ocultar retoots de @{name}", + "account.hide_reblogs": "Ocultar impulsos de @{name}", "account.in_memoriam": "En memoria.", "account.joined_short": "Se unió", "account.languages": "Cambiar idiomas suscritos", - "account.link_verified_on": "El proprietario de este link fue comprobado el {date}", + "account.link_verified_on": "El proprietario de este enlace fue comprobado el {date}", "account.locked_info": "El estado de privacidad de esta cuenta està configurado como bloqueado. El proprietario debe revisar manualmente quien puede seguirle.", "account.media": "Multimedia", "account.mention": "Mencionar a @{name}", @@ -61,7 +61,7 @@ "account.requested": "Esperando aprobación. Haga clic para cancelar la solicitud de seguimiento", "account.requested_follow": "{name} ha solicitado seguirte", "account.share": "Compartir el perfil de @{name}", - "account.show_reblogs": "Mostrar retoots de @{name}", + "account.show_reblogs": "Mostrar impulsos de @{name}", "account.statuses_counter": "{count, plural, one {{counter} publicación} other {{counter} publicaciones}}", "account.unblock": "Desbloquear a @{name}", "account.unblock_domain": "Mostrar a {domain}", @@ -70,8 +70,8 @@ "account.unfollow": "Dejar de seguir", "account.unmute": "Dejar de silenciar a @{name}", "account.unmute_notifications_short": "Dejar de silenciar notificaciones", - "account.unmute_short": "Desmutear", - "account_note.placeholder": "Clic para añadir nota", + "account.unmute_short": "Dejar de silenciar", + "account_note.placeholder": "Haz clic para agregar una nota", "admin.dashboard.daily_retention": "Tasa de retención de usuarios por día después de unirse", "admin.dashboard.monthly_retention": "Tasa de retención de usuarios por mes después de unirse", "admin.dashboard.retention.average": "Promedio", @@ -97,7 +97,7 @@ "block_modal.title": "¿Bloquear usuario?", "block_modal.you_wont_see_mentions": "No verás publicaciones que los mencionen.", "boost_modal.combo": "Puedes hacer clic en {combo} para saltar este aviso la próxima vez", - "boost_modal.reblog": "¿Impulsar la publicación?", + "boost_modal.reblog": "¿Deseas impulsar la publicación?", "boost_modal.undo_reblog": "¿Dejar de impulsar la publicación?", "bundle_column_error.copy_stacktrace": "Copiar informe de error", "bundle_column_error.error.body": "La página solicitada no pudo ser renderizada. Podría deberse a un error en nuestro código o a un problema de compatibilidad con el navegador.", @@ -130,7 +130,7 @@ "column.lists": "Listas", "column.mutes": "Usuarios silenciados", "column.notifications": "Notificaciones", - "column.pins": "Toots fijados", + "column.pins": "Publicaciones fijadas", "column.public": "Línea de tiempo federada", "column_back_button.label": "Atrás", "column_header.hide_settings": "Ocultar configuración", @@ -148,10 +148,10 @@ "compose.published.body": "Publicado.", "compose.published.open": "Abrir", "compose.saved.body": "Publicación guardada.", - "compose_form.direct_message_warning_learn_more": "Aprender mas", + "compose_form.direct_message_warning_learn_more": "Saber más", "compose_form.encryption_warning": "Las publicaciones en Mastodon no están cifradas de extremo a extremo. No comparta ninguna información sensible en Mastodon.", - "compose_form.hashtag_warning": "Este toot no será listado bajo ningún hashtag dado que no es público. Solo toots públicos pueden ser buscados por hashtag.", - "compose_form.lock_disclaimer": "Tu cuenta no está bloqueada. Todos pueden seguirte para ver tus toots solo para seguidores.", + "compose_form.hashtag_warning": "Esta publicación no será listada bajo ninguna etiqueta dado que no es pública. Solo publicaciones públicas pueden ser buscadas por etiqueta.", + "compose_form.lock_disclaimer": "Tu cuenta no está {locked}. Todos pueden seguirte para ver tus publicaciones solo para seguidores.", "compose_form.lock_disclaimer.lock": "bloqueado", "compose_form.placeholder": "¿En qué estás pensando?", "compose_form.poll.duration": "Duración de la encuesta", @@ -165,32 +165,32 @@ "compose_form.publish_form": "Publicar", "compose_form.reply": "Respuesta", "compose_form.save_changes": "Actualización", - "compose_form.spoiler.marked": "Texto oculto tras la advertencia", - "compose_form.spoiler.unmarked": "Texto no oculto", + "compose_form.spoiler.marked": "Quitar advertencia de contenido", + "compose_form.spoiler.unmarked": "Añadir advertencia de contenido", "compose_form.spoiler_placeholder": "Advertencia de contenido (opcional)", "confirmation_modal.cancel": "Cancelar", "confirmations.block.confirm": "Bloquear", "confirmations.delete.confirm": "Eliminar", - "confirmations.delete.message": "¿Estás seguro de que quieres borrar este toot?", + "confirmations.delete.message": "¿Estás seguro de que quieres borrar esta publicación?", "confirmations.delete.title": "¿Eliminar publicación?", "confirmations.delete_list.confirm": "Eliminar", "confirmations.delete_list.message": "¿Seguro que quieres borrar esta lista permanentemente?", - "confirmations.delete_list.title": "¿Eliminar lista?", + "confirmations.delete_list.title": "¿Deseas eliminar la lista?", "confirmations.discard_edit_media.confirm": "Descartar", "confirmations.discard_edit_media.message": "Tienes cambios sin guardar en la descripción o vista previa del archivo, ¿deseas descartarlos de cualquier manera?", "confirmations.edit.confirm": "Editar", "confirmations.edit.message": "Editar sobrescribirá el mensaje que estás escribiendo. ¿Estás seguro de que deseas continuar?", - "confirmations.edit.title": "¿Sobrescribir publicación?", + "confirmations.edit.title": "¿Sobreescribir publicación?", "confirmations.logout.confirm": "Cerrar sesión", - "confirmations.logout.message": "¿Estás seguro de querer cerrar la sesión?", - "confirmations.logout.title": "¿Cerrar sesión?", + "confirmations.logout.message": "¿Estás seguro de que quieres cerrar la sesión?", + "confirmations.logout.title": "¿Deseas cerrar sesión?", "confirmations.mute.confirm": "Silenciar", "confirmations.redraft.confirm": "Borrar y volver a borrador", "confirmations.redraft.message": "¿Estás seguro que quieres borrar esta publicación y editarla? Los favoritos e impulsos se perderán, y las respuestas a la publicación original quedarán separadas.", "confirmations.redraft.title": "¿Borrar y volver a redactar la publicación?", "confirmations.reply.confirm": "Responder", "confirmations.reply.message": "Responder sobrescribirá el mensaje que estás escribiendo. ¿Estás seguro de que deseas continuar?", - "confirmations.reply.title": "¿Sobrescribir publicación?", + "confirmations.reply.title": "¿Sobreescribir publicación?", "confirmations.unfollow.confirm": "Dejar de seguir", "confirmations.unfollow.message": "¿Estás seguro de que quieres dejar de seguir a {name}?", "confirmations.unfollow.title": "¿Dejar de seguir al usuario?", @@ -213,8 +213,8 @@ "dismissable_banner.dismiss": "Descartar", "dismissable_banner.explore_links": "Estas noticias están siendo discutidas por personas en este y otros servidores de la red descentralizada en este momento.", "dismissable_banner.explore_statuses": "Estas son las publicaciones que están en tendencia en la red ahora. Las publicaciones recientes con más impulsos y favoritos se muestran más arriba.", - "dismissable_banner.explore_tags": "Se trata de hashtags que están ganando adeptos en las redes sociales hoy en día. Los hashtags que son utilizados por más personas diferentes se clasifican mejor.", - "dismissable_banner.public_timeline": "Estos son los toots públicos más recientes de personas en la web social a las que sigue la gente en {domain}.", + "dismissable_banner.explore_tags": "Se trata de etiquetas que están ganando adeptos en las redes sociales hoy en día. Las etiquetas que son utilizadas por más personas diferentes se clasifican mejor.", + "dismissable_banner.public_timeline": "Estas son las publicaciones públicas más recientes de personas en la web social a las que sigue la gente en {domain}.", "domain_block_modal.block": "Bloquear servidor", "domain_block_modal.block_account_instead": "Bloquear @{name} en su lugar", "domain_block_modal.they_can_interact_with_old_posts": "Las personas de este servidor pueden interactuar con tus publicaciones antiguas.", @@ -236,7 +236,7 @@ "domain_pill.your_handle": "Tu alias:", "domain_pill.your_server": "Tu hogar digital, donde residen todas tus publicaciones. ¿No te gusta este sitio? Muévete a otro servidor en cualquier momento y llévate a tus seguidores.", "domain_pill.your_username": "Tu identificador único en este servidor. Es posible encontrar usuarios con el mismo nombre de usuario en diferentes servidores.", - "embed.instructions": "Añade este toot a tu sitio web con el siguiente código.", + "embed.instructions": "Añade esta publicación a tu sitio web con el siguiente código.", "embed.preview": "Así es como se verá:", "emoji_button.activity": "Actividad", "emoji_button.clear": "Borrar", @@ -249,16 +249,16 @@ "emoji_button.objects": "Objetos", "emoji_button.people": "Gente", "emoji_button.recent": "Usados frecuentemente", - "emoji_button.search": "Buscar…", + "emoji_button.search": "Buscar...", "emoji_button.search_results": "Resultados de búsqueda", "emoji_button.symbols": "Símbolos", "emoji_button.travel": "Viajes y lugares", "empty_column.account_hides_collections": "Este usuario ha elegido no hacer disponible esta información", "empty_column.account_suspended": "Cuenta suspendida", - "empty_column.account_timeline": "¡No hay toots aquí!", + "empty_column.account_timeline": "¡No hay publicaciones aquí!", "empty_column.account_unavailable": "Perfil no disponible", "empty_column.blocks": "Aún no has bloqueado a ningún usuario.", - "empty_column.bookmarked_statuses": "Aún no tienes ningún toot guardado como marcador. Cuando guardes uno, se mostrará aquí.", + "empty_column.bookmarked_statuses": "Aún no tienes ninguna publicación guardada como marcador. Cuando guardes una, se mostrará aquí.", "empty_column.community": "La línea de tiempo local está vacía. ¡Escribe algo para empezar la fiesta!", "empty_column.direct": "Aún no tienes menciones privadas. Cuando envíes o recibas una, aparecerán aquí.", "empty_column.domain_blocks": "Todavía no hay dominios ocultos.", @@ -266,8 +266,8 @@ "empty_column.favourited_statuses": "Todavía no tienes publicaciones favoritas. Cuando le des favorito a una publicación se mostrarán acá.", "empty_column.favourites": "Todavía nadie marcó como favorito esta publicación. Cuando alguien lo haga, se mostrará aquí.", "empty_column.follow_requests": "No tienes ninguna petición de seguidor. Cuando recibas una, se mostrará aquí.", - "empty_column.followed_tags": "No estás siguiendo ningún hashtag todavía. Cuando lo hagas, aparecerá aquí.", - "empty_column.hashtag": "No hay nada en este hashtag aún.", + "empty_column.followed_tags": "No estás siguiendo ninguna etiqueta todavía. Cuando lo hagas, aparecerá aquí.", + "empty_column.hashtag": "No hay nada en esta etiqueta aún.", "empty_column.home": "No estás siguiendo a nadie aún. Visita {public} o haz búsquedas para empezar y conocer gente nueva.", "empty_column.list": "No hay nada en esta lista aún. Cuando miembros de esta lista publiquen nuevos estatus, estos aparecerán qui.", "empty_column.lists": "No tienes ninguna lista. cuando crees una, se mostrará aquí.", @@ -304,7 +304,7 @@ "filter_modal.select_filter.title": "Filtrar esta publicación", "filter_modal.title.status": "Filtrar una publicación", "filter_warning.matches_filter": "Coincide con el filtro “{title}”", - "filtered_notifications_banner.pending_requests": "De {count, plural, =0 {nadie} one {una persona} other {# personas}} que puede que conozcas", + "filtered_notifications_banner.pending_requests": "De {count, plural, =0 {nadie} one {una persona} other {# people}} que puede que tú conozcas", "filtered_notifications_banner.title": "Notificaciones filtradas", "firehose.all": "Todas", "firehose.local": "Este servidor", @@ -315,7 +315,7 @@ "follow_suggestions.curated_suggestion": "Recomendaciones del equipo", "follow_suggestions.dismiss": "No mostrar de nuevo", "follow_suggestions.featured_longer": "Escogidos por el equipo de {domain}", - "follow_suggestions.friends_of_friends_longer": "Populares entre las personas a las que sigues", + "follow_suggestions.friends_of_friends_longer": "Popular entre las personas a las que sigues", "follow_suggestions.hints.featured": "Este perfil ha sido seleccionado a mano por el equipo de {domain}.", "follow_suggestions.hints.friends_of_friends": "Este perfil es popular entre las personas que sigues.", "follow_suggestions.hints.most_followed": "Este perfil es uno de los más seguidos en {domain}.", @@ -323,11 +323,11 @@ "follow_suggestions.hints.similar_to_recently_followed": "Este perfil es similar a los perfiles que has seguido recientemente.", "follow_suggestions.personalized_suggestion": "Sugerencia personalizada", "follow_suggestions.popular_suggestion": "Sugerencia popular", - "follow_suggestions.popular_suggestion_longer": "Populares en {domain}", + "follow_suggestions.popular_suggestion_longer": "Popular en {domain}", "follow_suggestions.similar_to_recently_followed_longer": "Similares a los perfiles que has seguido recientemente", "follow_suggestions.view_all": "Ver todo", "follow_suggestions.who_to_follow": "Recomendamos seguir", - "followed_tags": "Hashtags seguidos", + "followed_tags": "Etiquetas seguidas", "footer.about": "Acerca de", "footer.directory": "Directorio de perfiles", "footer.get_app": "Obtener la aplicación", @@ -344,8 +344,8 @@ "hashtag.column_settings.select.no_options_message": "No se encontraron sugerencias", "hashtag.column_settings.select.placeholder": "Introducir etiquetas…", "hashtag.column_settings.tag_mode.all": "Todos estos", - "hashtag.column_settings.tag_mode.any": "Cualquiera de estos", - "hashtag.column_settings.tag_mode.none": "Ninguno de estos", + "hashtag.column_settings.tag_mode.any": "Cualquiera de estas", + "hashtag.column_settings.tag_mode.none": "Ninguna de estas", "hashtag.column_settings.tag_toggle": "Incluye etiquetas adicionales para esta columna", "hashtag.counter_by_accounts": "{count, plural, one {{counter} participante} other {{counter} participantes}}", "hashtag.counter_by_uses": "{count, plural, one {{counter} publicación} other {{counter} publicaciones}}", @@ -361,7 +361,7 @@ "hints.profiles.see_more_posts": "Ver más publicaciones en {domain}", "hints.threads.replies_may_be_missing": "Puede que no se muestren algunas respuestas de otros servidores.", "hints.threads.see_more": "Ver más respuestas en {domain}", - "home.column_settings.show_reblogs": "Mostrar retoots", + "home.column_settings.show_reblogs": "Mostrar impulsos", "home.column_settings.show_replies": "Mostrar respuestas", "home.hide_announcements": "Ocultar anuncios", "home.pending_critical_update.body": "¡Por favor actualiza tu servidor Mastodon lo antes posible!", @@ -369,7 +369,7 @@ "home.pending_critical_update.title": "¡Actualización de seguridad crítica disponible!", "home.show_announcements": "Mostrar anuncios", "ignore_notifications_modal.disclaimer": "Mastodon no puede informar a los usuarios que has ignorado sus notificaciones. Ignorar notificaciones no impedirá que se sigan enviando los mensajes.", - "ignore_notifications_modal.filter_instead": "Filtrar en vez de ignorar", + "ignore_notifications_modal.filter_instead": "Filtrar en su lugar", "ignore_notifications_modal.filter_to_act_users": "Aún podrás aceptar, rechazar o reportar usuarios", "ignore_notifications_modal.filter_to_avoid_confusion": "Filtrar ayuda a evitar confusiones potenciales", "ignore_notifications_modal.filter_to_review_separately": "Puedes revisar las notificaciones filtradas por separado", @@ -399,13 +399,13 @@ "intervals.full.minutes": "{number, plural, one {# minuto} other {# minutos}}", "keyboard_shortcuts.back": "volver atrás", "keyboard_shortcuts.blocked": "abrir una lista de usuarios bloqueados", - "keyboard_shortcuts.boost": "retootear", + "keyboard_shortcuts.boost": "Impulsar publicación", "keyboard_shortcuts.column": "enfocar un estado en una de las columnas", "keyboard_shortcuts.compose": "enfocar el área de texto de redacción", "keyboard_shortcuts.description": "Descripción", "keyboard_shortcuts.direct": "para abrir la columna de menciones privadas", "keyboard_shortcuts.down": "mover hacia abajo en la lista", - "keyboard_shortcuts.enter": "abrir estado", + "keyboard_shortcuts.enter": "Abrir publicación", "keyboard_shortcuts.favourite": "Marcar como favorita la publicación", "keyboard_shortcuts.favourites": "Abrir lista de favoritos", "keyboard_shortcuts.federated": "abrir el timeline federado", @@ -419,16 +419,16 @@ "keyboard_shortcuts.my_profile": "abrir tu perfil", "keyboard_shortcuts.notifications": "abrir la columna de notificaciones", "keyboard_shortcuts.open_media": "para abrir archivos multimedia", - "keyboard_shortcuts.pinned": "abrir la lista de toots destacados", + "keyboard_shortcuts.pinned": "Abrir la lista de publicaciones fijadas", "keyboard_shortcuts.profile": "abrir el perfil del autor", - "keyboard_shortcuts.reply": "para responder", + "keyboard_shortcuts.reply": "Responder a la publicación", "keyboard_shortcuts.requests": "abrir la lista de peticiones de seguidores", "keyboard_shortcuts.search": "para poner el foco en la búsqueda", "keyboard_shortcuts.spoilers": "para mostrar/ocultar el campo CW", "keyboard_shortcuts.start": "abrir la columna \"comenzar\"", "keyboard_shortcuts.toggle_hidden": "mostrar/ocultar texto tras aviso de contenido (CW)", "keyboard_shortcuts.toggle_sensitivity": "mostrar/ocultar medios", - "keyboard_shortcuts.toot": "para comenzar un nuevo toot", + "keyboard_shortcuts.toot": "Comenzar una nueva publicación", "keyboard_shortcuts.unfocus": "para retirar el foco de la caja de redacción/búsqueda", "keyboard_shortcuts.up": "para ir hacia arriba en la lista", "lightbox.close": "Cerrar", @@ -474,7 +474,7 @@ "navigation_bar.blocks": "Usuarios bloqueados", "navigation_bar.bookmarks": "Marcadores", "navigation_bar.community_timeline": "Historia local", - "navigation_bar.compose": "Escribir un nuevo toot", + "navigation_bar.compose": "Redactar una nueva publicación", "navigation_bar.direct": "Menciones privadas", "navigation_bar.discover": "Descubrir", "navigation_bar.domain_blocks": "Dominios ocultos", @@ -482,7 +482,7 @@ "navigation_bar.favourites": "Favoritos", "navigation_bar.filters": "Palabras silenciadas", "navigation_bar.follow_requests": "Solicitudes para seguirte", - "navigation_bar.followed_tags": "Hashtags seguidos", + "navigation_bar.followed_tags": "Etiquetas seguidas", "navigation_bar.follows_and_followers": "Siguiendo y seguidores", "navigation_bar.lists": "Listas", "navigation_bar.logout": "Cerrar sesión", @@ -490,25 +490,25 @@ "navigation_bar.mutes": "Usuarios silenciados", "navigation_bar.opened_in_classic_interface": "Publicaciones, cuentas y otras páginas específicas se abren por defecto en la interfaz web clásica.", "navigation_bar.personal": "Personal", - "navigation_bar.pins": "Toots fijados", + "navigation_bar.pins": "Publicaciones fijadas", "navigation_bar.preferences": "Preferencias", "navigation_bar.public_timeline": "Historia federada", "navigation_bar.search": "Buscar", "navigation_bar.security": "Seguridad", "not_signed_in_indicator.not_signed_in": "Necesitas iniciar sesión para acceder a este recurso.", "notification.admin.report": "{name} denunció a {target}", - "notification.admin.report_account": "{name} informó de {count, plural, one {una publicación} other {# publicaciones}} de {target} por {category}", - "notification.admin.report_account_other": "{name} informó de {count, plural, one {una publicación} other {# publicaciones}} de {target}", - "notification.admin.report_statuses": "{name} informó de {target} por {category}", - "notification.admin.report_statuses_other": "{name} informó de {target}", + "notification.admin.report_account": "{name} reportó {count, plural, one {una publicación} other {# publicaciones}} de {target} por {category}", + "notification.admin.report_account_other": "{name} reportó {count, plural, one {una publicación} other {# publicaciones}} de {target}", + "notification.admin.report_statuses": "{name} reportó {target} por {category}", + "notification.admin.report_statuses_other": "{name} reportó {target}", "notification.admin.sign_up": "{name} se unio", - "notification.admin.sign_up.name_and_others": "{name} y {count, plural, one {# más} other {# más}} se registraron", + "notification.admin.sign_up.name_and_others": "{name} y {count, plural, one {# otro} other {# otros}} se registraron", "notification.favourite": "{name} marcó como favorita tu publicación", - "notification.favourite.name_and_others_with_link": "{name} y {count, plural, one {# más} other {# más}} marcaron tu publicación como favorita", + "notification.favourite.name_and_others_with_link": "{name} y {count, plural, one {# otro} other {# otros}} marcaron tu publicación como favorita", "notification.follow": "{name} te empezó a seguir", - "notification.follow.name_and_others": "{name} y {count, plural, one {# más} other {# más}} te siguieron", + "notification.follow.name_and_others": "{name} y {count, plural, one {# otro} other {# otros}} te siguieron", "notification.follow_request": "{name} ha solicitado seguirte", - "notification.follow_request.name_and_others": "{name} y {count, plural, one {# más} other {# más}} han solicitado seguirte", + "notification.follow_request.name_and_others": "{name} y {count, plural, one {# otro} other {# otros}} han solicitado seguirte", "notification.label.mention": "Mención", "notification.label.private_mention": "Mención privada", "notification.label.private_reply": "Respuesta privada", @@ -519,14 +519,14 @@ "notification.moderation_warning.action_delete_statuses": "Se han eliminado algunas de tus publicaciones.", "notification.moderation_warning.action_disable": "Tu cuenta ha sido desactivada.", "notification.moderation_warning.action_mark_statuses_as_sensitive": "Se han marcado como sensibles algunas de tus publicaciones.", - "notification.moderation_warning.action_none": "Tu cuenta ha recibido un aviso de moderación.", + "notification.moderation_warning.action_none": "Tu cuenta ha recibido una advertencia de moderación.", "notification.moderation_warning.action_sensitive": "De ahora en adelante, todas tus publicaciones se marcarán como sensibles.", "notification.moderation_warning.action_silence": "Tu cuenta ha sido limitada.", "notification.moderation_warning.action_suspend": "Tu cuenta ha sido suspendida.", "notification.own_poll": "Tu encuesta ha terminado", - "notification.poll": "Una encuesta ha terminado", - "notification.reblog": "{name} ha retooteado tu estado", - "notification.reblog.name_and_others_with_link": "{name} y {count, plural, one {# más} other {# más}} impulsaron tu publicación", + "notification.poll": "Una encuesta en la que has votado ha terminado", + "notification.reblog": "{name} ha impulsado tu publicación", + "notification.reblog.name_and_others_with_link": "{name} y {count, plural, one {# otro} other {# otros}} impulsaron tu publicación", "notification.relationships_severance_event": "Conexiones perdidas con {name}", "notification.relationships_severance_event.account_suspension": "Un administrador de {from} ha suspendido {target}, lo que significa que ya no puedes recibir actualizaciones de sus cuentas o interactuar con ellas.", "notification.relationships_severance_event.domain_block": "Un administrador de {from} ha bloqueado {target}, incluyendo {followersCount} de tus seguidores y {followingCount, plural, one {# cuenta} other {# cuentas}} que sigues.", @@ -536,7 +536,7 @@ "notification.update": "{name} editó una publicación", "notification_requests.accept": "Aceptar", "notification_requests.accept_multiple": "{count, plural, one {Aceptar # solicitud…} other {Aceptar # solicitudes…}}", - "notification_requests.confirm_accept_multiple.button": "{count, plural, one {Aceptar solicitud} other {Aceptar solicitudes}}", + "notification_requests.confirm_accept_multiple.button": "{count, plural, one {Solicitud aceptada} other {Solicitudes aceptadas}}", "notification_requests.confirm_accept_multiple.message": "Vas a aceptar {count, plural, one {una solicitud} other {# solicitudes}}. ¿Quieres continuar?", "notification_requests.confirm_accept_multiple.title": "¿Aceptar las solicitudes?", "notification_requests.confirm_dismiss_multiple.button": "{count, plural, one {Descartar solicitud} other {Descartar solicitudes}}", @@ -546,8 +546,8 @@ "notification_requests.dismiss_multiple": "{count, plural, one {Descartar # solicitud…} other {Descartar # solicitudes…}}", "notification_requests.edit_selection": "Editar", "notification_requests.exit_selection": "Hecho", - "notification_requests.explainer_for_limited_account": "Las notificaciones de esta cuenta han sido filtradas porque la cuenta ha sido limitada por un moderador.", - "notification_requests.explainer_for_limited_remote_account": "Las notificaciones de esta cuenta han sido filtradas porque la cuenta o su servidor ha sido limitada por un moderador.", + "notification_requests.explainer_for_limited_account": "Las notificaciones de esta cuenta han sido filtradas, ya que la cuenta ha sido limitada por un moderador.", + "notification_requests.explainer_for_limited_remote_account": "Las notificaciones de esta cuenta han sido filtradas, ya que la cuenta o su servidor ha sido limitada por un moderador.", "notification_requests.maximize": "Maximizar", "notification_requests.minimize_banner": "Minimizar banner de notificaciones filtradas", "notification_requests.notifications_from": "Notificaciones de {name}", @@ -555,7 +555,7 @@ "notification_requests.view": "Ver notificaciones", "notifications.clear": "Limpiar notificaciones", "notifications.clear_confirmation": "¿Seguro de querer borrar permanentemente todas tus notificaciones?", - "notifications.clear_title": "¿Borrar notificaciones?", + "notifications.clear_title": "¿Limpiar notificaciones?", "notifications.column_settings.admin.report": "Nuevas denuncias:", "notifications.column_settings.admin.sign_up": "Registros nuevos:", "notifications.column_settings.alert": "Notificaciones de escritorio", @@ -567,7 +567,7 @@ "notifications.column_settings.mention": "Menciones:", "notifications.column_settings.poll": "Resultados de la votación:", "notifications.column_settings.push": "Notificaciones push", - "notifications.column_settings.reblog": "Retoots:", + "notifications.column_settings.reblog": "Impulsos:", "notifications.column_settings.show": "Mostrar en columna", "notifications.column_settings.sound": "Reproducir sonido", "notifications.column_settings.status": "Nuevas publicaciones:", @@ -575,7 +575,7 @@ "notifications.column_settings.unread_notifications.highlight": "Destacar notificaciones no leídas", "notifications.column_settings.update": "Ediciones:", "notifications.filter.all": "Todos", - "notifications.filter.boosts": "Retoots", + "notifications.filter.boosts": "Impulsos", "notifications.filter.favourites": "Favoritos", "notifications.filter.follows": "Seguidores", "notifications.filter.mentions": "Menciones", @@ -621,7 +621,7 @@ "onboarding.profile.display_name_hint": "Tu nombre completo o tu apodo…", "onboarding.profile.lead": "Siempre puedes completar esto más tarde en los ajustes, donde hay aún más opciones de personalización disponibles.", "onboarding.profile.note": "Biografía", - "onboarding.profile.note_hint": "Puedes @mencionar a otras personas o #hashtags…", + "onboarding.profile.note_hint": "Puedes @mencionar a otras personas o #etiquetas…", "onboarding.profile.save_and_continue": "Guardar y continuar", "onboarding.profile.title": "Configuración del perfil", "onboarding.profile.upload_avatar": "Subir foto de perfil", @@ -639,7 +639,7 @@ "onboarding.steps.publish_status.title": "Escribe tu primera publicación", "onboarding.steps.setup_profile.body": "Si rellenas tu perfil tendrás más posibilidades de que otros interactúen contigo.", "onboarding.steps.setup_profile.title": "Personaliza tu perfil", - "onboarding.steps.share_profile.body": "¡Dile a tus amigos cómo encontrarte en Mastodon!", + "onboarding.steps.share_profile.body": "Dile a tus amigos cómo encontrarte en Mastodon", "onboarding.steps.share_profile.title": "Comparte tu perfil", "onboarding.tips.2fa": "¿Sabías que? Puedes proteger tu cuenta configurando la autenticación de dos factores en la configuración de su cuenta. Funciona con cualquier aplicación TOTP de su elección, ¡no necesitas número de teléfono!", "onboarding.tips.accounts_from_other_servers": "¿Sabías que? Como Mastodon es descentralizado, algunos perfiles que encuentras están alojados en servidores distintos del tuyo. Y sin embargo, ¡puedes interactuar con ellos! ¡Su servidor corresponde a la segunda mitad de su nombre de usuario!", @@ -665,7 +665,7 @@ "privacy.private.short": "Seguidores", "privacy.public.long": "Cualquiera dentro y fuera de Mastodon", "privacy.public.short": "Público", - "privacy.unlisted.additional": "Esto se comporta exactamente igual que el público, excepto que el post no aparecerá en las cronologías en directo o en los hashtags, la exploración o busquedas en Mastodon, incluso si está optado por activar la cuenta de usuario.", + "privacy.unlisted.additional": "Esto se comporta exactamente igual que el público, excepto que el post no aparecerá en las cronologías en directo o en las etiquetas, la exploración o busquedas en Mastodon, incluso si está optado por activar la cuenta de usuario.", "privacy.unlisted.long": "Menos fanfares algorítmicos", "privacy.unlisted.short": "Público silencioso", "privacy_policy.last_updated": "Actualizado por última vez {date}", @@ -699,7 +699,7 @@ "report.category.title_account": "perfil", "report.category.title_status": "publicación", "report.close": "Realizado", - "report.comment.title": "¿Hay algo más que usted cree que debamos saber?", + "report.comment.title": "¿Hay algo más que creas que deberíamos saber?", "report.forward": "Reenviar a {target}", "report.forward_hint": "Esta cuenta es de otro servidor. ¿Enviar una copia anonimizada del informe allí también?", "report.mute": "Silenciar", @@ -776,9 +776,9 @@ "status.admin_status": "Abrir este estado en la interfaz de moderación", "status.block": "Bloquear a @{name}", "status.bookmark": "Añadir marcador", - "status.cancel_reblog_private": "Eliminar retoot", - "status.cannot_reblog": "Este toot no puede retootearse", - "status.continued_thread": "Continuó el hilo", + "status.cancel_reblog_private": "Deshacer impulso", + "status.cannot_reblog": "Esta publicación no puede ser impulsada", + "status.continued_thread": "Hilo continuado", "status.copy": "Copiar enlace al estado", "status.delete": "Borrar", "status.detailed_status": "Vista de conversación detallada", @@ -803,16 +803,16 @@ "status.mute_conversation": "Silenciar conversación", "status.open": "Expandir estado", "status.pin": "Fijar", - "status.pinned": "Toot fijado", + "status.pinned": "Publicación fijada", "status.read_more": "Leer más", - "status.reblog": "Retootear", + "status.reblog": "Impulsar", "status.reblog_private": "Implusar a la audiencia original", - "status.reblogged_by": "Retooteado por {name}", + "status.reblogged_by": "Impulsado por {name}", "status.reblogs": "{count, plural, one {impulso} other {impulsos}}", - "status.reblogs.empty": "Nadie retooteó este toot todavía. Cuando alguien lo haga, aparecerá aquí.", + "status.reblogs.empty": "Nadie impulsó esta publicación todavía. Cuando alguien lo haga, aparecerá aquí.", "status.redraft": "Borrar y volver a borrador", "status.remove_bookmark": "Eliminar marcador", - "status.replied_in_thread": "Respondió en el hilo", + "status.replied_in_thread": "Respondido en el hilo", "status.replied_to": "Respondió a {name}", "status.reply": "Responder", "status.replyAll": "Responder al hilo", diff --git a/app/javascript/mastodon/locales/fr-CA.json b/app/javascript/mastodon/locales/fr-CA.json index e89256bc2f80bb..8e33c6844290cc 100644 --- a/app/javascript/mastodon/locales/fr-CA.json +++ b/app/javascript/mastodon/locales/fr-CA.json @@ -312,7 +312,7 @@ "follow_request.authorize": "Autoriser", "follow_request.reject": "Rejeter", "follow_requests.unlocked_explanation": "Même si votre compte n’est pas privé, l’équipe de {domain} a pensé que vous pourriez vouloir peut-être consulter manuellement les demandes d'abonnement de ces comptes.", - "follow_suggestions.curated_suggestion": "Choix du staff", + "follow_suggestions.curated_suggestion": "Sélectionné par l'équipe", "follow_suggestions.dismiss": "Ne plus afficher", "follow_suggestions.featured_longer": "Sélectionné par l'équipe de {domain}", "follow_suggestions.friends_of_friends_longer": "Populaire dans le cercle des personnes que vous suivez", diff --git a/app/javascript/mastodon/locales/fr.json b/app/javascript/mastodon/locales/fr.json index 03cb6471d6d068..e73ddf734f1b5e 100644 --- a/app/javascript/mastodon/locales/fr.json +++ b/app/javascript/mastodon/locales/fr.json @@ -312,7 +312,7 @@ "follow_request.authorize": "Accepter", "follow_request.reject": "Rejeter", "follow_requests.unlocked_explanation": "Même si votre compte n’est pas privé, l’équipe de {domain} a pensé que vous pourriez vouloir consulter manuellement les demandes de suivi de ces comptes.", - "follow_suggestions.curated_suggestion": "Choix du staff", + "follow_suggestions.curated_suggestion": "Sélectionné par l'équipe", "follow_suggestions.dismiss": "Ne plus afficher", "follow_suggestions.featured_longer": "Sélectionné par l'équipe de {domain}", "follow_suggestions.friends_of_friends_longer": "Populaire dans le cercle des personnes que vous suivez", diff --git a/app/javascript/mastodon/locales/gd.json b/app/javascript/mastodon/locales/gd.json index 97748e0ee97b1e..2b93662694aed6 100644 --- a/app/javascript/mastodon/locales/gd.json +++ b/app/javascript/mastodon/locales/gd.json @@ -36,7 +36,7 @@ "account.followers.empty": "Chan eil neach sam bith a’ leantainn air a’ chleachdaiche seo fhathast.", "account.followers_counter": "{count, plural, one {{counter} neach-leantainn} other {{counter} luchd-leantainn}}", "account.following": "A’ leantainn", - "account.following_counter": "{count, plural, one {Tha {counter} a’ leantainn} other {Tha {counter} a’ leantainn}}", + "account.following_counter": "{count, plural, one {A’ leantainn {counter}} other {A’ leantainn {counter}}}", "account.follows.empty": "Chan eil an cleachdaiche seo a’ leantainn neach sam bith fhathast.", "account.go_to_profile": "Tadhail air a’ phròifil", "account.hide_reblogs": "Falaich na brosnachaidhean o @{name}", diff --git a/app/javascript/mastodon/locales/he.json b/app/javascript/mastodon/locales/he.json index de20d505c04c06..5e6ab56c238909 100644 --- a/app/javascript/mastodon/locales/he.json +++ b/app/javascript/mastodon/locales/he.json @@ -787,6 +787,7 @@ "status.edit": "עריכה", "status.edited": "נערך לאחרונה {date}", "status.edited_x_times": "נערך {count, plural, one {פעם {count}} other {{count} פעמים}}", + "status.embed": "העתקת קוד להטמעה", "status.favourite": "חיבוב", "status.favourites": "{count, plural, one {חיבוב אחד} two {זוג חיבובים} other {# חיבובים}}", "status.filter": "סנן הודעה זו", diff --git a/app/javascript/mastodon/locales/kab.json b/app/javascript/mastodon/locales/kab.json index 24286a119feb98..822fb59e6f85f8 100644 --- a/app/javascript/mastodon/locales/kab.json +++ b/app/javascript/mastodon/locales/kab.json @@ -2,12 +2,14 @@ "about.blocks": "Ulac agbur", "about.contact": "Anermis:", "about.disclaimer": "Mastodon d aseɣẓan ilelli, d aseɣẓan n uɣbalu yeldin, d tnezzut n Mastodon gGmbH.", + "about.domain_blocks.no_reason_available": "Ulac taɣẓint", "about.domain_blocks.preamble": "Maṣṭudun s umata yeḍmen-ak ad teẓreḍ agbur, ad tesdemreḍ akked yimseqdacen-nniḍen seg yal aqeddac deg fedivers. Ha-tent-an ɣur-k tsuraf i yellan deg uqeddac-agi.", "about.domain_blocks.silenced.title": "Ɣur-s talast", "about.domain_blocks.suspended.title": "Yeḥbes", "about.not_available": "Talɣut-a ur tettwabder ara deg uqeddac-a.", "about.powered_by": "Azeṭṭa inmetti yettwasɣelsen sɣur {mastodon}", "about.rules": "Ilugan n uqeddac", + "account.account_note_header": "Tamawt tudmawant", "account.add_or_remove_from_list": "Rnu neɣ kkes seg tebdarin", "account.badges.bot": "Aṛubut", "account.badges.group": "Agraw", @@ -46,6 +48,7 @@ "account.mute_notifications_short": "Susem alɣuten", "account.mute_short": "Sgugem", "account.muted": "Yettwasgugem", + "account.mutual": "Temṭafarem", "account.no_bio": "Ulac aglam i d-yettunefken.", "account.open_original_page": "Ldi asebter anasli", "account.posts": "Tisuffaɣ", @@ -62,6 +65,7 @@ "account.unendorse": "Ur ttwellih ara fell-as deg umaɣnu-inek", "account.unfollow": "Ur ṭṭafaṛ ara", "account.unmute": "Kkes asgugem ɣef @{name}", + "account.unmute_notifications_short": "Serreḥ i yilɣa", "account.unmute_short": "Kkes asgugem", "account_note.placeholder": "Ulac iwenniten", "admin.dashboard.retention.cohort_size": "Iseqdacen imaynuten", @@ -152,6 +156,7 @@ "confirmations.edit.message": "Abeddel tura ad d-yaru izen-nni i d-tegreḍ akka tura. Tetḥeqqeḍ tebɣiḍ ad tkemmleḍ?", "confirmations.logout.confirm": "Ffeɣ", "confirmations.logout.message": "D tidet tebɣiḍ ad teffɣeḍ?", + "confirmations.logout.title": "Tebɣiḍ ad teffɣeḍ ssya?", "confirmations.mute.confirm": "Sgugem", "confirmations.redraft.confirm": "Kkes sakin ɛiwed tira", "confirmations.reply.confirm": "Err", @@ -351,6 +356,7 @@ "lists.subheading": "Tibdarin-ik·im", "load_pending": "{count, plural, one {# n uferdis amaynut} other {# n yiferdisen imaynuten}}", "loading_indicator.label": "Yessalay-d …", + "media_gallery.hide": "Ffer-it", "mute_modal.hide_from_notifications": "Ffer-it deg ulɣuten", "mute_modal.hide_options": "Ffer tinefrunin", "mute_modal.indefinite": "Alamma ssnesreɣ asgugem fell-as", @@ -405,6 +411,7 @@ "notification.status": "{name} akken i d-yessufeɣ", "notification_requests.accept": "Qbel", "notification_requests.dismiss": "Agi", + "notification_requests.edit_selection": "Ẓreg", "notification_requests.exit_selection": "Immed", "notification_requests.notifications_from": "Alɣuten sɣur {name}", "notifications.clear": "Sfeḍ alɣuten", diff --git a/config/locales/es-MX.yml b/config/locales/es-MX.yml index ebe26c3f14f398..1ea346a00c7119 100644 --- a/config/locales/es-MX.yml +++ b/config/locales/es-MX.yml @@ -175,7 +175,7 @@ es-MX: approve_appeal: Aprobar apelación approve_user: Aprobar Usuario assigned_to_self_report: Asignar Reporte - change_email_user: Cambiar Correo Electrónico del Usuario + change_email_user: Cambiar correo electrónico por usuario change_role_user: Cambiar rol del usuario confirm_user: Confirmar Usuario create_account_warning: Crear Advertencia @@ -761,7 +761,7 @@ es-MX: desc_html: Esto se basa en scripts externos de hCaptcha, que pueden suponer una preocupación de seguridad y privacidad. Además, esto puede volver el proceso de registro significativamente menos accesible para algunas personas (especialmente con discapacidades). Por estas razones, por favor, considera medidas alternativas como el registro por aprobación manual o con invitación. title: Solicita a los nuevos usuarios que resuelvan un CAPTCHA para confirmar su cuenta content_retention: - danger_zone: Zona peligrosa + danger_zone: Zona de peligro preamble: Controlar cómo el contenido generado por el usuario se almacena en Mastodon. title: Retención de contenido default_noindex: @@ -944,9 +944,9 @@ es-MX: statuses: allow: Permitir publicación allow_account: Permitir autor - confirm_allow: "¿Estás seguro de que deseas permitir los estados seleccionados?" + confirm_allow: "¿Estás seguro de que deseas permitir las publicaciones seleccionadas?" confirm_allow_account: "¿Estás seguro de que deseas permitir las cuentas seleccionadas?" - confirm_disallow: "¿Estás seguro de que deseas restringir los estados seleccionados?" + confirm_disallow: "¿Estás seguro de que deseas restringir las publicaciones seleccionadas?" confirm_disallow_account: "¿Estás seguro de que deseas restringir las cuentas seleccionadas?" description_html: Estos son publicaciones que su servidor conoce que están siendo compartidas y marcadas como favoritas mucho en este momento. Pueden ayudar a tus usuarios nuevos y retornantes a encontrar más gente a la que seguir. No hay mensajes que se muestren públicamente hasta que apruebes el autor y el autor permita que su cuenta sea sugerida a otros. También puedes permitir o rechazar mensajes individuales. disallow: Rechazar publicación @@ -1152,7 +1152,7 @@ es-MX: title: Crear cuenta de Mastodon en %{domain}. status: account_status: Estado de la cuenta - confirming: Esperando confirmación de correo electrónico. + confirming: Esperando a que se complete la confirmación por correo electrónico. functional: Tu cuenta está completamente operativa. pending: Tu solicitud está pendiente de revisión por nuestro personal. Eso puede tardar un tiempo. Recibirás un correo electrónico cuando tu solicitud sea aprobada. redirecting_to: Tu cuenta se encuentra inactiva porque está siendo redirigida a %{acct}. @@ -1538,7 +1538,7 @@ es-MX: update: subject: "%{name} editó una publicación" notifications: - administration_emails: Notificaciones administrativas por correo + administration_emails: Notificaciones de administración por correo electrónico email_events: Eventos para notificaciones por correo electrónico email_events_hint: 'Selecciona los eventos para los que deseas recibir notificaciones:' number: diff --git a/config/locales/kab.yml b/config/locales/kab.yml index 7c3d526702665e..7044983ac9fe90 100644 --- a/config/locales/kab.yml +++ b/config/locales/kab.yml @@ -703,6 +703,7 @@ kab: prev: Win iɛeddan preferences: other: Wiyaḍ + posting_defaults: Iɣewwaṛen n usuffeɣ imezwura privacy: privacy: Tabaḍnit search: Anadi @@ -779,6 +780,7 @@ kab: import: Kter import_and_export: Taktert d usifeḍ migrate: Tunigin n umiḍan + notifications: Alɣuten s imayl preferences: Imenyafen profile: Ameɣnu relationships: Imeḍfaṛen akked wid i teṭṭafaṛeḍ diff --git a/config/locales/lv.yml b/config/locales/lv.yml index 55a0751811efad..88faeae82bd3dc 100644 --- a/config/locales/lv.yml +++ b/config/locales/lv.yml @@ -1,7 +1,7 @@ --- lv: about: - about_mastodon_html: 'Nākotnes sociālais tīkls: bez reklāmām, bez korporatīvās uzraudzības, ētisks dizains un decentralizācija! Pārvaldi savus datus ar Mastodon!' + about_mastodon_html: 'Nākotnes sabiedriskais tīkls: bez reklāmām, bez korporatīvās novērošanas, ētiska projektēšana un decentralizēšana. Pārvaldi savus datus ar Mastodon!' contact_missing: Nav uzstādīts contact_unavailable: N/A hosted_on: Mastodon mitināts %{domain} @@ -613,6 +613,7 @@ lv: created_at: Ziņoti delete_and_resolve: Izdzēst rakstus forwarded: Pārsūtīti + forwarded_replies_explanation: Šis ziņojums ir no attāla lietotāja un par attālu saturu. Tas tika pārvirzīts šeit, jo saturs, par kuru tika ziņots, ir atbilde vienam no šī servera lietotājiem. forwarded_to: Pārsūtīti %{domain} mark_as_resolved: Atzīmēt kā atrisinātu mark_as_sensitive: Atzīmēt kā sensitīvu @@ -633,6 +634,7 @@ lv: report: 'Ziņojums #%{id}' reported_account: Ziņotais konts reported_by: Ziņoja + reported_with_application: Ziņots no lietotnes resolved: Atrisināts resolved_msg: Ziņojums veiksmīgi atrisināts! skip_to_actions: Pāriet uz darbībām @@ -876,7 +878,13 @@ lv: pending_review: Gaida pārskatīšanu review_requested: Pieprasīta pārskatīšana reviewed: Pārskatīts + title: Stāvoklis + name: Nosaukums + newest: Jaunākie + oldest: Vecākie + reset: Atiestatīt review: Pārskatīt stāvokli + search: Meklēt title: Tēmturi updated_msg: Tēmtura iestatījumi ir veiksmīgi atjaunināti title: Administrēšana @@ -1121,6 +1129,9 @@ lv: view_strikes: Skati iepriekšējos brīdinājumus par savu kontu too_fast: Veidlapa ir iesniegta pārāk ātri, mēģini vēlreiz. use_security_key: Lietot drošības atslēgu + author_attribution: + s_blog: "%{name} emuāri" + title: Autora attiecinājums challenge: confirm: Turpināt hint_html: "Padoms: Nākamās stundas laikā mēs tev vairs neprasīsim paroli." @@ -1158,6 +1169,9 @@ lv: before: 'Pirms turpināšanas lūgums uzmanīgi izlasīt šīs piezīmes:' caches: Citu serveru kešatmiņā saglabātais saturs var saglabāties data_removal: Tavas ziņas un citi dati tiks neatgriezeniski noņemti + email_change_html: Savu e-pasta adresi var mainīt bez sava konta izdzēšanas + email_contact_html: Ja tas joprojām nav saņemts, var nosūtīt e-pastu uz %{email}, lai saņemtu palīdzību + email_reconfirmation_html: Ja netiek saņemts apstiprinājuma e-pasta ziņojums, to var pieprasīt vēlreiz irreversible: Tu nevarēsi atjaunot vai atkārtoti aktivizēt savu kontu more_details_html: Plašāku informāciju skatīt privātuma politika. username_available: Tavs lietotājvārds atkal būs pieejams @@ -1377,6 +1391,7 @@ lv: '86400': 1 diena expires_in_prompt: Nekad generate: Ģenerēt uzaicinājuma saiti + invalid: Šis uzaicinājums nav derīgs invited_by: 'Tevi uzaicināja:' max_uses: one: 1 lietojums @@ -1395,6 +1410,7 @@ lv: authentication_methods: otp: divpakāpju autentifikācijas lietotne password: parole + sign_in_token: e-pasta drošības kods webauthn: drošības atslēgas description_html: Ja pamani darbības, kuras neatpazīsti, jāapsver iespēja nomainīt savu paroli un iespējot divpakāpju autentifikāciju. empty: Nav pieejama autentifikācijas vēsture @@ -1820,14 +1836,20 @@ lv: explanation: Šeit ir daži padomi, kā sākt darbu feature_action: Uzzināt vairāk feature_creativity: Mastodon nodrošina skaņas, video un attēlu ierakstus, pieejamības aprakstus, aptaujas, satura brīdinājumus, animētus profila attēlus, pielāgotas emocijzīmes, sīktēlu apgriešanas vadīklas un vēl, lai palīdzētu Tev sevi izpaust tiešsaistē. Vai Tu izplati savu mākslu, mūziku vai aplādes, Mastodon ir šeit ar Tevi. + feature_moderation_title: Moderēšana, kādai tai būtu jābūt follow_action: Sekot + follow_step: Sekošana aizraujošiem cilvēkiem ir viss, par ko ir Mastodon. follow_title: Pielāgo savu mājas barotni + follows_subtitle: Seko labi zināmiem kontiem follows_title: Kam sekot follows_view_more: Rādīt vairāk cilvēku, kuriem sekot hashtags_recent_count: one: "%{people} cilvēks pēdējās 2 dienās" other: "%{people} cilvēki pēdējās 2 dienās" zero: "%{people} cilvēku pēdējās divās dienās" + hashtags_subtitle: Izpēti, kas pēdējās divās dienāš ir piesasitījis cilvēku uzmanību + hashtags_title: Izplatīti tēmturi + hashtags_view_more: Skatīt vairāk izplatītu tēmturu post_action: Rakstīt post_step: Pasveicini pasauli ar tekstu, fotoattēliem, video vai aptaujām! post_title: Izveido savu pirmo ierakstu @@ -1843,6 +1865,7 @@ lv: invalid_otp_token: Nederīgs divfaktora kods otp_lost_help_html: Ja esi zaudējis piekļuvi abiem, tu vari sazināties ar %{email} rate_limited: Pārāk daudz autentifikācijas mēģinājumu, vēlāk jāmēģina vēlreiz. + seamless_external_login: Tu esi pieteicies caur ārēju pakalpojumu, tāpēc paroles un e-pasta iestatījumi nav pieejami. signed_in_as: 'Pieteicies kā:' verification: extra_instructions_html: Padoms: saite Tavā vietnē var būt neredzama. Svarīga daļa ir rel="me", kas novērš uzdošanos vietnēs ar lietotāju izveidotu saturu. Tu pat vari lapas galvenē izmantot tagu link, nevis a, taču HTML ir jābūt pieejamam bez JavaScript izpildīšanas. @@ -1851,6 +1874,7 @@ lv: instructions_html: Ievieto starpliktuvē un ielīmē tālāk norādīto kodu savas tīmekļvietnes HTML! Tad pievieno savas tīmekļvietnes adresi vienā no papildu laukiem savā profila cilnē "Labot profilu" un saglabā izmaiņas! verification: Pārbaude verified_links: Tavas verifikācijas saites + website_verification: Tīmekļvietnes apliecināšana webauthn_credentials: add: Pievienot jaunu drošības atslēgu create: diff --git a/config/locales/simple_form.es-MX.yml b/config/locales/simple_form.es-MX.yml index 2a2d06ce9b2974..3271b75668f75f 100644 --- a/config/locales/simple_form.es-MX.yml +++ b/config/locales/simple_form.es-MX.yml @@ -16,12 +16,12 @@ es-MX: account_migration: acct: Especifique el nombre de usuario@dominio de la cuenta a la cual desea migrar account_warning_preset: - text: Puede usar sintaxis de toots, como URLs, hashtags y menciones + text: Puede usar sintaxis de publicaciones, como URLs, etiquetas y menciones title: Opcional. No visible para el destinatario admin_account_action: - include_statuses: El usuario verá qué toots han causado la acción de moderación o advertencia + include_statuses: El usuario verá qué publicaciones han causado la acción de moderación o advertencia send_email_notification: El usuario recibirá una explicación de lo que sucedió con respecto a su cuenta - text_html: Opcional. Puede usar sintaxis de toots. Puede añadir configuraciones predefinidas de advertencia para ahorrar tiempo + text_html: Opcional. Puede usar sintaxis de publicaciones. Puede añadir configuraciones predefinidas de advertencia para ahorrar tiempo type_html: Elige qué hacer con %{acct} types: disable: Evitar que el usuario utilice su cuenta, pero no eliminar ni ocultar sus contenidos. @@ -35,7 +35,7 @@ es-MX: ends_at: Opcional. El anuncio desaparecerá automáticamente en este momento scheduled_at: Dejar en blanco para publicar el anuncio inmediatamente starts_at: Opcional. En caso de que su anuncio esté vinculado a un intervalo de tiempo específico - text: Puedes usar la sintaxis toot. Por favor ten en cuenta el espacio que ocupará el anuncio en la pantalla del usuario + text: Puedes usar la sintaxis de publicaciones. Por favor ten en cuenta el espacio que ocupará el anuncio en la pantalla del usuario appeal: text: Sólo puede apelar una amonestación a la vez defaults: @@ -49,12 +49,12 @@ es-MX: email: Se le enviará un correo de confirmación header: WEBP, PNG, GIF o JPG. Máximo %{size}. Será escalado a %{dimensions}px inbox_url: Copia la URL de la página principal del relés que quieres utilizar - irreversible: Los toots filtrados desaparecerán irreversiblemente, incluso si este filtro es eliminado más adelante + irreversible: Las publicaciones filtradas desaparecerán irreversiblemente, incluso si este filtro es eliminado más adelante locale: El idioma de la interfaz de usuario, correos y notificaciones push password: Utilice al menos 8 caracteres - phrase: Se aplicará sin importar las mayúsculas o los avisos de contenido de un toot + phrase: Se aplicará sin importar las mayúsculas o los avisos de contenido de una publicación scopes: Qué APIs de la aplicación tendrán acceso. Si seleccionas el alcance de nivel mas alto, no necesitas seleccionar las individuales. - setting_aggregate_reblogs: No mostrar nuevos retoots para los toots que han sido recientemente retooteados (sólo afecta a los retoots recibidos recientemente) + setting_aggregate_reblogs: No mostrar nuevos impulsos para las publicaciones que han sido recientemente impulsadas (sólo afecta a las publicaciones recibidas recientemente) setting_always_send_emails: Normalmente las notificaciones por correo electrónico no se enviarán cuando estés usando Mastodon activamente setting_default_sensitive: El contenido multimedia sensible está oculto por defecto y puede ser mostrado con un click setting_display_media_default: Ocultar contenido multimedia marcado como sensible @@ -130,7 +130,7 @@ es-MX: tag: name: Sólo se puede cambiar el cajón de las letras, por ejemplo, para que sea más legible user: - chosen_languages: Cuando se marca, solo se mostrarán los toots en los idiomas seleccionados en los timelines públicos + chosen_languages: Cuando se marca, solo se mostrarán las publicaciones en los idiomas seleccionados en las líneas de tiempo públicas role: El rol controla qué permisos tiene el usuario. user_role: color: Color que se utilizará para el rol a lo largo de la interfaz de usuario, como RGB en formato hexadecimal @@ -160,7 +160,7 @@ es-MX: text: Texto predefinido title: Título admin_account_action: - include_statuses: Incluir en el correo electrónico a los toots denunciados + include_statuses: Incluir en el correo electrónico a las publicaciones denunciadas send_email_notification: Notificar al usuario por correo electrónico text: Aviso personalizado type: Acción @@ -205,21 +205,21 @@ es-MX: password: Contraseña phrase: Palabra clave o frase setting_advanced_layout: Habilitar interfaz web avanzada - setting_aggregate_reblogs: Agrupar retoots en las líneas de tiempo + setting_aggregate_reblogs: Agrupar impulsos en las líneas de tiempo setting_always_send_emails: Enviar siempre notificaciones por correo setting_auto_play_gif: Reproducir automáticamente los GIFs animados - setting_boost_modal: Mostrar ventana de confirmación antes de un Retoot + setting_boost_modal: Mostrar ventana de confirmación antes de impulsar setting_default_language: Idioma de publicación setting_default_privacy: Privacidad de publicaciones setting_default_sensitive: Marcar siempre imágenes como sensibles - setting_delete_modal: Mostrar diálogo de confirmación antes de borrar un toot + setting_delete_modal: Mostrar diálogo de confirmación antes de borrar una publicación setting_disable_hover_cards: Desactivar vista previa del perfil al pasar el cursor setting_disable_swiping: Deshabilitar movimientos de deslizamiento setting_display_media: Visualización multimedia setting_display_media_default: Por defecto setting_display_media_hide_all: Ocultar todo setting_display_media_show_all: Mostrar todo - setting_expand_spoilers: Siempre expandir los toots marcados con advertencias de contenido + setting_expand_spoilers: Siempre expandir las publicaciones marcadas con advertencias de contenido setting_hide_network: Ocultar tu red setting_reduce_motion: Reducir el movimiento de las animaciones setting_system_font_ui: Utilizar la tipografía por defecto del sistema diff --git a/config/locales/simple_form.kab.yml b/config/locales/simple_form.kab.yml index 68fc629d9ecebf..203b02371539a8 100644 --- a/config/locales/simple_form.kab.yml +++ b/config/locales/simple_form.kab.yml @@ -20,6 +20,7 @@ kab: irreversible: Tisuffaɣ i tessazedgeḍ ad ttwakksent i lebda, ula ma tekkseḍ imsizdeg-nni ar zdat locale: Tutlayt n ugrudem, imaylen d walɣuten yettudemren password: Seqdec ma drus 8 n yisekkilen + setting_always_send_emails: S umata, ilɣa s yimayl ur d-ttwaceyyεen ara mi ara tesseqdaceḍ Mastodon s wudem urmid setting_display_media_default: Ffer imidyaten yettwacreḍ d infariyen setting_display_media_hide_all: Ffer yal tikkelt akk taywalt setting_display_media_show_all: Ffer yal tikkelt teywalt yettwacreḍ d tanafrit @@ -84,8 +85,9 @@ kab: password: Awal uffir phrase: Awal n tsarut neɣ tafyirt setting_advanced_layout: Rmed agrudem n web leqqayen - setting_default_language: Tutlayt n tira - setting_default_privacy: Tabaḍnit n tira + setting_always_send_emails: Dima ttazen-d ilɣa s yimayl + setting_default_language: Tutlayt n usuffeɣ + setting_default_privacy: Tabaḍnit n usuffeɣ setting_display_media: Askanay n imidyaten setting_display_media_default: Akk-a kan setting_display_media_hide_all: Ffer-iten akk diff --git a/config/locales/simple_form.lv.yml b/config/locales/simple_form.lv.yml index 4369c3c42bb52e..ed27e08bc33466 100644 --- a/config/locales/simple_form.lv.yml +++ b/config/locales/simple_form.lv.yml @@ -3,7 +3,7 @@ lv: simple_form: hints: account: - attribution_domains_as_text: Aizsargā no nepatiesas piedēvēšanas. + attribution_domains_as_text: Aizsargā no nepatiesa attiecinājuma. discoverable: Tavas publiskās ziņas un profils var tikt piedāvāti vai ieteikti dažādās Mastodon vietās, un tavs profils var tikt ieteikts citiem lietotājiem. display_name: Tavs pilnais vārds vai tavs joku vārds. fields: Tava mājas lapa, vietniekvārdi, vecums, viss, ko vēlies. From 6f3d7516dc535ab04b040d69b2682d8bc69d5d7e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 18 Sep 2024 08:10:22 +0000 Subject: [PATCH 05/51] Update dependency dotenv to v3.1.4 (#31953) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 12f1b5db2c81ed..3f321c7b7e9ac8 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -212,7 +212,7 @@ GEM domain_name (0.6.20240107) doorkeeper (5.7.1) railties (>= 5) - dotenv (3.1.2) + dotenv (3.1.4) drb (2.2.1) ed25519 (1.3.0) elasticsearch (7.17.11) From 943738671c02424b4956de0994c4bd298191fe54 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Wed, 18 Sep 2024 04:21:31 -0400 Subject: [PATCH 06/51] Remove unneeded `to_s` on `Link` header comparison in statuses controller spec (#31941) --- spec/controllers/statuses_controller_spec.rb | 30 ++++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/spec/controllers/statuses_controller_spec.rb b/spec/controllers/statuses_controller_spec.rb index 5042523df82864..d9702251f41d0a 100644 --- a/spec/controllers/statuses_controller_spec.rb +++ b/spec/controllers/statuses_controller_spec.rb @@ -63,7 +63,7 @@ expect(response.headers).to include( 'Vary' => 'Accept, Accept-Language, Cookie', 'Cache-Control' => include('public'), - 'Link' => satisfy { |header| header.to_s.include?('activity+json') } + 'Link' => include('activity+json') ) expect(response.body).to include status.text end @@ -79,7 +79,7 @@ expect(response.headers).to include( 'Content-Type' => include('application/activity+json'), - 'Link' => satisfy { |header| header.to_s.include?('activity+json') } + 'Link' => include('activity+json') ) expect(response.parsed_body) .to include(content: include(status.text)) @@ -168,7 +168,7 @@ expect(response.headers).to include( 'Vary' => 'Accept, Accept-Language, Cookie', 'Cache-Control' => include('private'), - 'Link' => satisfy { |header| header.to_s.include?('activity+json') } + 'Link' => include('activity+json') ) expect(response.body).to include status.text end @@ -184,7 +184,7 @@ 'Vary' => 'Accept, Accept-Language, Cookie', 'Cache-Control' => include('private'), 'Content-Type' => include('application/activity+json'), - 'Link' => satisfy { |header| header.to_s.include?('activity+json') } + 'Link' => include('activity+json') ) expect(response.parsed_body) .to include(content: include(status.text)) @@ -212,7 +212,7 @@ expect(response.headers).to include( 'Vary' => 'Accept, Accept-Language, Cookie', 'Cache-Control' => include('private'), - 'Link' => satisfy { |header| header.to_s.include?('activity+json') } + 'Link' => include('activity+json') ) expect(response.body).to include status.text end @@ -228,7 +228,7 @@ 'Vary' => 'Accept, Accept-Language, Cookie', 'Cache-Control' => include('private'), 'Content-Type' => include('application/activity+json'), - 'Link' => satisfy { |header| header.to_s.include?('activity+json') } + 'Link' => include('activity+json') ) expect(response.parsed_body) .to include(content: include(status.text)) @@ -278,7 +278,7 @@ expect(response.headers).to include( 'Vary' => 'Accept, Accept-Language, Cookie', 'Cache-Control' => include('private'), - 'Link' => satisfy { |header| header.to_s.include?('activity+json') } + 'Link' => include('activity+json') ) expect(response.body).to include status.text end @@ -294,7 +294,7 @@ 'Vary' => 'Accept, Accept-Language, Cookie', 'Cache-Control' => include('private'), 'Content-Type' => include('application/activity+json'), - 'Link' => satisfy { |header| header.to_s.include?('activity+json') } + 'Link' => include('activity+json') ) expect(response.parsed_body) .to include(content: include(status.text)) @@ -370,7 +370,7 @@ expect(response.headers).to include( 'Vary' => 'Accept, Accept-Language, Cookie', 'Cache-Control' => include('private'), - 'Link' => satisfy { |header| header.to_s.include?('activity+json') } + 'Link' => include('activity+json') ) expect(response.body).to include status.text end @@ -385,7 +385,7 @@ .and have_cacheable_headers.with_vary('Accept, Accept-Language, Cookie') expect(response.headers).to include( 'Content-Type' => include('application/activity+json'), - 'Link' => satisfy { |header| header.to_s.include?('activity+json') } + 'Link' => include('activity+json') ) expect(response.parsed_body) .to include(content: include(status.text)) @@ -412,7 +412,7 @@ expect(response.headers).to include( 'Vary' => 'Accept, Accept-Language, Cookie', 'Cache-Control' => include('private'), - 'Link' => satisfy { |header| header.to_s.include?('activity+json') } + 'Link' => include('activity+json') ) expect(response.body).to include status.text end @@ -428,7 +428,7 @@ 'Vary' => 'Accept, Accept-Language, Cookie', 'Cache-Control' => include('private'), 'Content-Type' => include('application/activity+json'), - 'Link' => satisfy { |header| header.to_s.include?('activity+json') } + 'Link' => include('activity+json') ) expect(response.parsed_body) @@ -479,7 +479,7 @@ expect(response.headers).to include( 'Vary' => 'Accept, Accept-Language, Cookie', 'Cache-Control' => include('private'), - 'Link' => satisfy { |header| header.to_s.include?('activity+json') } + 'Link' => include('activity+json') ) expect(response.body).to include status.text end @@ -495,7 +495,7 @@ 'Vary' => 'Accept, Accept-Language, Cookie', 'Cache-Control' => include('private'), 'Content-Type' => include('application/activity+json'), - 'Link' => satisfy { |header| header.to_s.include?('activity+json') } + 'Link' => include('activity+json') ) expect(response.parsed_body) .to include(content: include(status.text)) @@ -779,7 +779,7 @@ expect(response.headers).to include( 'Vary' => 'Accept, Accept-Language, Cookie', 'Cache-Control' => include('public'), - 'Link' => satisfy { |header| header.to_s.include?('activity+json') } + 'Link' => include('activity+json') ) end end From eb16763bff5cc2d38dec19c82d47615cd2b54e27 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Wed, 18 Sep 2024 04:22:07 -0400 Subject: [PATCH 07/51] Use `have_http_link_header` matcher in `api/v1/trends/*` specs (#31940) --- spec/requests/api/v1/trends/links_spec.rb | 9 ++++++--- spec/requests/api/v1/trends/statuses_spec.rb | 9 ++++++--- spec/requests/api/v1/trends/tags_spec.rb | 10 ++++++---- 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/spec/requests/api/v1/trends/links_spec.rb b/spec/requests/api/v1/trends/links_spec.rb index 012d0359072c74..04d36da0a26789 100644 --- a/spec/requests/api/v1/trends/links_spec.rb +++ b/spec/requests/api/v1/trends/links_spec.rb @@ -10,7 +10,9 @@ it 'returns http success' do get '/api/v1/trends/links' - expect(response).to have_http_status(200) + expect(response) + .to have_http_status(200) + .and not_have_http_link_header end end @@ -22,8 +24,9 @@ stub_const('Api::V1::Trends::LinksController::DEFAULT_LINKS_LIMIT', 2) get '/api/v1/trends/links' - expect(response).to have_http_status(200) - expect(response.headers).to include('Link') + expect(response) + .to have_http_status(200) + .and have_http_link_header(api_v1_trends_links_url(offset: 2)).for(rel: 'next') end def prepare_trends diff --git a/spec/requests/api/v1/trends/statuses_spec.rb b/spec/requests/api/v1/trends/statuses_spec.rb index 3b906e8f822b44..f04addfe0ac988 100644 --- a/spec/requests/api/v1/trends/statuses_spec.rb +++ b/spec/requests/api/v1/trends/statuses_spec.rb @@ -10,7 +10,9 @@ it 'returns http success' do get '/api/v1/trends/statuses' - expect(response).to have_http_status(200) + expect(response) + .to have_http_status(200) + .and not_have_http_link_header end end @@ -22,8 +24,9 @@ stub_const('Api::BaseController::DEFAULT_STATUSES_LIMIT', 2) get '/api/v1/trends/statuses' - expect(response).to have_http_status(200) - expect(response.headers).to include('Link') + expect(response) + .to have_http_status(200) + .and have_http_link_header(api_v1_trends_statuses_url(offset: 2)).for(rel: 'next') end def prepare_trends diff --git a/spec/requests/api/v1/trends/tags_spec.rb b/spec/requests/api/v1/trends/tags_spec.rb index 598f4e7752714d..2ff51eed6327a6 100644 --- a/spec/requests/api/v1/trends/tags_spec.rb +++ b/spec/requests/api/v1/trends/tags_spec.rb @@ -10,8 +10,9 @@ it 'returns http success' do get '/api/v1/trends/tags' - expect(response).to have_http_status(200) - expect(response.headers).to_not include('Link') + expect(response) + .to have_http_status(200) + .and not_have_http_link_header end end @@ -23,8 +24,9 @@ stub_const('Api::V1::Trends::TagsController::DEFAULT_TAGS_LIMIT', 2) get '/api/v1/trends/tags' - expect(response).to have_http_status(200) - expect(response.headers).to include('Link') + expect(response) + .to have_http_status(200) + .and have_http_link_header(api_v1_trends_tags_url(offset: 2)).for(rel: 'next') end def prepare_trends From a791274824d1136278a0139f4b15f54433e56561 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 18 Sep 2024 08:28:42 +0000 Subject: [PATCH 08/51] Update dependency sass to v1.79.1 (#31958) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 62 ++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 39 insertions(+), 23 deletions(-) diff --git a/yarn.lock b/yarn.lock index 49b6e241f87c40..73ea69ee71b7cc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5899,25 +5899,6 @@ __metadata: languageName: node linkType: hard -"chokidar@npm:>=3.0.0 <4.0.0, chokidar@npm:^3.4.1": - version: 3.5.3 - resolution: "chokidar@npm:3.5.3" - dependencies: - anymatch: "npm:~3.1.2" - braces: "npm:~3.0.2" - fsevents: "npm:~2.3.2" - glob-parent: "npm:~5.1.2" - is-binary-path: "npm:~2.1.0" - is-glob: "npm:~4.0.1" - normalize-path: "npm:~3.0.0" - readdirp: "npm:~3.6.0" - dependenciesMeta: - fsevents: - optional: true - checksum: 10c0/1076953093e0707c882a92c66c0f56ba6187831aa51bb4de878c1fec59ae611a3bf02898f190efec8e77a086b8df61c2b2a3ea324642a0558bdf8ee6c5dc9ca1 - languageName: node - linkType: hard - "chokidar@npm:^2.1.8": version: 2.1.8 resolution: "chokidar@npm:2.1.8" @@ -5941,6 +5922,34 @@ __metadata: languageName: node linkType: hard +"chokidar@npm:^3.4.1": + version: 3.5.3 + resolution: "chokidar@npm:3.5.3" + dependencies: + anymatch: "npm:~3.1.2" + braces: "npm:~3.0.2" + fsevents: "npm:~2.3.2" + glob-parent: "npm:~5.1.2" + is-binary-path: "npm:~2.1.0" + is-glob: "npm:~4.0.1" + normalize-path: "npm:~3.0.0" + readdirp: "npm:~3.6.0" + dependenciesMeta: + fsevents: + optional: true + checksum: 10c0/1076953093e0707c882a92c66c0f56ba6187831aa51bb4de878c1fec59ae611a3bf02898f190efec8e77a086b8df61c2b2a3ea324642a0558bdf8ee6c5dc9ca1 + languageName: node + linkType: hard + +"chokidar@npm:^4.0.0": + version: 4.0.0 + resolution: "chokidar@npm:4.0.0" + dependencies: + readdirp: "npm:^4.0.1" + checksum: 10c0/42d03c53b0ad200689e4fae7763133561480561cab8ba5304e8f2298ff45ff84bf0f6065c3f02b9e557b74b156813734439a1a2ff19a1ea6b35692395cd92738 + languageName: node + linkType: hard + "chownr@npm:^2.0.0": version: 2.0.0 resolution: "chownr@npm:2.0.0" @@ -14884,6 +14893,13 @@ __metadata: languageName: node linkType: hard +"readdirp@npm:^4.0.1": + version: 4.0.1 + resolution: "readdirp@npm:4.0.1" + checksum: 10c0/e5a0b547015f68ecc918f115b62b75b2b840611480a9240cb3317090a0ddac01bb9b40315a8fa08acdf52a43eea17b808c89b645263cba3ab64dc557d7f801f1 + languageName: node + linkType: hard + "readdirp@npm:~3.6.0": version: 3.6.0 resolution: "readdirp@npm:3.6.0" @@ -15459,15 +15475,15 @@ __metadata: linkType: hard "sass@npm:^1.62.1": - version: 1.78.0 - resolution: "sass@npm:1.78.0" + version: 1.79.1 + resolution: "sass@npm:1.79.1" dependencies: - chokidar: "npm:>=3.0.0 <4.0.0" + chokidar: "npm:^4.0.0" immutable: "npm:^4.0.0" source-map-js: "npm:>=0.6.2 <2.0.0" bin: sass: sass.js - checksum: 10c0/6577a87c00b03a5a50f3a11b4b6592f28abce34e61812e381535a3b712151bd94db3ca06467d20395431e0f38a23f99e616d6859d771fb6d4617c359f590c48c + checksum: 10c0/187bc885bad2e81e5414a146c9e14a28f622b5aba5c1425f4dd6d8ef2045b66c69de93689503886a8d2cc55d6bcce97b05ee87b074628fefd03e762789f931d0 languageName: node linkType: hard From 7740f1a6bb6c34a1d4fdfeaa29c31ac24fd4b236 Mon Sep 17 00:00:00 2001 From: Christian Schmidt Date: Wed, 18 Sep 2024 10:43:24 +0200 Subject: [PATCH 09/51] Mute XHR abort errors (#31952) --- app/javascript/mastodon/actions/alerts.js | 7 +++++++ app/javascript/mastodon/api.ts | 3 +++ 2 files changed, 10 insertions(+) diff --git a/app/javascript/mastodon/actions/alerts.js b/app/javascript/mastodon/actions/alerts.js index 42834146bf5ba6..48dee2587fe523 100644 --- a/app/javascript/mastodon/actions/alerts.js +++ b/app/javascript/mastodon/actions/alerts.js @@ -1,5 +1,7 @@ import { defineMessages } from 'react-intl'; +import { AxiosError } from 'axios'; + const messages = defineMessages({ unexpectedTitle: { id: 'alert.unexpected.title', defaultMessage: 'Oops!' }, unexpectedMessage: { id: 'alert.unexpected.message', defaultMessage: 'An unexpected error occurred.' }, @@ -50,6 +52,11 @@ export const showAlertForError = (error, skipNotFound = false) => { }); } + // An aborted request, e.g. due to reloading the browser window, it not really error + if (error.code === AxiosError.ECONNABORTED) { + return { type: ALERT_NOOP }; + } + console.error(error); return showAlert({ diff --git a/app/javascript/mastodon/api.ts b/app/javascript/mastodon/api.ts index 24672290c74f94..25bb25547cf94d 100644 --- a/app/javascript/mastodon/api.ts +++ b/app/javascript/mastodon/api.ts @@ -42,6 +42,9 @@ const authorizationTokenFromInitialState = (): RawAxiosRequestHeaders => { // eslint-disable-next-line import/no-default-export export default function api(withAuthorization = true) { return axios.create({ + transitional: { + clarifyTimeoutError: true, + }, headers: { ...csrfHeader, ...(withAuthorization ? authorizationTokenFromInitialState() : {}), From 2d399f5d4a26e2f9c398ec7ab01f1aaee1d0e1ac Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 18 Sep 2024 11:17:57 +0200 Subject: [PATCH 10/51] Update dependency pg-connection-string to v2.7.0 (#31950) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 73ea69ee71b7cc..45ede6ca997bc3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13002,9 +13002,9 @@ __metadata: linkType: hard "pg-connection-string@npm:^2.6.0, pg-connection-string@npm:^2.6.4": - version: 2.6.4 - resolution: "pg-connection-string@npm:2.6.4" - checksum: 10c0/0d0b617df0fc6507bf6a94bdcd56c7a305788a1402d69bff9773350947c8f525d6d8136128065370749a3325e99658ae40fbdcce620fb8e60126181f0591a6a6 + version: 2.7.0 + resolution: "pg-connection-string@npm:2.7.0" + checksum: 10c0/50a1496a1c858f9495d78a2c7a66d93ef3602e718aff2953bb5738f3ea616d7f727f32fc20513c9bed127650cd14c1ddc7b458396f4000e689d4b64c65c5c51e languageName: node linkType: hard From 5405bdd344ff140e4e9cf9782770e7180794edec Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Wed, 18 Sep 2024 05:27:43 -0400 Subject: [PATCH 11/51] Remove unused E2EE messaging code (#31193) --- Gemfile | 1 - Gemfile.lock | 2 - .../activitypub/claims_controller.rb | 18 ----- .../activitypub/collections_controller.rb | 6 +- .../api/v1/crypto/deliveries_controller.rb | 30 ------- .../crypto/encrypted_messages_controller.rb | 47 ----------- .../api/v1/crypto/keys/claims_controller.rb | 25 ------ .../api/v1/crypto/keys/counts_controller.rb | 17 ---- .../api/v1/crypto/keys/queries_controller.rb | 26 ------ .../api/v1/crypto/keys/uploads_controller.rb | 29 ------- app/helpers/context_helper.rb | 17 ---- app/lib/activitypub/activity/create.rb | 35 +------- app/lib/inline_renderer.rb | 2 - app/lib/vacuum/system_keys_vacuum.rb | 13 --- app/models/account.rb | 8 +- app/models/concerns/account/associations.rb | 3 - app/models/device.rb | 36 --------- app/models/encrypted_message.rb | 49 ------------ app/models/message_franking.rb | 19 ----- app/models/one_time_key.rb | 22 ------ app/models/system_key.rb | 41 ---------- .../activitypub/activity_serializer.rb | 2 - .../activitypub/actor_serializer.rb | 7 +- .../activitypub/collection_serializer.rb | 2 - .../activitypub/device_serializer.rb | 52 ------------ .../encrypted_message_serializer.rb | 61 -------------- .../activitypub/one_time_key_serializer.rb | 35 -------- .../rest/encrypted_message_serializer.rb | 19 ----- .../rest/keys/claim_result_serializer.rb | 9 --- .../rest/keys/device_serializer.rb | 6 -- .../rest/keys/query_result_serializer.rb | 11 --- .../activitypub/process_account_service.rb | 1 - app/services/delete_account_service.rb | 2 - app/services/deliver_to_device_service.rb | 78 ------------------ app/services/keys/claim_service.rb | 79 ------------------- app/services/keys/query_service.rb | 79 ------------------- app/validators/ed25519_key_validator.rb | 19 ----- app/validators/ed25519_signature_validator.rb | 29 ------- app/workers/push_encrypted_message_worker.rb | 16 ---- config/initializers/doorkeeper.rb | 3 +- config/initializers/inflections.rb | 1 - config/locales/an.yml | 1 - config/locales/ar.yml | 1 - config/locales/ast.yml | 1 - config/locales/be.yml | 1 - config/locales/bg.yml | 1 - config/locales/ca.yml | 1 - config/locales/ckb.yml | 1 - config/locales/co.yml | 1 - config/locales/cs.yml | 1 - config/locales/cy.yml | 1 - config/locales/da.yml | 1 - config/locales/de.yml | 1 - config/locales/el.yml | 1 - config/locales/en-GB.yml | 1 - config/locales/en.yml | 1 - config/locales/eo.yml | 1 - config/locales/es-AR.yml | 1 - config/locales/es-MX.yml | 1 - config/locales/es.yml | 1 - config/locales/et.yml | 1 - config/locales/eu.yml | 1 - config/locales/fa.yml | 1 - config/locales/fi.yml | 1 - config/locales/fo.yml | 1 - config/locales/fr-CA.yml | 1 - config/locales/fr.yml | 1 - config/locales/fy.yml | 1 - config/locales/ga.yml | 1 - config/locales/gd.yml | 1 - config/locales/gl.yml | 1 - config/locales/he.yml | 1 - config/locales/hu.yml | 1 - config/locales/hy.yml | 1 - config/locales/ia.yml | 1 - config/locales/id.yml | 1 - config/locales/ie.yml | 1 - config/locales/io.yml | 1 - config/locales/is.yml | 1 - config/locales/it.yml | 1 - config/locales/ja.yml | 1 - config/locales/ko.yml | 1 - config/locales/ku.yml | 1 - config/locales/lad.yml | 1 - config/locales/lv.yml | 1 - config/locales/ms.yml | 1 - config/locales/my.yml | 1 - config/locales/nl.yml | 1 - config/locales/nn.yml | 1 - config/locales/no.yml | 1 - config/locales/pl.yml | 1 - config/locales/pt-BR.yml | 1 - config/locales/pt-PT.yml | 1 - config/locales/ru.yml | 1 - config/locales/sc.yml | 1 - config/locales/sco.yml | 1 - config/locales/si.yml | 1 - config/locales/sl.yml | 1 - config/locales/sq.yml | 1 - config/locales/sr-Latn.yml | 1 - config/locales/sr.yml | 1 - config/locales/sv.yml | 1 - config/locales/ta.yml | 1 - config/locales/th.yml | 1 - config/locales/tr.yml | 1 - config/locales/uk.yml | 1 - config/locales/vi.yml | 1 - config/locales/zh-CN.yml | 1 - config/locales/zh-HK.yml | 1 - config/locales/zh-TW.yml | 1 - config/routes.rb | 1 - config/routes/api.rb | 17 ---- ...20140205_drop_end_to_end_message_tables.rb | 15 ++++ db/schema.rb | 50 ------------ .../activitypub/claims_controller_spec.rb | 19 ----- spec/fabricators/device_fabricator.rb | 10 --- .../encrypted_message_fabricator.rb | 7 -- spec/fabricators/one_time_key_fabricator.rb | 13 --- spec/fabricators/system_key_fabricator.rb | 3 - spec/lib/activitypub/activity/create_spec.rb | 58 -------------- spec/lib/vacuum/system_keys_vacuum_spec.rb | 24 ------ spec/models/one_time_key_spec.rb | 23 ------ .../activitypub/device_serializer_spec.rb | 14 ---- .../one_time_key_serializer_spec.rb | 14 ---- .../rest/encrypted_message_serializer_spec.rb | 14 ---- .../rest/keys/claim_result_serializer_spec.rb | 14 ---- .../rest/keys/device_serializer_spec.rb | 14 ---- .../rest/keys/query_result_serializer_spec.rb | 14 ---- .../push_encrypted_message_worker_spec.rb | 13 --- streaming/index.js | 9 +-- 130 files changed, 25 insertions(+), 1347 deletions(-) delete mode 100644 app/controllers/activitypub/claims_controller.rb delete mode 100644 app/controllers/api/v1/crypto/deliveries_controller.rb delete mode 100644 app/controllers/api/v1/crypto/encrypted_messages_controller.rb delete mode 100644 app/controllers/api/v1/crypto/keys/claims_controller.rb delete mode 100644 app/controllers/api/v1/crypto/keys/counts_controller.rb delete mode 100644 app/controllers/api/v1/crypto/keys/queries_controller.rb delete mode 100644 app/controllers/api/v1/crypto/keys/uploads_controller.rb delete mode 100644 app/lib/vacuum/system_keys_vacuum.rb delete mode 100644 app/models/device.rb delete mode 100644 app/models/encrypted_message.rb delete mode 100644 app/models/message_franking.rb delete mode 100644 app/models/one_time_key.rb delete mode 100644 app/models/system_key.rb delete mode 100644 app/serializers/activitypub/device_serializer.rb delete mode 100644 app/serializers/activitypub/encrypted_message_serializer.rb delete mode 100644 app/serializers/activitypub/one_time_key_serializer.rb delete mode 100644 app/serializers/rest/encrypted_message_serializer.rb delete mode 100644 app/serializers/rest/keys/claim_result_serializer.rb delete mode 100644 app/serializers/rest/keys/device_serializer.rb delete mode 100644 app/serializers/rest/keys/query_result_serializer.rb delete mode 100644 app/services/deliver_to_device_service.rb delete mode 100644 app/services/keys/claim_service.rb delete mode 100644 app/services/keys/query_service.rb delete mode 100644 app/validators/ed25519_key_validator.rb delete mode 100644 app/validators/ed25519_signature_validator.rb delete mode 100644 app/workers/push_encrypted_message_worker.rb create mode 100644 db/migrate/20240720140205_drop_end_to_end_message_tables.rb delete mode 100644 spec/controllers/activitypub/claims_controller_spec.rb delete mode 100644 spec/fabricators/device_fabricator.rb delete mode 100644 spec/fabricators/encrypted_message_fabricator.rb delete mode 100644 spec/fabricators/one_time_key_fabricator.rb delete mode 100644 spec/fabricators/system_key_fabricator.rb delete mode 100644 spec/lib/vacuum/system_keys_vacuum_spec.rb delete mode 100644 spec/models/one_time_key_spec.rb delete mode 100644 spec/serializers/activitypub/device_serializer_spec.rb delete mode 100644 spec/serializers/activitypub/one_time_key_serializer_spec.rb delete mode 100644 spec/serializers/rest/encrypted_message_serializer_spec.rb delete mode 100644 spec/serializers/rest/keys/claim_result_serializer_spec.rb delete mode 100644 spec/serializers/rest/keys/device_serializer_spec.rb delete mode 100644 spec/serializers/rest/keys/query_result_serializer_spec.rb delete mode 100644 spec/workers/push_encrypted_message_worker_spec.rb diff --git a/Gemfile b/Gemfile index 4cce095ec595bc..bcb19421ab9afe 100644 --- a/Gemfile +++ b/Gemfile @@ -47,7 +47,6 @@ gem 'color_diff', '~> 0.1' gem 'csv', '~> 3.2' gem 'discard', '~> 1.2' gem 'doorkeeper', '~> 5.6' -gem 'ed25519', '~> 1.3' gem 'fast_blank', '~> 1.0' gem 'fastimage' gem 'hiredis', '~> 0.6' diff --git a/Gemfile.lock b/Gemfile.lock index 3f321c7b7e9ac8..5ed8fe78ebcfb2 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -214,7 +214,6 @@ GEM railties (>= 5) dotenv (3.1.4) drb (2.2.1) - ed25519 (1.3.0) elasticsearch (7.17.11) elasticsearch-api (= 7.17.11) elasticsearch-transport (= 7.17.11) @@ -937,7 +936,6 @@ DEPENDENCIES discard (~> 1.2) doorkeeper (~> 5.6) dotenv - ed25519 (~> 1.3) email_spec fabrication (~> 2.30) faker (~> 3.2) diff --git a/app/controllers/activitypub/claims_controller.rb b/app/controllers/activitypub/claims_controller.rb deleted file mode 100644 index 480baaf2bcce0f..00000000000000 --- a/app/controllers/activitypub/claims_controller.rb +++ /dev/null @@ -1,18 +0,0 @@ -# frozen_string_literal: true - -class ActivityPub::ClaimsController < ActivityPub::BaseController - skip_before_action :authenticate_user! - - before_action :require_account_signature! - before_action :set_claim_result - - def create - render json: @claim_result, serializer: ActivityPub::OneTimeKeySerializer - end - - private - - def set_claim_result - @claim_result = ::Keys::ClaimService.new.call(@account.id, params[:id]) - end -end diff --git a/app/controllers/activitypub/collections_controller.rb b/app/controllers/activitypub/collections_controller.rb index c25362c9bc056f..ab1b98e646a1f6 100644 --- a/app/controllers/activitypub/collections_controller.rb +++ b/app/controllers/activitypub/collections_controller.rb @@ -22,8 +22,6 @@ def set_items @items = @items.map { |item| item.distributable? ? item : ActivityPub::TagManager.instance.uri_for(item) } when 'tags' @items = for_signed_account { @account.featured_tags } - when 'devices' - @items = @account.devices else not_found end @@ -31,7 +29,7 @@ def set_items def set_size case params[:id] - when 'featured', 'devices', 'tags' + when 'featured', 'tags' @size = @items.size else not_found @@ -42,7 +40,7 @@ def set_type case params[:id] when 'featured' @type = :ordered - when 'devices', 'tags' + when 'tags' @type = :unordered else not_found diff --git a/app/controllers/api/v1/crypto/deliveries_controller.rb b/app/controllers/api/v1/crypto/deliveries_controller.rb deleted file mode 100644 index aa9df6e03b20f2..00000000000000 --- a/app/controllers/api/v1/crypto/deliveries_controller.rb +++ /dev/null @@ -1,30 +0,0 @@ -# frozen_string_literal: true - -class Api::V1::Crypto::DeliveriesController < Api::BaseController - before_action -> { doorkeeper_authorize! :crypto } - before_action :require_user! - before_action :set_current_device - - def create - devices.each do |device_params| - DeliverToDeviceService.new.call(current_account, @current_device, device_params) - end - - render_empty - end - - private - - def set_current_device - @current_device = Device.find_by!(access_token: doorkeeper_token) - end - - def resource_params - params.require(:device) - params.permit(device: [:account_id, :device_id, :type, :body, :hmac]) - end - - def devices - Array(resource_params[:device]) - end -end diff --git a/app/controllers/api/v1/crypto/encrypted_messages_controller.rb b/app/controllers/api/v1/crypto/encrypted_messages_controller.rb deleted file mode 100644 index 93ae0e777139c3..00000000000000 --- a/app/controllers/api/v1/crypto/encrypted_messages_controller.rb +++ /dev/null @@ -1,47 +0,0 @@ -# frozen_string_literal: true - -class Api::V1::Crypto::EncryptedMessagesController < Api::BaseController - LIMIT = 80 - - before_action -> { doorkeeper_authorize! :crypto } - before_action :require_user! - before_action :set_current_device - - before_action :set_encrypted_messages, only: :index - after_action :insert_pagination_headers, only: :index - - def index - render json: @encrypted_messages, each_serializer: REST::EncryptedMessageSerializer - end - - def clear - @current_device.encrypted_messages.up_to(params[:up_to_id]).delete_all - render_empty - end - - private - - def set_current_device - @current_device = Device.find_by!(access_token: doorkeeper_token) - end - - def set_encrypted_messages - @encrypted_messages = @current_device.encrypted_messages.to_a_paginated_by_id(limit_param(LIMIT), params_slice(:max_id, :since_id, :min_id)) - end - - def next_path - api_v1_crypto_encrypted_messages_url pagination_params(max_id: pagination_max_id) if records_continue? - end - - def prev_path - api_v1_crypto_encrypted_messages_url pagination_params(min_id: pagination_since_id) unless @encrypted_messages.empty? - end - - def pagination_collection - @encrypted_messages - end - - def records_continue? - @encrypted_messages.size == limit_param(LIMIT) - end -end diff --git a/app/controllers/api/v1/crypto/keys/claims_controller.rb b/app/controllers/api/v1/crypto/keys/claims_controller.rb deleted file mode 100644 index f9d202d67b8ed8..00000000000000 --- a/app/controllers/api/v1/crypto/keys/claims_controller.rb +++ /dev/null @@ -1,25 +0,0 @@ -# frozen_string_literal: true - -class Api::V1::Crypto::Keys::ClaimsController < Api::BaseController - before_action -> { doorkeeper_authorize! :crypto } - before_action :require_user! - before_action :set_claim_results - - def create - render json: @claim_results, each_serializer: REST::Keys::ClaimResultSerializer - end - - private - - def set_claim_results - @claim_results = devices.filter_map { |device_params| ::Keys::ClaimService.new.call(current_account, device_params[:account_id], device_params[:device_id]) } - end - - def resource_params - params.permit(device: [:account_id, :device_id]) - end - - def devices - Array(resource_params[:device]) - end -end diff --git a/app/controllers/api/v1/crypto/keys/counts_controller.rb b/app/controllers/api/v1/crypto/keys/counts_controller.rb deleted file mode 100644 index ffd7151b78291e..00000000000000 --- a/app/controllers/api/v1/crypto/keys/counts_controller.rb +++ /dev/null @@ -1,17 +0,0 @@ -# frozen_string_literal: true - -class Api::V1::Crypto::Keys::CountsController < Api::BaseController - before_action -> { doorkeeper_authorize! :crypto } - before_action :require_user! - before_action :set_current_device - - def show - render json: { one_time_keys: @current_device.one_time_keys.count } - end - - private - - def set_current_device - @current_device = Device.find_by!(access_token: doorkeeper_token) - end -end diff --git a/app/controllers/api/v1/crypto/keys/queries_controller.rb b/app/controllers/api/v1/crypto/keys/queries_controller.rb deleted file mode 100644 index e6ce9f9192ac86..00000000000000 --- a/app/controllers/api/v1/crypto/keys/queries_controller.rb +++ /dev/null @@ -1,26 +0,0 @@ -# frozen_string_literal: true - -class Api::V1::Crypto::Keys::QueriesController < Api::BaseController - before_action -> { doorkeeper_authorize! :crypto } - before_action :require_user! - before_action :set_accounts - before_action :set_query_results - - def create - render json: @query_results, each_serializer: REST::Keys::QueryResultSerializer - end - - private - - def set_accounts - @accounts = Account.where(id: account_ids).includes(:devices) - end - - def set_query_results - @query_results = @accounts.filter_map { |account| ::Keys::QueryService.new.call(account) } - end - - def account_ids - Array(params[:id]).map(&:to_i) - end -end diff --git a/app/controllers/api/v1/crypto/keys/uploads_controller.rb b/app/controllers/api/v1/crypto/keys/uploads_controller.rb deleted file mode 100644 index fc4abf63b3a421..00000000000000 --- a/app/controllers/api/v1/crypto/keys/uploads_controller.rb +++ /dev/null @@ -1,29 +0,0 @@ -# frozen_string_literal: true - -class Api::V1::Crypto::Keys::UploadsController < Api::BaseController - before_action -> { doorkeeper_authorize! :crypto } - before_action :require_user! - - def create - device = Device.find_or_initialize_by(access_token: doorkeeper_token) - - device.transaction do - device.account = current_account - device.update!(resource_params[:device]) - - if resource_params[:one_time_keys].present? && resource_params[:one_time_keys].is_a?(Enumerable) - resource_params[:one_time_keys].each do |one_time_key_params| - device.one_time_keys.create!(one_time_key_params) - end - end - end - - render json: device, serializer: REST::Keys::DeviceSerializer - end - - private - - def resource_params - params.permit(device: [:device_id, :name, :fingerprint_key, :identity_key], one_time_keys: [:key_id, :key, :signature]) - end -end diff --git a/app/helpers/context_helper.rb b/app/helpers/context_helper.rb index a0c1781d240d77..18bb088b48aee9 100644 --- a/app/helpers/context_helper.rb +++ b/app/helpers/context_helper.rb @@ -23,23 +23,6 @@ module ContextHelper indexable: { 'toot' => 'http://joinmastodon.org/ns#', 'indexable' => 'toot:indexable' }, memorial: { 'toot' => 'http://joinmastodon.org/ns#', 'memorial' => 'toot:memorial' }, voters_count: { 'toot' => 'http://joinmastodon.org/ns#', 'votersCount' => 'toot:votersCount' }, - olm: { - 'toot' => 'http://joinmastodon.org/ns#', - 'Device' => 'toot:Device', - 'Ed25519Signature' => 'toot:Ed25519Signature', - 'Ed25519Key' => 'toot:Ed25519Key', - 'Curve25519Key' => 'toot:Curve25519Key', - 'EncryptedMessage' => 'toot:EncryptedMessage', - 'publicKeyBase64' => 'toot:publicKeyBase64', - 'deviceId' => 'toot:deviceId', - 'claim' => { '@type' => '@id', '@id' => 'toot:claim' }, - 'fingerprintKey' => { '@type' => '@id', '@id' => 'toot:fingerprintKey' }, - 'identityKey' => { '@type' => '@id', '@id' => 'toot:identityKey' }, - 'devices' => { '@type' => '@id', '@id' => 'toot:devices' }, - 'messageFranking' => 'toot:messageFranking', - 'messageType' => 'toot:messageType', - 'cipherText' => 'toot:cipherText', - }, suspended: { 'toot' => 'http://joinmastodon.org/ns#', 'suspended' => 'toot:suspended' }, attribution_domains: { 'toot' => 'http://joinmastodon.org/ns#', 'attributionDomains' => { '@id' => 'toot:attributionDomains', '@type' => '@id' } }, }.freeze diff --git a/app/lib/activitypub/activity/create.rb b/app/lib/activitypub/activity/create.rb index d539ce41a07175..aae73e01e0f03d 100644 --- a/app/lib/activitypub/activity/create.rb +++ b/app/lib/activitypub/activity/create.rb @@ -8,44 +8,11 @@ def perform dereference_object! - case @object['type'] - when 'EncryptedMessage' - create_encrypted_message - else - create_status - end + create_status end private - def create_encrypted_message - return reject_payload! if non_matching_uri_hosts?(@account.uri, object_uri) || @options[:delivered_to_account_id].blank? - - target_account = Account.find(@options[:delivered_to_account_id]) - target_device = target_account.devices.find_by(device_id: @object.dig('to', 'deviceId')) - - return if target_device.nil? - - target_device.encrypted_messages.create!( - from_account: @account, - from_device_id: @object.dig('attributedTo', 'deviceId'), - type: @object['messageType'], - body: @object['cipherText'], - digest: @object.dig('digest', 'digestValue'), - message_franking: message_franking.to_token - ) - end - - def message_franking - MessageFranking.new( - hmac: @object.dig('digest', 'digestValue'), - original_franking: @object['messageFranking'], - source_account_id: @account.id, - target_account_id: @options[:delivered_to_account_id], - timestamp: Time.now.utc - ) - end - def create_status return reject_payload! if unsupported_object_type? || non_matching_uri_hosts?(@account.uri, object_uri) || tombstone_exists? || !related_to_local_activity? diff --git a/app/lib/inline_renderer.rb b/app/lib/inline_renderer.rb index 0aebb13fce4476..af967ac215d6bf 100644 --- a/app/lib/inline_renderer.rb +++ b/app/lib/inline_renderer.rb @@ -20,8 +20,6 @@ def render serializer = REST::AnnouncementSerializer when :reaction serializer = REST::ReactionSerializer - when :encrypted_message - serializer = REST::EncryptedMessageSerializer else return end diff --git a/app/lib/vacuum/system_keys_vacuum.rb b/app/lib/vacuum/system_keys_vacuum.rb deleted file mode 100644 index ceee2fd164b2fb..00000000000000 --- a/app/lib/vacuum/system_keys_vacuum.rb +++ /dev/null @@ -1,13 +0,0 @@ -# frozen_string_literal: true - -class Vacuum::SystemKeysVacuum - def perform - vacuum_expired_system_keys! - end - - private - - def vacuum_expired_system_keys! - SystemKey.expired.delete_all - end -end diff --git a/app/models/account.rb b/app/models/account.rb index a4cab99f7b4152..078d7aaa056220 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -44,7 +44,6 @@ # hide_collections :boolean # avatar_storage_schema_version :integer # header_storage_schema_version :integer -# devices_url :string # suspension_origin :integer # sensitized_at :datetime # trendable :boolean @@ -56,11 +55,12 @@ class Account < ApplicationRecord self.ignored_columns += %w( - subscription_expires_at - secret + devices_url + hub_url remote_url salmon_url - hub_url + secret + subscription_expires_at trust_level ) diff --git a/app/models/concerns/account/associations.rb b/app/models/concerns/account/associations.rb index 1c67b07e511618..637e785953d06e 100644 --- a/app/models/concerns/account/associations.rb +++ b/app/models/concerns/account/associations.rb @@ -7,9 +7,6 @@ module Account::Associations # Local users has_one :user, inverse_of: :account, dependent: :destroy - # E2EE - has_many :devices, dependent: :destroy, inverse_of: :account - # Timelines has_many :statuses, inverse_of: :account, dependent: :destroy has_many :favourites, inverse_of: :account, dependent: :destroy diff --git a/app/models/device.rb b/app/models/device.rb deleted file mode 100644 index 5dc6cf1e66f6fb..00000000000000 --- a/app/models/device.rb +++ /dev/null @@ -1,36 +0,0 @@ -# frozen_string_literal: true - -# == Schema Information -# -# Table name: devices -# -# id :bigint(8) not null, primary key -# access_token_id :bigint(8) -# account_id :bigint(8) -# device_id :string default(""), not null -# name :string default(""), not null -# fingerprint_key :text default(""), not null -# identity_key :text default(""), not null -# created_at :datetime not null -# updated_at :datetime not null -# - -class Device < ApplicationRecord - belongs_to :access_token, class_name: 'Doorkeeper::AccessToken' - belongs_to :account - - has_many :one_time_keys, dependent: :destroy, inverse_of: :device - has_many :encrypted_messages, dependent: :destroy, inverse_of: :device - - validates :name, :fingerprint_key, :identity_key, presence: true - validates :fingerprint_key, :identity_key, ed25519_key: true - - before_save :invalidate_associations, if: -> { device_id_changed? || fingerprint_key_changed? || identity_key_changed? } - - private - - def invalidate_associations - one_time_keys.destroy_all - encrypted_messages.destroy_all - end -end diff --git a/app/models/encrypted_message.rb b/app/models/encrypted_message.rb deleted file mode 100644 index 3e7e95594cea26..00000000000000 --- a/app/models/encrypted_message.rb +++ /dev/null @@ -1,49 +0,0 @@ -# frozen_string_literal: true - -# == Schema Information -# -# Table name: encrypted_messages -# -# id :bigint(8) not null, primary key -# device_id :bigint(8) -# from_account_id :bigint(8) -# from_device_id :string default(""), not null -# type :integer default(0), not null -# body :text default(""), not null -# digest :text default(""), not null -# message_franking :text default(""), not null -# created_at :datetime not null -# updated_at :datetime not null -# - -class EncryptedMessage < ApplicationRecord - self.inheritance_column = nil - - include Paginable - include Redisable - - scope :up_to, ->(id) { where(arel_table[:id].lteq(id)) } - - belongs_to :device - belongs_to :from_account, class_name: 'Account' - - around_create Mastodon::Snowflake::Callbacks - - after_commit :push_to_streaming_api - - private - - def push_to_streaming_api - return if destroyed? || !subscribed_to_timeline? - - PushEncryptedMessageWorker.perform_async(id) - end - - def subscribed_to_timeline? - redis.exists?("subscribed:#{streaming_channel}") - end - - def streaming_channel - "timeline:#{device.account_id}:#{device.device_id}" - end -end diff --git a/app/models/message_franking.rb b/app/models/message_franking.rb deleted file mode 100644 index c72bd1ccaca99d..00000000000000 --- a/app/models/message_franking.rb +++ /dev/null @@ -1,19 +0,0 @@ -# frozen_string_literal: true - -class MessageFranking - attr_reader :hmac, :source_account_id, :target_account_id, - :timestamp, :original_franking - - def initialize(attributes = {}) - @hmac = attributes[:hmac] - @source_account_id = attributes[:source_account_id] - @target_account_id = attributes[:target_account_id] - @timestamp = attributes[:timestamp] - @original_franking = attributes[:original_franking] - end - - def to_token - crypt = ActiveSupport::MessageEncryptor.new(SystemKey.current_key, serializer: Oj) - crypt.encrypt_and_sign(self) - end -end diff --git a/app/models/one_time_key.rb b/app/models/one_time_key.rb deleted file mode 100644 index 23604e2f7d3c2b..00000000000000 --- a/app/models/one_time_key.rb +++ /dev/null @@ -1,22 +0,0 @@ -# frozen_string_literal: true - -# == Schema Information -# -# Table name: one_time_keys -# -# id :bigint(8) not null, primary key -# device_id :bigint(8) -# key_id :string default(""), not null -# key :text default(""), not null -# signature :text default(""), not null -# created_at :datetime not null -# updated_at :datetime not null -# - -class OneTimeKey < ApplicationRecord - belongs_to :device - - validates :key_id, :key, :signature, presence: true - validates :key, ed25519_key: true - validates :signature, ed25519_signature: { message: :key, verify_key: ->(one_time_key) { one_time_key.device.fingerprint_key } } -end diff --git a/app/models/system_key.rb b/app/models/system_key.rb deleted file mode 100644 index 1be399dd689c19..00000000000000 --- a/app/models/system_key.rb +++ /dev/null @@ -1,41 +0,0 @@ -# frozen_string_literal: true - -# == Schema Information -# -# Table name: system_keys -# -# id :bigint(8) not null, primary key -# key :binary -# created_at :datetime not null -# updated_at :datetime not null -# -class SystemKey < ApplicationRecord - ROTATION_PERIOD = 1.week.freeze - - before_validation :set_key - - scope :expired, ->(now = Time.now.utc) { where(arel_table[:created_at].lt(now - (ROTATION_PERIOD * 3))) } - - class << self - def current_key - previous_key = order(id: :asc).last - - if previous_key && previous_key.created_at >= ROTATION_PERIOD.ago - previous_key.key - else - create.key - end - end - end - - private - - def set_key - return if key.present? - - cipher = OpenSSL::Cipher.new('AES-256-GCM') - cipher.encrypt - - self.key = cipher.random_key - end -end diff --git a/app/serializers/activitypub/activity_serializer.rb b/app/serializers/activitypub/activity_serializer.rb index 5bdf53f032dddb..23a5b42bc790fa 100644 --- a/app/serializers/activitypub/activity_serializer.rb +++ b/app/serializers/activitypub/activity_serializer.rb @@ -5,8 +5,6 @@ def self.serializer_for(model, options) case model.class.name when 'Status' ActivityPub::NoteSerializer - when 'DeliverToDeviceService::EncryptedMessage' - ActivityPub::EncryptedMessageSerializer else super end diff --git a/app/serializers/activitypub/actor_serializer.rb b/app/serializers/activitypub/actor_serializer.rb index a6281e23b91e59..f698e758e8d6bb 100644 --- a/app/serializers/activitypub/actor_serializer.rb +++ b/app/serializers/activitypub/actor_serializer.rb @@ -7,7 +7,7 @@ class ActivityPub::ActorSerializer < ActivityPub::Serializer context :security context_extensions :manually_approves_followers, :featured, :also_known_as, - :moved_to, :property_value, :discoverable, :olm, :suspended, + :moved_to, :property_value, :discoverable, :suspended, :memorial, :indexable, :attribution_domains attributes :id, :type, :following, :followers, @@ -21,7 +21,6 @@ class ActivityPub::ActorSerializer < ActivityPub::Serializer has_many :virtual_tags, key: :tag has_many :virtual_attachments, key: :attachment - attribute :devices, unless: :instance_actor? attribute :moved_to, if: :moved? attribute :also_known_as, if: :also_known_as? attribute :suspended, if: :suspended? @@ -72,10 +71,6 @@ def inbox object.instance_actor? ? instance_actor_inbox_url : account_inbox_url(object) end - def devices - account_collection_url(object, :devices) - end - def outbox object.instance_actor? ? instance_actor_outbox_url : account_outbox_url(object) end diff --git a/app/serializers/activitypub/collection_serializer.rb b/app/serializers/activitypub/collection_serializer.rb index 34026a6b5be01d..1b410cecaef724 100644 --- a/app/serializers/activitypub/collection_serializer.rb +++ b/app/serializers/activitypub/collection_serializer.rb @@ -14,8 +14,6 @@ def self.serializer_for(model, options) case model.class.name when 'Status' ActivityPub::NoteSerializer - when 'Device' - ActivityPub::DeviceSerializer when 'FeaturedTag' ActivityPub::HashtagSerializer when 'ActivityPub::CollectionPresenter' diff --git a/app/serializers/activitypub/device_serializer.rb b/app/serializers/activitypub/device_serializer.rb deleted file mode 100644 index 5f0fdc8af935e4..00000000000000 --- a/app/serializers/activitypub/device_serializer.rb +++ /dev/null @@ -1,52 +0,0 @@ -# frozen_string_literal: true - -class ActivityPub::DeviceSerializer < ActivityPub::Serializer - context_extensions :olm - - include RoutingHelper - - class FingerprintKeySerializer < ActivityPub::Serializer - attributes :type, :public_key_base64 - - def type - 'Ed25519Key' - end - - def public_key_base64 - object.fingerprint_key - end - end - - class IdentityKeySerializer < ActivityPub::Serializer - attributes :type, :public_key_base64 - - def type - 'Curve25519Key' - end - - def public_key_base64 - object.identity_key - end - end - - attributes :device_id, :type, :name, :claim - - has_one :fingerprint_key, serializer: FingerprintKeySerializer - has_one :identity_key, serializer: IdentityKeySerializer - - def type - 'Device' - end - - def claim - account_claim_url(object.account, id: object.device_id) - end - - def fingerprint_key - object - end - - def identity_key - object - end -end diff --git a/app/serializers/activitypub/encrypted_message_serializer.rb b/app/serializers/activitypub/encrypted_message_serializer.rb deleted file mode 100644 index 3c525d23e5cee5..00000000000000 --- a/app/serializers/activitypub/encrypted_message_serializer.rb +++ /dev/null @@ -1,61 +0,0 @@ -# frozen_string_literal: true - -class ActivityPub::EncryptedMessageSerializer < ActivityPub::Serializer - context :security - - context_extensions :olm - - class DeviceSerializer < ActivityPub::Serializer - attributes :type, :device_id - - def type - 'Device' - end - - def device_id - object - end - end - - class DigestSerializer < ActivityPub::Serializer - attributes :type, :digest_algorithm, :digest_value - - def type - 'Digest' - end - - def digest_algorithm - 'http://www.w3.org/2000/09/xmldsig#hmac-sha256' - end - - def digest_value - object - end - end - - attributes :type, :message_type, :cipher_text, :message_franking - - has_one :attributed_to, serializer: DeviceSerializer - has_one :to, serializer: DeviceSerializer - has_one :digest, serializer: DigestSerializer - - def type - 'EncryptedMessage' - end - - def attributed_to - object.source_device.device_id - end - - def to - object.target_device_id - end - - def message_type - object.type - end - - def cipher_text - object.body - end -end diff --git a/app/serializers/activitypub/one_time_key_serializer.rb b/app/serializers/activitypub/one_time_key_serializer.rb deleted file mode 100644 index 5932eb5b55afee..00000000000000 --- a/app/serializers/activitypub/one_time_key_serializer.rb +++ /dev/null @@ -1,35 +0,0 @@ -# frozen_string_literal: true - -class ActivityPub::OneTimeKeySerializer < ActivityPub::Serializer - context :security - - context_extensions :olm - - class SignatureSerializer < ActivityPub::Serializer - attributes :type, :signature_value - - def type - 'Ed25519Signature' - end - - def signature_value - object.signature - end - end - - attributes :key_id, :type, :public_key_base64 - - has_one :signature, serializer: SignatureSerializer - - def type - 'Curve25519Key' - end - - def public_key_base64 - object.key - end - - def signature - object - end -end diff --git a/app/serializers/rest/encrypted_message_serializer.rb b/app/serializers/rest/encrypted_message_serializer.rb deleted file mode 100644 index 80c26d060ea32f..00000000000000 --- a/app/serializers/rest/encrypted_message_serializer.rb +++ /dev/null @@ -1,19 +0,0 @@ -# frozen_string_literal: true - -class REST::EncryptedMessageSerializer < ActiveModel::Serializer - attributes :id, :account_id, :device_id, - :type, :body, :digest, :message_franking, - :created_at - - def id - object.id.to_s - end - - def account_id - object.from_account_id.to_s - end - - def device_id - object.from_device_id - end -end diff --git a/app/serializers/rest/keys/claim_result_serializer.rb b/app/serializers/rest/keys/claim_result_serializer.rb deleted file mode 100644 index 145044f557231d..00000000000000 --- a/app/serializers/rest/keys/claim_result_serializer.rb +++ /dev/null @@ -1,9 +0,0 @@ -# frozen_string_literal: true - -class REST::Keys::ClaimResultSerializer < ActiveModel::Serializer - attributes :account_id, :device_id, :key_id, :key, :signature - - def account_id - object.account.id.to_s - end -end diff --git a/app/serializers/rest/keys/device_serializer.rb b/app/serializers/rest/keys/device_serializer.rb deleted file mode 100644 index f9b821b79ca1d8..00000000000000 --- a/app/serializers/rest/keys/device_serializer.rb +++ /dev/null @@ -1,6 +0,0 @@ -# frozen_string_literal: true - -class REST::Keys::DeviceSerializer < ActiveModel::Serializer - attributes :device_id, :name, :identity_key, - :fingerprint_key -end diff --git a/app/serializers/rest/keys/query_result_serializer.rb b/app/serializers/rest/keys/query_result_serializer.rb deleted file mode 100644 index 8f8bdde289fdb2..00000000000000 --- a/app/serializers/rest/keys/query_result_serializer.rb +++ /dev/null @@ -1,11 +0,0 @@ -# frozen_string_literal: true - -class REST::Keys::QueryResultSerializer < ActiveModel::Serializer - attributes :account_id - - has_many :devices, serializer: REST::Keys::DeviceSerializer - - def account_id - object.account.id.to_s - end -end diff --git a/app/services/activitypub/process_account_service.rb b/app/services/activitypub/process_account_service.rb index 1e2d614d72452e..a7422b5d02af7b 100644 --- a/app/services/activitypub/process_account_service.rb +++ b/app/services/activitypub/process_account_service.rb @@ -108,7 +108,6 @@ def set_immediate_protocol_attributes! def set_immediate_attributes! @account.featured_collection_url = @json['featured'] || '' - @account.devices_url = @json['devices'] || '' @account.display_name = @json['name'] || '' @account.note = @json['summary'] || '' @account.locked = @json['manuallyApprovesFollowers'] || false diff --git a/app/services/delete_account_service.rb b/app/services/delete_account_service.rb index 328d8ae8f83965..0c03267d43d0b3 100644 --- a/app/services/delete_account_service.rb +++ b/app/services/delete_account_service.rb @@ -13,7 +13,6 @@ class DeleteAccountService < BaseService conversation_mutes conversations custom_filters - devices domain_blocks featured_tags follow_requests @@ -40,7 +39,6 @@ class DeleteAccountService < BaseService conversation_mutes conversations custom_filters - devices domain_blocks featured_tags follow_requests diff --git a/app/services/deliver_to_device_service.rb b/app/services/deliver_to_device_service.rb deleted file mode 100644 index 71711945c06564..00000000000000 --- a/app/services/deliver_to_device_service.rb +++ /dev/null @@ -1,78 +0,0 @@ -# frozen_string_literal: true - -class DeliverToDeviceService < BaseService - include Payloadable - - class EncryptedMessage < ActiveModelSerializers::Model - attributes :source_account, :target_account, :source_device, - :target_device_id, :type, :body, :digest, - :message_franking - end - - def call(source_account, source_device, options = {}) - @source_account = source_account - @source_device = source_device - @target_account = Account.find(options[:account_id]) - @target_device_id = options[:device_id] - @body = options[:body] - @type = options[:type] - @hmac = options[:hmac] - - set_message_franking! - - if @target_account.local? - deliver_to_local! - else - deliver_to_remote! - end - end - - private - - def set_message_franking! - @message_franking = message_franking.to_token - end - - def deliver_to_local! - target_device = @target_account.devices.find_by!(device_id: @target_device_id) - - target_device.encrypted_messages.create!( - from_account: @source_account, - from_device_id: @source_device.device_id, - type: @type, - body: @body, - digest: @hmac, - message_franking: @message_franking - ) - end - - def deliver_to_remote! - ActivityPub::DeliveryWorker.perform_async( - Oj.dump(serialize_payload(ActivityPub::ActivityPresenter.from_encrypted_message(encrypted_message), ActivityPub::ActivitySerializer)), - @source_account.id, - @target_account.inbox_url - ) - end - - def message_franking - MessageFranking.new( - source_account_id: @source_account.id, - target_account_id: @target_account.id, - hmac: @hmac, - timestamp: Time.now.utc - ) - end - - def encrypted_message - EncryptedMessage.new( - source_account: @source_account, - target_account: @target_account, - source_device: @source_device, - target_device_id: @target_device_id, - type: @type, - body: @body, - digest: @hmac, - message_franking: @message_franking - ) - end -end diff --git a/app/services/keys/claim_service.rb b/app/services/keys/claim_service.rb deleted file mode 100644 index ebce9cce7d8c16..00000000000000 --- a/app/services/keys/claim_service.rb +++ /dev/null @@ -1,79 +0,0 @@ -# frozen_string_literal: true - -class Keys::ClaimService < BaseService - HEADERS = { 'Content-Type' => 'application/activity+json' }.freeze - - class Result < ActiveModelSerializers::Model - attributes :account, :device_id, :key_id, - :key, :signature - - def initialize(account, device_id, key_attributes = {}) - super( - account: account, - device_id: device_id, - key_id: key_attributes[:key_id], - key: key_attributes[:key], - signature: key_attributes[:signature], - ) - end - end - - def call(source_account, target_account_id, device_id) - @source_account = source_account - @target_account = Account.find(target_account_id) - @device_id = device_id - - if @target_account.local? - claim_local_key! - else - claim_remote_key! - end - rescue ActiveRecord::RecordNotFound - nil - end - - private - - def claim_local_key! - device = @target_account.devices.find_by(device_id: @device_id) - key = nil - - ApplicationRecord.transaction do - key = device.one_time_keys.order(Arel.sql('random()')).first! - key.destroy! - end - - @result = Result.new(@target_account, @device_id, key) - end - - def claim_remote_key! - query_result = QueryService.new.call(@target_account) - device = query_result.find(@device_id) - - return unless device.present? && device.valid_claim_url? - - json = fetch_resource_with_post(device.claim_url) - - return unless json.present? && json['publicKeyBase64'].present? - - @result = Result.new(@target_account, @device_id, key_id: json['id'], key: json['publicKeyBase64'], signature: json.dig('signature', 'signatureValue')) - rescue HTTP::Error, OpenSSL::SSL::SSLError, Mastodon::Error => e - Rails.logger.debug { "Claiming one-time key for #{@target_account.acct}:#{@device_id} failed: #{e}" } - nil - end - - def fetch_resource_with_post(uri) - build_post_request(uri).perform do |response| - raise Mastodon::UnexpectedResponseError, response unless response_successful?(response) || response_error_unsalvageable?(response) - - body_to_json(response.body_with_limit) if response.code == 200 - end - end - - def build_post_request(uri) - Request.new(:post, uri).tap do |request| - request.on_behalf_of(@source_account) - request.add_headers(HEADERS) - end - end -end diff --git a/app/services/keys/query_service.rb b/app/services/keys/query_service.rb deleted file mode 100644 index 33e13293f3155f..00000000000000 --- a/app/services/keys/query_service.rb +++ /dev/null @@ -1,79 +0,0 @@ -# frozen_string_literal: true - -class Keys::QueryService < BaseService - include JsonLdHelper - - class Result < ActiveModelSerializers::Model - attributes :account, :devices - - def initialize(account, devices) - super( - account: account, - devices: devices || [], - ) - end - - def find(device_id) - @devices.find { |device| device.device_id == device_id } - end - end - - class Device < ActiveModelSerializers::Model - attributes :device_id, :name, :identity_key, :fingerprint_key - - def initialize(attributes = {}) - super( - device_id: attributes[:device_id], - name: attributes[:name], - identity_key: attributes[:identity_key], - fingerprint_key: attributes[:fingerprint_key], - ) - @claim_url = attributes[:claim_url] - end - - def valid_claim_url? - return false if @claim_url.blank? - - begin - parsed_url = Addressable::URI.parse(@claim_url).normalize - rescue Addressable::URI::InvalidURIError - return false - end - - %w(http https).include?(parsed_url.scheme) && parsed_url.host.present? - end - end - - def call(account) - @account = account - - if @account.local? - query_local_devices! - else - query_remote_devices! - end - - Result.new(@account, @devices) - end - - private - - def query_local_devices! - @devices = @account.devices.map { |device| Device.new(device) } - end - - def query_remote_devices! - return if @account.devices_url.blank? - - json = fetch_resource(@account.devices_url) - - return if json['items'].blank? - - @devices = as_array(json['items']).map do |device| - Device.new(device_id: device['id'], name: device['name'], identity_key: device.dig('identityKey', 'publicKeyBase64'), fingerprint_key: device.dig('fingerprintKey', 'publicKeyBase64'), claim_url: device['claim']) - end - rescue HTTP::Error, OpenSSL::SSL::SSLError, Mastodon::Error => e - Rails.logger.debug { "Querying devices for #{@account.acct} failed: #{e}" } - nil - end -end diff --git a/app/validators/ed25519_key_validator.rb b/app/validators/ed25519_key_validator.rb deleted file mode 100644 index adf49296b2fb51..00000000000000 --- a/app/validators/ed25519_key_validator.rb +++ /dev/null @@ -1,19 +0,0 @@ -# frozen_string_literal: true - -class Ed25519KeyValidator < ActiveModel::EachValidator - def validate_each(record, attribute, value) - return if value.blank? - - key = Base64.decode64(value) - - record.errors.add(attribute, I18n.t('crypto.errors.invalid_key')) unless verified?(key) - end - - private - - def verified?(key) - Ed25519.validate_key_bytes(key) - rescue ArgumentError - false - end -end diff --git a/app/validators/ed25519_signature_validator.rb b/app/validators/ed25519_signature_validator.rb deleted file mode 100644 index 0e74c231ece4a3..00000000000000 --- a/app/validators/ed25519_signature_validator.rb +++ /dev/null @@ -1,29 +0,0 @@ -# frozen_string_literal: true - -class Ed25519SignatureValidator < ActiveModel::EachValidator - def validate_each(record, attribute, value) - return if value.blank? - - verify_key = Ed25519::VerifyKey.new(Base64.decode64(option_to_value(record, :verify_key))) - signature = Base64.decode64(value) - message = option_to_value(record, :message) - - record.errors.add(attribute, I18n.t('crypto.errors.invalid_signature')) unless verified?(verify_key, signature, message) - end - - private - - def verified?(verify_key, signature, message) - verify_key.verify(signature, message) - rescue Ed25519::VerifyError, ArgumentError - false - end - - def option_to_value(record, key) - if options[key].is_a?(Proc) - options[key].call(record) - else - record.public_send(options[key]) - end - end -end diff --git a/app/workers/push_encrypted_message_worker.rb b/app/workers/push_encrypted_message_worker.rb deleted file mode 100644 index 6bee12675b0db4..00000000000000 --- a/app/workers/push_encrypted_message_worker.rb +++ /dev/null @@ -1,16 +0,0 @@ -# frozen_string_literal: true - -class PushEncryptedMessageWorker - include Sidekiq::Worker - include Redisable - - def perform(encrypted_message_id) - encrypted_message = EncryptedMessage.find(encrypted_message_id) - message = InlineRenderer.render(encrypted_message, nil, :encrypted_message) - timeline_id = "timeline:#{encrypted_message.device.account_id}:#{encrypted_message.device.device_id}" - - redis.publish(timeline_id, Oj.dump(event: :encrypted_message, payload: message)) - rescue ActiveRecord::RecordNotFound - true - end -end diff --git a/config/initializers/doorkeeper.rb b/config/initializers/doorkeeper.rb index 86fde3cacf74dc..b47e76c08bc60a 100644 --- a/config/initializers/doorkeeper.rb +++ b/config/initializers/doorkeeper.rb @@ -118,8 +118,7 @@ :'admin:write:domain_blocks', :'admin:write:ip_blocks', :'admin:write:email_domain_blocks', - :'admin:write:canonical_email_blocks', - :crypto + :'admin:write:canonical_email_blocks' # Change the way client credentials are retrieved from the request object. # By default it retrieves first from the `HTTP_AUTHORIZATION` header, then diff --git a/config/initializers/inflections.rb b/config/initializers/inflections.rb index ba459e19f22c0d..2b0563f4d32a3a 100644 --- a/config/initializers/inflections.rb +++ b/config/initializers/inflections.rb @@ -19,7 +19,6 @@ inflect.acronym 'CLI' inflect.acronym 'DeepL' inflect.acronym 'DSL' - inflect.acronym 'Ed25519' inflect.acronym 'JsonLd' inflect.acronym 'OEmbed' inflect.acronym 'OStatus' diff --git a/config/locales/an.yml b/config/locales/an.yml index 589bb39836c567..2f181e0757da43 100644 --- a/config/locales/an.yml +++ b/config/locales/an.yml @@ -953,7 +953,6 @@ an: crypto: errors: invalid_key: no ye una clau Ed25519 u Curve25519 valida - invalid_signature: no ye una sinyatura Ed25519 valida date: formats: default: "%d %b %Y" diff --git a/config/locales/ar.yml b/config/locales/ar.yml index 7512e03fd50f18..81705acba045d6 100644 --- a/config/locales/ar.yml +++ b/config/locales/ar.yml @@ -1178,7 +1178,6 @@ ar: crypto: errors: invalid_key: ليس بمفتاح Ed25519 أو Curve25519 صالح - invalid_signature: ليس بتوقيع Ed25519 صالح date: formats: default: "%d %b %Y" diff --git a/config/locales/ast.yml b/config/locales/ast.yml index be3441507eacfb..a7d4a9917eebdb 100644 --- a/config/locales/ast.yml +++ b/config/locales/ast.yml @@ -486,7 +486,6 @@ ast: crypto: errors: invalid_key: nun ye una clave ed25519 o curve25519 válida - invalid_signature: nun ye una clave ed25519 válida datetime: distance_in_words: about_x_hours: "%{count} h" diff --git a/config/locales/be.yml b/config/locales/be.yml index 48ca5751cc806d..d45f443b75f06c 100644 --- a/config/locales/be.yml +++ b/config/locales/be.yml @@ -1189,7 +1189,6 @@ be: crypto: errors: invalid_key: гэта не сапраўдны Ed25519 або Curve25519 ключ - invalid_signature: гэта не сапраўдная Ed25519 сігнатура date: formats: default: "%d.%m.%Y" diff --git a/config/locales/bg.yml b/config/locales/bg.yml index 604eeca4808ba9..e2c246827afb4b 100644 --- a/config/locales/bg.yml +++ b/config/locales/bg.yml @@ -1115,7 +1115,6 @@ bg: crypto: errors: invalid_key: не е валиден ключ Ed25519 или Curve25519 - invalid_signature: не е валиден подпис Ed25519 date: formats: default: "%b %d, %Y" diff --git a/config/locales/ca.yml b/config/locales/ca.yml index d985a2ac42f858..33a1d4e88f7bfe 100644 --- a/config/locales/ca.yml +++ b/config/locales/ca.yml @@ -1171,7 +1171,6 @@ ca: crypto: errors: invalid_key: no és una clau Ed25519 o Curve25519 vàlida - invalid_signature: no és una signatura Ed25519 vàlida date: formats: default: "%b %d, %Y" diff --git a/config/locales/ckb.yml b/config/locales/ckb.yml index 8af3d8638831aa..bc668d2ce4c7e4 100644 --- a/config/locales/ckb.yml +++ b/config/locales/ckb.yml @@ -608,7 +608,6 @@ ckb: crypto: errors: invalid_key: کلیلی باوڕپێکراو Ed25519 یان Curve25519 دروست نییە - invalid_signature: واژووی Ed25519 بڕوادار نییە date: formats: default: "%b %d, %Y" diff --git a/config/locales/co.yml b/config/locales/co.yml index b072e5e4eff0f7..58ddd7d01b593f 100644 --- a/config/locales/co.yml +++ b/config/locales/co.yml @@ -569,7 +569,6 @@ co: crypto: errors: invalid_key: ùn hè micca una chjave Ed25519 o Curve25519 valida - invalid_signature: ùn hè micca una firma Ed25519 valida date: formats: default: "%d %b %Y" diff --git a/config/locales/cs.yml b/config/locales/cs.yml index 1000442870cc35..b7fc8ab1b0bc26 100644 --- a/config/locales/cs.yml +++ b/config/locales/cs.yml @@ -1149,7 +1149,6 @@ cs: crypto: errors: invalid_key: není platný klíč Ed25519 nebo Curve25519 - invalid_signature: není platný podpis typu Ed25519 date: formats: default: "%d. %b %Y" diff --git a/config/locales/cy.yml b/config/locales/cy.yml index 9d3c0c82f352d3..c2c193d9434292 100644 --- a/config/locales/cy.yml +++ b/config/locales/cy.yml @@ -1246,7 +1246,6 @@ cy: crypto: errors: invalid_key: ddim yn allwedd Ed25519 na Curve25519 dilys - invalid_signature: ddim yn llofnod Ed25519 dilys date: formats: default: "%b %d %Y" diff --git a/config/locales/da.yml b/config/locales/da.yml index 6f781742a85ecb..a177b97de76fd2 100644 --- a/config/locales/da.yml +++ b/config/locales/da.yml @@ -1174,7 +1174,6 @@ da: crypto: errors: invalid_key: er ikke en gyldig Ed25519- eller Curve25519-nøgle - invalid_signature: er ikke en gylidig Ed25519-signatur date: formats: default: "%d. %b %Y" diff --git a/config/locales/de.yml b/config/locales/de.yml index 040ddaaf6903bf..85e24c230ec339 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -1174,7 +1174,6 @@ de: crypto: errors: invalid_key: ist kein gültiger Ed25519- oder Curve25519-Schlüssel - invalid_signature: ist keine gültige Ed25519-Signatur date: formats: default: "%d. %b %Y" diff --git a/config/locales/el.yml b/config/locales/el.yml index 1f408e26ea96b7..0da957cdb28de7 100644 --- a/config/locales/el.yml +++ b/config/locales/el.yml @@ -1128,7 +1128,6 @@ el: crypto: errors: invalid_key: δεν είναι έγκυρο κλειδί Ed25519 ή Curve25519 - invalid_signature: δεν είναι έγκυρη υπογραφή Ed25519 date: formats: default: "%b %d, %Y" diff --git a/config/locales/en-GB.yml b/config/locales/en-GB.yml index 56255f5d7ae9e1..577978ce88d54f 100644 --- a/config/locales/en-GB.yml +++ b/config/locales/en-GB.yml @@ -1174,7 +1174,6 @@ en-GB: crypto: errors: invalid_key: is not a valid Ed25519 or Curve25519 key - invalid_signature: is not a valid Ed25519 signature date: formats: default: "%b %d, %Y" diff --git a/config/locales/en.yml b/config/locales/en.yml index b1c100da0c6fda..625f53f2e57108 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1174,7 +1174,6 @@ en: crypto: errors: invalid_key: is not a valid Ed25519 or Curve25519 key - invalid_signature: is not a valid Ed25519 signature date: formats: default: "%b %d, %Y" diff --git a/config/locales/eo.yml b/config/locales/eo.yml index 46c6cbcf866908..7ebf0748194067 100644 --- a/config/locales/eo.yml +++ b/config/locales/eo.yml @@ -1041,7 +1041,6 @@ eo: crypto: errors: invalid_key: 올바른 Ed25519 혹은 Curve25519 키가 아닙니다 - invalid_signature: 올바른 Ed25519 시그니처가 아닙니다 date: formats: default: "%Y-%b-%d" diff --git a/config/locales/es-AR.yml b/config/locales/es-AR.yml index 4d60d080a2f3ab..9f1dc46c9df8ce 100644 --- a/config/locales/es-AR.yml +++ b/config/locales/es-AR.yml @@ -1174,7 +1174,6 @@ es-AR: crypto: errors: invalid_key: no es una clave Ed25519 o Curve25519 válida - invalid_signature: no es una firma Ed25519 válida date: formats: default: "%d de %b de %Y" diff --git a/config/locales/es-MX.yml b/config/locales/es-MX.yml index 1ea346a00c7119..b39b23c6170801 100644 --- a/config/locales/es-MX.yml +++ b/config/locales/es-MX.yml @@ -1174,7 +1174,6 @@ es-MX: crypto: errors: invalid_key: no es una clave Ed25519 o Curve25519 válida - invalid_signature: no es una firma Ed25519 válida date: formats: default: "%d %b %Y" diff --git a/config/locales/es.yml b/config/locales/es.yml index c652876f3a9ef3..2815ada77957ce 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -1174,7 +1174,6 @@ es: crypto: errors: invalid_key: no es una clave Ed25519 o Curve25519 válida - invalid_signature: no es una firma Ed25519 válida date: formats: default: "%d %b %Y" diff --git a/config/locales/et.yml b/config/locales/et.yml index aca4f93c4db164..60f98a471e37a2 100644 --- a/config/locales/et.yml +++ b/config/locales/et.yml @@ -1174,7 +1174,6 @@ et: crypto: errors: invalid_key: ei ole õige Ed25519 ega Curve25519 võti - invalid_signature: ei ole õige Ed25519 allkiri date: formats: default: "%d. %b %Y" diff --git a/config/locales/eu.yml b/config/locales/eu.yml index 6260033990d309..7c7f995a7143b1 100644 --- a/config/locales/eu.yml +++ b/config/locales/eu.yml @@ -1091,7 +1091,6 @@ eu: crypto: errors: invalid_key: ez da baliozko Ed25519 edo Curve25519 gakoa - invalid_signature: ez da baliozko Ed25519 sinadura date: formats: default: "%Y(e)ko %b %d" diff --git a/config/locales/fa.yml b/config/locales/fa.yml index 996c8d6cd2abdf..c2c22af4c46a51 100644 --- a/config/locales/fa.yml +++ b/config/locales/fa.yml @@ -971,7 +971,6 @@ fa: crypto: errors: invalid_key: یک کلید معتبر Ed25519 یا Curve25519 نیست - invalid_signature: یک امضای معتبر Ed25519 نیست date: formats: default: "%d %b %Y" diff --git a/config/locales/fi.yml b/config/locales/fi.yml index b48b499bbe463a..5df82bff9fa440 100644 --- a/config/locales/fi.yml +++ b/config/locales/fi.yml @@ -1174,7 +1174,6 @@ fi: crypto: errors: invalid_key: ei ole kelvollinen Ed25519- tai Curve25519-avain - invalid_signature: ei ole kelvollinen Ed25519-allekirjoitus date: formats: default: "%b %d, %Y" diff --git a/config/locales/fo.yml b/config/locales/fo.yml index 266b73bb10aa1d..ce21aa3be7c44f 100644 --- a/config/locales/fo.yml +++ b/config/locales/fo.yml @@ -1174,7 +1174,6 @@ fo: crypto: errors: invalid_key: er ikki ein gildur Ed25519 ella Curve25519 lykil - invalid_signature: er ikki ein gildug Ed25519 undirskrift date: formats: default: "%b %d, %Y" diff --git a/config/locales/fr-CA.yml b/config/locales/fr-CA.yml index b70a515fd16eec..3f9252ffabff7b 100644 --- a/config/locales/fr-CA.yml +++ b/config/locales/fr-CA.yml @@ -1175,7 +1175,6 @@ fr-CA: crypto: errors: invalid_key: n’est pas une clé Ed25519 ou Curve25519 valide - invalid_signature: n’est pas une signature Ed25519 valide date: formats: default: "%d %b %Y" diff --git a/config/locales/fr.yml b/config/locales/fr.yml index bc616d28963b55..cb76ae2243c33d 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -1175,7 +1175,6 @@ fr: crypto: errors: invalid_key: n’est pas une clé Ed25519 ou Curve25519 valide - invalid_signature: n’est pas une signature Ed25519 valide date: formats: default: "%d %b %Y" diff --git a/config/locales/fy.yml b/config/locales/fy.yml index 6afdecd556d40d..8b854494d4b555 100644 --- a/config/locales/fy.yml +++ b/config/locales/fy.yml @@ -1164,7 +1164,6 @@ fy: crypto: errors: invalid_key: is gjin jildige Ed25519- of Curve25519-kaai - invalid_signature: is gjin jildige Ed25519-hantekening date: formats: default: "%d %b %Y" diff --git a/config/locales/ga.yml b/config/locales/ga.yml index 1071871c952327..a6369354cd8a56 100644 --- a/config/locales/ga.yml +++ b/config/locales/ga.yml @@ -1228,7 +1228,6 @@ ga: crypto: errors: invalid_key: nach eochair bhailí Ed25519 nó Curve25519 í - invalid_signature: nach síniú bailí Ed25519 é date: formats: default: "%b %d, %Y" diff --git a/config/locales/gd.yml b/config/locales/gd.yml index 5e63b5bd2300d0..b5cbc4a73ed372 100644 --- a/config/locales/gd.yml +++ b/config/locales/gd.yml @@ -1210,7 +1210,6 @@ gd: crypto: errors: invalid_key: "– chan e iuchair Ed25519 no Curve25519 dhligheach a th’ ann" - invalid_signature: "– chan e soidhneadh Ed25519 dligheach a th’ ann" date: formats: default: "%d %b %Y" diff --git a/config/locales/gl.yml b/config/locales/gl.yml index 58fd2d9bab50c4..9813514a7bf6cf 100644 --- a/config/locales/gl.yml +++ b/config/locales/gl.yml @@ -1174,7 +1174,6 @@ gl: crypto: errors: invalid_key: non é unha chave Ed25519 ou Curve25519 válida - invalid_signature: non é unha sinatura Ed25519 válida date: formats: default: "%d %b, %Y" diff --git a/config/locales/he.yml b/config/locales/he.yml index 7a2d0a1d987459..13a1f6f05d30d3 100644 --- a/config/locales/he.yml +++ b/config/locales/he.yml @@ -1210,7 +1210,6 @@ he: crypto: errors: invalid_key: זהו לא מפתח Ed25519 או Curve25519 קביל - invalid_signature: היא לא חתימת Ed25519 קבילה date: formats: default: "%b %d, %Y" diff --git a/config/locales/hu.yml b/config/locales/hu.yml index 10c7506b01159a..9767c488345493 100644 --- a/config/locales/hu.yml +++ b/config/locales/hu.yml @@ -1174,7 +1174,6 @@ hu: crypto: errors: invalid_key: érvénytelen Ed25519 vagy Curve25519 kulcs - invalid_signature: érvénytelen Ed25519 aláírás date: formats: default: "%Y. %b %d." diff --git a/config/locales/hy.yml b/config/locales/hy.yml index 80dbc77991c24c..1fda020c0790a4 100644 --- a/config/locales/hy.yml +++ b/config/locales/hy.yml @@ -495,7 +495,6 @@ hy: crypto: errors: invalid_key: անվաւեր Ed25519 կամ Curve25519 բանալի - invalid_signature: անվաւեր Ed25519 բանալի date: formats: default: "%b %d, %Y" diff --git a/config/locales/ia.yml b/config/locales/ia.yml index 957bae39915b7d..6632af061ec364 100644 --- a/config/locales/ia.yml +++ b/config/locales/ia.yml @@ -1158,7 +1158,6 @@ ia: crypto: errors: invalid_key: non es un clave Ed25519 o Curve25519 valide - invalid_signature: non es un signatura Ed25519 valide date: formats: default: "%d %b %Y" diff --git a/config/locales/id.yml b/config/locales/id.yml index 222d2b56803f4f..96a5022a7c81da 100644 --- a/config/locales/id.yml +++ b/config/locales/id.yml @@ -936,7 +936,6 @@ id: crypto: errors: invalid_key: bukan kunci Ed25519 atau Curve25519 yang valid - invalid_signature: bukan tanda tangan Ed25519 yang valid date: formats: default: "%d %b %Y" diff --git a/config/locales/ie.yml b/config/locales/ie.yml index 6a79686f484984..513a8eda7eb930 100644 --- a/config/locales/ie.yml +++ b/config/locales/ie.yml @@ -1089,7 +1089,6 @@ ie: crypto: errors: invalid_key: ne es un valid clave Ed25519 o Curve25519 - invalid_signature: ne es un valid signatura Ed25519 date: formats: default: "%d.%m.%Y" diff --git a/config/locales/io.yml b/config/locales/io.yml index dbbe228e72c236..97cc417df0f82e 100644 --- a/config/locales/io.yml +++ b/config/locales/io.yml @@ -1064,7 +1064,6 @@ io: crypto: errors: invalid_key: ne esas valida klefo Ed25519 o Curve25519 - invalid_signature: ne esas valida parafo Ed25519 date: formats: default: "%d %b, %Y" diff --git a/config/locales/is.yml b/config/locales/is.yml index 78dfd6048fe965..590805ad3027b0 100644 --- a/config/locales/is.yml +++ b/config/locales/is.yml @@ -1178,7 +1178,6 @@ is: crypto: errors: invalid_key: er ekki gildur Ed25519 eða Curve25519-lykill - invalid_signature: er ekki gild Ed25519 undirritun date: formats: default: "%d. %b, %Y" diff --git a/config/locales/it.yml b/config/locales/it.yml index 792add14e35e22..fe6fec17d778aa 100644 --- a/config/locales/it.yml +++ b/config/locales/it.yml @@ -1176,7 +1176,6 @@ it: crypto: errors: invalid_key: non è una chiave Ed25519 o Curve25519 valida - invalid_signature: non è una firma Ed25519 valida date: formats: default: "%d %b %Y" diff --git a/config/locales/ja.yml b/config/locales/ja.yml index 13f59e981b00e7..ed6293e1cabaa9 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -1146,7 +1146,6 @@ ja: crypto: errors: invalid_key: 有効なEd25519またはCurve25519キーではありません - invalid_signature: 有効なEd25519署名ではありません date: formats: default: "%Y年%m月%d日" diff --git a/config/locales/ko.yml b/config/locales/ko.yml index cbcc09a4dab1e3..3217a8e49a3842 100644 --- a/config/locales/ko.yml +++ b/config/locales/ko.yml @@ -1151,7 +1151,6 @@ ko: crypto: errors: invalid_key: 유효하지 않은 Ed25519 또는 Curve25519 키 - invalid_signature: 유효하지 않은 Ed25519 서명 date: formats: default: "%Y-%m-%d" diff --git a/config/locales/ku.yml b/config/locales/ku.yml index 6b80e32ba809c3..08843f645e2078 100644 --- a/config/locales/ku.yml +++ b/config/locales/ku.yml @@ -950,7 +950,6 @@ ku: crypto: errors: invalid_key: ed25519 ne derbasdare ne jî Curve25519 kilîta - invalid_signature: Ed25519 ne îmzeyek derbasdar e date: formats: default: "%b%d%Y" diff --git a/config/locales/lad.yml b/config/locales/lad.yml index 2f5eb1553e417e..3d1f1a61e2447d 100644 --- a/config/locales/lad.yml +++ b/config/locales/lad.yml @@ -1122,7 +1122,6 @@ lad: crypto: errors: invalid_key: no es una yave Ed25519 o Curve25519 valida - invalid_signature: no es una firma Ed25519 valida date: formats: default: "%d %b %Y" diff --git a/config/locales/lv.yml b/config/locales/lv.yml index 88faeae82bd3dc..be6555a3862657 100644 --- a/config/locales/lv.yml +++ b/config/locales/lv.yml @@ -1140,7 +1140,6 @@ lv: crypto: errors: invalid_key: nav derīga Ed25519 vai Curve25519 atslēga - invalid_signature: nav derīgs Ed25519 paraksts date: formats: default: "%b %d, %Y" diff --git a/config/locales/ms.yml b/config/locales/ms.yml index 39c695a5398adf..0c0ffb69bfc849 100644 --- a/config/locales/ms.yml +++ b/config/locales/ms.yml @@ -1051,7 +1051,6 @@ ms: crypto: errors: invalid_key: bukan kunci Ed25519 atau Curve25519 yang sah - invalid_signature: bukan tandatangan Ed25519 yang sah date: formats: default: "%b %d, %Y" diff --git a/config/locales/my.yml b/config/locales/my.yml index 92464523a06049..6acaa34cdc5c74 100644 --- a/config/locales/my.yml +++ b/config/locales/my.yml @@ -1044,7 +1044,6 @@ my: crypto: errors: invalid_key: မှန်ကန်သော Ed25519 သို့မဟုတ် Curve25519 ကီး မဟုတ်ပါ။ - invalid_signature: မှန်ကန်သော Ed25519 လက်မှတ်မဟုတ်ပါ date: formats: default: "%b %d, %Y" diff --git a/config/locales/nl.yml b/config/locales/nl.yml index 63656991a843a6..afc4652bf600cc 100644 --- a/config/locales/nl.yml +++ b/config/locales/nl.yml @@ -1174,7 +1174,6 @@ nl: crypto: errors: invalid_key: is geen geldige Ed25519- of Curve25519-sleutel - invalid_signature: is geen geldige Ed25519-handtekening date: formats: default: "%d %b %Y" diff --git a/config/locales/nn.yml b/config/locales/nn.yml index b7beeb4263ed53..dcf571a7923c46 100644 --- a/config/locales/nn.yml +++ b/config/locales/nn.yml @@ -1174,7 +1174,6 @@ nn: crypto: errors: invalid_key: er ikkje ein gild Ed25519 eller Curve25519 nykel - invalid_signature: er ikkje ein gild Ed25519-signatur date: formats: default: "%d. %b, %Y" diff --git a/config/locales/no.yml b/config/locales/no.yml index 635ceedde4c4d3..1f0b6baccecfd8 100644 --- a/config/locales/no.yml +++ b/config/locales/no.yml @@ -1083,7 +1083,6 @@ crypto: errors: invalid_key: er ikke en gyldig Ed25519- eller Curve25519-nøkkel - invalid_signature: er ikke en gyldig Ed25519-signatur date: formats: default: "%d. %b, %Y" diff --git a/config/locales/pl.yml b/config/locales/pl.yml index f0d09cb2d30178..314adf885f8dc6 100644 --- a/config/locales/pl.yml +++ b/config/locales/pl.yml @@ -1210,7 +1210,6 @@ pl: crypto: errors: invalid_key: nie jest prawidłowym kluczem Ed25519 lub Curve25519 - invalid_signature: nie jest prawidłowym podpisem Ed25519 date: formats: default: "%d. %b %Y" diff --git a/config/locales/pt-BR.yml b/config/locales/pt-BR.yml index d1140f36454458..7742a80569c1c8 100644 --- a/config/locales/pt-BR.yml +++ b/config/locales/pt-BR.yml @@ -1174,7 +1174,6 @@ pt-BR: crypto: errors: invalid_key: não é uma chave Ed25519 ou Curve25519 válida - invalid_signature: não é uma assinatura Ed25519 válida date: formats: default: "%d %b, %Y" diff --git a/config/locales/pt-PT.yml b/config/locales/pt-PT.yml index f155490eb5f2c2..d6c5c4a6ff01f7 100644 --- a/config/locales/pt-PT.yml +++ b/config/locales/pt-PT.yml @@ -1174,7 +1174,6 @@ pt-PT: crypto: errors: invalid_key: não é uma chave Ed25519 ou Curve25519 válida - invalid_signature: não é uma assinatura Ed25519 válida date: formats: default: "%d %b %Y" diff --git a/config/locales/ru.yml b/config/locales/ru.yml index d66dded89be07d..f1198c3f16b6c2 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -1129,7 +1129,6 @@ ru: crypto: errors: invalid_key: не является допустимым Ed25519 или Curve25519 ключом - invalid_signature: не является допустимой Ed25519 подписью date: formats: default: "%d %b %Y" diff --git a/config/locales/sc.yml b/config/locales/sc.yml index 435749f4706385..780270ddf17b2e 100644 --- a/config/locales/sc.yml +++ b/config/locales/sc.yml @@ -693,7 +693,6 @@ sc: crypto: errors: invalid_key: no est una crae Ed25519 o Curve25519 vàlida - invalid_signature: no est una firma Ed25519 vàlida date: formats: default: "%d %b, %Y" diff --git a/config/locales/sco.yml b/config/locales/sco.yml index 70143a968e84cb..97eaa21ed6d0d6 100644 --- a/config/locales/sco.yml +++ b/config/locales/sco.yml @@ -940,7 +940,6 @@ sco: crypto: errors: invalid_key: isnae a valid Ed25519 or Curve25519 key - invalid_signature: isnae a valid Ed25519 signature date: formats: default: "%b %d, %Y" diff --git a/config/locales/si.yml b/config/locales/si.yml index 135a99ceb21703..8460de01da9f6a 100644 --- a/config/locales/si.yml +++ b/config/locales/si.yml @@ -829,7 +829,6 @@ si: crypto: errors: invalid_key: වලංගු Ed25519 හෝ Curve25519 යතුරක් නොවේ - invalid_signature: වලංගු Ed25519 අත්සනක් නොවේ date: formats: default: "%Y %b %d" diff --git a/config/locales/sl.yml b/config/locales/sl.yml index ef6d00b8d31a68..c8e806bf35efa9 100644 --- a/config/locales/sl.yml +++ b/config/locales/sl.yml @@ -1196,7 +1196,6 @@ sl: crypto: errors: invalid_key: ni veljaven ključ Ed25519 ali Curve25519 - invalid_signature: ni veljaven podpis Ed25519 date: formats: default: "%d %b %Y" diff --git a/config/locales/sq.yml b/config/locales/sq.yml index 70d20592a5283d..294c8a888f043f 100644 --- a/config/locales/sq.yml +++ b/config/locales/sq.yml @@ -1166,7 +1166,6 @@ sq: crypto: errors: invalid_key: s’është kyç Ed25519 ose Curve25519 i vlefshëm - invalid_signature: s’është nënshkrim Ed25519 i vlefshëm date: formats: default: "%d %b, %Y" diff --git a/config/locales/sr-Latn.yml b/config/locales/sr-Latn.yml index 91f0933398d5b8..bfb52c275b66b1 100644 --- a/config/locales/sr-Latn.yml +++ b/config/locales/sr-Latn.yml @@ -1110,7 +1110,6 @@ sr-Latn: crypto: errors: invalid_key: nije validan Ed25519 ili Curve25519 ključ - invalid_signature: nije validan Ed25519 potpis date: formats: default: "%d. %b. %Y." diff --git a/config/locales/sr.yml b/config/locales/sr.yml index 67aee931be8922..af7e7ab8d6945e 100644 --- a/config/locales/sr.yml +++ b/config/locales/sr.yml @@ -1140,7 +1140,6 @@ sr: crypto: errors: invalid_key: није валидан Ed25519 или Curve25519 кључ - invalid_signature: није валидан Ed25519 потпис date: formats: default: "%d. %b. %Y." diff --git a/config/locales/sv.yml b/config/locales/sv.yml index 99b7ec9b3a1c52..6146cfc8d7feeb 100644 --- a/config/locales/sv.yml +++ b/config/locales/sv.yml @@ -1128,7 +1128,6 @@ sv: crypto: errors: invalid_key: är inte en giltig Ed25519 eller Curve25519 nyckel - invalid_signature: är inte en giltig Ed25519 signatur date: formats: default: "%b %d, %Y" diff --git a/config/locales/ta.yml b/config/locales/ta.yml index 3a98b6a25d44e3..56b2895c74fa3f 100644 --- a/config/locales/ta.yml +++ b/config/locales/ta.yml @@ -188,7 +188,6 @@ ta: crypto: errors: invalid_key: ஒரு முறையான Ed25519 அல்லது Curve25519 key அல்ல - invalid_signature: ஒரு முறையான Ed25519 அடையாளம் அல்ல filters: index: empty: தடுப்புகள் ஏதும் இல்லை. diff --git a/config/locales/th.yml b/config/locales/th.yml index 65424f4eb6c66c..d56385f2611add 100644 --- a/config/locales/th.yml +++ b/config/locales/th.yml @@ -1156,7 +1156,6 @@ th: crypto: errors: invalid_key: ไม่ใช่กุญแจ Ed25519 หรือ Curve25519 ที่ถูกต้อง - invalid_signature: ไม่ใช่ลายเซ็น Ed25519 ที่ถูกต้อง date: formats: default: "%d %b %Y" diff --git a/config/locales/tr.yml b/config/locales/tr.yml index d6ca6b4276c9b7..16dd4c899ded7c 100644 --- a/config/locales/tr.yml +++ b/config/locales/tr.yml @@ -1174,7 +1174,6 @@ tr: crypto: errors: invalid_key: geçerli bir Ed25519 veya Curve25519 anahtarı değil - invalid_signature: geçerli bir Ed25519 imzası değil date: formats: default: "%d %b %Y" diff --git a/config/locales/uk.yml b/config/locales/uk.yml index e8c4e689981712..0ef08a15555659 100644 --- a/config/locales/uk.yml +++ b/config/locales/uk.yml @@ -1210,7 +1210,6 @@ uk: crypto: errors: invalid_key: не є припустимим ключем Ed25519 або Curve25519 - invalid_signature: не є дійсним підписом Ed25519 date: formats: default: "%b %d, %Y" diff --git a/config/locales/vi.yml b/config/locales/vi.yml index 8f047a2cb7b2c4..98696aef7c8b63 100644 --- a/config/locales/vi.yml +++ b/config/locales/vi.yml @@ -1156,7 +1156,6 @@ vi: crypto: errors: invalid_key: không phải là mã khóa Ed25519 hoặc Curve25519 đúng - invalid_signature: không phải là chữ ký số Ed25519 đúng date: formats: default: "%-d %B, %Y" diff --git a/config/locales/zh-CN.yml b/config/locales/zh-CN.yml index 277785f683aa97..8b34da076a0162 100644 --- a/config/locales/zh-CN.yml +++ b/config/locales/zh-CN.yml @@ -1156,7 +1156,6 @@ zh-CN: crypto: errors: invalid_key: 不是有效的 Ed25519 或者 Curve25519 密钥 - invalid_signature: 不是有效的 Ed25519 签名 date: formats: default: "%Y年%m月%d日" diff --git a/config/locales/zh-HK.yml b/config/locales/zh-HK.yml index 7682712759c240..598c65d0494ec7 100644 --- a/config/locales/zh-HK.yml +++ b/config/locales/zh-HK.yml @@ -1071,7 +1071,6 @@ zh-HK: crypto: errors: invalid_key: 不是一個有效的 Ed25519 或 Curve25519 密鑰 - invalid_signature: 不是一個有效的 Ed25519 簽名 date: formats: default: "%Y年%b月%d日" diff --git a/config/locales/zh-TW.yml b/config/locales/zh-TW.yml index 35f000b6016edb..41c4c8a534e482 100644 --- a/config/locales/zh-TW.yml +++ b/config/locales/zh-TW.yml @@ -1158,7 +1158,6 @@ zh-TW: crypto: errors: invalid_key: 這不是一把有效的 Ed25519 或 Curve25519 金鑰 - invalid_signature: 這不是有效的 Ed25519 簽章 date: formats: default: "%Y年%b月%d日" diff --git a/config/routes.rb b/config/routes.rb index 3ac619519d40f7..ad607e537be9f1 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -134,7 +134,6 @@ def redirect_with_vary(path) scope module: :activitypub do resource :outbox, only: [:show] resource :inbox, only: [:create] - resource :claim, only: [:create] resources :collections, only: [:show] resource :followers_synchronization, only: [:show] end diff --git a/config/routes/api.rb b/config/routes/api.rb index b237791736f872..39c699b9a3042b 100644 --- a/config/routes/api.rb +++ b/config/routes/api.rb @@ -68,23 +68,6 @@ end end - # namespace :crypto do - # resources :deliveries, only: :create - - # namespace :keys do - # resource :upload, only: [:create] - # resource :query, only: [:create] - # resource :claim, only: [:create] - # resource :count, only: [:show] - # end - - # resources :encrypted_messages, only: [:index] do - # collection do - # post :clear - # end - # end - # end - resources :conversations, only: [:index, :destroy] do member do post :read diff --git a/db/migrate/20240720140205_drop_end_to_end_message_tables.rb b/db/migrate/20240720140205_drop_end_to_end_message_tables.rb new file mode 100644 index 00000000000000..dd5662885c59b3 --- /dev/null +++ b/db/migrate/20240720140205_drop_end_to_end_message_tables.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +class DropEndToEndMessageTables < ActiveRecord::Migration[7.1] + def up + drop_table :system_keys + drop_table :one_time_keys + drop_table :encrypted_messages + drop_table :devices + safety_assured { remove_column :accounts, :devices_url } + end + + def down + raise ActiveRecord::IrreversibleMigration + end +end diff --git a/db/schema.rb b/db/schema.rb index 540a971333a52a..e9aa78c2054557 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -193,7 +193,6 @@ t.boolean "hide_collections" t.integer "avatar_storage_schema_version" t.integer "header_storage_schema_version" - t.string "devices_url" t.datetime "sensitized_at", precision: nil t.integer "suspension_origin" t.boolean "trendable" @@ -412,19 +411,6 @@ t.index ["account_id"], name: "index_custom_filters_on_account_id" end - create_table "devices", force: :cascade do |t| - t.bigint "access_token_id" - t.bigint "account_id" - t.string "device_id", default: "", null: false - t.string "name", default: "", null: false - t.text "fingerprint_key", default: "", null: false - t.text "identity_key", default: "", null: false - t.datetime "created_at", precision: nil, null: false - t.datetime "updated_at", precision: nil, null: false - t.index ["access_token_id"], name: "index_devices_on_access_token_id" - t.index ["account_id"], name: "index_devices_on_account_id" - end - create_table "domain_allows", force: :cascade do |t| t.string "domain", default: "", null: false t.datetime "created_at", precision: nil, null: false @@ -454,20 +440,6 @@ t.index ["domain"], name: "index_email_domain_blocks_on_domain", unique: true end - create_table "encrypted_messages", id: :bigint, default: -> { "timestamp_id('encrypted_messages'::text)" }, force: :cascade do |t| - t.bigint "device_id" - t.bigint "from_account_id" - t.string "from_device_id", default: "", null: false - t.integer "type", default: 0, null: false - t.text "body", default: "", null: false - t.text "digest", default: "", null: false - t.text "message_franking", default: "", null: false - t.datetime "created_at", precision: nil, null: false - t.datetime "updated_at", precision: nil, null: false - t.index ["device_id"], name: "index_encrypted_messages_on_device_id" - t.index ["from_account_id"], name: "index_encrypted_messages_on_from_account_id" - end - create_table "favourites", force: :cascade do |t| t.datetime "created_at", precision: nil, null: false t.datetime "updated_at", precision: nil, null: false @@ -781,17 +753,6 @@ t.index ["uid"], name: "index_oauth_applications_on_uid", unique: true end - create_table "one_time_keys", force: :cascade do |t| - t.bigint "device_id" - t.string "key_id", default: "", null: false - t.text "key", default: "", null: false - t.text "signature", default: "", null: false - t.datetime "created_at", precision: nil, null: false - t.datetime "updated_at", precision: nil, null: false - t.index ["device_id"], name: "index_one_time_keys_on_device_id" - t.index ["key_id"], name: "index_one_time_keys_on_key_id" - end - create_table "pghero_space_stats", force: :cascade do |t| t.text "database" t.text "schema" @@ -1104,12 +1065,6 @@ t.index ["status_id"], name: "index_statuses_tags_on_status_id" end - create_table "system_keys", force: :cascade do |t| - t.binary "key" - t.datetime "created_at", precision: nil, null: false - t.datetime "updated_at", precision: nil, null: false - end - create_table "tag_follows", force: :cascade do |t| t.bigint "tag_id", null: false t.bigint "account_id", null: false @@ -1306,11 +1261,7 @@ add_foreign_key "custom_filter_statuses", "custom_filters", on_delete: :cascade add_foreign_key "custom_filter_statuses", "statuses", on_delete: :cascade add_foreign_key "custom_filters", "accounts", on_delete: :cascade - add_foreign_key "devices", "accounts", on_delete: :cascade - add_foreign_key "devices", "oauth_access_tokens", column: "access_token_id", on_delete: :cascade add_foreign_key "email_domain_blocks", "email_domain_blocks", column: "parent_id", on_delete: :cascade - add_foreign_key "encrypted_messages", "accounts", column: "from_account_id", on_delete: :cascade - add_foreign_key "encrypted_messages", "devices", on_delete: :cascade add_foreign_key "favourites", "accounts", name: "fk_5eb6c2b873", on_delete: :cascade add_foreign_key "favourites", "statuses", name: "fk_b0e856845e", on_delete: :cascade add_foreign_key "featured_tags", "accounts", on_delete: :cascade @@ -1353,7 +1304,6 @@ add_foreign_key "oauth_access_tokens", "oauth_applications", column: "application_id", name: "fk_f5fc4c1ee3", on_delete: :cascade add_foreign_key "oauth_access_tokens", "users", column: "resource_owner_id", name: "fk_e84df68546", on_delete: :cascade add_foreign_key "oauth_applications", "users", column: "owner_id", name: "fk_b0988c7c0a", on_delete: :cascade - add_foreign_key "one_time_keys", "devices", on_delete: :cascade add_foreign_key "poll_votes", "accounts", on_delete: :cascade add_foreign_key "poll_votes", "polls", on_delete: :cascade add_foreign_key "polls", "accounts", on_delete: :cascade diff --git a/spec/controllers/activitypub/claims_controller_spec.rb b/spec/controllers/activitypub/claims_controller_spec.rb deleted file mode 100644 index e887be2cbe1ae2..00000000000000 --- a/spec/controllers/activitypub/claims_controller_spec.rb +++ /dev/null @@ -1,19 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -RSpec.describe ActivityPub::ClaimsController do - let(:account) { Fabricate(:account) } - - describe 'POST #create' do - context 'without signature' do - before do - post :create, params: { account_username: account.username }, body: '{}' - end - - it 'returns http not authorized' do - expect(response).to have_http_status(401) - end - end - end -end diff --git a/spec/fabricators/device_fabricator.rb b/spec/fabricators/device_fabricator.rb deleted file mode 100644 index 37a2e8977d133b..00000000000000 --- a/spec/fabricators/device_fabricator.rb +++ /dev/null @@ -1,10 +0,0 @@ -# frozen_string_literal: true - -Fabricator(:device) do - access_token { Fabricate.build(:access_token) } - account { Fabricate.build(:account) } - device_id { Faker::Number.number(digits: 5) } - name { Faker::App.name } - fingerprint_key { Base64.strict_encode64(Ed25519::SigningKey.generate.verify_key.to_bytes) } - identity_key { Base64.strict_encode64(Ed25519::SigningKey.generate.verify_key.to_bytes) } -end diff --git a/spec/fabricators/encrypted_message_fabricator.rb b/spec/fabricators/encrypted_message_fabricator.rb deleted file mode 100644 index 349b659c2f515c..00000000000000 --- a/spec/fabricators/encrypted_message_fabricator.rb +++ /dev/null @@ -1,7 +0,0 @@ -# frozen_string_literal: true - -Fabricator(:encrypted_message) do - device { Fabricate.build(:device) } - from_account { Fabricate.build(:account) } - from_device_id { Faker::Number.number(digits: 5) } -end diff --git a/spec/fabricators/one_time_key_fabricator.rb b/spec/fabricators/one_time_key_fabricator.rb deleted file mode 100644 index 505282e05ddfbc..00000000000000 --- a/spec/fabricators/one_time_key_fabricator.rb +++ /dev/null @@ -1,13 +0,0 @@ -# frozen_string_literal: true - -Fabricator(:one_time_key) do - device { Fabricate.build(:device) } - key_id { Faker::Alphanumeric.alphanumeric(number: 10) } - key { Base64.strict_encode64(Ed25519::SigningKey.generate.verify_key.to_bytes) } - - signature do |attrs| - signing_key = Ed25519::SigningKey.generate - attrs[:device].update(fingerprint_key: Base64.strict_encode64(signing_key.verify_key.to_bytes)) - Base64.strict_encode64(signing_key.sign(attrs[:key])) - end -end diff --git a/spec/fabricators/system_key_fabricator.rb b/spec/fabricators/system_key_fabricator.rb deleted file mode 100644 index bcb3bd5577b14f..00000000000000 --- a/spec/fabricators/system_key_fabricator.rb +++ /dev/null @@ -1,3 +0,0 @@ -# frozen_string_literal: true - -Fabricator(:system_key) diff --git a/spec/lib/activitypub/activity/create_spec.rb b/spec/lib/activitypub/activity/create_spec.rb index dec17b916b803b..0986ae17151e0f 100644 --- a/spec/lib/activitypub/activity/create_spec.rb +++ b/spec/lib/activitypub/activity/create_spec.rb @@ -936,64 +936,6 @@ def activity_for_object(json) end end - context 'with an encrypted message' do - subject { described_class.new(json, sender, delivery: true, delivered_to_account_id: recipient.id) } - - let(:recipient) { Fabricate(:account) } - let(:object_json) do - { - id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, - type: 'EncryptedMessage', - attributedTo: { - type: 'Device', - deviceId: '1234', - }, - to: { - type: 'Device', - deviceId: target_device.device_id, - }, - messageType: 1, - cipherText: 'Foo', - messageFranking: 'Baz678', - digest: { - digestAlgorithm: 'Bar456', - digestValue: 'Foo123', - }, - } - end - let(:target_device) { Fabricate(:device, account: recipient) } - - before do - subject.perform - end - - it 'creates an encrypted message' do - encrypted_message = target_device.encrypted_messages.reload.first - - expect(encrypted_message) - .to be_present - .and have_attributes( - from_device_id: eq('1234'), - from_account: eq(sender), - type: eq(1), - body: eq('Foo'), - digest: eq('Foo123') - ) - end - - it 'creates a message franking' do - encrypted_message = target_device.encrypted_messages.reload.first - message_franking = encrypted_message.message_franking - - crypt = ActiveSupport::MessageEncryptor.new(SystemKey.current_key, serializer: Oj) - json = crypt.decrypt_and_verify(message_franking) - - expect(json['source_account_id']).to eq sender.id - expect(json['target_account_id']).to eq recipient.id - expect(json['original_franking']).to eq 'Baz678' - end - end - context 'when sender is followed by local users' do subject { described_class.new(json, sender, delivery: true) } diff --git a/spec/lib/vacuum/system_keys_vacuum_spec.rb b/spec/lib/vacuum/system_keys_vacuum_spec.rb deleted file mode 100644 index 84cae30411e760..00000000000000 --- a/spec/lib/vacuum/system_keys_vacuum_spec.rb +++ /dev/null @@ -1,24 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -RSpec.describe Vacuum::SystemKeysVacuum do - subject { described_class.new } - - describe '#perform' do - let!(:expired_system_key) { Fabricate(:system_key, created_at: (SystemKey::ROTATION_PERIOD * 4).ago) } - let!(:current_system_key) { Fabricate(:system_key) } - - before do - subject.perform - end - - it 'deletes the expired key' do - expect { expired_system_key.reload }.to raise_error ActiveRecord::RecordNotFound - end - - it 'does not delete the current key' do - expect { current_system_key.reload }.to_not raise_error - end - end -end diff --git a/spec/models/one_time_key_spec.rb b/spec/models/one_time_key_spec.rb deleted file mode 100644 index 17fcdf37883987..00000000000000 --- a/spec/models/one_time_key_spec.rb +++ /dev/null @@ -1,23 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -RSpec.describe OneTimeKey do - describe 'validations' do - context 'with an invalid signature' do - let(:one_time_key) { Fabricate.build(:one_time_key, signature: 'wrong!') } - - it 'is invalid' do - expect(one_time_key).to_not be_valid - end - end - - context 'with an invalid key' do - let(:one_time_key) { Fabricate.build(:one_time_key, key: 'wrong!') } - - it 'is invalid' do - expect(one_time_key).to_not be_valid - end - end - end -end diff --git a/spec/serializers/activitypub/device_serializer_spec.rb b/spec/serializers/activitypub/device_serializer_spec.rb deleted file mode 100644 index 226e1364465407..00000000000000 --- a/spec/serializers/activitypub/device_serializer_spec.rb +++ /dev/null @@ -1,14 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -RSpec.describe ActivityPub::DeviceSerializer do - let(:serialization) { serialized_record_json(record, described_class) } - let(:record) { Fabricate(:device) } - - describe 'type' do - it 'returns correct serialized type' do - expect(serialization['type']).to eq('Device') - end - end -end diff --git a/spec/serializers/activitypub/one_time_key_serializer_spec.rb b/spec/serializers/activitypub/one_time_key_serializer_spec.rb deleted file mode 100644 index b9792ebae35445..00000000000000 --- a/spec/serializers/activitypub/one_time_key_serializer_spec.rb +++ /dev/null @@ -1,14 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -RSpec.describe ActivityPub::OneTimeKeySerializer do - let(:serialization) { serialized_record_json(record, described_class) } - let(:record) { Fabricate(:one_time_key) } - - describe 'type' do - it 'returns correct serialized type' do - expect(serialization['type']).to eq('Curve25519Key') - end - end -end diff --git a/spec/serializers/rest/encrypted_message_serializer_spec.rb b/spec/serializers/rest/encrypted_message_serializer_spec.rb deleted file mode 100644 index a4b8ee83b2a1e9..00000000000000 --- a/spec/serializers/rest/encrypted_message_serializer_spec.rb +++ /dev/null @@ -1,14 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -RSpec.describe REST::EncryptedMessageSerializer do - let(:serialization) { serialized_record_json(record, described_class) } - let(:record) { Fabricate(:encrypted_message) } - - describe 'account' do - it 'returns the associated account' do - expect(serialization['account_id']).to eq(record.from_account.id.to_s) - end - end -end diff --git a/spec/serializers/rest/keys/claim_result_serializer_spec.rb b/spec/serializers/rest/keys/claim_result_serializer_spec.rb deleted file mode 100644 index e45112705b70cd..00000000000000 --- a/spec/serializers/rest/keys/claim_result_serializer_spec.rb +++ /dev/null @@ -1,14 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -RSpec.describe REST::Keys::ClaimResultSerializer do - let(:serialization) { serialized_record_json(record, described_class) } - let(:record) { Keys::ClaimService::Result.new(Account.new(id: 123), 456) } - - describe 'account' do - it 'returns the associated account' do - expect(serialization['account_id']).to eq('123') - end - end -end diff --git a/spec/serializers/rest/keys/device_serializer_spec.rb b/spec/serializers/rest/keys/device_serializer_spec.rb deleted file mode 100644 index b8370beac706f6..00000000000000 --- a/spec/serializers/rest/keys/device_serializer_spec.rb +++ /dev/null @@ -1,14 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -RSpec.describe REST::Keys::DeviceSerializer do - let(:serialization) { serialized_record_json(record, described_class) } - let(:record) { Device.new(name: 'Device name') } - - describe 'name' do - it 'returns the name' do - expect(serialization['name']).to eq('Device name') - end - end -end diff --git a/spec/serializers/rest/keys/query_result_serializer_spec.rb b/spec/serializers/rest/keys/query_result_serializer_spec.rb deleted file mode 100644 index 41492f5e78f265..00000000000000 --- a/spec/serializers/rest/keys/query_result_serializer_spec.rb +++ /dev/null @@ -1,14 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -RSpec.describe REST::Keys::QueryResultSerializer do - let(:serialization) { serialized_record_json(record, described_class) } - let(:record) { Keys::QueryService::Result.new(Account.new(id: 123), []) } - - describe 'account' do - it 'returns the associated account id' do - expect(serialization['account_id']).to eq('123') - end - end -end diff --git a/spec/workers/push_encrypted_message_worker_spec.rb b/spec/workers/push_encrypted_message_worker_spec.rb deleted file mode 100644 index 311545cf562297..00000000000000 --- a/spec/workers/push_encrypted_message_worker_spec.rb +++ /dev/null @@ -1,13 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -RSpec.describe PushEncryptedMessageWorker do - let(:worker) { described_class.new } - - describe 'perform' do - it 'runs without error for missing record' do - expect { worker.perform(nil) }.to_not raise_error - end - end -end diff --git a/streaming/index.js b/streaming/index.js index b302565a4e0521..48ed56b29b5d08 100644 --- a/streaming/index.js +++ b/streaming/index.js @@ -44,7 +44,6 @@ initializeLogLevel(process.env, environment); * @property {string[]} scopes * @property {string} accountId * @property {string[]} chosenLanguages - * @property {string} deviceId */ @@ -355,7 +354,7 @@ const startServer = async () => { * @returns {Promise} */ const accountFromToken = async (token, req) => { - const result = await pgPool.query('SELECT oauth_access_tokens.id, oauth_access_tokens.resource_owner_id, users.account_id, users.chosen_languages, oauth_access_tokens.scopes, devices.device_id FROM oauth_access_tokens INNER JOIN users ON oauth_access_tokens.resource_owner_id = users.id LEFT OUTER JOIN devices ON oauth_access_tokens.id = devices.access_token_id WHERE oauth_access_tokens.token = $1 AND oauth_access_tokens.revoked_at IS NULL LIMIT 1', [token]); + const result = await pgPool.query('SELECT oauth_access_tokens.id, oauth_access_tokens.resource_owner_id, users.account_id, users.chosen_languages, oauth_access_tokens.scopes FROM oauth_access_tokens INNER JOIN users ON oauth_access_tokens.resource_owner_id = users.id WHERE oauth_access_tokens.token = $1 AND oauth_access_tokens.revoked_at IS NULL LIMIT 1', [token]); if (result.rows.length === 0) { throw new AuthenticationError('Invalid access token'); @@ -365,14 +364,12 @@ const startServer = async () => { req.scopes = result.rows[0].scopes.split(' '); req.accountId = result.rows[0].account_id; req.chosenLanguages = result.rows[0].chosen_languages; - req.deviceId = result.rows[0].device_id; return { accessTokenId: result.rows[0].id, scopes: result.rows[0].scopes.split(' '), accountId: result.rows[0].account_id, chosenLanguages: result.rows[0].chosen_languages, - deviceId: result.rows[0].device_id }; }; @@ -983,10 +980,6 @@ const startServer = async () => { const channelsForUserStream = req => { const arr = [`timeline:${req.accountId}`]; - if (isInScope(req, ['crypto']) && req.deviceId) { - arr.push(`timeline:${req.accountId}:${req.deviceId}`); - } - if (isInScope(req, ['read', 'read:notifications'])) { arr.push(`timeline:${req.accountId}:notifications`); } From 6f836c45aa5862cdfd65877c99252fcb28ab4da1 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Wed, 18 Sep 2024 05:27:50 -0400 Subject: [PATCH 12/51] Remove `crypto` values from doorkeeper application/token `scopes` (#31945) --- ...240916190140_remove_crypto_scope_values.rb | 33 +++++++++++++++++++ db/schema.rb | 2 +- 2 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 db/migrate/20240916190140_remove_crypto_scope_values.rb diff --git a/db/migrate/20240916190140_remove_crypto_scope_values.rb b/db/migrate/20240916190140_remove_crypto_scope_values.rb new file mode 100644 index 00000000000000..8caf5b801dac88 --- /dev/null +++ b/db/migrate/20240916190140_remove_crypto_scope_values.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +class RemoveCryptoScopeValues < ActiveRecord::Migration[7.1] + def up + applications.in_batches do |records| + records.update_all(<<~SQL.squish) + scopes = TRIM(REPLACE(scopes, 'crypto', '')) + SQL + end + + tokens.in_batches do |records| + records.update_all(<<~SQL.squish) + scopes = TRIM(REPLACE(scopes, 'crypto', '')) + SQL + end + end + + def down + raise ActiveRecord::IrreversibleMigration + end + + private + + def applications + Doorkeeper::Application + .where("scopes LIKE '%crypto%'") + end + + def tokens + Doorkeeper::AccessToken + .where("scopes LIKE '%crypto%'") + end +end diff --git a/db/schema.rb b/db/schema.rb index e9aa78c2054557..f9f2d9739593b4 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_09_09_014637) do +ActiveRecord::Schema[7.1].define(version: 2024_09_16_190140) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" From bf8eaaa9a505f86bf7c965f981f585af34337635 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Wed, 18 Sep 2024 05:42:36 -0400 Subject: [PATCH 13/51] Convert controller spec for security_key_options endpoint to request spec (#31938) --- .../auth/sessions_controller_spec.rb | 40 --------------- .../sessions/security_key_options_spec.rb | 50 +++++++++++++++++++ 2 files changed, 50 insertions(+), 40 deletions(-) create mode 100644 spec/requests/auth/sessions/security_key_options_spec.rb diff --git a/spec/controllers/auth/sessions_controller_spec.rb b/spec/controllers/auth/sessions_controller_spec.rb index 3cc3460718caed..713ea3ff16c5bf 100644 --- a/spec/controllers/auth/sessions_controller_spec.rb +++ b/spec/controllers/auth/sessions_controller_spec.rb @@ -412,44 +412,4 @@ end end end - - describe 'GET #webauthn_options' do - subject { get :webauthn_options, session: { attempt_user_id: user.id } } - - let!(:user) do - Fabricate(:user, email: 'x@y.com', password: 'abcdefgh', otp_required_for_login: true, otp_secret: User.generate_otp_secret(32)) - end - - context 'with WebAuthn and OTP enabled as second factor' do - let(:domain) { "#{Rails.configuration.x.use_https ? 'https' : 'http'}://#{Rails.configuration.x.web_domain}" } - - let(:fake_client) { WebAuthn::FakeClient.new(domain) } - - before do - user.update(webauthn_id: WebAuthn.generate_user_id) - public_key_credential = WebAuthn::Credential.from_create(fake_client.create) - user.webauthn_credentials.create( - nickname: 'SecurityKeyNickname', - external_id: public_key_credential.id, - public_key: public_key_credential.public_key, - sign_count: '1000' - ) - post :create, params: { user: { email: user.email, password: user.password } } - end - - it 'returns http success' do - subject - - expect(response).to have_http_status 200 - end - end - - context 'when WebAuthn not enabled' do - it 'returns http unauthorized' do - subject - - expect(response).to have_http_status 401 - end - end - end end diff --git a/spec/requests/auth/sessions/security_key_options_spec.rb b/spec/requests/auth/sessions/security_key_options_spec.rb new file mode 100644 index 00000000000000..6246e4beb315f7 --- /dev/null +++ b/spec/requests/auth/sessions/security_key_options_spec.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +require 'rails_helper' +require 'webauthn/fake_client' + +RSpec.describe 'Security Key Options' do + describe 'GET /auth/sessions/security_key_options' do + let!(:user) do + Fabricate(:user, email: 'x@y.com', password: 'abcdefgh', otp_required_for_login: true, otp_secret: User.generate_otp_secret(32)) + end + + context 'with WebAuthn and OTP enabled as second factor' do + let(:domain) { "#{Rails.configuration.x.use_https ? 'https' : 'http'}://#{Rails.configuration.x.web_domain}" } + + let(:fake_client) { WebAuthn::FakeClient.new(domain) } + let(:public_key_credential) { WebAuthn::Credential.from_create(fake_client.create) } + + before do + user.update(webauthn_id: WebAuthn.generate_user_id) + Fabricate( + :webauthn_credential, + user_id: user.id, + external_id: public_key_credential.id, + public_key: public_key_credential.public_key + ) + post '/auth/sign_in', params: { user: { email: user.email, password: user.password } } + end + + it 'returns http success' do + get '/auth/sessions/security_key_options' + + expect(response) + .to have_http_status 200 + expect(response.content_type) + .to start_with('application/json') + end + end + + context 'when WebAuthn not enabled' do + it 'returns http unauthorized' do + get '/auth/sessions/security_key_options' + + expect(response) + .to have_http_status 401 + expect(response.content_type) + .to start_with('application/json') + end + end + end +end From 42f9f507b632c1b90fecfed5f38ce2dbb2d0c949 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 18 Sep 2024 15:29:21 +0200 Subject: [PATCH 14/51] Update dependency pg to v8.13.0 (#31949) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/yarn.lock b/yarn.lock index 45ede6ca997bc3..5372c39f47306f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13001,7 +13001,7 @@ __metadata: languageName: node linkType: hard -"pg-connection-string@npm:^2.6.0, pg-connection-string@npm:^2.6.4": +"pg-connection-string@npm:^2.6.0, pg-connection-string@npm:^2.7.0": version: 2.7.0 resolution: "pg-connection-string@npm:2.7.0" checksum: 10c0/50a1496a1c858f9495d78a2c7a66d93ef3602e718aff2953bb5738f3ea616d7f727f32fc20513c9bed127650cd14c1ddc7b458396f4000e689d4b64c65c5c51e @@ -13022,19 +13022,19 @@ __metadata: languageName: node linkType: hard -"pg-pool@npm:^3.6.2": - version: 3.6.2 - resolution: "pg-pool@npm:3.6.2" +"pg-pool@npm:^3.7.0": + version: 3.7.0 + resolution: "pg-pool@npm:3.7.0" peerDependencies: pg: ">=8.0" - checksum: 10c0/14c524549490954b5e48457a4b808df8f619f6deeb3b395b0cd184a8f4ed65a9273fe0697ba0341a41d6745af197f1437eb1cf51fff0cbbf5b0fb3852ebe5392 + checksum: 10c0/9128673cf941f288c0cb1a74ca959a9b4f6075ef73b2cc7dece5d4db3dd7043784869e7c12bce2e69ca0df22132a419cc45c2050b4373632856fe8bae9eb94b5 languageName: node linkType: hard -"pg-protocol@npm:*, pg-protocol@npm:^1.6.1": - version: 1.6.1 - resolution: "pg-protocol@npm:1.6.1" - checksum: 10c0/7eadef4010ac0a3925c460be7332ca4098a5c6d5181725a62193fcfa800000ae6632d98d814f3989b42cf5fdc3b45e34c714a1959d29174e81e30730e140ae5f +"pg-protocol@npm:*, pg-protocol@npm:^1.7.0": + version: 1.7.0 + resolution: "pg-protocol@npm:1.7.0" + checksum: 10c0/c4af854d9b843c808231c0040fed89f2b9101006157df8da2bb2f62a7dde702de748d852228dc22df41cc7ffddfb526af3bcb34b278b581e9f76a060789186c1 languageName: node linkType: hard @@ -13067,13 +13067,13 @@ __metadata: linkType: hard "pg@npm:^8.5.0": - version: 8.12.0 - resolution: "pg@npm:8.12.0" + version: 8.13.0 + resolution: "pg@npm:8.13.0" dependencies: pg-cloudflare: "npm:^1.1.1" - pg-connection-string: "npm:^2.6.4" - pg-pool: "npm:^3.6.2" - pg-protocol: "npm:^1.6.1" + pg-connection-string: "npm:^2.7.0" + pg-pool: "npm:^3.7.0" + pg-protocol: "npm:^1.7.0" pg-types: "npm:^2.1.0" pgpass: "npm:1.x" peerDependencies: @@ -13084,7 +13084,7 @@ __metadata: peerDependenciesMeta: pg-native: optional: true - checksum: 10c0/973e49b5e7327c42fc62806efa8c824159ab7a0b676cefe6eeb51a59b6e226587911ec27697f36c18d69e58a7f4f0b76d0829364087194d13ed431ab7c9c417a + checksum: 10c0/1521189063d2293d62f3fac61e797a3096a62a69668c223827d00b83c17a320805f31f0b5316feb80f8d9eed0c6c32f95146d8aca866af05816a66fd2ba8e32a languageName: node linkType: hard From e3baa1cdda2af3d0ff7920e6249a3a9c0ccbe562 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Wed, 18 Sep 2024 09:29:57 -0400 Subject: [PATCH 15/51] Add coverage for `AccountDeletionRequest` class (#31937) --- spec/models/account_deletion_request_spec.rb | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 spec/models/account_deletion_request_spec.rb diff --git a/spec/models/account_deletion_request_spec.rb b/spec/models/account_deletion_request_spec.rb new file mode 100644 index 00000000000000..7dbfbed12a1b5a --- /dev/null +++ b/spec/models/account_deletion_request_spec.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe AccountDeletionRequest do + describe 'Associations' do + it { is_expected.to belong_to(:account).required } + end + + describe '#due_at' do + before { stub_const 'AccountDeletionRequest::DELAY_TO_DELETION', 1.day } + + it 'returns time from created at with delay added' do + account_deletion_request = Fabricate :account_deletion_request, created_at: Date.current.at_midnight + expect(account_deletion_request.due_at) + .to be_within(0.1).of(Date.tomorrow.at_midnight) + end + end +end From 8b708340356e18696e5ab9e83067ce9297e481f7 Mon Sep 17 00:00:00 2001 From: Renaud Chaput Date: Wed, 18 Sep 2024 19:39:15 +0200 Subject: [PATCH 16/51] Fix the appearance of avatars when they do not load (#31966) --- app/javascript/styles/mastodon/components.scss | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index 5016480f324ee8..495481622a73a5 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -2032,13 +2032,14 @@ body > [data-popper-placement] { display: block; position: relative; border-radius: var(--avatar-border-radius); + background-color: var(--surface-background-color); img { - display: block; width: 100%; height: 100%; object-fit: cover; border-radius: var(--avatar-border-radius); + display: inline-block; // to not show broken images } &-inline { From 29656cb9e0e5fbecdec5bc5f4e8fc2249e1b8c4e Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Wed, 18 Sep 2024 19:39:32 +0200 Subject: [PATCH 17/51] Fix sass deprecation warning (#31961) --- .../styles/mastodon-light/variables.scss | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/app/javascript/styles/mastodon-light/variables.scss b/app/javascript/styles/mastodon-light/variables.scss index 76ede2623319f8..76bdc4022e3ef0 100644 --- a/app/javascript/styles/mastodon-light/variables.scss +++ b/app/javascript/styles/mastodon-light/variables.scss @@ -1,3 +1,5 @@ +@use 'sass:color'; + // Dependent colors $black: #000000; $white: #ffffff; @@ -47,11 +49,19 @@ $account-background-color: $white !default; // Invert darkened and lightened colors @function darken($color, $amount) { - @return hsl(hue($color), saturation($color), lightness($color) + $amount); + @return hsl( + hue($color), + color.channel($color, 'saturation', $space: hsl), + color.channel($color, 'lightness', $space: hsl) + $amount + ); } @function lighten($color, $amount) { - @return hsl(hue($color), saturation($color), lightness($color) - $amount); + @return hsl( + hue($color), + color.channel($color, 'saturation', $space: hsl), + color.channel($color, 'lightness', $space: hsl) - $amount + ); } $emojis-requiring-inversion: 'chains'; From 62a39d60cedc073b3190b9ba2f2f6f3173057eb2 Mon Sep 17 00:00:00 2001 From: Claire Date: Thu, 19 Sep 2024 11:50:06 +0200 Subject: [PATCH 18/51] Fix rolling updates by moving DropEndToEndMessageTables to post-deployment migrations (#31963) --- .../20240720140205_drop_end_to_end_message_tables.rb | 0 .../20240916190140_remove_crypto_scope_values.rb | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename db/{migrate => post_migrate}/20240720140205_drop_end_to_end_message_tables.rb (100%) rename db/{migrate => post_migrate}/20240916190140_remove_crypto_scope_values.rb (100%) diff --git a/db/migrate/20240720140205_drop_end_to_end_message_tables.rb b/db/post_migrate/20240720140205_drop_end_to_end_message_tables.rb similarity index 100% rename from db/migrate/20240720140205_drop_end_to_end_message_tables.rb rename to db/post_migrate/20240720140205_drop_end_to_end_message_tables.rb diff --git a/db/migrate/20240916190140_remove_crypto_scope_values.rb b/db/post_migrate/20240916190140_remove_crypto_scope_values.rb similarity index 100% rename from db/migrate/20240916190140_remove_crypto_scope_values.rb rename to db/post_migrate/20240916190140_remove_crypto_scope_values.rb From 90db524a90ea5fee8b791b15501c7348f8a87c39 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 19 Sep 2024 09:50:58 +0000 Subject: [PATCH 19/51] Update dependency puma to v6.4.3 (#31975) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 5ed8fe78ebcfb2..b19c5bb57867a6 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -609,7 +609,7 @@ GEM psych (5.1.2) stringio public_suffix (6.0.1) - puma (6.4.2) + puma (6.4.3) nio4r (~> 2.0) pundit (2.4.0) activesupport (>= 3.0.0) From 1fce55cf5dc63d944f45b938eb3df28742ca7d77 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 19 Sep 2024 09:51:14 +0000 Subject: [PATCH 20/51] Update dependency aws-sdk-s3 to v1.163.0 (#31972) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Gemfile.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index b19c5bb57867a6..41a8b68fae8ff8 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -100,8 +100,8 @@ GEM attr_required (1.0.2) awrence (1.2.1) aws-eventstream (1.3.0) - aws-partitions (1.974.0) - aws-sdk-core (3.205.0) + aws-partitions (1.977.0) + aws-sdk-core (3.206.0) aws-eventstream (~> 1, >= 1.3.0) aws-partitions (~> 1, >= 1.651.0) aws-sigv4 (~> 1.9) @@ -109,11 +109,11 @@ GEM aws-sdk-kms (1.91.0) aws-sdk-core (~> 3, >= 3.205.0) aws-sigv4 (~> 1.5) - aws-sdk-s3 (1.162.0) + aws-sdk-s3 (1.163.0) aws-sdk-core (~> 3, >= 3.205.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.5) - aws-sigv4 (1.9.1) + aws-sigv4 (1.10.0) aws-eventstream (~> 1, >= 1.0.2) azure-storage-blob (2.0.3) azure-storage-common (~> 2.0) From b071e618e7c53a89ee332ae4c7afe9c5b4e9d176 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Thu, 19 Sep 2024 06:15:21 -0400 Subject: [PATCH 21/51] Combine API request spec assertions (#31970) --- spec/requests/api/v1/admin/accounts_spec.rb | 6 +-- spec/requests/api/v1/admin/tags_spec.rb | 12 +----- spec/requests/api/v1/apps/credentials_spec.rb | 12 +----- spec/requests/api/v1/blocks_spec.rb | 7 +--- spec/requests/api/v1/bookmarks_spec.rb | 7 +--- spec/requests/api/v1/favourites_spec.rb | 14 +------ spec/requests/api/v1/featured_tags_spec.rb | 19 +--------- spec/requests/api/v1/followed_tags_spec.rb | 7 +--- .../api/v1/instances/languages_spec.rb | 5 +-- spec/requests/api/v1/lists/accounts_spec.rb | 7 +--- spec/requests/api/v1/media_spec.rb | 7 +--- spec/requests/api/v1/mutes_spec.rb | 14 +------ .../api/v1/notifications/requests_spec.rb | 7 +--- spec/requests/api/v1/profiles_spec.rb | 38 +------------------ .../api/v1/statuses/bookmarks_spec.rb | 18 ++------- .../api/v1/statuses/favourites_spec.rb | 18 ++------- spec/requests/api/v1/statuses/pins_spec.rb | 12 +----- spec/requests/api/v1/suggestions_spec.rb | 14 +------ spec/requests/api/v1/timelines/home_spec.rb | 12 +----- spec/requests/api/v1/timelines/link_spec.rb | 6 +-- spec/requests/api/v2/filters_spec.rb | 26 ++----------- 21 files changed, 36 insertions(+), 232 deletions(-) diff --git a/spec/requests/api/v1/admin/accounts_spec.rb b/spec/requests/api/v1/admin/accounts_spec.rb index 2dc45d5eb25505..f557b61403af4c 100644 --- a/spec/requests/api/v1/admin/accounts_spec.rb +++ b/spec/requests/api/v1/admin/accounts_spec.rb @@ -193,15 +193,11 @@ it_behaves_like 'forbidden for wrong scope', 'write write:accounts read admin:read' it_behaves_like 'forbidden for wrong role', '' - it 'removes the user successfully', :aggregate_failures do + it 'removes the user successfully and logs action', :aggregate_failures do subject expect(response).to have_http_status(200) expect(User.where(id: account.user.id)).to_not exist - end - - it 'logs action', :aggregate_failures do - subject expect(latest_admin_action_log) .to be_present diff --git a/spec/requests/api/v1/admin/tags_spec.rb b/spec/requests/api/v1/admin/tags_spec.rb index 2f730cdeb8dfaf..fda9227acf5402 100644 --- a/spec/requests/api/v1/admin/tags_spec.rb +++ b/spec/requests/api/v1/admin/tags_spec.rb @@ -73,14 +73,10 @@ it_behaves_like 'forbidden for wrong scope', 'write:statuses' it_behaves_like 'forbidden for wrong role', '' - it 'returns http success' do + it 'returns http success and expected tag content' do subject expect(response).to have_http_status(200) - end - - it 'returns expected tag content' do - subject expect(response.parsed_body[:id].to_i).to eq(tag.id) expect(response.parsed_body[:name]).to eq(tag.name) @@ -107,14 +103,10 @@ it_behaves_like 'forbidden for wrong scope', 'admin:read' it_behaves_like 'forbidden for wrong role', '' - it 'returns http success' do + it 'returns http success and updates tag' do subject expect(response).to have_http_status(200) - end - - it 'returns updated tag' do - subject expect(response.parsed_body[:id].to_i).to eq(tag.id) expect(response.parsed_body[:name]).to eq(tag.name.upcase) diff --git a/spec/requests/api/v1/apps/credentials_spec.rb b/spec/requests/api/v1/apps/credentials_spec.rb index 1cd6a4178fcd8e..6fd885eb8685f6 100644 --- a/spec/requests/api/v1/apps/credentials_spec.rb +++ b/spec/requests/api/v1/apps/credentials_spec.rb @@ -47,14 +47,10 @@ let(:token) { Fabricate(:accessible_access_token, application: application) } let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } - it 'returns http success' do + it 'returns http success and returns app information' do subject expect(response).to have_http_status(200) - end - - it 'returns the app information correctly' do - subject expect(response.parsed_body).to match( a_hash_including( @@ -108,14 +104,10 @@ let(:token) { Fabricate(:accessible_access_token, application: application) } let(:headers) { { 'Authorization' => "Bearer #{token.token}-invalid" } } - it 'returns http authorization error' do + it 'returns http authorization error with json error' do subject expect(response).to have_http_status(401) - end - - it 'returns the error in the json response' do - subject expect(response.parsed_body).to match( a_hash_including( diff --git a/spec/requests/api/v1/blocks_spec.rb b/spec/requests/api/v1/blocks_spec.rb index d2f1c46a5b9839..fc028f9bac8dd4 100644 --- a/spec/requests/api/v1/blocks_spec.rb +++ b/spec/requests/api/v1/blocks_spec.rb @@ -32,15 +32,10 @@ context 'with limit param' do let(:params) { { limit: 2 } } - it 'returns only the requested number of blocked accounts' do + it 'returns only the requested number of blocked accounts and sets link header pagination' do subject expect(response.parsed_body.size).to eq(params[:limit]) - end - - it 'sets correct link header pagination' do - subject - expect(response) .to include_pagination_headers( prev: api_v1_blocks_url(limit: params[:limit], since_id: blocks.last.id), diff --git a/spec/requests/api/v1/bookmarks_spec.rb b/spec/requests/api/v1/bookmarks_spec.rb index 95a71abcac706b..5955de66524287 100644 --- a/spec/requests/api/v1/bookmarks_spec.rb +++ b/spec/requests/api/v1/bookmarks_spec.rb @@ -24,15 +24,10 @@ it_behaves_like 'forbidden for wrong scope', 'write' - it 'returns http success' do + it 'returns http success and the bookmarked statuses' do subject expect(response).to have_http_status(200) - end - - it 'returns the bookmarked statuses' do - subject - expect(response.parsed_body).to match_array(expected_response) end diff --git a/spec/requests/api/v1/favourites_spec.rb b/spec/requests/api/v1/favourites_spec.rb index 78e9d61551fab7..2f8bef1190d75b 100644 --- a/spec/requests/api/v1/favourites_spec.rb +++ b/spec/requests/api/v1/favourites_spec.rb @@ -24,30 +24,20 @@ it_behaves_like 'forbidden for wrong scope', 'write' - it 'returns http success' do + it 'returns http success and includes the favourites' do subject expect(response).to have_http_status(200) - end - - it 'returns the favourites' do - subject - expect(response.parsed_body).to match_array(expected_response) end context 'with limit param' do let(:params) { { limit: 1 } } - it 'returns only the requested number of favourites' do + it 'returns only the requested number of favourites and sets pagination headers' do subject expect(response.parsed_body.size).to eq(params[:limit]) - end - - it 'sets the correct pagination headers' do - subject - expect(response) .to include_pagination_headers( prev: api_v1_favourites_url(limit: params[:limit], min_id: favourites.last.id), diff --git a/spec/requests/api/v1/featured_tags_spec.rb b/spec/requests/api/v1/featured_tags_spec.rb index 423cc0c560ff60..f0e939d42a19ac 100644 --- a/spec/requests/api/v1/featured_tags_spec.rb +++ b/spec/requests/api/v1/featured_tags_spec.rb @@ -58,15 +58,10 @@ describe 'POST /api/v1/featured_tags' do let(:params) { { name: 'tag' } } - it 'returns http success' do + it 'returns http success and includes correct tag name' do post '/api/v1/featured_tags', headers: headers, params: params expect(response).to have_http_status(200) - end - - it 'returns the correct tag name' do - post '/api/v1/featured_tags', headers: headers, params: params - expect(response.parsed_body) .to include( name: params[:name] @@ -132,23 +127,13 @@ let!(:featured_tag) { FeaturedTag.create(name: 'tag', account: user.account) } let(:id) { featured_tag.id } - it 'returns http success' do + it 'returns http success with an empty body and deletes the featured tag', :inline_jobs do delete "/api/v1/featured_tags/#{id}", headers: headers expect(response).to have_http_status(200) - end - - it 'returns an empty body' do - delete "/api/v1/featured_tags/#{id}", headers: headers - expect(response.parsed_body).to be_empty - end - - it 'deletes the featured tag', :inline_jobs do - delete "/api/v1/featured_tags/#{id}", headers: headers featured_tag = FeaturedTag.find_by(id: id) - expect(featured_tag).to be_nil end diff --git a/spec/requests/api/v1/followed_tags_spec.rb b/spec/requests/api/v1/followed_tags_spec.rb index f7787cb76360b3..f15c0d7f44db50 100644 --- a/spec/requests/api/v1/followed_tags_spec.rb +++ b/spec/requests/api/v1/followed_tags_spec.rb @@ -28,15 +28,10 @@ it_behaves_like 'forbidden for wrong scope', 'write write:follows' - it 'returns http success' do + it 'returns http success and includes followed tags' do subject expect(response).to have_http_status(:success) - end - - it 'returns the followed tags correctly' do - subject - expect(response.parsed_body).to match_array(expected_response) end diff --git a/spec/requests/api/v1/instances/languages_spec.rb b/spec/requests/api/v1/instances/languages_spec.rb index 79ea62c5996433..3ab1ba57b85c37 100644 --- a/spec/requests/api/v1/instances/languages_spec.rb +++ b/spec/requests/api/v1/instances/languages_spec.rb @@ -8,11 +8,8 @@ get '/api/v1/instance/languages' end - it 'returns http success' do + it 'returns http success and includes supported languages' do expect(response).to have_http_status(200) - end - - it 'returns the supported languages' do expect(response.parsed_body.pluck(:code)).to match_array LanguagesHelper::SUPPORTED_LOCALES.keys.map(&:to_s) end end diff --git a/spec/requests/api/v1/lists/accounts_spec.rb b/spec/requests/api/v1/lists/accounts_spec.rb index d147b21ee7f5eb..6e5f9e4e9dd424 100644 --- a/spec/requests/api/v1/lists/accounts_spec.rb +++ b/spec/requests/api/v1/lists/accounts_spec.rb @@ -139,16 +139,11 @@ list.accounts << [bob, peter] end - it 'removes the specified account from the list', :aggregate_failures do + it 'removes the specified account from the list but keeps other accounts in the list', :aggregate_failures do subject expect(response).to have_http_status(200) expect(list.accounts).to_not include(bob) - end - - it 'does not remove any other account from the list' do - subject - expect(list.accounts).to include(peter) end diff --git a/spec/requests/api/v1/media_spec.rb b/spec/requests/api/v1/media_spec.rb index d0af334825e35c..a10bbb31efb01a 100644 --- a/spec/requests/api/v1/media_spec.rb +++ b/spec/requests/api/v1/media_spec.rb @@ -17,15 +17,10 @@ it_behaves_like 'forbidden for wrong scope', 'read' - it 'returns http success' do + it 'returns http success with media information' do subject expect(response).to have_http_status(200) - end - - it 'returns the media information' do - subject - expect(response.parsed_body).to match( a_hash_including( id: media.id.to_s, diff --git a/spec/requests/api/v1/mutes_spec.rb b/spec/requests/api/v1/mutes_spec.rb index 6402c908ffe4ce..316d455d288fe5 100644 --- a/spec/requests/api/v1/mutes_spec.rb +++ b/spec/requests/api/v1/mutes_spec.rb @@ -18,32 +18,22 @@ it_behaves_like 'forbidden for wrong scope', 'write write:mutes' - it 'returns http success' do + it 'returns http success with muted accounts' do subject expect(response).to have_http_status(200) - end - - it 'returns the muted accounts' do - subject muted_accounts = mutes.map(&:target_account) - expect(response.parsed_body.pluck(:id)).to match_array(muted_accounts.map { |account| account.id.to_s }) end context 'with limit param' do let(:params) { { limit: 1 } } - it 'returns only the requested number of muted accounts' do + it 'returns only the requested number of muted accounts with pagination headers' do subject expect(response.parsed_body.size).to eq(params[:limit]) - end - - it 'sets the correct pagination headers', :aggregate_failures do - subject - expect(response) .to include_pagination_headers( prev: api_v1_mutes_url(limit: params[:limit], since_id: mutes.last.id), diff --git a/spec/requests/api/v1/notifications/requests_spec.rb b/spec/requests/api/v1/notifications/requests_spec.rb index dc125bc7aa8ef4..030b7cfa212e50 100644 --- a/spec/requests/api/v1/notifications/requests_spec.rb +++ b/spec/requests/api/v1/notifications/requests_spec.rb @@ -39,15 +39,10 @@ it_behaves_like 'forbidden for wrong scope', 'read read:notifications' - it 'returns http success' do + it 'returns http success and creates notification permission' do subject expect(response).to have_http_status(200) - end - - it 'creates notification permission' do - subject - expect(NotificationPermission.find_by(account: notification_request.account, from_account: notification_request.from_account)).to_not be_nil end diff --git a/spec/requests/api/v1/profiles_spec.rb b/spec/requests/api/v1/profiles_spec.rb index 26a9b848e56e50..9616f4155974f3 100644 --- a/spec/requests/api/v1/profiles_spec.rb +++ b/spec/requests/api/v1/profiles_spec.rb @@ -28,31 +28,14 @@ it_behaves_like 'forbidden for wrong scope', 'read' end - it 'returns http success' do + it 'returns http success and deletes the avatar, preserves the header, queues up distribution' do delete '/api/v1/profile/avatar', headers: headers expect(response).to have_http_status(200) - end - - it 'deletes the avatar' do - delete '/api/v1/profile/avatar', headers: headers account.reload - expect(account.avatar).to_not exist - end - - it 'does not delete the header' do - delete '/api/v1/profile/avatar', headers: headers - - account.reload - expect(account.header).to exist - end - - it 'queues up an account update distribution' do - delete '/api/v1/profile/avatar', headers: headers - expect(ActivityPub::UpdateDistributionWorker).to have_received(:perform_async).with(account.id) end end @@ -66,31 +49,14 @@ it_behaves_like 'forbidden for wrong scope', 'read' end - it 'returns http success' do + it 'returns http success, preserves the avatar, deletes the header, queues up distribution' do delete '/api/v1/profile/header', headers: headers expect(response).to have_http_status(200) - end - - it 'does not delete the avatar' do - delete '/api/v1/profile/header', headers: headers account.reload - expect(account.avatar).to exist - end - - it 'deletes the header' do - delete '/api/v1/profile/header', headers: headers - - account.reload - expect(account.header).to_not exist - end - - it 'queues up an account update distribution' do - delete '/api/v1/profile/header', headers: headers - expect(ActivityPub::UpdateDistributionWorker).to have_received(:perform_async).with(account.id) end end diff --git a/spec/requests/api/v1/statuses/bookmarks_spec.rb b/spec/requests/api/v1/statuses/bookmarks_spec.rb index f1bcfda0ff638b..6401a4370db7c4 100644 --- a/spec/requests/api/v1/statuses/bookmarks_spec.rb +++ b/spec/requests/api/v1/statuses/bookmarks_spec.rb @@ -18,15 +18,11 @@ it_behaves_like 'forbidden for wrong scope', 'read' context 'with public status' do - it 'bookmarks the status successfully', :aggregate_failures do + it 'bookmarks the status successfully and includes updated json', :aggregate_failures do subject expect(response).to have_http_status(200) expect(user.account.bookmarked?(status)).to be true - end - - it 'returns json with updated attributes' do - subject expect(response.parsed_body).to match( a_hash_including(id: status.id.to_s, bookmarked: true) @@ -93,15 +89,11 @@ Bookmark.find_or_create_by!(account: user.account, status: status) end - it 'unbookmarks the status successfully', :aggregate_failures do + it 'unbookmarks the status successfully and includes updated json', :aggregate_failures do subject expect(response).to have_http_status(200) expect(user.account.bookmarked?(status)).to be false - end - - it 'returns json with updated attributes' do - subject expect(response.parsed_body).to match( a_hash_including(id: status.id.to_s, bookmarked: false) @@ -117,15 +109,11 @@ status.account.block!(user.account) end - it 'unbookmarks the status successfully', :aggregate_failures do + it 'unbookmarks the status successfully and includes updated json', :aggregate_failures do subject expect(response).to have_http_status(200) expect(user.account.bookmarked?(status)).to be false - end - - it 'returns json with updated attributes' do - subject expect(response.parsed_body).to match( a_hash_including(id: status.id.to_s, bookmarked: false) diff --git a/spec/requests/api/v1/statuses/favourites_spec.rb b/spec/requests/api/v1/statuses/favourites_spec.rb index f9f0ff62990cfa..c3acf0413e7f58 100644 --- a/spec/requests/api/v1/statuses/favourites_spec.rb +++ b/spec/requests/api/v1/statuses/favourites_spec.rb @@ -18,15 +18,11 @@ it_behaves_like 'forbidden for wrong scope', 'read read:favourites' context 'with public status' do - it 'favourites the status successfully', :aggregate_failures do + it 'favourites the status successfully and includes updated json', :aggregate_failures do subject expect(response).to have_http_status(200) expect(user.account.favourited?(status)).to be true - end - - it 'returns json with updated attributes' do - subject expect(response.parsed_body).to match( a_hash_including(id: status.id.to_s, favourites_count: 1, favourited: true) @@ -84,16 +80,12 @@ FavouriteService.new.call(user.account, status) end - it 'unfavourites the status successfully', :aggregate_failures do + it 'unfavourites the status successfully and includes updated json', :aggregate_failures do subject expect(response).to have_http_status(200) expect(user.account.favourited?(status)).to be false - end - - it 'returns json with updated attributes' do - subject expect(response.parsed_body).to match( a_hash_including(id: status.id.to_s, favourites_count: 0, favourited: false) @@ -107,16 +99,12 @@ status.account.block!(user.account) end - it 'unfavourites the status successfully', :aggregate_failures do + it 'unfavourites the status successfully and includes updated json', :aggregate_failures do subject expect(response).to have_http_status(200) expect(user.account.favourited?(status)).to be false - end - - it 'returns json with updated attributes' do - subject expect(response.parsed_body).to match( a_hash_including(id: status.id.to_s, favourites_count: 0, favourited: false) diff --git a/spec/requests/api/v1/statuses/pins_spec.rb b/spec/requests/api/v1/statuses/pins_spec.rb index 56e60c6d368c2c..409c50e7c211e6 100644 --- a/spec/requests/api/v1/statuses/pins_spec.rb +++ b/spec/requests/api/v1/statuses/pins_spec.rb @@ -18,15 +18,11 @@ it_behaves_like 'forbidden for wrong scope', 'read read:accounts' context 'when the status is public' do - it 'pins the status successfully', :aggregate_failures do + it 'pins the status successfully and returns updated json', :aggregate_failures do subject expect(response).to have_http_status(200) expect(user.account.pinned?(status)).to be true - end - - it 'return json with updated attributes' do - subject expect(response.parsed_body).to match( a_hash_including(id: status.id.to_s, pinned: true) @@ -86,15 +82,11 @@ Fabricate(:status_pin, status: status, account: user.account) end - it 'unpins the status successfully', :aggregate_failures do + it 'unpins the status successfully and includes updated json', :aggregate_failures do subject expect(response).to have_http_status(200) expect(user.account.pinned?(status)).to be false - end - - it 'return json with updated attributes' do - subject expect(response.parsed_body).to match( a_hash_including(id: status.id.to_s, pinned: false) diff --git a/spec/requests/api/v1/suggestions_spec.rb b/spec/requests/api/v1/suggestions_spec.rb index 8267bb92a047db..b971f8812961f4 100644 --- a/spec/requests/api/v1/suggestions_spec.rb +++ b/spec/requests/api/v1/suggestions_spec.rb @@ -23,15 +23,10 @@ it_behaves_like 'forbidden for wrong scope', 'write' - it 'returns http success' do + it 'returns http success with accounts' do subject expect(response).to have_http_status(200) - end - - it 'returns accounts' do - subject - expect(response.parsed_body) .to contain_exactly(include(id: bob.id.to_s), include(id: jeff.id.to_s)) end @@ -72,15 +67,10 @@ it_behaves_like 'forbidden for wrong scope', 'read' - it 'returns http success' do + it 'returns http success and removes suggestion' do subject expect(response).to have_http_status(200) - end - - it 'removes the specified suggestion' do - subject - expect(FollowRecommendationMute.exists?(account: user.account, target_account: jeff)).to be true end diff --git a/spec/requests/api/v1/timelines/home_spec.rb b/spec/requests/api/v1/timelines/home_spec.rb index afad2988ca2a10..19a6f3adbc1521 100644 --- a/spec/requests/api/v1/timelines/home_spec.rb +++ b/spec/requests/api/v1/timelines/home_spec.rb @@ -31,14 +31,10 @@ PostStatusService.new.call(ana, text: 'New toot from ana.') end - it 'returns http success' do + it 'returns http success and statuses of followed users' do subject expect(response).to have_http_status(200) - end - - it 'returns the statuses of followed users' do - subject expect(response.parsed_body.pluck(:id)).to match_array(home_statuses.map { |status| status.id.to_s }) end @@ -46,14 +42,10 @@ context 'with limit param' do let(:params) { { limit: 1 } } - it 'returns only the requested number of statuses' do + it 'returns only the requested number of statuses with pagination headers', :aggregate_failures do subject expect(response.parsed_body.size).to eq(params[:limit]) - end - - it 'sets the correct pagination headers', :aggregate_failures do - subject expect(response) .to include_pagination_headers( diff --git a/spec/requests/api/v1/timelines/link_spec.rb b/spec/requests/api/v1/timelines/link_spec.rb index 8999364703f735..e1d421fb7af04b 100644 --- a/spec/requests/api/v1/timelines/link_spec.rb +++ b/spec/requests/api/v1/timelines/link_spec.rb @@ -123,15 +123,11 @@ context 'with limit param' do let(:params) { { limit: 1, url: url } } - it 'returns only the requested number of statuses', :aggregate_failures do + it 'returns only the requested number of statuses with pagination headers', :aggregate_failures do subject expect(response).to have_http_status(200) expect(response.parsed_body.size).to eq(params[:limit]) - end - - it 'sets the correct pagination headers', :aggregate_failures do - subject expect(response) .to include_pagination_headers( diff --git a/spec/requests/api/v2/filters_spec.rb b/spec/requests/api/v2/filters_spec.rb index 850c773df3d7a3..ad8b55973e2bc2 100644 --- a/spec/requests/api/v2/filters_spec.rb +++ b/spec/requests/api/v2/filters_spec.rb @@ -49,14 +49,10 @@ context 'with valid params' do let(:params) { { title: 'magic', context: %w(home), filter_action: 'hide', keywords_attributes: [keyword: 'magic'] } } - it 'returns http success' do + it 'returns http success with a filter with keywords in json and creates a filter', :aggregate_failures do subject expect(response).to have_http_status(200) - end - - it 'returns a filter with keywords', :aggregate_failures do - subject expect(response.parsed_body) .to include( @@ -67,10 +63,6 @@ include(keyword: 'magic', whole_word: true) ) ) - end - - it 'creates a filter', :aggregate_failures do - subject filter = user.account.custom_filters.first @@ -189,20 +181,12 @@ allow(redis).to receive_messages(publish: nil) end - it 'returns http success' do + it 'returns http success and updates keyword and sends a filters_changed event' do subject expect(response).to have_http_status(200) - end - - it 'updates the keyword' do - subject expect(keyword.reload.keyword).to eq 'updated' - end - - it 'sends exactly one filters_changed event' do - subject expect(redis).to have_received(:publish).with("timeline:#{user.account.id}", Oj.dump(event: :filters_changed)).once end @@ -229,14 +213,10 @@ it_behaves_like 'forbidden for wrong scope', 'read read:filters' it_behaves_like 'unauthorized for invalid token' - it 'returns http success' do + it 'returns http success and removes the filter' do subject expect(response).to have_http_status(200) - end - - it 'removes the filter' do - subject expect { filter.reload }.to raise_error ActiveRecord::RecordNotFound end From 5d573c976e8f02e67014154232d2c7f936d8f717 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Thu, 19 Sep 2024 06:23:58 -0400 Subject: [PATCH 22/51] Remove unused E2EE-related methods (#31964) --- app/javascript/mastodon/stream.js | 1 - app/presenters/activitypub/activity_presenter.rb | 11 ----------- 2 files changed, 12 deletions(-) diff --git a/app/javascript/mastodon/stream.js b/app/javascript/mastodon/stream.js index 40d69136a84808..59b2fd75828f4a 100644 --- a/app/javascript/mastodon/stream.js +++ b/app/javascript/mastodon/stream.js @@ -209,7 +209,6 @@ const KNOWN_EVENT_TYPES = [ 'notification', 'conversation', 'filters_changed', - 'encrypted_message', 'announcement', 'announcement.delete', 'announcement.reaction', diff --git a/app/presenters/activitypub/activity_presenter.rb b/app/presenters/activitypub/activity_presenter.rb index 38e8527e8e5adc..994cbeaf48fd0c 100644 --- a/app/presenters/activitypub/activity_presenter.rb +++ b/app/presenters/activitypub/activity_presenter.rb @@ -26,16 +26,5 @@ def from_status(status, allow_inlining: true) end end end - - def from_encrypted_message(encrypted_message) - new.tap do |presenter| - presenter.id = ActivityPub::TagManager.instance.generate_uri_for(nil) - presenter.type = 'Create' - presenter.actor = ActivityPub::TagManager.instance.uri_for(encrypted_message.source_account) - presenter.published = Time.now.utc - presenter.to = ActivityPub::TagManager.instance.uri_for(encrypted_message.target_account) - presenter.virtual_object = encrypted_message - end - end end end From efdc17513d4f929259f5d92b9ba2718e280295e3 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 19 Sep 2024 10:34:19 +0000 Subject: [PATCH 23/51] New Crowdin Translations (automated) (#31974) Co-authored-by: GitHub Actions --- app/javascript/mastodon/locales/ko.json | 3 +++ app/javascript/mastodon/locales/lv.json | 1 + config/locales/activerecord.ko.yml | 6 ++++++ config/locales/ko.yml | 8 ++++++++ config/locales/lv.yml | 2 +- config/locales/simple_form.ko.yml | 2 ++ 6 files changed, 21 insertions(+), 1 deletion(-) diff --git a/app/javascript/mastodon/locales/ko.json b/app/javascript/mastodon/locales/ko.json index 6795bc647c58cb..cf2082e105d42f 100644 --- a/app/javascript/mastodon/locales/ko.json +++ b/app/javascript/mastodon/locales/ko.json @@ -778,6 +778,7 @@ "status.bookmark": "북마크", "status.cancel_reblog_private": "부스트 취소", "status.cannot_reblog": "이 게시물은 부스트 할 수 없습니다", + "status.continued_thread": "이어지는 글타래", "status.copy": "게시물 링크 복사", "status.delete": "삭제", "status.detailed_status": "대화 자세히 보기", @@ -786,6 +787,7 @@ "status.edit": "수정", "status.edited": "{date}에 마지막으로 편집됨", "status.edited_x_times": "{count, plural, other {{count}}} 번 수정됨", + "status.embed": "임베드 코드 받기", "status.favourite": "좋아요", "status.favourites": "{count, plural, other {좋아요}}", "status.filter": "이 게시물을 필터", @@ -810,6 +812,7 @@ "status.reblogs.empty": "아직 아무도 이 게시물을 부스트하지 않았습니다. 부스트 한 사람들이 여기에 표시 됩니다.", "status.redraft": "지우고 다시 쓰기", "status.remove_bookmark": "북마크 삭제", + "status.replied_in_thread": "글타래에 답장", "status.replied_to": "{name} 님에게", "status.reply": "답장", "status.replyAll": "글타래에 답장", diff --git a/app/javascript/mastodon/locales/lv.json b/app/javascript/mastodon/locales/lv.json index b53f65ab431236..53b3c0fcaa22a9 100644 --- a/app/javascript/mastodon/locales/lv.json +++ b/app/javascript/mastodon/locales/lv.json @@ -409,6 +409,7 @@ "lists.subheading": "Tavi saraksti", "load_pending": "{count, plural, one {# jauna lieta} other {# jaunas lietas}}", "loading_indicator.label": "Ielādē…", + "media_gallery.hide": "Paslēpt", "moved_to_account_banner.text": "Tavs konts {disabledAccount} pašlaik ir atspējots, jo Tu pārcēlies uz kontu {movedToAccount}.", "mute_modal.hide_from_notifications": "Paslēpt paziņojumos", "mute_modal.hide_options": "Paslēpt iespējas", diff --git a/config/locales/activerecord.ko.yml b/config/locales/activerecord.ko.yml index 294d614bca63c6..6d437b72b0dfde 100644 --- a/config/locales/activerecord.ko.yml +++ b/config/locales/activerecord.ko.yml @@ -15,6 +15,12 @@ ko: user/invite_request: text: 이유 errors: + attributes: + domain: + invalid: 올바른 도메인 네임이 아닙니다 + messages: + invalid_domain_on_line: "%{value}는 올바른 도메인 네임이 아닙니다" + too_many_lines: "%{limit}줄 제한을 초과합니다" models: account: attributes: diff --git a/config/locales/ko.yml b/config/locales/ko.yml index 3217a8e49a3842..216e4687620368 100644 --- a/config/locales/ko.yml +++ b/config/locales/ko.yml @@ -22,6 +22,7 @@ ko: admin: account_actions: action: 조치 취하기 + already_silenced: 이 계정은 이미 제한되었습니다. already_suspended: 이 계정은 이미 정지되었습니다. title: "%{acct} 계정에 중재 취하기" account_moderation_notes: @@ -1143,6 +1144,12 @@ ko: view_strikes: 내 계정에 대한 과거 중재 기록 보기 too_fast: 너무 빠르게 양식이 제출되었습니다, 다시 시도하세요. use_security_key: 보안 키 사용 + author_attribution: + example_title: 예시 텍스트 + hint_html: 링크가 마스토돈에 공유될 때 내가 어떻게 표시될 지를 제어합니다. + more_from_html: "%{name}의 게시물 더 보기" + s_blog: "%{name}의 블로그" + title: 작성자 기여 challenge: confirm: 계속 hint_html: "팁: 한 시간 동안 다시 암호를 묻지 않을 것입니다." @@ -1906,6 +1913,7 @@ ko: instructions_html: 웹사이트에 아래 코드를 복사해 붙여 넣으세요. 그리고 "프로필 수정" 탭에서 그 웹사이트 주소를 프로필의 추가 필드 중 하나에 넣고 변경사항을 저장하세요. verification: 검증 verified_links: 인증된 링크들 + website_verification: 웹사이트 인증 webauthn_credentials: add: 보안 키 추가 create: diff --git a/config/locales/lv.yml b/config/locales/lv.yml index be6555a3862657..4aeec5ceca73dc 100644 --- a/config/locales/lv.yml +++ b/config/locales/lv.yml @@ -1114,7 +1114,7 @@ lv: new_confirmation_instructions_sent: Pēc dažām minūtēm saņemsi jaunu e-pasta ziņojumu ar apstiprinājuma saiti. title: Pārbaudi savu iesūtni sign_in: - preamble_html: Jāpiesakās ar saviem %{domain} piekļuves datiem. Ja Tavs konts tiek mitināts citā serverī, Tu nevarēsi šeit pieteikties. + preamble_html: Jāpiesakās ar saviem %{domain} piekļuves datiem. Ja konts tiek mitināts citā serverī, šeit nevarēs pieteikties. title: Pierakstīties %{domain} sign_up: manual_review: Reģistrācijas domēnā %{domain} manuāli pārbauda mūsu moderatori. Lai palīdzētu mums apstrādāt tavu reģistrāciju, uzraksti mazliet par sevi un to, kāpēc vēlies kontu %{domain}. diff --git a/config/locales/simple_form.ko.yml b/config/locales/simple_form.ko.yml index fee07fa5e0ff66..a649b4ec5a90df 100644 --- a/config/locales/simple_form.ko.yml +++ b/config/locales/simple_form.ko.yml @@ -3,6 +3,7 @@ ko: simple_form: hints: account: + attribution_domains_as_text: 가짜 기여로부터 보호합니다. discoverable: 내 공개 게시물과 프로필이 마스토돈의 다양한 추천 기능에 나타날 수 있고 프로필이 다른 사용자에게 제안될 수 있습니다 display_name: 진짜 이름 또는 재미난 이름. fields: 홈페이지, 호칭, 나이, 뭐든지 적고 싶은 것들. @@ -143,6 +144,7 @@ ko: url: 이벤트가 어디로 전송될 지 labels: account: + attribution_domains_as_text: 특정 웹사이트만 허용하기 discoverable: 발견하기 알고리즘에 프로필과 게시물을 추천하기 fields: name: 라벨 From ef4d6ab98875891716fa2b9ce22ed34afc58a53f Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Thu, 19 Sep 2024 12:52:46 +0200 Subject: [PATCH 24/51] Fix browser glitch caused by two overlapping scroll animations in web UI (#31960) --- .../features/ui/components/columns_area.jsx | 32 +------------------ app/javascript/mastodon/scroll.ts | 27 ++++++++++------ app/javascript/mastodon/test_helpers.tsx | 8 +++++ 3 files changed, 26 insertions(+), 41 deletions(-) diff --git a/app/javascript/mastodon/features/ui/components/columns_area.jsx b/app/javascript/mastodon/features/ui/components/columns_area.jsx index 5a2ea8c2c88216..ff76d5bcb296d3 100644 --- a/app/javascript/mastodon/features/ui/components/columns_area.jsx +++ b/app/javascript/mastodon/features/ui/components/columns_area.jsx @@ -4,8 +4,6 @@ import { Children, cloneElement, useCallback } from 'react'; import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; -import { supportsPassiveEvents } from 'detect-passive-events'; - import { scrollRight } from '../../../scroll'; import BundleContainer from '../containers/bundle_container'; import { @@ -71,10 +69,6 @@ export default class ColumnsArea extends ImmutablePureComponent { }; componentDidMount() { - if (!this.props.singleColumn) { - this.node.addEventListener('wheel', this.handleWheel, supportsPassiveEvents ? { passive: true } : false); - } - if (this.mediaQuery) { if (this.mediaQuery.addEventListener) { this.mediaQuery.addEventListener('change', this.handleLayoutChange); @@ -87,23 +81,7 @@ export default class ColumnsArea extends ImmutablePureComponent { this.isRtlLayout = document.getElementsByTagName('body')[0].classList.contains('rtl'); } - UNSAFE_componentWillUpdate(nextProps) { - if (this.props.singleColumn !== nextProps.singleColumn && nextProps.singleColumn) { - this.node.removeEventListener('wheel', this.handleWheel); - } - } - - componentDidUpdate(prevProps) { - if (this.props.singleColumn !== prevProps.singleColumn && !this.props.singleColumn) { - this.node.addEventListener('wheel', this.handleWheel, supportsPassiveEvents ? { passive: true } : false); - } - } - componentWillUnmount () { - if (!this.props.singleColumn) { - this.node.removeEventListener('wheel', this.handleWheel); - } - if (this.mediaQuery) { if (this.mediaQuery.removeEventListener) { this.mediaQuery.removeEventListener('change', this.handleLayoutChange); @@ -116,7 +94,7 @@ export default class ColumnsArea extends ImmutablePureComponent { handleChildrenContentChange() { if (!this.props.singleColumn) { const modifier = this.isRtlLayout ? -1 : 1; - this._interruptScrollAnimation = scrollRight(this.node, (this.node.scrollWidth - window.innerWidth) * modifier); + scrollRight(this.node, (this.node.scrollWidth - window.innerWidth) * modifier); } } @@ -124,14 +102,6 @@ export default class ColumnsArea extends ImmutablePureComponent { this.setState({ renderComposePanel: !e.matches }); }; - handleWheel = () => { - if (typeof this._interruptScrollAnimation !== 'function') { - return; - } - - this._interruptScrollAnimation(); - }; - setRef = (node) => { this.node = node; }; diff --git a/app/javascript/mastodon/scroll.ts b/app/javascript/mastodon/scroll.ts index 35e13a4527d1d3..0756edb4cefe2b 100644 --- a/app/javascript/mastodon/scroll.ts +++ b/app/javascript/mastodon/scroll.ts @@ -38,13 +38,20 @@ const scroll = ( const isScrollBehaviorSupported = 'scrollBehavior' in document.documentElement.style; -export const scrollRight = (node: Element, position: number) => { - if (isScrollBehaviorSupported) - node.scrollTo({ left: position, behavior: 'smooth' }); - else scroll(node, 'scrollLeft', position); -}; - -export const scrollTop = (node: Element) => { - if (isScrollBehaviorSupported) node.scrollTo({ top: 0, behavior: 'smooth' }); - else scroll(node, 'scrollTop', 0); -}; +export const scrollRight = (node: Element, position: number) => + requestIdleCallback(() => { + if (isScrollBehaviorSupported) { + node.scrollTo({ left: position, behavior: 'smooth' }); + } else { + scroll(node, 'scrollLeft', position); + } + }); + +export const scrollTop = (node: Element) => + requestIdleCallback(() => { + if (isScrollBehaviorSupported) { + node.scrollTo({ top: 0, behavior: 'smooth' }); + } else { + scroll(node, 'scrollTop', 0); + } + }); diff --git a/app/javascript/mastodon/test_helpers.tsx b/app/javascript/mastodon/test_helpers.tsx index f4050907306e85..8a6f5a33776e79 100644 --- a/app/javascript/mastodon/test_helpers.tsx +++ b/app/javascript/mastodon/test_helpers.tsx @@ -8,6 +8,14 @@ import { render as rtlRender } from '@testing-library/react'; import { IdentityContext } from './identity_context'; +beforeEach(() => { + global.requestIdleCallback = jest + .fn() + .mockImplementation((fn: () => void) => { + fn(); + }); +}); + function render( ui: React.ReactElement, { From 6801afa12f3611c86aeec78a2d43ffa8323ecec6 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 19 Sep 2024 10:56:09 +0000 Subject: [PATCH 25/51] Update dependency devise-two-factor to v6 [SECURITY] (#31957) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: David Roetzel --- Gemfile.lock | 2 +- spec/models/user_spec.rb | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 41a8b68fae8ff8..79e542014c1ef2 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -197,7 +197,7 @@ GEM railties (>= 4.1.0) responders warden (~> 1.2.3) - devise-two-factor (5.1.0) + devise-two-factor (6.0.0) activesupport (~> 7.0) devise (~> 4.0) railties (~> 7.0) diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index fcff4c0d3bd435..972453cd690835 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -4,6 +4,8 @@ require 'devise_two_factor/spec_helpers' RSpec.describe User do + subject { described_class.new(account: account) } + let(:password) { 'abcd1234' } let(:account) { Fabricate(:account, username: 'alice') } From 2946a9286b1b4a5323f9f7ea6e7b29b6ca5d309d Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Thu, 19 Sep 2024 09:38:32 -0400 Subject: [PATCH 26/51] Use `headers` shorthand in mailers (#31956) --- app/mailers/admin_mailer.rb | 8 +++++--- app/mailers/application_mailer.rb | 8 +++++--- app/mailers/notification_mailer.rb | 28 ++++++++++++++++------------ 3 files changed, 26 insertions(+), 18 deletions(-) diff --git a/app/mailers/admin_mailer.rb b/app/mailers/admin_mailer.rb index 8dd7b6e59fb055..72a2c2e64e144a 100644 --- a/app/mailers/admin_mailer.rb +++ b/app/mailers/admin_mailer.rb @@ -56,9 +56,11 @@ def new_software_updates def new_critical_software_updates @software_updates = SoftwareUpdate.where(urgent: true).to_a.sort_by(&:gem_version) - headers['Priority'] = 'urgent' - headers['X-Priority'] = '1' - headers['Importance'] = 'high' + headers( + 'Importance' => 'high', + 'Priority' => 'urgent', + 'X-Priority' => '1' + ) locale_for_account(@me) do mail subject: default_i18n_subject(instance: @instance) diff --git a/app/mailers/application_mailer.rb b/app/mailers/application_mailer.rb index 35f0b5fee18a9a..9a209aa77b8f55 100644 --- a/app/mailers/application_mailer.rb +++ b/app/mailers/application_mailer.rb @@ -16,8 +16,10 @@ def locale_for_account(account, &block) end def set_autoreply_headers! - headers['Precedence'] = 'list' - headers['X-Auto-Response-Suppress'] = 'All' - headers['Auto-Submitted'] = 'auto-generated' + headers( + 'Auto-Submitted' => 'auto-generated', + 'Precedence' => 'list', + 'X-Auto-Response-Suppress' => 'All' + ) end end diff --git a/app/mailers/notification_mailer.rb b/app/mailers/notification_mailer.rb index 4eb38ec340620f..6b21b4bedde1a4 100644 --- a/app/mailers/notification_mailer.rb +++ b/app/mailers/notification_mailer.rb @@ -6,7 +6,10 @@ class NotificationMailer < ApplicationMailer :routing before_action :process_params - before_action :set_status, only: [:mention, :favourite, :reblog] + with_options only: %i(mention favourite reblog) do + before_action :set_status + after_action :thread_by_conversation! + end before_action :set_account, only: [:follow, :favourite, :reblog, :follow_request] after_action :set_list_headers! @@ -18,7 +21,6 @@ def mention return unless @user.functional? && @status.present? locale_for_account(@me) do - thread_by_conversation(@status.conversation) mail subject: default_i18n_subject(name: @status.account.acct) end end @@ -35,7 +37,6 @@ def favourite return unless @user.functional? && @status.present? locale_for_account(@me) do - thread_by_conversation(@status.conversation) mail subject: default_i18n_subject(name: @account.acct) end end @@ -44,7 +45,6 @@ def reblog return unless @user.functional? && @status.present? locale_for_account(@me) do - thread_by_conversation(@status.conversation) mail subject: default_i18n_subject(name: @account.acct) end end @@ -76,17 +76,21 @@ def set_account end def set_list_headers! - headers['List-ID'] = "<#{@type}.#{@me.username}.#{Rails.configuration.x.local_domain}>" - headers['List-Unsubscribe'] = "<#{@unsubscribe_url}>" - headers['List-Unsubscribe-Post'] = 'List-Unsubscribe=One-Click' + headers( + 'List-ID' => "<#{@type}.#{@me.username}.#{Rails.configuration.x.local_domain}>", + 'List-Unsubscribe-Post' => 'List-Unsubscribe=One-Click', + 'List-Unsubscribe' => "<#{@unsubscribe_url}>" + ) end - def thread_by_conversation(conversation) - return if conversation.nil? + def thread_by_conversation! + return if @status.conversation.nil? - msg_id = "" + conversation_message_id = "" - headers['In-Reply-To'] = msg_id - headers['References'] = msg_id + headers( + 'In-Reply-To' => conversation_message_id, + 'References' => conversation_message_id + ) end end From 5a8f2fe31d15a2a07384d118b5cbcd10ad714be4 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Thu, 19 Sep 2024 09:43:40 -0400 Subject: [PATCH 27/51] Convert `settings/exports` controller spec to system/request specs (#31965) --- .../settings/exports_controller_spec.rb | 47 ------------------- spec/requests/settings/exports_spec.rb | 25 ++++++++++ spec/system/settings/exports_spec.rb | 40 ++++++++++++++++ 3 files changed, 65 insertions(+), 47 deletions(-) delete mode 100644 spec/controllers/settings/exports_controller_spec.rb create mode 100644 spec/requests/settings/exports_spec.rb create mode 100644 spec/system/settings/exports_spec.rb diff --git a/spec/controllers/settings/exports_controller_spec.rb b/spec/controllers/settings/exports_controller_spec.rb deleted file mode 100644 index 1eafabc7e50129..00000000000000 --- a/spec/controllers/settings/exports_controller_spec.rb +++ /dev/null @@ -1,47 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -RSpec.describe Settings::ExportsController do - render_views - - describe 'GET #show' do - context 'when signed in' do - let(:user) { Fabricate(:user) } - - before do - sign_in user, scope: :user - get :show - end - - it 'returns http success with private cache control headers', :aggregate_failures do - expect(response).to have_http_status(200) - expect(response.headers['Cache-Control']).to include('private, no-store') - end - end - - context 'when not signed in' do - it 'redirects' do - get :show - expect(response).to redirect_to '/auth/sign_in' - end - end - end - - describe 'POST #create' do - before do - sign_in Fabricate(:user), scope: :user - end - - it 'redirects to settings_export_path' do - post :create - expect(response).to redirect_to(settings_export_path) - end - - it 'queues BackupWorker job by 1' do - expect do - post :create - end.to change(BackupWorker.jobs, :size).by(1) - end - end -end diff --git a/spec/requests/settings/exports_spec.rb b/spec/requests/settings/exports_spec.rb new file mode 100644 index 00000000000000..db823ac770fe2f --- /dev/null +++ b/spec/requests/settings/exports_spec.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'Settings / Exports' do + context 'when not signed in' do + describe 'GET /settings/export' do + it 'redirects to sign in page' do + get settings_export_path + + expect(response) + .to redirect_to new_user_session_path + end + end + + describe 'POST /settings/export' do + it 'redirects to sign in page' do + post settings_export_path + + expect(response) + .to redirect_to new_user_session_path + end + end + end +end diff --git a/spec/system/settings/exports_spec.rb b/spec/system/settings/exports_spec.rb new file mode 100644 index 00000000000000..2cc2961a0bcee1 --- /dev/null +++ b/spec/system/settings/exports_spec.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'Export page' do + let(:user) { Fabricate :user } + + before { sign_in user } + + describe 'Viewing the export page' do + context 'when signed in' do + it 'shows the export page', :aggregate_failures do + visit settings_export_path + + expect(page) + .to have_content(takeout_summary) + .and have_private_cache_control + end + end + end + + describe 'Creating a new archive' do + it 'queues a worker and redirects' do + visit settings_export_path + + expect { request_archive } + .to change(BackupWorker.jobs, :size).by(1) + expect(page) + .to have_content(takeout_summary) + end + + def request_archive + click_on I18n.t('exports.archive_takeout.request') + end + end + + def takeout_summary + I18n.t('settings.export') + end +end From 57a38f071b0ffd74b813516e24c9e86a23c4d467 Mon Sep 17 00:00:00 2001 From: Claire Date: Thu, 19 Sep 2024 16:58:33 +0200 Subject: [PATCH 28/51] Fix custom `history.push` and `history.replace` building bogus location if path is omitted (#31980) --- app/javascript/mastodon/components/router.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/javascript/mastodon/components/router.tsx b/app/javascript/mastodon/components/router.tsx index 33fb60abb74bfd..558d0307e75002 100644 --- a/app/javascript/mastodon/components/router.tsx +++ b/app/javascript/mastodon/components/router.tsx @@ -51,7 +51,8 @@ function normalizePath( if ( layoutFromWindow() === 'multi-column' && - !location.pathname?.startsWith('/deck') + location.pathname && + !location.pathname.startsWith('/deck') ) { location.pathname = `/deck${location.pathname}`; } From ae03e4ffc6a25c8a3e3c61701180fdc1ea194141 Mon Sep 17 00:00:00 2001 From: Renaud Chaput Date: Thu, 19 Sep 2024 17:34:08 +0200 Subject: [PATCH 29/51] Update directory page options to use URL params (#31977) --- app/javascript/hooks/useSearchParam.ts | 31 ++++++++++++++++ .../mastodon/features/directory/index.tsx | 35 +++++++++++-------- 2 files changed, 51 insertions(+), 15 deletions(-) create mode 100644 app/javascript/hooks/useSearchParam.ts diff --git a/app/javascript/hooks/useSearchParam.ts b/app/javascript/hooks/useSearchParam.ts new file mode 100644 index 00000000000000..2df8c0b3a9e79f --- /dev/null +++ b/app/javascript/hooks/useSearchParam.ts @@ -0,0 +1,31 @@ +import { useMemo, useCallback } from 'react'; + +import { useLocation, useHistory } from 'react-router'; + +export function useSearchParams() { + const { search } = useLocation(); + + return useMemo(() => new URLSearchParams(search), [search]); +} + +export function useSearchParam(name: string, defaultValue?: string) { + const searchParams = useSearchParams(); + const history = useHistory(); + + const value = searchParams.get(name) ?? defaultValue; + + const setValue = useCallback( + (value: string | null) => { + if (value === null) { + searchParams.delete(name); + } else { + searchParams.set(name, value); + } + + history.push({ search: searchParams.toString() }); + }, + [history, name, searchParams], + ); + + return [value, setValue] as const; +} diff --git a/app/javascript/mastodon/features/directory/index.tsx b/app/javascript/mastodon/features/directory/index.tsx index 51d283a482d9c8..d0e57600bb8933 100644 --- a/app/javascript/mastodon/features/directory/index.tsx +++ b/app/javascript/mastodon/features/directory/index.tsx @@ -1,5 +1,5 @@ import type { ChangeEventHandler } from 'react'; -import { useCallback, useEffect, useRef, useState } from 'react'; +import { useCallback, useEffect, useRef } from 'react'; import { defineMessages, useIntl } from 'react-intl'; @@ -23,6 +23,8 @@ import { RadioButton } from 'mastodon/components/radio_button'; import ScrollContainer from 'mastodon/containers/scroll_container'; import { useAppDispatch, useAppSelector } from 'mastodon/store'; +import { useSearchParam } from '../../../hooks/useSearchParam'; + import { AccountCard } from './components/account_card'; const messages = defineMessages({ @@ -47,18 +49,19 @@ export const Directory: React.FC<{ const intl = useIntl(); const dispatch = useAppDispatch(); - const [state, setState] = useState<{ - order: string | null; - local: boolean | null; - }>({ - order: null, - local: null, - }); - const column = useRef(null); - const order = state.order ?? params?.order ?? 'active'; - const local = state.local ?? params?.local ?? false; + const [orderParam, setOrderParam] = useSearchParam('order'); + const [localParam, setLocalParam] = useSearchParam('local'); + + let localParamBool: boolean | undefined; + + if (localParam === 'false') { + localParamBool = false; + } + + const order = orderParam ?? params?.order ?? 'active'; + const local = localParamBool ?? params?.local ?? true; const handlePin = useCallback(() => { if (columnId) { @@ -101,10 +104,10 @@ export const Directory: React.FC<{ if (columnId) { dispatch(changeColumnParams(columnId, ['order'], e.target.value)); } else { - setState((s) => ({ order: e.target.value, local: s.local })); + setOrderParam(e.target.value); } }, - [dispatch, columnId], + [dispatch, columnId, setOrderParam], ); const handleChangeLocal = useCallback>( @@ -113,11 +116,13 @@ export const Directory: React.FC<{ dispatch( changeColumnParams(columnId, ['local'], e.target.value === '1'), ); + } else if (e.target.value === '1') { + setLocalParam('true'); } else { - setState((s) => ({ local: e.target.value === '1', order: s.order })); + setLocalParam('false'); } }, - [dispatch, columnId], + [dispatch, columnId, setLocalParam], ); const handleLoadMore = useCallback(() => { From 09459ed0008b42098549c0213916e85f567b0579 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 20 Sep 2024 10:15:14 +0200 Subject: [PATCH 30/51] Update dependency react-select to v5.8.1 (#31982) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 5372c39f47306f..99977274a14329 100644 --- a/yarn.lock +++ b/yarn.lock @@ -14689,8 +14689,8 @@ __metadata: linkType: hard "react-select@npm:^5.7.3": - version: 5.8.0 - resolution: "react-select@npm:5.8.0" + version: 5.8.1 + resolution: "react-select@npm:5.8.1" dependencies: "@babel/runtime": "npm:^7.12.0" "@emotion/cache": "npm:^11.4.0" @@ -14704,7 +14704,7 @@ __metadata: peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 - checksum: 10c0/b4b98aaf117ee5cc4642871b7bd51fd0e2697988d0b880f30b21e933ca90258959147117d8aada36713b622e0e4cb06bd18ec02069f3f108896e0d31e69e3c16 + checksum: 10c0/0fd73e1e472105f980e09c86f0e6adbdc9f2f5c1befa275b08c71653becdd1829f596155a81b5085cb86f18b20bf4f4cc439ab5fe23e68f326e169dcfe00ccf6 languageName: node linkType: hard From 9a03902ab602fe49ea8bfbc56cdbed0831e5963f Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Fri, 20 Sep 2024 04:16:19 -0400 Subject: [PATCH 31/51] Capture actual behavior in v2/notifications "someone else" dismiss scenario (#31985) --- spec/requests/api/v2/notifications_spec.rb | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/spec/requests/api/v2/notifications_spec.rb b/spec/requests/api/v2/notifications_spec.rb index edf333ecd85586..9522a39e0f55a4 100644 --- a/spec/requests/api/v2/notifications_spec.rb +++ b/spec/requests/api/v2/notifications_spec.rb @@ -312,12 +312,15 @@ def body_json_types end context 'when notification belongs to someone else' do - let(:notification) { Fabricate(:notification) } + let(:notification) { Fabricate(:notification, group_key: 'foobar') } - it 'returns http not found' do - subject + it 'leaves the notification alone' do + expect { subject } + .to_not change(Notification, :count) - expect(response).to have_http_status(404) + expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end end From 840fd697306ddb8d54768bdc083b1c93cb16fc3a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 20 Sep 2024 10:20:27 +0200 Subject: [PATCH 32/51] Update dependency sass to v1.79.2 (#31992) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 99977274a14329..c90ada78f5baf4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -15475,15 +15475,15 @@ __metadata: linkType: hard "sass@npm:^1.62.1": - version: 1.79.1 - resolution: "sass@npm:1.79.1" + version: 1.79.2 + resolution: "sass@npm:1.79.2" dependencies: chokidar: "npm:^4.0.0" immutable: "npm:^4.0.0" source-map-js: "npm:>=0.6.2 <2.0.0" bin: sass: sass.js - checksum: 10c0/187bc885bad2e81e5414a146c9e14a28f622b5aba5c1425f4dd6d8ef2045b66c69de93689503886a8d2cc55d6bcce97b05ee87b074628fefd03e762789f931d0 + checksum: 10c0/b637daf133da4fbafbb7e6ae07b01ff7c73e406f3134e66749bf6f712dcc0056c6971d8629d8cc2b186df5ffb2282baa8f1818f35e326b3558ab284e31fdd87d languageName: node linkType: hard From 162f9a3c90dd688cd042f241add098c1ef6c6625 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 20 Sep 2024 08:31:28 +0000 Subject: [PATCH 33/51] New Crowdin Translations (automated) (#31993) Co-authored-by: GitHub Actions --- app/javascript/mastodon/locales/es-MX.json | 10 +- app/javascript/mastodon/locales/ru.json | 67 +++++++++- config/locales/activerecord.ru.yml | 6 + config/locales/doorkeeper.es-MX.yml | 2 +- config/locales/es-MX.yml | 6 +- config/locales/ru.yml | 145 +++++++++++++++++++++ config/locales/simple_form.es-MX.yml | 2 +- config/locales/simple_form.ru.yml | 9 ++ 8 files changed, 236 insertions(+), 11 deletions(-) diff --git a/app/javascript/mastodon/locales/es-MX.json b/app/javascript/mastodon/locales/es-MX.json index 7b09ada9f828e7..5c8f44d4a7f5d7 100644 --- a/app/javascript/mastodon/locales/es-MX.json +++ b/app/javascript/mastodon/locales/es-MX.json @@ -537,11 +537,11 @@ "notification_requests.accept": "Aceptar", "notification_requests.accept_multiple": "{count, plural, one {Aceptar # solicitud…} other {Aceptar # solicitudes…}}", "notification_requests.confirm_accept_multiple.button": "{count, plural, one {Solicitud aceptada} other {Solicitudes aceptadas}}", - "notification_requests.confirm_accept_multiple.message": "Vas a aceptar {count, plural, one {una solicitud} other {# solicitudes}}. ¿Quieres continuar?", - "notification_requests.confirm_accept_multiple.title": "¿Aceptar las solicitudes?", - "notification_requests.confirm_dismiss_multiple.button": "{count, plural, one {Descartar solicitud} other {Descartar solicitudes}}", - "notification_requests.confirm_dismiss_multiple.message": "Vas a descartar {count, plural, one {una solicitud} other {# solicitudes}}. No podrás volver a acceder fácilmente a {count, plural, one {ella} other {ellas}} de nuevo. ¿Seguro que quieres continuar?", - "notification_requests.confirm_dismiss_multiple.title": "¿Descartar las solicitudes?", + "notification_requests.confirm_accept_multiple.message": "Estás por aceptar {count, plural, one {una solicitud de notificación} other {# solicitudes de notificación}}. ¿Estás seguro de que quieres continuar?", + "notification_requests.confirm_accept_multiple.title": "¿Deseas aceptar las solicitudes de notificación?", + "notification_requests.confirm_dismiss_multiple.button": "{count, plural, one {Solicitud descartada} other {Solicitudes descartadas}}", + "notification_requests.confirm_dismiss_multiple.message": "Estás por descartar {count, plural, one {una solicitud de notificación} other {# solicitudes de notificación}}. No serás capaz de acceder fácilmente a {count, plural, one {ella} other {ellas}} de nuevo. ¿Estás seguro de que quieres continuar?", + "notification_requests.confirm_dismiss_multiple.title": "¿Deseas descartar las solicitudes de notificación?", "notification_requests.dismiss": "Descartar", "notification_requests.dismiss_multiple": "{count, plural, one {Descartar # solicitud…} other {Descartar # solicitudes…}}", "notification_requests.edit_selection": "Editar", diff --git a/app/javascript/mastodon/locales/ru.json b/app/javascript/mastodon/locales/ru.json index 4a03f2b6e80db9..d97b1658c552ae 100644 --- a/app/javascript/mastodon/locales/ru.json +++ b/app/javascript/mastodon/locales/ru.json @@ -34,7 +34,9 @@ "account.follow_back": "Подписаться в ответ", "account.followers": "Подписчики", "account.followers.empty": "На этого пользователя пока никто не подписан.", + "account.followers_counter": "{count, plural, one {{counter} последователя} other {{counter} последователей}}", "account.following": "Подписки", + "account.following_counter": "{count, plural, one {{counter} последующий} other {{counter} последующие}}", "account.follows.empty": "Этот пользователь пока ни на кого не подписался.", "account.go_to_profile": "Перейти к профилю", "account.hide_reblogs": "Скрыть продвижения от @{name}", @@ -48,7 +50,7 @@ "account.moved_to": "У {name} теперь новый аккаунт:", "account.mute": "Игнорировать @{name}", "account.mute_notifications_short": "Отключить уведомления", - "account.mute_short": "Глохни!", + "account.mute_short": "Приглушить", "account.muted": "Игнорируется", "account.mutual": "Взаимно", "account.no_bio": "Описание не предоставлено.", @@ -94,6 +96,8 @@ "block_modal.title": "Заблокировать пользователя?", "block_modal.you_wont_see_mentions": "Вы не увидите записи, которые упоминают его.", "boost_modal.combo": "{combo}, чтобы пропустить это в следующий раз", + "boost_modal.reblog": "Повысить пост?", + "boost_modal.undo_reblog": "Разгрузить пост?", "bundle_column_error.copy_stacktrace": "Скопировать отчет об ошибке", "bundle_column_error.error.body": "Запрошенная страница не может быть отображена. Это может быть вызвано ошибкой в нашем коде или проблемой совместимости браузера.", "bundle_column_error.error.title": "О нет!", @@ -298,6 +302,8 @@ "filter_modal.select_filter.subtitle": "Используйте существующую категорию или создайте новую", "filter_modal.select_filter.title": "Фильтровать этот пост", "filter_modal.title.status": "Фильтровать пост", + "filter_warning.matches_filter": "Соответствует фильтру \"{title}\"", + "filtered_notifications_banner.pending_requests": "Вы можете знать {count, plural, =0 {ни один} one {один человек} other {# люди}}", "filtered_notifications_banner.title": "Отфильтрованные уведомления", "firehose.all": "Все", "firehose.local": "Текущий сервер", @@ -346,6 +352,14 @@ "hashtag.follow": "Подписаться на новые посты", "hashtag.unfollow": "Отписаться", "hashtags.and_other": "...и {count, plural, other {# ещё}}", + "hints.profiles.followers_may_be_missing": "Последователи для этого профиля могут отсутствовать.", + "hints.profiles.follows_may_be_missing": "Фолловеры для этого профиля могут отсутствовать.", + "hints.profiles.posts_may_be_missing": "Некоторые сообщения из этого профиля могут отсутствовать.", + "hints.profiles.see_more_followers": "Посмотреть больше подписчиков на {domain}", + "hints.profiles.see_more_follows": "Смотрите другие материалы по теме {domain}", + "hints.profiles.see_more_posts": "Посмотреть другие сообщения на {domain}", + "hints.threads.replies_may_be_missing": "Ответы с других серверов могут отсутствовать.", + "hints.threads.see_more": "Посмотреть другие ответы на {domain}", "home.column_settings.show_reblogs": "Показывать продвижения", "home.column_settings.show_replies": "Показывать ответы", "home.hide_announcements": "Скрыть объявления", @@ -353,7 +367,17 @@ "home.pending_critical_update.link": "Посмотреть обновления", "home.pending_critical_update.title": "Доступно критическое обновление безопасности!", "home.show_announcements": "Показать объявления", + "ignore_notifications_modal.disclaimer": "Mastodon не может сообщить пользователям, что вы проигнорировали их уведомления. Игнорирование уведомлений не остановит отправку самих сообщений.", + "ignore_notifications_modal.filter_instead": "Фильтр вместо", "ignore_notifications_modal.filter_to_act_users": "Вы и далее сможете принять, отвергнуть и жаловаться на пользователей", + "ignore_notifications_modal.filter_to_avoid_confusion": "Фильтрация помогает избежать потенциальной путаницы", + "ignore_notifications_modal.filter_to_review_separately": "Вы можете просматривать отфильтрованные уведомления отдельно", + "ignore_notifications_modal.ignore": "Игнорировать уведомления", + "ignore_notifications_modal.limited_accounts_title": "Игнорировать уведомления от модерируемых аккаунтов?", + "ignore_notifications_modal.new_accounts_title": "Игнорировать уведомления от новых аккаунтов?", + "ignore_notifications_modal.not_followers_title": "Игнорировать уведомления от людей, которые не следят за вами?", + "ignore_notifications_modal.not_following_title": "Игнорировать уведомления от людей, за которыми вы не следите?", + "ignore_notifications_modal.private_mentions_title": "Игнорировать уведомления о нежелательных личных сообщениях?", "interaction_modal.description.favourite": "С учётной записью Mastodon, вы можете добавить этот пост в избранное, чтобы сохранить его на будущее и дать автору знать, что пост вам понравился.", "interaction_modal.description.follow": "С учётной записью Mastodon вы можете подписаться на {name}, чтобы получать их посты в своей домашней ленте.", "interaction_modal.description.reblog": "С учётной записью Mastodon, вы можете продвинуть этот пост, чтобы поделиться им со своими подписчиками.", @@ -432,6 +456,7 @@ "lists.subheading": "Ваши списки", "load_pending": "{count, plural, one {# новый элемент} few {# новых элемента} other {# новых элементов}}", "loading_indicator.label": "Загрузка…", + "media_gallery.hide": "Скрыть", "moved_to_account_banner.text": "Ваша учетная запись {disabledAccount} в настоящее время заморожена, потому что вы переехали на {movedToAccount}.", "mute_modal.hide_from_notifications": "Скрыть из уведомлений", "mute_modal.hide_options": "Скрыть параметры", @@ -443,6 +468,7 @@ "mute_modal.you_wont_see_mentions": "Вы не увидите постов, которые их упоминают.", "mute_modal.you_wont_see_posts": "Они по-прежнему смогут видеть ваши посты, но вы не сможете видеть их посты.", "navigation_bar.about": "О проекте", + "navigation_bar.administration": "Администрация", "navigation_bar.advanced_interface": "Включить многоколоночный интерфейс", "navigation_bar.blocks": "Заблокированные пользователи", "navigation_bar.bookmarks": "Закладки", @@ -459,6 +485,7 @@ "navigation_bar.follows_and_followers": "Подписки и подписчики", "navigation_bar.lists": "Списки", "navigation_bar.logout": "Выйти", + "navigation_bar.moderation": "Модерация", "navigation_bar.mutes": "Игнорируемые пользователи", "navigation_bar.opened_in_classic_interface": "Сообщения, учётные записи и другие специфические страницы по умолчанию открываются в классическом веб-интерфейсе.", "navigation_bar.personal": "Личное", @@ -469,10 +496,22 @@ "navigation_bar.security": "Безопасность", "not_signed_in_indicator.not_signed_in": "Вам нужно войти, чтобы иметь доступ к этому ресурсу.", "notification.admin.report": "{name} сообщил о {target}", + "notification.admin.report_account": "{name} сообщил {count, plural, one {один пост} other {# постов}} от {target} для {category}", + "notification.admin.report_account_other": "{name} сообщил {count, plural, one {одно сообщение} other {# сообщений}} от {target}", + "notification.admin.report_statuses": "{name} сообщил {target} для {category}", + "notification.admin.report_statuses_other": "{name} сообщает {target}", "notification.admin.sign_up": "{name} зарегистрирован", + "notification.admin.sign_up.name_and_others": "{name} и {count, plural, one {# другой} other {# другие}} подписались", "notification.favourite": "{name} добавил(а) ваш пост в избранное", + "notification.favourite.name_and_others_with_link": "{name} и {count, plural, one {# другие} other {# другие}} отдали предпочтение вашему посту", "notification.follow": "{name} подписался (-лась) на вас", "notification.follow_request": "{name} отправил запрос на подписку", + "notification.follow_request.name_and_others": "{name} и {count, plural, one {# другие} other {# другие}} последовали за тобой", + "notification.label.mention": "Упоминание", + "notification.label.private_mention": "Частное упоминание", + "notification.label.private_reply": "Частный ответ", + "notification.label.reply": "Ответить", + "notification.mention": "Упоминание", "notification.moderation-warning.learn_more": "Узнать больше", "notification.moderation_warning": "Вы получили предупреждение от модерации", "notification.moderation_warning.action_delete_statuses": "Некоторые из ваших публикаций были удалены.", @@ -483,7 +522,9 @@ "notification.moderation_warning.action_silence": "Ваша учётная запись была ограничена.", "notification.moderation_warning.action_suspend": "Действие вашей учётной записи приостановлено.", "notification.own_poll": "Ваш опрос закончился", + "notification.poll": "Голосование, в котором вы приняли участие, завершилось", "notification.reblog": "{name} продвинул(а) ваш пост", + "notification.reblog.name_and_others_with_link": "{name} и {count, plural, one {# other} other {# others}} увеличили ваш пост", "notification.relationships_severance_event": "Потеряно соединение с {name}", "notification.relationships_severance_event.account_suspension": "Администратор {from} заблокировал {target}, что означает, что вы больше не сможете получать обновления от них или взаймодествовать с ними.", "notification.relationships_severance_event.domain_block": "Администратор {from} заблокировал {target} включая {followersCount} ваших подписчиков и {followingCount, plural, one {# аккаунт} few {# аккаунта} other {# аккаунтов}}, на которые вы подписаны.", @@ -492,10 +533,19 @@ "notification.status": "{name} только что запостил", "notification.update": "{name} изменил(а) пост", "notification_requests.accept": "Принять", + "notification_requests.confirm_accept_multiple.button": "{count, plural, one {Принять запрос} other {Принять запросы}}", + "notification_requests.confirm_accept_multiple.title": "Принимать запросы на уведомления?", + "notification_requests.confirm_dismiss_multiple.title": "Отклонять запросы на уведомления?", "notification_requests.dismiss": "Отклонить", + "notification_requests.edit_selection": "Редактировать", + "notification_requests.exit_selection": "Готово", + "notification_requests.explainer_for_limited_account": "Уведомления от этой учетной записи были отфильтрованы, поскольку учетная запись была ограничена модератором.", + "notification_requests.explainer_for_limited_remote_account": "Уведомления от этой учетной записи были отфильтрованы, поскольку учетная запись или ее сервер были ограничены модератором.", "notification_requests.maximize": "Развернуть", + "notification_requests.minimize_banner": "Минимизация баннера отфильтрованных уведомлений", "notification_requests.notifications_from": "Уведомления от {name}", "notification_requests.title": "Отфильтрованные уведомления", + "notification_requests.view": "Просмотр уведомлений", "notifications.clear": "Очистить уведомления", "notifications.clear_confirmation": "Вы уверены, что хотите очистить все уведомления?", "notifications.clear_title": "Сбросить уведомления?", @@ -530,7 +580,14 @@ "notifications.permission_denied": "Уведомления на рабочем столе недоступны, так как вы запретили их отправку в браузере. Проверьте настройки для сайта, чтобы включить их обратно.", "notifications.permission_denied_alert": "Уведомления на рабочем столе недоступны, так как вы ранее отклонили запрос на их отправку.", "notifications.permission_required": "Чтобы включить уведомления на рабочем столе, необходимо разрешить их в браузере.", + "notifications.policy.accept": "Принять", + "notifications.policy.accept_hint": "Показать в уведомлениях", "notifications.policy.drop": "Игнорируем", + "notifications.policy.drop_hint": "Отправить в пустоту, чтобы никогда больше не увидеть", + "notifications.policy.filter": "Фильтр", + "notifications.policy.filter_hint": "Отправка в папку фильтрованных уведомлений", + "notifications.policy.filter_limited_accounts_hint": "Ограничено модераторами сервера", + "notifications.policy.filter_limited_accounts_title": "Модерируемые аккаунты", "notifications.policy.filter_new_accounts.hint": "Создано в течение последних {days, plural, one {один день} few {# дней} many {# дней} other {# дня}}", "notifications.policy.filter_new_accounts_title": "Новые учётные записи", "notifications.policy.filter_not_followers_title": "Люди, не подписанные на вас", @@ -538,6 +595,7 @@ "notifications.policy.filter_not_following_title": "Люди, на которых вы не подписаны", "notifications.policy.filter_private_mentions_hint": "Фильтруется, если только это не ответ на ваше собственное упоминание или если вы подписаны на отправителя", "notifications.policy.filter_private_mentions_title": "Нежелательные личные упоминания", + "notifications.policy.title": "………Управлять уведомлениями от…", "notifications_permission_banner.enable": "Включить уведомления", "notifications_permission_banner.how_to_control": "Получайте уведомления даже когда Mastodon закрыт, включив уведомления на рабочем столе. А чтобы лишний шум не отвлекал, вы можете настроить какие уведомления вы хотите получать, нажав на кнопку {icon} выше.", "notifications_permission_banner.title": "Будьте в курсе происходящего", @@ -666,6 +724,7 @@ "report_notification.categories.legal": "Правовая информация", "report_notification.categories.legal_sentence": "срамной контент", "report_notification.categories.other": "Прочее", + "report_notification.categories.other_sentence": "другое", "report_notification.categories.spam": "Спам", "report_notification.categories.spam_sentence": "спам", "report_notification.categories.violation": "Нарушение правил", @@ -696,8 +755,11 @@ "server_banner.about_active_users": "Люди, заходившие на этот сервер за последние 30 дней (ежемесячные активные пользователи)", "server_banner.active_users": "активные пользователи", "server_banner.administered_by": "Управляется:", + "server_banner.is_one_of_many": "{domain} - это один из многих независимых серверов Mastodon, которые вы можете использовать для участия в fediverse.", "server_banner.server_stats": "Статистика сервера:", "sign_in_banner.create_account": "Создать учётную запись", + "sign_in_banner.follow_anyone": "Следите за любым человеком в федеральной вселенной и смотрите все в хронологическом порядке. Никаких алгоритмов, рекламы или клик бейта.", + "sign_in_banner.mastodon_is": "Mastodon - лучший способ быть в курсе всего происходящего.", "sign_in_banner.sign_in": "Войти", "sign_in_banner.sso_redirect": "Войдите или Зарегистрируйтесь", "status.admin_account": "Открыть интерфейс модератора для @{name}", @@ -707,6 +769,7 @@ "status.bookmark": "Сохранить в закладки", "status.cancel_reblog_private": "Не продвигать", "status.cannot_reblog": "Этот пост не может быть продвинут", + "status.continued_thread": "Продолжение темы", "status.copy": "Скопировать ссылку на пост", "status.delete": "Удалить", "status.detailed_status": "Подробный просмотр обсуждения", @@ -715,6 +778,7 @@ "status.edit": "Изменить", "status.edited": "Дата последнего изменения: {date}", "status.edited_x_times": "{count, plural, one {{count} изменение} many {{count} изменений} other {{count} изменения}}", + "status.embed": "Получить код для встраивания", "status.favourite": "Избранное", "status.filter": "Фильтровать этот пост", "status.history.created": "{name} создал {date}", @@ -737,6 +801,7 @@ "status.reblogs.empty": "Никто ещё не продвинул этот пост. Как только кто-то это сделает, они появятся здесь.", "status.redraft": "Создать заново", "status.remove_bookmark": "Убрать из закладок", + "status.replied_in_thread": "Ответил в теме", "status.replied_to": "Ответил(а) {name}", "status.reply": "Ответить", "status.replyAll": "Ответить всем", diff --git a/config/locales/activerecord.ru.yml b/config/locales/activerecord.ru.yml index 92d85af4d9d28d..203d8e2c34d0d7 100644 --- a/config/locales/activerecord.ru.yml +++ b/config/locales/activerecord.ru.yml @@ -15,6 +15,12 @@ ru: user/invite_request: text: Причина errors: + attributes: + domain: + invalid: не является действующим доменным именем + messages: + invalid_domain_on_line: "%{value} Не является действительным доменным именем" + too_many_lines: Превышает предел %{limit} строк models: account: attributes: diff --git a/config/locales/doorkeeper.es-MX.yml b/config/locales/doorkeeper.es-MX.yml index b5987676d2824c..49ff9a1e43d15d 100644 --- a/config/locales/doorkeeper.es-MX.yml +++ b/config/locales/doorkeeper.es-MX.yml @@ -167,7 +167,7 @@ es-MX: admin:write:reports: realizar acciones de moderación en informes crypto: usar cifrado de extremo a extremo follow: seguir, bloquear, desbloquear y dejar de seguir cuentas - profile: leer sólo la información del perfil de tu cuenta + profile: leer solamente la información del perfil de tu cuenta push: recibir tus notificaciones push read: leer los datos de tu cuenta read:accounts: ver información de cuentas diff --git a/config/locales/es-MX.yml b/config/locales/es-MX.yml index b39b23c6170801..8aac7cbb47b2c2 100644 --- a/config/locales/es-MX.yml +++ b/config/locales/es-MX.yml @@ -202,10 +202,10 @@ es-MX: destroy_user_role: Destruir Rol disable_2fa_user: Deshabilitar 2FA disable_custom_emoji: Deshabilitar Emoji Personalizado - disable_sign_in_token_auth_user: Deshabilitar la Autenticación por Token de Correo Electrónico para el Usuario + disable_sign_in_token_auth_user: Deshabilitar la autenticación por token de correo electrónico para el usuario disable_user: Deshabilitar Usuario enable_custom_emoji: Habilitar Emoji Personalizado - enable_sign_in_token_auth_user: Habilitar la Autenticación por Token de Correo Electrónico para el Usuario + enable_sign_in_token_auth_user: Habilitar la autenticación por token de correo electrónico para el usuario enable_user: Habilitar Usuario memorialize_account: Transformar en Cuenta Conmemorativa promote_user: Promover Usuario @@ -896,7 +896,7 @@ es-MX: reviewed: Revisada title: Estado trendable: Puede ser tendencia - unreviewed: Sin revisar + unreviewed: No revisado usable: Disponible name: Nombre newest: Más reciente diff --git a/config/locales/ru.yml b/config/locales/ru.yml index f1198c3f16b6c2..387772749006e1 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -28,6 +28,8 @@ ru: admin: account_actions: action: Выполнить действие + already_silenced: Эта учетная запись уже ограничена. + already_suspended: Действие этой учетной записи уже приостановлено. title: Произвести модерацию учётной записи %{acct} account_moderation_notes: create: Создать @@ -49,6 +51,7 @@ ru: title: Сменить e-mail для %{username} change_role: changed_msg: Роль успешно изменена! + edit_roles: Управление ролями пользователей label: Изменить роль no_role: Нет роли title: Изменить роль %{username} @@ -61,6 +64,7 @@ ru: demote: Разжаловать destroyed_msg: Данные %{username} поставлены в очередь на удаление disable: Заморозка + disable_sign_in_token_auth: Отключите аутентификацию с помощью маркера электронной почты disable_two_factor_authentication: Отключить 2FA disabled: Отключено display_name: Отображаемое имя @@ -69,6 +73,7 @@ ru: email: E-mail email_status: Статус e-mail enable: Включить + enable_sign_in_token_auth: Включите аутентификацию с помощью маркера электронной почты enabled: Включен enabled_msg: Учётная запись %{username} успешно разморожена followers: Подписчики @@ -135,6 +140,7 @@ ru: resubscribe: Переподписаться role: Роль search: Поиск + search_same_email_domain: Другие пользователи с тем же почтовым доменом search_same_ip: Другие пользователи с таким же IP security: Безопасность security_measures: @@ -180,17 +186,21 @@ ru: confirm_user: Подтверждение пользователей create_account_warning: Выдача предупреждения create_announcement: Создание объявлений + create_canonical_email_block: Создать блок электронной почты create_custom_emoji: Добавление эмодзи create_domain_allow: Разрешение доменов create_domain_block: Блокировка доменов + create_email_domain_block: Создать блок домена электронной почты create_ip_block: Создание правил для IP-адресов create_unavailable_domain: Добавление домена в список недоступных create_user_role: Создать роль demote_user: Разжалование пользователей destroy_announcement: Удаление объявлений + destroy_canonical_email_block: Удалить блок электронной почты destroy_custom_emoji: Удаление эмодзи destroy_domain_allow: Отзыв разрешений для доменов destroy_domain_block: Разблокировка доменов + destroy_email_domain_block: Удалить блок домена электронной почты destroy_instance: Очистить домен destroy_ip_block: Удаление правил для IP-адресов destroy_status: Удаление постов @@ -198,8 +208,10 @@ ru: destroy_user_role: Удалить роль disable_2fa_user: Отключение 2FA disable_custom_emoji: Отключение эмодзи + disable_sign_in_token_auth_user: Отключить аутентификацию пользователя с помощью токена электронной почты disable_user: Заморозка пользователей enable_custom_emoji: Включение эмодзи + enable_sign_in_token_auth_user: Включить аутентификацию пользователя с помощью токена электронной почты enable_user: Разморозка пользователей memorialize_account: Присвоение пользователям статуса «мемориала» promote_user: Повышение пользователей @@ -229,20 +241,26 @@ ru: approve_appeal_html: "%{name} одобрил апелляцию на умеренное решение от %{target}" approve_user_html: "%{name} утвердил(а) регистрацию %{target}" assigned_to_self_report_html: "%{name} назначил(а) себя для решения жалобы %{target}" + change_email_user_html: "%{name} изменил адрес электронной почты пользователя %{target}" change_role_user_html: "%{name} изменил(а) роль %{target}" + confirm_user_html: "%{name} подтвержденный адрес электронной почты пользователя %{target}" create_account_warning_html: "%{name} выдал(а) предупреждение %{target}" create_announcement_html: "%{name} создал(а) новое объявление %{target}" + create_canonical_email_block_html: "%{name} заблокировал письмо с хэшем %{target}" create_custom_emoji_html: "%{name} загрузил(а) новый эмодзи %{target}" create_domain_allow_html: "%{name} разрешил(а) федерацию с доменом %{target}" create_domain_block_html: "%{name} заблокировал(а) домен %{target}" + create_email_domain_block_html: "%{name} заблокированный почтовый домен %{target}" create_ip_block_html: "%{name} создал(а) правило для IP %{target}" create_unavailable_domain_html: "%{name} приостановил доставку на узел %{target}" create_user_role_html: "%{name} создал(а) роль %{target}" demote_user_html: "%{name} разжаловал(а) пользователя %{target}" destroy_announcement_html: "%{name} удалил(а) объявление %{target}" + destroy_canonical_email_block_html: "%{name} разблокированное письмо с хэшем %{target}" destroy_custom_emoji_html: "%{name} удалил(а) эмодзи %{target}" destroy_domain_allow_html: "%{name} запретил(а) федерацию с доменом %{target}" destroy_domain_block_html: "%{name} снял(а) блокировку с домена %{target}" + destroy_email_domain_block_html: "%{name} разблокированный почтовый домен %{target}" destroy_instance_html: "%{name} очистил(а) данные для домена %{target}" destroy_ip_block_html: "%{name} удалил(а) правило для IP %{target}" destroy_status_html: "%{name} удалил(а) пост пользователя %{target}" @@ -250,8 +268,10 @@ ru: destroy_user_role_html: "%{name} удалил(а) роль %{target}" disable_2fa_user_html: "%{name} отключил(а) требование двухэтапной авторизации для пользователя %{target}" disable_custom_emoji_html: "%{name} отключил(а) эмодзи %{target}" + disable_sign_in_token_auth_user_html: "%{name} отключил аутентификацию по маркеру электронной почты для %{target}" disable_user_html: "%{name} заморозил(а) пользователя %{target}" enable_custom_emoji_html: "%{name} включил(а) эмодзи %{target}" + enable_sign_in_token_auth_user_html: "%{name} включил аутентификацию с помощью маркера электронной почты для %{target}" enable_user_html: "%{name} разморозил(а) пользователя %{target}" memorialize_account_html: "%{name} перевел(а) учётную запись пользователя %{target} в статус памятника" promote_user_html: "%{name} повысил(а) пользователя %{target}" @@ -259,6 +279,7 @@ ru: reject_user_html: "%{name} отклонил(а) регистрацию %{target}" remove_avatar_user_html: "%{name} убрал(а) аватарку пользователя %{target}" reopen_report_html: "%{name} повторно открыл(а) жалобу %{target}" + resend_user_html: "%{name} повторно отправил письмо с подтверждением для %{target}" reset_password_user_html: "%{name} сбросил(а) пароль пользователя %{target}" resolve_report_html: "%{name} решил(а) жалобу %{target}" sensitive_account_html: "%{name} установил(а) отметку файлов %{target} как «деликатного характера»" @@ -273,6 +294,7 @@ ru: update_custom_emoji_html: "%{name} обновил(а) эмодзи %{target}" update_domain_block_html: "%{name} обновил(а) блокировку домена для %{target}" update_ip_block_html: "%{name} изменил(а) правило для IP %{target}" + update_report_html: "%{name} обновленный отчет %{target}" update_status_html: "%{name} изменил(а) пост пользователя %{target}" update_user_role_html: "%{name} изменил(а) роль %{target}" deleted_account: удалённая учётная запись @@ -437,6 +459,8 @@ ru: new: create: Создать блокировку resolve: Проверить домен + title: Блокировка нового почтового домена + no_email_domain_block_selected: Блоки почтовых доменов не были изменены, так как ни один из них не был выбран not_permitted: Не разрешено resolved_through_html: Разрешено через %{domain} title: Заблокированные e-mail домены @@ -464,6 +488,9 @@ ru: title: Рекомендации подписок unsuppress: Восстановить рекомендацию instances: + audit_log: + title: Последние журналы аудита + view_all: Просмотр полных журналов аудита availability: description_html: few: Если доставка в домен завершается сбоем %{count} дня, дальнейшие попытки доставки предприниматься не будут, пока не будет получена доставка из домена. @@ -599,6 +626,7 @@ ru: silence_description_html: Учетная запись будет видна только тем пользователям, которые уже подписаны на неё, либо открыли его вручную. Это действие можно отменить в любой момент, и отменяет все жалобы против аккаунта. suspend_description_html: Аккаунт и все его содержимое будут недоступны и в конечном итоге удалены, и взаимодействие с ним будет невозможно. Это действие можно отменить в течение 30 дней. Отменяет все жалобы против этого аккаунта. actions_description_remote_html: Решите вопрос о том, какие меры необходимо принять для урегулирования этой жалобы. Это повлияет только на то, как ваш сервер взаимодействует с этим удаленным аккаунтом и обрабатывает его содержимое. + actions_no_posts: У этого отчета нет связанных с ним сообщений для удаления add_to_report: Прикрепить ещё already_suspended_badges: local: На этом сервере уже забанен @@ -639,6 +667,7 @@ ru: report: Жалоба №%{id} reported_account: Учётная запись нарушителя reported_by: Отправитель жалобы + reported_with_application: Сообщается с приложением resolved: Решённые resolved_msg: Жалоба обработана, спасибо! skip_to_actions: Перейти к действиям @@ -661,6 +690,7 @@ ru: delete_data_html: Удалить профиль и контент @%{acct} через 30 дней, если за это время они не будут разблокированы preview_preamble_html: "@%{acct} получит предупреждение со следующим содержанием:" record_strike_html: Запишите замечание против @%{acct}, чтобы помочь вам в решении будущих нарушений с этого аккаунта + send_email_html: Отправить @%{acct} предупреждающее письмо warning_placeholder: Необязательное дополнительное обоснование действия модерации. target_origin: Происхождение объекта жалобы title: Жалобы @@ -704,6 +734,7 @@ ru: manage_appeals: Управление апелляциями manage_appeals_description: Позволяет пользователям просматривать апелляции на действия модерации manage_blocks: Управление блоками + manage_blocks_description: Позволить пользователям блокировать провайдеров электронной почты и IP-адреса manage_custom_emojis: Управление смайлами manage_custom_emojis_description: Позволяет пользователям управлять пользовательскими эмодзи на сервере manage_federation: Управление Федерацией @@ -776,6 +807,7 @@ ru: disabled: Никому users: Залогиненным локальным пользователям registrations: + moderation_recommandation: Убедитесь, что у вас есть адекватная и оперативная команда модераторов, прежде чем открывать регистрацию для всех желающих! preamble: Контролируйте, кто может создать учетную запись на вашем сервере. title: Регистрации registrations_mode: @@ -783,6 +815,7 @@ ru: approved: Для регистрации требуется подтверждение none: Никто не может регистрироваться open: Все могут регистрироваться + warning_hint: Мы рекомендуем использовать "Требуется одобрение для регистрации", если вы не уверены, что ваша команда модераторов сможет своевременно справиться со спамом и вредоносными регистрациями. security: authorized_fetch: Требовать аутентификацию от федеративных серверов authorized_fetch_hint: Требование аутентификации от федеративных серверов позволяет более строго соблюдать блокировки как на уровне пользователя, так и на уровне сервера. Однако при этом снижается производительность, уменьшается охват ваших ответов и могут возникнуть проблемы совместимости с некоторыми федеративными сервисами. Кроме того, это не помешает специальным исполнителям получать ваши публичные сообщения и учётные записи. @@ -794,6 +827,7 @@ ru: destroyed_msg: Файл успешно удалён. software_updates: critical_update: Критично — пожалуйста, обновите как можно скорее + description: Рекомендуется поддерживать установку Mastodon в актуальном состоянии, чтобы воспользоваться последними исправлениями и функциями. Кроме того, иногда очень важно своевременно обновлять Mastodon, чтобы избежать проблем с безопасностью. По этим причинам Mastodon проверяет наличие обновлений каждые 30 минут и уведомляет вас об этом в соответствии с вашими предпочтениями в отношении уведомлений по электронной почте. documentation_link: Узнать больше release_notes: Примечания к выпуску title: Доступные обновления @@ -881,17 +915,38 @@ ru: message_html: "Ваше хранилище объектов неправильно настроено. Безопасность ваших пользователей находится под угрозой" tags: moderation: + not_trendable: Не в тренде + not_usable: Невозможно использовать + pending_review: В ожидании обзора + review_requested: Обзор запрошен + reviewed: Рассмотрено title: Статус + trendable: Трендовый + unreviewed: Без рецензии + usable: Полезное + name: Название + newest: Новейший + oldest: Старейший + open: Посмотреть публично + reset: Сброс review: Состояние проверки + search: Поиск + title: Хэштеги updated_msg: Настройки хэштега обновлены title: Администрирование trends: allow: Разрешить approved: Принятые + confirm_allow: Вы уверены, что хотите разрешить выбранные теги? + confirm_disallow: Вы уверены, что хотите запретить выбранные теги? disallow: Отклонить links: allow: Разрешить ссылку allow_provider: Разрешить издание + confirm_allow: Вы уверены, что хотите разрешить выбранные ссылки? + confirm_allow_provider: Вы уверены, что хотите разрешить выбранных провайдеров? + confirm_disallow: Вы уверены, что хотите запретить выбранные ссылки? + confirm_disallow_provider: Вы уверены, что хотите запретить выбранных поставщиков? description_html: Это ссылки, которыми в настоящее время много пользуются аккаунты, с которых ваш сервер видит сообщения. Это может помочь вашим пользователям узнать, что происходит в мире. Никакие ссылки не отображаются публично, пока вы не одобрите издателя. Вы также можете разрешить или отклонить индивидуальные ссылки. disallow: Запретить ссылку disallow_provider: Отклонить издание @@ -917,6 +972,10 @@ ru: statuses: allow: Разрешить пост allow_account: Разрешить автора + confirm_allow: Вы уверены, что хотите разрешить выбранные статусы? + confirm_allow_account: Вы уверены, что хотите разрешить выбранные учетные записи? + confirm_disallow: Вы уверены, что хотите запретить выбранные статусы? + confirm_disallow_account: Вы уверены, что хотите запретить выбранные учетные записи? description_html: Это посты, которыми на вашем сервере в данный момент часто делятся и предпочитают, что может помочь вашим новым и постоянным пользователям найти больше людей, чтобы на них подписаться. Посты не будут отображаться публично, пока вы не одобрите автора, а автор не разрешит предлагать его аккаунт другим. Вы также можете разрешить или отклонить отдельные сообщения. disallow: Запретить пост disallow_account: Запретить автора @@ -953,12 +1012,14 @@ ru: many: За последнюю неделю использовало %{count} человек one: За последнюю неделю использовал один человек other: За последнюю неделю использовал %{count} человек + title: Рекомендации и тенденции trending: Популярное warning_presets: add_new: Добавить delete: Удалить edit_preset: Удалить шаблон предупреждения empty: Вы еще не определили пресеты предупреждений. + title: Предупреждающие пред установки webhooks: add_new: Добавить конечную точку delete: Удалить @@ -982,6 +1043,9 @@ ru: title: Вебхуки webhook: Вебхук admin_mailer: + auto_close_registrations: + body: В связи с отсутствием активности модераторов в последнее время, регистрация на %{instance} была автоматически переведена в режим, требующий ручной проверки, чтобы предотвратить использование %{instance} в качестве платформы для потенциальных плохих игроков. Вы можете в любой момент переключить его обратно на открытые регистрации. + subject: Регистрации для %{instance} были автоматически переведены в разряд требующих одобрения new_appeal: actions: delete_statuses: удалить их посты @@ -1035,7 +1099,9 @@ ru: guide_link_text: Каждый может внести свой вклад. sensitive_content: Содержимое деликатного характера application_mailer: + notification_preferences: Изменение предпочтений электронной почты salutation: "%{name}," + settings: 'Измените настройки электронной почты: %{link}' unsubscribe: Отписаться view: 'Просмотр:' view_profile: Просмотреть профиль @@ -1055,6 +1121,7 @@ ru: hint_html: Еще одна вещь! Нам нужно подтвердить, что вы человек (так что мы можем держать спам!). Решите капчу ниже и нажмите кнопку «Продолжить». title: Проверка безопасности confirmations: + awaiting_review: Ваш адрес электронной почты подтвержден! Сотрудники %{domain} сейчас рассматривают вашу регистрацию. Вы получите письмо, если они одобрят вашу учетную запись! awaiting_review_title: Ваша регистрация проверяется clicking_this_link: нажатие на эту ссылку login_link: войти @@ -1062,6 +1129,7 @@ ru: redirect_to_app_html: Вы должны были перенаправлены на приложение %{app_name}. Если этого не произошло, попробуйте %{clicking_this_link} или вернитесь к приложению вручную. registration_complete: Ваша регистрация на %{domain} завершена! welcome_title: Добро пожаловать, %{name}! + wrong_email_hint: Если этот адрес электронной почты неверен, вы можете изменить его в настройках аккаунта. delete_account: Удалить учётную запись delete_account_html: Удалить свою учётную запись можно в два счёта здесь, но прежде у вас будет спрошено подтверждение. description: @@ -1082,6 +1150,7 @@ ru: or_log_in_with: Или войти с помощью privacy_policy_agreement_html: Мной прочитана и принята политика конфиденциальности progress: + confirm: Подтвердите электронную почту details: Ваши данные review: Наш обзор rules: Принять правила @@ -1103,8 +1172,10 @@ ru: security: Безопасность set_new_password: Задать новый пароль setup: + email_below_hint_html: Проверьте папку "Спам" или запросите другую. Вы можете исправить свой адрес электронной почты, если он неправильный. email_settings_hint_html: Нажмите на ссылку, которую мы отправили вам для проверки %{email}. Мы будем ждать прямо здесь. link_not_received: Не получили ссылку? + new_confirmation_instructions_sent: Через несколько минут вы получите новое письмо со ссылкой для подтверждения! title: Проверьте свой почтовый ящик sign_in: preamble_html: Войдите, используя ваши учётные данные %{domain}. Если ваша учётная запись размещена на другом сервере, вы не сможете здесь войти. @@ -1115,12 +1186,20 @@ ru: title: Зарегистрируйтесь в %{domain}. status: account_status: Статус учётной записи + confirming: Жду подтверждения по электронной почте. functional: Ваша учётная запись в полном порядке. + pending: Ваша заявка находится на рассмотрении у наших сотрудников. Это может занять некоторое время. Вы получите электронное письмо, если ваша заявка будет одобрена. redirecting_to: Ваша учётная запись деактивированна, потому что вы настроили перенаправление на %{acct}. self_destruct: Поскольку %{domain} закрывается, вы получите ограниченный доступ к вашей учетной записи. view_strikes: Просмотр предыдущих замечаний в адрес вашей учетной записи too_fast: Форма отправлена слишком быстро, попробуйте еще раз. use_security_key: Использовать ключ безопасности + author_attribution: + example_title: Образец текста + hint_html: Контролируйте, как вы будете отмечены при обмене ссылками на Mastodon. + more_from_html: Больше от %{name} + s_blog: "%{name}'S Блог" + title: Авторская атрибуция challenge: confirm: Продолжить hint_html: "Подсказка: мы не будем спрашивать пароль повторно в течение часа." @@ -1157,6 +1236,9 @@ ru: before: 'Внимательно прочитайте следующую информацию перед началом:' caches: Некоторые данные, обработанные другими узлами, однако, могут храниться ещё какое-то время data_removal: Все ваши золотые посты, шикарный профиль и прочие данные будут безвозвратно уничтожены + email_change_html: Вы можете изменить свой адрес электронной почты, не удаляя свою учетную запись + email_contact_html: Если оно все еще не пришло, вы можете обратиться за помощью по электронной почте %{email} + email_reconfirmation_html: Если вы не получили подтверждение по электронной почте, вы можете запросить его снова irreversible: После удаления восстановить или повторно активировать учётную запись не получится more_details_html: За всеми подробностями, изучите политику конфиденциальности. username_available: Ваше имя пользователя снова станет доступным @@ -1403,6 +1485,7 @@ ru: authentication_methods: otp: приложение двухфакторной аутентификации password: пароль + sign_in_token: код безопасности электронной почты webauthn: ключи безопасности description_html: Если вы видите неопознанное действие, смените пароль и/или включите двухфакторную авторизацию. empty: Нет доступной истории входов @@ -1413,10 +1496,20 @@ ru: unsubscribe: action: Да, отписаться complete: Подписка отменена + emails: + notification_emails: + favourite: любимые электронные письма с уведомлениями + follow: Следить за электронными сообщениями + follow_request: Письма с просьбой о помощи + mention: Упоминание электронных писем с уведомлениями + reblog: Уведомления по электронной почте + resubscribe_html: Если вы отписались от рассылки по ошибке, вы можете повторно подписаться на рассылку в настройках настроек почтовых уведомлений. + success_html: Вы больше не будете получать %{type} для Mastodon на %{domain} на вашу электронную почту %{email}. title: Отписаться media_attachments: validations: images_and_video: Нельзя добавить видео к посту с изображениями + not_found: Медиа %{ids} не найдено или уже прикреплено к другому сообщению not_ready: Не удаётся прикрепить файлы, обработка которых не завершена. Повторите попытку чуть позже! too_many: Нельзя добавить более 4 файлов migrations: @@ -1493,6 +1586,8 @@ ru: update: subject: "%{name} изменил(а) пост" notifications: + administration_emails: Уведомления администратора по электронной почте + email_events: События для уведомлений по электронной почте email_events_hint: 'Выберите события, для которых вы хотели бы получать уведомления:' number: human: @@ -1651,16 +1746,26 @@ ru: import: Импорт import_and_export: Импорт и экспорт migrate: Миграция учётной записи + notifications: E-mail уведомление preferences: Настройки profile: Профиль relationships: Подписки и подписчики + severed_relationships: Разорванные отношения statuses_cleanup: Авто-удаление постов strikes: Замечания модерации two_factor_authentication: Подтверждение входа webauthn_authentication: Ключи безопасности severed_relationships: + download: Скачать (%{count}) event_type: + account_suspension: Приостановка аккаунта (%{target_name}) + domain_block: Приостановка сервера (%{target_name}) user_domain_block: Вы заблокировали %{target_name} + lost_followers: Потерянные подписчики + lost_follows: Потерянный следует + preamble: Вы можете потерять подписчиков и последователей, если заблокируете домен или, если ваши модераторы решат приостановить работу удаленного сервера. Когда это произойдет, вы сможете загрузить списки разорванных отношений, чтобы проверить их и, возможно, импортировать на другой сервер. + purged: Информация об этом сервере была удалена администраторами вашего сервера. + type: Событие statuses: attached: audio: @@ -1751,6 +1856,7 @@ ru: contrast: Mastodon (высококонтрастная) default: Mastodon (тёмная) mastodon-light: Mastodon (светлая) + system: Автоматически (используйте системную тему) time: formats: default: "%d %b %Y, %H:%M" @@ -1839,8 +1945,45 @@ ru: suspend: Учётная запись заблокирована welcome: apps_android_action: Скачать на Google Play + apps_ios_action: Скачать в App Store + apps_step: Загрузите наши официальные приложения. + apps_title: Приложения Mastodon + checklist_subtitle: 'Давайте начнем знакомство с этим новым социальным рубежом:' + checklist_title: Приветственный контрольный список + edit_profile_action: Персонализация + edit_profile_step: Усильте взаимодействие, заполнив полный профиль. + edit_profile_title: Персонализируйте свой профиль explanation: Вот несколько советов для новичков feature_action: Подробнее + feature_audience: Mastodon предоставляет вам уникальную возможность управлять своей аудиторией без посредников. Mastodon, развернутый на вашей собственной инфраструктуре, позволяет вам следить и быть преследуемым с любого другого сервера Mastodon в Интернете и не контролируется никем, кроме вас. + feature_audience_title: Создайте уверенную аудиторию + feature_control: Вы сами знаете, что хотите видеть в своей ленте. Никаких алгоритмов или рекламы, чтобы тратить ваше время. Следите за любым человеком на любом сервере Mastodon с одного аккаунта и получайте его сообщения в хронологическом порядке, а также сделайте свой уголок интернета немного больше похожим на себя. + feature_control_title: Контролируйте свой график + feature_creativity: Mastodon поддерживает аудио-, видео- и фотопосты, описания доступности, опросы, предупреждения о содержании, анимированные аватары, пользовательские emojis, управление обрезкой миниатюр и многое другое, чтобы помочь вам выразить себя в Интернете. Публикуете ли вы свои работы, музыку или подкаст, Mastodon всегда готов помочь вам. + feature_creativity_title: Непревзойденная креативность + feature_moderation: Mastodon возвращает принятие решений в ваши руки. Каждый сервер создает свои собственные правила и нормы, которые соблюдаются локально, а не сверху вниз, как в корпоративных социальных сетях, что позволяет наиболее гибко реагировать на потребности различных групп людей. Присоединяйтесь к серверу с правилами, с которыми вы согласны, или создайте свой собственный. + feature_moderation_title: Модерирование, каким оно должно быть + follow_action: Следуйте за + follow_step: Следить за интересными людьми - вот что такое Mastodon. + follow_title: Персонализируйте свою домашнюю ленту + follows_subtitle: Следите за известными аккаунтами + follows_title: За кем следить + follows_view_more: Посмотреть больше людей, за которыми стоит следить + hashtags_recent_count: + few: "%{people} человека за последние 2 дня" + many: "%{people} человек за последние 2 дня" + one: "%{people} человек за последние 2 дня" + other: "%{people} человек за последние 2 дня" + hashtags_subtitle: Изучите, что было в тренде за последние 2 дня + hashtags_title: Модные хэштеги + hashtags_view_more: Посмотреть другие трендовые хэштеги + post_action: Составить + post_step: Поприветствуйте мир с помощью текста, фотографий, видео или опросов. + post_title: Сделайте свой первый пост + share_action: Поделиться + share_step: Пусть ваши друзья знают, как найти вас на Mastodon. + share_title: Поделитесь информацией о компании Mastodon + sign_in_action: Зарегистрироваться subject: Добро пожаловать в Mastodon title: Добро пожаловать на борт, %{name}! users: @@ -1849,6 +1992,7 @@ ru: invalid_otp_token: Введен неверный код двухфакторной аутентификации otp_lost_help_html: Если Вы потеряли доступ к обоим, свяжитесь с %{email} rate_limited: Слишком много попыток аутентификации, повторите попытку позже. + seamless_external_login: Вы вошли в систему через внешнюю службу, поэтому настройки пароля и электронной почты недоступны. signed_in_as: 'Выполнен вход под именем:' verification: extra_instructions_html: Подсказка: Ссылка на вашем сайте может быть невидимой. Важной частью является rel="me", который предотвращает выдачу себя за другое лицо на сайтах с пользовательским контентом. Вы даже можете использовать тег link в заголовке страницы вместо a, но HTML должен быть доступен без выполнения JavaScript. @@ -1857,6 +2001,7 @@ ru: instructions_html: Скопируйте и вставьте код ниже в HTML вашего сайта. Затем, добавьте адрес вашего веб сайта в одно из дополнительных полей на вкладке "Редактировать профиль" и сохраните изменения. verification: Верификация ссылок verified_links: Ваши ссылки подтверждения + website_verification: Проверка веб-сайта webauthn_credentials: add: Добавить новый ключ безопасности create: diff --git a/config/locales/simple_form.es-MX.yml b/config/locales/simple_form.es-MX.yml index 3271b75668f75f..f2108828d5643b 100644 --- a/config/locales/simple_form.es-MX.yml +++ b/config/locales/simple_form.es-MX.yml @@ -84,7 +84,7 @@ es-MX: closed_registrations_message: Mostrado cuando los registros están cerrados content_cache_retention_period: Todas las publicaciones de otros servidores (incluso impulsos y respuestas) se eliminarán después del número de días especificado, sin tener en cuenta la interacción del usuario local con esos mensajes. Esto incluye mensajes donde un usuario local los ha marcado como marcadores o favoritos. Las menciones privadas entre usuarios de diferentes instancias también se perderán sin posibilidad de recuperación. El uso de esta configuración está destinado a instancias de propósito especial, y rompe muchas expectativas de los usuarios cuando se implementa para un uso de propósito general. custom_css: Puedes aplicar estilos personalizados a la versión web de Mastodon. - favicon: WEBP, PNG, GIF o JPG. Reemplaza el favicon predeterminado de Mastodon con un icono personalizado. + favicon: WEBP, PNG, GIF o JPG. Reemplaza el icono predeterminado de Mastodon con un icono personalizado. mascot: Reemplaza la ilustración en la interfaz web avanzada. media_cache_retention_period: Los archivos multimedia de las publicaciones creadas por usuarios remotos se almacenan en caché en tu servidor. Cuando se establece un valor positivo, estos archivos se eliminarán después del número especificado de días. Si los datos multimedia se solicitan después de eliminarse, se volverán a descargar, si el contenido fuente todavía está disponible. Debido a restricciones en la frecuencia con la que las tarjetas de previsualización de enlaces realizan peticiones a sitios de terceros, se recomienda establecer este valor a al menos 14 días, o las tarjetas de previsualización de enlaces no se actualizarán bajo demanda antes de ese momento. peers_api_enabled: Una lista de nombres de dominio que este servidor ha encontrado en el fediverso. Aquí no se incluye ningún dato sobre si usted federa con un servidor determinado, sólo que su servidor lo sabe. Esto es utilizado por los servicios que recopilan estadísticas sobre la federación en un sentido general. diff --git a/config/locales/simple_form.ru.yml b/config/locales/simple_form.ru.yml index b41457e86ac6ce..3ff746451b3279 100644 --- a/config/locales/simple_form.ru.yml +++ b/config/locales/simple_form.ru.yml @@ -3,6 +3,7 @@ ru: simple_form: hints: account: + attribution_domains_as_text: Защищает от ложных атрибуций. discoverable: Ваши публичные сообщения и профиль могут быть показаны или рекомендованы в различных разделах Mastodon, и ваш профиль может быть предложен другим пользователям. display_name: Ваше полное имя или псевдоним. fields: Ваша домашняя страница, местоимения, возраст - все, что угодно. @@ -77,11 +78,15 @@ ru: warn: Скрыть отфильтрованный контент за предупреждением с указанием названия фильтра form_admin_settings: activity_api_enabled: Подсчёт количества локальных постов, активных пользователей и новых регистраций на еженедельной основе + app_icon: WEBP, PNG, GIF или JPG. Замените значок приложения по умолчанию на мобильных устройствах пользовательским значком. backups_retention_period: Пользователи могут создавать архивы своих постов, чтобы потом их забрать. Если задать положительное значение, эти архивы автоматически удалятся с вашего хранилища через указанное число дней. bootstrap_timeline_accounts: Эти аккаунты будут рекомендованы для подписки новым пользователям. closed_registrations_message: Отображается, когда регистрация закрыта + content_cache_retention_period: Все сообщения с других серверов (включая бусты и ответы) будут удалены через указанное количество дней, независимо от того, как локальный пользователь взаимодействовал с этими сообщениями. Это касается и тех сообщений, которые локальный пользователь пометил в закладки или избранное. Приватные упоминания между пользователями из разных инстансов также будут потеряны и не смогут быть восстановлены. Использование этой настройки предназначено для экземпляров специального назначения и нарушает многие ожидания пользователей при использовании в общих целях. custom_css: Вы можете применять пользовательские стили в веб-версии Mastodon. + favicon: WEBP, PNG, GIF или JPG. Заменяет стандартный фавикон Mastodon на собственный значок. mascot: Заменяет иллюстрацию в расширенном веб-интерфейсе. + media_cache_retention_period: Медиафайлы из сообщений, сделанных удаленными пользователями, кэшируются на вашем сервере. При положительном значении медиафайлы будут удалены через указанное количество дней. Если медиаданные будут запрошены после удаления, они будут загружены повторно, если исходный контент все еще доступен. В связи с ограничениями на частоту опроса карточек предварительного просмотра ссылок на сторонних сайтах рекомендуется устанавливать значение не менее 14 дней, иначе карточки предварительного просмотра ссылок не будут обновляться по запросу до этого времени. peers_api_enabled: Список доменных имен, с которыми сервер столкнулся в fediverse. Здесь нет данных о том, федерировались ли вы с данным сервером, только что ваш сервер знает об этом. Это используется службами, которые собирают статистику по федерации в общем смысле. profile_directory: В каталоге профилей перечислены все пользователи, которые согласились быть доступными для обнаружения. require_invite_text: Когда регистрация требует ручного одобрения, сделайте текстовый ввод "Почему вы хотите присоединиться?" обязательным, а не опциональным @@ -114,6 +119,7 @@ ru: sign_up_requires_approval: Новые регистрации потребуют вашего одобрения severity: Выберите, что будет происходить с запросами с этого IP rule: + hint: Необязательно. Предоставьте дополнительные сведения о правиле text: Опишите правило или требование для пользователей на этом сервере. Постарайтесь сделать его коротким и простым sessions: otp: 'Введите код двухфакторной аутентификации, сгенерированный в мобильном приложении, или используйте один из ваших кодов восстановления:' @@ -125,6 +131,7 @@ ru: name: Вы можете изменить только регистр букв чтобы, например, сделать тег более читаемым user: chosen_languages: Если выбрано, то в публичных лентах будут показаны только посты на выбранных языках. + role: Роль определяет, какими правами обладает пользователь. user_role: color: Цвет, который будет использоваться для роли в интерфейсе (UI), как RGB в формате HEX highlighted: Это действие сделает роль публичной @@ -137,6 +144,7 @@ ru: url: Куда события будут отправляться labels: account: + attribution_domains_as_text: Разрешить только определенные сайты discoverable: Профиль и сообщения в алгоритмах обнаружения fields: name: Пункт @@ -241,6 +249,7 @@ ru: backups_retention_period: Период хранения архива пользователя bootstrap_timeline_accounts: Всегда рекомендовать эти учетные записи новым пользователям closed_registrations_message: Сообщение, когда регистрация недоступна + content_cache_retention_period: Период хранения удаленного содержимого custom_css: Пользовательский CSS favicon: Favicon mascot: Пользовательский маскот (устаревшее) From c922af27375ec9e902981c6b66c96a7ae57c7513 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Fri, 20 Sep 2024 04:31:58 -0400 Subject: [PATCH 34/51] Add `LIMIT` constant for `api/v1/peers/search` endpoint (#31989) --- app/controllers/api/v1/peers/search_controller.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/controllers/api/v1/peers/search_controller.rb b/app/controllers/api/v1/peers/search_controller.rb index 1780554c5d8bc0..d9c82327022194 100644 --- a/app/controllers/api/v1/peers/search_controller.rb +++ b/app/controllers/api/v1/peers/search_controller.rb @@ -7,6 +7,8 @@ class Api::V1::Peers::SearchController < Api::BaseController skip_before_action :require_authenticated_user!, unless: :limited_federation_mode? skip_around_action :set_locale + LIMIT = 10 + vary_by '' def index @@ -35,10 +37,10 @@ def set_domains field: 'accounts_count', modifier: 'log2p', }, - }).limit(10).pluck(:domain) + }).limit(LIMIT).pluck(:domain) else domain = normalized_domain - @domains = Instance.searchable.domain_starts_with(domain).limit(10).pluck(:domain) + @domains = Instance.searchable.domain_starts_with(domain).limit(LIMIT).pluck(:domain) end rescue Addressable::URI::InvalidURIError @domains = [] From 04a939d6407d4ae5873757b8ab8d7187110fe6e7 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Fri, 20 Sep 2024 04:51:37 -0400 Subject: [PATCH 35/51] Add `reviewed` and `unreviewed` scopes to `Reviewable` model concern (#31988) --- app/models/concerns/reviewable.rb | 5 ++++ app/models/preview_card_provider.rb | 2 -- app/models/tag.rb | 4 +-- .../trends/preview_card_provider_filter.rb | 2 +- .../preview_card_providers/index.html.haml | 2 +- spec/models/preview_card_provider_spec.rb | 16 ------------ .../examples/models/concerns/reviewable.rb | 25 +++++++++++++++++++ 7 files changed, 33 insertions(+), 23 deletions(-) diff --git a/app/models/concerns/reviewable.rb b/app/models/concerns/reviewable.rb index 1f70474b35b1fd..d08b473342792d 100644 --- a/app/models/concerns/reviewable.rb +++ b/app/models/concerns/reviewable.rb @@ -3,6 +3,11 @@ module Reviewable extend ActiveSupport::Concern + included do + scope :reviewed, -> { where.not(reviewed_at: nil) } + scope :unreviewed, -> { where(reviewed_at: nil) } + end + def requires_review? reviewed_at.nil? end diff --git a/app/models/preview_card_provider.rb b/app/models/preview_card_provider.rb index 48944fe638467d..889176036c7558 100644 --- a/app/models/preview_card_provider.rb +++ b/app/models/preview_card_provider.rb @@ -34,8 +34,6 @@ class PreviewCardProvider < ApplicationRecord scope :trendable, -> { where(trendable: true) } scope :not_trendable, -> { where(trendable: false) } - scope :reviewed, -> { where.not(reviewed_at: nil) } - scope :pending_review, -> { where(reviewed_at: nil) } def self.matching_domain(domain) segments = domain.split('.') diff --git a/app/models/tag.rb b/app/models/tag.rb index acf514919be9b7..93210eb307e936 100644 --- a/app/models/tag.rb +++ b/app/models/tag.rb @@ -50,8 +50,6 @@ class Tag < ApplicationRecord validate :validate_name_change, if: -> { !new_record? && name_changed? } validate :validate_display_name_change, if: -> { !new_record? && display_name_changed? } - scope :reviewed, -> { where.not(reviewed_at: nil) } - scope :unreviewed, -> { where(reviewed_at: nil) } scope :pending_review, -> { unreviewed.where.not(requested_review_at: nil) } scope :usable, -> { where(usable: [true, nil]) } scope :not_usable, -> { where(usable: false) } @@ -127,7 +125,7 @@ def search_for(term, limit = 5, offset = 0, options = {}) query = Tag.matches_name(stripped_term) query = query.merge(Tag.listable) if options[:exclude_unlistable] - query = query.merge(matching_name(stripped_term).or(where.not(reviewed_at: nil))) if options[:exclude_unreviewed] + query = query.merge(matching_name(stripped_term).or(reviewed)) if options[:exclude_unreviewed] query.order(Arel.sql('length(name) ASC, name ASC')) .limit(limit) diff --git a/app/models/trends/preview_card_provider_filter.rb b/app/models/trends/preview_card_provider_filter.rb index 219793f01e950f..33f4e97912a89d 100644 --- a/app/models/trends/preview_card_provider_filter.rb +++ b/app/models/trends/preview_card_provider_filter.rb @@ -41,7 +41,7 @@ def status_scope(value) when 'rejected' PreviewCardProvider.not_trendable when 'pending_review' - PreviewCardProvider.pending_review + PreviewCardProvider.unreviewed else raise Mastodon::InvalidParameterError, "Unknown status: #{value}" end diff --git a/app/views/admin/trends/links/preview_card_providers/index.html.haml b/app/views/admin/trends/links/preview_card_providers/index.html.haml index b43b8dfff9f4e4..93daf25f31a74d 100644 --- a/app/views/admin/trends/links/preview_card_providers/index.html.haml +++ b/app/views/admin/trends/links/preview_card_providers/index.html.haml @@ -12,7 +12,7 @@ %li= filter_link_to t('generic.all'), status: nil %li= filter_link_to t('admin.trends.approved'), status: 'approved' %li= filter_link_to t('admin.trends.rejected'), status: 'rejected' - %li= filter_link_to safe_join([t('admin.accounts.moderation.pending'), "(#{PreviewCardProvider.pending_review.count})"], ' '), status: 'pending_review' + %li= filter_link_to safe_join([t('admin.accounts.moderation.pending'), "(#{PreviewCardProvider.unreviewed.count})"], ' '), status: 'pending_review' .back-link = link_to admin_trends_links_path do = material_symbol 'chevron_left' diff --git a/spec/models/preview_card_provider_spec.rb b/spec/models/preview_card_provider_spec.rb index 12bca83440f126..a3bd4f49ad1b8b 100644 --- a/spec/models/preview_card_provider_spec.rb +++ b/spec/models/preview_card_provider_spec.rb @@ -24,21 +24,5 @@ expect(results).to eq([not_trendable_and_not_reviewed]) end end - - describe 'reviewed' do - it 'returns the relevant records' do - results = described_class.reviewed - - expect(results).to eq([trendable_and_reviewed]) - end - end - - describe 'pending_review' do - it 'returns the relevant records' do - results = described_class.pending_review - - expect(results).to eq([not_trendable_and_not_reviewed]) - end - end end end diff --git a/spec/support/examples/models/concerns/reviewable.rb b/spec/support/examples/models/concerns/reviewable.rb index b63e44b43f9853..144019d969af3e 100644 --- a/spec/support/examples/models/concerns/reviewable.rb +++ b/spec/support/examples/models/concerns/reviewable.rb @@ -6,6 +6,31 @@ let(:reviewed_at) { nil } let(:requested_review_at) { nil } + describe 'Scopes' do + let!(:reviewed_record) { Fabricate factory_name, reviewed_at: 10.days.ago } + let!(:un_reviewed_record) { Fabricate factory_name, reviewed_at: nil } + + describe '.reviewed' do + it 'returns reviewed records' do + expect(described_class.reviewed) + .to include(reviewed_record) + .and not_include(un_reviewed_record) + end + end + + describe '.unreviewed' do + it 'returns non reviewed records' do + expect(described_class.unreviewed) + .to include(un_reviewed_record) + .and not_include(reviewed_record) + end + end + + def factory_name + described_class.name.underscore.to_sym + end + end + describe '#requires_review?' do it { is_expected.to be_requires_review } From e7fd0985c9cd14437443345adfe725ecd3b038a6 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Fri, 20 Sep 2024 11:42:02 +0200 Subject: [PATCH 36/51] Change zoom icon in web UI (#29683) --- .../mastodon/components/modal_root.jsx | 2 +- .../features/ui/components/image_loader.jsx | 7 +- .../features/ui/components/media_modal.jsx | 52 +++++-- .../features/ui/components/zoomable_image.jsx | 129 +++++------------- app/javascript/mastodon/locales/en.json | 4 +- .../400-24px/fit_screen-fill.svg | 1 + .../material-icons/400-24px/fit_screen.svg | 1 + .../styles/mastodon/components.scss | 59 ++++---- app/javascript/svg-icons/actual_size.svg | 4 + 9 files changed, 114 insertions(+), 145 deletions(-) create mode 100644 app/javascript/material-icons/400-24px/fit_screen-fill.svg create mode 100644 app/javascript/material-icons/400-24px/fit_screen.svg create mode 100644 app/javascript/svg-icons/actual_size.svg diff --git a/app/javascript/mastodon/components/modal_root.jsx b/app/javascript/mastodon/components/modal_root.jsx index fd13564af2cb44..e7fa5e6f9a797e 100644 --- a/app/javascript/mastodon/components/modal_root.jsx +++ b/app/javascript/mastodon/components/modal_root.jsx @@ -148,7 +148,7 @@ class ModalRoot extends PureComponent { return (
-
+
{children}
diff --git a/app/javascript/mastodon/features/ui/components/image_loader.jsx b/app/javascript/mastodon/features/ui/components/image_loader.jsx index 9dabc621b427e4..b1417deda77b34 100644 --- a/app/javascript/mastodon/features/ui/components/image_loader.jsx +++ b/app/javascript/mastodon/features/ui/components/image_loader.jsx @@ -17,7 +17,7 @@ export default class ImageLoader extends PureComponent { width: PropTypes.number, height: PropTypes.number, onClick: PropTypes.func, - zoomButtonHidden: PropTypes.bool, + zoomedIn: PropTypes.bool, }; static defaultProps = { @@ -134,7 +134,7 @@ export default class ImageLoader extends PureComponent { }; render () { - const { alt, lang, src, width, height, onClick } = this.props; + const { alt, lang, src, width, height, onClick, zoomedIn } = this.props; const { loading } = this.state; const className = classNames('image-loader', { @@ -149,6 +149,7 @@ export default class ImageLoader extends PureComponent {
+ )}
diff --git a/app/javascript/mastodon/features/ui/components/media_modal.jsx b/app/javascript/mastodon/features/ui/components/media_modal.jsx index 0f6e8a727bea3f..d69ceba5396740 100644 --- a/app/javascript/mastodon/features/ui/components/media_modal.jsx +++ b/app/javascript/mastodon/features/ui/components/media_modal.jsx @@ -12,6 +12,8 @@ import ReactSwipeableViews from 'react-swipeable-views'; import ChevronLeftIcon from '@/material-icons/400-24px/chevron_left.svg?react'; import ChevronRightIcon from '@/material-icons/400-24px/chevron_right.svg?react'; import CloseIcon from '@/material-icons/400-24px/close.svg?react'; +import FitScreenIcon from '@/material-icons/400-24px/fit_screen.svg?react'; +import ActualSizeIcon from '@/svg-icons/actual_size.svg?react'; import { getAverageFromBlurhash } from 'mastodon/blurhash'; import { GIFV } from 'mastodon/components/gifv'; import { Icon } from 'mastodon/components/icon'; @@ -26,6 +28,8 @@ const messages = defineMessages({ close: { id: 'lightbox.close', defaultMessage: 'Close' }, previous: { id: 'lightbox.previous', defaultMessage: 'Previous' }, next: { id: 'lightbox.next', defaultMessage: 'Next' }, + zoomIn: { id: 'lightbox.zoom_in', defaultMessage: 'Zoom to actual size' }, + zoomOut: { id: 'lightbox.zoom_out', defaultMessage: 'Zoom to fit' }, }); class MediaModal extends ImmutablePureComponent { @@ -46,30 +50,39 @@ class MediaModal extends ImmutablePureComponent { state = { index: null, navigationHidden: false, - zoomButtonHidden: false, + zoomedIn: false, + }; + + handleZoomClick = () => { + this.setState(prevState => ({ + zoomedIn: !prevState.zoomedIn, + })); }; handleSwipe = (index) => { - this.setState({ index: index % this.props.media.size }); + this.setState({ + index: index % this.props.media.size, + zoomedIn: false, + }); }; handleTransitionEnd = () => { this.setState({ - zoomButtonHidden: false, + zoomedIn: false, }); }; handleNextClick = () => { this.setState({ index: (this.getIndex() + 1) % this.props.media.size, - zoomButtonHidden: true, + zoomedIn: false, }); }; handlePrevClick = () => { this.setState({ index: (this.props.media.size + this.getIndex() - 1) % this.props.media.size, - zoomButtonHidden: true, + zoomedIn: false, }); }; @@ -78,7 +91,7 @@ class MediaModal extends ImmutablePureComponent { this.setState({ index: index % this.props.media.size, - zoomButtonHidden: true, + zoomedIn: false, }); }; @@ -130,15 +143,22 @@ class MediaModal extends ImmutablePureComponent { return this.state.index !== null ? this.state.index : this.props.index; } - toggleNavigation = () => { + handleToggleNavigation = () => { this.setState(prevState => ({ navigationHidden: !prevState.navigationHidden, })); }; + setRef = c => { + this.setState({ + viewportWidth: c?.clientWidth, + viewportHeight: c?.clientHeight, + }); + }; + render () { const { media, statusId, lang, intl, onClose } = this.props; - const { navigationHidden } = this.state; + const { navigationHidden, zoomedIn, viewportWidth, viewportHeight } = this.state; const index = this.getIndex(); @@ -160,8 +180,8 @@ class MediaModal extends ImmutablePureComponent { alt={description} lang={lang} key={image.get('url')} - onClick={this.toggleNavigation} - zoomButtonHidden={this.state.zoomButtonHidden} + onClick={this.handleToggleNavigation} + zoomedIn={zoomedIn} /> ); } else if (image.get('type') === 'video') { @@ -230,9 +250,12 @@ class MediaModal extends ImmutablePureComponent { )); } + const currentMedia = media.get(index); + const zoomable = currentMedia.get('type') === 'image' && (currentMedia.getIn(['meta', 'original', 'width']) > viewportWidth || currentMedia.getIn(['meta', 'original', 'height']) > viewportHeight); + return ( -
-
+
+
- +
+ {zoomable && } + +
{leftNav} {rightNav} diff --git a/app/javascript/mastodon/features/ui/components/zoomable_image.jsx b/app/javascript/mastodon/features/ui/components/zoomable_image.jsx index 272a3cff009186..c4129bf2609936 100644 --- a/app/javascript/mastodon/features/ui/components/zoomable_image.jsx +++ b/app/javascript/mastodon/features/ui/components/zoomable_image.jsx @@ -1,17 +1,6 @@ import PropTypes from 'prop-types'; import { PureComponent } from 'react'; -import { defineMessages, injectIntl } from 'react-intl'; - -import FullscreenExitIcon from '@/material-icons/400-24px/fullscreen_exit.svg?react'; -import RectangleIcon from '@/material-icons/400-24px/rectangle.svg?react'; -import { IconButton } from 'mastodon/components/icon_button'; - -const messages = defineMessages({ - compress: { id: 'lightbox.compress', defaultMessage: 'Compress image view box' }, - expand: { id: 'lightbox.expand', defaultMessage: 'Expand image view box' }, -}); - const MIN_SCALE = 1; const MAX_SCALE = 4; const NAV_BAR_HEIGHT = 66; @@ -104,8 +93,7 @@ class ZoomableImage extends PureComponent { width: PropTypes.number, height: PropTypes.number, onClick: PropTypes.func, - zoomButtonHidden: PropTypes.bool, - intl: PropTypes.object.isRequired, + zoomedIn: PropTypes.bool, }; static defaultProps = { @@ -131,8 +119,6 @@ class ZoomableImage extends PureComponent { translateX: null, translateY: null, }, - zoomState: 'expand', // 'expand' 'compress' - navigationHidden: false, dragPosition: { top: 0, left: 0, x: 0, y: 0 }, dragged: false, lockScroll: { x: 0, y: 0 }, @@ -169,35 +155,20 @@ class ZoomableImage extends PureComponent { this.container.addEventListener('DOMMouseScroll', handler); this.removers.push(() => this.container.removeEventListener('DOMMouseScroll', handler)); - this.initZoomMatrix(); + this._initZoomMatrix(); } componentWillUnmount () { - this.removeEventListeners(); + this._removeEventListeners(); } - componentDidUpdate () { - this.setState({ zoomState: this.state.scale >= this.state.zoomMatrix.rate ? 'compress' : 'expand' }); - - if (this.state.scale === MIN_SCALE) { - this.container.style.removeProperty('cursor'); + componentDidUpdate (prevProps) { + if (prevProps.zoomedIn !== this.props.zoomedIn) { + this._toggleZoom(); } } - UNSAFE_componentWillReceiveProps () { - // reset when slide to next image - if (this.props.zoomButtonHidden) { - this.setState({ - scale: MIN_SCALE, - lockTranslate: { x: 0, y: 0 }, - }, () => { - this.container.scrollLeft = 0; - this.container.scrollTop = 0; - }); - } - } - - removeEventListeners () { + _removeEventListeners () { this.removers.forEach(listeners => listeners()); this.removers = []; } @@ -220,9 +191,6 @@ class ZoomableImage extends PureComponent { }; mouseDownHandler = e => { - this.container.style.cursor = 'grabbing'; - this.container.style.userSelect = 'none'; - this.setState({ dragPosition: { left: this.container.scrollLeft, top: this.container.scrollTop, @@ -246,9 +214,6 @@ class ZoomableImage extends PureComponent { }; mouseUpHandler = () => { - this.container.style.cursor = 'grab'; - this.container.style.removeProperty('user-select'); - this.image.removeEventListener('mousemove', this.mouseMoveHandler); this.image.removeEventListener('mouseup', this.mouseUpHandler); }; @@ -276,13 +241,13 @@ class ZoomableImage extends PureComponent { const _MAX_SCALE = Math.max(MAX_SCALE, this.state.zoomMatrix.rate); const scale = clamp(MIN_SCALE, _MAX_SCALE, this.state.scale * distance / this.lastDistance); - this.zoom(scale, midpoint); + this._zoom(scale, midpoint); this.lastMidpoint = midpoint; this.lastDistance = distance; }; - zoom(nextScale, midpoint) { + _zoom(nextScale, midpoint) { const { scale, zoomMatrix } = this.state; const { scrollLeft, scrollTop } = this.container; @@ -318,14 +283,13 @@ class ZoomableImage extends PureComponent { if (dragged) return; const handler = this.props.onClick; if (handler) handler(); - this.setState({ navigationHidden: !this.state.navigationHidden }); }; handleMouseDown = e => { e.preventDefault(); }; - initZoomMatrix = () => { + _initZoomMatrix = () => { const { width, height } = this.props; const { clientWidth, clientHeight } = this.container; const { offsetWidth, offsetHeight } = this.image; @@ -357,10 +321,7 @@ class ZoomableImage extends PureComponent { }); }; - handleZoomClick = e => { - e.preventDefault(); - e.stopPropagation(); - + _toggleZoom () { const { scale, zoomMatrix } = this.state; if ( scale >= zoomMatrix.rate ) { @@ -394,10 +355,7 @@ class ZoomableImage extends PureComponent { this.container.scrollTop = zoomMatrix.scrollTop; }); } - - this.container.style.cursor = 'grab'; - this.container.style.removeProperty('user-select'); - }; + } setContainerRef = c => { this.container = c; @@ -408,52 +366,37 @@ class ZoomableImage extends PureComponent { }; render () { - const { alt, lang, src, width, height, intl } = this.props; - const { scale, lockTranslate } = this.state; + const { alt, lang, src, width, height } = this.props; + const { scale, lockTranslate, dragged } = this.state; const overflow = scale === MIN_SCALE ? 'hidden' : 'scroll'; - const zoomButtonShouldHide = this.state.navigationHidden || this.props.zoomButtonHidden || this.state.zoomMatrix.rate <= MIN_SCALE ? 'media-modal__zoom-button--hidden' : ''; - const zoomButtonTitle = this.state.zoomState === 'compress' ? intl.formatMessage(messages.compress) : intl.formatMessage(messages.expand); + const cursor = scale === MIN_SCALE ? null : (dragged ? 'grabbing' : 'grab'); return ( - <> - + {alt} -
- {alt} -
- +
); } - } -export default injectIntl(ZoomableImage); +export default ZoomableImage; diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json index 7f2e88d44019e4..d0563bb1b2c5cd 100644 --- a/app/javascript/mastodon/locales/en.json +++ b/app/javascript/mastodon/locales/en.json @@ -432,10 +432,10 @@ "keyboard_shortcuts.unfocus": "Unfocus compose textarea/search", "keyboard_shortcuts.up": "Move up in the list", "lightbox.close": "Close", - "lightbox.compress": "Compress image view box", - "lightbox.expand": "Expand image view box", "lightbox.next": "Next", "lightbox.previous": "Previous", + "lightbox.zoom_in": "Zoom to actual size", + "lightbox.zoom_out": "Zoom to fit", "limited_account_hint.action": "Show profile anyway", "limited_account_hint.title": "This profile has been hidden by the moderators of {domain}.", "link_preview.author": "By {name}", diff --git a/app/javascript/material-icons/400-24px/fit_screen-fill.svg b/app/javascript/material-icons/400-24px/fit_screen-fill.svg new file mode 100644 index 00000000000000..a2ed8ca581afdf --- /dev/null +++ b/app/javascript/material-icons/400-24px/fit_screen-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/fit_screen.svg b/app/javascript/material-icons/400-24px/fit_screen.svg new file mode 100644 index 00000000000000..d8d06f6e8b3337 --- /dev/null +++ b/app/javascript/material-icons/400-24px/fit_screen.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index 495481622a73a5..53becb3b925bc8 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -5764,19 +5764,34 @@ a.status-card { height: 100%; position: relative; - &__close, - &__zoom-button { - color: rgba($white, 0.7); + &__buttons { + position: absolute; + inset-inline-end: 8px; + top: 8px; + z-index: 100; + display: flex; + gap: 8px; + align-items: center; - &:hover, - &:focus, - &:active { - color: $white; - background-color: rgba($white, 0.15); - } + .icon-button { + color: rgba($white, 0.7); + padding: 8px; - &:focus { - background-color: rgba($white, 0.3); + .icon { + width: 24px; + height: 24px; + } + + &:hover, + &:focus, + &:active { + color: $white; + background-color: rgba($white, 0.15); + } + + &:focus { + background-color: rgba($white, 0.3); + } } } } @@ -5937,28 +5952,6 @@ a.status-card { } } -.media-modal__close { - position: absolute; - inset-inline-end: 8px; - top: 8px; - z-index: 100; -} - -.media-modal__zoom-button { - position: absolute; - inset-inline-end: 64px; - top: 8px; - z-index: 100; - pointer-events: auto; - transition: opacity 0.3s linear; - will-change: opacity; -} - -.media-modal__zoom-button--hidden { - pointer-events: none; - opacity: 0; -} - .onboarding-modal, .error-modal, .embed-modal { diff --git a/app/javascript/svg-icons/actual_size.svg b/app/javascript/svg-icons/actual_size.svg new file mode 100644 index 00000000000000..75939cac875144 --- /dev/null +++ b/app/javascript/svg-icons/actual_size.svg @@ -0,0 +1,4 @@ + + + + From 8afa3bb2fa872c2413cd264ca3321a7509a5ffdf Mon Sep 17 00:00:00 2001 From: Claire Date: Fri, 20 Sep 2024 12:10:09 +0200 Subject: [PATCH 37/51] Change Mastodon to issue correctly-signed queries by default (#31994) --- app/lib/request.rb | 2 +- app/services/activitypub/fetch_replies_service.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/lib/request.rb b/app/lib/request.rb index ab42e823009047..d7da9fe63c1303 100644 --- a/app/lib/request.rb +++ b/app/lib/request.rb @@ -77,7 +77,7 @@ def initialize(verb, url, **options) @url = Addressable::URI.parse(url).normalize @http_client = options.delete(:http_client) @allow_local = options.delete(:allow_local) - @full_path = options.delete(:with_query_string) + @full_path = !options.delete(:omit_query_string) @options = options.merge(socket_class: use_proxy? || @allow_local ? ProxySocket : Socket) @options = @options.merge(timeout_class: PerOperationWithDeadline, timeout_options: TIMEOUT) @options = @options.merge(proxy_url) if use_proxy? diff --git a/app/services/activitypub/fetch_replies_service.rb b/app/services/activitypub/fetch_replies_service.rb index e2ecdef1659990..46cab6caf93dc3 100644 --- a/app/services/activitypub/fetch_replies_service.rb +++ b/app/services/activitypub/fetch_replies_service.rb @@ -49,7 +49,7 @@ def fetch_collection(collection_or_uri) rescue Mastodon::UnexpectedResponseError => e raise unless e.response && e.response.code == 401 && Addressable::URI.parse(collection_or_uri).query.present? - fetch_resource_without_id_validation(collection_or_uri, nil, true, request_options: { with_query_string: true }) + fetch_resource_without_id_validation(collection_or_uri, nil, true, request_options: { omit_query_string: false }) end end From bdf83c353f8ff8c9794a11a4133f74129fac8670 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Fri, 20 Sep 2024 08:39:48 -0400 Subject: [PATCH 38/51] Move default embed size knowledge into `OEmbedSerializer` (#31990) Co-authored-by: Claire --- app/controllers/api/oembed_controller.rb | 10 +--------- app/controllers/api/web/embeds_controller.rb | 2 +- app/serializers/oembed_serializer.rb | 6 ++++-- 3 files changed, 6 insertions(+), 12 deletions(-) diff --git a/app/controllers/api/oembed_controller.rb b/app/controllers/api/oembed_controller.rb index 66da65bedaf741..b7f22824a7afbf 100644 --- a/app/controllers/api/oembed_controller.rb +++ b/app/controllers/api/oembed_controller.rb @@ -7,7 +7,7 @@ class Api::OEmbedController < Api::BaseController before_action :require_public_status! def show - render json: @status, serializer: OEmbedSerializer, width: maxwidth_or_default, height: maxheight_or_default + render json: @status, serializer: OEmbedSerializer, width: params[:maxwidth], height: params[:maxheight] end private @@ -23,12 +23,4 @@ def require_public_status! def status_finder StatusFinder.new(params[:url]) end - - def maxwidth_or_default - (params[:maxwidth].presence || 400).to_i - end - - def maxheight_or_default - params[:maxheight].present? ? params[:maxheight].to_i : nil - end end diff --git a/app/controllers/api/web/embeds_controller.rb b/app/controllers/api/web/embeds_controller.rb index 63c3f2d90a79c6..f82c1c50d79502 100644 --- a/app/controllers/api/web/embeds_controller.rb +++ b/app/controllers/api/web/embeds_controller.rb @@ -9,7 +9,7 @@ def show return not_found if @status.hidden? if @status.local? - render json: @status, serializer: OEmbedSerializer, width: 400 + render json: @status, serializer: OEmbedSerializer else return not_found unless user_signed_in? diff --git a/app/serializers/oembed_serializer.rb b/app/serializers/oembed_serializer.rb index 19fa5ddec7856b..a693a961f49eb4 100644 --- a/app/serializers/oembed_serializer.rb +++ b/app/serializers/oembed_serializer.rb @@ -8,6 +8,8 @@ class OEmbedSerializer < ActiveModel::Serializer div1: 'font-weight: 500;', }.freeze + DEFAULT_WIDTH = 400 + include RoutingHelper include ActionView::Helpers::TagHelper @@ -57,10 +59,10 @@ def html end def width - instance_options[:width] + (instance_options[:width] || DEFAULT_WIDTH).to_i end def height - instance_options[:height] + instance_options[:height].presence&.to_i end end From a7dbf6f5a5f3bddbc40d8b4d9067bec0a7025169 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Fri, 20 Sep 2024 08:50:51 -0400 Subject: [PATCH 39/51] Use heredoc/squish for inline css styles in oembed serializer (#31991) --- app/serializers/oembed_serializer.rb | 40 +++++++++++++++++++++++----- 1 file changed, 33 insertions(+), 7 deletions(-) diff --git a/app/serializers/oembed_serializer.rb b/app/serializers/oembed_serializer.rb index a693a961f49eb4..c87f14f26b72df 100644 --- a/app/serializers/oembed_serializer.rb +++ b/app/serializers/oembed_serializer.rb @@ -2,10 +2,36 @@ class OEmbedSerializer < ActiveModel::Serializer INLINE_STYLES = { - blockquote: 'max-width: 540px; min-width: 270px; background:#FCF8FF; border: 1px solid #C9C4DA; border-radius: 8px; overflow: hidden; margin: 0; padding: 0;', - a: "color: #1C1A25; text-decoration: none; display: flex; align-items: center; justify-content: center; flex-direction: column; padding: 24px; font-size: 14px; line-height: 20px; letter-spacing: 0.25px; font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Oxygen, Ubuntu, Cantarell, 'Fira Sans', 'Droid Sans', 'Helvetica Neue', Roboto, sans-serif;", # rubocop:disable Layout/LineLength - div0: 'margin-top: 16px; color: #787588;', - div1: 'font-weight: 500;', + blockquote: <<~CSS.squish, + background: #FCF8FF; + border-radius: 8px; + border: 1px solid #C9C4DA; + margin: 0; + max-width: 540px; + min-width: 270px; + overflow: hidden; + padding: 0; + CSS + status_link: <<~CSS.squish, + align-items: center; + color: #1C1A25; + display: flex; + flex-direction: column; + font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Oxygen, Ubuntu, Cantarell, 'Fira Sans', 'Droid Sans', 'Helvetica Neue', Roboto, sans-serif; + font-size: 14px; + justify-content: center; + letter-spacing: 0.25px; + line-height: 20px; + padding: 24px; + text-decoration: none; + CSS + div_account: <<~CSS.squish, + color: #787588; + margin-top: 16px; + CSS + div_view: <<~CSS.squish, + font-weight: 500; + CSS }.freeze DEFAULT_WIDTH = 400 @@ -48,10 +74,10 @@ def cache_age def html <<~HTML.squish
- + -
Post by @#{object.account.pretty_acct}@#{provider_name}
-
View on Mastodon
+
Post by @#{object.account.pretty_acct}@#{provider_name}
+
View on Mastodon
From 66326065b096fdfa4f8589747dc1717be03b5626 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Fri, 20 Sep 2024 09:13:04 -0400 Subject: [PATCH 40/51] Add `response.content_type` checks for JSON to `api/v1` request specs (#31981) --- spec/requests/api/oembed_spec.rb | 4 ++ .../api/v1/accounts/credentials_spec.rb | 10 +++++ .../v1/accounts/familiar_followers_spec.rb | 2 + .../api/v1/accounts/featured_tags_spec.rb | 4 ++ .../api/v1/accounts/follower_accounts_spec.rb | 4 ++ .../v1/accounts/following_accounts_spec.rb | 4 ++ .../api/v1/accounts/identity_proofs_spec.rb | 2 + spec/requests/api/v1/accounts/lists_spec.rb | 2 + spec/requests/api/v1/accounts/lookup_spec.rb | 2 + spec/requests/api/v1/accounts/notes_spec.rb | 4 ++ spec/requests/api/v1/accounts/pins_spec.rb | 4 ++ .../api/v1/accounts/relationships_spec.rb | 10 +++++ spec/requests/api/v1/accounts/search_spec.rb | 2 + .../requests/api/v1/accounts/statuses_spec.rb | 14 +++++++ spec/requests/api/v1/accounts_spec.rb | 32 ++++++++++++++ .../api/v1/admin/account_actions_spec.rb | 14 +++++++ spec/requests/api/v1/admin/accounts_spec.rb | 42 +++++++++++++++++++ .../v1/admin/canonical_email_blocks_spec.rb | 26 ++++++++++++ spec/requests/api/v1/admin/dimensions_spec.rb | 5 +++ .../api/v1/admin/domain_allows_spec.rb | 16 +++++++ .../api/v1/admin/domain_blocks_spec.rb | 24 +++++++++++ .../api/v1/admin/email_domain_blocks_spec.rb | 18 ++++++++ spec/requests/api/v1/admin/ip_blocks_spec.rb | 24 +++++++++++ spec/requests/api/v1/admin/measures_spec.rb | 4 ++ spec/requests/api/v1/admin/reports_spec.rb | 14 +++++++ spec/requests/api/v1/admin/retention_spec.rb | 4 ++ spec/requests/api/v1/admin/tags_spec.rb | 12 ++++++ .../api/v1/admin/trends/links/links_spec.rb | 14 +++++++ .../links/preview_card_providers_spec.rb | 6 +++ .../api/v1/admin/trends/statuses_spec.rb | 6 +++ .../requests/api/v1/admin/trends/tags_spec.rb | 6 +++ .../api/v1/announcements/reactions_spec.rb | 12 +++++- spec/requests/api/v1/announcements_spec.rb | 12 +++++- spec/requests/api/v1/annual_reports_spec.rb | 6 +++ spec/requests/api/v1/apps/credentials_spec.rb | 12 ++++++ spec/requests/api/v1/apps_spec.rb | 32 ++++++++++++++ spec/requests/api/v1/blocks_spec.rb | 4 ++ spec/requests/api/v1/bookmarks_spec.rb | 6 +++ spec/requests/api/v1/conversations_spec.rb | 2 + spec/requests/api/v1/custom_emojis_spec.rb | 4 ++ spec/requests/api/v1/directories_spec.rb | 8 ++++ spec/requests/api/v1/domain_blocks_spec.rb | 12 ++++++ .../api/v1/emails/confirmations_spec.rb | 24 +++++++++++ spec/requests/api/v1/endorsements_spec.rb | 6 +++ spec/requests/api/v1/favourites_spec.rb | 6 +++ .../api/v1/featured_tags/suggestions_spec.rb | 2 + spec/requests/api/v1/featured_tags_spec.rb | 22 ++++++++++ spec/requests/api/v1/filters_spec.rb | 12 ++++++ spec/requests/api/v1/follow_requests_spec.rb | 6 +++ spec/requests/api/v1/followed_tags_spec.rb | 10 ++--- spec/requests/api/v1/instance_spec.rb | 4 ++ .../api/v1/instances/activity_spec.rb | 3 ++ .../api/v1/instances/domain_blocks_spec.rb | 3 ++ .../instances/extended_descriptions_spec.rb | 2 + .../api/v1/instances/languages_spec.rb | 2 + spec/requests/api/v1/instances/peers_spec.rb | 2 + .../api/v1/instances/privacy_policies_spec.rb | 2 + spec/requests/api/v1/instances/rules_spec.rb | 2 + .../instances/translation_languages_spec.rb | 4 ++ spec/requests/api/v1/lists/accounts_spec.rb | 18 ++++++++ spec/requests/api/v1/lists_spec.rb | 24 +++++++++++ spec/requests/api/v1/markers_spec.rb | 8 ++++ spec/requests/api/v1/media_spec.rb | 18 ++++++++ spec/requests/api/v1/mutes_spec.rb | 6 +++ .../api/v1/notifications/policies_spec.rb | 4 ++ .../api/v1/notifications/requests_spec.rb | 18 ++++++++ spec/requests/api/v1/notifications_spec.rb | 32 ++++++++++++++ spec/requests/api/v1/peers/search_spec.rb | 6 +++ spec/requests/api/v1/polls/votes_spec.rb | 4 ++ spec/requests/api/v1/polls_spec.rb | 4 ++ spec/requests/api/v1/preferences_spec.rb | 5 +++ spec/requests/api/v1/profiles_spec.rb | 4 ++ .../api/v1/push/subscriptions_spec.rb | 2 + spec/requests/api/v1/reports_spec.rb | 4 ++ spec/requests/api/v1/scheduled_status_spec.rb | 8 ++++ .../api/v1/statuses/bookmarks_spec.rb | 18 ++++++++ .../statuses/favourited_by_accounts_spec.rb | 6 +++ .../api/v1/statuses/favourites_spec.rb | 16 +++++++ .../api/v1/statuses/histories_spec.rb | 2 + spec/requests/api/v1/statuses/mutes_spec.rb | 4 ++ spec/requests/api/v1/statuses/pins_spec.rb | 18 ++++++++ .../v1/statuses/reblogged_by_accounts_spec.rb | 6 +++ spec/requests/api/v1/statuses/reblogs_spec.rb | 10 +++++ spec/requests/api/v1/statuses/sources_spec.rb | 8 ++++ .../api/v1/statuses/translations_spec.rb | 4 ++ spec/requests/api/v1/statuses_spec.rb | 38 +++++++++++++++++ spec/requests/api/v1/streaming_spec.rb | 2 + spec/requests/api/v1/suggestions_spec.rb | 6 +++ spec/requests/api/v1/tags_spec.rb | 20 +++++++++ spec/requests/api/v1/timelines/home_spec.rb | 10 +++++ spec/requests/api/v1/timelines/link_spec.rb | 14 +++++++ spec/requests/api/v1/timelines/list_spec.rb | 4 ++ spec/requests/api/v1/timelines/public_spec.rb | 14 ++++--- spec/requests/api/v1/timelines/tag_spec.rb | 6 +++ spec/requests/api/v1/trends/links_spec.rb | 4 ++ spec/requests/api/v1/trends/statuses_spec.rb | 4 ++ spec/requests/api/v1/trends/tags_spec.rb | 4 ++ spec/requests/api/web/settings_spec.rb | 4 ++ 98 files changed, 930 insertions(+), 14 deletions(-) diff --git a/spec/requests/api/oembed_spec.rb b/spec/requests/api/oembed_spec.rb index b9578b37c817df..767f20dddf4837 100644 --- a/spec/requests/api/oembed_spec.rb +++ b/spec/requests/api/oembed_spec.rb @@ -14,6 +14,8 @@ expect(response) .to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.headers['Cache-Control']) .to include('private, no-store') end @@ -27,6 +29,8 @@ expect(response) .to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end diff --git a/spec/requests/api/v1/accounts/credentials_spec.rb b/spec/requests/api/v1/accounts/credentials_spec.rb index cc82e1892e2e69..0bd3ace1325eab 100644 --- a/spec/requests/api/v1/accounts/credentials_spec.rb +++ b/spec/requests/api/v1/accounts/credentials_spec.rb @@ -20,6 +20,8 @@ expect(response) .to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body).to include({ source: hash_including({ discoverable: false, @@ -36,6 +38,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body).to include({ locked: true, @@ -75,6 +79,8 @@ it 'returns http success' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end @@ -84,6 +90,8 @@ it 'returns http unprocessable entity' do subject expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') end end @@ -92,6 +100,8 @@ expect(response) .to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body).to include({ source: hash_including({ diff --git a/spec/requests/api/v1/accounts/familiar_followers_spec.rb b/spec/requests/api/v1/accounts/familiar_followers_spec.rb index 8edfa4c8831f60..c698c2d6892a72 100644 --- a/spec/requests/api/v1/accounts/familiar_followers_spec.rb +++ b/spec/requests/api/v1/accounts/familiar_followers_spec.rb @@ -14,6 +14,8 @@ get '/api/v1/accounts/familiar_followers', params: { account_id: account.id, limit: 2 }, headers: headers expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end context 'when there are duplicate account IDs in the params' do diff --git a/spec/requests/api/v1/accounts/featured_tags_spec.rb b/spec/requests/api/v1/accounts/featured_tags_spec.rb index f48ed01def00be..632db65c443fd8 100644 --- a/spec/requests/api/v1/accounts/featured_tags_spec.rb +++ b/spec/requests/api/v1/accounts/featured_tags_spec.rb @@ -23,6 +23,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body).to contain_exactly(a_hash_including({ name: 'bar', url: "https://cb6e6126.ngrok.io/@#{account.username}/tagged/bar", @@ -37,6 +39,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body).to contain_exactly(a_hash_including({ name: 'bar', url: "https://cb6e6126.ngrok.io/@#{account.pretty_acct}/tagged/bar", diff --git a/spec/requests/api/v1/accounts/follower_accounts_spec.rb b/spec/requests/api/v1/accounts/follower_accounts_spec.rb index 2672615390a36e..7db9884a57a8f7 100644 --- a/spec/requests/api/v1/accounts/follower_accounts_spec.rb +++ b/spec/requests/api/v1/accounts/follower_accounts_spec.rb @@ -21,6 +21,8 @@ get "/api/v1/accounts/#{account.id}/followers", params: { limit: 2 }, headers: headers expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body.size).to eq 2 expect([response.parsed_body[0][:id], response.parsed_body[1][:id]]).to contain_exactly(alice.id.to_s, bob.id.to_s) end @@ -30,6 +32,8 @@ get "/api/v1/accounts/#{account.id}/followers", params: { limit: 2 }, headers: headers expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body.size).to eq 1 expect(response.parsed_body[0][:id]).to eq alice.id.to_s end diff --git a/spec/requests/api/v1/accounts/following_accounts_spec.rb b/spec/requests/api/v1/accounts/following_accounts_spec.rb index 19105ebf2f3e0b..ffb7332c4eb7b9 100644 --- a/spec/requests/api/v1/accounts/following_accounts_spec.rb +++ b/spec/requests/api/v1/accounts/following_accounts_spec.rb @@ -21,6 +21,8 @@ get "/api/v1/accounts/#{account.id}/following", params: { limit: 2 }, headers: headers expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body.size).to eq 2 expect([response.parsed_body[0][:id], response.parsed_body[1][:id]]).to contain_exactly(alice.id.to_s, bob.id.to_s) end @@ -30,6 +32,8 @@ get "/api/v1/accounts/#{account.id}/following", params: { limit: 2 }, headers: headers expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body.size).to eq 1 expect(response.parsed_body[0][:id]).to eq alice.id.to_s end diff --git a/spec/requests/api/v1/accounts/identity_proofs_spec.rb b/spec/requests/api/v1/accounts/identity_proofs_spec.rb index d1d9db8e737973..ba04ed45b9f8b5 100644 --- a/spec/requests/api/v1/accounts/identity_proofs_spec.rb +++ b/spec/requests/api/v1/accounts/identity_proofs_spec.rb @@ -14,6 +14,8 @@ get "/api/v1/accounts/#{account.id}/identity_proofs", params: { limit: 2 }, headers: headers expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end end diff --git a/spec/requests/api/v1/accounts/lists_spec.rb b/spec/requests/api/v1/accounts/lists_spec.rb index 8b04f07f652519..cb1ff6b9f28a7c 100644 --- a/spec/requests/api/v1/accounts/lists_spec.rb +++ b/spec/requests/api/v1/accounts/lists_spec.rb @@ -20,6 +20,8 @@ get "/api/v1/accounts/#{account.id}/lists", headers: headers expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end end diff --git a/spec/requests/api/v1/accounts/lookup_spec.rb b/spec/requests/api/v1/accounts/lookup_spec.rb index dfd9fad49d8a96..77c09c0902b502 100644 --- a/spec/requests/api/v1/accounts/lookup_spec.rb +++ b/spec/requests/api/v1/accounts/lookup_spec.rb @@ -14,6 +14,8 @@ get '/api/v1/accounts/lookup', params: { account_id: account.id, acct: account.acct }, headers: headers expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end end diff --git a/spec/requests/api/v1/accounts/notes_spec.rb b/spec/requests/api/v1/accounts/notes_spec.rb index b8c493abcc96d0..1677ec07e3d6d6 100644 --- a/spec/requests/api/v1/accounts/notes_spec.rb +++ b/spec/requests/api/v1/accounts/notes_spec.rb @@ -22,6 +22,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(AccountNote.find_by(account_id: user.account.id, target_account_id: account.id).comment).to eq comment end end @@ -33,6 +35,8 @@ subject expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') expect(AccountNote.where(account_id: user.account.id, target_account_id: account.id)).to_not exist end end diff --git a/spec/requests/api/v1/accounts/pins_spec.rb b/spec/requests/api/v1/accounts/pins_spec.rb index c66b80c7fd98d5..8ebcb27d282e01 100644 --- a/spec/requests/api/v1/accounts/pins_spec.rb +++ b/spec/requests/api/v1/accounts/pins_spec.rb @@ -21,6 +21,8 @@ subject end.to change { AccountPin.where(account: user.account, target_account: kevin.account).count }.by(1) expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end @@ -36,6 +38,8 @@ subject end.to change { AccountPin.where(account: user.account, target_account: kevin.account).count }.by(-1) expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end end diff --git a/spec/requests/api/v1/accounts/relationships_spec.rb b/spec/requests/api/v1/accounts/relationships_spec.rb index 9570d1214c6d04..52aeb0132807ad 100644 --- a/spec/requests/api/v1/accounts/relationships_spec.rb +++ b/spec/requests/api/v1/accounts/relationships_spec.rb @@ -29,6 +29,8 @@ expect(response) .to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body) .to be_an(Enumerable) .and contain_exactly( @@ -50,6 +52,8 @@ expect(response) .to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body) .to be_an(Enumerable) .and have_attributes( @@ -70,6 +74,8 @@ expect(response) .to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body) .to be_an(Enumerable) .and have_attributes( @@ -149,6 +155,8 @@ def bob_item expect(response) .to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body) .to be_an(Enumerable) @@ -171,6 +179,8 @@ def bob_item expect(response) .to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body) .to be_an(Enumerable) diff --git a/spec/requests/api/v1/accounts/search_spec.rb b/spec/requests/api/v1/accounts/search_spec.rb index f6ab7a85319da1..dc24813e7392ad 100644 --- a/spec/requests/api/v1/accounts/search_spec.rb +++ b/spec/requests/api/v1/accounts/search_spec.rb @@ -13,6 +13,8 @@ get '/api/v1/accounts/search', params: { q: 'query' }, headers: headers expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end end diff --git a/spec/requests/api/v1/accounts/statuses_spec.rb b/spec/requests/api/v1/accounts/statuses_spec.rb index e056a7890158fe..1e219502874352 100644 --- a/spec/requests/api/v1/accounts/statuses_spec.rb +++ b/spec/requests/api/v1/accounts/statuses_spec.rb @@ -19,6 +19,8 @@ prev: api_v1_account_statuses_url(limit: 1, min_id: status.id), next: api_v1_account_statuses_url(limit: 1, max_id: status.id) ) + expect(response.content_type) + .to start_with('application/json') end context 'with only media' do @@ -26,6 +28,8 @@ get "/api/v1/accounts/#{user.account.id}/statuses", params: { only_media: true }, headers: headers expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end @@ -41,6 +45,8 @@ it 'returns posts along with self replies', :aggregate_failures do expect(response) .to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body) .to have_attributes(size: 2) .and contain_exactly( @@ -61,6 +67,8 @@ expect(response) .to have_http_status(200) .and include_pagination_headers(prev: api_v1_account_statuses_url(pinned: true, min_id: Status.first.id)) + expect(response.content_type) + .to start_with('application/json') end end @@ -79,6 +87,8 @@ prev: api_v1_account_statuses_url(pinned: true, min_id: Status.first.id), next: api_v1_account_statuses_url(pinned: true, max_id: Status.first.id) ) + expect(response.content_type) + .to start_with('application/json') end end @@ -96,6 +106,8 @@ get "/api/v1/accounts/#{account.id}/statuses", params: { pinned: true }, headers: headers expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end context 'when user does not follow account' do @@ -122,6 +134,8 @@ a_hash_including(id: status.id.to_s), a_hash_including(id: private_status.id.to_s) ) + expect(response.content_type) + .to start_with('application/json') end end end diff --git a/spec/requests/api/v1/accounts_spec.rb b/spec/requests/api/v1/accounts_spec.rb index 2ebe56fa7d0281..45e66f07445d8d 100644 --- a/spec/requests/api/v1/accounts_spec.rb +++ b/spec/requests/api/v1/accounts_spec.rb @@ -17,6 +17,8 @@ get '/api/v1/accounts', headers: headers, params: { id: [account.id, other_account.id, 123_123] } expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body).to contain_exactly( hash_including(id: account.id.to_s), hash_including(id: other_account.id.to_s) @@ -32,6 +34,8 @@ get "/api/v1/accounts/#{account.id}" expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body[:id]).to eq(account.id.to_s) end end @@ -41,6 +45,8 @@ get '/api/v1/accounts/1' expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body[:error]).to eq('Record not found') end end @@ -57,6 +63,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body[:id]).to eq(account.id.to_s) end @@ -80,6 +88,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body[:access_token]).to_not be_blank user = User.find_by(email: 'hello@world.tld') @@ -93,6 +103,8 @@ subject expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') end end end @@ -113,6 +125,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body) .to include( @@ -133,6 +147,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body) .to include( @@ -203,6 +219,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(user.account.following?(other_account)).to be false end @@ -225,6 +243,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(user.account.followed_by?(other_account)).to be false end @@ -247,6 +267,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(user.account.following?(other_account)).to be false expect(user.account.blocking?(other_account)).to be true end @@ -270,6 +292,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(user.account.blocking?(other_account)).to be false end @@ -292,6 +316,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(user.account.following?(other_account)).to be true expect(user.account.muting?(other_account)).to be true expect(user.account.muting_notifications?(other_account)).to be true @@ -316,6 +342,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(user.account.following?(other_account)).to be true expect(user.account.muting?(other_account)).to be true expect(user.account.muting_notifications?(other_account)).to be false @@ -340,6 +368,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(user.account.following?(other_account)).to be true expect(user.account.muting?(other_account)).to be true expect(user.account.muting_notifications?(other_account)).to be true @@ -364,6 +394,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(user.account.muting?(other_account)).to be false end diff --git a/spec/requests/api/v1/admin/account_actions_spec.rb b/spec/requests/api/v1/admin/account_actions_spec.rb index 5bcf809401da4c..4884dba9c79dfc 100644 --- a/spec/requests/api/v1/admin/account_actions_spec.rb +++ b/spec/requests/api/v1/admin/account_actions_spec.rb @@ -61,6 +61,8 @@ def latest_admin_action_log it 'disables the target account' do expect { subject }.to change { target_account.reload.user_disabled? }.from(false).to(true) expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end @@ -75,6 +77,8 @@ def latest_admin_action_log it 'marks the target account as sensitive' do expect { subject }.to change { target_account.reload.sensitized? }.from(false).to(true) expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end @@ -89,6 +93,8 @@ def latest_admin_action_log it 'marks the target account as silenced' do expect { subject }.to change { target_account.reload.silenced? }.from(false).to(true) expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end @@ -103,6 +109,8 @@ def latest_admin_action_log it 'marks the target account as suspended' do expect { subject }.to change { target_account.reload.suspended? }.from(false).to(true) expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end @@ -115,6 +123,8 @@ def latest_admin_action_log subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end @@ -125,6 +135,8 @@ def latest_admin_action_log subject expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') end end @@ -135,6 +147,8 @@ def latest_admin_action_log subject expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') end end end diff --git a/spec/requests/api/v1/admin/accounts_spec.rb b/spec/requests/api/v1/admin/accounts_spec.rb index f557b61403af4c..6a681f9c5e5b2a 100644 --- a/spec/requests/api/v1/admin/accounts_spec.rb +++ b/spec/requests/api/v1/admin/accounts_spec.rb @@ -19,6 +19,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body.pluck(:id)).to match_array(expected_results.map { |a| a.id.to_s }) end end @@ -93,6 +95,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body.size).to eq(params[:limit]) end end @@ -112,6 +116,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body).to match( a_hash_including(id: account.id.to_s, username: account.username, email: account.user.email) ) @@ -122,6 +128,8 @@ get '/api/v1/admin/accounts/-1', headers: headers expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end @@ -145,6 +153,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(account.reload.user_approved?).to be(true) end @@ -166,6 +176,8 @@ subject expect(response).to have_http_status(403) + expect(response.content_type) + .to start_with('application/json') end end @@ -174,6 +186,8 @@ post '/api/v1/admin/accounts/-1/approve', headers: headers expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end @@ -197,6 +211,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(User.where(id: account.user.id)).to_not exist expect(latest_admin_action_log) @@ -214,6 +230,8 @@ subject expect(response).to have_http_status(403) + expect(response.content_type) + .to start_with('application/json') end end @@ -222,6 +240,8 @@ post '/api/v1/admin/accounts/-1/reject', headers: headers expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end @@ -244,6 +264,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(account.reload.user_disabled?).to be false end @@ -252,6 +274,8 @@ post '/api/v1/admin/accounts/-1/enable', headers: headers expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end @@ -275,6 +299,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(account.reload.suspended?).to be false end end @@ -284,6 +310,8 @@ subject expect(response).to have_http_status(403) + expect(response.content_type) + .to start_with('application/json') end end @@ -292,6 +320,8 @@ post '/api/v1/admin/accounts/-1/unsuspend', headers: headers expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end @@ -314,6 +344,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(account.reload.sensitized?).to be false end @@ -322,6 +354,8 @@ post '/api/v1/admin/accounts/-1/unsensitive', headers: headers expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end @@ -344,6 +378,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(account.reload.silenced?).to be false end @@ -352,6 +388,8 @@ post '/api/v1/admin/accounts/-1/unsilence', headers: headers expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end @@ -376,6 +414,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(Admin::AccountDeletionWorker).to have_received(:perform_async).with(account.id).once end end @@ -393,6 +433,8 @@ delete '/api/v1/admin/accounts/-1', headers: headers expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end diff --git a/spec/requests/api/v1/admin/canonical_email_blocks_spec.rb b/spec/requests/api/v1/admin/canonical_email_blocks_spec.rb index dd7e119911cbe9..eaa011d5167716 100644 --- a/spec/requests/api/v1/admin/canonical_email_blocks_spec.rb +++ b/spec/requests/api/v1/admin/canonical_email_blocks_spec.rb @@ -24,6 +24,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end context 'when there is no canonical email block' do @@ -96,6 +98,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body) .to include( id: eq(canonical_email_block.id.to_s), @@ -109,6 +113,8 @@ get '/api/v1/admin/canonical_email_blocks/-1', headers: headers expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end @@ -131,6 +137,8 @@ subject expect(response).to have_http_status(400) + expect(response.content_type) + .to start_with('application/json') end end @@ -142,6 +150,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body.first[:canonical_email_hash]).to eq(canonical_email_block.canonical_email_hash) end end @@ -151,6 +161,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body).to be_empty end end @@ -173,6 +185,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body[:canonical_email_hash]).to eq(canonical_email_block.canonical_email_hash) end @@ -183,6 +197,8 @@ subject expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') end end @@ -193,6 +209,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body[:canonical_email_hash]).to eq(params[:canonical_email_hash]) end end @@ -204,6 +222,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body[:canonical_email_hash]).to eq(canonical_email_block.canonical_email_hash) end end @@ -217,6 +237,8 @@ subject expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') end end end @@ -237,6 +259,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(CanonicalEmailBlock.find_by(id: canonical_email_block.id)).to be_nil end @@ -245,6 +269,8 @@ delete '/api/v1/admin/canonical_email_blocks/0', headers: headers expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end diff --git a/spec/requests/api/v1/admin/dimensions_spec.rb b/spec/requests/api/v1/admin/dimensions_spec.rb index a28c2a9e3e521a..81fb580ba72f37 100644 --- a/spec/requests/api/v1/admin/dimensions_spec.rb +++ b/spec/requests/api/v1/admin/dimensions_spec.rb @@ -15,6 +15,8 @@ expect(response) .to have_http_status(403) + expect(response.content_type) + .to start_with('application/json') end end @@ -27,6 +29,9 @@ expect(response) .to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') + expect(response.parsed_body) .to be_an(Array) end diff --git a/spec/requests/api/v1/admin/domain_allows_spec.rb b/spec/requests/api/v1/admin/domain_allows_spec.rb index 26c962b347a61f..eb5128e420834d 100644 --- a/spec/requests/api/v1/admin/domain_allows_spec.rb +++ b/spec/requests/api/v1/admin/domain_allows_spec.rb @@ -24,6 +24,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end context 'when there is no allowed domains' do @@ -79,6 +81,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body[:domain]).to eq domain_allow.domain end @@ -87,6 +91,8 @@ get '/api/v1/admin/domain_allows/-1', headers: headers expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end @@ -107,6 +113,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body[:domain]).to eq 'foo.bar.com' expect(DomainAllow.find_by(domain: 'foo.bar.com')).to be_present end @@ -119,6 +127,8 @@ subject expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') end end @@ -129,6 +139,8 @@ subject expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') end end @@ -160,6 +172,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(DomainAllow.find_by(id: domain_allow.id)).to be_nil end @@ -168,6 +182,8 @@ delete '/api/v1/admin/domain_allows/-1', headers: headers expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end diff --git a/spec/requests/api/v1/admin/domain_blocks_spec.rb b/spec/requests/api/v1/admin/domain_blocks_spec.rb index 3f2cbbf1135788..1a506bd9bebca7 100644 --- a/spec/requests/api/v1/admin/domain_blocks_spec.rb +++ b/spec/requests/api/v1/admin/domain_blocks_spec.rb @@ -24,6 +24,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end context 'when there are no domain blocks' do @@ -94,6 +96,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body).to match( id: domain_block.id.to_s, domain: domain_block.domain, @@ -113,6 +117,8 @@ get '/api/v1/admin/domain_blocks/-1', headers: headers expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end @@ -132,6 +138,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body).to match a_hash_including( { domain: 'foo.bar.com', @@ -153,6 +161,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body).to match a_hash_including( { domain: 'foo.bar.com', @@ -173,6 +183,8 @@ subject expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body[:existing_domain_block][:domain]).to eq('foo.bar.com') end end @@ -186,6 +198,8 @@ subject expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body[:existing_domain_block][:domain]).to eq('bar.com') end end @@ -197,6 +211,8 @@ subject expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') end end end @@ -217,6 +233,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body).to match a_hash_including( { id: domain_block.id.to_s, @@ -236,6 +254,8 @@ put '/api/v1/admin/domain_blocks/-1', headers: headers expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end @@ -255,6 +275,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(DomainBlock.find_by(id: domain_block.id)).to be_nil end @@ -263,6 +285,8 @@ delete '/api/v1/admin/domain_blocks/-1', headers: headers expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end diff --git a/spec/requests/api/v1/admin/email_domain_blocks_spec.rb b/spec/requests/api/v1/admin/email_domain_blocks_spec.rb index aa3073341a9a24..3f51a3ea2d4fb6 100644 --- a/spec/requests/api/v1/admin/email_domain_blocks_spec.rb +++ b/spec/requests/api/v1/admin/email_domain_blocks_spec.rb @@ -25,6 +25,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end context 'when there is no email domain block' do @@ -97,6 +99,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body[:domain]).to eq(email_domain_block.domain) end end @@ -106,6 +110,8 @@ get '/api/v1/admin/email_domain_blocks/-1', headers: headers expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end @@ -125,6 +131,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body[:domain]).to eq(params[:domain]) end @@ -135,6 +143,8 @@ subject expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') end end @@ -145,6 +155,8 @@ subject expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') end end @@ -157,6 +169,8 @@ subject expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') end end end @@ -176,6 +190,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body).to be_empty expect(EmailDomainBlock.find_by(id: email_domain_block.id)).to be_nil end @@ -185,6 +201,8 @@ delete '/api/v1/admin/email_domain_blocks/-1', headers: headers expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end diff --git a/spec/requests/api/v1/admin/ip_blocks_spec.rb b/spec/requests/api/v1/admin/ip_blocks_spec.rb index b18f8f885cfe9d..c096aa33283cae 100644 --- a/spec/requests/api/v1/admin/ip_blocks_spec.rb +++ b/spec/requests/api/v1/admin/ip_blocks_spec.rb @@ -24,6 +24,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end context 'when there is no ip block' do @@ -88,6 +90,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body) .to include( @@ -101,6 +105,8 @@ get '/api/v1/admin/ip_blocks/-1', headers: headers expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end @@ -120,6 +126,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body) .to include( ip: eq("#{params[:ip]}/32"), @@ -135,6 +143,8 @@ subject expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') end end @@ -145,6 +155,8 @@ subject expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') end end @@ -157,6 +169,8 @@ subject expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') end end @@ -167,6 +181,8 @@ subject expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') end end end @@ -185,6 +201,8 @@ .and change_comment_value expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body).to match(hash_including({ ip: "#{ip_block.ip}/#{ip_block.ip.prefix}", severity: 'sign_up_requires_approval', @@ -205,6 +223,8 @@ def change_comment_value put '/api/v1/admin/ip_blocks/-1', headers: headers, params: params expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end @@ -220,6 +240,8 @@ def change_comment_value subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body).to be_empty expect(IpBlock.find_by(id: ip_block.id)).to be_nil end @@ -229,6 +251,8 @@ def change_comment_value delete '/api/v1/admin/ip_blocks/-1', headers: headers expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end diff --git a/spec/requests/api/v1/admin/measures_spec.rb b/spec/requests/api/v1/admin/measures_spec.rb index de359a5ccdfea1..519b5cc7b3ca6c 100644 --- a/spec/requests/api/v1/admin/measures_spec.rb +++ b/spec/requests/api/v1/admin/measures_spec.rb @@ -32,6 +32,8 @@ expect(response) .to have_http_status(403) + expect(response.content_type) + .to start_with('application/json') end end @@ -43,6 +45,8 @@ expect(response) .to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body) .to be_an(Array) diff --git a/spec/requests/api/v1/admin/reports_spec.rb b/spec/requests/api/v1/admin/reports_spec.rb index 2c40f56dc8adea..a0e7b0a369a3e1 100644 --- a/spec/requests/api/v1/admin/reports_spec.rb +++ b/spec/requests/api/v1/admin/reports_spec.rb @@ -23,6 +23,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end context 'when there are no reports' do @@ -126,6 +128,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body).to include( { id: report.id.to_s, @@ -156,6 +160,8 @@ .and create_an_action_log expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') report.reload @@ -190,6 +196,8 @@ .to change { report.reload.unresolved? }.from(true).to(false) .and create_an_action_log expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end @@ -208,6 +216,8 @@ .to change { report.reload.unresolved? }.from(false).to(true) .and create_an_action_log expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end @@ -226,6 +236,8 @@ .to change { report.reload.assigned_account_id }.from(nil).to(user.account.id) .and create_an_action_log expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end @@ -244,6 +256,8 @@ .to change { report.reload.assigned_account_id }.from(user.account.id).to(nil) .and create_an_action_log expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end diff --git a/spec/requests/api/v1/admin/retention_spec.rb b/spec/requests/api/v1/admin/retention_spec.rb index c28fa6de815bfa..e28bc2a94f815c 100644 --- a/spec/requests/api/v1/admin/retention_spec.rb +++ b/spec/requests/api/v1/admin/retention_spec.rb @@ -15,6 +15,8 @@ expect(response) .to have_http_status(403) + expect(response.content_type) + .to start_with('application/json') end end @@ -26,6 +28,8 @@ expect(response) .to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body) .to be_an(Array) diff --git a/spec/requests/api/v1/admin/tags_spec.rb b/spec/requests/api/v1/admin/tags_spec.rb index fda9227acf5402..3623c09ac7ad58 100644 --- a/spec/requests/api/v1/admin/tags_spec.rb +++ b/spec/requests/api/v1/admin/tags_spec.rb @@ -24,6 +24,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end context 'when there are no tags' do @@ -77,6 +79,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body[:id].to_i).to eq(tag.id) expect(response.parsed_body[:name]).to eq(tag.name) @@ -87,6 +91,8 @@ get '/api/v1/admin/tags/-1', headers: headers expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end @@ -107,6 +113,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body[:id].to_i).to eq(tag.id) expect(response.parsed_body[:name]).to eq(tag.name.upcase) @@ -119,6 +127,8 @@ subject expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') end end @@ -127,6 +137,8 @@ get '/api/v1/admin/tags/-1', headers: headers expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end diff --git a/spec/requests/api/v1/admin/trends/links/links_spec.rb b/spec/requests/api/v1/admin/trends/links/links_spec.rb index c436b7081e615f..51e800734aaaec 100644 --- a/spec/requests/api/v1/admin/trends/links/links_spec.rb +++ b/spec/requests/api/v1/admin/trends/links/links_spec.rb @@ -18,6 +18,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end @@ -36,6 +38,8 @@ .to change_link_trendable_to_true expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expects_correct_link_data end @@ -60,6 +64,8 @@ def expects_correct_link_data post '/api/v1/admin/trends/links/-1/approve', headers: headers expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end @@ -70,6 +76,8 @@ def expects_correct_link_data subject expect(response).to have_http_status(403) + expect(response.content_type) + .to start_with('application/json') end end end @@ -89,6 +97,8 @@ def expects_correct_link_data .to_not change_link_trendable expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end def change_link_trendable @@ -114,6 +124,8 @@ def change_link_trendable post '/api/v1/admin/trends/links/-1/reject', headers: headers expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end @@ -124,6 +136,8 @@ def change_link_trendable subject expect(response).to have_http_status(403) + expect(response.content_type) + .to start_with('application/json') end end end diff --git a/spec/requests/api/v1/admin/trends/links/preview_card_providers_spec.rb b/spec/requests/api/v1/admin/trends/links/preview_card_providers_spec.rb index 193906ab057b6d..d46d0ff5555404 100644 --- a/spec/requests/api/v1/admin/trends/links/preview_card_providers_spec.rb +++ b/spec/requests/api/v1/admin/trends/links/preview_card_providers_spec.rb @@ -16,6 +16,8 @@ get '/api/v1/admin/trends/links/publishers', params: { account_id: account.id, limit: 2 }, headers: headers expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end @@ -29,6 +31,8 @@ it 'returns http success' do expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end @@ -42,6 +46,8 @@ it 'returns http success' do expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end end diff --git a/spec/requests/api/v1/admin/trends/statuses_spec.rb b/spec/requests/api/v1/admin/trends/statuses_spec.rb index e33a9658a9ff1e..c63d8d925c7c98 100644 --- a/spec/requests/api/v1/admin/trends/statuses_spec.rb +++ b/spec/requests/api/v1/admin/trends/statuses_spec.rb @@ -16,6 +16,8 @@ get '/api/v1/admin/trends/statuses', params: { account_id: account.id, limit: 2 }, headers: headers expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end @@ -29,6 +31,8 @@ it 'returns http success' do expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end @@ -42,6 +46,8 @@ it 'returns http success' do expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end end diff --git a/spec/requests/api/v1/admin/trends/tags_spec.rb b/spec/requests/api/v1/admin/trends/tags_spec.rb index 748a27283c14b7..433cc6c5a6e5f3 100644 --- a/spec/requests/api/v1/admin/trends/tags_spec.rb +++ b/spec/requests/api/v1/admin/trends/tags_spec.rb @@ -16,6 +16,8 @@ get '/api/v1/admin/trends/tags', params: { account_id: account.id, limit: 2 }, headers: headers expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end @@ -29,6 +31,8 @@ it 'returns http success' do expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end @@ -42,6 +46,8 @@ it 'returns http success' do expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end end diff --git a/spec/requests/api/v1/announcements/reactions_spec.rb b/spec/requests/api/v1/announcements/reactions_spec.rb index ffacb2b0afa270..82154b4a265210 100644 --- a/spec/requests/api/v1/announcements/reactions_spec.rb +++ b/spec/requests/api/v1/announcements/reactions_spec.rb @@ -15,7 +15,9 @@ it 'returns http unauthorized' do put "/api/v1/announcements/#{announcement.id}/reactions/#{escaped_emoji}" - expect(response).to have_http_status 401 + expect(response).to have_http_status(401) + expect(response.content_type) + .to start_with('application/json') end end @@ -26,6 +28,8 @@ it 'creates reaction', :aggregate_failures do expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(announcement.announcement_reactions.find_by(name: '😂', account: user.account)).to_not be_nil end end @@ -39,7 +43,9 @@ context 'without token' do it 'returns http unauthorized' do delete "/api/v1/announcements/#{announcement.id}/reactions/#{escaped_emoji}" - expect(response).to have_http_status 401 + expect(response).to have_http_status(401) + expect(response.content_type) + .to start_with('application/json') end end @@ -50,6 +56,8 @@ it 'creates reaction', :aggregate_failures do expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(announcement.announcement_reactions.find_by(name: '😂', account: user.account)).to be_nil end end diff --git a/spec/requests/api/v1/announcements_spec.rb b/spec/requests/api/v1/announcements_spec.rb index 1624b76012563b..97a4442aa98855 100644 --- a/spec/requests/api/v1/announcements_spec.rb +++ b/spec/requests/api/v1/announcements_spec.rb @@ -15,7 +15,9 @@ it 'returns http unprocessable entity' do get '/api/v1/announcements' - expect(response).to have_http_status 422 + expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') end end @@ -26,6 +28,8 @@ it 'returns http success' do expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end end @@ -35,7 +39,9 @@ it 'returns http unauthorized' do post "/api/v1/announcements/#{announcement.id}/dismiss" - expect(response).to have_http_status 401 + expect(response).to have_http_status(401) + expect(response.content_type) + .to start_with('application/json') end end @@ -48,6 +54,8 @@ it 'dismisses announcement', :aggregate_failures do expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(announcement.announcement_mutes.find_by(account: user.account)).to_not be_nil end end diff --git a/spec/requests/api/v1/annual_reports_spec.rb b/spec/requests/api/v1/annual_reports_spec.rb index 8051a654847ecc..b9831d17e2c4f9 100644 --- a/spec/requests/api/v1/annual_reports_spec.rb +++ b/spec/requests/api/v1/annual_reports_spec.rb @@ -14,6 +14,8 @@ expect(response) .to have_http_status(401) + expect(response.content_type) + .to start_with('application/json') end end @@ -33,6 +35,8 @@ expect(response) .to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body) .to be_present @@ -51,6 +55,8 @@ .to change { annual_report.reload.viewed? }.to(true) expect(response) .to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end end diff --git a/spec/requests/api/v1/apps/credentials_spec.rb b/spec/requests/api/v1/apps/credentials_spec.rb index 6fd885eb8685f6..30200ed60c3471 100644 --- a/spec/requests/api/v1/apps/credentials_spec.rb +++ b/spec/requests/api/v1/apps/credentials_spec.rb @@ -17,6 +17,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body).to match( a_hash_including( @@ -36,6 +38,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body[:client_id]).to_not be_present expect(response.parsed_body[:client_secret]).to_not be_present @@ -51,6 +55,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body).to match( a_hash_including( @@ -74,6 +80,8 @@ subject expect(response).to have_http_status(401) + expect(response.content_type) + .to start_with('application/json') end end @@ -86,6 +94,8 @@ subject expect(response).to have_http_status(401) + expect(response.content_type) + .to start_with('application/json') end it 'returns the error in the json response' do @@ -108,6 +118,8 @@ subject expect(response).to have_http_status(401) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body).to match( a_hash_including( diff --git a/spec/requests/api/v1/apps_spec.rb b/spec/requests/api/v1/apps_spec.rb index 51a0c3fd0c6dc7..cf43e14d62c267 100644 --- a/spec/requests/api/v1/apps_spec.rb +++ b/spec/requests/api/v1/apps_spec.rb @@ -28,6 +28,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') app = Doorkeeper::Application.find_by(name: client_name) @@ -59,6 +61,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(Doorkeeper::Application.find_by(name: client_name)).to be_present expect(response.parsed_body) @@ -76,6 +80,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') app = Doorkeeper::Application.find_by(name: client_name) @@ -96,6 +102,8 @@ subject expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') end end @@ -106,6 +114,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(Doorkeeper::Application.find_by(name: client_name).scopes.to_s).to eq 'read' end end @@ -117,6 +127,8 @@ subject expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') end end @@ -127,6 +139,8 @@ subject expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') end end @@ -137,6 +151,8 @@ subject expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') end end @@ -148,6 +164,8 @@ subject expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') end end @@ -158,6 +176,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') app = Doorkeeper::Application.find_by(name: client_name) @@ -180,6 +200,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') app = Doorkeeper::Application.find_by(name: client_name) @@ -202,6 +224,8 @@ subject expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') end end @@ -212,6 +236,8 @@ subject expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') end end @@ -222,6 +248,8 @@ subject expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') end end @@ -232,6 +260,8 @@ subject expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') end end @@ -242,6 +272,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') app = Doorkeeper::Application.find_by(name: client_name) diff --git a/spec/requests/api/v1/blocks_spec.rb b/spec/requests/api/v1/blocks_spec.rb index fc028f9bac8dd4..498cf932756de8 100644 --- a/spec/requests/api/v1/blocks_spec.rb +++ b/spec/requests/api/v1/blocks_spec.rb @@ -26,6 +26,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body).to match_array(expected_response) end @@ -36,6 +38,8 @@ subject expect(response.parsed_body.size).to eq(params[:limit]) + expect(response.content_type) + .to start_with('application/json') expect(response) .to include_pagination_headers( prev: api_v1_blocks_url(limit: params[:limit], since_id: blocks.last.id), diff --git a/spec/requests/api/v1/bookmarks_spec.rb b/spec/requests/api/v1/bookmarks_spec.rb index 5955de66524287..c78e6912365b51 100644 --- a/spec/requests/api/v1/bookmarks_spec.rb +++ b/spec/requests/api/v1/bookmarks_spec.rb @@ -28,6 +28,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body).to match_array(expected_response) end @@ -40,6 +42,8 @@ expect(response.parsed_body.size) .to eq(params[:limit]) + expect(response.content_type) + .to start_with('application/json') expect(response) .to include_pagination_headers( prev: api_v1_bookmarks_url(limit: params[:limit], min_id: bookmarks.last.id), @@ -55,6 +59,8 @@ subject expect(response).to have_http_status(401) + expect(response.content_type) + .to start_with('application/json') end end end diff --git a/spec/requests/api/v1/conversations_spec.rb b/spec/requests/api/v1/conversations_spec.rb index bd3cbfd0e7f43f..6e2ac1df53e0a8 100644 --- a/spec/requests/api/v1/conversations_spec.rb +++ b/spec/requests/api/v1/conversations_spec.rb @@ -26,6 +26,8 @@ prev: api_v1_conversations_url(limit: 1, min_id: Status.first.id), next: api_v1_conversations_url(limit: 1, max_id: Status.first.id) ) + expect(response.content_type) + .to start_with('application/json') end it 'returns conversations', :aggregate_failures do diff --git a/spec/requests/api/v1/custom_emojis_spec.rb b/spec/requests/api/v1/custom_emojis_spec.rb index 0942734ff3d809..e860fbeb17c105 100644 --- a/spec/requests/api/v1/custom_emojis_spec.rb +++ b/spec/requests/api/v1/custom_emojis_spec.rb @@ -18,6 +18,8 @@ expect(response) .to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body) .to be_present @@ -33,6 +35,8 @@ expect(response) .to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body) .to be_present diff --git a/spec/requests/api/v1/directories_spec.rb b/spec/requests/api/v1/directories_spec.rb index aa602a71cd65e3..282be9a582551e 100644 --- a/spec/requests/api/v1/directories_spec.rb +++ b/spec/requests/api/v1/directories_spec.rb @@ -82,6 +82,8 @@ get '/api/v1/directory', headers: headers expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body.size).to eq(2) expect(response.parsed_body.pluck(:id)).to contain_exactly(eligible_remote_account.id.to_s, local_discoverable_account.id.to_s) end @@ -101,6 +103,8 @@ get '/api/v1/directory', headers: headers, params: { local: '1' } expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body.size).to eq(1) expect(response.parsed_body.first[:id]).to include(local_account.id.to_s) expect(response.body).to_not include(remote_account.id.to_s) @@ -115,6 +119,8 @@ get '/api/v1/directory', headers: headers, params: { order: 'active' } expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body.size).to eq(2) expect(response.parsed_body.first[:id]).to include(new_stat.account_id.to_s) expect(response.parsed_body.second[:id]).to include(old_stat.account_id.to_s) @@ -130,6 +136,8 @@ get '/api/v1/directory', headers: headers, params: { order: 'new' } expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body.size).to eq(2) expect(response.parsed_body.first[:id]).to include(account_new.id.to_s) expect(response.parsed_body.second[:id]).to include(account_old.id.to_s) diff --git a/spec/requests/api/v1/domain_blocks_spec.rb b/spec/requests/api/v1/domain_blocks_spec.rb index 8184c26bed101f..339f49fe761f75 100644 --- a/spec/requests/api/v1/domain_blocks_spec.rb +++ b/spec/requests/api/v1/domain_blocks_spec.rb @@ -26,6 +26,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body).to match_array(blocked_domains) end @@ -53,6 +55,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(user.account.domain_blocking?(params[:domain])).to be(true) end @@ -63,6 +67,8 @@ subject expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') end end @@ -73,6 +79,8 @@ subject expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') end end end @@ -94,6 +102,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(user.account.domain_blocking?('example.com')).to be(false) end @@ -104,6 +114,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end end diff --git a/spec/requests/api/v1/emails/confirmations_spec.rb b/spec/requests/api/v1/emails/confirmations_spec.rb index 0a419a10cfb937..1408ad05067318 100644 --- a/spec/requests/api/v1/emails/confirmations_spec.rb +++ b/spec/requests/api/v1/emails/confirmations_spec.rb @@ -26,6 +26,8 @@ subject expect(response).to have_http_status(403) + expect(response.content_type) + .to start_with('application/json') end end @@ -41,6 +43,8 @@ subject expect(response).to have_http_status(403) + expect(response.content_type) + .to start_with('application/json') end context 'when user changed e-mail and has not confirmed it' do @@ -52,6 +56,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end end @@ -61,6 +67,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end @@ -71,6 +79,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(user.reload.unconfirmed_email).to eq('foo@bar.com') end end @@ -82,6 +92,8 @@ subject expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') end end end @@ -94,6 +106,8 @@ subject expect(response).to have_http_status(401) + expect(response.content_type) + .to start_with('application/json') end end end @@ -111,6 +125,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body).to be false end end @@ -122,6 +138,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body).to be true end end @@ -139,6 +157,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body).to be false end end @@ -150,6 +170,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body).to be true end end @@ -162,6 +184,8 @@ subject expect(response).to have_http_status(401) + expect(response.content_type) + .to start_with('application/json') end end end diff --git a/spec/requests/api/v1/endorsements_spec.rb b/spec/requests/api/v1/endorsements_spec.rb index 25917f527a1645..730ba6350cf05e 100644 --- a/spec/requests/api/v1/endorsements_spec.rb +++ b/spec/requests/api/v1/endorsements_spec.rb @@ -14,6 +14,8 @@ expect(response) .to have_http_status(401) + expect(response.content_type) + .to start_with('application/json') end end @@ -36,6 +38,8 @@ expect(response) .to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body) .to be_present @@ -51,6 +55,8 @@ expect(response) .to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body) .to_not be_present diff --git a/spec/requests/api/v1/favourites_spec.rb b/spec/requests/api/v1/favourites_spec.rb index 2f8bef1190d75b..44d0239556b2ab 100644 --- a/spec/requests/api/v1/favourites_spec.rb +++ b/spec/requests/api/v1/favourites_spec.rb @@ -28,6 +28,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body).to match_array(expected_response) end @@ -43,6 +45,8 @@ prev: api_v1_favourites_url(limit: params[:limit], min_id: favourites.last.id), next: api_v1_favourites_url(limit: params[:limit], max_id: favourites.second.id) ) + expect(response.content_type) + .to start_with('application/json') end end @@ -53,6 +57,8 @@ subject expect(response).to have_http_status(401) + expect(response.content_type) + .to start_with('application/json') end end end diff --git a/spec/requests/api/v1/featured_tags/suggestions_spec.rb b/spec/requests/api/v1/featured_tags/suggestions_spec.rb index 8815c65cf1ee39..5fbbec50976f08 100644 --- a/spec/requests/api/v1/featured_tags/suggestions_spec.rb +++ b/spec/requests/api/v1/featured_tags/suggestions_spec.rb @@ -32,6 +32,8 @@ expect(response) .to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body) .to contain_exactly( include(name: used_tag.name) diff --git a/spec/requests/api/v1/featured_tags_spec.rb b/spec/requests/api/v1/featured_tags_spec.rb index f0e939d42a19ac..b9c78cc11bdf5c 100644 --- a/spec/requests/api/v1/featured_tags_spec.rb +++ b/spec/requests/api/v1/featured_tags_spec.rb @@ -22,6 +22,8 @@ get '/api/v1/featured_tags' expect(response).to have_http_status(401) + expect(response.content_type) + .to start_with('application/json') end end @@ -29,6 +31,8 @@ get '/api/v1/featured_tags', headers: headers expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end context 'when the requesting user has no featured tag' do @@ -62,6 +66,8 @@ post '/api/v1/featured_tags', headers: headers, params: params expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body) .to include( name: params[:name] @@ -89,6 +95,8 @@ post '/api/v1/featured_tags', params: params expect(response).to have_http_status(401) + expect(response.content_type) + .to start_with('application/json') end end @@ -97,6 +105,8 @@ post '/api/v1/featured_tags', headers: headers expect(response).to have_http_status(400) + expect(response.content_type) + .to start_with('application/json') end end @@ -107,6 +117,8 @@ post '/api/v1/featured_tags', headers: headers, params: params expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') end end @@ -119,6 +131,8 @@ post '/api/v1/featured_tags', headers: headers, params: params expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') end end end @@ -131,6 +145,8 @@ delete "/api/v1/featured_tags/#{id}", headers: headers expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body).to be_empty featured_tag = FeaturedTag.find_by(id: id) @@ -150,6 +166,8 @@ delete "/api/v1/featured_tags/#{id}" expect(response).to have_http_status(401) + expect(response.content_type) + .to start_with('application/json') end end @@ -158,6 +176,8 @@ delete '/api/v1/featured_tags/0', headers: headers expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end @@ -169,6 +189,8 @@ delete "/api/v1/featured_tags/#{id}", headers: headers expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end diff --git a/spec/requests/api/v1/filters_spec.rb b/spec/requests/api/v1/filters_spec.rb index 93ed78b34606c8..51f03cc04d44c7 100644 --- a/spec/requests/api/v1/filters_spec.rb +++ b/spec/requests/api/v1/filters_spec.rb @@ -15,6 +15,8 @@ it 'returns http success' do get '/api/v1/filters', headers: headers expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body) .to contain_exactly( include(id: custom_filter_keyword.id.to_s) @@ -35,6 +37,8 @@ filter = user.account.custom_filters.first expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(filter).to_not be_nil expect(filter.keywords.pluck(:keyword, :whole_word)).to eq [['magic', whole_word]] expect(filter.context).to eq %w(home) @@ -50,6 +54,8 @@ filter = user.account.custom_filters.first expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(filter).to_not be_nil expect(filter.keywords.pluck(:keyword, :whole_word)).to eq [['magic', whole_word]] expect(filter.context).to eq %w(home) @@ -68,6 +74,8 @@ get "/api/v1/filters/#{keyword.id}", headers: headers expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end @@ -82,6 +90,8 @@ it 'updates the filter', :aggregate_failures do expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(keyword.reload.phrase).to eq 'updated' end end @@ -97,6 +107,8 @@ it 'removes the filter', :aggregate_failures do expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect { keyword.reload }.to raise_error ActiveRecord::RecordNotFound end end diff --git a/spec/requests/api/v1/follow_requests_spec.rb b/spec/requests/api/v1/follow_requests_spec.rb index c143ccaec1944e..f0f73d38ad0d9e 100644 --- a/spec/requests/api/v1/follow_requests_spec.rb +++ b/spec/requests/api/v1/follow_requests_spec.rb @@ -36,6 +36,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body).to match_array(expected_response) end @@ -66,6 +68,8 @@ it 'allows the requesting follower to follow', :aggregate_failures do expect { subject }.to change { follower.following?(user.account) }.from(false).to(true) expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body[:followed_by]).to be true end end @@ -87,6 +91,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(FollowRequest.where(target_account: user.account, account: follower)).to_not exist expect(response.parsed_body[:followed_by]).to be false end diff --git a/spec/requests/api/v1/followed_tags_spec.rb b/spec/requests/api/v1/followed_tags_spec.rb index f15c0d7f44db50..b0191b523fc308 100644 --- a/spec/requests/api/v1/followed_tags_spec.rb +++ b/spec/requests/api/v1/followed_tags_spec.rb @@ -32,20 +32,20 @@ subject expect(response).to have_http_status(:success) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body).to match_array(expected_response) end context 'with limit param' do let(:params) { { limit: 1 } } - it 'returns only the requested number of follow tags' do + it 'returns only the requested number of follow tags and sets pagination headers' do subject + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body.size).to eq(params[:limit]) - end - - it 'sets the correct pagination headers' do - subject expect(response) .to include_pagination_headers( diff --git a/spec/requests/api/v1/instance_spec.rb b/spec/requests/api/v1/instance_spec.rb index 8d6ba572e0aebd..821cbfec6146cf 100644 --- a/spec/requests/api/v1/instance_spec.rb +++ b/spec/requests/api/v1/instance_spec.rb @@ -14,6 +14,8 @@ expect(response) .to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body) .to be_present @@ -27,6 +29,8 @@ expect(response) .to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body) .to be_present diff --git a/spec/requests/api/v1/instances/activity_spec.rb b/spec/requests/api/v1/instances/activity_spec.rb index 72e3faeb650dd3..c2f94cc578a1e6 100644 --- a/spec/requests/api/v1/instances/activity_spec.rb +++ b/spec/requests/api/v1/instances/activity_spec.rb @@ -13,6 +13,9 @@ expect(response) .to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') + expect(response.parsed_body) .to be_present .and(be_an(Array)) diff --git a/spec/requests/api/v1/instances/domain_blocks_spec.rb b/spec/requests/api/v1/instances/domain_blocks_spec.rb index 460d338607bc9d..b214fda73b5a54 100644 --- a/spec/requests/api/v1/instances/domain_blocks_spec.rb +++ b/spec/requests/api/v1/instances/domain_blocks_spec.rb @@ -17,6 +17,9 @@ expect(response) .to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') + expect(response.parsed_body) .to be_present .and(be_an(Array)) diff --git a/spec/requests/api/v1/instances/extended_descriptions_spec.rb b/spec/requests/api/v1/instances/extended_descriptions_spec.rb index bf6d58216a0b0f..62a7fff2eccc33 100644 --- a/spec/requests/api/v1/instances/extended_descriptions_spec.rb +++ b/spec/requests/api/v1/instances/extended_descriptions_spec.rb @@ -9,6 +9,8 @@ expect(response) .to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body) .to be_present diff --git a/spec/requests/api/v1/instances/languages_spec.rb b/spec/requests/api/v1/instances/languages_spec.rb index 3ab1ba57b85c37..3d188d23c05bde 100644 --- a/spec/requests/api/v1/instances/languages_spec.rb +++ b/spec/requests/api/v1/instances/languages_spec.rb @@ -10,6 +10,8 @@ it 'returns http success and includes supported languages' do expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body.pluck(:code)).to match_array LanguagesHelper::SUPPORTED_LOCALES.keys.map(&:to_s) end end diff --git a/spec/requests/api/v1/instances/peers_spec.rb b/spec/requests/api/v1/instances/peers_spec.rb index 1140612f0a9438..8ebfc933579055 100644 --- a/spec/requests/api/v1/instances/peers_spec.rb +++ b/spec/requests/api/v1/instances/peers_spec.rb @@ -12,6 +12,8 @@ expect(response) .to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body) .to be_an(Array) diff --git a/spec/requests/api/v1/instances/privacy_policies_spec.rb b/spec/requests/api/v1/instances/privacy_policies_spec.rb index 93490542cdd4d7..519c2b29d048bd 100644 --- a/spec/requests/api/v1/instances/privacy_policies_spec.rb +++ b/spec/requests/api/v1/instances/privacy_policies_spec.rb @@ -9,6 +9,8 @@ expect(response) .to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body) .to be_present diff --git a/spec/requests/api/v1/instances/rules_spec.rb b/spec/requests/api/v1/instances/rules_spec.rb index 620c991ae2c3b0..b73004886392be 100644 --- a/spec/requests/api/v1/instances/rules_spec.rb +++ b/spec/requests/api/v1/instances/rules_spec.rb @@ -9,6 +9,8 @@ expect(response) .to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body) .to be_an(Array) diff --git a/spec/requests/api/v1/instances/translation_languages_spec.rb b/spec/requests/api/v1/instances/translation_languages_spec.rb index 0de5ec3bc236ae..fef8700db928e4 100644 --- a/spec/requests/api/v1/instances/translation_languages_spec.rb +++ b/spec/requests/api/v1/instances/translation_languages_spec.rb @@ -10,6 +10,8 @@ expect(response) .to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body) .to eq({}) @@ -24,6 +26,8 @@ expect(response) .to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body) .to match({ und: %w(en de), en: ['de'] }) diff --git a/spec/requests/api/v1/lists/accounts_spec.rb b/spec/requests/api/v1/lists/accounts_spec.rb index 6e5f9e4e9dd424..3911d1f28b50f7 100644 --- a/spec/requests/api/v1/lists/accounts_spec.rb +++ b/spec/requests/api/v1/lists/accounts_spec.rb @@ -34,6 +34,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body).to match_array(expected_response) end @@ -68,6 +70,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(list.accounts).to include(bob) end end @@ -81,6 +85,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(list.accounts).to include(bob) end end @@ -90,6 +96,8 @@ subject expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') expect(list.accounts).to_not include(bob) end end @@ -105,6 +113,8 @@ subject expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end @@ -118,6 +128,8 @@ subject expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') end end end @@ -143,6 +155,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(list.accounts).to_not include(bob) expect(list.accounts).to include(peter) end @@ -154,6 +168,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(list.accounts).to contain_exactly(bob, peter) end end @@ -167,6 +183,8 @@ subject expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end diff --git a/spec/requests/api/v1/lists_spec.rb b/spec/requests/api/v1/lists_spec.rb index 2042a64d5ceb00..20f27a74310574 100644 --- a/spec/requests/api/v1/lists_spec.rb +++ b/spec/requests/api/v1/lists_spec.rb @@ -43,6 +43,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body).to match_array(expected_response) end end @@ -60,6 +62,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body).to match({ id: list.id.to_s, title: list.title, @@ -75,6 +79,8 @@ subject expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end @@ -83,6 +89,8 @@ get '/api/v1/lists/-1', headers: headers expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end @@ -100,6 +108,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body).to match(a_hash_including(title: 'my list', replies_policy: 'none', exclusive: true)) expect(List.where(account: user.account).count).to eq(1) end @@ -111,6 +121,8 @@ subject expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') end end @@ -121,6 +133,8 @@ subject expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') end end end @@ -142,6 +156,8 @@ .and change_list_exclusive expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') list.reload expect(response.parsed_body).to match({ @@ -169,6 +185,8 @@ def change_list_exclusive put '/api/v1/lists/-1', headers: headers, params: params expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end @@ -179,6 +197,8 @@ def change_list_exclusive subject expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end @@ -196,6 +216,8 @@ def change_list_exclusive subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(List.where(id: list.id)).to_not exist end @@ -214,6 +236,8 @@ def change_list_exclusive subject expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end diff --git a/spec/requests/api/v1/markers_spec.rb b/spec/requests/api/v1/markers_spec.rb index a10d2dc3e280ec..d7cd78924bcec1 100644 --- a/spec/requests/api/v1/markers_spec.rb +++ b/spec/requests/api/v1/markers_spec.rb @@ -18,6 +18,8 @@ it 'returns markers', :aggregate_failures do expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body) .to include( home: include(last_read_id: '123'), @@ -34,6 +36,8 @@ it 'creates a marker', :aggregate_failures do expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(user.markers.first.timeline).to eq 'home' expect(user.markers.first.last_read_id).to eq 69_420 end @@ -47,6 +51,8 @@ it 'updates a marker', :aggregate_failures do expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(user.markers.first.timeline).to eq 'home' expect(user.markers.first.last_read_id).to eq 70_120 end @@ -61,6 +67,8 @@ it 'returns error json' do expect(response) .to have_http_status(409) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body) .to include(error: /Conflict during update/) end diff --git a/spec/requests/api/v1/media_spec.rb b/spec/requests/api/v1/media_spec.rb index a10bbb31efb01a..d7d0b92f11bfb5 100644 --- a/spec/requests/api/v1/media_spec.rb +++ b/spec/requests/api/v1/media_spec.rb @@ -21,6 +21,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body).to match( a_hash_including( id: media.id.to_s, @@ -39,6 +41,8 @@ subject expect(response).to have_http_status(206) + expect(response.content_type) + .to start_with('application/json') end end @@ -49,6 +53,8 @@ subject expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end @@ -59,6 +65,8 @@ subject expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end @@ -75,6 +83,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(MediaAttachment.first).to be_present expect(MediaAttachment.first).to have_attached_file(:file) @@ -102,6 +112,8 @@ subject expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') end end @@ -112,6 +124,8 @@ subject expect(response).to have_http_status(500) + expect(response.content_type) + .to start_with('application/json') end end end @@ -153,6 +167,8 @@ subject expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end @@ -171,6 +187,8 @@ subject expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end diff --git a/spec/requests/api/v1/mutes_spec.rb b/spec/requests/api/v1/mutes_spec.rb index 316d455d288fe5..61e32cb9ae0452 100644 --- a/spec/requests/api/v1/mutes_spec.rb +++ b/spec/requests/api/v1/mutes_spec.rb @@ -22,6 +22,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') muted_accounts = mutes.map(&:target_account) expect(response.parsed_body.pluck(:id)).to match_array(muted_accounts.map { |account| account.id.to_s }) @@ -34,6 +36,8 @@ subject expect(response.parsed_body.size).to eq(params[:limit]) + expect(response.content_type) + .to start_with('application/json') expect(response) .to include_pagination_headers( prev: api_v1_mutes_url(limit: params[:limit], since_id: mutes.last.id), @@ -71,6 +75,8 @@ subject expect(response).to have_http_status(401) + expect(response.content_type) + .to start_with('application/json') end end end diff --git a/spec/requests/api/v1/notifications/policies_spec.rb b/spec/requests/api/v1/notifications/policies_spec.rb index 8bafcad2fe397d..ac24501526fa38 100644 --- a/spec/requests/api/v1/notifications/policies_spec.rb +++ b/spec/requests/api/v1/notifications/policies_spec.rb @@ -26,6 +26,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body).to include( filter_not_following: false, filter_not_followers: false, @@ -54,6 +56,8 @@ .to change { NotificationPolicy.find_or_initialize_by(account: user.account).for_not_following.to_sym }.from(:accept).to(:filter) expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body).to include( filter_not_following: true, filter_not_followers: false, diff --git a/spec/requests/api/v1/notifications/requests_spec.rb b/spec/requests/api/v1/notifications/requests_spec.rb index 030b7cfa212e50..bee9d3a3da93f3 100644 --- a/spec/requests/api/v1/notifications/requests_spec.rb +++ b/spec/requests/api/v1/notifications/requests_spec.rb @@ -26,6 +26,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end end @@ -43,6 +45,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(NotificationPermission.find_by(account: notification_request.account, from_account: notification_request.from_account)).to_not be_nil end @@ -53,6 +57,8 @@ subject expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end @@ -70,6 +76,8 @@ expect { subject }.to change(NotificationRequest, :count).by(-1) expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end context 'when notification request belongs to someone else' do @@ -79,6 +87,8 @@ subject expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end @@ -97,6 +107,8 @@ expect(NotificationPermission.find_by(account: notification_request.account, from_account: notification_request.from_account)).to_not be_nil expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end @@ -113,6 +125,8 @@ expect { subject }.to change(NotificationRequest, :count).by(-1) expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end @@ -128,6 +142,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body).to match({ merged: true }) end end @@ -141,6 +157,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body).to match({ merged: false }) end end diff --git a/spec/requests/api/v1/notifications_spec.rb b/spec/requests/api/v1/notifications_spec.rb index b74adb5dffc5e1..0e8eb6ad3ba2e6 100644 --- a/spec/requests/api/v1/notifications_spec.rb +++ b/spec/requests/api/v1/notifications_spec.rb @@ -31,6 +31,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body[:count]).to eq 5 end end @@ -45,6 +47,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body[:count]).to eq 2 end end @@ -56,6 +60,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body[:count]).to eq 4 end end @@ -67,6 +73,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body[:count]).to eq 2 end end @@ -80,6 +88,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body[:count]).to eq Api::V1::NotificationsController::DEFAULT_NOTIFICATIONS_COUNT_LIMIT end end @@ -111,6 +121,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body.size).to eq 5 expect(body_json_types).to include('reblog', 'mention', 'favourite', 'follow') expect(response.parsed_body.any? { |x| x[:filtered] }).to be false @@ -124,6 +136,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body.size).to eq 6 expect(body_json_types).to include('reblog', 'mention', 'favourite', 'follow') expect(response.parsed_body.any? { |x| x[:filtered] }).to be true @@ -137,6 +151,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(body_json_account_ids.uniq).to eq [tom.account.id.to_s] end @@ -152,6 +168,8 @@ def body_json_account_ids subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body.size).to eq 0 end end @@ -163,6 +181,8 @@ def body_json_account_ids subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body.size).to_not eq 0 expect(body_json_types.uniq).to_not include 'mention' end @@ -175,6 +195,8 @@ def body_json_account_ids subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(body_json_types.uniq).to eq ['mention'] end end @@ -216,6 +238,8 @@ def body_json_types subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end context 'when notification belongs to someone else' do @@ -225,6 +249,8 @@ def body_json_types subject expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end @@ -242,6 +268,8 @@ def body_json_types subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect { notification.reload }.to raise_error(ActiveRecord::RecordNotFound) end @@ -252,6 +280,8 @@ def body_json_types subject expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end @@ -272,6 +302,8 @@ def body_json_types expect(user.account.reload.notifications).to be_empty expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end end diff --git a/spec/requests/api/v1/peers/search_spec.rb b/spec/requests/api/v1/peers/search_spec.rb index dc5f550d0e092d..d00a2437f279d6 100644 --- a/spec/requests/api/v1/peers/search_spec.rb +++ b/spec/requests/api/v1/peers/search_spec.rb @@ -23,6 +23,8 @@ expect(response) .to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body) .to be_blank end @@ -34,6 +36,8 @@ expect(response) .to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body) .to be_blank end @@ -49,6 +53,8 @@ expect(response) .to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body.size) .to eq(1) expect(response.parsed_body.first) diff --git a/spec/requests/api/v1/polls/votes_spec.rb b/spec/requests/api/v1/polls/votes_spec.rb index 669f64b6e4577e..d3f7eb431d93b5 100644 --- a/spec/requests/api/v1/polls/votes_spec.rb +++ b/spec/requests/api/v1/polls/votes_spec.rb @@ -18,6 +18,8 @@ it 'creates a vote', :aggregate_failures do expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(vote).to_not be_nil expect(vote.choice).to eq 1 @@ -30,6 +32,8 @@ it 'returns http bad request' do expect(response).to have_http_status(400) + expect(response.content_type) + .to start_with('application/json') end end diff --git a/spec/requests/api/v1/polls_spec.rb b/spec/requests/api/v1/polls_spec.rb index 138a37a73ce990..fd38297931a155 100644 --- a/spec/requests/api/v1/polls_spec.rb +++ b/spec/requests/api/v1/polls_spec.rb @@ -23,6 +23,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body).to match( a_hash_including( id: poll.id.to_s, @@ -41,6 +43,8 @@ subject expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end diff --git a/spec/requests/api/v1/preferences_spec.rb b/spec/requests/api/v1/preferences_spec.rb index d6991ca90ca71e..e03b9cf1087cd6 100644 --- a/spec/requests/api/v1/preferences_spec.rb +++ b/spec/requests/api/v1/preferences_spec.rb @@ -14,6 +14,8 @@ expect(response) .to have_http_status(401) + expect(response.content_type) + .to start_with('application/json') end end @@ -34,6 +36,9 @@ expect(response) .to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') + expect(response.parsed_body) .to be_present end diff --git a/spec/requests/api/v1/profiles_spec.rb b/spec/requests/api/v1/profiles_spec.rb index 9616f4155974f3..fd3ab4bf587afd 100644 --- a/spec/requests/api/v1/profiles_spec.rb +++ b/spec/requests/api/v1/profiles_spec.rb @@ -32,6 +32,8 @@ delete '/api/v1/profile/avatar', headers: headers expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') account.reload expect(account.avatar).to_not exist @@ -53,6 +55,8 @@ delete '/api/v1/profile/header', headers: headers expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') account.reload expect(account.avatar).to exist diff --git a/spec/requests/api/v1/push/subscriptions_spec.rb b/spec/requests/api/v1/push/subscriptions_spec.rb index a9587f8d58343f..8ad672c95ee961 100644 --- a/spec/requests/api/v1/push/subscriptions_spec.rb +++ b/spec/requests/api/v1/push/subscriptions_spec.rb @@ -45,6 +45,8 @@ subject expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') expect(endpoint_push_subscriptions.count).to eq(0) expect(endpoint_push_subscription).to be_nil end diff --git a/spec/requests/api/v1/reports_spec.rb b/spec/requests/api/v1/reports_spec.rb index a176bd78a6ef32..18b894bf632714 100644 --- a/spec/requests/api/v1/reports_spec.rb +++ b/spec/requests/api/v1/reports_spec.rb @@ -37,6 +37,8 @@ emails = capture_emails { subject } expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body).to match( a_hash_including( status_ids: [status.id.to_s], @@ -65,6 +67,8 @@ subject expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end diff --git a/spec/requests/api/v1/scheduled_status_spec.rb b/spec/requests/api/v1/scheduled_status_spec.rb index eb03827c9ab0d5..3a1b81ce65c1d4 100644 --- a/spec/requests/api/v1/scheduled_status_spec.rb +++ b/spec/requests/api/v1/scheduled_status_spec.rb @@ -14,6 +14,8 @@ expect(response) .to have_http_status(401) + expect(response.content_type) + .to start_with('application/json') end end @@ -33,6 +35,8 @@ expect(response) .to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') end end @@ -45,6 +49,8 @@ expect(response) .to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body) .to_not be_present @@ -59,6 +65,8 @@ expect(response) .to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body) .to be_present diff --git a/spec/requests/api/v1/statuses/bookmarks_spec.rb b/spec/requests/api/v1/statuses/bookmarks_spec.rb index 6401a4370db7c4..583f5b6a0ef641 100644 --- a/spec/requests/api/v1/statuses/bookmarks_spec.rb +++ b/spec/requests/api/v1/statuses/bookmarks_spec.rb @@ -22,6 +22,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(user.account.bookmarked?(status)).to be true expect(response.parsed_body).to match( @@ -37,6 +39,8 @@ subject expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end @@ -51,6 +55,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(user.account.bookmarked?(status)).to be true end end @@ -60,6 +66,8 @@ post '/api/v1/statuses/-1/bookmark', headers: headers expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end @@ -70,6 +78,8 @@ subject expect(response).to have_http_status(401) + expect(response.content_type) + .to start_with('application/json') end end end @@ -93,6 +103,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(user.account.bookmarked?(status)).to be false expect(response.parsed_body).to match( @@ -113,6 +125,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(user.account.bookmarked?(status)).to be false expect(response.parsed_body).to match( @@ -126,6 +140,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end end @@ -137,6 +153,8 @@ subject expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end diff --git a/spec/requests/api/v1/statuses/favourited_by_accounts_spec.rb b/spec/requests/api/v1/statuses/favourited_by_accounts_spec.rb index 24bd03d343b207..6471697154c7df 100644 --- a/spec/requests/api/v1/statuses/favourited_by_accounts_spec.rb +++ b/spec/requests/api/v1/statuses/favourited_by_accounts_spec.rb @@ -33,6 +33,8 @@ prev: api_v1_status_favourited_by_index_url(limit: 2, since_id: Favourite.last.id), next: api_v1_status_favourited_by_index_url(limit: 2, max_id: Favourite.first.id) ) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body.size) .to eq(2) @@ -72,6 +74,8 @@ subject expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end @@ -88,6 +92,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end end diff --git a/spec/requests/api/v1/statuses/favourites_spec.rb b/spec/requests/api/v1/statuses/favourites_spec.rb index c3acf0413e7f58..3d1021e29d6ef9 100644 --- a/spec/requests/api/v1/statuses/favourites_spec.rb +++ b/spec/requests/api/v1/statuses/favourites_spec.rb @@ -22,6 +22,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(user.account.favourited?(status)).to be true expect(response.parsed_body).to match( @@ -37,6 +39,8 @@ subject expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end @@ -51,6 +55,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(user.account.favourited?(status)).to be true end end @@ -62,6 +68,8 @@ subject expect(response).to have_http_status(401) + expect(response.content_type) + .to start_with('application/json') end end end @@ -84,6 +92,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(user.account.favourited?(status)).to be false @@ -103,6 +113,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(user.account.favourited?(status)).to be false @@ -117,6 +129,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end @@ -127,6 +141,8 @@ subject expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end diff --git a/spec/requests/api/v1/statuses/histories_spec.rb b/spec/requests/api/v1/statuses/histories_spec.rb index 4115a52fa8cc0e..9c7f93d3437f86 100644 --- a/spec/requests/api/v1/statuses/histories_spec.rb +++ b/spec/requests/api/v1/statuses/histories_spec.rb @@ -18,6 +18,8 @@ it 'returns http success' do expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body.size).to_not be 0 end end diff --git a/spec/requests/api/v1/statuses/mutes_spec.rb b/spec/requests/api/v1/statuses/mutes_spec.rb index 69ae948852888a..55313482d6e8a4 100644 --- a/spec/requests/api/v1/statuses/mutes_spec.rb +++ b/spec/requests/api/v1/statuses/mutes_spec.rb @@ -18,6 +18,8 @@ it 'creates a conversation mute', :aggregate_failures do expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(ConversationMute.find_by(account: user.account, conversation_id: status.conversation_id)).to_not be_nil end end @@ -32,6 +34,8 @@ it 'destroys the conversation mute', :aggregate_failures do expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(ConversationMute.find_by(account: user.account, conversation_id: status.conversation_id)).to be_nil end end diff --git a/spec/requests/api/v1/statuses/pins_spec.rb b/spec/requests/api/v1/statuses/pins_spec.rb index 409c50e7c211e6..05d8f570cc8455 100644 --- a/spec/requests/api/v1/statuses/pins_spec.rb +++ b/spec/requests/api/v1/statuses/pins_spec.rb @@ -22,6 +22,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(user.account.pinned?(status)).to be true expect(response.parsed_body).to match( @@ -37,6 +39,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(user.account.pinned?(status)).to be true end end @@ -48,6 +52,8 @@ subject expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') end end @@ -56,6 +62,8 @@ post '/api/v1/statuses/-1/pin', headers: headers expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end @@ -66,6 +74,8 @@ subject expect(response).to have_http_status(401) + expect(response.content_type) + .to start_with('application/json') end end end @@ -86,6 +96,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(user.account.pinned?(status)).to be false expect(response.parsed_body).to match( @@ -99,6 +111,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end @@ -107,6 +121,8 @@ post '/api/v1/statuses/-1/unpin', headers: headers expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end @@ -117,6 +133,8 @@ subject expect(response).to have_http_status(401) + expect(response.content_type) + .to start_with('application/json') end end end diff --git a/spec/requests/api/v1/statuses/reblogged_by_accounts_spec.rb b/spec/requests/api/v1/statuses/reblogged_by_accounts_spec.rb index bd26c22f08e388..40457f6e894a57 100644 --- a/spec/requests/api/v1/statuses/reblogged_by_accounts_spec.rb +++ b/spec/requests/api/v1/statuses/reblogged_by_accounts_spec.rb @@ -32,6 +32,8 @@ prev: api_v1_status_reblogged_by_index_url(limit: 2, since_id: bob.statuses.first.id), next: api_v1_status_reblogged_by_index_url(limit: 2, max_id: alice.statuses.first.id) ) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body.size) .to eq(2) @@ -71,6 +73,8 @@ subject expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end @@ -87,6 +91,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end end diff --git a/spec/requests/api/v1/statuses/reblogs_spec.rb b/spec/requests/api/v1/statuses/reblogs_spec.rb index 8c7894d875b0b4..5e88690074e22d 100644 --- a/spec/requests/api/v1/statuses/reblogs_spec.rb +++ b/spec/requests/api/v1/statuses/reblogs_spec.rb @@ -19,6 +19,8 @@ context 'with public status' do it 'reblogs the status', :aggregate_failures do expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(status.reblogs.count).to eq 1 @@ -40,6 +42,8 @@ it 'returns http not found' do expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end @@ -55,6 +59,8 @@ it 'destroys the reblog', :aggregate_failures do expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(status.reblogs.count).to eq 0 @@ -80,6 +86,8 @@ it 'destroys the reblog', :aggregate_failures do expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(status.reblogs.count).to eq 0 @@ -103,6 +111,8 @@ it 'returns http not found' do expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end diff --git a/spec/requests/api/v1/statuses/sources_spec.rb b/spec/requests/api/v1/statuses/sources_spec.rb index eab19d64daa27b..6b769f35e1ad08 100644 --- a/spec/requests/api/v1/statuses/sources_spec.rb +++ b/spec/requests/api/v1/statuses/sources_spec.rb @@ -22,6 +22,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body).to match({ id: status.id.to_s, text: status.text, @@ -37,6 +39,8 @@ subject expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end @@ -51,6 +55,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body).to match({ id: status.id.to_s, text: status.text, @@ -66,6 +72,8 @@ subject expect(response).to have_http_status(401) + expect(response.content_type) + .to start_with('application/json') end end end diff --git a/spec/requests/api/v1/statuses/translations_spec.rb b/spec/requests/api/v1/statuses/translations_spec.rb index 047b2f0485548e..e316bd451b0b16 100644 --- a/spec/requests/api/v1/statuses/translations_spec.rb +++ b/spec/requests/api/v1/statuses/translations_spec.rb @@ -20,6 +20,8 @@ it 'returns http unprocessable entity' do expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') end end end @@ -38,6 +40,8 @@ it 'returns http success' do expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end end diff --git a/spec/requests/api/v1/statuses_spec.rb b/spec/requests/api/v1/statuses_spec.rb index 057800a266cae9..ddf5945d2562cd 100644 --- a/spec/requests/api/v1/statuses_spec.rb +++ b/spec/requests/api/v1/statuses_spec.rb @@ -18,6 +18,8 @@ get '/api/v1/statuses', headers: headers, params: { id: [status.id, other_status.id, 123_123] } expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body).to contain_exactly( hash_including(id: status.id.to_s), hash_including(id: other_status.id.to_s) @@ -39,6 +41,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end context 'when post includes filtered terms' do @@ -52,6 +56,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body[:filtered][0]).to include({ filter: a_hash_including({ id: user.account.custom_filters.first.id.to_s, @@ -75,6 +81,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body[:filtered][0]).to include({ filter: a_hash_including({ id: user.account.custom_filters.first.id.to_s, @@ -97,6 +105,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body[:reblog][:filtered][0]).to include({ filter: a_hash_including({ id: user.account.custom_filters.first.id.to_s, @@ -121,6 +131,8 @@ get "/api/v1/statuses/#{status.id}/context", headers: headers expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end @@ -139,6 +151,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.headers['X-RateLimit-Limit']).to eq RateLimiter::FAMILIES[:statuses][:limit].to_s expect(response.headers['X-RateLimit-Remaining']).to eq (RateLimiter::FAMILIES[:statuses][:limit] - 1).to_s end @@ -154,6 +168,8 @@ subject expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body[:unexpected_accounts].map { |a| a.slice(:id, :acct) }).to match [{ id: bob.id.to_s, acct: bob.acct }] end end @@ -165,6 +181,8 @@ subject expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') expect(response.headers['X-RateLimit-Limit']).to eq RateLimiter::FAMILIES[:statuses][:limit].to_s end end @@ -179,6 +197,8 @@ subject expect(response).to have_http_status(429) + expect(response.content_type) + .to start_with('application/json') expect(response.headers['X-RateLimit-Limit']).to eq RateLimiter::FAMILIES[:statuses][:limit].to_s expect(response.headers['X-RateLimit-Remaining']).to eq '0' end @@ -191,6 +211,8 @@ subject expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end @@ -202,6 +224,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end it 'creates a scheduled status' do @@ -215,6 +239,8 @@ subject expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') expect(account.scheduled_statuses).to be_empty end end @@ -235,6 +261,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(Status.find_by(id: status.id)).to be_nil end end @@ -253,6 +281,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(status.reload.text).to eq 'I am updated' end end @@ -267,6 +297,8 @@ get "/api/v1/statuses/#{status.id}" expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end @@ -279,6 +311,8 @@ get "/api/v1/statuses/#{status.id}/context" expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end @@ -291,6 +325,8 @@ get "/api/v1/statuses/#{status.id}" expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end @@ -303,6 +339,8 @@ get "/api/v1/statuses/#{status.id}/context" expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end end diff --git a/spec/requests/api/v1/streaming_spec.rb b/spec/requests/api/v1/streaming_spec.rb index a1f64846cf7d6a..62b43d657a5f25 100644 --- a/spec/requests/api/v1/streaming_spec.rb +++ b/spec/requests/api/v1/streaming_spec.rb @@ -17,6 +17,8 @@ get '/api/v1/streaming' expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end diff --git a/spec/requests/api/v1/suggestions_spec.rb b/spec/requests/api/v1/suggestions_spec.rb index b971f8812961f4..0a32d8899bce43 100644 --- a/spec/requests/api/v1/suggestions_spec.rb +++ b/spec/requests/api/v1/suggestions_spec.rb @@ -27,6 +27,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body) .to contain_exactly(include(id: bob.id.to_s), include(id: jeff.id.to_s)) end @@ -48,6 +50,8 @@ subject expect(response).to have_http_status(401) + expect(response.content_type) + .to start_with('application/json') end end end @@ -71,6 +75,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(FollowRecommendationMute.exists?(account: user.account, target_account: jeff)).to be true end diff --git a/spec/requests/api/v1/tags_spec.rb b/spec/requests/api/v1/tags_spec.rb index 9637823d457a1c..f6ff7c614fe328 100644 --- a/spec/requests/api/v1/tags_spec.rb +++ b/spec/requests/api/v1/tags_spec.rb @@ -21,6 +21,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body[:name]).to eq(name) end end @@ -32,6 +34,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end @@ -42,6 +46,8 @@ subject expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end @@ -61,6 +67,8 @@ subject expect(response).to have_http_status(:success) + expect(response.content_type) + .to start_with('application/json') expect(TagFollow.where(tag: tag, account: user.account)).to exist end end @@ -72,6 +80,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(Tag.where(name: name)).to exist expect(TagFollow.where(tag: Tag.find_by(name: name), account: user.account)).to exist end @@ -84,6 +94,8 @@ subject expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end @@ -95,6 +107,8 @@ subject expect(response).to have_http_status(401) + expect(response.content_type) + .to start_with('application/json') end end end @@ -117,6 +131,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(TagFollow.where(tag: tag, account: user.account)).to_not exist end @@ -127,6 +143,8 @@ subject expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end @@ -138,6 +156,8 @@ subject expect(response).to have_http_status(401) + expect(response.content_type) + .to start_with('application/json') end end end diff --git a/spec/requests/api/v1/timelines/home_spec.rb b/spec/requests/api/v1/timelines/home_spec.rb index 19a6f3adbc1521..2023b189ecb8a0 100644 --- a/spec/requests/api/v1/timelines/home_spec.rb +++ b/spec/requests/api/v1/timelines/home_spec.rb @@ -35,6 +35,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body.pluck(:id)).to match_array(home_statuses.map { |status| status.id.to_s }) end @@ -52,6 +54,8 @@ prev: api_v1_timelines_home_url(limit: params[:limit], min_id: ana.statuses.first.id), next: api_v1_timelines_home_url(limit: params[:limit], max_id: ana.statuses.first.id) ) + expect(response.content_type) + .to start_with('application/json') end end end @@ -67,6 +71,8 @@ subject expect(response).to have_http_status(206) + expect(response.content_type) + .to start_with('application/json') end end @@ -77,6 +83,8 @@ subject expect(response).to have_http_status(401) + expect(response.content_type) + .to start_with('application/json') end end @@ -89,6 +97,8 @@ expect(response) .to have_http_status(422) .and not_have_http_link_header + expect(response.content_type) + .to start_with('application/json') end end end diff --git a/spec/requests/api/v1/timelines/link_spec.rb b/spec/requests/api/v1/timelines/link_spec.rb index e1d421fb7af04b..37a3b36872f076 100644 --- a/spec/requests/api/v1/timelines/link_spec.rb +++ b/spec/requests/api/v1/timelines/link_spec.rb @@ -13,6 +13,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body.pluck(:id)).to match_array(expected_statuses.map { |status| status.id.to_s }) end end @@ -50,6 +52,8 @@ subject expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end @@ -62,6 +66,8 @@ subject expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end @@ -74,6 +80,8 @@ subject expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end @@ -91,6 +99,8 @@ subject expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') end end @@ -101,6 +111,8 @@ subject expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') end end @@ -127,6 +139,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body.size).to eq(params[:limit]) expect(response) diff --git a/spec/requests/api/v1/timelines/list_spec.rb b/spec/requests/api/v1/timelines/list_spec.rb index eb4395d1f97b54..1be754f264d750 100644 --- a/spec/requests/api/v1/timelines/list_spec.rb +++ b/spec/requests/api/v1/timelines/list_spec.rb @@ -23,6 +23,8 @@ get "/api/v1/timelines/list/#{list.id}", headers: headers expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end end @@ -36,6 +38,8 @@ get "/api/v1/timelines/list/#{list.id}", headers: headers expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end diff --git a/spec/requests/api/v1/timelines/public_spec.rb b/spec/requests/api/v1/timelines/public_spec.rb index 759e236d05b1e4..1e459bf3ec1b44 100644 --- a/spec/requests/api/v1/timelines/public_spec.rb +++ b/spec/requests/api/v1/timelines/public_spec.rb @@ -13,6 +13,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body.pluck(:id)).to match_array(expected_statuses.map { |status| status.id.to_s }) end end @@ -77,15 +79,13 @@ context 'with limit param' do let(:params) { { limit: 1 } } - it 'returns only the requested number of statuses', :aggregate_failures do + it 'returns only the requested number of statuses and sets pagination headers', :aggregate_failures do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body.size).to eq(params[:limit]) - end - - it 'sets the correct pagination headers', :aggregate_failures do - subject expect(response) .to include_pagination_headers( @@ -110,6 +110,8 @@ subject expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') end end @@ -120,6 +122,8 @@ subject expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') end end diff --git a/spec/requests/api/v1/timelines/tag_spec.rb b/spec/requests/api/v1/timelines/tag_spec.rb index 03d34e59f104ec..75348fe1b4efb6 100644 --- a/spec/requests/api/v1/timelines/tag_spec.rb +++ b/spec/requests/api/v1/timelines/tag_spec.rb @@ -19,6 +19,8 @@ expect(response) .to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body.pluck(:id)) .to match_array(expected_statuses.map { |status| status.id.to_s }) .and not_include(private_status.id) @@ -81,6 +83,8 @@ prev: api_v1_timelines_tag_url(limit: params[:limit], min_id: love_status.id), next: api_v1_timelines_tag_url(limit: params[:limit], max_id: love_status.id) ) + expect(response.content_type) + .to start_with('application/json') end end @@ -107,6 +111,8 @@ subject expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') end end diff --git a/spec/requests/api/v1/trends/links_spec.rb b/spec/requests/api/v1/trends/links_spec.rb index 04d36da0a26789..b1b77e7fd8c7ce 100644 --- a/spec/requests/api/v1/trends/links_spec.rb +++ b/spec/requests/api/v1/trends/links_spec.rb @@ -13,6 +13,8 @@ expect(response) .to have_http_status(200) .and not_have_http_link_header + expect(response.content_type) + .to start_with('application/json') end end @@ -27,6 +29,8 @@ expect(response) .to have_http_status(200) .and have_http_link_header(api_v1_trends_links_url(offset: 2)).for(rel: 'next') + expect(response.content_type) + .to start_with('application/json') end def prepare_trends diff --git a/spec/requests/api/v1/trends/statuses_spec.rb b/spec/requests/api/v1/trends/statuses_spec.rb index f04addfe0ac988..fe00c9c6458564 100644 --- a/spec/requests/api/v1/trends/statuses_spec.rb +++ b/spec/requests/api/v1/trends/statuses_spec.rb @@ -13,6 +13,8 @@ expect(response) .to have_http_status(200) .and not_have_http_link_header + expect(response.content_type) + .to start_with('application/json') end end @@ -27,6 +29,8 @@ expect(response) .to have_http_status(200) .and have_http_link_header(api_v1_trends_statuses_url(offset: 2)).for(rel: 'next') + expect(response.content_type) + .to start_with('application/json') end def prepare_trends diff --git a/spec/requests/api/v1/trends/tags_spec.rb b/spec/requests/api/v1/trends/tags_spec.rb index 2ff51eed6327a6..14ab73fc96c30e 100644 --- a/spec/requests/api/v1/trends/tags_spec.rb +++ b/spec/requests/api/v1/trends/tags_spec.rb @@ -13,6 +13,8 @@ expect(response) .to have_http_status(200) .and not_have_http_link_header + expect(response.content_type) + .to start_with('application/json') end end @@ -27,6 +29,8 @@ expect(response) .to have_http_status(200) .and have_http_link_header(api_v1_trends_tags_url(offset: 2)).for(rel: 'next') + expect(response.content_type) + .to start_with('application/json') end def prepare_trends diff --git a/spec/requests/api/web/settings_spec.rb b/spec/requests/api/web/settings_spec.rb index 81b8b449535a18..3873bc179b62d1 100644 --- a/spec/requests/api/web/settings_spec.rb +++ b/spec/requests/api/web/settings_spec.rb @@ -17,6 +17,8 @@ expect(response) .to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end @@ -29,6 +31,8 @@ expect(response) .to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') end end From 171394e914b4f3cc15b6659a45ecc210e42ee004 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Fri, 20 Sep 2024 09:13:47 -0400 Subject: [PATCH 41/51] Add coverage for CSV responses for severed relationships (#31962) --- spec/requests/severed_relationships_spec.rb | 43 +++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 spec/requests/severed_relationships_spec.rb diff --git a/spec/requests/severed_relationships_spec.rb b/spec/requests/severed_relationships_spec.rb new file mode 100644 index 00000000000000..ac98ab8f94fdbe --- /dev/null +++ b/spec/requests/severed_relationships_spec.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'Severed Relationships' do + let(:account_rs_event) { Fabricate :account_relationship_severance_event } + + before { sign_in Fabricate(:user) } + + describe 'GET /severed_relationships/:id/following' do + it 'returns a CSV file with correct data' do + get following_severed_relationship_path(account_rs_event, format: :csv) + + expect(response) + .to have_http_status(200) + expect(response.content_type) + .to start_with('text/csv') + expect(response.headers['Content-Disposition']) + .to match(<<~FILENAME.squish) + attachment; filename="following-example.com-#{Date.current}.csv" + FILENAME + expect(response.body) + .to include('Account address') + end + end + + describe 'GET /severed_relationships/:id/followers' do + it 'returns a CSV file with correct data' do + get followers_severed_relationship_path(account_rs_event, format: :csv) + + expect(response) + .to have_http_status(200) + expect(response.content_type) + .to start_with('text/csv') + expect(response.headers['Content-Disposition']) + .to match(<<~FILENAME.squish) + attachment; filename="followers-example.com-#{Date.current}.csv" + FILENAME + expect(response.body) + .to include('Account address') + end + end +end From d55f4fbda1967ab0889e6553dfad9f91bb2805f0 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Fri, 20 Sep 2024 09:19:53 -0400 Subject: [PATCH 42/51] Add content type checks to api/v2 request specs (#31983) --- spec/requests/api/v2/admin/accounts_spec.rb | 12 ++++++ spec/requests/api/v2/filters/keywords_spec.rb | 20 ++++++++++ spec/requests/api/v2/filters/statuses_spec.rb | 14 +++++++ spec/requests/api/v2/filters_spec.rb | 28 ++++++++++++++ spec/requests/api/v2/instance_spec.rb | 6 +++ spec/requests/api/v2/media_spec.rb | 12 ++++++ .../api/v2/notifications/accounts_spec.rb | 4 ++ .../api/v2/notifications/policies_spec.rb | 4 ++ spec/requests/api/v2/notifications_spec.rb | 38 +++++++++++++++++++ spec/requests/api/v2/search_spec.rb | 24 ++++++++++++ spec/requests/api/v2/suggestions_spec.rb | 2 + spec/requests/api/web/embeds_spec.rb | 24 ++++++++++++ 12 files changed, 188 insertions(+) diff --git a/spec/requests/api/v2/admin/accounts_spec.rb b/spec/requests/api/v2/admin/accounts_spec.rb index 17c38e2e557b85..bc3db4f8860fcf 100644 --- a/spec/requests/api/v2/admin/accounts_spec.rb +++ b/spec/requests/api/v2/admin/accounts_spec.rb @@ -34,6 +34,8 @@ it 'returns the correct accounts' do expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(body_json_ids).to eq([admin_account.id]) end end @@ -43,6 +45,8 @@ it 'returns the correct accounts' do expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(body_json_ids).to include(remote_account.id) expect(body_json_ids).to_not include(other_remote_account.id) end @@ -53,6 +57,8 @@ it 'returns the correct accounts' do expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(body_json_ids).to include(suspended_remote.id, suspended_account.id) end end @@ -62,6 +68,8 @@ it 'returns the correct accounts' do expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(body_json_ids).to include(disabled_account.id) end end @@ -71,6 +79,8 @@ it 'returns the correct accounts' do expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(body_json_ids).to include(pending_account.id) end end @@ -85,6 +95,8 @@ def body_json_ids it 'sets the correct pagination headers' do expect(response) .to include_pagination_headers(next: api_v2_admin_accounts_url(limit: 1, max_id: admin_account.id)) + expect(response.content_type) + .to start_with('application/json') end end end diff --git a/spec/requests/api/v2/filters/keywords_spec.rb b/spec/requests/api/v2/filters/keywords_spec.rb index a31accaa5cd72d..f0d62e9ecc6ae3 100644 --- a/spec/requests/api/v2/filters/keywords_spec.rb +++ b/spec/requests/api/v2/filters/keywords_spec.rb @@ -17,6 +17,8 @@ it 'returns http success' do get "/api/v2/filters/#{filter.id}/keywords", headers: headers expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body) .to contain_exactly( include(id: keyword.id.to_s) @@ -27,6 +29,8 @@ it 'returns http not found' do get "/api/v2/filters/#{other_filter.id}/keywords", headers: headers expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end @@ -41,6 +45,8 @@ it 'creates a filter', :aggregate_failures do expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body) .to include( @@ -58,6 +64,8 @@ it 'returns http not found' do expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end @@ -72,6 +80,8 @@ it 'responds with the keyword', :aggregate_failures do expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body) .to include( @@ -85,6 +95,8 @@ it 'returns http not found' do expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end @@ -99,6 +111,8 @@ it 'updates the keyword', :aggregate_failures do expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(keyword.reload.keyword).to eq 'updated' end @@ -108,6 +122,8 @@ it 'returns http not found' do expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end @@ -122,6 +138,8 @@ it 'destroys the keyword', :aggregate_failures do expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect { keyword.reload }.to raise_error ActiveRecord::RecordNotFound end @@ -131,6 +149,8 @@ it 'returns http not found' do expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end diff --git a/spec/requests/api/v2/filters/statuses_spec.rb b/spec/requests/api/v2/filters/statuses_spec.rb index aed8934a5e3838..f20a6ae5e6d245 100644 --- a/spec/requests/api/v2/filters/statuses_spec.rb +++ b/spec/requests/api/v2/filters/statuses_spec.rb @@ -17,6 +17,8 @@ it 'returns http success' do get "/api/v2/filters/#{filter.id}/statuses", headers: headers expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body) .to contain_exactly( include(id: status_filter.id.to_s) @@ -27,6 +29,8 @@ it 'returns http not found' do get "/api/v2/filters/#{other_filter.id}/statuses", headers: headers expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end @@ -42,6 +46,8 @@ it 'creates a filter', :aggregate_failures do expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body) .to include( @@ -58,6 +64,8 @@ it 'returns http not found' do expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end @@ -72,6 +80,8 @@ it 'responds with the filter', :aggregate_failures do expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body) .to include( @@ -98,6 +108,8 @@ it 'destroys the filter', :aggregate_failures do expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect { status_filter.reload }.to raise_error ActiveRecord::RecordNotFound end @@ -107,6 +119,8 @@ it 'returns http not found' do expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end diff --git a/spec/requests/api/v2/filters_spec.rb b/spec/requests/api/v2/filters_spec.rb index ad8b55973e2bc2..3b5c44cefa3e15 100644 --- a/spec/requests/api/v2/filters_spec.rb +++ b/spec/requests/api/v2/filters_spec.rb @@ -15,6 +15,8 @@ subject expect(response).to have_http_status(401) + expect(response.content_type) + .to start_with('application/json') end end @@ -32,6 +34,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body.pluck(:id)).to match_array(filters.map { |filter| filter.id.to_s }) end end @@ -53,6 +57,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body) .to include( @@ -81,6 +87,8 @@ subject expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') end end @@ -91,6 +99,8 @@ subject expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') end end @@ -101,6 +111,8 @@ subject expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') end end end @@ -119,6 +131,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body) .to include( id: filter.id.to_s @@ -132,6 +146,8 @@ subject expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end @@ -158,6 +174,8 @@ filter.reload expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(filter.title).to eq 'updated' expect(filter.reload.context).to eq %w(home public) end @@ -170,6 +188,8 @@ subject expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') end end end @@ -185,6 +205,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(keyword.reload.keyword).to eq 'updated' @@ -199,6 +221,8 @@ subject expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end @@ -217,6 +241,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect { filter.reload }.to raise_error ActiveRecord::RecordNotFound end @@ -228,6 +254,8 @@ subject expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end diff --git a/spec/requests/api/v2/instance_spec.rb b/spec/requests/api/v2/instance_spec.rb index d484dc7c463ef0..fae92b739108c4 100644 --- a/spec/requests/api/v2/instance_spec.rb +++ b/spec/requests/api/v2/instance_spec.rb @@ -15,6 +15,9 @@ expect(response) .to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') + expect(response.parsed_body) .to be_present .and include(title: 'Mastodon') @@ -30,6 +33,9 @@ expect(response) .to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') + expect(response.parsed_body) .to be_present .and include(title: 'Mastodon') diff --git a/spec/requests/api/v2/media_spec.rb b/spec/requests/api/v2/media_spec.rb index 06ce0053e87689..70e0679f57442c 100644 --- a/spec/requests/api/v2/media_spec.rb +++ b/spec/requests/api/v2/media_spec.rb @@ -21,6 +21,9 @@ expect(response) .to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') + expect(response.parsed_body) .to be_a(Hash) end @@ -38,6 +41,9 @@ expect(response) .to have_http_status(202) + expect(response.content_type) + .to start_with('application/json') + expect(response.parsed_body) .to be_a(Hash) end @@ -63,6 +69,9 @@ expect(response) .to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') + expect(response.parsed_body) .to be_a(Hash) .and include(error: /File type/) @@ -80,6 +89,9 @@ expect(response) .to have_http_status(500) + expect(response.content_type) + .to start_with('application/json') + expect(response.parsed_body) .to be_a(Hash) .and include(error: /processing/) diff --git a/spec/requests/api/v2/notifications/accounts_spec.rb b/spec/requests/api/v2/notifications/accounts_spec.rb index 102b009c0b9f4a..29880a4c0a6c6a 100644 --- a/spec/requests/api/v2/notifications/accounts_spec.rb +++ b/spec/requests/api/v2/notifications/accounts_spec.rb @@ -30,6 +30,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') # The group we are interested in is only favorites notifications = user.account.notifications.where(type: 'favourite').reorder(id: :desc) @@ -55,6 +57,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') # The group we are interested in is only favorites notifications = user.account.notifications.where(type: 'favourite').reorder(id: :desc) diff --git a/spec/requests/api/v2/notifications/policies_spec.rb b/spec/requests/api/v2/notifications/policies_spec.rb index dc205b6ebb6d25..f080bc730fdcd1 100644 --- a/spec/requests/api/v2/notifications/policies_spec.rb +++ b/spec/requests/api/v2/notifications/policies_spec.rb @@ -26,6 +26,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body).to include( for_not_following: 'accept', for_not_followers: 'accept', @@ -56,6 +58,8 @@ .and change { NotificationPolicy.find_or_initialize_by(account: user.account).for_limited_accounts.to_sym }.from(:filter).to(:drop) expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body).to include( for_not_following: 'filter', for_not_followers: 'accept', diff --git a/spec/requests/api/v2/notifications_spec.rb b/spec/requests/api/v2/notifications_spec.rb index 9522a39e0f55a4..ffa0a71c779e1b 100644 --- a/spec/requests/api/v2/notifications_spec.rb +++ b/spec/requests/api/v2/notifications_spec.rb @@ -31,6 +31,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body[:count]).to eq 4 end end @@ -42,6 +44,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body[:count]).to eq 5 end end @@ -56,6 +60,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body[:count]).to eq 2 end end @@ -67,6 +73,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body[:count]).to eq 3 end end @@ -78,6 +86,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body[:count]).to eq 2 end end @@ -91,6 +101,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body[:count]).to eq Api::V2::NotificationsController::DEFAULT_NOTIFICATIONS_COUNT_LIMIT end end @@ -125,6 +137,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body[:notification_groups]).to eq [] end end @@ -134,6 +148,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(body_json_types).to include('reblog', 'mention', 'favourite', 'follow') end end @@ -145,6 +161,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body[:notification_groups]).to contain_exactly( a_hash_including( type: 'reblog', @@ -177,6 +195,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body.size).to_not eq 0 expect(body_json_types.uniq).to_not include 'mention' end @@ -189,6 +209,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(body_json_types.uniq).to eq ['mention'] expect(response.parsed_body.dig(:notification_groups, 0, :page_min_id)).to_not be_nil end @@ -211,6 +233,8 @@ # not the last that has been skipped, so pagination is very likely to give overlap next: api_v2_notifications_url(limit: params[:limit], max_id: notifications[3].id) ) + expect(response.content_type) + .to start_with('application/json') end end @@ -224,6 +248,8 @@ expect(response.parsed_body[:notification_groups].size) .to eq(2) + expect(response.content_type) + .to start_with('application/json') expect(response) .to include_pagination_headers( prev: api_v2_notifications_url(limit: params[:limit], min_id: notifications.first.id), @@ -247,6 +273,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body[:partial_accounts].size).to be > 0 expect(response.parsed_body[:partial_accounts][0].keys.map(&:to_sym)).to contain_exactly(:acct, :avatar, :avatar_static, :bot, :id, :locked, :url) expect(response.parsed_body[:partial_accounts].pluck(:id)).to_not include(recent_account.id.to_s) @@ -261,6 +289,8 @@ subject expect(response).to have_http_status(400) + expect(response.content_type) + .to start_with('application/json') end end @@ -282,6 +312,8 @@ def body_json_types subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end context 'when notification belongs to someone else' do @@ -291,6 +323,8 @@ def body_json_types subject expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end @@ -308,6 +342,8 @@ def body_json_types subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect { notification.reload }.to raise_error(ActiveRecord::RecordNotFound) end @@ -341,6 +377,8 @@ def body_json_types expect(user.account.reload.notifications).to be_empty expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end end diff --git a/spec/requests/api/v2/search_spec.rb b/spec/requests/api/v2/search_spec.rb index a59ec7ca6b1807..5a2346dc39eda0 100644 --- a/spec/requests/api/v2/search_spec.rb +++ b/spec/requests/api/v2/search_spec.rb @@ -19,6 +19,8 @@ get '/api/v2/search', headers: headers, params: params expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end context 'when searching accounts' do @@ -37,6 +39,8 @@ get '/api/v2/search', headers: headers, params: params expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end @@ -47,6 +51,8 @@ get '/api/v2/search', headers: headers, params: params expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end @@ -57,6 +63,8 @@ get '/api/v2/search', headers: headers, params: params expect(response).to have_http_status(400) + expect(response.content_type) + .to start_with('application/json') end end @@ -67,6 +75,8 @@ get '/api/v2/search', headers: headers, params: params expect(response).to have_http_status(400) + expect(response.content_type) + .to start_with('application/json') end end @@ -92,6 +102,8 @@ get '/api/v2/search', headers: headers, params: params expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') end end @@ -102,6 +114,8 @@ get '/api/v2/search', headers: headers, params: params expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end @@ -118,6 +132,8 @@ context 'without a `q` param' do it 'returns http bad_request' do expect(response).to have_http_status(400) + expect(response.content_type) + .to start_with('application/json') end end @@ -126,6 +142,8 @@ it 'returns http success' do expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end @@ -134,6 +152,8 @@ it 'returns http success' do expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end context 'with truthy `resolve`' do @@ -141,6 +161,8 @@ it 'returns http unauthorized' do expect(response).to have_http_status(401) + expect(response.content_type) + .to start_with('application/json') expect(response.body).to match('resolve remote resources') end end @@ -150,6 +172,8 @@ it 'returns http unauthorized' do expect(response).to have_http_status(401) + expect(response.content_type) + .to start_with('application/json') expect(response.body).to match('pagination is not supported') end end diff --git a/spec/requests/api/v2/suggestions_spec.rb b/spec/requests/api/v2/suggestions_spec.rb index e92507ed661fe8..099d9bc3b22735 100644 --- a/spec/requests/api/v2/suggestions_spec.rb +++ b/spec/requests/api/v2/suggestions_spec.rb @@ -21,6 +21,8 @@ get '/api/v2/suggestions', headers: headers expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body).to match_array( [bob, jeff].map do |account| diff --git a/spec/requests/api/web/embeds_spec.rb b/spec/requests/api/web/embeds_spec.rb index 2b28502835843c..3cc2f977f87fb5 100644 --- a/spec/requests/api/web/embeds_spec.rb +++ b/spec/requests/api/web/embeds_spec.rb @@ -18,6 +18,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body[:html]).to be_present end end @@ -29,6 +31,8 @@ subject expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end @@ -42,6 +46,8 @@ subject expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end @@ -52,6 +58,8 @@ subject expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end @@ -71,6 +79,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body[:html]).to be_present end @@ -83,6 +93,8 @@ subject expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end @@ -98,6 +110,8 @@ subject expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end @@ -123,6 +137,8 @@ subject expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end @@ -133,6 +149,8 @@ subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body[:html]).to be_present end end @@ -146,6 +164,8 @@ subject expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end @@ -156,6 +176,8 @@ subject expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end @@ -167,6 +189,8 @@ subject expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end From ed8b0e4b1ea9df593aff6d831bfb9ad8fe0ed32d Mon Sep 17 00:00:00 2001 From: Claire Date: Fri, 20 Sep 2024 15:33:26 +0200 Subject: [PATCH 43/51] Fix links for reblogs in moderation interface (#31979) --- app/views/admin/reports/_status.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/admin/reports/_status.html.haml b/app/views/admin/reports/_status.html.haml index e0870503d6c70c..f4630ed25ae978 100644 --- a/app/views/admin/reports/_status.html.haml +++ b/app/views/admin/reports/_status.html.haml @@ -18,7 +18,7 @@ - if status.application = status.application.name · - = link_to ActivityPub::TagManager.instance.url_for(status), class: 'detailed-status__datetime', target: stream_link_target, rel: 'noopener noreferrer' do + = link_to ActivityPub::TagManager.instance.url_for(status.proper), class: 'detailed-status__datetime', target: stream_link_target, rel: 'noopener noreferrer' do %time.formatted{ datetime: status.created_at.iso8601, title: l(status.created_at) }= l(status.created_at) - if status.edited? · From 7ed9c590b98610f8d68deab9ef8df260eec6d8f0 Mon Sep 17 00:00:00 2001 From: Claire Date: Fri, 20 Sep 2024 16:58:06 +0200 Subject: [PATCH 44/51] Fix issue when encountering reblog of deleted post in feed rebuild (#32001) --- app/lib/feed_manager.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/lib/feed_manager.rb b/app/lib/feed_manager.rb index 1fb224a1337c50..97cb25d58f5112 100644 --- a/app/lib/feed_manager.rb +++ b/app/lib/feed_manager.rb @@ -558,7 +558,7 @@ def build_crutches(receiver_id, statuses) arr = crutches[:active_mentions][s.id] || [] arr.push(s.account_id) - if s.reblog? + if s.reblog? && s.reblog.present? arr.push(s.reblog.account_id) arr.concat(crutches[:active_mentions][s.reblog_of_id] || []) end From 0820cbcb35cf40b546993611f5226cf45d8330ef Mon Sep 17 00:00:00 2001 From: Christian Schmidt Date: Wed, 18 Sep 2024 10:43:24 +0200 Subject: [PATCH 45/51] [Glitch] Mute XHR abort errors Port 7740f1a6bb6c34a1d4fdfeaa29c31ac24fd4b236 to glitch-soc Signed-off-by: Claire --- app/javascript/flavours/glitch/actions/alerts.js | 7 +++++++ app/javascript/flavours/glitch/api.ts | 3 +++ 2 files changed, 10 insertions(+) diff --git a/app/javascript/flavours/glitch/actions/alerts.js b/app/javascript/flavours/glitch/actions/alerts.js index 42834146bf5ba6..48dee2587fe523 100644 --- a/app/javascript/flavours/glitch/actions/alerts.js +++ b/app/javascript/flavours/glitch/actions/alerts.js @@ -1,5 +1,7 @@ import { defineMessages } from 'react-intl'; +import { AxiosError } from 'axios'; + const messages = defineMessages({ unexpectedTitle: { id: 'alert.unexpected.title', defaultMessage: 'Oops!' }, unexpectedMessage: { id: 'alert.unexpected.message', defaultMessage: 'An unexpected error occurred.' }, @@ -50,6 +52,11 @@ export const showAlertForError = (error, skipNotFound = false) => { }); } + // An aborted request, e.g. due to reloading the browser window, it not really error + if (error.code === AxiosError.ECONNABORTED) { + return { type: ALERT_NOOP }; + } + console.error(error); return showAlert({ diff --git a/app/javascript/flavours/glitch/api.ts b/app/javascript/flavours/glitch/api.ts index 24672290c74f94..25bb25547cf94d 100644 --- a/app/javascript/flavours/glitch/api.ts +++ b/app/javascript/flavours/glitch/api.ts @@ -42,6 +42,9 @@ const authorizationTokenFromInitialState = (): RawAxiosRequestHeaders => { // eslint-disable-next-line import/no-default-export export default function api(withAuthorization = true) { return axios.create({ + transitional: { + clarifyTimeoutError: true, + }, headers: { ...csrfHeader, ...(withAuthorization ? authorizationTokenFromInitialState() : {}), From d77348f830c5725ef3025cb07d79220300247725 Mon Sep 17 00:00:00 2001 From: Renaud Chaput Date: Wed, 18 Sep 2024 19:39:15 +0200 Subject: [PATCH 46/51] [Glitch] Fix the appearance of avatars when they do not load Port 8b708340356e18696e5ab9e83067ce9297e481f7 to glitch-soc Signed-off-by: Claire --- app/javascript/flavours/glitch/styles/components.scss | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/javascript/flavours/glitch/styles/components.scss b/app/javascript/flavours/glitch/styles/components.scss index d58c54369db320..7539fb6ddc5610 100644 --- a/app/javascript/flavours/glitch/styles/components.scss +++ b/app/javascript/flavours/glitch/styles/components.scss @@ -2196,13 +2196,14 @@ body > [data-popper-placement] { display: block; position: relative; border-radius: var(--avatar-border-radius); + background-color: var(--surface-background-color); img { - display: block; width: 100%; height: 100%; object-fit: cover; border-radius: var(--avatar-border-radius); + display: inline-block; // to not show broken images } &-inline { From 80cb285819a4544f732fcc3315c8283471771d98 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Wed, 18 Sep 2024 19:39:32 +0200 Subject: [PATCH 47/51] [Glitch] Fix sass deprecation warning Port 29656cb9e0e5fbecdec5bc5f4e8fc2249e1b8c4e to glitch-soc Signed-off-by: Claire --- .../glitch/styles/mastodon-light/variables.scss | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/app/javascript/flavours/glitch/styles/mastodon-light/variables.scss b/app/javascript/flavours/glitch/styles/mastodon-light/variables.scss index 76ede2623319f8..76bdc4022e3ef0 100644 --- a/app/javascript/flavours/glitch/styles/mastodon-light/variables.scss +++ b/app/javascript/flavours/glitch/styles/mastodon-light/variables.scss @@ -1,3 +1,5 @@ +@use 'sass:color'; + // Dependent colors $black: #000000; $white: #ffffff; @@ -47,11 +49,19 @@ $account-background-color: $white !default; // Invert darkened and lightened colors @function darken($color, $amount) { - @return hsl(hue($color), saturation($color), lightness($color) + $amount); + @return hsl( + hue($color), + color.channel($color, 'saturation', $space: hsl), + color.channel($color, 'lightness', $space: hsl) + $amount + ); } @function lighten($color, $amount) { - @return hsl(hue($color), saturation($color), lightness($color) - $amount); + @return hsl( + hue($color), + color.channel($color, 'saturation', $space: hsl), + color.channel($color, 'lightness', $space: hsl) - $amount + ); } $emojis-requiring-inversion: 'chains'; From 9bd58386463adf2f312cc13915559958fd22c430 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Thu, 19 Sep 2024 12:52:46 +0200 Subject: [PATCH 48/51] [Glitch] Fix browser glitch caused by two overlapping scroll animations in web UI Port ef4d6ab98875891716fa2b9ce22ed34afc58a53f to glitch-soc Signed-off-by: Claire --- .../features/ui/components/columns_area.jsx | 32 +------------------ app/javascript/flavours/glitch/scroll.ts | 27 ++++++++++------ 2 files changed, 18 insertions(+), 41 deletions(-) diff --git a/app/javascript/flavours/glitch/features/ui/components/columns_area.jsx b/app/javascript/flavours/glitch/features/ui/components/columns_area.jsx index f76a8e5beb7fea..9471104b97f48d 100644 --- a/app/javascript/flavours/glitch/features/ui/components/columns_area.jsx +++ b/app/javascript/flavours/glitch/features/ui/components/columns_area.jsx @@ -4,8 +4,6 @@ import { Children, cloneElement, useCallback } from 'react'; import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; -import { supportsPassiveEvents } from 'detect-passive-events'; - import { scrollRight } from '../../../scroll'; import BundleContainer from '../containers/bundle_container'; import { @@ -72,10 +70,6 @@ export default class ColumnsArea extends ImmutablePureComponent { }; componentDidMount() { - if (!this.props.singleColumn) { - this.node.addEventListener('wheel', this.handleWheel, supportsPassiveEvents ? { passive: true } : false); - } - if (this.mediaQuery) { if (this.mediaQuery.addEventListener) { this.mediaQuery.addEventListener('change', this.handleLayoutChange); @@ -88,23 +82,7 @@ export default class ColumnsArea extends ImmutablePureComponent { this.isRtlLayout = document.getElementsByTagName('body')[0].classList.contains('rtl'); } - UNSAFE_componentWillUpdate(nextProps) { - if (this.props.singleColumn !== nextProps.singleColumn && nextProps.singleColumn) { - this.node.removeEventListener('wheel', this.handleWheel); - } - } - - componentDidUpdate(prevProps) { - if (this.props.singleColumn !== prevProps.singleColumn && !this.props.singleColumn) { - this.node.addEventListener('wheel', this.handleWheel, supportsPassiveEvents ? { passive: true } : false); - } - } - componentWillUnmount () { - if (!this.props.singleColumn) { - this.node.removeEventListener('wheel', this.handleWheel); - } - if (this.mediaQuery) { if (this.mediaQuery.removeEventListener) { this.mediaQuery.removeEventListener('change', this.handleLayoutChange); @@ -117,7 +95,7 @@ export default class ColumnsArea extends ImmutablePureComponent { handleChildrenContentChange() { if (!this.props.singleColumn) { const modifier = this.isRtlLayout ? -1 : 1; - this._interruptScrollAnimation = scrollRight(this.node, (this.node.scrollWidth - window.innerWidth) * modifier); + scrollRight(this.node, (this.node.scrollWidth - window.innerWidth) * modifier); } } @@ -125,14 +103,6 @@ export default class ColumnsArea extends ImmutablePureComponent { this.setState({ renderComposePanel: !e.matches }); }; - handleWheel = () => { - if (typeof this._interruptScrollAnimation !== 'function') { - return; - } - - this._interruptScrollAnimation(); - }; - setRef = (node) => { this.node = node; }; diff --git a/app/javascript/flavours/glitch/scroll.ts b/app/javascript/flavours/glitch/scroll.ts index 35e13a4527d1d3..0756edb4cefe2b 100644 --- a/app/javascript/flavours/glitch/scroll.ts +++ b/app/javascript/flavours/glitch/scroll.ts @@ -38,13 +38,20 @@ const scroll = ( const isScrollBehaviorSupported = 'scrollBehavior' in document.documentElement.style; -export const scrollRight = (node: Element, position: number) => { - if (isScrollBehaviorSupported) - node.scrollTo({ left: position, behavior: 'smooth' }); - else scroll(node, 'scrollLeft', position); -}; - -export const scrollTop = (node: Element) => { - if (isScrollBehaviorSupported) node.scrollTo({ top: 0, behavior: 'smooth' }); - else scroll(node, 'scrollTop', 0); -}; +export const scrollRight = (node: Element, position: number) => + requestIdleCallback(() => { + if (isScrollBehaviorSupported) { + node.scrollTo({ left: position, behavior: 'smooth' }); + } else { + scroll(node, 'scrollLeft', position); + } + }); + +export const scrollTop = (node: Element) => + requestIdleCallback(() => { + if (isScrollBehaviorSupported) { + node.scrollTo({ top: 0, behavior: 'smooth' }); + } else { + scroll(node, 'scrollTop', 0); + } + }); From 86b9d3b4e5f95be5a97924e4787a92112c6e6f11 Mon Sep 17 00:00:00 2001 From: Claire Date: Thu, 19 Sep 2024 16:58:33 +0200 Subject: [PATCH 49/51] [Glitch] Fix custom `history.push` and `history.replace` building bogus location if path is omitted Port 57a38f071b0ffd74b813516e24c9e86a23c4d467 to glitch-soc Signed-off-by: Claire --- app/javascript/flavours/glitch/components/router.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/javascript/flavours/glitch/components/router.tsx b/app/javascript/flavours/glitch/components/router.tsx index 48f35d8aedefa9..46477b96ff09a9 100644 --- a/app/javascript/flavours/glitch/components/router.tsx +++ b/app/javascript/flavours/glitch/components/router.tsx @@ -51,7 +51,8 @@ function normalizePath( if ( layoutFromWindow() === 'multi-column' && - !location.pathname?.startsWith('/deck') + location.pathname && + !location.pathname.startsWith('/deck') ) { location.pathname = `/deck${location.pathname}`; } From 03829d8e1d0b952e1ff6fdca64435bae401a47c5 Mon Sep 17 00:00:00 2001 From: Renaud Chaput Date: Thu, 19 Sep 2024 17:34:08 +0200 Subject: [PATCH 50/51] [Glitch] Update directory page options to use URL params Port ae03e4ffc6a25c8a3e3c61701180fdc1ea194141 to glitch-soc Signed-off-by: Claire --- .../glitch/features/directory/index.tsx | 35 +++++++++++-------- .../flavours/glitch/hooks/useSearchParam.ts | 31 ++++++++++++++++ 2 files changed, 51 insertions(+), 15 deletions(-) create mode 100644 app/javascript/flavours/glitch/hooks/useSearchParam.ts diff --git a/app/javascript/flavours/glitch/features/directory/index.tsx b/app/javascript/flavours/glitch/features/directory/index.tsx index d58ef2eab02c01..150adee94e4bac 100644 --- a/app/javascript/flavours/glitch/features/directory/index.tsx +++ b/app/javascript/flavours/glitch/features/directory/index.tsx @@ -1,5 +1,5 @@ import type { ChangeEventHandler } from 'react'; -import { useCallback, useEffect, useRef, useState } from 'react'; +import { useCallback, useEffect, useRef } from 'react'; import { defineMessages, useIntl } from 'react-intl'; @@ -26,6 +26,8 @@ import { RadioButton } from 'flavours/glitch/components/radio_button'; import ScrollContainer from 'flavours/glitch/containers/scroll_container'; import { useAppDispatch, useAppSelector } from 'flavours/glitch/store'; +import { useSearchParam } from '../../hooks/useSearchParam'; + import { AccountCard } from './components/account_card'; const messages = defineMessages({ @@ -50,18 +52,19 @@ export const Directory: React.FC<{ const intl = useIntl(); const dispatch = useAppDispatch(); - const [state, setState] = useState<{ - order: string | null; - local: boolean | null; - }>({ - order: null, - local: null, - }); - const column = useRef(null); - const order = state.order ?? params?.order ?? 'active'; - const local = state.local ?? params?.local ?? false; + const [orderParam, setOrderParam] = useSearchParam('order'); + const [localParam, setLocalParam] = useSearchParam('local'); + + let localParamBool: boolean | undefined; + + if (localParam === 'false') { + localParamBool = false; + } + + const order = orderParam ?? params?.order ?? 'active'; + const local = localParamBool ?? params?.local ?? true; const handlePin = useCallback(() => { if (columnId) { @@ -104,10 +107,10 @@ export const Directory: React.FC<{ if (columnId) { dispatch(changeColumnParams(columnId, ['order'], e.target.value)); } else { - setState((s) => ({ order: e.target.value, local: s.local })); + setOrderParam(e.target.value); } }, - [dispatch, columnId], + [dispatch, columnId, setOrderParam], ); const handleChangeLocal = useCallback>( @@ -116,11 +119,13 @@ export const Directory: React.FC<{ dispatch( changeColumnParams(columnId, ['local'], e.target.value === '1'), ); + } else if (e.target.value === '1') { + setLocalParam('true'); } else { - setState((s) => ({ local: e.target.value === '1', order: s.order })); + setLocalParam('false'); } }, - [dispatch, columnId], + [dispatch, columnId, setLocalParam], ); const handleLoadMore = useCallback(() => { diff --git a/app/javascript/flavours/glitch/hooks/useSearchParam.ts b/app/javascript/flavours/glitch/hooks/useSearchParam.ts new file mode 100644 index 00000000000000..2df8c0b3a9e79f --- /dev/null +++ b/app/javascript/flavours/glitch/hooks/useSearchParam.ts @@ -0,0 +1,31 @@ +import { useMemo, useCallback } from 'react'; + +import { useLocation, useHistory } from 'react-router'; + +export function useSearchParams() { + const { search } = useLocation(); + + return useMemo(() => new URLSearchParams(search), [search]); +} + +export function useSearchParam(name: string, defaultValue?: string) { + const searchParams = useSearchParams(); + const history = useHistory(); + + const value = searchParams.get(name) ?? defaultValue; + + const setValue = useCallback( + (value: string | null) => { + if (value === null) { + searchParams.delete(name); + } else { + searchParams.set(name, value); + } + + history.push({ search: searchParams.toString() }); + }, + [history, name, searchParams], + ); + + return [value, setValue] as const; +} From a969c6a6a60fcf7813d66b9e7a2c8700c500eb24 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Fri, 20 Sep 2024 11:42:02 +0200 Subject: [PATCH 51/51] [Glitch] Change zoom icon in web UI Port e7fd0985c9cd14437443345adfe725ecd3b038a6 to glitch-soc Signed-off-by: Claire --- .../flavours/glitch/components/modal_root.jsx | 2 +- .../features/ui/components/image_loader.jsx | 7 +- .../features/ui/components/media_modal.jsx | 52 +++++-- .../features/ui/components/zoomable_image.jsx | 129 +++++------------- .../flavours/glitch/styles/components.scss | 59 ++++---- 5 files changed, 106 insertions(+), 143 deletions(-) diff --git a/app/javascript/flavours/glitch/components/modal_root.jsx b/app/javascript/flavours/glitch/components/modal_root.jsx index f338c4ec0eda5c..71b875cfeeb547 100644 --- a/app/javascript/flavours/glitch/components/modal_root.jsx +++ b/app/javascript/flavours/glitch/components/modal_root.jsx @@ -153,7 +153,7 @@ class ModalRoot extends PureComponent { return (
-
+
{children}
diff --git a/app/javascript/flavours/glitch/features/ui/components/image_loader.jsx b/app/javascript/flavours/glitch/features/ui/components/image_loader.jsx index 9dabc621b427e4..b1417deda77b34 100644 --- a/app/javascript/flavours/glitch/features/ui/components/image_loader.jsx +++ b/app/javascript/flavours/glitch/features/ui/components/image_loader.jsx @@ -17,7 +17,7 @@ export default class ImageLoader extends PureComponent { width: PropTypes.number, height: PropTypes.number, onClick: PropTypes.func, - zoomButtonHidden: PropTypes.bool, + zoomedIn: PropTypes.bool, }; static defaultProps = { @@ -134,7 +134,7 @@ export default class ImageLoader extends PureComponent { }; render () { - const { alt, lang, src, width, height, onClick } = this.props; + const { alt, lang, src, width, height, onClick, zoomedIn } = this.props; const { loading } = this.state; const className = classNames('image-loader', { @@ -149,6 +149,7 @@ export default class ImageLoader extends PureComponent {
+ )}
diff --git a/app/javascript/flavours/glitch/features/ui/components/media_modal.jsx b/app/javascript/flavours/glitch/features/ui/components/media_modal.jsx index d16b274170b539..4d44483b4160e3 100644 --- a/app/javascript/flavours/glitch/features/ui/components/media_modal.jsx +++ b/app/javascript/flavours/glitch/features/ui/components/media_modal.jsx @@ -12,6 +12,8 @@ import ReactSwipeableViews from 'react-swipeable-views'; import ChevronLeftIcon from '@/material-icons/400-24px/chevron_left.svg?react'; import ChevronRightIcon from '@/material-icons/400-24px/chevron_right.svg?react'; import CloseIcon from '@/material-icons/400-24px/close.svg?react'; +import FitScreenIcon from '@/material-icons/400-24px/fit_screen.svg?react'; +import ActualSizeIcon from '@/svg-icons/actual_size.svg?react'; import { getAverageFromBlurhash } from 'flavours/glitch/blurhash'; import { GIFV } from 'flavours/glitch/components/gifv'; import { Icon } from 'flavours/glitch/components/icon'; @@ -26,6 +28,8 @@ const messages = defineMessages({ close: { id: 'lightbox.close', defaultMessage: 'Close' }, previous: { id: 'lightbox.previous', defaultMessage: 'Previous' }, next: { id: 'lightbox.next', defaultMessage: 'Next' }, + zoomIn: { id: 'lightbox.zoom_in', defaultMessage: 'Zoom to actual size' }, + zoomOut: { id: 'lightbox.zoom_out', defaultMessage: 'Zoom to fit' }, }); class MediaModal extends ImmutablePureComponent { @@ -46,30 +50,39 @@ class MediaModal extends ImmutablePureComponent { state = { index: null, navigationHidden: false, - zoomButtonHidden: false, + zoomedIn: false, + }; + + handleZoomClick = () => { + this.setState(prevState => ({ + zoomedIn: !prevState.zoomedIn, + })); }; handleSwipe = (index) => { - this.setState({ index: index % this.props.media.size }); + this.setState({ + index: index % this.props.media.size, + zoomedIn: false, + }); }; handleTransitionEnd = () => { this.setState({ - zoomButtonHidden: false, + zoomedIn: false, }); }; handleNextClick = () => { this.setState({ index: (this.getIndex() + 1) % this.props.media.size, - zoomButtonHidden: true, + zoomedIn: false, }); }; handlePrevClick = () => { this.setState({ index: (this.props.media.size + this.getIndex() - 1) % this.props.media.size, - zoomButtonHidden: true, + zoomedIn: false, }); }; @@ -78,7 +91,7 @@ class MediaModal extends ImmutablePureComponent { this.setState({ index: index % this.props.media.size, - zoomButtonHidden: true, + zoomedIn: false, }); }; @@ -130,15 +143,22 @@ class MediaModal extends ImmutablePureComponent { return this.state.index !== null ? this.state.index : this.props.index; } - toggleNavigation = () => { + handleToggleNavigation = () => { this.setState(prevState => ({ navigationHidden: !prevState.navigationHidden, })); }; + setRef = c => { + this.setState({ + viewportWidth: c?.clientWidth, + viewportHeight: c?.clientHeight, + }); + }; + render () { const { media, statusId, lang, intl, onClose } = this.props; - const { navigationHidden } = this.state; + const { navigationHidden, zoomedIn, viewportWidth, viewportHeight } = this.state; const index = this.getIndex(); @@ -160,8 +180,8 @@ class MediaModal extends ImmutablePureComponent { alt={description} lang={lang} key={image.get('url')} - onClick={this.toggleNavigation} - zoomButtonHidden={this.state.zoomButtonHidden} + onClick={this.handleToggleNavigation} + zoomedIn={zoomedIn} /> ); } else if (image.get('type') === 'video') { @@ -229,9 +249,12 @@ class MediaModal extends ImmutablePureComponent { )); } + const currentMedia = media.get(index); + const zoomable = currentMedia.get('type') === 'image' && (currentMedia.getIn(['meta', 'original', 'width']) > viewportWidth || currentMedia.getIn(['meta', 'original', 'height']) > viewportHeight); + return ( -
-
+
+
- +
+ {zoomable && } + +
{leftNav} {rightNav} diff --git a/app/javascript/flavours/glitch/features/ui/components/zoomable_image.jsx b/app/javascript/flavours/glitch/features/ui/components/zoomable_image.jsx index 31a25159eae7c7..c4129bf2609936 100644 --- a/app/javascript/flavours/glitch/features/ui/components/zoomable_image.jsx +++ b/app/javascript/flavours/glitch/features/ui/components/zoomable_image.jsx @@ -1,17 +1,6 @@ import PropTypes from 'prop-types'; import { PureComponent } from 'react'; -import { defineMessages, injectIntl } from 'react-intl'; - -import FullscreenExitIcon from '@/material-icons/400-24px/fullscreen_exit.svg?react'; -import RectangleIcon from '@/material-icons/400-24px/rectangle.svg?react'; -import { IconButton } from 'flavours/glitch/components/icon_button'; - -const messages = defineMessages({ - compress: { id: 'lightbox.compress', defaultMessage: 'Compress image view box' }, - expand: { id: 'lightbox.expand', defaultMessage: 'Expand image view box' }, -}); - const MIN_SCALE = 1; const MAX_SCALE = 4; const NAV_BAR_HEIGHT = 66; @@ -104,8 +93,7 @@ class ZoomableImage extends PureComponent { width: PropTypes.number, height: PropTypes.number, onClick: PropTypes.func, - zoomButtonHidden: PropTypes.bool, - intl: PropTypes.object.isRequired, + zoomedIn: PropTypes.bool, }; static defaultProps = { @@ -131,8 +119,6 @@ class ZoomableImage extends PureComponent { translateX: null, translateY: null, }, - zoomState: 'expand', // 'expand' 'compress' - navigationHidden: false, dragPosition: { top: 0, left: 0, x: 0, y: 0 }, dragged: false, lockScroll: { x: 0, y: 0 }, @@ -169,35 +155,20 @@ class ZoomableImage extends PureComponent { this.container.addEventListener('DOMMouseScroll', handler); this.removers.push(() => this.container.removeEventListener('DOMMouseScroll', handler)); - this.initZoomMatrix(); + this._initZoomMatrix(); } componentWillUnmount () { - this.removeEventListeners(); + this._removeEventListeners(); } - componentDidUpdate () { - this.setState({ zoomState: this.state.scale >= this.state.zoomMatrix.rate ? 'compress' : 'expand' }); - - if (this.state.scale === MIN_SCALE) { - this.container.style.removeProperty('cursor'); + componentDidUpdate (prevProps) { + if (prevProps.zoomedIn !== this.props.zoomedIn) { + this._toggleZoom(); } } - UNSAFE_componentWillReceiveProps () { - // reset when slide to next image - if (this.props.zoomButtonHidden) { - this.setState({ - scale: MIN_SCALE, - lockTranslate: { x: 0, y: 0 }, - }, () => { - this.container.scrollLeft = 0; - this.container.scrollTop = 0; - }); - } - } - - removeEventListeners () { + _removeEventListeners () { this.removers.forEach(listeners => listeners()); this.removers = []; } @@ -220,9 +191,6 @@ class ZoomableImage extends PureComponent { }; mouseDownHandler = e => { - this.container.style.cursor = 'grabbing'; - this.container.style.userSelect = 'none'; - this.setState({ dragPosition: { left: this.container.scrollLeft, top: this.container.scrollTop, @@ -246,9 +214,6 @@ class ZoomableImage extends PureComponent { }; mouseUpHandler = () => { - this.container.style.cursor = 'grab'; - this.container.style.removeProperty('user-select'); - this.image.removeEventListener('mousemove', this.mouseMoveHandler); this.image.removeEventListener('mouseup', this.mouseUpHandler); }; @@ -276,13 +241,13 @@ class ZoomableImage extends PureComponent { const _MAX_SCALE = Math.max(MAX_SCALE, this.state.zoomMatrix.rate); const scale = clamp(MIN_SCALE, _MAX_SCALE, this.state.scale * distance / this.lastDistance); - this.zoom(scale, midpoint); + this._zoom(scale, midpoint); this.lastMidpoint = midpoint; this.lastDistance = distance; }; - zoom(nextScale, midpoint) { + _zoom(nextScale, midpoint) { const { scale, zoomMatrix } = this.state; const { scrollLeft, scrollTop } = this.container; @@ -318,14 +283,13 @@ class ZoomableImage extends PureComponent { if (dragged) return; const handler = this.props.onClick; if (handler) handler(); - this.setState({ navigationHidden: !this.state.navigationHidden }); }; handleMouseDown = e => { e.preventDefault(); }; - initZoomMatrix = () => { + _initZoomMatrix = () => { const { width, height } = this.props; const { clientWidth, clientHeight } = this.container; const { offsetWidth, offsetHeight } = this.image; @@ -357,10 +321,7 @@ class ZoomableImage extends PureComponent { }); }; - handleZoomClick = e => { - e.preventDefault(); - e.stopPropagation(); - + _toggleZoom () { const { scale, zoomMatrix } = this.state; if ( scale >= zoomMatrix.rate ) { @@ -394,10 +355,7 @@ class ZoomableImage extends PureComponent { this.container.scrollTop = zoomMatrix.scrollTop; }); } - - this.container.style.cursor = 'grab'; - this.container.style.removeProperty('user-select'); - }; + } setContainerRef = c => { this.container = c; @@ -408,52 +366,37 @@ class ZoomableImage extends PureComponent { }; render () { - const { alt, lang, src, width, height, intl } = this.props; - const { scale, lockTranslate } = this.state; + const { alt, lang, src, width, height } = this.props; + const { scale, lockTranslate, dragged } = this.state; const overflow = scale === MIN_SCALE ? 'hidden' : 'scroll'; - const zoomButtonShouldHide = this.state.navigationHidden || this.props.zoomButtonHidden || this.state.zoomMatrix.rate <= MIN_SCALE ? 'media-modal__zoom-button--hidden' : ''; - const zoomButtonTitle = this.state.zoomState === 'compress' ? intl.formatMessage(messages.compress) : intl.formatMessage(messages.expand); + const cursor = scale === MIN_SCALE ? null : (dragged ? 'grabbing' : 'grab'); return ( - <> - + {alt} -
- {alt} -
- +
); } - } -export default injectIntl(ZoomableImage); +export default ZoomableImage; diff --git a/app/javascript/flavours/glitch/styles/components.scss b/app/javascript/flavours/glitch/styles/components.scss index 7539fb6ddc5610..f044227c95aad1 100644 --- a/app/javascript/flavours/glitch/styles/components.scss +++ b/app/javascript/flavours/glitch/styles/components.scss @@ -6212,19 +6212,34 @@ a.status-card { height: 100%; position: relative; - &__close, - &__zoom-button { - color: rgba($white, 0.7); + &__buttons { + position: absolute; + inset-inline-end: 8px; + top: 8px; + z-index: 100; + display: flex; + gap: 8px; + align-items: center; - &:hover, - &:focus, - &:active { - color: $white; - background-color: rgba($white, 0.15); - } + .icon-button { + color: rgba($white, 0.7); + padding: 8px; - &:focus { - background-color: rgba($white, 0.3); + .icon { + width: 24px; + height: 24px; + } + + &:hover, + &:focus, + &:active { + color: $white; + background-color: rgba($white, 0.15); + } + + &:focus { + background-color: rgba($white, 0.3); + } } } } @@ -6385,28 +6400,6 @@ a.status-card { } } -.media-modal__close { - position: absolute; - inset-inline-end: 8px; - top: 8px; - z-index: 100; -} - -.media-modal__zoom-button { - position: absolute; - inset-inline-end: 64px; - top: 8px; - z-index: 100; - pointer-events: auto; - transition: opacity 0.3s linear; - will-change: opacity; -} - -.media-modal__zoom-button--hidden { - pointer-events: none; - opacity: 0; -} - .onboarding-modal, .error-modal, .embed-modal {