Merge branch 'main' into patch-1

This commit is contained in:
Michael Stanclift 2024-10-02 08:27:48 -05:00 committed by GitHub
commit 8210279f61
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
44 changed files with 551 additions and 172 deletions

View File

@ -815,7 +815,7 @@ GEM
docile (~> 1.1)
simplecov-html (~> 0.11)
simplecov_json_formatter (~> 0.1)
simplecov-html (0.12.3)
simplecov-html (0.13.1)
simplecov-lcov (0.8.0)
simplecov_json_formatter (0.1.4)
stackprof (0.2.26)

View File

@ -7,7 +7,23 @@ module WellKnown
def show
@webfinger_template = "#{webfinger_url}?resource={uri}"
expires_in 3.days, public: true
respond_to do |format|
format.any do
render content_type: 'application/xrd+xml', formats: [:xml]
end
format.json do
render json: {
links: [
{
rel: 'lrdd',
template: @webfinger_template,
},
],
}
end
end
end
end
end

View File

@ -37,8 +37,7 @@ export const synchronouslySubmitMarkers = createAppAsyncThunk(
});
return;
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
} else if ('navigator' && 'sendBeacon' in navigator) {
} else if ('sendBeacon' in navigator) {
// Failing that, we can use sendBeacon, but we have to encode the data as
// FormData for DoorKeeper to recognize the token.
const formData = new FormData();

View File

@ -85,6 +85,7 @@
"alert.rate_limited.title": "Cyfradd gyfyngedig",
"alert.unexpected.message": "Digwyddodd gwall annisgwyl.",
"alert.unexpected.title": "Wps!",
"alt_text_badge.title": "Testun Amgen",
"announcement.announcement": "Cyhoeddiad",
"attachments_list.unprocessed": "(heb eu prosesu)",
"audio.hide": "Cuddio sain",
@ -221,6 +222,7 @@
"domain_block_modal.they_cant_follow": "Ni all neb o'r gweinydd hwn eich dilyn.",
"domain_block_modal.they_wont_know": "Fyddan nhw ddim yn gwybod eu bod wedi cael eu blocio.",
"domain_block_modal.title": "Blocio parth?",
"domain_block_modal.you_will_lose_relationships": "Byddwch yn colli'r holl ddilynwyr a phobl rydych chi'n eu dilyn o'r gweinydd hwn.",
"domain_block_modal.you_wont_see_posts": "Fyddwch chi ddim yn gweld postiadau na hysbysiadau gan ddefnyddwyr ar y gweinydd hwn.",
"domain_pill.activitypub_lets_connect": "Mae'n caniatáu ichi gysylltu a rhyngweithio â phobl nid yn unig ar Mastodon, ond ar draws gwahanol apiau cymdeithasol hefyd.",
"domain_pill.activitypub_like_language": "Mae ActivityPub fel yr iaith y mae Mastodon yn ei siarad â rhwydweithiau cymdeithasol eraill.",
@ -849,6 +851,11 @@
"upload_error.poll": "Nid oes modd llwytho ffeiliau â phleidleisiau.",
"upload_form.audio_description": "Disgrifio ar gyfer pobl sydd â cholled clyw",
"upload_form.description": "Disgrifio i'r rheini a nam ar ei golwg",
"upload_form.drag_and_drop.instructions": "I godi atodiad cyfryngau, pwyswch y space neu enter. Wrth lusgo, defnyddiwch y bysellau saeth i symud yr atodiad cyfryngau i unrhyw gyfeiriad penodol. Pwyswch space neu enter eto i ollwng yr atodiad cyfryngau yn ei safle newydd, neu pwyswch escape i ddiddymu.",
"upload_form.drag_and_drop.on_drag_cancel": "Cafodd llusgo ei ddiddymu. Cafodd atodiad cyfryngau {item} ei ollwng.",
"upload_form.drag_and_drop.on_drag_end": "Cafodd atodiad cyfryngau {item} ei ollwng.",
"upload_form.drag_and_drop.on_drag_over": "Symudwyd atodiad cyfryngau {item}.",
"upload_form.drag_and_drop.on_drag_start": "Atodiad cyfryngau godwyd {item}.",
"upload_form.edit": "Golygu",
"upload_form.thumbnail": "Newid llun bach",
"upload_form.video_description": "Disgrifio ar gyfer pobl sydd â cholled clyw neu amhariad golwg",

View File

@ -22,10 +22,10 @@
"account.cancel_follow_request": "Nuligi peton por sekvado",
"account.copy": "Kopii ligilon al profilo",
"account.direct": "Private mencii @{name}",
"account.disable_notifications": "Ne plu sciigi min, kiam @{name} mesaĝas",
"account.disable_notifications": "Ĉesu sciigi min kiam @{name} afiŝas",
"account.domain_blocked": "Domajno blokita",
"account.edit_profile": "Redakti la profilon",
"account.enable_notifications": "Sciigi min, kiam @{name} mesaĝas",
"account.enable_notifications": "Sciigu min kiam @{name} afiŝos",
"account.endorse": "Rekomendi ĉe via profilo",
"account.featured_tags.last_status_at": "Lasta afîŝo je {date}",
"account.featured_tags.last_status_never": "Neniu afiŝo",
@ -49,14 +49,14 @@
"account.mention": "Mencii @{name}",
"account.moved_to": "{name} indikis, ke ria nova konto estas nun:",
"account.mute": "Silentigi @{name}",
"account.mute_notifications_short": "Silentigu Sciigojn",
"account.mute_notifications_short": "Silentigu sciigojn",
"account.mute_short": "Silentigu",
"account.muted": "Silentigita",
"account.mutual": "Reciproka",
"account.no_bio": "Neniu priskribo estas provizita.",
"account.open_original_page": "Malfermi la originalan paĝon",
"account.posts": "Afiŝoj",
"account.posts_with_replies": "Mesaĝoj kaj respondoj",
"account.posts_with_replies": "Afiŝoj kaj respondoj",
"account.report": "Raporti @{name}",
"account.requested": "Atendo de aprobo. Klaku por nuligi la peton por sekvado",
"account.requested_follow": "{name} petis sekvi vin",
@ -69,7 +69,7 @@
"account.unendorse": "Ne plu rekomendi ĉe la profilo",
"account.unfollow": "Ĉesi sekvi",
"account.unmute": "Ne plu silentigi @{name}",
"account.unmute_notifications_short": "Malsilentigu Sciigojn",
"account.unmute_notifications_short": "Malsilentigu sciigojn",
"account.unmute_short": "Ne plu silentigi",
"account_note.placeholder": "Alklaku por aldoni noton",
"admin.dashboard.daily_retention": "Uzantoretenprocento lau tag post registro",
@ -81,7 +81,7 @@
"admin.impact_report.instance_followers": "Sekvantojn niaj uzantoj perdus",
"admin.impact_report.instance_follows": "Sekvantojn ties uzantoj perdus",
"admin.impact_report.title": "Influa reporto",
"alert.rate_limited.message": "Bonvolu reprovi post {retry_time, time, medium}.",
"alert.rate_limited.message": "Bonvolu reprovi poste {retry_time, time, medium}.",
"alert.rate_limited.title": "Mesaĝkvante limigita",
"alert.unexpected.message": "Neatendita eraro okazis.",
"alert.unexpected.title": "Aj!",
@ -163,7 +163,7 @@
"compose_form.poll.switch_to_single": "Ŝanĝi la balotenketon por permesi unu solan elekton",
"compose_form.poll.type": "Stilo",
"compose_form.publish": "Afiŝo",
"compose_form.publish_form": "Afiŝi",
"compose_form.publish_form": "Nova afiŝo",
"compose_form.reply": "Respondi",
"compose_form.save_changes": "Ĝisdatigi",
"compose_form.spoiler.marked": "Forigi la averton de enhavo",
@ -173,7 +173,7 @@
"confirmations.block.confirm": "Bloki",
"confirmations.delete.confirm": "Forigi",
"confirmations.delete.message": "Ĉu vi certas, ke vi volas forigi ĉi tiun afiŝon?",
"confirmations.delete.title": "Ĉu forigi Afiŝon?",
"confirmations.delete.title": "Ĉu forigi afiŝon?",
"confirmations.delete_list.confirm": "Forigi",
"confirmations.delete_list.message": "Ĉu vi certas, ke vi volas porĉiame forigi ĉi tiun liston?",
"confirmations.delete_list.title": "Ĉu forigi liston?",
@ -213,9 +213,9 @@
"dismissable_banner.community_timeline": "Jen la plej novaj publikaj afiŝoj de uzantoj, kies kontojn gastigas {domain}.",
"dismissable_banner.dismiss": "Eksigi",
"dismissable_banner.explore_links": "Tiuj novaĵoj estas aktuale priparolataj de uzantoj en tiu ĉi kaj aliaj serviloj, sur la malcentrigita reto.",
"dismissable_banner.explore_statuses": "Ĉi tioj estas afiŝoj de socia reto kiu populariĝas hodiau.",
"dismissable_banner.explore_statuses": "Ĉi tiuj estas afiŝoj de la tuta socia reto, kiuj populariĝas hodiaŭ. Pli novaj afiŝoj kun pli da diskonigoj kaj plej ŝatataj estas rangigitaj pli alte.",
"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.",
"dismissable_banner.public_timeline": "Ĉi tiuj estas la plej lastatempaj publikaj afiŝoj de homoj en la socia reto, kiujn homoj sur {domain} sekvas.",
"domain_block_modal.block": "Bloki servilon",
"domain_block_modal.block_account_instead": "Bloki @{name} anstataŭe",
"domain_block_modal.they_can_interact_with_old_posts": "Homoj de ĉi tiu servilo povas interagi kun viaj malnovaj afiŝoj.",
@ -265,8 +265,8 @@
"empty_column.direct": "Vi ankoraŭ ne havas privatan mencion. Kiam vi sendos aŭ ricevos iun, tiu aperos ĉi tie.",
"empty_column.domain_blocks": "Ankoraŭ neniu domajno estas blokita.",
"empty_column.explore_statuses": "Nenio tendencas nun. Rekontrolu poste!",
"empty_column.favourited_statuses": "Vi ankoraŭ ne havas stelumitan afiŝon.",
"empty_column.favourites": "Ankoraŭ neniu stelumis tiun afiŝon.",
"empty_column.favourited_statuses": "Vi ankoraŭ ne havas plej ŝatatajn afiŝojn. Kiam vi ŝatatas unu, ĝi aperos ĉi tie.",
"empty_column.favourites": "Neniu ankoraŭ ŝatis ĉi tiun afiŝon. Kiam iu ŝatos ĝin, ili aperos ĉi tie.",
"empty_column.follow_requests": "Vi ne ankoraŭ havas iun peton de sekvado. Kiam vi ricevos unu, ĝi aperos ĉi tie.",
"empty_column.followed_tags": "Vi ankoraŭ ne sekvas iujn kradvortojn. Kiam vi faras, ili aperos ĉi tie.",
"empty_column.hashtag": "Ankoraŭ estas nenio per ĉi tiu kradvorto.",
@ -296,7 +296,7 @@
"filter_modal.added.review_and_configure": "Por kontroli kaj pli modifi ĉi tiu filtrilkategorio, iru al la {settings_link}.",
"filter_modal.added.review_and_configure_title": "Filtrilopcioj",
"filter_modal.added.settings_link": "opciopaĝo",
"filter_modal.added.short_explanation": "Ĉi tiu mesaĝo aldonitas al la filtrilkategorio: {title}.",
"filter_modal.added.short_explanation": "Ĉi tiu afiŝo aldonitas al la filtrilkategorio: {title}.",
"filter_modal.added.title": "Filtrilo aldonita!",
"filter_modal.select_filter.context_mismatch": "ne kongruas la kuntekston",
"filter_modal.select_filter.expired": "eksvalidiĝinta",
@ -304,7 +304,7 @@
"filter_modal.select_filter.search": "Serĉi aŭ krei",
"filter_modal.select_filter.subtitle": "Uzu ekzistantan kategorion aŭ kreu novan",
"filter_modal.select_filter.title": "Filtri ĉi tiun afiŝon",
"filter_modal.title.status": "Filtri mesaĝon",
"filter_modal.title.status": "Filtri afiŝon",
"filter_warning.matches_filter": "Filtrilo de kongruoj “{title}”",
"filtered_notifications_banner.pending_requests": "El {count, plural, =0 {neniu} one {unu persono} other {# homoj}} vi eble konas",
"filtered_notifications_banner.title": "Filtritaj sciigoj",
@ -351,7 +351,7 @@
"hashtag.column_settings.tag_toggle": "Aldoni pliajn etikedojn por ĉi tiu kolumno",
"hashtag.counter_by_accounts": "{count, plural,one {{counter} partoprenanto} other {{counter} partoprenantoj}}",
"hashtag.counter_by_uses": "{count, plural,one {{counter} afiŝo} other {{counter} afiŝoj}}",
"hashtag.counter_by_uses_today": "{count, plural,one {{counter} afiŝo} other {{counter} afiŝoj}} hodiau",
"hashtag.counter_by_uses_today": "{count, plural,one {{counter} afiŝo} other {{counter} afiŝoj}} hodiaŭ",
"hashtag.follow": "Sekvi la kradvorton",
"hashtag.unfollow": "Ne plu sekvi la kradvorton",
"hashtags.and_other": "…kaj {count, plural,other {# pli}}",
@ -382,9 +382,9 @@
"ignore_notifications_modal.not_following_title": "Ĉu ignori sciigojn de homoj, kiujn vi ne sekvas?",
"ignore_notifications_modal.private_mentions_title": "Ĉu ignori sciigojn de nepetitaj privataj mencioj?",
"interaction_modal.description.favourite": "Per konto ĉe Mastodon, vi povas stelumiti ĉi tiun afiŝon por sciigi la afiŝanton ke vi aprezigas ŝin kaj konservas por la estonteco.",
"interaction_modal.description.follow": "Kun konto ĉe Mastodon, vi povos sekvi {name} por vidi ties mesaĝojn en via hejmo.",
"interaction_modal.description.follow": "Kun konto ĉe Mastodon, vi povas sekvi {name} por ricevi iliajn afiŝojn en via hejma fluo.",
"interaction_modal.description.reblog": "Kun konto ĉe Mastodon, vi povas diskonigi ĉi tiun afiŝon, por ke viaj propraj sekvantoj vidu ĝin.",
"interaction_modal.description.reply": "Kun konto ĉe Mastodon, vi povos respondi al ĉi tiu mesaĝo.",
"interaction_modal.description.reply": "Kun konto ĉe Mastodon, vi povos respondi al ĉi tiu afiŝo.",
"interaction_modal.login.action": "Prenu min hejmen",
"interaction_modal.login.prompt": "Domajno de via hejma servilo, ekz. mastodon.social",
"interaction_modal.no_account_yet": "Ĉu ne estas ĉe Mastodon?",
@ -402,12 +402,12 @@
"keyboard_shortcuts.back": "reveni",
"keyboard_shortcuts.blocked": "Malfermi la liston de blokitaj uzantoj",
"keyboard_shortcuts.boost": "Diskonigi la mesaĝon",
"keyboard_shortcuts.column": "fokusi mesaĝon en unu el la kolumnoj",
"keyboard_shortcuts.column": "Fokusi kolumnon",
"keyboard_shortcuts.compose": "enfokusigi la tekstujon",
"keyboard_shortcuts.description": "Priskribo",
"keyboard_shortcuts.direct": "por malfermi la kolumnon pri privataj mencioj",
"keyboard_shortcuts.down": "iri suben en la listo",
"keyboard_shortcuts.enter": "malfermi mesaĝon",
"keyboard_shortcuts.enter": "Malfermi afiŝon",
"keyboard_shortcuts.favourite": "Stelumi afiŝon",
"keyboard_shortcuts.favourites": "Malfermi la liston de la stelumoj",
"keyboard_shortcuts.federated": "Malfermi la frataran templinion",
@ -421,16 +421,16 @@
"keyboard_shortcuts.my_profile": "malfermi vian profilon",
"keyboard_shortcuts.notifications": "malfermi la kolumnon de sciigoj",
"keyboard_shortcuts.open_media": "Malfermi plurmedion",
"keyboard_shortcuts.pinned": "malfermi la liston de alpinglitaj mesaĝoj",
"keyboard_shortcuts.pinned": "Malfermu alpinglitajn afiŝojn-liston",
"keyboard_shortcuts.profile": "malfermi la profilon de la aŭtoro",
"keyboard_shortcuts.reply": "respondi",
"keyboard_shortcuts.reply": "Respondu al afiŝo",
"keyboard_shortcuts.requests": "Malfermi la liston de petoj por sekvado",
"keyboard_shortcuts.search": "enfokusigi la serĉilon",
"keyboard_shortcuts.spoilers": "Montri/kaŝi la kampon de averto de enhavo (\"CW\")",
"keyboard_shortcuts.start": "malfermi la kolumnon «por komenci»",
"keyboard_shortcuts.toggle_hidden": "Montri/kaŝi tekston malantaŭ la averto de enhavo (\"CW\")",
"keyboard_shortcuts.toggle_sensitivity": "Montri/kaŝi plurmedion",
"keyboard_shortcuts.toot": "Krei novan mesaĝon",
"keyboard_shortcuts.toot": "Komencu novan afiŝon",
"keyboard_shortcuts.unfocus": "malenfokusigi la tekstujon aŭ la serĉilon",
"keyboard_shortcuts.up": "iri supren en la listo",
"lightbox.close": "Fermi",
@ -476,9 +476,9 @@
"navigation_bar.blocks": "Blokitaj uzantoj",
"navigation_bar.bookmarks": "Legosignoj",
"navigation_bar.community_timeline": "Loka templinio",
"navigation_bar.compose": "Skribi novan mesaĝon",
"navigation_bar.compose": "Redakti novan afiŝon",
"navigation_bar.direct": "Privataj mencioj",
"navigation_bar.discover": "Esplori",
"navigation_bar.discover": "Malkovri",
"navigation_bar.domain_blocks": "Blokitaj domajnoj",
"navigation_bar.explore": "Esplori",
"navigation_bar.favourites": "Stelumoj",
@ -487,12 +487,12 @@
"navigation_bar.followed_tags": "Sekvataj kradvortoj",
"navigation_bar.follows_and_followers": "Sekvatoj kaj sekvantoj",
"navigation_bar.lists": "Listoj",
"navigation_bar.logout": "Adiaŭi",
"navigation_bar.logout": "Elsaluti",
"navigation_bar.moderation": "Modereco",
"navigation_bar.mutes": "Silentigitaj uzantoj",
"navigation_bar.opened_in_classic_interface": "Afiŝoj, kontoj, kaj aliaj specifaj paĝoj kiuj estas malfermititaj defaulta en la klasika reta interfaco.",
"navigation_bar.personal": "Persone",
"navigation_bar.pins": "Alpinglitaj mesaĝoj",
"navigation_bar.pins": "Alpinglitaj afiŝoj",
"navigation_bar.preferences": "Preferoj",
"navigation_bar.public_timeline": "Fratara templinio",
"navigation_bar.search": "Serĉi",
@ -572,7 +572,7 @@
"notifications.column_settings.reblog": "Diskonigoj:",
"notifications.column_settings.show": "Montri en kolumno",
"notifications.column_settings.sound": "Eligi sonon",
"notifications.column_settings.status": "Novaj mesaĝoj:",
"notifications.column_settings.status": "Novaj afiŝoj:",
"notifications.column_settings.unread_notifications.category": "Nelegitaj sciigoj",
"notifications.column_settings.unread_notifications.highlight": "Marki nelegitajn sciigojn",
"notifications.column_settings.update": "Redaktoj:",
@ -660,7 +660,7 @@
"poll.votes": "{votes, plural, one {# voĉdono} other {# voĉdonoj}}",
"poll_button.add_poll": "Aldoni balotenketon",
"poll_button.remove_poll": "Forigi balotenketon",
"privacy.change": "Agordi mesaĝan privatecon",
"privacy.change": "Ŝanĝu afiŝan privatecon",
"privacy.direct.long": "Ĉiuj menciitaj en la afiŝo",
"privacy.direct.short": "Specifaj homoj",
"privacy.private.long": "Nur viaj sekvantoj",
@ -775,13 +775,13 @@
"sign_in_banner.sso_redirect": "Ensalutu aŭ Registriĝi",
"status.admin_account": "Malfermi fasadon de moderigado por @{name}",
"status.admin_domain": "Malfermu moderigan interfacon por {domain}",
"status.admin_status": "Malfermi ĉi tiun mesaĝon en la kontrola interfaco",
"status.admin_status": "Malfermi ĉi tiun afiŝon en la kontrola interfaco",
"status.block": "Bloki @{name}",
"status.bookmark": "Aldoni al la legosignoj",
"status.cancel_reblog_private": "Ne plu diskonigi",
"status.cannot_reblog": "Ĉi tiun afiŝon ne eblas diskonigi",
"status.continued_thread": "Daŭrigis fadenon",
"status.copy": "Kopii la ligilon al la mesaĝo",
"status.copy": "Kopii la ligilon al la afiŝo",
"status.delete": "Forigi",
"status.detailed_status": "Detala konversacia vido",
"status.direct": "Private mencii @{name}",
@ -803,9 +803,9 @@
"status.more": "Pli",
"status.mute": "Silentigi @{name}",
"status.mute_conversation": "Silentigi konversacion",
"status.open": "Disvolvi la mesaĝon",
"status.open": "Pligrandigu ĉi tiun afiŝon",
"status.pin": "Alpingli al la profilo",
"status.pinned": "Alpinglita mesaĝo",
"status.pinned": "Alpinglita afiŝo",
"status.read_more": "Legi pli",
"status.reblog": "Diskonigi",
"status.reblog_private": "Diskonigi kun la sama videbleco",

View File

@ -193,7 +193,7 @@
"confirmations.reply.message": "Ao responder sobrescribirás a mensaxe que estás a compor. Tes a certeza de que queres continuar?",
"confirmations.reply.title": "Editar a publicación?",
"confirmations.unfollow.confirm": "Deixar de seguir",
"confirmations.unfollow.message": "Desexas deixar de seguir a {name}?",
"confirmations.unfollow.message": "Tes certeza de querer deixar de seguir a {name}?",
"confirmations.unfollow.title": "Deixar de seguir á usuaria?",
"content_warning.hide": "Agochar publicación",
"content_warning.show": "Mostrar igualmente",

View File

@ -85,6 +85,7 @@
"alert.rate_limited.title": "חלה הגבלה על קצב התעבורה",
"alert.unexpected.message": "אירעה שגיאה בלתי צפויה.",
"alert.unexpected.title": "אופס!",
"alt_text_badge.title": "כיתוב חלופי",
"announcement.announcement": "הכרזה",
"attachments_list.unprocessed": "(לא מעובד)",
"audio.hide": "השתק",
@ -221,6 +222,8 @@
"domain_block_modal.they_cant_follow": "משתמש משרת זה לא יכול לעקוב אחריך.",
"domain_block_modal.they_wont_know": "הם לא ידעו כי נחסמו.",
"domain_block_modal.title": "לחסום שרת?",
"domain_block_modal.you_will_lose_num_followers": "{followersCount, plural,one {יאבד לך עוקב אחד}other {יאבדו לך {followersCountDisplay} עוקבים}} {followingCount, plural,one {ונעקב אחד}other {ו־{followingCountDisplay} נעקבים}}.",
"domain_block_modal.you_will_lose_relationships": "יאבדו לך כל העוקבים והנעקבים משרת זה.",
"domain_block_modal.you_wont_see_posts": "לא תוכלו לראות הודעות ממשתמשים על שרת זה.",
"domain_pill.activitypub_lets_connect": "מאפשר לך להתחבר ולהתרועע עם אחרים לא רק במסטודון, אלא גם ביישומים חברתיים שונים אחרים.",
"domain_pill.activitypub_like_language": "אקטיביטיפאב היא למעשה השפה בה מסטודון מדבר עם רשתות חברתיות אחרות.",
@ -849,6 +852,11 @@
"upload_error.poll": "לא ניתן להעלות קובץ עם סקר.",
"upload_form.audio_description": "תאר/י עבור לקויי שמיעה",
"upload_form.description": "תיאור לכבדי ראיה",
"upload_form.drag_and_drop.instructions": "כדי לבחור קובץ מוצמד, יש ללחוץ על מקש רווח או אנטר. בעת הגרירה, השתמשו במקשי החיצים כדי להזיז את הקובץ המוצמד בכל כיוון. לחצו רווח או אנטר בשנית כדי לעזוב את הקובץ במקומו החדש, או לחצו אסקייפ לביטול.",
"upload_form.drag_and_drop.on_drag_cancel": "הגרירה בוטלה. קובץ המדיה {item} נעזב.",
"upload_form.drag_and_drop.on_drag_end": "קובץ המדיה {item} נעזב.",
"upload_form.drag_and_drop.on_drag_over": "קובץ המדיה {item} הוזז.",
"upload_form.drag_and_drop.on_drag_start": "קובץ המדיה {item} נבחר.",
"upload_form.edit": "עריכה",
"upload_form.thumbnail": "שנה/י תמונה ממוזערת",
"upload_form.video_description": "תאר/י עבור לקויי שמיעה ולקויי ראייה",

View File

@ -852,8 +852,8 @@
"upload_error.poll": "Szavazásnál nem lehet fájlt feltölteni.",
"upload_form.audio_description": "Leírás siket vagy hallássérült emberek számára",
"upload_form.description": "Leírás vak vagy gyengénlátó emberek számára",
"upload_form.drag_and_drop.instructions": "Egy médiamelléklet kiválasztásához nyomjon Szóközt vagy Entert. Húzás közben használja a nyílgombokat a médiamelléklet adott irányba történő mozgatásához. A médiamelléklet új pozícióba helyezéséhez nyomja meg a Szóközt vagy az Entert, vagy a megszakításhoz nyomja meg az Esc gombot.",
"upload_form.drag_and_drop.on_drag_cancel": "Az áthúzást megszakította. A(z) {item} médiamelléklet el lett dobva.",
"upload_form.drag_and_drop.instructions": "Egy médiamelléklet kiválasztásához nyomj Szóközt vagy Entert. Húzás közben használd a nyílgombokat a médiamelléklet adott irányba történő mozgatásához. A médiamelléklet új pozícióba helyezéséhez nyomd meg a Szóközt vagy az Entert, vagy a megszakításhoz nyomd meg az Esc gombot.",
"upload_form.drag_and_drop.on_drag_cancel": "Az áthúzás megszakítva. A(z) {item} médiamelléklet el lett dobva.",
"upload_form.drag_and_drop.on_drag_end": "A(z) {item} médiamelléklet el lett dobva.",
"upload_form.drag_and_drop.on_drag_over": "A(z) {item} médiamelléklet át lett helyezve.",
"upload_form.drag_and_drop.on_drag_start": "A(z) {item} médiamelléklet fel lett véve.",

View File

@ -85,6 +85,7 @@
"alert.rate_limited.title": "制限に達しました",
"alert.unexpected.message": "不明なエラーが発生しました。",
"alert.unexpected.title": "エラー!",
"alt_text_badge.title": "代替テキスト",
"announcement.announcement": "お知らせ",
"attachments_list.unprocessed": "(未処理)",
"audio.hide": "音声を閉じる",

View File

@ -585,6 +585,7 @@
"status.bookmark": "Creḍ",
"status.cancel_reblog_private": "Sefsex beṭṭu",
"status.cannot_reblog": "Tasuffeɣt-a ur tezmir ara ad tettwabḍu tikelt-nniḍen",
"status.continued_thread": "Asentel yettkemmil",
"status.copy": "Nɣel assaɣ ɣer tasuffeɣt",
"status.delete": "Kkes",
"status.direct": "Bder-d @{name} weḥd-s",
@ -616,6 +617,7 @@
"status.reblogs.empty": "Ula yiwen ur yebḍi tajewwiqt-agi ar tura. Ticki yebḍa-tt yiwen, ad d-iban da.",
"status.redraft": "Kkes tɛiwdeḍ tira",
"status.remove_bookmark": "Kkes tacreḍt",
"status.replied_in_thread": "Y·t·erra-d deg usentel",
"status.replied_to": "Y·terra-yas i {name}",
"status.reply": "Err",
"status.replyAll": "Err i lxiḍ",

View File

@ -502,6 +502,8 @@
"notification.reblog": "{name} fremhevet ditt innlegg",
"notification.status": "{name} la nettopp ut",
"notification.update": "{name} redigerte et innlegg",
"notification_requests.minimize_banner": "Minimer banneret for filtrerte varsler",
"notification_requests.view": "Vis varsler",
"notifications.clear": "Fjern varsler",
"notifications.clear_confirmation": "Er du sikker på at du vil fjerne alle dine varsler permanent?",
"notifications.column_settings.admin.report": "Nye rapporter:",

View File

@ -852,6 +852,11 @@
"upload_error.poll": "Anketlerde dosya yüklemesine izin verilmez.",
"upload_form.audio_description": "İşitme kaybı olan kişiler için yazı ekleyiniz",
"upload_form.description": "Görme engelliler için açıklama",
"upload_form.drag_and_drop.instructions": "Bir medya eklentisini taşımak için, boşluk veya enter tuşuna basın. Sürükleme sırasında medya eklentisini herhangi bir yöne hareket ettirmek için ok tuşlarını kullanın. Medya eklentisini yeni konumuna bırakmak için tekrar boşluk veya enter tuşuna basın veya işlemi iptal etmek için escape tuşuna basın.",
"upload_form.drag_and_drop.on_drag_cancel": "Sürükleme iptal edildi. Medya eklentisi {item} bırakıldı.",
"upload_form.drag_and_drop.on_drag_end": "Medya eklentisi {item} bırakıldı.",
"upload_form.drag_and_drop.on_drag_over": "Medya eklentisi {item} hareket ettirildi.",
"upload_form.drag_and_drop.on_drag_start": "Medya eklentisi {item} tutuldu.",
"upload_form.edit": "Düzenle",
"upload_form.thumbnail": "Küçük resmi değiştir",
"upload_form.video_description": "İşitme kaybı veya görme engeli olan kişiler için açıklama ekleyiniz",

View File

@ -42,6 +42,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
def process_status
@tags = []
@mentions = []
@unresolved_mentions = []
@silenced_account_ids = []
@params = {}
@ -55,6 +56,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
end
resolve_thread(@status)
resolve_unresolved_mentions(@status)
fetch_replies(@status)
distribute
forward_for_reply
@ -197,6 +199,8 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
return if account.nil?
@mentions << Mention.new(account: account, silent: false)
rescue Mastodon::UnexpectedResponseError, HTTP::TimeoutError, HTTP::ConnectionError, OpenSSL::SSL::SSLError
@unresolved_mentions << tag['href']
end
def process_emoji(tag)
@ -301,6 +305,12 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
ThreadResolveWorker.perform_async(status.id, in_reply_to_uri, { 'request_id' => @options[:request_id] })
end
def resolve_unresolved_mentions(status)
@unresolved_mentions.uniq.each do |uri|
MentionResolveWorker.perform_in(rand(30...600).seconds, status.id, uri, { 'request_id' => @options[:request_id] })
end
end
def fetch_replies(status)
collection = @object['replies']
return if collection.blank?

View File

@ -9,10 +9,10 @@ class Vacuum::ImportsVacuum
private
def clean_unconfirmed_imports!
BulkImport.state_unconfirmed.where(created_at: ..10.minutes.ago).reorder(nil).in_batches.delete_all
BulkImport.state_unconfirmed.where(created_at: ..10.minutes.ago).in_batches.delete_all
end
def clean_old_imports!
BulkImport.where(created_at: ..1.week.ago).reorder(nil).in_batches.delete_all
BulkImport.where(created_at: ..1.week.ago).in_batches.delete_all
end
end

View File

@ -0,0 +1,72 @@
# frozen_string_literal: true
class WebPushRequest
SIGNATURE_ALGORITHM = 'p256ecdsa'
AUTH_HEADER = 'WebPush'
PAYLOAD_EXPIRATION = 24.hours
JWT_ALGORITHM = 'ES256'
JWT_TYPE = 'JWT'
attr_reader :web_push_subscription
delegate(
:endpoint,
:key_auth,
:key_p256dh,
to: :web_push_subscription
)
def initialize(web_push_subscription)
@web_push_subscription = web_push_subscription
end
def audience
@audience ||= Addressable::URI.parse(endpoint).normalized_site
end
def authorization_header
[AUTH_HEADER, encoded_json_web_token].join(' ')
end
def crypto_key_header
[SIGNATURE_ALGORITHM, vapid_key.public_key_for_push_header].join('=')
end
def encrypt(payload)
Webpush::Encryption.encrypt(payload, key_p256dh, key_auth)
end
private
def encoded_json_web_token
JWT.encode(
web_token_payload,
vapid_key.curve,
JWT_ALGORITHM,
typ: JWT_TYPE
)
end
def web_token_payload
{
aud: audience,
exp: PAYLOAD_EXPIRATION.from_now.to_i,
sub: payload_subject,
}
end
def payload_subject
[:mailto, contact_email].join(':')
end
def vapid_key
@vapid_key ||= Webpush::VapidKey.from_keys(
Rails.configuration.x.vapid_public_key,
Rails.configuration.x.vapid_private_key
)
end
def contact_email
@contact_email ||= ::Setting.site_contact_email
end
end

View File

@ -21,7 +21,7 @@ class AccountFilter
end
def results
scope = Account.includes(:account_stat, user: [:ips, :invite_request]).without_instance_actor.reorder(nil)
scope = Account.includes(:account_stat, user: [:ips, :invite_request]).without_instance_actor
relevant_params.each do |key, value|
next if key.to_s == 'page'

View File

@ -14,7 +14,7 @@ class Admin::TagFilter
end
def results
scope = Tag.reorder(nil)
scope = Tag.all
params.each do |key, value|
next if key == :page

View File

@ -29,26 +29,6 @@ class Web::PushSubscription < ApplicationRecord
delegate :locale, to: :associated_user
def encrypt(payload)
Webpush::Encryption.encrypt(payload, key_p256dh, key_auth)
end
def audience
@audience ||= Addressable::URI.parse(endpoint).normalized_site
end
def crypto_key_header
p256ecdsa = vapid_key.public_key_for_push_header
"p256ecdsa=#{p256ecdsa}"
end
def authorization_header
jwt = JWT.encode({ aud: audience, exp: 24.hours.from_now.to_i, sub: "mailto:#{contact_email}" }, vapid_key.curve, 'ES256', typ: 'JWT')
"WebPush #{jwt}"
end
def pushable?(notification)
policy_allows_notification?(notification) && alert_enabled_for_notification_type?(notification)
end
@ -92,14 +72,6 @@ class Web::PushSubscription < ApplicationRecord
)
end
def vapid_key
@vapid_key ||= Webpush::VapidKey.from_keys(Rails.configuration.x.vapid_public_key, Rails.configuration.x.vapid_private_key)
end
def contact_email
@contact_email ||= ::Setting.site_contact_email
end
def alert_enabled_for_notification_type?(notification)
truthy?(data&.dig('alerts', notification.type.to_s))
end

