From f02411da404f730e3c05dc7dca1ac0f2d631315e Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 19 Mar 2018 01:51:19 +0100 Subject: [PATCH 001/381] Ignore media validation when attaching to status during processing (#6822) Fix #6821 --- app/lib/activitypub/activity/create.rb | 2 +- app/lib/ostatus/activity/creation.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/lib/activitypub/activity/create.rb b/app/lib/activitypub/activity/create.rb index 5a1c13d..676e885 100644 --- a/app/lib/activitypub/activity/create.rb +++ b/app/lib/activitypub/activity/create.rb @@ -53,7 +53,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity visibility: visibility_from_audience, thread: replied_to_status, conversation: conversation_from_uri(@object['conversation']), - media_attachments: process_attachments.take(4), + media_attachment_ids: process_attachments.take(4).map(&:id), } end diff --git a/app/lib/ostatus/activity/creation.rb b/app/lib/ostatus/activity/creation.rb index aa46267..6235127 100644 --- a/app/lib/ostatus/activity/creation.rb +++ b/app/lib/ostatus/activity/creation.rb @@ -45,7 +45,7 @@ class OStatus::Activity::Creation < OStatus::Activity::Base visibility: visibility_scope, conversation: find_or_create_conversation, thread: thread? ? find_status(thread.first) || find_activitypub_status(thread.first, thread.second) : nil, - media_attachments: media_attachments + media_attachment_ids: media_attachments.map(&:id) ) save_mentions(status) From 74c39fada0e3d02836f85316077c5d1dc2e44588 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 19 Mar 2018 12:20:57 +0100 Subject: [PATCH 002/381] Bump version to 2.3.2rc3 --- lib/mastodon/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mastodon/version.rb b/lib/mastodon/version.rb index fefe632..78a2dd9 100644 --- a/lib/mastodon/version.rb +++ b/lib/mastodon/version.rb @@ -21,7 +21,7 @@ module Mastodon end def flags - 'rc2' + 'rc3' end def to_a From f7c46fc1138ff573c482e0c3059a2e7be43ef07e Mon Sep 17 00:00:00 2001 From: "Renato \"Lond\" Cerqueira" Date: Mon, 19 Mar 2018 15:12:06 +0100 Subject: [PATCH 003/381] Weblate translations 20180319 (#6827) * Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (280 of 280 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/pt_BR/ * Translated using Weblate (Spanish) Currently translated at 99.6% (579 of 581 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/es/ * Translated using Weblate (Spanish) Currently translated at 100.0% (58 of 58 strings) Translation: Mastodon/Preferences Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/simple_form/es/ * Translated using Weblate (Japanese) Currently translated at 100.0% (280 of 280 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/ja/ * Translated using Weblate (French) Currently translated at 100.0% (280 of 280 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/fr/ * Translated using Weblate (Spanish) Currently translated at 100.0% (58 of 58 strings) Translation: Mastodon/Preferences Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/simple_form/es/ * Translated using Weblate (Indonesian) Currently translated at 100.0% (280 of 280 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/id/ * Translated using Weblate (Spanish) Currently translated at 100.0% (280 of 280 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/es/ * Translated using Weblate (Indonesian) Currently translated at 94.6% (71 of 75 strings) Translation: Mastodon/Doorkeeper Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/doorkeeper/id/ * Translated using Weblate (Indonesian) Currently translated at 100.0% (280 of 280 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/id/ * Translated using Weblate (Arabic) Currently translated at 100.0% (62 of 62 strings) Translation: Mastodon/Devise Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/devise/ar/ * Translated using Weblate (Indonesian) Currently translated at 100.0% (280 of 280 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/id/ * Translated using Weblate (Dutch) Currently translated at 100.0% (280 of 280 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/nl/ * Translated using Weblate (Arabic) Currently translated at 75.5% (439 of 581 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/ar/ * Translated using Weblate (Arabic) Currently translated at 76.2% (443 of 581 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/ar/ * Translated using Weblate (Russian) Currently translated at 95.8% (557 of 581 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/ru/ * Translated using Weblate (Finnish) Currently translated at 100.0% (280 of 280 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/fi/ * Translated using Weblate (Slovak) Currently translated at 100.0% (280 of 280 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/sk/ * Translated using Weblate (Slovak) Currently translated at 91.7% (533 of 581 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/sk/ * Translated using Weblate (Dutch) Currently translated at 100.0% (280 of 280 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/nl/ * Translated using Weblate (Spanish) Currently translated at 99.8% (580 of 581 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/es/ * Translated using Weblate (Finnish) Currently translated at 100.0% (280 of 280 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/fi/ * Translated using Weblate (Slovak) Currently translated at 100.0% (280 of 280 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/sk/ * Translated using Weblate (Spanish) Currently translated at 100.0% (280 of 280 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/es/ * Translated using Weblate (Swedish) Currently translated at 100.0% (280 of 280 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/sv/ * Translated using Weblate (Finnish) Currently translated at 93.1% (54 of 58 strings) Translation: Mastodon/Preferences Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/simple_form/fi/ * Translated using Weblate (Arabic) Currently translated at 76.7% (446 of 581 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/ar/ * Translated using Weblate (Slovak) Currently translated at 93.2% (542 of 581 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/sk/ * Translated using Weblate (Arabic) Currently translated at 100.0% (280 of 280 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/ar/ * Normalize translations Ran i18n-tasks normalize && yarn manage:translations --- app/javascript/mastodon/locales/ar.json | 10 ++++----- app/javascript/mastodon/locales/fi.json | 20 ++++++++--------- app/javascript/mastodon/locales/sk.json | 38 ++++++++++++++++---------------- app/javascript/mastodon/locales/sv.json | 12 +++++----- config/locales/ar.yml | 3 +++ config/locales/es.yml | 1 + config/locales/simple_form.fi.yml | 39 +++++++++++++++++++++++++-------- config/locales/sk.yml | 14 +++++++++++- 8 files changed, 87 insertions(+), 50 deletions(-) diff --git a/app/javascript/mastodon/locales/ar.json b/app/javascript/mastodon/locales/ar.json index 4928930..73680a1 100644 --- a/app/javascript/mastodon/locales/ar.json +++ b/app/javascript/mastodon/locales/ar.json @@ -60,10 +60,10 @@ "compose_form.placeholder": "فيمَ تفكّر؟", "compose_form.publish": "بوّق", "compose_form.publish_loud": "{publish}!", - "compose_form.sensitive.marked": "Media is marked as sensitive", + "compose_form.sensitive.marked": "لقد تم تحديد هذه الصورة كحساسة", "compose_form.sensitive.unmarked": "Media is not marked as sensitive", - "compose_form.spoiler.marked": "Text is hidden behind warning", - "compose_form.spoiler.unmarked": "Text is not hidden", + "compose_form.spoiler.marked": "إنّ النص مخفي وراء تحذير", + "compose_form.spoiler.unmarked": "النص غير مخفي", "compose_form.spoiler_placeholder": "تنبيه عن المحتوى", "confirmation_modal.cancel": "إلغاء", "confirmations.block.confirm": "حجب", @@ -254,9 +254,9 @@ "status.sensitive_warning": "محتوى حساس", "status.share": "مشاركة", "status.show_less": "إعرض أقلّ", - "status.show_less_all": "Show less for all", + "status.show_less_all": "طي الكل", "status.show_more": "أظهر المزيد", - "status.show_more_all": "Show more for all", + "status.show_more_all": "توسيع الكل", "status.unmute_conversation": "فك الكتم عن المحادثة", "status.unpin": "فك التدبيس من الملف الشخصي", "tabs_bar.federated_timeline": "الموحَّد", diff --git a/app/javascript/mastodon/locales/fi.json b/app/javascript/mastodon/locales/fi.json index 1dea42e..1741445 100644 --- a/app/javascript/mastodon/locales/fi.json +++ b/app/javascript/mastodon/locales/fi.json @@ -3,7 +3,7 @@ "account.block_domain": "Piilota kaikki sisältö verkkotunnuksesta {domain}", "account.blocked": "Estetty", "account.disclaimer_full": "Alla olevat käyttäjän profiilitiedot saattavat olla epätäydellisiä.", - "account.domain_blocked": "Domain hidden", + "account.domain_blocked": "Verkko-osoite piilotettu", "account.edit_profile": "Muokkaa", "account.follow": "Seuraa", "account.followers": "Seuraajia", @@ -60,10 +60,10 @@ "compose_form.placeholder": "Mitä sinulla on mielessä?", "compose_form.publish": "Toot", "compose_form.publish_loud": "{publish}!", - "compose_form.sensitive.marked": "Media is marked as sensitive", - "compose_form.sensitive.unmarked": "Media is not marked as sensitive", - "compose_form.spoiler.marked": "Text is hidden behind warning", - "compose_form.spoiler.unmarked": "Text is not hidden", + "compose_form.sensitive.marked": "Media on merkitty arkaluontoiseksi", + "compose_form.sensitive.unmarked": "Mediaa ei ole merkitty arkaluontoiseksi", + "compose_form.spoiler.marked": "Teksti on piilotettu varoituksen taakse", + "compose_form.spoiler.unmarked": "Teksti ei ole piilotettu", "compose_form.spoiler_placeholder": "Content warning", "confirmation_modal.cancel": "Peruuta", "confirmations.block.confirm": "Estä", @@ -182,13 +182,13 @@ "onboarding.page_four.notifications": "Ilmoitukset-sarake näyttää sinulle, kun joku on viestii kanssasi.", "onboarding.page_one.federation": "Mastodon on yhteisöpalvelu, joka toimii monen itsenäisen palvelimen muodostamassa verkossa. Me kutsumme näitä palvelimia instansseiksi.", "onboarding.page_one.full_handle": "Koko käyttäjänimesi", - "onboarding.page_one.handle_hint": "This is what you would tell your friends to search for.", + "onboarding.page_one.handle_hint": "Tämä on se, mitä voisit ehdottaa ystäviäsi etsimään.", "onboarding.page_one.welcome": "Tervetuloa Mastodoniin!", "onboarding.page_six.admin": "Instanssisi ylläpitäjä on {admin}.", "onboarding.page_six.almost_done": "Melkein valmista...", "onboarding.page_six.appetoot": "Bon Appetööt!", "onboarding.page_six.apps_available": "{apps} on saatavilla iOS:lle, Androidille ja muille alustoille.", - "onboarding.page_six.github": "Mastodon is free open-source software. You can report bugs, request features, or contribute to the code on {github}.", + "onboarding.page_six.github": "Mastodon on ilmainen, vapaan lähdekoodin ohjelma. Voit raportoida bugeja, pyytää ominaisuuksia tai osallistua kehittämiseen GitHub-palvelussa: {github}.", "onboarding.page_six.guidelines": "yhteisön säännöt", "onboarding.page_six.read_guidelines": "Ole hyvä ja lue {domain}:n {guidelines}!", "onboarding.page_six.various_app": "mobiilisovellukset", @@ -254,12 +254,12 @@ "status.sensitive_warning": "Arkaluontoista sisältöä", "status.share": "Jaa", "status.show_less": "Näytä vähemmän", - "status.show_less_all": "Show less for all", + "status.show_less_all": "Näytä vähemmän kaikista", "status.show_more": "Näytä lisää", - "status.show_more_all": "Show more for all", + "status.show_more_all": "Näytä enemmän kaikista", "status.unmute_conversation": "Poista mykistys keskustelulta", "status.unpin": "Irrota profiilista", - "tabs_bar.federated_timeline": "Federated", + "tabs_bar.federated_timeline": "Yleinen", "tabs_bar.home": "Koti", "tabs_bar.local_timeline": "Paikallinen", "tabs_bar.notifications": "Ilmoitukset", diff --git a/app/javascript/mastodon/locales/sk.json b/app/javascript/mastodon/locales/sk.json index 2cb249d..683f2aa 100644 --- a/app/javascript/mastodon/locales/sk.json +++ b/app/javascript/mastodon/locales/sk.json @@ -18,7 +18,7 @@ "account.muted": "Utíšený/á", "account.posts": "Hlášky", "account.posts_with_replies": "Príspevky s odpoveďami", - "account.report": "Nahlásiť @{name}", + "account.report": "Nahlás @{name}", "account.requested": "Čaká na schválenie. Kliknite pre zrušenie žiadosti", "account.share": "Zdieľať @{name} profil", "account.show_reblogs": "Zobraziť povýšenia od @{name}", @@ -35,13 +35,13 @@ "bundle_modal_error.close": "Zatvoriť", "bundle_modal_error.message": "Nastala chyba pri načítaní tohto komponentu.", "bundle_modal_error.retry": "Skúsiť znova", - "column.blocks": "Blokovaní používatelia", + "column.blocks": "Blokovaní užívatelia", "column.community": "Lokálna časová os", "column.favourites": "Obľúbené", "column.follow_requests": "Žiadosti o sledovaní", "column.home": "Domov", "column.lists": "Zoznamy", - "column.mutes": "Ignorovaní používatelia", + "column.mutes": "Ignorovaní užívatelia", "column.notifications": "Notifikácie", "column.pins": "Pripnuté toots", "column.public": "Federovaná časová os", @@ -50,20 +50,20 @@ "column_header.moveLeft_settings": "Presunúť stĺpec doľava", "column_header.moveRight_settings": "Presunúť stĺpec doprava", "column_header.pin": "Pripnúť", - "column_header.show_settings": "Ukázať nastavenia", + "column_header.show_settings": "Ukáž nastavenia", "column_header.unpin": "Odopnúť", "column_subheading.navigation": "Navigácia", "column_subheading.settings": "Nastavenia", - "compose_form.hashtag_warning": "Tento toot nebude zobrazený pod žiadným haštagom lebo nieje listovaný. Iba verejné toots môžu byť nájdené podľa haštagu.", + "compose_form.hashtag_warning": "Tento toot nebude zobrazený pod žiadným haštagom lebo nieje listovaný. Iba verejné tooty môžu byť nájdené podľa haštagu.", "compose_form.lock_disclaimer": "Váš účet nie je zamknutý. Ktokoľvek ťa môže nasledovať a vidieť tvoje správy pre sledujúcich.", "compose_form.lock_disclaimer.lock": "zamknutý", "compose_form.placeholder": "Na čo myslíš?", "compose_form.publish": "Toot", "compose_form.publish_loud": "{publish}!", - "compose_form.sensitive.marked": "Media is marked as sensitive", - "compose_form.sensitive.unmarked": "Media is not marked as sensitive", - "compose_form.spoiler.marked": "Text is hidden behind warning", - "compose_form.spoiler.unmarked": "Text is not hidden", + "compose_form.sensitive.marked": "Médiálny obsah je označený ako chúlostivý", + "compose_form.sensitive.unmarked": "Médiálny obsah nieje označený ako chúlostivý", + "compose_form.spoiler.marked": "Text je ukrytý za varovaním", + "compose_form.spoiler.unmarked": "Text nieje ukrytý", "compose_form.spoiler_placeholder": "Sem napíšte vaše varovanie", "confirmation_modal.cancel": "Zrušiť", "confirmations.block.confirm": "Blokovať", @@ -101,14 +101,14 @@ "empty_column.list": "Tento zoznam je ešte prázdny. Keď ale členovia tohoto zoznamu napíšu nové správy, tak tie sa objavia priamo tu.", "empty_column.notifications": "Nemáte ešte žiadne notifikácie. Napíšte niekomu, následujte niekoho a komunikujte s ostatnými aby diskusia mohla začať.", "empty_column.public": "Ešte tu nič nie je. Napíšte niečo verejne alebo začnite sledovať používateľov z iných Mastodon serverov aby tu niečo pribudlo", - "follow_request.authorize": "Povoliť prístup", - "follow_request.reject": "Odmietnúť", + "follow_request.authorize": "Povoľ prístup", + "follow_request.reject": "Odmietni", "getting_started.appsshort": "Aplikácie", - "getting_started.faq": "FAQ", - "getting_started.heading": "Začíname", + "getting_started.faq": "Časté otázky", + "getting_started.heading": "Začni tu", "getting_started.open_source_notice": "Mastodon má otvorený kód. Nahlásiť chyby, alebo prispievať vlastným kódom môžete na GitHube v {github}.", "getting_started.userguide": "Používateľská príručka", - "home.column_settings.advanced": "Rozšírené", + "home.column_settings.advanced": "Pokročilé", "home.column_settings.basic": "Základné", "home.column_settings.filter_regex": "Filtrovať použitím regulárnych výrazov", "home.column_settings.show_reblogs": "Zobraziť povýšené", @@ -147,7 +147,7 @@ "missing_indicator.label": "Nenájdené", "missing_indicator.sublabel": "Tento zdroj sa nepodarilo nájsť", "mute_modal.hide_notifications": "Skryť notifikácie od tohoto užívateľa?", - "navigation_bar.blocks": "Blokovaní používatelia", + "navigation_bar.blocks": "Blokovaní užívatelia", "navigation_bar.community_timeline": "Lokálna časová os", "navigation_bar.edit_profile": "Upraviť profil", "navigation_bar.favourites": "Obľúbené", @@ -156,9 +156,9 @@ "navigation_bar.keyboard_shortcuts": "Klávesové skratky", "navigation_bar.lists": "Zoznamy", "navigation_bar.logout": "Odhlásiť", - "navigation_bar.mutes": "Ignorovaní používatelia", + "navigation_bar.mutes": "Ignorovaní užívatelia", "navigation_bar.pins": "Pripnuté toots", - "navigation_bar.preferences": "Možnosti", + "navigation_bar.preferences": "Voľby", "navigation_bar.public_timeline": "Federovaná časová os", "notification.favourite": "{name} sa páči tvoj status", "notification.follow": "{name} ťa začal/a následovať", @@ -254,9 +254,9 @@ "status.sensitive_warning": "Chúlostivý obsah", "status.share": "Zdieľať", "status.show_less": "Zobraz menej", - "status.show_less_all": "Show less for all", + "status.show_less_all": "Všetkým ukáž menej", "status.show_more": "Zobraz viac", - "status.show_more_all": "Show more for all", + "status.show_more_all": "Všetkým ukáž viac", "status.unmute_conversation": "Prestať ignorovať konverzáciu", "status.unpin": "Odopnúť z profilu", "tabs_bar.federated_timeline": "Federovaná", diff --git a/app/javascript/mastodon/locales/sv.json b/app/javascript/mastodon/locales/sv.json index 3451212..4fa1291 100644 --- a/app/javascript/mastodon/locales/sv.json +++ b/app/javascript/mastodon/locales/sv.json @@ -60,10 +60,10 @@ "compose_form.placeholder": "Vad funderar du på?", "compose_form.publish": "Toot", "compose_form.publish_loud": "{publish}!", - "compose_form.sensitive.marked": "Media is marked as sensitive", - "compose_form.sensitive.unmarked": "Media is not marked as sensitive", - "compose_form.spoiler.marked": "Text is hidden behind warning", - "compose_form.spoiler.unmarked": "Text is not hidden", + "compose_form.sensitive.marked": "Media har markerats som känsligt", + "compose_form.sensitive.unmarked": "Media har inte markerats som känsligt", + "compose_form.spoiler.marked": "Texten har dolts bakom en varning", + "compose_form.spoiler.unmarked": "Texten är inte dold", "compose_form.spoiler_placeholder": "Skriv din varning här", "confirmation_modal.cancel": "Ångra", "confirmations.block.confirm": "Blockera", @@ -254,9 +254,9 @@ "status.sensitive_warning": "Känsligt innehåll", "status.share": "Dela", "status.show_less": "Visa mindre", - "status.show_less_all": "Show less for all", + "status.show_less_all": "Visa mindre för alla", "status.show_more": "Visa mer", - "status.show_more_all": "Show more for all", + "status.show_more_all": "Visa mer för alla", "status.unmute_conversation": "Öppna konversation", "status.unpin": "Ångra fäst i profil", "tabs_bar.federated_timeline": "Förenad", diff --git a/config/locales/ar.yml b/config/locales/ar.yml index ad674a3..e6447ca 100644 --- a/config/locales/ar.yml +++ b/config/locales/ar.yml @@ -513,6 +513,8 @@ ar: over_character_limit: تم تجاوز حد الـ %{max} حرف المسموح بها pin_errors: ownership: لا يمكن تدبيس تبويق نشره شخص آخر + private: لا يمكن تثبيت تبويق لم يُنشر للعامة + reblog: لا يمكن تثبيت ترقية show_more: أظهر المزيد title: '%{name} : "%{quote}"' visibilities: @@ -524,6 +526,7 @@ ar: unlisted_long: يُمكن لأيٍ كان رُؤيتَه و لكن لن يُعرَض على الخيوط العامة stream_entries: click_to_show: إضغط للعرض + pinned: تبويق مثبّت reblogged: رقى sensitive_content: محتوى حساس terms: diff --git a/config/locales/es.yml b/config/locales/es.yml index d5ba177..671f17d 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -275,6 +275,7 @@ es: username: Nombre de usuario hero: desc_html: Mostrado en la página principal. Recomendable al menos 600x100px. Por defecto se establece a la miniatura de la instancia + title: Imagen de portada peers_api_enabled: desc_html: Nombres de dominio que esta instancia ha encontrado en el fediverso title: Publicar lista de instancias descubiertas diff --git a/config/locales/simple_form.fi.yml b/config/locales/simple_form.fi.yml index a5cded1..34605c4 100644 --- a/config/locales/simple_form.fi.yml +++ b/config/locales/simple_form.fi.yml @@ -4,13 +4,19 @@ fi: hints: defaults: avatar: PNG, GIF tai JPG. Korkeintaan 2MB. Skaalataan kokoon 400x400px - digest: Lähetetään vain pitkän poissaolon jälkeen, ja vain jos olet vastaanottanut yksityisviestejä poissaolosi aikana. + digest: Lähetetään vain pitkän poissaolon jälkeen, ja vain jos olet vastaanottanut yksityisviestejä poissaolosi aikana display_name: Korkeintaan 30 merkkiä header: PNG, GIF tai JPG. Korkeintaan 2MB. Skaalataan kokoon 700x335px - locked: Vaatii sinun manuaalisesti hyväksymään seuraajat, ja asettaa julkaisujen yksityisyyden vain seuraajille + locked: Vaatii sinua manuaalisesti hyväksymään seuraajat note: Korkeintaan 160 merkkiä + setting_noindex: Vaikuttaa julkiseen profiiliisi ja statuspäivityksiisi + setting_theme: Vaikuttaa siihen, miltä Mastodon näyttää kun olet kirjautuneena milllä tahansa laitteella. imports: - data: CSV tiedosto tuotu toiselta Mastodon palvelimelta + data: CSV tiedosto, joka on tuotu toiselta Mastodon-palvelimelta + sessions: + otp: Syötä kaksivaiheisen tunnistuksen koodi puhelimestasi tai käytä yhtä palautuskoodeistasi. + user: + filtered_languages: Valitut kielet suodatetaan julkisilta aikajanoilta labels: defaults: avatar: Profiilikuva @@ -18,22 +24,37 @@ fi: confirm_password: Varmista salasana current_password: Nykyinen salasana data: Data - display_name: Näykyvä nimi + display_name: Nimimerkki email: Sähköpostiosoite - header: Otsake + expires_in: Vanhentuu + filtered_languages: Suodatetut kielet + header: Otsakekuva locale: Kieli locked: Tee tilistä yksityinen max_uses: Max käyttökerrat new_password: Uusi salasana - note: Bio + note: Kuvaus otp_attempt: Kaksivaiheinen koodi password: Salasana + setting_auto_play_gif: Animoitujen GIFfien automaattitoisto + setting_boost_modal: Näytä vahvistusikkuna ennen boostausta setting_default_privacy: Julkaisun yksityisyys + setting_default_sensitive: Merkitse media aina arkaluontoiseksi + setting_delete_modal: Näytä vahvistusikkuna ennen töötin poistamista + setting_display_sensitive_media: Näytä aina arkaluontoiseksi merkitty media + setting_noindex: Jättäydy pois hakukoneindeksoinnista + setting_reduce_motion: Vähennä liikettä animaatioissa + setting_system_font_ui: Käytä käyttöjärjestelmän oletusfonttia + setting_theme: Sivuston teema + setting_unfollow_modal: Näytä vahvistusikkuna ennen seuraamisen lopettamista + severity: Vakavuusaste type: Tuontityyppi username: Käyttäjänimi + username_or_email: Käyttäjänimi tai sähköposti interactions: - must_be_follower: Estä ilmoitukset käyttäjiltä jotka eivät seuraa sinua - must_be_following: Estä ilmoitukset käyttäjiltä joita et seuraa + must_be_follower: Estä ilmoitukset käyttäjiltä, jotka eivät seuraa sinua + must_be_following: Estä ilmoitukset käyttäjiltä, joita et seuraa + must_be_following_dm: Estä suorat viestit ihmisiltä, joita et seuraa notification_emails: digest: Lähetä koosteviestejä sähköpostilla favourite: Lähetä sähköposti, kun joku tykkää statuksestasi @@ -44,5 +65,5 @@ fi: 'no': Ei required: mark: "*" - text: vaaditaan + text: pakollinen tieto 'yes': Kyllä diff --git a/config/locales/sk.yml b/config/locales/sk.yml index dd81201..e391974 100644 --- a/config/locales/sk.yml +++ b/config/locales/sk.yml @@ -593,7 +593,7 @@ sk: title: Sezóna settings: authorized_apps: Autorizované aplikácie - back: Naspäť na stránku + back: Späť do Mastodonu delete: Zmazanie účtu development: Vývoj edit_profile: Upraviť profil @@ -630,8 +630,15 @@ sk: title: Podmienky užívania, a pravidlá o súkromí pre %{instance} two_factor_authentication: enable: Povoliť + enabled: Dvoj-faktorové overovanie je povolené + enabled_success: Dvoj-faktorové overovanie bolo úspešne povolené generate_recovery_codes: Vygeneruj zálohové kódy + lost_recovery_codes: Zálohové kódy ti umožnia dostať sa k svojmu účtu ak stratíš telefón. Pokiaľ si stratila svoje zálohové kódy, môžeš si ich tu znovu vygenerovať. Tvoje staré zálohové kódy budú zneplatnené. + manual_instructions: 'Pokiaľ nemôžeš oskenovať daný QR kód, a potrebuješ ho zadať ručne, tu je tajomstvo v textovom formáte:' + recovery_codes: Zálohuj kódy pre obnovu + recovery_codes_regenerated: Zálohové kódy boli úspešne zvova vygenerované setup: Nastavenie + wrong_code: Zadaný kód bol neplatný. Je serverový čas a čas na zariadení správny? user_mailer: backup_ready: explanation: Vyžiadal/a si si úplnú zálohu tvojho Mastodon účtu. Táto záloha je teraz pripravená na stiahnutie! @@ -639,12 +646,17 @@ sk: title: Odber archívu welcome: edit_profile_action: Nastav profil + edit_profile_step: Profil si môžeš prispôsobiť nahratím portrétu a hlavičky, môžeš upraviť svoje meno a viac. Pokiaľ chceš preverovať nových následovateľov predtým než ťa budú môcť sledovať, môžeš uzamknúť svoj účet. explanation: Tu nájdeš nejaké tipy do začiatku final_action: Začni prispievať final_step: 'Začnite písať! Aj bez následovníkov budú vaše verejné správy videné ostatnými, napríklad na lokálnej osi a pod haštagmi. Môžete sa ostatným predstaviť pod haštagom #introductions.' full_handle: Adresa tvojho profilu v celom formáte + full_handle_hint: Toto je čo musíš dať vedieť svojím priateľom aby ti mohli posielať správy, alebo ťa následovať z inej instancie. review_preferences_action: Zmeniť nastavenia subject: Vitaj na Mastodone + tip_bridge_html: Ak prichádzaš z Twitteru, môžeš svojích priateľov nájsť na Mastodone pomocou tzv. mostíkovej aplikácie. Ale tá funguje iba ak ju aj oni niekedy použili! + tip_federated_timeline: Federovaná os zobrazuje sieť Mastodonu až po jej hranice. Ale zahŕňa iba ľúdí ktorých ostatní okolo teba sledujú, takže predsa nieje úplne celistvá. + tip_following: Správcu servera následuješ automaticky. Môžeš ale nájsť mnoho iných zaujímavých ľudí ak prezrieš tak lokálnu, ako aj globálne federovanú os. tip_local_timeline: Lokálna os je celkový pohľad na aktivitu užívateľov %{instance}. Toto sú tvoji najbližší susedia! tip_mobile_webapp: Pokiaľ ti prehliadač ponúkne možnosť pridať Mastodon na tvoju obrazovku, môžeš potom dostávať notifikácie skoro ako z natívnej aplikácie! tips: Tipy From 357f9298bdb595f67b1f89292be9fa4268e1bffd Mon Sep 17 00:00:00 2001 From: ThibG Date: Mon, 19 Mar 2018 20:07:47 +0100 Subject: [PATCH 004/381] Fix e-mail changed notification (fixes #6778) (#6835) In Devise::Mailer#email_changed, the new email might be in the email attr. See: https://github.com/plataformatec/devise/blob/master/app/views/devise/mailer/email_changed.html.erb --- app/views/user_mailer/email_changed.html.haml | 2 +- app/views/user_mailer/email_changed.text.erb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/user_mailer/email_changed.html.haml b/app/views/user_mailer/email_changed.html.haml index 7e82f23..0802aaf 100644 --- a/app/views/user_mailer/email_changed.html.haml +++ b/app/views/user_mailer/email_changed.html.haml @@ -38,7 +38,7 @@ %table.input{ align: 'center', cellspacing: 0, cellpadding: 0 } %tbody %tr - %td= @resource.unconfirmed_email + %td= @resource.try(:unconfirmed_email) ? @resource.unconfirmed_email : @resource.email %table.email-table{ cellspacing: 0, cellpadding: 0 } %tbody diff --git a/app/views/user_mailer/email_changed.text.erb b/app/views/user_mailer/email_changed.text.erb index 2b58415..345b16a 100644 --- a/app/views/user_mailer/email_changed.text.erb +++ b/app/views/user_mailer/email_changed.text.erb @@ -4,6 +4,6 @@ <%= t 'devise.mailer.email_changed.explanation' %> -<%= @resource.unconfirmed_email %> +<%= @resource.try(:unconfirmed_email) ? @resource.unconfirmed_email : @resource.email %> <%= t 'devise.mailer.email_changed.extra' %> From 0306e3e9bebe0b0d9ad7e5fe328dd3677717b7e5 Mon Sep 17 00:00:00 2001 From: Alexander Date: Mon, 19 Mar 2018 20:08:56 +0100 Subject: [PATCH 005/381] bugfixes and gem update (#6831) * update to new version of devise_pam_authenticatable2 * fix behaviour if suffix is nil, fix environment loading, fix user email creation * code cleanup/fix linter warning --- Gemfile | 4 +++- Gemfile.lock | 4 ++-- app/models/user.rb | 32 +++++++++++++++++--------------- config/application.rb | 2 ++ 4 files changed, 24 insertions(+), 18 deletions(-) diff --git a/Gemfile b/Gemfile index 3fce2dd..fe5bf57 100644 --- a/Gemfile +++ b/Gemfile @@ -32,7 +32,9 @@ gem 'cld3', '~> 3.2.0' gem 'devise', '~> 4.4' gem 'devise-two-factor', '~> 3.0' -gem 'devise_pam_authenticatable2', '~> 8.0', install_if: -> { ENV['PAM_ENABLED'] == 'true' } +group :pam_authentication, optional: true do + gem 'devise_pam_authenticatable2', '~> 9.0' +end gem 'net-ldap', '~> 0.10' gem 'omniauth-cas', '~> 1.1' gem 'omniauth-saml', '~> 1.10' diff --git a/Gemfile.lock b/Gemfile.lock index 0640b14..ca6365c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -141,7 +141,7 @@ GEM devise (~> 4.0) railties (< 5.2) rotp (~> 2.0) - devise_pam_authenticatable2 (8.0.1) + devise_pam_authenticatable2 (9.0.0) devise (>= 4.0.0) rpam2 (~> 3.0) diff-lcs (1.3) @@ -631,7 +631,7 @@ DEPENDENCIES climate_control (~> 0.2) devise (~> 4.4) devise-two-factor (~> 3.0) - devise_pam_authenticatable2 (~> 8.0) + devise_pam_authenticatable2 (~> 9.0) doorkeeper (~> 4.2) dotenv-rails (~> 2.2) fabrication (~> 2.18) diff --git a/app/models/user.rb b/app/models/user.rb index b716c13..2d5f145 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -52,6 +52,8 @@ class User < ApplicationRecord devise :registerable, :recoverable, :rememberable, :trackable, :validatable, :confirmable + devise :pam_authenticatable if ENV['PAM_ENABLED'] == 'true' + devise :omniauthable belongs_to :account, inverse_of: :user @@ -96,7 +98,7 @@ class User < ApplicationRecord def pam_conflict? return false unless Devise.pam_authentication - encrypted_password.present? && is_pam_account? + encrypted_password.present? && pam_managed_user? end def pam_get_name @@ -267,22 +269,22 @@ class User < ApplicationRecord end def self.pam_get_user(attributes = {}) - if attributes[:email] - resource = - if Devise.check_at_sign && !attributes[:email].index('@') - joins(:account).find_by(accounts: { username: attributes[:email] }) - else - find_by(email: attributes[:email]) - end - - if resource.blank? - resource = new(email: attributes[:email]) - if Devise.check_at_sign && !resource[:email].index('@') - resource[:email] = "#{attributes[:email]}@#{resource.find_pam_suffix}" - end + return nil unless attributes[:email] + resource = + if Devise.check_at_sign && !attributes[:email].index('@') + joins(:account).find_by(accounts: { username: attributes[:email] }) + else + find_by(email: attributes[:email]) + end + + if resource.blank? + resource = new(email: attributes[:email]) + if Devise.check_at_sign && !resource[:email].index('@') + resource[:email] = Rpam2.getenv(resource.find_pam_service, attributes[:email], attributes[:password], 'email', false) + resource[:email] = "#{attributes[:email]}@#{resource.find_pam_suffix}" unless resource[:email] end - resource end + resource end def self.ldap_get_user(attributes = {}) diff --git a/config/application.rb b/config/application.rb index 097cbf5..326a0ec 100644 --- a/config/application.rb +++ b/config/application.rb @@ -16,6 +16,8 @@ require_relative '../lib/devise/ldap_authenticatable' Dotenv::Railtie.load +Bundler.require(:pam_authentication) if ENV['PAM_ENABLED'] == 'true' + require_relative '../lib/mastodon/redis_config' module Mastodon From 33ee347c995515946e0a045394fe693875ee1cdf Mon Sep 17 00:00:00 2001 From: Alexander Date: Mon, 19 Mar 2018 20:09:26 +0100 Subject: [PATCH 006/381] rename pam email environment variable to something more understandable and default to LOCAL_DOMAIN (better fallback) (#6833) --- .env.production.sample | 4 ++-- config/initializers/devise.rb | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.env.production.sample b/.env.production.sample index 579ad66..1e5ed9f 100644 --- a/.env.production.sample +++ b/.env.production.sample @@ -155,8 +155,8 @@ STREAMING_CLUSTER_NUM=1 # The pam environment variable "email" is provided by: # https://github.com/devkral/pam_email_extractor # PAM_ENABLED=true -# Fallback Suffix for email address generation (nil by default) -# PAM_DEFAULT_SUFFIX=pam +# Fallback email domain for email address generation (LOCAL_DOMAIN by default) +# PAM_EMAIL_DOMAIN=example.com # Name of the pam service (pam "auth" section is evaluated) # PAM_DEFAULT_SERVICE=rpam # Name of the pam service used for checking if an user can register (pam "account" section is evaluated) (nil (disabled) by default) diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb index df45dcd..97757d0 100644 --- a/config/initializers/devise.rb +++ b/config/initializers/devise.rb @@ -342,7 +342,7 @@ Devise.setup do |config| config.usernamefield = nil config.emailfield = 'email' config.check_at_sign = true - config.pam_default_suffix = ENV.fetch('PAM_DEFAULT_SUFFIX') { nil } + config.pam_default_suffix = ENV.fetch('PAM_EMAIL_DOMAIN') { ENV['LOCAL_DOMAIN'] } config.pam_default_service = ENV.fetch('PAM_DEFAULT_SERVICE') { 'rpam' } config.pam_controlled_service = ENV.fetch('PAM_CONTROLLED_SERVICE') { nil } end From 6b76a6212d8aa596379115b248ec145905946c42 Mon Sep 17 00:00:00 2001 From: ThibG Date: Mon, 19 Mar 2018 20:12:20 +0100 Subject: [PATCH 007/381] Display content warning in mail notification emails (#6832) --- app/views/notification_mailer/_status.html.haml | 5 +++++ app/views/notification_mailer/_status.text.erb | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/app/views/notification_mailer/_status.html.haml b/app/views/notification_mailer/_status.html.haml index f82ada1..57b5688 100644 --- a/app/views/notification_mailer/_status.html.haml +++ b/app/views/notification_mailer/_status.html.haml @@ -24,6 +24,11 @@ %bdi= display_name(status.account) = "@#{status.account.acct}" + - if status.spoiler_text? + %div{ dir: rtl_status?(status) ? 'rtl' : 'ltr' } + %p + = Formatter.instance.format_spoiler(status) + %div{ dir: rtl_status?(status) ? 'rtl' : 'ltr' } = Formatter.instance.format(status) diff --git a/app/views/notification_mailer/_status.text.erb b/app/views/notification_mailer/_status.text.erb index 85a0136..8999a1f 100644 --- a/app/views/notification_mailer/_status.text.erb +++ b/app/views/notification_mailer/_status.text.erb @@ -1,3 +1,8 @@ +<% if status.spoiler_text? %> +<%= raw status.spoiler_text %> +---- + +<% end %> <%= raw Formatter.instance.plaintext(status) %> <%= raw t('application_mailer.view')%> <%= web_url("statuses/#{status.id}") %> From ff6b8a6443c2c97d185927053bdc8816e0e03434 Mon Sep 17 00:00:00 2001 From: ThibG Date: Mon, 19 Mar 2018 20:19:35 +0100 Subject: [PATCH 008/381] Serialize mentions in the order they are added (#6836) Up until now, the order seemed to be in the *opposite* order, which caused the WebUI to populate mentions in reversed order when replying to toots local to one's instance. --- app/lib/ostatus/atom_serializer.rb | 2 +- app/serializers/activitypub/note_serializer.rb | 2 +- app/serializers/rest/status_serializer.rb | 6 +++++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/app/lib/ostatus/atom_serializer.rb b/app/lib/ostatus/atom_serializer.rb index 656e458..46d0a8b 100644 --- a/app/lib/ostatus/atom_serializer.rb +++ b/app/lib/ostatus/atom_serializer.rb @@ -351,7 +351,7 @@ class OStatus::AtomSerializer append_element(entry, 'summary', status.spoiler_text, 'xml:lang': status.language) if status.spoiler_text? append_element(entry, 'content', Formatter.instance.format(status).to_str, type: 'html', 'xml:lang': status.language) - status.mentions.each do |mentioned| + status.mentions.order(:id).each do |mentioned| append_element(entry, 'link', nil, rel: :mentioned, 'ostatus:object-type': OStatus::TagManager::TYPES[:person], href: OStatus::TagManager.instance.uri_for(mentioned.account)) end diff --git a/app/serializers/activitypub/note_serializer.rb b/app/serializers/activitypub/note_serializer.rb index d0e6290..abaf290 100644 --- a/app/serializers/activitypub/note_serializer.rb +++ b/app/serializers/activitypub/note_serializer.rb @@ -57,7 +57,7 @@ class ActivityPub::NoteSerializer < ActiveModel::Serializer end def virtual_tags - object.mentions + object.tags + object.emojis + object.mentions.order(:id) + object.tags + object.emojis end def atom_uri diff --git a/app/serializers/rest/status_serializer.rb b/app/serializers/rest/status_serializer.rb index e6270f9..67da92c 100644 --- a/app/serializers/rest/status_serializer.rb +++ b/app/serializers/rest/status_serializer.rb @@ -15,7 +15,7 @@ class REST::StatusSerializer < ActiveModel::Serializer belongs_to :account, serializer: REST::AccountSerializer has_many :media_attachments, serializer: REST::MediaAttachmentSerializer - has_many :mentions + has_many :ordered_mentions, key: :mentions has_many :tags has_many :emojis, serializer: REST::CustomEmojiSerializer @@ -86,6 +86,10 @@ class REST::StatusSerializer < ActiveModel::Serializer %w(public unlisted).include?(object.visibility) end + def ordered_mentions + object.mentions.order(:id) + end + class ApplicationSerializer < ActiveModel::Serializer attributes :name, :website end From 36b57037961383466b7f5c20b39ee68cd9f202a0 Mon Sep 17 00:00:00 2001 From: Rey Tucker Date: Tue, 20 Mar 2018 04:06:08 -0400 Subject: [PATCH 009/381] request: in the event of failure, try other IPs (#6761) (#6813) * request: in the event of failure, try other IPs (#6761) In the case where a name has multiple A/AAAA records, we should try subsequent records instead of immediately failing when we have a failure on the first IP address. This significantly improves delivery success when there are network connectivity problems affecting only IPv4 or IPv6. * fix method call style * request_spec: adjust test case to use Addrinfo * request: Request/open: move private addr check to within begin/rescue * request_spec: add case to test failover, fix exception check * Double Addrinfo.foreach so that it correctly yields instances --- app/lib/request.rb | 13 ++++++++++--- spec/lib/request_spec.rb | 11 ++++++++++- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/app/lib/request.rb b/app/lib/request.rb index 5776b3d..298fb95 100644 --- a/app/lib/request.rb +++ b/app/lib/request.rb @@ -94,9 +94,16 @@ class Request class Socket < TCPSocket class << self def open(host, *args) - address = IPSocket.getaddress(host) - raise Mastodon::HostValidationError if PrivateAddressCheck.private_address? IPAddr.new(address) - super address, *args + outer_e = nil + Addrinfo.foreach(host, nil, nil, :SOCK_STREAM) do |address| + begin + raise Mastodon::HostValidationError if PrivateAddressCheck.private_address? IPAddr.new(address.ip_address) + return super address.ip_address, *args + rescue => e + outer_e = e + end + end + raise outer_e if outer_e end alias new open diff --git a/spec/lib/request_spec.rb b/spec/lib/request_spec.rb index dc7daa5..5da357c 100644 --- a/spec/lib/request_spec.rb +++ b/spec/lib/request_spec.rb @@ -48,6 +48,13 @@ describe Request do expect(a_request(:get, 'http://example.com')).to have_been_made.once end + it 'executes a HTTP request when the first address is private' do + allow(Addrinfo).to receive(:foreach).with('example.com', nil, nil, :SOCK_STREAM) + .and_yield(Addrinfo.new(["AF_INET", 0, "example.com", "0.0.0.0"], :PF_INET, :SOCK_STREAM)) + .and_yield(Addrinfo.new(["AF_INET6", 0, "example.com", "2001:4860:4860::8844"], :PF_INET6, :SOCK_STREAM)) + expect(a_request(:get, 'http://example.com')).to have_been_made.once + end + it 'sets headers' do expect(a_request(:get, 'http://example.com').with(headers: subject.headers)).to have_been_made end @@ -61,7 +68,9 @@ describe Request do end it 'raises Mastodon::ValidationError' do - allow(IPSocket).to receive(:getaddress).with('example.com').and_return('0.0.0.0') + allow(Addrinfo).to receive(:foreach).with('example.com', nil, nil, :SOCK_STREAM) + .and_yield(Addrinfo.new(["AF_INET", 0, "example.com", "0.0.0.0"], :PF_INET, :SOCK_STREAM)) + .and_yield(Addrinfo.new(["AF_INET6", 0, "example.com", "2001:db8::face"], :PF_INET6, :SOCK_STREAM)) expect{ subject.perform }.to raise_error Mastodon::ValidationError end end From a5c6c748e096f61d00bbd778a263e22117e1ae9f Mon Sep 17 00:00:00 2001 From: ThibG Date: Tue, 20 Mar 2018 12:40:12 +0100 Subject: [PATCH 010/381] Cancel outdated pending compose suggestions (#6838) --- app/javascript/mastodon/actions/compose.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/app/javascript/mastodon/actions/compose.js b/app/javascript/mastodon/actions/compose.js index 130b4af..1371f22 100644 --- a/app/javascript/mastodon/actions/compose.js +++ b/app/javascript/mastodon/actions/compose.js @@ -1,4 +1,5 @@ import api from '../api'; +import { CancelToken } from 'axios'; import { throttle } from 'lodash'; import { search as emojiSearch } from '../features/emoji/emoji_mart_search_light'; import { tagHistory } from '../settings'; @@ -11,6 +12,8 @@ import { refreshPublicTimeline, } from './timelines'; +let cancelFetchComposeSuggestionsAccounts; + export const COMPOSE_CHANGE = 'COMPOSE_CHANGE'; export const COMPOSE_SUBMIT_REQUEST = 'COMPOSE_SUBMIT_REQUEST'; export const COMPOSE_SUBMIT_SUCCESS = 'COMPOSE_SUBMIT_SUCCESS'; @@ -257,13 +260,22 @@ export function undoUploadCompose(media_id) { }; export function clearComposeSuggestions() { + if (cancelFetchComposeSuggestionsAccounts) { + cancelFetchComposeSuggestionsAccounts(); + } return { type: COMPOSE_SUGGESTIONS_CLEAR, }; }; const fetchComposeSuggestionsAccounts = throttle((dispatch, getState, token) => { + if (cancelFetchComposeSuggestionsAccounts) { + cancelFetchComposeSuggestionsAccounts(); + } api(getState).get('/api/v1/accounts/search', { + cancelToken: new CancelToken(cancel => { + cancelFetchComposeSuggestionsAccounts = cancel; + }), params: { q: token.slice(1), resolve: false, From 9381a7d9d55ea734d6c498a82d17d73fd02fbe87 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Tue, 20 Mar 2018 14:57:46 +0100 Subject: [PATCH 011/381] Use username/domain to match existing accounts in ActivityPub (#6842) See also: #6837, #6667 --- app/services/activitypub/process_account_service.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/services/activitypub/process_account_service.rb b/app/services/activitypub/process_account_service.rb index 68e9db7..7d8dc13 100644 --- a/app/services/activitypub/process_account_service.rb +++ b/app/services/activitypub/process_account_service.rb @@ -16,7 +16,7 @@ class ActivityPub::ProcessAccountService < BaseService RedisLock.acquire(lock_options) do |lock| if lock.acquired? - @account = Account.find_by(uri: @uri) + @account = Account.find_remote(@username, @domain) @old_public_key = @account&.public_key @old_protocol = @account&.protocol From 61dcb686a8f0a3272e2948c9a072aa58593a7409 Mon Sep 17 00:00:00 2001 From: Akihiko Odaki Date: Wed, 21 Mar 2018 00:36:20 +0900 Subject: [PATCH 012/381] Fix i18n fallback configuration conflicts with environment configurations (#6843) --- config/application.rb | 4 +--- config/environments/production.rb | 4 ++-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/config/application.rb b/config/application.rb index 326a0ec..385bd47 100644 --- a/config/application.rb +++ b/config/application.rb @@ -76,9 +76,7 @@ module Mastodon ] config.i18n.default_locale = ENV['DEFAULT_LOCALE']&.to_sym - if config.i18n.available_locales.include?(config.i18n.default_locale) - config.i18n.fallbacks = [:en] - else + unless config.i18n.available_locales.include?(config.i18n.default_locale) config.i18n.default_locale = :en end diff --git a/config/environments/production.rb b/config/environments/production.rb index 3136a40..f372cd3 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -55,8 +55,8 @@ Rails.application.configure do # config.action_mailer.raise_delivery_errors = false # Enable locale fallbacks for I18n (makes lookups for any locale fall back to - # the I18n.default_locale when a translation cannot be found). - config.i18n.fallbacks = true + # English when a translation cannot be found). + config.i18n.fallbacks = [:en] # Send deprecation notices to registered listeners. config.active_support.deprecation = :notify From ac49c7932d848fbb946c37a69f42b7dbc774c56c Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Tue, 20 Mar 2018 19:41:51 +0100 Subject: [PATCH 013/381] Add LDAP_TLS_NO_VERIFY option, don't require LDAP_ENABLED outside .env (#6845) Fix #6816, fix #6790 --- config/initializers/devise.rb | 3 ++ lib/devise/ldap_authenticatable.rb | 76 ++++++++++++++++++++------------------ 2 files changed, 43 insertions(+), 36 deletions(-) diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb index 97757d0..e0d263f 100644 --- a/config/initializers/devise.rb +++ b/config/initializers/devise.rb @@ -55,6 +55,8 @@ module Devise @@ldap_bind_dn = nil mattr_accessor :ldap_password @@ldap_password = nil + mattr_accessor :ldap_tls_no_verify + @@ldap_tls_no_verify = false class Strategies::PamAuthenticatable def valid? @@ -357,5 +359,6 @@ Devise.setup do |config| config.ldap_bind_dn = ENV.fetch('LDAP_BIND_DN') config.ldap_password = ENV.fetch('LDAP_PASSWORD') config.ldap_uid = ENV.fetch('LDAP_UID', 'cn') + config.ldap_tls_no_verify = ENV['LDAP_TLS_NO_VERIFY'] == 'true' end end diff --git a/lib/devise/ldap_authenticatable.rb b/lib/devise/ldap_authenticatable.rb index 531abdb..ef786fb 100644 --- a/lib/devise/ldap_authenticatable.rb +++ b/lib/devise/ldap_authenticatable.rb @@ -1,49 +1,53 @@ # frozen_string_literal: true -if ENV['LDAP_ENABLED'] == 'true' - require 'net/ldap' - require 'devise/strategies/authenticatable' +require 'net/ldap' +require 'devise/strategies/authenticatable' - module Devise - module Strategies - class LdapAuthenticatable < Authenticatable - def authenticate! - if params[:user] - ldap = Net::LDAP.new( - host: Devise.ldap_host, - port: Devise.ldap_port, - base: Devise.ldap_base, - encryption: { - method: Devise.ldap_method, - tls_options: OpenSSL::SSL::SSLContext::DEFAULT_PARAMS, - }, - auth: { - method: :simple, - username: Devise.ldap_bind_dn, - password: Devise.ldap_password, - }, - connect_timeout: 10 - ) +module Devise + module Strategies + class LdapAuthenticatable < Authenticatable + def authenticate! + if params[:user] + ldap = Net::LDAP.new( + host: Devise.ldap_host, + port: Devise.ldap_port, + base: Devise.ldap_base, + encryption: { + method: Devise.ldap_method, + tls_options: tls_options, + }, + auth: { + method: :simple, + username: Devise.ldap_bind_dn, + password: Devise.ldap_password, + }, + connect_timeout: 10 + ) - if (user_info = ldap.bind_as(base: Devise.ldap_base, filter: "(#{Devise.ldap_uid}=#{email})", password: password)) - user = User.ldap_get_user(user_info.first) - success!(user) - else - return fail(:invalid_login) - end + if (user_info = ldap.bind_as(base: Devise.ldap_base, filter: "(#{Devise.ldap_uid}=#{email})", password: password)) + user = User.ldap_get_user(user_info.first) + success!(user) + else + return fail(:invalid_login) end end + end - def email - params[:user][:email] - end + def email + params[:user][:email] + end - def password - params[:user][:password] + def password + params[:user][:password] + end + + def tls_options + OpenSSL::SSL::SSLContext::DEFAULT_PARAMS.tap do |options| + options[:verify_mode] = OpenSSL::SSL::VERIFY_NONE if Devise.ldap_tls_no_verify end end end end - - Warden::Strategies.add(:ldap_authenticatable, Devise::Strategies::LdapAuthenticatable) end + +Warden::Strategies.add(:ldap_authenticatable, Devise::Strategies::LdapAuthenticatable) From f64af6473fd1c61190ede960791efddc29806f92 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Tue, 20 Mar 2018 23:49:24 +0100 Subject: [PATCH 014/381] Bump version to 2.3.2rc4 --- lib/mastodon/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mastodon/version.rb b/lib/mastodon/version.rb index 78a2dd9..8065076 100644 --- a/lib/mastodon/version.rb +++ b/lib/mastodon/version.rb @@ -21,7 +21,7 @@ module Mastodon end def flags - 'rc3' + 'rc4' end def to_a From a6b59cd1a32ce2d9ac54fa7b5e04672a63692fdf Mon Sep 17 00:00:00 2001 From: Akihiko Odaki Date: Wed, 21 Mar 2018 18:26:15 +0900 Subject: [PATCH 015/381] Remove debug option from Babel preset env (#6852) --- .babelrc | 1 - 1 file changed, 1 deletion(-) diff --git a/.babelrc b/.babelrc index ed28aa5..190b503 100644 --- a/.babelrc +++ b/.babelrc @@ -4,7 +4,6 @@ [ "env", { - "debug": true, "exclude": ["transform-async-to-generator", "transform-regenerator"], "loose": true, "modules": false, From 93897134caf42f1b70620282cef04865af7026b1 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Wed, 21 Mar 2018 10:26:53 +0100 Subject: [PATCH 016/381] Permit dots in usernames with conditions (#6844) * Permit dots in usernames with conditions - Dot cannot be the start or end of username - a.lice and al.ice are considered the same during sign-up * Fix regex mixin flags --- app/models/account.rb | 6 ++++-- app/validators/unique_username_validator.rb | 14 ++++++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) create mode 100644 app/validators/unique_username_validator.rb diff --git a/app/models/account.rb b/app/models/account.rb index c1347fe..1426986 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -47,7 +47,8 @@ # class Account < ApplicationRecord - MENTION_RE = /(?<=^|[^\/[:word:]])@(([a-z0-9_]+)(?:@[a-z0-9\.\-]+[a-z0-9]+)?)/i + USERNAME_RE = /[a-z0-9_]+([a-z0-9_\.]+[a-z0-9_]+)?/i + MENTION_RE = /(?<=^|[^\/[:word:]])@((#{USERNAME_RE}?)(?:@[a-z0-9\.\-]+[a-z0-9]+)?)/i include AccountAvatar include AccountFinderConcern @@ -68,7 +69,8 @@ class Account < ApplicationRecord validates :username, uniqueness: { scope: :domain, case_sensitive: true }, if: -> { !local? && will_save_change_to_username? } # Local user validations - validates :username, format: { with: /\A[a-z0-9_]+\z/i }, uniqueness: { scope: :domain, case_sensitive: false }, length: { maximum: 30 }, if: -> { local? && will_save_change_to_username? } + validates :username, format: { with: /\A#{USERNAME_RE}\z/i }, length: { maximum: 30 }, if: -> { local? && will_save_change_to_username? } + validates_with UniqueUsernameValidator, if: -> { local? && will_save_change_to_username? } validates_with UnreservedUsernameValidator, if: -> { local? && will_save_change_to_username? } validates :display_name, length: { maximum: 30 }, if: -> { local? && will_save_change_to_display_name? } validates :note, length: { maximum: 160 }, if: -> { local? && will_save_change_to_note? } diff --git a/app/validators/unique_username_validator.rb b/app/validators/unique_username_validator.rb new file mode 100644 index 0000000..c76407b --- /dev/null +++ b/app/validators/unique_username_validator.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +class UniqueUsernameValidator < ActiveModel::Validator + def validate(account) + return if account.username.nil? + + normalized_username = account.username.downcase.delete('.') + + scope = Account.where(domain: nil, username: normalized_username) + scope = scope.where.not(id: account.id) if account.persisted? + + account.errors.add(:username, :taken) if scope.exists? + end +end From d97903a3587e137316adbd8a9f0460552b5bfbcd Mon Sep 17 00:00:00 2001 From: Patrick Figel Date: Wed, 21 Mar 2018 17:43:28 +0100 Subject: [PATCH 017/381] Update sanitize and loofah (#6855) Fixes CVE-2018-8048 and CVE-2018-3740, two medium-severity XSS vulnerabilities present in these gems when built against libxml2 >= 2.9.2. --- Gemfile | 2 +- Gemfile.lock | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Gemfile b/Gemfile index fe5bf57..8bc28b8 100644 --- a/Gemfile +++ b/Gemfile @@ -71,7 +71,7 @@ gem 'mario-redis-lock', '~> 1.2', require: 'redis_lock' gem 'rqrcode', '~> 0.10' gem 'ruby-oembed', '~> 0.12', require: 'oembed' gem 'ruby-progressbar', '~> 1.4' -gem 'sanitize', '~> 4.4' +gem 'sanitize', '~> 4.6.4' gem 'sidekiq', '~> 5.0' gem 'sidekiq-scheduler', '~> 2.1' gem 'sidekiq-unique-jobs', '~> 5.0' diff --git a/Gemfile.lock b/Gemfile.lock index ca6365c..7360ce7 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -288,7 +288,7 @@ GEM activesupport (>= 4, < 5.2) railties (>= 4, < 5.2) request_store (~> 1.0) - loofah (2.1.1) + loofah (2.2.1) crass (~> 1.0.2) nokogiri (>= 1.5.9) mail (2.7.0) @@ -316,9 +316,9 @@ GEM net-ssh (>= 2.6.5) net-ssh (4.2.0) nio4r (2.1.0) - nokogiri (1.8.1) + nokogiri (1.8.2) mini_portile2 (~> 2.3.0) - nokogumbo (1.4.13) + nokogumbo (1.5.0) nokogiri nsa (0.2.4) activesupport (>= 4.2, < 6) @@ -496,10 +496,10 @@ GEM rufus-scheduler (3.4.2) et-orbi (~> 1.0) safe_yaml (1.0.4) - sanitize (4.5.0) + sanitize (4.6.4) crass (~> 1.0.2) nokogiri (>= 1.4.4) - nokogumbo (~> 1.4.1) + nokogumbo (~> 1.4) sass (3.5.3) sass-listen (~> 4.0.0) sass-listen (4.0.0) @@ -699,7 +699,7 @@ DEPENDENCIES rubocop ruby-oembed (~> 0.12) ruby-progressbar (~> 1.4) - sanitize (~> 4.4) + sanitize (~> 4.6.4) scss_lint (~> 0.55) sidekiq (~> 5.0) sidekiq-bulk (~> 0.1.1) From f66a7860291e6b2fef1844b580c22296dbad9202 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Thu, 22 Mar 2018 09:33:14 +0100 Subject: [PATCH 018/381] Hide floating action button on thread views (#6859) --- app/javascript/mastodon/features/ui/components/columns_area.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/javascript/mastodon/features/ui/components/columns_area.js b/app/javascript/mastodon/features/ui/components/columns_area.js index e82c464..05cdb4e 100644 --- a/app/javascript/mastodon/features/ui/components/columns_area.js +++ b/app/javascript/mastodon/features/ui/components/columns_area.js @@ -28,6 +28,8 @@ const componentMap = { 'LIST': ListTimeline, }; +const shouldHideFAB = path => path.match(/^\/statuses\//); + @component => injectIntl(component, { withRef: true }) export default class ColumnsArea extends ImmutablePureComponent { @@ -153,7 +155,7 @@ export default class ColumnsArea extends ImmutablePureComponent { this.pendingIndex = null; if (singleColumn) { - const floatingActionButton = this.context.router.history.location.pathname === '/statuses/new' ? null : ; + const floatingActionButton = shouldHideFAB(this.context.router.history.location.pathname) ? null : ; return columnIndex !== -1 ? [ From 6f531d140b8398a4680567934fe66cb0ca465fbc Mon Sep 17 00:00:00 2001 From: ThibG Date: Thu, 22 Mar 2018 10:45:48 +0100 Subject: [PATCH 019/381] Fix MENTION_RE to not match nil usernames (#6862) --- app/models/account.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/account.rb b/app/models/account.rb index 1426986..c88c6ec 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -48,7 +48,7 @@ class Account < ApplicationRecord USERNAME_RE = /[a-z0-9_]+([a-z0-9_\.]+[a-z0-9_]+)?/i - MENTION_RE = /(?<=^|[^\/[:word:]])@((#{USERNAME_RE}?)(?:@[a-z0-9\.\-]+[a-z0-9]+)?)/i + MENTION_RE = /(?<=^|[^\/[:word:]])@((#{USERNAME_RE})(?:@[a-z0-9\.\-]+[a-z0-9]+)?)/i include AccountAvatar include AccountFinderConcern From da70aca28eaa68f21c450c8f7b6ecb6168d29941 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Thu, 22 Mar 2018 11:30:22 +0100 Subject: [PATCH 020/381] Restore username validation to disallow dots, for now (#6863) Usernames with dots in them do not work with routes, because the dot usually separates the desired page format (e.g. json). I don't want to mess with changing route constraints for this patch release. --- app/models/account.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/account.rb b/app/models/account.rb index c88c6ec..9a83d97 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -69,7 +69,7 @@ class Account < ApplicationRecord validates :username, uniqueness: { scope: :domain, case_sensitive: true }, if: -> { !local? && will_save_change_to_username? } # Local user validations - validates :username, format: { with: /\A#{USERNAME_RE}\z/i }, length: { maximum: 30 }, if: -> { local? && will_save_change_to_username? } + validates :username, format: { with: /\A[a-z0-9_]+\z/i }, length: { maximum: 30 }, if: -> { local? && will_save_change_to_username? } validates_with UniqueUsernameValidator, if: -> { local? && will_save_change_to_username? } validates_with UnreservedUsernameValidator, if: -> { local? && will_save_change_to_username? } validates :display_name, length: { maximum: 30 }, if: -> { local? && will_save_change_to_display_name? } From 9fe1619db94e3267b263633ecd4d05a840064215 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Thu, 22 Mar 2018 11:31:17 +0100 Subject: [PATCH 021/381] Do not re-query mentions from serializers (#6858) Fix performance regression from #6836 --- app/serializers/activitypub/note_serializer.rb | 2 +- app/serializers/rest/status_serializer.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/serializers/activitypub/note_serializer.rb b/app/serializers/activitypub/note_serializer.rb index abaf290..ddafb54 100644 --- a/app/serializers/activitypub/note_serializer.rb +++ b/app/serializers/activitypub/note_serializer.rb @@ -57,7 +57,7 @@ class ActivityPub::NoteSerializer < ActiveModel::Serializer end def virtual_tags - object.mentions.order(:id) + object.tags + object.emojis + object.mentions.to_a.sort_by(&:id) + object.tags + object.emojis end def atom_uri diff --git a/app/serializers/rest/status_serializer.rb b/app/serializers/rest/status_serializer.rb index 67da92c..fe3dc9b 100644 --- a/app/serializers/rest/status_serializer.rb +++ b/app/serializers/rest/status_serializer.rb @@ -87,7 +87,7 @@ class REST::StatusSerializer < ActiveModel::Serializer end def ordered_mentions - object.mentions.order(:id) + object.mentions.to_a.sort_by(&:id) end class ApplicationSerializer < ActiveModel::Serializer From dafae9818da27573fdd872cfdb8680fdbca3fdd8 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Thu, 22 Mar 2018 11:31:52 +0100 Subject: [PATCH 022/381] Bump version to 2.3.2rc5 --- lib/mastodon/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mastodon/version.rb b/lib/mastodon/version.rb index 8065076..035aafa 100644 --- a/lib/mastodon/version.rb +++ b/lib/mastodon/version.rb @@ -21,7 +21,7 @@ module Mastodon end def flags - 'rc4' + 'rc5' end def to_a From 6cc432bbc4b6ba64f71e559e7614144c2610eda1 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Thu, 22 Mar 2018 14:13:46 +0100 Subject: [PATCH 023/381] Bump version to 2.3.2 --- lib/mastodon/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mastodon/version.rb b/lib/mastodon/version.rb index 035aafa..121c5c6 100644 --- a/lib/mastodon/version.rb +++ b/lib/mastodon/version.rb @@ -21,7 +21,7 @@ module Mastodon end def flags - 'rc5' + '' end def to_a From ecdc5957a34b7f7a7a342c631690802d79d13f37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Quent=C3=AD?= <33203663+Quenty31@users.noreply.github.com> Date: Fri, 23 Mar 2018 10:52:25 +0100 Subject: [PATCH 024/381] [i18n] Occitan update (#6869) * Update oc.yml * Update simple_form.oc.yml * Update oc.json * Update oc.yml * bundle exec i18n-tasks normalize --- app/javascript/mastodon/locales/oc.json | 44 ++++++++++++++++----------------- config/locales/oc.yml | 39 ++++++++++++++++++++++++++--- config/locales/simple_form.oc.yml | 2 ++ 3 files changed, 60 insertions(+), 25 deletions(-) diff --git a/app/javascript/mastodon/locales/oc.json b/app/javascript/mastodon/locales/oc.json index c7a26c1..f93fe29 100644 --- a/app/javascript/mastodon/locales/oc.json +++ b/app/javascript/mastodon/locales/oc.json @@ -1,9 +1,9 @@ { "account.block": "Blocar @{name}", "account.block_domain": "Tot amagar del domeni {domain}", - "account.blocked": "Blocked", + "account.blocked": "Blocat", "account.disclaimer_full": "Aquelas informacions de perfil pòdon èsser incomplètas.", - "account.domain_blocked": "Domain hidden", + "account.domain_blocked": "Domeni amagat", "account.edit_profile": "Modificar lo perfil", "account.follow": "Sègre", "account.followers": "Seguidors", @@ -15,9 +15,9 @@ "account.moved_to": "{name} a mudat los catons a :", "account.mute": "Rescondre @{name}", "account.mute_notifications": "Rescondre las notificacions de @{name}", - "account.muted": "Muted", - "account.posts": "Estatuts", - "account.posts_with_replies": "Toots with replies", + "account.muted": "Mes en silenci", + "account.posts": "Tuts", + "account.posts_with_replies": "Tuts amb responsas", "account.report": "Senhalar @{name}", "account.requested": "Invitacion mandada. Clicatz per anullar", "account.share": "Partejar lo perfil a @{name}", @@ -60,10 +60,10 @@ "compose_form.placeholder": "A de qué pensatz ?", "compose_form.publish": "Tut", "compose_form.publish_loud": "{publish} !", - "compose_form.sensitive.marked": "Media is marked as sensitive", - "compose_form.sensitive.unmarked": "Media is not marked as sensitive", - "compose_form.spoiler.marked": "Text is hidden behind warning", - "compose_form.spoiler.unmarked": "Text is not hidden", + "compose_form.sensitive.marked": "Lo mèdia es marcat coma sensible", + "compose_form.sensitive.unmarked": "Lo mèdia es pas marcat coma sensible", + "compose_form.spoiler.marked": "Lo tèxte es rescondut jos l’avertiment", + "compose_form.spoiler.unmarked": "Lo tèxte es pas rescondut", "compose_form.spoiler_placeholder": "Escrivètz l’avertiment aquí", "confirmation_modal.cancel": "Anullar", "confirmations.block.confirm": "Blocar", @@ -207,28 +207,28 @@ "privacy.unlisted.short": "Pas-listat", "regeneration_indicator.label": "Cargament…", "regeneration_indicator.sublabel": "Sèm a preparar vòstre flux d’acuèlh !", - "relative_time.days": "fa {number}d", + "relative_time.days": "fa {number} d", "relative_time.hours": "fa {number}h", "relative_time.just_now": "ara", - "relative_time.minutes": "fa {number}min", + "relative_time.minutes": "fa {number} min", "relative_time.seconds": "fa {number}s", "reply_indicator.cancel": "Anullar", - "report.forward": "Forward to {target}", - "report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?", - "report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:", + "report.forward": "Far sègre a {target}", + "report.forward_hint": "Lo compte ven d’un autre servidor. Volètz mandar una còpia anonima del rapòrt enlai tanben ?", + "report.hint": "Lo moderator de l’instància aurà lo rapòrt. Podètz fornir una explicacion de vòstre senhalament aquí dejós :", "report.placeholder": "Comentaris addicionals", "report.submit": "Mandar", "report.target": "Senhalar {target}", "search.placeholder": "Recercar", "search_popout.search_format": "Format recèrca avançada", - "search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.", + "search_popout.tips.full_text": "Un tèxte simple que tòrna los estatuts qu’avètz escriches, mes en favorits, partejats, o ont sètz mencionat, e tanben los noms d’utilizaires, escais-noms e etiquetas que correspondonas.", "search_popout.tips.hashtag": "etiqueta", "search_popout.tips.status": "estatut", "search_popout.tips.text": "Lo tèxt brut tòrna escais, noms d’utilizaire e etiquetas correspondents", "search_popout.tips.user": "utilizaire", - "search_results.accounts": "People", - "search_results.hashtags": "Hashtags", - "search_results.statuses": "Toots", + "search_results.accounts": "Monde", + "search_results.hashtags": "Etiquetas", + "search_results.statuses": "Tuts", "search_results.total": "{count, number} {count, plural, one {resultat} other {resultats}}", "standalone.public_title": "Una ulhada dedins…", "status.block": "Blocar @{name}", @@ -244,7 +244,7 @@ "status.mute_conversation": "Rescondre la conversacion", "status.open": "Desplegar aqueste estatut", "status.pin": "Penjar al perfil", - "status.pinned": "Pinned toot", + "status.pinned": "Tut penjat", "status.reblog": "Partejar", "status.reblogged_by": "{name} a partejat", "status.reply": "Respondre", @@ -254,9 +254,9 @@ "status.sensitive_warning": "Contengut sensible", "status.share": "Partejar", "status.show_less": "Tornar plegar", - "status.show_less_all": "Show less for all", + "status.show_less_all": "Los tornar plegar totes", "status.show_more": "Desplegar", - "status.show_more_all": "Show more for all", + "status.show_more_all": "Los desplegar totes", "status.unmute_conversation": "Tornar mostrar la conversacion", "status.unpin": "Tirar del perfil", "tabs_bar.federated_timeline": "Flux public global", @@ -267,7 +267,7 @@ "upload_area.title": "Lisatz e depausatz per mandar", "upload_button.label": "Ajustar un mèdia", "upload_form.description": "Descripcion pels mal vesents", - "upload_form.focus": "Crop", + "upload_form.focus": "Retalhar", "upload_form.undo": "Anullar", "upload_progress.label": "Mandadís…", "video.close": "Tampar la vidèo", diff --git a/config/locales/oc.yml b/config/locales/oc.yml index 160bbc3..49b1df8 100644 --- a/config/locales/oc.yml +++ b/config/locales/oc.yml @@ -193,7 +193,7 @@ oc: create: Crear blocatge hint: Lo blocatge empacharà pas la creacion de compte dins la basa de donadas, mai aplicarà la moderacion sus aquestes comptes. severity: - desc_html: "Silenci farà venir invisibles los estatuts del compte al mond que son pas de seguidors. Suspendre levarà tot lo contengut del compte, los mèdias e las donadas de perfil." + desc_html: "Silenci farà venir invisibles los estatuts del compte al monde que son pas de seguidors. Suspendre levarà tot lo contengut del compte, los mèdias e las donadas de perfil. Utilizatz Cap se volètz regetar totes los mèdias." noop: Cap silence: Silenci suspend: Suspendre @@ -273,6 +273,9 @@ oc: contact_information: email: Picatz una adreça de corrièl username: Picatz un nom d’utilizaire + hero: + desc_html: Mostrat en primièra pagina. Almens 600x100px recomandat. S’es pas configurat l’imatge de l’instància serà mostrat + title: Imatge de l’eròi peers_api_enabled: desc_html: Noms de domeni qu’aquesta instància a trobats pel fediverse title: Publica la lista de las instàncias conegudas @@ -289,6 +292,9 @@ oc: open: desc_html: Autorizar lo mond a se marcar title: Inscripcions + show_known_fediverse_at_about_page: + desc_html: Un còp activat mostrarà los tuts de totes los fediverse dins l’apercebut. Autrament mostrarà pas que los tuts locals. + title: Mostrar los fediverse coneguts dins l’apercebut del flux show_staff_badge: desc_html: Mostrar lo badge Personal sus la pagina de perfil title: Mostrar lo badge personal @@ -353,6 +359,8 @@ oc: your_token: Vòstre geton d’accès auth: agreement_html: En vos marcar acceptatz las règlas de l’instància e politica de confidencialitat. + change_password: Senhal + confirm_email: Confirmar lo corrièl delete_account: Suprimir lo compte delete_account_html: Se volètz suprimir vòstre compte, podètz o far aquí. Vos demandarem que confirmetz. didnt_get_confirmation: Avètz pas recebut las instruccions de confirmacion ? @@ -362,10 +370,13 @@ oc: logout: Se desconnectar migrate_account: Mudar endacòm mai migrate_account_html: Se volètz mandar los visitors d’aqueste compte a un autre, podètz o configurar aquí. + or: o + or_log_in_with: O autentificatz-vos amb providers: cas: CAS saml: SAML register: Se marcar + register_elsewhere: Se marcar endacòm mai resend_confirmation: Tornar mandar las instruccions de confirmacion reset_password: Reïnicializar lo senhal security: Seguretat @@ -493,6 +504,13 @@ oc: title: Aquesta pagina es incorrècta noscript_html: Per utilizar l’aplicacion web de Mastodon, mercés d’activar JavaScript. O podètz utilizar una aplicacion per vòstra plataforma coma alernativa. exports: + archive_takeout: + date: Data + download: Telecargar vòstre archiu + hint_html: Podètz demandar un archiu de vòstres tuts e mèdias enviats. Las donadas exportadas seràn al format ActivityPub, ligible pels logicials compatibles. + in_progress: Complilacion de vòstre archiu... + request: Demandar vòstre archiu + size: Talha blocks: Personas que blocatz csv: CSV follows: Personas que seguètz @@ -565,7 +583,7 @@ oc: notification_mailer: digest: action: Veire totas las notificacions - body: 'Trobatz aquí un resumit dels messatges qu’avètz mancats dempuèi vòstra darrièra visita lo %{since} :' + body: Trobatz aquí un resumit dels messatges qu’avètz mancats dempuèi vòstra darrièra visita lo %{since} mention: "%{name} vos a mencionat dins :" new_followers_summary: one: Avètz un nòu seguidor dempuèi vòstra darrièra visita ! Ouà ! @@ -608,7 +626,9 @@ oc: trillion: T unit: '' pagination: + newer: Mai recent next: Seguent + older: Mai ancian prev: Precedent truncate: "…" preferences: @@ -692,6 +712,14 @@ oc: two_factor_authentication: Autentificacion en dos temps your_apps: Vòstras aplicacions statuses: + attached: + description: 'Ajustat : %{attached}' + image: + one: "%{count} imatge" + other: "%{count} imatges" + video: + one: "%{count} vidèo" + other: "%{count} vidèos" open_in_web: Dobrir sul web over_character_limit: limit de %{max} caractèrs passat pin_errors: @@ -799,10 +827,14 @@ oc: manual_instructions: 'Se podètz pas numerizar lo còdi QR e que vos cal picar lo còdi a la man, vaquí lo còdi en clar :' recovery_codes: Salvar los còdis de recuperacion recovery_codes_regenerated: Los còdis de recuperacion son ben estats tornats generar - recovery_instructions_html: Se vos arriba de perdre vòstre mobil, podètz utilizar un dels còdis de recuperacion cai-jos per poder tornar accedir a vòstre compte. Gardatz los còdis en seguretat, per exemple, imprimissètz los e gardatz los amb vòstres documents importants. + recovery_instructions_html: Se vos arriba de perdre vòstre mobil, podètz utilizar un dels còdis de recuperacion cai-jos per poder tornar accedir a vòstre compte. Gardatz los còdis en seguretat, per exemple, imprimissètz los e gardatz los amb vòstres documents importants. setup: Paramètres wrong_code: Lo còdi picat es invalid ! L’ora es la bona sul servidor e lo mobil ? user_mailer: + backup_ready: + explanation: Avètz demandat una salvagarda complèta de vòstre compte Mastodon. Es prèsta per telecargament ! + subject: Vòstre archiu es prèst per telecargament + title: Archiu per emportar welcome: edit_profile_action: Configuracion del perfil edit_profile_step: Podètz personalizar lo perfil en mandar un avatard, cambiar l’escais-nom e mai. Se volètz repassar las demandas d’abonaments abans que los nòus seguidors pòscan veire vòstre perfil, podètz clavar vòstre compte. @@ -824,4 +856,5 @@ oc: users: invalid_email: L’adreça de corrièl es invalida invalid_otp_token: Còdi d’autentificacion en dos temps invalid + seamless_external_login: Sètz connectat via un servici extèrn, los paramètres de senhal e de corrièl son doncas pas disponibles. signed_in_as: 'Session a :' diff --git a/config/locales/simple_form.oc.yml b/config/locales/simple_form.oc.yml index addca75..690d1de 100644 --- a/config/locales/simple_form.oc.yml +++ b/config/locales/simple_form.oc.yml @@ -45,6 +45,7 @@ oc: setting_default_privacy: Confidencialitat de las publicacions setting_default_sensitive: Totjorn marcar los mèdias coma sensibles setting_delete_modal: Afichar una fenèstra de confirmacion abans de suprimir un estatut + setting_display_sensitive_media: Totjorn mostrar los mèdias coma sensibles setting_noindex: Èsser pas indexat pels motors de recèrca setting_reduce_motion: Reduire la velocitat de las animacions setting_system_font_ui: Utilizar la polissa del sisèma @@ -53,6 +54,7 @@ oc: severity: Severitat type: Tip d’impòrt username: Nom d’utilizaire + username_or_email: Nom d’utilizaire o corrièl interactions: must_be_follower: Blocar las notificacions del mond que vos sègon pas must_be_following: Blocar las notificacions del mond que seguètz pas From 65c10c0bc829bb97ad86436e0715d17e82d53c2f Mon Sep 17 00:00:00 2001 From: Yamagishi Kazutoshi Date: Sat, 24 Mar 2018 09:04:02 +0900 Subject: [PATCH 025/381] Weblate translations (2018-03-23) (#6874) * Translated using Weblate (Galician) Currently translated at 100.0% (587 of 587 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/gl/ * Translated using Weblate (Dutch) Currently translated at 100.0% (587 of 587 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/nl/ * Translated using Weblate (Catalan) Currently translated at 100.0% (587 of 587 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/ca/ * Translated using Weblate (Arabic) Currently translated at 76.4% (449 of 587 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/ar/ * Translated using Weblate (Japanese) Currently translated at 99.8% (586 of 587 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/ja/ * Translated using Weblate (Slovak) Currently translated at 92.3% (542 of 587 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/sk/ * Translated using Weblate (Slovak) Currently translated at 92.3% (542 of 587 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/sk/ * Translated using Weblate (Slovak) Currently translated at 100.0% (58 of 58 strings) Translation: Mastodon/Preferences Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/simple_form/sk/ * Translated using Weblate (Polish) Currently translated at 98.9% (581 of 587 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/pl/ * Translated using Weblate (French) Currently translated at 99.6% (585 of 587 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/fr/ * Translated using Weblate (Portuguese (Brazil)) Currently translated at 99.8% (586 of 587 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/pt_BR/ * Translated using Weblate (Catalan) Currently translated at 100.0% (280 of 280 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/ca/ * bundle exec i18n-tasks normalize && yarn manage:translations --- app/javascript/mastodon/locales/ca.json | 22 +++++++++++----------- config/locales/ar.yml | 5 ++++- config/locales/ca.yml | 9 +++++++++ config/locales/fr.yml | 8 ++++++++ config/locales/gl.yml | 9 +++++++++ config/locales/ja.yml | 9 +++++++++ config/locales/nl.yml | 9 +++++++++ config/locales/pl.yml | 2 ++ config/locales/pt-BR.yml | 9 +++++++++ config/locales/simple_form.sk.yml | 2 +- config/locales/sk.yml | 8 ++++---- 11 files changed, 75 insertions(+), 17 deletions(-) diff --git a/app/javascript/mastodon/locales/ca.json b/app/javascript/mastodon/locales/ca.json index 4923c10..3222daa 100644 --- a/app/javascript/mastodon/locales/ca.json +++ b/app/javascript/mastodon/locales/ca.json @@ -1,9 +1,9 @@ { "account.block": "Bloca @{name}", "account.block_domain": "Amaga-ho tot de {domain}", - "account.blocked": "Blocked", + "account.blocked": "Bloquejat", "account.disclaimer_full": "La informació següent pot reflectir incompleta el perfil de l'usuari.", - "account.domain_blocked": "Domain hidden", + "account.domain_blocked": "Domini ocult", "account.edit_profile": "Edita el perfil", "account.follow": "Segueix", "account.followers": "Seguidors", @@ -15,7 +15,7 @@ "account.moved_to": "{name} s'ha mogut a:", "account.mute": "Silencia @{name}", "account.mute_notifications": "Notificacions desactivades de @{name}", - "account.muted": "Muted", + "account.muted": "Silenciat", "account.posts": "Toots", "account.posts_with_replies": "Toots amb respostes", "account.report": "Informe @{name}", @@ -60,10 +60,10 @@ "compose_form.placeholder": "En què estàs pensant?", "compose_form.publish": "Toot", "compose_form.publish_loud": "{publish}!", - "compose_form.sensitive.marked": "Media is marked as sensitive", - "compose_form.sensitive.unmarked": "Media is not marked as sensitive", - "compose_form.spoiler.marked": "Text is hidden behind warning", - "compose_form.spoiler.unmarked": "Text is not hidden", + "compose_form.sensitive.marked": "Mèdia marcat com a sensible", + "compose_form.sensitive.unmarked": "Mèdia no està marcat com a sensible", + "compose_form.spoiler.marked": "Text ocult sota l'avís", + "compose_form.spoiler.unmarked": "Text no ocult", "compose_form.spoiler_placeholder": "Escriu l'avís aquí", "confirmation_modal.cancel": "Cancel·la", "confirmations.block.confirm": "Bloca", @@ -221,7 +221,7 @@ "report.target": "Informes", "search.placeholder": "Cercar", "search_popout.search_format": "Format de cerca avançada", - "search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.", + "search_popout.tips.full_text": "Text simple recupera publicacions que has escrit, les marcades com a favorites, les impulsades o en les que has estat esmentat, així com usuaris, noms d'usuari i etiquetes.", "search_popout.tips.hashtag": "etiqueta", "search_popout.tips.status": "status", "search_popout.tips.text": "El text simple retorna coincidències amb els noms de visualització, els noms d'usuari i els hashtags", @@ -244,7 +244,7 @@ "status.mute_conversation": "Silenciar conversació", "status.open": "Ampliar aquest estat", "status.pin": "Fixat en el perfil", - "status.pinned": "Pinned toot", + "status.pinned": "Toot fixat", "status.reblog": "Impuls", "status.reblogged_by": "{name} ha retootejat", "status.reply": "Respondre", @@ -254,9 +254,9 @@ "status.sensitive_warning": "Contingut sensible", "status.share": "Compartir", "status.show_less": "Mostra menys", - "status.show_less_all": "Show less for all", + "status.show_less_all": "Mostra menys per a tot", "status.show_more": "Mostra més", - "status.show_more_all": "Show more for all", + "status.show_more_all": "Mostra més per a tot", "status.unmute_conversation": "Activar conversació", "status.unpin": "Deslliga del perfil", "tabs_bar.federated_timeline": "Federada", diff --git a/config/locales/ar.yml b/config/locales/ar.yml index e6447ca..25ca302 100644 --- a/config/locales/ar.yml +++ b/config/locales/ar.yml @@ -273,6 +273,7 @@ ar: your_token: رمز نفاذك auth: agreement_html: بقبولك التسجيل فإنك تُصرِّح قبول قواعد مثيل الخادوم و شروط الخدمة التي نوفرها لك. + change_password: الكلمة السرية confirm_email: تأكيد عنوان البريد الإلكتروني delete_account: حذف حساب delete_account_html: إن كنت ترغب في حذف حسابك يُمكنك المواصلة هنا. سوف يُطلَبُ منك التأكيد قبل الحذف. @@ -290,7 +291,7 @@ ar: resend_confirmation: إعادة إرسال تعليمات التأكيد reset_password: إعادة تعيين كلمة المرور security: الهوية - set_new_password: تعيين كلمة مرور جديدة + set_new_password: إدخال كلمة مرور جديدة authorize_follow: error: يا للأسف، وقع هناك خطأ إثر عملية البحث عن الحساب عن بعد follow: إتبع @@ -493,6 +494,7 @@ ar: windows: ويندوز windows_mobile: ويندوز موبايل windows_phone: ويندوز فون + revoke_success: تم إبطال الجلسة بنجاح title: الجلسات settings: authorized_apps: التطبيقات المرخص لها @@ -557,3 +559,4 @@ ar: users: invalid_email: عنوان البريد الإلكتروني غير صالح invalid_otp_token: الرمز الثنائي غير صالح + seamless_external_login: لقد قمت بتسجيل الدخول عبر خدمة خارجية، إنّ إعدادات الكلمة السرية و البريد الإلكتروني غير متوفرة. diff --git a/config/locales/ca.yml b/config/locales/ca.yml index c4008c9..7727bad 100644 --- a/config/locales/ca.yml +++ b/config/locales/ca.yml @@ -634,6 +634,15 @@ ca: two_factor_authentication: Autenticació de dos factors your_apps: Les teves aplicacions statuses: + attached: + description: 'Adjunt: %{attached}' + image: + one: "%{count} imatge" + other: "%{count} imatges" + video: + one: "%{count} vídeo" + other: "%{count} vídeos" + content_warning: 'Avís de contingut: %{warning}' open_in_web: Obre en la web over_character_limit: Límit de caràcters de %{max} superat pin_errors: diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 57ed05f..6137e1b 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -634,6 +634,14 @@ fr: two_factor_authentication: Identification à deux facteurs your_apps: Vos applications statuses: + attached: + description: 'Attaché : %{attached}' + image: + one: "%{count} image" + other: "%{count} images" + video: + one: "%{count} vidéo" + other: "%{count} vidéos" open_in_web: Ouvrir sur le web over_character_limit: limite de caractères dépassée de %{max} caractères pin_errors: diff --git a/config/locales/gl.yml b/config/locales/gl.yml index 30b68d7..bddc1b7 100644 --- a/config/locales/gl.yml +++ b/config/locales/gl.yml @@ -634,6 +634,15 @@ gl: two_factor_authentication: Validar Doble Factor your_apps: Os seus aplicativos statuses: + attached: + description: 'Axenado: %{attached}' + image: + one: "%{count} imaxe" + other: "%{count} imaxes" + video: + one: "%{count} vídeo" + other: "%{count} vídeos" + content_warning: 'Aviso sobre o contido: %{warning}' open_in_web: Abrir na web over_character_limit: Excedeu o límite de caracteres %{max} pin_errors: diff --git a/config/locales/ja.yml b/config/locales/ja.yml index 887eb01..3b19902 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -634,6 +634,15 @@ ja: two_factor_authentication: 二段階認証 your_apps: アプリ statuses: + attached: + description: '添付: %{attached}' + image: + one: "%{count} 枚の画像" + other: "%{count} 枚の画像" + video: + one: "%{count} 枚の動画" + other: "%{count} 枚の動画" + content_warning: '閲覧注意: %{warning}' open_in_web: Webで開く over_character_limit: 上限は %{max}文字までです pin_errors: diff --git a/config/locales/nl.yml b/config/locales/nl.yml index 66057e6..f3488f7 100644 --- a/config/locales/nl.yml +++ b/config/locales/nl.yml @@ -634,6 +634,15 @@ nl: two_factor_authentication: Tweestapsverificatie your_apps: Jouw toepassingen statuses: + attached: + description: 'Bijlagen: %{attached}' + image: + one: "%{count} afbeelding" + other: "%{count} afbeeldingen" + video: + one: "%{count} video" + other: "%{count} video's" + content_warning: 'Tekstwaarschuwing: %{warning}' open_in_web: In de webapp openen over_character_limit: Limiet van %{max} tekens overschreden pin_errors: diff --git a/config/locales/pl.yml b/config/locales/pl.yml index 78ca411..de43ca9 100644 --- a/config/locales/pl.yml +++ b/config/locales/pl.yml @@ -641,6 +641,8 @@ pl: two_factor_authentication: Uwierzytelnianie dwuetapowe your_apps: Twoje aplikacje statuses: + attached: + description: 'Przytwierdzony: %{attached}' open_in_web: Otwórz w przeglądarce over_character_limit: limit %{max} znaków przekroczony pin_errors: diff --git a/config/locales/pt-BR.yml b/config/locales/pt-BR.yml index 88d4e92..589f44f 100644 --- a/config/locales/pt-BR.yml +++ b/config/locales/pt-BR.yml @@ -634,6 +634,15 @@ pt-BR: two_factor_authentication: Autenticação em dois passos your_apps: Seus aplicativos statuses: + attached: + description: 'Anexado: %{attached}' + image: + one: "%{count} imagem" + other: "%{count} imagens" + video: + one: "%{count} vídeo" + other: "%{count} vídeos" + content_warning: 'Aviso de conteúdo: %{warning}' open_in_web: Abrir na web over_character_limit: limite de caracteres de %{max} excedido pin_errors: diff --git a/config/locales/simple_form.sk.yml b/config/locales/simple_form.sk.yml index dd3651e..7d4241b 100644 --- a/config/locales/simple_form.sk.yml +++ b/config/locales/simple_form.sk.yml @@ -46,7 +46,7 @@ sk: setting_default_sensitive: Označiť každý obrázok/video/súbor ako chúlostivý setting_delete_modal: Zobrazovať potvrdzovacie okno pred zmazaním toot-u setting_display_sensitive_media: Vždy zobrazovať médiá označované ako senzitívne - setting_noindex: Nezaradzovať vaše príspevky do indexácie pre vyhľadávanie + setting_noindex: Nezaraďuj príspevky do indexu pre vyhľadávče setting_reduce_motion: Redukovať pohyb v animáciách setting_system_font_ui: Použiť základné systémové písmo setting_theme: Vzhľad diff --git a/config/locales/sk.yml b/config/locales/sk.yml index e391974..a0e1a59 100644 --- a/config/locales/sk.yml +++ b/config/locales/sk.yml @@ -414,10 +414,10 @@ sk: warning_title: Dostupnosť distribuovaného obsahu errors: '403': Nemáte dostatočné povolenie na zobrazenie tejto stránky. - '404': Stránka ktorú ste hľadali neexistuje. - '410': Stránka ktorú tu hľadáte už viac neexistuje. + '404': Stránka ktorú si hľadal/a sa tu nenachádza. + '410': Stránka ktorú tu hľadáš už viac neexistuje. '422': - content: Bezpečtnostné overenie zlyhalo. Blokujete cookies? + content: Bezpečtnostné overenie zlyhalo. Blokuješ cookies? title: Bezpečtnostné overenie zlyhalo '429': Zamlčané '500': @@ -602,7 +602,7 @@ sk: import: Importovať migrate: Presunúť účet notifications: Oznámenia - preferences: Možnosti + preferences: Voľby settings: Nastavenia two_factor_authentication: Dvoj-faktorové overenie your_apps: Tvoje aplikácie From 4e71b104e6d5f02069120c7a56b26888c6f0fef5 Mon Sep 17 00:00:00 2001 From: Akihiko Odaki Date: Sat, 24 Mar 2018 18:54:19 +0900 Subject: [PATCH 026/381] Internationalize unexpected error message (#6887) --- .../features/ui/containers/notifications_container.js | 19 +++++++++++++++---- app/javascript/mastodon/locales/ar.json | 2 ++ app/javascript/mastodon/locales/bg.json | 2 ++ app/javascript/mastodon/locales/ca.json | 2 ++ app/javascript/mastodon/locales/de.json | 2 ++ app/javascript/mastodon/locales/defaultMessages.json | 13 +++++++++++++ app/javascript/mastodon/locales/en.json | 2 ++ app/javascript/mastodon/locales/eo.json | 2 ++ app/javascript/mastodon/locales/es.json | 2 ++ app/javascript/mastodon/locales/fa.json | 2 ++ app/javascript/mastodon/locales/fi.json | 2 ++ app/javascript/mastodon/locales/fr.json | 2 ++ app/javascript/mastodon/locales/gl.json | 2 ++ app/javascript/mastodon/locales/he.json | 2 ++ app/javascript/mastodon/locales/hr.json | 2 ++ app/javascript/mastodon/locales/hu.json | 2 ++ app/javascript/mastodon/locales/hy.json | 2 ++ app/javascript/mastodon/locales/id.json | 2 ++ app/javascript/mastodon/locales/io.json | 2 ++ app/javascript/mastodon/locales/it.json | 2 ++ app/javascript/mastodon/locales/ja.json | 2 ++ app/javascript/mastodon/locales/ko.json | 2 ++ app/javascript/mastodon/locales/nl.json | 2 ++ app/javascript/mastodon/locales/no.json | 2 ++ app/javascript/mastodon/locales/oc.json | 2 ++ app/javascript/mastodon/locales/pl.json | 2 ++ app/javascript/mastodon/locales/pt-BR.json | 2 ++ app/javascript/mastodon/locales/pt.json | 2 ++ app/javascript/mastodon/locales/ru.json | 2 ++ app/javascript/mastodon/locales/sk.json | 2 ++ app/javascript/mastodon/locales/sr-Latn.json | 2 ++ app/javascript/mastodon/locales/sr.json | 2 ++ app/javascript/mastodon/locales/sv.json | 2 ++ app/javascript/mastodon/locales/th.json | 2 ++ app/javascript/mastodon/locales/tr.json | 2 ++ app/javascript/mastodon/locales/uk.json | 2 ++ app/javascript/mastodon/locales/zh-CN.json | 2 ++ app/javascript/mastodon/locales/zh-HK.json | 2 ++ app/javascript/mastodon/locales/zh-TW.json | 2 ++ app/javascript/mastodon/middleware/errors.js | 8 +++++++- 40 files changed, 109 insertions(+), 5 deletions(-) diff --git a/app/javascript/mastodon/features/ui/containers/notifications_container.js b/app/javascript/mastodon/features/ui/containers/notifications_container.js index 5924197..b60a021 100644 --- a/app/javascript/mastodon/features/ui/containers/notifications_container.js +++ b/app/javascript/mastodon/features/ui/containers/notifications_container.js @@ -1,11 +1,22 @@ +import { injectIntl } from 'react-intl'; import { connect } from 'react-redux'; import { NotificationStack } from 'react-notification'; import { dismissAlert } from '../../../actions/alerts'; import { getAlerts } from '../../../selectors'; -const mapStateToProps = state => ({ - notifications: getAlerts(state), -}); +const mapStateToProps = (state, { intl }) => { + const notifications = getAlerts(state); + + notifications.forEach(notification => ['title', 'message'].forEach(key => { + const value = notification[key]; + + if (typeof value === 'object') { + notification[key] = intl.formatMessage(value); + } + })); + + return { notifications }; +}; const mapDispatchToProps = (dispatch) => { return { @@ -15,4 +26,4 @@ const mapDispatchToProps = (dispatch) => { }; }; -export default connect(mapStateToProps, mapDispatchToProps)(NotificationStack); +export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(NotificationStack)); diff --git a/app/javascript/mastodon/locales/ar.json b/app/javascript/mastodon/locales/ar.json index 73680a1..3d96207 100644 --- a/app/javascript/mastodon/locales/ar.json +++ b/app/javascript/mastodon/locales/ar.json @@ -28,6 +28,8 @@ "account.unmute": "إلغاء الكتم عن @{name}", "account.unmute_notifications": "إلغاء كتم إخطارات @{name}", "account.view_full_profile": "عرض الملف الشخصي كاملا", + "alert.unexpected.message": "An unexpected error occurred.", + "alert.unexpected.title": "Oops!", "boost_modal.combo": "يمكنك ضغط {combo} لتخطّي هذه في المرّة القادمة", "bundle_column_error.body": "لقد وقع هناك خطأ أثناء عملية تحميل هذا العنصر.", "bundle_column_error.retry": "إعادة المحاولة", diff --git a/app/javascript/mastodon/locales/bg.json b/app/javascript/mastodon/locales/bg.json index 1dee167..39eb05f 100644 --- a/app/javascript/mastodon/locales/bg.json +++ b/app/javascript/mastodon/locales/bg.json @@ -28,6 +28,8 @@ "account.unmute": "Unmute @{name}", "account.unmute_notifications": "Unmute notifications from @{name}", "account.view_full_profile": "View full profile", + "alert.unexpected.message": "An unexpected error occurred.", + "alert.unexpected.title": "Oops!", "boost_modal.combo": "You can press {combo} to skip this next time", "bundle_column_error.body": "Something went wrong while loading this component.", "bundle_column_error.retry": "Try again", diff --git a/app/javascript/mastodon/locales/ca.json b/app/javascript/mastodon/locales/ca.json index 3222daa..33545d8 100644 --- a/app/javascript/mastodon/locales/ca.json +++ b/app/javascript/mastodon/locales/ca.json @@ -28,6 +28,8 @@ "account.unmute": "Treure silenci de @{name}", "account.unmute_notifications": "Activar notificacions de @{name}", "account.view_full_profile": "Mostra el perfil complet", + "alert.unexpected.message": "An unexpected error occurred.", + "alert.unexpected.title": "Oops!", "boost_modal.combo": "Pots premer {combo} per saltar-te això el proper cop", "bundle_column_error.body": "S'ha produït un error en carregar aquest component.", "bundle_column_error.retry": "Torna-ho a provar", diff --git a/app/javascript/mastodon/locales/de.json b/app/javascript/mastodon/locales/de.json index e0fc0ee..7bdb6a3 100644 --- a/app/javascript/mastodon/locales/de.json +++ b/app/javascript/mastodon/locales/de.json @@ -28,6 +28,8 @@ "account.unmute": "@{name} nicht mehr stummschalten", "account.unmute_notifications": "Benachrichtigungen von @{name} einschalten", "account.view_full_profile": "Vollständiges Profil anzeigen", + "alert.unexpected.message": "An unexpected error occurred.", + "alert.unexpected.title": "Oops!", "boost_modal.combo": "Du kannst {combo} drücken, um dies beim nächsten Mal zu überspringen", "bundle_column_error.body": "Etwas ist beim Laden schiefgelaufen.", "bundle_column_error.retry": "Erneut versuchen", diff --git a/app/javascript/mastodon/locales/defaultMessages.json b/app/javascript/mastodon/locales/defaultMessages.json index d5b9c09..b983823 100644 --- a/app/javascript/mastodon/locales/defaultMessages.json +++ b/app/javascript/mastodon/locales/defaultMessages.json @@ -1734,5 +1734,18 @@ } ], "path": "app/javascript/mastodon/features/video/index.json" + }, + { + "descriptors": [ + { + "defaultMessage": "Oops!", + "id": "alert.unexpected.title" + }, + { + "defaultMessage": "An unexpected error occurred.", + "id": "alert.unexpected.message" + } + ], + "path": "app/javascript/mastodon/middleware/errors.json" } ] \ No newline at end of file diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json index d0d863f..5553772 100644 --- a/app/javascript/mastodon/locales/en.json +++ b/app/javascript/mastodon/locales/en.json @@ -28,6 +28,8 @@ "account.unmute": "Unmute @{name}", "account.unmute_notifications": "Unmute notifications from @{name}", "account.view_full_profile": "View full profile", + "alert.unexpected.message": "An unexpected error occurred.", + "alert.unexpected.title": "Oops!", "boost_modal.combo": "You can press {combo} to skip this next time", "bundle_column_error.body": "Something went wrong while loading this component.", "bundle_column_error.retry": "Try again", diff --git a/app/javascript/mastodon/locales/eo.json b/app/javascript/mastodon/locales/eo.json index fd687e8..35d9edf 100644 --- a/app/javascript/mastodon/locales/eo.json +++ b/app/javascript/mastodon/locales/eo.json @@ -28,6 +28,8 @@ "account.unmute": "Malsilentigi @{name}", "account.unmute_notifications": "Malsilentigi sciigojn de @{name}", "account.view_full_profile": "Vidi plenan profilon", + "alert.unexpected.message": "An unexpected error occurred.", + "alert.unexpected.title": "Oops!", "boost_modal.combo": "Vi povas premi {combo} por preterpasi sekvafoje", "bundle_column_error.body": "Io misfunkciis en la ŝargado de ĉi tiu elemento.", "bundle_column_error.retry": "Bonvolu reprovi", diff --git a/app/javascript/mastodon/locales/es.json b/app/javascript/mastodon/locales/es.json index 2107a15..e69938b 100644 --- a/app/javascript/mastodon/locales/es.json +++ b/app/javascript/mastodon/locales/es.json @@ -28,6 +28,8 @@ "account.unmute": "Dejar de silenciar a @{name}", "account.unmute_notifications": "Dejar de silenciar las notificaciones de @{name}", "account.view_full_profile": "Ver perfil completo", + "alert.unexpected.message": "An unexpected error occurred.", + "alert.unexpected.title": "Oops!", "boost_modal.combo": "Puedes presionar {combo} para saltear este aviso la próxima vez", "bundle_column_error.body": "Algo salió mal al cargar este componente.", "bundle_column_error.retry": "Inténtalo de nuevo", diff --git a/app/javascript/mastodon/locales/fa.json b/app/javascript/mastodon/locales/fa.json index 455dc5d..c9695d0 100644 --- a/app/javascript/mastodon/locales/fa.json +++ b/app/javascript/mastodon/locales/fa.json @@ -28,6 +28,8 @@ "account.unmute": "باصدا کردن @{name}", "account.unmute_notifications": "باصداکردن اعلان‌ها از طرف @{name}", "account.view_full_profile": "نمایش نمایهٔ کامل", + "alert.unexpected.message": "An unexpected error occurred.", + "alert.unexpected.title": "Oops!", "boost_modal.combo": "دکمهٔ {combo} را بزنید تا دیگر این را نبینید", "bundle_column_error.body": "هنگام بازکردن این بخش خطایی رخ داد.", "bundle_column_error.retry": "تلاش دوباره", diff --git a/app/javascript/mastodon/locales/fi.json b/app/javascript/mastodon/locales/fi.json index 1741445..cbdffec 100644 --- a/app/javascript/mastodon/locales/fi.json +++ b/app/javascript/mastodon/locales/fi.json @@ -28,6 +28,8 @@ "account.unmute": "Poista mykistys käyttäjältä @{name}", "account.unmute_notifications": "Poista mykistys käyttäjän @{name} ilmoituksilta", "account.view_full_profile": "Näytä koko profiili", + "alert.unexpected.message": "An unexpected error occurred.", + "alert.unexpected.title": "Oops!", "boost_modal.combo": "Voit painaa näppäimiä {combo} ohittaaksesi tämän ensi kerralla", "bundle_column_error.body": "Jokin meni vikaan tätä komponenttia ladatessa.", "bundle_column_error.retry": "Yritä uudestaan", diff --git a/app/javascript/mastodon/locales/fr.json b/app/javascript/mastodon/locales/fr.json index 40fd616..8c56a75 100644 --- a/app/javascript/mastodon/locales/fr.json +++ b/app/javascript/mastodon/locales/fr.json @@ -28,6 +28,8 @@ "account.unmute": "Ne plus masquer", "account.unmute_notifications": "Réactiver les notifications de @{name}", "account.view_full_profile": "Afficher le profil complet", + "alert.unexpected.message": "An unexpected error occurred.", + "alert.unexpected.title": "Oops!", "boost_modal.combo": "Vous pouvez appuyer sur {combo} pour pouvoir passer ceci, la prochaine fois", "bundle_column_error.body": "Une erreur s’est produite lors du chargement de ce composant.", "bundle_column_error.retry": "Réessayer", diff --git a/app/javascript/mastodon/locales/gl.json b/app/javascript/mastodon/locales/gl.json index edfb9cf..c5cedd6 100644 --- a/app/javascript/mastodon/locales/gl.json +++ b/app/javascript/mastodon/locales/gl.json @@ -28,6 +28,8 @@ "account.unmute": "Non acalar @{name}", "account.unmute_notifications": "Desbloquear as notificacións de @{name}", "account.view_full_profile": "Ver o perfil completo", + "alert.unexpected.message": "An unexpected error occurred.", + "alert.unexpected.title": "Oops!", "boost_modal.combo": "Pulse {combo} para saltar esto a próxima vez", "bundle_column_error.body": "Houbo un fallo mentras se cargaba este compoñente.", "bundle_column_error.retry": "Inténteo de novo", diff --git a/app/javascript/mastodon/locales/he.json b/app/javascript/mastodon/locales/he.json index b637ae4..fe6f9bb 100644 --- a/app/javascript/mastodon/locales/he.json +++ b/app/javascript/mastodon/locales/he.json @@ -28,6 +28,8 @@ "account.unmute": "הפסקת השתקת @{name}", "account.unmute_notifications": "להפסיק הסתרת הודעות מעם @{name}", "account.view_full_profile": "הראה אודות מלאות", + "alert.unexpected.message": "An unexpected error occurred.", + "alert.unexpected.title": "Oops!", "boost_modal.combo": "ניתן להקיש {combo} כדי לדלג בפעם הבאה", "bundle_column_error.body": "משהו השתבש בעת הצגת הרכיב הזה.", "bundle_column_error.retry": "לנסות שוב", diff --git a/app/javascript/mastodon/locales/hr.json b/app/javascript/mastodon/locales/hr.json index 4b64d79..11cd1bf 100644 --- a/app/javascript/mastodon/locales/hr.json +++ b/app/javascript/mastodon/locales/hr.json @@ -28,6 +28,8 @@ "account.unmute": "Poništi utišavanje @{name}", "account.unmute_notifications": "Unmute notifications from @{name}", "account.view_full_profile": "View full profile", + "alert.unexpected.message": "An unexpected error occurred.", + "alert.unexpected.title": "Oops!", "boost_modal.combo": "Možeš pritisnuti {combo} kako bi ovo preskočio sljedeći put", "bundle_column_error.body": "Something went wrong while loading this component.", "bundle_column_error.retry": "Try again", diff --git a/app/javascript/mastodon/locales/hu.json b/app/javascript/mastodon/locales/hu.json index 79888e4..1ea6576 100644 --- a/app/javascript/mastodon/locales/hu.json +++ b/app/javascript/mastodon/locales/hu.json @@ -28,6 +28,8 @@ "account.unmute": "@{name} kinémítása", "account.unmute_notifications": "@{name} értesítéseinek kinémítása", "account.view_full_profile": "Teljes profil megtekintése", + "alert.unexpected.message": "An unexpected error occurred.", + "alert.unexpected.title": "Oops!", "boost_modal.combo": "Megnyomhatod {combo}, hogy átugord következő alkalommal", "bundle_column_error.body": "Hiba történt a komponens betöltése közben.", "bundle_column_error.retry": "Próbálja újra", diff --git a/app/javascript/mastodon/locales/hy.json b/app/javascript/mastodon/locales/hy.json index 932ff15..e9638bf 100644 --- a/app/javascript/mastodon/locales/hy.json +++ b/app/javascript/mastodon/locales/hy.json @@ -28,6 +28,8 @@ "account.unmute": "Ապալռեցնել @{name}֊ին", "account.unmute_notifications": "Միացնել ծանուցումները @{name}֊ից", "account.view_full_profile": "Դիտել ամբողջական տարբերակը։", + "alert.unexpected.message": "An unexpected error occurred.", + "alert.unexpected.title": "Oops!", "boost_modal.combo": "Կարող ես սեղմել {combo}՝ սա հաջորդ անգամ բաց թողնելու համար", "bundle_column_error.body": "Այս բաղադրիչը բեռնելու ընթացքում ինչ֊որ բան խափանվեց։", "bundle_column_error.retry": "Կրկին փորձել", diff --git a/app/javascript/mastodon/locales/id.json b/app/javascript/mastodon/locales/id.json index bc42946..c8d8ebe 100644 --- a/app/javascript/mastodon/locales/id.json +++ b/app/javascript/mastodon/locales/id.json @@ -28,6 +28,8 @@ "account.unmute": "Berhenti membisukan @{name}", "account.unmute_notifications": "Munculkan notifikasi dari @{name}", "account.view_full_profile": "Lihat profil lengkap", + "alert.unexpected.message": "An unexpected error occurred.", + "alert.unexpected.title": "Oops!", "boost_modal.combo": "Anda dapat menekan {combo} untuk melewati ini", "bundle_column_error.body": "Kesalahan terjadi saat memuat komponen ini.", "bundle_column_error.retry": "Coba lagi", diff --git a/app/javascript/mastodon/locales/io.json b/app/javascript/mastodon/locales/io.json index 5ea982f..a2e9af8 100644 --- a/app/javascript/mastodon/locales/io.json +++ b/app/javascript/mastodon/locales/io.json @@ -28,6 +28,8 @@ "account.unmute": "Ne plus celar @{name}", "account.unmute_notifications": "Unmute notifications from @{name}", "account.view_full_profile": "View full profile", + "alert.unexpected.message": "An unexpected error occurred.", + "alert.unexpected.title": "Oops!", "boost_modal.combo": "Tu povas presar sur {combo} por omisar co en la venonta foyo", "bundle_column_error.body": "Something went wrong while loading this component.", "bundle_column_error.retry": "Try again", diff --git a/app/javascript/mastodon/locales/it.json b/app/javascript/mastodon/locales/it.json index 068598d..40ea9b2 100644 --- a/app/javascript/mastodon/locales/it.json +++ b/app/javascript/mastodon/locales/it.json @@ -28,6 +28,8 @@ "account.unmute": "Non silenziare @{name}", "account.unmute_notifications": "Unmute notifications from @{name}", "account.view_full_profile": "View full profile", + "alert.unexpected.message": "An unexpected error occurred.", + "alert.unexpected.title": "Oops!", "boost_modal.combo": "Puoi premere {combo} per saltare questo passaggio la prossima volta", "bundle_column_error.body": "Something went wrong while loading this component.", "bundle_column_error.retry": "Try again", diff --git a/app/javascript/mastodon/locales/ja.json b/app/javascript/mastodon/locales/ja.json index 0b88ac2..08f5e79 100644 --- a/app/javascript/mastodon/locales/ja.json +++ b/app/javascript/mastodon/locales/ja.json @@ -28,6 +28,8 @@ "account.unmute": "@{name}さんのミュートを解除", "account.unmute_notifications": "@{name}さんからの通知を受け取る", "account.view_full_profile": "全ての情報を見る", + "alert.unexpected.message": "不明なエラーが発生しました", + "alert.unexpected.title": "エラー", "boost_modal.combo": "次からは{combo}を押せばスキップできます", "bundle_column_error.body": "コンポーネントの読み込み中に問題が発生しました。", "bundle_column_error.retry": "再試行", diff --git a/app/javascript/mastodon/locales/ko.json b/app/javascript/mastodon/locales/ko.json index 532c1f0..bde4397 100644 --- a/app/javascript/mastodon/locales/ko.json +++ b/app/javascript/mastodon/locales/ko.json @@ -28,6 +28,8 @@ "account.unmute": "뮤트 해제", "account.unmute_notifications": "@{name}의 알림 뮤트 해제", "account.view_full_profile": "전체 프로필 보기", + "alert.unexpected.message": "An unexpected error occurred.", + "alert.unexpected.title": "Oops!", "boost_modal.combo": "다음부터 {combo}를 누르면 이 과정을 건너뛸 수 있습니다.", "bundle_column_error.body": "컴포넌트를 불러오는 과정에서 문제가 발생했습니다.", "bundle_column_error.retry": "다시 시도", diff --git a/app/javascript/mastodon/locales/nl.json b/app/javascript/mastodon/locales/nl.json index a83971f..140be0d 100644 --- a/app/javascript/mastodon/locales/nl.json +++ b/app/javascript/mastodon/locales/nl.json @@ -28,6 +28,8 @@ "account.unmute": "@{name} niet meer negeren", "account.unmute_notifications": "@{name} meldingen niet meer negeren", "account.view_full_profile": "Volledig profiel tonen", + "alert.unexpected.message": "An unexpected error occurred.", + "alert.unexpected.title": "Oops!", "boost_modal.combo": "Je kunt {combo} klikken om dit de volgende keer over te slaan", "bundle_column_error.body": "Tijdens het laden van dit onderdeel is er iets fout gegaan.", "bundle_column_error.retry": "Opnieuw proberen", diff --git a/app/javascript/mastodon/locales/no.json b/app/javascript/mastodon/locales/no.json index aaad033..4d6ac13 100644 --- a/app/javascript/mastodon/locales/no.json +++ b/app/javascript/mastodon/locales/no.json @@ -28,6 +28,8 @@ "account.unmute": "Avdemp @{name}", "account.unmute_notifications": "Vis varsler fra @{name}", "account.view_full_profile": "Vis hele profilen", + "alert.unexpected.message": "An unexpected error occurred.", + "alert.unexpected.title": "Oops!", "boost_modal.combo": "You kan trykke {combo} for å hoppe over dette neste gang", "bundle_column_error.body": "Noe gikk galt mens denne komponenten lastet.", "bundle_column_error.retry": "Prøv igjen", diff --git a/app/javascript/mastodon/locales/oc.json b/app/javascript/mastodon/locales/oc.json index f93fe29..24dfa93 100644 --- a/app/javascript/mastodon/locales/oc.json +++ b/app/javascript/mastodon/locales/oc.json @@ -28,6 +28,8 @@ "account.unmute": "Quitar de rescondre @{name}", "account.unmute_notifications": "Mostrar las notificacions de @{name}", "account.view_full_profile": "Veire lo perfil complèt", + "alert.unexpected.message": "An unexpected error occurred.", + "alert.unexpected.title": "Oops!", "boost_modal.combo": "Podètz botar {combo} per passar aquò lo còp que ven", "bundle_column_error.body": "Quicòm a fach mèuca pendent lo cargament d’aqueste compausant.", "bundle_column_error.retry": "Tornar ensajar", diff --git a/app/javascript/mastodon/locales/pl.json b/app/javascript/mastodon/locales/pl.json index 8496495..0b6f178 100644 --- a/app/javascript/mastodon/locales/pl.json +++ b/app/javascript/mastodon/locales/pl.json @@ -28,6 +28,8 @@ "account.unmute": "Cofnij wyciszenie @{name}", "account.unmute_notifications": "Cofnij wyciszenie powiadomień od @{name}", "account.view_full_profile": "Wyświetl pełny profil", + "alert.unexpected.message": "An unexpected error occurred.", + "alert.unexpected.title": "Oops!", "boost_modal.combo": "Naciśnij {combo}, aby pominąć to następnym razem", "bundle_column_error.body": "Coś poszło nie tak podczas ładowania tego składnika.", "bundle_column_error.retry": "Spróbuj ponownie", diff --git a/app/javascript/mastodon/locales/pt-BR.json b/app/javascript/mastodon/locales/pt-BR.json index c90fb37..dcaeace 100644 --- a/app/javascript/mastodon/locales/pt-BR.json +++ b/app/javascript/mastodon/locales/pt-BR.json @@ -28,6 +28,8 @@ "account.unmute": "Não silenciar @{name}", "account.unmute_notifications": "Retirar silêncio das notificações vindas de @{name}", "account.view_full_profile": "Ver perfil completo", + "alert.unexpected.message": "An unexpected error occurred.", + "alert.unexpected.title": "Oops!", "boost_modal.combo": "Você pode pressionar {combo} para ignorar este diálogo na próxima vez", "bundle_column_error.body": "Algo de errado aconteceu enquanto este componente era carregado.", "bundle_column_error.retry": "Tente novamente", diff --git a/app/javascript/mastodon/locales/pt.json b/app/javascript/mastodon/locales/pt.json index 3b20cf4..4725a82 100644 --- a/app/javascript/mastodon/locales/pt.json +++ b/app/javascript/mastodon/locales/pt.json @@ -28,6 +28,8 @@ "account.unmute": "Não silenciar @{name}", "account.unmute_notifications": "Deixar de silenciar @{name}", "account.view_full_profile": "Ver perfil completo", + "alert.unexpected.message": "An unexpected error occurred.", + "alert.unexpected.title": "Oops!", "boost_modal.combo": "Pode clicar {combo} para não voltar a ver", "bundle_column_error.body": "Algo de errado aconteceu enquanto este componente era carregado.", "bundle_column_error.retry": "Tente de novo", diff --git a/app/javascript/mastodon/locales/ru.json b/app/javascript/mastodon/locales/ru.json index ec21b5d..8e7d366 100644 --- a/app/javascript/mastodon/locales/ru.json +++ b/app/javascript/mastodon/locales/ru.json @@ -28,6 +28,8 @@ "account.unmute": "Снять глушение", "account.unmute_notifications": "Показывать уведомления от @{name}", "account.view_full_profile": "Показать полный профиль", + "alert.unexpected.message": "An unexpected error occurred.", + "alert.unexpected.title": "Oops!", "boost_modal.combo": "Нажмите {combo}, чтобы пропустить это в следующий раз", "bundle_column_error.body": "Что-то пошло не так при загрузке этого компонента.", "bundle_column_error.retry": "Попробовать снова", diff --git a/app/javascript/mastodon/locales/sk.json b/app/javascript/mastodon/locales/sk.json index 683f2aa..e3b3239 100644 --- a/app/javascript/mastodon/locales/sk.json +++ b/app/javascript/mastodon/locales/sk.json @@ -28,6 +28,8 @@ "account.unmute": "Prestať ignorovať @{name}", "account.unmute_notifications": "Odtĺmiť notifikácie od @{name}", "account.view_full_profile": "Pozri celý profil", + "alert.unexpected.message": "An unexpected error occurred.", + "alert.unexpected.title": "Oops!", "boost_modal.combo": "Nabudúce môžete kliknúť {combo} aby ste preskočili", "bundle_column_error.body": "Nastala chyba pri načítaní tohto komponentu.", "bundle_column_error.retry": "Skúste znova", diff --git a/app/javascript/mastodon/locales/sr-Latn.json b/app/javascript/mastodon/locales/sr-Latn.json index c6512cd..d38e8e3 100644 --- a/app/javascript/mastodon/locales/sr-Latn.json +++ b/app/javascript/mastodon/locales/sr-Latn.json @@ -28,6 +28,8 @@ "account.unmute": "Ukloni ućutkavanje korisniku @{name}", "account.unmute_notifications": "Uključi nazad obaveštenja od korisnika @{name}", "account.view_full_profile": "Vidi ceo profil", + "alert.unexpected.message": "An unexpected error occurred.", + "alert.unexpected.title": "Oops!", "boost_modal.combo": "Možete pritisnuti {combo} da preskočite ovo sledeći put", "bundle_column_error.body": "Nešto je pošlo po zlu prilikom učitavanja ove komponente.", "bundle_column_error.retry": "Pokušajte ponovo", diff --git a/app/javascript/mastodon/locales/sr.json b/app/javascript/mastodon/locales/sr.json index 93fbe59..3be0c89 100644 --- a/app/javascript/mastodon/locales/sr.json +++ b/app/javascript/mastodon/locales/sr.json @@ -28,6 +28,8 @@ "account.unmute": "Уклони ућуткавање кориснику @{name}", "account.unmute_notifications": "Укључи назад обавештења од корисника @{name}", "account.view_full_profile": "Види цео профил", + "alert.unexpected.message": "An unexpected error occurred.", + "alert.unexpected.title": "Oops!", "boost_modal.combo": "Можете притиснути {combo} да прескочите ово следећи пут", "bundle_column_error.body": "Нешто је пошло по злу приликом учитавања ове компоненте.", "bundle_column_error.retry": "Покушајте поново", diff --git a/app/javascript/mastodon/locales/sv.json b/app/javascript/mastodon/locales/sv.json index 4fa1291..a13ba98 100644 --- a/app/javascript/mastodon/locales/sv.json +++ b/app/javascript/mastodon/locales/sv.json @@ -28,6 +28,8 @@ "account.unmute": "Ta bort tystad @{name}", "account.unmute_notifications": "Återaktivera notifikationer från @{name}", "account.view_full_profile": "Visa hela profilen", + "alert.unexpected.message": "An unexpected error occurred.", + "alert.unexpected.title": "Oops!", "boost_modal.combo": "Du kan trycka {combo} för att slippa denna nästa gång", "bundle_column_error.body": "Något gick fel när du laddade denna komponent.", "bundle_column_error.retry": "Försök igen", diff --git a/app/javascript/mastodon/locales/th.json b/app/javascript/mastodon/locales/th.json index 95a933b..59ff10b 100644 --- a/app/javascript/mastodon/locales/th.json +++ b/app/javascript/mastodon/locales/th.json @@ -28,6 +28,8 @@ "account.unmute": "Unmute @{name}", "account.unmute_notifications": "Unmute notifications from @{name}", "account.view_full_profile": "View full profile", + "alert.unexpected.message": "An unexpected error occurred.", + "alert.unexpected.title": "Oops!", "boost_modal.combo": "You can press {combo} to skip this next time", "bundle_column_error.body": "Something went wrong while loading this component.", "bundle_column_error.retry": "Try again", diff --git a/app/javascript/mastodon/locales/tr.json b/app/javascript/mastodon/locales/tr.json index baaa5c9..e83af31 100644 --- a/app/javascript/mastodon/locales/tr.json +++ b/app/javascript/mastodon/locales/tr.json @@ -28,6 +28,8 @@ "account.unmute": "Sesi aç @{name}", "account.unmute_notifications": "Unmute notifications from @{name}", "account.view_full_profile": "View full profile", + "alert.unexpected.message": "An unexpected error occurred.", + "alert.unexpected.title": "Oops!", "boost_modal.combo": "Bir dahaki sefere {combo} tuşuna basabilirsiniz", "bundle_column_error.body": "Something went wrong while loading this component.", "bundle_column_error.retry": "Try again", diff --git a/app/javascript/mastodon/locales/uk.json b/app/javascript/mastodon/locales/uk.json index 1755c55..accc2d0 100644 --- a/app/javascript/mastodon/locales/uk.json +++ b/app/javascript/mastodon/locales/uk.json @@ -28,6 +28,8 @@ "account.unmute": "Зняти глушення", "account.unmute_notifications": "Unmute notifications from @{name}", "account.view_full_profile": "View full profile", + "alert.unexpected.message": "An unexpected error occurred.", + "alert.unexpected.title": "Oops!", "boost_modal.combo": "Ви можете натиснути {combo}, щоб пропустити це наступного разу", "bundle_column_error.body": "Something went wrong while loading this component.", "bundle_column_error.retry": "Try again", diff --git a/app/javascript/mastodon/locales/zh-CN.json b/app/javascript/mastodon/locales/zh-CN.json index d031c85..b9a912f 100644 --- a/app/javascript/mastodon/locales/zh-CN.json +++ b/app/javascript/mastodon/locales/zh-CN.json @@ -28,6 +28,8 @@ "account.unmute": "不再隐藏 @{name}", "account.unmute_notifications": "不再隐藏来自 @{name} 的通知", "account.view_full_profile": "查看完整资料", + "alert.unexpected.message": "An unexpected error occurred.", + "alert.unexpected.title": "Oops!", "boost_modal.combo": "下次按住 {combo} 即可跳过此提示", "bundle_column_error.body": "载入这个组件时发生了错误。", "bundle_column_error.retry": "重试", diff --git a/app/javascript/mastodon/locales/zh-HK.json b/app/javascript/mastodon/locales/zh-HK.json index d3ad238..91b1d00 100644 --- a/app/javascript/mastodon/locales/zh-HK.json +++ b/app/javascript/mastodon/locales/zh-HK.json @@ -28,6 +28,8 @@ "account.unmute": "取消 @{name} 的靜音", "account.unmute_notifications": "Unmute notifications from @{name}", "account.view_full_profile": "查看完整資料", + "alert.unexpected.message": "An unexpected error occurred.", + "alert.unexpected.title": "Oops!", "boost_modal.combo": "如你想在下次路過這顯示,請按{combo},", "bundle_column_error.body": "加載本組件出錯。", "bundle_column_error.retry": "重試", diff --git a/app/javascript/mastodon/locales/zh-TW.json b/app/javascript/mastodon/locales/zh-TW.json index 3a5eade..7e845c6 100644 --- a/app/javascript/mastodon/locales/zh-TW.json +++ b/app/javascript/mastodon/locales/zh-TW.json @@ -28,6 +28,8 @@ "account.unmute": "不再消音 @{name}", "account.unmute_notifications": "Unmute notifications from @{name}", "account.view_full_profile": "查看完整資訊", + "alert.unexpected.message": "An unexpected error occurred.", + "alert.unexpected.title": "Oops!", "boost_modal.combo": "下次你可以按 {combo} 來跳過", "bundle_column_error.body": "加載本組件出錯。", "bundle_column_error.retry": "重試", diff --git a/app/javascript/mastodon/middleware/errors.js b/app/javascript/mastodon/middleware/errors.js index b2c5f08..72e5631 100644 --- a/app/javascript/mastodon/middleware/errors.js +++ b/app/javascript/mastodon/middleware/errors.js @@ -1,7 +1,13 @@ +import { defineMessages } from 'react-intl'; import { showAlert } from '../actions/alerts'; const defaultFailSuffix = 'FAIL'; +const messages = defineMessages({ + unexpectedTitle: { id: 'alert.unexpected.title', defaultMessage: 'Oops!' }, + unexpectedMessage: { id: 'alert.unexpected.message', defaultMessage: 'An unexpected error occurred.' }, +}); + export default function errorsMiddleware() { return ({ dispatch }) => next => action => { if (action.type && !action.skipAlert) { @@ -21,7 +27,7 @@ export default function errorsMiddleware() { dispatch(showAlert(title, message)); } else { console.error(action.error); - dispatch(showAlert('Oops!', 'An unexpected error occurred.')); + dispatch(showAlert(messages.unexpectedTitle, messages.unexpectedMessage)); } } } From 54b273bf993888cd079113dd588cb7a90228b93b Mon Sep 17 00:00:00 2001 From: Akihiko Odaki Date: Sat, 24 Mar 2018 20:49:54 +0900 Subject: [PATCH 027/381] Close http connection in perform method of Request class (#6889) HTTP connections must be explicitly closed in many cases, and letting perform method close connections makes its callers less redundant and prevent them from forgetting to close connections. --- app/helpers/jsonld_helper.rb | 6 ++-- app/lib/provider_discovery.rb | 17 +++++---- app/lib/request.rb | 16 ++++++--- app/models/concerns/remotable.rb | 34 +++++++++--------- app/services/fetch_atom_service.rb | 47 +++++++++++++------------ app/services/fetch_link_card_service.rb | 21 +++++++---- app/services/resolve_account_service.rb | 9 +++-- app/services/send_interaction_service.rb | 8 ++--- app/services/subscribe_service.rb | 36 +++++++++---------- app/services/unsubscribe_service.rb | 7 ++-- app/workers/activitypub/delivery_worker.rb | 15 ++++---- app/workers/pubsubhubbub/confirmation_worker.rb | 18 ++++------ app/workers/pubsubhubbub/delivery_worker.rb | 17 ++++----- lib/tasks/mastodon.rake | 4 +-- spec/lib/request_spec.rb | 14 +++++--- 15 files changed, 138 insertions(+), 131 deletions(-) diff --git a/app/helpers/jsonld_helper.rb b/app/helpers/jsonld_helper.rb index 9530ad9..957a2cb 100644 --- a/app/helpers/jsonld_helper.rb +++ b/app/helpers/jsonld_helper.rb @@ -60,9 +60,9 @@ module JsonLdHelper end def fetch_resource_without_id_validation(uri) - response = build_request(uri).perform - return if response.code != 200 - body_to_json(response.to_s) + build_request(uri).perform do |response| + response.code == 200 ? body_to_json(response.to_s) : nil + end end def body_to_json(body) diff --git a/app/lib/provider_discovery.rb b/app/lib/provider_discovery.rb index 5732e4f..bbd3a2d 100644 --- a/app/lib/provider_discovery.rb +++ b/app/lib/provider_discovery.rb @@ -13,15 +13,14 @@ class ProviderDiscovery < OEmbed::ProviderDiscovery def discover_provider(url, **options) format = options[:format] - if options[:html] - html = Nokogiri::HTML(options[:html]) - else - res = Request.new(:get, url).perform - - raise OEmbed::NotFound, url if res.code != 200 || res.mime_type != 'text/html' - - html = Nokogiri::HTML(res.to_s) - end + html = if options[:html] + Nokogiri::HTML(options[:html]) + else + Request.new(:get, url).perform do |res| + raise OEmbed::NotFound, url if res.code != 200 || res.mime_type != 'text/html' + Nokogiri::HTML(res.to_s) + end + end if format.nil? || format == :json provider_endpoint ||= html.at_xpath('//link[@type="application/json+oembed"]')&.attribute('href')&.value diff --git a/app/lib/request.rb b/app/lib/request.rb index 298fb95..8a127c6 100644 --- a/app/lib/request.rb +++ b/app/lib/request.rb @@ -33,9 +33,17 @@ class Request end def perform - http_client.headers(headers).public_send(@verb, @url.to_s, @options) - rescue => e - raise e.class, "#{e.message} on #{@url}", e.backtrace[0] + begin + response = http_client.headers(headers).public_send(@verb, @url.to_s, @options) + rescue => e + raise e.class, "#{e.message} on #{@url}", e.backtrace[0] + end + + begin + yield response + ensure + http_client.close + end end def headers @@ -88,7 +96,7 @@ class Request end def http_client - HTTP.timeout(:per_operation, timeout).follow(max_hops: 2) + @http_client ||= HTTP.timeout(:per_operation, timeout).follow(max_hops: 2) end class Socket < TCPSocket diff --git a/app/models/concerns/remotable.rb b/app/models/concerns/remotable.rb index 69685ec..0f18c5d 100644 --- a/app/models/concerns/remotable.rb +++ b/app/models/concerns/remotable.rb @@ -21,23 +21,23 @@ module Remotable return if !%w(http https).include?(parsed_url.scheme) || parsed_url.host.empty? || self[attribute_name] == url begin - response = Request.new(:get, url).perform - - return if response.code != 200 - - matches = response.headers['content-disposition']&.match(/filename="([^"]*)"/) - filename = matches.nil? ? parsed_url.path.split('/').last : matches[1] - basename = SecureRandom.hex(8) - extname = if filename.nil? - '' - else - File.extname(filename) - end - - send("#{attachment_name}=", StringIO.new(response.to_s)) - send("#{attachment_name}_file_name=", basename + extname) - - self[attribute_name] = url if has_attribute?(attribute_name) + Request.new(:get, url).perform do |response| + next if response.code != 200 + + matches = response.headers['content-disposition']&.match(/filename="([^"]*)"/) + filename = matches.nil? ? parsed_url.path.split('/').last : matches[1] + basename = SecureRandom.hex(8) + extname = if filename.nil? + '' + else + File.extname(filename) + end + + send("#{attachment_name}=", StringIO.new(response.to_s)) + send("#{attachment_name}_file_name=", basename + extname) + + self[attribute_name] = url if has_attribute?(attribute_name) + end rescue HTTP::TimeoutError, HTTP::ConnectionError, OpenSSL::SSL::SSLError, Paperclip::Errors::NotIdentifiedByImageMagickError, Addressable::URI::InvalidURIError, Mastodon::HostValidationError => e Rails.logger.debug "Error fetching remote #{attachment_name}: #{e}" nil diff --git a/app/services/fetch_atom_service.rb b/app/services/fetch_atom_service.rb index c078598..48ad5dc 100644 --- a/app/services/fetch_atom_service.rb +++ b/app/services/fetch_atom_service.rb @@ -24,43 +24,44 @@ class FetchAtomService < BaseService def process(url, terminal = false) @url = url - perform_request - process_response(terminal) + perform_request { |response| process_response(response, terminal) } end - def perform_request + def perform_request(&block) accept = 'text/html' accept = 'application/activity+json, application/ld+json, application/atom+xml, ' + accept unless @unsupported_activity - @response = Request.new(:get, @url) - .add_headers('Accept' => accept) - .perform + Request.new(:get, @url).add_headers('Accept' => accept).perform(&block) end - def process_response(terminal = false) - return nil if @response.code != 200 + def process_response(response, terminal = false) + return nil if response.code != 200 - if @response.mime_type == 'application/atom+xml' - [@url, { prefetched_body: @response.to_s }, :ostatus] - elsif ['application/activity+json', 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'].include?(@response.mime_type) - json = body_to_json(@response.to_s) + if response.mime_type == 'application/atom+xml' + [@url, { prefetched_body: response.to_s }, :ostatus] + elsif ['application/activity+json', 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'].include?(response.mime_type) + json = body_to_json(response.to_s) if supported_context?(json) && json['type'] == 'Person' && json['inbox'].present? - [json['id'], { prefetched_body: @response.to_s, id: true }, :activitypub] + [json['id'], { prefetched_body: response.to_s, id: true }, :activitypub] elsif supported_context?(json) && json['type'] == 'Note' - [json['id'], { prefetched_body: @response.to_s, id: true }, :activitypub] + [json['id'], { prefetched_body: response.to_s, id: true }, :activitypub] else @unsupported_activity = true nil end - elsif @response['Link'] && !terminal && link_header.find_link(%w(rel alternate)) - process_headers - elsif @response.mime_type == 'text/html' && !terminal - process_html + elsif !terminal + link_header = response['Link'] && parse_link_header(response) + + if link_header&.find_link(%w(rel alternate)) + process_link_headers(link_header) + elsif response.mime_type == 'text/html' + process_html(response) + end end end - def process_html - page = Nokogiri::HTML(@response.to_s) + def process_html(response) + page = Nokogiri::HTML(response.to_s) json_link = page.xpath('//link[@rel="alternate"]').find { |link| ['application/activity+json', 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'].include?(link['type']) } atom_link = page.xpath('//link[@rel="alternate"]').find { |link| link['type'] == 'application/atom+xml' } @@ -71,7 +72,7 @@ class FetchAtomService < BaseService result end - def process_headers + def process_link_headers(link_header) json_link = link_header.find_link(%w(rel alternate), %w(type application/activity+json)) || link_header.find_link(%w(rel alternate), ['type', 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"']) atom_link = link_header.find_link(%w(rel alternate), %w(type application/atom+xml)) @@ -81,7 +82,7 @@ class FetchAtomService < BaseService result end - def link_header - @link_header ||= LinkHeader.parse(@response['Link'].is_a?(Array) ? @response['Link'].first : @response['Link']) + def parse_link_header(response) + LinkHeader.parse(response['Link'].is_a?(Array) ? response['Link'].first : response['Link']) end end diff --git a/app/services/fetch_link_card_service.rb b/app/services/fetch_link_card_service.rb index 8f252e6..26deb5e 100644 --- a/app/services/fetch_link_card_service.rb +++ b/app/services/fetch_link_card_service.rb @@ -36,15 +36,24 @@ class FetchLinkCardService < BaseService def process_url @card ||= PreviewCard.new(url: @url) - res = Request.new(:head, @url).perform - return if res.code != 405 && (res.code != 200 || res.mime_type != 'text/html') + failed = Request.new(:head, @url).perform do |res| + res.code != 405 && (res.code != 200 || res.mime_type != 'text/html') + end - @response = Request.new(:get, @url).perform + return if failed - return if @response.code != 200 || @response.mime_type != 'text/html' + Request.new(:get, @url).perform do |res| + if res.code == 200 && res.mime_type == 'text/html' + @html = res.to_s + @html_charset = res.charset + else + @html = nil + @html_charset = nil + end + end - @html = @response.to_s + return if @html.nil? attempt_oembed || attempt_opengraph end @@ -118,7 +127,7 @@ class FetchLinkCardService < BaseService detector = CharlockHolmes::EncodingDetector.new detector.strip_tags = true - guess = detector.detect(@html, @response.charset) + guess = detector.detect(@html, @html_charset) page = Nokogiri::HTML(@html, nil, guess&.fetch(:encoding, nil)) if meta_property(page, 'twitter:player') diff --git a/app/services/resolve_account_service.rb b/app/services/resolve_account_service.rb index fd6d306..034821d 100644 --- a/app/services/resolve_account_service.rb +++ b/app/services/resolve_account_service.rb @@ -179,11 +179,10 @@ class ResolveAccountService < BaseService def atom_body return @atom_body if defined?(@atom_body) - response = Request.new(:get, atom_url).perform - - raise Mastodon::UnexpectedResponseError, response unless response.code == 200 - - @atom_body = response.to_s + @atom_body = Request.new(:get, atom_url).perform do |response| + raise Mastodon::UnexpectedResponseError, response unless response.code == 200 + response.to_s + end end def actor_json diff --git a/app/services/send_interaction_service.rb b/app/services/send_interaction_service.rb index fabba8a..3419043 100644 --- a/app/services/send_interaction_service.rb +++ b/app/services/send_interaction_service.rb @@ -12,11 +12,9 @@ class SendInteractionService < BaseService return if !target_account.ostatus? || block_notification? - delivery = build_request.perform - - raise Mastodon::UnexpectedResponseError, delivery unless delivery.code > 199 && delivery.code < 300 - - delivery.connection&.close + build_request.perform do |delivery| + raise Mastodon::UnexpectedResponseError, delivery unless delivery.code > 199 && delivery.code < 300 + end end private diff --git a/app/services/subscribe_service.rb b/app/services/subscribe_service.rb index 2f725e2..2893b54 100644 --- a/app/services/subscribe_service.rb +++ b/app/services/subscribe_service.rb @@ -6,21 +6,21 @@ class SubscribeService < BaseService @account = account @account.secret = SecureRandom.hex - @response = build_request.perform - - if response_failed_permanently? - # We're not allowed to subscribe. Fail and move on. - @account.secret = '' - @account.save! - elsif response_successful? - # The subscription will be confirmed asynchronously. - @account.save! - else - # The response was either a 429 rate limit, or a 5xx error. - # We need to retry at a later time. Fail loudly! - raise Mastodon::UnexpectedResponseError, @response + + build_request.perform do |response| + if response_failed_permanently? response + # We're not allowed to subscribe. Fail and move on. + @account.secret = '' + @account.save! + elsif response_successful? response + # The subscription will be confirmed asynchronously. + @account.save! + else + # The response was either a 429 rate limit, or a 5xx error. + # We need to retry at a later time. Fail loudly! + raise Mastodon::UnexpectedResponseError, response + end end - @response.connection&.close end private @@ -47,12 +47,12 @@ class SubscribeService < BaseService end # Any response in the 3xx or 4xx range, except for 429 (rate limit) - def response_failed_permanently? - (@response.status.redirect? || @response.status.client_error?) && !@response.status.too_many_requests? + def response_failed_permanently?(response) + (response.status.redirect? || response.status.client_error?) && !response.status.too_many_requests? end # Any response in the 2xx range - def response_successful? - @response.status.success? + def response_successful?(response) + response.status.success? end end diff --git a/app/services/unsubscribe_service.rb b/app/services/unsubscribe_service.rb index 01f5c6b..95c1fb4 100644 --- a/app/services/unsubscribe_service.rb +++ b/app/services/unsubscribe_service.rb @@ -7,10 +7,9 @@ class UnsubscribeService < BaseService @account = account begin - @response = build_request.perform - - Rails.logger.debug "PuSH unsubscribe for #{@account.acct} failed: #{@response.status}" unless @response.status.success? - @response.connection&.close + build_request.perform do |response| + Rails.logger.debug "PuSH unsubscribe for #{@account.acct} failed: #{response.status}" unless response.status.success? + end rescue HTTP::Error, OpenSSL::SSL::SSLError => e Rails.logger.debug "PuSH unsubscribe for #{@account.acct} failed: #{e}" end diff --git a/app/workers/activitypub/delivery_worker.rb b/app/workers/activitypub/delivery_worker.rb index 4763856..e6cfd0d 100644 --- a/app/workers/activitypub/delivery_worker.rb +++ b/app/workers/activitypub/delivery_worker.rb @@ -12,11 +12,10 @@ class ActivityPub::DeliveryWorker @source_account = Account.find(source_account_id) @inbox_url = inbox_url - perform_request + perform_request do |response| + raise Mastodon::UnexpectedResponseError, response unless response_successful? response + end - raise Mastodon::UnexpectedResponseError, @response unless response_successful? - - @response.connection&.close failure_tracker.track_success! rescue => e failure_tracker.track_failure! @@ -31,12 +30,12 @@ class ActivityPub::DeliveryWorker request.add_headers(HEADERS) end - def perform_request - @response = build_request.perform + def perform_request(&block) + build_request.perform(&block) end - def response_successful? - @response.code > 199 && @response.code < 300 + def response_successful?(response) + response.code > 199 && response.code < 300 end def failure_tracker diff --git a/app/workers/pubsubhubbub/confirmation_worker.rb b/app/workers/pubsubhubbub/confirmation_worker.rb index e1ccfb9..cc2d122 100644 --- a/app/workers/pubsubhubbub/confirmation_worker.rb +++ b/app/workers/pubsubhubbub/confirmation_worker.rb @@ -21,8 +21,8 @@ class Pubsubhubbub::ConfirmationWorker def process_confirmation prepare_subscription - confirm_callback - logger.debug "Confirming PuSH subscription for #{subscription.callback_url} with challenge #{challenge}: #{callback_response_body}" + callback_get_with_params + logger.debug "Confirming PuSH subscription for #{subscription.callback_url} with challenge #{challenge}: #{@callback_response_body}" update_subscription end @@ -44,7 +44,7 @@ class Pubsubhubbub::ConfirmationWorker end def response_matches_challenge? - callback_response_body == challenge + @callback_response_body == challenge end def subscribing? @@ -55,16 +55,10 @@ class Pubsubhubbub::ConfirmationWorker mode == 'unsubscribe' end - def confirm_callback - @_confirm_callback ||= callback_get_with_params - end - def callback_get_with_params - Request.new(:get, subscription.callback_url, params: callback_params).perform - end - - def callback_response_body - confirm_callback.body.to_s + Request.new(:get, subscription.callback_url, params: callback_params).perform do |response| + @callback_response_body = response.body.to_s + end end def callback_params diff --git a/app/workers/pubsubhubbub/delivery_worker.rb b/app/workers/pubsubhubbub/delivery_worker.rb index a9174ed..619bfa4 100644 --- a/app/workers/pubsubhubbub/delivery_worker.rb +++ b/app/workers/pubsubhubbub/delivery_worker.rb @@ -23,22 +23,17 @@ class Pubsubhubbub::DeliveryWorker private def process_delivery - payload_delivery + callback_post_payload do |payload_delivery| + raise Mastodon::UnexpectedResponseError, payload_delivery unless response_successful? payload_delivery + end - raise Mastodon::UnexpectedResponseError, payload_delivery unless response_successful? - - payload_delivery.connection&.close subscription.touch(:last_successful_delivery_at) end - def payload_delivery - @_payload_delivery ||= callback_post_payload - end - - def callback_post_payload + def callback_post_payload(&block) request = Request.new(:post, subscription.callback_url, body: payload) request.add_headers(headers) - request.perform + request.perform(&block) end def blocked_domain? @@ -80,7 +75,7 @@ class Pubsubhubbub::DeliveryWorker OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha1'), subscription.secret, payload) end - def response_successful? + def response_successful?(payload_delivery) payload_delivery.code > 199 && payload_delivery.code < 300 end end diff --git a/lib/tasks/mastodon.rake b/lib/tasks/mastodon.rake index cf32b14..0972e43 100644 --- a/lib/tasks/mastodon.rake +++ b/lib/tasks/mastodon.rake @@ -777,7 +777,7 @@ namespace :mastodon do progress_bar.increment begin - res = Request.new(:head, account.uri).perform + code = Request.new(:head, account.uri).perform(&:code) rescue StandardError # This could happen due to network timeout, DNS timeout, wrong SSL cert, etc, # which should probably not lead to perceiving the account as deleted, so @@ -785,7 +785,7 @@ namespace :mastodon do next end - if [404, 410].include?(res.code) + if [404, 410].include?(code) if options[:force] SuspendAccountService.new.call(account) account.destroy diff --git a/spec/lib/request_spec.rb b/spec/lib/request_spec.rb index 5da357c..4d6b20d 100644 --- a/spec/lib/request_spec.rb +++ b/spec/lib/request_spec.rb @@ -39,12 +39,10 @@ describe Request do describe '#perform' do context 'with valid host' do - before do - stub_request(:get, 'http://example.com') - subject.perform - end + before { stub_request(:get, 'http://example.com') } it 'executes a HTTP request' do + expect { |block| subject.perform &block }.to yield_control expect(a_request(:get, 'http://example.com')).to have_been_made.once end @@ -52,12 +50,20 @@ describe Request do allow(Addrinfo).to receive(:foreach).with('example.com', nil, nil, :SOCK_STREAM) .and_yield(Addrinfo.new(["AF_INET", 0, "example.com", "0.0.0.0"], :PF_INET, :SOCK_STREAM)) .and_yield(Addrinfo.new(["AF_INET6", 0, "example.com", "2001:4860:4860::8844"], :PF_INET6, :SOCK_STREAM)) + + expect { |block| subject.perform &block }.to yield_control expect(a_request(:get, 'http://example.com')).to have_been_made.once end it 'sets headers' do + expect { |block| subject.perform &block }.to yield_control expect(a_request(:get, 'http://example.com').with(headers: subject.headers)).to have_been_made end + + it 'closes underlaying connection' do + expect_any_instance_of(HTTP::Client).to receive(:close) + expect { |block| subject.perform &block }.to yield_control + end end context 'with private host' do From 580835ab698fb116adf26fe4c9c465b2218d124b Mon Sep 17 00:00:00 2001 From: Jeroen Date: Sat, 24 Mar 2018 12:50:14 +0100 Subject: [PATCH 028/381] Invites: Add '1 week' as expire option (#6872) * Invites: Add '1 week' as expire option IMO a max. of 1 day is too short. Not everyone has the time and motivation to use an invite in a 24 hour period. 1 week as a max. is I think a good compromise between convenience and security. * Invites: Add '1 week' as expire option IMO a max. of 1 day is too short. Not everyone has the time and motivation to use an invite in a 24 hour period. 1 week as a max. is I think a good compromise between convenience and security. * Update en.yml --- app/views/invites/_form.html.haml | 2 +- config/locales/en.yml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/views/invites/_form.html.haml b/app/views/invites/_form.html.haml index a01cf59..3f0871f 100644 --- a/app/views/invites/_form.html.haml +++ b/app/views/invites/_form.html.haml @@ -3,7 +3,7 @@ .fields-group = f.input :max_uses, wrapper: :with_label, collection: [1, 5, 10, 25, 50, 100], label_method: lambda { |num| I18n.t('invites.max_uses', count: num) }, prompt: I18n.t('invites.max_uses_prompt') - = f.input :expires_in, wrapper: :with_label, collection: [30.minutes, 1.hour, 6.hours, 12.hours, 1.day].map(&:to_i), label_method: lambda { |i| I18n.t("invites.expires_in.#{i}") }, prompt: I18n.t('invites.expires_in_prompt') + = f.input :expires_in, wrapper: :with_label, collection: [30.minutes, 1.hour, 6.hours, 12.hours, 1.day, 1.week].map(&:to_i), label_method: lambda { |i| I18n.t("invites.expires_in.#{i}") }, prompt: I18n.t('invites.expires_in_prompt') .actions = f.button :button, t('invites.generate'), type: :submit diff --git a/config/locales/en.yml b/config/locales/en.yml index 735a349..995cbda 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -474,6 +474,7 @@ en: '21600': 6 hours '3600': 1 hour '43200': 12 hours + '604800': 1 week '86400': 1 day expires_in_prompt: Never generate: Generate From fa310695fa0b5fe76739232dd6acee81da6cd401 Mon Sep 17 00:00:00 2001 From: Akihiko Odaki Date: Sat, 24 Mar 2018 20:50:41 +0900 Subject: [PATCH 029/381] Note if the user is already following the target when authorizing follow (#6325) --- app/views/authorize_follows/_post_follow_actions.html.haml | 4 ++++ app/views/authorize_follows/show.html.haml | 8 +++++++- app/views/authorize_follows/success.html.haml | 5 +---- config/locales/en.yml | 1 + config/locales/ja.yml | 1 + 5 files changed, 14 insertions(+), 5 deletions(-) create mode 100644 app/views/authorize_follows/_post_follow_actions.html.haml diff --git a/app/views/authorize_follows/_post_follow_actions.html.haml b/app/views/authorize_follows/_post_follow_actions.html.haml new file mode 100644 index 0000000..2a9c062 --- /dev/null +++ b/app/views/authorize_follows/_post_follow_actions.html.haml @@ -0,0 +1,4 @@ +.post-follow-actions + %div= link_to t('authorize_follow.post_follow.web'), web_url("accounts/#{@account.id}"), class: 'button button--block' + %div= link_to t('authorize_follow.post_follow.return'), TagManager.instance.url_for(@account), class: 'button button--block' + %div= t('authorize_follow.post_follow.close') diff --git a/app/views/authorize_follows/show.html.haml b/app/views/authorize_follows/show.html.haml index f7a8f72..a1fd01d 100644 --- a/app/views/authorize_follows/show.html.haml +++ b/app/views/authorize_follows/show.html.haml @@ -5,7 +5,13 @@ .follow-prompt = render 'card', account: @account - - unless current_account.following?(@account) + - if current_account.following?(@account) + .flash-message + %strong + = t('authorize_follow.already_following') + = render 'post_follow_actions' + + - else = form_tag authorize_follow_path, method: :post, class: 'simple_form' do = hidden_field_tag :acct, @account.acct = button_tag t('authorize_follow.follow'), type: :submit diff --git a/app/views/authorize_follows/success.html.haml b/app/views/authorize_follows/success.html.haml index 63ff3bc..fa59b24 100644 --- a/app/views/authorize_follows/success.html.haml +++ b/app/views/authorize_follows/success.html.haml @@ -10,7 +10,4 @@ = render 'card', account: @account - .post-follow-actions - %div= link_to t('authorize_follow.post_follow.web'), web_url("accounts/#{@account.id}"), class: 'button button--block' - %div= link_to t('authorize_follow.post_follow.return'), TagManager.instance.url_for(@account), class: 'button button--block' - %div= t('authorize_follow.post_follow.close') + = render 'post_follow_actions' diff --git a/config/locales/en.yml b/config/locales/en.yml index 995cbda..e3d7697 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -382,6 +382,7 @@ en: security: Security set_new_password: Set new password authorize_follow: + already_following: You are already following this account error: Unfortunately, there was an error looking up the remote account follow: Follow follow_request: 'You have sent a follow request to:' diff --git a/config/locales/ja.yml b/config/locales/ja.yml index 3b19902..1ff3097 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -382,6 +382,7 @@ ja: security: セキュリティ set_new_password: 新しいパスワード authorize_follow: + already_following: あなたは既にこのアカウントをフォローしています error: 残念ながら、リモートアカウント情報の取得中にエラーが発生しました follow: フォロー follow_request: 'あなたは以下のアカウントにフォローリクエストを送信しました:' From b2a4ffd3a91abc5030baf2ede97c0867924d8fbc Mon Sep 17 00:00:00 2001 From: Akihiko Odaki Date: Sat, 24 Mar 2018 20:51:28 +0900 Subject: [PATCH 030/381] Change columns in notifications nonnullable (#6764) --- app/models/notification.rb | 8 ++++---- ...180310000000_change_columns_in_notifications_nonnullable.rb | 8 ++++++++ db/schema.rb | 10 +++++----- spec/fabricators/notification_fabricator.rb | 4 ++-- spec/models/notification_spec.rb | 10 ++++------ 5 files changed, 23 insertions(+), 17 deletions(-) create mode 100644 db/migrate/20180310000000_change_columns_in_notifications_nonnullable.rb diff --git a/app/models/notification.rb b/app/models/notification.rb index 7f8dae5..be99640 100644 --- a/app/models/notification.rb +++ b/app/models/notification.rb @@ -4,12 +4,12 @@ # Table name: notifications # # id :integer not null, primary key -# activity_id :integer -# activity_type :string +# activity_id :integer not null +# activity_type :string not null # created_at :datetime not null # updated_at :datetime not null -# account_id :integer -# from_account_id :integer +# account_id :integer not null +# from_account_id :integer not null # class Notification < ApplicationRecord diff --git a/db/migrate/20180310000000_change_columns_in_notifications_nonnullable.rb b/db/migrate/20180310000000_change_columns_in_notifications_nonnullable.rb new file mode 100644 index 0000000..05ffd05 --- /dev/null +++ b/db/migrate/20180310000000_change_columns_in_notifications_nonnullable.rb @@ -0,0 +1,8 @@ +class ChangeColumnsInNotificationsNonnullable < ActiveRecord::Migration[5.1] + def change + change_column_null :notifications, :activity_id, false + change_column_null :notifications, :activity_type, false + change_column_null :notifications, :account_id, false + change_column_null :notifications, :from_account_id, false + end +end diff --git a/db/schema.rb b/db/schema.rb index c52a6f0..18c61db 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.define(version: 20180304013859) do +ActiveRecord::Schema.define(version: 20180310000000) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -274,12 +274,12 @@ ActiveRecord::Schema.define(version: 20180304013859) do end create_table "notifications", force: :cascade do |t| - t.bigint "activity_id" - t.string "activity_type" + t.bigint "activity_id", null: false + t.string "activity_type", null: false t.datetime "created_at", null: false t.datetime "updated_at", null: false - t.bigint "account_id" - t.bigint "from_account_id" + t.bigint "account_id", null: false + t.bigint "from_account_id", null: false t.index ["account_id", "activity_id", "activity_type"], name: "account_activity", unique: true t.index ["account_id", "id"], name: "index_notifications_on_account_id_and_id", order: { id: :desc } t.index ["activity_id", "activity_type"], name: "index_notifications_on_activity_id_and_activity_type" diff --git a/spec/fabricators/notification_fabricator.rb b/spec/fabricators/notification_fabricator.rb index b92af06..638844e 100644 --- a/spec/fabricators/notification_fabricator.rb +++ b/spec/fabricators/notification_fabricator.rb @@ -1,4 +1,4 @@ Fabricator(:notification) do - activity_id 1 - activity_type 'Favourite' + activity fabricator: [:mention, :status, :follow, :follow_request, :favourite].sample + account end diff --git a/spec/models/notification_spec.rb b/spec/models/notification_spec.rb index 8444c8f..c781f2a 100644 --- a/spec/models/notification_spec.rb +++ b/spec/models/notification_spec.rb @@ -6,14 +6,13 @@ RSpec.describe Notification, type: :model do end describe '#target_status' do - let(:notification) { Fabricate(:notification, activity_type: type, activity: activity) } + let(:notification) { Fabricate(:notification, activity: activity) } let(:status) { Fabricate(:status) } let(:reblog) { Fabricate(:status, reblog: status) } let(:favourite) { Fabricate(:favourite, status: status) } let(:mention) { Fabricate(:mention, status: status) } - context 'type is :reblog' do - let(:type) { :reblog } + context 'activity is reblog' do let(:activity) { reblog } it 'returns status' do @@ -21,7 +20,7 @@ RSpec.describe Notification, type: :model do end end - context 'type is :favourite' do + context 'activity is favourite' do let(:type) { :favourite } let(:activity) { favourite } @@ -30,8 +29,7 @@ RSpec.describe Notification, type: :model do end end - context 'type is :mention' do - let(:type) { :mention } + context 'activity is mention' do let(:activity) { mention } it 'returns status' do From 1c15329cce07adeeb9e2abf670b3eb37e8d36e95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Miko=C5=82ajczak?= Date: Sat, 24 Mar 2018 12:51:51 +0100 Subject: [PATCH 031/381] =?UTF-8?q?Change=20=E2=80=9CToots=20with=20replie?= =?UTF-8?q?s=E2=80=9D=20to=20=E2=80=9CToots=20and=20replies=E2=80=9D=20(#6?= =?UTF-8?q?875)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Marcin Mikołajczak --- .../mastodon/features/account_timeline/components/header.js | 2 +- app/javascript/mastodon/locales/defaultMessages.json | 4 ++-- app/javascript/mastodon/locales/en.json | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/javascript/mastodon/features/account_timeline/components/header.js b/app/javascript/mastodon/features/account_timeline/components/header.js index 9d594fb..6b88a7a 100644 --- a/app/javascript/mastodon/features/account_timeline/components/header.js +++ b/app/javascript/mastodon/features/account_timeline/components/header.js @@ -99,7 +99,7 @@ export default class Header extends ImmutablePureComponent { {!hideTabs && (
- +
)} diff --git a/app/javascript/mastodon/locales/defaultMessages.json b/app/javascript/mastodon/locales/defaultMessages.json index b983823..eee60c5 100644 --- a/app/javascript/mastodon/locales/defaultMessages.json +++ b/app/javascript/mastodon/locales/defaultMessages.json @@ -326,7 +326,7 @@ "id": "account.posts" }, { - "defaultMessage": "Toots with replies", + "defaultMessage": "Toots and replies", "id": "account.posts_with_replies" }, { @@ -1748,4 +1748,4 @@ ], "path": "app/javascript/mastodon/middleware/errors.json" } -] \ No newline at end of file +] diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json index 5553772..de44bd0 100644 --- a/app/javascript/mastodon/locales/en.json +++ b/app/javascript/mastodon/locales/en.json @@ -17,7 +17,7 @@ "account.mute_notifications": "Mute notifications from @{name}", "account.muted": "Muted", "account.posts": "Toots", - "account.posts_with_replies": "Toots with replies", + "account.posts_with_replies": "Toots and replies", "account.report": "Report @{name}", "account.requested": "Awaiting approval. Click to cancel follow request", "account.share": "Share @{name}'s profile", From ff7941e652af1d54d9c991254556e7932a8b183c Mon Sep 17 00:00:00 2001 From: Akihiko Odaki Date: Sat, 24 Mar 2018 20:52:26 +0900 Subject: [PATCH 032/381] Show media modal on public pages (#6801) --- .../mastodon/components/media_gallery.js | 6 +- app/javascript/mastodon/components/modal_root.js | 84 ++++++++++++++++++++++ .../containers/media_galleries_container.js | 68 ++++++++++++++++++ .../mastodon/containers/media_gallery_container.js | 34 --------- .../mastodon/features/ui/components/modal_root.js | 77 +++----------------- app/javascript/packs/public.js | 16 +++-- app/javascript/styles/mastodon/components.scss | 5 +- app/javascript/styles/mastodon/containers.scss | 4 ++ 8 files changed, 178 insertions(+), 116 deletions(-) create mode 100644 app/javascript/mastodon/components/modal_root.js create mode 100644 app/javascript/mastodon/containers/media_galleries_container.js delete mode 100644 app/javascript/mastodon/containers/media_gallery_container.js diff --git a/app/javascript/mastodon/components/media_gallery.js b/app/javascript/mastodon/components/media_gallery.js index 1cef029..13e1fcc 100644 --- a/app/javascript/mastodon/components/media_gallery.js +++ b/app/javascript/mastodon/components/media_gallery.js @@ -14,10 +14,6 @@ const messages = defineMessages({ class Item extends React.PureComponent { - static contextTypes = { - router: PropTypes.object, - }; - static propTypes = { attachment: ImmutablePropTypes.map.isRequired, standalone: PropTypes.bool, @@ -53,7 +49,7 @@ class Item extends React.PureComponent { handleClick = (e) => { const { index, onClick } = this.props; - if (this.context.router && e.button === 0) { + if (e.button === 0) { e.preventDefault(); onClick(index); } diff --git a/app/javascript/mastodon/components/modal_root.js b/app/javascript/mastodon/components/modal_root.js new file mode 100644 index 0000000..114f749 --- /dev/null +++ b/app/javascript/mastodon/components/modal_root.js @@ -0,0 +1,84 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +export default class ModalRoot extends React.PureComponent { + + static propTypes = { + children: PropTypes.node, + onClose: PropTypes.func.isRequired, + }; + + state = { + revealed: !!this.props.children, + }; + + activeElement = this.state.revealed ? document.activeElement : null; + + handleKeyUp = (e) => { + if ((e.key === 'Escape' || e.key === 'Esc' || e.keyCode === 27) + && !!this.props.children) { + this.props.onClose(); + } + } + + componentDidMount () { + window.addEventListener('keyup', this.handleKeyUp, false); + } + + componentWillReceiveProps (nextProps) { + if (!!nextProps.children && !this.props.children) { + this.activeElement = document.activeElement; + + this.getSiblings().forEach(sibling => sibling.setAttribute('inert', true)); + } else if (!nextProps.children) { + this.setState({ revealed: false }); + } + } + + componentDidUpdate (prevProps) { + if (!this.props.children && !!prevProps.children) { + this.getSiblings().forEach(sibling => sibling.removeAttribute('inert')); + this.activeElement.focus(); + this.activeElement = null; + } + if (this.props.children) { + requestAnimationFrame(() => { + this.setState({ revealed: true }); + }); + } + } + + componentWillUnmount () { + window.removeEventListener('keyup', this.handleKeyUp); + } + + getSiblings = () => { + return Array(...this.node.parentElement.childNodes).filter(node => node !== this.node); + } + + setRef = ref => { + this.node = ref; + } + + render () { + const { children, onClose } = this.props; + const { revealed } = this.state; + const visible = !!children; + + if (!visible) { + return ( +
+ ); + } + + return ( +
+
+
+
{children}
+
+
+ ); + } + +} diff --git a/app/javascript/mastodon/containers/media_galleries_container.js b/app/javascript/mastodon/containers/media_galleries_container.js new file mode 100644 index 0000000..d77bd68 --- /dev/null +++ b/app/javascript/mastodon/containers/media_galleries_container.js @@ -0,0 +1,68 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import PropTypes from 'prop-types'; +import { IntlProvider, addLocaleData } from 'react-intl'; +import { getLocale } from '../locales'; +import MediaGallery from '../components/media_gallery'; +import ModalRoot from '../components/modal_root'; +import MediaModal from '../features/ui/components/media_modal'; +import { fromJS } from 'immutable'; + +const { localeData, messages } = getLocale(); +addLocaleData(localeData); + +export default class MediaGalleriesContainer extends React.PureComponent { + + static propTypes = { + locale: PropTypes.string.isRequired, + galleries: PropTypes.object.isRequired, + }; + + state = { + media: null, + index: null, + }; + + handleOpenMedia = (media, index) => { + document.body.classList.add('media-gallery-standalone__body'); + this.setState({ media, index }); + } + + handleCloseMedia = () => { + document.body.classList.remove('media-gallery-standalone__body'); + this.setState({ media: null, index: null }); + } + + render () { + const { locale, galleries } = this.props; + + return ( + + + {[].map.call(galleries, gallery => { + const { media, ...props } = JSON.parse(gallery.getAttribute('data-props')); + + return ReactDOM.createPortal( + , + gallery + ); + })} + + {this.state.media === null || this.state.index === null ? null : ( + + )} + + + + ); + } + +} diff --git a/app/javascript/mastodon/containers/media_gallery_container.js b/app/javascript/mastodon/containers/media_gallery_container.js deleted file mode 100644 index 812c3d4..0000000 --- a/app/javascript/mastodon/containers/media_gallery_container.js +++ /dev/null @@ -1,34 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { IntlProvider, addLocaleData } from 'react-intl'; -import { getLocale } from '../locales'; -import MediaGallery from '../components/media_gallery'; -import { fromJS } from 'immutable'; - -const { localeData, messages } = getLocale(); -addLocaleData(localeData); - -export default class MediaGalleryContainer extends React.PureComponent { - - static propTypes = { - locale: PropTypes.string.isRequired, - media: PropTypes.array.isRequired, - }; - - handleOpenMedia = () => {} - - render () { - const { locale, media, ...props } = this.props; - - return ( - - - - ); - } - -} diff --git a/app/javascript/mastodon/features/ui/components/modal_root.js b/app/javascript/mastodon/features/ui/components/modal_root.js index 20bf211..4185cba 100644 --- a/app/javascript/mastodon/features/ui/components/modal_root.js +++ b/app/javascript/mastodon/features/ui/components/modal_root.js @@ -1,5 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; +import Base from '../../../components/modal_root'; import BundleContainer from '../containers/bundle_container'; import BundleModalError from './bundle_modal_error'; import ModalLoading from './modal_loading'; @@ -39,56 +40,6 @@ export default class ModalRoot extends React.PureComponent { onClose: PropTypes.func.isRequired, }; - state = { - revealed: false, - }; - - handleKeyUp = (e) => { - if ((e.key === 'Escape' || e.key === 'Esc' || e.keyCode === 27) - && !!this.props.type) { - this.props.onClose(); - } - } - - componentDidMount () { - window.addEventListener('keyup', this.handleKeyUp, false); - } - - componentWillReceiveProps (nextProps) { - if (!!nextProps.type && !this.props.type) { - this.activeElement = document.activeElement; - - this.getSiblings().forEach(sibling => sibling.setAttribute('inert', true)); - } else if (!nextProps.type) { - this.setState({ revealed: false }); - } - } - - componentDidUpdate (prevProps) { - if (!this.props.type && !!prevProps.type) { - this.getSiblings().forEach(sibling => sibling.removeAttribute('inert')); - this.activeElement.focus(); - this.activeElement = null; - } - if (this.props.type) { - requestAnimationFrame(() => { - this.setState({ revealed: true }); - }); - } - } - - componentWillUnmount () { - window.removeEventListener('keyup', this.handleKeyUp); - } - - getSiblings = () => { - return Array(...this.node.parentElement.childNodes).filter(node => node !== this.node); - } - - setRef = ref => { - this.node = ref; - } - renderLoading = modalId => () => { return ['MEDIA', 'VIDEO', 'BOOST', 'CONFIRM', 'ACTIONS'].indexOf(modalId) === -1 ? : null; } @@ -101,28 +52,16 @@ export default class ModalRoot extends React.PureComponent { render () { const { type, props, onClose } = this.props; - const { revealed } = this.state; const visible = !!type; - if (!visible) { - return ( -
- ); - } - return ( -
-
-
-
- {visible && ( - - {(SpecificComponent) => } - - )} -
-
-
+ + {visible && ( + + {(SpecificComponent) => } + + )} + ); } diff --git a/app/javascript/packs/public.js b/app/javascript/packs/public.js index a47fc28..7096b9b 100644 --- a/app/javascript/packs/public.js +++ b/app/javascript/packs/public.js @@ -25,7 +25,6 @@ function main() { const { getLocale } = require('../mastodon/locales'); const { localeData } = getLocale(); const VideoContainer = require('../mastodon/containers/video_container').default; - const MediaGalleryContainer = require('../mastodon/containers/media_gallery_container').default; const CardContainer = require('../mastodon/containers/card_container').default; const React = require('react'); const ReactDOM = require('react-dom'); @@ -76,15 +75,20 @@ function main() { ReactDOM.render(, content); }); - [].forEach.call(document.querySelectorAll('[data-component="MediaGallery"]'), (content) => { - const props = JSON.parse(content.getAttribute('data-props')); - ReactDOM.render(, content); - }); - [].forEach.call(document.querySelectorAll('[data-component="Card"]'), (content) => { const props = JSON.parse(content.getAttribute('data-props')); ReactDOM.render(, content); }); + + const mediaGalleries = document.querySelectorAll('[data-component="MediaGallery"]'); + + if (mediaGalleries.length > 0) { + const MediaGalleriesContainer = require('../mastodon/containers/media_galleries_container').default; + const content = document.createElement('div'); + + ReactDOM.render(, content); + document.body.appendChild(content); + } }); delegate(document, '.webapp-btn', 'click', ({ target, button }) => { diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index 20e07a0..ea6e393 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -3375,13 +3375,14 @@ a.status-card { } .modal-root { + position: relative; transition: opacity 0.3s linear; will-change: opacity; z-index: 9999; } .modal-root__overlay { - position: absolute; + position: fixed; top: 0; left: 0; right: 0; @@ -3390,7 +3391,7 @@ a.status-card { } .modal-root__container { - position: absolute; + position: fixed; top: 0; left: 0; width: 100%; diff --git a/app/javascript/styles/mastodon/containers.scss b/app/javascript/styles/mastodon/containers.scss index 6fa1fa3..e761f58 100644 --- a/app/javascript/styles/mastodon/containers.scss +++ b/app/javascript/styles/mastodon/containers.scss @@ -60,6 +60,10 @@ } } +.media-gallery-standalone__body { + overflow: hidden; +} + .account-header { width: 400px; margin: 0 auto; From 28384c1771ccaa600e429f41cb2e19234961a9bd Mon Sep 17 00:00:00 2001 From: Yamagishi Kazutoshi Date: Sat, 24 Mar 2018 20:52:45 +0900 Subject: [PATCH 033/381] Revert "Revert "Upgrade Paperclip to version 6.0.0" (#6807)" (#6808) This reverts commit 40871caa4b06c7ee1c3b07f439ed984ead295ced. --- Gemfile | 4 ++-- Gemfile.lock | 29 ++++++++++++++++++----------- config/initializers/paperclip.rb | 3 +-- 3 files changed, 21 insertions(+), 15 deletions(-) diff --git a/Gemfile b/Gemfile index 8bc28b8..29fa9cd 100644 --- a/Gemfile +++ b/Gemfile @@ -13,11 +13,11 @@ gem 'pg', '~> 0.20' gem 'pghero', '~> 1.7' gem 'dotenv-rails', '~> 2.2' -gem 'aws-sdk', '~> 2.10', require: false +gem 'aws-sdk-s3', '~> 1.8', require: false gem 'fog-core', '~> 1.45' gem 'fog-local', '~> 0.4', require: false gem 'fog-openstack', '~> 0.1', require: false -gem 'paperclip', '~> 5.1' +gem 'paperclip', '~> 6.0' gem 'paperclip-av-transcoder', '~> 0.6' gem 'streamio-ffmpeg', '~> 3.0' diff --git a/Gemfile.lock b/Gemfile.lock index 7360ce7..f68419d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -57,13 +57,18 @@ GEM encryptor (~> 3.0.0) av (0.9.0) cocaine (~> 0.5.3) - aws-sdk (2.10.100) - aws-sdk-resources (= 2.10.100) - aws-sdk-core (2.10.100) + aws-partitions (1.70.0) + aws-sdk-core (3.17.0) + aws-partitions (~> 1.0) aws-sigv4 (~> 1.0) jmespath (~> 1.0) - aws-sdk-resources (2.10.100) - aws-sdk-core (= 2.10.100) + aws-sdk-kms (1.5.0) + aws-sdk-core (~> 3) + aws-sigv4 (~> 1.0) + aws-sdk-s3 (1.8.2) + aws-sdk-core (~> 3) + aws-sdk-kms (~> 1) + aws-sigv4 (~> 1.0) aws-sigv4 (1.0.2) bcrypt (3.1.11) better_errors (2.4.0) @@ -236,7 +241,7 @@ GEM httplog (0.99.7) colorize rack - i18n (0.9.3) + i18n (0.9.5) concurrent-ruby (~> 1.0) i18n-tasks (0.9.19) activesupport (>= 4.0.2) @@ -342,12 +347,12 @@ GEM http (~> 3.0) nokogiri (~> 1.8) ox (2.8.2) - paperclip (5.2.1) + paperclip (6.0.0) activemodel (>= 4.2.0) activesupport (>= 4.2.0) - cocaine (~> 0.5.5) mime-types mimemagic (~> 0.3.0) + terrapin (~> 0.6.0) paperclip-av-transcoder (0.6.4) av (~> 0.9.0) paperclip (>= 2.5.2) @@ -552,6 +557,8 @@ GEM temple (0.8.0) terminal-table (1.8.0) unicode-display_width (~> 1.1, >= 1.1.1) + terrapin (0.6.0) + climate_control (>= 0.0.3, < 1.0) thor (0.20.0) thread (0.2.2) thread_safe (0.3.6) @@ -575,7 +582,7 @@ GEM tty-screen (0.6.4) twitter-text (1.14.7) unf (~> 0.1.0) - tzinfo (1.2.4) + tzinfo (1.2.5) thread_safe (~> 0.1) tzinfo-data (1.2017.3) tzinfo (>= 1.0.0) @@ -612,7 +619,7 @@ DEPENDENCIES active_record_query_trace (~> 1.5) addressable (~> 2.5) annotate (~> 2.7) - aws-sdk (~> 2.10) + aws-sdk-s3 (~> 1.8) better_errors (~> 2.4) binding_of_caller (~> 0.7) bootsnap @@ -671,7 +678,7 @@ DEPENDENCIES omniauth-saml (~> 1.10) ostatus2 (~> 2.0) ox (~> 2.8) - paperclip (~> 5.1) + paperclip (~> 6.0) paperclip-av-transcoder (~> 0.6) parallel_tests (~> 2.17) pg (~> 0.20) diff --git a/config/initializers/paperclip.rb b/config/initializers/paperclip.rb index 8aa1d1b..17a520a 100644 --- a/config/initializers/paperclip.rb +++ b/config/initializers/paperclip.rb @@ -14,8 +14,7 @@ Paperclip::Attachment.default_options.merge!( ) if ENV['S3_ENABLED'] == 'true' - require 'aws-sdk' - Aws.eager_autoload!(services: %w(S3)) + require 'aws-sdk-s3' s3_region = ENV.fetch('S3_REGION') { 'us-east-1' } s3_protocol = ENV.fetch('S3_PROTOCOL') { 'https' } From fe398a098e9990ee3146e70be9e2cda6227274b8 Mon Sep 17 00:00:00 2001 From: Akihiko Odaki Date: Sat, 24 Mar 2018 21:06:27 +0900 Subject: [PATCH 034/381] Store objects to IndexedDB (#6826) --- app/javascript/mastodon/actions/accounts.js | 42 ++++++- app/javascript/mastodon/actions/blocks.js | 3 + app/javascript/mastodon/actions/compose.js | 2 + app/javascript/mastodon/actions/favourites.js | 3 + app/javascript/mastodon/actions/importer/index.js | 76 +++++++++++++ .../mastodon/actions/importer/normalizer.js | 46 ++++++++ app/javascript/mastodon/actions/interactions.js | 39 ++++--- app/javascript/mastodon/actions/lists.js | 14 ++- app/javascript/mastodon/actions/mutes.js | 3 + app/javascript/mastodon/actions/notifications.js | 22 +++- app/javascript/mastodon/actions/pin_statuses.js | 2 + app/javascript/mastodon/actions/search.js | 11 +- app/javascript/mastodon/actions/statuses.js | 65 ++++++++++- app/javascript/mastodon/actions/store.js | 2 + app/javascript/mastodon/actions/timelines.js | 5 + app/javascript/mastodon/db/async.js | 28 +++++ app/javascript/mastodon/db/modifier.js | 93 +++++++++++++++ app/javascript/mastodon/reducers/accounts.js | 125 +-------------------- .../mastodon/reducers/accounts_counters.js | 112 +----------------- app/javascript/mastodon/reducers/statuses.js | 95 ++-------------- 20 files changed, 433 insertions(+), 355 deletions(-) create mode 100644 app/javascript/mastodon/actions/importer/index.js create mode 100644 app/javascript/mastodon/actions/importer/normalizer.js create mode 100644 app/javascript/mastodon/db/async.js create mode 100644 app/javascript/mastodon/db/modifier.js diff --git a/app/javascript/mastodon/actions/accounts.js b/app/javascript/mastodon/actions/accounts.js index f633256..1d1947a 100644 --- a/app/javascript/mastodon/actions/accounts.js +++ b/app/javascript/mastodon/actions/accounts.js @@ -1,4 +1,6 @@ import api, { getLinks } from '../api'; +import asyncDB from '../db/async'; +import { importAccount, importFetchedAccount, importFetchedAccounts } from './importer'; export const ACCOUNT_FETCH_REQUEST = 'ACCOUNT_FETCH_REQUEST'; export const ACCOUNT_FETCH_SUCCESS = 'ACCOUNT_FETCH_SUCCESS'; @@ -64,6 +66,24 @@ export const FOLLOW_REQUEST_REJECT_REQUEST = 'FOLLOW_REQUEST_REJECT_REQUEST'; export const FOLLOW_REQUEST_REJECT_SUCCESS = 'FOLLOW_REQUEST_REJECT_SUCCESS'; export const FOLLOW_REQUEST_REJECT_FAIL = 'FOLLOW_REQUEST_REJECT_FAIL'; +function getFromDB(dispatch, getState, index, id) { + return new Promise((resolve, reject) => { + const request = index.get(id); + + request.onerror = reject; + + request.onsuccess = () => { + if (!request.result) { + reject(); + return; + } + + dispatch(importAccount(request.result)); + resolve(request.result.moved && getFromDB(dispatch, getState, index, request.result.moved)); + }; + }); +} + export function fetchAccount(id) { return (dispatch, getState) => { dispatch(fetchRelationships([id])); @@ -74,9 +94,16 @@ export function fetchAccount(id) { dispatch(fetchAccountRequest(id)); - api(getState).get(`/api/v1/accounts/${id}`).then(response => { - dispatch(fetchAccountSuccess(response.data)); - }).catch(error => { + asyncDB.then(db => getFromDB( + dispatch, + getState, + db.transaction('accounts', 'read').objectStore('accounts').index('id'), + id + )).catch(() => api(getState).get(`/api/v1/accounts/${id}`).then(response => { + dispatch(importFetchedAccount(response.data)); + })).then(() => { + dispatch(fetchAccountSuccess()); + }, error => { dispatch(fetchAccountFail(id, error)); }); }; @@ -89,10 +116,9 @@ export function fetchAccountRequest(id) { }; }; -export function fetchAccountSuccess(account) { +export function fetchAccountSuccess() { return { type: ACCOUNT_FETCH_SUCCESS, - account, }; }; @@ -319,6 +345,7 @@ export function fetchFollowers(id) { api(getState).get(`/api/v1/accounts/${id}/followers`).then(response => { const next = getLinks(response).refs.find(link => link.rel === 'next'); + dispatch(importFetchedAccounts(response.data)); dispatch(fetchFollowersSuccess(id, response.data, next ? next.uri : null)); dispatch(fetchRelationships(response.data.map(item => item.id))); }).catch(error => { @@ -364,6 +391,7 @@ export function expandFollowers(id) { api(getState).get(url).then(response => { const next = getLinks(response).refs.find(link => link.rel === 'next'); + dispatch(importFetchedAccounts(response.data)); dispatch(expandFollowersSuccess(id, response.data, next ? next.uri : null)); dispatch(fetchRelationships(response.data.map(item => item.id))); }).catch(error => { @@ -403,6 +431,7 @@ export function fetchFollowing(id) { api(getState).get(`/api/v1/accounts/${id}/following`).then(response => { const next = getLinks(response).refs.find(link => link.rel === 'next'); + dispatch(importFetchedAccounts(response.data)); dispatch(fetchFollowingSuccess(id, response.data, next ? next.uri : null)); dispatch(fetchRelationships(response.data.map(item => item.id))); }).catch(error => { @@ -448,6 +477,7 @@ export function expandFollowing(id) { api(getState).get(url).then(response => { const next = getLinks(response).refs.find(link => link.rel === 'next'); + dispatch(importFetchedAccounts(response.data)); dispatch(expandFollowingSuccess(id, response.data, next ? next.uri : null)); dispatch(fetchRelationships(response.data.map(item => item.id))); }).catch(error => { @@ -529,6 +559,7 @@ export function fetchFollowRequests() { api(getState).get('/api/v1/follow_requests').then(response => { const next = getLinks(response).refs.find(link => link.rel === 'next'); + dispatch(importFetchedAccounts(response.data)); dispatch(fetchFollowRequestsSuccess(response.data, next ? next.uri : null)); }).catch(error => dispatch(fetchFollowRequestsFail(error))); }; @@ -567,6 +598,7 @@ export function expandFollowRequests() { api(getState).get(url).then(response => { const next = getLinks(response).refs.find(link => link.rel === 'next'); + dispatch(importFetchedAccounts(response.data)); dispatch(expandFollowRequestsSuccess(response.data, next ? next.uri : null)); }).catch(error => dispatch(expandFollowRequestsFail(error))); }; diff --git a/app/javascript/mastodon/actions/blocks.js b/app/javascript/mastodon/actions/blocks.js index 553283a..7000f5a 100644 --- a/app/javascript/mastodon/actions/blocks.js +++ b/app/javascript/mastodon/actions/blocks.js @@ -1,5 +1,6 @@ import api, { getLinks } from '../api'; import { fetchRelationships } from './accounts'; +import { importFetchedAccounts } from './importer'; export const BLOCKS_FETCH_REQUEST = 'BLOCKS_FETCH_REQUEST'; export const BLOCKS_FETCH_SUCCESS = 'BLOCKS_FETCH_SUCCESS'; @@ -15,6 +16,7 @@ export function fetchBlocks() { api(getState).get('/api/v1/blocks').then(response => { const next = getLinks(response).refs.find(link => link.rel === 'next'); + dispatch(importFetchedAccounts(response.data)); dispatch(fetchBlocksSuccess(response.data, next ? next.uri : null)); dispatch(fetchRelationships(response.data.map(item => item.id))); }).catch(error => dispatch(fetchBlocksFail(error))); @@ -54,6 +56,7 @@ export function expandBlocks() { api(getState).get(url).then(response => { const next = getLinks(response).refs.find(link => link.rel === 'next'); + dispatch(importFetchedAccounts(response.data)); dispatch(expandBlocksSuccess(response.data, next ? next.uri : null)); dispatch(fetchRelationships(response.data.map(item => item.id))); }).catch(error => dispatch(expandBlocksFail(error))); diff --git a/app/javascript/mastodon/actions/compose.js b/app/javascript/mastodon/actions/compose.js index 1371f22..8e13209 100644 --- a/app/javascript/mastodon/actions/compose.js +++ b/app/javascript/mastodon/actions/compose.js @@ -4,6 +4,7 @@ import { throttle } from 'lodash'; import { search as emojiSearch } from '../features/emoji/emoji_mart_search_light'; import { tagHistory } from '../settings'; import { useEmoji } from './emojis'; +import { importFetchedAccounts } from './importer'; import { updateTimeline, @@ -282,6 +283,7 @@ const fetchComposeSuggestionsAccounts = throttle((dispatch, getState, token) => limit: 4, }, }).then(response => { + dispatch(importFetchedAccounts(response.data)); dispatch(readyComposeSuggestionsAccounts(token, response.data)); }); }, 200, { leading: true, trailing: true }); diff --git a/app/javascript/mastodon/actions/favourites.js b/app/javascript/mastodon/actions/favourites.js index 93094c5..124cf8c 100644 --- a/app/javascript/mastodon/actions/favourites.js +++ b/app/javascript/mastodon/actions/favourites.js @@ -1,4 +1,5 @@ import api, { getLinks } from '../api'; +import { importFetchedStatuses } from './importer'; export const FAVOURITED_STATUSES_FETCH_REQUEST = 'FAVOURITED_STATUSES_FETCH_REQUEST'; export const FAVOURITED_STATUSES_FETCH_SUCCESS = 'FAVOURITED_STATUSES_FETCH_SUCCESS'; @@ -18,6 +19,7 @@ export function fetchFavouritedStatuses() { api(getState).get('/api/v1/favourites').then(response => { const next = getLinks(response).refs.find(link => link.rel === 'next'); + dispatch(importFetchedStatuses(response.data)); dispatch(fetchFavouritedStatusesSuccess(response.data, next ? next.uri : null)); }).catch(error => { dispatch(fetchFavouritedStatusesFail(error)); @@ -58,6 +60,7 @@ export function expandFavouritedStatuses() { api(getState).get(url).then(response => { const next = getLinks(response).refs.find(link => link.rel === 'next'); + dispatch(importFetchedStatuses(response.data)); dispatch(expandFavouritedStatusesSuccess(response.data, next ? next.uri : null)); }).catch(error => { dispatch(expandFavouritedStatusesFail(error)); diff --git a/app/javascript/mastodon/actions/importer/index.js b/app/javascript/mastodon/actions/importer/index.js new file mode 100644 index 0000000..d1ea40c --- /dev/null +++ b/app/javascript/mastodon/actions/importer/index.js @@ -0,0 +1,76 @@ +import { putAccounts, putStatuses } from '../../db/modifier'; +import { normalizeAccount, normalizeStatus } from './normalizer'; + +export const ACCOUNT_IMPORT = 'ACCOUNT_IMPORT'; +export const ACCOUNTS_IMPORT = 'ACCOUNTS_IMPORT'; +export const STATUS_IMPORT = 'STATUS_IMPORT'; +export const STATUSES_IMPORT = 'STATUSES_IMPORT'; + +function pushUnique(array, object) { + if (array.every(element => element.id !== object.id)) { + array.push(object); + } +} + +export function importAccount(account) { + return { type: ACCOUNT_IMPORT, account }; +} + +export function importAccounts(accounts) { + return { type: ACCOUNTS_IMPORT, accounts }; +} + +export function importStatus(status) { + return { type: STATUS_IMPORT, status }; +} + +export function importStatuses(statuses) { + return { type: STATUSES_IMPORT, statuses }; +} + +export function importFetchedAccount(account) { + return importFetchedAccounts([account]); +} + +export function importFetchedAccounts(accounts) { + const normalAccounts = []; + + function processAccount(account) { + pushUnique(normalAccounts, normalizeAccount(account)); + + if (account.moved) { + processAccount(account); + } + } + + accounts.forEach(processAccount); + putAccounts(normalAccounts); + + return importAccounts(normalAccounts); +} + +export function importFetchedStatus(status) { + return importFetchedStatuses([status]); +} + +export function importFetchedStatuses(statuses) { + return (dispatch, getState) => { + const accounts = []; + const normalStatuses = []; + + function processStatus(status) { + pushUnique(normalStatuses, normalizeStatus(status, getState().getIn(['statuses', status.id]))); + pushUnique(accounts, status.account); + + if (status.reblog && status.reblog.id) { + processStatus(status.reblog); + } + } + + statuses.forEach(processStatus); + putStatuses(normalStatuses); + + dispatch(importFetchedAccounts(accounts)); + dispatch(importStatuses(normalStatuses)); + }; +} diff --git a/app/javascript/mastodon/actions/importer/normalizer.js b/app/javascript/mastodon/actions/importer/normalizer.js new file mode 100644 index 0000000..c88f694 --- /dev/null +++ b/app/javascript/mastodon/actions/importer/normalizer.js @@ -0,0 +1,46 @@ +import escapeTextContentForBrowser from 'escape-html'; +import emojify from '../../features/emoji/emoji'; + +const domParser = new DOMParser(); + +export function normalizeAccount(account) { + account = { ...account }; + + const displayName = account.display_name.length === 0 ? account.username : account.display_name; + account.display_name_html = emojify(escapeTextContentForBrowser(displayName)); + account.note_emojified = emojify(account.note); + + return account; +} + +export function normalizeStatus(status, normalOldStatus) { + const normalStatus = { ...status }; + normalStatus.account = status.account.id; + + if (status.reblog && status.reblog.id) { + normalStatus.reblog = status.reblog.id; + } + + // Only calculate these values when status first encountered + // Otherwise keep the ones already in the reducer + if (normalOldStatus) { + normalStatus.search_index = normalOldStatus.get('search_index'); + normalStatus.contentHtml = normalOldStatus.get('contentHtml'); + normalStatus.spoilerHtml = normalOldStatus.get('spoilerHtml'); + normalStatus.hidden = normalOldStatus.get('hidden'); + } else { + const searchContent = [status.spoiler_text, status.content].join('\n\n').replace(//g, '\n').replace(/<\/p>

/g, '\n\n'); + + const emojiMap = normalStatus.emojis.reduce((obj, emoji) => { + obj[`:${emoji.shortcode}:`] = emoji; + return obj; + }, {}); + + normalStatus.search_index = domParser.parseFromString(searchContent, 'text/html').documentElement.textContent; + normalStatus.contentHtml = emojify(normalStatus.content, emojiMap); + normalStatus.spoilerHtml = emojify(escapeTextContentForBrowser(normalStatus.spoiler_text || ''), emojiMap); + normalStatus.hidden = normalStatus.sensitive; + } + + return normalStatus; +} diff --git a/app/javascript/mastodon/actions/interactions.js b/app/javascript/mastodon/actions/interactions.js index 10e6691..2dc4c57 100644 --- a/app/javascript/mastodon/actions/interactions.js +++ b/app/javascript/mastodon/actions/interactions.js @@ -1,4 +1,5 @@ import api from '../api'; +import { importFetchedAccounts, importFetchedStatus } from './importer'; export const REBLOG_REQUEST = 'REBLOG_REQUEST'; export const REBLOG_SUCCESS = 'REBLOG_SUCCESS'; @@ -39,7 +40,8 @@ export function reblog(status) { api(getState).post(`/api/v1/statuses/${status.get('id')}/reblog`).then(function (response) { // The reblog API method returns a new status wrapped around the original. In this case we are only // interested in how the original is modified, hence passing it skipping the wrapper - dispatch(reblogSuccess(status, response.data.reblog)); + dispatch(importFetchedStatus(response.data.reblog)); + dispatch(reblogSuccess(status)); }).catch(function (error) { dispatch(reblogFail(status, error)); }); @@ -51,7 +53,8 @@ export function unreblog(status) { dispatch(unreblogRequest(status)); api(getState).post(`/api/v1/statuses/${status.get('id')}/unreblog`).then(response => { - dispatch(unreblogSuccess(status, response.data)); + dispatch(importFetchedStatus(response.data)); + dispatch(unreblogSuccess(status)); }).catch(error => { dispatch(unreblogFail(status, error)); }); @@ -66,11 +69,10 @@ export function reblogRequest(status) { }; }; -export function reblogSuccess(status, response) { +export function reblogSuccess(status) { return { type: REBLOG_SUCCESS, status: status, - response: response, skipLoading: true, }; }; @@ -92,11 +94,10 @@ export function unreblogRequest(status) { }; }; -export function unreblogSuccess(status, response) { +export function unreblogSuccess(status) { return { type: UNREBLOG_SUCCESS, status: status, - response: response, skipLoading: true, }; }; @@ -115,7 +116,8 @@ export function favourite(status) { dispatch(favouriteRequest(status)); api(getState).post(`/api/v1/statuses/${status.get('id')}/favourite`).then(function (response) { - dispatch(favouriteSuccess(status, response.data)); + dispatch(importFetchedStatus(response.data)); + dispatch(favouriteSuccess(status)); }).catch(function (error) { dispatch(favouriteFail(status, error)); }); @@ -127,7 +129,8 @@ export function unfavourite(status) { dispatch(unfavouriteRequest(status)); api(getState).post(`/api/v1/statuses/${status.get('id')}/unfavourite`).then(response => { - dispatch(unfavouriteSuccess(status, response.data)); + dispatch(importFetchedStatus(response.data)); + dispatch(unfavouriteSuccess(status)); }).catch(error => { dispatch(unfavouriteFail(status, error)); }); @@ -142,11 +145,10 @@ export function favouriteRequest(status) { }; }; -export function favouriteSuccess(status, response) { +export function favouriteSuccess(status) { return { type: FAVOURITE_SUCCESS, status: status, - response: response, skipLoading: true, }; }; @@ -168,11 +170,10 @@ export function unfavouriteRequest(status) { }; }; -export function unfavouriteSuccess(status, response) { +export function unfavouriteSuccess(status) { return { type: UNFAVOURITE_SUCCESS, status: status, - response: response, skipLoading: true, }; }; @@ -191,6 +192,7 @@ export function fetchReblogs(id) { dispatch(fetchReblogsRequest(id)); api(getState).get(`/api/v1/statuses/${id}/reblogged_by`).then(response => { + dispatch(importFetchedAccounts(response.data)); dispatch(fetchReblogsSuccess(id, response.data)); }).catch(error => { dispatch(fetchReblogsFail(id, error)); @@ -225,6 +227,7 @@ export function fetchFavourites(id) { dispatch(fetchFavouritesRequest(id)); api(getState).get(`/api/v1/statuses/${id}/favourited_by`).then(response => { + dispatch(importFetchedAccounts(response.data)); dispatch(fetchFavouritesSuccess(id, response.data)); }).catch(error => { dispatch(fetchFavouritesFail(id, error)); @@ -259,7 +262,8 @@ export function pin(status) { dispatch(pinRequest(status)); api(getState).post(`/api/v1/statuses/${status.get('id')}/pin`).then(response => { - dispatch(pinSuccess(status, response.data)); + dispatch(importFetchedStatus(response.data)); + dispatch(pinSuccess(status)); }).catch(error => { dispatch(pinFail(status, error)); }); @@ -274,11 +278,10 @@ export function pinRequest(status) { }; }; -export function pinSuccess(status, response) { +export function pinSuccess(status) { return { type: PIN_SUCCESS, status, - response, skipLoading: true, }; }; @@ -297,7 +300,8 @@ export function unpin (status) { dispatch(unpinRequest(status)); api(getState).post(`/api/v1/statuses/${status.get('id')}/unpin`).then(response => { - dispatch(unpinSuccess(status, response.data)); + dispatch(importFetchedStatus(response.data)); + dispatch(unpinSuccess(status)); }).catch(error => { dispatch(unpinFail(status, error)); }); @@ -312,11 +316,10 @@ export function unpinRequest(status) { }; }; -export function unpinSuccess(status, response) { +export function unpinSuccess(status) { return { type: UNPIN_SUCCESS, status, - response, skipLoading: true, }; }; diff --git a/app/javascript/mastodon/actions/lists.js b/app/javascript/mastodon/actions/lists.js index 4c8f9b1..12d60e3 100644 --- a/app/javascript/mastodon/actions/lists.js +++ b/app/javascript/mastodon/actions/lists.js @@ -1,4 +1,5 @@ import api from '../api'; +import { importFetchedAccounts } from './importer'; export const LIST_FETCH_REQUEST = 'LIST_FETCH_REQUEST'; export const LIST_FETCH_SUCCESS = 'LIST_FETCH_SUCCESS'; @@ -200,9 +201,10 @@ export const deleteListFail = (id, error) => ({ export const fetchListAccounts = listId => (dispatch, getState) => { dispatch(fetchListAccountsRequest(listId)); - api(getState).get(`/api/v1/lists/${listId}/accounts`, { params: { limit: 0 } }) - .then(({ data }) => dispatch(fetchListAccountsSuccess(listId, data))) - .catch(err => dispatch(fetchListAccountsFail(listId, err))); + api(getState).get(`/api/v1/lists/${listId}/accounts`, { params: { limit: 0 } }).then(({ data }) => { + dispatch(importFetchedAccounts(data)); + dispatch(fetchListAccountsSuccess(listId, data)); + }).catch(err => dispatch(fetchListAccountsFail(listId, err))); }; export const fetchListAccountsRequest = id => ({ @@ -231,8 +233,10 @@ export const fetchListSuggestions = q => (dispatch, getState) => { following: true, }; - api(getState).get('/api/v1/accounts/search', { params }) - .then(({ data }) => dispatch(fetchListSuggestionsReady(q, data))); + api(getState).get('/api/v1/accounts/search', { params }).then(({ data }) => { + dispatch(importFetchedAccounts(data)); + dispatch(fetchListSuggestionsReady(q, data)); + }); }; export const fetchListSuggestionsReady = (query, accounts) => ({ diff --git a/app/javascript/mastodon/actions/mutes.js b/app/javascript/mastodon/actions/mutes.js index daa76a8..9f645fa 100644 --- a/app/javascript/mastodon/actions/mutes.js +++ b/app/javascript/mastodon/actions/mutes.js @@ -1,5 +1,6 @@ import api, { getLinks } from '../api'; import { fetchRelationships } from './accounts'; +import { importFetchedAccounts } from './importer'; import { openModal } from './modal'; export const MUTES_FETCH_REQUEST = 'MUTES_FETCH_REQUEST'; @@ -19,6 +20,7 @@ export function fetchMutes() { api(getState).get('/api/v1/mutes').then(response => { const next = getLinks(response).refs.find(link => link.rel === 'next'); + dispatch(importFetchedAccounts(response.data)); dispatch(fetchMutesSuccess(response.data, next ? next.uri : null)); dispatch(fetchRelationships(response.data.map(item => item.id))); }).catch(error => dispatch(fetchMutesFail(error))); @@ -58,6 +60,7 @@ export function expandMutes() { api(getState).get(url).then(response => { const next = getLinks(response).refs.find(link => link.rel === 'next'); + dispatch(importFetchedAccounts(response.data)); dispatch(expandMutesSuccess(response.data, next ? next.uri : null)); dispatch(fetchRelationships(response.data.map(item => item.id))); }).catch(error => dispatch(expandMutesFail(error))); diff --git a/app/javascript/mastodon/actions/notifications.js b/app/javascript/mastodon/actions/notifications.js index cf9242d..a664cd9 100644 --- a/app/javascript/mastodon/actions/notifications.js +++ b/app/javascript/mastodon/actions/notifications.js @@ -2,6 +2,12 @@ import api, { getLinks } from '../api'; import { List as ImmutableList } from 'immutable'; import IntlMessageFormat from 'intl-messageformat'; import { fetchRelationships } from './accounts'; +import { + importFetchedAccount, + importFetchedAccounts, + importFetchedStatus, + importFetchedStatuses, +} from './importer'; import { defineMessages } from 'react-intl'; export const NOTIFICATIONS_UPDATE = 'NOTIFICATIONS_UPDATE'; @@ -41,11 +47,12 @@ export function updateNotifications(notification, intlMessages, intlLocale) { const showAlert = getState().getIn(['settings', 'notifications', 'alerts', notification.type], true); const playSound = getState().getIn(['settings', 'notifications', 'sounds', notification.type], true); + dispatch(importFetchedAccount(notification.account)); + dispatch(importFetchedStatus(notification.status)); + dispatch({ type: NOTIFICATIONS_UPDATE, notification, - account: notification.account, - status: notification.status, meta: playSound ? { sound: 'boop' } : undefined, }); @@ -89,6 +96,9 @@ export function refreshNotifications() { api(getState).get('/api/v1/notifications', { params }).then(response => { const next = getLinks(response).refs.find(link => link.rel === 'next'); + dispatch(importFetchedAccounts(response.data.map(item => item.account))); + dispatch(importFetchedStatuses(response.data.map(item => item.status).filter(status => !!status))); + dispatch(refreshNotificationsSuccess(response.data, skipLoading, next ? next.uri : null)); fetchRelatedRelationships(dispatch, response.data); }).catch(error => { @@ -108,8 +118,6 @@ export function refreshNotificationsSuccess(notifications, skipLoading, next) { return { type: NOTIFICATIONS_REFRESH_SUCCESS, notifications, - accounts: notifications.map(item => item.account), - statuses: notifications.map(item => item.status).filter(status => !!status), skipLoading, next, }; @@ -141,6 +149,10 @@ export function expandNotifications() { api(getState).get('/api/v1/notifications', { params }).then(response => { const next = getLinks(response).refs.find(link => link.rel === 'next'); + + dispatch(importFetchedAccounts(response.data.map(item => item.account))); + dispatch(importFetchedStatuses(response.data.map(item => item.status).filter(status => !!status))); + dispatch(expandNotificationsSuccess(response.data, next ? next.uri : null)); fetchRelatedRelationships(dispatch, response.data); }).catch(error => { @@ -159,8 +171,6 @@ export function expandNotificationsSuccess(notifications, next) { return { type: NOTIFICATIONS_EXPAND_SUCCESS, notifications, - accounts: notifications.map(item => item.account), - statuses: notifications.map(item => item.status).filter(status => !!status), next, }; }; diff --git a/app/javascript/mastodon/actions/pin_statuses.js b/app/javascript/mastodon/actions/pin_statuses.js index 3f40f6c..77abba7 100644 --- a/app/javascript/mastodon/actions/pin_statuses.js +++ b/app/javascript/mastodon/actions/pin_statuses.js @@ -1,4 +1,5 @@ import api from '../api'; +import { importFetchedStatuses } from './importer'; export const PINNED_STATUSES_FETCH_REQUEST = 'PINNED_STATUSES_FETCH_REQUEST'; export const PINNED_STATUSES_FETCH_SUCCESS = 'PINNED_STATUSES_FETCH_SUCCESS'; @@ -11,6 +12,7 @@ export function fetchPinnedStatuses() { dispatch(fetchPinnedStatusesRequest()); api(getState).get(`/api/v1/accounts/${me}/statuses`, { params: { pinned: true } }).then(response => { + dispatch(importFetchedStatuses(response.data)); dispatch(fetchPinnedStatusesSuccess(response.data, null)); }).catch(error => { dispatch(fetchPinnedStatusesFail(error)); diff --git a/app/javascript/mastodon/actions/search.js b/app/javascript/mastodon/actions/search.js index 73cb106..882c170 100644 --- a/app/javascript/mastodon/actions/search.js +++ b/app/javascript/mastodon/actions/search.js @@ -1,5 +1,6 @@ import api from '../api'; import { fetchRelationships } from './accounts'; +import { importFetchedAccounts, importFetchedStatuses } from './importer'; export const SEARCH_CHANGE = 'SEARCH_CHANGE'; export const SEARCH_CLEAR = 'SEARCH_CLEAR'; @@ -38,6 +39,14 @@ export function submitSearch() { resolve: true, }, }).then(response => { + if (response.data.accounts) { + dispatch(importFetchedAccounts(response.data.accounts)); + } + + if (response.data.statuses) { + dispatch(importFetchedStatuses(response.data.statuses)); + } + dispatch(fetchSearchSuccess(response.data)); dispatch(fetchRelationships(response.data.accounts.map(item => item.id))); }).catch(error => { @@ -56,8 +65,6 @@ export function fetchSearchSuccess(results) { return { type: SEARCH_FETCH_SUCCESS, results, - accounts: results.accounts, - statuses: results.statuses, }; }; diff --git a/app/javascript/mastodon/actions/statuses.js b/app/javascript/mastodon/actions/statuses.js index 073f098..dcd813d 100644 --- a/app/javascript/mastodon/actions/statuses.js +++ b/app/javascript/mastodon/actions/statuses.js @@ -1,7 +1,10 @@ import api from '../api'; +import asyncDB from '../db/async'; +import { evictStatus } from '../db/modifier'; import { deleteFromTimelines } from './timelines'; import { fetchStatusCard } from './cards'; +import { importFetchedStatus, importFetchedStatuses, importAccount, importStatus } from './importer'; export const STATUS_FETCH_REQUEST = 'STATUS_FETCH_REQUEST'; export const STATUS_FETCH_SUCCESS = 'STATUS_FETCH_SUCCESS'; @@ -34,6 +37,48 @@ export function fetchStatusRequest(id, skipLoading) { }; }; +function getFromDB(dispatch, getState, accountIndex, index, id) { + return new Promise((resolve, reject) => { + const request = index.get(id); + + request.onerror = reject; + + request.onsuccess = () => { + const promises = []; + + if (!request.result) { + reject(); + return; + } + + dispatch(importStatus(request.result)); + + if (getState().getIn(['accounts', request.result.account], null) === null) { + promises.push(new Promise((accountResolve, accountReject) => { + const accountRequest = accountIndex.get(request.result.account); + + accountRequest.onerror = accountReject; + accountRequest.onsuccess = () => { + if (!request.result) { + accountReject(); + return; + } + + dispatch(importAccount(accountRequest.result)); + accountResolve(); + }; + })); + } + + if (request.result.reblog && getState().getIn(['statuses', request.result.reblog], null) === null) { + promises.push(getFromDB(dispatch, getState, accountIndex, index, request.result.reblog)); + } + + resolve(Promise.all(promises)); + }; + }); +} + export function fetchStatus(id) { return (dispatch, getState) => { const skipLoading = getState().getIn(['statuses', id], null) !== null; @@ -47,18 +92,26 @@ export function fetchStatus(id) { dispatch(fetchStatusRequest(id, skipLoading)); - api(getState).get(`/api/v1/statuses/${id}`).then(response => { - dispatch(fetchStatusSuccess(response.data, skipLoading)); - }).catch(error => { + asyncDB.then(db => { + const transaction = db.transaction(['accounts', 'statuses'], 'read'); + const accountIndex = transaction.objectStore('accounts').index('id'); + const index = transaction.objectStore('statuses').index('id'); + + return getFromDB(dispatch, getState, accountIndex, index, id); + }).then(() => { + dispatch(fetchStatusSuccess(skipLoading)); + }, () => api(getState).get(`/api/v1/statuses/${id}`).then(response => { + dispatch(importFetchedStatus(response.data)); + dispatch(fetchStatusSuccess(skipLoading)); + })).catch(error => { dispatch(fetchStatusFail(id, error, skipLoading)); }); }; }; -export function fetchStatusSuccess(status, skipLoading) { +export function fetchStatusSuccess(skipLoading) { return { type: STATUS_FETCH_SUCCESS, - status, skipLoading, }; }; @@ -78,6 +131,7 @@ export function deleteStatus(id) { dispatch(deleteStatusRequest(id)); api(getState).delete(`/api/v1/statuses/${id}`).then(() => { + evictStatus(id); dispatch(deleteStatusSuccess(id)); dispatch(deleteFromTimelines(id)); }).catch(error => { @@ -113,6 +167,7 @@ export function fetchContext(id) { dispatch(fetchContextRequest(id)); api(getState).get(`/api/v1/statuses/${id}/context`).then(response => { + dispatch(importFetchedStatuses(response.data.ancestors.concat(response.data.descendants))); dispatch(fetchContextSuccess(id, response.data.ancestors, response.data.descendants)); }).catch(error => { diff --git a/app/javascript/mastodon/actions/store.js b/app/javascript/mastodon/actions/store.js index 2dd94a9..34dcafc 100644 --- a/app/javascript/mastodon/actions/store.js +++ b/app/javascript/mastodon/actions/store.js @@ -1,5 +1,6 @@ import { Iterable, fromJS } from 'immutable'; import { hydrateCompose } from './compose'; +import { importFetchedAccounts } from './importer'; export const STORE_HYDRATE = 'STORE_HYDRATE'; export const STORE_HYDRATE_LAZY = 'STORE_HYDRATE_LAZY'; @@ -18,5 +19,6 @@ export function hydrateStore(rawState) { }); dispatch(hydrateCompose()); + dispatch(importFetchedAccounts(Object.values(rawState.accounts))); }; }; diff --git a/app/javascript/mastodon/actions/timelines.js b/app/javascript/mastodon/actions/timelines.js index f0ab16a..e5748b4 100644 --- a/app/javascript/mastodon/actions/timelines.js +++ b/app/javascript/mastodon/actions/timelines.js @@ -1,3 +1,4 @@ +import { importFetchedStatus, importFetchedStatuses } from './importer'; import api, { getLinks } from '../api'; import { Map as ImmutableMap, List as ImmutableList } from 'immutable'; @@ -44,6 +45,8 @@ export function updateTimeline(timeline, status) { } } + dispatch(importFetchedStatus(status)); + dispatch({ type: TIMELINE_UPDATE, timeline, @@ -109,6 +112,7 @@ export function refreshTimeline(timelineId, path, params = {}) { dispatch(refreshTimelineSuccess(timelineId, [], skipLoading, null, true)); } else { const next = getLinks(response).refs.find(link => link.rel === 'next'); + dispatch(importFetchedStatuses(response.data)); dispatch(refreshTimelineSuccess(timelineId, response.data, skipLoading, next ? next.uri : null, false)); } }).catch(error => { @@ -152,6 +156,7 @@ export function expandTimeline(timelineId, path, params = {}) { api(getState).get(path, { params }).then(response => { const next = getLinks(response).refs.find(link => link.rel === 'next'); + dispatch(importFetchedStatuses(response.data)); dispatch(expandTimelineSuccess(timelineId, response.data, next ? next.uri : null)); }).catch(error => { dispatch(expandTimelineFail(timelineId, error)); diff --git a/app/javascript/mastodon/db/async.js b/app/javascript/mastodon/db/async.js new file mode 100644 index 0000000..e08fc3f --- /dev/null +++ b/app/javascript/mastodon/db/async.js @@ -0,0 +1,28 @@ +import { me } from '../initial_state'; + +export default new Promise((resolve, reject) => { + // Microsoft Edge 17 does not support getAll according to: + // Catalog of standard and vendor APIs across browsers - Microsoft Edge Development + // https://developer.microsoft.com/en-us/microsoft-edge/platform/catalog/?q=specName%3Aindexeddb + if (!me || !('getAll' in IDBObjectStore.prototype)) { + reject(); + return; + } + + const request = indexedDB.open('mastodon:' + me); + + request.onerror = reject; + request.onsuccess = ({ target }) => resolve(target.result); + + request.onupgradeneeded = ({ target }) => { + const accounts = target.result.createObjectStore('accounts', { autoIncrement: true }); + const statuses = target.result.createObjectStore('statuses', { autoIncrement: true }); + + accounts.createIndex('id', 'id', { unique: true }); + accounts.createIndex('moved', 'moved'); + + statuses.createIndex('id', 'id', { unique: true }); + statuses.createIndex('account', 'account'); + statuses.createIndex('reblog', 'reblog'); + }; +}); diff --git a/app/javascript/mastodon/db/modifier.js b/app/javascript/mastodon/db/modifier.js new file mode 100644 index 0000000..eb95190 --- /dev/null +++ b/app/javascript/mastodon/db/modifier.js @@ -0,0 +1,93 @@ +import asyncDB from './async'; + +const limit = 1024; + +function put(name, objects, callback) { + asyncDB.then(db => { + const putTransaction = db.transaction(name, 'readwrite'); + const putStore = putTransaction.objectStore(name); + const putIndex = putStore.index('id'); + + objects.forEach(object => { + function add() { + putStore.add(object); + } + + putIndex.getKey(object.id).onsuccess = retrieval => { + if (retrieval.target.result) { + putStore.delete(retrieval.target.result).onsuccess = add; + } else { + add(); + } + }; + }); + + putTransaction.oncomplete = () => { + const readTransaction = db.transaction(name, 'readonly'); + const readStore = readTransaction.objectStore(name); + + readStore.count().onsuccess = count => { + const excess = count.target.result - limit; + + if (excess > 0) { + readStore.getAll(null, excess).onsuccess = + retrieval => callback(retrieval.target.result.map(({ id }) => id)); + } + }; + }; + }); +} + +export function evictAccounts(ids) { + asyncDB.then(db => { + const transaction = db.transaction(['accounts', 'statuses'], 'readwrite'); + const accounts = transaction.objectStore('accounts'); + const accountsIdIndex = accounts.index('id'); + const accountsMovedIndex = accounts.index('moved'); + const statuses = transaction.objectStore('statuses'); + const statusesIndex = statuses.index('account'); + + function evict(toEvict) { + toEvict.forEach(id => { + accountsMovedIndex.getAllKeys(id).onsuccess = + ({ target }) => evict(target.result); + + statusesIndex.getAll(id).onsuccess = + ({ target }) => evictStatuses(target.result.map(({ id }) => id)); + + accountsIdIndex.getKey(id).onsuccess = + ({ target }) => target.result && accounts.delete(target.result); + }); + } + + evict(ids); + }); +} + +export function evictStatus(id) { + return evictStatuses([id]); +} + +export function evictStatuses(ids) { + asyncDB.then(db => { + const store = db.transaction('statuses', 'readwrite').objectStore('statuses'); + const idIndex = store.index('id'); + const reblogIndex = store.index('reblog'); + + ids.forEach(id => { + reblogIndex.getAllKeys(id).onsuccess = + ({ target }) => target.result.forEach(reblogKey => store.delete(reblogKey)); + + idIndex.getKey(id).onsuccess = + ({ target }) => target.result && store.delete(target.result); + }); + }); +} + +export function putAccounts(records) { + put('accounts', records, evictAccounts); +} + +export function putStatuses(records) { + put('statuses', records, evictStatuses); +} diff --git a/app/javascript/mastodon/reducers/accounts.js b/app/javascript/mastodon/reducers/accounts.js index 47e6d23..530ed8e 100644 --- a/app/javascript/mastodon/reducers/accounts.js +++ b/app/javascript/mastodon/reducers/accounts.js @@ -1,56 +1,7 @@ -import { - ACCOUNT_FETCH_SUCCESS, - FOLLOWERS_FETCH_SUCCESS, - FOLLOWERS_EXPAND_SUCCESS, - FOLLOWING_FETCH_SUCCESS, - FOLLOWING_EXPAND_SUCCESS, - FOLLOW_REQUESTS_FETCH_SUCCESS, - FOLLOW_REQUESTS_EXPAND_SUCCESS, -} from '../actions/accounts'; -import { - BLOCKS_FETCH_SUCCESS, - BLOCKS_EXPAND_SUCCESS, -} from '../actions/blocks'; -import { - MUTES_FETCH_SUCCESS, - MUTES_EXPAND_SUCCESS, -} from '../actions/mutes'; -import { COMPOSE_SUGGESTIONS_READY } from '../actions/compose'; -import { - REBLOG_SUCCESS, - UNREBLOG_SUCCESS, - FAVOURITE_SUCCESS, - UNFAVOURITE_SUCCESS, - REBLOGS_FETCH_SUCCESS, - FAVOURITES_FETCH_SUCCESS, -} from '../actions/interactions'; -import { - TIMELINE_REFRESH_SUCCESS, - TIMELINE_UPDATE, - TIMELINE_EXPAND_SUCCESS, -} from '../actions/timelines'; -import { - STATUS_FETCH_SUCCESS, - CONTEXT_FETCH_SUCCESS, -} from '../actions/statuses'; -import { SEARCH_FETCH_SUCCESS } from '../actions/search'; -import { - NOTIFICATIONS_UPDATE, - NOTIFICATIONS_REFRESH_SUCCESS, - NOTIFICATIONS_EXPAND_SUCCESS, -} from '../actions/notifications'; -import { - FAVOURITED_STATUSES_FETCH_SUCCESS, - FAVOURITED_STATUSES_EXPAND_SUCCESS, -} from '../actions/favourites'; -import { - LIST_ACCOUNTS_FETCH_SUCCESS, - LIST_EDITOR_SUGGESTIONS_READY, -} from '../actions/lists'; -import { STORE_HYDRATE } from '../actions/store'; -import emojify from '../features/emoji/emoji'; +import { ACCOUNT_IMPORT, ACCOUNTS_IMPORT } from '../actions/importer'; import { Map as ImmutableMap, fromJS } from 'immutable'; -import escapeTextContentForBrowser from 'escape-html'; + +const initialState = ImmutableMap(); const normalizeAccount = (state, account) => { account = { ...account }; @@ -59,15 +10,6 @@ const normalizeAccount = (state, account) => { delete account.following_count; delete account.statuses_count; - const displayName = account.display_name.length === 0 ? account.username : account.display_name; - account.display_name_html = emojify(escapeTextContentForBrowser(displayName)); - account.note_emojified = emojify(account.note); - - if (account.moved) { - state = normalizeAccount(state, account.moved); - account.moved = account.moved.id; - } - return state.set(account.id, fromJS(account)); }; @@ -79,67 +21,12 @@ const normalizeAccounts = (state, accounts) => { return state; }; -const normalizeAccountFromStatus = (state, status) => { - state = normalizeAccount(state, status.account); - - if (status.reblog && status.reblog.account) { - state = normalizeAccount(state, status.reblog.account); - } - - return state; -}; - -const normalizeAccountsFromStatuses = (state, statuses) => { - statuses.forEach(status => { - state = normalizeAccountFromStatus(state, status); - }); - - return state; -}; - -const initialState = ImmutableMap(); - export default function accounts(state = initialState, action) { switch(action.type) { - case STORE_HYDRATE: - return normalizeAccounts(state, Object.values(action.state.get('accounts').toJS())); - case ACCOUNT_FETCH_SUCCESS: - case NOTIFICATIONS_UPDATE: + case ACCOUNT_IMPORT: return normalizeAccount(state, action.account); - case FOLLOWERS_FETCH_SUCCESS: - case FOLLOWERS_EXPAND_SUCCESS: - case FOLLOWING_FETCH_SUCCESS: - case FOLLOWING_EXPAND_SUCCESS: - case REBLOGS_FETCH_SUCCESS: - case FAVOURITES_FETCH_SUCCESS: - case COMPOSE_SUGGESTIONS_READY: - case FOLLOW_REQUESTS_FETCH_SUCCESS: - case FOLLOW_REQUESTS_EXPAND_SUCCESS: - case BLOCKS_FETCH_SUCCESS: - case BLOCKS_EXPAND_SUCCESS: - case MUTES_FETCH_SUCCESS: - case MUTES_EXPAND_SUCCESS: - case LIST_ACCOUNTS_FETCH_SUCCESS: - case LIST_EDITOR_SUGGESTIONS_READY: - return action.accounts ? normalizeAccounts(state, action.accounts) : state; - case NOTIFICATIONS_REFRESH_SUCCESS: - case NOTIFICATIONS_EXPAND_SUCCESS: - case SEARCH_FETCH_SUCCESS: - return normalizeAccountsFromStatuses(normalizeAccounts(state, action.accounts), action.statuses); - case TIMELINE_REFRESH_SUCCESS: - case TIMELINE_EXPAND_SUCCESS: - case CONTEXT_FETCH_SUCCESS: - case FAVOURITED_STATUSES_FETCH_SUCCESS: - case FAVOURITED_STATUSES_EXPAND_SUCCESS: - return normalizeAccountsFromStatuses(state, action.statuses); - case REBLOG_SUCCESS: - case FAVOURITE_SUCCESS: - case UNREBLOG_SUCCESS: - case UNFAVOURITE_SUCCESS: - return normalizeAccountFromStatus(state, action.response); - case TIMELINE_UPDATE: - case STATUS_FETCH_SUCCESS: - return normalizeAccountFromStatus(state, action.status); + case ACCOUNTS_IMPORT: + return normalizeAccounts(state, action.accounts); default: return state; } diff --git a/app/javascript/mastodon/reducers/accounts_counters.js b/app/javascript/mastodon/reducers/accounts_counters.js index a93fa42..9ebf72a 100644 --- a/app/javascript/mastodon/reducers/accounts_counters.js +++ b/app/javascript/mastodon/reducers/accounts_counters.js @@ -1,55 +1,8 @@ import { - ACCOUNT_FETCH_SUCCESS, - FOLLOWERS_FETCH_SUCCESS, - FOLLOWERS_EXPAND_SUCCESS, - FOLLOWING_FETCH_SUCCESS, - FOLLOWING_EXPAND_SUCCESS, - FOLLOW_REQUESTS_FETCH_SUCCESS, - FOLLOW_REQUESTS_EXPAND_SUCCESS, ACCOUNT_FOLLOW_SUCCESS, ACCOUNT_UNFOLLOW_SUCCESS, } from '../actions/accounts'; -import { - BLOCKS_FETCH_SUCCESS, - BLOCKS_EXPAND_SUCCESS, -} from '../actions/blocks'; -import { - MUTES_FETCH_SUCCESS, - MUTES_EXPAND_SUCCESS, -} from '../actions/mutes'; -import { COMPOSE_SUGGESTIONS_READY } from '../actions/compose'; -import { - REBLOG_SUCCESS, - UNREBLOG_SUCCESS, - FAVOURITE_SUCCESS, - UNFAVOURITE_SUCCESS, - REBLOGS_FETCH_SUCCESS, - FAVOURITES_FETCH_SUCCESS, -} from '../actions/interactions'; -import { - TIMELINE_REFRESH_SUCCESS, - TIMELINE_UPDATE, - TIMELINE_EXPAND_SUCCESS, -} from '../actions/timelines'; -import { - STATUS_FETCH_SUCCESS, - CONTEXT_FETCH_SUCCESS, -} from '../actions/statuses'; -import { SEARCH_FETCH_SUCCESS } from '../actions/search'; -import { - NOTIFICATIONS_UPDATE, - NOTIFICATIONS_REFRESH_SUCCESS, - NOTIFICATIONS_EXPAND_SUCCESS, -} from '../actions/notifications'; -import { - FAVOURITED_STATUSES_FETCH_SUCCESS, - FAVOURITED_STATUSES_EXPAND_SUCCESS, -} from '../actions/favourites'; -import { - LIST_ACCOUNTS_FETCH_SUCCESS, - LIST_EDITOR_SUGGESTIONS_READY, -} from '../actions/lists'; -import { STORE_HYDRATE } from '../actions/store'; +import { ACCOUNT_IMPORT, ACCOUNTS_IMPORT } from '../actions/importer'; import { Map as ImmutableMap, fromJS } from 'immutable'; const normalizeAccount = (state, account) => state.set(account.id, fromJS({ @@ -66,71 +19,14 @@ const normalizeAccounts = (state, accounts) => { return state; }; -const normalizeAccountFromStatus = (state, status) => { - state = normalizeAccount(state, status.account); - - if (status.reblog && status.reblog.account) { - state = normalizeAccount(state, status.reblog.account); - } - - return state; -}; - -const normalizeAccountsFromStatuses = (state, statuses) => { - statuses.forEach(status => { - state = normalizeAccountFromStatus(state, status); - }); - - return state; -}; - const initialState = ImmutableMap(); export default function accountsCounters(state = initialState, action) { switch(action.type) { - case STORE_HYDRATE: - return state.merge(action.state.get('accounts').map(item => fromJS({ - followers_count: item.get('followers_count'), - following_count: item.get('following_count'), - statuses_count: item.get('statuses_count'), - }))); - case ACCOUNT_FETCH_SUCCESS: - case NOTIFICATIONS_UPDATE: + case ACCOUNT_IMPORT: return normalizeAccount(state, action.account); - case FOLLOWERS_FETCH_SUCCESS: - case FOLLOWERS_EXPAND_SUCCESS: - case FOLLOWING_FETCH_SUCCESS: - case FOLLOWING_EXPAND_SUCCESS: - case REBLOGS_FETCH_SUCCESS: - case FAVOURITES_FETCH_SUCCESS: - case COMPOSE_SUGGESTIONS_READY: - case FOLLOW_REQUESTS_FETCH_SUCCESS: - case FOLLOW_REQUESTS_EXPAND_SUCCESS: - case BLOCKS_FETCH_SUCCESS: - case BLOCKS_EXPAND_SUCCESS: - case MUTES_FETCH_SUCCESS: - case MUTES_EXPAND_SUCCESS: - case LIST_ACCOUNTS_FETCH_SUCCESS: - case LIST_EDITOR_SUGGESTIONS_READY: - return action.accounts ? normalizeAccounts(state, action.accounts) : state; - case NOTIFICATIONS_REFRESH_SUCCESS: - case NOTIFICATIONS_EXPAND_SUCCESS: - case SEARCH_FETCH_SUCCESS: - return normalizeAccountsFromStatuses(normalizeAccounts(state, action.accounts), action.statuses); - case TIMELINE_REFRESH_SUCCESS: - case TIMELINE_EXPAND_SUCCESS: - case CONTEXT_FETCH_SUCCESS: - case FAVOURITED_STATUSES_FETCH_SUCCESS: - case FAVOURITED_STATUSES_EXPAND_SUCCESS: - return normalizeAccountsFromStatuses(state, action.statuses); - case REBLOG_SUCCESS: - case FAVOURITE_SUCCESS: - case UNREBLOG_SUCCESS: - case UNFAVOURITE_SUCCESS: - return normalizeAccountFromStatus(state, action.response); - case TIMELINE_UPDATE: - case STATUS_FETCH_SUCCESS: - return normalizeAccountFromStatus(state, action.status); + case ACCOUNTS_IMPORT: + return normalizeAccounts(state, action.accounts); case ACCOUNT_FOLLOW_SUCCESS: return action.alreadyFollowing ? state : state.updateIn([action.relationship.id, 'followers_count'], num => num + 1); diff --git a/app/javascript/mastodon/reducers/statuses.js b/app/javascript/mastodon/reducers/statuses.js index 7b31416..fc4b490 100644 --- a/app/javascript/mastodon/reducers/statuses.js +++ b/app/javascript/mastodon/reducers/statuses.js @@ -1,87 +1,25 @@ import { REBLOG_REQUEST, - REBLOG_SUCCESS, REBLOG_FAIL, - UNREBLOG_SUCCESS, FAVOURITE_REQUEST, - FAVOURITE_SUCCESS, FAVOURITE_FAIL, - UNFAVOURITE_SUCCESS, - PIN_SUCCESS, - UNPIN_SUCCESS, } from '../actions/interactions'; import { - STATUS_FETCH_SUCCESS, - CONTEXT_FETCH_SUCCESS, STATUS_MUTE_SUCCESS, STATUS_UNMUTE_SUCCESS, STATUS_REVEAL, STATUS_HIDE, } from '../actions/statuses'; import { - TIMELINE_REFRESH_SUCCESS, - TIMELINE_UPDATE, TIMELINE_DELETE, - TIMELINE_EXPAND_SUCCESS, } from '../actions/timelines'; -import { - NOTIFICATIONS_UPDATE, - NOTIFICATIONS_REFRESH_SUCCESS, - NOTIFICATIONS_EXPAND_SUCCESS, -} from '../actions/notifications'; -import { - FAVOURITED_STATUSES_FETCH_SUCCESS, - FAVOURITED_STATUSES_EXPAND_SUCCESS, -} from '../actions/favourites'; -import { - PINNED_STATUSES_FETCH_SUCCESS, -} from '../actions/pin_statuses'; -import { SEARCH_FETCH_SUCCESS } from '../actions/search'; -import emojify from '../features/emoji/emoji'; +import { STATUS_IMPORT, STATUSES_IMPORT } from '../actions/importer'; import { Map as ImmutableMap, fromJS } from 'immutable'; -import escapeTextContentForBrowser from 'escape-html'; - -const domParser = new DOMParser(); - -const normalizeStatus = (state, status) => { - if (!status) { - return state; - } - - const normalStatus = { ...status }; - normalStatus.account = status.account.id; - - if (status.reblog && status.reblog.id) { - state = normalizeStatus(state, status.reblog); - normalStatus.reblog = status.reblog.id; - } - - // Only calculate these values when status first encountered - // Otherwise keep the ones already in the reducer - if (!state.has(status.id)) { - const searchContent = [status.spoiler_text, status.content].join('\n\n').replace(//g, '\n').replace(/<\/p>

/g, '\n\n'); - const emojiMap = normalStatus.emojis.reduce((obj, emoji) => { - obj[`:${emoji.shortcode}:`] = emoji; - return obj; - }, {}); +const importStatus = (state, status) => state.set(status.id, fromJS(status)); - normalStatus.search_index = domParser.parseFromString(searchContent, 'text/html').documentElement.textContent; - normalStatus.contentHtml = emojify(normalStatus.content, emojiMap); - normalStatus.spoilerHtml = emojify(escapeTextContentForBrowser(normalStatus.spoiler_text || ''), emojiMap); - normalStatus.hidden = normalStatus.sensitive; - } - - return state.update(status.id, ImmutableMap(), map => map.mergeDeep(fromJS(normalStatus))); -}; - -const normalizeStatuses = (state, statuses) => { - statuses.forEach(status => { - state = normalizeStatus(state, status); - }); - - return state; -}; +const importStatuses = (state, statuses) => + state.withMutations(mutable => statuses.forEach(status => importStatus(mutable, status))); const deleteStatus = (state, id, references) => { references.forEach(ref => { @@ -95,17 +33,10 @@ const initialState = ImmutableMap(); export default function statuses(state = initialState, action) { switch(action.type) { - case TIMELINE_UPDATE: - case STATUS_FETCH_SUCCESS: - case NOTIFICATIONS_UPDATE: - return normalizeStatus(state, action.status); - case REBLOG_SUCCESS: - case UNREBLOG_SUCCESS: - case FAVOURITE_SUCCESS: - case UNFAVOURITE_SUCCESS: - case PIN_SUCCESS: - case UNPIN_SUCCESS: - return normalizeStatus(state, action.response); + case STATUS_IMPORT: + return importStatus(state, action.status); + case STATUSES_IMPORT: + return importStatuses(state, action.statuses); case FAVOURITE_REQUEST: return state.setIn([action.status.get('id'), 'favourited'], true); case FAVOURITE_FAIL: @@ -126,16 +57,6 @@ export default function statuses(state = initialState, action) { return state.withMutations(map => { action.ids.forEach(id => map.setIn([id, 'hidden'], true)); }); - case TIMELINE_REFRESH_SUCCESS: - case TIMELINE_EXPAND_SUCCESS: - case CONTEXT_FETCH_SUCCESS: - case NOTIFICATIONS_REFRESH_SUCCESS: - case NOTIFICATIONS_EXPAND_SUCCESS: - case FAVOURITED_STATUSES_FETCH_SUCCESS: - case FAVOURITED_STATUSES_EXPAND_SUCCESS: - case PINNED_STATUSES_FETCH_SUCCESS: - case SEARCH_FETCH_SUCCESS: - return normalizeStatuses(state, action.statuses); case TIMELINE_DELETE: return deleteStatus(state, action.id, action.references); default: From 59657e24b9737cb2b38ea6b0f9e99192908b15df Mon Sep 17 00:00:00 2001 From: Akihiko Odaki Date: Sat, 24 Mar 2018 21:36:44 +0900 Subject: [PATCH 035/381] Rename variables to have semantic meanings in notifications reducer (#6890) --- app/javascript/mastodon/reducers/notifications.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/javascript/mastodon/reducers/notifications.js b/app/javascript/mastodon/reducers/notifications.js index 264db4f..06c36c8 100644 --- a/app/javascript/mastodon/reducers/notifications.js +++ b/app/javascript/mastodon/reducers/notifications.js @@ -49,11 +49,11 @@ const normalizeNotification = (state, notification) => { }; const normalizeNotifications = (state, notifications, next) => { - let items = ImmutableList(); + let newItems = ImmutableList(); const loaded = state.get('loaded'); notifications.forEach((n, i) => { - items = items.set(i, notificationToMap(n)); + newItems = newItems.set(i, notificationToMap(n)); }); if (state.get('next') === null) { @@ -61,7 +61,7 @@ const normalizeNotifications = (state, notifications, next) => { } return state - .update('items', list => loaded ? items.concat(list) : list.concat(items)) + .update('items', oldItems => loaded ? newItems.concat(oldItems) : oldItems.concat(newItems)) .set('loaded', true) .set('isLoading', false); }; From 9a1a55ce526c956ac6b35897d483c316b7ad4394 Mon Sep 17 00:00:00 2001 From: Akihiko Odaki Date: Sat, 24 Mar 2018 23:25:15 +0900 Subject: [PATCH 036/381] Allow clients to fetch statuses made while they were offline (#6876) --- app/javascript/mastodon/actions/compose.js | 20 ++-- app/javascript/mastodon/actions/streaming.js | 9 +- app/javascript/mastodon/actions/timelines.js | 111 +++------------------ app/javascript/mastodon/components/load_more.js | 5 +- .../mastodon/components/scrollable_list.js | 6 +- app/javascript/mastodon/components/status_list.js | 37 ++++++- .../mastodon/features/account_gallery/index.js | 51 ++++++++-- .../mastodon/features/account_timeline/index.js | 18 ++-- .../mastodon/features/community_timeline/index.js | 13 +-- .../mastodon/features/hashtag_timeline/index.js | 15 ++- .../mastodon/features/home_timeline/index.js | 12 +-- .../mastodon/features/list_timeline/index.js | 10 +- .../mastodon/features/public_timeline/index.js | 13 +-- .../standalone/community_timeline/index.js | 13 +-- .../features/standalone/hashtag_timeline/index.js | 13 +-- .../features/standalone/public_timeline/index.js | 13 +-- .../features/ui/components/report_modal.js | 6 +- .../ui/containers/status_list_container.js | 6 +- app/javascript/mastodon/features/ui/index.js | 4 +- app/javascript/mastodon/reducers/statuses.js | 4 +- app/javascript/mastodon/reducers/timelines.js | 65 ++++++------ app/javascript/mastodon/stream.js | 6 +- 22 files changed, 191 insertions(+), 259 deletions(-) diff --git a/app/javascript/mastodon/actions/compose.js b/app/javascript/mastodon/actions/compose.js index 8e13209..5e7cdd2 100644 --- a/app/javascript/mastodon/actions/compose.js +++ b/app/javascript/mastodon/actions/compose.js @@ -5,13 +5,7 @@ import { search as emojiSearch } from '../features/emoji/emoji_mart_search_light import { tagHistory } from '../settings'; import { useEmoji } from './emojis'; import { importFetchedAccounts } from './importer'; - -import { - updateTimeline, - refreshHomeTimeline, - refreshCommunityTimeline, - refreshPublicTimeline, -} from './timelines'; +import { updateTimeline } from './timelines'; let cancelFetchComposeSuggestionsAccounts; @@ -125,19 +119,17 @@ export function submitCompose() { // To make the app more responsive, immediately get the status into the columns - const insertOrRefresh = (timelineId, refreshAction) => { - if (getState().getIn(['timelines', timelineId, 'online'])) { + const insertIfOnline = (timelineId) => { + if (getState().getIn(['timelines', timelineId, 'items', 0]) !== null) { dispatch(updateTimeline(timelineId, { ...response.data })); - } else if (getState().getIn(['timelines', timelineId, 'loaded'])) { - dispatch(refreshAction()); } }; - insertOrRefresh('home', refreshHomeTimeline); + insertIfOnline('home'); if (response.data.in_reply_to_id === null && response.data.visibility === 'public') { - insertOrRefresh('community', refreshCommunityTimeline); - insertOrRefresh('public', refreshPublicTimeline); + insertIfOnline('community'); + insertIfOnline('public'); } }).catch(function (error) { dispatch(submitComposeFail(error)); diff --git a/app/javascript/mastodon/actions/streaming.js b/app/javascript/mastodon/actions/streaming.js index c22152e..3ac6b8a 100644 --- a/app/javascript/mastodon/actions/streaming.js +++ b/app/javascript/mastodon/actions/streaming.js @@ -2,8 +2,7 @@ import { connectStream } from '../stream'; import { updateTimeline, deleteFromTimelines, - refreshHomeTimeline, - connectTimeline, + expandHomeTimeline, disconnectTimeline, } from './timelines'; import { updateNotifications, refreshNotifications } from './notifications'; @@ -16,10 +15,6 @@ export function connectTimelineStream (timelineId, path, pollingRefresh = null) return connectStream (path, pollingRefresh, (dispatch, getState) => { const locale = getState().getIn(['meta', 'locale']); return { - onConnect() { - dispatch(connectTimeline(timelineId)); - }, - onDisconnect() { dispatch(disconnectTimeline(timelineId)); }, @@ -42,7 +37,7 @@ export function connectTimelineStream (timelineId, path, pollingRefresh = null) } function refreshHomeTimelineAndNotification (dispatch) { - dispatch(refreshHomeTimeline()); + dispatch(expandHomeTimeline()); dispatch(refreshNotifications()); } diff --git a/app/javascript/mastodon/actions/timelines.js b/app/javascript/mastodon/actions/timelines.js index e5748b4..5be0712 100644 --- a/app/javascript/mastodon/actions/timelines.js +++ b/app/javascript/mastodon/actions/timelines.js @@ -1,36 +1,20 @@ import { importFetchedStatus, importFetchedStatuses } from './importer'; import api, { getLinks } from '../api'; -import { Map as ImmutableMap, List as ImmutableList } from 'immutable'; +import { Map as ImmutableMap } from 'immutable'; export const TIMELINE_UPDATE = 'TIMELINE_UPDATE'; export const TIMELINE_DELETE = 'TIMELINE_DELETE'; -export const TIMELINE_REFRESH_REQUEST = 'TIMELINE_REFRESH_REQUEST'; -export const TIMELINE_REFRESH_SUCCESS = 'TIMELINE_REFRESH_SUCCESS'; -export const TIMELINE_REFRESH_FAIL = 'TIMELINE_REFRESH_FAIL'; - export const TIMELINE_EXPAND_REQUEST = 'TIMELINE_EXPAND_REQUEST'; export const TIMELINE_EXPAND_SUCCESS = 'TIMELINE_EXPAND_SUCCESS'; export const TIMELINE_EXPAND_FAIL = 'TIMELINE_EXPAND_FAIL'; export const TIMELINE_SCROLL_TOP = 'TIMELINE_SCROLL_TOP'; -export const TIMELINE_CONNECT = 'TIMELINE_CONNECT'; export const TIMELINE_DISCONNECT = 'TIMELINE_DISCONNECT'; export const TIMELINE_CONTEXT_UPDATE = 'CONTEXT_UPDATE'; -export function refreshTimelineSuccess(timeline, statuses, skipLoading, next, partial) { - return { - type: TIMELINE_REFRESH_SUCCESS, - timeline, - statuses, - skipLoading, - next, - partial, - }; -}; - export function updateTimeline(timeline, status) { return (dispatch, getState) => { const references = status.reblog ? getState().get('statuses').filter((item, itemId) => (itemId === status.reblog.id || item.get('reblog') === status.reblog.id)).map((_, itemId) => itemId) : []; @@ -80,97 +64,34 @@ export function deleteFromTimelines(id) { }; }; -export function refreshTimelineRequest(timeline, skipLoading) { - return { - type: TIMELINE_REFRESH_REQUEST, - timeline, - skipLoading, - }; -}; - -export function refreshTimeline(timelineId, path, params = {}) { - return function (dispatch, getState) { - const timeline = getState().getIn(['timelines', timelineId], ImmutableMap()); - - if (timeline.get('isLoading') || (timeline.get('online') && !timeline.get('isPartial'))) { - return; - } - - const ids = timeline.get('items', ImmutableList()); - const newestId = ids.size > 0 ? ids.first() : null; - - let skipLoading = timeline.get('loaded'); - - if (newestId !== null) { - params.since_id = newestId; - } - - dispatch(refreshTimelineRequest(timelineId, skipLoading)); - - api(getState).get(path, { params }).then(response => { - if (response.status === 206) { - dispatch(refreshTimelineSuccess(timelineId, [], skipLoading, null, true)); - } else { - const next = getLinks(response).refs.find(link => link.rel === 'next'); - dispatch(importFetchedStatuses(response.data)); - dispatch(refreshTimelineSuccess(timelineId, response.data, skipLoading, next ? next.uri : null, false)); - } - }).catch(error => { - dispatch(refreshTimelineFail(timelineId, error, skipLoading)); - }); - }; -}; - -export const refreshHomeTimeline = () => refreshTimeline('home', '/api/v1/timelines/home'); -export const refreshPublicTimeline = () => refreshTimeline('public', '/api/v1/timelines/public'); -export const refreshCommunityTimeline = () => refreshTimeline('community', '/api/v1/timelines/public', { local: true }); -export const refreshAccountTimeline = (accountId, withReplies) => refreshTimeline(`account:${accountId}${withReplies ? ':with_replies' : ''}`, `/api/v1/accounts/${accountId}/statuses`, { exclude_replies: !withReplies }); -export const refreshAccountFeaturedTimeline = accountId => refreshTimeline(`account:${accountId}:pinned`, `/api/v1/accounts/${accountId}/statuses`, { pinned: true }); -export const refreshAccountMediaTimeline = accountId => refreshTimeline(`account:${accountId}:media`, `/api/v1/accounts/${accountId}/statuses`, { only_media: true }); -export const refreshHashtagTimeline = hashtag => refreshTimeline(`hashtag:${hashtag}`, `/api/v1/timelines/tag/${hashtag}`); -export const refreshListTimeline = id => refreshTimeline(`list:${id}`, `/api/v1/timelines/list/${id}`); - -export function refreshTimelineFail(timeline, error, skipLoading) { - return { - type: TIMELINE_REFRESH_FAIL, - timeline, - error, - skipLoading, - skipAlert: error.response && error.response.status === 404, - }; -}; - export function expandTimeline(timelineId, path, params = {}) { return (dispatch, getState) => { const timeline = getState().getIn(['timelines', timelineId], ImmutableMap()); - const ids = timeline.get('items', ImmutableList()); - if (timeline.get('isLoading') || ids.size === 0) { + if (timeline.get('isLoading')) { return; } - params.max_id = ids.last(); - params.limit = 10; - dispatch(expandTimelineRequest(timelineId)); api(getState).get(path, { params }).then(response => { const next = getLinks(response).refs.find(link => link.rel === 'next'); dispatch(importFetchedStatuses(response.data)); - dispatch(expandTimelineSuccess(timelineId, response.data, next ? next.uri : null)); + dispatch(expandTimelineSuccess(timelineId, response.data, next ? next.uri : null, response.code === 206)); }).catch(error => { dispatch(expandTimelineFail(timelineId, error)); }); }; }; -export const expandHomeTimeline = () => expandTimeline('home', '/api/v1/timelines/home'); -export const expandPublicTimeline = () => expandTimeline('public', '/api/v1/timelines/public'); -export const expandCommunityTimeline = () => expandTimeline('community', '/api/v1/timelines/public', { local: true }); -export const expandAccountTimeline = (accountId, withReplies) => expandTimeline(`account:${accountId}${withReplies ? ':with_replies' : ''}`, `/api/v1/accounts/${accountId}/statuses`, { exclude_replies: !withReplies }); -export const expandAccountMediaTimeline = accountId => expandTimeline(`account:${accountId}:media`, `/api/v1/accounts/${accountId}/statuses`, { only_media: true }); -export const expandHashtagTimeline = hashtag => expandTimeline(`hashtag:${hashtag}`, `/api/v1/timelines/tag/${hashtag}`); -export const expandListTimeline = id => expandTimeline(`list:${id}`, `/api/v1/timelines/list/${id}`); +export const expandHomeTimeline = ({ maxId } = {}) => expandTimeline('home', '/api/v1/timelines/home', { max_id: maxId }); +export const expandPublicTimeline = ({ maxId } = {}) => expandTimeline('public', '/api/v1/timelines/public', { max_id: maxId }); +export const expandCommunityTimeline = ({ maxId } = {}) => expandTimeline('community', '/api/v1/timelines/public', { local: true, max_id: maxId }); +export const expandAccountTimeline = (accountId, { maxId, withReplies } = {}) => expandTimeline(`account:${accountId}${withReplies ? ':with_replies' : ''}`, `/api/v1/accounts/${accountId}/statuses`, { exclude_replies: !withReplies, max_id: maxId }); +export const expandAccountFeaturedTimeline = accountId => expandTimeline(`account:${accountId}:pinned`, `/api/v1/accounts/${accountId}/statuses`, { pinned: true }); +export const expandAccountMediaTimeline = (accountId, { maxId } = {}) => expandTimeline(`account:${accountId}:media`, `/api/v1/accounts/${accountId}/statuses`, { max_id: maxId, only_media: true }); +export const expandHashtagTimeline = (hashtag, { maxId } = {}) => expandTimeline(`hashtag:${hashtag}`, `/api/v1/timelines/tag/${hashtag}`, { max_id: maxId }); +export const expandListTimeline = (id, { maxId } = {}) => expandTimeline(`list:${id}`, `/api/v1/timelines/list/${id}`, { max_id: maxId }); export function expandTimelineRequest(timeline) { return { @@ -179,12 +100,13 @@ export function expandTimelineRequest(timeline) { }; }; -export function expandTimelineSuccess(timeline, statuses, next) { +export function expandTimelineSuccess(timeline, statuses, next, partial) { return { type: TIMELINE_EXPAND_SUCCESS, timeline, statuses, next, + partial, }; }; @@ -204,13 +126,6 @@ export function scrollTopTimeline(timeline, top) { }; }; -export function connectTimeline(timeline) { - return { - type: TIMELINE_CONNECT, - timeline, - }; -}; - export function disconnectTimeline(timeline) { return { type: TIMELINE_DISCONNECT, diff --git a/app/javascript/mastodon/components/load_more.js b/app/javascript/mastodon/components/load_more.js index c4c8c94..389c3e1 100644 --- a/app/javascript/mastodon/components/load_more.js +++ b/app/javascript/mastodon/components/load_more.js @@ -6,6 +6,7 @@ export default class LoadMore extends React.PureComponent { static propTypes = { onClick: PropTypes.func, + disabled: PropTypes.bool, visible: PropTypes.bool, } @@ -14,10 +15,10 @@ export default class LoadMore extends React.PureComponent { } render() { - const { visible } = this.props; + const { disabled, visible } = this.props; return ( - ); diff --git a/app/javascript/mastodon/components/scrollable_list.js b/app/javascript/mastodon/components/scrollable_list.js index ac3e404..ee07106 100644 --- a/app/javascript/mastodon/components/scrollable_list.js +++ b/app/javascript/mastodon/components/scrollable_list.js @@ -17,7 +17,7 @@ export default class ScrollableList extends PureComponent { static propTypes = { scrollKey: PropTypes.string.isRequired, - onLoadMore: PropTypes.func.isRequired, + onLoadMore: PropTypes.func, onScrollToTop: PropTypes.func, onScroll: PropTypes.func, trackScroll: PropTypes.bool, @@ -148,11 +148,11 @@ export default class ScrollableList extends PureComponent { } render () { - const { children, scrollKey, trackScroll, shouldUpdateScroll, isLoading, hasMore, prepend, emptyMessage } = this.props; + const { children, scrollKey, trackScroll, shouldUpdateScroll, isLoading, hasMore, prepend, emptyMessage, onLoadMore } = this.props; const { fullscreen } = this.state; const childrenCount = React.Children.count(children); - const loadMore = (hasMore && childrenCount > 0) ? : null; + const loadMore = (hasMore && childrenCount > 0 && onLoadMore) ? : null; let scrollableArea = null; if (isLoading || childrenCount > 0 || !emptyMessage) { diff --git a/app/javascript/mastodon/components/status_list.js b/app/javascript/mastodon/components/status_list.js index 3bebf70..8c2673f 100644 --- a/app/javascript/mastodon/components/status_list.js +++ b/app/javascript/mastodon/components/status_list.js @@ -1,11 +1,31 @@ +import { debounce } from 'lodash'; import React from 'react'; import ImmutablePropTypes from 'react-immutable-proptypes'; import PropTypes from 'prop-types'; import StatusContainer from '../containers/status_container'; import ImmutablePureComponent from 'react-immutable-pure-component'; +import LoadMore from './load_more'; import ScrollableList from './scrollable_list'; import { FormattedMessage } from 'react-intl'; +class LoadGap extends ImmutablePureComponent { + + static propTypes = { + disabled: PropTypes.bool, + maxId: PropTypes.string, + onClick: PropTypes.func.isRequired, + }; + + handleClick = () => { + this.props.onClick(this.props.maxId); + } + + render () { + return ; + } + +} + export default class StatusList extends ImmutablePureComponent { static propTypes = { @@ -38,6 +58,10 @@ export default class StatusList extends ImmutablePureComponent { this._selectChild(elementIndex); } + handleLoadOlder = debounce(() => { + this.props.onLoadMore(this.props.statusIds.last()); + }, 300, { leading: true }) + _selectChild (index) { const element = this.node.node.querySelector(`article:nth-of-type(${index + 1}) .focusable`); @@ -51,7 +75,7 @@ export default class StatusList extends ImmutablePureComponent { } render () { - const { statusIds, featuredStatusIds, ...other } = this.props; + const { statusIds, featuredStatusIds, onLoadMore, ...other } = this.props; const { isLoading, isPartial } = other; if (isPartial) { @@ -70,7 +94,14 @@ export default class StatusList extends ImmutablePureComponent { } let scrollableContent = (isLoading || statusIds.size > 0) ? ( - statusIds.map(statusId => ( + statusIds.map((statusId, index) => statusId === null ? ( + 0 ? statusIds.get(index - 1) : null} + onClick={onLoadMore} + /> + ) : ( + {scrollableContent} ); diff --git a/app/javascript/mastodon/features/account_gallery/index.js b/app/javascript/mastodon/features/account_gallery/index.js index 4b40825..9a40d13 100644 --- a/app/javascript/mastodon/features/account_gallery/index.js +++ b/app/javascript/mastodon/features/account_gallery/index.js @@ -3,7 +3,7 @@ import { connect } from 'react-redux'; import ImmutablePropTypes from 'react-immutable-proptypes'; import PropTypes from 'prop-types'; import { fetchAccount } from '../../actions/accounts'; -import { refreshAccountMediaTimeline, expandAccountMediaTimeline } from '../../actions/timelines'; +import { expandAccountMediaTimeline } from '../../actions/timelines'; import LoadingIndicator from '../../components/loading_indicator'; import Column from '../ui/components/column'; import ColumnBackButton from '../../components/column_back_button'; @@ -17,9 +17,31 @@ import LoadMore from '../../components/load_more'; const mapStateToProps = (state, props) => ({ medias: getAccountGallery(state, props.params.accountId), isLoading: state.getIn(['timelines', `account:${props.params.accountId}:media`, 'isLoading']), - hasMore: !!state.getIn(['timelines', `account:${props.params.accountId}:media`, 'next']), + hasMore: state.getIn(['timelines', `account:${props.params.accountId}:media`, 'hasMore']), }); +class LoadMoreMedia extends ImmutablePureComponent { + + static propTypes = { + maxId: PropTypes.string, + onLoadMore: PropTypes.func.isRequired, + }; + + handleLoadMore = () => { + this.props.onLoadMore(this.props.maxId); + } + + render () { + return ( + + ); + } + +} + @connect(mapStateToProps) export default class AccountGallery extends ImmutablePureComponent { @@ -33,19 +55,19 @@ export default class AccountGallery extends ImmutablePureComponent { componentDidMount () { this.props.dispatch(fetchAccount(this.props.params.accountId)); - this.props.dispatch(refreshAccountMediaTimeline(this.props.params.accountId)); + this.props.dispatch(expandAccountMediaTimeline(this.props.params.accountId)); } componentWillReceiveProps (nextProps) { if (nextProps.params.accountId !== this.props.params.accountId && nextProps.params.accountId) { this.props.dispatch(fetchAccount(nextProps.params.accountId)); - this.props.dispatch(refreshAccountMediaTimeline(this.props.params.accountId)); + this.props.dispatch(expandAccountMediaTimeline(this.props.params.accountId)); } } handleScrollToBottom = () => { if (this.props.hasMore) { - this.props.dispatch(expandAccountMediaTimeline(this.props.params.accountId)); + this.handleLoadMore(this.props.medias.last().get('id')); } } @@ -58,7 +80,11 @@ export default class AccountGallery extends ImmutablePureComponent { } } - handleLoadMore = (e) => { + handleLoadMore = maxId => { + this.props.dispatch(expandAccountMediaTimeline(this.props.params.accountId, { maxId })); + }; + + handleLoadOlder = (e) => { e.preventDefault(); this.handleScrollToBottom(); } @@ -66,7 +92,7 @@ export default class AccountGallery extends ImmutablePureComponent { render () { const { medias, isLoading, hasMore } = this.props; - let loadMore = null; + let loadOlder = null; if (!medias && isLoading) { return ( @@ -77,7 +103,7 @@ export default class AccountGallery extends ImmutablePureComponent { } if (!isLoading && medias.size > 0 && hasMore) { - loadMore = ; + loadOlder = ; } return ( @@ -89,13 +115,18 @@ export default class AccountGallery extends ImmutablePureComponent {

- {medias.map(media => ( + {medias.map((media, index) => media === null ? ( + 0 ? medias.getIn(index - 1, 'id') : null} + /> + ) : ( ))} - {loadMore} + {loadOlder}
diff --git a/app/javascript/mastodon/features/account_timeline/index.js b/app/javascript/mastodon/features/account_timeline/index.js index 5e21cf7..d329bac 100644 --- a/app/javascript/mastodon/features/account_timeline/index.js +++ b/app/javascript/mastodon/features/account_timeline/index.js @@ -3,7 +3,7 @@ import { connect } from 'react-redux'; import ImmutablePropTypes from 'react-immutable-proptypes'; import PropTypes from 'prop-types'; import { fetchAccount } from '../../actions/accounts'; -import { refreshAccountTimeline, refreshAccountFeaturedTimeline, expandAccountTimeline } from '../../actions/timelines'; +import { expandAccountFeaturedTimeline, expandAccountTimeline } from '../../actions/timelines'; import StatusList from '../../components/status_list'; import LoadingIndicator from '../../components/loading_indicator'; import Column from '../ui/components/column'; @@ -19,7 +19,7 @@ const mapStateToProps = (state, { params: { accountId }, withReplies = false }) statusIds: state.getIn(['timelines', `account:${path}`, 'items'], ImmutableList()), featuredStatusIds: withReplies ? ImmutableList() : state.getIn(['timelines', `account:${accountId}:pinned`, 'items'], ImmutableList()), isLoading: state.getIn(['timelines', `account:${path}`, 'isLoading']), - hasMore: !!state.getIn(['timelines', `account:${path}`, 'next']), + hasMore: state.getIn(['timelines', `account:${path}`, 'hasMore']), }; }; @@ -41,25 +41,23 @@ export default class AccountTimeline extends ImmutablePureComponent { this.props.dispatch(fetchAccount(accountId)); if (!withReplies) { - this.props.dispatch(refreshAccountFeaturedTimeline(accountId)); + this.props.dispatch(expandAccountFeaturedTimeline(accountId)); } - this.props.dispatch(refreshAccountTimeline(accountId, withReplies)); + this.props.dispatch(expandAccountTimeline(accountId, { withReplies })); } componentWillReceiveProps (nextProps) { if ((nextProps.params.accountId !== this.props.params.accountId && nextProps.params.accountId) || nextProps.withReplies !== this.props.withReplies) { this.props.dispatch(fetchAccount(nextProps.params.accountId)); if (!nextProps.withReplies) { - this.props.dispatch(refreshAccountFeaturedTimeline(nextProps.params.accountId)); + this.props.dispatch(expandAccountFeaturedTimeline(nextProps.params.accountId)); } - this.props.dispatch(refreshAccountTimeline(nextProps.params.accountId, nextProps.params.withReplies)); + this.props.dispatch(expandAccountTimeline(nextProps.params.accountId, { withReplies: nextProps.params.withReplies })); } } - handleLoadMore = () => { - if (!this.props.isLoading && this.props.hasMore) { - this.props.dispatch(expandAccountTimeline(this.props.params.accountId, this.props.withReplies)); - } + handleLoadMore = maxId => { + this.props.dispatch(expandAccountTimeline(this.props.params.accountId, { maxId, withReplies: this.props.withReplies })); } render () { diff --git a/app/javascript/mastodon/features/community_timeline/index.js b/app/javascript/mastodon/features/community_timeline/index.js index 596a894..870474e 100644 --- a/app/javascript/mastodon/features/community_timeline/index.js +++ b/app/javascript/mastodon/features/community_timeline/index.js @@ -4,10 +4,7 @@ import PropTypes from 'prop-types'; import StatusListContainer from '../ui/containers/status_list_container'; import Column from '../../components/column'; import ColumnHeader from '../../components/column_header'; -import { - refreshCommunityTimeline, - expandCommunityTimeline, -} from '../../actions/timelines'; +import { expandCommunityTimeline } from '../../actions/timelines'; import { addColumn, removeColumn, moveColumn } from '../../actions/columns'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import ColumnSettingsContainer from './containers/column_settings_container'; @@ -55,7 +52,7 @@ export default class CommunityTimeline extends React.PureComponent { componentDidMount () { const { dispatch } = this.props; - dispatch(refreshCommunityTimeline()); + dispatch(expandCommunityTimeline()); this.disconnect = dispatch(connectCommunityStream()); } @@ -70,8 +67,8 @@ export default class CommunityTimeline extends React.PureComponent { this.column = c; } - handleLoadMore = () => { - this.props.dispatch(expandCommunityTimeline()); + handleLoadMore = maxId => { + this.props.dispatch(expandCommunityTimeline({ maxId })); } render () { @@ -97,7 +94,7 @@ export default class CommunityTimeline extends React.PureComponent { trackScroll={!pinned} scrollKey={`community_timeline-${columnId}`} timelineId='community' - loadMore={this.handleLoadMore} + onLoadMore={this.handleLoadMore} emptyMessage={} /> diff --git a/app/javascript/mastodon/features/hashtag_timeline/index.js b/app/javascript/mastodon/features/hashtag_timeline/index.js index 5fe21ce..374615a 100644 --- a/app/javascript/mastodon/features/hashtag_timeline/index.js +++ b/app/javascript/mastodon/features/hashtag_timeline/index.js @@ -4,10 +4,7 @@ import PropTypes from 'prop-types'; import StatusListContainer from '../ui/containers/status_list_container'; import Column from '../../components/column'; import ColumnHeader from '../../components/column_header'; -import { - refreshHashtagTimeline, - expandHashtagTimeline, -} from '../../actions/timelines'; +import { expandHashtagTimeline } from '../../actions/timelines'; import { addColumn, removeColumn, moveColumn } from '../../actions/columns'; import { FormattedMessage } from 'react-intl'; import { connectHashtagStream } from '../../actions/streaming'; @@ -61,13 +58,13 @@ export default class HashtagTimeline extends React.PureComponent { const { dispatch } = this.props; const { id } = this.props.params; - dispatch(refreshHashtagTimeline(id)); + dispatch(expandHashtagTimeline(id)); this._subscribe(dispatch, id); } componentWillReceiveProps (nextProps) { if (nextProps.params.id !== this.props.params.id) { - this.props.dispatch(refreshHashtagTimeline(nextProps.params.id)); + this.props.dispatch(expandHashtagTimeline(nextProps.params.id)); this._unsubscribe(); this._subscribe(this.props.dispatch, nextProps.params.id); } @@ -81,8 +78,8 @@ export default class HashtagTimeline extends React.PureComponent { this.column = c; } - handleLoadMore = () => { - this.props.dispatch(expandHashtagTimeline(this.props.params.id)); + handleLoadMore = maxId => { + this.props.dispatch(expandHashtagTimeline(this.props.params.id, { maxId })); } render () { @@ -108,7 +105,7 @@ export default class HashtagTimeline extends React.PureComponent { trackScroll={!pinned} scrollKey={`hashtag_timeline-${columnId}`} timelineId={`hashtag:${id}`} - loadMore={this.handleLoadMore} + onLoadMore={this.handleLoadMore} emptyMessage={} /> diff --git a/app/javascript/mastodon/features/home_timeline/index.js b/app/javascript/mastodon/features/home_timeline/index.js index 31f5a3c..db6bbde 100644 --- a/app/javascript/mastodon/features/home_timeline/index.js +++ b/app/javascript/mastodon/features/home_timeline/index.js @@ -1,6 +1,6 @@ import React from 'react'; import { connect } from 'react-redux'; -import { expandHomeTimeline, refreshHomeTimeline } from '../../actions/timelines'; +import { expandHomeTimeline } from '../../actions/timelines'; import PropTypes from 'prop-types'; import StatusListContainer from '../ui/containers/status_list_container'; import Column from '../../components/column'; @@ -16,7 +16,7 @@ const messages = defineMessages({ const mapStateToProps = state => ({ hasUnread: state.getIn(['timelines', 'home', 'unread']) > 0, - isPartial: state.getIn(['timelines', 'home', 'isPartial'], false), + isPartial: state.getIn(['timelines', 'home', 'items', 0], null) === null, }); @connect(mapStateToProps) @@ -55,8 +55,8 @@ export default class HomeTimeline extends React.PureComponent { this.column = c; } - handleLoadMore = () => { - this.props.dispatch(expandHomeTimeline()); + handleLoadMore = maxId => { + this.props.dispatch(expandHomeTimeline({ maxId })); } componentDidMount () { @@ -78,7 +78,7 @@ export default class HomeTimeline extends React.PureComponent { return; } else if (!wasPartial && isPartial) { this.polling = setInterval(() => { - dispatch(refreshHomeTimeline()); + dispatch(expandHomeTimeline()); }, 3000); } else if (wasPartial && !isPartial) { this._stopPolling(); @@ -114,7 +114,7 @@ export default class HomeTimeline extends React.PureComponent { }} />} /> diff --git a/app/javascript/mastodon/features/list_timeline/index.js b/app/javascript/mastodon/features/list_timeline/index.js index 3b97ac6..9a1e3c6 100644 --- a/app/javascript/mastodon/features/list_timeline/index.js +++ b/app/javascript/mastodon/features/list_timeline/index.js @@ -8,7 +8,7 @@ import ColumnHeader from '../../components/column_header'; import { addColumn, removeColumn, moveColumn } from '../../actions/columns'; import { FormattedMessage, defineMessages, injectIntl } from 'react-intl'; import { connectListStream } from '../../actions/streaming'; -import { refreshListTimeline, expandListTimeline } from '../../actions/timelines'; +import { expandListTimeline } from '../../actions/timelines'; import { fetchList, deleteList } from '../../actions/lists'; import { openModal } from '../../actions/modal'; import MissingIndicator from '../../components/missing_indicator'; @@ -67,7 +67,7 @@ export default class ListTimeline extends React.PureComponent { const { id } = this.props.params; dispatch(fetchList(id)); - dispatch(refreshListTimeline(id)); + dispatch(expandListTimeline(id)); this.disconnect = dispatch(connectListStream(id)); } @@ -83,9 +83,9 @@ export default class ListTimeline extends React.PureComponent { this.column = c; } - handleLoadMore = () => { + handleLoadMore = maxId => { const { id } = this.props.params; - this.props.dispatch(expandListTimeline(id)); + this.props.dispatch(expandListTimeline(id, { maxId })); } handleEditClick = () => { @@ -164,7 +164,7 @@ export default class ListTimeline extends React.PureComponent { trackScroll={!pinned} scrollKey={`list_timeline-${columnId}`} timelineId={`list:${id}`} - loadMore={this.handleLoadMore} + onLoadMore={this.handleLoadMore} emptyMessage={} /> diff --git a/app/javascript/mastodon/features/public_timeline/index.js b/app/javascript/mastodon/features/public_timeline/index.js index 193489c..5a88f76 100644 --- a/app/javascript/mastodon/features/public_timeline/index.js +++ b/app/javascript/mastodon/features/public_timeline/index.js @@ -4,10 +4,7 @@ import PropTypes from 'prop-types'; import StatusListContainer from '../ui/containers/status_list_container'; import Column from '../../components/column'; import ColumnHeader from '../../components/column_header'; -import { - refreshPublicTimeline, - expandPublicTimeline, -} from '../../actions/timelines'; +import { expandPublicTimeline } from '../../actions/timelines'; import { addColumn, removeColumn, moveColumn } from '../../actions/columns'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import ColumnSettingsContainer from './containers/column_settings_container'; @@ -55,7 +52,7 @@ export default class PublicTimeline extends React.PureComponent { componentDidMount () { const { dispatch } = this.props; - dispatch(refreshPublicTimeline()); + dispatch(expandPublicTimeline()); this.disconnect = dispatch(connectPublicStream()); } @@ -70,8 +67,8 @@ export default class PublicTimeline extends React.PureComponent { this.column = c; } - handleLoadMore = () => { - this.props.dispatch(expandPublicTimeline()); + handleLoadMore = maxId => { + this.props.dispatch(expandPublicTimeline({ maxId })); } render () { @@ -95,7 +92,7 @@ export default class PublicTimeline extends React.PureComponent { } diff --git a/app/javascript/mastodon/features/standalone/community_timeline/index.js b/app/javascript/mastodon/features/standalone/community_timeline/index.js index 51e50e1..629d058 100644 --- a/app/javascript/mastodon/features/standalone/community_timeline/index.js +++ b/app/javascript/mastodon/features/standalone/community_timeline/index.js @@ -2,10 +2,7 @@ import React from 'react'; import { connect } from 'react-redux'; import PropTypes from 'prop-types'; import StatusListContainer from '../../ui/containers/status_list_container'; -import { - refreshCommunityTimeline, - expandCommunityTimeline, -} from '../../../actions/timelines'; +import { expandCommunityTimeline } from '../../../actions/timelines'; import Column from '../../../components/column'; import ColumnHeader from '../../../components/column_header'; import { defineMessages, injectIntl } from 'react-intl'; @@ -35,7 +32,7 @@ export default class CommunityTimeline extends React.PureComponent { componentDidMount () { const { dispatch } = this.props; - dispatch(refreshCommunityTimeline()); + dispatch(expandCommunityTimeline()); this.disconnect = dispatch(connectCommunityStream()); } @@ -46,8 +43,8 @@ export default class CommunityTimeline extends React.PureComponent { } } - handleLoadMore = () => { - this.props.dispatch(expandCommunityTimeline()); + handleLoadMore = maxId => { + this.props.dispatch(expandCommunityTimeline({ maxId })); } render () { @@ -63,7 +60,7 @@ export default class CommunityTimeline extends React.PureComponent { diff --git a/app/javascript/mastodon/features/standalone/hashtag_timeline/index.js b/app/javascript/mastodon/features/standalone/hashtag_timeline/index.js index f14be2a..931ca2a 100644 --- a/app/javascript/mastodon/features/standalone/hashtag_timeline/index.js +++ b/app/javascript/mastodon/features/standalone/hashtag_timeline/index.js @@ -2,10 +2,7 @@ import React from 'react'; import { connect } from 'react-redux'; import PropTypes from 'prop-types'; import StatusListContainer from '../../ui/containers/status_list_container'; -import { - refreshHashtagTimeline, - expandHashtagTimeline, -} from '../../../actions/timelines'; +import { expandHashtagTimeline } from '../../../actions/timelines'; import Column from '../../../components/column'; import ColumnHeader from '../../../components/column_header'; import { connectHashtagStream } from '../../../actions/streaming'; @@ -29,7 +26,7 @@ export default class HashtagTimeline extends React.PureComponent { componentDidMount () { const { dispatch, hashtag } = this.props; - dispatch(refreshHashtagTimeline(hashtag)); + dispatch(expandHashtagTimeline(hashtag)); this.disconnect = dispatch(connectHashtagStream(hashtag)); } @@ -40,8 +37,8 @@ export default class HashtagTimeline extends React.PureComponent { } } - handleLoadMore = () => { - this.props.dispatch(expandHashtagTimeline(this.props.hashtag)); + handleLoadMore = maxId => { + this.props.dispatch(expandHashtagTimeline(this.props.hashtag, { maxId })); } render () { @@ -59,7 +56,7 @@ export default class HashtagTimeline extends React.PureComponent { trackScroll={false} scrollKey='standalone_hashtag_timeline' timelineId={`hashtag:${hashtag}`} - loadMore={this.handleLoadMore} + onLoadMore={this.handleLoadMore} /> ); diff --git a/app/javascript/mastodon/features/standalone/public_timeline/index.js b/app/javascript/mastodon/features/standalone/public_timeline/index.js index 5805d1a..1236cb9 100644 --- a/app/javascript/mastodon/features/standalone/public_timeline/index.js +++ b/app/javascript/mastodon/features/standalone/public_timeline/index.js @@ -2,10 +2,7 @@ import React from 'react'; import { connect } from 'react-redux'; import PropTypes from 'prop-types'; import StatusListContainer from '../../ui/containers/status_list_container'; -import { - refreshPublicTimeline, - expandPublicTimeline, -} from '../../../actions/timelines'; +import { expandPublicTimeline } from '../../../actions/timelines'; import Column from '../../../components/column'; import ColumnHeader from '../../../components/column_header'; import { defineMessages, injectIntl } from 'react-intl'; @@ -35,7 +32,7 @@ export default class PublicTimeline extends React.PureComponent { componentDidMount () { const { dispatch } = this.props; - dispatch(refreshPublicTimeline()); + dispatch(expandPublicTimeline()); this.disconnect = dispatch(connectPublicStream()); } @@ -46,8 +43,8 @@ export default class PublicTimeline extends React.PureComponent { } } - handleLoadMore = () => { - this.props.dispatch(expandPublicTimeline()); + handleLoadMore = maxId => { + this.props.dispatch(expandPublicTimeline({ maxId })); } render () { @@ -63,7 +60,7 @@ export default class PublicTimeline extends React.PureComponent { diff --git a/app/javascript/mastodon/features/ui/components/report_modal.js b/app/javascript/mastodon/features/ui/components/report_modal.js index 3ae9764..8a55c55 100644 --- a/app/javascript/mastodon/features/ui/components/report_modal.js +++ b/app/javascript/mastodon/features/ui/components/report_modal.js @@ -1,7 +1,7 @@ import React from 'react'; import { connect } from 'react-redux'; import { changeReportComment, changeReportForward, submitReport } from '../../../actions/reports'; -import { refreshAccountTimeline } from '../../../actions/timelines'; +import { expandAccountTimeline } from '../../../actions/timelines'; import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; import { makeGetAccount } from '../../../selectors'; @@ -64,12 +64,12 @@ export default class ReportModal extends ImmutablePureComponent { } componentDidMount () { - this.props.dispatch(refreshAccountTimeline(this.props.account.get('id'))); + this.props.dispatch(expandAccountTimeline(this.props.account.get('id'))); } componentWillReceiveProps (nextProps) { if (this.props.account !== nextProps.account && nextProps.account) { - this.props.dispatch(refreshAccountTimeline(nextProps.account.get('id'))); + this.props.dispatch(expandAccountTimeline(nextProps.account.get('id'))); } } diff --git a/app/javascript/mastodon/features/ui/containers/status_list_container.js b/app/javascript/mastodon/features/ui/containers/status_list_container.js index fc2867c..4efacda 100644 --- a/app/javascript/mastodon/features/ui/containers/status_list_container.js +++ b/app/javascript/mastodon/features/ui/containers/status_list_container.js @@ -48,15 +48,13 @@ const makeMapStateToProps = () => { statusIds: getStatusIds(state, { type: timelineId }), isLoading: state.getIn(['timelines', timelineId, 'isLoading'], true), isPartial: state.getIn(['timelines', timelineId, 'isPartial'], false), - hasMore: !!state.getIn(['timelines', timelineId, 'next']), + hasMore: state.getIn(['timelines', timelineId, 'hasMore']), }); return mapStateToProps; }; -const mapDispatchToProps = (dispatch, { timelineId, loadMore }) => ({ - - onLoadMore: debounce(loadMore, 300, { leading: true }), +const mapDispatchToProps = (dispatch, { timelineId }) => ({ onScrollToTop: debounce(() => { dispatch(scrollTopTimeline(timelineId, true)); diff --git a/app/javascript/mastodon/features/ui/index.js b/app/javascript/mastodon/features/ui/index.js index 6cf0022..5941290 100644 --- a/app/javascript/mastodon/features/ui/index.js +++ b/app/javascript/mastodon/features/ui/index.js @@ -10,7 +10,7 @@ import { Redirect, withRouter } from 'react-router-dom'; import { isMobile } from '../../is_mobile'; import { debounce } from 'lodash'; import { uploadCompose, resetCompose } from '../../actions/compose'; -import { refreshHomeTimeline } from '../../actions/timelines'; +import { expandHomeTimeline } from '../../actions/timelines'; import { refreshNotifications } from '../../actions/notifications'; import { clearHeight } from '../../actions/height_cache'; import { WrappedSwitch, WrappedRoute } from './util/react_router_helpers'; @@ -284,7 +284,7 @@ export default class UI extends React.PureComponent { navigator.serviceWorker.addEventListener('message', this.handleServiceWorkerPostMessage); } - this.props.dispatch(refreshHomeTimeline()); + this.props.dispatch(expandHomeTimeline()); this.props.dispatch(refreshNotifications()); } diff --git a/app/javascript/mastodon/reducers/statuses.js b/app/javascript/mastodon/reducers/statuses.js index fc4b490..3abe69b 100644 --- a/app/javascript/mastodon/reducers/statuses.js +++ b/app/javascript/mastodon/reducers/statuses.js @@ -10,9 +10,7 @@ import { STATUS_REVEAL, STATUS_HIDE, } from '../actions/statuses'; -import { - TIMELINE_DELETE, -} from '../actions/timelines'; +import { TIMELINE_DELETE } from '../actions/timelines'; import { STATUS_IMPORT, STATUSES_IMPORT } from '../actions/importer'; import { Map as ImmutableMap, fromJS } from 'immutable'; diff --git a/app/javascript/mastodon/reducers/timelines.js b/app/javascript/mastodon/reducers/timelines.js index 9a10bcc..f795e7e 100644 --- a/app/javascript/mastodon/reducers/timelines.js +++ b/app/javascript/mastodon/reducers/timelines.js @@ -1,14 +1,10 @@ import { - TIMELINE_REFRESH_REQUEST, - TIMELINE_REFRESH_SUCCESS, - TIMELINE_REFRESH_FAIL, TIMELINE_UPDATE, TIMELINE_DELETE, TIMELINE_EXPAND_SUCCESS, TIMELINE_EXPAND_REQUEST, TIMELINE_EXPAND_FAIL, TIMELINE_SCROLL_TOP, - TIMELINE_CONNECT, TIMELINE_DISCONNECT, } from '../actions/timelines'; import { @@ -22,37 +18,33 @@ const initialState = ImmutableMap(); const initialTimeline = ImmutableMap({ unread: 0, - online: false, top: true, - loaded: false, isLoading: false, - next: false, + hasMore: true, items: ImmutableList(), }); -const normalizeTimeline = (state, timeline, statuses, next, isPartial) => { - const oldIds = state.getIn([timeline, 'items'], ImmutableList()); - const ids = ImmutableList(statuses.map(status => status.get('id'))).filter(newId => !oldIds.includes(newId)); - const wasLoaded = state.getIn([timeline, 'loaded']); - const hadNext = state.getIn([timeline, 'next']); - - return state.update(timeline, initialTimeline, map => map.withMutations(mMap => { - mMap.set('loaded', true); - mMap.set('isLoading', false); - if (!hadNext) mMap.set('next', next); - mMap.set('items', wasLoaded ? ids.concat(oldIds) : oldIds.concat(ids)); - mMap.set('isPartial', isPartial); - })); -}; - -const appendNormalizedTimeline = (state, timeline, statuses, next) => { - const oldIds = state.getIn([timeline, 'items'], ImmutableList()); - const ids = ImmutableList(statuses.map(status => status.get('id'))).filter(newId => !oldIds.includes(newId)); - +const expandNormalizedTimeline = (state, timeline, statuses, next, isPartial) => { return state.update(timeline, initialTimeline, map => map.withMutations(mMap => { mMap.set('isLoading', false); - mMap.set('next', next); - mMap.set('items', oldIds.concat(ids)); + if (!next) mMap.set('hasMore', false); + + if (!statuses.isEmpty()) { + mMap.update('items', ImmutableList(), oldIds => { + const newIds = statuses.map(status => status.get('id')); + const lastIndex = oldIds.findLastIndex(id => id !== null && id >= newIds.last()) + 1; + const firstIndex = oldIds.take(lastIndex).findLastIndex(id => id !== null && id > newIds.first()); + + if (firstIndex < 0) { + return (isPartial ? newIds.unshift(null) : newIds).concat(oldIds.skip(lastIndex)); + } + + return oldIds.take(firstIndex + 1).concat( + isPartial && oldIds.get(firstIndex) !== null ? newIds.unshift(null) : newIds, + oldIds.skip(lastIndex) + ); + }); + } })); }; @@ -118,16 +110,12 @@ const updateTop = (state, timeline, top) => { export default function timelines(state = initialState, action) { switch(action.type) { - case TIMELINE_REFRESH_REQUEST: case TIMELINE_EXPAND_REQUEST: return state.update(action.timeline, initialTimeline, map => map.set('isLoading', true)); - case TIMELINE_REFRESH_FAIL: case TIMELINE_EXPAND_FAIL: return state.update(action.timeline, initialTimeline, map => map.set('isLoading', false)); - case TIMELINE_REFRESH_SUCCESS: - return normalizeTimeline(state, action.timeline, fromJS(action.statuses), action.next, action.partial); case TIMELINE_EXPAND_SUCCESS: - return appendNormalizedTimeline(state, action.timeline, fromJS(action.statuses), action.next); + return expandNormalizedTimeline(state, action.timeline, fromJS(action.statuses), action.next, action.partial); case TIMELINE_UPDATE: return updateTimeline(state, action.timeline, fromJS(action.status)); case TIMELINE_DELETE: @@ -139,10 +127,15 @@ export default function timelines(state = initialState, action) { return filterTimeline('home', state, action.relationship, action.statuses); case TIMELINE_SCROLL_TOP: return updateTop(state, action.timeline, action.top); - case TIMELINE_CONNECT: - return state.update(action.timeline, initialTimeline, map => map.set('online', true)); case TIMELINE_DISCONNECT: - return state.update(action.timeline, initialTimeline, map => map.set('online', false)); + return state.update( + action.timeline, + initialTimeline, + map => map.update( + 'items', + items => items.first() ? items : items.unshift(null) + ) + ); default: return state; } diff --git a/app/javascript/mastodon/stream.js b/app/javascript/mastodon/stream.js index 9a6f4f2..6c67ba2 100644 --- a/app/javascript/mastodon/stream.js +++ b/app/javascript/mastodon/stream.js @@ -1,10 +1,10 @@ import WebSocketClient from 'websocket.js'; -export function connectStream(path, pollingRefresh = null, callbacks = () => ({ onConnect() {}, onDisconnect() {}, onReceive() {} })) { +export function connectStream(path, pollingRefresh = null, callbacks = () => ({ onDisconnect() {}, onReceive() {} })) { return (dispatch, getState) => { const streamingAPIBaseURL = getState().getIn(['meta', 'streaming_api_base_url']); const accessToken = getState().getIn(['meta', 'access_token']); - const { onConnect, onDisconnect, onReceive } = callbacks(dispatch, getState); + const { onDisconnect, onReceive } = callbacks(dispatch, getState); let polling = null; const setupPolling = () => { @@ -25,7 +25,6 @@ export function connectStream(path, pollingRefresh = null, callbacks = () => ({ if (pollingRefresh) { clearPolling(); } - onConnect(); }, disconnected () { @@ -44,7 +43,6 @@ export function connectStream(path, pollingRefresh = null, callbacks = () => ({ clearPolling(); pollingRefresh(dispatch); } - onConnect(); }, }); From cbf97c03bba35a642e6f1d1a698aad7a69ad13a3 Mon Sep 17 00:00:00 2001 From: Akihiko Odaki Date: Sun, 25 Mar 2018 06:07:23 +0900 Subject: [PATCH 037/381] Allow clients to fetch notifications made while they were offline (#6886) --- app/javascript/mastodon/actions/notifications.js | 71 +--------------------- app/javascript/mastodon/actions/streaming.js | 4 +- .../mastodon/features/notifications/index.js | 49 ++++++++++++--- app/javascript/mastodon/features/ui/index.js | 4 +- app/javascript/mastodon/reducers/notifications.js | 68 +++++++++++---------- 5 files changed, 82 insertions(+), 114 deletions(-) diff --git a/app/javascript/mastodon/actions/notifications.js b/app/javascript/mastodon/actions/notifications.js index a664cd9..7267b85 100644 --- a/app/javascript/mastodon/actions/notifications.js +++ b/app/javascript/mastodon/actions/notifications.js @@ -1,5 +1,4 @@ import api, { getLinks } from '../api'; -import { List as ImmutableList } from 'immutable'; import IntlMessageFormat from 'intl-messageformat'; import { fetchRelationships } from './accounts'; import { @@ -12,10 +11,6 @@ import { defineMessages } from 'react-intl'; export const NOTIFICATIONS_UPDATE = 'NOTIFICATIONS_UPDATE'; -export const NOTIFICATIONS_REFRESH_REQUEST = 'NOTIFICATIONS_REFRESH_REQUEST'; -export const NOTIFICATIONS_REFRESH_SUCCESS = 'NOTIFICATIONS_REFRESH_SUCCESS'; -export const NOTIFICATIONS_REFRESH_FAIL = 'NOTIFICATIONS_REFRESH_FAIL'; - export const NOTIFICATIONS_EXPAND_REQUEST = 'NOTIFICATIONS_EXPAND_REQUEST'; export const NOTIFICATIONS_EXPAND_SUCCESS = 'NOTIFICATIONS_EXPAND_SUCCESS'; export const NOTIFICATIONS_EXPAND_FAIL = 'NOTIFICATIONS_EXPAND_FAIL'; @@ -74,74 +69,14 @@ export function updateNotifications(notification, intlMessages, intlLocale) { const excludeTypesFromSettings = state => state.getIn(['settings', 'notifications', 'shows']).filter(enabled => !enabled).keySeq().toJS(); -export function refreshNotifications() { - return (dispatch, getState) => { - const params = {}; - const ids = getState().getIn(['notifications', 'items']); - - let skipLoading = false; - - if (ids.size > 0) { - params.since_id = ids.first().get('id'); - } - - if (getState().getIn(['notifications', 'loaded'])) { - skipLoading = true; - } - - params.exclude_types = excludeTypesFromSettings(getState()); - - dispatch(refreshNotificationsRequest(skipLoading)); - - api(getState).get('/api/v1/notifications', { params }).then(response => { - const next = getLinks(response).refs.find(link => link.rel === 'next'); - - dispatch(importFetchedAccounts(response.data.map(item => item.account))); - dispatch(importFetchedStatuses(response.data.map(item => item.status).filter(status => !!status))); - - dispatch(refreshNotificationsSuccess(response.data, skipLoading, next ? next.uri : null)); - fetchRelatedRelationships(dispatch, response.data); - }).catch(error => { - dispatch(refreshNotificationsFail(error, skipLoading)); - }); - }; -}; - -export function refreshNotificationsRequest(skipLoading) { - return { - type: NOTIFICATIONS_REFRESH_REQUEST, - skipLoading, - }; -}; - -export function refreshNotificationsSuccess(notifications, skipLoading, next) { - return { - type: NOTIFICATIONS_REFRESH_SUCCESS, - notifications, - skipLoading, - next, - }; -}; - -export function refreshNotificationsFail(error, skipLoading) { - return { - type: NOTIFICATIONS_REFRESH_FAIL, - error, - skipLoading, - }; -}; - -export function expandNotifications() { +export function expandNotifications({ maxId } = {}) { return (dispatch, getState) => { - const items = getState().getIn(['notifications', 'items'], ImmutableList()); - - if (getState().getIn(['notifications', 'isLoading']) || items.size === 0) { + if (getState().getIn(['notifications', 'isLoading'])) { return; } const params = { - max_id: items.last().get('id'), - limit: 20, + max_id: maxId, exclude_types: excludeTypesFromSettings(getState()), }; diff --git a/app/javascript/mastodon/actions/streaming.js b/app/javascript/mastodon/actions/streaming.js index 3ac6b8a..f76510c 100644 --- a/app/javascript/mastodon/actions/streaming.js +++ b/app/javascript/mastodon/actions/streaming.js @@ -5,7 +5,7 @@ import { expandHomeTimeline, disconnectTimeline, } from './timelines'; -import { updateNotifications, refreshNotifications } from './notifications'; +import { updateNotifications, expandNotifications } from './notifications'; import { getLocale } from '../locales'; const { messages } = getLocale(); @@ -38,7 +38,7 @@ export function connectTimelineStream (timelineId, path, pollingRefresh = null) function refreshHomeTimelineAndNotification (dispatch) { dispatch(expandHomeTimeline()); - dispatch(refreshNotifications()); + dispatch(expandNotifications()); } export const connectUserStream = () => connectTimelineStream('home', 'user', refreshHomeTimelineAndNotification); diff --git a/app/javascript/mastodon/features/notifications/index.js b/app/javascript/mastodon/features/notifications/index.js index cb9d025..9a6fb45 100644 --- a/app/javascript/mastodon/features/notifications/index.js +++ b/app/javascript/mastodon/features/notifications/index.js @@ -13,6 +13,7 @@ import { createSelector } from 'reselect'; import { List as ImmutableList } from 'immutable'; import { debounce } from 'lodash'; import ScrollableList from '../../components/scrollable_list'; +import LoadMore from '../../components/load_more'; const messages = defineMessages({ title: { id: 'column.notifications', defaultMessage: 'Notifications' }, @@ -21,13 +22,31 @@ const messages = defineMessages({ const getNotifications = createSelector([ state => ImmutableList(state.getIn(['settings', 'notifications', 'shows']).filter(item => !item).keys()), state => state.getIn(['notifications', 'items']), -], (excludedTypes, notifications) => notifications.filterNot(item => excludedTypes.includes(item.get('type')))); +], (excludedTypes, notifications) => notifications.filterNot(item => item !== null && excludedTypes.includes(item.get('type')))); + +class LoadGap extends React.PureComponent { + + static propTypes = { + disabled: PropTypes.bool, + maxId: PropTypes.string, + onClick: PropTypes.func.isRequired, + }; + + handleClick = () => { + this.props.onClick(this.props.maxId); + } + + render () { + return ; + } + +} const mapStateToProps = state => ({ notifications: getNotifications(state), isLoading: state.getIn(['notifications', 'isLoading'], true), isUnread: state.getIn(['notifications', 'unread']) > 0, - hasMore: !!state.getIn(['notifications', 'next']), + hasMore: state.getIn(['notifications', 'hasMore']), }); @connect(mapStateToProps) @@ -51,14 +70,19 @@ export default class Notifications extends React.PureComponent { }; componentWillUnmount () { - this.handleLoadMore.cancel(); + this.handleLoadOlder.cancel(); this.handleScrollToTop.cancel(); this.handleScroll.cancel(); this.props.dispatch(scrollTopNotifications(false)); } - handleLoadMore = debounce(() => { - this.props.dispatch(expandNotifications()); + handleLoadGap = (maxId) => { + this.props.dispatch(expandNotifications({ maxId })); + }; + + handleLoadOlder = debounce(() => { + const last = this.props.notifications.last(); + this.props.dispatch(expandNotifications({ maxId: last && last.get('id') })); }, 300, { leading: true }); handleScrollToTop = debounce(() => { @@ -93,12 +117,12 @@ export default class Notifications extends React.PureComponent { } handleMoveUp = id => { - const elementIndex = this.props.notifications.findIndex(item => item.get('id') === id) - 1; + const elementIndex = this.props.notifications.findIndex(item => item !== null && item.get('id') === id) - 1; this._selectChild(elementIndex); } handleMoveDown = id => { - const elementIndex = this.props.notifications.findIndex(item => item.get('id') === id) + 1; + const elementIndex = this.props.notifications.findIndex(item => item !== null && item.get('id') === id) + 1; this._selectChild(elementIndex); } @@ -120,7 +144,14 @@ export default class Notifications extends React.PureComponent { if (isLoading && this.scrollableContent) { scrollableContent = this.scrollableContent; } else if (notifications.size > 0 || hasMore) { - scrollableContent = notifications.map((item) => ( + scrollableContent = notifications.map((item, index) => item === null ? ( + 0 ? notifications.getIn([index - 1, 'id']) : null} + onClick={this.handleLoadGap} + /> + ) : ( ImmutableMap({ @@ -48,35 +44,41 @@ const normalizeNotification = (state, notification) => { }); }; -const normalizeNotifications = (state, notifications, next) => { - let newItems = ImmutableList(); - const loaded = state.get('loaded'); +const newer = (m, n) => { + const mId = m.get('id'); + const nId = n.get('id'); - notifications.forEach((n, i) => { - newItems = newItems.set(i, notificationToMap(n)); - }); - - if (state.get('next') === null) { - state = state.set('next', next); - } - - return state - .update('items', oldItems => loaded ? newItems.concat(oldItems) : oldItems.concat(newItems)) - .set('loaded', true) - .set('isLoading', false); + return mId.length === nId.length ? mId > nId : mId.length > nId.length; }; -const appendNormalizedNotifications = (state, notifications, next) => { +const expandNormalizedNotifications = (state, notifications, next) => { let items = ImmutableList(); notifications.forEach((n, i) => { items = items.set(i, notificationToMap(n)); }); - return state - .update('items', list => list.concat(items)) - .set('next', next) - .set('isLoading', false); + return state.withMutations(mutable => { + if (!items.isEmpty()) { + mutable.update('items', list => { + const lastIndex = 1 + list.findLastIndex( + item => item !== null && (newer(item, items.last()) || item.get('id') === items.last().get('id')) + ); + + const firstIndex = 1 + list.take(lastIndex).findLastIndex( + item => item !== null && newer(item, items.first()) + ); + + return list.take(firstIndex).concat(items, list.skip(lastIndex)); + }); + } + + if (!next) { + mutable.set('hasMore', true); + } + + mutable.set('isLoading', false); + }); }; const filterNotifications = (state, relationship) => { @@ -97,27 +99,27 @@ const deleteByStatus = (state, statusId) => { export default function notifications(state = initialState, action) { switch(action.type) { - case NOTIFICATIONS_REFRESH_REQUEST: case NOTIFICATIONS_EXPAND_REQUEST: return state.set('isLoading', true); - case NOTIFICATIONS_REFRESH_FAIL: case NOTIFICATIONS_EXPAND_FAIL: return state.set('isLoading', false); case NOTIFICATIONS_SCROLL_TOP: return updateTop(state, action.top); case NOTIFICATIONS_UPDATE: return normalizeNotification(state, action.notification); - case NOTIFICATIONS_REFRESH_SUCCESS: - return normalizeNotifications(state, action.notifications, action.next); case NOTIFICATIONS_EXPAND_SUCCESS: - return appendNormalizedNotifications(state, action.notifications, action.next); + return expandNormalizedNotifications(state, action.notifications, action.next); case ACCOUNT_BLOCK_SUCCESS: case ACCOUNT_MUTE_SUCCESS: return filterNotifications(state, action.relationship); case NOTIFICATIONS_CLEAR: - return state.set('items', ImmutableList()).set('next', null); + return state.set('items', ImmutableList()).set('hasMore', false); case TIMELINE_DELETE: return deleteByStatus(state, action.id); + case TIMELINE_DISCONNECT: + return action.timeline === 'home' ? + state.update('items', items => items.first() ? items.unshift(null) : items) : + state; default: return state; } From 85a395fab6d7077a252bfe6f96673931ea3aa5ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Miko=C5=82ajczak?= Date: Sun, 25 Mar 2018 16:33:07 +0200 Subject: [PATCH 038/381] i18n: Update Polish translation (#6903) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Marcin Mikołajczak --- app/javascript/mastodon/locales/defaultMessages.json | 2 +- app/javascript/mastodon/locales/pl.json | 4 ++-- config/locales/pl.yml | 13 +++++++++++++ 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/app/javascript/mastodon/locales/defaultMessages.json b/app/javascript/mastodon/locales/defaultMessages.json index eee60c5..76b302f 100644 --- a/app/javascript/mastodon/locales/defaultMessages.json +++ b/app/javascript/mastodon/locales/defaultMessages.json @@ -1748,4 +1748,4 @@ ], "path": "app/javascript/mastodon/middleware/errors.json" } -] +] \ No newline at end of file diff --git a/app/javascript/mastodon/locales/pl.json b/app/javascript/mastodon/locales/pl.json index 0b6f178..7262ce7 100644 --- a/app/javascript/mastodon/locales/pl.json +++ b/app/javascript/mastodon/locales/pl.json @@ -28,8 +28,8 @@ "account.unmute": "Cofnij wyciszenie @{name}", "account.unmute_notifications": "Cofnij wyciszenie powiadomień od @{name}", "account.view_full_profile": "Wyświetl pełny profil", - "alert.unexpected.message": "An unexpected error occurred.", - "alert.unexpected.title": "Oops!", + "alert.unexpected.message": "Wystąpił nieoczekiwany błąd.", + "alert.unexpected.title": "O nie!", "boost_modal.combo": "Naciśnij {combo}, aby pominąć to następnym razem", "bundle_column_error.body": "Coś poszło nie tak podczas ładowania tego składnika.", "bundle_column_error.retry": "Spróbuj ponownie", diff --git a/config/locales/pl.yml b/config/locales/pl.yml index de43ca9..e92742e 100644 --- a/config/locales/pl.yml +++ b/config/locales/pl.yml @@ -383,6 +383,7 @@ pl: security: Bezpieczeństwo set_new_password: Ustaw nowe hasło authorize_follow: + already_following: Już śledzisz to konto error: Niestety, podczas sprawdzania zdalnego konta wystąpił błąd follow: Śledź follow_request: 'Wysłano prośbę o pozwolenie na śledzenie:' @@ -475,6 +476,7 @@ pl: '21600': 6 godzinach '3600': godzinie '43200': 12 godzinach + '604800': 1 tygodniu '86400': dobie expires_in_prompt: Nigdy generate: Wygeneruj @@ -643,6 +645,17 @@ pl: statuses: attached: description: 'Przytwierdzony: %{attached}' + image: + few: "%{count} obrazy" + many: "%{count} obrazów" + one: "%{count} obraz" + other: "%{count} obrazów" + video: + few: "%{count} filmy" + many: "%{count} filmów" + one: "%{count} film" + other: "%{count} filmów" + content_warning: 'Ostrzeżenie o zawartości: %{warning}' open_in_web: Otwórz w przeglądarce over_character_limit: limit %{max} znaków przekroczony pin_errors: From 3b2c7a33a9fe151b65724f9076a4ef93ad1d6948 Mon Sep 17 00:00:00 2001 From: Yann Klis Date: Mon, 26 Mar 2018 12:47:34 +0200 Subject: [PATCH 039/381] Missing OTP_SECRET in scalingo.json (#6917) --- scalingo.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scalingo.json b/scalingo.json index 426698b..0cc648f 100644 --- a/scalingo.json +++ b/scalingo.json @@ -21,6 +21,10 @@ "description": "The secret key base", "generator": "secret" }, + "OTP_SECRET": { + "description": "One-time password secret", + "generator": "secret" + }, "SINGLE_USER_MODE": { "description": "Should the instance run in single user mode? (Disable registrations, redirect to front page)", "value": "false", From 605a92b4607589c64acf9c5cb58d2fcc68e2606a Mon Sep 17 00:00:00 2001 From: unarist Date: Mon, 26 Mar 2018 19:48:01 +0900 Subject: [PATCH 040/381] Fix moved account handling in IndexedDB feature (#6915) * Fix stack overflow on importFetchedAccounts When the account has moved property, it should process destination account instead of source account itself. * Set account id instead of account object for moved property This restores "foo has moved to" indication on account view, and fixes `reblog` index on `accounts` object store. --- app/javascript/mastodon/actions/importer/index.js | 2 +- app/javascript/mastodon/actions/importer/normalizer.js | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/app/javascript/mastodon/actions/importer/index.js b/app/javascript/mastodon/actions/importer/index.js index d1ea40c..a97f4d1 100644 --- a/app/javascript/mastodon/actions/importer/index.js +++ b/app/javascript/mastodon/actions/importer/index.js @@ -39,7 +39,7 @@ export function importFetchedAccounts(accounts) { pushUnique(normalAccounts, normalizeAccount(account)); if (account.moved) { - processAccount(account); + processAccount(account.moved); } } diff --git a/app/javascript/mastodon/actions/importer/normalizer.js b/app/javascript/mastodon/actions/importer/normalizer.js index c88f694..1b09f31 100644 --- a/app/javascript/mastodon/actions/importer/normalizer.js +++ b/app/javascript/mastodon/actions/importer/normalizer.js @@ -10,6 +10,10 @@ export function normalizeAccount(account) { account.display_name_html = emojify(escapeTextContentForBrowser(displayName)); account.note_emojified = emojify(account.note); + if (account.moved) { + account.moved = account.moved.id; + } + return account; } From f691afaae913fdb3041864b2824ca092e092ba84 Mon Sep 17 00:00:00 2001 From: Yuto Tokunaga Date: Mon, 26 Mar 2018 20:59:21 +0900 Subject: [PATCH 041/381] Refactor scss (#6913) * Refactoring scss introduce scss variables for the media modal fix css block structure corresponding to react components fix flex layouts remove background image of the loaded image on the media modal * Fix typo --- app/javascript/styles/mastodon/components.scss | 49 +++++++++++--------------- app/javascript/styles/mastodon/variables.scss | 5 +++ 2 files changed, 26 insertions(+), 28 deletions(-) diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index ea6e393..1fb1fa8 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -1435,14 +1435,19 @@ position: relative; width: 100%; height: 100%; + display: flex; + align-items: center; + justify-content: center; - &.image-loader--loading { - display: flex; - align-content: center; + .image-loader__preview-canvas { + max-width: $media-modal-media-max-width; + max-height: $media-modal-media-max-height; + background: url('../images/void.png') repeat; + object-fit: contain; + } - .image-loader__preview-canvas { - filter: blur(2px); - } + &.image-loader--loading .image-loader__preview-canvas { + filter: blur(2px); } &.image-loader--amorphous .image-loader__preview-canvas { @@ -1455,7 +1460,16 @@ width: 100%; height: 100%; display: flex; - align-content: center; + align-items: center; + justify-content: center; + + img { + max-width: $media-modal-media-max-width; + max-height: $media-modal-media-max-height; + width: auto; + height: auto; + object-fit: contain; + } } .navigation-bar { @@ -3422,27 +3436,6 @@ a.status-card { width: 100%; height: 100%; position: relative; - - img, - canvas, - video { - max-width: 100%; - /* - put margins on top and bottom of image to avoid the screen coverd by - image. - */ - max-height: 80%; - width: auto; - height: auto; - margin: auto; - } - - img, - canvas { - display: block; - background: url('../images/void.png') repeat; - object-fit: contain; - } } .media-modal__closer { diff --git a/app/javascript/styles/mastodon/variables.scss b/app/javascript/styles/mastodon/variables.scss index dcc2857..e456c27 100644 --- a/app/javascript/styles/mastodon/variables.scss +++ b/app/javascript/styles/mastodon/variables.scss @@ -30,3 +30,8 @@ $ui-highlight-color: $classic-highlight-color !default; // Vibrant // Language codes that uses CJK fonts $cjk-langs: ja, ko, zh-CN, zh-HK, zh-TW; + +// Variables for components +$media-modal-media-max-width: 100%; +// put margins on top and bottom of image to avoid the screen covered by image. +$media-modal-media-max-height: 80%; From 18965cb0e611b226c6252f1669f228f5b95f1ac6 Mon Sep 17 00:00:00 2001 From: Stephen Burgess Date: Mon, 26 Mar 2018 07:59:44 -0400 Subject: [PATCH 042/381] feat(ShowMore): Add classname to show more/show less button (#6904) --- app/javascript/mastodon/components/status_content.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/javascript/mastodon/components/status_content.js b/app/javascript/mastodon/components/status_content.js index b6082f0..9b86592 100644 --- a/app/javascript/mastodon/components/status_content.js +++ b/app/javascript/mastodon/components/status_content.js @@ -158,7 +158,7 @@ export default class StatusContent extends React.PureComponent { {mentionsPlaceholder} From 40e5d2303ba1edc51beae66cc15263675980106a Mon Sep 17 00:00:00 2001 From: Akihiko Odaki Date: Mon, 26 Mar 2018 21:02:10 +0900 Subject: [PATCH 043/381] Validate HTTP response length while receiving (#6891) to_s method of HTTP::Response keeps blocking while it receives the whole content, no matter how it is big. This means it may waste time to receive unacceptably large files. It may also consume memory and disk in the process. This solves the inefficency by checking response length while receiving. --- app/helpers/jsonld_helper.rb | 2 +- app/lib/exceptions.rb | 1 + app/lib/provider_discovery.rb | 2 +- app/lib/request.rb | 31 +++++++++++++++- app/models/account.rb | 1 - app/models/application_record.rb | 1 + app/models/concerns/account_avatar.rb | 4 +- app/models/concerns/account_header.rb | 4 +- app/models/concerns/remotable.rb | 6 +-- app/models/custom_emoji.rb | 6 ++- app/models/media_attachment.rb | 7 ++-- app/models/preview_card.rb | 5 ++- app/services/fetch_atom_service.rb | 11 +++--- app/services/fetch_link_card_service.rb | 2 +- app/services/resolve_account_service.rb | 2 +- app/workers/pubsubhubbub/confirmation_worker.rb | 2 +- spec/lib/request_spec.rb | 49 +++++++++++++++++++++++++ spec/models/concerns/remotable_spec.rb | 5 ++- 18 files changed, 115 insertions(+), 26 deletions(-) diff --git a/app/helpers/jsonld_helper.rb b/app/helpers/jsonld_helper.rb index 957a2cb..dfb8fcb 100644 --- a/app/helpers/jsonld_helper.rb +++ b/app/helpers/jsonld_helper.rb @@ -61,7 +61,7 @@ module JsonLdHelper def fetch_resource_without_id_validation(uri) build_request(uri).perform do |response| - response.code == 200 ? body_to_json(response.to_s) : nil + response.code == 200 ? body_to_json(response.body_with_limit) : nil end end diff --git a/app/lib/exceptions.rb b/app/lib/exceptions.rb index 95e3365..e88e98e 100644 --- a/app/lib/exceptions.rb +++ b/app/lib/exceptions.rb @@ -5,6 +5,7 @@ module Mastodon class NotPermittedError < Error; end class ValidationError < Error; end class HostValidationError < ValidationError; end + class LengthValidationError < ValidationError; end class RaceConditionError < Error; end class UnexpectedResponseError < Error diff --git a/app/lib/provider_discovery.rb b/app/lib/provider_discovery.rb index bbd3a2d..3bec721 100644 --- a/app/lib/provider_discovery.rb +++ b/app/lib/provider_discovery.rb @@ -18,7 +18,7 @@ class ProviderDiscovery < OEmbed::ProviderDiscovery else Request.new(:get, url).perform do |res| raise OEmbed::NotFound, url if res.code != 200 || res.mime_type != 'text/html' - Nokogiri::HTML(res.to_s) + Nokogiri::HTML(res.body_with_limit) end end diff --git a/app/lib/request.rb b/app/lib/request.rb index 8a127c6..dca93a6 100644 --- a/app/lib/request.rb +++ b/app/lib/request.rb @@ -40,7 +40,7 @@ class Request end begin - yield response + yield response.extend(ClientLimit) ensure http_client.close end @@ -99,6 +99,33 @@ class Request @http_client ||= HTTP.timeout(:per_operation, timeout).follow(max_hops: 2) end + module ClientLimit + def body_with_limit(limit = 1.megabyte) + raise Mastodon::LengthValidationError if content_length.present? && content_length > limit + + if charset.nil? + encoding = Encoding::BINARY + else + begin + encoding = Encoding.find(charset) + rescue ArgumentError + encoding = Encoding::BINARY + end + end + + contents = String.new(encoding: encoding) + + while (chunk = readpartial) + contents << chunk + chunk.clear + + raise Mastodon::LengthValidationError if contents.bytesize > limit + end + + contents + end + end + class Socket < TCPSocket class << self def open(host, *args) @@ -118,5 +145,5 @@ class Request end end - private_constant :Socket + private_constant :ClientLimit, :Socket end diff --git a/app/models/account.rb b/app/models/account.rb index 9a83d97..25e7d74 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -55,7 +55,6 @@ class Account < ApplicationRecord include AccountHeader include AccountInteractions include Attachmentable - include Remotable include Paginable enum protocol: [:ostatus, :activitypub] diff --git a/app/models/application_record.rb b/app/models/application_record.rb index 71fbba5..83134d4 100644 --- a/app/models/application_record.rb +++ b/app/models/application_record.rb @@ -2,4 +2,5 @@ class ApplicationRecord < ActiveRecord::Base self.abstract_class = true + include Remotable end diff --git a/app/models/concerns/account_avatar.rb b/app/models/concerns/account_avatar.rb index 9e34a94..2d5ebfc 100644 --- a/app/models/concerns/account_avatar.rb +++ b/app/models/concerns/account_avatar.rb @@ -4,6 +4,7 @@ module AccountAvatar extend ActiveSupport::Concern IMAGE_MIME_TYPES = ['image/jpeg', 'image/png', 'image/gif'].freeze + LIMIT = 2.megabytes class_methods do def avatar_styles(file) @@ -19,7 +20,8 @@ module AccountAvatar # Avatar upload has_attached_file :avatar, styles: ->(f) { avatar_styles(f) }, convert_options: { all: '-strip' }, processors: [:lazy_thumbnail] validates_attachment_content_type :avatar, content_type: IMAGE_MIME_TYPES - validates_attachment_size :avatar, less_than: 2.megabytes + validates_attachment_size :avatar, less_than: LIMIT + remotable_attachment :avatar, LIMIT end def avatar_original_url diff --git a/app/models/concerns/account_header.rb b/app/models/concerns/account_header.rb index 04c576b..ef40b81 100644 --- a/app/models/concerns/account_header.rb +++ b/app/models/concerns/account_header.rb @@ -4,6 +4,7 @@ module AccountHeader extend ActiveSupport::Concern IMAGE_MIME_TYPES = ['image/jpeg', 'image/png', 'image/gif'].freeze + LIMIT = 2.megabytes class_methods do def header_styles(file) @@ -19,7 +20,8 @@ module AccountHeader # Header upload has_attached_file :header, styles: ->(f) { header_styles(f) }, convert_options: { all: '-strip' }, processors: [:lazy_thumbnail] validates_attachment_content_type :header, content_type: IMAGE_MIME_TYPES - validates_attachment_size :header, less_than: 2.megabytes + validates_attachment_size :header, less_than: LIMIT + remotable_attachment :header, LIMIT end def header_original_url diff --git a/app/models/concerns/remotable.rb b/app/models/concerns/remotable.rb index 0f18c5d..3b8c507 100644 --- a/app/models/concerns/remotable.rb +++ b/app/models/concerns/remotable.rb @@ -3,8 +3,8 @@ module Remotable extend ActiveSupport::Concern - included do - attachment_definitions.each_key do |attachment_name| + class_methods do + def remotable_attachment(attachment_name, limit) attribute_name = "#{attachment_name}_remote_url".to_sym method_name = "#{attribute_name}=".to_sym alt_method_name = "reset_#{attachment_name}!".to_sym @@ -33,7 +33,7 @@ module Remotable File.extname(filename) end - send("#{attachment_name}=", StringIO.new(response.to_s)) + send("#{attachment_name}=", StringIO.new(response.body_with_limit(limit))) send("#{attachment_name}_file_name=", basename + extname) self[attribute_name] = url if has_attribute?(attribute_name) diff --git a/app/models/custom_emoji.rb b/app/models/custom_emoji.rb index a77b53c..476178e 100644 --- a/app/models/custom_emoji.rb +++ b/app/models/custom_emoji.rb @@ -19,6 +19,8 @@ # class CustomEmoji < ApplicationRecord + LIMIT = 50.kilobytes + SHORTCODE_RE_FRAGMENT = '[a-zA-Z0-9_]{2,}' SCAN_RE = /(?<=[^[:alnum:]:]|\n|^) @@ -29,14 +31,14 @@ class CustomEmoji < ApplicationRecord has_attached_file :image, styles: { static: { format: 'png', convert_options: '-coalesce -strip' } } - validates_attachment :image, content_type: { content_type: 'image/png' }, presence: true, size: { in: 0..50.kilobytes } + validates_attachment :image, content_type: { content_type: 'image/png' }, presence: true, size: { less_than: LIMIT } validates :shortcode, uniqueness: { scope: :domain }, format: { with: /\A#{SHORTCODE_RE_FRAGMENT}\z/ }, length: { minimum: 2 } scope :local, -> { where(domain: nil) } scope :remote, -> { where.not(domain: nil) } scope :alphabetic, -> { order(domain: :asc, shortcode: :asc) } - include Remotable + remotable_attachment :image, LIMIT def local? domain.nil? diff --git a/app/models/media_attachment.rb b/app/models/media_attachment.rb index a4d9cd9..ac2aa7e 100644 --- a/app/models/media_attachment.rb +++ b/app/models/media_attachment.rb @@ -56,6 +56,8 @@ class MediaAttachment < ApplicationRecord }, }.freeze + LIMIT = 8.megabytes + belongs_to :account, inverse_of: :media_attachments, optional: true belongs_to :status, inverse_of: :media_attachments, optional: true @@ -64,10 +66,9 @@ class MediaAttachment < ApplicationRecord processors: ->(f) { file_processors f }, convert_options: { all: '-quality 90 -strip' } - include Remotable - validates_attachment_content_type :file, content_type: IMAGE_MIME_TYPES + VIDEO_MIME_TYPES - validates_attachment_size :file, less_than: 8.megabytes + validates_attachment_size :file, less_than: LIMIT + remotable_attachment :file, LIMIT validates :account, presence: true validates :description, length: { maximum: 420 }, if: :local? diff --git a/app/models/preview_card.rb b/app/models/preview_card.rb index 86eecdf..0c82f06 100644 --- a/app/models/preview_card.rb +++ b/app/models/preview_card.rb @@ -26,6 +26,7 @@ class PreviewCard < ApplicationRecord IMAGE_MIME_TYPES = ['image/jpeg', 'image/png', 'image/gif'].freeze + LIMIT = 1.megabytes self.inheritance_column = false @@ -36,11 +37,11 @@ class PreviewCard < ApplicationRecord has_attached_file :image, styles: { original: { geometry: '400x400>', file_geometry_parser: FastGeometryParser } }, convert_options: { all: '-quality 80 -strip' } include Attachmentable - include Remotable validates :url, presence: true, uniqueness: true validates_attachment_content_type :image, content_type: IMAGE_MIME_TYPES - validates_attachment_size :image, less_than: 1.megabytes + validates_attachment_size :image, less_than: LIMIT + remotable_attachment :image, LIMIT before_save :extract_dimensions, if: :link? diff --git a/app/services/fetch_atom_service.rb b/app/services/fetch_atom_service.rb index 48ad5dc..62dea82 100644 --- a/app/services/fetch_atom_service.rb +++ b/app/services/fetch_atom_service.rb @@ -38,13 +38,14 @@ class FetchAtomService < BaseService return nil if response.code != 200 if response.mime_type == 'application/atom+xml' - [@url, { prefetched_body: response.to_s }, :ostatus] + [@url, { prefetched_body: response.body_with_limit }, :ostatus] elsif ['application/activity+json', 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'].include?(response.mime_type) - json = body_to_json(response.to_s) + body = response.body_with_limit + json = body_to_json(body) if supported_context?(json) && json['type'] == 'Person' && json['inbox'].present? - [json['id'], { prefetched_body: response.to_s, id: true }, :activitypub] + [json['id'], { prefetched_body: body, id: true }, :activitypub] elsif supported_context?(json) && json['type'] == 'Note' - [json['id'], { prefetched_body: response.to_s, id: true }, :activitypub] + [json['id'], { prefetched_body: body, id: true }, :activitypub] else @unsupported_activity = true nil @@ -61,7 +62,7 @@ class FetchAtomService < BaseService end def process_html(response) - page = Nokogiri::HTML(response.to_s) + page = Nokogiri::HTML(response.body_with_limit) json_link = page.xpath('//link[@rel="alternate"]').find { |link| ['application/activity+json', 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'].include?(link['type']) } atom_link = page.xpath('//link[@rel="alternate"]').find { |link| link['type'] == 'application/atom+xml' } diff --git a/app/services/fetch_link_card_service.rb b/app/services/fetch_link_card_service.rb index 26deb5e..d5920a4 100644 --- a/app/services/fetch_link_card_service.rb +++ b/app/services/fetch_link_card_service.rb @@ -45,7 +45,7 @@ class FetchLinkCardService < BaseService Request.new(:get, @url).perform do |res| if res.code == 200 && res.mime_type == 'text/html' - @html = res.to_s + @html = res.body_with_limit @html_charset = res.charset else @html = nil diff --git a/app/services/resolve_account_service.rb b/app/services/resolve_account_service.rb index 034821d..744ea24 100644 --- a/app/services/resolve_account_service.rb +++ b/app/services/resolve_account_service.rb @@ -181,7 +181,7 @@ class ResolveAccountService < BaseService @atom_body = Request.new(:get, atom_url).perform do |response| raise Mastodon::UnexpectedResponseError, response unless response.code == 200 - response.to_s + response.body_with_limit end end diff --git a/app/workers/pubsubhubbub/confirmation_worker.rb b/app/workers/pubsubhubbub/confirmation_worker.rb index cc2d122..c0e7b67 100644 --- a/app/workers/pubsubhubbub/confirmation_worker.rb +++ b/app/workers/pubsubhubbub/confirmation_worker.rb @@ -57,7 +57,7 @@ class Pubsubhubbub::ConfirmationWorker def callback_get_with_params Request.new(:get, subscription.callback_url, params: callback_params).perform do |response| - @callback_response_body = response.body.to_s + @callback_response_body = response.body_with_limit end end diff --git a/spec/lib/request_spec.rb b/spec/lib/request_spec.rb index 4d6b20d..939ac00 100644 --- a/spec/lib/request_spec.rb +++ b/spec/lib/request_spec.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require 'rails_helper' +require 'securerandom' describe Request do subject { Request.new(:get, 'http://example.com') } @@ -64,6 +65,12 @@ describe Request do expect_any_instance_of(HTTP::Client).to receive(:close) expect { |block| subject.perform &block }.to yield_control end + + it 'returns response which implements body_with_limit' do + subject.perform do |response| + expect(response).to respond_to :body_with_limit + end + end end context 'with private host' do @@ -81,4 +88,46 @@ describe Request do end end end + + describe "response's body_with_limit method" do + it 'rejects body more than 1 megabyte by default' do + stub_request(:any, 'http://example.com').to_return(body: SecureRandom.random_bytes(2.megabytes)) + expect { subject.perform { |response| response.body_with_limit } }.to raise_error Mastodon::LengthValidationError + end + + it 'accepts body less than 1 megabyte by default' do + stub_request(:any, 'http://example.com').to_return(body: SecureRandom.random_bytes(2.kilobytes)) + expect { subject.perform { |response| response.body_with_limit } }.not_to raise_error + end + + it 'rejects body by given size' do + stub_request(:any, 'http://example.com').to_return(body: SecureRandom.random_bytes(2.kilobytes)) + expect { subject.perform { |response| response.body_with_limit(1.kilobyte) } }.to raise_error Mastodon::LengthValidationError + end + + it 'rejects too large chunked body' do + stub_request(:any, 'http://example.com').to_return(body: SecureRandom.random_bytes(2.megabytes), headers: { 'Transfer-Encoding' => 'chunked' }) + expect { subject.perform { |response| response.body_with_limit } }.to raise_error Mastodon::LengthValidationError + end + + it 'rejects too large monolithic body' do + stub_request(:any, 'http://example.com').to_return(body: SecureRandom.random_bytes(2.megabytes), headers: { 'Content-Length' => 2.megabytes }) + expect { subject.perform { |response| response.body_with_limit } }.to raise_error Mastodon::LengthValidationError + end + + it 'uses binary encoding if Content-Type does not tell encoding' do + stub_request(:any, 'http://example.com').to_return(body: '', headers: { 'Content-Type' => 'text/html' }) + expect(subject.perform { |response| response.body_with_limit.encoding }).to eq Encoding::BINARY + end + + it 'uses binary encoding if Content-Type tells unknown encoding' do + stub_request(:any, 'http://example.com').to_return(body: '', headers: { 'Content-Type' => 'text/html; charset=unknown' }) + expect(subject.perform { |response| response.body_with_limit.encoding }).to eq Encoding::BINARY + end + + it 'uses encoding specified by Content-Type' do + stub_request(:any, 'http://example.com').to_return(body: '', headers: { 'Content-Type' => 'text/html; charset=UTF-8' }) + expect(subject.perform { |response| response.body_with_limit.encoding }).to eq Encoding::UTF_8 + end + end end diff --git a/spec/models/concerns/remotable_spec.rb b/spec/models/concerns/remotable_spec.rb index 0b2dad2..b392337 100644 --- a/spec/models/concerns/remotable_spec.rb +++ b/spec/models/concerns/remotable_spec.rb @@ -29,7 +29,10 @@ RSpec.describe Remotable do context 'Remotable module is included' do before do - class Foo; include Remotable; end + class Foo + include Remotable + remotable_attachment :hoge, 1.kilobyte + end end let(:attribute_name) { "#{hoge}_remote_url".to_sym } From 2a90da18375a38957ae4c94fa3e86a8180237d8a Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Tue, 27 Mar 2018 04:33:57 +0200 Subject: [PATCH 044/381] Fix UniqueUsernameValidator comparison (#6926) Comparison was downcasing only one side, therefore if previously existing account had a non-lowercase spelling, it would be ignored when checking for duplicates. New rake task `mastodon:maintenance:find_duplicate_usernames` will help find constraint violations that might have occured from the presence of this bug. Bump version to 2.3.3 --- app/models/concerns/account_finder_concern.rb | 2 +- app/validators/unique_username_validator.rb | 2 +- lib/mastodon/version.rb | 2 +- lib/tasks/mastodon.rake | 18 ++++++++++++++++++ 4 files changed, 21 insertions(+), 3 deletions(-) diff --git a/app/models/concerns/account_finder_concern.rb b/app/models/concerns/account_finder_concern.rb index 2e8a7fb..6b7237e 100644 --- a/app/models/concerns/account_finder_concern.rb +++ b/app/models/concerns/account_finder_concern.rb @@ -30,7 +30,7 @@ module AccountFinderConcern end def account - scoped_accounts.take + scoped_accounts.order(id: :asc).take end private diff --git a/app/validators/unique_username_validator.rb b/app/validators/unique_username_validator.rb index c76407b..fb67105 100644 --- a/app/validators/unique_username_validator.rb +++ b/app/validators/unique_username_validator.rb @@ -6,7 +6,7 @@ class UniqueUsernameValidator < ActiveModel::Validator normalized_username = account.username.downcase.delete('.') - scope = Account.where(domain: nil, username: normalized_username) + scope = Account.where(domain: nil).where('lower(username) = ?', normalized_username) scope = scope.where.not(id: account.id) if account.persisted? account.errors.add(:username, :taken) if scope.exists? diff --git a/lib/mastodon/version.rb b/lib/mastodon/version.rb index 121c5c6..a6927ee 100644 --- a/lib/mastodon/version.rb +++ b/lib/mastodon/version.rb @@ -13,7 +13,7 @@ module Mastodon end def patch - 2 + 3 end def pre diff --git a/lib/tasks/mastodon.rake b/lib/tasks/mastodon.rake index 0972e43..cfd6a1d 100644 --- a/lib/tasks/mastodon.rake +++ b/lib/tasks/mastodon.rake @@ -740,6 +740,24 @@ namespace :mastodon do LinkCrawlWorker.push_bulk status_ids end + desc 'Find case-insensitive username duplicates of local users' + task find_duplicate_usernames: :environment do + include RoutingHelper + + disable_log_stdout! + + duplicate_masters = Account.find_by_sql('SELECT * FROM accounts WHERE id IN (SELECT min(id) FROM accounts WHERE domain IS NULL GROUP BY lower(username) HAVING count(*) > 1)') + pastel = Pastel.new + + duplicate_masters.each do |account| + puts pastel.yellow("First of their name: ") + pastel.bold(account.username) + " (#{admin_account_url(account.id)})" + + Account.where('lower(username) = ?', account.username.downcase).where.not(id: account.id).each do |duplicate| + puts " " + pastel.red("Duplicate: ") + admin_account_url(duplicate.id) + end + end + end + desc 'Remove all home feed regeneration markers' task remove_regeneration_markers: :environment do keys = Redis.current.keys('account:*:regeneration') From a1d091558555c8a58f7f63c4a9c3002421ea9041 Mon Sep 17 00:00:00 2001 From: unarist Date: Tue, 27 Mar 2018 12:22:58 +0900 Subject: [PATCH 045/381] Add a spec for UniqueUsernameValidator (#6927) Note that this spec has a pending test about dots in the username, because allowing it has been reverted for now. --- spec/validators/unique_username_validator_spec.rb | 33 +++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 spec/validators/unique_username_validator_spec.rb diff --git a/spec/validators/unique_username_validator_spec.rb b/spec/validators/unique_username_validator_spec.rb new file mode 100644 index 0000000..b9d773b --- /dev/null +++ b/spec/validators/unique_username_validator_spec.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe UniqueUsernameValidator do + describe '#validate' do + it 'does not add errors if username is nil' do + account = double(username: nil, persisted?: false, errors: double(add: nil)) + subject.validate(account) + expect(account.errors).to_not have_received(:add) + end + + it 'does not add errors when existing one is subject itself' do + account = Fabricate(:account, username: 'abcdef') + expect(account).to be_valid + end + + it 'adds an error when the username is already used with ignoring dots' do + pending 'allowing dots in username is still in development' + Fabricate(:account, username: 'abcd.ef') + account = double(username: 'ab.cdef', persisted?: false, errors: double(add: nil)) + subject.validate(account) + expect(account.errors).to have_received(:add) + end + + it 'adds an error when the username is already used with ignoring cases' do + Fabricate(:account, username: 'ABCdef') + account = double(username: 'abcDEF', persisted?: false, errors: double(add: nil)) + subject.validate(account) + expect(account.errors).to have_received(:add) + end + end +end From 31e7b7308489ecc8b43f83b78ec0a288c4195d5b Mon Sep 17 00:00:00 2001 From: Yuto Tokunaga Date: Tue, 27 Mar 2018 19:30:28 +0900 Subject: [PATCH 046/381] fix #6846 (#6914) --- app/javascript/styles/mastodon/components.scss | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index 1fb1fa8..2b13b80 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -3436,6 +3436,19 @@ a.status-card { width: 100%; height: 100%; position: relative; + + .extended-video-player { + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; + + video { + max-width: $media-modal-media-max-width; + max-height: $media-modal-media-max-height; + } + } } .media-modal__closer { @@ -4411,8 +4424,8 @@ a.status-card { border-radius: 4px; video { - height: 100%; - width: 100%; + max-width: 100vw; + max-height: 80vh; z-index: 1; } From ca42f9b0ebfa1f4e8e86745a79af138b5865daee Mon Sep 17 00:00:00 2001 From: Akihiko Odaki Date: Tue, 27 Mar 2018 19:32:30 +0900 Subject: [PATCH 047/381] Cache media (#6902) --- app/javascript/mastodon/actions/accounts.js | 2 +- app/javascript/mastodon/actions/importer/index.js | 2 +- app/javascript/mastodon/actions/statuses.js | 4 +- app/javascript/mastodon/db/async.js | 28 ---- app/javascript/mastodon/db/modifier.js | 93 ------------- app/javascript/mastodon/service_worker/entry.js | 30 ++++- app/javascript/mastodon/storage/db.js | 28 ++++ app/javascript/mastodon/storage/modifier.js | 151 ++++++++++++++++++++++ config/webpack/production.js | 2 +- package.json | 1 + yarn.lock | 7 + 11 files changed, 217 insertions(+), 131 deletions(-) delete mode 100644 app/javascript/mastodon/db/async.js delete mode 100644 app/javascript/mastodon/db/modifier.js create mode 100644 app/javascript/mastodon/storage/db.js create mode 100644 app/javascript/mastodon/storage/modifier.js diff --git a/app/javascript/mastodon/actions/accounts.js b/app/javascript/mastodon/actions/accounts.js index 1d1947a..7cacff9 100644 --- a/app/javascript/mastodon/actions/accounts.js +++ b/app/javascript/mastodon/actions/accounts.js @@ -1,5 +1,5 @@ import api, { getLinks } from '../api'; -import asyncDB from '../db/async'; +import asyncDB from '../storage/db'; import { importAccount, importFetchedAccount, importFetchedAccounts } from './importer'; export const ACCOUNT_FETCH_REQUEST = 'ACCOUNT_FETCH_REQUEST'; diff --git a/app/javascript/mastodon/actions/importer/index.js b/app/javascript/mastodon/actions/importer/index.js index a97f4d1..e671d41 100644 --- a/app/javascript/mastodon/actions/importer/index.js +++ b/app/javascript/mastodon/actions/importer/index.js @@ -1,4 +1,4 @@ -import { putAccounts, putStatuses } from '../../db/modifier'; +import { putAccounts, putStatuses } from '../../storage/modifier'; import { normalizeAccount, normalizeStatus } from './normalizer'; export const ACCOUNT_IMPORT = 'ACCOUNT_IMPORT'; diff --git a/app/javascript/mastodon/actions/statuses.js b/app/javascript/mastodon/actions/statuses.js index dcd813d..d28aef8 100644 --- a/app/javascript/mastodon/actions/statuses.js +++ b/app/javascript/mastodon/actions/statuses.js @@ -1,6 +1,6 @@ import api from '../api'; -import asyncDB from '../db/async'; -import { evictStatus } from '../db/modifier'; +import asyncDB from '../storage/db'; +import { evictStatus } from '../storage/modifier'; import { deleteFromTimelines } from './timelines'; import { fetchStatusCard } from './cards'; diff --git a/app/javascript/mastodon/db/async.js b/app/javascript/mastodon/db/async.js deleted file mode 100644 index e08fc3f..0000000 --- a/app/javascript/mastodon/db/async.js +++ /dev/null @@ -1,28 +0,0 @@ -import { me } from '../initial_state'; - -export default new Promise((resolve, reject) => { - // Microsoft Edge 17 does not support getAll according to: - // Catalog of standard and vendor APIs across browsers - Microsoft Edge Development - // https://developer.microsoft.com/en-us/microsoft-edge/platform/catalog/?q=specName%3Aindexeddb - if (!me || !('getAll' in IDBObjectStore.prototype)) { - reject(); - return; - } - - const request = indexedDB.open('mastodon:' + me); - - request.onerror = reject; - request.onsuccess = ({ target }) => resolve(target.result); - - request.onupgradeneeded = ({ target }) => { - const accounts = target.result.createObjectStore('accounts', { autoIncrement: true }); - const statuses = target.result.createObjectStore('statuses', { autoIncrement: true }); - - accounts.createIndex('id', 'id', { unique: true }); - accounts.createIndex('moved', 'moved'); - - statuses.createIndex('id', 'id', { unique: true }); - statuses.createIndex('account', 'account'); - statuses.createIndex('reblog', 'reblog'); - }; -}); diff --git a/app/javascript/mastodon/db/modifier.js b/app/javascript/mastodon/db/modifier.js deleted file mode 100644 index eb95190..0000000 --- a/app/javascript/mastodon/db/modifier.js +++ /dev/null @@ -1,93 +0,0 @@ -import asyncDB from './async'; - -const limit = 1024; - -function put(name, objects, callback) { - asyncDB.then(db => { - const putTransaction = db.transaction(name, 'readwrite'); - const putStore = putTransaction.objectStore(name); - const putIndex = putStore.index('id'); - - objects.forEach(object => { - function add() { - putStore.add(object); - } - - putIndex.getKey(object.id).onsuccess = retrieval => { - if (retrieval.target.result) { - putStore.delete(retrieval.target.result).onsuccess = add; - } else { - add(); - } - }; - }); - - putTransaction.oncomplete = () => { - const readTransaction = db.transaction(name, 'readonly'); - const readStore = readTransaction.objectStore(name); - - readStore.count().onsuccess = count => { - const excess = count.target.result - limit; - - if (excess > 0) { - readStore.getAll(null, excess).onsuccess = - retrieval => callback(retrieval.target.result.map(({ id }) => id)); - } - }; - }; - }); -} - -export function evictAccounts(ids) { - asyncDB.then(db => { - const transaction = db.transaction(['accounts', 'statuses'], 'readwrite'); - const accounts = transaction.objectStore('accounts'); - const accountsIdIndex = accounts.index('id'); - const accountsMovedIndex = accounts.index('moved'); - const statuses = transaction.objectStore('statuses'); - const statusesIndex = statuses.index('account'); - - function evict(toEvict) { - toEvict.forEach(id => { - accountsMovedIndex.getAllKeys(id).onsuccess = - ({ target }) => evict(target.result); - - statusesIndex.getAll(id).onsuccess = - ({ target }) => evictStatuses(target.result.map(({ id }) => id)); - - accountsIdIndex.getKey(id).onsuccess = - ({ target }) => target.result && accounts.delete(target.result); - }); - } - - evict(ids); - }); -} - -export function evictStatus(id) { - return evictStatuses([id]); -} - -export function evictStatuses(ids) { - asyncDB.then(db => { - const store = db.transaction('statuses', 'readwrite').objectStore('statuses'); - const idIndex = store.index('id'); - const reblogIndex = store.index('reblog'); - - ids.forEach(id => { - reblogIndex.getAllKeys(id).onsuccess = - ({ target }) => target.result.forEach(reblogKey => store.delete(reblogKey)); - - idIndex.getKey(id).onsuccess = - ({ target }) => target.result && store.delete(target.result); - }); - }); -} - -export function putAccounts(records) { - put('accounts', records, evictAccounts); -} - -export function putStatuses(records) { - put('statuses', records, evictStatuses); -} diff --git a/app/javascript/mastodon/service_worker/entry.js b/app/javascript/mastodon/service_worker/entry.js index 8b65f27..b9cf06e 100644 --- a/app/javascript/mastodon/service_worker/entry.js +++ b/app/javascript/mastodon/service_worker/entry.js @@ -1,6 +1,10 @@ import './web_push_notifications'; -function openCache() { +function openSystemCache() { + return caches.open('mastodon-system'); +} + +function openWebCache() { return caches.open('mastodon-web'); } @@ -11,7 +15,7 @@ function fetchRoot() { // Cause a new version of a registered Service Worker to replace an existing one // that is already installed, and replace the currently active worker on open pages. self.addEventListener('install', function(event) { - event.waitUntil(Promise.all([openCache(), fetchRoot()]).then(([cache, root]) => cache.put('/', root))); + event.waitUntil(Promise.all([openWebCache(), fetchRoot()]).then(([cache, root]) => cache.put('/', root))); }); self.addEventListener('activate', function(event) { event.waitUntil(self.clients.claim()); @@ -21,7 +25,7 @@ self.addEventListener('fetch', function(event) { if (url.pathname.startsWith('/web/')) { const asyncResponse = fetchRoot(); - const asyncCache = openCache(); + const asyncCache = openWebCache(); event.respondWith(asyncResponse.then(async response => { if (response.ok) { @@ -31,10 +35,10 @@ self.addEventListener('fetch', function(event) { } throw null; - }).catch(() => caches.match('/'))); + }).catch(() => asyncCache.then(cache => cache.match('/')))); } else if (url.pathname === '/auth/sign_out') { const asyncResponse = fetch(event.request); - const asyncCache = openCache(); + const asyncCache = openWebCache(); event.respondWith(asyncResponse.then(async response => { if (response.ok || response.type === 'opaqueredirect') { @@ -44,5 +48,21 @@ self.addEventListener('fetch', function(event) { return response; })); + } else if (process.env.CDN_HOST ? url.host === process.env.CDN_HOST : url.pathname.startsWith('/system/')) { + event.respondWith(openSystemCache().then(async cache => { + const cached = await cache.match(event.request.url); + + if (cached === undefined) { + const fetched = await fetch(event.request); + + if (fetched.ok) { + await cache.put(event.request.url, fetched); + } + + return fetched.clone(); + } + + return cached; + })); } }); diff --git a/app/javascript/mastodon/storage/db.js b/app/javascript/mastodon/storage/db.js new file mode 100644 index 0000000..e08fc3f --- /dev/null +++ b/app/javascript/mastodon/storage/db.js @@ -0,0 +1,28 @@ +import { me } from '../initial_state'; + +export default new Promise((resolve, reject) => { + // Microsoft Edge 17 does not support getAll according to: + // Catalog of standard and vendor APIs across browsers - Microsoft Edge Development + // https://developer.microsoft.com/en-us/microsoft-edge/platform/catalog/?q=specName%3Aindexeddb + if (!me || !('getAll' in IDBObjectStore.prototype)) { + reject(); + return; + } + + const request = indexedDB.open('mastodon:' + me); + + request.onerror = reject; + request.onsuccess = ({ target }) => resolve(target.result); + + request.onupgradeneeded = ({ target }) => { + const accounts = target.result.createObjectStore('accounts', { autoIncrement: true }); + const statuses = target.result.createObjectStore('statuses', { autoIncrement: true }); + + accounts.createIndex('id', 'id', { unique: true }); + accounts.createIndex('moved', 'moved'); + + statuses.createIndex('id', 'id', { unique: true }); + statuses.createIndex('account', 'account'); + statuses.createIndex('reblog', 'reblog'); + }; +}); diff --git a/app/javascript/mastodon/storage/modifier.js b/app/javascript/mastodon/storage/modifier.js new file mode 100644 index 0000000..63e49fe --- /dev/null +++ b/app/javascript/mastodon/storage/modifier.js @@ -0,0 +1,151 @@ +import asyncDB from './db'; +import { autoPlayGif } from '../initial_state'; + +const accountAssetKeys = ['avatar', 'avatar_static', 'header', 'header_static']; +const avatarKey = autoPlayGif ? 'avatar' : 'avatar_static'; +const limit = 1024; +const asyncCache = caches.open('mastodon-system'); + +function put(name, objects, onupdate, oncreate) { + return asyncDB.then(db => new Promise((resolve, reject) => { + const putTransaction = db.transaction(name, 'readwrite'); + const putStore = putTransaction.objectStore(name); + const putIndex = putStore.index('id'); + + objects.forEach(object => { + putIndex.getKey(object.id).onsuccess = retrieval => { + function addObject() { + putStore.add(object); + } + + function deleteObject() { + putStore.delete(retrieval.target.result).onsuccess = addObject; + } + + if (retrieval.target.result) { + if (onupdate) { + onupdate(object, retrieval.target.result, putStore, deleteObject); + } else { + deleteObject(); + } + } else { + if (oncreate) { + oncreate(object, addObject); + } else { + addObject(); + } + } + }; + }); + + putTransaction.oncomplete = () => { + const readTransaction = db.transaction(name, 'readonly'); + const readStore = readTransaction.objectStore(name); + const count = readStore.count(); + + count.onsuccess = () => { + const excess = count.result - limit; + + if (excess > 0) { + const retrieval = readStore.getAll(null, excess); + + retrieval.onsuccess = () => resolve(retrieval.result); + retrieval.onerror = reject; + } else { + resolve([]); + } + }; + + count.onerror = reject; + }; + + putTransaction.onerror = reject; + })); +} + +function evictAccountsByRecords(records) { + asyncDB.then(db => { + const transaction = db.transaction(['accounts', 'statuses'], 'readwrite'); + const accounts = transaction.objectStore('accounts'); + const accountsIdIndex = accounts.index('id'); + const accountsMovedIndex = accounts.index('moved'); + const statuses = transaction.objectStore('statuses'); + const statusesIndex = statuses.index('account'); + + function evict(toEvict) { + toEvict.forEach(record => { + asyncCache.then(cache => accountAssetKeys.forEach(key => cache.delete(records[key]))); + + accountsMovedIndex.getAll(record.id).onsuccess = ({ target }) => evict(target.result); + + statusesIndex.getAll(record.id).onsuccess = + ({ target }) => evictStatusesByRecords(target.result); + + accountsIdIndex.getKey(record.id).onsuccess = + ({ target }) => target.result && accounts.delete(target.result); + }); + } + + evict(records); + }); +} + +export function evictStatus(id) { + return evictStatuses([id]); +} + +export function evictStatuses(ids) { + asyncDB.then(db => { + const store = db.transaction('statuses', 'readwrite').objectStore('statuses'); + const idIndex = store.index('id'); + const reblogIndex = store.index('reblog'); + + ids.forEach(id => { + reblogIndex.getAllKeys(id).onsuccess = + ({ target }) => target.result.forEach(reblogKey => store.delete(reblogKey)); + + idIndex.getKey(id).onsuccess = + ({ target }) => target.result && store.delete(target.result); + }); + }); +} + +function evictStatusesByRecords(records) { + evictStatuses(records.map(({ id }) => id)); +} + +export function putAccounts(records) { + const newURLs = []; + + put('accounts', records, (newRecord, oldKey, store, oncomplete) => { + store.get(oldKey).onsuccess = ({ target }) => { + accountAssetKeys.forEach(key => { + const newURL = newRecord[key]; + const oldURL = target.result[key]; + + if (newURL !== oldURL) { + asyncCache.then(cache => cache.delete(oldURL)); + } + }); + + const newURL = newRecord[avatarKey]; + const oldURL = target.result[avatarKey]; + + if (newURL !== oldURL) { + newURLs.push(newURL); + } + + oncomplete(); + }; + }, (newRecord, oncomplete) => { + newURLs.push(newRecord[avatarKey]); + oncomplete(); + }).then(records => { + evictAccountsByRecords(records); + asyncCache.then(cache => cache.addAll(newURLs)); + }); +} + +export function putStatuses(records) { + put('statuses', records).then(evictStatusesByRecords); +} diff --git a/config/webpack/production.js b/config/webpack/production.js index e2d7f11..e1c6812 100644 --- a/config/webpack/production.js +++ b/config/webpack/production.js @@ -90,7 +90,7 @@ module.exports = merge(sharedConfig, { '**/*.woff', ], ServiceWorker: { - entry: path.join(__dirname, '../../app/javascript/mastodon/service_worker/entry.js'), + entry: `imports-loader?process.env=>${encodeURIComponent(JSON.stringify(process.env))}!${encodeURI(path.join(__dirname, '../../app/javascript/mastodon/service_worker/entry.js'))}`, cacheName: 'mastodon', output: '../assets/sw.js', publicPath: '/sw.js', diff --git a/package.json b/package.json index 3385351..76f665d 100644 --- a/package.json +++ b/package.json @@ -56,6 +56,7 @@ "glob": "^7.1.1", "http-link-header": "^0.8.0", "immutable": "^3.8.2", + "imports-loader": "^0.8.0", "intersection-observer": "^0.5.0", "intl": "^1.2.5", "intl-messageformat": "^2.2.0", diff --git a/yarn.lock b/yarn.lock index fbce624..a1dd4c6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3364,6 +3364,13 @@ import-local@^0.1.1: pkg-dir "^2.0.0" resolve-cwd "^2.0.0" +imports-loader@^0.8.0: + version "0.8.0" + resolved "https://registry.yarnpkg.com/imports-loader/-/imports-loader-0.8.0.tgz#030ea51b8ca05977c40a3abfd9b4088fe0be9a69" + dependencies: + loader-utils "^1.0.2" + source-map "^0.6.1" + imurmurhash@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" From 2f3ac14a434c773577771b74292aa313d57db3c9 Mon Sep 17 00:00:00 2001 From: unarist Date: Tue, 27 Mar 2018 20:05:59 +0900 Subject: [PATCH 048/381] Add missing null handling in notification reducer (#6930) This patch adds null item (i.e. gap) handling on below functions to avoid TypeError. * `filterNotifications` called on user mute/block * `deleteByStatus` called on status deletion --- app/javascript/mastodon/reducers/notifications.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/javascript/mastodon/reducers/notifications.js b/app/javascript/mastodon/reducers/notifications.js index f023984..1ac7eb7 100644 --- a/app/javascript/mastodon/reducers/notifications.js +++ b/app/javascript/mastodon/reducers/notifications.js @@ -82,7 +82,7 @@ const expandNormalizedNotifications = (state, notifications, next) => { }; const filterNotifications = (state, relationship) => { - return state.update('items', list => list.filterNot(item => item.get('account') === relationship.id)); + return state.update('items', list => list.filterNot(item => item !== null && item.get('account') === relationship.id)); }; const updateTop = (state, top) => { @@ -94,7 +94,7 @@ const updateTop = (state, top) => { }; const deleteByStatus = (state, statusId) => { - return state.update('items', list => list.filterNot(item => item.get('status') === statusId)); + return state.update('items', list => list.filterNot(item => item !== null && item.get('status') === statusId)); }; export default function notifications(state = initialState, action) { From f5ed5f386020a08e8a659f4a6d25d2b875852be8 Mon Sep 17 00:00:00 2001 From: unarist Date: Tue, 27 Mar 2018 22:18:35 +0900 Subject: [PATCH 049/381] Clone response before put it to the cache (#6932) `Response.prototype.clone()` must be called before the response used. This fixes an error from ServiceWorker and failing to load image when the image is not cached. --- app/javascript/mastodon/service_worker/entry.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/javascript/mastodon/service_worker/entry.js b/app/javascript/mastodon/service_worker/entry.js index b9cf06e..160c3fb 100644 --- a/app/javascript/mastodon/service_worker/entry.js +++ b/app/javascript/mastodon/service_worker/entry.js @@ -56,10 +56,10 @@ self.addEventListener('fetch', function(event) { const fetched = await fetch(event.request); if (fetched.ok) { - await cache.put(event.request.url, fetched); + await cache.put(event.request.url, fetched.clone()); } - return fetched.clone(); + return fetched; } return cached; From 3523aa440ba3f52bf28fe1e9707506d327c4431f Mon Sep 17 00:00:00 2001 From: unarist Date: Tue, 27 Mar 2018 23:53:52 +0900 Subject: [PATCH 050/381] Fix LoadMore on account media gallery (#6933) max_id in the fetch request should be a status id, but media attachment id was used. --- app/javascript/mastodon/features/account_gallery/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/javascript/mastodon/features/account_gallery/index.js b/app/javascript/mastodon/features/account_gallery/index.js index 9a40d13..5f564d3 100644 --- a/app/javascript/mastodon/features/account_gallery/index.js +++ b/app/javascript/mastodon/features/account_gallery/index.js @@ -67,7 +67,7 @@ export default class AccountGallery extends ImmutablePureComponent { handleScrollToBottom = () => { if (this.props.hasMore) { - this.handleLoadMore(this.props.medias.last().get('id')); + this.handleLoadMore(this.props.medias.last().getIn(['status', 'id'])); } } From 4f9136d2d55e1547c84fc394c0e5e1bb259d58d2 Mon Sep 17 00:00:00 2001 From: Akihiko Odaki Date: Thu, 29 Mar 2018 03:40:18 +0900 Subject: [PATCH 051/381] Document CORS requirement for asset host (#6941) --- .env.production.sample | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.env.production.sample b/.env.production.sample index 1e5ed9f..9de2c06 100644 --- a/.env.production.sample +++ b/.env.production.sample @@ -81,6 +81,10 @@ SMTP_FROM_ADDRESS=notifications@example.com # PAPERCLIP_ROOT_URL=/system # Optional asset host for multi-server setups +# The asset host must allow cross origin request from WEB_DOMAIN or LOCAL_DOMAIN +# if WEB_DOMAIN is not set. For example, the server may have the +# following header field: +# Access-Control-Allow-Origin: https://example.com/ # CDN_HOST=https://assets.example.com # S3 (optional) From 5021c4e9ca78881f5379a18185a46e580b8f2c34 Mon Sep 17 00:00:00 2001 From: Yuto Tokunaga Date: Thu, 29 Mar 2018 03:40:51 +0900 Subject: [PATCH 052/381] Add double-tap zoom functionary to `ZoomableImage` (#6944) add to keep margin of the image on zooming move setting `scrollLeft` and `scrollTop` of container from callback of `setState` to `componentDidUpdate` add 'hammerjs' package for touch gesture detection rewrite `ZoomableImage` using 'hammerjs' --- .../features/ui/components/zoomable_image.js | 165 ++++++++++++--------- app/javascript/styles/mastodon/components.scss | 3 - package.json | 1 + yarn.lock | 4 + 4 files changed, 98 insertions(+), 75 deletions(-) diff --git a/app/javascript/mastodon/features/ui/components/zoomable_image.js b/app/javascript/mastodon/features/ui/components/zoomable_image.js index 0a0a4d4..0cae086 100644 --- a/app/javascript/mastodon/features/ui/components/zoomable_image.js +++ b/app/javascript/mastodon/features/ui/components/zoomable_image.js @@ -1,16 +1,10 @@ import React from 'react'; import PropTypes from 'prop-types'; +import Hammer from 'hammerjs'; const MIN_SCALE = 1; const MAX_SCALE = 4; - -const getMidpoint = (p1, p2) => ({ - x: (p1.clientX + p2.clientX) / 2, - y: (p1.clientY + p2.clientY) / 2, -}); - -const getDistance = (p1, p2) => - Math.sqrt(Math.pow(p1.clientX - p2.clientX, 2) + Math.pow(p1.clientY - p2.clientY, 2)); +const DOUBLE_TAP_SCALE = 2; const clamp = (min, max, value) => Math.min(max, Math.max(min, value)); @@ -37,83 +31,97 @@ export default class ZoomableImage extends React.PureComponent { removers = []; container = null; image = null; - lastTouchEndTime = 0; - lastDistance = 0; + lastScale = null; + zoomCenter = null; componentDidMount () { - let handler = this.handleTouchStart; - this.container.addEventListener('touchstart', handler); - this.removers.push(() => this.container.removeEventListener('touchstart', handler)); - handler = this.handleTouchMove; - // on Chrome 56+, touch event listeners will default to passive - // https://www.chromestatus.com/features/5093566007214080 - this.container.addEventListener('touchmove', handler, { passive: false }); - this.removers.push(() => this.container.removeEventListener('touchend', handler)); + // register pinch event handlers to the container + let hammer = new Hammer.Manager(this.container, { + // required to make container scrollable by touch + touchAction: 'pan-x pan-y', + }); + hammer.add(new Hammer.Pinch()); + hammer.on('pinchstart', this.handlePinchStart); + hammer.on('pinchmove', this.handlePinchMove); + this.removers.push(() => hammer.off('pinchstart pinchmove')); + + // register tap event handlers + hammer = new Hammer.Manager(this.image); + // NOTE the order of adding is also the order of gesture recognition + hammer.add(new Hammer.Tap({ event: 'doubletap', taps: 2 })); + hammer.add(new Hammer.Tap()); + // prevent the 'tap' event handler be fired on double tap + hammer.get('tap').requireFailure('doubletap'); + // NOTE 'tap' and 'doubletap' events are fired by touch and *mouse* + hammer.on('tap', this.handleTap); + hammer.on('doubletap', this.handleDoubleTap); + this.removers.push(() => hammer.off('tap doubletap')); } componentWillUnmount () { this.removeEventListeners(); } - removeEventListeners () { - this.removers.forEach(listeners => listeners()); - this.removers = []; - } + componentDidUpdate (prevProps, prevState) { + if (!this.zoomCenter) return; - handleTouchStart = e => { - if (e.touches.length !== 2) return; + const { x: cx, y: cy } = this.zoomCenter; + const { scale: prevScale } = prevState; + const { scale: nextScale } = this.state; + const { scrollLeft, scrollTop } = this.container; - this.lastDistance = getDistance(...e.touches); + // math memo: + // x = (scrollLeft + cx) / scrollWidth + // x' = (nextScrollLeft + cx) / nextScrollWidth + // scrollWidth = clientWidth * prevScale + // scrollWidth' = clientWidth * nextScale + // Solve x = x' for nextScrollLeft + const nextScrollLeft = (scrollLeft + cx) * nextScale / prevScale - cx; + const nextScrollTop = (scrollTop + cy) * nextScale / prevScale - cy; + + this.container.scrollLeft = nextScrollLeft; + this.container.scrollTop = nextScrollTop; } - handleTouchMove = e => { - const { scrollTop, scrollHeight, clientHeight } = this.container; - if (e.touches.length === 1 && scrollTop !== scrollHeight - clientHeight) { - // prevent propagating event to MediaModal - e.stopPropagation(); - return; - } - if (e.touches.length !== 2) return; + removeEventListeners () { + this.removers.forEach(listeners => listeners()); + this.removers = []; + } - e.preventDefault(); + handleClick = e => { + // prevent the click event propagated to parent e.stopPropagation(); - const distance = getDistance(...e.touches); - const midpoint = getMidpoint(...e.touches); - const scale = clamp(MIN_SCALE, MAX_SCALE, this.state.scale * distance / this.lastDistance); - - this.zoom(scale, midpoint); - - this.lastMidpoint = midpoint; - this.lastDistance = distance; + // the tap event handler is executed at the same time by touch and mouse, + // so we don't need to execute the onClick handler here } - zoom(nextScale, midpoint) { - const { scale } = this.state; - const { scrollLeft, scrollTop } = this.container; - - // math memo: - // x = (scrollLeft + midpoint.x) / scrollWidth - // x' = (nextScrollLeft + midpoint.x) / nextScrollWidth - // scrollWidth = clientWidth * scale - // scrollWidth' = clientWidth * nextScale - // Solve x = x' for nextScrollLeft - const nextScrollLeft = (scrollLeft + midpoint.x) * nextScale / scale - midpoint.x; - const nextScrollTop = (scrollTop + midpoint.y) * nextScale / scale - midpoint.y; + handlePinchStart = () => { + this.lastScale = this.state.scale; + } - this.setState({ scale: nextScale }, () => { - this.container.scrollLeft = nextScrollLeft; - this.container.scrollTop = nextScrollTop; - }); + handlePinchMove = e => { + const scale = clamp(MIN_SCALE, MAX_SCALE, this.lastScale * e.scale); + this.zoom(scale, e.center); } - handleClick = e => { - // don't propagate event to MediaModal - e.stopPropagation(); + handleTap = () => { const handler = this.props.onClick; if (handler) handler(); } + handleDoubleTap = e => { + if (this.state.scale === MIN_SCALE) + this.zoom(DOUBLE_TAP_SCALE, e.center); + else + this.zoom(MIN_SCALE, e.center); + } + + zoom (scale, center) { + this.zoomCenter = center; + this.setState({ scale }); + } + setContainerRef = c => { this.container = c; } @@ -126,6 +134,18 @@ export default class ZoomableImage extends React.PureComponent { const { alt, src } = this.props; const { scale } = this.state; const overflow = scale === 1 ? 'hidden' : 'scroll'; + const marginStyle = { + position: 'absolute', + top: 0, + bottom: 0, + left: 0, + right: 0, + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + transform: `scale(${scale})`, + transformOrigin: '0 0', + }; return (
- {alt} +
+ {alt} +
); } diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index 2b13b80..447e6bc 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -1459,9 +1459,6 @@ position: relative; width: 100%; height: 100%; - display: flex; - align-items: center; - justify-content: center; img { max-width: $media-modal-media-max-width; diff --git a/package.json b/package.json index 76f665d..dfba49a 100644 --- a/package.json +++ b/package.json @@ -54,6 +54,7 @@ "file-loader": "^0.11.2", "font-awesome": "^4.7.0", "glob": "^7.1.1", + "hammerjs": "^2.0.8", "http-link-header": "^0.8.0", "immutable": "^3.8.2", "imports-loader": "^0.8.0", diff --git a/yarn.lock b/yarn.lock index a1dd4c6..a306ebf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3092,6 +3092,10 @@ gzip-size@^3.0.0: dependencies: duplexer "^0.1.1" +hammerjs@^2.0.8: + version "2.0.8" + resolved "https://registry.yarnpkg.com/hammerjs/-/hammerjs-2.0.8.tgz#04ef77862cff2bb79d30f7692095930222bf60f1" + handle-thing@^1.2.5: version "1.2.5" resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-1.2.5.tgz#fd7aad726bf1a5fd16dfc29b2f7a6601d27139c4" From 9ed5eebd7ce8381af77dd2918678202a0776af4a Mon Sep 17 00:00:00 2001 From: ThibG Date: Thu, 29 Mar 2018 00:52:24 +0200 Subject: [PATCH 053/381] Do not ignore unknown media attachments, only skip them (#6948) That way, they are displayed in a list below the corresponding toot. --- app/lib/activitypub/activity/create.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/lib/activitypub/activity/create.rb b/app/lib/activitypub/activity/create.rb index 676e885..afee8a2 100644 --- a/app/lib/activitypub/activity/create.rb +++ b/app/lib/activitypub/activity/create.rb @@ -113,13 +113,13 @@ class ActivityPub::Activity::Create < ActivityPub::Activity media_attachments = [] as_array(@object['attachment']).each do |attachment| - next if unsupported_media_type?(attachment['mediaType']) || attachment['url'].blank? + next if attachment['url'].blank? href = Addressable::URI.parse(attachment['url']).normalize.to_s media_attachment = MediaAttachment.create(account: @account, remote_url: href, description: attachment['name'].presence, focus: attachment['focalPoint']) media_attachments << media_attachment - next if skip_download? + next if unsupported_media_type?(attachment['mediaType']) || skip_download? media_attachment.file_remote_url = href media_attachment.save From 41452e83028148b3ef6e5888e9bd3b16fd608ec7 Mon Sep 17 00:00:00 2001 From: unarist Date: Thu, 29 Mar 2018 19:59:12 +0900 Subject: [PATCH 054/381] Fix TypeError on follow notification (#6950) `notification.status` may not be present, e.g. follow notification. --- app/javascript/mastodon/actions/notifications.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/javascript/mastodon/actions/notifications.js b/app/javascript/mastodon/actions/notifications.js index 7267b85..da77afb 100644 --- a/app/javascript/mastodon/actions/notifications.js +++ b/app/javascript/mastodon/actions/notifications.js @@ -43,7 +43,9 @@ export function updateNotifications(notification, intlMessages, intlLocale) { const playSound = getState().getIn(['settings', 'notifications', 'sounds', notification.type], true); dispatch(importFetchedAccount(notification.account)); - dispatch(importFetchedStatus(notification.status)); + if (notification.status) { + dispatch(importFetchedStatus(notification.status)); + } dispatch({ type: NOTIFICATIONS_UPDATE, From f1f846045f26518525d5484ed9e782b73086ebe4 Mon Sep 17 00:00:00 2001 From: unarist Date: Thu, 29 Mar 2018 21:57:02 +0900 Subject: [PATCH 055/381] Fix ReferenceError when Cache API is missing (#6953) Cache API is not supported on Safari 11.0 / iOS 11. Since those caching is optional, this patch simply ignores it. --- app/javascript/mastodon/storage/modifier.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/javascript/mastodon/storage/modifier.js b/app/javascript/mastodon/storage/modifier.js index 63e49fe..1bec04d 100644 --- a/app/javascript/mastodon/storage/modifier.js +++ b/app/javascript/mastodon/storage/modifier.js @@ -4,7 +4,10 @@ import { autoPlayGif } from '../initial_state'; const accountAssetKeys = ['avatar', 'avatar_static', 'header', 'header_static']; const avatarKey = autoPlayGif ? 'avatar' : 'avatar_static'; const limit = 1024; -const asyncCache = caches.open('mastodon-system'); + +// ServiceWorker and Cache API is not available on iOS 11 +// https://webkit.org/status/#specification-service-workers +const asyncCache = window.caches ? caches.open('mastodon-system') : Promise.reject(); function put(name, objects, onupdate, oncreate) { return asyncDB.then(db => new Promise((resolve, reject) => { From d1f34151aee564bb1e60ee48107797681c869a81 Mon Sep 17 00:00:00 2001 From: Emelia Smith Date: Thu, 29 Mar 2018 19:08:34 +0200 Subject: [PATCH 056/381] Feature: Direct message from menu (#6956) * Implement ability to send direct messages from the user menu * Implement message warning users that direct messages are visible to all mentioned users * Update locales --- app/javascript/mastodon/actions/compose.js | 14 ++++++++++++++ .../mastodon/features/account/components/action_bar.js | 3 +++ .../features/account_timeline/components/header.js | 6 ++++++ .../account_timeline/containers/header_container.js | 9 ++++++++- .../features/compose/containers/warning_container.js | 7 ++++++- app/javascript/mastodon/locales/ar.json | 2 ++ app/javascript/mastodon/locales/bg.json | 2 ++ app/javascript/mastodon/locales/ca.json | 2 ++ app/javascript/mastodon/locales/de.json | 2 ++ app/javascript/mastodon/locales/defaultMessages.json | 8 ++++++++ app/javascript/mastodon/locales/en.json | 2 ++ app/javascript/mastodon/locales/eo.json | 2 ++ app/javascript/mastodon/locales/es.json | 2 ++ app/javascript/mastodon/locales/fa.json | 2 ++ app/javascript/mastodon/locales/fi.json | 2 ++ app/javascript/mastodon/locales/fr.json | 2 ++ app/javascript/mastodon/locales/gl.json | 2 ++ app/javascript/mastodon/locales/he.json | 2 ++ app/javascript/mastodon/locales/hr.json | 2 ++ app/javascript/mastodon/locales/hu.json | 2 ++ app/javascript/mastodon/locales/hy.json | 2 ++ app/javascript/mastodon/locales/id.json | 2 ++ app/javascript/mastodon/locales/io.json | 2 ++ app/javascript/mastodon/locales/it.json | 2 ++ app/javascript/mastodon/locales/ja.json | 2 ++ app/javascript/mastodon/locales/ko.json | 2 ++ app/javascript/mastodon/locales/nl.json | 2 ++ app/javascript/mastodon/locales/no.json | 2 ++ app/javascript/mastodon/locales/oc.json | 2 ++ app/javascript/mastodon/locales/pl.json | 2 ++ app/javascript/mastodon/locales/pt-BR.json | 2 ++ app/javascript/mastodon/locales/pt.json | 2 ++ app/javascript/mastodon/locales/ru.json | 2 ++ app/javascript/mastodon/locales/sk.json | 2 ++ app/javascript/mastodon/locales/sr-Latn.json | 2 ++ app/javascript/mastodon/locales/sr.json | 2 ++ app/javascript/mastodon/locales/sv.json | 2 ++ app/javascript/mastodon/locales/th.json | 2 ++ app/javascript/mastodon/locales/tr.json | 2 ++ app/javascript/mastodon/locales/uk.json | 2 ++ app/javascript/mastodon/locales/zh-CN.json | 2 ++ app/javascript/mastodon/locales/zh-HK.json | 2 ++ app/javascript/mastodon/locales/zh-TW.json | 2 ++ app/javascript/mastodon/reducers/compose.js | 7 +++++++ 44 files changed, 126 insertions(+), 2 deletions(-) diff --git a/app/javascript/mastodon/actions/compose.js b/app/javascript/mastodon/actions/compose.js index 5e7cdd2..2138f94 100644 --- a/app/javascript/mastodon/actions/compose.js +++ b/app/javascript/mastodon/actions/compose.js @@ -15,6 +15,7 @@ export const COMPOSE_SUBMIT_SUCCESS = 'COMPOSE_SUBMIT_SUCCESS'; export const COMPOSE_SUBMIT_FAIL = 'COMPOSE_SUBMIT_FAIL'; export const COMPOSE_REPLY = 'COMPOSE_REPLY'; export const COMPOSE_REPLY_CANCEL = 'COMPOSE_REPLY_CANCEL'; +export const COMPOSE_DIRECT = 'COMPOSE_DIRECT'; export const COMPOSE_MENTION = 'COMPOSE_MENTION'; export const COMPOSE_RESET = 'COMPOSE_RESET'; export const COMPOSE_UPLOAD_REQUEST = 'COMPOSE_UPLOAD_REQUEST'; @@ -91,6 +92,19 @@ export function mentionCompose(account, router) { }; }; +export function directCompose(account, router) { + return (dispatch, getState) => { + dispatch({ + type: COMPOSE_DIRECT, + account: account, + }); + + if (!getState().getIn(['compose', 'mounted'])) { + router.push('/statuses/new'); + } + }; +}; + export function submitCompose() { return function (dispatch, getState) { const status = getState().getIn(['compose', 'text'], ''); diff --git a/app/javascript/mastodon/features/account/components/action_bar.js b/app/javascript/mastodon/features/account/components/action_bar.js index b538fa5..23dbf32 100644 --- a/app/javascript/mastodon/features/account/components/action_bar.js +++ b/app/javascript/mastodon/features/account/components/action_bar.js @@ -8,6 +8,7 @@ import { me } from '../../../initial_state'; const messages = defineMessages({ mention: { id: 'account.mention', defaultMessage: 'Mention @{name}' }, + direct: { id: 'account.direct', defaultMessage: 'Direct message @{name}' }, edit_profile: { id: 'account.edit_profile', defaultMessage: 'Edit profile' }, unblock: { id: 'account.unblock', defaultMessage: 'Unblock @{name}' }, unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' }, @@ -32,6 +33,7 @@ export default class ActionBar extends React.PureComponent { onFollow: PropTypes.func, onBlock: PropTypes.func.isRequired, onMention: PropTypes.func.isRequired, + onDirect: PropTypes.func.isRequired, onReblogToggle: PropTypes.func.isRequired, onReport: PropTypes.func.isRequired, onMute: PropTypes.func.isRequired, @@ -53,6 +55,7 @@ export default class ActionBar extends React.PureComponent { let extraInfo = ''; menu.push({ text: intl.formatMessage(messages.mention, { name: account.get('username') }), action: this.props.onMention }); + menu.push({ text: intl.formatMessage(messages.direct, { name: account.get('username') }), action: this.props.onDirect }); if ('share' in navigator) { menu.push({ text: intl.formatMessage(messages.share, { name: account.get('username') }), action: this.handleShare }); diff --git a/app/javascript/mastodon/features/account_timeline/components/header.js b/app/javascript/mastodon/features/account_timeline/components/header.js index 6b88a7a..789999d 100644 --- a/app/javascript/mastodon/features/account_timeline/components/header.js +++ b/app/javascript/mastodon/features/account_timeline/components/header.js @@ -16,6 +16,7 @@ export default class Header extends ImmutablePureComponent { onFollow: PropTypes.func.isRequired, onBlock: PropTypes.func.isRequired, onMention: PropTypes.func.isRequired, + onDirect: PropTypes.func.isRequired, onReblogToggle: PropTypes.func.isRequired, onReport: PropTypes.func.isRequired, onMute: PropTypes.func.isRequired, @@ -40,6 +41,10 @@ export default class Header extends ImmutablePureComponent { this.props.onMention(this.props.account, this.context.router.history); } + handleDirect = () => { + this.props.onDirect(this.props.account, this.context.router.history); + } + handleReport = () => { this.props.onReport(this.props.account); } @@ -89,6 +94,7 @@ export default class Header extends ImmutablePureComponent { account={account} onBlock={this.handleBlock} onMention={this.handleMention} + onDirect={this.handleDirect} onReblogToggle={this.handleReblogToggle} onReport={this.handleReport} onMute={this.handleMute} diff --git a/app/javascript/mastodon/features/account_timeline/containers/header_container.js b/app/javascript/mastodon/features/account_timeline/containers/header_container.js index b5e0e9a..214441b 100644 --- a/app/javascript/mastodon/features/account_timeline/containers/header_container.js +++ b/app/javascript/mastodon/features/account_timeline/containers/header_container.js @@ -9,7 +9,10 @@ import { unblockAccount, unmuteAccount, } from '../../../actions/accounts'; -import { mentionCompose } from '../../../actions/compose'; +import { + mentionCompose, + directCompose, +} from '../../../actions/compose'; import { initMuteModal } from '../../../actions/mutes'; import { initReport } from '../../../actions/reports'; import { openModal } from '../../../actions/modal'; @@ -67,6 +70,10 @@ const mapDispatchToProps = (dispatch, { intl }) => ({ dispatch(mentionCompose(account, router)); }, + onDirect (account, router) { + dispatch(directCompose(account, router)); + }, + onReblogToggle (account) { if (account.getIn(['relationship', 'showing_reblogs'])) { dispatch(followAccount(account.get('id'), false)); diff --git a/app/javascript/mastodon/features/compose/containers/warning_container.js b/app/javascript/mastodon/features/compose/containers/warning_container.js index 8ee8ea1..efaa02e 100644 --- a/app/javascript/mastodon/features/compose/containers/warning_container.js +++ b/app/javascript/mastodon/features/compose/containers/warning_container.js @@ -10,15 +10,19 @@ const APPROX_HASHTAG_RE = /(?:^|[^\/\)\w])#(\w*[a-zA-Z·]\w*)/i; const mapStateToProps = state => ({ needsLockWarning: state.getIn(['compose', 'privacy']) === 'private' && !state.getIn(['accounts', me, 'locked']), hashtagWarning: state.getIn(['compose', 'privacy']) !== 'public' && APPROX_HASHTAG_RE.test(state.getIn(['compose', 'text'])), + directMessageWarning: state.getIn(['compose', 'privacy']) === 'direct', }); -const WarningWrapper = ({ needsLockWarning, hashtagWarning }) => { +const WarningWrapper = ({ needsLockWarning, hashtagWarning, directMessageWarning }) => { if (needsLockWarning) { return }} />} />; } if (hashtagWarning) { return } />; } + if (directMessageWarning) { + return } />; + } return null; }; @@ -26,6 +30,7 @@ const WarningWrapper = ({ needsLockWarning, hashtagWarning }) => { WarningWrapper.propTypes = { needsLockWarning: PropTypes.bool, hashtagWarning: PropTypes.bool, + directMessageWarning: PropTypes.bool, }; export default connect(mapStateToProps)(WarningWrapper); diff --git a/app/javascript/mastodon/locales/ar.json b/app/javascript/mastodon/locales/ar.json index 3d96207..f9af062 100644 --- a/app/javascript/mastodon/locales/ar.json +++ b/app/javascript/mastodon/locales/ar.json @@ -2,6 +2,7 @@ "account.block": "حظر @{name}", "account.block_domain": "إخفاء كل شيئ قادم من إسم النطاق {domain}", "account.blocked": "محظور", + "account.direct": "Direct Message @{name}", "account.disclaimer_full": "قد لا تعكس المعلومات أدناه الملف الشخصي الكامل للمستخدم.", "account.domain_blocked": "النطاق مخفي", "account.edit_profile": "تعديل الملف الشخصي", @@ -56,6 +57,7 @@ "column_header.unpin": "فك التدبيس", "column_subheading.navigation": "التصفح", "column_subheading.settings": "الإعدادات", + "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.", "compose_form.hashtag_warning": "هذا التبويق لن يُدرَج تحت أي وسم كان بما أنه غير مُدرَج. لا يُسمح بالبحث إلّا عن التبويقات العمومية عن طريق الوسوم.", "compose_form.lock_disclaimer": "حسابك ليس {locked}. يمكن لأي شخص متابعتك و عرض المنشورات.", "compose_form.lock_disclaimer.lock": "مقفل", diff --git a/app/javascript/mastodon/locales/bg.json b/app/javascript/mastodon/locales/bg.json index 39eb05f..58795ca 100644 --- a/app/javascript/mastodon/locales/bg.json +++ b/app/javascript/mastodon/locales/bg.json @@ -2,6 +2,7 @@ "account.block": "Блокирай", "account.block_domain": "Hide everything from {domain}", "account.blocked": "Blocked", + "account.direct": "Direct Message @{name}", "account.disclaimer_full": "Information below may reflect the user's profile incompletely.", "account.domain_blocked": "Domain hidden", "account.edit_profile": "Редактирай профила си", @@ -56,6 +57,7 @@ "column_header.unpin": "Unpin", "column_subheading.navigation": "Navigation", "column_subheading.settings": "Settings", + "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.", "compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.", "compose_form.lock_disclaimer": "Your account is not {locked}. Anyone can follow you to view your follower-only posts.", "compose_form.lock_disclaimer.lock": "locked", diff --git a/app/javascript/mastodon/locales/ca.json b/app/javascript/mastodon/locales/ca.json index 33545d8..b0ce34c 100644 --- a/app/javascript/mastodon/locales/ca.json +++ b/app/javascript/mastodon/locales/ca.json @@ -2,6 +2,7 @@ "account.block": "Bloca @{name}", "account.block_domain": "Amaga-ho tot de {domain}", "account.blocked": "Bloquejat", + "account.direct": "Direct Message @{name}", "account.disclaimer_full": "La informació següent pot reflectir incompleta el perfil de l'usuari.", "account.domain_blocked": "Domini ocult", "account.edit_profile": "Edita el perfil", @@ -56,6 +57,7 @@ "column_header.unpin": "No fixis", "column_subheading.navigation": "Navegació", "column_subheading.settings": "Configuració", + "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.", "compose_form.hashtag_warning": "Aquest toot no es mostrarà en cap etiqueta ja que no està llistat. Només els toots públics poden ser cercats per etiqueta.", "compose_form.lock_disclaimer": "El teu compte no està bloquejat {locked}. Tothom pot seguir-te i veure els teus missatges a seguidors.", "compose_form.lock_disclaimer.lock": "blocat", diff --git a/app/javascript/mastodon/locales/de.json b/app/javascript/mastodon/locales/de.json index 7bdb6a3..eb0c505 100644 --- a/app/javascript/mastodon/locales/de.json +++ b/app/javascript/mastodon/locales/de.json @@ -2,6 +2,7 @@ "account.block": "@{name} blocken", "account.block_domain": "Alles von {domain} verstecken", "account.blocked": "Blockiert", + "account.direct": "Direct Message @{name}", "account.disclaimer_full": "Das Profil wird möglicherweise unvollständig wiedergegeben.", "account.domain_blocked": "Domain versteckt", "account.edit_profile": "Profil bearbeiten", @@ -56,6 +57,7 @@ "column_header.unpin": "Lösen", "column_subheading.navigation": "Navigation", "column_subheading.settings": "Einstellungen", + "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.", "compose_form.hashtag_warning": "Dieser Beitrag wird nicht unter einen dieser Hashtags sichtbar sein, solange er ungelistet ist. Bei einer Suche kann er nicht gefunden werden.", "compose_form.lock_disclaimer": "Dein Profil ist nicht {locked}. Wer dir folgen will, kann das jederzeit tun und dann auch deine privaten Beiträge sehen.", "compose_form.lock_disclaimer.lock": "gesperrt", diff --git a/app/javascript/mastodon/locales/defaultMessages.json b/app/javascript/mastodon/locales/defaultMessages.json index 76b302f..1fe6861 100644 --- a/app/javascript/mastodon/locales/defaultMessages.json +++ b/app/javascript/mastodon/locales/defaultMessages.json @@ -381,6 +381,10 @@ "id": "account.mention" }, { + "defaultMessage": "Direct message @{name}", + "id": "account.direct" + }, + { "defaultMessage": "Edit profile", "id": "account.edit_profile" }, @@ -804,6 +808,10 @@ { "defaultMessage": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.", "id": "compose_form.hashtag_warning" + }, + { + "defaultMessage": "This toot will only be visible to all the mentioned users.", + "id": "compose_form.direct_message_warning" } ], "path": "app/javascript/mastodon/features/compose/containers/warning_container.json" diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json index de44bd0..d481596 100644 --- a/app/javascript/mastodon/locales/en.json +++ b/app/javascript/mastodon/locales/en.json @@ -2,6 +2,7 @@ "account.block": "Block @{name}", "account.block_domain": "Hide everything from {domain}", "account.blocked": "Blocked", + "account.direct": "Direct message @{name}", "account.disclaimer_full": "Information below may reflect the user's profile incompletely.", "account.domain_blocked": "Domain hidden", "account.edit_profile": "Edit profile", @@ -56,6 +57,7 @@ "column_header.unpin": "Unpin", "column_subheading.navigation": "Navigation", "column_subheading.settings": "Settings", + "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.", "compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.", "compose_form.lock_disclaimer": "Your account is not {locked}. Anyone can follow you to view your follower-only posts.", "compose_form.lock_disclaimer.lock": "locked", diff --git a/app/javascript/mastodon/locales/eo.json b/app/javascript/mastodon/locales/eo.json index 35d9edf..9b00edb 100644 --- a/app/javascript/mastodon/locales/eo.json +++ b/app/javascript/mastodon/locales/eo.json @@ -2,6 +2,7 @@ "account.block": "Bloki @{name}", "account.block_domain": "Kaŝi ĉion de {domain}", "account.blocked": "Blokita", + "account.direct": "Direct Message @{name}", "account.disclaimer_full": "Subaj informoj povas reflekti la profilon de la uzanto nekomplete.", "account.domain_blocked": "Domajno kaŝita", "account.edit_profile": "Redakti profilon", @@ -56,6 +57,7 @@ "column_header.unpin": "Depingli", "column_subheading.navigation": "Navigado", "column_subheading.settings": "Agordado", + "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.", "compose_form.hashtag_warning": "Ĉi tiu mesaĝo ne estos listigita per ajna kradvorto. Nur publikaj mesaĝoj estas serĉeblaj per kradvortoj.", "compose_form.lock_disclaimer": "Via konta ne estas {locked}. Iu ajn povas sekvi vin por vidi viajn mesaĝojn nur por sekvantoj.", "compose_form.lock_disclaimer.lock": "ŝlosita", diff --git a/app/javascript/mastodon/locales/es.json b/app/javascript/mastodon/locales/es.json index e69938b..9f03b31 100644 --- a/app/javascript/mastodon/locales/es.json +++ b/app/javascript/mastodon/locales/es.json @@ -2,6 +2,7 @@ "account.block": "Bloquear", "account.block_domain": "Ocultar todo de {domain}", "account.blocked": "Bloqueado", + "account.direct": "Direct Message @{name}", "account.disclaimer_full": "La siguiente información del usuario puede estar incompleta.", "account.domain_blocked": "Dominio oculto", "account.edit_profile": "Editar perfil", @@ -56,6 +57,7 @@ "column_header.unpin": "Dejar de fijar", "column_subheading.navigation": "Navegación", "column_subheading.settings": "Ajustes", + "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.", "compose_form.hashtag_warning": "Este toot no se mostrará bajo hashtags porque no es público. Sólo los toots públicos se pueden buscar por hashtag.", "compose_form.lock_disclaimer": "Tu cuenta no está bloqueada. Todos pueden seguirte para ver tus toots solo para seguidores.", "compose_form.lock_disclaimer.lock": "bloqueado", diff --git a/app/javascript/mastodon/locales/fa.json b/app/javascript/mastodon/locales/fa.json index c9695d0..9421746 100644 --- a/app/javascript/mastodon/locales/fa.json +++ b/app/javascript/mastodon/locales/fa.json @@ -2,6 +2,7 @@ "account.block": "مسدودسازی @{name}", "account.block_domain": "پنهان‌سازی همه چیز از سرور {domain}", "account.blocked": "Blocked", + "account.direct": "Direct Message @{name}", "account.disclaimer_full": "اطلاعات زیر ممکن است نمایهٔ این کاربر را به تمامی نشان ندهد.", "account.domain_blocked": "Domain hidden", "account.edit_profile": "ویرایش نمایه", @@ -56,6 +57,7 @@ "column_header.unpin": "رهاکردن", "column_subheading.navigation": "گشت و گذار", "column_subheading.settings": "تنظیمات", + "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.", "compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.", "compose_form.lock_disclaimer": "حساب شما {locked} نیست. هر کسی می‌تواند پیگیر شما شود و نوشته‌های ویژهٔ پیگیران شما را ببیند.", "compose_form.lock_disclaimer.lock": "قفل", diff --git a/app/javascript/mastodon/locales/fi.json b/app/javascript/mastodon/locales/fi.json index cbdffec..fce441d 100644 --- a/app/javascript/mastodon/locales/fi.json +++ b/app/javascript/mastodon/locales/fi.json @@ -2,6 +2,7 @@ "account.block": "Estä @{name}", "account.block_domain": "Piilota kaikki sisältö verkkotunnuksesta {domain}", "account.blocked": "Estetty", + "account.direct": "Direct Message @{name}", "account.disclaimer_full": "Alla olevat käyttäjän profiilitiedot saattavat olla epätäydellisiä.", "account.domain_blocked": "Verkko-osoite piilotettu", "account.edit_profile": "Muokkaa", @@ -56,6 +57,7 @@ "column_header.unpin": "Poista kiinnitys", "column_subheading.navigation": "Navigaatio", "column_subheading.settings": "Asetukset", + "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.", "compose_form.hashtag_warning": "Tämä töötti ei tule näkymään hashtag-hauissa, koska se ei näy julkisilla aikajanoilla. Vain julkisia tööttejä voi hakea hashtageilla.", "compose_form.lock_disclaimer": "Tilisi ei ole {locked}. Kuka tahansa voi seurata tiliäsi ja nähdä vain seuraajille -postauksesi.", "compose_form.lock_disclaimer.lock": "lukittu", diff --git a/app/javascript/mastodon/locales/fr.json b/app/javascript/mastodon/locales/fr.json index 8c56a75..6eb34e6 100644 --- a/app/javascript/mastodon/locales/fr.json +++ b/app/javascript/mastodon/locales/fr.json @@ -2,6 +2,7 @@ "account.block": "Bloquer @{name}", "account.block_domain": "Tout masquer venant de {domain}", "account.blocked": "Bloqué", + "account.direct": "Direct Message @{name}", "account.disclaimer_full": "Les données ci-dessous peuvent ne pas refléter ce profil dans sa totalité.", "account.domain_blocked": "Domaine caché", "account.edit_profile": "Modifier le profil", @@ -56,6 +57,7 @@ "column_header.unpin": "Retirer", "column_subheading.navigation": "Navigation", "column_subheading.settings": "Paramètres", + "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.", "compose_form.hashtag_warning": "Ce pouet ne sera pas listé dans les recherches par hashtag car sa visibilité est réglée sur \"non-listé\". Seuls les pouets avec une visibilité \"publique\" peuvent être recherchés par hashtag.", "compose_form.lock_disclaimer": "Votre compte n’est pas {locked}. Tout le monde peut vous suivre et voir vos pouets privés.", "compose_form.lock_disclaimer.lock": "verrouillé", diff --git a/app/javascript/mastodon/locales/gl.json b/app/javascript/mastodon/locales/gl.json index c5cedd6..a0823b9 100644 --- a/app/javascript/mastodon/locales/gl.json +++ b/app/javascript/mastodon/locales/gl.json @@ -2,6 +2,7 @@ "account.block": "Bloquear @{name}", "account.block_domain": "Ocultar calquer contido de {domain}", "account.blocked": "Blocked", + "account.direct": "Direct Message @{name}", "account.disclaimer_full": "A información inferior podería mostrar un perfil incompleto da usuaria.", "account.domain_blocked": "Domain hidden", "account.edit_profile": "Editar perfil", @@ -56,6 +57,7 @@ "column_header.unpin": "Soltar", "column_subheading.navigation": "Navegación", "column_subheading.settings": "Axustes", + "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.", "compose_form.hashtag_warning": "Esta mensaxe non será listada baixo ningunha etiqueta xa que está marcada como non listada. Só os toots públicos poden buscarse por etiquetas.", "compose_form.lock_disclaimer": "A súa conta non está {locked}. Calquera pode seguila para ver as súas mensaxes só-para-seguidoras.", "compose_form.lock_disclaimer.lock": "bloqueado", diff --git a/app/javascript/mastodon/locales/he.json b/app/javascript/mastodon/locales/he.json index fe6f9bb..0e2ee8d 100644 --- a/app/javascript/mastodon/locales/he.json +++ b/app/javascript/mastodon/locales/he.json @@ -2,6 +2,7 @@ "account.block": "חסימת @{name}", "account.block_domain": "להסתיר הכל מהקהילה {domain}", "account.blocked": "Blocked", + "account.direct": "Direct Message @{name}", "account.disclaimer_full": "המידע להלן עשוי להיות לא עדכני או לא שלם.", "account.domain_blocked": "Domain hidden", "account.edit_profile": "עריכת פרופיל", @@ -56,6 +57,7 @@ "column_header.unpin": "שחרור קיבוע", "column_subheading.navigation": "ניווט", "column_subheading.settings": "אפשרויות", + "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.", "compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.", "compose_form.lock_disclaimer": "חשבונך אינו {locked}. כל אחד יוכל לעקוב אחריך כדי לקרוא את הודעותיך המיועדות לעוקבים בלבד.", "compose_form.lock_disclaimer.lock": "נעול", diff --git a/app/javascript/mastodon/locales/hr.json b/app/javascript/mastodon/locales/hr.json index 11cd1bf..1e8ce8e 100644 --- a/app/javascript/mastodon/locales/hr.json +++ b/app/javascript/mastodon/locales/hr.json @@ -2,6 +2,7 @@ "account.block": "Blokiraj @{name}", "account.block_domain": "Sakrij sve sa {domain}", "account.blocked": "Blocked", + "account.direct": "Direct Message @{name}", "account.disclaimer_full": "Ovaj korisnik je sa druge instance. Ovaj broj bi mogao biti veći.", "account.domain_blocked": "Domain hidden", "account.edit_profile": "Uredi profil", @@ -56,6 +57,7 @@ "column_header.unpin": "Unpin", "column_subheading.navigation": "Navigacija", "column_subheading.settings": "Postavke", + "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.", "compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.", "compose_form.lock_disclaimer": "Tvoj račun nije {locked}. Svatko te može slijediti kako bi vidio postove namijenjene samo tvojim sljedbenicima.", "compose_form.lock_disclaimer.lock": "zaključan", diff --git a/app/javascript/mastodon/locales/hu.json b/app/javascript/mastodon/locales/hu.json index 1ea6576..deb17c6 100644 --- a/app/javascript/mastodon/locales/hu.json +++ b/app/javascript/mastodon/locales/hu.json @@ -2,6 +2,7 @@ "account.block": "@{name} letiltása", "account.block_domain": "Minden elrejtése innen: {domain}", "account.blocked": "Blocked", + "account.direct": "Direct Message @{name}", "account.disclaimer_full": "Az alul található információk hiányosan mutathatják be a felhasználót.", "account.domain_blocked": "Domain hidden", "account.edit_profile": "Profil szerkesztése", @@ -56,6 +57,7 @@ "column_header.unpin": "Kitűzés eltávolítása", "column_subheading.navigation": "Navigáció", "column_subheading.settings": "Beállítások", + "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.", "compose_form.hashtag_warning": "Ezen tülkölés nem fog megjelenni semmilyen hashtag alatt mivel listázatlan. Csak a publikus tülkölések kereshetőek hashtag-el.", "compose_form.lock_disclaimer": "Az ön fiókja nincs {locked}. Bárki követni tud, hogy megtekintse a kizárt követőknek szánt üzeneteid.", "compose_form.lock_disclaimer.lock": "lezárva", diff --git a/app/javascript/mastodon/locales/hy.json b/app/javascript/mastodon/locales/hy.json index e9638bf..ee20553 100644 --- a/app/javascript/mastodon/locales/hy.json +++ b/app/javascript/mastodon/locales/hy.json @@ -2,6 +2,7 @@ "account.block": "Արգելափակել @{name}֊ին", "account.block_domain": "Թաքցնել ամենը հետեւյալ տիրույթից՝ {domain}", "account.blocked": "Blocked", + "account.direct": "Direct Message @{name}", "account.disclaimer_full": "Ներքոհիշյալը կարող է ոչ ամբողջությամբ արտացոլել օգտատիրոջ էջի տվյալները։", "account.domain_blocked": "Domain hidden", "account.edit_profile": "Խմբագրել անձնական էջը", @@ -56,6 +57,7 @@ "column_header.unpin": "Հանել", "column_subheading.navigation": "Նավարկություն", "column_subheading.settings": "Կարգավորումներ", + "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.", "compose_form.hashtag_warning": "Այս թութը չի հաշվառվի որեւէ պիտակի տակ, քանզի այն ծածուկ է։ Միայն հրապարակային թթերը հնարավոր է որոնել պիտակներով։", "compose_form.lock_disclaimer": "Քո հաշիվը {locked} չէ։ Յուրաքանչյուր ոք կարող է հետեւել քեզ եւ տեսնել միայն հետեւողների համար նախատեսված գրառումները։", "compose_form.lock_disclaimer.lock": "փակ", diff --git a/app/javascript/mastodon/locales/id.json b/app/javascript/mastodon/locales/id.json index c8d8ebe..cae3211 100644 --- a/app/javascript/mastodon/locales/id.json +++ b/app/javascript/mastodon/locales/id.json @@ -2,6 +2,7 @@ "account.block": "Blokir @{name}", "account.block_domain": "Sembunyikan segalanya dari {domain}", "account.blocked": "Terblokir", + "account.direct": "Direct Message @{name}", "account.disclaimer_full": "Informasi di bawah mungkin tidak mencerminkan profil user secara lengkap.", "account.domain_blocked": "Domain disembunyikan", "account.edit_profile": "Ubah profil", @@ -56,6 +57,7 @@ "column_header.unpin": "Lepaskan", "column_subheading.navigation": "Navigasi", "column_subheading.settings": "Pengaturan", + "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.", "compose_form.hashtag_warning": "Toot ini tidak akan ada dalam daftar tagar manapun karena telah di set sebagai tidak terdaftar. Hanya postingan publik yang bisa dicari dengan tagar.", "compose_form.lock_disclaimer": "Akun anda tidak {locked}. Semua orang dapat mengikuti anda untuk melihat postingan khusus untuk pengikut anda.", "compose_form.lock_disclaimer.lock": "terkunci", diff --git a/app/javascript/mastodon/locales/io.json b/app/javascript/mastodon/locales/io.json index a2e9af8..121d745 100644 --- a/app/javascript/mastodon/locales/io.json +++ b/app/javascript/mastodon/locales/io.json @@ -2,6 +2,7 @@ "account.block": "Blokusar @{name}", "account.block_domain": "Hide everything from {domain}", "account.blocked": "Blocked", + "account.direct": "Direct Message @{name}", "account.disclaimer_full": "Information below may reflect the user's profile incompletely.", "account.domain_blocked": "Domain hidden", "account.edit_profile": "Modifikar profilo", @@ -56,6 +57,7 @@ "column_header.unpin": "Unpin", "column_subheading.navigation": "Navigation", "column_subheading.settings": "Settings", + "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.", "compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.", "compose_form.lock_disclaimer": "Your account is not {locked}. Anyone can follow you to view your follower-only posts.", "compose_form.lock_disclaimer.lock": "locked", diff --git a/app/javascript/mastodon/locales/it.json b/app/javascript/mastodon/locales/it.json index 40ea9b2..5e57143 100644 --- a/app/javascript/mastodon/locales/it.json +++ b/app/javascript/mastodon/locales/it.json @@ -2,6 +2,7 @@ "account.block": "Blocca @{name}", "account.block_domain": "Hide everything from {domain}", "account.blocked": "Blocked", + "account.direct": "Direct Message @{name}", "account.disclaimer_full": "Information below may reflect the user's profile incompletely.", "account.domain_blocked": "Domain hidden", "account.edit_profile": "Modifica profilo", @@ -56,6 +57,7 @@ "column_header.unpin": "Unpin", "column_subheading.navigation": "Navigation", "column_subheading.settings": "Settings", + "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.", "compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.", "compose_form.lock_disclaimer": "Your account is not {locked}. Anyone can follow you to view your follower-only posts.", "compose_form.lock_disclaimer.lock": "locked", diff --git a/app/javascript/mastodon/locales/ja.json b/app/javascript/mastodon/locales/ja.json index 08f5e79..c14d619 100644 --- a/app/javascript/mastodon/locales/ja.json +++ b/app/javascript/mastodon/locales/ja.json @@ -2,6 +2,7 @@ "account.block": "@{name}さんをブロック", "account.block_domain": "{domain}全体を非表示", "account.blocked": "ブロック済み", + "account.direct": "Direct Message @{name}", "account.disclaimer_full": "以下の情報は不正確な可能性があります。", "account.domain_blocked": "ドメイン非表示中", "account.edit_profile": "プロフィールを編集", @@ -56,6 +57,7 @@ "column_header.unpin": "ピン留めを外す", "column_subheading.navigation": "ナビゲーション", "column_subheading.settings": "設定", + "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.", "compose_form.hashtag_warning": "このトゥートは未収載なのでハッシュタグの一覧に表示されません。公開トゥートだけがハッシュタグで検索できます。", "compose_form.lock_disclaimer": "あなたのアカウントは{locked}になっていません。誰でもあなたをフォローすることができ、フォロワー限定の投稿を見ることができます。", "compose_form.lock_disclaimer.lock": "非公開", diff --git a/app/javascript/mastodon/locales/ko.json b/app/javascript/mastodon/locales/ko.json index bde4397..fa15214 100644 --- a/app/javascript/mastodon/locales/ko.json +++ b/app/javascript/mastodon/locales/ko.json @@ -2,6 +2,7 @@ "account.block": "@{name}을 차단", "account.block_domain": "{domain} 전체를 숨김", "account.blocked": "차단 됨", + "account.direct": "Direct Message @{name}", "account.disclaimer_full": "여기 있는 정보는 유저의 프로파일을 정확히 반영하지 못 할 수도 있습니다.", "account.domain_blocked": "도메인 숨겨짐", "account.edit_profile": "프로필 편집", @@ -56,6 +57,7 @@ "column_header.unpin": "고정 해제", "column_subheading.navigation": "내비게이션", "column_subheading.settings": "설정", + "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.", "compose_form.hashtag_warning": "이 툿은 어떤 해시태그로도 검색 되지 않습니다. 전체공개로 게시 된 툿만이 해시태그로 검색 될 수 있습니다.", "compose_form.lock_disclaimer": "이 계정은 {locked}로 설정 되어 있지 않습니다. 누구나 이 계정을 팔로우 할 수 있으며, 팔로워 공개의 포스팅을 볼 수 있습니다.", "compose_form.lock_disclaimer.lock": "비공개", diff --git a/app/javascript/mastodon/locales/nl.json b/app/javascript/mastodon/locales/nl.json index 140be0d..ff82799 100644 --- a/app/javascript/mastodon/locales/nl.json +++ b/app/javascript/mastodon/locales/nl.json @@ -2,6 +2,7 @@ "account.block": "Blokkeer @{name}", "account.block_domain": "Negeer alles van {domain}", "account.blocked": "Geblokkeerd", + "account.direct": "Direct Message @{name}", "account.disclaimer_full": "De informatie hieronder kan mogelijk een incompleet beeld geven van dit gebruikersprofiel.", "account.domain_blocked": "Domein verborgen", "account.edit_profile": "Profiel bewerken", @@ -56,6 +57,7 @@ "column_header.unpin": "Losmaken", "column_subheading.navigation": "Navigatie", "column_subheading.settings": "Instellingen", + "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.", "compose_form.hashtag_warning": "Deze toot valt niet onder een hashtag te bekijken, omdat deze niet op openbare tijdlijnen wordt getoond. Alleen openbare toots kunnen via hashtags gevonden worden.", "compose_form.lock_disclaimer": "Jouw account is niet {locked}. Iedereen kan jou volgen en toots zien die je alleen aan volgers hebt gericht.", "compose_form.lock_disclaimer.lock": "besloten", diff --git a/app/javascript/mastodon/locales/no.json b/app/javascript/mastodon/locales/no.json index 4d6ac13..d3bc757 100644 --- a/app/javascript/mastodon/locales/no.json +++ b/app/javascript/mastodon/locales/no.json @@ -2,6 +2,7 @@ "account.block": "Blokkér @{name}", "account.block_domain": "Skjul alt fra {domain}", "account.blocked": "Blocked", + "account.direct": "Direct Message @{name}", "account.disclaimer_full": "Informasjonen nedenfor kan gi et ufullstendig bilde av brukerens profil.", "account.domain_blocked": "Domain hidden", "account.edit_profile": "Rediger profil", @@ -56,6 +57,7 @@ "column_header.unpin": "Løsne", "column_subheading.navigation": "Navigasjon", "column_subheading.settings": "Innstillinger", + "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.", "compose_form.hashtag_warning": "Denne tuten blir ikke listet under noen emneknagger da den er ulistet. Kun offentlige tuter kan søktes etter med emneknagg.", "compose_form.lock_disclaimer": "Din konto er ikke {locked}. Hvem som helst kan følge deg og se dine private poster.", "compose_form.lock_disclaimer.lock": "låst", diff --git a/app/javascript/mastodon/locales/oc.json b/app/javascript/mastodon/locales/oc.json index 24dfa93..39ba31d 100644 --- a/app/javascript/mastodon/locales/oc.json +++ b/app/javascript/mastodon/locales/oc.json @@ -2,6 +2,7 @@ "account.block": "Blocar @{name}", "account.block_domain": "Tot amagar del domeni {domain}", "account.blocked": "Blocat", + "account.direct": "Direct Message @{name}", "account.disclaimer_full": "Aquelas informacions de perfil pòdon èsser incomplètas.", "account.domain_blocked": "Domeni amagat", "account.edit_profile": "Modificar lo perfil", @@ -56,6 +57,7 @@ "column_header.unpin": "Despenjar", "column_subheading.navigation": "Navigacion", "column_subheading.settings": "Paramètres", + "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.", "compose_form.hashtag_warning": "Aqueste tut serà pas ligat a cap etiqueta estant qu’es pas listat. Òm pas cercar que los tuts publics per etiqueta.", "compose_form.lock_disclaimer": "Vòstre compte es pas {locked}. Tot lo mond pòt vos sègre e veire los estatuts reservats als seguidors.", "compose_form.lock_disclaimer.lock": "clavat", diff --git a/app/javascript/mastodon/locales/pl.json b/app/javascript/mastodon/locales/pl.json index 7262ce7..d9490c5 100644 --- a/app/javascript/mastodon/locales/pl.json +++ b/app/javascript/mastodon/locales/pl.json @@ -2,6 +2,7 @@ "account.block": "Blokuj @{name}", "account.block_domain": "Blokuj wszystko z {domain}", "account.blocked": "Zablokowany", + "account.direct": "Direct Message @{name}", "account.disclaimer_full": "Poniższe informacje mogą nie odwzorowywać bezbłędnie profilu użytkownika.", "account.domain_blocked": "Ukryto domenę", "account.edit_profile": "Edytuj profil", @@ -56,6 +57,7 @@ "column_header.unpin": "Cofnij przypięcie", "column_subheading.navigation": "Nawigacja", "column_subheading.settings": "Ustawienia", + "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.", "compose_form.hashtag_warning": "Ten wpis nie będzie widoczny pod podanymi hashtagami, ponieważ jest oznaczony jako niewidoczny. Tylko publiczne wpisy mogą zostać znalezione z użyciem hashtagów.", "compose_form.lock_disclaimer": "Twoje konto nie jest {locked}. Każdy, kto Cię śledzi, może wyświetlać Twoje wpisy przeznaczone tylko dla śledzących.", "compose_form.lock_disclaimer.lock": "zablokowane", diff --git a/app/javascript/mastodon/locales/pt-BR.json b/app/javascript/mastodon/locales/pt-BR.json index dcaeace..3d42eed 100644 --- a/app/javascript/mastodon/locales/pt-BR.json +++ b/app/javascript/mastodon/locales/pt-BR.json @@ -2,6 +2,7 @@ "account.block": "Bloquear @{name}", "account.block_domain": "Esconder tudo de {domain}", "account.blocked": "Bloqueado", + "account.direct": "Direct Message @{name}", "account.disclaimer_full": "As informações abaixo podem refletir o perfil do usuário de maneira incompleta.", "account.domain_blocked": "Domínio escondido", "account.edit_profile": "Editar perfil", @@ -56,6 +57,7 @@ "column_header.unpin": "Desafixar", "column_subheading.navigation": "Navegação", "column_subheading.settings": "Configurações", + "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.", "compose_form.hashtag_warning": "Esse toot não será listado em nenhuma hashtag por ser não listado. Somente toots públicos podem ser pesquisados por hashtag.", "compose_form.lock_disclaimer": "A sua conta não está {locked}. Qualquer pessoa pode te seguir e visualizar postagens direcionadas a apenas seguidores.", "compose_form.lock_disclaimer.lock": "trancada", diff --git a/app/javascript/mastodon/locales/pt.json b/app/javascript/mastodon/locales/pt.json index 4725a82..5c93614 100644 --- a/app/javascript/mastodon/locales/pt.json +++ b/app/javascript/mastodon/locales/pt.json @@ -2,6 +2,7 @@ "account.block": "Bloquear @{name}", "account.block_domain": "Esconder tudo do domínio {domain}", "account.blocked": "Blocked", + "account.direct": "Direct Message @{name}", "account.disclaimer_full": "As informações abaixo podem refletir o perfil do usuário de forma incompleta.", "account.domain_blocked": "Domain hidden", "account.edit_profile": "Editar perfil", @@ -56,6 +57,7 @@ "column_header.unpin": "Desafixar", "column_subheading.navigation": "Navegação", "column_subheading.settings": "Preferências", + "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.", "compose_form.hashtag_warning": "Esta pulbicacção não será listada em nenhuma hashtag por ser não listada. Somente publicações públicas podem ser pesquisadas por hashtag.", "compose_form.lock_disclaimer": "A tua conta não está {locked}. Qualquer pessoa pode seguir-te e ver as publicações direcionadas apenas a seguidores.", "compose_form.lock_disclaimer.lock": "bloqueada", diff --git a/app/javascript/mastodon/locales/ru.json b/app/javascript/mastodon/locales/ru.json index 8e7d366..7dffbb2 100644 --- a/app/javascript/mastodon/locales/ru.json +++ b/app/javascript/mastodon/locales/ru.json @@ -2,6 +2,7 @@ "account.block": "Блокировать", "account.block_domain": "Блокировать все с {domain}", "account.blocked": "Blocked", + "account.direct": "Direct Message @{name}", "account.disclaimer_full": "Нижеуказанная информация может не полностью отражать профиль пользователя.", "account.domain_blocked": "Domain hidden", "account.edit_profile": "Изменить профиль", @@ -56,6 +57,7 @@ "column_header.unpin": "Открепить", "column_subheading.navigation": "Навигация", "column_subheading.settings": "Настройки", + "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.", "compose_form.hashtag_warning": "Этот пост не будет показывается в поиске по хэштегу, т.к. он непубличный. Только публичные посты можно найти в поиске по хэштегу.", "compose_form.lock_disclaimer": "Ваш аккаунт не {locked}. Любой человек может подписаться на Вас и просматривать посты для подписчиков.", "compose_form.lock_disclaimer.lock": "закрыт", diff --git a/app/javascript/mastodon/locales/sk.json b/app/javascript/mastodon/locales/sk.json index e3b3239..0a248d2 100644 --- a/app/javascript/mastodon/locales/sk.json +++ b/app/javascript/mastodon/locales/sk.json @@ -2,6 +2,7 @@ "account.block": "Blokovať @{name}", "account.block_domain": "Ukryť všetko z {domain}", "account.blocked": "Blokovaný/á", + "account.direct": "Direct Message @{name}", "account.disclaimer_full": "Inofrmácie nižšie nemusia byť úplným odrazom uživateľovho účtu.", "account.domain_blocked": "Doména ukrytá", "account.edit_profile": "Upraviť profil", @@ -56,6 +57,7 @@ "column_header.unpin": "Odopnúť", "column_subheading.navigation": "Navigácia", "column_subheading.settings": "Nastavenia", + "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.", "compose_form.hashtag_warning": "Tento toot nebude zobrazený pod žiadným haštagom lebo nieje listovaný. Iba verejné tooty môžu byť nájdené podľa haštagu.", "compose_form.lock_disclaimer": "Váš účet nie je zamknutý. Ktokoľvek ťa môže nasledovať a vidieť tvoje správy pre sledujúcich.", "compose_form.lock_disclaimer.lock": "zamknutý", diff --git a/app/javascript/mastodon/locales/sr-Latn.json b/app/javascript/mastodon/locales/sr-Latn.json index d38e8e3..b9effce 100644 --- a/app/javascript/mastodon/locales/sr-Latn.json +++ b/app/javascript/mastodon/locales/sr-Latn.json @@ -2,6 +2,7 @@ "account.block": "Blokiraj korisnika @{name}", "account.block_domain": "Sakrij sve sa domena {domain}", "account.blocked": "Blocked", + "account.direct": "Direct Message @{name}", "account.disclaimer_full": "Navedene informacije možda ne odslikavaju korisnički profil u potpunosti.", "account.domain_blocked": "Domain hidden", "account.edit_profile": "Izmeni profil", @@ -56,6 +57,7 @@ "column_header.unpin": "Otkači", "column_subheading.navigation": "Navigacija", "column_subheading.settings": "Postavke", + "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.", "compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.", "compose_form.lock_disclaimer": "Vaš nalog nije {locked}. Svako može da Vas zaprati i da vidi objave namenjene samo Vašim pratiocima.", "compose_form.lock_disclaimer.lock": "zaključan", diff --git a/app/javascript/mastodon/locales/sr.json b/app/javascript/mastodon/locales/sr.json index 3be0c89..a6c5f22 100644 --- a/app/javascript/mastodon/locales/sr.json +++ b/app/javascript/mastodon/locales/sr.json @@ -2,6 +2,7 @@ "account.block": "Блокирај корисника @{name}", "account.block_domain": "Сакриј све са домена {domain}", "account.blocked": "Blocked", + "account.direct": "Direct Message @{name}", "account.disclaimer_full": "Наведене информације можда не одсликавају кориснички профил у потпуности.", "account.domain_blocked": "Domain hidden", "account.edit_profile": "Измени профил", @@ -56,6 +57,7 @@ "column_header.unpin": "Откачи", "column_subheading.navigation": "Навигација", "column_subheading.settings": "Поставке", + "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.", "compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.", "compose_form.lock_disclaimer": "Ваш налог није {locked}. Свако може да Вас запрати и да види објаве намењене само Вашим пратиоцима.", "compose_form.lock_disclaimer.lock": "закључан", diff --git a/app/javascript/mastodon/locales/sv.json b/app/javascript/mastodon/locales/sv.json index a13ba98..6dc3d7a 100644 --- a/app/javascript/mastodon/locales/sv.json +++ b/app/javascript/mastodon/locales/sv.json @@ -2,6 +2,7 @@ "account.block": "Blockera @{name}", "account.block_domain": "Dölj allt från {domain}", "account.blocked": "Blockerad", + "account.direct": "Direct Message @{name}", "account.disclaimer_full": "Informationen nedan kan spegla användarens profil ofullständigt.", "account.domain_blocked": "Domän gömd", "account.edit_profile": "Redigera profil", @@ -56,6 +57,7 @@ "column_header.unpin": "Ångra fäst", "column_subheading.navigation": "Navigation", "column_subheading.settings": "Inställningar", + "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.", "compose_form.hashtag_warning": "Denna toot kommer inte att listas under någon hashtag eftersom den är onoterad. Endast offentliga toots kan sökas med hashtag.", "compose_form.lock_disclaimer": "Ditt konto är inte {locked}. Vemsomhelst kan följa dig och även se dina inlägg skrivna för endast dina följare.", "compose_form.lock_disclaimer.lock": "låst", diff --git a/app/javascript/mastodon/locales/th.json b/app/javascript/mastodon/locales/th.json index 59ff10b..4de3540 100644 --- a/app/javascript/mastodon/locales/th.json +++ b/app/javascript/mastodon/locales/th.json @@ -2,6 +2,7 @@ "account.block": "Block @{name}", "account.block_domain": "Hide everything from {domain}", "account.blocked": "Blocked", + "account.direct": "Direct Message @{name}", "account.disclaimer_full": "Information below may reflect the user's profile incompletely.", "account.domain_blocked": "Domain hidden", "account.edit_profile": "Edit profile", @@ -56,6 +57,7 @@ "column_header.unpin": "Unpin", "column_subheading.navigation": "Navigation", "column_subheading.settings": "Settings", + "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.", "compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.", "compose_form.lock_disclaimer": "Your account is not {locked}. Anyone can follow you to view your follower-only posts.", "compose_form.lock_disclaimer.lock": "locked", diff --git a/app/javascript/mastodon/locales/tr.json b/app/javascript/mastodon/locales/tr.json index e83af31..9d0affe 100644 --- a/app/javascript/mastodon/locales/tr.json +++ b/app/javascript/mastodon/locales/tr.json @@ -2,6 +2,7 @@ "account.block": "Engelle @{name}", "account.block_domain": "Hide everything from {domain}", "account.blocked": "Blocked", + "account.direct": "Direct Message @{name}", "account.disclaimer_full": "Information below may reflect the user's profile incompletely.", "account.domain_blocked": "Domain hidden", "account.edit_profile": "Profili düzenle", @@ -56,6 +57,7 @@ "column_header.unpin": "Unpin", "column_subheading.navigation": "Navigasyon", "column_subheading.settings": "Ayarlar", + "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.", "compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.", "compose_form.lock_disclaimer": "Hesabınız {locked} değil. Sadece takipçilerle paylaştığınız gönderileri görebilmek için sizi herhangi bir kullanıcı takip edebilir.", "compose_form.lock_disclaimer.lock": "kilitli", diff --git a/app/javascript/mastodon/locales/uk.json b/app/javascript/mastodon/locales/uk.json index accc2d0..c49d3c7 100644 --- a/app/javascript/mastodon/locales/uk.json +++ b/app/javascript/mastodon/locales/uk.json @@ -2,6 +2,7 @@ "account.block": "Заблокувати", "account.block_domain": "Заглушити {domain}", "account.blocked": "Blocked", + "account.direct": "Direct Message @{name}", "account.disclaimer_full": "Information below may reflect the user's profile incompletely.", "account.domain_blocked": "Domain hidden", "account.edit_profile": "Налаштування профілю", @@ -56,6 +57,7 @@ "column_header.unpin": "Unpin", "column_subheading.navigation": "Навігація", "column_subheading.settings": "Налаштування", + "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.", "compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.", "compose_form.lock_disclaimer": "Ваш акаунт не {locked}. Кожен може підписатися на Вас та бачити Ваші приватні пости.", "compose_form.lock_disclaimer.lock": "приватний", diff --git a/app/javascript/mastodon/locales/zh-CN.json b/app/javascript/mastodon/locales/zh-CN.json index b9a912f..e95cf81 100644 --- a/app/javascript/mastodon/locales/zh-CN.json +++ b/app/javascript/mastodon/locales/zh-CN.json @@ -2,6 +2,7 @@ "account.block": "屏蔽 @{name}", "account.block_domain": "隐藏来自 {domain} 的内容", "account.blocked": "Blocked", + "account.direct": "Direct Message @{name}", "account.disclaimer_full": "此处显示的信息可能不是全部内容。", "account.domain_blocked": "Domain hidden", "account.edit_profile": "修改个人资料", @@ -56,6 +57,7 @@ "column_header.unpin": "取消固定", "column_subheading.navigation": "导航", "column_subheading.settings": "设置", + "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.", "compose_form.hashtag_warning": "这条嘟文被设置为“不公开”,因此它不会出现在任何话题标签的列表下。只有公开的嘟文才能通过话题标签进行搜索。", "compose_form.lock_disclaimer": "你的帐户没有{locked}。任何人都可以在关注你后立即查看仅关注者可见的嘟文。", "compose_form.lock_disclaimer.lock": "开启保护", diff --git a/app/javascript/mastodon/locales/zh-HK.json b/app/javascript/mastodon/locales/zh-HK.json index 91b1d00..1801c83 100644 --- a/app/javascript/mastodon/locales/zh-HK.json +++ b/app/javascript/mastodon/locales/zh-HK.json @@ -2,6 +2,7 @@ "account.block": "封鎖 @{name}", "account.block_domain": "隱藏來自 {domain} 的一切文章", "account.blocked": "Blocked", + "account.direct": "Direct Message @{name}", "account.disclaimer_full": "下列資料不一定完整。", "account.domain_blocked": "Domain hidden", "account.edit_profile": "修改個人資料", @@ -56,6 +57,7 @@ "column_header.unpin": "取下", "column_subheading.navigation": "瀏覽", "column_subheading.settings": "設定", + "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.", "compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.", "compose_form.lock_disclaimer": "你的用戶狀態為「{locked}」,任何人都能立即關注你,然後看到「只有關注者能看」的文章。", "compose_form.lock_disclaimer.lock": "公共", diff --git a/app/javascript/mastodon/locales/zh-TW.json b/app/javascript/mastodon/locales/zh-TW.json index 7e845c6..acbe6eb 100644 --- a/app/javascript/mastodon/locales/zh-TW.json +++ b/app/javascript/mastodon/locales/zh-TW.json @@ -2,6 +2,7 @@ "account.block": "封鎖 @{name}", "account.block_domain": "隱藏來自 {domain} 的一切貼文", "account.blocked": "Blocked", + "account.direct": "Direct Message @{name}", "account.disclaimer_full": "下列資料不一定完整。", "account.domain_blocked": "Domain hidden", "account.edit_profile": "編輯用者資訊", @@ -56,6 +57,7 @@ "column_header.unpin": "取下", "column_subheading.navigation": "瀏覽", "column_subheading.settings": "設定", + "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.", "compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.", "compose_form.lock_disclaimer": "你的帳號沒有{locked}。任何人都可以關注你,看到發給關注者的貼文。", "compose_form.lock_disclaimer.lock": "上鎖", diff --git a/app/javascript/mastodon/reducers/compose.js b/app/javascript/mastodon/reducers/compose.js index 5eadebb..a48c469 100644 --- a/app/javascript/mastodon/reducers/compose.js +++ b/app/javascript/mastodon/reducers/compose.js @@ -4,6 +4,7 @@ import { COMPOSE_CHANGE, COMPOSE_REPLY, COMPOSE_REPLY_CANCEL, + COMPOSE_DIRECT, COMPOSE_MENTION, COMPOSE_SUBMIT_REQUEST, COMPOSE_SUBMIT_SUCCESS, @@ -262,6 +263,12 @@ export default function compose(state = initialState, action) { .update('text', text => `${text}@${action.account.get('acct')} `) .set('focusDate', new Date()) .set('idempotencyKey', uuid()); + case COMPOSE_DIRECT: + return state + .update('text', text => `${text}@${action.account.get('acct')} `) + .set('privacy', 'direct') + .set('focusDate', new Date()) + .set('idempotencyKey', uuid()); case COMPOSE_SUGGESTIONS_CLEAR: return state.update('suggestions', ImmutableList(), list => list.clear()).set('suggestion_token', null); case COMPOSE_SUGGESTIONS_READY: From 947eedcab2c263e92900b53d64b184c1af856d24 Mon Sep 17 00:00:00 2001 From: takayamaki Date: Fri, 30 Mar 2018 10:04:04 +0900 Subject: [PATCH 057/381] update ja locale (#6965) related https://github.com/tootsuite/mastodon/pull/6956 --- app/javascript/mastodon/locales/ja.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/javascript/mastodon/locales/ja.json b/app/javascript/mastodon/locales/ja.json index c14d619..b03906d 100644 --- a/app/javascript/mastodon/locales/ja.json +++ b/app/javascript/mastodon/locales/ja.json @@ -2,7 +2,7 @@ "account.block": "@{name}さんをブロック", "account.block_domain": "{domain}全体を非表示", "account.blocked": "ブロック済み", - "account.direct": "Direct Message @{name}", + "account.direct": "@{name}さんにダイレクトメッセージ", "account.disclaimer_full": "以下の情報は不正確な可能性があります。", "account.domain_blocked": "ドメイン非表示中", "account.edit_profile": "プロフィールを編集", @@ -57,7 +57,7 @@ "column_header.unpin": "ピン留めを外す", "column_subheading.navigation": "ナビゲーション", "column_subheading.settings": "設定", - "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.", + "compose_form.direct_message_warning": "このトゥートはメンションされた人だけが見ることができます。", "compose_form.hashtag_warning": "このトゥートは未収載なのでハッシュタグの一覧に表示されません。公開トゥートだけがハッシュタグで検索できます。", "compose_form.lock_disclaimer": "あなたのアカウントは{locked}になっていません。誰でもあなたをフォローすることができ、フォロワー限定の投稿を見ることができます。", "compose_form.lock_disclaimer.lock": "非公開", From 47cee7cc8e47471b372630cd28d50c6284aad8b3 Mon Sep 17 00:00:00 2001 From: Pierre-Morgan Gate Date: Fri, 30 Mar 2018 00:52:44 -0700 Subject: [PATCH 058/381] Upgrade charlock_holmes to version 0.7.6 (#6966) This version fixes compilation errors when trying to build its native extension with ICU 61. --- Gemfile | 2 +- Gemfile.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index 29fa9cd..9e644e7 100644 --- a/Gemfile +++ b/Gemfile @@ -25,7 +25,7 @@ gem 'active_model_serializers', '~> 0.10' gem 'addressable', '~> 2.5' gem 'bootsnap' gem 'browser' -gem 'charlock_holmes', '~> 0.7.5' +gem 'charlock_holmes', '~> 0.7.6' gem 'iso-639' gem 'chewy', '~> 5.0' gem 'cld3', '~> 3.2.0' diff --git a/Gemfile.lock b/Gemfile.lock index f68419d..a185a60 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -113,7 +113,7 @@ GEM xpath (~> 2.0) case_transform (0.2) activesupport - charlock_holmes (0.7.5) + charlock_holmes (0.7.6) chewy (5.0.0) activesupport (>= 4.0) elasticsearch (>= 2.0.0) @@ -632,7 +632,7 @@ DEPENDENCIES capistrano-rbenv (~> 2.1) capistrano-yarn (~> 2.0) capybara (~> 2.15) - charlock_holmes (~> 0.7.5) + charlock_holmes (~> 0.7.6) chewy (~> 5.0) cld3 (~> 3.2.0) climate_control (~> 0.2) From a6c129ddbdaaa84bc631d85eb248fb5a9fa7eb96 Mon Sep 17 00:00:00 2001 From: ThibG Date: Fri, 30 Mar 2018 12:38:00 +0200 Subject: [PATCH 059/381] Add some UI for user-defined domain blocks (#6628) * Keep list of blocked domains Might be overkill, but I'm trying to follow the same logic as for blocked users * Add basic domain block UI * Add the domain blocks UI to Getting Started * Fix undefined URL in `fetchDomainBlocks` * Update all known users' domain_blocking relationship instead of just one's --- app/javascript/mastodon/actions/domain_blocks.js | 66 +++++++++++++++++++--- app/javascript/mastodon/components/domain.js | 42 ++++++++++++++ .../mastodon/containers/domain_container.js | 33 +++++++++++ .../features/account_timeline/components/header.js | 4 +- .../containers/header_container.js | 8 +-- .../mastodon/features/domain_blocks/index.js | 66 ++++++++++++++++++++++ .../mastodon/features/getting_started/index.js | 2 + app/javascript/mastodon/features/ui/index.js | 2 + .../mastodon/features/ui/util/async-components.js | 4 ++ app/javascript/mastodon/reducers/domain_lists.js | 23 ++++++++ app/javascript/mastodon/reducers/index.js | 2 + app/javascript/mastodon/reducers/relationships.js | 12 +++- app/javascript/styles/mastodon/components.scss | 24 ++++++++ 13 files changed, 271 insertions(+), 17 deletions(-) create mode 100644 app/javascript/mastodon/components/domain.js create mode 100644 app/javascript/mastodon/containers/domain_container.js create mode 100644 app/javascript/mastodon/features/domain_blocks/index.js create mode 100644 app/javascript/mastodon/reducers/domain_lists.js diff --git a/app/javascript/mastodon/actions/domain_blocks.js b/app/javascript/mastodon/actions/domain_blocks.js index 4436369..47e2df7 100644 --- a/app/javascript/mastodon/actions/domain_blocks.js +++ b/app/javascript/mastodon/actions/domain_blocks.js @@ -12,12 +12,18 @@ export const DOMAIN_BLOCKS_FETCH_REQUEST = 'DOMAIN_BLOCKS_FETCH_REQUEST'; export const DOMAIN_BLOCKS_FETCH_SUCCESS = 'DOMAIN_BLOCKS_FETCH_SUCCESS'; export const DOMAIN_BLOCKS_FETCH_FAIL = 'DOMAIN_BLOCKS_FETCH_FAIL'; -export function blockDomain(domain, accountId) { +export const DOMAIN_BLOCKS_EXPAND_REQUEST = 'DOMAIN_BLOCKS_EXPAND_REQUEST'; +export const DOMAIN_BLOCKS_EXPAND_SUCCESS = 'DOMAIN_BLOCKS_EXPAND_SUCCESS'; +export const DOMAIN_BLOCKS_EXPAND_FAIL = 'DOMAIN_BLOCKS_EXPAND_FAIL'; + +export function blockDomain(domain) { return (dispatch, getState) => { dispatch(blockDomainRequest(domain)); api(getState).post('/api/v1/domain_blocks', { domain }).then(() => { - dispatch(blockDomainSuccess(domain, accountId)); + const at_domain = '@' + domain; + const accounts = getState().get('accounts').filter(item => item.get('acct').endsWith(at_domain)).valueSeq().map(item => item.get('id')); + dispatch(blockDomainSuccess(domain, accounts)); }).catch(err => { dispatch(blockDomainFail(domain, err)); }); @@ -31,11 +37,11 @@ export function blockDomainRequest(domain) { }; }; -export function blockDomainSuccess(domain, accountId) { +export function blockDomainSuccess(domain, accounts) { return { type: DOMAIN_BLOCK_SUCCESS, domain, - accountId, + accounts, }; }; @@ -47,12 +53,14 @@ export function blockDomainFail(domain, error) { }; }; -export function unblockDomain(domain, accountId) { +export function unblockDomain(domain) { return (dispatch, getState) => { dispatch(unblockDomainRequest(domain)); api(getState).delete('/api/v1/domain_blocks', { params: { domain } }).then(() => { - dispatch(unblockDomainSuccess(domain, accountId)); + const at_domain = '@' + domain; + const accounts = getState().get('accounts').filter(item => item.get('acct').endsWith(at_domain)).valueSeq().map(item => item.get('id')); + dispatch(unblockDomainSuccess(domain, accounts)); }).catch(err => { dispatch(unblockDomainFail(domain, err)); }); @@ -66,11 +74,11 @@ export function unblockDomainRequest(domain) { }; }; -export function unblockDomainSuccess(domain, accountId) { +export function unblockDomainSuccess(domain, accounts) { return { type: DOMAIN_UNBLOCK_SUCCESS, domain, - accountId, + accounts, }; }; @@ -86,7 +94,7 @@ export function fetchDomainBlocks() { return (dispatch, getState) => { dispatch(fetchDomainBlocksRequest()); - api(getState).get().then(response => { + api(getState).get('/api/v1/domain_blocks').then(response => { const next = getLinks(response).refs.find(link => link.rel === 'next'); dispatch(fetchDomainBlocksSuccess(response.data, next ? next.uri : null)); }).catch(err => { @@ -115,3 +123,43 @@ export function fetchDomainBlocksFail(error) { error, }; }; + +export function expandDomainBlocks() { + return (dispatch, getState) => { + const url = getState().getIn(['domain_lists', 'blocks', 'next']); + + if (url === null) { + return; + } + + dispatch(expandDomainBlocksRequest()); + + api(getState).get(url).then(response => { + const next = getLinks(response).refs.find(link => link.rel === 'next'); + dispatch(expandDomainBlocksSuccess(response.data, next ? next.uri : null)); + }).catch(err => { + dispatch(expandDomainBlocksFail(err)); + }); + }; +}; + +export function expandDomainBlocksRequest() { + return { + type: DOMAIN_BLOCKS_EXPAND_REQUEST, + }; +}; + +export function expandDomainBlocksSuccess(domains, next) { + return { + type: DOMAIN_BLOCKS_EXPAND_SUCCESS, + domains, + next, + }; +}; + +export function expandDomainBlocksFail(error) { + return { + type: DOMAIN_BLOCKS_EXPAND_FAIL, + error, + }; +}; diff --git a/app/javascript/mastodon/components/domain.js b/app/javascript/mastodon/components/domain.js new file mode 100644 index 0000000..f657cb8 --- /dev/null +++ b/app/javascript/mastodon/components/domain.js @@ -0,0 +1,42 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import IconButton from './icon_button'; +import { defineMessages, injectIntl } from 'react-intl'; +import ImmutablePureComponent from 'react-immutable-pure-component'; + +const messages = defineMessages({ + unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unhide {domain}' }, +}); + +@injectIntl +export default class Account extends ImmutablePureComponent { + + static propTypes = { + domain: PropTypes.string, + onUnblockDomain: PropTypes.func.isRequired, + intl: PropTypes.object.isRequired, + }; + + handleDomainUnblock = () => { + this.props.onUnblockDomain(this.props.domain); + } + + render () { + const { domain, intl } = this.props; + + return ( +
+
+ + {domain} + + +
+ +
+
+
+ ); + } + +} diff --git a/app/javascript/mastodon/containers/domain_container.js b/app/javascript/mastodon/containers/domain_container.js new file mode 100644 index 0000000..52d5c16 --- /dev/null +++ b/app/javascript/mastodon/containers/domain_container.js @@ -0,0 +1,33 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import { blockDomain, unblockDomain } from '../actions/domain_blocks'; +import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; +import Domain from '../components/domain'; +import { openModal } from '../actions/modal'; + +const messages = defineMessages({ + blockDomainConfirm: { id: 'confirmations.domain_block.confirm', defaultMessage: 'Hide entire domain' }, +}); + +const makeMapStateToProps = () => { + const mapStateToProps = (state, { }) => ({ + }); + + return mapStateToProps; +}; + +const mapDispatchToProps = (dispatch, { intl }) => ({ + onBlockDomain (domain) { + dispatch(openModal('CONFIRM', { + message: {domain} }} />, + confirm: intl.formatMessage(messages.blockDomainConfirm), + onConfirm: () => dispatch(blockDomain(domain)), + })); + }, + + onUnblockDomain (domain) { + dispatch(unblockDomain(domain)); + }, +}); + +export default injectIntl(connect(makeMapStateToProps, mapDispatchToProps)(Domain)); diff --git a/app/javascript/mastodon/features/account_timeline/components/header.js b/app/javascript/mastodon/features/account_timeline/components/header.js index 789999d..1ae5126 100644 --- a/app/javascript/mastodon/features/account_timeline/components/header.js +++ b/app/javascript/mastodon/features/account_timeline/components/header.js @@ -62,7 +62,7 @@ export default class Header extends ImmutablePureComponent { if (!domain) return; - this.props.onBlockDomain(domain, this.props.account.get('id')); + this.props.onBlockDomain(domain); } handleUnblockDomain = () => { @@ -70,7 +70,7 @@ export default class Header extends ImmutablePureComponent { if (!domain) return; - this.props.onUnblockDomain(domain, this.props.account.get('id')); + this.props.onUnblockDomain(domain); } render () { diff --git a/app/javascript/mastodon/features/account_timeline/containers/header_container.js b/app/javascript/mastodon/features/account_timeline/containers/header_container.js index 214441b..4d53082 100644 --- a/app/javascript/mastodon/features/account_timeline/containers/header_container.js +++ b/app/javascript/mastodon/features/account_timeline/containers/header_container.js @@ -94,16 +94,16 @@ const mapDispatchToProps = (dispatch, { intl }) => ({ } }, - onBlockDomain (domain, accountId) { + onBlockDomain (domain) { dispatch(openModal('CONFIRM', { message: {domain} }} />, confirm: intl.formatMessage(messages.blockDomainConfirm), - onConfirm: () => dispatch(blockDomain(domain, accountId)), + onConfirm: () => dispatch(blockDomain(domain)), })); }, - onUnblockDomain (domain, accountId) { - dispatch(unblockDomain(domain, accountId)); + onUnblockDomain (domain) { + dispatch(unblockDomain(domain)); }, }); diff --git a/app/javascript/mastodon/features/domain_blocks/index.js b/app/javascript/mastodon/features/domain_blocks/index.js new file mode 100644 index 0000000..b17c47e --- /dev/null +++ b/app/javascript/mastodon/features/domain_blocks/index.js @@ -0,0 +1,66 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import ImmutablePropTypes from 'react-immutable-proptypes'; +import PropTypes from 'prop-types'; +import LoadingIndicator from '../../components/loading_indicator'; +import Column from '../ui/components/column'; +import ColumnBackButtonSlim from '../../components/column_back_button_slim'; +import DomainContainer from '../../containers/domain_container'; +import { fetchDomainBlocks, expandDomainBlocks } from '../../actions/domain_blocks'; +import { defineMessages, injectIntl } from 'react-intl'; +import ImmutablePureComponent from 'react-immutable-pure-component'; +import { debounce } from 'lodash'; +import ScrollableList from '../../components/scrollable_list'; + +const messages = defineMessages({ + heading: { id: 'column.domain_blocks', defaultMessage: 'Hidden domains' }, + unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unhide {domain}' }, +}); + +const mapStateToProps = state => ({ + domains: state.getIn(['domain_lists', 'blocks', 'items']), +}); + +@connect(mapStateToProps) +@injectIntl +export default class Blocks extends ImmutablePureComponent { + + static propTypes = { + params: PropTypes.object.isRequired, + dispatch: PropTypes.func.isRequired, + domains: ImmutablePropTypes.list, + intl: PropTypes.object.isRequired, + }; + + componentWillMount () { + this.props.dispatch(fetchDomainBlocks()); + } + + handleLoadMore = debounce(() => { + this.props.dispatch(expandDomainBlocks()); + }, 300, { leading: true }); + + render () { + const { intl, domains } = this.props; + + if (!domains) { + return ( + + + + ); + } + + return ( + + + + {domains.map(domain => + + )} + + + ); + } + +} diff --git a/app/javascript/mastodon/features/getting_started/index.js b/app/javascript/mastodon/features/getting_started/index.js index 3a87516..1a71cff 100644 --- a/app/javascript/mastodon/features/getting_started/index.js +++ b/app/javascript/mastodon/features/getting_started/index.js @@ -24,6 +24,7 @@ const messages = defineMessages({ sign_out: { id: 'navigation_bar.logout', defaultMessage: 'Logout' }, favourites: { id: 'navigation_bar.favourites', defaultMessage: 'Favourites' }, blocks: { id: 'navigation_bar.blocks', defaultMessage: 'Blocked users' }, + domain_blocks: { id: 'navigation_bar.domain_blocks', defaultMessage: 'Hidden domains' }, mutes: { id: 'navigation_bar.mutes', defaultMessage: 'Muted users' }, info: { id: 'navigation_bar.info', defaultMessage: 'Extended information' }, pins: { id: 'navigation_bar.pins', defaultMessage: 'Pinned toots' }, @@ -121,6 +122,7 @@ export default class GettingStarted extends ImmutablePureComponent { +
diff --git a/app/javascript/mastodon/features/ui/index.js b/app/javascript/mastodon/features/ui/index.js index b6a2a6c..8894eb4 100644 --- a/app/javascript/mastodon/features/ui/index.js +++ b/app/javascript/mastodon/features/ui/index.js @@ -37,6 +37,7 @@ import { FavouritedStatuses, ListTimeline, Blocks, + DomainBlocks, Mutes, PinnedStatuses, Lists, @@ -158,6 +159,7 @@ class SwitchingColumnsArea extends React.PureComponent { + diff --git a/app/javascript/mastodon/features/ui/util/async-components.js b/app/javascript/mastodon/features/ui/util/async-components.js index d658668..1995720 100644 --- a/app/javascript/mastodon/features/ui/util/async-components.js +++ b/app/javascript/mastodon/features/ui/util/async-components.js @@ -90,6 +90,10 @@ export function Blocks () { return import(/* webpackChunkName: "features/blocks" */'../../blocks'); } +export function DomainBlocks () { + return import(/* webpackChunkName: "features/domain_blocks" */'../../domain_blocks'); +} + export function Mutes () { return import(/* webpackChunkName: "features/mutes" */'../../mutes'); } diff --git a/app/javascript/mastodon/reducers/domain_lists.js b/app/javascript/mastodon/reducers/domain_lists.js new file mode 100644 index 0000000..a9e3519 --- /dev/null +++ b/app/javascript/mastodon/reducers/domain_lists.js @@ -0,0 +1,23 @@ +import { + DOMAIN_BLOCKS_FETCH_SUCCESS, + DOMAIN_BLOCKS_EXPAND_SUCCESS, + DOMAIN_UNBLOCK_SUCCESS, +} from '../actions/domain_blocks'; +import { Map as ImmutableMap, OrderedSet as ImmutableOrderedSet } from 'immutable'; + +const initialState = ImmutableMap({ + blocks: ImmutableMap(), +}); + +export default function domainLists(state = initialState, action) { + switch(action.type) { + case DOMAIN_BLOCKS_FETCH_SUCCESS: + return state.setIn(['blocks', 'items'], ImmutableOrderedSet(action.domains)).setIn(['blocks', 'next'], action.next); + case DOMAIN_BLOCKS_EXPAND_SUCCESS: + return state.updateIn(['blocks', 'items'], set => set.union(action.domains)).setIn(['blocks', 'next'], action.next); + case DOMAIN_UNBLOCK_SUCCESS: + return state.updateIn(['blocks', 'items'], set => set.delete(action.domain)); + default: + return state; + } +}; diff --git a/app/javascript/mastodon/reducers/index.js b/app/javascript/mastodon/reducers/index.js index b84b2d1..3d9a6a1 100644 --- a/app/javascript/mastodon/reducers/index.js +++ b/app/javascript/mastodon/reducers/index.js @@ -6,6 +6,7 @@ import alerts from './alerts'; import { loadingBarReducer } from 'react-redux-loading-bar'; import modal from './modal'; import user_lists from './user_lists'; +import domain_lists from './domain_lists'; import accounts from './accounts'; import accounts_counters from './accounts_counters'; import statuses from './statuses'; @@ -34,6 +35,7 @@ const reducers = { loadingBar: loadingBarReducer, modal, user_lists, + domain_lists, status_lists, accounts, accounts_counters, diff --git a/app/javascript/mastodon/reducers/relationships.js b/app/javascript/mastodon/reducers/relationships.js index c7b04a6..d1caabc 100644 --- a/app/javascript/mastodon/reducers/relationships.js +++ b/app/javascript/mastodon/reducers/relationships.js @@ -23,6 +23,14 @@ const normalizeRelationships = (state, relationships) => { return state; }; +const setDomainBlocking = (state, accounts, blocking) => { + return state.withMutations(map => { + accounts.forEach(id => { + map.setIn([id, 'domain_blocking'], blocking); + }); + }); +}; + const initialState = ImmutableMap(); export default function relationships(state = initialState, action) { @@ -37,9 +45,9 @@ export default function relationships(state = initialState, action) { case RELATIONSHIPS_FETCH_SUCCESS: return normalizeRelationships(state, action.relationships); case DOMAIN_BLOCK_SUCCESS: - return state.setIn([action.accountId, 'domain_blocking'], true); + return setDomainBlocking(state, action.accounts, true); case DOMAIN_UNBLOCK_SUCCESS: - return state.setIn([action.accountId, 'domain_blocking'], false); + return setDomainBlocking(state, action.accounts, false); default: return state; } diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index 447e6bc..6a83be4 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -1001,6 +1001,30 @@ } } +.domain { + padding: 10px; + border-bottom: 1px solid lighten($ui-base-color, 8%); + + .domain__domain-name { + flex: 1 1 auto; + display: block; + color: $primary-text-color; + text-decoration: none; + font-size: 14px; + font-weight: 500; + } +} + +.domain__wrapper { + display: flex; +} + +.domain_buttons { + height: 18px; + padding: 10px; + white-space: nowrap; +} + .account { padding: 10px; border-bottom: 1px solid lighten($ui-base-color, 8%); From e573bb0990ece4b1a521ccf8a4c7bec5972d3538 Mon Sep 17 00:00:00 2001 From: ThibG Date: Fri, 30 Mar 2018 15:44:54 +0200 Subject: [PATCH 060/381] Fix compatibility with PeerTube (#6968) * Support fetching objects of convertible types by URL (fixes #6924) * Ignore invalid hashtags --- app/lib/activitypub/activity/create.rb | 2 ++ app/services/fetch_atom_service.rb | 6 +++++- app/services/resolve_url_service.rb | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/app/lib/activitypub/activity/create.rb b/app/lib/activitypub/activity/create.rb index afee8a2..45c0e91 100644 --- a/app/lib/activitypub/activity/create.rb +++ b/app/lib/activitypub/activity/create.rb @@ -79,6 +79,8 @@ class ActivityPub::Activity::Create < ActivityPub::Activity hashtag = Tag.where(name: hashtag).first_or_initialize(name: hashtag) status.tags << hashtag + rescue ActiveRecord::RecordInvalid + nil end def process_mention(tag, status) diff --git a/app/services/fetch_atom_service.rb b/app/services/fetch_atom_service.rb index 62dea82..87076cc 100644 --- a/app/services/fetch_atom_service.rb +++ b/app/services/fetch_atom_service.rb @@ -44,7 +44,7 @@ class FetchAtomService < BaseService json = body_to_json(body) if supported_context?(json) && json['type'] == 'Person' && json['inbox'].present? [json['id'], { prefetched_body: body, id: true }, :activitypub] - elsif supported_context?(json) && json['type'] == 'Note' + elsif supported_context?(json) && expected_type?(json) [json['id'], { prefetched_body: body, id: true }, :activitypub] else @unsupported_activity = true @@ -61,6 +61,10 @@ class FetchAtomService < BaseService end end + def expected_type?(json) + (ActivityPub::Activity::Create::SUPPORTED_TYPES + ActivityPub::Activity::Create::CONVERTED_TYPES).include? json['type'] + end + def process_html(response) page = Nokogiri::HTML(response.body_with_limit) diff --git a/app/services/resolve_url_service.rb b/app/services/resolve_url_service.rb index 1f2b245..9499dc2 100644 --- a/app/services/resolve_url_service.rb +++ b/app/services/resolve_url_service.rb @@ -19,7 +19,7 @@ class ResolveURLService < BaseService case type when 'Person' FetchRemoteAccountService.new.call(atom_url, body, protocol) - when 'Note' + when 'Note', 'Article', 'Image', 'Video' FetchRemoteStatusService.new.call(atom_url, body, protocol) end end From fb3dc00ddab3f8c1af42ebc0520f6108cc40a1fc Mon Sep 17 00:00:00 2001 From: unarist Date: Sat, 31 Mar 2018 20:16:38 +0900 Subject: [PATCH 061/381] Ignore AbortError when cancelled sharing (#6978) `navigator.share()` rejects Promise if user cancelled sharing, and it may print it as an error on JavaScript console. This patch ignores it and prints other errors on the console. --- app/javascript/mastodon/components/status_action_bar.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/javascript/mastodon/components/status_action_bar.js b/app/javascript/mastodon/components/status_action_bar.js index cd59c78..e036dc1 100644 --- a/app/javascript/mastodon/components/status_action_bar.js +++ b/app/javascript/mastodon/components/status_action_bar.js @@ -67,6 +67,8 @@ export default class StatusActionBar extends ImmutablePureComponent { navigator.share({ text: this.props.status.get('search_index'), url: this.props.status.get('url'), + }).catch((e) => { + if (e.name !== 'AbortError') console.error(e); }); } From 3886bfb5eb9e08bb5de61b1314c6cae1be3e5530 Mon Sep 17 00:00:00 2001 From: Daniel Hunsaker Date: Sat, 31 Mar 2018 05:17:25 -0600 Subject: [PATCH 062/381] [Nanobox] Enable ElasticSearch support by default (#6977) Admins can still disable the feature by adding `ES_ENABLED=false` to their environment, if they prefer not to use it. Be sure to set the variable before you deploy! --- .env.nanobox | 6 +++--- boxfile.yml | 31 +++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/.env.nanobox b/.env.nanobox index 0d14f8a..8e0af6a 100644 --- a/.env.nanobox +++ b/.env.nanobox @@ -14,9 +14,9 @@ DB_PORT=5432 DATABASE_URL=postgresql://$DATA_DB_USER:$DATA_DB_PASS@$DATA_DB_HOST/gonano # Optional ElasticSearch configuration -# ES_ENABLED=true -# ES_HOST=localhost -# ES_PORT=9200 +ES_ENABLED=true +ES_HOST=$DATA_ELASTIC_HOST +ES_PORT=9200 # Optimizations LD_PRELOAD=/data/lib/libjemalloc.so diff --git a/boxfile.yml b/boxfile.yml index aa2003a..9368a7d 100644 --- a/boxfile.yml +++ b/boxfile.yml @@ -61,6 +61,11 @@ deploy.config: before_live: web.web: - bundle exec rake db:migrate:setup + - |- + if [[ "${ES_ENABLED}" != "false" ]] + then + bundle exec rake chewy:deploy + fi web.web: @@ -208,6 +213,32 @@ data.db: done +data.elastic: + image: nanobox/elasticsearch:5 + + cron: + - id: backup + schedule: '0 3 * * *' + command: | + id=$(cat /proc/sys/kernel/random/uuid) + curl -X PUT -H "Content-Type: application/json" "127.0.0.1:9200/_snapshot/${id}" -d "{\"type\": \"fs\",\"settings\": {\"location\": \"/var/tmp/${id}\",\"compress\": true}}" + curl -X PUT -H "Content-Type: application/json" "127.0.0.1:9200/_snapshot/${id}/backup?wait_for_completion=true&pretty" + tar -cz -C "/var/tmp/${id}" . | + curl -k -H "X-AUTH-TOKEN: ${WAREHOUSE_DATA_HOARDER_TOKEN}" https://${WAREHOUSE_DATA_HOARDER_HOST}:7410/blobs/backup-${HOSTNAME}-$(date -u +%Y-%m-%d.%H-%M-%S).tgz -X POST -T - >&2 + curl -X DELETE -H "Content-Type: application/json" "127.0.0.1:9200/_snapshot/${id}" + rm -rf "/var/tmp/${id}" + curl -k -s -H "X-AUTH-TOKEN: ${WAREHOUSE_DATA_HOARDER_TOKEN}" https://${WAREHOUSE_DATA_HOARDER_HOST}:7410/blobs/ | + sed 's/,/\n/g' | + grep ${HOSTNAME} | + sort | + head -n-${BACKUP_COUNT:-1} | + sed 's/.*: \?"\(.*\)".*/\1/' | + while read file + do + curl -k -H "X-AUTH-TOKEN: ${WAREHOUSE_DATA_HOARDER_TOKEN}" https://${WAREHOUSE_DATA_HOARDER_HOST}:7410/blobs/${file} -X DELETE + done + + data.redis: image: nanobox/redis:4.0 From b7d633c1bbe8a250d10c517810b700042bf2f40e Mon Sep 17 00:00:00 2001 From: mayaeh Date: Sat, 31 Mar 2018 21:09:39 +0900 Subject: [PATCH 063/381] i18n: Add Japanese translations for domain blocks (#6979) * run yarn manage:translations * Update Japanese translations for domain blocks. --- app/javascript/mastodon/locales/ar.json | 2 ++ app/javascript/mastodon/locales/bg.json | 2 ++ app/javascript/mastodon/locales/ca.json | 2 ++ app/javascript/mastodon/locales/de.json | 2 ++ .../mastodon/locales/defaultMessages.json | 39 ++++++++++++++++++++++ app/javascript/mastodon/locales/en.json | 2 ++ app/javascript/mastodon/locales/eo.json | 2 ++ app/javascript/mastodon/locales/es.json | 2 ++ app/javascript/mastodon/locales/fa.json | 2 ++ app/javascript/mastodon/locales/fi.json | 2 ++ app/javascript/mastodon/locales/fr.json | 2 ++ app/javascript/mastodon/locales/gl.json | 2 ++ app/javascript/mastodon/locales/he.json | 2 ++ app/javascript/mastodon/locales/hr.json | 2 ++ app/javascript/mastodon/locales/hu.json | 2 ++ app/javascript/mastodon/locales/hy.json | 2 ++ app/javascript/mastodon/locales/id.json | 2 ++ app/javascript/mastodon/locales/io.json | 2 ++ app/javascript/mastodon/locales/it.json | 2 ++ app/javascript/mastodon/locales/ja.json | 2 ++ app/javascript/mastodon/locales/ko.json | 2 ++ app/javascript/mastodon/locales/nl.json | 2 ++ app/javascript/mastodon/locales/no.json | 2 ++ app/javascript/mastodon/locales/oc.json | 2 ++ app/javascript/mastodon/locales/pl.json | 2 ++ app/javascript/mastodon/locales/pt-BR.json | 2 ++ app/javascript/mastodon/locales/pt.json | 2 ++ app/javascript/mastodon/locales/ru.json | 2 ++ app/javascript/mastodon/locales/sk.json | 2 ++ app/javascript/mastodon/locales/sr-Latn.json | 2 ++ app/javascript/mastodon/locales/sr.json | 2 ++ app/javascript/mastodon/locales/sv.json | 2 ++ app/javascript/mastodon/locales/th.json | 2 ++ app/javascript/mastodon/locales/tr.json | 2 ++ app/javascript/mastodon/locales/uk.json | 2 ++ app/javascript/mastodon/locales/zh-CN.json | 2 ++ app/javascript/mastodon/locales/zh-HK.json | 2 ++ app/javascript/mastodon/locales/zh-TW.json | 2 ++ 38 files changed, 113 insertions(+) diff --git a/app/javascript/mastodon/locales/ar.json b/app/javascript/mastodon/locales/ar.json index f9af062..3078b5b 100644 --- a/app/javascript/mastodon/locales/ar.json +++ b/app/javascript/mastodon/locales/ar.json @@ -40,6 +40,7 @@ "bundle_modal_error.retry": "إعادة المحاولة", "column.blocks": "الحسابات المحجوبة", "column.community": "الخيط العام المحلي", + "column.domain_blocks": "Hidden domains", "column.favourites": "المفضلة", "column.follow_requests": "طلبات المتابعة", "column.home": "الرئيسية", @@ -153,6 +154,7 @@ "mute_modal.hide_notifications": "هل تود إخفاء الإخطارات القادمة من هذا المستخدم ؟", "navigation_bar.blocks": "الحسابات المحجوبة", "navigation_bar.community_timeline": "الخيط العام المحلي", + "navigation_bar.domain_blocks": "Hidden domains", "navigation_bar.edit_profile": "تعديل الملف الشخصي", "navigation_bar.favourites": "المفضلة", "navigation_bar.follow_requests": "طلبات المتابعة", diff --git a/app/javascript/mastodon/locales/bg.json b/app/javascript/mastodon/locales/bg.json index 58795ca..9aaff0d 100644 --- a/app/javascript/mastodon/locales/bg.json +++ b/app/javascript/mastodon/locales/bg.json @@ -40,6 +40,7 @@ "bundle_modal_error.retry": "Try again", "column.blocks": "Blocked users", "column.community": "Local timeline", + "column.domain_blocks": "Hidden domains", "column.favourites": "Favourites", "column.follow_requests": "Follow requests", "column.home": "Начало", @@ -153,6 +154,7 @@ "mute_modal.hide_notifications": "Hide notifications from this user?", "navigation_bar.blocks": "Blocked users", "navigation_bar.community_timeline": "Local timeline", + "navigation_bar.domain_blocks": "Hidden domains", "navigation_bar.edit_profile": "Редактирай профил", "navigation_bar.favourites": "Favourites", "navigation_bar.follow_requests": "Follow requests", diff --git a/app/javascript/mastodon/locales/ca.json b/app/javascript/mastodon/locales/ca.json index b0ce34c..ec5a8a1 100644 --- a/app/javascript/mastodon/locales/ca.json +++ b/app/javascript/mastodon/locales/ca.json @@ -40,6 +40,7 @@ "bundle_modal_error.retry": "Torna-ho a provar", "column.blocks": "Usuaris blocats", "column.community": "Línia de temps local", + "column.domain_blocks": "Hidden domains", "column.favourites": "Favorits", "column.follow_requests": "Peticions per seguir-te", "column.home": "Inici", @@ -153,6 +154,7 @@ "mute_modal.hide_notifications": "Amagar notificacions d'aquest usuari?", "navigation_bar.blocks": "Usuaris bloquejats", "navigation_bar.community_timeline": "Línia de temps Local", + "navigation_bar.domain_blocks": "Hidden domains", "navigation_bar.edit_profile": "Editar perfil", "navigation_bar.favourites": "Favorits", "navigation_bar.follow_requests": "Sol·licituds de seguiment", diff --git a/app/javascript/mastodon/locales/de.json b/app/javascript/mastodon/locales/de.json index eb0c505..a618b85 100644 --- a/app/javascript/mastodon/locales/de.json +++ b/app/javascript/mastodon/locales/de.json @@ -40,6 +40,7 @@ "bundle_modal_error.retry": "Erneut versuchen", "column.blocks": "Blockierte Profile", "column.community": "Lokale Zeitleiste", + "column.domain_blocks": "Hidden domains", "column.favourites": "Favoriten", "column.follow_requests": "Folgeanfragen", "column.home": "Startseite", @@ -153,6 +154,7 @@ "mute_modal.hide_notifications": "Benachrichtigungen von diesem Account verbergen?", "navigation_bar.blocks": "Blockierte Profile", "navigation_bar.community_timeline": "Lokale Zeitleiste", + "navigation_bar.domain_blocks": "Hidden domains", "navigation_bar.edit_profile": "Profil bearbeiten", "navigation_bar.favourites": "Favoriten", "navigation_bar.follow_requests": "Folgeanfragen", diff --git a/app/javascript/mastodon/locales/defaultMessages.json b/app/javascript/mastodon/locales/defaultMessages.json index 1fe6861..4c9401d 100644 --- a/app/javascript/mastodon/locales/defaultMessages.json +++ b/app/javascript/mastodon/locales/defaultMessages.json @@ -95,6 +95,15 @@ { "descriptors": [ { + "defaultMessage": "Unhide {domain}", + "id": "account.unblock_domain" + } + ], + "path": "app/javascript/mastodon/components/domain.json" + }, + { + "descriptors": [ + { "defaultMessage": "Load more", "id": "status.load_more" } @@ -301,6 +310,19 @@ { "descriptors": [ { + "defaultMessage": "Hide entire domain", + "id": "confirmations.domain_block.confirm" + }, + { + "defaultMessage": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable.", + "id": "confirmations.domain_block.message" + } + ], + "path": "app/javascript/mastodon/containers/domain_container.json" + }, + { + "descriptors": [ + { "defaultMessage": "Delete", "id": "confirmations.delete.confirm" }, @@ -852,6 +874,19 @@ { "descriptors": [ { + "defaultMessage": "Hidden domains", + "id": "column.domain_blocks" + }, + { + "defaultMessage": "Unhide {domain}", + "id": "account.unblock_domain" + } + ], + "path": "app/javascript/mastodon/features/domain_blocks/index.json" + }, + { + "descriptors": [ + { "defaultMessage": "Favourites", "id": "column.favourites" } @@ -931,6 +966,10 @@ "id": "navigation_bar.blocks" }, { + "defaultMessage": "Hidden domains", + "id": "navigation_bar.domain_blocks" + }, + { "defaultMessage": "Muted users", "id": "navigation_bar.mutes" }, diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json index d481596..23040f7 100644 --- a/app/javascript/mastodon/locales/en.json +++ b/app/javascript/mastodon/locales/en.json @@ -40,6 +40,7 @@ "bundle_modal_error.retry": "Try again", "column.blocks": "Blocked users", "column.community": "Local timeline", + "column.domain_blocks": "Hidden domains", "column.favourites": "Favourites", "column.follow_requests": "Follow requests", "column.home": "Home", @@ -153,6 +154,7 @@ "mute_modal.hide_notifications": "Hide notifications from this user?", "navigation_bar.blocks": "Blocked users", "navigation_bar.community_timeline": "Local timeline", + "navigation_bar.domain_blocks": "Hidden domains", "navigation_bar.edit_profile": "Edit profile", "navigation_bar.favourites": "Favourites", "navigation_bar.follow_requests": "Follow requests", diff --git a/app/javascript/mastodon/locales/eo.json b/app/javascript/mastodon/locales/eo.json index 9b00edb..82b7494 100644 --- a/app/javascript/mastodon/locales/eo.json +++ b/app/javascript/mastodon/locales/eo.json @@ -40,6 +40,7 @@ "bundle_modal_error.retry": "Bonvolu reprovi", "column.blocks": "Blokitaj uzantoj", "column.community": "Loka tempolinio", + "column.domain_blocks": "Hidden domains", "column.favourites": "Stelumoj", "column.follow_requests": "Petoj de sekvado", "column.home": "Hejmo", @@ -153,6 +154,7 @@ "mute_modal.hide_notifications": "Ĉu kaŝi sciigojn el ĉi tiu uzanto?", "navigation_bar.blocks": "Blokitaj uzantoj", "navigation_bar.community_timeline": "Loka tempolinio", + "navigation_bar.domain_blocks": "Hidden domains", "navigation_bar.edit_profile": "Redakti profilon", "navigation_bar.favourites": "Stelumoj", "navigation_bar.follow_requests": "Petoj de sekvado", diff --git a/app/javascript/mastodon/locales/es.json b/app/javascript/mastodon/locales/es.json index 9f03b31..6f9c06c 100644 --- a/app/javascript/mastodon/locales/es.json +++ b/app/javascript/mastodon/locales/es.json @@ -40,6 +40,7 @@ "bundle_modal_error.retry": "Inténtalo de nuevo", "column.blocks": "Usuarios bloqueados", "column.community": "Línea de tiempo local", + "column.domain_blocks": "Hidden domains", "column.favourites": "Favoritos", "column.follow_requests": "Solicitudes de seguimiento", "column.home": "Inicio", @@ -153,6 +154,7 @@ "mute_modal.hide_notifications": "Ocultar notificaciones de este usuario?", "navigation_bar.blocks": "Usuarios bloqueados", "navigation_bar.community_timeline": "Historia local", + "navigation_bar.domain_blocks": "Hidden domains", "navigation_bar.edit_profile": "Editar perfil", "navigation_bar.favourites": "Favoritos", "navigation_bar.follow_requests": "Solicitudes para seguirte", diff --git a/app/javascript/mastodon/locales/fa.json b/app/javascript/mastodon/locales/fa.json index 9421746..4b64ca3 100644 --- a/app/javascript/mastodon/locales/fa.json +++ b/app/javascript/mastodon/locales/fa.json @@ -40,6 +40,7 @@ "bundle_modal_error.retry": "تلاش دوباره", "column.blocks": "کاربران مسدودشده", "column.community": "نوشته‌های محلی", + "column.domain_blocks": "Hidden domains", "column.favourites": "پسندیده‌ها", "column.follow_requests": "درخواست‌های پیگیری", "column.home": "خانه", @@ -153,6 +154,7 @@ "mute_modal.hide_notifications": "اعلان‌های این کاربر پنهان شود؟", "navigation_bar.blocks": "کاربران مسدودشده", "navigation_bar.community_timeline": "نوشته‌های محلی", + "navigation_bar.domain_blocks": "Hidden domains", "navigation_bar.edit_profile": "ویرایش نمایه", "navigation_bar.favourites": "پسندیده‌ها", "navigation_bar.follow_requests": "درخواست‌های پیگیری", diff --git a/app/javascript/mastodon/locales/fi.json b/app/javascript/mastodon/locales/fi.json index fce441d..f4be805 100644 --- a/app/javascript/mastodon/locales/fi.json +++ b/app/javascript/mastodon/locales/fi.json @@ -40,6 +40,7 @@ "bundle_modal_error.retry": "Yritä uudestaan", "column.blocks": "Estetyt käyttäjät", "column.community": "Paikallinen aikajana", + "column.domain_blocks": "Hidden domains", "column.favourites": "Suosikit", "column.follow_requests": "Seurauspyynnöt", "column.home": "Koti", @@ -153,6 +154,7 @@ "mute_modal.hide_notifications": "Piilota ilmoitukset tältä käyttäjältä?", "navigation_bar.blocks": "Estetyt käyttäjät", "navigation_bar.community_timeline": "Paikallinen aikajana", + "navigation_bar.domain_blocks": "Hidden domains", "navigation_bar.edit_profile": "Muokkaa profiilia", "navigation_bar.favourites": "Suosikit", "navigation_bar.follow_requests": "Seurauspyynnöt", diff --git a/app/javascript/mastodon/locales/fr.json b/app/javascript/mastodon/locales/fr.json index 6eb34e6..58e6ad5 100644 --- a/app/javascript/mastodon/locales/fr.json +++ b/app/javascript/mastodon/locales/fr.json @@ -40,6 +40,7 @@ "bundle_modal_error.retry": "Réessayer", "column.blocks": "Comptes bloqués", "column.community": "Fil public local", + "column.domain_blocks": "Hidden domains", "column.favourites": "Favoris", "column.follow_requests": "Demandes de suivi", "column.home": "Accueil", @@ -153,6 +154,7 @@ "mute_modal.hide_notifications": "Masquer les notifications de cet utilisateur ?", "navigation_bar.blocks": "Comptes bloqués", "navigation_bar.community_timeline": "Fil public local", + "navigation_bar.domain_blocks": "Hidden domains", "navigation_bar.edit_profile": "Modifier le profil", "navigation_bar.favourites": "Favoris", "navigation_bar.follow_requests": "Demandes de suivi", diff --git a/app/javascript/mastodon/locales/gl.json b/app/javascript/mastodon/locales/gl.json index a0823b9..8d58640 100644 --- a/app/javascript/mastodon/locales/gl.json +++ b/app/javascript/mastodon/locales/gl.json @@ -40,6 +40,7 @@ "bundle_modal_error.retry": "Inténteo de novo", "column.blocks": "Usuarias bloqueadas", "column.community": "Liña temporal local", + "column.domain_blocks": "Hidden domains", "column.favourites": "Favoritas", "column.follow_requests": "Peticións de seguimento", "column.home": "Inicio", @@ -153,6 +154,7 @@ "mute_modal.hide_notifications": "Esconder notificacións deste usuario?", "navigation_bar.blocks": "Usuarias bloqueadas", "navigation_bar.community_timeline": "Liña temporal local", + "navigation_bar.domain_blocks": "Hidden domains", "navigation_bar.edit_profile": "Editar perfil", "navigation_bar.favourites": "Favoritas", "navigation_bar.follow_requests": "Peticións de seguimento", diff --git a/app/javascript/mastodon/locales/he.json b/app/javascript/mastodon/locales/he.json index 0e2ee8d..6bec26f 100644 --- a/app/javascript/mastodon/locales/he.json +++ b/app/javascript/mastodon/locales/he.json @@ -40,6 +40,7 @@ "bundle_modal_error.retry": "לנסות שוב", "column.blocks": "חסימות", "column.community": "ציר זמן מקומי", + "column.domain_blocks": "Hidden domains", "column.favourites": "חיבובים", "column.follow_requests": "בקשות מעקב", "column.home": "בבית", @@ -153,6 +154,7 @@ "mute_modal.hide_notifications": "להסתיר הודעות מחשבון זה?", "navigation_bar.blocks": "חסימות", "navigation_bar.community_timeline": "ציר זמן מקומי", + "navigation_bar.domain_blocks": "Hidden domains", "navigation_bar.edit_profile": "עריכת פרופיל", "navigation_bar.favourites": "חיבובים", "navigation_bar.follow_requests": "בקשות מעקב", diff --git a/app/javascript/mastodon/locales/hr.json b/app/javascript/mastodon/locales/hr.json index 1e8ce8e..f7a5d0a 100644 --- a/app/javascript/mastodon/locales/hr.json +++ b/app/javascript/mastodon/locales/hr.json @@ -40,6 +40,7 @@ "bundle_modal_error.retry": "Try again", "column.blocks": "Blokirani korisnici", "column.community": "Lokalni timeline", + "column.domain_blocks": "Hidden domains", "column.favourites": "Favoriti", "column.follow_requests": "Zahtjevi za slijeđenje", "column.home": "Dom", @@ -153,6 +154,7 @@ "mute_modal.hide_notifications": "Hide notifications from this user?", "navigation_bar.blocks": "Blokirani korisnici", "navigation_bar.community_timeline": "Lokalni timeline", + "navigation_bar.domain_blocks": "Hidden domains", "navigation_bar.edit_profile": "Uredi profil", "navigation_bar.favourites": "Favoriti", "navigation_bar.follow_requests": "Zahtjevi za slijeđenje", diff --git a/app/javascript/mastodon/locales/hu.json b/app/javascript/mastodon/locales/hu.json index deb17c6..8b9c149 100644 --- a/app/javascript/mastodon/locales/hu.json +++ b/app/javascript/mastodon/locales/hu.json @@ -40,6 +40,7 @@ "bundle_modal_error.retry": "Próbálja újra", "column.blocks": "Letiltott felhasználók", "column.community": "Helyi idővonal", + "column.domain_blocks": "Hidden domains", "column.favourites": "Kedvencek", "column.follow_requests": "Követési kérések", "column.home": "Kezdőlap", @@ -153,6 +154,7 @@ "mute_modal.hide_notifications": "Értesítések elrejtése ezen felhasználótól?", "navigation_bar.blocks": "Tiltott felhasználók", "navigation_bar.community_timeline": "Helyi idővonal", + "navigation_bar.domain_blocks": "Hidden domains", "navigation_bar.edit_profile": "Profil szerkesztése", "navigation_bar.favourites": "Kedvencek", "navigation_bar.follow_requests": "Követési kérések", diff --git a/app/javascript/mastodon/locales/hy.json b/app/javascript/mastodon/locales/hy.json index ee20553..22ba89a 100644 --- a/app/javascript/mastodon/locales/hy.json +++ b/app/javascript/mastodon/locales/hy.json @@ -40,6 +40,7 @@ "bundle_modal_error.retry": "Կրկին փորձել", "column.blocks": "Արգելափակված օգտատերեր", "column.community": "Տեղական հոսք", + "column.domain_blocks": "Hidden domains", "column.favourites": "Հավանածներ", "column.follow_requests": "Հետեւելու հայցեր", "column.home": "Հիմնական", @@ -153,6 +154,7 @@ "mute_modal.hide_notifications": "Թաքցնե՞լ ցանուցումներն այս օգտատիրոջից։", "navigation_bar.blocks": "Արգելափակված օգտատերեր", "navigation_bar.community_timeline": "Տեղական հոսք", + "navigation_bar.domain_blocks": "Hidden domains", "navigation_bar.edit_profile": "Խմբագրել անձնական էջը", "navigation_bar.favourites": "Հավանածներ", "navigation_bar.follow_requests": "Հետեւելու հայցեր", diff --git a/app/javascript/mastodon/locales/id.json b/app/javascript/mastodon/locales/id.json index cae3211..1ef610f 100644 --- a/app/javascript/mastodon/locales/id.json +++ b/app/javascript/mastodon/locales/id.json @@ -40,6 +40,7 @@ "bundle_modal_error.retry": "Coba lagi", "column.blocks": "Pengguna diblokir", "column.community": "Linimasa Lokal", + "column.domain_blocks": "Hidden domains", "column.favourites": "Favorit", "column.follow_requests": "Permintaan mengikuti", "column.home": "Beranda", @@ -153,6 +154,7 @@ "mute_modal.hide_notifications": "Hide notifications from this user?", "navigation_bar.blocks": "Pengguna diblokir", "navigation_bar.community_timeline": "Linimasa lokal", + "navigation_bar.domain_blocks": "Hidden domains", "navigation_bar.edit_profile": "Ubah profil", "navigation_bar.favourites": "Favorit", "navigation_bar.follow_requests": "Permintaan mengikuti", diff --git a/app/javascript/mastodon/locales/io.json b/app/javascript/mastodon/locales/io.json index 121d745..1435178 100644 --- a/app/javascript/mastodon/locales/io.json +++ b/app/javascript/mastodon/locales/io.json @@ -40,6 +40,7 @@ "bundle_modal_error.retry": "Try again", "column.blocks": "Blokusita uzeri", "column.community": "Lokala tempolineo", + "column.domain_blocks": "Hidden domains", "column.favourites": "Favorati", "column.follow_requests": "Demandi di sequado", "column.home": "Hemo", @@ -153,6 +154,7 @@ "mute_modal.hide_notifications": "Hide notifications from this user?", "navigation_bar.blocks": "Blokusita uzeri", "navigation_bar.community_timeline": "Lokala tempolineo", + "navigation_bar.domain_blocks": "Hidden domains", "navigation_bar.edit_profile": "Modifikar profilo", "navigation_bar.favourites": "Favorati", "navigation_bar.follow_requests": "Demandi di sequado", diff --git a/app/javascript/mastodon/locales/it.json b/app/javascript/mastodon/locales/it.json index 5e57143..226127e 100644 --- a/app/javascript/mastodon/locales/it.json +++ b/app/javascript/mastodon/locales/it.json @@ -40,6 +40,7 @@ "bundle_modal_error.retry": "Try again", "column.blocks": "Utenti bloccati", "column.community": "Timeline locale", + "column.domain_blocks": "Hidden domains", "column.favourites": "Apprezzati", "column.follow_requests": "Richieste di amicizia", "column.home": "Home", @@ -153,6 +154,7 @@ "mute_modal.hide_notifications": "Hide notifications from this user?", "navigation_bar.blocks": "Utenti bloccati", "navigation_bar.community_timeline": "Timeline locale", + "navigation_bar.domain_blocks": "Hidden domains", "navigation_bar.edit_profile": "Modifica profilo", "navigation_bar.favourites": "Apprezzati", "navigation_bar.follow_requests": "Richieste di amicizia", diff --git a/app/javascript/mastodon/locales/ja.json b/app/javascript/mastodon/locales/ja.json index b03906d..80a13a9 100644 --- a/app/javascript/mastodon/locales/ja.json +++ b/app/javascript/mastodon/locales/ja.json @@ -40,6 +40,7 @@ "bundle_modal_error.retry": "再試行", "column.blocks": "ブロックしたユーザー", "column.community": "ローカルタイムライン", + "column.domain_blocks": "非表示にしたドメイン", "column.favourites": "お気に入り", "column.follow_requests": "フォローリクエスト", "column.home": "ホーム", @@ -153,6 +154,7 @@ "mute_modal.hide_notifications": "このユーザーからの通知を隠しますか?", "navigation_bar.blocks": "ブロックしたユーザー", "navigation_bar.community_timeline": "ローカルタイムライン", + "navigation_bar.domain_blocks": "非表示にしたドメイン", "navigation_bar.edit_profile": "プロフィールを編集", "navigation_bar.favourites": "お気に入り", "navigation_bar.follow_requests": "フォローリクエスト", diff --git a/app/javascript/mastodon/locales/ko.json b/app/javascript/mastodon/locales/ko.json index fa15214..449df42 100644 --- a/app/javascript/mastodon/locales/ko.json +++ b/app/javascript/mastodon/locales/ko.json @@ -40,6 +40,7 @@ "bundle_modal_error.retry": "다시 시도", "column.blocks": "차단 중인 사용자", "column.community": "로컬 타임라인", + "column.domain_blocks": "Hidden domains", "column.favourites": "즐겨찾기", "column.follow_requests": "팔로우 요청", "column.home": "홈", @@ -153,6 +154,7 @@ "mute_modal.hide_notifications": "이 사용자로부터의 알림을 뮤트하시겠습니까?", "navigation_bar.blocks": "차단한 사용자", "navigation_bar.community_timeline": "로컬 타임라인", + "navigation_bar.domain_blocks": "Hidden domains", "navigation_bar.edit_profile": "프로필 편집", "navigation_bar.favourites": "즐겨찾기", "navigation_bar.follow_requests": "팔로우 요청", diff --git a/app/javascript/mastodon/locales/nl.json b/app/javascript/mastodon/locales/nl.json index ff82799..7bfb74a 100644 --- a/app/javascript/mastodon/locales/nl.json +++ b/app/javascript/mastodon/locales/nl.json @@ -40,6 +40,7 @@ "bundle_modal_error.retry": "Opnieuw proberen", "column.blocks": "Geblokkeerde gebruikers", "column.community": "Lokale tijdlijn", + "column.domain_blocks": "Hidden domains", "column.favourites": "Favorieten", "column.follow_requests": "Volgverzoeken", "column.home": "Start", @@ -153,6 +154,7 @@ "mute_modal.hide_notifications": "Verberg meldingen van deze persoon?", "navigation_bar.blocks": "Geblokkeerde gebruikers", "navigation_bar.community_timeline": "Lokale tijdlijn", + "navigation_bar.domain_blocks": "Hidden domains", "navigation_bar.edit_profile": "Profiel bewerken", "navigation_bar.favourites": "Favorieten", "navigation_bar.follow_requests": "Volgverzoeken", diff --git a/app/javascript/mastodon/locales/no.json b/app/javascript/mastodon/locales/no.json index d3bc757..b79277c 100644 --- a/app/javascript/mastodon/locales/no.json +++ b/app/javascript/mastodon/locales/no.json @@ -40,6 +40,7 @@ "bundle_modal_error.retry": "Prøv igjen", "column.blocks": "Blokkerte brukere", "column.community": "Lokal tidslinje", + "column.domain_blocks": "Hidden domains", "column.favourites": "Likt", "column.follow_requests": "Følgeforespørsler", "column.home": "Hjem", @@ -153,6 +154,7 @@ "mute_modal.hide_notifications": "Skjul varslinger fra denne brukeren?", "navigation_bar.blocks": "Blokkerte brukere", "navigation_bar.community_timeline": "Lokal tidslinje", + "navigation_bar.domain_blocks": "Hidden domains", "navigation_bar.edit_profile": "Rediger profil", "navigation_bar.favourites": "Favoritter", "navigation_bar.follow_requests": "Følgeforespørsler", diff --git a/app/javascript/mastodon/locales/oc.json b/app/javascript/mastodon/locales/oc.json index 39ba31d..5b12f88 100644 --- a/app/javascript/mastodon/locales/oc.json +++ b/app/javascript/mastodon/locales/oc.json @@ -40,6 +40,7 @@ "bundle_modal_error.retry": "Tornar ensajar", "column.blocks": "Personas blocadas", "column.community": "Flux public local", + "column.domain_blocks": "Hidden domains", "column.favourites": "Favorits", "column.follow_requests": "Demandas d’abonament", "column.home": "Acuèlh", @@ -153,6 +154,7 @@ "mute_modal.hide_notifications": "Rescondre las notificacions d’aquesta persona ?", "navigation_bar.blocks": "Personas blocadas", "navigation_bar.community_timeline": "Flux public local", + "navigation_bar.domain_blocks": "Hidden domains", "navigation_bar.edit_profile": "Modificar lo perfil", "navigation_bar.favourites": "Favorits", "navigation_bar.follow_requests": "Demandas d’abonament", diff --git a/app/javascript/mastodon/locales/pl.json b/app/javascript/mastodon/locales/pl.json index d9490c5..30ef51b 100644 --- a/app/javascript/mastodon/locales/pl.json +++ b/app/javascript/mastodon/locales/pl.json @@ -40,6 +40,7 @@ "bundle_modal_error.retry": "Spróbuj ponownie", "column.blocks": "Zablokowani użytkownicy", "column.community": "Lokalna oś czasu", + "column.domain_blocks": "Hidden domains", "column.favourites": "Ulubione", "column.follow_requests": "Prośby o śledzenie", "column.home": "Strona główna", @@ -153,6 +154,7 @@ "mute_modal.hide_notifications": "Chcesz ukryć powiadomienia od tego użytkownika?", "navigation_bar.blocks": "Zablokowani użytkownicy", "navigation_bar.community_timeline": "Lokalna oś czasu", + "navigation_bar.domain_blocks": "Hidden domains", "navigation_bar.edit_profile": "Edytuj profil", "navigation_bar.favourites": "Ulubione", "navigation_bar.follow_requests": "Prośby o śledzenie", diff --git a/app/javascript/mastodon/locales/pt-BR.json b/app/javascript/mastodon/locales/pt-BR.json index 3d42eed..b056ec8 100644 --- a/app/javascript/mastodon/locales/pt-BR.json +++ b/app/javascript/mastodon/locales/pt-BR.json @@ -40,6 +40,7 @@ "bundle_modal_error.retry": "Tente novamente", "column.blocks": "Usuários bloqueados", "column.community": "Local", + "column.domain_blocks": "Hidden domains", "column.favourites": "Favoritos", "column.follow_requests": "Seguidores pendentes", "column.home": "Página inicial", @@ -153,6 +154,7 @@ "mute_modal.hide_notifications": "Esconder notificações deste usuário?", "navigation_bar.blocks": "Usuários bloqueados", "navigation_bar.community_timeline": "Local", + "navigation_bar.domain_blocks": "Hidden domains", "navigation_bar.edit_profile": "Editar perfil", "navigation_bar.favourites": "Favoritos", "navigation_bar.follow_requests": "Seguidores pendentes", diff --git a/app/javascript/mastodon/locales/pt.json b/app/javascript/mastodon/locales/pt.json index 5c93614..6598300 100644 --- a/app/javascript/mastodon/locales/pt.json +++ b/app/javascript/mastodon/locales/pt.json @@ -40,6 +40,7 @@ "bundle_modal_error.retry": "Tente de novo", "column.blocks": "Utilizadores Bloqueados", "column.community": "Local", + "column.domain_blocks": "Hidden domains", "column.favourites": "Favoritos", "column.follow_requests": "Seguidores Pendentes", "column.home": "Início", @@ -153,6 +154,7 @@ "mute_modal.hide_notifications": "Esconder notificações deste utilizador?", "navigation_bar.blocks": "Utilizadores bloqueados", "navigation_bar.community_timeline": "Local", + "navigation_bar.domain_blocks": "Hidden domains", "navigation_bar.edit_profile": "Editar perfil", "navigation_bar.favourites": "Favoritos", "navigation_bar.follow_requests": "Seguidores pendentes", diff --git a/app/javascript/mastodon/locales/ru.json b/app/javascript/mastodon/locales/ru.json index 7dffbb2..1595909 100644 --- a/app/javascript/mastodon/locales/ru.json +++ b/app/javascript/mastodon/locales/ru.json @@ -40,6 +40,7 @@ "bundle_modal_error.retry": "Попробовать снова", "column.blocks": "Список блокировки", "column.community": "Локальная лента", + "column.domain_blocks": "Hidden domains", "column.favourites": "Понравившееся", "column.follow_requests": "Запросы на подписку", "column.home": "Главная", @@ -153,6 +154,7 @@ "mute_modal.hide_notifications": "Убрать уведомления от этого пользователя?", "navigation_bar.blocks": "Список блокировки", "navigation_bar.community_timeline": "Локальная лента", + "navigation_bar.domain_blocks": "Hidden domains", "navigation_bar.edit_profile": "Изменить профиль", "navigation_bar.favourites": "Понравившееся", "navigation_bar.follow_requests": "Запросы на подписку", diff --git a/app/javascript/mastodon/locales/sk.json b/app/javascript/mastodon/locales/sk.json index 0a248d2..7bfae03 100644 --- a/app/javascript/mastodon/locales/sk.json +++ b/app/javascript/mastodon/locales/sk.json @@ -40,6 +40,7 @@ "bundle_modal_error.retry": "Skúsiť znova", "column.blocks": "Blokovaní užívatelia", "column.community": "Lokálna časová os", + "column.domain_blocks": "Hidden domains", "column.favourites": "Obľúbené", "column.follow_requests": "Žiadosti o sledovaní", "column.home": "Domov", @@ -153,6 +154,7 @@ "mute_modal.hide_notifications": "Skryť notifikácie od tohoto užívateľa?", "navigation_bar.blocks": "Blokovaní užívatelia", "navigation_bar.community_timeline": "Lokálna časová os", + "navigation_bar.domain_blocks": "Hidden domains", "navigation_bar.edit_profile": "Upraviť profil", "navigation_bar.favourites": "Obľúbené", "navigation_bar.follow_requests": "Žiadosti o sledovanie", diff --git a/app/javascript/mastodon/locales/sr-Latn.json b/app/javascript/mastodon/locales/sr-Latn.json index b9effce..8fae49a 100644 --- a/app/javascript/mastodon/locales/sr-Latn.json +++ b/app/javascript/mastodon/locales/sr-Latn.json @@ -40,6 +40,7 @@ "bundle_modal_error.retry": "Pokušajte ponovo", "column.blocks": "Blokirani korisnici", "column.community": "Lokalna lajna", + "column.domain_blocks": "Hidden domains", "column.favourites": "Omiljeni", "column.follow_requests": "Zahtevi za praćenje", "column.home": "Početna", @@ -153,6 +154,7 @@ "mute_modal.hide_notifications": "Sakrij obaveštenja od ovog korisnika?", "navigation_bar.blocks": "Blokirani korisnici", "navigation_bar.community_timeline": "Lokalna lajna", + "navigation_bar.domain_blocks": "Hidden domains", "navigation_bar.edit_profile": "Izmeni profil", "navigation_bar.favourites": "Omiljeni", "navigation_bar.follow_requests": "Zahtevi za praćenje", diff --git a/app/javascript/mastodon/locales/sr.json b/app/javascript/mastodon/locales/sr.json index a6c5f22..a39fea5 100644 --- a/app/javascript/mastodon/locales/sr.json +++ b/app/javascript/mastodon/locales/sr.json @@ -40,6 +40,7 @@ "bundle_modal_error.retry": "Покушајте поново", "column.blocks": "Блокирани корисници", "column.community": "Локална лајна", + "column.domain_blocks": "Hidden domains", "column.favourites": "Омиљени", "column.follow_requests": "Захтеви за праћење", "column.home": "Почетна", @@ -153,6 +154,7 @@ "mute_modal.hide_notifications": "Сакриј обавештења од овог корисника?", "navigation_bar.blocks": "Блокирани корисници", "navigation_bar.community_timeline": "Локална лајна", + "navigation_bar.domain_blocks": "Hidden domains", "navigation_bar.edit_profile": "Измени профил", "navigation_bar.favourites": "Омиљени", "navigation_bar.follow_requests": "Захтеви за праћење", diff --git a/app/javascript/mastodon/locales/sv.json b/app/javascript/mastodon/locales/sv.json index 6dc3d7a..014492e 100644 --- a/app/javascript/mastodon/locales/sv.json +++ b/app/javascript/mastodon/locales/sv.json @@ -40,6 +40,7 @@ "bundle_modal_error.retry": "Försök igen", "column.blocks": "Blockerade användare", "column.community": "Lokal tidslinje", + "column.domain_blocks": "Hidden domains", "column.favourites": "Favoriter", "column.follow_requests": "Följ förfrågningar", "column.home": "Hem", @@ -153,6 +154,7 @@ "mute_modal.hide_notifications": "Dölj notifikationer från denna användare?", "navigation_bar.blocks": "Blockerade användare", "navigation_bar.community_timeline": "Lokal tidslinje", + "navigation_bar.domain_blocks": "Hidden domains", "navigation_bar.edit_profile": "Redigera profil", "navigation_bar.favourites": "Favoriter", "navigation_bar.follow_requests": "Följförfrågningar", diff --git a/app/javascript/mastodon/locales/th.json b/app/javascript/mastodon/locales/th.json index 4de3540..ecfe7c9 100644 --- a/app/javascript/mastodon/locales/th.json +++ b/app/javascript/mastodon/locales/th.json @@ -40,6 +40,7 @@ "bundle_modal_error.retry": "Try again", "column.blocks": "Blocked users", "column.community": "Local timeline", + "column.domain_blocks": "Hidden domains", "column.favourites": "Favourites", "column.follow_requests": "Follow requests", "column.home": "Home", @@ -153,6 +154,7 @@ "mute_modal.hide_notifications": "Hide notifications from this user?", "navigation_bar.blocks": "Blocked users", "navigation_bar.community_timeline": "Local timeline", + "navigation_bar.domain_blocks": "Hidden domains", "navigation_bar.edit_profile": "Edit profile", "navigation_bar.favourites": "Favourites", "navigation_bar.follow_requests": "Follow requests", diff --git a/app/javascript/mastodon/locales/tr.json b/app/javascript/mastodon/locales/tr.json index 9d0affe..58d0e57 100644 --- a/app/javascript/mastodon/locales/tr.json +++ b/app/javascript/mastodon/locales/tr.json @@ -40,6 +40,7 @@ "bundle_modal_error.retry": "Try again", "column.blocks": "Engellenen kullanıcılar", "column.community": "Yerel zaman tüneli", + "column.domain_blocks": "Hidden domains", "column.favourites": "Favoriler", "column.follow_requests": "Takip istekleri", "column.home": "Anasayfa", @@ -153,6 +154,7 @@ "mute_modal.hide_notifications": "Hide notifications from this user?", "navigation_bar.blocks": "Engellenen kullanıcılar", "navigation_bar.community_timeline": "Yerel zaman tüneli", + "navigation_bar.domain_blocks": "Hidden domains", "navigation_bar.edit_profile": "Profili düzenle", "navigation_bar.favourites": "Favoriler", "navigation_bar.follow_requests": "Takip istekleri", diff --git a/app/javascript/mastodon/locales/uk.json b/app/javascript/mastodon/locales/uk.json index c49d3c7..63866d3 100644 --- a/app/javascript/mastodon/locales/uk.json +++ b/app/javascript/mastodon/locales/uk.json @@ -40,6 +40,7 @@ "bundle_modal_error.retry": "Try again", "column.blocks": "Заблоковані користувачі", "column.community": "Локальна стрічка", + "column.domain_blocks": "Hidden domains", "column.favourites": "Вподобане", "column.follow_requests": "Запити на підписку", "column.home": "Головна", @@ -153,6 +154,7 @@ "mute_modal.hide_notifications": "Hide notifications from this user?", "navigation_bar.blocks": "Заблоковані користувачі", "navigation_bar.community_timeline": "Локальна стрічка", + "navigation_bar.domain_blocks": "Hidden domains", "navigation_bar.edit_profile": "Редагувати профіль", "navigation_bar.favourites": "Вподобане", "navigation_bar.follow_requests": "Запити на підписку", diff --git a/app/javascript/mastodon/locales/zh-CN.json b/app/javascript/mastodon/locales/zh-CN.json index e95cf81..f7cb496 100644 --- a/app/javascript/mastodon/locales/zh-CN.json +++ b/app/javascript/mastodon/locales/zh-CN.json @@ -40,6 +40,7 @@ "bundle_modal_error.retry": "重试", "column.blocks": "屏蔽用户", "column.community": "本站时间轴", + "column.domain_blocks": "Hidden domains", "column.favourites": "收藏过的嘟文", "column.follow_requests": "关注请求", "column.home": "主页", @@ -153,6 +154,7 @@ "mute_modal.hide_notifications": "同时隐藏来自这个用户的通知", "navigation_bar.blocks": "被屏蔽的用户", "navigation_bar.community_timeline": "本站时间轴", + "navigation_bar.domain_blocks": "Hidden domains", "navigation_bar.edit_profile": "修改个人资料", "navigation_bar.favourites": "收藏的内容", "navigation_bar.follow_requests": "关注请求", diff --git a/app/javascript/mastodon/locales/zh-HK.json b/app/javascript/mastodon/locales/zh-HK.json index 1801c83..0504a8c 100644 --- a/app/javascript/mastodon/locales/zh-HK.json +++ b/app/javascript/mastodon/locales/zh-HK.json @@ -40,6 +40,7 @@ "bundle_modal_error.retry": "重試", "column.blocks": "封鎖用戶", "column.community": "本站時間軸", + "column.domain_blocks": "Hidden domains", "column.favourites": "最愛的文章", "column.follow_requests": "關注請求", "column.home": "主頁", @@ -153,6 +154,7 @@ "mute_modal.hide_notifications": "Hide notifications from this user?", "navigation_bar.blocks": "被你封鎖的用戶", "navigation_bar.community_timeline": "本站時間軸", + "navigation_bar.domain_blocks": "Hidden domains", "navigation_bar.edit_profile": "修改個人資料", "navigation_bar.favourites": "最愛的內容", "navigation_bar.follow_requests": "關注請求", diff --git a/app/javascript/mastodon/locales/zh-TW.json b/app/javascript/mastodon/locales/zh-TW.json index acbe6eb..fab7ecf 100644 --- a/app/javascript/mastodon/locales/zh-TW.json +++ b/app/javascript/mastodon/locales/zh-TW.json @@ -40,6 +40,7 @@ "bundle_modal_error.retry": "重試", "column.blocks": "封鎖的使用者", "column.community": "本地時間軸", + "column.domain_blocks": "Hidden domains", "column.favourites": "最愛", "column.follow_requests": "關注請求", "column.home": "家", @@ -153,6 +154,7 @@ "mute_modal.hide_notifications": "Hide notifications from this user?", "navigation_bar.blocks": "封鎖的使用者", "navigation_bar.community_timeline": "本地時間軸", + "navigation_bar.domain_blocks": "Hidden domains", "navigation_bar.edit_profile": "編輯用者資訊", "navigation_bar.favourites": "最愛", "navigation_bar.follow_requests": "關注請求", From 929f58f1809e69d34f72aea3cea5ca7a8b01bc61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Miko=C5=82ajczak?= Date: Sun, 1 Apr 2018 10:31:38 +0200 Subject: [PATCH 064/381] i18n: Update Polish translation (#6985) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Marcin Mikołajczak --- app/javascript/mastodon/locales/pl.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/javascript/mastodon/locales/pl.json b/app/javascript/mastodon/locales/pl.json index 30ef51b..ff7180d 100644 --- a/app/javascript/mastodon/locales/pl.json +++ b/app/javascript/mastodon/locales/pl.json @@ -18,7 +18,7 @@ "account.mute_notifications": "Wycisz powiadomienia o @{name}", "account.muted": "Wyciszony", "account.posts": "Wpisy", - "account.posts_with_replies": "Wpisy z odpowiedziami", + "account.posts_with_replies": "Wpisy i odpowiedzi", "account.report": "Zgłoś @{name}", "account.requested": "Oczekująca prośba, kliknij aby anulować", "account.share": "Udostępnij profil @{name}", @@ -40,7 +40,7 @@ "bundle_modal_error.retry": "Spróbuj ponownie", "column.blocks": "Zablokowani użytkownicy", "column.community": "Lokalna oś czasu", - "column.domain_blocks": "Hidden domains", + "column.domain_blocks": "Ukryte domeny", "column.favourites": "Ulubione", "column.follow_requests": "Prośby o śledzenie", "column.home": "Strona główna", @@ -58,7 +58,7 @@ "column_header.unpin": "Cofnij przypięcie", "column_subheading.navigation": "Nawigacja", "column_subheading.settings": "Ustawienia", - "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.", + "compose_form.direct_message_warning": "Ten wpis będzie widoczny tylko dla wszystkich wspomnianych użytkowników.", "compose_form.hashtag_warning": "Ten wpis nie będzie widoczny pod podanymi hashtagami, ponieważ jest oznaczony jako niewidoczny. Tylko publiczne wpisy mogą zostać znalezione z użyciem hashtagów.", "compose_form.lock_disclaimer": "Twoje konto nie jest {locked}. Każdy, kto Cię śledzi, może wyświetlać Twoje wpisy przeznaczone tylko dla śledzących.", "compose_form.lock_disclaimer.lock": "zablokowane", @@ -154,7 +154,7 @@ "mute_modal.hide_notifications": "Chcesz ukryć powiadomienia od tego użytkownika?", "navigation_bar.blocks": "Zablokowani użytkownicy", "navigation_bar.community_timeline": "Lokalna oś czasu", - "navigation_bar.domain_blocks": "Hidden domains", + "navigation_bar.domain_blocks": "Ukryte domeny", "navigation_bar.edit_profile": "Edytuj profil", "navigation_bar.favourites": "Ulubione", "navigation_bar.follow_requests": "Prośby o śledzenie", From 993ce0e5a7d86330d1ac7a9123735d8a218652b3 Mon Sep 17 00:00:00 2001 From: luzi82 Date: Sun, 1 Apr 2018 20:57:26 +0800 Subject: [PATCH 065/381] improve zh-HK localization by referring zh-TW and zh-CN (#6988) --- config/locales/activerecord.zh-HK.yml | 13 ++ config/locales/devise.zh-HK.yml | 21 ++ config/locales/doorkeeper.zh-HK.yml | 6 + config/locales/simple_form.zh-HK.yml | 18 +- config/locales/zh-HK.yml | 351 ++++++++++++++++++++++++++++++++++ 5 files changed, 408 insertions(+), 1 deletion(-) create mode 100644 config/locales/activerecord.zh-HK.yml diff --git a/config/locales/activerecord.zh-HK.yml b/config/locales/activerecord.zh-HK.yml new file mode 100644 index 0000000..2ebf958 --- /dev/null +++ b/config/locales/activerecord.zh-HK.yml @@ -0,0 +1,13 @@ +--- +zh-HK: + activerecord: + errors: + models: + account: + attributes: + username: + invalid: 只能使用字母、數字和下劃線 + status: + attributes: + reblog: + taken: 已經被轉推過 diff --git a/config/locales/devise.zh-HK.yml b/config/locales/devise.zh-HK.yml index a3e5980..12d6e3c 100644 --- a/config/locales/devise.zh-HK.yml +++ b/config/locales/devise.zh-HK.yml @@ -17,11 +17,32 @@ zh-HK: unconfirmed: 你必須先確認電郵地址,繼續使用。 mailer: confirmation_instructions: + action: 驗證電子郵件地址 + explanation: 你在 %{host} 上使用這個電子郵件地址建立了一個帳戶。只需點擊下面的連結,即可啟用帳戶。如果你並沒有建立過帳戶,請忽略此郵件。 + extra_html: 請記得閱讀本服務站的相關規定使用條款。 subject: 'Mastodon: 確認電郵地址' + title: 驗證電子郵件地址 + email_changed: + explanation: 你的帳戶的電子郵件地址即將變更為: + extra: 如果你沒有請求更改你的電子郵件地址,則他人很有可能已經入侵你的帳戶。請立即更改你的密碼;如果你已經無法訪問你的帳戶,請聯繫服務站的管理員請求協助。 + subject: Mastodon:電子郵件地址已被更改 + title: 新電子郵件地址 password_change: + explanation: 你的帳戶的密碼已被更改。 + extra: 如果你沒有請求更改你的密碼,則他人很有可能已經入侵你的帳戶。請立即更改你的密碼;如果你已經無法訪問你的帳戶,請聯繫服務站的管理員請求協助。 subject: 'Mastodon: 更改密碼' + title: 密碼已被重設 + reconfirmation_instructions: + explanation: 點擊下面的連結來確認你的新電子郵件地址。 + extra: 如果你沒有請求本次變更,請忽略此郵件。 Mastodon 帳戶的電子郵件地址只有在你點擊上面的連結後才會更改。 + subject: Mastodon:確認 %{instance} 電子郵件地址 + title: 驗證電子郵件地址 reset_password_instructions: + action: 更改密碼 + explanation: 點擊下面的連結來更改帳戶的密碼。 + extra: 如果你沒有請求本次變更,請忽略此郵件。你的密碼只有在你點擊上面的連結並輸入新密碼後才會更改。 subject: 'Mastodon: 重設密碼' + title: 重設密碼 unlock_instructions: subject: 'Mastodon: 解除用戶鎖定' omniauth_callbacks: diff --git a/config/locales/doorkeeper.zh-HK.yml b/config/locales/doorkeeper.zh-HK.yml index c8edc2b..7471087 100644 --- a/config/locales/doorkeeper.zh-HK.yml +++ b/config/locales/doorkeeper.zh-HK.yml @@ -5,6 +5,8 @@ zh-HK: doorkeeper/application: name: 名稱 redirect_uri: 轉接 URI + scopes: 權限範圍 + website: 應用網站 errors: models: doorkeeper/application: @@ -33,9 +35,13 @@ zh-HK: redirect_uri: 每行輸入一個 URI scopes: 請用半形空格分開權限範圍 (scope)。留空表示使用預設的權限範圍 index: + application: 應用 callback_url: 回傳網址 + delete: 刪除 name: 名稱 new: 新增應用程式 + scopes: 權限範圍 + show: 顯示 title: 你的應用程式 new: title: 新增應用程式 diff --git a/config/locales/simple_form.zh-HK.yml b/config/locales/simple_form.zh-HK.yml index 1e545da..01ba61f 100644 --- a/config/locales/simple_form.zh-HK.yml +++ b/config/locales/simple_form.zh-HK.yml @@ -4,12 +4,17 @@ zh-HK: hints: defaults: avatar: 支援 PNG, GIF 或 JPG 圖片,檔案最大為 2MB,會縮裁成 400x400px + digest: 僅在你長時間未登錄,且收到了私信時發送 display_name: 最多 30 個字元 header: 支援 PNG, GIF 或 JPG 圖片,檔案最大為 2MB,會縮裁成 700x335px locked: 你必須人手核准每個用戶對你的關注請求,而你的文章私隱會被預設為「只有關注你的人能看」 note: 最多 160 個字元 + setting_noindex: 此設定會影響到你的公開個人資料以及文章頁面 + setting_theme: 此設置會影響到你從任意設備登入時 Mastodon 的顯示樣式 imports: data: 自其他服務站匯出的 CSV 檔案 + sessions: + otp: 輸入你手機上生成的雙重認證碼,或者任意一個恢復代碼。 user: filtered_languages: 下面被選擇的語言的文章將不會出現在你的公共時間軸上。 labels: @@ -21,22 +26,33 @@ zh-HK: data: 資料 display_name: 顯示名稱 email: 電郵地址 - filtered_languages: 封鎖下面语言的文章 + expires_in: 失效時間 + filtered_languages: 封鎖下面語言的文章 header: 個人頁面頂部 locale: 語言 locked: 將用戶轉為「私人」 + max_uses: 最大使用次數 new_password: 新密碼 note: 簡介 otp_attempt: 雙重認證碼 password: 密碼 + setting_auto_play_gif: 自動播放 GIF setting_boost_modal: 在轉推前詢問我 setting_default_privacy: 文章預設為 + setting_default_sensitive: 預設我的內容為敏感內容 + setting_delete_modal: 刪推前詢問我 + setting_noindex: 阻止搜尋引擎檢索 + setting_reduce_motion: 減低動畫效果 + setting_system_font_ui: 使用系統預設字型 + setting_theme: 網站主題 + setting_unfollow_modal: 取消關注前跳出詢問我 severity: 等級 type: 匯入資料類型 username: 用戶名稱 interactions: must_be_follower: 隱藏沒有關注你的用戶的通知 must_be_following: 隱藏你不關注的用戶的通知 + must_be_following_dm: 隱藏你不關注的用戶的私信 notification_emails: digest: 定期電郵摘要 favourite: 當有用戶喜歡你的文章時,發電郵通知 diff --git a/config/locales/zh-HK.yml b/config/locales/zh-HK.yml index e7ab347..cc1cade 100644 --- a/config/locales/zh-HK.yml +++ b/config/locales/zh-HK.yml @@ -1,53 +1,98 @@ --- zh-HK: about: + about_hashtag_html: 這些是包含「#%{hashtag}」標籤的公開文章。只要你有任何 Mastodon 服務站、或者聯盟網站的用戶,便可以與他們互動。 about_mastodon_html: Mastodon(萬象)是自由、開源的社交網絡。服務站各自獨立而互連,避免單一商業機構壟斷。找你所信任的服務站,建立帳號,你即可與任何服務站上的用戶溝通,享受無縫的網絡交流。 about_this: 關於本服務站 closed_registrations: 本服務站暫時停止接受登記。 contact: 聯絡 + contact_missing: 未設定 + contact_unavailable: 未公開 description_headline: 關於 %{domain} domain_count_after: 個其他服務站 domain_count_before: 已連接至 + extended_description_html: | +

這裡可以寫一些網站規則

+

本站未有詳細介紹

+ features: + humane_approach_body: Mastodon 從其他網絡的失敗經驗中汲取教訓,以合乎道德的設計對抗社交媒體的濫用問題。 + humane_approach_title: 以人為本 + not_a_product_body: Mastodon 不是商業網絡。沒有廣告,沒有數據挖掘,也沒有中央機構操控。平台完全開放。 + not_a_product_title: 你是用戶,不是商品 + real_conversation_body: Mastodon 的字數限制高達 500 字,並支援仔細的媒體警告選項,令你暢所欲言。 + real_conversation_title: 為真正的交流而生 + within_reach_body: 簡易的 API 系統,令用戶可以透過不同的 iOS、Android 及其他平台的應用軟件,與朋友保持聯繫。 + within_reach_title: 無處不在 + generic_description: "%{domain} 是 Mastodon 網絡中其中一個服務站" + hosted_on: 在 %{domain} 運作的 Mastodon 服務站 + learn_more: 了解更多 other_instances: 其他服務站 source_code: 源代碼 status_count_after: 篇文章 status_count_before: 他們共發佈了 user_count_after: 位使用者 user_count_before: 這裏共註冊有 + what_is_mastodon: Mastodon 是甚麼? accounts: follow: 關注 followers: 關注者 following: 正在關注 + media: 媒體 + moved_html: "%{name} 已經轉移到 %{new_profile_link}:" nothing_here: 暫時未有內容可以顯示 people_followed_by: "%{name} 關注的人" people_who_follow: 關注 %{name} 的人 posts: 文章 + posts_with_replies: 文章和回覆 remote_follow: 跨站關注 + reserved_username: 此用戶名已被保留 + roles: + admin: 管理員 + moderator: 監察员 unfollow: 取消關注 admin: + account_moderation_notes: + account: 管理員 + create: 新增 + created_at: 日期 + created_msg: 管理記錄已新增 + delete: 刪除 + destroyed_msg: 管理記錄已被刪除 accounts: are_you_sure: 你確定嗎? + by_domain: 域名 confirm: 確定 confirmed: 已確定 + demote: 降任 + disable: 停用 disable_two_factor_authentication: 停用雙重認證 + disabled: 已停用 display_name: 顯示名稱 domain: 域名 edit: 編輯 email: 電郵地址 + enable: 啟用 + enabled: 已啟用 feed_url: Feed URL followers: 關注者 + followers_url: 關注者(Followers)URL follows: 正在關注 + inbox_url: 收件箱(Inbox)URL + ip: IP 位域 location: all: 全部 local: 本地 remote: 遠端 title: 地點 + login_status: 登入狀態 media_attachments: 媒體檔案 + memorialize: 設定為追悼帳戶 moderation: all: 全部 silenced: 被靜音的 suspended: 被停權的 title: 管理操作 + moderation_notes: 管理記錄 most_recent_activity: 最新活動 most_recent_ip: 最新 IP 位域 not_subscribed: 未訂閱 @@ -55,23 +100,90 @@ zh-HK: alphabetic: 按字母 most_recent: 按時間 title: 排序 + outbox_url: 寄件箱(Outbox)URL perform_full_suspension: 實行完全暫停 profile_url: 個人檔案 URL + promote: 升任 + protocol: 協議 public: 公共 push_subscription_expires: PuSH 訂閱過期 + redownload: 更新頭像 + reset: 重設 reset_password: 重設密碼 + resubscribe: 重新訂閱 + role: 身份 + roles: + admin: 管理員 + moderator: 監察员 + staff: 管理人員 + user: 普通用戶 salmon_url: Salmon 反饋 URL + search: 搜索 + shared_inbox_url: 公共收件箱(Shared Inbox)URL show: created_reports: 此用戶所提舉報的紀錄 report: 舉報 targeted_reports: 此用戶被舉報的紀錄 silence: 靜音 statuses: 文章 + subscribe: 訂閱 title: 用戶 undo_silenced: 解除靜音 undo_suspension: 解除停權 + unsubscribe: 取消訂閱 username: 用戶名稱 web: 用戶頁面 + action_logs: + actions: + confirm_user: "%{name} 確認了用戶 %{target} 的電郵地址" + create_custom_emoji: "%{name} 加入自訂表情符號 %{target}" + create_domain_block: "%{name} 阻隔了網域 %{target}" + create_email_domain_block: "%{name} 阻隔了電郵網域 %{target}" + demote_user: "%{name} 把用戶 %{target} 降任" + destroy_domain_block: "%{name} 取消了對網域 %{target} 的阻隔" + destroy_email_domain_block: "%{name} 取消了對電郵網域 %{target} 的阻隔" + destroy_status: "%{name} 刪除了 %{target} 的文章" + disable_2fa_user: "%{name} 停用了用戶 %{target} 的雙重認證" + disable_custom_emoji: "%{name} 停用了自訂表情符號 %{target}" + disable_user: "%{name} 把用戶 %{target} 設定為禁止登入" + enable_custom_emoji: "%{name} 啟用了自訂表情符號 %{target}" + enable_user: "%{name} 把用戶 %{target} 設定為允許登入" + memorialize_account: "%{name} 把 %{target} 設定為追悼帳戶" + promote_user: "%{name} 對用戶 %{target} 进行了升任操作" + reset_password_user: "%{name} 重設了用戶 %{target} 的密碼" + resolve_report: "%{name} 處理了 %{target} 的舉報" + silence_account: "%{name} 靜音了用戶 %{target}" + suspend_account: "%{name} 停權了用戶 %{target}" + unsilence_account: "%{name} 取消了用戶 %{target} 的靜音狀態" + unsuspend_account: "%{name} 取消了用戶 %{target} 的停權狀態" + update_custom_emoji: "%{name} 更新了自訂表情符號 %{target}" + update_status: "%{name} 刷新了 %{target} 的文章" + title: 營運日誌 + custom_emojis: + by_domain: 網域 + copied_msg: 成功將表情複製到本地 + copy: 複製 + copy_failed_msg: 無法將表情複製到本地 + created_msg: 已新增表情符號 + delete: 刪除 + destroyed_msg: 已刪除表情符號 + disable: 停用 + disabled_msg: 已停用表情符號 + emoji: emoji + enable: 啟用 + enabled_msg: 已啟用表情符號 + image_hint: PNG 格式,最大 50KB + listed: 已顯示 + new: + title: 加入新的自訂表情符號 + overwrite: 覆蓋 + shortcode: 短代碼 + shortcode_hint: 至少 2 個字元,只能使用字母、數字和下劃線 + title: 自訂 emoji + unlisted: 已隱藏 + update_failed_msg: 無法更新表情符號 + updated_msg: 已更新表情符號 + upload: 上傳新的表情符號 domain_blocks: add_new: 新增 created_msg: 正處理域名阻隔 @@ -82,12 +194,14 @@ zh-HK: hint: "「域名阻隔」不會隔絕該域名用戶的用戶進入本站資料庫,而是會在時候自動套用特定的審批操作。" severity: desc_html: "「自動靜音」令該域名用戶的文章,設為只對關注者顯示,沒有關注的人會看不到。 「自動刪除」會自動將該域名用戶的文章、媒體檔案、個人資料自本服務站刪除。" + noop: 無 silence: 自動靜音 suspend: 自動刪除 title: 新增域名阻隔 reject_media: 拒絕媒體檔案 reject_media_hint: 刪除本地緩存的媒體檔案,再也不在未來下載這個站點的檔案。和自動刪除無關。 severities: + noop: 無 silence: 自動靜音 suspend: 自動刪除 severity: 阻隔分級 @@ -101,17 +215,41 @@ zh-HK: undo: 撤銷 title: 域名阻隔 undo: 撤銷 + email_domain_blocks: + add_new: 加入新項目 + created_msg: 已新增電郵網域阻隔 + delete: 刪除 + destroyed_msg: 已刪除電郵網域阻隔 + domain: 網域 + new: + create: 新增網域 + title: 新增電郵網域阻隔 + title: 電郵網域阻隔 instances: account_count: 已知帳號 domain_name: 域名 + reset: 重設 + search: 搜索 title: 已知服務站 + invites: + filter: + all: 全部 + available: 可用 + expired: 已失效 + title: 篩選 + title: 邀請用戶 reports: + action_taken_by: 操作執行者 + are_you_sure: 你確認嗎? comment: label: 詳細解釋 none: 沒有 delete: 刪除 id: ID mark_as_resolved: 標示為「已處理」 + nsfw: + 'false': 取消 NSFW 標記 + 'true': 添加 NSFW 標記 report: '舉報 #%{id}' report_contents: 舉報內容 reported_account: 舉報用戶 @@ -125,23 +263,66 @@ zh-HK: unresolved: 未處理 view: 檢視 settings: + activity_api_enabled: + desc_html: 本站用戶發佈的文章,以及本站的活躍用戶和一週內新用戶數 + title: 公開用戶活躍度的統計數據 + bootstrap_timeline_accounts: + desc_html: 以半形逗號分隔多個用戶名。只能加入來自本站且未開啟保護的帳號。如果留空,則默認關注本站所有管理員。 + title: 新用戶默認關注 contact_information: email: 輸入一個公開的電郵地址 username: 輸入用戶名稱 + peers_api_enabled: + desc_html: 現時本服務站在網絡中已發現的域名 + title: 公開已知服務站的列表 registrations: closed_message: desc_html: 當本站暫停接受註冊時,會顯示這個訊息。
可使用 HTML title: 暫停註冊訊息 + deletion: + desc_html: 允許所有人刪除自己的帳戶 + title: 開放刪除帳戶的權限 + min_invite_role: + disabled: 沒有人 + title: 允許發送邀請的身份 open: + desc_html: 允許所有人建立帳戶 title: 開放註冊 + show_staff_badge: + desc_html: 在個人資料頁上顯示管理人員標誌 + title: 顯示管理人員標誌 site_description: desc_html: 在首頁顯示,及在 meta 標籤使用作網站介紹。
你可以在此使用 <a><em> 等 HTML 標籤。 title: 本站介紹 site_description_extended: desc_html: 本站詳細資訊頁的內文
你可以在此使用 HTML title: 本站詳細資訊 + site_terms: + desc_html: 可以填寫自己的隱私權政策、使用條款或其他法律文本。可以使用 HTML 標籤 + title: 自訂使用條款 site_title: 本站名稱 + thumbnail: + desc_html: 用於在 OpenGraph 和 API 中顯示預覽圖。推薦大小 1200×630px + title: 本站縮圖 + timeline_preview: + desc_html: 在主頁顯示本站時間軸 + title: 時間軸預覽 title: 網站設定 + statuses: + back_to_account: 返回帳戶信息頁 + batch: + delete: 刪除 + nsfw_off: 取消 NSFW 標記 + nsfw_on: 添加 NSFW 標記 + execute: 執行 + failed_to_execute: 執行失敗 + media: + hide: 隱藏媒體檔案 + show: 顯示媒體檔案 + title: 媒體檔案 + no_media: 不含媒體檔案 + title: 帳戶文章 + with_media: 含有媒體檔案 subscriptions: callback_url: 回傳 URL confirmed: 確定 @@ -150,16 +331,36 @@ zh-HK: title: PuSH 訂閱 topic: 所訂閱資源 title: 管理 + admin_mailer: + new_report: + body: "%{reporter} 舉報了用戶 %{target}。" + subject: 來自 %{instance} 的用戶舉報(#%{id}) application_mailer: + notification_preferences: 更改電郵首選項 + salutation: "%{name}:" settings: 修改電郵設定︰%{link} view: 進入瀏覽︰ + view_profile: 檢視個人資料頁 + view_status: 檢視文章 applications: + created: 已建立應用 + destroyed: 已刪除應用 invalid_url: 所提供的網址不正確 + regenerate_token: 重設 token + token_regenerated: 已重設 token + warning: 警告,不要把它分享給任何人! + your_token: token auth: + agreement_html: 登記即表示你同意遵守本服務站的規則使用條款。 + delete_account: 刪除帳戶 + delete_account_html: 如果你想刪除你的帳戶,請點擊這裡繼續。你需要確認你的操作。 didnt_get_confirmation: 沒有收到確認指示電郵? forgot_password: 忘記了密碼? + invalid_reset_password_token: 密碼重置 token 無效或已過期。請重新重設密碼。 login: 登入 logout: 登出 + migrate_account: 轉移到另一個帳號 + migrate_account_html: 想要將這個帳號指向另一個帳號可到這裡設定。 register: 登記 resend_confirmation: 重發確認指示電郵 reset_password: 重設密碼 @@ -168,6 +369,12 @@ zh-HK: authorize_follow: error: 對不起,尋找這個跨站用戶的過程發生錯誤 follow: 關注 + follow_request: 關注請求已發送给: + following: 成功!你正在關注: + post_follow: + close: 你也可以直接關閉這個頁面 + return: 返回至個人資料頁 + web: 返回本站 title: 關注 %{acct} datetime: distance_in_words: @@ -183,6 +390,14 @@ zh-HK: x_minutes: "%{count}分鐘" x_months: "%{count}個月" x_seconds: "%{count}秒" + deletes: + bad_password_msg: 想得美,黑客!密碼輸入錯誤 + confirm_password: 輸入你現在的密碼來驗證身份 + description_html: 繼續操作將會永久地、不可還原地刪除帳戶中的所有內容,然後凍結帳戶。你的用戶名將會被保留,以防有人冒用你的身份。 + proceed: 刪除帳戶 + success_msg: 你的帳戶已經成功刪除 + warning_html: 我們只能保證本服務站上的內容將會被徹底刪除。對於已經被廣泛傳播的內容,它們在本服務站以外的某些地方可能仍然可見。此外,失去連接的服務站以及停止接收訂閱的服務站所存儲的數據亦無法刪除。 + warning_title: 關於已傳播的內容的警告 errors: '403': 你沒有觀看本頁的權限 '404': 找不到內容 @@ -191,6 +406,10 @@ zh-HK: content: 無法確認登入資訊。會不會你阻擋了本站使用 Cookies 的權限? title: 無法確認登入資訊 '429': 伺服器繁忙 + '500': + content: 抱歉,我們的後台出錯了。 + title: 這個頁面有問題 + noscript_html: 使用 Mastodon 網頁版應用需要啟用 JavaScript。你也可以選擇適用於你的平台的 Mastodon 應用。 exports: blocks: 被你封鎖的用戶 csv: CSV @@ -224,14 +443,44 @@ zh-HK: following: 你所關注的用戶名單 muting: 靜音名單 upload: 上載 + in_memoriam_html: 謹此悼念。 + invites: + delete: 停用 + expired: 已失效 + expires_in: + '1800': 30 分鐘後 + '21600': 6 小時後 + '3600': 1 小時後 + '43200': 12 小時後 + '86400': 1 天後 + expires_in_prompt: 永不過期 + generate: 生成邀請連結 + max_uses: "%{count} 次" + max_uses_prompt: 無限制 + prompt: 生成分享連結,邀請他人在本服務站註冊 + table: + expires_at: 失效時間 + uses: 已使用次數 + title: 邀請用戶 landing_strip_html: "%{name} 是一個在 %{link_to_root_path} 的用戶。只要你有任何 Mastodon 服務站、或者聯盟網站的用戶,便可以跨站關注此站用戶,或者與他們互動。" landing_strip_signup_html: 如果你沒有這類用戶,歡迎在此處登記。 + lists: + errors: + limit: 你所建立的列表數量已經達到上限 media_attachments: validations: images_and_video: 不能在已有圖片的文章上加入影片 too_many: 不可以加入超過 4 個檔案 + migrations: + acct: 新帳戶的 用戶名@域名 + currently_redirecting: 目前你的個人資料頁顯示的新帳戶是: + proceed: 保存 + updated_msg: 帳戶遷移設置更新成功! + moderation: + title: 營運 notification_mailer: digest: + action: 查看所有通知 body: 這是自從你在%{since}使用%{instance}以後,你錯失了的訊息︰ mention: "%{name} 在此提及了你︰" new_followers_summary: @@ -240,21 +489,29 @@ zh-HK: subject: one: "自從上次登入以來,你收到 1 則新的通知 \U0001F418" other: "自從上次登入以來,你收到 %{count} 則新的通知 \U0001F418" + title: 在你不在的這段時間…… favourite: body: 你的文章是 %{name} 的最愛! subject: "%{name} 收藏了你的文章" + title: 新的收藏 follow: body: "%{name} 開始關注你!" subject: "%{name} 現正關注你" + title: 新的關注者 follow_request: + action: 處理關注請求 body: "%{name} 要求關注你" subject: 等候關注你的用戶︰ %{name} + title: 新的關注請求 mention: + action: 回覆 body: "%{name} 在文章中提及你︰" subject: "%{name} 在文章中提及你" + title: 新的提及 reblog: body: 你的文章得到 %{name} 的轉推 subject: "%{name} 轉推了你的文章" + title: 新的轉推 number: human: decimal_units: @@ -270,25 +527,94 @@ zh-HK: next: 下一頁 prev: 上一頁 truncate: "……" + preferences: + languages: 語言 + other: 其他 + publishing: 發佈 + web: 站内 + push_notifications: + favourite: + title: "%{name} 收藏了你的文章" + follow: + title: "%{name} 關注了你" + group: + title: "%{count} 條新通知" + mention: + action_boost: 轉推 + action_expand: 顯示更多 + action_favourite: 收藏 + title: "%{name} 提到了你" + reblog: + title: "%{name} 轉推了你的文章" remote_follow: acct: 請輸入你的︰用戶名稱@服務點域名 missing_resource: 無法找到你用戶的轉接網址 proceed: 下一步 prompt: 你希望關注︰ + sessions: + activity: 最近活動 + browser: 瀏覽器 + browsers: + alipay: 支付寶 + blackberry: Blackberry + chrome: Chrome + edge: Microsoft Edge + firefox: Firefox + generic: 未知的瀏覽器 + ie: Internet Explorer + micro_messenger: 微信 + nokia: Nokia S40 Ovi 瀏覽器 + opera: Opera + phantom_js: PhantomJS + qq: QQ瀏覽器 + safari: Safari + uc_browser: UC瀏覽器 + weibo: 新浪微博 + current_session: 目前的 session + description: "%{platform} 上的 %{browser}" + explanation: 這些是現在正登入於你的 Mastodon 帳號的瀏覽器。 + ip: IP 位址 + platforms: + adobe_air: Adobe Air + android: Android + blackberry: Blackberry + chrome_os: ChromeOS + firefox_os: Firefox OS + ios: iOS + linux: Linux + mac: Mac + other: 未知平台 + windows: Windows + windows_mobile: Windows Mobile + windows_phone: Windows Phone + revoke: 取消 + revoke_success: Session 取消成功。 + title: Session settings: authorized_apps: 授權應用程式 back: 回到 Mastodon + delete: 刪除帳戶 + development: 開發 edit_profile: 修改個人資料 export: 匯出 followers: 授權關注 import: 匯入 + migrate: 帳戶遷移 + notifications: 通知 preferences: 偏好設定 settings: 設定 two_factor_authentication: 雙重認證 + your_apps: 你的應用程式 statuses: open_in_web: 開啟網頁 over_character_limit: 超過了 %{max} 字的限制 + pin_errors: + limit: 你所置頂的文章數量已經達到上限 + ownership: 不能置頂他人的文章 + private: 不能置頂非公開的文章 + reblog: 不能置頂轉推 show_more: 顯示更多 + title: "%{name}:「%{quote}」" visibilities: private: 關注者觀看 private_long: 只有關注你的人能看到 @@ -298,8 +624,11 @@ zh-HK: unlisted_long: 所有人都能看到,但不在公共時間軸(本站時間軸、跨站時間軸)顯示 stream_entries: click_to_show: 點擊顯示 + pinned: 置頂文章 reblogged: 轉推 sensitive_content: 敏感內容 + terms: + title: "%{instance} 使用條款和隱私權政策" time: formats: default: "%Y年%-m月%d日 %H:%M" @@ -308,15 +637,37 @@ zh-HK: description_html: 當你啟用雙重認證後,你登入時將需要使你手機、或其他種類認證器產生的代碼。 disable: 停用 enable: 啟用 + enabled: 雙重認證已啟用 enabled_success: 已成功啟用雙重認證 generate_recovery_codes: 產生備用驗證碼 instructions_html: "請用你手機的認證器應用程式(如 Google Authenticator、Authy),掃描這裏的QR 圖形碼。在雙重認證啟用後,你登入時將須要使用此應用程式產生的認證碼。" lost_recovery_codes: 讓你可以在遺失電話時,使用備用驗證碼登入。如果你遺失了備用驗證碼,可以在這裏產生一批新的,舊有的備用驗證碼將會失效。 manual_instructions: 如果你無法掃描 QR 圖形碼,請手動輸入這個文字密碼︰ + recovery_codes: 備份恢復驗證碼 recovery_codes_regenerated: 成功產生新的備用驗證碼 recovery_instructions_html: 如果你遺失了安裝認證器的裝置(如︰你的電話),你可以使用備用驗證碼進行登入。請確保將備用驗證碼收藏穩當,(如列印出來,和你其他重要文件一起存放) setup: 設定 wrong_code: 你輸入的認證碼並不正確!可能伺服器時間和你手機不一致,請檢查你手機的時鐘,或與本站管理員聯絡。 + user_mailer: + welcome: + edit_profile_action: 設定個人資料 + edit_profile_step: 你可以設定你的個人資料,包括上傳頭像、橫幅圖片、更改顯示名稱等等。如果你想在新的關注者關注你之前對他們進行審核,你也可以選擇為你的帳戶設為「私人」。 + explanation: 下面是幾個小貼士,希望它們能幫到你 + final_action: 開始發文 + final_step: '開始發文吧!即使你現在沒有關注者,其他人仍然能在本站時間軸或者話題標籤等地方看到你的公開文章。試著用 #introductions 這個話題標籤介紹一下自己吧。' + full_handle: 你的完整用戶地址 + full_handle_hint: 你需要把這個告訴你的朋友們,這樣他們就能從另一個實例向你發送信息或者關注你。 + review_preferences_action: 更改首選項 + review_preferences_step: 記得調整你的偏好設置,比如你想接收什麼類型的郵件,或者你想把你的文章可見範圍默認設定為什麼級別。如果你沒有暈車的話,考慮一下啟用「自動播放 GIF 動畫」這個選項吧。 + subject: 歡迎來到 Mastodon + tip_bridge_html: 如果你剛從 Twitter 來到這裡,你可以在橋樑站(bridge app)上尋找你的朋友。當然,前提是他們也登錄了橋樑站! + tip_federated_timeline: 跨站公共時間軸可以讓你一窺更廣闊的 Mastodon 網絡。不過,由於它只顯示你的鄰居們所訂閱的內容,所以並不是全部。 + tip_following: 默認情況下,你會自動關注你所在服務站的管理員。想結交更多有趣的人的話,記得多逛逛本站時間軸和跨站公共時間軸哦。 + tip_local_timeline: 本站時間軸可以讓你一窺 %{instance} 上的用戶。他們就是離你最近的鄰居! + tip_mobile_webapp: 如果你的移動設備瀏覽器允許你將 Mastodon 添加到主屏幕,你就能夠接收推送消息。它就像本地應用一樣好使! + tips: 小貼士 + title: "%{name},歡迎你的加入!" users: invalid_email: 電郵地址格式不正確 invalid_otp_token: 雙重認證確認碼不正確 + signed_in_as: 目前登入的帳戶: From 6a895e1ab3d69cd018423460518a1e16307999ad Mon Sep 17 00:00:00 2001 From: Emelia Smith Date: Sun, 1 Apr 2018 22:19:43 +0200 Subject: [PATCH 066/381] Fix: Prevent submission using same logic as submit button disabling. (#6993) This prevents submission through ctrl/cmd+enter when the submit button is disabled. --- .../mastodon/features/compose/components/compose_form.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/javascript/mastodon/features/compose/components/compose_form.js b/app/javascript/mastodon/features/compose/components/compose_form.js index 663ccfb..fe7bb1c 100644 --- a/app/javascript/mastodon/features/compose/components/compose_form.js +++ b/app/javascript/mastodon/features/compose/components/compose_form.js @@ -74,6 +74,14 @@ export default class ComposeForm extends ImmutablePureComponent { this.props.onChange(this.autosuggestTextarea.textarea.value); } + // Submit disabled: + const { is_submitting, is_uploading, anyMedia } = this.props; + const fulltext = [this.props.spoiler_text, countableText(this.props.text)].join(''); + + if (is_submitting || is_uploading || length(fulltext) > 500 || (fulltext.length !== 0 && fulltext.trim().length === 0 && !anyMedia)) { + return; + } + this.props.onSubmit(); } From f464f98fd3b8ef33b3afa5acf09e829c046134de Mon Sep 17 00:00:00 2001 From: Yamagishi Kazutoshi Date: Mon, 2 Apr 2018 06:43:08 +0900 Subject: [PATCH 067/381] Update Ruby to version 2.4.4 (#6964) https://www.ruby-lang.org/en/news/2018/03/28/ruby-2-4-4-released/ > This release includes some bug fixes and some security fixes. > > - CVE-2017-17742: HTTP response splitting in WEBrick > - CVE-2018-6914: Unintentional file and directory creation with directory traversal in tempfile and tmpdir > - CVE-2018-8777: DoS by large request in WEBrick > - CVE-2018-8778: Buffer under-read in String#unpack > - CVE-2018-8779: Unintentional socket creation by poisoned NUL byte in UNIXServer and UNIXSocket > - CVE-2018-8780: Unintentional directory traversal by poisoned NUL byte in Dir > - Multiple vulnerabilities in RubyGems --- .ruby-version | 2 +- .travis.yml | 2 +- Dockerfile | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.ruby-version b/.ruby-version index 437459c..73462a5 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -2.5.0 +2.5.1 diff --git a/.travis.yml b/.travis.yml index 576659a..989237a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -39,7 +39,7 @@ addons: - yarn rvm: - - 2.4.2 + - 2.4.3 - 2.5.0 services: diff --git a/Dockerfile b/Dockerfile index 0801f57..5f17c5b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM ruby:2.4.3-alpine3.6 +FROM ruby:2.4.4-alpine3.6 LABEL maintainer="https://github.com/tootsuite/mastodon" \ description="Your self-hosted, globally interconnected microblogging community" From 123a343d116d3e83cbd04460cc7bd0b6f3d208c4 Mon Sep 17 00:00:00 2001 From: David Underwood Date: Sun, 1 Apr 2018 17:55:42 -0400 Subject: [PATCH 068/381] [WIP] Enable custom emoji on account pages and in the sidebar (#6124) Federate custom emojis with accounts --- app/lib/formatter.rb | 11 ++- app/lib/ostatus/atom_serializer.rb | 3 + app/models/account.rb | 4 ++ app/models/remote_profile.rb | 4 ++ app/serializers/activitypub/actor_serializer.rb | 9 +++ app/serializers/rest/account_serializer.rb | 2 +- .../activitypub/process_account_service.rb | 28 ++++++++ app/services/update_remote_profile_service.rb | 21 ++++++ app/views/accounts/_header.html.haml | 2 +- spec/lib/formatter_spec.rb | 79 ++++++++++++++++++++++ 10 files changed, 158 insertions(+), 5 deletions(-) diff --git a/app/lib/formatter.rb b/app/lib/formatter.rb index 1df4ff8..f7e7a3c 100644 --- a/app/lib/formatter.rb +++ b/app/lib/formatter.rb @@ -51,9 +51,14 @@ class Formatter strip_tags(text) end - def simplified_format(account) - return reformat(account.note).html_safe unless account.local? # rubocop:disable Rails/OutputSafety - linkify(account.note) + def simplified_format(account, **options) + html = if account.local? + linkify(account.note) + else + reformat(account.note) + end + html = encode_custom_emojis(html, CustomEmoji.from_text(account.note, account.domain)) if options[:custom_emojify] + html.html_safe # rubocop:disable Rails/OutputSafety end def sanitize(html, config) diff --git a/app/lib/ostatus/atom_serializer.rb b/app/lib/ostatus/atom_serializer.rb index 46d0a8b..055b464 100644 --- a/app/lib/ostatus/atom_serializer.rb +++ b/app/lib/ostatus/atom_serializer.rb @@ -26,6 +26,9 @@ class OStatus::AtomSerializer append_element(author, 'link', nil, rel: :alternate, type: 'text/html', href: ::TagManager.instance.url_for(account)) append_element(author, 'link', nil, rel: :avatar, type: account.avatar_content_type, 'media:width': 120, 'media:height': 120, href: full_asset_url(account.avatar.url(:original))) if account.avatar? append_element(author, 'link', nil, rel: :header, type: account.header_content_type, 'media:width': 700, 'media:height': 335, href: full_asset_url(account.header.url(:original))) if account.header? + account.emojis.each do |emoji| + append_element(author, 'link', nil, rel: :emoji, href: full_asset_url(emoji.image.url), name: emoji.shortcode) + end append_element(author, 'poco:preferredUsername', account.username) append_element(author, 'poco:displayName', account.display_name) if account.display_name? append_element(author, 'poco:note', account.local? ? account.note : strip_tags(account.note)) if account.note? diff --git a/app/models/account.rb b/app/models/account.rb index 25e7d74..a34b6a2 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -350,6 +350,10 @@ class Account < ApplicationRecord end end + def emojis + CustomEmoji.from_text(note, domain) + end + before_create :generate_keys before_validation :normalize_domain before_validation :prepare_contents, if: :local? diff --git a/app/models/remote_profile.rb b/app/models/remote_profile.rb index 613911c..742d2b5 100644 --- a/app/models/remote_profile.rb +++ b/app/models/remote_profile.rb @@ -41,6 +41,10 @@ class RemoteProfile @header ||= link_href_from_xml(author, 'header') end + def emojis + @emojis ||= author.xpath('./xmlns:link[@rel="emoji"]', xmlns: OStatus::TagManager::XMLNS) + end + def locked? scope == 'private' end diff --git a/app/serializers/activitypub/actor_serializer.rb b/app/serializers/activitypub/actor_serializer.rb index afcd377..df30907 100644 --- a/app/serializers/activitypub/actor_serializer.rb +++ b/app/serializers/activitypub/actor_serializer.rb @@ -10,6 +10,8 @@ class ActivityPub::ActorSerializer < ActiveModel::Serializer has_one :public_key, serializer: ActivityPub::PublicKeySerializer + has_many :virtual_tags, key: :tag + attribute :moved_to, if: :moved? class EndpointsSerializer < ActiveModel::Serializer @@ -101,7 +103,14 @@ class ActivityPub::ActorSerializer < ActiveModel::Serializer object.locked end + def virtual_tags + object.emojis + end + def moved_to ActivityPub::TagManager.instance.uri_for(object.moved_to_account) end + + class CustomEmojiSerializer < ActivityPub::EmojiSerializer + end end diff --git a/app/serializers/rest/account_serializer.rb b/app/serializers/rest/account_serializer.rb index 19b7465..6097acd 100644 --- a/app/serializers/rest/account_serializer.rb +++ b/app/serializers/rest/account_serializer.rb @@ -14,7 +14,7 @@ class REST::AccountSerializer < ActiveModel::Serializer end def note - Formatter.instance.simplified_format(object) + Formatter.instance.simplified_format(object, custom_emojify: true) end def url diff --git a/app/services/activitypub/process_account_service.rb b/app/services/activitypub/process_account_service.rb index 7d8dc13..cf84628 100644 --- a/app/services/activitypub/process_account_service.rb +++ b/app/services/activitypub/process_account_service.rb @@ -22,6 +22,7 @@ class ActivityPub::ProcessAccountService < BaseService create_account if @account.nil? update_account + process_tags(@account) end end @@ -187,4 +188,31 @@ class ActivityPub::ProcessAccountService < BaseService def lock_options { redis: Redis.current, key: "process_account:#{@uri}" } end + + def process_tags(account) + return if @json['tag'].blank? + as_array(@json['tag']).each do |tag| + case tag['type'] + when 'Emoji' + process_emoji tag, account + end + end + end + + def process_emoji(tag, _account) + return if skip_download? + return if tag['name'].blank? || tag['icon'].blank? || tag['icon']['url'].blank? + + shortcode = tag['name'].delete(':') + image_url = tag['icon']['url'] + uri = tag['id'] + updated = tag['updated'] + emoji = CustomEmoji.find_by(shortcode: shortcode, domain: @account.domain) + + return unless emoji.nil? || emoji.updated_at >= updated + + emoji ||= CustomEmoji.new(domain: @account.domain, shortcode: shortcode, uri: uri) + emoji.image_remote_url = image_url + emoji.save + end end diff --git a/app/services/update_remote_profile_service.rb b/app/services/update_remote_profile_service.rb index 49a9076..aca1185 100644 --- a/app/services/update_remote_profile_service.rb +++ b/app/services/update_remote_profile_service.rb @@ -40,6 +40,27 @@ class UpdateRemoteProfileService < BaseService account.header_remote_url = '' account.header.destroy end + + save_emojis(account) if remote_profile.emojis.present? + end + end + + def save_emojis(parent) + do_not_download = DomainBlock.find_by(domain: parent.account.domain)&.reject_media? + + return if do_not_download + + remote_account.emojis.each do |link| + next unless link['href'] && link['name'] + + shortcode = link['name'].delete(':') + emoji = CustomEmoji.find_by(shortcode: shortcode, domain: parent.account.domain) + + next unless emoji.nil? + + emoji = CustomEmoji.new(shortcode: shortcode, domain: parent.account.domain) + emoji.image_remote_url = link['href'] + emoji.save end end end diff --git a/app/views/accounts/_header.html.haml b/app/views/accounts/_header.html.haml index b3c91b8..b78998e 100644 --- a/app/views/accounts/_header.html.haml +++ b/app/views/accounts/_header.html.haml @@ -21,7 +21,7 @@ = t 'accounts.roles.moderator' .bio - .account__header__content.p-note.emojify= Formatter.instance.simplified_format(account) + .account__header__content.p-note.emojify= Formatter.instance.simplified_format(account, custom_emojify: true) .details-counters .counter{ class: active_nav_class(short_account_url(account)) } diff --git a/spec/lib/formatter_spec.rb b/spec/lib/formatter_spec.rb index 67fbfe9..6e849f3 100644 --- a/spec/lib/formatter_spec.rb +++ b/spec/lib/formatter_spec.rb @@ -394,6 +394,45 @@ RSpec.describe Formatter do end end + context 'with custom_emojify option' do + let!(:emoji) { Fabricate(:custom_emoji) } + + before { account.note = text } + subject { Formatter.instance.simplified_format(account, custom_emojify: true) } + + context 'with emoji at the start' do + let(:text) { ':coolcat: Beep boop' } + + it 'converts shortcode to image tag' do + is_expected.to match(/

:coolcat:alert("Hello")' end + + context 'with custom_emojify option' do + let!(:emoji) { Fabricate(:custom_emoji, domain: remote_account.domain) } + + before { remote_account.note = text } + + subject { Formatter.instance.simplified_format(remote_account, custom_emojify: true) } + + context 'with emoji at the start' do + let(:text) { '

:coolcat: Beep boop
' } + + it 'converts shortcode to image tag' do + is_expected.to match(/

:coolcat:Beep :coolcat: boop

' } + + it 'converts shortcode to image tag' do + is_expected.to match(/Beep :coolcat::coolcat::coolcat:

' } + + it 'does not touch the shortcodes' do + is_expected.to match(/

:coolcat::coolcat:<\/p>/) + end + end + + context 'with emoji at the end' do + let(:text) { '

Beep boop
:coolcat:

' } + + it 'converts shortcode to image tag' do + is_expected.to match(/
:coolcat: Date: Mon, 2 Apr 2018 02:09:50 +0200 Subject: [PATCH 069/381] Fix unpermitted parameters warning when generating pagination URLs (#6995) --- app/controllers/api/v1/accounts/follower_accounts_controller.rb | 2 +- app/controllers/api/v1/accounts/following_accounts_controller.rb | 2 +- app/controllers/api/v1/accounts/statuses_controller.rb | 2 +- app/controllers/api/v1/blocks_controller.rb | 2 +- app/controllers/api/v1/domain_blocks_controller.rb | 2 +- app/controllers/api/v1/favourites_controller.rb | 2 +- app/controllers/api/v1/follow_requests_controller.rb | 2 +- app/controllers/api/v1/lists/accounts_controller.rb | 2 +- app/controllers/api/v1/mutes_controller.rb | 2 +- app/controllers/api/v1/notifications_controller.rb | 2 +- app/controllers/api/v1/statuses/favourited_by_accounts_controller.rb | 2 +- app/controllers/api/v1/statuses/reblogged_by_accounts_controller.rb | 2 +- app/controllers/api/v1/statuses_controller.rb | 2 +- app/controllers/api/v1/timelines/home_controller.rb | 2 +- app/controllers/api/v1/timelines/list_controller.rb | 2 +- app/controllers/api/v1/timelines/public_controller.rb | 2 +- app/controllers/api/v1/timelines/tag_controller.rb | 2 +- 17 files changed, 17 insertions(+), 17 deletions(-) diff --git a/app/controllers/api/v1/accounts/follower_accounts_controller.rb b/app/controllers/api/v1/accounts/follower_accounts_controller.rb index 80b0bef..c4f600c 100644 --- a/app/controllers/api/v1/accounts/follower_accounts_controller.rb +++ b/app/controllers/api/v1/accounts/follower_accounts_controller.rb @@ -63,6 +63,6 @@ class Api::V1::Accounts::FollowerAccountsController < Api::BaseController end def pagination_params(core_params) - params.permit(:limit).merge(core_params) + params.slice(:limit).permit(:limit).merge(core_params) end end diff --git a/app/controllers/api/v1/accounts/following_accounts_controller.rb b/app/controllers/api/v1/accounts/following_accounts_controller.rb index 55cffdf..90b1f7f 100644 --- a/app/controllers/api/v1/accounts/following_accounts_controller.rb +++ b/app/controllers/api/v1/accounts/following_accounts_controller.rb @@ -63,6 +63,6 @@ class Api::V1::Accounts::FollowingAccountsController < Api::BaseController end def pagination_params(core_params) - params.permit(:limit).merge(core_params) + params.slice(:limit).permit(:limit).merge(core_params) end end diff --git a/app/controllers/api/v1/accounts/statuses_controller.rb b/app/controllers/api/v1/accounts/statuses_controller.rb index 1e1511a..cbcc7ef 100644 --- a/app/controllers/api/v1/accounts/statuses_controller.rb +++ b/app/controllers/api/v1/accounts/statuses_controller.rb @@ -69,7 +69,7 @@ class Api::V1::Accounts::StatusesController < Api::BaseController end def pagination_params(core_params) - params.permit(:limit, :only_media, :exclude_replies).merge(core_params) + params.slice(:limit, :only_media, :exclude_replies).permit(:limit, :only_media, :exclude_replies).merge(core_params) end def insert_pagination_headers diff --git a/app/controllers/api/v1/blocks_controller.rb b/app/controllers/api/v1/blocks_controller.rb index 3a66907..a397013 100644 --- a/app/controllers/api/v1/blocks_controller.rb +++ b/app/controllers/api/v1/blocks_controller.rb @@ -57,6 +57,6 @@ class Api::V1::BlocksController < Api::BaseController end def pagination_params(core_params) - params.permit(:limit).merge(core_params) + params.slice(:limit).permit(:limit).merge(core_params) end end diff --git a/app/controllers/api/v1/domain_blocks_controller.rb b/app/controllers/api/v1/domain_blocks_controller.rb index e93dc60..ae6ad79 100644 --- a/app/controllers/api/v1/domain_blocks_controller.rb +++ b/app/controllers/api/v1/domain_blocks_controller.rb @@ -67,7 +67,7 @@ class Api::V1::DomainBlocksController < Api::BaseController end def pagination_params(core_params) - params.permit(:limit).merge(core_params) + params.slice(:limit).permit(:limit).merge(core_params) end def domain_block_params diff --git a/app/controllers/api/v1/favourites_controller.rb b/app/controllers/api/v1/favourites_controller.rb index 9d73bb3..b4265ed 100644 --- a/app/controllers/api/v1/favourites_controller.rb +++ b/app/controllers/api/v1/favourites_controller.rb @@ -66,6 +66,6 @@ class Api::V1::FavouritesController < Api::BaseController end def pagination_params(core_params) - params.permit(:limit).merge(core_params) + params.slice(:limit).permit(:limit).merge(core_params) end end diff --git a/app/controllers/api/v1/follow_requests_controller.rb b/app/controllers/api/v1/follow_requests_controller.rb index b9f50d7..d5c7c56 100644 --- a/app/controllers/api/v1/follow_requests_controller.rb +++ b/app/controllers/api/v1/follow_requests_controller.rb @@ -71,6 +71,6 @@ class Api::V1::FollowRequestsController < Api::BaseController end def pagination_params(core_params) - params.permit(:limit).merge(core_params) + params.slice(:limit).permit(:limit).merge(core_params) end end diff --git a/app/controllers/api/v1/lists/accounts_controller.rb b/app/controllers/api/v1/lists/accounts_controller.rb index c29c73b..f2bded8 100644 --- a/app/controllers/api/v1/lists/accounts_controller.rb +++ b/app/controllers/api/v1/lists/accounts_controller.rb @@ -88,7 +88,7 @@ class Api::V1::Lists::AccountsController < Api::BaseController end def pagination_params(core_params) - params.permit(:limit).merge(core_params) + params.slice(:limit).permit(:limit).merge(core_params) end def unlimited? diff --git a/app/controllers/api/v1/mutes_controller.rb b/app/controllers/api/v1/mutes_controller.rb index 0c43cb9..c457408 100644 --- a/app/controllers/api/v1/mutes_controller.rb +++ b/app/controllers/api/v1/mutes_controller.rb @@ -59,6 +59,6 @@ class Api::V1::MutesController < Api::BaseController end def pagination_params(core_params) - params.permit(:limit).merge(core_params) + params.slice(:limit).permit(:limit).merge(core_params) end end diff --git a/app/controllers/api/v1/notifications_controller.rb b/app/controllers/api/v1/notifications_controller.rb index 8910b77..ebbe0b2 100644 --- a/app/controllers/api/v1/notifications_controller.rb +++ b/app/controllers/api/v1/notifications_controller.rb @@ -82,6 +82,6 @@ class Api::V1::NotificationsController < Api::BaseController end def pagination_params(core_params) - params.permit(:limit, exclude_types: []).merge(core_params) + params.slice(:limit, :exclude_types).permit(:limit, exclude_types: []).merge(core_params) end end diff --git a/app/controllers/api/v1/statuses/favourited_by_accounts_controller.rb b/app/controllers/api/v1/statuses/favourited_by_accounts_controller.rb index f95cf94..3fe3041 100644 --- a/app/controllers/api/v1/statuses/favourited_by_accounts_controller.rb +++ b/app/controllers/api/v1/statuses/favourited_by_accounts_controller.rb @@ -77,6 +77,6 @@ class Api::V1::Statuses::FavouritedByAccountsController < Api::BaseController end def pagination_params(core_params) - params.permit(:limit).merge(core_params) + params.slice(:limit).permit(:limit).merge(core_params) end end diff --git a/app/controllers/api/v1/statuses/reblogged_by_accounts_controller.rb b/app/controllers/api/v1/statuses/reblogged_by_accounts_controller.rb index 175217e..b065db2 100644 --- a/app/controllers/api/v1/statuses/reblogged_by_accounts_controller.rb +++ b/app/controllers/api/v1/statuses/reblogged_by_accounts_controller.rb @@ -74,6 +74,6 @@ class Api::V1::Statuses::RebloggedByAccountsController < Api::BaseController end def pagination_params(core_params) - params.permit(:limit).merge(core_params) + params.slice(:limit).permit(:limit).merge(core_params) end end diff --git a/app/controllers/api/v1/statuses_controller.rb b/app/controllers/api/v1/statuses_controller.rb index 544a4ce..28c2859 100644 --- a/app/controllers/api/v1/statuses_controller.rb +++ b/app/controllers/api/v1/statuses_controller.rb @@ -76,7 +76,7 @@ class Api::V1::StatusesController < Api::BaseController end def pagination_params(core_params) - params.permit(:limit).merge(core_params) + params.slice(:limit).permit(:limit).merge(core_params) end def authorize_if_got_token diff --git a/app/controllers/api/v1/timelines/home_controller.rb b/app/controllers/api/v1/timelines/home_controller.rb index bbbcf7f..cde4e84 100644 --- a/app/controllers/api/v1/timelines/home_controller.rb +++ b/app/controllers/api/v1/timelines/home_controller.rb @@ -43,7 +43,7 @@ class Api::V1::Timelines::HomeController < Api::BaseController end def pagination_params(core_params) - params.permit(:local, :limit).merge(core_params) + params.slice(:local, :limit).permit(:local, :limit).merge(core_params) end def next_path diff --git a/app/controllers/api/v1/timelines/list_controller.rb b/app/controllers/api/v1/timelines/list_controller.rb index f5db71e..06d596c 100644 --- a/app/controllers/api/v1/timelines/list_controller.rb +++ b/app/controllers/api/v1/timelines/list_controller.rb @@ -45,7 +45,7 @@ class Api::V1::Timelines::ListController < Api::BaseController end def pagination_params(core_params) - params.permit(:limit).merge(core_params) + params.slice(:limit).permit(:limit).merge(core_params) end def next_path diff --git a/app/controllers/api/v1/timelines/public_controller.rb b/app/controllers/api/v1/timelines/public_controller.rb index d7d70b9..13fe015 100644 --- a/app/controllers/api/v1/timelines/public_controller.rb +++ b/app/controllers/api/v1/timelines/public_controller.rb @@ -45,7 +45,7 @@ class Api::V1::Timelines::PublicController < Api::BaseController end def pagination_params(core_params) - params.permit(:local, :limit, :only_media).merge(core_params) + params.slice(:local, :limit, :only_media).permit(:local, :limit, :only_media).merge(core_params) end def next_path diff --git a/app/controllers/api/v1/timelines/tag_controller.rb b/app/controllers/api/v1/timelines/tag_controller.rb index eb32611..7de49a5 100644 --- a/app/controllers/api/v1/timelines/tag_controller.rb +++ b/app/controllers/api/v1/timelines/tag_controller.rb @@ -54,7 +54,7 @@ class Api::V1::Timelines::TagController < Api::BaseController end def pagination_params(core_params) - params.permit(:local, :limit, :only_media).merge(core_params) + params.slice(:local, :limit, :only_media).permit(:local, :limit, :only_media).merge(core_params) end def next_path From f890d2a766ae4c7fd8611dd4f3a15a13408f68c3 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 2 Apr 2018 02:10:53 +0200 Subject: [PATCH 070/381] Support all ActivityPub actor types (#6997) Fix #6973 --- app/services/activitypub/fetch_remote_account_service.rb | 4 +++- app/services/activitypub/fetch_remote_key_service.rb | 4 ++-- app/services/fetch_atom_service.rb | 2 +- app/services/resolve_account_service.rb | 2 +- app/services/resolve_url_service.rb | 2 +- 5 files changed, 8 insertions(+), 6 deletions(-) diff --git a/app/services/activitypub/fetch_remote_account_service.rb b/app/services/activitypub/fetch_remote_account_service.rb index d6ba625..5024853 100644 --- a/app/services/activitypub/fetch_remote_account_service.rb +++ b/app/services/activitypub/fetch_remote_account_service.rb @@ -3,6 +3,8 @@ class ActivityPub::FetchRemoteAccountService < BaseService include JsonLdHelper + SUPPORTED_TYPES = %w(Application Group Organization Person Service).freeze + # Should be called when uri has already been checked for locality # Does a WebFinger roundtrip on each call def call(uri, id: true, prefetched_body: nil) @@ -54,6 +56,6 @@ class ActivityPub::FetchRemoteAccountService < BaseService end def expected_type? - @json['type'] == 'Person' + SUPPORTED_TYPES.include?(@json['type']) end end diff --git a/app/services/activitypub/fetch_remote_key_service.rb b/app/services/activitypub/fetch_remote_key_service.rb index ce1048f..41837d4 100644 --- a/app/services/activitypub/fetch_remote_key_service.rb +++ b/app/services/activitypub/fetch_remote_key_service.rb @@ -43,7 +43,7 @@ class ActivityPub::FetchRemoteKeyService < BaseService end def person? - @json['type'] == 'Person' + ActivityPub::FetchRemoteAccountService::SUPPORTED_TYPES.include?(@json['type']) end def public_key? @@ -55,6 +55,6 @@ class ActivityPub::FetchRemoteKeyService < BaseService end def confirmed_owner? - @owner['type'] == 'Person' && value_or_id(@owner['publicKey']) == @json['id'] + ActivityPub::FetchRemoteAccountService::SUPPORTED_TYPES.include?(@owner['type']) && value_or_id(@owner['publicKey']) == @json['id'] end end diff --git a/app/services/fetch_atom_service.rb b/app/services/fetch_atom_service.rb index 87076cc..0444baf 100644 --- a/app/services/fetch_atom_service.rb +++ b/app/services/fetch_atom_service.rb @@ -42,7 +42,7 @@ class FetchAtomService < BaseService elsif ['application/activity+json', 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'].include?(response.mime_type) body = response.body_with_limit json = body_to_json(body) - if supported_context?(json) && json['type'] == 'Person' && json['inbox'].present? + if supported_context?(json) && ActivityPub::FetchRemoteAccountService::SUPPORTED_TYPES.include?(json['type']) && json['inbox'].present? [json['id'], { prefetched_body: body, id: true }, :activitypub] elsif supported_context?(json) && expected_type?(json) [json['id'], { prefetched_body: body, id: true }, :activitypub] diff --git a/app/services/resolve_account_service.rb b/app/services/resolve_account_service.rb index 744ea24..8cba88f 100644 --- a/app/services/resolve_account_service.rb +++ b/app/services/resolve_account_service.rb @@ -189,7 +189,7 @@ class ResolveAccountService < BaseService return @actor_json if defined?(@actor_json) json = fetch_resource(actor_url, false) - @actor_json = supported_context?(json) && json['type'] == 'Person' ? json : nil + @actor_json = supported_context?(json) && ActivityPub::FetchRemoteAccountService::SUPPORTED_TYPES.include?(json['type']) ? json : nil end def atom diff --git a/app/services/resolve_url_service.rb b/app/services/resolve_url_service.rb index 9499dc2..c19b568 100644 --- a/app/services/resolve_url_service.rb +++ b/app/services/resolve_url_service.rb @@ -17,7 +17,7 @@ class ResolveURLService < BaseService def process_url case type - when 'Person' + when 'Application', 'Group', 'Organization', 'Person', 'Service' FetchRemoteAccountService.new.call(atom_url, body, protocol) when 'Note', 'Article', 'Image', 'Video' FetchRemoteStatusService.new.call(atom_url, body, protocol) From 24611d8deb8eb31e3af1aa2035aab29fbd4510f9 Mon Sep 17 00:00:00 2001 From: luzi82 Date: Mon, 2 Apr 2018 18:11:37 +0800 Subject: [PATCH 071/381] i18n: update zh-HK translation (#7004) * i18n: update zh-HK translation * i18n: update zh-HK translation * i18n-tasks normalize --- app/javascript/mastodon/locales/zh-HK.json | 172 ++++++++++++++--------------- config/locales/simple_form.zh-HK.yml | 2 + config/locales/zh-HK.yml | 31 +++++- 3 files changed, 118 insertions(+), 87 deletions(-) diff --git a/app/javascript/mastodon/locales/zh-HK.json b/app/javascript/mastodon/locales/zh-HK.json index 0504a8c..1cbc9f1 100644 --- a/app/javascript/mastodon/locales/zh-HK.json +++ b/app/javascript/mastodon/locales/zh-HK.json @@ -1,36 +1,36 @@ { "account.block": "封鎖 @{name}", "account.block_domain": "隱藏來自 {domain} 的一切文章", - "account.blocked": "Blocked", - "account.direct": "Direct Message @{name}", + "account.blocked": "封鎖", + "account.direct": "私訊 @{name}", "account.disclaimer_full": "下列資料不一定完整。", - "account.domain_blocked": "Domain hidden", + "account.domain_blocked": "服務站被隱藏", "account.edit_profile": "修改個人資料", "account.follow": "關注", "account.followers": "關注的人", "account.follows": "正關注", "account.follows_you": "關注你", - "account.hide_reblogs": "Hide boosts from @{name}", + "account.hide_reblogs": "隱藏 @{name} 的轉推", "account.media": "媒體", "account.mention": "提及 @{name}", - "account.moved_to": "{name} has moved to:", + "account.moved_to": "{name} 已經遷移到:", "account.mute": "將 @{name} 靜音", - "account.mute_notifications": "Mute notifications from @{name}", - "account.muted": "Muted", + "account.mute_notifications": "將來自 @{name} 的通知靜音", + "account.muted": "靜音", "account.posts": "文章", - "account.posts_with_replies": "Toots with replies", + "account.posts_with_replies": "包含回覆的文章", "account.report": "舉報 @{name}", "account.requested": "等候審批", "account.share": "分享 @{name} 的個人資料", - "account.show_reblogs": "Show boosts from @{name}", + "account.show_reblogs": "顯示 @{name} 的推文", "account.unblock": "解除對 @{name} 的封鎖", "account.unblock_domain": "不再隱藏 {domain}", "account.unfollow": "取消關注", "account.unmute": "取消 @{name} 的靜音", - "account.unmute_notifications": "Unmute notifications from @{name}", + "account.unmute_notifications": "取消來自 @{name} 通知的靜音", "account.view_full_profile": "查看完整資料", - "alert.unexpected.message": "An unexpected error occurred.", - "alert.unexpected.title": "Oops!", + "alert.unexpected.message": "發生不可預期的錯誤。", + "alert.unexpected.title": "噢!", "boost_modal.combo": "如你想在下次路過這顯示,請按{combo},", "bundle_column_error.body": "加載本組件出錯。", "bundle_column_error.retry": "重試", @@ -40,11 +40,11 @@ "bundle_modal_error.retry": "重試", "column.blocks": "封鎖用戶", "column.community": "本站時間軸", - "column.domain_blocks": "Hidden domains", + "column.domain_blocks": "隱藏的服務站", "column.favourites": "最愛的文章", "column.follow_requests": "關注請求", "column.home": "主頁", - "column.lists": "Lists", + "column.lists": "列表", "column.mutes": "靜音名單", "column.notifications": "通知", "column.pins": "置頂文章", @@ -58,25 +58,25 @@ "column_header.unpin": "取下", "column_subheading.navigation": "瀏覽", "column_subheading.settings": "設定", - "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.", - "compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.", + "compose_form.direct_message_warning": "這文章只有被提及的用戶才可以看到。", + "compose_form.hashtag_warning": "這文章因為不是公開,所以不會被標籤搜索。只有公開的文章才會被標籤搜索。", "compose_form.lock_disclaimer": "你的用戶狀態為「{locked}」,任何人都能立即關注你,然後看到「只有關注者能看」的文章。", "compose_form.lock_disclaimer.lock": "公共", "compose_form.placeholder": "你在想甚麼?", "compose_form.publish": "發文", "compose_form.publish_loud": "{publish}!", - "compose_form.sensitive.marked": "Media is marked as sensitive", - "compose_form.sensitive.unmarked": "Media is not marked as sensitive", - "compose_form.spoiler.marked": "Text is hidden behind warning", - "compose_form.spoiler.unmarked": "Text is not hidden", + "compose_form.sensitive.marked": "媒體被標示為敏感", + "compose_form.sensitive.unmarked": "媒體沒有被標示為敏感", + "compose_form.spoiler.marked": "文字被警告隱藏", + "compose_form.spoiler.unmarked": "文字沒有被隱藏", "compose_form.spoiler_placeholder": "敏感警告訊息", "confirmation_modal.cancel": "取消", "confirmations.block.confirm": "封鎖", "confirmations.block.message": "你確定要封鎖{name}嗎?", "confirmations.delete.confirm": "刪除", "confirmations.delete.message": "你確定要刪除{name}嗎?", - "confirmations.delete_list.confirm": "Delete", - "confirmations.delete_list.message": "Are you sure you want to permanently delete this list?", + "confirmations.delete_list.confirm": "刪除", + "confirmations.delete_list.message": "你確定要永久刪除這列表嗎?", "confirmations.domain_block.confirm": "隱藏整個網站", "confirmations.domain_block.message": "你真的真的確定要隱藏整個 {domain} ?多數情況下,比較推薦封鎖或靜音幾個特定目標就好。", "confirmations.mute.confirm": "靜音", @@ -86,7 +86,7 @@ "embed.instructions": "要內嵌此文章,請將以下代碼貼進你的網站。", "embed.preview": "看上去會是這樣:", "emoji_button.activity": "活動", - "emoji_button.custom": "Custom", + "emoji_button.custom": "自訂", "emoji_button.flags": "旗幟", "emoji_button.food": "飲飲食食", "emoji_button.label": "加入表情符號", @@ -94,9 +94,9 @@ "emoji_button.not_found": "No emojos!! (╯°□°)╯︵ ┻━┻", "emoji_button.objects": "物品", "emoji_button.people": "人物", - "emoji_button.recent": "Frequently used", + "emoji_button.recent": "常用", "emoji_button.search": "搜尋…", - "emoji_button.search_results": "Search results", + "emoji_button.search_results": "搜尋結果", "emoji_button.symbols": "符號", "emoji_button.travel": "旅遊景物", "empty_column.community": "本站時間軸暫時未有內容,快文章來搶頭香啊!", @@ -119,48 +119,48 @@ "home.column_settings.show_reblogs": "顯示被轉推的文章", "home.column_settings.show_replies": "顯示回應文章", "home.settings": "欄位設定", - "keyboard_shortcuts.back": "to navigate back", - "keyboard_shortcuts.boost": "to boost", - "keyboard_shortcuts.column": "to focus a status in one of the columns", - "keyboard_shortcuts.compose": "to focus the compose textarea", - "keyboard_shortcuts.description": "Description", - "keyboard_shortcuts.down": "to move down in the list", - "keyboard_shortcuts.enter": "to open status", - "keyboard_shortcuts.favourite": "to favourite", - "keyboard_shortcuts.heading": "Keyboard Shortcuts", - "keyboard_shortcuts.hotkey": "Hotkey", - "keyboard_shortcuts.legend": "to display this legend", - "keyboard_shortcuts.mention": "to mention author", - "keyboard_shortcuts.reply": "to reply", - "keyboard_shortcuts.search": "to focus search", - "keyboard_shortcuts.toot": "to start a brand new toot", - "keyboard_shortcuts.unfocus": "to un-focus compose textarea/search", - "keyboard_shortcuts.up": "to move up in the list", + "keyboard_shortcuts.back": "後退", + "keyboard_shortcuts.boost": "轉推", + "keyboard_shortcuts.column": "把標示移動到其中一列", + "keyboard_shortcuts.compose": "把標示移動到文字輸入區", + "keyboard_shortcuts.description": "描述", + "keyboard_shortcuts.down": "在列表往下移動", + "keyboard_shortcuts.enter": "打開文章", + "keyboard_shortcuts.favourite": "收藏", + "keyboard_shortcuts.heading": "鍵盤快速鍵", + "keyboard_shortcuts.hotkey": "快速鍵", + "keyboard_shortcuts.legend": "顯示這個說明", + "keyboard_shortcuts.mention": "提及作者", + "keyboard_shortcuts.reply": "回覆", + "keyboard_shortcuts.search": "把標示移動到搜索", + "keyboard_shortcuts.toot": "新的推文", + "keyboard_shortcuts.unfocus": "把標示移離文字輸入和搜索", + "keyboard_shortcuts.up": "在列表往上移動", "lightbox.close": "關閉", "lightbox.next": "繼續", "lightbox.previous": "回退", - "lists.account.add": "Add to list", - "lists.account.remove": "Remove from list", - "lists.delete": "Delete list", - "lists.edit": "Edit list", - "lists.new.create": "Add list", - "lists.new.title_placeholder": "New list title", - "lists.search": "Search among people you follow", - "lists.subheading": "Your lists", + "lists.account.add": "新增到列表", + "lists.account.remove": "從列表刪除", + "lists.delete": "刪除列表", + "lists.edit": "編輯列表", + "lists.new.create": "新增列表", + "lists.new.title_placeholder": "新列表標題", + "lists.search": "從你關注的用戶中搜索", + "lists.subheading": "列表", "loading_indicator.label": "載入中...", "media_gallery.toggle_visible": "打開或關上", "missing_indicator.label": "找不到內容", - "missing_indicator.sublabel": "This resource could not be found", - "mute_modal.hide_notifications": "Hide notifications from this user?", + "missing_indicator.sublabel": "無法找到內容", + "mute_modal.hide_notifications": "隱藏來自這用戶的通知嗎?", "navigation_bar.blocks": "被你封鎖的用戶", "navigation_bar.community_timeline": "本站時間軸", - "navigation_bar.domain_blocks": "Hidden domains", + "navigation_bar.domain_blocks": "隱藏的服務站", "navigation_bar.edit_profile": "修改個人資料", "navigation_bar.favourites": "最愛的內容", "navigation_bar.follow_requests": "關注請求", "navigation_bar.info": "關於本服務站", - "navigation_bar.keyboard_shortcuts": "Keyboard shortcuts", - "navigation_bar.lists": "Lists", + "navigation_bar.keyboard_shortcuts": "鍵盤快速鍵", + "navigation_bar.lists": "列表", "navigation_bar.logout": "登出", "navigation_bar.mutes": "被你靜音的用戶", "navigation_bar.pins": "置頂文章", @@ -187,8 +187,8 @@ "onboarding.page_four.home": "「主頁」顯示你所關注用戶的文章", "onboarding.page_four.notifications": "「通知」欄顯示你和其他人的互動。", "onboarding.page_one.federation": "Mastodon(萬象社交)是由一批獨立網站組成的龐大網絡,我們將這些獨立又互連網站稱為「服務站」(instance)", - "onboarding.page_one.full_handle": "Your full handle", - "onboarding.page_one.handle_hint": "This is what you would tell your friends to search for.", + "onboarding.page_one.full_handle": "你的帳號全名", + "onboarding.page_one.handle_hint": "朋友可以從這個帳號全名找到你", "onboarding.page_one.welcome": "歡迎使用 Mastodon(萬象社交)", "onboarding.page_six.admin": "你服務站的管理員是{admin}", "onboarding.page_six.almost_done": "差不多了……", @@ -211,33 +211,33 @@ "privacy.public.short": "公共", "privacy.unlisted.long": "公開,但不在公共時間軸顯示", "privacy.unlisted.short": "公開", - "regeneration_indicator.label": "Loading…", - "regeneration_indicator.sublabel": "Your home feed is being prepared!", - "relative_time.days": "{number}d", - "relative_time.hours": "{number}h", - "relative_time.just_now": "now", - "relative_time.minutes": "{number}m", - "relative_time.seconds": "{number}s", + "regeneration_indicator.label": "載入中……", + "regeneration_indicator.sublabel": "你的主頁時間軸正在準備中!", + "relative_time.days": "{number}日", + "relative_time.hours": "{number}小時", + "relative_time.just_now": "剛剛", + "relative_time.minutes": "{number}分鐘", + "relative_time.seconds": "{number}秒", "reply_indicator.cancel": "取消", - "report.forward": "Forward to {target}", - "report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?", - "report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:", + "report.forward": "轉寄到 {target}", + "report.forward_hint": "這個帳戶屬於其他服務站。要向該服務站發送匿名的舉報訊息嗎?", + "report.hint": "這訊息會發送到你服務站的管理員。你可以提供舉報這個帳戶的理由:", "report.placeholder": "額外訊息", "report.submit": "提交", "report.target": "舉報", "search.placeholder": "搜尋", - "search_popout.search_format": "Advanced search format", - "search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.", - "search_popout.tips.hashtag": "hashtag", - "search_popout.tips.status": "status", - "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags", - "search_popout.tips.user": "user", - "search_results.accounts": "People", - "search_results.hashtags": "Hashtags", - "search_results.statuses": "Toots", + "search_popout.search_format": "高級搜索格式", + "search_popout.tips.full_text": "輸入簡單的文字,搜索由你發放、收藏、轉推和提及你的文章,以及符合的用戶名稱,帳號名稱和標籤。", + "search_popout.tips.hashtag": "標籤", + "search_popout.tips.status": "文章", + "search_popout.tips.text": "輸入簡單的文字,搜索符合的用戶名稱,帳號名稱和標籤。", + "search_popout.tips.user": "用戶", + "search_results.accounts": "使用者", + "search_results.hashtags": "標籤", + "search_results.statuses": "文章", "search_results.total": "{count, number} 項結果", "standalone.public_title": "站點一瞥…", - "status.block": "Block @{name}", + "status.block": "封鎖 @{name}", "status.cannot_reblog": "這篇文章無法被轉推", "status.delete": "刪除", "status.embed": "鑲嵌", @@ -245,12 +245,12 @@ "status.load_more": "載入更多", "status.media_hidden": "隱藏媒體內容", "status.mention": "提及 @{name}", - "status.more": "More", - "status.mute": "Mute @{name}", + "status.more": "更多", + "status.mute": "把 @{name} 靜音", "status.mute_conversation": "靜音對話", "status.open": "展開文章", "status.pin": "置頂到資料頁", - "status.pinned": "Pinned toot", + "status.pinned": "置頂文章", "status.reblog": "轉推", "status.reblogged_by": "{name} 轉推", "status.reply": "回應", @@ -258,22 +258,22 @@ "status.report": "舉報 @{name}", "status.sensitive_toggle": "點擊顯示", "status.sensitive_warning": "敏感內容", - "status.share": "Share", + "status.share": "分享", "status.show_less": "減少顯示", - "status.show_less_all": "Show less for all", + "status.show_less_all": "減少顯示這類文章", "status.show_more": "顯示更多", - "status.show_more_all": "Show more for all", + "status.show_more_all": "顯示更多這類文章", "status.unmute_conversation": "解禁對話", "status.unpin": "解除置頂", "tabs_bar.federated_timeline": "跨站", "tabs_bar.home": "主頁", "tabs_bar.local_timeline": "本站", "tabs_bar.notifications": "通知", - "ui.beforeunload": "Your draft will be lost if you leave Mastodon.", + "ui.beforeunload": "如果你現在離開 Mastodon,你的草稿內容將會被丟棄。", "upload_area.title": "將檔案拖放至此上載", "upload_button.label": "上載媒體檔案", - "upload_form.description": "Describe for the visually impaired", - "upload_form.focus": "Crop", + "upload_form.description": "為視覺障礙人士添加文字說明", + "upload_form.focus": "裁切", "upload_form.undo": "還原", "upload_progress.label": "上載中……", "video.close": "關閉影片", diff --git a/config/locales/simple_form.zh-HK.yml b/config/locales/simple_form.zh-HK.yml index 01ba61f..da0292a 100644 --- a/config/locales/simple_form.zh-HK.yml +++ b/config/locales/simple_form.zh-HK.yml @@ -41,6 +41,7 @@ zh-HK: setting_default_privacy: 文章預設為 setting_default_sensitive: 預設我的內容為敏感內容 setting_delete_modal: 刪推前詢問我 + setting_display_sensitive_media: 預設我的媒體為敏感內容 setting_noindex: 阻止搜尋引擎檢索 setting_reduce_motion: 減低動畫效果 setting_system_font_ui: 使用系統預設字型 @@ -49,6 +50,7 @@ zh-HK: severity: 等級 type: 匯入資料類型 username: 用戶名稱 + username_or_email: 用戶名稱或電郵 interactions: must_be_follower: 隱藏沒有關注你的用戶的通知 must_be_following: 隱藏你不關注的用戶的通知 diff --git a/config/locales/zh-HK.yml b/config/locales/zh-HK.yml index cc1cade..5c1feab 100644 --- a/config/locales/zh-HK.yml +++ b/config/locales/zh-HK.yml @@ -101,7 +101,7 @@ zh-HK: most_recent: 按時間 title: 排序 outbox_url: 寄件箱(Outbox)URL - perform_full_suspension: 實行完全暫停 + perform_full_suspension: 完全停權 profile_url: 個人檔案 URL promote: 升任 protocol: 協議 @@ -272,6 +272,9 @@ zh-HK: contact_information: email: 輸入一個公開的電郵地址 username: 輸入用戶名稱 + hero: + desc_html: 在首頁顯示。推薦最小 600x100px。如果留空,就會默認為服務站縮圖。 + title: 主題圖片 peers_api_enabled: desc_html: 現時本服務站在網絡中已發現的域名 title: 公開已知服務站的列表 @@ -288,6 +291,9 @@ zh-HK: open: desc_html: 允許所有人建立帳戶 title: 開放註冊 + show_known_fediverse_at_about_page: + desc_html: 如果開啟,就會在時間軸預覽顯示跨站文章,否則就只會顯示本站文章。 + title: 在時間軸預覽顯示跨站文章 show_staff_badge: desc_html: 在個人資料頁上顯示管理人員標誌 title: 顯示管理人員標誌 @@ -352,6 +358,8 @@ zh-HK: your_token: token auth: agreement_html: 登記即表示你同意遵守本服務站的規則使用條款。 + change_password: 密碼 + confirm_email: 確認電郵 delete_account: 刪除帳戶 delete_account_html: 如果你想刪除你的帳戶,請點擊這裡繼續。你需要確認你的操作。 didnt_get_confirmation: 沒有收到確認指示電郵? @@ -361,7 +369,13 @@ zh-HK: logout: 登出 migrate_account: 轉移到另一個帳號 migrate_account_html: 想要將這個帳號指向另一個帳號可到這裡設定。 + or: 或 + or_log_in_with: 或登入於 + providers: + cas: CAS + saml: SAML register: 登記 + register_elsewhere: 在其他服務站登記 resend_confirmation: 重發確認指示電郵 reset_password: 重設密碼 security: 登入資訊 @@ -411,6 +425,13 @@ zh-HK: title: 這個頁面有問題 noscript_html: 使用 Mastodon 網頁版應用需要啟用 JavaScript。你也可以選擇適用於你的平台的 Mastodon 應用。 exports: + archive_takeout: + date: 日期 + download: 下載檔案 + hint_html: 你可以下載包含你的文章和媒體的檔案。資料以 ActivityPub 格式儲存,可用於相容的軟體。 + in_progress: 檔案製作中... + request: 下載檔案 + size: 檔案大小 blocks: 被你封鎖的用戶 csv: CSV follows: 你所關注的用戶 @@ -524,7 +545,9 @@ zh-HK: trillion: T unit: '' pagination: + newer: 較新 next: 下一頁 + older: 較舊 prev: 上一頁 truncate: "……" preferences: @@ -606,6 +629,11 @@ zh-HK: two_factor_authentication: 雙重認證 your_apps: 你的應用程式 statuses: + attached: + description: 附件: %{attached} + image: "%{count} 張圖片" + video: "%{count} 段影片" + content_warning: 'Content warning: %{warning}' open_in_web: 開啟網頁 over_character_limit: 超過了 %{max} 字的限制 pin_errors: @@ -670,4 +698,5 @@ zh-HK: users: invalid_email: 電郵地址格式不正確 invalid_otp_token: 雙重認證確認碼不正確 + seamless_external_login: 由於你是從外部系統登入,所以不能設定密碼和電郵。 signed_in_as: 目前登入的帳戶: From b04f73ce664b1f2c8f863b53ba8ff8fa0f0b330f Mon Sep 17 00:00:00 2001 From: Evgeny Petrov Date: Mon, 2 Apr 2018 13:49:06 +0300 Subject: [PATCH 072/381] Russian language updated (#7005) * Russian language updated * Small fixes for RU language * bundle exec i18n-tasks normalize --- app/javascript/mastodon/locales/ru.json | 30 +++++++++--------- config/locales/doorkeeper.ru.yml | 2 +- config/locales/ru.yml | 54 +++++++++++++++++++++++++++++---- config/locales/simple_form.ru.yml | 4 ++- 4 files changed, 67 insertions(+), 23 deletions(-) diff --git a/app/javascript/mastodon/locales/ru.json b/app/javascript/mastodon/locales/ru.json index 1595909..8616ef9 100644 --- a/app/javascript/mastodon/locales/ru.json +++ b/app/javascript/mastodon/locales/ru.json @@ -1,10 +1,10 @@ { "account.block": "Блокировать", "account.block_domain": "Блокировать все с {domain}", - "account.blocked": "Blocked", - "account.direct": "Direct Message @{name}", + "account.blocked": "Заблокирован(а)", + "account.direct": "Написать @{name}", "account.disclaimer_full": "Нижеуказанная информация может не полностью отражать профиль пользователя.", - "account.domain_blocked": "Domain hidden", + "account.domain_blocked": "Домен скрыт", "account.edit_profile": "Изменить профиль", "account.follow": "Подписаться", "account.followers": "Подписаны", @@ -16,9 +16,9 @@ "account.moved_to": "Ищите {name} здесь:", "account.mute": "Заглушить", "account.mute_notifications": "Скрыть уведомления от @{name}", - "account.muted": "Muted", + "account.muted": "Приглушён", "account.posts": "Посты", - "account.posts_with_replies": "Toots with replies", + "account.posts_with_replies": "Посты с ответами", "account.report": "Пожаловаться", "account.requested": "Ожидает подтверждения", "account.share": "Поделиться профилем @{name}", @@ -29,8 +29,8 @@ "account.unmute": "Снять глушение", "account.unmute_notifications": "Показывать уведомления от @{name}", "account.view_full_profile": "Показать полный профиль", - "alert.unexpected.message": "An unexpected error occurred.", - "alert.unexpected.title": "Oops!", + "alert.unexpected.message": "Что-то пошло не так.", + "alert.unexpected.title": "Ой!", "boost_modal.combo": "Нажмите {combo}, чтобы пропустить это в следующий раз", "bundle_column_error.body": "Что-то пошло не так при загрузке этого компонента.", "bundle_column_error.retry": "Попробовать снова", @@ -40,7 +40,7 @@ "bundle_modal_error.retry": "Попробовать снова", "column.blocks": "Список блокировки", "column.community": "Локальная лента", - "column.domain_blocks": "Hidden domains", + "column.domain_blocks": "Скрытые домены", "column.favourites": "Понравившееся", "column.follow_requests": "Запросы на подписку", "column.home": "Главная", @@ -65,10 +65,10 @@ "compose_form.placeholder": "О чем Вы думаете?", "compose_form.publish": "Трубить", "compose_form.publish_loud": "{publish}!", - "compose_form.sensitive.marked": "Media is marked as sensitive", - "compose_form.sensitive.unmarked": "Media is not marked as sensitive", - "compose_form.spoiler.marked": "Text is hidden behind warning", - "compose_form.spoiler.unmarked": "Text is not hidden", + "compose_form.sensitive.marked": "Медиафайлы не отмечены как чувствительные", + "compose_form.sensitive.unmarked": "Медиафайлы не отмечены как чувствительные", + "compose_form.spoiler.marked": "Текст скрыт за предупреждением", + "compose_form.spoiler.unmarked": "Текст не скрыт", "compose_form.spoiler_placeholder": "Напишите свое предупреждение здесь", "confirmation_modal.cancel": "Отмена", "confirmations.block.confirm": "Заблокировать", @@ -154,7 +154,7 @@ "mute_modal.hide_notifications": "Убрать уведомления от этого пользователя?", "navigation_bar.blocks": "Список блокировки", "navigation_bar.community_timeline": "Локальная лента", - "navigation_bar.domain_blocks": "Hidden domains", + "navigation_bar.domain_blocks": "Скрытые домены", "navigation_bar.edit_profile": "Изменить профиль", "navigation_bar.favourites": "Понравившееся", "navigation_bar.follow_requests": "Запросы на подписку", @@ -221,13 +221,13 @@ "reply_indicator.cancel": "Отмена", "report.forward": "Forward to {target}", "report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?", - "report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:", + "report.hint": "Жалоба будет отправлена модераторам Вашего сервера. Вы также можете указать подробную причину жалобы ниже:", "report.placeholder": "Комментарий", "report.submit": "Отправить", "report.target": "Жалуемся на", "search.placeholder": "Поиск", "search_popout.search_format": "Продвинутый формат поиска", - "search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.", + "search_popout.tips.full_text": "Возвращает посты, которые Вы написали, отметили как 'избранное', продвинули или в которых были упомянуты, а также содержащие юзернейм, имя и хэштеги.", "search_popout.tips.hashtag": "хэштег", "search_popout.tips.status": "статус", "search_popout.tips.text": "Простой ввод текста покажет совпадающие имена пользователей, отображаемые имена и хэштеги", diff --git a/config/locales/doorkeeper.ru.yml b/config/locales/doorkeeper.ru.yml index 05c3d97..28c0ff0 100644 --- a/config/locales/doorkeeper.ru.yml +++ b/config/locales/doorkeeper.ru.yml @@ -39,7 +39,7 @@ ru: callback_url: Callback URL delete: Удалить name: Название - new: Новое Приложение + new: Новое приложение scopes: Права show: Показывать title: Ваши приложения diff --git a/config/locales/ru.yml b/config/locales/ru.yml index 81af425..108ca33 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -48,7 +48,7 @@ ru: reserved_username: Имя пользователя зарезервировано roles: admin: Администратор - moderator: Мод + moderator: Модератор unfollow: Отписаться admin: account_moderation_notes: @@ -217,7 +217,7 @@ ru: title: Снять блокировку с домена %{domain} undo: Отменить title: Доменные блокировки - undo: Отемнить + undo: Отменить email_domain_blocks: add_new: Добавить новую created_msg: Доменная блокировка еmail успешно создана @@ -275,6 +275,9 @@ ru: contact_information: email: Введите публичный e-mail username: Введите имя пользователя + hero: + desc_html: Отображается на главной странице. Рекомендуется разрешение не менее 600х100px. Если не установлено, используется изображение узла + title: Баннер узла peers_api_enabled: desc_html: Домены, которые были замечены этим узлом среди всей федерации title: Публикация списка обнаруженных узлов @@ -291,6 +294,9 @@ ru: open: desc_html: Позволяет любому создавать аккаунт title: Открыть регистрацию + show_known_fediverse_at_about_page: + desc_html: Если включено, показывает посты со всех известных узлов в предпросмотре ленты. В противном случае отображаются только локальные посты. + title: Показывать известные узлы в предпросмотре ленты show_staff_badge: desc_html: Показывать метку персонала на странице пользователя title: Показывать метку персонала @@ -308,7 +314,7 @@ ru: desc_html: Используется для предпросмотра с помощью OpenGraph и API. Рекомендуется разрешение 1200x630px title: Картинка узла timeline_preview: - desc_html: Показывать публичную ленту на целевой странице + desc_html: Показывать публичную ленту на приветственной странице title: Предпросмотр ленты title: Настройки сайта statuses: @@ -368,13 +374,17 @@ ru: migrate_account_html: Если Вы хотите перенести этот аккаунт на другой, вы можете сделать это здесь. or: или or_log_in_with: Или войти с помощью + providers: + cas: CAS + saml: SAML register: Зарегистрироваться - register_elsewhere: Зарегистрироваться на другом сервере + register_elsewhere: Зарегистрироваться на другом узле resend_confirmation: Повторить отправку инструкции для подтверждения reset_password: Сбросить пароль - security: Изменить пароль + security: Безопасность set_new_password: Задать новый пароль authorize_follow: + already_following: Вы уже подписаны на этот аккаунт error: К сожалению, при поиске удаленного аккаунта возникла ошибка follow: Подписаться follow_request: 'Вы отправили запрос на подписку:' @@ -467,10 +477,13 @@ ru: '21600': 6 часов '3600': 1 час '43200': 12 часов + '604800': 1 неделю '86400': 1 день expires_in_prompt: Никогда generate: Сгенерировать max_uses: + few: "%{count} исп." + many: "%{count} исп." one: 1 исп. other: "%{count} исп." max_uses_prompt: Без лимита @@ -514,11 +527,13 @@ ru: favourite: body: 'Ваш статус понравился %{name}:' subject: "%{name} понравился Ваш статус" + title: Понравившийся статус follow: body: "%{name} теперь подписан(а) на Вас!" subject: "%{name} теперь подписан(а) на Вас" title: Новый подписчик follow_request: + action: Управление запросами на подписку body: "%{name} запросил Вас о подписке" subject: "%{name} хочет подписаться на Вас" title: Новый запрос о подписке @@ -587,6 +602,7 @@ ru: micro_messenger: MicroMessenger nokia: Nokia S40 Ovi Browser opera: Opera + otter: Otter phantom_js: PhantomJS qq: QQ Browser safari: Safari @@ -628,6 +644,19 @@ ru: two_factor_authentication: Двухфакторная аутентификация your_apps: Ваши приложения statuses: + attached: + description: 'Вложение: %{attached}' + image: + few: "%{count} изображения" + many: "%{count} изображений" + one: "%{count} изображение" + other: "%{count} изображений" + video: + few: "%{count} видео" + many: "%{count} видео" + one: "%{count} видео" + other: "%{count} видео" + content_warning: 'Спойлер: %{warning}' open_in_web: Открыть в WWW over_character_limit: превышен лимит символов (%{max}) pin_errors: @@ -636,6 +665,7 @@ ru: private: Нельзя закрепить непубличный статус reblog: Нельзя закрепить продвинутый статус show_more: Подробнее + title: '%{name}: "%{quote}"' visibilities: private: Для подписчиков private_long: Показывать только подписчикам @@ -717,12 +747,24 @@ ru: title: Вынос архива welcome: edit_profile_action: Настроить профиль + edit_profile_step: Вы можете настроить свой профиль, загрузив аватар, обложку, сменив имя и много чего ещё. Если Вы хотите фильтровать подписчиков до того, как они смогут на Вас подписаться, Вы можете закрыть свой аккаунт. + explanation: Несколько советов для новичков final_action: Начать постить + final_step: 'Начните постить! Ваши публичные посты могут видеть другие, например, в локальной ленте или по хэштегам, даже если у Вас нет подписчиков. Вы также можете поздороваться с остальными и представиться, используя хэштек #приветствие.' + full_handle: Ваше обращение + full_handle_hint: То, что Вы хотите сообщить своим друзьям, чтобы они могли написать Вам или подписаться с другого узла. review_preferences_action: Изменить настройки - subject: Добро пожаловать на Mastodon + review_preferences_step: Проверьте все настройки, например, какие письма Вы хотите получать или уровень приватности статусов по умолчанию. Если Вы не страдаете морской болезнь, можете включить автовоспроизведение GIF. + subject: Добро пожаловать в Mastodon + tip_bridge_html: Если Вы пришли из Twitter, можете поискать своих друзей в Mastodon, используя приложение-мост. Но это работает только если они тоже использовали это приложение! + tip_federated_timeline: В глобальной ленте отображается сеть Mastodon. Но в ней показаны посты только от людей, на которых подписаны Вы и Ваши соседи, поэтому лента может быть неполной. + tip_following: По умолчанию Вы подписаны на администратора(-ов) Вашего узла. Чтобы найти других интересных людей, проверьте локальную и глобальную ленты. + tip_local_timeline: В локальной ленте показаны посты от людей с %{instance}. Это Ваши непосредственные соседи! + tip_mobile_webapp: Если Ваш мобильный браузер предлагает добавить иконку Mastodon на домашний экран, то Вы можете получать push-уведомления. Прямо как полноценное приложение! tips: Советы title: Добро пожаловать на борт, %{name}! users: invalid_email: Введенный e-mail неверен invalid_otp_token: Введен неверный код + seamless_external_login: Вы залогинены через сторонний сервис, поэтому настройки e-mail и пароля недоступны. signed_in_as: 'Выполнен вход под именем:' diff --git a/config/locales/simple_form.ru.yml b/config/locales/simple_form.ru.yml index 5c4df21..b8ee589 100644 --- a/config/locales/simple_form.ru.yml +++ b/config/locales/simple_form.ru.yml @@ -38,7 +38,7 @@ ru: filtered_languages: Фильтруемые языки header: Заголовок locale: Язык - locked: Сделать аккаунт приватным + locked: Сделать аккаунт закрытым max_uses: Макс. число использований new_password: Новый пароль note: О Вас @@ -49,6 +49,7 @@ ru: setting_default_privacy: Видимость постов setting_default_sensitive: Всегда отмечать медиаконтент как чувствительный setting_delete_modal: Показывать диалог подтверждения перед удалением + setting_display_sensitive_media: Всегда показывать медиаконтент, отмеченный как чувствительный setting_noindex: Отказаться от индексации в поисковых машинах setting_reduce_motion: Уменьшить движение в анимации setting_system_font_ui: Использовать шрифт системы по умолчанию @@ -57,6 +58,7 @@ ru: severity: Строгость type: Тип импорта username: Имя пользователя + username_or_email: Имя пользователя или e-mail interactions: must_be_follower: Заблокировать уведомления не от подписчиков must_be_following: Заблокировать уведомления от людей, на которых Вы не подписаны From 3f51c6efaac0a0705d363e021951b0dd6b071a28 Mon Sep 17 00:00:00 2001 From: Yamagishi Kazutoshi Date: Mon, 2 Apr 2018 20:43:30 +0900 Subject: [PATCH 073/381] Weblate translations (2018-04-02) (#7007) * Translated using Weblate (Galician) Currently translated at 100.0% (587 of 587 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/gl/ * Translated using Weblate (Dutch) Currently translated at 100.0% (587 of 587 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/nl/ * Translated using Weblate (Catalan) Currently translated at 100.0% (587 of 587 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/ca/ * Translated using Weblate (Arabic) Currently translated at 76.4% (449 of 587 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/ar/ * Translated using Weblate (Japanese) Currently translated at 99.8% (586 of 587 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/ja/ * Translated using Weblate (Slovak) Currently translated at 92.3% (542 of 587 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/sk/ * Translated using Weblate (Slovak) Currently translated at 92.3% (542 of 587 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/sk/ * Translated using Weblate (Slovak) Currently translated at 100.0% (58 of 58 strings) Translation: Mastodon/Preferences Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/simple_form/sk/ * Translated using Weblate (Polish) Currently translated at 98.9% (581 of 587 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/pl/ * Translated using Weblate (French) Currently translated at 99.6% (585 of 587 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/fr/ * Translated using Weblate (Portuguese (Brazil)) Currently translated at 99.8% (586 of 587 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/pt_BR/ * Translated using Weblate (Catalan) Currently translated at 100.0% (280 of 280 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/ca/ * Translated using Weblate (Persian) Currently translated at 100.0% (75 of 75 strings) Translation: Mastodon/Doorkeeper Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/doorkeeper/fa/ * Translated using Weblate (Persian) Currently translated at 100.0% (280 of 280 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/fa/ * Translated using Weblate (French) Currently translated at 100.0% (280 of 280 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/fr/ * Translated using Weblate (Japanese) Currently translated at 99.8% (586 of 587 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/ja/ * Translated using Weblate (Japanese) Currently translated at 99.8% (586 of 587 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/ja/ * Translated using Weblate (Slovak) Currently translated at 100.0% (280 of 280 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/sk/ * Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (280 of 280 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/zh_Hant/ * Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (62 of 62 strings) Translation: Mastodon/Devise Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/devise/zh_Hant/ * Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (75 of 75 strings) Translation: Mastodon/Doorkeeper Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/doorkeeper/zh_Hant/ * Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (62 of 62 strings) Translation: Mastodon/Devise Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/devise/zh_Hant/ * Translated using Weblate (Esperanto) Currently translated at 100.0% (587 of 587 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/eo/ * Translated using Weblate (Esperanto) Currently translated at 100.0% (280 of 280 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/eo/ * Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (75 of 75 strings) Translation: Mastodon/Doorkeeper Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/doorkeeper/zh_Hant/ * Translated using Weblate (Arabic) Currently translated at 76.6% (450 of 587 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/ar/ * Translated using Weblate (Esperanto) Currently translated at 100.0% (587 of 587 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/eo/ * Translated using Weblate (Esperanto) Currently translated at 100.0% (280 of 280 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/eo/ * Translated using Weblate (Slovak) Currently translated at 92.6% (544 of 587 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/sk/ * Translated using Weblate (Slovak) Currently translated at 100.0% (75 of 75 strings) Translation: Mastodon/Doorkeeper Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/doorkeeper/sk/ * Translated using Weblate (Arabic) Currently translated at 82.9% (487 of 587 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/ar/ * Translated using Weblate (Arabic) Currently translated at 98.6% (74 of 75 strings) Translation: Mastodon/Doorkeeper Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/doorkeeper/ar/ * Translated using Weblate (Slovak) Currently translated at 93.6% (550 of 587 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/sk/ * Translated using Weblate (Slovak) Currently translated at 95.4% (560 of 587 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/sk/ * Translated using Weblate (Japanese) Currently translated at 100.0% (280 of 280 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/ja/ * Translated using Weblate (Indonesian) Currently translated at 100.0% (75 of 75 strings) Translation: Mastodon/Doorkeeper Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/doorkeeper/id/ * Translated using Weblate (Korean) Currently translated at 100.0% (58 of 58 strings) Translation: Mastodon/Preferences Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/simple_form/ko/ * Translated using Weblate (Korean) Currently translated at 99.8% (586 of 587 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/ko/ * Translated using Weblate (Korean) Currently translated at 100.0% (280 of 280 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/ko/ * Translated using Weblate (Esperanto) Currently translated at 100.0% (280 of 280 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/eo/ * Translated using Weblate (Arabic) Currently translated at 82.9% (487 of 587 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/ar/ * Translated using Weblate (Arabic) Currently translated at 99.2% (278 of 280 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/ar/ * Translated using Weblate (Arabic) Currently translated at 87.3% (513 of 587 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/ar/ * Translated using Weblate (Arabic) Currently translated at 99.6% (279 of 280 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/ar/ * bundle exec i18n-tasks normalize && yarn manage:translations * revert --- app/javascript/mastodon/locales/ar.json | 6 +-- app/javascript/mastodon/locales/ca.json | 2 +- app/javascript/mastodon/locales/eo.json | 18 +++---- app/javascript/mastodon/locales/fa.json | 10 ++-- app/javascript/mastodon/locales/fr.json | 42 ++++++++-------- app/javascript/mastodon/locales/ja.json | 2 +- app/javascript/mastodon/locales/ko.json | 4 +- app/javascript/mastodon/locales/sk.json | 8 +-- app/javascript/mastodon/locales/zh-TW.json | 4 +- config/locales/ar.yml | 79 +++++++++++++++++++++++++++++- config/locales/devise.zh-TW.yml | 25 +++++++++- config/locales/doorkeeper.ar.yml | 5 ++ config/locales/doorkeeper.fa.yml | 16 +++--- config/locales/doorkeeper.id.yml | 4 ++ config/locales/doorkeeper.sk.yml | 4 +- config/locales/doorkeeper.zh-TW.yml | 14 ++++-- config/locales/eo.yml | 13 ++++- config/locales/ja.yml | 4 +- config/locales/ko.yml | 16 ++++++ config/locales/simple_form.ko.yml | 2 + config/locales/sk.yml | 24 +++++++++ 21 files changed, 233 insertions(+), 69 deletions(-) diff --git a/app/javascript/mastodon/locales/ar.json b/app/javascript/mastodon/locales/ar.json index 3078b5b..34e3441 100644 --- a/app/javascript/mastodon/locales/ar.json +++ b/app/javascript/mastodon/locales/ar.json @@ -2,7 +2,7 @@ "account.block": "حظر @{name}", "account.block_domain": "إخفاء كل شيئ قادم من إسم النطاق {domain}", "account.blocked": "محظور", - "account.direct": "Direct Message @{name}", + "account.direct": "Direct message @{name}", "account.disclaimer_full": "قد لا تعكس المعلومات أدناه الملف الشخصي الكامل للمستخدم.", "account.domain_blocked": "النطاق مخفي", "account.edit_profile": "تعديل الملف الشخصي", @@ -66,7 +66,7 @@ "compose_form.publish": "بوّق", "compose_form.publish_loud": "{publish}!", "compose_form.sensitive.marked": "لقد تم تحديد هذه الصورة كحساسة", - "compose_form.sensitive.unmarked": "Media is not marked as sensitive", + "compose_form.sensitive.unmarked": "لم يتم تحديد الصورة كحساسة", "compose_form.spoiler.marked": "إنّ النص مخفي وراء تحذير", "compose_form.spoiler.unmarked": "النص غير مخفي", "compose_form.spoiler_placeholder": "تنبيه عن المحتوى", @@ -221,7 +221,7 @@ "reply_indicator.cancel": "إلغاء", "report.forward": "التحويل إلى {target}", "report.forward_hint": "هذا الحساب ينتمي إلى خادوم آخَر. هل تودّ إرسال نسخة مجهولة مِن التقرير إلى هنالك أيضًا ؟", - "report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:", + "report.hint": "سوف يتم إرسال التقرير إلى مُشرِفي مثيل خادومكم. بإمكانك الإدلاء بشرح عن سبب الإبلاغ عن الحساب أسفله :", "report.placeholder": "تعليقات إضافية", "report.submit": "إرسال", "report.target": "إبلاغ", diff --git a/app/javascript/mastodon/locales/ca.json b/app/javascript/mastodon/locales/ca.json index ec5a8a1..b7f95a6 100644 --- a/app/javascript/mastodon/locales/ca.json +++ b/app/javascript/mastodon/locales/ca.json @@ -2,7 +2,7 @@ "account.block": "Bloca @{name}", "account.block_domain": "Amaga-ho tot de {domain}", "account.blocked": "Bloquejat", - "account.direct": "Direct Message @{name}", + "account.direct": "Direct message @{name}", "account.disclaimer_full": "La informació següent pot reflectir incompleta el perfil de l'usuari.", "account.domain_blocked": "Domini ocult", "account.edit_profile": "Edita el perfil", diff --git a/app/javascript/mastodon/locales/eo.json b/app/javascript/mastodon/locales/eo.json index 82b7494..6dee6e5 100644 --- a/app/javascript/mastodon/locales/eo.json +++ b/app/javascript/mastodon/locales/eo.json @@ -2,7 +2,7 @@ "account.block": "Bloki @{name}", "account.block_domain": "Kaŝi ĉion de {domain}", "account.blocked": "Blokita", - "account.direct": "Direct Message @{name}", + "account.direct": "Direct message @{name}", "account.disclaimer_full": "Subaj informoj povas reflekti la profilon de la uzanto nekomplete.", "account.domain_blocked": "Domajno kaŝita", "account.edit_profile": "Redakti profilon", @@ -17,8 +17,8 @@ "account.mute": "Silentigi @{name}", "account.mute_notifications": "Silentigi sciigojn el @{name}", "account.muted": "Silentigita", - "account.posts": "Mesaĝoj", - "account.posts_with_replies": "Mesaĝoj kun respondoj", + "account.posts": "Tootoj", + "account.posts_with_replies": "Kun respondoj", "account.report": "Signali @{name}", "account.requested": "Atendo de aprobo. Alklaku por nuligi peton de sekvado", "account.share": "Diskonigi la profilon de @{name}", @@ -65,10 +65,10 @@ "compose_form.placeholder": "Pri kio vi pensas?", "compose_form.publish": "Hup", "compose_form.publish_loud": "{publish}!", - "compose_form.sensitive.marked": "Media is marked as sensitive", - "compose_form.sensitive.unmarked": "Media is not marked as sensitive", - "compose_form.spoiler.marked": "Text is hidden behind warning", - "compose_form.spoiler.unmarked": "Text is not hidden", + "compose_form.sensitive.marked": "Aŭdovidaĵo markita tikla", + "compose_form.sensitive.unmarked": "Aŭdovidaĵo ne markita tikla", + "compose_form.spoiler.marked": "Teksto kaŝita malantaŭ averto", + "compose_form.spoiler.unmarked": "Teksto ne kaŝita", "compose_form.spoiler_placeholder": "Skribu vian averton ĉi tie", "confirmation_modal.cancel": "Nuligi", "confirmations.block.confirm": "Bloki", @@ -260,9 +260,9 @@ "status.sensitive_warning": "Tikla enhavo", "status.share": "Diskonigi", "status.show_less": "Malgrandigi", - "status.show_less_all": "Show less for all", + "status.show_less_all": "Malgrandigi ĉiujn", "status.show_more": "Grandigi", - "status.show_more_all": "Show more for all", + "status.show_more_all": "Grandigi ĉiujn", "status.unmute_conversation": "Malsilentigi konversacion", "status.unpin": "Depingli de profilo", "tabs_bar.federated_timeline": "Fratara tempolinio", diff --git a/app/javascript/mastodon/locales/fa.json b/app/javascript/mastodon/locales/fa.json index 4b64ca3..61cdcd0 100644 --- a/app/javascript/mastodon/locales/fa.json +++ b/app/javascript/mastodon/locales/fa.json @@ -1,10 +1,10 @@ { "account.block": "مسدودسازی @{name}", "account.block_domain": "پنهان‌سازی همه چیز از سرور {domain}", - "account.blocked": "Blocked", - "account.direct": "Direct Message @{name}", + "account.blocked": "مسدودشده", + "account.direct": "Direct message @{name}", "account.disclaimer_full": "اطلاعات زیر ممکن است نمایهٔ این کاربر را به تمامی نشان ندهد.", - "account.domain_blocked": "Domain hidden", + "account.domain_blocked": "دامین پنهان‌شده", "account.edit_profile": "ویرایش نمایه", "account.follow": "پی بگیرید", "account.followers": "پیگیران", @@ -16,9 +16,9 @@ "account.moved_to": "{name} منتقل شده است به:", "account.mute": "بی‌صدا کردن @{name}", "account.mute_notifications": "بی‌صداکردن اعلان‌ها از طرف @{name}", - "account.muted": "Muted", + "account.muted": "بی‌صداشده", "account.posts": "نوشته‌ها", - "account.posts_with_replies": "Toots with replies", + "account.posts_with_replies": "نوشته‌ها و پاسخ‌ها", "account.report": "گزارش @{name}", "account.requested": "در انتظار پذیرش", "account.share": "هم‌رسانی نمایهٔ @{name}", diff --git a/app/javascript/mastodon/locales/fr.json b/app/javascript/mastodon/locales/fr.json index 58e6ad5..57c55c9 100644 --- a/app/javascript/mastodon/locales/fr.json +++ b/app/javascript/mastodon/locales/fr.json @@ -2,7 +2,7 @@ "account.block": "Bloquer @{name}", "account.block_domain": "Tout masquer venant de {domain}", "account.blocked": "Bloqué", - "account.direct": "Direct Message @{name}", + "account.direct": "Direct message @{name}", "account.disclaimer_full": "Les données ci-dessous peuvent ne pas refléter ce profil dans sa totalité.", "account.domain_blocked": "Domaine caché", "account.edit_profile": "Modifier le profil", @@ -20,7 +20,7 @@ "account.posts": "Pouets", "account.posts_with_replies": "Pouets avec réponses", "account.report": "Signaler", - "account.requested": "Invitation envoyée", + "account.requested": "En attente d'approbation. Cliquez pour annuler la requête", "account.share": "Partager le profil de @{name}", "account.show_reblogs": "Afficher les partages de @{name}", "account.unblock": "Débloquer", @@ -88,7 +88,7 @@ "emoji_button.activity": "Activités", "emoji_button.custom": "Personnalisés", "emoji_button.flags": "Drapeaux", - "emoji_button.food": "Boire et manger", + "emoji_button.food": "Nourriture & Boisson", "emoji_button.label": "Insérer un émoji", "emoji_button.nature": "Nature", "emoji_button.not_found": "Pas d'emojis !! (╯°□°)╯︵ ┻━┻", @@ -98,14 +98,14 @@ "emoji_button.search": "Recherche…", "emoji_button.search_results": "Résultats de la recherche", "emoji_button.symbols": "Symboles", - "emoji_button.travel": "Lieux et voyages", + "emoji_button.travel": "Lieux & Voyages", "empty_column.community": "Le fil public local est vide. Écrivez donc quelque chose pour le remplir !", "empty_column.hashtag": "Il n’y a encore aucun contenu associé à ce hashtag.", - "empty_column.home": "Vous ne suivez encore personne. Visitez {public} ou bien utilisez la recherche pour vous connecter à d’autres utilisateur⋅ice⋅s.", + "empty_column.home": "Vous ne suivez encore personne. Visitez {public} ou bien utilisez la recherche pour vous connecter à d’autres personnes.", "empty_column.home.public_timeline": "le fil public", "empty_column.list": "Il n'y a rien dans cette liste pour l'instant. Dès que des personnes de cette liste publierons de nouveaux statuts, ils apparaîtront ici.", - "empty_column.notifications": "Vous n’avez pas encore de notification. Interagissez avec d’autres utilisateur⋅ice⋅s pour débuter la conversation.", - "empty_column.public": "Il n’y a rien ici ! Écrivez quelque chose publiquement, ou bien suivez manuellement des utilisateur⋅ice·s d’autres instances pour remplir le fil public", + "empty_column.notifications": "Vous n’avez pas encore de notification. Interagissez avec d’autres personnes pour débuter la conversation.", + "empty_column.public": "Il n’y a rien ici ! Écrivez quelque chose publiquement, ou bien suivez manuellement des personnes d’autres instances pour remplir le fil public", "follow_request.authorize": "Accepter", "follow_request.reject": "Rejeter", "getting_started.appsshort": "Applications", @@ -122,9 +122,9 @@ "keyboard_shortcuts.back": "revenir en arrière", "keyboard_shortcuts.boost": "partager", "keyboard_shortcuts.column": "focaliser un statut dans l'une des colonnes", - "keyboard_shortcuts.compose": "pour centrer la zone de redaction", + "keyboard_shortcuts.compose": "pour centrer la zone de rédaction", "keyboard_shortcuts.description": "Description", - "keyboard_shortcuts.down": "descendre dans la liste", + "keyboard_shortcuts.down": "pour descendre dans la liste", "keyboard_shortcuts.enter": "pour ouvrir le statut", "keyboard_shortcuts.favourite": "vers les favoris", "keyboard_shortcuts.heading": "Raccourcis clavier", @@ -151,7 +151,7 @@ "media_gallery.toggle_visible": "Modifier la visibilité", "missing_indicator.label": "Non trouvé", "missing_indicator.sublabel": "Ressource introuvable", - "mute_modal.hide_notifications": "Masquer les notifications de cet utilisateur ?", + "mute_modal.hide_notifications": "Masquer les notifications de cette personne ?", "navigation_bar.blocks": "Comptes bloqués", "navigation_bar.community_timeline": "Fil public local", "navigation_bar.domain_blocks": "Hidden domains", @@ -170,7 +170,7 @@ "notification.follow": "{name} vous suit", "notification.mention": "{name} vous a mentionné⋅e :", "notification.reblog": "{name} a partagé votre statut :", - "notifications.clear": "Nettoyer", + "notifications.clear": "Nettoyer les notifications", "notifications.clear_confirmation": "Voulez-vous vraiment supprimer toutes vos notifications ?", "notifications.column_settings.alert": "Notifications locales", "notifications.column_settings.favourite": "Favoris :", @@ -183,12 +183,12 @@ "notifications.column_settings.sound": "Émettre un son", "onboarding.done": "Effectué", "onboarding.next": "Suivant", - "onboarding.page_five.public_timelines": "Le fil public global affiche les posts de tou⋅te⋅s les utilisateur⋅ice⋅s suivi⋅es par les membres de {domain}. Le fil public local est identique mais se limite aux utilisateur⋅ice⋅s de {domain}.", - "onboarding.page_four.home": "L’Accueil affiche les posts de tou⋅te·s les utilisateur⋅ice·s que vous suivez.", - "onboarding.page_four.notifications": "Les Notifications vous informent lorsque quelqu’un interagit avec vous.", - "onboarding.page_one.federation": "Mastodon est un réseau social qui appartient à tou⋅te⋅s.", - "onboarding.page_one.full_handle": "Votre pleine maîtrise", - "onboarding.page_one.handle_hint": "C'est ce que vous diriez à vos amis de rechercher.", + "onboarding.page_five.public_timelines": "Le fil public global affiche les messages de toutes les personnes suivies par les membres de {domain}. Le fil public local est identique mais se limite aux membres de {domain}.", + "onboarding.page_four.home": "L’Accueil affiche les messages des personnes que vous suivez.", + "onboarding.page_four.notifications": "La colonne de notification vous avertit lors d'une interaction avec vous.", + "onboarding.page_one.federation": "Mastodon est un réseau de serveurs indépendants qui se joignent pour former un réseau social plus vaste. Nous appelons ces serveurs des instances.", + "onboarding.page_one.full_handle": "Votre identifiant complet", + "onboarding.page_one.handle_hint": "C'est ce que vos amis devront rechercher.", "onboarding.page_one.welcome": "Bienvenue sur Mastodon !", "onboarding.page_six.admin": "L’administrateur⋅ice de votre instance est {admin}.", "onboarding.page_six.almost_done": "Nous y sommes presque…", @@ -199,7 +199,7 @@ "onboarding.page_six.read_guidelines": "S’il vous plaît, n’oubliez pas de lire les {guidelines} !", "onboarding.page_six.various_app": "applications mobiles", "onboarding.page_three.profile": "Modifiez votre profil pour changer votre avatar, votre description ainsi que votre nom. Vous y trouverez également d’autres préférences.", - "onboarding.page_three.search": "Utilisez la barre de recherche pour trouver des utilisateur⋅ice⋅s et regarder des hashtags tels que {illustration} et {introductions}. Pour trouver quelqu’un qui n’est pas sur cette instance, utilisez son nom d’utilisateur⋅ice complet.", + "onboarding.page_three.search": "Utilisez la barre de recherche pour trouver des utilisateur⋅ice⋅s ou regardez des hashtags tels que {illustration} et {introductions}. Pour trouver quelqu’un qui n’est pas sur cette instance, utilisez son identifiant complet.", "onboarding.page_two.compose": "Écrivez depuis la colonne de composition. Vous pouvez ajouter des images, changer les réglages de confidentialité, et ajouter des avertissements de contenu (Content Warning) grâce aux icônes en dessous.", "onboarding.skip": "Passer", "privacy.change": "Ajuster la confidentialité du message", @@ -227,16 +227,16 @@ "report.target": "Signalement", "search.placeholder": "Rechercher", "search_popout.search_format": "Recherche avancée", - "search_popout.tips.full_text": "Les textes simples retournent les pouets que vous avez écris, mis en favori, épinglés, ou ayant été mentionnés, ainsi que les noms d'utilisateurs, les noms affichés, et les hashtags correspondant.", + "search_popout.tips.full_text": "Les textes simples retournent les pouets que vous avez écris, mis en favori, épinglés, ou ayant été mentionnés, ainsi que les identifiants, les noms affichés, et les hashtags des personnes et messages correspondant.", "search_popout.tips.hashtag": "hashtag", "search_popout.tips.status": "statuts", - "search_popout.tips.text": "Un texte simple renvoie les noms affichés, les noms d’utilisateur⋅ice et les hashtags correspondants", + "search_popout.tips.text": "Un texte simple renvoie les noms affichés, les identifiants et les hashtags correspondants", "search_popout.tips.user": "utilisateur⋅ice", "search_results.accounts": "Personnes", "search_results.hashtags": "Hashtags", "search_results.statuses": "Pouets", "search_results.total": "{count, number} {count, plural, one {résultat} other {résultats}}", - "standalone.public_title": "Jeter un coup d’œil…", + "standalone.public_title": "Un aperçu …", "status.block": "Block @{name}", "status.cannot_reblog": "Cette publication ne peut être boostée", "status.delete": "Effacer", diff --git a/app/javascript/mastodon/locales/ja.json b/app/javascript/mastodon/locales/ja.json index 80a13a9..4325177 100644 --- a/app/javascript/mastodon/locales/ja.json +++ b/app/javascript/mastodon/locales/ja.json @@ -27,7 +27,7 @@ "account.unblock_domain": "{domain}を表示", "account.unfollow": "フォロー解除", "account.unmute": "@{name}さんのミュートを解除", - "account.unmute_notifications": "@{name}さんからの通知を受け取る", + "account.unmute_notifications": "@{name}さんからの通知を受け取るようにする", "account.view_full_profile": "全ての情報を見る", "alert.unexpected.message": "不明なエラーが発生しました", "alert.unexpected.title": "エラー", diff --git a/app/javascript/mastodon/locales/ko.json b/app/javascript/mastodon/locales/ko.json index 449df42..89fde89 100644 --- a/app/javascript/mastodon/locales/ko.json +++ b/app/javascript/mastodon/locales/ko.json @@ -2,7 +2,7 @@ "account.block": "@{name}을 차단", "account.block_domain": "{domain} 전체를 숨김", "account.blocked": "차단 됨", - "account.direct": "Direct Message @{name}", + "account.direct": "Direct message @{name}", "account.disclaimer_full": "여기 있는 정보는 유저의 프로파일을 정확히 반영하지 못 할 수도 있습니다.", "account.domain_blocked": "도메인 숨겨짐", "account.edit_profile": "프로필 편집", @@ -17,7 +17,7 @@ "account.mute": "@{name} 뮤트", "account.mute_notifications": "@{name}의 알림을 뮤트", "account.muted": "뮤트 됨", - "account.posts": "게시물", + "account.posts": "툿", "account.posts_with_replies": "툿과 답장", "account.report": "@{name} 신고", "account.requested": "승인 대기 중. 클릭해서 취소하기", diff --git a/app/javascript/mastodon/locales/sk.json b/app/javascript/mastodon/locales/sk.json index 7bfae03..925b46d 100644 --- a/app/javascript/mastodon/locales/sk.json +++ b/app/javascript/mastodon/locales/sk.json @@ -2,7 +2,7 @@ "account.block": "Blokovať @{name}", "account.block_domain": "Ukryť všetko z {domain}", "account.blocked": "Blokovaný/á", - "account.direct": "Direct Message @{name}", + "account.direct": "Direct message @{name}", "account.disclaimer_full": "Inofrmácie nižšie nemusia byť úplným odrazom uživateľovho účtu.", "account.domain_blocked": "Doména ukrytá", "account.edit_profile": "Upraviť profil", @@ -42,12 +42,12 @@ "column.community": "Lokálna časová os", "column.domain_blocks": "Hidden domains", "column.favourites": "Obľúbené", - "column.follow_requests": "Žiadosti o sledovaní", + "column.follow_requests": "Žiadosti o sledovanie", "column.home": "Domov", "column.lists": "Zoznamy", "column.mutes": "Ignorovaní užívatelia", "column.notifications": "Notifikácie", - "column.pins": "Pripnuté toots", + "column.pins": "Pripnuté tooty", "column.public": "Federovaná časová os", "column_back_button.label": "Späť", "column_header.hide_settings": "Skryť nastavenia", @@ -163,7 +163,7 @@ "navigation_bar.lists": "Zoznamy", "navigation_bar.logout": "Odhlásiť", "navigation_bar.mutes": "Ignorovaní užívatelia", - "navigation_bar.pins": "Pripnuté toots", + "navigation_bar.pins": "Pripnuté tooty", "navigation_bar.preferences": "Voľby", "navigation_bar.public_timeline": "Federovaná časová os", "notification.favourite": "{name} sa páči tvoj status", diff --git a/app/javascript/mastodon/locales/zh-TW.json b/app/javascript/mastodon/locales/zh-TW.json index fab7ecf..c792582 100644 --- a/app/javascript/mastodon/locales/zh-TW.json +++ b/app/javascript/mastodon/locales/zh-TW.json @@ -2,7 +2,7 @@ "account.block": "封鎖 @{name}", "account.block_domain": "隱藏來自 {domain} 的一切貼文", "account.blocked": "Blocked", - "account.direct": "Direct Message @{name}", + "account.direct": "Direct message @{name}", "account.disclaimer_full": "下列資料不一定完整。", "account.domain_blocked": "Domain hidden", "account.edit_profile": "編輯用者資訊", @@ -103,7 +103,7 @@ "empty_column.hashtag": "這個主題標籤下什麼都沒有。", "empty_column.home": "你還沒關注任何人。造訪{public}或利用搜尋功能找到其他用者。", "empty_column.home.public_timeline": "公開時間軸", - "empty_column.list": "There is nothing in this list yet.", + "empty_column.list": "此份清單尚未有東西。當此清單的成員貼出了新的狀態時,它們就會出現在這裡。", "empty_column.notifications": "還沒有任何通知。和別的使用者互動來開始對話。", "empty_column.public": "這裡什麼都沒有!公開寫些什麼,或是關注其他副本的使用者。", "follow_request.authorize": "授權", diff --git a/config/locales/ar.yml b/config/locales/ar.yml index 25ca302..c316a2f 100644 --- a/config/locales/ar.yml +++ b/config/locales/ar.yml @@ -119,6 +119,7 @@ ar: user: مستخدِم salmon_url: عنوان رابط سالمون Salmon search: البحث + shared_inbox_url: رابط الصندوق المُشترَك للبريد الوارد show: created_reports: البلاغات التي أنشأها هذا الحساب report: التقرير @@ -140,14 +141,23 @@ ar: create_email_domain_block: "%{name} قد قام بحظر نطاق البريد الإلكتروني %{target}" demote_user: "%{name} قد قام بإنزال الرتبة الوظيفية لـ %{target}" destroy_domain_block: "%{name} قام بإلغاء الحجب عن النطاق %{target}" + destroy_email_domain_block: قام %{name} بإضافة نطاق البريد الإلكتروني %{target} إلى اللائحة البيضاء + destroy_status: لقد قام %{name} بحذف منشور %{target} disable_2fa_user: "%{name} لقد قام بتعطيل ميزة المصادقة بخطوتين للمستخدم %{target}" disable_custom_emoji: "%{name} قام بتعطيل الإيموجي %{target}" disable_user: "%{name} لقد قام بتعطيل تسجيل الدخول للمستخدِم %{target}" enable_custom_emoji: "%{name} قام بتنشيط الإيموجي %{target}" enable_user: "%{name} لقد قام بتنشيط تسجيل الدخول للمستخدِم %{target}" + memorialize_account: لقد قام %{name} بتحويل حساب %{target} إلى صفحة تذكارية promote_user: "%{name} قام بترقية المستخدم %{target}" reset_password_user: "%{name} لقد قام بإعادة تعيين الكلمة السرية الخاصة بـ %{target}" + resolve_report: قام %{name} بإلغاء التقرير المُرسَل مِن طرف %{target} + silence_account: لقد قام %{name} بكتم حساب %{target} + suspend_account: لقد قام %{name} بتعليق حساب %{target} + unsilence_account: لقد قام %{name} بإلغاء الكتم عن حساب %{target} + unsuspend_account: لقد قام %{name} بإلغاء التعليق المفروض على حساب %{target} update_custom_emoji: "%{name} قام بتحديث الإيموجي %{target}" + update_status: لقد قام %{name} بتحديث منشور %{target} title: سِجلّ التفتيش و المعاينة custom_emojis: by_domain: النطاق @@ -163,35 +173,54 @@ ar: enable: تفعيل enabled_msg: تم تنشيط ذاك الإيموجي بنجاح image_hint: ملف PNG إلى غاية حجم 50 ك.ب + listed: مُدرَج new: title: إضافة إيموجي خاص جديد + overwrite: إعادة الكتابة shortcode: الترميز المُصَغّر shortcode_hint: على الأقل حرفين، و فقط رموز أبجدية عددية و أسطر سفلية title: الإيموجي الخاصة + unlisted: غير مدرج update_failed_msg: تعذرت عملية تحذيث ذاك الإيموجي updated_msg: تم تحديث الإيموجي بنجاح ! upload: رفع domain_blocks: add_new: إضافة نطاق جديد + created_msg: إنّ حجب النطاق حيز التشغيل + destroyed_msg: تم إلغاء الحجب المفروض على النطاق domain: النطاق new: create: إنشاء حظر severity: noop: لا شيء silence: كتم + suspend: تعليق title: حجب نطاق جديد reject_media: رفض ملفات الوسائط severities: noop: لا شيء + silence: إخفاء أو كتم + suspend: تعليق + severity: الشدة show: + affected_accounts: + other: هناك %{count} حسابات في قاعدة البيانات متأثرة بذلك + retroactive: + silence: إلغاء الكتم عن كافة الحسابات المتواجدة على هذا النطاق + suspend: إلغاء التعليق المفروض على كافة حسابات هذا النطاق title: رفع حظر النطاق عن %{domain} undo: إلغاء + title: حظر النطاقات undo: إلغاء email_domain_blocks: + add_new: إضافة + created_msg: لقد دخل حظر نطاق البريد الإلكتروني حيّز الخدمة delete: حذف + destroyed_msg: تم حذف نطاق البريد الإلكتروني من اللائحة السوداء بنجاح domain: النطاق new: create: إضافة نطاق + title: إضافة نطاق بريد جديد إلى اللائحة السوداء title: القائمة السوداء للبريد الإلكتروني instances: account_count: الحسابات المعروفة @@ -214,27 +243,52 @@ ar: none: لا شيء delete: حذف id: معرّف ID + mark_as_resolved: إعتبار التقرير كمحلول nsfw: + 'false': الكشف عن الصور 'true': إخفاء الوسائط المرفقة + report: 'التقرير #%{id}' report_contents: المحتويات + reported_account: حساب مُبلّغ عنه reported_by: أبلغ عنه من طرف + resolved: معالجة + silence_account: كتم و إخفاء الحساب status: الحالة + suspend_account: فرض تعليق على الحساب + target: الهدف title: التقارير + unresolved: غير معالجة view: عرض settings: + activity_api_enabled: + desc_html: عدد المنشورات المحلية و المستخدمين النشطين و التسجيلات الأسبوعية الجديدة + bootstrap_timeline_accounts: + title: الإشتراكات الإفتراضية للمستخدمين الجدد contact_information: email: البريد الإلكتروني المهني username: الإتصال بالمستخدِم + hero: + title: الصورة الرأسية + peers_api_enabled: + desc_html: أسماء النطاقات التي إلتقى بها مثيل الخادوم على البيئة الموحَّدة فيديفرس + title: نشر عدد مثيلات الخوادم التي تم مصادفتها registrations: closed_message: title: رسالة التسجيلات المقفلة deletion: desc_html: السماح لأي مستخدم إغلاق حسابه + title: السماح بحذف الحسابات + min_invite_role: + disabled: لا أحد open: desc_html: السماح للجميع بإنشاء حساب title: فتح التسجيل + show_known_fediverse_at_about_page: + title: إظهار الفيديفرس الموحَّد في خيط المُعايَنة site_description: title: وصف مثيل الخادوم + site_description_extended: + title: الوصف المُفصّل للموقع site_terms: title: شروط الخدمة المخصصة site_title: إسم مثيل الخادم @@ -242,11 +296,14 @@ ar: title: الصورة الرمزية المصغرة لمثيل الخادوم timeline_preview: desc_html: عرض الخيط العمومي على صفحة الإستقبال + title: مُعاينة الخيط العام title: إعدادات الموقع statuses: back_to_account: العودة إلى صفحة الحساب batch: delete: حذف + execute: تفعيل + failed_to_execute: خطأ في التفعيل media: hide: إخفاء الوسائط show: إظهار الوسائط @@ -256,8 +313,13 @@ ar: subscriptions: confirmed: مؤكَّد expires_in: تنتهي مدة صلاحيتها في + last_delivery: آخر إيداع + title: WebSub topic: الموضوع title: الإدارة + admin_mailer: + new_report: + body: قام %{reporter} بالإبلاغ عن %{target} application_mailer: notification_preferences: تعديل خيارات البريد الإلكتروني salutation: "%{name}،" @@ -270,6 +332,7 @@ ar: destroyed: تم حذف التطبيق بنجاح invalid_url: إن الرابط المقدم غير صالح regenerate_token: إعادة توليد رمز النفاذ + warning: كن حذرا مع هذه البيانات. لا تقم أبدا بمشاركتها مع الآخَرين ! your_token: رمز نفاذك auth: agreement_html: بقبولك التسجيل فإنك تُصرِّح قبول قواعد مثيل الخادوم و شروط الخدمة التي نوفرها لك. @@ -283,11 +346,13 @@ ar: logout: خروج migrate_account: الإنتقال إلى حساب آخر migrate_account_html: إن كنت ترغب في تحويل هذا الحساب نحو حساب آخَر، يُمكِنُك إعداده هنا. + or: أو or_log_in_with: أو قم بتسجيل الدخول بواسطة providers: cas: CAS saml: SAML register: إنشاء حساب + register_elsewhere: التسجيل على خادوم آخَر resend_confirmation: إعادة إرسال تعليمات التأكيد reset_password: إعادة تعيين كلمة المرور security: الهوية @@ -296,6 +361,7 @@ ar: error: يا للأسف، وقع هناك خطأ إثر عملية البحث عن الحساب عن بعد follow: إتبع follow_request: 'لقد قمت بإرسال طلب متابعة إلى :' + following: 'مرحى ! أنت الآن تتبع :' post_follow: close: أو يمكنك إغلاق هذه النافذة. return: العودة إلى الملف الشخصي للمستخدم @@ -324,18 +390,24 @@ ar: '403': ليس لك الصلاحيات الكافية لعرض هذه الصفحة. '404': إنّ الصفحة التي تبحث عنها لا وجود لها أصلا. '410': إنّ الصفحة التي تبحث عنها لم تعد موجودة. + '422': + content: فشل التحقق الآمن. ربما منعتَ كعكات الكوكيز ؟ + title: فشِل التحقق الآمن '500': content: نحن متأسفون، لقد حدث خطأ ما مِن جانبنا. title: هذه الصفحة خاطئة exports: archive_takeout: + date: التاريخ download: تنزيل نسخة لحسابك hint_html: بإمكانك طلب نسخة كاملة لـ كافة تبويقاتك و الوسائط التي قمت بنشرها. البيانات المُصدَّرة ستكون محفوظة على شكل نسق ActivityPub و باستطاعتك قراءتها بأي برنامج يدعم هذا النسق. in_progress: عملية جمع نسخة لبيانات حسابك جارية … request: طلب نسخة لحسابك + size: الحجم blocks: قمت بحظر csv: CSV follows: أنت تتبع + mutes: قُمتَ بكتم storage: ذاكرة التخزين followers: domain: النطاق @@ -368,9 +440,14 @@ ar: '86400': يوم واحد expires_in_prompt: أبدا generate: توليد + max_uses: + one: إستعمال واحد + other: "%{count} استخدامات" max_uses_prompt: بلا حدود + prompt: توليد و مشاركة روابط للسماح للآخَرين بالنفاذ إلى مثيل الخادوم هذا table: expires_at: تنتهي مدة صلاحيتها في + uses: يستخدِم title: دعوة أشخاص landing_strip_html: "%{name} هو أحد مُستخدِمي %{link_to_root_path}. بإمكانك متابعته أو التواصل معه إن كنت تملك حسابًا أيا كان على البيئة الموحَّدة فيديفرس." landing_strip_signup_html: إن كنت لا تملك واحدا، يمكنك التسجيل مِن هنا. @@ -433,7 +510,7 @@ ar: next: التالي older: الأقدَم prev: السابق - truncate: "…" + truncate: و preferences: languages: اللغات other: إعدادات أخرى diff --git a/config/locales/devise.zh-TW.yml b/config/locales/devise.zh-TW.yml index e627653..976e96b 100644 --- a/config/locales/devise.zh-TW.yml +++ b/config/locales/devise.zh-TW.yml @@ -2,7 +2,7 @@ zh-TW: devise: confirmations: - confirmed: 信箱驗證成功 + confirmed: 您的電子郵件地址確認成功。 send_instructions: 您將會在幾分鐘內收到驗證信。 send_paranoid_instructions: 如果您的電子信箱已經存在於我們的資料庫,您將會在幾分鐘內收到信,確認您電子信箱的指示。 failure: @@ -10,18 +10,39 @@ zh-TW: inactive: 您的帳號尚未啟用。 invalid: 不正確的 %{authentication_keys} 或密碼。 last_attempt: 若您再次嘗試失敗,我們將鎖定您的帳號,以策安全。 - locked: 您的帳號已被鎖定 + locked: 您的帳號已被鎖定。 not_found_in_database: 不正確的 %{authentication_keys} 或密碼。 timeout: 您的登入階段已經逾期,請重新登入以繼續使用。 unauthenticated: 您必須先登入或註冊,以繼續使用。 unconfirmed: 您必須先完成信箱驗證,以繼續使用。 mailer: confirmation_instructions: + action: 驗證電子郵件地址 + explanation: 您已經在 %{host} 上以此電子郵件地址建立了一個帳號。您距離啟用它只剩一次點擊之遙了。如果這不是你,請忽略此電子郵件。 + extra_html: 同時也請看看該實體的規則我們的服務條款。 subject: 'Mastodon: 信箱驗證' + title: 驗證電子郵件地址 + email_changed: + explanation: 您帳號的電子郵件地址被變更為: + extra: 如果您並未變更您的電子郵件,那麼很有可能是某人取得了您帳號的存取權限。請立刻變更您的密碼,或是若您的帳號已被鎖定,請聯絡所使用實體的管理員。 + subject: Mastodon 電子郵件變更 + title: 新電子郵件地址 password_change: + explanation: 您帳號的密碼已變更。 + extra: 如果您並未變更您的密碼,那麼很有可能是某人取得了您帳號的存取權限。請立刻變更您的密碼,或是若您的帳號已被鎖定,請聯絡所使用實體的管理員。 subject: 'Mastodon: 更改密碼' + title: 密碼已變更 + reconfirmation_instructions: + explanation: 確認新的電子郵件地址以變更您的電子郵件。 + extra: 若此次變更不是由您開啟的,請忽略這個電子郵件。Mastodon 帳號的電子郵件地址在您存取上面的連結前不會變更。 + subject: Mastodon:%{instance} 的確認電子郵件 + title: 驗證電子郵件地址 reset_password_instructions: + action: 變更密碼 + explanation: 您為您的帳號請求了一個新密碼。 + extra: 若您並未請求這個,請忽略此電子郵件。您的密碼在您存取上面的連結並建立一個新的之前不會變更。 subject: 'Mastodon: 重設密碼' + title: 重設密碼 unlock_instructions: subject: 'Mastodon: 帳號解鎖' omniauth_callbacks: diff --git a/config/locales/doorkeeper.ar.yml b/config/locales/doorkeeper.ar.yml index d13c223..5586b8d 100644 --- a/config/locales/doorkeeper.ar.yml +++ b/config/locales/doorkeeper.ar.yml @@ -79,12 +79,17 @@ ar: messages: access_denied: لقد رفض مالك المَورِدِ أو تصريح السيرفر طلبك. invalid_client: فشلت المصادقة مع العميل لأنه العميل مجهول أو لغياب المصادقة ضمن العميل أو أنّ أسلوب المصادقة غير مدعومة. + invalid_grant: إنّ التصريح المقدَّم غير صالح، سواء انتهت مدة صلاحيته أو تم إلغاؤه أو أنه لا يتطابق مع عنوان إعادة التحويل في طلب التصريح أو أنّ هذا التصريح قد تم تقديمه لعميل آخر. invalid_redirect_uri: إنّ عنوان إعادة التحويل غير صالح. + invalid_request: إنّ هذا الطلب يستلزم مؤشرا أو يحمل قيمة مُعامِل غير مدعومة أو فيه خلل ما. + invalid_resource_owner: إنّ المُعرِّفات التي قدّمها صاحب المورِد غير صحيحة أو أنه لا وجود لصاحب المورِد invalid_scope: المجال المطلوب غير صحيح أو مجهول أو مُعبَّر عنه بشكل خاطئ. invalid_token: expired: إنتهت فترة صلاحيته رمز المصادقة revoked: تم إبطال رمز المصادقة unknown: رمز المصادقة غير صالح + resource_owner_authenticator_not_configured: لقد أخفقت عملية البحث عن صاحب المَورِد لغياب الضبط في Doorkeeper.configure.resource_owner_authenticator. + server_error: لقد صادفَ خادوم التصريحات ضروفا غير مواتية، الأمر الذي مَنَعه مِن مواصلة دراسة الطلب. temporarily_unavailable: تعذر على خادم التفويض معالجة الطلب و ذلك بسبب زيادة مؤقتة في التحميل أو عملية صيانة مبرمجة على الخادم. unauthorized_client: لا يصرح للعميل بتنفيذ هذا الطلب باستخدام هذه الطريقة. unsupported_grant_type: هذا النوع من منح التصريح غير معتمد في خادم الترخيص. diff --git a/config/locales/doorkeeper.fa.yml b/config/locales/doorkeeper.fa.yml index 6a4be57..f3db862 100644 --- a/config/locales/doorkeeper.fa.yml +++ b/config/locales/doorkeeper.fa.yml @@ -3,19 +3,19 @@ fa: activerecord: attributes: doorkeeper/application: - name: Application name - redirect_uri: Redirect URI - scopes: Scopes - website: Application website + name: نام برنامه + redirect_uri: نشانی تغییرمسیر + scopes: محدوده + website: وبگاه برنامه errors: models: doorkeeper/application: attributes: redirect_uri: - fragment_present: cannot contain a fragment. - invalid_uri: must be a valid URI. - relative_uri: must be an absolute URI. - secured_uri: must be an HTTPS/SSL URI. + fragment_present: نمی‌تواند چندتکه باشد. + invalid_uri: باید یک نشانی معتبر باشد. + relative_uri: باید یک نشانی مطلق باشد. + secured_uri: باید یک نشانی HTTPS/SSL باشد. doorkeeper: applications: buttons: diff --git a/config/locales/doorkeeper.id.yml b/config/locales/doorkeeper.id.yml index 6db797c..0a99b86 100644 --- a/config/locales/doorkeeper.id.yml +++ b/config/locales/doorkeeper.id.yml @@ -35,9 +35,13 @@ id: redirect_uri: Gunakan satu baris per URI scopes: Pisahkan scope dengan spasi. Biarkan kosong jika ingin menggunakan scope default. index: + application: Aplikasi callback_url: URL Callback + delete: Hapus name: Nama new: Aplikasi baru + scopes: Cakupan + show: Tampilkan title: Aplikasi anda new: title: Aplikasi Baru diff --git a/config/locales/doorkeeper.sk.yml b/config/locales/doorkeeper.sk.yml index b8fd281..7a285eb 100644 --- a/config/locales/doorkeeper.sk.yml +++ b/config/locales/doorkeeper.sk.yml @@ -19,7 +19,7 @@ sk: doorkeeper: applications: buttons: - authorize: Autorizovať + authorize: Overiť cancel: Zrušiť destroy: Zničiť edit: Upraviť @@ -54,7 +54,7 @@ sk: title: 'Aplikácia: %{name}' authorizations: buttons: - authorize: Autorizovať + authorize: Overiť deny: Zamietnuť error: title: Nastala chyba diff --git a/config/locales/doorkeeper.zh-TW.yml b/config/locales/doorkeeper.zh-TW.yml index 01e62df..2aa2717 100644 --- a/config/locales/doorkeeper.zh-TW.yml +++ b/config/locales/doorkeeper.zh-TW.yml @@ -5,12 +5,14 @@ zh-TW: doorkeeper/application: name: 名稱 redirect_uri: 重新導向 URI + scopes: 範圍 + website: 應用程式網頁 errors: models: doorkeeper/application: attributes: redirect_uri: - fragment_present: URI 不可包含 "#fragment" 部份 + fragment_present: 不能包含 fragment。 invalid_uri: 必需有正確的 URI. relative_uri: 必需為絕對 URI. secured_uri: 必需使用有 HTTPS/SSL 加密的 URI. @@ -31,11 +33,15 @@ zh-TW: help: native_redirect_uri: 使用 %{native_redirect_uri} 作局部測試 redirect_uri: 每行輸入一個 URI - scopes: 請用半形空格分開權限範圍 (scope)。留空表示使用預設的權限範圍 + scopes: 請用半形空格分開權限範圍 (scope)。留空表示使用預設的權限範圍。 index: + application: 應用程式 callback_url: 回傳網址 + delete: 刪除 name: 名稱 new: 新增應用程式 + scopes: 範圍 + show: 顯示 title: 您的應用程式 new: title: 新增應用程式 @@ -57,7 +63,7 @@ zh-TW: prompt: 應用程式 %{client_name} 要求取得您帳號的部份權限 title: 需要授權 show: - title: Copy this authorization code and paste it to the application. + title: 複製此授權碼並貼上到應用程式中。 authorized_applications: buttons: revoke: 取消授權 @@ -77,7 +83,7 @@ zh-TW: invalid_grant: 授權申請不正確、逾期、已被取消、與授權請求內的重新導向 URI 不符、或屬於別的客戶端程式。 invalid_redirect_uri: 不正確的重新導向網址。 invalid_request: 請求缺少必要的參數、包含不支援的參數、或其他輸入錯誤。 - invalid_resource_owner: 資源擁有者的登入資訊錯誤、或無法找到該資源擁有者。 + invalid_resource_owner: 資源擁有者的登入資訊錯誤、或無法找到該資源擁有者 invalid_scope: 請求的權限範圍不正確、未有定義、或輸入錯誤。 invalid_token: expired: access token 已過期 diff --git a/config/locales/eo.yml b/config/locales/eo.yml index 8bc2aaa..84d63d8 100644 --- a/config/locales/eo.yml +++ b/config/locales/eo.yml @@ -333,7 +333,7 @@ eo: subscriptions: callback_url: Revena URL confirmed: Konfirmita - expires_in: Eksvalidiĝas en + expires_in: Eksvalidiĝas je last_delivery: Lasta livero title: WebSub topic: Temo @@ -485,7 +485,7 @@ eo: max_uses_prompt: Neniu limo prompt: Krei kaj diskonigi ligilojn al aliaj por doni aliron al ĉi tiu nodo table: - expires_at: Eksvalidiĝas + expires_at: Eksvalidiĝas je uses: Uzoj title: Inviti homojn landing_strip_html: "%{name} estas uzanto en %{link_to_root_path}. Vi povas sekvi tiun aŭ interagi kun tiu, se vi havas konton ie ajn en la fediverse." @@ -636,6 +636,15 @@ eo: two_factor_authentication: Dufaktora aŭtentigo your_apps: Viaj aplikaĵoj statuses: + attached: + description: 'Ligita: %{attached}' + image: + one: "%{count} bildo" + other: "%{count} bildoj" + video: + one: "%{count} video" + other: "%{count} videoj" + content_warning: 'Enhava averto: %{warning}' open_in_web: Malfermi retumile over_character_limit: limo de %{max} signoj transpasita pin_errors: diff --git a/config/locales/ja.yml b/config/locales/ja.yml index 1ff3097..090b080 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -641,8 +641,8 @@ ja: one: "%{count} 枚の画像" other: "%{count} 枚の画像" video: - one: "%{count} 枚の動画" - other: "%{count} 枚の動画" + one: "%{count} 本の動画" + other: "%{count} 本の動画" content_warning: '閲覧注意: %{warning}' open_in_web: Webで開く over_character_limit: 上限は %{max}文字までです diff --git a/config/locales/ko.yml b/config/locales/ko.yml index 8a11b09..ba55b35 100644 --- a/config/locales/ko.yml +++ b/config/locales/ko.yml @@ -361,6 +361,7 @@ ko: your_token: 액세스 토큰 auth: agreement_html: 이 등록으로 이용규약약관에 동의하는 것으로 간주됩니다. + change_password: 패스워드 confirm_email: 확인 메일 승인 delete_account: 계정 삭제 delete_account_html: 계정을 삭제하고 싶은 경우, 여기서 삭제할 수 있습니다. 삭제 전 확인 화면이 표시됩니다. @@ -373,7 +374,11 @@ ko: migrate_account_html: 이 계정을 다른 계정으로 리디렉션 하길 원하는 경우 여기에서 설정할 수 있습니다. or: 또는 or_log_in_with: 다른 방법으로 로그인 하려면 + providers: + cas: CAS + saml: SAML register: 등록하기 + register_elsewhere: 다른 인스턴스에서 가입 resend_confirmation: 확인 메일을 다시 보내기 reset_password: 비밀번호 재설정 security: 보안 @@ -545,7 +550,9 @@ ko: trillion: T unit: '' pagination: + newer: 새로운 툿 next: 다음 + older: 오래된 툿 prev: 이전 truncate: "…" preferences: @@ -629,6 +636,15 @@ ko: two_factor_authentication: 2단계 인증 your_apps: 애플리케이션 statuses: + attached: + description: '첨부: %{attached}' + image: + one: "%{count} 이미지" + other: "%{count} 이미지" + video: + one: "%{count} 영상" + other: "%{count} 영상" + content_warning: '열람 주의: %{warning}' open_in_web: Web으로 열기 over_character_limit: 최대 %{max}자까지 입력할 수 있습니다 pin_errors: diff --git a/config/locales/simple_form.ko.yml b/config/locales/simple_form.ko.yml index e49bfd9..85eccf0 100644 --- a/config/locales/simple_form.ko.yml +++ b/config/locales/simple_form.ko.yml @@ -45,6 +45,7 @@ ko: setting_default_privacy: 툿 프라이버시 setting_default_sensitive: 미디어를 언제나 민감한 컨텐츠로 설정 setting_delete_modal: 툿 삭제 전 확인 창을 표시 + setting_display_sensitive_media: 열람주의로 설정 된 이미지도 항상 보여주기 setting_noindex: 검색엔진의 인덱싱을 거절 setting_reduce_motion: 애니메이션 줄이기 setting_system_font_ui: 시스템의 초기 설정 폰트를 사용 @@ -53,6 +54,7 @@ ko: severity: 심각도 type: 불러오기 종류 username: 유저 이름 + username_or_email: 유저네임 또는 이메일 interactions: must_be_follower: 나를 팔로우 하지 않는 사람에게서 온 알림을 차단 must_be_following: 내가 팔로우 하지 않는 사람에게서 온 알림을 차단 diff --git a/config/locales/sk.yml b/config/locales/sk.yml index a0e1a59..2ee25b3 100644 --- a/config/locales/sk.yml +++ b/config/locales/sk.yml @@ -580,7 +580,12 @@ sk: browser: Prehliadač browsers: alipay: Alipay + chrome: Google Chrome + edge: Microsoft Edge + electron: Electron + firefox: Mozilla Firefox generic: Neznámy prehliadač + ie: Internet Explorer current_session: Aktuálna sezóna description: "%{browser} na %{platform}" explanation: Tieto sú prehliadače ktoré sú teraz prihlásené na tvoj Mastodon účet. @@ -607,12 +612,22 @@ sk: two_factor_authentication: Dvoj-faktorové overenie your_apps: Tvoje aplikácie statuses: + attached: + description: 'Priložené: %{attached}' + image: + one: "%{count} obrázok" + other: "%{count} obrázkov" + video: + one: "%{count} video" + other: "%{count} videí" + content_warning: 'Varovanie o obsahu: %{warning}' open_in_web: Otvor v okne prehliadača over_character_limit: limit počtu %{max} znakov bol presiahnutý pin_errors: limit: Už ste si pripli ten najvyšší možný počet príspevkov ownership: Nemožno pripnúť príspevok od niekoho iného private: Neverejné príspevky nemôžu byť pripnuté + reblog: Pozdvihnutie sa nedá pripnúť show_more: Ukáž viac visibilities: private: Iba pre sledovateľov @@ -628,11 +643,20 @@ sk: sensitive_content: Senzitívny obsah terms: title: Podmienky užívania, a pravidlá o súkromí pre %{instance} + themes: + default: Mastodon + time: + formats: + default: "%b %d, %R, %H:%M" two_factor_authentication: + code_hint: Pre potvrdenie teraz zadaj kód vygenerovaný pomocou tvojej overovacej aplikácie + description_html: Ak povolíš dvoj-faktorové overovanie, na prihlásenie potom budeš potrebovať svoj telefón, ktorý vygeneruje prístupové kódy, čo musíš zadať. + disable: Zakáž enable: Povoliť enabled: Dvoj-faktorové overovanie je povolené enabled_success: Dvoj-faktorové overovanie bolo úspešne povolené generate_recovery_codes: Vygeneruj zálohové kódy + instructions_html: "Naskenuj tento QR kód do Google Autentikátora, alebo do podobnej TOTP aplikácie pomocou svojho telefónu. Od tejto chvíle bude táto aplikácia pre teba generovať kódy ktoré musíš zadať aby si sa prihlásil/a." lost_recovery_codes: Zálohové kódy ti umožnia dostať sa k svojmu účtu ak stratíš telefón. Pokiaľ si stratila svoje zálohové kódy, môžeš si ich tu znovu vygenerovať. Tvoje staré zálohové kódy budú zneplatnené. manual_instructions: 'Pokiaľ nemôžeš oskenovať daný QR kód, a potrebuješ ho zadať ručne, tu je tajomstvo v textovom formáte:' recovery_codes: Zálohuj kódy pre obnovu From 4fd71accd419fb79cc75e0ebf30df374d174ebf5 Mon Sep 17 00:00:00 2001 From: Emelia Smith Date: Mon, 2 Apr 2018 13:44:19 +0200 Subject: [PATCH 074/381] Fix issues with sending direct messages from user profile (#6999) * Clear compose textarea when starting a new direct message Previous behaviour resulted in potentially misdirected direct messages. * Hide search when starting to compose a direct message --- app/javascript/mastodon/reducers/compose.js | 2 +- app/javascript/mastodon/reducers/search.js | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/app/javascript/mastodon/reducers/compose.js b/app/javascript/mastodon/reducers/compose.js index a48c469..1f41775 100644 --- a/app/javascript/mastodon/reducers/compose.js +++ b/app/javascript/mastodon/reducers/compose.js @@ -265,7 +265,7 @@ export default function compose(state = initialState, action) { .set('idempotencyKey', uuid()); case COMPOSE_DIRECT: return state - .update('text', text => `${text}@${action.account.get('acct')} `) + .update('text', text => `@${action.account.get('acct')} `) .set('privacy', 'direct') .set('focusDate', new Date()) .set('idempotencyKey', uuid()); diff --git a/app/javascript/mastodon/reducers/search.js b/app/javascript/mastodon/reducers/search.js index 08d90e4..56fd722 100644 --- a/app/javascript/mastodon/reducers/search.js +++ b/app/javascript/mastodon/reducers/search.js @@ -4,7 +4,11 @@ import { SEARCH_FETCH_SUCCESS, SEARCH_SHOW, } from '../actions/search'; -import { COMPOSE_MENTION, COMPOSE_REPLY } from '../actions/compose'; +import { + COMPOSE_MENTION, + COMPOSE_REPLY, + COMPOSE_DIRECT, +} from '../actions/compose'; import { Map as ImmutableMap, List as ImmutableList } from 'immutable'; const initialState = ImmutableMap({ @@ -29,6 +33,7 @@ export default function search(state = initialState, action) { return state.set('hidden', false); case COMPOSE_REPLY: case COMPOSE_MENTION: + case COMPOSE_DIRECT: return state.set('hidden', true); case SEARCH_FETCH_SUCCESS: return state.set('results', ImmutableMap({ From e7a17167015dca6864f31152c47334c3b3a857a2 Mon Sep 17 00:00:00 2001 From: Emelia Smith Date: Mon, 2 Apr 2018 13:45:07 +0200 Subject: [PATCH 075/381] Implement the ability for an Administrator or Moderator to remove an account avatar (#6998) --- app/controllers/admin/accounts_controller.rb | 13 ++++++++++++- app/helpers/admin/action_logs_helper.rb | 2 +- app/policies/account_policy.rb | 4 ++++ app/views/admin/accounts/show.html.haml | 8 ++++++++ config/locales/en.yml | 3 +++ config/routes.rb | 1 + 6 files changed, 29 insertions(+), 2 deletions(-) diff --git a/app/controllers/admin/accounts_controller.rb b/app/controllers/admin/accounts_controller.rb index 7428c3f..e7ca6b9 100644 --- a/app/controllers/admin/accounts_controller.rb +++ b/app/controllers/admin/accounts_controller.rb @@ -2,7 +2,7 @@ module Admin class AccountsController < BaseController - before_action :set_account, only: [:show, :subscribe, :unsubscribe, :redownload, :enable, :disable, :memorialize] + before_action :set_account, only: [:show, :subscribe, :unsubscribe, :redownload, :remove_avatar, :enable, :disable, :memorialize] before_action :require_remote_account!, only: [:subscribe, :unsubscribe, :redownload] before_action :require_local_account!, only: [:enable, :disable, :memorialize] @@ -60,6 +60,17 @@ module Admin redirect_to admin_account_path(@account.id) end + def remove_avatar + authorize @account, :remove_avatar? + + @account.avatar = nil + @account.save! + + log_action :remove_avatar, @account.user + + redirect_to admin_account_path(@account.id) + end + private def set_account diff --git a/app/helpers/admin/action_logs_helper.rb b/app/helpers/admin/action_logs_helper.rb index 4475034..78278c7 100644 --- a/app/helpers/admin/action_logs_helper.rb +++ b/app/helpers/admin/action_logs_helper.rb @@ -86,7 +86,7 @@ module Admin::ActionLogsHelper opposite_verbs?(log) ? 'negative' : 'positive' when :update, :reset_password, :disable_2fa, :memorialize 'neutral' - when :demote, :silence, :disable, :suspend + when :demote, :silence, :disable, :suspend, :remove_avatar 'negative' when :destroy opposite_verbs?(log) ? 'positive' : 'negative' diff --git a/app/policies/account_policy.rb b/app/policies/account_policy.rb index 85e2c84..efabe80 100644 --- a/app/policies/account_policy.rb +++ b/app/policies/account_policy.rb @@ -29,6 +29,10 @@ class AccountPolicy < ApplicationPolicy admin? end + def remove_avatar? + staff? + end + def subscribe? admin? end diff --git a/app/views/admin/accounts/show.html.haml b/app/views/admin/accounts/show.html.haml index dbbf5fc..fecfd6c 100644 --- a/app/views/admin/accounts/show.html.haml +++ b/app/views/admin/accounts/show.html.haml @@ -14,6 +14,14 @@ %th= t('admin.accounts.display_name') %td= @account.display_name + %tr + %th= t('admin.accounts.avatar') + %th + = link_to @account.avatar.url(:original) do + = image_tag @account.avatar.url(:original), alt: '', width: 40, height: 40, class: 'avatar' + - if @account.local? && @account.avatar? + = table_link_to 'trash', t('admin.accounts.remove_avatar'), remove_avatar_admin_account_path(@account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') } if can?(:remove_avatar, @account) + - if @account.local? %tr %th= t('admin.accounts.role') diff --git a/config/locales/en.yml b/config/locales/en.yml index e3d7697..fb2bbf4 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -60,6 +60,7 @@ en: destroyed_msg: Moderation note successfully destroyed! accounts: are_you_sure: Are you sure? + avatar: Avatar by_domain: Domain confirm: Confirm confirmed: Confirmed @@ -108,6 +109,7 @@ en: public: Public push_subscription_expires: PuSH subscription expires redownload: Refresh avatar + remove_avatar: Remove avatar reset: Reset reset_password: Reset password resubscribe: Resubscribe @@ -150,6 +152,7 @@ en: enable_user: "%{name} enabled login for user %{target}" memorialize_account: "%{name} turned %{target}'s account into a memoriam page" promote_user: "%{name} promoted user %{target}" + remove_avatar_user: "%{name} removed %{target}'s avatar" reset_password_user: "%{name} reset password of user %{target}" resolve_report: "%{name} dismissed report %{target}" silence_account: "%{name} silenced %{target}'s account" diff --git a/config/routes.rb b/config/routes.rb index 0542cb6..9a44605 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -144,6 +144,7 @@ Rails.application.routes.draw do post :enable post :disable post :redownload + post :remove_avatar post :memorialize end From 2c51bc0ca5a4c3a4bb140b4b40dabdda859ebb94 Mon Sep 17 00:00:00 2001 From: unarist Date: Mon, 2 Apr 2018 21:51:02 +0900 Subject: [PATCH 076/381] Add missing rejection handling for Promises (#7008) * Add eslint-plugin-promise to detect uncaught rejections * Move alert generation for errors to actions/alert * Add missing rejection handling for Promises * Use catch() instead of onReject on then() Then it will catches rejection from onFulfilled. This detection can be disabled by `allowThen` option, though. --- .eslintrc.yml | 3 +++ app/javascript/mastodon/actions/accounts.js | 2 +- app/javascript/mastodon/actions/alerts.js | 25 ++++++++++++++++++ app/javascript/mastodon/actions/compose.js | 7 ++++- app/javascript/mastodon/actions/lists.js | 3 ++- .../actions/push_notifications/registerer.js | 15 +++++------ app/javascript/mastodon/actions/settings.js | 5 +++- .../mastodon/containers/status_container.js | 6 ++++- .../mastodon/features/ui/components/embed_modal.js | 3 +++ app/javascript/mastodon/middleware/errors.js | 24 ++--------------- app/javascript/mastodon/storage/modifier.js | 30 ++++++++++++++++------ package.json | 1 + yarn.lock | 4 +++ 13 files changed, 84 insertions(+), 44 deletions(-) diff --git a/.eslintrc.yml b/.eslintrc.yml index cf276a1..576e5b7 100644 --- a/.eslintrc.yml +++ b/.eslintrc.yml @@ -13,6 +13,7 @@ plugins: - react - jsx-a11y - import +- promise parserOptions: sourceType: module @@ -152,3 +153,5 @@ rules: - "app/javascript/**/__tests__/**" import/no-unresolved: error import/no-webpack-loader-syntax: error + + promise/catch-or-return: error diff --git a/app/javascript/mastodon/actions/accounts.js b/app/javascript/mastodon/actions/accounts.js index 7cacff9..28ae567 100644 --- a/app/javascript/mastodon/actions/accounts.js +++ b/app/javascript/mastodon/actions/accounts.js @@ -103,7 +103,7 @@ export function fetchAccount(id) { dispatch(importFetchedAccount(response.data)); })).then(() => { dispatch(fetchAccountSuccess()); - }, error => { + }).catch(error => { dispatch(fetchAccountFail(id, error)); }); }; diff --git a/app/javascript/mastodon/actions/alerts.js b/app/javascript/mastodon/actions/alerts.js index f37fdee..3f5d7ef 100644 --- a/app/javascript/mastodon/actions/alerts.js +++ b/app/javascript/mastodon/actions/alerts.js @@ -1,3 +1,10 @@ +import { defineMessages } from 'react-intl'; + +const messages = defineMessages({ + unexpectedTitle: { id: 'alert.unexpected.title', defaultMessage: 'Oops!' }, + unexpectedMessage: { id: 'alert.unexpected.message', defaultMessage: 'An unexpected error occurred.' }, +}); + export const ALERT_SHOW = 'ALERT_SHOW'; export const ALERT_DISMISS = 'ALERT_DISMISS'; export const ALERT_CLEAR = 'ALERT_CLEAR'; @@ -22,3 +29,21 @@ export function showAlert(title, message) { message, }; }; + +export function showAlertForError(error) { + if (error.response) { + const { data, status, statusText } = error.response; + + let message = statusText; + let title = `${status}`; + + if (data.error) { + message = data.error; + } + + return showAlert(title, message); + } else { + console.error(error); + return showAlert(messages.unexpectedTitle, messages.unexpectedMessage); + } +} diff --git a/app/javascript/mastodon/actions/compose.js b/app/javascript/mastodon/actions/compose.js index 2138f94..59aa6f9 100644 --- a/app/javascript/mastodon/actions/compose.js +++ b/app/javascript/mastodon/actions/compose.js @@ -1,11 +1,12 @@ import api from '../api'; -import { CancelToken } from 'axios'; +import { CancelToken, isCancel } from 'axios'; import { throttle } from 'lodash'; import { search as emojiSearch } from '../features/emoji/emoji_mart_search_light'; import { tagHistory } from '../settings'; import { useEmoji } from './emojis'; import { importFetchedAccounts } from './importer'; import { updateTimeline } from './timelines'; +import { showAlertForError } from './alerts'; let cancelFetchComposeSuggestionsAccounts; @@ -291,6 +292,10 @@ const fetchComposeSuggestionsAccounts = throttle((dispatch, getState, token) => }).then(response => { dispatch(importFetchedAccounts(response.data)); dispatch(readyComposeSuggestionsAccounts(token, response.data)); + }).catch(error => { + if (!isCancel(error)) { + dispatch(showAlertForError(error)); + } }); }, 200, { leading: true, trailing: true }); diff --git a/app/javascript/mastodon/actions/lists.js b/app/javascript/mastodon/actions/lists.js index 12d60e3..12cb171 100644 --- a/app/javascript/mastodon/actions/lists.js +++ b/app/javascript/mastodon/actions/lists.js @@ -1,5 +1,6 @@ import api from '../api'; import { importFetchedAccounts } from './importer'; +import { showAlertForError } from './alerts'; export const LIST_FETCH_REQUEST = 'LIST_FETCH_REQUEST'; export const LIST_FETCH_SUCCESS = 'LIST_FETCH_SUCCESS'; @@ -236,7 +237,7 @@ export const fetchListSuggestions = q => (dispatch, getState) => { api(getState).get('/api/v1/accounts/search', { params }).then(({ data }) => { dispatch(importFetchedAccounts(data)); dispatch(fetchListSuggestionsReady(q, data)); - }); + }).catch(error => dispatch(showAlertForError(error))); }; export const fetchListSuggestionsReady = (query, accounts) => ({ diff --git a/app/javascript/mastodon/actions/push_notifications/registerer.js b/app/javascript/mastodon/actions/push_notifications/registerer.js index 51e68ca..f17d929 100644 --- a/app/javascript/mastodon/actions/push_notifications/registerer.js +++ b/app/javascript/mastodon/actions/push_notifications/registerer.js @@ -116,14 +116,11 @@ export function register () { pushNotificationsSetting.remove(me); } - try { - getRegistration() - .then(getPushSubscription) - .then(unsubscribe); - } catch (e) { - - } - }); + return getRegistration() + .then(getPushSubscription) + .then(unsubscribe); + }) + .catch(console.warn); } else { console.warn('Your browser does not support Web Push Notifications.'); } @@ -143,6 +140,6 @@ export function saveSettings() { if (me) { pushNotificationsSetting.set(me, data); } - }); + }).catch(console.warn); }; } diff --git a/app/javascript/mastodon/actions/settings.js b/app/javascript/mastodon/actions/settings.js index b96383d..5634a11 100644 --- a/app/javascript/mastodon/actions/settings.js +++ b/app/javascript/mastodon/actions/settings.js @@ -1,5 +1,6 @@ import api from '../api'; import { debounce } from 'lodash'; +import { showAlertForError } from './alerts'; export const SETTING_CHANGE = 'SETTING_CHANGE'; export const SETTING_SAVE = 'SETTING_SAVE'; @@ -23,7 +24,9 @@ const debouncedSave = debounce((dispatch, getState) => { const data = getState().get('settings').filter((_, path) => path !== 'saved').toJS(); - api(getState).put('/api/web/settings', { data }).then(() => dispatch({ type: SETTING_SAVE })); + api(getState).put('/api/web/settings', { data }) + .then(() => dispatch({ type: SETTING_SAVE })) + .catch(error => dispatch(showAlertForError(error))); }, 5000, { trailing: true }); export function saveSettings() { diff --git a/app/javascript/mastodon/containers/status_container.js b/app/javascript/mastodon/containers/status_container.js index 8ba1015..4579bd1 100644 --- a/app/javascript/mastodon/containers/status_container.js +++ b/app/javascript/mastodon/containers/status_container.js @@ -27,6 +27,7 @@ import { initReport } from '../actions/reports'; import { openModal } from '../actions/modal'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import { boostModal, deleteModal } from '../initial_state'; +import { showAlertForError } from '../actions/alerts'; const messages = defineMessages({ deleteConfirm: { id: 'confirmations.delete.confirm', defaultMessage: 'Delete' }, @@ -83,7 +84,10 @@ const mapDispatchToProps = (dispatch, { intl }) => ({ }, onEmbed (status) { - dispatch(openModal('EMBED', { url: status.get('url') })); + dispatch(openModal('EMBED', { + url: status.get('url'), + onError: error => dispatch(showAlertForError(error)), + })); }, onDelete (status) { diff --git a/app/javascript/mastodon/features/ui/components/embed_modal.js b/app/javascript/mastodon/features/ui/components/embed_modal.js index d440a88..52aab00 100644 --- a/app/javascript/mastodon/features/ui/components/embed_modal.js +++ b/app/javascript/mastodon/features/ui/components/embed_modal.js @@ -10,6 +10,7 @@ export default class EmbedModal extends ImmutablePureComponent { static propTypes = { url: PropTypes.string.isRequired, onClose: PropTypes.func.isRequired, + onError: PropTypes.func.isRequired, intl: PropTypes.object.isRequired, } @@ -35,6 +36,8 @@ export default class EmbedModal extends ImmutablePureComponent { iframeDocument.body.style.margin = 0; this.iframe.width = iframeDocument.body.scrollWidth; this.iframe.height = iframeDocument.body.scrollHeight; + }).catch(error => { + this.props.onError(error); }); } diff --git a/app/javascript/mastodon/middleware/errors.js b/app/javascript/mastodon/middleware/errors.js index 72e5631..3cebb42 100644 --- a/app/javascript/mastodon/middleware/errors.js +++ b/app/javascript/mastodon/middleware/errors.js @@ -1,34 +1,14 @@ -import { defineMessages } from 'react-intl'; -import { showAlert } from '../actions/alerts'; +import { showAlertForError } from '../actions/alerts'; const defaultFailSuffix = 'FAIL'; -const messages = defineMessages({ - unexpectedTitle: { id: 'alert.unexpected.title', defaultMessage: 'Oops!' }, - unexpectedMessage: { id: 'alert.unexpected.message', defaultMessage: 'An unexpected error occurred.' }, -}); - export default function errorsMiddleware() { return ({ dispatch }) => next => action => { if (action.type && !action.skipAlert) { const isFail = new RegExp(`${defaultFailSuffix}$`, 'g'); if (action.type.match(isFail)) { - if (action.error.response) { - const { data, status, statusText } = action.error.response; - - let message = statusText; - let title = `${status}`; - - if (data.error) { - message = data.error; - } - - dispatch(showAlert(title, message)); - } else { - console.error(action.error); - dispatch(showAlert(messages.unexpectedTitle, messages.unexpectedMessage)); - } + dispatch(showAlertForError(action.error)); } } diff --git a/app/javascript/mastodon/storage/modifier.js b/app/javascript/mastodon/storage/modifier.js index 1bec04d..4773d07 100644 --- a/app/javascript/mastodon/storage/modifier.js +++ b/app/javascript/mastodon/storage/modifier.js @@ -9,6 +9,12 @@ const limit = 1024; // https://webkit.org/status/#specification-service-workers const asyncCache = window.caches ? caches.open('mastodon-system') : Promise.reject(); +function printErrorIfAvailable(error) { + if (error) { + console.warn(error); + } +} + function put(name, objects, onupdate, oncreate) { return asyncDB.then(db => new Promise((resolve, reject) => { const putTransaction = db.transaction(name, 'readwrite'); @@ -77,7 +83,9 @@ function evictAccountsByRecords(records) { function evict(toEvict) { toEvict.forEach(record => { - asyncCache.then(cache => accountAssetKeys.forEach(key => cache.delete(records[key]))); + asyncCache + .then(cache => accountAssetKeys.forEach(key => cache.delete(records[key]))) + .catch(printErrorIfAvailable); accountsMovedIndex.getAll(record.id).onsuccess = ({ target }) => evict(target.result); @@ -90,11 +98,11 @@ function evictAccountsByRecords(records) { } evict(records); - }); + }).catch(printErrorIfAvailable); } export function evictStatus(id) { - return evictStatuses([id]); + evictStatuses([id]); } export function evictStatuses(ids) { @@ -110,7 +118,7 @@ export function evictStatuses(ids) { idIndex.getKey(id).onsuccess = ({ target }) => target.result && store.delete(target.result); }); - }); + }).catch(printErrorIfAvailable); } function evictStatusesByRecords(records) { @@ -127,7 +135,9 @@ export function putAccounts(records) { const oldURL = target.result[key]; if (newURL !== oldURL) { - asyncCache.then(cache => cache.delete(oldURL)); + asyncCache + .then(cache => cache.delete(oldURL)) + .catch(printErrorIfAvailable); } }); @@ -145,10 +155,14 @@ export function putAccounts(records) { oncomplete(); }).then(records => { evictAccountsByRecords(records); - asyncCache.then(cache => cache.addAll(newURLs)); - }); + asyncCache + .then(cache => cache.addAll(newURLs)) + .catch(printErrorIfAvailable); + }).catch(printErrorIfAvailable); } export function putStatuses(records) { - put('statuses', records).then(evictStatusesByRecords); + put('statuses', records) + .then(evictStatusesByRecords) + .catch(printErrorIfAvailable); } diff --git a/package.json b/package.json index dfba49a..d4de3a1 100644 --- a/package.json +++ b/package.json @@ -129,6 +129,7 @@ "eslint": "^4.15.0", "eslint-plugin-import": "^2.8.0", "eslint-plugin-jsx-a11y": "^5.1.1", + "eslint-plugin-promise": "^3.7.0", "eslint-plugin-react": "^7.5.1", "jest": "^21.2.1", "raf": "^3.4.0", diff --git a/yarn.lock b/yarn.lock index a306ebf..866b24c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2433,6 +2433,10 @@ eslint-plugin-jsx-a11y@^5.1.1: emoji-regex "^6.1.0" jsx-ast-utils "^1.4.0" +eslint-plugin-promise@^3.7.0: + version "3.7.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-promise/-/eslint-plugin-promise-3.7.0.tgz#f4bde5c2c77cdd69557a8f69a24d1ad3cfc9e67e" + eslint-plugin-react@^7.5.1: version "7.5.1" resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.5.1.tgz#52e56e8d80c810de158859ef07b880d2f56ee30b" From 36eac8ba9011f225f7f949bbf1ca173832561f10 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 2 Apr 2018 19:19:51 +0200 Subject: [PATCH 077/381] Do not default SMTP verify mode to "peer", default to "none" (#6996) --- lib/tasks/mastodon.rake | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/lib/tasks/mastodon.rake b/lib/tasks/mastodon.rake index cfd6a1d..505c7e0 100644 --- a/lib/tasks/mastodon.rake +++ b/lib/tasks/mastodon.rake @@ -256,11 +256,7 @@ namespace :mastodon do q.modify :strip end - env['SMTP_OPENSSL_VERIFY_MODE'] = prompt.ask('SMTP OpenSSL verify mode:') do |q| - q.required - q.default 'peer' - q.modify :strip - end + env['SMTP_OPENSSL_VERIFY_MODE'] = prompt.select('SMTP OpenSSL verify mode:', %w(none peer client_once fail_if_no_peer_cert)) end env['SMTP_FROM_ADDRESS'] = prompt.ask('E-mail address to send e-mails "from":') do |q| From e85cffb2362f914c0f2f7ced4112430b30bc7997 Mon Sep 17 00:00:00 2001 From: Emelia Smith Date: Mon, 2 Apr 2018 22:04:14 +0200 Subject: [PATCH 078/381] Feature: Report improvements (#6967) (#7000) * Implement Assignment of Reports (#6967) * Change translation of admin.report.comment.label to "Report Comment" for clarity As we'll soon add the ability for reports to have comments on them, this clarification makes sense. * Implement notes for Reports This enables moderators to leave comments about a report whilst they work on it * Fix display of report moderation notes * Allow reports to be reopened / marked as unresolved * Redirect to reports listing upon resolution of report * Implement "resolve with note" functionality * Add inverse relationship for report notes * Remove additional database querying when loading report notes * Fix tests for reports * Fix localisations for report notes / reports --- app/controllers/admin/report_notes_controller.rb | 49 +++++++++++++ app/controllers/admin/reports_controller.rb | 20 +++++- app/helpers/admin/action_logs_helper.rb | 2 +- app/models/account.rb | 2 + app/models/report.rb | 4 ++ app/models/report_note.rb | 21 ++++++ app/policies/report_note_policy.rb | 17 +++++ .../admin/report_notes/_report_note.html.haml | 11 +++ app/views/admin/reports/_report.html.haml | 5 ++ app/views/admin/reports/index.html.haml | 1 + app/views/admin/reports/show.html.haml | 84 +++++++++++++++++----- config/locales/en.yml | 22 +++++- config/routes.rb | 2 + ...402031200_add_assigned_account_id_to_reports.rb | 5 ++ db/migrate/20180402040909_create_report_notes.rb | 14 ++++ db/schema.rb | 16 ++++- spec/controllers/admin/reports_controller_spec.rb | 40 ++++++++++- 17 files changed, 290 insertions(+), 25 deletions(-) create mode 100644 app/controllers/admin/report_notes_controller.rb create mode 100644 app/models/report_note.rb create mode 100644 app/policies/report_note_policy.rb create mode 100644 app/views/admin/report_notes/_report_note.html.haml create mode 100644 db/migrate/20180402031200_add_assigned_account_id_to_reports.rb create mode 100644 db/migrate/20180402040909_create_report_notes.rb diff --git a/app/controllers/admin/report_notes_controller.rb b/app/controllers/admin/report_notes_controller.rb new file mode 100644 index 0000000..ef8c0f4 --- /dev/null +++ b/app/controllers/admin/report_notes_controller.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +module Admin + class ReportNotesController < BaseController + before_action :set_report_note, only: [:destroy] + + def create + authorize ReportNote, :create? + + @report_note = current_account.report_notes.new(resource_params) + + if @report_note.save + if params[:create_and_resolve] + @report_note.report.update!(action_taken: true, action_taken_by_account_id: current_account.id) + log_action :resolve, @report_note.report + + redirect_to admin_reports_path, notice: I18n.t('admin.reports.resolved_msg') + else + redirect_to admin_report_path(@report_note.report_id), notice: I18n.t('admin.report_notes.created_msg') + end + else + @report = @report_note.report + @report_notes = @report.notes.latest + @form = Form::StatusBatch.new + + render template: 'admin/reports/show' + end + end + + def destroy + authorize @report_note, :destroy? + @report_note.destroy! + redirect_to admin_report_path(@report_note.report_id), notice: I18n.t('admin.report_notes.destroyed_msg') + end + + private + + def resource_params + params.require(:report_note).permit( + :content, + :report_id + ) + end + + def set_report_note + @report_note = ReportNote.find(params[:id]) + end + end +end diff --git a/app/controllers/admin/reports_controller.rb b/app/controllers/admin/reports_controller.rb index 75db6b7..fc3785e 100644 --- a/app/controllers/admin/reports_controller.rb +++ b/app/controllers/admin/reports_controller.rb @@ -11,19 +11,35 @@ module Admin def show authorize @report, :show? + @report_note = @report.notes.new + @report_notes = @report.notes.latest @form = Form::StatusBatch.new end def update authorize @report, :update? process_report - redirect_to admin_report_path(@report) + + if @report.action_taken? + redirect_to admin_reports_path, notice: I18n.t('admin.reports.resolved_msg') + else + redirect_to admin_report_path(@report) + end end private def process_report case params[:outcome].to_s + when 'assign_to_self' + @report.update!(assigned_account_id: current_account.id) + log_action :assigned_to_self, @report + when 'unassign' + @report.update!(assigned_account_id: nil) + log_action :unassigned, @report + when 'reopen' + @report.update!(action_taken: false, action_taken_by_account_id: nil) + log_action :reopen, @report when 'resolve' @report.update!(action_taken_by_current_attributes) log_action :resolve, @report @@ -32,11 +48,13 @@ module Admin log_action :resolve, @report log_action :suspend, @report.target_account resolve_all_target_account_reports + @report.reload when 'silence' @report.target_account.update!(silenced: true) log_action :resolve, @report log_action :silence, @report.target_account resolve_all_target_account_reports + @report.reload else raise ActiveRecord::RecordNotFound end diff --git a/app/helpers/admin/action_logs_helper.rb b/app/helpers/admin/action_logs_helper.rb index 78278c7..7c26c0b 100644 --- a/app/helpers/admin/action_logs_helper.rb +++ b/app/helpers/admin/action_logs_helper.rb @@ -86,7 +86,7 @@ module Admin::ActionLogsHelper opposite_verbs?(log) ? 'negative' : 'positive' when :update, :reset_password, :disable_2fa, :memorialize 'neutral' - when :demote, :silence, :disable, :suspend, :remove_avatar + when :demote, :silence, :disable, :suspend, :remove_avatar, :reopen 'negative' when :destroy opposite_verbs?(log) ? 'positive' : 'negative' diff --git a/app/models/account.rb b/app/models/account.rb index a34b6a2..446144a 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -95,6 +95,8 @@ class Account < ApplicationRecord has_many :reports has_many :targeted_reports, class_name: 'Report', foreign_key: :target_account_id + has_many :report_notes, dependent: :destroy + # Moderation notes has_many :account_moderation_notes, dependent: :destroy has_many :targeted_moderation_notes, class_name: 'AccountModerationNote', foreign_key: :target_account_id, dependent: :destroy diff --git a/app/models/report.rb b/app/models/report.rb index dd123fc..f5b37cb 100644 --- a/app/models/report.rb +++ b/app/models/report.rb @@ -12,12 +12,16 @@ # account_id :integer not null # action_taken_by_account_id :integer # target_account_id :integer not null +# assigned_account_id :integer # class Report < ApplicationRecord belongs_to :account belongs_to :target_account, class_name: 'Account' belongs_to :action_taken_by_account, class_name: 'Account', optional: true + belongs_to :assigned_account, class_name: 'Account', optional: true + + has_many :notes, class_name: 'ReportNote', foreign_key: :report_id, inverse_of: :report, dependent: :destroy scope :unresolved, -> { where(action_taken: false) } scope :resolved, -> { where(action_taken: true) } diff --git a/app/models/report_note.rb b/app/models/report_note.rb new file mode 100644 index 0000000..3d12cf7 --- /dev/null +++ b/app/models/report_note.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true +# == Schema Information +# +# Table name: report_notes +# +# id :integer not null, primary key +# content :text not null +# report_id :integer not null +# account_id :integer not null +# created_at :datetime not null +# updated_at :datetime not null +# + +class ReportNote < ApplicationRecord + belongs_to :account + belongs_to :report, inverse_of: :notes + + scope :latest, -> { reorder('created_at ASC') } + + validates :content, presence: true, length: { maximum: 500 } +end diff --git a/app/policies/report_note_policy.rb b/app/policies/report_note_policy.rb new file mode 100644 index 0000000..694bc09 --- /dev/null +++ b/app/policies/report_note_policy.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +class ReportNotePolicy < ApplicationPolicy + def create? + staff? + end + + def destroy? + admin? || owner? + end + + private + + def owner? + record.account_id == current_account&.id + end +end diff --git a/app/views/admin/report_notes/_report_note.html.haml b/app/views/admin/report_notes/_report_note.html.haml new file mode 100644 index 0000000..60ac5d0 --- /dev/null +++ b/app/views/admin/report_notes/_report_note.html.haml @@ -0,0 +1,11 @@ +%tr + %td + %p + %strong= report_note.account.acct + on + %time.formatted{ datetime: report_note.created_at.iso8601, title: l(report_note.created_at) } + = l report_note.created_at + = table_link_to 'trash', t('admin.reports.notes.delete'), admin_report_note_path(report_note), method: :delete if can?(:destroy, report_note) + %br/ + %br/ + = simple_format(h(report_note.content)) diff --git a/app/views/admin/reports/_report.html.haml b/app/views/admin/reports/_report.html.haml index d5eb161..d266f48 100644 --- a/app/views/admin/reports/_report.html.haml +++ b/app/views/admin/reports/_report.html.haml @@ -18,4 +18,9 @@ = fa_icon('camera') = report.media_attachments.count %td + - if report.assigned_account.nil? + \- + - else + = link_to report.assigned_account.acct, admin_account_path(report.assigned_account.id) + %td = table_link_to 'circle', t('admin.reports.view'), admin_report_path(report) diff --git a/app/views/admin/reports/index.html.haml b/app/views/admin/reports/index.html.haml index 577c68a..3b127c4 100644 --- a/app/views/admin/reports/index.html.haml +++ b/app/views/admin/reports/index.html.haml @@ -20,6 +20,7 @@ %th= t('admin.reports.reported_by') %th= t('admin.reports.comment.label') %th= t('admin.reports.report_contents') + %th= t('admin.reports.assigned') %th %tbody = render @reports diff --git a/app/views/admin/reports/show.html.haml b/app/views/admin/reports/show.html.haml index 5747cc2..e7634a0 100644 --- a/app/views/admin/reports/show.html.haml +++ b/app/views/admin/reports/show.html.haml @@ -4,24 +4,68 @@ - content_for :page_title do = t('admin.reports.report', id: @report.id) +%div{ style: 'overflow: hidden; margin-bottom: 20px' } + - if !@report.action_taken? + %div{ style: 'float: right' } + = link_to t('admin.reports.silence_account'), admin_report_path(@report, outcome: 'silence'), method: :put, class: 'button' + = link_to t('admin.reports.suspend_account'), admin_report_path(@report, outcome: 'suspend'), method: :put, class: 'button' + %div{ style: 'float: left' } + = link_to t('admin.reports.mark_as_resolved'), admin_report_path(@report, outcome: 'resolve'), method: :put, class: 'button' + - else + = link_to t('admin.reports.mark_as_unresolved'), admin_report_path(@report, outcome: 'reopen'), method: :put, class: 'button' + +.table-wrapper + %table.table.inline-table + %tbody + %tr + %th= t('admin.reports.updated_at') + %td{colspan: 2} + %time.formatted{ datetime: @report.updated_at.iso8601 } + %tr + %th= t('admin.reports.status') + %td{colspan: 2} + - if @report.action_taken? + = t('admin.reports.resolved') + = table_link_to 'envelope-open', t('admin.reports.reopen'), admin_report_path(@report, outcome: 'reopen'), method: :put + - else + = t('admin.reports.unresolved') + - if !@report.action_taken_by_account.nil? + %tr + %th= t('admin.reports.action_taken_by') + %td= @report.action_taken_by_account.acct + - else + %tr + %th= t('admin.reports.assigned') + %td + - if @report.assigned_account.nil? + \- + - else + = link_to @report.assigned_account.acct, admin_account_path(@report.assigned_account.id) + %td{style: "text-align: right"} + - if @report.assigned_account != current_user.account + = table_link_to 'user', t('admin.reports.assign_to_self'), admin_report_path(@report, outcome: 'assign_to_self'), method: :put + - if !@report.assigned_account.nil? + = table_link_to 'trash', t('admin.reports.unassign'), admin_report_path(@report, outcome: 'unassign'), method: :put + .report-accounts .report-accounts__item - %strong= t('admin.reports.reported_account') + %h3= t('admin.reports.reported_account') = render 'authorize_follows/card', account: @report.target_account, admin: true = render 'admin/accounts/card', account: @report.target_account .report-accounts__item - %strong= t('admin.reports.reported_by') + %h3= t('admin.reports.reported_by') = render 'authorize_follows/card', account: @report.account, admin: true = render 'admin/accounts/card', account: @report.account -%p - %strong= t('admin.reports.comment.label') - \: - = simple_format(@report.comment.presence || t('admin.reports.comment.none')) +%h3= t('admin.reports.comment.label') + += simple_format(@report.comment.presence || t('admin.reports.comment.none')) - unless @report.statuses.empty? %hr/ + %h3= t('admin.reports.statuses') + = form_for(@form, url: admin_report_reported_statuses_path(@report.id)) do |f| .batch-form-box .batch-checkbox-all @@ -46,14 +90,20 @@ %hr/ -- if !@report.action_taken? - %div{ style: 'overflow: hidden' } - %div{ style: 'float: right' } - = link_to t('admin.reports.silence_account'), admin_report_path(@report, outcome: 'silence'), method: :put, class: 'button' - = link_to t('admin.reports.suspend_account'), admin_report_path(@report, outcome: 'suspend'), method: :put, class: 'button' - %div{ style: 'float: left' } - = link_to t('admin.reports.mark_as_resolved'), admin_report_path(@report, outcome: 'resolve'), method: :put, class: 'button' -- elsif !@report.action_taken_by_account.nil? - %p - %strong #{t('admin.reports.action_taken_by')}: - = @report.action_taken_by_account.acct +%h3= t('admin.reports.notes.label') + +- if @report_notes.length > 0 + .table-wrapper + %table.table + %thead + %tr + %th + %tbody + = render @report_notes + += simple_form_for @report_note, url: admin_report_notes_path do |f| + = render 'shared/error_messages', object: @report_note + = f.input :content + = f.hidden_field :report_id + = f.button :button, t('admin.reports.notes.create'), type: :submit + = f.button :button, t('admin.reports.notes.create_and_resolve'), type: :submit, name: :create_and_resolve diff --git a/config/locales/en.yml b/config/locales/en.yml index fb2bbf4..51d9c90 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -137,6 +137,7 @@ en: web: Web action_logs: actions: + assigned_to_self_report: "%{name} assigned report %{target} to themselves" confirm_user: "%{name} confirmed e-mail address of user %{target}" create_custom_emoji: "%{name} uploaded new emoji %{target}" create_domain_block: "%{name} blocked domain %{target}" @@ -153,10 +154,12 @@ en: memorialize_account: "%{name} turned %{target}'s account into a memoriam page" promote_user: "%{name} promoted user %{target}" remove_avatar_user: "%{name} removed %{target}'s avatar" + reopen_report: "%{name} reopened report %{target}" reset_password_user: "%{name} reset password of user %{target}" - resolve_report: "%{name} dismissed report %{target}" + resolve_report: "%{name} resolved report %{target}" silence_account: "%{name} silenced %{target}'s account" suspend_account: "%{name} suspended %{target}'s account" + unassigned_report: "%{name} unassigned report %{target}" unsilence_account: "%{name} unsilenced %{target}'s account" unsuspend_account: "%{name} unsuspended %{target}'s account" update_custom_emoji: "%{name} updated emoji %{target}" @@ -242,15 +245,26 @@ en: expired: Expired title: Filter title: Invites + report_notes: + created_msg: Moderation note successfully created! + destroyed_msg: Moderation note successfully destroyed! reports: action_taken_by: Action taken by are_you_sure: Are you sure? + assign_to_self: Assign to me + assigned: Assigned Moderator comment: - label: Comment + label: Report Comment none: None delete: Delete id: ID mark_as_resolved: Mark as resolved + mark_as_unresolved: Mark as unresolved + notes: + create: Add Note + create_and_resolve: Resolve with Note + delete: Delete + label: Notes nsfw: 'false': Unhide media attachments 'true': Hide media attachments @@ -259,12 +273,16 @@ en: reported_account: Reported account reported_by: Reported by resolved: Resolved + resolved_msg: Report successfully resolved! silence_account: Silence account status: Status + statuses: Reported Toots suspend_account: Suspend account target: Target title: Reports + unassign: Unassign unresolved: Unresolved + updated_at: Updated view: View settings: activity_api_enabled: diff --git a/config/routes.rb b/config/routes.rb index 9a44605..4b5ba5c 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -137,6 +137,8 @@ Rails.application.routes.draw do resources :reported_statuses, only: [:create, :update, :destroy] end + resources :report_notes, only: [:create, :destroy] + resources :accounts, only: [:index, :show] do member do post :subscribe diff --git a/db/migrate/20180402031200_add_assigned_account_id_to_reports.rb b/db/migrate/20180402031200_add_assigned_account_id_to_reports.rb new file mode 100644 index 0000000..0456839 --- /dev/null +++ b/db/migrate/20180402031200_add_assigned_account_id_to_reports.rb @@ -0,0 +1,5 @@ +class AddAssignedAccountIdToReports < ActiveRecord::Migration[5.1] + def change + add_reference :reports, :assigned_account, null: true, default: nil, foreign_key: { on_delete: :nullify, to_table: :accounts }, index: false + end +end diff --git a/db/migrate/20180402040909_create_report_notes.rb b/db/migrate/20180402040909_create_report_notes.rb new file mode 100644 index 0000000..732ddf8 --- /dev/null +++ b/db/migrate/20180402040909_create_report_notes.rb @@ -0,0 +1,14 @@ +class CreateReportNotes < ActiveRecord::Migration[5.1] + def change + create_table :report_notes do |t| + t.text :content, null: false + t.references :report, null: false + t.references :account, null: false + + t.timestamps + end + + add_foreign_key :report_notes, :reports, column: :report_id, on_delete: :cascade + add_foreign_key :report_notes, :accounts, column: :account_id, on_delete: :cascade + end +end diff --git a/db/schema.rb b/db/schema.rb index 18c61db..a9733a2 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.define(version: 20180310000000) do +ActiveRecord::Schema.define(version: 20180402040909) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -355,6 +355,16 @@ ActiveRecord::Schema.define(version: 20180310000000) do t.index ["status_id", "preview_card_id"], name: "index_preview_cards_statuses_on_status_id_and_preview_card_id" end + create_table "report_notes", force: :cascade do |t| + t.text "content", null: false + t.bigint "report_id", null: false + t.bigint "account_id", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["account_id"], name: "index_report_notes_on_account_id" + t.index ["report_id"], name: "index_report_notes_on_report_id" + end + create_table "reports", force: :cascade do |t| t.bigint "status_ids", default: [], null: false, array: true t.text "comment", default: "", null: false @@ -364,6 +374,7 @@ ActiveRecord::Schema.define(version: 20180310000000) do t.bigint "account_id", null: false t.bigint "action_taken_by_account_id" t.bigint "target_account_id", null: false + t.bigint "assigned_account_id" t.index ["account_id"], name: "index_reports_on_account_id" t.index ["target_account_id"], name: "index_reports_on_target_account_id" end @@ -569,7 +580,10 @@ ActiveRecord::Schema.define(version: 20180310000000) do 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 "report_notes", "accounts", on_delete: :cascade + add_foreign_key "report_notes", "reports", on_delete: :cascade add_foreign_key "reports", "accounts", column: "action_taken_by_account_id", name: "fk_bca45b75fd", on_delete: :nullify + add_foreign_key "reports", "accounts", column: "assigned_account_id", on_delete: :nullify add_foreign_key "reports", "accounts", column: "target_account_id", name: "fk_eb37af34f0", on_delete: :cascade add_foreign_key "reports", "accounts", name: "fk_4b81f7522c", on_delete: :cascade add_foreign_key "session_activations", "oauth_access_tokens", column: "access_token_id", name: "fk_957e5bda89", on_delete: :cascade diff --git a/spec/controllers/admin/reports_controller_spec.rb b/spec/controllers/admin/reports_controller_spec.rb index 71a1851..9be298d 100644 --- a/spec/controllers/admin/reports_controller_spec.rb +++ b/spec/controllers/admin/reports_controller_spec.rb @@ -61,7 +61,7 @@ describe Admin::ReportsController do report = Fabricate(:report) put :update, params: { id: report, outcome: 'resolve' } - expect(response).to redirect_to(admin_report_path(report)) + expect(response).to redirect_to(admin_reports_path) report.reload expect(report.action_taken_by_account).to eq user.account expect(report.action_taken).to eq true @@ -74,7 +74,7 @@ describe Admin::ReportsController do allow(Admin::SuspensionWorker).to receive(:perform_async) put :update, params: { id: report, outcome: 'suspend' } - expect(response).to redirect_to(admin_report_path(report)) + expect(response).to redirect_to(admin_reports_path) report.reload expect(report.action_taken_by_account).to eq user.account expect(report.action_taken).to eq true @@ -88,12 +88,46 @@ describe Admin::ReportsController do report = Fabricate(:report) put :update, params: { id: report, outcome: 'silence' } - expect(response).to redirect_to(admin_report_path(report)) + expect(response).to redirect_to(admin_reports_path) report.reload expect(report.action_taken_by_account).to eq user.account expect(report.action_taken).to eq true expect(report.target_account).to be_silenced end end + + describe 'with an outsome of `reopen`' do + it 'reopens the report' do + report = Fabricate(:report) + + put :update, params: { id: report, outcome: 'reopen' } + expect(response).to redirect_to(admin_report_path(report)) + report.reload + expect(report.action_taken_by_account).to eq nil + expect(report.action_taken).to eq false + end + end + + describe 'with an outsome of `assign_to_self`' do + it 'reopens the report' do + report = Fabricate(:report) + + put :update, params: { id: report, outcome: 'assign_to_self' } + expect(response).to redirect_to(admin_report_path(report)) + report.reload + expect(report.assigned_account).to eq user.account + end + end + + describe 'with an outsome of `unassign`' do + it 'reopens the report' do + report = Fabricate(:report) + + put :update, params: { id: report, outcome: 'unassign' } + expect(response).to redirect_to(admin_report_path(report)) + report.reload + expect(report.assigned_account).to eq nil + end + end end end From 1c293086a16fce465d5bdc123809f2d28b3e2ab6 Mon Sep 17 00:00:00 2001 From: mayaeh Date: Tue, 3 Apr 2018 18:21:33 +0900 Subject: [PATCH 079/381] i18n: Add Japanese translations for #7000 (#7022) * run yarn manage:translations. * run i18n-tasks add-missing ja. * Update Japanese translations. --- .../mastodon/locales/defaultMessages.json | 13 ++++++++++++ config/locales/ja.yml | 23 ++++++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/app/javascript/mastodon/locales/defaultMessages.json b/app/javascript/mastodon/locales/defaultMessages.json index 4c9401d..5059fc6 100644 --- a/app/javascript/mastodon/locales/defaultMessages.json +++ b/app/javascript/mastodon/locales/defaultMessages.json @@ -2,6 +2,19 @@ { "descriptors": [ { + "defaultMessage": "Oops!", + "id": "alert.unexpected.title" + }, + { + "defaultMessage": "An unexpected error occurred.", + "id": "alert.unexpected.message" + } + ], + "path": "app/javascript/mastodon/actions/alerts.json" + }, + { + "descriptors": [ + { "defaultMessage": "{name} mentioned you", "id": "notification.mention" } diff --git a/config/locales/ja.yml b/config/locales/ja.yml index 090b080..629d688 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -60,6 +60,7 @@ ja: destroyed_msg: モデレーションメモを削除しました! accounts: are_you_sure: 本当に実行しますか? + avatar: アイコン by_domain: ドメイン confirm: 確認 confirmed: 確認済み @@ -108,6 +109,7 @@ ja: public: パブリック push_subscription_expires: PuSH購読期限 redownload: アバターの更新 + remove_avatar: アイコンを削除 reset: リセット reset_password: パスワード再設定 resubscribe: 再講読 @@ -135,6 +137,7 @@ ja: web: Web action_logs: actions: + assigned_to_self_report: "%{name} さんがレポート %{target} を自身の担当に割り当てました" confirm_user: "%{name} さんが %{target} さんのメールアドレスを確認済みにしました" create_custom_emoji: "%{name} さんがカスタム絵文字 %{target} を追加しました" create_domain_block: "%{name} さんがドメイン %{target} をブロックしました" @@ -150,10 +153,13 @@ ja: enable_user: "%{name} さんが %{target} さんのログインを有効化しました" memorialize_account: "%{name} さんが %{target} さんを追悼アカウントページに登録しました" promote_user: "%{name} さんが %{target} さんを昇格しました" + remove_avatar_user: "%{name} さんが %{target} さんのアイコンを削除しました" + reopen_report: "%{name} さんがレポート %{target} を再び開きました" reset_password_user: "%{name} さんが %{target} さんのパスワードをリセットしました" resolve_report: "%{name} さんがレポート %{target} を解決済みにしました" silence_account: "%{name} さんが %{target} さんをサイレンスにしました" suspend_account: "%{name} さんが %{target} さんを停止しました" + unassigned_report: "%{name} さんがレポート %{target} の担当を外しました" unsilence_account: "%{name} さんが %{target} さんのサイレンスを解除しました" unsuspend_account: "%{name} さんが %{target} さんの停止を解除しました" update_custom_emoji: "%{name} さんがカスタム絵文字 %{target} を更新しました" @@ -239,29 +245,45 @@ ja: expired: 期限切れ title: フィルター title: 招待 + report_notes: + created_msg: モデレーションメモを書き込みました! + destroyed_msg: モデレーションメモを削除しました! reports: action_taken_by: レポート処理者 are_you_sure: 本当に実行しますか? + assign_to_self: 担当になる + assigned: 担当者 comment: label: コメント none: なし delete: 削除 id: ID mark_as_resolved: 解決済みとしてマーク + mark_as_unresolved: 未解決として再び開く + notes: + create: 書き込む + create_and_resolve: 書き込み、解決済みにする + delete: 削除 + label: メモ nsfw: 'false': NSFW オフ 'true': NSFW オン + reopen: 再び開く report: レポート#%{id} report_contents: 内容 reported_account: 報告対象アカウント reported_by: 報告者 resolved: 解決済み + resolved_msg: レポートを解決済みにしました! silence_account: アカウントをサイレンス status: ステータス + statuses: 通報されたトゥート suspend_account: アカウントを停止 target: ターゲット title: レポート + unassign: 担当を外す unresolved: 未解決 + updated_at: 更新日時 view: 表示 settings: activity_api_enabled: @@ -475,6 +497,7 @@ ja: '21600': 6 時間 '3600': 1 時間 '43200': 12 時間 + '604800': 1 週間 '86400': 1 日 expires_in_prompt: 無期限 generate: 作成 From 2e59751823585a8ef8729d4287239b326ab02193 Mon Sep 17 00:00:00 2001 From: Emelia Smith Date: Tue, 3 Apr 2018 13:07:32 +0200 Subject: [PATCH 080/381] Improve require_admin! and require_staff! filters (#7018) Previously these returns 302 redirects instead of 403s, which meant posting links to admin pages in slack caused them to unfurl, rather than stay as a link. Additionally, require_admin! doesn't appear to be actively used, on require_staff! --- app/controllers/application_controller.rb | 4 +-- spec/controllers/admin/base_controller_spec.rb | 19 +++++++---- spec/controllers/application_controller_spec.rb | 42 +++++++++++++++++++++++-- 3 files changed, 55 insertions(+), 10 deletions(-) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 6e50426..5885264 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -39,11 +39,11 @@ class ApplicationController < ActionController::Base end def require_admin! - redirect_to root_path unless current_user&.admin? + forbidden unless current_user&.admin? end def require_staff! - redirect_to root_path unless current_user&.staff? + forbidden unless current_user&.staff? end def check_suspension diff --git a/spec/controllers/admin/base_controller_spec.rb b/spec/controllers/admin/base_controller_spec.rb index 2b60e7e..9ac8336 100644 --- a/spec/controllers/admin/base_controller_spec.rb +++ b/spec/controllers/admin/base_controller_spec.rb @@ -9,18 +9,25 @@ describe Admin::BaseController, type: :controller do end end - it 'renders admin layout' do + it 'requires administrator or moderator' do routes.draw { get 'success' => 'admin/base#success' } - sign_in(Fabricate(:user, admin: true)) + sign_in(Fabricate(:user, admin: false, moderator: false)) get :success - expect(response).to render_template layout: 'admin' + + expect(response).to have_http_status(:forbidden) end - it 'requires administrator' do + it 'renders admin layout as a moderator' do routes.draw { get 'success' => 'admin/base#success' } - sign_in(Fabricate(:user, admin: false)) + sign_in(Fabricate(:user, moderator: true)) get :success + expect(response).to render_template layout: 'admin' + end - expect(response).to redirect_to('/') + it 'renders admin layout as an admin' do + routes.draw { get 'success' => 'admin/base#success' } + sign_in(Fabricate(:user, admin: true)) + get :success + expect(response).to render_template layout: 'admin' end end diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb index 51c0e3c..3e4d27e 100644 --- a/spec/controllers/application_controller_spec.rb +++ b/spec/controllers/application_controller_spec.rb @@ -181,10 +181,48 @@ describe ApplicationController, type: :controller do routes.draw { get 'sucesss' => 'anonymous#sucesss' } end - it 'redirects to root path if current user is not admin' do + it 'returns a 403 if current user is not admin' do sign_in(Fabricate(:user, admin: false)) get 'sucesss' - expect(response).to redirect_to('/') + expect(response).to have_http_status(403) + end + + it 'returns a 403 if current user is only a moderator' do + sign_in(Fabricate(:user, moderator: true)) + get 'sucesss' + expect(response).to have_http_status(403) + end + + it 'does nothing if current user is admin' do + sign_in(Fabricate(:user, admin: true)) + get 'sucesss' + expect(response).to have_http_status(200) + end + end + + describe 'require_staff!' do + controller do + before_action :require_staff! + + def sucesss + head 200 + end + end + + before do + routes.draw { get 'sucesss' => 'anonymous#sucesss' } + end + + it 'returns a 403 if current user is not admin or moderator' do + sign_in(Fabricate(:user, admin: false, moderator: false)) + get 'sucesss' + expect(response).to have_http_status(403) + end + + it 'does nothing if current user is moderator' do + sign_in(Fabricate(:user, moderator: true)) + get 'sucesss' + expect(response).to have_http_status(200) end it 'does nothing if current user is admin' do From 6ff3b3e4db09c8ddf2faad6710e4a25f988e762d Mon Sep 17 00:00:00 2001 From: Nolan Lawson Date: Tue, 3 Apr 2018 04:08:11 -0700 Subject: [PATCH 081/381] Fix nil account issue in ProcessAccountService (#7019) --- app/services/activitypub/process_account_service.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/services/activitypub/process_account_service.rb b/app/services/activitypub/process_account_service.rb index cf84628..21c2fc5 100644 --- a/app/services/activitypub/process_account_service.rb +++ b/app/services/activitypub/process_account_service.rb @@ -28,7 +28,7 @@ class ActivityPub::ProcessAccountService < BaseService after_protocol_change! if protocol_changed? after_key_change! if key_changed? - check_featured_collection! if @account.featured_collection_url.present? + check_featured_collection! if @account&.featured_collection_url&.present? @account rescue Oj::ParseError From d8d42179590db772cc5b1873385cba7e5afe20df Mon Sep 17 00:00:00 2001 From: ThibG Date: Tue, 3 Apr 2018 21:09:14 +0200 Subject: [PATCH 082/381] Fix french wording of reblogged toots in public views, matching the wording of reblogged_by (#7029) --- config/locales/fr.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 6137e1b..d7371dc 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -661,7 +661,7 @@ fr: stream_entries: click_to_show: Cliquer pour afficher pinned: Pouet épinglé - reblogged: partagé + reblogged: a partagé sensitive_content: Contenu sensible terms: body_html: | From 07176fed374b0ef94aacd1f3ea95b6dec2eb79dd Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Wed, 4 Apr 2018 01:11:26 +0200 Subject: [PATCH 083/381] Add contact account to landing page ("Administered by") (#6984) --- app/javascript/styles/mastodon/about.scss | 48 +++++++++++++++++++++++++++++++ app/views/about/show.html.haml | 22 ++++++++++++++ config/locales/en.yml | 1 + spec/views/about/show.html.haml_spec.rb | 1 + 4 files changed, 72 insertions(+) diff --git a/app/javascript/styles/mastodon/about.scss b/app/javascript/styles/mastodon/about.scss index c484f07..0321103 100644 --- a/app/javascript/styles/mastodon/about.scss +++ b/app/javascript/styles/mastodon/about.scss @@ -681,6 +681,54 @@ $small-breakpoint: 960px; margin-bottom: 0; } + .account { + border-bottom: 0; + padding: 0; + + &__display-name { + align-items: center; + display: flex; + margin-right: 5px; + } + + div.account__display-name { + &:hover { + .display-name strong { + text-decoration: none; + } + } + + .account__avatar { + cursor: default; + } + } + + &__avatar-wrapper { + margin-left: 0; + flex: 0 0 auto; + } + + &__avatar { + width: 44px; + height: 44px; + background-size: 44px 44px; + } + + .display-name { + font-size: 15px; + + &__account { + font-size: 14px; + } + } + } + + @media screen and (max-width: $small-breakpoint) { + .contact { + margin-top: 30px; + } + } + @media screen and (max-width: $column-breakpoint) { padding: 25px 20px; } diff --git a/app/views/about/show.html.haml b/app/views/about/show.html.haml index 85e5442..12213cd 100644 --- a/app/views/about/show.html.haml +++ b/app/views/about/show.html.haml @@ -108,6 +108,28 @@ %div %h3= t 'about.what_is_mastodon' %p= t 'about.about_mastodon_html' + %div.contact + %h3= t 'about.administered_by' + + .account + .account__wrapper + - if @instance_presenter.contact_account + = link_to TagManager.instance.url_for(@instance_presenter.contact_account), class: 'account__display-name' do + .account__avatar-wrapper + .account__avatar{ style: "background-image: url(#{@instance_presenter.contact_account.avatar.url})" } + %span.display-name + %bdi + %strong.display-name__html.emojify= display_name(@instance_presenter.contact_account) + %span.display-name__account @#{@instance_presenter.contact_account.acct} + - else + .account__display-name + .account__avatar-wrapper + .account__avatar{ style: "background-image: url(#{full_asset_url('avatars/original/missing.png', skip_pipeline: true)})" } + %span.display-name + %strong= t 'about.contact_missing' + %span.display-name__account= t 'about.contact_unavailable' + + = link_to t('about.learn_more'), about_more_path, class: 'button button-alternative' = render 'features' diff --git a/config/locales/en.yml b/config/locales/en.yml index 51d9c90..945faa1 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -4,6 +4,7 @@ en: about_hashtag_html: These are public toots tagged with #%{hashtag}. You can interact with them if you have an account anywhere in the fediverse. about_mastodon_html: Mastodon is a social network based on open web protocols and free, open-source software. It is decentralized like e-mail. about_this: About + administered_by: 'Administered by:' closed_registrations: Registrations are currently closed on this instance. However! You can find a different instance to make an account on and get access to the very same network from there. contact: Contact contact_missing: Not set diff --git a/spec/views/about/show.html.haml_spec.rb b/spec/views/about/show.html.haml_spec.rb index 03d6fb7..cbe5aa9 100644 --- a/spec/views/about/show.html.haml_spec.rb +++ b/spec/views/about/show.html.haml_spec.rb @@ -19,6 +19,7 @@ describe 'about/show.html.haml', without_verify_partial_doubles: true do hero: nil, user_count: 0, status_count: 0, + contact_account: nil, closed_registrations_message: 'yes') assign(:instance_presenter, instance_presenter) render From 7a810827040f31fb3936047f444efb77b743ed85 Mon Sep 17 00:00:00 2001 From: Emelia Smith Date: Wed, 4 Apr 2018 11:58:15 +0200 Subject: [PATCH 084/381] Revert "Add double-tap zoom functionary to `ZoomableImage` (#6944)" (#7035) Unfortunately the new hammer.js functionality wasn't correctly tested and didn't work across devices and browsers, as such, it's best to revert PR #6944 until we can revisit this functionality and make it work across all devices and browsers that are supported by Mastodon. This reverts commit 5021c4e9ca78881f5379a18185a46e580b8f2c34. --- .../features/ui/components/zoomable_image.js | 165 +++++++++------------ app/javascript/styles/mastodon/components.scss | 3 + package.json | 1 - yarn.lock | 4 - 4 files changed, 75 insertions(+), 98 deletions(-) diff --git a/app/javascript/mastodon/features/ui/components/zoomable_image.js b/app/javascript/mastodon/features/ui/components/zoomable_image.js index 0cae086..0a0a4d4 100644 --- a/app/javascript/mastodon/features/ui/components/zoomable_image.js +++ b/app/javascript/mastodon/features/ui/components/zoomable_image.js @@ -1,10 +1,16 @@ import React from 'react'; import PropTypes from 'prop-types'; -import Hammer from 'hammerjs'; const MIN_SCALE = 1; const MAX_SCALE = 4; -const DOUBLE_TAP_SCALE = 2; + +const getMidpoint = (p1, p2) => ({ + x: (p1.clientX + p2.clientX) / 2, + y: (p1.clientY + p2.clientY) / 2, +}); + +const getDistance = (p1, p2) => + Math.sqrt(Math.pow(p1.clientX - p2.clientX, 2) + Math.pow(p1.clientY - p2.clientY, 2)); const clamp = (min, max, value) => Math.min(max, Math.max(min, value)); @@ -31,95 +37,81 @@ export default class ZoomableImage extends React.PureComponent { removers = []; container = null; image = null; - lastScale = null; - zoomCenter = null; + lastTouchEndTime = 0; + lastDistance = 0; componentDidMount () { - // register pinch event handlers to the container - let hammer = new Hammer.Manager(this.container, { - // required to make container scrollable by touch - touchAction: 'pan-x pan-y', - }); - hammer.add(new Hammer.Pinch()); - hammer.on('pinchstart', this.handlePinchStart); - hammer.on('pinchmove', this.handlePinchMove); - this.removers.push(() => hammer.off('pinchstart pinchmove')); - - // register tap event handlers - hammer = new Hammer.Manager(this.image); - // NOTE the order of adding is also the order of gesture recognition - hammer.add(new Hammer.Tap({ event: 'doubletap', taps: 2 })); - hammer.add(new Hammer.Tap()); - // prevent the 'tap' event handler be fired on double tap - hammer.get('tap').requireFailure('doubletap'); - // NOTE 'tap' and 'doubletap' events are fired by touch and *mouse* - hammer.on('tap', this.handleTap); - hammer.on('doubletap', this.handleDoubleTap); - this.removers.push(() => hammer.off('tap doubletap')); + let handler = this.handleTouchStart; + this.container.addEventListener('touchstart', handler); + this.removers.push(() => this.container.removeEventListener('touchstart', handler)); + handler = this.handleTouchMove; + // on Chrome 56+, touch event listeners will default to passive + // https://www.chromestatus.com/features/5093566007214080 + this.container.addEventListener('touchmove', handler, { passive: false }); + this.removers.push(() => this.container.removeEventListener('touchend', handler)); } componentWillUnmount () { this.removeEventListeners(); } - componentDidUpdate (prevProps, prevState) { - if (!this.zoomCenter) return; - - const { x: cx, y: cy } = this.zoomCenter; - const { scale: prevScale } = prevState; - const { scale: nextScale } = this.state; - const { scrollLeft, scrollTop } = this.container; - - // math memo: - // x = (scrollLeft + cx) / scrollWidth - // x' = (nextScrollLeft + cx) / nextScrollWidth - // scrollWidth = clientWidth * prevScale - // scrollWidth' = clientWidth * nextScale - // Solve x = x' for nextScrollLeft - const nextScrollLeft = (scrollLeft + cx) * nextScale / prevScale - cx; - const nextScrollTop = (scrollTop + cy) * nextScale / prevScale - cy; - - this.container.scrollLeft = nextScrollLeft; - this.container.scrollTop = nextScrollTop; - } - removeEventListeners () { this.removers.forEach(listeners => listeners()); this.removers = []; } - handleClick = e => { - // prevent the click event propagated to parent - e.stopPropagation(); + handleTouchStart = e => { + if (e.touches.length !== 2) return; - // the tap event handler is executed at the same time by touch and mouse, - // so we don't need to execute the onClick handler here + this.lastDistance = getDistance(...e.touches); } - handlePinchStart = () => { - this.lastScale = this.state.scale; - } + handleTouchMove = e => { + const { scrollTop, scrollHeight, clientHeight } = this.container; + if (e.touches.length === 1 && scrollTop !== scrollHeight - clientHeight) { + // prevent propagating event to MediaModal + e.stopPropagation(); + return; + } + if (e.touches.length !== 2) return; - handlePinchMove = e => { - const scale = clamp(MIN_SCALE, MAX_SCALE, this.lastScale * e.scale); - this.zoom(scale, e.center); - } + e.preventDefault(); + e.stopPropagation(); - handleTap = () => { - const handler = this.props.onClick; - if (handler) handler(); + const distance = getDistance(...e.touches); + const midpoint = getMidpoint(...e.touches); + const scale = clamp(MIN_SCALE, MAX_SCALE, this.state.scale * distance / this.lastDistance); + + this.zoom(scale, midpoint); + + this.lastMidpoint = midpoint; + this.lastDistance = distance; } - handleDoubleTap = e => { - if (this.state.scale === MIN_SCALE) - this.zoom(DOUBLE_TAP_SCALE, e.center); - else - this.zoom(MIN_SCALE, e.center); + zoom(nextScale, midpoint) { + const { scale } = this.state; + const { scrollLeft, scrollTop } = this.container; + + // math memo: + // x = (scrollLeft + midpoint.x) / scrollWidth + // x' = (nextScrollLeft + midpoint.x) / nextScrollWidth + // scrollWidth = clientWidth * scale + // scrollWidth' = clientWidth * nextScale + // Solve x = x' for nextScrollLeft + const nextScrollLeft = (scrollLeft + midpoint.x) * nextScale / scale - midpoint.x; + const nextScrollTop = (scrollTop + midpoint.y) * nextScale / scale - midpoint.y; + + this.setState({ scale: nextScale }, () => { + this.container.scrollLeft = nextScrollLeft; + this.container.scrollTop = nextScrollTop; + }); } - zoom (scale, center) { - this.zoomCenter = center; - this.setState({ scale }); + handleClick = e => { + // don't propagate event to MediaModal + e.stopPropagation(); + const handler = this.props.onClick; + if (handler) handler(); } setContainerRef = c => { @@ -134,18 +126,6 @@ export default class ZoomableImage extends React.PureComponent { const { alt, src } = this.props; const { scale } = this.state; const overflow = scale === 1 ? 'hidden' : 'scroll'; - const marginStyle = { - position: 'absolute', - top: 0, - bottom: 0, - left: 0, - right: 0, - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - transform: `scale(${scale})`, - transformOrigin: '0 0', - }; return (
-
- {alt} -
+ {alt}
); } diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index 6a83be4..d76dc10 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -1483,6 +1483,9 @@ position: relative; width: 100%; height: 100%; + display: flex; + align-items: center; + justify-content: center; img { max-width: $media-modal-media-max-width; diff --git a/package.json b/package.json index d4de3a1..9858d28 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,6 @@ "file-loader": "^0.11.2", "font-awesome": "^4.7.0", "glob": "^7.1.1", - "hammerjs": "^2.0.8", "http-link-header": "^0.8.0", "immutable": "^3.8.2", "imports-loader": "^0.8.0", diff --git a/yarn.lock b/yarn.lock index 866b24c..fba2cb9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3096,10 +3096,6 @@ gzip-size@^3.0.0: dependencies: duplexer "^0.1.1" -hammerjs@^2.0.8: - version "2.0.8" - resolved "https://registry.yarnpkg.com/hammerjs/-/hammerjs-2.0.8.tgz#04ef77862cff2bb79d30f7692095930222bf60f1" - handle-thing@^1.2.5: version "1.2.5" resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-1.2.5.tgz#fd7aad726bf1a5fd16dfc29b2f7a6601d27139c4" From 1c1042556d21e4c2eb22b7c5cbc11aa88087ca60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Miko=C5=82ajczak?= Date: Wed, 4 Apr 2018 20:13:43 +0200 Subject: [PATCH 085/381] i18n: Update Polish translation (#7037) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * i18n: Update Polish translation Signed-off-by: Marcin Mikołajczak * i18n: Update Polish translation Signed-off-by: Marcin Mikołajczak --- config/locales/pl.yml | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/config/locales/pl.yml b/config/locales/pl.yml index e92742e..c626360 100644 --- a/config/locales/pl.yml +++ b/config/locales/pl.yml @@ -4,6 +4,7 @@ pl: about_hashtag_html: Znajdują się tu publiczne wpisy oznaczone hashtagiem #%{hashtag}. Możesz dołączyć do dyskusji, jeżeli posiadasz konto gdziekolwiek w Fediwersum. about_mastodon_html: Mastodon jest wolną i otwartą siecią społecznościową, zdecentralizowaną alternatywą dla zamkniętych, komercyjnych platform. about_this: O tej instancji + administered_by: 'Administrowana przez:' closed_registrations: Rejestracja na tej instancji jest obecnie zamknięta. Możesz jednak zarejestrować się na innej instancji, uzyskując dostęp do tej samej sieci. contact: Kontakt contact_missing: Nie ustawiono @@ -60,6 +61,7 @@ pl: destroyed_msg: Pomyślnie usunięto notatkę moderacyjną! accounts: are_you_sure: Jesteś tego pewien? + avatar: Awatar by_domain: Domena confirm: Potwierdź confirmed: Potwierdzono @@ -108,6 +110,7 @@ pl: public: Publiczne push_subscription_expires: Subskrypcja PuSH wygasa redownload: Odśwież awatar + remove_avatar: Usun awatar reset: Resetuj reset_password: Resetuj hasło resubscribe: Ponów subskrypcję @@ -135,6 +138,7 @@ pl: web: Sieć action_logs: actions: + assigned_to_self_report: "%{name} przypisał sobie zgłoszenie %{target}" confirm_user: "%{name} potwierdził adres e-mail użytkownika %{target}" create_custom_emoji: "%{name} dodał nowe emoji %{target}" create_domain_block: "%{name} zablokował domenę %{target}" @@ -150,10 +154,13 @@ pl: enable_user: "%{name} przywrócił możliwość logowania użytkownikowi %{target}" memorialize_account: "%{name} nadał kontu %{target} status in memoriam" promote_user: "%{name} podniósł uprawnienia użytkownikowi %{target}" + remove_avatar_user: "%{name} usunął awatar użytkownikowi %{target}" + reopen_report: "%{name} otworzył ponownie zgłoszenie %{target}" reset_password_user: "%{name} przywrócił hasło użytkownikowi %{target}" - resolve_report: "%{name} odrzucił zgłoszenie %{target}" + resolve_report: "%{name} rozwiązał zgłoszenie %{target}" silence_account: "%{name} wyciszył konto %{target}" suspend_account: "%{name} zawiesił konto %{target}" + unassigned_report: "%{name} cofnął przypisanie zgłoszenia %{target}" unsilence_account: "%{name} cofnął wyciszenie konta %{target}" unsuspend_account: "%{name} cofnął zawieszenie konta %{target}" update_custom_emoji: "%{name} zaktualizował emoji %{target}" @@ -240,15 +247,26 @@ pl: expired: Wygasłe title: Filtruj title: Zaproszenia + report_notes: + created_msg: Pomyslnie utworzono notatkę moderacyjną. + destroyed_msg: Pomyślnie usunięto notatkę moderacyjną. reports: action_taken_by: Działanie podjęte przez are_you_sure: Czy na pewno? + assign_to_self: Przypisz do siebie + assigned: Przypisany moderator comment: - label: Komentarz + label: Komentarz do zgłoszenia none: Brak delete: Usuń id: ID mark_as_resolved: Oznacz jako rozwiązane + mark_as_unresolved: Oznacz jako nierozwiązane + notes: + create: Utwórz notatkę + create_and_resolve: Rozwiąż i pozostaw notatkę + delete: Usuń + label: Notatki nsfw: 'false': Nie oznaczaj jako NSFW 'true': Oznaczaj jako NSFW @@ -257,12 +275,16 @@ pl: reported_account: Zgłoszone konto reported_by: Zgłaszający resolved: Rozwiązane + resolved_msg: Pomyślnie rozwiązano zgłoszenie. silence_account: Wycisz konto status: Stan + statuses: Zgłoszone wpisy suspend_account: Zawieś konto target: Cel title: Zgłoszenia + unassign: Cofnij przypisanie unresolved: Nierozwiązane + updated_at: Zaktualizowano view: Wyświetl settings: activity_api_enabled: From f1867a73881444dfed9e093425435681ee764922 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Wed, 4 Apr 2018 21:47:39 +0200 Subject: [PATCH 086/381] Adjust privacy policy (#6666) * Adjust privacy policy to be more specific to Mastodon Fix #6613 * Change data retention of IP addresses from 5 years to 1 year * Add even more information * Remove all (now invalid) translations of the privacy policy * Add information about archive takeout, remove pointless consent section * Emphasis on DM privacy * Improve wording * Add line about data use for moderation purposes --- app/javascript/styles/mastodon/about.scss | 5 ++ app/workers/scheduler/ip_cleanup_scheduler.rb | 4 +- config/locales/ca.yml | 68 -------------------------- config/locales/en.yml | 61 +++++++++++++---------- config/locales/eo.yml | 68 -------------------------- config/locales/es.yml | 68 -------------------------- config/locales/fa.yml | 68 -------------------------- config/locales/fr.yml | 68 -------------------------- config/locales/gl.yml | 68 -------------------------- config/locales/hu.yml | 70 --------------------------- config/locales/ja.yml | 68 -------------------------- config/locales/ko.yml | 68 -------------------------- config/locales/nl.yml | 68 -------------------------- config/locales/no.yml | 68 -------------------------- config/locales/oc.yml | 68 -------------------------- config/locales/pl.yml | 68 -------------------------- config/locales/pt-BR.yml | 68 -------------------------- config/locales/pt.yml | 68 -------------------------- config/locales/ru.yml | 39 --------------- config/locales/sr-Latn.yml | 68 -------------------------- config/locales/sr.yml | 68 -------------------------- config/locales/sv.yml | 68 -------------------------- config/locales/zh-CN.yml | 68 -------------------------- 23 files changed, 43 insertions(+), 1360 deletions(-) diff --git a/app/javascript/styles/mastodon/about.scss b/app/javascript/styles/mastodon/about.scss index 0321103..034c35e 100644 --- a/app/javascript/styles/mastodon/about.scss +++ b/app/javascript/styles/mastodon/about.scss @@ -322,6 +322,11 @@ $small-breakpoint: 960px; border: 0; border-bottom: 1px solid rgba($ui-base-lighter-color, .6); margin: 20px 0; + + &.spacer { + height: 1px; + border: 0; + } } .container-alt { diff --git a/app/workers/scheduler/ip_cleanup_scheduler.rb b/app/workers/scheduler/ip_cleanup_scheduler.rb index 9f1593c..a33ca03 100644 --- a/app/workers/scheduler/ip_cleanup_scheduler.rb +++ b/app/workers/scheduler/ip_cleanup_scheduler.rb @@ -4,8 +4,10 @@ require 'sidekiq-scheduler' class Scheduler::IpCleanupScheduler include Sidekiq::Worker + RETENTION_PERIOD = 1.year + def perform - time_ago = 5.years.ago + time_ago = RETENTION_PERIOD.ago SessionActivation.where('updated_at < ?', time_ago).destroy_all User.where('last_sign_in_at < ?', time_ago).update_all(last_sign_in_ip: nil) end diff --git a/config/locales/ca.yml b/config/locales/ca.yml index 7727bad..fc30b36 100644 --- a/config/locales/ca.yml +++ b/config/locales/ca.yml @@ -665,74 +665,6 @@ ca: reblogged: ha impulsat sensitive_content: Contingut sensible terms: - body_html: | -

Política de privacitat

- -

Quina informació recollim?

- -

Recopilem informació teva quan et registres en aquesta instància i recopilem dades quan participes en el fòrum llegint, escrivint i avaluant el contingut aquí compartit.

- -

En registrar-te en aquesta instància, se't pot demanar que introduexis el teu nom i l'adreça de correu electrònic. També pots visitar el nostre lloc sense registrar-te. La teva adreça de correu electrònic es verificarà mitjançant un correu electrònic que conté un enllaç únic. Si es visita aquest enllaç, sabem que controles l'adreça de correu electrònic.

- -

Quan es registra i publica, registrem l'adreça IP de la qual es va originar la publicació. També podrem conservar els registres del servidor que inclouen l'adreça IP de cada sol·licitud al nostre servidor.

- -

Per a què utilitzem la teva informació?

- -

Qualsevol de la informació que recopilem de tu pot utilitzar-se d'una de les maneres següents:

- -
    -
  • Per a personalitzar la teva experiència — la teva informació ens ajuda a respondre millor a les teves necessitats individuals.
  • -
  • Per millorar el nostre lloc — ens esforcem contínuament per millorar les nostres ofertes de llocs basats en la informació i els comentaris que rebem de tu.
  • -
  • Per millorar el servei al client — la teva informació ens ajuda a respondre més eficaçment a les teves sol·licituds de servei al client i a les necessitats de suport.
  • -
  • Per enviar correus electrònics periòdics — l'adreça electrònica que proporcionis es pot utilitzar per enviar-te informació, notificacions que sol·licitis sobre canvis en temes o en resposta al teu nom d'usuari, respondre a les consultes i/o altres sol·licituds o preguntes.
  • -
- -

Com protegim la teva informació?

- -

Implementem diverses mesures de seguretat per mantenir la seguretat de la teva informació personal quan introdueixes, envies o accedeixes a la teva informació personal.

- -

Quina és la nostre política de retenció de dades?

- -

Farem un esforç de bona fe per a:

- -
    -
  • Conserva els registres de servidor que continguin l'adreça IP de totes les sol·licituds a aquest servidor no més de 90 dies.
  • -
  • Conserva les adreces IP associades als usuaris registrats i les seves publicacions no més de 5 anys.
  • -
- -

Utilitzem galetes?

- -

Sí. Les cookies són fitxers petits que un lloc o el proveïdor de serveis transfereix al disc dur del vostre ordinador a través del navegador web (si ho permet). Aquestes galetes permeten al lloc reconèixer el vostre navegador i, si teniu un compte registrat, associar-lo al vostre compte registrat.

- -

Utilitzem cookies per comprendre i desar les vostres preferències per a futures visites i compilar dades agregades sobre el trànsit del lloc i la interacció del lloc, de manera que podrem oferir millors experiències i eines del lloc en el futur. Podem contractar amb proveïdors de serveis de tercers per ajudar-nos a comprendre millor els visitants del nostre lloc. Aquests proveïdors de serveis no estan autoritzats a utilitzar la informació recollida en nom nostre, excepte per ajudar-nos a dur a terme i millorar el nostre negoci.

- -

Publiquem informació al exterior?

- -

No venem, comercialitzem ni transmetem a tercers la vostra informació d'identificació personal. Això no inclou tercers de confiança que ens ajudin a operar el nostre lloc, a dur a terme el nostre negoci o a fer-ho, sempre que aquestes parts acceptin mantenir confidencial aquesta informació. També podem publicar la vostra informació quan creiem que l'alliberament és apropiat per complir amb la llei, fer complir les polítiques del nostre lloc o protegir els nostres drets o altres drets, propietat o seguretat. No obstant això, la informació de visitant que no sigui personalment identificable es pot proporcionar a altres parts per a la comercialització, la publicitat o altres usos.

- -

Vincles de tercers

- -

De tant en tant, segons el nostre criteri, podem incloure o oferir productes o serveis de tercers al nostre lloc. Aquests llocs de tercers tenen polítiques de privadesa separades i independents. Per tant, no tenim responsabilitat ni responsabilitat civil pel contingut i les activitats d'aquests llocs enllaçats. No obstant això, busquem protegir la integritat del nostre lloc i donem la benvinguda a qualsevol comentari sobre aquests llocs.

- -

Compliment de la Llei de protecció de la privacitat en línia dels nens

- -

El nostre lloc, productes i serveis estan dirigits a persones que tenen almenys 13 anys. Si aquest servidor es troba als EUA, i teniu menys de 13 anys, segons els requisits de COPPA (Children's Online Privacy Protection Act) no feu servir aquest lloc.

- -

Només la política de privacitat en línia

- -

Aquesta política de privacitat en línia només s'aplica a la informació recopilada a través del nostre lloc i no a la informació recopilada fora de línia.

- - - -

En utilitzar el nostre lloc, accepta la política de privadesa del nostre lloc web.

- -

Canvis a la nostra política de privacitat

- -

Si decidim canviar la nostra política de privadesa, publicarem aquests canvis en aquesta pàgina.

- -

Aquest document és CC-BY-SA. Es va actualitzar per última vegada el 31 de maig de 2013.

- -

Originalment adaptat a la política de privadesa del Discurs.

title: "%{instance} Condicions del servei i política de privadesa" themes: default: Mastodont diff --git a/config/locales/en.yml b/config/locales/en.yml index 945faa1..70af953 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -691,70 +691,79 @@ en: terms: body_html: |

Privacy Policy

-

What information do we collect?

-

We collect information from you when you register on our site and gather data when you participate in the forum by reading, writing, and evaluating the content shared here.

- -

When registering on our site, you may be asked to enter your name and e-mail address. You may, however, visit our site without registering. Your e-mail address will be verified by an email containing a unique link. If that link is visited, we know that you control the e-mail address.

+
    +
  • Basic account information: If you register on this server, you may be asked to enter a username, an e-mail address and a password. You may also enter additional profile information such as a display name and biography, and upload a profile picture and header image. The username, display name, biography, profile picture and header image are always listed publicly.
  • +
  • Posts, following and other public information: The list of people you follow is listed publicly, the same is true for your followers. When you submit a message, the date and time is stored as well as the application you submitted the message from. Messages may contain media attachments, such as pictures and videos. Public and unlisted posts are available publicly. When you feature a post on your profile, that is also publicly available information. Your posts are delivered to your followers, in some cases it means they are delivered to different servers and copies are stored there. When you delete posts, this is likewise delivered to your followers. The action of reblogging or favouriting another post is always public.
  • +
  • Direct and followers-only posts: All posts are stored and processed on the server. Followers-only posts are delivered to your followers and users who are mentioned in them, and direct posts are delivered only to users mentioned in them. In some cases it means they are delivered to different servers and copies are stored there. We make a good faith effort to limit the access to those posts only to authorized persons, but other servers may fail to do so. Therefore it's important to review servers your followers belong to. You may toggle an option to approve and reject new followers manually in the settings. Please keep in mind that the operators of the server and any receiving server may view such messages, and that recipients may screenshot, copy or otherwise re-share them. Do not share any dangerous information over Mastodon.
  • +
  • IPs and other metadata: When you log in, we record the IP address you log in from, as well as the name of your browser application. All the logged in sessions are available for your review and revocation in the settings. The latest IP address used is stored for up to 12 months. We also may retain server logs which include the IP address of every request to our server.
  • +
-

When registered and posting, we record the IP address that the post originated from. We also may retain server logs which include the IP address of every request to our server.

+

What do we use your information for?

-

Any of the information we collect from you may be used in one of the following ways:

+

Any of the information we collect from you may be used in the following ways:

    -
  • To personalize your experience — your information helps us to better respond to your individual needs.
  • -
  • To improve our site — we continually strive to improve our site offerings based on the information and feedback we receive from you.
  • -
  • To improve customer service — your information helps us to more effectively respond to your customer service requests and support needs.
  • -
  • To send periodic emails — The email address you provide may be used to send you information, notifications that you request about changes to topics or in response to your user name, respond to inquiries, and/or other requests or questions.
  • +
  • To provide the core functionality of Mastodon. You can only interact with other people's content and post your own content when you are logged in. For example, you may follow other people to view their combined posts in your own personalized home timeline.
  • +
  • To aid moderation of the community, for example comparing your IP address with other known ones to determine ban evasion or other violations.
  • +
  • The email address you provide may be used to send you information, notifications about other people interacting with your content or sending you messages, and to respond to inquiries, and/or other requests or questions.
+
+

How do we protect your information?

-

We implement a variety of security measures to maintain the safety of your personal information when you enter, submit, or access your personal information.

+

We implement a variety of security measures to maintain the safety of your personal information when you enter, submit, or access your personal information. Among other things, your browser session, as well as the traffic between your applications and the API, are secured with SSL, and your password is hashed using a strong one-way algorithm. You may enable two-factor authentication to further secure access to your account.

+ +
-

What is your data retention policy?

+

What is our data retention policy?

We will make a good faith effort to:

    -
  • Retain server logs containing the IP address of all requests to this server no more than 90 days.
  • -
  • Retain the IP addresses associated with registered users and their posts no more than 5 years.
  • +
  • Retain server logs containing the IP address of all requests to this server, in so far as such logs are kept, no more than 90 days.
  • +
  • Retain the IP addresses associated with registered users no more than 12 months.
+

You can request and download an archive of your content, including your posts, media attachments, profile picture, and header image.

+ +

You may irreversibly delete your account at any time.

+ +
+

Do we use cookies?

Yes. Cookies are small files that a site or its service provider transfers to your computer's hard drive through your Web browser (if you allow). These cookies enable the site to recognize your browser and, if you have a registered account, associate it with your registered account.

-

We use cookies to understand and save your preferences for future visits and compile aggregate data about site traffic and site interaction so that we can offer better site experiences and tools in the future. We may contract with third-party service providers to assist us in better understanding our site visitors. These service providers are not permitted to use the information collected on our behalf except to help us conduct and improve our business.

+

We use cookies to understand and save your preferences for future visits.

+ +

Do we disclose any information to outside parties?

-

We do not sell, trade, or otherwise transfer to outside parties your personally identifiable information. This does not include trusted third parties who assist us in operating our site, conducting our business, or servicing you, so long as those parties agree to keep this information confidential. We may also release your information when we believe release is appropriate to comply with the law, enforce our site policies, or protect ours or others rights, property, or safety. However, non-personally identifiable visitor information may be provided to other parties for marketing, advertising, or other uses.

+

We do not sell, trade, or otherwise transfer to outside parties your personally identifiable information. This does not include trusted third parties who assist us in operating our site, conducting our business, or servicing you, so long as those parties agree to keep this information confidential. We may also release your information when we believe release is appropriate to comply with the law, enforce our site policies, or protect ours or others rights, property, or safety.

+ +

Your public content may be downloaded by other servers in the network. Your public and followers-only posts are delivered to the servers where your followers reside, and direct messages are delivered to the servers of the recipients, in so far as those followers or recipients reside on a different server than this.

-

Third party links

+

When you authorize an application to use your account, depending on the scope of permissions you approve, it may access your public profile information, your following list, your followers, your lists, all your posts, and your favourites. Applications can never access your e-mail address or password.

-

Occasionally, at our discretion, we may include or offer third party products or services on our site. These third party sites have separate and independent privacy policies. We therefore have no responsibility or liability for the content and activities of these linked sites. Nonetheless, we seek to protect the integrity of our site and welcome any feedback about these sites.

+

Children's Online Privacy Protection Act Compliance

Our site, products and services are all directed to people who are at least 13 years old. If this server is in the USA, and you are under the age of 13, per the requirements of COPPA (Children's Online Privacy Protection Act) do not use this site.

-

Online Privacy Policy Only

- -

This online privacy policy applies only to information collected through our site and not to information collected offline.

- - - -

By using our site, you consent to our web site privacy policy.

+

Changes to our Privacy Policy

If we decide to change our privacy policy, we will post those changes on this page.

-

This document is CC-BY-SA. It was last updated May 31, 2013.

+

This document is CC-BY-SA. It was last updated March 7, 2018.

Originally adapted from the Discourse privacy policy.

title: "%{instance} Terms of Service and Privacy Policy" diff --git a/config/locales/eo.yml b/config/locales/eo.yml index 84d63d8..a896592 100644 --- a/config/locales/eo.yml +++ b/config/locales/eo.yml @@ -667,74 +667,6 @@ eo: reblogged: diskonigita sensitive_content: Tikla enhavo terms: - body_html: | -

Privateca politiko

- -

Kiujn informojn ni kolektas?

- -

Ni kolektas informojn de vi, kiam vi registriĝas en nia retejo aŭ partoprenas en la forumo per legado, skribado, kaj traktado de la enhavo diskonigita ĉi tie.

- -

En registriĝo, ni povas peti al vi vian nomon kaj retadreson. Vi tamen povas viziti nian retejon sen registriĝo. Via retadreso estos validigita per retmesaĝo, kiu enhavos unikan ligilon. Se tiu ligilo estas vizitita, ni scios ke vi regas la retadreson.

- -

Post registriĝo, ni registras la IP-adreson de tiu, kiu kreas mesaĝon. Ni ankaŭ povas konservi servilan historion, en kiu troviĝas la IP-adreso de ĉiu peto al nia servilo.

- -

Por kio ni uzas viajn informojn?

- -

Ajna informo, kiun ni kolektas povas esti uzata por unu el tiuj celoj:

- -
    -
  • Proprigi vian sperton — viaj informoj helpas nin pli bone respondi al viaj propraj bezonoj.
  • -
  • Plibonigi nian retejon — ni daŭre klopodas por plibonigi nian retejon uzante la informojn kaj komentojn, kiujn ni ricevas de vi.
  • -
  • Plibonigi nian helpon al klientoj — viaj informoj helpas nin pli bone respondi al klientaj petoj kaj al subtenaj bezonoj.
  • -
  • Sendi periodajn retmesaĝojn — La retadreso, kiun vi donas al ni, povas esti uzata por sendi al vi informojn kaj sciigojn, kiujn vi volas ricevi pri ŝanĝoj rilate al apartaj temoj, aŭ responde al via uzantnomo, al petoj kaj al demandoj.
  • -
- -

Kiel ni protektas viajn informojn?

- -

Ni realigis diversajn sekurigajn procedojn por konservi la sekurecon de viaj personaj informoj kiam vi enmetas, sendas, aŭ aliras viajn personajn informojn.

- -

Kio estas nia politiko pri konservado de datumoj?

- -

Ni honeste klopodas:

- -
    -
  • Ne konservi servilan historion, kiu enhavas la IP-adresojn de ĉiuj petoj, dum pli ol 90 tagoj.
  • -
  • Ne konservi la IP-adresojn de registritaj uzantoj kaj de iliaj mesaĝoj dum pli ol 5 jaroj.
  • -
- -

Ĉu ni uzas kuketojn?

- -

Jes. Kuketoj estas etaj dosieroj, kiujn retejo aŭ ĝia servo donas al la memoro de via komputilo, per via retumilo (se vi permesas tion). Ĉi tiuj kuketoj ebligas al la retejo rekoni vian retumilon, kaj se vi havas registritan konton, ligas ĝin al via registrita konto.

- -

Ni uzas kuketojn por kompreni kaj konservi viajn preferojn por postaj vizitoj, kaj kunmeti informojn pri reteja trafiko kaj interago, por ke ni povu doni pli bonan retejan sperton kaj pli bonajn ilojn estonte. Ni povas kontrakti kun eksteraj servoj por helpi nin pli bone kompreni la vizitantojn de la retejo. Ĉi tiuj eksteraj servoj ne rajtas uzi la informojn, kiujn ni kolektis, krom por helpi nin regi kaj plibonigi nian komercon.

- -

Ĉu ni disdonas informojn al eksteraj personoj?

- -

Ni ne vendas, interŝanĝas aŭ transdonas al eksteraj personoj viajn persone identigeblajn informojn. Ĉi tio ne inkludas la eksterajn servojn, kiujn ni fidas, kiuj helpas nin funkciigi nian retejon, regi nian komercon, aŭ servi vin, kiom longe tiuj personoj konsentas pri la sekura konservado de ĉi tiuj informoj. Ni ankaŭ povas disdoni viajn informojn, kiam ni pensas ke tio estas nepra por respekti leĝojn, por respektigi la politikojn de nia retejo, aŭ por protekti la rajtojn, posedaĵojn, kaj sekurecon de ni kaj de aliaj. Tamen, informoj de vizitantoj, kiuj ne identigas personojn, povas esti donitaj al eksteraj personoj por merkatado, reklamado, aŭ aliaj uzoj.

- -

Eksteraj ligiloj

- -

Foje, laŭ nia elekto, ni povas enmeti aŭ oferti eksterajn produktojn aŭ servojn en nia retejo. Ĉi tiuj eksteraj retejoj havas apartajn kaj sendependajn privatecajn politikojn. Tial, ni havas nek responsojn nek devigojn rilate al la enhavoj kaj agadoj de ĉi tiuj ligitaj retejoj. Tamen, ni celas protekti tiujn, kiuj uzas nian retejon, kaj bonvenigas ajnan komenton pri ĉi tiuj retejoj.

- -

Children's Online Privacy Protection Act Compliance

- -

Niaj retejo, produktoj kaj servoj estas por tiuj, kiuj havas almenaŭ 13 jarojn. Se ĉi tiu servilo estas en Usono, kaj vi havas malpli ol 13 jarojn, pro la postuloj de COPPA (Children's Online Privacy Protection Act) ne uzu ĉi tiun retejon.

- -

Privateca politiko nur rete

- -

Ĉi tiu privateca politiko validas nur por informoj kolektitaj per nia retejo kaj ne por informoj kolektitaj eksterrete.

- - - -

Per uzado de nia retejo, vi konsentas kun nia reta privateca politiko.

- -

Ŝanĝoj al nia privateca politiko

- -

Se ni decidas ŝanĝi nian privatecan politikon, ni afiŝos tiujn ŝanĝojn en ĉi tiu paĝo.

- -

Ĉi tiu dokumento estas laŭ permeso CC-BY-SA. Ĝi estis laste ĝisdatigita je 2018-02-27.

- -

Originale adaptita el la privateca politiko de Discourse.

title: Uzkondiĉoj kaj privateca politiko de %{instance} themes: default: Mastodon diff --git a/config/locales/es.yml b/config/locales/es.yml index 671f17d..8e7a766 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -656,74 +656,6 @@ es: reblogged: retooteado sensitive_content: Contenido sensible terms: - body_html: | -

Políticas de privacidad

- -

¿Qué información recolectamos?

- -

Recolectamos información tuya cuando te registras en nuestro sitio y adquirimos datos cuando participas en el foro leyendo, escribiendo, y evaluando el contenido compartido aquí.

- -

Cuando te registras en nuestro sitio, puede que se te pida tu nombre y dirección de correo electrónico. De todas formas, puedes visitar nuestro sitio sin registrarte. Tu dirección de correo electrónico será verificada por un e-mail conteniendo un enlace único. Si ese enlace es visitado, sabemos que tú controlas esa dirección.

- -

Cuando te registras y posteas, grabamos la IP de la que se origina esa acción. También puede que retengamos logs del servidor, que incluyen la dirección IP de todos los pedidos a nuestro servidor.

- -

¿Para qué usamos tu información?

- -

Toda la información que recolectamos de ti puede usarse en una de las siguientes maneras:

- -
    -
  • Para personalizar tu experiencia — tu información nos ayuda a responder mejor tus necesidades individuales.
  • -
  • Para mejorar nuestro sitio — nos esforzamos continuamente en mejorar nuestras ofertas del sitio basándonos en la información y apoyo que recibimos de ti.
  • -
  • Para mejorar el servicio al cliente — tu información nos ayuda a responder más efectivamente al servicio al cliente y otras necesidades.
  • -
  • Para enviar e-mails periódicos — la dirección de e-mail que provees puede usarse para enviarte información, notificaciones que pides sobre cambios en tópicos o en respuesta a tu nombre de usuario, responder consultas, y/u otros pedidos o preguntas.
  • -
- -

¿Cómo protegemos tu información?

- -

Implementamos una variedad de medidas de seguridad para mantener a salvo tu información personal cuando entras, publicas, o accesas a ella.

- -

¿Cuáles son sus políticas de retención de datos?

- -

Haremos un gran esfuerzo en:

- -
    -
  • Retener logs del servidor conteniendo la dirección IP de todos los pedidos a este servidor en no más de 90 días.
  • -
  • Retener las direcciones IP asociadas con usuarios registrados y sus posts no más de 5 años.
  • -
- -

- -

Sí. Las cookies son pequeños archivos que un sitio web o su proveedor de servicio transfieren al disco duro de tu computadora a través de tu navegador web (si se le permite). Estas cookies permiten al sitio reconocer tu navegador y, si y tienes una cuenta registrada, asociarlo con ella.

- -

Usamos cookies para entender y guardar tus preferencias para futuras visitas y agregar datos compilados sobre el tráfico del sitio e interacción para que podamos ofrecer una mejor experiencia y herramientas en el futuro. Puede que contratemos con proveedores de servicio de tercera mano para que nos asistan en el mejor entendimiento de nuestros visitantes del sitio. A estos proveedores de servicio no se les permite usar la información recolectada a nuestras espaldas excepto para ayudarnos a conducir y mejorar nuestro trabajo.

- -

¿Revelamos alguna información a terceras manos?

- -

No vendemos, intercambiamos, ni de ninguna otra manera transferimos tu información personal identificable a terceras partes. Esto no incluye las terceras manos que nos asisten en operar nuestro sitio, conducción o trabajo, o en servirte, tanto como que éstas acepten en mantener esta información confidencial. Puede que también liberemos tu información cuando creamos que es apropiado para cumplir con la ley, enforzar nuestras políticas del sitio, o proteger la nuestra u otros derechos, propiedad, o seguridad. De todas formas, la información del visitante autorizado no-personal puede proveerse a otras partes por marketing, publicidad, u otros usos.

- -

Enlaces de terceras partes

- -

Ocasionalmente, a nuestra discreción, puede que incluyamos u ofrezcamos productos de terceras partes o servicios en nuestro sitio. Estas terceras partes tienen políticas de privacidad separadas e independientes. Por lo tanto no tenemos responsabilidad u obligación por el contenido y actividades de estos sitios enlazados. Sin embargo, buscamos proteger la integridad de nuestro sitio y dar la bienvenida a cualquier ayuda sobre estos sitios.

- -

Children's Online Privacy Protection Act Compliance (Cumplimiento de la Ley de la Protección Privada en Línea del Niño)

- -

Nuestro sitio y todos nuestros productos y servicios están dirigidos a gente que tiene al menos 13 años de edad. Si el servidor está alojado en EE.UU, y tienes menos de 13 años, no uses este sitio por los requerimientos del COPPA (Children's Online Privacy Protection Act).

- -

Solo Políticas de Privacidad en Línea

- -

Estas políticas de privacidad aplican únicamente a la información recolectada a través de nuestro sitio y no a información recolectada offline.

- - - -

Al usar nuestro sitio, estás consentido a nuestras políticas de privacidad del sitio.

- -

Cambios a nuestras Políticas de Privacidad

- -

Si decidimos cambiar nuestras políticas de privacidad, las publicaremos en esta página.

- -

Este documento está publicado bajo la licencia CC-BY-SA. Última vez actualizado el 31 de Mayo del 2013.

- -

Adaptado originalmente del discurso de las políticas de privacidad.

title: Términos del Servicio y Políticas de Privacidad de %{instance} themes: default: Mastodon diff --git a/config/locales/fa.yml b/config/locales/fa.yml index 86756c0..ed25ea8 100644 --- a/config/locales/fa.yml +++ b/config/locales/fa.yml @@ -613,74 +613,6 @@ fa: reblogged: بازبوقید sensitive_content: محتوای حساس terms: - body_html: | -

سیاست رازداری (Privacy Policy)

- -

ما چه اطلاعاتی را گردآوری می‌کنیم؟

- -

این سایت برخی از اطلاعات مربوط به شما را ثبت می‌کند. این موارد شامل اطلاعات ثبت‌نامی شماست، و نیز شامل نوشته‌هایی است که این‌جا می‌خوانید، می‌نویسید، یا واکنش‌هایی که به نوشته‌های دیگران نشان می‌دهید.

- -

وقتی که در این سایت ثبت‌نام می‌کنید، ممکن است از شما بخواهیم که نام و نشانی ایمیل خود را وارد کنید. البته بدون ثبت‌نام نیز می‌توان از این سایت بازدید کرد. برای تأیید ایمیل شما، ما یک نشانی اینترنتی یکتا را به آن می‌فرستیم. اگر آن نشانی را کسی باز کند، ما می‌فهمیم که آن شما بوده‌اید و بنابراین نشانی ایمیل متعلق به شماست.

- -

وقتی که عضو باشید و چیزی بنویسید، ما نشانی اینترنتی‌ای (IP) را که نوشته از آن آمده است ثبت می‌کنیم. سیاههٔ کاری (log) سرور شامل نشانی IP همهٔ درخواست‌ها به سرور است که ما شاید آن را هم ثبت کنیم.

- -

ما با اطلاعات شما چه کار می‌کنیم؟

- -

اطلاعاتی را که ما از شما ثبت می‌کنیم، ممکن است در موارد زیر به کار بروند:

- -
    -
  • برای شخصی‌سازی تجربهٔ کاربری شما — ما به کمک اطلاعات شما بهتر می‌توانیم نیازهای شما را برآورده کنیم.
  • -
  • برای بهتر کردن سایت — ما پیوسته می‌کوشیم تا خدمات این سایت را به کمک اطلاعات و بازخوردی که از شما می‌گیریم بهتر کنیم.
  • -
  • برای بهتر کردن خدمات به کاربران — ما به کمک اطلاعات شما به طور مؤثرتری می‌توانیم به درخواست‌های پشتیبانی شما پاسخ دهیم.
  • -
  • برای فرستادن ایمیل‌های دوره‌ای — ما گاهی به نشانی ایمیلی که وارد کرده‌اید نامه می‌فرستیم تا به درخواست‌های شما پاسخ دهیم یا شما را در جریان پاسخ دیگران به شما قرار دهیم.
  • -
- -

ما چگونه از اطلاعات شما محافظت می‌کنیم؟

- -

ما روش‌های امنیتی گوناگونی را پیاده کرده‌ایم تا امنیت اطلاعات شخصی شما هنگام ثبت، فرستاده‌شدن، و بازیابی آن‌ها حفظ شود.

- -

سیاست ما برای نگهداری اطلاعات شما چیست؟

- -

ما با حسن نیت تلاش می‌کنیم تا:

- -
    -
  • سیاههٔ کاری سرور که شامل نشانی IP همهٔ درخواست‌ها به این سرور است را بیشتر از ۹۰ روز ذخیره نکنیم.
  • -
  • نشانی IP مربوط به کاربران ثبت‌نام‌شده را بیشتر از ۵ سال نگه نداریم.
  • -
- -

آیا ما کوکی‌ها را به‌کار می‌بریم؟

- -

بله. کوکی‌ها پرونده‌های کوچکی هستند که یک سایت یا خدمات‌دهنده‌اش (اگر شما اجازه بدهید) از راه مرورگر در کامپیوتر شما ذخیره می‌کنند. به کمک این کوکی‌ها سایت می‌تواند مرورگر شما را بشناسد و اگر شما ثبت‌نام کرده باشید، حساب شما را به مرورگرتان مرتبط کند.

- -

ما به کمک کوکی‌ها ترجیحات شما را برای بازدیدهای آینده می‌فهمیم و ذخیره می‌کنیم و داده‌های جامعی دربارهٔ بازدیدها از سایت و برهمکنش‌ها با آن را تهیه می‌کنیم. به این ترتیب می‌توانیم در آینده تجربهٔ کاربری سایت و ابزارهای مربوط به آن را بهتر کنیم. برای داشتن درک بهتری از بازدیدکنندگان این سایت، ما گاهی از خدمات‌دهنده‌های دیگر نیز کمک می‌گیریم. این خدمات‌دهنده‌ها اجازه ندارند تا از اطلاعاتی که به جای ما جمع می‌کنند برای کاری به جز بهترکردن کار ما استفاده کنند.

- -

آیا ما اطلاعاتی به نهادهای دیگر فاش می‌کنیم؟

- -

ما اطلاعاتی را که بتواند شما را شناسایی کند به نهادهای دیگر نمی‌فروشیم، معامله نمی‌کنیم، یا به هر روش دیگری منتقل نمی‌کنیم. این شامل نهادهای مورد اعتمادی نمی‌شود که به ما در گرداندن این سایت یا انجام کارهایمان کمک می‌کنند، یا به شما خدمات می‌رسانند، تا جایی که آن‌ها این داده‌ها را محرمانه نگه دارند. ما همچنین ممکن است اطلاعات شما را به حکم قانون یا برای اِعمال سیاست‌های سایت، یا به خاطر حفظ حقوق، دارایی‌ها، یا امنیت خودمان یا دیگران منتشر کنیم. ما ممکن است اطلاعات بازدیدکنندگان سایت را که با آن نمی‌توان شما را شناسایی کرد برای بازاریابی، تبلیغات، یا هدف‌های دیگر به نهادهای دیگر ارائه دهیم.

- -

پیوند (لینک) به صفحه‌های دیگران

- -

ما گاهی ممکن است به صلاحدید خودمان محصولات یا خدمات دیگران را در این سایت بگنجانیم یا پیشنهاد دهیم. سایت‌های مرتبط با این محصولات و خدمات دارای سیاست‌های رازداری جداگانه و مستقل خودشان هستند. بنابراین ما مسئولیتی دربارهٔ محتوا و کنش‌های این سایت‌ها به عهده نمی‌گیریم. با این وجود، ما تلاش می‌کنیم که این سایت به درستی کار کند و از بازخورد شما برای چنین محصولات و خدماتی استقبال می‌کنیم.

- -

پیروی از قانون پشتیبانی از حریم خصوصی آنلاین کودکان

- -

سایت ما، محصولات و خدماتش همه برای کسانی است که دست‌کم ۱۳ سال سن داشته باشند. اگر این سرور در خاک ایالات متحدهٔ امریکا قرار دارد و سن شما کمتر از ۱۳ سال است، به خاطر رعایت قانون COPPA (Children's Online Privacy Protection Act) لطفاً این سایت را به کار نبرید.

- -

تنها سیاست رازداری آنلاین

- -

این سیاست رازداری آنلاین تنها مربوط به اطلاعاتی است که از راه سایت ما گردآوری می‌شود و شامل اطلاعاتی که به طور آفلاین گردآوری شده نیست.

- - - -

با استفاده از این سایت، شما موافقت خود را با سیاست رازداری ما اعلام می‌کنید.

- -

تغییرات در سیاست رازداری ما

- -

اگر ما سیاست رازداری خود را تغییر دهیم، این تغییرات را در این صفحه خواهیم نوشت.

- -

این نوشته تحت اجازه‌نامهٔ CC-BY-SA قرار دارد. تاریخ آخرین به‌روزرسانی آن ۱۰ خرداد ۱۳۹۲ است.

- -

این نوشته اقتباسی است از سیاست رازداری Discourse.

title: شرایط استفاده و سیاست رازداری %{instance} themes: default: ماستدون diff --git a/config/locales/fr.yml b/config/locales/fr.yml index d7371dc..4571cc3 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -664,74 +664,6 @@ fr: reblogged: a partagé sensitive_content: Contenu sensible terms: - body_html: | -

Politique de confidentialité

- -

Quelles données collectons-nous ?

- -

Nous collectons des données lorsque vous vous enregistrez sur notre site et les récoltons lorsque vous participez dans le forum en lisant, écrivant, et évaluant le contenu partagé ici.

- -

Lors de l’enregistrement sur notre site, il peut vous être demandé de renseigner votre nom et adresse électronique. Vous pouvez, cependant, visiter notre site sans inscription. Votre adresse électronique devra être vérifiée grâce à un courriel contenant un lien unique. Si ce lien est visité, nous savons que vous contrôlez cette adresse.

- -

Lors de l’inscription et de la publication de statuts, nous enregistrons l’adresse IP de laquelle les statuts proviennent. Nous pouvons également conserver des historiques serveurs qui contiendront l’adresse IP de chaque requête adressée à notre serveur.

- -

Que faisons-nous avec vos données ?

- -

Toute information que nous collectons pourra être utilisée d’une des manières suivantes :

- -
    -
  • Pour personnaliser votre expérience — vos données nous aident à mieux répondre à vos besoins individuels.
  • -
  • Pour améliorer notre site — nous faisons tout notre possible pour améliorer notre site en fonction des données, retours et suggestions que nous recevons.
  • -
  • Afin d’améliorer le support client — vos données nous aident à mieux répondre à vos requêtes et demandes de support.
  • -
  • Afin d’envoyer des courriels à intervalles réguliers — l’adresse électronique que vous renseignez peut être utilisée pour vous envoyer des données et notifications concernant des changements ou en réponse à votre nom d’utilisateur⋅ice, en réponse à vos demandes et/ou autres requêtes ou questions
  • -
- -

Comment protégeons-nous vos données ?

- -

Nous appliquons une multitude de mesures afin de maintenir la sécurité de vos données personnelles lorsque vous entrez, soumettez, ou accédez à ces dernières.

- -

Quelle est notre politique de conservation des données ?

- -

Nous nous efforçons de :

- -
    -
  • ne pas garder les historiques serveurs contenant l’adresse IP de chaque requête adressée à ce serveur plus de 90 jours ;
  • -
  • ne pas conserver les adresses IP associées aux utilisateur⋅trices et leur contenu plus de 5 ans.
  • -
- -

Utilisons-nous des « cookies » ?

- -

Oui. Les cookies sont de petits fichiers qu’un site ou prestataires de services transfèrent sur le disque dur de votre ordinateur par le biais de votre navigateur Web (si ce dernier le permet). Ces cookies permettent au site de reconnaître votre navigateur et, si vous disposez d’un compte, de l’associer à celui-ci.

- -

Nous utilisons les cookies pour enregistrer vos préférences pour de futures visites, compiler des données agrégées à propos du trafic et des interactions effectuées sur le site afin de proposer une meilleure expérience dans le futur. Nous pouvons contracter les services de tiers afin de nous aider à mieux comprendre les visiteurs de notre site. Ces tiers ont l’autorisation d’utiliser ces données seulement à des fins d’améliorations.

- -

Divulguons-nous des données à des tiers ?

- -

Nous n’échangeons pas, ne vendons pas ni effectuons de quelconques transferts avec des tiers d’informations permettant de vous identifier personnellement. Cela n’inclut pas les tiers de confiance qui nous aident à gérer notre entreprise et à vous servir tant que ces tiers s’accordent à garder lesdites informations confidentielles. Nous pouvons être amenés à délivrer vos informations lorsque jugé adéquat afin de respecter la loi, d’appliquer la politique de notre site, ou afin de protéger nos droits, ceux des autres, notre propriété ou sécurité. Cependant, aucune information permettant l’identification de nos visiteur⋅euse⋅s ne sera divulguée à des fins publicitaires, commerciales ou tout autre usage.

- -

Liens vers des tiers

- -

Nous pouvons être amenés à inclure ou offrir les services ou produits de tiers sur notre site. Ces tiers possèdent leur propre politique de confidentialité. Nous ne sommes donc pas responsables du contenu ou activités desdits tiers. Néanmoins, nous cherchons à protéger l’intégrité de notre site et sommes ouverts à toute remarque concernant ces tiers.

- -

Children's Online Privacy Protection Act

- -

Notre site, nos produits et services sont tous destinés à l’usage de personnes âgées de 13 ans ou plus. Si ce serveur est hébergé aux États-Unis et que vous êtes âgé⋅e de moins de 13 ans, au vu du COPPA (Children's Online Privacy Protection Act) n’utilisez pas ce site.

- -

Politique de confidentialité en ligne

- -

Cette politique de confidentialité en ligne s'applique uniquement aux informations collectées par le biais de notre site et non aux informations collectées hors ligne.

- - - -

En utilisant notre site, vous consentez à la présente politique de confidentialité.

- -

Changements de notre politique de confidentialité

- -

Si nous décidons d’apporter des changements à notre politique de confidentialité, nous les publierons sur cette page.

- -

Ce document est distribué sous licence CC-BY-SA. Il a été mis à jour pour la dernière fois le 31 mai 2013. Il a été traduit en français en juillet 2017.

- -

Originellement adapté à partir de la politique de confidentialité de Discourse.

title: "%{instance} Conditions d’utilisations et politique de confidentialité" themes: default: Mastodon diff --git a/config/locales/gl.yml b/config/locales/gl.yml index bddc1b7..f4ca7e8 100644 --- a/config/locales/gl.yml +++ b/config/locales/gl.yml @@ -665,74 +665,6 @@ gl: reblogged: promocionada sensitive_content: Contido sensible terms: - body_html: | -

Política de intimidade

- -

Qué información recollemos?

- -

Recollemos información sobre vostede cando se rexistra no noso sitio web e recollemos datos cando participa no foro lendo, escribindo e evaluando o contido compartido aquí.

- -

Cando se rexistra no noso sitio, podería solicitarselle o seu nome e enderezo de correo electrónico. Vostede podería, porén, visitar o noso sitio sin rexistrarse. O seu enderezo de correo será verificado con un correo con unha ligazón única. Si esa ligazón é visitada, saberemos que vostede controla ese enderezo de correo.

- -

Cando se rexistra e publica, almacenamos o enderezo IP desde onde publicou. Poderiamos tamén gardar rexistros do servidor que inclúan a IP de cada petición ao realizada ao servidor.

- -

Con qué fin utilizamos a súa información?

- -

Toda a información recollida de vostede podería ser utilizada dos seguintes xeitos:

- -
    -
  • Para individualizar a súa experiencia — a súa información axúdanos a respostar mellor as súas necesidades individuais.
  • -
  • Para mellorar o noso sitio —esforzámonos en mellorar o que ofrece o noso sitio baseándonos na información e críticas que vostede nos proporciona.
  • -
  • Para mellorar o servizo ao cliente —a súa información axúdanos a respostar máis eficientemente as súas peticións de servizo ao cliente e axuda.
  • -
  • Para enviar correos electrónicos periodicamente — O enderezo de correo que nos proporciona podería ser utilizado para enviarlle información, notificacións que solicitou sobre cambios ou asuntos ou en resposta ao ser nome de usuaria, responder a enquisas, e/ou outras peticións ou cuestións.
  • -
- -

Cómo protexemos a súa información?

- -

Implementamos varias medidas de seguridade para manter a seguiridade da súa información personal cando introduce, envía ou accede a súa información personal.

- -

Qué é a política de retención dos seus datos?

- -

Faremos un sincero esforzo para:

- -
    -
  • Manter rexistros do sistema con enderezos IP de todas as peticións feitas a este servidor no seu nome non máis de 90 días.
  • -
  • Manter os enderezos IP asociados a usuarias rexistradas e as súas mensaxes non máis de 5 anos.
  • -
- -

Utilizamos cookies?

- -

Si. As cookies son pequenos ficheiros que un sitio ou o seu proveedor de servizo transfire ao disco duro da súa computadora a través do navegador web (se vostede o permite). Estas cookies permiten ao sitio recoñer o seu navegador e, si ten unha conta rexistrada, asocialo coa súa conta.

- -

Utilizamos cookies para comprender e gardar as súas preferencias para futuras visitas e compilar datos agregados sobre o tráfico do sitio e interacción co sitio de tal xeito que no futuro poidamos ofrecer unha mellor experiencia de uso do sitio e ferramentas. Poderiamos contratar servizos de terceiros para axudarnos a entender mellor as nosas visitantes. Estos proveedores de servizo non teñen permiso para utilizar a información recollida no noso nome excepto para axudarnos a xestionar e mellorar o noso negocio.

- -

Mostramos información a terceiros alleos?

- -

Non vendemos, nin negociamos con, ou transmitimos de outros xeitos a axentes terceiros alleos a información que a información que a identifica personalmente. Esto non inclúe terceiros de confianza que non axudan a operar o sitio, xestionar o negocio, ou servila, así estas partes se comprometan coa confidencialidade dos datos. Poderiamos revelar a súa información cando creamos que facelo así é axeitado para cumplir coa lei, cumplir coas normas do sitio ou protexer os nosos dereitos, propiedades ou seguridade e os de outras. Porén, non se proporcionará información identificable a terceiros para publicidade, márquetin ou outros usos.

- -

Ligazóns a terceiros

- -

De xeito ocasional, a nosa discreción, poderiamos incluír ofertas de productos e servizos de terceiros no noso sitio. Estos sitios de terceiros teñen políticas de intimidade propias. Polo tanto non temos responsabilidade ou obligacións polo contido e actividades de esos sitios. Con todo, procuramos protexer a integridade do noso sitio e agradecemos calquer opinión e crítica sobre estos sitios.

- -

Cumplimento coa Children's Online Privacy Protection Act

- -

O noso sitio, productos e servizos están dirixidos as personas con 13 anos como mínimo. Si este servidor está en USA e vostede ten menos de 13 anos, a requerimento da COPPA (Children's Online Privacy Protection Act) non utilice este sitio web.

- -

Política de intimidade só en liña

- -

Esta política de intimidade aplícase só a información recollida no noso sitio e non a información recollida fora de liña.

- - - -

Utilizando o noso sitio, vostede acepta esta política de intimidade.

- -

Cambios na política de intimidade

- -

Si decidimos cambiar a nosa política de intimidade publicaremos esos cambios en esta páxina.

- -

Este documento ten licenza CC-BY-SA. Foi actualizado o 31 de maio de 2013.

- -

Adaptado do orixinal Discourse privacy policy.

title: "%{instance} Termos do Servizo e Política de Intimidade" themes: default: Mastodon diff --git a/config/locales/hu.yml b/config/locales/hu.yml index 6be82c1..2560b38 100644 --- a/config/locales/hu.yml +++ b/config/locales/hu.yml @@ -633,76 +633,6 @@ hu: reblogged: reblogolt sensitive_content: Szenzitív tartalom terms: - body_html: | -

Adatvédelmi és adatkezelési nyilatkozat

- -

Milyen információt gyűjtünk?

- -

Az oldalra történő regisztráció és a szolgáltatás használata - olvasás, tartalom létrehozása, tartalommegosztás - során információt gyűjtünk veled kapcsolatban.

- -

A regisztráció során kérhetjük nevedet és e-mail címedet. Az oldalt természetesen regisztráció nélkül is felkeresheted. Az e-mail címed megerősítése egy egyedi információt tartalmazó link segítségével történik. Mondott linkre kattintva ellenőrizzük, hogy valóban te vagy a cím kezelője.

- -

Regisztrált felhasználók esetében tülkök írásakor rögzítjük a felhasználó IP-címét. A szerver napjófájlja szintén tárolhatja ezt az IP-címet, valamint a szerverre érkező minden kérés küldő-oldali IP-címét.

- -

Mire használjuk a begyűjtött információt?

- -

Minden begyűjtött információt az alábbi okokból használhatunk fel:

- -
    -
  • A felhasználói élmény személyre szabásához — a tőled gyűjtött információ segítségével biztosíthatjuk számodra az egyedi igényeknek történő megfelelést.
  • -
  • Szolgáltatásunk fejlesztéséhez — folyamatosan igyekszünk fejlődni és jobbá válni, és ezt a tőled kapott adatok és visszajelzések is nagyban segítik.
  • -
  • Az ügyféltámogatás fejlesztéséhez — a gyűjtött adatok segítségével hatékonyabban támogathatjuk felhasználóinkat, ha azok segítségre szorulnak.
  • -
  • E-mail értesítések küldéséhez — a megadott e-mail címedre küldjük ki az általad igényelt értesítéseket, a szolgáltatásra vonatkozó információkat és a válaszokat a tőled beérkező megkeresésekre.
  • -
- -

Hogyan védjük a tőled gyűjtött információt?

- -

Bitonsági mechanizmusok egész sorát vetjük be annak érdekében, hogy biztosítsuk a tőled származó személyes és használatai adatok és információk biztonságát.

- -

Meddig tároljuk a tőled származó adatokat?

- -

Minden tőlünk telhetőt megteszünk annak érdekében, hogy

- -
    -
  • a szerver naplófájljaiban tárolt, a szerverre érkező kérések küldő-oldali IP-címét maximum 90 napig,
  • -
  • a regisztrált felhasználók tülkjeinek eredeti IP-címét pedig maximum 5 évig
  • -
- -

tároljuk.

- -

Használunk-e sütiket?

- -

Igen. A sütik olyan kisméretű fájlok, amelyeket a szolgáltatások vagy internet-szolgáltatók küldenek a felhasználó számítógépére a böngészőn keresztül (természetesen csak abban az esetben, ha a felhasználó ezt engedélyezi). Oldalunk ezen sütik segítségével ismerik fel a böngésződet és - amennyiben rendelkezel nálunk fiókkal - kötik össze azt a felhasználói fiókoddal.

- -

A sütik segítségével jobban megérthetjük használati szokásaidat, eltárolhatjuk beállításaidat következő látogatásodig, valamint így mérhetjük az oldal látogatottságát és használatát, mely adatok segítenek abban, hogy jobbá tehessük az általunk nyújtott szolgáltatást. Esetenként harmadik féllel is kapcsolatba léphetünk a kinyert használati adatok jobb megértése érdekében. Ezen harmadik felek számára azonban az adatok használata szigorú feltételekhez kötött: kizárólag az engedélyünkkel és királólag a mi szolgáltatásunk fejlesztésével összefüggésben használhatják azokat.

- -

Milyen információt adunk ki külső szereplőknek?

- -

Soha, semmilyen körülmények között nem adunk ki, át vagy el külső szereplőknek olyan adatot, amelynek segítségével egyes felhasználóink egyedileg azonosíthatók. Ez nem vonatkozik olyan harmadik felekre, melyek jelen szolgáltatás üzemeltetésében, javításában vagy támogatásában segítségünkre vannak – ezeket a feleket azonban titoktartási szerződés köti mondott adatokkal kapcsolatban. A gyűjtött adatokat ezen felül megfelelő meghagyás megléte esetén kiadhatjuk a törvény és a rendfenntartás képviselőinek, amennyiben ezen adatoknak jog-, élet- vagy vagyonvédelmi jelentőségük van. Hirdetési- és marketing-, valamint egyéb, a fentiekben nem érintett célból csak olyan adatok adhatók ki, amelyek nem teszik lehetővé az egyes felhasználók egyedi azonosítását.

- -

Harmadik felekre mutató hivatkozások

- -

Esetenként elhelyezhetünk harmadik fél által ajánlott termékekre vagy szolgáltatásokra mutató hivatkozásokat az oldalon. Ezen harmadik feleknek saját, tőlünk független adatvédelmi és adatkezelési nyilatkozatuk van. Ennek értelmében az oldal üzemeltetői semmilyen felelősséget nem tudnak vállalni az ezen harmadik fél által üzemeltetett oldalak viselkedésével és tartalmával kapcsolatban. Ugyanakkor arra törekszünk, hogy mindenben saját felhasználóink érdekeit képviseljük, így minden, a fenti harmadik felekkel kacsolatos visszejelzést szívesen veszünk.

- -

Megfelelés a Gyermekek Online Adatvédelméről Szóló Rendeletnek

- -

Az oldal, valamint az azon keresztül nyújtott szolgáltatás a 13 éven felülieket célozza. Amennyiben ez a szerver az Amerikai Egyesült Államok területén található és te nem vagy még 13 éves, a COPPA (Gyermekek Online Adatvédelméről Szóló Rendelet) értelmében kérjük ne használd ezt az oldalt és szolgáltatást. - -

Az adatvédelmi és adatkezelési nyilatkozat hatálya

- -

Jelen adatvédelmi és adatkezelési nyilatkozat kizárólag az oldalunkon keresztül gyűjtött online adatokra vonatkozik, offline módon gyűjtött adatokra nem terjed ki.

- - - -

Az oldal és a szolgáltatás használatával elfogadottnak tekinted jelen adatvédelmi és adatkezelési nyilatkozatot.

- -

A nyilatkozat módosításairól

- -

Amennyiben a jövőben módosítjuk jelen adatvédelmi és adatkezelési nyilatkozatunkat, a módosított szöveg ugyanezen oldalon lesz megtalálható.

- -

Jelen dokumentum a CC-BY-SA licenc alatt érhető el. Angol eredetijének utolsó módosítása: 2013. május 31.

- -

A dokumetum a Discourse adatvédelmi és adatkezelési nyilatkozatán alapul.

title: "%{instance} Felhasználási feltételek és Adatkezelési nyilatkozat" themes: default: Mastodon diff --git a/config/locales/ja.yml b/config/locales/ja.yml index 629d688..b943f0f 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -689,74 +689,6 @@ ja: reblogged: さんがブースト sensitive_content: 閲覧注意 terms: - body_html: | -

プライバシーポリシー

- -

どのような情報を収集するのですか?

- -

あなたがこのサイトに登録すると、ここで共有された情報を読んだり、書いたり、評価したりして、フォーラムでの情報を集める事ができます。

- -

このサイトに登録する際には、名前とメールアドレスの入力を求めることがあります。ただし、登録をすることなくこのサイトを利用することも可能です。あなたのメールアドレスは、固有のリンクを含んだメールで確認されます。そのリンクにアクセスした場合にメールアドレスを制御することとなります。

- -

アカウントを登録し、投稿を行った際にはその投稿が行われたIPアドレスを記録します。また、このサーバーに対する全てのリクエストはIPアドレスを含むサーバーログとして保管されます。

- -

自分の情報を何に使うのですか?

- -

このサイトで収集された情報は、次のいくつかの方法で使用されます:

- -
    -
  • パーソナライズ・エクスペリエンス — あなたの情報は、あなたや他のユーザーのニーズに対応するために役立ちます。
  • -
  • サイトの改善・最適化 — このサービスはあなたから受け取った情報やフィードバックに基づいて提供されるサイトの改善を行いつづけます。
  • -
  • サービスの向上 — あなたの情報は、ユーザーからの要求やサポートへより効果的に対応するために役立ちます。
  • -
  • 定期メールの送信 — メールアドレスは、情報の送信、トピックの変更やユーザー名に関係するお知らせ、お問い合わせに関する返答、その他のリクエストや質問に関してお知らせするために使用されます。
  • -
- -

自分の情報はどのように保護されるのですか?

- -

このサービスはあなたの個人情報の入力、送信、またはアクセスに際してあなたの個人情報の安全性を維持するために様々なセキュリティ手段をとっています。

- -

データ保持のポリシーはどのようになっていますか?

- -

このサービスはデータ保持に関して次のことを行うよう努めます。:

- -
    -
  • このサーバーへのすべての要求に対して、IPアドレスを含むサーバーログを90日以内に渡って保持します。
  • -
  • 登録されたユーザーとその投稿に関連付けされたIPアドレスを5年以内に渡って保持します。
  • -
- -

クッキーを使用していますか?

- -

はい。クッキーはあなたがウェブブラウザ上で許可した場合にコンピュータのストレージに転送される小さなファイルです。これらのクッキーを使用すると、サイトでブラウザが識別され、登録済みのアカウントを持っている場合は登録済みのアカウントに関連付けがされます。

- -

クッキーを使用して、今後再度閲覧された場合に前回のデータから設定を呼び出したり、今後の改善のためにサイトのトラフィックやサイトの相互作用に関する集計データを作成します。このサービスは、サイトを訪れた方との理解を深めるために、第三者のサービス提供者と契約することがあります。これらのサービス提供者というものは、このサービスでの業務を行ったり、改善するためにこのサービスの代わって収集された情報を使用することはできません。

- -

このサイトは外部に何らかの情報を開示していますか?

- -

私たちは、個人を特定出来る情報を外部へ販売、取引、または他の方法で渡すことはありません。これには、このサイトを操作したり、業務を行ったり、サービスを提供するのに役立つ信頼できる第三者は含まれません。法令遵守、サイトポリシーの施行、このサービスや他の人の権利、財産または安全の保護のために適切であると判断した場合に、あなたの情報を公開する場合があります。ただし、マーケティングや広告、その他の目的で匿名での訪問者情報を他者へ提供することができます。

- -

サードパーティのリンク

- -

必要に応じて、このサービスの方針にもとづいてこのサイトや第三者のサービスを提供することがあります。これらの第三者のサイトには、個別の独立したプライバシーポリシーがあります。従って、これらのリンク先のサイトに関するコンテンツや活動にかんしては一切責任を負いません。ですが、サイトの完全性やこれらのサイトに関するフィードバックは非常に重要なものであると認識しております。

- -

子供のオンライン・プライバシー保護法

- -

このサイト、製品、サービスはすべて13歳以上の人を対象としております。このサーバーが米国にあり、13歳未満の場合はCOPPA (Children's Online Privacy Protection Act) にもとづいてこのサイトを使用しないでください。

- -

オンライン限定のプライバシーポリシー

- -

このオンライン・プライバシーポリシーは、このサイトを通じて収集された情報のみに適用され、オフラインで収集される情報には適用されません。

- - - -

このサービスを使用することにより、このサイトのプライバシーポリシーに同意するものとします。

- -

プライバシーポリシーの変更

- -

プライバシーポリシーを変更する場合は、このページへ変更内容を掲載します。

- -

この文章のライセンスはCC-BY-SAです。このページは2017年5月6日が最終更新です。

- -

オリジナルの出典 Discourse privacy policy.

title: "%{instance} 利用規約・プライバシーポリシー" themes: default: Mastodon diff --git a/config/locales/ko.yml b/config/locales/ko.yml index ba55b35..72c98bc 100644 --- a/config/locales/ko.yml +++ b/config/locales/ko.yml @@ -667,74 +667,6 @@ ko: reblogged: 님이 부스트 했습니다 sensitive_content: 민감한 컨텐츠 terms: - body_html: | -

사생활 정책

- -

우리가 어떤 정보를 수집하나요?

- -

우리는 귀하가 우리 사이트에 가입할 때, 그리고 우리의 포럼에 읽고, 쓸 때, 그리고 포럼에 게시된 공유된 콘텐츠를 평가할 때 정보를 수집합니다.

- -

우리 사이트에 가입할 때, 귀하는 이름과 이메일 주소 입력을 요구 받을 수 있습니다. 하지만 귀하는 우리의 사이트를 가입하지 않고도 방문할 수 있습니다. 귀하의 이메일 주소는 고유한 링크를 담고 있는 이메일로 검증 될 것입니다. 만약 귀하가 그 링크를 방문한다면, 우리는 귀하가 그 이메일 주소를 소유하고 있다는 것을 알 수 있습니다.

- -

가입을 하고 글을 쓸 때, 우리는 글이 어떤 IP에서 작성 되었는지 기록합니다. 또한 우리는 모든 요청에 대한 IP 주소를 담고 있는 서버 로그를 보관할 수 있습니다.

- -

우리가 귀하의 정보를 어떻게 사용하나요?

- -

우리가 귀하에게서 수집하는 어떠한 정보는 다음 중 하나와 같은 방법으로 사용될 수 있습니다:

- -
    -
  • 귀하의 경험을 개인화 하기 위해 — 귀하의 정보는 우리가 귀하의 개별적인 요구에 더 나은 응답을 할 수 있도록 돕습니다.
  • -
  • 우리의 사이트를 개선하기 위해 — 우리는 귀하에게 받는 정보와 귀하에게 받는 피드박을 바탕으로 우리의 사이트 내용을 계속 개선하기 위해 노력합니다.
  • -
  • 고객 서비스를 개선하기 위해 — 귀하의 정보는 우리가 귀하의 서비스 요청과 지원 요청에 더 효과적으로 응답할 수 있게 돕습니다.
  • -
  • 주기적인 이메일을 보내기 위해 — 귀하가 제공하는 이메일 주소는 귀하에게 정보, 귀하가 요청하는 주제에 대한 변경과 귀하의 유저 이름에 대한 응답에 대한 알림, 문의에 대한 답, 또는 다른 요청과 질문을 보내는 데에 사용될 수 있습니다.
  • -
- -

우리가 어떻게 귀하의 정보를 보호하나요?

- -

우리는 귀하가 개인정보를 입력, 제출, 접근 할 때 귀하의 개인정보의 안전을 유지하기 위한 여러가지 보안 방법을 구현합니다.

- -

정보 보관 정책은 어떻게 되나요?

- -

우리는 다음과 같이 노력 하겠습니다:

- -
    -
  • 모든 요청에 대한 IP 주소를 담고 있는 서버 로그를 최대 90일까지 보관합니다.
  • -
  • 등록된 사용자와 관련된 IP 주소와 그들의 게시물들을 최대 5년까지 보관합니다.
  • -
- -

쿠키를 사용하나요?

- -

네. 쿠키는 사이트나 서비스 제공자가 (만약 허용하신다면) 웹 브라우저를 통해 귀하의 컴퓨터 하드디스크에 전송하는 작은 파일들입니다. 이 쿠키들은 사이트가 귀하의 브라우저를 인식하게 하고, 만약 가입한 계정이 있다면 브라우저를 가입한 계정과 연관짓는 일을 가능하게 합니다.

- -

우리는 쿠키를 사용해 귀하의 환경설정을 미래의 방문을 위해 저장하고, 사이트 접근 기록과 사이트 상호작용 기록을 모아 미래에 우리가 더 나은 사이트 경험과 도구를 제공할 수 있도록 합니다. 우리는 제 3자의 서비스 제공자와 계약하여 우리 사이트의 방문자에 대해 더 나은 이해를 하기 위해 도움을 받을 수 있습니다. 이러한 서비스 제공자들은 우리가 더 나은 서비스를 제공하도록 돕는 목적 외에는 이 정보를 사용할 수 없습니다.

- -

우리가 외부에 정보를 공개하나요?

- -

우리는 귀하를 식별할 수 있는 정보를 외부에 팔거나, 거래하거나, 전송하지 않습니다. 이는 우리가 우리의 사이트를 운영하고, 사업을 하고, 귀하에게 서비스를 제공하는 데에 도움을 주는 믿을 수 있는 제 3자의 서비스 제공자를 포함하지 않으며, 이는 그 서비스 제공자가 이 정보를 비밀로 취급하는 것에 동의하는지에 따라 다릅니다. 우리는 또한 법을 지키는 것, 우리 사이트의 정책을 집행하는 것, 우리와 다른 사람들의 권리, 재산, 안전을 보호하는 것으로 인해 정보 공개가 적합하다고 생각되면 정보를 공개 할 수 있습니다. 그러나, 귀하를 식별할 수 없는 방문자 정보는 외부에 마케팅, 광고, 혹은 다른 용도로 제공될 수 있습니다.

- -

제 3자 링크

- -

종종, 우리의 재량에 따라 우리의 사이트에 제 3자의 상품이나 서비스를 포함하거나 제공할 수 있습니다. 이러한 제 3자 사이트는 독립적인 개인정보 정책을 가지고 있습니다. 이러한 링크된 제 3자 사이트의 내용과 활동에 대해서 우리는 어떠한 의무와 법적 책임을 가지고 있지 않습니다. 그래도 우리는 그 사이트에 대한 피드백을 환영하며, 우리 사이트만의 정체성을 유지하도록 노력하겠습니다.

- -

아동 온라인 사생활 보호법 준수

- -

우리 사이트, 제품과 서비스는 적어도 13살인 사람들에게 맞춰져 있습니다. 만약 이 서버가 미합중국에 위치하고, 귀하가 13살이 되지 않는다면, COPPA (Children's Online Privacy Protection Act) 의 요구사항에 따라 이 사이트를 이용하지 마십시오.

- -

온라인 사생활 정책 한정

- -

이 온라인 사생활 정책은 우리 사이트를 통해 수집된 정보에게만 적용되며, 오프라인에서 수집된 정보에는 적용되지 않습니다.

- - - -

우리의 사이트를 사용함으로서, 귀하는 우리 사이트의 사생활 정책에 동의합니다.

- -

사생활 정책의 변경

- -

만약 우리가 사생활 정책을 변경하도록 결정한다면, 우리는 그 변경사항을 이 페이지에 게시하겠습니다.

- -

이 문서는 CC-BY-SA 정책으로 배포됩니다. 마지막으로 2013년 3월 31일에 수정되었습니다.

- -

Discourse privacy policy에서 가져옴.

title: "%{instance} 이용약관과 개인정보 취급 방침" themes: default: 마스토돈 diff --git a/config/locales/nl.yml b/config/locales/nl.yml index f3488f7..a46bb72 100644 --- a/config/locales/nl.yml +++ b/config/locales/nl.yml @@ -665,74 +665,6 @@ nl: reblogged: boostte sensitive_content: Gevoelige inhoud terms: - body_html: | -

Privacy Policy

- -

What information do we collect?

- -

We collect information from you when you register on our site and gather data when you participate in the forum by reading, writing, and evaluating the content shared here.

- -

When registering on our site, you may be asked to enter your name and e-mail address. You may, however, visit our site without registering. Your e-mail address will be verified by an email containing a unique link. If that link is visited, we know that you control the e-mail address.

- -

When registered and posting, we record the IP address that the post originated from. We also may retain server logs which include the IP address of every request to our server.

- -

What do we use your information for?

- -

Any of the information we collect from you may be used in one of the following ways:

- -
    -
  • To personalize your experience — your information helps us to better respond to your individual needs.
  • -
  • To improve our site — we continually strive to improve our site offerings based on the information and feedback we receive from you.
  • -
  • To improve customer service — your information helps us to more effectively respond to your customer service requests and support needs.
  • -
  • To send periodic emails — The email address you provide may be used to send you information, notifications that you request about changes to topics or in response to your user name, respond to inquiries, and/or other requests or questions.
  • -
- -

How do we protect your information?

- -

We implement a variety of security measures to maintain the safety of your personal information when you enter, submit, or access your personal information.

- -

What is your data retention policy?

- -

We will make a good faith effort to:

- -
    -
  • Retain server logs containing the IP address of all requests to this server no more than 90 days.
  • -
  • Retain the IP addresses associated with registered users and their posts no more than 5 years.
  • -
- -

Do we use cookies?

- -

Yes. Cookies are small files that a site or its service provider transfers to your computer's hard drive through your Web browser (if you allow). These cookies enable the site to recognize your browser and, if you have a registered account, associate it with your registered account.

- -

We use cookies to understand and save your preferences for future visits and compile aggregate data about site traffic and site interaction so that we can offer better site experiences and tools in the future. We may contract with third-party service providers to assist us in better understanding our site visitors. These service providers are not permitted to use the information collected on our behalf except to help us conduct and improve our business.

- -

Do we disclose any information to outside parties?

- -

We do not sell, trade, or otherwise transfer to outside parties your personally identifiable information. This does not include trusted third parties who assist us in operating our site, conducting our business, or servicing you, so long as those parties agree to keep this information confidential. We may also release your information when we believe release is appropriate to comply with the law, enforce our site policies, or protect ours or others rights, property, or safety. However, non-personally identifiable visitor information may be provided to other parties for marketing, advertising, or other uses.

- -

Third party links

- -

Occasionally, at our discretion, we may include or offer third party products or services on our site. These third party sites have separate and independent privacy policies. We therefore have no responsibility or liability for the content and activities of these linked sites. Nonetheless, we seek to protect the integrity of our site and welcome any feedback about these sites.

- -

Children's Online Privacy Protection Act Compliance

- -

Our site, products and services are all directed to people who are at least 13 years old. If this server is in the USA, and you are under the age of 13, per the requirements of COPPA (Children's Online Privacy Protection Act) do not use this site.

- -

Online Privacy Policy Only

- -

This online privacy policy applies only to information collected through our site and not to information collected offline.

- - - -

By using our site, you consent to our web site privacy policy.

- -

Changes to our Privacy Policy

- -

If we decide to change our privacy policy, we will post those changes on this page.

- -

This document is CC-BY-SA. It was last updated May 31, 2013.

- -

Originally adapted from the Discourse privacy policy.

title: "%{instance} Terms of Service and Privacy Policy" themes: default: Mastodon diff --git a/config/locales/no.yml b/config/locales/no.yml index 3adf71b..d5edb39 100644 --- a/config/locales/no.yml +++ b/config/locales/no.yml @@ -633,74 +633,6 @@ reblogged: fremhevde sensitive_content: Følsomt innhold terms: - body_html: | -

Personvernserklæring

- -

Hvilke opplysninger samler vi?

- -

Vi samler opplysninger fra deg når du registrerer deg på nettstedet vårt, og vi samler data når du deltar på forumet ved å lese, skrive og evaluere innholdet som deles her.

- -

Når du registrerer deg på nettstedet vårt, kan du bli bedt om å oppgi navnet og e-postadressen din. Imidlertid kan du besøke nettstedet vårt uten å registrere deg. E-postadressen din vil bli bekreftet med en e-post som inneholder en unik lenke. Hvis siden den lenker til, blir besøkt, vet vi at du har kontroll over e-postadressen.

- -

Når du registrerer deg og skriver innlegg, registrerer vi IP-adressen som innlegget stammer fra. Vi kan også oppbevare logger som inkluderer IP-adressen til alle forespørslene sendt til tjeneren vår.

- -

Hva bruker vi opplysningene dine til?

- -

Alle opplysningene vi samler fra deg, kan bli brukt på en av følgende måter:

- -
    -
  • For å gjøre opplevelsen din mer personlig. Opplysningene dine hjelper oss å svare bedre på dine individuelle behov.
  • -
  • For å forbedre nettstedet vårt. Vi jobber konstant for å forbedre nettstedets tilbud basert på opplysningene og tilbakemeldingene vi mottar fra deg.
  • -
  • For å forbedre vår kundeservice. Dine opplysninger hjelper oss å svare mer effektivt på dine forespørsler sendt til kundeservice eller behov om støtte.
  • -
  • For å sende periodiske e-poster. E-postadressen du oppgir, kan bli brukt til å sende deg informasjon, påminnelser som du ber om ved endringer av emner eller ved svar til brukernavnet ditt, til henvendelser, og/eller andre forspørsler eller andre spørsmål.
  • -
- -

Hvordan sikrer vi opplysningene?

- -

Vi gjennomfører flere sikkerhetstiltak for å holde personopplysningene dine sikre når du skriver inn, lagrer eller henter dem.

- -

Hva er retningslinjene deres for lagring av data?

- -

Vi vil forsøke i god tro å:

- -
    -
  • Ikke oppbevare tjener-logger som inneholder IP-adressen til alle forespørslene til denne tjeneren i lenger enn i 90 dager.
  • -
  • Ikke oppbevare IP-adressene forbundet med registrerte brukere og deres innlegg lenger enn i 5 år.
  • -
- -

Bruker vi informasjonskapsler?

- -

Ja. Informasjonskapsler er små filer som et nettsted eller dets tjenesteleverandør overfører til harddisken på datamaskinen din gjennom nettleseren din (dersom du tillater det). Disse informasjonskapslene gjør det mulig for nettstedet å gjenkjenne nettleseren din og, dersom du har en konto, knytte nettleseren til den.

- -

Vi bruker informasjonskapsler for å forstå og lagre preferansene dine for fremtidige besøk og for å samle aggregatdata om trafikk på og samhandling med nettstedet slik at vi kan tilby bedre opplevelser og verktøy på nettstedet i fremtiden. Vi kan inngå avtaler med tredjeparts tjenesteleverandører for å bistå oss i å forstå besøkerne våres bedre. Disse tjenesteleverandørene har ikke lov til å bruke opplysningene samlet på våres vegne unntatt til å hjelpe oss å gjennomføre og forbedre anliggendet vårt.

- -

Gir vi noen opplysninger videre til andre parter?

- -

Vi verken selger, handler med eller overfører på noen annen måte til andre parter dine identifiserbare personopplysninger. Dette inkluderer ikke tredjeparter som har vår tillit og bistår oss i å drive nettstedet, utføre våre anliggender eller yter tjenester til deg, så lenge disse partene samtykker til å behandle disse opplysningene fortrolig. Vi kan også frigi opplysningene dine dersom vi tror at å frigi dem er hensiktsmessig for å overholde loven, håndheve nettstedet retningslinjer eller beskytte våre og andres rettigheter. Imidlertid kan opplysninger som ikke er personlig identifiserbare, bli delt med andre parter for markedsføring, reklame eller annet bruk.

- -

Tredjeparts lenker

- -

Av og til, etter skjønn, kan vil inkludere eller tilby tredjeparts produkter eller tjenester på nettstedet vårt. Disse tredjeparts nettstedene har separate og selvstendige personvernerklæringer. Vi bærer derfor intet ansvar eller forpliktelser for innholdet eller aktivitetene til disse nettstedene det lenkes til. Ikke mindre prøver vi å bevare vår eget nettsteds integritet og ønsker enhver tilbakemelding om disse nettstedene velkomne.

- -

Overensstemmelse med Children's Online Privacy Protection Act

- -

Nettstedet er rettet mot folk som er minst 13 år gamle. Dersom denne tjeneren er i USA, og du er under 13 år i henhold til kravene i COPPA (Children's Online Privacy Protection Act), ikke bruk dette nettstedet.

- -

Personvernerklæring bare for nettet

- -

Denne nett-personvernerklæringen gjelder bare for informasjon samlet gjennom nettstedet vårt og ikke for opplysninger samlet når en er frakoblet.

- - - -

Ved å bruke dette nettstedet samtykker du til nettstedets personvernerklæring.

- -

Endringer i vår personvernerklæring

- -

Dersom vi beslutter å endre personvernerklæringen vår, vil vi publisere disse endringene på denne siden.

- -

Dette dokumentet er lisensiert under CC-BY-SA. De ble sist oppdatert 12. april 2017.

- -

Dokumentet er en adoptert og endret versjon fra Discourse privacy policy.

title: "%{instance} Personvern og villkår for bruk av nettstedet" themes: default: Mastodon diff --git a/config/locales/oc.yml b/config/locales/oc.yml index 49b1df8..f8e819c 100644 --- a/config/locales/oc.yml +++ b/config/locales/oc.yml @@ -742,74 +742,6 @@ oc: reblogged: a partejat sensitive_content: Contengut sensible terms: - body_html: | -

Politica de confidencialitat

- -

Quinas informacions reculhèm ?

- -

Collectem informacions sus vos quand vos marcatz sus nòstre site e juntem las donadas quand participatz a nòstre forum en legir, escriure e notar lo contengut partejat aquí.

- -

Pendent l’inscripcion podèm vos demandar vòstre nom e adreça de corrièl. Podètz çaquelà visitar nòstre site sens vos marcar. Verificarem vòstra adreça amb un messatge donant un ligam unic. Se clicatz sul ligam sauprem qu’avètz lo contraròtle de l’adreça.

- -

Quand sètz marcat e que publicatz quicòm, enregistrem l’adreça IP d’origina. Podèm tanben salvagardar los jornals del servidor que tenon l’adreça IP de totas las demandas fachas al nòstre servidor.

- -

Qué fasèm de vòstras informacions ?

- -

Totas las informacions que collectem de vos pòdon servir dins los cases seguents :

- -
    -
  • Per personalizar vòstre experiéncia — vòstras informacions nos ajudaràn a respondre melhor a vòstres besonhs individuals.
  • -
  • Per melhorar nòstre site — s’eforcem de longa a melhorar çò que nòstre site ofrís segon las informacions e los comentaris que recebèm de vòstra part.
  • -
  • Per melhorar nòstre servici client — vòstras informacions nos ajudan per respondre amb mai eficacitat a vòstras demandas d’assisténcia.
  • -
  • Per enviar periodicament de corrièls — Podèm utilizar l’adreça qu’avètz donada per vos enviar d’informacions e de notificacions que demandatz tocant de cambiaments dins los subjèctes del forum o en responsa a vòstre nom d’utilizaire, en responsa a una demanda, e/o tota autra question.
  • -
- -

Cossí protegèm vòstras informacions ?

- -

Apliquem tota una mena de mesuras de seguretat per manténer la fisança de vòstras informacions personalas quand las picatz, mandatz, o i accedètz.

- -

Quala es vòstra politica de conservacion de donadas ?

- -

Farem esfòrces per :

- -
    -
  • Gardar los jornals del servidor que contenon las adreças IP de totas las demandas al servidor pas mai de 90 jorns.
  • -
  • Gardar las adreças IP ligadas als utilizaires e lors publicacions pas mai de 5 ans.
  • -
- -

Empleguem de cookies ?

- -

Òc-ben. Los cookies son de pichons fichièrs qu’un site o sos provesidors de servicis plaçan dins lo disc dur de vòstre ordenador via lo navigator Web (Se los acceptatz). Aqueles cookies permeton al site de reconéisser vòstre navigator e se tenètz un compte enregistrat de l’associar a vòstre compte.

- -

Empleguem de cookies per comprendre e enregistrar vòstras preferéncias per vòstras visitas venentas, per recampar de donadas sul trafic del site e las interaccions per dire que posquem ofrir una melhora experiéncia del site e de las aisinas pel futur. Pòt arribar que contractèssem amb de provesidors de servicis tèrces per nos ajudar a comprendre melhor nòstres visitors. Aqueles provesidors an pas lo drech que d’utilizar las donadas collectadas per nos ajudar a menar e melhorar nòstre afar.

- -

Divulguem d’informacions a de tèrces ?

- -

Vendèm pas, comercem o qualque transferiment que siasque a de tèrces vòstras informacions personalas identificablas. Aquò inclutz pas los tèrces partits de confisança que nos assiston a menar nòstre site, menar nòstre afar o vos servir, baste que son d’acòrd per gardar aquelas informacions confidencialas. Pòt tanben arribar que liberèssem vòstras informacions quand cresèm qu’es apropriat d’o far per se sometre a la lei, per refortir nòstras politicas, o per protegir los dreches, proprietats o seguritat de qualqu’un o de nosautres. Pasmens es possible que mandèssem d’informacions non-personalas e identificablas de nòstres visitors a d’autres partits per d’utilizacion en marketing, publicitat o un emplec mai.

- -

Ligams de tèrces

- -

Pòt arribar, a nòstra discrecion, qu’incluguèssem o ofriguèssem de produches o servicis de tèrces partits sus nòstre site. Aqueles sites tèrces an de politicas de confidencialitats separadas e independentas. En consequéncia avèm pas cap de responsabilitat pel contengut e las activitats d’aqueles sites ligats. Pasmens cerquem de protegir l’integritat de nòstre site e aculhèm los comentaris tocant aqueles sites.

- -

Conformitat amb la lei de proteccion de la confidencialitat dels mainatges

- -

Nòstre site, nòstres produches e servicis son totes destinats a de monde d’almens 13 ans. S’aqueste servidor se tròba en los Estats Units per acontentar las exigéncias del COPPA (Children's Online Privacy Protection Act) utilizetz pas aqueste site.

- -

Politica de confidencialitat en linha solament

- -

Aquesta politica de confidencialitat s’aplica pas qu’a las informacions collectadas via nòstre site e non pas a las informacions collectadas fòra linha.

- - - -

N’utilizant nòstre site, consentètz a nòstra politica de confidencialitat.

- -

Cambiament dins nòstra politica de confidencialitat

- -

Se decidèm de cambiar nòstra politica de confidencialitat, publicarem los cambiaments sus aquesta pagina.

- -

Aqueste document es jos licéncia CC-BY-SA. Darrièra mesa a jorn lo 31 de mai de 2013

- -

Prima adaptacion de la politica de confidencialitat de Discourse.

title: Condicions d’utilizacion e politica de confidencialitat de %{instance} time: formats: diff --git a/config/locales/pl.yml b/config/locales/pl.yml index c626360..64c1ff5 100644 --- a/config/locales/pl.yml +++ b/config/locales/pl.yml @@ -700,74 +700,6 @@ pl: reblogged: podbił sensitive_content: Wrażliwa zawartość terms: - body_html: | -

Polityka prywatności

- -

Jakie informacje zbieramy?

- -

Zbieramy informacje podane przy rejestracji i treści utworzone w trakcie korzystania z serwisu.

- -

Podczas rejestracji, możesz otrzymać prośbę o podanie adresu e-mail. Możesz jednak odwiedzać stronę bez rejestracji. Adres zostanie zweryfikowany przez kliknięcie w link wysłany w wiadomości. Dzięki temu wiemy, że jesteś właścicielem tego adresu.

- -

Podczas rejestracji i tworzenia wpisów, Twój adres IP jest zapisywany na naszych serwerach. Możemy też przechowywać adres IP użyty przy każdej operacji w serwisie.

- -

Jak wykorzystujemy zebrane informacje?

- -

Zebrane informacje mogą zostać w jednym z następujących celach:

- -
    -
  • Aby poprawić wrażenia — informacje o Tobie pomagają w dostosowywaniu serwisu do Twoich potrzeb.
  • -
  • Aby usprawnić stronę — nieustannie staramy się ulepszyć stronę na podstawie informacji o Tobie i Twoich opinii.
  • -
  • Aby usprawnić obsługę klienta — informacje pomogą obsłudze klienta utrzymywać kontakt z Tobą.
  • -
  • Aby okazjonalnie wysyłać wiadomości e-mail — Na podany adres e-mail mogą zostać wysłane wiadomości o wspomnieniu o Tobie we wpisach, przejrzeniu Twojego zgłoszenia i innych interakcji z Tobą.
  • -
- -

Jak zabezpieczamy dane?

- -

Korzystamy z wielu zabezpieczeń, aby utrudnić osobom niepowołanym dostęp do danych, które wprowadzasz, publikujesz i czytasz.

- -

Jak długo przechowujecie dane?

- -

Dołożymy wszelkich starań, aby przechowywać:

- -
    -
  • dzienniki serwera zawierające adresy IP przypisane do każdych operacji nie dłużej niż 90 dni.
  • -
  • adresy IP przypisane do użytkowników i ich wpisów nie dłużej niż 5 lat.
  • -
- -

Czy używamy plików cookies?

- -

Tak. Pliki cookies (zwane często ciasteczkami) są małymi zbiorami danych przechowywanych na Twoim dysku przez stronę internetową, aby rozpoznawać przeglądarkę i powiązać ją (jeżeli jesteś zarejestrowany/a) z Twoim kontem, jeżeli na to pozwolisz.

- -

Możemy używać ciasteczek, aby skonfigurować stronę na podstawie zapisanych preferencji, oraz dostosować ją do potrzeb innych użytkowników. Możemy korzystać z usług firm trzecich pomagających w zrozumieniu potrzeb użytkownika. Te usługi nie mogą korzystać ze zdobytych danych w celach innych niż analiza pomagająca ulepszać ten serwis.

- -

Czy przekazujecie dane podmiotów trzecim?

- -

Nie dokonujemy transakcji danych pozwalających na identyfikację Twojej osoby umieszczonych na tym serwisie. Nie oznacza to, że nie przekazujemy ich zaufanym podmiotom, które korzystają z nich poufnie. Możemy jednak udostępniać dane, jeżeli jest to wymagane prawnie, lub dla utrzymania bezpieczeństwa strony i innych użytkowników. W celach marketingowych (i podobnych) mogą zostać użyte jedynie dane niepozwalające na identyfikację osoby.

- -

Odnośniki do treści stron trzecich

- -

Czasem na stronie mogą pojawić się odnośniki do stron trzecich. Mają one odrębne regulaminy i politykę prywatności. Nie odpowiadamy więc za zawartość tych stron. Dokładamy jednak wszelkich starań, aby nie stanowiły one zagrożenia, prosimy jednak o opinie na temat ich wykorzystania.

- -

Children's Online Privacy Protection Act Compliance

- -

Ta strona i usługa jest przeznaczona dla osób, które ukończyły 13 lat. Jeżeli serwer znajduje się na terenie USA i nie masz ukończonych 13 lat, zgodnie z amerykańską ustawą COPPA (Children's Online Privacy Protection Act) nie możesz korzystać z tego serwisu.

- -

Polityka prywatności dotyczy tylko Internetu

- -

Ta polityka prywatności dotyczy jedynie danych zbieranych w Internecie, nie tych, które przechowywane są na Twoim komputerze, np. pliki cookies.

- - - -

Korzystanie ze strony jest równoznaczne z akceptacją naszej polityki prywatności.

- -

Zmiany w naszej polityce prywatności

- -

Jeżeli zdecydujemy się na zmiany w polityce prywatności, zmiany pojawią się na tej stronie.

- -

Dokument jest dostępny na licencji CC-BY-SA. Ostatnio modyfikowany 31 maja 2013, przetłumaczony 4 lipca 2017. Tłumaczenie (mimo dołożenia wszelkich starań) może nie być w pełni poprawne.

- -

Tekst bazuje na polityce prywatności Discourse.

title: Zasady korzystania i polityka prywatności %{instance} themes: default: Mastodon diff --git a/config/locales/pt-BR.yml b/config/locales/pt-BR.yml index 589f44f..c1225d3 100644 --- a/config/locales/pt-BR.yml +++ b/config/locales/pt-BR.yml @@ -665,74 +665,6 @@ pt-BR: reblogged: compartilhou sensitive_content: Conteúdo sensível terms: - body_html: | -

Política de privacidade

- -

Que informações nós coletamos?

- -

Coletamos informações quando você se cadastra em nosso site e capturamos dados quando você participa do fórum lendo, escrevendo e analisando o conteúdo aqui compartilhado.

- -

Quando você se registrar em nosso site, será requisitado que você ceda seu nome e endereço de e-mail. Você pode, porém, visitar nosso site sem se cadastrar. Seu endereço de e-mail será verificado por uma mensagem contendo um link único. Se este link for visitado, saberemos que você controla este endereço de e-mail.

- -

Quando registrado e postando, nós gravamos o endereço de IP de onde a postagem se originou. Nós também podemos reter logs de serviores que incluem o endereço de IP em cada requisição para o nosso servidor.

- -

Para que usamos essas informações?

- -

Quaisquer das informações que coletamos podem ser usadas das seguintes formas:

- -
    -
  • Para personalizar a sua experiência — suas informações nos ajudam a nos adequar melhor às suas necessidades individuais.
  • -
  • Para melhorar nosso site — nós continuamente nos esforçamos para aprimorar nosso site baseados na informação e no feedback que recebemos de você.
  • -
  • Para aprimorar o serviço ao consumidor — suas informações nos ajudam a responder efetivamente às suas requisições e solicitações por suporte.
  • -
  • Para mandar e-mails periódicos — O endereço de e-mail que você forneceu pode ser usado para lhe enviar informações, notificações que você requisitar sobre mudanças a determinados tópicos ou menções ao seu nome de usuário, responder requisições e/ou solicitações e perguntas.
  • -
- -

Como protegemos as suas informações?

- -

Nós implementamos uma variedade de medidas de segurança para manter a segurança de suas informações pessoais quando você insere, submete ou acessa as suas informações pessoais.

- -

Qual a sua política de retenção de dados?

- -

Faremos esforços de boa fé para:

- -
    -
  • Reter logs de servidor contendo o endereço IP de todas as requisições a este servidor por não mais que 90 dias.
  • -
  • Reter os endereços IP associados a usuários cadastrados e suas postagens por não mais que 5 anos.
  • -
- -

Nós usamos cookies?

- -

Sim. Cookies são pequenos arquivos que um site ou o provedor de serviço transfere para o armazenamento interno de seu computador através de seu navegador (se você permitir). Estes cookies habilitam o site para reconhecer o seu navegador e, se você ter um cadastro, associá-lo a esta conta.

- -

Nós usamos cookies para entender e salvar as suas preferências para futuras visitas e compilar dados agregados sobre o tráfego do site para que possamos oferecer melhores experiências e ferramentas no futuro. Nós podemos contratar serviços de terceiros para nos auxiliar a entender melhor nossos visitantes. Estes provedores de serviço não são autoriza usar as informações coletadas em nosso nome exceto para nos ajudar a conduzir e aprimorar nosso funcionamento.

- -

Nós revelamos informações para terceiros?

- -

Nós não vendemos, tocamos ou transferimos para terceiros informações pessoais que te identificam. Isso não inclui partes em que confiamos para nos ajudar a operar nosso site, conduzir nosso funcionamento ou servir você desde que estes terceiros concordem em manter essas informações em segredo. Nós também podemos prover as suas informações para obedecer ordens judiciais, reforçar nossas políticas ou proteger nossos direitos ou de outrem, propriedades ou segurança. Entretanto, informações pessoais não identificáveis podem ser enviadas para outras partes para marketing, propaganda e outros usos.

- -

Links de terceiros

- -

Ocasionalmente, à nossa discrição, podemos icluir ou oferecer produtos ou serviços de terceiros em nosso site. Estes terceiros têm políticas de privacidade separadas e independentes. Nós, portanto, não nos responsabilizamos pelo conteúdo e atividades destes sites de terceiros. Occasionally, at our discretion, we may include or offer third party products or services on our site. Não obstante, nós procuramos proteger a integridade de nosso site e todo feedback sobre estes sites de terceiros é bem-vindo.

- -

Obediência ao Ato de Proteção da Privacidade Online de Crianças

- -

Nosso site, produtos e serviços são todos direcionados a pessoas que têm pelo menos 13 anos de idade. Se este servidor estiver nos EUA, e você tiver menos de 13 anos, pelos requerimentos da COPPA (Children's Online Privacy Protection Act) não use este site.

- -

Política de Apenas Privacidade Online

- -

Esta política de privacidade online se aplica somente a informações coletadas por nosso site e não a informações coletadas offline.

- - - -

Usando o nosso site, você concorda com a nossa política de privacidade.

- -

Mudanças em nossa Política de Privacidade

- -

Se decidirmos mudar a nossa política de privacidade, publicaremos as mudanças nesta página.

- -

Este documento é CC-BY-SA. A sua última atualização aconteceu em 31 de maio de 2013.

- -

Originalmente adaptado da política de privacidade do Discourse.

title: "%{instance} Termos de Serviço e Política de Privacidade" themes: default: Mastodon diff --git a/config/locales/pt.yml b/config/locales/pt.yml index 5012e17..27d4e88 100644 --- a/config/locales/pt.yml +++ b/config/locales/pt.yml @@ -635,74 +635,6 @@ pt: reblogged: boosted sensitive_content: Conteúdo sensível terms: - body_html: | -

Política de privacidade

- -

Quais são as informações que recolhemos?

- -

Recolhemos informações quando te registas no nosso site e capturamos dados quando participas do fórum lendo, escrevendo e analisando o conteúdo aqui partilhado.

- -

Quando te registas no nosso site, será requisitado que você ceda seu nome e endereço de e-mail. Você pode, porém, visitar nosso site sem se cadastrar. Seu endereço de e-mail será verificado por uma mensagem contendo um link único. Se este link for visitado, saberemos que você controla este endereço de e-mail.

- -

Quando registrado e postando, nós gravamos o endereço de IP de onde a postagem se originou. Nós também podemos reter logs de serviores que incluem o endereço de IP em cada requisição para o nosso servidor.

- -

Para que usamos essas informações?

- -

Quaisquer das informações que coletamos podem ser usadas das seguintes formas:

- -
    -
  • Para personalizar a sua experiência — suas informações nos ajudam a nos adequar melhor às suas necessidades individuais.
  • -
  • Para melhorar nosso site — nós continuamente nos esforçamos para aprimorar nosso site baseados na informação e no feedback que recebemos de você.
  • -
  • Para aprimorar o serviço ao consumidor — suas informações nos ajudam a responder efetivamente às suas requisições e solicitações por suporte.
  • -
  • Para mandar e-mails periódicos — O endereço de e-mail que você forneceu pode ser usado para lhe enviar informações, notificações que você requisitar sobre mudanças a determinados tópicos ou menções ao seu nome de usuário, responder requisições e/ou solicitações e perguntas.
  • -
- -

Como protegemos as suas informações?

- -

Nós implementamos uma variedade de medidas de segurança para manter a segurança de suas informações pessoais quando você insere, submete ou acessa as suas informações pessoais.

- -

Qual a sua política de retenção de dados?

- -

Faremos esforços de boa fé para:

- -
    -
  • Reter logs de servidor contendo o endereço IP de todas as requisições a este servidor por não mais que 90 dias.
  • -
  • Reter os endereços IP associados a usuários cadastrados e suas postagens por não mais que 5 anos.
  • -
- -

Nós usamos cookies?

- -

Sim. Cookies são pequenos arquivos que um site ou o provedor de serviço transfere para o armazenamento interno de seu computador através de seu navegador (se você permitir). Estes cookies habilitam o site para reconhecer o seu navegador e, se você ter um cadastro, associá-lo a esta conta.

- -

Nós usamos cookies para entender e salvar as suas preferências para futuras visitas e compilar dados agregados sobre o tráfego do site para que possamos oferecer melhores experiências e ferramentas no futuro. Nós podemos contratar serviços de terceiros para nos auxiliar a entender melhor nossos visitantes. Estes provedores de serviço não são autoriza usar as informações coletadas em nosso nome exceto para nos ajudar a conduzir e aprimorar nosso funcionamento.

- -

Nós revelamos informações para terceiros?

- -

Nós não vendemos, tocamos ou transferimos para terceiros informações pessoais que te identificam. Isso não inclui partes em que confiamos para nos ajudar a operar nosso site, conduzir nosso funcionamento ou servir você desde que estes terceiros concordem em manter essas informações em segredo. Nós também podemos prover as suas informações para obedecer ordens judiciais, reforçar nossas políticas ou proteger nossos direitos ou de outrem, propriedades ou segurança. Entretanto, informações pessoais não identificáveis podem ser enviadas para outras partes para marketing, propaganda e outros usos.

- -

Links de terceiros

- -

Ocasionalmente, à nossa discrição, podemos icluir ou oferecer produtos ou serviços de terceiros em nosso site. Estes terceiros têm políticas de privacidade separadas e independentes. Nós, portanto, não nos responsabilizamos pelo conteúdo e atividades destes sites de terceiros. Occasionally, at our discretion, we may include or offer third party products or services on our site. Não obstante, nós procuramos proteger a integridade de nosso site e todo feedback sobre estes sites de terceiros é bem-vindo.

- -

Obediência ao Ato de Proteção da Privacidade Online de Crianças

- -

Nosso site, produtos e serviços são todos direcionados a pessoas que têm pelo menos 13 anos de idade. Se este servidor estiver nos EUA, e você tiver menos de 13 anos, pelos requerimentos da COPPA (Children's Online Privacy Protection Act) não use este site.

- -

Política de Apenas Privacidade Online

- -

Esta política de privacidade online se aplica somente a informações coletadas por nosso site e não a informações coletadas offline.

- - - -

Usando o nosso site, você concorda com a nossa política de privacidade.

- -

Mudanças em nossa Política de Privacidade

- -

Se decidirmos mudar a nossa política de privacidade, publicaremos as mudanças nesta página.

- -

Este documento é CC-BY-SA. A sua última atualização aconteceu em 31 de maio de 2013.

- -

Originalmente adaptado da política de privacidade do Discourse.

title: "%{instance} Termos de Serviço e Política de Privacidade" themes: default: Mastodon diff --git a/config/locales/ru.yml b/config/locales/ru.yml index 108ca33..176ace9 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -679,45 +679,6 @@ ru: reblogged: продвинул(а) sensitive_content: Чувствительный контент terms: - body_html: | -

Privacy Policy

-

What information do we collect?

-

We collect information from you when you register on our site and gather data when you participate in the forum by reading, writing, and evaluating the content shared here.

-

When registering on our site, you may be asked to enter your name and e-mail address. You may, however, visit our site without registering. Your e-mail address will be verified by an email containing a unique link. If that link is visited, we know that you control the e-mail address.

-

When registered and posting, we record the IP address that the post originated from. We also may retain server logs which include the IP address of every request to our server.

-

What do we use your information for?

-

Any of the information we collect from you may be used in one of the following ways:

-
    -
  • To personalize your experience — your information helps us to better respond to your individual needs.
  • -
  • To improve our site — we continually strive to improve our site offerings based on the information and feedback we receive from you.
  • -
  • To improve customer service — your information helps us to more effectively respond to your customer service requests and support needs.
  • -
  • To send periodic emails — The email address you provide may be used to send you information, notifications that you request about changes to topics or in response to your user name, respond to inquiries, and/or other requests or questions.
  • -
-

How do we protect your information?

-

We implement a variety of security measures to maintain the safety of your personal information when you enter, submit, or access your personal information.

-

What is your data retention policy?

-

We will make a good faith effort to:

-
    -
  • Retain server logs containing the IP address of all requests to this server no more than 90 days.
  • -
  • Retain the IP addresses associated with registered users and their posts no more than 5 years.
  • -
-

Do we use cookies?

-

Yes. Cookies are small files that a site or its service provider transfers to your computer's hard drive through your Web browser (if you allow). These cookies enable the site to recognize your browser and, if you have a registered account, associate it with your registered account.

-

We use cookies to understand and save your preferences for future visits and compile aggregate data about site traffic and site interaction so that we can offer better site experiences and tools in the future. We may contract with third-party service providers to assist us in better understanding our site visitors. These service providers are not permitted to use the information collected on our behalf except to help us conduct and improve our business.

-

Do we disclose any information to outside parties?

-

We do not sell, trade, or otherwise transfer to outside parties your personally identifiable information. This does not include trusted third parties who assist us in operating our site, conducting our business, or servicing you, so long as those parties agree to keep this information confidential. We may also release your information when we believe release is appropriate to comply with the law, enforce our site policies, or protect ours or others rights, property, or safety. However, non-personally identifiable visitor information may be provided to other parties for marketing, advertising, or other uses.

-

Third party links

-

Occasionally, at our discretion, we may include or offer third party products or services on our site. These third party sites have separate and independent privacy policies. We therefore have no responsibility or liability for the content and activities of these linked sites. Nonetheless, we seek to protect the integrity of our site and welcome any feedback about these sites.

-

Children's Online Privacy Protection Act Compliance

-

Our site, products and services are all directed to people who are at least 13 years old. If this server is in the USA, and you are under the age of 13, per the requirements of COPPA (Children's Online Privacy Protection Act) do not use this site.

-

Online Privacy Policy Only

-

This online privacy policy applies only to information collected through our site and not to information collected offline.

- -

By using our site, you consent to our web site privacy policy.

-

Changes to our Privacy Policy

-

If we decide to change our privacy policy, we will post those changes on this page.

-

This document is CC-BY-SA. It was last updated May 31, 2013.

-

Originally adapted from the Discourse privacy policy.

title: Условия обслуживания и политика конфиденциальности %{instance} themes: default: Mastodon diff --git a/config/locales/sr-Latn.yml b/config/locales/sr-Latn.yml index 2d98404..8d39d35 100644 --- a/config/locales/sr-Latn.yml +++ b/config/locales/sr-Latn.yml @@ -625,74 +625,6 @@ sr-Latn: reblogged: podržano sensitive_content: Osetljiv sadržaj terms: - body_html: | -

Privacy Policy

- -

What information do we collect?

- -

We collect information from you when you register on our site and gather data when you participate in the forum by reading, writing, and evaluating the content shared here.

- -

When registering on our site, you may be asked to enter your name and e-mail address. You may, however, visit our site without registering. Your e-mail address will be verified by an email containing a unique link. If that link is visited, we know that you control the e-mail address.

- -

When registered and posting, we record the IP address that the post originated from. We also may retain server logs which include the IP address of every request to our server.

- -

What do we use your information for?

- -

Any of the information we collect from you may be used in one of the following ways:

- -
    -
  • To personalize your experience — your information helps us to better respond to your individual needs.
  • -
  • To improve our site — we continually strive to improve our site offerings based on the information and feedback we receive from you.
  • -
  • To improve customer service — your information helps us to more effectively respond to your customer service requests and support needs.
  • -
  • To send periodic emails — The email address you provide may be used to send you information, notifications that you request about changes to topics or in response to your user name, respond to inquiries, and/or other requests or questions.
  • -
- -

How do we protect your information?

- -

We implement a variety of security measures to maintain the safety of your personal information when you enter, submit, or access your personal information.

- -

What is your data retention policy?

- -

We will make a good faith effort to:

- -
    -
  • Retain server logs containing the IP address of all requests to this server no more than 90 days.
  • -
  • Retain the IP addresses associated with registered users and their posts no more than 5 years.
  • -
- -

Do we use cookies?

- -

Yes. Cookies are small files that a site or its service provider transfers to your computer's hard drive through your Web browser (if you allow). These cookies enable the site to recognize your browser and, if you have a registered account, associate it with your registered account.

- -

We use cookies to understand and save your preferences for future visits and compile aggregate data about site traffic and site interaction so that we can offer better site experiences and tools in the future. We may contract with third-party service providers to assist us in better understanding our site visitors. These service providers are not permitted to use the information collected on our behalf except to help us conduct and improve our business.

- -

Do we disclose any information to outside parties?

- -

We do not sell, trade, or otherwise transfer to outside parties your personally identifiable information. This does not include trusted third parties who assist us in operating our site, conducting our business, or servicing you, so long as those parties agree to keep this information confidential. We may also release your information when we believe release is appropriate to comply with the law, enforce our site policies, or protect ours or others rights, property, or safety. However, non-personally identifiable visitor information may be provided to other parties for marketing, advertising, or other uses.

- -

Third party links

- -

Occasionally, at our discretion, we may include or offer third party products or services on our site. These third party sites have separate and independent privacy policies. We therefore have no responsibility or liability for the content and activities of these linked sites. Nonetheless, we seek to protect the integrity of our site and welcome any feedback about these sites.

- -

Children's Online Privacy Protection Act Compliance

- -

Our site, products and services are all directed to people who are at least 13 years old. If this server is in the USA, and you are under the age of 13, per the requirements of COPPA (Children's Online Privacy Protection Act) do not use this site.

- -

Online Privacy Policy Only

- -

This online privacy policy applies only to information collected through our site and not to information collected offline.

- - - -

By using our site, you consent to our web site privacy policy.

- -

Changes to our Privacy Policy

- -

If we decide to change our privacy policy, we will post those changes on this page.

- -

This document is CC-BY-SA. It was last updated May 31, 2013.

- -

Originally adapted from the Discourse privacy policy.

title: Uslovi korišćenja i politika privatnosti instance %{instance} themes: default: Mastodont diff --git a/config/locales/sr.yml b/config/locales/sr.yml index 2daf329..af4c6a8 100644 --- a/config/locales/sr.yml +++ b/config/locales/sr.yml @@ -625,74 +625,6 @@ sr: reblogged: подржано sensitive_content: Осетљив садржај terms: - body_html: | -

Privacy Policy

- -

What information do we collect?

- -

We collect information from you when you register on our site and gather data when you participate in the forum by reading, writing, and evaluating the content shared here.

- -

When registering on our site, you may be asked to enter your name and e-mail address. You may, however, visit our site without registering. Your e-mail address will be verified by an email containing a unique link. If that link is visited, we know that you control the e-mail address.

- -

When registered and posting, we record the IP address that the post originated from. We also may retain server logs which include the IP address of every request to our server.

- -

What do we use your information for?

- -

Any of the information we collect from you may be used in one of the following ways:

- -
    -
  • To personalize your experience — your information helps us to better respond to your individual needs.
  • -
  • To improve our site — we continually strive to improve our site offerings based on the information and feedback we receive from you.
  • -
  • To improve customer service — your information helps us to more effectively respond to your customer service requests and support needs.
  • -
  • To send periodic emails — The email address you provide may be used to send you information, notifications that you request about changes to topics or in response to your user name, respond to inquiries, and/or other requests or questions.
  • -
- -

How do we protect your information?

- -

We implement a variety of security measures to maintain the safety of your personal information when you enter, submit, or access your personal information.

- -

What is your data retention policy?

- -

We will make a good faith effort to:

- -
    -
  • Retain server logs containing the IP address of all requests to this server no more than 90 days.
  • -
  • Retain the IP addresses associated with registered users and their posts no more than 5 years.
  • -
- -

Do we use cookies?

- -

Yes. Cookies are small files that a site or its service provider transfers to your computer's hard drive through your Web browser (if you allow). These cookies enable the site to recognize your browser and, if you have a registered account, associate it with your registered account.

- -

We use cookies to understand and save your preferences for future visits and compile aggregate data about site traffic and site interaction so that we can offer better site experiences and tools in the future. We may contract with third-party service providers to assist us in better understanding our site visitors. These service providers are not permitted to use the information collected on our behalf except to help us conduct and improve our business.

- -

Do we disclose any information to outside parties?

- -

We do not sell, trade, or otherwise transfer to outside parties your personally identifiable information. This does not include trusted third parties who assist us in operating our site, conducting our business, or servicing you, so long as those parties agree to keep this information confidential. We may also release your information when we believe release is appropriate to comply with the law, enforce our site policies, or protect ours or others rights, property, or safety. However, non-personally identifiable visitor information may be provided to other parties for marketing, advertising, or other uses.

- -

Third party links

- -

Occasionally, at our discretion, we may include or offer third party products or services on our site. These third party sites have separate and independent privacy policies. We therefore have no responsibility or liability for the content and activities of these linked sites. Nonetheless, we seek to protect the integrity of our site and welcome any feedback about these sites.

- -

Children's Online Privacy Protection Act Compliance

- -

Our site, products and services are all directed to people who are at least 13 years old. If this server is in the USA, and you are under the age of 13, per the requirements of COPPA (Children's Online Privacy Protection Act) do not use this site.

- -

Online Privacy Policy Only

- -

This online privacy policy applies only to information collected through our site and not to information collected offline.

- - - -

By using our site, you consent to our web site privacy policy.

- -

Changes to our Privacy Policy

- -

If we decide to change our privacy policy, we will post those changes on this page.

- -

This document is CC-BY-SA. It was last updated May 31, 2013.

- -

Originally adapted from the Discourse privacy policy.

title: Услови коришћења и политика приватности инстанце %{instance} themes: default: Мастодонт diff --git a/config/locales/sv.yml b/config/locales/sv.yml index 8ce6b31..f85ed6e 100644 --- a/config/locales/sv.yml +++ b/config/locales/sv.yml @@ -656,74 +656,6 @@ sv: reblogged: boostad sensitive_content: Känsligt innehåll terms: - body_html: | -

Integritetspolicy

- -

Vilken information samlar vi in

- -

Vi samlar in information från dig när du registrerar dig på vår webbplats och samlar in data när du deltar i forumet genom att läsa, skriva och utvärdera innehållet som delas här.

- -

När du registrerar dig på vår webbplats kan du bli ombedd att ange ditt namn och din e-postadress. Du kan dock besöka vår webbplats utan att registrera dig. Din e-postadress kommer att verifieras med ett e-postmeddelande som innehåller en unik länk. Om den länken besöks vet vi att du kontrollerar e-postadressen.

- -

När vi registrerar och postar registrerar vi den IP-adress som posten härstammar från. Vi kan också behålla serverns loggar som innehåller IP-adress för varje begäran till vår server.

- -

Vad använder vi din information för?

- -

Vilken som helst information vi samlar in från dig kan användas på något av följande sätt:

- -
    -
  • För att personifiera din upplevelse — Din information hjälper oss att bättre svara på dina individuella behov.
  • -
  • För att förbättra vår webbplats — Vi strävar kontinuerligt efter att förbättra våra erbjudanden på webbplatsen baserat på information och feedback vi mottar från dig.
  • -
  • För att förbättra kundtjänst — Din information hjälper oss att effektivt svara på dina kundserviceförfrågningar och supportbehov.
  • -
  • För att skicka periodiska e-postmeddelanden — Den e-postadress du anger kan användas för att skicka information, meddelanden som du begär om ändringar i ämnen eller som svar på ditt användarnamn, svara på förfrågningar och / eller andra förfrågningar eller frågor.
  • -
- -

Hur skyddar vi din information?

- -

Vi genomför en rad säkerhetsåtgärder för att upprätthålla säkerheten för din personliga information när du anger, lämnar in eller har tillgång till din personliga information.

- -

Vad är policyn för lagring av data?

- -

Vi kommer att göra en ansträngning för:

- -
    -
  • Behåll serverloggar som innehåller IP-adressen för alla förfrågningar till den här servern inte mer än 90 dagar.
  • -
  • Behåll IP-adresserna i samband med registrerade användare och deras inlägg inte längre än 5 år.
  • -
- -

Använder vi cookies?

- -

Ja. Cookies är små filer som en webbplats eller tjänstleverantör överför till datorns hårddisk via din webbläsare (om du tillåter). Dessa cookies tillåter webbplatsen att känna igen din webbläsare och, om du har ett registrerat konto, associerar det med ditt registrerade konto.

- -

Vi använder cookies för att förstå och spara dina inställningar för framtida besök och sammanställa sammanlagda data om webbplatsstrafik och webbplatsinteraktion så att vi kan erbjuda bättre sajtupplevelser och verktyg i framtiden. Vi kan komma överens med tredje parts tjänsteleverantörer för att hjälpa oss att bättre förstå våra besökare. Dessa tjänsteleverantörer får inte använda den information som samlas in för vår räkning utom för att hjälpa oss att bedriva och förbättra vår verksamhet.

- -

Avslöjar vi information till utomstående parter?

- -

Vi säljer inte, handlar eller på annat sätt överför dina personuppgifter till utomstående parter. Det här omfattar inte betrodda tredje parter som hjälper oss att driva vår webbplats, bedriva vår verksamhet eller service dig, så länge dessa parter är överens om att hålla denna information konfidentiell. Vi kan också släppa din information när vi anser att utgåvan är lämplig för att följa lagen, tillämpa vår webbplatspolicy eller skydda vår eller andra rättigheter, egendom eller säkerhet. Däremot kan personuppgifter som inte identifieras personligen lämnas till andra parter för marknadsföring, reklam eller annan användning.

- -

Tredjepartslänkar

- -

Ibland kan vi, efter eget gottfinnande, inkludera eller erbjuda produkter från tredje part eller tjänster på vår webbplats. Dessa tredje parts webbplatser har separata och oberoende sekretesspolicyer. Vi har därför inget ansvar eller ansvar för innehållet och aktiviteterna för dessa länkade webbplatser. Ändå försöker vi skydda integriteten på vår webbplats och välkomna eventuella återkopplingar om dessa webbplatser.

- -

Children's Online Privacy Protection Act Compliance

- -

Vår webbplats, produkter och tjänster riktas alla till personer som är minst 13 år gamla. Om den här servern är i USA, och du är under 13 år, enligt kraven i COPPA (Children's Online Privacy Protection Act) ska du inte använda denna sida.

- -

Endast online sekretesspolicy

- -

Denna online sekretesspolicy gäller endast information som samlas in via vår webbplats och inte till information som samlas in offline.

- - - -

Genom att använda vår webbplats godkänner du vår hemsida sekretesspolicy.

- -

Ändringar i vår sekretesspolicy

- -

Om vi bestämmer oss för att ändra vår integritetspolicy, lägger vi in de ändringar på den här sidan.

- -

This document is CC-BY-SA. It was last updated May 31, 2013.

- -

Ursprungligen anpassad från Discourse integritetspolicy.

title: "%{instance} Användarvillkor och Sekretesspolicy" themes: default: Mastodon diff --git a/config/locales/zh-CN.yml b/config/locales/zh-CN.yml index 1254651..be868e6 100644 --- a/config/locales/zh-CN.yml +++ b/config/locales/zh-CN.yml @@ -623,74 +623,6 @@ zh-CN: reblogged: 转嘟 sensitive_content: 敏感内容 terms: - body_html: | -

隐私权政策

- -

我们收集什么信息?

- -

我们从你在我们站点注册开始从你那开始收集信息,并收集关于你在论坛的阅读和写作的数据,并评估分享的内容。

- -

当在我们站点注册时,你可能被要求输入你的名字和邮件地址。然而你可以在不用注册的情况下访问站点。你的邮件地将通过一个独一无二的链接验证。如果链接被访问了,我们就知道你控制了该邮件地址。

- -

当已注册和发帖时,我们记录发布帖子时的 IP 地址。我们也可能保留服务器日志,其中包括了每一个向我们服务器的请求。

- -

我们如何使用你的信息?

- -

从你那收集的任何数据将以以下方式使用:

- -
    -
  • 改进你的个人体验 — 你的信息帮助我们更好地满足你的个人需求。
  • -
  • 改进我们的站点 — 我们基于信息和我们从你那收到的反馈不断地试图改进我们的站点。
  • -
  • 改善我们的客户服务 — 你的信息帮助我们更有效地回应用户服务请求和支持。
  • -
  • 用于发送阶段性的邮件 — 你提供的邮件地址可能用于接受信息、你想看到的通知或与你用户名有关的回复和询问,或是其他的请求和问题。
  • -
- -

我们如何保护你的信息?

- -

我们实现了一系列的安全措施保证你输入、提交或者访问你个人信息的数据安全。

- -

数据保存政策是什么?

- -

我们将善意地:

- -
    -
  • 保存 90 天内的所有向服务器的包含 IP 地址的请求。
  • -
  • 保存 5 年内已注册用户和与他们的帖子有关的 IP 地址。
  • -
- -

我们使用 Cookie 吗?

- -

是的。Cookie 是网站或它的服务商通过网页浏览器存储在你电脑硬盘上的小文件(如果你同意)。这些 Cookie 使站点能分辨你的浏览器,并且,如果你注册了一个账户,与你的注册账户关联。

- -

我们使用 Cookie 为之后的访问和编译一小段关于站点流量和交互的数据来判断并保存你的个人设置,这样我们可以在之后提供更好的站点体验和工具。我们可能使用第三方服务商来帮助我们更好地理解我们的站点访客。这些服务商是不允许代表我们使用收集的信息,除非是在帮助我们执行和改进我们的站点。

- -

我们会在站外提供任何信息吗?

- -

我们绝不销售、交易或任何向外转移你个人信息的行为。这不包括帮助我们管理站点、改进站点或给你提供服务的第三方团体,这些团体需要保证对这些信息保密。当我们认为提交你的信息符合法律、我们的站点政策或保护我们或其他人的权利、知识产权或安全时,我们也可能提交你的信息。然而,非个人访问信息可能供其他团体使用,用于市场、广告或其他用途。

- -

第三方链接

- -

偶尔地,根据我们的判断,我们可能在我们的站点上包括或提供第三方团体的产品或服务。这些第三方站点用于独立和不同的隐私政策。因此我们对链接到的站点或活动没有责任和权利。尽管如此,我们寻求保护我们的整个站点并且欢迎你给这些站点反馈。

- -

儿童在线隐私保护法案合规

- -

我们的站点、产品和服务提供给 13 岁以上的人们。如果服务器位于美国,并且你小于 13 岁,根据儿童在线隐私保护法案合规,不要使用这个站点。

- -

仅用于在线隐私政策

- -

这个线上隐私政策只适用于通过我们站点收集到的信息,并不包括线下收集的信息。

- - - -

你使用站点的同时,代表你同意了我们网站的隐私政策。

- -

隐私政策的更改

- -

如果我们决定更改我们的隐私政策,我们将在此页更新这些改变。

- -

文档以 CC-BY-SA 发布。最后更新时间为2013年5月31日。

- -

原文出自 Discourse 隐私权政策

title: "%{instance} 使用条款和隐私权政策" time: formats: From b08ab329f4d149fd414e0539574f49062c571a8a Mon Sep 17 00:00:00 2001 From: Isatis <515462+Reverite@users.noreply.github.com> Date: Wed, 4 Apr 2018 13:25:34 -0700 Subject: [PATCH 087/381] retrieve custom emoji list via API instead of before page load (#7047) --- app/javascript/mastodon/actions/custom_emojis.js | 37 +++++++++++++++++++++++ app/javascript/mastodon/containers/mastodon.js | 4 +++ app/javascript/mastodon/reducers/custom_emojis.js | 17 +++++------ app/serializers/initial_state_serializer.rb | 6 ---- 4 files changed, 49 insertions(+), 15 deletions(-) create mode 100644 app/javascript/mastodon/actions/custom_emojis.js diff --git a/app/javascript/mastodon/actions/custom_emojis.js b/app/javascript/mastodon/actions/custom_emojis.js new file mode 100644 index 0000000..aa37bc4 --- /dev/null +++ b/app/javascript/mastodon/actions/custom_emojis.js @@ -0,0 +1,37 @@ +import api from '../api'; + +export const CUSTOM_EMOJIS_FETCH_REQUEST = 'CUSTOM_EMOJIS_FETCH_REQUEST'; +export const CUSTOM_EMOJIS_FETCH_SUCCESS = 'CUSTOM_EMOJIS_FETCH_SUCCESS'; +export const CUSTOM_EMOJIS_FETCH_FAIL = 'CUSTOM_EMOJIS_FETCH_FAIL'; + +export function fetchCustomEmojis() { + return (dispatch, getState) => { + dispatch(fetchCustomEmojisRequest()); + + api(getState).get('/api/v1/custom_emojis').then(response => { + dispatch(fetchCustomEmojisSuccess(response.data)); + }).catch(error => { + dispatch(fetchCustomEmojisFail(error)); + }); + }; +}; + +export function fetchCustomEmojisRequest() { + return { + type: CUSTOM_EMOJIS_FETCH_REQUEST, + }; +}; + +export function fetchCustomEmojisSuccess(custom_emojis) { + return { + type: CUSTOM_EMOJIS_FETCH_SUCCESS, + custom_emojis, + }; +}; + +export function fetchCustomEmojisFail(error) { + return { + type: CUSTOM_EMOJIS_FETCH_FAIL, + error, + }; +}; diff --git a/app/javascript/mastodon/containers/mastodon.js b/app/javascript/mastodon/containers/mastodon.js index d171044..b29898d 100644 --- a/app/javascript/mastodon/containers/mastodon.js +++ b/app/javascript/mastodon/containers/mastodon.js @@ -6,6 +6,7 @@ import { showOnboardingOnce } from '../actions/onboarding'; import { BrowserRouter, Route } from 'react-router-dom'; import { ScrollContext } from 'react-router-scroll-4'; import UI from '../features/ui'; +import { fetchCustomEmojis } from '../actions/custom_emojis'; import { hydrateStore } from '../actions/store'; import { connectUserStream } from '../actions/streaming'; import { IntlProvider, addLocaleData } from 'react-intl'; @@ -19,6 +20,9 @@ export const store = configureStore(); const hydrateAction = hydrateStore(initialState); store.dispatch(hydrateAction); +// load custom emojis +store.dispatch(fetchCustomEmojis()); + export default class Mastodon extends React.PureComponent { static propTypes = { diff --git a/app/javascript/mastodon/reducers/custom_emojis.js b/app/javascript/mastodon/reducers/custom_emojis.js index 307bcc7..d2c801a 100644 --- a/app/javascript/mastodon/reducers/custom_emojis.js +++ b/app/javascript/mastodon/reducers/custom_emojis.js @@ -1,16 +1,15 @@ -import { List as ImmutableList } from 'immutable'; -import { STORE_HYDRATE } from '../actions/store'; +import { List as ImmutableList, fromJS as ConvertToImmutable } from 'immutable'; +import { CUSTOM_EMOJIS_FETCH_SUCCESS } from '../actions/custom_emojis'; import { search as emojiSearch } from '../features/emoji/emoji_mart_search_light'; import { buildCustomEmojis } from '../features/emoji/emoji'; -const initialState = ImmutableList(); +const initialState = ImmutableList([]); export default function custom_emojis(state = initialState, action) { - switch(action.type) { - case STORE_HYDRATE: - emojiSearch('', { custom: buildCustomEmojis(action.state.get('custom_emojis', [])) }); - return action.state.get('custom_emojis'); - default: - return state; + if(action.type === CUSTOM_EMOJIS_FETCH_SUCCESS) { + state = ConvertToImmutable(action.custom_emojis); + emojiSearch('', { custom: buildCustomEmojis(state) }); } + + return state; }; diff --git a/app/serializers/initial_state_serializer.rb b/app/serializers/initial_state_serializer.rb index 216cf54..3b908e2 100644 --- a/app/serializers/initial_state_serializer.rb +++ b/app/serializers/initial_state_serializer.rb @@ -4,12 +4,6 @@ class InitialStateSerializer < ActiveModel::Serializer attributes :meta, :compose, :accounts, :media_attachments, :settings, :push_subscription - has_many :custom_emojis, serializer: REST::CustomEmojiSerializer - - def custom_emojis - CustomEmoji.local.where(disabled: false) - end - def meta store = { streaming_api_base_url: Rails.configuration.x.streaming_api_base_url, From 98146281e1beaf994710b13ef70f6224e8588cba Mon Sep 17 00:00:00 2001 From: Harmon Date: Sat, 7 Apr 2018 07:53:11 -0500 Subject: [PATCH 088/381] Remove duplicate frequently used emojis (#7064) --- .../features/compose/containers/emoji_picker_dropdown_container.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/javascript/mastodon/features/compose/containers/emoji_picker_dropdown_container.js b/app/javascript/mastodon/features/compose/containers/emoji_picker_dropdown_container.js index e6a535a..5ec937a 100644 --- a/app/javascript/mastodon/features/compose/containers/emoji_picker_dropdown_container.js +++ b/app/javascript/mastodon/features/compose/containers/emoji_picker_dropdown_container.js @@ -38,7 +38,8 @@ const getFrequentlyUsedEmojis = createSelector([ .toArray(); if (emojis.length < DEFAULTS.length) { - emojis = emojis.concat(DEFAULTS.slice(0, DEFAULTS.length - emojis.length)); + let uniqueDefaults = DEFAULTS.filter(emoji => !emojis.includes(emoji)); + emojis = emojis.concat(uniqueDefaults.slice(0, DEFAULTS.length - emojis.length)); } return emojis; From b5726def55994db8eb5797bbea1d2b79df3e884a Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Sat, 7 Apr 2018 18:54:46 +0200 Subject: [PATCH 089/381] Forward deletes on the same path as reply forwarding (#7058) * Forward deletes on the same path as reply forwarding * Remove trailing whitespace --- app/lib/activitypub/activity/delete.rb | 36 +++++++++++++++++++++++++--------- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/app/lib/activitypub/activity/delete.rb b/app/lib/activitypub/activity/delete.rb index 5fa60a8..3474d55 100644 --- a/app/lib/activitypub/activity/delete.rb +++ b/app/lib/activitypub/activity/delete.rb @@ -17,21 +17,25 @@ class ActivityPub::Activity::Delete < ActivityPub::Activity end def delete_note - status = Status.find_by(uri: object_uri, account: @account) - status ||= Status.find_by(uri: @object['atomUri'], account: @account) if @object.is_a?(Hash) && @object['atomUri'].present? + @status = Status.find_by(uri: object_uri, account: @account) + @status ||= Status.find_by(uri: @object['atomUri'], account: @account) if @object.is_a?(Hash) && @object['atomUri'].present? delete_later!(object_uri) - return if status.nil? + return if @status.nil? - forward_for_reblogs(status) - delete_now!(status) + if @status.public_visibility? || @status.unlisted_visibility? + forward_for_reply + forward_for_reblogs + end + + delete_now! end - def forward_for_reblogs(status) + def forward_for_reblogs return if @json['signature'].blank? - rebloggers_ids = status.reblogs.includes(:account).references(:account).merge(Account.local).pluck(:account_id) + rebloggers_ids = @status.reblogs.includes(:account).references(:account).merge(Account.local).pluck(:account_id) inboxes = Account.where(id: ::Follow.where(target_account_id: rebloggers_ids).select(:account_id)).inboxes - [@account.preferred_inbox_url] ActivityPub::DeliveryWorker.push_bulk(inboxes) do |inbox_url| @@ -39,8 +43,22 @@ class ActivityPub::Activity::Delete < ActivityPub::Activity end end - def delete_now!(status) - RemoveStatusService.new.call(status) + def replied_to_status + return @replied_to_status if defined?(@replied_to_status) + @replied_to_status = @status.thread + end + + def reply_to_local? + !replied_to_status.nil? && replied_to_status.account.local? + end + + def forward_for_reply + return unless @json['signature'].present? && reply_to_local? + ActivityPub::RawDistributionWorker.perform_async(Oj.dump(@json), replied_to_status.account_id, [@account.preferred_inbox_url]) + end + + def delete_now! + RemoveStatusService.new.call(@status) end def payload From b65eb00c53af939444e0e891c0a3a4563f4897ac Mon Sep 17 00:00:00 2001 From: Alda Marteau-Hardi Date: Sat, 7 Apr 2018 21:33:01 +0200 Subject: [PATCH 090/381] Prevent admins and moderators eavesdropping in private and direct toots (#7067) Fix #6986 --- app/controllers/admin/statuses_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/admin/statuses_controller.rb b/app/controllers/admin/statuses_controller.rb index 5d4325f..d5787ac 100644 --- a/app/controllers/admin/statuses_controller.rb +++ b/app/controllers/admin/statuses_controller.rb @@ -12,7 +12,7 @@ module Admin def index authorize :status, :index? - @statuses = @account.statuses + @statuses = @account.statuses.where(visibility: [:public, :unlisted]) if params[:media] account_media_status_ids = @account.media_attachments.attached.reorder(nil).select(:status_id).distinct From 4a9becfca2d7399acb422da646c48bdd9f39c989 Mon Sep 17 00:00:00 2001 From: Hisham Muhammad Date: Sat, 7 Apr 2018 16:36:03 -0300 Subject: [PATCH 091/381] i18n: improve "Welcome" translation in Portuguese (#7068) * i18n: update gender-neutral language for pt and pt-BR Instead of using "bem-vindo(a)" (a masculine form of "Welcome" with a "(a)" in the end to mean "bem-vinda" for the feminine form), use "boas-vindas", which is a gender-neutral form of "Welcome"). There is already precedent for using "boas-vindas" in the Brazilian Portuguese localization, in `config/locales/pt-BR.yml`. European Portuguese dictionary Priberam also registers it as a valid form: https://www.priberam.pt/dlpo/boas-vindas * i18n: pt-BR minor orthography fix The form "a bordo" does not take an accent. http://oredator.com.br/curso-de-redacao/uncategorized/a-bordo-ou-a-bordo --- app/javascript/mastodon/locales/pt-BR.json | 2 +- app/javascript/mastodon/locales/pt.json | 2 +- config/locales/pt-BR.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/javascript/mastodon/locales/pt-BR.json b/app/javascript/mastodon/locales/pt-BR.json index b056ec8..4cd2e06 100644 --- a/app/javascript/mastodon/locales/pt-BR.json +++ b/app/javascript/mastodon/locales/pt-BR.json @@ -189,7 +189,7 @@ "onboarding.page_one.federation": "Mastodon é uma rede de servidores independentes que se juntam para fazer uma grande rede social. Nós chamamos estes servidores de instâncias.", "onboarding.page_one.full_handle": "Seu nome de usuário completo", "onboarding.page_one.handle_hint": "Isso é o que você diz aos seus amigos para que eles possam te mandar mensagens ou te seguir a partir de outra instância.", - "onboarding.page_one.welcome": "Seja bem-vindo(a) ao Mastodon!", + "onboarding.page_one.welcome": "Boas-vindas ao Mastodon!", "onboarding.page_six.admin": "O administrador de sua instância é {admin}.", "onboarding.page_six.almost_done": "Quase acabando...", "onboarding.page_six.appetoot": "Bom Apetoot!", diff --git a/app/javascript/mastodon/locales/pt.json b/app/javascript/mastodon/locales/pt.json index 6598300..7a404ea 100644 --- a/app/javascript/mastodon/locales/pt.json +++ b/app/javascript/mastodon/locales/pt.json @@ -189,7 +189,7 @@ "onboarding.page_one.federation": "Mastodon é uma rede de servidores independentes ligados entre si para fazer uma grande rede social. Nós chamamos instâncias a estes servidores.", "onboarding.page_one.full_handle": "O teu nome de utilizador completo", "onboarding.page_one.handle_hint": "Isto é o que dizes aos teus amigos para pesquisar.", - "onboarding.page_one.welcome": "Bem-vindo(a) ao Mastodon!", + "onboarding.page_one.welcome": "Boas-vindas ao Mastodon!", "onboarding.page_six.admin": "O administrador da tua instância é {admin}.", "onboarding.page_six.almost_done": "Quase pronto...", "onboarding.page_six.appetoot": "Bon Appetoot!", diff --git a/config/locales/pt-BR.yml b/config/locales/pt-BR.yml index c1225d3..d6f463a 100644 --- a/config/locales/pt-BR.yml +++ b/config/locales/pt-BR.yml @@ -709,7 +709,7 @@ pt-BR: tip_local_timeline: A timeline local é uma visão contínua das pessoas que estão em %{instance}. Esses são seus vizinhos próximos! tip_mobile_webapp: Se o seu navegador móvel oferecer a opção de adicionar Mastodon à tela inicial, você pode receber notificações push. Vai funcionar quase como um aplicativo nativo! tips: Dicas - title: Boas-vindas à bordo, %{name}! + title: Boas-vindas a bordo, %{name}! users: invalid_email: O endereço de e-mail é inválido invalid_otp_token: Código de autenticação inválido From d4de2239b0ab04bf6a42db9f28d1fdd8e45f7d8b Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Sat, 7 Apr 2018 21:36:58 +0200 Subject: [PATCH 092/381] Add a circuit breaker for ActivityPub deliveries (#7053) --- Gemfile | 2 ++ Gemfile.lock | 2 ++ app/workers/activitypub/delivery_worker.rb | 14 +++++++++----- config/initializers/stoplight.rb | 3 +++ 4 files changed, 16 insertions(+), 5 deletions(-) create mode 100644 config/initializers/stoplight.rb diff --git a/Gemfile b/Gemfile index 9e644e7..4a5a166 100644 --- a/Gemfile +++ b/Gemfile @@ -35,6 +35,7 @@ gem 'devise-two-factor', '~> 3.0' group :pam_authentication, optional: true do gem 'devise_pam_authenticatable2', '~> 9.0' end + gem 'net-ldap', '~> 0.10' gem 'omniauth-cas', '~> 1.1' gem 'omniauth-saml', '~> 1.10' @@ -79,6 +80,7 @@ gem 'sidekiq-bulk', '~>0.1.1' gem 'simple-navigation', '~> 4.0' gem 'simple_form', '~> 3.4' gem 'sprockets-rails', '~> 3.2', require: 'sprockets/railtie' +gem 'stoplight', '~> 2.1.3' gem 'strong_migrations' gem 'tty-command' gem 'tty-prompt' diff --git a/Gemfile.lock b/Gemfile.lock index a185a60..0f5a1fb 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -550,6 +550,7 @@ GEM net-scp (>= 1.1.2) net-ssh (>= 2.8.0) statsd-ruby (1.2.1) + stoplight (2.1.3) streamio-ffmpeg (3.0.2) multi_json (~> 1.8) strong_migrations (0.1.9) @@ -716,6 +717,7 @@ DEPENDENCIES simple_form (~> 3.4) simplecov (~> 0.14) sprockets-rails (~> 3.2) + stoplight (~> 2.1.3) streamio-ffmpeg (~> 3.0) strong_migrations tty-command diff --git a/app/workers/activitypub/delivery_worker.rb b/app/workers/activitypub/delivery_worker.rb index e6cfd0d..adffd1d 100644 --- a/app/workers/activitypub/delivery_worker.rb +++ b/app/workers/activitypub/delivery_worker.rb @@ -12,9 +12,7 @@ class ActivityPub::DeliveryWorker @source_account = Account.find(source_account_id) @inbox_url = inbox_url - perform_request do |response| - raise Mastodon::UnexpectedResponseError, response unless response_successful? response - end + perform_request failure_tracker.track_success! rescue => e @@ -30,8 +28,14 @@ class ActivityPub::DeliveryWorker request.add_headers(HEADERS) end - def perform_request(&block) - build_request.perform(&block) + def perform_request + light = Stoplight(@inbox_url) do + build_request.perform do |response| + raise Mastodon::UnexpectedResponseError, response unless response_successful?(response) + end + end + + light.run end def response_successful?(response) diff --git a/config/initializers/stoplight.rb b/config/initializers/stoplight.rb new file mode 100644 index 0000000..1bd4ee6 --- /dev/null +++ b/config/initializers/stoplight.rb @@ -0,0 +1,3 @@ +require 'stoplight' + +Stoplight::Light.default_data_store = Stoplight::DataStore::Redis.new(Redis.current) From b83ce18b30d33c30b461f593ac4cd6e86057a365 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?THE=20BOSS=20=E2=99=A8?= <30565780+theboss@users.noreply.github.com> Date: Sun, 8 Apr 2018 16:57:16 +0900 Subject: [PATCH 093/381] Ignore elasticsearch directory (#7070) --- .dockerignore | 1 + .gitignore | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.dockerignore b/.dockerignore index 5cd3b17..5fb9861 100644 --- a/.dockerignore +++ b/.dockerignore @@ -11,3 +11,4 @@ vendor/bundle *~ postgres redis +elasticsearch diff --git a/.gitignore b/.gitignore index 38ebc93..51e47bb 100644 --- a/.gitignore +++ b/.gitignore @@ -36,9 +36,10 @@ config/deploy/* .vscode/ .idea/ -# Ignore postgres + redis volume optionally created by docker-compose +# Ignore postgres + redis + elasticsearch volume optionally created by docker-compose postgres redis +elasticsearch # Ignore Apple files .DS_Store From 1ed1014546bcfef0d2441702673deab586f6bca0 Mon Sep 17 00:00:00 2001 From: Akihiko Odaki Date: Sun, 8 Apr 2018 20:32:39 +0900 Subject: [PATCH 094/381] Free stroage if it is exceeding disk quota (#7061) --- app/javascript/mastodon/actions/accounts.js | 9 ++- app/javascript/mastodon/actions/importer/index.js | 3 +- app/javascript/mastodon/actions/statuses.js | 11 +++- app/javascript/mastodon/service_worker/entry.js | 13 +++- app/javascript/mastodon/storage/db.js | 9 ++- app/javascript/mastodon/storage/modifier.js | 80 +++++++++++++++++------ 6 files changed, 89 insertions(+), 36 deletions(-) diff --git a/app/javascript/mastodon/actions/accounts.js b/app/javascript/mastodon/actions/accounts.js index 28ae567..c9e4afc 100644 --- a/app/javascript/mastodon/actions/accounts.js +++ b/app/javascript/mastodon/actions/accounts.js @@ -1,5 +1,5 @@ import api, { getLinks } from '../api'; -import asyncDB from '../storage/db'; +import openDB from '../storage/db'; import { importAccount, importFetchedAccount, importFetchedAccounts } from './importer'; export const ACCOUNT_FETCH_REQUEST = 'ACCOUNT_FETCH_REQUEST'; @@ -94,12 +94,15 @@ export function fetchAccount(id) { dispatch(fetchAccountRequest(id)); - asyncDB.then(db => getFromDB( + openDB().then(db => getFromDB( dispatch, getState, db.transaction('accounts', 'read').objectStore('accounts').index('id'), id - )).catch(() => api(getState).get(`/api/v1/accounts/${id}`).then(response => { + ).then(() => db.close(), error => { + db.close(); + throw error; + })).catch(() => api(getState).get(`/api/v1/accounts/${id}`).then(response => { dispatch(importFetchedAccount(response.data)); })).then(() => { dispatch(fetchAccountSuccess()); diff --git a/app/javascript/mastodon/actions/importer/index.js b/app/javascript/mastodon/actions/importer/index.js index e671d41..5b18cbc 100644 --- a/app/javascript/mastodon/actions/importer/index.js +++ b/app/javascript/mastodon/actions/importer/index.js @@ -1,3 +1,4 @@ +import { autoPlayGif } from '../../initial_state'; import { putAccounts, putStatuses } from '../../storage/modifier'; import { normalizeAccount, normalizeStatus } from './normalizer'; @@ -44,7 +45,7 @@ export function importFetchedAccounts(accounts) { } accounts.forEach(processAccount); - putAccounts(normalAccounts); + putAccounts(normalAccounts, !autoPlayGif); return importAccounts(normalAccounts); } diff --git a/app/javascript/mastodon/actions/statuses.js b/app/javascript/mastodon/actions/statuses.js index d28aef8..849cb4f 100644 --- a/app/javascript/mastodon/actions/statuses.js +++ b/app/javascript/mastodon/actions/statuses.js @@ -1,5 +1,5 @@ import api from '../api'; -import asyncDB from '../storage/db'; +import openDB from '../storage/db'; import { evictStatus } from '../storage/modifier'; import { deleteFromTimelines } from './timelines'; @@ -92,12 +92,17 @@ export function fetchStatus(id) { dispatch(fetchStatusRequest(id, skipLoading)); - asyncDB.then(db => { + openDB().then(db => { const transaction = db.transaction(['accounts', 'statuses'], 'read'); const accountIndex = transaction.objectStore('accounts').index('id'); const index = transaction.objectStore('statuses').index('id'); - return getFromDB(dispatch, getState, accountIndex, index, id); + return getFromDB(dispatch, getState, accountIndex, index, id).then(() => { + db.close(); + }, error => { + db.close(); + throw error; + }); }).then(() => { dispatch(fetchStatusSuccess(skipLoading)); }, () => api(getState).get(`/api/v1/statuses/${id}`).then(response => { diff --git a/app/javascript/mastodon/service_worker/entry.js b/app/javascript/mastodon/service_worker/entry.js index 160c3fb..ba54ae9 100644 --- a/app/javascript/mastodon/service_worker/entry.js +++ b/app/javascript/mastodon/service_worker/entry.js @@ -1,3 +1,4 @@ +import { freeStorage } from '../storage/modifier'; import './web_push_notifications'; function openSystemCache() { @@ -42,8 +43,10 @@ self.addEventListener('fetch', function(event) { event.respondWith(asyncResponse.then(async response => { if (response.ok || response.type === 'opaqueredirect') { - const cache = await asyncCache; - await cache.delete('/'); + await Promise.all([ + asyncCache.then(cache => cache.delete('/')), + indexedDB.deleteDatabase('mastodon'), + ]); } return response; @@ -56,7 +59,11 @@ self.addEventListener('fetch', function(event) { const fetched = await fetch(event.request); if (fetched.ok) { - await cache.put(event.request.url, fetched.clone()); + try { + await cache.put(event.request.url, fetched.clone()); + } finally { + freeStorage(); + } } return fetched; diff --git a/app/javascript/mastodon/storage/db.js b/app/javascript/mastodon/storage/db.js index e08fc3f..377a792 100644 --- a/app/javascript/mastodon/storage/db.js +++ b/app/javascript/mastodon/storage/db.js @@ -1,15 +1,14 @@ -import { me } from '../initial_state'; - -export default new Promise((resolve, reject) => { +export default () => new Promise((resolve, reject) => { + // ServiceWorker is required to synchronize the login state. // Microsoft Edge 17 does not support getAll according to: // Catalog of standard and vendor APIs across browsers - Microsoft Edge Development // https://developer.microsoft.com/en-us/microsoft-edge/platform/catalog/?q=specName%3Aindexeddb - if (!me || !('getAll' in IDBObjectStore.prototype)) { + if (!('caches' in self && 'getAll' in IDBObjectStore.prototype)) { reject(); return; } - const request = indexedDB.open('mastodon:' + me); + const request = indexedDB.open('mastodon'); request.onerror = reject; request.onsuccess = ({ target }) => resolve(target.result); diff --git a/app/javascript/mastodon/storage/modifier.js b/app/javascript/mastodon/storage/modifier.js index 4773d07..c2ed6f8 100644 --- a/app/javascript/mastodon/storage/modifier.js +++ b/app/javascript/mastodon/storage/modifier.js @@ -1,13 +1,14 @@ -import asyncDB from './db'; -import { autoPlayGif } from '../initial_state'; +import openDB from './db'; const accountAssetKeys = ['avatar', 'avatar_static', 'header', 'header_static']; -const avatarKey = autoPlayGif ? 'avatar' : 'avatar_static'; -const limit = 1024; +const storageMargin = 8388608; +const storeLimit = 1024; -// ServiceWorker and Cache API is not available on iOS 11 -// https://webkit.org/status/#specification-service-workers -const asyncCache = window.caches ? caches.open('mastodon-system') : Promise.reject(); +function openCache() { + // ServiceWorker and Cache API is not available on iOS 11 + // https://webkit.org/status/#specification-service-workers + return self.caches ? caches.open('mastodon-system') : Promise.reject(); +} function printErrorIfAvailable(error) { if (error) { @@ -16,7 +17,7 @@ function printErrorIfAvailable(error) { } function put(name, objects, onupdate, oncreate) { - return asyncDB.then(db => new Promise((resolve, reject) => { + return openDB().then(db => (new Promise((resolve, reject) => { const putTransaction = db.transaction(name, 'readwrite'); const putStore = putTransaction.objectStore(name); const putIndex = putStore.index('id'); @@ -53,7 +54,7 @@ function put(name, objects, onupdate, oncreate) { const count = readStore.count(); count.onsuccess = () => { - const excess = count.result - limit; + const excess = count.result - storeLimit; if (excess > 0) { const retrieval = readStore.getAll(null, excess); @@ -69,11 +70,17 @@ function put(name, objects, onupdate, oncreate) { }; putTransaction.onerror = reject; + })).then(resolved => { + db.close(); + return resolved; + }, error => { + db.close(); + throw error; })); } function evictAccountsByRecords(records) { - asyncDB.then(db => { + return openDB().then(db => { const transaction = db.transaction(['accounts', 'statuses'], 'readwrite'); const accounts = transaction.objectStore('accounts'); const accountsIdIndex = accounts.index('id'); @@ -83,7 +90,7 @@ function evictAccountsByRecords(records) { function evict(toEvict) { toEvict.forEach(record => { - asyncCache + openCache() .then(cache => accountAssetKeys.forEach(key => cache.delete(records[key]))) .catch(printErrorIfAvailable); @@ -98,6 +105,8 @@ function evictAccountsByRecords(records) { } evict(records); + + db.close(); }).catch(printErrorIfAvailable); } @@ -106,8 +115,9 @@ export function evictStatus(id) { } export function evictStatuses(ids) { - asyncDB.then(db => { - const store = db.transaction('statuses', 'readwrite').objectStore('statuses'); + return openDB().then(db => { + const transaction = db.transaction('statuses', 'readwrite'); + const store = transaction.objectStore('statuses'); const idIndex = store.index('id'); const reblogIndex = store.index('reblog'); @@ -118,14 +128,17 @@ export function evictStatuses(ids) { idIndex.getKey(id).onsuccess = ({ target }) => target.result && store.delete(target.result); }); + + db.close(); }).catch(printErrorIfAvailable); } function evictStatusesByRecords(records) { - evictStatuses(records.map(({ id }) => id)); + return evictStatuses(records.map(({ id }) => id)); } -export function putAccounts(records) { +export function putAccounts(records, avatarStatic) { + const avatarKey = avatarStatic ? 'avatar_static' : 'avatar'; const newURLs = []; put('accounts', records, (newRecord, oldKey, store, oncomplete) => { @@ -135,7 +148,7 @@ export function putAccounts(records) { const oldURL = target.result[key]; if (newURL !== oldURL) { - asyncCache + openCache() .then(cache => cache.delete(oldURL)) .catch(printErrorIfAvailable); } @@ -153,11 +166,12 @@ export function putAccounts(records) { }, (newRecord, oncomplete) => { newURLs.push(newRecord[avatarKey]); oncomplete(); - }).then(records => { - evictAccountsByRecords(records); - asyncCache - .then(cache => cache.addAll(newURLs)) - .catch(printErrorIfAvailable); + }).then(records => Promise.all([ + evictAccountsByRecords(records), + openCache().then(cache => cache.addAll(newURLs)), + ])).then(freeStorage, error => { + freeStorage(); + throw error; }).catch(printErrorIfAvailable); } @@ -166,3 +180,27 @@ export function putStatuses(records) { .then(evictStatusesByRecords) .catch(printErrorIfAvailable); } + +export function freeStorage() { + return navigator.storage.estimate().then(({ quota, usage }) => { + if (usage + storageMargin < quota) { + return null; + } + + return openDB().then(db => new Promise((resolve, reject) => { + const retrieval = db.transaction('accounts', 'readonly').objectStore('accounts').getAll(null, 1); + + retrieval.onsuccess = () => { + if (retrieval.result.length > 0) { + resolve(evictAccountsByRecords(retrieval.result).then(freeStorage)); + } else { + resolve(caches.delete('mastodon-system')); + } + }; + + retrieval.onerror = reject; + + db.close(); + })); + }); +} From 1364e9e4ae1fb12a1c970795f1d0afd651c7cfe2 Mon Sep 17 00:00:00 2001 From: ThibG Date: Sun, 8 Apr 2018 13:40:22 +0200 Subject: [PATCH 095/381] Fix follow/unfollow buttons on public profile (fixes #7036) (#7040) * Fix follow/unfollow buttons on public profile - Present non-logged users with web+mastodon:// URLs for remote accounts - Present logged-in users with appropriate links (authorize_follows and remote_unfollows) for remote accounts * Do not cache rendered cards if user is logged in --- .../concerns/remote_account_controller_concern.rb | 21 ++++++++++++ app/controllers/remote_unfollows.rb | 39 ++++++++++++++++++++++ app/views/accounts/_follow_button.html.haml | 6 ++-- app/views/accounts/_follow_grid.html.haml | 2 +- app/views/remote_unfollows/_card.html.haml | 13 ++++++++ .../_post_follow_actions.html.haml | 4 +++ app/views/remote_unfollows/error.html.haml | 3 ++ app/views/remote_unfollows/success.html.haml | 10 ++++++ config/routes.rb | 1 + 9 files changed, 95 insertions(+), 4 deletions(-) create mode 100644 app/controllers/concerns/remote_account_controller_concern.rb create mode 100644 app/controllers/remote_unfollows.rb create mode 100644 app/views/remote_unfollows/_card.html.haml create mode 100644 app/views/remote_unfollows/_post_follow_actions.html.haml create mode 100644 app/views/remote_unfollows/error.html.haml create mode 100644 app/views/remote_unfollows/success.html.haml diff --git a/app/controllers/concerns/remote_account_controller_concern.rb b/app/controllers/concerns/remote_account_controller_concern.rb new file mode 100644 index 0000000..e179106 --- /dev/null +++ b/app/controllers/concerns/remote_account_controller_concern.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module RemoteAccountControllerConcern + extend ActiveSupport::Concern + + included do + layout 'public' + before_action :set_account + before_action :check_account_suspension + end + + private + + def set_account + @account = Account.find_remote!(params[:acct]) + end + + def check_account_suspension + gone if @account.suspended? + end +end diff --git a/app/controllers/remote_unfollows.rb b/app/controllers/remote_unfollows.rb new file mode 100644 index 0000000..af59433 --- /dev/null +++ b/app/controllers/remote_unfollows.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +class RemoteUnfollowsController < ApplicationController + layout 'modal' + + before_action :authenticate_user! + before_action :set_body_classes + + def create + @account = unfollow_attempt.try(:target_account) + + if @account.nil? + render :error + else + render :success + end + rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError + render :error + end + + private + + def unfollow_attempt + username, domain = acct_without_prefix.split('@') + UnfollowService.new.call(current_account, Account.find_remote!(username, domain)) + end + + def acct_without_prefix + acct_params.gsub(/\Aacct:/, '') + end + + def acct_params + params.fetch(:acct, '') + end + + def set_body_classes + @body_classes = 'modal-layout' + end +end diff --git a/app/views/accounts/_follow_button.html.haml b/app/views/accounts/_follow_button.html.haml index e476e0a..96ae232 100644 --- a/app/views/accounts/_follow_button.html.haml +++ b/app/views/accounts/_follow_button.html.haml @@ -8,16 +8,16 @@ - if user_signed_in? && current_account.id != account.id && !requested .controls - if following - = link_to account_unfollow_path(account), data: { method: :post }, class: 'icon-button' do + = link_to (account.local? ? account_unfollow_path(account) : remote_unfollow_path(acct: account.acct)), data: { method: :post }, class: 'icon-button' do = fa_icon 'user-times' = t('accounts.unfollow') - else - = link_to account_follow_path(account), data: { method: :post }, class: 'icon-button' do + = link_to (account.local? ? account_follow_path(account) : authorize_follow_path(acct: account.acct)), data: { method: :post }, class: 'icon-button' do = fa_icon 'user-plus' = t('accounts.follow') - elsif !user_signed_in? .controls .remote-follow - = link_to account_remote_follow_path(account), class: 'icon-button' do + = link_to (account.local? ? account_remote_follow_path(account) : "web+mastodon://follow?uri=#{account.uri}"), class: 'icon-button' do = fa_icon 'user-plus' = t('accounts.remote_follow') diff --git a/app/views/accounts/_follow_grid.html.haml b/app/views/accounts/_follow_grid.html.haml index 10fbfa5..a6d0ee8 100644 --- a/app/views/accounts/_follow_grid.html.haml +++ b/app/views/accounts/_follow_grid.html.haml @@ -2,6 +2,6 @@ - if accounts.empty? = render partial: 'accounts/nothing_here' - else - = render partial: 'accounts/grid_card', collection: accounts, as: :account, cached: true + = render partial: 'accounts/grid_card', collection: accounts, as: :account, cached: !user_signed_in? = paginate follows diff --git a/app/views/remote_unfollows/_card.html.haml b/app/views/remote_unfollows/_card.html.haml new file mode 100644 index 0000000..e81e292 --- /dev/null +++ b/app/views/remote_unfollows/_card.html.haml @@ -0,0 +1,13 @@ +.account-card + .detailed-status__display-name + %div + = image_tag account.avatar.url(:original), alt: '', width: 48, height: 48, class: 'avatar' + + %span.display-name + - account_url = local_assigns[:admin] ? admin_account_path(account.id) : TagManager.instance.url_for(account) + = link_to account_url, class: 'detailed-status__display-name p-author h-card', target: '_blank', rel: 'noopener' do + %strong.emojify= display_name(account) + %span @#{account.acct} + + - if account.note? + .account__header__content.emojify= Formatter.instance.simplified_format(account) diff --git a/app/views/remote_unfollows/_post_follow_actions.html.haml b/app/views/remote_unfollows/_post_follow_actions.html.haml new file mode 100644 index 0000000..2a9c062 --- /dev/null +++ b/app/views/remote_unfollows/_post_follow_actions.html.haml @@ -0,0 +1,4 @@ +.post-follow-actions + %div= link_to t('authorize_follow.post_follow.web'), web_url("accounts/#{@account.id}"), class: 'button button--block' + %div= link_to t('authorize_follow.post_follow.return'), TagManager.instance.url_for(@account), class: 'button button--block' + %div= t('authorize_follow.post_follow.close') diff --git a/app/views/remote_unfollows/error.html.haml b/app/views/remote_unfollows/error.html.haml new file mode 100644 index 0000000..cb63f02 --- /dev/null +++ b/app/views/remote_unfollows/error.html.haml @@ -0,0 +1,3 @@ +.form-container + .flash-message#error_explanation + = t('remote_unfollow.error') diff --git a/app/views/remote_unfollows/success.html.haml b/app/views/remote_unfollows/success.html.haml new file mode 100644 index 0000000..aa3c838 --- /dev/null +++ b/app/views/remote_unfollows/success.html.haml @@ -0,0 +1,10 @@ +- content_for :page_title do + = t('remote_unfollow.title', acct: @account.acct) + +.form-container + .follow-prompt + %h2= t('remote_unfollow.unfollowed') + + = render 'card', account: @account + + = render 'post_follow_actions' diff --git a/config/routes.rb b/config/routes.rb index 4b5ba5c..7187fd7 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -116,6 +116,7 @@ Rails.application.routes.draw do get '/media_proxy/:id/(*any)', to: 'media_proxy#show', as: :media_proxy # Remote follow + resource :remote_unfollow, only: [:create] resource :authorize_follow, only: [:show, :create] resource :share, only: [:show, :create] From cd0eaa349ca5d7e53e2ed246e70e99fc61c98370 Mon Sep 17 00:00:00 2001 From: Levi Bard Date: Sun, 8 Apr 2018 13:43:10 +0200 Subject: [PATCH 096/381] Enable updating additional account information from user preferences via rest api (#6789) * Enable updating additional account information from user preferences via rest api Resolves #6553 * Pacify rubocop * Decoerce incoming settings in UserSettingsDecorator * Create user preferences hash directly from incoming credentials instead of going through ActionController::Parameters * Clean up user preferences update * Use ActiveModel::Type::Boolean instead of manually checking stringified number equivalence --- app/controllers/api/v1/accounts/credentials_controller.rb | 12 ++++++++++++ app/lib/user_settings_decorator.rb | 4 ++-- .../api/v1/accounts/credentials_controller_spec.rb | 6 ++++++ spec/lib/user_settings_decorator_spec.rb | 11 +++++++++++ 4 files changed, 31 insertions(+), 2 deletions(-) diff --git a/app/controllers/api/v1/accounts/credentials_controller.rb b/app/controllers/api/v1/accounts/credentials_controller.rb index 68af225..062d490 100644 --- a/app/controllers/api/v1/accounts/credentials_controller.rb +++ b/app/controllers/api/v1/accounts/credentials_controller.rb @@ -13,6 +13,7 @@ class Api::V1::Accounts::CredentialsController < Api::BaseController def update @account = current_account UpdateAccountService.new.call(@account, account_params, raise_error: true) + UserSettingsDecorator.new(current_user).update(user_settings_params) if user_settings_params ActivityPub::UpdateDistributionWorker.perform_async(@account.id) render json: @account, serializer: REST::CredentialAccountSerializer end @@ -22,4 +23,15 @@ class Api::V1::Accounts::CredentialsController < Api::BaseController def account_params params.permit(:display_name, :note, :avatar, :header, :locked) end + + def user_settings_params + return nil unless params.key?(:source) + + source_params = params.require(:source) + + { + 'setting_default_privacy' => source_params.fetch(:privacy, @account.user.setting_default_privacy), + 'setting_default_sensitive' => source_params.fetch(:sensitive, @account.user.setting_default_sensitive), + } + end end diff --git a/app/lib/user_settings_decorator.rb b/app/lib/user_settings_decorator.rb index 4d6f194..9260a81 100644 --- a/app/lib/user_settings_decorator.rb +++ b/app/lib/user_settings_decorator.rb @@ -83,7 +83,7 @@ class UserSettingsDecorator end def boolean_cast_setting(key) - settings[key] == '1' + ActiveModel::Type::Boolean.new.cast(settings[key]) end def coerced_settings(key) @@ -91,7 +91,7 @@ class UserSettingsDecorator end def coerce_values(params_hash) - params_hash.transform_values { |x| x == '1' } + params_hash.transform_values { |x| ActiveModel::Type::Boolean.new.cast(x) } end def change?(key) diff --git a/spec/controllers/api/v1/accounts/credentials_controller_spec.rb b/spec/controllers/api/v1/accounts/credentials_controller_spec.rb index 461b8b3..87fce64 100644 --- a/spec/controllers/api/v1/accounts/credentials_controller_spec.rb +++ b/spec/controllers/api/v1/accounts/credentials_controller_spec.rb @@ -28,6 +28,10 @@ describe Api::V1::Accounts::CredentialsController do note: "Hi!\n\nToot toot!", avatar: fixture_file_upload('files/avatar.gif', 'image/gif'), header: fixture_file_upload('files/attachment.jpg', 'image/jpeg'), + source: { + privacy: 'unlisted', + sensitive: true, + } } end @@ -42,6 +46,8 @@ describe Api::V1::Accounts::CredentialsController do expect(user.account.note).to eq("Hi!\n\nToot toot!") expect(user.account.avatar).to exist expect(user.account.header).to exist + expect(user.setting_default_privacy).to eq('unlisted') + expect(user.setting_default_sensitive).to eq(true) end it 'queues up an account update distribution' do diff --git a/spec/lib/user_settings_decorator_spec.rb b/spec/lib/user_settings_decorator_spec.rb index fee8753..462c5b1 100644 --- a/spec/lib/user_settings_decorator_spec.rb +++ b/spec/lib/user_settings_decorator_spec.rb @@ -69,5 +69,16 @@ describe UserSettingsDecorator do settings.update(values) expect(user.settings['system_font_ui']).to eq false end + + it 'decoerces setting values before applying' do + values = { + 'setting_delete_modal' => 'false', + 'setting_boost_modal' => 'true', + } + + settings.update(values) + expect(user.settings['delete_modal']).to eq false + expect(user.settings['boost_modal']).to eq true + end end end From c9cbb8de703e321c0d152813a2e22471ffe5eef7 Mon Sep 17 00:00:00 2001 From: Emelia Smith Date: Sun, 8 Apr 2018 14:26:58 +0200 Subject: [PATCH 097/381] Add search item to tab bar for mobile devices (#7072) * Add search item to tab bar for mobile devices * Fix missing prop validation --- app/javascript/mastodon/features/compose/index.js | 5 +++-- app/javascript/mastodon/features/ui/components/tabs_bar.js | 1 + app/javascript/mastodon/features/ui/index.js | 2 ++ app/javascript/mastodon/locales/defaultMessages.json | 4 ++++ app/javascript/mastodon/locales/en.json | 1 + 5 files changed, 11 insertions(+), 2 deletions(-) diff --git a/app/javascript/mastodon/features/compose/index.js b/app/javascript/mastodon/features/compose/index.js index d5cd854..da5bf7c 100644 --- a/app/javascript/mastodon/features/compose/index.js +++ b/app/javascript/mastodon/features/compose/index.js @@ -38,6 +38,7 @@ export default class Compose extends React.PureComponent { columns: ImmutablePropTypes.list.isRequired, multiColumn: PropTypes.bool, showSearch: PropTypes.bool, + isSearchPage: PropTypes.bool, intl: PropTypes.object.isRequired, }; @@ -58,7 +59,7 @@ export default class Compose extends React.PureComponent { } render () { - const { multiColumn, showSearch, intl } = this.props; + const { multiColumn, showSearch, isSearchPage, intl } = this.props; let header = ''; @@ -102,7 +103,7 @@ export default class Compose extends React.PureComponent { )}
- + {({ x }) => (
diff --git a/app/javascript/mastodon/features/ui/components/tabs_bar.js b/app/javascript/mastodon/features/ui/components/tabs_bar.js index dba3be9..ed6de6f 100644 --- a/app/javascript/mastodon/features/ui/components/tabs_bar.js +++ b/app/javascript/mastodon/features/ui/components/tabs_bar.js @@ -8,6 +8,7 @@ import { isUserTouching } from '../../../is_mobile'; export const links = [ , , + , , , diff --git a/app/javascript/mastodon/features/ui/index.js b/app/javascript/mastodon/features/ui/index.js index 8894eb4..8b905fa 100644 --- a/app/javascript/mastodon/features/ui/index.js +++ b/app/javascript/mastodon/features/ui/index.js @@ -146,6 +146,8 @@ class SwitchingColumnsArea extends React.PureComponent { + + diff --git a/app/javascript/mastodon/locales/defaultMessages.json b/app/javascript/mastodon/locales/defaultMessages.json index 5059fc6..52b9db9 100644 --- a/app/javascript/mastodon/locales/defaultMessages.json +++ b/app/javascript/mastodon/locales/defaultMessages.json @@ -1718,6 +1718,10 @@ "id": "tabs_bar.notifications" }, { + "defaultMessage": "Search", + "id": "tabs_bar.search" + }, + { "defaultMessage": "Local", "id": "tabs_bar.local_timeline" }, diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json index 23040f7..2286d2e 100644 --- a/app/javascript/mastodon/locales/en.json +++ b/app/javascript/mastodon/locales/en.json @@ -269,6 +269,7 @@ "tabs_bar.home": "Home", "tabs_bar.local_timeline": "Local", "tabs_bar.notifications": "Notifications", + "tabs_bar.search": "Search", "ui.beforeunload": "Your draft will be lost if you leave Mastodon.", "upload_area.title": "Drag & drop to upload", "upload_button.label": "Add media", From 0893b1669548858daee79ab1260fb98646a0b3fa Mon Sep 17 00:00:00 2001 From: Emelia Smith Date: Sun, 8 Apr 2018 18:25:08 +0200 Subject: [PATCH 098/381] Hide search from Compose on mobile devices (#7077) * Hide search from Compose on mobile devices We're presently seeing large numbers of users accidentally tooting what they're trying to search for. This PR hides the search form from the Compose view, now that we have a dedicated "search" tab on mobile. * Don't "showSearch" on mobile if we're not currently searching (isSearchPage) --- app/javascript/mastodon/features/compose/index.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/javascript/mastodon/features/compose/index.js b/app/javascript/mastodon/features/compose/index.js index da5bf7c..67f0e79 100644 --- a/app/javascript/mastodon/features/compose/index.js +++ b/app/javascript/mastodon/features/compose/index.js @@ -24,9 +24,9 @@ const messages = defineMessages({ logout: { id: 'navigation_bar.logout', defaultMessage: 'Logout' }, }); -const mapStateToProps = state => ({ +const mapStateToProps = (state, ownProps) => ({ columns: state.getIn(['settings', 'columns']), - showSearch: state.getIn(['search', 'submitted']) && !state.getIn(['search', 'hidden']), + showSearch: ownProps.multiColumn ? state.getIn(['search', 'submitted']) && !state.getIn(['search', 'hidden']) : ownProps.isSearchPage, }); @connect(mapStateToProps) @@ -90,7 +90,7 @@ export default class Compose extends React.PureComponent {
{header} - + {(multiColumn || isSearchPage) && }
From 498327b2e3f2e64f3a11993afdbd9d87ba73cc92 Mon Sep 17 00:00:00 2001 From: Akihiko Odaki Date: Mon, 9 Apr 2018 16:58:53 +0900 Subject: [PATCH 099/381] Exclude status itself from context query (#7083) ancestor_statuses and descendant_statuses used to include the root status itself, but the behavior is confusing because the root status is not an ancestor nor descendant. --- app/models/concerns/status_threading_concern.rb | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/app/models/concerns/status_threading_concern.rb b/app/models/concerns/status_threading_concern.rb index 65f8e11..b539ba1 100644 --- a/app/models/concerns/status_threading_concern.rb +++ b/app/models/concerns/status_threading_concern.rb @@ -15,16 +15,12 @@ module StatusThreadingConcern def ancestor_ids Rails.cache.fetch("ancestors:#{id}") do - ancestors_without_self.pluck(:id) + ancestor_statuses.pluck(:id) end end - def ancestors_without_self - ancestor_statuses - [self] - end - def ancestor_statuses - Status.find_by_sql([<<-SQL.squish, id: id]) + Status.find_by_sql([<<-SQL.squish, id: in_reply_to_id]) WITH RECURSIVE search_tree(id, in_reply_to_id, path) AS ( SELECT id, in_reply_to_id, ARRAY[id] @@ -43,11 +39,7 @@ module StatusThreadingConcern end def descendant_ids - descendants_without_self.pluck(:id) - end - - def descendants_without_self - descendant_statuses - [self] + descendant_statuses.pluck(:id) end def descendant_statuses @@ -56,7 +48,7 @@ module StatusThreadingConcern AS ( SELECT id, ARRAY[id] FROM statuses - WHERE id = :id + WHERE in_reply_to_id = :id UNION ALL SELECT statuses.id, path || statuses.id FROM search_tree From 07d90b0414a68a7864cf824dcd55c4b7b5b9b984 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Miko=C5=82ajczak?= Date: Mon, 9 Apr 2018 10:28:53 +0200 Subject: [PATCH 100/381] i18n: Update Polish translation (#7085) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Marcin Mikołajczak --- app/javascript/mastodon/locales/pl.json | 1 + config/locales/pl.yml | 77 +++++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+) diff --git a/app/javascript/mastodon/locales/pl.json b/app/javascript/mastodon/locales/pl.json index ff7180d..0419356 100644 --- a/app/javascript/mastodon/locales/pl.json +++ b/app/javascript/mastodon/locales/pl.json @@ -269,6 +269,7 @@ "tabs_bar.home": "Strona główna", "tabs_bar.local_timeline": "Lokalne", "tabs_bar.notifications": "Powiadomienia", + "tabs_bar.search": "Szukaj", "ui.beforeunload": "Utracisz tworzony wpis, jeżeli opuścisz Mastodona.", "upload_area.title": "Przeciągnij i upuść aby wysłać", "upload_button.label": "Dodaj zawartość multimedialną", diff --git a/config/locales/pl.yml b/config/locales/pl.yml index 64c1ff5..d303171 100644 --- a/config/locales/pl.yml +++ b/config/locales/pl.yml @@ -700,6 +700,83 @@ pl: reblogged: podbił sensitive_content: Wrażliwa zawartość terms: + body_html: | +

Polityka prywatności

+

Jakie informacje zbieramy?

+ +
    +
  • Podstawowe informacje o koncie: Podczas rejestracji na tym serwerze, możesz zostać poproszony o wprowadzenie nazwy użytkownika, adresu e-mail i hasła. Możesz także wprowadzić dodatkowe informacje o profilu, takie jak nazwa wyświetlana i biografia oraz wysłać awatar i obraz nagłówka. Nazwa użytkownika, nazwa wyświetlana, biografia, awatar i obraz nagłówka są zawsze widoczne dla wszystkich.
  • +
  • Wpisy, śledzenie i inne publiczne informacje: Lista osób które śledzisz jest widoczna publicznie, tak jak lista osób, które Cię śledzą. Jeżeli dodasz wpis, data i czas jego utworzenia i aplikacja, z której go wysłano są przechowywane. Wiadomości mogą zawierać załączniki multimedialne, takie jak zdjęcia i filmy. Publiczne i niewidoczne wpisy są dostępne publicznie. Udostępniony wpis również jest widoczny publicznie. Twoje wpisy są dostarczane obserwującym, co oznacza że jego kopie mogą zostać dostarczone i być przechowywane na innych serwerach. Kiedy usuniesz wpis, przestaje być widoczny również dla osób śledzących Cię. „Podbijanie” i dodanie do ulubionych jest zawsze publiczne.
  • +
  • Wpisy bezpośrednie i tylko dla śledzących: Wszystkie wpisy są przechowywane i przetwarzane na serwerze. Wpisy przeznaczone tylko dla śledzących są widoczne tylko dla nich i osób wspomnianych we wpisie, a wpisy bezpośrednie tylko dla wspimnianych. W wielu przypadkach oznacza to, że ich kopie są dostarczane i przechowywane na innych serwerach. Staramy się ograniczać zasięg tych wpisów wyłącznie do właściwych odbiorców, ale inne serwery mogą tego nie robić. Ważne jest, aby sprawdzać jakich serwerów używają osoby, które Cię śledzą. Możesz aktywować opcję pozwalającą na ręczne akceptowanie i odrzucanie nowych śledzących. Pamiętaj, że właściciele serwerów mogą zobaczyć te wiadomości, a odbiorcy mogą wykonać zrzut ekranu, skopiować lub udostępniać ten wpis. Nie udostępniaj wrażliwych danych z użyciem Mastodona.
  • +
  • Adresy IP i inne metadane: Kiedy zalogujesz się, przechowujemy adres IP użyty w trakcie logowania wraz z nazwą używanej przeglądarki. Wszystkie aktywne sesje możesz zobaczyć (i wygasić) w ustawieniach. Ostatnio używany adres IP jest przechowywany przez nas do 12 miesięcy. Możemy również przechowywać adresy IP wykorzystywane przy każdym działaniu na serwerze.
  • +
+ +
+ +

W jakim celu wykorzystujecie informacje?

+ +

Zebrane informacje mogą zostać użyte w następujące sposoby:

+ +
    +
  • Aby dostarczyć podstawową funkcjonalność Mastodona. Możesz wchodzić w interakcje z zawartością tworzoną przez innych tylko gdy jesteś zalogowany. Na przykład, możesz śledzić innych, aby widzieć ich wpisy w dostosowanej osi czasu.
  • +
  • Aby wspomóc moderację społeczności, na przykład porównując Twój adres IP ze znanymi, aby rozpoznać próbę obejścia blokady i inne naruszenia.
  • +
  • Adres e-mail może zostać wykorzystany, aby wysyłać Ci informacje, powiadomienia o osobach wchodzących w interakcje z tworzoną przez Ciebie zawartością, wysyłających Ci wiadomości, odpowiadać na zgłoszenia i inne żądania lub zapytania.
  • +
+ +
+ +

W jaki sposób chronimy Twoje dane?

+ +

Wykorzystujemy różne zabezpieczenia, aby zapewnić bezpieczeństwo informacji, które wprowadzasz, wysyłasz lub do których uzyskujesz dostęp. Poza tym, sesja przeglądarki oraz ruch pomiędzy aplikacją a API jest zabezpieczany z użyciem SSL, a hasło jest hashowane z użyciem silnego algorytmu. Możesz też aktywować uwierzytelnianie dwustopniowe, aby lepiej zabezpieczyć dostęp do konta.

+ +
+ +

Jaka jest nasza polityka przechowywania danych?

+ +

Staramy się:

+ +
    +
  • Przechowywać logi zawierające adresy IP używane przy każdym żądaniu do serwera przez nie dłużej niż 90 dni.
  • +
  • Przechowywać adresy IP przypisane do użytkowników przez nie dłużej niż 12 miesięcy.
  • +
+ +

Możesz zażądać i pobrać archiwum tworzonej zawartości, wliczając Twoje wpisy, załączniki multimedialne, awatar i zdjęcie nagłówka.

+ +

Możesz nieodwracalnie usunąć konto w każdej chwili.

+ +
+ +

Czy używany plików cookies?

+ +

Tak. Pliki cookies są małymi plikami, które strona lub dostawca jej usługi dostarcza na dysk twardy komputera z użyciem przeglądarki internetowej (jeżeli na to pozwoli). Pliki cookies pozwalają na rozpoznanie przeglądarki i – jeśli jesteś zarejestrowany – przypisanie jej do konta.

+ +

Wykorzystujemy pliki cookies, aby przechowywać preferencję użytkowników na przyszłe wizyty.

+ +
+ +

Czy przekazujemy informacje osobom trzecim?

+ +

Nie sprzedajemy, nie wymieniamy i nie przekazujemy osobom trzecim informacji pozwalających na identyfikację Ciebie. Nie dotyczy to zaufanym dostawcom pomagającym w prowadzeniu lub obsługiwaniu użytkowników, jeżeli zgadzają się, aby nie przekazywać dalej tych informacji. Możemy również udostępnić informacje, jeżeli uważany to za wymagane przez prawo, konieczne do wypełnienia polityki strony, przestrzegania naszych lub cudzych praw, własności i bezpieczeństwa.

+ +

Twoja publiczna zawartość może zostać pobrana przez inne serwery w sieci. Wpisy publiczne i tylko dla śledzących są dostarczane na serwery, na których znajdują się śledzący Cię, a wiadomości bezpośrednie trafiają na serwery adresatów, jeżeli są oni użytkownikami innego serwera.

+ +

Kiedy pozwolisz aplikacji na dostęp do Twojego konta, w zależności od nadanych jej pozwoleń, może uzyskać dostęp do publicznych informacji, listy śledzonych, Twoich list, wszystkich wpisów i ulubionych. Aplikacje nie mogą uzyskać dostępu do Twojego adresu e-mail i hasła.

+ +
+ +

Children's Online Privacy Protection Act Compliance

+ +

Ta strona, produkty i usługi są przeznaczone dla osób, które ukończyły 13 lat. Jeżeli serwer znajduje się w USA, a nie ukończyłeś 13 roku życia, zgodnie z wymogami COPPA (Prawo o Ochronie Prywatności Dzieci w Internecie), nie używaj tej strony.

+ +
+ +

Zmiany w naszej polityce prywatności

+ +

Jeżeli zdecydujemy się na zmiany w polityce prywatności, pojawią się na tej stronie.

+ +

Dokument jest dostępny na licencji CC-BY-SA. Ostatnio zmodyfikowano go 7 marca 2018, przetłumaczono 9 kwietnia 2018. Tłumaczenie (mimo dołożenia wszelkich starań) może nie być w pełni poprawne.

+ +

Bazowano na polityce prywatności Discourse.

title: Zasady korzystania i polityka prywatności %{instance} themes: default: Mastodon From e057c0e525692a7dcca188f254f043e3c77e1a8a Mon Sep 17 00:00:00 2001 From: Una Date: Mon, 9 Apr 2018 05:34:48 -0400 Subject: [PATCH 101/381] Optimize public/headers/missing.png (#7084) --- public/headers/original/missing.png | Bin 14573 -> 81 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/public/headers/original/missing.png b/public/headers/original/missing.png index fdc34289db331fee185f39fe660d99412d1b4276..26b59e75a08de1c5d3d7b0705e9fc19d317dc4cb 100644 GIT binary patch delta 63 zcmaD`7&t+~l92%j%=3P#04b)DAirP+hO)_LEr2{OPZ!4!j_b(@AQ=Y6M~Zulfh-13 LS3j3^P6Hn!8G zJs~*ll@mf-x$+Ni-~xXEH%?qZNL)cel<|)^FK)84i}pB{k~}|u-+P|VyqWmoCr5`L z+}(O}3n6s(V844ze{Ut9J1^7kw_f}43H^B`*guO9di7rNxrM&|<_&~eJ-2rnpPC

l*J+EN)J@3@I%MSTUxno4>ab^D zo!hD-?igG8T%!plB({Y4Wb8%Sye+1AHEJhrEDEV4K5vV=NkQS%JQ6y7NQ8Q|QL*Li zme5qIa$RjHwFiPC$qg*2SguthS(8*vl7;*bjV)^Fp)=5qyAShn^sFt8;yBPSp3P>} z*>=?rhgeos6-x?MlnPZ;qDwEf<`plxpCwu3>5|9}-5_>-Pe}4weSaFaMKLK<9Qn8= zK~czya(0v=o?8Kys}f!pYr4Q;Y_L5FgG-RNU?u$-KgOv#mv_fF>S zhOp#H7M8X`HbXictJVpL6_YOg7_@_$w0A0s(vuomO@2?NeI)6)BwJ2#n!xlOcW}8b zfuboA%mI|;9JJjXE4H2~3)GhKq=WIpp6`#1Y{?ug`a$S)k|oj<-Z3rP^^zue7Yr>A z0{I+U8~EYGiVb&S4G9jsp^leMpe&k7>-b|oq{Ae}wvN}iK~&mk+JP6vmS>ZLu0b7D z*LAeM)MzRF`k>O6`^`#CRhyMoLy{`Cy4@Po`*v;Mko3+?6NcYN-SzEhvRLxe&U2;) z;Wtu$6uNYyv&KM^-2L;GSd->PseLz!{qPdBEp6U(D?1^du6h?2#%{VL1y&f5WWlw? z#fmHTuwsi!V$y}HS$49S8Ig5C938LtmN!+z|HX>emZNjCMrAk$s zlSKX?o>?K;9ny|?wzZMvT@Bn^z#duNkT?cjuv<9FAv#X}FUIcb+-2U?DP1)A%@)v; zZBV1_LkDA}(F%FPB4?kso_*Y$$oP zk{d)Ndv&&xoY2P#Vl?XY?YfT3?sCMDJ167Qu1lAElr%CWl7^K1Yvax~!~uUEmrWG- z`%mXF^U{Y4`dovH2br3({cP92XN26mVRS2Gi!an24Z&Yis$cAo1w?fiwCd;2+IjAJCTw1$+P4MCj^m zgg*NMp}()`?{5fAC4_!|jF9#fLa+N@KKkh))!sYk?)K)t^#8iP-nmWBudi>TJO8}& V@6X>(EqaO$_71z>z5n?0{{X8qwp0KB From 904a2479dd2085dfc94f33746ad6f7a755e72609 Mon Sep 17 00:00:00 2001 From: Emelia Smith Date: Mon, 9 Apr 2018 17:09:11 +0200 Subject: [PATCH 102/381] Feature: Direct message from Statuses (#7089) * Fix: Switching between composing direct message and mention from menus Previously clicking "direct message" followed by "mention" resulted in the composed status staying as "direct", along with weird spacing of items in the text area. This attempts to fix that. * Fix: Add missing proptype check for onMention in Status component * Add the ability to send a direct message to a user from the menu on Statuses * Add space between "Embed" and "Mention" on expanded statuses menu --- app/javascript/mastodon/components/status.js | 2 ++ .../mastodon/components/status_action_bar.js | 7 +++++++ .../mastodon/containers/status_container.js | 5 +++++ .../features/status/components/action_bar.js | 8 ++++++++ app/javascript/mastodon/features/status/index.js | 6 ++++++ app/javascript/mastodon/locales/defaultMessages.json | 8 ++++++++ app/javascript/mastodon/locales/en.json | 1 + app/javascript/mastodon/reducers/compose.js | 20 +++++++++++--------- 8 files changed, 48 insertions(+), 9 deletions(-) diff --git a/app/javascript/mastodon/components/status.js b/app/javascript/mastodon/components/status.js index a918a94..6129b3f 100644 --- a/app/javascript/mastodon/components/status.js +++ b/app/javascript/mastodon/components/status.js @@ -31,6 +31,8 @@ export default class Status extends ImmutablePureComponent { onFavourite: PropTypes.func, onReblog: PropTypes.func, onDelete: PropTypes.func, + onDirect: PropTypes.func, + onMention: PropTypes.func, onPin: PropTypes.func, onOpenMedia: PropTypes.func, onOpenVideo: PropTypes.func, diff --git a/app/javascript/mastodon/components/status_action_bar.js b/app/javascript/mastodon/components/status_action_bar.js index e036dc1..10f34b0 100644 --- a/app/javascript/mastodon/components/status_action_bar.js +++ b/app/javascript/mastodon/components/status_action_bar.js @@ -9,6 +9,7 @@ import { me } from '../initial_state'; const messages = defineMessages({ delete: { id: 'status.delete', defaultMessage: 'Delete' }, + direct: { id: 'status.direct', defaultMessage: 'Direct message @{name}' }, mention: { id: 'status.mention', defaultMessage: 'Mention @{name}' }, mute: { id: 'account.mute', defaultMessage: 'Mute @{name}' }, block: { id: 'account.block', defaultMessage: 'Block @{name}' }, @@ -41,6 +42,7 @@ export default class StatusActionBar extends ImmutablePureComponent { onFavourite: PropTypes.func, onReblog: PropTypes.func, onDelete: PropTypes.func, + onDirect: PropTypes.func, onMention: PropTypes.func, onMute: PropTypes.func, onBlock: PropTypes.func, @@ -92,6 +94,10 @@ export default class StatusActionBar extends ImmutablePureComponent { this.props.onMention(this.props.status.get('account'), this.context.router.history); } + handleDirectClick = () => { + this.props.onDirect(this.props.status.get('account'), this.context.router.history); + } + handleMuteClick = () => { this.props.onMute(this.props.status.get('account')); } @@ -149,6 +155,7 @@ export default class StatusActionBar extends ImmutablePureComponent { menu.push({ text: intl.formatMessage(messages.delete), action: this.handleDeleteClick }); } else { menu.push({ text: intl.formatMessage(messages.mention, { name: status.getIn(['account', 'username']) }), action: this.handleMentionClick }); + menu.push({ text: intl.formatMessage(messages.direct, { name: status.getIn(['account', 'username']) }), action: this.handleDirectClick }); menu.push(null); menu.push({ text: intl.formatMessage(messages.mute, { name: status.getIn(['account', 'username']) }), action: this.handleMuteClick }); menu.push({ text: intl.formatMessage(messages.block, { name: status.getIn(['account', 'username']) }), action: this.handleBlockClick }); diff --git a/app/javascript/mastodon/containers/status_container.js b/app/javascript/mastodon/containers/status_container.js index 4579bd1..f22509e 100644 --- a/app/javascript/mastodon/containers/status_container.js +++ b/app/javascript/mastodon/containers/status_container.js @@ -5,6 +5,7 @@ import { makeGetStatus } from '../selectors'; import { replyCompose, mentionCompose, + directCompose, } from '../actions/compose'; import { reblog, @@ -102,6 +103,10 @@ const mapDispatchToProps = (dispatch, { intl }) => ({ } }, + onDirect (account, router) { + dispatch(directCompose(account, router)); + }, + onMention (account, router) { dispatch(mentionCompose(account, router)); }, diff --git a/app/javascript/mastodon/features/status/components/action_bar.js b/app/javascript/mastodon/features/status/components/action_bar.js index 13cc10c..4aa6b08 100644 --- a/app/javascript/mastodon/features/status/components/action_bar.js +++ b/app/javascript/mastodon/features/status/components/action_bar.js @@ -8,6 +8,7 @@ import { me } from '../../../initial_state'; const messages = defineMessages({ delete: { id: 'status.delete', defaultMessage: 'Delete' }, + direct: { id: 'status.direct', defaultMessage: 'Direct message @{name}' }, mention: { id: 'status.mention', defaultMessage: 'Mention @{name}' }, reply: { id: 'status.reply', defaultMessage: 'Reply' }, reblog: { id: 'status.reblog', defaultMessage: 'Boost' }, @@ -37,6 +38,7 @@ export default class ActionBar extends React.PureComponent { onReblog: PropTypes.func.isRequired, onFavourite: PropTypes.func.isRequired, onDelete: PropTypes.func.isRequired, + onDirect: PropTypes.func.isRequired, onMention: PropTypes.func.isRequired, onMute: PropTypes.func, onMuteConversation: PropTypes.func, @@ -63,6 +65,10 @@ export default class ActionBar extends React.PureComponent { this.props.onDelete(this.props.status); } + handleDirectClick = () => { + this.props.onDirect(this.props.status.get('account'), this.context.router.history); + } + handleMentionClick = () => { this.props.onMention(this.props.status.get('account'), this.context.router.history); } @@ -108,6 +114,7 @@ export default class ActionBar extends React.PureComponent { if (publicStatus) { menu.push({ text: intl.formatMessage(messages.embed), action: this.handleEmbed }); + menu.push(null); } if (me === status.getIn(['account', 'id'])) { @@ -121,6 +128,7 @@ export default class ActionBar extends React.PureComponent { menu.push({ text: intl.formatMessage(messages.delete), action: this.handleDeleteClick }); } else { menu.push({ text: intl.formatMessage(messages.mention, { name: status.getIn(['account', 'username']) }), action: this.handleMentionClick }); + menu.push({ text: intl.formatMessage(messages.direct, { name: status.getIn(['account', 'username']) }), action: this.handleDirectClick }); menu.push(null); menu.push({ text: intl.formatMessage(messages.mute, { name: status.getIn(['account', 'username']) }), action: this.handleMuteClick }); menu.push({ text: intl.formatMessage(messages.block, { name: status.getIn(['account', 'username']) }), action: this.handleBlockClick }); diff --git a/app/javascript/mastodon/features/status/index.js b/app/javascript/mastodon/features/status/index.js index 2f482b2..55eff08 100644 --- a/app/javascript/mastodon/features/status/index.js +++ b/app/javascript/mastodon/features/status/index.js @@ -19,6 +19,7 @@ import { import { replyCompose, mentionCompose, + directCompose, } from '../../actions/compose'; import { blockAccount } from '../../actions/accounts'; import { @@ -148,6 +149,10 @@ export default class Status extends ImmutablePureComponent { } } + handleDirectClick = (account, router) => { + this.props.dispatch(directCompose(account, router)); + } + handleMentionClick = (account, router) => { this.props.dispatch(mentionCompose(account, router)); } @@ -379,6 +384,7 @@ export default class Status extends ImmutablePureComponent { onFavourite={this.handleFavouriteClick} onReblog={this.handleReblogClick} onDelete={this.handleDeleteClick} + onDirect={this.handleDirectClick} onMention={this.handleMentionClick} onMute={this.handleMuteClick} onMuteConversation={this.handleConversationMuteClick} diff --git a/app/javascript/mastodon/locales/defaultMessages.json b/app/javascript/mastodon/locales/defaultMessages.json index 52b9db9..03be288 100644 --- a/app/javascript/mastodon/locales/defaultMessages.json +++ b/app/javascript/mastodon/locales/defaultMessages.json @@ -198,6 +198,10 @@ "id": "status.delete" }, { + "defaultMessage": "Direct message @{name}", + "id": "status.direct" + }, + { "defaultMessage": "Mention @{name}", "id": "status.mention" }, @@ -1371,6 +1375,10 @@ "id": "status.delete" }, { + "defaultMessage": "Direct message @{name}", + "id": "status.direct" + }, + { "defaultMessage": "Mention @{name}", "id": "status.mention" }, diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json index 2286d2e..a389735 100644 --- a/app/javascript/mastodon/locales/en.json +++ b/app/javascript/mastodon/locales/en.json @@ -240,6 +240,7 @@ "status.block": "Block @{name}", "status.cannot_reblog": "This post cannot be boosted", "status.delete": "Delete", + "status.direct": "Direct message @{name}", "status.embed": "Embed", "status.favourite": "Favourite", "status.load_more": "Load more", diff --git a/app/javascript/mastodon/reducers/compose.js b/app/javascript/mastodon/reducers/compose.js index 1f41775..87049ea 100644 --- a/app/javascript/mastodon/reducers/compose.js +++ b/app/javascript/mastodon/reducers/compose.js @@ -259,16 +259,18 @@ export default function compose(state = initialState, action) { case COMPOSE_UPLOAD_PROGRESS: return state.set('progress', Math.round((action.loaded / action.total) * 100)); case COMPOSE_MENTION: - return state - .update('text', text => `${text}@${action.account.get('acct')} `) - .set('focusDate', new Date()) - .set('idempotencyKey', uuid()); + return state.withMutations(map => { + map.update('text', text => [text.trim(), `@${action.account.get('acct')} `].filter((str) => str.length !== 0).join(' ')); + map.set('focusDate', new Date()); + map.set('idempotencyKey', uuid()); + }); case COMPOSE_DIRECT: - return state - .update('text', text => `@${action.account.get('acct')} `) - .set('privacy', 'direct') - .set('focusDate', new Date()) - .set('idempotencyKey', uuid()); + return state.withMutations(map => { + map.update('text', text => [text.trim(), `@${action.account.get('acct')} `].filter((str) => str.length !== 0).join(' ')); + map.set('privacy', 'direct'); + map.set('focusDate', new Date()); + map.set('idempotencyKey', uuid()); + }); case COMPOSE_SUGGESTIONS_CLEAR: return state.update('suggestions', ImmutableList(), list => list.clear()).set('suggestion_token', null); case COMPOSE_SUGGESTIONS_READY: From 0c52654b5275fd0e7a0b544d3c6f895825e4a266 Mon Sep 17 00:00:00 2001 From: "Renato \"Lond\" Cerqueira" Date: Mon, 9 Apr 2018 23:02:42 +0200 Subject: [PATCH 103/381] When creating status, if no sensitive status is given, use default (#7057) Clients using the API that do not provide the sensitive flag are always posting with false sensitive option. --- app/services/post_status_service.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/services/post_status_service.rb b/app/services/post_status_service.rb index c911921..9d2c7a4 100644 --- a/app/services/post_status_service.rb +++ b/app/services/post_status_service.rb @@ -28,7 +28,7 @@ class PostStatusService < BaseService status = account.statuses.create!(text: text, media_attachments: media || [], thread: in_reply_to, - sensitive: options[:sensitive], + sensitive: (options[:sensitive].nil? ? account.user&.setting_default_sensitive : options[:sensitive]), spoiler_text: options[:spoiler_text] || '', visibility: options[:visibility] || account.user&.setting_default_privacy, language: LanguageDetector.instance.detect(text, account), From 80a944c882ddbca4d546d03503f0ccff15703484 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Tue, 10 Apr 2018 01:20:18 +0200 Subject: [PATCH 104/381] Log rate limit hits (#7096) Fix #7095 --- config/initializers/rack_attack_logging.rb | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 config/initializers/rack_attack_logging.rb diff --git a/config/initializers/rack_attack_logging.rb b/config/initializers/rack_attack_logging.rb new file mode 100644 index 0000000..2ddbfb9 --- /dev/null +++ b/config/initializers/rack_attack_logging.rb @@ -0,0 +1,4 @@ +ActiveSupport::Notifications.subscribe('rack.attack') do |_name, _start, _finish, _request_id, req| + next unless [:throttle, :blacklist].include? req.env['rack.attack.match_type'] + Rails.logger.info("Rate limit hit (#{req.env['rack.attack.match_type']}): #{req.ip} #{req.request_method} #{req.fullpath}") +end From e6e93ecd8a45cea5f0c398054c2292a5fdf944cf Mon Sep 17 00:00:00 2001 From: MIYAGI Hikaru Date: Tue, 10 Apr 2018 16:11:55 +0900 Subject: [PATCH 105/381] Fix GIFV encoding params (#7098) - Explicitly specify video codec. When ffmpeg isn't compiled with libx264 but openh264, mpeg4 is selected as video codec. - Swap avarage bitrate and max bitrate. --- app/models/media_attachment.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/models/media_attachment.rb b/app/models/media_attachment.rb index ac2aa7e..8fd9ac0 100644 --- a/app/models/media_attachment.rb +++ b/app/models/media_attachment.rb @@ -130,8 +130,9 @@ class MediaAttachment < ApplicationRecord 'pix_fmt' => 'yuv420p', 'vf' => 'scale=\'trunc(iw/2)*2:trunc(ih/2)*2\'', 'vsync' => 'cfr', - 'b:v' => '1300K', - 'maxrate' => '500K', + 'c:v' => 'h264', + 'b:v' => '500K', + 'maxrate' => '1300K', 'bufsize' => '1300K', 'crf' => 18, }, From 219a4423d8371fc89f122f3ef4874e9121b423f7 Mon Sep 17 00:00:00 2001 From: Emelia Smith Date: Tue, 10 Apr 2018 09:16:06 +0200 Subject: [PATCH 106/381] Feature: Allow staff to change user emails (#7074) * Admin: Show unconfirmed email address on account page * Admin: Allow staff to change user email addresses * ActionLog: On change_email, log current email address and new unconfirmed email address --- app/controllers/admin/change_emails_controller.rb | 49 ++++++++++++++++++++++ app/helpers/admin/action_logs_helper.rb | 4 +- app/models/account.rb | 1 + app/models/admin/action_log.rb | 5 +++ app/policies/user_policy.rb | 4 ++ app/views/admin/accounts/show.html.haml | 6 ++- app/views/admin/change_emails/show.html.haml | 7 ++++ config/locales/en.yml | 9 ++++ config/routes.rb | 1 + .../admin/change_email_controller_spec.rb | 47 +++++++++++++++++++++ 10 files changed, 131 insertions(+), 2 deletions(-) create mode 100644 app/controllers/admin/change_emails_controller.rb create mode 100644 app/views/admin/change_emails/show.html.haml create mode 100644 spec/controllers/admin/change_email_controller_spec.rb diff --git a/app/controllers/admin/change_emails_controller.rb b/app/controllers/admin/change_emails_controller.rb new file mode 100644 index 0000000..a689d3a --- /dev/null +++ b/app/controllers/admin/change_emails_controller.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +module Admin + class ChangeEmailsController < BaseController + before_action :set_account + before_action :require_local_account! + + def show + authorize @user, :change_email? + end + + def update + authorize @user, :change_email? + + new_email = resource_params.fetch(:unconfirmed_email) + + if new_email != @user.email + @user.update!( + unconfirmed_email: new_email, + # Regenerate the confirmation token: + confirmation_token: nil + ) + + log_action :change_email, @user + + @user.send_confirmation_instructions + end + + redirect_to admin_account_path(@account.id), notice: I18n.t('admin.accounts.change_email.changed_msg') + end + + private + + def set_account + @account = Account.find(params[:account_id]) + @user = @account.user + end + + def require_local_account! + redirect_to admin_account_path(@account.id) unless @account.local? && @account.user.present? + end + + def resource_params + params.require(:user).permit( + :unconfirmed_email + ) + end + end +end diff --git a/app/helpers/admin/action_logs_helper.rb b/app/helpers/admin/action_logs_helper.rb index 7c26c0b..4c66321 100644 --- a/app/helpers/admin/action_logs_helper.rb +++ b/app/helpers/admin/action_logs_helper.rb @@ -45,6 +45,8 @@ module Admin::ActionLogsHelper log.recorded_changes.slice('domain', 'visible_in_picker') elsif log.target_type == 'User' && [:promote, :demote].include?(log.action) log.recorded_changes.slice('moderator', 'admin') + elsif log.target_type == 'User' && [:change_email].include?(log.action) + log.recorded_changes.slice('email', 'unconfirmed_email') elsif log.target_type == 'DomainBlock' log.recorded_changes.slice('severity', 'reject_media') elsif log.target_type == 'Status' && log.action == :update @@ -84,7 +86,7 @@ module Admin::ActionLogsHelper 'positive' when :create opposite_verbs?(log) ? 'negative' : 'positive' - when :update, :reset_password, :disable_2fa, :memorialize + when :update, :reset_password, :disable_2fa, :memorialize, :change_email 'neutral' when :demote, :silence, :disable, :suspend, :remove_avatar, :reopen 'negative' diff --git a/app/models/account.rb b/app/models/account.rb index 446144a..51304fc 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -124,6 +124,7 @@ class Account < ApplicationRecord scope :matches_domain, ->(value) { where(arel_table[:domain].matches("%#{value}%")) } delegate :email, + :unconfirmed_email, :current_sign_in_ip, :current_sign_in_at, :confirmed?, diff --git a/app/models/admin/action_log.rb b/app/models/admin/action_log.rb index c437c8e..81f278e 100644 --- a/app/models/admin/action_log.rb +++ b/app/models/admin/action_log.rb @@ -35,6 +35,11 @@ class Admin::ActionLog < ApplicationRecord self.recorded_changes = target.attributes when :update, :promote, :demote self.recorded_changes = target.previous_changes + when :change_email + self.recorded_changes = ActiveSupport::HashWithIndifferentAccess.new( + email: [target.email, nil], + unconfirmed_email: [nil, target.unconfirmed_email] + ) end end end diff --git a/app/policies/user_policy.rb b/app/policies/user_policy.rb index aae207d..dabdf70 100644 --- a/app/policies/user_policy.rb +++ b/app/policies/user_policy.rb @@ -5,6 +5,10 @@ class UserPolicy < ApplicationPolicy staff? && !record.staff? end + def change_email? + staff? && !record.staff? + end + def disable_2fa? admin? && !record.staff? end diff --git a/app/views/admin/accounts/show.html.haml b/app/views/admin/accounts/show.html.haml index fecfd6c..7312618 100644 --- a/app/views/admin/accounts/show.html.haml +++ b/app/views/admin/accounts/show.html.haml @@ -36,9 +36,13 @@ %th= t('admin.accounts.email') %td = @account.user_email - - if @account.user_confirmed? = fa_icon('check') + = table_link_to 'edit', t('admin.accounts.change_email.label'), admin_account_change_email_path(@account.id) if can?(:change_email, @account.user) + - if @account.user_unconfirmed_email.present? + %th= t('admin.accounts.unconfirmed_email') + %td + = @account.user_unconfirmed_email %tr %th= t('admin.accounts.login_status') %td diff --git a/app/views/admin/change_emails/show.html.haml b/app/views/admin/change_emails/show.html.haml new file mode 100644 index 0000000..a661b1a --- /dev/null +++ b/app/views/admin/change_emails/show.html.haml @@ -0,0 +1,7 @@ +- content_for :page_title do + = t('admin.accounts.change_email.title', username: @account.acct) + += simple_form_for @user, url: admin_account_change_email_path(@account.id) do |f| + = f.input :email, wrapper: :with_label, disabled: true, label: t('admin.accounts.change_email.current_email') + = f.input :unconfirmed_email, wrapper: :with_label, label: t('admin.accounts.change_email.new_email') + = f.button :submit, class: "button", value: t('admin.accounts.change_email.submit') diff --git a/config/locales/en.yml b/config/locales/en.yml index 70af953..11f3fb9 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -63,6 +63,13 @@ en: are_you_sure: Are you sure? avatar: Avatar by_domain: Domain + change_email: + changed_msg: Account email successfully changed! + current_email: Current Email + label: Change Email + new_email: New Email + submit: Change Email + title: Change Email for %{username} confirm: Confirm confirmed: Confirmed demote: Demote @@ -131,6 +138,7 @@ en: statuses: Statuses subscribe: Subscribe title: Accounts + unconfirmed_email: Unconfirmed E-mail undo_silenced: Undo silence undo_suspension: Undo suspension unsubscribe: Unsubscribe @@ -139,6 +147,7 @@ en: action_logs: actions: assigned_to_self_report: "%{name} assigned report %{target} to themselves" + change_email_user: "%{name} changed the e-mail address of user %{target}" confirm_user: "%{name} confirmed e-mail address of user %{target}" create_custom_emoji: "%{name} uploaded new emoji %{target}" create_domain_block: "%{name} blocked domain %{target}" diff --git a/config/routes.rb b/config/routes.rb index 7187fd7..2776898 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -151,6 +151,7 @@ Rails.application.routes.draw do post :memorialize end + resource :change_email, only: [:show, :update] resource :reset, only: [:create] resource :silence, only: [:create, :destroy] resource :suspension, only: [:create, :destroy] diff --git a/spec/controllers/admin/change_email_controller_spec.rb b/spec/controllers/admin/change_email_controller_spec.rb new file mode 100644 index 0000000..50f94f8 --- /dev/null +++ b/spec/controllers/admin/change_email_controller_spec.rb @@ -0,0 +1,47 @@ +require 'rails_helper' + +RSpec.describe Admin::ChangeEmailsController, type: :controller do + render_views + + let(:admin) { Fabricate(:user, admin: true) } + + before do + sign_in admin + end + + describe "GET #show" do + it "returns http success" do + account = Fabricate(:account) + user = Fabricate(:user, account: account) + + get :show, params: { account_id: account.id } + + expect(response).to have_http_status(:success) + end + end + + describe "GET #update" do + before do + allow(UserMailer).to receive(:confirmation_instructions).and_return(double('email', deliver_later: nil)) + end + + it "returns http success" do + account = Fabricate(:account) + user = Fabricate(:user, account: account) + + previous_email = user.email + + post :update, params: { account_id: account.id, user: { unconfirmed_email: 'test@example.com' } } + + user.reload + + expect(user.email).to eq previous_email + expect(user.unconfirmed_email).to eq 'test@example.com' + expect(user.confirmation_token).not_to be_nil + + expect(UserMailer).to have_received(:confirmation_instructions).with(user, user.confirmation_token, { to: 'test@example.com' }) + + expect(response).to redirect_to(admin_account_path(account.id)) + end + end +end From 8f800ad6917e5fb41d17098f3f860d0d1aedcabe Mon Sep 17 00:00:00 2001 From: Paul Woolcock Date: Tue, 10 Apr 2018 09:46:27 -0400 Subject: [PATCH 107/381] Change custom emoji search to `ILIKE` instead of `=` (#7099) --- app/models/custom_emoji.rb | 4 ++++ app/models/custom_emoji_filter.rb | 2 +- spec/models/custom_emoji_spec.rb | 24 ++++++++++++++++++++++++ 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/app/models/custom_emoji.rb b/app/models/custom_emoji.rb index 476178e..1ec21d1 100644 --- a/app/models/custom_emoji.rb +++ b/app/models/custom_emoji.rb @@ -58,5 +58,9 @@ class CustomEmoji < ApplicationRecord where(shortcode: shortcodes, domain: domain, disabled: false) end + + def search(shortcode) + where('"custom_emojis"."shortcode" ILIKE ?', "%#{shortcode}%") + end end end diff --git a/app/models/custom_emoji_filter.rb b/app/models/custom_emoji_filter.rb index 2c09ed6..c4bc310 100644 --- a/app/models/custom_emoji_filter.rb +++ b/app/models/custom_emoji_filter.rb @@ -28,7 +28,7 @@ class CustomEmojiFilter when 'by_domain' CustomEmoji.where(domain: value) when 'shortcode' - CustomEmoji.where(shortcode: value) + CustomEmoji.search(value) else raise "Unknown filter: #{key}" end diff --git a/spec/models/custom_emoji_spec.rb b/spec/models/custom_emoji_spec.rb index bb150b8..87367df 100644 --- a/spec/models/custom_emoji_spec.rb +++ b/spec/models/custom_emoji_spec.rb @@ -1,6 +1,30 @@ require 'rails_helper' RSpec.describe CustomEmoji, type: :model do + describe '#search' do + let(:custom_emoji) { Fabricate(:custom_emoji, shortcode: shortcode) } + + subject { described_class.search(search_term) } + + context 'shortcode is exact' do + let(:shortcode) { 'blobpats' } + let(:search_term) { 'blobpats' } + + it 'finds emoji' do + is_expected.to include(custom_emoji) + end + end + + context 'shortcode is partial' do + let(:shortcode) { 'blobpats' } + let(:search_term) { 'blob' } + + it 'finds emoji' do + is_expected.to include(custom_emoji) + end + end + end + describe '#local?' do let(:custom_emoji) { Fabricate(:custom_emoji, domain: domain) } From 49bbef1202f483d4e8abc43d00d12551bb25f80f Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Tue, 10 Apr 2018 16:08:28 +0200 Subject: [PATCH 108/381] Use RAILS_LOG_LEVEL to set log level of Sidekiq, too (#7079) Fix #3565 (oops) --- config/initializers/sidekiq.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/config/initializers/sidekiq.rb b/config/initializers/sidekiq.rb index f875fbd..05c8041 100644 --- a/config/initializers/sidekiq.rb +++ b/config/initializers/sidekiq.rb @@ -1,10 +1,10 @@ # frozen_string_literal: true -namespace = ENV.fetch('REDIS_NAMESPACE') { nil } +namespace = ENV.fetch('REDIS_NAMESPACE') { nil } redis_params = { url: ENV['REDIS_URL'] } if namespace - redis_params [:namespace] = namespace + redis_params[:namespace] = namespace end Sidekiq.configure_server do |config| @@ -18,3 +18,5 @@ end Sidekiq.configure_client do |config| config.redis = redis_params end + +Sidekiq::Logging.logger.level = ::Logger::const_get(ENV.fetch('RAILS_LOG_LEVEL', 'info').upcase.to_s) From 45c9f16f714dd6de15391b5e2ae2bf0d30ef20fb Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Tue, 10 Apr 2018 17:12:10 +0200 Subject: [PATCH 109/381] Improve load gap styling in web UI (#7100) --- app/javascript/mastodon/components/load_gap.js | 33 ++++++++++++++++++++++ app/javascript/mastodon/components/status_list.js | 20 +------------ .../mastodon/features/notifications/index.js | 20 +------------ app/javascript/styles/mastodon/components.scss | 4 +++ 4 files changed, 39 insertions(+), 38 deletions(-) create mode 100644 app/javascript/mastodon/components/load_gap.js diff --git a/app/javascript/mastodon/components/load_gap.js b/app/javascript/mastodon/components/load_gap.js new file mode 100644 index 0000000..012303a --- /dev/null +++ b/app/javascript/mastodon/components/load_gap.js @@ -0,0 +1,33 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { injectIntl, defineMessages } from 'react-intl'; + +const messages = defineMessages({ + load_more: { id: 'status.load_more', defaultMessage: 'Load more' }, +}); + +@injectIntl +export default class LoadGap extends React.PureComponent { + + static propTypes = { + disabled: PropTypes.bool, + maxId: PropTypes.string, + onClick: PropTypes.func.isRequired, + intl: PropTypes.object.isRequired, + }; + + handleClick = () => { + this.props.onClick(this.props.maxId); + } + + render () { + const { disabled, intl } = this.props; + + return ( + + ); + } + +} diff --git a/app/javascript/mastodon/components/status_list.js b/app/javascript/mastodon/components/status_list.js index 8c2673f..c98d456 100644 --- a/app/javascript/mastodon/components/status_list.js +++ b/app/javascript/mastodon/components/status_list.js @@ -4,28 +4,10 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import PropTypes from 'prop-types'; import StatusContainer from '../containers/status_container'; import ImmutablePureComponent from 'react-immutable-pure-component'; -import LoadMore from './load_more'; +import LoadGap from './load_gap'; import ScrollableList from './scrollable_list'; import { FormattedMessage } from 'react-intl'; -class LoadGap extends ImmutablePureComponent { - - static propTypes = { - disabled: PropTypes.bool, - maxId: PropTypes.string, - onClick: PropTypes.func.isRequired, - }; - - handleClick = () => { - this.props.onClick(this.props.maxId); - } - - render () { - return ; - } - -} - export default class StatusList extends ImmutablePureComponent { static propTypes = { diff --git a/app/javascript/mastodon/features/notifications/index.js b/app/javascript/mastodon/features/notifications/index.js index 9a6fb45..94a46b8 100644 --- a/app/javascript/mastodon/features/notifications/index.js +++ b/app/javascript/mastodon/features/notifications/index.js @@ -13,7 +13,7 @@ import { createSelector } from 'reselect'; import { List as ImmutableList } from 'immutable'; import { debounce } from 'lodash'; import ScrollableList from '../../components/scrollable_list'; -import LoadMore from '../../components/load_more'; +import LoadGap from '../../components/load_gap'; const messages = defineMessages({ title: { id: 'column.notifications', defaultMessage: 'Notifications' }, @@ -24,24 +24,6 @@ const getNotifications = createSelector([ state => state.getIn(['notifications', 'items']), ], (excludedTypes, notifications) => notifications.filterNot(item => item !== null && excludedTypes.includes(item.get('type')))); -class LoadGap extends React.PureComponent { - - static propTypes = { - disabled: PropTypes.bool, - maxId: PropTypes.string, - onClick: PropTypes.func.isRequired, - }; - - handleClick = () => { - this.props.onClick(this.props.maxId); - } - - render () { - return ; - } - -} - const mapStateToProps = state => ({ notifications: getNotifications(state), isLoading: state.getIn(['notifications', 'isLoading'], true), diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index d76dc10..888a0ad 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -2455,6 +2455,10 @@ a.status-card { } } +.load-gap { + border-bottom: 1px solid lighten($ui-base-color, 8%); +} + .regeneration-indicator { text-align: center; font-size: 16px; From d9b62e34da0c0238176f27557ac7b953da94df7e Mon Sep 17 00:00:00 2001 From: Emelia Smith Date: Tue, 10 Apr 2018 20:27:59 +0200 Subject: [PATCH 110/381] Feature: Improve reports ui (#7032) * Further improvements to Reports UI - Clean up notes display - Clean up add new note form - Simplify controller - Allow reopening a report with a note - Show created at date for reports - Fix report details table formatting * Show history of report using Admin::ActionLog beneath the report * Fix incorrect log message when reopening a report * Implement fetching of all ActionLog items that could be related to the report * Ensure adding a report_note updates the report's updated_at * Limit Report History to actions that happened between the report being created and the report being resolved * Fix linting issues * Improve report history builder Thanks @gargron for the improvements --- app/controllers/admin/report_notes_controller.rb | 17 +++++--- app/controllers/admin/reports_controller.rb | 20 +++++---- app/javascript/styles/mastodon/admin.scss | 35 ++++++++++++++++ app/models/report.rb | 46 +++++++++++++++++++++ app/models/report_note.rb | 2 +- .../admin/report_notes/_report_note.html.haml | 12 +++--- app/views/admin/reports/show.html.haml | 47 ++++++++++++++-------- config/locales/en.yml | 11 +++-- 8 files changed, 147 insertions(+), 43 deletions(-) diff --git a/app/controllers/admin/report_notes_controller.rb b/app/controllers/admin/report_notes_controller.rb index ef8c0f4..bcb3f20 100644 --- a/app/controllers/admin/report_notes_controller.rb +++ b/app/controllers/admin/report_notes_controller.rb @@ -8,19 +8,26 @@ module Admin authorize ReportNote, :create? @report_note = current_account.report_notes.new(resource_params) + @report = @report_note.report if @report_note.save if params[:create_and_resolve] - @report_note.report.update!(action_taken: true, action_taken_by_account_id: current_account.id) - log_action :resolve, @report_note.report + @report.resolve!(current_account) + log_action :resolve, @report redirect_to admin_reports_path, notice: I18n.t('admin.reports.resolved_msg') - else - redirect_to admin_report_path(@report_note.report_id), notice: I18n.t('admin.report_notes.created_msg') + return end + + if params[:create_and_unresolve] + @report.unresolve! + log_action :reopen, @report + end + + redirect_to admin_report_path(@report), notice: I18n.t('admin.report_notes.created_msg') else - @report = @report_note.report @report_notes = @report.notes.latest + @report_history = @report.history @form = Form::StatusBatch.new render template: 'admin/reports/show' diff --git a/app/controllers/admin/reports_controller.rb b/app/controllers/admin/reports_controller.rb index fc3785e..a4ae950 100644 --- a/app/controllers/admin/reports_controller.rb +++ b/app/controllers/admin/reports_controller.rb @@ -13,6 +13,7 @@ module Admin authorize @report, :show? @report_note = @report.notes.new @report_notes = @report.notes.latest + @report_history = @report.history @form = Form::StatusBatch.new end @@ -38,36 +39,33 @@ module Admin @report.update!(assigned_account_id: nil) log_action :unassigned, @report when 'reopen' - @report.update!(action_taken: false, action_taken_by_account_id: nil) + @report.unresolve! log_action :reopen, @report when 'resolve' - @report.update!(action_taken_by_current_attributes) + @report.resolve!(current_account) log_action :resolve, @report when 'suspend' Admin::SuspensionWorker.perform_async(@report.target_account.id) + log_action :resolve, @report log_action :suspend, @report.target_account + resolve_all_target_account_reports - @report.reload when 'silence' @report.target_account.update!(silenced: true) + log_action :resolve, @report log_action :silence, @report.target_account + resolve_all_target_account_reports - @report.reload else raise ActiveRecord::RecordNotFound end - end - - def action_taken_by_current_attributes - { action_taken: true, action_taken_by_account_id: current_account.id } + @report.reload end def resolve_all_target_account_reports - unresolved_reports_for_target_account.update_all( - action_taken_by_current_attributes - ) + unresolved_reports_for_target_account.update_all(action_taken: true, action_taken_by_account_id: current_account.id) end def unresolved_reports_for_target_account diff --git a/app/javascript/styles/mastodon/admin.scss b/app/javascript/styles/mastodon/admin.scss index e6bd0c7..6bd6590 100644 --- a/app/javascript/styles/mastodon/admin.scss +++ b/app/javascript/styles/mastodon/admin.scss @@ -145,6 +145,11 @@ border: 0; background: transparent; border-bottom: 1px solid $ui-base-color; + + &.section-break { + margin: 30px 0; + border-bottom: 2px solid $ui-base-lighter-color; + } } .muted-hint { @@ -330,6 +335,36 @@ } } +.report-note__comment { + margin-bottom: 20px; +} + +.report-note__form { + margin-bottom: 20px; + + .report-note__textarea { + box-sizing: border-box; + border: 0; + padding: 7px 4px; + margin-bottom: 10px; + font-size: 16px; + color: $ui-base-color; + display: block; + width: 100%; + outline: 0; + font-family: inherit; + resize: vertical; + } + + .report-note__buttons { + text-align: right; + } + + .report-note__button { + margin: 0 0 5px 5px; + } +} + .batch-form-box { display: flex; flex-wrap: wrap; diff --git a/app/models/report.rb b/app/models/report.rb index f5b37cb..5b90c7b 100644 --- a/app/models/report.rb +++ b/app/models/report.rb @@ -39,4 +39,50 @@ class Report < ApplicationRecord def media_attachments MediaAttachment.where(status_id: status_ids) end + + def assign_to_self!(current_account) + update!(assigned_account_id: current_account.id) + end + + def unassign! + update!(assigned_account_id: nil) + end + + def resolve!(acting_account) + update!(action_taken: true, action_taken_by_account_id: acting_account.id) + end + + def unresolve! + update!(action_taken: false, action_taken_by_account_id: nil) + end + + def unresolved? + !action_taken? + end + + def history + time_range = created_at..updated_at + + sql = [ + Admin::ActionLog.where( + target_type: 'Report', + target_id: id, + created_at: time_range + ).unscope(:order), + + Admin::ActionLog.where( + target_type: 'Account', + target_id: target_account_id, + created_at: time_range + ).unscope(:order), + + Admin::ActionLog.where( + target_type: 'Status', + target_id: status_ids, + created_at: time_range + ).unscope(:order), + ].map { |query| "(#{query.to_sql})" }.join(' UNION ALL ') + + Admin::ActionLog.from("(#{sql}) AS admin_action_logs") + end end diff --git a/app/models/report_note.rb b/app/models/report_note.rb index 3d12cf7..6d9dec8 100644 --- a/app/models/report_note.rb +++ b/app/models/report_note.rb @@ -13,7 +13,7 @@ class ReportNote < ApplicationRecord belongs_to :account - belongs_to :report, inverse_of: :notes + belongs_to :report, inverse_of: :notes, touch: true scope :latest, -> { reorder('created_at ASC') } diff --git a/app/views/admin/report_notes/_report_note.html.haml b/app/views/admin/report_notes/_report_note.html.haml index 60ac5d0..1f621e0 100644 --- a/app/views/admin/report_notes/_report_note.html.haml +++ b/app/views/admin/report_notes/_report_note.html.haml @@ -1,11 +1,9 @@ -%tr - %td - %p - %strong= report_note.account.acct - on +%li + %h4 + = report_note.account.acct + %div{ style: 'float: right' } %time.formatted{ datetime: report_note.created_at.iso8601, title: l(report_note.created_at) } = l report_note.created_at = table_link_to 'trash', t('admin.reports.notes.delete'), admin_report_note_path(report_note), method: :delete if can?(:destroy, report_note) - %br/ - %br/ + %div{ class: 'report-note__comment' } = simple_format(h(report_note.content)) diff --git a/app/views/admin/reports/show.html.haml b/app/views/admin/reports/show.html.haml index e7634a0..d57b4ad 100644 --- a/app/views/admin/reports/show.html.haml +++ b/app/views/admin/reports/show.html.haml @@ -5,7 +5,7 @@ = t('admin.reports.report', id: @report.id) %div{ style: 'overflow: hidden; margin-bottom: 20px' } - - if !@report.action_taken? + - if @report.unresolved? %div{ style: 'float: right' } = link_to t('admin.reports.silence_account'), admin_report_path(@report, outcome: 'silence'), method: :put, class: 'button' = link_to t('admin.reports.suspend_account'), admin_report_path(@report, outcome: 'suspend'), method: :put, class: 'button' @@ -18,21 +18,28 @@ %table.table.inline-table %tbody %tr + %th= t('admin.reports.created_at') + %td{colspan: 2} + %time.formatted{ datetime: @report.created_at.iso8601 } + %tr %th= t('admin.reports.updated_at') %td{colspan: 2} %time.formatted{ datetime: @report.updated_at.iso8601 } %tr %th= t('admin.reports.status') - %td{colspan: 2} + %td - if @report.action_taken? = t('admin.reports.resolved') - = table_link_to 'envelope-open', t('admin.reports.reopen'), admin_report_path(@report, outcome: 'reopen'), method: :put - else = t('admin.reports.unresolved') + %td{style: "text-align: right; overflow: hidden;"} + - if @report.action_taken? + = table_link_to 'envelope-open', t('admin.reports.reopen'), admin_report_path(@report, outcome: 'reopen'), method: :put - if !@report.action_taken_by_account.nil? %tr %th= t('admin.reports.action_taken_by') - %td= @report.action_taken_by_account.acct + %td{colspan: 2} + = @report.action_taken_by_account.acct - else %tr %th= t('admin.reports.assigned') @@ -47,6 +54,8 @@ - if !@report.assigned_account.nil? = table_link_to 'trash', t('admin.reports.unassign'), admin_report_path(@report, outcome: 'unassign'), method: :put +%hr{ class: "section-break"}/ + .report-accounts .report-accounts__item %h3= t('admin.reports.reported_account') @@ -88,22 +97,28 @@ = link_to admin_report_reported_status_path(@report, status), method: :delete, class: 'icon-button trash-button', title: t('admin.reports.delete'), data: { confirm: t('admin.reports.are_you_sure') }, remote: true do = fa_icon 'trash' -%hr/ +%hr{ class: "section-break"}/ %h3= t('admin.reports.notes.label') - if @report_notes.length > 0 - .table-wrapper - %table.table - %thead - %tr - %th - %tbody - = render @report_notes + %ul + = render @report_notes -= simple_form_for @report_note, url: admin_report_notes_path do |f| +%h4= t('admin.reports.notes.new_label') += form_for @report_note, url: admin_report_notes_path, html: { class: 'report-note__form' } do |f| = render 'shared/error_messages', object: @report_note - = f.input :content + = f.text_area :content, placeholder: t('admin.reports.notes.placeholder'), rows: 6, class: 'report-note__textarea' = f.hidden_field :report_id - = f.button :button, t('admin.reports.notes.create'), type: :submit - = f.button :button, t('admin.reports.notes.create_and_resolve'), type: :submit, name: :create_and_resolve + %div{ class: 'report-note__buttons' } + - if @report.unresolved? + = f.submit t('admin.reports.notes.create_and_resolve'), name: :create_and_resolve, class: 'button report-note__button' + - else + = f.submit t('admin.reports.notes.create_and_unresolve'), name: :create_and_unresolve, class: 'button report-note__button' + = f.submit t('admin.reports.notes.create'), class: 'button report-note__button' + +- if @report_history.length > 0 + %h3= t('admin.reports.history') + + %ul + = render @report_history diff --git a/config/locales/en.yml b/config/locales/en.yml index 11f3fb9..98f1359 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -256,8 +256,8 @@ en: title: Filter title: Invites report_notes: - created_msg: Moderation note successfully created! - destroyed_msg: Moderation note successfully destroyed! + created_msg: Report note successfully created! + destroyed_msg: Report note successfully deleted! reports: action_taken_by: Action taken by are_you_sure: Are you sure? @@ -266,15 +266,20 @@ en: comment: label: Report Comment none: None + created_at: Reported delete: Delete + history: Moderation History id: ID mark_as_resolved: Mark as resolved mark_as_unresolved: Mark as unresolved notes: create: Add Note create_and_resolve: Resolve with Note + create_and_unresolve: Reopen with Note delete: Delete - label: Notes + label: Moderator Notes + new_label: Add Moderator Note + placeholder: Describe what actions have been taken, or any other updates to this report… nsfw: 'false': Unhide media attachments 'true': Hide media attachments From 519119f657cf97ec187008a28dba00c1125a9292 Mon Sep 17 00:00:00 2001 From: Akihiko Odaki Date: Wed, 11 Apr 2018 19:35:09 +0900 Subject: [PATCH 111/381] Paginate ancestor statuses in public page (#7102) This also limits the statuses returned by API, but pagination is not implemented in Web API yet. I still expect it brings user experience better than making a user wait to fetch all ancestor statuses and flooding the column with them. --- app/controllers/api/v1/statuses_controller.rb | 2 +- app/controllers/statuses_controller.rb | 7 ++-- app/javascript/styles/mastodon/stream_entries.scss | 14 +++++++- app/models/concerns/status_threading_concern.rb | 24 +++++++++----- app/views/stream_entries/_status.html.haml | 4 +++ .../concerns/status_threading_concern_spec.rb | 38 +++++++++++++++++----- spec/views/stream_entries/show.html.haml_spec.rb | 2 +- 7 files changed, 70 insertions(+), 21 deletions(-) diff --git a/app/controllers/api/v1/statuses_controller.rb b/app/controllers/api/v1/statuses_controller.rb index 28c2859..e982413 100644 --- a/app/controllers/api/v1/statuses_controller.rb +++ b/app/controllers/api/v1/statuses_controller.rb @@ -17,7 +17,7 @@ class Api::V1::StatusesController < Api::BaseController end def context - ancestors_results = @status.in_reply_to_id.nil? ? [] : @status.ancestors(current_account) + ancestors_results = @status.in_reply_to_id.nil? ? [] : @status.ancestors(DEFAULT_STATUSES_LIMIT, current_account) descendants_results = @status.descendants(current_account) loaded_ancestors = cache_collection(ancestors_results, Status) loaded_descendants = cache_collection(descendants_results, Status) diff --git a/app/controllers/statuses_controller.rb b/app/controllers/statuses_controller.rb index 45226c8..41f098a 100644 --- a/app/controllers/statuses_controller.rb +++ b/app/controllers/statuses_controller.rb @@ -4,6 +4,8 @@ class StatusesController < ApplicationController include SignatureAuthentication include Authorization + ANCESTORS_LIMIT = 20 + layout 'public' before_action :set_account @@ -16,8 +18,9 @@ class StatusesController < ApplicationController def show respond_to do |format| format.html do - @ancestors = @status.reply? ? cache_collection(@status.ancestors(current_account), Status) : [] - @descendants = cache_collection(@status.descendants(current_account), Status) + @ancestors = @status.reply? ? cache_collection(@status.ancestors(ANCESTORS_LIMIT, current_account), Status) : [] + @next_ancestor = @ancestors.size < ANCESTORS_LIMIT ? nil : @ancestors.shift + @descendants = cache_collection(@status.descendants(current_account), Status) render 'stream_entries/show' end diff --git a/app/javascript/styles/mastodon/stream_entries.scss b/app/javascript/styles/mastodon/stream_entries.scss index 442b143..dfdc48d 100644 --- a/app/javascript/styles/mastodon/stream_entries.scss +++ b/app/javascript/styles/mastodon/stream_entries.scss @@ -6,7 +6,8 @@ background: $simple-background-color; .detailed-status.light, - .status.light { + .status.light, + .more.light { border-bottom: 1px solid $ui-secondary-color; animation: none; } @@ -290,6 +291,17 @@ text-decoration: underline; } } + + .more { + color: $classic-primary-color; + display: block; + padding: 14px; + text-align: center; + + &:not(:hover) { + text-decoration: none; + } + } } .embed { diff --git a/app/models/concerns/status_threading_concern.rb b/app/models/concerns/status_threading_concern.rb index b539ba1..fffc095 100644 --- a/app/models/concerns/status_threading_concern.rb +++ b/app/models/concerns/status_threading_concern.rb @@ -3,8 +3,8 @@ module StatusThreadingConcern extend ActiveSupport::Concern - def ancestors(account = nil) - find_statuses_from_tree_path(ancestor_ids, account) + def ancestors(limit, account = nil) + find_statuses_from_tree_path(ancestor_ids(limit), account) end def descendants(account = nil) @@ -13,14 +13,21 @@ module StatusThreadingConcern private - def ancestor_ids - Rails.cache.fetch("ancestors:#{id}") do - ancestor_statuses.pluck(:id) + def ancestor_ids(limit) + key = "ancestors:#{id}" + ancestors = Rails.cache.fetch(key) + + if ancestors.nil? || ancestors[:limit] < limit + ids = ancestor_statuses(limit).pluck(:id).reverse! + Rails.cache.write key, limit: limit, ids: ids + ids + else + ancestors[:ids].last(limit) end end - def ancestor_statuses - Status.find_by_sql([<<-SQL.squish, id: in_reply_to_id]) + def ancestor_statuses(limit) + Status.find_by_sql([<<-SQL.squish, id: in_reply_to_id, limit: limit]) WITH RECURSIVE search_tree(id, in_reply_to_id, path) AS ( SELECT id, in_reply_to_id, ARRAY[id] @@ -34,7 +41,8 @@ module StatusThreadingConcern ) SELECT id FROM search_tree - ORDER BY path DESC + ORDER BY path + LIMIT :limit SQL end diff --git a/app/views/stream_entries/_status.html.haml b/app/views/stream_entries/_status.html.haml index e2e1fdd..2d0dafc 100644 --- a/app/views/stream_entries/_status.html.haml +++ b/app/views/stream_entries/_status.html.haml @@ -14,6 +14,10 @@ entry_classes = h_class + ' ' + mf_classes + ' ' + style_classes - if status.reply? && include_threads + - if @next_ancestor + .entry{ class: entry_classes } + = link_to short_account_status_url(@next_ancestor.account.username, @next_ancestor), class: 'more light' do + = t('statuses.show_more') = render partial: 'stream_entries/status', collection: @ancestors, as: :status, locals: { is_predecessor: true, direct_reply_id: status.in_reply_to_id } .entry{ class: entry_classes } diff --git a/spec/models/concerns/status_threading_concern_spec.rb b/spec/models/concerns/status_threading_concern_spec.rb index 62f5f6e..b8ebdd5 100644 --- a/spec/models/concerns/status_threading_concern_spec.rb +++ b/spec/models/concerns/status_threading_concern_spec.rb @@ -14,34 +14,34 @@ describe StatusThreadingConcern do let!(:viewer) { Fabricate(:account, username: 'viewer') } it 'returns conversation history' do - expect(reply3.ancestors).to include(status, reply1, reply2) + expect(reply3.ancestors(4)).to include(status, reply1, reply2) end it 'does not return conversation history user is not allowed to see' do reply1.update(visibility: :private) status.update(visibility: :direct) - expect(reply3.ancestors(viewer)).to_not include(reply1, status) + expect(reply3.ancestors(4, viewer)).to_not include(reply1, status) end it 'does not return conversation history from blocked users' do viewer.block!(jeff) - expect(reply3.ancestors(viewer)).to_not include(reply1) + expect(reply3.ancestors(4, viewer)).to_not include(reply1) end it 'does not return conversation history from muted users' do viewer.mute!(jeff) - expect(reply3.ancestors(viewer)).to_not include(reply1) + expect(reply3.ancestors(4, viewer)).to_not include(reply1) end it 'does not return conversation history from silenced and not followed users' do jeff.update(silenced: true) - expect(reply3.ancestors(viewer)).to_not include(reply1) + expect(reply3.ancestors(4, viewer)).to_not include(reply1) end it 'does not return conversation history from blocked domains' do viewer.block_domain!('example.com') - expect(reply3.ancestors(viewer)).to_not include(reply2) + expect(reply3.ancestors(4, viewer)).to_not include(reply2) end it 'ignores deleted records' do @@ -49,10 +49,32 @@ describe StatusThreadingConcern do second_status = Fabricate(:status, thread: first_status, account: alice) # Create cache and delete cached record - second_status.ancestors + second_status.ancestors(4) first_status.destroy - expect(second_status.ancestors).to eq([]) + expect(second_status.ancestors(4)).to eq([]) + end + + it 'can return more records than previously requested' do + first_status = Fabricate(:status, account: bob) + second_status = Fabricate(:status, thread: first_status, account: alice) + third_status = Fabricate(:status, thread: second_status, account: alice) + + # Create cache + second_status.ancestors(1) + + expect(third_status.ancestors(2)).to eq([first_status, second_status]) + end + + it 'can return fewer records than previously requested' do + first_status = Fabricate(:status, account: bob) + second_status = Fabricate(:status, thread: first_status, account: alice) + third_status = Fabricate(:status, thread: second_status, account: alice) + + # Create cache + second_status.ancestors(2) + + expect(third_status.ancestors(1)).to eq([second_status]) end end diff --git a/spec/views/stream_entries/show.html.haml_spec.rb b/spec/views/stream_entries/show.html.haml_spec.rb index 59ea409..6074bbc 100644 --- a/spec/views/stream_entries/show.html.haml_spec.rb +++ b/spec/views/stream_entries/show.html.haml_spec.rb @@ -48,7 +48,7 @@ describe 'stream_entries/show.html.haml', without_verify_partial_doubles: true d assign(:stream_entry, reply.stream_entry) assign(:account, alice) assign(:type, reply.stream_entry.activity_type.downcase) - assign(:ancestors, reply.stream_entry.activity.ancestors(bob) ) + assign(:ancestors, reply.stream_entry.activity.ancestors(1, bob) ) assign(:descendants, reply.stream_entry.activity.descendants(bob)) render From 12f5f13fab1023c3cb2a50c7bcb11f281c7984fb Mon Sep 17 00:00:00 2001 From: ThibG Date: Wed, 11 Apr 2018 20:42:50 +0200 Subject: [PATCH 112/381] Place privacy dropdown menu top if it is closer to the bottom of the viewport (#7106) --- .../compose/components/privacy_dropdown.js | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/app/javascript/mastodon/features/compose/components/privacy_dropdown.js b/app/javascript/mastodon/features/compose/components/privacy_dropdown.js index e5de131..6b22ba8 100644 --- a/app/javascript/mastodon/features/compose/components/privacy_dropdown.js +++ b/app/javascript/mastodon/features/compose/components/privacy_dropdown.js @@ -32,6 +32,10 @@ class PrivacyDropdownMenu extends React.PureComponent { onChange: PropTypes.func.isRequired, }; + state = { + mounted: false, + }; + handleDocumentClick = e => { if (this.node && !this.node.contains(e.target)) { this.props.onClose(); @@ -54,6 +58,7 @@ class PrivacyDropdownMenu extends React.PureComponent { componentDidMount () { document.addEventListener('click', this.handleDocumentClick, false); document.addEventListener('touchend', this.handleDocumentClick, listenerOptions); + this.setState({ mounted: true }); } componentWillUnmount () { @@ -66,12 +71,16 @@ class PrivacyDropdownMenu extends React.PureComponent { } render () { + const { mounted } = this.state; const { style, items, value } = this.props; return ( {({ opacity, scaleX, scaleY }) => ( -

+ // It should not be transformed when mounting because the resulting + // size will be used to determine the coordinate of the menu by + // react-overlays +
{items.map(item => (
@@ -107,9 +116,10 @@ export default class PrivacyDropdown extends React.PureComponent { state = { open: false, + placement: null, }; - handleToggle = () => { + handleToggle = ({ target }) => { if (this.props.isUserTouching()) { if (this.state.open) { this.props.onModalClose(); @@ -120,6 +130,8 @@ export default class PrivacyDropdown extends React.PureComponent { }); } } else { + const { top } = target.getBoundingClientRect(); + this.setState({ placement: top * 2 < innerHeight ? 'bottom' : 'top' }); this.setState({ open: !this.state.open }); } } @@ -136,7 +148,7 @@ export default class PrivacyDropdown extends React.PureComponent { handleKeyDown = e => { switch(e.key) { case 'Enter': - this.handleToggle(); + this.handleToggle(e); break; case 'Escape': this.handleClose(); @@ -165,7 +177,7 @@ export default class PrivacyDropdown extends React.PureComponent { render () { const { value, intl } = this.props; - const { open } = this.state; + const { open, placement } = this.state; const valueOption = this.options.find(item => item.value === value); @@ -185,7 +197,7 @@ export default class PrivacyDropdown extends React.PureComponent { />
- + Date: Wed, 11 Apr 2018 21:40:38 +0200 Subject: [PATCH 113/381] update gem, test pam authentication (#7028) * update gem, test pam authentication * add description for test parameters * fix inclusion of optional group --- .env.test | 4 ++ .travis.yml | 3 +- Gemfile | 2 +- Gemfile.lock | 8 ++-- config/environments/test.rb | 11 +++++ spec/controllers/auth/sessions_controller_spec.rb | 51 +++++++++++++++++++++++ 6 files changed, 73 insertions(+), 6 deletions(-) diff --git a/.env.test b/.env.test index b57f52e..7da76f8 100644 --- a/.env.test +++ b/.env.test @@ -1,3 +1,7 @@ # Federation LOCAL_DOMAIN=cb6e6126.ngrok.io LOCAL_HTTPS=true +# test pam authentication +PAM_ENABLED=true +PAM_DEFAULT_SERVICE=pam_test +PAM_CONTROLLED_SERVICE=pam_test_controlled diff --git a/.travis.yml b/.travis.yml index 989237a..2addd9b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -23,6 +23,7 @@ env: - RAILS_ENV=test - NOKOGIRI_USE_SYSTEM_LIBRARIES=true - PARALLEL_TEST_PROCESSORS=2 + - ALLOW_NOPAM=true addons: postgresql: 9.4 @@ -47,7 +48,7 @@ services: install: - nvm install - - bundle install --path=vendor/bundle --without development production --retry=3 --jobs=16 + - bundle install --path=vendor/bundle --with pam_authentication --without development production --retry=3 --jobs=16 - yarn install before_script: diff --git a/Gemfile b/Gemfile index 4a5a166..7f9591d 100644 --- a/Gemfile +++ b/Gemfile @@ -33,7 +33,7 @@ gem 'devise', '~> 4.4' gem 'devise-two-factor', '~> 3.0' group :pam_authentication, optional: true do - gem 'devise_pam_authenticatable2', '~> 9.0' + gem 'devise_pam_authenticatable2', '~> 9.1' end gem 'net-ldap', '~> 0.10' diff --git a/Gemfile.lock b/Gemfile.lock index 0f5a1fb..5322b87 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -146,9 +146,9 @@ GEM devise (~> 4.0) railties (< 5.2) rotp (~> 2.0) - devise_pam_authenticatable2 (9.0.0) + devise_pam_authenticatable2 (9.1.0) devise (>= 4.0.0) - rpam2 (~> 3.0) + rpam2 (~> 4.0) diff-lcs (1.3) docile (1.1.5) domain_name (0.5.20170404) @@ -464,7 +464,7 @@ GEM actionpack (>= 4.2.0, < 5.3) railties (>= 4.2.0, < 5.3) rotp (2.1.2) - rpam2 (3.1.0) + rpam2 (4.0.2) rqrcode (0.10.1) chunky_png (~> 1.0) rspec-core (3.7.0) @@ -639,7 +639,7 @@ DEPENDENCIES climate_control (~> 0.2) devise (~> 4.4) devise-two-factor (~> 3.0) - devise_pam_authenticatable2 (~> 9.0) + devise_pam_authenticatable2 (~> 9.1) doorkeeper (~> 4.2) dotenv-rails (~> 2.2) fabrication (~> 2.18) diff --git a/config/environments/test.rb b/config/environments/test.rb index 7d77a17..122634d 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -59,3 +59,14 @@ Rails.application.configure do end Paperclip::Attachment.default_options[:path] = "#{Rails.root}/spec/test_files/:class/:id_partition/:style.:extension" + +# set fake_data for pam, don't do real calls, just use fake data +if ENV['PAM_ENABLED'] == 'true' + Rpam2.fake_data = + { + usernames: Set['pam_user1', 'pam_user2'], + servicenames: Set['pam_test', 'pam_test_controlled'], + password: '123456', + env: { email: 'pam@example.com' } + } +end diff --git a/spec/controllers/auth/sessions_controller_spec.rb b/spec/controllers/auth/sessions_controller_spec.rb index 88f0a47..d5fed17 100644 --- a/spec/controllers/auth/sessions_controller_spec.rb +++ b/spec/controllers/auth/sessions_controller_spec.rb @@ -48,6 +48,57 @@ RSpec.describe Auth::SessionsController, type: :controller do request.env['devise.mapping'] = Devise.mappings[:user] end + context 'using PAM authentication' do + context 'using a valid password' do + before do + post :create, params: { user: { email: "pam_user1", password: '123456' } } + end + + it 'redirects to home' do + expect(response).to redirect_to(root_path) + end + + it 'logs the user in' do + expect(controller.current_user).to be_instance_of(User) + end + end + + context 'using an invalid password' do + before do + post :create, params: { user: { email: "pam_user1", password: 'WRONGPW' } } + end + + it 'shows a login error' do + expect(flash[:alert]).to match I18n.t('devise.failure.invalid', authentication_keys: 'Email') + end + + it "doesn't log the user in" do + expect(controller.current_user).to be_nil + end + end + + context 'using a valid email and existing user' do + let(:user) do + account = Fabricate.build(:account, username: 'pam_user1') + account.save!(validate: false) + user = Fabricate(:user, email: 'pam@example.com', password: nil, account: account) + user + end + + before do + post :create, params: { user: { email: user.email, password: '123456' } } + end + + it 'redirects to home' do + expect(response).to redirect_to(root_path) + end + + it 'logs the user in' do + expect(controller.current_user).to eq user + end + end + end + context 'using password authentication' do let(:user) { Fabricate(:user, email: 'foo@bar.com', password: 'abcdefgh') } From 50529cbceb84e611bca497624a7a4c38113e5135 Mon Sep 17 00:00:00 2001 From: Yamagishi Kazutoshi Date: Thu, 12 Apr 2018 21:45:17 +0900 Subject: [PATCH 114/381] Upgrade Rails to version 5.2.0 (#5898) --- Gemfile | 60 ++-- Gemfile.lock | 374 +++++++++++---------- .../api/web/push_subscriptions_controller.rb | 25 +- .../settings/follower_domains_controller.rb | 2 +- app/models/account.rb | 4 +- app/models/notification.rb | 2 - app/models/status.rb | 2 +- bin/bundle | 2 +- bin/setup | 3 +- bin/update | 6 +- bin/webpack | 14 +- bin/webpack-dev-server | 14 +- bin/yarn | 11 + config/application.rb | 11 +- config/boot.rb | 2 +- config/deploy.rb | 2 +- config/environments/development.rb | 3 +- config/environments/production.rb | 4 + config/environments/test.rb | 2 +- config/initializers/content_security_policy.rb | 20 ++ config/initializers/cors.rb | 26 ++ db/schema.rb | 3 +- 22 files changed, 330 insertions(+), 262 deletions(-) create mode 100755 bin/yarn create mode 100644 config/initializers/content_security_policy.rb create mode 100644 config/initializers/cors.rb diff --git a/Gemfile b/Gemfile index 7f9591d..efafe29 100644 --- a/Gemfile +++ b/Gemfile @@ -5,12 +5,12 @@ ruby '>= 2.3.0', '< 2.6.0' gem 'pkg-config', '~> 1.2' -gem 'puma', '~> 3.10' -gem 'rails', '~> 5.1.4' +gem 'puma', '~> 3.11' +gem 'rails', '~> 5.2.0' gem 'hamlit-rails', '~> 0.2' -gem 'pg', '~> 0.20' -gem 'pghero', '~> 1.7' +gem 'pg', '~> 1.0' +gem 'pghero', '~> 2.1' gem 'dotenv-rails', '~> 2.2' gem 'aws-sdk-s3', '~> 1.8', require: false @@ -23,14 +23,14 @@ gem 'streamio-ffmpeg', '~> 3.0' gem 'active_model_serializers', '~> 0.10' gem 'addressable', '~> 2.5' -gem 'bootsnap' +gem 'bootsnap', '~> 1.3' gem 'browser' gem 'charlock_holmes', '~> 0.7.6' gem 'iso-639' gem 'chewy', '~> 5.0' gem 'cld3', '~> 3.2.0' gem 'devise', '~> 4.4' -gem 'devise-two-factor', '~> 3.0' +gem 'devise-two-factor', '~> 3.0', git: 'https://github.com/ykzts/devise-two-factor.git', branch: 'rails-5.2' group :pam_authentication, optional: true do gem 'devise_pam_authenticatable2', '~> 9.1' @@ -41,7 +41,7 @@ gem 'omniauth-cas', '~> 1.1' gem 'omniauth-saml', '~> 1.10' gem 'omniauth', '~> 1.2' -gem 'doorkeeper', '~> 4.2' +gem 'doorkeeper', '~> 4.3' gem 'fast_blank', '~> 1.0' gem 'fastimage' gem 'goldfinger', '~> 2.1' @@ -50,50 +50,50 @@ gem 'redis-namespace', '~> 1.5' gem 'htmlentities', '~> 4.3' gem 'http', '~> 3.0' gem 'http_accept_language', '~> 2.1' -gem 'httplog', '~> 0.99' +gem 'httplog', '~> 1.0' gem 'idn-ruby', require: 'idn' gem 'kaminari', '~> 1.1' gem 'link_header', '~> 0.0' gem 'mime-types', '~> 3.1' gem 'nokogiri', '~> 1.8' gem 'nsa', '~> 0.2' -gem 'oj', '~> 3.3' +gem 'oj', '~> 3.4' gem 'ostatus2', '~> 2.0' gem 'ox', '~> 2.8' gem 'pundit', '~> 1.1' gem 'premailer-rails' -gem 'rack-attack', '~> 5.0' -gem 'rack-cors', '~> 0.4', require: 'rack/cors' +gem 'rack-attack', '~> 5.2' +gem 'rack-cors', '~> 1.0', require: 'rack/cors' gem 'rack-timeout', '~> 0.4' -gem 'rails-i18n', '~> 5.0' +gem 'rails-i18n', '~> 5.1' gem 'rails-settings-cached', '~> 0.6' -gem 'redis', '~> 3.3', require: ['redis', 'redis/connection/hiredis'] +gem 'redis', '~> 4.0', require: ['redis', 'redis/connection/hiredis'] gem 'mario-redis-lock', '~> 1.2', require: 'redis_lock' gem 'rqrcode', '~> 0.10' gem 'ruby-oembed', '~> 0.12', require: 'oembed' gem 'ruby-progressbar', '~> 1.4' -gem 'sanitize', '~> 4.6.4' -gem 'sidekiq', '~> 5.0' -gem 'sidekiq-scheduler', '~> 2.1' +gem 'sanitize', '~> 4.6' +gem 'sidekiq', '~> 5.1' +gem 'sidekiq-scheduler', '~> 2.2' gem 'sidekiq-unique-jobs', '~> 5.0' gem 'sidekiq-bulk', '~>0.1.1' gem 'simple-navigation', '~> 4.0' -gem 'simple_form', '~> 3.4' +gem 'simple_form', '~> 4.0' gem 'sprockets-rails', '~> 3.2', require: 'sprockets/railtie' gem 'stoplight', '~> 2.1.3' -gem 'strong_migrations' +gem 'strong_migrations', '~> 0.2' gem 'tty-command' gem 'tty-prompt' gem 'twitter-text', '~> 1.14' -gem 'tzinfo-data', '~> 1.2017' -gem 'webpacker', '~> 3.0' +gem 'tzinfo-data', '~> 1.2018' +gem 'webpacker', '~> 3.4' gem 'webpush' -gem 'json-ld-preloaded', '~> 2.2.1' -gem 'rdf-normalize', '~> 0.3.1' +gem 'json-ld-preloaded', '~> 2.2' +gem 'rdf-normalize', '~> 0.3' group :development, :test do - gem 'fabrication', '~> 2.18' + gem 'fabrication', '~> 2.20' gem 'fuubar', '~> 2.2' gem 'i18n-tasks', '~> 0.9', require: false gem 'pry-rails', '~> 0.3' @@ -105,15 +105,15 @@ group :production, :test do end group :test do - gem 'capybara', '~> 2.15' + gem 'capybara', '~> 2.18' gem 'climate_control', '~> 0.2' - gem 'faker', '~> 1.7' + gem 'faker', '~> 1.8' gem 'microformats', '~> 4.0' gem 'rails-controller-testing', '~> 1.0' gem 'rspec-sidekiq', '~> 3.0' gem 'simplecov', '~> 0.14', require: false - gem 'webmock', '~> 3.0' - gem 'parallel_tests', '~> 2.17' + gem 'webmock', '~> 3.3' + gem 'parallel_tests', '~> 2.21' end group :development do @@ -121,12 +121,12 @@ group :development do gem 'annotate', '~> 2.7' gem 'better_errors', '~> 2.4' gem 'binding_of_caller', '~> 0.7' - gem 'bullet', '~> 5.5' + gem 'bullet', '~> 5.7' gem 'letter_opener', '~> 1.4' gem 'letter_opener_web', '~> 1.3' gem 'memory_profiler' gem 'rubocop', require: false - gem 'brakeman', '~> 4.0', require: false + gem 'brakeman', '~> 4.2', require: false gem 'bundler-audit', '~> 0.6', require: false gem 'scss_lint', '~> 0.55', require: false @@ -137,6 +137,6 @@ group :development do end group :production do - gem 'lograge', '~> 0.7' + gem 'lograge', '~> 0.9' gem 'redis-rails', '~> 5.0' end diff --git a/Gemfile.lock b/Gemfile.lock index 5322b87..e799533 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,25 +1,37 @@ +GIT + remote: https://github.com/ykzts/devise-two-factor.git + revision: f60492b29c174d4c959ac02406392f8eb9c4d374 + branch: rails-5.2 + specs: + devise-two-factor (3.0.2) + activesupport (< 5.3) + attr_encrypted (>= 1.3, < 4, != 2) + devise (~> 4.0) + railties (< 5.3) + rotp (~> 2.0) + GEM remote: https://rubygems.org/ specs: - actioncable (5.1.4) - actionpack (= 5.1.4) + actioncable (5.2.0) + actionpack (= 5.2.0) nio4r (~> 2.0) - websocket-driver (~> 0.6.1) - actionmailer (5.1.4) - actionpack (= 5.1.4) - actionview (= 5.1.4) - activejob (= 5.1.4) + websocket-driver (>= 0.6.1) + actionmailer (5.2.0) + actionpack (= 5.2.0) + actionview (= 5.2.0) + activejob (= 5.2.0) mail (~> 2.5, >= 2.5.4) rails-dom-testing (~> 2.0) - actionpack (5.1.4) - actionview (= 5.1.4) - activesupport (= 5.1.4) + actionpack (5.2.0) + actionview (= 5.2.0) + activesupport (= 5.2.0) rack (~> 2.0) rack-test (>= 0.6.3) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.0.2) - actionview (5.1.4) - activesupport (= 5.1.4) + actionview (5.2.0) + activesupport (= 5.2.0) builder (~> 3.1) erubi (~> 1.4) rails-dom-testing (~> 2.0) @@ -30,18 +42,22 @@ GEM case_transform (>= 0.2) jsonapi-renderer (>= 0.1.1.beta1, < 0.3) active_record_query_trace (1.5.4) - activejob (5.1.4) - activesupport (= 5.1.4) + activejob (5.2.0) + activesupport (= 5.2.0) globalid (>= 0.3.6) - activemodel (5.1.4) - activesupport (= 5.1.4) - activerecord (5.1.4) - activemodel (= 5.1.4) - activesupport (= 5.1.4) - arel (~> 8.0) - activesupport (5.1.4) + activemodel (5.2.0) + activesupport (= 5.2.0) + activerecord (5.2.0) + activemodel (= 5.2.0) + activesupport (= 5.2.0) + arel (>= 9.0) + activestorage (5.2.0) + actionpack (= 5.2.0) + activerecord (= 5.2.0) + marcel (~> 0.3.1) + activesupport (5.2.0) concurrent-ruby (~> 1.0, >= 1.0.2) - i18n (~> 0.7) + i18n (>= 0.7, < 2) minitest (~> 5.1) tzinfo (~> 1.1) addressable (2.5.2) @@ -51,9 +67,9 @@ GEM annotate (2.7.2) activerecord (>= 3.2, < 6.0) rake (>= 10.4, < 13.0) - arel (8.0.0) - ast (2.3.0) - attr_encrypted (3.0.3) + arel (9.0.0) + ast (2.4.0) + attr_encrypted (3.1.0) encryptor (~> 3.0.0) av (0.9.0) cocaine (~> 0.5.3) @@ -77,18 +93,18 @@ GEM rack (>= 0.9.0) binding_of_caller (0.8.0) debug_inspector (>= 0.0.1) - bootsnap (1.1.5) + bootsnap (1.3.0) msgpack (~> 1.0) - brakeman (4.0.1) + brakeman (4.2.1) browser (2.5.2) builder (3.2.3) - bullet (5.6.1) + bullet (5.7.5) activesupport (>= 3.0.0) - uniform_notifier (~> 1.10.0) + uniform_notifier (~> 1.11.0) bundler-audit (0.6.0) bundler (~> 1.2) thor (~> 0.18) - capistrano (3.10.0) + capistrano (3.10.1) airbrussh (>= 1.0.0) i18n rake (>= 10.0.0) @@ -104,13 +120,13 @@ GEM sshkit (~> 1.3) capistrano-yarn (2.0.2) capistrano (~> 3.0) - capybara (2.16.1) + capybara (2.18.0) addressable mini_mime (>= 0.1.3) nokogiri (>= 1.3.3) rack (>= 1.0.0) rack-test (>= 0.5.4) - xpath (~> 2.0) + xpath (>= 2.0, < 4.0) case_transform (0.2) activesupport charlock_holmes (0.7.6) @@ -118,7 +134,7 @@ GEM activesupport (>= 4.0) elasticsearch (>= 2.0.0) elasticsearch-dsl - chunky_png (1.3.8) + chunky_png (1.3.10) cld3 (3.2.2) ffi (>= 1.1.0, < 1.10.0) climate_control (0.2.0) @@ -130,22 +146,16 @@ GEM connection_pool (2.2.1) crack (0.4.3) safe_yaml (~> 1.0.0) - crass (1.0.3) + crass (1.0.4) css_parser (1.6.0) addressable debug_inspector (0.0.3) - devise (4.4.0) + devise (4.4.3) bcrypt (~> 3.0) orm_adapter (~> 0.1) - railties (>= 4.1.0, < 5.2) + railties (>= 4.1.0, < 6.0) responders warden (~> 1.2.3) - devise-two-factor (3.0.2) - activesupport (< 5.2) - attr_encrypted (>= 1.3, < 4, != 2) - devise (~> 4.0) - railties (< 5.2) - rotp (~> 2.0) devise_pam_authenticatable2 (9.1.0) devise (>= 4.0.0) rpam2 (~> 4.0) @@ -153,14 +163,13 @@ GEM docile (1.1.5) domain_name (0.5.20170404) unf (>= 0.0.5, < 1.0.0) - doorkeeper (4.2.6) + doorkeeper (4.3.2) railties (>= 4.2) - dotenv (2.2.1) - dotenv-rails (2.2.1) - dotenv (= 2.2.1) - railties (>= 3.2, < 5.2) - easy_translate (0.5.0) - json + dotenv (2.2.2) + dotenv-rails (2.2.2) + dotenv (= 2.2.2) + railties (>= 3.2, < 6.0) + easy_translate (0.5.1) thread thread_safe elasticsearch (6.0.1) @@ -174,18 +183,18 @@ GEM multi_json encryptor (3.0.0) equatable (0.5.0) - erubi (1.7.0) - et-orbi (1.0.8) + erubi (1.7.1) + et-orbi (1.0.9) tzinfo - excon (0.59.0) - fabrication (2.18.0) - faker (1.8.4) - i18n (~> 0.5) + excon (0.60.0) + fabrication (2.20.1) + faker (1.8.7) + i18n (>= 0.7) faraday (0.14.0) multipart-post (>= 1.2, < 3) fast_blank (1.0.0) fastimage (2.1.1) - ffi (1.9.18) + ffi (1.9.21) fog-core (1.45.0) builder excon (~> 0.58) @@ -195,12 +204,12 @@ GEM multi_json (~> 1.10) fog-local (0.4.0) fog-core (~> 1.27) - fog-openstack (0.1.22) - fog-core (>= 1.40) + fog-openstack (0.1.23) + fog-core (~> 1.40) fog-json (>= 1.0) ipaddress (>= 0.8) formatador (0.2.5) - fuubar (2.2.0) + fuubar (2.3.1) rspec-core (~> 3.0) ruby-progressbar (~> 1.4) globalid (0.4.1) @@ -210,7 +219,7 @@ GEM http (~> 3.0) nokogiri (~> 1.8) oj (~> 3.0) - hamlit (2.8.5) + hamlit (2.8.8) temple (>= 0.8.0) thor tilt @@ -238,33 +247,33 @@ GEM http-form_data (2.0.0) http_accept_language (2.1.1) http_parser.rb (0.6.0) - httplog (0.99.7) - colorize - rack - i18n (0.9.5) + httplog (1.0.2) + colorize (~> 0.8) + rack (>= 1.0) + i18n (1.0.0) concurrent-ruby (~> 1.0) - i18n-tasks (0.9.19) + i18n-tasks (0.9.21) activesupport (>= 4.0.2) ast (>= 2.1.0) - easy_translate (>= 0.5.0) + easy_translate (>= 0.5.1) erubi highline (>= 1.7.3) i18n parser (>= 2.2.3.0) - rainbow (~> 2.2) + rainbow (>= 2.2.2, < 4.0) terminal-table (>= 1.5.1) idn-ruby (0.1.0) ipaddress (0.8.3) iso-639 (0.2.8) jmespath (1.3.1) json (2.1.0) - json-ld (2.1.7) + json-ld (2.2.1) + multi_json (~> 1.12) + rdf (>= 2.2.8, < 4.0) + json-ld-preloaded (2.2.3) + json-ld (>= 2.2, < 4.0) multi_json (~> 1.12) - rdf (~> 2.2, >= 2.2.8) - json-ld-preloaded (2.2.2) - json-ld (~> 2.1, >= 2.1.5) - multi_json (~> 1.11) - rdf (~> 2.2) + rdf (>= 2.2, < 4.0) jsonapi-renderer (0.2.0) jwt (2.1.0) kaminari (1.1.1) @@ -281,25 +290,27 @@ GEM kaminari-core (1.1.1) launchy (2.4.3) addressable (~> 2.3) - letter_opener (1.4.1) + letter_opener (1.6.0) launchy (~> 2.2) - letter_opener_web (1.3.1) + letter_opener_web (1.3.4) actionmailer (>= 3.2) letter_opener (~> 1.0) railties (>= 3.2) link_header (0.0.8) - lograge (0.7.1) - actionpack (>= 4, < 5.2) - activesupport (>= 4, < 5.2) - railties (>= 4, < 5.2) + lograge (0.9.0) + actionpack (>= 4) + activesupport (>= 4) + railties (>= 4) request_store (~> 1.0) - loofah (2.2.1) + loofah (2.2.2) crass (~> 1.0.2) nokogiri (>= 1.5.9) mail (2.7.0) mini_mime (>= 0.1.1) - mario-redis-lock (1.2.0) - redis (~> 3, >= 3.0.5) + marcel (0.3.2) + mimemagic (~> 0.3.2) + mario-redis-lock (1.2.1) + redis (>= 3.0.5) memory_profiler (0.9.10) method_source (0.9.0) microformats (4.0.7) @@ -312,15 +323,15 @@ GEM mini_mime (1.0.0) mini_portile2 (2.3.0) minitest (5.11.3) - msgpack (1.1.0) - multi_json (1.12.2) + msgpack (1.2.4) + multi_json (1.13.1) multipart-post (2.0.0) necromancer (0.4.0) net-ldap (0.16.1) net-scp (1.2.1) net-ssh (>= 2.6.5) net-ssh (4.2.0) - nio4r (2.1.0) + nio4r (2.3.0) nokogiri (1.8.2) mini_portile2 (~> 2.3.0) nokogumbo (1.5.0) @@ -330,7 +341,7 @@ GEM concurrent-ruby (~> 1.0.0) sidekiq (>= 3.5.0) statsd-ruby (~> 1.2.0) - oj (3.3.10) + oj (3.4.0) omniauth (1.8.1) hashie (>= 3.4.6, < 3.6.0) rack (>= 1.6.2, < 3) @@ -356,24 +367,24 @@ GEM paperclip-av-transcoder (0.6.4) av (~> 0.9.0) paperclip (>= 2.5.2) - parallel (1.12.0) - parallel_tests (2.19.0) + parallel (1.12.1) + parallel_tests (2.21.1) parallel - parser (2.4.0.2) - ast (~> 2.3) + parser (2.5.1.0) + ast (~> 2.4.0) pastel (0.7.2) equatable (~> 0.5.0) tty-color (~> 0.4.0) - pg (0.21.0) - pghero (1.7.0) + pg (1.0.0) + pghero (2.1.0) activerecord - pkg-config (1.2.8) + pkg-config (1.2.9) powerpack (0.1.1) premailer (1.11.1) addressable css_parser (>= 1.6.0) htmlentities (>= 4.0.0) - premailer-rails (1.10.1) + premailer-rails (1.10.2) actionmailer (>= 3, < 6) premailer (~> 1.7, >= 1.7.9) private_address_check (0.4.1) @@ -382,32 +393,33 @@ GEM method_source (~> 0.9.0) pry-rails (0.3.6) pry (>= 0.10.4) - public_suffix (3.0.1) - puma (3.11.0) + public_suffix (3.0.2) + puma (3.11.3) pundit (1.1.0) activesupport (>= 3.0.0) - rack (2.0.3) - rack-attack (5.0.1) + rack (2.0.4) + rack-attack (5.2.0) rack - rack-cors (0.4.1) - rack-protection (2.0.0) + rack-cors (1.0.2) + rack-protection (2.0.1) rack - rack-proxy (0.6.2) + rack-proxy (0.6.4) rack - rack-test (0.8.2) + rack-test (1.0.0) rack (>= 1.0, < 3) rack-timeout (0.4.2) - rails (5.1.4) - actioncable (= 5.1.4) - actionmailer (= 5.1.4) - actionpack (= 5.1.4) - actionview (= 5.1.4) - activejob (= 5.1.4) - activemodel (= 5.1.4) - activerecord (= 5.1.4) - activesupport (= 5.1.4) + rails (5.2.0) + actioncable (= 5.2.0) + actionmailer (= 5.2.0) + actionpack (= 5.2.0) + actionview (= 5.2.0) + activejob (= 5.2.0) + activemodel (= 5.2.0) + activerecord (= 5.2.0) + activestorage (= 5.2.0) + activesupport (= 5.2.0) bundler (>= 1.3.0) - railties (= 5.1.4) + railties (= 5.2.0) sprockets-rails (>= 2.0.0) rails-controller-testing (1.0.2) actionpack (~> 5.x, >= 5.0.1) @@ -416,31 +428,30 @@ GEM rails-dom-testing (2.0.3) activesupport (>= 4.2.0) nokogiri (>= 1.6) - rails-html-sanitizer (1.0.3) - loofah (~> 2.0) - rails-i18n (5.0.4) - i18n (~> 0.7) - railties (~> 5.0) + rails-html-sanitizer (1.0.4) + loofah (~> 2.2, >= 2.2.2) + rails-i18n (5.1.1) + i18n (>= 0.7, < 2) + railties (>= 5.0, < 6) rails-settings-cached (0.6.6) rails (>= 4.2.0) - railties (5.1.4) - actionpack (= 5.1.4) - activesupport (= 5.1.4) + railties (5.2.0) + actionpack (= 5.2.0) + activesupport (= 5.2.0) method_source rake (>= 0.8.7) thor (>= 0.18.1, < 2.0) - rainbow (2.2.2) - rake - rake (12.3.0) + rainbow (3.0.0) + rake (12.3.1) rb-fsevent (0.10.2) rb-inotify (0.9.10) ffi (>= 0.5.0, < 2) - rdf (2.2.12) + rdf (3.0.1) hamster (~> 3.0) link_header (~> 0.0, >= 0.0.8) - rdf-normalize (0.3.2) - rdf (~> 2.0) - redis (3.3.5) + rdf-normalize (0.3.3) + rdf (>= 2.2, < 4.0) + redis (4.0.1) redis-actionpack (5.0.2) actionpack (>= 4.0, < 6) redis-rack (>= 1, < 3) @@ -450,7 +461,7 @@ GEM redis-store (>= 1.3, < 2) redis-namespace (1.6.0) redis (>= 3.0.4) - redis-rack (2.0.3) + redis-rack (2.0.4) rack (>= 1.5, < 3) redis-store (>= 1.2, < 2) redis-rails (5.0.2) @@ -459,7 +470,8 @@ GEM redis-store (>= 1.2, < 2) redis-store (1.4.1) redis (>= 2.2, < 5) - request_store (1.3.2) + request_store (1.4.0) + rack (>= 1.4) responders (2.4.0) actionpack (>= 4.2.0, < 5.3) railties (>= 4.2.0, < 5.3) @@ -467,7 +479,7 @@ GEM rpam2 (4.0.2) rqrcode (0.10.1) chunky_png (~> 1.0) - rspec-core (3.7.0) + rspec-core (3.7.1) rspec-support (~> 3.7.0) rspec-expectations (3.7.0) diff-lcs (>= 1.2.0, < 2.0) @@ -486,12 +498,12 @@ GEM rspec-sidekiq (3.0.3) rspec-core (~> 3.0, >= 3.0.0) sidekiq (>= 2.4.0) - rspec-support (3.7.0) - rubocop (0.51.0) + rspec-support (3.7.1) + rubocop (0.52.1) parallel (~> 1.10) - parser (>= 2.3.3.1, < 3.0) + parser (>= 2.4.0.2, < 3.0) powerpack (~> 0.1) - rainbow (>= 2.2.2, < 3.0) + rainbow (>= 2.2.2, < 4.0) ruby-progressbar (~> 1.7) unicode-display_width (~> 1.0, >= 1.0.1) ruby-oembed (0.12.0) @@ -505,7 +517,7 @@ GEM crass (~> 1.0.2) nokogiri (>= 1.4.4) nokogumbo (~> 1.4) - sass (3.5.3) + sass (3.5.5) sass-listen (~> 4.0.0) sass-listen (4.0.0) rb-fsevent (~> 0.9, >= 0.9.4) @@ -513,15 +525,15 @@ GEM scss_lint (0.56.0) rake (>= 0.9, < 13) sass (~> 3.5.3) - sidekiq (5.0.5) + sidekiq (5.1.3) concurrent-ruby (~> 1.0) connection_pool (~> 2.2, >= 2.2.0) rack-protection (>= 1.5.0) - redis (>= 3.3.4, < 5) + redis (>= 3.3.5, < 5) sidekiq-bulk (0.1.1) activesupport sidekiq - sidekiq-scheduler (2.1.10) + sidekiq-scheduler (2.2.1) redis (>= 3, < 5) rufus-scheduler (~> 3.2) sidekiq (>= 3) @@ -531,9 +543,9 @@ GEM thor (~> 0) simple-navigation (4.0.5) activesupport (>= 2.3.2) - simple_form (3.5.0) - actionpack (> 4, < 5.2) - activemodel (> 4, < 5.2) + simple_form (4.0.0) + actionpack (> 4) + activemodel (> 4) simplecov (0.15.1) docile (~> 1.1.0) json (>= 1.8, < 3) @@ -546,14 +558,14 @@ GEM actionpack (>= 4.0) activesupport (>= 4.0) sprockets (>= 3.0.0) - sshkit (1.15.1) + sshkit (1.16.0) net-scp (>= 1.1.2) net-ssh (>= 2.8.0) statsd-ruby (1.2.1) stoplight (2.1.3) streamio-ffmpeg (3.0.2) multi_json (~> 1.8) - strong_migrations (0.1.9) + strong_migrations (0.2.2) activerecord (>= 3.2.0) temple (0.8.0) terminal-table (1.8.0) @@ -585,32 +597,32 @@ GEM unf (~> 0.1.0) tzinfo (1.2.5) thread_safe (~> 0.1) - tzinfo-data (1.2017.3) + tzinfo-data (1.2018.4) tzinfo (>= 1.0.0) unf (0.1.4) unf_ext - unf_ext (0.0.7.4) + unf_ext (0.0.7.5) unicode-display_width (1.3.0) - uniform_notifier (1.10.0) + uniform_notifier (1.11.0) warden (1.2.7) rack (>= 1.0) - webmock (3.1.1) + webmock (3.3.0) addressable (>= 2.3.6) crack (>= 0.3.2) hashdiff - webpacker (3.0.2) + webpacker (3.4.3) activesupport (>= 4.2) rack-proxy (>= 0.6.1) railties (>= 4.2) webpush (0.3.3) hkdf (~> 0.2) jwt (~> 2.0) - websocket-driver (0.6.5) + websocket-driver (0.7.0) websocket-extensions (>= 0.1.0) websocket-extensions (0.1.3) wisper (2.0.0) - xpath (2.1.0) - nokogiri (~> 1.3) + xpath (3.0.0) + nokogiri (~> 1.8) PLATFORMS ruby @@ -623,27 +635,27 @@ DEPENDENCIES aws-sdk-s3 (~> 1.8) better_errors (~> 2.4) binding_of_caller (~> 0.7) - bootsnap - brakeman (~> 4.0) + bootsnap (~> 1.3) + brakeman (~> 4.2) browser - bullet (~> 5.5) + bullet (~> 5.7) bundler-audit (~> 0.6) capistrano (~> 3.10) capistrano-rails (~> 1.3) capistrano-rbenv (~> 2.1) capistrano-yarn (~> 2.0) - capybara (~> 2.15) + capybara (~> 2.18) charlock_holmes (~> 0.7.6) chewy (~> 5.0) cld3 (~> 3.2.0) climate_control (~> 0.2) devise (~> 4.4) - devise-two-factor (~> 3.0) + devise-two-factor (~> 3.0)! devise_pam_authenticatable2 (~> 9.1) - doorkeeper (~> 4.2) + doorkeeper (~> 4.3) dotenv-rails (~> 2.2) - fabrication (~> 2.18) - faker (~> 1.7) + fabrication (~> 2.20) + faker (~> 1.8) fast_blank (~> 1.0) fastimage fog-core (~> 1.45) @@ -656,16 +668,16 @@ DEPENDENCIES htmlentities (~> 4.3) http (~> 3.0) http_accept_language (~> 2.1) - httplog (~> 0.99) + httplog (~> 1.0) i18n-tasks (~> 0.9) idn-ruby iso-639 - json-ld-preloaded (~> 2.2.1) + json-ld-preloaded (~> 2.2) kaminari (~> 1.1) letter_opener (~> 1.4) letter_opener_web (~> 1.3) link_header (~> 0.0) - lograge (~> 0.7) + lograge (~> 0.9) mario-redis-lock (~> 1.2) memory_profiler microformats (~> 4.0) @@ -673,7 +685,7 @@ DEPENDENCIES net-ldap (~> 0.10) nokogiri (~> 1.8) nsa (~> 0.2) - oj (~> 3.3) + oj (~> 3.4) omniauth (~> 1.2) omniauth-cas (~> 1.1) omniauth-saml (~> 1.10) @@ -681,24 +693,24 @@ DEPENDENCIES ox (~> 2.8) paperclip (~> 6.0) paperclip-av-transcoder (~> 0.6) - parallel_tests (~> 2.17) - pg (~> 0.20) - pghero (~> 1.7) + parallel_tests (~> 2.21) + pg (~> 1.0) + pghero (~> 2.1) pkg-config (~> 1.2) premailer-rails private_address_check (~> 0.4.1) pry-rails (~> 0.3) - puma (~> 3.10) + puma (~> 3.11) pundit (~> 1.1) - rack-attack (~> 5.0) - rack-cors (~> 0.4) + rack-attack (~> 5.2) + rack-cors (~> 1.0) rack-timeout (~> 0.4) - rails (~> 5.1.4) + rails (~> 5.2.0) rails-controller-testing (~> 1.0) - rails-i18n (~> 5.0) + rails-i18n (~> 5.1) rails-settings-cached (~> 0.6) - rdf-normalize (~> 0.3.1) - redis (~> 3.3) + rdf-normalize (~> 0.3) + redis (~> 4.0) redis-namespace (~> 1.5) redis-rails (~> 5.0) rqrcode (~> 0.10) @@ -707,25 +719,25 @@ DEPENDENCIES rubocop ruby-oembed (~> 0.12) ruby-progressbar (~> 1.4) - sanitize (~> 4.6.4) + sanitize (~> 4.6) scss_lint (~> 0.55) - sidekiq (~> 5.0) + sidekiq (~> 5.1) sidekiq-bulk (~> 0.1.1) - sidekiq-scheduler (~> 2.1) + sidekiq-scheduler (~> 2.2) sidekiq-unique-jobs (~> 5.0) simple-navigation (~> 4.0) - simple_form (~> 3.4) + simple_form (~> 4.0) simplecov (~> 0.14) sprockets-rails (~> 3.2) stoplight (~> 2.1.3) streamio-ffmpeg (~> 3.0) - strong_migrations + strong_migrations (~> 0.2) tty-command tty-prompt twitter-text (~> 1.14) - tzinfo-data (~> 1.2017) - webmock (~> 3.0) - webpacker (~> 3.0) + tzinfo-data (~> 1.2018) + webmock (~> 3.3) + webpacker (~> 3.4) webpush RUBY VERSION diff --git a/app/controllers/api/web/push_subscriptions_controller.rb b/app/controllers/api/web/push_subscriptions_controller.rb index 68ccbd5..c611031 100644 --- a/app/controllers/api/web/push_subscriptions_controller.rb +++ b/app/controllers/api/web/push_subscriptions_controller.rb @@ -7,9 +7,6 @@ class Api::Web::PushSubscriptionsController < Api::BaseController protect_from_forgery with: :exception def create - params.require(:subscription).require(:endpoint) - params.require(:subscription).require(:keys).require([:auth, :p256dh]) - active_session = current_session unless active_session.web_push_subscription.nil? @@ -29,12 +26,12 @@ class Api::Web::PushSubscriptionsController < Api::BaseController }, } - data.deep_merge!(params[:data]) if params[:data] + data.deep_merge!(data_params) if params[:data] web_subscription = ::Web::PushSubscription.create!( - endpoint: params[:subscription][:endpoint], - key_p256dh: params[:subscription][:keys][:p256dh], - key_auth: params[:subscription][:keys][:auth], + endpoint: subscription_params[:endpoint], + key_p256dh: subscription_params[:keys][:p256dh], + key_auth: subscription_params[:keys][:auth], data: data ) @@ -44,12 +41,22 @@ class Api::Web::PushSubscriptionsController < Api::BaseController end def update - params.require([:id, :data]) + params.require([:id]) web_subscription = ::Web::PushSubscription.find(params[:id]) - web_subscription.update!(data: params[:data]) + web_subscription.update!(data: data_params) render json: web_subscription.as_payload end + + private + + def subscription_params + @subscription_params ||= params.require(:subscription).permit(:endpoint, keys: [:auth, :p256dh]) + end + + def data_params + @data_params ||= params.require(:data).permit(:alerts) + end end diff --git a/app/controllers/settings/follower_domains_controller.rb b/app/controllers/settings/follower_domains_controller.rb index 9968504..213d9e9 100644 --- a/app/controllers/settings/follower_domains_controller.rb +++ b/app/controllers/settings/follower_domains_controller.rb @@ -9,7 +9,7 @@ class Settings::FollowerDomainsController < ApplicationController def show @account = current_account - @domains = current_account.followers.reorder('MIN(follows.id) DESC').group('accounts.domain').select('accounts.domain, count(accounts.id) as accounts_from_domain').page(params[:page]).per(10) + @domains = current_account.followers.reorder(Arel.sql('MIN(follows.id) DESC')).group('accounts.domain').select('accounts.domain, count(accounts.id) as accounts_from_domain').page(params[:page]).per(10) end def update diff --git a/app/models/account.rb b/app/models/account.rb index 51304fc..5bdcfa9 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -244,11 +244,11 @@ class Account < ApplicationRecord end def domains - reorder(nil).pluck('distinct accounts.domain') + reorder(nil).pluck(Arel.sql('distinct accounts.domain')) end def inboxes - urls = reorder(nil).where(protocol: :activitypub).pluck("distinct coalesce(nullif(accounts.shared_inbox_url, ''), accounts.inbox_url)") + urls = reorder(nil).where(protocol: :activitypub).pluck(Arel.sql("distinct coalesce(nullif(accounts.shared_inbox_url, ''), accounts.inbox_url)")) DeliveryFailureTracker.filter(urls) end diff --git a/app/models/notification.rb b/app/models/notification.rb index be99640..0b0f01a 100644 --- a/app/models/notification.rb +++ b/app/models/notification.rb @@ -81,8 +81,6 @@ class Notification < ApplicationRecord end end - private - def activity_types_from_types(types) types.map { |type| TYPE_CLASS_MAP[type.to_sym] }.compact end diff --git a/app/models/status.rb b/app/models/status.rb index 60fa7a2..f924be4 100644 --- a/app/models/status.rb +++ b/app/models/status.rb @@ -322,7 +322,7 @@ class Status < ApplicationRecord self.in_reply_to_account_id = carried_over_reply_to_account_id self.conversation_id = thread.conversation_id if conversation_id.nil? elsif conversation_id.nil? - create_conversation + self.conversation = Conversation.new end end diff --git a/bin/bundle b/bin/bundle index 66e9889..f19acf5 100755 --- a/bin/bundle +++ b/bin/bundle @@ -1,3 +1,3 @@ #!/usr/bin/env ruby -ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) +ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) load Gem.bin_path('bundler', 'bundle') diff --git a/bin/setup b/bin/setup index 72b62a0..fc77b08 100755 --- a/bin/setup +++ b/bin/setup @@ -1,10 +1,9 @@ #!/usr/bin/env ruby -require 'pathname' require 'fileutils' include FileUtils # path to your application root. -APP_ROOT = Pathname.new File.expand_path('../../', __FILE__) +APP_ROOT = File.expand_path('..', __dir__) def system!(*args) system(*args) || abort("\n== Command #{args} failed ==") diff --git a/bin/update b/bin/update index a8e4462..6d73559 100755 --- a/bin/update +++ b/bin/update @@ -1,10 +1,9 @@ #!/usr/bin/env ruby -require 'pathname' require 'fileutils' include FileUtils # path to your application root. -APP_ROOT = Pathname.new File.expand_path('../../', __FILE__) +APP_ROOT = File.expand_path('..', __dir__) def system!(*args) system(*args) || abort("\n== Command #{args} failed ==") @@ -18,6 +17,9 @@ chdir APP_ROOT do system! 'gem install bundler --conservative' system('bundle check') || system!('bundle install') + # Install JavaScript dependencies if using Yarn + system('bin/yarn') + puts "\n== Updating database ==" system! 'bin/rails db:migrate' diff --git a/bin/webpack b/bin/webpack index 9d3800c..0869ad2 100755 --- a/bin/webpack +++ b/bin/webpack @@ -1,11 +1,7 @@ #!/usr/bin/env ruby -# frozen_string_literal: true -# -# This file was generated by Bundler. -# -# The application 'webpack' is installed as part of a gem, and -# this file is here to facilitate running it. -# + +ENV["RAILS_ENV"] ||= ENV["RACK_ENV"] || "development" +ENV["NODE_ENV"] ||= ENV["NODE_ENV"] || "development" require "pathname" ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", @@ -14,4 +10,6 @@ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", require "rubygems" require "bundler/setup" -load Gem.bin_path("webpacker", "webpack") +require "webpacker" +require "webpacker/webpack_runner" +Webpacker::WebpackRunner.run(ARGV) diff --git a/bin/webpack-dev-server b/bin/webpack-dev-server index cf70110..251f65e 100755 --- a/bin/webpack-dev-server +++ b/bin/webpack-dev-server @@ -1,11 +1,7 @@ #!/usr/bin/env ruby -# frozen_string_literal: true -# -# This file was generated by Bundler. -# -# The application 'webpack-dev-server' is installed as part of a gem, and -# this file is here to facilitate running it. -# + +ENV["RAILS_ENV"] ||= ENV["RACK_ENV"] || "development" +ENV["NODE_ENV"] ||= ENV["NODE_ENV"] || "development" require "pathname" ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", @@ -14,4 +10,6 @@ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", require "rubygems" require "bundler/setup" -load Gem.bin_path("webpacker", "webpack-dev-server") +require "webpacker" +require "webpacker/dev_server_runner" +Webpacker::DevServerRunner.run(ARGV) diff --git a/bin/yarn b/bin/yarn new file mode 100755 index 0000000..8c1535a --- /dev/null +++ b/bin/yarn @@ -0,0 +1,11 @@ +#!/usr/bin/env ruby +APP_ROOT = File.expand_path('..', __dir__) +Dir.chdir(APP_ROOT) do + begin + exec "yarnpkg #{ARGV.join(' ')}" unless Dir.exist?('node_modules') + rescue Errno::ENOENT + $stderr.puts "Yarn executable was not detected in the system." + $stderr.puts "Download Yarn at https://yarnpkg.com/en/docs/install" + exit 1 + end +end diff --git a/config/application.rb b/config/application.rb index 385bd47..e989e23 100644 --- a/config/application.rb +++ b/config/application.rb @@ -23,7 +23,7 @@ require_relative '../lib/mastodon/redis_config' module Mastodon class Application < Rails::Application # Initialize configuration defaults for originally generated Rails version. - config.load_defaults 5.1 + config.load_defaults 5.2 # Settings in config/environments/* take precedence over those specified here. # Application configuration should go into files in config/initializers @@ -85,15 +85,6 @@ module Mastodon config.active_job.queue_adapter = :sidekiq - config.middleware.insert_before 0, Rack::Cors do - allow do - origins '*' - resource '/@:username', headers: :any, methods: [:get], credentials: false - resource '/api/*', headers: :any, methods: [:post, :put, :delete, :get, :patch, :options], credentials: false, expose: ['Link', 'X-RateLimit-Reset', 'X-RateLimit-Limit', 'X-RateLimit-Remaining', 'X-Request-Id'] - resource '/oauth/token', headers: :any, methods: [:post], credentials: false - end - end - config.middleware.use Rack::Attack config.middleware.use Rack::Deflater diff --git a/config/boot.rb b/config/boot.rb index 703738b..0a3cd4e 100644 --- a/config/boot.rb +++ b/config/boot.rb @@ -1,7 +1,7 @@ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) require 'bundler/setup' # Set up gems listed in the Gemfile. -require 'bootsnap' +require 'bootsnap' # Speed up boot time by caching expensive operations. Bootsnap.setup( cache_dir: 'tmp/cache', diff --git a/config/deploy.rb b/config/deploy.rb index 3fd149f..180dd1c 100644 --- a/config/deploy.rb +++ b/config/deploy.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -lock '3.10.0' +lock '3.10.1' set :repo_url, ENV.fetch('REPO', 'https://github.com/tootsuite/mastodon.git') set :branch, ENV.fetch('BRANCH', 'master') diff --git a/config/environments/development.rb b/config/environments/development.rb index 285fea8..b6478f1 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -13,13 +13,14 @@ Rails.application.configure do config.consider_all_requests_local = true # Enable/disable caching. By default caching is disabled. + # Run rails dev:cache to toggle caching. if Rails.root.join('tmp/caching-dev.txt').exist? config.action_controller.perform_caching = true config.cache_store = :redis_store, ENV['REDIS_URL'], REDIS_CACHE_PARAMS config.public_file_server.headers = { - 'Cache-Control' => "public, max-age=#{2.days.seconds.to_i}", + 'Cache-Control' => "public, max-age=#{2.days.to_i}", } else config.action_controller.perform_caching = false diff --git a/config/environments/production.rb b/config/environments/production.rb index f372cd3..e742f66 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -15,6 +15,10 @@ Rails.application.configure do config.action_controller.perform_caching = true config.action_controller.asset_host = ENV['CDN_HOST'] if ENV.key?('CDN_HOST') + # Ensures that a master key has been made available in either ENV["RAILS_MASTER_KEY"] + # or in config/master.key. This key is used to decrypt credentials (and other encrypted files). + # config.require_master_key = true + # Disable serving static files from the `/public` folder by default since # Apache or NGINX already handles this. config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present? diff --git a/config/environments/test.rb b/config/environments/test.rb index 122634d..1c18915 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -15,7 +15,7 @@ Rails.application.configure do # Configure public file server for tests with Cache-Control for performance. config.public_file_server.enabled = true config.public_file_server.headers = { - 'Cache-Control' => "public, max-age=#{1.hour.seconds.to_i}" + 'Cache-Control' => "public, max-age=#{1.hour.to_i}" } config.assets.digest = false diff --git a/config/initializers/content_security_policy.rb b/config/initializers/content_security_policy.rb new file mode 100644 index 0000000..37f2c0d --- /dev/null +++ b/config/initializers/content_security_policy.rb @@ -0,0 +1,20 @@ +# Define an application-wide content security policy +# For further information see the following documentation +# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy + +# Rails.application.config.content_security_policy do |p| +# p.default_src :self, :https +# p.font_src :self, :https, :data +# p.img_src :self, :https, :data +# p.object_src :none +# p.script_src :self, :https +# p.style_src :self, :https, :unsafe_inline +# +# # Specify URI for violation reports +# # p.report_uri "/csp-violation-report-endpoint" +# end + +# Report CSP violations to a specified URI +# For further information see the following documentation: +# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only +# Rails.application.config.content_security_policy_report_only = true diff --git a/config/initializers/cors.rb b/config/initializers/cors.rb new file mode 100644 index 0000000..681a749 --- /dev/null +++ b/config/initializers/cors.rb @@ -0,0 +1,26 @@ +# Be sure to restart your server when you modify this file. + +# Avoid CORS issues when API is called from the frontend app. +# Handle Cross-Origin Resource Sharing (CORS) in order to accept cross-origin AJAX requests. + +# Read more: https://github.com/cyu/rack-cors + +Rails.application.config.middleware.insert_before 0, Rack::Cors do + allow do + origins '*' + + resource '/@:username', + headers: :any, + methods: [:get], + credentials: false + resource '/api/*', + headers: :any, + methods: [:post, :put, :delete, :get, :patch, :options], + credentials: false, + expose: ['Link', 'X-RateLimit-Reset', 'X-RateLimit-Limit', 'X-RateLimit-Remaining', 'X-Request-Id'] + resource '/oauth/token', + headers: :any, + methods: [:post], + credentials: false + end +end diff --git a/db/schema.rb b/db/schema.rb index a9733a2..218457e 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,9 +10,10 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20180402040909) do +ActiveRecord::Schema.define(version: 2018_04_02_040909) do # These are extensions that must be enabled in order to support this database + enable_extension "pg_stat_statements" enable_extension "plpgsql" create_table "account_domain_blocks", force: :cascade do |t| From 14d86eb0d076cbadc5944d817ddb731d95312ccf Mon Sep 17 00:00:00 2001 From: "Renato \"Lond\" Cerqueira" Date: Thu, 12 Apr 2018 20:36:02 +0200 Subject: [PATCH 115/381] Allow more than the max pins if account is not local (#7105) Sidekiq sometimes throws errors for users that have more pinned items than the allowed by the local instance. It should only validate the number of pins for local accounts. --- app/validators/status_pin_validator.rb | 2 +- spec/models/status_pin_spec.rb | 31 +++++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/app/validators/status_pin_validator.rb b/app/validators/status_pin_validator.rb index 64da041..2c7bce6 100644 --- a/app/validators/status_pin_validator.rb +++ b/app/validators/status_pin_validator.rb @@ -5,6 +5,6 @@ class StatusPinValidator < ActiveModel::Validator pin.errors.add(:base, I18n.t('statuses.pin_errors.reblog')) if pin.status.reblog? pin.errors.add(:base, I18n.t('statuses.pin_errors.ownership')) if pin.account_id != pin.status.account_id pin.errors.add(:base, I18n.t('statuses.pin_errors.private')) unless %w(public unlisted).include?(pin.status.visibility) - pin.errors.add(:base, I18n.t('statuses.pin_errors.limit')) if pin.account.status_pins.count > 4 + pin.errors.add(:base, I18n.t('statuses.pin_errors.limit')) if pin.account.status_pins.count > 4 && pin.account.local? end end diff --git a/spec/models/status_pin_spec.rb b/spec/models/status_pin_spec.rb index 6f54f80..944baf6 100644 --- a/spec/models/status_pin_spec.rb +++ b/spec/models/status_pin_spec.rb @@ -37,5 +37,36 @@ RSpec.describe StatusPin, type: :model do expect(StatusPin.new(account: account, status: status).save).to be false end + + max_pins = 5 + it 'does not allow pins above the max' do + account = Fabricate(:account) + status = [] + + (max_pins + 1).times do |i| + status[i] = Fabricate(:status, account: account) + end + + max_pins.times do |i| + expect(StatusPin.new(account: account, status: status[i]).save).to be true + end + + expect(StatusPin.new(account: account, status: status[max_pins]).save).to be false + end + + it 'allows pins above the max for remote accounts' do + account = Fabricate(:account, domain: 'remote', username: 'bob', url: 'https://remote/') + status = [] + + (max_pins + 1).times do |i| + status[i] = Fabricate(:status, account: account) + end + + max_pins.times do |i| + expect(StatusPin.new(account: account, status: status[i]).save).to be true + end + + expect(StatusPin.new(account: account, status: status[max_pins]).save).to be true + end end end From 9e45b051cfea667f9ca3d3c72d13022259315090 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Fri, 13 Apr 2018 01:20:04 +0200 Subject: [PATCH 116/381] When notification type filtered, ignore live updates for it (#7101) Fix #5625 --- app/javascript/mastodon/actions/notifications.js | 37 +++++++++++++++--------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/app/javascript/mastodon/actions/notifications.js b/app/javascript/mastodon/actions/notifications.js index da77afb..7aa070f 100644 --- a/app/javascript/mastodon/actions/notifications.js +++ b/app/javascript/mastodon/actions/notifications.js @@ -9,7 +9,8 @@ import { } from './importer'; import { defineMessages } from 'react-intl'; -export const NOTIFICATIONS_UPDATE = 'NOTIFICATIONS_UPDATE'; +export const NOTIFICATIONS_UPDATE = 'NOTIFICATIONS_UPDATE'; +export const NOTIFICATIONS_UPDATE_NOOP = 'NOTIFICATIONS_UPDATE_NOOP'; export const NOTIFICATIONS_EXPAND_REQUEST = 'NOTIFICATIONS_EXPAND_REQUEST'; export const NOTIFICATIONS_EXPAND_SUCCESS = 'NOTIFICATIONS_EXPAND_SUCCESS'; @@ -39,21 +40,30 @@ const unescapeHTML = (html) => { export function updateNotifications(notification, intlMessages, intlLocale) { return (dispatch, getState) => { - const showAlert = getState().getIn(['settings', 'notifications', 'alerts', notification.type], true); - const playSound = getState().getIn(['settings', 'notifications', 'sounds', notification.type], true); + const showInColumn = getState().getIn(['settings', 'notifications', 'shows', notification.type], true); + const showAlert = getState().getIn(['settings', 'notifications', 'alerts', notification.type], true); + const playSound = getState().getIn(['settings', 'notifications', 'sounds', notification.type], true); - dispatch(importFetchedAccount(notification.account)); - if (notification.status) { - dispatch(importFetchedStatus(notification.status)); - } + if (showInColumn) { + dispatch(importFetchedAccount(notification.account)); - dispatch({ - type: NOTIFICATIONS_UPDATE, - notification, - meta: playSound ? { sound: 'boop' } : undefined, - }); + if (notification.status) { + dispatch(importFetchedStatus(notification.status)); + } + + dispatch({ + type: NOTIFICATIONS_UPDATE, + notification, + meta: playSound ? { sound: 'boop' } : undefined, + }); - fetchRelatedRelationships(dispatch, [notification]); + fetchRelatedRelationships(dispatch, [notification]); + } else if (playSound) { + dispatch({ + type: NOTIFICATIONS_UPDATE_NOOP, + meta: { sound: 'boop' }, + }); + } // Desktop notifications if (typeof window.Notification !== 'undefined' && showAlert) { @@ -61,6 +71,7 @@ export function updateNotifications(notification, intlMessages, intlLocale) { const body = (notification.status && notification.status.spoiler_text.length > 0) ? notification.status.spoiler_text : unescapeHTML(notification.status ? notification.status.content : ''); const notify = new Notification(title, { body, icon: notification.account.avatar, tag: notification.id }); + notify.addEventListener('click', () => { window.focus(); notify.close(); From 778562c223844226a52198100dc081811bdd9d35 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Fri, 13 Apr 2018 01:27:22 +0200 Subject: [PATCH 117/381] Ensure SynchronizeFeaturedCollectionWorker is unique and clean up (#7043) * Ensure SynchronizeFeaturedCollectionWorker is unique and clean up Fix #7041 * Fix code style issue --- app/services/activitypub/process_account_service.rb | 13 ++++++++----- .../activitypub/synchronize_featured_collection_worker.rb | 2 +- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/app/services/activitypub/process_account_service.rb b/app/services/activitypub/process_account_service.rb index 21c2fc5..4475a90 100644 --- a/app/services/activitypub/process_account_service.rb +++ b/app/services/activitypub/process_account_service.rb @@ -22,13 +22,15 @@ class ActivityPub::ProcessAccountService < BaseService create_account if @account.nil? update_account - process_tags(@account) + process_tags end end + return if @account.nil? + after_protocol_change! if protocol_changed? after_key_change! if key_changed? - check_featured_collection! if @account&.featured_collection_url&.present? + check_featured_collection! if @account.featured_collection_url.present? @account rescue Oj::ParseError @@ -189,17 +191,18 @@ class ActivityPub::ProcessAccountService < BaseService { redis: Redis.current, key: "process_account:#{@uri}" } end - def process_tags(account) + def process_tags return if @json['tag'].blank? + as_array(@json['tag']).each do |tag| case tag['type'] when 'Emoji' - process_emoji tag, account + process_emoji tag end end end - def process_emoji(tag, _account) + def process_emoji(tag) return if skip_download? return if tag['name'].blank? || tag['icon'].blank? || tag['icon']['url'].blank? diff --git a/app/workers/activitypub/synchronize_featured_collection_worker.rb b/app/workers/activitypub/synchronize_featured_collection_worker.rb index dd676a3..7b16d34 100644 --- a/app/workers/activitypub/synchronize_featured_collection_worker.rb +++ b/app/workers/activitypub/synchronize_featured_collection_worker.rb @@ -3,7 +3,7 @@ class ActivityPub::SynchronizeFeaturedCollectionWorker include Sidekiq::Worker - sidekiq_options queue: 'pull' + sidekiq_options queue: 'pull', unique: :until_executed def perform(account_id) ActivityPub::FetchFeaturedCollectionService.new.call(Account.find(account_id)) From 85ab30abf7f8da61d37e4711cba350877bfb6f2b Mon Sep 17 00:00:00 2001 From: mayaeh Date: Fri, 13 Apr 2018 20:11:26 +0900 Subject: [PATCH 118/381] i18n: Add Japanese translations for privacy policy and more (#7049) * Update Japanese translations. * Update Japanese translations. * Update Japanese translations. * Update Japanese translations. * Add Japanese translations for #6984, #7040, #7072. Update Japanese translations for privacy policy. * Add Japanese translations for #7032, #7074, #7089. * Proofreading Japanese translations for privacy policy. --- .../mastodon/locales/defaultMessages.json | 9 ++ app/javascript/mastodon/locales/ja.json | 2 + config/locales/ja.yml | 104 ++++++++++++++++++++- 3 files changed, 111 insertions(+), 4 deletions(-) diff --git a/app/javascript/mastodon/locales/defaultMessages.json b/app/javascript/mastodon/locales/defaultMessages.json index 03be288..21cd835 100644 --- a/app/javascript/mastodon/locales/defaultMessages.json +++ b/app/javascript/mastodon/locales/defaultMessages.json @@ -121,6 +121,15 @@ "id": "status.load_more" } ], + "path": "app/javascript/mastodon/components/load_gap.json" + }, + { + "descriptors": [ + { + "defaultMessage": "Load more", + "id": "status.load_more" + } + ], "path": "app/javascript/mastodon/components/load_more.json" }, { diff --git a/app/javascript/mastodon/locales/ja.json b/app/javascript/mastodon/locales/ja.json index 4325177..a9beaa3 100644 --- a/app/javascript/mastodon/locales/ja.json +++ b/app/javascript/mastodon/locales/ja.json @@ -240,6 +240,7 @@ "status.block": "@{name}さんをブロック", "status.cannot_reblog": "この投稿はブーストできません", "status.delete": "削除", + "status.direct": "@{name}さんにダイレクトメッセージ", "status.embed": "埋め込み", "status.favourite": "お気に入り", "status.load_more": "もっと見る", @@ -269,6 +270,7 @@ "tabs_bar.home": "ホーム", "tabs_bar.local_timeline": "ローカル", "tabs_bar.notifications": "通知", + "tabs_bar.search": "検索", "ui.beforeunload": "Mastodonから離れると送信前の投稿は失われます。", "upload_area.title": "ドラッグ&ドロップでアップロード", "upload_button.label": "メディアを追加", diff --git a/config/locales/ja.yml b/config/locales/ja.yml index b943f0f..960dd38 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -4,6 +4,7 @@ ja: about_hashtag_html: ハッシュタグ #%{hashtag} の付いた公開トゥートです。どこでもいいので、連合に参加しているSNS上にアカウントを作れば会話に参加することができます。 about_mastodon_html: Mastodon は、オープンなウェブプロトコルを採用した、自由でオープンソースなソーシャルネットワークです。電子メールのような分散型の仕組みを採っています。 about_this: 詳細情報 + administered_by: '管理者:' closed_registrations: 現在このインスタンスでの新規登録は受け付けていません。しかし、他のインスタンスにアカウントを作成しても全く同じネットワークに参加することができます。 contact: 連絡先 contact_missing: 未設定 @@ -62,6 +63,13 @@ ja: are_you_sure: 本当に実行しますか? avatar: アイコン by_domain: ドメイン + change_email: + changed_msg: メールアドレスの変更に成功しました! + current_email: 現在のメールアドレス + label: メールアドレスを変更 + new_email: 新しいメールアドレス + submit: Change Email + title: "%{username} さんのメールアドレスを変更" confirm: 確認 confirmed: 確認済み demote: 降格 @@ -71,7 +79,7 @@ ja: display_name: 表示名 domain: ドメイン edit: 編集 - email: E-mail + email: メールアドレス enable: 有効化 enabled: 有効 feed_url: フィードURL @@ -130,6 +138,7 @@ ja: statuses: トゥート数 subscribe: 購読する title: アカウント + unconfirmed_email: 確認待ちのメールアドレス undo_silenced: サイレンスから戻す undo_suspension: 停止から戻す unsubscribe: 購読の解除 @@ -138,6 +147,7 @@ ja: action_logs: actions: assigned_to_self_report: "%{name} さんがレポート %{target} を自身の担当に割り当てました" + change_email_user: "%{name} さんが %{target} さんのメールアドレスを変更しました" confirm_user: "%{name} さんが %{target} さんのメールアドレスを確認済みにしました" create_custom_emoji: "%{name} さんがカスタム絵文字 %{target} を追加しました" create_domain_block: "%{name} さんがドメイン %{target} をブロックしました" @@ -246,8 +256,8 @@ ja: title: フィルター title: 招待 report_notes: - created_msg: モデレーションメモを書き込みました! - destroyed_msg: モデレーションメモを削除しました! + created_msg: レポートメモを書き込みました! + destroyed_msg: レポートメモを削除しました! reports: action_taken_by: レポート処理者 are_you_sure: 本当に実行しますか? @@ -256,15 +266,20 @@ ja: comment: label: コメント none: なし + created_at: レポート日時 delete: 削除 + history: モデレーション履歴 id: ID mark_as_resolved: 解決済みとしてマーク mark_as_unresolved: 未解決として再び開く notes: create: 書き込む create_and_resolve: 書き込み、解決済みにする + create_and_unresolve: 書き込み、未解決として開く delete: 削除 - label: メモ + label: モデレーターメモ + new_label: モデレーターメモの追加 + placeholder: このレポートに取られた措置やその他更新を記述してください nsfw: 'false': NSFW オフ 'true': NSFW オン @@ -601,6 +616,10 @@ ja: missing_resource: リダイレクト先が見つかりませんでした proceed: フォローする prompt: 'フォローしようとしています:' + remote_unfollow: + error: エラー + title: タイトル + unfollowed: フォロー解除しました sessions: activity: 最後のアクティビティ browser: ブラウザ @@ -689,6 +708,83 @@ ja: reblogged: さんがブースト sensitive_content: 閲覧注意 terms: + body_html: | +

プライバシーポリシー

+

どのような情報を収集しますか?

+ +
    +
  • 基本的なアカウント情報: 当サイトに登録すると、ユーザー名・メールアドレス・パスワードの入力を求められることがあります。また表示名や自己紹介・プロフィール画像・ヘッダー画像といった追加のプロフィールを登録できます。ユーザー名・表示名・自己紹介・プロフィール画像・ヘッダー画像は常に公開されます。
  • +
  • 投稿・フォロー・その他公開情報: フォローしているユーザーの一覧は一般公開されます。フォロワーも同様です。メッセージを投稿する際、日時だけでなく投稿に使用したアプリケーション名も記録されます。メッセージには写真や動画といった添付メディアを含むことがあります。「公開」や「未収載」の投稿は一般公開されます。プロフィールに投稿を載せるとそれもまた公開情報となります。投稿はフォロワーに配信されます。場合によっては他のサーバーに配信され、そこにコピーが保存されることを意味します。投稿を削除した場合も同様にフォロワーに配信されます。他の投稿をリブログやお気に入り登録する行動は常に公開されます。
  • +
  • 「ダイレクト」と「非公開」投稿: すべての投稿はサーバーに保存され、処理されます。「非公開」投稿はフォロワーと投稿に書かれたユーザーに配信されます。「ダイレクト」投稿は投稿に書かれたユーザーにのみ配信されます。場合によっては他のサーバーに配信され、そこにコピーが保存されることを意味します。私たちはこれらの閲覧を一部の許可された者に限定するよう誠意を持って努めます。しかし他のサーバーにおいても同様に扱われるとは限りません。したがって、相手の所属するサーバーを吟味することが重要です。設定で新しいフォロワーの承認または拒否を手動で行うよう切り替えることもできます。サーバー管理者は「ダイレクト」や「非公開」投稿も閲覧する可能性があることを忘れないでください。また受信者がスクリーンショットやコピー、もしくは共有する可能性があることを忘れないでください。いかなる危険な情報もMastodon上で共有しないでください。
  • +
  • IPアドレスやその他メタデータ: ログインする際IPアドレスだけでなくブラウザーアプリケーション名を記録します。ログインしたセッションはすべてユーザー設定で見直し、取り消すことができます。使用されている最新のIPアドレスは最大12ヵ月間保存されます。またサーバーへのIPアドレスを含むすべてのリクエストのログを保持することがあります。
  • +
+ +
+ +

情報を何に使用しますか?

+ +

収集した情報は次の用途に使用されることがあります:

+ +
    +
  • Mastodonのコア機能の提供: ログインしている間にかぎり他の人たちと投稿を通じて交流することができます。例えば自分専用のホームタイムラインで投稿をまとめて読むために他の人たちをフォローできます。
  • +
  • コミュニティ維持の補助: 例えばIPアドレスを既知のものと比較し、BAN回避目的の複数登録者やその他違反者を判別します。
  • +
  • 提供されたメールアドレスはお知らせの送信・投稿に対するリアクションやメッセージ送信の通知・お問い合わせやその他要求や質問への返信に使用されることがあります。
  • +
+ +
+ +

情報をどのように保護しますか?

+ +

私たちはあなたが入力・送信する際や自身の情報にアクセスする際に個人情報を安全に保つため、さまざまなセキュリティ上の対策を実施します。特にブラウザーセッションだけでなくアプリケーションとAPI間の通信もSSLによって保護されます。またパスワードは強力な不可逆アルゴリズムでハッシュ化されます。二段階認証を有効にし、アカウントへのアクセスをさらに安全にすることができます。

+ +
+ +

データ保持方針はどうなっていますか?

+ +

私たちは次のように誠意を持って努めます:

+ +
    +
  • 当サイトへのIPアドレスを含むすべての要求に対するサーバーログを90日以内のできるかぎりの間保持します。
  • +
  • 登録されたユーザーに関連付けられたIPアドレスを12ヵ月以内の間保持します。
  • +
+ +

あなたは投稿・添付メディア・プロフィール画像・ヘッダー画像を含む自身のデータのアーカイブを要求し、ダウンロードすることができます。

+ +

あなたはいつでもアカウントの削除を要求できます。削除は取り消すことができません。

+ +
+ +

クッキーを使用していますか?

+ +

はい。クッキーは (あなたが許可した場合に) WebサイトやサービスがWebブラウザーを介してコンピューターに保存する小さなファイルです。使用することで Web サイトがブラウザーを識別し、登録済みのアカウントがある場合関連付けます。

+ +

私たちはクッキーを将来の訪問のために設定を保存し呼び出す用途に使用します。

+ +
+ +

なんらかの情報を外部に提供していますか?

+ +

私たちは個人を特定できる情報を外部へ販売・取引・その他方法で渡すことはありません。これには当サイトの運営・業務遂行・サービス提供を行ううえで補助する信頼できる第三者をこの機密情報の保護に同意するかぎり含みません。法令の遵守やサイトポリシーの施行、権利・財産・安全の保護に適切と判断した場合、あなたの情報を公開することがあります。

+ +

あなたの公開情報はネットワーク上の他のサーバーにダウンロードされることがあります。相手が異なるサーバーに所属する場合、「公開」と「非公開」投稿はフォロワーの所属するサーバーに配信され、「ダイレクト」投稿は受信者の所属するサーバーに配信されます。

+ +

あなたがアカウントの使用をアプリケーションに許可すると、承認した権限の範囲内で公開プロフィール情報・フォローリスト・フォロワー・リスト・すべての投稿・お気に入り登録にアクセスできます。アプリケーションはメールアドレスやパスワードに決してアクセスできません。

+ +
+ +

児童オンラインプライバシー保護法の遵守

+ +

当サイト・製品・サービスは13歳以上の人を対象としています。サーバーが米国にあり、あなたが13歳未満の場合、COPPA (Children's Online Privacy Protection Act - 児童オンラインプライバシー保護法) により当サイトを使用できません。

+ +
+ +

プライバシーポリシーの変更

+ +

プライバシーポリシーの変更を決定した場合、このページに変更点を掲載します。

+ +

この文章のライセンスはCC-BY-SAです。最終更新日は2018年3月7日です。

+ +

オリジナルの出典: Discourse privacy policy

title: "%{instance} 利用規約・プライバシーポリシー" themes: default: Mastodon From 78ed4ab75ff77d7cba60d478aa1f45d1c104785d Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Sat, 14 Apr 2018 12:41:08 +0200 Subject: [PATCH 119/381] Add bio fields (#6645) * Add bio fields - Fix #3211 - Fix #232 - Fix #121 * Display bio fields in web UI * Fix output of links and missing fields * Federate bio fields over ActivityPub as PropertyValue * Improve how the fields are stored, add to Edit profile form * Add rel=me to links in fields Fix #121 --- app/controllers/settings/profiles_controller.rb | 6 ++- .../mastodon/actions/importer/normalizer.js | 8 ++++ .../mastodon/features/account/components/header.js | 14 ++++++ app/javascript/styles/mastodon/accounts.scss | 54 ++++++++++++++++++++++ app/javascript/styles/mastodon/components.scss | 37 +++++++++++++++ app/javascript/styles/mastodon/forms.scss | 12 +++++ app/lib/activitypub/adapter.rb | 3 ++ app/lib/formatter.rb | 18 ++++++-- app/models/account.rb | 36 +++++++++++++++ app/serializers/activitypub/actor_serializer.rb | 17 +++++++ app/serializers/rest/account_serializer.rb | 10 ++++ .../activitypub/process_account_service.rb | 6 +++ app/views/accounts/_header.html.haml | 8 ++++ app/views/settings/profiles/show.html.haml | 10 ++++ config/locales/simple_form.en.yml | 6 +++ .../20180410204633_add_fields_to_accounts.rb | 5 ++ db/schema.rb | 3 +- .../activitypub/process_account_service_spec.rb | 28 ++++++++++- 18 files changed, 274 insertions(+), 7 deletions(-) create mode 100644 db/migrate/20180410204633_add_fields_to_accounts.rb diff --git a/app/controllers/settings/profiles_controller.rb b/app/controllers/settings/profiles_controller.rb index 28f78a4..5d81668 100644 --- a/app/controllers/settings/profiles_controller.rb +++ b/app/controllers/settings/profiles_controller.rb @@ -11,7 +11,9 @@ class Settings::ProfilesController < ApplicationController obfuscate_filename [:account, :avatar] obfuscate_filename [:account, :header] - def show; end + def show + @account.build_fields + end def update if UpdateAccountService.new.call(@account, account_params) @@ -25,7 +27,7 @@ class Settings::ProfilesController < ApplicationController private def account_params - params.require(:account).permit(:display_name, :note, :avatar, :header, :locked) + params.require(:account).permit(:display_name, :note, :avatar, :header, :locked, fields_attributes: [:name, :value]) end def set_account diff --git a/app/javascript/mastodon/actions/importer/normalizer.js b/app/javascript/mastodon/actions/importer/normalizer.js index 1b09f31..5f1274f 100644 --- a/app/javascript/mastodon/actions/importer/normalizer.js +++ b/app/javascript/mastodon/actions/importer/normalizer.js @@ -10,6 +10,14 @@ export function normalizeAccount(account) { account.display_name_html = emojify(escapeTextContentForBrowser(displayName)); account.note_emojified = emojify(account.note); + if (account.fields) { + account.fields = account.fields.map(pair => ({ + ...pair, + name_emojified: emojify(escapeTextContentForBrowser(pair.name)), + value_emojified: emojify(pair.value), + })); + } + if (account.moved) { account.moved = account.moved.id; } diff --git a/app/javascript/mastodon/features/account/components/header.js b/app/javascript/mastodon/features/account/components/header.js index bb7b3b6..bbf886d 100644 --- a/app/javascript/mastodon/features/account/components/header.js +++ b/app/javascript/mastodon/features/account/components/header.js @@ -130,6 +130,7 @@ export default class Header extends ImmutablePureComponent { const content = { __html: account.get('note_emojified') }; const displayNameHtml = { __html: account.get('display_name_html') }; + const fields = account.get('fields'); return (
@@ -140,6 +141,19 @@ export default class Header extends ImmutablePureComponent { @{account.get('acct')} {lockedIcon}
+ {fields.size > 0 && ( + + + {fields.map((pair, i) => ( + + + ))} + +
+ +
+ )} + {info} {mutingInfo} {actionBtn} diff --git a/app/javascript/styles/mastodon/accounts.scss b/app/javascript/styles/mastodon/accounts.scss index dd82ab3..0b49da1 100644 --- a/app/javascript/styles/mastodon/accounts.scss +++ b/app/javascript/styles/mastodon/accounts.scss @@ -563,3 +563,57 @@ border-color: rgba(lighten($error-red, 12%), 0.5); } } + +.account__header__fields { + border-collapse: collapse; + padding: 0; + margin: 15px -15px -15px; + border: 0 none; + border-top: 1px solid lighten($ui-base-color, 4%); + border-bottom: 1px solid lighten($ui-base-color, 4%); + + th, + td { + padding: 15px; + padding-left: 15px; + border: 0 none; + border-bottom: 1px solid lighten($ui-base-color, 4%); + vertical-align: middle; + } + + th { + padding-left: 15px; + font-weight: 500; + text-align: center; + width: 94px; + color: $ui-secondary-color; + background: rgba(darken($ui-base-color, 8%), 0.5); + } + + td { + color: $ui-primary-color; + text-align: center; + width: 100%; + padding-left: 0; + } + + a { + color: $ui-highlight-color; + text-decoration: none; + + &:hover, + &:focus, + &:active { + text-decoration: underline; + } + } + + tr { + &:last-child { + th, + td { + border-bottom: 0; + } + } + } +} diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index 888a0ad..96112d8 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -5176,3 +5176,40 @@ noscript { background: lighten($ui-highlight-color, 7%); } } + +.account__header .account__header__fields { + font-size: 14px; + line-height: 20px; + overflow: hidden; + border-collapse: collapse; + margin: 20px -10px -20px; + border-bottom: 0; + + tr { + border-top: 1px solid lighten($ui-base-color, 8%); + text-align: center; + } + + th, + td { + padding: 14px 20px; + vertical-align: middle; + max-height: 40px; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } + + th { + color: $ui-primary-color; + background: darken($ui-base-color, 4%); + max-width: 120px; + font-weight: 500; + } + + td { + flex: auto; + color: $primary-text-color; + background: $ui-base-color; + } +} diff --git a/app/javascript/styles/mastodon/forms.scss b/app/javascript/styles/mastodon/forms.scss index d74c5a4..945579a 100644 --- a/app/javascript/styles/mastodon/forms.scss +++ b/app/javascript/styles/mastodon/forms.scss @@ -15,6 +15,18 @@ code { overflow: hidden; } + .row { + display: flex; + margin: 0 -5px; + + .input { + box-sizing: border-box; + flex: 1 1 auto; + width: 50%; + padding: 0 5px; + } + } + span.hint { display: block; color: $ui-primary-color; diff --git a/app/lib/activitypub/adapter.rb b/app/lib/activitypub/adapter.rb index f19b04a..e880499 100644 --- a/app/lib/activitypub/adapter.rb +++ b/app/lib/activitypub/adapter.rb @@ -19,6 +19,9 @@ class ActivityPub::Adapter < ActiveModelSerializers::Adapter::Base 'Emoji' => 'toot:Emoji', 'focalPoint' => { '@container' => '@list', '@id' => 'toot:focalPoint' }, 'featured' => 'toot:featured', + 'schema' => 'http://schema.org#', + 'PropertyValue' => 'schema:PropertyValue', + 'value' => 'schema:value', }, ], }.freeze diff --git a/app/lib/formatter.rb b/app/lib/formatter.rb index f7e7a3c..4124f16 100644 --- a/app/lib/formatter.rb +++ b/app/lib/formatter.rb @@ -71,6 +71,11 @@ class Formatter html.html_safe # rubocop:disable Rails/OutputSafety end + def format_field(account, str) + return reformat(str).html_safe unless account.local? # rubocop:disable Rails/OutputSafety + encode_and_link_urls(str, me: true).html_safe # rubocop:disable Rails/OutputSafety + end + def linkify(text) html = encode_and_link_urls(text) html = simple_format(html, {}, sanitize: false) @@ -85,12 +90,17 @@ class Formatter HTMLEntities.new.encode(html) end - def encode_and_link_urls(html, accounts = nil) + def encode_and_link_urls(html, accounts = nil, options = {}) entities = Extractor.extract_entities_with_indices(html, extract_url_without_protocol: false) + if accounts.is_a?(Hash) + options = accounts + accounts = nil + end + rewrite(html.dup, entities) do |entity| if entity[:url] - link_to_url(entity) + link_to_url(entity, options) elsif entity[:hashtag] link_to_hashtag(entity) elsif entity[:screen_name] @@ -177,10 +187,12 @@ class Formatter result.flatten.join end - def link_to_url(entity) + def link_to_url(entity, options = {}) url = Addressable::URI.parse(entity[:url]) html_attrs = { target: '_blank', rel: 'nofollow noopener' } + html_attrs[:rel] = "me #{html_attrs[:rel]}" if options[:me] + Twitter::Autolink.send(:link_to_text, entity, link_html(entity[:url]), url, html_attrs) rescue Addressable::URI::InvalidURIError, IDN::Idna::IdnaError encode(entity[:url]) diff --git a/app/models/account.rb b/app/models/account.rb index 5bdcfa9..05e817f 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -44,6 +44,7 @@ # memorial :boolean default(FALSE), not null # moved_to_account_id :integer # featured_collection_url :string +# fields :jsonb # class Account < ApplicationRecord @@ -189,6 +190,30 @@ class Account < ApplicationRecord @keypair ||= OpenSSL::PKey::RSA.new(private_key || public_key) end + def fields + (self[:fields] || []).map { |f| Field.new(self, f) } + end + + def fields_attributes=(attributes) + fields = [] + + attributes.each_value do |attr| + next if attr[:name].blank? + fields << attr + end + + self[:fields] = fields + end + + def build_fields + return if fields.size >= 4 + + raw_fields = self[:fields] || [] + add_fields = 4 - raw_fields.size + add_fields.times { raw_fields << { name: '', value: '' } } + self.fields = raw_fields + end + def magic_key modulus, exponent = [keypair.public_key.n, keypair.public_key.e].map do |component| result = [] @@ -238,6 +263,17 @@ class Account < ApplicationRecord shared_inbox_url.presence || inbox_url end + class Field < ActiveModelSerializers::Model + attributes :name, :value, :account, :errors + + def initialize(account, attr) + @account = account + @name = attr['name'] + @value = attr['value'] + @errors = {} + end + end + class << self def readonly_attributes super - %w(statuses_count following_count followers_count) diff --git a/app/serializers/activitypub/actor_serializer.rb b/app/serializers/activitypub/actor_serializer.rb index df30907..fcf3bdf 100644 --- a/app/serializers/activitypub/actor_serializer.rb +++ b/app/serializers/activitypub/actor_serializer.rb @@ -11,6 +11,7 @@ class ActivityPub::ActorSerializer < ActiveModel::Serializer has_one :public_key, serializer: ActivityPub::PublicKeySerializer has_many :virtual_tags, key: :tag + has_many :virtual_attachments, key: :attachment attribute :moved_to, if: :moved? @@ -107,10 +108,26 @@ class ActivityPub::ActorSerializer < ActiveModel::Serializer object.emojis end + def virtual_attachments + object.fields + end + def moved_to ActivityPub::TagManager.instance.uri_for(object.moved_to_account) end class CustomEmojiSerializer < ActivityPub::EmojiSerializer end + + class Account::FieldSerializer < ActiveModel::Serializer + attributes :type, :name, :value + + def type + 'PropertyValue' + end + + def value + Formatter.instance.format_field(object.account, object.value) + end + end end diff --git a/app/serializers/rest/account_serializer.rb b/app/serializers/rest/account_serializer.rb index 6097acd..863238e 100644 --- a/app/serializers/rest/account_serializer.rb +++ b/app/serializers/rest/account_serializer.rb @@ -9,6 +9,16 @@ class REST::AccountSerializer < ActiveModel::Serializer has_one :moved_to_account, key: :moved, serializer: REST::AccountSerializer, if: :moved_and_not_nested? + class FieldSerializer < ActiveModel::Serializer + attributes :name, :value + + def value + Formatter.instance.format_field(object.account, object.value) + end + end + + has_many :fields + def id object.id.to_s end diff --git a/app/services/activitypub/process_account_service.rb b/app/services/activitypub/process_account_service.rb index 4475a90..da32f96 100644 --- a/app/services/activitypub/process_account_service.rb +++ b/app/services/activitypub/process_account_service.rb @@ -70,6 +70,7 @@ class ActivityPub::ProcessAccountService < BaseService @account.display_name = @json['name'] || '' @account.note = @json['summary'] || '' @account.locked = @json['manuallyApprovesFollowers'] || false + @account.fields = property_values || {} end def set_fetchable_attributes! @@ -126,6 +127,11 @@ class ActivityPub::ProcessAccountService < BaseService end end + def property_values + return unless @json['attachment'].is_a?(Array) + @json['attachment'].select { |attachment| attachment['type'] == 'PropertyValue' }.map { |attachment| attachment.slice('name', 'value') } + end + def mismatching_origin?(url) needle = Addressable::URI.parse(url).host haystack = Addressable::URI.parse(@uri).host diff --git a/app/views/accounts/_header.html.haml b/app/views/accounts/_header.html.haml index b78998e..0d3a0d0 100644 --- a/app/views/accounts/_header.html.haml +++ b/app/views/accounts/_header.html.haml @@ -23,6 +23,14 @@ .bio .account__header__content.p-note.emojify= Formatter.instance.simplified_format(account, custom_emojify: true) + - unless account.fields.empty? + %table.account__header__fields + %tbody + - account.fields.each do |field| + %tr + %th.emojify= field.name + %td.emojify= Formatter.instance.format_field(account, field.value) + .details-counters .counter{ class: active_nav_class(short_account_url(account)) } = link_to short_account_url(account), class: 'u-url u-uid' do diff --git a/app/views/settings/profiles/show.html.haml b/app/views/settings/profiles/show.html.haml index 0fc9db2..f28834d 100644 --- a/app/views/settings/profiles/show.html.haml +++ b/app/views/settings/profiles/show.html.haml @@ -19,6 +19,16 @@ .fields-group = f.input :locked, as: :boolean, wrapper: :with_label, hint: t('simple_form.hints.defaults.locked') + .fields-group + .input.with_block_label + %label= t('simple_form.labels.defaults.fields') + %span.hint= t('simple_form.hints.defaults.fields') + + = f.simple_fields_for :fields do |fields_f| + .row + = fields_f.input :name, placeholder: t('simple_form.labels.account.fields.name') + = fields_f.input :value, placeholder: t('simple_form.labels.account.fields.value') + .actions = f.button :button, t('generic.save_changes'), type: :submit diff --git a/config/locales/simple_form.en.yml b/config/locales/simple_form.en.yml index 37a02bd..1a0d60f 100644 --- a/config/locales/simple_form.en.yml +++ b/config/locales/simple_form.en.yml @@ -8,6 +8,7 @@ en: display_name: one: 1 character left other: %{count} characters left + fields: You can have up to 4 items displayed as a table on your profile header: PNG, GIF or JPG. At most 2MB. Will be downscaled to 700x335px locked: Requires you to manually approve followers note: @@ -22,6 +23,10 @@ en: user: filtered_languages: Checked languages will be filtered from public timelines for you labels: + account: + fields: + name: Label + value: Content defaults: avatar: Avatar confirm_new_password: Confirm new password @@ -31,6 +36,7 @@ en: display_name: Display name email: E-mail address expires_in: Expire after + fields: Profile metadata filtered_languages: Filtered languages header: Header locale: Language diff --git a/db/migrate/20180410204633_add_fields_to_accounts.rb b/db/migrate/20180410204633_add_fields_to_accounts.rb new file mode 100644 index 0000000..5b8c174 --- /dev/null +++ b/db/migrate/20180410204633_add_fields_to_accounts.rb @@ -0,0 +1,5 @@ +class AddFieldsToAccounts < ActiveRecord::Migration[5.1] + def change + add_column :accounts, :fields, :jsonb + end +end diff --git a/db/schema.rb b/db/schema.rb index 218457e..10a8f2e 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.define(version: 2018_04_02_040909) do +ActiveRecord::Schema.define(version: 2018_04_10_204633) do # These are extensions that must be enabled in order to support this database enable_extension "pg_stat_statements" @@ -75,6 +75,7 @@ ActiveRecord::Schema.define(version: 2018_04_02_040909) do t.boolean "memorial", default: false, null: false t.bigint "moved_to_account_id" t.string "featured_collection_url" + t.jsonb "fields" t.index "(((setweight(to_tsvector('simple'::regconfig, (display_name)::text), 'A'::\"char\") || setweight(to_tsvector('simple'::regconfig, (username)::text), 'B'::\"char\")) || setweight(to_tsvector('simple'::regconfig, (COALESCE(domain, ''::character varying))::text), 'C'::\"char\")))", name: "search_index", using: :gin t.index "lower((username)::text), lower((domain)::text)", name: "index_accounts_on_username_and_domain_lower" t.index ["uri"], name: "index_accounts_on_uri" diff --git a/spec/services/activitypub/process_account_service_spec.rb b/spec/services/activitypub/process_account_service_spec.rb index 84a74c2..15e1f4b 100644 --- a/spec/services/activitypub/process_account_service_spec.rb +++ b/spec/services/activitypub/process_account_service_spec.rb @@ -1,5 +1,31 @@ require 'rails_helper' RSpec.describe ActivityPub::ProcessAccountService do - pending + subject { described_class.new } + + context 'property values' do + let(:payload) do + { + id: 'https://foo', + type: 'Actor', + inbox: 'https://foo/inbox', + attachment: [ + { type: 'PropertyValue', name: 'Pronouns', value: 'They/them' }, + { type: 'PropertyValue', name: 'Occupation', value: 'Unit test' }, + ], + }.with_indifferent_access + end + + it 'parses out of attachment' do + account = subject.call('alice', 'example.com', payload) + expect(account.fields).to be_a Array + expect(account.fields.size).to eq 2 + expect(account.fields[0]).to be_a Account::Field + expect(account.fields[0].name).to eq 'Pronouns' + expect(account.fields[0].value).to eq 'They/them' + expect(account.fields[1]).to be_a Account::Field + expect(account.fields[1].name).to eq 'Occupation' + expect(account.fields[1].value).to eq 'Unit test' + end + end end From fed0b5ed0453b0347b27acfb2950d94af870b62b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Miko=C5=82ajczak?= Date: Sun, 15 Apr 2018 07:12:41 +0200 Subject: [PATCH 120/381] i18n: Update Polish translation (#7131) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Marcin Mikołajczak --- app/javascript/mastodon/locales/defaultMessages.json | 13 ------------- app/javascript/mastodon/locales/pl.json | 3 ++- config/locales/en.yml | 5 +++++ config/locales/pl.yml | 19 +++++++++++++++++++ 4 files changed, 26 insertions(+), 14 deletions(-) diff --git a/app/javascript/mastodon/locales/defaultMessages.json b/app/javascript/mastodon/locales/defaultMessages.json index 21cd835..a3c4f77 100644 --- a/app/javascript/mastodon/locales/defaultMessages.json +++ b/app/javascript/mastodon/locales/defaultMessages.json @@ -1815,18 +1815,5 @@ } ], "path": "app/javascript/mastodon/features/video/index.json" - }, - { - "descriptors": [ - { - "defaultMessage": "Oops!", - "id": "alert.unexpected.title" - }, - { - "defaultMessage": "An unexpected error occurred.", - "id": "alert.unexpected.message" - } - ], - "path": "app/javascript/mastodon/middleware/errors.json" } ] \ No newline at end of file diff --git a/app/javascript/mastodon/locales/pl.json b/app/javascript/mastodon/locales/pl.json index 0419356..5360b6f 100644 --- a/app/javascript/mastodon/locales/pl.json +++ b/app/javascript/mastodon/locales/pl.json @@ -2,7 +2,7 @@ "account.block": "Blokuj @{name}", "account.block_domain": "Blokuj wszystko z {domain}", "account.blocked": "Zablokowany", - "account.direct": "Direct Message @{name}", + "account.direct": "Wyślij wiadomość bezpośrednią do @{name}", "account.disclaimer_full": "Poniższe informacje mogą nie odwzorowywać bezbłędnie profilu użytkownika.", "account.domain_blocked": "Ukryto domenę", "account.edit_profile": "Edytuj profil", @@ -240,6 +240,7 @@ "status.block": "Zablokuj @{name}", "status.cannot_reblog": "Ten wpis nie może zostać podbity", "status.delete": "Usuń", + "status.direct": "Direct message @{name}", "status.embed": "Osadź", "status.favourite": "Ulubione", "status.load_more": "Załaduj więcej", diff --git a/config/locales/en.yml b/config/locales/en.yml index 98f1359..4816cc5 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -283,6 +283,7 @@ en: nsfw: 'false': Unhide media attachments 'true': Hide media attachments + reopen: Reopen Report report: 'Report #%{id}' report_contents: Contents reported_account: Reported account @@ -615,6 +616,10 @@ en: missing_resource: Could not find the required redirect URL for your account proceed: Proceed to follow prompt: 'You are going to follow:' + remote_unfollow: + error: Error + title: Title + unfollowed: Unfollowed sessions: activity: Last activity browser: Browser diff --git a/config/locales/pl.yml b/config/locales/pl.yml index d303171..0f18ace 100644 --- a/config/locales/pl.yml +++ b/config/locales/pl.yml @@ -63,6 +63,13 @@ pl: are_you_sure: Jesteś tego pewien? avatar: Awatar by_domain: Domena + change_email: + changed_msg: Pomyślnie zmieniono adres e-mail konta! + current_email: Obecny adres e-mail + label: Zmień adres e-mail + new_email: Nowy adres e-mail + submit: Zmień adres e-mail + title: Zmień adres e-mail dla %{username} confirm: Potwierdź confirmed: Potwierdzono demote: Degraduj @@ -131,6 +138,7 @@ pl: statuses: Wpisy subscribe: Subskrybuj title: Konta + unconfirmed_email: Niepotwierdzony adres e-mail undo_silenced: Cofnij wyciszenie undo_suspension: Cofnij zawieszenie unsubscribe: Przestań subskrybować @@ -139,6 +147,7 @@ pl: action_logs: actions: assigned_to_self_report: "%{name} przypisał sobie zgłoszenie %{target}" + change_email_user: "%{name} zmienił adres-email użytkownika %{target}" confirm_user: "%{name} potwierdził adres e-mail użytkownika %{target}" create_custom_emoji: "%{name} dodał nowe emoji %{target}" create_domain_block: "%{name} zablokował domenę %{target}" @@ -258,18 +267,24 @@ pl: comment: label: Komentarz do zgłoszenia none: Brak + created_at: Zgłoszono delete: Usuń + history: Historia moderacji id: ID mark_as_resolved: Oznacz jako rozwiązane mark_as_unresolved: Oznacz jako nierozwiązane notes: create: Utwórz notatkę create_and_resolve: Rozwiąż i pozostaw notatkę + create_and_unresolve: Cofnij rozwiązanie i pozostaw notatkę delete: Usuń label: Notatki + new_label: Dodaj notatkę moderacyjną + placeholder: Opisz wykonane akcje i inne szczegóły dotyczące tego zgłoszenia… nsfw: 'false': Nie oznaczaj jako NSFW 'true': Oznaczaj jako NSFW + reopen: Otwórz ponownie report: 'Zgłoszenie #%{id}' report_contents: Zawartość reported_account: Zgłoszone konto @@ -608,6 +623,10 @@ pl: missing_resource: Nie udało się znaleźć adresu przekierowania z Twojej domeny proceed: Śledź prompt: 'Zamierzasz śledzić:' + remote_unfollow: + error: Błąd + title: Tytuł + unfollowed: Przestałeś śledzić sessions: activity: Ostatnia aktywność browser: Przeglądarka From 1e87ed44d551e1ff9ea9e0d7219386ecf15173cb Mon Sep 17 00:00:00 2001 From: Valentin Lorentz Date: Sun, 15 Apr 2018 13:57:58 +0200 Subject: [PATCH 121/381] docker-compose: Only bind ports 3000 and 4000 on localhost. (#7138) --- docker-compose.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 836cb00..8058326 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -40,7 +40,7 @@ services: - external_network - internal_network ports: - - "3000:3000" + - "127.0.0.1:3000:3000" depends_on: - db - redis @@ -60,7 +60,7 @@ services: - external_network - internal_network ports: - - "4000:4000" + - "127.0.0.1:4000:4000" depends_on: - db - redis From fa04945365bc552748b9d312f936a74cfd8d35b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Miko=C5=82ajczak?= Date: Sun, 15 Apr 2018 13:58:47 +0200 Subject: [PATCH 122/381] Change icon for domain blocks (#7139) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Change icon for domain blocks Both domain blocks and user blocks uses the same icon… * Update index.js --- app/javascript/mastodon/features/domain_blocks/index.js | 2 +- app/javascript/mastodon/features/getting_started/index.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/javascript/mastodon/features/domain_blocks/index.js b/app/javascript/mastodon/features/domain_blocks/index.js index b17c47e..3b29e2a 100644 --- a/app/javascript/mastodon/features/domain_blocks/index.js +++ b/app/javascript/mastodon/features/domain_blocks/index.js @@ -52,7 +52,7 @@ export default class Blocks extends ImmutablePureComponent { } return ( - + {domains.map(domain => diff --git a/app/javascript/mastodon/features/getting_started/index.js b/app/javascript/mastodon/features/getting_started/index.js index 1a71cff..053a1ca 100644 --- a/app/javascript/mastodon/features/getting_started/index.js +++ b/app/javascript/mastodon/features/getting_started/index.js @@ -122,7 +122,7 @@ export default class GettingStarted extends ImmutablePureComponent { - +
From 9f5ae7c2e81d898496743e68bc295e42515189aa Mon Sep 17 00:00:00 2001 From: YMHuang Date: Sun, 15 Apr 2018 12:00:32 +0000 Subject: [PATCH 123/381] Add and revise Traditional Chinese (zh-TW) translation for serveral strings (#7002) * Translation: add and revise Traditional Chinese translation for serveral strings * Translation: polish zh-TW locale * Translation: polish zh-TW locale --- app/javascript/mastodon/locales/zh-TW.json | 124 ++++++++++++++--------------- 1 file changed, 62 insertions(+), 62 deletions(-) diff --git a/app/javascript/mastodon/locales/zh-TW.json b/app/javascript/mastodon/locales/zh-TW.json index c792582..3edb3fa 100644 --- a/app/javascript/mastodon/locales/zh-TW.json +++ b/app/javascript/mastodon/locales/zh-TW.json @@ -1,24 +1,24 @@ { "account.block": "封鎖 @{name}", "account.block_domain": "隱藏來自 {domain} 的一切貼文", - "account.blocked": "Blocked", - "account.direct": "Direct message @{name}", + "account.blocked": "已被封鎖的", + "account.direct": "給 @{name} 私人訊息", "account.disclaimer_full": "下列資料不一定完整。", - "account.domain_blocked": "Domain hidden", - "account.edit_profile": "編輯用者資訊", + "account.domain_blocked": "域名隱藏", + "account.edit_profile": "編輯使用者資訊", "account.follow": "關注", - "account.followers": "專注者", + "account.followers": "關注者", "account.follows": "正關注", "account.follows_you": "關注你", - "account.hide_reblogs": "Hide boosts from @{name}", + "account.hide_reblogs": "隱藏來自 @{name} 的轉推", "account.media": "媒體", "account.mention": "提到 @{name}", - "account.moved_to": "{name} has moved to:", + "account.moved_to": "{name} 已經移至:", "account.mute": "消音 @{name}", - "account.mute_notifications": "Mute notifications from @{name}", - "account.muted": "Muted", + "account.mute_notifications": "消音來自 @{name} 的通知", + "account.muted": "消音的", "account.posts": "貼文", - "account.posts_with_replies": "Toots with replies", + "account.posts_with_replies": "貼文及回覆", "account.report": "檢舉 @{name}", "account.requested": "正在等待許可", "account.share": "分享 @{name} 的用者資訊", @@ -27,10 +27,10 @@ "account.unblock_domain": "不再隱藏 {domain}", "account.unfollow": "取消關注", "account.unmute": "不再消音 @{name}", - "account.unmute_notifications": "Unmute notifications from @{name}", + "account.unmute_notifications": "不再對來自 @{name} 的通知消音", "account.view_full_profile": "查看完整資訊", - "alert.unexpected.message": "An unexpected error occurred.", - "alert.unexpected.title": "Oops!", + "alert.unexpected.message": "發生非預期的錯誤。", + "alert.unexpected.title": "哎呀!", "boost_modal.combo": "下次你可以按 {combo} 來跳過", "bundle_column_error.body": "加載本組件出錯。", "bundle_column_error.retry": "重試", @@ -40,11 +40,11 @@ "bundle_modal_error.retry": "重試", "column.blocks": "封鎖的使用者", "column.community": "本地時間軸", - "column.domain_blocks": "Hidden domains", + "column.domain_blocks": "隱藏域名", "column.favourites": "最愛", "column.follow_requests": "關注請求", "column.home": "家", - "column.lists": "Lists", + "column.lists": "名單", "column.mutes": "消音的使用者", "column.notifications": "通知", "column.pins": "置頂貼文", @@ -58,17 +58,17 @@ "column_header.unpin": "取下", "column_subheading.navigation": "瀏覽", "column_subheading.settings": "設定", - "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.", - "compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.", + "compose_form.direct_message_warning": "此則推文只會被所有提到的使用者看見。", + "compose_form.hashtag_warning": "此則推文將不會在任何主題標籤中看見,只有公開的推文可以用主題標籤來搜尋。", "compose_form.lock_disclaimer": "你的帳號沒有{locked}。任何人都可以關注你,看到發給關注者的貼文。", "compose_form.lock_disclaimer.lock": "上鎖", "compose_form.placeholder": "在想些什麼?", "compose_form.publish": "貼掉", "compose_form.publish_loud": "{publish}!", - "compose_form.sensitive.marked": "Media is marked as sensitive", - "compose_form.sensitive.unmarked": "Media is not marked as sensitive", - "compose_form.spoiler.marked": "Text is hidden behind warning", - "compose_form.spoiler.unmarked": "Text is not hidden", + "compose_form.sensitive.marked": "此媒體已被標註為敏感的", + "compose_form.sensitive.unmarked": "此媒體未被標註為敏感的", + "compose_form.spoiler.marked": "文字隱藏在警告後", + "compose_form.spoiler.unmarked": "文字不是隱藏的", "compose_form.spoiler_placeholder": "內容警告", "confirmation_modal.cancel": "取消", "confirmations.block.confirm": "封鎖", @@ -86,17 +86,17 @@ "embed.instructions": "要內嵌此貼文,請將以下代碼貼進你的網站。", "embed.preview": "看上去會變成這樣:", "emoji_button.activity": "活動", - "emoji_button.custom": "Custom", + "emoji_button.custom": "自訂", "emoji_button.flags": "旗幟", - "emoji_button.food": "食物與飲料", + "emoji_button.food": "飲食", "emoji_button.label": "插入表情符號", "emoji_button.nature": "自然", - "emoji_button.not_found": "No emojos!! (╯°□°)╯︵ ┻━┻", + "emoji_button.not_found": "沒有表情符號吼!! (╯°□°)╯︵ ┻━┻", "emoji_button.objects": "物件", "emoji_button.people": "人", - "emoji_button.recent": "Frequently used", + "emoji_button.recent": "常用", "emoji_button.search": "搜尋…", - "emoji_button.search_results": "Search results", + "emoji_button.search_results": "搜尋結果", "emoji_button.symbols": "符號", "emoji_button.travel": "旅遊與地點", "empty_column.community": "本地時間軸是空的。公開寫點什麼吧!", @@ -108,8 +108,8 @@ "empty_column.public": "這裡什麼都沒有!公開寫些什麼,或是關注其他副本的使用者。", "follow_request.authorize": "授權", "follow_request.reject": "拒絕", - "getting_started.appsshort": "Apps", - "getting_started.faq": "FAQ", + "getting_started.appsshort": "應用程式", + "getting_started.faq": "常見問答", "getting_started.heading": "馬上開始", "getting_started.open_source_notice": "Mastodon 是開源軟體。你可以在 GitHub {github} 上做出貢獻或是回報問題。", "getting_started.userguide": "使用者指南", @@ -119,19 +119,19 @@ "home.column_settings.show_reblogs": "顯示轉推", "home.column_settings.show_replies": "顯示回應", "home.settings": "欄位設定", - "keyboard_shortcuts.back": "to navigate back", - "keyboard_shortcuts.boost": "to boost", + "keyboard_shortcuts.back": "回到上一個", + "keyboard_shortcuts.boost": "到轉推", "keyboard_shortcuts.column": "to focus a status in one of the columns", - "keyboard_shortcuts.compose": "to focus the compose textarea", - "keyboard_shortcuts.description": "Description", + "keyboard_shortcuts.compose": "焦點移至撰寫文字區塊", + "keyboard_shortcuts.description": "描述", "keyboard_shortcuts.down": "to move down in the list", "keyboard_shortcuts.enter": "to open status", - "keyboard_shortcuts.favourite": "to favourite", - "keyboard_shortcuts.heading": "Keyboard Shortcuts", - "keyboard_shortcuts.hotkey": "Hotkey", + "keyboard_shortcuts.favourite": "收藏", + "keyboard_shortcuts.heading": "鍵盤快速鍵", + "keyboard_shortcuts.hotkey": "快速鍵", "keyboard_shortcuts.legend": "to display this legend", - "keyboard_shortcuts.mention": "to mention author", - "keyboard_shortcuts.reply": "to reply", + "keyboard_shortcuts.mention": "到提到的作者", + "keyboard_shortcuts.reply": "到回應", "keyboard_shortcuts.search": "to focus search", "keyboard_shortcuts.toot": "to start a brand new toot", "keyboard_shortcuts.unfocus": "to un-focus compose textarea/search", @@ -150,11 +150,11 @@ "loading_indicator.label": "讀取中...", "media_gallery.toggle_visible": "切換可見性", "missing_indicator.label": "找不到", - "missing_indicator.sublabel": "This resource could not be found", - "mute_modal.hide_notifications": "Hide notifications from this user?", + "missing_indicator.sublabel": "找不到此資源", + "mute_modal.hide_notifications": "隱藏來自這個使用者的通知?", "navigation_bar.blocks": "封鎖的使用者", "navigation_bar.community_timeline": "本地時間軸", - "navigation_bar.domain_blocks": "Hidden domains", + "navigation_bar.domain_blocks": "隱藏的域名", "navigation_bar.edit_profile": "編輯用者資訊", "navigation_bar.favourites": "最愛", "navigation_bar.follow_requests": "關注請求", @@ -197,7 +197,7 @@ "onboarding.page_six.github": "Mastodon 是自由的開源軟體。你可以在 {github} 上回報臭蟲、請求新功能或是做出貢獻。", "onboarding.page_six.guidelines": "社群指南", "onboarding.page_six.read_guidelines": "請閱讀 {domain} 的 {guidelines} !", - "onboarding.page_six.various_app": "行動 apps", + "onboarding.page_six.various_app": "行動版應用程式", "onboarding.page_three.profile": "編輯你的大頭貼、自傳和顯示名稱。你也可以在這邊找到其他設定。", "onboarding.page_three.search": "利用搜尋列來找到其他人或是主題標籤,像是 {illustration} 或 {introductions} 。用完整的帳號名稱來找不在這個副本上的使用者。", "onboarding.page_two.compose": "在編輯欄寫些什麼。可以上傳圖片、改變隱私設定或是用下面的圖示加上內容警告。", @@ -211,13 +211,13 @@ "privacy.public.short": "公開貼", "privacy.unlisted.long": "不要貼到公開時間軸", "privacy.unlisted.short": "不列出來", - "regeneration_indicator.label": "Loading…", + "regeneration_indicator.label": "載入中…", "regeneration_indicator.sublabel": "Your home feed is being prepared!", - "relative_time.days": "{number}d", - "relative_time.hours": "{number}h", - "relative_time.just_now": "now", - "relative_time.minutes": "{number}m", - "relative_time.seconds": "{number}s", + "relative_time.days": "{number} 天", + "relative_time.hours": "{number} 時", + "relative_time.just_now": "現在", + "relative_time.minutes": "{number} 分", + "relative_time.seconds": "{number} 秒", "reply_indicator.cancel": "取消", "report.forward": "Forward to {target}", "report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?", @@ -226,18 +226,18 @@ "report.submit": "送出", "report.target": "通報中", "search.placeholder": "搜尋", - "search_popout.search_format": "Advanced search format", + "search_popout.search_format": "進階搜尋格式", "search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.", - "search_popout.tips.hashtag": "hashtag", - "search_popout.tips.status": "status", + "search_popout.tips.hashtag": "主題標籤", + "search_popout.tips.status": "狀態", "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags", - "search_popout.tips.user": "user", - "search_results.accounts": "People", - "search_results.hashtags": "Hashtags", - "search_results.statuses": "Toots", + "search_popout.tips.user": "使用者", + "search_results.accounts": "人", + "search_results.hashtags": "主題標籤", + "search_results.statuses": "推文", "search_results.total": "{count, number} 項結果", "standalone.public_title": "站點一瞥…", - "status.block": "Block @{name}", + "status.block": "封鎖 @{name}", "status.cannot_reblog": "此貼文無法轉推", "status.delete": "刪除", "status.embed": "Embed", @@ -250,7 +250,7 @@ "status.mute_conversation": "消音對話", "status.open": "展開這個狀態", "status.pin": "置頂到個人資訊頁", - "status.pinned": "Pinned toot", + "status.pinned": "置頂的推文", "status.reblog": "轉推", "status.reblogged_by": "{name} 轉推了", "status.reply": "回應", @@ -258,7 +258,7 @@ "status.report": "通報 @{name}", "status.sensitive_toggle": "點來看", "status.sensitive_warning": "敏感內容", - "status.share": "Share", + "status.share": "分享", "status.show_less": "看少點", "status.show_less_all": "Show less for all", "status.show_more": "看更多", @@ -269,17 +269,17 @@ "tabs_bar.home": "家", "tabs_bar.local_timeline": "本地", "tabs_bar.notifications": "通知", - "ui.beforeunload": "Your draft will be lost if you leave Mastodon.", + "ui.beforeunload": "如果離開 Mastodon,你的草稿將會不見。", "upload_area.title": "拖放來上傳", "upload_button.label": "增加媒體", - "upload_form.description": "Describe for the visually impaired", - "upload_form.focus": "Crop", + "upload_form.description": "爲視障者加上描述", + "upload_form.focus": "裁切", "upload_form.undo": "復原", "upload_progress.label": "上傳中...", "video.close": "關閉影片", - "video.exit_fullscreen": "退出全熒幕", + "video.exit_fullscreen": "退出全螢幕", "video.expand": "展開影片", - "video.fullscreen": "全熒幕", + "video.fullscreen": "全螢幕", "video.hide": "隱藏影片", "video.mute": "消音", "video.pause": "暫停", From 7c43ed04fec0603073fe0b5a687992cab684a3b5 Mon Sep 17 00:00:00 2001 From: Yamagishi Kazutoshi Date: Sun, 15 Apr 2018 23:56:03 +0900 Subject: [PATCH 124/381] Weblate translations (2018-04-15) (#7141) * Translated using Weblate (Galician) Currently translated at 100.0% (587 of 587 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/gl/ * Translated using Weblate (Dutch) Currently translated at 100.0% (587 of 587 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/nl/ * Translated using Weblate (Catalan) Currently translated at 100.0% (587 of 587 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/ca/ * Translated using Weblate (Arabic) Currently translated at 76.4% (449 of 587 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/ar/ * Translated using Weblate (Japanese) Currently translated at 99.8% (586 of 587 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/ja/ * Translated using Weblate (Slovak) Currently translated at 92.3% (542 of 587 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/sk/ * Translated using Weblate (Slovak) Currently translated at 92.3% (542 of 587 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/sk/ * Translated using Weblate (Slovak) Currently translated at 100.0% (58 of 58 strings) Translation: Mastodon/Preferences Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/simple_form/sk/ * Translated using Weblate (Polish) Currently translated at 98.9% (581 of 587 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/pl/ * Translated using Weblate (French) Currently translated at 99.6% (585 of 587 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/fr/ * Translated using Weblate (Portuguese (Brazil)) Currently translated at 99.8% (586 of 587 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/pt_BR/ * Translated using Weblate (Catalan) Currently translated at 100.0% (280 of 280 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/ca/ * Translated using Weblate (Persian) Currently translated at 100.0% (75 of 75 strings) Translation: Mastodon/Doorkeeper Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/doorkeeper/fa/ * Translated using Weblate (Persian) Currently translated at 100.0% (280 of 280 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/fa/ * Translated using Weblate (French) Currently translated at 100.0% (280 of 280 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/fr/ * Translated using Weblate (Japanese) Currently translated at 99.8% (586 of 587 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/ja/ * Translated using Weblate (Japanese) Currently translated at 99.8% (586 of 587 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/ja/ * Translated using Weblate (Slovak) Currently translated at 100.0% (280 of 280 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/sk/ * Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (280 of 280 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/zh_Hant/ * Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (62 of 62 strings) Translation: Mastodon/Devise Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/devise/zh_Hant/ * Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (75 of 75 strings) Translation: Mastodon/Doorkeeper Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/doorkeeper/zh_Hant/ * Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (62 of 62 strings) Translation: Mastodon/Devise Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/devise/zh_Hant/ * Translated using Weblate (Esperanto) Currently translated at 100.0% (587 of 587 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/eo/ * Translated using Weblate (Esperanto) Currently translated at 100.0% (280 of 280 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/eo/ * Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (75 of 75 strings) Translation: Mastodon/Doorkeeper Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/doorkeeper/zh_Hant/ * Translated using Weblate (Arabic) Currently translated at 76.6% (450 of 587 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/ar/ * Translated using Weblate (Esperanto) Currently translated at 100.0% (587 of 587 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/eo/ * Translated using Weblate (Esperanto) Currently translated at 100.0% (280 of 280 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/eo/ * Translated using Weblate (Slovak) Currently translated at 92.6% (544 of 587 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/sk/ * Translated using Weblate (Slovak) Currently translated at 100.0% (75 of 75 strings) Translation: Mastodon/Doorkeeper Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/doorkeeper/sk/ * Translated using Weblate (Arabic) Currently translated at 82.9% (487 of 587 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/ar/ * Translated using Weblate (Arabic) Currently translated at 98.6% (74 of 75 strings) Translation: Mastodon/Doorkeeper Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/doorkeeper/ar/ * Translated using Weblate (Slovak) Currently translated at 93.6% (550 of 587 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/sk/ * Translated using Weblate (Slovak) Currently translated at 95.4% (560 of 587 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/sk/ * Translated using Weblate (Japanese) Currently translated at 100.0% (280 of 280 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/ja/ * Translated using Weblate (Indonesian) Currently translated at 100.0% (75 of 75 strings) Translation: Mastodon/Doorkeeper Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/doorkeeper/id/ * Translated using Weblate (Korean) Currently translated at 100.0% (58 of 58 strings) Translation: Mastodon/Preferences Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/simple_form/ko/ * Translated using Weblate (Korean) Currently translated at 99.8% (586 of 587 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/ko/ * Translated using Weblate (Korean) Currently translated at 100.0% (280 of 280 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/ko/ * Translated using Weblate (Esperanto) Currently translated at 100.0% (280 of 280 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/eo/ * Translated using Weblate (Arabic) Currently translated at 82.9% (487 of 587 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/ar/ * Translated using Weblate (Arabic) Currently translated at 99.2% (278 of 280 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/ar/ * Translated using Weblate (Arabic) Currently translated at 87.3% (513 of 587 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/ar/ * Translated using Weblate (Arabic) Currently translated at 99.6% (279 of 280 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/ar/ * Translated using Weblate (Esperanto) Currently translated at 100.0% (280 of 280 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/eo/ * Translated using Weblate (Slovak) Currently translated at 100.0% (280 of 280 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/sk/ * Translated using Weblate (Slovak) Currently translated at 100.0% (280 of 280 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/sk/ * Translated using Weblate (Slovak) Currently translated at 100.0% (75 of 75 strings) Translation: Mastodon/Doorkeeper Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/doorkeeper/sk/ * Translated using Weblate (Slovak) Currently translated at 100.0% (58 of 58 strings) Translation: Mastodon/Preferences Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/simple_form/sk/ * Translated using Weblate (Finnish) Currently translated at 100.0% (58 of 58 strings) Translation: Mastodon/Preferences Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/simple_form/fi/ * Translated using Weblate (Arabic) Currently translated at 88.0% (517 of 587 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/ar/ * Translated using Weblate (Finnish) Currently translated at 100.0% (280 of 280 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/fi/ * Translated using Weblate (Japanese) Currently translated at 100.0% (280 of 280 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/ja/ * Translated using Weblate (Spanish) Currently translated at 100.0% (587 of 587 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/es/ * Translated using Weblate (Slovak) Currently translated at 100.0% (280 of 280 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/sk/ * Translated using Weblate (Finnish) Currently translated at 100.0% (75 of 75 strings) Translation: Mastodon/Doorkeeper Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/doorkeeper/fi/ * Translated using Weblate (Esperanto) Currently translated at 100.0% (587 of 587 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/eo/ * Translated using Weblate (Slovak) Currently translated at 95.4% (560 of 587 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/sk/ * Translated using Weblate (Esperanto) Currently translated at 100.0% (280 of 280 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/eo/ * Translated using Weblate (Slovak) Currently translated at 100.0% (280 of 280 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/sk/ * Translated using Weblate (Slovak) Currently translated at 100.0% (62 of 62 strings) Translation: Mastodon/Devise Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/devise/sk/ * Translated using Weblate (Finnish) Currently translated at 100.0% (75 of 75 strings) Translation: Mastodon/Doorkeeper Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/doorkeeper/fi/ * Translated using Weblate (Esperanto) Currently translated at 100.0% (58 of 58 strings) Translation: Mastodon/Preferences Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/simple_form/eo/ * Translated using Weblate (Slovak) Currently translated at 100.0% (58 of 58 strings) Translation: Mastodon/Preferences Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/simple_form/sk/ * Translated using Weblate (Esperanto) Currently translated at 100.0% (280 of 280 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/eo/ * Translated using Weblate (Finnish) Currently translated at 100.0% (62 of 62 strings) Translation: Mastodon/Devise Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/devise/fi/ * Translated using Weblate (Finnish) Currently translated at 100.0% (62 of 62 strings) Translation: Mastodon/Devise Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/devise/fi/ * Translated using Weblate (Finnish) Currently translated at 100.0% (280 of 280 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/fi/ * Translated using Weblate (Finnish) Currently translated at 100.0% (58 of 58 strings) Translation: Mastodon/Preferences Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/simple_form/fi/ * Translated using Weblate (Finnish) Currently translated at 25.8% (152 of 587 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/fi/ * Translated using Weblate (Finnish) Currently translated at 25.8% (152 of 587 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/fi/ * Translated using Weblate (Japanese) Currently translated at 99.8% (586 of 587 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/ja/ * Translated using Weblate (Finnish) Currently translated at 33.0% (194 of 587 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/fi/ * Translated using Weblate (Esperanto) Currently translated at 100.0% (280 of 280 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/eo/ * Translated using Weblate (Finnish) Currently translated at 99.8% (586 of 587 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/fi/ * Translated using Weblate (Slovak) Currently translated at 95.5% (561 of 587 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/sk/ * Translated using Weblate (Slovak) Currently translated at 100.0% (280 of 280 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/sk/ * Translated using Weblate (Esperanto) Currently translated at 100.0% (587 of 587 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/eo/ * Translated using Weblate (Galician) Currently translated at 100.0% (280 of 280 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/gl/ * yarn manage:translations --- app/javascript/mastodon/locales/ar.json | 2 + app/javascript/mastodon/locales/bg.json | 2 + app/javascript/mastodon/locales/ca.json | 2 + app/javascript/mastodon/locales/de.json | 2 + app/javascript/mastodon/locales/eo.json | 14 +- app/javascript/mastodon/locales/es.json | 2 + app/javascript/mastodon/locales/fa.json | 2 + app/javascript/mastodon/locales/fi.json | 228 ++++----- app/javascript/mastodon/locales/fr.json | 2 + app/javascript/mastodon/locales/gl.json | 6 +- app/javascript/mastodon/locales/he.json | 2 + app/javascript/mastodon/locales/hr.json | 2 + app/javascript/mastodon/locales/hu.json | 2 + app/javascript/mastodon/locales/hy.json | 2 + app/javascript/mastodon/locales/id.json | 2 + app/javascript/mastodon/locales/io.json | 2 + app/javascript/mastodon/locales/it.json | 2 + app/javascript/mastodon/locales/ja.json | 2 +- app/javascript/mastodon/locales/ko.json | 2 + app/javascript/mastodon/locales/nl.json | 2 + app/javascript/mastodon/locales/no.json | 2 + app/javascript/mastodon/locales/oc.json | 2 + app/javascript/mastodon/locales/pt-BR.json | 2 + app/javascript/mastodon/locales/pt.json | 2 + app/javascript/mastodon/locales/ru.json | 2 + app/javascript/mastodon/locales/sk.json | 28 +- app/javascript/mastodon/locales/sr-Latn.json | 2 + app/javascript/mastodon/locales/sr.json | 2 + app/javascript/mastodon/locales/sv.json | 2 + app/javascript/mastodon/locales/th.json | 2 + app/javascript/mastodon/locales/tr.json | 2 + app/javascript/mastodon/locales/uk.json | 2 + app/javascript/mastodon/locales/zh-CN.json | 2 + app/javascript/mastodon/locales/zh-HK.json | 2 + app/javascript/mastodon/locales/zh-TW.json | 2 + config/locales/ar.yml | 4 + config/locales/devise.fi.yml | 91 ++-- config/locales/devise.sk.yml | 10 +- config/locales/doorkeeper.fi.yml | 108 +++-- config/locales/doorkeeper.sk.yml | 2 +- config/locales/eo.yml | 6 +- config/locales/es.yml | 13 +- config/locales/fi.yml | 698 +++++++++++++++++++++++---- config/locales/ja.yml | 2 +- config/locales/simple_form.eo.yml | 4 +- config/locales/simple_form.fi.yml | 66 +-- config/locales/simple_form.sk.yml | 16 +- config/locales/sk.yml | 27 +- 48 files changed, 1011 insertions(+), 374 deletions(-) diff --git a/app/javascript/mastodon/locales/ar.json b/app/javascript/mastodon/locales/ar.json index 34e3441..c13ff77 100644 --- a/app/javascript/mastodon/locales/ar.json +++ b/app/javascript/mastodon/locales/ar.json @@ -240,6 +240,7 @@ "status.block": "Block @{name}", "status.cannot_reblog": "تعذرت ترقية هذا المنشور", "status.delete": "إحذف", + "status.direct": "Direct message @{name}", "status.embed": "إدماج", "status.favourite": "أضف إلى المفضلة", "status.load_more": "حمّل المزيد", @@ -269,6 +270,7 @@ "tabs_bar.home": "الرئيسية", "tabs_bar.local_timeline": "المحلي", "tabs_bar.notifications": "الإخطارات", + "tabs_bar.search": "Search", "ui.beforeunload": "سوف تفقد مسودتك إن تركت ماستدون.", "upload_area.title": "إسحب ثم أفلت للرفع", "upload_button.label": "إضافة وسائط", diff --git a/app/javascript/mastodon/locales/bg.json b/app/javascript/mastodon/locales/bg.json index 9aaff0d..981aced 100644 --- a/app/javascript/mastodon/locales/bg.json +++ b/app/javascript/mastodon/locales/bg.json @@ -240,6 +240,7 @@ "status.block": "Block @{name}", "status.cannot_reblog": "This post cannot be boosted", "status.delete": "Изтриване", + "status.direct": "Direct message @{name}", "status.embed": "Embed", "status.favourite": "Предпочитани", "status.load_more": "Load more", @@ -269,6 +270,7 @@ "tabs_bar.home": "Начало", "tabs_bar.local_timeline": "Local", "tabs_bar.notifications": "Известия", + "tabs_bar.search": "Search", "ui.beforeunload": "Your draft will be lost if you leave Mastodon.", "upload_area.title": "Drag & drop to upload", "upload_button.label": "Добави медия", diff --git a/app/javascript/mastodon/locales/ca.json b/app/javascript/mastodon/locales/ca.json index b7f95a6..d9270e0 100644 --- a/app/javascript/mastodon/locales/ca.json +++ b/app/javascript/mastodon/locales/ca.json @@ -240,6 +240,7 @@ "status.block": "Block @{name}", "status.cannot_reblog": "Aquesta publicació no pot ser retootejada", "status.delete": "Esborrar", + "status.direct": "Direct message @{name}", "status.embed": "Incrustar", "status.favourite": "Favorit", "status.load_more": "Carrega més", @@ -269,6 +270,7 @@ "tabs_bar.home": "Inici", "tabs_bar.local_timeline": "Local", "tabs_bar.notifications": "Notificacions", + "tabs_bar.search": "Search", "ui.beforeunload": "El vostre esborrany es perdrà si sortiu de Mastodon.", "upload_area.title": "Arrossega i deixa anar per carregar", "upload_button.label": "Afegir multimèdia", diff --git a/app/javascript/mastodon/locales/de.json b/app/javascript/mastodon/locales/de.json index a618b85..6eb5e68 100644 --- a/app/javascript/mastodon/locales/de.json +++ b/app/javascript/mastodon/locales/de.json @@ -240,6 +240,7 @@ "status.block": "Block @{name}", "status.cannot_reblog": "Dieser Beitrag kann nicht geteilt werden", "status.delete": "Löschen", + "status.direct": "Direct message @{name}", "status.embed": "Einbetten", "status.favourite": "Favorisieren", "status.load_more": "Weitere laden", @@ -269,6 +270,7 @@ "tabs_bar.home": "Startseite", "tabs_bar.local_timeline": "Lokal", "tabs_bar.notifications": "Mitteilungen", + "tabs_bar.search": "Search", "ui.beforeunload": "Your draft will be lost if you leave Mastodon.", "upload_area.title": "Zum Hochladen hereinziehen", "upload_button.label": "Mediendatei hinzufügen", diff --git a/app/javascript/mastodon/locales/eo.json b/app/javascript/mastodon/locales/eo.json index 6dee6e5..19f3c59 100644 --- a/app/javascript/mastodon/locales/eo.json +++ b/app/javascript/mastodon/locales/eo.json @@ -60,7 +60,7 @@ "column_subheading.settings": "Agordado", "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.", "compose_form.hashtag_warning": "Ĉi tiu mesaĝo ne estos listigita per ajna kradvorto. Nur publikaj mesaĝoj estas serĉeblaj per kradvortoj.", - "compose_form.lock_disclaimer": "Via konta ne estas {locked}. Iu ajn povas sekvi vin por vidi viajn mesaĝojn nur por sekvantoj.", + "compose_form.lock_disclaimer": "Via konta ne estas {locked}. Iu ajn povas sekvi vin por vidi viajn mesaĝojn, kiuj estas nur por sekvantoj.", "compose_form.lock_disclaimer.lock": "ŝlosita", "compose_form.placeholder": "Pri kio vi pensas?", "compose_form.publish": "Hup", @@ -102,7 +102,7 @@ "empty_column.community": "La loka tempolinio estas malplena. Skribu ion por plenigi ĝin!", "empty_column.hashtag": "Ankoraŭ estas nenio per ĉi tiu kradvorto.", "empty_column.home": "Via hejma tempolinio estas malplena! Vizitu {public} aŭ uzu la serĉilon por renkonti aliajn uzantojn.", - "empty_column.home.public_timeline": "la publika tempolinio", + "empty_column.home.public_timeline": "la publikan tempolinion", "empty_column.list": "Ankoraŭ estas nenio en ĉi tiu listo. Kiam membroj de ĉi tiu listo afiŝos novajn mesaĝojn, ili aperos ĉi tie.", "empty_column.notifications": "Vi ankoraŭ ne havas sciigojn. Interagu kun aliaj por komenci konversacion.", "empty_column.public": "Estas nenio ĉi tie! Publike skribu ion, aŭ mane sekvu uzantojn de aliaj nodoj por plenigi la publikan tempolinion", @@ -150,8 +150,8 @@ "loading_indicator.label": "Ŝargado…", "media_gallery.toggle_visible": "Baskuligi videblecon", "missing_indicator.label": "Ne trovita", - "missing_indicator.sublabel": "Ĉi tiu rimedo ne estis trovita", - "mute_modal.hide_notifications": "Ĉu kaŝi sciigojn el ĉi tiu uzanto?", + "missing_indicator.sublabel": "Ĉi tiu elemento ne estis trovita", + "mute_modal.hide_notifications": "Ĉu vi volas kaŝi la sciigojn el ĉi tiu uzanto?", "navigation_bar.blocks": "Blokitaj uzantoj", "navigation_bar.community_timeline": "Loka tempolinio", "navigation_bar.domain_blocks": "Hidden domains", @@ -240,6 +240,7 @@ "status.block": "Bloki @{name}", "status.cannot_reblog": "Ĉi tiu mesaĝo ne diskonigeblas", "status.delete": "Forigi", + "status.direct": "Direct message @{name}", "status.embed": "Enkorpigi", "status.favourite": "Stelumi", "status.load_more": "Ŝargi pli", @@ -248,8 +249,8 @@ "status.more": "Pli", "status.mute": "Silentigi @{name}", "status.mute_conversation": "Silentigi konversacion", - "status.open": "Grandigi ĉi tiun mesaĝon", - "status.pin": "Alpingli en la profilo", + "status.open": "Grandigi", + "status.pin": "Alpingli profile", "status.pinned": "Alpinglita mesaĝo", "status.reblog": "Diskonigi", "status.reblogged_by": "{name} diskonigis", @@ -269,6 +270,7 @@ "tabs_bar.home": "Hejmo", "tabs_bar.local_timeline": "Loka tempolinio", "tabs_bar.notifications": "Sciigoj", + "tabs_bar.search": "Search", "ui.beforeunload": "Via malneto perdiĝos se vi eliras de Mastodon.", "upload_area.title": "Altreni kaj lasi por alŝuti", "upload_button.label": "Aldoni aŭdovidaĵon", diff --git a/app/javascript/mastodon/locales/es.json b/app/javascript/mastodon/locales/es.json index 6f9c06c..e765cc0 100644 --- a/app/javascript/mastodon/locales/es.json +++ b/app/javascript/mastodon/locales/es.json @@ -240,6 +240,7 @@ "status.block": "Block @{name}", "status.cannot_reblog": "Este toot no puede retootearse", "status.delete": "Borrar", + "status.direct": "Direct message @{name}", "status.embed": "Incrustado", "status.favourite": "Favorito", "status.load_more": "Cargar más", @@ -269,6 +270,7 @@ "tabs_bar.home": "Inicio", "tabs_bar.local_timeline": "Local", "tabs_bar.notifications": "Notificaciones", + "tabs_bar.search": "Search", "ui.beforeunload": "Tu borrador se perderá si sales de Mastodon.", "upload_area.title": "Arrastra y suelta para subir", "upload_button.label": "Subir multimedia", diff --git a/app/javascript/mastodon/locales/fa.json b/app/javascript/mastodon/locales/fa.json index 61cdcd0..822c998 100644 --- a/app/javascript/mastodon/locales/fa.json +++ b/app/javascript/mastodon/locales/fa.json @@ -240,6 +240,7 @@ "status.block": "Block @{name}", "status.cannot_reblog": "این نوشته را نمی‌شود بازبوقید", "status.delete": "پاک‌کردن", + "status.direct": "Direct message @{name}", "status.embed": "جاگذاری", "status.favourite": "پسندیدن", "status.load_more": "بیشتر نشان بده", @@ -269,6 +270,7 @@ "tabs_bar.home": "خانه", "tabs_bar.local_timeline": "محلی", "tabs_bar.notifications": "اعلان‌ها", + "tabs_bar.search": "Search", "ui.beforeunload": "اگر از ماستدون خارج شوید پیش‌نویس شما پاک خواهد شد.", "upload_area.title": "برای بارگذاری به این‌جا بکشید", "upload_button.label": "افزودن تصویر", diff --git a/app/javascript/mastodon/locales/fi.json b/app/javascript/mastodon/locales/fi.json index f4be805..5763ac4 100644 --- a/app/javascript/mastodon/locales/fi.json +++ b/app/javascript/mastodon/locales/fi.json @@ -2,7 +2,7 @@ "account.block": "Estä @{name}", "account.block_domain": "Piilota kaikki sisältö verkkotunnuksesta {domain}", "account.blocked": "Estetty", - "account.direct": "Direct Message @{name}", + "account.direct": "Direct message @{name}", "account.disclaimer_full": "Alla olevat käyttäjän profiilitiedot saattavat olla epätäydellisiä.", "account.domain_blocked": "Verkko-osoite piilotettu", "account.edit_profile": "Muokkaa", @@ -17,37 +17,37 @@ "account.mute": "Mykistä @{name}", "account.mute_notifications": "Mykistä ilmoitukset käyttäjältä @{name}", "account.muted": "Mykistetty", - "account.posts": "Töötit", - "account.posts_with_replies": "Töötit ja vastaukset", - "account.report": "Report @{name}", - "account.requested": "Odottaa hyväksyntää. Klikkaa peruuttaaksesi seurauspyynnön", + "account.posts": "Tuuttaukset", + "account.posts_with_replies": "Tuuttaukset ja vastaukset", + "account.report": "Raportoi @{name}", + "account.requested": "Odottaa hyväksyntää. Peruuta seuraamispyyntö klikkaamalla", "account.share": "Jaa käyttäjän @{name} profiili", - "account.show_reblogs": "Näytä boostaukset käyttäjältä @{name}", + "account.show_reblogs": "Näytä buustaukset käyttäjältä @{name}", "account.unblock": "Salli @{name}", "account.unblock_domain": "Näytä {domain}", "account.unfollow": "Lakkaa seuraamasta", - "account.unmute": "Poista mykistys käyttäjältä @{name}", + "account.unmute": "Poista käyttäjän @{name} mykistys", "account.unmute_notifications": "Poista mykistys käyttäjän @{name} ilmoituksilta", "account.view_full_profile": "Näytä koko profiili", "alert.unexpected.message": "An unexpected error occurred.", "alert.unexpected.title": "Oops!", - "boost_modal.combo": "Voit painaa näppäimiä {combo} ohittaaksesi tämän ensi kerralla", - "bundle_column_error.body": "Jokin meni vikaan tätä komponenttia ladatessa.", + "boost_modal.combo": "Ensi kerralla voit ohittaa tämän painamalla {combo}", + "bundle_column_error.body": "Jokin meni vikaan komponenttia ladattaessa.", "bundle_column_error.retry": "Yritä uudestaan", - "bundle_column_error.title": "Network error", + "bundle_column_error.title": "Verkkovirhe", "bundle_modal_error.close": "Sulje", - "bundle_modal_error.message": "Jokin meni vikaan tätä komponenttia ladatessa.", + "bundle_modal_error.message": "Jokin meni vikaan komponenttia ladattaessa.", "bundle_modal_error.retry": "Yritä uudestaan", "column.blocks": "Estetyt käyttäjät", "column.community": "Paikallinen aikajana", "column.domain_blocks": "Hidden domains", "column.favourites": "Suosikit", - "column.follow_requests": "Seurauspyynnöt", + "column.follow_requests": "Seuraamispyynnöt", "column.home": "Koti", "column.lists": "Listat", "column.mutes": "Mykistetyt käyttäjät", "column.notifications": "Ilmoitukset", - "column.pins": "Pinned toot", + "column.pins": "Kiinnitetty tuuttaus", "column.public": "Yleinen aikajana", "column_back_button.label": "Takaisin", "column_header.hide_settings": "Piilota asetukset", @@ -59,32 +59,32 @@ "column_subheading.navigation": "Navigaatio", "column_subheading.settings": "Asetukset", "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.", - "compose_form.hashtag_warning": "Tämä töötti ei tule näkymään hashtag-hauissa, koska se ei näy julkisilla aikajanoilla. Vain julkisia tööttejä voi hakea hashtageilla.", - "compose_form.lock_disclaimer": "Tilisi ei ole {locked}. Kuka tahansa voi seurata tiliäsi ja nähdä vain seuraajille -postauksesi.", + "compose_form.hashtag_warning": "Tämä tuuttaus ei näy hashtag-hauissa, koska se on listaamaton. Hashtagien avulla voi hakea vain julkisia tuuttauksia.", + "compose_form.lock_disclaimer": "Tilisi ei ole {locked}. Kuka tahansa voi seurata tiliäsi ja nähdä vain seuraajille rajaamasi julkaisut.", "compose_form.lock_disclaimer.lock": "lukittu", - "compose_form.placeholder": "Mitä sinulla on mielessä?", - "compose_form.publish": "Toot", + "compose_form.placeholder": "Mitä mietit?", + "compose_form.publish": "Tuuttaa", "compose_form.publish_loud": "{publish}!", "compose_form.sensitive.marked": "Media on merkitty arkaluontoiseksi", "compose_form.sensitive.unmarked": "Mediaa ei ole merkitty arkaluontoiseksi", "compose_form.spoiler.marked": "Teksti on piilotettu varoituksen taakse", "compose_form.spoiler.unmarked": "Teksti ei ole piilotettu", - "compose_form.spoiler_placeholder": "Content warning", + "compose_form.spoiler_placeholder": "Sisältövaroitus", "confirmation_modal.cancel": "Peruuta", "confirmations.block.confirm": "Estä", - "confirmations.block.message": "Oletko varma, että haluat estää käyttäjän {name}?", - "confirmations.delete.confirm": "Delete", - "confirmations.delete.message": "Oletko varma, että haluat poistaa tämän statuspäivityksen?", - "confirmations.delete_list.confirm": "Delete", - "confirmations.delete_list.message": "Oletko varma, että haluat poistaa tämän listan pysyvästi?", + "confirmations.block.message": "Haluatko varmasti estää käyttäjän {name}?", + "confirmations.delete.confirm": "Poista", + "confirmations.delete.message": "Haluatko varmasti poistaa tämän tilapäivityksen?", + "confirmations.delete_list.confirm": "Poista", + "confirmations.delete_list.message": "Haluatko varmasti poistaa tämän listan kokonaan?", "confirmations.domain_block.confirm": "Piilota koko verkko-osoite", - "confirmations.domain_block.message": "Oletko aivan oikeasti varma että haluat estää koko verkko-osoitteen {domain}? Useimmissa tapauksissa muutamat kohdistetut estot ja mykistykset ovat riittäviä ja suositeltavampia.", + "confirmations.domain_block.message": "Haluatko aivan varmasti estää koko verkko-osoitteen {domain}? Useimmiten jokunen kohdistettu esto ja mykistys riittää, ja se on suositeltavampi tapa toimia.", "confirmations.mute.confirm": "Mykistä", - "confirmations.mute.message": "Oletko varma että haluat mykistää käyttäjän {name}?", + "confirmations.mute.message": "Haluatko varmasti mykistää käyttäjän {name}?", "confirmations.unfollow.confirm": "Lakkaa seuraamasta", - "confirmations.unfollow.message": "Oletko varma, että haluat lakata seuraamasta käyttäjää {name}?", - "embed.instructions": "Upota tämä statuspäivitys sivullesi kopioimalla alla oleva koodi.", - "embed.preview": "Tältä se tulee näyttämään:", + "confirmations.unfollow.message": "Haluatko varmasti lakata seuraamasta käyttäjää {name}?", + "embed.instructions": "Upota statuspäivitys sivullesi kopioimalla alla oleva koodi.", + "embed.preview": "Se tulee näyttämään tältä:", "emoji_button.activity": "Aktiviteetit", "emoji_button.custom": "Mukautetut", "emoji_button.flags": "Liput", @@ -92,154 +92,155 @@ "emoji_button.label": "Lisää emoji", "emoji_button.nature": "Luonto", "emoji_button.not_found": "Ei emojeja!! (╯°□°)╯︵ ┻━┻", - "emoji_button.objects": "Objektit", + "emoji_button.objects": "Esineet", "emoji_button.people": "Ihmiset", "emoji_button.recent": "Usein käytetyt", "emoji_button.search": "Etsi...", "emoji_button.search_results": "Hakutulokset", "emoji_button.symbols": "Symbolit", "emoji_button.travel": "Matkailu", - "empty_column.community": "Paikallinen aikajana on tyhjä. Kirjoita jotain julkista saadaksesi pyörät pyörimään!", - "empty_column.hashtag": "Tässä hashtagissa ei ole vielä mitään.", - "empty_column.home": "Kotiaikajanasi on tyhjä! Käy vierailemassa {public}ssa tai käytä hakutoimintoa aloittaaksesi ja tavataksesi muita käyttäjiä.", + "empty_column.community": "Paikallinen aikajana on tyhjä. Homma lähtee käyntiin, kun kirjoitat jotain julkista!", + "empty_column.hashtag": "Tällä hashtagilla ei ole vielä mitään.", + "empty_column.home": "Kotiaikajanasi on tyhjä! {public} ja hakutoiminto auttavat alkuun ja kohtaamaan muita käyttäjiä.", "empty_column.home.public_timeline": "yleinen aikajana", - "empty_column.list": "Tämä lista on vielä tyhjä. Kun listan jäsenet julkaisevat statuspäivityksiä, ne näkyvät tässä.", - "empty_column.notifications": "Sinulle ei ole vielä ilmoituksia. Juttele muille aloittaaksesi keskustelun.", - "empty_column.public": "Täällä ei ole mitään! Kirjoita jotain julkisesti, tai käy manuaalisesti seuraamassa käyttäjiä muista instansseista saadaksesi sisältöä", + "empty_column.list": "Lista on vielä tyhjä. Listan jäsenten julkaisemat tilapäivitykset tulevat tähän näkyviin.", + "empty_column.notifications": "Sinulle ei ole vielä ilmoituksia. Aloita keskustelu juttelemalla muille.", + "empty_column.public": "Täällä ei ole mitään! Saat sisältöä, kun kirjoitat jotain julkisesti tai käyt manuaalisesti seuraamassa muiden instanssien käyttäjiä", "follow_request.authorize": "Valtuuta", "follow_request.reject": "Hylkää", "getting_started.appsshort": "Sovellukset", "getting_started.faq": "FAQ", "getting_started.heading": "Aloitus", - "getting_started.open_source_notice": "Mastodon on avoimen lähdekoodin ohjelma. Voit avustaa tai raportoida ongelmia GitHub palvelussa {github}.", + "getting_started.open_source_notice": "Mastodon on avoimen lähdekoodin ohjelma. Voit avustaa tai raportoida ongelmia GitHubissa: {github}.", "getting_started.userguide": "Käyttöopas", - "home.column_settings.advanced": "Tarkemmat asetukset", + "home.column_settings.advanced": "Lisäasetukset", "home.column_settings.basic": "Perusasetukset", - "home.column_settings.filter_regex": "Suodata säännöllisten lauseiden avulla", + "home.column_settings.filter_regex": "Suodata säännöllisillä lausekkeilla", "home.column_settings.show_reblogs": "Näytä buustaukset", "home.column_settings.show_replies": "Näytä vastaukset", "home.settings": "Sarakeasetukset", - "keyboard_shortcuts.back": "liikkuaksesi taaksepäin", - "keyboard_shortcuts.boost": "buustataksesi", - "keyboard_shortcuts.column": "keskittääksesi statuspäivitykseen yhdessä sarakkeista", - "keyboard_shortcuts.compose": "aktivoidaksesi tekstinkirjoitusalueen", - "keyboard_shortcuts.description": "Description", - "keyboard_shortcuts.down": "liikkuaksesi listassa alaspäin", - "keyboard_shortcuts.enter": "to open status", - "keyboard_shortcuts.favourite": "tykätäksesi", - "keyboard_shortcuts.heading": "Näppäinoikotiet", + "keyboard_shortcuts.back": "liiku taaksepäin", + "keyboard_shortcuts.boost": "buustaa", + "keyboard_shortcuts.column": "siirrä fokus tietyn sarakkeen tilapäivitykseen", + "keyboard_shortcuts.compose": "siirry tekstinsyöttöön", + "keyboard_shortcuts.description": "Kuvaus", + "keyboard_shortcuts.down": "siirry listassa alaspäin", + "keyboard_shortcuts.enter": "avaa tilapäivitys", + "keyboard_shortcuts.favourite": "tykkää", + "keyboard_shortcuts.heading": "Näppäinkomennot", "keyboard_shortcuts.hotkey": "Pikanäppäin", - "keyboard_shortcuts.legend": "näyttääksesi tämän selitteen", - "keyboard_shortcuts.mention": "mainitaksesi julkaisijan", - "keyboard_shortcuts.reply": "vastataksesi", - "keyboard_shortcuts.search": "aktivoidaksesi hakukentän", - "keyboard_shortcuts.toot": "aloittaaksesi uuden töötin kirjoittamisen", - "keyboard_shortcuts.unfocus": "poistaaksesi aktivoinnin tekstikentästä/hakukentästä", - "keyboard_shortcuts.up": "liikkuaksesi listassa ylöspäin", + "keyboard_shortcuts.legend": "näytä tämä selite", + "keyboard_shortcuts.mention": "mainitse julkaisija", + "keyboard_shortcuts.reply": "vastaa", + "keyboard_shortcuts.search": "siirry hakukenttään", + "keyboard_shortcuts.toot": "ala kirjoittaa uutta tuuttausta", + "keyboard_shortcuts.unfocus": "siirry pois tekstikentästä tai hakukentästä", + "keyboard_shortcuts.up": "siirry listassa ylöspäin", "lightbox.close": "Sulje", "lightbox.next": "Seuraava", "lightbox.previous": "Edellinen", "lists.account.add": "Lisää listaan", - "lists.account.remove": "Poista listalta", - "lists.delete": "Delete list", + "lists.account.remove": "Poista listasta", + "lists.delete": "Poista lista", "lists.edit": "Muokkaa listaa", "lists.new.create": "Lisää lista", - "lists.new.title_placeholder": "Uuden listan otsikko", - "lists.search": "Etsi seuraamiesi henkilöiden joukosta", + "lists.new.title_placeholder": "Uuden listan nimi", + "lists.search": "Etsi seuraamistasi henkilöistä", "lists.subheading": "Omat listat", "loading_indicator.label": "Ladataan...", "media_gallery.toggle_visible": "Säädä näkyvyyttä", - "missing_indicator.label": "Ei löydetty", + "missing_indicator.label": "Ei löytynyt", "missing_indicator.sublabel": "Tätä resurssia ei löytynyt", - "mute_modal.hide_notifications": "Piilota ilmoitukset tältä käyttäjältä?", + "mute_modal.hide_notifications": "Piilota tältä käyttäjältä tulevat ilmoitukset?", "navigation_bar.blocks": "Estetyt käyttäjät", "navigation_bar.community_timeline": "Paikallinen aikajana", "navigation_bar.domain_blocks": "Hidden domains", "navigation_bar.edit_profile": "Muokkaa profiilia", "navigation_bar.favourites": "Suosikit", - "navigation_bar.follow_requests": "Seurauspyynnöt", + "navigation_bar.follow_requests": "Seuraamispyynnöt", "navigation_bar.info": "Tietoa tästä instanssista", - "navigation_bar.keyboard_shortcuts": "Näppäinoikotiet", + "navigation_bar.keyboard_shortcuts": "Näppäinkomennot", "navigation_bar.lists": "Listat", "navigation_bar.logout": "Kirjaudu ulos", "navigation_bar.mutes": "Mykistetyt käyttäjät", - "navigation_bar.pins": "Kiinnitetyt töötit", - "navigation_bar.preferences": "Ominaisuudet", + "navigation_bar.pins": "Kiinnitetyt tuuttaukset", + "navigation_bar.preferences": "Asetukset", "navigation_bar.public_timeline": "Yleinen aikajana", - "notification.favourite": "{name} tykkäsi statuksestasi", + "notification.favourite": "{name} tykkäsi tilastasi", "notification.follow": "{name} seurasi sinua", "notification.mention": "{name} mainitsi sinut", - "notification.reblog": "{name} buustasi statustasi", + "notification.reblog": "{name} buustasi tilaasi", "notifications.clear": "Tyhjennä ilmoitukset", - "notifications.clear_confirmation": "Oletko varma, että haluat lopullisesti tyhjentää kaikki ilmoituksesi?", - "notifications.column_settings.alert": "Työpöytä ilmoitukset", - "notifications.column_settings.favourite": "Tykkäyksiä:", - "notifications.column_settings.follow": "Uusia seuraajia:", - "notifications.column_settings.mention": "Mainintoja:", + "notifications.clear_confirmation": "Haluatko varmasti poistaa kaikki ilmoitukset pysyvästi?", + "notifications.column_settings.alert": "Työpöytäilmoitukset", + "notifications.column_settings.favourite": "Tykkäykset:", + "notifications.column_settings.follow": "Uudet seuraajat:", + "notifications.column_settings.mention": "Maininnat:", "notifications.column_settings.push": "Push-ilmoitukset", "notifications.column_settings.push_meta": "Tämä laite", - "notifications.column_settings.reblog": "Buusteja:", + "notifications.column_settings.reblog": "Buustit:", "notifications.column_settings.show": "Näytä sarakkeessa", - "notifications.column_settings.sound": "Soita ääni", + "notifications.column_settings.sound": "Äänimerkki", "onboarding.done": "Valmis", "onboarding.next": "Seuraava", - "onboarding.page_five.public_timelines": "Paikallinen aikajana näyttää kaikki julkiset julkaisut kaikilta, jotka ovat verkko-osoitteessa {domain}. Yleinen aikajana näyttää julkiset julkaisut kaikilta niiltä, joita käyttäjät verkko-osoitteessa {domain} seuraavat. Nämä ovat julkiset aikajanat, ja ne ovat hyviä tapoja löytää uusia ihmisiä.", - "onboarding.page_four.home": "Kotiaikajana näyttää julkaisut ihmisiltä joita seuraat.", - "onboarding.page_four.notifications": "Ilmoitukset-sarake näyttää sinulle, kun joku on viestii kanssasi.", - "onboarding.page_one.federation": "Mastodon on yhteisöpalvelu, joka toimii monen itsenäisen palvelimen muodostamassa verkossa. Me kutsumme näitä palvelimia instansseiksi.", + "onboarding.page_five.public_timelines": "Paikallisella aikajanalla näytetään instanssin {domain} kaikkien käyttäjien julkiset julkaisut. Yleisellä aikajanalla näytetään kaikkien instanssin {domain} käyttäjien seuraamien käyttäjien julkiset julkaisut. Nämä julkiset aikajanat ovat loistavia paikkoja löytää uusia ihmisiä.", + "onboarding.page_four.home": "Kotiaikajanalla näytetään seuraamiesi ihmisten julkaisut.", + "onboarding.page_four.notifications": "Ilmoitukset-sarakkeessa näytetään muiden sinuun liittyvä toiminta.", + "onboarding.page_one.federation": "Mastodon on usean itsenäisen palvelimen muodostama yhteisöpalvelu. Näitä palvelimia kutsutaan instansseiksi.", "onboarding.page_one.full_handle": "Koko käyttäjänimesi", - "onboarding.page_one.handle_hint": "Tämä on se, mitä voisit ehdottaa ystäviäsi etsimään.", + "onboarding.page_one.handle_hint": "Tällä nimellä ystäväsi löytävät sinut.", "onboarding.page_one.welcome": "Tervetuloa Mastodoniin!", - "onboarding.page_six.admin": "Instanssisi ylläpitäjä on {admin}.", + "onboarding.page_six.admin": "Instanssin ylläpitäjä on {admin}.", "onboarding.page_six.almost_done": "Melkein valmista...", - "onboarding.page_six.appetoot": "Bon Appetööt!", + "onboarding.page_six.appetoot": "Tuuttailun iloa!", "onboarding.page_six.apps_available": "{apps} on saatavilla iOS:lle, Androidille ja muille alustoille.", - "onboarding.page_six.github": "Mastodon on ilmainen, vapaan lähdekoodin ohjelma. Voit raportoida bugeja, pyytää ominaisuuksia tai osallistua kehittämiseen GitHub-palvelussa: {github}.", + "onboarding.page_six.github": "Mastodon on ilmainen, vapaan lähdekoodin ohjelma. Voit raportoida bugeja, ehdottaa ominaisuuksia tai osallistua kehittämiseen GitHubissa: {github}.", "onboarding.page_six.guidelines": "yhteisön säännöt", "onboarding.page_six.read_guidelines": "Ole hyvä ja lue {domain}:n {guidelines}!", "onboarding.page_six.various_app": "mobiilisovellukset", - "onboarding.page_three.profile": "Muokkaa profiiliasi muuttaaksesi kuvakettasi, esittelyäsi ja nimimerkkiäsi. Löydät sieltä myös muita henkilökohtaisia asetuksia.", - "onboarding.page_three.search": "Käytä hakukenttää löytääksesi ihmisiä ja etsiäksesi hashtageja, kuten {illustration} tai {introductions}. Hakeaksesi henkilöä joka on toisessa instanssissa, käytä hänen käyttäjänimeään kokonaisuudessaan.", - "onboarding.page_two.compose": "Kirjoita postauksia kirjoita-sarakkeessa. Voit ladata kuvia, vaihtaa yksityisyysasetuksia ja lisätä sisältövaroituksia alla olevista painikkeista.", + "onboarding.page_three.profile": "Voit muuttaa profiilikuvaasi, esittelyäsi ja nimimerkkiäsi sekä muita asetuksia muokkaamalla profiiliasi.", + "onboarding.page_three.search": "Etsi ihmisiä ja hashtageja (esimerkiksi {illustration} tai {introductions}) hakukentän avulla. Jos haet toista instanssia käyttävää henkilöä, käytä hänen koko käyttäjänimeään.", + "onboarding.page_two.compose": "Kirjoita julkaisuja kirjoitussarakkeessa. Voit ladata kuvia, vaihtaa näkyvyysasetuksia ja lisätä sisältövaroituksia alla olevista painikkeista.", "onboarding.skip": "Ohita", - "privacy.change": "Säädä töötin yksityisyysasetuksia", + "privacy.change": "Säädä tuuttauksen näkyvyyttä", "privacy.direct.long": "Julkaise vain mainituille käyttäjille", - "privacy.direct.short": "Yksityisviesti", + "privacy.direct.short": "Suora viesti", "privacy.private.long": "Julkaise vain seuraajille", "privacy.private.short": "Vain seuraajat", "privacy.public.long": "Julkaise julkisille aikajanoille", "privacy.public.short": "Julkinen", - "privacy.unlisted.long": "Älä julkaise yleisillä aikajanoilla", - "privacy.unlisted.short": "Julkinen, mutta älä näytä julkisella aikajanalla", + "privacy.unlisted.long": "Älä julkaise julkisilla aikajanoilla", + "privacy.unlisted.short": "Listaamaton julkinen", "regeneration_indicator.label": "Ladataan…", "regeneration_indicator.sublabel": "Kotinäkymääsi valmistellaan!", - "relative_time.days": "{number}d", - "relative_time.hours": "{number}h", + "relative_time.days": "{number} pv", + "relative_time.hours": "{number} h", "relative_time.just_now": "nyt", - "relative_time.minutes": "{number}m", - "relative_time.seconds": "{number}s", + "relative_time.minutes": "{number} m", + "relative_time.seconds": "{number} s", "reply_indicator.cancel": "Peruuta", - "report.forward": "Uudelleenohjaa kohteeseen {target}", - "report.forward_hint": "Tämä tili on toiselta serveriltä. Haluatko, että myös sinne lähetetään anonymisoitu kopio ilmiantoraportista?", - "report.hint": "Ilmianto lähetetään instanssisi moderaattoreille. Voit antaa kuvauksen käyttäjän ilmiantamisen syystä alle:", + "report.forward": "Välitä kohteeseen {target}", + "report.forward_hint": "Tämä tili on toisella palvelimella. Haluatko lähettää nimettömän raportin myös sinne?", + "report.hint": "Raportti lähetetään oman instanssisi moderaattoreille. Seuraavassa voit kertoa, miksi raportoit tästä tilistä:", "report.placeholder": "Lisäkommentit", - "report.submit": "Submit", - "report.target": "Reporting", + "report.submit": "Lähetä", + "report.target": "Raportoidaan {target}", "search.placeholder": "Hae", "search_popout.search_format": "Tarkennettu haku", - "search_popout.tips.full_text": "Tekstihaku palauttaa statuspäivitykset jotka olet kirjoittanut, lisännyt suosikkeihisi, boostannut tai joissa sinut mainitaan, sekä käyttäjänimet, nimimerkit ja hastagit jotka sisältävät tekstin.", - "search_popout.tips.hashtag": "hashtagi", - "search_popout.tips.status": "status", - "search_popout.tips.text": "Pelkkä tekstihaku palauttaa hakua vastaavat nimimerkit, käyttäjänimet ja hastagit", + "search_popout.tips.full_text": "Tekstihaku palauttaa tilapäivitykset, jotka olet kirjoittanut, lisännyt suosikkeihisi, boostannut tai joissa sinut mainitaan, sekä tekstin sisältävät käyttäjänimet, nimimerkit ja hastagit.", + "search_popout.tips.hashtag": "hashtag", + "search_popout.tips.status": "tila", + "search_popout.tips.text": "Tekstihaku palauttaa hakua vastaavat nimimerkit, käyttäjänimet ja hastagit", "search_popout.tips.user": "käyttäjä", "search_results.accounts": "Ihmiset", "search_results.hashtags": "Hashtagit", - "search_results.statuses": "Töötit", + "search_results.statuses": "Tuuttaukset", "search_results.total": "{count, number} {count, plural, one {result} other {results}}", "standalone.public_title": "Kurkistus sisälle...", - "status.block": "Block @{name}", - "status.cannot_reblog": "Tätä postausta ei voi buustata", + "status.block": "Estä @{name}", + "status.cannot_reblog": "Tätä julkaisua ei voi buustata", "status.delete": "Poista", + "status.direct": "Direct message @{name}", "status.embed": "Upota", "status.favourite": "Tykkää", "status.load_more": "Lataa lisää", @@ -248,29 +249,30 @@ "status.more": "Lisää", "status.mute": "Mykistä @{name}", "status.mute_conversation": "Mykistä keskustelu", - "status.open": "Laajenna statuspäivitys", + "status.open": "Laajenna tilapäivitys", "status.pin": "Kiinnitä profiiliin", - "status.pinned": "Kiinnitetty töötti", + "status.pinned": "Kiinnitetty tuuttaus", "status.reblog": "Buustaa", "status.reblogged_by": "{name} buustasi", "status.reply": "Vastaa", "status.replyAll": "Vastaa ketjuun", - "status.report": "Report @{name}", + "status.report": "Raportoi @{name}", "status.sensitive_toggle": "Klikkaa nähdäksesi", "status.sensitive_warning": "Arkaluontoista sisältöä", "status.share": "Jaa", "status.show_less": "Näytä vähemmän", "status.show_less_all": "Näytä vähemmän kaikista", "status.show_more": "Näytä lisää", - "status.show_more_all": "Näytä enemmän kaikista", - "status.unmute_conversation": "Poista mykistys keskustelulta", + "status.show_more_all": "Näytä lisää kaikista", + "status.unmute_conversation": "Poista keskustelun mykistys", "status.unpin": "Irrota profiilista", "tabs_bar.federated_timeline": "Yleinen", "tabs_bar.home": "Koti", "tabs_bar.local_timeline": "Paikallinen", "tabs_bar.notifications": "Ilmoitukset", - "ui.beforeunload": "Luonnoksesi menetetään, jos poistut Mastodonista.", - "upload_area.title": "Raahaa ja pudota tähän ladataksesi", + "tabs_bar.search": "Search", + "ui.beforeunload": "Luonnos häviää, jos poistut Mastodonista.", + "upload_area.title": "Lataa raahaamalla ja pudottamalla tähän", "upload_button.label": "Lisää mediaa", "upload_form.description": "Anna kuvaus näkörajoitteisia varten", "upload_form.focus": "Rajaa", @@ -279,10 +281,10 @@ "video.close": "Sulje video", "video.exit_fullscreen": "Poistu koko näytön tilasta", "video.expand": "Laajenna video", - "video.fullscreen": "Full screen", + "video.fullscreen": "Koko näyttö", "video.hide": "Piilota video", "video.mute": "Mykistä ääni", "video.pause": "Keskeytä", "video.play": "Toista", - "video.unmute": "Poista mykistys ääneltä" + "video.unmute": "Poista äänen mykistys" } diff --git a/app/javascript/mastodon/locales/fr.json b/app/javascript/mastodon/locales/fr.json index 57c55c9..e340fda 100644 --- a/app/javascript/mastodon/locales/fr.json +++ b/app/javascript/mastodon/locales/fr.json @@ -240,6 +240,7 @@ "status.block": "Block @{name}", "status.cannot_reblog": "Cette publication ne peut être boostée", "status.delete": "Effacer", + "status.direct": "Direct message @{name}", "status.embed": "Intégrer", "status.favourite": "Ajouter aux favoris", "status.load_more": "Charger plus", @@ -269,6 +270,7 @@ "tabs_bar.home": "Accueil", "tabs_bar.local_timeline": "Fil public local", "tabs_bar.notifications": "Notifications", + "tabs_bar.search": "Search", "ui.beforeunload": "Votre brouillon sera perdu si vous quittez Mastodon.", "upload_area.title": "Glissez et déposez pour envoyer", "upload_button.label": "Joindre un média", diff --git a/app/javascript/mastodon/locales/gl.json b/app/javascript/mastodon/locales/gl.json index 8d58640..5cbb7d3 100644 --- a/app/javascript/mastodon/locales/gl.json +++ b/app/javascript/mastodon/locales/gl.json @@ -2,7 +2,7 @@ "account.block": "Bloquear @{name}", "account.block_domain": "Ocultar calquer contido de {domain}", "account.blocked": "Blocked", - "account.direct": "Direct Message @{name}", + "account.direct": "Direct message @{name}", "account.disclaimer_full": "A información inferior podería mostrar un perfil incompleto da usuaria.", "account.domain_blocked": "Domain hidden", "account.edit_profile": "Editar perfil", @@ -148,7 +148,7 @@ "lists.search": "Procurar entre a xente que segues", "lists.subheading": "As túas listas", "loading_indicator.label": "Cargando...", - "media_gallery.toggle_visible": "Dar visibilidade", + "media_gallery.toggle_visible": "Ocultar", "missing_indicator.label": "Non atopado", "missing_indicator.sublabel": "This resource could not be found", "mute_modal.hide_notifications": "Esconder notificacións deste usuario?", @@ -240,6 +240,7 @@ "status.block": "Block @{name}", "status.cannot_reblog": "Esta mensaxe non pode ser promocionada", "status.delete": "Eliminar", + "status.direct": "Direct message @{name}", "status.embed": "Incrustar", "status.favourite": "Favorita", "status.load_more": "Cargar máis", @@ -269,6 +270,7 @@ "tabs_bar.home": "Inicio", "tabs_bar.local_timeline": "Local", "tabs_bar.notifications": "Notificacións", + "tabs_bar.search": "Search", "ui.beforeunload": "O borrador perderase se sae de Mastodon.", "upload_area.title": "Arrastre e solte para subir", "upload_button.label": "Engadir medios", diff --git a/app/javascript/mastodon/locales/he.json b/app/javascript/mastodon/locales/he.json index 6bec26f..656d93c 100644 --- a/app/javascript/mastodon/locales/he.json +++ b/app/javascript/mastodon/locales/he.json @@ -240,6 +240,7 @@ "status.block": "Block @{name}", "status.cannot_reblog": "לא ניתן להדהד הודעה זו", "status.delete": "מחיקה", + "status.direct": "Direct message @{name}", "status.embed": "הטמעה", "status.favourite": "חיבוב", "status.load_more": "עוד", @@ -269,6 +270,7 @@ "tabs_bar.home": "בבית", "tabs_bar.local_timeline": "ציר זמן מקומי", "tabs_bar.notifications": "התראות", + "tabs_bar.search": "Search", "ui.beforeunload": "הטיוטא תאבד אם תעזבו את מסטודון.", "upload_area.title": "ניתן להעלות על ידי Drag & drop", "upload_button.label": "הוספת מדיה", diff --git a/app/javascript/mastodon/locales/hr.json b/app/javascript/mastodon/locales/hr.json index f7a5d0a..2d7d0a5 100644 --- a/app/javascript/mastodon/locales/hr.json +++ b/app/javascript/mastodon/locales/hr.json @@ -240,6 +240,7 @@ "status.block": "Block @{name}", "status.cannot_reblog": "Ovaj post ne može biti boostan", "status.delete": "Obriši", + "status.direct": "Direct message @{name}", "status.embed": "Embed", "status.favourite": "Označi omiljenim", "status.load_more": "Učitaj više", @@ -269,6 +270,7 @@ "tabs_bar.home": "Dom", "tabs_bar.local_timeline": "Lokalno", "tabs_bar.notifications": "Notifikacije", + "tabs_bar.search": "Search", "ui.beforeunload": "Your draft will be lost if you leave Mastodon.", "upload_area.title": "Povuci i spusti kako bi uploadao", "upload_button.label": "Dodaj media", diff --git a/app/javascript/mastodon/locales/hu.json b/app/javascript/mastodon/locales/hu.json index 8b9c149..24f3a78 100644 --- a/app/javascript/mastodon/locales/hu.json +++ b/app/javascript/mastodon/locales/hu.json @@ -240,6 +240,7 @@ "status.block": "Block @{name}", "status.cannot_reblog": "Ezen státusz nem rebloggolható", "status.delete": "Törlés", + "status.direct": "Direct message @{name}", "status.embed": "Beágyaz", "status.favourite": "Kedvenc", "status.load_more": "Többet", @@ -269,6 +270,7 @@ "tabs_bar.home": "Kezdőlap", "tabs_bar.local_timeline": "Local", "tabs_bar.notifications": "Értesítések", + "tabs_bar.search": "Search", "ui.beforeunload": "A piszkozata el fog vesztődni ha elhagyja Mastodon-t.", "upload_area.title": "Húzza ide a feltöltéshez", "upload_button.label": "Média hozzáadása", diff --git a/app/javascript/mastodon/locales/hy.json b/app/javascript/mastodon/locales/hy.json index 22ba89a..2ba52c5 100644 --- a/app/javascript/mastodon/locales/hy.json +++ b/app/javascript/mastodon/locales/hy.json @@ -240,6 +240,7 @@ "status.block": "Արգելափակել @{name}֊ին", "status.cannot_reblog": "Այս թութը չի կարող տարածվել", "status.delete": "Ջնջել", + "status.direct": "Direct message @{name}", "status.embed": "Ներդնել", "status.favourite": "Հավանել", "status.load_more": "Բեռնել ավելին", @@ -269,6 +270,7 @@ "tabs_bar.home": "Հիմնական", "tabs_bar.local_timeline": "Տեղական", "tabs_bar.notifications": "Ծանուցումներ", + "tabs_bar.search": "Search", "ui.beforeunload": "Քո սեւագիրը կկորի, եթե լքես Մաստոդոնը։", "upload_area.title": "Քաշիր ու նետիր՝ վերբեռնելու համար", "upload_button.label": "Ավելացնել մեդիա", diff --git a/app/javascript/mastodon/locales/id.json b/app/javascript/mastodon/locales/id.json index 1ef610f..e1518c1 100644 --- a/app/javascript/mastodon/locales/id.json +++ b/app/javascript/mastodon/locales/id.json @@ -240,6 +240,7 @@ "status.block": "Block @{name}", "status.cannot_reblog": "This post cannot be boosted", "status.delete": "Hapus", + "status.direct": "Direct message @{name}", "status.embed": "Embed", "status.favourite": "Difavoritkan", "status.load_more": "Tampilkan semua", @@ -269,6 +270,7 @@ "tabs_bar.home": "Beranda", "tabs_bar.local_timeline": "Lokal", "tabs_bar.notifications": "Notifikasi", + "tabs_bar.search": "Search", "ui.beforeunload": "Naskah anda akan hilang jika anda keluar dari Mastodon.", "upload_area.title": "Seret & lepaskan untuk mengunggah", "upload_button.label": "Tambahkan media", diff --git a/app/javascript/mastodon/locales/io.json b/app/javascript/mastodon/locales/io.json index 1435178..c79d4a6 100644 --- a/app/javascript/mastodon/locales/io.json +++ b/app/javascript/mastodon/locales/io.json @@ -240,6 +240,7 @@ "status.block": "Block @{name}", "status.cannot_reblog": "This post cannot be boosted", "status.delete": "Efacar", + "status.direct": "Direct message @{name}", "status.embed": "Embed", "status.favourite": "Favorizar", "status.load_more": "Kargar pluse", @@ -269,6 +270,7 @@ "tabs_bar.home": "Hemo", "tabs_bar.local_timeline": "Lokala", "tabs_bar.notifications": "Savigi", + "tabs_bar.search": "Search", "ui.beforeunload": "Your draft will be lost if you leave Mastodon.", "upload_area.title": "Tranar faligar por kargar", "upload_button.label": "Adjuntar kontenajo", diff --git a/app/javascript/mastodon/locales/it.json b/app/javascript/mastodon/locales/it.json index 226127e..3c85a3e 100644 --- a/app/javascript/mastodon/locales/it.json +++ b/app/javascript/mastodon/locales/it.json @@ -240,6 +240,7 @@ "status.block": "Block @{name}", "status.cannot_reblog": "This post cannot be boosted", "status.delete": "Elimina", + "status.direct": "Direct message @{name}", "status.embed": "Embed", "status.favourite": "Apprezzato", "status.load_more": "Mostra di più", @@ -269,6 +270,7 @@ "tabs_bar.home": "Home", "tabs_bar.local_timeline": "Locale", "tabs_bar.notifications": "Notifiche", + "tabs_bar.search": "Search", "ui.beforeunload": "Your draft will be lost if you leave Mastodon.", "upload_area.title": "Trascina per caricare", "upload_button.label": "Aggiungi file multimediale", diff --git a/app/javascript/mastodon/locales/ja.json b/app/javascript/mastodon/locales/ja.json index a9beaa3..2da9192 100644 --- a/app/javascript/mastodon/locales/ja.json +++ b/app/javascript/mastodon/locales/ja.json @@ -186,7 +186,7 @@ "onboarding.page_five.public_timelines": "連合タイムラインでは{domain}の人がフォローしているMastodon全体での公開投稿を表示します。同じくローカルタイムラインでは{domain}のみの公開投稿を表示します。", "onboarding.page_four.home": "「ホーム」タイムラインではあなたがフォローしている人の投稿を表示します。", "onboarding.page_four.notifications": "「通知」ではあなたへの他の人からの関わりを表示します。", - "onboarding.page_one.federation": "Mastodonは誰でも参加できるSNSです。", + "onboarding.page_one.federation": "Mastodonは独立したインスタンス(サーバー)の集合体です。", "onboarding.page_one.full_handle": "あなたのフルハンドル", "onboarding.page_one.handle_hint": "あなたを探している友達に伝えるといいでしょう。", "onboarding.page_one.welcome": "Mastodonへようこそ!", diff --git a/app/javascript/mastodon/locales/ko.json b/app/javascript/mastodon/locales/ko.json index 89fde89..e2fadff 100644 --- a/app/javascript/mastodon/locales/ko.json +++ b/app/javascript/mastodon/locales/ko.json @@ -240,6 +240,7 @@ "status.block": "@{name} 차단", "status.cannot_reblog": "이 포스트는 부스트 할 수 없습니다", "status.delete": "삭제", + "status.direct": "Direct message @{name}", "status.embed": "공유하기", "status.favourite": "즐겨찾기", "status.load_more": "더 보기", @@ -269,6 +270,7 @@ "tabs_bar.home": "홈", "tabs_bar.local_timeline": "로컬", "tabs_bar.notifications": "알림", + "tabs_bar.search": "Search", "ui.beforeunload": "지금 나가면 저장되지 않은 항목을 잃게 됩니다.", "upload_area.title": "드래그 & 드롭으로 업로드", "upload_button.label": "미디어 추가", diff --git a/app/javascript/mastodon/locales/nl.json b/app/javascript/mastodon/locales/nl.json index 7bfb74a..0222432 100644 --- a/app/javascript/mastodon/locales/nl.json +++ b/app/javascript/mastodon/locales/nl.json @@ -240,6 +240,7 @@ "status.block": "Blokkeer @{name}", "status.cannot_reblog": "Deze toot kan niet geboost worden", "status.delete": "Verwijderen", + "status.direct": "Direct message @{name}", "status.embed": "Embed", "status.favourite": "Favoriet", "status.load_more": "Meer laden", @@ -269,6 +270,7 @@ "tabs_bar.home": "Start", "tabs_bar.local_timeline": "Lokaal", "tabs_bar.notifications": "Meldingen", + "tabs_bar.search": "Search", "ui.beforeunload": "Je concept zal verloren gaan als je Mastodon verlaat.", "upload_area.title": "Hierin slepen om te uploaden", "upload_button.label": "Media toevoegen", diff --git a/app/javascript/mastodon/locales/no.json b/app/javascript/mastodon/locales/no.json index b79277c..20b2cbb 100644 --- a/app/javascript/mastodon/locales/no.json +++ b/app/javascript/mastodon/locales/no.json @@ -240,6 +240,7 @@ "status.block": "Block @{name}", "status.cannot_reblog": "Denne posten kan ikke fremheves", "status.delete": "Slett", + "status.direct": "Direct message @{name}", "status.embed": "Bygge inn", "status.favourite": "Lik", "status.load_more": "Last mer", @@ -269,6 +270,7 @@ "tabs_bar.home": "Hjem", "tabs_bar.local_timeline": "Lokal", "tabs_bar.notifications": "Varslinger", + "tabs_bar.search": "Search", "ui.beforeunload": "Din kladd vil bli forkastet om du forlater Mastodon.", "upload_area.title": "Dra og slipp for å laste opp", "upload_button.label": "Legg til media", diff --git a/app/javascript/mastodon/locales/oc.json b/app/javascript/mastodon/locales/oc.json index 5b12f88..32133c1 100644 --- a/app/javascript/mastodon/locales/oc.json +++ b/app/javascript/mastodon/locales/oc.json @@ -240,6 +240,7 @@ "status.block": "Blocar @{name}", "status.cannot_reblog": "Aqueste estatut pòt pas èsser partejat", "status.delete": "Escafar", + "status.direct": "Direct message @{name}", "status.embed": "Embarcar", "status.favourite": "Apondre als favorits", "status.load_more": "Cargar mai", @@ -269,6 +270,7 @@ "tabs_bar.home": "Acuèlh", "tabs_bar.local_timeline": "Flux public local", "tabs_bar.notifications": "Notificacions", + "tabs_bar.search": "Search", "ui.beforeunload": "Vòstre brolhon serà perdut se quitatz Mastodon.", "upload_area.title": "Lisatz e depausatz per mandar", "upload_button.label": "Ajustar un mèdia", diff --git a/app/javascript/mastodon/locales/pt-BR.json b/app/javascript/mastodon/locales/pt-BR.json index 4cd2e06..b4be0bb 100644 --- a/app/javascript/mastodon/locales/pt-BR.json +++ b/app/javascript/mastodon/locales/pt-BR.json @@ -240,6 +240,7 @@ "status.block": "Block @{name}", "status.cannot_reblog": "Esta postagem não pode ser compartilhada", "status.delete": "Excluir", + "status.direct": "Direct message @{name}", "status.embed": "Incorporar", "status.favourite": "Adicionar aos favoritos", "status.load_more": "Carregar mais", @@ -269,6 +270,7 @@ "tabs_bar.home": "Página inicial", "tabs_bar.local_timeline": "Local", "tabs_bar.notifications": "Notificações", + "tabs_bar.search": "Search", "ui.beforeunload": "Seu rascunho será perdido se você sair do Mastodon.", "upload_area.title": "Arraste e solte para enviar", "upload_button.label": "Adicionar mídia", diff --git a/app/javascript/mastodon/locales/pt.json b/app/javascript/mastodon/locales/pt.json index 7a404ea..132de52 100644 --- a/app/javascript/mastodon/locales/pt.json +++ b/app/javascript/mastodon/locales/pt.json @@ -240,6 +240,7 @@ "status.block": "Block @{name}", "status.cannot_reblog": "Este post não pode ser partilhado", "status.delete": "Eliminar", + "status.direct": "Direct message @{name}", "status.embed": "Incorporar", "status.favourite": "Adicionar aos favoritos", "status.load_more": "Carregar mais", @@ -269,6 +270,7 @@ "tabs_bar.home": "Home", "tabs_bar.local_timeline": "Local", "tabs_bar.notifications": "Notificações", + "tabs_bar.search": "Search", "ui.beforeunload": "O teu rascunho vai ser perdido se abandonares o Mastodon.", "upload_area.title": "Arraste e solte para enviar", "upload_button.label": "Adicionar media", diff --git a/app/javascript/mastodon/locales/ru.json b/app/javascript/mastodon/locales/ru.json index 8616ef9..b56ccf1 100644 --- a/app/javascript/mastodon/locales/ru.json +++ b/app/javascript/mastodon/locales/ru.json @@ -240,6 +240,7 @@ "status.block": "Заблокировать @{name}", "status.cannot_reblog": "Этот статус не может быть продвинут", "status.delete": "Удалить", + "status.direct": "Direct message @{name}", "status.embed": "Встроить", "status.favourite": "Нравится", "status.load_more": "Показать еще", @@ -269,6 +270,7 @@ "tabs_bar.home": "Главная", "tabs_bar.local_timeline": "Локальная", "tabs_bar.notifications": "Уведомления", + "tabs_bar.search": "Search", "ui.beforeunload": "Ваш черновик будет утерян, если вы покинете Mastodon.", "upload_area.title": "Перетащите сюда, чтобы загрузить", "upload_button.label": "Добавить медиаконтент", diff --git a/app/javascript/mastodon/locales/sk.json b/app/javascript/mastodon/locales/sk.json index 925b46d..1593151 100644 --- a/app/javascript/mastodon/locales/sk.json +++ b/app/javascript/mastodon/locales/sk.json @@ -8,11 +8,11 @@ "account.edit_profile": "Upraviť profil", "account.follow": "Následovať", "account.followers": "Sledujúci", - "account.follows": "Sledujete", + "account.follows": "Následuje", "account.follows_you": "Následuje ťa", "account.hide_reblogs": "Skryť povýšenia od @{name}", "account.media": "Médiá", - "account.mention": "Spomeňte @{name}", + "account.mention": "Spomeň @{name}", "account.moved_to": "{name} sa presunul/a na:", "account.mute": "Ignorovať @{name}", "account.mute_notifications": "Stĺmiť notifikácie od @{name}", @@ -22,7 +22,7 @@ "account.report": "Nahlás @{name}", "account.requested": "Čaká na schválenie. Kliknite pre zrušenie žiadosti", "account.share": "Zdieľať @{name} profil", - "account.show_reblogs": "Zobraziť povýšenia od @{name}", + "account.show_reblogs": "Ukáž povýšenia od @{name}", "account.unblock": "Odblokovať @{name}", "account.unblock_domain": "Prestať blokovať {domain}", "account.unfollow": "Prestať nasledovať", @@ -83,7 +83,7 @@ "confirmations.mute.message": "Naozaj chcete ignorovať {name}?", "confirmations.unfollow.confirm": "Nesledovať", "confirmations.unfollow.message": "Naozaj chcete prestať sledovať {name}?", - "embed.instructions": "Umiestnite kód uvedený nižšie pre pridanie tohto statusu na vašu web stránku.", + "embed.instructions": "Umiestni kód uvedený nižšie pre pridanie tohto statusu na tvoju web stránku.", "embed.preview": "Tu je ako to bude vyzerať:", "emoji_button.activity": "Aktivita", "emoji_button.custom": "Vlastné", @@ -111,13 +111,13 @@ "getting_started.appsshort": "Aplikácie", "getting_started.faq": "Časté otázky", "getting_started.heading": "Začni tu", - "getting_started.open_source_notice": "Mastodon má otvorený kód. Nahlásiť chyby, alebo prispievať vlastným kódom môžete na GitHube v {github}.", + "getting_started.open_source_notice": "Mastodon má otvorený kód. Nahlásiť chyby, alebo prispieť môžeš na GitHube v {github}.", "getting_started.userguide": "Používateľská príručka", "home.column_settings.advanced": "Pokročilé", "home.column_settings.basic": "Základné", "home.column_settings.filter_regex": "Filtrovať použitím regulárnych výrazov", "home.column_settings.show_reblogs": "Zobraziť povýšené", - "home.column_settings.show_replies": "Zobraziť odpovede", + "home.column_settings.show_replies": "Ukázať odpovede", "home.settings": "Nastavenia stĺpcov", "keyboard_shortcuts.back": "dostať sa naspäť", "keyboard_shortcuts.boost": "vyzdvihnúť", @@ -169,7 +169,7 @@ "notification.favourite": "{name} sa páči tvoj status", "notification.follow": "{name} ťa začal/a následovať", "notification.mention": "{name} ťa spomenul/a", - "notification.reblog": "{name} re-tootol tvoj status", + "notification.reblog": "{name} zdieľal/a tvoj status", "notifications.clear": "Vyčistiť zoznam notifikácii", "notifications.clear_confirmation": "Naozaj chcete nenávratne prečistiť všetky vaše notifikácie?", "notifications.column_settings.alert": "Notifikácie na ploche", @@ -235,24 +235,25 @@ "search_results.accounts": "Ľudia", "search_results.hashtags": "Haštagy", "search_results.statuses": "Príspevky", - "search_results.total": "{count, number} {count, plural, one {result} ostatné {results}}", - "standalone.public_title": "Pohľad dovnútra...", + "search_results.total": "{count, number} {count, plural, jeden {výsledok} ostatné {výsledky}}", + "standalone.public_title": "Náhľad dovnútra...", "status.block": "Blokovať @{name}", "status.cannot_reblog": "Tento príspevok nemôže byť re-tootnutý", "status.delete": "Zmazať", + "status.direct": "Direct message @{name}", "status.embed": "Vložiť", "status.favourite": "Páči sa mi", "status.load_more": "Zobraz viac", "status.media_hidden": "Skryté médiá", - "status.mention": "Napísať @{name}", + "status.mention": "Spomeň @{name}", "status.more": "Viac", "status.mute": "Utíšiť @{name}", "status.mute_conversation": "Ignorovať konverzáciu", "status.open": "Otvoriť tento status", - "status.pin": "Pripnúť na profil", + "status.pin": "Pripni na profil", "status.pinned": "Pripnutý príspevok", "status.reblog": "Povýšiť", - "status.reblogged_by": "{name} povýšil", + "status.reblogged_by": "{name} povýšil/a", "status.reply": "Odpovedať", "status.replyAll": "Odpovedať na diskusiu", "status.report": "Nahlásiť @{name}", @@ -261,7 +262,7 @@ "status.share": "Zdieľať", "status.show_less": "Zobraz menej", "status.show_less_all": "Všetkým ukáž menej", - "status.show_more": "Zobraz viac", + "status.show_more": "Zobraziť viac", "status.show_more_all": "Všetkým ukáž viac", "status.unmute_conversation": "Prestať ignorovať konverzáciu", "status.unpin": "Odopnúť z profilu", @@ -269,6 +270,7 @@ "tabs_bar.home": "Domov", "tabs_bar.local_timeline": "Lokálna", "tabs_bar.notifications": "Notifikácie", + "tabs_bar.search": "Search", "ui.beforeunload": "Čo máte rozpísané sa stratí, ak opustíte Mastodon.", "upload_area.title": "Ťahaj a pusti pre nahratie", "upload_button.label": "Pridať médiá", diff --git a/app/javascript/mastodon/locales/sr-Latn.json b/app/javascript/mastodon/locales/sr-Latn.json index 8fae49a..69c7aa6 100644 --- a/app/javascript/mastodon/locales/sr-Latn.json +++ b/app/javascript/mastodon/locales/sr-Latn.json @@ -240,6 +240,7 @@ "status.block": "Block @{name}", "status.cannot_reblog": "Ovaj status ne može da se podrži", "status.delete": "Obriši", + "status.direct": "Direct message @{name}", "status.embed": "Ugradi na sajt", "status.favourite": "Omiljeno", "status.load_more": "Učitaj još", @@ -269,6 +270,7 @@ "tabs_bar.home": "Početna", "tabs_bar.local_timeline": "Lokalno", "tabs_bar.notifications": "Obaveštenja", + "tabs_bar.search": "Search", "ui.beforeunload": "Ako napustite Mastodont, izgubićete napisani nacrt.", "upload_area.title": "Prevucite ovde da otpremite", "upload_button.label": "Dodaj multimediju", diff --git a/app/javascript/mastodon/locales/sr.json b/app/javascript/mastodon/locales/sr.json index a39fea5..e973945 100644 --- a/app/javascript/mastodon/locales/sr.json +++ b/app/javascript/mastodon/locales/sr.json @@ -240,6 +240,7 @@ "status.block": "Block @{name}", "status.cannot_reblog": "Овај статус не може да се подржи", "status.delete": "Обриши", + "status.direct": "Direct message @{name}", "status.embed": "Угради на сајт", "status.favourite": "Омиљено", "status.load_more": "Учитај још", @@ -269,6 +270,7 @@ "tabs_bar.home": "Почетна", "tabs_bar.local_timeline": "Локално", "tabs_bar.notifications": "Обавештења", + "tabs_bar.search": "Search", "ui.beforeunload": "Ако напустите Мастодонт, изгубићете написани нацрт.", "upload_area.title": "Превуците овде да отпремите", "upload_button.label": "Додај мултимедију", diff --git a/app/javascript/mastodon/locales/sv.json b/app/javascript/mastodon/locales/sv.json index 014492e..b063adb 100644 --- a/app/javascript/mastodon/locales/sv.json +++ b/app/javascript/mastodon/locales/sv.json @@ -240,6 +240,7 @@ "status.block": "Block @{name}", "status.cannot_reblog": "Detta inlägg kan inte knuffas", "status.delete": "Ta bort", + "status.direct": "Direct message @{name}", "status.embed": "Bädda in", "status.favourite": "Favorit", "status.load_more": "Ladda fler", @@ -269,6 +270,7 @@ "tabs_bar.home": "Hem", "tabs_bar.local_timeline": "Lokal", "tabs_bar.notifications": "Meddelanden", + "tabs_bar.search": "Search", "ui.beforeunload": "Ditt utkast kommer att förloras om du lämnar Mastodon.", "upload_area.title": "Dra & släpp för att ladda upp", "upload_button.label": "Lägg till media", diff --git a/app/javascript/mastodon/locales/th.json b/app/javascript/mastodon/locales/th.json index ecfe7c9..22a75c2 100644 --- a/app/javascript/mastodon/locales/th.json +++ b/app/javascript/mastodon/locales/th.json @@ -240,6 +240,7 @@ "status.block": "Block @{name}", "status.cannot_reblog": "This post cannot be boosted", "status.delete": "Delete", + "status.direct": "Direct message @{name}", "status.embed": "Embed", "status.favourite": "Favourite", "status.load_more": "Load more", @@ -269,6 +270,7 @@ "tabs_bar.home": "Home", "tabs_bar.local_timeline": "Local", "tabs_bar.notifications": "Notifications", + "tabs_bar.search": "Search", "ui.beforeunload": "Your draft will be lost if you leave Mastodon.", "upload_area.title": "Drag & drop to upload", "upload_button.label": "Add media", diff --git a/app/javascript/mastodon/locales/tr.json b/app/javascript/mastodon/locales/tr.json index 58d0e57..8e36c51 100644 --- a/app/javascript/mastodon/locales/tr.json +++ b/app/javascript/mastodon/locales/tr.json @@ -240,6 +240,7 @@ "status.block": "Block @{name}", "status.cannot_reblog": "Bu gönderi boost edilemez", "status.delete": "Sil", + "status.direct": "Direct message @{name}", "status.embed": "Embed", "status.favourite": "Favorilere ekle", "status.load_more": "Daha fazla", @@ -269,6 +270,7 @@ "tabs_bar.home": "Ana sayfa", "tabs_bar.local_timeline": "Yerel", "tabs_bar.notifications": "Bildirimler", + "tabs_bar.search": "Search", "ui.beforeunload": "Your draft will be lost if you leave Mastodon.", "upload_area.title": "Upload için sürükle bırak yapınız", "upload_button.label": "Görsel ekle", diff --git a/app/javascript/mastodon/locales/uk.json b/app/javascript/mastodon/locales/uk.json index 63866d3..09210a3 100644 --- a/app/javascript/mastodon/locales/uk.json +++ b/app/javascript/mastodon/locales/uk.json @@ -240,6 +240,7 @@ "status.block": "Block @{name}", "status.cannot_reblog": "Цей допис не може бути передмухнутий", "status.delete": "Видалити", + "status.direct": "Direct message @{name}", "status.embed": "Embed", "status.favourite": "Подобається", "status.load_more": "Завантажити більше", @@ -269,6 +270,7 @@ "tabs_bar.home": "Головна", "tabs_bar.local_timeline": "Локальна", "tabs_bar.notifications": "Сповіщення", + "tabs_bar.search": "Search", "ui.beforeunload": "Your draft will be lost if you leave Mastodon.", "upload_area.title": "Перетягніть сюди, щоб завантажити", "upload_button.label": "Додати медіаконтент", diff --git a/app/javascript/mastodon/locales/zh-CN.json b/app/javascript/mastodon/locales/zh-CN.json index f7cb496..f0772ff 100644 --- a/app/javascript/mastodon/locales/zh-CN.json +++ b/app/javascript/mastodon/locales/zh-CN.json @@ -240,6 +240,7 @@ "status.block": "屏蔽 @{name}", "status.cannot_reblog": "无法转嘟这条嘟文", "status.delete": "删除", + "status.direct": "Direct message @{name}", "status.embed": "嵌入", "status.favourite": "收藏", "status.load_more": "加载更多", @@ -269,6 +270,7 @@ "tabs_bar.home": "主页", "tabs_bar.local_timeline": "本站", "tabs_bar.notifications": "通知", + "tabs_bar.search": "Search", "ui.beforeunload": "如果你现在离开 Mastodon,你的草稿内容将会被丢弃。", "upload_area.title": "将文件拖放到此处开始上传", "upload_button.label": "上传媒体文件", diff --git a/app/javascript/mastodon/locales/zh-HK.json b/app/javascript/mastodon/locales/zh-HK.json index 1cbc9f1..bebb33e 100644 --- a/app/javascript/mastodon/locales/zh-HK.json +++ b/app/javascript/mastodon/locales/zh-HK.json @@ -240,6 +240,7 @@ "status.block": "封鎖 @{name}", "status.cannot_reblog": "這篇文章無法被轉推", "status.delete": "刪除", + "status.direct": "Direct message @{name}", "status.embed": "鑲嵌", "status.favourite": "收藏", "status.load_more": "載入更多", @@ -269,6 +270,7 @@ "tabs_bar.home": "主頁", "tabs_bar.local_timeline": "本站", "tabs_bar.notifications": "通知", + "tabs_bar.search": "Search", "ui.beforeunload": "如果你現在離開 Mastodon,你的草稿內容將會被丟棄。", "upload_area.title": "將檔案拖放至此上載", "upload_button.label": "上載媒體檔案", diff --git a/app/javascript/mastodon/locales/zh-TW.json b/app/javascript/mastodon/locales/zh-TW.json index 3edb3fa..efed9cd 100644 --- a/app/javascript/mastodon/locales/zh-TW.json +++ b/app/javascript/mastodon/locales/zh-TW.json @@ -240,6 +240,7 @@ "status.block": "封鎖 @{name}", "status.cannot_reblog": "此貼文無法轉推", "status.delete": "刪除", + "status.direct": "Direct message @{name}", "status.embed": "Embed", "status.favourite": "收藏", "status.load_more": "載入更多", @@ -269,6 +270,7 @@ "tabs_bar.home": "家", "tabs_bar.local_timeline": "本地", "tabs_bar.notifications": "通知", + "tabs_bar.search": "Search", "ui.beforeunload": "如果離開 Mastodon,你的草稿將會不見。", "upload_area.title": "拖放來上傳", "upload_button.label": "增加媒體", diff --git a/config/locales/ar.yml b/config/locales/ar.yml index c316a2f..8b9a668 100644 --- a/config/locales/ar.yml +++ b/config/locales/ar.yml @@ -204,6 +204,7 @@ ar: severity: الشدة show: affected_accounts: + one: هناك حساب واحد متأثر في قاعدة البيانات other: هناك %{count} حسابات في قاعدة البيانات متأثرة بذلك retroactive: silence: إلغاء الكتم عن كافة الحسابات المتواجدة على هذا النطاق @@ -262,6 +263,7 @@ ar: settings: activity_api_enabled: desc_html: عدد المنشورات المحلية و المستخدمين النشطين و التسجيلات الأسبوعية الجديدة + title: نشر مُجمل الإحصائيات عن نشاط المستخدمين bootstrap_timeline_accounts: title: الإشتراكات الإفتراضية للمستخدمين الجدد contact_information: @@ -274,12 +276,14 @@ ar: title: نشر عدد مثيلات الخوادم التي تم مصادفتها registrations: closed_message: + desc_html: يتم عرضه على الصفحة الرئيسية عندما يتم غلق تسجيل الحسابات الجديدة. يمكنكم إستخدام علامات الأيتش تي أم أل HTML title: رسالة التسجيلات المقفلة deletion: desc_html: السماح لأي مستخدم إغلاق حسابه title: السماح بحذف الحسابات min_invite_role: disabled: لا أحد + title: المستخدِمون المصرح لهم لإرسال الدعوات open: desc_html: السماح للجميع بإنشاء حساب title: فتح التسجيل diff --git a/config/locales/devise.fi.yml b/config/locales/devise.fi.yml index 91ab955..e356abf 100644 --- a/config/locales/devise.fi.yml +++ b/config/locales/devise.fi.yml @@ -2,60 +2,81 @@ fi: devise: confirmations: - confirmed: Sähköpostisi on onnistuneesti vahvistettu. - send_instructions: Saat kohta sähköpostiisi ohjeet kuinka voit aktivoida tilisi. - send_paranoid_instructions: Jos sähköpostisi on meidän tietokannassa, saat pian ohjeet sen varmentamiseen. + confirmed: Sähköpostiosoitteen vahvistus onnistui. + send_instructions: Saat kohta sähköpostitse ohjeet, kuinka vahvistat sähköpostiosoitteen. Jos et saa viestiä, tarkista roskapostikansio. + send_paranoid_instructions: Jos sähköpostiosoite on tietokannassamme, saat pian ohjeet, kuinka vahvistat osoitteen. Jos et saa viestiä, tarkista roskapostikansio. failure: already_authenticated: Olet jo kirjautunut sisään. - inactive: Tiliäsi ei ole viellä aktivoitu. + inactive: Tiliäsi ei ole vielä aktivoitu. invalid: Virheellinen %{authentication_keys} tai salasana. - last_attempt: Sinulla on yksi yritys jäljellä tai tili lukitaan. + last_attempt: Voit yrittää enää kerran, ennen kuin tili lukitaan. locked: Tili on lukittu. not_found_in_database: Virheellinen %{authentication_keys} tai salasana. - timeout: Sessiosi on umpeutunut. Kirjaudu sisään jatkaaksesi. - unauthenticated: Sinun tarvitsee kirjautua sisään tai rekisteröityä jatkaaksesi. - unconfirmed: Sinun tarvitsee varmentaa sähköpostisi jatkaaksesi. + timeout: Istunto on umpeutunut. Jatka kirjautumalla sisään. + unauthenticated: Kirjaudu sisään tai rekisteröidy, jos haluat jatkaa. + unconfirmed: Vahvista sähköpostiosoitteesi, ennen kuin jatkat. mailer: confirmation_instructions: - subject: 'Mastodon: Varmistus ohjeet' + action: Vahvista sähköpostiosoite + explanation: Olet luonut tilin palvelimelle %{host} käyttäen tätä sähköpostiosoitetta. Aktivoi tili yhdellä klikkauksella. Jos et luonut tiliä itse, voit jättää tämän viestin huomiotta. + extra_html: Katso myös instanssin säännöt ja käyttöehdot. + subject: 'Mastodon: Vahvistusohjeet - %{instance}' + title: Vahvista sähköpostiosoite + email_changed: + explanation: 'Tilin sähköpostiosoitteeksi vaihdetaan:' + extra: Jos et vaihtanut sähköpostiosoitettasi, joku muu on todennäköisesti päässyt käyttämään tiliäsi. Vaihda salasanasi viipymättä. Jos et pääse kirjautumaan tilillesi, ota yhteyttä instanssin ylläpitäjään. + subject: 'Mastodon: Sähköpostiosoite vaihdettu' + title: Uusi sähköpostiosoite password_change: + explanation: Tilin salasana on vaihdettu. + extra: Jos et vaihtanut salasanaasi, joku muu on todennäköisesti päässyt käyttämään tiliäsi. Vaihda salasanasi viipymättä. Jos et pääse kirjautumaan tilillesi, ota yhteyttä instanssin ylläpitäjään. subject: 'Mastodon: Salasana vaihdettu' + title: Salasana vaihdettu + reconfirmation_instructions: + explanation: Vahvista uusi sähköpostiosoite, niin muutos astuu voimaan. + extra: Jos et tehnyt muutosta itse, voit jättää tämän viestin huomiotta. Mastodon-tilin sähköpostiosoitetta ei vaihdeta, ennen kuin klikkaat yllä olevaa linkkiä. + subject: 'Mastodon: Vahvista sähköpostiosoite - %{instance}' + title: Vahvista sähköpostiosoite reset_password_instructions: - subject: 'Mastodon: Salasanan vaihto ohjeet' + action: Vaihda salasana + explanation: Pyysit tilillesi uuden salasanan. + extra: Jos et tehnyt pyyntöä itse, voit jättää tämän viestin huomiotta. Salasanaasi ei vaihdeta, ennen kuin klikkaat yllä olevaa linkkiä ja luot uuden salasanan. + subject: 'Mastodon: Ohjeet salasanan vaihtoon' + title: Salasanan vaihto unlock_instructions: - subject: 'Mastodon: Avauksen ohjeet' + subject: 'Mastodon: Ohjeet lukituksen poistoon' omniauth_callbacks: - failure: Varmennus %{kind} epäonnistui koska "%{reason}". - success: Onnistuneesti varmennettu %{kind} tilillä. + failure: Tunnistautuminen lähteestä %{kind} ei onnistunut, koska "%{reason}". + success: Tunnistautuminen tililtä %{kind} onnistui. passwords: - no_token: Et pääse tälle sivulle ilman salasanan vaihto sähköpostia. Jos tulet tämmöisestä postista, varmista että sinulla on täydellinen URL. - send_instructions: Jos sähköpostisi on meidän tietokannassa, saat pian ohjeet salasanan palautukseen. - send_paranoid_instructions: Jos sähköpostisi on meidän tietokannassa, saat pian ohjeet salasanan palautukseen. - updated: Salasanasi vaihdettu onnistuneesti. Olet nyt kirjautunut sisään. - updated_not_active: Salasanasi vaihdettu onnistuneesti. + no_token: Tälle sivulle pääsee vain salasananvaihtoviestin kautta. Jos tiedät tulevasi salasananvaihtoviestin kautta, varmista, että käytät koko viestissä mainittua URL-osoitetta. + send_instructions: Jos sähköpostiosoite on tietokannassamme, siihen lähetetään pian linkki salasanan vaihtoon. Jos et saa viestiä, tarkista roskapostikansio. + send_paranoid_instructions: Jos sähköpostiosoite on tietokannassamme, siihen lähetetään pian linkki salasanan vaihtoon. Jos et saa viestiä, tarkista roskapostikansio. + updated: Salasanan vaihto onnistui. Olet nyt kirjautunut sisään. + updated_not_active: Salasanan vaihto onnistui. registrations: - destroyed: Näkemiin! Tilisi on onnistuneesti peruttu. Toivottavasti näemme joskus uudestaan. - signed_up: Tervetuloa! Rekisteröitymisesi onnistu. - signed_up_but_inactive: Olet onnistuneesti rekisteröitynyt, mutta emme voi kirjata sinua sisään koska tiliäsi ei ole viellä aktivoitu. - signed_up_but_locked: Olet onnistuneesti rekisteröitynyt, mutta emme voi kirjata sinua sisään koska tilisi on lukittu. - signed_up_but_unconfirmed: Varmistuslinkki on lähetty sähköpostiisi. Seuraa sitä jotta tilisi voidaan aktivoida. - update_needs_confirmation: Tilisi on onnistuneesti päivitetty, mutta meidän tarvitsee vahvistaa sinun uusi sähköpostisi. Tarkista sähköpostisi ja seuraa viestissä tullutta linkkiä varmistaaksesi uuden osoitteen.. - updated: Tilisi on onnistuneesti päivitetty. + destroyed: Tilisi on poistettu. Näkemiin ja tervetuloa uudelleen! + signed_up: Tervetuloa! Rekisteröityminen onnistui. + signed_up_but_inactive: Rekisteröityminen onnistui. Emme kuitenkaan voi kirjata sinua sisään, sillä tiliäsi ei ole vielä aktivoitu. + signed_up_but_locked: Rekisteröityminen onnistui. Emme kuitenkaan voi kirjata sinua sisään, sillä tilisi on lukittu. + signed_up_but_unconfirmed: Sähköpostiosoitteeseesi on lähetetty vahvistuslinkki. Aktivoi tili seuraamalla linkkiä. Jos et saanut viestiä, tarkista roskapostikansio. + update_needs_confirmation: Tilin päivitys onnistui, mutta uusi sähköpostiosoite on vahvistettava. Tarkista sähköpostisi ja vahvista uusi sähköpostiosoite seuraamalla vahvistuslinkkiä. Jos et saanut viestiä, tarkista roskapostikansio. + updated: Tilin päivitys onnistui. sessions: - already_signed_out: Ulos kirjautuminen onnistui. + already_signed_out: Uloskirjautuminen onnistui. signed_in: Sisäänkirjautuminen onnistui. - signed_out: Ulos kirjautuminen onnistui. + signed_out: Uloskirjautuminen onnistui. unlocks: - send_instructions: Saat sähköpostiisi pian ohjeet, jolla voit avata tilisi uudestaan. - send_paranoid_instructions: Jos tilisi on olemassa, saat sähköpostiisi pian ohjeet tilisi avaamiseen. - unlocked: Tilisi on avattu onnistuneesti. Kirjaudu normaalisti sisään. + send_instructions: Saat pian sähköpostitse ohjeet tilin lukituksen poistoon. Jos et saanut viestiä, tarkista roskapostikansio. + send_paranoid_instructions: Jos tili on olemassa, saat pian sähköpostitse ohjeet tilin lukituksen poistoon. Jos et saanut viestiä, tarkista roskapostikansio. + unlocked: Tilin lukituksen poisto onnistui. Jatka kirjautumalla sisään. errors: messages: - already_confirmed: on jo varmistettu. Yritä kirjautua sisään - confirmation_period_expired: pitää varmistaa %{period} sisällä, ole hyvä ja pyydä uusi - expired: on erääntynyt, ole hyvä ja pyydä uusi + already_confirmed: on jo vahvistettu. Yritä kirjautua sisään + confirmation_period_expired: on vahvistettava %{period} sisällä. Pyydä uusi + expired: on vanhentunut. Pyydä uusi not_found: ei löydy not_locked: ei ollut lukittu not_saved: - one: '1 virhe esti %{resource} tallennuksen:' - other: "%{count} virhettä esti %{resource} tallennuksen:" + one: '1 virhe esti kohteen %{resource} tallennuksen:' + other: "%{count} virhettä esti kohteen %{resource} tallennuksen:" diff --git a/config/locales/devise.sk.yml b/config/locales/devise.sk.yml index 2ce328d..e9c5dd4 100644 --- a/config/locales/devise.sk.yml +++ b/config/locales/devise.sk.yml @@ -13,13 +13,13 @@ sk: locked: Váš účet je zamknutý. not_found_in_database: Nesprávny %{authentication_keys} alebo heslo. timeout: Vaša aktívna sezóna vypršala. Pre pokračovanie sa prosím znovu prihláste. - unauthenticated: Pred pokračovaním sa musíte zaregistrovať alebo prihlásiť. - unconfirmed: Pred pokračovaním musíte potvrdiť svoj email. + unauthenticated: K pokračovaniu sa musíš zaregistrovať alebo prihlásiť. + unconfirmed: Pred pokračovaním musíš potvrdiť svoj email. mailer: confirmation_instructions: - action: Potvrite emailovú adresu - explanation: S touto email adresou ste si vytvoril/a účet na %{host}. Si iba jeden klik od jeho aktivácie. Pokiaľ ste to ale nebol/a vy, prosím ignoruj tento email. - extra_html: Prosím pozri sa aj na pravidla tohto servera, a naše užívaťeľské podiemky. + action: Potvŕď emailovú adresu + explanation: S touto emailovou adresou si si vytvoril/a účet na %{host}. Si iba jeden klik od jeho aktivácie. Pokiaľ si to ale nebol/a ty, prosím ignoruj tento email. + extra_html: Prosím pozri sa aj na pravidlá tohto servera, a naše užívaťeľské podiemky. subject: 'Mastodon: Potvrdzovacie inštrukcie pre %{instance}' title: Potvrď emailovú adresu email_changed: diff --git a/config/locales/doorkeeper.fi.yml b/config/locales/doorkeeper.fi.yml index 8c1baf9..a3b878b 100644 --- a/config/locales/doorkeeper.fi.yml +++ b/config/locales/doorkeeper.fi.yml @@ -3,17 +3,19 @@ fi: activerecord: attributes: doorkeeper/application: - name: Nimi - redirect_uri: Uudelleenohjaus URI + name: Sovelluksen nimi + redirect_uri: Uudelleenohjauksen URI + scopes: Oikeudet + website: Sovelluksen verkkosivu errors: models: doorkeeper/application: attributes: redirect_uri: fragment_present: ei voi sisältää osia. - invalid_uri: pitää olla validi URI. - relative_uri: pitää olla täydellinen URI. - secured_uri: pitää olla HTTPS/SSL URI. + invalid_uri: on oltava kelvollinen URI. + relative_uri: on oltava täydellinen URI. + secured_uri: on oltava HTTPS/SSL-URI. doorkeeper: applications: buttons: @@ -25,89 +27,93 @@ fi: confirmations: destroy: Oletko varma? edit: - title: Muokkaa applikaatiota + title: Muokkaa sovellusta form: - error: Whoops! Tarkista lomakkeesi mahdollisten virheiden varalta + error: Hups! Tarkista, että lomakkeessa ei ole virheitä help: native_redirect_uri: Käytä %{native_redirect_uri} paikallisiin testeihin - redirect_uri: Käytä yhtä riviä per URI - scopes: Erota scopet välilyönnein. Jätä tyhjäksi käyteksi oletus scopeja. + redirect_uri: Lisää jokainen URI omalle rivilleen + scopes: Erota oikeudet välilyönnein. Jos kenttä jätetään tyhjäksi, käytetään oletusoikeuksia. index: - callback_url: Callback URL + application: Sovellus + callback_url: Takaisinkutsu-URL + delete: Poista name: Nimi - new: Uusi applikaatio - title: Sinun applikaatiosi + new: Uusi sovellus + scopes: Oikeudet + show: Näytä + title: Omat sovellukset new: - title: Uusi applikaatio + title: Uusi sovellus show: actions: Toiminnot - application_id: Applikaation Id - callback_urls: Callback urls - scopes: Scopet - secret: Salainen avain - title: 'Applikaatio: %{name}' + application_id: Asiakasohjelman tunnus + callback_urls: Takaisinkutsu-URL:t + scopes: Oikeudet + secret: Asiakasohjelman salainen avain + title: 'Sovellus: %{name}' authorizations: buttons: authorize: Valtuuta deny: Evää error: - title: Virhe on tapahtunut + title: Tapahtui virhe new: able_to: Se voi - prompt: Applikaatio %{client_name} pyytää lupaa tilillesi + prompt: Sovellus %{client_name} pyytää lupaa käyttää tiliäsi title: Valtuutus vaaditaan show: - title: Kopioi tämä valtuutuskoodi ja liitä se applikaatioon. + title: Kopioi tämä valtuutuskoodi ja liitä se sovellukseen. authorized_applications: buttons: - revoke: Evää + revoke: Peru confirmations: revoke: Oletko varma? index: - application: Applikaatio + application: Sovellus created_at: Valtuutettu date_format: "%Y-%m-%d %H:%M:%S" - scopes: Scopet - title: Valtuuttamasi applikaatiot + scopes: Oikeudet + title: Valtuutetut sovellukset errors: messages: - access_denied: Resurssin omistaja tai valtuutus palvelin hylkäsi pyynnönr. - credential_flow_not_configured: Resurssin omistajan salasana epäonnistui koska Doorkeeper.configure.resource_owner_from_credentials ei ole konfiguroitu. - invalid_client: Asiakkaan valtuutus epäonnistui koska tuntematon asiakas, asiakas ei sisältänyt valtuutusta, tai tukematon valtuutus tapa. - invalid_grant: Antamasi valtuutus lupa on joko väärä, erääntynyt, peruttu, ei vastaa uudelleenohjaus URI jota käytetään valtuutus pyynnössä, tai se myönnettin toiselle asiakkaalle. - invalid_redirect_uri: Uudelleenohjaus uri ei ole oikein. - invalid_request: Pyynnöstä puutti parametri, sisältää tukemattoman parametri arvonn, tai on korruptoitunut. - invalid_resource_owner: Annetut resurssin omistajan tunnnukset ovat väärät, tai resurssin omistajaa ei löydy - invalid_scope: Pyydetty scope on väärä, tuntemat, tai korruptoitunut. + access_denied: Resurssin omistaja tai valtuutuspalvelin hylkäsi pyynnön. + credential_flow_not_configured: Resurssin omistajan salasana epäonnistui, koska asetusta Doorkeeper.configure.resource_owner_from_credentials ei ole konfiguroitu. + invalid_client: Asiakasohjelman valtuutus epäonnistui, koska asiakas on tuntematon, asiakkaan valtuutus ei ollut mukana tai valtuutustapaa ei tueta. + invalid_grant: Valtuutuslupa on virheellinen, umpeutunut, peruttu, valtuutuspyynnössä käytettyä uudelleenohjaus-URI:tä vastaamaton tai myönnetty toiselle asiakkaalle. + invalid_redirect_uri: Uudelleenohjaus-URI on virheellinen. + invalid_request: Pyynnöstä puuttuu vaadittu parametri, se sisältää tukemattoman parametriarvon tai on muulla tavoin väärin muotoiltu. + invalid_resource_owner: Annetut resurssin omistajan tunnnukset ovat virheelliset, tai resurssin omistajaa ei löydy + invalid_scope: Pyydetyt oikeudet ovat virheellisiä, tuntemattomia tai väärin muotoiltuja. invalid_token: - expired: Access token vanhentunut - revoked: Access token evätty - unknown: Access token väärä - resource_owner_authenticator_not_configured: Resurssin omistajan etsiminen epäonnistui koska Doorkeeper.configure.resource_owner_authenticator ei ole konfiguroitu. - server_error: Valtuutus palvelin kohtasi odottamattoman virheen joka esti sitä täyttämästä pyyntöä. - temporarily_unavailable: Valtuutus palvelin ei voi tällä hetkellä käsitellä pyyntöäsi joko väliaikaisen ruuhkan tai huollon takia. - unauthorized_client: Asiakas ei ole valtuutettu tekemään tätä pyyntöä käyttäen tätä metodia. - unsupported_grant_type: Valtuutus grant type ei ole tuettu valtuutus palvelimella. - unsupported_response_type: Valtuutus palvelin ei tue tätä vastaus tyyppiä. + expired: Käyttöoikeustunnus on vanhentunut + revoked: Käyttöoikeustunnus on peruttu + unknown: Käyttöoikeustunnus on virheellinen + resource_owner_authenticator_not_configured: Resurssin omistajaa ei löytynyt, koska asetusta Doorkeeper.configure.resource_owner_authenticator ei ole konfiguroitu. + server_error: Valtuutuspalvelin kohtasi odottamattoman virheen, joka esti pyynnön täyttämisen. + temporarily_unavailable: Valtuutuspalvelin ei voi tällä hetkellä käsitellä pyyntöä joko väliaikaisen ruuhkan tai huollon takia. + unauthorized_client: Asiakkaalla ei ole valtuuksia tehdä tätä pyyntöä tällä metodilla. + unsupported_grant_type: Valtuutuspalvelin ei tue tätä valtuutusluvan tyyppiä. + unsupported_response_type: Valtuutuspalvelin ei tue tätä vastauksen tyyppiä. flash: applications: create: - notice: Applikaatio luotu. + notice: Sovellus luotu. destroy: - notice: Applikaatio poistettu. + notice: Sovellus poistettu. update: - notice: Applikaatio päivitetty. + notice: Sovellus päivitetty. authorized_applications: destroy: - notice: Applikaatio tuhottu. + notice: Sovellus peruttu. layouts: admin: nav: - applications: Applikaatiot - oauth2_provider: OAuth2 Provider + applications: Sovellukset + oauth2_provider: OAuth2-palveluntarjoaja application: - title: OAuth valtuutus tarvitaan + title: OAuth-valtuutus tarvitaan scopes: - follow: seuraa, estä, peru esto ja lopeta tilien seuraaminen - read: lukea tilin dataa + follow: seurata, estää, perua eston ja lopettaa tilien seuraaminen + read: lukea tilin tietoja write: julkaista puolestasi diff --git a/config/locales/doorkeeper.sk.yml b/config/locales/doorkeeper.sk.yml index 7a285eb..bda2642 100644 --- a/config/locales/doorkeeper.sk.yml +++ b/config/locales/doorkeeper.sk.yml @@ -63,7 +63,7 @@ sk: prompt: Aplikácia %{client_name} žiada prístup k vašemu účtu title: Je potrebná autorizácia show: - title: Skopírujte tento autorizačný kód a vložte ho do aplikácie. + title: Skopíruj tento autorizačný kód a vlož ho do aplikácie. authorized_applications: buttons: revoke: Zrušiť oprávnenie diff --git a/config/locales/eo.yml b/config/locales/eo.yml index a896592..27c62f8 100644 --- a/config/locales/eo.yml +++ b/config/locales/eo.yml @@ -24,12 +24,12 @@ eo: within_reach_body: Pluraj aplikaĵoj por iOS, Android, kaj aliaj platformoj danke al API-medio bonveniga por programistoj permesas resti en kontakto kun viaj amikoj ĉie. within_reach_title: Ĉiam kontaktebla generic_description: "%{domain} estas unu servilo en la reto" - hosted_on: Mastodon gastigita en %{domain} + hosted_on: "%{domain} estas nodo de Mastodon" learn_more: Lerni pli other_instances: Listo de nodoj source_code: Fontkodo status_count_after: mesaĝoj - status_count_before: Kiu publikigis + status_count_before: Kie skribiĝis user_count_after: uzantoj user_count_before: Hejmo de what_is_mastodon: Kio estas Mastodon? @@ -358,7 +358,7 @@ eo: warning: Estu tre atenta kun ĉi tiu datumo. Neniam diskonigu ĝin al iu ajn! your_token: Via alira ĵetono auth: - agreement_html: Per registriĝo, vi konsentas kun la reguloj de la nodo kaj niaj uzkondiĉoj. + agreement_html: Per registriĝo, vi konsentas kun la reguloj de nia nodo kaj niaj uzkondiĉoj. change_password: Pasvorto confirm_email: Konfirmi retadreson delete_account: Forigi konton diff --git a/config/locales/es.yml b/config/locales/es.yml index 8e7a766..a5a20aa 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -546,7 +546,7 @@ es: quadrillion: Q thousand: K trillion: T - unit: '' + unit: " " pagination: newer: Más nuevo next: Próximo @@ -634,6 +634,15 @@ es: two_factor_authentication: Autenticación de dos factores your_apps: Tus aplicaciones statuses: + attached: + description: 'Adjunto: %{attached}' + image: + one: "%{count} imagen" + other: "%{count} imágenes" + video: + one: "%{count} vídeo" + other: "%{count} vídeos" + content_warning: 'Alerta de contenido: %{warning}' open_in_web: Abrir en web over_character_limit: Límite de caracteres de %{max} superado pin_errors: @@ -682,7 +691,7 @@ es: backup_ready: explanation: Has solicitado una copia completa de tu cuenta de Mastodon. ¡Ya está preparada para descargar! subject: Tu archivo está preparado para descargar - title: Recogida del archivo + title: Descargar archivo welcome: edit_profile_action: Configurar el perfil edit_profile_step: Puedes personalizar tu perfil subiendo un avatar, cabecera, cambiando tu nombre para mostrar y más. Si te gustaría revisar seguidores antes de autorizarlos a que te sigan, puedes bloquear tu cuenta. diff --git a/config/locales/fi.yml b/config/locales/fi.yml index 939ebd1..62f6560 100644 --- a/config/locales/fi.yml +++ b/config/locales/fi.yml @@ -1,31 +1,35 @@ --- fi: about: - about_mastodon_html: Mastodon on vapaa, avoimeen lähdekoodiin perustuva sosiaalinen verkosto. Hajautettu vaihtoehto kaupallisille alustoille, se välttää eiskit yhden yrityksen monopolisoinnin sinun viestinnässäsi. Valitse palvelin mihin luotat — minkä tahansa valitset, voit vuorovaikuttaa muiden kanssa. Kuka tahansa voi luoda Mastodon palvelimen ja ottaa osaa sosiaaliseen verkkoon saumattomasti. + about_hashtag_html: Nämä ovat hashtagilla #%{hashtag} merkittyjä julkisia tuuttauksia. Voit vastata niihin, jos sinulla on tili jossain päin fediversumia. + about_mastodon_html: Mastodon on sosiaalinen verkosto. Se on toteutettu avoimilla verkkoprotokollilla ja vapailla, avoimen lähdekoodin ohjelmistoilla, ja se toimii hajautetusti samaan tapaan kuin sähköposti. about_this: Tietoja tästä palvelimesta - closed_registrations: Rekisteröityminen tässä instanssissa on juuri nyt suljettu. Mutta! Voit yhdistää täysin samaan, yhteiseen verkostoon rekisteröitymällä jossain toisessa instanssissa. + closed_registrations: Tähän instanssiin ei voi tällä hetkellä rekisteröityä. Voit kuitenkin luoda tilin johonkin toiseen instanssiin ja käyttää samaa verkostoa sitä kautta. contact: Ota yhteyttä contact_missing: Ei asetettu contact_unavailable: Ei saatavilla description_headline: Mikä on %{domain}? - domain_count_after: muuhun palvelimeen + domain_count_after: muuhun instanssiin domain_count_before: Yhdistyneenä extended_description_html: |

Hyvä paikka säännöille

-

Pidennettyä kuvausta ei ole vielä asetettu.

+

Pidempää kuvausta ei ole vielä laadittu.

features: - humane_approach_body: Muiden verkostojen virheistä oppien, Mastodon pyrkii tekemään eettisiä valintoja suunnittelussa taistellakseen sosiaalisen median väärinkäyttöä vastaan. - humane_approach_title: Humaanimpi lähestymistapa - not_a_product_body: Mastodon ei ole kaupallinen verkosto. Ei mainoksia, ei tiedonlouhintaa, ei suljettuja sisäpiirejä. Mastodonissa ei ole keskitettyä auktoriteettiä. + humane_approach_body: Mastodonissa otetaan oppia muiden verkostojen virheistä, ja sen suunnittelussa pyritään toimimaan eettisesti ja ehkäisemään sosiaalisen median väärinkäyttöä. + humane_approach_title: Ihmisläheisempi ote + not_a_product_body: Mastodon ei ole kaupallinen verkosto. Ei mainoksia, ei tiedonlouhintaa, ei suljettuja protokollia. Mastodonissa ei ole keskusjohtoa. not_a_product_title: Olet henkilö, et tuote - real_conversation_title: Rakennettu oikealle keskustelulle - within_reach_body: Kehittäjäystävällisen rajapintaekosysteemin ansiosta useita appeja Androidille, iOS:lle ja muille alustoille, jotka mahdollistavat yhteydenpidon ystäviesi kanssa missä vain. + real_conversation_body: 'Voit ilmaista itseäsi niin kuin itse haluat: tilaa on 500 merkkiä, ja sisältövaroituksia voi tehdä monin tavoin.' + real_conversation_title: Tehty oikeaa keskustelua varten + within_reach_body: Rajapintoja on tarjolla moniin eri kehitysympäristöihin, minkä ansiosta iOS:lle, Androidille ja muille alustoille on saatavana useita eri sovelluksia. Näin voit pitää yhteyttä ystäviisi missä vain. within_reach_title: Aina lähellä + generic_description: "%{domain} on yksi verkostoon kuuluvista palvelimista" + hosted_on: Mastodon palvelimella %{domain} learn_more: Lisätietoja other_instances: Muut palvelimet source_code: Lähdekoodi status_count_after: statusta - status_count_before: Ovat luoneet + status_count_before: He ovat luoneet user_count_after: käyttäjälle user_count_before: Koti what_is_mastodon: Mikä on Mastodon? @@ -33,161 +37,681 @@ fi: follow: Seuraa followers: Seuraajat following: Seuratut + media: Media + moved_html: "%{name} on muuttanut osoitteeseen %{new_profile_link}:" nothing_here: Täällä ei ole mitään! - people_followed_by: Henkilöitä joita %{name} seuraa - people_who_follow: Henkilöt jotka seuraa %{name} - posts: Postaukset + people_followed_by: Henkilöt, joita %{name} seuraa + people_who_follow: Käyttäjän %{name} seuraajat + posts: Tuuttaukset + posts_with_replies: Tuuttaukset ja vastaukset remote_follow: Etäseuranta reserved_username: Käyttäjänimi on varattu roles: admin: Ylläpitäjä + moderator: Moderaattori unfollow: Lopeta seuraaminen admin: account_moderation_notes: account: Moderaattori create: Luo created_at: Päiväys - created_msg: Moderointimerkintä luotu onnistuneesti! + created_msg: Moderointimerkinnän luonti onnistui! delete: Poista - destroyed_msg: Moderointimerkintä tuhottu onnistuneesti! + destroyed_msg: Moderointimerkinnän poisto onnistui! accounts: are_you_sure: Oletko varma? - confirm: Hyväksy - confirmed: Hyväksytty + by_domain: Verkko-osoite + confirm: Vahvista + confirmed: Vahvistettu + demote: Alenna disable: Poista käytöstä disable_two_factor_authentication: Poista 2FA käytöstä disabled: Poistettu käytöstä + display_name: Näyttönimi + domain: Verkko-osoite edit: Muokkaa email: Sähköposti + enable: Ota käyttöön + enabled: Käytössä + feed_url: Syötteen URL followers: Seuraajat - followers_url: Seuraajat URL + followers_url: Seuraajien URL + follows: Seuraa + inbox_url: Saapuvan postilaatikon URL + ip: IP + location: + all: Kaikki + local: Paikalliset + remote: Etätilit + title: Sijainti + login_status: Sisäänkirjautumisen tila + media_attachments: Medialiitteet + memorialize: Muuta muistosivuksi + moderation: + all: Kaikki + silenced: Hiljennetty + suspended: Jäähyllä + title: Moderointi + moderation_notes: Moderointimerkinnät + most_recent_activity: Viimeisin toiminta + most_recent_ip: Viimeisin IP + not_subscribed: Ei tilaaja + order: + alphabetic: Aakkosjärjestys + most_recent: Uusin + title: Järjestys + outbox_url: Lähtevän postilaatikon URL + perform_full_suspension: Siirrä kokonaan jäähylle + profile_url: Profiilin URL + promote: Ylennä + protocol: Protokolla + public: Julkinen + push_subscription_expires: PuSH-tilaus vanhenee + redownload: Päivitä profiilikuva + reset: Palauta + reset_password: Palauta salasana + resubscribe: Tilaa uudelleen + role: Oikeudet + roles: + admin: Ylläpitäjä + moderator: Moderaattori + staff: Henkilöstö + user: Käyttäjä + salmon_url: Salmon-URL + search: Haku + shared_inbox_url: Jaetun saapuvan postilaatikon URL + show: + created_reports: Tilin luomat raportit + report: raportti + targeted_reports: Tästä tilistä tehdyt raportit + silence: Hiljennä + statuses: Tilat + subscribe: Tilaa + title: Tilit + undo_silenced: Peru hiljennys + undo_suspension: Peru jäähy + unsubscribe: Lopeta tilaus + username: Käyttäjänimi + web: Web + action_logs: + actions: + confirm_user: "%{name} vahvisti käyttäjän %{target} sähköpostiosoitteen" + create_custom_emoji: "%{name} lähetti uuden emojin %{target}" + create_domain_block: "%{name} esti verkkotunnuksen %{target}" + create_email_domain_block: "%{name} lisäsi sähköpostiverkkotunnuksen %{target} estolistalle" + demote_user: "%{name} alensi käyttäjän %{target}" + destroy_domain_block: "%{name} poisti verkkotunnuksen %{target} eston" + destroy_email_domain_block: "%{name} lisäsi sähköpostiverkkotunnuksen %{target} sallittujen listalle" + destroy_status: "%{name} poisti käyttäjän %{target} tilan" + disable_2fa_user: "%{name} poisti käyttäjältä %{target} kaksivaiheisen todentamisen vaatimuksen" + disable_custom_emoji: "%{name} poisti emojin %{target} käytöstä" + disable_user: "%{name} poisti sisäänkirjautumisen käytöstä käyttäjältä %{target}" + enable_custom_emoji: "%{name} salli emojin %{target} käyttöön" + enable_user: "%{name} salli sisäänkirjautumisen käyttäjälle %{target}" + memorialize_account: "%{name} muutti käyttäjän %{target} tilin muistosivuksi" + promote_user: "%{name} ylensi käyttäjän %{target}" + reset_password_user: "%{name} palautti käyttäjän %{target} salasanan" + resolve_report: "%{name} hylkäsi raportin %{target}" + silence_account: "%{name} hiljensi käyttäjän %{target}" + suspend_account: "%{name} siirsi käyttäjän %{target} jäähylle" + unsilence_account: "%{name} poisti käyttäjän %{target} hiljennyksen" + unsuspend_account: "%{name} perui käyttäjän %{target} jäähyn" + update_custom_emoji: "%{name} päivitti emojin %{target}" + update_status: "%{name} päivitti käyttäjän %{target} tilan" + title: Auditointiloki + custom_emojis: + by_domain: Verkkotunnus + copied_msg: Emojin paikallisen kopion luonti onnistui + copy: Kopioi + copy_failed_msg: Emojista ei voitu tehdä paikallista kopiota + created_msg: Emojin luonti onnistui! + delete: Poista + destroyed_msg: Emojon poisto onnistui! + disable: Poista käytöstä + disabled_msg: Emojin käytöstäpoisto onnistui + emoji: Emoji + enable: Ota käyttöön + enabled_msg: Emojin käyttöönotto onnistui + image_hint: PNG enintään 50 kt + listed: Listassa + new: + title: Lisää uusi mukautettu emoji + overwrite: Kirjoita yli + shortcode: Lyhennekoodi + shortcode_hint: Vähintään kaksi merkkiä, vain kirjaimia, numeroita ja alaviivoja + title: Mukautetut emojit + unlisted: Ei listassa + update_failed_msg: Emojin päivitys epäonnistui + updated_msg: Emojin päivitys onnistui! + upload: Lähetä + domain_blocks: + add_new: Lisää uusi + created_msg: Verkkotunnuksen estoa käsitellään + destroyed_msg: Verkkotunnuksen esto on peruttu + domain: Verkkotunnus + new: + create: Luo esto + hint: Verkkotunnuksen esto ei estä tilien luomista ja lisäämistä tietokantaan, mutta se soveltaa näihin tileihin automaattisesti määrättyjä moderointitoimia tilin luomisen jälkeen. + severity: + desc_html: "Hiljennys estää tilin julkaisuja näkymästä muille kuin tilin seuraajille. Jäähy poistaa tilin kaiken sisällön, median ja profiilitiedot. Jos haluat vain hylätä mediatiedostot, valitse Ei mitään." + noop: Ei mitään + silence: Hiljennys + suspend: Jäähy + title: Uusi verkkotunnuksen esto + reject_media: Hylkää mediatiedostot + reject_media_hint: Poistaa paikallisesti tallennetut mediatiedostot eikä lataa niitä enää jatkossa. Ei merkitystä jäähyn kohdalla + severities: + noop: Ei mitään + silence: Hiljennys + suspend: Jäähy + severity: Vakavuus + show: + affected_accounts: + one: Vaikuttaa yhteen tiliin tietokannassa + other: Vaikuttaa %{count} tiliin tietokannassa + retroactive: + silence: Peru kaikkien tässä verkkotunnuksessa jo olemassa olevien tilien hiljennys + suspend: Peru kaikkien tässä verkkotunnuksessa jo olemassa olevien tilien jäähy + title: Peru verkkotunnuksen %{domain} esto + undo: Peru + title: Verkkotunnusten estot + undo: Peru + email_domain_blocks: + add_new: Lisää uusi + created_msg: Sähköpostiverkkotunnuksen lisäys estolistalle onnistui + delete: Poista + destroyed_msg: Sähköpostiverkkotunnuksen poisto estolistalta onnistui + domain: Verkkotunnus + new: + create: Lisää verkkotunnus + title: Uusi sähköpostiestolistan merkintä + title: Sähköpostiestolista + instances: + account_count: Tiedossa olevat tilit + domain_name: Verkkotunnus + reset: Palauta + search: Hae + title: Tiedossa olevat instanssit + invites: + filter: + all: Kaikki + available: Saatavilla + expired: Vanhentunut + title: Suodata + title: Kutsut + reports: + action_taken_by: Toimenpiteen tekijä + are_you_sure: Oletko varma? + comment: + label: Kommentti + none: Ei mitään + delete: Poista + id: Tunniste + mark_as_resolved: Merkitse ratkaistuksi + nsfw: + 'false': Peru medialiitteiden piilotus + 'true': Piilota medialiitteet + report: Raportti nro %{id} + report_contents: Sisältö + reported_account: Raportoitu tili + reported_by: Raportoija + resolved: Ratkaistut + silence_account: Hiljennä tili + status: Tila + suspend_account: Siirrä tili jäähylle + target: Kohde + title: Raportit + unresolved: Ratkaisemattomat + view: Näytä + settings: + activity_api_enabled: + desc_html: Paikallisesti julkaistujen tilojen, aktiivisten käyttäjien ja uusien rekisteröintien määrät viikoittain + title: Julkaise koostetilastoja käyttäjien aktiivisuudesta + bootstrap_timeline_accounts: + desc_html: Erota käyttäjänimet pilkulla. Vain paikalliset ja lukitsemattomat tilit toimivat. Jos kenttä jätetään tyhjäksi, oletusarvona ovat kaikki paikalliset ylläpitäjät. + title: Uudet käyttäjät seuraavat oletuksena seuraavia tilejä + contact_information: + email: Työsähköposti + username: Yhteyshenkilön käyttäjänimi + hero: + desc_html: Näytetään etusivulla. Suosituskoko vähintään 600x100 pikseliä. Jos kuvaa ei aseteta, käytetään instanssin pikkukuvaa + title: Sankarin kuva + peers_api_enabled: + desc_html: Verkkotunnukset, jotka tämä instanssi on kohdannut fediversumissa + title: Julkaise löydettyjen instanssien luettelo + registrations: + closed_message: + desc_html: Näytetään etusivulla, kun rekisteröinti on suljettu. HTML-tagit käytössä + title: Viesti, kun rekisteröinti on suljettu + deletion: + desc_html: Salli jokaisen poistaa oma tilinsä + title: Avoin tilin poisto + min_invite_role: + disabled: Ei kukaan + title: Salli kutsut käyttäjältä + open: + desc_html: Salli kenen tahansa luoda tili + title: Avoin rekisteröinti + show_known_fediverse_at_about_page: + desc_html: Kun tämä on valittu, esikatselussa näytetään tuuttaukset kaikkialta tunnetusta fediversumista. Muutoin näytetään vain paikalliset tuuttaukset. + title: Näytä aikajanan esikatselussa koko tunnettu fediversumi + show_staff_badge: + desc_html: Näytä käyttäjäsivulla henkilöstömerkki + title: Näytä henkilöstömerkki + site_description: + desc_html: Esittelykappale etusivulla ja metatunnisteissa. HTML-tagit käytössä, tärkeimmät ovat <a> ja <em>. + title: Instanssin kuvaus + site_description_extended: + desc_html: Hyvä paikka käytösohjeille, säännöille, ohjeistuksille ja muille instanssin muista erottaville asioille. HTML-tagit käytössä + title: Omavalintaiset laajat tiedot + site_terms: + desc_html: Tähän voi kirjoittaa instanssin tietosuojakäytännöstä, käyttöehdoista ja sen sellaisista asioista. HTML-tagit käytössä + title: Omavalintaiset käyttöehdot + site_title: Instanssin nimi + thumbnail: + desc_html: Käytetään esikatseluissa OpenGraphin ja API:n kautta. Suosituskoko 1200x630 pikseliä + title: Instanssin pikkukuva + timeline_preview: + desc_html: Näytä julkinen aikajana aloitussivulla + title: Aikajanan esikatselu + title: Sivuston asetukset + statuses: + back_to_account: Takaisin tilin sivulle + batch: + delete: Poista + nsfw_off: NSFW POIS + nsfw_on: NSFW PÄÄLLÄ + execute: Suorita + failed_to_execute: Suoritus epäonnistui + media: + hide: Piilota media + show: Näytä media + title: Media + no_media: Ei mediaa + title: Tilin tilat + with_media: Sisältää mediaa + subscriptions: + callback_url: Paluu-URL + confirmed: Vahvistettu + expires_in: Vanhenee + last_delivery: Viimeisin toimitus + title: WebSub + topic: Aihe + title: Ylläpito + admin_mailer: + new_report: + body: "%{reporter} on raportoinut kohteen %{target}" + subject: Uusi raportti instanssista %{instance} (nro %{id}) application_mailer: - settings: 'Muokkaa sähköpostiasetuksia: %{link}' - view: 'Katso:' + notification_preferences: Muuta sähköpostiasetuksia + salutation: "%{name}," + settings: 'Muuta sähköpostiasetuksia: %{link}' + view: 'Näytä:' + view_profile: Näytä profiili + view_status: Näytä tila applications: - invalid_url: Annettu URL on väärä + created: Sovelluksen luonti onnistui + destroyed: Sovelluksen poisto onnistui + invalid_url: Annettu URL on virheellinen + regenerate_token: Luo pääsytunnus uudelleen + token_regenerated: Pääsytunnuksen uudelleenluonti onnistui + warning: Säilytä tietoa hyvin. Älä milloinkaan jaa sitä muille! + your_token: Pääsytunnus auth: - didnt_get_confirmation: Etkö saanut varmennusohjeita? + agreement_html: Rekisteröityessäsi sitoudut noudattamaan instanssin sääntöjä ja käyttöehtoja. + change_password: Salasana + confirm_email: Vahvista sähköpostiosoite + delete_account: Poista tili + delete_account_html: Jos haluat poistaa tilisi, paina tästä. Poisto on vahvistettava. + didnt_get_confirmation: Etkö saanut vahvistusohjeita? forgot_password: Unohditko salasanasi? + invalid_reset_password_token: Salasananpalautustunnus on virheellinen tai vanhentunut. Pyydä uusi. login: Kirjaudu sisään logout: Kirjaudu ulos + migrate_account: Muuta toiseen tiliin + migrate_account_html: Jos haluat ohjata tämän tilin toiseen tiliin, voit asettaa toisen tilin tästä. + or: tai + or_log_in_with: Tai käytä kirjautumiseen + providers: + cas: CAS + saml: SAML register: Rekisteröidy - resend_confirmation: Lähetä varmennusohjeet uudestaan + register_elsewhere: Rekisteröidy toiselle palvelimelle + resend_confirmation: Lähetä vahvistusohjeet uudestaan reset_password: Palauta salasana security: Tunnukset set_new_password: Aseta uusi salasana authorize_follow: - error: Valitettavasti tapahtui virhe etätilin haussa. + error: Valitettavasti etätilin haussa tapahtui virhe follow: Seuraa - title: Seuraa %{acct} + follow_request: 'Olet lähettänyt seuraamispyynnön käyttäjälle:' + following: 'Onnistui! Seuraat käyttäjää:' + post_follow: + close: Tai voit sulkea tämän ikkunan. + return: Palaa käyttäjän profiiliin + web: Siirry verkkosivulle + title: Seuraa käyttäjää %{acct} datetime: distance_in_words: - about_x_hours: "%{count}t" - about_x_months: "%{count}kk" - about_x_years: "%{count}v" - almost_x_years: "%{count}v" - half_a_minute: Juuri nyt - less_than_x_minutes: "%{count}m" - less_than_x_seconds: Juuri nyt - over_x_years: "%{count}v" - x_days: "%{count}pv" - x_minutes: "%{count}m" - x_months: "%{count}kk" - x_seconds: "%{count}s" + about_x_hours: "%{count} h" + about_x_months: "%{count} kk" + about_x_years: "%{count} v" + almost_x_years: "%{count} v" + half_a_minute: Nyt + less_than_x_minutes: "%{count} m" + less_than_x_seconds: Nyt + over_x_years: "%{count} v" + x_days: "%{count} pv" + x_minutes: "%{count} m" + x_months: "%{count} kk" + x_seconds: "%{count} s" + deletes: + bad_password_msg: Hyvä yritys, hakkerit! Väärä salasana + confirm_password: Tunnistaudu syöttämällä nykyinen salasanasi + description_html: Tämä poistaa pysyvästi ja peruuttamattomasti kaiken tilisi sisällön ja poistaa tilin käytöstä. Käyttäjänimesi pysyy varattuna, jotta identiteettiäsi ei myöhemmin varasteta. + proceed: Poista tili + success_msg: Tilin poisto onnistui + warning_html: Sisällön poistaminen taataan vain tämän instanssin osalta. Jos sisältöä on jaettu paljon, siitä todennäköisesti jää jälkiä. Palvelimet, joihin ei saada yhteyttä tai jotka ovat lopettaneet päivitystesi tilaamisen, eivät päivitä tietokantojaan. + warning_title: Sisällön saatavuustieto levitetty + errors: + '403': Sinulla ei ole lupaa nähdä tätä sivua. + '404': Etsimääsi sivua ei ole olemassa. + '410': Etsimääsi sivua ei ole enää olemassa. + '422': + content: Turvallisuusvahvistus epäonnistui. Oletko estänyt evästeet? + title: Turvallisuusvahvistus epäonnistui + '429': Rajoitettu + '500': + content: Valitettavasti jokin meni pieleen meidän päässämme. + title: Sivu ei ole oikein + noscript_html: Mastodon-selainsovelluksen käyttöön vaaditaan JavaScript. Voit vaihtoehtoisesti kokeilla jotakin omalle käyttöjärjestelmällesi tehtyä Mastodonsovellusta. exports: - blocks: Estosi + archive_takeout: + date: Päiväys + download: Lataa arkisto + hint_html: Voit pyytää arkistoa omista tuuttauksistasi ja mediastasi. Vientitiedot ovat ActivityPub-muodossa, ja ne voi lukea millä tahansa yhteensopivalla ohjelmalla. + in_progress: Arkistoa kootaan... + request: Pyydä arkisto + size: Koko + blocks: Estot csv: CSV follows: Seurattavat - storage: Mediasi + mutes: Mykistetyt + storage: Media-arkisto + followers: + domain: Verkkotunnus + explanation_html: Jos haluat olla varma tilapäivitystesi yksityisyydestä, sinun täytyy tietää, ketkä seuraavat sinua. Yksityiset tilapäivityksesi lähetetään kaikkiin niihin instansseihin, joissa sinulla on seuraajia. Jos et luota siihen, että näiden instanssien ylläpitäjät tai ohjelmisto kunnioittavat yksityisyyttäsi, käy läpi seuraajaluettelosi ja poista tarvittaessa käyttäjiä. + followers_count: Seuraajien määrä + lock_link: Lukitse tili + purge: Poista seuraajista + success: + one: Estetään kevyesti seuraajia yhdestä verkkotunnuksesta... + other: Estetään kevyesti seuraajia %{count} verkkotunnuksesta... + true_privacy_html: Muista, että kunnollinen yksityisyys voidaan varmistaa vain päästä päähän -salauksella. + unlocked_warning_html: Kuka tahansa voi seurata sinua ja nähdä saman tien yksityiset tilapäivityksesi. %{lock_link}, niin voit tarkastaa ja torjua seuraajia. + unlocked_warning_title: Tiliäsi ei ole lukittu generic: - changes_saved_msg: Muutokset onnistuneesti tallennettu! - powered_by: powered by %{link} + changes_saved_msg: Muutosten tallennus onnistui! + powered_by: voimanlähteenä %{link} save_changes: Tallenna muutokset validation_errors: - one: Jokin ei ole viellä oikein! Katso virhe alapuolelta. - other: Jokin ei ole viellä oikein! Katso %{count} virhettä alapuolelta. + one: Kaikki ei ole aivan oikein! Tarkasta alla oleva virhe + other: Kaikki ei ole aivan oikein! Tarkasta alla olevat %{count} virhettä imports: - preface: Voit tuoda tiettyä dataa kaikista ihmisistä joita seuraat tai estät tilillesi tälle palvelimelle tiedostoista, jotka on luotu toisella palvelimella. - success: Datasi on onnistuneesti ladattu ja käsitellään pian + preface: Voit tuoda toisesta instanssista viemiäsi tietoja, kuten esimerkiksi seuraamiesi tai estämiesi henkilöiden listan. + success: Tietojen lähettäminen onnistui, ja ne käsitellään kohtapuoliin types: - blocking: Estetyt lista - following: Seuratut lista + blocking: Estettyjen lista + following: Seurattujen lista + muting: Mykistettyjen lista upload: Lähetä - landing_strip_html: "%{name} on käyttäjä domainilla %{link_to_root_path}. Voit seurata tai vuorovaikuttaa heidän kanssaan jos sinulla on tili yleisessä verkossa." - landing_strip_signup_html: Jos sinulla ei ole tiliä, voit rekisteröityä täällä. + in_memoriam_html: Muistoissamme. + invites: + delete: Poista käytöstä + expired: Vanhentunut + expires_in: + '1800': 30 minuuttia + '21600': 6 tuntia + '3600': 1 tunti + '43200': 12 tuntia + '86400': 1 vuorokausi + expires_in_prompt: Ei koskaan + generate: Luo + max_uses: + one: kertakäyttöinen + other: "%{count} käyttökertaa" + max_uses_prompt: Ei rajoitusta + prompt: Luo linkkejä ja jaa niiden avulla muille pääsyoikeus tähän instanssiin + table: + expires_at: Vanhenee + uses: Käytetty + title: Kutsu ihmisiä + landing_strip_html: "%{name} on käyttäjänä palvelimella %{link_to_root_path}. Voit seurata heitä tai pitää heihin yhteyttä, jos sinulla on tili missä tahansa fediversumin kolkassa." + landing_strip_signup_html: Jos sinulla ei ole tiliä, voit rekisteröityä tätä kautta. + lists: + errors: + limit: Sinulla on jo suurin sallittu määrä listoja + media_attachments: + validations: + images_and_video: Videota ei voi liittää tilapäivitykseen, jossa on jo kuvia + too_many: Tiedostoja voi liittää enintään 4 + migrations: + acct: uuden tilin käyttäjätunnus@verkkotunnus + currently_redirecting: 'Profiiliisi on asetettu uudelleenohjaus:' + proceed: Tallenna + updated_msg: Tilinsiirtoasetusten päivitys onnistui! + moderation: + title: Moderointi notification_mailer: digest: - body: 'Tässä on pieni yhteenveto palvelimelta %{instance} viimeksi kun olit paikalla %{since}:' + action: Näytä kaikki ilmoitukset + body: Tässä lyhyt yhteenveto viime käyntisi (%{since}) jälkeen tulleista viesteistä mention: "%{name} mainitsi sinut:" new_followers_summary: - one: Olet myös saanut yhden uuden seuraajan poissaollessasi! Jee! - other: Olet saanut %{count} uutta seuraajaa poissaollessasi! Loistavaa! + one: Olet myös saanut yhden uuden seuraajan! Juhuu! + other: Olet myös saanut %{count} uutta seuraajaa! Aivan mahtavaa! subject: - one: "1 uusi ilmoitus viimeisen käyntisi jälkeen \U0001F418" - other: "%{count} uutta ilmoitusta viimeisen käyntisi jälkeen \U0001F418" + one: "1 uusi ilmoitus viime käyntisi jälkeen \U0001F418" + other: "%{count} uutta ilmoitusta viime käyntisi jälkeen \U0001F418" + title: Poissaollessasi… favourite: - body: 'Statuksestasi tykkäsi %{name}:' - subject: "%{name} tykkäsi sinun statuksestasi" + body: "%{name} tykkäsi tilastasi:" + subject: "%{name} tykkäsi tilastasi" + title: Uusi tykkäys follow: body: "%{name} seuraa nyt sinua!" subject: "%{name} seuraa nyt sinua" + title: Uusi seuraaja follow_request: - body: "%{name} on pyytänyt seurata sinua" - subject: 'Odottava seuraus pyyntö: %{name}' + action: Hallinnoi seuraamispyyntöjä + body: "%{name} haluaa seurata sinua" + subject: 'Odottava seuraamispyyntö: %{name}' + title: Uusi seuraamispyyntö mention: - body: 'Sinut mainitsi %{name} postauksessa:' - subject: Sinut mainitsi %{name} + action: Vastaa + body: "%{name} mainitsi sinut:" + subject: "%{name} mainitsi sinut" + title: Uusi maininta reblog: - body: 'Sinun statustasi boostasi %{name}:' - subject: "%{name} boostasi statustasi" + body: "%{name} buustasi tilaasi:" + subject: "%{name} boostasi tilaasi" + title: Uusi buustaus number: human: decimal_units: - format: "%n%u" + format: "%n %u" units: - billion: B + billion: Mrd million: M - quadrillion: Q - thousand: K - trillion: T + quadrillion: Brd + thousand: k + trillion: B unit: '' pagination: + newer: Uudemmat next: Seuraava + older: Vanhemmat prev: Edellinen + truncate: "…" + preferences: + languages: Kielet + other: Muut + publishing: Julkaiseminen + web: Web + push_notifications: + favourite: + title: "%{name} tykkäsi tilastasi" + follow: + title: "%{name} seuraa nyt sinua" + group: + title: "%{count} ilmoitusta" + mention: + action_boost: Buustaa + action_expand: Näytä lisää + action_favourite: Tykkää + title: "%{nimi} mainitsi sinut" + reblog: + title: "%{name} buustasi tilaasi" remote_follow: - acct: Syötä sinun käyttäjänimesi@domain jos haluat seurata palvelimelta - missing_resource: Ei löydetty tarvittavaa uudelleenohjaavaa URL-linkkiä tilillesi - proceed: Siirry seuraamiseen - prompt: 'Sinä aiot seurata:' + acct: Syötä se käyttäjätunnus@verkkotunnus, josta haluat seurata + missing_resource: Vaadittavaa uudelleenohjaus-URL:ää tiliisi ei löytynyt + proceed: Siirry seuraamaan + prompt: 'Olet aikeissa seurata:' + sessions: + activity: Viimeisin toiminta + browser: Selain + browsers: + alipay: Alipay + blackberry: Blackberry + chrome: Chrome + edge: Microsoft Edge + electron: Electron + firefox: Firefox + generic: Tuntematon selain + ie: Internet Explorer + micro_messenger: MicroMessenger + nokia: Nokia S40 Ovi -selain + opera: Opera + otter: Otter + phantom_js: PhantomJS + qq: QQ Browser + safari: Safari + uc_browser: UCBrowser + weibo: Weibo + current_session: Nykyinen istunto + description: "%{selain}, %{platform}" + explanation: Nämä verkkoselaimet ovat tällä hetkellä kirjautuneet Mastodon-tilillesi. + ip: IP + platforms: + adobe_air: Adobe Air + android: Android + blackberry: Blackberry + chrome_os: ChromeOS + firefox_os: Firefox OS + ios: iOS + linux: Linux + mac: Mac + other: tuntematon järjestelmä + windows: Windows + windows_mobile: Windows Mobile + windows_phone: Windows Phone + revoke: Hylkää + revoke_success: Istunnon hylkäys onnistui + title: Istunnot settings: - authorized_apps: Valtuutetut ohjelmat + authorized_apps: Valtuutetut sovellukset back: Takaisin Mastodoniin + delete: Tilin poisto + development: Kehittäminen edit_profile: Muokkaa profiilia - export: Vie dataa - import: Tuo dataa + export: Vie tietoja + followers: Valtuutetut seuraajat + import: Tuo + migrate: Tilin muutto muualle + notifications: Ilmoitukset preferences: Ominaisuudet settings: Asetukset - two_factor_authentication: Kaksivaiheinen tunnistus + two_factor_authentication: Kaksivaiheinen todentaminen + your_apps: Omat sovellukset statuses: - open_in_web: Avaa webissä - over_character_limit: sallittu kirjanmäärä %{max} ylitetty + attached: + description: 'Liitetty: %{attached}' + image: + one: "%{count} kuva" + other: "%{count} kuvaa" + video: + one: "%{count} video" + other: "%{count} videota" + content_warning: 'Sisältövaroitus: %{warning}' + open_in_web: Avaa selaimessa + over_character_limit: merkkimäärän rajoitus %{max} ylitetty + pin_errors: + limit: Olet jo kiinnittänyt suurimman mahdollisen määrän tuuttauksia + ownership: Muiden tuuttauksia ei voi kiinnittää + private: Piilotettua tuuttausta ei voi kiinnittää + reblog: Buustausta ei voi kiinnittää show_more: Näytä lisää + title: "%{name}: ”%{quote}”" visibilities: - private: Näytä vain seuraajille + private: Vain seuraajille + private_long: Näytä vain seuraajille public: Julkinen - unlisted: Julkinen, mutta älä näytä julkisella aikajanalla + public_long: Kaikki voivat nähdä + unlisted: Listaamaton julkinen + unlisted_long: Kaikki voivat nähdä, mutta ei näytetä julkisilla aikajanoilla stream_entries: - click_to_show: Klikkaa näyttääksesi - reblogged: boosted - sensitive_content: Herkkä materiaali + click_to_show: Katso napsauttamalla + pinned: Kiinnitetty tuuttaus + reblogged: buustasi + sensitive_content: Arkaluontoista sisältöä + terms: + title: "%{instance}, käyttöehdot ja tietosuojakäytäntö" + themes: + default: Mastodon time: formats: - default: "%b %d, %Y, %H:%M" + default: "%d.%m.%Y klo %H.%M" two_factor_authentication: - description_html: Jos otat käyttöön kaksivaiheisen tunnistuksen, kirjautumiseen vaaditaan puhelin, joka voi luoda tokeneita kirjautumista varten. + code_hint: Vahvista syöttämällä todentamissovelluksen generoima koodi + description_html: Jos otat käyttöön kaksivaiheisen todentamisen, kirjautumiseen vaaditaan puhelin, jolla voidaan luoda kirjautumistunnuksia. disable: Poista käytöstä enable: Ota käyttöön - instructions_html: "Skannaa tämä QR-koodi Google Authenticator- tai vastaavaan sovellukseen puhelimellasi. Tästä hetkestä lähtien ohjelma luo koodin, mikä sinun tarvitsee syöttää sisäänkirjautuessa." + enabled: Kaksivaiheinen todentaminen käytössä + enabled_success: Kaksivaiheisen todentamisen käyttöönotto onnistui + generate_recovery_codes: Luo palautuskoodit + instructions_html: "Lue tämä QR-koodi puhelimen Google Authenticator- tai vastaavalla TOTP-sovelluksella. Sen jälkeen sovellus luo tunnuksia, joita tarvitset sisäänkirjautuessasi." + lost_recovery_codes: Palautuskoodien avulla voit käyttää tiliä, jos menetät puhelimesi. Jos olet hukannut palautuskoodit, voit luoda uudet tästä. Vanhat palautuskoodit poistetaan käytöstä. + manual_instructions: 'Jos et voi lukea QR-koodia ja haluat syöttää sen käsin, tässä on salainen koodi tekstinä:' + recovery_codes: Varapalautuskoodit + recovery_codes_regenerated: Uusien palautuskoodien luonti onnistui + recovery_instructions_html: Jos menetät puhelimesi, voit kirjautua tilillesi jollakin alla olevista palautuskoodeista. Pidä palautuskoodit hyvässä tallessa. Voit esimerkiksi tulostaa ne ja säilyttää muiden tärkeiden papereiden joukossa. + setup: Ota käyttöön + wrong_code: Annettu koodi oli virheellinen! Ovatko palvelimen aika ja laitteen aika oikein? + user_mailer: + backup_ready: + explanation: Pyysit täydellistä varmuuskopiota Mastodon-tilistäsi. Voit nyt ladata sen! + subject: Arkisto on valmiina ladattavaksi + title: Arkiston tallennus + welcome: + edit_profile_action: Aseta profiili + edit_profile_step: Voit mukauttaa profiiliasi lataamalla profiilikuvan ja otsakekuvan, muuttamalla näyttönimeäsi ym. Jos haluat hyväksyä uudet seuraajat ennen kuin he voivat seurata sinua, voit lukita tilisi. + explanation: Näillä vinkeillä pääset alkuun + final_action: Ala julkaista + final_step: 'Ala julkaista! Vaikkei sinulla olisi seuraajia, monet voivat nähdä julkiset viestisi esimerkiksi paikallisella aikajanalla ja hashtagien avulla. Kannattaa esittäytyä! Käytä hashtagia #introductions. (Jos haluat esittäytyä myös suomeksi, se kannattaa tehdä erillisessä tuuttauksessa ja käyttää hashtagia #esittely.)' + full_handle: Koko käyttäjätunnuksesi + full_handle_hint: Kerro tämä ystävillesi, niin he voivat lähettää sinulle viestejä tai löytää sinut toisen instanssin kautta. + review_preferences_action: Muuta asetuksia + review_preferences_step: Käy tarkistamassa, että asetukset ovat haluamallasi tavalla. Voit valita, missä tilanteissa haluat saada sähköpostia, mikä on julkaisujesi oletusnäkyvyys jne. Jos et saa helposti pahoinvointia, voit valita, että GIF-animaatiot toistetaan automaattisesti. + subject: Tervetuloa Mastodoniin + tip_bridge_html: Jos tulet Twitteristä, voit etsiä ystäviäsi Mastodonista siltasovelluksen avulla. Se kuitenkin löytää heidät vain, jos hekin käyttävät sitä! + tip_federated_timeline: Yleinen aikajana näyttää sisältöä koko Mastodon-verkostosta. Siinä näkyvät kuitenkin vain ne henkilöt, joita oman instanssisi käyttäjät seuraavat. Siinä ei siis näytetä aivan kaikkea. + tip_following: Oletusarvoisesti seuraat oman palvelimesi ylläpitäjiä. Etsi lisää kiinnostavia ihmisiä paikalliselta ja yleiseltä aikajanalta. + tip_local_timeline: Paikallinen aikajana näyttää instanssin %{instance} käyttäjien julkaisut. He ovat naapureitasi! + tip_mobile_webapp: Jos voit lisätä Mastodonin mobiiliselaimen kautta aloitusnäytöllesi, voit vastaanottaa push-ilmoituksia. Toiminta vastaa monin tavoin tavanomaista sovellusta! + tips: Vinkkejä + title: Tervetuloa mukaan, %name}! users: - invalid_email: Virheellinen sähköposti - invalid_otp_token: Virheellinen kaksivaihetunnistuskoodi + invalid_email: Virheellinen sähköpostiosoite + invalid_otp_token: Virheellinen kaksivaiheisen todentamisen koodi + seamless_external_login: Olet kirjautunut ulkoisen palvelun kautta, joten salasana- ja sähköpostiasetukset eivät ole käytettävissä. + signed_in_as: 'Kirjautunut henkilönä:' diff --git a/config/locales/ja.yml b/config/locales/ja.yml index 960dd38..fecf996 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -18,7 +18,7 @@ ja: features: humane_approach_body: 他の SNS の失敗から学び、Mastodon はソーシャルメディアが誤った使い方をされることの無いように倫理的な設計を目指しています。 humane_approach_title: より思いやりのある設計 - not_a_product_body: Mastodon は営利的な SNS ではありません。広告や、データの収集・解析は無く、またユーザーの囲い込みもありません。 + not_a_product_body: Mastodon は営利的な SNS ではありません。広告や、データの収集・解析は無く、またユーザーの囲い込みもありません。ここには中央権力はありません。 not_a_product_title: あなたは人間であり、商品ではありません real_conversation_body: 好きなように書ける500文字までの投稿や、文章やメディアの内容に警告をつけられる機能で、思い通りに自分自身を表現することができます。 real_conversation_title: 本当のコミュニケーションのために diff --git a/config/locales/simple_form.eo.yml b/config/locales/simple_form.eo.yml index 17862f9..41a0c26 100644 --- a/config/locales/simple_form.eo.yml +++ b/config/locales/simple_form.eo.yml @@ -14,7 +14,7 @@ eo: one: 1 signo restas other: %{count} signoj restas setting_noindex: Influas vian publikan profilon kaj mesaĝajn paĝojn - setting_theme: Influas kiel Mastodon aspektas kiam vi ensalutis en ajna aparato. + setting_theme: Influas kiel Mastodon aspektas post ensaluto de ajna aparato. imports: data: CSV-dosiero el alia nodo de Mastodon sessions: @@ -45,7 +45,7 @@ eo: setting_default_privacy: Mesaĝa videbleco setting_default_sensitive: Ĉiam marki aŭdovidaĵojn tiklaj setting_delete_modal: Montri fenestron por konfirmi antaŭ ol forigi mesaĝon - setting_display_sensitive_media: Ĉiam montri aŭdovidaĵon markitajn tiklaj + setting_display_sensitive_media: Ĉiam montri aŭdovidaĵojn markitajn tiklaj setting_noindex: Ellistiĝi de retserĉila indeksado setting_reduce_motion: Malrapidigi animaciojn setting_system_font_ui: Uzi la dekomencan tiparon de la sistemo diff --git a/config/locales/simple_form.fi.yml b/config/locales/simple_form.fi.yml index 34605c4..f48e9ab 100644 --- a/config/locales/simple_form.fi.yml +++ b/config/locales/simple_form.fi.yml @@ -3,64 +3,68 @@ fi: simple_form: hints: defaults: - avatar: PNG, GIF tai JPG. Korkeintaan 2MB. Skaalataan kokoon 400x400px - digest: Lähetetään vain pitkän poissaolon jälkeen, ja vain jos olet vastaanottanut yksityisviestejä poissaolosi aikana - display_name: Korkeintaan 30 merkkiä - header: PNG, GIF tai JPG. Korkeintaan 2MB. Skaalataan kokoon 700x335px - locked: Vaatii sinua manuaalisesti hyväksymään seuraajat - note: Korkeintaan 160 merkkiä - setting_noindex: Vaikuttaa julkiseen profiiliisi ja statuspäivityksiisi - setting_theme: Vaikuttaa siihen, miltä Mastodon näyttää kun olet kirjautuneena milllä tahansa laitteella. + avatar: PNG, GIF tai JPG. Enintään 2 Mt. Skaalataan kokoon 400 x 400 px + digest: Lähetetään vain pitkän poissaolon jälkeen ja vain, jos olet saanut suoria viestejä poissaolosi aikana + display_name: + one: 1 merkki jäljellä + other: %{count} merkkiä jäljellä + header: PNG, GIF tai JPG. Enintään 2 Mt. Skaalataan kokoon 700 x 335 px + locked: Sinun täytyy hyväksyä seuraajat manuaalisesti + note: + one: 1 merkki jäljellä + other: %{count} merkkiä jäljellä + setting_noindex: Vaikuttaa julkiseen profiiliisi ja tilasivuihisi + setting_theme: Vaikuttaa Mastodonin ulkoasuun millä tahansa laitteella kirjauduttaessa. imports: - data: CSV tiedosto, joka on tuotu toiselta Mastodon-palvelimelta + data: Toisesta Mastodon-instanssista tuotu CSV-tiedosto sessions: - otp: Syötä kaksivaiheisen tunnistuksen koodi puhelimestasi tai käytä yhtä palautuskoodeistasi. + otp: Syötä puhelimeen saamasi kaksivaiheisen tunnistautumisen koodi tai käytä palautuskoodia. user: - filtered_languages: Valitut kielet suodatetaan julkisilta aikajanoilta + filtered_languages: Valitut kielet suodatetaan pois julkisilta aikajanoilta labels: defaults: avatar: Profiilikuva - confirm_new_password: Varmista uusi salasana - confirm_password: Varmista salasana + confirm_new_password: Vahvista uusi salasana + confirm_password: Vahvista salasana current_password: Nykyinen salasana - data: Data + data: Tiedot display_name: Nimimerkki email: Sähköpostiosoite - expires_in: Vanhentuu + expires_in: Vanhenee filtered_languages: Suodatetut kielet header: Otsakekuva locale: Kieli - locked: Tee tilistä yksityinen - max_uses: Max käyttökerrat + locked: Lukitse tili + max_uses: Käyttökertoja enintään new_password: Uusi salasana note: Kuvaus - otp_attempt: Kaksivaiheinen koodi + otp_attempt: Kaksivaiheisen tunnistautumisen koodi password: Salasana - setting_auto_play_gif: Animoitujen GIFfien automaattitoisto - setting_boost_modal: Näytä vahvistusikkuna ennen boostausta - setting_default_privacy: Julkaisun yksityisyys + setting_auto_play_gif: Toista GIF-animaatiot automaattisesti + setting_boost_modal: Kysy vahvistusta ennen buustausta + setting_default_privacy: Julkaisun näkyvyys setting_default_sensitive: Merkitse media aina arkaluontoiseksi - setting_delete_modal: Näytä vahvistusikkuna ennen töötin poistamista + setting_delete_modal: Kysy vahvistusta ennen tuuttauksen poistamista setting_display_sensitive_media: Näytä aina arkaluontoiseksi merkitty media setting_noindex: Jättäydy pois hakukoneindeksoinnista - setting_reduce_motion: Vähennä liikettä animaatioissa - setting_system_font_ui: Käytä käyttöjärjestelmän oletusfonttia + setting_reduce_motion: Vähennä animaatioiden liikettä + setting_system_font_ui: Käytä järjestelmän oletusfonttia setting_theme: Sivuston teema - setting_unfollow_modal: Näytä vahvistusikkuna ennen seuraamisen lopettamista - severity: Vakavuusaste - type: Tuontityyppi + setting_unfollow_modal: Kysy vahvistusta, ennen kuin lopetat seuraamisen + severity: Vakavuus + type: Tietojen laji username: Käyttäjänimi username_or_email: Käyttäjänimi tai sähköposti interactions: must_be_follower: Estä ilmoitukset käyttäjiltä, jotka eivät seuraa sinua must_be_following: Estä ilmoitukset käyttäjiltä, joita et seuraa - must_be_following_dm: Estä suorat viestit ihmisiltä, joita et seuraa + must_be_following_dm: Estä suorat viestit käyttäjiltä, joita et seuraa notification_emails: - digest: Lähetä koosteviestejä sähköpostilla - favourite: Lähetä sähköposti, kun joku tykkää statuksestasi + digest: Lähetä koosteviestejä sähköpostitse + favourite: Lähetä sähköposti, kun joku tykkää tilastasi follow: Lähetä sähköposti, kun joku seuraa sinua follow_request: Lähetä sähköposti, kun joku pyytää seurata sinua - mention: Lähetä sähköposti, kun joku mainitsee sinut + mention: Lähetä sähköposti, kun sinut mainitaan reblog: Lähetä sähköposti, kun joku buustaa julkaisusi 'no': Ei required: diff --git a/config/locales/simple_form.sk.yml b/config/locales/simple_form.sk.yml index 7d4241b..e504c97 100644 --- a/config/locales/simple_form.sk.yml +++ b/config/locales/simple_form.sk.yml @@ -6,21 +6,21 @@ sk: avatar: PNG, GIF alebo JPG. Maximálne 2MB. Bude zmenšený na 400x400px digest: Odoslané iba v prípade dlhodobej neprítomnosti, a len ak ste obdŕžali nejaké osobné správy kým ste boli preč display_name: - one: Ostáva vám 1 znak - other: Ostáva vám %{count} znakov + one: Ostáva ti 1 znak + other: Ostáva ti %{count} znakov header: PNG, GIF alebo JPG. Maximálne 2MB. Bude zmenšený na 700x335px locked: Musíte manuálne schváliť sledujúcich note: one: Ostáva vám 1 znak - other: Ostáva vám %{count}znakov - setting_noindex: Ovplyvňuje profil a správy tak, že ich nebude možné nájsť vyhľadávaním - setting_theme: Ovplyvní ako bude Mastodon vyzerať pri prihlásení z hociktorého zariadenia. + other: Ostáva ti %{count} znakov + setting_noindex: Ovplyvňuje profil a správy tak, že ich nebude možné nájsť vyhľadávaním + setting_theme: Toto ovplyvní ako bude Mastodon vyzerať pri prihlásení z hociktorého zariadenia. imports: data: CSV súbor vyexportovaný z inej Mastodon inštancie sessions: - otp: Vložte 2FA kód z telefónu alebo použite jeden z vašich obnovovacích kódov. + otp: Napíš sem dvoj-faktorový kód z telefónu, alebo použite jeden z vašich obnovovacích kódov. user: - filtered_languages: Zaškrtnuté jazyky vám nebudú zobrazené vo verejnej časovej osi + filtered_languages: Zaškrtnuté jazyky budú pre teba vynechané nebudú z verejnej časovej osi labels: defaults: avatar: Avatar @@ -53,7 +53,7 @@ sk: setting_unfollow_modal: Zobrazovať potvrdzovacie okno pred skončením sledovania iného používateľa severity: Závažnosť type: Typ importu - username: Používateľské meno + username: Užívateľské meno username_or_email: Prezívka, alebo Email interactions: must_be_follower: Blokovať notifikácie pod používateľov, ktorí ťa nesledujú diff --git a/config/locales/sk.yml b/config/locales/sk.yml index 2ee25b3..25e6726 100644 --- a/config/locales/sk.yml +++ b/config/locales/sk.yml @@ -149,15 +149,15 @@ sk: enable_custom_emoji: "%{name} povolil emoji %{target}" enable_user: "%{name} povolil prihlásenie pre používateľa %{target}" memorialize_account: '%{name} zmenil účet %{target} na stránku "Navždy budeme spomínať"' - promote_user: "%{name} povýšil používateľa %{target}" - reset_password_user: "%{name} resetoval heslo pre používateľa %{target}" - resolve_report: "%{name} zamietol nahlásenie %{target}" - silence_account: "%{name} stíšil účet %{target}" - suspend_account: "%{name} suspendoval účet používateľa %{target}" - unsilence_account: "%{name} zrušil stíšenie účtu používateľa %{target}" - unsuspend_account: "%{name} zrušil suspendáciu účtu používateľa %{target}" - update_custom_emoji: "%{name} aktualizoval emoji %{target}" - update_status: "%{name} aktualizoval status %{target}" + promote_user: "%{name} povýšil/a používateľa %{target}" + reset_password_user: "%{name} resetoval/a heslo pre používateľa %{target}" + resolve_report: "%{name} zamietli nahlásenie %{target}" + silence_account: "%{name} utíšil/a účet %{target}" + suspend_account: "%{name} zablokoval/a účet používateľa %{target}" + unsilence_account: "%{name} zrušil/a utíšenie účtu používateľa %{target}" + unsuspend_account: "%{name} zrušil/a blokovanie účtu používateľa %{target}" + update_custom_emoji: "%{name} aktualizoval/a emoji %{target}" + update_status: "%{name} aktualizoval/a status pre %{target}" title: Kontrólny záznam custom_emojis: by_domain: Doména @@ -358,7 +358,7 @@ sk: warning: Na tieto údaje dávajte ohromný pozor. Nikdy ich s nikým nezďieľajte! your_token: Váš prístupový token auth: - agreement_html: V rámci registrácie súhlasíte, že sa budete riadiť 1 pravidlami tejto instancie 2 a taktiež 3 našími servisnými podmienkami 4. + agreement_html: V rámci registrácie súhlasíš, že sa budeš riadiť pravidlami tejto instancie, a taktiež našími servisnými podmienkami . change_password: Heslo confirm_email: Potvrdiť email delete_account: Vymazať účet @@ -366,8 +366,8 @@ sk: didnt_get_confirmation: Neobdŕžali ste kroky pre potvrdenie? forgot_password: Zabudli ste heslo? invalid_reset_password_token: Token na obnovu hesla vypršal. Prosím vypítajte si nový. - login: Prihlásenie - logout: Odhlásiť sa + login: Prihlás sa + logout: Odhlás sa migrate_account: Presunúť sa na iný účet migrate_account_html: Pokiaľ si želáte presmerovať tento účet na nejaký iný, môžete tak urobiť tu. or: alebo @@ -375,7 +375,7 @@ sk: providers: cas: CAS saml: SAML - register: Zaregistrovať sa + register: Zaregistruj sa register_elsewhere: Zaregistruj sa na inom serveri resend_confirmation: Poslať potvrdzujúce pokyny znovu reset_password: Resetovať heslo @@ -677,6 +677,7 @@ sk: full_handle: Adresa tvojho profilu v celom formáte full_handle_hint: Toto je čo musíš dať vedieť svojím priateľom aby ti mohli posielať správy, alebo ťa následovať z inej instancie. review_preferences_action: Zmeniť nastavenia + review_preferences_step: Daj si záležať na svojích nastaveniach, napríklad že aké emailové notifikácie chceš dostávať, alebo pod aký level súkromia sa tvoje príspevky majú sami automaticky zaradiť. Pokiaľ nemáš malátnosť z pohybu, môžeš si zvoliť aj automatické spúšťanie GIF animácií. subject: Vitaj na Mastodone tip_bridge_html: Ak prichádzaš z Twitteru, môžeš svojích priateľov nájsť na Mastodone pomocou tzv. mostíkovej aplikácie. Ale tá funguje iba ak ju aj oni niekedy použili! tip_federated_timeline: Federovaná os zobrazuje sieť Mastodonu až po jej hranice. Ale zahŕňa iba ľúdí ktorých ostatní okolo teba sledujú, takže predsa nieje úplne celistvá. From a1049e93807f57796eb6229f2dd1388be8b18225 Mon Sep 17 00:00:00 2001 From: Akihiko Odaki Date: Mon, 16 Apr 2018 17:04:31 +0900 Subject: [PATCH 125/381] Redirect to account status page for page of status stream entry (#7104) Commit 519119f657cf97ec187008a28dba00c1125a9292 missed a change for stream entry page. Instead of duplicating the change, redirect to account status page. It would also help crawlers (of search engines, for example) to understand a stream entry URL and its corresponding status URL points to the same page. --- app/controllers/stream_entries_controller.rb | 3 +-- spec/controllers/stream_entries_controller_spec.rb | 10 +++------- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/app/controllers/stream_entries_controller.rb b/app/controllers/stream_entries_controller.rb index f81856c..97cf850 100644 --- a/app/controllers/stream_entries_controller.rb +++ b/app/controllers/stream_entries_controller.rb @@ -15,8 +15,7 @@ class StreamEntriesController < ApplicationController def show respond_to do |format| format.html do - @ancestors = @stream_entry.activity.reply? ? cache_collection(@stream_entry.activity.ancestors(current_account), Status) : [] - @descendants = cache_collection(@stream_entry.activity.descendants(current_account), Status) + redirect_to short_account_status_url(params[:account_username], @stream_entry.activity) if @type == 'status' end format.atom do diff --git a/spec/controllers/stream_entries_controller_spec.rb b/spec/controllers/stream_entries_controller_spec.rb index f81e2be..665c5b7 100644 --- a/spec/controllers/stream_entries_controller_spec.rb +++ b/spec/controllers/stream_entries_controller_spec.rb @@ -66,16 +66,12 @@ RSpec.describe StreamEntriesController, type: :controller do describe 'GET #show' do include_examples 'before_action', :show - it 'renders with HTML' do - ancestor = Fabricate(:status) - status = Fabricate(:status, in_reply_to_id: ancestor.id) - descendant = Fabricate(:status, in_reply_to_id: status.id) + it 'redirects to status page' do + status = Fabricate(:status) get :show, params: { account_username: status.account.username, id: status.stream_entry.id } - expect(assigns(:ancestors)).to eq [ancestor] - expect(assigns(:descendants)).to eq [descendant] - expect(response).to have_http_status(:success) + expect(response).to redirect_to(short_account_status_url(status.account, status)) end it 'returns http success with Atom' do From 1a37d7e252aa41fd1c66e780e1a2a1426d8f3545 Mon Sep 17 00:00:00 2001 From: Akihiko Odaki Date: Mon, 16 Apr 2018 18:34:34 +0900 Subject: [PATCH 126/381] Fix status filtering in contexts reducer (#7149) --- app/javascript/mastodon/reducers/contexts.js | 38 ++++++++++++++++------------ 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/app/javascript/mastodon/reducers/contexts.js b/app/javascript/mastodon/reducers/contexts.js index fe8308d..c1ecf7e 100644 --- a/app/javascript/mastodon/reducers/contexts.js +++ b/app/javascript/mastodon/reducers/contexts.js @@ -21,24 +21,30 @@ const normalizeContext = (state, id, ancestors, descendants) => { }); }; -const deleteFromContexts = (state, id) => { - state.getIn(['descendants', id], ImmutableList()).forEach(descendantId => { - state = state.updateIn(['ancestors', descendantId], ImmutableList(), list => list.filterNot(itemId => itemId === id)); - }); +const deleteFromContexts = (immutableState, ids) => immutableState.withMutations(state => { + state.update('ancestors', immutableAncestors => immutableAncestors.withMutations(ancestors => { + state.update('descendants', immutableDescendants => immutableDescendants.withMutations(descendants => { + ids.forEach(id => { + descendants.get(id, ImmutableList()).forEach(descendantId => { + ancestors.update(descendantId, ImmutableList(), list => list.filterNot(itemId => itemId === id)); + }); - state.getIn(['ancestors', id], ImmutableList()).forEach(ancestorId => { - state = state.updateIn(['descendants', ancestorId], ImmutableList(), list => list.filterNot(itemId => itemId === id)); - }); + ancestors.get(id, ImmutableList()).forEach(ancestorId => { + descendants.update(ancestorId, ImmutableList(), list => list.filterNot(itemId => itemId === id)); + }); - state = state.deleteIn(['descendants', id]).deleteIn(['ancestors', id]); + descendants.delete(id); + ancestors.delete(id); + }); + })); + })); +}); - return state; -}; +const filterContexts = (state, relationship, statuses) => { + const ownedStatusIds = statuses.filter(status => status.get('account') === relationship.id) + .map(status => status.get('id')); -const filterContexts = (state, relationship) => { - return state.map( - statuses => statuses.filter( - status => status.get('account') !== relationship.id)); + return deleteFromContexts(state, ownedStatusIds); }; const updateContext = (state, status, references) => { @@ -61,11 +67,11 @@ export default function contexts(state = initialState, action) { switch(action.type) { case ACCOUNT_BLOCK_SUCCESS: case ACCOUNT_MUTE_SUCCESS: - return filterContexts(state, action.relationship); + return filterContexts(state, action.relationship, action.statuses); case CONTEXT_FETCH_SUCCESS: return normalizeContext(state, action.id, action.ancestors, action.descendants); case TIMELINE_DELETE: - return deleteFromContexts(state, action.id); + return deleteFromContexts(state, [action.id]); case TIMELINE_CONTEXT_UPDATE: return updateContext(state, action.status, action.references); default: From 1c379b7ef46edeaa7a8238902546ed7215b02fe2 Mon Sep 17 00:00:00 2001 From: ThibG Date: Mon, 16 Apr 2018 17:19:04 +0200 Subject: [PATCH 127/381] Remove extra spaces from search API queries and public account headers (fixes #7129) (#7152) --- app/services/search_service.rb | 2 +- app/views/accounts/_header.html.haml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/services/search_service.rb b/app/services/search_service.rb index 00a8b3d..5bb3959 100644 --- a/app/services/search_service.rb +++ b/app/services/search_service.rb @@ -4,7 +4,7 @@ class SearchService < BaseService attr_accessor :query, :account, :limit, :resolve def call(query, limit, resolve = false, account = nil) - @query = query + @query = query.strip @account = account @limit = limit @resolve = resolve diff --git a/app/views/accounts/_header.html.haml b/app/views/accounts/_header.html.haml index 0d3a0d0..f246f53 100644 --- a/app/views/accounts/_header.html.haml +++ b/app/views/accounts/_header.html.haml @@ -6,8 +6,8 @@ .card__bio %h1.name %span.p-name.emojify= display_name(account) - %small - %span @#{account.local_username_and_domain} + %small< + %span>< @#{account.local_username_and_domain} = fa_icon('lock') if account.locked? - if Setting.show_staff_badge From 7e0aed398f7e3e74a3c15e426082f88cf8b79bcf Mon Sep 17 00:00:00 2001 From: ThibG Date: Mon, 16 Apr 2018 21:04:24 +0200 Subject: [PATCH 128/381] Fix scrolling behavior (#7151) * Update React.JS * Use React's new lifecycles for scrollable lists * Clean up dead code * Make CodeClimate happy --- .../mastodon/components/scrollable_list.js | 32 ++++++++-------------- package.json | 4 +-- 2 files changed, 14 insertions(+), 22 deletions(-) diff --git a/app/javascript/mastodon/components/scrollable_list.js b/app/javascript/mastodon/components/scrollable_list.js index ee07106..fd6858d 100644 --- a/app/javascript/mastodon/components/scrollable_list.js +++ b/app/javascript/mastodon/components/scrollable_list.js @@ -34,7 +34,7 @@ export default class ScrollableList extends PureComponent { }; state = { - lastMouseMove: null, + fullscreen: null, }; intersectionObserverWrapper = new IntersectionObserverWrapper(); @@ -43,7 +43,6 @@ export default class ScrollableList extends PureComponent { if (this.node) { const { scrollTop, scrollHeight, clientHeight } = this.node; const offset = scrollHeight - scrollTop - clientHeight; - this._oldScrollPosition = scrollHeight - scrollTop; if (400 > offset && this.props.onLoadMore && !this.props.isLoading) { this.props.onLoadMore(); @@ -59,14 +58,6 @@ export default class ScrollableList extends PureComponent { trailing: true, }); - handleMouseMove = throttle(() => { - this._lastMouseMove = new Date(); - }, 300); - - handleMouseLeave = () => { - this._lastMouseMove = null; - } - componentDidMount () { this.attachScrollListener(); this.attachIntersectionObserver(); @@ -76,21 +67,26 @@ export default class ScrollableList extends PureComponent { this.handleScroll(); } - componentDidUpdate (prevProps) { + getSnapshotBeforeUpdate (prevProps) { const someItemInserted = React.Children.count(prevProps.children) > 0 && React.Children.count(prevProps.children) < React.Children.count(this.props.children) && this.getFirstChildKey(prevProps) !== this.getFirstChildKey(this.props); + if (someItemInserted && this.node.scrollTop > 0) { + return this.node.scrollHeight - this.node.scrollTop; + } else { + return null; + } + } + componentDidUpdate (prevProps, prevState, snapshot) { // Reset the scroll position when a new child comes in in order not to // jerk the scrollbar around if you're already scrolled down the page. - if (someItemInserted && this._oldScrollPosition && this.node.scrollTop > 0) { - const newScrollTop = this.node.scrollHeight - this._oldScrollPosition; + if (snapshot !== null) { + const newScrollTop = this.node.scrollHeight - snapshot; if (this.node.scrollTop !== newScrollTop) { this.node.scrollTop = newScrollTop; } - } else { - this._oldScrollPosition = this.node.scrollHeight - this.node.scrollTop; } } @@ -143,10 +139,6 @@ export default class ScrollableList extends PureComponent { this.props.onLoadMore(); } - _recentlyMoved () { - return this._lastMouseMove !== null && ((new Date()) - this._lastMouseMove < 600); - } - render () { const { children, scrollKey, trackScroll, shouldUpdateScroll, isLoading, hasMore, prepend, emptyMessage, onLoadMore } = this.props; const { fullscreen } = this.state; @@ -157,7 +149,7 @@ export default class ScrollableList extends PureComponent { if (isLoading || childrenCount > 0 || !emptyMessage) { scrollableArea = ( -
+
{prepend} diff --git a/package.json b/package.json index 9858d28..75e9e9f 100644 --- a/package.json +++ b/package.json @@ -83,8 +83,8 @@ "prop-types": "^15.5.10", "punycode": "^2.1.0", "rails-ujs": "^5.1.2", - "react": "^16.2.0", - "react-dom": "^16.2.0", + "react": "^16.3.0", + "react-dom": "^16.3.0", "react-hotkeys": "^0.10.0", "react-immutable-proptypes": "^2.1.0", "react-immutable-pure-component": "^1.1.1", From 3c722fe687de0c6e6ba144a8c87c34465fca3af9 Mon Sep 17 00:00:00 2001 From: Remi Rampin Date: Tue, 17 Apr 2018 04:37:51 +0200 Subject: [PATCH 129/381] Update French javascript locale file (#7165) Match config/locales/fr.yml: "private" was changed to "followers-only" by 501514960a9de238e23cd607d2e8f4c1ff9f16c1. --- app/javascript/mastodon/locales/fr.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/javascript/mastodon/locales/fr.json b/app/javascript/mastodon/locales/fr.json index e340fda..ef1c115 100644 --- a/app/javascript/mastodon/locales/fr.json +++ b/app/javascript/mastodon/locales/fr.json @@ -205,8 +205,8 @@ "privacy.change": "Ajuster la confidentialité du message", "privacy.direct.long": "N'envoyer qu'aux personnes mentionnées", "privacy.direct.short": "Direct", - "privacy.private.long": "N'envoyer qu'à vos abonné⋅e⋅s", - "privacy.private.short": "Privé", + "privacy.private.long": "Seul⋅e⋅s vos abonné⋅e⋅s verront vos statuts", + "privacy.private.short": "Abonné⋅e⋅s uniquement", "privacy.public.long": "Afficher dans les fils publics", "privacy.public.short": "Public", "privacy.unlisted.long": "Ne pas afficher dans les fils publics", From 609bf9302976768c56116f9d920f34a430d538b5 Mon Sep 17 00:00:00 2001 From: abcang Date: Tue, 17 Apr 2018 20:49:09 +0900 Subject: [PATCH 130/381] Perform processing that does not use the database before connecting to the database (#7168) --- streaming/index.js | 81 +++++++++++++++++++++++++++--------------------------- 1 file changed, 41 insertions(+), 40 deletions(-) diff --git a/streaming/index.js b/streaming/index.js index a42a912..1b4f859 100644 --- a/streaming/index.js +++ b/streaming/index.js @@ -329,52 +329,53 @@ const startWorker = (workerId) => { // Only messages that may require filtering are statuses, since notifications // are already personalized and deletes do not matter - if (needsFiltering && event === 'update') { - pgPool.connect((err, client, done) => { - if (err) { - log.error(err); - return; - } + if (!needsFiltering || event !== 'update') { + transmit(); + return; + } - const unpackedPayload = payload; - const targetAccountIds = [unpackedPayload.account.id].concat(unpackedPayload.mentions.map(item => item.id)); - const accountDomain = unpackedPayload.account.acct.split('@')[1]; + const unpackedPayload = payload; + const targetAccountIds = [unpackedPayload.account.id].concat(unpackedPayload.mentions.map(item => item.id)); + const accountDomain = unpackedPayload.account.acct.split('@')[1]; - if (Array.isArray(req.filteredLanguages) && req.filteredLanguages.indexOf(unpackedPayload.language) !== -1) { - log.silly(req.requestId, `Message ${unpackedPayload.id} filtered by language (${unpackedPayload.language})`); - done(); + if (Array.isArray(req.filteredLanguages) && req.filteredLanguages.indexOf(unpackedPayload.language) !== -1) { + log.silly(req.requestId, `Message ${unpackedPayload.id} filtered by language (${unpackedPayload.language})`); + return; + } + + // When the account is not logged in, it is not necessary to confirm the block or mute + if (!req.accountId) { + transmit(); + return; + } + + pgPool.connect((err, client, done) => { + if (err) { + log.error(err); + return; + } + + const queries = [ + client.query(`SELECT 1 FROM blocks WHERE (account_id = $1 AND target_account_id IN (${placeholders(targetAccountIds, 2)})) OR (account_id = $2 AND target_account_id = $1) UNION SELECT 1 FROM mutes WHERE account_id = $1 AND target_account_id IN (${placeholders(targetAccountIds, 2)})`, [req.accountId, unpackedPayload.account.id].concat(targetAccountIds)), + ]; + + if (accountDomain) { + queries.push(client.query('SELECT 1 FROM account_domain_blocks WHERE account_id = $1 AND domain = $2', [req.accountId, accountDomain])); + } + + Promise.all(queries).then(values => { + done(); + + if (values[0].rows.length > 0 || (values.length > 1 && values[1].rows.length > 0)) { return; } - if (req.accountId) { - const queries = [ - client.query(`SELECT 1 FROM blocks WHERE (account_id = $1 AND target_account_id IN (${placeholders(targetAccountIds, 2)})) OR (account_id = $2 AND target_account_id = $1) UNION SELECT 1 FROM mutes WHERE account_id = $1 AND target_account_id IN (${placeholders(targetAccountIds, 2)})`, [req.accountId, unpackedPayload.account.id].concat(targetAccountIds)), - ]; - - if (accountDomain) { - queries.push(client.query('SELECT 1 FROM account_domain_blocks WHERE account_id = $1 AND domain = $2', [req.accountId, accountDomain])); - } - - Promise.all(queries).then(values => { - done(); - - if (values[0].rows.length > 0 || (values.length > 1 && values[1].rows.length > 0)) { - return; - } - - transmit(); - }).catch(err => { - done(); - log.error(err); - }); - } else { - done(); - transmit(); - } + transmit(); + }).catch(err => { + done(); + log.error(err); }); - } else { - transmit(); - } + }); }; subscribe(`${redisPrefix}${id}`, listener); From 727917e91eea3582586d3ce710826ef33ae26ffb Mon Sep 17 00:00:00 2001 From: abcang Date: Tue, 17 Apr 2018 20:50:33 +0900 Subject: [PATCH 131/381] Fix caret position after inserting emoji (#7167) --- app/javascript/mastodon/actions/compose.js | 3 ++- .../mastodon/features/compose/components/compose_form.js | 9 +++++++-- .../features/compose/containers/compose_form_container.js | 4 ++-- app/javascript/mastodon/reducers/compose.js | 7 ++----- 4 files changed, 13 insertions(+), 10 deletions(-) diff --git a/app/javascript/mastodon/actions/compose.js b/app/javascript/mastodon/actions/compose.js index 59aa6f9..ea9d9f3 100644 --- a/app/javascript/mastodon/actions/compose.js +++ b/app/javascript/mastodon/actions/compose.js @@ -446,11 +446,12 @@ export function changeComposeVisibility(value) { }; }; -export function insertEmojiCompose(position, emoji) { +export function insertEmojiCompose(position, emoji, needsSpace) { return { type: COMPOSE_EMOJI_INSERT, position, emoji, + needsSpace, }; }; diff --git a/app/javascript/mastodon/features/compose/components/compose_form.js b/app/javascript/mastodon/features/compose/components/compose_form.js index fe7bb1c..39eb023 100644 --- a/app/javascript/mastodon/features/compose/components/compose_form.js +++ b/app/javascript/mastodon/features/compose/components/compose_form.js @@ -19,6 +19,8 @@ import ImmutablePureComponent from 'react-immutable-pure-component'; import { length } from 'stringz'; import { countableText } from '../util/counter'; +const allowedAroundShortCode = '><\u0085\u0020\u00a0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029\u0009\u000a\u000b\u000c\u000d'; + const messages = defineMessages({ placeholder: { id: 'compose_form.placeholder', defaultMessage: 'What is on your mind?' }, spoiler_placeholder: { id: 'compose_form.spoiler_placeholder', defaultMessage: 'Write your warning here' }, @@ -144,10 +146,13 @@ export default class ComposeForm extends ImmutablePureComponent { } handleEmojiPick = (data) => { + const { text } = this.props; const position = this.autosuggestTextarea.textarea.selectionStart; const emojiChar = data.native; - this._restoreCaret = position + emojiChar.length + 1; - this.props.onPickEmoji(position, data); + const needsSpace = data.custom && position > 0 && !allowedAroundShortCode.includes(text[position - 1]); + + this._restoreCaret = position + emojiChar.length + 1 + (needsSpace ? 1 : 0); + this.props.onPickEmoji(position, data, needsSpace); } render () { diff --git a/app/javascript/mastodon/features/compose/containers/compose_form_container.js b/app/javascript/mastodon/features/compose/containers/compose_form_container.js index ede23d3..c3aa580 100644 --- a/app/javascript/mastodon/features/compose/containers/compose_form_container.js +++ b/app/javascript/mastodon/features/compose/containers/compose_form_container.js @@ -56,8 +56,8 @@ const mapDispatchToProps = (dispatch) => ({ dispatch(uploadCompose(files)); }, - onPickEmoji (position, data) { - dispatch(insertEmojiCompose(position, data)); + onPickEmoji (position, data, needsSpace) { + dispatch(insertEmojiCompose(position, data, needsSpace)); }, }); diff --git a/app/javascript/mastodon/reducers/compose.js b/app/javascript/mastodon/reducers/compose.js index 87049ea..46d9d6c 100644 --- a/app/javascript/mastodon/reducers/compose.js +++ b/app/javascript/mastodon/reducers/compose.js @@ -36,8 +36,6 @@ import { Map as ImmutableMap, List as ImmutableList, OrderedSet as ImmutableOrde import uuid from '../uuid'; import { me } from '../initial_state'; -const allowedAroundShortCode = '><\u0085\u0020\u00a0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029\u0009\u000a\u000b\u000c\u000d'; - const initialState = ImmutableMap({ mounted: 0, sensitive: false, @@ -137,9 +135,8 @@ const updateSuggestionTags = (state, token) => { }); }; -const insertEmoji = (state, position, emojiData) => { +const insertEmoji = (state, position, emojiData, needsSpace) => { const oldText = state.get('text'); - const needsSpace = emojiData.custom && position > 0 && !allowedAroundShortCode.includes(oldText[position - 1]); const emoji = needsSpace ? ' ' + emojiData.native : emojiData.native; return state.merge({ @@ -288,7 +285,7 @@ export default function compose(state = initialState, action) { return state; } case COMPOSE_EMOJI_INSERT: - return insertEmoji(state, action.position, action.emoji); + return insertEmoji(state, action.position, action.emoji, action.needsSpace); case COMPOSE_UPLOAD_CHANGE_SUCCESS: return state .set('is_submitting', false) From bb58fc003b5d9ac521a89e7f37c0b7fc1d45a4c9 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Tue, 17 Apr 2018 13:50:48 +0200 Subject: [PATCH 132/381] Fix warning about using SQL in order for Account#partitioned (#7159) --- app/models/account.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/account.rb b/app/models/account.rb index 05e817f..a3436b4 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -114,7 +114,7 @@ class Account < ApplicationRecord scope :without_followers, -> { where(followers_count: 0) } scope :with_followers, -> { where('followers_count > 0') } scope :expiring, ->(time) { remote.where.not(subscription_expires_at: nil).where('subscription_expires_at < ?', time) } - scope :partitioned, -> { order('row_number() over (partition by domain)') } + scope :partitioned, -> { order(Arel.sql('row_number() over (partition by domain)')) } scope :silenced, -> { where(silenced: true) } scope :suspended, -> { where(suspended: true) } scope :recent, -> { reorder(id: :desc) } From aab5581c436c306e08df2668c530aab1cf526f20 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Tue, 17 Apr 2018 13:51:01 +0200 Subject: [PATCH 133/381] Set Referrer-Policy to origin in web UI and public pages of private toots (#7162) Fix #7115 --- app/controllers/home_controller.rb | 5 +++++ app/controllers/statuses_controller.rb | 6 ++++++ 2 files changed, 11 insertions(+) diff --git a/app/controllers/home_controller.rb b/app/controllers/home_controller.rb index b1f8f1a..b714241 100644 --- a/app/controllers/home_controller.rb +++ b/app/controllers/home_controller.rb @@ -2,6 +2,7 @@ class HomeController < ApplicationController before_action :authenticate_user! + before_action :set_referrer_policy_header before_action :set_initial_state_json def index @@ -62,4 +63,8 @@ class HomeController < ApplicationController about_path end end + + def set_referrer_policy_header + response.headers['Referrer-Policy'] = 'origin' + end end diff --git a/app/controllers/statuses_controller.rb b/app/controllers/statuses_controller.rb index 41f098a..a294398 100644 --- a/app/controllers/statuses_controller.rb +++ b/app/controllers/statuses_controller.rb @@ -13,6 +13,7 @@ class StatusesController < ApplicationController before_action :set_link_headers before_action :check_account_suspension before_action :redirect_to_original, only: [:show] + before_action :set_referrer_policy_header, only: [:show] before_action :set_cache_headers def show @@ -81,4 +82,9 @@ class StatusesController < ApplicationController def redirect_to_original redirect_to ::TagManager.instance.url_for(@status.reblog) if @status.reblog? end + + def set_referrer_policy_header + return if @status.public_visibility? || @status.unlisted_visibility? + response.headers['Referrer-Policy'] = 'origin' + end end From 07a7d5959cb722ca77b65b4f7458f861e4b6eb61 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Tue, 17 Apr 2018 13:51:14 +0200 Subject: [PATCH 134/381] Fix missing "Administered by" when timeline preview disabled (#7161) --- app/views/about/_administration.html.haml | 19 +++++++++++++++++++ app/views/about/show.html.haml | 30 ++++++++---------------------- 2 files changed, 27 insertions(+), 22 deletions(-) create mode 100644 app/views/about/_administration.html.haml diff --git a/app/views/about/_administration.html.haml b/app/views/about/_administration.html.haml new file mode 100644 index 0000000..ec5834f --- /dev/null +++ b/app/views/about/_administration.html.haml @@ -0,0 +1,19 @@ +.account + .account__wrapper + - if @instance_presenter.contact_account + = link_to TagManager.instance.url_for(@instance_presenter.contact_account), class: 'account__display-name' do + .account__avatar-wrapper + .account__avatar{ style: "background-image: url(#{@instance_presenter.contact_account.avatar.url})" } + %span.display-name + %bdi + %strong.display-name__html.emojify= display_name(@instance_presenter.contact_account) + %span.display-name__account @#{@instance_presenter.contact_account.acct} + - else + .account__display-name + .account__avatar-wrapper + .account__avatar{ style: "background-image: url(#{full_asset_url('avatars/original/missing.png', skip_pipeline: true)})" } + %span.display-name + %strong= t 'about.contact_missing' + %span.display-name__account= t 'about.contact_unavailable' + + = link_to t('about.learn_more'), about_more_path, class: 'button button-alternative' diff --git a/app/views/about/show.html.haml b/app/views/about/show.html.haml index 12213cd..870dafd 100644 --- a/app/views/about/show.html.haml +++ b/app/views/about/show.html.haml @@ -110,26 +110,7 @@ %p= t 'about.about_mastodon_html' %div.contact %h3= t 'about.administered_by' - - .account - .account__wrapper - - if @instance_presenter.contact_account - = link_to TagManager.instance.url_for(@instance_presenter.contact_account), class: 'account__display-name' do - .account__avatar-wrapper - .account__avatar{ style: "background-image: url(#{@instance_presenter.contact_account.avatar.url})" } - %span.display-name - %bdi - %strong.display-name__html.emojify= display_name(@instance_presenter.contact_account) - %span.display-name__account @#{@instance_presenter.contact_account.acct} - - else - .account__display-name - .account__avatar-wrapper - .account__avatar{ style: "background-image: url(#{full_asset_url('avatars/original/missing.png', skip_pipeline: true)})" } - %span.display-name - %strong= t 'about.contact_missing' - %span.display-name__account= t 'about.contact_unavailable' - - = link_to t('about.learn_more'), about_more_path, class: 'button button-alternative' + = render 'administration' = render 'features' @@ -144,8 +125,13 @@ - else .column-4.non-preview.landing-page__information .landing-page__features - %h3= t 'about.what_is_mastodon' - %p= t 'about.about_mastodon_html' + .features-list + %div + %h3= t 'about.what_is_mastodon' + %p= t 'about.about_mastodon_html' + %div.contact + %h3= t 'about.administered_by' + = render 'administration' = render 'features' From ef12a2b74c735d78371be3f52b372c03318c3b16 Mon Sep 17 00:00:00 2001 From: Jennifer Kruse Date: Tue, 17 Apr 2018 06:52:08 -0500 Subject: [PATCH 135/381] Able to deactivate invites if they aren't expired (#7163) --- app/views/invites/_invite.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/invites/_invite.html.haml b/app/views/invites/_invite.html.haml index 81d67eb..1c7ec31 100644 --- a/app/views/invites/_invite.html.haml +++ b/app/views/invites/_invite.html.haml @@ -13,5 +13,5 @@ = l invite.expires_at %td= table_link_to 'link', public_invite_url(invite_code: invite.code), public_invite_url(invite_code: invite.code) %td - - if invite.expired? && policy(invite).destroy? + - if !invite.expired? && policy(invite).destroy? = table_link_to 'times', t('invites.delete'), invite_path(invite), method: :delete From 204d72fbe49b3749e168ed2cd0ff83706946352a Mon Sep 17 00:00:00 2001 From: Emelia Smith Date: Tue, 17 Apr 2018 14:58:11 +0200 Subject: [PATCH 136/381] Feature: add count of account notes to reports (#7130) * Reports: Colocate account details with reports * Reports: Add count of account moderation notes Sometimes an account will be left with a note instead of the report, this adds a way to quickly see from a given report if this is the case. --- app/views/admin/accounts/_card.html.haml | 17 ----------------- app/views/admin/reports/_account_details.html.haml | 20 ++++++++++++++++++++ app/views/admin/reports/show.html.haml | 4 ++-- config/locales/en.yml | 10 ++++++++++ 4 files changed, 32 insertions(+), 19 deletions(-) delete mode 100644 app/views/admin/accounts/_card.html.haml create mode 100644 app/views/admin/reports/_account_details.html.haml diff --git a/app/views/admin/accounts/_card.html.haml b/app/views/admin/accounts/_card.html.haml deleted file mode 100644 index 2f59550..0000000 --- a/app/views/admin/accounts/_card.html.haml +++ /dev/null @@ -1,17 +0,0 @@ -.table-wrapper - %table.table - %tbody - %tr - %td= t('admin.accounts.show.created_reports') - %td= link_to pluralize(account.reports.count, t('admin.accounts.show.report')), admin_reports_path(account_id: account.id) - %tr - %td= t('admin.accounts.show.targeted_reports') - %td= link_to pluralize(account.targeted_reports.count, t('admin.accounts.show.report')), admin_reports_path(target_account_id: account.id) - - if account.silenced? || account.suspended? - %tr - %td= t('admin.accounts.moderation.title') - %td - - if account.silenced? - %p= t('admin.accounts.moderation.silenced') - - if account.suspended? - %p= t('admin.accounts.moderation.suspended') diff --git a/app/views/admin/reports/_account_details.html.haml b/app/views/admin/reports/_account_details.html.haml new file mode 100644 index 0000000..a8af39b --- /dev/null +++ b/app/views/admin/reports/_account_details.html.haml @@ -0,0 +1,20 @@ +.table-wrapper + %table.table + %tbody + %tr + %td= t('admin.reports.account.created_reports') + %td= link_to pluralize(account.reports.count, t('admin.reports.account.report')), admin_reports_path(account_id: account.id) + %tr + %td= t('admin.reports.account.targeted_reports') + %td= link_to pluralize(account.targeted_reports.count, t('admin.reports.account.report')), admin_reports_path(target_account_id: account.id) + %tr + %td= t('admin.reports.account.moderation_notes') + %td= link_to pluralize(account.targeted_moderation_notes.count, t('admin.reports.account.note')), admin_reports_path(target_account_id: account.id) + - if account.silenced? || account.suspended? + %tr + %td= t('admin.reports.account.moderation.title') + %td + - if account.silenced? + %p= t('admin.reports.account.moderation.silenced') + - if account.suspended? + %p= t('admin.reports.account.moderation.suspended') diff --git a/app/views/admin/reports/show.html.haml b/app/views/admin/reports/show.html.haml index d57b4ad..1306500 100644 --- a/app/views/admin/reports/show.html.haml +++ b/app/views/admin/reports/show.html.haml @@ -60,11 +60,11 @@ .report-accounts__item %h3= t('admin.reports.reported_account') = render 'authorize_follows/card', account: @report.target_account, admin: true - = render 'admin/accounts/card', account: @report.target_account + = render 'admin/reports/account_details', account: @report.target_account .report-accounts__item %h3= t('admin.reports.reported_by') = render 'authorize_follows/card', account: @report.account, admin: true - = render 'admin/accounts/card', account: @report.account + = render 'admin/reports/account_details', account: @report.account %h3= t('admin.reports.comment.label') diff --git a/config/locales/en.yml b/config/locales/en.yml index 4816cc5..20bfd0f 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -259,6 +259,16 @@ en: created_msg: Report note successfully created! destroyed_msg: Report note successfully deleted! reports: + account: + created_reports: Reports created by this account + moderation: + silenced: Silenced + suspended: Suspended + title: Moderation + moderation_notes: Moderation Notes + note: note + report: report + targeted_reports: Reports made about this account action_taken_by: Action taken by are_you_sure: Are you sure? assign_to_self: Assign to me From 897199910fc29d17b4a019b6ee2473e138d777a2 Mon Sep 17 00:00:00 2001 From: abcang Date: Tue, 17 Apr 2018 22:23:46 +0900 Subject: [PATCH 137/381] Improve web api protect (#6343) --- app/controllers/api/web/base_controller.rb | 9 +++++++++ app/controllers/api/web/embeds_controller.rb | 2 +- app/controllers/api/web/push_subscriptions_controller.rb | 3 +-- app/controllers/api/web/settings_controller.rb | 2 +- .../mastodon/actions/push_notifications/registerer.js | 10 +++++----- app/javascript/mastodon/actions/settings.js | 2 +- 6 files changed, 18 insertions(+), 10 deletions(-) create mode 100644 app/controllers/api/web/base_controller.rb diff --git a/app/controllers/api/web/base_controller.rb b/app/controllers/api/web/base_controller.rb new file mode 100644 index 0000000..8da549b --- /dev/null +++ b/app/controllers/api/web/base_controller.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class Api::Web::BaseController < Api::BaseController + protect_from_forgery with: :exception + + rescue_from ActionController::InvalidAuthenticityToken do + render json: { error: "Can't verify CSRF token authenticity." }, status: 422 + end +end diff --git a/app/controllers/api/web/embeds_controller.rb b/app/controllers/api/web/embeds_controller.rb index 2ed5161..f2fe74b 100644 --- a/app/controllers/api/web/embeds_controller.rb +++ b/app/controllers/api/web/embeds_controller.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class Api::Web::EmbedsController < Api::BaseController +class Api::Web::EmbedsController < Api::Web::BaseController respond_to :json before_action :require_user! diff --git a/app/controllers/api/web/push_subscriptions_controller.rb b/app/controllers/api/web/push_subscriptions_controller.rb index c611031..249e7c1 100644 --- a/app/controllers/api/web/push_subscriptions_controller.rb +++ b/app/controllers/api/web/push_subscriptions_controller.rb @@ -1,10 +1,9 @@ # frozen_string_literal: true -class Api::Web::PushSubscriptionsController < Api::BaseController +class Api::Web::PushSubscriptionsController < Api::Web::BaseController respond_to :json before_action :require_user! - protect_from_forgery with: :exception def create active_session = current_session diff --git a/app/controllers/api/web/settings_controller.rb b/app/controllers/api/web/settings_controller.rb index f6739d5..e3178bf 100644 --- a/app/controllers/api/web/settings_controller.rb +++ b/app/controllers/api/web/settings_controller.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class Api::Web::SettingsController < Api::BaseController +class Api::Web::SettingsController < Api::Web::BaseController respond_to :json before_action :require_user! diff --git a/app/javascript/mastodon/actions/push_notifications/registerer.js b/app/javascript/mastodon/actions/push_notifications/registerer.js index f17d929..60b215f 100644 --- a/app/javascript/mastodon/actions/push_notifications/registerer.js +++ b/app/javascript/mastodon/actions/push_notifications/registerer.js @@ -36,7 +36,7 @@ const subscribe = (registration) => const unsubscribe = ({ registration, subscription }) => subscription ? subscription.unsubscribe().then(() => registration) : registration; -const sendSubscriptionToBackend = (getState, subscription) => { +const sendSubscriptionToBackend = (subscription) => { const params = { subscription }; if (me) { @@ -46,7 +46,7 @@ const sendSubscriptionToBackend = (getState, subscription) => { } } - return api(getState).post('/api/web/push_subscriptions', params).then(response => response.data); + return api().post('/api/web/push_subscriptions', params).then(response => response.data); }; // Last one checks for payload support: https://web-push-book.gauntface.com/chapter-06/01-non-standards-browsers/#no-payload @@ -85,13 +85,13 @@ export function register () { } else { // Something went wrong, try to subscribe again return unsubscribe({ registration, subscription }).then(subscribe).then( - subscription => sendSubscriptionToBackend(getState, subscription)); + subscription => sendSubscriptionToBackend(subscription)); } } // No subscription, try to subscribe return subscribe(registration).then( - subscription => sendSubscriptionToBackend(getState, subscription)); + subscription => sendSubscriptionToBackend(subscription)); }) .then(subscription => { // If we got a PushSubscription (and not a subscription object from the backend) @@ -134,7 +134,7 @@ export function saveSettings() { const alerts = state.get('alerts'); const data = { alerts }; - api(getState).put(`/api/web/push_subscriptions/${subscription.get('id')}`, { + api().put(`/api/web/push_subscriptions/${subscription.get('id')}`, { data, }).then(() => { if (me) { diff --git a/app/javascript/mastodon/actions/settings.js b/app/javascript/mastodon/actions/settings.js index 5634a11..6bf85e4 100644 --- a/app/javascript/mastodon/actions/settings.js +++ b/app/javascript/mastodon/actions/settings.js @@ -24,7 +24,7 @@ const debouncedSave = debounce((dispatch, getState) => { const data = getState().get('settings').filter((_, path) => path !== 'saved').toJS(); - api(getState).put('/api/web/settings', { data }) + api().put('/api/web/settings', { data }) .then(() => dispatch({ type: SETTING_SAVE })) .catch(error => dispatch(showAlertForError(error))); }, 5000, { trailing: true }); From 11715454d033784bf6176b75a954e5c28b5d79e5 Mon Sep 17 00:00:00 2001 From: Sam Schlinkert Date: Tue, 17 Apr 2018 17:25:54 -0400 Subject: [PATCH 138/381] Make scroll bars wider (#7060) * Set scrollbars to 12 px wide rather than 8px Should overwrite the setting in reset.scss. This is untested at this point. * removes scrollbar height and width specifications from reset.scss and basics.scss --- app/javascript/styles/mastodon/reset.scss | 5 ----- 1 file changed, 5 deletions(-) diff --git a/app/javascript/styles/mastodon/reset.scss b/app/javascript/styles/mastodon/reset.scss index cc5ba9d..58d4de3 100644 --- a/app/javascript/styles/mastodon/reset.scss +++ b/app/javascript/styles/mastodon/reset.scss @@ -53,11 +53,6 @@ table { border-spacing: 0; } -::-webkit-scrollbar { - width: 8px; - height: 8px; -} - ::-webkit-scrollbar-thumb { background: lighten($ui-base-color, 4%); border: 0px none $base-border-color; From e5dd385431d8a3c35ceb3fd9d76fb0549c720f35 Mon Sep 17 00:00:00 2001 From: ThibG Date: Tue, 17 Apr 2018 23:35:45 +0200 Subject: [PATCH 139/381] Allow boosting own private toots (#6157) * Adjust policy to allow boosting own private toots * Add ability to reblog private toots from dropdown menu --- app/javascript/mastodon/components/status_action_bar.js | 4 ++++ app/javascript/mastodon/features/status/components/action_bar.js | 4 ++++ app/policies/status_policy.rb | 2 +- 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/app/javascript/mastodon/components/status_action_bar.js b/app/javascript/mastodon/components/status_action_bar.js index 10f34b0..e586255 100644 --- a/app/javascript/mastodon/components/status_action_bar.js +++ b/app/javascript/mastodon/components/status_action_bar.js @@ -18,6 +18,8 @@ const messages = defineMessages({ more: { id: 'status.more', defaultMessage: 'More' }, replyAll: { id: 'status.replyAll', defaultMessage: 'Reply to thread' }, reblog: { id: 'status.reblog', defaultMessage: 'Boost' }, + reblog_private: { id: 'status.reblog_private', defaultMessage: 'Boost to original audience' }, + cancel_reblog_private: { id: 'status.cancel_reblog_private', defaultMessage: 'Unboost' }, cannot_reblog: { id: 'status.cannot_reblog', defaultMessage: 'This post cannot be boosted' }, favourite: { id: 'status.favourite', defaultMessage: 'Favourite' }, open: { id: 'status.open', defaultMessage: 'Expand this status' }, @@ -150,6 +152,8 @@ export default class StatusActionBar extends ImmutablePureComponent { if (status.getIn(['account', 'id']) === me) { if (publicStatus) { menu.push({ text: intl.formatMessage(status.get('pinned') ? messages.unpin : messages.pin), action: this.handlePinClick }); + } else { + menu.push({ text: intl.formatMessage(status.get('reblog') ? messages.reblog_private : messages.cancel_reblog_private), action: this.handleReblogClick }); } menu.push({ text: intl.formatMessage(messages.delete), action: this.handleDeleteClick }); diff --git a/app/javascript/mastodon/features/status/components/action_bar.js b/app/javascript/mastodon/features/status/components/action_bar.js index 4aa6b08..fc34c8c 100644 --- a/app/javascript/mastodon/features/status/components/action_bar.js +++ b/app/javascript/mastodon/features/status/components/action_bar.js @@ -12,6 +12,8 @@ const messages = defineMessages({ mention: { id: 'status.mention', defaultMessage: 'Mention @{name}' }, reply: { id: 'status.reply', defaultMessage: 'Reply' }, reblog: { id: 'status.reblog', defaultMessage: 'Boost' }, + reblog_private: { id: 'status.reblog_private', defaultMessage: 'Boost to original audience' }, + cancel_reblog_private: { id: 'status.cancel_reblog_private', defaultMessage: 'Unboost' }, cannot_reblog: { id: 'status.cannot_reblog', defaultMessage: 'This post cannot be boosted' }, favourite: { id: 'status.favourite', defaultMessage: 'Favourite' }, mute: { id: 'status.mute', defaultMessage: 'Mute @{name}' }, @@ -120,6 +122,8 @@ export default class ActionBar extends React.PureComponent { if (me === status.getIn(['account', 'id'])) { if (publicStatus) { menu.push({ text: intl.formatMessage(status.get('pinned') ? messages.unpin : messages.pin), action: this.handlePinClick }); + } else { + menu.push({ text: intl.formatMessage(status.get('reblog') ? messages.reblog_private : messages.cancel_reblog_private), action: this.handleReblogClick }); } menu.push(null); diff --git a/app/policies/status_policy.rb b/app/policies/status_policy.rb index 0373fdf..5573289 100644 --- a/app/policies/status_policy.rb +++ b/app/policies/status_policy.rb @@ -16,7 +16,7 @@ class StatusPolicy < ApplicationPolicy end def reblog? - !direct? && !private? && show? + !direct? && (!private? || owned?) && show? end def destroy? From fad7b9f5f297f970056d86cef0139e709ed47516 Mon Sep 17 00:00:00 2001 From: Neil Moore Date: Tue, 17 Apr 2018 21:33:59 -0400 Subject: [PATCH 140/381] Adds keyboard hotkey for revealing/hiding statuses (#7173) Resolves #5550 --- app/javascript/mastodon/components/status.js | 5 +++++ app/javascript/mastodon/features/status/index.js | 5 +++++ app/javascript/mastodon/features/ui/index.js | 1 + 3 files changed, 11 insertions(+) diff --git a/app/javascript/mastodon/components/status.js b/app/javascript/mastodon/components/status.js index 6129b3f..e5f7c93 100644 --- a/app/javascript/mastodon/components/status.js +++ b/app/javascript/mastodon/components/status.js @@ -122,6 +122,10 @@ export default class Status extends ImmutablePureComponent { this.props.onMoveDown(this.props.status.get('id')); } + handleHotkeyToggleHidden = () => { + this.props.onToggleHidden(this._properStatus()); + } + _properStatus () { const { status } = this.props; @@ -224,6 +228,7 @@ export default class Status extends ImmutablePureComponent { openProfile: this.handleHotkeyOpenProfile, moveUp: this.handleHotkeyMoveUp, moveDown: this.handleHotkeyMoveDown, + toggleHidden: this.handleHotkeyToggleHidden, }; return ( diff --git a/app/javascript/mastodon/features/status/index.js b/app/javascript/mastodon/features/status/index.js index 55eff08..d5af2a4 100644 --- a/app/javascript/mastodon/features/status/index.js +++ b/app/javascript/mastodon/features/status/index.js @@ -244,6 +244,10 @@ export default class Status extends ImmutablePureComponent { this.context.router.history.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`); } + handleHotkeyToggleHidden = () => { + this.handleToggleHidden(this.props.status); + } + handleMoveUp = id => { const { status, ancestorsIds, descendantsIds } = this.props; @@ -354,6 +358,7 @@ export default class Status extends ImmutablePureComponent { boost: this.handleHotkeyBoost, mention: this.handleHotkeyMention, openProfile: this.handleHotkeyOpenProfile, + toggleHidden: this.handleHotkeyToggleHidden, }; return ( diff --git a/app/javascript/mastodon/features/ui/index.js b/app/javascript/mastodon/features/ui/index.js index 8b905fa..d2ef19e 100644 --- a/app/javascript/mastodon/features/ui/index.js +++ b/app/javascript/mastodon/features/ui/index.js @@ -85,6 +85,7 @@ const keyMap = { goToProfile: 'g u', goToBlocked: 'g b', goToMuted: 'g m', + toggleHidden: 'x', }; class SwitchingColumnsArea extends React.PureComponent { From aedfea3554dc5cd60f4bebd828109d1ef4327814 Mon Sep 17 00:00:00 2001 From: luzi82 Date: Wed, 18 Apr 2018 13:28:26 +0800 Subject: [PATCH 141/381] zh-HK translation (#7177) * zh-HK translation * zh-HK fix * zh-HK translation * add missing zh-HK translate * fix translate * i18n-tasks normalize --- app/javascript/mastodon/locales/zh-HK.json | 10 ++--- config/locales/devise.zh-HK.yml | 4 +- config/locales/doorkeeper.zh-HK.yml | 4 +- config/locales/simple_form.zh-HK.yml | 6 +++ config/locales/zh-HK.yml | 63 ++++++++++++++++++++++++++++-- 5 files changed, 75 insertions(+), 12 deletions(-) diff --git a/app/javascript/mastodon/locales/zh-HK.json b/app/javascript/mastodon/locales/zh-HK.json index bebb33e..28685f4 100644 --- a/app/javascript/mastodon/locales/zh-HK.json +++ b/app/javascript/mastodon/locales/zh-HK.json @@ -74,7 +74,7 @@ "confirmations.block.confirm": "封鎖", "confirmations.block.message": "你確定要封鎖{name}嗎?", "confirmations.delete.confirm": "刪除", - "confirmations.delete.message": "你確定要刪除{name}嗎?", + "confirmations.delete.message": "你確定要刪除這文章嗎?", "confirmations.delete_list.confirm": "刪除", "confirmations.delete_list.message": "你確定要永久刪除這列表嗎?", "confirmations.domain_block.confirm": "隱藏整個網站", @@ -99,11 +99,11 @@ "emoji_button.search_results": "搜尋結果", "emoji_button.symbols": "符號", "emoji_button.travel": "旅遊景物", - "empty_column.community": "本站時間軸暫時未有內容,快文章來搶頭香啊!", + "empty_column.community": "本站時間軸暫時未有內容,快寫一點東西來搶頭香啊!", "empty_column.hashtag": "這個標籤暫時未有內容。", "empty_column.home": "你還沒有關注任何用戶。快看看{public},向其他用戶搭訕吧。", "empty_column.home.public_timeline": "公共時間軸", - "empty_column.list": "There is nothing in this list yet.", + "empty_column.list": "這個列表暫時未有內容。", "empty_column.notifications": "你沒有任何通知紀錄,快向其他用戶搭訕吧。", "empty_column.public": "跨站時間軸暫時沒有內容!快寫一些公共的文章,或者關注另一些服務站的用戶吧!你和本站、友站的交流,將決定這裏出現的內容。", "follow_request.authorize": "批准", @@ -240,7 +240,7 @@ "status.block": "封鎖 @{name}", "status.cannot_reblog": "這篇文章無法被轉推", "status.delete": "刪除", - "status.direct": "Direct message @{name}", + "status.direct": "私訊 @{name}", "status.embed": "鑲嵌", "status.favourite": "收藏", "status.load_more": "載入更多", @@ -270,7 +270,7 @@ "tabs_bar.home": "主頁", "tabs_bar.local_timeline": "本站", "tabs_bar.notifications": "通知", - "tabs_bar.search": "Search", + "tabs_bar.search": "搜尋", "ui.beforeunload": "如果你現在離開 Mastodon,你的草稿內容將會被丟棄。", "upload_area.title": "將檔案拖放至此上載", "upload_button.label": "上載媒體檔案", diff --git a/config/locales/devise.zh-HK.yml b/config/locales/devise.zh-HK.yml index 12d6e3c..ca16e06 100644 --- a/config/locales/devise.zh-HK.yml +++ b/config/locales/devise.zh-HK.yml @@ -78,5 +78,5 @@ zh-HK: not_found: 找不到 not_locked: 並未被鎖定 not_saved: - one: 1 個錯誤令 %{resource} 被法被儲存︰ - other: "%{count} 個錯誤令 %{resource} 被法被儲存︰" + one: 1 個錯誤令 %{resource} 無法被儲存︰ + other: "%{count} 個錯誤令 %{resource} 無法被儲存︰" diff --git a/config/locales/doorkeeper.zh-HK.yml b/config/locales/doorkeeper.zh-HK.yml index 7471087..4f46a41 100644 --- a/config/locales/doorkeeper.zh-HK.yml +++ b/config/locales/doorkeeper.zh-HK.yml @@ -14,7 +14,7 @@ zh-HK: redirect_uri: fragment_present: URI 不可包含 "#fragment" 部份 invalid_uri: 必需有正確的 URI. - relative_uri: 必需為絕對 URI. + relative_uri: 必需為完整 URI. secured_uri: 必需使用有 HTTPS/SSL 加密的 URI. doorkeeper: applications: @@ -63,7 +63,7 @@ zh-HK: prompt: 應用程式 %{client_name} 要求得到你用戶的部份權限 title: 需要用戶授權 show: - title: Copy this authorization code and paste it to the application. + title: 請把這個授權碼複製到應用程式中。 authorized_applications: buttons: revoke: 取消授權 diff --git a/config/locales/simple_form.zh-HK.yml b/config/locales/simple_form.zh-HK.yml index da0292a..6b890b0 100644 --- a/config/locales/simple_form.zh-HK.yml +++ b/config/locales/simple_form.zh-HK.yml @@ -6,6 +6,7 @@ zh-HK: avatar: 支援 PNG, GIF 或 JPG 圖片,檔案最大為 2MB,會縮裁成 400x400px digest: 僅在你長時間未登錄,且收到了私信時發送 display_name: 最多 30 個字元 + fields: 個人資料頁可顯示多至 4 個項目 header: 支援 PNG, GIF 或 JPG 圖片,檔案最大為 2MB,會縮裁成 700x335px locked: 你必須人手核准每個用戶對你的關注請求,而你的文章私隱會被預設為「只有關注你的人能看」 note: 最多 160 個字元 @@ -18,6 +19,10 @@ zh-HK: user: filtered_languages: 下面被選擇的語言的文章將不會出現在你的公共時間軸上。 labels: + account: + fields: + name: 標籤 + value: 內容 defaults: avatar: 個人頭像 confirm_new_password: 確認新密碼 @@ -27,6 +32,7 @@ zh-HK: display_name: 顯示名稱 email: 電郵地址 expires_in: 失效時間 + fields: 資料 filtered_languages: 封鎖下面語言的文章 header: 個人頁面頂部 locale: 語言 diff --git a/config/locales/zh-HK.yml b/config/locales/zh-HK.yml index 5c1feab..964ff58 100644 --- a/config/locales/zh-HK.yml +++ b/config/locales/zh-HK.yml @@ -4,6 +4,7 @@ zh-HK: about_hashtag_html: 這些是包含「#%{hashtag}」標籤的公開文章。只要你有任何 Mastodon 服務站、或者聯盟網站的用戶,便可以與他們互動。 about_mastodon_html: Mastodon(萬象)是自由、開源的社交網絡。服務站各自獨立而互連,避免單一商業機構壟斷。找你所信任的服務站,建立帳號,你即可與任何服務站上的用戶溝通,享受無縫的網絡交流。 about_this: 關於本服務站 + administered_by: 管理者: closed_registrations: 本服務站暫時停止接受登記。 contact: 聯絡 contact_missing: 未設定 @@ -60,7 +61,15 @@ zh-HK: destroyed_msg: 管理記錄已被刪除 accounts: are_you_sure: 你確定嗎? + avatar: 頭像 by_domain: 域名 + change_email: + changed_msg: 帳號電郵更新成功! + current_email: 現時電郵 + label: 改變電郵 + new_email: 新的電郵 + submit: 改變電郵 + title: 改變 %{username} 的電郵 confirm: 確定 confirmed: 已確定 demote: 降任 @@ -108,6 +117,7 @@ zh-HK: public: 公共 push_subscription_expires: PuSH 訂閱過期 redownload: 更新頭像 + remove_avatar: 取消頭像 reset: 重設 reset_password: 重設密碼 resubscribe: 重新訂閱 @@ -128,6 +138,7 @@ zh-HK: statuses: 文章 subscribe: 訂閱 title: 用戶 + unconfirmed_email: 未確認的電郵 undo_silenced: 解除靜音 undo_suspension: 解除停權 unsubscribe: 取消訂閱 @@ -135,6 +146,8 @@ zh-HK: web: 用戶頁面 action_logs: actions: + assigned_to_self_report: "%{name} 指派了 %{target} 的舉報給自己" + change_email_user: "%{name} 改變了用戶 %{target} 的電郵地址" confirm_user: "%{name} 確認了用戶 %{target} 的電郵地址" create_custom_emoji: "%{name} 加入自訂表情符號 %{target}" create_domain_block: "%{name} 阻隔了網域 %{target}" @@ -150,10 +163,13 @@ zh-HK: enable_user: "%{name} 把用戶 %{target} 設定為允許登入" memorialize_account: "%{name} 把 %{target} 設定為追悼帳戶" promote_user: "%{name} 對用戶 %{target} 进行了升任操作" + remove_avatar_user: "%{name} 取消了 %{target} 的頭像" + reopen_report: "%{name} 重開 %{target} 的舉報" reset_password_user: "%{name} 重設了用戶 %{target} 的密碼" resolve_report: "%{name} 處理了 %{target} 的舉報" silence_account: "%{name} 靜音了用戶 %{target}" suspend_account: "%{name} 停權了用戶 %{target}" + unassigned_report: "%{name} 取消指派 %{target} 的舉報" unsilence_account: "%{name} 取消了用戶 %{target} 的靜音狀態" unsuspend_account: "%{name} 取消了用戶 %{target} 的停權狀態" update_custom_emoji: "%{name} 更新了自訂表情符號 %{target}" @@ -238,29 +254,60 @@ zh-HK: expired: 已失效 title: 篩選 title: 邀請用戶 + report_notes: + created_msg: 舉報筆記已建立。 + destroyed_msg: 舉報筆記已刪除。 reports: + account: + created_reports: 由此帳號發出的舉報 + moderation: + silenced: 被靜音的 + suspended: 被停權的 + title: 管理操作 + moderation_notes: 管理筆記 + note: 筆記 + report: 舉報 + targeted_reports: 關於此帳號的舉報 action_taken_by: 操作執行者 are_you_sure: 你確認嗎? + assign_to_self: 指派給自己 + assigned: 指派負責人 comment: label: 詳細解釋 none: 沒有 + created_at: 日期 delete: 刪除 + history: 執行紀錄 id: ID mark_as_resolved: 標示為「已處理」 + mark_as_unresolved: 標示為「未處理」 + notes: + create: 建立筆記 + create_and_resolve: 建立筆記並標示為「已處理」 + create_and_unresolve: 建立筆記並標示為「未處理」 + delete: 刪除 + label: 管理筆記 + new_label: 建立管理筆記 + placeholder: 記錄已執行的動作,或其他更新 nsfw: 'false': 取消 NSFW 標記 'true': 添加 NSFW 標記 + reopen: 重開舉報 report: '舉報 #%{id}' report_contents: 舉報內容 reported_account: 舉報用戶 reported_by: 舉報者 resolved: 已處理 + resolved_msg: 舉報已處理。 silence_account: 將用戶靜音 status: 狀態 + statuses: 被舉報的文章 suspend_account: 將用戶停權 target: 對象 title: 舉報 + unassign: 取消指派 unresolved: 未處理 + updated_at: 更新 view: 檢視 settings: activity_api_enabled: @@ -381,6 +428,7 @@ zh-HK: security: 登入資訊 set_new_password: 設定新密碼 authorize_follow: + already_following: 你已經關注了這個帳號 error: 對不起,尋找這個跨站用戶的過程發生錯誤 follow: 關注 follow_request: 關注請求已發送给: @@ -473,6 +521,7 @@ zh-HK: '21600': 6 小時後 '3600': 1 小時後 '43200': 12 小時後 + '604800': 1 週後 '86400': 1 天後 expires_in_prompt: 永不過期 generate: 生成邀請連結 @@ -574,6 +623,10 @@ zh-HK: missing_resource: 無法找到你用戶的轉接網址 proceed: 下一步 prompt: 你希望關注︰ + remote_unfollow: + error: 錯誤 + title: 標題 + unfollowed: 取消關注 sessions: activity: 最近活動 browser: 瀏覽器 @@ -593,7 +646,7 @@ zh-HK: safari: Safari uc_browser: UC瀏覽器 weibo: 新浪微博 - current_session: 目前的 session + current_session: 目前的作業階段 description: "%{platform} 上的 %{browser}" explanation: 這些是現在正登入於你的 Mastodon 帳號的瀏覽器。 ip: IP 位址 @@ -611,8 +664,8 @@ zh-HK: windows_mobile: Windows Mobile windows_phone: Windows Phone revoke: 取消 - revoke_success: Session 取消成功。 - title: Session + revoke_success: 作業階段取消成功。 + title: 作業階段 settings: authorized_apps: 授權應用程式 back: 回到 Mastodon @@ -677,6 +730,10 @@ zh-HK: setup: 設定 wrong_code: 你輸入的認證碼並不正確!可能伺服器時間和你手機不一致,請檢查你手機的時鐘,或與本站管理員聯絡。 user_mailer: + backup_ready: + explanation: 你要求的 Mastodon 帳號完整備份檔案現已就緒,可供下載。 + subject: 你的備份檔已可供下載 + title: 檔案匯出 welcome: edit_profile_action: 設定個人資料 edit_profile_step: 你可以設定你的個人資料,包括上傳頭像、橫幅圖片、更改顯示名稱等等。如果你想在新的關注者關注你之前對他們進行審核,你也可以選擇為你的帳戶設為「私人」。 From 156b916caf4ec902a8db525843e95c1a42350207 Mon Sep 17 00:00:00 2001 From: Kaito Sinclaire Date: Wed, 18 Apr 2018 04:09:06 -0700 Subject: [PATCH 142/381] Direct messages column (#4514) * Added a timeline for Direct statuses * Lists all Direct statuses you've sent and received * Displayed in Getting Started * Streaming server support for direct TL * Changes to match other timelines in 2.0 --- .../api/v1/timelines/direct_controller.rb | 60 ++++++++++++ app/javascript/mastodon/actions/compose.js | 2 + app/javascript/mastodon/actions/streaming.js | 1 + app/javascript/mastodon/actions/timelines.js | 1 + .../containers/column_settings_container.js | 17 ++++ .../mastodon/features/direct_timeline/index.js | 104 +++++++++++++++++++++ .../mastodon/features/getting_started/index.js | 15 ++- .../features/ui/components/columns_area.js | 3 +- app/javascript/mastodon/features/ui/index.js | 8 ++ .../mastodon/features/ui/util/async-components.js | 4 + app/javascript/mastodon/locales/ar.json | 5 + app/javascript/mastodon/locales/bg.json | 5 + app/javascript/mastodon/locales/ca.json | 5 + app/javascript/mastodon/locales/de.json | 5 + .../mastodon/locales/defaultMessages.json | 33 +++++++ app/javascript/mastodon/locales/en.json | 5 + app/javascript/mastodon/locales/eo.json | 5 + app/javascript/mastodon/locales/es.json | 5 + app/javascript/mastodon/locales/fa.json | 5 + app/javascript/mastodon/locales/fi.json | 5 + app/javascript/mastodon/locales/fr.json | 5 + app/javascript/mastodon/locales/gl.json | 5 + app/javascript/mastodon/locales/he.json | 5 + app/javascript/mastodon/locales/hr.json | 5 + app/javascript/mastodon/locales/hu.json | 5 + app/javascript/mastodon/locales/hy.json | 5 + app/javascript/mastodon/locales/id.json | 5 + app/javascript/mastodon/locales/io.json | 5 + app/javascript/mastodon/locales/it.json | 5 + app/javascript/mastodon/locales/ja.json | 5 + app/javascript/mastodon/locales/ko.json | 5 + app/javascript/mastodon/locales/nl.json | 5 + app/javascript/mastodon/locales/no.json | 5 + app/javascript/mastodon/locales/oc.json | 5 + app/javascript/mastodon/locales/pl.json | 5 + app/javascript/mastodon/locales/pt-BR.json | 5 + app/javascript/mastodon/locales/pt.json | 5 + app/javascript/mastodon/locales/ru.json | 5 + app/javascript/mastodon/locales/sk.json | 5 + app/javascript/mastodon/locales/sr-Latn.json | 5 + app/javascript/mastodon/locales/sr.json | 5 + app/javascript/mastodon/locales/sv.json | 5 + app/javascript/mastodon/locales/th.json | 5 + app/javascript/mastodon/locales/tr.json | 5 + app/javascript/mastodon/locales/uk.json | 5 + app/javascript/mastodon/locales/zh-CN.json | 5 + app/javascript/mastodon/locales/zh-HK.json | 5 + app/javascript/mastodon/locales/zh-TW.json | 5 + app/javascript/mastodon/reducers/contexts.js | 5 +- app/javascript/mastodon/reducers/settings.js | 6 ++ app/models/status.rb | 8 ++ app/services/batched_remove_status_service.rb | 11 +++ app/services/fan_out_on_write_service.rb | 13 ++- app/services/remove_status_service.rb | 8 ++ config/routes.rb | 1 + spec/models/status_spec.rb | 49 ++++++++++ streaming/index.js | 7 ++ yarn.lock | 12 +-- 58 files changed, 538 insertions(+), 15 deletions(-) create mode 100644 app/controllers/api/v1/timelines/direct_controller.rb create mode 100644 app/javascript/mastodon/features/direct_timeline/containers/column_settings_container.js create mode 100644 app/javascript/mastodon/features/direct_timeline/index.js diff --git a/app/controllers/api/v1/timelines/direct_controller.rb b/app/controllers/api/v1/timelines/direct_controller.rb new file mode 100644 index 0000000..d455227 --- /dev/null +++ b/app/controllers/api/v1/timelines/direct_controller.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +class Api::V1::Timelines::DirectController < Api::BaseController + before_action -> { doorkeeper_authorize! :read }, only: [:show] + before_action :require_user!, only: [:show] + after_action :insert_pagination_headers, unless: -> { @statuses.empty? } + + respond_to :json + + def show + @statuses = load_statuses + render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id) + end + + private + + def load_statuses + cached_direct_statuses + end + + def cached_direct_statuses + cache_collection direct_statuses, Status + end + + def direct_statuses + direct_timeline_statuses.paginate_by_max_id( + limit_param(DEFAULT_STATUSES_LIMIT), + params[:max_id], + params[:since_id] + ) + end + + def direct_timeline_statuses + Status.as_direct_timeline(current_account) + end + + def insert_pagination_headers + set_pagination_headers(next_path, prev_path) + end + + def pagination_params(core_params) + params.permit(:local, :limit).merge(core_params) + end + + def next_path + api_v1_timelines_direct_url pagination_params(max_id: pagination_max_id) + end + + def prev_path + api_v1_timelines_direct_url pagination_params(since_id: pagination_since_id) + end + + def pagination_max_id + @statuses.last.id + end + + def pagination_since_id + @statuses.first.id + end +end diff --git a/app/javascript/mastodon/actions/compose.js b/app/javascript/mastodon/actions/compose.js index ea9d9f3..eee9c69 100644 --- a/app/javascript/mastodon/actions/compose.js +++ b/app/javascript/mastodon/actions/compose.js @@ -145,6 +145,8 @@ export function submitCompose() { if (response.data.in_reply_to_id === null && response.data.visibility === 'public') { insertIfOnline('community'); insertIfOnline('public'); + } else if (response.data.visibility === 'direct') { + insertIfOnline('direct'); } }).catch(function (error) { dispatch(submitComposeFail(error)); diff --git a/app/javascript/mastodon/actions/streaming.js b/app/javascript/mastodon/actions/streaming.js index f76510c..14215ab 100644 --- a/app/javascript/mastodon/actions/streaming.js +++ b/app/javascript/mastodon/actions/streaming.js @@ -46,4 +46,5 @@ export const connectCommunityStream = () => connectTimelineStream('community', ' export const connectMediaStream = () => connectTimelineStream('community', 'public:local'); export const connectPublicStream = () => connectTimelineStream('public', 'public'); export const connectHashtagStream = (tag) => connectTimelineStream(`hashtag:${tag}`, `hashtag&tag=${tag}`); +export const connectDirectStream = () => connectTimelineStream('direct', 'direct'); export const connectListStream = (id) => connectTimelineStream(`list:${id}`, `list&list=${id}`); diff --git a/app/javascript/mastodon/actions/timelines.js b/app/javascript/mastodon/actions/timelines.js index 5be0712..eca847e 100644 --- a/app/javascript/mastodon/actions/timelines.js +++ b/app/javascript/mastodon/actions/timelines.js @@ -87,6 +87,7 @@ export function expandTimeline(timelineId, path, params = {}) { export const expandHomeTimeline = ({ maxId } = {}) => expandTimeline('home', '/api/v1/timelines/home', { max_id: maxId }); export const expandPublicTimeline = ({ maxId } = {}) => expandTimeline('public', '/api/v1/timelines/public', { max_id: maxId }); export const expandCommunityTimeline = ({ maxId } = {}) => expandTimeline('community', '/api/v1/timelines/public', { local: true, max_id: maxId }); +export const expandDirectTimeline = ({ maxId } = {}) => expandTimeline('direct', '/api/v1/timelines/direct', { max_id: maxId }); export const expandAccountTimeline = (accountId, { maxId, withReplies } = {}) => expandTimeline(`account:${accountId}${withReplies ? ':with_replies' : ''}`, `/api/v1/accounts/${accountId}/statuses`, { exclude_replies: !withReplies, max_id: maxId }); export const expandAccountFeaturedTimeline = accountId => expandTimeline(`account:${accountId}:pinned`, `/api/v1/accounts/${accountId}/statuses`, { pinned: true }); export const expandAccountMediaTimeline = (accountId, { maxId } = {}) => expandTimeline(`account:${accountId}:media`, `/api/v1/accounts/${accountId}/statuses`, { max_id: maxId, only_media: true }); diff --git a/app/javascript/mastodon/features/direct_timeline/containers/column_settings_container.js b/app/javascript/mastodon/features/direct_timeline/containers/column_settings_container.js new file mode 100644 index 0000000..1833f69 --- /dev/null +++ b/app/javascript/mastodon/features/direct_timeline/containers/column_settings_container.js @@ -0,0 +1,17 @@ +import { connect } from 'react-redux'; +import ColumnSettings from '../../community_timeline/components/column_settings'; +import { changeSetting } from '../../../actions/settings'; + +const mapStateToProps = state => ({ + settings: state.getIn(['settings', 'direct']), +}); + +const mapDispatchToProps = dispatch => ({ + + onChange (key, checked) { + dispatch(changeSetting(['direct', ...key], checked)); + }, + +}); + +export default connect(mapStateToProps, mapDispatchToProps)(ColumnSettings); diff --git a/app/javascript/mastodon/features/direct_timeline/index.js b/app/javascript/mastodon/features/direct_timeline/index.js new file mode 100644 index 0000000..fda57f6 --- /dev/null +++ b/app/javascript/mastodon/features/direct_timeline/index.js @@ -0,0 +1,104 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import PropTypes from 'prop-types'; +import StatusListContainer from '../ui/containers/status_list_container'; +import Column from '../../components/column'; +import ColumnHeader from '../../components/column_header'; +import { expandDirectTimeline } from '../../actions/timelines'; +import { addColumn, removeColumn, moveColumn } from '../../actions/columns'; +import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; +import ColumnSettingsContainer from './containers/column_settings_container'; +import { connectDirectStream } from '../../actions/streaming'; + +const messages = defineMessages({ + title: { id: 'column.direct', defaultMessage: 'Direct messages' }, +}); + +const mapStateToProps = state => ({ + hasUnread: state.getIn(['timelines', 'direct', 'unread']) > 0, +}); + +@connect(mapStateToProps) +@injectIntl +export default class DirectTimeline extends React.PureComponent { + + static propTypes = { + dispatch: PropTypes.func.isRequired, + columnId: PropTypes.string, + intl: PropTypes.object.isRequired, + hasUnread: PropTypes.bool, + multiColumn: PropTypes.bool, + }; + + handlePin = () => { + const { columnId, dispatch } = this.props; + + if (columnId) { + dispatch(removeColumn(columnId)); + } else { + dispatch(addColumn('DIRECT', {})); + } + } + + handleMove = (dir) => { + const { columnId, dispatch } = this.props; + dispatch(moveColumn(columnId, dir)); + } + + handleHeaderClick = () => { + this.column.scrollTop(); + } + + componentDidMount () { + const { dispatch } = this.props; + + dispatch(expandDirectTimeline()); + this.disconnect = dispatch(connectDirectStream()); + } + + componentWillUnmount () { + if (this.disconnect) { + this.disconnect(); + this.disconnect = null; + } + } + + setRef = c => { + this.column = c; + } + + handleLoadMore = maxId => { + this.props.dispatch(expandDirectTimeline({ maxId })); + } + + render () { + const { intl, hasUnread, columnId, multiColumn } = this.props; + const pinned = !!columnId; + + return ( + + + + + + } + /> + + ); + } + +} diff --git a/app/javascript/mastodon/features/getting_started/index.js b/app/javascript/mastodon/features/getting_started/index.js index 053a1ca..4a249f3 100644 --- a/app/javascript/mastodon/features/getting_started/index.js +++ b/app/javascript/mastodon/features/getting_started/index.js @@ -19,6 +19,7 @@ const messages = defineMessages({ navigation_subheading: { id: 'column_subheading.navigation', defaultMessage: 'Navigation' }, settings_subheading: { id: 'column_subheading.settings', defaultMessage: 'Settings' }, community_timeline: { id: 'navigation_bar.community_timeline', defaultMessage: 'Local timeline' }, + direct: { id: 'navigation_bar.direct', defaultMessage: 'Direct messages' }, preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' }, follow_requests: { id: 'navigation_bar.follow_requests', defaultMessage: 'Follow requests' }, sign_out: { id: 'navigation_bar.logout', defaultMessage: 'Logout' }, @@ -98,20 +99,24 @@ export default class GettingStarted extends ImmutablePureComponent { } } + if (!multiColumn || !columns.find(item => item.get('id') === 'DIRECT')) { + navItems.push(); + } + navItems.push( - , - + , + ); if (myAccount.get('locked')) { - navItems.push(); + navItems.push(); } if (multiColumn) { - navItems.push(); + navItems.push(); } - navItems.push(); + navItems.push(); return ( diff --git a/app/javascript/mastodon/features/ui/components/columns_area.js b/app/javascript/mastodon/features/ui/components/columns_area.js index 05cdb4e..0a62cbb 100644 --- a/app/javascript/mastodon/features/ui/components/columns_area.js +++ b/app/javascript/mastodon/features/ui/components/columns_area.js @@ -12,7 +12,7 @@ import BundleContainer from '../containers/bundle_container'; import ColumnLoading from './column_loading'; import DrawerLoading from './drawer_loading'; import BundleColumnError from './bundle_column_error'; -import { Compose, Notifications, HomeTimeline, CommunityTimeline, PublicTimeline, HashtagTimeline, FavouritedStatuses, ListTimeline } from '../../ui/util/async-components'; +import { Compose, Notifications, HomeTimeline, CommunityTimeline, PublicTimeline, HashtagTimeline, DirectTimeline, FavouritedStatuses, ListTimeline } from '../../ui/util/async-components'; import detectPassiveEvents from 'detect-passive-events'; import { scrollRight } from '../../../scroll'; @@ -24,6 +24,7 @@ const componentMap = { 'PUBLIC': PublicTimeline, 'COMMUNITY': CommunityTimeline, 'HASHTAG': HashtagTimeline, + 'DIRECT': DirectTimeline, 'FAVOURITES': FavouritedStatuses, 'LIST': ListTimeline, }; diff --git a/app/javascript/mastodon/features/ui/index.js b/app/javascript/mastodon/features/ui/index.js index d2ef19e..adca0d6 100644 --- a/app/javascript/mastodon/features/ui/index.js +++ b/app/javascript/mastodon/features/ui/index.js @@ -30,6 +30,7 @@ import { Following, Reblogs, Favourites, + DirectTimeline, HashtagTimeline, Notifications, FollowRequests, @@ -79,6 +80,7 @@ const keyMap = { goToNotifications: 'g n', goToLocal: 'g l', goToFederated: 'g t', + goToDirect: 'g d', goToStart: 'g s', goToFavourites: 'g f', goToPinned: 'g p', @@ -140,6 +142,7 @@ class SwitchingColumnsArea extends React.PureComponent { + @@ -386,6 +389,10 @@ export default class UI extends React.PureComponent { this.context.router.history.push('/timelines/public'); } + handleHotkeyGoToDirect = () => { + this.context.router.history.push('/timelines/direct'); + } + handleHotkeyGoToStart = () => { this.context.router.history.push('/getting-started'); } @@ -425,6 +432,7 @@ export default class UI extends React.PureComponent { goToNotifications: this.handleHotkeyGoToNotifications, goToLocal: this.handleHotkeyGoToLocal, goToFederated: this.handleHotkeyGoToFederated, + goToDirect: this.handleHotkeyGoToDirect, goToStart: this.handleHotkeyGoToStart, goToFavourites: this.handleHotkeyGoToFavourites, goToPinned: this.handleHotkeyGoToPinned, diff --git a/app/javascript/mastodon/features/ui/util/async-components.js b/app/javascript/mastodon/features/ui/util/async-components.js index 1995720..8cf2a6e 100644 --- a/app/javascript/mastodon/features/ui/util/async-components.js +++ b/app/javascript/mastodon/features/ui/util/async-components.js @@ -26,6 +26,10 @@ export function HashtagTimeline () { return import(/* webpackChunkName: "features/hashtag_timeline" */'../../hashtag_timeline'); } +export function DirectTimeline() { + return import(/* webpackChunkName: "features/direct_timeline" */'../../direct_timeline'); +} + export function ListTimeline () { return import(/* webpackChunkName: "features/list_timeline" */'../../list_timeline'); } diff --git a/app/javascript/mastodon/locales/ar.json b/app/javascript/mastodon/locales/ar.json index c13ff77..24c8a5b 100644 --- a/app/javascript/mastodon/locales/ar.json +++ b/app/javascript/mastodon/locales/ar.json @@ -40,6 +40,7 @@ "bundle_modal_error.retry": "إعادة المحاولة", "column.blocks": "الحسابات المحجوبة", "column.community": "الخيط العام المحلي", + "column.direct": "Direct messages", "column.domain_blocks": "Hidden domains", "column.favourites": "المفضلة", "column.follow_requests": "طلبات المتابعة", @@ -100,6 +101,7 @@ "emoji_button.symbols": "رموز", "emoji_button.travel": "أماكن و أسفار", "empty_column.community": "الخط الزمني المحلي فارغ. أكتب شيئا ما للعامة كبداية !", + "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.", "empty_column.hashtag": "ليس هناك بعدُ أي محتوى ذو علاقة بهذا الوسم.", "empty_column.home": "إنك لا تتبع بعد أي شخص إلى حد الآن. زر {public} أو استخدام حقل البحث لكي تبدأ على التعرف على مستخدمين آخرين.", "empty_column.home.public_timeline": "الخيط العام", @@ -154,6 +156,7 @@ "mute_modal.hide_notifications": "هل تود إخفاء الإخطارات القادمة من هذا المستخدم ؟", "navigation_bar.blocks": "الحسابات المحجوبة", "navigation_bar.community_timeline": "الخيط العام المحلي", + "navigation_bar.direct": "Direct messages", "navigation_bar.domain_blocks": "Hidden domains", "navigation_bar.edit_profile": "تعديل الملف الشخصي", "navigation_bar.favourites": "المفضلة", @@ -238,6 +241,7 @@ "search_results.total": "{count, number} {count, plural, one {result} و {results}}", "standalone.public_title": "نظرة على ...", "status.block": "Block @{name}", + "status.cancel_reblog_private": "Unboost", "status.cannot_reblog": "تعذرت ترقية هذا المنشور", "status.delete": "إحذف", "status.direct": "Direct message @{name}", @@ -253,6 +257,7 @@ "status.pin": "تدبيس على الملف الشخصي", "status.pinned": "تبويق مثبَّت", "status.reblog": "رَقِّي", + "status.reblog_private": "Boost to original audience", "status.reblogged_by": "{name} رقى", "status.reply": "ردّ", "status.replyAll": "رُد على الخيط", diff --git a/app/javascript/mastodon/locales/bg.json b/app/javascript/mastodon/locales/bg.json index 981aced..25ef6db 100644 --- a/app/javascript/mastodon/locales/bg.json +++ b/app/javascript/mastodon/locales/bg.json @@ -40,6 +40,7 @@ "bundle_modal_error.retry": "Try again", "column.blocks": "Blocked users", "column.community": "Local timeline", + "column.direct": "Direct messages", "column.domain_blocks": "Hidden domains", "column.favourites": "Favourites", "column.follow_requests": "Follow requests", @@ -100,6 +101,7 @@ "emoji_button.symbols": "Symbols", "emoji_button.travel": "Travel & Places", "empty_column.community": "The local timeline is empty. Write something publicly to get the ball rolling!", + "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.", "empty_column.hashtag": "There is nothing in this hashtag yet.", "empty_column.home": "Your home timeline is empty! Visit {public} or use search to get started and meet other users.", "empty_column.home.public_timeline": "the public timeline", @@ -154,6 +156,7 @@ "mute_modal.hide_notifications": "Hide notifications from this user?", "navigation_bar.blocks": "Blocked users", "navigation_bar.community_timeline": "Local timeline", + "navigation_bar.direct": "Direct messages", "navigation_bar.domain_blocks": "Hidden domains", "navigation_bar.edit_profile": "Редактирай профил", "navigation_bar.favourites": "Favourites", @@ -238,6 +241,7 @@ "search_results.total": "{count, number} {count, plural, one {result} other {results}}", "standalone.public_title": "A look inside...", "status.block": "Block @{name}", + "status.cancel_reblog_private": "Unboost", "status.cannot_reblog": "This post cannot be boosted", "status.delete": "Изтриване", "status.direct": "Direct message @{name}", @@ -253,6 +257,7 @@ "status.pin": "Pin on profile", "status.pinned": "Pinned toot", "status.reblog": "Споделяне", + "status.reblog_private": "Boost to original audience", "status.reblogged_by": "{name} сподели", "status.reply": "Отговор", "status.replyAll": "Reply to thread", diff --git a/app/javascript/mastodon/locales/ca.json b/app/javascript/mastodon/locales/ca.json index d9270e0..6a44808 100644 --- a/app/javascript/mastodon/locales/ca.json +++ b/app/javascript/mastodon/locales/ca.json @@ -40,6 +40,7 @@ "bundle_modal_error.retry": "Torna-ho a provar", "column.blocks": "Usuaris blocats", "column.community": "Línia de temps local", + "column.direct": "Direct messages", "column.domain_blocks": "Hidden domains", "column.favourites": "Favorits", "column.follow_requests": "Peticions per seguir-te", @@ -100,6 +101,7 @@ "emoji_button.symbols": "Símbols", "emoji_button.travel": "Viatges i Llocs", "empty_column.community": "La línia de temps local és buida. Escriu alguna cosa públicament per fer rodar la pilota!", + "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.", "empty_column.hashtag": "Encara no hi ha res amb aquesta etiqueta.", "empty_column.home": "Encara no segueixes ningú. Visita {public} o fes cerca per començar i conèixer altres usuaris.", "empty_column.home.public_timeline": "la línia de temps pública", @@ -154,6 +156,7 @@ "mute_modal.hide_notifications": "Amagar notificacions d'aquest usuari?", "navigation_bar.blocks": "Usuaris bloquejats", "navigation_bar.community_timeline": "Línia de temps Local", + "navigation_bar.direct": "Direct messages", "navigation_bar.domain_blocks": "Hidden domains", "navigation_bar.edit_profile": "Editar perfil", "navigation_bar.favourites": "Favorits", @@ -238,6 +241,7 @@ "search_results.total": "{count, number} {count, plural, un {result} altres {results}}", "standalone.public_title": "Una mirada a l'interior ...", "status.block": "Block @{name}", + "status.cancel_reblog_private": "Unboost", "status.cannot_reblog": "Aquesta publicació no pot ser retootejada", "status.delete": "Esborrar", "status.direct": "Direct message @{name}", @@ -253,6 +257,7 @@ "status.pin": "Fixat en el perfil", "status.pinned": "Toot fixat", "status.reblog": "Impuls", + "status.reblog_private": "Boost to original audience", "status.reblogged_by": "{name} ha retootejat", "status.reply": "Respondre", "status.replyAll": "Respondre al tema", diff --git a/app/javascript/mastodon/locales/de.json b/app/javascript/mastodon/locales/de.json index 6eb5e68..69c2ae8 100644 --- a/app/javascript/mastodon/locales/de.json +++ b/app/javascript/mastodon/locales/de.json @@ -40,6 +40,7 @@ "bundle_modal_error.retry": "Erneut versuchen", "column.blocks": "Blockierte Profile", "column.community": "Lokale Zeitleiste", + "column.direct": "Direct messages", "column.domain_blocks": "Hidden domains", "column.favourites": "Favoriten", "column.follow_requests": "Folgeanfragen", @@ -100,6 +101,7 @@ "emoji_button.symbols": "Symbole", "emoji_button.travel": "Reisen und Orte", "empty_column.community": "Die lokale Zeitleiste ist leer. Schreibe einen öffentlichen Beitrag, um den Ball ins Rollen zu bringen!", + "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.", "empty_column.hashtag": "Unter diesem Hashtag gibt es noch nichts.", "empty_column.home": "Deine Startseite ist leer! Besuche {public} oder nutze die Suche, um loszulegen und andere Leute zu finden.", "empty_column.home.public_timeline": "die öffentliche Zeitleiste", @@ -154,6 +156,7 @@ "mute_modal.hide_notifications": "Benachrichtigungen von diesem Account verbergen?", "navigation_bar.blocks": "Blockierte Profile", "navigation_bar.community_timeline": "Lokale Zeitleiste", + "navigation_bar.direct": "Direct messages", "navigation_bar.domain_blocks": "Hidden domains", "navigation_bar.edit_profile": "Profil bearbeiten", "navigation_bar.favourites": "Favoriten", @@ -238,6 +241,7 @@ "search_results.total": "{count, number} {count, plural, one {Ergebnis} other {Ergebnisse}}", "standalone.public_title": "Ein kleiner Einblick …", "status.block": "Block @{name}", + "status.cancel_reblog_private": "Unboost", "status.cannot_reblog": "Dieser Beitrag kann nicht geteilt werden", "status.delete": "Löschen", "status.direct": "Direct message @{name}", @@ -253,6 +257,7 @@ "status.pin": "Im Profil anheften", "status.pinned": "Pinned toot", "status.reblog": "Teilen", + "status.reblog_private": "Boost to original audience", "status.reblogged_by": "{name} teilte", "status.reply": "Antworten", "status.replyAll": "Auf Thread antworten", diff --git a/app/javascript/mastodon/locales/defaultMessages.json b/app/javascript/mastodon/locales/defaultMessages.json index a3c4f77..5a02e0c 100644 --- a/app/javascript/mastodon/locales/defaultMessages.json +++ b/app/javascript/mastodon/locales/defaultMessages.json @@ -243,6 +243,14 @@ "id": "status.reblog" }, { + "defaultMessage": "Boost to original audience", + "id": "status.reblog_private" + }, + { + "defaultMessage": "Unboost", + "id": "status.cancel_reblog_private" + }, + { "defaultMessage": "This post cannot be boosted", "id": "status.cannot_reblog" }, @@ -900,6 +908,19 @@ { "descriptors": [ { + "defaultMessage": "Direct messages", + "id": "column.direct" + }, + { + "defaultMessage": "You don't have any direct messages yet. When you send or receive one, it will show up here.", + "id": "empty_column.direct" + } + ], + "path": "app/javascript/mastodon/features/direct_timeline/index.json" + }, + { + "descriptors": [ + { "defaultMessage": "Hidden domains", "id": "column.domain_blocks" }, @@ -972,6 +993,10 @@ "id": "navigation_bar.community_timeline" }, { + "defaultMessage": "Direct messages", + "id": "navigation_bar.direct" + }, + { "defaultMessage": "Preferences", "id": "navigation_bar.preferences" }, @@ -1400,6 +1425,14 @@ "id": "status.reblog" }, { + "defaultMessage": "Boost to original audience", + "id": "status.reblog_private" + }, + { + "defaultMessage": "Unboost", + "id": "status.cancel_reblog_private" + }, + { "defaultMessage": "This post cannot be boosted", "id": "status.cannot_reblog" }, diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json index a389735..d8bd7e3 100644 --- a/app/javascript/mastodon/locales/en.json +++ b/app/javascript/mastodon/locales/en.json @@ -40,6 +40,7 @@ "bundle_modal_error.retry": "Try again", "column.blocks": "Blocked users", "column.community": "Local timeline", + "column.direct": "Direct messages", "column.domain_blocks": "Hidden domains", "column.favourites": "Favourites", "column.follow_requests": "Follow requests", @@ -100,6 +101,7 @@ "emoji_button.symbols": "Symbols", "emoji_button.travel": "Travel & Places", "empty_column.community": "The local timeline is empty. Write something publicly to get the ball rolling!", + "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.", "empty_column.hashtag": "There is nothing in this hashtag yet.", "empty_column.home": "Your home timeline is empty! Visit {public} or use search to get started and meet other users.", "empty_column.home.public_timeline": "the public timeline", @@ -154,6 +156,7 @@ "mute_modal.hide_notifications": "Hide notifications from this user?", "navigation_bar.blocks": "Blocked users", "navigation_bar.community_timeline": "Local timeline", + "navigation_bar.direct": "Direct messages", "navigation_bar.domain_blocks": "Hidden domains", "navigation_bar.edit_profile": "Edit profile", "navigation_bar.favourites": "Favourites", @@ -238,6 +241,7 @@ "search_results.total": "{count, number} {count, plural, one {result} other {results}}", "standalone.public_title": "A look inside...", "status.block": "Block @{name}", + "status.cancel_reblog_private": "Unboost", "status.cannot_reblog": "This post cannot be boosted", "status.delete": "Delete", "status.direct": "Direct message @{name}", @@ -253,6 +257,7 @@ "status.pin": "Pin on profile", "status.pinned": "Pinned toot", "status.reblog": "Boost", + "status.reblog_private": "Boost to original audience", "status.reblogged_by": "{name} boosted", "status.reply": "Reply", "status.replyAll": "Reply to thread", diff --git a/app/javascript/mastodon/locales/eo.json b/app/javascript/mastodon/locales/eo.json index 19f3c59..e511639 100644 --- a/app/javascript/mastodon/locales/eo.json +++ b/app/javascript/mastodon/locales/eo.json @@ -40,6 +40,7 @@ "bundle_modal_error.retry": "Bonvolu reprovi", "column.blocks": "Blokitaj uzantoj", "column.community": "Loka tempolinio", + "column.direct": "Direct messages", "column.domain_blocks": "Hidden domains", "column.favourites": "Stelumoj", "column.follow_requests": "Petoj de sekvado", @@ -100,6 +101,7 @@ "emoji_button.symbols": "Simboloj", "emoji_button.travel": "Vojaĝoj kaj lokoj", "empty_column.community": "La loka tempolinio estas malplena. Skribu ion por plenigi ĝin!", + "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.", "empty_column.hashtag": "Ankoraŭ estas nenio per ĉi tiu kradvorto.", "empty_column.home": "Via hejma tempolinio estas malplena! Vizitu {public} aŭ uzu la serĉilon por renkonti aliajn uzantojn.", "empty_column.home.public_timeline": "la publikan tempolinion", @@ -154,6 +156,7 @@ "mute_modal.hide_notifications": "Ĉu vi volas kaŝi la sciigojn el ĉi tiu uzanto?", "navigation_bar.blocks": "Blokitaj uzantoj", "navigation_bar.community_timeline": "Loka tempolinio", + "navigation_bar.direct": "Direct messages", "navigation_bar.domain_blocks": "Hidden domains", "navigation_bar.edit_profile": "Redakti profilon", "navigation_bar.favourites": "Stelumoj", @@ -238,6 +241,7 @@ "search_results.total": "{count, number} {count, plural, one {rezulto} other {rezultoj}}", "standalone.public_title": "Enrigardo…", "status.block": "Bloki @{name}", + "status.cancel_reblog_private": "Unboost", "status.cannot_reblog": "Ĉi tiu mesaĝo ne diskonigeblas", "status.delete": "Forigi", "status.direct": "Direct message @{name}", @@ -253,6 +257,7 @@ "status.pin": "Alpingli profile", "status.pinned": "Alpinglita mesaĝo", "status.reblog": "Diskonigi", + "status.reblog_private": "Boost to original audience", "status.reblogged_by": "{name} diskonigis", "status.reply": "Respondi", "status.replyAll": "Respondi al la fadeno", diff --git a/app/javascript/mastodon/locales/es.json b/app/javascript/mastodon/locales/es.json index e765cc0..61ea058 100644 --- a/app/javascript/mastodon/locales/es.json +++ b/app/javascript/mastodon/locales/es.json @@ -40,6 +40,7 @@ "bundle_modal_error.retry": "Inténtalo de nuevo", "column.blocks": "Usuarios bloqueados", "column.community": "Línea de tiempo local", + "column.direct": "Direct messages", "column.domain_blocks": "Hidden domains", "column.favourites": "Favoritos", "column.follow_requests": "Solicitudes de seguimiento", @@ -100,6 +101,7 @@ "emoji_button.symbols": "Símbolos", "emoji_button.travel": "Viajes y lugares", "empty_column.community": "La línea de tiempo local está vacía. ¡Escribe algo para empezar la fiesta!", + "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.", "empty_column.hashtag": "No hay nada en este hashtag 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.home.public_timeline": "la línea de tiempo pública", @@ -154,6 +156,7 @@ "mute_modal.hide_notifications": "Ocultar notificaciones de este usuario?", "navigation_bar.blocks": "Usuarios bloqueados", "navigation_bar.community_timeline": "Historia local", + "navigation_bar.direct": "Direct messages", "navigation_bar.domain_blocks": "Hidden domains", "navigation_bar.edit_profile": "Editar perfil", "navigation_bar.favourites": "Favoritos", @@ -238,6 +241,7 @@ "search_results.total": "{count, number} {count, plural, one {resultado} other {resultados}}", "standalone.public_title": "Un pequeño vistazo...", "status.block": "Block @{name}", + "status.cancel_reblog_private": "Unboost", "status.cannot_reblog": "Este toot no puede retootearse", "status.delete": "Borrar", "status.direct": "Direct message @{name}", @@ -253,6 +257,7 @@ "status.pin": "Fijar", "status.pinned": "Toot fijado", "status.reblog": "Retootear", + "status.reblog_private": "Boost to original audience", "status.reblogged_by": "Retooteado por {name}", "status.reply": "Responder", "status.replyAll": "Responder al hilo", diff --git a/app/javascript/mastodon/locales/fa.json b/app/javascript/mastodon/locales/fa.json index 822c998..cfe9300 100644 --- a/app/javascript/mastodon/locales/fa.json +++ b/app/javascript/mastodon/locales/fa.json @@ -40,6 +40,7 @@ "bundle_modal_error.retry": "تلاش دوباره", "column.blocks": "کاربران مسدودشده", "column.community": "نوشته‌های محلی", + "column.direct": "Direct messages", "column.domain_blocks": "Hidden domains", "column.favourites": "پسندیده‌ها", "column.follow_requests": "درخواست‌های پیگیری", @@ -100,6 +101,7 @@ "emoji_button.symbols": "نمادها", "emoji_button.travel": "سفر و مکان", "empty_column.community": "فهرست نوشته‌های محلی خالی است. چیزی بنویسید تا چرخش بچرخد!", + "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.", "empty_column.hashtag": "هنوز هیچ چیزی با این هشتگ نیست.", "empty_column.home": "شما هنوز پیگیر کسی نیستید. {public} را ببینید یا چیزی را جستجو کنید تا کاربران دیگر را ببینید.", "empty_column.home.public_timeline": "فهرست نوشته‌های همه‌جا", @@ -154,6 +156,7 @@ "mute_modal.hide_notifications": "اعلان‌های این کاربر پنهان شود؟", "navigation_bar.blocks": "کاربران مسدودشده", "navigation_bar.community_timeline": "نوشته‌های محلی", + "navigation_bar.direct": "Direct messages", "navigation_bar.domain_blocks": "Hidden domains", "navigation_bar.edit_profile": "ویرایش نمایه", "navigation_bar.favourites": "پسندیده‌ها", @@ -238,6 +241,7 @@ "search_results.total": "{count, number} {count, plural, one {نتیجه} other {نتیجه}}", "standalone.public_title": "نگاهی به کاربران این سرور...", "status.block": "Block @{name}", + "status.cancel_reblog_private": "Unboost", "status.cannot_reblog": "این نوشته را نمی‌شود بازبوقید", "status.delete": "پاک‌کردن", "status.direct": "Direct message @{name}", @@ -253,6 +257,7 @@ "status.pin": "نوشتهٔ ثابت نمایه", "status.pinned": "Pinned toot", "status.reblog": "بازبوقیدن", + "status.reblog_private": "Boost to original audience", "status.reblogged_by": "‫{name}‬ بازبوقید", "status.reply": "پاسخ", "status.replyAll": "به نوشته پاسخ دهید", diff --git a/app/javascript/mastodon/locales/fi.json b/app/javascript/mastodon/locales/fi.json index 5763ac4..1677c3c 100644 --- a/app/javascript/mastodon/locales/fi.json +++ b/app/javascript/mastodon/locales/fi.json @@ -40,6 +40,7 @@ "bundle_modal_error.retry": "Yritä uudestaan", "column.blocks": "Estetyt käyttäjät", "column.community": "Paikallinen aikajana", + "column.direct": "Direct messages", "column.domain_blocks": "Hidden domains", "column.favourites": "Suosikit", "column.follow_requests": "Seuraamispyynnöt", @@ -100,6 +101,7 @@ "emoji_button.symbols": "Symbolit", "emoji_button.travel": "Matkailu", "empty_column.community": "Paikallinen aikajana on tyhjä. Homma lähtee käyntiin, kun kirjoitat jotain julkista!", + "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.", "empty_column.hashtag": "Tällä hashtagilla ei ole vielä mitään.", "empty_column.home": "Kotiaikajanasi on tyhjä! {public} ja hakutoiminto auttavat alkuun ja kohtaamaan muita käyttäjiä.", "empty_column.home.public_timeline": "yleinen aikajana", @@ -154,6 +156,7 @@ "mute_modal.hide_notifications": "Piilota tältä käyttäjältä tulevat ilmoitukset?", "navigation_bar.blocks": "Estetyt käyttäjät", "navigation_bar.community_timeline": "Paikallinen aikajana", + "navigation_bar.direct": "Direct messages", "navigation_bar.domain_blocks": "Hidden domains", "navigation_bar.edit_profile": "Muokkaa profiilia", "navigation_bar.favourites": "Suosikit", @@ -238,6 +241,7 @@ "search_results.total": "{count, number} {count, plural, one {result} other {results}}", "standalone.public_title": "Kurkistus sisälle...", "status.block": "Estä @{name}", + "status.cancel_reblog_private": "Unboost", "status.cannot_reblog": "Tätä julkaisua ei voi buustata", "status.delete": "Poista", "status.direct": "Direct message @{name}", @@ -253,6 +257,7 @@ "status.pin": "Kiinnitä profiiliin", "status.pinned": "Kiinnitetty tuuttaus", "status.reblog": "Buustaa", + "status.reblog_private": "Boost to original audience", "status.reblogged_by": "{name} buustasi", "status.reply": "Vastaa", "status.replyAll": "Vastaa ketjuun", diff --git a/app/javascript/mastodon/locales/fr.json b/app/javascript/mastodon/locales/fr.json index ef1c115..98c1c43 100644 --- a/app/javascript/mastodon/locales/fr.json +++ b/app/javascript/mastodon/locales/fr.json @@ -40,6 +40,7 @@ "bundle_modal_error.retry": "Réessayer", "column.blocks": "Comptes bloqués", "column.community": "Fil public local", + "column.direct": "Direct messages", "column.domain_blocks": "Hidden domains", "column.favourites": "Favoris", "column.follow_requests": "Demandes de suivi", @@ -100,6 +101,7 @@ "emoji_button.symbols": "Symboles", "emoji_button.travel": "Lieux & Voyages", "empty_column.community": "Le fil public local est vide. Écrivez donc quelque chose pour le remplir !", + "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.", "empty_column.hashtag": "Il n’y a encore aucun contenu associé à ce hashtag.", "empty_column.home": "Vous ne suivez encore personne. Visitez {public} ou bien utilisez la recherche pour vous connecter à d’autres personnes.", "empty_column.home.public_timeline": "le fil public", @@ -154,6 +156,7 @@ "mute_modal.hide_notifications": "Masquer les notifications de cette personne ?", "navigation_bar.blocks": "Comptes bloqués", "navigation_bar.community_timeline": "Fil public local", + "navigation_bar.direct": "Direct messages", "navigation_bar.domain_blocks": "Hidden domains", "navigation_bar.edit_profile": "Modifier le profil", "navigation_bar.favourites": "Favoris", @@ -238,6 +241,7 @@ "search_results.total": "{count, number} {count, plural, one {résultat} other {résultats}}", "standalone.public_title": "Un aperçu …", "status.block": "Block @{name}", + "status.cancel_reblog_private": "Unboost", "status.cannot_reblog": "Cette publication ne peut être boostée", "status.delete": "Effacer", "status.direct": "Direct message @{name}", @@ -253,6 +257,7 @@ "status.pin": "Épingler sur le profil", "status.pinned": "Pouet épinglé", "status.reblog": "Partager", + "status.reblog_private": "Boost to original audience", "status.reblogged_by": "{name} a partagé :", "status.reply": "Répondre", "status.replyAll": "Répondre au fil", diff --git a/app/javascript/mastodon/locales/gl.json b/app/javascript/mastodon/locales/gl.json index 5cbb7d3..fca4237 100644 --- a/app/javascript/mastodon/locales/gl.json +++ b/app/javascript/mastodon/locales/gl.json @@ -40,6 +40,7 @@ "bundle_modal_error.retry": "Inténteo de novo", "column.blocks": "Usuarias bloqueadas", "column.community": "Liña temporal local", + "column.direct": "Direct messages", "column.domain_blocks": "Hidden domains", "column.favourites": "Favoritas", "column.follow_requests": "Peticións de seguimento", @@ -100,6 +101,7 @@ "emoji_button.symbols": "Símbolos", "emoji_button.travel": "Viaxes e Lugares", "empty_column.community": "A liña temporal local está baldeira. Escriba algo de xeito público para que rule!", + "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.", "empty_column.hashtag": "Aínda non hai nada con esta etiqueta.", "empty_column.home": "A súa liña temporal de inicio está baldeira! Visite {public} ou utilice a busca para atopar outras usuarias.", "empty_column.home.public_timeline": "a liña temporal pública", @@ -154,6 +156,7 @@ "mute_modal.hide_notifications": "Esconder notificacións deste usuario?", "navigation_bar.blocks": "Usuarias bloqueadas", "navigation_bar.community_timeline": "Liña temporal local", + "navigation_bar.direct": "Direct messages", "navigation_bar.domain_blocks": "Hidden domains", "navigation_bar.edit_profile": "Editar perfil", "navigation_bar.favourites": "Favoritas", @@ -238,6 +241,7 @@ "search_results.total": "{count, number} {count,plural,one {result} outros {results}}", "standalone.public_title": "Ollada dentro...", "status.block": "Block @{name}", + "status.cancel_reblog_private": "Unboost", "status.cannot_reblog": "Esta mensaxe non pode ser promocionada", "status.delete": "Eliminar", "status.direct": "Direct message @{name}", @@ -253,6 +257,7 @@ "status.pin": "Fixar no perfil", "status.pinned": "Pinned toot", "status.reblog": "Promover", + "status.reblog_private": "Boost to original audience", "status.reblogged_by": "{name} promoveu", "status.reply": "Resposta", "status.replyAll": "Resposta a conversa", diff --git a/app/javascript/mastodon/locales/he.json b/app/javascript/mastodon/locales/he.json index 656d93c..e3e87f1 100644 --- a/app/javascript/mastodon/locales/he.json +++ b/app/javascript/mastodon/locales/he.json @@ -40,6 +40,7 @@ "bundle_modal_error.retry": "לנסות שוב", "column.blocks": "חסימות", "column.community": "ציר זמן מקומי", + "column.direct": "Direct messages", "column.domain_blocks": "Hidden domains", "column.favourites": "חיבובים", "column.follow_requests": "בקשות מעקב", @@ -100,6 +101,7 @@ "emoji_button.symbols": "סמלים", "emoji_button.travel": "טיולים ואתרים", "empty_column.community": "טור הסביבה ריק. יש לפרסם משהו כדי שדברים יתרחילו להתגלגל!", + "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.", "empty_column.hashtag": "אין כלום בהאשתג הזה עדיין.", "empty_column.home": "אף אחד לא במעקב עדיין. אפשר לבקר ב{public} או להשתמש בחיפוש כדי להתחיל ולהכיר חצוצרנים אחרים.", "empty_column.home.public_timeline": "ציר זמן בין-קהילתי", @@ -154,6 +156,7 @@ "mute_modal.hide_notifications": "להסתיר הודעות מחשבון זה?", "navigation_bar.blocks": "חסימות", "navigation_bar.community_timeline": "ציר זמן מקומי", + "navigation_bar.direct": "Direct messages", "navigation_bar.domain_blocks": "Hidden domains", "navigation_bar.edit_profile": "עריכת פרופיל", "navigation_bar.favourites": "חיבובים", @@ -238,6 +241,7 @@ "search_results.total": "{count, number} {count, plural, one {תוצאה} other {תוצאות}}", "standalone.public_title": "הצצה פנימה...", "status.block": "Block @{name}", + "status.cancel_reblog_private": "Unboost", "status.cannot_reblog": "לא ניתן להדהד הודעה זו", "status.delete": "מחיקה", "status.direct": "Direct message @{name}", @@ -253,6 +257,7 @@ "status.pin": "לקבע באודות", "status.pinned": "Pinned toot", "status.reblog": "הדהוד", + "status.reblog_private": "Boost to original audience", "status.reblogged_by": "הודהד על ידי {name}", "status.reply": "תגובה", "status.replyAll": "תגובה לכולם", diff --git a/app/javascript/mastodon/locales/hr.json b/app/javascript/mastodon/locales/hr.json index 2d7d0a5..b41c983 100644 --- a/app/javascript/mastodon/locales/hr.json +++ b/app/javascript/mastodon/locales/hr.json @@ -40,6 +40,7 @@ "bundle_modal_error.retry": "Try again", "column.blocks": "Blokirani korisnici", "column.community": "Lokalni timeline", + "column.direct": "Direct messages", "column.domain_blocks": "Hidden domains", "column.favourites": "Favoriti", "column.follow_requests": "Zahtjevi za slijeđenje", @@ -100,6 +101,7 @@ "emoji_button.symbols": "Simboli", "emoji_button.travel": "Putovanja & Mjesta", "empty_column.community": "Lokalni timeline je prazan. Napiši nešto javno kako bi pokrenuo stvari!", + "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.", "empty_column.hashtag": "Još ne postoji ništa s ovim hashtagom.", "empty_column.home": "Još ne slijediš nikoga. Posjeti {public} ili koristi tražilicu kako bi počeo i upoznao druge korisnike.", "empty_column.home.public_timeline": "javni timeline", @@ -154,6 +156,7 @@ "mute_modal.hide_notifications": "Hide notifications from this user?", "navigation_bar.blocks": "Blokirani korisnici", "navigation_bar.community_timeline": "Lokalni timeline", + "navigation_bar.direct": "Direct messages", "navigation_bar.domain_blocks": "Hidden domains", "navigation_bar.edit_profile": "Uredi profil", "navigation_bar.favourites": "Favoriti", @@ -238,6 +241,7 @@ "search_results.total": "{count, number} {count, plural, one {result} other {results}}", "standalone.public_title": "A look inside...", "status.block": "Block @{name}", + "status.cancel_reblog_private": "Unboost", "status.cannot_reblog": "Ovaj post ne može biti boostan", "status.delete": "Obriši", "status.direct": "Direct message @{name}", @@ -253,6 +257,7 @@ "status.pin": "Pin on profile", "status.pinned": "Pinned toot", "status.reblog": "Podigni", + "status.reblog_private": "Boost to original audience", "status.reblogged_by": "{name} je podigao", "status.reply": "Odgovori", "status.replyAll": "Odgovori na temu", diff --git a/app/javascript/mastodon/locales/hu.json b/app/javascript/mastodon/locales/hu.json index 24f3a78..956accc 100644 --- a/app/javascript/mastodon/locales/hu.json +++ b/app/javascript/mastodon/locales/hu.json @@ -40,6 +40,7 @@ "bundle_modal_error.retry": "Próbálja újra", "column.blocks": "Letiltott felhasználók", "column.community": "Helyi idővonal", + "column.direct": "Direct messages", "column.domain_blocks": "Hidden domains", "column.favourites": "Kedvencek", "column.follow_requests": "Követési kérések", @@ -100,6 +101,7 @@ "emoji_button.symbols": "Szimbólumok", "emoji_button.travel": "Utazás és Helyek", "empty_column.community": "A helyi idővonal üres. Írj egy publikus stástuszt, hogy elindítsd a labdát!", + "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.", "empty_column.hashtag": "Jelenleg nem található semmi ezen hashtaggel.", "empty_column.home": "A hazai idővonala üres! Látogasd meg a {public} vagy használd a keresőt, hogy ismerj meg más felhasználókat.", "empty_column.home.public_timeline": "publikus idővonal", @@ -154,6 +156,7 @@ "mute_modal.hide_notifications": "Értesítések elrejtése ezen felhasználótól?", "navigation_bar.blocks": "Tiltott felhasználók", "navigation_bar.community_timeline": "Helyi idővonal", + "navigation_bar.direct": "Direct messages", "navigation_bar.domain_blocks": "Hidden domains", "navigation_bar.edit_profile": "Profil szerkesztése", "navigation_bar.favourites": "Kedvencek", @@ -238,6 +241,7 @@ "search_results.total": "{count, number} {count, plural, one {result} other {results}}", "standalone.public_title": "Betekintés...", "status.block": "Block @{name}", + "status.cancel_reblog_private": "Unboost", "status.cannot_reblog": "Ezen státusz nem rebloggolható", "status.delete": "Törlés", "status.direct": "Direct message @{name}", @@ -253,6 +257,7 @@ "status.pin": "Kitűzés a profilra", "status.pinned": "Pinned toot", "status.reblog": "Reblog", + "status.reblog_private": "Boost to original audience", "status.reblogged_by": "{name} reblogolta", "status.reply": "Válasz", "status.replyAll": "Válaszolj a beszélgetésre", diff --git a/app/javascript/mastodon/locales/hy.json b/app/javascript/mastodon/locales/hy.json index 2ba52c5..33e0792 100644 --- a/app/javascript/mastodon/locales/hy.json +++ b/app/javascript/mastodon/locales/hy.json @@ -40,6 +40,7 @@ "bundle_modal_error.retry": "Կրկին փորձել", "column.blocks": "Արգելափակված օգտատերեր", "column.community": "Տեղական հոսք", + "column.direct": "Direct messages", "column.domain_blocks": "Hidden domains", "column.favourites": "Հավանածներ", "column.follow_requests": "Հետեւելու հայցեր", @@ -100,6 +101,7 @@ "emoji_button.symbols": "Նշաններ", "emoji_button.travel": "Ուղեւորություն եւ տեղանքներ", "empty_column.community": "Տեղական հոսքը դատա՛րկ է։ Հրապարակային մի բան գրիր շարժիչը խոդ տալու համար։", + "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.", "empty_column.hashtag": "Այս պիտակով դեռ ոչինչ չկա։", "empty_column.home": "Քո հիմնական հոսքը դատա՛րկ է։ Այցելի՛ր {public}ը կամ օգտվիր որոնումից՝ այլ մարդկանց հանդիպելու համար։", "empty_column.home.public_timeline": "հրապարակային հոսք", @@ -154,6 +156,7 @@ "mute_modal.hide_notifications": "Թաքցնե՞լ ցանուցումներն այս օգտատիրոջից։", "navigation_bar.blocks": "Արգելափակված օգտատերեր", "navigation_bar.community_timeline": "Տեղական հոսք", + "navigation_bar.direct": "Direct messages", "navigation_bar.domain_blocks": "Hidden domains", "navigation_bar.edit_profile": "Խմբագրել անձնական էջը", "navigation_bar.favourites": "Հավանածներ", @@ -238,6 +241,7 @@ "search_results.total": "{count, number} {count, plural, one {result} other {results}}", "standalone.public_title": "Այս պահին…", "status.block": "Արգելափակել @{name}֊ին", + "status.cancel_reblog_private": "Unboost", "status.cannot_reblog": "Այս թութը չի կարող տարածվել", "status.delete": "Ջնջել", "status.direct": "Direct message @{name}", @@ -253,6 +257,7 @@ "status.pin": "Ամրացնել անձնական էջում", "status.pinned": "Pinned toot", "status.reblog": "Տարածել", + "status.reblog_private": "Boost to original audience", "status.reblogged_by": "{name} տարածել է", "status.reply": "Պատասխանել", "status.replyAll": "Պատասխանել թելին", diff --git a/app/javascript/mastodon/locales/id.json b/app/javascript/mastodon/locales/id.json index e1518c1..412ffd3 100644 --- a/app/javascript/mastodon/locales/id.json +++ b/app/javascript/mastodon/locales/id.json @@ -40,6 +40,7 @@ "bundle_modal_error.retry": "Coba lagi", "column.blocks": "Pengguna diblokir", "column.community": "Linimasa Lokal", + "column.direct": "Direct messages", "column.domain_blocks": "Hidden domains", "column.favourites": "Favorit", "column.follow_requests": "Permintaan mengikuti", @@ -100,6 +101,7 @@ "emoji_button.symbols": "Simbol", "emoji_button.travel": "Tempat Wisata", "empty_column.community": "Linimasa lokal masih kosong. Tulis sesuatu secara publik dan buat roda berputar!", + "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.", "empty_column.hashtag": "Tidak ada apapun dalam hashtag ini.", "empty_column.home": "Linimasa anda kosong! Kunjungi {public} atau gunakan pencarian untuk memulai dan bertemu pengguna lain.", "empty_column.home.public_timeline": "linimasa publik", @@ -154,6 +156,7 @@ "mute_modal.hide_notifications": "Hide notifications from this user?", "navigation_bar.blocks": "Pengguna diblokir", "navigation_bar.community_timeline": "Linimasa lokal", + "navigation_bar.direct": "Direct messages", "navigation_bar.domain_blocks": "Hidden domains", "navigation_bar.edit_profile": "Ubah profil", "navigation_bar.favourites": "Favorit", @@ -238,6 +241,7 @@ "search_results.total": "{count, number} {count, plural, one {hasil} other {hasil}}", "standalone.public_title": "A look inside...", "status.block": "Block @{name}", + "status.cancel_reblog_private": "Unboost", "status.cannot_reblog": "This post cannot be boosted", "status.delete": "Hapus", "status.direct": "Direct message @{name}", @@ -253,6 +257,7 @@ "status.pin": "Pin on profile", "status.pinned": "Pinned toot", "status.reblog": "Boost", + "status.reblog_private": "Boost to original audience", "status.reblogged_by": "di-boost {name}", "status.reply": "Balas", "status.replyAll": "Balas ke semua", diff --git a/app/javascript/mastodon/locales/io.json b/app/javascript/mastodon/locales/io.json index c79d4a6..9730bf9 100644 --- a/app/javascript/mastodon/locales/io.json +++ b/app/javascript/mastodon/locales/io.json @@ -40,6 +40,7 @@ "bundle_modal_error.retry": "Try again", "column.blocks": "Blokusita uzeri", "column.community": "Lokala tempolineo", + "column.direct": "Direct messages", "column.domain_blocks": "Hidden domains", "column.favourites": "Favorati", "column.follow_requests": "Demandi di sequado", @@ -100,6 +101,7 @@ "emoji_button.symbols": "Symbols", "emoji_button.travel": "Travel & Places", "empty_column.community": "La lokala tempolineo esas vakua. Skribez ulo publike por iniciar la agiveso!", + "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.", "empty_column.hashtag": "Esas ankore nulo en ta gretovorto.", "empty_column.home": "Tu sequas ankore nulu. Vizitez {public} od uzez la serchilo por komencar e renkontrar altra uzeri.", "empty_column.home.public_timeline": "la publika tempolineo", @@ -154,6 +156,7 @@ "mute_modal.hide_notifications": "Hide notifications from this user?", "navigation_bar.blocks": "Blokusita uzeri", "navigation_bar.community_timeline": "Lokala tempolineo", + "navigation_bar.direct": "Direct messages", "navigation_bar.domain_blocks": "Hidden domains", "navigation_bar.edit_profile": "Modifikar profilo", "navigation_bar.favourites": "Favorati", @@ -238,6 +241,7 @@ "search_results.total": "{count, number} {count, plural, one {rezulto} other {rezulti}}", "standalone.public_title": "A look inside...", "status.block": "Block @{name}", + "status.cancel_reblog_private": "Unboost", "status.cannot_reblog": "This post cannot be boosted", "status.delete": "Efacar", "status.direct": "Direct message @{name}", @@ -253,6 +257,7 @@ "status.pin": "Pin on profile", "status.pinned": "Pinned toot", "status.reblog": "Repetar", + "status.reblog_private": "Boost to original audience", "status.reblogged_by": "{name} repetita", "status.reply": "Respondar", "status.replyAll": "Respondar a filo", diff --git a/app/javascript/mastodon/locales/it.json b/app/javascript/mastodon/locales/it.json index 3c85a3e..5146d7c 100644 --- a/app/javascript/mastodon/locales/it.json +++ b/app/javascript/mastodon/locales/it.json @@ -40,6 +40,7 @@ "bundle_modal_error.retry": "Try again", "column.blocks": "Utenti bloccati", "column.community": "Timeline locale", + "column.direct": "Direct messages", "column.domain_blocks": "Hidden domains", "column.favourites": "Apprezzati", "column.follow_requests": "Richieste di amicizia", @@ -100,6 +101,7 @@ "emoji_button.symbols": "Symbols", "emoji_button.travel": "Travel & Places", "empty_column.community": "La timeline locale è vuota. Condividi qualcosa pubblicamente per dare inizio alla festa!", + "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.", "empty_column.hashtag": "Non c'è ancora nessun post con questo hashtag.", "empty_column.home": "Non stai ancora seguendo nessuno. Visita {public} o usa la ricerca per incontrare nuove persone.", "empty_column.home.public_timeline": "la timeline pubblica", @@ -154,6 +156,7 @@ "mute_modal.hide_notifications": "Hide notifications from this user?", "navigation_bar.blocks": "Utenti bloccati", "navigation_bar.community_timeline": "Timeline locale", + "navigation_bar.direct": "Direct messages", "navigation_bar.domain_blocks": "Hidden domains", "navigation_bar.edit_profile": "Modifica profilo", "navigation_bar.favourites": "Apprezzati", @@ -238,6 +241,7 @@ "search_results.total": "{count} {count, plural, one {risultato} other {risultati}}", "standalone.public_title": "A look inside...", "status.block": "Block @{name}", + "status.cancel_reblog_private": "Unboost", "status.cannot_reblog": "This post cannot be boosted", "status.delete": "Elimina", "status.direct": "Direct message @{name}", @@ -253,6 +257,7 @@ "status.pin": "Pin on profile", "status.pinned": "Pinned toot", "status.reblog": "Condividi", + "status.reblog_private": "Boost to original audience", "status.reblogged_by": "{name} ha condiviso", "status.reply": "Rispondi", "status.replyAll": "Reply to thread", diff --git a/app/javascript/mastodon/locales/ja.json b/app/javascript/mastodon/locales/ja.json index 2da9192..a4aa06a 100644 --- a/app/javascript/mastodon/locales/ja.json +++ b/app/javascript/mastodon/locales/ja.json @@ -40,6 +40,7 @@ "bundle_modal_error.retry": "再試行", "column.blocks": "ブロックしたユーザー", "column.community": "ローカルタイムライン", + "column.direct": "Direct messages", "column.domain_blocks": "非表示にしたドメイン", "column.favourites": "お気に入り", "column.follow_requests": "フォローリクエスト", @@ -100,6 +101,7 @@ "emoji_button.symbols": "記号", "emoji_button.travel": "旅行と場所", "empty_column.community": "ローカルタイムラインはまだ使われていません。何か書いてみましょう!", + "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.", "empty_column.hashtag": "このハッシュタグはまだ使われていません。", "empty_column.home": "まだ誰もフォローしていません。{public}を見に行くか、検索を使って他のユーザーを見つけましょう。", "empty_column.home.public_timeline": "連合タイムライン", @@ -154,6 +156,7 @@ "mute_modal.hide_notifications": "このユーザーからの通知を隠しますか?", "navigation_bar.blocks": "ブロックしたユーザー", "navigation_bar.community_timeline": "ローカルタイムライン", + "navigation_bar.direct": "Direct messages", "navigation_bar.domain_blocks": "非表示にしたドメイン", "navigation_bar.edit_profile": "プロフィールを編集", "navigation_bar.favourites": "お気に入り", @@ -238,6 +241,7 @@ "search_results.total": "{count, number}件の結果", "standalone.public_title": "今こんな話をしています...", "status.block": "@{name}さんをブロック", + "status.cancel_reblog_private": "Unboost", "status.cannot_reblog": "この投稿はブーストできません", "status.delete": "削除", "status.direct": "@{name}さんにダイレクトメッセージ", @@ -253,6 +257,7 @@ "status.pin": "プロフィールに固定表示", "status.pinned": "固定されたトゥート", "status.reblog": "ブースト", + "status.reblog_private": "Boost to original audience", "status.reblogged_by": "{name}さんがブースト", "status.reply": "返信", "status.replyAll": "全員に返信", diff --git a/app/javascript/mastodon/locales/ko.json b/app/javascript/mastodon/locales/ko.json index e2fadff..92367dc 100644 --- a/app/javascript/mastodon/locales/ko.json +++ b/app/javascript/mastodon/locales/ko.json @@ -40,6 +40,7 @@ "bundle_modal_error.retry": "다시 시도", "column.blocks": "차단 중인 사용자", "column.community": "로컬 타임라인", + "column.direct": "Direct messages", "column.domain_blocks": "Hidden domains", "column.favourites": "즐겨찾기", "column.follow_requests": "팔로우 요청", @@ -100,6 +101,7 @@ "emoji_button.symbols": "기호", "emoji_button.travel": "여행과 장소", "empty_column.community": "로컬 타임라인에 아무 것도 없습니다. 아무거나 적어 보세요!", + "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.", "empty_column.hashtag": "이 해시태그는 아직 사용되지 않았습니다.", "empty_column.home": "아직 아무도 팔로우 하고 있지 않습니다. {public}를 보러 가거나, 검색하여 다른 사용자를 찾아 보세요.", "empty_column.home.public_timeline": "연합 타임라인", @@ -154,6 +156,7 @@ "mute_modal.hide_notifications": "이 사용자로부터의 알림을 뮤트하시겠습니까?", "navigation_bar.blocks": "차단한 사용자", "navigation_bar.community_timeline": "로컬 타임라인", + "navigation_bar.direct": "Direct messages", "navigation_bar.domain_blocks": "Hidden domains", "navigation_bar.edit_profile": "프로필 편집", "navigation_bar.favourites": "즐겨찾기", @@ -238,6 +241,7 @@ "search_results.total": "{count, number}건의 결과", "standalone.public_title": "지금 이런 이야기를 하고 있습니다…", "status.block": "@{name} 차단", + "status.cancel_reblog_private": "Unboost", "status.cannot_reblog": "이 포스트는 부스트 할 수 없습니다", "status.delete": "삭제", "status.direct": "Direct message @{name}", @@ -253,6 +257,7 @@ "status.pin": "고정", "status.pinned": "고정 된 툿", "status.reblog": "부스트", + "status.reblog_private": "Boost to original audience", "status.reblogged_by": "{name}님이 부스트 했습니다", "status.reply": "답장", "status.replyAll": "전원에게 답장", diff --git a/app/javascript/mastodon/locales/nl.json b/app/javascript/mastodon/locales/nl.json index 0222432..c18ddbd 100644 --- a/app/javascript/mastodon/locales/nl.json +++ b/app/javascript/mastodon/locales/nl.json @@ -40,6 +40,7 @@ "bundle_modal_error.retry": "Opnieuw proberen", "column.blocks": "Geblokkeerde gebruikers", "column.community": "Lokale tijdlijn", + "column.direct": "Direct messages", "column.domain_blocks": "Hidden domains", "column.favourites": "Favorieten", "column.follow_requests": "Volgverzoeken", @@ -100,6 +101,7 @@ "emoji_button.symbols": "Symbolen", "emoji_button.travel": "Reizen en plekken", "empty_column.community": "De lokale tijdlijn is nog leeg. Toot iets in het openbaar om de bal aan het rollen te krijgen!", + "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.", "empty_column.hashtag": "Er is nog niks te vinden onder deze hashtag.", "empty_column.home": "Jij volgt nog niemand. Bezoek {public} of gebruik het zoekvenster om andere mensen te ontmoeten.", "empty_column.home.public_timeline": "de globale tijdlijn", @@ -154,6 +156,7 @@ "mute_modal.hide_notifications": "Verberg meldingen van deze persoon?", "navigation_bar.blocks": "Geblokkeerde gebruikers", "navigation_bar.community_timeline": "Lokale tijdlijn", + "navigation_bar.direct": "Direct messages", "navigation_bar.domain_blocks": "Hidden domains", "navigation_bar.edit_profile": "Profiel bewerken", "navigation_bar.favourites": "Favorieten", @@ -238,6 +241,7 @@ "search_results.total": "{count, number} {count, plural, one {resultaat} other {resultaten}}", "standalone.public_title": "Een kijkje binnenin...", "status.block": "Blokkeer @{name}", + "status.cancel_reblog_private": "Unboost", "status.cannot_reblog": "Deze toot kan niet geboost worden", "status.delete": "Verwijderen", "status.direct": "Direct message @{name}", @@ -253,6 +257,7 @@ "status.pin": "Aan profielpagina vastmaken", "status.pinned": "Vastgemaakte toot", "status.reblog": "Boost", + "status.reblog_private": "Boost to original audience", "status.reblogged_by": "{name} boostte", "status.reply": "Reageren", "status.replyAll": "Reageer op iedereen", diff --git a/app/javascript/mastodon/locales/no.json b/app/javascript/mastodon/locales/no.json index 20b2cbb..282a72a 100644 --- a/app/javascript/mastodon/locales/no.json +++ b/app/javascript/mastodon/locales/no.json @@ -40,6 +40,7 @@ "bundle_modal_error.retry": "Prøv igjen", "column.blocks": "Blokkerte brukere", "column.community": "Lokal tidslinje", + "column.direct": "Direct messages", "column.domain_blocks": "Hidden domains", "column.favourites": "Likt", "column.follow_requests": "Følgeforespørsler", @@ -100,6 +101,7 @@ "emoji_button.symbols": "Symboler", "emoji_button.travel": "Reise & steder", "empty_column.community": "Den lokale tidslinjen er tom. Skriv noe offentlig for å få snøballen til å rulle!", + "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.", "empty_column.hashtag": "Det er ingenting i denne hashtagen ennå.", "empty_column.home": "Du har ikke fulgt noen ennå. Besøk {publlic} eller bruk søk for å komme i gang og møte andre brukere.", "empty_column.home.public_timeline": "en offentlig tidslinje", @@ -154,6 +156,7 @@ "mute_modal.hide_notifications": "Skjul varslinger fra denne brukeren?", "navigation_bar.blocks": "Blokkerte brukere", "navigation_bar.community_timeline": "Lokal tidslinje", + "navigation_bar.direct": "Direct messages", "navigation_bar.domain_blocks": "Hidden domains", "navigation_bar.edit_profile": "Rediger profil", "navigation_bar.favourites": "Favoritter", @@ -238,6 +241,7 @@ "search_results.total": "{count, number} {count, plural, one {resultat} other {resultater}}", "standalone.public_title": "En titt inni...", "status.block": "Block @{name}", + "status.cancel_reblog_private": "Unboost", "status.cannot_reblog": "Denne posten kan ikke fremheves", "status.delete": "Slett", "status.direct": "Direct message @{name}", @@ -253,6 +257,7 @@ "status.pin": "Fest på profilen", "status.pinned": "Pinned toot", "status.reblog": "Fremhev", + "status.reblog_private": "Boost to original audience", "status.reblogged_by": "Fremhevd av {name}", "status.reply": "Svar", "status.replyAll": "Svar til samtale", diff --git a/app/javascript/mastodon/locales/oc.json b/app/javascript/mastodon/locales/oc.json index 32133c1..7170aef 100644 --- a/app/javascript/mastodon/locales/oc.json +++ b/app/javascript/mastodon/locales/oc.json @@ -40,6 +40,7 @@ "bundle_modal_error.retry": "Tornar ensajar", "column.blocks": "Personas blocadas", "column.community": "Flux public local", + "column.direct": "Direct messages", "column.domain_blocks": "Hidden domains", "column.favourites": "Favorits", "column.follow_requests": "Demandas d’abonament", @@ -100,6 +101,7 @@ "emoji_button.symbols": "Simbòls", "emoji_button.travel": "Viatges & lòcs", "empty_column.community": "Lo flux public local es void. Escrivètz quicòm per lo garnir !", + "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.", "empty_column.hashtag": "I a pas encara de contengut ligat a aquesta etiqueta.", "empty_column.home": "Vòstre flux d’acuèlh es void. Visitatz {public} o utilizatz la recèrca per vos connectar a d’autras personas.", "empty_column.home.public_timeline": "lo flux public", @@ -154,6 +156,7 @@ "mute_modal.hide_notifications": "Rescondre las notificacions d’aquesta persona ?", "navigation_bar.blocks": "Personas blocadas", "navigation_bar.community_timeline": "Flux public local", + "navigation_bar.direct": "Direct messages", "navigation_bar.domain_blocks": "Hidden domains", "navigation_bar.edit_profile": "Modificar lo perfil", "navigation_bar.favourites": "Favorits", @@ -238,6 +241,7 @@ "search_results.total": "{count, number} {count, plural, one {resultat} other {resultats}}", "standalone.public_title": "Una ulhada dedins…", "status.block": "Blocar @{name}", + "status.cancel_reblog_private": "Unboost", "status.cannot_reblog": "Aqueste estatut pòt pas èsser partejat", "status.delete": "Escafar", "status.direct": "Direct message @{name}", @@ -253,6 +257,7 @@ "status.pin": "Penjar al perfil", "status.pinned": "Tut penjat", "status.reblog": "Partejar", + "status.reblog_private": "Boost to original audience", "status.reblogged_by": "{name} a partejat", "status.reply": "Respondre", "status.replyAll": "Respondre a la conversacion", diff --git a/app/javascript/mastodon/locales/pl.json b/app/javascript/mastodon/locales/pl.json index 5360b6f..c55603a 100644 --- a/app/javascript/mastodon/locales/pl.json +++ b/app/javascript/mastodon/locales/pl.json @@ -40,6 +40,7 @@ "bundle_modal_error.retry": "Spróbuj ponownie", "column.blocks": "Zablokowani użytkownicy", "column.community": "Lokalna oś czasu", + "column.direct": "Direct messages", "column.domain_blocks": "Ukryte domeny", "column.favourites": "Ulubione", "column.follow_requests": "Prośby o śledzenie", @@ -100,6 +101,7 @@ "emoji_button.symbols": "Symbole", "emoji_button.travel": "Podróże i miejsca", "empty_column.community": "Lokalna oś czasu jest pusta. Napisz coś publicznie, aby zagaić!", + "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.", "empty_column.hashtag": "Nie ma wpisów oznaczonych tym hashtagiem. Możesz napisać pierwszy!", "empty_column.home": "Nie śledzisz nikogo. Odwiedź publiczną oś czasu lub użyj wyszukiwarki, aby znaleźć interesujące Cię profile.", "empty_column.home.public_timeline": "publiczna oś czasu", @@ -154,6 +156,7 @@ "mute_modal.hide_notifications": "Chcesz ukryć powiadomienia od tego użytkownika?", "navigation_bar.blocks": "Zablokowani użytkownicy", "navigation_bar.community_timeline": "Lokalna oś czasu", + "navigation_bar.direct": "Direct messages", "navigation_bar.domain_blocks": "Ukryte domeny", "navigation_bar.edit_profile": "Edytuj profil", "navigation_bar.favourites": "Ulubione", @@ -238,6 +241,7 @@ "search_results.total": "{count, number} {count, plural, one {wynik} few {wyniki} many {wyników} more {wyników}}", "standalone.public_title": "Spojrzenie w głąb…", "status.block": "Zablokuj @{name}", + "status.cancel_reblog_private": "Unboost", "status.cannot_reblog": "Ten wpis nie może zostać podbity", "status.delete": "Usuń", "status.direct": "Direct message @{name}", @@ -253,6 +257,7 @@ "status.pin": "Przypnij do profilu", "status.pinned": "Przypięty wpis", "status.reblog": "Podbij", + "status.reblog_private": "Boost to original audience", "status.reblogged_by": "{name} podbił", "status.reply": "Odpowiedz", "status.replyAll": "Odpowiedz na wątek", diff --git a/app/javascript/mastodon/locales/pt-BR.json b/app/javascript/mastodon/locales/pt-BR.json index b4be0bb..c604476 100644 --- a/app/javascript/mastodon/locales/pt-BR.json +++ b/app/javascript/mastodon/locales/pt-BR.json @@ -40,6 +40,7 @@ "bundle_modal_error.retry": "Tente novamente", "column.blocks": "Usuários bloqueados", "column.community": "Local", + "column.direct": "Direct messages", "column.domain_blocks": "Hidden domains", "column.favourites": "Favoritos", "column.follow_requests": "Seguidores pendentes", @@ -100,6 +101,7 @@ "emoji_button.symbols": "Símbolos", "emoji_button.travel": "Viagens & Lugares", "empty_column.community": "A timeline local está vazia. Escreva algo publicamente para começar!", + "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.", "empty_column.hashtag": "Ainda não há qualquer conteúdo com essa hashtag.", "empty_column.home": "Você ainda não segue usuário algum. Visite a timeline {public} ou use o buscador para procurar e conhecer outros usuários.", "empty_column.home.public_timeline": "global", @@ -154,6 +156,7 @@ "mute_modal.hide_notifications": "Esconder notificações deste usuário?", "navigation_bar.blocks": "Usuários bloqueados", "navigation_bar.community_timeline": "Local", + "navigation_bar.direct": "Direct messages", "navigation_bar.domain_blocks": "Hidden domains", "navigation_bar.edit_profile": "Editar perfil", "navigation_bar.favourites": "Favoritos", @@ -238,6 +241,7 @@ "search_results.total": "{count, number} {count, plural, one {resultado} other {resultados}}", "standalone.public_title": "Dê uma espiada...", "status.block": "Block @{name}", + "status.cancel_reblog_private": "Unboost", "status.cannot_reblog": "Esta postagem não pode ser compartilhada", "status.delete": "Excluir", "status.direct": "Direct message @{name}", @@ -253,6 +257,7 @@ "status.pin": "Fixar no perfil", "status.pinned": "Toot fixado", "status.reblog": "Compartilhar", + "status.reblog_private": "Boost to original audience", "status.reblogged_by": "{name} compartilhou", "status.reply": "Responder", "status.replyAll": "Responder à sequência", diff --git a/app/javascript/mastodon/locales/pt.json b/app/javascript/mastodon/locales/pt.json index 132de52..826785a 100644 --- a/app/javascript/mastodon/locales/pt.json +++ b/app/javascript/mastodon/locales/pt.json @@ -40,6 +40,7 @@ "bundle_modal_error.retry": "Tente de novo", "column.blocks": "Utilizadores Bloqueados", "column.community": "Local", + "column.direct": "Direct messages", "column.domain_blocks": "Hidden domains", "column.favourites": "Favoritos", "column.follow_requests": "Seguidores Pendentes", @@ -100,6 +101,7 @@ "emoji_button.symbols": "Símbolos", "emoji_button.travel": "Viagens & Lugares", "empty_column.community": "Ainda não existe conteúdo local para mostrar!", + "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.", "empty_column.hashtag": "Não foram encontradas publicações com essa hashtag.", "empty_column.home": "Ainda não segues qualquer utilizador. Visita {public} ou utiliza a pesquisa para procurar outros utilizadores.", "empty_column.home.public_timeline": "global", @@ -154,6 +156,7 @@ "mute_modal.hide_notifications": "Esconder notificações deste utilizador?", "navigation_bar.blocks": "Utilizadores bloqueados", "navigation_bar.community_timeline": "Local", + "navigation_bar.direct": "Direct messages", "navigation_bar.domain_blocks": "Hidden domains", "navigation_bar.edit_profile": "Editar perfil", "navigation_bar.favourites": "Favoritos", @@ -238,6 +241,7 @@ "search_results.total": "{count, number} {count, plural, one {resultado} other {resultados}}", "standalone.public_title": "Espreitar lá dentro...", "status.block": "Block @{name}", + "status.cancel_reblog_private": "Unboost", "status.cannot_reblog": "Este post não pode ser partilhado", "status.delete": "Eliminar", "status.direct": "Direct message @{name}", @@ -253,6 +257,7 @@ "status.pin": "Fixar no perfil", "status.pinned": "Pinned toot", "status.reblog": "Partilhar", + "status.reblog_private": "Boost to original audience", "status.reblogged_by": "{name} partilhou", "status.reply": "Responder", "status.replyAll": "Responder à conversa", diff --git a/app/javascript/mastodon/locales/ru.json b/app/javascript/mastodon/locales/ru.json index b56ccf1..bb3cc17 100644 --- a/app/javascript/mastodon/locales/ru.json +++ b/app/javascript/mastodon/locales/ru.json @@ -40,6 +40,7 @@ "bundle_modal_error.retry": "Попробовать снова", "column.blocks": "Список блокировки", "column.community": "Локальная лента", + "column.direct": "Direct messages", "column.domain_blocks": "Скрытые домены", "column.favourites": "Понравившееся", "column.follow_requests": "Запросы на подписку", @@ -100,6 +101,7 @@ "emoji_button.symbols": "Символы", "emoji_button.travel": "Путешествия", "empty_column.community": "Локальная лента пуста. Напишите что-нибудь, чтобы разогреть народ!", + "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.", "empty_column.hashtag": "Статусов с таким хэштегом еще не существует.", "empty_column.home": "Пока Вы ни на кого не подписаны. Полистайте {public} или используйте поиск, чтобы освоиться и завести новые знакомства.", "empty_column.home.public_timeline": "публичные ленты", @@ -154,6 +156,7 @@ "mute_modal.hide_notifications": "Убрать уведомления от этого пользователя?", "navigation_bar.blocks": "Список блокировки", "navigation_bar.community_timeline": "Локальная лента", + "navigation_bar.direct": "Direct messages", "navigation_bar.domain_blocks": "Скрытые домены", "navigation_bar.edit_profile": "Изменить профиль", "navigation_bar.favourites": "Понравившееся", @@ -238,6 +241,7 @@ "search_results.total": "{count, number} {count, plural, one {результат} few {результата} many {результатов} other {результатов}}", "standalone.public_title": "Прямо сейчас", "status.block": "Заблокировать @{name}", + "status.cancel_reblog_private": "Unboost", "status.cannot_reblog": "Этот статус не может быть продвинут", "status.delete": "Удалить", "status.direct": "Direct message @{name}", @@ -253,6 +257,7 @@ "status.pin": "Закрепить в профиле", "status.pinned": "Pinned toot", "status.reblog": "Продвинуть", + "status.reblog_private": "Boost to original audience", "status.reblogged_by": "{name} продвинул(а)", "status.reply": "Ответить", "status.replyAll": "Ответить на тред", diff --git a/app/javascript/mastodon/locales/sk.json b/app/javascript/mastodon/locales/sk.json index 1593151..58274fd 100644 --- a/app/javascript/mastodon/locales/sk.json +++ b/app/javascript/mastodon/locales/sk.json @@ -40,6 +40,7 @@ "bundle_modal_error.retry": "Skúsiť znova", "column.blocks": "Blokovaní užívatelia", "column.community": "Lokálna časová os", + "column.direct": "Direct messages", "column.domain_blocks": "Hidden domains", "column.favourites": "Obľúbené", "column.follow_requests": "Žiadosti o sledovanie", @@ -100,6 +101,7 @@ "emoji_button.symbols": "Symboly", "emoji_button.travel": "Cestovanie a miesta", "empty_column.community": "Lokálna časová os je prázdna. Napíšte niečo, aby sa to tu začalo hýbať!", + "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.", "empty_column.hashtag": "Pod týmto hashtagom sa ešte nič nenachádza.", "empty_column.home": "Vaša lokálna osa je zatiaľ prázdna! Pre začiatok pozrite {public} alebo použite vyhľadávanie a nájdite tak ostatných používateľov.", "empty_column.home.public_timeline": "verejná časová os", @@ -154,6 +156,7 @@ "mute_modal.hide_notifications": "Skryť notifikácie od tohoto užívateľa?", "navigation_bar.blocks": "Blokovaní užívatelia", "navigation_bar.community_timeline": "Lokálna časová os", + "navigation_bar.direct": "Direct messages", "navigation_bar.domain_blocks": "Hidden domains", "navigation_bar.edit_profile": "Upraviť profil", "navigation_bar.favourites": "Obľúbené", @@ -238,6 +241,7 @@ "search_results.total": "{count, number} {count, plural, jeden {výsledok} ostatné {výsledky}}", "standalone.public_title": "Náhľad dovnútra...", "status.block": "Blokovať @{name}", + "status.cancel_reblog_private": "Unboost", "status.cannot_reblog": "Tento príspevok nemôže byť re-tootnutý", "status.delete": "Zmazať", "status.direct": "Direct message @{name}", @@ -253,6 +257,7 @@ "status.pin": "Pripni na profil", "status.pinned": "Pripnutý príspevok", "status.reblog": "Povýšiť", + "status.reblog_private": "Boost to original audience", "status.reblogged_by": "{name} povýšil/a", "status.reply": "Odpovedať", "status.replyAll": "Odpovedať na diskusiu", diff --git a/app/javascript/mastodon/locales/sr-Latn.json b/app/javascript/mastodon/locales/sr-Latn.json index 69c7aa6..e4d07ed 100644 --- a/app/javascript/mastodon/locales/sr-Latn.json +++ b/app/javascript/mastodon/locales/sr-Latn.json @@ -40,6 +40,7 @@ "bundle_modal_error.retry": "Pokušajte ponovo", "column.blocks": "Blokirani korisnici", "column.community": "Lokalna lajna", + "column.direct": "Direct messages", "column.domain_blocks": "Hidden domains", "column.favourites": "Omiljeni", "column.follow_requests": "Zahtevi za praćenje", @@ -100,6 +101,7 @@ "emoji_button.symbols": "Simboli", "emoji_button.travel": "Putovanja & mesta", "empty_column.community": "Lokalna lajna je prazna. Napišite nešto javno da lajna produva!", + "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.", "empty_column.hashtag": "Trenutno nema ništa na ovom heštegu.", "empty_column.home": "Vaša lajna je prazna! Posetite {public} ili koristite pretragu da počnete i upoznajete nove ljude.", "empty_column.home.public_timeline": "javna lajna", @@ -154,6 +156,7 @@ "mute_modal.hide_notifications": "Sakrij obaveštenja od ovog korisnika?", "navigation_bar.blocks": "Blokirani korisnici", "navigation_bar.community_timeline": "Lokalna lajna", + "navigation_bar.direct": "Direct messages", "navigation_bar.domain_blocks": "Hidden domains", "navigation_bar.edit_profile": "Izmeni profil", "navigation_bar.favourites": "Omiljeni", @@ -238,6 +241,7 @@ "search_results.total": "{count, number} {count, plural, one {rezultat} few {rezultata} other {rezultata}}", "standalone.public_title": "Pogled iznutra...", "status.block": "Block @{name}", + "status.cancel_reblog_private": "Unboost", "status.cannot_reblog": "Ovaj status ne može da se podrži", "status.delete": "Obriši", "status.direct": "Direct message @{name}", @@ -253,6 +257,7 @@ "status.pin": "Prikači na profil", "status.pinned": "Pinned toot", "status.reblog": "Podrži", + "status.reblog_private": "Boost to original audience", "status.reblogged_by": "{name} podržao(la)", "status.reply": "Odgovori", "status.replyAll": "Odgovori na diskusiju", diff --git a/app/javascript/mastodon/locales/sr.json b/app/javascript/mastodon/locales/sr.json index e973945..60c781e 100644 --- a/app/javascript/mastodon/locales/sr.json +++ b/app/javascript/mastodon/locales/sr.json @@ -40,6 +40,7 @@ "bundle_modal_error.retry": "Покушајте поново", "column.blocks": "Блокирани корисници", "column.community": "Локална лајна", + "column.direct": "Direct messages", "column.domain_blocks": "Hidden domains", "column.favourites": "Омиљени", "column.follow_requests": "Захтеви за праћење", @@ -100,6 +101,7 @@ "emoji_button.symbols": "Симболи", "emoji_button.travel": "Путовања & места", "empty_column.community": "Локална лајна је празна. Напишите нешто јавно да лајна продува!", + "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.", "empty_column.hashtag": "Тренутно нема ништа на овом хештегу.", "empty_column.home": "Ваша лајна је празна! Посетите {public} или користите претрагу да почнете и упознајете нове људе.", "empty_column.home.public_timeline": "јавна лајна", @@ -154,6 +156,7 @@ "mute_modal.hide_notifications": "Сакриј обавештења од овог корисника?", "navigation_bar.blocks": "Блокирани корисници", "navigation_bar.community_timeline": "Локална лајна", + "navigation_bar.direct": "Direct messages", "navigation_bar.domain_blocks": "Hidden domains", "navigation_bar.edit_profile": "Измени профил", "navigation_bar.favourites": "Омиљени", @@ -238,6 +241,7 @@ "search_results.total": "{count, number} {count, plural, one {резултат} few {резултата} other {резултата}}", "standalone.public_title": "Поглед изнутра...", "status.block": "Block @{name}", + "status.cancel_reblog_private": "Unboost", "status.cannot_reblog": "Овај статус не може да се подржи", "status.delete": "Обриши", "status.direct": "Direct message @{name}", @@ -253,6 +257,7 @@ "status.pin": "Прикачи на профил", "status.pinned": "Pinned toot", "status.reblog": "Подржи", + "status.reblog_private": "Boost to original audience", "status.reblogged_by": "{name} подржао(ла)", "status.reply": "Одговори", "status.replyAll": "Одговори на дискусију", diff --git a/app/javascript/mastodon/locales/sv.json b/app/javascript/mastodon/locales/sv.json index b063adb..8fa6992 100644 --- a/app/javascript/mastodon/locales/sv.json +++ b/app/javascript/mastodon/locales/sv.json @@ -40,6 +40,7 @@ "bundle_modal_error.retry": "Försök igen", "column.blocks": "Blockerade användare", "column.community": "Lokal tidslinje", + "column.direct": "Direct messages", "column.domain_blocks": "Hidden domains", "column.favourites": "Favoriter", "column.follow_requests": "Följ förfrågningar", @@ -100,6 +101,7 @@ "emoji_button.symbols": "Symboler", "emoji_button.travel": "Resor & Platser", "empty_column.community": "Den lokala tidslinjen är tom. Skriv något offentligt för att få bollen att rulla!", + "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.", "empty_column.hashtag": "Det finns inget i denna hashtag ännu.", "empty_column.home": "Din hemma-tidslinje är tom! Besök {public} eller använd sökning för att komma igång och träffa andra användare.", "empty_column.home.public_timeline": "den publika tidslinjen", @@ -154,6 +156,7 @@ "mute_modal.hide_notifications": "Dölj notifikationer från denna användare?", "navigation_bar.blocks": "Blockerade användare", "navigation_bar.community_timeline": "Lokal tidslinje", + "navigation_bar.direct": "Direct messages", "navigation_bar.domain_blocks": "Hidden domains", "navigation_bar.edit_profile": "Redigera profil", "navigation_bar.favourites": "Favoriter", @@ -238,6 +241,7 @@ "search_results.total": "{count, number} {count, plural, ett {result} andra {results}}", "standalone.public_title": "En titt inuti...", "status.block": "Block @{name}", + "status.cancel_reblog_private": "Unboost", "status.cannot_reblog": "Detta inlägg kan inte knuffas", "status.delete": "Ta bort", "status.direct": "Direct message @{name}", @@ -253,6 +257,7 @@ "status.pin": "Fäst i profil", "status.pinned": "Fäst toot", "status.reblog": "Knuff", + "status.reblog_private": "Boost to original audience", "status.reblogged_by": "{name} knuffade", "status.reply": "Svara", "status.replyAll": "Svara på tråden", diff --git a/app/javascript/mastodon/locales/th.json b/app/javascript/mastodon/locales/th.json index 22a75c2..3b91c0d 100644 --- a/app/javascript/mastodon/locales/th.json +++ b/app/javascript/mastodon/locales/th.json @@ -40,6 +40,7 @@ "bundle_modal_error.retry": "Try again", "column.blocks": "Blocked users", "column.community": "Local timeline", + "column.direct": "Direct messages", "column.domain_blocks": "Hidden domains", "column.favourites": "Favourites", "column.follow_requests": "Follow requests", @@ -100,6 +101,7 @@ "emoji_button.symbols": "Symbols", "emoji_button.travel": "Travel & Places", "empty_column.community": "The local timeline is empty. Write something publicly to get the ball rolling!", + "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.", "empty_column.hashtag": "There is nothing in this hashtag yet.", "empty_column.home": "Your home timeline is empty! Visit {public} or use search to get started and meet other users.", "empty_column.home.public_timeline": "the public timeline", @@ -154,6 +156,7 @@ "mute_modal.hide_notifications": "Hide notifications from this user?", "navigation_bar.blocks": "Blocked users", "navigation_bar.community_timeline": "Local timeline", + "navigation_bar.direct": "Direct messages", "navigation_bar.domain_blocks": "Hidden domains", "navigation_bar.edit_profile": "Edit profile", "navigation_bar.favourites": "Favourites", @@ -238,6 +241,7 @@ "search_results.total": "{count, number} {count, plural, one {result} other {results}}", "standalone.public_title": "A look inside...", "status.block": "Block @{name}", + "status.cancel_reblog_private": "Unboost", "status.cannot_reblog": "This post cannot be boosted", "status.delete": "Delete", "status.direct": "Direct message @{name}", @@ -253,6 +257,7 @@ "status.pin": "Pin on profile", "status.pinned": "Pinned toot", "status.reblog": "Boost", + "status.reblog_private": "Boost to original audience", "status.reblogged_by": "{name} boosted", "status.reply": "Reply", "status.replyAll": "Reply to thread", diff --git a/app/javascript/mastodon/locales/tr.json b/app/javascript/mastodon/locales/tr.json index 8e36c51..cdf6f46 100644 --- a/app/javascript/mastodon/locales/tr.json +++ b/app/javascript/mastodon/locales/tr.json @@ -40,6 +40,7 @@ "bundle_modal_error.retry": "Try again", "column.blocks": "Engellenen kullanıcılar", "column.community": "Yerel zaman tüneli", + "column.direct": "Direct messages", "column.domain_blocks": "Hidden domains", "column.favourites": "Favoriler", "column.follow_requests": "Takip istekleri", @@ -100,6 +101,7 @@ "emoji_button.symbols": "Semboller", "emoji_button.travel": "Seyahat ve Yerler", "empty_column.community": "Yerel zaman tüneliniz boş. Daha fazla eğlence için herkese açık bir gönderi paylaşın.", + "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.", "empty_column.hashtag": "Henüz bu hashtag’e sahip hiçbir gönderi yok.", "empty_column.home": "Henüz kimseyi takip etmiyorsunuz. {public} ziyaret edebilir veya arama kısmını kullanarak diğer kullanıcılarla iletişime geçebilirsiniz.", "empty_column.home.public_timeline": "herkese açık zaman tüneli", @@ -154,6 +156,7 @@ "mute_modal.hide_notifications": "Hide notifications from this user?", "navigation_bar.blocks": "Engellenen kullanıcılar", "navigation_bar.community_timeline": "Yerel zaman tüneli", + "navigation_bar.direct": "Direct messages", "navigation_bar.domain_blocks": "Hidden domains", "navigation_bar.edit_profile": "Profili düzenle", "navigation_bar.favourites": "Favoriler", @@ -238,6 +241,7 @@ "search_results.total": "{count, number} {count, plural, one {sonuç} other {sonuçlar}}", "standalone.public_title": "A look inside...", "status.block": "Block @{name}", + "status.cancel_reblog_private": "Unboost", "status.cannot_reblog": "Bu gönderi boost edilemez", "status.delete": "Sil", "status.direct": "Direct message @{name}", @@ -253,6 +257,7 @@ "status.pin": "Pin on profile", "status.pinned": "Pinned toot", "status.reblog": "Boost'la", + "status.reblog_private": "Boost to original audience", "status.reblogged_by": "{name} boost etti", "status.reply": "Cevapla", "status.replyAll": "Konuşmayı cevapla", diff --git a/app/javascript/mastodon/locales/uk.json b/app/javascript/mastodon/locales/uk.json index 09210a3..261e579 100644 --- a/app/javascript/mastodon/locales/uk.json +++ b/app/javascript/mastodon/locales/uk.json @@ -40,6 +40,7 @@ "bundle_modal_error.retry": "Try again", "column.blocks": "Заблоковані користувачі", "column.community": "Локальна стрічка", + "column.direct": "Direct messages", "column.domain_blocks": "Hidden domains", "column.favourites": "Вподобане", "column.follow_requests": "Запити на підписку", @@ -100,6 +101,7 @@ "emoji_button.symbols": "Символи", "emoji_button.travel": "Подорожі", "empty_column.community": "Локальна стрічка пуста. Напишіть щось, щоб розігріти народ!", + "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.", "empty_column.hashtag": "Дописів з цим хештегом поки не існує.", "empty_column.home": "Ви поки ні на кого не підписані. Погортайте {public}, або скористуйтесь пошуком, щоб освоїтися та познайомитися з іншими користувачами.", "empty_column.home.public_timeline": "публічні стрічки", @@ -154,6 +156,7 @@ "mute_modal.hide_notifications": "Hide notifications from this user?", "navigation_bar.blocks": "Заблоковані користувачі", "navigation_bar.community_timeline": "Локальна стрічка", + "navigation_bar.direct": "Direct messages", "navigation_bar.domain_blocks": "Hidden domains", "navigation_bar.edit_profile": "Редагувати профіль", "navigation_bar.favourites": "Вподобане", @@ -238,6 +241,7 @@ "search_results.total": "{count, number} {count, plural, one {результат} few {результати} many {результатів} other {результатів}}", "standalone.public_title": "A look inside...", "status.block": "Block @{name}", + "status.cancel_reblog_private": "Unboost", "status.cannot_reblog": "Цей допис не може бути передмухнутий", "status.delete": "Видалити", "status.direct": "Direct message @{name}", @@ -253,6 +257,7 @@ "status.pin": "Pin on profile", "status.pinned": "Pinned toot", "status.reblog": "Передмухнути", + "status.reblog_private": "Boost to original audience", "status.reblogged_by": "{name} передмухнув(-ла)", "status.reply": "Відповісти", "status.replyAll": "Відповісти на тред", diff --git a/app/javascript/mastodon/locales/zh-CN.json b/app/javascript/mastodon/locales/zh-CN.json index f0772ff..aba0bde 100644 --- a/app/javascript/mastodon/locales/zh-CN.json +++ b/app/javascript/mastodon/locales/zh-CN.json @@ -40,6 +40,7 @@ "bundle_modal_error.retry": "重试", "column.blocks": "屏蔽用户", "column.community": "本站时间轴", + "column.direct": "Direct messages", "column.domain_blocks": "Hidden domains", "column.favourites": "收藏过的嘟文", "column.follow_requests": "关注请求", @@ -100,6 +101,7 @@ "emoji_button.symbols": "符号", "emoji_button.travel": "旅行和地点", "empty_column.community": "本站时间轴暂时没有内容,快嘟几个来抢头香啊!", + "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.", "empty_column.hashtag": "这个话题标签下暂时没有内容。", "empty_column.home": "你还没有关注任何用户。快看看{public},向其他用户搭讪吧。", "empty_column.home.public_timeline": "公共时间轴", @@ -154,6 +156,7 @@ "mute_modal.hide_notifications": "同时隐藏来自这个用户的通知", "navigation_bar.blocks": "被屏蔽的用户", "navigation_bar.community_timeline": "本站时间轴", + "navigation_bar.direct": "Direct messages", "navigation_bar.domain_blocks": "Hidden domains", "navigation_bar.edit_profile": "修改个人资料", "navigation_bar.favourites": "收藏的内容", @@ -238,6 +241,7 @@ "search_results.total": "共 {count, number} 个结果", "standalone.public_title": "大家都在干啥?", "status.block": "屏蔽 @{name}", + "status.cancel_reblog_private": "Unboost", "status.cannot_reblog": "无法转嘟这条嘟文", "status.delete": "删除", "status.direct": "Direct message @{name}", @@ -253,6 +257,7 @@ "status.pin": "在个人资料页面置顶", "status.pinned": "Pinned toot", "status.reblog": "转嘟", + "status.reblog_private": "Boost to original audience", "status.reblogged_by": "{name} 转嘟了", "status.reply": "回复", "status.replyAll": "回复所有人", diff --git a/app/javascript/mastodon/locales/zh-HK.json b/app/javascript/mastodon/locales/zh-HK.json index 28685f4..b5ebd20 100644 --- a/app/javascript/mastodon/locales/zh-HK.json +++ b/app/javascript/mastodon/locales/zh-HK.json @@ -40,6 +40,7 @@ "bundle_modal_error.retry": "重試", "column.blocks": "封鎖用戶", "column.community": "本站時間軸", + "column.direct": "Direct messages", "column.domain_blocks": "隱藏的服務站", "column.favourites": "最愛的文章", "column.follow_requests": "關注請求", @@ -100,6 +101,7 @@ "emoji_button.symbols": "符號", "emoji_button.travel": "旅遊景物", "empty_column.community": "本站時間軸暫時未有內容,快寫一點東西來搶頭香啊!", + "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.", "empty_column.hashtag": "這個標籤暫時未有內容。", "empty_column.home": "你還沒有關注任何用戶。快看看{public},向其他用戶搭訕吧。", "empty_column.home.public_timeline": "公共時間軸", @@ -154,6 +156,7 @@ "mute_modal.hide_notifications": "隱藏來自這用戶的通知嗎?", "navigation_bar.blocks": "被你封鎖的用戶", "navigation_bar.community_timeline": "本站時間軸", + "navigation_bar.direct": "Direct messages", "navigation_bar.domain_blocks": "隱藏的服務站", "navigation_bar.edit_profile": "修改個人資料", "navigation_bar.favourites": "最愛的內容", @@ -238,6 +241,7 @@ "search_results.total": "{count, number} 項結果", "standalone.public_title": "站點一瞥…", "status.block": "封鎖 @{name}", + "status.cancel_reblog_private": "Unboost", "status.cannot_reblog": "這篇文章無法被轉推", "status.delete": "刪除", "status.direct": "私訊 @{name}", @@ -253,6 +257,7 @@ "status.pin": "置頂到資料頁", "status.pinned": "置頂文章", "status.reblog": "轉推", + "status.reblog_private": "Boost to original audience", "status.reblogged_by": "{name} 轉推", "status.reply": "回應", "status.replyAll": "回應所有人", diff --git a/app/javascript/mastodon/locales/zh-TW.json b/app/javascript/mastodon/locales/zh-TW.json index efed9cd..28d6346 100644 --- a/app/javascript/mastodon/locales/zh-TW.json +++ b/app/javascript/mastodon/locales/zh-TW.json @@ -40,6 +40,7 @@ "bundle_modal_error.retry": "重試", "column.blocks": "封鎖的使用者", "column.community": "本地時間軸", + "column.direct": "Direct messages", "column.domain_blocks": "隱藏域名", "column.favourites": "最愛", "column.follow_requests": "關注請求", @@ -100,6 +101,7 @@ "emoji_button.symbols": "符號", "emoji_button.travel": "旅遊與地點", "empty_column.community": "本地時間軸是空的。公開寫點什麼吧!", + "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.", "empty_column.hashtag": "這個主題標籤下什麼都沒有。", "empty_column.home": "你還沒關注任何人。造訪{public}或利用搜尋功能找到其他用者。", "empty_column.home.public_timeline": "公開時間軸", @@ -154,6 +156,7 @@ "mute_modal.hide_notifications": "隱藏來自這個使用者的通知?", "navigation_bar.blocks": "封鎖的使用者", "navigation_bar.community_timeline": "本地時間軸", + "navigation_bar.direct": "Direct messages", "navigation_bar.domain_blocks": "隱藏的域名", "navigation_bar.edit_profile": "編輯用者資訊", "navigation_bar.favourites": "最愛", @@ -238,6 +241,7 @@ "search_results.total": "{count, number} 項結果", "standalone.public_title": "站點一瞥…", "status.block": "封鎖 @{name}", + "status.cancel_reblog_private": "Unboost", "status.cannot_reblog": "此貼文無法轉推", "status.delete": "刪除", "status.direct": "Direct message @{name}", @@ -253,6 +257,7 @@ "status.pin": "置頂到個人資訊頁", "status.pinned": "置頂的推文", "status.reblog": "轉推", + "status.reblog_private": "Boost to original audience", "status.reblogged_by": "{name} 轉推了", "status.reply": "回應", "status.replyAll": "回應這串", diff --git a/app/javascript/mastodon/reducers/contexts.js b/app/javascript/mastodon/reducers/contexts.js index c1ecf7e..ebd01e5 100644 --- a/app/javascript/mastodon/reducers/contexts.js +++ b/app/javascript/mastodon/reducers/contexts.js @@ -41,8 +41,9 @@ const deleteFromContexts = (immutableState, ids) => immutableState.withMutations }); const filterContexts = (state, relationship, statuses) => { - const ownedStatusIds = statuses.filter(status => status.get('account') === relationship.id) - .map(status => status.get('id')); + const ownedStatusIds = statuses + .filter(status => status.get('account') === relationship.id) + .map(status => status.get('id')); return deleteFromContexts(state, ownedStatusIds); }; diff --git a/app/javascript/mastodon/reducers/settings.js b/app/javascript/mastodon/reducers/settings.js index 390b2a1..9ec52a7 100644 --- a/app/javascript/mastodon/reducers/settings.js +++ b/app/javascript/mastodon/reducers/settings.js @@ -58,6 +58,12 @@ const initialState = ImmutableMap({ body: '', }), }), + + direct: ImmutableMap({ + regex: ImmutableMap({ + body: '', + }), + }), }); const defaultColumns = fromJS([ diff --git a/app/models/status.rb b/app/models/status.rb index f924be4..62857dd 100644 --- a/app/models/status.rb +++ b/app/models/status.rb @@ -183,6 +183,14 @@ class Status < ApplicationRecord where(account: [account] + account.following).where(visibility: [:public, :unlisted, :private]) end + def as_direct_timeline(account) + query = joins("LEFT OUTER JOIN mentions ON statuses.id = mentions.status_id AND mentions.account_id = #{account.id}") + .where("mentions.account_id = #{account.id} OR statuses.account_id = #{account.id}") + .where(visibility: [:direct]) + + apply_timeline_filters(query, account, false) + end + def as_public_timeline(account = nil, local_only = false) query = timeline_scope(local_only).without_replies diff --git a/app/services/batched_remove_status_service.rb b/app/services/batched_remove_status_service.rb index e2763c2..cb65a22 100644 --- a/app/services/batched_remove_status_service.rb +++ b/app/services/batched_remove_status_service.rb @@ -36,6 +36,7 @@ class BatchedRemoveStatusService < BaseService # Cannot be batched statuses.each do |status| unpush_from_public_timelines(status) + unpush_from_direct_timelines(status) if status.direct_visibility? batch_salmon_slaps(status) if status.local? end @@ -87,6 +88,16 @@ class BatchedRemoveStatusService < BaseService end end + def unpush_from_direct_timelines(status) + payload = @json_payloads[status.id] + redis.pipelined do + @mentions[status.id].each do |mention| + redis.publish("timeline:direct:#{mention.account.id}", payload) if mention.account.local? + end + redis.publish("timeline:direct:#{status.account.id}", payload) if status.account.local? + end + end + def batch_salmon_slaps(status) return if @mentions[status.id].empty? diff --git a/app/services/fan_out_on_write_service.rb b/app/services/fan_out_on_write_service.rb index bbaf309..0f77556 100644 --- a/app/services/fan_out_on_write_service.rb +++ b/app/services/fan_out_on_write_service.rb @@ -10,8 +10,11 @@ class FanOutOnWriteService < BaseService deliver_to_self(status) if status.account.local? + render_anonymous_payload(status) + if status.direct_visibility? deliver_to_mentioned_followers(status) + deliver_to_direct_timelines(status) else deliver_to_followers(status) deliver_to_lists(status) @@ -19,7 +22,6 @@ class FanOutOnWriteService < BaseService return if status.account.silenced? || !status.public_visibility? || status.reblog? - render_anonymous_payload(status) deliver_to_hashtags(status) return if status.reply? && status.in_reply_to_account_id != status.account_id @@ -84,4 +86,13 @@ class FanOutOnWriteService < BaseService Redis.current.publish('timeline:public', @payload) Redis.current.publish('timeline:public:local', @payload) if status.local? end + + def deliver_to_direct_timelines(status) + Rails.logger.debug "Delivering status #{status.id} to direct timelines" + + status.mentions.includes(:account).each do |mention| + Redis.current.publish("timeline:direct:#{mention.account.id}", @payload) if mention.account.local? + end + Redis.current.publish("timeline:direct:#{status.account.id}", @payload) if status.account.local? + end end diff --git a/app/services/remove_status_service.rb b/app/services/remove_status_service.rb index a100f73..e164c03 100644 --- a/app/services/remove_status_service.rb +++ b/app/services/remove_status_service.rb @@ -20,6 +20,7 @@ class RemoveStatusService < BaseService remove_reblogs remove_from_hashtags remove_from_public + remove_from_direct if status.direct_visibility? @status.destroy! @@ -130,6 +131,13 @@ class RemoveStatusService < BaseService Redis.current.publish('timeline:public:local', @payload) if @status.local? end + def remove_from_direct + @mentions.each do |mention| + Redis.current.publish("timeline:direct:#{mention.account.id}", @payload) if mention.account.local? + end + Redis.current.publish("timeline:direct:#{@account.id}", @payload) if @account.local? + end + def redis Redis.current end diff --git a/config/routes.rb b/config/routes.rb index 2776898..d959301 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -229,6 +229,7 @@ Rails.application.routes.draw do end namespace :timelines do + resource :direct, only: :show, controller: :direct resource :home, only: :show, controller: :home resource :public, only: :show, controller: :public resources :tag, only: :show diff --git a/spec/models/status_spec.rb b/spec/models/status_spec.rb index 4b5c208..c670101 100644 --- a/spec/models/status_spec.rb +++ b/spec/models/status_spec.rb @@ -304,6 +304,55 @@ RSpec.describe Status, type: :model do end end + describe '.as_direct_timeline' do + let(:account) { Fabricate(:account) } + let(:followed) { Fabricate(:account) } + let(:not_followed) { Fabricate(:account) } + + before do + Fabricate(:follow, account: account, target_account: followed) + + @self_public_status = Fabricate(:status, account: account, visibility: :public) + @self_direct_status = Fabricate(:status, account: account, visibility: :direct) + @followed_public_status = Fabricate(:status, account: followed, visibility: :public) + @followed_direct_status = Fabricate(:status, account: followed, visibility: :direct) + @not_followed_direct_status = Fabricate(:status, account: not_followed, visibility: :direct) + + @results = Status.as_direct_timeline(account) + end + + it 'does not include public statuses from self' do + expect(@results).to_not include(@self_public_status) + end + + it 'includes direct statuses from self' do + expect(@results).to include(@self_direct_status) + end + + it 'does not include public statuses from followed' do + expect(@results).to_not include(@followed_public_status) + end + + it 'includes direct statuses mentioning recipient from followed' do + Fabricate(:mention, account: account, status: @followed_direct_status) + expect(@results).to include(@followed_direct_status) + end + + it 'does not include direct statuses not mentioning recipient from followed' do + expect(@results).to_not include(@followed_direct_status) + end + + it 'includes direct statuses mentioning recipient from non-followed' do + Fabricate(:mention, account: account, status: @not_followed_direct_status) + expect(@results).to include(@not_followed_direct_status) + end + + it 'does not include direct statuses not mentioning recipient from non-followed' do + expect(@results).to_not include(@not_followed_direct_status) + end + + end + describe '.as_public_timeline' do it 'only includes statuses with public visibility' do public_status = Fabricate(:status, visibility: :public) diff --git a/streaming/index.js b/streaming/index.js index 1b4f859..48bab80 100644 --- a/streaming/index.js +++ b/streaming/index.js @@ -466,6 +466,10 @@ const startWorker = (workerId) => { streamFrom('timeline:public:local', req, streamToHttp(req, res), streamHttpEnd(req), true); }); + app.get('/api/v1/streaming/direct', (req, res) => { + streamFrom(`timeline:direct:${req.accountId}`, req, streamToHttp(req, res), streamHttpEnd(req), true); + }); + app.get('/api/v1/streaming/hashtag', (req, res) => { streamFrom(`timeline:hashtag:${req.query.tag.toLowerCase()}`, req, streamToHttp(req, res), streamHttpEnd(req), true); }); @@ -517,6 +521,9 @@ const startWorker = (workerId) => { case 'public:local': streamFrom('timeline:public:local', req, streamToWs(req, ws), streamWsEnd(req, ws), true); break; + case 'direct': + streamFrom(`timeline:direct:${req.accountId}`, req, streamToWs(req, ws), streamWsEnd(req, ws), true); + break; case 'hashtag': streamFrom(`timeline:hashtag:${location.query.tag.toLowerCase()}`, req, streamToWs(req, ws), streamWsEnd(req, ws), true); break; diff --git a/yarn.lock b/yarn.lock index fba2cb9..c5a49a4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5994,9 +5994,9 @@ rc@^1.1.7: minimist "^1.2.0" strip-json-comments "~2.0.1" -react-dom@^16.2.0: - version "16.2.0" - resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.2.0.tgz#69003178601c0ca19b709b33a83369fe6124c044" +react-dom@^16.3.0: + version "16.3.2" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.3.2.tgz#cb90f107e09536d683d84ed5d4888e9640e0e4df" dependencies: fbjs "^0.8.16" loose-envify "^1.1.0" @@ -6180,9 +6180,9 @@ react-transition-group@^2.2.0: prop-types "^15.5.8" warning "^3.0.0" -react@^16.2.0: - version "16.2.0" - resolved "https://registry.yarnpkg.com/react/-/react-16.2.0.tgz#a31bd2dab89bff65d42134fa187f24d054c273ba" +react@^16.3.0: + version "16.3.2" + resolved "https://registry.yarnpkg.com/react/-/react-16.3.2.tgz#fdc8420398533a1e58872f59091b272ce2f91ea9" dependencies: fbjs "^0.8.16" loose-envify "^1.1.0" From 7162a28c34868eb4b8cdd23a4078d99b46949491 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Miko=C5=82ajczak?= Date: Wed, 18 Apr 2018 13:15:57 +0200 Subject: [PATCH 143/381] Add revealing/hiding statuses button to keyboard shortcuts legend column (#7178) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Marcin Mikołajczak --- app/javascript/mastodon/features/keyboard_shortcuts/index.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/javascript/mastodon/features/keyboard_shortcuts/index.js b/app/javascript/mastodon/features/keyboard_shortcuts/index.js index 8531e03..5ae7b34 100644 --- a/app/javascript/mastodon/features/keyboard_shortcuts/index.js +++ b/app/javascript/mastodon/features/keyboard_shortcuts/index.js @@ -53,6 +53,10 @@ export default class KeyboardShortcuts extends ImmutablePureComponent { + x + + + up From 0ba49eca8b49c6ce0ec04fd546951c95938da4e6 Mon Sep 17 00:00:00 2001 From: abcang Date: Wed, 18 Apr 2018 23:50:19 +0900 Subject: [PATCH 144/381] Fix comparing id (#7180) --- app/javascript/mastodon/compare_id.js | 10 ++++++++++ app/javascript/mastodon/reducers/notifications.js | 12 +++--------- app/javascript/mastodon/reducers/timelines.js | 5 +++-- 3 files changed, 16 insertions(+), 11 deletions(-) create mode 100644 app/javascript/mastodon/compare_id.js diff --git a/app/javascript/mastodon/compare_id.js b/app/javascript/mastodon/compare_id.js new file mode 100644 index 0000000..aaff664 --- /dev/null +++ b/app/javascript/mastodon/compare_id.js @@ -0,0 +1,10 @@ +export default function compareId(id1, id2) { + if (id1 === id2) { + return 0; + } + if (id1.length === id2.length) { + return id1 > id2 ? 1 : -1; + } else { + return id1.length > id2.length ? 1 : -1; + } +} diff --git a/app/javascript/mastodon/reducers/notifications.js b/app/javascript/mastodon/reducers/notifications.js index 1ac7eb7..da9b8c4 100644 --- a/app/javascript/mastodon/reducers/notifications.js +++ b/app/javascript/mastodon/reducers/notifications.js @@ -12,6 +12,7 @@ import { } from '../actions/accounts'; import { TIMELINE_DELETE, TIMELINE_DISCONNECT } from '../actions/timelines'; import { Map as ImmutableMap, List as ImmutableList } from 'immutable'; +import compareId from '../compare_id'; const initialState = ImmutableMap({ items: ImmutableList(), @@ -44,13 +45,6 @@ const normalizeNotification = (state, notification) => { }); }; -const newer = (m, n) => { - const mId = m.get('id'); - const nId = n.get('id'); - - return mId.length === nId.length ? mId > nId : mId.length > nId.length; -}; - const expandNormalizedNotifications = (state, notifications, next) => { let items = ImmutableList(); @@ -62,11 +56,11 @@ const expandNormalizedNotifications = (state, notifications, next) => { if (!items.isEmpty()) { mutable.update('items', list => { const lastIndex = 1 + list.findLastIndex( - item => item !== null && (newer(item, items.last()) || item.get('id') === items.last().get('id')) + item => item !== null && (compareId(item.get('id'), items.last().get('id')) > 0 || item.get('id') === items.last().get('id')) ); const firstIndex = 1 + list.take(lastIndex).findLastIndex( - item => item !== null && newer(item, items.first()) + item => item !== null && compareId(item.get('id'), items.first().get('id')) > 0 ); return list.take(firstIndex).concat(items, list.skip(lastIndex)); diff --git a/app/javascript/mastodon/reducers/timelines.js b/app/javascript/mastodon/reducers/timelines.js index f795e7e..ad897bc 100644 --- a/app/javascript/mastodon/reducers/timelines.js +++ b/app/javascript/mastodon/reducers/timelines.js @@ -13,6 +13,7 @@ import { ACCOUNT_UNFOLLOW_SUCCESS, } from '../actions/accounts'; import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable'; +import compareId from '../compare_id'; const initialState = ImmutableMap(); @@ -32,8 +33,8 @@ const expandNormalizedTimeline = (state, timeline, statuses, next, isPartial) => if (!statuses.isEmpty()) { mMap.update('items', ImmutableList(), oldIds => { const newIds = statuses.map(status => status.get('id')); - const lastIndex = oldIds.findLastIndex(id => id !== null && id >= newIds.last()) + 1; - const firstIndex = oldIds.take(lastIndex).findLastIndex(id => id !== null && id > newIds.first()); + const lastIndex = oldIds.findLastIndex(id => id !== null && compareId(id, newIds.last()) >= 0) + 1; + const firstIndex = oldIds.take(lastIndex).findLastIndex(id => id !== null && compareId(id, newIds.first()) >= 0); if (firstIndex < 0) { return (isPartial ? newIds.unshift(null) : newIds).concat(oldIds.skip(lastIndex)); From 74dae9458d118b066cd74b16aab2aa9cafbf3fba Mon Sep 17 00:00:00 2001 From: Lynx Kotoura Date: Wed, 18 Apr 2018 23:52:15 +0900 Subject: [PATCH 145/381] Add color variables of texts for better accesibility (#7125) * Add variables for text colors * Change variables in sass files * Apply text color variables for recently added colors * Fix text colors of emoji mart anchors * Fix text colors of search__input * Fix text colors of text area of compose-form * Fix icon colors of privacy dropdown and modal * Inverted icon colors by classname * Change variables in boost.scss * Change action-button-color * Fix text colors of pre-header --- .../features/ui/components/actions_modal.js | 2 +- app/javascript/styles/mastodon/about.scss | 64 ++-- app/javascript/styles/mastodon/accounts.scss | 42 +-- app/javascript/styles/mastodon/admin.scss | 42 +-- app/javascript/styles/mastodon/basics.scss | 2 +- app/javascript/styles/mastodon/boost.scss | 6 +- app/javascript/styles/mastodon/compact_header.scss | 4 +- app/javascript/styles/mastodon/components.scss | 353 +++++++++------------ app/javascript/styles/mastodon/containers.scss | 2 +- app/javascript/styles/mastodon/emoji_picker.scss | 16 +- app/javascript/styles/mastodon/footer.scss | 2 +- app/javascript/styles/mastodon/forms.scss | 39 ++- app/javascript/styles/mastodon/landing_strip.scss | 8 +- app/javascript/styles/mastodon/stream_entries.scss | 32 +- app/javascript/styles/mastodon/tables.scss | 4 +- app/javascript/styles/mastodon/variables.scss | 7 +- 16 files changed, 285 insertions(+), 340 deletions(-) diff --git a/app/javascript/mastodon/features/ui/components/actions_modal.js b/app/javascript/mastodon/features/ui/components/actions_modal.js index 79a5a20..9792eba 100644 --- a/app/javascript/mastodon/features/ui/components/actions_modal.js +++ b/app/javascript/mastodon/features/ui/components/actions_modal.js @@ -27,7 +27,7 @@ export default class ActionsModal extends ImmutablePureComponent { return (
  • - {icon && } + {icon && }
    {text}
    {meta}
    diff --git a/app/javascript/styles/mastodon/about.scss b/app/javascript/styles/mastodon/about.scss index 034c35e..0a09a38 100644 --- a/app/javascript/styles/mastodon/about.scss +++ b/app/javascript/styles/mastodon/about.scss @@ -169,7 +169,7 @@ $small-breakpoint: 960px; background: $ui-base-color; font-size: 12px; font-weight: 500; - color: $ui-primary-color; + color: $darker-text-color; text-transform: uppercase; position: relative; z-index: 1; @@ -186,10 +186,10 @@ $small-breakpoint: 960px; font-size: 16px; line-height: 30px; margin-bottom: 12px; - color: $ui-primary-color; + color: $darker-text-color; a { - color: $ui-highlight-color; + color: $highlight-text-color; text-decoration: underline; } } @@ -202,11 +202,11 @@ $small-breakpoint: 960px; text-align: center; font-size: 12px; line-height: 18px; - color: $ui-primary-color; + color: $darker-text-color; margin-bottom: 0; a { - color: $ui-highlight-color; + color: $highlight-text-color; text-decoration: underline; } } @@ -225,7 +225,7 @@ $small-breakpoint: 960px; font-family: inherit; font-size: inherit; line-height: inherit; - color: lighten($ui-primary-color, 10%); + color: transparentize($darker-text-color, 0.1); } h1 { @@ -234,14 +234,14 @@ $small-breakpoint: 960px; line-height: 30px; font-weight: 500; margin-bottom: 20px; - color: $ui-secondary-color; + color: $primary-text-color; small { font-family: 'mastodon-font-sans-serif', sans-serif; display: block; font-size: 18px; font-weight: 400; - color: $ui-base-lighter-color; + color: opacify($darker-text-color, 0.1); } } @@ -251,7 +251,7 @@ $small-breakpoint: 960px; line-height: 26px; font-weight: 500; margin-bottom: 20px; - color: $ui-secondary-color; + color: $primary-text-color; } h3 { @@ -260,7 +260,7 @@ $small-breakpoint: 960px; line-height: 24px; font-weight: 500; margin-bottom: 20px; - color: $ui-secondary-color; + color: $primary-text-color; } h4 { @@ -269,7 +269,7 @@ $small-breakpoint: 960px; line-height: 24px; font-weight: 500; margin-bottom: 20px; - color: $ui-secondary-color; + color: $primary-text-color; } h5 { @@ -278,7 +278,7 @@ $small-breakpoint: 960px; line-height: 24px; font-weight: 500; margin-bottom: 20px; - color: $ui-secondary-color; + color: $primary-text-color; } h6 { @@ -287,7 +287,7 @@ $small-breakpoint: 960px; line-height: 24px; font-weight: 500; margin-bottom: 20px; - color: $ui-secondary-color; + color: $primary-text-color; } ul, @@ -354,10 +354,10 @@ $small-breakpoint: 960px; font-weight: 400; font-size: 16px; line-height: 30px; - color: $ui-primary-color; + color: $darker-text-color; a { - color: $ui-highlight-color; + color: $highlight-text-color; text-decoration: underline; } } @@ -405,7 +405,7 @@ $small-breakpoint: 960px; font-size: 14px; &:hover { - color: $ui-secondary-color; + color: $darker-text-color; } } @@ -478,10 +478,10 @@ $small-breakpoint: 960px; font-weight: 400; font-size: 16px; line-height: 30px; - color: $ui-primary-color; + color: $darker-text-color; a { - color: $ui-highlight-color; + color: $highlight-text-color; text-decoration: underline; } } @@ -517,7 +517,7 @@ $small-breakpoint: 960px; span { &:last-child { - color: $ui-secondary-color; + color: $darker-text-color; } } @@ -548,7 +548,7 @@ $small-breakpoint: 960px; font-size: 14px; line-height: 24px; font-weight: 500; - color: $ui-primary-color; + color: $darker-text-color; padding-bottom: 5px; margin-bottom: 15px; border-bottom: 1px solid lighten($ui-base-color, 4%); @@ -559,7 +559,7 @@ $small-breakpoint: 960px; a, span { font-weight: 400; - color: darken($ui-primary-color, 10%); + color: opacify($darker-text-color, 0.1); } a { @@ -602,7 +602,7 @@ $small-breakpoint: 960px; .username { display: block; - color: $ui-primary-color; + color: $darker-text-color; } } } @@ -775,7 +775,7 @@ $small-breakpoint: 960px; } p a { - color: $ui-secondary-color; + color: $darker-text-color; } h1 { @@ -784,10 +784,10 @@ $small-breakpoint: 960px; margin-bottom: 0; small { - color: $ui-primary-color; + color: $darker-text-color; span { - color: $ui-secondary-color; + color: $darker-text-color; } } } @@ -896,7 +896,7 @@ $small-breakpoint: 960px; } a { - color: $ui-secondary-color; + color: $darker-text-color; text-decoration: none; } } @@ -935,7 +935,7 @@ $small-breakpoint: 960px; .fa { display: block; - color: $ui-primary-color; + color: $darker-text-color; font-size: 48px; } } @@ -943,7 +943,7 @@ $small-breakpoint: 960px; .text { font-size: 16px; line-height: 30px; - color: $ui-primary-color; + color: $darker-text-color; h6 { font-size: inherit; @@ -969,10 +969,10 @@ $small-breakpoint: 960px; font-weight: 400; font-size: 16px; line-height: 30px; - color: $ui-primary-color; + color: $darker-text-color; a { - color: $ui-highlight-color; + color: $highlight-text-color; text-decoration: underline; } } @@ -980,7 +980,7 @@ $small-breakpoint: 960px; .footer-links { padding-bottom: 50px; text-align: right; - color: $ui-base-lighter-color; + color: $darker-text-color; p { font-size: 14px; @@ -995,7 +995,7 @@ $small-breakpoint: 960px; &__footer { margin-top: 10px; text-align: center; - color: $ui-base-lighter-color; + color: $darker-text-color; p { font-size: 14px; diff --git a/app/javascript/styles/mastodon/accounts.scss b/app/javascript/styles/mastodon/accounts.scss index 0b49da1..f9af6f2 100644 --- a/app/javascript/styles/mastodon/accounts.scss +++ b/app/javascript/styles/mastodon/accounts.scss @@ -75,7 +75,7 @@ small { display: block; font-size: 14px; - color: $ui-highlight-color; + color: $highlight-text-color; font-weight: 400; overflow: hidden; text-overflow: ellipsis; @@ -113,7 +113,7 @@ width: 33.3%; box-sizing: border-box; flex: 0 0 auto; - color: $ui-primary-color; + color: $darker-text-color; padding: 5px 10px 0; margin-bottom: 10px; border-right: 1px solid lighten($ui-base-color, 4%); @@ -143,7 +143,7 @@ &.active { &::after { - border-bottom: 4px solid $ui-highlight-color; + border-bottom: 4px solid $highlight-text-color; opacity: 1; } } @@ -178,7 +178,7 @@ font-size: 14px; line-height: 18px; padding: 0 15px; - color: $ui-secondary-color; + color: $darker-text-color; } @media screen and (max-width: 480px) { @@ -256,7 +256,7 @@ .current { background: $simple-background-color; border-radius: 100px; - color: $ui-base-color; + color: $lighter-text-color; cursor: default; margin: 0 10px; } @@ -268,7 +268,7 @@ .older, .newer { text-transform: uppercase; - color: $ui-secondary-color; + color: $primary-text-color; } .older { @@ -293,7 +293,7 @@ .disabled { cursor: default; - color: lighten($ui-base-color, 10%); + color: opacify($lighter-text-color, 0.1); } @media screen and (max-width: 700px) { @@ -332,7 +332,7 @@ width: 335px; background: $simple-background-color; border-radius: 4px; - color: $ui-base-color; + color: $lighter-text-color; margin: 0 5px 10px; position: relative; @@ -344,7 +344,7 @@ overflow: hidden; height: 100px; border-radius: 4px 4px 0 0; - background-color: lighten($ui-base-color, 4%); + background-color: opacify($lighter-text-color, 0.04); background-size: cover; background-position: center; position: relative; @@ -392,7 +392,7 @@ a { display: block; - color: $ui-base-color; + color: $inverted-text-color; text-decoration: none; text-overflow: ellipsis; overflow: hidden; @@ -414,7 +414,7 @@ } .username { - color: lighten($ui-base-color, 34%); + color: $lighter-text-color; font-size: 14px; font-weight: 400; } @@ -422,7 +422,7 @@ .account__header__content { padding: 10px 15px; padding-top: 15px; - color: lighten($ui-base-color, 26%); + color: transparentize($lighter-text-color, 0.1); word-wrap: break-word; overflow: hidden; text-overflow: ellipsis; @@ -434,7 +434,7 @@ .nothing-here { width: 100%; display: block; - color: $ui-primary-color; + color: $lighter-text-color; font-size: 14px; font-weight: 500; text-align: center; @@ -493,7 +493,7 @@ span { font-size: 14px; - color: $ui-primary-color; + color: $inverted-text-color; } } @@ -508,7 +508,7 @@ .account__header__content { font-size: 14px; - color: $ui-base-color; + color: $darker-text-color; } } @@ -522,18 +522,18 @@ display: inline-block; padding: 15px; text-decoration: none; - color: $ui-highlight-color; + color: $highlight-text-color; text-transform: uppercase; font-weight: 500; &:hover, &:active, &:focus { - color: lighten($ui-highlight-color, 8%); + color: lighten($highlight-text-color, 8%); } &.active { - color: $ui-base-color; + color: $inverted-text-color; cursor: default; } } @@ -586,19 +586,19 @@ font-weight: 500; text-align: center; width: 94px; - color: $ui-secondary-color; + color: opacify($darker-text-color, 0.1); background: rgba(darken($ui-base-color, 8%), 0.5); } td { - color: $ui-primary-color; + color: $darker-text-color; text-align: center; width: 100%; padding-left: 0; } a { - color: $ui-highlight-color; + color: $highlight-text-color; text-decoration: none; &:hover, diff --git a/app/javascript/styles/mastodon/admin.scss b/app/javascript/styles/mastodon/admin.scss index 6bd6590..348f720 100644 --- a/app/javascript/styles/mastodon/admin.scss +++ b/app/javascript/styles/mastodon/admin.scss @@ -33,7 +33,7 @@ a { display: block; padding: 15px; - color: rgba($primary-text-color, 0.7); + color: $darker-text-color; text-decoration: none; transition: all 200ms linear; border-radius: 4px 0 0 4px; @@ -90,7 +90,7 @@ padding-left: 25px; h2 { - color: $ui-secondary-color; + color: $primary-text-color; font-size: 24px; line-height: 28px; font-weight: 400; @@ -98,7 +98,7 @@ } h3 { - color: $ui-secondary-color; + color: $primary-text-color; font-size: 20px; line-height: 28px; font-weight: 400; @@ -109,7 +109,7 @@ text-transform: uppercase; font-size: 13px; font-weight: 500; - color: $ui-primary-color; + color: $primary-text-color; padding-bottom: 8px; margin-bottom: 8px; border-bottom: 1px solid lighten($ui-base-color, 8%); @@ -117,7 +117,7 @@ h6 { font-size: 16px; - color: $ui-secondary-color; + color: $primary-text-color; line-height: 28px; font-weight: 400; } @@ -125,7 +125,7 @@ & > p { font-size: 14px; line-height: 18px; - color: $ui-secondary-color; + color: $darker-text-color; margin-bottom: 20px; strong { @@ -153,10 +153,10 @@ } .muted-hint { - color: $ui-primary-color; + color: $darker-text-color; a { - color: $ui-highlight-color; + color: $highlight-text-color; } } @@ -253,7 +253,7 @@ a { display: inline-block; - color: rgba($primary-text-color, 0.7); + color: $darker-text-color; text-decoration: none; text-transform: uppercase; font-size: 12px; @@ -266,7 +266,7 @@ } &.selected { - color: $ui-highlight-color; + color: $highlight-text-color; border-bottom: 2px solid $ui-highlight-color; } } @@ -291,7 +291,7 @@ font-weight: 500; font-size: 14px; line-height: 18px; - color: $ui-secondary-color; + color: $primary-text-color; @each $lang in $cjk-langs { &:lang(#{$lang}) { @@ -348,7 +348,7 @@ padding: 7px 4px; margin-bottom: 10px; font-size: 16px; - color: $ui-base-color; + color: $inverted-text-color; display: block; width: 100%; outline: 0; @@ -402,7 +402,7 @@ font-size: 14px; a { - color: $classic-highlight-color; + color: $highlight-text-color; text-decoration: none; &:hover { @@ -425,7 +425,7 @@ align-items: center; padding: 10px; background: $ui-base-color; - color: $ui-primary-color; + color: $darker-text-color; border-radius: 4px 4px 0 0; font-size: 14px; position: relative; @@ -452,14 +452,14 @@ } &__timestamp { - color: lighten($ui-base-color, 34%); + color: $darker-text-color; } &__extras { background: lighten($ui-base-color, 6%); border-radius: 0 0 4px 4px; padding: 10px; - color: $ui-primary-color; + color: $darker-text-color; font-family: 'mastodon-font-monospace', monospace; font-size: 12px; word-wrap: break-word; @@ -469,7 +469,7 @@ &__icon { font-size: 28px; margin-right: 10px; - color: lighten($ui-base-color, 34%); + color: $darker-text-color; } &__icon__overlay { @@ -485,7 +485,7 @@ } &.negative { - background: $error-red; + background: lighten($error-red, 12%); } &.neutral { @@ -496,17 +496,17 @@ a, .username, .target { - color: $ui-secondary-color; + color: $primary-text-color; text-decoration: none; font-weight: 500; } .diff-old { - color: $error-red; + color: lighten($error-red, 12%); } .diff-neutral { - color: $ui-secondary-color; + color: $darker-text-color; } .diff-new { diff --git a/app/javascript/styles/mastodon/basics.scss b/app/javascript/styles/mastodon/basics.scss index bec0d4d..c52e069 100644 --- a/app/javascript/styles/mastodon/basics.scss +++ b/app/javascript/styles/mastodon/basics.scss @@ -75,7 +75,7 @@ body { &.error { position: absolute; text-align: center; - color: $ui-primary-color; + color: $darker-text-color; background: $ui-base-color; width: 100%; height: 100%; diff --git a/app/javascript/styles/mastodon/boost.scss b/app/javascript/styles/mastodon/boost.scss index 31053de..8e11cb5 100644 --- a/app/javascript/styles/mastodon/boost.scss +++ b/app/javascript/styles/mastodon/boost.scss @@ -6,13 +6,13 @@ } button.icon-button i.fa-retweet { - background-image: url("data:image/svg+xml;utf8,"); + background-image: url("data:image/svg+xml;utf8,"); &:hover { - background-image: url("data:image/svg+xml;utf8,"); + background-image: url("data:image/svg+xml;utf8,"); } } button.icon-button.disabled i.fa-retweet { - background-image: url("data:image/svg+xml;utf8,"); + background-image: url("data:image/svg+xml;utf8,"); } diff --git a/app/javascript/styles/mastodon/compact_header.scss b/app/javascript/styles/mastodon/compact_header.scss index 90d98cc..83ac7a8 100644 --- a/app/javascript/styles/mastodon/compact_header.scss +++ b/app/javascript/styles/mastodon/compact_header.scss @@ -2,7 +2,7 @@ h1 { font-size: 24px; line-height: 28px; - color: $ui-primary-color; + color: $primary-text-color; font-weight: 500; margin-bottom: 20px; padding: 0 10px; @@ -20,7 +20,7 @@ small { font-weight: 400; - color: $ui-secondary-color; + color: $darker-text-color; } img { diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index 96112d8..cdf3a9d 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -4,7 +4,7 @@ } .button { - background-color: darken($ui-highlight-color, 3%); + background-color: $ui-highlight-color; border: 10px none; border-radius: 4px; box-sizing: border-box; @@ -31,7 +31,7 @@ &:active, &:focus, &:hover { - background-color: lighten($ui-highlight-color, 7%); + background-color: lighten($ui-highlight-color, 4%); transition: all 200ms ease-out; } @@ -52,7 +52,7 @@ } &.button-alternative { - color: $ui-base-color; + color: $inverted-text-color; background: $ui-primary-color; &:active, @@ -98,26 +98,10 @@ position: relative; } -.column-icon { - background: lighten($ui-base-color, 4%); - color: $ui-primary-color; - cursor: pointer; - font-size: 16px; - padding: 15px; - position: absolute; - right: 0; - top: -48px; - z-index: 3; - - &:hover { - color: lighten($ui-primary-color, 7%); - } -} - .icon-button { display: inline-block; padding: 0; - color: $ui-base-lighter-color; + color: $action-button-color; border: none; background: transparent; cursor: pointer; @@ -126,17 +110,17 @@ &:hover, &:active, &:focus { - color: lighten($ui-base-color, 33%); + color: lighten($action-button-color, 7%); transition: color 200ms ease-out; } &.disabled { - color: lighten($ui-base-color, 13%); + color: darken($action-button-color, 13%); cursor: default; } &.active { - color: $ui-highlight-color; + color: $highlight-text-color; } &::-moz-focus-inner { @@ -150,23 +134,23 @@ } &.inverted { - color: lighten($ui-base-color, 33%); + color: $lighter-text-color; &:hover, &:active, &:focus { - color: $ui-base-lighter-color; + color: transparentize($lighter-text-color, 0.07); } &.disabled { - color: $ui-primary-color; + color: opacify($lighter-text-color, 0.07); } &.active { - color: $ui-highlight-color; + color: $highlight-text-color; &.disabled { - color: lighten($ui-highlight-color, 13%); + color: opacify($lighter-text-color, 0.13); } } } @@ -185,7 +169,7 @@ } .text-icon-button { - color: lighten($ui-base-color, 33%); + color: $lighter-text-color; border: none; background: transparent; cursor: pointer; @@ -199,17 +183,17 @@ &:hover, &:active, &:focus { - color: $ui-base-lighter-color; + color: opacify($lighter-text-color, 0.07); transition: color 200ms ease-out; } &.disabled { - color: lighten($ui-base-color, 13%); + color: transparentize($lighter-text-color, 0.2); cursor: default; } &.active { - color: $ui-highlight-color; + color: $highlight-text-color; } &::-moz-focus-inner { @@ -228,25 +212,6 @@ transform-origin: 50% 0; } -.dropdown--active .icon-button { - color: $ui-highlight-color; -} - -.dropdown--active::after { - @media screen and (min-width: 631px) { - content: ""; - display: block; - position: absolute; - width: 0; - height: 0; - border-style: solid; - border-width: 0 4.5px 7.8px; - border-color: transparent transparent $ui-secondary-color; - bottom: 8px; - right: 104px; - } -} - .invisible { font-size: 0; line-height: 0; @@ -271,15 +236,11 @@ } } -.lightbox .icon-button { - color: $ui-base-color; -} - .compose-form { padding: 10px; .compose-form__warning { - color: darken($ui-secondary-color, 65%); + color: $inverted-text-color; margin-bottom: 15px; background: $ui-primary-color; box-shadow: 0 2px 6px rgba($base-shadow-color, 0.3); @@ -289,7 +250,7 @@ font-weight: 400; strong { - color: darken($ui-secondary-color, 65%); + color: $inverted-text-color; font-weight: 500; @each $lang in $cjk-langs { @@ -300,7 +261,7 @@ } a { - color: darken($ui-primary-color, 33%); + color: $lighter-text-color; font-weight: 500; text-decoration: underline; @@ -333,7 +294,7 @@ box-sizing: border-box; width: 100%; margin: 0; - color: $ui-base-color; + color: $inverted-text-color; background: $simple-background-color; padding: 10px; font-family: inherit; @@ -378,7 +339,7 @@ box-shadow: 4px 4px 6px rgba($base-shadow-color, 0.4); background: $ui-secondary-color; border-radius: 0 0 4px 4px; - color: $ui-base-color; + color: $lighter-text-color; font-size: 14px; padding: 6px; @@ -419,11 +380,11 @@ } .autosuggest-account .display-name__account { - color: lighten($ui-base-color, 36%); + color: $lighter-text-color; } .compose-form__modifiers { - color: $ui-base-color; + color: $inverted-text-color; font-family: inherit; font-size: 14px; background: $simple-background-color; @@ -454,7 +415,7 @@ .icon-button { flex: 0 1 auto; - color: $ui-secondary-color; + color: $action-button-color; font-size: 14px; font-weight: 500; padding: 10px; @@ -463,7 +424,7 @@ &:hover, &:focus, &:active { - color: lighten($ui-secondary-color, 4%); + color: lighten($action-button-color, 7%); } } @@ -486,7 +447,7 @@ input { background: transparent; - color: $ui-secondary-color; + color: $primary-text-color; border: 0; padding: 0; margin: 0; @@ -501,7 +462,7 @@ &::placeholder { opacity: 0.54; - color: $ui-secondary-color; + color: $darker-text-color; } } @@ -563,7 +524,7 @@ font-family: 'mastodon-font-sans-serif', sans-serif; font-size: 14px; font-weight: 600; - color: lighten($ui-base-color, 12%); + color: $lighter-text-color; &.character-counter--over { color: $warning-red; @@ -617,7 +578,7 @@ } .reply-indicator__display-name { - color: $ui-base-color; + color: $lighter-text-color; display: block; max-width: 100%; line-height: 24px; @@ -679,7 +640,7 @@ text-decoration: underline; .fa { - color: lighten($ui-base-color, 40%); + color: lighten($action-button-color, 7%); } } @@ -694,15 +655,15 @@ } .fa { - color: lighten($ui-base-color, 30%); + color: $action-button-color; } } .status__content__spoiler-link { - background: lighten($ui-base-color, 30%); + background: $action-button-color; &:hover { - background: lighten($ui-base-color, 33%); + background: lighten($action-button-color, 7%); text-decoration: none; } } @@ -721,7 +682,7 @@ border-radius: 2px; background: transparent; border: 0; - color: lighten($ui-base-color, 8%); + color: $lighter-text-color; font-weight: 700; font-size: 11px; padding: 0 6px; @@ -784,36 +745,32 @@ &.status-direct { background: lighten($ui-base-color, 8%); - - .icon-button.disabled { - color: lighten($ui-base-color, 16%); - } } &.light { .status__relative-time { - color: $ui-primary-color; + color: $lighter-text-color; } .status__display-name { - color: $ui-base-color; + color: $inverted-text-color; } .display-name { strong { - color: $ui-base-color; + color: $inverted-text-color; } span { - color: $ui-primary-color; + color: $lighter-text-color; } } .status__content { - color: $ui-base-color; + color: $inverted-text-color; a { - color: $ui-highlight-color; + color: $highlight-text-color; } a.status__content__spoiler-link { @@ -833,19 +790,19 @@ background: transparent; .icon-button.disabled { - color: lighten($ui-base-color, 13%); + color: lighten($action-button-color, 13%); } } } .status__relative-time { - color: $ui-base-lighter-color; + color: $darker-text-color; float: right; font-size: 14px; } .status__display-name { - color: $ui-base-lighter-color; + color: $darker-text-color; } .status__info .status__display-name { @@ -896,14 +853,14 @@ .status__prepend { margin-left: 68px; - color: $ui-base-lighter-color; + color: $darker-text-color; padding: 8px 0; padding-bottom: 2px; font-size: 14px; position: relative; .status__display-name strong { - color: $ui-base-lighter-color; + color: $darker-text-color; } > span { @@ -965,7 +922,7 @@ .detailed-status__meta { margin-top: 15px; - color: $ui-base-lighter-color; + color: $darker-text-color; font-size: 14px; line-height: 18px; } @@ -993,11 +950,11 @@ } .reply-indicator__content { - color: $ui-base-color; + color: $inverted-text-color; font-size: 14px; a { - color: lighten($ui-base-color, 20%); + color: $lighter-text-color; } } @@ -1032,7 +989,7 @@ .account__display-name { flex: 1 1 auto; display: block; - color: $ui-primary-color; + color: $darker-text-color; overflow: hidden; text-decoration: none; font-size: 14px; @@ -1102,7 +1059,7 @@ } .account__header__username { - color: $ui-primary-color; + color: $darker-text-color; } } @@ -1112,7 +1069,7 @@ } .account__header__content { - color: $ui-secondary-color; + color: $darker-text-color; } .account__header__display-name { @@ -1127,7 +1084,7 @@ } .account__header__username { - color: $ui-highlight-color; + color: $highlight-text-color; font-size: 14px; font-weight: 400; display: block; @@ -1140,7 +1097,7 @@ .account__disclaimer { padding: 10px; border-top: 1px solid lighten($ui-base-color, 8%); - color: $ui-base-lighter-color; + color: $darker-text-color; strong { font-weight: 500; @@ -1166,7 +1123,7 @@ } .account__header__content { - color: $ui-primary-color; + color: $darker-text-color; font-size: 14px; font-weight: 400; overflow: hidden; @@ -1243,7 +1200,7 @@ display: block; text-transform: uppercase; font-size: 11px; - color: $ui-primary-color; + color: $darker-text-color; } strong { @@ -1258,10 +1215,6 @@ } } } - - abbr { - color: $ui-base-lighter-color; - } } .account__header__avatar { @@ -1331,7 +1284,7 @@ } .detailed-status__display-name { - color: $ui-secondary-color; + color: $darker-text-color; display: block; line-height: 24px; margin-bottom: 15px; @@ -1366,11 +1319,11 @@ .muted { .status__content p, .status__content a { - color: $ui-base-lighter-color; + color: $darker-text-color; } .status__display-name strong { - color: $ui-base-lighter-color; + color: $darker-text-color; } .status__avatar { @@ -1378,11 +1331,11 @@ } a.status__content__spoiler-link { - background: $ui-base-lighter-color; + background: $darker-text-color; color: lighten($ui-base-color, 4%); &:hover { - background: lighten($ui-base-color, 29%); + background: transparentize($darker-text-color, 0.07); text-decoration: none; } } @@ -1398,7 +1351,7 @@ position: relative; .fa { - color: $ui-highlight-color; + color: $highlight-text-color; } > span { @@ -1501,7 +1454,7 @@ display: flex; flex-shrink: 0; cursor: default; - color: $ui-primary-color; + color: $darker-text-color; strong { color: $primary-text-color; @@ -1609,7 +1562,7 @@ box-sizing: border-box; text-decoration: none; background: $ui-secondary-color; - color: $ui-base-color; + color: $inverted-text-color; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; @@ -1618,7 +1571,7 @@ &:hover, &:active { background: $ui-highlight-color; - color: $ui-secondary-color; + color: $primary-text-color; outline: 0; } } @@ -1660,7 +1613,7 @@ box-sizing: border-box; text-decoration: none; background: $ui-secondary-color; - color: $ui-base-color; + color: $inverted-text-color; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; @@ -1671,7 +1624,7 @@ &:hover { background: $ui-highlight-color; - color: $ui-secondary-color; + color: $primary-text-color; } } } @@ -1683,7 +1636,7 @@ .static-content { padding: 10px; padding-top: 20px; - color: $ui-base-lighter-color; + color: $darker-text-color; h1 { font-size: 16px; @@ -1935,8 +1888,8 @@ } &.active { - border-bottom: 2px solid $ui-highlight-color; - color: $ui-highlight-color; + border-bottom: 2px solid $highlight-text-color; + color: $highlight-text-color; } &:hover, @@ -1991,7 +1944,7 @@ .column-back-button { background: lighten($ui-base-color, 4%); - color: $ui-highlight-color; + color: $highlight-text-color; cursor: pointer; flex: 0 0 auto; font-size: 16px; @@ -2010,7 +1963,7 @@ background: lighten($ui-base-color, 4%); border: 0; font-family: inherit; - color: $ui-highlight-color; + color: $highlight-text-color; cursor: pointer; white-space: nowrap; font-size: 16px; @@ -2182,7 +2135,7 @@ .column-subheading { background: $ui-base-color; - color: $ui-base-lighter-color; + color: $darker-text-color; padding: 8px 20px; font-size: 12px; font-weight: 500; @@ -2205,11 +2158,11 @@ flex: 1 0 auto; p { - color: $ui-secondary-color; + color: $darker-text-color; } a { - color: $ui-base-lighter-color; + color: opacify($darker-text-color, 0.07); } } @@ -2235,7 +2188,7 @@ } .setting-text { - color: $ui-primary-color; + color: $darker-text-color; background: transparent; border: none; border-bottom: 2px solid $ui-primary-color; @@ -2249,23 +2202,12 @@ &:focus, &:active { color: $primary-text-color; - border-bottom-color: $ui-highlight-color; + border-bottom-color: $highlight-text-color; } @media screen and (max-width: 600px) { font-size: 16px; } - - &.light { - color: $ui-base-color; - border-bottom: 2px solid lighten($ui-base-color, 27%); - - &:focus, - &:active { - color: $ui-base-color; - border-bottom-color: $ui-highlight-color; - } - } } .no-reduce-motion button.icon-button i.fa-retweet { @@ -2288,12 +2230,12 @@ } .reduce-motion button.icon-button i.fa-retweet { - color: $ui-base-lighter-color; + color: $action-button-color; transition: color 100ms ease-in; } .reduce-motion button.icon-button.active i.fa-retweet { - color: $ui-highlight-color; + color: $highlight-text-color; } .status-card { @@ -2301,7 +2243,7 @@ font-size: 14px; border: 1px solid lighten($ui-base-color, 8%); border-radius: 4px; - color: $ui-base-lighter-color; + color: $darker-text-color; margin-top: 14px; text-decoration: none; overflow: hidden; @@ -2439,7 +2381,7 @@ a.status-card { .load-more { display: block; - color: $ui-base-lighter-color; + color: $darker-text-color; background-color: transparent; border: 0; font-size: inherit; @@ -2463,7 +2405,7 @@ a.status-card { text-align: center; font-size: 16px; font-weight: 500; - color: lighten($ui-base-color, 16%); + color: opacify($darker-text-color, 0.07); background: $ui-base-color; cursor: default; display: flex; @@ -2503,7 +2445,7 @@ a.status-card { strong { display: block; margin-bottom: 10px; - color: lighten($ui-base-color, 34%); + color: $darker-text-color; } span { @@ -2561,15 +2503,15 @@ a.status-card { } & > .column-header__back-button { - color: $ui-highlight-color; + color: $highlight-text-color; } &.active { - box-shadow: 0 1px 0 rgba($ui-highlight-color, 0.3); + box-shadow: 0 1px 0 rgba($highlight-text-color, 0.3); .column-header__icon { - color: $ui-highlight-color; - text-shadow: 0 0 10px rgba($ui-highlight-color, 0.4); + color: $highlight-text-color; + text-shadow: 0 0 10px rgba($highlight-text-color, 0.4); } } @@ -2615,7 +2557,7 @@ a.status-card { max-height: 70vh; overflow: hidden; overflow-y: auto; - color: $ui-primary-color; + color: $darker-text-color; transition: max-height 150ms ease-in-out, opacity 300ms linear; opacity: 1; @@ -2644,7 +2586,7 @@ a.status-card { .column-header__setting-btn { &:hover { - color: lighten($ui-primary-color, 4%); + color: $darker-text-color; text-decoration: underline; } } @@ -2678,7 +2620,7 @@ a.status-card { } .loading-indicator { - color: lighten($ui-base-color, 26%); + color: $darker-text-color; font-size: 12px; font-weight: 400; text-transform: uppercase; @@ -2763,7 +2705,7 @@ a.status-card { .media-spoiler { background: $base-overlay-background; - color: $ui-primary-color; + color: $darker-text-color; border: 0; padding: 0; width: 100%; @@ -2775,7 +2717,7 @@ a.status-card { &:active, &:focus { padding: 0; - color: lighten($ui-primary-color, 8%); + color: transparentize($darker-text-color, 0.07); } } @@ -2828,7 +2770,7 @@ a.status-card { } .column-settings__section { - color: $ui-primary-color; + color: $darker-text-color; cursor: default; display: block; font-weight: 500; @@ -2886,7 +2828,7 @@ a.status-card { .setting-toggle__label, .setting-meta__label { - color: $ui-primary-color; + color: $darker-text-color; display: inline-block; margin-bottom: 14px; margin-left: 8px; @@ -2894,13 +2836,12 @@ a.status-card { } .setting-meta__label { - color: $ui-primary-color; float: right; } .empty-column-indicator, .error-column { - color: lighten($ui-base-color, 20%); + color: $darker-text-color; background: $ui-base-color; text-align: center; padding: 20px; @@ -2917,7 +2858,7 @@ a.status-card { } a { - color: $ui-highlight-color; + color: $highlight-text-color; text-decoration: none; &:hover { @@ -3102,7 +3043,7 @@ a.status-card { display: flex; align-items: center; justify-content: center; - color: $ui-secondary-color; + color: $primary-text-color; font-size: 18px; font-weight: 500; border: 2px dashed $ui-base-lighter-color; @@ -3111,7 +3052,7 @@ a.status-card { .upload-progress { padding: 10px; - color: $ui-base-lighter-color; + color: $lighter-text-color; overflow: hidden; display: flex; @@ -3200,7 +3141,7 @@ a.status-card { } .privacy-dropdown__option { - color: $ui-base-color; + color: $lighter-text-color; padding: 10px; cursor: pointer; display: flex; @@ -3233,12 +3174,12 @@ a.status-card { .privacy-dropdown__option__content { flex: 1 1 auto; - color: darken($ui-primary-color, 24%); + color: $lighter-text-color; strong { font-weight: 500; display: block; - color: $ui-base-color; + color: $inverted-text-color; @each $lang in $cjk-langs { &:lang(#{$lang}) { @@ -3287,7 +3228,7 @@ a.status-card { padding-right: 30px; font-family: inherit; background: $ui-base-color; - color: $ui-primary-color; + color: $darker-text-color; font-size: 14px; margin: 0; @@ -3344,6 +3285,7 @@ a.status-card { .fa-times-circle { top: 11px; transform: rotate(0deg); + color: $action-button-color; cursor: pointer; &.active { @@ -3351,13 +3293,13 @@ a.status-card { } &:hover { - color: $primary-text-color; + color: lighten($action-button-color, 7%); } } } .search-results__header { - color: $ui-base-lighter-color; + color: $darker-text-color; background: lighten($ui-base-color, 2%); border-bottom: 1px solid darken($ui-base-color, 4%); padding: 15px 10px; @@ -3386,7 +3328,7 @@ a.status-card { span { display: inline-block; background: $ui-base-color; - color: $ui-primary-color; + color: $darker-text-color; font-size: 14px; font-weight: 500; padding: 10px; @@ -3405,13 +3347,13 @@ a.status-card { .search-results__hashtag { display: block; padding: 10px; - color: $ui-secondary-color; + color: darken($primary-text-color, 4%); text-decoration: none; &:hover, &:active, &:focus { - color: lighten($ui-secondary-color, 4%); + color: $primary-text-color; text-decoration: underline; } } @@ -3549,7 +3491,7 @@ a.status-card { } .media-modal__button { - background-color: $white; + background-color: $primary-text-color; height: 12px; width: 12px; border-radius: 6px; @@ -3560,7 +3502,7 @@ a.status-card { } .media-modal__button--active { - background-color: $ui-highlight-color; + background-color: $highlight-text-color; } .media-modal__close { @@ -3574,7 +3516,7 @@ a.status-card { .error-modal, .embed-modal { background: $ui-secondary-color; - color: $ui-base-color; + color: $inverted-text-color; border-radius: 8px; overflow: hidden; display: flex; @@ -3662,7 +3604,7 @@ a.status-card { .onboarding-modal__nav, .error-modal__nav { - color: darken($ui-secondary-color, 34%); + color: $lighter-text-color; border: 0; font-size: 14px; font-weight: 500; @@ -3676,18 +3618,18 @@ a.status-card { &:hover, &:focus, &:active { - color: darken($ui-secondary-color, 38%); + color: transparentize($lighter-text-color, 0.04); background-color: darken($ui-secondary-color, 16%); } &.onboarding-modal__done, &.onboarding-modal__next { - color: $ui-base-color; + color: $inverted-text-color; &:hover, &:focus, &:active { - color: darken($ui-base-color, 4%); + color: lighten($inverted-text-color, 4%); } } } @@ -3739,17 +3681,17 @@ a.status-card { h1 { font-size: 18px; font-weight: 500; - color: $ui-base-color; + color: $inverted-text-color; margin-bottom: 20px; } a { - color: $ui-highlight-color; + color: $highlight-text-color; &:hover, &:focus, &:active { - color: lighten($ui-highlight-color, 4%); + color: lighten($highlight-text-color, 4%); } } @@ -3759,7 +3701,7 @@ a.status-card { p { font-size: 16px; - color: lighten($ui-base-color, 8%); + color: $lighter-text-color; margin-top: 10px; margin-bottom: 10px; @@ -3770,7 +3712,7 @@ a.status-card { strong { font-weight: 500; background: $ui-base-color; - color: $ui-secondary-color; + color: $primary-text-color; border-radius: 4px; font-size: 14px; padding: 3px 6px; @@ -3822,7 +3764,7 @@ a.status-card { &__label { font-weight: 500; - color: $ui-base-color; + color: $inverted-text-color; margin-bottom: 5px; text-transform: uppercase; font-size: 12px; @@ -3830,7 +3772,7 @@ a.status-card { &__case { background: $ui-base-color; - color: $ui-secondary-color; + color: $primary-text-color; font-weight: 500; padding: 10px; border-radius: 4px; @@ -3847,7 +3789,7 @@ a.status-card { .figure { background: darken($ui-base-color, 8%); - color: $ui-secondary-color; + color: $darker-text-color; margin-bottom: 20px; border-radius: 4px; padding: 10px; @@ -3936,7 +3878,7 @@ a.status-card { .actions-modal, .mute-modal { background: lighten($ui-secondary-color, 8%); - color: $ui-base-color; + color: $inverted-text-color; border-radius: 8px; overflow: hidden; max-width: 90vw; @@ -3994,7 +3936,7 @@ a.status-card { & > div { flex: 1 1 auto; text-align: right; - color: lighten($ui-base-color, 33%); + color: $lighter-text-color; padding-right: 10px; } @@ -4081,7 +4023,7 @@ a.status-card { box-sizing: border-box; width: 100%; margin: 0; - color: $ui-base-color; + color: $inverted-text-color; background: $white; padding: 10px; font-family: inherit; @@ -4103,7 +4045,7 @@ a.status-card { margin-bottom: 24px; &__label { - color: $ui-base-color; + color: $inverted-text-color; font-size: 14px; } } @@ -4142,7 +4084,7 @@ a.status-card { li:not(:empty) { a { - color: $ui-base-color; + color: $inverted-text-color; display: flex; padding: 12px 16px; font-size: 15px; @@ -4178,14 +4120,14 @@ a.status-card { .confirmation-modal__cancel-button, .mute-modal__cancel-button { background-color: transparent; - color: darken($ui-secondary-color, 34%); + color: $lighter-text-color; font-size: 14px; font-weight: 500; &:hover, &:focus, &:active { - color: darken($ui-secondary-color, 38%); + color: transparentize($lighter-text-color, 0.04); } } } @@ -4218,7 +4160,7 @@ a.status-card { } .loading-bar { - background-color: $ui-highlight-color; + background-color: $highlight-text-color; height: 3px; position: absolute; top: 0; @@ -4266,7 +4208,7 @@ a.status-card { &__icon { flex: 0 0 auto; - color: $ui-base-lighter-color; + color: $darker-text-color; padding: 8px 18px; cursor: default; border-right: 1px solid lighten($ui-base-color, 8%); @@ -4296,7 +4238,7 @@ a.status-card { a { text-decoration: none; - color: $ui-base-lighter-color; + color: $darker-text-color; font-weight: 500; &:hover { @@ -4315,7 +4257,7 @@ a.status-card { } .fa { - color: $ui-base-lighter-color; + color: $darker-text-color; } } } @@ -4511,7 +4453,7 @@ a.status-card { z-index: 4; border: 0; background: $base-shadow-color; - color: $ui-primary-color; + color: $darker-text-color; transition: none; pointer-events: none; @@ -4522,7 +4464,7 @@ a.status-card { &:hover, &:active, &:focus { - color: lighten($ui-primary-color, 8%); + color: transparentize($darker-text-color, 0.07); } } @@ -4719,7 +4661,7 @@ a.status-card { background-size: cover; background-position: center; position: absolute; - color: $ui-primary-color; + color: $darker-text-color; text-decoration: none; border-radius: 4px; @@ -4727,7 +4669,7 @@ a.status-card { &:active, &:focus { outline: 0; - color: $ui-secondary-color; + color: transparentize($darker-text-color, 0.07); &::before { content: ""; @@ -4758,7 +4700,7 @@ a.status-card { a { display: block; flex: 1 1 auto; - color: $ui-primary-color; + color: $darker-text-color; padding: 15px 0; font-size: 14px; font-weight: 500; @@ -4767,7 +4709,7 @@ a.status-card { position: relative; &.active { - color: $ui-secondary-color; + color: transparentize($darker-text-color, 0.07); &::before, &::after { @@ -4802,12 +4744,12 @@ a.status-card { padding: 10px 14px; padding-bottom: 14px; margin-top: 10px; - color: $ui-primary-color; + color: $lighter-text-color; box-shadow: 2px 4px 15px rgba($base-shadow-color, 0.4); h4 { text-transform: uppercase; - color: $ui-primary-color; + color: $lighter-text-color; font-size: 13px; font-weight: 500; margin-bottom: 10px; @@ -4823,7 +4765,7 @@ a.status-card { em { font-weight: 500; - color: $ui-base-color; + color: $inverted-text-color; } } @@ -4839,11 +4781,11 @@ noscript { div { font-size: 14px; margin: 30px auto; - color: $ui-secondary-color; + color: $primary-text-color; max-width: 400px; a { - color: $ui-highlight-color; + color: $highlight-text-color; text-decoration: underline; &:hover { @@ -4941,7 +4883,6 @@ noscript { } .embed-modal__html { - color: $ui-secondary-color; outline: 0; box-sizing: border-box; display: block; @@ -4950,7 +4891,7 @@ noscript { padding: 10px; font-family: 'mastodon-font-monospace', monospace; background: $ui-base-color; - color: $ui-primary-color; + color: $primary-text-color; font-size: 14px; margin: 0; margin-bottom: 15px; @@ -4993,7 +4934,7 @@ noscript { &__message { position: relative; margin-left: 58px; - color: $ui-base-lighter-color; + color: $darker-text-color; padding: 8px 0; padding-top: 0; padding-bottom: 4px; @@ -5201,7 +5142,7 @@ noscript { } th { - color: $ui-primary-color; + color: $darker-text-color; background: darken($ui-base-color, 4%); max-width: 120px; font-weight: 500; diff --git a/app/javascript/styles/mastodon/containers.scss b/app/javascript/styles/mastodon/containers.scss index e761f58..8df2902 100644 --- a/app/javascript/styles/mastodon/containers.scss +++ b/app/javascript/styles/mastodon/containers.scss @@ -100,7 +100,7 @@ .name { flex: 1 1 auto; - color: $ui-secondary-color; + color: $darker-text-color; width: calc(100% - 88px); .username { diff --git a/app/javascript/styles/mastodon/emoji_picker.scss b/app/javascript/styles/mastodon/emoji_picker.scss index 4161cc0..3620a6f 100644 --- a/app/javascript/styles/mastodon/emoji_picker.scss +++ b/app/javascript/styles/mastodon/emoji_picker.scss @@ -7,7 +7,7 @@ font-size: 13px; display: inline-block; - color: $ui-base-color; + color: $inverted-text-color; .emoji-mart-emoji { padding: 6px; @@ -36,7 +36,7 @@ display: flex; justify-content: space-between; padding: 0 6px; - color: $ui-primary-color; + color: $lighter-text-color; line-height: 0; } @@ -50,15 +50,15 @@ cursor: pointer; &:hover { - color: darken($ui-primary-color, 4%); + color: opacify($lighter-text-color, 0.04); } } .emoji-mart-anchor-selected { - color: darken($ui-highlight-color, 3%); + color: $highlight-text-color; &:hover { - color: darken($ui-highlight-color, 3%); + color: darken($highlight-text-color, 4%); } .emoji-mart-anchor-bar { @@ -72,7 +72,7 @@ left: 0; width: 100%; height: 3px; - background-color: darken($ui-highlight-color, 3%); + background-color: $highlight-text-color; } .emoji-mart-anchors { @@ -115,7 +115,7 @@ display: block; width: 100%; background: rgba($ui-secondary-color, 0.3); - color: $ui-primary-color; + color: $inverted-text-color; border: 1px solid $ui-secondary-color; border-radius: 4px; @@ -184,7 +184,7 @@ font-size: 14px; text-align: center; padding-top: 70px; - color: $ui-primary-color; + color: $lighter-text-color; .emoji-mart-category-label { display: none; diff --git a/app/javascript/styles/mastodon/footer.scss b/app/javascript/styles/mastodon/footer.scss index 2d953b3..ba2a069 100644 --- a/app/javascript/styles/mastodon/footer.scss +++ b/app/javascript/styles/mastodon/footer.scss @@ -2,7 +2,7 @@ text-align: center; margin-top: 30px; font-size: 12px; - color: darken($ui-secondary-color, 25%); + color: $darker-text-color; .domain { font-weight: 500; diff --git a/app/javascript/styles/mastodon/forms.scss b/app/javascript/styles/mastodon/forms.scss index 945579a..3a3b4c3 100644 --- a/app/javascript/styles/mastodon/forms.scss +++ b/app/javascript/styles/mastodon/forms.scss @@ -29,14 +29,14 @@ code { span.hint { display: block; - color: $ui-primary-color; + color: $darker-text-color; font-size: 12px; margin-top: 4px; } p.hint { margin-bottom: 15px; - color: $ui-primary-color; + color: $darker-text-color; &.subtle-hint { text-align: center; @@ -44,10 +44,10 @@ code { line-height: 18px; margin-top: 15px; margin-bottom: 0; - color: $ui-primary-color; + color: $darker-text-color; a { - color: $ui-highlight-color; + color: $highlight-text-color; } } } @@ -244,35 +244,35 @@ code { } &:focus:invalid { - border-bottom-color: $error-value-color; + border-bottom-color: lighten($error-red, 12%); } &:required:valid { - border-bottom-color: $valid-value-color; + border-bottom-color: lighten($error-red, 12%); } &:active, &:focus { - border-bottom-color: $ui-highlight-color; + border-bottom-color: $highlight-text-color; background: rgba($base-overlay-background, 0.1); } } .input.field_with_errors { label { - color: $error-value-color; + color: lighten($error-red, 12%); } input[type=text], input[type=email], input[type=password] { - border-bottom-color: $error-value-color; + border-bottom-color: lighten($error-red, 12%); } .error { display: block; font-weight: 500; - color: $error-value-color; + color: lighten($error-red, 12%); margin-top: 4px; } } @@ -356,7 +356,7 @@ code { padding: 7px 4px; padding-bottom: 9px; font-size: 16px; - color: $ui-base-lighter-color; + color: $darker-text-color; font-family: inherit; pointer-events: none; cursor: default; @@ -366,7 +366,7 @@ code { .flash-message { background: lighten($ui-base-color, 8%); - color: $ui-primary-color; + color: $darker-text-color; border-radius: 4px; padding: 15px 10px; margin-bottom: 30px; @@ -378,7 +378,6 @@ code { } .oauth-code { - color: $ui-secondary-color; outline: 0; box-sizing: border-box; display: block; @@ -387,7 +386,7 @@ code { padding: 10px; font-family: 'mastodon-font-monospace', monospace; background: $ui-base-color; - color: $ui-primary-color; + color: $primary-text-color; font-size: 14px; margin: 0; @@ -426,7 +425,7 @@ code { text-align: center; a { - color: $ui-primary-color; + color: $darker-text-color; text-decoration: none; &:hover { @@ -439,7 +438,7 @@ code { .follow-prompt { margin-bottom: 30px; text-align: center; - color: $ui-primary-color; + color: $darker-text-color; h2 { font-size: 16px; @@ -447,7 +446,7 @@ code { } strong { - color: $ui-secondary-color; + color: $primary-text-color; font-weight: 500; @each $lang in $cjk-langs { @@ -484,7 +483,7 @@ code { .qr-alternative { margin-bottom: 20px; - color: $ui-secondary-color; + color: $darker-text-color; flex: 150px; samp { @@ -569,7 +568,7 @@ code { .post-follow-actions { text-align: center; - color: $ui-primary-color; + color: $darker-text-color; div { margin-bottom: 4px; @@ -582,7 +581,7 @@ code { h4 { font-size: 16px; - color: $ui-base-lighter-color; + color: $primary-text-color; text-align: center; margin-bottom: 20px; border: 0; diff --git a/app/javascript/styles/mastodon/landing_strip.scss b/app/javascript/styles/mastodon/landing_strip.scss index ffa1e14..651c06c 100644 --- a/app/javascript/styles/mastodon/landing_strip.scss +++ b/app/javascript/styles/mastodon/landing_strip.scss @@ -1,7 +1,7 @@ .landing-strip, .memoriam-strip { background: rgba(darken($ui-base-color, 7%), 0.8); - color: $ui-primary-color; + color: $darker-text-color; font-weight: 400; padding: 14px; border-radius: 4px; @@ -45,7 +45,7 @@ padding: 14px; border-radius: 4px; background: rgba(darken($ui-base-color, 7%), 0.8); - color: $ui-secondary-color; + color: $darker-text-color; font-weight: 400; margin-bottom: 20px; @@ -88,7 +88,7 @@ .fa { margin-right: 5px; - color: $ui-primary-color; + color: $darker-text-color; } } @@ -103,7 +103,7 @@ text-decoration: none; span { - color: $ui-highlight-color; + color: $highlight-text-color; font-weight: 400; } } diff --git a/app/javascript/styles/mastodon/stream_entries.scss b/app/javascript/styles/mastodon/stream_entries.scss index dfdc48d..c39163b 100644 --- a/app/javascript/styles/mastodon/stream_entries.scss +++ b/app/javascript/styles/mastodon/stream_entries.scss @@ -84,7 +84,7 @@ font-size: 14px; .status__relative-time { - color: $ui-primary-color; + color: $lighter-text-color; } } } @@ -93,7 +93,7 @@ display: block; max-width: 100%; padding-right: 25px; - color: $ui-base-color; + color: $lighter-text-color; } .status__avatar { @@ -123,7 +123,7 @@ strong { font-weight: 500; - color: $ui-base-color; + color: $inverted-text-color; @each $lang in $cjk-langs { &:lang(#{$lang}) { @@ -134,15 +134,15 @@ span { font-size: 14px; - color: $ui-primary-color; + color: $inverted-text-color; } } .status__content { - color: $ui-base-color; + color: $inverted-text-color; a { - color: $ui-highlight-color; + color: $highlight-text-color; } a.status__content__spoiler-link { @@ -180,7 +180,7 @@ strong { font-weight: 500; - color: $ui-base-color; + color: $inverted-text-color; @each $lang in $cjk-langs { &:lang(#{$lang}) { @@ -191,7 +191,7 @@ span { font-size: 14px; - color: $ui-primary-color; + color: $lighter-text-color; } } } @@ -207,10 +207,10 @@ } .status__content { - color: $ui-base-color; + color: $inverted-text-color; a { - color: $ui-highlight-color; + color: $highlight-text-color; } a.status__content__spoiler-link { @@ -225,7 +225,7 @@ .detailed-status__meta { margin-top: 15px; - color: $ui-primary-color; + color: $lighter-text-color; font-size: 14px; line-height: 18px; @@ -243,7 +243,7 @@ .status-card { border-color: lighten($ui-secondary-color, 4%); - color: darken($ui-primary-color, 4%); + color: $lighter-text-color; &:hover { background: lighten($ui-secondary-color, 4%); @@ -252,7 +252,7 @@ .status-card__title, .status-card__description { - color: $ui-base-color; + color: $inverted-text-color; } .status-card__image { @@ -262,7 +262,7 @@ .media-spoiler { background: $ui-base-color; - color: $ui-primary-color; + color: $darker-text-color; } .pre-header { @@ -270,7 +270,7 @@ padding-left: (48px + 14px * 2); padding-bottom: 0; margin-bottom: -4px; - color: $ui-primary-color; + color: $lighter-text-color; font-size: 14px; position: relative; @@ -280,7 +280,7 @@ } .status__display-name.muted strong { - color: $ui-primary-color; + color: $lighter-text-color; } } diff --git a/app/javascript/styles/mastodon/tables.scss b/app/javascript/styles/mastodon/tables.scss index 92870e6..c12d84f 100644 --- a/app/javascript/styles/mastodon/tables.scss +++ b/app/javascript/styles/mastodon/tables.scss @@ -30,7 +30,7 @@ } a { - color: $ui-highlight-color; + color: $highlight-text-color; text-decoration: underline; &:hover { @@ -68,7 +68,7 @@ a.table-action-link { display: inline-block; margin-right: 5px; padding: 0 10px; - color: rgba($primary-text-color, 0.7); + color: $darker-text-color; font-weight: 500; &:hover { diff --git a/app/javascript/styles/mastodon/variables.scss b/app/javascript/styles/mastodon/variables.scss index e456c27..dc4e72a 100644 --- a/app/javascript/styles/mastodon/variables.scss +++ b/app/javascript/styles/mastodon/variables.scss @@ -18,6 +18,11 @@ $base-overlay-background: $black !default; $base-border-color: $white !default; $simple-background-color: $white !default; $primary-text-color: $white !default; +$darker-text-color: rgba($primary-text-color, 0.7) !default; +$highlight-text-color: $classic-highlight-color !default; +$inverted-text-color: $black !default; +$lighter-text-color: rgba($inverted-text-color, 0.7) !default; +$action-button-color: #8d9ac2; $valid-value-color: $success-green !default; $error-value-color: $error-red !default; @@ -26,7 +31,7 @@ $ui-base-color: $classic-base-color !default; // Darkest $ui-base-lighter-color: lighten($ui-base-color, 26%) !default; // Lighter darkest $ui-primary-color: $classic-primary-color !default; // Lighter $ui-secondary-color: $classic-secondary-color !default; // Lightest -$ui-highlight-color: $classic-highlight-color !default; // Vibrant +$ui-highlight-color: #2b5fd9; // Language codes that uses CJK fonts $cjk-langs: ja, ko, zh-CN, zh-HK, zh-TW; From 1957209efd4054842a2ae0afd91b9fe0c38cbf0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Miko=C5=82ajczak?= Date: Wed, 18 Apr 2018 17:14:21 +0200 Subject: [PATCH 146/381] i18n: Update Polish translation (#7181) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Marcin Mikołajczak --- app/javascript/mastodon/locales/defaultMessages.json | 4 ++++ app/javascript/mastodon/locales/pl.json | 13 +++++++------ config/locales/pl.yml | 10 ++++++++++ config/locales/simple_form.pl.yml | 6 ++++++ 4 files changed, 27 insertions(+), 6 deletions(-) diff --git a/app/javascript/mastodon/locales/defaultMessages.json b/app/javascript/mastodon/locales/defaultMessages.json index 5a02e0c..86cf839 100644 --- a/app/javascript/mastodon/locales/defaultMessages.json +++ b/app/javascript/mastodon/locales/defaultMessages.json @@ -1149,6 +1149,10 @@ "id": "keyboard_shortcuts.enter" }, { + "defaultMessage": "to show/hide text behind CW", + "id": "keyboard_shortcuts.toggle_hidden" + }, + { "defaultMessage": "to move up in the list", "id": "keyboard_shortcuts.up" }, diff --git a/app/javascript/mastodon/locales/pl.json b/app/javascript/mastodon/locales/pl.json index c55603a..22ae2de 100644 --- a/app/javascript/mastodon/locales/pl.json +++ b/app/javascript/mastodon/locales/pl.json @@ -40,7 +40,7 @@ "bundle_modal_error.retry": "Spróbuj ponownie", "column.blocks": "Zablokowani użytkownicy", "column.community": "Lokalna oś czasu", - "column.direct": "Direct messages", + "column.direct": "Wiadomości bezpośrednie", "column.domain_blocks": "Ukryte domeny", "column.favourites": "Ulubione", "column.follow_requests": "Prośby o śledzenie", @@ -101,7 +101,7 @@ "emoji_button.symbols": "Symbole", "emoji_button.travel": "Podróże i miejsca", "empty_column.community": "Lokalna oś czasu jest pusta. Napisz coś publicznie, aby zagaić!", - "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.", + "empty_column.direct": "Nie masz żadnych wiadomości bezpośrednich. Kiedy dostaniesz lub wyślesz jakąś, pojawi się ona tutaj.", "empty_column.hashtag": "Nie ma wpisów oznaczonych tym hashtagiem. Możesz napisać pierwszy!", "empty_column.home": "Nie śledzisz nikogo. Odwiedź publiczną oś czasu lub użyj wyszukiwarki, aby znaleźć interesujące Cię profile.", "empty_column.home.public_timeline": "publiczna oś czasu", @@ -135,6 +135,7 @@ "keyboard_shortcuts.mention": "aby wspomnieć o autorze", "keyboard_shortcuts.reply": "aby odpowiedzieć", "keyboard_shortcuts.search": "aby przejść do pola wyszukiwania", + "keyboard_shortcuts.toggle_hidden": "aby wyświetlić lub ukryć wpis spod CW", "keyboard_shortcuts.toot": "aby utworzyć nowy wpis", "keyboard_shortcuts.unfocus": "aby opuścić pole wyszukiwania/pisania", "keyboard_shortcuts.up": "aby przejść na górę listy", @@ -156,7 +157,7 @@ "mute_modal.hide_notifications": "Chcesz ukryć powiadomienia od tego użytkownika?", "navigation_bar.blocks": "Zablokowani użytkownicy", "navigation_bar.community_timeline": "Lokalna oś czasu", - "navigation_bar.direct": "Direct messages", + "navigation_bar.direct": "Wiadomości bezpośrednie", "navigation_bar.domain_blocks": "Ukryte domeny", "navigation_bar.edit_profile": "Edytuj profil", "navigation_bar.favourites": "Ulubione", @@ -241,10 +242,10 @@ "search_results.total": "{count, number} {count, plural, one {wynik} few {wyniki} many {wyników} more {wyników}}", "standalone.public_title": "Spojrzenie w głąb…", "status.block": "Zablokuj @{name}", - "status.cancel_reblog_private": "Unboost", + "status.cancel_reblog_private": "Cofnij podbicie", "status.cannot_reblog": "Ten wpis nie może zostać podbity", "status.delete": "Usuń", - "status.direct": "Direct message @{name}", + "status.direct": "Wyślij wiadomość bezpośrednią do @{name}", "status.embed": "Osadź", "status.favourite": "Ulubione", "status.load_more": "Załaduj więcej", @@ -257,7 +258,7 @@ "status.pin": "Przypnij do profilu", "status.pinned": "Przypięty wpis", "status.reblog": "Podbij", - "status.reblog_private": "Boost to original audience", + "status.reblog_private": "Podbij dla odbiorców oryginalnego wpisu", "status.reblogged_by": "{name} podbił", "status.reply": "Odpowiedz", "status.replyAll": "Odpowiedz na wątek", diff --git a/config/locales/pl.yml b/config/locales/pl.yml index 0f18ace..faa5494 100644 --- a/config/locales/pl.yml +++ b/config/locales/pl.yml @@ -260,6 +260,16 @@ pl: created_msg: Pomyslnie utworzono notatkę moderacyjną. destroyed_msg: Pomyślnie usunięto notatkę moderacyjną. reports: + account: + created_reports: Zgłoszenia utworzone z tego konta + moderation: + silenced: Wyciszone + suspended: Zawieszone + title: Moderacja + moderation_notes: Notatki moderacyjne + note: notatka + report: zgłoszenie + targeted_reports: Zgłoszenia dotycząće tego konta action_taken_by: Działanie podjęte przez are_you_sure: Czy na pewno? assign_to_self: Przypisz do siebie diff --git a/config/locales/simple_form.pl.yml b/config/locales/simple_form.pl.yml index 2b55f60..cbca232 100644 --- a/config/locales/simple_form.pl.yml +++ b/config/locales/simple_form.pl.yml @@ -10,6 +10,7 @@ pl: many: Pozostało %{count} znaków one: Pozostał 1 znak other: Pozostało %{count} znaków + fields: Możesz ustawić maksymalnie 4 niestandardowe pola wyświetlane jako tabela na Twoim profilu header: PNG, GIF lub JPG. Maksymalnie 2MB. Zostanie zmniejszony do 700x335px locked: Musisz akceptować prośby o śledzenie note: @@ -26,6 +27,10 @@ pl: user: filtered_languages: Wpisy w wybranych językach nie będą wyświetlać się na publicznych osiach czasu labels: + account: + fields: + name: Nazwa + value: Zawartość defaults: avatar: Awatar confirm_new_password: Potwierdź nowe hasło @@ -35,6 +40,7 @@ pl: display_name: Widoczna nazwa email: Adres e-mail expires_in: Wygaśnie po + fields: Metadane profilu filtered_languages: Filtrowane języki header: Nagłówek locale: Język From ff87d1bc3ecdb81ff5c523f0964ecf223a503d30 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Thu, 19 Apr 2018 00:53:31 +0200 Subject: [PATCH 147/381] Rescue SSL errors when processing mentions, remove useless line (#7184) --- app/services/process_mentions_service.rb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/services/process_mentions_service.rb b/app/services/process_mentions_service.rb index 8e285e1..dc8df4a 100644 --- a/app/services/process_mentions_service.rb +++ b/app/services/process_mentions_service.rb @@ -17,13 +17,11 @@ class ProcessMentionsService < BaseService if mention_undeliverable?(status, mentioned_account) begin mentioned_account = resolve_account_service.call($1) - rescue Goldfinger::Error, HTTP::Error + rescue Goldfinger::Error, HTTP::Error, OpenSSL::SSL::SSLError, Mastodon::UnexpectedResponseError mentioned_account = nil end end - mentioned_account ||= Account.find_remote(username, domain) - next match if mention_undeliverable?(status, mentioned_account) mentioned_account.mentions.where(status: status).first_or_create(status: status) From ba917e15f66c7848fe943e571d1ec5eeb549b59d Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Thu, 19 Apr 2018 02:36:31 +0200 Subject: [PATCH 148/381] Fix text color in "show more" link inside boost confirmation modal (#7183) --- app/javascript/styles/mastodon/components.scss | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index cdf3a9d..9b5ab6f 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -40,6 +40,16 @@ cursor: default; } + &::-moz-focus-inner { + border: 0; + } + + &::-moz-focus-inner, + &:focus, + &:active { + outline: 0 !important; + } + &.button-primary, &.button-alternative, &.button-secondary, @@ -666,6 +676,16 @@ background: lighten($action-button-color, 7%); text-decoration: none; } + + &::-moz-focus-inner { + border: 0; + } + + &::-moz-focus-inner, + &:focus, + &:active { + outline: 0 !important; + } } .status__content__text { @@ -3899,6 +3919,10 @@ a.status-card { top: 10px; width: 48px; } + + .status__content__spoiler-link { + color: lighten($ui-secondary-color, 8%); + } } .actions-modal { From b9c35785e2513ff5ea33c1247eb18e48aa70fc5f Mon Sep 17 00:00:00 2001 From: Emelia Smith Date: Thu, 19 Apr 2018 15:39:54 +0200 Subject: [PATCH 149/381] Reports: Merge contents and comment columns (#7189) --- app/views/admin/reports/_report.html.haml | 22 +++++++++++----------- app/views/admin/reports/index.html.haml | 1 - 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/app/views/admin/reports/_report.html.haml b/app/views/admin/reports/_report.html.haml index d266f48..84db00a 100644 --- a/app/views/admin/reports/_report.html.haml +++ b/app/views/admin/reports/_report.html.haml @@ -5,18 +5,18 @@ = link_to report.target_account.acct, admin_account_path(report.target_account.id) %td.reporter = link_to report.account.acct, admin_account_path(report.account.id) - %td.comment - %span{ title: report.comment } + %td + %div{ title: report.comment } = truncate(report.comment, length: 30, separator: ' ') - %td.stats - - unless report.statuses.empty? - %span{ title: t('admin.accounts.statuses') } - = fa_icon('comment') - = report.statuses.count - - unless report.media_attachments.empty? - %span{ title: t('admin.accounts.media_attachments') } - = fa_icon('camera') - = report.media_attachments.count + %div + - unless report.statuses.empty? + %span{ title: t('admin.accounts.statuses') } + = fa_icon('comment') + = report.statuses.count + - unless report.media_attachments.empty? + %span{ title: t('admin.accounts.media_attachments') } + = fa_icon('camera') + = report.media_attachments.count %td - if report.assigned_account.nil? \- diff --git a/app/views/admin/reports/index.html.haml b/app/views/admin/reports/index.html.haml index 3b127c4..c3baaf6 100644 --- a/app/views/admin/reports/index.html.haml +++ b/app/views/admin/reports/index.html.haml @@ -18,7 +18,6 @@ %th= t('admin.reports.id') %th= t('admin.reports.target') %th= t('admin.reports.reported_by') - %th= t('admin.reports.comment.label') %th= t('admin.reports.report_contents') %th= t('admin.reports.assigned') %th From ca2cbe8f0f6eb1efb095817f1dba26f89f1b4a54 Mon Sep 17 00:00:00 2001 From: beatrix Date: Thu, 19 Apr 2018 17:35:47 -0400 Subject: [PATCH 150/381] Fix webkit scrollbars (#7191) * Revert "Make scroll bars wider (#7060)" This reverts commit 11715454d033784bf6176b75a954e5c28b5d79e5. * for real make webkit scrollbar wider --- app/javascript/styles/mastodon/reset.scss | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/javascript/styles/mastodon/reset.scss b/app/javascript/styles/mastodon/reset.scss index 58d4de3..ff3b2c0 100644 --- a/app/javascript/styles/mastodon/reset.scss +++ b/app/javascript/styles/mastodon/reset.scss @@ -53,6 +53,11 @@ table { border-spacing: 0; } +::-webkit-scrollbar { + width: 12px; + height: 12px; +} + ::-webkit-scrollbar-thumb { background: lighten($ui-base-color, 4%); border: 0px none $base-border-color; From 1663368724b0a0076823aa31459eb77b9264969b Mon Sep 17 00:00:00 2001 From: Yamagishi Kazutoshi Date: Fri, 20 Apr 2018 09:06:53 +0900 Subject: [PATCH 151/381] Replace preload link tag to Rails helper (#7192) --- app/views/home/index.html.haml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/app/views/home/index.html.haml b/app/views/home/index.html.haml index 8c88d2d..7b1a7e5 100644 --- a/app/views/home/index.html.haml +++ b/app/views/home/index.html.haml @@ -1,8 +1,9 @@ - content_for :header_tags do - %link{ href: asset_pack_path('features/getting_started.js'), crossorigin: 'anonymous', rel: 'preload', as: 'script' }/ - %link{ href: asset_pack_path('features/compose.js'), crossorigin: 'anonymous', rel: 'preload', as: 'script' }/ - %link{ href: asset_pack_path('features/home_timeline.js'), crossorigin: 'anonymous', rel: 'preload', as: 'script' }/ - %link{ href: asset_pack_path('features/notifications.js'), crossorigin: 'anonymous', rel: 'preload', as: 'script' }/ + = preload_link_tag asset_pack_path('features/getting_started.js'), crossorigin: 'anonymous' + = preload_link_tag asset_pack_path('features/compose.js'), crossorigin: 'anonymous' + = preload_link_tag asset_pack_path('features/home_timeline.js'), crossorigin: 'anonymous' + = preload_link_tag asset_pack_path('features/notifications.js'), crossorigin: 'anonymous' + %meta{name: 'applicationServerKey', content: Rails.configuration.x.vapid_public_key} %script#initial-state{ type: 'application/json' }!= json_escape(@initial_state_json) From a9c440637ca9f36bcf051094abe3bcba1da63166 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Fri, 20 Apr 2018 02:28:48 +0200 Subject: [PATCH 152/381] Improve report layout (#7188) * Use table for statuses in report * Display reported account and reporter in the same table * Split accounts and general report info into two tables again * Redesign report statuses table, notes, merge notes and action log * Remove unused translations * Fix code style issue * Fix code style issue * Fix code style issue --- .../admin/reported_statuses_controller.rb | 14 ++- app/controllers/admin/reports_controller.rb | 8 +- .../admin/account_moderation_notes_helper.rb | 16 +++ app/helpers/application_helper.rb | 4 + app/helpers/stream_entries_helper.rb | 13 +++ app/javascript/packs/admin.js | 1 + app/javascript/styles/mastodon/admin.scss | 103 +++++++++++------- app/javascript/styles/mastodon/components.scss | 16 ++- app/javascript/styles/mastodon/tables.scss | 116 +++++++++++++++++++- app/views/admin/action_logs/_action_log.html.haml | 2 +- app/views/admin/action_logs/index.html.haml | 3 +- .../admin/report_notes/_report_note.html.haml | 14 +-- app/views/admin/reports/_account.html.haml | 19 ++++ app/views/admin/reports/_account_details.html.haml | 20 ---- app/views/admin/reports/_action_log.html.haml | 6 + app/views/admin/reports/_report.html.haml | 6 +- app/views/admin/reports/_status.html.haml | 28 +++++ app/views/admin/reports/index.html.haml | 27 ++--- app/views/admin/reports/show.html.haml | 121 ++++++++++----------- .../stream_entries/_detailed_status.html.haml | 6 +- app/views/stream_entries/_simple_status.html.haml | 4 +- config/locales/ar.yml | 1 - config/locales/ca.yml | 1 - config/locales/de.yml | 1 - config/locales/en.yml | 26 ++--- config/locales/eo.yml | 1 - config/locales/es.yml | 1 - config/locales/fa.yml | 1 - config/locales/fi.yml | 1 - config/locales/fr.yml | 1 - config/locales/gl.yml | 1 - config/locales/he.yml | 1 - config/locales/hu.yml | 1 - config/locales/id.yml | 1 - config/locales/io.yml | 1 - config/locales/ja.yml | 5 - config/locales/ko.yml | 1 - config/locales/nl.yml | 1 - config/locales/no.yml | 1 - config/locales/oc.yml | 1 - config/locales/pl.yml | 12 -- config/locales/pt-BR.yml | 1 - config/locales/pt.yml | 1 - config/locales/ru.yml | 1 - config/locales/sk.yml | 1 - config/locales/sr-Latn.yml | 1 - config/locales/sr.yml | 1 - config/locales/sv.yml | 1 - config/locales/th.yml | 1 - config/locales/tr.yml | 1 - config/locales/uk.yml | 1 - config/locales/zh-CN.yml | 1 - config/locales/zh-HK.yml | 12 -- config/locales/zh-TW.yml | 1 - .../admin/reported_statuses_controller_spec.rb | 2 +- 55 files changed, 382 insertions(+), 251 deletions(-) create mode 100644 app/views/admin/reports/_account.html.haml delete mode 100644 app/views/admin/reports/_account_details.html.haml create mode 100644 app/views/admin/reports/_action_log.html.haml create mode 100644 app/views/admin/reports/_status.html.haml diff --git a/app/controllers/admin/reported_statuses_controller.rb b/app/controllers/admin/reported_statuses_controller.rb index 535bd11..522f68c 100644 --- a/app/controllers/admin/reported_statuses_controller.rb +++ b/app/controllers/admin/reported_statuses_controller.rb @@ -8,7 +8,7 @@ module Admin def create authorize :status, :update? - @form = Form::StatusBatch.new(form_status_batch_params.merge(current_account: current_account)) + @form = Form::StatusBatch.new(form_status_batch_params.merge(current_account: current_account, action: action_from_button)) flash[:alert] = I18n.t('admin.statuses.failed_to_execute') unless @form.save redirect_to admin_report_path(@report) @@ -35,7 +35,17 @@ module Admin end def form_status_batch_params - params.require(:form_status_batch).permit(:action, status_ids: []) + params.require(:form_status_batch).permit(status_ids: []) + end + + def action_from_button + if params[:nsfw_on] + 'nsfw_on' + elsif params[:nsfw_off] + 'nsfw_off' + elsif params[:delete] + 'delete' + end end def set_report diff --git a/app/controllers/admin/reports_controller.rb b/app/controllers/admin/reports_controller.rb index a4ae950..d00b3d2 100644 --- a/app/controllers/admin/reports_controller.rb +++ b/app/controllers/admin/reports_controller.rb @@ -11,10 +11,10 @@ module Admin def show authorize @report, :show? - @report_note = @report.notes.new - @report_notes = @report.notes.latest - @report_history = @report.history - @form = Form::StatusBatch.new + + @report_note = @report.notes.new + @report_notes = (@report.notes.latest + @report.history).sort_by(&:created_at) + @form = Form::StatusBatch.new end def update diff --git a/app/helpers/admin/account_moderation_notes_helper.rb b/app/helpers/admin/account_moderation_notes_helper.rb index b17c522..fdfadef 100644 --- a/app/helpers/admin/account_moderation_notes_helper.rb +++ b/app/helpers/admin/account_moderation_notes_helper.rb @@ -1,4 +1,20 @@ # frozen_string_literal: true module Admin::AccountModerationNotesHelper + def admin_account_link_to(account) + link_to admin_account_path(account.id), class: name_tag_classes(account) do + safe_join([ + image_tag(account.avatar.url, width: 15, height: 15, alt: display_name(account), class: 'avatar'), + content_tag(:span, account.acct, class: 'username'), + ], ' ') + end + end + + private + + def name_tag_classes(account) + classes = ['name-tag'] + classes << 'suspended' if account.suspended? + classes.join(' ') + end end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index bab4615..95863ab 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -63,4 +63,8 @@ module ApplicationHelper def opengraph(property, content) tag(:meta, content: content, property: property) end + + def react_component(name, props = {}) + content_tag(:div, nil, data: { component: name.to_s.camelcase, props: Oj.dump(props) }) + end end diff --git a/app/helpers/stream_entries_helper.rb b/app/helpers/stream_entries_helper.rb index 3992432..8254ef4 100644 --- a/app/helpers/stream_entries_helper.rb +++ b/app/helpers/stream_entries_helper.rb @@ -113,6 +113,19 @@ module StreamEntriesHelper end end + def fa_visibility_icon(status) + case status.visibility + when 'public' + fa_icon 'globe fw' + when 'unlisted' + fa_icon 'unlock-alt fw' + when 'private' + fa_icon 'lock fw' + when 'direct' + fa_icon 'envelope fw' + end + end + private def simplified_text(text) diff --git a/app/javascript/packs/admin.js b/app/javascript/packs/admin.js index 2bf1514..5dbcc03 100644 --- a/app/javascript/packs/admin.js +++ b/app/javascript/packs/admin.js @@ -24,6 +24,7 @@ delegate(document, batchCheckboxClassName, 'change', () => { const checkAllElement = document.querySelector('#batch_checkbox_all'); if (checkAllElement) { checkAllElement.checked = [].every.call(document.querySelectorAll(batchCheckboxClassName), (content) => content.checked); + checkAllElement.indeterminate = !checkAllElement.checked && [].some.call(document.querySelectorAll(batchCheckboxClassName), (content) => content.checked); } }); diff --git a/app/javascript/styles/mastodon/admin.scss b/app/javascript/styles/mastodon/admin.scss index 348f720..a0f6944 100644 --- a/app/javascript/styles/mastodon/admin.scss +++ b/app/javascript/styles/mastodon/admin.scss @@ -141,14 +141,15 @@ } hr { - margin: 20px 0; + width: 100%; + height: 0; border: 0; - background: transparent; - border-bottom: 1px solid $ui-base-color; + border-bottom: 1px solid rgba($ui-base-lighter-color, .6); + margin: 20px 0; - &.section-break { - margin: 30px 0; - border-bottom: 2px solid $ui-base-lighter-color; + &.spacer { + height: 1px; + border: 0; } } @@ -335,34 +336,8 @@ } } -.report-note__comment { - margin-bottom: 20px; -} - -.report-note__form { - margin-bottom: 20px; - - .report-note__textarea { - box-sizing: border-box; - border: 0; - padding: 7px 4px; - margin-bottom: 10px; - font-size: 16px; - color: $inverted-text-color; - display: block; - width: 100%; - outline: 0; - font-family: inherit; - resize: vertical; - } - - .report-note__buttons { - text-align: right; - } - - .report-note__button { - margin: 0 0 5px 5px; - } +.simple_form.new_report_note { + max-width: 100%; } .batch-form-box { @@ -390,13 +365,6 @@ } } -.batch-checkbox, -.batch-checkbox-all { - display: flex; - align-items: center; - margin-right: 5px; -} - .back-link { margin-bottom: 10px; font-size: 14px; @@ -416,7 +384,7 @@ } .log-entry { - margin-bottom: 8px; + margin-bottom: 20px; line-height: 20px; &__header { @@ -514,9 +482,12 @@ } } +a.name-tag, .name-tag { display: flex; align-items: center; + text-decoration: none; + color: $ui-secondary-color; .avatar { display: block; @@ -528,4 +499,52 @@ .username { font-weight: 500; } + + &.suspended { + .username { + text-decoration: line-through; + color: lighten($error-red, 12%); + } + + .avatar { + filter: grayscale(100%); + opacity: 0.8; + } + } +} + +.speech-bubble { + margin-bottom: 20px; + border-left: 4px solid $ui-highlight-color; + + &.positive { + border-left-color: $success-green; + } + + &.negative { + border-left-color: lighten($error-red, 12%); + } + + &__bubble { + padding: 16px; + padding-left: 14px; + font-size: 15px; + line-height: 20px; + border-radius: 4px 4px 4px 0; + position: relative; + font-weight: 500; + + a { + color: $ui-primary-color; + } + } + + &__owner { + padding: 8px; + padding-left: 12px; + } + + time { + color: $darker-text-color; + } } diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index 9b5ab6f..908fa8a 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -1006,6 +1006,15 @@ padding: 10px; border-bottom: 1px solid lighten($ui-base-color, 8%); + &.compact { + padding: 0; + border-bottom: 0; + + .account__avatar-wrapper { + margin-left: 0; + } + } + .account__display-name { flex: 1 1 auto; display: block; @@ -1029,7 +1038,6 @@ .account__avatar { @include avatar-radius(); position: relative; - cursor: pointer; &-inline { display: inline-block; @@ -1038,6 +1046,10 @@ } } +a .account__avatar { + cursor: pointer; +} + .account__avatar-overlay { @include avatar-size(48px); @@ -1286,7 +1298,7 @@ .status__display-name, .reply-indicator__display-name, .detailed-status__display-name, -.account__display-name { +a.account__display-name { &:hover strong { text-decoration: underline; } diff --git a/app/javascript/styles/mastodon/tables.scss b/app/javascript/styles/mastodon/tables.scss index c12d84f..fa876e6 100644 --- a/app/javascript/styles/mastodon/tables.scss +++ b/app/javascript/styles/mastodon/tables.scss @@ -11,6 +11,7 @@ vertical-align: top; border-top: 1px solid $ui-base-color; text-align: left; + background: darken($ui-base-color, 4%); } & > thead > tr > th { @@ -48,9 +49,38 @@ } } - &.inline-table > tbody > tr:nth-child(odd) > td, - &.inline-table > tbody > tr:nth-child(odd) > th { - background: transparent; + &.inline-table { + & > tbody > tr:nth-child(odd) { + & > td, + & > th { + background: transparent; + } + } + + & > tbody > tr:first-child { + & > td, + & > th { + border-top: 0; + } + } + } + + &.batch-table { + & > thead > tr > th { + background: $ui-base-color; + border-top: 1px solid darken($ui-base-color, 8%); + border-bottom: 1px solid darken($ui-base-color, 8%); + + &:first-child { + border-radius: 4px 0 0; + border-left: 1px solid darken($ui-base-color, 8%); + } + + &:last-child { + border-radius: 0 4px 0 0; + border-right: 1px solid darken($ui-base-color, 8%); + } + } } } @@ -63,6 +93,13 @@ samp { font-family: 'mastodon-font-monospace', monospace; } +button.table-action-link { + background: transparent; + border: 0; + font: inherit; +} + +button.table-action-link, a.table-action-link { text-decoration: none; display: inline-block; @@ -79,4 +116,77 @@ a.table-action-link { font-weight: 400; margin-right: 5px; } + + &:first-child { + padding-left: 0; + } +} + +.batch-table { + &__toolbar, + &__row { + display: flex; + + &__select { + box-sizing: border-box; + padding: 8px 16px; + cursor: pointer; + min-height: 100%; + + input { + margin-top: 8px; + } + } + + &__actions, + &__content { + padding: 8px 0; + padding-right: 16px; + flex: 1 1 auto; + } + } + + &__toolbar { + border: 1px solid darken($ui-base-color, 8%); + background: $ui-base-color; + border-radius: 4px 0 0; + height: 47px; + align-items: center; + + &__actions { + text-align: right; + padding-right: 16px - 5px; + } + } + + &__row { + border: 1px solid darken($ui-base-color, 8%); + border-top: 0; + background: darken($ui-base-color, 4%); + + &:hover { + background: darken($ui-base-color, 2%); + } + + &:nth-child(even) { + background: $ui-base-color; + + &:hover { + background: lighten($ui-base-color, 2%); + } + } + + &__content { + padding-top: 12px; + padding-bottom: 16px; + } + } + + .status__content { + padding-top: 0; + + strong { + font-weight: 700; + } + } } diff --git a/app/views/admin/action_logs/_action_log.html.haml b/app/views/admin/action_logs/_action_log.html.haml index ec90961..f059814 100644 --- a/app/views/admin/action_logs/_action_log.html.haml +++ b/app/views/admin/action_logs/_action_log.html.haml @@ -1,4 +1,4 @@ -%li.log-entry +.log-entry .log-entry__header .log-entry__avatar = image_tag action_log.account.avatar.url(:original), alt: '', width: 40, height: 40, class: 'avatar' diff --git a/app/views/admin/action_logs/index.html.haml b/app/views/admin/action_logs/index.html.haml index bb6d7b5..a4d3871 100644 --- a/app/views/admin/action_logs/index.html.haml +++ b/app/views/admin/action_logs/index.html.haml @@ -1,7 +1,6 @@ - content_for :page_title do = t('admin.action_logs.title') -%ul - = render @action_logs += render @action_logs = paginate @action_logs diff --git a/app/views/admin/report_notes/_report_note.html.haml b/app/views/admin/report_notes/_report_note.html.haml index 1f621e0..d34dc3d 100644 --- a/app/views/admin/report_notes/_report_note.html.haml +++ b/app/views/admin/report_notes/_report_note.html.haml @@ -1,9 +1,7 @@ -%li - %h4 - = report_note.account.acct - %div{ style: 'float: right' } - %time.formatted{ datetime: report_note.created_at.iso8601, title: l(report_note.created_at) } - = l report_note.created_at - = table_link_to 'trash', t('admin.reports.notes.delete'), admin_report_note_path(report_note), method: :delete if can?(:destroy, report_note) - %div{ class: 'report-note__comment' } +.speech-bubble + .speech-bubble__bubble = simple_format(h(report_note.content)) + .speech-bubble__owner + = admin_account_link_to report_note.account + %time.formatted{ datetime: report_note.created_at.iso8601 }= l report_note.created_at + = table_link_to 'trash', t('admin.reports.notes.delete'), admin_report_note_path(report_note), method: :delete if can?(:destroy, report_note) diff --git a/app/views/admin/reports/_account.html.haml b/app/views/admin/reports/_account.html.haml new file mode 100644 index 0000000..22b7a08 --- /dev/null +++ b/app/views/admin/reports/_account.html.haml @@ -0,0 +1,19 @@ +- size ||= 36 + +.account.compact + .account__wrapper + - if account.nil? + .account__display-name + .account__avatar-wrapper + .account__avatar{ style: "background-image: url(#{full_asset_url('avatars/original/missing.png', skip_pipeline: true)}); width: #{size}px; height: #{size}px; background-size: #{size}px #{size}px" } + %span.display-name + %strong= t 'about.contact_missing' + %span.display-name__account= t 'about.contact_unavailable' + - else + = link_to TagManager.instance.url_for(account), class: 'account__display-name' do + .account__avatar-wrapper + .account__avatar{ style: "background-image: url(#{account.avatar.url}); width: #{size}px; height: #{size}px; background-size: #{size}px #{size}px" } + %span.display-name + %bdi + %strong.display-name__html.emojify= display_name(account) + %span.display-name__account @#{account.acct} diff --git a/app/views/admin/reports/_account_details.html.haml b/app/views/admin/reports/_account_details.html.haml deleted file mode 100644 index a8af39b..0000000 --- a/app/views/admin/reports/_account_details.html.haml +++ /dev/null @@ -1,20 +0,0 @@ -.table-wrapper - %table.table - %tbody - %tr - %td= t('admin.reports.account.created_reports') - %td= link_to pluralize(account.reports.count, t('admin.reports.account.report')), admin_reports_path(account_id: account.id) - %tr - %td= t('admin.reports.account.targeted_reports') - %td= link_to pluralize(account.targeted_reports.count, t('admin.reports.account.report')), admin_reports_path(target_account_id: account.id) - %tr - %td= t('admin.reports.account.moderation_notes') - %td= link_to pluralize(account.targeted_moderation_notes.count, t('admin.reports.account.note')), admin_reports_path(target_account_id: account.id) - - if account.silenced? || account.suspended? - %tr - %td= t('admin.reports.account.moderation.title') - %td - - if account.silenced? - %p= t('admin.reports.account.moderation.silenced') - - if account.suspended? - %p= t('admin.reports.account.moderation.suspended') diff --git a/app/views/admin/reports/_action_log.html.haml b/app/views/admin/reports/_action_log.html.haml new file mode 100644 index 0000000..024078e --- /dev/null +++ b/app/views/admin/reports/_action_log.html.haml @@ -0,0 +1,6 @@ +.speech-bubble.positive + .speech-bubble__bubble + = t("admin.action_logs.actions.#{action_log.action}_#{action_log.target_type.underscore}", name: content_tag(:span, action_log.account.username, class: 'username'), target: content_tag(:span, log_target(action_log), class: 'target')).html_safe + .speech-bubble__owner + = admin_account_link_to(action_log.account) + %time.formatted{ datetime: action_log.created_at.iso8601 }= l action_log.created_at diff --git a/app/views/admin/reports/_report.html.haml b/app/views/admin/reports/_report.html.haml index 84db00a..d6c8819 100644 --- a/app/views/admin/reports/_report.html.haml +++ b/app/views/admin/reports/_report.html.haml @@ -2,9 +2,9 @@ %td.id = "##{report.id}" %td.target - = link_to report.target_account.acct, admin_account_path(report.target_account.id) + = admin_account_link_to report.target_account %td.reporter - = link_to report.account.acct, admin_account_path(report.account.id) + = admin_account_link_to report.account %td %div{ title: report.comment } = truncate(report.comment, length: 30, separator: ' ') @@ -21,6 +21,6 @@ - if report.assigned_account.nil? \- - else - = link_to report.assigned_account.acct, admin_account_path(report.assigned_account.id) + = admin_account_link_to report.assigned_account %td = table_link_to 'circle', t('admin.reports.view'), admin_report_path(report) diff --git a/app/views/admin/reports/_status.html.haml b/app/views/admin/reports/_status.html.haml new file mode 100644 index 0000000..1376095 --- /dev/null +++ b/app/views/admin/reports/_status.html.haml @@ -0,0 +1,28 @@ +.batch-table__row + %label.batch-table__row__select.batch-checkbox + = f.check_box :status_ids, { multiple: true, include_hidden: false }, status.id + .batch-table__row__content + .status__content>< + - unless status.spoiler_text.blank? + %p>< + %strong= Formatter.instance.format_spoiler(status) + + = Formatter.instance.format(status) + + - unless status.media_attachments.empty? + - if status.media_attachments.first.video? + - video = status.media_attachments.first + = react_component :video, src: video.file.url(:original), preview: video.file.url(:small), sensitive: status.sensitive? && !current_account&.user&.setting_display_sensitive_media, width: 610, height: 343, inline: true + - else + = react_component :media_gallery, height: 343, sensitive: status.sensitive? && !current_account&.user&.setting_display_sensitive_media, 'autoPlayGif': current_account&.user&.setting_auto_play_gif, media: status.media_attachments.map { |a| ActiveModelSerializers::SerializableResource.new(a, serializer: REST::MediaAttachmentSerializer).as_json } + + .detailed-status__meta + = link_to TagManager.instance.url_for(status), class: 'detailed-status__datetime', target: stream_link_target, rel: 'noopener' do + %time.formatted{ datetime: status.created_at.iso8601, title: l(status.created_at) }= l(status.created_at) + · + = fa_visibility_icon(status) + = t("statuses.visibilities.#{status.visibility}") + - if status.sensitive? + · + = fa_icon('eye-slash fw') + = t('stream_entries.sensitive_content') diff --git a/app/views/admin/reports/index.html.haml b/app/views/admin/reports/index.html.haml index c3baaf6..44a531f 100644 --- a/app/views/admin/reports/index.html.haml +++ b/app/views/admin/reports/index.html.haml @@ -8,20 +8,17 @@ %li= filter_link_to t('admin.reports.unresolved'), resolved: nil %li= filter_link_to t('admin.reports.resolved'), resolved: '1' -= form_tag do - - .table-wrapper - %table.table - %thead - %tr - -# %th - %th= t('admin.reports.id') - %th= t('admin.reports.target') - %th= t('admin.reports.reported_by') - %th= t('admin.reports.report_contents') - %th= t('admin.reports.assigned') - %th - %tbody - = render @reports +.table-wrapper + %table.table + %thead + %tr + %th= t('admin.reports.id') + %th= t('admin.reports.target') + %th= t('admin.reports.reported_by') + %th= t('admin.reports.report_contents') + %th= t('admin.reports.assigned') + %th + %tbody + = render @reports = paginate @reports diff --git a/app/views/admin/reports/show.html.haml b/app/views/admin/reports/show.html.haml index 1306500..2bba307 100644 --- a/app/views/admin/reports/show.html.haml +++ b/app/views/admin/reports/show.html.haml @@ -14,16 +14,28 @@ - else = link_to t('admin.reports.mark_as_unresolved'), admin_report_path(@report, outcome: 'reopen'), method: :put, class: 'button' +%hr.spacer + .table-wrapper %table.table.inline-table %tbody %tr + %th= t('admin.reports.reported_account') + %td= admin_account_link_to @report.target_account + %td= table_link_to 'flag', pluralize(@report.target_account.targeted_reports.count, t('admin.reports.account.report')), admin_reports_path(target_account_id: @report.target_account.id) + %td= table_link_to 'file', pluralize(@report.target_account.targeted_moderation_notes.count, t('admin.reports.account.note')), admin_reports_path(target_account_id: @report.target_account.id) + %tr + %th= t('admin.reports.reported_by') + %td= admin_account_link_to @report.account + %td= table_link_to 'flag', pluralize(@report.account.targeted_reports.count, t('admin.reports.account.report')), admin_reports_path(target_account_id: @report.account.id) + %td= table_link_to 'file', pluralize(@report.account.targeted_moderation_notes.count, t('admin.reports.account.note')), admin_reports_path(target_account_id: @report.account.id) + %tr %th= t('admin.reports.created_at') - %td{colspan: 2} + %td{ colspan: 3 } %time.formatted{ datetime: @report.created_at.iso8601 } %tr %th= t('admin.reports.updated_at') - %td{colspan: 2} + %td{ colspan: 3 } %time.formatted{ datetime: @report.updated_at.iso8601 } %tr %th= t('admin.reports.status') @@ -32,14 +44,14 @@ = t('admin.reports.resolved') - else = t('admin.reports.unresolved') - %td{style: "text-align: right; overflow: hidden;"} + %td{ colspan: 2 } - if @report.action_taken? = table_link_to 'envelope-open', t('admin.reports.reopen'), admin_report_path(@report, outcome: 'reopen'), method: :put - if !@report.action_taken_by_account.nil? %tr %th= t('admin.reports.action_taken_by') - %td{colspan: 2} - = @report.action_taken_by_account.acct + %td{ colspan: 3 } + = admin_account_link_to @report.action_taken_by_account - else %tr %th= t('admin.reports.assigned') @@ -47,78 +59,55 @@ - if @report.assigned_account.nil? \- - else - = link_to @report.assigned_account.acct, admin_account_path(@report.assigned_account.id) - %td{style: "text-align: right"} + = admin_account_link_to @report.assigned_account + %td - if @report.assigned_account != current_user.account = table_link_to 'user', t('admin.reports.assign_to_self'), admin_report_path(@report, outcome: 'assign_to_self'), method: :put + %td - if !@report.assigned_account.nil? = table_link_to 'trash', t('admin.reports.unassign'), admin_report_path(@report, outcome: 'unassign'), method: :put -%hr{ class: "section-break"}/ - -.report-accounts - .report-accounts__item - %h3= t('admin.reports.reported_account') - = render 'authorize_follows/card', account: @report.target_account, admin: true - = render 'admin/reports/account_details', account: @report.target_account - .report-accounts__item - %h3= t('admin.reports.reported_by') - = render 'authorize_follows/card', account: @report.account, admin: true - = render 'admin/reports/account_details', account: @report.account - -%h3= t('admin.reports.comment.label') +%hr.spacer -= simple_format(@report.comment.presence || t('admin.reports.comment.none')) +.speech-bubble + .speech-bubble__bubble= simple_format(@report.comment.presence || t('admin.reports.comment.none')) + .speech-bubble__owner + = admin_account_link_to @report.account + %time.formatted{ datetime: @report.created_at.iso8601 } - unless @report.statuses.empty? - %hr/ - - %h3= t('admin.reports.statuses') + %hr.spacer/ = form_for(@form, url: admin_report_reported_statuses_path(@report.id)) do |f| - .batch-form-box - .batch-checkbox-all - = check_box_tag :batch_checkbox_all, nil, false - = f.select :action, Form::StatusBatch::ACTION_TYPE.map{|action| [t("admin.statuses.batch.#{action}"), action]} - = f.submit t('admin.statuses.execute'), data: { confirm: t('admin.reports.are_you_sure') }, class: 'button' - .media-spoiler-toggle-buttons - .media-spoiler-show-button.button= t('admin.statuses.media.show') - .media-spoiler-hide-button.button= t('admin.statuses.media.hide') - - @report.statuses.each do |status| - .report-status{ data: { id: status.id } } - .batch-checkbox - = f.check_box :status_ids, { multiple: true, include_hidden: false }, status.id - .activity-stream.activity-stream-headless - .entry= render 'stream_entries/simple_status', status: status - .report-status__actions - - unless status.media_attachments.empty? - = link_to admin_report_reported_status_path(@report, status, status: { sensitive: !status.sensitive }), method: :put, class: 'icon-button nsfw-button', title: t("admin.reports.nsfw.#{!status.sensitive}") do - = fa_icon status.sensitive? ? 'eye' : 'eye-slash' - = link_to admin_report_reported_status_path(@report, status), method: :delete, class: 'icon-button trash-button', title: t('admin.reports.delete'), data: { confirm: t('admin.reports.are_you_sure') }, remote: true do - = fa_icon 'trash' - -%hr{ class: "section-break"}/ - -%h3= t('admin.reports.notes.label') + .batch-table + .batch-table__toolbar + %label.batch-table__toolbar__select.batch-checkbox-all + = check_box_tag :batch_checkbox_all, nil, false + .batch-table__toolbar__actions + = f.button safe_join([fa_icon('eye-slash'), t('admin.statuses.batch.nsfw_on')]), name: :nsfw_on, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') } + = f.button safe_join([fa_icon('eye'), t('admin.statuses.batch.nsfw_off')]), name: :nsfw_off, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') } + = f.button safe_join([fa_icon('trash'), t('admin.statuses.batch.delete')]), name: :delete, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') } + .batch-table__body + = render partial: 'admin/reports/status', collection: @report.statuses, locals: { f: f } + +%hr.spacer/ + +- @report_notes.each do |item| + - if item.is_a?(Admin::ActionLog) + = render partial: 'action_log', locals: { action_log: item } + - elsif item.is_a?(ReportNote) + = render item + += simple_form_for @report_note, url: admin_report_notes_path do |f| + = render 'shared/error_messages', object: @report_note + = f.input :report_id, as: :hidden -- if @report_notes.length > 0 - %ul - = render @report_notes + .field-group + = f.input :content, placeholder: t('admin.reports.notes.placeholder'), rows: 6 -%h4= t('admin.reports.notes.new_label') -= form_for @report_note, url: admin_report_notes_path, html: { class: 'report-note__form' } do |f| - = render 'shared/error_messages', object: @report_note - = f.text_area :content, placeholder: t('admin.reports.notes.placeholder'), rows: 6, class: 'report-note__textarea' - = f.hidden_field :report_id - %div{ class: 'report-note__buttons' } + .actions - if @report.unresolved? - = f.submit t('admin.reports.notes.create_and_resolve'), name: :create_and_resolve, class: 'button report-note__button' + = f.button :button, t('admin.reports.notes.create_and_resolve'), name: :create_and_resolve, type: :submit - else - = f.submit t('admin.reports.notes.create_and_unresolve'), name: :create_and_unresolve, class: 'button report-note__button' - = f.submit t('admin.reports.notes.create'), class: 'button report-note__button' - -- if @report_history.length > 0 - %h3= t('admin.reports.history') - - %ul - = render @report_history + = f.button :button, t('admin.reports.notes.create_and_unresolve'), name: :create_and_unresolve, type: :submit + = f.button :button, t('admin.reports.notes.create'), type: :submit diff --git a/app/views/stream_entries/_detailed_status.html.haml b/app/views/stream_entries/_detailed_status.html.haml index e1122d5..afc66d1 100644 --- a/app/views/stream_entries/_detailed_status.html.haml +++ b/app/views/stream_entries/_detailed_status.html.haml @@ -22,11 +22,11 @@ - if !status.media_attachments.empty? - if status.media_attachments.first.video? - video = status.media_attachments.first - %div{ data: { component: 'Video', props: Oj.dump(src: video.file.url(:original), preview: video.file.url(:small), sensitive: status.sensitive? && !current_account&.user&.setting_display_sensitive_media, width: 670, height: 380, detailed: true, inline: true) }} + = react_component :video, src: video.file.url(:original), preview: video.file.url(:small), sensitive: status.sensitive? && !current_account&.user&.setting_display_sensitive_media, width: 670, height: 380, detailed: true, inline: true - else - %div{ data: { component: 'MediaGallery', props: Oj.dump(height: 380, sensitive: status.sensitive? && !current_account&.user&.setting_display_sensitive_media, standalone: true, 'autoPlayGif': current_account&.user&.setting_auto_play_gif, 'reduceMotion': current_account&.user&.setting_reduce_motion, media: status.media_attachments.map { |a| ActiveModelSerializers::SerializableResource.new(a, serializer: REST::MediaAttachmentSerializer).as_json }) }} + = react_component :media_gallery, height: 380, sensitive: status.sensitive? && !current_account&.user&.setting_display_sensitive_media, standalone: true, 'autoPlayGif': current_account&.user&.setting_auto_play_gif, 'reduceMotion': current_account&.user&.setting_reduce_motion, media: status.media_attachments.map { |a| ActiveModelSerializers::SerializableResource.new(a, serializer: REST::MediaAttachmentSerializer).as_json } - elsif status.preview_cards.first - %div{ data: { component: 'Card', props: Oj.dump('maxDescription': 160, card: ActiveModelSerializers::SerializableResource.new(status.preview_cards.first, serializer: REST::PreviewCardSerializer).as_json) }} + = react_component :card, 'maxDescription': 160, card: ActiveModelSerializers::SerializableResource.new(status.preview_cards.first, serializer: REST::PreviewCardSerializer).as_json .detailed-status__meta %data.dt-published{ value: status.created_at.to_time.iso8601 } diff --git a/app/views/stream_entries/_simple_status.html.haml b/app/views/stream_entries/_simple_status.html.haml index 9846910..a6f5120 100644 --- a/app/views/stream_entries/_simple_status.html.haml +++ b/app/views/stream_entries/_simple_status.html.haml @@ -23,6 +23,6 @@ - unless status.media_attachments.empty? - if status.media_attachments.first.video? - video = status.media_attachments.first - %div{ data: { component: 'Video', props: Oj.dump(src: video.file.url(:original), preview: video.file.url(:small), sensitive: status.sensitive? && !current_account&.user&.setting_display_sensitive_media, width: 610, height: 343, inline: true) }} + = react_component :video, src: video.file.url(:original), preview: video.file.url(:small), sensitive: status.sensitive? && !current_account&.user&.setting_display_sensitive_media, width: 610, height: 343, inline: true - else - %div{ data: { component: 'MediaGallery', props: Oj.dump(height: 343, sensitive: status.sensitive? && !current_account&.user&.setting_display_sensitive_media, 'autoPlayGif': current_account&.user&.setting_auto_play_gif, media: status.media_attachments.map { |a| ActiveModelSerializers::SerializableResource.new(a, serializer: REST::MediaAttachmentSerializer).as_json }) }} + = react_component :media_gallery, height: 343, sensitive: status.sensitive? && !current_account&.user&.setting_display_sensitive_media, 'autoPlayGif': current_account&.user&.setting_auto_play_gif, media: status.media_attachments.map { |a| ActiveModelSerializers::SerializableResource.new(a, serializer: REST::MediaAttachmentSerializer).as_json } diff --git a/config/locales/ar.yml b/config/locales/ar.yml index 8b9a668..b3cb71d 100644 --- a/config/locales/ar.yml +++ b/config/locales/ar.yml @@ -240,7 +240,6 @@ ar: action_taken_by: تم اتخاذ الإجراء مِن طرف are_you_sure: هل أنت متأكد ؟ comment: - label: تعليق none: لا شيء delete: حذف id: معرّف ID diff --git a/config/locales/ca.yml b/config/locales/ca.yml index fc30b36..c9173cd 100644 --- a/config/locales/ca.yml +++ b/config/locales/ca.yml @@ -243,7 +243,6 @@ ca: action_taken_by: Mesures adoptades per are_you_sure: N'estàs segur? comment: - label: Comentari none: Cap delete: Suprimeix id: ID diff --git a/config/locales/de.yml b/config/locales/de.yml index 6233d29..51936aa 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -243,7 +243,6 @@ de: action_taken_by: Maßnahme ergriffen durch are_you_sure: Bist du dir sicher? comment: - label: Kommentar none: Kein delete: Löschen id: ID diff --git a/config/locales/en.yml b/config/locales/en.yml index 20bfd0f..53b64a1 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -260,40 +260,29 @@ en: destroyed_msg: Report note successfully deleted! reports: account: - created_reports: Reports created by this account - moderation: - silenced: Silenced - suspended: Suspended - title: Moderation - moderation_notes: Moderation Notes note: note report: report - targeted_reports: Reports made about this account action_taken_by: Action taken by are_you_sure: Are you sure? assign_to_self: Assign to me - assigned: Assigned Moderator + assigned: Assigned moderator comment: - label: Report Comment none: None created_at: Reported delete: Delete - history: Moderation History id: ID mark_as_resolved: Mark as resolved mark_as_unresolved: Mark as unresolved notes: - create: Add Note - create_and_resolve: Resolve with Note - create_and_unresolve: Reopen with Note + create: Add note + create_and_resolve: Resolve with note + create_and_unresolve: Reopen with note delete: Delete - label: Moderator Notes - new_label: Add Moderator Note placeholder: Describe what actions have been taken, or any other updates to this report… nsfw: 'false': Unhide media attachments 'true': Hide media attachments - reopen: Reopen Report + reopen: Reopen report report: 'Report #%{id}' report_contents: Contents reported_account: Reported account @@ -302,7 +291,6 @@ en: resolved_msg: Report successfully resolved! silence_account: Silence account status: Status - statuses: Reported Toots suspend_account: Suspend account target: Target title: Reports @@ -366,8 +354,8 @@ en: back_to_account: Back to account page batch: delete: Delete - nsfw_off: NSFW OFF - nsfw_on: NSFW ON + nsfw_off: Mark as not sensitive + nsfw_on: Mark as sensitive execute: Execute failed_to_execute: Failed to execute media: diff --git a/config/locales/eo.yml b/config/locales/eo.yml index 27c62f8..c768d8a 100644 --- a/config/locales/eo.yml +++ b/config/locales/eo.yml @@ -243,7 +243,6 @@ eo: action_taken_by: Ago farita de are_you_sure: Ĉu vi certas? comment: - label: Komento none: Nenio delete: Forigi id: ID diff --git a/config/locales/es.yml b/config/locales/es.yml index a5a20aa..3143add 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -243,7 +243,6 @@ es: action_taken_by: Acción tomada por are_you_sure: "¿Estás seguro?" comment: - label: Comentario none: Ninguno delete: Eliminar id: ID diff --git a/config/locales/fa.yml b/config/locales/fa.yml index ed25ea8..a300554 100644 --- a/config/locales/fa.yml +++ b/config/locales/fa.yml @@ -243,7 +243,6 @@ fa: action_taken_by: انجام‌دهنده are_you_sure: آیا مطمئن هستید؟ comment: - label: توضیح none: خالی delete: پاک‌کردن id: شناسه diff --git a/config/locales/fi.yml b/config/locales/fi.yml index 62f6560..550ad18 100644 --- a/config/locales/fi.yml +++ b/config/locales/fi.yml @@ -243,7 +243,6 @@ fi: action_taken_by: Toimenpiteen tekijä are_you_sure: Oletko varma? comment: - label: Kommentti none: Ei mitään delete: Poista id: Tunniste diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 4571cc3..ebe5793 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -243,7 +243,6 @@ fr: action_taken_by: Intervention de are_you_sure: Êtes vous certain⋅e ? comment: - label: Commentaire none: Aucun delete: Supprimer id: ID diff --git a/config/locales/gl.yml b/config/locales/gl.yml index f4ca7e8..89de27a 100644 --- a/config/locales/gl.yml +++ b/config/locales/gl.yml @@ -243,7 +243,6 @@ gl: action_taken_by: Acción tomada por are_you_sure: Está segura? comment: - label: Comentario none: Nada delete: Eliminar id: ID diff --git a/config/locales/he.yml b/config/locales/he.yml index 1a7c84d..d641c6e 100644 --- a/config/locales/he.yml +++ b/config/locales/he.yml @@ -180,7 +180,6 @@ he: reports: are_you_sure: 100% על בטוח? comment: - label: הערה none: ללא delete: מחיקה id: ID diff --git a/config/locales/hu.yml b/config/locales/hu.yml index 2560b38..7fe431d 100644 --- a/config/locales/hu.yml +++ b/config/locales/hu.yml @@ -243,7 +243,6 @@ hu: action_taken_by: 'Kezelte:' are_you_sure: Biztos vagy benne? comment: - label: Hozzászólás none: Egyik sem delete: Törlés id: ID diff --git a/config/locales/id.yml b/config/locales/id.yml index 0ef1d50..5a63b80 100644 --- a/config/locales/id.yml +++ b/config/locales/id.yml @@ -106,7 +106,6 @@ id: title: Server yang diketahui reports: comment: - label: Komentar none: Tidak ada delete: Hapus id: ID diff --git a/config/locales/io.yml b/config/locales/io.yml index 29ab451..7c25acc 100644 --- a/config/locales/io.yml +++ b/config/locales/io.yml @@ -105,7 +105,6 @@ io: title: Known Instances reports: comment: - label: Comment none: None delete: Delete id: ID diff --git a/config/locales/ja.yml b/config/locales/ja.yml index fecf996..b23c027 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -264,11 +264,9 @@ ja: assign_to_self: 担当になる assigned: 担当者 comment: - label: コメント none: なし created_at: レポート日時 delete: 削除 - history: モデレーション履歴 id: ID mark_as_resolved: 解決済みとしてマーク mark_as_unresolved: 未解決として再び開く @@ -277,8 +275,6 @@ ja: create_and_resolve: 書き込み、解決済みにする create_and_unresolve: 書き込み、未解決として開く delete: 削除 - label: モデレーターメモ - new_label: モデレーターメモの追加 placeholder: このレポートに取られた措置やその他更新を記述してください nsfw: 'false': NSFW オフ @@ -292,7 +288,6 @@ ja: resolved_msg: レポートを解決済みにしました! silence_account: アカウントをサイレンス status: ステータス - statuses: 通報されたトゥート suspend_account: アカウントを停止 target: ターゲット title: レポート diff --git a/config/locales/ko.yml b/config/locales/ko.yml index 72c98bc..3470085 100644 --- a/config/locales/ko.yml +++ b/config/locales/ko.yml @@ -245,7 +245,6 @@ ko: action_taken_by: 신고 처리자 are_you_sure: 정말로 실행하시겠습니까? comment: - label: 코멘트 none: 없음 delete: 삭제 id: ID diff --git a/config/locales/nl.yml b/config/locales/nl.yml index a46bb72..2342ef6 100644 --- a/config/locales/nl.yml +++ b/config/locales/nl.yml @@ -243,7 +243,6 @@ nl: action_taken_by: Actie uitgevoerd door are_you_sure: Weet je het zeker? comment: - label: Opmerking none: Geen delete: Verwijderen id: ID diff --git a/config/locales/no.yml b/config/locales/no.yml index d5edb39..8b84182 100644 --- a/config/locales/no.yml +++ b/config/locales/no.yml @@ -243,7 +243,6 @@ action_taken_by: Handling utført av are_you_sure: Er du sikker? comment: - label: Kommentar none: Ingen delete: Slett id: ID diff --git a/config/locales/oc.yml b/config/locales/oc.yml index f8e819c..195a1d9 100644 --- a/config/locales/oc.yml +++ b/config/locales/oc.yml @@ -243,7 +243,6 @@ oc: action_taken_by: Mesura menada per are_you_sure: Es segur ? comment: - label: Comentari none: Pas cap delete: Suprimir id: ID diff --git a/config/locales/pl.yml b/config/locales/pl.yml index faa5494..839767c 100644 --- a/config/locales/pl.yml +++ b/config/locales/pl.yml @@ -261,25 +261,16 @@ pl: destroyed_msg: Pomyślnie usunięto notatkę moderacyjną. reports: account: - created_reports: Zgłoszenia utworzone z tego konta - moderation: - silenced: Wyciszone - suspended: Zawieszone - title: Moderacja - moderation_notes: Notatki moderacyjne note: notatka report: zgłoszenie - targeted_reports: Zgłoszenia dotycząće tego konta action_taken_by: Działanie podjęte przez are_you_sure: Czy na pewno? assign_to_self: Przypisz do siebie assigned: Przypisany moderator comment: - label: Komentarz do zgłoszenia none: Brak created_at: Zgłoszono delete: Usuń - history: Historia moderacji id: ID mark_as_resolved: Oznacz jako rozwiązane mark_as_unresolved: Oznacz jako nierozwiązane @@ -288,8 +279,6 @@ pl: create_and_resolve: Rozwiąż i pozostaw notatkę create_and_unresolve: Cofnij rozwiązanie i pozostaw notatkę delete: Usuń - label: Notatki - new_label: Dodaj notatkę moderacyjną placeholder: Opisz wykonane akcje i inne szczegóły dotyczące tego zgłoszenia… nsfw: 'false': Nie oznaczaj jako NSFW @@ -303,7 +292,6 @@ pl: resolved_msg: Pomyślnie rozwiązano zgłoszenie. silence_account: Wycisz konto status: Stan - statuses: Zgłoszone wpisy suspend_account: Zawieś konto target: Cel title: Zgłoszenia diff --git a/config/locales/pt-BR.yml b/config/locales/pt-BR.yml index d6f463a..d3c1d53 100644 --- a/config/locales/pt-BR.yml +++ b/config/locales/pt-BR.yml @@ -243,7 +243,6 @@ pt-BR: action_taken_by: Ação realizada por are_you_sure: Você tem certeza? comment: - label: Comentário none: Nenhum delete: Excluir id: ID diff --git a/config/locales/pt.yml b/config/locales/pt.yml index 27d4e88..fb2a6ca 100644 --- a/config/locales/pt.yml +++ b/config/locales/pt.yml @@ -243,7 +243,6 @@ pt: action_taken_by: Ação tomada por are_you_sure: Tens a certeza? comment: - label: Comentário none: Nenhum delete: Eliminar id: ID diff --git a/config/locales/ru.yml b/config/locales/ru.yml index 176ace9..bf42257 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -245,7 +245,6 @@ ru: action_taken_by: 'Действие предпринято:' are_you_sure: Вы уверены? comment: - label: Комментарий none: Нет delete: Удалить id: ID diff --git a/config/locales/sk.yml b/config/locales/sk.yml index 25e6726..37f7113 100644 --- a/config/locales/sk.yml +++ b/config/locales/sk.yml @@ -243,7 +243,6 @@ sk: action_taken_by: Zákrok vykonal are_you_sure: Ste si istý/á? comment: - label: Vyjadriť sa none: Žiadne delete: Vymazať id: Identifikácia diff --git a/config/locales/sr-Latn.yml b/config/locales/sr-Latn.yml index 8d39d35..742c976 100644 --- a/config/locales/sr-Latn.yml +++ b/config/locales/sr-Latn.yml @@ -245,7 +245,6 @@ sr-Latn: action_taken_by: Akciju izveo are_you_sure: Da li ste sigurni? comment: - label: Komentar none: Ništa delete: Obriši id: ID diff --git a/config/locales/sr.yml b/config/locales/sr.yml index af4c6a8..0d55910 100644 --- a/config/locales/sr.yml +++ b/config/locales/sr.yml @@ -245,7 +245,6 @@ sr: action_taken_by: Акцију извео are_you_sure: Да ли сте сигурни? comment: - label: Коментар none: Ништа delete: Обриши id: ID diff --git a/config/locales/sv.yml b/config/locales/sv.yml index f85ed6e..7e763c2 100644 --- a/config/locales/sv.yml +++ b/config/locales/sv.yml @@ -243,7 +243,6 @@ sv: action_taken_by: Åtgärder vidtagna av are_you_sure: Är du säker? comment: - label: Kommentar none: Ingen delete: Radera id: ID diff --git a/config/locales/th.yml b/config/locales/th.yml index 45fe1e4..350b93b 100644 --- a/config/locales/th.yml +++ b/config/locales/th.yml @@ -108,7 +108,6 @@ th: title: Known Instances reports: comment: - label: คอมเม้นต์ none: None delete: ลบ id: ไอดี diff --git a/config/locales/tr.yml b/config/locales/tr.yml index ee0e330..6e7aeb7 100644 --- a/config/locales/tr.yml +++ b/config/locales/tr.yml @@ -107,7 +107,6 @@ tr: title: Bilinen Sunucular reports: comment: - label: Yorum none: Yok delete: Sil id: ID diff --git a/config/locales/uk.yml b/config/locales/uk.yml index 4c1c66b..44f64b5 100644 --- a/config/locales/uk.yml +++ b/config/locales/uk.yml @@ -99,7 +99,6 @@ uk: undo: Відмінити reports: comment: - label: Коментар none: Немає delete: Видалити id: ID diff --git a/config/locales/zh-CN.yml b/config/locales/zh-CN.yml index be868e6..78c72bd 100644 --- a/config/locales/zh-CN.yml +++ b/config/locales/zh-CN.yml @@ -241,7 +241,6 @@ zh-CN: action_taken_by: 操作执行者 are_you_sure: 你确定吗? comment: - label: 备注 none: 没有 delete: 删除 id: ID diff --git a/config/locales/zh-HK.yml b/config/locales/zh-HK.yml index 964ff58..a27b0c0 100644 --- a/config/locales/zh-HK.yml +++ b/config/locales/zh-HK.yml @@ -259,25 +259,16 @@ zh-HK: destroyed_msg: 舉報筆記已刪除。 reports: account: - created_reports: 由此帳號發出的舉報 - moderation: - silenced: 被靜音的 - suspended: 被停權的 - title: 管理操作 - moderation_notes: 管理筆記 note: 筆記 report: 舉報 - targeted_reports: 關於此帳號的舉報 action_taken_by: 操作執行者 are_you_sure: 你確認嗎? assign_to_self: 指派給自己 assigned: 指派負責人 comment: - label: 詳細解釋 none: 沒有 created_at: 日期 delete: 刪除 - history: 執行紀錄 id: ID mark_as_resolved: 標示為「已處理」 mark_as_unresolved: 標示為「未處理」 @@ -286,8 +277,6 @@ zh-HK: create_and_resolve: 建立筆記並標示為「已處理」 create_and_unresolve: 建立筆記並標示為「未處理」 delete: 刪除 - label: 管理筆記 - new_label: 建立管理筆記 placeholder: 記錄已執行的動作,或其他更新 nsfw: 'false': 取消 NSFW 標記 @@ -301,7 +290,6 @@ zh-HK: resolved_msg: 舉報已處理。 silence_account: 將用戶靜音 status: 狀態 - statuses: 被舉報的文章 suspend_account: 將用戶停權 target: 對象 title: 舉報 diff --git a/config/locales/zh-TW.yml b/config/locales/zh-TW.yml index 2fec09e..f69d22d 100644 --- a/config/locales/zh-TW.yml +++ b/config/locales/zh-TW.yml @@ -79,7 +79,6 @@ zh-TW: title: 網域封鎖 reports: comment: - label: 留言 none: 無 delete: 刪除 id: ID diff --git a/spec/controllers/admin/reported_statuses_controller_spec.rb b/spec/controllers/admin/reported_statuses_controller_spec.rb index 297807d..41e032f 100644 --- a/spec/controllers/admin/reported_statuses_controller_spec.rb +++ b/spec/controllers/admin/reported_statuses_controller_spec.rb @@ -13,7 +13,7 @@ describe Admin::ReportedStatusesController do describe 'POST #create' do subject do - -> { post :create, params: { report_id: report, form_status_batch: { action: action, status_ids: status_ids } } } + -> { post :create, params: { :report_id => report, action => '', :form_status_batch => { status_ids: status_ids } } } end let(:action) { 'nsfw_on' } From 084cf0babffec9e7bee537fd4f6b2294de6c33dc Mon Sep 17 00:00:00 2001 From: Yamagishi Kazutoshi Date: Fri, 20 Apr 2018 19:21:28 +0900 Subject: [PATCH 153/381] Add extract_foreign_key_action to Mastodon::MigrationHelpers (#7195) --- lib/mastodon/migration_helpers.rb | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/lib/mastodon/migration_helpers.rb b/lib/mastodon/migration_helpers.rb index 6f6f99f..e154b5a 100644 --- a/lib/mastodon/migration_helpers.rb +++ b/lib/mastodon/migration_helpers.rb @@ -985,6 +985,17 @@ into similar problems in the future (e.g. when new tables are created). BackgroundMigrationWorker.perform_in(delay_interval * index, job_class_name, [start_id, end_id]) end end + + private + + # https://github.com/rails/rails/blob/v5.2.0/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb#L678-L684 + def extract_foreign_key_action(specifier) + case specifier + when 'c'; :cascade + when 'n'; :nullify + when 'r'; :restrict + end + end end end From 6f63cbb53c6ba7a5e2224c4bf846ccff1cac6a3d Mon Sep 17 00:00:00 2001 From: Yamagishi Kazutoshi Date: Fri, 20 Apr 2018 20:46:08 +0900 Subject: [PATCH 154/381] Replace Travis to CircleCI (#7196) --- .circleci/config.yml | 169 +++++++++++++++++++++++++++++++++++++++++++++++++++ .travis.yml | 60 ------------------ 2 files changed, 169 insertions(+), 60 deletions(-) create mode 100644 .circleci/config.yml delete mode 100644 .travis.yml diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000..e3a9628 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,169 @@ +version: 2 + +aliases: + - &defaults + docker: + - image: circleci/ruby:2.5.1-stretch-node + environment: &ruby_environment + BUNDLE_APP_CONFIG: ./.bundle/ + RAILS_ENV: test + NODE_ENV: test + DB_HOST: localhost + DB_USER: root + LOCAL_DOMAIN: cb6e6126.ngrok.io + LOCAL_HTTPS: true + PARALLEL_TEST_PROCESSORS: 2 + ALLOW_NOPAM: true + working_directory: ~/projects/mastodon/ + + - &attach_workspace + attach_workspace: + at: ~/projects/ + + - &persist_to_workspace + persist_to_workspace: + root: ~/projects/ + paths: + - ./mastodon/ + + - &install_steps + steps: + - checkout + - *attach_workspace + + - restore_cache: + keys: + - v1-node-dependencies-{{ checksum "yarn.lock" }} + - v1-node-dependencies- + - run: yarn install --frozen-lockfile + - save_cache: + key: v1-node-dependencies-{{ checksum "yarn.lock" }} + paths: + - ./node_modules/ + + - *persist_to_workspace + + - &install_system_dependencies + run: + name: Install system dependencies + command: | + sudo apt-get update + sudo apt-get install -y libicu-dev libidn11-dev libprotobuf-dev protobuf-compiler + + - &install_ruby_dependencies + steps: + - *attach_workspace + + - *install_system_dependencies + + - run: ruby -e 'puts RUBY_VERSION' | tee /tmp/.ruby-version + - restore_cache: + keys: + - v1-ruby-dependencies-{{ checksum "/tmp/.ruby-version" }}-{{ checksum "Gemfile.lock" }} + - v1-ruby-dependencies-{{ checksum "/tmp/.ruby-version" }}- + - v1-ruby-dependencies-- + - run: bundle install --clean --jobs 16 --path ./vendor/bundle/ --retry 3 --with pam_authentication --without development production + - save_cache: + key: v1-ruby-dependencies-{{ checksum "/tmp/.ruby-version" }}-{{ checksum "Gemfile.lock" }} + paths: + - ./vendor/bundle/ + + - run: + name: Precompile Assets + command: | + if [ ! -d ./public/assets/ -o ! -d ./public/packs-test/ ]; then + ./bin/rails assets:precompile + fi + + - *persist_to_workspace + + - &test_steps + steps: + - *attach_workspace + + - *install_system_dependencies + - run: sudo apt-get install -y ffmpeg + + - run: + name: Prepare Tests + command: ./bin/rails parallel:create parallel:load_schema parallel:prepare + - run: + name: Run Tests + command: bundle exec parallel_test ./spec/ --group-by filesize --type rspec + +jobs: + install: + <<: *defaults + <<: *install_steps + + install-ruby2.5: + <<: *defaults + <<: *install_ruby_dependencies + + install-ruby2.4: + <<: *defaults + docker: + - image: circleci/ruby:2.4.4-stretch-node + environment: *ruby_environment + <<: *install_ruby_dependencies + + test-ruby2.5: + <<: *defaults + docker: + - image: circleci/ruby:2.5.1-stretch-node + environment: *ruby_environment + - image: circleci/postgres:10.3-alpine + environment: + POSTGRES_USER: root + - image: circleci/redis:4.0.9-alpine + <<: *test_steps + + test-ruby2.4: + <<: *defaults + docker: + - image: circleci/ruby:2.4.4-stretch-node + environment: *ruby_environment + - image: circleci/postgres:10.3-alpine + environment: + POSTGRES_USER: root + - image: circleci/redis:4.0.9-alpine + <<: *test_steps + + test-webui: + <<: *defaults + docker: + - image: circleci/node:8.11.1-stretch + steps: + - *attach_workspace + - run: yarn test:jest + + check-i18n: + <<: *defaults + steps: + - *attach_workspace + - run: bundle exec i18n-tasks check-normalized + - run: bundle exec i18n-tasks unused + +workflows: + version: 2 + build-and-test: + jobs: + - install + - install-ruby2.5: + requires: + - install + - install-ruby2.4: + requires: + - install-ruby2.5 + - test-ruby2.5: + requires: + - install-ruby2.5 + - test-ruby2.4: + requires: + - install-ruby2.4 + - test-webui: + requires: + - install + - check-i18n: + requires: + - install-ruby2.5 diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 2addd9b..0000000 --- a/.travis.yml +++ /dev/null @@ -1,60 +0,0 @@ -language: ruby -cache: - bundler: true - yarn: true - directories: - - node_modules - - public/assets - - public/packs-test - - tmp/cache/babel-loader -dist: trusty -sudo: false -branches: - only: - - master - -notifications: - email: false - -env: - global: - - LOCAL_DOMAIN=cb6e6126.ngrok.io - - LOCAL_HTTPS=true - - RAILS_ENV=test - - NOKOGIRI_USE_SYSTEM_LIBRARIES=true - - PARALLEL_TEST_PROCESSORS=2 - - ALLOW_NOPAM=true - -addons: - postgresql: 9.4 - apt: - sources: - - trusty-media - - sourceline: deb https://dl.yarnpkg.com/debian/ stable main - key_url: https://dl.yarnpkg.com/debian/pubkey.gpg - packages: - - ffmpeg - - libicu-dev - - libprotobuf-dev - - protobuf-compiler - - yarn - -rvm: - - 2.4.3 - - 2.5.0 - -services: - - redis-server - -install: - - nvm install - - bundle install --path=vendor/bundle --with pam_authentication --without development production --retry=3 --jobs=16 - - yarn install - -before_script: - - ./bin/rails parallel:create parallel:load_schema parallel:prepare assets:precompile - -script: - - travis_retry bundle exec parallel_test spec/ --group-by filesize --type rspec - - yarn run test:jest - - bundle exec i18n-tasks check-normalized && bundle exec i18n-tasks unused From 4e35ce82691dff9cd81dae2f0e2f566f6f3ef85c Mon Sep 17 00:00:00 2001 From: unarist Date: Fri, 20 Apr 2018 21:04:16 +0900 Subject: [PATCH 155/381] Fix Esc hotkey behavior (#7199) This fixes following cases which causes hotkey action accidentally: * hitting Esc key to cancel text composition (mostly in CJK) Although events on cancelling composition are still heavily browser / input method dependent, but this implementation would covers current UI Events spec and some exceptions. * hitting Esc key to close autocomplete suggestions This PR changes to use keydown event instead of keyup event as well as other hotkeys. --- .../mastodon/components/autosuggest_textarea.js | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/app/javascript/mastodon/components/autosuggest_textarea.js b/app/javascript/mastodon/components/autosuggest_textarea.js index 3490419..5474771 100644 --- a/app/javascript/mastodon/components/autosuggest_textarea.js +++ b/app/javascript/mastodon/components/autosuggest_textarea.js @@ -86,7 +86,9 @@ export default class AutosuggestTextarea extends ImmutablePureComponent { switch(e.key) { case 'Escape': - if (!suggestionsHidden) { + if (suggestions.size === 0 || suggestionsHidden) { + document.querySelector('.ui').parentElement.focus(); + } else { e.preventDefault(); this.setState({ suggestionsHidden: true }); } @@ -125,16 +127,6 @@ export default class AutosuggestTextarea extends ImmutablePureComponent { this.props.onKeyDown(e); } - onKeyUp = e => { - if (e.key === 'Escape' && this.state.suggestionsHidden) { - document.querySelector('.ui').parentElement.focus(); - } - - if (this.props.onKeyUp) { - this.props.onKeyUp(e); - } - } - onBlur = () => { this.setState({ suggestionsHidden: true }); } @@ -186,7 +178,7 @@ export default class AutosuggestTextarea extends ImmutablePureComponent { } render () { - const { value, suggestions, disabled, placeholder, autoFocus } = this.props; + const { value, suggestions, disabled, placeholder, onKeyUp, autoFocus } = this.props; const { suggestionsHidden } = this.state; const style = { direction: 'ltr' }; @@ -208,7 +200,7 @@ export default class AutosuggestTextarea extends ImmutablePureComponent { value={value} onChange={this.onChange} onKeyDown={this.onKeyDown} - onKeyUp={this.onKeyUp} + onKeyUp={onKeyUp} onBlur={this.onBlur} onPaste={this.onPaste} style={style} From ee2e0f694a2ebd403834e55192897d5995a20cf4 Mon Sep 17 00:00:00 2001 From: mayaeh Date: Fri, 20 Apr 2018 21:58:33 +0900 Subject: [PATCH 156/381] Fix #6157: boosting own private toots (#7200) * Fix boosting own private toots. * Run yarn manage:translations and update Japanese translations. --- app/javascript/mastodon/components/dropdown_menu.js | 2 +- app/javascript/mastodon/components/status_action_bar.js | 4 +++- app/javascript/mastodon/features/status/components/action_bar.js | 4 +++- app/javascript/mastodon/locales/en.json | 1 + app/javascript/mastodon/locales/ja.json | 9 +++++---- 5 files changed, 13 insertions(+), 7 deletions(-) diff --git a/app/javascript/mastodon/components/dropdown_menu.js b/app/javascript/mastodon/components/dropdown_menu.js index c5c6f73..982d347 100644 --- a/app/javascript/mastodon/components/dropdown_menu.js +++ b/app/javascript/mastodon/components/dropdown_menu.js @@ -63,7 +63,7 @@ class DropdownMenu extends React.PureComponent { if (typeof action === 'function') { e.preventDefault(); - action(); + action(e); } else if (to) { e.preventDefault(); this.context.router.history.push(to); diff --git a/app/javascript/mastodon/components/status_action_bar.js b/app/javascript/mastodon/components/status_action_bar.js index e586255..d605dbc 100644 --- a/app/javascript/mastodon/components/status_action_bar.js +++ b/app/javascript/mastodon/components/status_action_bar.js @@ -153,7 +153,9 @@ export default class StatusActionBar extends ImmutablePureComponent { if (publicStatus) { menu.push({ text: intl.formatMessage(status.get('pinned') ? messages.unpin : messages.pin), action: this.handlePinClick }); } else { - menu.push({ text: intl.formatMessage(status.get('reblog') ? messages.reblog_private : messages.cancel_reblog_private), action: this.handleReblogClick }); + if (status.get('visibility') === 'private') { + menu.push({ text: intl.formatMessage(status.get('reblogged') ? messages.cancel_reblog_private : messages.reblog_private), action: this.handleReblogClick }); + } } menu.push({ text: intl.formatMessage(messages.delete), action: this.handleDeleteClick }); diff --git a/app/javascript/mastodon/features/status/components/action_bar.js b/app/javascript/mastodon/features/status/components/action_bar.js index fc34c8c..bb9b755 100644 --- a/app/javascript/mastodon/features/status/components/action_bar.js +++ b/app/javascript/mastodon/features/status/components/action_bar.js @@ -123,7 +123,9 @@ export default class ActionBar extends React.PureComponent { if (publicStatus) { menu.push({ text: intl.formatMessage(status.get('pinned') ? messages.unpin : messages.pin), action: this.handlePinClick }); } else { - menu.push({ text: intl.formatMessage(status.get('reblog') ? messages.reblog_private : messages.cancel_reblog_private), action: this.handleReblogClick }); + if (status.get('visibility') === 'private') { + menu.push({ text: intl.formatMessage(status.get('reblogged') ? messages.cancel_reblog_private : messages.reblog_private), action: this.handleReblogClick }); + } } menu.push(null); diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json index d8bd7e3..98d6c0d 100644 --- a/app/javascript/mastodon/locales/en.json +++ b/app/javascript/mastodon/locales/en.json @@ -135,6 +135,7 @@ "keyboard_shortcuts.mention": "to mention author", "keyboard_shortcuts.reply": "to reply", "keyboard_shortcuts.search": "to focus search", + "keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW", "keyboard_shortcuts.toot": "to start a brand new toot", "keyboard_shortcuts.unfocus": "to un-focus compose textarea/search", "keyboard_shortcuts.up": "to move up in the list", diff --git a/app/javascript/mastodon/locales/ja.json b/app/javascript/mastodon/locales/ja.json index a4aa06a..23223ca 100644 --- a/app/javascript/mastodon/locales/ja.json +++ b/app/javascript/mastodon/locales/ja.json @@ -101,7 +101,7 @@ "emoji_button.symbols": "記号", "emoji_button.travel": "旅行と場所", "empty_column.community": "ローカルタイムラインはまだ使われていません。何か書いてみましょう!", - "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.", + "empty_column.direct": "ダイレクトメッセージはまだありません。ダイレクトメッセージをやりとりすると、ここに表示されます。", "empty_column.hashtag": "このハッシュタグはまだ使われていません。", "empty_column.home": "まだ誰もフォローしていません。{public}を見に行くか、検索を使って他のユーザーを見つけましょう。", "empty_column.home.public_timeline": "連合タイムライン", @@ -135,6 +135,7 @@ "keyboard_shortcuts.mention": "メンション", "keyboard_shortcuts.reply": "返信", "keyboard_shortcuts.search": "検索欄に移動", + "keyboard_shortcuts.toggle_hidden": "CWで隠れた文を見る/隠す", "keyboard_shortcuts.toot": "新規トゥート", "keyboard_shortcuts.unfocus": "トゥート入力欄・検索欄から離れる", "keyboard_shortcuts.up": "カラム内一つ上に移動", @@ -156,7 +157,7 @@ "mute_modal.hide_notifications": "このユーザーからの通知を隠しますか?", "navigation_bar.blocks": "ブロックしたユーザー", "navigation_bar.community_timeline": "ローカルタイムライン", - "navigation_bar.direct": "Direct messages", + "navigation_bar.direct": "ダイレクトメッセージ", "navigation_bar.domain_blocks": "非表示にしたドメイン", "navigation_bar.edit_profile": "プロフィールを編集", "navigation_bar.favourites": "お気に入り", @@ -241,7 +242,7 @@ "search_results.total": "{count, number}件の結果", "standalone.public_title": "今こんな話をしています...", "status.block": "@{name}さんをブロック", - "status.cancel_reblog_private": "Unboost", + "status.cancel_reblog_private": "ブースト解除", "status.cannot_reblog": "この投稿はブーストできません", "status.delete": "削除", "status.direct": "@{name}さんにダイレクトメッセージ", @@ -257,7 +258,7 @@ "status.pin": "プロフィールに固定表示", "status.pinned": "固定されたトゥート", "status.reblog": "ブースト", - "status.reblog_private": "Boost to original audience", + "status.reblog_private": "ブースト", "status.reblogged_by": "{name}さんがブースト", "status.reply": "返信", "status.replyAll": "全員に返信", From 23106844a10606dd0e04c8382281d5ff80d4bdd9 Mon Sep 17 00:00:00 2001 From: TakesxiSximada Date: Sat, 21 Apr 2018 01:14:21 +0900 Subject: [PATCH 157/381] Fix the hot key (j, k) does not function correctly when there is a pinned toot in account timeline. (#7202) * Fix the hot key (j, k) does not function correctly when there is a pinned toot in account timeline. * Fix typo * Add custom attribute prefix --- app/javascript/mastodon/components/status.js | 10 +++++----- app/javascript/mastodon/components/status_list.js | 20 ++++++++++++++++---- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/app/javascript/mastodon/components/status.js b/app/javascript/mastodon/components/status.js index e5f7c93..402d558 100644 --- a/app/javascript/mastodon/components/status.js +++ b/app/javascript/mastodon/components/status.js @@ -114,12 +114,12 @@ export default class Status extends ImmutablePureComponent { this.context.router.history.push(`/accounts/${this._properStatus().getIn(['account', 'id'])}`); } - handleHotkeyMoveUp = () => { - this.props.onMoveUp(this.props.status.get('id')); + handleHotkeyMoveUp = e => { + this.props.onMoveUp(this.props.status.get('id'), e.target.getAttribute('data-featured')); } - handleHotkeyMoveDown = () => { - this.props.onMoveDown(this.props.status.get('id')); + handleHotkeyMoveDown = e => { + this.props.onMoveDown(this.props.status.get('id'), e.target.getAttribute('data-featured')); } handleHotkeyToggleHidden = () => { @@ -233,7 +233,7 @@ export default class Status extends ImmutablePureComponent { return ( -
    +
    {prepend}
    diff --git a/app/javascript/mastodon/components/status_list.js b/app/javascript/mastodon/components/status_list.js index c98d456..0c971ce 100644 --- a/app/javascript/mastodon/components/status_list.js +++ b/app/javascript/mastodon/components/status_list.js @@ -30,13 +30,25 @@ export default class StatusList extends ImmutablePureComponent { trackScroll: true, }; - handleMoveUp = id => { - const elementIndex = this.props.statusIds.indexOf(id) - 1; + getFeaturedStatusCount = () => { + return this.props.featuredStatusIds ? this.props.featuredStatusIds.size : 0; + } + + getCurrentStatusIndex = (id, featured) => { + if (featured) { + return this.props.featuredStatusIds.indexOf(id); + } else { + return this.props.statusIds.indexOf(id) + this.getFeaturedStatusCount(); + } + } + + handleMoveUp = (id, featured) => { + const elementIndex = this.getCurrentStatusIndex(id, featured) - 1; this._selectChild(elementIndex); } - handleMoveDown = id => { - const elementIndex = this.props.statusIds.indexOf(id) + 1; + handleMoveDown = (id, featured) => { + const elementIndex = this.getCurrentStatusIndex(id, featured) + 1; this._selectChild(elementIndex); } From 87e3f0a41d2a2d223c663365a199c01989afc8ed Mon Sep 17 00:00:00 2001 From: Yamagishi Kazutoshi Date: Sat, 21 Apr 2018 01:14:31 +0900 Subject: [PATCH 158/381] Fix spec for sr-Latn (#7203) --- app/controllers/concerns/localized.rb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/controllers/concerns/localized.rb b/app/controllers/concerns/localized.rb index abd85ea..145549b 100644 --- a/app/controllers/concerns/localized.rb +++ b/app/controllers/concerns/localized.rb @@ -29,10 +29,14 @@ module Localized end def preferred_locale - http_accept_language.preferred_language_from(I18n.available_locales) + http_accept_language.preferred_language_from(available_locales) end def compatible_locale - http_accept_language.compatible_language_from(I18n.available_locales) + http_accept_language.compatible_language_from(available_locales) + end + + def available_locales + I18n.available_locales.reverse end end From 84214b864c63aee08357a719ab386b8e4ed5b901 Mon Sep 17 00:00:00 2001 From: unarist Date: Sat, 21 Apr 2018 01:36:52 +0900 Subject: [PATCH 159/381] Ignore keyevents during text composition (#7205) KeyboardEvent.key may be physical key name (Escape, Tab, etc.) even in text composition and it causes hotkeys or suggestion selection. So we need to check e.which or e.isComposing. Checking e.which also allows us to avoid Esc key on compositionend in Safari. --- app/javascript/mastodon/components/autosuggest_textarea.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/javascript/mastodon/components/autosuggest_textarea.js b/app/javascript/mastodon/components/autosuggest_textarea.js index 5474771..a4f5cf5 100644 --- a/app/javascript/mastodon/components/autosuggest_textarea.js +++ b/app/javascript/mastodon/components/autosuggest_textarea.js @@ -84,6 +84,12 @@ export default class AutosuggestTextarea extends ImmutablePureComponent { return; } + if (e.which === 229 || e.isComposing) { + // Ignore key events during text composition + // e.key may be a name of the physical key even in this case (e.x. Safari / Chrome on Mac) + return; + } + switch(e.key) { case 'Escape': if (suggestions.size === 0 || suggestionsHidden) { From b4382247515728521275002643e4d1b7360bf7fb Mon Sep 17 00:00:00 2001 From: Yamagishi Kazutoshi Date: Sat, 21 Apr 2018 02:31:30 +0900 Subject: [PATCH 160/381] Introduce rspec-retry (#7206) --- Gemfile | 1 + Gemfile.lock | 3 +++ spec/spec_helper.rb | 8 ++++++++ 3 files changed, 12 insertions(+) diff --git a/Gemfile b/Gemfile index efafe29..8055f15 100644 --- a/Gemfile +++ b/Gemfile @@ -111,6 +111,7 @@ group :test do gem 'microformats', '~> 4.0' gem 'rails-controller-testing', '~> 1.0' gem 'rspec-sidekiq', '~> 3.0' + gem 'rspec-retry', '~> 0.5', require: false gem 'simplecov', '~> 0.14', require: false gem 'webmock', '~> 3.3' gem 'parallel_tests', '~> 2.21' diff --git a/Gemfile.lock b/Gemfile.lock index e799533..eeb4bf1 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -495,6 +495,8 @@ GEM rspec-expectations (~> 3.7.0) rspec-mocks (~> 3.7.0) rspec-support (~> 3.7.0) + rspec-retry (0.5.7) + rspec-core (> 3.3) rspec-sidekiq (3.0.3) rspec-core (~> 3.0, >= 3.0.0) sidekiq (>= 2.4.0) @@ -715,6 +717,7 @@ DEPENDENCIES redis-rails (~> 5.0) rqrcode (~> 0.10) rspec-rails (~> 3.7) + rspec-retry (~> 0.5) rspec-sidekiq (~> 3.0) rubocop ruby-oembed (~> 0.12) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index a0466dd..66ac75e 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,3 +1,4 @@ +require 'rspec/retry' require 'simplecov' GC.disable @@ -11,6 +12,9 @@ end gc_counter = -1 RSpec.configure do |config| + config.verbose_retry = true + config.display_try_failure_messages = true + config.expect_with :rspec do |expectations| expectations.include_chain_clauses_in_custom_matcher_descriptions = true end @@ -25,6 +29,10 @@ RSpec.configure do |config| end end + config.around :each do |ex| + ex.run_with_retry retry: 3 + end + config.before :suite do Chewy.strategy(:bypass) end From 1a27f9f46fb751fa4b3bff9aff3d712bb04b1481 Mon Sep 17 00:00:00 2001 From: goofy-bz Date: Sat, 21 Apr 2018 20:07:25 +0200 Subject: [PATCH 161/381] one grammar fix (#7212) --- app/javascript/mastodon/locales/fr.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/javascript/mastodon/locales/fr.json b/app/javascript/mastodon/locales/fr.json index 98c1c43..9a9e8ab 100644 --- a/app/javascript/mastodon/locales/fr.json +++ b/app/javascript/mastodon/locales/fr.json @@ -105,7 +105,7 @@ "empty_column.hashtag": "Il n’y a encore aucun contenu associé à ce hashtag.", "empty_column.home": "Vous ne suivez encore personne. Visitez {public} ou bien utilisez la recherche pour vous connecter à d’autres personnes.", "empty_column.home.public_timeline": "le fil public", - "empty_column.list": "Il n'y a rien dans cette liste pour l'instant. Dès que des personnes de cette liste publierons de nouveaux statuts, ils apparaîtront ici.", + "empty_column.list": "Il n'y a rien dans cette liste pour l'instant. Dès que des personnes de cette liste publieront de nouveaux statuts, ils apparaîtront ici.", "empty_column.notifications": "Vous n’avez pas encore de notification. Interagissez avec d’autres personnes pour débuter la conversation.", "empty_column.public": "Il n’y a rien ici ! Écrivez quelque chose publiquement, ou bien suivez manuellement des personnes d’autres instances pour remplir le fil public", "follow_request.authorize": "Accepter", From bfe26ef67b6ee2ce4a4ca05565cd383074de3a8b Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Sat, 21 Apr 2018 21:34:36 +0200 Subject: [PATCH 162/381] Force convert to JPG for preview card thumbnails to avoid animations (#7109) * Force convert to JPG for preview card thumbnails to avoid animations Fix #7093 * Conditionally convert to JPG only if original is GIF Coalesce and strip on all formats to ensure no animated APNGs --- app/models/preview_card.rb | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/app/models/preview_card.rb b/app/models/preview_card.rb index 0c82f06..0ffa6b1 100644 --- a/app/models/preview_card.rb +++ b/app/models/preview_card.rb @@ -34,7 +34,7 @@ class PreviewCard < ApplicationRecord has_and_belongs_to_many :statuses - has_attached_file :image, styles: { original: { geometry: '400x400>', file_geometry_parser: FastGeometryParser } }, convert_options: { all: '-quality 80 -strip' } + has_attached_file :image, styles: ->(f) { image_styles(f) }, convert_options: { all: '-quality 80 -strip' } include Attachmentable @@ -52,6 +52,23 @@ class PreviewCard < ApplicationRecord save! end + class << self + private + + def image_styles(f) + styles = { + original: { + geometry: '400x400>', + file_geometry_parser: FastGeometryParser, + convert_options: '-coalesce -strip', + }, + } + + styles[:original][:format] = 'jpg' if f.instance.image_content_type == 'image/gif' + styles + end + end + private def extract_dimensions From d10447c3a82d771f8ab61837128b011254894694 Mon Sep 17 00:00:00 2001 From: Yamagishi Kazutoshi Date: Sun, 22 Apr 2018 04:35:07 +0900 Subject: [PATCH 163/381] Use raw status code on have_http_status (#7214) --- spec/controllers/about_controller_spec.rb | 6 +++--- spec/controllers/accounts_controller_spec.rb | 2 +- .../activitypub/outboxes_controller_spec.rb | 2 +- spec/controllers/admin/accounts_controller_spec.rb | 4 ++-- .../admin/change_email_controller_spec.rb | 2 +- .../admin/confirmations_controller_spec.rb | 4 ++-- .../admin/domain_blocks_controller_spec.rb | 6 +++--- .../admin/email_domain_blocks_controller_spec.rb | 4 ++-- spec/controllers/admin/instances_controller_spec.rb | 2 +- .../admin/reported_statuses_controller_spec.rb | 2 +- spec/controllers/admin/reports_controller_spec.rb | 8 ++++---- spec/controllers/admin/settings_controller_spec.rb | 2 +- spec/controllers/admin/statuses_controller_spec.rb | 6 +++--- .../admin/subscriptions_controller_spec.rb | 2 +- spec/controllers/api/base_controller_spec.rb | 2 +- spec/controllers/api/oembed_controller_spec.rb | 2 +- spec/controllers/api/push_controller_spec.rb | 4 ++-- spec/controllers/api/salmon_controller_spec.rb | 2 +- .../controllers/api/subscriptions_controller_spec.rb | 6 +++--- .../api/v1/accounts/credentials_controller_spec.rb | 4 ++-- .../v1/accounts/follower_accounts_controller_spec.rb | 2 +- .../accounts/following_accounts_controller_spec.rb | 2 +- .../api/v1/accounts/lists_controller_spec.rb | 2 +- .../api/v1/accounts/relationships_controller_spec.rb | 4 ++-- .../api/v1/accounts/search_controller_spec.rb | 2 +- .../api/v1/accounts/statuses_controller_spec.rb | 8 ++++---- spec/controllers/api/v1/accounts_controller_spec.rb | 18 +++++++++--------- .../api/v1/apps/credentials_controller_spec.rb | 2 +- spec/controllers/api/v1/apps_controller_spec.rb | 2 +- spec/controllers/api/v1/blocks_controller_spec.rb | 2 +- .../api/v1/custom_emojis_controller_spec.rb | 2 +- .../api/v1/domain_blocks_controller_spec.rb | 6 +++--- .../api/v1/follow_requests_controller_spec.rb | 6 +++--- spec/controllers/api/v1/follows_controller_spec.rb | 4 ++-- spec/controllers/api/v1/instances_controller_spec.rb | 2 +- .../api/v1/lists/accounts_controller_spec.rb | 6 +++--- spec/controllers/api/v1/lists_controller_spec.rb | 10 +++++----- spec/controllers/api/v1/media_controller_spec.rb | 8 ++++---- spec/controllers/api/v1/mutes_controller_spec.rb | 2 +- .../api/v1/notifications_controller_spec.rb | 10 +++++----- spec/controllers/api/v1/reports_controller_spec.rb | 4 ++-- spec/controllers/api/v1/search_controller_spec.rb | 2 +- .../favourited_by_accounts_controller_spec.rb | 6 +++--- .../api/v1/statuses/favourites_controller_spec.rb | 4 ++-- .../api/v1/statuses/mutes_controller_spec.rb | 4 ++-- .../api/v1/statuses/pins_controller_spec.rb | 4 ++-- .../reblogged_by_accounts_controller_spec.rb | 6 +++--- .../api/v1/statuses/reblogs_controller_spec.rb | 4 ++-- spec/controllers/api/v1/statuses_controller_spec.rb | 20 ++++++++++---------- .../api/v1/timelines/home_controller_spec.rb | 2 +- .../api/v1/timelines/list_controller_spec.rb | 2 +- .../api/v1/timelines/public_controller_spec.rb | 6 +++--- .../api/v1/timelines/tag_controller_spec.rb | 4 ++-- spec/controllers/api/web/settings_controller_spec.rb | 2 +- spec/controllers/application_controller_spec.rb | 6 +++--- .../auth/confirmations_controller_spec.rb | 2 +- spec/controllers/auth/passwords_controller_spec.rb | 4 ++-- .../auth/registrations_controller_spec.rb | 6 +++--- spec/controllers/auth/sessions_controller_spec.rb | 2 +- .../controllers/authorize_follows_controller_spec.rb | 4 ++-- .../concerns/account_controller_concern_spec.rb | 2 +- .../concerns/export_controller_concern_spec.rb | 2 +- .../controllers/follower_accounts_controller_spec.rb | 2 +- .../following_accounts_controller_spec.rb | 2 +- spec/controllers/manifests_controller_spec.rb | 2 +- spec/controllers/media_controller_spec.rb | 6 +++--- .../oauth/authorizations_controller_spec.rb | 2 +- .../oauth/authorized_applications_controller_spec.rb | 2 +- spec/controllers/remote_follow_controller_spec.rb | 4 ++-- .../settings/applications_controller_spec.rb | 10 +++++----- spec/controllers/settings/deletes_controller_spec.rb | 2 +- spec/controllers/settings/exports_controller_spec.rb | 2 +- .../settings/follower_domains_controller_spec.rb | 2 +- spec/controllers/settings/imports_controller_spec.rb | 2 +- .../settings/notifications_controller_spec.rb | 2 +- .../settings/preferences_controller_spec.rb | 2 +- .../controllers/settings/profiles_controller_spec.rb | 2 +- .../confirmations_controller_spec.rb | 4 ++-- .../recovery_codes_controller_spec.rb | 2 +- .../two_factor_authentications_controller_spec.rb | 4 ++-- spec/controllers/statuses_controller_spec.rb | 2 +- spec/controllers/stream_entries_controller_spec.rb | 2 +- spec/controllers/tags_controller_spec.rb | 4 ++-- .../well_known/host_meta_controller_spec.rb | 2 +- .../well_known/webfinger_controller_spec.rb | 6 +++--- spec/requests/host_meta_request_spec.rb | 2 +- spec/requests/webfinger_request_spec.rb | 10 +++++----- 87 files changed, 176 insertions(+), 176 deletions(-) diff --git a/spec/controllers/about_controller_spec.rb b/spec/controllers/about_controller_spec.rb index c2c34d3..2089b3b 100644 --- a/spec/controllers/about_controller_spec.rb +++ b/spec/controllers/about_controller_spec.rb @@ -17,7 +17,7 @@ RSpec.describe AboutController, type: :controller do end it 'returns http success' do - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end @@ -35,7 +35,7 @@ RSpec.describe AboutController, type: :controller do end it 'returns http success' do - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end @@ -49,7 +49,7 @@ RSpec.describe AboutController, type: :controller do end it 'returns http success' do - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end diff --git a/spec/controllers/accounts_controller_spec.rb b/spec/controllers/accounts_controller_spec.rb index a8ade79..18c249c 100644 --- a/spec/controllers/accounts_controller_spec.rb +++ b/spec/controllers/accounts_controller_spec.rb @@ -40,7 +40,7 @@ RSpec.describe AccountsController, type: :controller do end it 'returns http success' do - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end it 'returns correct format' do diff --git a/spec/controllers/activitypub/outboxes_controller_spec.rb b/spec/controllers/activitypub/outboxes_controller_spec.rb index a259980..47460b2 100644 --- a/spec/controllers/activitypub/outboxes_controller_spec.rb +++ b/spec/controllers/activitypub/outboxes_controller_spec.rb @@ -13,7 +13,7 @@ RSpec.describe ActivityPub::OutboxesController, type: :controller do end it 'returns http success' do - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end it 'returns application/activity+json' do diff --git a/spec/controllers/admin/accounts_controller_spec.rb b/spec/controllers/admin/accounts_controller_spec.rb index 8be27d8..ff9dbbf 100644 --- a/spec/controllers/admin/accounts_controller_spec.rb +++ b/spec/controllers/admin/accounts_controller_spec.rb @@ -63,7 +63,7 @@ RSpec.describe Admin::AccountsController, type: :controller do it 'returns http success' do get :index - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end @@ -72,7 +72,7 @@ RSpec.describe Admin::AccountsController, type: :controller do it 'returns http success' do get :show, params: { id: account.id } - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end end diff --git a/spec/controllers/admin/change_email_controller_spec.rb b/spec/controllers/admin/change_email_controller_spec.rb index 50f94f8..31df0f0 100644 --- a/spec/controllers/admin/change_email_controller_spec.rb +++ b/spec/controllers/admin/change_email_controller_spec.rb @@ -16,7 +16,7 @@ RSpec.describe Admin::ChangeEmailsController, type: :controller do get :show, params: { account_id: account.id } - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end diff --git a/spec/controllers/admin/confirmations_controller_spec.rb b/spec/controllers/admin/confirmations_controller_spec.rb index 3f2b28c..7c80349 100644 --- a/spec/controllers/admin/confirmations_controller_spec.rb +++ b/spec/controllers/admin/confirmations_controller_spec.rb @@ -20,14 +20,14 @@ RSpec.describe Admin::ConfirmationsController, type: :controller do it 'raises an error when there is no account' do post :create, params: { account_id: 'fake' } - expect(response).to have_http_status(:missing) + expect(response).to have_http_status(404) end it 'raises an error when there is no user' do account = Fabricate(:account, user: nil) post :create, params: { account_id: account.id } - expect(response).to have_http_status(:missing) + expect(response).to have_http_status(404) end end end diff --git a/spec/controllers/admin/domain_blocks_controller_spec.rb b/spec/controllers/admin/domain_blocks_controller_spec.rb index b9e73c0..79e7fea 100644 --- a/spec/controllers/admin/domain_blocks_controller_spec.rb +++ b/spec/controllers/admin/domain_blocks_controller_spec.rb @@ -23,7 +23,7 @@ RSpec.describe Admin::DomainBlocksController, type: :controller do assigned = assigns(:domain_blocks) expect(assigned.count).to eq 1 expect(assigned.klass).to be DomainBlock - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end @@ -32,7 +32,7 @@ RSpec.describe Admin::DomainBlocksController, type: :controller do get :new expect(assigns(:domain_block)).to be_instance_of(DomainBlock) - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end @@ -41,7 +41,7 @@ RSpec.describe Admin::DomainBlocksController, type: :controller do domain_block = Fabricate(:domain_block) get :show, params: { id: domain_block.id } - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end diff --git a/spec/controllers/admin/email_domain_blocks_controller_spec.rb b/spec/controllers/admin/email_domain_blocks_controller_spec.rb index 295de90..133d38f 100644 --- a/spec/controllers/admin/email_domain_blocks_controller_spec.rb +++ b/spec/controllers/admin/email_domain_blocks_controller_spec.rb @@ -25,7 +25,7 @@ RSpec.describe Admin::EmailDomainBlocksController, type: :controller do assigned = assigns(:email_domain_blocks) expect(assigned.count).to eq 1 expect(assigned.klass).to be EmailDomainBlock - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end @@ -34,7 +34,7 @@ RSpec.describe Admin::EmailDomainBlocksController, type: :controller do get :new expect(assigns(:email_domain_block)).to be_instance_of(EmailDomainBlock) - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end diff --git a/spec/controllers/admin/instances_controller_spec.rb b/spec/controllers/admin/instances_controller_spec.rb index f57e3fa..412b814 100644 --- a/spec/controllers/admin/instances_controller_spec.rb +++ b/spec/controllers/admin/instances_controller_spec.rb @@ -26,7 +26,7 @@ RSpec.describe Admin::InstancesController, type: :controller do expect(instances.size).to eq 1 expect(instances[0].domain).to eq 'less.popular' - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end end diff --git a/spec/controllers/admin/reported_statuses_controller_spec.rb b/spec/controllers/admin/reported_statuses_controller_spec.rb index 41e032f..29957ed 100644 --- a/spec/controllers/admin/reported_statuses_controller_spec.rb +++ b/spec/controllers/admin/reported_statuses_controller_spec.rb @@ -84,7 +84,7 @@ describe Admin::ReportedStatusesController do allow(RemovalWorker).to receive(:perform_async) delete :destroy, params: { report_id: report, id: status } - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) expect(RemovalWorker). to have_received(:perform_async).with(status.id) end diff --git a/spec/controllers/admin/reports_controller_spec.rb b/spec/controllers/admin/reports_controller_spec.rb index 9be298d..e50c02a 100644 --- a/spec/controllers/admin/reports_controller_spec.rb +++ b/spec/controllers/admin/reports_controller_spec.rb @@ -18,7 +18,7 @@ describe Admin::ReportsController do reports = assigns(:reports).to_a expect(reports.size).to eq 1 expect(reports[0]).to eq specified - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end it 'returns http success with resolved filter' do @@ -31,7 +31,7 @@ describe Admin::ReportsController do expect(reports.size).to eq 1 expect(reports[0]).to eq specified - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end @@ -42,7 +42,7 @@ describe Admin::ReportsController do get :show, params: { id: report } expect(assigns(:report)).to eq report - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end @@ -52,7 +52,7 @@ describe Admin::ReportsController do report = Fabricate(:report) put :update, params: { id: report, outcome: 'unknown' } - expect(response).to have_http_status(:missing) + expect(response).to have_http_status(404) end end diff --git a/spec/controllers/admin/settings_controller_spec.rb b/spec/controllers/admin/settings_controller_spec.rb index 609bc76..eaf9967 100644 --- a/spec/controllers/admin/settings_controller_spec.rb +++ b/spec/controllers/admin/settings_controller_spec.rb @@ -14,7 +14,7 @@ RSpec.describe Admin::SettingsController, type: :controller do it 'returns http success' do get :edit - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end diff --git a/spec/controllers/admin/statuses_controller_spec.rb b/spec/controllers/admin/statuses_controller_spec.rb index 1515e29..cbaf397 100644 --- a/spec/controllers/admin/statuses_controller_spec.rb +++ b/spec/controllers/admin/statuses_controller_spec.rb @@ -20,7 +20,7 @@ describe Admin::StatusesController do statuses = assigns(:statuses).to_a expect(statuses.size).to eq 2 - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end it 'returns http success with media' do @@ -28,7 +28,7 @@ describe Admin::StatusesController do statuses = assigns(:statuses).to_a expect(statuses.size).to eq 1 - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end @@ -99,7 +99,7 @@ describe Admin::StatusesController do allow(RemovalWorker).to receive(:perform_async) delete :destroy, params: { account_id: account.id, id: status } - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) expect(RemovalWorker). to have_received(:perform_async).with(status.id) end diff --git a/spec/controllers/admin/subscriptions_controller_spec.rb b/spec/controllers/admin/subscriptions_controller_spec.rb index eb6f12b..967152a 100644 --- a/spec/controllers/admin/subscriptions_controller_spec.rb +++ b/spec/controllers/admin/subscriptions_controller_spec.rb @@ -26,7 +26,7 @@ RSpec.describe Admin::SubscriptionsController, type: :controller do expect(subscriptions.count).to eq 1 expect(subscriptions[0]).to eq specified - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end end diff --git a/spec/controllers/api/base_controller_spec.rb b/spec/controllers/api/base_controller_spec.rb index 0c7ca89..750ccc8 100644 --- a/spec/controllers/api/base_controller_spec.rb +++ b/spec/controllers/api/base_controller_spec.rb @@ -23,7 +23,7 @@ describe Api::BaseController do it 'does not protect from forgery' do ActionController::Base.allow_forgery_protection = true post 'success' - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end diff --git a/spec/controllers/api/oembed_controller_spec.rb b/spec/controllers/api/oembed_controller_spec.rb index 7af4a6a..7fee15a 100644 --- a/spec/controllers/api/oembed_controller_spec.rb +++ b/spec/controllers/api/oembed_controller_spec.rb @@ -13,7 +13,7 @@ RSpec.describe Api::OEmbedController, type: :controller do end it 'returns http success' do - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end end diff --git a/spec/controllers/api/push_controller_spec.rb b/spec/controllers/api/push_controller_spec.rb index 647698b..d769d85 100644 --- a/spec/controllers/api/push_controller_spec.rb +++ b/spec/controllers/api/push_controller_spec.rb @@ -23,7 +23,7 @@ RSpec.describe Api::PushController, type: :controller do '3600', nil ) - expect(response).to have_http_status(:success) + expect(response).to have_http_status(202) end end @@ -43,7 +43,7 @@ RSpec.describe Api::PushController, type: :controller do account, 'https://callback.host/api', ) - expect(response).to have_http_status(:success) + expect(response).to have_http_status(202) end end diff --git a/spec/controllers/api/salmon_controller_spec.rb b/spec/controllers/api/salmon_controller_spec.rb index 8af8b83..5f01f80 100644 --- a/spec/controllers/api/salmon_controller_spec.rb +++ b/spec/controllers/api/salmon_controller_spec.rb @@ -24,7 +24,7 @@ RSpec.describe Api::SalmonController, type: :controller do end it 'returns http success' do - expect(response).to have_http_status(:success) + expect(response).to have_http_status(202) end it 'creates remote account' do diff --git a/spec/controllers/api/subscriptions_controller_spec.rb b/spec/controllers/api/subscriptions_controller_spec.rb index d90da9e..48eb1fc 100644 --- a/spec/controllers/api/subscriptions_controller_spec.rb +++ b/spec/controllers/api/subscriptions_controller_spec.rb @@ -12,7 +12,7 @@ RSpec.describe Api::SubscriptionsController, type: :controller do end it 'returns http success' do - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end it 'echoes back the challenge' do @@ -27,7 +27,7 @@ RSpec.describe Api::SubscriptionsController, type: :controller do end it 'returns http success' do - expect(response).to have_http_status(:missing) + expect(response).to have_http_status(404) end end end @@ -59,7 +59,7 @@ RSpec.describe Api::SubscriptionsController, type: :controller do end it 'returns http success' do - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end it 'creates statuses for feed' do diff --git a/spec/controllers/api/v1/accounts/credentials_controller_spec.rb b/spec/controllers/api/v1/accounts/credentials_controller_spec.rb index 87fce64..9a52fd1 100644 --- a/spec/controllers/api/v1/accounts/credentials_controller_spec.rb +++ b/spec/controllers/api/v1/accounts/credentials_controller_spec.rb @@ -14,7 +14,7 @@ describe Api::V1::Accounts::CredentialsController do describe 'GET #show' do it 'returns http success' do get :show - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end @@ -36,7 +36,7 @@ describe Api::V1::Accounts::CredentialsController do end it 'returns http success' do - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end it 'updates account info' do diff --git a/spec/controllers/api/v1/accounts/follower_accounts_controller_spec.rb b/spec/controllers/api/v1/accounts/follower_accounts_controller_spec.rb index 33982cb..b47af49 100644 --- a/spec/controllers/api/v1/accounts/follower_accounts_controller_spec.rb +++ b/spec/controllers/api/v1/accounts/follower_accounts_controller_spec.rb @@ -15,7 +15,7 @@ describe Api::V1::Accounts::FollowerAccountsController do it 'returns http success' do get :index, params: { account_id: user.account.id, limit: 1 } - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end end diff --git a/spec/controllers/api/v1/accounts/following_accounts_controller_spec.rb b/spec/controllers/api/v1/accounts/following_accounts_controller_spec.rb index e22f54a..29fd7cd 100644 --- a/spec/controllers/api/v1/accounts/following_accounts_controller_spec.rb +++ b/spec/controllers/api/v1/accounts/following_accounts_controller_spec.rb @@ -15,7 +15,7 @@ describe Api::V1::Accounts::FollowingAccountsController do it 'returns http success' do get :index, params: { account_id: user.account.id, limit: 1 } - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end end diff --git a/spec/controllers/api/v1/accounts/lists_controller_spec.rb b/spec/controllers/api/v1/accounts/lists_controller_spec.rb index 0a372f6..df9fe0e 100644 --- a/spec/controllers/api/v1/accounts/lists_controller_spec.rb +++ b/spec/controllers/api/v1/accounts/lists_controller_spec.rb @@ -17,7 +17,7 @@ describe Api::V1::Accounts::ListsController do describe 'GET #index' do it 'returns http success' do get :index, params: { account_id: account.id } - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end end diff --git a/spec/controllers/api/v1/accounts/relationships_controller_spec.rb b/spec/controllers/api/v1/accounts/relationships_controller_spec.rb index e0de790..7e350da 100644 --- a/spec/controllers/api/v1/accounts/relationships_controller_spec.rb +++ b/spec/controllers/api/v1/accounts/relationships_controller_spec.rb @@ -25,7 +25,7 @@ describe Api::V1::Accounts::RelationshipsController do end it 'returns http success' do - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end it 'returns JSON with correct data' do @@ -43,7 +43,7 @@ describe Api::V1::Accounts::RelationshipsController do end it 'returns http success' do - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end it 'returns JSON with correct data' do diff --git a/spec/controllers/api/v1/accounts/search_controller_spec.rb b/spec/controllers/api/v1/accounts/search_controller_spec.rb index 42cc3f6..dbc4b9f 100644 --- a/spec/controllers/api/v1/accounts/search_controller_spec.rb +++ b/spec/controllers/api/v1/accounts/search_controller_spec.rb @@ -14,7 +14,7 @@ RSpec.describe Api::V1::Accounts::SearchController, type: :controller do it 'returns http success' do get :show, params: { q: 'query' } - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end end diff --git a/spec/controllers/api/v1/accounts/statuses_controller_spec.rb b/spec/controllers/api/v1/accounts/statuses_controller_spec.rb index c49a77a..09bb469 100644 --- a/spec/controllers/api/v1/accounts/statuses_controller_spec.rb +++ b/spec/controllers/api/v1/accounts/statuses_controller_spec.rb @@ -15,7 +15,7 @@ describe Api::V1::Accounts::StatusesController do it 'returns http success' do get :index, params: { account_id: user.account.id, limit: 1 } - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) expect(response.headers['Link'].links.size).to eq(2) end @@ -23,7 +23,7 @@ describe Api::V1::Accounts::StatusesController do it 'returns http success' do get :index, params: { account_id: user.account.id, only_media: true } - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end @@ -35,7 +35,7 @@ describe Api::V1::Accounts::StatusesController do it 'returns http success' do get :index, params: { account_id: user.account.id, exclude_replies: true } - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end @@ -47,7 +47,7 @@ describe Api::V1::Accounts::StatusesController do it 'returns http success' do get :index, params: { account_id: user.account.id, pinned: true } - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end end diff --git a/spec/controllers/api/v1/accounts_controller_spec.rb b/spec/controllers/api/v1/accounts_controller_spec.rb index 053c53e..7a9e0f8 100644 --- a/spec/controllers/api/v1/accounts_controller_spec.rb +++ b/spec/controllers/api/v1/accounts_controller_spec.rb @@ -13,7 +13,7 @@ RSpec.describe Api::V1::AccountsController, type: :controller do describe 'GET #show' do it 'returns http success' do get :show, params: { id: user.account.id } - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end @@ -28,7 +28,7 @@ RSpec.describe Api::V1::AccountsController, type: :controller do let(:locked) { false } it 'returns http success' do - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end it 'returns JSON with following=true and requested=false' do @@ -47,7 +47,7 @@ RSpec.describe Api::V1::AccountsController, type: :controller do let(:locked) { true } it 'returns http success' do - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end it 'returns JSON with following=false and requested=true' do @@ -72,7 +72,7 @@ RSpec.describe Api::V1::AccountsController, type: :controller do end it 'returns http success' do - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end it 'removes the following relation between user and target user' do @@ -89,7 +89,7 @@ RSpec.describe Api::V1::AccountsController, type: :controller do end it 'returns http success' do - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end it 'removes the following relation between user and target user' do @@ -110,7 +110,7 @@ RSpec.describe Api::V1::AccountsController, type: :controller do end it 'returns http success' do - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end it 'removes the blocking relation between user and target user' do @@ -127,7 +127,7 @@ RSpec.describe Api::V1::AccountsController, type: :controller do end it 'returns http success' do - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end it 'does not remove the following relation between user and target user' do @@ -152,7 +152,7 @@ RSpec.describe Api::V1::AccountsController, type: :controller do end it 'returns http success' do - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end it 'does not remove the following relation between user and target user' do @@ -177,7 +177,7 @@ RSpec.describe Api::V1::AccountsController, type: :controller do end it 'returns http success' do - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end it 'removes the muting relation between user and target user' do diff --git a/spec/controllers/api/v1/apps/credentials_controller_spec.rb b/spec/controllers/api/v1/apps/credentials_controller_spec.rb index 38f2a4e..0f811d5 100644 --- a/spec/controllers/api/v1/apps/credentials_controller_spec.rb +++ b/spec/controllers/api/v1/apps/credentials_controller_spec.rb @@ -16,7 +16,7 @@ describe Api::V1::Apps::CredentialsController do end it 'returns http success' do - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end it 'does not contain client credentials' do diff --git a/spec/controllers/api/v1/apps_controller_spec.rb b/spec/controllers/api/v1/apps_controller_spec.rb index 1ad9d63..60a4c3b 100644 --- a/spec/controllers/api/v1/apps_controller_spec.rb +++ b/spec/controllers/api/v1/apps_controller_spec.rb @@ -9,7 +9,7 @@ RSpec.describe Api::V1::AppsController, type: :controller do end it 'returns http success' do - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end it 'creates an OAuth app' do diff --git a/spec/controllers/api/v1/blocks_controller_spec.rb b/spec/controllers/api/v1/blocks_controller_spec.rb index 9b2bbdf..eff5fb9 100644 --- a/spec/controllers/api/v1/blocks_controller_spec.rb +++ b/spec/controllers/api/v1/blocks_controller_spec.rb @@ -47,7 +47,7 @@ RSpec.describe Api::V1::BlocksController, type: :controller do it 'returns http success' do get :index - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end end diff --git a/spec/controllers/api/v1/custom_emojis_controller_spec.rb b/spec/controllers/api/v1/custom_emojis_controller_spec.rb index 9f35228..fe8daa7 100644 --- a/spec/controllers/api/v1/custom_emojis_controller_spec.rb +++ b/spec/controllers/api/v1/custom_emojis_controller_spec.rb @@ -12,7 +12,7 @@ RSpec.describe Api::V1::CustomEmojisController, type: :controller do end it 'returns http success' do - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end end diff --git a/spec/controllers/api/v1/domain_blocks_controller_spec.rb b/spec/controllers/api/v1/domain_blocks_controller_spec.rb index 3713931..bae4612 100644 --- a/spec/controllers/api/v1/domain_blocks_controller_spec.rb +++ b/spec/controllers/api/v1/domain_blocks_controller_spec.rb @@ -17,7 +17,7 @@ RSpec.describe Api::V1::DomainBlocksController, type: :controller do end it 'returns http success' do - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end it 'returns blocked domains' do @@ -31,7 +31,7 @@ RSpec.describe Api::V1::DomainBlocksController, type: :controller do end it 'returns http success' do - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end it 'creates a domain block' do @@ -45,7 +45,7 @@ RSpec.describe Api::V1::DomainBlocksController, type: :controller do end it 'returns http success' do - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end it 'deletes a domain block' do diff --git a/spec/controllers/api/v1/follow_requests_controller_spec.rb b/spec/controllers/api/v1/follow_requests_controller_spec.rb index 51df006..3c0b84a 100644 --- a/spec/controllers/api/v1/follow_requests_controller_spec.rb +++ b/spec/controllers/api/v1/follow_requests_controller_spec.rb @@ -18,7 +18,7 @@ RSpec.describe Api::V1::FollowRequestsController, type: :controller do end it 'returns http success' do - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end @@ -28,7 +28,7 @@ RSpec.describe Api::V1::FollowRequestsController, type: :controller do end it 'returns http success' do - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end it 'allows follower to follow' do @@ -42,7 +42,7 @@ RSpec.describe Api::V1::FollowRequestsController, type: :controller do end it 'returns http success' do - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end it 'removes follow request' do diff --git a/spec/controllers/api/v1/follows_controller_spec.rb b/spec/controllers/api/v1/follows_controller_spec.rb index ea9e76d..38badb8 100644 --- a/spec/controllers/api/v1/follows_controller_spec.rb +++ b/spec/controllers/api/v1/follows_controller_spec.rb @@ -24,7 +24,7 @@ RSpec.describe Api::V1::FollowsController, type: :controller do end it 'returns http success' do - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end it 'creates account for remote user' do @@ -45,7 +45,7 @@ RSpec.describe Api::V1::FollowsController, type: :controller do it 'returns http success if already following, too' do post :create, params: { uri: 'gargron@quitter.no' } - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end end diff --git a/spec/controllers/api/v1/instances_controller_spec.rb b/spec/controllers/api/v1/instances_controller_spec.rb index eba233b..7397d25 100644 --- a/spec/controllers/api/v1/instances_controller_spec.rb +++ b/spec/controllers/api/v1/instances_controller_spec.rb @@ -16,7 +16,7 @@ RSpec.describe Api::V1::InstancesController, type: :controller do it 'returns http success' do get :show - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end end diff --git a/spec/controllers/api/v1/lists/accounts_controller_spec.rb b/spec/controllers/api/v1/lists/accounts_controller_spec.rb index 953e590..c37a481 100644 --- a/spec/controllers/api/v1/lists/accounts_controller_spec.rb +++ b/spec/controllers/api/v1/lists/accounts_controller_spec.rb @@ -17,7 +17,7 @@ describe Api::V1::Lists::AccountsController do it 'returns http success' do get :show, params: { list_id: list.id } - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end @@ -30,7 +30,7 @@ describe Api::V1::Lists::AccountsController do end it 'returns http success' do - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end it 'adds account to the list' do @@ -44,7 +44,7 @@ describe Api::V1::Lists::AccountsController do end it 'returns http success' do - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end it 'removes account from the list' do diff --git a/spec/controllers/api/v1/lists_controller_spec.rb b/spec/controllers/api/v1/lists_controller_spec.rb index be08c22..2134295 100644 --- a/spec/controllers/api/v1/lists_controller_spec.rb +++ b/spec/controllers/api/v1/lists_controller_spec.rb @@ -12,14 +12,14 @@ RSpec.describe Api::V1::ListsController, type: :controller do describe 'GET #index' do it 'returns http success' do get :index - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end describe 'GET #show' do it 'returns http success' do get :show, params: { id: list.id } - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end @@ -29,7 +29,7 @@ RSpec.describe Api::V1::ListsController, type: :controller do end it 'returns http success' do - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end it 'creates list' do @@ -44,7 +44,7 @@ RSpec.describe Api::V1::ListsController, type: :controller do end it 'returns http success' do - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end it 'updates the list' do @@ -58,7 +58,7 @@ RSpec.describe Api::V1::ListsController, type: :controller do end it 'returns http success' do - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end it 'deletes the list' do diff --git a/spec/controllers/api/v1/media_controller_spec.rb b/spec/controllers/api/v1/media_controller_spec.rb index 0e49463..ce260eb 100644 --- a/spec/controllers/api/v1/media_controller_spec.rb +++ b/spec/controllers/api/v1/media_controller_spec.rb @@ -30,7 +30,7 @@ RSpec.describe Api::V1::MediaController, type: :controller do end it 'returns http 422' do - expect(response).to have_http_status(:error) + expect(response).to have_http_status(500) end end end @@ -41,7 +41,7 @@ RSpec.describe Api::V1::MediaController, type: :controller do end it 'returns http success' do - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end it 'creates a media attachment' do @@ -63,7 +63,7 @@ RSpec.describe Api::V1::MediaController, type: :controller do end it 'returns http success' do - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end it 'creates a media attachment' do @@ -85,7 +85,7 @@ RSpec.describe Api::V1::MediaController, type: :controller do end xit 'returns http success' do - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end xit 'creates a media attachment' do diff --git a/spec/controllers/api/v1/mutes_controller_spec.rb b/spec/controllers/api/v1/mutes_controller_spec.rb index 97d6c27..dc4a975 100644 --- a/spec/controllers/api/v1/mutes_controller_spec.rb +++ b/spec/controllers/api/v1/mutes_controller_spec.rb @@ -15,7 +15,7 @@ RSpec.describe Api::V1::MutesController, type: :controller do it 'returns http success' do get :index, params: { limit: 1 } - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end end diff --git a/spec/controllers/api/v1/notifications_controller_spec.rb b/spec/controllers/api/v1/notifications_controller_spec.rb index f493d0d..2e6163f 100644 --- a/spec/controllers/api/v1/notifications_controller_spec.rb +++ b/spec/controllers/api/v1/notifications_controller_spec.rb @@ -16,7 +16,7 @@ RSpec.describe Api::V1::NotificationsController, type: :controller do notification = Fabricate(:notification, account: user.account) get :show, params: { id: notification.id } - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end @@ -25,7 +25,7 @@ RSpec.describe Api::V1::NotificationsController, type: :controller do notification = Fabricate(:notification, account: user.account) post :dismiss, params: { id: notification.id } - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) expect { notification.reload }.to raise_error(ActiveRecord::RecordNotFound) end end @@ -36,7 +36,7 @@ RSpec.describe Api::V1::NotificationsController, type: :controller do post :clear expect(notification.account.reload.notifications).to be_empty - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end @@ -56,7 +56,7 @@ RSpec.describe Api::V1::NotificationsController, type: :controller do end it 'returns http success' do - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end it 'includes reblog' do @@ -82,7 +82,7 @@ RSpec.describe Api::V1::NotificationsController, type: :controller do end it 'returns http success' do - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end it 'includes reblog' do diff --git a/spec/controllers/api/v1/reports_controller_spec.rb b/spec/controllers/api/v1/reports_controller_spec.rb index 1eb5a43..1e1ef93 100644 --- a/spec/controllers/api/v1/reports_controller_spec.rb +++ b/spec/controllers/api/v1/reports_controller_spec.rb @@ -16,7 +16,7 @@ RSpec.describe Api::V1::ReportsController, type: :controller do it 'returns http success' do get :index - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end @@ -31,7 +31,7 @@ RSpec.describe Api::V1::ReportsController, type: :controller do it 'creates a report' do expect(status.reload.account.targeted_reports).not_to be_empty - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end it 'sends e-mails to admins' do diff --git a/spec/controllers/api/v1/search_controller_spec.rb b/spec/controllers/api/v1/search_controller_spec.rb index ff0c254..0247038 100644 --- a/spec/controllers/api/v1/search_controller_spec.rb +++ b/spec/controllers/api/v1/search_controller_spec.rb @@ -16,7 +16,7 @@ RSpec.describe Api::V1::SearchController, type: :controller do it 'returns http success' do get :index, params: { q: 'test' } - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end end diff --git a/spec/controllers/api/v1/statuses/favourited_by_accounts_controller_spec.rb b/spec/controllers/api/v1/statuses/favourited_by_accounts_controller_spec.rb index 556731d..c873e05 100644 --- a/spec/controllers/api/v1/statuses/favourited_by_accounts_controller_spec.rb +++ b/spec/controllers/api/v1/statuses/favourited_by_accounts_controller_spec.rb @@ -21,7 +21,7 @@ RSpec.describe Api::V1::Statuses::FavouritedByAccountsController, type: :control it 'returns http success' do get :index, params: { status_id: status.id, limit: 1 } - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) expect(response.headers['Link'].links.size).to eq(2) end end @@ -43,7 +43,7 @@ RSpec.describe Api::V1::Statuses::FavouritedByAccountsController, type: :control it 'returns http unautharized' do get :index, params: { status_id: status.id } - expect(response).to have_http_status(:missing) + expect(response).to have_http_status(404) end end end @@ -58,7 +58,7 @@ RSpec.describe Api::V1::Statuses::FavouritedByAccountsController, type: :control it 'returns http success' do get :index, params: { status_id: status.id } - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end end diff --git a/spec/controllers/api/v1/statuses/favourites_controller_spec.rb b/spec/controllers/api/v1/statuses/favourites_controller_spec.rb index aba7cd4..53f6026 100644 --- a/spec/controllers/api/v1/statuses/favourites_controller_spec.rb +++ b/spec/controllers/api/v1/statuses/favourites_controller_spec.rb @@ -22,7 +22,7 @@ describe Api::V1::Statuses::FavouritesController do end it 'returns http success' do - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end it 'updates the favourites count' do @@ -51,7 +51,7 @@ describe Api::V1::Statuses::FavouritesController do end it 'returns http success' do - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end it 'updates the favourites count' do diff --git a/spec/controllers/api/v1/statuses/mutes_controller_spec.rb b/spec/controllers/api/v1/statuses/mutes_controller_spec.rb index 54c594e..13b4625 100644 --- a/spec/controllers/api/v1/statuses/mutes_controller_spec.rb +++ b/spec/controllers/api/v1/statuses/mutes_controller_spec.rb @@ -22,7 +22,7 @@ describe Api::V1::Statuses::MutesController do end it 'returns http success' do - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end it 'creates a conversation mute' do @@ -39,7 +39,7 @@ describe Api::V1::Statuses::MutesController do end it 'returns http success' do - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end it 'destroys the conversation mute' do diff --git a/spec/controllers/api/v1/statuses/pins_controller_spec.rb b/spec/controllers/api/v1/statuses/pins_controller_spec.rb index 79005c9..8f5b080 100644 --- a/spec/controllers/api/v1/statuses/pins_controller_spec.rb +++ b/spec/controllers/api/v1/statuses/pins_controller_spec.rb @@ -22,7 +22,7 @@ describe Api::V1::Statuses::PinsController do end it 'returns http success' do - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end it 'updates the pinned attribute' do @@ -46,7 +46,7 @@ describe Api::V1::Statuses::PinsController do end it 'returns http success' do - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end it 'updates the pinned attribute' do diff --git a/spec/controllers/api/v1/statuses/reblogged_by_accounts_controller_spec.rb b/spec/controllers/api/v1/statuses/reblogged_by_accounts_controller_spec.rb index ba022a9..9c0c2b6 100644 --- a/spec/controllers/api/v1/statuses/reblogged_by_accounts_controller_spec.rb +++ b/spec/controllers/api/v1/statuses/reblogged_by_accounts_controller_spec.rb @@ -21,7 +21,7 @@ RSpec.describe Api::V1::Statuses::RebloggedByAccountsController, type: :controll it 'returns http success' do get :index, params: { status_id: status.id, limit: 1 } - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) expect(response.headers['Link'].links.size).to eq(2) end end @@ -42,7 +42,7 @@ RSpec.describe Api::V1::Statuses::RebloggedByAccountsController, type: :controll it 'returns http unautharized' do get :index, params: { status_id: status.id } - expect(response).to have_http_status(:missing) + expect(response).to have_http_status(404) end end end @@ -57,7 +57,7 @@ RSpec.describe Api::V1::Statuses::RebloggedByAccountsController, type: :controll it 'returns http success' do get :index, params: { status_id: status.id } - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end end diff --git a/spec/controllers/api/v1/statuses/reblogs_controller_spec.rb b/spec/controllers/api/v1/statuses/reblogs_controller_spec.rb index 7417ff6..e60f8da 100644 --- a/spec/controllers/api/v1/statuses/reblogs_controller_spec.rb +++ b/spec/controllers/api/v1/statuses/reblogs_controller_spec.rb @@ -22,7 +22,7 @@ describe Api::V1::Statuses::ReblogsController do end it 'returns http success' do - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end it 'updates the reblogs count' do @@ -51,7 +51,7 @@ describe Api::V1::Statuses::ReblogsController do end it 'returns http success' do - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end it 'updates the reblogs count' do diff --git a/spec/controllers/api/v1/statuses_controller_spec.rb b/spec/controllers/api/v1/statuses_controller_spec.rb index a362653..27e4f4e 100644 --- a/spec/controllers/api/v1/statuses_controller_spec.rb +++ b/spec/controllers/api/v1/statuses_controller_spec.rb @@ -17,7 +17,7 @@ RSpec.describe Api::V1::StatusesController, type: :controller do it 'returns http success' do get :show, params: { id: status.id } - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end @@ -30,7 +30,7 @@ RSpec.describe Api::V1::StatusesController, type: :controller do it 'returns http success' do get :context, params: { id: status.id } - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end @@ -40,7 +40,7 @@ RSpec.describe Api::V1::StatusesController, type: :controller do end it 'returns http success' do - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end @@ -52,7 +52,7 @@ RSpec.describe Api::V1::StatusesController, type: :controller do end it 'returns http success' do - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end it 'removes the status' do @@ -72,7 +72,7 @@ RSpec.describe Api::V1::StatusesController, type: :controller do describe 'GET #show' do it 'returns http unautharized' do get :show, params: { id: status.id } - expect(response).to have_http_status(:missing) + expect(response).to have_http_status(404) end end @@ -83,14 +83,14 @@ RSpec.describe Api::V1::StatusesController, type: :controller do it 'returns http unautharized' do get :context, params: { id: status.id } - expect(response).to have_http_status(:missing) + expect(response).to have_http_status(404) end end describe 'GET #card' do it 'returns http unautharized' do get :card, params: { id: status.id } - expect(response).to have_http_status(:missing) + expect(response).to have_http_status(404) end end end @@ -101,7 +101,7 @@ RSpec.describe Api::V1::StatusesController, type: :controller do describe 'GET #show' do it 'returns http success' do get :show, params: { id: status.id } - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end @@ -112,14 +112,14 @@ RSpec.describe Api::V1::StatusesController, type: :controller do it 'returns http success' do get :context, params: { id: status.id } - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end describe 'GET #card' do it 'returns http success' do get :card, params: { id: status.id } - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end end diff --git a/spec/controllers/api/v1/timelines/home_controller_spec.rb b/spec/controllers/api/v1/timelines/home_controller_spec.rb index 4d45235..85b0316 100644 --- a/spec/controllers/api/v1/timelines/home_controller_spec.rb +++ b/spec/controllers/api/v1/timelines/home_controller_spec.rb @@ -23,7 +23,7 @@ describe Api::V1::Timelines::HomeController do it 'returns http success' do get :show - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) expect(response.headers['Link'].links.size).to eq(2) end end diff --git a/spec/controllers/api/v1/timelines/list_controller_spec.rb b/spec/controllers/api/v1/timelines/list_controller_spec.rb index 07eba95..1729217 100644 --- a/spec/controllers/api/v1/timelines/list_controller_spec.rb +++ b/spec/controllers/api/v1/timelines/list_controller_spec.rb @@ -24,7 +24,7 @@ describe Api::V1::Timelines::ListController do it 'returns http success' do get :show, params: { id: list.id } - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end end diff --git a/spec/controllers/api/v1/timelines/public_controller_spec.rb b/spec/controllers/api/v1/timelines/public_controller_spec.rb index 3acf2e2..68d87bb 100644 --- a/spec/controllers/api/v1/timelines/public_controller_spec.rb +++ b/spec/controllers/api/v1/timelines/public_controller_spec.rb @@ -22,7 +22,7 @@ describe Api::V1::Timelines::PublicController do it 'returns http success' do get :show - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) expect(response.headers['Link'].links.size).to eq(2) end end @@ -35,7 +35,7 @@ describe Api::V1::Timelines::PublicController do it 'returns http success' do get :show, params: { local: true } - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) expect(response.headers['Link'].links.size).to eq(2) end end @@ -48,7 +48,7 @@ describe Api::V1::Timelines::PublicController do it 'returns http success' do get :show - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) expect(response.headers['Link']).to be_nil end end diff --git a/spec/controllers/api/v1/timelines/tag_controller_spec.rb b/spec/controllers/api/v1/timelines/tag_controller_spec.rb index 6c66ee5..472779f 100644 --- a/spec/controllers/api/v1/timelines/tag_controller_spec.rb +++ b/spec/controllers/api/v1/timelines/tag_controller_spec.rb @@ -21,7 +21,7 @@ describe Api::V1::Timelines::TagController do it 'returns http success' do get :show, params: { id: 'test' } - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) expect(response.headers['Link'].links.size).to eq(2) end end @@ -33,7 +33,7 @@ describe Api::V1::Timelines::TagController do describe 'GET #show' do it 'returns http success' do get :show, params: { id: 'test' } - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) expect(response.headers['Link']).to be_nil end end diff --git a/spec/controllers/api/web/settings_controller_spec.rb b/spec/controllers/api/web/settings_controller_spec.rb index ff211c7..815da04 100644 --- a/spec/controllers/api/web/settings_controller_spec.rb +++ b/spec/controllers/api/web/settings_controller_spec.rb @@ -13,7 +13,7 @@ describe Api::Web::SettingsController do patch :update, format: :json, params: { data: { 'onboarded' => true } } user.reload - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) expect(user_web_setting.data['onboarded']).to eq('true') end diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb index 3e4d27e..c6c78d3 100644 --- a/spec/controllers/application_controller_spec.rb +++ b/spec/controllers/application_controller_spec.rb @@ -51,7 +51,7 @@ describe ApplicationController, type: :controller do routes.draw { get 'success' => 'anonymous#success' } allow(Rails.env).to receive(:production?).and_return(false) get 'success' - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end it "forces ssl if Rails.env.production? is 'true'" do @@ -145,13 +145,13 @@ describe ApplicationController, type: :controller do it 'does nothing if not signed in' do get 'success' - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end it 'does nothing if user who signed in is not suspended' do sign_in(Fabricate(:user, account: Fabricate(:account, suspended: false))) get 'success' - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end it 'returns http 403 if user who signed in is suspended' do diff --git a/spec/controllers/auth/confirmations_controller_spec.rb b/spec/controllers/auth/confirmations_controller_spec.rb index 80a06c4..b3af5e0 100644 --- a/spec/controllers/auth/confirmations_controller_spec.rb +++ b/spec/controllers/auth/confirmations_controller_spec.rb @@ -7,7 +7,7 @@ describe Auth::ConfirmationsController, type: :controller do it 'returns http success' do @request.env['devise.mapping'] = Devise.mappings[:user] get :new - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end diff --git a/spec/controllers/auth/passwords_controller_spec.rb b/spec/controllers/auth/passwords_controller_spec.rb index 992d2e2..dcfdebb 100644 --- a/spec/controllers/auth/passwords_controller_spec.rb +++ b/spec/controllers/auth/passwords_controller_spec.rb @@ -9,7 +9,7 @@ describe Auth::PasswordsController, type: :controller do it 'returns http success' do @request.env['devise.mapping'] = Devise.mappings[:user] get :new - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end @@ -24,7 +24,7 @@ describe Auth::PasswordsController, type: :controller do context 'with valid reset_password_token' do it 'returns http success' do get :edit, params: { reset_password_token: @token } - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end diff --git a/spec/controllers/auth/registrations_controller_spec.rb b/spec/controllers/auth/registrations_controller_spec.rb index 97d2c53..9cfbd48 100644 --- a/spec/controllers/auth/registrations_controller_spec.rb +++ b/spec/controllers/auth/registrations_controller_spec.rb @@ -35,7 +35,7 @@ RSpec.describe Auth::RegistrationsController, type: :controller do request.env["devise.mapping"] = Devise.mappings[:user] sign_in(Fabricate(:user)) get :edit - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end @@ -44,7 +44,7 @@ RSpec.describe Auth::RegistrationsController, type: :controller do request.env["devise.mapping"] = Devise.mappings[:user] sign_in(Fabricate(:user), scope: :user) post :update - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end @@ -63,7 +63,7 @@ RSpec.describe Auth::RegistrationsController, type: :controller do it 'returns http success' do Setting.open_registrations = true get :new - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end diff --git a/spec/controllers/auth/sessions_controller_spec.rb b/spec/controllers/auth/sessions_controller_spec.rb index d5fed17..97719a6 100644 --- a/spec/controllers/auth/sessions_controller_spec.rb +++ b/spec/controllers/auth/sessions_controller_spec.rb @@ -12,7 +12,7 @@ RSpec.describe Auth::SessionsController, type: :controller do it 'returns http success' do get :new - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end diff --git a/spec/controllers/authorize_follows_controller_spec.rb b/spec/controllers/authorize_follows_controller_spec.rb index b1cbef7..52971c7 100644 --- a/spec/controllers/authorize_follows_controller_spec.rb +++ b/spec/controllers/authorize_follows_controller_spec.rb @@ -47,7 +47,7 @@ describe AuthorizeFollowsController do get :show, params: { acct: 'http://example.com' } - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) expect(assigns(:account)).to eq account end @@ -59,7 +59,7 @@ describe AuthorizeFollowsController do get :show, params: { acct: 'acct:found@hostname' } - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) expect(assigns(:account)).to eq account end end diff --git a/spec/controllers/concerns/account_controller_concern_spec.rb b/spec/controllers/concerns/account_controller_concern_spec.rb index ae46f9b..9368510 100644 --- a/spec/controllers/concerns/account_controller_concern_spec.rb +++ b/spec/controllers/concerns/account_controller_concern_spec.rb @@ -39,7 +39,7 @@ describe ApplicationController, type: :controller do it 'returns http success' do account = Fabricate(:account) get 'success', params: { account_username: account.username } - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end end diff --git a/spec/controllers/concerns/export_controller_concern_spec.rb b/spec/controllers/concerns/export_controller_concern_spec.rb index 9d6f782..6a13db6 100644 --- a/spec/controllers/concerns/export_controller_concern_spec.rb +++ b/spec/controllers/concerns/export_controller_concern_spec.rb @@ -19,7 +19,7 @@ describe ApplicationController, type: :controller do sign_in user get :index, format: :csv - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) expect(response.content_type).to eq 'text/csv' expect(response.headers['Content-Disposition']).to eq 'attachment; filename="anonymous.csv"' expect(response.body).to eq user.account.username diff --git a/spec/controllers/follower_accounts_controller_spec.rb b/spec/controllers/follower_accounts_controller_spec.rb index b9b7fef..3a42a6e 100644 --- a/spec/controllers/follower_accounts_controller_spec.rb +++ b/spec/controllers/follower_accounts_controller_spec.rb @@ -19,7 +19,7 @@ describe FollowerAccountsController do expect(assigned[0]).to eq follow1 expect(assigned[1]).to eq follow0 - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end end diff --git a/spec/controllers/following_accounts_controller_spec.rb b/spec/controllers/following_accounts_controller_spec.rb index 55e7265..3337636 100644 --- a/spec/controllers/following_accounts_controller_spec.rb +++ b/spec/controllers/following_accounts_controller_spec.rb @@ -19,7 +19,7 @@ describe FollowingAccountsController do expect(assigned[0]).to eq follow1 expect(assigned[1]).to eq follow0 - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end end diff --git a/spec/controllers/manifests_controller_spec.rb b/spec/controllers/manifests_controller_spec.rb index 71967e4..a549ade 100644 --- a/spec/controllers/manifests_controller_spec.rb +++ b/spec/controllers/manifests_controller_spec.rb @@ -9,7 +9,7 @@ describe ManifestsController do end it 'returns http success' do - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end end diff --git a/spec/controllers/media_controller_spec.rb b/spec/controllers/media_controller_spec.rb index 5b03899..ac44a76 100644 --- a/spec/controllers/media_controller_spec.rb +++ b/spec/controllers/media_controller_spec.rb @@ -18,13 +18,13 @@ describe MediaController do media_attachment = Fabricate(:media_attachment, status: nil) get :show, params: { id: media_attachment.to_param } - expect(response).to have_http_status(:missing) + expect(response).to have_http_status(404) end it 'raises when shortcode cant be found' do get :show, params: { id: 'missing' } - expect(response).to have_http_status(:missing) + expect(response).to have_http_status(404) end it 'raises when not permitted to view' do @@ -33,7 +33,7 @@ describe MediaController do allow_any_instance_of(MediaController).to receive(:authorize).and_raise(ActiveRecord::RecordNotFound) get :show, params: { id: media_attachment.to_param } - expect(response).to have_http_status(:missing) + expect(response).to have_http_status(404) end end end diff --git a/spec/controllers/oauth/authorizations_controller_spec.rb b/spec/controllers/oauth/authorizations_controller_spec.rb index 5c2a62b..91c2d03 100644 --- a/spec/controllers/oauth/authorizations_controller_spec.rb +++ b/spec/controllers/oauth/authorizations_controller_spec.rb @@ -26,7 +26,7 @@ RSpec.describe Oauth::AuthorizationsController, type: :controller do it 'returns http success' do subject - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end it 'gives options to authorize and deny' do diff --git a/spec/controllers/oauth/authorized_applications_controller_spec.rb b/spec/controllers/oauth/authorized_applications_controller_spec.rb index 2a2b922..f967b50 100644 --- a/spec/controllers/oauth/authorized_applications_controller_spec.rb +++ b/spec/controllers/oauth/authorized_applications_controller_spec.rb @@ -24,7 +24,7 @@ describe Oauth::AuthorizedApplicationsController do it 'returns http success' do subject - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end include_examples 'stores location for user' diff --git a/spec/controllers/remote_follow_controller_spec.rb b/spec/controllers/remote_follow_controller_spec.rb index 86b1eb8..5088c2e 100644 --- a/spec/controllers/remote_follow_controller_spec.rb +++ b/spec/controllers/remote_follow_controller_spec.rb @@ -10,7 +10,7 @@ describe RemoteFollowController do account = Fabricate(:account) get :new, params: { account_username: account.to_param } - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) expect(response).to render_template(:new) expect(assigns(:remote_follow).acct).to be_nil end @@ -20,7 +20,7 @@ describe RemoteFollowController do account = Fabricate(:account) get :new, params: { account_username: account.to_param } - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) expect(response).to render_template(:new) expect(assigns(:remote_follow).acct).to eq 'user@example.com' end diff --git a/spec/controllers/settings/applications_controller_spec.rb b/spec/controllers/settings/applications_controller_spec.rb index 90e6a63..f871076 100644 --- a/spec/controllers/settings/applications_controller_spec.rb +++ b/spec/controllers/settings/applications_controller_spec.rb @@ -15,7 +15,7 @@ describe Settings::ApplicationsController do it 'shows apps' do get :index - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) expect(assigns(:applications)).to include(app) expect(assigns(:applications)).to_not include(other_app) end @@ -25,7 +25,7 @@ describe Settings::ApplicationsController do describe 'GET #show' do it 'returns http success' do get :show, params: { id: app.id } - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) expect(assigns[:application]).to eql(app) end @@ -40,7 +40,7 @@ describe Settings::ApplicationsController do describe 'GET #new' do it 'works' do get :new - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end @@ -102,7 +102,7 @@ describe Settings::ApplicationsController do end it 'returns http success' do - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end it 'renders form again' do @@ -151,7 +151,7 @@ describe Settings::ApplicationsController do end it 'returns http success' do - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end it 'renders form again' do diff --git a/spec/controllers/settings/deletes_controller_spec.rb b/spec/controllers/settings/deletes_controller_spec.rb index 9b55090..35fd64e 100644 --- a/spec/controllers/settings/deletes_controller_spec.rb +++ b/spec/controllers/settings/deletes_controller_spec.rb @@ -13,7 +13,7 @@ describe Settings::DeletesController do it 'renders confirmation page' do get :show - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end diff --git a/spec/controllers/settings/exports_controller_spec.rb b/spec/controllers/settings/exports_controller_spec.rb index 19cb0ab..b7cab4d 100644 --- a/spec/controllers/settings/exports_controller_spec.rb +++ b/spec/controllers/settings/exports_controller_spec.rb @@ -17,7 +17,7 @@ describe Settings::ExportsController do export = assigns(:export) expect(export).to be_instance_of Export expect(export.account).to eq user.account - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end diff --git a/spec/controllers/settings/follower_domains_controller_spec.rb b/spec/controllers/settings/follower_domains_controller_spec.rb index 333223c..6d415a6 100644 --- a/spec/controllers/settings/follower_domains_controller_spec.rb +++ b/spec/controllers/settings/follower_domains_controller_spec.rb @@ -36,7 +36,7 @@ describe Settings::FollowerDomainsController do it 'returns http success' do sign_in user, scope: :user subject - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end include_examples 'authenticate user' diff --git a/spec/controllers/settings/imports_controller_spec.rb b/spec/controllers/settings/imports_controller_spec.rb index 59b10e0..7a9b021 100644 --- a/spec/controllers/settings/imports_controller_spec.rb +++ b/spec/controllers/settings/imports_controller_spec.rb @@ -10,7 +10,7 @@ RSpec.describe Settings::ImportsController, type: :controller do describe "GET #show" do it "returns http success" do get :show - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end diff --git a/spec/controllers/settings/notifications_controller_spec.rb b/spec/controllers/settings/notifications_controller_spec.rb index 0bd9934..981ef67 100644 --- a/spec/controllers/settings/notifications_controller_spec.rb +++ b/spec/controllers/settings/notifications_controller_spec.rb @@ -12,7 +12,7 @@ describe Settings::NotificationsController do describe 'GET #show' do it 'returns http success' do get :show - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end diff --git a/spec/controllers/settings/preferences_controller_spec.rb b/spec/controllers/settings/preferences_controller_spec.rb index 0f94316..7877c73 100644 --- a/spec/controllers/settings/preferences_controller_spec.rb +++ b/spec/controllers/settings/preferences_controller_spec.rb @@ -12,7 +12,7 @@ describe Settings::PreferencesController do describe 'GET #show' do it 'returns http success' do get :show - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end diff --git a/spec/controllers/settings/profiles_controller_spec.rb b/spec/controllers/settings/profiles_controller_spec.rb index ee3315b..a453200 100644 --- a/spec/controllers/settings/profiles_controller_spec.rb +++ b/spec/controllers/settings/profiles_controller_spec.rb @@ -11,7 +11,7 @@ RSpec.describe Settings::ProfilesController, type: :controller do describe "GET #show" do it "returns http success" do get :show - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end diff --git a/spec/controllers/settings/two_factor_authentication/confirmations_controller_spec.rb b/spec/controllers/settings/two_factor_authentication/confirmations_controller_spec.rb index aee82a3..7612bf9 100644 --- a/spec/controllers/settings/two_factor_authentication/confirmations_controller_spec.rb +++ b/spec/controllers/settings/two_factor_authentication/confirmations_controller_spec.rb @@ -15,7 +15,7 @@ describe Settings::TwoFactorAuthentication::ConfirmationsController do expect(assigns(:confirmation)).to be_instance_of Form::TwoFactorConfirmation expect(assigns(:provision_url)).to eq 'otpauth://totp/local-part@domain?secret=thisisasecretforthespecofnewview&issuer=cb6e6126.ngrok.io' expect(assigns(:qrcode)).to be_instance_of RQRCode::QRCode - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) expect(response).to render_template(:new) end end @@ -71,7 +71,7 @@ describe Settings::TwoFactorAuthentication::ConfirmationsController do expect(assigns(:recovery_codes)).to eq otp_backup_codes expect(flash[:notice]).to eq 'Two-factor authentication successfully enabled' - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) expect(response).to render_template('settings/two_factor_authentication/recovery_codes/index') end end diff --git a/spec/controllers/settings/two_factor_authentication/recovery_codes_controller_spec.rb b/spec/controllers/settings/two_factor_authentication/recovery_codes_controller_spec.rb index aa28cdf..c04760e 100644 --- a/spec/controllers/settings/two_factor_authentication/recovery_codes_controller_spec.rb +++ b/spec/controllers/settings/two_factor_authentication/recovery_codes_controller_spec.rb @@ -19,7 +19,7 @@ describe Settings::TwoFactorAuthentication::RecoveryCodesController do expect(assigns(:recovery_codes)).to eq otp_backup_codes expect(flash[:notice]).to eq 'Recovery codes successfully regenerated' - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) expect(response).to render_template(:index) end diff --git a/spec/controllers/settings/two_factor_authentications_controller_spec.rb b/spec/controllers/settings/two_factor_authentications_controller_spec.rb index 6c49f6f..9f27222 100644 --- a/spec/controllers/settings/two_factor_authentications_controller_spec.rb +++ b/spec/controllers/settings/two_factor_authentications_controller_spec.rb @@ -18,7 +18,7 @@ describe Settings::TwoFactorAuthenticationsController do user.update(otp_required_for_login: true) get :show - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end @@ -27,7 +27,7 @@ describe Settings::TwoFactorAuthenticationsController do user.update(otp_required_for_login: false) get :show - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end end diff --git a/spec/controllers/statuses_controller_spec.rb b/spec/controllers/statuses_controller_spec.rb index 95fb4d5..89af556 100644 --- a/spec/controllers/statuses_controller_spec.rb +++ b/spec/controllers/statuses_controller_spec.rb @@ -85,7 +85,7 @@ describe StatusesController do it 'returns a success' do status = Fabricate(:status) get :show, params: { account_username: status.account.username, id: status.id } - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end it 'renders stream_entries/show' do diff --git a/spec/controllers/stream_entries_controller_spec.rb b/spec/controllers/stream_entries_controller_spec.rb index 665c5b7..534bc39 100644 --- a/spec/controllers/stream_entries_controller_spec.rb +++ b/spec/controllers/stream_entries_controller_spec.rb @@ -77,7 +77,7 @@ RSpec.describe StreamEntriesController, type: :controller do it 'returns http success with Atom' do status = Fabricate(:status) get :show, params: { account_username: status.account.username, id: status.stream_entry.id }, format: 'atom' - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end diff --git a/spec/controllers/tags_controller_spec.rb b/spec/controllers/tags_controller_spec.rb index b04666c..33ccaed 100644 --- a/spec/controllers/tags_controller_spec.rb +++ b/spec/controllers/tags_controller_spec.rb @@ -12,7 +12,7 @@ RSpec.describe TagsController, type: :controller do context 'when tag exists' do it 'returns http success' do get :show, params: { id: 'test', max_id: late.id } - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end it 'renders application layout' do @@ -25,7 +25,7 @@ RSpec.describe TagsController, type: :controller do it 'returns http missing for non-existent tag' do get :show, params: { id: 'none' } - expect(response).to have_http_status(:missing) + expect(response).to have_http_status(404) end end end diff --git a/spec/controllers/well_known/host_meta_controller_spec.rb b/spec/controllers/well_known/host_meta_controller_spec.rb index 87c1485..8147e8f 100644 --- a/spec/controllers/well_known/host_meta_controller_spec.rb +++ b/spec/controllers/well_known/host_meta_controller_spec.rb @@ -7,7 +7,7 @@ describe WellKnown::HostMetaController, type: :controller do it 'returns http success' do get :show, format: :xml - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) expect(response.content_type).to eq 'application/xrd+xml' expect(response.body).to eq < diff --git a/spec/controllers/well_known/webfinger_controller_spec.rb b/spec/controllers/well_known/webfinger_controller_spec.rb index 466f87c..b05745e 100644 --- a/spec/controllers/well_known/webfinger_controller_spec.rb +++ b/spec/controllers/well_known/webfinger_controller_spec.rb @@ -50,7 +50,7 @@ PEM json = body_as_json - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) expect(response.content_type).to eq 'application/jrd+json' expect(json[:subject]).to eq 'acct:alice@cb6e6126.ngrok.io' expect(json[:aliases]).to include('https://cb6e6126.ngrok.io/@alice', 'https://cb6e6126.ngrok.io/users/alice') @@ -61,7 +61,7 @@ PEM xml = Nokogiri::XML(response.body) - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) expect(response.content_type).to eq 'application/xrd+xml' expect(xml.at_xpath('//xmlns:Subject').content).to eq 'acct:alice@cb6e6126.ngrok.io' expect(xml.xpath('//xmlns:Alias').map(&:content)).to include('https://cb6e6126.ngrok.io/@alice', 'https://cb6e6126.ngrok.io/users/alice') @@ -81,7 +81,7 @@ PEM json = body_as_json - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) expect(response.content_type).to eq 'application/jrd+json' expect(json[:subject]).to eq 'acct:alice@cb6e6126.ngrok.io' expect(json[:aliases]).to include('https://cb6e6126.ngrok.io/@alice', 'https://cb6e6126.ngrok.io/users/alice') diff --git a/spec/requests/host_meta_request_spec.rb b/spec/requests/host_meta_request_spec.rb index 0c51b5f..beb33a8 100644 --- a/spec/requests/host_meta_request_spec.rb +++ b/spec/requests/host_meta_request_spec.rb @@ -5,7 +5,7 @@ describe "The host_meta route" do it "returns an xml response" do get host_meta_url - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) expect(response.content_type).to eq "application/xrd+xml" end end diff --git a/spec/requests/webfinger_request_spec.rb b/spec/requests/webfinger_request_spec.rb index a17d6cc..7f9e116 100644 --- a/spec/requests/webfinger_request_spec.rb +++ b/spec/requests/webfinger_request_spec.rb @@ -7,7 +7,7 @@ describe 'The webfinger route' do it 'returns a json response' do get webfinger_url(resource: alice.to_webfinger_s) - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) expect(response.content_type).to eq 'application/jrd+json' end end @@ -16,7 +16,7 @@ describe 'The webfinger route' do it 'returns an xml response for xml format' do get webfinger_url(resource: alice.to_webfinger_s, format: :xml) - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) expect(response.content_type).to eq 'application/xrd+xml' end @@ -24,7 +24,7 @@ describe 'The webfinger route' do headers = { 'HTTP_ACCEPT' => 'application/xrd+xml' } get webfinger_url(resource: alice.to_webfinger_s), headers: headers - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) expect(response.content_type).to eq 'application/xrd+xml' end end @@ -33,7 +33,7 @@ describe 'The webfinger route' do it 'returns a json response for json format' do get webfinger_url(resource: alice.to_webfinger_s, format: :json) - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) expect(response.content_type).to eq 'application/jrd+json' end @@ -41,7 +41,7 @@ describe 'The webfinger route' do headers = { 'HTTP_ACCEPT' => 'application/jrd+json' } get webfinger_url(resource: alice.to_webfinger_s), headers: headers - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) expect(response.content_type).to eq 'application/jrd+json' end end From a4a36d994b0949aff134a2c1ba4efc361b7fb358 Mon Sep 17 00:00:00 2001 From: Lynx Kotoura Date: Sun, 22 Apr 2018 04:35:55 +0900 Subject: [PATCH 164/381] Separate high contrast theme (#7213) --- app/javascript/styles/contrast.scss | 3 + app/javascript/styles/contrast/diff.scss | 14 ++ app/javascript/styles/contrast/variables.scss | 22 +++ app/javascript/styles/mastodon/about.scss | 32 ++--- app/javascript/styles/mastodon/accounts.scss | 22 +-- app/javascript/styles/mastodon/admin.scss | 26 ++-- app/javascript/styles/mastodon/compact_header.scss | 4 +- app/javascript/styles/mastodon/components.scss | 150 ++++++++++----------- app/javascript/styles/mastodon/containers.scss | 2 +- app/javascript/styles/mastodon/emoji_picker.scss | 4 +- app/javascript/styles/mastodon/forms.scss | 10 +- app/javascript/styles/mastodon/landing_strip.scss | 2 +- app/javascript/styles/mastodon/stream_entries.scss | 14 +- app/javascript/styles/mastodon/variables.scss | 20 ++- config/locales/en.yml | 1 + config/themes.yml | 1 + 16 files changed, 187 insertions(+), 140 deletions(-) create mode 100644 app/javascript/styles/contrast.scss create mode 100644 app/javascript/styles/contrast/diff.scss create mode 100644 app/javascript/styles/contrast/variables.scss diff --git a/app/javascript/styles/contrast.scss b/app/javascript/styles/contrast.scss new file mode 100644 index 0000000..5b43aec --- /dev/null +++ b/app/javascript/styles/contrast.scss @@ -0,0 +1,3 @@ +@import 'contrast/variables'; +@import 'application'; +@import 'contrast/diff'; diff --git a/app/javascript/styles/contrast/diff.scss b/app/javascript/styles/contrast/diff.scss new file mode 100644 index 0000000..eee9ecc --- /dev/null +++ b/app/javascript/styles/contrast/diff.scss @@ -0,0 +1,14 @@ +// components.scss +.compose-form { + .compose-form__modifiers { + .compose-form__upload { + &-description { + input { + &::placeholder { + opacity: 1.0; + } + } + } + } + } +} diff --git a/app/javascript/styles/contrast/variables.scss b/app/javascript/styles/contrast/variables.scss new file mode 100644 index 0000000..35d1106 --- /dev/null +++ b/app/javascript/styles/contrast/variables.scss @@ -0,0 +1,22 @@ +// Dependent colors +$black: #000000; + +$classic-base-color: #282c37; +$classic-primary-color: #9baec8; +$classic-secondary-color: #d9e1e8; + +$ui-base-color: $classic-base-color !default; +$ui-primary-color: $classic-primary-color !default; +$ui-secondary-color: $classic-secondary-color !default; + +// Differences +$ui-highlight-color: #2b5fd9; + +$darker-text-color: lighten($ui-primary-color, 20%) !default; +$dark-text-color: lighten($ui-primary-color, 12%) !default; +$secondary-text-color: lighten($ui-secondary-color, 6%) !default; +$action-button-color: #8d9ac2; + +$inverted-text-color: $black !default; +$lighter-text-color: darken($ui-base-color,6%) !default; +$light-text-color: darken($ui-primary-color, 40%) !default; diff --git a/app/javascript/styles/mastodon/about.scss b/app/javascript/styles/mastodon/about.scss index 0a09a38..c9c0e30 100644 --- a/app/javascript/styles/mastodon/about.scss +++ b/app/javascript/styles/mastodon/about.scss @@ -225,7 +225,7 @@ $small-breakpoint: 960px; font-family: inherit; font-size: inherit; line-height: inherit; - color: transparentize($darker-text-color, 0.1); + color: lighten($darker-text-color, 10%); } h1 { @@ -234,14 +234,14 @@ $small-breakpoint: 960px; line-height: 30px; font-weight: 500; margin-bottom: 20px; - color: $primary-text-color; + color: $secondary-text-color; small { font-family: 'mastodon-font-sans-serif', sans-serif; display: block; font-size: 18px; font-weight: 400; - color: opacify($darker-text-color, 0.1); + color: lighten($darker-text-color, 10%); } } @@ -251,7 +251,7 @@ $small-breakpoint: 960px; line-height: 26px; font-weight: 500; margin-bottom: 20px; - color: $primary-text-color; + color: $secondary-text-color; } h3 { @@ -260,7 +260,7 @@ $small-breakpoint: 960px; line-height: 24px; font-weight: 500; margin-bottom: 20px; - color: $primary-text-color; + color: $secondary-text-color; } h4 { @@ -269,7 +269,7 @@ $small-breakpoint: 960px; line-height: 24px; font-weight: 500; margin-bottom: 20px; - color: $primary-text-color; + color: $secondary-text-color; } h5 { @@ -278,7 +278,7 @@ $small-breakpoint: 960px; line-height: 24px; font-weight: 500; margin-bottom: 20px; - color: $primary-text-color; + color: $secondary-text-color; } h6 { @@ -287,7 +287,7 @@ $small-breakpoint: 960px; line-height: 24px; font-weight: 500; margin-bottom: 20px; - color: $primary-text-color; + color: $secondary-text-color; } ul, @@ -405,7 +405,7 @@ $small-breakpoint: 960px; font-size: 14px; &:hover { - color: $darker-text-color; + color: $secondary-text-color; } } @@ -517,7 +517,7 @@ $small-breakpoint: 960px; span { &:last-child { - color: $darker-text-color; + color: $secondary-text-color; } } @@ -559,7 +559,7 @@ $small-breakpoint: 960px; a, span { font-weight: 400; - color: opacify($darker-text-color, 0.1); + color: darken($darker-text-color, 10%); } a { @@ -775,7 +775,7 @@ $small-breakpoint: 960px; } p a { - color: $darker-text-color; + color: $secondary-text-color; } h1 { @@ -787,7 +787,7 @@ $small-breakpoint: 960px; color: $darker-text-color; span { - color: $darker-text-color; + color: $secondary-text-color; } } } @@ -896,7 +896,7 @@ $small-breakpoint: 960px; } a { - color: $darker-text-color; + color: $secondary-text-color; text-decoration: none; } } @@ -980,7 +980,7 @@ $small-breakpoint: 960px; .footer-links { padding-bottom: 50px; text-align: right; - color: $darker-text-color; + color: $dark-text-color; p { font-size: 14px; @@ -995,7 +995,7 @@ $small-breakpoint: 960px; &__footer { margin-top: 10px; text-align: center; - color: $darker-text-color; + color: $dark-text-color; p { font-size: 14px; diff --git a/app/javascript/styles/mastodon/accounts.scss b/app/javascript/styles/mastodon/accounts.scss index f9af6f2..c2d0de4 100644 --- a/app/javascript/styles/mastodon/accounts.scss +++ b/app/javascript/styles/mastodon/accounts.scss @@ -178,7 +178,7 @@ font-size: 14px; line-height: 18px; padding: 0 15px; - color: $darker-text-color; + color: $secondary-text-color; } @media screen and (max-width: 480px) { @@ -256,7 +256,7 @@ .current { background: $simple-background-color; border-radius: 100px; - color: $lighter-text-color; + color: $inverted-text-color; cursor: default; margin: 0 10px; } @@ -268,7 +268,7 @@ .older, .newer { text-transform: uppercase; - color: $primary-text-color; + color: $secondary-text-color; } .older { @@ -293,7 +293,7 @@ .disabled { cursor: default; - color: opacify($lighter-text-color, 0.1); + color: lighten($inverted-text-color, 10%); } @media screen and (max-width: 700px) { @@ -332,7 +332,7 @@ width: 335px; background: $simple-background-color; border-radius: 4px; - color: $lighter-text-color; + color: $inverted-text-color; margin: 0 5px 10px; position: relative; @@ -344,7 +344,7 @@ overflow: hidden; height: 100px; border-radius: 4px 4px 0 0; - background-color: opacify($lighter-text-color, 0.04); + background-color: lighten($inverted-text-color, 4%); background-size: cover; background-position: center; position: relative; @@ -422,7 +422,7 @@ .account__header__content { padding: 10px 15px; padding-top: 15px; - color: transparentize($lighter-text-color, 0.1); + color: $lighter-text-color; word-wrap: break-word; overflow: hidden; text-overflow: ellipsis; @@ -434,7 +434,7 @@ .nothing-here { width: 100%; display: block; - color: $lighter-text-color; + color: $light-text-color; font-size: 14px; font-weight: 500; text-align: center; @@ -493,7 +493,7 @@ span { font-size: 14px; - color: $inverted-text-color; + color: $light-text-color; } } @@ -508,7 +508,7 @@ .account__header__content { font-size: 14px; - color: $darker-text-color; + color: $inverted-text-color; } } @@ -586,7 +586,7 @@ font-weight: 500; text-align: center; width: 94px; - color: opacify($darker-text-color, 0.1); + color: $secondary-text-color; background: rgba(darken($ui-base-color, 8%), 0.5); } diff --git a/app/javascript/styles/mastodon/admin.scss b/app/javascript/styles/mastodon/admin.scss index a0f6944..a6cc8b6 100644 --- a/app/javascript/styles/mastodon/admin.scss +++ b/app/javascript/styles/mastodon/admin.scss @@ -90,7 +90,7 @@ padding-left: 25px; h2 { - color: $primary-text-color; + color: $secondary-text-color; font-size: 24px; line-height: 28px; font-weight: 400; @@ -98,7 +98,7 @@ } h3 { - color: $primary-text-color; + color: $secondary-text-color; font-size: 20px; line-height: 28px; font-weight: 400; @@ -109,7 +109,7 @@ text-transform: uppercase; font-size: 13px; font-weight: 500; - color: $primary-text-color; + color: $darker-text-color; padding-bottom: 8px; margin-bottom: 8px; border-bottom: 1px solid lighten($ui-base-color, 8%); @@ -117,7 +117,7 @@ h6 { font-size: 16px; - color: $primary-text-color; + color: $secondary-text-color; line-height: 28px; font-weight: 400; } @@ -125,7 +125,7 @@ & > p { font-size: 14px; line-height: 18px; - color: $darker-text-color; + color: $secondary-text-color; margin-bottom: 20px; strong { @@ -292,7 +292,7 @@ font-weight: 500; font-size: 14px; line-height: 18px; - color: $primary-text-color; + color: $secondary-text-color; @each $lang in $cjk-langs { &:lang(#{$lang}) { @@ -420,7 +420,7 @@ } &__timestamp { - color: $darker-text-color; + color: $dark-text-color; } &__extras { @@ -437,7 +437,7 @@ &__icon { font-size: 28px; margin-right: 10px; - color: $darker-text-color; + color: $dark-text-color; } &__icon__overlay { @@ -464,7 +464,7 @@ a, .username, .target { - color: $primary-text-color; + color: $secondary-text-color; text-decoration: none; font-weight: 500; } @@ -474,7 +474,7 @@ } .diff-neutral { - color: $darker-text-color; + color: $secondary-text-color; } .diff-new { @@ -487,7 +487,7 @@ a.name-tag, display: flex; align-items: center; text-decoration: none; - color: $ui-secondary-color; + color: $secondary-text-color; .avatar { display: block; @@ -535,7 +535,7 @@ a.name-tag, font-weight: 500; a { - color: $ui-primary-color; + color: $darker-text-color; } } @@ -545,6 +545,6 @@ a.name-tag, } time { - color: $darker-text-color; + color: $dark-text-color; } } diff --git a/app/javascript/styles/mastodon/compact_header.scss b/app/javascript/styles/mastodon/compact_header.scss index 83ac7a8..4980ab5 100644 --- a/app/javascript/styles/mastodon/compact_header.scss +++ b/app/javascript/styles/mastodon/compact_header.scss @@ -2,7 +2,7 @@ h1 { font-size: 24px; line-height: 28px; - color: $primary-text-color; + color: $darker-text-color; font-weight: 500; margin-bottom: 20px; padding: 0 10px; @@ -20,7 +20,7 @@ small { font-weight: 400; - color: $darker-text-color; + color: $secondary-text-color; } img { diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index 908fa8a..1d0a7e9 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -31,7 +31,7 @@ &:active, &:focus, &:hover { - background-color: lighten($ui-highlight-color, 4%); + background-color: lighten($ui-highlight-color, 10%); transition: all 200ms ease-out; } @@ -83,7 +83,7 @@ } &.button-secondary { - color: $ui-primary-color; + color: $darker-text-color; background: transparent; padding: 3px 15px; border: 1px solid $ui-primary-color; @@ -92,7 +92,7 @@ &:focus, &:hover { border-color: lighten($ui-primary-color, 4%); - color: lighten($ui-primary-color, 4%); + color: lighten($darker-text-color, 4%); } } @@ -149,18 +149,18 @@ &:hover, &:active, &:focus { - color: transparentize($lighter-text-color, 0.07); + color: darken($lighter-text-color, 7%); } &.disabled { - color: opacify($lighter-text-color, 0.07); + color: lighten($lighter-text-color, 7%); } &.active { color: $highlight-text-color; &.disabled { - color: opacify($lighter-text-color, 0.13); + color: lighten($highlight-text-color, 13%); } } } @@ -193,12 +193,12 @@ &:hover, &:active, &:focus { - color: opacify($lighter-text-color, 0.07); + color: darken($lighter-text-color, 7%); transition: color 200ms ease-out; } &.disabled { - color: transparentize($lighter-text-color, 0.2); + color: lighten($lighter-text-color, 20%); cursor: default; } @@ -349,7 +349,7 @@ box-shadow: 4px 4px 6px rgba($base-shadow-color, 0.4); background: $ui-secondary-color; border-radius: 0 0 4px 4px; - color: $lighter-text-color; + color: $inverted-text-color; font-size: 14px; padding: 6px; @@ -457,7 +457,7 @@ input { background: transparent; - color: $primary-text-color; + color: $secondary-text-color; border: 0; padding: 0; margin: 0; @@ -471,8 +471,8 @@ } &::placeholder { - opacity: 0.54; - color: $darker-text-color; + opacity: 0.75; + color: $secondary-text-color; } } @@ -588,7 +588,7 @@ } .reply-indicator__display-name { - color: $lighter-text-color; + color: $inverted-text-color; display: block; max-width: 100%; line-height: 24px; @@ -643,14 +643,14 @@ } a { - color: $ui-secondary-color; + color: $secondary-text-color; text-decoration: none; &:hover { text-decoration: underline; .fa { - color: lighten($action-button-color, 7%); + color: lighten($dark-text-color, 7%); } } @@ -665,7 +665,7 @@ } .fa { - color: $action-button-color; + color: $dark-text-color; } } @@ -769,7 +769,7 @@ &.light { .status__relative-time { - color: $lighter-text-color; + color: $light-text-color; } .status__display-name { @@ -782,7 +782,7 @@ } span { - color: $lighter-text-color; + color: $light-text-color; } } @@ -816,13 +816,13 @@ } .status__relative-time { - color: $darker-text-color; + color: $dark-text-color; float: right; font-size: 14px; } .status__display-name { - color: $darker-text-color; + color: $dark-text-color; } .status__info .status__display-name { @@ -873,14 +873,14 @@ .status__prepend { margin-left: 68px; - color: $darker-text-color; + color: $dark-text-color; padding: 8px 0; padding-bottom: 2px; font-size: 14px; position: relative; .status__display-name strong { - color: $darker-text-color; + color: $dark-text-color; } > span { @@ -942,7 +942,7 @@ .detailed-status__meta { margin-top: 15px; - color: $darker-text-color; + color: $dark-text-color; font-size: 14px; line-height: 18px; } @@ -1091,7 +1091,7 @@ a .account__avatar { } .account__header__username { - color: $darker-text-color; + color: $secondary-text-color; } } @@ -1101,7 +1101,7 @@ a .account__avatar { } .account__header__content { - color: $darker-text-color; + color: $secondary-text-color; } .account__header__display-name { @@ -1129,7 +1129,7 @@ a .account__avatar { .account__disclaimer { padding: 10px; border-top: 1px solid lighten($ui-base-color, 8%); - color: $darker-text-color; + color: $dark-text-color; strong { font-weight: 500; @@ -1316,7 +1316,7 @@ a.account__display-name { } .detailed-status__display-name { - color: $darker-text-color; + color: $secondary-text-color; display: block; line-height: 24px; margin-bottom: 15px; @@ -1351,11 +1351,11 @@ a.account__display-name { .muted { .status__content p, .status__content a { - color: $darker-text-color; + color: $dark-text-color; } .status__display-name strong { - color: $darker-text-color; + color: $dark-text-color; } .status__avatar { @@ -1363,11 +1363,11 @@ a.account__display-name { } a.status__content__spoiler-link { - background: $darker-text-color; - color: lighten($ui-base-color, 4%); + background: $ui-base-lighter-color; + color: $inverted-text-color; &:hover { - background: transparentize($darker-text-color, 0.07); + background: lighten($ui-base-lighter-color, 7%); text-decoration: none; } } @@ -1378,7 +1378,7 @@ a.account__display-name { padding: 8px 0; padding-bottom: 0; cursor: default; - color: $ui-primary-color; + color: $darker-text-color; font-size: 15px; position: relative; @@ -1489,7 +1489,7 @@ a.account__display-name { color: $darker-text-color; strong { - color: $primary-text-color; + color: $secondary-text-color; } a { @@ -1603,7 +1603,7 @@ a.account__display-name { &:hover, &:active { background: $ui-highlight-color; - color: $primary-text-color; + color: $secondary-text-color; outline: 0; } } @@ -1656,7 +1656,7 @@ a.account__display-name { &:hover { background: $ui-highlight-color; - color: $primary-text-color; + color: $secondary-text-color; } } } @@ -1668,7 +1668,7 @@ a.account__display-name { .static-content { padding: 10px; padding-top: 20px; - color: $darker-text-color; + color: $dark-text-color; h1 { font-size: 16px; @@ -1755,7 +1755,7 @@ a.account__display-name { display: block; flex: 1 1 auto; padding: 15px 5px 13px; - color: $ui-primary-color; + color: $darker-text-color; text-decoration: none; text-align: center; font-size: 16px; @@ -2167,7 +2167,7 @@ a.account__display-name { .column-subheading { background: $ui-base-color; - color: $darker-text-color; + color: $dark-text-color; padding: 8px 20px; font-size: 12px; font-weight: 500; @@ -2190,11 +2190,11 @@ a.account__display-name { flex: 1 0 auto; p { - color: $darker-text-color; + color: $secondary-text-color; } a { - color: opacify($darker-text-color, 0.07); + color: $dark-text-color; } } @@ -2275,7 +2275,7 @@ a.account__display-name { font-size: 14px; border: 1px solid lighten($ui-base-color, 8%); border-radius: 4px; - color: $darker-text-color; + color: $dark-text-color; margin-top: 14px; text-decoration: none; overflow: hidden; @@ -2355,7 +2355,7 @@ a.status-card { display: block; font-weight: 500; margin-bottom: 5px; - color: $ui-primary-color; + color: $darker-text-color; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; @@ -2369,7 +2369,7 @@ a.status-card { } .status-card__description { - color: $ui-primary-color; + color: $darker-text-color; } .status-card__host { @@ -2413,7 +2413,7 @@ a.status-card { .load-more { display: block; - color: $darker-text-color; + color: $dark-text-color; background-color: transparent; border: 0; font-size: inherit; @@ -2437,7 +2437,7 @@ a.status-card { text-align: center; font-size: 16px; font-weight: 500; - color: opacify($darker-text-color, 0.07); + color: $dark-text-color; background: $ui-base-color; cursor: default; display: flex; @@ -2477,7 +2477,7 @@ a.status-card { strong { display: block; margin-bottom: 10px; - color: $darker-text-color; + color: $dark-text-color; } span { @@ -2565,13 +2565,13 @@ a.status-card { .column-header__button { background: lighten($ui-base-color, 4%); border: 0; - color: $ui-primary-color; + color: $darker-text-color; cursor: pointer; font-size: 16px; padding: 0 15px; &:hover { - color: lighten($ui-primary-color, 7%); + color: lighten($darker-text-color, 7%); } &.active { @@ -2652,7 +2652,7 @@ a.status-card { } .loading-indicator { - color: $darker-text-color; + color: $dark-text-color; font-size: 12px; font-weight: 400; text-transform: uppercase; @@ -2749,7 +2749,7 @@ a.status-card { &:active, &:focus { padding: 0; - color: transparentize($darker-text-color, 0.07); + color: lighten($darker-text-color, 8%); } } @@ -2873,7 +2873,7 @@ a.status-card { .empty-column-indicator, .error-column { - color: $darker-text-color; + color: $dark-text-color; background: $ui-base-color; text-align: center; padding: 20px; @@ -3075,7 +3075,7 @@ a.status-card { display: flex; align-items: center; justify-content: center; - color: $primary-text-color; + color: $secondary-text-color; font-size: 18px; font-weight: 500; border: 2px dashed $ui-base-lighter-color; @@ -3173,7 +3173,7 @@ a.status-card { } .privacy-dropdown__option { - color: $lighter-text-color; + color: $inverted-text-color; padding: 10px; cursor: pointer; display: flex; @@ -3295,7 +3295,7 @@ a.status-card { font-size: 18px; width: 18px; height: 18px; - color: $ui-secondary-color; + color: $secondary-text-color; cursor: default; pointer-events: none; @@ -3331,7 +3331,7 @@ a.status-card { } .search-results__header { - color: $darker-text-color; + color: $dark-text-color; background: lighten($ui-base-color, 2%); border-bottom: 1px solid darken($ui-base-color, 4%); padding: 15px 10px; @@ -3379,13 +3379,13 @@ a.status-card { .search-results__hashtag { display: block; padding: 10px; - color: darken($primary-text-color, 4%); + color: $secondary-text-color; text-decoration: none; &:hover, &:active, &:focus { - color: $primary-text-color; + color: lighten($secondary-text-color, 4%); text-decoration: underline; } } @@ -3650,7 +3650,7 @@ a.status-card { &:hover, &:focus, &:active { - color: transparentize($lighter-text-color, 0.04); + color: darken($lighter-text-color, 4%); background-color: darken($ui-secondary-color, 16%); } @@ -3744,7 +3744,7 @@ a.status-card { strong { font-weight: 500; background: $ui-base-color; - color: $primary-text-color; + color: $secondary-text-color; border-radius: 4px; font-size: 14px; padding: 3px 6px; @@ -3804,7 +3804,7 @@ a.status-card { &__case { background: $ui-base-color; - color: $primary-text-color; + color: $secondary-text-color; font-weight: 500; padding: 10px; border-radius: 4px; @@ -3821,7 +3821,7 @@ a.status-card { .figure { background: darken($ui-base-color, 8%); - color: $darker-text-color; + color: $secondary-text-color; margin-bottom: 20px; border-radius: 4px; padding: 10px; @@ -3933,7 +3933,7 @@ a.status-card { } .status__content__spoiler-link { - color: lighten($ui-secondary-color, 8%); + color: lighten($secondary-text-color, 8%); } } @@ -4163,7 +4163,7 @@ a.status-card { &:hover, &:focus, &:active { - color: transparentize($lighter-text-color, 0.04); + color: darken($lighter-text-color, 4%); } } } @@ -4244,7 +4244,7 @@ a.status-card { &__icon { flex: 0 0 auto; - color: $darker-text-color; + color: $dark-text-color; padding: 8px 18px; cursor: default; border-right: 1px solid lighten($ui-base-color, 8%); @@ -4274,7 +4274,7 @@ a.status-card { a { text-decoration: none; - color: $darker-text-color; + color: $dark-text-color; font-weight: 500; &:hover { @@ -4293,7 +4293,7 @@ a.status-card { } .fa { - color: $darker-text-color; + color: $dark-text-color; } } } @@ -4329,7 +4329,7 @@ a.status-card { cursor: zoom-in; display: block; text-decoration: none; - color: $ui-secondary-color; + color: $secondary-text-color; line-height: 0; &, @@ -4500,7 +4500,7 @@ a.status-card { &:hover, &:active, &:focus { - color: transparentize($darker-text-color, 0.07); + color: lighten($darker-text-color, 7%); } } @@ -4705,7 +4705,7 @@ a.status-card { &:active, &:focus { outline: 0; - color: transparentize($darker-text-color, 0.07); + color: $secondary-text-color; &::before { content: ""; @@ -4745,7 +4745,7 @@ a.status-card { position: relative; &.active { - color: transparentize($darker-text-color, 0.07); + color: $secondary-text-color; &::before, &::after { @@ -4780,12 +4780,12 @@ a.status-card { padding: 10px 14px; padding-bottom: 14px; margin-top: 10px; - color: $lighter-text-color; + color: $light-text-color; box-shadow: 2px 4px 15px rgba($base-shadow-color, 0.4); h4 { text-transform: uppercase; - color: $lighter-text-color; + color: $light-text-color; font-size: 13px; font-weight: 500; margin-bottom: 10px; @@ -4817,7 +4817,7 @@ noscript { div { font-size: 14px; margin: 30px auto; - color: $primary-text-color; + color: $secondary-text-color; max-width: 400px; a { @@ -4970,7 +4970,7 @@ noscript { &__message { position: relative; margin-left: 58px; - color: $darker-text-color; + color: $dark-text-color; padding: 8px 0; padding-top: 0; padding-bottom: 4px; diff --git a/app/javascript/styles/mastodon/containers.scss b/app/javascript/styles/mastodon/containers.scss index 8df2902..9d5ab66 100644 --- a/app/javascript/styles/mastodon/containers.scss +++ b/app/javascript/styles/mastodon/containers.scss @@ -100,7 +100,7 @@ .name { flex: 1 1 auto; - color: $darker-text-color; + color: $secondary-text-color; width: calc(100% - 88px); .username { diff --git a/app/javascript/styles/mastodon/emoji_picker.scss b/app/javascript/styles/mastodon/emoji_picker.scss index 3620a6f..cf95475 100644 --- a/app/javascript/styles/mastodon/emoji_picker.scss +++ b/app/javascript/styles/mastodon/emoji_picker.scss @@ -50,7 +50,7 @@ cursor: pointer; &:hover { - color: opacify($lighter-text-color, 0.04); + color: darken($lighter-text-color, 4%); } } @@ -184,7 +184,7 @@ font-size: 14px; text-align: center; padding-top: 70px; - color: $lighter-text-color; + color: $light-text-color; .emoji-mart-category-label { display: none; diff --git a/app/javascript/styles/mastodon/forms.scss b/app/javascript/styles/mastodon/forms.scss index 3a3b4c3..f978901 100644 --- a/app/javascript/styles/mastodon/forms.scss +++ b/app/javascript/styles/mastodon/forms.scss @@ -248,7 +248,7 @@ code { } &:required:valid { - border-bottom-color: lighten($error-red, 12%); + border-bottom-color: $valid-value-color; } &:active, @@ -266,7 +266,7 @@ code { input[type=text], input[type=email], input[type=password] { - border-bottom-color: lighten($error-red, 12%); + border-bottom-color: $valid-value-color; } .error { @@ -356,7 +356,7 @@ code { padding: 7px 4px; padding-bottom: 9px; font-size: 16px; - color: $darker-text-color; + color: $dark-text-color; font-family: inherit; pointer-events: none; cursor: default; @@ -446,7 +446,7 @@ code { } strong { - color: $primary-text-color; + color: $secondary-text-color; font-weight: 500; @each $lang in $cjk-langs { @@ -483,7 +483,7 @@ code { .qr-alternative { margin-bottom: 20px; - color: $darker-text-color; + color: $secondary-text-color; flex: 150px; samp { diff --git a/app/javascript/styles/mastodon/landing_strip.scss b/app/javascript/styles/mastodon/landing_strip.scss index 651c06c..86614b8 100644 --- a/app/javascript/styles/mastodon/landing_strip.scss +++ b/app/javascript/styles/mastodon/landing_strip.scss @@ -45,7 +45,7 @@ padding: 14px; border-radius: 4px; background: rgba(darken($ui-base-color, 7%), 0.8); - color: $darker-text-color; + color: $secondary-text-color; font-weight: 400; margin-bottom: 20px; diff --git a/app/javascript/styles/mastodon/stream_entries.scss b/app/javascript/styles/mastodon/stream_entries.scss index c39163b..281cbaf 100644 --- a/app/javascript/styles/mastodon/stream_entries.scss +++ b/app/javascript/styles/mastodon/stream_entries.scss @@ -93,7 +93,7 @@ display: block; max-width: 100%; padding-right: 25px; - color: $lighter-text-color; + color: $inverted-text-color; } .status__avatar { @@ -134,7 +134,7 @@ span { font-size: 14px; - color: $inverted-text-color; + color: $light-text-color; } } @@ -191,7 +191,7 @@ span { font-size: 14px; - color: $lighter-text-color; + color: $light-text-color; } } } @@ -225,7 +225,7 @@ .detailed-status__meta { margin-top: 15px; - color: $lighter-text-color; + color: $light-text-color; font-size: 14px; line-height: 18px; @@ -270,7 +270,7 @@ padding-left: (48px + 14px * 2); padding-bottom: 0; margin-bottom: -4px; - color: $lighter-text-color; + color: $light-text-color; font-size: 14px; position: relative; @@ -280,7 +280,7 @@ } .status__display-name.muted strong { - color: $lighter-text-color; + color: $light-text-color; } } @@ -293,7 +293,7 @@ } .more { - color: $classic-primary-color; + color: $darker-text-color; display: block; padding: 14px; text-align: center; diff --git a/app/javascript/styles/mastodon/variables.scss b/app/javascript/styles/mastodon/variables.scss index dc4e72a..cbefe35 100644 --- a/app/javascript/styles/mastodon/variables.scss +++ b/app/javascript/styles/mastodon/variables.scss @@ -17,12 +17,6 @@ $base-shadow-color: $black !default; $base-overlay-background: $black !default; $base-border-color: $white !default; $simple-background-color: $white !default; -$primary-text-color: $white !default; -$darker-text-color: rgba($primary-text-color, 0.7) !default; -$highlight-text-color: $classic-highlight-color !default; -$inverted-text-color: $black !default; -$lighter-text-color: rgba($inverted-text-color, 0.7) !default; -$action-button-color: #8d9ac2; $valid-value-color: $success-green !default; $error-value-color: $error-red !default; @@ -31,7 +25,19 @@ $ui-base-color: $classic-base-color !default; // Darkest $ui-base-lighter-color: lighten($ui-base-color, 26%) !default; // Lighter darkest $ui-primary-color: $classic-primary-color !default; // Lighter $ui-secondary-color: $classic-secondary-color !default; // Lightest -$ui-highlight-color: #2b5fd9; +$ui-highlight-color: $classic-highlight-color !default; + +// Variables for texts +$primary-text-color: $white !default; +$darker-text-color: $ui-primary-color !default; +$dark-text-color: $ui-base-lighter-color !default; +$secondary-text-color: $ui-secondary-color !default; +$highlight-text-color: $ui-highlight-color !default; +$action-button-color: $ui-base-lighter-color !default; +// For texts on inverted backgrounds +$inverted-text-color: $ui-base-color !default; +$lighter-text-color: $ui-base-lighter-color !default; +$light-text-color: $ui-primary-color !default; // Language codes that uses CJK fonts $cjk-langs: ja, ko, zh-CN, zh-HK, zh-TW; diff --git a/config/locales/en.yml b/config/locales/en.yml index 53b64a1..8b66b91 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -785,6 +785,7 @@ en:

    Originally adapted from the Discourse privacy policy.

    title: "%{instance} Terms of Service and Privacy Policy" themes: + contrast: High contrast default: Mastodon time: formats: diff --git a/config/themes.yml b/config/themes.yml index a1049fa..f0bb1e6 100644 --- a/config/themes.yml +++ b/config/themes.yml @@ -1 +1,2 @@ default: styles/application.scss +contrast: styles/contrast.scss From b8f0cfd6e34bc1285e952072a8c8a0eeafd8918c Mon Sep 17 00:00:00 2001 From: Yamagishi Kazutoshi Date: Sun, 22 Apr 2018 04:36:22 +0900 Subject: [PATCH 165/381] Add parallel test processors (#7215) --- .circleci/config.yml | 7 ++----- .env.test | 2 ++ 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index e3a9628..c5d6ec9 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -6,13 +6,10 @@ aliases: - image: circleci/ruby:2.5.1-stretch-node environment: &ruby_environment BUNDLE_APP_CONFIG: ./.bundle/ - RAILS_ENV: test - NODE_ENV: test DB_HOST: localhost DB_USER: root - LOCAL_DOMAIN: cb6e6126.ngrok.io - LOCAL_HTTPS: true - PARALLEL_TEST_PROCESSORS: 2 + RAILS_ENV: test + PARALLEL_TEST_PROCESSORS: 4 ALLOW_NOPAM: true working_directory: ~/projects/mastodon/ diff --git a/.env.test b/.env.test index 7da76f8..726351c 100644 --- a/.env.test +++ b/.env.test @@ -1,3 +1,5 @@ +# Node.js +NODE_ENV=test # Federation LOCAL_DOMAIN=cb6e6126.ngrok.io LOCAL_HTTPS=true From 9b8bb2a5df2839041ccd33e4ae7ec81b8746ddb2 Mon Sep 17 00:00:00 2001 From: Yamagishi Kazutoshi Date: Sun, 22 Apr 2018 04:56:40 +0900 Subject: [PATCH 166/381] Replace badge to CircleCI (#7216) --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7b85b16..34d56c9 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ ![Mastodon](https://i.imgur.com/NhZc40l.png) ======== -[![Build Status](https://img.shields.io/travis/tootsuite/mastodon.svg)][travis] +[![Build Status](https://img.shields.io/circleci/project/github/tootsuite/mastodon.svg)][circleci] [![Code Climate](https://img.shields.io/codeclimate/maintainability/tootsuite/mastodon.svg)][code_climate] -[travis]: https://travis-ci.org/tootsuite/mastodon +[circleci]: https://circleci.com/gh/tootsuite/mastodon [code_climate]: https://codeclimate.com/github/tootsuite/mastodon Mastodon is a **free, open-source social network server** based on **open web protocols** like ActivityPub and OStatus. The social focus of the project is a viable decentralized alternative to commercial social media silos that returns the control of the content distribution channels to the people. The technical focus of the project is a good user interface, a clean REST API for 3rd party apps and robust anti-abuse tools. From 3f6893c6419c842f32dc0727db8d46dd8e457777 Mon Sep 17 00:00:00 2001 From: Yamagishi Kazutoshi Date: Sun, 22 Apr 2018 06:37:07 +0900 Subject: [PATCH 167/381] Reset locale on registration tests (#7219) --- spec/controllers/auth/registrations_controller_spec.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/spec/controllers/auth/registrations_controller_spec.rb b/spec/controllers/auth/registrations_controller_spec.rb index 9cfbd48..eeb01d5 100644 --- a/spec/controllers/auth/registrations_controller_spec.rb +++ b/spec/controllers/auth/registrations_controller_spec.rb @@ -73,6 +73,12 @@ RSpec.describe Auth::RegistrationsController, type: :controller do describe 'POST #create' do let(:accept_language) { Rails.application.config.i18n.available_locales.sample.to_s } + around do |example| + current_locale = I18n.locale + example.run + I18n.locale = current_locale + end + before { request.env["devise.mapping"] = Devise.mappings[:user] } context do From 3fa316147228819c48fd4f20c5c30f4f34b85f07 Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Sun, 22 Apr 2018 05:28:12 +0200 Subject: [PATCH 168/381] Fix: Use "welches" instead of "dass" in translation (#7185) --- config/locales/de.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/locales/de.yml b/config/locales/de.yml index 51936aa..221437a 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -428,7 +428,7 @@ de: archive_takeout: date: Datum download: Dein Archiv herunterladen - hint_html: Du kannst ein Archiv deiner Beiträge und hochgeladenen Medien anfragen. Die exportieren Daten werden im ActivityPub-Format gespeichert, dass lesbar mit jeder Software ist, die das Format unterstützt. + hint_html: Du kannst ein Archiv deiner Beiträge und hochgeladenen Medien anfragen. Die exportieren Daten werden im ActivityPub-Format gespeichert, welches mit jeder Software lesbar ist die das Format unterstützt. in_progress: Stelle dein Archiv zusammen... request: Dein Archiv anfragen size: Größe From 648d645c2fc4e7b266cb4d83bd4ed62e929dd363 Mon Sep 17 00:00:00 2001 From: Yamagishi Kazutoshi Date: Sun, 22 Apr 2018 18:41:39 +0900 Subject: [PATCH 169/381] Fix randomly fail (similar #7219) (#7225) --- spec/controllers/concerns/localized_spec.rb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/spec/controllers/concerns/localized_spec.rb b/spec/controllers/concerns/localized_spec.rb index f71c96a..8c80b7d 100644 --- a/spec/controllers/concerns/localized_spec.rb +++ b/spec/controllers/concerns/localized_spec.rb @@ -11,13 +11,17 @@ describe ApplicationController, type: :controller do end end + around do |example| + current_locale = I18n.locale + example.run + I18n.locale = current_locale + end + before do routes.draw { get 'success' => 'anonymous#success' } end shared_examples 'default locale' do - after { I18n.locale = I18n.default_locale } - it 'sets available and preferred language' do request.headers['Accept-Language'] = 'ca-ES, fa' get 'success' From ca9192d9ba1c27689d7a14d0fb5b426c3d73c9f4 Mon Sep 17 00:00:00 2001 From: David Baucum Date: Sun, 22 Apr 2018 05:49:16 -0400 Subject: [PATCH 170/381] Ability to specify Redis passwd on mastodon:setup (#7222) Closes #7221 --- lib/tasks/mastodon.rake | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/tasks/mastodon.rake b/lib/tasks/mastodon.rake index 505c7e0..b5a1483 100644 --- a/lib/tasks/mastodon.rake +++ b/lib/tasks/mastodon.rake @@ -107,9 +107,16 @@ namespace :mastodon do q.convert :int end + env['REDIS_PASSWORD'] = prompt.ask('Redis password:') do |q| + q.required false + a.default nil + q.modify :strip + end + redis_options = { host: env['REDIS_HOST'], port: env['REDIS_PORT'], + password: env['REDIS_PASSWORD'], driver: :hiredis, } From 597948fb1316d2e1de76be330db441786c571132 Mon Sep 17 00:00:00 2001 From: ThibG Date: Sun, 22 Apr 2018 12:10:37 +0200 Subject: [PATCH 171/381] Do not set emoji as inline-block (fixes #5743) (#7207) --- app/javascript/styles/mastodon/components.scss | 1 - 1 file changed, 1 deletion(-) diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index 1d0a7e9..f6e403b 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -556,7 +556,6 @@ } .emojione { - display: inline-block; font-size: inherit; vertical-align: middle; object-fit: contain; From 3c5006ec7fa98ec53df619b0e6e52b3bbdb6f046 Mon Sep 17 00:00:00 2001 From: Lynx Kotoura Date: Sun, 22 Apr 2018 21:29:40 +0900 Subject: [PATCH 172/381] Fix text colors (#7227) --- app/javascript/styles/contrast/variables.scss | 2 ++ app/javascript/styles/mastodon/components.scss | 6 +++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/app/javascript/styles/contrast/variables.scss b/app/javascript/styles/contrast/variables.scss index 35d1106..f6cadf0 100644 --- a/app/javascript/styles/contrast/variables.scss +++ b/app/javascript/styles/contrast/variables.scss @@ -4,6 +4,7 @@ $black: #000000; $classic-base-color: #282c37; $classic-primary-color: #9baec8; $classic-secondary-color: #d9e1e8; +$classic-highlight-color: #2b90d9; $ui-base-color: $classic-base-color !default; $ui-primary-color: $classic-primary-color !default; @@ -15,6 +16,7 @@ $ui-highlight-color: #2b5fd9; $darker-text-color: lighten($ui-primary-color, 20%) !default; $dark-text-color: lighten($ui-primary-color, 12%) !default; $secondary-text-color: lighten($ui-secondary-color, 6%) !default; +$highlight-text-color: $classic-highlight-color !default; $action-button-color: #8d9ac2; $inverted-text-color: $black !default; diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index f6e403b..c84e613 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -701,7 +701,7 @@ border-radius: 2px; background: transparent; border: 0; - color: $lighter-text-color; + color: $inverted-text-color; font-weight: 700; font-size: 11px; padding: 0 6px; @@ -4037,6 +4037,10 @@ a.status-card { overflow-y: auto; overflow-x: hidden; + .status__content a { + color: $highlight-text-color; + } + @media screen and (max-width: 480px) { max-height: 10vh; } From b305a2393378fb365f26f7b440a67be2d415c4e2 Mon Sep 17 00:00:00 2001 From: jumoru Date: Sun, 22 Apr 2018 14:46:19 +0200 Subject: [PATCH 173/381] Fix: Use "exportierten" instead of "exportieren" in translation (#7186) Spotted when looking at https://metalhead.club/@thomas/99881521526619858 --- config/locales/de.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/locales/de.yml b/config/locales/de.yml index 221437a..f4f842a 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -428,7 +428,7 @@ de: archive_takeout: date: Datum download: Dein Archiv herunterladen - hint_html: Du kannst ein Archiv deiner Beiträge und hochgeladenen Medien anfragen. Die exportieren Daten werden im ActivityPub-Format gespeichert, welches mit jeder Software lesbar ist die das Format unterstützt. + hint_html: Du kannst ein Archiv deiner Beiträge und hochgeladenen Medien anfragen. Die exportierten Daten werden im ActivityPub-Format gespeichert, welches mit jeder Software lesbar ist die das Format unterstützt. in_progress: Stelle dein Archiv zusammen... request: Dein Archiv anfragen size: Größe From 4ca2f73b126de89a917680d60f40cae7f589f55f Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Sun, 22 Apr 2018 15:42:00 +0200 Subject: [PATCH 174/381] Rescue Mastodon::LengthValidationError in Remoteable (#7228) Fix #7198 by allowing records with optional attachments to save --- app/models/concerns/remotable.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/concerns/remotable.rb b/app/models/concerns/remotable.rb index 3b8c507..7f1ef51 100644 --- a/app/models/concerns/remotable.rb +++ b/app/models/concerns/remotable.rb @@ -38,7 +38,7 @@ module Remotable self[attribute_name] = url if has_attribute?(attribute_name) end - rescue HTTP::TimeoutError, HTTP::ConnectionError, OpenSSL::SSL::SSLError, Paperclip::Errors::NotIdentifiedByImageMagickError, Addressable::URI::InvalidURIError, Mastodon::HostValidationError => e + rescue HTTP::TimeoutError, HTTP::ConnectionError, OpenSSL::SSL::SSLError, Paperclip::Errors::NotIdentifiedByImageMagickError, Addressable::URI::InvalidURIError, Mastodon::HostValidationError, Mastodon::LengthValidationError => e Rails.logger.debug "Error fetching remote #{attachment_name}: #{e}" nil end From 75c4ab9d12d3a2f3de52c51b5006fe9d5d9afae4 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Sun, 22 Apr 2018 22:09:03 +0200 Subject: [PATCH 175/381] Remove "nsfw" category for sensitive statuses in OStatus serializer (#7048) Fix #7011 --- app/lib/ostatus/atom_serializer.rb | 2 -- app/services/process_hashtags_service.rb | 2 -- spec/lib/ostatus/atom_serializer_spec.rb | 6 ------ 3 files changed, 10 deletions(-) diff --git a/app/lib/ostatus/atom_serializer.rb b/app/lib/ostatus/atom_serializer.rb index 055b464..7c66f20 100644 --- a/app/lib/ostatus/atom_serializer.rb +++ b/app/lib/ostatus/atom_serializer.rb @@ -364,8 +364,6 @@ class OStatus::AtomSerializer append_element(entry, 'category', nil, term: tag.name) end - append_element(entry, 'category', nil, term: 'nsfw') if status.sensitive? - status.media_attachments.each do |media| append_element(entry, 'link', nil, rel: :enclosure, type: media.file_content_type, length: media.file_file_size, href: full_asset_url(media.file.url(:original, false))) end diff --git a/app/services/process_hashtags_service.rb b/app/services/process_hashtags_service.rb index 990e01a..5b45c86 100644 --- a/app/services/process_hashtags_service.rb +++ b/app/services/process_hashtags_service.rb @@ -7,7 +7,5 @@ class ProcessHashtagsService < BaseService tags.map { |str| str.mb_chars.downcase }.uniq(&:to_s).each do |tag| status.tags << Tag.where(name: tag).first_or_initialize(name: tag) end - - status.update(sensitive: true) if tags.include?('nsfw') end end diff --git a/spec/lib/ostatus/atom_serializer_spec.rb b/spec/lib/ostatus/atom_serializer_spec.rb index 00e6f09..d467913 100644 --- a/spec/lib/ostatus/atom_serializer_spec.rb +++ b/spec/lib/ostatus/atom_serializer_spec.rb @@ -386,12 +386,6 @@ RSpec.describe OStatus::AtomSerializer do expect(entry.category[:term]).to eq 'tag' end - it 'appends category element for NSFW if status is sensitive' do - status = Fabricate(:status, sensitive: true) - entry = OStatus::AtomSerializer.new.entry(status.stream_entry) - expect(entry.category[:term]).to eq 'nsfw' - end - it 'appends link elements for media attachments' do file = attachment_fixture('attachment.jpg') media_attachment = Fabricate(:media_attachment, file: file) From 05fb6f096db7c58698f8e0bc7fc79e129b241176 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 23 Apr 2018 00:43:36 +0200 Subject: [PATCH 176/381] Resize images before upload in web UI to reduce bandwidth (#7223) * Resize images before upload in web UI to reduce bandwidth Fix #7218 * Fix issues * Do not resize GIFs in JS --- app/javascript/mastodon/actions/compose.js | 97 ++++++++++++++++++++++++++---- 1 file changed, 86 insertions(+), 11 deletions(-) diff --git a/app/javascript/mastodon/actions/compose.js b/app/javascript/mastodon/actions/compose.js index eee9c69..c8ea5aa 100644 --- a/app/javascript/mastodon/actions/compose.js +++ b/app/javascript/mastodon/actions/compose.js @@ -174,6 +174,79 @@ export function submitComposeFail(error) { }; }; +const MAX_IMAGE_DIMENSION = 1280; + +const dataURLtoBlob = dataURL => { + const BASE64_MARKER = ';base64,'; + + if (dataURL.indexOf(BASE64_MARKER) === -1) { + const parts = dataURL.split(','); + const contentType = parts[0].split(':')[1]; + const raw = parts[1]; + + return new Blob([raw], { type: contentType }); + } + + const parts = dataURL.split(BASE64_MARKER); + const contentType = parts[0].split(':')[1]; + const raw = window.atob(parts[1]); + const rawLength = raw.length; + + const uInt8Array = new Uint8Array(rawLength); + + for (let i = 0; i < rawLength; ++i) { + uInt8Array[i] = raw.charCodeAt(i); + } + + return new Blob([uInt8Array], { type: contentType }); +}; + +const resizeImage = (inputFile, callback) => { + if (inputFile.type.match(/image.*/) && inputFile.type !== 'image/gif') { + const reader = new FileReader(); + + reader.onload = e => { + const img = new Image(); + + img.onload = () => { + const canvas = document.createElement('canvas'); + const { width, height } = img; + + let newWidth, newHeight; + + if (width < MAX_IMAGE_DIMENSION && height < MAX_IMAGE_DIMENSION) { + callback(inputFile); + return; + } + + if (width > height) { + newHeight = height * MAX_IMAGE_DIMENSION / width; + newWidth = MAX_IMAGE_DIMENSION; + } else if (height > width) { + newWidth = width * MAX_IMAGE_DIMENSION / height; + newHeight = MAX_IMAGE_DIMENSION; + } else { + newWidth = MAX_IMAGE_DIMENSION; + newHeight = MAX_IMAGE_DIMENSION; + } + + canvas.width = newWidth; + canvas.height = newHeight; + + canvas.getContext('2d').drawImage(img, 0, 0, newWidth, newHeight); + + callback(dataURLtoBlob(canvas.toDataURL(inputFile.type))); + }; + + img.src = e.target.result; + }; + + reader.readAsDataURL(inputFile); + } else { + callback(inputFile); + } +}; + export function uploadCompose(files) { return function (dispatch, getState) { if (getState().getIn(['compose', 'media_attachments']).size > 3) { @@ -182,17 +255,19 @@ export function uploadCompose(files) { dispatch(uploadComposeRequest()); - let data = new FormData(); - data.append('file', files[0]); - - api(getState).post('/api/v1/media', data, { - onUploadProgress: function (e) { - dispatch(uploadComposeProgress(e.loaded, e.total)); - }, - }).then(function (response) { - dispatch(uploadComposeSuccess(response.data)); - }).catch(function (error) { - dispatch(uploadComposeFail(error)); + resizeImage(files[0], file => { + let data = new FormData(); + data.append('file', file); + + api(getState).post('/api/v1/media', data, { + onUploadProgress: function (e) { + dispatch(uploadComposeProgress(e.loaded, e.total)); + }, + }).then(function (response) { + dispatch(uploadComposeSuccess(response.data)); + }).catch(function (error) { + dispatch(uploadComposeFail(error)); + }); }); }; }; From 660cb058e18f8607a0044b5a89614e1caeb07ed9 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 23 Apr 2018 00:43:53 +0200 Subject: [PATCH 177/381] Improve relative timestamps in web UI (#7233) Use short instead of numeric month, display year when different year E.g.: "Apr 4" instead of "4/4", "Apr 4, 2017" if different year --- app/javascript/mastodon/components/relative_timestamp.js | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/app/javascript/mastodon/components/relative_timestamp.js b/app/javascript/mastodon/components/relative_timestamp.js index 51588e7..3c8db70 100644 --- a/app/javascript/mastodon/components/relative_timestamp.js +++ b/app/javascript/mastodon/components/relative_timestamp.js @@ -20,7 +20,7 @@ const dateFormatOptions = { }; const shortDateFormatOptions = { - month: 'numeric', + month: 'short', day: 'numeric', }; @@ -66,12 +66,17 @@ export default class RelativeTimestamp extends React.Component { static propTypes = { intl: PropTypes.object.isRequired, timestamp: PropTypes.string.isRequired, + year: PropTypes.number.isRequired, }; state = { now: this.props.intl.now(), }; + static defaultProps = { + year: (new Date()).getFullYear(), + }; + shouldComponentUpdate (nextProps, nextState) { // As of right now the locale doesn't change without a new page load, // but we might as well check in case that ever changes. @@ -114,7 +119,7 @@ export default class RelativeTimestamp extends React.Component { } render () { - const { timestamp, intl } = this.props; + const { timestamp, intl, year } = this.props; const date = new Date(timestamp); const delta = this.state.now - date.getTime(); @@ -123,7 +128,7 @@ export default class RelativeTimestamp extends React.Component { if (delta < 10 * SECOND) { relativeTime = intl.formatMessage(messages.just_now); - } else if (delta < 3 * DAY) { + } else if (delta < 7 * DAY) { if (delta < MINUTE) { relativeTime = intl.formatMessage(messages.seconds, { number: Math.floor(delta / SECOND) }); } else if (delta < HOUR) { @@ -133,8 +138,10 @@ export default class RelativeTimestamp extends React.Component { } else { relativeTime = intl.formatMessage(messages.days, { number: Math.floor(delta / DAY) }); } - } else { + } else if (date.getFullYear() === year) { relativeTime = intl.formatDate(date, shortDateFormatOptions); + } else { + relativeTime = intl.formatDate(date, { ...shortDateFormatOptions, year: 'numeric' }); } return ( From 0758b00bfddf4a2c845d0d611e50717a268fd48a Mon Sep 17 00:00:00 2001 From: Yamagishi Kazutoshi Date: Mon, 23 Apr 2018 16:15:51 +0900 Subject: [PATCH 178/381] Refactor resizeImage method (#7236) - Use URL.createObjectURL (replace from FileReader) - Use HTMLCanvasElement.prototype.toBlob (replace from HTMLCanvasElement.prototype.toDataURL) - Use Promise (replace callback interface) --- app/javascript/mastodon/actions/compose.js | 92 ++-------------------- .../actions/push_notifications/registerer.js | 9 +-- app/javascript/mastodon/base_polyfills.js | 21 +++++ app/javascript/mastodon/load_polyfills.js | 7 +- .../mastodon/utils/__tests__/base64-test.js | 10 +++ app/javascript/mastodon/utils/base64.js | 10 +++ app/javascript/mastodon/utils/resize_image.js | 66 ++++++++++++++++ 7 files changed, 120 insertions(+), 95 deletions(-) create mode 100644 app/javascript/mastodon/utils/__tests__/base64-test.js create mode 100644 app/javascript/mastodon/utils/base64.js create mode 100644 app/javascript/mastodon/utils/resize_image.js diff --git a/app/javascript/mastodon/actions/compose.js b/app/javascript/mastodon/actions/compose.js index c8ea5aa..fe3e831 100644 --- a/app/javascript/mastodon/actions/compose.js +++ b/app/javascript/mastodon/actions/compose.js @@ -4,6 +4,7 @@ import { throttle } from 'lodash'; import { search as emojiSearch } from '../features/emoji/emoji_mart_search_light'; import { tagHistory } from '../settings'; import { useEmoji } from './emojis'; +import resizeImage from '../utils/resize_image'; import { importFetchedAccounts } from './importer'; import { updateTimeline } from './timelines'; import { showAlertForError } from './alerts'; @@ -174,79 +175,6 @@ export function submitComposeFail(error) { }; }; -const MAX_IMAGE_DIMENSION = 1280; - -const dataURLtoBlob = dataURL => { - const BASE64_MARKER = ';base64,'; - - if (dataURL.indexOf(BASE64_MARKER) === -1) { - const parts = dataURL.split(','); - const contentType = parts[0].split(':')[1]; - const raw = parts[1]; - - return new Blob([raw], { type: contentType }); - } - - const parts = dataURL.split(BASE64_MARKER); - const contentType = parts[0].split(':')[1]; - const raw = window.atob(parts[1]); - const rawLength = raw.length; - - const uInt8Array = new Uint8Array(rawLength); - - for (let i = 0; i < rawLength; ++i) { - uInt8Array[i] = raw.charCodeAt(i); - } - - return new Blob([uInt8Array], { type: contentType }); -}; - -const resizeImage = (inputFile, callback) => { - if (inputFile.type.match(/image.*/) && inputFile.type !== 'image/gif') { - const reader = new FileReader(); - - reader.onload = e => { - const img = new Image(); - - img.onload = () => { - const canvas = document.createElement('canvas'); - const { width, height } = img; - - let newWidth, newHeight; - - if (width < MAX_IMAGE_DIMENSION && height < MAX_IMAGE_DIMENSION) { - callback(inputFile); - return; - } - - if (width > height) { - newHeight = height * MAX_IMAGE_DIMENSION / width; - newWidth = MAX_IMAGE_DIMENSION; - } else if (height > width) { - newWidth = width * MAX_IMAGE_DIMENSION / height; - newHeight = MAX_IMAGE_DIMENSION; - } else { - newWidth = MAX_IMAGE_DIMENSION; - newHeight = MAX_IMAGE_DIMENSION; - } - - canvas.width = newWidth; - canvas.height = newHeight; - - canvas.getContext('2d').drawImage(img, 0, 0, newWidth, newHeight); - - callback(dataURLtoBlob(canvas.toDataURL(inputFile.type))); - }; - - img.src = e.target.result; - }; - - reader.readAsDataURL(inputFile); - } else { - callback(inputFile); - } -}; - export function uploadCompose(files) { return function (dispatch, getState) { if (getState().getIn(['compose', 'media_attachments']).size > 3) { @@ -255,20 +183,14 @@ export function uploadCompose(files) { dispatch(uploadComposeRequest()); - resizeImage(files[0], file => { - let data = new FormData(); + resizeImage(files[0]).then(file => { + const data = new FormData(); data.append('file', file); - api(getState).post('/api/v1/media', data, { - onUploadProgress: function (e) { - dispatch(uploadComposeProgress(e.loaded, e.total)); - }, - }).then(function (response) { - dispatch(uploadComposeSuccess(response.data)); - }).catch(function (error) { - dispatch(uploadComposeFail(error)); - }); - }); + return api(getState).post('/api/v1/media', data, { + onUploadProgress: ({ loaded, total }) => dispatch(uploadComposeProgress(loaded, total)), + }).then(({ data }) => dispatch(uploadComposeSuccess(data))); + }).catch(error => dispatch(uploadComposeFail(error))); }; }; diff --git a/app/javascript/mastodon/actions/push_notifications/registerer.js b/app/javascript/mastodon/actions/push_notifications/registerer.js index 60b215f..82fe451 100644 --- a/app/javascript/mastodon/actions/push_notifications/registerer.js +++ b/app/javascript/mastodon/actions/push_notifications/registerer.js @@ -1,4 +1,5 @@ import api from '../../api'; +import { decode as decodeBase64 } from '../../utils/base64'; import { pushNotificationsSetting } from '../../settings'; import { setBrowserSupport, setSubscription, clearSubscription } from './setter'; import { me } from '../../initial_state'; @@ -10,13 +11,7 @@ const urlBase64ToUint8Array = (base64String) => { .replace(/\-/g, '+') .replace(/_/g, '/'); - const rawData = window.atob(base64); - const outputArray = new Uint8Array(rawData.length); - - for (let i = 0; i < rawData.length; ++i) { - outputArray[i] = rawData.charCodeAt(i); - } - return outputArray; + return decodeBase64(base64); }; const getApplicationServerKey = () => document.querySelector('[name="applicationServerKey"]').getAttribute('content'); diff --git a/app/javascript/mastodon/base_polyfills.js b/app/javascript/mastodon/base_polyfills.js index 8fbb177..997813a 100644 --- a/app/javascript/mastodon/base_polyfills.js +++ b/app/javascript/mastodon/base_polyfills.js @@ -5,6 +5,7 @@ import includes from 'array-includes'; import assign from 'object-assign'; import values from 'object.values'; import isNaN from 'is-nan'; +import { decode as decodeBase64 } from './utils/base64'; if (!Array.prototype.includes) { includes.shim(); @@ -21,3 +22,23 @@ if (!Object.values) { if (!Number.isNaN) { Number.isNaN = isNaN; } + +if (!HTMLCanvasElement.prototype.toBlob) { + const BASE64_MARKER = ';base64,'; + + Object.defineProperty(HTMLCanvasElement.prototype, 'toBlob', { + value(callback, type = 'image/png', quality) { + const dataURL = this.toDataURL(type, quality); + let data; + + if (dataURL.indexOf(BASE64_MARKER) >= 0) { + const [, base64] = dataURL.split(BASE64_MARKER); + data = decodeBase64(base64); + } else { + [, data] = dataURL.split(','); + } + + callback(new Blob([data], { type })); + }, + }); +} diff --git a/app/javascript/mastodon/load_polyfills.js b/app/javascript/mastodon/load_polyfills.js index 815e190..8cb81c1 100644 --- a/app/javascript/mastodon/load_polyfills.js +++ b/app/javascript/mastodon/load_polyfills.js @@ -12,12 +12,13 @@ function importExtraPolyfills() { function loadPolyfills() { const needsBasePolyfills = !( + Array.prototype.includes && + HTMLCanvasElement.prototype.toBlob && window.Intl && + Number.isNaN && Object.assign && Object.values && - Number.isNaN && - window.Symbol && - Array.prototype.includes + window.Symbol ); // Latest version of Firefox and Safari do not have IntersectionObserver. diff --git a/app/javascript/mastodon/utils/__tests__/base64-test.js b/app/javascript/mastodon/utils/__tests__/base64-test.js new file mode 100644 index 0000000..1b3260f --- /dev/null +++ b/app/javascript/mastodon/utils/__tests__/base64-test.js @@ -0,0 +1,10 @@ +import * as base64 from '../base64'; + +describe('base64', () => { + describe('decode', () => { + it('returns a uint8 array', () => { + const arr = base64.decode('dGVzdA=='); + expect(arr).toEqual(new Uint8Array([116, 101, 115, 116])); + }); + }); +}); diff --git a/app/javascript/mastodon/utils/base64.js b/app/javascript/mastodon/utils/base64.js new file mode 100644 index 0000000..8226e2c --- /dev/null +++ b/app/javascript/mastodon/utils/base64.js @@ -0,0 +1,10 @@ +export const decode = base64 => { + const rawData = window.atob(base64); + const outputArray = new Uint8Array(rawData.length); + + for (let i = 0; i < rawData.length; ++i) { + outputArray[i] = rawData.charCodeAt(i); + } + + return outputArray; +}; diff --git a/app/javascript/mastodon/utils/resize_image.js b/app/javascript/mastodon/utils/resize_image.js new file mode 100644 index 0000000..6442eda --- /dev/null +++ b/app/javascript/mastodon/utils/resize_image.js @@ -0,0 +1,66 @@ +const MAX_IMAGE_DIMENSION = 1280; + +const getImageUrl = inputFile => new Promise((resolve, reject) => { + if (window.URL && URL.createObjectURL) { + try { + resolve(URL.createObjectURL(inputFile)); + } catch (error) { + reject(error); + } + return; + } + + const reader = new FileReader(); + reader.onerror = (...args) => reject(...args); + reader.onload = ({ target }) => resolve(target.result); + + reader.readAsDataURL(inputFile); +}); + +const loadImage = inputFile => new Promise((resolve, reject) => { + getImageUrl(inputFile).then(url => { + const img = new Image(); + + img.onerror = (...args) => reject(...args); + img.onload = () => resolve(img); + + img.src = url; + }).catch(reject); +}); + +export default inputFile => new Promise((resolve, reject) => { + if (!inputFile.type.match(/image.*/) || inputFile.type === 'image/gif') { + resolve(inputFile); + return; + } + + loadImage(inputFile).then(img => { + const canvas = document.createElement('canvas'); + const { width, height } = img; + + let newWidth, newHeight; + + if (width < MAX_IMAGE_DIMENSION && height < MAX_IMAGE_DIMENSION) { + resolve(inputFile); + return; + } + + if (width > height) { + newHeight = height * MAX_IMAGE_DIMENSION / width; + newWidth = MAX_IMAGE_DIMENSION; + } else if (height > width) { + newWidth = width * MAX_IMAGE_DIMENSION / height; + newHeight = MAX_IMAGE_DIMENSION; + } else { + newWidth = MAX_IMAGE_DIMENSION; + newHeight = MAX_IMAGE_DIMENSION; + } + + canvas.width = newWidth; + canvas.height = newHeight; + + canvas.getContext('2d').drawImage(img, 0, 0, newWidth, newHeight); + + canvas.toBlob(resolve, inputFile.type); + }).catch(reject); +}); From 3bf6da1ffcf8208a0608de7bff6e2155c40e2871 Mon Sep 17 00:00:00 2001 From: Yamagishi Kazutoshi Date: Mon, 23 Apr 2018 16:16:26 +0900 Subject: [PATCH 179/381] Move precompile step to build stage (#7235) --- .circleci/config.yml | 55 ++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 40 insertions(+), 15 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index c5d6ec9..fbaf60a 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -23,6 +23,13 @@ aliases: paths: - ./mastodon/ + - &restore_ruby_dependencies + restore_cache: + keys: + - v2-ruby-dependencies-{{ checksum "/tmp/.ruby-version" }}-{{ checksum "Gemfile.lock" }} + - v2-ruby-dependencies-{{ checksum "/tmp/.ruby-version" }}- + - v2-ruby-dependencies-- + - &install_steps steps: - checkout @@ -54,26 +61,14 @@ aliases: - *install_system_dependencies - run: ruby -e 'puts RUBY_VERSION' | tee /tmp/.ruby-version - - restore_cache: - keys: - - v1-ruby-dependencies-{{ checksum "/tmp/.ruby-version" }}-{{ checksum "Gemfile.lock" }} - - v1-ruby-dependencies-{{ checksum "/tmp/.ruby-version" }}- - - v1-ruby-dependencies-- + - *restore_ruby_dependencies - run: bundle install --clean --jobs 16 --path ./vendor/bundle/ --retry 3 --with pam_authentication --without development production - save_cache: - key: v1-ruby-dependencies-{{ checksum "/tmp/.ruby-version" }}-{{ checksum "Gemfile.lock" }} + key: v2-ruby-dependencies-{{ checksum "/tmp/.ruby-version" }}-{{ checksum "Gemfile.lock" }} paths: + - ./.bundle/ - ./vendor/bundle/ - - run: - name: Precompile Assets - command: | - if [ ! -d ./public/assets/ -o ! -d ./public/packs-test/ ]; then - ./bin/rails assets:precompile - fi - - - *persist_to_workspace - - &test_steps steps: - *attach_workspace @@ -81,6 +76,15 @@ aliases: - *install_system_dependencies - run: sudo apt-get install -y ffmpeg + - run: ruby -e 'puts RUBY_VERSION' | tee /tmp/.ruby-version + - *restore_ruby_dependencies + + - restore_cache: + keys: + - precompiled-assets-{{ .Branch }}-{{ .Revision }} + - precompiled-assets-{{ .Branch }}- + - precompiled-assets-- + - run: name: Prepare Tests command: ./bin/rails parallel:create parallel:load_schema parallel:prepare @@ -104,6 +108,20 @@ jobs: environment: *ruby_environment <<: *install_ruby_dependencies + build: + <<: *defaults + steps: + - *attach_workspace + - *install_system_dependencies + - run: ruby -e 'puts RUBY_VERSION' | tee /tmp/.ruby-version + - *restore_ruby_dependencies + - run: ./bin/rails assets:precompile + - save_cache: + key: precompiled-assets-{{ .Branch }}-{{ .Revision }} + paths: + - ./public/assets + - ./public/packs-test/ + test-ruby2.5: <<: *defaults docker: @@ -138,6 +156,8 @@ jobs: <<: *defaults steps: - *attach_workspace + - run: ruby -e 'puts RUBY_VERSION' | tee /tmp/.ruby-version + - *restore_ruby_dependencies - run: bundle exec i18n-tasks check-normalized - run: bundle exec i18n-tasks unused @@ -151,13 +171,18 @@ workflows: - install - install-ruby2.4: requires: + - install + - build: + requires: - install-ruby2.5 - test-ruby2.5: requires: - install-ruby2.5 + - build - test-ruby2.4: requires: - install-ruby2.4 + - build - test-webui: requires: - install From 7db7d68136d8c58c6d354e85096137c39d421671 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 23 Apr 2018 09:16:38 +0200 Subject: [PATCH 180/381] Detect and prevent image bombs, max. processable dimension 4096^2 (#7229) --- app/lib/exceptions.rb | 1 + app/models/concerns/attachmentable.rb | 32 +++++++++++++++++++++++++++++--- app/models/custom_emoji.rb | 2 ++ app/models/media_attachment.rb | 16 ++-------------- 4 files changed, 34 insertions(+), 17 deletions(-) diff --git a/app/lib/exceptions.rb b/app/lib/exceptions.rb index e88e98e..01346bf 100644 --- a/app/lib/exceptions.rb +++ b/app/lib/exceptions.rb @@ -6,6 +6,7 @@ module Mastodon class ValidationError < Error; end class HostValidationError < ValidationError; end class LengthValidationError < ValidationError; end + class DimensionsValidationError < ValidationError; end class RaceConditionError < Error; end class UnexpectedResponseError < Error diff --git a/app/models/concerns/attachmentable.rb b/app/models/concerns/attachmentable.rb index 90ce884..6f8489b 100644 --- a/app/models/concerns/attachmentable.rb +++ b/app/models/concerns/attachmentable.rb @@ -1,10 +1,15 @@ # frozen_string_literal: true +require 'mime/types' + module Attachmentable extend ActiveSupport::Concern + MAX_MATRIX_LIMIT = 16_777_216 # 4096x4096px or approx. 16MB + included do before_post_process :set_file_extensions + before_post_process :check_image_dimensions end private @@ -12,10 +17,31 @@ module Attachmentable def set_file_extensions self.class.attachment_definitions.each_key do |attachment_name| attachment = send(attachment_name) + next if attachment.blank? - extension = Paperclip::Interpolations.content_type_extension(attachment, :original) - basename = Paperclip::Interpolations.basename(attachment, :original) - attachment.instance_write :file_name, [basename, extension].delete_if(&:blank?).join('.') + + attachment.instance_write :file_name, [Paperclip::Interpolations.basename(attachment, :original), appropriate_extension(attachment)].delete_if(&:blank?).join('.') + end + end + + def check_image_dimensions + self.class.attachment_definitions.each_key do |attachment_name| + attachment = send(attachment_name) + + next if attachment.blank? || !attachment.content_type.match?(/image.*/) || attachment.queued_for_write[:original].blank? + + width, height = FastImage.size(attachment.queued_for_write[:original].path) + + raise Mastodon::DimensionsValidationError, "#{width}x#{height} images are not supported" if width.present? && height.present? && (width * height >= MAX_MATRIX_LIMIT) end end + + def appropriate_extension(attachment) + mime_type = MIME::Types[attachment.content_type] + + extensions_for_mime_type = mime_type.empty? ? [] : mime_type.first.extensions + original_extension = Paperclip::Interpolations.extension(attachment, :original) + + extensions_for_mime_type.include?(original_extension) ? original_extension : extensions_for_mime_type.first + end end diff --git a/app/models/custom_emoji.rb b/app/models/custom_emoji.rb index 1ec21d1..2dd3cac 100644 --- a/app/models/custom_emoji.rb +++ b/app/models/custom_emoji.rb @@ -40,6 +40,8 @@ class CustomEmoji < ApplicationRecord remotable_attachment :image, LIMIT + include Attachmentable + def local? domain.nil? end diff --git a/app/models/media_attachment.rb b/app/models/media_attachment.rb index 8fd9ac0..c9abab9 100644 --- a/app/models/media_attachment.rb +++ b/app/models/media_attachment.rb @@ -19,8 +19,6 @@ # description :text # -require 'mime/types' - class MediaAttachment < ApplicationRecord self.inheritance_column = nil @@ -70,6 +68,8 @@ class MediaAttachment < ApplicationRecord validates_attachment_size :file, less_than: LIMIT remotable_attachment :file, LIMIT + include Attachmentable + validates :account, presence: true validates :description, length: { maximum: 420 }, if: :local? @@ -176,9 +176,6 @@ class MediaAttachment < ApplicationRecord def set_type_and_extension self.type = VIDEO_MIME_TYPES.include?(file_content_type) ? :video : :image - extension = appropriate_extension - basename = Paperclip::Interpolations.basename(file, :original) - file.instance_write :file_name, [basename, extension].delete_if(&:blank?).join('.') end def set_meta @@ -223,13 +220,4 @@ class MediaAttachment < ApplicationRecord bitrate: movie.bitrate, } end - - def appropriate_extension - mime_type = MIME::Types[file.content_type] - - extensions_for_mime_type = mime_type.empty? ? [] : mime_type.first.extensions - original_extension = Paperclip::Interpolations.extension(file, :original) - - extensions_for_mime_type.include?(original_extension) ? original_extension : extensions_for_mime_type.first - end end From 9613a53cb36a2d4cb1dd1c9a1eced0f61b7970dc Mon Sep 17 00:00:00 2001 From: Yamagishi Kazutoshi Date: Mon, 23 Apr 2018 18:29:17 +0900 Subject: [PATCH 181/381] Update dependencies for Ruby (2018-04-23) (#7237) * Update annotate to version 2.7.3 * Update aws-sdk-s3 to version 1.9.2 * Update browser to version 2.5.3 * Update capistrano to version 3.10.2 * Update domain_name to version 0.5.20180417 * Update http to version 3.2.0 * Update lograge to version 0.10.0 * Update oj to version 3.5.1 * Update parallel_tests to version 2.21.3 * Update puma to version 3.11.4 * Update rubocop to version 0.55.0 * Update scss_lint to version 0.57.0 * Update simplecov to version 0.16.1 * Update tty-command to version 0.8.0 * Update tty-prompt to version 0.16.0 * Update pkg-config to version 1.3.0 * Update fog-local to version 0.5.0 * Update fog-openstack to version 0.1.25 * Update devise-two-factor to version 3.0.3 * bundle update --- Gemfile | 28 +++---- Gemfile.lock | 136 ++++++++++++++++------------------ app/models/account.rb | 4 +- app/models/account_domain_block.rb | 4 +- app/models/account_moderation_note.rb | 6 +- app/models/admin/action_log.rb | 6 +- app/models/backup.rb | 4 +- app/models/block.rb | 6 +- app/models/conversation.rb | 2 +- app/models/conversation_mute.rb | 6 +- app/models/custom_emoji.rb | 2 +- app/models/domain_block.rb | 2 +- app/models/email_domain_block.rb | 2 +- app/models/favourite.rb | 6 +- app/models/follow.rb | 6 +- app/models/follow_request.rb | 6 +- app/models/import.rb | 4 +- app/models/invite.rb | 4 +- app/models/list.rb | 4 +- app/models/list_account.rb | 8 +- app/models/media_attachment.rb | 6 +- app/models/mention.rb | 6 +- app/models/mute.rb | 6 +- app/models/notification.rb | 8 +- app/models/preview_card.rb | 2 +- app/models/report.rb | 12 +-- app/models/report_note.rb | 6 +- app/models/session_activation.rb | 8 +- app/models/setting.rb | 4 +- app/models/site_upload.rb | 2 +- app/models/status.rb | 14 ++-- app/models/status_pin.rb | 6 +- app/models/stream_entry.rb | 6 +- app/models/subscription.rb | 4 +- app/models/tag.rb | 2 +- app/models/user.rb | 6 +- app/models/web/push_subscription.rb | 2 +- app/models/web/setting.rb | 4 +- config/deploy.rb | 2 +- 39 files changed, 173 insertions(+), 179 deletions(-) diff --git a/Gemfile b/Gemfile index 8055f15..a337485 100644 --- a/Gemfile +++ b/Gemfile @@ -3,7 +3,7 @@ source 'https://rubygems.org' ruby '>= 2.3.0', '< 2.6.0' -gem 'pkg-config', '~> 1.2' +gem 'pkg-config', '~> 1.3' gem 'puma', '~> 3.11' gem 'rails', '~> 5.2.0' @@ -11,11 +11,11 @@ gem 'rails', '~> 5.2.0' gem 'hamlit-rails', '~> 0.2' gem 'pg', '~> 1.0' gem 'pghero', '~> 2.1' -gem 'dotenv-rails', '~> 2.2' +gem 'dotenv-rails', '~> 2.2', '< 2.3' -gem 'aws-sdk-s3', '~> 1.8', require: false +gem 'aws-sdk-s3', '~> 1.9', require: false gem 'fog-core', '~> 1.45' -gem 'fog-local', '~> 0.4', require: false +gem 'fog-local', '~> 0.5', require: false gem 'fog-openstack', '~> 0.1', require: false gem 'paperclip', '~> 6.0' gem 'paperclip-av-transcoder', '~> 0.6' @@ -30,7 +30,7 @@ gem 'iso-639' gem 'chewy', '~> 5.0' gem 'cld3', '~> 3.2.0' gem 'devise', '~> 4.4' -gem 'devise-two-factor', '~> 3.0', git: 'https://github.com/ykzts/devise-two-factor.git', branch: 'rails-5.2' +gem 'devise-two-factor', '~> 3.0' group :pam_authentication, optional: true do gem 'devise_pam_authenticatable2', '~> 9.1' @@ -48,7 +48,7 @@ gem 'goldfinger', '~> 2.1' gem 'hiredis', '~> 0.6' gem 'redis-namespace', '~> 1.5' gem 'htmlentities', '~> 4.3' -gem 'http', '~> 3.0' +gem 'http', '~> 3.2' gem 'http_accept_language', '~> 2.1' gem 'httplog', '~> 1.0' gem 'idn-ruby', require: 'idn' @@ -57,9 +57,9 @@ gem 'link_header', '~> 0.0' gem 'mime-types', '~> 3.1' gem 'nokogiri', '~> 1.8' gem 'nsa', '~> 0.2' -gem 'oj', '~> 3.4' +gem 'oj', '~> 3.5' gem 'ostatus2', '~> 2.0' -gem 'ox', '~> 2.8' +gem 'ox', '~> 2.9' gem 'pundit', '~> 1.1' gem 'premailer-rails' gem 'rack-attack', '~> 5.2' @@ -82,8 +82,8 @@ gem 'simple_form', '~> 4.0' gem 'sprockets-rails', '~> 3.2', require: 'sprockets/railtie' gem 'stoplight', '~> 2.1.3' gem 'strong_migrations', '~> 0.2' -gem 'tty-command' -gem 'tty-prompt' +gem 'tty-command', '~> 0.8' +gem 'tty-prompt', '~> 0.16' gem 'twitter-text', '~> 1.14' gem 'tzinfo-data', '~> 1.2018' gem 'webpacker', '~> 3.4' @@ -112,7 +112,7 @@ group :test do gem 'rails-controller-testing', '~> 1.0' gem 'rspec-sidekiq', '~> 3.0' gem 'rspec-retry', '~> 0.5', require: false - gem 'simplecov', '~> 0.14', require: false + gem 'simplecov', '~> 0.16', require: false gem 'webmock', '~> 3.3' gem 'parallel_tests', '~> 2.21' end @@ -126,10 +126,10 @@ group :development do gem 'letter_opener', '~> 1.4' gem 'letter_opener_web', '~> 1.3' gem 'memory_profiler' - gem 'rubocop', require: false + gem 'rubocop', '~> 0.55', require: false gem 'brakeman', '~> 4.2', require: false gem 'bundler-audit', '~> 0.6', require: false - gem 'scss_lint', '~> 0.55', require: false + gem 'scss_lint', '~> 0.57', require: false gem 'capistrano', '~> 3.10' gem 'capistrano-rails', '~> 1.3' @@ -138,6 +138,6 @@ group :development do end group :production do - gem 'lograge', '~> 0.9' + gem 'lograge', '~> 0.10' gem 'redis-rails', '~> 5.0' end diff --git a/Gemfile.lock b/Gemfile.lock index eeb4bf1..d96165d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,15 +1,3 @@ -GIT - remote: https://github.com/ykzts/devise-two-factor.git - revision: f60492b29c174d4c959ac02406392f8eb9c4d374 - branch: rails-5.2 - specs: - devise-two-factor (3.0.2) - activesupport (< 5.3) - attr_encrypted (>= 1.3, < 4, != 2) - devise (~> 4.0) - railties (< 5.3) - rotp (~> 2.0) - GEM remote: https://rubygems.org/ specs: @@ -64,7 +52,7 @@ GEM public_suffix (>= 2.0.2, < 4.0) airbrussh (1.3.0) sshkit (>= 1.6.1, != 1.7.0) - annotate (2.7.2) + annotate (2.7.3) activerecord (>= 3.2, < 6.0) rake (>= 10.4, < 13.0) arel (9.0.0) @@ -73,15 +61,15 @@ GEM encryptor (~> 3.0.0) av (0.9.0) cocaine (~> 0.5.3) - aws-partitions (1.70.0) - aws-sdk-core (3.17.0) + aws-partitions (1.80.0) + aws-sdk-core (3.19.0) aws-partitions (~> 1.0) aws-sigv4 (~> 1.0) jmespath (~> 1.0) aws-sdk-kms (1.5.0) aws-sdk-core (~> 3) aws-sigv4 (~> 1.0) - aws-sdk-s3 (1.8.2) + aws-sdk-s3 (1.9.1) aws-sdk-core (~> 3) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.0) @@ -96,7 +84,7 @@ GEM bootsnap (1.3.0) msgpack (~> 1.0) brakeman (4.2.1) - browser (2.5.2) + browser (2.5.3) builder (3.2.3) bullet (5.7.5) activesupport (>= 3.0.0) @@ -104,7 +92,7 @@ GEM bundler-audit (0.6.0) bundler (~> 1.2) thor (~> 0.18) - capistrano (3.10.1) + capistrano (3.10.2) airbrussh (>= 1.0.0) i18n rake (>= 10.0.0) @@ -156,12 +144,18 @@ GEM railties (>= 4.1.0, < 6.0) responders warden (~> 1.2.3) + devise-two-factor (3.0.3) + activesupport (< 5.3) + attr_encrypted (>= 1.3, < 4, != 2) + devise (~> 4.0) + railties (< 5.3) + rotp (~> 2.0) devise_pam_authenticatable2 (9.1.0) devise (>= 4.0.0) rpam2 (~> 4.0) diff-lcs (1.3) - docile (1.1.5) - domain_name (0.5.20170404) + docile (1.3.0) + domain_name (0.5.20180417) unf (>= 0.0.5, < 1.0.0) doorkeeper (4.3.2) railties (>= 4.2) @@ -172,29 +166,29 @@ GEM easy_translate (0.5.1) thread thread_safe - elasticsearch (6.0.1) - elasticsearch-api (= 6.0.1) - elasticsearch-transport (= 6.0.1) - elasticsearch-api (6.0.1) + elasticsearch (6.0.2) + elasticsearch-api (= 6.0.2) + elasticsearch-transport (= 6.0.2) + elasticsearch-api (6.0.2) multi_json elasticsearch-dsl (0.1.5) - elasticsearch-transport (6.0.1) + elasticsearch-transport (6.0.2) faraday multi_json encryptor (3.0.0) equatable (0.5.0) erubi (1.7.1) - et-orbi (1.0.9) + et-orbi (1.1.0) tzinfo - excon (0.60.0) + excon (0.62.0) fabrication (2.20.1) faker (1.8.7) i18n (>= 0.7) - faraday (0.14.0) + faraday (0.15.0) multipart-post (>= 1.2, < 3) fast_blank (1.0.0) fastimage (2.1.1) - ffi (1.9.21) + ffi (1.9.23) fog-core (1.45.0) builder excon (~> 0.58) @@ -202,9 +196,9 @@ GEM fog-json (1.0.2) fog-core (~> 1.0) multi_json (~> 1.10) - fog-local (0.4.0) - fog-core (~> 1.27) - fog-openstack (0.1.23) + fog-local (0.5.0) + fog-core (>= 1.27, < 3.0) + fog-openstack (0.1.25) fog-core (~> 1.40) fog-json (>= 1.0) ipaddress (>= 0.8) @@ -237,20 +231,20 @@ GEM hitimes (1.2.6) hkdf (0.3.0) htmlentities (4.3.4) - http (3.0.0) + http (3.2.0) addressable (~> 2.3) http-cookie (~> 1.0) - http-form_data (>= 2.0.0.pre.pre2, < 3) + http-form_data (~> 2.0) http_parser.rb (~> 0.6.0) http-cookie (1.0.3) domain_name (~> 0.5) - http-form_data (2.0.0) + http-form_data (2.1.0) http_accept_language (2.1.1) http_parser.rb (0.6.0) httplog (1.0.2) colorize (~> 0.8) rack (>= 1.0) - i18n (1.0.0) + i18n (1.0.1) concurrent-ruby (~> 1.0) i18n-tasks (0.9.21) activesupport (>= 4.0.2) @@ -265,7 +259,7 @@ GEM idn-ruby (0.1.0) ipaddress (0.8.3) iso-639 (0.2.8) - jmespath (1.3.1) + jmespath (1.4.0) json (2.1.0) json-ld (2.2.1) multi_json (~> 1.12) @@ -297,7 +291,7 @@ GEM letter_opener (~> 1.0) railties (>= 3.2) link_header (0.0.8) - lograge (0.9.0) + lograge (0.10.0) actionpack (>= 4) activesupport (>= 4) railties (>= 4) @@ -341,7 +335,7 @@ GEM concurrent-ruby (~> 1.0.0) sidekiq (>= 3.5.0) statsd-ruby (~> 1.2.0) - oj (3.4.0) + oj (3.5.1) omniauth (1.8.1) hashie (>= 3.4.6, < 3.6.0) rack (>= 1.6.2, < 3) @@ -357,7 +351,7 @@ GEM addressable (~> 2.5) http (~> 3.0) nokogiri (~> 1.8) - ox (2.8.2) + ox (2.9.2) paperclip (6.0.0) activemodel (>= 4.2.0) activesupport (>= 4.2.0) @@ -368,7 +362,7 @@ GEM av (~> 0.9.0) paperclip (>= 2.5.2) parallel (1.12.1) - parallel_tests (2.21.1) + parallel_tests (2.21.3) parallel parser (2.5.1.0) ast (~> 2.4.0) @@ -378,7 +372,7 @@ GEM pg (1.0.0) pghero (2.1.0) activerecord - pkg-config (1.2.9) + pkg-config (1.3.0) powerpack (0.1.1) premailer (1.11.1) addressable @@ -394,7 +388,7 @@ GEM pry-rails (0.3.6) pry (>= 0.10.4) public_suffix (3.0.2) - puma (3.11.3) + puma (3.11.4) pundit (1.1.0) activesupport (>= 3.0.0) rack (2.0.4) @@ -443,10 +437,10 @@ GEM thor (>= 0.18.1, < 2.0) rainbow (3.0.0) rake (12.3.1) - rb-fsevent (0.10.2) + rb-fsevent (0.10.3) rb-inotify (0.9.10) ffi (>= 0.5.0, < 2) - rdf (3.0.1) + rdf (3.0.2) hamster (~> 3.0) link_header (~> 0.0, >= 0.0.8) rdf-normalize (0.3.3) @@ -468,9 +462,9 @@ GEM redis-actionpack (>= 5.0, < 6) redis-activesupport (>= 5.0, < 6) redis-store (>= 1.2, < 2) - redis-store (1.4.1) + redis-store (1.5.0) redis (>= 2.2, < 5) - request_store (1.4.0) + request_store (1.4.1) rack (>= 1.4) responders (2.4.0) actionpack (>= 4.2.0, < 5.3) @@ -501,9 +495,9 @@ GEM rspec-core (~> 3.0, >= 3.0.0) sidekiq (>= 2.4.0) rspec-support (3.7.1) - rubocop (0.52.1) + rubocop (0.55.0) parallel (~> 1.10) - parser (>= 2.4.0.2, < 3.0) + parser (>= 2.5) powerpack (~> 0.1) rainbow (>= 2.2.2, < 4.0) ruby-progressbar (~> 1.7) @@ -519,14 +513,14 @@ GEM crass (~> 1.0.2) nokogiri (>= 1.4.4) nokogumbo (~> 1.4) - sass (3.5.5) + sass (3.5.6) sass-listen (~> 4.0.0) sass-listen (4.0.0) rb-fsevent (~> 0.9, >= 0.9.4) rb-inotify (~> 0.9, >= 0.9.7) - scss_lint (0.56.0) + scss_lint (0.57.0) rake (>= 0.9, < 13) - sass (~> 3.5.3) + sass (~> 3.5.5) sidekiq (5.1.3) concurrent-ruby (~> 1.0) connection_pool (~> 2.2, >= 2.2.0) @@ -548,8 +542,8 @@ GEM simple_form (4.0.0) actionpack (> 4) activemodel (> 4) - simplecov (0.15.1) - docile (~> 1.1.0) + simplecov (0.16.1) + docile (~> 1.1) json (>= 1.8, < 3) simplecov-html (~> 0.10.0) simplecov-html (0.10.2) @@ -581,10 +575,10 @@ GEM timers (4.1.2) hitimes tty-color (0.4.2) - tty-command (0.7.0) + tty-command (0.8.0) pastel (~> 0.7.0) tty-cursor (0.5.0) - tty-prompt (0.15.0) + tty-prompt (0.16.0) necromancer (~> 0.4.0) pastel (~> 0.7.0) timers (~> 4.0) @@ -604,7 +598,7 @@ GEM unf (0.1.4) unf_ext unf_ext (0.0.7.5) - unicode-display_width (1.3.0) + unicode-display_width (1.3.2) uniform_notifier (1.11.0) warden (1.2.7) rack (>= 1.0) @@ -634,7 +628,7 @@ DEPENDENCIES active_record_query_trace (~> 1.5) addressable (~> 2.5) annotate (~> 2.7) - aws-sdk-s3 (~> 1.8) + aws-sdk-s3 (~> 1.9) better_errors (~> 2.4) binding_of_caller (~> 0.7) bootsnap (~> 1.3) @@ -652,23 +646,23 @@ DEPENDENCIES cld3 (~> 3.2.0) climate_control (~> 0.2) devise (~> 4.4) - devise-two-factor (~> 3.0)! + devise-two-factor (~> 3.0) devise_pam_authenticatable2 (~> 9.1) doorkeeper (~> 4.3) - dotenv-rails (~> 2.2) + dotenv-rails (~> 2.2, < 2.3) fabrication (~> 2.20) faker (~> 1.8) fast_blank (~> 1.0) fastimage fog-core (~> 1.45) - fog-local (~> 0.4) + fog-local (~> 0.5) fog-openstack (~> 0.1) fuubar (~> 2.2) goldfinger (~> 2.1) hamlit-rails (~> 0.2) hiredis (~> 0.6) htmlentities (~> 4.3) - http (~> 3.0) + http (~> 3.2) http_accept_language (~> 2.1) httplog (~> 1.0) i18n-tasks (~> 0.9) @@ -679,7 +673,7 @@ DEPENDENCIES letter_opener (~> 1.4) letter_opener_web (~> 1.3) link_header (~> 0.0) - lograge (~> 0.9) + lograge (~> 0.10) mario-redis-lock (~> 1.2) memory_profiler microformats (~> 4.0) @@ -687,18 +681,18 @@ DEPENDENCIES net-ldap (~> 0.10) nokogiri (~> 1.8) nsa (~> 0.2) - oj (~> 3.4) + oj (~> 3.5) omniauth (~> 1.2) omniauth-cas (~> 1.1) omniauth-saml (~> 1.10) ostatus2 (~> 2.0) - ox (~> 2.8) + ox (~> 2.9) paperclip (~> 6.0) paperclip-av-transcoder (~> 0.6) parallel_tests (~> 2.21) pg (~> 1.0) pghero (~> 2.1) - pkg-config (~> 1.2) + pkg-config (~> 1.3) premailer-rails private_address_check (~> 0.4.1) pry-rails (~> 0.3) @@ -719,24 +713,24 @@ DEPENDENCIES rspec-rails (~> 3.7) rspec-retry (~> 0.5) rspec-sidekiq (~> 3.0) - rubocop + rubocop (~> 0.55) ruby-oembed (~> 0.12) ruby-progressbar (~> 1.4) sanitize (~> 4.6) - scss_lint (~> 0.55) + scss_lint (~> 0.57) sidekiq (~> 5.1) sidekiq-bulk (~> 0.1.1) sidekiq-scheduler (~> 2.2) sidekiq-unique-jobs (~> 5.0) simple-navigation (~> 4.0) simple_form (~> 4.0) - simplecov (~> 0.14) + simplecov (~> 0.16) sprockets-rails (~> 3.2) stoplight (~> 2.1.3) streamio-ffmpeg (~> 3.0) strong_migrations (~> 0.2) - tty-command - tty-prompt + tty-command (~> 0.8) + tty-prompt (~> 0.16) twitter-text (~> 1.14) tzinfo-data (~> 1.2018) webmock (~> 3.3) diff --git a/app/models/account.rb b/app/models/account.rb index a3436b4..0a4370b 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -3,7 +3,7 @@ # # Table name: accounts # -# id :integer not null, primary key +# id :bigint(8) not null, primary key # username :string default(""), not null # domain :string # secret :string default(""), not null @@ -42,7 +42,7 @@ # followers_url :string default(""), not null # protocol :integer default("ostatus"), not null # memorial :boolean default(FALSE), not null -# moved_to_account_id :integer +# moved_to_account_id :bigint(8) # featured_collection_url :string # fields :jsonb # diff --git a/app/models/account_domain_block.rb b/app/models/account_domain_block.rb index bc00b4f..e352000 100644 --- a/app/models/account_domain_block.rb +++ b/app/models/account_domain_block.rb @@ -3,11 +3,11 @@ # # Table name: account_domain_blocks # -# id :integer not null, primary key +# id :bigint(8) not null, primary key # domain :string # created_at :datetime not null # updated_at :datetime not null -# account_id :integer +# account_id :bigint(8) # class AccountDomainBlock < ApplicationRecord diff --git a/app/models/account_moderation_note.rb b/app/models/account_moderation_note.rb index 3ac9b1a..22e312b 100644 --- a/app/models/account_moderation_note.rb +++ b/app/models/account_moderation_note.rb @@ -3,10 +3,10 @@ # # Table name: account_moderation_notes # -# id :integer not null, primary key +# id :bigint(8) not null, primary key # content :text not null -# account_id :integer not null -# target_account_id :integer not null +# account_id :bigint(8) not null +# target_account_id :bigint(8) not null # created_at :datetime not null # updated_at :datetime not null # diff --git a/app/models/admin/action_log.rb b/app/models/admin/action_log.rb index 81f278e..1d1db1b 100644 --- a/app/models/admin/action_log.rb +++ b/app/models/admin/action_log.rb @@ -3,11 +3,11 @@ # # Table name: admin_action_logs # -# id :integer not null, primary key -# account_id :integer +# id :bigint(8) not null, primary key +# account_id :bigint(8) # action :string default(""), not null # target_type :string -# target_id :integer +# target_id :bigint(8) # recorded_changes :text default(""), not null # created_at :datetime not null # updated_at :datetime not null diff --git a/app/models/backup.rb b/app/models/backup.rb index 5a7e6a1..c265131 100644 --- a/app/models/backup.rb +++ b/app/models/backup.rb @@ -3,8 +3,8 @@ # # Table name: backups # -# id :integer not null, primary key -# user_id :integer +# id :bigint(8) not null, primary key +# user_id :bigint(8) # dump_file_name :string # dump_content_type :string # dump_file_size :integer diff --git a/app/models/block.rb b/app/models/block.rb index d6ecabd..df4a6bb 100644 --- a/app/models/block.rb +++ b/app/models/block.rb @@ -3,11 +3,11 @@ # # Table name: blocks # -# id :integer not null, primary key +# id :bigint(8) not null, primary key # created_at :datetime not null # updated_at :datetime not null -# account_id :integer not null -# target_account_id :integer not null +# account_id :bigint(8) not null +# target_account_id :bigint(8) not null # class Block < ApplicationRecord diff --git a/app/models/conversation.rb b/app/models/conversation.rb index 08c1ce9..4dfaea8 100644 --- a/app/models/conversation.rb +++ b/app/models/conversation.rb @@ -3,7 +3,7 @@ # # Table name: conversations # -# id :integer not null, primary key +# id :bigint(8) not null, primary key # uri :string # created_at :datetime not null # updated_at :datetime not null diff --git a/app/models/conversation_mute.rb b/app/models/conversation_mute.rb index 272eb81..52c1a33 100644 --- a/app/models/conversation_mute.rb +++ b/app/models/conversation_mute.rb @@ -3,9 +3,9 @@ # # Table name: conversation_mutes # -# id :integer not null, primary key -# conversation_id :integer not null -# account_id :integer not null +# id :bigint(8) not null, primary key +# conversation_id :bigint(8) not null +# account_id :bigint(8) not null # class ConversationMute < ApplicationRecord diff --git a/app/models/custom_emoji.rb b/app/models/custom_emoji.rb index 2dd3cac..8235332 100644 --- a/app/models/custom_emoji.rb +++ b/app/models/custom_emoji.rb @@ -3,7 +3,7 @@ # # Table name: custom_emojis # -# id :integer not null, primary key +# id :bigint(8) not null, primary key # shortcode :string default(""), not null # domain :string # image_file_name :string diff --git a/app/models/domain_block.rb b/app/models/domain_block.rb index aea8919..9365879 100644 --- a/app/models/domain_block.rb +++ b/app/models/domain_block.rb @@ -3,7 +3,7 @@ # # Table name: domain_blocks # -# id :integer not null, primary key +# id :bigint(8) not null, primary key # domain :string default(""), not null # created_at :datetime not null # updated_at :datetime not null diff --git a/app/models/email_domain_block.rb b/app/models/email_domain_block.rb index a104810..1049037 100644 --- a/app/models/email_domain_block.rb +++ b/app/models/email_domain_block.rb @@ -3,7 +3,7 @@ # # Table name: email_domain_blocks # -# id :integer not null, primary key +# id :bigint(8) not null, primary key # domain :string default(""), not null # created_at :datetime not null # updated_at :datetime not null diff --git a/app/models/favourite.rb b/app/models/favourite.rb index fa1884b..c998a67 100644 --- a/app/models/favourite.rb +++ b/app/models/favourite.rb @@ -3,11 +3,11 @@ # # Table name: favourites # -# id :integer not null, primary key +# id :bigint(8) not null, primary key # created_at :datetime not null # updated_at :datetime not null -# account_id :integer not null -# status_id :integer not null +# account_id :bigint(8) not null +# status_id :bigint(8) not null # class Favourite < ApplicationRecord diff --git a/app/models/follow.rb b/app/models/follow.rb index 8e6fe53..2ca42ff 100644 --- a/app/models/follow.rb +++ b/app/models/follow.rb @@ -3,11 +3,11 @@ # # Table name: follows # -# id :integer not null, primary key +# id :bigint(8) not null, primary key # created_at :datetime not null # updated_at :datetime not null -# account_id :integer not null -# target_account_id :integer not null +# account_id :bigint(8) not null +# target_account_id :bigint(8) not null # show_reblogs :boolean default(TRUE), not null # diff --git a/app/models/follow_request.rb b/app/models/follow_request.rb index cde26ce..d559a8f 100644 --- a/app/models/follow_request.rb +++ b/app/models/follow_request.rb @@ -3,11 +3,11 @@ # # Table name: follow_requests # -# id :integer not null, primary key +# id :bigint(8) not null, primary key # created_at :datetime not null # updated_at :datetime not null -# account_id :integer not null -# target_account_id :integer not null +# account_id :bigint(8) not null +# target_account_id :bigint(8) not null # show_reblogs :boolean default(TRUE), not null # diff --git a/app/models/import.rb b/app/models/import.rb index fdb4c6b..55e970b 100644 --- a/app/models/import.rb +++ b/app/models/import.rb @@ -3,7 +3,7 @@ # # Table name: imports # -# id :integer not null, primary key +# id :bigint(8) not null, primary key # type :integer not null # approved :boolean default(FALSE), not null # created_at :datetime not null @@ -12,7 +12,7 @@ # data_content_type :string # data_file_size :integer # data_updated_at :datetime -# account_id :integer not null +# account_id :bigint(8) not null # class Import < ApplicationRecord diff --git a/app/models/invite.rb b/app/models/invite.rb index 4ba5432..2250e58 100644 --- a/app/models/invite.rb +++ b/app/models/invite.rb @@ -3,8 +3,8 @@ # # Table name: invites # -# id :integer not null, primary key -# user_id :integer not null +# id :bigint(8) not null, primary key +# user_id :bigint(8) not null # code :string default(""), not null # expires_at :datetime # max_uses :integer diff --git a/app/models/list.rb b/app/models/list.rb index a2ec7e8..c9c94fc 100644 --- a/app/models/list.rb +++ b/app/models/list.rb @@ -3,8 +3,8 @@ # # Table name: lists # -# id :integer not null, primary key -# account_id :integer not null +# id :bigint(8) not null, primary key +# account_id :bigint(8) not null # title :string default(""), not null # created_at :datetime not null # updated_at :datetime not null diff --git a/app/models/list_account.rb b/app/models/list_account.rb index da46cf0..87b4982 100644 --- a/app/models/list_account.rb +++ b/app/models/list_account.rb @@ -3,10 +3,10 @@ # # Table name: list_accounts # -# id :integer not null, primary key -# list_id :integer not null -# account_id :integer not null -# follow_id :integer not null +# id :bigint(8) not null, primary key +# list_id :bigint(8) not null +# account_id :bigint(8) not null +# follow_id :bigint(8) not null # class ListAccount < ApplicationRecord diff --git a/app/models/media_attachment.rb b/app/models/media_attachment.rb index c9abab9..f9a8f32 100644 --- a/app/models/media_attachment.rb +++ b/app/models/media_attachment.rb @@ -3,8 +3,8 @@ # # Table name: media_attachments # -# id :integer not null, primary key -# status_id :integer +# id :bigint(8) not null, primary key +# status_id :bigint(8) # file_file_name :string # file_content_type :string # file_file_size :integer @@ -15,7 +15,7 @@ # shortcode :string # type :integer default("image"), not null # file_meta :json -# account_id :integer +# account_id :bigint(8) # description :text # diff --git a/app/models/mention.rb b/app/models/mention.rb index f864bf8..8ab886b 100644 --- a/app/models/mention.rb +++ b/app/models/mention.rb @@ -3,11 +3,11 @@ # # Table name: mentions # -# id :integer not null, primary key -# status_id :integer +# id :bigint(8) not null, primary key +# status_id :bigint(8) # created_at :datetime not null # updated_at :datetime not null -# account_id :integer +# account_id :bigint(8) # class Mention < ApplicationRecord diff --git a/app/models/mute.rb b/app/models/mute.rb index 8efa27a..0e00c22 100644 --- a/app/models/mute.rb +++ b/app/models/mute.rb @@ -3,11 +3,11 @@ # # Table name: mutes # -# id :integer not null, primary key +# id :bigint(8) not null, primary key # created_at :datetime not null # updated_at :datetime not null -# account_id :integer not null -# target_account_id :integer not null +# account_id :bigint(8) not null +# target_account_id :bigint(8) not null # hide_notifications :boolean default(TRUE), not null # diff --git a/app/models/notification.rb b/app/models/notification.rb index 0b0f01a..4f6ec8e 100644 --- a/app/models/notification.rb +++ b/app/models/notification.rb @@ -3,13 +3,13 @@ # # Table name: notifications # -# id :integer not null, primary key -# activity_id :integer not null +# id :bigint(8) not null, primary key +# activity_id :bigint(8) not null # activity_type :string not null # created_at :datetime not null # updated_at :datetime not null -# account_id :integer not null -# from_account_id :integer not null +# account_id :bigint(8) not null +# from_account_id :bigint(8) not null # class Notification < ApplicationRecord diff --git a/app/models/preview_card.rb b/app/models/preview_card.rb index 0ffa6b1..a792b35 100644 --- a/app/models/preview_card.rb +++ b/app/models/preview_card.rb @@ -3,7 +3,7 @@ # # Table name: preview_cards # -# id :integer not null, primary key +# id :bigint(8) not null, primary key # url :string default(""), not null # title :string default(""), not null # description :string default(""), not null diff --git a/app/models/report.rb b/app/models/report.rb index 5b90c7b..efe385b 100644 --- a/app/models/report.rb +++ b/app/models/report.rb @@ -3,16 +3,16 @@ # # Table name: reports # -# id :integer not null, primary key -# status_ids :integer default([]), not null, is an Array +# id :bigint(8) not null, primary key +# status_ids :bigint(8) default([]), not null, is an Array # comment :text default(""), not null # action_taken :boolean default(FALSE), not null # created_at :datetime not null # updated_at :datetime not null -# account_id :integer not null -# action_taken_by_account_id :integer -# target_account_id :integer not null -# assigned_account_id :integer +# account_id :bigint(8) not null +# action_taken_by_account_id :bigint(8) +# target_account_id :bigint(8) not null +# assigned_account_id :bigint(8) # class Report < ApplicationRecord diff --git a/app/models/report_note.rb b/app/models/report_note.rb index 6d9dec8..54b4165 100644 --- a/app/models/report_note.rb +++ b/app/models/report_note.rb @@ -3,10 +3,10 @@ # # Table name: report_notes # -# id :integer not null, primary key +# id :bigint(8) not null, primary key # content :text not null -# report_id :integer not null -# account_id :integer not null +# report_id :bigint(8) not null +# account_id :bigint(8) not null # created_at :datetime not null # updated_at :datetime not null # diff --git a/app/models/session_activation.rb b/app/models/session_activation.rb index d364f03..34d25c8 100644 --- a/app/models/session_activation.rb +++ b/app/models/session_activation.rb @@ -3,15 +3,15 @@ # # Table name: session_activations # -# id :integer not null, primary key +# id :bigint(8) not null, primary key # session_id :string not null # created_at :datetime not null # updated_at :datetime not null # user_agent :string default(""), not null # ip :inet -# access_token_id :integer -# user_id :integer not null -# web_push_subscription_id :integer +# access_token_id :bigint(8) +# user_id :bigint(8) not null +# web_push_subscription_id :bigint(8) # class SessionActivation < ApplicationRecord diff --git a/app/models/setting.rb b/app/models/setting.rb index df93590..033d09f 100644 --- a/app/models/setting.rb +++ b/app/models/setting.rb @@ -3,13 +3,13 @@ # # Table name: settings # -# id :integer not null, primary key +# id :bigint(8) not null, primary key # var :string not null # value :text # thing_type :string # created_at :datetime # updated_at :datetime -# thing_id :integer +# thing_id :bigint(8) # class Setting < RailsSettings::Base diff --git a/app/models/site_upload.rb b/app/models/site_upload.rb index 641128a..14d6837 100644 --- a/app/models/site_upload.rb +++ b/app/models/site_upload.rb @@ -3,7 +3,7 @@ # # Table name: site_uploads # -# id :integer not null, primary key +# id :bigint(8) not null, primary key # var :string default(""), not null # file_file_name :string # file_content_type :string diff --git a/app/models/status.rb b/app/models/status.rb index 62857dd..ed4bcef 100644 --- a/app/models/status.rb +++ b/app/models/status.rb @@ -3,13 +3,13 @@ # # Table name: statuses # -# id :integer not null, primary key +# id :bigint(8) not null, primary key # uri :string # text :text default(""), not null # created_at :datetime not null # updated_at :datetime not null -# in_reply_to_id :integer -# reblog_of_id :integer +# in_reply_to_id :bigint(8) +# reblog_of_id :bigint(8) # url :string # sensitive :boolean default(FALSE), not null # visibility :integer default("public"), not null @@ -18,11 +18,11 @@ # favourites_count :integer default(0), not null # reblogs_count :integer default(0), not null # language :string -# conversation_id :integer +# conversation_id :bigint(8) # local :boolean -# account_id :integer not null -# application_id :integer -# in_reply_to_account_id :integer +# account_id :bigint(8) not null +# application_id :bigint(8) +# in_reply_to_account_id :bigint(8) # class Status < ApplicationRecord diff --git a/app/models/status_pin.rb b/app/models/status_pin.rb index d3a98d8..afc76bd 100644 --- a/app/models/status_pin.rb +++ b/app/models/status_pin.rb @@ -3,9 +3,9 @@ # # Table name: status_pins # -# id :integer not null, primary key -# account_id :integer not null -# status_id :integer not null +# id :bigint(8) not null, primary key +# account_id :bigint(8) not null +# status_id :bigint(8) not null # created_at :datetime not null # updated_at :datetime not null # diff --git a/app/models/stream_entry.rb b/app/models/stream_entry.rb index 2ae034d..a2f2732 100644 --- a/app/models/stream_entry.rb +++ b/app/models/stream_entry.rb @@ -3,13 +3,13 @@ # # Table name: stream_entries # -# id :integer not null, primary key -# activity_id :integer +# id :bigint(8) not null, primary key +# activity_id :bigint(8) # activity_type :string # created_at :datetime not null # updated_at :datetime not null # hidden :boolean default(FALSE), not null -# account_id :integer +# account_id :bigint(8) # class StreamEntry < ApplicationRecord diff --git a/app/models/subscription.rb b/app/models/subscription.rb index ea11731..79b8182 100644 --- a/app/models/subscription.rb +++ b/app/models/subscription.rb @@ -3,7 +3,7 @@ # # Table name: subscriptions # -# id :integer not null, primary key +# id :bigint(8) not null, primary key # callback_url :string default(""), not null # secret :string # expires_at :datetime @@ -12,7 +12,7 @@ # updated_at :datetime not null # last_successful_delivery_at :datetime # domain :string -# account_id :integer not null +# account_id :bigint(8) not null # class Subscription < ApplicationRecord diff --git a/app/models/tag.rb b/app/models/tag.rb index 9fa9405..8b1b024 100644 --- a/app/models/tag.rb +++ b/app/models/tag.rb @@ -3,7 +3,7 @@ # # Table name: tags # -# id :integer not null, primary key +# id :bigint(8) not null, primary key # name :string default(""), not null # created_at :datetime not null # updated_at :datetime not null diff --git a/app/models/user.rb b/app/models/user.rb index 2d5f145..a9f3e1d 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -3,7 +3,7 @@ # # Table name: users # -# id :integer not null, primary key +# id :bigint(8) not null, primary key # email :string default(""), not null # created_at :datetime not null # updated_at :datetime not null @@ -30,10 +30,10 @@ # last_emailed_at :datetime # otp_backup_codes :string is an Array # filtered_languages :string default([]), not null, is an Array -# account_id :integer not null +# account_id :bigint(8) not null # disabled :boolean default(FALSE), not null # moderator :boolean default(FALSE), not null -# invite_id :integer +# invite_id :bigint(8) # remember_token :string # diff --git a/app/models/web/push_subscription.rb b/app/models/web/push_subscription.rb index 5aee92d..1736106 100644 --- a/app/models/web/push_subscription.rb +++ b/app/models/web/push_subscription.rb @@ -3,7 +3,7 @@ # # Table name: web_push_subscriptions # -# id :integer not null, primary key +# id :bigint(8) not null, primary key # endpoint :string not null # key_p256dh :string not null # key_auth :string not null diff --git a/app/models/web/setting.rb b/app/models/web/setting.rb index 0a5129d..99588d2 100644 --- a/app/models/web/setting.rb +++ b/app/models/web/setting.rb @@ -3,11 +3,11 @@ # # Table name: web_settings # -# id :integer not null, primary key +# id :bigint(8) not null, primary key # data :json # created_at :datetime not null # updated_at :datetime not null -# user_id :integer not null +# user_id :bigint(8) not null # class Web::Setting < ApplicationRecord diff --git a/config/deploy.rb b/config/deploy.rb index 180dd1c..e0cd60f 100644 --- a/config/deploy.rb +++ b/config/deploy.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -lock '3.10.1' +lock '3.10.2' set :repo_url, ENV.fetch('REPO', 'https://github.com/tootsuite/mastodon.git') set :branch, ENV.fetch('BRANCH', 'master') From 3ccca6cece51fd071fed8c1e05af6a48c00c8897 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Miko=C5=82ajczak?= Date: Mon, 23 Apr 2018 11:49:03 +0200 Subject: [PATCH 182/381] =?UTF-8?q?=F0=9F=8C=8D:=20Make=20=F0=9F=87=B5?= =?UTF-8?q?=F0=9F=87=B1=20translation=20more=20consistent=20(#7239)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Marcin Mikołajczak --- app/javascript/mastodon/locales/pl.json | 10 +++++----- config/locales/pl.yml | 12 +++++++----- config/locales/simple_form.pl.yml | 2 +- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/app/javascript/mastodon/locales/pl.json b/app/javascript/mastodon/locales/pl.json index 22ae2de..9dff9d1 100644 --- a/app/javascript/mastodon/locales/pl.json +++ b/app/javascript/mastodon/locales/pl.json @@ -103,8 +103,8 @@ "empty_column.community": "Lokalna oś czasu jest pusta. Napisz coś publicznie, aby zagaić!", "empty_column.direct": "Nie masz żadnych wiadomości bezpośrednich. Kiedy dostaniesz lub wyślesz jakąś, pojawi się ona tutaj.", "empty_column.hashtag": "Nie ma wpisów oznaczonych tym hashtagiem. Możesz napisać pierwszy!", - "empty_column.home": "Nie śledzisz nikogo. Odwiedź publiczną oś czasu lub użyj wyszukiwarki, aby znaleźć interesujące Cię profile.", - "empty_column.home.public_timeline": "publiczna oś czasu", + "empty_column.home": "Nie śledzisz nikogo. Odwiedź globalną oś czasu lub użyj wyszukiwarki, aby znaleźć interesujące Cię profile.", + "empty_column.home.public_timeline": "globalna oś czasu", "empty_column.list": "Nie ma nic na tej liście. Kiedy członkowie listy dodadzą nowe wpisy, pojawia się one tutaj.", "empty_column.notifications": "Nie masz żadnych powiadomień. Rozpocznij interakcje z innymi użytkownikami.", "empty_column.public": "Tu nic nie ma! Napisz coś publicznie, lub dodaj ludzi z innych instancji, aby to wyświetlić", @@ -169,7 +169,7 @@ "navigation_bar.mutes": "Wyciszeni użytkownicy", "navigation_bar.pins": "Przypięte wpisy", "navigation_bar.preferences": "Preferencje", - "navigation_bar.public_timeline": "Oś czasu federacji", + "navigation_bar.public_timeline": "Globalna oś czasu", "notification.favourite": "{name} dodał Twój wpis do ulubionych", "notification.follow": "{name} zaczął Cię śledzić", "notification.mention": "{name} wspomniał o tobie", @@ -187,7 +187,7 @@ "notifications.column_settings.sound": "Odtwarzaj dźwięk", "onboarding.done": "Gotowe", "onboarding.next": "Dalej", - "onboarding.page_five.public_timelines": "Lokalna oś czasu zawiera wszystkie publiczne wpisy z {domain}. Federalna oś czasu wyświetla publiczne wpisy śledzonych przez członków {domain}. Są to publiczne osie czasu – najlepszy sposób na poznanie nowych osób.", + "onboarding.page_five.public_timelines": "Lokalna oś czasu zawiera wszystkie publiczne wpisy z {domain}. Globalna oś czasu wyświetla publiczne wpisy śledzonych przez członków {domain}. Są to publiczne osie czasu – najlepszy sposób na poznanie nowych osób.", "onboarding.page_four.home": "Główna oś czasu wyświetla publiczne wpisy.", "onboarding.page_four.notifications": "Kolumna powiadomień wyświetla, gdy ktoś dokonuje interakcji z tobą.", "onboarding.page_one.federation": "Mastodon jest siecią niezależnych serwerów połączonych w jeden portal społecznościowy. Nazywamy te serwery instancjami.", @@ -247,7 +247,7 @@ "status.delete": "Usuń", "status.direct": "Wyślij wiadomość bezpośrednią do @{name}", "status.embed": "Osadź", - "status.favourite": "Ulubione", + "status.favourite": "Dodaj do ulubionych", "status.load_more": "Załaduj więcej", "status.media_hidden": "Zawartość multimedialna ukryta", "status.mention": "Wspomnij o @{name}", diff --git a/config/locales/pl.yml b/config/locales/pl.yml index 839767c..71bf6bf 100644 --- a/config/locales/pl.yml +++ b/config/locales/pl.yml @@ -466,7 +466,7 @@ pl: archive_takeout: date: Data download: Pobierz swoje archiwum - hint_html: Możesz uzyskać archiwum swoich wpisów i wysłanej zawartości multimedialnej. Wyeksportowane dane będą dostępne w formacie ActivityPub, obsługiwanym przez odpowiednie programy. + hint_html: Możesz uzyskać archiwum swoich wpisów i wysłanej zawartości multimedialnej. Wyeksportowane dane będą dostępne w formacie ActivityPub, który możesz otworzyć w obsługujących go programach. in_progress: Tworzenie archiwum… request: Uzyskaj archiwum size: Rozmiar @@ -485,17 +485,19 @@ pl: one: W trakcie usuwania śledzących z jednej domeny… other: W trakcie usuwania śledzących z %{count} domen… true_privacy_html: Pamiętaj, że rzeczywista prywatność może zostać uzyskana wyłącznie dzięki szyfrowaniu end-to-end. - unlocked_warning_html: Każdy może Cię śledzić, aby natychmiastowo zobaczyć twoje wpisy. %{lock_link} aby móc kontrolować, kto Cię śledzi. + unlocked_warning_html: Każdy może Cię śledzić, dzięki czemu może zobaczyć Twoje niepubliczne wpisy. %{lock_link} aby móc kontrolować, kto Cię śledzi. unlocked_warning_title: Twoje konto nie jest zablokowane generic: changes_saved_msg: Ustawienia zapisane! powered_by: uruchomione na %{link} save_changes: Zapisz zmiany validation_errors: - one: Coś jest wciąż nie tak! Przyjrzyj się błędowi poniżej - other: Coś jest wciąż nie tak! Przejrzyj błędy (%{count}) poniżej + few: Coś jest wciąż nie tak! Przejrzyj %{count} poniższe błędy + many: Coś jest wciąż nie tak! Przejrzyj %{count} poniższych błędów + one: Coś jest wciąż nie tak! Przyjrzyj się poniższemu błędowi + other: Coś jest wciąż nie tak! Przejrzyj poniższe błędy (%{count}) imports: - preface: Możesz zaimportować pewne dane (jak dane kont, które śledzisz lub blokujesz) do swojego konta na tym serwerze, korzystając z danych wyeksportowanych z innego serwera. + preface: Możesz zaimportować pewne dane (np. lista kont, które śledzisz lub blokujesz) do swojego konta na tym serwerze, korzystając z danych wyeksportowanych z innego serwera. success: Twoje dane zostały załadowane i zostaną niebawem przetworzone types: blocking: Lista blokowanych diff --git a/config/locales/simple_form.pl.yml b/config/locales/simple_form.pl.yml index cbca232..4c1833b 100644 --- a/config/locales/simple_form.pl.yml +++ b/config/locales/simple_form.pl.yml @@ -62,7 +62,7 @@ pl: setting_theme: Motyw strony setting_unfollow_modal: Pytaj o potwierdzenie przed cofnięciem śledzenia severity: Priorytet - type: Typ importu + type: Importowane dane username: Nazwa użytkownika username_or_email: Nazwa użytkownika lub adres e-mail interactions: From 06817b3c1fdcc9c2b3484478588cc348a4a06537 Mon Sep 17 00:00:00 2001 From: Alejandro Martinez Ruiz Date: Mon, 23 Apr 2018 15:03:58 +0100 Subject: [PATCH 183/381] tasks/mastodon: fix prompt for Redis password (#7241) --- lib/tasks/mastodon.rake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/tasks/mastodon.rake b/lib/tasks/mastodon.rake index b5a1483..7a33784 100644 --- a/lib/tasks/mastodon.rake +++ b/lib/tasks/mastodon.rake @@ -109,7 +109,7 @@ namespace :mastodon do env['REDIS_PASSWORD'] = prompt.ask('Redis password:') do |q| q.required false - a.default nil + q.default nil q.modify :strip end From 1258efa882b7a0eedc868640eb8e5a9075445ca0 Mon Sep 17 00:00:00 2001 From: Akihiko Odaki Date: Tue, 24 Apr 2018 02:27:35 +0900 Subject: [PATCH 184/381] Paginate descendant statuses in public page (#7148) --- app/controllers/api/v1/statuses_controller.rb | 2 +- app/controllers/statuses_controller.rb | 73 +++++++++++++++++++++- app/models/concerns/status_threading_concern.rb | 17 ++--- app/views/stream_entries/_more.html.haml | 2 + app/views/stream_entries/_status.html.haml | 15 ++++- spec/controllers/statuses_controller_spec.rb | 43 +++++++++++++ .../concerns/status_threading_concern_spec.rb | 12 ++-- spec/views/stream_entries/show.html.haml_spec.rb | 4 +- 8 files changed, 146 insertions(+), 22 deletions(-) create mode 100644 app/views/stream_entries/_more.html.haml diff --git a/app/controllers/api/v1/statuses_controller.rb b/app/controllers/api/v1/statuses_controller.rb index e982413..0188056 100644 --- a/app/controllers/api/v1/statuses_controller.rb +++ b/app/controllers/api/v1/statuses_controller.rb @@ -18,7 +18,7 @@ class Api::V1::StatusesController < Api::BaseController def context ancestors_results = @status.in_reply_to_id.nil? ? [] : @status.ancestors(DEFAULT_STATUSES_LIMIT, current_account) - descendants_results = @status.descendants(current_account) + descendants_results = @status.descendants(DEFAULT_STATUSES_LIMIT, current_account) loaded_ancestors = cache_collection(ancestors_results, Status) loaded_descendants = cache_collection(descendants_results, Status) diff --git a/app/controllers/statuses_controller.rb b/app/controllers/statuses_controller.rb index a294398..01dac35 100644 --- a/app/controllers/statuses_controller.rb +++ b/app/controllers/statuses_controller.rb @@ -5,6 +5,8 @@ class StatusesController < ApplicationController include Authorization ANCESTORS_LIMIT = 20 + DESCENDANTS_LIMIT = 20 + DESCENDANTS_DEPTH_LIMIT = 4 layout 'public' @@ -19,9 +21,8 @@ class StatusesController < ApplicationController def show respond_to do |format| format.html do - @ancestors = @status.reply? ? cache_collection(@status.ancestors(ANCESTORS_LIMIT, current_account), Status) : [] - @next_ancestor = @ancestors.size < ANCESTORS_LIMIT ? nil : @ancestors.shift - @descendants = cache_collection(@status.descendants(current_account), Status) + set_ancestors + set_descendants render 'stream_entries/show' end @@ -51,10 +52,76 @@ class StatusesController < ApplicationController private + def create_descendant_thread(depth, statuses) + if depth < DESCENDANTS_DEPTH_LIMIT + { statuses: statuses } + else + next_status = statuses.pop + { statuses: statuses, next_status: next_status } + end + end + def set_account @account = Account.find_local!(params[:account_username]) end + def set_ancestors + @ancestors = @status.reply? ? cache_collection(@status.ancestors(ANCESTORS_LIMIT, current_account), Status) : [] + @next_ancestor = @ancestors.size < ANCESTORS_LIMIT ? nil : @ancestors.shift + end + + def set_descendants + @max_descendant_thread_id = params[:max_descendant_thread_id]&.to_i + @since_descendant_thread_id = params[:since_descendant_thread_id]&.to_i + + descendants = cache_collection( + @status.descendants( + DESCENDANTS_LIMIT, + current_account, + @max_descendant_thread_id, + @since_descendant_thread_id, + DESCENDANTS_DEPTH_LIMIT + ), + Status + ) + @descendant_threads = [] + + if descendants.present? + statuses = [descendants.first] + depth = 1 + + descendants.drop(1).each_with_index do |descendant, index| + if descendants[index].id == descendant.in_reply_to_id + depth += 1 + statuses << descendant + else + @descendant_threads << create_descendant_thread(depth, statuses) + + @descendant_threads.reverse_each do |descendant_thread| + statuses = descendant_thread[:statuses] + + index = statuses.find_index do |thread_status| + thread_status.id == descendant.in_reply_to_id + end + + if index.present? + depth += index - statuses.size + break + end + + depth -= statuses.size + end + + statuses = [descendant] + end + end + + @descendant_threads << create_descendant_thread(depth, statuses) + end + + @max_descendant_thread_id = @descendant_threads.pop[:statuses].first.id if descendants.size >= DESCENDANTS_LIMIT + end + def set_link_headers response.headers['Link'] = LinkHeader.new( [ diff --git a/app/models/concerns/status_threading_concern.rb b/app/models/concerns/status_threading_concern.rb index fffc095..a3fd34e 100644 --- a/app/models/concerns/status_threading_concern.rb +++ b/app/models/concerns/status_threading_concern.rb @@ -7,8 +7,8 @@ module StatusThreadingConcern find_statuses_from_tree_path(ancestor_ids(limit), account) end - def descendants(account = nil) - find_statuses_from_tree_path(descendant_ids, account) + def descendants(limit, account = nil, max_child_id = nil, since_child_id = nil, depth = nil) + find_statuses_from_tree_path(descendant_ids(limit, max_child_id, since_child_id, depth), account) end private @@ -46,26 +46,27 @@ module StatusThreadingConcern SQL end - def descendant_ids - descendant_statuses.pluck(:id) + def descendant_ids(limit, max_child_id, since_child_id, depth) + descendant_statuses(limit, max_child_id, since_child_id, depth).pluck(:id) end - def descendant_statuses - Status.find_by_sql([<<-SQL.squish, id: id]) + def descendant_statuses(limit, max_child_id, since_child_id, depth) + Status.find_by_sql([<<-SQL.squish, id: id, limit: limit, max_child_id: max_child_id, since_child_id: since_child_id, depth: depth]) WITH RECURSIVE search_tree(id, path) AS ( SELECT id, ARRAY[id] FROM statuses - WHERE in_reply_to_id = :id + WHERE in_reply_to_id = :id AND COALESCE(id < :max_child_id, TRUE) AND COALESCE(id > :since_child_id, TRUE) UNION ALL SELECT statuses.id, path || statuses.id FROM search_tree JOIN statuses ON statuses.in_reply_to_id = search_tree.id - WHERE NOT statuses.id = ANY(path) + WHERE COALESCE(array_length(path, 1) < :depth, TRUE) AND NOT statuses.id = ANY(path) ) SELECT id FROM search_tree ORDER BY path + LIMIT :limit SQL end diff --git a/app/views/stream_entries/_more.html.haml b/app/views/stream_entries/_more.html.haml new file mode 100644 index 0000000..9b1dfe4 --- /dev/null +++ b/app/views/stream_entries/_more.html.haml @@ -0,0 +1,2 @@ += link_to url, class: 'more light' do + = t('statuses.show_more') diff --git a/app/views/stream_entries/_status.html.haml b/app/views/stream_entries/_status.html.haml index 2d0dafc..8decdf6 100644 --- a/app/views/stream_entries/_status.html.haml +++ b/app/views/stream_entries/_status.html.haml @@ -16,8 +16,7 @@ - if status.reply? && include_threads - if @next_ancestor .entry{ class: entry_classes } - = link_to short_account_status_url(@next_ancestor.account.username, @next_ancestor), class: 'more light' do - = t('statuses.show_more') + = render 'stream_entries/more', url: short_account_status_url(@next_ancestor.account.username, @next_ancestor) = render partial: 'stream_entries/status', collection: @ancestors, as: :status, locals: { is_predecessor: true, direct_reply_id: status.in_reply_to_id } .entry{ class: entry_classes } @@ -40,4 +39,14 @@ = render (centered ? 'stream_entries/detailed_status' : 'stream_entries/simple_status'), status: status.proper - if include_threads - = render partial: 'stream_entries/status', collection: @descendants, as: :status, locals: { is_successor: true, parent_id: status.id } + - if @since_descendant_thread_id + .entry{ class: entry_classes } + = render 'stream_entries/more', url: short_account_status_url(status.account.username, status, max_descendant_thread_id: @since_descendant_thread_id + 1) + - @descendant_threads.each do |thread| + = render partial: 'stream_entries/status', collection: thread[:statuses], as: :status, locals: { is_successor: true, parent_id: status.id } + - if thread[:next_status] + .entry{ class: entry_classes } + = render 'stream_entries/more', url: short_account_status_url(thread[:next_status].account.username, thread[:next_status]) + - if @next_descendant_thread + .entry{ class: entry_classes } + = render 'stream_entries/more', url: short_account_status_url(status.account.username, status, since_descendant_thread_id: @max_descendant_thread_id - 1) diff --git a/spec/controllers/statuses_controller_spec.rb b/spec/controllers/statuses_controller_spec.rb index 89af556..b4f3c5a 100644 --- a/spec/controllers/statuses_controller_spec.rb +++ b/spec/controllers/statuses_controller_spec.rb @@ -82,6 +82,49 @@ describe StatusesController do expect(assigns(:ancestors)).to eq [] end + it 'assigns @descendant_threads for a thread with several statuses' do + status = Fabricate(:status) + child = Fabricate(:status, in_reply_to_id: status.id) + grandchild = Fabricate(:status, in_reply_to_id: child.id) + + get :show, params: { account_username: status.account.username, id: status.id } + + expect(assigns(:descendant_threads)[0][:statuses].pluck(:id)).to eq [child.id, grandchild.id] + end + + it 'assigns @descendant_threads for several threads sharing the same descendant' do + status = Fabricate(:status) + child = Fabricate(:status, in_reply_to_id: status.id) + grandchildren = 2.times.map { Fabricate(:status, in_reply_to_id: child.id) } + + get :show, params: { account_username: status.account.username, id: status.id } + + expect(assigns(:descendant_threads)[0][:statuses].pluck(:id)).to eq [child.id, grandchildren[0].id] + expect(assigns(:descendant_threads)[1][:statuses].pluck(:id)).to eq [grandchildren[1].id] + end + + it 'assigns @max_descendant_thread_id for the last thread if it is hitting the status limit' do + stub_const 'StatusesController::DESCENDANTS_LIMIT', 1 + status = Fabricate(:status) + child = Fabricate(:status, in_reply_to_id: status.id) + + get :show, params: { account_username: status.account.username, id: status.id } + + expect(assigns(:descendant_threads)).to eq [] + expect(assigns(:max_descendant_thread_id)).to eq child.id + end + + it 'assigns @descendant_threads for threads with :next_status key if they are hitting the depth limit' do + stub_const 'StatusesController::DESCENDANTS_DEPTH_LIMIT', 1 + status = Fabricate(:status) + child = Fabricate(:status, in_reply_to_id: status.id) + + get :show, params: { account_username: status.account.username, id: status.id } + + expect(assigns(:descendant_threads)[0][:statuses].pluck(:id)).not_to include child.id + expect(assigns(:descendant_threads)[0][:next_status].id).to eq child.id + end + it 'returns a success' do status = Fabricate(:status) get :show, params: { account_username: status.account.username, id: status.id } diff --git a/spec/models/concerns/status_threading_concern_spec.rb b/spec/models/concerns/status_threading_concern_spec.rb index b8ebdd5..e5736a3 100644 --- a/spec/models/concerns/status_threading_concern_spec.rb +++ b/spec/models/concerns/status_threading_concern_spec.rb @@ -89,34 +89,34 @@ describe StatusThreadingConcern do let!(:viewer) { Fabricate(:account, username: 'viewer') } it 'returns replies' do - expect(status.descendants).to include(reply1, reply2, reply3) + expect(status.descendants(4)).to include(reply1, reply2, reply3) end it 'does not return replies user is not allowed to see' do reply1.update(visibility: :private) reply3.update(visibility: :direct) - expect(status.descendants(viewer)).to_not include(reply1, reply3) + expect(status.descendants(4, viewer)).to_not include(reply1, reply3) end it 'does not return replies from blocked users' do viewer.block!(jeff) - expect(status.descendants(viewer)).to_not include(reply3) + expect(status.descendants(4, viewer)).to_not include(reply3) end it 'does not return replies from muted users' do viewer.mute!(jeff) - expect(status.descendants(viewer)).to_not include(reply3) + expect(status.descendants(4, viewer)).to_not include(reply3) end it 'does not return replies from silenced and not followed users' do jeff.update(silenced: true) - expect(status.descendants(viewer)).to_not include(reply3) + expect(status.descendants(4, viewer)).to_not include(reply3) end it 'does not return replies from blocked domains' do viewer.block_domain!('example.com') - expect(status.descendants(viewer)).to_not include(reply2) + expect(status.descendants(4, viewer)).to_not include(reply2) end end end diff --git a/spec/views/stream_entries/show.html.haml_spec.rb b/spec/views/stream_entries/show.html.haml_spec.rb index 6074bbc..560039f 100644 --- a/spec/views/stream_entries/show.html.haml_spec.rb +++ b/spec/views/stream_entries/show.html.haml_spec.rb @@ -24,6 +24,7 @@ describe 'stream_entries/show.html.haml', without_verify_partial_doubles: true d assign(:stream_entry, status.stream_entry) assign(:account, alice) assign(:type, status.stream_entry.activity_type.downcase) + assign(:descendant_threads, []) render @@ -49,7 +50,7 @@ describe 'stream_entries/show.html.haml', without_verify_partial_doubles: true d assign(:account, alice) assign(:type, reply.stream_entry.activity_type.downcase) assign(:ancestors, reply.stream_entry.activity.ancestors(1, bob) ) - assign(:descendants, reply.stream_entry.activity.descendants(bob)) + assign(:descendant_threads, [{ statuses: reply.stream_entry.activity.descendants(1)}]) render @@ -75,6 +76,7 @@ describe 'stream_entries/show.html.haml', without_verify_partial_doubles: true d assign(:stream_entry, status.stream_entry) assign(:account, alice) assign(:type, status.stream_entry.activity_type.downcase) + assign(:descendant_threads, []) render From 53b1d8887325160934dec7557e97a43ce2896264 Mon Sep 17 00:00:00 2001 From: ThibG Date: Mon, 23 Apr 2018 20:12:16 +0200 Subject: [PATCH 185/381] Fix fullscreen video player (fixes #7244) (#7245) --- app/javascript/styles/mastodon/components.scss | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index c84e613..f1284b3 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -4446,6 +4446,8 @@ a.status-card { video { max-width: 100% !important; max-height: 100% !important; + width: 100% !important; + height: 100% !important; } } From 495303d9b86919c72bf1948a714bf8d00b41fa0f Mon Sep 17 00:00:00 2001 From: Emelia Smith Date: Mon, 23 Apr 2018 21:27:18 +0200 Subject: [PATCH 186/381] Prevent suspended accounts from appearing in AccountSearchService (#7246) --- app/models/account.rb | 1 + app/services/account_search_service.rb | 4 ++-- spec/services/account_search_service_spec.rb | 19 +++++++++++++++++++ 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/app/models/account.rb b/app/models/account.rb index 0a4370b..ee47f04 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -117,6 +117,7 @@ class Account < ApplicationRecord scope :partitioned, -> { order(Arel.sql('row_number() over (partition by domain)')) } scope :silenced, -> { where(silenced: true) } scope :suspended, -> { where(suspended: true) } + scope :without_suspended, -> { where(suspended: false) } scope :recent, -> { reorder(id: :desc) } scope :alphabetic, -> { order(domain: :asc, username: :asc) } scope :by_domain_accounts, -> { group(:domain).select(:domain, 'COUNT(*) AS accounts_count').order('accounts_count desc') } diff --git a/app/services/account_search_service.rb b/app/services/account_search_service.rb index 3860a9c..7edbd9b 100644 --- a/app/services/account_search_service.rb +++ b/app/services/account_search_service.rb @@ -65,9 +65,9 @@ class AccountSearchService < BaseService def exact_match @_exact_match ||= begin if domain_is_local? - search_from.find_local(query_username) + search_from.without_suspended.find_local(query_username) else - search_from.find_remote(query_username, query_domain) + search_from.without_suspended.find_remote(query_username, query_domain) end end end diff --git a/spec/services/account_search_service_spec.rb b/spec/services/account_search_service_spec.rb index 9bb27ed..c5ddc58 100644 --- a/spec/services/account_search_service_spec.rb +++ b/spec/services/account_search_service_spec.rb @@ -137,5 +137,24 @@ describe AccountSearchService do expect(service).not_to have_received(:call) end end + + describe 'should not include suspended accounts' do + it 'returns the fuzzy match first, and does not return suspended exacts' do + partial = Fabricate(:account, username: 'exactness') + exact = Fabricate(:account, username: 'exact', suspended: true) + + results = subject.call('exact', 10) + expect(results.size).to eq 1 + expect(results).to eq [partial] + end + + it "does not return suspended remote accounts" do + remote = Fabricate(:account, username: 'a', domain: 'remote', display_name: 'e', suspended: true) + + results = subject.call('a@example.com', 2) + expect(results.size).to eq 0 + expect(results).to eq [] + end + end end end From 60b871d56c554ddb22b7890d803e4ba6650ed286 Mon Sep 17 00:00:00 2001 From: Emelia Smith Date: Mon, 23 Apr 2018 23:52:58 +0200 Subject: [PATCH 187/381] Implement the ability for instances to define a list of disallowed hashtags (#7176) The goal here isn't to prevent these hashtags from existing, but just to strongly curtail their usage; The hashtags may still exist in the database via federated status, or from being created prior to this feature. --- app/models/status.rb | 1 + app/validators/disallowed_hashtags_validator.rb | 22 ++++++++++++++++++++++ config/locales/en.yml | 3 +++ config/settings.yml | 1 + 4 files changed, 27 insertions(+) create mode 100644 app/validators/disallowed_hashtags_validator.rb diff --git a/app/models/status.rb b/app/models/status.rb index ed4bcef..37f2db5 100644 --- a/app/models/status.rb +++ b/app/models/status.rb @@ -59,6 +59,7 @@ class Status < ApplicationRecord validates :uri, uniqueness: true, presence: true, unless: :local? validates :text, presence: true, unless: -> { with_media? || reblog? } validates_with StatusLengthValidator + validates_with DisallowedHashtagsValidator validates :reblog, uniqueness: { scope: :account }, if: :reblog? default_scope { recent } diff --git a/app/validators/disallowed_hashtags_validator.rb b/app/validators/disallowed_hashtags_validator.rb new file mode 100644 index 0000000..22c027b --- /dev/null +++ b/app/validators/disallowed_hashtags_validator.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +class DisallowedHashtagsValidator < ActiveModel::Validator + def validate(status) + return unless status.local? && !status.reblog? + + tags = Extractor.extract_hashtags(status.text) + tags.keep_if { |tag| disallowed_hashtags.include? tag.downcase } + + status.errors.add(:text, I18n.t('statuses.disallowed_hashtags', tags: tags.join(', '), count: tags.size)) unless tags.empty? + end + + private + + def disallowed_hashtags + return @disallowed_hashtags if @disallowed_hashtags + + @disallowed_hashtags = Setting.disallowed_hashtags.nil? ? [] : Setting.disallowed_hashtags + @disallowed_hashtags = @disallowed_hashtags.split(' ') if @disallowed_hashtags.is_a? String + @disallowed_hashtags = @disallowed_hashtags.map(&:downcase) + end +end diff --git a/config/locales/en.yml b/config/locales/en.yml index 8b66b91..1468d85 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -684,6 +684,9 @@ en: one: "%{count} video" other: "%{count} videos" content_warning: 'Content warning: %{warning}' + disallowed_hashtags: + one: 'contained a disallowed hashtag: %{tags}' + other: 'contained the disallowed hashtags: %{tags}' open_in_web: Open in web over_character_limit: character limit of %{max} exceeded pin_errors: diff --git a/config/settings.yml b/config/settings.yml index 68579ad..dcf6550 100644 --- a/config/settings.yml +++ b/config/settings.yml @@ -47,6 +47,7 @@ defaults: &defaults - root - webmaster - administrator + disallowed_hashtags: # space separated string or list of hashtags without the hash bootstrap_timeline_accounts: '' activity_api_enabled: true peers_api_enabled: true From 306267dbd275363422f9288c91e634a92511620c Mon Sep 17 00:00:00 2001 From: Akihiko Odaki Date: Tue, 24 Apr 2018 18:47:27 +0900 Subject: [PATCH 188/381] Fix ID duplication in timelines (#7251) --- app/javascript/mastodon/reducers/timelines.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/javascript/mastodon/reducers/timelines.js b/app/javascript/mastodon/reducers/timelines.js index ad897bc..dd675d7 100644 --- a/app/javascript/mastodon/reducers/timelines.js +++ b/app/javascript/mastodon/reducers/timelines.js @@ -34,7 +34,7 @@ const expandNormalizedTimeline = (state, timeline, statuses, next, isPartial) => mMap.update('items', ImmutableList(), oldIds => { const newIds = statuses.map(status => status.get('id')); const lastIndex = oldIds.findLastIndex(id => id !== null && compareId(id, newIds.last()) >= 0) + 1; - const firstIndex = oldIds.take(lastIndex).findLastIndex(id => id !== null && compareId(id, newIds.first()) >= 0); + const firstIndex = oldIds.take(lastIndex).findLastIndex(id => id !== null && compareId(id, newIds.first()) > 0); if (firstIndex < 0) { return (isPartial ? newIds.unshift(null) : newIds).concat(oldIds.skip(lastIndex)); From 7681ad80449dafc6fb4572fa66c463930ef82f19 Mon Sep 17 00:00:00 2001 From: "Renato \"Lond\" Cerqueira" Date: Tue, 24 Apr 2018 11:48:11 +0200 Subject: [PATCH 189/381] Weblate translations (2018-04-24) (#7252) * Translated using Weblate (Dutch) Currently translated at 100.0% (288 of 288 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/nl/ * Translated using Weblate (French) Currently translated at 100.0% (288 of 288 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/fr/ * Translated using Weblate (Dutch) Currently translated at 100.0% (626 of 626 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/nl/ * Translated using Weblate (Dutch) Currently translated at 100.0% (288 of 288 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/nl/ * Translated using Weblate (Dutch) Currently translated at 100.0% (62 of 62 strings) Translation: Mastodon/Preferences Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/simple_form/nl/ * Translated using Weblate (Japanese) Currently translated at 99.6% (287 of 288 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/ja/ * Translated using Weblate (Japanese) Currently translated at 99.5% (623 of 626 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/ja/ * Translated using Weblate (Galician) Currently translated at 99.8% (625 of 626 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/gl/ * Translated using Weblate (French) Currently translated at 100.0% (288 of 288 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/fr/ * Translated using Weblate (French) Currently translated at 99.6% (624 of 626 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/fr/ * Translated using Weblate (French) Currently translated at 100.0% (62 of 62 strings) Translation: Mastodon/Preferences Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/simple_form/fr/ * Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (288 of 288 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/pt_BR/ * Translated using Weblate (Portuguese (Brazil)) Currently translated at 99.5% (631 of 634 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/pt_BR/ * Translated using Weblate (Korean) Currently translated at 99.8% (633 of 634 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/ko/ * Translated using Weblate (Korean) Currently translated at 100.0% (62 of 62 strings) Translation: Mastodon/Preferences Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/simple_form/ko/ * Translated using Weblate (French) Currently translated at 99.6% (632 of 634 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/fr/ * Translated using Weblate (Japanese) Currently translated at 99.6% (632 of 634 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/ja/ * Translated using Weblate (Dutch) Currently translated at 100.0% (634 of 634 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/nl/ * Translated using Weblate (Dutch) Currently translated at 100.0% (634 of 634 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/nl/ * Translated using Weblate (Dutch) Currently translated at 100.0% (62 of 62 strings) Translation: Mastodon/Preferences Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/simple_form/nl/ * Translated using Weblate (Japanese) Currently translated at 93.5% (58 of 62 strings) Translation: Mastodon/Preferences Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/simple_form/ja/ * Added translation using Weblate (Basque) * Added translation using Weblate (Basque) * Translated using Weblate (Galician) Currently translated at 100.0% (62 of 62 strings) Translation: Mastodon/Preferences Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/simple_form/gl/ * Added translation using Weblate (Basque) * Added translation using Weblate (Basque) * Added translation using Weblate (Basque) * Added translation using Weblate (Basque) * Translated using Weblate (Galician) Currently translated at 100.0% (288 of 288 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/gl/ * Translated using Weblate (Galician) Currently translated at 99.8% (633 of 634 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/gl/ * Translated using Weblate (Basque) Currently translated at 0.3% (1 of 288 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/eu/ * Translated using Weblate (Basque) Currently translated at 50.0% (1 of 2 strings) Translation: Mastodon/Activerecord Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/activerecord/eu/ * Translated using Weblate (Basque) Currently translated at 1.6% (1 of 62 strings) Translation: Mastodon/Devise Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/devise/eu/ * Translated using Weblate (Basque) Currently translated at 1.3% (1 of 75 strings) Translation: Mastodon/Doorkeeper Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/doorkeeper/eu/ * Translated using Weblate (Basque) Currently translated at 1.6% (1 of 62 strings) Translation: Mastodon/Preferences Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/simple_form/eu/ * Translated using Weblate (Catalan) Currently translated at 100.0% (62 of 62 strings) Translation: Mastodon/Preferences Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/simple_form/ca/ * Translated using Weblate (Catalan) Currently translated at 100.0% (288 of 288 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/ca/ * Translated using Weblate (Catalan) Currently translated at 100.0% (634 of 634 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/ca/ * Translated using Weblate (Catalan) Currently translated at 100.0% (288 of 288 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/ca/ * Translated using Weblate (Catalan) Currently translated at 100.0% (634 of 634 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/ca/ * Translated using Weblate (Catalan) Currently translated at 100.0% (288 of 288 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/ca/ * Translated using Weblate (Japanese) Currently translated at 93.5% (58 of 62 strings) Translation: Mastodon/Preferences Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/simple_form/ja/ * Translated using Weblate (Arabic) Currently translated at 100.0% (62 of 62 strings) Translation: Mastodon/Preferences Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/simple_form/ar/ * Translated using Weblate (Arabic) Currently translated at 100.0% (288 of 288 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/ar/ * Translated using Weblate (Arabic) Currently translated at 100.0% (288 of 288 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/ar/ * Translated using Weblate (Slovak) Currently translated at 100.0% (62 of 62 strings) Translation: Mastodon/Preferences Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/simple_form/sk/ * Translated using Weblate (Japanese) Currently translated at 93.5% (58 of 62 strings) Translation: Mastodon/Preferences Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/simple_form/ja/ * Translated using Weblate (Slovak) Currently translated at 99.6% (287 of 288 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/sk/ * Translated using Weblate (Slovak) Currently translated at 88.4% (561 of 634 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/sk/ * Translated using Weblate (Slovak) Currently translated at 90.3% (573 of 634 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/sk/ * Translated using Weblate (Slovak) Currently translated at 100.0% (288 of 288 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/sk/ * Translated using Weblate (Korean) Currently translated at 100.0% (288 of 288 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/ko/ * Translated using Weblate (Italian) Currently translated at 100.0% (75 of 75 strings) Translation: Mastodon/Doorkeeper Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/doorkeeper/it/ * Translated using Weblate (Italian) Currently translated at 3.2% (2 of 62 strings) Translation: Mastodon/Preferences Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/simple_form/it/ * Translated using Weblate (Italian) Currently translated at 100.0% (288 of 288 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/it/ * Translated using Weblate (Italian) Currently translated at 100.0% (75 of 75 strings) Translation: Mastodon/Doorkeeper Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/doorkeeper/it/ * Translated using Weblate (Italian) Currently translated at 100.0% (62 of 62 strings) Translation: Mastodon/Preferences Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/simple_form/it/ * Translated using Weblate (Italian) Currently translated at 32.4% (206 of 634 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/it/ * Translated using Weblate (Italian) Currently translated at 100.0% (62 of 62 strings) Translation: Mastodon/Devise Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/devise/it/ * Translated using Weblate (Arabic) Currently translated at 81.3% (516 of 634 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/ar/ * Translated using Weblate (Italian) Currently translated at 51.2% (325 of 634 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/it/ * Translated using Weblate (Slovak) Currently translated at 91.6% (581 of 634 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/sk/ * Translated using Weblate (Italian) Currently translated at 51.8% (329 of 634 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/it/ * Normalize translations Ran yarn build:development && i18n-tasks normalize && yarn manage:translations * Remove unused translations Ran i18n-tasks remove-unused --- app/javascript/mastodon/locales/ar.json | 17 +- app/javascript/mastodon/locales/bg.json | 1 + app/javascript/mastodon/locales/ca.json | 21 +- app/javascript/mastodon/locales/de.json | 1 + app/javascript/mastodon/locales/eo.json | 1 + app/javascript/mastodon/locales/es.json | 1 + app/javascript/mastodon/locales/eu.json | 296 +++++++++++++++++++++++++++ app/javascript/mastodon/locales/fa.json | 1 + app/javascript/mastodon/locales/fi.json | 1 + app/javascript/mastodon/locales/fr.json | 21 +- app/javascript/mastodon/locales/gl.json | 3 +- app/javascript/mastodon/locales/he.json | 1 + app/javascript/mastodon/locales/hr.json | 1 + app/javascript/mastodon/locales/hu.json | 1 + app/javascript/mastodon/locales/hy.json | 1 + app/javascript/mastodon/locales/id.json | 1 + app/javascript/mastodon/locales/io.json | 1 + app/javascript/mastodon/locales/it.json | 7 +- app/javascript/mastodon/locales/ja.json | 4 +- app/javascript/mastodon/locales/ko.json | 5 +- app/javascript/mastodon/locales/nl.json | 17 +- app/javascript/mastodon/locales/no.json | 1 + app/javascript/mastodon/locales/oc.json | 1 + app/javascript/mastodon/locales/pt-BR.json | 7 +- app/javascript/mastodon/locales/pt.json | 1 + app/javascript/mastodon/locales/ru.json | 1 + app/javascript/mastodon/locales/sk.json | 15 +- app/javascript/mastodon/locales/sr-Latn.json | 1 + app/javascript/mastodon/locales/sr.json | 1 + app/javascript/mastodon/locales/sv.json | 1 + app/javascript/mastodon/locales/th.json | 1 + app/javascript/mastodon/locales/tr.json | 1 + app/javascript/mastodon/locales/uk.json | 1 + app/javascript/mastodon/locales/zh-CN.json | 1 + app/javascript/mastodon/locales/zh-HK.json | 1 + app/javascript/mastodon/locales/zh-TW.json | 1 + config/locales/activerecord.eu.yml | 9 + config/locales/ca.yml | 121 ++++++++++- config/locales/devise.eu.yml | 5 + config/locales/devise.it.yml | 21 ++ config/locales/doorkeeper.eu.yml | 6 + config/locales/doorkeeper.it.yml | 16 +- config/locales/eu.yml | 1 + config/locales/fr.yml | 45 +++- config/locales/gl.yml | 44 +++- config/locales/it.yml | 251 ++++++++++++++++++++++- config/locales/ja.yml | 5 +- config/locales/ko.yml | 44 +++- config/locales/nl.yml | 121 ++++++++++- config/locales/pt-BR.yml | 43 +++- config/locales/simple_form.ar.yml | 12 ++ config/locales/simple_form.ca.yml | 6 + config/locales/simple_form.eu.yml | 6 + config/locales/simple_form.fr.yml | 6 + config/locales/simple_form.gl.yml | 6 + config/locales/simple_form.it.yml | 84 +++++--- config/locales/simple_form.ja.yml | 8 +- config/locales/simple_form.ko.yml | 6 + config/locales/simple_form.nl.yml | 8 +- config/locales/simple_form.sk.yml | 12 +- config/locales/sk.yml | 22 +- 61 files changed, 1243 insertions(+), 103 deletions(-) create mode 100644 app/javascript/mastodon/locales/eu.json create mode 100644 config/locales/activerecord.eu.yml create mode 100644 config/locales/devise.eu.yml create mode 100644 config/locales/doorkeeper.eu.yml create mode 100644 config/locales/eu.yml create mode 100644 config/locales/simple_form.eu.yml diff --git a/app/javascript/mastodon/locales/ar.json b/app/javascript/mastodon/locales/ar.json index 24c8a5b..7975ac1 100644 --- a/app/javascript/mastodon/locales/ar.json +++ b/app/javascript/mastodon/locales/ar.json @@ -2,7 +2,7 @@ "account.block": "حظر @{name}", "account.block_domain": "إخفاء كل شيئ قادم من إسم النطاق {domain}", "account.blocked": "محظور", - "account.direct": "Direct message @{name}", + "account.direct": "رسالة خاصة إلى @{name}", "account.disclaimer_full": "قد لا تعكس المعلومات أدناه الملف الشخصي الكامل للمستخدم.", "account.domain_blocked": "النطاق مخفي", "account.edit_profile": "تعديل الملف الشخصي", @@ -18,7 +18,7 @@ "account.mute_notifications": "كتم إخطارات @{name}", "account.muted": "مكتوم", "account.posts": "التبويقات", - "account.posts_with_replies": "تبويقات تحتوي على رُدود", + "account.posts_with_replies": "التبويقات و الردود", "account.report": "أبلغ عن @{name}", "account.requested": "في انتظار الموافقة", "account.share": "مشاركة @{name}'s profile", @@ -29,8 +29,8 @@ "account.unmute": "إلغاء الكتم عن @{name}", "account.unmute_notifications": "إلغاء كتم إخطارات @{name}", "account.view_full_profile": "عرض الملف الشخصي كاملا", - "alert.unexpected.message": "An unexpected error occurred.", - "alert.unexpected.title": "Oops!", + "alert.unexpected.message": "لقد طرأ هناك خطأ غير متوقّع.", + "alert.unexpected.title": "المعذرة !", "boost_modal.combo": "يمكنك ضغط {combo} لتخطّي هذه في المرّة القادمة", "bundle_column_error.body": "لقد وقع هناك خطأ أثناء عملية تحميل هذا العنصر.", "bundle_column_error.retry": "إعادة المحاولة", @@ -41,7 +41,7 @@ "column.blocks": "الحسابات المحجوبة", "column.community": "الخيط العام المحلي", "column.direct": "Direct messages", - "column.domain_blocks": "Hidden domains", + "column.domain_blocks": "النطاقات المخفية", "column.favourites": "المفضلة", "column.follow_requests": "طلبات المتابعة", "column.home": "الرئيسية", @@ -135,6 +135,7 @@ "keyboard_shortcuts.mention": "لذِكر الناشر", "keyboard_shortcuts.reply": "للردّ", "keyboard_shortcuts.search": "للتركيز على البحث", + "keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW", "keyboard_shortcuts.toot": "لتحرير تبويق جديد", "keyboard_shortcuts.unfocus": "لإلغاء التركيز على حقل النص أو نافذة البحث", "keyboard_shortcuts.up": "للإنتقال إلى أعلى القائمة", @@ -157,7 +158,7 @@ "navigation_bar.blocks": "الحسابات المحجوبة", "navigation_bar.community_timeline": "الخيط العام المحلي", "navigation_bar.direct": "Direct messages", - "navigation_bar.domain_blocks": "Hidden domains", + "navigation_bar.domain_blocks": "النطاقات المخفية", "navigation_bar.edit_profile": "تعديل الملف الشخصي", "navigation_bar.favourites": "المفضلة", "navigation_bar.follow_requests": "طلبات المتابعة", @@ -244,7 +245,7 @@ "status.cancel_reblog_private": "Unboost", "status.cannot_reblog": "تعذرت ترقية هذا المنشور", "status.delete": "إحذف", - "status.direct": "Direct message @{name}", + "status.direct": "رسالة خاصة إلى @{name}", "status.embed": "إدماج", "status.favourite": "أضف إلى المفضلة", "status.load_more": "حمّل المزيد", @@ -275,7 +276,7 @@ "tabs_bar.home": "الرئيسية", "tabs_bar.local_timeline": "المحلي", "tabs_bar.notifications": "الإخطارات", - "tabs_bar.search": "Search", + "tabs_bar.search": "البحث", "ui.beforeunload": "سوف تفقد مسودتك إن تركت ماستدون.", "upload_area.title": "إسحب ثم أفلت للرفع", "upload_button.label": "إضافة وسائط", diff --git a/app/javascript/mastodon/locales/bg.json b/app/javascript/mastodon/locales/bg.json index 25ef6db..9714751 100644 --- a/app/javascript/mastodon/locales/bg.json +++ b/app/javascript/mastodon/locales/bg.json @@ -135,6 +135,7 @@ "keyboard_shortcuts.mention": "to mention author", "keyboard_shortcuts.reply": "to reply", "keyboard_shortcuts.search": "to focus search", + "keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW", "keyboard_shortcuts.toot": "to start a brand new toot", "keyboard_shortcuts.unfocus": "to un-focus compose textarea/search", "keyboard_shortcuts.up": "to move up in the list", diff --git a/app/javascript/mastodon/locales/ca.json b/app/javascript/mastodon/locales/ca.json index 6a44808..2e5004c 100644 --- a/app/javascript/mastodon/locales/ca.json +++ b/app/javascript/mastodon/locales/ca.json @@ -2,7 +2,7 @@ "account.block": "Bloca @{name}", "account.block_domain": "Amaga-ho tot de {domain}", "account.blocked": "Bloquejat", - "account.direct": "Direct message @{name}", + "account.direct": "Missatge directe @{name}", "account.disclaimer_full": "La informació següent pot reflectir incompleta el perfil de l'usuari.", "account.domain_blocked": "Domini ocult", "account.edit_profile": "Edita el perfil", @@ -18,7 +18,7 @@ "account.mute_notifications": "Notificacions desactivades de @{name}", "account.muted": "Silenciat", "account.posts": "Toots", - "account.posts_with_replies": "Toots amb respostes", + "account.posts_with_replies": "Toots i respostes", "account.report": "Informe @{name}", "account.requested": "Esperant aprovació. Clic per a cancel·lar la petició de seguiment", "account.share": "Comparteix el perfil de @{name}", @@ -29,8 +29,8 @@ "account.unmute": "Treure silenci de @{name}", "account.unmute_notifications": "Activar notificacions de @{name}", "account.view_full_profile": "Mostra el perfil complet", - "alert.unexpected.message": "An unexpected error occurred.", - "alert.unexpected.title": "Oops!", + "alert.unexpected.message": "S'ha produït un error inesperat.", + "alert.unexpected.title": "Vaja!", "boost_modal.combo": "Pots premer {combo} per saltar-te això el proper cop", "bundle_column_error.body": "S'ha produït un error en carregar aquest component.", "bundle_column_error.retry": "Torna-ho a provar", @@ -41,7 +41,7 @@ "column.blocks": "Usuaris blocats", "column.community": "Línia de temps local", "column.direct": "Direct messages", - "column.domain_blocks": "Hidden domains", + "column.domain_blocks": "Dominis ocults", "column.favourites": "Favorits", "column.follow_requests": "Peticions per seguir-te", "column.home": "Inici", @@ -59,7 +59,7 @@ "column_header.unpin": "No fixis", "column_subheading.navigation": "Navegació", "column_subheading.settings": "Configuració", - "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.", + "compose_form.direct_message_warning": "Aquest toot només serà visible per a tots els usuaris esmentats.", "compose_form.hashtag_warning": "Aquest toot no es mostrarà en cap etiqueta ja que no està llistat. Només els toots públics poden ser cercats per etiqueta.", "compose_form.lock_disclaimer": "El teu compte no està bloquejat {locked}. Tothom pot seguir-te i veure els teus missatges a seguidors.", "compose_form.lock_disclaimer.lock": "blocat", @@ -68,7 +68,7 @@ "compose_form.publish_loud": "{publish}!", "compose_form.sensitive.marked": "Mèdia marcat com a sensible", "compose_form.sensitive.unmarked": "Mèdia no està marcat com a sensible", - "compose_form.spoiler.marked": "Text ocult sota l'avís", + "compose_form.spoiler.marked": "Text es ocult sota l'avís", "compose_form.spoiler.unmarked": "Text no ocult", "compose_form.spoiler_placeholder": "Escriu l'avís aquí", "confirmation_modal.cancel": "Cancel·la", @@ -135,6 +135,7 @@ "keyboard_shortcuts.mention": "per esmentar l'autor", "keyboard_shortcuts.reply": "respondre", "keyboard_shortcuts.search": "per centrar la cerca", + "keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW", "keyboard_shortcuts.toot": "per a començar un toot nou de trinca", "keyboard_shortcuts.unfocus": "descentrar l'area de composició de text/cerca", "keyboard_shortcuts.up": "moure amunt en la llista", @@ -157,7 +158,7 @@ "navigation_bar.blocks": "Usuaris bloquejats", "navigation_bar.community_timeline": "Línia de temps Local", "navigation_bar.direct": "Direct messages", - "navigation_bar.domain_blocks": "Hidden domains", + "navigation_bar.domain_blocks": "Dominis ocults", "navigation_bar.edit_profile": "Editar perfil", "navigation_bar.favourites": "Favorits", "navigation_bar.follow_requests": "Sol·licituds de seguiment", @@ -244,7 +245,7 @@ "status.cancel_reblog_private": "Unboost", "status.cannot_reblog": "Aquesta publicació no pot ser retootejada", "status.delete": "Esborrar", - "status.direct": "Direct message @{name}", + "status.direct": "Missatge directe @{name}", "status.embed": "Incrustar", "status.favourite": "Favorit", "status.load_more": "Carrega més", @@ -275,7 +276,7 @@ "tabs_bar.home": "Inici", "tabs_bar.local_timeline": "Local", "tabs_bar.notifications": "Notificacions", - "tabs_bar.search": "Search", + "tabs_bar.search": "Cerca", "ui.beforeunload": "El vostre esborrany es perdrà si sortiu de Mastodon.", "upload_area.title": "Arrossega i deixa anar per carregar", "upload_button.label": "Afegir multimèdia", diff --git a/app/javascript/mastodon/locales/de.json b/app/javascript/mastodon/locales/de.json index 69c2ae8..5ce9081 100644 --- a/app/javascript/mastodon/locales/de.json +++ b/app/javascript/mastodon/locales/de.json @@ -135,6 +135,7 @@ "keyboard_shortcuts.mention": "um Autor_in zu erwähnen", "keyboard_shortcuts.reply": "um zu antworten", "keyboard_shortcuts.search": "um die Suche zu fokussieren", + "keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW", "keyboard_shortcuts.toot": "um einen neuen Toot zu beginnen", "keyboard_shortcuts.unfocus": "um das Textfeld/die Suche nicht mehr zu fokussieren", "keyboard_shortcuts.up": "sich in der Liste hinauf bewegen", diff --git a/app/javascript/mastodon/locales/eo.json b/app/javascript/mastodon/locales/eo.json index e511639..37587c1 100644 --- a/app/javascript/mastodon/locales/eo.json +++ b/app/javascript/mastodon/locales/eo.json @@ -135,6 +135,7 @@ "keyboard_shortcuts.mention": "por mencii la aŭtoron", "keyboard_shortcuts.reply": "por respondi", "keyboard_shortcuts.search": "por fokusigi la serĉilon", + "keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW", "keyboard_shortcuts.toot": "por komenci tute novan mesaĝon", "keyboard_shortcuts.unfocus": "por malfokusigi la tekstujon aŭ la serĉilon", "keyboard_shortcuts.up": "por iri supren en la listo", diff --git a/app/javascript/mastodon/locales/es.json b/app/javascript/mastodon/locales/es.json index 61ea058..41d7db9 100644 --- a/app/javascript/mastodon/locales/es.json +++ b/app/javascript/mastodon/locales/es.json @@ -135,6 +135,7 @@ "keyboard_shortcuts.mention": "para mencionar al autor", "keyboard_shortcuts.reply": "para responder", "keyboard_shortcuts.search": "para poner el foco en la búsqueda", + "keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW", "keyboard_shortcuts.toot": "para comenzar un nuevo toot", "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", diff --git a/app/javascript/mastodon/locales/eu.json b/app/javascript/mastodon/locales/eu.json new file mode 100644 index 0000000..02b7bf7 --- /dev/null +++ b/app/javascript/mastodon/locales/eu.json @@ -0,0 +1,296 @@ +{ + "account.block": "Block @{name}", + "account.block_domain": "Hide everything from {domain}", + "account.blocked": "Blokeatuta", + "account.direct": "Direct message @{name}", + "account.disclaimer_full": "Information below may reflect the user's profile incompletely.", + "account.domain_blocked": "Domain hidden", + "account.edit_profile": "Edit profile", + "account.follow": "Follow", + "account.followers": "Followers", + "account.follows": "Follows", + "account.follows_you": "Follows you", + "account.hide_reblogs": "Hide boosts from @{name}", + "account.media": "Media", + "account.mention": "Mention @{name}", + "account.moved_to": "{name} has moved to:", + "account.mute": "Mute @{name}", + "account.mute_notifications": "Mute notifications from @{name}", + "account.muted": "Muted", + "account.posts": "Toots", + "account.posts_with_replies": "Toots and replies", + "account.report": "Report @{name}", + "account.requested": "Awaiting approval. Click to cancel follow request", + "account.share": "Share @{name}'s profile", + "account.show_reblogs": "Show boosts from @{name}", + "account.unblock": "Unblock @{name}", + "account.unblock_domain": "Unhide {domain}", + "account.unfollow": "Unfollow", + "account.unmute": "Unmute @{name}", + "account.unmute_notifications": "Unmute notifications from @{name}", + "account.view_full_profile": "View full profile", + "alert.unexpected.message": "An unexpected error occurred.", + "alert.unexpected.title": "Oops!", + "boost_modal.combo": "You can press {combo} to skip this next time", + "bundle_column_error.body": "Something went wrong while loading this component.", + "bundle_column_error.retry": "Try again", + "bundle_column_error.title": "Network error", + "bundle_modal_error.close": "Close", + "bundle_modal_error.message": "Something went wrong while loading this component.", + "bundle_modal_error.retry": "Try again", + "column.blocks": "Blocked users", + "column.community": "Local timeline", + "column.direct": "Direct messages", + "column.domain_blocks": "Hidden domains", + "column.favourites": "Favourites", + "column.follow_requests": "Follow requests", + "column.home": "Home", + "column.lists": "Lists", + "column.mutes": "Muted users", + "column.notifications": "Notifications", + "column.pins": "Pinned toot", + "column.public": "Federated timeline", + "column_back_button.label": "Back", + "column_header.hide_settings": "Hide settings", + "column_header.moveLeft_settings": "Move column to the left", + "column_header.moveRight_settings": "Move column to the right", + "column_header.pin": "Pin", + "column_header.show_settings": "Show settings", + "column_header.unpin": "Unpin", + "column_subheading.navigation": "Navigation", + "column_subheading.settings": "Settings", + "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.", + "compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.", + "compose_form.lock_disclaimer": "Your account is not {locked}. Anyone can follow you to view your follower-only posts.", + "compose_form.lock_disclaimer.lock": "locked", + "compose_form.placeholder": "What is on your mind?", + "compose_form.publish": "Toot", + "compose_form.publish_loud": "{publish}!", + "compose_form.sensitive.marked": "Media is marked as sensitive", + "compose_form.sensitive.unmarked": "Media is not marked as sensitive", + "compose_form.spoiler.marked": "Text is hidden behind warning", + "compose_form.spoiler.unmarked": "Text is not hidden", + "compose_form.spoiler_placeholder": "Write your warning here", + "confirmation_modal.cancel": "Cancel", + "confirmations.block.confirm": "Block", + "confirmations.block.message": "Are you sure you want to block {name}?", + "confirmations.delete.confirm": "Delete", + "confirmations.delete.message": "Are you sure you want to delete this status?", + "confirmations.delete_list.confirm": "Delete", + "confirmations.delete_list.message": "Are you sure you want to permanently delete this list?", + "confirmations.domain_block.confirm": "Hide entire domain", + "confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable.", + "confirmations.mute.confirm": "Mute", + "confirmations.mute.message": "Are you sure you want to mute {name}?", + "confirmations.unfollow.confirm": "Unfollow", + "confirmations.unfollow.message": "Are you sure you want to unfollow {name}?", + "embed.instructions": "Embed this status on your website by copying the code below.", + "embed.preview": "Here is what it will look like:", + "emoji_button.activity": "Activity", + "emoji_button.custom": "Custom", + "emoji_button.flags": "Flags", + "emoji_button.food": "Food & Drink", + "emoji_button.label": "Insert emoji", + "emoji_button.nature": "Nature", + "emoji_button.not_found": "No emojos!! (╯°□°)╯︵ ┻━┻", + "emoji_button.objects": "Objects", + "emoji_button.people": "People", + "emoji_button.recent": "Frequently used", + "emoji_button.search": "Search...", + "emoji_button.search_results": "Search results", + "emoji_button.symbols": "Symbols", + "emoji_button.travel": "Travel & Places", + "empty_column.community": "The local timeline is empty. Write something publicly to get the ball rolling!", + "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.", + "empty_column.hashtag": "There is nothing in this hashtag yet.", + "empty_column.home": "Your home timeline is empty! Visit {public} or use search to get started and meet other users.", + "empty_column.home.public_timeline": "the public timeline", + "empty_column.list": "There is nothing in this list yet. When members of this list post new statuses, they will appear here.", + "empty_column.notifications": "You don't have any notifications yet. Interact with others to start the conversation.", + "empty_column.public": "There is nothing here! Write something publicly, or manually follow users from other instances to fill it up", + "follow_request.authorize": "Authorize", + "follow_request.reject": "Reject", + "getting_started.appsshort": "Apps", + "getting_started.faq": "FAQ", + "getting_started.heading": "Getting started", + "getting_started.open_source_notice": "Mastodon is open source software. You can contribute or report issues on GitHub at {github}.", + "getting_started.userguide": "User Guide", + "home.column_settings.advanced": "Advanced", + "home.column_settings.basic": "Basic", + "home.column_settings.filter_regex": "Filter out by regular expressions", + "home.column_settings.show_reblogs": "Show boosts", + "home.column_settings.show_replies": "Show replies", + "home.settings": "Column settings", + "keyboard_shortcuts.back": "to navigate back", + "keyboard_shortcuts.boost": "to boost", + "keyboard_shortcuts.column": "to focus a status in one of the columns", + "keyboard_shortcuts.compose": "to focus the compose textarea", + "keyboard_shortcuts.description": "Description", + "keyboard_shortcuts.down": "to move down in the list", + "keyboard_shortcuts.enter": "to open status", + "keyboard_shortcuts.favourite": "to favourite", + "keyboard_shortcuts.heading": "Keyboard Shortcuts", + "keyboard_shortcuts.hotkey": "Hotkey", + "keyboard_shortcuts.legend": "to display this legend", + "keyboard_shortcuts.mention": "to mention author", + "keyboard_shortcuts.reply": "to reply", + "keyboard_shortcuts.search": "to focus search", + "keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW", + "keyboard_shortcuts.toot": "to start a brand new toot", + "keyboard_shortcuts.unfocus": "to un-focus compose textarea/search", + "keyboard_shortcuts.up": "to move up in the list", + "lightbox.close": "Close", + "lightbox.next": "Next", + "lightbox.previous": "Previous", + "lists.account.add": "Add to list", + "lists.account.remove": "Remove from list", + "lists.delete": "Delete list", + "lists.edit": "Edit list", + "lists.new.create": "Add list", + "lists.new.title_placeholder": "New list title", + "lists.search": "Search among people you follow", + "lists.subheading": "Your lists", + "loading_indicator.label": "Loading...", + "media_gallery.toggle_visible": "Toggle visibility", + "missing_indicator.label": "Not found", + "missing_indicator.sublabel": "This resource could not be found", + "mute_modal.hide_notifications": "Hide notifications from this user?", + "navigation_bar.blocks": "Blocked users", + "navigation_bar.community_timeline": "Local timeline", + "navigation_bar.direct": "Direct messages", + "navigation_bar.domain_blocks": "Hidden domains", + "navigation_bar.edit_profile": "Edit profile", + "navigation_bar.favourites": "Favourites", + "navigation_bar.follow_requests": "Follow requests", + "navigation_bar.info": "Extended information", + "navigation_bar.keyboard_shortcuts": "Keyboard shortcuts", + "navigation_bar.lists": "Lists", + "navigation_bar.logout": "Logout", + "navigation_bar.mutes": "Muted users", + "navigation_bar.pins": "Pinned toots", + "navigation_bar.preferences": "Preferences", + "navigation_bar.public_timeline": "Federated timeline", + "notification.favourite": "{name} favourited your status", + "notification.follow": "{name} followed you", + "notification.mention": "{name} mentioned you", + "notification.reblog": "{name} boosted your status", + "notifications.clear": "Clear notifications", + "notifications.clear_confirmation": "Are you sure you want to permanently clear all your notifications?", + "notifications.column_settings.alert": "Desktop notifications", + "notifications.column_settings.favourite": "Favourites:", + "notifications.column_settings.follow": "New followers:", + "notifications.column_settings.mention": "Mentions:", + "notifications.column_settings.push": "Push notifications", + "notifications.column_settings.push_meta": "This device", + "notifications.column_settings.reblog": "Boosts:", + "notifications.column_settings.show": "Show in column", + "notifications.column_settings.sound": "Play sound", + "onboarding.done": "Done", + "onboarding.next": "Next", + "onboarding.page_five.public_timelines": "The local timeline shows public posts from everyone on {domain}. The federated timeline shows public posts from everyone who people on {domain} follow. These are the Public Timelines, a great way to discover new people.", + "onboarding.page_four.home": "The home timeline shows posts from people you follow.", + "onboarding.page_four.notifications": "The notifications column shows when someone interacts with you.", + "onboarding.page_one.federation": "Mastodon is a network of independent servers joining up to make one larger social network. We call these servers instances.", + "onboarding.page_one.full_handle": "Your full handle", + "onboarding.page_one.handle_hint": "This is what you would tell your friends to search for.", + "onboarding.page_one.welcome": "Welcome to Mastodon!", + "onboarding.page_six.admin": "Your instance's admin is {admin}.", + "onboarding.page_six.almost_done": "Almost done...", + "onboarding.page_six.appetoot": "Bon Appetoot!", + "onboarding.page_six.apps_available": "There are {apps} available for iOS, Android and other platforms.", + "onboarding.page_six.github": "Mastodon is free open-source software. You can report bugs, request features, or contribute to the code on {github}.", + "onboarding.page_six.guidelines": "community guidelines", + "onboarding.page_six.read_guidelines": "Please read {domain}'s {guidelines}!", + "onboarding.page_six.various_app": "mobile apps", + "onboarding.page_three.profile": "Edit your profile to change your avatar, bio, and display name. There, you will also find other preferences.", + "onboarding.page_three.search": "Use the search bar to find people and look at hashtags, such as {illustration} and {introductions}. To look for a person who is not on this instance, use their full handle.", + "onboarding.page_two.compose": "Write posts from the compose column. You can upload images, change privacy settings, and add content warnings with the icons below.", + "onboarding.skip": "Skip", + "privacy.change": "Adjust status privacy", + "privacy.direct.long": "Post to mentioned users only", + "privacy.direct.short": "Direct", + "privacy.private.long": "Post to followers only", + "privacy.private.short": "Followers-only", + "privacy.public.long": "Post to public timelines", + "privacy.public.short": "Public", + "privacy.unlisted.long": "Do not show in public timelines", + "privacy.unlisted.short": "Unlisted", + "regeneration_indicator.label": "Loading…", + "regeneration_indicator.sublabel": "Your home feed is being prepared!", + "relative_time.days": "{number}d", + "relative_time.hours": "{number}h", + "relative_time.just_now": "now", + "relative_time.minutes": "{number}m", + "relative_time.seconds": "{number}s", + "reply_indicator.cancel": "Cancel", + "report.forward": "Forward to {target}", + "report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?", + "report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:", + "report.placeholder": "Additional comments", + "report.submit": "Submit", + "report.target": "Report {target}", + "search.placeholder": "Search", + "search_popout.search_format": "Advanced search format", + "search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.", + "search_popout.tips.hashtag": "hashtag", + "search_popout.tips.status": "status", + "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags", + "search_popout.tips.user": "user", + "search_results.accounts": "People", + "search_results.hashtags": "Hashtags", + "search_results.statuses": "Toots", + "search_results.total": "{count, number} {count, plural, one {result} other {results}}", + "standalone.public_title": "A look inside...", + "status.block": "Block @{name}", + "status.cancel_reblog_private": "Unboost", + "status.cannot_reblog": "This post cannot be boosted", + "status.delete": "Delete", + "status.direct": "Direct message @{name}", + "status.embed": "Embed", + "status.favourite": "Favourite", + "status.load_more": "Load more", + "status.media_hidden": "Media hidden", + "status.mention": "Mention @{name}", + "status.more": "More", + "status.mute": "Mute @{name}", + "status.mute_conversation": "Mute conversation", + "status.open": "Expand this status", + "status.pin": "Pin on profile", + "status.pinned": "Pinned toot", + "status.reblog": "Boost", + "status.reblog_private": "Boost to original audience", + "status.reblogged_by": "{name} boosted", + "status.reply": "Reply", + "status.replyAll": "Reply to thread", + "status.report": "Report @{name}", + "status.sensitive_toggle": "Click to view", + "status.sensitive_warning": "Sensitive content", + "status.share": "Share", + "status.show_less": "Show less", + "status.show_less_all": "Show less for all", + "status.show_more": "Show more", + "status.show_more_all": "Show more for all", + "status.unmute_conversation": "Unmute conversation", + "status.unpin": "Unpin from profile", + "tabs_bar.federated_timeline": "Federated", + "tabs_bar.home": "Home", + "tabs_bar.local_timeline": "Local", + "tabs_bar.notifications": "Notifications", + "tabs_bar.search": "Search", + "ui.beforeunload": "Your draft will be lost if you leave Mastodon.", + "upload_area.title": "Drag & drop to upload", + "upload_button.label": "Add media", + "upload_form.description": "Describe for the visually impaired", + "upload_form.focus": "Crop", + "upload_form.undo": "Undo", + "upload_progress.label": "Uploading...", + "video.close": "Close video", + "video.exit_fullscreen": "Exit full screen", + "video.expand": "Expand video", + "video.fullscreen": "Full screen", + "video.hide": "Hide video", + "video.mute": "Mute sound", + "video.pause": "Pause", + "video.play": "Play", + "video.unmute": "Unmute sound" +} diff --git a/app/javascript/mastodon/locales/fa.json b/app/javascript/mastodon/locales/fa.json index cfe9300..99aba00 100644 --- a/app/javascript/mastodon/locales/fa.json +++ b/app/javascript/mastodon/locales/fa.json @@ -135,6 +135,7 @@ "keyboard_shortcuts.mention": "برای نام‌بردن از نویسنده", "keyboard_shortcuts.reply": "برای پاسخ‌دادن", "keyboard_shortcuts.search": "برای فعال‌کردن جستجو", + "keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW", "keyboard_shortcuts.toot": "برای آغاز یک بوق تازه", "keyboard_shortcuts.unfocus": "برای برداشتن توجه از نوشتن/جستجو", "keyboard_shortcuts.up": "برای بالا رفتن در فهرست", diff --git a/app/javascript/mastodon/locales/fi.json b/app/javascript/mastodon/locales/fi.json index 1677c3c..07d4d9a 100644 --- a/app/javascript/mastodon/locales/fi.json +++ b/app/javascript/mastodon/locales/fi.json @@ -135,6 +135,7 @@ "keyboard_shortcuts.mention": "mainitse julkaisija", "keyboard_shortcuts.reply": "vastaa", "keyboard_shortcuts.search": "siirry hakukenttään", + "keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW", "keyboard_shortcuts.toot": "ala kirjoittaa uutta tuuttausta", "keyboard_shortcuts.unfocus": "siirry pois tekstikentästä tai hakukentästä", "keyboard_shortcuts.up": "siirry listassa ylöspäin", diff --git a/app/javascript/mastodon/locales/fr.json b/app/javascript/mastodon/locales/fr.json index 9a9e8ab..0343cc6 100644 --- a/app/javascript/mastodon/locales/fr.json +++ b/app/javascript/mastodon/locales/fr.json @@ -2,7 +2,7 @@ "account.block": "Bloquer @{name}", "account.block_domain": "Tout masquer venant de {domain}", "account.blocked": "Bloqué", - "account.direct": "Direct message @{name}", + "account.direct": "Message direct @{name}", "account.disclaimer_full": "Les données ci-dessous peuvent ne pas refléter ce profil dans sa totalité.", "account.domain_blocked": "Domaine caché", "account.edit_profile": "Modifier le profil", @@ -18,7 +18,7 @@ "account.mute_notifications": "Ignorer les notifications de @{name}", "account.muted": "Silencé", "account.posts": "Pouets", - "account.posts_with_replies": "Pouets avec réponses", + "account.posts_with_replies": "Pouets et réponses", "account.report": "Signaler", "account.requested": "En attente d'approbation. Cliquez pour annuler la requête", "account.share": "Partager le profil de @{name}", @@ -29,8 +29,8 @@ "account.unmute": "Ne plus masquer", "account.unmute_notifications": "Réactiver les notifications de @{name}", "account.view_full_profile": "Afficher le profil complet", - "alert.unexpected.message": "An unexpected error occurred.", - "alert.unexpected.title": "Oops!", + "alert.unexpected.message": "Une erreur non-attendue s'est produite.", + "alert.unexpected.title": "Oups !", "boost_modal.combo": "Vous pouvez appuyer sur {combo} pour pouvoir passer ceci, la prochaine fois", "bundle_column_error.body": "Une erreur s’est produite lors du chargement de ce composant.", "bundle_column_error.retry": "Réessayer", @@ -41,7 +41,7 @@ "column.blocks": "Comptes bloqués", "column.community": "Fil public local", "column.direct": "Direct messages", - "column.domain_blocks": "Hidden domains", + "column.domain_blocks": "Domaines cachés", "column.favourites": "Favoris", "column.follow_requests": "Demandes de suivi", "column.home": "Accueil", @@ -59,7 +59,7 @@ "column_header.unpin": "Retirer", "column_subheading.navigation": "Navigation", "column_subheading.settings": "Paramètres", - "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.", + "compose_form.direct_message_warning": "Ce pouet sera uniquement visible à tous les utilisateurs mentionnés.", "compose_form.hashtag_warning": "Ce pouet ne sera pas listé dans les recherches par hashtag car sa visibilité est réglée sur \"non-listé\". Seuls les pouets avec une visibilité \"publique\" peuvent être recherchés par hashtag.", "compose_form.lock_disclaimer": "Votre compte n’est pas {locked}. Tout le monde peut vous suivre et voir vos pouets privés.", "compose_form.lock_disclaimer.lock": "verrouillé", @@ -105,7 +105,7 @@ "empty_column.hashtag": "Il n’y a encore aucun contenu associé à ce hashtag.", "empty_column.home": "Vous ne suivez encore personne. Visitez {public} ou bien utilisez la recherche pour vous connecter à d’autres personnes.", "empty_column.home.public_timeline": "le fil public", - "empty_column.list": "Il n'y a rien dans cette liste pour l'instant. Dès que des personnes de cette liste publieront de nouveaux statuts, ils apparaîtront ici.", + "empty_column.list": "Il n'y a rien dans cette liste pour l'instant. Dès que des personnes de cette liste publierons de nouveaux statuts, ils apparaîtront ici.", "empty_column.notifications": "Vous n’avez pas encore de notification. Interagissez avec d’autres personnes pour débuter la conversation.", "empty_column.public": "Il n’y a rien ici ! Écrivez quelque chose publiquement, ou bien suivez manuellement des personnes d’autres instances pour remplir le fil public", "follow_request.authorize": "Accepter", @@ -135,6 +135,7 @@ "keyboard_shortcuts.mention": "pour mentionner l'auteur", "keyboard_shortcuts.reply": "pour répondre", "keyboard_shortcuts.search": "pour cibler la recherche", + "keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW", "keyboard_shortcuts.toot": "pour démarrer un tout nouveau pouet", "keyboard_shortcuts.unfocus": "pour recentrer composer textarea/search", "keyboard_shortcuts.up": "pour remonter dans la liste", @@ -157,7 +158,7 @@ "navigation_bar.blocks": "Comptes bloqués", "navigation_bar.community_timeline": "Fil public local", "navigation_bar.direct": "Direct messages", - "navigation_bar.domain_blocks": "Hidden domains", + "navigation_bar.domain_blocks": "Domaines cachés", "navigation_bar.edit_profile": "Modifier le profil", "navigation_bar.favourites": "Favoris", "navigation_bar.follow_requests": "Demandes de suivi", @@ -244,7 +245,7 @@ "status.cancel_reblog_private": "Unboost", "status.cannot_reblog": "Cette publication ne peut être boostée", "status.delete": "Effacer", - "status.direct": "Direct message @{name}", + "status.direct": "Message direct @{name}", "status.embed": "Intégrer", "status.favourite": "Ajouter aux favoris", "status.load_more": "Charger plus", @@ -275,7 +276,7 @@ "tabs_bar.home": "Accueil", "tabs_bar.local_timeline": "Fil public local", "tabs_bar.notifications": "Notifications", - "tabs_bar.search": "Search", + "tabs_bar.search": "Chercher", "ui.beforeunload": "Votre brouillon sera perdu si vous quittez Mastodon.", "upload_area.title": "Glissez et déposez pour envoyer", "upload_button.label": "Joindre un média", diff --git a/app/javascript/mastodon/locales/gl.json b/app/javascript/mastodon/locales/gl.json index fca4237..f7ad3e5 100644 --- a/app/javascript/mastodon/locales/gl.json +++ b/app/javascript/mastodon/locales/gl.json @@ -18,7 +18,7 @@ "account.mute_notifications": "Acalar as notificacións de @{name}", "account.muted": "Muted", "account.posts": "Toots", - "account.posts_with_replies": "Toots with replies", + "account.posts_with_replies": "Toots e respostas", "account.report": "Informar sobre @{name}", "account.requested": "Agardando aceptación. Pulse para cancelar a solicitude de seguimento", "account.share": "Compartir o perfil de @{name}", @@ -135,6 +135,7 @@ "keyboard_shortcuts.mention": "para mencionar o autor", "keyboard_shortcuts.reply": "para responder", "keyboard_shortcuts.search": "para centrar a busca", + "keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW", "keyboard_shortcuts.toot": "escribir un toot novo", "keyboard_shortcuts.unfocus": "quitar o foco do área de escritura/busca", "keyboard_shortcuts.up": "ir hacia arriba na lista", diff --git a/app/javascript/mastodon/locales/he.json b/app/javascript/mastodon/locales/he.json index e3e87f1..0ffbb14 100644 --- a/app/javascript/mastodon/locales/he.json +++ b/app/javascript/mastodon/locales/he.json @@ -135,6 +135,7 @@ "keyboard_shortcuts.mention": "לאזכר את המחבר(ת)", "keyboard_shortcuts.reply": "לענות", "keyboard_shortcuts.search": "להתמקד בחלון החיפוש", + "keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW", "keyboard_shortcuts.toot": "להתחיל חיצרוץ חדש", "keyboard_shortcuts.unfocus": "לצאת מתיבת חיבור/חיפוש", "keyboard_shortcuts.up": "לנוע במעלה הרשימה", diff --git a/app/javascript/mastodon/locales/hr.json b/app/javascript/mastodon/locales/hr.json index b41c983..c41cc3e 100644 --- a/app/javascript/mastodon/locales/hr.json +++ b/app/javascript/mastodon/locales/hr.json @@ -135,6 +135,7 @@ "keyboard_shortcuts.mention": "to mention author", "keyboard_shortcuts.reply": "to reply", "keyboard_shortcuts.search": "to focus search", + "keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW", "keyboard_shortcuts.toot": "to start a brand new toot", "keyboard_shortcuts.unfocus": "to un-focus compose textarea/search", "keyboard_shortcuts.up": "to move up in the list", diff --git a/app/javascript/mastodon/locales/hu.json b/app/javascript/mastodon/locales/hu.json index 956accc..a0c1861 100644 --- a/app/javascript/mastodon/locales/hu.json +++ b/app/javascript/mastodon/locales/hu.json @@ -135,6 +135,7 @@ "keyboard_shortcuts.mention": "szerző megjelenítése", "keyboard_shortcuts.reply": "válaszolás", "keyboard_shortcuts.search": "kereső kiemelése", + "keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW", "keyboard_shortcuts.toot": "új tülk megkezdése", "keyboard_shortcuts.unfocus": "tülk szerkesztés/keresés fókuszpontból való kivétele", "keyboard_shortcuts.up": "fennebb helyezés a listában", diff --git a/app/javascript/mastodon/locales/hy.json b/app/javascript/mastodon/locales/hy.json index 33e0792..a0442ba 100644 --- a/app/javascript/mastodon/locales/hy.json +++ b/app/javascript/mastodon/locales/hy.json @@ -135,6 +135,7 @@ "keyboard_shortcuts.mention": "հեղինակին նշելու համար", "keyboard_shortcuts.reply": "պատասխանելու համար", "keyboard_shortcuts.search": "որոնման դաշտին սեւեռվելու համար", + "keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW", "keyboard_shortcuts.toot": "թարմ թութ սկսելու համար", "keyboard_shortcuts.unfocus": "տեքստի/որոնման տիրույթից ապասեւեռվելու համար", "keyboard_shortcuts.up": "ցանկով վերեւ շարժվելու համար", diff --git a/app/javascript/mastodon/locales/id.json b/app/javascript/mastodon/locales/id.json index 412ffd3..2fd9225 100644 --- a/app/javascript/mastodon/locales/id.json +++ b/app/javascript/mastodon/locales/id.json @@ -135,6 +135,7 @@ "keyboard_shortcuts.mention": "to mention author", "keyboard_shortcuts.reply": "to reply", "keyboard_shortcuts.search": "untuk fokus mencari", + "keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW", "keyboard_shortcuts.toot": "to start a brand new toot", "keyboard_shortcuts.unfocus": "to un-focus compose textarea/search", "keyboard_shortcuts.up": "to move up in the list", diff --git a/app/javascript/mastodon/locales/io.json b/app/javascript/mastodon/locales/io.json index 9730bf9..ed45ee1 100644 --- a/app/javascript/mastodon/locales/io.json +++ b/app/javascript/mastodon/locales/io.json @@ -135,6 +135,7 @@ "keyboard_shortcuts.mention": "to mention author", "keyboard_shortcuts.reply": "to reply", "keyboard_shortcuts.search": "to focus search", + "keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW", "keyboard_shortcuts.toot": "to start a brand new toot", "keyboard_shortcuts.unfocus": "to un-focus compose textarea/search", "keyboard_shortcuts.up": "to move up in the list", diff --git a/app/javascript/mastodon/locales/it.json b/app/javascript/mastodon/locales/it.json index 5146d7c..c80fc0b 100644 --- a/app/javascript/mastodon/locales/it.json +++ b/app/javascript/mastodon/locales/it.json @@ -17,8 +17,8 @@ "account.mute": "Silenzia @{name}", "account.mute_notifications": "Mute notifications from @{name}", "account.muted": "Muted", - "account.posts": "Posts", - "account.posts_with_replies": "Toots with replies", + "account.posts": "Toot", + "account.posts_with_replies": "Toot con risposte", "account.report": "Segnala @{name}", "account.requested": "In attesa di approvazione", "account.share": "Share @{name}'s profile", @@ -105,7 +105,7 @@ "empty_column.hashtag": "Non c'è ancora nessun post con questo hashtag.", "empty_column.home": "Non stai ancora seguendo nessuno. Visita {public} o usa la ricerca per incontrare nuove persone.", "empty_column.home.public_timeline": "la timeline pubblica", - "empty_column.list": "There is nothing in this list yet.", + "empty_column.list": "Non c'è niente in questo elenco ancora. Quando i membri di questo elenco postano nuovi stati, questi appariranno qui.", "empty_column.notifications": "Non hai ancora nessuna notifica. Interagisci con altri per iniziare conversazioni.", "empty_column.public": "Qui non c'è nulla! Scrivi qualcosa pubblicamente, o aggiungi utenti da altri server per riempire questo spazio.", "follow_request.authorize": "Autorizza", @@ -135,6 +135,7 @@ "keyboard_shortcuts.mention": "to mention author", "keyboard_shortcuts.reply": "to reply", "keyboard_shortcuts.search": "to focus search", + "keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW", "keyboard_shortcuts.toot": "to start a brand new toot", "keyboard_shortcuts.unfocus": "to un-focus compose textarea/search", "keyboard_shortcuts.up": "to move up in the list", diff --git a/app/javascript/mastodon/locales/ja.json b/app/javascript/mastodon/locales/ja.json index 23223ca..a06bdad 100644 --- a/app/javascript/mastodon/locales/ja.json +++ b/app/javascript/mastodon/locales/ja.json @@ -29,8 +29,8 @@ "account.unmute": "@{name}さんのミュートを解除", "account.unmute_notifications": "@{name}さんからの通知を受け取るようにする", "account.view_full_profile": "全ての情報を見る", - "alert.unexpected.message": "不明なエラーが発生しました", - "alert.unexpected.title": "エラー", + "alert.unexpected.message": "不明なエラーが発生しました。", + "alert.unexpected.title": "エラー!", "boost_modal.combo": "次からは{combo}を押せばスキップできます", "bundle_column_error.body": "コンポーネントの読み込み中に問題が発生しました。", "bundle_column_error.retry": "再試行", diff --git a/app/javascript/mastodon/locales/ko.json b/app/javascript/mastodon/locales/ko.json index 92367dc..2a27346 100644 --- a/app/javascript/mastodon/locales/ko.json +++ b/app/javascript/mastodon/locales/ko.json @@ -2,7 +2,7 @@ "account.block": "@{name}을 차단", "account.block_domain": "{domain} 전체를 숨김", "account.blocked": "차단 됨", - "account.direct": "Direct message @{name}", + "account.direct": "@{name}으로부터의 다이렉트 메시지", "account.disclaimer_full": "여기 있는 정보는 유저의 프로파일을 정확히 반영하지 못 할 수도 있습니다.", "account.domain_blocked": "도메인 숨겨짐", "account.edit_profile": "프로필 편집", @@ -12,7 +12,7 @@ "account.follows_you": "날 팔로우합니다", "account.hide_reblogs": "@{name}의 부스트를 숨기기", "account.media": "미디어", - "account.mention": "답장", + "account.mention": "@{name}에게 글쓰기", "account.moved_to": "{name}는 계정을 이동했습니다:", "account.mute": "@{name} 뮤트", "account.mute_notifications": "@{name}의 알림을 뮤트", @@ -135,6 +135,7 @@ "keyboard_shortcuts.mention": "멘션", "keyboard_shortcuts.reply": "답장", "keyboard_shortcuts.search": "검색창에 포커스", + "keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW", "keyboard_shortcuts.toot": "새 툿 작성", "keyboard_shortcuts.unfocus": "작성창에서 포커스 해제", "keyboard_shortcuts.up": "리스트에서 위로 이동", diff --git a/app/javascript/mastodon/locales/nl.json b/app/javascript/mastodon/locales/nl.json index c18ddbd..338b7ae 100644 --- a/app/javascript/mastodon/locales/nl.json +++ b/app/javascript/mastodon/locales/nl.json @@ -18,7 +18,7 @@ "account.mute_notifications": "Negeer meldingen van @{name}", "account.muted": "Genegeerd", "account.posts": "Toots", - "account.posts_with_replies": "Toots met reacties", + "account.posts_with_replies": "Toots en reacties", "account.report": "Rapporteer @{name}", "account.requested": "Wacht op goedkeuring. Klik om het volgverzoek te annuleren", "account.share": "Profiel van @{name} delen", @@ -29,8 +29,8 @@ "account.unmute": "@{name} niet meer negeren", "account.unmute_notifications": "@{name} meldingen niet meer negeren", "account.view_full_profile": "Volledig profiel tonen", - "alert.unexpected.message": "An unexpected error occurred.", - "alert.unexpected.title": "Oops!", + "alert.unexpected.message": "Er deed zich een onverwachte fout voor", + "alert.unexpected.title": "Oeps!", "boost_modal.combo": "Je kunt {combo} klikken om dit de volgende keer over te slaan", "bundle_column_error.body": "Tijdens het laden van dit onderdeel is er iets fout gegaan.", "bundle_column_error.retry": "Opnieuw proberen", @@ -41,7 +41,7 @@ "column.blocks": "Geblokkeerde gebruikers", "column.community": "Lokale tijdlijn", "column.direct": "Direct messages", - "column.domain_blocks": "Hidden domains", + "column.domain_blocks": "Verborgen domeinen", "column.favourites": "Favorieten", "column.follow_requests": "Volgverzoeken", "column.home": "Start", @@ -59,7 +59,7 @@ "column_header.unpin": "Losmaken", "column_subheading.navigation": "Navigatie", "column_subheading.settings": "Instellingen", - "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.", + "compose_form.direct_message_warning": "Deze toot zal alleen zichtbaar zijn voor alle vermelde gebruikers.", "compose_form.hashtag_warning": "Deze toot valt niet onder een hashtag te bekijken, omdat deze niet op openbare tijdlijnen wordt getoond. Alleen openbare toots kunnen via hashtags gevonden worden.", "compose_form.lock_disclaimer": "Jouw account is niet {locked}. Iedereen kan jou volgen en toots zien die je alleen aan volgers hebt gericht.", "compose_form.lock_disclaimer.lock": "besloten", @@ -135,6 +135,7 @@ "keyboard_shortcuts.mention": "om de auteur te vermelden", "keyboard_shortcuts.reply": "om te reageren", "keyboard_shortcuts.search": "om het zoekvak te focussen", + "keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW", "keyboard_shortcuts.toot": "om een nieuwe toot te starten", "keyboard_shortcuts.unfocus": "om het tekst- en zoekvak te ontfocussen", "keyboard_shortcuts.up": "om omhoog te bewegen in de lijst", @@ -157,7 +158,7 @@ "navigation_bar.blocks": "Geblokkeerde gebruikers", "navigation_bar.community_timeline": "Lokale tijdlijn", "navigation_bar.direct": "Direct messages", - "navigation_bar.domain_blocks": "Hidden domains", + "navigation_bar.domain_blocks": "Verborgen domeinen", "navigation_bar.edit_profile": "Profiel bewerken", "navigation_bar.favourites": "Favorieten", "navigation_bar.follow_requests": "Volgverzoeken", @@ -244,7 +245,7 @@ "status.cancel_reblog_private": "Unboost", "status.cannot_reblog": "Deze toot kan niet geboost worden", "status.delete": "Verwijderen", - "status.direct": "Direct message @{name}", + "status.direct": "Directe toot @{name}", "status.embed": "Embed", "status.favourite": "Favoriet", "status.load_more": "Meer laden", @@ -275,7 +276,7 @@ "tabs_bar.home": "Start", "tabs_bar.local_timeline": "Lokaal", "tabs_bar.notifications": "Meldingen", - "tabs_bar.search": "Search", + "tabs_bar.search": "Zoeken", "ui.beforeunload": "Je concept zal verloren gaan als je Mastodon verlaat.", "upload_area.title": "Hierin slepen om te uploaden", "upload_button.label": "Media toevoegen", diff --git a/app/javascript/mastodon/locales/no.json b/app/javascript/mastodon/locales/no.json index 282a72a..0ee6d07 100644 --- a/app/javascript/mastodon/locales/no.json +++ b/app/javascript/mastodon/locales/no.json @@ -135,6 +135,7 @@ "keyboard_shortcuts.mention": "å nevne forfatter", "keyboard_shortcuts.reply": "for å svare", "keyboard_shortcuts.search": "å fokusere søk", + "keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW", "keyboard_shortcuts.toot": "å starte en helt ny tut", "keyboard_shortcuts.unfocus": "å ufokusere komponerings-/søkefeltet", "keyboard_shortcuts.up": "å flytte opp i listen", diff --git a/app/javascript/mastodon/locales/oc.json b/app/javascript/mastodon/locales/oc.json index 7170aef..66f3fa2 100644 --- a/app/javascript/mastodon/locales/oc.json +++ b/app/javascript/mastodon/locales/oc.json @@ -135,6 +135,7 @@ "keyboard_shortcuts.mention": "mencionar l’autor", "keyboard_shortcuts.reply": "respondre", "keyboard_shortcuts.search": "anar a la recèrca", + "keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW", "keyboard_shortcuts.toot": "començar un estatut tot novèl", "keyboard_shortcuts.unfocus": "quitar lo camp tèxte/de recèrca", "keyboard_shortcuts.up": "far montar dins la lista", diff --git a/app/javascript/mastodon/locales/pt-BR.json b/app/javascript/mastodon/locales/pt-BR.json index c604476..7013822 100644 --- a/app/javascript/mastodon/locales/pt-BR.json +++ b/app/javascript/mastodon/locales/pt-BR.json @@ -29,7 +29,7 @@ "account.unmute": "Não silenciar @{name}", "account.unmute_notifications": "Retirar silêncio das notificações vindas de @{name}", "account.view_full_profile": "Ver perfil completo", - "alert.unexpected.message": "An unexpected error occurred.", + "alert.unexpected.message": "Um erro inesperado ocorreu.", "alert.unexpected.title": "Oops!", "boost_modal.combo": "Você pode pressionar {combo} para ignorar este diálogo na próxima vez", "bundle_column_error.body": "Algo de errado aconteceu enquanto este componente era carregado.", @@ -59,7 +59,7 @@ "column_header.unpin": "Desafixar", "column_subheading.navigation": "Navegação", "column_subheading.settings": "Configurações", - "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.", + "compose_form.direct_message_warning": "Este toot só será visível a todos os usuários mencionados.", "compose_form.hashtag_warning": "Esse toot não será listado em nenhuma hashtag por ser não listado. Somente toots públicos podem ser pesquisados por hashtag.", "compose_form.lock_disclaimer": "A sua conta não está {locked}. Qualquer pessoa pode te seguir e visualizar postagens direcionadas a apenas seguidores.", "compose_form.lock_disclaimer.lock": "trancada", @@ -135,6 +135,7 @@ "keyboard_shortcuts.mention": "para mencionar o autor", "keyboard_shortcuts.reply": "para responder", "keyboard_shortcuts.search": "para focar a pesquisa", + "keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW", "keyboard_shortcuts.toot": "para compor um novo toot", "keyboard_shortcuts.unfocus": "para remover o foco da área de composição/pesquisa", "keyboard_shortcuts.up": "para mover para cima na lista", @@ -275,7 +276,7 @@ "tabs_bar.home": "Página inicial", "tabs_bar.local_timeline": "Local", "tabs_bar.notifications": "Notificações", - "tabs_bar.search": "Search", + "tabs_bar.search": "Buscar", "ui.beforeunload": "Seu rascunho será perdido se você sair do Mastodon.", "upload_area.title": "Arraste e solte para enviar", "upload_button.label": "Adicionar mídia", diff --git a/app/javascript/mastodon/locales/pt.json b/app/javascript/mastodon/locales/pt.json index 826785a..ce816dc 100644 --- a/app/javascript/mastodon/locales/pt.json +++ b/app/javascript/mastodon/locales/pt.json @@ -135,6 +135,7 @@ "keyboard_shortcuts.mention": "para mencionar o autor", "keyboard_shortcuts.reply": "para responder", "keyboard_shortcuts.search": "para focar na pesquisa", + "keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW", "keyboard_shortcuts.toot": "para compor um novo post", "keyboard_shortcuts.unfocus": "para remover o foco da área de publicação/pesquisa", "keyboard_shortcuts.up": "para mover para cima na lista", diff --git a/app/javascript/mastodon/locales/ru.json b/app/javascript/mastodon/locales/ru.json index bb3cc17..8eeebaf 100644 --- a/app/javascript/mastodon/locales/ru.json +++ b/app/javascript/mastodon/locales/ru.json @@ -135,6 +135,7 @@ "keyboard_shortcuts.mention": "упомянуть автора поста", "keyboard_shortcuts.reply": "ответить", "keyboard_shortcuts.search": "перейти к поиску", + "keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW", "keyboard_shortcuts.toot": "начать писать новый пост", "keyboard_shortcuts.unfocus": "убрать фокус с поля ввода/поиска", "keyboard_shortcuts.up": "вверх по списку", diff --git a/app/javascript/mastodon/locales/sk.json b/app/javascript/mastodon/locales/sk.json index 58274fd..8b19cac 100644 --- a/app/javascript/mastodon/locales/sk.json +++ b/app/javascript/mastodon/locales/sk.json @@ -2,7 +2,7 @@ "account.block": "Blokovať @{name}", "account.block_domain": "Ukryť všetko z {domain}", "account.blocked": "Blokovaný/á", - "account.direct": "Direct message @{name}", + "account.direct": "Súkromná správa pre @{name}", "account.disclaimer_full": "Inofrmácie nižšie nemusia byť úplným odrazom uživateľovho účtu.", "account.domain_blocked": "Doména ukrytá", "account.edit_profile": "Upraviť profil", @@ -29,7 +29,7 @@ "account.unmute": "Prestať ignorovať @{name}", "account.unmute_notifications": "Odtĺmiť notifikácie od @{name}", "account.view_full_profile": "Pozri celý profil", - "alert.unexpected.message": "An unexpected error occurred.", + "alert.unexpected.message": "Vyskytla sa neočakávaná chyba.", "alert.unexpected.title": "Oops!", "boost_modal.combo": "Nabudúce môžete kliknúť {combo} aby ste preskočili", "bundle_column_error.body": "Nastala chyba pri načítaní tohto komponentu.", @@ -41,7 +41,7 @@ "column.blocks": "Blokovaní užívatelia", "column.community": "Lokálna časová os", "column.direct": "Direct messages", - "column.domain_blocks": "Hidden domains", + "column.domain_blocks": "Skryté domény", "column.favourites": "Obľúbené", "column.follow_requests": "Žiadosti o sledovanie", "column.home": "Domov", @@ -59,7 +59,7 @@ "column_header.unpin": "Odopnúť", "column_subheading.navigation": "Navigácia", "column_subheading.settings": "Nastavenia", - "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.", + "compose_form.direct_message_warning": "Tento príspevok bude videný výhradne iba spomenutými užívateľmi.", "compose_form.hashtag_warning": "Tento toot nebude zobrazený pod žiadným haštagom lebo nieje listovaný. Iba verejné tooty môžu byť nájdené podľa haštagu.", "compose_form.lock_disclaimer": "Váš účet nie je zamknutý. Ktokoľvek ťa môže nasledovať a vidieť tvoje správy pre sledujúcich.", "compose_form.lock_disclaimer.lock": "zamknutý", @@ -135,6 +135,7 @@ "keyboard_shortcuts.mention": "spomenúť autora", "keyboard_shortcuts.reply": "odpovedať", "keyboard_shortcuts.search": "zamerať sa na vyhľadávanie", + "keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW", "keyboard_shortcuts.toot": "začať úplne novú hlášku", "keyboard_shortcuts.unfocus": "nesústrediť sa na písaciu plochu, alebo hľadanie", "keyboard_shortcuts.up": "posunúť sa vyššie v zozname", @@ -157,7 +158,7 @@ "navigation_bar.blocks": "Blokovaní užívatelia", "navigation_bar.community_timeline": "Lokálna časová os", "navigation_bar.direct": "Direct messages", - "navigation_bar.domain_blocks": "Hidden domains", + "navigation_bar.domain_blocks": "Skryté domény", "navigation_bar.edit_profile": "Upraviť profil", "navigation_bar.favourites": "Obľúbené", "navigation_bar.follow_requests": "Žiadosti o sledovanie", @@ -244,7 +245,7 @@ "status.cancel_reblog_private": "Unboost", "status.cannot_reblog": "Tento príspevok nemôže byť re-tootnutý", "status.delete": "Zmazať", - "status.direct": "Direct message @{name}", + "status.direct": "Súkromná správa @{name}", "status.embed": "Vložiť", "status.favourite": "Páči sa mi", "status.load_more": "Zobraz viac", @@ -275,7 +276,7 @@ "tabs_bar.home": "Domov", "tabs_bar.local_timeline": "Lokálna", "tabs_bar.notifications": "Notifikácie", - "tabs_bar.search": "Search", + "tabs_bar.search": "Hľadaj", "ui.beforeunload": "Čo máte rozpísané sa stratí, ak opustíte Mastodon.", "upload_area.title": "Ťahaj a pusti pre nahratie", "upload_button.label": "Pridať médiá", diff --git a/app/javascript/mastodon/locales/sr-Latn.json b/app/javascript/mastodon/locales/sr-Latn.json index e4d07ed..b1ea0d1 100644 --- a/app/javascript/mastodon/locales/sr-Latn.json +++ b/app/javascript/mastodon/locales/sr-Latn.json @@ -135,6 +135,7 @@ "keyboard_shortcuts.mention": "da pomenete autora", "keyboard_shortcuts.reply": "da odgovorite", "keyboard_shortcuts.search": "da se prebacite na pretragu", + "keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW", "keyboard_shortcuts.toot": "da započnete skroz novi tut", "keyboard_shortcuts.unfocus": "da ne budete više na pretrazi/pravljenju novog tuta", "keyboard_shortcuts.up": "da se pomerite na gore u listi", diff --git a/app/javascript/mastodon/locales/sr.json b/app/javascript/mastodon/locales/sr.json index 60c781e..aa97867 100644 --- a/app/javascript/mastodon/locales/sr.json +++ b/app/javascript/mastodon/locales/sr.json @@ -135,6 +135,7 @@ "keyboard_shortcuts.mention": "да поменете аутора", "keyboard_shortcuts.reply": "да одговорите", "keyboard_shortcuts.search": "да се пребаците на претрагу", + "keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW", "keyboard_shortcuts.toot": "да започнете скроз нови тут", "keyboard_shortcuts.unfocus": "да не будете више на претрази/прављењу новог тута", "keyboard_shortcuts.up": "да се померите на горе у листи", diff --git a/app/javascript/mastodon/locales/sv.json b/app/javascript/mastodon/locales/sv.json index 8fa6992..7db4d57 100644 --- a/app/javascript/mastodon/locales/sv.json +++ b/app/javascript/mastodon/locales/sv.json @@ -135,6 +135,7 @@ "keyboard_shortcuts.mention": "att nämna författaren", "keyboard_shortcuts.reply": "att svara", "keyboard_shortcuts.search": "att fokusera sökfältet", + "keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW", "keyboard_shortcuts.toot": "att börja en helt ny toot", "keyboard_shortcuts.unfocus": "att avfokusera komponera text fält / sökfält", "keyboard_shortcuts.up": "att flytta upp i listan", diff --git a/app/javascript/mastodon/locales/th.json b/app/javascript/mastodon/locales/th.json index 3b91c0d..82b44fe 100644 --- a/app/javascript/mastodon/locales/th.json +++ b/app/javascript/mastodon/locales/th.json @@ -135,6 +135,7 @@ "keyboard_shortcuts.mention": "to mention author", "keyboard_shortcuts.reply": "to reply", "keyboard_shortcuts.search": "to focus search", + "keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW", "keyboard_shortcuts.toot": "to start a brand new toot", "keyboard_shortcuts.unfocus": "to un-focus compose textarea/search", "keyboard_shortcuts.up": "to move up in the list", diff --git a/app/javascript/mastodon/locales/tr.json b/app/javascript/mastodon/locales/tr.json index cdf6f46..056fbfe 100644 --- a/app/javascript/mastodon/locales/tr.json +++ b/app/javascript/mastodon/locales/tr.json @@ -135,6 +135,7 @@ "keyboard_shortcuts.mention": "to mention author", "keyboard_shortcuts.reply": "to reply", "keyboard_shortcuts.search": "to focus search", + "keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW", "keyboard_shortcuts.toot": "to start a brand new toot", "keyboard_shortcuts.unfocus": "to un-focus compose textarea/search", "keyboard_shortcuts.up": "to move up in the list", diff --git a/app/javascript/mastodon/locales/uk.json b/app/javascript/mastodon/locales/uk.json index 261e579..1a7b587 100644 --- a/app/javascript/mastodon/locales/uk.json +++ b/app/javascript/mastodon/locales/uk.json @@ -135,6 +135,7 @@ "keyboard_shortcuts.mention": "to mention author", "keyboard_shortcuts.reply": "to reply", "keyboard_shortcuts.search": "to focus search", + "keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW", "keyboard_shortcuts.toot": "to start a brand new toot", "keyboard_shortcuts.unfocus": "to un-focus compose textarea/search", "keyboard_shortcuts.up": "to move up in the list", diff --git a/app/javascript/mastodon/locales/zh-CN.json b/app/javascript/mastodon/locales/zh-CN.json index aba0bde..a3a4de0 100644 --- a/app/javascript/mastodon/locales/zh-CN.json +++ b/app/javascript/mastodon/locales/zh-CN.json @@ -135,6 +135,7 @@ "keyboard_shortcuts.mention": "提及嘟文作者", "keyboard_shortcuts.reply": "回复嘟文", "keyboard_shortcuts.search": "选择搜索框", + "keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW", "keyboard_shortcuts.toot": "发送新嘟文", "keyboard_shortcuts.unfocus": "取消输入", "keyboard_shortcuts.up": "在列表中让光标上移", diff --git a/app/javascript/mastodon/locales/zh-HK.json b/app/javascript/mastodon/locales/zh-HK.json index b5ebd20..7719e08 100644 --- a/app/javascript/mastodon/locales/zh-HK.json +++ b/app/javascript/mastodon/locales/zh-HK.json @@ -135,6 +135,7 @@ "keyboard_shortcuts.mention": "提及作者", "keyboard_shortcuts.reply": "回覆", "keyboard_shortcuts.search": "把標示移動到搜索", + "keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW", "keyboard_shortcuts.toot": "新的推文", "keyboard_shortcuts.unfocus": "把標示移離文字輸入和搜索", "keyboard_shortcuts.up": "在列表往上移動", diff --git a/app/javascript/mastodon/locales/zh-TW.json b/app/javascript/mastodon/locales/zh-TW.json index 28d6346..84ff25e 100644 --- a/app/javascript/mastodon/locales/zh-TW.json +++ b/app/javascript/mastodon/locales/zh-TW.json @@ -135,6 +135,7 @@ "keyboard_shortcuts.mention": "到提到的作者", "keyboard_shortcuts.reply": "到回應", "keyboard_shortcuts.search": "to focus search", + "keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW", "keyboard_shortcuts.toot": "to start a brand new toot", "keyboard_shortcuts.unfocus": "to un-focus compose textarea/search", "keyboard_shortcuts.up": "to move up in the list", diff --git a/config/locales/activerecord.eu.yml b/config/locales/activerecord.eu.yml new file mode 100644 index 0000000..7b0ebe0 --- /dev/null +++ b/config/locales/activerecord.eu.yml @@ -0,0 +1,9 @@ +--- +eu: + activerecord: + errors: + models: + account: + attributes: + username: + invalid: letrak, zenbakiak eta gidoi baxuak besterik ez diff --git a/config/locales/ca.yml b/config/locales/ca.yml index c9173cd..9e927f7 100644 --- a/config/locales/ca.yml +++ b/config/locales/ca.yml @@ -4,6 +4,7 @@ ca: about_hashtag_html: Aquests són toots públics etiquetats amb #%{hashtag}. Pots interactuar amb ells si tens un compte a qualsevol lloc del fediverse. about_mastodon_html: Mastodon és una xarxa social basada en protocols web oberts i en programari lliure i de codi obert. Està descentralitzat com el correu electrònic. about_this: Quant a + administered_by: 'Administrat per:' closed_registrations: Actualment, el registre està tancat en aquesta instància. Malgrat això! Pots trobar una altra instància per fer-te un compte i obtenir accés a la mateixa xarxa des d'allà. contact: Contacte contact_missing: No configurat @@ -60,7 +61,15 @@ ca: destroyed_msg: Nota de moderació destruïda amb èxit! accounts: are_you_sure: N'estàs segur? + avatar: Avatar by_domain: Domini + change_email: + changed_msg: El correu electrònic del compte s'ha canviat correctament! + current_email: Correu electrònic actual + label: Canviar l'adreça de correu + new_email: Nou correu + submit: Canviar adreça de correu + title: Canviar adreça de correu de %{username} confirm: Confirma confirmed: Confirmat demote: Degrada @@ -108,6 +117,7 @@ ca: public: Públic push_subscription_expires: La subscripció PuSH expira redownload: Actualitza l'avatar + remove_avatar: Eliminar avatar reset: Reinicialitza reset_password: Restableix la contrasenya resubscribe: Torna a subscriure @@ -128,6 +138,7 @@ ca: statuses: Estats subscribe: Subscriu title: Comptes + unconfirmed_email: Correu electrònic sense confirmar undo_silenced: Deixa de silenciar undo_suspension: Desfés la suspensió unsubscribe: Cancel·la la subscripció @@ -135,6 +146,8 @@ ca: web: Web action_logs: actions: + assigned_to_self_report: "%{name} han assignat l'informe %{target} a ells mateixos" + change_email_user: "%{name} ha canviat l'adreça de correu electrònic del usuari %{target}" confirm_user: "%{name} ha confirmat l'adreça de correu electrònic de l'usuari %{target}" create_custom_emoji: "%{name} ha pujat un nou emoji %{target}" create_domain_block: "%{name} ha blocat el domini %{target}" @@ -150,10 +163,13 @@ ca: enable_user: "%{name} ha activat l'accés per a l'usuari %{target}" memorialize_account: "%{name} ha convertit el compte %{target} en una pàgina de memorial" promote_user: "%{name} ha promogut l'usuari %{target}" + remove_avatar_user: "%{name} ha eliminat l'avatar de %{target}" + reopen_report: "%{name} ha reobert l'informe %{target}" reset_password_user: "%{name} ha restablert la contrasenya de l'usuari %{target}" - resolve_report: "%{name} ha descartat l'informe %{target}" + resolve_report: "%{name} ha resolt l'informe %{target}" silence_account: "%{name} ha silenciat el compte de %{target}" suspend_account: "%{name} ha suspès el compte de %{target}" + unassigned_report: "%{name} ha des-assignat l'informe %{target}" unsilence_account: "%{name} ha silenciat el compte de %{target}" unsuspend_account: "%{name} ha llevat la suspensió del compte de %{target}" update_custom_emoji: "%{name} ha actualitzat l'emoji %{target}" @@ -239,28 +255,48 @@ ca: expired: Caducat title: Filtre title: Convida + report_notes: + created_msg: La nota del informe s'ha creat correctament! + destroyed_msg: La nota del informe s'ha esborrat correctament! reports: + account: + note: nota + report: informe action_taken_by: Mesures adoptades per are_you_sure: N'estàs segur? + assign_to_self: Assignar-me + assigned: Assignar Moderador comment: none: Cap + created_at: Reportat delete: Suprimeix id: ID mark_as_resolved: Marca com a resolt + mark_as_unresolved: Marcar sense resoldre + notes: + create: Afegir nota + create_and_resolve: Resoldre amb Nota + create_and_unresolve: Reobrir amb Nota + delete: Esborrar + placeholder: Descriu les accions que s'han pres o qualsevol altra actualització d'aquest informe… nsfw: 'false': Mostra els fitxers multimèdia adjunts 'true': Amaga els fitxers multimèdia adjunts + reopen: Reobrir Informe report: 'Informe #%{id}' report_contents: Contingut reported_account: Compte reportat reported_by: Reportat per resolved: Resolt + resolved_msg: Informe resolt amb èxit! silence_account: Silencia el compte status: Estat suspend_account: Suspèn el compte target: Objectiu title: Informes + unassign: Treure assignació unresolved: No resolt + updated_at: Actualitzat view: Visualització settings: activity_api_enabled: @@ -381,6 +417,7 @@ ca: security: Seguretat set_new_password: Estableix una contrasenya nova authorize_follow: + already_following: Ja estàs seguint aquest compte error: Malauradament, ha ocorregut un error cercant el compte remot follow: Segueix follow_request: 'Has enviat una sol·licitud de seguiment a:' @@ -473,6 +510,7 @@ ca: '21600': 6 hores '3600': 1 hora '43200': 12 hores + '604800': 1 setmana '86400': 1 dia expires_in_prompt: Mai generate: Genera @@ -576,6 +614,10 @@ ca: missing_resource: No s'ha pogut trobar la URL de redirecció necessaria per al compte proceed: Comença a seguir prompt: 'Seguiràs a:' + remote_unfollow: + error: Error + title: Títol + unfollowed: Sense seguir sessions: activity: Última activitat browser: Navegador @@ -664,6 +706,83 @@ ca: reblogged: ha impulsat sensitive_content: Contingut sensible terms: + body_html: | +

    Privacy Policy

    +

    Quina informació recollim?

    + +
      +
    • Informació bàsica del compte: Si et registres en aquest servidor, se´t pot demanar que introdueixis un nom d'usuari, una adreça de correu electrònic i una contrasenya. També pots introduir informació de perfil addicional, com ara un nom de visualització i una biografia, i carregar una imatge de perfil i de capçalera. El nom d'usuari, el nom de visualització, la biografia, la imatge de perfil i la imatge de capçalera sempre apareixen públicament.
    • +
    • Publicacions, seguiment i altra informació pública: La llista de persones que segueixes s'enumeren públicament i el mateix passa amb els teus seguidors. Quan envies un missatge, la data i l'hora s'emmagatzemen, així com l'aplicació que va enviar el missatge. Els missatges poden contenir multimèdia, com ara imatges i vídeos. Els toots públics i no llistats estan disponibles públicament. En quan tinguis un toot en el teu perfil, aquest també és informació pública. Les teves entrades es lliuren als teus seguidors que en alguns casos significa que es lliuren a diferents servidors i s'hi emmagatzemen còpies. Quan suprimeixes publicacions, també es lliurarà als vostres seguidors. L'acció d'impulsar o marcar com a favorit una publicació sempre és pública.
    • +
    • Toots directes i per a només seguidors: Totes les publicacions s'emmagatzemen i processen al servidor. Els toots per a només seguidors només es lliuren als teus seguidors i als usuaris que s'esmenten en ells i els toots directes només es lliuren als usuaris esmentats. En alguns casos, significa que es lliuren a diferents servidors i s'hi emmagatzemen còpies. Fem un esforç de bona fe per limitar l'accés a aquestes publicacions només a les persones autoritzades, però és possible que altres servidors no ho facin. Per tant, és important revisar els servidors als quals pertanyen els teus seguidors. Pots canviar d'opció per aprovar i rebutjar els nous seguidors manualment a la configuració. Tingues en compte que els operadors del servidor i qualsevol servidor receptor poden visualitzar aquests missatges i els destinataris poden fer una captura de pantalla, copiar-los o tornar-los a compartir. No comparteixis cap informació perillosa a Mastodon.
    • +
    • IPs i altres metadades: Quan inicies sessió registrem l'adreça IP en què has iniciat la sessió, així com el nom de l'aplicació o navegador. Totes les sessions registrades estan disponibles per a la teva revisió i revocació a la configuració. L'última adreça IP utilitzada s'emmagatzema durant un màxim de 12 mesos. També podrem conservar els registres del servidor que inclouen l'adreça IP de cada sol·licitud al nostre servidor.
    • +
    + +
    + +

    Per a què utilitzem la teva informació?

    + +

    Qualsevol de la informació que recopilem de tu es pot utilitzar de la manera següent:

    + +
      +
    • Per proporcionar la funcionalitat bàsica de Mastodon. Només pots interactuar amb el contingut d'altres persones i publicar el teu propi contingut quan hàgis iniciat la sessió. Per exemple, pots seguir altres persones per veure les publicacions combinades a la teva pròpia línia de temps personalitzada.
    • +
    • Per ajudar a la moderació de la comunitat, per exemple comparar la teva adreça IP amb altres conegudes per determinar l'evasió de la prohibició o altres infraccions.
    • +
    • L'adreça electrònica que proporciones pot utilitzar-se per enviar-te informació, notificacions sobre altres persones que interactuen amb el teu contingut o t'envien missatges, i per respondre a les consultes i / o altres sol·licituds o preguntes.
    • +
    + +
    + +

    Com protegim la teva informació

    + +

    Implementem diverses mesures per mantenir la seguretat de la teva informació personal quan introdueixes, envies o accedeixes a la teva informació personal. Entre altres coses, la sessió del teu navegador així com el trànsit entre les teves aplicacions i l'API estan protegides amb SSL i la teva contrasenya es codifica utilitzant un algoritme de direcció única. Pots habilitar l'autenticació de dos factors per a garantir l'accés segur al teu compte.

    + +
    + +

    Quina és la nostra política de retenció de dades?

    + +

    Farem un esforç de bona fe per:

    + +
      +
    • Conservar els registres del servidor que continguin l'adreça IP de totes les sol·licituds a aquest servidor, tenint em compte que aquests registres es mantenen no més de 90 dies.
    • +
    • Conservar les adreces IP associades als usuaris registrats no més de 12 mesos.
    • +
    + +

    Pots sol·licitar i descarregar un arxiu del teu contingut incloses les publicacions, els fitxers adjunts multimèdia, la imatge de perfil i la imatge de capçalera.

    + +

    Pots eliminar el compte de forma irreversible en qualsevol moment.

    + +
    + +

    Utilitzem cookies?

    + +

    Sí. Les cookies són petits fitxers que un lloc o el proveïdor de serveis transfereix al disc dur del teu ordinador a través del navegador web (si ho permet). Aquestes galetes permeten al lloc reconèixer el teu navegador i, si teniu un compte registrat, associar-lo al teu compte registrat.

    + +

    Utilitzem cookies per entendre i guardar les teves preferències per a futures visites.

    + +
    + +

    Revelem informació a terceres parts?

    + +

    No venem, comercialitzem ni transmetem a tercers la teva informació d'identificació personal. Això no inclou tercers de confiança que ens ajuden a operar el nostre lloc, a dur a terme el nostre negoci o a servir-te, sempre que aquestes parts acceptin mantenir confidencial aquesta informació. També podem publicar la teva informació quan creiem que l'alliberament és apropiat per complir amb la llei, fer complir les polítiques del nostre lloc o protegir els nostres drets o altres drets, propietat o seguretat.

    + +

    Els altres servidors de la teva xarxa poden descarregar contingut públic. Els teus toots públics i per a només seguidors es lliuren als servidors on resideixen els teus seguidors i els missatges directes s'envien als servidors dels destinataris, sempre que aquests seguidors o destinataris resideixin en un servidor diferent d'això.

    + +

    Quan autoritzes una aplicació a utilitzar el teu compte, segons l'abast dels permisos que aprovis, pot accedir a la teva informació de perfil pública, a la teva llista de seguits, als teus seguidors, a les teves llistes, a totes les teves publicacions i als teus favorits. Les aplicacions mai no poden accedir a la teva adreça de correu electrònic o contrasenya.

    + +
    + +

    Compliment de la Llei de protecció de la privacitat en línia dels nens

    + +

    El nostre lloc, productes i serveis estan dirigits a persones que tenen almenys 13 anys. Si aquest servidor es troba als EUA, i teniu menys de 13 anys, segons els requisits de COPPA (Children's Online Privacy Protection Act) no utilitzis aquest lloc.

    + +
    + +

    Canvis a la nostra política de privacitat

    + +

    Si decidim canviar la nostra política de privadesa, publicarem aquests canvis en aquesta pàgina.

    + +

    Aquest document és CC-BY-SA. Actualitzat per darrera vegada el 7 de Març del 2018.

    + +

    Originalment adaptat des del Discourse privacy policy.

    title: "%{instance} Condicions del servei i política de privadesa" themes: default: Mastodont diff --git a/config/locales/devise.eu.yml b/config/locales/devise.eu.yml new file mode 100644 index 0000000..215b72e --- /dev/null +++ b/config/locales/devise.eu.yml @@ -0,0 +1,5 @@ +--- +eu: + devise: + failure: + already_authenticated: Saioa hasi duzu jada. diff --git a/config/locales/devise.it.yml b/config/locales/devise.it.yml index e1ba7bb..0c5d896 100644 --- a/config/locales/devise.it.yml +++ b/config/locales/devise.it.yml @@ -17,11 +17,32 @@ it: unconfirmed: Devi confermare il tuo indirizzo email per continuare. mailer: confirmation_instructions: + action: Verifica indirizzo email + explanation: Hai creato un account su %{host} con questo indirizzo email. Sei lonatno solo un clic dall'attivarlo. Se non sei stato tu, per favore ignora questa email. + extra_html: Per favore controllale regole dell'istanza e i nostri termini di servizio. subject: 'Mastodon: Istruzioni di conferma per %{instance}' + title: Verifica indirizzo email + email_changed: + explanation: 'L''indirizzo email del tuo account sta per essere cambiato in:' + extra: Se non hai cambiato la tua email, è probabile che qualcuno abbia accesso al tuo account. Cambia immediatamente la tua password e contatta l'amministratore dell'istanza se sei bloccato fuori dal tuo account. + subject: 'Mastodon: Email cambiata' + title: Nuovo indirizzo email password_change: + explanation: La password del tuo account è stata cambiata. + extra: Se non hai cambiato la password, è probabile che qualcuno abbia accesso al tuo account. Cambia immediatamente la tua password e contatta l'amministratore dell'istanza se sei bloccato fuori dal tuo account. subject: 'Mastodon: Password modificata' + title: Password cambiata + reconfirmation_instructions: + explanation: Conferma il nuovo indirizzo per cambiare la tua email. + extra: Se questo cambiamento non è stato chiesto da te, ignora questa email. L'indirizzo email per l'account Mastodon non verrà cambiato finché non accedi dal link qui sopra. + subject: 'Mastodon: Email di conferma per %{instance}' + title: Verifica indirizzo email reset_password_instructions: + action: Cambia password + explanation: Hai richiesto una nuova password per il tuo account. + extra: Se questo cambiamento non è stato chiesto da te, ignora questa email. La tua password non verrà cambiata finché non accedi tramite il link qui sopra e ne crei una nuova. subject: 'Mastodon: Istruzioni per il reset della password' + title: Ripristino password unlock_instructions: subject: 'Mastodon: Istruzioni di sblocco' omniauth_callbacks: diff --git a/config/locales/doorkeeper.eu.yml b/config/locales/doorkeeper.eu.yml new file mode 100644 index 0000000..a51b1dc --- /dev/null +++ b/config/locales/doorkeeper.eu.yml @@ -0,0 +1,6 @@ +--- +eu: + activerecord: + attributes: + doorkeeper/application: + name: Aplikazioaren izena diff --git a/config/locales/doorkeeper.it.yml b/config/locales/doorkeeper.it.yml index e5a2d3f..50b2c97 100644 --- a/config/locales/doorkeeper.it.yml +++ b/config/locales/doorkeeper.it.yml @@ -3,8 +3,10 @@ it: activerecord: attributes: doorkeeper/application: - name: Nome + name: Nome applicazione redirect_uri: URI di reindirizzamento + scopes: Scopi + website: Sito web applicazione errors: models: doorkeeper/application: @@ -33,9 +35,13 @@ it: redirect_uri: Usa una riga per URI scopes: Dividi gli scopes con spazi. Lascia vuoto per utilizzare gli scopes di default. index: + application: Applicazione callback_url: Callback URL + delete: Elimina name: Nome new: Nuova applicazione + scopes: Scopes + show: Mostra title: Le tue applicazioni new: title: Nuova applicazione @@ -43,7 +49,7 @@ it: actions: Azioni application_id: Id applicazione callback_urls: Callback urls - scopes: Scopes + scopes: Scopi secret: Secret title: 'Applicazione: %{name}' authorizations: @@ -57,7 +63,7 @@ it: prompt: L'applicazione %{client_name} richiede l'accesso al tuo account title: Autorizzazione richiesta show: - title: Copy this authorization code and paste it to the application. + title: Copia questo codice di autorizzazione e incollalo nell'applicazione. authorized_applications: buttons: revoke: Disabilita @@ -67,7 +73,7 @@ it: application: Applicazione created_at: Autorizzato date_format: "%d-%m-%Y %H:%M:%S" - scopes: Scopes + scopes: Scopi title: Applicazioni autorizzate errors: messages: @@ -104,7 +110,7 @@ it: admin: nav: applications: Applicazioni - oauth2_provider: OAuth2 Provider + oauth2_provider: Provider OAuth2 application: title: Autorizzazione OAuth richiesta scopes: diff --git a/config/locales/eu.yml b/config/locales/eu.yml new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/config/locales/eu.yml @@ -0,0 +1 @@ +{} diff --git a/config/locales/fr.yml b/config/locales/fr.yml index ebe5793..fa96d9d 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -4,6 +4,7 @@ fr: about_hashtag_html: Figurent ci-dessous les pouets tagués avec #%{hashtag}. Vous pouvez interagir avec eux si vous avez un compte n’importe où dans le Fediverse. about_mastodon_html: Mastodon est un réseau social utilisant des formats ouverts et des logiciels libres. Comme le courriel, il est décentralisé. about_this: À propos + administered_by: 'Administré par :' closed_registrations: Les inscriptions sont actuellement fermées sur cette instance. Cependant, vous pouvez trouver une autre instance sur laquelle vous créer un compte et à partir de laquelle vous pourrez accéder au même réseau. contact: Contact contact_missing: Manquant @@ -60,7 +61,15 @@ fr: destroyed_msg: Note de modération supprimée avec succès ! accounts: are_you_sure: Êtes-vous certain⋅e ? + avatar: Avatar by_domain: Domaine + change_email: + changed_msg: Courriel du compte modifié avec succès ! + current_email: Courriel actuel + label: Modifier le courriel + new_email: Nouveau courriel + submit: Modifier le courriel + title: Modifier le courriel pour %{username} confirm: Confirmer confirmed: Confirmé demote: Rétrograder @@ -108,6 +117,7 @@ fr: public: Publique push_subscription_expires: Expiration de l’abonnement PuSH redownload: Rafraîchir les avatars + remove_avatar: Supprimer l'avatar reset: Réinitialiser reset_password: Réinitialiser le mot de passe resubscribe: Se réabonner @@ -128,6 +138,7 @@ fr: statuses: Statuts subscribe: S’abonner title: Comptes + unconfirmed_email: Courriel non-confirmé undo_silenced: Démasquer undo_suspension: Annuler la suspension unsubscribe: Se désabonner @@ -135,6 +146,8 @@ fr: web: Web action_logs: actions: + assigned_to_self_report: "%{name} s'est assigné le signalement de %{target} à eux-même" + change_email_user: "%{name} a modifié l'adresse de courriel de l'utilisateur %{target}" confirm_user: "%{name} adresse courriel confirmée de l'utilisateur %{target}" create_custom_emoji: "%{name} a importé de nouveaux emoji %{target}" create_domain_block: "%{name} a bloqué le domaine %{target}" @@ -150,10 +163,13 @@ fr: enable_user: "%{name} a activé le login pour l'utilisateur %{target}" memorialize_account: "%{name} a transformé le compte de %{target} en une page de mémorial" promote_user: "%{name} a promu l'utilisateur %{target}" + remove_avatar_user: "%{name} a supprimé l'avatar de %{target}'s" + reopen_report: "%{name} a ré-ouvert le signalement %{target}" reset_password_user: "%{name} a réinitialisé le mot de passe de %{target}" - resolve_report: "%{name} n'a pas pris en compte la dénonciation de %{target}" + resolve_report: "%{name} a résolu la dénonciation de %{target}" silence_account: "%{name} a mis le compte %{target} en mode silence" suspend_account: "%{name} a suspendu le compte %{target}" + unassigned_report: "%{name} a dés-assigné le signalement %{target}" unsilence_account: "%{name} a mis fin au mode silence de %{target}" unsuspend_account: "%{name} a réactivé le compte de %{target}" update_custom_emoji: "%{name} a mis à jour l'emoji %{target}" @@ -239,28 +255,48 @@ fr: expired: Expiré title: Filtre title: Invitations + report_notes: + created_msg: Note de signalement créée avec succès ! + destroyed_msg: Note de signalement effacée avec succès ! reports: + account: + note: note + report: signaler action_taken_by: Intervention de are_you_sure: Êtes vous certain⋅e ? + assign_to_self: Me l'assigner + assigned: Modérateur assigné comment: none: Aucun + created_at: Signalé delete: Supprimer id: ID mark_as_resolved: Marquer comme résolu + mark_as_unresolved: Marquer comme non-résolu + notes: + create: Ajouter une note + create_and_resolve: Résoudre avec une note + create_and_unresolve: Ré-ouvrir avec une note + delete: Effacer + placeholder: Décrivez quelles actions ont été prises, ou toute autre mise à jour de ce signalement… nsfw: 'false': Ré-afficher les médias 'true': Masquer les médias + reopen: Ré-ouvrir le signalement report: 'Signalement #%{id}' report_contents: Contenu reported_account: Compte signalé reported_by: Signalé par resolved: Résolus + resolved_msg: Signalement résolu avec succès ! silence_account: Masquer le compte status: Statut suspend_account: Suspendre le compte target: Cible title: Signalements + unassign: Dés-assigner unresolved: Non résolus + updated_at: Mis à jour view: Voir settings: activity_api_enabled: @@ -381,6 +417,7 @@ fr: security: Sécurité set_new_password: Définir le nouveau mot de passe authorize_follow: + already_following: Vous suivez déjà ce compte error: Malheureusement, il y a eu une erreur en cherchant les détails du compte distant follow: Suivre follow_request: 'Vous avez demandé à suivre :' @@ -473,6 +510,7 @@ fr: '21600': 6 heures '3600': 1 heure '43200': 12 heures + '604800': 1 semaine '86400': 1 jour expires_in_prompt: Jamais generate: Générer @@ -576,6 +614,10 @@ fr: missing_resource: L’URL de redirection n’a pas pu être trouvée proceed: Continuez pour suivre prompt: 'Vous allez suivre :' + remote_unfollow: + error: Erreur + title: Titre + unfollowed: Non-suivi sessions: activity: Dernière activité browser: Navigateur @@ -641,6 +683,7 @@ fr: video: one: "%{count} vidéo" other: "%{count} vidéos" + content_warning: 'Attention au contenu : %{warning}' open_in_web: Ouvrir sur le web over_character_limit: limite de caractères dépassée de %{max} caractères pin_errors: diff --git a/config/locales/gl.yml b/config/locales/gl.yml index 89de27a..3f936c0 100644 --- a/config/locales/gl.yml +++ b/config/locales/gl.yml @@ -4,6 +4,7 @@ gl: about_hashtag_html: Estas son mensaxes públicas etiquetadas con #%{hashtag}. Pode interactuar con elas si ten unha conta nalgures do fediverso. about_mastodon_html: Mastodon é unha rede social que se basea en protocolos web abertos e libres, software de código aberto. É descentralizada como o correo electrónico. about_this: Sobre + administered_by: 'Administrada por:' closed_registrations: O rexistro en esta instancia está pechado en este intre. Porén! Pode atopar unha instancia diferente para obter unha conta e ter acceso exactamente a misma rede desde alí. contact: Contacto contact_missing: Non establecido @@ -60,7 +61,15 @@ gl: destroyed_msg: Nota a moderación destruída con éxito! accounts: are_you_sure: Está segura? + avatar: Avatar by_domain: Dominio + change_email: + changed_msg: Cambiouse correctamente o correo-e da conta! + current_email: Correo-e actual + label: Cambiar correo-e + new_email: Novo correo-e + submit: Cambiar correo-e + title: Cambiar o correo-e de %{username} confirm: Confirmar confirmed: Confirmado demote: Degradar @@ -108,6 +117,7 @@ gl: public: Público push_subscription_expires: A suscrición PuSH caduca redownload: Actualizar avatar + remove_avatar: Eliminar avatar reset: Restablecer reset_password: Restablecer contrasinal resubscribe: Voltar a suscribir @@ -128,6 +138,7 @@ gl: statuses: Estados subscribe: Subscribir title: Contas + unconfirmed_email: Correo-e non confirmado undo_silenced: Desfacer acalar undo_suspension: Desfacer suspensión unsubscribe: Non subscribir @@ -135,6 +146,8 @@ gl: web: Web action_logs: actions: + assigned_to_self_report: "%{name} asignou o informe %{target} a ela misma" + change_email_user: "%{name} cambiou o enderezo de correo-e da usuaria %{target}" confirm_user: "%{name} comfirmou o enderezo de correo da usuaria %{target}" create_custom_emoji: "%{name} subeu un novo emoji %{target}" create_domain_block: "%{name} bloqueou o dominio %{target}" @@ -150,10 +163,13 @@ gl: enable_user: "%{name} habilitou a conexión para a usuaria %{target}" memorialize_account: "%{name} converteu a conta de %{target} nunha páxina para a lembranza" promote_user: "%{name} promoveu a usuaria %{target}" + remove_avatar_user: "%{name} eliminou o avatar de %{target}" + reopen_report: "%{name} voltou abrir informe %{target}" reset_password_user: "%{name} restableceu o contrasinal da usuaria %{target}" - resolve_report: "%{name} rexeitou o informe %{target}" + resolve_report: "%{name} solucionou o informe %{target}" silence_account: "%{name} acalou a conta de %{target}" suspend_account: "%{name} suspendeu a conta de %{target}" + unassigned_report: "%{name} non asignou informe %{target}" unsilence_account: "%{name} deulle voz a conta de %{target}" unsuspend_account: "%{name} activou a conta de %{target}" update_custom_emoji: "%{name} actualizou emoji %{target}" @@ -239,28 +255,48 @@ gl: expired: Cadudado title: Filtro title: Convida + report_notes: + created_msg: Creouse correctamente a nota do informe! + destroyed_msg: Nota do informe eliminouse con éxito! reports: + account: + note: nota + report: informe action_taken_by: Acción tomada por are_you_sure: Está segura? + assign_to_self: Asignarmo + assigned: Asignado ao Moderador comment: none: Nada + created_at: Reportado delete: Eliminar id: ID mark_as_resolved: Marcar como resolto + mark_as_unresolved: Marcar como non resolto + notes: + create: Engadir nota + create_and_resolve: Resolver con nota + create_and_unresolve: Votar a abrir con Nota + delete: Eliminar + placeholder: Describir qué decisións foron tomadas, ou calquer actualización a este informe… nsfw: 'false': Non agochar anexos de medios 'true': Agochar anexos de medios + reopen: Voltar a abrir o informe report: 'Informe #%{id}' report_contents: Contidos reported_account: Conta reportada reported_by: Reportada por resolved: Resolto + resolved_msg: Resolveuse con éxito o informe! silence_account: Acalar conta status: Estado suspend_account: Suspender conta target: Obxetivo title: Informes + unassign: Non asignar unresolved: Non resolto + updated_at: Actualizado view: Vista settings: activity_api_enabled: @@ -381,6 +417,7 @@ gl: security: Seguridade set_new_password: Establecer novo contrasinal authorize_follow: + already_following: Xa está a seguir esta conta error: Desgraciadamente, algo fallou ao buscar a conta remota follow: Seguir follow_request: 'Enviou unha petición de seguimento a:' @@ -473,6 +510,7 @@ gl: '21600': 6 horas '3600': 1 hora '43200': 12 horas + '604800': 1 semana '86400': 1 día expires_in_prompt: Nunca generate: Xerar @@ -576,6 +614,10 @@ gl: missing_resource: Non se puido atopar o URL de redirecionamento requerido para a súa conta proceed: Proceda para seguir prompt: 'Vostede vai seguir:' + remote_unfollow: + error: Fallo + title: Título + unfollowed: Deixou de seguir sessions: activity: Última actividade browser: Navegador diff --git a/config/locales/it.yml b/config/locales/it.yml index 7e5bfd2..78bf8ba 100644 --- a/config/locales/it.yml +++ b/config/locales/it.yml @@ -3,43 +3,283 @@ it: about: about_mastodon_html: Mastodon è un social network gratuito e open-source. Un'alternativa decentralizzata alle piattaforme commerciali che evita che una singola compagnia monopolizzi il tuo modo di comunicare. Scegli un server di cui ti fidi — qualunque sia la tua scelta, potrai interagire con chiunque altro. Chiunque può sviluppare un suo server Mastodon e partecipare alla vita del social network. about_this: A proposito di questo server - closed_registrations: Al momento le iscrizioni a questo server sono chiuse. + administered_by: 'Amministrato da:' + closed_registrations: Al momento le iscrizioni a questo server sono chiuse. Tuttavia! Puoi provare a cercare un istanza diversa su cui creare un account ed avere accesso alla stessa identica rete di questa. contact: Contatti + contact_missing: Non impostato + contact_unavailable: N/D description_headline: Cos'è %{domain}? domain_count_after: altri server domain_count_before: Connesso a - other_instances: Altri server + features: + humane_approach_title: Un approccio più umano + not_a_product_body: Mastodon non è una rete commerciale. Niente pubblicità, niente data mining, nessun giardino murato. Non c'è nessuna autorità centrale. + not_a_product_title: Tu sei una persona, non un prodotto + real_conversation_title: Creato per conversazioni reali + generic_description: "%{domain} è un server nella rete" + learn_more: Scopri altro + other_instances: Elenco istanze source_code: Codice sorgente - status_count_after: status + status_count_after: stati status_count_before: Che hanno pubblicato user_count_after: utenti - user_count_before: Casa di + user_count_before: Home di + what_is_mastodon: Che cos'è Mastodon? accounts: follow: Segui followers: Seguaci following: Seguiti + media: Media nothing_here: Qui non c'è nulla! people_followed_by: Persone seguite da %{name} people_who_follow: Persone che seguono %{name} posts: Posts + posts_with_replies: Toot e repliche remote_follow: Segui da remoto + reserved_username: Il nome utente è riservato + roles: + admin: Amministratore + moderator: Mod unfollow: Non seguire più + admin: + account_moderation_notes: + account: Moderatore + create: Crea + created_at: Data + created_msg: Nota di moderazione creata con successo! + delete: Elimina + destroyed_msg: Nota di moderazione distrutta con successo! + accounts: + are_you_sure: Sei sicuro? + avatar: Avatar + by_domain: Dominio + change_email: + changed_msg: Account email cambiato con successo! + current_email: Email corrente + label: Cambia email + new_email: Nuova email + submit: Cambia email + title: Cambia email per %{username} + confirm: Conferma + confirmed: Confermato + demote: Declassa + disable: Disabilita + disable_two_factor_authentication: Disabilita 2FA + disabled: Disabilitato + display_name: Nome visualizzato + domain: Dominio + edit: Modifica + email: Email + enable: Abilita + enabled: Abilitato + feed_url: URL Feed + followers: Follower + followers_url: URL follower + follows: Follows + inbox_url: URL inbox + ip: IP + location: + all: Tutto + local: Locale + remote: Remoto + title: Luogo + login_status: Stato login + media_attachments: Media allegati + memorialize: Trasforma in memoriam + moderation: + all: Tutto + silenced: Silenziati + suspended: Sospesi + title: Moderazione + moderation_notes: Note di moderazione + most_recent_activity: Attività più recenti + most_recent_ip: IP più recenti + not_subscribed: Non sottoscritto + order: + alphabetic: Alfabetico + most_recent: Più recente + title: Ordine + outbox_url: URL outbox + perform_full_suspension: Esegui sospensione completa + profile_url: URL profilo + promote: Promuovi + protocol: Protocollo + public: Pubblico + redownload: Aggiorna avatar + remove_avatar: Rimuovi avatar + reset: Reimposta + reset_password: Reimposta password + role: Permessi + roles: + admin: Amministratore + moderator: Moderatore + staff: Staff + user: Utente + search: Cerca + silence: Silenzia + statuses: Stati + subscribe: Sottoscrivi + title: Account + unconfirmed_email: Email non confermata + undo_silenced: Rimuovi silenzia + undo_suspension: Rimuovi sospensione + username: Nome utente + web: Web + action_logs: + actions: + confirm_user: "%{name} ha confermato l'indirizzo email per l'utente %{target}" + create_custom_emoji: "%{name} ha caricato un nuovo emoji %{target}" + create_domain_block: "%{name} ha bloccato il dominio %{target}" + custom_emojis: + by_domain: Dominio + copied_msg: Creata con successo una copia locale dell'emoji + copy: Copia + copy_failed_msg: Impossibile creare una copia locale di questo emoji + created_msg: Emoji creato con successo! + delete: Elimina + destroyed_msg: Emoji distrutto con successo! + disable: Disabilita + disabled_msg: Questa emoji è stata disabilitata con successo + emoji: Emoji + enable: Abilita + enabled_msg: Questa emoji è stata abilitata con successo + image_hint: PNG fino a 50KB + listed: Elencato + new: + title: Aggiungi nuovo emoji personalizzato + overwrite: Sovrascrivi + shortcode: Shortcode + title: Emoji personalizzate + unlisted: Non elencato + update_failed_msg: Impossibile aggiornare questa emojii + updated_msg: Emoji aggiornata con successo! + upload: Carica + domain_blocks: + add_new: Aggiungi nuovo + created_msg: Il blocco del dominio sta venendo processato + destroyed_msg: Il blocco del dominio è stato rimosso + domain: Dominio + new: + create: Crea blocco + severity: + noop: Nessuno + silence: Silenzia + suspend: Sospendi + title: Nuovo blocco dominio + severities: + noop: Nessuno + silence: Silenzia + suspend: Sospendi + severity: Severità + title: Blocchi dominio + email_domain_blocks: + add_new: Aggiungi nuovo + delete: Elimina + domain: Dominio + new: + create: Aggiungi dominio + instances: + domain_name: Dominio + reset: Reimposta + search: Cerca + title: Istanze conosciute + invites: + filter: + all: Tutto + available: Disponibile + expired: Scaduto + title: Filtro + title: Inviti + reports: + account: + note: note + are_you_sure: Sei sicuro? + assign_to_self: Assegna a me + comment: + none: Nessuno + delete: Elimina + id: ID + mark_as_resolved: Segna come risolto + mark_as_unresolved: Segna come non risolto + notes: + create: Aggiungi nota + create_and_resolve: Risolvi con nota + create_and_unresolve: Riapri con nota + delete: Elimina + report_contents: Contenuti + resolved: Risolto + status: Stati + target: Obbiettivo + unassign: Non assegnare + unresolved: Non risolto + updated_at: Aggiornato + view: Mostra + settings: + activity_api_enabled: + title: Pubblica statistiche aggregate circa l'attività dell'utente + peers_api_enabled: + title: Pubblica elenco di istanze scoperte + registrations: + min_invite_role: + disabled: Nessuno + show_staff_badge: + title: Mostra badge staff + site_description: + title: Descrizione istanza + site_terms: + title: Termini di servizio personalizzati + site_title: Nome istanza + timeline_preview: + title: Anteprima timeline + title: Impostazioni sito + statuses: + batch: + delete: Elimina + nsfw_off: NSFW OFF + nsfw_on: NSFW ON + execute: Esegui + failed_to_execute: Impossibile eseguire + media: + hide: Nascondi media + show: Mostra media + title: Media + no_media: Nessun media + with_media: con media + subscriptions: + callback_url: URL Callback + confirmed: Confermato + expires_in: Scade in + topic: Argomento + title: Amministrazione application_mailer: + notification_preferences: Cambia preferenze email + salutation: "%{name}," settings: 'Cambia le impostazioni per le e-mail: %{link}' view: 'Guarda:' + view_profile: Mostra profilo + view_status: Mostra stati applications: + created: Applicazione creata con successo + destroyed: Applicazione eliminata con successo invalid_url: L'URL fornito non è valido auth: + change_password: Password + confirm_email: Conferma email + delete_account: Elimina account didnt_get_confirmation: Non hai ricevuto le istruzioni di conferma? forgot_password: Hai dimenticato la tua password? login: Entra logout: Logout + migrate_account: Sposta ad un account differente + or: o register: Iscriviti + register_elsewhere: Iscriviti su un altro server resend_confirmation: Invia di nuovo le istruzioni di conferma reset_password: Resetta la password security: Credenziali set_new_password: Imposta una nuova password authorize_follow: + already_following: Stai già seguendo questo account error: Sfortunatamente c'è stato un errore nel consultare l'account remoto follow: Segui title: Segui %{acct} @@ -161,6 +401,9 @@ it: manual_instructions: 'Se non puoi scannerizzare il QR code e hai bisogno di inserirlo manualmente, questo è il codice segreto in chiaro:' setup: Configura wrong_code: Il codice inserito non è corretto! Assicurati che l'orario del server e l'orario del telefono siano corretti. + user_mailer: + welcome: + title: Benvenuto a bordo, %{name}! users: invalid_email: L'indirizzo e-mail inserito non è valido invalid_otp_token: Codice d'accesso non valido diff --git a/config/locales/ja.yml b/config/locales/ja.yml index b23c027..aaebec6 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -68,7 +68,7 @@ ja: current_email: 現在のメールアドレス label: メールアドレスを変更 new_email: 新しいメールアドレス - submit: Change Email + submit: メールアドレスの変更 title: "%{username} さんのメールアドレスを変更" confirm: 確認 confirmed: 確認済み @@ -259,6 +259,9 @@ ja: created_msg: レポートメモを書き込みました! destroyed_msg: レポートメモを削除しました! reports: + account: + note: メモ + report: レポート action_taken_by: レポート処理者 are_you_sure: 本当に実行しますか? assign_to_self: 担当になる diff --git a/config/locales/ko.yml b/config/locales/ko.yml index 3470085..0f3c648 100644 --- a/config/locales/ko.yml +++ b/config/locales/ko.yml @@ -4,6 +4,7 @@ ko: about_hashtag_html: "#%{hashtag} 라는 해시태그가 붙은 공개 툿 입니다. 같은 연합에 속한 임의의 인스턴스에 계정을 생성하면 당신도 대화에 참여할 수 있습니다." about_mastodon_html: Mastodon은 오픈 소스 기반의 소셜 네트워크 서비스 입니다. 상용 플랫폼의 대체로서 분산형 구조를 채택해, 여러분의 대화가 한 회사에 독점되는 것을 방지합니다. 신뢰할 수 있는 인스턴스를 선택하세요 — 어떤 인스턴스를 고르더라도, 누구와도 대화할 수 있습니다. 누구나 자신만의 Mastodon 인스턴스를 만들 수 있으며, 아주 매끄럽게 소셜 네트워크에 참가할 수 있습니다. about_this: 이 인스턴스에 대해서 + administered_by: '관리자:' closed_registrations: 현재 이 인스턴스에서는 신규 등록을 받고 있지 않습니다. contact: 연락처 contact_missing: 미설정 @@ -60,7 +61,15 @@ ko: destroyed_msg: 모더레이션 기록이 성공적으로 삭제되었습니다! accounts: are_you_sure: 정말로 실행하시겠습니까? + avatar: 아바타 by_domain: 도메인 + change_email: + changed_msg: 이메일이 성공적으로 바뀌었습니다! + current_email: 현재 이메일 주소 + label: 이메일 주소 변경 + new_email: 새 이메일 주소 + submit: 이메일 주소 변경 + title: "%{username}의 이메일 주소 변경" confirm: 확인 confirmed: 확인됨 demote: 모더레이터 강등 @@ -108,6 +117,7 @@ ko: public: 전체 공개 push_subscription_expires: PuSH 구독 기간 만료 redownload: 아바타 업데이트 + remove_avatar: 아바타 지우기 reset: 초기화 reset_password: 비밀번호 초기화 resubscribe: 다시 구독 @@ -128,6 +138,7 @@ ko: statuses: 툿 수 subscribe: 구독하기 title: 계정 + unconfirmed_email: 미확인 된 이메일 주소 undo_silenced: 침묵 해제 undo_suspension: 정지 해제 unsubscribe: 구독 해제 @@ -135,6 +146,8 @@ ko: web: 웹 action_logs: actions: + assigned_to_self_report: "%{name}이 리포트 %{target}을 자신에게 할당했습니다" + change_email_user: "%{name}이 %{target}의 이메일 주소를 변경했습니다" confirm_user: "%{name}이 %{target}의 이메일 주소를 컨펌했습니다" create_custom_emoji: "%{name}이 새로운 에모지 %{target}를 추가했습니다" create_domain_block: "%{name}이 도메인 %{target}를 차단했습니다" @@ -150,10 +163,13 @@ ko: enable_user: "%{name}이 %{target}의 로그인을 활성화 했습니다" memorialize_account: "%{name}이 %{target}의 계정을 메모리엄으로 전환했습니다" promote_user: "%{name}이 %{target}를 승급시켰습니다" + remove_avatar_user: "%{name}이 %{target}의 아바타를 지웠습니다" + reopen_report: "%{name}이 리포트 %{target}을 다시 열었습니다" reset_password_user: "%{name}이 %{target}의 암호를 초기화했습니다" resolve_report: "%{name}이 %{target} 신고를 처리됨으로 변경하였습니다" silence_account: "%{name}이 %{target}의 계정을 뮤트시켰습니다" suspend_account: "%{name}이 %{target}의 계정을 정지시켰습니다" + unassigned_report: "%{name}이 리포트 %{target}을 할당 해제했습니다" unsilence_account: "%{name}이 %{target}에 대한 뮤트를 해제했습니다" unsuspend_account: "%{name}이 %{target}에 대한 정지를 해제했습니다" update_custom_emoji: "%{name}이 에모지 %{target}를 업데이트 했습니다" @@ -241,28 +257,48 @@ ko: expired: 만료됨 title: 필터 title: 초대 + report_notes: + created_msg: 리포트 노트가 성공적으로 작성되었습니다! + destroyed_msg: 리포트 노트가 성공적으로 삭제되었습니다! reports: + account: + note: 노트 + report: 리포트 action_taken_by: 신고 처리자 are_you_sure: 정말로 실행하시겠습니까? + assign_to_self: 나에게 할당 됨 + assigned: 할당 된 모더레이터 comment: none: 없음 + created_at: 리포트 시각 delete: 삭제 id: ID mark_as_resolved: 해결 완료 처리 + mark_as_unresolved: 미해결로 표시 + notes: + create: 노트 추가 + create_and_resolve: 노트를 작성하고 해결됨으로 표시 + create_and_unresolve: 노트 작성과 함께 미해결로 표시 + delete: 삭제 + placeholder: 이 리포트에 대한 조치, 다른 업데이트 사항에 대해 설명합니다… nsfw: 'false': NSFW 꺼짐 'true': NSFW 켜짐 + reopen: 리포트 다시 열기 report: '신고 #%{id}' report_contents: 내용 reported_account: 신고 대상 계정 reported_by: 신고자 resolved: 해결됨 + resolved_msg: 리포트가 성공적으로 해결되었습니다! silence_account: 계정을 침묵 처리 status: 상태 suspend_account: 계정을 정지 target: 대상 title: 신고 + unassign: 할당 해제 unresolved: 미해결 + updated_at: 업데이트 시각 view: 표시 settings: activity_api_enabled: @@ -383,6 +419,7 @@ ko: security: 보안 set_new_password: 새 비밀번호 authorize_follow: + already_following: 이미 이 계정을 팔로우 하고 있습니다 error: 리모트 계정을 확인하는 도중 오류가 발생했습니다 follow: 팔로우 follow_request: '당신은 다음 계정에 팔로우 신청을 했습니다:' @@ -475,6 +512,7 @@ ko: '21600': 6 시간 '3600': 1 시간 '43200': 12 시간 + '604800': 1주일 '86400': 하루 expires_in_prompt: 영원히 generate: 생성 @@ -547,7 +585,7 @@ ko: quadrillion: Q thousand: K trillion: T - unit: '' + unit: "." pagination: newer: 새로운 툿 next: 다음 @@ -578,6 +616,10 @@ ko: missing_resource: 리디렉션 대상을 찾을 수 없습니다 proceed: 팔로우 하기 prompt: '팔로우 하려 하고 있습니다:' + remote_unfollow: + error: 에러 + title: 타이틀 + unfollowed: 언팔로우됨 sessions: activity: 마지막 활동 browser: 브라우저 diff --git a/config/locales/nl.yml b/config/locales/nl.yml index 2342ef6..64bc718 100644 --- a/config/locales/nl.yml +++ b/config/locales/nl.yml @@ -4,6 +4,7 @@ nl: about_hashtag_html: Dit zijn openbare toots die getagged zijn met #%{hashtag}. Je kunt er op reageren of iets anders mee doen als je op Mastodon (of ergens anders in de fediverse) een account hebt. about_mastodon_html: Mastodon is een sociaal netwerk dat gebruikt maakt van open webprotocollen en vrije software. Het is net zoals e-mail gedecentraliseerd. about_this: Over deze server + administered_by: 'Beheerd door:' closed_registrations: Registreren op deze server is momenteel uitgeschakeld. contact: Contact contact_missing: Niet ingesteld @@ -60,7 +61,15 @@ nl: destroyed_msg: Verwijderen van opmerking voor moderatoren geslaagd! accounts: are_you_sure: Weet je het zeker? + avatar: Avatar by_domain: Domein + change_email: + changed_msg: E-mailadres van account succesvol veranderd! + current_email: Huidig e-mailadres + label: E-mailadres veranderen + new_email: Nieuw e-mailadres + submit: E-mailadres veranderen + title: E-mailadres veranderen voor %{username} confirm: Bevestigen confirmed: Bevestigd demote: Degraderen @@ -108,6 +117,7 @@ nl: public: Openbaar push_subscription_expires: PuSH-abonnement verloopt op redownload: Avatar vernieuwen + remove_avatar: Avatar verwijderen reset: Opnieuw reset_password: Wachtwoord opnieuw instellen resubscribe: Opnieuw abonneren @@ -128,6 +138,7 @@ nl: statuses: Toots subscribe: Abonneren title: Accounts + unconfirmed_email: Onbevestigd e-mailadres undo_silenced: Niet meer negeren undo_suspension: Niet meer opschorten unsubscribe: Opzeggen @@ -135,6 +146,8 @@ nl: web: Webapp action_logs: actions: + assigned_to_self_report: "%{name} heeft gerapporteerde toot %{target} aan zichzelf toegewezen" + change_email_user: "%{name} veranderde het e-mailadres van gebruiker %{target}" confirm_user: E-mailadres van gebruiker %{target} is door %{name} bevestigd create_custom_emoji: Nieuwe emoji %{target} is door %{name} geüpload create_domain_block: Domein %{target} is door %{name} geblokkeerd @@ -150,10 +163,13 @@ nl: enable_user: Inloggen voor %{target} is door %{name} ingeschakeld memorialize_account: Account %{target} is door %{name} in een in-memoriampagina veranderd promote_user: Gebruiker %{target} is door %{name} gepromoveerd + remove_avatar_user: "%{name} verwijderde de avatar van %{target}" + reopen_report: "%{name} heeft gerapporteerde toot %{target} heropend" reset_password_user: Wachtwoord van gebruiker %{target} is door %{name} opnieuw ingesteld - resolve_report: Gerapporteerde toots van %{target} zijn door %{name} verworpen + resolve_report: "%{name} heeft gerapporteerde toot %{target} opgelost" silence_account: Account %{target} is door %{name} genegeerd suspend_account: Account %{target} is door %{name} opgeschort + unassigned_report: "%{name} heeft het toewijzen van gerapporteerde toot %{target} ongedaan gemaakt" unsilence_account: Negeren van account %{target} is door %{name} opgeheven unsuspend_account: Opschorten van account %{target} is door %{name} opgeheven update_custom_emoji: Emoji %{target} is door %{name} bijgewerkt @@ -239,28 +255,48 @@ nl: expired: Verlopen title: Filter title: Uitnodigingen + report_notes: + created_msg: Opmerking bij gerapporteerde toot succesvol aangemaakt! + destroyed_msg: Opmerking bij gerapporteerde toot succesvol verwijderd! reports: + account: + note: opmerking + report: gerapporteerde toot action_taken_by: Actie uitgevoerd door are_you_sure: Weet je het zeker? + assign_to_self: Aan mij toewijzen + assigned: Toegewezen moderator comment: none: Geen + created_at: Gerapporteerd op delete: Verwijderen id: ID mark_as_resolved: Markeer als opgelost + mark_as_unresolved: Markeer als onopgelost + notes: + create: Opmerking toevoegen + create_and_resolve: Oplossen met opmerking + create_and_unresolve: Heropenen met opmerking + delete: Verwijderen + placeholder: Beschrijf welke acties zijn ondernomen of andere opmerkingen over deze gerapporteerde toot… nsfw: 'false': Media tonen 'true': Media verbergen + reopen: Gerapporteerde toot heropenen report: 'Gerapporteerde toot #%{id}' report_contents: Inhoud reported_account: Gerapporteerde account reported_by: Gerapporteerd door resolved: Opgelost + resolved_msg: Gerapporteerde toot succesvol opgelost! silence_account: Account negeren status: Toot suspend_account: Account opschorten target: Gerapporteerde account title: Gerapporteerde toots + unassign: Niet meer toewijzen unresolved: Onopgelost + updated_at: Bijgewerkt view: Weergeven settings: activity_api_enabled: @@ -381,6 +417,7 @@ nl: security: Beveiliging set_new_password: Nieuw wachtwoord instellen authorize_follow: + already_following: Je volgt dit account al error: Helaas, er is een fout opgetreden bij het opzoeken van de externe account follow: Volgen follow_request: 'Jij hebt een volgverzoek ingediend bij:' @@ -473,6 +510,7 @@ nl: '21600': 6 uur '3600': 1 uur '43200': 12 uur + '604800': 1 week '86400': 1 dag expires_in_prompt: Nooit generate: Genereren @@ -576,6 +614,10 @@ nl: missing_resource: Kon vereiste doorverwijzings-URL voor jouw account niet vinden proceed: Ga door om te volgen prompt: 'Jij gaat volgen:' + remote_unfollow: + error: Fout + title: Titel + unfollowed: Ontvolgd sessions: activity: Laatst actief browser: Webbrowser @@ -664,6 +706,83 @@ nl: reblogged: boostte sensitive_content: Gevoelige inhoud terms: + body_html: | +

    Privacy Policy

    +

    What information do we collect?

    + +
      +
    • Basic account information: If you register on this server, you may be asked to enter a username, an e-mail address and a password. You may also enter additional profile information such as a display name and biography, and upload a profile picture and header image. The username, display name, biography, profile picture and header image are always listed publicly.
    • +
    • Posts, following and other public information: The list of people you follow is listed publicly, the same is true for your followers. When you submit a message, the date and time is stored as well as the application you submitted the message from. Messages may contain media attachments, such as pictures and videos. Public and unlisted posts are available publicly. When you feature a post on your profile, that is also publicly available information. Your posts are delivered to your followers, in some cases it means they are delivered to different servers and copies are stored there. When you delete posts, this is likewise delivered to your followers. The action of reblogging or favouriting another post is always public.
    • +
    • Direct and followers-only posts: All posts are stored and processed on the server. Followers-only posts are delivered to your followers and users who are mentioned in them, and direct posts are delivered only to users mentioned in them. In some cases it means they are delivered to different servers and copies are stored there. We make a good faith effort to limit the access to those posts only to authorized persons, but other servers may fail to do so. Therefore it's important to review servers your followers belong to. You may toggle an option to approve and reject new followers manually in the settings. Please keep in mind that the operators of the server and any receiving server may view such messages, and that recipients may screenshot, copy or otherwise re-share them. Do not share any dangerous information over Mastodon.
    • +
    • IPs and other metadata: When you log in, we record the IP address you log in from, as well as the name of your browser application. All the logged in sessions are available for your review and revocation in the settings. The latest IP address used is stored for up to 12 months. We also may retain server logs which include the IP address of every request to our server.
    • +
    + +
    + +

    What do we use your information for?

    + +

    Any of the information we collect from you may be used in the following ways:

    + +
      +
    • To provide the core functionality of Mastodon. You can only interact with other people's content and post your own content when you are logged in. For example, you may follow other people to view their combined posts in your own personalized home timeline.
    • +
    • To aid moderation of the community, for example comparing your IP address with other known ones to determine ban evasion or other violations.
    • +
    • The email address you provide may be used to send you information, notifications about other people interacting with your content or sending you messages, and to respond to inquiries, and/or other requests or questions.
    • +
    + +
    + +

    How do we protect your information?

    + +

    We implement a variety of security measures to maintain the safety of your personal information when you enter, submit, or access your personal information. Among other things, your browser session, as well as the traffic between your applications and the API, are secured with SSL, and your password is hashed using a strong one-way algorithm. You may enable two-factor authentication to further secure access to your account.

    + +
    + +

    What is our data retention policy?

    + +

    We will make a good faith effort to:

    + +
      +
    • Retain server logs containing the IP address of all requests to this server, in so far as such logs are kept, no more than 90 days.
    • +
    • Retain the IP addresses associated with registered users no more than 12 months.
    • +
    + +

    You can request and download an archive of your content, including your posts, media attachments, profile picture, and header image.

    + +

    You may irreversibly delete your account at any time.

    + +
    + +

    Do we use cookies?

    + +

    Yes. Cookies are small files that a site or its service provider transfers to your computer's hard drive through your Web browser (if you allow). These cookies enable the site to recognize your browser and, if you have a registered account, associate it with your registered account.

    + +

    We use cookies to understand and save your preferences for future visits.

    + +
    + +

    Do we disclose any information to outside parties?

    + +

    We do not sell, trade, or otherwise transfer to outside parties your personally identifiable information. This does not include trusted third parties who assist us in operating our site, conducting our business, or servicing you, so long as those parties agree to keep this information confidential. We may also release your information when we believe release is appropriate to comply with the law, enforce our site policies, or protect ours or others rights, property, or safety.

    + +

    Your public content may be downloaded by other servers in the network. Your public and followers-only posts are delivered to the servers where your followers reside, and direct messages are delivered to the servers of the recipients, in so far as those followers or recipients reside on a different server than this.

    + +

    When you authorize an application to use your account, depending on the scope of permissions you approve, it may access your public profile information, your following list, your followers, your lists, all your posts, and your favourites. Applications can never access your e-mail address or password.

    + +
    + +

    Children's Online Privacy Protection Act Compliance

    + +

    Our site, products and services are all directed to people who are at least 13 years old. If this server is in the USA, and you are under the age of 13, per the requirements of COPPA (Children's Online Privacy Protection Act) do not use this site.

    + +
    + +

    Changes to our Privacy Policy

    + +

    If we decide to change our privacy policy, we will post those changes on this page.

    + +

    This document is CC-BY-SA. It was last updated March 7, 2018.

    + +

    Originally adapted from the Discourse privacy policy.

    title: "%{instance} Terms of Service and Privacy Policy" themes: default: Mastodon diff --git a/config/locales/pt-BR.yml b/config/locales/pt-BR.yml index d3c1d53..aeafe77 100644 --- a/config/locales/pt-BR.yml +++ b/config/locales/pt-BR.yml @@ -4,6 +4,7 @@ pt-BR: about_hashtag_html: Estes são toots públicos com a hashtag #%{hashtag}. Você pode interagir com eles se tiver uma conta em qualquer lugar no fediverso. about_mastodon_html: Mastodon é uma rede social baseada em protocolos abertos e software gratuito e de código aberto. É descentralizada como e-mail. about_this: Sobre + administered_by: 'Administrado por:' closed_registrations: Os cadastros estão atualmente fechados nesta instância. No entanto, você pode procurar uma instância diferente na qual possa criar uma conta e acessar a mesma rede por lá. contact: Contato contact_missing: Não definido @@ -60,7 +61,15 @@ pt-BR: destroyed_msg: Nota de moderação excluída com sucesso! accounts: are_you_sure: Você tem certeza? + avatar: Avatar by_domain: Domínio + change_email: + changed_msg: E-mail da conta modificado com sucesso! + current_email: E-mail atual + label: Mudar e-mail + new_email: Novo e-mail + submit: Mudar e-mail + title: Mudar e-mail para %{username} confirm: Confirmar confirmed: Confirmado demote: Rebaixar @@ -108,6 +117,7 @@ pt-BR: public: Público push_subscription_expires: Inscrição PuSH expira redownload: Atualizar avatar + remove_avatar: Remover avatar reset: Anular reset_password: Modificar senha resubscribe: Reinscrever-se @@ -128,6 +138,7 @@ pt-BR: statuses: Postagens subscribe: Inscrever-se title: Contas + unconfirmed_email: E-mail não confirmado undo_silenced: Retirar silenciamento undo_suspension: Retirar suspensão unsubscribe: Desinscrever-se @@ -135,6 +146,8 @@ pt-BR: web: Web action_logs: actions: + assigned_to_self_report: "%{name} designou a denúncia %{target} para si" + change_email_user: "%{name} mudou o endereço de e-mail do usuário %{target}" confirm_user: "%{name} confirmou o endereço de e-mail do usuário %{target}" create_custom_emoji: "%{name} enviou o emoji novo %{target}" create_domain_block: "%{name} bloqueou o domínio %{target}" @@ -150,10 +163,13 @@ pt-BR: enable_user: "%{name} habilitou o acesso para o usuário %{target}" memorialize_account: "%{name} transformou a conta de %{target} em um memorial" promote_user: "%{name} promoveu o usuário %{target}" + remove_avatar_user: "%{name} removeu o avatar de %{target}" + reopen_report: "%{name} reabriu a denúncia %{target}" reset_password_user: "%{name} redefiniu a senha do usuário %{target}" - resolve_report: "%{name} dispensou a denúncia %{target}" + resolve_report: "%{name} resolveu a denúncia %{target}" silence_account: "%{name} silenciou a conta de %{target}" suspend_account: "%{name} suspendeu a conta de %{target}" + unassigned_report: "%{name} desatribuiu a denúncia %{target}" unsilence_account: "%{name} desativou o silêncio de %{target}" unsuspend_account: "%{name} desativou a suspensão de %{target}" update_custom_emoji: "%{name} atualizou o emoji %{target}" @@ -239,28 +255,48 @@ pt-BR: expired: Expirados title: Filtro title: Convites + report_notes: + created_msg: Nota de denúncia criada com sucesso! + destroyed_msg: Nota de denúncia excluída com sucesso! reports: + account: + note: nota + report: denúncia action_taken_by: Ação realizada por are_you_sure: Você tem certeza? + assign_to_self: Designar para mim + assigned: Moderador designado comment: none: Nenhum + created_at: Denunciado delete: Excluir id: ID mark_as_resolved: Marcar como resolvido + mark_as_unresolved: Marcar como não resolvido + notes: + create: Adicionar nota + create_and_resolve: Resolver com nota + create_and_unresolve: Reabrir com nota + delete: Excluir + placeholder: Descreva que ações foram tomadas, ou quaisquer atualizações sobre esta denúncia… nsfw: 'false': Mostrar mídias anexadas 'true': Esconder mídias anexadas + reopen: Reabrir denúncia report: 'Denúncia #%{id}' report_contents: Conteúdos reported_account: Conta denunciada reported_by: Denunciada por resolved: Resolvido + resolved_msg: Denúncia resolvida com sucesso! silence_account: Silenciar conta status: Status suspend_account: Suspender conta target: Alvo title: Denúncias + unassign: Desatribuir unresolved: Não resolvido + updated_at: Atualizado view: Visualizar settings: activity_api_enabled: @@ -381,6 +417,7 @@ pt-BR: security: Segurança set_new_password: Definir uma nova senha authorize_follow: + already_following: Você já está seguindo esta conta error: Infelizmente, ocorreu um erro ao buscar a conta remota follow: Seguir follow_request: 'Você mandou uma solicitação de seguidor para:' @@ -473,6 +510,7 @@ pt-BR: '21600': 6 horas '3600': 1 hora '43200': 12 horas + '604800': 1 semana '86400': 1 dia expires_in_prompt: Nunca generate: Gerar @@ -576,6 +614,9 @@ pt-BR: missing_resource: Não foi possível encontrar a URL de direcionamento para a sua conta proceed: Prosseguir para seguir prompt: 'Você irá seguir:' + remote_unfollow: + error: Erro + title: Título sessions: activity: Última atividade browser: Navegador diff --git a/config/locales/simple_form.ar.yml b/config/locales/simple_form.ar.yml index 414f0c3..28cfa8a 100644 --- a/config/locales/simple_form.ar.yml +++ b/config/locales/simple_form.ar.yml @@ -5,8 +5,15 @@ ar: defaults: avatar: ملف PNG أو GIF أو JPG. حجمه على أقصى تصدير 2MB. سيتم تصغيره إلى 400x400px digest: تُرسَل إليك بعد مُضيّ مدة مِن خمول نشاطك و فقط إذا ما تلقيت رسائل شخصية مباشِرة أثناء فترة غيابك مِن الشبكة + display_name: + one: 1 حرف باقي + other: %{count} حروف متبقية + fields: يُمكنك عرض 4 عناصر على شكل جدول في ملفك الشخصي header: ملف PNG أو GIF أو JPG. حجمه على أقصى تصدير 2MB. سيتم تصغيره إلى 700x335px locked: يتطلب منك الموافقة يدويا على طلبات المتابعة + note: + one: 1 حرف متبقي + other: %{count} حروف متبقية setting_noindex: ذلك يؤثر على حالة ملفك الشخصي و صفحاتك setting_theme: ذلك يؤثر على الشكل الذي سيبدو عليه ماستدون عندما تقوم بالدخول مِن أي جهاز. imports: @@ -16,6 +23,10 @@ ar: user: filtered_languages: سوف يتم تصفية و إخفاء اللغات المختارة من خيوطك العمومية labels: + account: + fields: + name: التسمية + value: المحتوى defaults: avatar: الصورة الرمزية confirm_new_password: تأكيد كلمة السر الجديدة @@ -25,6 +36,7 @@ ar: display_name: الإسم المعروض email: عنوان البريد الإلكتروني expires_in: تنتهي مدة صلاحيته بعد + fields: واصفات بيانات الملف الشخصي filtered_languages: اللغات التي تم تصفيتها header: الرأسية locale: اللغة diff --git a/config/locales/simple_form.ca.yml b/config/locales/simple_form.ca.yml index 300da45..1b04da9 100644 --- a/config/locales/simple_form.ca.yml +++ b/config/locales/simple_form.ca.yml @@ -8,6 +8,7 @@ ca: display_name: one: 1 càracter restant other: %{count} càracters restans + fields: Pots tenir fins a 4 elements que es mostren com a taula al teu perfil header: PNG, GIF o JPG. Màxim 2MB. S'escalarà a 700x335px locked: Requereix que aprovis manualment els seguidors note: @@ -22,6 +23,10 @@ ca: user: filtered_languages: Les llengües seleccionades s'eliminaran de les línies de temps públiques labels: + account: + fields: + name: Etiqueta + value: Contingut defaults: avatar: Avatar confirm_new_password: Confirma la contrasenya nova @@ -31,6 +36,7 @@ ca: display_name: Nom visible email: Adreça de correu electrònic expires_in: Expira després + fields: Metadades del perfil filtered_languages: Llengües filtrades header: Capçalera locale: Llengua diff --git a/config/locales/simple_form.eu.yml b/config/locales/simple_form.eu.yml new file mode 100644 index 0000000..9664357 --- /dev/null +++ b/config/locales/simple_form.eu.yml @@ -0,0 +1,6 @@ +--- +eu: + simple_form: + hints: + defaults: + avatar: PNG, GIF edo JPG. Gehienez 2MB. 400x400px neurrira eskalatuko da diff --git a/config/locales/simple_form.fr.yml b/config/locales/simple_form.fr.yml index 7167419..88e1b88 100644 --- a/config/locales/simple_form.fr.yml +++ b/config/locales/simple_form.fr.yml @@ -8,6 +8,7 @@ fr: display_name: one: 1 caractère restant other: %{count} caractères restants + fields: Vous pouvez avoir jusqu'à 4 éléments affichés en tant que tableau sur votre profil header: Au format PNG, GIF ou JPG. 2 Mo maximum. Sera réduit à 700x335px locked: Vous devrez approuver chaque abonné⋅e et vos statuts ne s’afficheront qu’à vos abonné⋅es note: @@ -22,6 +23,10 @@ fr: user: filtered_languages: Les langues sélectionnées seront filtrées hors de vos fils publics pour vous labels: + account: + fields: + name: Étiquette + value: Contenu defaults: avatar: Image de profil confirm_new_password: Confirmation du nouveau mot de passe @@ -31,6 +36,7 @@ fr: display_name: Nom public email: Adresse courriel expires_in: Expire après + fields: Métadonnées du profil filtered_languages: Langues filtrées header: Image d’en-tête locale: Langue diff --git a/config/locales/simple_form.gl.yml b/config/locales/simple_form.gl.yml index 4dcdd04..a490388 100644 --- a/config/locales/simple_form.gl.yml +++ b/config/locales/simple_form.gl.yml @@ -8,6 +8,7 @@ gl: display_name: one: 1 caracter restante other: %{count} caracteres restantes + fields: Pode ter ate 4 elementos no seu perfil mostrados como unha táboa header: PNG, GIF ou JPG. Como moito 2MB. Será reducida a 700x335px locked: Require que vostede aprove as seguidoras de xeito manual note: @@ -22,6 +23,10 @@ gl: user: filtered_languages: Os idiomas marcados filtraranse das liñas temporais públicas para vostede labels: + account: + fields: + name: Etiqueta + value: Contido defaults: avatar: Avatar confirm_new_password: Confirme o novo contrasinal @@ -31,6 +36,7 @@ gl: display_name: Nome mostrado email: enderezo correo electrónico expires_in: Caducidade despois de + fields: Metadatos do perfil filtered_languages: Idiomas filtrados header: Cabezallo locale: Idioma diff --git a/config/locales/simple_form.it.yml b/config/locales/simple_form.it.yml index b2fcef1..5d9ae18 100644 --- a/config/locales/simple_form.it.yml +++ b/config/locales/simple_form.it.yml @@ -3,45 +3,77 @@ it: simple_form: hints: defaults: - avatar: PNG, GIF o JPG. Al massimo 2MB. Sarà ridotto a 400x400px - display_name: Al massimo 30 characters - header: PNG, GIF or JPG. Al massimo 2MB. Sarà ridotto a 700x335px - locked: Richiede la tua approvazione per i nuovi seguaci e rende i nuovi post automaticamente visibili solo ai seguaci - note: Al massimo 160 caratteri + avatar: PNG, GIF o JPG. Al massimo 2MB. Verranno scalate a 400x400px + digest: Inviata solo dopo un lungo periodo di intattività e solo se hai ricevuto qualsiasi messaggio personale in tua assenza + display_name: + one: 1 carattere rimanente + other: %{count} caratteri rimanenti + fields: Puoi avere fino a 4 voci visualizzate come una tabella sul tuo profilo + header: PNG, GIF o JPG. Al massimo 2MB. Verranno scalate a 700x335px + locked: Richiede che approvi i follower manualmente + note: + one: 1 carattere rimanente + other: %{count} caratteri rimanenti + setting_noindex: Coinvolge il tuo profilo pubblico e le pagine di stato + setting_theme: Coinvolge il modo in cui Mastodon verrà visualizzato quando sarai collegato da qualsiasi dispositivo. imports: - data: CSV esportato da un altro server Mastodon + data: File CSV esportato da un altra istanza di Mastodon + sessions: + otp: Inserisci il codice due-fattori dal tuo telefono o usa uno dei codici di recupero. + user: + filtered_languages: Le lingue selezionate verranno filtrate dalla timeline pubblica per te labels: + account: + fields: + name: Etichetta + value: Contenuto defaults: avatar: Avatar - confirm_new_password: Conferma la nuova password - confirm_password: Conferma la password + confirm_new_password: Conferma nuova password + confirm_password: Conferma password current_password: Password corrente data: Data - display_name: Nome pubblico - email: Indirizzo e-mail + display_name: Nome visualizzato + email: Indirizzo email + expires_in: Scade dopo + fields: Metadata profilo + filtered_languages: Lingue filtrate header: Header locale: Lingua - locked: Rendi l'account privato + locked: Blocca account + max_uses: Numero massimo di utilizzi new_password: Nuova password - note: Biografia - otp_attempt: Codice d'accesso + note: Bio + otp_attempt: Codice due-fattori password: Password - setting_boost_modal: Mostra finestra di conferma prima di condividere - setting_default_privacy: Privacy del post - type: Importa - username: Username + setting_auto_play_gif: Play automatico GIF animate + setting_boost_modal: Mostra dialogo di conferma prima del boost + setting_default_privacy: Privacy post + setting_default_sensitive: Segna sempre i media come sensibili + setting_delete_modal: Mostra dialogo di conferma prima di eliminare un toot + setting_display_sensitive_media: Mostra sempre i media segnati come sensibili + setting_noindex: Non indicizzare dai motori di ricerca + setting_reduce_motion: Riduci movimento nelle animazioni + setting_system_font_ui: Usa il carattere di default del sistema + setting_theme: Tema sito + setting_unfollow_modal: Mostra dialogo di conferma prima di smettere di seguire qualcuno + severity: Severità + type: Tipo importazione + username: Nome utente + username_or_email: Nome utente o email interactions: - must_be_follower: Blocca notifiche da chi non ti segue - must_be_following: Blocca notifiche da chi non segui + must_be_follower: Blocca notifiche dai non follower + must_be_following: Blocca notifiche dalle persone che non segui + must_be_following_dm: Blocca i messaggi diretti dalle persone che non segui notification_emails: - digest: Invia riassunto via e-mail - favourite: Invia e-mail quando qualcuno apprezza i tuoi status - follow: Invia e-mail quando qualcuno ti segue - follow_request: Invia e-mail quando qualcuno ti richiede di seguirti - mention: Invia e-mail quando qualcuno ti menziona - reblog: Invia e-mail quando qualcuno condivide i tuoi status + digest: Invia email riassuntive + favourite: Invia email quando segna come preferito al tuo stato + follow: Invia email quando qualcuno ti segue + follow_request: Invia email quando qualcuno richiede di seguirti + mention: Invia email quando qualcuno ti menziona + reblog: Invia email quando qualcuno da un boost al tuo stato 'no': 'No' required: mark: "*" text: richiesto - 'yes': Sì + 'yes': Si diff --git a/config/locales/simple_form.ja.yml b/config/locales/simple_form.ja.yml index d7cccfc..6b2d934 100644 --- a/config/locales/simple_form.ja.yml +++ b/config/locales/simple_form.ja.yml @@ -6,6 +6,7 @@ ja: avatar: 2MBまでのPNGやGIF、JPGが利用可能です。400x400pxまで縮小されます digest: 長期間使用していない場合と不在時に返信を受けた場合のみ送信されます display_name: あと%{count}文字入力できます。 + fields: プロフィールに表として4つまでの項目を表示することができます header: 2MBまでのPNGやGIF、JPGが利用可能です。 700x335pxまで縮小されます locked: フォロワーを手動で承認する必要があります note: あと%{count}文字入力できます。 @@ -18,6 +19,10 @@ ja: user: filtered_languages: 選択した言語があなたの公開タイムラインから取り除かれます labels: + account: + fields: + name: ラベル + value: 内容 defaults: avatar: アイコン confirm_new_password: 新しいパスワード(確認用) @@ -27,6 +32,7 @@ ja: display_name: 表示名 email: メールアドレス expires_in: 有効期限 + fields: プロフィール補足情報 filtered_languages: 除外する言語 header: ヘッダー locale: 言語 @@ -46,7 +52,7 @@ ja: setting_reduce_motion: アニメーションの動きを減らす setting_system_font_ui: システムのデフォルトフォントを使う setting_theme: サイトテーマ - setting_unfollow_modal: フォロー解除する前に確認ダイアログを表示する + setting_unfollow_modal: フォローを解除する前に確認ダイアログを表示する severity: 重大性 type: インポートする項目 username: ユーザー名 diff --git a/config/locales/simple_form.ko.yml b/config/locales/simple_form.ko.yml index 85eccf0..ccb05fd 100644 --- a/config/locales/simple_form.ko.yml +++ b/config/locales/simple_form.ko.yml @@ -8,6 +8,7 @@ ko: display_name: one: 1 글자 남음 other: %{count} 글자 남음 + fields: 당신의 프로파일에 최대 4개까지 표 형식으로 나타낼 수 있습니다 header: PNG, GIF 혹은 JPG. 최대 2MB. 700x335px로 다운스케일 됨 locked: 수동으로 팔로워를 승인하고, 기본 툿 프라이버시 설정을 팔로워 전용으로 변경 note: @@ -22,6 +23,10 @@ ko: user: filtered_languages: 선택된 언어가 공개 타임라인에서 제외 될 것입니다 labels: + account: + fields: + name: 라벨 + value: 내용 defaults: avatar: 아바타 confirm_new_password: 새로운 비밀번호 다시 입력 @@ -31,6 +36,7 @@ ko: display_name: 표시되는 이름 email: 이메일 주소 expires_in: 만료시각 + fields: 프로필 메타데이터 filtered_languages: 숨긴 언어들 header: 헤더 locale: 언어 diff --git a/config/locales/simple_form.nl.yml b/config/locales/simple_form.nl.yml index 9876230..ec42adf 100644 --- a/config/locales/simple_form.nl.yml +++ b/config/locales/simple_form.nl.yml @@ -8,6 +8,7 @@ nl: display_name: one: 1 teken over other: %{count} tekens over + fields: Je kan maximaal 4 items als een tabel op je profiel weergeven header: PNG, GIF of JPG. Maximaal 2MB. Wordt teruggeschaald naar 700x335px locked: Vereist dat je handmatig volgers moet accepteren note: @@ -22,6 +23,10 @@ nl: user: filtered_languages: De geselecteerde talen worden uit de lokale en globale tijdlijn verwijderd labels: + account: + fields: + name: Label + value: Inhoud defaults: avatar: Avatar confirm_new_password: Nieuw wachtwoord bevestigen @@ -31,6 +36,7 @@ nl: display_name: Weergavenaam email: E-mailadres expires_in: Vervalt na + fields: Metadata profiel filtered_languages: Talen filteren header: Omslagfoto locale: Taal @@ -63,7 +69,7 @@ nl: digest: Periodiek e-mails met een samenvatting versturen favourite: Een e-mail versturen wanneer iemand jouw toot als favoriet markeert follow: Een e-mail versturen wanneer iemand jou volgt - follow_request: Een e-mail versturen wanneer iemand jou wilt volgen + follow_request: Een e-mail versturen wanneer iemand jou wil volgen mention: Een e-mail versturen wanneer iemand jou vermeld reblog: Een e-mail versturen wanneer iemand jouw toot heeft geboost 'no': Nee diff --git a/config/locales/simple_form.sk.yml b/config/locales/simple_form.sk.yml index e504c97..f928fd6 100644 --- a/config/locales/simple_form.sk.yml +++ b/config/locales/simple_form.sk.yml @@ -8,6 +8,7 @@ sk: display_name: one: Ostáva ti 1 znak other: Ostáva ti %{count} znakov + fields: Môžeš mať 4 položky na svojom profile zobrazené vo forme tabuľky header: PNG, GIF alebo JPG. Maximálne 2MB. Bude zmenšený na 700x335px locked: Musíte manuálne schváliť sledujúcich note: @@ -22,6 +23,10 @@ sk: user: filtered_languages: Zaškrtnuté jazyky budú pre teba vynechané nebudú z verejnej časovej osi labels: + account: + fields: + name: Označenie + value: Obsah defaults: avatar: Avatar confirm_new_password: Opäť vaše nové heslo pre potvrdenie @@ -31,6 +36,7 @@ sk: display_name: Meno email: Emailová adresa expires_in: Expirovať po + fields: Metadáta profilu filtered_languages: Filtrované jazyky header: Obrázok v hlavičke locale: Jazyk @@ -43,9 +49,9 @@ sk: setting_auto_play_gif: Automaticky prehrávať animované GIFy setting_boost_modal: Zobrazovať potvrdzovacie okno pred re-toot setting_default_privacy: Nastavenie súkromia príspevkov - setting_default_sensitive: Označiť každý obrázok/video/súbor ako chúlostivý - setting_delete_modal: Zobrazovať potvrdzovacie okno pred zmazaním toot-u - setting_display_sensitive_media: Vždy zobrazovať médiá označované ako senzitívne + setting_default_sensitive: Označ všetky mediálne súbory ako chúlostivé + setting_delete_modal: Zobrazuj potvrdzovacie okno pred vymazaním toot-u + setting_display_sensitive_media: Vždy zobraz médiá označené ako chúlostivé setting_noindex: Nezaraďuj príspevky do indexu pre vyhľadávče setting_reduce_motion: Redukovať pohyb v animáciách setting_system_font_ui: Použiť základné systémové písmo diff --git a/config/locales/sk.yml b/config/locales/sk.yml index 37f7113..a38b245 100644 --- a/config/locales/sk.yml +++ b/config/locales/sk.yml @@ -4,6 +4,7 @@ sk: about_hashtag_html: Toto sú verejné toot príspevky otagované #%{tagom}. Ak máš účet niekde vo fediverse, môžeš ich používať. about_mastodon_html: Mastodon je sociálna sieť založená na otvorených webových protokoloch. Jej zrojový kód je otvorený a je decentralizovaná podobne ako email. about_this: O tejto instancii + administered_by: 'Správca je:' closed_registrations: Registrácie sú momentálne uzatvorené. Avšak, môžeš nájsť nejaký iný Mastodon server kde si založ účet a získaj tak prístup do presne tej istej siete, odtiaľ. contact: Kontakt contact_missing: Nezadané @@ -60,7 +61,15 @@ sk: destroyed_msg: Poznámka moderátora bola úspešne zmazaná! accounts: are_you_sure: Ste si istý? + avatar: Maskot by_domain: Doména + change_email: + changed_msg: Email k tomuto účtu bol úspešne zmenený! + current_email: Súčastný email + label: Zmeniť email + new_email: Nový email + submit: Zmeniť email + title: Zmeň email pre %{username} confirm: Potvrdiť confirmed: Potvrdený demote: Degradovať @@ -108,6 +117,7 @@ sk: public: Verejná os push_subscription_expires: PuSH odoberanie expiruje redownload: Obnoviť avatar + remove_avatar: Odstrániť avatár reset: Reset reset_password: Obnoviť heslo resubscribe: Znovu odoberať @@ -128,6 +138,7 @@ sk: statuses: Príspevky subscribe: Odoberať title: Účty + unconfirmed_email: Nepotvrdený email undo_silenced: Zrušiť stíšenie undo_suspension: Zrušiť suspendáciu unsubscribe: Prestať odoberať @@ -150,8 +161,9 @@ sk: enable_user: "%{name} povolil prihlásenie pre používateľa %{target}" memorialize_account: '%{name} zmenil účet %{target} na stránku "Navždy budeme spomínať"' promote_user: "%{name} povýšil/a používateľa %{target}" + remove_avatar_user: "%{name} odstránil/a %{target}ov avatár" reset_password_user: "%{name} resetoval/a heslo pre používateľa %{target}" - resolve_report: "%{name} zamietli nahlásenie %{target}" + resolve_report: "%{name} vyriešili nahlásenie užívateľa %{target}" silence_account: "%{name} utíšil/a účet %{target}" suspend_account: "%{name} zablokoval/a účet používateľa %{target}" unsilence_account: "%{name} zrušil/a utíšenie účtu používateľa %{target}" @@ -239,8 +251,14 @@ sk: expired: Expirované title: Filtrovať title: Pozvánky + report_notes: + created_msg: Poznámka o nahlásení úspešne vytvorená! + destroyed_msg: Poznámka o nahlásení úspešne vymazaná! reports: - action_taken_by: Zákrok vykonal + account: + note: poznámka + report: nahlás + action_taken_by: Zákrok vykonal/a are_you_sure: Ste si istý/á? comment: none: Žiadne From bfc41711dd5b9725d135c11807aa645ebc78bc18 Mon Sep 17 00:00:00 2001 From: Stefan Midjich Date: Wed, 25 Apr 2018 01:54:24 +0200 Subject: [PATCH 190/381] =?UTF-8?q?more=20sane=20phrasing=20in=20?= =?UTF-8?q?=F0=9F=87=B8=F0=9F=87=AA=20translation=20(#7256)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * more sane phrasing in 🇸🇪 translation * another small issue in 🇸🇪 translation --- config/locales/sv.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config/locales/sv.yml b/config/locales/sv.yml index 7e763c2..993bdd9 100644 --- a/config/locales/sv.yml +++ b/config/locales/sv.yml @@ -9,7 +9,7 @@ sv: contact_missing: Inte inställd contact_unavailable: N/A description_headline: Vad är %{domain}? - domain_count_after: annan instans + domain_count_after: andra instanser domain_count_before: Uppkopplad mot extended_description_html: |

    En bra plats för regler

    @@ -29,7 +29,7 @@ sv: other_instances: Instanslista source_code: Källkod status_count_after: statusar - status_count_before: Vem författade + status_count_before: Som skapat user_count_after: användare user_count_before: Hem till what_is_mastodon: Vad är Mastodon? From 9d4710ed0059b2f789e6b32b9f81d4ce90b98907 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Wed, 25 Apr 2018 02:10:02 +0200 Subject: [PATCH 191/381] Add RSS feeds for end-users (#7259) * Add RSS feed for accounts * Add RSS feeds for hashtags * Fix code style issues * Fix code style issues --- app/controllers/accounts_controller.rb | 10 ++- app/controllers/tags_controller.rb | 11 ++- app/helpers/stream_entries_helper.rb | 12 +-- app/lib/rss_builder.rb | 130 ++++++++++++++++++++++++++++++ app/serializers/rss/account_serializer.rb | 39 +++++++++ app/serializers/rss/tag_serializer.rb | 37 +++++++++ 6 files changed, 230 insertions(+), 9 deletions(-) create mode 100644 app/lib/rss_builder.rb create mode 100644 app/serializers/rss/account_serializer.rb create mode 100644 app/serializers/rss/tag_serializer.rb diff --git a/app/controllers/accounts_controller.rb b/app/controllers/accounts_controller.rb index 7bf3582..1152d4a 100644 --- a/app/controllers/accounts_controller.rb +++ b/app/controllers/accounts_controller.rb @@ -20,9 +20,10 @@ class AccountsController < ApplicationController @pinned_statuses = cache_collection(@account.pinned_statuses, Status) if show_pinned_statuses? @statuses = filtered_status_page(params) @statuses = cache_collection(@statuses, Status) + unless @statuses.empty? - @older_url = older_url if @statuses.last.id > filtered_statuses.last.id - @newer_url = newer_url if @statuses.first.id < filtered_statuses.first.id + @older_url = older_url if @statuses.last.id > filtered_statuses.last.id + @newer_url = newer_url if @statuses.first.id < filtered_statuses.first.id end end @@ -31,6 +32,11 @@ class AccountsController < ApplicationController render xml: OStatus::AtomSerializer.render(OStatus::AtomSerializer.new.feed(@account, @entries.reject { |entry| entry.status.nil? })) end + format.rss do + @statuses = cache_collection(default_statuses.without_reblogs.without_replies.limit(PAGE_SIZE), Status) + render xml: RSS::AccountSerializer.render(@account, @statuses) + end + format.json do skip_session! diff --git a/app/controllers/tags_controller.rb b/app/controllers/tags_controller.rb index 9f3090e..014a5c9 100644 --- a/app/controllers/tags_controller.rb +++ b/app/controllers/tags_controller.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true class TagsController < ApplicationController + PAGE_SIZE = 20 + before_action :set_body_classes before_action :set_instance_presenter @@ -13,8 +15,15 @@ class TagsController < ApplicationController @initial_state_json = serializable_resource.to_json end + format.rss do + @statuses = Status.as_tag_timeline(@tag).limit(PAGE_SIZE) + @statuses = cache_collection(@statuses, Status) + + render xml: RSS::TagSerializer.render(@tag, @statuses) + end + format.json do - @statuses = Status.as_tag_timeline(@tag, current_account, params[:local]).paginate_by_max_id(20, params[:max_id]) + @statuses = Status.as_tag_timeline(@tag, current_account, params[:local]).paginate_by_max_id(PAGE_SIZE, params[:max_id]) @statuses = cache_collection(@statuses, Status) render json: collection_presenter, diff --git a/app/helpers/stream_entries_helper.rb b/app/helpers/stream_entries_helper.rb index 8254ef4..c6f12ec 100644 --- a/app/helpers/stream_entries_helper.rb +++ b/app/helpers/stream_entries_helper.rb @@ -12,17 +12,17 @@ module StreamEntriesHelper prepend_str = [ [ number_to_human(account.statuses_count, strip_insignificant_zeros: true), - t('accounts.posts'), + I18n.t('accounts.posts'), ].join(' '), [ number_to_human(account.following_count, strip_insignificant_zeros: true), - t('accounts.following'), + I18n.t('accounts.following'), ].join(' '), [ number_to_human(account.followers_count, strip_insignificant_zeros: true), - t('accounts.followers'), + I18n.t('accounts.followers'), ].join(' '), ].join(', ') @@ -40,16 +40,16 @@ module StreamEntriesHelper end end - text = attachments.to_a.reject { |_, value| value.zero? }.map { |key, value| t("statuses.attached.#{key}", count: value) }.join(' · ') + text = attachments.to_a.reject { |_, value| value.zero? }.map { |key, value| I18n.t("statuses.attached.#{key}", count: value) }.join(' · ') return if text.blank? - t('statuses.attached.description', attached: text) + I18n.t('statuses.attached.description', attached: text) end def status_text_summary(status) return if status.spoiler_text.blank? - t('statuses.content_warning', warning: status.spoiler_text) + I18n.t('statuses.content_warning', warning: status.spoiler_text) end def status_description(status) diff --git a/app/lib/rss_builder.rb b/app/lib/rss_builder.rb new file mode 100644 index 0000000..63ddba2 --- /dev/null +++ b/app/lib/rss_builder.rb @@ -0,0 +1,130 @@ +# frozen_string_literal: true + +class RSSBuilder + class ItemBuilder + def initialize + @item = Ox::Element.new('item') + end + + def title(str) + @item << (Ox::Element.new('title') << str) + + self + end + + def link(str) + @item << Ox::Element.new('guid').tap do |guid| + guid['isPermalink'] = 'true' + guid << str + end + + @item << (Ox::Element.new('link') << str) + + self + end + + def pub_date(date) + @item << (Ox::Element.new('pubDate') << date.to_formatted_s(:rfc822)) + + self + end + + def description(str) + @item << (Ox::Element.new('description') << str) + + self + end + + def enclosure(url, type, size) + @item << Ox::Element.new('enclosure').tap do |enclosure| + enclosure['url'] = url + enclosure['length'] = size + enclosure['type'] = type + end + + self + end + + def to_element + @item + end + end + + def initialize + @document = Ox::Document.new(version: '1.0') + @channel = Ox::Element.new('channel') + + @document << (rss << @channel) + end + + def title(str) + @channel << (Ox::Element.new('title') << str) + + self + end + + def link(str) + @channel << (Ox::Element.new('link') << str) + + self + end + + def image(str) + @channel << Ox::Element.new('image').tap do |image| + image << (Ox::Element.new('url') << str) + image << (Ox::Element.new('title') << '') + image << (Ox::Element.new('link') << '') + end + + @channel << (Ox::Element.new('webfeeds:icon') << str) + + self + end + + def cover(str) + @channel << Ox::Element.new('webfeeds:cover').tap do |cover| + cover['image'] = str + end + + self + end + + def logo(str) + @channel << (Ox::Element.new('webfeeds:logo') << str) + + self + end + + def accent_color(str) + @channel << (Ox::Element.new('webfeeds:accentColor') << str) + + self + end + + def description(str) + @channel << (Ox::Element.new('description') << str) + + self + end + + def item + @channel << ItemBuilder.new.tap do |item| + yield item + end.to_element + + self + end + + def to_xml + ('' + Ox.dump(@document, effort: :tolerant)).force_encoding('UTF-8') + end + + private + + def rss + Ox::Element.new('rss').tap do |rss| + rss['version'] = '2.0' + rss['xmlns:webfeeds'] = 'http://webfeeds.org/rss/1.0' + end + end +end diff --git a/app/serializers/rss/account_serializer.rb b/app/serializers/rss/account_serializer.rb new file mode 100644 index 0000000..bde360a --- /dev/null +++ b/app/serializers/rss/account_serializer.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +class RSS::AccountSerializer + include ActionView::Helpers::NumberHelper + include StreamEntriesHelper + include RoutingHelper + + def render(account, statuses) + builder = RSSBuilder.new + + builder.title("#{display_name(account)} (@#{account.local_username_and_domain})") + .description(account_description(account)) + .link(TagManager.instance.url_for(account)) + .logo(full_asset_url(asset_pack_path('logo.svg'))) + .accent_color('2b90d9') + + builder.image(full_asset_url(account.avatar.url(:original))) if account.avatar? + builder.cover(full_asset_url(account.header.url(:original))) if account.header? + + statuses.each do |status| + builder.item do |item| + item.title(status.title) + .link(TagManager.instance.url_for(status)) + .pub_date(status.created_at) + .description(status.spoiler_text.presence || Formatter.instance.format(status).to_str) + + status.media_attachments.each do |media| + item.enclosure(full_asset_url(media.file.url(:original, false)), media.file.content_type, length: media.file.size) + end + end + end + + builder.to_xml + end + + def self.render(account, statuses) + new.render(account, statuses) + end +end diff --git a/app/serializers/rss/tag_serializer.rb b/app/serializers/rss/tag_serializer.rb new file mode 100644 index 0000000..7680a8d --- /dev/null +++ b/app/serializers/rss/tag_serializer.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +class RSS::TagSerializer + include ActionView::Helpers::NumberHelper + include ActionView::Helpers::SanitizeHelper + include StreamEntriesHelper + include RoutingHelper + + def render(tag, statuses) + builder = RSSBuilder.new + + builder.title("##{tag.name}") + .description(strip_tags(I18n.t('about.about_hashtag_html', hashtag: tag.name))) + .link(tag_url(tag)) + .logo(full_asset_url(asset_pack_path('logo.svg'))) + .accent_color('2b90d9') + + statuses.each do |status| + builder.item do |item| + item.title(status.title) + .link(TagManager.instance.url_for(status)) + .pub_date(status.created_at) + .description(status.spoiler_text.presence || Formatter.instance.format(status).to_str) + + status.media_attachments.each do |media| + item.enclosure(full_asset_url(media.file.url(:original, false)), media.file.content_type, length: media.file.size) + end + end + end + + builder.to_xml + end + + def self.render(tag, statuses) + new.render(tag, statuses) + end +end From f58dcbc9814b5ba2fd4f7d7af643aa25dcf40594 Mon Sep 17 00:00:00 2001 From: MIYAGI Hikaru Date: Wed, 25 Apr 2018 09:14:49 +0900 Subject: [PATCH 192/381] HTTP proxy support for outgoing request, manage access to hidden service (#7134) * Add support for HTTP client proxy * Add access control for darknet Supress error when access to darknet via transparent proxy * Fix the codes pointed out * Lint * Fix an omission + lint * any? -> include? * Change detection method to regexp to avoid test fail --- .env.production.sample | 7 +++++++ app/lib/request.rb | 16 +++++++++++++++- config/initializers/http_client_proxy.rb | 24 ++++++++++++++++++++++++ 3 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 config/initializers/http_client_proxy.rb diff --git a/.env.production.sample b/.env.production.sample index 9de2c06..c936546 100644 --- a/.env.production.sample +++ b/.env.production.sample @@ -214,3 +214,10 @@ STREAMING_CLUSTER_NUM=1 # SAML_UID_ATTRIBUTE="urn:oid:0.9.2342.19200300.100.1.1" # SAML_ATTRIBUTES_STATEMENTS_VERIFIED= # SAML_ATTRIBUTES_STATEMENTS_VERIFIED_EMAIL= + +# Use HTTP proxy for outgoing request (optional) +# http_proxy=http://gateway.local:8118 +# Access control for hidden service. +# ALLOW_ACCESS_TO_HIDDEN_SERVICE=true +# If you use transparent proxy to access to hidden service, uncomment following for skipping private address check. +# HIDDEN_SERVICE_VIA_TRANSPARENT_PROXY=true diff --git a/app/lib/request.rb b/app/lib/request.rb index dca93a6..0acd654 100644 --- a/app/lib/request.rb +++ b/app/lib/request.rb @@ -11,9 +11,10 @@ class Request def initialize(verb, url, **options) @verb = verb @url = Addressable::URI.parse(url).normalize - @options = options.merge(socket_class: Socket) + @options = options.merge(use_proxy? ? Rails.configuration.x.http_client_proxy : { socket_class: Socket }) @headers = {} + raise Mastodon::HostValidationError, 'Instance does not support hidden service connections' if block_hidden_service? set_common_headers! set_digest! if options.key?(:body) end @@ -99,6 +100,14 @@ class Request @http_client ||= HTTP.timeout(:per_operation, timeout).follow(max_hops: 2) end + def use_proxy? + Rails.configuration.x.http_client_proxy.present? + end + + def block_hidden_service? + !Rails.configuration.x.access_to_hidden_service && /\.(onion|i2p)$/.match(@url.host) + end + module ClientLimit def body_with_limit(limit = 1.megabyte) raise Mastodon::LengthValidationError if content_length.present? && content_length > limit @@ -129,6 +138,7 @@ class Request class Socket < TCPSocket class << self def open(host, *args) + return super host, *args if thru_hidden_service? host outer_e = nil Addrinfo.foreach(host, nil, nil, :SOCK_STREAM) do |address| begin @@ -142,6 +152,10 @@ class Request end alias new open + + def thru_hidden_service?(host) + Rails.configuration.x.hidden_service_via_transparent_proxy && /\.(onion|i2p)$/.match(host) + end end end diff --git a/config/initializers/http_client_proxy.rb b/config/initializers/http_client_proxy.rb new file mode 100644 index 0000000..f5026d5 --- /dev/null +++ b/config/initializers/http_client_proxy.rb @@ -0,0 +1,24 @@ +Rails.application.configure do + config.x.http_client_proxy = {} + if ENV['http_proxy'].present? + proxy = URI.parse(ENV['http_proxy']) + raise "Unsupported proxy type: #{proxy.scheme}" unless %w(http https).include? proxy.scheme + raise "No proxy host" unless proxy.host + + host = proxy.host + host = host[1...-1] if host[0] == '[' #for IPv6 address + config.x.http_client_proxy[:proxy] = { proxy_address: host, proxy_port: proxy.port, proxy_username: proxy.user, proxy_password: proxy.password }.compact + end + + config.x.access_to_hidden_service = ENV['ALLOW_ACCESS_TO_HIDDEN_SERVICE'] == 'true' + config.x.hidden_service_via_transparent_proxy = ENV['HIDDEN_SERVICE_VIA_TRANSPARENT_PROXY'] == 'true' +end + +module Goldfinger + def self.finger(uri, opts = {}) + to_hidden = /\.(onion|i2p)(:\d+)?$/.match(uri) + raise Mastodon::HostValidationError, 'Instance does not support hidden service connections' if !Rails.configuration.x.access_to_hidden_service && to_hidden + opts = opts.merge(Rails.configuration.x.http_client_proxy).merge(ssl: !to_hidden) + Goldfinger::Client.new(uri, opts).finger + end +end From eb593a5a0c9ee9749115f867a5675ceb1f231379 Mon Sep 17 00:00:00 2001 From: MIYAGI Hikaru Date: Wed, 25 Apr 2018 21:12:28 +0900 Subject: [PATCH 193/381] Append '.test' to hostname in stub data (#7260) --- spec/fixtures/requests/oembed_json.html | 2 +- spec/fixtures/requests/oembed_json_xml.html | 4 +-- spec/fixtures/requests/oembed_xml.html | 2 +- spec/helpers/jsonld_helper_spec.rb | 24 +++++++------- spec/lib/formatter_spec.rb | 6 ++-- spec/lib/ostatus/atom_serializer_spec.rb | 26 +++++++-------- spec/lib/provider_discovery_spec.rb | 38 +++++++++++----------- spec/lib/tag_manager_spec.rb | 34 +++++++++---------- spec/models/account_spec.rb | 10 +++--- spec/models/status_pin_spec.rb | 2 +- .../activitypub/process_account_service_spec.rb | 4 +-- 11 files changed, 76 insertions(+), 76 deletions(-) diff --git a/spec/fixtures/requests/oembed_json.html b/spec/fixtures/requests/oembed_json.html index 773a4f9..1670858 100644 --- a/spec/fixtures/requests/oembed_json.html +++ b/spec/fixtures/requests/oembed_json.html @@ -1,7 +1,7 @@ - + diff --git a/spec/fixtures/requests/oembed_json_xml.html b/spec/fixtures/requests/oembed_json_xml.html index 8afd8e9..9f5b9e8 100644 --- a/spec/fixtures/requests/oembed_json_xml.html +++ b/spec/fixtures/requests/oembed_json_xml.html @@ -7,8 +7,8 @@ > The type attribute must contain either application/json+oembed for JSON > responses, or text/xml+oembed for XML. --> - - + + diff --git a/spec/fixtures/requests/oembed_xml.html b/spec/fixtures/requests/oembed_xml.html index bdfcca1..788dfaa 100644 --- a/spec/fixtures/requests/oembed_xml.html +++ b/spec/fixtures/requests/oembed_xml.html @@ -7,7 +7,7 @@ > The type attribute must contain either application/json+oembed for JSON > responses, or text/xml+oembed for XML. --> - + diff --git a/spec/helpers/jsonld_helper_spec.rb b/spec/helpers/jsonld_helper_spec.rb index 48bfdc3..a5ab249 100644 --- a/spec/helpers/jsonld_helper_spec.rb +++ b/spec/helpers/jsonld_helper_spec.rb @@ -32,37 +32,37 @@ describe JsonLdHelper do describe '#fetch_resource' do context 'when the second argument is false' do it 'returns resource even if the retrieved ID and the given URI does not match' do - stub_request(:get, 'https://bob/').to_return body: '{"id": "https://alice/"}' - stub_request(:get, 'https://alice/').to_return body: '{"id": "https://alice/"}' + stub_request(:get, 'https://bob.test/').to_return body: '{"id": "https://alice.test/"}' + stub_request(:get, 'https://alice.test/').to_return body: '{"id": "https://alice.test/"}' - expect(fetch_resource('https://bob/', false)).to eq({ 'id' => 'https://alice/' }) + expect(fetch_resource('https://bob.test/', false)).to eq({ 'id' => 'https://alice.test/' }) end it 'returns nil if the object identified by the given URI and the object identified by the retrieved ID does not match' do - stub_request(:get, 'https://mallory/').to_return body: '{"id": "https://marvin/"}' - stub_request(:get, 'https://marvin/').to_return body: '{"id": "https://alice/"}' + stub_request(:get, 'https://mallory.test/').to_return body: '{"id": "https://marvin.test/"}' + stub_request(:get, 'https://marvin.test/').to_return body: '{"id": "https://alice.test/"}' - expect(fetch_resource('https://mallory/', false)).to eq nil + expect(fetch_resource('https://mallory.test/', false)).to eq nil end end context 'when the second argument is true' do it 'returns nil if the retrieved ID and the given URI does not match' do - stub_request(:get, 'https://mallory/').to_return body: '{"id": "https://alice/"}' - expect(fetch_resource('https://mallory/', true)).to eq nil + stub_request(:get, 'https://mallory.test/').to_return body: '{"id": "https://alice.test/"}' + expect(fetch_resource('https://mallory.test/', true)).to eq nil end end end describe '#fetch_resource_without_id_validation' do it 'returns nil if the status code is not 200' do - stub_request(:get, 'https://host/').to_return status: 400, body: '{}' - expect(fetch_resource_without_id_validation('https://host/')).to eq nil + stub_request(:get, 'https://host.test/').to_return status: 400, body: '{}' + expect(fetch_resource_without_id_validation('https://host.test/')).to eq nil end it 'returns hash' do - stub_request(:get, 'https://host/').to_return status: 200, body: '{}' - expect(fetch_resource_without_id_validation('https://host/')).to eq({}) + stub_request(:get, 'https://host.test/').to_return status: 200, body: '{}' + expect(fetch_resource_without_id_validation('https://host.test/')).to eq({}) end end end diff --git a/spec/lib/formatter_spec.rb b/spec/lib/formatter_spec.rb index 6e849f3..b8683e7 100644 --- a/spec/lib/formatter_spec.rb +++ b/spec/lib/formatter_spec.rb @@ -2,7 +2,7 @@ require 'rails_helper' RSpec.describe Formatter do let(:local_account) { Fabricate(:account, domain: nil, username: 'alice') } - let(:remote_account) { Fabricate(:account, domain: 'remote', username: 'bob', url: 'https://remote/') } + let(:remote_account) { Fabricate(:account, domain: 'remote.test', username: 'bob', url: 'https://remote.test/') } shared_examples 'encode and link URLs' do context 'matches a stand-alone medium URL' do @@ -377,12 +377,12 @@ RSpec.describe Formatter do end context 'contains linkable mentions for remote accounts' do - let(:text) { '@bob@remote' } + let(:text) { '@bob@remote.test' } before { remote_account } it 'links' do - is_expected.to eq '

    @bob

    ' + is_expected.to eq '

    @bob

    ' end end diff --git a/spec/lib/ostatus/atom_serializer_spec.rb b/spec/lib/ostatus/atom_serializer_spec.rb index d467913..0bd2288 100644 --- a/spec/lib/ostatus/atom_serializer_spec.rb +++ b/spec/lib/ostatus/atom_serializer_spec.rb @@ -30,13 +30,13 @@ RSpec.describe OStatus::AtomSerializer do end it 'appends activity:object with target account' do - target_account = Fabricate(:account, domain: 'domain', uri: 'https://domain/id') + target_account = Fabricate(:account, domain: 'domain.test', uri: 'https://domain.test/id') follow_request = Fabricate(:follow_request, target_account: target_account) follow_request_salmon = serialize(follow_request) object = follow_request_salmon.nodes.find { |node| node.name == 'activity:object' } - expect(object.id.text).to eq 'https://domain/id' + expect(object.id.text).to eq 'https://domain.test/id' end end @@ -413,20 +413,20 @@ RSpec.describe OStatus::AtomSerializer do entry = OStatus::AtomSerializer.new.entry(remote_status.stream_entry, true) entry.nodes.delete_if { |node| node[:type] == 'application/activity+json' } # Remove ActivityPub link to simplify test - xml = OStatus::AtomSerializer.render(entry).gsub('cb6e6126.ngrok.io', 'remote') + xml = OStatus::AtomSerializer.render(entry).gsub('cb6e6126.ngrok.io', 'remote.test') remote_status.destroy! remote_account.destroy! account = Account.create!( - domain: 'remote', + domain: 'remote.test', username: 'username', last_webfingered_at: Time.now.utc ) ProcessFeedService.new.call(xml, account) - expect(Status.find_by(uri: "https://remote/users/#{remote_status.account.to_param}/statuses/#{remote_status.id}")).to be_instance_of Status + expect(Status.find_by(uri: "https://remote.test/users/#{remote_status.account.to_param}/statuses/#{remote_status.id}")).to be_instance_of Status end end @@ -776,13 +776,13 @@ RSpec.describe OStatus::AtomSerializer do end it 'appends activity:object element with target account' do - target_account = Fabricate(:account, domain: 'domain', uri: 'https://domain/id') + target_account = Fabricate(:account, domain: 'domain.test', uri: 'https://domain.test/id') block = Fabricate(:block, target_account: target_account) block_salmon = OStatus::AtomSerializer.new.block_salmon(block) object = block_salmon.nodes.find { |node| node.name == 'activity:object' } - expect(object.id.text).to eq 'https://domain/id' + expect(object.id.text).to eq 'https://domain.test/id' end it 'returns element whose rendered view triggers block when processed' do @@ -863,13 +863,13 @@ RSpec.describe OStatus::AtomSerializer do end it 'appends activity:object element with target account' do - target_account = Fabricate(:account, domain: 'domain', uri: 'https://domain/id') + target_account = Fabricate(:account, domain: 'domain.test', uri: 'https://domain.test/id') block = Fabricate(:block, target_account: target_account) unblock_salmon = OStatus::AtomSerializer.new.unblock_salmon(block) object = unblock_salmon.nodes.find { |node| node.name == 'activity:object' } - expect(object.id.text).to eq 'https://domain/id' + expect(object.id.text).to eq 'https://domain.test/id' end it 'returns element whose rendered view triggers block when processed' do @@ -1124,13 +1124,13 @@ RSpec.describe OStatus::AtomSerializer do end it 'appends activity:object element with target account' do - target_account = Fabricate(:account, domain: 'domain', uri: 'https://domain/id') + target_account = Fabricate(:account, domain: 'domain.test', uri: 'https://domain.test/id') follow = Fabricate(:follow, target_account: target_account) follow_salmon = OStatus::AtomSerializer.new.follow_salmon(follow) object = follow_salmon.nodes.find { |node| node.name == 'activity:object' } - expect(object.id.text).to eq 'https://domain/id' + expect(object.id.text).to eq 'https://domain.test/id' end it 'includes description' do @@ -1242,14 +1242,14 @@ RSpec.describe OStatus::AtomSerializer do end it 'appends activity:object element with target account' do - target_account = Fabricate(:account, domain: 'domain', uri: 'https://domain/id') + target_account = Fabricate(:account, domain: 'domain.test', uri: 'https://domain.test/id') follow = Fabricate(:follow, target_account: target_account) follow.destroy! unfollow_salmon = OStatus::AtomSerializer.new.unfollow_salmon(follow) object = unfollow_salmon.nodes.find { |node| node.name == 'activity:object' } - expect(object.id.text).to eq 'https://domain/id' + expect(object.id.text).to eq 'https://domain.test/id' end it 'returns element whose rendered view triggers unfollow when processed' do diff --git a/spec/lib/provider_discovery_spec.rb b/spec/lib/provider_discovery_spec.rb index 12e2616..de2ac16 100644 --- a/spec/lib/provider_discovery_spec.rb +++ b/spec/lib/provider_discovery_spec.rb @@ -7,7 +7,7 @@ describe ProviderDiscovery do context 'when status code is 200 and MIME type is text/html' do context 'Both of JSON and XML provider are discoverable' do before do - stub_request(:get, 'https://host/oembed.html').to_return( + stub_request(:get, 'https://host.test/oembed.html').to_return( status: 200, headers: { 'Content-Type': 'text/html' }, body: request_fixture('oembed_json_xml.html') @@ -15,21 +15,21 @@ describe ProviderDiscovery do end it 'returns new OEmbed::Provider for JSON provider if :format option is set to :json' do - provider = ProviderDiscovery.discover_provider('https://host/oembed.html', format: :json) - expect(provider.endpoint).to eq 'https://host/provider.json' + provider = ProviderDiscovery.discover_provider('https://host.test/oembed.html', format: :json) + expect(provider.endpoint).to eq 'https://host.test/provider.json' expect(provider.format).to eq :json end it 'returns new OEmbed::Provider for XML provider if :format option is set to :xml' do - provider = ProviderDiscovery.discover_provider('https://host/oembed.html', format: :xml) - expect(provider.endpoint).to eq 'https://host/provider.xml' + provider = ProviderDiscovery.discover_provider('https://host.test/oembed.html', format: :xml) + expect(provider.endpoint).to eq 'https://host.test/provider.xml' expect(provider.format).to eq :xml end end context 'JSON provider is discoverable while XML provider is not' do before do - stub_request(:get, 'https://host/oembed.html').to_return( + stub_request(:get, 'https://host.test/oembed.html').to_return( status: 200, headers: { 'Content-Type': 'text/html' }, body: request_fixture('oembed_json.html') @@ -37,15 +37,15 @@ describe ProviderDiscovery do end it 'returns new OEmbed::Provider for JSON provider' do - provider = ProviderDiscovery.discover_provider('https://host/oembed.html') - expect(provider.endpoint).to eq 'https://host/provider.json' + provider = ProviderDiscovery.discover_provider('https://host.test/oembed.html') + expect(provider.endpoint).to eq 'https://host.test/provider.json' expect(provider.format).to eq :json end end context 'XML provider is discoverable while JSON provider is not' do before do - stub_request(:get, 'https://host/oembed.html').to_return( + stub_request(:get, 'https://host.test/oembed.html').to_return( status: 200, headers: { 'Content-Type': 'text/html' }, body: request_fixture('oembed_xml.html') @@ -53,15 +53,15 @@ describe ProviderDiscovery do end it 'returns new OEmbed::Provider for XML provider' do - provider = ProviderDiscovery.discover_provider('https://host/oembed.html') - expect(provider.endpoint).to eq 'https://host/provider.xml' + provider = ProviderDiscovery.discover_provider('https://host.test/oembed.html') + expect(provider.endpoint).to eq 'https://host.test/provider.xml' expect(provider.format).to eq :xml end end context 'Invalid XML provider is discoverable while JSON provider is not' do before do - stub_request(:get, 'https://host/oembed.html').to_return( + stub_request(:get, 'https://host.test/oembed.html').to_return( status: 200, headers: { 'Content-Type': 'text/html' }, body: request_fixture('oembed_invalid_xml.html') @@ -69,13 +69,13 @@ describe ProviderDiscovery do end it 'raises OEmbed::NotFound' do - expect { ProviderDiscovery.discover_provider('https://host/oembed.html') }.to raise_error OEmbed::NotFound + expect { ProviderDiscovery.discover_provider('https://host.test/oembed.html') }.to raise_error OEmbed::NotFound end end context 'Neither of JSON and XML provider is discoverable' do before do - stub_request(:get, 'https://host/oembed.html').to_return( + stub_request(:get, 'https://host.test/oembed.html').to_return( status: 200, headers: { 'Content-Type': 'text/html' }, body: request_fixture('oembed_undiscoverable.html') @@ -83,14 +83,14 @@ describe ProviderDiscovery do end it 'raises OEmbed::NotFound' do - expect { ProviderDiscovery.discover_provider('https://host/oembed.html') }.to raise_error OEmbed::NotFound + expect { ProviderDiscovery.discover_provider('https://host.test/oembed.html') }.to raise_error OEmbed::NotFound end end end context 'when status code is not 200' do before do - stub_request(:get, 'https://host/oembed.html').to_return( + stub_request(:get, 'https://host.test/oembed.html').to_return( status: 400, headers: { 'Content-Type': 'text/html' }, body: request_fixture('oembed_xml.html') @@ -98,20 +98,20 @@ describe ProviderDiscovery do end it 'raises OEmbed::NotFound' do - expect { ProviderDiscovery.discover_provider('https://host/oembed.html') }.to raise_error OEmbed::NotFound + expect { ProviderDiscovery.discover_provider('https://host.test/oembed.html') }.to raise_error OEmbed::NotFound end end context 'when MIME type is not text/html' do before do - stub_request(:get, 'https://host/oembed.html').to_return( + stub_request(:get, 'https://host.test/oembed.html').to_return( status: 200, body: request_fixture('oembed_xml.html') ) end it 'raises OEmbed::NotFound' do - expect { ProviderDiscovery.discover_provider('https://host/oembed.html') }.to raise_error OEmbed::NotFound + expect { ProviderDiscovery.discover_provider('https://host.test/oembed.html') }.to raise_error OEmbed::NotFound end end end diff --git a/spec/lib/tag_manager_spec.rb b/spec/lib/tag_manager_spec.rb index 5427a29..3a804ac 100644 --- a/spec/lib/tag_manager_spec.rb +++ b/spec/lib/tag_manager_spec.rb @@ -6,7 +6,7 @@ RSpec.describe TagManager do around do |example| original_local_domain = Rails.configuration.x.local_domain - Rails.configuration.x.local_domain = 'domain' + Rails.configuration.x.local_domain = 'domain.test' example.run @@ -18,11 +18,11 @@ RSpec.describe TagManager do end it 'returns true if the slash-stripped string equals to local domain' do - expect(TagManager.instance.local_domain?('DoMaIn/')).to eq true + expect(TagManager.instance.local_domain?('DoMaIn.Test/')).to eq true end it 'returns false for irrelevant string' do - expect(TagManager.instance.local_domain?('DoMaIn!')).to eq false + expect(TagManager.instance.local_domain?('DoMaIn.Test!')).to eq false end end @@ -31,7 +31,7 @@ RSpec.describe TagManager do around do |example| original_web_domain = Rails.configuration.x.web_domain - Rails.configuration.x.web_domain = 'domain' + Rails.configuration.x.web_domain = 'domain.test' example.run @@ -43,11 +43,11 @@ RSpec.describe TagManager do end it 'returns true if the slash-stripped string equals to web domain' do - expect(TagManager.instance.web_domain?('DoMaIn/')).to eq true + expect(TagManager.instance.web_domain?('DoMaIn.Test/')).to eq true end it 'returns false for string with irrelevant characters' do - expect(TagManager.instance.web_domain?('DoMaIn!')).to eq false + expect(TagManager.instance.web_domain?('DoMaIn.Test!')).to eq false end end @@ -57,7 +57,7 @@ RSpec.describe TagManager do end it 'returns normalized domain' do - expect(TagManager.instance.normalize_domain('DoMaIn/')).to eq 'domain' + expect(TagManager.instance.normalize_domain('DoMaIn.Test/')).to eq 'domain.test' end end @@ -69,18 +69,18 @@ RSpec.describe TagManager do end it 'returns true if the normalized string with port is local URL' do - Rails.configuration.x.web_domain = 'domain:42' - expect(TagManager.instance.local_url?('https://DoMaIn:42/')).to eq true + Rails.configuration.x.web_domain = 'domain.test:42' + expect(TagManager.instance.local_url?('https://DoMaIn.Test:42/')).to eq true end it 'returns true if the normalized string without port is local URL' do - Rails.configuration.x.web_domain = 'domain' - expect(TagManager.instance.local_url?('https://DoMaIn/')).to eq true + Rails.configuration.x.web_domain = 'domain.test' + expect(TagManager.instance.local_url?('https://DoMaIn.Test/')).to eq true end it 'returns false for string with irrelevant characters' do - Rails.configuration.x.web_domain = 'domain' - expect(TagManager.instance.local_url?('https://domainn/')).to eq false + Rails.configuration.x.web_domain = 'domain.test' + expect(TagManager.instance.local_url?('https://domainn.test/')).to eq false end end @@ -88,19 +88,19 @@ RSpec.describe TagManager do # The following comparisons MUST be case-insensitive. it 'returns true if the needle has a correct username and domain for remote user' do - expect(TagManager.instance.same_acct?('username@domain', 'UsErNaMe@DoMaIn')).to eq true + expect(TagManager.instance.same_acct?('username@domain.test', 'UsErNaMe@DoMaIn.Test')).to eq true end it 'returns false if the needle is missing a domain for remote user' do - expect(TagManager.instance.same_acct?('username@domain', 'UsErNaMe')).to eq false + expect(TagManager.instance.same_acct?('username@domain.test', 'UsErNaMe')).to eq false end it 'returns false if the needle has an incorrect domain for remote user' do - expect(TagManager.instance.same_acct?('username@domain', 'UsErNaMe@incorrect')).to eq false + expect(TagManager.instance.same_acct?('username@domain.test', 'UsErNaMe@incorrect.test')).to eq false end it 'returns false if the needle has an incorrect username for remote user' do - expect(TagManager.instance.same_acct?('username@domain', 'incorrect@DoMaIn')).to eq false + expect(TagManager.instance.same_acct?('username@domain.test', 'incorrect@DoMaIn.test')).to eq false end it 'returns true if the needle has a correct username and domain for local user' do diff --git a/spec/models/account_spec.rb b/spec/models/account_spec.rb index a8b24d0..fb7ddfa 100644 --- a/spec/models/account_spec.rb +++ b/spec/models/account_spec.rb @@ -94,14 +94,14 @@ RSpec.describe Account, type: :model do describe '#save_with_optional_media!' do before do - stub_request(:get, 'https://remote/valid_avatar').to_return(request_fixture('avatar.txt')) - stub_request(:get, 'https://remote/invalid_avatar').to_return(request_fixture('feed.txt')) + stub_request(:get, 'https://remote.test/valid_avatar').to_return(request_fixture('avatar.txt')) + stub_request(:get, 'https://remote.test/invalid_avatar').to_return(request_fixture('feed.txt')) end let(:account) do Fabricate(:account, - avatar_remote_url: 'https://remote/valid_avatar', - header_remote_url: 'https://remote/valid_avatar') + avatar_remote_url: 'https://remote.test/valid_avatar', + header_remote_url: 'https://remote.test/valid_avatar') end let!(:expectation) { account.dup } @@ -121,7 +121,7 @@ RSpec.describe Account, type: :model do context 'with invalid properties' do before do - account.avatar_remote_url = 'https://remote/invalid_avatar' + account.avatar_remote_url = 'https://remote.test/invalid_avatar' account.save_with_optional_media! end diff --git a/spec/models/status_pin_spec.rb b/spec/models/status_pin_spec.rb index 944baf6..6f0b2fe 100644 --- a/spec/models/status_pin_spec.rb +++ b/spec/models/status_pin_spec.rb @@ -55,7 +55,7 @@ RSpec.describe StatusPin, type: :model do end it 'allows pins above the max for remote accounts' do - account = Fabricate(:account, domain: 'remote', username: 'bob', url: 'https://remote/') + account = Fabricate(:account, domain: 'remote.test', username: 'bob', url: 'https://remote.test/') status = [] (max_pins + 1).times do |i| diff --git a/spec/services/activitypub/process_account_service_spec.rb b/spec/services/activitypub/process_account_service_spec.rb index 15e1f4b..d67d72a 100644 --- a/spec/services/activitypub/process_account_service_spec.rb +++ b/spec/services/activitypub/process_account_service_spec.rb @@ -6,9 +6,9 @@ RSpec.describe ActivityPub::ProcessAccountService do context 'property values' do let(:payload) do { - id: 'https://foo', + id: 'https://foo.test', type: 'Actor', - inbox: 'https://foo/inbox', + inbox: 'https://foo.test/inbox', attachment: [ { type: 'PropertyValue', name: 'Pronouns', value: 'They/them' }, { type: 'PropertyValue', name: 'Occupation', value: 'Unit test' }, From fa5b28df8aaa564623f98f315436f4b260a12030 Mon Sep 17 00:00:00 2001 From: Stefan Midjich Date: Thu, 26 Apr 2018 00:36:52 +0200 Subject: [PATCH 194/381] Better phrasing in swedish translation (#7263) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * more sane phrasing in 🇸🇪 translation * another small issue in 🇸🇪 translation * better phrasing in 🇸🇪 translation --- app/javascript/mastodon/locales/sv.json | 4 ++-- config/locales/sv.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/javascript/mastodon/locales/sv.json b/app/javascript/mastodon/locales/sv.json index 7db4d57..479fc9a 100644 --- a/app/javascript/mastodon/locales/sv.json +++ b/app/javascript/mastodon/locales/sv.json @@ -113,7 +113,7 @@ "getting_started.appsshort": "Appar", "getting_started.faq": "FAQ", "getting_started.heading": "Kom igång", - "getting_started.open_source_notice": "Mastodon är programvara med öppen källkod. Du kan bidra eller rapportera problem på GitHub på {github}.", + "getting_started.open_source_notice": "Mastodon är programvara med öppen källkod. Du kan bidra eller rapportera problem via GitHub på {github}.", "getting_started.userguide": "Användarguide", "home.column_settings.advanced": "Avancerad", "home.column_settings.basic": "Grundläggande", @@ -206,7 +206,7 @@ "onboarding.page_three.search": "Använd sökfältet för att hitta personer och titta på hashtags, till exempel {illustration} och {introductions}. För att leta efter en person som inte befinner sig i detta fall använd deras fulla handhavande.", "onboarding.page_two.compose": "Skriv inlägg från skrivkolumnen. Du kan ladda upp bilder, ändra integritetsinställningar och lägga till varningar med ikonerna nedan.", "onboarding.skip": "Hoppa över", - "privacy.change": "Justera status sekretess", + "privacy.change": "Justera sekretess", "privacy.direct.long": "Skicka endast till nämnda användare", "privacy.direct.short": "Direkt", "privacy.private.long": "Skicka endast till följare", diff --git a/config/locales/sv.yml b/config/locales/sv.yml index 993bdd9..9a559c0 100644 --- a/config/locales/sv.yml +++ b/config/locales/sv.yml @@ -545,7 +545,7 @@ sv: quadrillion: Q thousand: K trillion: T - unit: enhet + unit: '' pagination: newer: Nyare next: Nästa From 3afdd6a17b2e539cc4684cb7adeab9423f375e2f Mon Sep 17 00:00:00 2001 From: "Renato \"Lond\" Cerqueira" Date: Thu, 26 Apr 2018 11:58:22 +0200 Subject: [PATCH 195/381] Weblate translations 20180426 (#7266) * Translated using Weblate (Swedish) Currently translated at 100.0% (294 of 294 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/sv/ * Translated using Weblate (Slovak) Currently translated at 92.0% (576 of 626 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/sk/ * Translated using Weblate (Slovak) Currently translated at 100.0% (294 of 294 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/sk/ * Translated using Weblate (Dutch) Currently translated at 100.0% (626 of 626 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/nl/ * Translated using Weblate (Dutch) Currently translated at 100.0% (294 of 294 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/nl/ * Translated using Weblate (Swedish) Currently translated at 100.0% (294 of 294 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/sv/ * Translated using Weblate (Swedish) Currently translated at 99.6% (624 of 626 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/sv/ * Translated using Weblate (Japanese) Currently translated at 100.0% (294 of 294 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/ja/ * Translated using Weblate (Japanese) Currently translated at 99.3% (622 of 626 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/ja/ * Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (62 of 62 strings) Translation: Mastodon/Preferences Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/simple_form/pt_BR/ * Translated using Weblate (Galician) Currently translated at 100.0% (626 of 626 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/gl/ * Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (294 of 294 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/pt_BR/ * Translated using Weblate (Portuguese (Brazil)) Currently translated at 99.2% (621 of 626 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/pt_BR/ * Translated using Weblate (Basque) Currently translated at 32.2% (20 of 62 strings) Translation: Mastodon/Preferences Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/simple_form/eu/ * Translated using Weblate (Arabic) Currently translated at 99.6% (293 of 294 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/ar/ * Translated using Weblate (Arabic) Currently translated at 82.4% (516 of 626 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/ar/ * Translated using Weblate (Slovak) Currently translated at 92.1% (577 of 626 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/sk/ * Translated using Weblate (Catalan) Currently translated at 100.0% (626 of 626 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/ca/ * Translated using Weblate (Basque) Currently translated at 100.0% (294 of 294 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/eu/ * Translated using Weblate (Slovak) Currently translated at 92.3% (578 of 626 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/sk/ * Translated using Weblate (Basque) Currently translated at 100.0% (294 of 294 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/eu/ https://sustatu.eus/1380226549995 * Translated using Weblate (Catalan) Currently translated at 100.0% (294 of 294 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/ca/ * Normalize translations ran yarn build:development && i18n-tasks normalize && yarn manage:translations && i18n-tasks remove-unused --- app/javascript/mastodon/locales/ar.json | 12 ++-- app/javascript/mastodon/locales/ca.json | 12 ++-- app/javascript/mastodon/locales/eu.json | 46 +++++++-------- app/javascript/mastodon/locales/ja.json | 2 +- app/javascript/mastodon/locales/nl.json | 14 ++--- app/javascript/mastodon/locales/pt-BR.json | 18 +++--- app/javascript/mastodon/locales/sk.json | 6 +- app/javascript/mastodon/locales/sv.json | 26 ++++----- config/locales/ar.yml | 2 +- config/locales/ca.yml | 16 ++++-- config/locales/gl.yml | 89 ++++++++++++++++++++++++++++-- config/locales/ja.yml | 5 +- config/locales/nl.yml | 8 ++- config/locales/pt-BR.yml | 8 ++- config/locales/simple_form.eu.yml | 26 +++++++++ config/locales/simple_form.pt-BR.yml | 6 ++ config/locales/sk.yml | 10 +++- config/locales/sv.yml | 60 +++++++++++++++++++- 18 files changed, 277 insertions(+), 89 deletions(-) diff --git a/app/javascript/mastodon/locales/ar.json b/app/javascript/mastodon/locales/ar.json index 7975ac1..947348f 100644 --- a/app/javascript/mastodon/locales/ar.json +++ b/app/javascript/mastodon/locales/ar.json @@ -40,7 +40,7 @@ "bundle_modal_error.retry": "إعادة المحاولة", "column.blocks": "الحسابات المحجوبة", "column.community": "الخيط العام المحلي", - "column.direct": "Direct messages", + "column.direct": "الرسائل المباشرة", "column.domain_blocks": "النطاقات المخفية", "column.favourites": "المفضلة", "column.follow_requests": "طلبات المتابعة", @@ -59,7 +59,7 @@ "column_header.unpin": "فك التدبيس", "column_subheading.navigation": "التصفح", "column_subheading.settings": "الإعدادات", - "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.", + "compose_form.direct_message_warning": "لن يَظهر هذا التبويق إلا للمستخدمين المذكورين.", "compose_form.hashtag_warning": "هذا التبويق لن يُدرَج تحت أي وسم كان بما أنه غير مُدرَج. لا يُسمح بالبحث إلّا عن التبويقات العمومية عن طريق الوسوم.", "compose_form.lock_disclaimer": "حسابك ليس {locked}. يمكن لأي شخص متابعتك و عرض المنشورات.", "compose_form.lock_disclaimer.lock": "مقفل", @@ -101,7 +101,7 @@ "emoji_button.symbols": "رموز", "emoji_button.travel": "أماكن و أسفار", "empty_column.community": "الخط الزمني المحلي فارغ. أكتب شيئا ما للعامة كبداية !", - "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.", + "empty_column.direct": "لم تتلق أية رسالة خاصة مباشِرة بعد. سوف يتم عرض الرسائل المباشرة هنا إن قمت بإرسال واحدة أو تلقيت البعض منها.", "empty_column.hashtag": "ليس هناك بعدُ أي محتوى ذو علاقة بهذا الوسم.", "empty_column.home": "إنك لا تتبع بعد أي شخص إلى حد الآن. زر {public} أو استخدام حقل البحث لكي تبدأ على التعرف على مستخدمين آخرين.", "empty_column.home.public_timeline": "الخيط العام", @@ -135,7 +135,7 @@ "keyboard_shortcuts.mention": "لذِكر الناشر", "keyboard_shortcuts.reply": "للردّ", "keyboard_shortcuts.search": "للتركيز على البحث", - "keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW", + "keyboard_shortcuts.toggle_hidden": "لعرض أو إخفاء النص مِن وراء التحذير", "keyboard_shortcuts.toot": "لتحرير تبويق جديد", "keyboard_shortcuts.unfocus": "لإلغاء التركيز على حقل النص أو نافذة البحث", "keyboard_shortcuts.up": "للإنتقال إلى أعلى القائمة", @@ -157,7 +157,7 @@ "mute_modal.hide_notifications": "هل تود إخفاء الإخطارات القادمة من هذا المستخدم ؟", "navigation_bar.blocks": "الحسابات المحجوبة", "navigation_bar.community_timeline": "الخيط العام المحلي", - "navigation_bar.direct": "Direct messages", + "navigation_bar.direct": "الرسائل المباشِرة", "navigation_bar.domain_blocks": "النطاقات المخفية", "navigation_bar.edit_profile": "تعديل الملف الشخصي", "navigation_bar.favourites": "المفضلة", @@ -242,7 +242,7 @@ "search_results.total": "{count, number} {count, plural, one {result} و {results}}", "standalone.public_title": "نظرة على ...", "status.block": "Block @{name}", - "status.cancel_reblog_private": "Unboost", + "status.cancel_reblog_private": "إلغاء الترقية", "status.cannot_reblog": "تعذرت ترقية هذا المنشور", "status.delete": "إحذف", "status.direct": "رسالة خاصة إلى @{name}", diff --git a/app/javascript/mastodon/locales/ca.json b/app/javascript/mastodon/locales/ca.json index 2e5004c..f2e3699 100644 --- a/app/javascript/mastodon/locales/ca.json +++ b/app/javascript/mastodon/locales/ca.json @@ -40,7 +40,7 @@ "bundle_modal_error.retry": "Torna-ho a provar", "column.blocks": "Usuaris blocats", "column.community": "Línia de temps local", - "column.direct": "Direct messages", + "column.direct": "Missatges directes", "column.domain_blocks": "Dominis ocults", "column.favourites": "Favorits", "column.follow_requests": "Peticions per seguir-te", @@ -101,7 +101,7 @@ "emoji_button.symbols": "Símbols", "emoji_button.travel": "Viatges i Llocs", "empty_column.community": "La línia de temps local és buida. Escriu alguna cosa públicament per fer rodar la pilota!", - "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.", + "empty_column.direct": "Encara no tens missatges directes. Quan enviïs o rebis un, es mostrarà aquí.", "empty_column.hashtag": "Encara no hi ha res amb aquesta etiqueta.", "empty_column.home": "Encara no segueixes ningú. Visita {public} o fes cerca per començar i conèixer altres usuaris.", "empty_column.home.public_timeline": "la línia de temps pública", @@ -135,7 +135,7 @@ "keyboard_shortcuts.mention": "per esmentar l'autor", "keyboard_shortcuts.reply": "respondre", "keyboard_shortcuts.search": "per centrar la cerca", - "keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW", + "keyboard_shortcuts.toggle_hidden": "per a mostrar/amagar text sota CW", "keyboard_shortcuts.toot": "per a començar un toot nou de trinca", "keyboard_shortcuts.unfocus": "descentrar l'area de composició de text/cerca", "keyboard_shortcuts.up": "moure amunt en la llista", @@ -157,7 +157,7 @@ "mute_modal.hide_notifications": "Amagar notificacions d'aquest usuari?", "navigation_bar.blocks": "Usuaris bloquejats", "navigation_bar.community_timeline": "Línia de temps Local", - "navigation_bar.direct": "Direct messages", + "navigation_bar.direct": "Missatges directes", "navigation_bar.domain_blocks": "Dominis ocults", "navigation_bar.edit_profile": "Editar perfil", "navigation_bar.favourites": "Favorits", @@ -242,7 +242,7 @@ "search_results.total": "{count, number} {count, plural, un {result} altres {results}}", "standalone.public_title": "Una mirada a l'interior ...", "status.block": "Block @{name}", - "status.cancel_reblog_private": "Unboost", + "status.cancel_reblog_private": "Desfer l'impuls", "status.cannot_reblog": "Aquesta publicació no pot ser retootejada", "status.delete": "Esborrar", "status.direct": "Missatge directe @{name}", @@ -258,7 +258,7 @@ "status.pin": "Fixat en el perfil", "status.pinned": "Toot fixat", "status.reblog": "Impuls", - "status.reblog_private": "Boost to original audience", + "status.reblog_private": "Impulsar a l'audiència original", "status.reblogged_by": "{name} ha retootejat", "status.reply": "Respondre", "status.replyAll": "Respondre al tema", diff --git a/app/javascript/mastodon/locales/eu.json b/app/javascript/mastodon/locales/eu.json index 02b7bf7..49cdf56 100644 --- a/app/javascript/mastodon/locales/eu.json +++ b/app/javascript/mastodon/locales/eu.json @@ -1,31 +1,31 @@ { - "account.block": "Block @{name}", - "account.block_domain": "Hide everything from {domain}", + "account.block": "Blokeatu @{name}", + "account.block_domain": "{domain}(e)ko guztia ezkutatu", "account.blocked": "Blokeatuta", - "account.direct": "Direct message @{name}", - "account.disclaimer_full": "Information below may reflect the user's profile incompletely.", - "account.domain_blocked": "Domain hidden", - "account.edit_profile": "Edit profile", - "account.follow": "Follow", - "account.followers": "Followers", - "account.follows": "Follows", - "account.follows_you": "Follows you", - "account.hide_reblogs": "Hide boosts from @{name}", + "account.direct": "@{name}(e)ri mezu zuzena bidali", + "account.disclaimer_full": "Baliteke beheko informazioak erabiltzailearen profilaren zati bat baino ez erakustea.", + "account.domain_blocked": "Ezkutatutako domeinua", + "account.edit_profile": "Profila aldatu", + "account.follow": "Jarraitu", + "account.followers": "Jarraitzaileak", + "account.follows": "Jarraitzen", + "account.follows_you": "Jarraitzen dizu", + "account.hide_reblogs": "@{name}(e)k sustatutakoak ezkutatu", "account.media": "Media", - "account.mention": "Mention @{name}", - "account.moved_to": "{name} has moved to:", - "account.mute": "Mute @{name}", - "account.mute_notifications": "Mute notifications from @{name}", - "account.muted": "Muted", + "account.mention": "@{name} aipatu", + "account.moved_to": "{name} hona lekualdatu da:", + "account.mute": "@{name} isilarazi", + "account.mute_notifications": "@{name}(e)ren jakinarazpenak isilarazi", + "account.muted": "Isilarazita", "account.posts": "Toots", "account.posts_with_replies": "Toots and replies", - "account.report": "Report @{name}", - "account.requested": "Awaiting approval. Click to cancel follow request", - "account.share": "Share @{name}'s profile", - "account.show_reblogs": "Show boosts from @{name}", - "account.unblock": "Unblock @{name}", - "account.unblock_domain": "Unhide {domain}", - "account.unfollow": "Unfollow", + "account.report": "@{name} salatu", + "account.requested": "Onarpenaren zain. Klikatu jarraitzeko eskaera ezeztatzeko", + "account.share": "@{name}(e)ren profila elkarbanatu", + "account.show_reblogs": "@{name}(e)k sustatutakoak erakutsi", + "account.unblock": "@{name} desblokeatu", + "account.unblock_domain": "Berriz erakutsi {domain}", + "account.unfollow": "Jarraitzeari utzi", "account.unmute": "Unmute @{name}", "account.unmute_notifications": "Unmute notifications from @{name}", "account.view_full_profile": "View full profile", diff --git a/app/javascript/mastodon/locales/ja.json b/app/javascript/mastodon/locales/ja.json index a06bdad..a5857c1 100644 --- a/app/javascript/mastodon/locales/ja.json +++ b/app/javascript/mastodon/locales/ja.json @@ -40,7 +40,7 @@ "bundle_modal_error.retry": "再試行", "column.blocks": "ブロックしたユーザー", "column.community": "ローカルタイムライン", - "column.direct": "Direct messages", + "column.direct": "ダイレクトメッセージ", "column.domain_blocks": "非表示にしたドメイン", "column.favourites": "お気に入り", "column.follow_requests": "フォローリクエスト", diff --git a/app/javascript/mastodon/locales/nl.json b/app/javascript/mastodon/locales/nl.json index 338b7ae..adc1d19 100644 --- a/app/javascript/mastodon/locales/nl.json +++ b/app/javascript/mastodon/locales/nl.json @@ -40,7 +40,7 @@ "bundle_modal_error.retry": "Opnieuw proberen", "column.blocks": "Geblokkeerde gebruikers", "column.community": "Lokale tijdlijn", - "column.direct": "Direct messages", + "column.direct": "Directe berichten", "column.domain_blocks": "Verborgen domeinen", "column.favourites": "Favorieten", "column.follow_requests": "Volgverzoeken", @@ -101,7 +101,7 @@ "emoji_button.symbols": "Symbolen", "emoji_button.travel": "Reizen en plekken", "empty_column.community": "De lokale tijdlijn is nog leeg. Toot iets in het openbaar om de bal aan het rollen te krijgen!", - "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.", + "empty_column.direct": "Je hebt nog geen directe berichten. Wanneer je er een verzend of ontvangt, zijn deze hier te zien.", "empty_column.hashtag": "Er is nog niks te vinden onder deze hashtag.", "empty_column.home": "Jij volgt nog niemand. Bezoek {public} of gebruik het zoekvenster om andere mensen te ontmoeten.", "empty_column.home.public_timeline": "de globale tijdlijn", @@ -127,7 +127,7 @@ "keyboard_shortcuts.compose": "om het tekstvak voor toots te focussen", "keyboard_shortcuts.description": "Omschrijving", "keyboard_shortcuts.down": "om naar beneden door de lijst te bewegen", - "keyboard_shortcuts.enter": "to open status", + "keyboard_shortcuts.enter": "om toot volledig te tonen", "keyboard_shortcuts.favourite": "om als favoriet te markeren", "keyboard_shortcuts.heading": "Sneltoetsen", "keyboard_shortcuts.hotkey": "Sneltoets", @@ -135,7 +135,7 @@ "keyboard_shortcuts.mention": "om de auteur te vermelden", "keyboard_shortcuts.reply": "om te reageren", "keyboard_shortcuts.search": "om het zoekvak te focussen", - "keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW", + "keyboard_shortcuts.toggle_hidden": "om tekst achter een waarschuwing (CW) te tonen/verbergen", "keyboard_shortcuts.toot": "om een nieuwe toot te starten", "keyboard_shortcuts.unfocus": "om het tekst- en zoekvak te ontfocussen", "keyboard_shortcuts.up": "om omhoog te bewegen in de lijst", @@ -157,7 +157,7 @@ "mute_modal.hide_notifications": "Verberg meldingen van deze persoon?", "navigation_bar.blocks": "Geblokkeerde gebruikers", "navigation_bar.community_timeline": "Lokale tijdlijn", - "navigation_bar.direct": "Direct messages", + "navigation_bar.direct": "Directe berichten", "navigation_bar.domain_blocks": "Verborgen domeinen", "navigation_bar.edit_profile": "Profiel bewerken", "navigation_bar.favourites": "Favorieten", @@ -242,7 +242,7 @@ "search_results.total": "{count, number} {count, plural, one {resultaat} other {resultaten}}", "standalone.public_title": "Een kijkje binnenin...", "status.block": "Blokkeer @{name}", - "status.cancel_reblog_private": "Unboost", + "status.cancel_reblog_private": "Niet meer boosten", "status.cannot_reblog": "Deze toot kan niet geboost worden", "status.delete": "Verwijderen", "status.direct": "Directe toot @{name}", @@ -258,7 +258,7 @@ "status.pin": "Aan profielpagina vastmaken", "status.pinned": "Vastgemaakte toot", "status.reblog": "Boost", - "status.reblog_private": "Boost to original audience", + "status.reblog_private": "Boost naar oorspronkelijke ontvangers", "status.reblogged_by": "{name} boostte", "status.reply": "Reageren", "status.replyAll": "Reageer op iedereen", diff --git a/app/javascript/mastodon/locales/pt-BR.json b/app/javascript/mastodon/locales/pt-BR.json index 7013822..7f8690f 100644 --- a/app/javascript/mastodon/locales/pt-BR.json +++ b/app/javascript/mastodon/locales/pt-BR.json @@ -40,8 +40,8 @@ "bundle_modal_error.retry": "Tente novamente", "column.blocks": "Usuários bloqueados", "column.community": "Local", - "column.direct": "Direct messages", - "column.domain_blocks": "Hidden domains", + "column.direct": "Mensagens diretas", + "column.domain_blocks": "Domínios escondidos", "column.favourites": "Favoritos", "column.follow_requests": "Seguidores pendentes", "column.home": "Página inicial", @@ -101,7 +101,7 @@ "emoji_button.symbols": "Símbolos", "emoji_button.travel": "Viagens & Lugares", "empty_column.community": "A timeline local está vazia. Escreva algo publicamente para começar!", - "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.", + "empty_column.direct": "Você não tem nenhuma mensagem direta ainda. Quando você enviar ou receber uma, as mensagens aparecerão por aqui.", "empty_column.hashtag": "Ainda não há qualquer conteúdo com essa hashtag.", "empty_column.home": "Você ainda não segue usuário algum. Visite a timeline {public} ou use o buscador para procurar e conhecer outros usuários.", "empty_column.home.public_timeline": "global", @@ -135,7 +135,7 @@ "keyboard_shortcuts.mention": "para mencionar o autor", "keyboard_shortcuts.reply": "para responder", "keyboard_shortcuts.search": "para focar a pesquisa", - "keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW", + "keyboard_shortcuts.toggle_hidden": "mostrar/esconder o texto com aviso de conteúdo", "keyboard_shortcuts.toot": "para compor um novo toot", "keyboard_shortcuts.unfocus": "para remover o foco da área de composição/pesquisa", "keyboard_shortcuts.up": "para mover para cima na lista", @@ -157,8 +157,8 @@ "mute_modal.hide_notifications": "Esconder notificações deste usuário?", "navigation_bar.blocks": "Usuários bloqueados", "navigation_bar.community_timeline": "Local", - "navigation_bar.direct": "Direct messages", - "navigation_bar.domain_blocks": "Hidden domains", + "navigation_bar.direct": "Mensagens diretas", + "navigation_bar.domain_blocks": "Domínios escondidos", "navigation_bar.edit_profile": "Editar perfil", "navigation_bar.favourites": "Favoritos", "navigation_bar.follow_requests": "Seguidores pendentes", @@ -242,10 +242,10 @@ "search_results.total": "{count, number} {count, plural, one {resultado} other {resultados}}", "standalone.public_title": "Dê uma espiada...", "status.block": "Block @{name}", - "status.cancel_reblog_private": "Unboost", + "status.cancel_reblog_private": "Retirar o compartilhamento", "status.cannot_reblog": "Esta postagem não pode ser compartilhada", "status.delete": "Excluir", - "status.direct": "Direct message @{name}", + "status.direct": "Enviar mensagem direta à @{name}", "status.embed": "Incorporar", "status.favourite": "Adicionar aos favoritos", "status.load_more": "Carregar mais", @@ -258,7 +258,7 @@ "status.pin": "Fixar no perfil", "status.pinned": "Toot fixado", "status.reblog": "Compartilhar", - "status.reblog_private": "Boost to original audience", + "status.reblog_private": "Compartilhar com a audiência original", "status.reblogged_by": "{name} compartilhou", "status.reply": "Responder", "status.replyAll": "Responder à sequência", diff --git a/app/javascript/mastodon/locales/sk.json b/app/javascript/mastodon/locales/sk.json index 8b19cac..1977fe5 100644 --- a/app/javascript/mastodon/locales/sk.json +++ b/app/javascript/mastodon/locales/sk.json @@ -40,7 +40,7 @@ "bundle_modal_error.retry": "Skúsiť znova", "column.blocks": "Blokovaní užívatelia", "column.community": "Lokálna časová os", - "column.direct": "Direct messages", + "column.direct": "Súkromné správy", "column.domain_blocks": "Skryté domény", "column.favourites": "Obľúbené", "column.follow_requests": "Žiadosti o sledovanie", @@ -101,7 +101,7 @@ "emoji_button.symbols": "Symboly", "emoji_button.travel": "Cestovanie a miesta", "empty_column.community": "Lokálna časová os je prázdna. Napíšte niečo, aby sa to tu začalo hýbať!", - "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.", + "empty_column.direct": "Ešte nemáš žiadne súkromné správy. Keď nejakú pošleš, alebo dostaneš, ukáže sa tu.", "empty_column.hashtag": "Pod týmto hashtagom sa ešte nič nenachádza.", "empty_column.home": "Vaša lokálna osa je zatiaľ prázdna! Pre začiatok pozrite {public} alebo použite vyhľadávanie a nájdite tak ostatných používateľov.", "empty_column.home.public_timeline": "verejná časová os", @@ -157,7 +157,7 @@ "mute_modal.hide_notifications": "Skryť notifikácie od tohoto užívateľa?", "navigation_bar.blocks": "Blokovaní užívatelia", "navigation_bar.community_timeline": "Lokálna časová os", - "navigation_bar.direct": "Direct messages", + "navigation_bar.direct": "Súkromné správy", "navigation_bar.domain_blocks": "Skryté domény", "navigation_bar.edit_profile": "Upraviť profil", "navigation_bar.favourites": "Obľúbené", diff --git a/app/javascript/mastodon/locales/sv.json b/app/javascript/mastodon/locales/sv.json index 479fc9a..4efe88a 100644 --- a/app/javascript/mastodon/locales/sv.json +++ b/app/javascript/mastodon/locales/sv.json @@ -18,7 +18,7 @@ "account.mute_notifications": "Stäng av notifieringar från @{name}", "account.muted": "Nertystad", "account.posts": "Inlägg", - "account.posts_with_replies": "Toots med svar", + "account.posts_with_replies": "Toots och svar", "account.report": "Rapportera @{name}", "account.requested": "Inväntar godkännande. Klicka för att avbryta följförfrågan", "account.share": "Dela @{name}'s profil", @@ -29,7 +29,7 @@ "account.unmute": "Ta bort tystad @{name}", "account.unmute_notifications": "Återaktivera notifikationer från @{name}", "account.view_full_profile": "Visa hela profilen", - "alert.unexpected.message": "An unexpected error occurred.", + "alert.unexpected.message": "Ett oväntat fel uppstod.", "alert.unexpected.title": "Oops!", "boost_modal.combo": "Du kan trycka {combo} för att slippa denna nästa gång", "bundle_column_error.body": "Något gick fel när du laddade denna komponent.", @@ -40,8 +40,8 @@ "bundle_modal_error.retry": "Försök igen", "column.blocks": "Blockerade användare", "column.community": "Lokal tidslinje", - "column.direct": "Direct messages", - "column.domain_blocks": "Hidden domains", + "column.direct": "Direktmeddelande", + "column.domain_blocks": "Dolda domäner", "column.favourites": "Favoriter", "column.follow_requests": "Följ förfrågningar", "column.home": "Hem", @@ -59,7 +59,7 @@ "column_header.unpin": "Ångra fäst", "column_subheading.navigation": "Navigation", "column_subheading.settings": "Inställningar", - "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.", + "compose_form.direct_message_warning": "Denna toot kommer endast vara synlig för nämnda användare.", "compose_form.hashtag_warning": "Denna toot kommer inte att listas under någon hashtag eftersom den är onoterad. Endast offentliga toots kan sökas med hashtag.", "compose_form.lock_disclaimer": "Ditt konto är inte {locked}. Vemsomhelst kan följa dig och även se dina inlägg skrivna för endast dina följare.", "compose_form.lock_disclaimer.lock": "låst", @@ -101,7 +101,7 @@ "emoji_button.symbols": "Symboler", "emoji_button.travel": "Resor & Platser", "empty_column.community": "Den lokala tidslinjen är tom. Skriv något offentligt för att få bollen att rulla!", - "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.", + "empty_column.direct": "Du har inga direktmeddelanden än. När du skickar eller tar emot kommer den att dyka upp här.", "empty_column.hashtag": "Det finns inget i denna hashtag ännu.", "empty_column.home": "Din hemma-tidslinje är tom! Besök {public} eller använd sökning för att komma igång och träffa andra användare.", "empty_column.home.public_timeline": "den publika tidslinjen", @@ -135,7 +135,7 @@ "keyboard_shortcuts.mention": "att nämna författaren", "keyboard_shortcuts.reply": "att svara", "keyboard_shortcuts.search": "att fokusera sökfältet", - "keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW", + "keyboard_shortcuts.toggle_hidden": "att visa/gömma text bakom CW", "keyboard_shortcuts.toot": "att börja en helt ny toot", "keyboard_shortcuts.unfocus": "att avfokusera komponera text fält / sökfält", "keyboard_shortcuts.up": "att flytta upp i listan", @@ -157,8 +157,8 @@ "mute_modal.hide_notifications": "Dölj notifikationer från denna användare?", "navigation_bar.blocks": "Blockerade användare", "navigation_bar.community_timeline": "Lokal tidslinje", - "navigation_bar.direct": "Direct messages", - "navigation_bar.domain_blocks": "Hidden domains", + "navigation_bar.direct": "Direktmeddelanden", + "navigation_bar.domain_blocks": "Dolda domäner", "navigation_bar.edit_profile": "Redigera profil", "navigation_bar.favourites": "Favoriter", "navigation_bar.follow_requests": "Följförfrågningar", @@ -242,10 +242,10 @@ "search_results.total": "{count, number} {count, plural, ett {result} andra {results}}", "standalone.public_title": "En titt inuti...", "status.block": "Block @{name}", - "status.cancel_reblog_private": "Unboost", + "status.cancel_reblog_private": "Ta bort knuff", "status.cannot_reblog": "Detta inlägg kan inte knuffas", "status.delete": "Ta bort", - "status.direct": "Direct message @{name}", + "status.direct": "Direktmeddela @{name}", "status.embed": "Bädda in", "status.favourite": "Favorit", "status.load_more": "Ladda fler", @@ -258,7 +258,7 @@ "status.pin": "Fäst i profil", "status.pinned": "Fäst toot", "status.reblog": "Knuff", - "status.reblog_private": "Boost to original audience", + "status.reblog_private": "Knuffa till de ursprungliga åhörarna", "status.reblogged_by": "{name} knuffade", "status.reply": "Svara", "status.replyAll": "Svara på tråden", @@ -276,7 +276,7 @@ "tabs_bar.home": "Hem", "tabs_bar.local_timeline": "Lokal", "tabs_bar.notifications": "Meddelanden", - "tabs_bar.search": "Search", + "tabs_bar.search": "Sök", "ui.beforeunload": "Ditt utkast kommer att förloras om du lämnar Mastodon.", "upload_area.title": "Dra & släpp för att ladda upp", "upload_button.label": "Lägg till media", diff --git a/config/locales/ar.yml b/config/locales/ar.yml index b3cb71d..e9ca303 100644 --- a/config/locales/ar.yml +++ b/config/locales/ar.yml @@ -151,7 +151,7 @@ ar: memorialize_account: لقد قام %{name} بتحويل حساب %{target} إلى صفحة تذكارية promote_user: "%{name} قام بترقية المستخدم %{target}" reset_password_user: "%{name} لقد قام بإعادة تعيين الكلمة السرية الخاصة بـ %{target}" - resolve_report: قام %{name} بإلغاء التقرير المُرسَل مِن طرف %{target} + resolve_report: قام %{name} بحل التقرير %{target} silence_account: لقد قام %{name} بكتم حساب %{target} suspend_account: لقد قام %{name} بتعليق حساب %{target} unsilence_account: لقد قام %{name} بإلغاء الكتم عن حساب %{target} diff --git a/config/locales/ca.yml b/config/locales/ca.yml index 9e927f7..53f8be1 100644 --- a/config/locales/ca.yml +++ b/config/locales/ca.yml @@ -265,7 +265,7 @@ ca: action_taken_by: Mesures adoptades per are_you_sure: N'estàs segur? assign_to_self: Assignar-me - assigned: Assignar Moderador + assigned: Moderador assignat comment: none: Cap created_at: Reportat @@ -275,14 +275,14 @@ ca: mark_as_unresolved: Marcar sense resoldre notes: create: Afegir nota - create_and_resolve: Resoldre amb Nota - create_and_unresolve: Reobrir amb Nota + create_and_resolve: Resoldre amb nota + create_and_unresolve: Reobrir amb nota delete: Esborrar placeholder: Descriu les accions que s'han pres o qualsevol altra actualització d'aquest informe… nsfw: 'false': Mostra els fitxers multimèdia adjunts 'true': Amaga els fitxers multimèdia adjunts - reopen: Reobrir Informe + reopen: Reobrir informe report: 'Informe #%{id}' report_contents: Contingut reported_account: Compte reportat @@ -354,8 +354,8 @@ ca: back_to_account: Torna a la pàgina del compte batch: delete: Suprimeix - nsfw_off: NSFW OFF - nsfw_on: NSFW ON + nsfw_off: Marcar com a no sensible + nsfw_on: Marcar com a sensible execute: Executa failed_to_execute: No s'ha pogut executar media: @@ -684,6 +684,9 @@ ca: one: "%{count} vídeo" other: "%{count} vídeos" content_warning: 'Avís de contingut: %{warning}' + disallowed_hashtags: + one: 'conté una etiqueta no permesa: %{tags}' + other: 'conté les etiquetes no permeses: %{tags}' open_in_web: Obre en la web over_character_limit: Límit de caràcters de %{max} superat pin_errors: @@ -785,6 +788,7 @@ ca:

    Originalment adaptat des del Discourse privacy policy.

    title: "%{instance} Condicions del servei i política de privadesa" themes: + contrast: Alt contrast default: Mastodont time: formats: diff --git a/config/locales/gl.yml b/config/locales/gl.yml index 3f936c0..129d826 100644 --- a/config/locales/gl.yml +++ b/config/locales/gl.yml @@ -265,7 +265,7 @@ gl: action_taken_by: Acción tomada por are_you_sure: Está segura? assign_to_self: Asignarmo - assigned: Asignado ao Moderador + assigned: Moderador asignado comment: none: Nada created_at: Reportado @@ -276,7 +276,7 @@ gl: notes: create: Engadir nota create_and_resolve: Resolver con nota - create_and_unresolve: Votar a abrir con Nota + create_and_unresolve: Voltar a abrir con nota delete: Eliminar placeholder: Describir qué decisións foron tomadas, ou calquer actualización a este informe… nsfw: @@ -354,8 +354,8 @@ gl: back_to_account: Voltar a páxina da conta batch: delete: Eliminar - nsfw_off: NSFW apagado - nsfw_on: NSFW acendido + nsfw_off: Marcar como non sensible + nsfw_on: Marcar como sensible execute: Executar failed_to_execute: Fallou a execución media: @@ -684,6 +684,9 @@ gl: one: "%{count} vídeo" other: "%{count} vídeos" content_warning: 'Aviso sobre o contido: %{warning}' + disallowed_hashtags: + one: 'contiña unha etiqueta non permitida: %{tags}' + other: 'contiña etiquetas non permitidas: %{tags}' open_in_web: Abrir na web over_character_limit: Excedeu o límite de caracteres %{max} pin_errors: @@ -706,8 +709,86 @@ gl: reblogged: promocionada sensitive_content: Contido sensible terms: + body_html: | +

    Intimidade

    +

    Qué información recollemos?

    + +
      +
    • Información básica da conta: Si se rexistra en este servidor, pediráselle un nome de usuaria, un enderezo de correo electrónico e un contrasinal. De xeito adicional tamén poderá introducir información como un nome público e biografía, tamén subir unha fotografía de perfil e unha imaxe para a cabeceira. O nome de usuaria, o nome público, a biografía e as imaxes de perfil e cabeceira sempre se mostran publicamente.
    • +
    • Publicacións, seguimento e outra información pública: O listado das persoas que segue é un listado público, o mesmo acontece coas súas seguidoras. Cando evía unha mensaxe, a data e hora gárdanse así como o aplicativo que utilizou para enviar a mensaxe. As publicacións poderían conter ficheiros de medios anexos, como fotografías e vídeos. As publicacións públicas e as non listadas están dispoñibles de xeito público. Cando destaca unha publicación no seu perfil tamén é pública. As publicacións son enviadas as súas seguidoras, en algúns casos pode acontecer que estén en diferentes servidores e gárdanse copias neles. Cando elemina unha publicación tamén se envía as súas seguidoras. A acción de voltar a publicar ou marcar como favorita outra publicación sempre é pública.
    • +
    • Mensaxes directas e só para seguidoras: Todas as mensaxes gárdanse e procésanse no servidor. As mensaxes só para seguidoras son entregadas as súas seguidoras e as usuarias que son mencionadas en elas, e as mensaxes directas entréganse só as usuarias mencionadas en elas. En algúns casos esto implica que son entregadas a diferentes servidores e gárdanse copias alí. Facemos un esforzo sincero para limitar o acceso a esas publicacións só as persoas autorizadas, pero outros servidores poderían non ser tan escrupulosos. Polo tanto, é importante revisar os servidores onde se hospedan as súas seguidoras. Nos axustes pode activar a opción de aprovar ou rexeitar novas seguidoras de xeito manual. Teña en conta que a administración do servidor e todos os outros servidores implicados poden ver as mensaxes., e as destinatarias poderían facer capturas de pantalla, copiar e voltar a compartir as mensaxes. Non comparta información comprometida en Mastodon.
    • +
    • IPs e outros metadatos: Cando se conecta, gravamos o IP desde onde se conecta, así como o nome do aplicativo desde onde o fai. Todas as sesións conectadas están dispoñibles para revisar e revogar nos axustes. O último enderezo IP utilizado gárdase ate por 12 meses. Tamén poderiamos gardar informes do servidor que inclúan o enderezo IP de cada petición ao servidor.
    • +
    + +
    + +

    De qué xeito utilizamos os seus datos?

    + +

    Toda a información que recollemos podería ser utilizada dos seguintes xeitos:

    + +
      +
    • Para proporcionar a funcionabiliade básica de Mastodon. Só pode interactuar co contido de outra xente e publicar o seu propio contido si está conectada. Por exemplo, podería seguir outra xente e ver as súas publicacións combinadas nunha liña temporal inicial personalizada.
    • +
    • Para axudar a moderar a comunidade, por exemplo comparando o seu enderezo IP con outros coñecidos para evitar esquivar os rexeitamentos ou outras infraccións.
    • +
    • O endero de correo electrónico que nos proporciona podería ser utilizado para enviarlle información, notificacións sobre outra xente que interactúa cos seus contidos ou lle envía mensaxes, e para respostar a consultas, e/ou outras cuestións ou peticións.
    • +
    + +
    + +

    Cómo proxetemos os seus datos?

    + +

    Implementamos varias medidas de seguridade para protexer os seus datos personais cando introduce, envía ou accede a súa información personal. Entre outras medidas, a súa sesión de navegación, así como o tráfico entre os seus aplicativos e o API están aseguradas mediante SSL, e o seu contrasinal está camuflado utilizando un algoritmo potente de unha sóa vía. Pode habilitar a autenticación de doble factor para protexer o acceso a súa conta aínda máis.

    + +
    + +

    Cal é a nosa política de retención de datos?

    + +

    Faremos un sincero esforzo en:

    + +
      +
    • Protexer informes do servidor que conteñan direccións IP das peticións ao servidor, ate a data estos informes gárdanse por non máis de 90 días.
    • +
    • Reter os enderezos IP asociados con usuarias rexistradas non máis de 12 meses.
    • +
    + +

    Pode solicitar e descargar un ficheiro cos seus contidos, incluíndo publicacións, anexos de medios, imaxes de perfil e imaxe da cabeceira.

    + +

    En calquer momento pode eliminar de xeito irreversible a súa conta.

    + +
    + +

    Utilizamos testemuños?

    + +

    Si. Os testemuños son pequenos ficheiros que un sitio web ou o provedor de servizo transfiren ao disco duro da súa computadora a través do navegador web (si vostede o permite). Estos testemuños posibilitan ao sitio web recoñecer o seu navegador e, si ten unha conta rexistrada, asocialo con dita conta.

    + +

    Utilizamos testemuños para comprender e gardar as súas preferencias para futuras visitas.

    + +
    + +

    Entregamos algunha información a terceiras alleas?

    + +

    Non vendemos, negociamos ou transferimos de algún xeito a terceiras partes alleas a súa información identificativa persoal. Esto non inclúe terceiras partes de confianza que nos axudan a operar o sitio web, a xestionar a empresa, ou darlle servizo si esas partes aceptan manter esa información baixo confidencialidade. Poderiamos liberar esa información si cremos que eso da cumplimento axeitado a lei, reforza as políticas do noso sitio ou protexe os nosos, e de outros, dereitos, propiedade ou seguridade.

    + +

    O seu contido público podería ser descargado por outros servidores na rede. As súas publicacións públicas e para só seguidoras son entregadas aos servidores onde residen as súas seguidoras na rede, e as mensaxes directas son entregadas aos servidores das destinatarias sempre que esas seguidoras ou destinatarios residan en servidores distintos de este.

    + +

    Cado autoriza a este aplicativo a utilizar a súa conta, dependendo da amplitude dos permisos que autorice, podería acceder a información pública de perfil, ao listado de seguimento, as súas seguidoras, os seus listados, todas as súas publicacións, as publicacións favoritas. Os aplicativos non poden nunca acceder ao seu enderezo de correo nin ao seu contrasinal.

    + +
    + +

    Children's Online Privacy Protection Act Compliance

    + +

    O noso sitio, productos e servizos diríxense a persoas que teñen un mínimo de 13 anos. Si este servidor está en EEUU, e ten vostede menos de 13 anos, a requerimento da COPPA (Children's Online Privacy Protection Act) non utilice este sitio.

    + +
    + +

    Cambios na nosa política de intimidade

    + +

    Si decidimos cambiar a nosa política de intimidade publicaremos os cambios en esta páxina.

    + +

    Este documento ten licenza CC-BY-SA. Actualizouse o 7 de Marzo de 2018.

    + +

    Adaptado do orixinal Discourse privacy policy.

    title: "%{instance} Termos do Servizo e Política de Intimidade" themes: + contrast: Alto contraste default: Mastodon time: formats: diff --git a/config/locales/ja.yml b/config/locales/ja.yml index aaebec6..aafd29f 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -278,7 +278,7 @@ ja: create_and_resolve: 書き込み、解決済みにする create_and_unresolve: 書き込み、未解決として開く delete: 削除 - placeholder: このレポートに取られた措置やその他更新を記述してください + placeholder: このレポートに取られた措置や、その他の更新を記述してください… nsfw: 'false': NSFW オフ 'true': NSFW オン @@ -684,6 +684,9 @@ ja: one: "%{count} 本の動画" other: "%{count} 本の動画" content_warning: '閲覧注意: %{warning}' + disallowed_hashtags: + one: '許可されていないハッシュタグが含まれています: %{tags}' + other: '許可されていないハッシュタグが含まれています: %{tags}' open_in_web: Webで開く over_character_limit: 上限は %{max}文字までです pin_errors: diff --git a/config/locales/nl.yml b/config/locales/nl.yml index 64bc718..7a488bb 100644 --- a/config/locales/nl.yml +++ b/config/locales/nl.yml @@ -354,8 +354,8 @@ nl: back_to_account: Terug naar accountpagina batch: delete: Verwijderen - nsfw_off: NSFW UIT - nsfw_on: NSFW AAN + nsfw_off: Als niet gevoelig markeren + nsfw_on: Als gevoelig markeren execute: Uitvoeren failed_to_execute: Uitvoeren mislukt media: @@ -684,6 +684,9 @@ nl: one: "%{count} video" other: "%{count} video's" content_warning: 'Tekstwaarschuwing: %{warning}' + disallowed_hashtags: + one: 'bevatte een niet toegestane hashtag: %{tags}' + other: 'bevatte niet toegestane hashtags: %{tags}' open_in_web: In de webapp openen over_character_limit: Limiet van %{max} tekens overschreden pin_errors: @@ -785,6 +788,7 @@ nl:

    Originally adapted from the Discourse privacy policy.

    title: "%{instance} Terms of Service and Privacy Policy" themes: + contrast: Hoog contrast default: Mastodon time: formats: diff --git a/config/locales/pt-BR.yml b/config/locales/pt-BR.yml index aeafe77..c3620b4 100644 --- a/config/locales/pt-BR.yml +++ b/config/locales/pt-BR.yml @@ -354,8 +354,8 @@ pt-BR: back_to_account: Voltar para página da conta batch: delete: Deletar - nsfw_off: NSFW ATIVADO - nsfw_on: NSFW DESATIVADO + nsfw_off: Marcar como não-sensível + nsfw_on: Marcar como sensível execute: Executar failed_to_execute: Falha em executar media: @@ -683,6 +683,9 @@ pt-BR: one: "%{count} vídeo" other: "%{count} vídeos" content_warning: 'Aviso de conteúdo: %{warning}' + disallowed_hashtags: + one: 'continha a hashtag não permitida: %{tags}' + other: 'continha as hashtags não permitidas: %{tags}' open_in_web: Abrir na web over_character_limit: limite de caracteres de %{max} excedido pin_errors: @@ -707,6 +710,7 @@ pt-BR: terms: title: "%{instance} Termos de Serviço e Política de Privacidade" themes: + contrast: Alto contraste default: Mastodon time: formats: diff --git a/config/locales/simple_form.eu.yml b/config/locales/simple_form.eu.yml index 9664357..d856fea 100644 --- a/config/locales/simple_form.eu.yml +++ b/config/locales/simple_form.eu.yml @@ -4,3 +4,29 @@ eu: hints: defaults: avatar: PNG, GIF edo JPG. Gehienez 2MB. 400x400px neurrira eskalatuko da + locked: Jarraitzaileak eskuz onartu behar dituzu + note: + other: %{count} karaktere faltan + setting_noindex: Zure profil publiko eta egoera orrietan eragina du + setting_theme: Edozein gailutik konektatzean Mastodon-en itxuran eragiten du. + imports: + data: Mastodon-en beste instantzia batetik CSV fitxategia esportatu da + user: + filtered_languages: Aukeratutako hizkuntzak timeline publikotik filtratuko dira + labels: + account: + fields: + name: Etiketa + value: Edukia + defaults: + confirm_new_password: Pasahitz berria berretsi + confirm_password: Pasahitza berretsi + current_password: Oraingo pasahitza + display_name: Izena erakutsi + email: Helbide elektronikoa + fields: Profilaren metadatuak + filtered_languages: Iragazitako hizkuntzak + locale: Hizkuntza + new_password: Pasahitz berria + note: Bio + password: Pasahitza diff --git a/config/locales/simple_form.pt-BR.yml b/config/locales/simple_form.pt-BR.yml index 0c22b26..cae1f67 100644 --- a/config/locales/simple_form.pt-BR.yml +++ b/config/locales/simple_form.pt-BR.yml @@ -8,6 +8,7 @@ pt-BR: display_name: one: 1 caracter restante other: %{count} caracteres restantes + fields: Você pode ter até 4 itens exibidos em forma de tabela no seu perfil header: PNG, GIF or JPG. Arquivos de até 2MB. Eles serão diminuídos para 700x335px locked: Requer aprovação manual de seguidores note: @@ -22,6 +23,10 @@ pt-BR: user: filtered_languages: Selecione os idiomas que devem ser removidos de suas timelines públicas labels: + account: + fields: + name: Rótulo + value: Conteúdo defaults: avatar: Avatar confirm_new_password: Confirmar nova senha @@ -31,6 +36,7 @@ pt-BR: display_name: Nome de exibição email: Endereço de e-mail expires_in: Expira em + fields: Metadados do perfil filtered_languages: Idiomas filtrados header: Cabeçalho locale: Idioma diff --git a/config/locales/sk.yml b/config/locales/sk.yml index a38b245..e9fa55a 100644 --- a/config/locales/sk.yml +++ b/config/locales/sk.yml @@ -146,6 +146,8 @@ sk: web: Web action_logs: actions: + assigned_to_self_report: "%{name}pridelil/a hlásenie užívateľa %{target}sebe" + change_email_user: "%{name} zmenil/a emailovú adresu užívateľa %{target}" confirm_user: "%{name} potvrdil e-mailovú adresu používateľa %{target}" create_custom_emoji: "%{name} nahral nový emoji %{target}" create_domain_block: "%{name} zablokoval doménu %{target}" @@ -162,6 +164,7 @@ sk: memorialize_account: '%{name} zmenil účet %{target} na stránku "Navždy budeme spomínať"' promote_user: "%{name} povýšil/a používateľa %{target}" remove_avatar_user: "%{name} odstránil/a %{target}ov avatár" + reopen_report: "%{name} znovu otvoril/a hlásenie užívateľa %{target}" reset_password_user: "%{name} resetoval/a heslo pre používateľa %{target}" resolve_report: "%{name} vyriešili nahlásenie užívateľa %{target}" silence_account: "%{name} utíšil/a účet %{target}" @@ -260,8 +263,11 @@ sk: report: nahlás action_taken_by: Zákrok vykonal/a are_you_sure: Ste si istý/á? + assign_to_self: '' + assigned: '' comment: none: Žiadne + created_at: '' delete: Vymazať id: Identifikácia mark_as_resolved: Označiť ako vyriešené @@ -336,8 +342,8 @@ sk: back_to_account: Späť na účet batch: delete: Vymazať - nsfw_off: Nevhodný obsah je vypnutý - nsfw_on: Nevhodný obsah je zapnutý + nsfw_off: Obsah nieje chúlostivý + nsfw_on: Označ obeah aka chúlostivý execute: Vykonať failed_to_execute: Nepodarilo sa vykonať media: diff --git a/config/locales/sv.yml b/config/locales/sv.yml index 9a559c0..6f648ab 100644 --- a/config/locales/sv.yml +++ b/config/locales/sv.yml @@ -4,6 +4,7 @@ sv: about_hashtag_html: Dessa är offentliga toots märkta med #%{hashtag}. Du kan interagera med dem om du har ett konto någonstans i federationen. about_mastodon_html: Mastodon är ett socialt nätverk baserat på öppna webbprotokoll och gratis, öppen källkodsprogramvara. Det är decentraliserat som e-post. about_this: Om + administered_by: 'Administreras av:' closed_registrations: Registreringar är för närvarande stängda i denna instans. Dock så kan du hitta en annan instans för att skapa ett konto och få tillgång till samma nätverk från det. contact: Kontakt contact_missing: Inte inställd @@ -60,7 +61,15 @@ sv: destroyed_msg: Modereringsnotering borttagen utan problem! accounts: are_you_sure: Är du säker? + avatar: Avatar by_domain: Domän + change_email: + changed_msg: E-postadressen har ändrats! + current_email: Nuvarande E-postadress + label: Byt E-postadress + new_email: Ny E-postadress + submit: Byt E-postadress + title: Byt E-postadress för %{username} confirm: Bekräfta confirmed: Bekräftad demote: Degradera @@ -108,6 +117,7 @@ sv: public: Offentlig push_subscription_expires: PuSH-prenumerationen löper ut redownload: Uppdatera avatar + remove_avatar: Ta bort avatar reset: Återställ reset_password: Återställ lösenord resubscribe: Starta en ny prenumeration @@ -128,6 +138,7 @@ sv: statuses: Status subscribe: Prenumerera title: Konton + unconfirmed_email: Obekräftad E-postadress undo_silenced: Ångra tystnad undo_suspension: Ångra avstängning unsubscribe: Avsluta prenumeration @@ -135,6 +146,8 @@ sv: web: Webb action_logs: actions: + assigned_to_self_report: "%{name} tilldelade anmälan %{target} till sig själv" + change_email_user: "%{name} bytte e-postadress för användare %{target}" confirm_user: "%{name} bekräftade e-postadress för användare %{target}" create_custom_emoji: "%{name} laddade upp ny emoji %{target}" create_domain_block: "%{name} blockerade domän %{target}" @@ -150,10 +163,13 @@ sv: enable_user: "%{name} aktiverade inloggning för användare %{target}" memorialize_account: "%{name} omvandlade %{target}s konto till en memoriam-sida" promote_user: "%{name} flyttade upp användare %{target}" + remove_avatar_user: "%{name} tog bort %{target}s avatar" + reopen_report: "%{name} återupptog anmälan %{target}" reset_password_user: "%{name} återställde lösenord för användaren %{target}" - resolve_report: "%{name} avvisade anmälan %{target}" + resolve_report: "%{name} löste anmälan %{target}" silence_account: "%{name} tystade ner %{target}s konto" suspend_account: "%{name} suspenderade %{target}s konto" + unassigned_report: "%{name} otilldelade anmälan %{target}" unsilence_account: "%{name} återljudade %{target}s konto" unsuspend_account: "%{name} aktiverade %{target}s konto" update_custom_emoji: "%{name} uppdaterade emoji %{target}" @@ -239,28 +255,48 @@ sv: expired: Utgångna title: Filtrera title: Inbjudningar + report_notes: + created_msg: Anmälningsanteckning har skapats! + destroyed_msg: Anmälningsanteckning har raderats! reports: + account: + note: anteckning + report: anmälan action_taken_by: Åtgärder vidtagna av are_you_sure: Är du säker? + assign_to_self: Tilldela till mig + assigned: Tilldelad moderator comment: none: Ingen + created_at: Anmäld delete: Radera id: ID mark_as_resolved: Markera som löst + mark_as_unresolved: Markera som olöst + notes: + create: Lägg till anteckning + create_and_resolve: Lös med anteckning + create_and_unresolve: Återuppta med anteckning + delete: Radera + placeholder: Beskriv vilka åtgärder som vidtagits eller andra uppdateringar till den här anmälan… nsfw: 'false': Visa bifogade mediafiler 'true': Dölj bifogade mediafiler + reopen: Återuppta anmälan report: 'Anmäl #%{id}' report_contents: Innehåll reported_account: Anmält konto reported_by: Anmäld av resolved: Löst + resolved_msg: Anmälan har lösts framgångsrikt! silence_account: Tysta ner konto status: Status suspend_account: Suspendera konto target: Mål title: Anmälningar + unassign: Otilldela unresolved: Olösta + updated_at: Uppdaterad view: Granska settings: activity_api_enabled: @@ -318,8 +354,8 @@ sv: back_to_account: Tillbaka till kontosidan batch: delete: Radera - nsfw_off: NSFW AV - nsfw_on: NSFW PÅ + nsfw_off: Markera som ej känslig + nsfw_on: Markera som känslig execute: Utför failed_to_execute: Misslyckades att utföra media: @@ -381,6 +417,7 @@ sv: security: Säkerhet set_new_password: Skriv in nytt lösenord authorize_follow: + already_following: Du följer redan detta konto error: Tyvärr inträffade ett fel när vi kontrollerade fjärrkontot follow: Följ follow_request: 'Du har skickat en följaförfrågan till:' @@ -473,6 +510,7 @@ sv: '21600': 6 timmar '3600': 1 timma '43200': 12 timmar + '604800': 1 vecka '86400': 1 dag expires_in_prompt: Aldrig generate: Skapa @@ -576,6 +614,10 @@ sv: missing_resource: Det gick inte att hitta den begärda omdirigeringsadressen för ditt konto proceed: Fortsätt för att följa prompt: 'Du kommer att följa:' + remote_unfollow: + error: Fel + title: Titel + unfollowed: Slutade följa sessions: activity: Senaste aktivitet browser: Webbläsare @@ -633,6 +675,18 @@ sv: two_factor_authentication: Tvåstegsautentisering your_apps: Dina applikationer statuses: + attached: + description: 'Bifogad: %{attached}' + image: + one: "%{count} bild" + other: "%{count} bilder" + video: + one: "%{count} video" + other: "%{count} videor" + content_warning: 'Innehållsvarning: %{warning}' + disallowed_hashtags: + one: 'innehöll en otillåten hashtag: %{tags}' + other: 'innehöll de otillåtna hashtagarna: %{tags}' open_in_web: Öppna på webben over_character_limit: teckengräns på %{max} har överskridits pin_errors: From 36b6631c1261f35ca22b9d2f9e99b72f83ed3492 Mon Sep 17 00:00:00 2001 From: Yamagishi Kazutoshi Date: Thu, 26 Apr 2018 20:56:45 +0900 Subject: [PATCH 196/381] Add Basque language support (#7267) --- app/helpers/settings_helper.rb | 1 + config/application.rb | 1 + 2 files changed, 2 insertions(+) diff --git a/app/helpers/settings_helper.rb b/app/helpers/settings_helper.rb index a2f5917..2252aab 100644 --- a/app/helpers/settings_helper.rb +++ b/app/helpers/settings_helper.rb @@ -9,6 +9,7 @@ module SettingsHelper de: 'Deutsch', eo: 'Esperanto', es: 'Español', + eu: 'Euskara', fa: 'فارسی', gl: 'Galego', fi: 'Suomi', diff --git a/config/application.rb b/config/application.rb index e989e23..ec3ff47 100644 --- a/config/application.rb +++ b/config/application.rb @@ -43,6 +43,7 @@ module Mastodon :de, :eo, :es, + :eu, :fa, :fi, :fr, From 63553c6b5c927950a45c5acb5af32af0dacee8c9 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Fri, 27 Apr 2018 01:37:59 +0200 Subject: [PATCH 197/381] Add support for separate Redis for cache (#7272) * Add support for separate Redis for cache CACHE_REDIS_URL to allow using a different Redis server for cache purposes, with cache-specific configuration such as key eviction * Fix code style issues --- config/environments/production.rb | 2 +- lib/mastodon/redis_config.rb | 27 ++++++++++++++++++++------- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/config/environments/production.rb b/config/environments/production.rb index e742f66..3023967 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -52,7 +52,7 @@ Rails.application.configure do config.log_tags = [:request_id] # Use a different cache store in production. - config.cache_store = :redis_store, ENV['REDIS_URL'], REDIS_CACHE_PARAMS + config.cache_store = :redis_store, ENV['CACHE_REDIS_URL'], REDIS_CACHE_PARAMS # Ignore bad email addresses and do not raise email delivery errors. # Set this to true and configure the email server for immediate delivery to raise delivery errors. diff --git a/lib/mastodon/redis_config.rb b/lib/mastodon/redis_config.rb index cf4f20f..f11d94a 100644 --- a/lib/mastodon/redis_config.rb +++ b/lib/mastodon/redis_config.rb @@ -1,16 +1,29 @@ # frozen_string_literal: true -if ENV['REDIS_URL'].blank? - password = ENV.fetch('REDIS_PASSWORD') { '' } - host = ENV.fetch('REDIS_HOST') { 'localhost' } - port = ENV.fetch('REDIS_PORT') { 6379 } - db = ENV.fetch('REDIS_DB') { 0 } +def setup_redis_env_url(prefix = nil, defaults = true) + prefix = prefix.to_s.upcase + '_' unless prefix.nil? + prefix = '' if prefix.nil? - ENV['REDIS_URL'] = "redis://#{password.blank? ? '' : ":#{password}@"}#{host}:#{port}/#{db}" + return if ENV[prefix + 'REDIS_URL'].present? + + password = ENV.fetch(prefix + 'REDIS_PASSWORD') { '' if defaults } + host = ENV.fetch(prefix + 'REDIS_HOST') { 'localhost' if defaults } + port = ENV.fetch(prefix + 'REDIS_PORT') { 6379 if defaults } + db = ENV.fetch(prefix + 'REDIS_DB') { 0 if defaults } + + ENV[prefix + 'REDIS_URL'] = if [password, host, port, db].all?(&:nil?) + ENV['REDIS_URL'] + else + "redis://#{password.blank? ? '' : ":#{password}@"}#{host}:#{port}/#{db}" + end end -namespace = ENV.fetch('REDIS_NAMESPACE') { nil } +setup_redis_env_url +setup_redis_env_url(:cache, false) + +namespace = ENV.fetch('REDIS_NAMESPACE') { nil } cache_namespace = namespace ? namespace + '_cache' : 'cache' + REDIS_CACHE_PARAMS = { expires_in: 10.minutes, namespace: cache_namespace, From a872392cd958167d5d9dd3fef613415cc9068774 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Fri, 27 Apr 2018 01:38:10 +0200 Subject: [PATCH 198/381] Add entity cache (#7271) * Add entity cache Use a caching layer for mentions and custom emojis that are dynamically extracted from text. Reduce duplicate text extractions * Fix code style issue --- app/lib/entity_cache.rb | 34 ++++++++++++++++++++++++++++++++++ app/lib/formatter.rb | 10 +++------- app/models/account.rb | 2 +- app/models/custom_emoji.rb | 10 +++++++++- app/models/status.rb | 2 +- 5 files changed, 48 insertions(+), 10 deletions(-) create mode 100644 app/lib/entity_cache.rb diff --git a/app/lib/entity_cache.rb b/app/lib/entity_cache.rb new file mode 100644 index 0000000..0c4edbf --- /dev/null +++ b/app/lib/entity_cache.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +require 'singleton' + +class EntityCache + include Singleton + + MAX_EXPIRATION = 7.days.freeze + + def mention(username, domain) + Rails.cache.fetch(to_key(:mention, username, domain), expires_in: MAX_EXPIRATION) { Account.select(:username, :domain, :url).find_remote(username, domain) } + end + + def emoji(shortcodes, domain) + shortcodes = [shortcodes] unless shortcodes.is_a?(Array) + cached = Rails.cache.read_multi(*shortcodes.map { |shortcode| to_key(:emoji, shortcode, domain) }) + uncached_ids = [] + + shortcodes.each do |shortcode| + uncached_ids << shortcode unless cached.key?(to_key(:emoji, shortcode, domain)) + end + + unless uncached_ids.empty? + uncached = CustomEmoji.where(shortcode: shortcodes, domain: domain, disabled: false).select(:shortcode, :id, :image_file_name, :visible_in_picker).map { |item| [item.shortcode, item] }.to_h + uncached.each_value { |item| Rails.cache.write(to_key(:emoji, item.shortcode, domain), item, expires_in: MAX_EXPIRATION) } + end + + shortcodes.map { |shortcode| cached[to_key(:emoji, shortcode, domain)] || uncached[shortcode] }.compact + end + + def to_key(type, *ids) + "#{type}:#{ids.compact.map(&:downcase).join(':')}" + end +end diff --git a/app/lib/formatter.rb b/app/lib/formatter.rb index 4124f16..050c651 100644 --- a/app/lib/formatter.rb +++ b/app/lib/formatter.rb @@ -52,12 +52,8 @@ class Formatter end def simplified_format(account, **options) - html = if account.local? - linkify(account.note) - else - reformat(account.note) - end - html = encode_custom_emojis(html, CustomEmoji.from_text(account.note, account.domain)) if options[:custom_emojify] + html = account.local? ? linkify(account.note) : reformat(account.note) + html = encode_custom_emojis(html, account.emojis) if options[:custom_emojify] html.html_safe # rubocop:disable Rails/OutputSafety end @@ -211,7 +207,7 @@ class Formatter username, domain = acct.split('@') domain = nil if TagManager.instance.local_domain?(domain) - account = Account.find_remote(username, domain) + account = EntityCache.instance.mention(username, domain) account ? mention_html(account) : "@#{acct}" end diff --git a/app/models/account.rb b/app/models/account.rb index ee47f04..647b5c3 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -391,7 +391,7 @@ class Account < ApplicationRecord end def emojis - CustomEmoji.from_text(note, domain) + @emojis ||= CustomEmoji.from_text(note, domain) end before_create :generate_keys diff --git a/app/models/custom_emoji.rb b/app/models/custom_emoji.rb index 8235332..b99ed01 100644 --- a/app/models/custom_emoji.rb +++ b/app/models/custom_emoji.rb @@ -42,6 +42,8 @@ class CustomEmoji < ApplicationRecord include Attachmentable + after_commit :remove_entity_cache + def local? domain.nil? end @@ -58,11 +60,17 @@ class CustomEmoji < ApplicationRecord return [] if shortcodes.empty? - where(shortcode: shortcodes, domain: domain, disabled: false) + EntityCache.instance.emoji(shortcodes, domain) end def search(shortcode) where('"custom_emojis"."shortcode" ILIKE ?', "%#{shortcode}%") end end + + private + + def remove_entity_cache + Rails.cache.delete(EntityCache.instance.to_key(:emoji, shortcode, domain)) + end end diff --git a/app/models/status.rb b/app/models/status.rb index 37f2db5..fbb1f89 100644 --- a/app/models/status.rb +++ b/app/models/status.rb @@ -160,7 +160,7 @@ class Status < ApplicationRecord end def emojis - CustomEmoji.from_text([spoiler_text, text].join(' '), account.domain) + @emojis ||= CustomEmoji.from_text([spoiler_text, text].join(' '), account.domain) end after_create_commit :store_uri, if: :local? From fc01ae31c60f68c629f33816a7aee9c32307ba00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Miko=C5=82ajczak?= Date: Fri, 27 Apr 2018 15:12:14 +0200 Subject: [PATCH 199/381] =?UTF-8?q?=F0=9F=8C=8D:=20=F0=9F=87=B5?= =?UTF-8?q?=F0=9F=87=B1=E2=AC=86=EF=B8=8F=20(#7275)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Marcin Mikołajczak --- config/locales/pl.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/config/locales/pl.yml b/config/locales/pl.yml index 71bf6bf..7455df2 100644 --- a/config/locales/pl.yml +++ b/config/locales/pl.yml @@ -697,6 +697,9 @@ pl: one: "%{count} film" other: "%{count} filmów" content_warning: 'Ostrzeżenie o zawartości: %{warning}' + disallowed_hashtags: + one: 'zawiera niedozwolony hashtag: %{tags}' + other: 'zawiera niedozwolone hashtagi: %{tags}' open_in_web: Otwórz w przeglądarce over_character_limit: limit %{max} znaków przekroczony pin_errors: @@ -798,6 +801,7 @@ pl:

    Bazowano na polityce prywatności Discourse.

    title: Zasady korzystania i polityka prywatności %{instance} themes: + contrast: Wysoki kontrast default: Mastodon time: formats: From 1c3ace23cbaa8590ab58ed0fd9d4d90ccb3d1eeb Mon Sep 17 00:00:00 2001 From: Yamagishi Kazutoshi Date: Sat, 28 Apr 2018 18:20:30 +0900 Subject: [PATCH 200/381] Remove unnecessary hyphen from restore_cache key (#7276) --- .circleci/config.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index fbaf60a..70d03f6 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -28,7 +28,7 @@ aliases: keys: - v2-ruby-dependencies-{{ checksum "/tmp/.ruby-version" }}-{{ checksum "Gemfile.lock" }} - v2-ruby-dependencies-{{ checksum "/tmp/.ruby-version" }}- - - v2-ruby-dependencies-- + - v2-ruby-dependencies- - &install_steps steps: @@ -83,7 +83,7 @@ aliases: keys: - precompiled-assets-{{ .Branch }}-{{ .Revision }} - precompiled-assets-{{ .Branch }}- - - precompiled-assets-- + - precompiled-assets- - run: name: Prepare Tests From da61352fab2e59ba42caf59d4e2e33d62eebe060 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 30 Apr 2018 01:59:42 +0200 Subject: [PATCH 201/381] Fix "Show more" URL on paginated threads for remote statuses (#7285) * Fix URL of "Show more" link in paginated threads (ancestors side) Increase item limits in threads Fix #7268 * Fix "Show more" link in paginated threads (descendants side) --- app/controllers/statuses_controller.rb | 11 ++++++----- app/controllers/stream_entries_controller.rb | 1 + app/views/stream_entries/_status.html.haml | 20 +++++++++++--------- 3 files changed, 18 insertions(+), 14 deletions(-) diff --git a/app/controllers/statuses_controller.rb b/app/controllers/statuses_controller.rb index 01dac35..645995c 100644 --- a/app/controllers/statuses_controller.rb +++ b/app/controllers/statuses_controller.rb @@ -4,9 +4,9 @@ class StatusesController < ApplicationController include SignatureAuthentication include Authorization - ANCESTORS_LIMIT = 20 - DESCENDANTS_LIMIT = 20 - DESCENDANTS_DEPTH_LIMIT = 4 + ANCESTORS_LIMIT = 40 + DESCENDANTS_LIMIT = 60 + DESCENDANTS_DEPTH_LIMIT = 20 layout 'public' @@ -71,7 +71,7 @@ class StatusesController < ApplicationController end def set_descendants - @max_descendant_thread_id = params[:max_descendant_thread_id]&.to_i + @max_descendant_thread_id = params[:max_descendant_thread_id]&.to_i @since_descendant_thread_id = params[:since_descendant_thread_id]&.to_i descendants = cache_collection( @@ -84,11 +84,12 @@ class StatusesController < ApplicationController ), Status ) + @descendant_threads = [] if descendants.present? statuses = [descendants.first] - depth = 1 + depth = 1 descendants.drop(1).each_with_index do |descendant, index| if descendants[index].id == descendant.in_reply_to_id diff --git a/app/controllers/stream_entries_controller.rb b/app/controllers/stream_entries_controller.rb index 97cf850..8568b15 100644 --- a/app/controllers/stream_entries_controller.rb +++ b/app/controllers/stream_entries_controller.rb @@ -23,6 +23,7 @@ class StreamEntriesController < ApplicationController skip_session! expires_in 3.minutes, public: true end + render xml: OStatus::AtomSerializer.render(OStatus::AtomSerializer.new.entry(@stream_entry, true)) end end diff --git a/app/views/stream_entries/_status.html.haml b/app/views/stream_entries/_status.html.haml index 8decdf6..9764bc7 100644 --- a/app/views/stream_entries/_status.html.haml +++ b/app/views/stream_entries/_status.html.haml @@ -5,18 +5,19 @@ is_successor ||= false direct_reply_id ||= false parent_id ||= false - is_direct_parent = direct_reply_id == status.id - is_direct_child = parent_id == status.in_reply_to_id - centered ||= include_threads && !is_predecessor && !is_successor - h_class = microformats_h_class(status, is_predecessor, is_successor, include_threads) - style_classes = style_classes(status, is_predecessor, is_successor, include_threads) - mf_classes = microformats_classes(status, is_direct_parent, is_direct_child) - entry_classes = h_class + ' ' + mf_classes + ' ' + style_classes + is_direct_parent = direct_reply_id == status.id + is_direct_child = parent_id == status.in_reply_to_id + centered ||= include_threads && !is_predecessor && !is_successor + h_class = microformats_h_class(status, is_predecessor, is_successor, include_threads) + style_classes = style_classes(status, is_predecessor, is_successor, include_threads) + mf_classes = microformats_classes(status, is_direct_parent, is_direct_child) + entry_classes = h_class + ' ' + mf_classes + ' ' + style_classes - if status.reply? && include_threads - if @next_ancestor .entry{ class: entry_classes } - = render 'stream_entries/more', url: short_account_status_url(@next_ancestor.account.username, @next_ancestor) + = render 'stream_entries/more', url: TagManager.instance.url_for(@next_ancestor) + = render partial: 'stream_entries/status', collection: @ancestors, as: :status, locals: { is_predecessor: true, direct_reply_id: status.in_reply_to_id } .entry{ class: entry_classes } @@ -44,9 +45,10 @@ = render 'stream_entries/more', url: short_account_status_url(status.account.username, status, max_descendant_thread_id: @since_descendant_thread_id + 1) - @descendant_threads.each do |thread| = render partial: 'stream_entries/status', collection: thread[:statuses], as: :status, locals: { is_successor: true, parent_id: status.id } + - if thread[:next_status] .entry{ class: entry_classes } - = render 'stream_entries/more', url: short_account_status_url(thread[:next_status].account.username, thread[:next_status]) + = render 'stream_entries/more', url: TagManager.instance.url_for(thread[:next_status]) - if @next_descendant_thread .entry{ class: entry_classes } = render 'stream_entries/more', url: short_account_status_url(status.account.username, status, since_descendant_thread_id: @max_descendant_thread_id - 1) From 54f34d3f2ab4dbe3a4b572d60af18a4b49358ec6 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 30 Apr 2018 09:12:36 +0200 Subject: [PATCH 202/381] Return HTTP 410 for suspended accounts in GET /api/v1/accounts/:id (#7287) Fix #7243 --- app/controllers/api/v1/accounts_controller.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/controllers/api/v1/accounts_controller.rb b/app/controllers/api/v1/accounts_controller.rb index d643259..b7133ca 100644 --- a/app/controllers/api/v1/accounts_controller.rb +++ b/app/controllers/api/v1/accounts_controller.rb @@ -5,6 +5,7 @@ class Api::V1::AccountsController < Api::BaseController before_action -> { doorkeeper_authorize! :follow }, only: [:follow, :unfollow, :block, :unblock, :mute, :unmute] before_action :require_user!, except: [:show] before_action :set_account + before_action :check_account_suspension, only: [:show] respond_to :json @@ -54,4 +55,8 @@ class Api::V1::AccountsController < Api::BaseController def relationships(**options) AccountRelationshipsPresenter.new([@account.id], current_user.account_id, options) end + + def check_account_suspension + gone if @account.suspended? + end end From 295e3ef02bb3fcdd4d8992ad6105c0ada2b3db0c Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 30 Apr 2018 09:12:55 +0200 Subject: [PATCH 203/381] Fix missing domain attribute in EntityCache for emoji (#7290) --- app/lib/entity_cache.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/lib/entity_cache.rb b/app/lib/entity_cache.rb index 0c4edbf..03bfb7c 100644 --- a/app/lib/entity_cache.rb +++ b/app/lib/entity_cache.rb @@ -21,7 +21,7 @@ class EntityCache end unless uncached_ids.empty? - uncached = CustomEmoji.where(shortcode: shortcodes, domain: domain, disabled: false).select(:shortcode, :id, :image_file_name, :visible_in_picker).map { |item| [item.shortcode, item] }.to_h + uncached = CustomEmoji.where(shortcode: shortcodes, domain: domain, disabled: false).select(:shortcode, :id, :domain, :image_file_name, :visible_in_picker).map { |item| [item.shortcode, item] }.to_h uncached.each_value { |item| Rails.cache.write(to_key(:emoji, item.shortcode, domain), item, expires_in: MAX_EXPIRATION) } end From f62ee1ddb0364d749e9df5559a243ebe3570cd2a Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 30 Apr 2018 09:13:14 +0200 Subject: [PATCH 204/381] Disable API access when login is disabled (#7289) --- app/controllers/api/base_controller.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/controllers/api/base_controller.rb b/app/controllers/api/base_controller.rb index 7b5168b..b5c084e 100644 --- a/app/controllers/api/base_controller.rb +++ b/app/controllers/api/base_controller.rb @@ -66,8 +66,10 @@ class Api::BaseController < ApplicationController end def require_user! - if current_user + if current_user && !current_user.disabled? set_user_activity + elsif current_user + render json: { error: 'Your login is currently disabled' }, status: 403 else render json: { error: 'This method requires an authenticated user' }, status: 422 end From 16468bdf1bf04ecab18ebd89dcded35ad0899439 Mon Sep 17 00:00:00 2001 From: Yamagishi Kazutoshi Date: Mon, 30 Apr 2018 20:14:50 +0900 Subject: [PATCH 205/381] [i18n] Occitan update (#7294) * Translated new strings + few changes * First update * Almost complet Missing the changes in the privacy policy * Update simple_form.oc.yml * bundle exec i18n-tasks normalize * bundle exec i18n-tasks remove-unused --- app/javascript/mastodon/locales/oc.json | 38 ++++++++++++++--------------- config/locales/oc.yml | 43 +++++++++++++++++++++++++++++++-- config/locales/simple_form.oc.yml | 6 +++++ 3 files changed, 66 insertions(+), 21 deletions(-) diff --git a/app/javascript/mastodon/locales/oc.json b/app/javascript/mastodon/locales/oc.json index 66f3fa2..d4836e9 100644 --- a/app/javascript/mastodon/locales/oc.json +++ b/app/javascript/mastodon/locales/oc.json @@ -18,7 +18,7 @@ "account.mute_notifications": "Rescondre las notificacions de @{name}", "account.muted": "Mes en silenci", "account.posts": "Tuts", - "account.posts_with_replies": "Tuts amb responsas", + "account.posts_with_replies": "Tuts e responsas", "account.report": "Senhalar @{name}", "account.requested": "Invitacion mandada. Clicatz per anullar", "account.share": "Partejar lo perfil a @{name}", @@ -29,8 +29,8 @@ "account.unmute": "Quitar de rescondre @{name}", "account.unmute_notifications": "Mostrar las notificacions de @{name}", "account.view_full_profile": "Veire lo perfil complèt", - "alert.unexpected.message": "An unexpected error occurred.", - "alert.unexpected.title": "Oops!", + "alert.unexpected.message": "Una error s’es producha.", + "alert.unexpected.title": "Ops !", "boost_modal.combo": "Podètz botar {combo} per passar aquò lo còp que ven", "bundle_column_error.body": "Quicòm a fach mèuca pendent lo cargament d’aqueste compausant.", "bundle_column_error.retry": "Tornar ensajar", @@ -40,8 +40,8 @@ "bundle_modal_error.retry": "Tornar ensajar", "column.blocks": "Personas blocadas", "column.community": "Flux public local", - "column.direct": "Direct messages", - "column.domain_blocks": "Hidden domains", + "column.direct": "Messatges dirèctes", + "column.domain_blocks": "Domenis blocats", "column.favourites": "Favorits", "column.follow_requests": "Demandas d’abonament", "column.home": "Acuèlh", @@ -59,7 +59,7 @@ "column_header.unpin": "Despenjar", "column_subheading.navigation": "Navigacion", "column_subheading.settings": "Paramètres", - "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.", + "compose_form.direct_message_warning": "Aqueste tut serà pas que visibile pel monde mencionat.", "compose_form.hashtag_warning": "Aqueste tut serà pas ligat a cap etiqueta estant qu’es pas listat. Òm pas cercar que los tuts publics per etiqueta.", "compose_form.lock_disclaimer": "Vòstre compte es pas {locked}. Tot lo mond pòt vos sègre e veire los estatuts reservats als seguidors.", "compose_form.lock_disclaimer.lock": "clavat", @@ -73,13 +73,13 @@ "compose_form.spoiler_placeholder": "Escrivètz l’avertiment aquí", "confirmation_modal.cancel": "Anullar", "confirmations.block.confirm": "Blocar", - "confirmations.block.message": "Sètz segur de voler blocar {name} ?", + "confirmations.block.message": "Volètz vertadièrament blocar {name} ?", "confirmations.delete.confirm": "Escafar", - "confirmations.delete.message": "Sètz segur de voler escafar l’estatut ?", + "confirmations.delete.message": "Volètz vertadièrament escafar l’estatut ?", "confirmations.delete_list.confirm": "Suprimir", - "confirmations.delete_list.message": "Sètz segur de voler suprimir aquesta lista per totjorn ?", + "confirmations.delete_list.message": "Volètz vertadièrament suprimir aquesta lista per totjorn ?", "confirmations.domain_block.confirm": "Amagar tot lo domeni", - "confirmations.domain_block.message": "Sètz segur segur de voler blocar complètament {domain} ? De còps cal pas que blocar o rescondre unas personas solament.", + "confirmations.domain_block.message": "Volètz vertadièrament blocar complètament {domain} ? De còps cal pas que blocar o rescondre unas personas solament.", "confirmations.mute.confirm": "Rescondre", "confirmations.mute.message": "Sètz segur de voler rescondre {name} ?", "confirmations.unfollow.confirm": "Quitar de sègre", @@ -101,7 +101,7 @@ "emoji_button.symbols": "Simbòls", "emoji_button.travel": "Viatges & lòcs", "empty_column.community": "Lo flux public local es void. Escrivètz quicòm per lo garnir !", - "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.", + "empty_column.direct": "Avètz pas encara de messatges. Quand ne mandatz un o que ne recebètz un, serà mostrat aquí.", "empty_column.hashtag": "I a pas encara de contengut ligat a aquesta etiqueta.", "empty_column.home": "Vòstre flux d’acuèlh es void. Visitatz {public} o utilizatz la recèrca per vos connectar a d’autras personas.", "empty_column.home.public_timeline": "lo flux public", @@ -135,7 +135,7 @@ "keyboard_shortcuts.mention": "mencionar l’autor", "keyboard_shortcuts.reply": "respondre", "keyboard_shortcuts.search": "anar a la recèrca", - "keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW", + "keyboard_shortcuts.toggle_hidden": "mostrar/amagar lo tèxte dels avertiments", "keyboard_shortcuts.toot": "començar un estatut tot novèl", "keyboard_shortcuts.unfocus": "quitar lo camp tèxte/de recèrca", "keyboard_shortcuts.up": "far montar dins la lista", @@ -157,7 +157,7 @@ "mute_modal.hide_notifications": "Rescondre las notificacions d’aquesta persona ?", "navigation_bar.blocks": "Personas blocadas", "navigation_bar.community_timeline": "Flux public local", - "navigation_bar.direct": "Direct messages", + "navigation_bar.direct": "Messatges dirèctes", "navigation_bar.domain_blocks": "Hidden domains", "navigation_bar.edit_profile": "Modificar lo perfil", "navigation_bar.favourites": "Favorits", @@ -217,7 +217,7 @@ "privacy.unlisted.short": "Pas-listat", "regeneration_indicator.label": "Cargament…", "regeneration_indicator.sublabel": "Sèm a preparar vòstre flux d’acuèlh !", - "relative_time.days": "fa {number} d", + "relative_time.days": "fa {number}d", "relative_time.hours": "fa {number}h", "relative_time.just_now": "ara", "relative_time.minutes": "fa {number} min", @@ -236,16 +236,16 @@ "search_popout.tips.status": "estatut", "search_popout.tips.text": "Lo tèxt brut tòrna escais, noms d’utilizaire e etiquetas correspondents", "search_popout.tips.user": "utilizaire", - "search_results.accounts": "Monde", + "search_results.accounts": "Gents", "search_results.hashtags": "Etiquetas", "search_results.statuses": "Tuts", "search_results.total": "{count, number} {count, plural, one {resultat} other {resultats}}", "standalone.public_title": "Una ulhada dedins…", "status.block": "Blocar @{name}", - "status.cancel_reblog_private": "Unboost", + "status.cancel_reblog_private": "Quitar de partejar", "status.cannot_reblog": "Aqueste estatut pòt pas èsser partejat", "status.delete": "Escafar", - "status.direct": "Direct message @{name}", + "status.direct": "Messatge per @{name}", "status.embed": "Embarcar", "status.favourite": "Apondre als favorits", "status.load_more": "Cargar mai", @@ -258,7 +258,7 @@ "status.pin": "Penjar al perfil", "status.pinned": "Tut penjat", "status.reblog": "Partejar", - "status.reblog_private": "Boost to original audience", + "status.reblog_private": "Partejar al l’audiéncia d’origina", "status.reblogged_by": "{name} a partejat", "status.reply": "Respondre", "status.replyAll": "Respondre a la conversacion", @@ -276,7 +276,7 @@ "tabs_bar.home": "Acuèlh", "tabs_bar.local_timeline": "Flux public local", "tabs_bar.notifications": "Notificacions", - "tabs_bar.search": "Search", + "tabs_bar.search": "Recèrcas", "ui.beforeunload": "Vòstre brolhon serà perdut se quitatz Mastodon.", "upload_area.title": "Lisatz e depausatz per mandar", "upload_button.label": "Ajustar un mèdia", diff --git a/config/locales/oc.yml b/config/locales/oc.yml index 195a1d9..c248ffd 100644 --- a/config/locales/oc.yml +++ b/config/locales/oc.yml @@ -4,6 +4,7 @@ oc: about_hashtag_html: Vaquí los estatuts publics ligats a #%{hashtag}. Podètz interagir amb eles s’avètz un compte ont que siasque sul fediverse. about_mastodon_html: Mastodon es un malhum social bastit amb de protocòls liures e gratuits. Es descentralizat coma los corrièls. about_this: A prepaus d’aquesta instància + administered_by: 'Gerida per :' closed_registrations: Las inscripcions son clavadas pel moment sus aquesta instància. contact: Contacte contact_missing: Pas parametrat @@ -60,7 +61,15 @@ oc: destroyed_msg: Nòta de moderacion ben suprimida ! accounts: are_you_sure: Sètz segur ? + avatar: Avatar by_domain: Domeni + change_email: + changed_msg: Adreça corrèctament cambiada ! + current_email: Adreça actuala + label: Cambiar d’adreça + new_email: Novèla adreça + submit: Cambiar + title: Cambiar l’adreça a %{username} confirm: Confirmar confirmed: Confirmat demote: Retrogradar @@ -108,6 +117,7 @@ oc: public: Public push_subscription_expires: Fin de l’abonament PuSH redownload: Actualizar los avatars + remove_avatar: Supriir l’avatar reset: Reïnicializar reset_password: Reïnicializar lo senhal resubscribe: Se tornar abonar @@ -128,6 +138,7 @@ oc: statuses: Estatuts subscribe: S’abonar title: Comptes + unconfirmed_email: Adreça pas confirmada undo_silenced: Levar lo silenci undo_suspension: Levar la suspension unsubscribe: Se desabonar @@ -135,6 +146,8 @@ oc: web: Web action_logs: actions: + assigned_to_self_report: "%{name} s’assignèt lo rapòrt %{target}" + change_email_user: "%{name} cambièt l’adreça de corrièl de %{target}" confirm_user: "%{name} confirmèt l’adreça a %{target}" create_custom_emoji: "%{name} mandèt un nòu emoji %{target}" create_domain_block: "%{name} bloquèt lo domeni %{target}" @@ -150,6 +163,7 @@ oc: enable_user: "%{name} activèt la connexion per %{target}" memorialize_account: "%{name} transformèt en memorial la pagina de perfil a %{target}" promote_user: "%{name} promoguèt %{target}" + remove_avatar_user: "%{name} suprimèt l’avatar a %{target}" reset_password_user: "%{name} reïnicializèt lo senhal a %{target}" resolve_report: "%{name} anullèt lo rapòrt de %{target}" silence_account: "%{name} metèt en silenci lo compte a %{target}" @@ -239,17 +253,31 @@ oc: expired: Expirats title: Filtre title: Convits + report_notes: + created_msg: Nòta de moderacion corrèctament creada ! + destroyed_msg: Nòta de moderacion corrèctament suprimida ! reports: + account: + note: nòta + report: rapòrt action_taken_by: Mesura menada per are_you_sure: Es segur ? comment: none: Pas cap + created_at: Creacion delete: Suprimir id: ID - mark_as_resolved: Marcat coma resolgut + mark_as_resolved: Marcar coma resolgut + mark_as_unresolved: Marcar coma pas resolgut + notes: + create: Ajustar una nòta + create_and_resolve: Resòlvre amb una nòta + create_and_unresolve: Tornar dobrir amb una nòta + placeholder: Explicatz las accions que son estadas menadas o çò qu’es estat fach per aqueste rapòrt… nsfw: 'false': Sens contengut sensible 'true': Contengut sensible activat + reopen: Tornar dobrir lo rapòrt report: 'senhalament #%{id}' report_contents: Contenguts reported_account: Compte senhalat @@ -381,6 +409,7 @@ oc: security: Seguretat set_new_password: Picar un nòu senhal authorize_follow: + already_following: Seguètz ja aqueste compte error: O planhèm, i a agut una error al moment de cercar lo compte follow: Sègre follow_request: 'Avètz demandat de sègre :' @@ -551,6 +580,7 @@ oc: '21600': 6 oras '3600': 1 ora '43200': 12 oras + '604800': 1 setmana '86400': 1 jorn expires_in_prompt: Jamai generate: Generar @@ -652,8 +682,12 @@ oc: remote_follow: acct: Picatz vòstre utilizaire@instància que cal utilizar per sègre aqueste utilizaire missing_resource: URL de redireccion pas trobada - proceed: Contunhatz per sègre + proceed: Clicatz per sègre prompt: 'Sètz per sègre :' + remote_unfollow: + error: Error + title: Títol + unfollowed: Pas mai seguit sessions: activity: Darrièra activitat browser: Navigator @@ -719,6 +753,9 @@ oc: video: one: "%{count} vidèo" other: "%{count} vidèos" + disallowed_hashtags: + one: 'conten una etiqueta desactivada : %{tags}' + other: 'conten las etiquetas desactivadas : %{tags}' open_in_web: Dobrir sul web over_character_limit: limit de %{max} caractèrs passat pin_errors: @@ -742,6 +779,8 @@ oc: sensitive_content: Contengut sensible terms: title: Condicions d’utilizacion e politica de confidencialitat de %{instance} + themes: + contrast: Fòrt contrast time: formats: default: Lo %d %b de %Y a %Ho%M diff --git a/config/locales/simple_form.oc.yml b/config/locales/simple_form.oc.yml index 690d1de..4ca58c1 100644 --- a/config/locales/simple_form.oc.yml +++ b/config/locales/simple_form.oc.yml @@ -8,6 +8,7 @@ oc: display_name: one: Demòra encara 1 caractèr other: Demòran encara %{count} caractèrs + fields: Podètz far veire cap a 4 elements sus vòstre perfil header: PNG, GIF o JPG. Maximum 2 Mo. Serà retalhada en 700x335px locked: Demanda qu’acceptetz manualament lo mond que vos sègon e botarà la visibilitat de vòstras publicacions coma accessiblas a vòstres seguidors solament note: @@ -22,6 +23,10 @@ oc: user: filtered_languages: Las lengas seleccionadas seràn levadas de vòstre flux d’actualitat labels: + account: + fields: + name: Nom + value: Contengut defaults: avatar: Avatar confirm_new_password: Confirmacion del nòu senhal @@ -31,6 +36,7 @@ oc: display_name: Escais email: Corrièl expires_in: Expira aprèp + fields: Metadonada del perfil filtered_languages: Lengas filtradas header: Bandièra locale: Lenga From 705f1d7bf15b7dc46256ab4a3bfff4075c79a8e7 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 30 Apr 2018 22:49:33 +0200 Subject: [PATCH 206/381] Fix missing updated_at attribute on emoji EntityCache (#7297) Just don't try to save space by only selecting few attributes. If anyone is wondering, this is needed because the emoji entity cache is not really only used for entities, it's accessed again to generate Emoji tags in ActivityPub/OStatus, so a lot more properties are used than what is needed in HTML alone... --- app/lib/entity_cache.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/lib/entity_cache.rb b/app/lib/entity_cache.rb index 03bfb7c..2aa3738 100644 --- a/app/lib/entity_cache.rb +++ b/app/lib/entity_cache.rb @@ -21,7 +21,7 @@ class EntityCache end unless uncached_ids.empty? - uncached = CustomEmoji.where(shortcode: shortcodes, domain: domain, disabled: false).select(:shortcode, :id, :domain, :image_file_name, :visible_in_picker).map { |item| [item.shortcode, item] }.to_h + uncached = CustomEmoji.where(shortcode: shortcodes, domain: domain, disabled: false).map { |item| [item.shortcode, item] }.to_h uncached.each_value { |item| Rails.cache.write(to_key(:emoji, item.shortcode, domain), item, expires_in: MAX_EXPIRATION) } end From 86efccce2a874d16aa783d989ff4824bcfac40b5 Mon Sep 17 00:00:00 2001 From: Lynx Kotoura Date: Tue, 1 May 2018 21:02:04 +0900 Subject: [PATCH 207/381] Fix low-contrasted cancel button of reply indicator (#7300) --- app/javascript/mastodon/features/compose/components/reply_indicator.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/javascript/mastodon/features/compose/components/reply_indicator.js b/app/javascript/mastodon/features/compose/components/reply_indicator.js index d8cda96..5b4b81e 100644 --- a/app/javascript/mastodon/features/compose/components/reply_indicator.js +++ b/app/javascript/mastodon/features/compose/components/reply_indicator.js @@ -51,7 +51,7 @@ export default class ReplyIndicator extends ImmutablePureComponent { return (
    -
    +
    From dc786c0cf4467ade8db7d8b17e09f16923bfc1e8 Mon Sep 17 00:00:00 2001 From: Surinna Curtis Date: Wed, 2 May 2018 05:40:24 -0500 Subject: [PATCH 208/381] Support Actors/Statuses with multiple types (#7305) * Add equals_or_includes_any? helper in JsonLdHelper * Support arrays in JSON-LD type fields for actors/tags/objects. * Spec for resolving accounts with extension types * Style tweaks for codeclimate --- app/helpers/jsonld_helper.rb | 4 ++++ app/lib/activitypub/activity/create.rb | 11 +++++------ app/lib/activitypub/activity/update.rb | 5 +---- app/services/activitypub/fetch_remote_account_service.rb | 2 +- app/services/activitypub/fetch_remote_key_service.rb | 4 ++-- app/services/activitypub/fetch_remote_status_service.rb | 2 +- app/services/activitypub/process_account_service.rb | 5 +---- app/services/fetch_atom_service.rb | 4 ++-- app/services/resolve_account_service.rb | 2 +- app/services/resolve_url_service.rb | 5 ++--- spec/fixtures/requests/activitypub-actor-individual.txt | 9 +++++++++ spec/services/resolve_account_service_spec.rb | 14 ++++++++++++++ 12 files changed, 43 insertions(+), 24 deletions(-) create mode 100644 spec/fixtures/requests/activitypub-actor-individual.txt diff --git a/app/helpers/jsonld_helper.rb b/app/helpers/jsonld_helper.rb index dfb8fcb..a3cfdad 100644 --- a/app/helpers/jsonld_helper.rb +++ b/app/helpers/jsonld_helper.rb @@ -5,6 +5,10 @@ module JsonLdHelper haystack.is_a?(Array) ? haystack.include?(needle) : haystack == needle end + def equals_or_includes_any?(haystack, needles) + needles.any? { |needle| equals_or_includes?(haystack, needle) } + end + def first_of_value(value) value.is_a?(Array) ? value.first : value end diff --git a/app/lib/activitypub/activity/create.rb b/app/lib/activitypub/activity/create.rb index 45c0e91..411286f 100644 --- a/app/lib/activitypub/activity/create.rb +++ b/app/lib/activitypub/activity/create.rb @@ -61,12 +61,11 @@ class ActivityPub::Activity::Create < ActivityPub::Activity return if @object['tag'].nil? as_array(@object['tag']).each do |tag| - case tag['type'] - when 'Hashtag' + if equals_or_includes?(tag['type'], 'Hashtag') process_hashtag tag, status - when 'Mention' + elsif equals_or_includes?(tag['type'], 'Mention') process_mention tag, status - when 'Emoji' + elsif equals_or_includes?(tag['type'], 'Emoji') process_emoji tag, status end end @@ -235,11 +234,11 @@ class ActivityPub::Activity::Create < ActivityPub::Activity end def supported_object_type? - SUPPORTED_TYPES.include?(@object['type']) + equals_or_includes_any?(@object['type'], SUPPORTED_TYPES) end def converted_object_type? - CONVERTED_TYPES.include?(@object['type']) + equals_or_includes_any?(@object['type'], CONVERTED_TYPES) end def skip_download? diff --git a/app/lib/activitypub/activity/update.rb b/app/lib/activitypub/activity/update.rb index 0134b40..47e98e0 100644 --- a/app/lib/activitypub/activity/update.rb +++ b/app/lib/activitypub/activity/update.rb @@ -2,10 +2,7 @@ class ActivityPub::Activity::Update < ActivityPub::Activity def perform - case @object['type'] - when 'Person' - update_account - end + update_account if equals_or_includes?(@object['type'], 'Person') end private diff --git a/app/services/activitypub/fetch_remote_account_service.rb b/app/services/activitypub/fetch_remote_account_service.rb index 5024853..867e708 100644 --- a/app/services/activitypub/fetch_remote_account_service.rb +++ b/app/services/activitypub/fetch_remote_account_service.rb @@ -56,6 +56,6 @@ class ActivityPub::FetchRemoteAccountService < BaseService end def expected_type? - SUPPORTED_TYPES.include?(@json['type']) + equals_or_includes_any?(@json['type'], SUPPORTED_TYPES) end end diff --git a/app/services/activitypub/fetch_remote_key_service.rb b/app/services/activitypub/fetch_remote_key_service.rb index 41837d4..505bacc 100644 --- a/app/services/activitypub/fetch_remote_key_service.rb +++ b/app/services/activitypub/fetch_remote_key_service.rb @@ -43,7 +43,7 @@ class ActivityPub::FetchRemoteKeyService < BaseService end def person? - ActivityPub::FetchRemoteAccountService::SUPPORTED_TYPES.include?(@json['type']) + equals_or_includes_any?(@json['type'], ActivityPub::FetchRemoteAccountService::SUPPORTED_TYPES) end def public_key? @@ -55,6 +55,6 @@ class ActivityPub::FetchRemoteKeyService < BaseService end def confirmed_owner? - ActivityPub::FetchRemoteAccountService::SUPPORTED_TYPES.include?(@owner['type']) && value_or_id(@owner['publicKey']) == @json['id'] + equals_or_includes_any?(@owner['type'], ActivityPub::FetchRemoteAccountService::SUPPORTED_TYPES) && value_or_id(@owner['publicKey']) == @json['id'] end end diff --git a/app/services/activitypub/fetch_remote_status_service.rb b/app/services/activitypub/fetch_remote_status_service.rb index 503c175..930fbad 100644 --- a/app/services/activitypub/fetch_remote_status_service.rb +++ b/app/services/activitypub/fetch_remote_status_service.rb @@ -42,7 +42,7 @@ class ActivityPub::FetchRemoteStatusService < BaseService end def expected_type? - (ActivityPub::Activity::Create::SUPPORTED_TYPES + ActivityPub::Activity::Create::CONVERTED_TYPES).include? @json['type'] + equals_or_includes_any?(@json['type'], ActivityPub::Activity::Create::SUPPORTED_TYPES + ActivityPub::Activity::Create::CONVERTED_TYPES) end def needs_update(actor) diff --git a/app/services/activitypub/process_account_service.rb b/app/services/activitypub/process_account_service.rb index da32f96..f67ebb4 100644 --- a/app/services/activitypub/process_account_service.rb +++ b/app/services/activitypub/process_account_service.rb @@ -201,10 +201,7 @@ class ActivityPub::ProcessAccountService < BaseService return if @json['tag'].blank? as_array(@json['tag']).each do |tag| - case tag['type'] - when 'Emoji' - process_emoji tag - end + process_emoji tag if equals_or_includes?(tag['type'], 'Emoji') end end diff --git a/app/services/fetch_atom_service.rb b/app/services/fetch_atom_service.rb index 0444baf..550e75f 100644 --- a/app/services/fetch_atom_service.rb +++ b/app/services/fetch_atom_service.rb @@ -42,7 +42,7 @@ class FetchAtomService < BaseService elsif ['application/activity+json', 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'].include?(response.mime_type) body = response.body_with_limit json = body_to_json(body) - if supported_context?(json) && ActivityPub::FetchRemoteAccountService::SUPPORTED_TYPES.include?(json['type']) && json['inbox'].present? + if supported_context?(json) && equals_or_includes_any?(json['type'], ActivityPub::FetchRemoteAccountService::SUPPORTED_TYPES) && json['inbox'].present? [json['id'], { prefetched_body: body, id: true }, :activitypub] elsif supported_context?(json) && expected_type?(json) [json['id'], { prefetched_body: body, id: true }, :activitypub] @@ -62,7 +62,7 @@ class FetchAtomService < BaseService end def expected_type?(json) - (ActivityPub::Activity::Create::SUPPORTED_TYPES + ActivityPub::Activity::Create::CONVERTED_TYPES).include? json['type'] + equals_or_includes_any?(json['type'], ActivityPub::Activity::Create::SUPPORTED_TYPES + ActivityPub::Activity::Create::CONVERTED_TYPES) end def process_html(response) diff --git a/app/services/resolve_account_service.rb b/app/services/resolve_account_service.rb index 8cba88f..de8d115 100644 --- a/app/services/resolve_account_service.rb +++ b/app/services/resolve_account_service.rb @@ -189,7 +189,7 @@ class ResolveAccountService < BaseService return @actor_json if defined?(@actor_json) json = fetch_resource(actor_url, false) - @actor_json = supported_context?(json) && ActivityPub::FetchRemoteAccountService::SUPPORTED_TYPES.include?(json['type']) ? json : nil + @actor_json = supported_context?(json) && equals_or_includes_any?(json['type'], ActivityPub::FetchRemoteAccountService::SUPPORTED_TYPES) ? json : nil end def atom diff --git a/app/services/resolve_url_service.rb b/app/services/resolve_url_service.rb index c19b568..a068c1e 100644 --- a/app/services/resolve_url_service.rb +++ b/app/services/resolve_url_service.rb @@ -16,10 +16,9 @@ class ResolveURLService < BaseService private def process_url - case type - when 'Application', 'Group', 'Organization', 'Person', 'Service' + if equals_or_includes_any?(type, %w(Application Group Organization Person Service)) FetchRemoteAccountService.new.call(atom_url, body, protocol) - when 'Note', 'Article', 'Image', 'Video' + elsif equals_or_includes_any?(type, %w(Note Article Image Video)) FetchRemoteStatusService.new.call(atom_url, body, protocol) end end diff --git a/spec/fixtures/requests/activitypub-actor-individual.txt b/spec/fixtures/requests/activitypub-actor-individual.txt new file mode 100644 index 0000000..74411e5 --- /dev/null +++ b/spec/fixtures/requests/activitypub-actor-individual.txt @@ -0,0 +1,9 @@ +HTTP/1.1 200 OK +Cache-Control: max-age=0, private, must-revalidate +Content-Type: application/activity+json; charset=utf-8 +Link: ; rel="lrdd"; type="application/xrd+xml", ; rel="alternate"; type="application/atom+xml", ; rel="alternate"; type="application/activity+json" +Vary: Accept-Encoding +X-Content-Type-Options: nosniff +X-Xss-Protection: 1; mode=block + +{"@context":["https://www.w3.org/ns/activitystreams","https://w3id.org/security/v1",{"vcard": "http://www.w3.org/2006/vcard/ns#"},{"manuallyApprovesFollowers":"as:manuallyApprovesFollowers","sensitive":"as:sensitive","Hashtag":"as:Hashtag","ostatus":"http://ostatus.org#","atomUri":"ostatus:atomUri","inReplyToAtomUri":"ostatus:inReplyToAtomUri","conversation":"ostatus:conversation"}],"id":"https://ap.example.com/users/foo","type":["Person","vcard:individual"],"following":"https://ap.example.com/users/foo/following","followers":"https://ap.example.com/users/foo/followers","inbox":"https://ap.example.com/users/foo/inbox","outbox":"https://ap.example.com/users/foo/outbox","preferredUsername":"foo","vcard:fn":"foo","name":"","summary":"\u003cp\u003etest\u003c/p\u003e","url":"https://ap.example.com/@foo","manuallyApprovesFollowers":false,"publicKey":{"id":"https://ap.example.com/users/foo#main-key","owner":"https://ap.example.com/users/foo","publicKeyPem":"-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu3L4vnpNLzVH31MeWI39\n4F0wKeJFsLDAsNXGeOu0QF2x+h1zLWZw/agqD2R3JPU9/kaDJGPIV2Sn5zLyUA9S\n6swCCMOtn7BBR9g9sucgXJmUFB0tACH2QSgHywMAybGfmSb3LsEMNKsGJ9VsvYoh\n8lDET6X4Pyw+ZJU0/OLo/41q9w+OrGtlsTm/PuPIeXnxa6BLqnDaxC+4IcjG/FiP\nahNCTINl/1F/TgSSDZ4Taf4U9XFEIFw8wmgploELozzIzKq+t8nhQYkgAkt64euW\npva3qL5KD1mTIZQEP+LZvh3s2WHrLi3fhbdRuwQ2c0KkJA2oSTFPDpqqbPGZ3Qvu\nHQIDAQAB\n-----END PUBLIC KEY-----\n"},"endpoints":{"sharedInbox":"https://ap.example.com/inbox"},"icon":{"type":"Image","url":"https://quitter.no/avatar/7477-300-20160211190340.png"}} \ No newline at end of file diff --git a/spec/services/resolve_account_service_spec.rb b/spec/services/resolve_account_service_spec.rb index 5f1b446..84dfe57 100644 --- a/spec/services/resolve_account_service_spec.rb +++ b/spec/services/resolve_account_service_spec.rb @@ -105,6 +105,20 @@ RSpec.describe ResolveAccountService do expect(account.inbox_url).to eq 'https://ap.example.com/users/foo/inbox' end + context 'with multiple types' do + before do + stub_request(:get, "https://ap.example.com/users/foo").to_return(request_fixture('activitypub-actor-individual.txt')) + end + + it 'returns new remote account' do + account = subject.call('foo@ap.example.com') + + expect(account.activitypub?).to eq true + expect(account.domain).to eq 'ap.example.com' + expect(account.inbox_url).to eq 'https://ap.example.com/users/foo/inbox' + end + end + pending end From 6c40e567aaa166eb618c316d1de2eb81b8eced9a Mon Sep 17 00:00:00 2001 From: Shuhei Kitagawa Date: Wed, 2 May 2018 21:13:52 +0900 Subject: [PATCH 209/381] Add missing tests for user.rb (#7306) --- dump.rdb | Bin 0 -> 851 bytes spec/models/user_spec.rb | 214 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 214 insertions(+) create mode 100644 dump.rdb diff --git a/dump.rdb b/dump.rdb new file mode 100644 index 0000000000000000000000000000000000000000..ade9c6abc4f911663879c263bd869f94e8649256 GIT binary patch literal 851 zcmbVKId2m|6dpT1$ib05hSD#DuR$1?>>`NW_RqFSww`8 z=umzL1r6dSSV8n@>1fkYn}Au{N(kTviW%w6`{wC8-q!ls#zR7gH<%R=M$&i=qG(k~ zwK15RJhUSv`^`ZyP*DsAH{Tv@56-0$LL-Lw07Z*vut0go=n9~5n?WD_K;P!_=dZ~q zgx#oT$Y2M;l!5RQc%fRODROlcA%wG&M`$S(l;r{NqMrT5xMYvG^27K*(z z0#)o#CZP}|9saR-*!)^~iFdeE35@~!cnY7&FG5(ySsFq?*rE#iHQTh!x_i}f+^Xd` zX5BJ%+>Js!A+BRZpJ@-uapoKkd7|B^1Uv~+As`9%uy5;lk|g#Y<4KlCY1d~)kv%OT z)=VzOqva72KhZIxZD37Zn=fT6o2W=>>t8N^c)edf@ke_%({S*sLK~=B98+^@4za6V X-6f`5Xs2?okA_>H?`?el#P@#!$ioff literal 0 HcmV?d00001 diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 8171c93..760214d 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -324,4 +324,218 @@ RSpec.describe User, type: :model do expect(admin.role?('moderator')).to be true end end + + describe '#disable!' do + subject(:user) { Fabricate(:user, disabled: false, current_sign_in_at: current_sign_in_at, last_sign_in_at: nil) } + let(:current_sign_in_at) { Time.zone.now } + + before do + user.disable! + end + + it 'disables user' do + expect(user).to have_attributes(disabled: true, current_sign_in_at: nil, last_sign_in_at: current_sign_in_at) + end + end + + describe '#disable!' do + subject(:user) { Fabricate(:user, disabled: false, current_sign_in_at: current_sign_in_at, last_sign_in_at: nil) } + let(:current_sign_in_at) { Time.zone.now } + + before do + user.disable! + end + + it 'disables user' do + expect(user).to have_attributes(disabled: true, current_sign_in_at: nil, last_sign_in_at: current_sign_in_at) + end + end + + describe '#enable!' do + subject(:user) { Fabricate(:user, disabled: true) } + + before do + user.enable! + end + + it 'enables user' do + expect(user).to have_attributes(disabled: false) + end + end + + describe '#confirm!' do + subject(:user) { Fabricate(:user, confirmed_at: confirmed_at) } + + before do + ActionMailer::Base.deliveries.clear + user.confirm! + end + + after { ActionMailer::Base.deliveries.clear } + + context 'when user is new' do + let(:confirmed_at) { nil } + + it 'confirms user' do + expect(user.confirmed_at).to be_present + end + + it 'delivers mails' do + expect(ActionMailer::Base.deliveries.count).to eq 2 + end + end + + context 'when user is not new' do + let(:confirmed_at) { Time.zone.now } + + it 'confirms user' do + expect(user.confirmed_at).to be_present + end + + it 'does not deliver mail' do + expect(ActionMailer::Base.deliveries.count).to eq 0 + end + end + end + + describe '#promote!' do + subject(:user) { Fabricate(:user, admin: is_admin, moderator: is_moderator) } + + before do + user.promote! + end + + context 'when user is an admin' do + let(:is_admin) { true } + + context 'when user is a moderator' do + let(:is_moderator) { true } + + it 'changes moderator filed false' do + expect(user).to be_admin + expect(user).not_to be_moderator + end + end + + context 'when user is not a moderator' do + let(:is_moderator) { false } + + it 'does not change status' do + expect(user).to be_admin + expect(user).not_to be_moderator + end + end + end + + context 'when user is not admin' do + let(:is_admin) { false } + + context 'when user is a moderator' do + let(:is_moderator) { true } + + it 'changes user into an admin' do + expect(user).to be_admin + expect(user).not_to be_moderator + end + end + + context 'when user is not a moderator' do + let(:is_moderator) { false } + + it 'changes user into a moderator' do + expect(user).not_to be_admin + expect(user).to be_moderator + end + end + end + end + + describe '#demote!' do + subject(:user) { Fabricate(:user, admin: admin, moderator: moderator) } + + before do + user.demote! + end + + context 'when user is an admin' do + let(:admin) { true } + + context 'when user is a moderator' do + let(:moderator) { true } + + it 'changes user into a moderator' do + expect(user).not_to be_admin + expect(user).to be_moderator + end + end + + context 'when user is not a moderator' do + let(:moderator) { false } + + it 'changes user into a moderator' do + expect(user).not_to be_admin + expect(user).to be_moderator + end + end + end + + context 'when user is not an admin' do + let(:admin) { false } + + context 'when user is a moderator' do + let(:moderator) { true } + + it 'changes user into a plain user' do + expect(user).not_to be_admin + expect(user).not_to be_moderator + end + end + + context 'when user is not a moderator' do + let(:moderator) { false } + + it 'does not change any fields' do + expect(user).not_to be_admin + expect(user).not_to be_moderator + end + end + end + end + + describe '#active_for_authentication?' do + subject { user.active_for_authentication? } + let(:user) { Fabricate(:user, disabled: disabled, confirmed_at: confirmed_at) } + + context 'when user is disabled' do + let(:disabled) { true } + + context 'when user is confirmed' do + let(:confirmed_at) { Time.zone.now } + + it { is_expected.to be false } + end + + context 'when user is not confirmed' do + let(:confirmed_at) { nil } + + it { is_expected.to be false } + end + end + + context 'when user is not disabled' do + let(:disabled) { false } + + context 'when user is confirmed' do + let(:confirmed_at) { Time.zone.now } + + it { is_expected.to be true } + end + + context 'when user is not confirmed' do + let(:confirmed_at) { nil } + + it { is_expected.to be false } + end + end + end end From 965345316fb3fef640a6bcc463d09d4a38b28608 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Wed, 2 May 2018 15:44:22 +0200 Subject: [PATCH 210/381] Guard against nil URLs in Request class (#7284) Fix #7265 --- app/lib/request.rb | 3 +++ app/services/activitypub/fetch_featured_collection_service.rb | 2 ++ 2 files changed, 5 insertions(+) diff --git a/app/lib/request.rb b/app/lib/request.rb index 0acd654..00f94da 100644 --- a/app/lib/request.rb +++ b/app/lib/request.rb @@ -9,12 +9,15 @@ class Request include RoutingHelper def initialize(verb, url, **options) + raise ArgumentError if url.blank? + @verb = verb @url = Addressable::URI.parse(url).normalize @options = options.merge(use_proxy? ? Rails.configuration.x.http_client_proxy : { socket_class: Socket }) @headers = {} raise Mastodon::HostValidationError, 'Instance does not support hidden service connections' if block_hidden_service? + set_common_headers! set_digest! if options.key?(:body) end diff --git a/app/services/activitypub/fetch_featured_collection_service.rb b/app/services/activitypub/fetch_featured_collection_service.rb index 40714e9..6a137b5 100644 --- a/app/services/activitypub/fetch_featured_collection_service.rb +++ b/app/services/activitypub/fetch_featured_collection_service.rb @@ -4,6 +4,8 @@ class ActivityPub::FetchFeaturedCollectionService < BaseService include JsonLdHelper def call(account) + return if account.featured_collection_url.blank? + @account = account @json = fetch_resource(@account.featured_collection_url, true) From c5dcd7d836d53ede4751405ec5701f9f3b48102c Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Wed, 2 May 2018 15:45:24 +0200 Subject: [PATCH 211/381] Speed up test suite by not generating RSA keys in test environment (#7296) One RSA keypair for all fabricated test accounts is enough --- app/models/account.rb | 4 ++-- spec/fabricators/account_fabricator.rb | 8 +++++++- spec/models/account_spec.rb | 3 ++- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/app/models/account.rb b/app/models/account.rb index 647b5c3..0cd2a10 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -406,9 +406,9 @@ class Account < ApplicationRecord end def generate_keys - return unless local? + return unless local? && !Rails.env.test? - keypair = OpenSSL::PKey::RSA.new(Rails.env.test? ? 512 : 2048) + keypair = OpenSSL::PKey::RSA.new(2048) self.private_key = keypair.to_pem self.public_key = keypair.public_key.to_pem end diff --git a/spec/fabricators/account_fabricator.rb b/spec/fabricators/account_fabricator.rb index 446f8ea..7aa983f 100644 --- a/spec/fabricators/account_fabricator.rb +++ b/spec/fabricators/account_fabricator.rb @@ -1,4 +1,10 @@ +keypair = OpenSSL::PKey::RSA.new(2048) +public_key = keypair.public_key.to_pem +private_key = keypair.to_pem + Fabricator(:account) do - username { sequence(:username) { |i| "#{Faker::Internet.user_name(nil, %w(_))}#{i}" } } + username { sequence(:username) { |i| "#{Faker::Internet.user_name(nil, %w(_))}#{i}" } } last_webfingered_at { Time.now.utc } + public_key { public_key } + private_key { private_key} end diff --git a/spec/models/account_spec.rb b/spec/models/account_spec.rb index fb7ddfa..3aaaa55 100644 --- a/spec/models/account_spec.rb +++ b/spec/models/account_spec.rb @@ -815,7 +815,8 @@ RSpec.describe Account, type: :model do end context 'when is local' do - it 'generates keys' do + # Test disabled because test environment omits autogenerating keys for performance + xit 'generates keys' do account = Account.create!(domain: nil, username: Faker::Internet.user_name(nil, ['_'])) expect(account.keypair.private?).to eq true end From f62539ce5c106e27a371702d499ec4df52eccde6 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Wed, 2 May 2018 15:50:20 +0200 Subject: [PATCH 212/381] Remove most behaviour disparities between blocks and mutes (#7231) * Remove most behaviour disparities between blocks and mutes The only differences between block and mute should be: - Mutes can optionally NOT affect notifications - Mutes should not be visible to the muted Fix #7230 Fix #5713 * Do not allow boosting someone you blocked Fix #7248 * Do not allow favouriting someone you blocked * Fix nil error in StatusPolicy --- app/lib/feed_manager.rb | 21 ++++++++++++--------- app/policies/status_policy.rb | 6 +++++- app/services/favourite_service.rb | 2 +- 3 files changed, 18 insertions(+), 11 deletions(-) diff --git a/app/lib/feed_manager.rb b/app/lib/feed_manager.rb index dd78e54..c18c07b 100644 --- a/app/lib/feed_manager.rb +++ b/app/lib/feed_manager.rb @@ -145,19 +145,20 @@ class FeedManager redis.exists("subscribed:#{timeline_id}") end + def blocks_or_mutes?(receiver_id, account_ids, context) + Block.where(account_id: receiver_id, target_account_id: account_ids).any? || + (context == :home ? Mute.where(account_id: receiver_id, target_account_id: account_ids).any? : Mute.where(account_id: receiver_id, target_account_id: account_ids, hide_notifications: true).any?) + end + def filter_from_home?(status, receiver_id) return false if receiver_id == status.account_id return true if status.reply? && (status.in_reply_to_id.nil? || status.in_reply_to_account_id.nil?) - check_for_mutes = [status.account_id] - check_for_mutes.concat([status.reblog.account_id]) if status.reblog? - - return true if Mute.where(account_id: receiver_id, target_account_id: check_for_mutes).any? - check_for_blocks = status.mentions.pluck(:account_id) + check_for_blocks.concat([status.account_id]) check_for_blocks.concat([status.reblog.account_id]) if status.reblog? - return true if Block.where(account_id: receiver_id, target_account_id: check_for_blocks).any? + return true if blocks_or_mutes?(receiver_id, check_for_blocks, :home) if status.reply? && !status.in_reply_to_account_id.nil? # Filter out if it's a reply should_filter = !Follow.where(account_id: receiver_id, target_account_id: status.in_reply_to_account_id).exists? # and I'm not following the person it's a reply to @@ -177,11 +178,13 @@ class FeedManager def filter_from_mentions?(status, receiver_id) return true if receiver_id == status.account_id - check_for_blocks = [status.account_id] - check_for_blocks.concat(status.mentions.pluck(:account_id)) + # This filter is called from NotifyService, but already after the sender of + # the notification has been checked for mute/block. Therefore, it's not + # necessary to check the author of the toot for mute/block again + check_for_blocks = status.mentions.pluck(:account_id) check_for_blocks.concat([status.in_reply_to_account]) if status.reply? && !status.in_reply_to_account_id.nil? - should_filter = Block.where(account_id: receiver_id, target_account_id: check_for_blocks).any? # Filter if it's from someone I blocked, in reply to someone I blocked, or mentioning someone I blocked + should_filter = blocks_or_mutes?(receiver_id, check_for_blocks, :mentions) # Filter if it's from someone I blocked, in reply to someone I blocked, or mentioning someone I blocked (or muted) should_filter ||= (status.account.silenced? && !Follow.where(account_id: receiver_id, target_account_id: status.account_id).exists?) # of if the account is silenced and I'm not following them should_filter diff --git a/app/policies/status_policy.rb b/app/policies/status_policy.rb index 5573289..4145d7e 100644 --- a/app/policies/status_policy.rb +++ b/app/policies/status_policy.rb @@ -16,7 +16,11 @@ class StatusPolicy < ApplicationPolicy end def reblog? - !direct? && (!private? || owned?) && show? + !direct? && (!private? || owned?) && show? && !current_account&.blocking?(author) + end + + def favourite? + show? && !current_account&.blocking?(author) end def destroy? diff --git a/app/services/favourite_service.rb b/app/services/favourite_service.rb index 44df3ed..bc2d154 100644 --- a/app/services/favourite_service.rb +++ b/app/services/favourite_service.rb @@ -8,7 +8,7 @@ class FavouriteService < BaseService # @param [Status] status # @return [Favourite] def call(account, status) - authorize_with account, status, :show? + authorize_with account, status, :favourite? favourite = Favourite.find_by(account: account, status: status) From cae933510cbc64db27aeb44e205ce17ff4974da7 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Wed, 2 May 2018 15:57:37 +0200 Subject: [PATCH 213/381] Allow updating bio fields via PUT /api/v1/accounts/update_credentials (#7288) Add raw bio fields to the source attribute on GET /api/v1/accounts/verify_credentials --- app/controllers/api/v1/accounts/credentials_controller.rb | 2 +- app/models/account.rb | 4 ++++ app/serializers/rest/credential_account_serializer.rb | 2 ++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/app/controllers/api/v1/accounts/credentials_controller.rb b/app/controllers/api/v1/accounts/credentials_controller.rb index 062d490..a3c4008 100644 --- a/app/controllers/api/v1/accounts/credentials_controller.rb +++ b/app/controllers/api/v1/accounts/credentials_controller.rb @@ -21,7 +21,7 @@ class Api::V1::Accounts::CredentialsController < Api::BaseController private def account_params - params.permit(:display_name, :note, :avatar, :header, :locked) + params.permit(:display_name, :note, :avatar, :header, :locked, fields_attributes: [:name, :value]) end def user_settings_params diff --git a/app/models/account.rb b/app/models/account.rb index 0cd2a10..a166fb4 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -273,6 +273,10 @@ class Account < ApplicationRecord @value = attr['value'] @errors = {} end + + def to_h + { name: @name, value: @value } + end end class << self diff --git a/app/serializers/rest/credential_account_serializer.rb b/app/serializers/rest/credential_account_serializer.rb index 870d8b7..56857cb 100644 --- a/app/serializers/rest/credential_account_serializer.rb +++ b/app/serializers/rest/credential_account_serializer.rb @@ -5,10 +5,12 @@ class REST::CredentialAccountSerializer < REST::AccountSerializer def source user = object.user + { privacy: user.setting_default_privacy, sensitive: user.setting_default_sensitive, note: object.note, + fields: object.fields.map(&:to_h), } end end From d0cdd5cf94ff479e4037dc47539f7f9c408831b3 Mon Sep 17 00:00:00 2001 From: ThibG Date: Wed, 2 May 2018 16:08:16 +0200 Subject: [PATCH 214/381] Accept actor object updates from all supported actor types (#7312) --- app/lib/activitypub/activity/update.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/lib/activitypub/activity/update.rb b/app/lib/activitypub/activity/update.rb index 47e98e0..aa5907f 100644 --- a/app/lib/activitypub/activity/update.rb +++ b/app/lib/activitypub/activity/update.rb @@ -1,8 +1,10 @@ # frozen_string_literal: true class ActivityPub::Activity::Update < ActivityPub::Activity + SUPPORTED_TYPES = %w(Application Group Organization Person Service).freeze + def perform - update_account if equals_or_includes?(@object['type'], 'Person') + update_account if equals_or_includes_any?(@object['type'], SUPPORTED_TYPES) end private From 71a7cea73fdfb45d06986e108b2ce1dbf7e32579 Mon Sep 17 00:00:00 2001 From: abcang Date: Wed, 2 May 2018 23:14:51 +0900 Subject: [PATCH 215/381] Keep notification when muting_notifications is true (#7311) * Keep notification when muting_notifications is true * Retrun mute object * Fix test --- app/javascript/mastodon/reducers/notifications.js | 2 +- app/models/concerns/account_interactions.rb | 1 + app/services/mute_service.rb | 8 ++- spec/models/concerns/account_interactions_spec.rb | 62 ++++++++++------------- 4 files changed, 34 insertions(+), 39 deletions(-) diff --git a/app/javascript/mastodon/reducers/notifications.js b/app/javascript/mastodon/reducers/notifications.js index da9b8c4..84d4fc6 100644 --- a/app/javascript/mastodon/reducers/notifications.js +++ b/app/javascript/mastodon/reducers/notifications.js @@ -105,7 +105,7 @@ export default function notifications(state = initialState, action) { return expandNormalizedNotifications(state, action.notifications, action.next); case ACCOUNT_BLOCK_SUCCESS: case ACCOUNT_MUTE_SUCCESS: - return filterNotifications(state, action.relationship); + return action.relationship.muting_notifications ? filterNotifications(state, action.relationship) : state; case NOTIFICATIONS_CLEAR: return state.set('items', ImmutableList()).set('hasMore', false); case TIMELINE_DELETE: diff --git a/app/models/concerns/account_interactions.rb b/app/models/concerns/account_interactions.rb index fdf35a4..2dbd259 100644 --- a/app/models/concerns/account_interactions.rb +++ b/app/models/concerns/account_interactions.rb @@ -93,6 +93,7 @@ module AccountInteractions if mute.hide_notifications? != notifications mute.update!(hide_notifications: notifications) end + mute end def mute_conversation!(conversation) diff --git a/app/services/mute_service.rb b/app/services/mute_service.rb index 9b7cbd8..c6122a1 100644 --- a/app/services/mute_service.rb +++ b/app/services/mute_service.rb @@ -3,9 +3,13 @@ class MuteService < BaseService def call(account, target_account, notifications: nil) return if account.id == target_account.id - FeedManager.instance.clear_from_timeline(account, target_account) + mute = account.mute!(target_account, notifications: notifications) - BlockWorker.perform_async(account.id, target_account.id) + if mute.hide_notifications? + BlockWorker.perform_async(account.id, target_account.id) + else + FeedManager.instance.clear_from_timeline(account, target_account) + end mute end end diff --git a/spec/models/concerns/account_interactions_spec.rb b/spec/models/concerns/account_interactions_spec.rb index d08bdc8..8df52b7 100644 --- a/spec/models/concerns/account_interactions_spec.rb +++ b/spec/models/concerns/account_interactions_spec.rb @@ -108,13 +108,15 @@ describe AccountInteractions do end describe '#mute!' do + subject { account.mute!(target_account, notifications: arg_notifications) } + context 'Mute does not exist yet' do context 'arg :notifications is nil' do let(:arg_notifications) { nil } - it 'creates Mute, and returns nil' do + it 'creates Mute, and returns Mute' do expect do - expect(account.mute!(target_account, notifications: arg_notifications)).to be nil + expect(subject).to be_kind_of Mute end.to change { account.mute_relationships.count }.by 1 end end @@ -122,9 +124,9 @@ describe AccountInteractions do context 'arg :notifications is false' do let(:arg_notifications) { false } - it 'creates Mute, and returns nil' do + it 'creates Mute, and returns Mute' do expect do - expect(account.mute!(target_account, notifications: arg_notifications)).to be nil + expect(subject).to be_kind_of Mute end.to change { account.mute_relationships.count }.by 1 end end @@ -132,9 +134,9 @@ describe AccountInteractions do context 'arg :notifications is true' do let(:arg_notifications) { true } - it 'creates Mute, and returns nil' do + it 'creates Mute, and returns Mute' do expect do - expect(account.mute!(target_account, notifications: arg_notifications)).to be nil + expect(subject).to be_kind_of Mute end.to change { account.mute_relationships.count }.by 1 end end @@ -158,36 +160,30 @@ describe AccountInteractions do context 'arg :notifications is nil' do let(:arg_notifications) { nil } - it 'returns nil without updating mute.hide_notifications' do + it 'returns Mute without updating mute.hide_notifications' do expect do - expect(account.mute!(target_account, notifications: arg_notifications)).to be nil - mute = account.mute_relationships.find_by(target_account: target_account) - expect(mute.hide_notifications?).to be true - end + expect(subject).to be_kind_of Mute + end.not_to change { mute.reload.hide_notifications? }.from(true) end end context 'arg :notifications is false' do let(:arg_notifications) { false } - it 'returns true, and updates mute.hide_notifications false' do + it 'returns Mute, and updates mute.hide_notifications false' do expect do - expect(account.mute!(target_account, notifications: arg_notifications)).to be true - mute = account.mute_relationships.find_by(target_account: target_account) - expect(mute.hide_notifications?).to be false - end + expect(subject).to be_kind_of Mute + end.to change { mute.reload.hide_notifications? }.from(true).to(false) end end context 'arg :notifications is true' do let(:arg_notifications) { true } - it 'returns nil without updating mute.hide_notifications' do + it 'returns Mute without updating mute.hide_notifications' do expect do - expect(account.mute!(target_account, notifications: arg_notifications)).to be nil - mute = account.mute_relationships.find_by(target_account: target_account) - expect(mute.hide_notifications?).to be true - end + expect(subject).to be_kind_of Mute + end.not_to change { mute.reload.hide_notifications? }.from(true) end end end @@ -198,36 +194,30 @@ describe AccountInteractions do context 'arg :notifications is nil' do let(:arg_notifications) { nil } - it 'returns true, and updates mute.hide_notifications true' do + it 'returns Mute, and updates mute.hide_notifications true' do expect do - expect(account.mute!(target_account, notifications: arg_notifications)).to be true - mute = account.mute_relationships.find_by(target_account: target_account) - expect(mute.hide_notifications?).to be true - end + expect(subject).to be_kind_of Mute + end.to change { mute.reload.hide_notifications? }.from(false).to(true) end end context 'arg :notifications is false' do let(:arg_notifications) { false } - it 'returns nil without updating mute.hide_notifications' do + it 'returns Mute without updating mute.hide_notifications' do expect do - expect(account.mute!(target_account, notifications: arg_notifications)).to be nil - mute = account.mute_relationships.find_by(target_account: target_account) - expect(mute.hide_notifications?).to be false - end + expect(subject).to be_kind_of Mute + end.not_to change { mute.reload.hide_notifications? }.from(false) end end context 'arg :notifications is true' do let(:arg_notifications) { true } - it 'returns true, and updates mute.hide_notifications true' do + it 'returns Mute, and updates mute.hide_notifications true' do expect do - expect(account.mute!(target_account, notifications: arg_notifications)).to be true - mute = account.mute_relationships.find_by(target_account: target_account) - expect(mute.hide_notifications?).to be true - end + expect(subject).to be_kind_of Mute + end.to change { mute.reload.hide_notifications? }.from(false).to(true) end end end From cb5b5cb5f79bb2187d8124df91af4c8e1bfd7256 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Wed, 2 May 2018 18:58:48 +0200 Subject: [PATCH 216/381] Slightly reduce RAM usage (#7301) * No need to re-require sidekiq plugins, they are required via Gemfile * Add derailed_benchmarks tool, no need to require TTY gems in Gemfile * Replace ruby-oembed with FetchOEmbedService Reduce startup by 45382 allocated objects * Remove preloaded JSON-LD in favour of caching HTTP responses Reduce boot RAM by about 6 MiB * Fix tests * Fix test suite by stubbing out JSON-LD contexts --- Gemfile | 12 +- Gemfile.lock | 22 +- app/controllers/api/web/embeds_controller.rb | 11 +- .../settings/follower_domains_controller.rb | 2 - app/helpers/jsonld_helper.rb | 17 +- app/lib/provider_discovery.rb | 47 --- app/services/fan_out_on_write_service.rb | 2 - app/services/fetch_link_card_service.rb | 38 +- app/services/fetch_oembed_service.rb | 71 ++++ app/workers/scheduler/backup_cleanup_scheduler.rb | 1 - .../scheduler/doorkeeper_cleanup_scheduler.rb | 1 - app/workers/scheduler/email_scheduler.rb | 1 - app/workers/scheduler/feed_cleanup_scheduler.rb | 1 - app/workers/scheduler/ip_cleanup_scheduler.rb | 1 - app/workers/scheduler/media_cleanup_scheduler.rb | 1 - .../scheduler/subscriptions_cleanup_scheduler.rb | 2 - app/workers/scheduler/subscriptions_scheduler.rb | 3 - app/workers/scheduler/user_cleanup_scheduler.rb | 1 - app/workers/soft_block_domain_followers_worker.rb | 2 - config/initializers/json_ld.rb | 5 - config/initializers/oembed.rb | 4 - lib/json_ld/activitystreams.rb | 153 -------- lib/json_ld/identity.rb | 86 ----- lib/json_ld/security.rb | 50 --- lib/tasks/mastodon.rake | 2 + spec/fixtures/requests/json-ld.activitystreams.txt | 391 +++++++++++++++++++++ spec/fixtures/requests/json-ld.identity.txt | 100 ++++++ spec/fixtures/requests/json-ld.security.txt | 61 ++++ spec/lib/activitypub/linked_data_signature_spec.rb | 4 + spec/lib/provider_discovery_spec.rb | 118 ------- spec/rails_helper.rb | 14 + spec/services/account_search_service_spec.rb | 2 +- .../fetch_remote_account_service_spec.rb | 2 +- .../fetch_remote_status_service_spec.rb | 2 +- .../activitypub/process_account_service_spec.rb | 2 +- .../activitypub/process_collection_service_spec.rb | 2 +- spec/services/after_block_service_spec.rb | 2 +- spec/services/authorize_follow_service_spec.rb | 2 +- .../services/batched_remove_status_service_spec.rb | 2 +- .../block_domain_from_account_service_spec.rb | 2 +- spec/services/block_domain_service_spec.rb | 2 +- spec/services/block_service_spec.rb | 2 +- spec/services/bootstrap_timeline_service_spec.rb | 2 +- spec/services/fan_out_on_write_service_spec.rb | 2 +- spec/services/favourite_service_spec.rb | 2 +- spec/services/fetch_atom_service_spec.rb | 2 +- spec/services/fetch_link_card_service_spec.rb | 2 +- spec/services/fetch_oembed_service_spec.rb | 125 +++++++ spec/services/fetch_remote_account_service_spec.rb | 2 +- spec/services/fetch_remote_status_service_spec.rb | 2 +- spec/services/follow_service_spec.rb | 2 +- spec/services/mute_service_spec.rb | 2 +- spec/services/notify_service_spec.rb | 2 +- spec/services/post_status_service_spec.rb | 2 +- spec/services/precompute_feed_service_spec.rb | 2 +- spec/services/process_feed_service_spec.rb | 2 +- spec/services/process_interaction_service_spec.rb | 2 +- spec/services/process_mentions_service_spec.rb | 2 +- .../pubsubhubbub/subscribe_service_spec.rb | 2 +- .../pubsubhubbub/unsubscribe_service_spec.rb | 2 +- spec/services/reblog_service_spec.rb | 2 +- spec/services/reject_follow_service_spec.rb | 2 +- spec/services/remove_status_service_spec.rb | 2 +- spec/services/report_service_spec.rb | 2 +- spec/services/resolve_account_service_spec.rb | 2 +- spec/services/resolve_url_service_spec.rb | 2 +- spec/services/search_service_spec.rb | 2 +- spec/services/send_interaction_service_spec.rb | 2 +- spec/services/subscribe_service_spec.rb | 2 +- spec/services/suspend_account_service_spec.rb | 2 +- spec/services/unblock_domain_service_spec.rb | 2 +- spec/services/unblock_service_spec.rb | 2 +- spec/services/unfollow_service_spec.rb | 2 +- spec/services/unmute_service_spec.rb | 2 +- spec/services/unsubscribe_service_spec.rb | 2 +- .../services/update_remote_profile_service_spec.rb | 2 +- spec/spec_helper.rb | 12 +- 77 files changed, 881 insertions(+), 568 deletions(-) delete mode 100644 app/lib/provider_discovery.rb create mode 100644 app/services/fetch_oembed_service.rb delete mode 100644 config/initializers/json_ld.rb delete mode 100644 config/initializers/oembed.rb delete mode 100644 lib/json_ld/activitystreams.rb delete mode 100644 lib/json_ld/identity.rb delete mode 100644 lib/json_ld/security.rb create mode 100644 spec/fixtures/requests/json-ld.activitystreams.txt create mode 100644 spec/fixtures/requests/json-ld.identity.txt create mode 100644 spec/fixtures/requests/json-ld.security.txt delete mode 100644 spec/lib/provider_discovery_spec.rb create mode 100644 spec/services/fetch_oembed_service_spec.rb diff --git a/Gemfile b/Gemfile index a337485..f1665ce 100644 --- a/Gemfile +++ b/Gemfile @@ -54,7 +54,7 @@ gem 'httplog', '~> 1.0' gem 'idn-ruby', require: 'idn' gem 'kaminari', '~> 1.1' gem 'link_header', '~> 0.0' -gem 'mime-types', '~> 3.1' +gem 'mime-types', '~> 3.1', require: 'mime/types/columnar' gem 'nokogiri', '~> 1.8' gem 'nsa', '~> 0.2' gem 'oj', '~> 3.5' @@ -70,7 +70,6 @@ gem 'rails-settings-cached', '~> 0.6' gem 'redis', '~> 4.0', require: ['redis', 'redis/connection/hiredis'] gem 'mario-redis-lock', '~> 1.2', require: 'redis_lock' gem 'rqrcode', '~> 0.10' -gem 'ruby-oembed', '~> 0.12', require: 'oembed' gem 'ruby-progressbar', '~> 1.4' gem 'sanitize', '~> 4.6' gem 'sidekiq', '~> 5.1' @@ -82,14 +81,14 @@ gem 'simple_form', '~> 4.0' gem 'sprockets-rails', '~> 3.2', require: 'sprockets/railtie' gem 'stoplight', '~> 2.1.3' gem 'strong_migrations', '~> 0.2' -gem 'tty-command', '~> 0.8' -gem 'tty-prompt', '~> 0.16' +gem 'tty-command', '~> 0.8', require: false +gem 'tty-prompt', '~> 0.16', require: false gem 'twitter-text', '~> 1.14' gem 'tzinfo-data', '~> 1.2018' gem 'webpacker', '~> 3.4' gem 'webpush' -gem 'json-ld-preloaded', '~> 2.2' +gem 'json-ld', '~> 2.2' gem 'rdf-normalize', '~> 0.3' group :development, :test do @@ -135,6 +134,9 @@ group :development do gem 'capistrano-rails', '~> 1.3' gem 'capistrano-rbenv', '~> 2.1' gem 'capistrano-yarn', '~> 2.0' + + gem 'derailed_benchmarks' + gem 'stackprof' end group :production do diff --git a/Gemfile.lock b/Gemfile.lock index d96165d..94ab0b7 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -75,6 +75,7 @@ GEM aws-sigv4 (~> 1.0) aws-sigv4 (1.0.2) bcrypt (3.1.11) + benchmark-ips (2.7.2) better_errors (2.4.0) coderay (>= 1.0.0) erubi (>= 1.0.0) @@ -138,6 +139,14 @@ GEM css_parser (1.6.0) addressable debug_inspector (0.0.3) + derailed_benchmarks (1.3.4) + benchmark-ips (~> 2) + get_process_mem (~> 0) + heapy (~> 0) + memory_profiler (~> 0) + rack (>= 1) + rake (> 10, < 13) + thor (~> 0.19) devise (4.4.3) bcrypt (~> 3.0) orm_adapter (~> 0.1) @@ -206,6 +215,7 @@ GEM fuubar (2.3.1) rspec-core (~> 3.0) ruby-progressbar (~> 1.4) + get_process_mem (0.2.1) globalid (0.4.1) activesupport (>= 4.2.0) goldfinger (2.1.0) @@ -226,6 +236,7 @@ GEM concurrent-ruby (~> 1.0) hashdiff (0.3.7) hashie (3.5.7) + heapy (0.1.3) highline (1.7.10) hiredis (0.6.1) hitimes (1.2.6) @@ -264,10 +275,6 @@ GEM json-ld (2.2.1) multi_json (~> 1.12) rdf (>= 2.2.8, < 4.0) - json-ld-preloaded (2.2.3) - json-ld (>= 2.2, < 4.0) - multi_json (~> 1.12) - rdf (>= 2.2, < 4.0) jsonapi-renderer (0.2.0) jwt (2.1.0) kaminari (1.1.1) @@ -502,7 +509,6 @@ GEM rainbow (>= 2.2.2, < 4.0) ruby-progressbar (~> 1.7) unicode-display_width (~> 1.0, >= 1.0.1) - ruby-oembed (0.12.0) ruby-progressbar (1.9.0) ruby-saml (1.7.2) nokogiri (>= 1.5.10) @@ -557,6 +563,7 @@ GEM sshkit (1.16.0) net-scp (>= 1.1.2) net-ssh (>= 2.8.0) + stackprof (0.2.11) statsd-ruby (1.2.1) stoplight (2.1.3) streamio-ffmpeg (3.0.2) @@ -645,6 +652,7 @@ DEPENDENCIES chewy (~> 5.0) cld3 (~> 3.2.0) climate_control (~> 0.2) + derailed_benchmarks devise (~> 4.4) devise-two-factor (~> 3.0) devise_pam_authenticatable2 (~> 9.1) @@ -668,7 +676,7 @@ DEPENDENCIES i18n-tasks (~> 0.9) idn-ruby iso-639 - json-ld-preloaded (~> 2.2) + json-ld (~> 2.2) kaminari (~> 1.1) letter_opener (~> 1.4) letter_opener_web (~> 1.3) @@ -714,7 +722,6 @@ DEPENDENCIES rspec-retry (~> 0.5) rspec-sidekiq (~> 3.0) rubocop (~> 0.55) - ruby-oembed (~> 0.12) ruby-progressbar (~> 1.4) sanitize (~> 4.6) scss_lint (~> 0.57) @@ -726,6 +733,7 @@ DEPENDENCIES simple_form (~> 4.0) simplecov (~> 0.16) sprockets-rails (~> 3.2) + stackprof stoplight (~> 2.1.3) streamio-ffmpeg (~> 3.0) strong_migrations (~> 0.2) diff --git a/app/controllers/api/web/embeds_controller.rb b/app/controllers/api/web/embeds_controller.rb index f2fe74b..987290a 100644 --- a/app/controllers/api/web/embeds_controller.rb +++ b/app/controllers/api/web/embeds_controller.rb @@ -9,9 +9,12 @@ class Api::Web::EmbedsController < Api::Web::BaseController status = StatusFinder.new(params[:url]).status render json: status, serializer: OEmbedSerializer, width: 400 rescue ActiveRecord::RecordNotFound - oembed = OEmbed::Providers.get(params[:url]) - render json: Oj.dump(oembed.fields) - rescue OEmbed::NotFound - render json: {}, status: :not_found + oembed = FetchOEmbedService.new.call(params[:url]) + + if oembed + render json: oembed + else + render json: {}, status: :not_found + end end end diff --git a/app/controllers/settings/follower_domains_controller.rb b/app/controllers/settings/follower_domains_controller.rb index 213d9e9..91b521e 100644 --- a/app/controllers/settings/follower_domains_controller.rb +++ b/app/controllers/settings/follower_domains_controller.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require 'sidekiq-bulk' - class Settings::FollowerDomainsController < ApplicationController layout 'admin' diff --git a/app/helpers/jsonld_helper.rb b/app/helpers/jsonld_helper.rb index a3cfdad..e905616 100644 --- a/app/helpers/jsonld_helper.rb +++ b/app/helpers/jsonld_helper.rb @@ -48,7 +48,7 @@ module JsonLdHelper end def canonicalize(json) - graph = RDF::Graph.new << JSON::LD::API.toRdf(json) + graph = RDF::Graph.new << JSON::LD::API.toRdf(json, documentLoader: method(:load_jsonld_context)) graph.dump(:normalize) end @@ -90,4 +90,19 @@ module JsonLdHelper request.add_headers('Accept' => 'application/activity+json, application/ld+json') request end + + def load_jsonld_context(url, _options = {}, &_block) + json = Rails.cache.fetch("jsonld:context:#{url}", expires_in: 30.days, raw: true) do + request = Request.new(:get, url) + request.add_headers('Accept' => 'application/ld+json') + + request.perform do |res| + raise JSON::LD::JsonLdError::LoadingDocumentFailed unless res.code == 200 && res.mime_type == 'application/ld+json' + res.body_with_limit + end + end + + doc = JSON::LD::API::RemoteDocument.new(url, json) + block_given? ? yield(doc) : doc + end end diff --git a/app/lib/provider_discovery.rb b/app/lib/provider_discovery.rb deleted file mode 100644 index 3bec721..0000000 --- a/app/lib/provider_discovery.rb +++ /dev/null @@ -1,47 +0,0 @@ -# frozen_string_literal: true - -class ProviderDiscovery < OEmbed::ProviderDiscovery - class << self - def get(url, **options) - provider = discover_provider(url, options) - - options.delete(:html) - - provider.get(url, options) - end - - def discover_provider(url, **options) - format = options[:format] - - html = if options[:html] - Nokogiri::HTML(options[:html]) - else - Request.new(:get, url).perform do |res| - raise OEmbed::NotFound, url if res.code != 200 || res.mime_type != 'text/html' - Nokogiri::HTML(res.body_with_limit) - end - end - - if format.nil? || format == :json - provider_endpoint ||= html.at_xpath('//link[@type="application/json+oembed"]')&.attribute('href')&.value - format ||= :json if provider_endpoint - end - - if format.nil? || format == :xml - provider_endpoint ||= html.at_xpath('//link[@type="text/xml+oembed"]')&.attribute('href')&.value - format ||= :xml if provider_endpoint - end - - raise OEmbed::NotFound, url if provider_endpoint.nil? - begin - provider_endpoint = Addressable::URI.parse(provider_endpoint) - provider_endpoint.query = nil - provider_endpoint = provider_endpoint.to_s - rescue Addressable::URI::InvalidURIError - raise OEmbed::NotFound, url - end - - OEmbed::Provider.new(provider_endpoint, format) - end - end -end diff --git a/app/services/fan_out_on_write_service.rb b/app/services/fan_out_on_write_service.rb index 0f77556..510b80c 100644 --- a/app/services/fan_out_on_write_service.rb +++ b/app/services/fan_out_on_write_service.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require 'sidekiq-bulk' - class FanOutOnWriteService < BaseService # Push a status into home and mentions feeds # @param [Status] status diff --git a/app/services/fetch_link_card_service.rb b/app/services/fetch_link_card_service.rb index d5920a4..77d4aa5 100644 --- a/app/services/fetch_link_card_service.rb +++ b/app/services/fetch_link_card_service.rb @@ -85,42 +85,40 @@ class FetchLinkCardService < BaseService end def attempt_oembed - embed = OEmbed::Providers.get(@url, html: @html) + embed = FetchOEmbedService.new.call(@url, html: @html) - return false unless embed.respond_to?(:type) + return false if embed.nil? - @card.type = embed.type - @card.title = embed.respond_to?(:title) ? embed.title : '' - @card.author_name = embed.respond_to?(:author_name) ? embed.author_name : '' - @card.author_url = embed.respond_to?(:author_url) ? embed.author_url : '' - @card.provider_name = embed.respond_to?(:provider_name) ? embed.provider_name : '' - @card.provider_url = embed.respond_to?(:provider_url) ? embed.provider_url : '' + @card.type = embed[:type] + @card.title = embed[:title] || '' + @card.author_name = embed[:author_name] || '' + @card.author_url = embed[:author_url] || '' + @card.provider_name = embed[:provider_name] || '' + @card.provider_url = embed[:provider_url] || '' @card.width = 0 @card.height = 0 case @card.type when 'link' - @card.image_remote_url = embed.thumbnail_url if embed.respond_to?(:thumbnail_url) + @card.image_remote_url = embed[:thumbnail_url] if embed[:thumbnail_url].present? when 'photo' - return false unless embed.respond_to?(:url) + return false if embed[:url].blank? - @card.embed_url = embed.url - @card.image_remote_url = embed.url - @card.width = embed.width.presence || 0 - @card.height = embed.height.presence || 0 + @card.embed_url = embed[:url] + @card.image_remote_url = embed[:url] + @card.width = embed[:width].presence || 0 + @card.height = embed[:height].presence || 0 when 'video' - @card.width = embed.width.presence || 0 - @card.height = embed.height.presence || 0 - @card.html = Formatter.instance.sanitize(embed.html, Sanitize::Config::MASTODON_OEMBED) - @card.image_remote_url = embed.thumbnail_url if embed.respond_to?(:thumbnail_url) + @card.width = embed[:width].presence || 0 + @card.height = embed[:height].presence || 0 + @card.html = Formatter.instance.sanitize(embed[:html], Sanitize::Config::MASTODON_OEMBED) + @card.image_remote_url = embed[:thumbnail_url] if embed[:thumbnail_url].present? when 'rich' # Most providers rely on