View File

@ -16,12 +16,12 @@ class PurgeDomainService < BaseService
end
def purge_accounts!
Account.remote.where(domain: @domain).reorder(nil).find_each do |account|
Account.remote.where(domain: @domain).find_each do |account|
DeleteAccountService.new.call(account, reserve_username: false, skip_side_effects: true)
end
end
def purge_emojis!
CustomEmoji.remote.where(domain: @domain).reorder(nil).find_each(&:destroy)
CustomEmoji.remote.where(domain: @domain).find_each(&:destroy)
end
end

View File

@ -30,7 +30,7 @@
= render 'admin/accounts/counters', account: @account
- if @account.local? && @account.user.nil?
= link_to t('admin.accounts.unblock_email'), unblock_email_admin_account_path(@account.id), method: :post, class: 'button' if can?(:unblock_email, @account) && CanonicalEmailBlock.exists?(reference_account_id: @account.id)
= button_to t('admin.accounts.unblock_email'), unblock_email_admin_account_path(@account.id), class: :button if can?(:unblock_email, @account) && CanonicalEmailBlock.exists?(reference_account_id: @account.id)
- else
.table-wrapper
%table.table.inline-table

View File

@ -21,7 +21,7 @@
- if @instance.domain_allow
= link_to t('admin.domain_allows.undo'), admin_domain_allow_path(@instance.domain_allow), class: 'button button--destructive', data: { confirm: t('admin.accounts.are_you_sure'), method: :delete }
- else
= link_to t('admin.domain_allows.add_new'), admin_domain_allows_path(domain_allow: { domain: @instance.domain }), class: 'button', method: :post
= button_to t('admin.domain_allows.add_new'), admin_domain_allows_path(domain_allow: { domain: @instance.domain }), class: :button
- else
%p= t('admin.instances.content_policies.description_html')
@ -40,7 +40,7 @@
%td= @instance.domain_block.policies.map { |policy| t(policy, scope: 'admin.instances.content_policies.policies') }.join(' · ')
= link_to t('admin.domain_blocks.edit'), edit_admin_domain_block_path(@instance.domain_block), class: 'button'
= link_to t('admin.domain_blocks.undo'), admin_domain_block_path(@instance.domain_block), class: 'button', data: { confirm: t('admin.accounts.are_you_sure'), method: :delete }
= button_to t('admin.domain_blocks.undo'), admin_domain_block_path(@instance.domain_block), class: :button, data: { confirm: t('admin.accounts.are_you_sure'), method: :delete }
- else
= link_to t('admin.domain_blocks.add_new'), new_admin_domain_block_path(_domain: @instance.domain), class: 'button'
@ -70,16 +70,16 @@
- if @instance.unavailable?
%span.negative-hint
= t('admin.instances.availability.failure_threshold_reached', date: l(@instance.unavailable_domain.created_at.to_date))
= link_to t('admin.instances.delivery.restart'), restart_delivery_admin_instance_path(@instance), data: { confirm: t('admin.accounts.are_you_sure'), method: :post }
= button_to t('admin.instances.delivery.restart'), restart_delivery_admin_instance_path(@instance), data: { confirm: t('admin.accounts.are_you_sure') }
- elsif @instance.exhausted_deliveries_days.empty?
%span.positive-hint
= t('admin.instances.availability.no_failures_recorded')
= link_to t('admin.instances.delivery.stop'), stop_delivery_admin_instance_path(@instance), data: { confirm: t('admin.accounts.are_you_sure'), method: :post }
= button_to t('admin.instances.delivery.stop'), stop_delivery_admin_instance_path(@instance), data: { confirm: t('admin.accounts.are_you_sure') }
- else
%span.negative-hint
= t('admin.instances.availability.failures_recorded', count: @instance.delivery_failure_tracker.days)
%span= link_to t('admin.instances.delivery.clear'), clear_delivery_errors_admin_instance_path(@instance), data: { confirm: t('admin.accounts.are_you_sure'), method: :post } unless @instance.exhausted_deliveries_days.empty?
%span= link_to t('admin.instances.delivery.stop'), stop_delivery_admin_instance_path(@instance), data: { confirm: t('admin.accounts.are_you_sure'), method: :post }
%span= button_to t('admin.instances.delivery.clear'), clear_delivery_errors_admin_instance_path(@instance), data: { confirm: t('admin.accounts.are_you_sure') } unless @instance.exhausted_deliveries_days.empty?
%span= button_to t('admin.instances.delivery.stop'), stop_delivery_admin_instance_path(@instance), data: { confirm: t('admin.accounts.are_you_sure') }
- if @instance.purgeable?
%p= t('admin.instances.purge_description_html')

View File

@ -34,4 +34,4 @@
= paginate @invites
- if policy(:invite).deactivate_all?
= link_to t('admin.invites.deactivate_all'), deactivate_all_admin_invites_path, method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button'
= button_to t('admin.invites.deactivate_all'), deactivate_all_admin_invites_path, data: { confirm: t('admin.accounts.are_you_sure') }, class: :button

View File

@ -2,7 +2,7 @@
.report-actions
.report-actions__item
.report-actions__item__button
= link_to t('admin.reports.mark_as_resolved'), resolve_admin_report_path(report), method: :post, class: 'button'
= button_to t('admin.reports.mark_as_resolved'), resolve_admin_report_path(report), class: :button
.report-actions__item__description
= t('admin.reports.actions.resolve_description_html')
- if statuses.any? { |status| (status.with_media? || status.with_preview_card?) && !status.discarded? }

View File

@ -3,9 +3,9 @@
- content_for :heading_actions do
- if @report.unresolved?
= link_to t('admin.reports.mark_as_resolved'), resolve_admin_report_path(@report), method: :post, class: 'button'
= button_to t('admin.reports.mark_as_resolved'), resolve_admin_report_path(@report), class: :button
- else
= link_to t('admin.reports.mark_as_unresolved'), reopen_admin_report_path(@report), method: :post, class: 'button'
= button_to t('admin.reports.mark_as_unresolved'), reopen_admin_report_path(@report), class: :button
- unless @report.account.local? || @report.target_account.local?
.flash-message= t('admin.reports.forwarded_replies_explanation')

View File

@ -3,8 +3,8 @@
- content_for :heading_actions do
- if @appeal.persisted?
= link_to t('disputes.strikes.approve_appeal'), approve_admin_disputes_appeal_path(@appeal), method: :post, class: 'button' if can?(:approve, @appeal)
= link_to t('disputes.strikes.reject_appeal'), reject_admin_disputes_appeal_path(@appeal), method: :post, class: 'button button--destructive' if can?(:reject, @appeal)
= button_to t('disputes.strikes.approve_appeal'), approve_admin_disputes_appeal_path(@appeal), class: :button if can?(:approve, @appeal)
= button_to t('disputes.strikes.reject_appeal'), reject_admin_disputes_appeal_path(@appeal), class: 'button button--destructive' if can?(:reject, @appeal)
- if @strike.overruled?
%p.hint

View File

@ -46,7 +46,7 @@
%p.muted-hint= t('exports.archive_takeout.hint_html')
- if policy(:backup).create?
%p= link_to t('exports.archive_takeout.request'), settings_export_path, class: 'button', method: :post
%p= button_to t('exports.archive_takeout.request'), settings_export_path, class: :button
- unless @backups.empty?
%hr.spacer/

View File

@ -6,4 +6,4 @@
%hr.spacer/
= link_to t('otp_authentication.setup'), settings_otp_authentication_path, data: { method: :post }, class: 'block-button'
= button_to t('otp_authentication.setup'), settings_otp_authentication_path, class: 'block-button'

View File

@ -2,7 +2,7 @@
= t('settings.two_factor_authentication')
- content_for :heading_actions do
= link_to t('two_factor_authentication.disable'), disable_settings_two_factor_authentication_methods_path, class: 'button button--destructive', method: :post
= button_to t('two_factor_authentication.disable'), disable_settings_two_factor_authentication_methods_path, class: 'button button--destructive'
%p.hint
%span.positive-hint
@ -38,4 +38,4 @@
%hr.spacer/
.simple_form
= link_to t('two_factor_authentication.generate_recovery_codes'), settings_two_factor_authentication_recovery_codes_path, data: { method: :post }, class: 'block-button'
= button_to t('two_factor_authentication.generate_recovery_codes'), settings_two_factor_authentication_recovery_codes_path, class: 'block-button'

View File

@ -4,6 +4,6 @@ class FilteredNotificationCleanupWorker
include Sidekiq::Worker
def perform(account_id, from_account_id)
Notification.where(account_id: account_id, from_account_id: from_account_id, filtered: true).reorder(nil).in_batches(order: :desc).delete_all
Notification.where(account_id: account_id, from_account_id: from_account_id, filtered: true).in_batches(order: :desc).delete_all
end
end

View File

@ -0,0 +1,37 @@
# frozen_string_literal: true
class MentionResolveWorker
include Sidekiq::Worker
include ExponentialBackoff
include JsonLdHelper
sidekiq_options queue: 'pull', retry: 7
def perform(status_id, uri, options = {})
status = Status.find_by(id: status_id)
return if status.nil?
account = account_from_uri(uri)
account = ActivityPub::FetchRemoteAccountService.new.call(uri, request_id: options[:request_id]) if account.nil?
return if account.nil?
status.mentions.create!(account: account, silent: false)
rescue ActiveRecord::RecordNotFound
# Do nothing
rescue Mastodon::UnexpectedResponseError => e
response = e.response
if response_error_unsalvageable?(response)
# Give up
else
raise e
end
end
private
def account_from_uri(uri)
ActivityPub::TagManager.instance.uri_to_resource(uri, Account)
end
end

View File

@ -16,7 +16,7 @@ class Scheduler::UserCleanupScheduler
private
def clean_unconfirmed_accounts!
User.unconfirmed.where(confirmation_sent_at: ..UNCONFIRMED_ACCOUNTS_MAX_AGE_DAYS.days.ago).reorder(nil).find_in_batches do |batch|
User.unconfirmed.where(confirmation_sent_at: ..UNCONFIRMED_ACCOUNTS_MAX_AGE_DAYS.days.ago).find_in_batches do |batch|
# We have to do it separately because of missing database constraints
AccountModerationNote.where(target_account_id: batch.map(&:account_id)).delete_all
Account.where(id: batch.map(&:account_id)).delete_all

View File

@ -16,10 +16,10 @@ class Web::PushNotificationWorker
# in the meantime, so we have to double-check before proceeding
return unless @notification.activity.present? && @subscription.pushable?(@notification)
payload = @subscription.encrypt(push_notification_json)
payload = web_push_request.encrypt(push_notification_json)
request_pool.with(@subscription.audience) do |http_client|
request = Request.new(:post, @subscription.endpoint, body: payload.fetch(:ciphertext), http_client: http_client)
request_pool.with(web_push_request.audience) do |http_client|
request = Request.new(:post, web_push_request.endpoint, body: payload.fetch(:ciphertext), http_client: http_client)
request.add_headers(
'Content-Type' => 'application/octet-stream',
@ -27,8 +27,8 @@ class Web::PushNotificationWorker
'Urgency' => URGENCY,
'Content-Encoding' => 'aesgcm',
'Encryption' => "salt=#{Webpush.encode64(payload.fetch(:salt)).delete('=')}",
'Crypto-Key' => "dh=#{Webpush.encode64(payload.fetch(:server_public_key)).delete('=')};#{@subscription.crypto_key_header}",
'Authorization' => @subscription.authorization_header
'Crypto-Key' => "dh=#{Webpush.encode64(payload.fetch(:server_public_key)).delete('=')};#{web_push_request.crypto_key_header}",
'Authorization' => web_push_request.authorization_header
)
request.perform do |response|
@ -50,17 +50,27 @@ class Web::PushNotificationWorker
private
def web_push_request
@web_push_request || WebPushRequest.new(@subscription)
end
def push_notification_json
json = I18n.with_locale(@subscription.locale.presence || I18n.default_locale) do
Oj.dump(serialized_notification_in_subscription_locale.as_json)
end
def serialized_notification_in_subscription_locale
I18n.with_locale(@subscription.locale.presence || I18n.default_locale) do
serialized_notification
end
end
def serialized_notification
ActiveModelSerializers::SerializableResource.new(
@notification,
serializer: Web::NotificationSerializer,
scope: @subscription,
scope_name: :current_push_subscription
).as_json
end
Oj.dump(json)
)
end
def request_pool

View File

@ -931,6 +931,9 @@ cy:
message_html: Nid ydych wedi diffinio unrhyw reolau gweinydd.
sidekiq_process_check:
message_html: Does dim proses Sidekiq yn rhedeg ar gyfer y ciw(iau) %{value}. Adolygwch eich ffurfweddiad Sidekiq
software_version_check:
action: Gweld y diweddariadau sydd ar gael
message_html: Mae diweddariad Mastodon ar gael.
software_version_critical_check:
action: Gweld y diweddariadau sydd ar gael
message_html: Mae diweddariad hanfodol Mastodon ar gael, diweddarwch cyn gynted â phosibl.
@ -1796,6 +1799,7 @@ cy:
delete: Dileu cyfrif
development: Datblygu
edit_profile: Golygu proffil
export: Allforio
featured_tags: Prif hashnodau
import: Mewnforio
import_and_export: Mewnforio ac allforio

View File

@ -69,7 +69,7 @@ gl:
buttons:
revoke: Retirar autorización
confirmations:
revoke: Estás segura?
revoke: Tes certeza?
index:
authorized_at: Autorizada o %{date}
description_html: Estas aplicacións poden acceder á túa conta usando a API. Se ves aplicacións que non recoñeces, ou hai comportamentos non consentidos dalgunha delas, podes revogar o acceso.

View File

@ -903,6 +903,9 @@ he:
message_html: לא הוגדרו שום כללי שרת.
sidekiq_process_check:
message_html: שום הליכי Sidekiq לא רצים עבור %{value} תור(ות). בחנו בבקשה את הגדרות Sidekiq
software_version_check:
action: ראו עדכונים זמינים
message_html: עדכון מסטודון זמין כעת.
software_version_critical_check:
action: ראו עדכונים זמינים
message_html: יצא עדכון קריטי למסטודון, נא לעדכן את תוכנת מסטודון בהקדם האפשרי.
@ -1744,6 +1747,7 @@ he:
delete: מחיקת חשבון
development: פיתוח
edit_profile: עריכת פרופיל
export: ייצוא
featured_tags: תגיות נבחרות
import: יבוא
import_and_export: יבוא ויצוא

View File

@ -1,7 +1,7 @@
---
nn:
about:
about_mastodon_html: 'Framtidas sosiale nettverk: Ingen annonsar, ingen verksemder som overvaker deg, etisk design og desentralisering! Eig idéane dine med Mastodon!'
about_mastodon_html: 'Framtidas sosiale nettverk: Ingen annonsar, ingen verksemder som overvaker deg, etisk design og desentralisering! Eig dataene dine med Mastodon!'
contact_missing: Ikkje sett
contact_unavailable: I/T
hosted_on: "%{domain} er vert for Mastodon"
@ -39,7 +39,7 @@ nn:
avatar: Bilete
by_domain: Domene
change_email:
changed_msg: Konto-e-posten er endra!
changed_msg: E-post for konto er endra!
current_email: Noverande e-post
label: Byt e-post
new_email: Ny e-post
@ -62,7 +62,7 @@ nn:
disable: Slå av
disable_sign_in_token_auth: Slå av e-post-token-autentisering
disable_two_factor_authentication: Slå av 2FA
disabled: Slege av
disabled: Inaktiv
display_name: Synleg namn
domain: Domene
edit: Rediger

View File

@ -67,7 +67,7 @@ Rails.application.routes.draw do
scope path: '.well-known' do
scope module: :well_known do
get 'oauth-authorization-server', to: 'oauth_metadata#show', as: :oauth_metadata, defaults: { format: 'json' }
get 'host-meta', to: 'host_meta#show', as: :host_meta, defaults: { format: 'xml' }
get 'host-meta', to: 'host_meta#show', as: :host_meta
get 'nodeinfo', to: 'node_info#index', as: :nodeinfo, defaults: { format: 'json' }
get 'webfinger', to: 'webfinger#show', as: :webfinger
end

View File

@ -2,5 +2,5 @@
Fabricator(:account_domain_block) do
account { Fabricate.build(:account) }
domain 'example.com'
domain { sequence { |n| "host-#{n}.example" } }
end

View File

@ -63,6 +63,24 @@ RSpec.describe ActivityPub::Activity::Create do
}
end
let(:invalid_mention_json) do
{
id: [ActivityPub::TagManager.instance.uri_for(sender), 'post2'].join('/'),
type: 'Note',
to: [
'https://www.w3.org/ns/activitystreams#Public',
ActivityPub::TagManager.instance.uri_for(follower),
],
content: '@bob lorem ipsum',
published: 1.hour.ago.utc.iso8601,
updated: 1.hour.ago.utc.iso8601,
tag: {
type: 'Mention',
href: 'http://notexisting.dontexistingtld/actor',
},
}
end
def activity_for_object(json)
{
'@context': 'https://www.w3.org/ns/activitystreams',
@ -117,6 +135,25 @@ RSpec.describe ActivityPub::Activity::Create do
# Creates two notifications
expect(Notification.count).to eq 2
end
it 'ignores unprocessable mention', :aggregate_failures do
stub_request(:get, invalid_mention_json[:tag][:href]).to_raise(HTTP::ConnectionError)
# When receiving the post that contains an invalid mention…
described_class.new(activity_for_object(invalid_mention_json), sender, delivery: true).perform
# NOTE: Refering explicitly to the workers is a bit awkward
DistributionWorker.drain
FeedInsertWorker.drain
# …it creates a status
status = Status.find_by(uri: invalid_mention_json[:id])
# Check the process did not crash
expect(status.nil?).to be false
# It has queued a mention resolve job
expect(MentionResolveWorker).to have_enqueued_sidekiq_job(status.id, invalid_mention_json[:tag][:href], anything)
end
end
describe '#perform' do

View File

@ -3,66 +3,175 @@
require 'rails_helper'
RSpec.describe Export do
subject { described_class.new(account) }
let(:account) { Fabricate(:account) }
let(:target_accounts) do
[{}, { username: 'one', domain: 'local.host' }].map(&method(:Fabricate).curry(2).call(:account))
[
Fabricate(:account),
Fabricate(:account, username: 'one', domain: 'local.host'),
]
end
describe 'to_csv' do
describe '#to_bookmarks_csv' do
before { Fabricate.times(2, :bookmark, account: account) }
let(:export) { CSV.parse(subject.to_bookmarks_csv) }
it 'returns a csv of bookmarks' do
expect(export)
.to contain_exactly(
include(/statuses/),
include(/statuses/)
)
end
end
describe '#to_blocked_accounts_csv' do
before { target_accounts.each { |target_account| account.block!(target_account) } }
let(:export) { CSV.parse(subject.to_blocked_accounts_csv) }
it 'returns a csv of the blocked accounts' do
target_accounts.each { |target_account| account.block!(target_account) }
export = described_class.new(account).to_blocked_accounts_csv
results = export.strip.split
expect(results.size).to eq 2
expect(results.first).to eq 'one@local.host'
expect(export)
.to contain_exactly(
include('one@local.host'),
include(be_present)
)
end
end
describe '#to_muted_accounts_csv' do
before { target_accounts.each { |target_account| account.mute!(target_account) } }
let(:export) { CSV.parse(subject.to_muted_accounts_csv) }
it 'returns a csv of the muted accounts' do
target_accounts.each { |target_account| account.mute!(target_account) }
export = described_class.new(account).to_muted_accounts_csv
results = export.strip.split("\n")
expect(results.size).to eq 3
expect(results.first).to eq 'Account address,Hide notifications'
expect(results.second).to eq 'one@local.host,true'
expect(export)
.to contain_exactly(
contain_exactly('Account address', 'Hide notifications'),
include('one@local.host', 'true'),
include(be_present)
)
end
end
describe '#to_following_accounts_csv' do
before { target_accounts.each { |target_account| account.follow!(target_account) } }
let(:export) { CSV.parse(subject.to_following_accounts_csv) }
it 'returns a csv of the following accounts' do
target_accounts.each { |target_account| account.follow!(target_account) }
export = described_class.new(account).to_following_accounts_csv
results = export.strip.split("\n")
expect(results.size).to eq 3
expect(results.first).to eq 'Account address,Show boosts,Notify on new posts,Languages'
expect(results.second).to eq 'one@local.host,true,false,'
expect(export)
.to contain_exactly(
contain_exactly('Account address', 'Show boosts', 'Notify on new posts', 'Languages'),
include('one@local.host', 'true', 'false', be_blank),
include(be_present)
)
end
end
describe 'total_storage' do
describe '#to_lists_csv' do
before do
target_accounts.each do |target_account|
account.follow!(target_account)
Fabricate(:list, account: account).accounts << target_account
end
end
let(:export) { CSV.parse(subject.to_lists_csv) }
it 'returns a csv of the lists' do
expect(export)
.to contain_exactly(
include('one@local.host'),
include(be_present)
)
end
end
describe '#to_blocked_domains_csv' do
before { Fabricate.times(2, :account_domain_block, account: account) }
let(:export) { CSV.parse(subject.to_blocked_domains_csv) }
it 'returns a csv of the blocked domains' do
expect(export)
.to contain_exactly(
include(/example/),
include(/example/)
)
end
end
describe '#total_storage' do
it 'returns the total size of the media attachments' do
media_attachment = Fabricate(:media_attachment, account: account)
expect(described_class.new(account).total_storage).to eq media_attachment.file_file_size || 0
expect(subject.total_storage).to eq media_attachment.file_file_size || 0
end
end
describe 'total_follows' do
it 'returns the total number of the followed accounts' do
target_accounts.each { |target_account| account.follow!(target_account) }
expect(described_class.new(account.reload).total_follows).to eq 2
describe '#total_statuses' do
before { Fabricate.times(2, :status, account: account) }
it 'returns the total number of statuses' do
expect(subject.total_statuses).to eq(2)
end
end
describe '#total_bookmarks' do
before { Fabricate.times(2, :bookmark, account: account) }
it 'returns the total number of bookmarks' do
expect(subject.total_bookmarks).to eq(2)
end
end
describe '#total_follows' do
before { target_accounts.each { |target_account| account.follow!(target_account) } }
it 'returns the total number of the followed accounts' do
expect(subject.total_follows).to eq(2)
end
end
describe '#total_lists' do
before { Fabricate.times(2, :list, account: account) }
it 'returns the total number of lists' do
expect(subject.total_lists).to eq(2)
end
end
describe '#total_followers' do
before { target_accounts.each { |target_account| target_account.follow!(account) } }
it 'returns the total number of the follower accounts' do
expect(subject.total_followers).to eq(2)
end
end
describe '#total_blocks' do
before { target_accounts.each { |target_account| account.block!(target_account) } }
it 'returns the total number of the blocked accounts' do
target_accounts.each { |target_account| account.block!(target_account) }
expect(described_class.new(account.reload).total_blocks).to eq 2
expect(subject.total_blocks).to eq(2)
end
end
describe '#total_mutes' do
before { target_accounts.each { |target_account| account.mute!(target_account) } }
it 'returns the total number of the muted accounts' do
target_accounts.each { |target_account| account.mute!(target_account) }
expect(described_class.new(account.reload).total_mutes).to eq 2
expect(subject.total_mutes).to eq(2)
end
end
describe '#total_domain_blocks' do
before { Fabricate.times(2, :account_domain_block, account: account) }
it 'returns the total number of account domain blocks' do
expect(subject.total_domain_blocks).to eq(2)
end
end
end

View File

@ -9,19 +9,39 @@ RSpec.describe 'The /.well-known/host-meta request' do
expect(response)
.to have_http_status(200)
.and have_attributes(
media_type: 'application/xrd+xml',
body: host_meta_xml_template
media_type: 'application/xrd+xml'
)
doc = Nokogiri::XML(response.parsed_body)
expect(doc.at_xpath('/xrd:XRD/xrd:Link[@rel="lrdd"]/@template', 'xrd' => 'http://docs.oasis-open.org/ns/xri/xrd-1.0').value)
.to eq 'https://cb6e6126.ngrok.io/.well-known/webfinger?resource={uri}'
end
it 'returns http success with valid JSON response with .json extension' do
get '/.well-known/host-meta.json'
expect(response)
.to have_http_status(200)
.and have_attributes(
media_type: 'application/json'
)
expect(response.parsed_body)
.to include(
links: [
'rel' => 'lrdd',
'template' => 'https://cb6e6126.ngrok.io/.well-known/webfinger?resource={uri}',
]
)
end
private
it 'returns http success with valid JSON response with Accept header' do
get '/.well-known/host-meta', headers: { 'Accept' => 'application/json' }
def host_meta_xml_template
<<~XML
<?xml version="1.0" encoding="UTF-8"?>
<XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0">
<Link rel="lrdd" template="https://cb6e6126.ngrok.io/.well-known/webfinger?resource={uri}"/>
</XRD>
XML
expect(response)
.to have_http_status(200)
.and have_attributes(
media_type: 'application/json'
)
end
end

View File

@ -4,9 +4,14 @@ require 'rails_helper'
RSpec.describe 'Well Known routes' do
describe 'the host-meta route' do
it 'routes to correct place with xml format' do
it 'routes to correct place' do
expect(get('/.well-known/host-meta'))
.to route_to('well_known/host_meta#show', format: 'xml')
.to route_to('well_known/host_meta#show')
end
it 'routes to correct place with json format' do
expect(get('/.well-known/host-meta.json'))
.to route_to('well_known/host_meta#show', format: 'json')
end
end

View File

@ -0,0 +1,38 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe MentionResolveWorker do
let(:status_id) { -42 }
let(:uri) { 'https://example.com/users/unknown' }
describe '#perform' do
subject { described_class.new.perform(status_id, uri, {}) }
context 'with a non-existent status' do
it 'returns nil' do
expect(subject).to be_nil
end
end
context 'with a valid user' do
let(:status) { Fabricate(:status) }
let(:status_id) { status.id }
let(:service_double) { instance_double(ActivityPub::FetchRemoteAccountService) }
before do
allow(ActivityPub::FetchRemoteAccountService).to receive(:new).and_return(service_double)
allow(service_double).to receive(:call).with(uri, anything) { Fabricate(:account, domain: 'example.com', uri: uri) }
end
it 'resolves the account and adds a new mention', :aggregate_failures do
expect { subject }
.to change { status.reload.mentions }.from([]).to(a_collection_including(having_attributes(account: having_attributes(uri: uri), silent: false)))
expect(service_double).to have_received(:call).once
end
end
end
end

View File

@ -22,19 +22,38 @@ RSpec.describe Web::PushNotificationWorker do
let(:payload) { { ciphertext: ciphertext, salt: salt, server_public_key: server_public_key, shared_secret: shared_secret } }
describe 'perform' do
around do |example|
original_private = Rails.configuration.x.vapid_private_key
original_public = Rails.configuration.x.vapid_public_key
Rails.configuration.x.vapid_private_key = vapid_private_key
Rails.configuration.x.vapid_public_key = vapid_public_key
example.run
Rails.configuration.x.vapid_private_key = original_private
Rails.configuration.x.vapid_public_key = original_public
end
before do
allow(subscription).to receive_messages(contact_email: contact_email, vapid_key: vapid_key)
allow(Web::PushSubscription).to receive(:find).with(subscription.id).and_return(subscription)
Setting.site_contact_email = contact_email
allow(Webpush::Encryption).to receive(:encrypt).and_return(payload)
allow(JWT).to receive(:encode).and_return('jwt.encoded.payload')
stub_request(:post, endpoint).to_return(status: 201, body: '')
subject.perform(subscription.id, notification.id)
end
it 'calls the relevant service with the correct headers' do
expect(a_request(:post, endpoint).with(headers: {
subject.perform(subscription.id, notification.id)
expect(web_push_endpoint_request)
.to have_been_made
end
def web_push_endpoint_request
a_request(
:post,
endpoint
).with(
headers: {
'Content-Encoding' => 'aesgcm',
'Content-Type' => 'application/octet-stream',
'Crypto-Key' => "dh=BAgtUks5d90kFmxGevk9tH7GEmvz9DB0qcEMUsOBgKwMf-TMjsKIIG6LQvGcFAf6jcmAod15VVwmYwGIIxE4VWE;p256ecdsa=#{vapid_public_key.delete('=')}",
@ -42,7 +61,9 @@ RSpec.describe Web::PushNotificationWorker do
'Ttl' => '172800',
'Urgency' => 'normal',
'Authorization' => 'WebPush jwt.encoded.payload',
}, body: "+\xB8\xDBT}\u0013\xB6\xDD.\xF9\xB0\xA7\xC8Ҁ\xFD\x99#\xF7\xAC\x83\xA4\xDB,\u001F\xB5\xB9w\x85>\xF7\xADr")).to have_been_made
},
body: "+\xB8\xDBT}\u0013\xB6\xDD.\xF9\xB0\xA7\xC8Ҁ\xFD\x99#\xF7\xAC\x83\xA4\xDB,\u001F\xB5\xB9w\x85>\xF7\xADr"
)
end
end
end