From 2d548e273eab741385db40eaa4ba5d30221f1b2b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 3 Nov 2023 11:10:13 +0100 Subject: [PATCH 1/5] New Crowdin Translations (automated) (#27646) Co-authored-by: GitHub Actions --- app/javascript/mastodon/locales/de.json | 2 +- app/javascript/mastodon/locales/he.json | 4 +-- app/javascript/mastodon/locales/hr.json | 22 ++++++++++++++++ app/javascript/mastodon/locales/sl.json | 8 +++--- config/locales/ar.yml | 6 +++++ config/locales/cy.yml | 10 +++++++- config/locales/doorkeeper.hr.yml | 13 ++++++++++ config/locales/doorkeeper.sl.yml | 34 ++++++++++++------------- config/locales/my.yml | 11 ++++++++ config/locales/simple_form.sq.yml | 4 +-- config/locales/sk.yml | 8 ++++++ config/locales/uk.yml | 4 +-- 12 files changed, 97 insertions(+), 29 deletions(-) diff --git a/app/javascript/mastodon/locales/de.json b/app/javascript/mastodon/locales/de.json index 76b37ef44b06df..3fcab71d708115 100644 --- a/app/javascript/mastodon/locales/de.json +++ b/app/javascript/mastodon/locales/de.json @@ -425,7 +425,7 @@ "notification.admin.report": "{name} meldete {target}", "notification.admin.sign_up": "{name} registrierte sich", "notification.favourite": "{name} favorisierte deinen Beitrag", - "notification.follow": "{name} folgt dir jetzt", + "notification.follow": "{name} folgt dir", "notification.follow_request": "{name} möchte dir folgen", "notification.mention": "{name} erwähnte dich", "notification.own_poll": "Deine Umfrage ist beendet", diff --git a/app/javascript/mastodon/locales/he.json b/app/javascript/mastodon/locales/he.json index 01618118a316c5..a8cb2ec27663b3 100644 --- a/app/javascript/mastodon/locales/he.json +++ b/app/javascript/mastodon/locales/he.json @@ -202,7 +202,7 @@ "dismissable_banner.community_timeline": "אלו הם החצרוצים הציבוריים האחרונים מהמשתמשים על שרת {domain}.", "dismissable_banner.dismiss": "בטל", "dismissable_banner.explore_links": "אלו הקישורים האחרונים ששותפו על ידי משתמשים ששרת זה רואה ברשת המבוזרת כרגע.", - "dismissable_banner.explore_statuses": "ההודעות האלו, משרת זה ואחרים ברשת המבוזרת, צוברים חשיפה היום. הודעות חדשות יותר עם יותר הדהודים וחיבובים מדורגות גבוה יותר.", + "dismissable_banner.explore_statuses": "אלו הודעות משרת זה ואחרים ברשת המבוזרת שצוברות חשיפה היום. הודעות חדשות יותר עם יותר הדהודים וחיבובים מדורגות גבוה יותר.", "dismissable_banner.explore_tags": "התגיות האלו, משרת זה ואחרים ברשת המבוזרת, צוברות חשיפה כעת.", "dismissable_banner.public_timeline": "אלו ההודעות האחרונות שהתקבלו מהמשתמשים שנעקבים על ידי משתמשים מ־{domain}.", "embed.instructions": "ניתן להטמיע את ההודעה הזו באתרך ע\"י העתקת הקוד שלהלן.", @@ -630,7 +630,7 @@ "status.edited": "נערך ב{date}", "status.edited_x_times": "נערך {count, plural, one {פעם {count}} other {{count} פעמים}}", "status.embed": "הטמעה", - "status.favourite": "מחובבת", + "status.favourite": "חיבוב", "status.filter": "סנן הודעה זו", "status.filtered": "סונן", "status.hide": "הסתרת חיצרוץ", diff --git a/app/javascript/mastodon/locales/hr.json b/app/javascript/mastodon/locales/hr.json index 4d751cdda7d8e9..cb1b201a123f61 100644 --- a/app/javascript/mastodon/locales/hr.json +++ b/app/javascript/mastodon/locales/hr.json @@ -1,5 +1,6 @@ { "about.contact": "Kontakt:", + "about.domain_blocks.no_reason_available": "Razlog nije dostupan", "account.account_note_header": "Bilješka", "account.add_or_remove_from_list": "Dodaj ili ukloni s liste", "account.badges.bot": "Bot", @@ -14,6 +15,7 @@ "account.edit_profile": "Uredi profil", "account.enable_notifications": "Obavjesti me kada @{name} napravi objavu", "account.endorse": "Istakni na profilu", + "account.featured_tags.last_status_never": "Nema postova", "account.follow": "Prati", "account.followers": "Pratitelji", "account.followers.empty": "Nitko još ne prati korisnika/cu.", @@ -21,13 +23,18 @@ "account.following_counter": "{count, plural, one {{counter} praćeni} few{{counter} praćena} other {{counter} praćenih}}", "account.follows.empty": "Korisnik/ca još ne prati nikoga.", "account.follows_you": "Prati te", + "account.go_to_profile": "Idi na profil", "account.hide_reblogs": "Sakrij boostove od @{name}", + "account.in_memoriam": "U sjećanje.", "account.link_verified_on": "Vlasništvo ove poveznice provjereno je {date}", "account.locked_info": "Status privatnosti ovog računa postavljen je na zaključano. Vlasnik ručno pregledava tko ih može pratiti.", "account.media": "Medijski sadržaj", "account.mention": "Spomeni @{name}", "account.mute": "Utišaj @{name}", + "account.mute_notifications_short": "Utišaj obavijesti", + "account.mute_short": "Utišaj", "account.muted": "Utišano", + "account.open_original_page": "Otvori originalnu stranicu", "account.posts": "Objave", "account.posts_with_replies": "Objave i odgovori", "account.report": "Prijavi @{name}", @@ -52,6 +59,7 @@ "alert.unexpected.title": "Ups!", "announcement.announcement": "Najava", "attachments_list.unprocessed": "(neobrađeno)", + "audio.hide": "Sakrij audio", "autosuggest_hashtag.per_week": "{count} tjedno", "boost_modal.combo": "Možete pritisnuti {combo} kako biste preskočili ovo sljedeći put", "bundle_column_error.error.title": "Oh, ne!", @@ -66,6 +74,7 @@ "column.community": "Lokalna vremenska crta", "column.directory": "Pregledavanje profila", "column.domain_blocks": "Blokirane domene", + "column.favourites": "Favoriti", "column.follow_requests": "Zahtjevi za praćenje", "column.home": "Početna", "column.lists": "Liste", @@ -86,6 +95,8 @@ "community.column_settings.remote_only": "Samo udaljeno", "compose.language.change": "Promijeni jezik", "compose.language.search": "Pretraži jezike...", + "compose.published.open": "Otvori", + "compose.saved.body": "Post spremljen.", "compose_form.direct_message_warning_learn_more": "Saznajte više", "compose_form.encryption_warning": "Posts on Mastodon are not end-to-end encrypted. Do not share any dangerous information over Mastodon.", "compose_form.hashtag_warning": "This post won't be listed under any hashtag as it is unlisted. Only public posts can be searched by hashtag.", @@ -179,6 +190,8 @@ "errors.unexpected_crash.copy_stacktrace": "Kopiraj stacktrace u međuspremnik", "errors.unexpected_crash.report_issue": "Prijavi problem", "explore.search_results": "Rezultati pretrage", + "explore.suggested_follows": "Ljudi", + "explore.title": "Pretraži", "explore.trending_links": "Novosti", "explore.trending_statuses": "Objave", "explore.trending_tags": "Hashtagovi", @@ -189,12 +202,17 @@ "filter_modal.select_filter.subtitle": "Odaberite postojeću kategoriju ili stvorite novu", "filter_modal.select_filter.title": "Filtriraj ovu objavu", "filter_modal.title.status": "Filtriraj objavu", + "firehose.all": "Sve", + "firehose.local": "Ovaj server", "follow_request.authorize": "Autoriziraj", "follow_request.reject": "Odbij", + "footer.about": "O aplikaciji", "footer.get_app": "Preuzmi aplikaciju", + "footer.invite": "Pozovi ljude", "footer.keyboard_shortcuts": "Tipkovni prečaci", "footer.privacy_policy": "Pravila o zaštiti privatnosti", "footer.source_code": "Prikaz izvornog koda", + "footer.status": "Stanje", "generic.saved": "Spremljeno", "getting_started.heading": "Počnimo", "hashtag.column_header.tag_mode.all": "i {additional}", @@ -212,7 +230,11 @@ "home.column_settings.show_reblogs": "Pokaži boostove", "home.column_settings.show_replies": "Pokaži odgovore", "home.hide_announcements": "Sakrij najave", + "home.pending_critical_update.title": "Dostupno je kritično sigurnosno ažuriranje!", "home.show_announcements": "Prikaži najave", + "interaction_modal.login.action": "Odvedi me kući", + "interaction_modal.no_account_yet": "Nisi na Mastodonu?", + "interaction_modal.on_this_server": "Na ovom serveru", "intervals.full.days": "{number, plural, one {# dan} other {# dana}}", "intervals.full.hours": "{number, plural, one {# sat} few {# sata} other {# sati}}", "intervals.full.minutes": "{number, plural, one {# minuta} few {# minute} other {# minuta}}", diff --git a/app/javascript/mastodon/locales/sl.json b/app/javascript/mastodon/locales/sl.json index 3841237f47cc9a..63507be791c919 100644 --- a/app/javascript/mastodon/locales/sl.json +++ b/app/javascript/mastodon/locales/sl.json @@ -61,12 +61,12 @@ "account.requested_follow": "{name} vam želi slediti", "account.share": "Deli profil osebe @{name}", "account.show_reblogs": "Pokaži izpostavitve osebe @{name}", - "account.statuses_counter": "{count, plural, one {{count} tut} two {{count} tuta} few {{count} tuti} other {{count} tutov}}", + "account.statuses_counter": "{count, plural, one {{count} objava} two {{count} objavi} few {{count} objave} other {{count} objav}}", "account.unblock": "Odblokiraj @{name}", "account.unblock_domain": "Odblokiraj domeno {domain}", "account.unblock_short": "Odblokiraj", "account.unendorse": "Ne vključi v profil", - "account.unfollow": "Prenehaj slediti", + "account.unfollow": "Ne sledi več", "account.unmute": "Odtišaj @{name}", "account.unmute_notifications_short": "Izklopi utišanje obvestil", "account.unmute_short": "Odtišaj", @@ -185,7 +185,7 @@ "confirmations.redraft.message": "Ali ste prepričani, da želite izbrisati ta status in ga preoblikovati? Vzljubi in izpostavitve bodo izgubljeni, odgovori na izvirno objavo pa bodo osiroteli.", "confirmations.reply.confirm": "Odgovori", "confirmations.reply.message": "Odgovarjanje bo prepisalo sporočilo, ki ga trenutno sestavljate. Ali ste prepričani, da želite nadaljevati?", - "confirmations.unfollow.confirm": "Prenehaj slediti", + "confirmations.unfollow.confirm": "Ne sledi več", "confirmations.unfollow.message": "Ali ste prepričani, da ne želite več slediti {name}?", "conversation.delete": "Izbriši pogovor", "conversation.mark_as_read": "Označi kot prebrano", @@ -301,7 +301,7 @@ "hashtag.counter_by_uses_today": "{count, plural, one {{counter} objava} two {{counter} objavi} few {{counter} objav} other {{counter} objav}}", "hashtag.follow": "Sledi ključniku", "hashtag.unfollow": "Nehaj slediti ključniku", - "hashtags.and_other": "…and {count, plural, one {} two {# več} few {# več}other {# več}}", + "hashtags.and_other": "…in še {count, plural, other {#}}", "home.actions.go_to_explore": "Poglejte, kaj je v trendu", "home.actions.go_to_suggestions": "Poiščite osebe, ki jim želite slediti", "home.column_settings.basic": "Osnovno", diff --git a/config/locales/ar.yml b/config/locales/ar.yml index f8c74207de7434..f43d1ad2a67bad 100644 --- a/config/locales/ar.yml +++ b/config/locales/ar.yml @@ -578,6 +578,7 @@ ar: total_reported: تقارير عنهم total_storage: الوسائط المُرفَقة totals_time_period_hint_html: المجموع المعروض في الأسفل يشمل بيانات منذ البداية. + unknown_instance: لا يوجد حاليا أي تسجيل لهذا النطاق على هذا الخادم. invites: deactivate_all: تعطيلها كافة filter: @@ -1112,6 +1113,11 @@ ar: hint_html: شيء واحد آخر! نحن بحاجة إلى التأكّد من أنك إنسان (حتى نتمكن من تتفادي البريد المزعج!). حل رمز CAPTCHA أدناه وانقر فوق "متابعة". title: التحقق من الأمان confirmations: + awaiting_review: تمّ تأكيد عنوان بريدك الإلكتروني! موظفو %{domain} يعاينونَ تسجيلكَ حاليًا. ستتلقى بريدًا إلكترونيًا إن تَمّ قُبولك! + awaiting_review_title: التسجيل الخاص بك قيد المُعاينة + clicking_this_link: اضغط على هذا الرابط + login_link: تسجيل الدخول + proceed_to_login_html: يمكنكَ الآن الاستمرار إلى %{login_link}. wrong_email_hint: إذا كان عنوان البريد الإلكتروني هذا غير صحيح، يمكنك تغييره في إعدادات الحساب. delete_account: احذف الحساب delete_account_html: إن كنت ترغب في حذف حسابك يُمكنك المواصلة هنا. سوف يُطلَبُ منك التأكيد قبل الحذف. diff --git a/config/locales/cy.yml b/config/locales/cy.yml index baa6d31e3580ba..846ce41def5bd9 100644 --- a/config/locales/cy.yml +++ b/config/locales/cy.yml @@ -1,7 +1,7 @@ --- cy: about: - about_mastodon_html: 'Rhwydwaith cymdeithasol y dyfodol: Dim hysbysebion, dim gwyliadwriaeth gorfforaethol, dylunio moesegol, a datganoli! Chi sy berchen eich data gyda Mastodon!' + about_mastodon_html: 'Rhwydwaith cymdeithasol y dyfodol: Dim hysbysebion, dim gwyliadwriaeth gorfforaethol, dylunio moesegol a datganoli! Chi sy berchen eich data gyda Mastodon!' contact_missing: Heb ei osodF contact_unavailable: Ddim yn berthnasol hosted_on: Mastodon wedi ei weinyddu ar %{domain} @@ -1113,6 +1113,14 @@ cy: hint_html: Un peth arall! Mae angen i ni gadarnhau eich bod yn ddynol (mae hyn er mwyn i ni allu cadw'r sbam allan!). Datryswch y CAPTCHA isod a chliciwch "Parhau". title: Gwiriad diogelwch confirmations: + awaiting_review: Mae eich cyfeiriad e-bost wedi'i gadarnhau! Mae aelod o staff %{domain} wrthi'n adolygu'ch cofrestriad. Byddwch yn derbyn e-bost os ydyn nhw'n cymeradwyo eich cyfrif! + awaiting_review_title: Mae eich cofrestriad yn cael ei adolygu + clicking_this_link: clicio ar y ddolen hon + login_link: mewngofnodi + proceed_to_login_html: Gallwch nawr symud ymlaen i %{login_link}. + redirect_to_app_html: Dylech fod wedi cael eich ailgyfeirio i ap %{app_name}. Os na ddigwyddodd hynny, rhowch gynnig ar %{clicking_this_link} neu ewch eich hun i'r ap. + registration_complete: Mae eich cofrestriad ar %{domain} bellach wedi'i gwblhau! + welcome_title: Croeso, %{name}! wrong_email_hint: Os nad yw'r cyfeiriad e-bost hwnnw'n gywir, gallwch ei newid yng ngosodiadau'r cyfrif. delete_account: Dileu cyfrif delete_account_html: Os hoffech chi ddileu eich cyfrif, mae modd parhau yma. Bydd gofyn i chi gadarnhau. diff --git a/config/locales/doorkeeper.hr.yml b/config/locales/doorkeeper.hr.yml index cb48de313549e1..abf07ab723628a 100644 --- a/config/locales/doorkeeper.hr.yml +++ b/config/locales/doorkeeper.hr.yml @@ -35,8 +35,11 @@ hr: scopes: Odvojite opsege razmacima. Za korištenje zadanih opsega, ostavite prazno. index: application: Aplikacija + delete: Obriši + empty: Nemaš aplikacija. name: Ime new: Nova aplikacija + show: Prikaži title: Vaše aplikacije new: title: Nova aplikacija @@ -60,6 +63,7 @@ hr: confirmations: revoke: Jeste li sigurni? index: + authorized_at: Autorizirano %{date} title: Vaše autorizirane aplikacije errors: messages: @@ -88,6 +92,14 @@ hr: authorized_applications: destroy: notice: Aplikacija je opozvana. + grouped_scopes: + title: + favourites: Favoriti + filters: Filteri + follows: Praćeni + lists: Liste + search: Traži + statuses: Objave layouts: admin: nav: @@ -95,6 +107,7 @@ hr: application: title: Traži se OAuth autorizacija scopes: + admin:read: pročitaj sve podatke na serveru follow: mijenjati odnose između računa read: čitati sve podatke Vašeg računa write: mijenjati sve podatke Vašeg računa diff --git a/config/locales/doorkeeper.sl.yml b/config/locales/doorkeeper.sl.yml index 8fe39bbf61f903..a613308b28afbf 100644 --- a/config/locales/doorkeeper.sl.yml +++ b/config/locales/doorkeeper.sl.yml @@ -4,7 +4,7 @@ sl: attributes: doorkeeper/application: name: Ime programa - redirect_uri: Preusmeritev URI + redirect_uri: URI za preusmeritev scopes: Obsegi website: Spletišče programa errors: @@ -19,7 +19,7 @@ sl: doorkeeper: applications: buttons: - authorize: Overi + authorize: Odobri cancel: Prekliči destroy: Uniči edit: Uredi @@ -55,23 +55,23 @@ sl: title: 'Program: %{name}' authorizations: buttons: - authorize: Overi + authorize: Odobri deny: Zavrni error: title: Prišlo je do napake new: prompt_html: "%{client_name} želi dovoljenje za dostop do vašega računa. Gre za zunanji program. Če mu ne zaupate, mu ne dodelite teh pravic." - review_permissions: Preglej pravice - title: Potrebna je pooblastitev + review_permissions: Preglej dovoljenja + title: Potrebna je odobritev show: - title: Kopirajte to pooblastilno kodo in jo prilepite v program. + title: Kopirajte to odobritveno kodo in jo prilepite v program. authorized_applications: buttons: revoke: Prekliči confirmations: revoke: Ali ste prepričani? index: - authorized_at: Overjen(a) %{date} + authorized_at: Odobreno %{date} description_html: To so programi, ki lahko dostopajo do vašega računa prek vmesnika API. Če so na seznamu programi, ki jih ne prepoznate ali pa se čudno vedejo, lahko prekličete njihovo pravico do dostopa. last_used_at: Zadnjič uporabljeno %{date} never_used: Nikoli uporabljeno @@ -80,14 +80,14 @@ sl: title: Vaši odobreni programi errors: messages: - access_denied: Lastnik virov ali strežnik pooblastil je zavrnil zahtevo. + access_denied: Lastnik virov ali odobritveni strežnik je zavrnil zahtevo. credential_flow_not_configured: Pretok geselskih pooblastil lastnika virov ni uspel, ker Doorkeeper.configure.resource_owner_from_credentials ni nastavljen. - invalid_client: Overitev odjemalca ni uspela zaradi neznanega odjemalca, zaradi nevključitve overitve odjemalca ali zaradi nepodprte metode overitve. - invalid_grant: Predložena odobritev za pooblastilo je neveljavna, potekla, preklicana, se ne ujema z URI preusmeritvijo, ki je uporabljena v zahtevi za pooblastilo ali je bila izdana drugemu odjemalcu. + invalid_client: Odobritev odjemalca ni uspela zaradi neznanega odjemalca, zaradi nevključitve odobritve odjemalca ali zaradi nepodprte metode odobritve. + invalid_grant: Predložena odobritev je neveljavna, je potekla, je preklicana, se ne ujema z URI-jem za preusmeritev uporabljenim v zahtevi za odobritev, ali pa je bila izdana drugemu odjemalcu. invalid_redirect_uri: URI za preusmeritev ni veljaven. invalid_request: missing_param: 'Zahtevani parameter manjka: %{value}.' - request_not_authorized: Zahtevo je potrebno overiti. Zahtevani parameter za overjanje zahteve manjka ali ni veljaven. + request_not_authorized: Zahtevo je potrebno odobriti. Zahtevani parameter za odobritev zahteve manjka ali ni veljaven. unknown: Zahtevku manjka zahtevani parameter, vključuje nepodprto vrednost parametra ali je nepravilno oblikovan. invalid_resource_owner: Predložene poverilnice lastnika virov niso veljavne ali pa lastnika virov ni mogoče najti invalid_scope: Zahtevani obseg je neveljaven, neznan ali nepravilen. @@ -96,11 +96,11 @@ sl: revoked: Žeton za dostop je bil preklican unknown: Žeton za dostop je neveljaven resource_owner_authenticator_not_configured: Iskanje lastnika virov ni uspelo, ker Doorkeeper.configure.resource_owner_authenticator ni nastavljen. - server_error: Strežnik pooblastil je naletel na nepričakovano stanje, ki je preprečilo, da bi izpolnil zahtevo. - temporarily_unavailable: Strežnik pooblastil, zaradi začasne preobremenitve ali vzdrževanja, trenutno ne more obdelati zahteve. - unauthorized_client: Odjemalec nima pooblastila za izvajanje te zahteve po tej metodi. - unsupported_grant_type: Strežnik pooblastil ne podpira vrste odobritve pooblastila. - unsupported_response_type: Strežnik pooblastil ne podpira te vrste odziva. + server_error: Odobritveni strežnik je naletel na nepričakovano stanje, ki je preprečilo, da bi izpolnil zahtevo. + temporarily_unavailable: Odobritveni strežnik zaradi začasne preobremenitve ali vzdrževanja trenutno ne more obdelati zahteve. + unauthorized_client: Odjemalec nima odobritve za izvajanje te zahteve po tej metodi. + unsupported_grant_type: Odobritveni strežnik ne podpira zahtevane vrste odobritve. + unsupported_response_type: Odobritveni strežnik pooblastil ne podpira te vrste odziva. flash: applications: create: @@ -145,7 +145,7 @@ sl: applications: Programi oauth2_provider: Ponudnik OAuth2 application: - title: Potrebna je pooblastitev OAuth + title: Potrebna je odobritev OAuth scopes: admin:read: preberi vse podatke na strežniku admin:read:accounts: preberi občutljive informacije vseh računov diff --git a/config/locales/my.yml b/config/locales/my.yml index 8e01e82afe60e7..d8e83543c691b7 100644 --- a/config/locales/my.yml +++ b/config/locales/my.yml @@ -523,6 +523,7 @@ my: total_reported: "၎င်းတို့နှင့်ဆိုင်သော အစီရင်ခံစာများ" total_storage: မီဒီယာ ပူးတွဲချက်များ totals_time_period_hint_html: အောက်တွင်ဖော်ပြထားသော စုစုပေါင်းမှာ အချိန်တိုင်းအတွက် အချက်အလက်များဖြစ်သည်။ + unknown_instance: လောလောဆယ် ဤဆာဗာတွင် ဤဒိုမိန်း၏ မှတ်တမ်းမရှိပါ။ invites: deactivate_all: အားလုံးပယ်ဖျက်ရန် filter: @@ -1006,6 +1007,13 @@ my: hint_html: နောက်ထပ်တစ်ခုသာ။ သင်သည် လူသားဖြစ်ကြောင်း ကျွန်ုပ်တို့ အတည်ပြုရန် လိုအပ်ပါသည် (စပမ်းများကို ရှောင်ရှားနိုင်စေရန် အတွက်ဖြစ်ပါသည်။) အောက်ပါ CAPTCHA ကိုဖြေရှင်းပြီး "Continue" ကို နှိပ်ပါ။ title: လုံခြုံရေးစစ်ဆေးမှု confirmations: + awaiting_review: သင့်အီးမေးလ်လိပ်စာကို အတည်ပြုပြီးပါပြီ။ %{domain} စီမံသူများမှ ယခု သင်စာရင်းသွင်းခြင်းကို ပြန်လည်သုံးသပ်နေပါသည်။ သင့်အကောင့်ကို အတည်ပြုပါက အီးမေးလ်တစ်စောင် လက်ခံရရှိမည်ဖြစ်သည်။ + awaiting_review_title: သင် စာရင်သွင်းထားခြင်းကို စစ်ဆေးနေပါသည် + clicking_this_link: ဤလင့်ခ်ကို နှိပ်ပါ + login_link: အကောင့်ဝင်ရန် + proceed_to_login_html: သင် ယခု %{login_link} သို့ ဆက်သွားနိုင်ပါပြီ။ + registration_complete: "%{domain} တွင် သင် စာရင်းသွင်းခြင်းမှာ မှန်ကန်ပါပြီ။" + welcome_title: ကြိုဆိုပါတယ် %{name}။ wrong_email_hint: ထိုအီးမေးလ်လိပ်စာ မမှန်ပါက အကောင့်သတ်မှတ်ချက်များတွင် ပြောင်းလဲနိုင်သည်။ delete_account: အကောင့်ဖျက်ပါ delete_account_html: သင့်အကောင့်ဖျက်လိုပါကဤနေရာတွင် ဆက်လက်လုပ်ဆောင်နိုင်သည်။ အတည်ပြုချက်တောင်းပါမည်။ @@ -1067,6 +1075,7 @@ my: functional: သင့်အကောင့်မှာ အပြည့်အဝလုပ်ဆောင်နေပါပြီ။ pending: သင့်အက်ပလီကေးရှင်းကို ကျွန်ုပ်တို့၏ဝန်ထမ်းများမှ ပြန်လည်သုံးသပ်နေပါသည်။ အချိန်အနည်းငယ်ကြာနိုင်ပါသည်။ သင့်အက်ပလီကေးရှင်းကို အတည်ပြုပြီးပါက အီးမေးလ်တစ်စောင် သင် လက်ခံရရှိမည်ဖြစ်သည်။ redirecting_to: သင့်အကောင့်မှာ လက်ရှိတွင် %{acct} သို့ ပြန်ညွှန်းနေသောကြောင့် သုံးစွဲ၍မရပါ။ + self_destruct: "%{domain} ပိတ်သွားသောကြောင့် သင့်အကောင့်သို့ အကန့်အသတ်ဖြင့်သာ ဝင်ရောက်နိုင်မည်ဖြစ်သည်။" view_strikes: သင့်အကောင့်ကို ဆန့်ကျင်သည့် ယခင်ကလုပ်ဆောင်ချက်များကို ကြည့်ပါ too_fast: ဖောင်တင်သည်မှာ မြန်နေပါသည်။ ထပ်စမ်းကြည့်ပါ။ use_security_key: လုံခြုံရေးကီးကို သုံးပါ @@ -1525,6 +1534,8 @@ my: over_daily_limit: ယနေ့အတွက် စီစဉ်ထားသည့် ပို့စ်များ၏ ကန့်သတ်ချက် %{limit} ကို ကျော်လွန်သွားပါပြီ over_total_limit: စီစဉ်ထားသည့် ပို့စ်များ၏ ကန့်သတ်ချက် %{limit} ကို ကျော်လွန်သွားပါပြီ too_soon: စီစဉ်ထားသောရက်စွဲမှာ အနာဂတ်အတွက်ဖြစ်သည် + self_destruct: + title: ဤဆာဗာ ပိတ်ထားပါသည် sessions: activity: နောက်ဆုံးလုပ်ဆောင်ချက် browser: ဘရောက်ဇာ diff --git a/config/locales/simple_form.sq.yml b/config/locales/simple_form.sq.yml index 3256e7378dd915..dc07478070cc2a 100644 --- a/config/locales/simple_form.sq.yml +++ b/config/locales/simple_form.sq.yml @@ -256,8 +256,8 @@ sq: site_contact_username: Emër përdoruesi kontakti site_extended_description: Përshkrim i zgjeruar site_short_description: Përshkrim shërbyesi - site_terms: Rregulla Privatësie - site_title: Emër shërbyesi + site_terms: Politika e privatësisë + site_title: Emri i serverit status_page_url: URL faqeje gjendjesh theme: Temë parazgjedhje thumbnail: Miniaturë shërbyesi diff --git a/config/locales/sk.yml b/config/locales/sk.yml index f16803fb29aa64..b3bc8c2dcd0003 100644 --- a/config/locales/sk.yml +++ b/config/locales/sk.yml @@ -585,6 +585,7 @@ sk: title: Serverové pravidlá settings: about: + manage_rules: Spravuj serverové pravidlá title: Ohľadom appearance: title: Vzhľad @@ -607,9 +608,16 @@ sk: approved: Pre registráciu je nutné povolenie none: Nikto sa nemôže registrovať open: Ktokoľvek sa môže zaregistrovať + title: Nastavenia servera site_uploads: delete: Vymaž nahratý súbor destroyed_msg: Nahratie bolo zo stránky úspešne vymazané! + software_updates: + documentation_link: Zisti viac + title: Dostupné aktualizácie + types: + major: Hlavné vydanie + version: Verzia statuses: account: Autor application: Aplikácia diff --git a/config/locales/uk.yml b/config/locales/uk.yml index 8069c76c6d95c4..85db04715b5727 100644 --- a/config/locales/uk.yml +++ b/config/locales/uk.yml @@ -1077,12 +1077,12 @@ uk: hint_html: Ще одне! Ми повинні пересвідчитись, що ви людина (щоб ми могли уникнути спаму!). Розв'яжіть CAPTCHA внизу і натисніть кнопку "Продовжити". title: Перевірка безпеки confirmations: - awaiting_review: Ваша електронна адреса підтверджена! Співробітники %{domain} тепер переглядають вашу реєстрацію. Ви отримаєте електронного листа, якщо вони затвердять ваш обліковий запис! + awaiting_review: Ваша електронна адреса підтверджена! Наразі співробітники %{domain} розглядають вашу реєстрацію. Ви отримаєте електронний лист, якщо вони затвердять ваш обліковий запис! awaiting_review_title: Ваша реєстрація розглядається clicking_this_link: натисніть це посилання login_link: увійти proceed_to_login_html: Тепер ви можете перейти до %{login_link}. - redirect_to_app_html: Ви мали бути перенаправлені до програми %{app_name}. Якщо цього не сталося, спробуйте %{clicking_this_link} або вручну поверніться до програми. + redirect_to_app_html: Вас мало переспрямувати до програми %{app_name}. Якщо цього не сталося, спробуйте %{clicking_this_link} або вручну поверніться до програми. registration_complete: Ваша реєстрація на %{domain} завершена! welcome_title: Ласкаво просимо, %{name}! wrong_email_hint: Якщо ця адреса електронної пошти неправильна, можна змінити її в налаштуваннях облікового запису. From 5d9e71ebe0e20b56cf5fcf7664938806a829af14 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Fri, 3 Nov 2023 10:12:14 -0400 Subject: [PATCH 2/5] Archive uploaded CI assets into single file between build/test (#27668) --- .github/workflows/test-ruby.yml | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test-ruby.yml b/.github/workflows/test-ruby.yml index 117e751454c6db..66627f9cd35a0d 100644 --- a/.github/workflows/test-ruby.yml +++ b/.github/workflows/test-ruby.yml @@ -48,12 +48,15 @@ jobs: run: |- ./bin/rails assets:precompile + - name: Archive asset artifacts + run: | + tar --exclude={"*.br","*.gz"} -zcf artifacts.tar.gz public/assets public/packs* + - uses: actions/upload-artifact@v3 if: matrix.mode == 'test' with: path: |- - ./public/assets - ./public/packs-test + ./artifacts.tar.gz name: ${{ github.sha }} retention-days: 0 @@ -122,9 +125,13 @@ jobs: - uses: actions/download-artifact@v3 with: - path: './public' + path: './' name: ${{ github.sha }} + - name: Expand archived asset artifacts + run: | + tar xvzf artifacts.tar.gz + - name: Set up Ruby environment uses: ./.github/actions/setup-ruby with: From 9d799d40ba9b7428dc4bbf3246e44399129913f9 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Fri, 3 Nov 2023 10:58:33 -0400 Subject: [PATCH 3/5] Reduce CI job matrix job count (#27660) --- .github/workflows/test-ruby.yml | 8 +------- Gemfile | 3 --- Gemfile.lock | 2 -- 3 files changed, 1 insertion(+), 12 deletions(-) diff --git a/.github/workflows/test-ruby.yml b/.github/workflows/test-ruby.yml index 66627f9cd35a0d..07fd25fb1bffe5 100644 --- a/.github/workflows/test-ruby.yml +++ b/.github/workflows/test-ruby.yml @@ -105,7 +105,6 @@ jobs: SAML_ENABLED: true CAS_ENABLED: true BUNDLE_WITH: 'pam_authentication test' - CI_JOBS: ${{ matrix.ci_job }}/4 GITHUB_RSPEC: ${{ matrix.ruby-version == '.ruby-version' && github.event.pull_request && 'true' }} strategy: @@ -115,11 +114,6 @@ jobs: - '3.0' - '3.1' - '.ruby-version' - ci_job: - - 1 - - 2 - - 3 - - 4 steps: - uses: actions/checkout@v4 @@ -141,7 +135,7 @@ jobs: - name: Load database schema run: './bin/rails db:create db:schema:load db:seed' - - run: bundle exec rake rspec_chunked + - run: bin/rspec test-e2e: name: End to End testing diff --git a/Gemfile b/Gemfile index c208824b49873c..04874debd541b0 100644 --- a/Gemfile +++ b/Gemfile @@ -103,9 +103,6 @@ gem 'rdf-normalize', '~> 0.5' gem 'private_address_check', '~> 0.5' group :test do - # Used to split testing into chunks in CI - gem 'rspec_chunked', '~> 0.6' - # Adds RSpec Error/Warning annotations to GitHub PRs on the Files tab gem 'rspec-github', '~> 2.4', require: false diff --git a/Gemfile.lock b/Gemfile.lock index fa0d988b77834b..4ebc600829244e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -648,7 +648,6 @@ GEM rspec-mocks (~> 3.0) sidekiq (>= 5, < 8) rspec-support (3.12.1) - rspec_chunked (0.6) rubocop (1.57.1) base64 (~> 0.1.1) json (~> 2.3) @@ -919,7 +918,6 @@ DEPENDENCIES rspec-github (~> 2.4) rspec-rails (~> 6.0) rspec-sidekiq (~> 4.0) - rspec_chunked (~> 0.6) rubocop rubocop-capybara rubocop-performance From 3bf2a7296e6d80d5cd9d5d3cba1507af6ba83dfd Mon Sep 17 00:00:00 2001 From: Renaud Chaput Date: Fri, 3 Nov 2023 16:00:03 +0100 Subject: [PATCH 4/5] Use Immutable `Record` for accounts in Redux state (#26559) --- .../mastodon/actions/account_notes.ts | 4 +- app/javascript/mastodon/actions/accounts.js | 189 +++--------------- .../mastodon/actions/accounts_typed.ts | 97 +++++++++ .../mastodon/actions/domain_blocks.js | 26 +-- .../mastodon/actions/domain_blocks_typed.ts | 13 ++ .../mastodon/actions/importer/index.js | 18 +- .../mastodon/actions/importer/normalizer.js | 27 --- .../mastodon/actions/notifications.js | 12 +- .../mastodon/actions/notifications_typed.ts | 23 +++ app/javascript/mastodon/actions/store.js | 1 + app/javascript/mastodon/api_types/accounts.ts | 4 +- .../mastodon/components/account.jsx | 2 +- app/javascript/mastodon/components/avatar.tsx | 3 +- .../mastodon/components/avatar_overlay.tsx | 3 +- .../mastodon/components/display_name.tsx | 3 +- .../mastodon/components/inline_account.jsx | 2 +- app/javascript/mastodon/components/status.jsx | 2 +- .../account/components/account_note.jsx | 2 +- .../account/components/featured_tags.jsx | 2 +- .../components/follow_request_note.jsx | 2 +- .../features/account/components/header.jsx | 2 +- .../account_timeline/components/header.jsx | 2 +- .../components/limited_account_hint.jsx | 40 ---- .../components/limited_account_hint.tsx | 35 ++++ .../features/account_timeline/index.jsx | 2 +- .../compose/components/action_bar.jsx | 2 +- .../components/autosuggest_account.jsx | 2 +- .../compose/components/navigation_bar.jsx | 2 +- .../directory/components/account_card.jsx | 2 +- .../components/account_authorize.jsx | 2 +- .../mastodon/features/followers/index.jsx | 2 +- .../mastodon/features/following/index.jsx | 2 +- .../list_adder/components/account.jsx | 2 +- .../list_editor/components/account.jsx | 2 +- .../components/follow_request.jsx | 2 +- .../notifications/components/report.jsx | 2 +- .../mastodon/features/onboarding/index.jsx | 2 +- .../mastodon/features/onboarding/share.jsx | 2 +- .../picture_in_picture/components/header.jsx | 2 +- .../mastodon/features/report/thanks.jsx | 2 +- .../ui/components/focal_point_modal.jsx | 2 +- .../features/ui/components/report_modal.jsx | 2 +- app/javascript/mastodon/initial_state.js | 40 +--- app/javascript/mastodon/models/account.ts | 149 ++++++++++++++ .../mastodon/models/custom_emoji.ts | 15 ++ .../mastodon/models/relationship.ts | 29 +++ app/javascript/mastodon/reducers/accounts.js | 39 ---- app/javascript/mastodon/reducers/accounts.ts | 82 ++++++++ .../mastodon/reducers/accounts_counters.js | 49 ----- .../mastodon/reducers/accounts_map.js | 8 +- app/javascript/mastodon/reducers/contexts.js | 10 +- .../mastodon/reducers/conversations.js | 14 +- .../mastodon/reducers/domain_lists.js | 6 +- app/javascript/mastodon/reducers/index.ts | 10 +- .../mastodon/reducers/notifications.js | 34 ++-- .../mastodon/reducers/relationships.js | 88 -------- .../mastodon/reducers/relationships.ts | 123 ++++++++++++ .../mastodon/reducers/status_lists.js | 10 +- .../mastodon/reducers/suggestions.js | 14 +- app/javascript/mastodon/reducers/timelines.js | 16 +- .../mastodon/reducers/user_lists.js | 20 +- app/javascript/mastodon/selectors/accounts.ts | 47 +++++ app/javascript/mastodon/selectors/index.js | 18 +- app/javascript/mastodon/store/store.ts | 1 - app/javascript/types/resources.ts | 55 ----- 65 files changed, 765 insertions(+), 662 deletions(-) create mode 100644 app/javascript/mastodon/actions/accounts_typed.ts create mode 100644 app/javascript/mastodon/actions/domain_blocks_typed.ts create mode 100644 app/javascript/mastodon/actions/notifications_typed.ts delete mode 100644 app/javascript/mastodon/features/account_timeline/components/limited_account_hint.jsx create mode 100644 app/javascript/mastodon/features/account_timeline/components/limited_account_hint.tsx create mode 100644 app/javascript/mastodon/models/account.ts create mode 100644 app/javascript/mastodon/models/custom_emoji.ts create mode 100644 app/javascript/mastodon/models/relationship.ts delete mode 100644 app/javascript/mastodon/reducers/accounts.js create mode 100644 app/javascript/mastodon/reducers/accounts.ts delete mode 100644 app/javascript/mastodon/reducers/accounts_counters.js delete mode 100644 app/javascript/mastodon/reducers/relationships.js create mode 100644 app/javascript/mastodon/reducers/relationships.ts create mode 100644 app/javascript/mastodon/selectors/accounts.ts delete mode 100644 app/javascript/types/resources.ts diff --git a/app/javascript/mastodon/actions/account_notes.ts b/app/javascript/mastodon/actions/account_notes.ts index eeef23e3666c50..e524e5235bfe5a 100644 --- a/app/javascript/mastodon/actions/account_notes.ts +++ b/app/javascript/mastodon/actions/account_notes.ts @@ -1,3 +1,4 @@ +import type { ApiRelationshipJSON } from 'mastodon/api_types/relationships'; import { createAppAsyncThunk } from 'mastodon/store/typed_functions'; import api from '../api'; @@ -5,8 +6,7 @@ import api from '../api'; export const submitAccountNote = createAppAsyncThunk( 'account_note/submit', async (args: { id: string; value: string }, { getState }) => { - // TODO: replace `unknown` with `ApiRelationshipJSON` when it is merged - const response = await api(getState).post( + const response = await api(getState).post( `/api/v1/accounts/${args.id}/note`, { comment: args.value, diff --git a/app/javascript/mastodon/actions/accounts.js b/app/javascript/mastodon/actions/accounts.js index 3a85393d6cf273..4a985a41eff0f3 100644 --- a/app/javascript/mastodon/actions/accounts.js +++ b/app/javascript/mastodon/actions/accounts.js @@ -1,5 +1,15 @@ import api, { getLinks } from '../api'; +import { + followAccountSuccess, unfollowAccountSuccess, + authorizeFollowRequestSuccess, rejectFollowRequestSuccess, + followAccountRequest, followAccountFail, + unfollowAccountRequest, unfollowAccountFail, + muteAccountSuccess, unmuteAccountSuccess, + blockAccountSuccess, unblockAccountSuccess, + pinAccountSuccess, unpinAccountSuccess, + fetchRelationshipsSuccess, +} from './accounts_typed'; import { importFetchedAccount, importFetchedAccounts } from './importer'; export const ACCOUNT_FETCH_REQUEST = 'ACCOUNT_FETCH_REQUEST'; @@ -10,36 +20,22 @@ export const ACCOUNT_LOOKUP_REQUEST = 'ACCOUNT_LOOKUP_REQUEST'; export const ACCOUNT_LOOKUP_SUCCESS = 'ACCOUNT_LOOKUP_SUCCESS'; export const ACCOUNT_LOOKUP_FAIL = 'ACCOUNT_LOOKUP_FAIL'; -export const ACCOUNT_FOLLOW_REQUEST = 'ACCOUNT_FOLLOW_REQUEST'; -export const ACCOUNT_FOLLOW_SUCCESS = 'ACCOUNT_FOLLOW_SUCCESS'; -export const ACCOUNT_FOLLOW_FAIL = 'ACCOUNT_FOLLOW_FAIL'; - -export const ACCOUNT_UNFOLLOW_REQUEST = 'ACCOUNT_UNFOLLOW_REQUEST'; -export const ACCOUNT_UNFOLLOW_SUCCESS = 'ACCOUNT_UNFOLLOW_SUCCESS'; -export const ACCOUNT_UNFOLLOW_FAIL = 'ACCOUNT_UNFOLLOW_FAIL'; - export const ACCOUNT_BLOCK_REQUEST = 'ACCOUNT_BLOCK_REQUEST'; -export const ACCOUNT_BLOCK_SUCCESS = 'ACCOUNT_BLOCK_SUCCESS'; export const ACCOUNT_BLOCK_FAIL = 'ACCOUNT_BLOCK_FAIL'; export const ACCOUNT_UNBLOCK_REQUEST = 'ACCOUNT_UNBLOCK_REQUEST'; -export const ACCOUNT_UNBLOCK_SUCCESS = 'ACCOUNT_UNBLOCK_SUCCESS'; export const ACCOUNT_UNBLOCK_FAIL = 'ACCOUNT_UNBLOCK_FAIL'; export const ACCOUNT_MUTE_REQUEST = 'ACCOUNT_MUTE_REQUEST'; -export const ACCOUNT_MUTE_SUCCESS = 'ACCOUNT_MUTE_SUCCESS'; export const ACCOUNT_MUTE_FAIL = 'ACCOUNT_MUTE_FAIL'; export const ACCOUNT_UNMUTE_REQUEST = 'ACCOUNT_UNMUTE_REQUEST'; -export const ACCOUNT_UNMUTE_SUCCESS = 'ACCOUNT_UNMUTE_SUCCESS'; export const ACCOUNT_UNMUTE_FAIL = 'ACCOUNT_UNMUTE_FAIL'; export const ACCOUNT_PIN_REQUEST = 'ACCOUNT_PIN_REQUEST'; -export const ACCOUNT_PIN_SUCCESS = 'ACCOUNT_PIN_SUCCESS'; export const ACCOUNT_PIN_FAIL = 'ACCOUNT_PIN_FAIL'; export const ACCOUNT_UNPIN_REQUEST = 'ACCOUNT_UNPIN_REQUEST'; -export const ACCOUNT_UNPIN_SUCCESS = 'ACCOUNT_UNPIN_SUCCESS'; export const ACCOUNT_UNPIN_FAIL = 'ACCOUNT_UNPIN_FAIL'; export const FOLLOWERS_FETCH_REQUEST = 'FOLLOWERS_FETCH_REQUEST'; @@ -59,7 +55,6 @@ export const FOLLOWING_EXPAND_SUCCESS = 'FOLLOWING_EXPAND_SUCCESS'; export const FOLLOWING_EXPAND_FAIL = 'FOLLOWING_EXPAND_FAIL'; export const RELATIONSHIPS_FETCH_REQUEST = 'RELATIONSHIPS_FETCH_REQUEST'; -export const RELATIONSHIPS_FETCH_SUCCESS = 'RELATIONSHIPS_FETCH_SUCCESS'; export const RELATIONSHIPS_FETCH_FAIL = 'RELATIONSHIPS_FETCH_FAIL'; export const FOLLOW_REQUESTS_FETCH_REQUEST = 'FOLLOW_REQUESTS_FETCH_REQUEST'; @@ -71,15 +66,15 @@ export const FOLLOW_REQUESTS_EXPAND_SUCCESS = 'FOLLOW_REQUESTS_EXPAND_SUCCESS'; export const FOLLOW_REQUESTS_EXPAND_FAIL = 'FOLLOW_REQUESTS_EXPAND_FAIL'; export const FOLLOW_REQUEST_AUTHORIZE_REQUEST = 'FOLLOW_REQUEST_AUTHORIZE_REQUEST'; -export const FOLLOW_REQUEST_AUTHORIZE_SUCCESS = 'FOLLOW_REQUEST_AUTHORIZE_SUCCESS'; export const FOLLOW_REQUEST_AUTHORIZE_FAIL = 'FOLLOW_REQUEST_AUTHORIZE_FAIL'; 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'; export const ACCOUNT_REVEAL = 'ACCOUNT_REVEAL'; +export * from './accounts_typed'; + export function fetchAccount(id) { return (dispatch, getState) => { dispatch(fetchRelationships([id])); @@ -149,12 +144,12 @@ export function followAccount(id, options = { reblogs: true }) { const alreadyFollowing = getState().getIn(['relationships', id, 'following']); const locked = getState().getIn(['accounts', id, 'locked'], false); - dispatch(followAccountRequest(id, locked)); + dispatch(followAccountRequest({ id, locked })); api(getState).post(`/api/v1/accounts/${id}/follow`, options).then(response => { - dispatch(followAccountSuccess(response.data, alreadyFollowing)); + dispatch(followAccountSuccess({relationship: response.data, alreadyFollowing})); }).catch(error => { - dispatch(followAccountFail(error, locked)); + dispatch(followAccountFail({ id, error, locked })); }); }; } @@ -164,74 +159,22 @@ export function unfollowAccount(id) { dispatch(unfollowAccountRequest(id)); api(getState).post(`/api/v1/accounts/${id}/unfollow`).then(response => { - dispatch(unfollowAccountSuccess(response.data, getState().get('statuses'))); + dispatch(unfollowAccountSuccess({relationship: response.data, statuses: getState().get('statuses')})); }).catch(error => { - dispatch(unfollowAccountFail(error)); + dispatch(unfollowAccountFail({ id, error })); }); }; } -export function followAccountRequest(id, locked) { - return { - type: ACCOUNT_FOLLOW_REQUEST, - id, - locked, - skipLoading: true, - }; -} - -export function followAccountSuccess(relationship, alreadyFollowing) { - return { - type: ACCOUNT_FOLLOW_SUCCESS, - relationship, - alreadyFollowing, - skipLoading: true, - }; -} - -export function followAccountFail(error, locked) { - return { - type: ACCOUNT_FOLLOW_FAIL, - error, - locked, - skipLoading: true, - }; -} - -export function unfollowAccountRequest(id) { - return { - type: ACCOUNT_UNFOLLOW_REQUEST, - id, - skipLoading: true, - }; -} - -export function unfollowAccountSuccess(relationship, statuses) { - return { - type: ACCOUNT_UNFOLLOW_SUCCESS, - relationship, - statuses, - skipLoading: true, - }; -} - -export function unfollowAccountFail(error) { - return { - type: ACCOUNT_UNFOLLOW_FAIL, - error, - skipLoading: true, - }; -} - export function blockAccount(id) { return (dispatch, getState) => { dispatch(blockAccountRequest(id)); api(getState).post(`/api/v1/accounts/${id}/block`).then(response => { // Pass in entire statuses map so we can use it to filter stuff in different parts of the reducers - dispatch(blockAccountSuccess(response.data, getState().get('statuses'))); + dispatch(blockAccountSuccess({ relationship: response.data, statuses: getState().get('statuses') })); }).catch(error => { - dispatch(blockAccountFail(id, error)); + dispatch(blockAccountFail({ id, error })); }); }; } @@ -241,9 +184,9 @@ export function unblockAccount(id) { dispatch(unblockAccountRequest(id)); api(getState).post(`/api/v1/accounts/${id}/unblock`).then(response => { - dispatch(unblockAccountSuccess(response.data)); + dispatch(unblockAccountSuccess({ relationship: response.data })); }).catch(error => { - dispatch(unblockAccountFail(id, error)); + dispatch(unblockAccountFail({ id, error })); }); }; } @@ -254,15 +197,6 @@ export function blockAccountRequest(id) { id, }; } - -export function blockAccountSuccess(relationship, statuses) { - return { - type: ACCOUNT_BLOCK_SUCCESS, - relationship, - statuses, - }; -} - export function blockAccountFail(error) { return { type: ACCOUNT_BLOCK_FAIL, @@ -277,13 +211,6 @@ export function unblockAccountRequest(id) { }; } -export function unblockAccountSuccess(relationship) { - return { - type: ACCOUNT_UNBLOCK_SUCCESS, - relationship, - }; -} - export function unblockAccountFail(error) { return { type: ACCOUNT_UNBLOCK_FAIL, @@ -298,9 +225,9 @@ export function muteAccount(id, notifications, duration=0) { api(getState).post(`/api/v1/accounts/${id}/mute`, { notifications, duration }).then(response => { // Pass in entire statuses map so we can use it to filter stuff in different parts of the reducers - dispatch(muteAccountSuccess(response.data, getState().get('statuses'))); + dispatch(muteAccountSuccess({ relationship: response.data, statuses: getState().get('statuses') })); }).catch(error => { - dispatch(muteAccountFail(id, error)); + dispatch(muteAccountFail({ id, error })); }); }; } @@ -310,9 +237,9 @@ export function unmuteAccount(id) { dispatch(unmuteAccountRequest(id)); api(getState).post(`/api/v1/accounts/${id}/unmute`).then(response => { - dispatch(unmuteAccountSuccess(response.data)); + dispatch(unmuteAccountSuccess({ relationship: response.data })); }).catch(error => { - dispatch(unmuteAccountFail(id, error)); + dispatch(unmuteAccountFail({ id, error })); }); }; } @@ -324,14 +251,6 @@ export function muteAccountRequest(id) { }; } -export function muteAccountSuccess(relationship, statuses) { - return { - type: ACCOUNT_MUTE_SUCCESS, - relationship, - statuses, - }; -} - export function muteAccountFail(error) { return { type: ACCOUNT_MUTE_FAIL, @@ -346,13 +265,6 @@ export function unmuteAccountRequest(id) { }; } -export function unmuteAccountSuccess(relationship) { - return { - type: ACCOUNT_UNMUTE_SUCCESS, - relationship, - }; -} - export function unmuteAccountFail(error) { return { type: ACCOUNT_UNMUTE_FAIL, @@ -549,7 +461,7 @@ export function fetchRelationships(accountIds) { dispatch(fetchRelationshipsRequest(newAccountIds)); api(getState).get(`/api/v1/accounts/relationships?${newAccountIds.map(id => `id[]=${id}`).join('&')}`).then(response => { - dispatch(fetchRelationshipsSuccess(response.data)); + dispatch(fetchRelationshipsSuccess({ relationships: response.data })); }).catch(error => { dispatch(fetchRelationshipsFail(error)); }); @@ -564,14 +476,6 @@ export function fetchRelationshipsRequest(ids) { }; } -export function fetchRelationshipsSuccess(relationships) { - return { - type: RELATIONSHIPS_FETCH_SUCCESS, - relationships, - skipLoading: true, - }; -} - export function fetchRelationshipsFail(error) { return { type: RELATIONSHIPS_FETCH_FAIL, @@ -659,7 +563,7 @@ export function authorizeFollowRequest(id) { api(getState) .post(`/api/v1/follow_requests/${id}/authorize`) - .then(() => dispatch(authorizeFollowRequestSuccess(id))) + .then(() => dispatch(authorizeFollowRequestSuccess({ id }))) .catch(error => dispatch(authorizeFollowRequestFail(id, error))); }; } @@ -671,13 +575,6 @@ export function authorizeFollowRequestRequest(id) { }; } -export function authorizeFollowRequestSuccess(id) { - return { - type: FOLLOW_REQUEST_AUTHORIZE_SUCCESS, - id, - }; -} - export function authorizeFollowRequestFail(id, error) { return { type: FOLLOW_REQUEST_AUTHORIZE_FAIL, @@ -693,7 +590,7 @@ export function rejectFollowRequest(id) { api(getState) .post(`/api/v1/follow_requests/${id}/reject`) - .then(() => dispatch(rejectFollowRequestSuccess(id))) + .then(() => dispatch(rejectFollowRequestSuccess({ id }))) .catch(error => dispatch(rejectFollowRequestFail(id, error))); }; } @@ -705,13 +602,6 @@ export function rejectFollowRequestRequest(id) { }; } -export function rejectFollowRequestSuccess(id) { - return { - type: FOLLOW_REQUEST_REJECT_SUCCESS, - id, - }; -} - export function rejectFollowRequestFail(id, error) { return { type: FOLLOW_REQUEST_REJECT_FAIL, @@ -725,7 +615,7 @@ export function pinAccount(id) { dispatch(pinAccountRequest(id)); api(getState).post(`/api/v1/accounts/${id}/pin`).then(response => { - dispatch(pinAccountSuccess(response.data)); + dispatch(pinAccountSuccess({ relationship: response.data })); }).catch(error => { dispatch(pinAccountFail(error)); }); @@ -737,7 +627,7 @@ export function unpinAccount(id) { dispatch(unpinAccountRequest(id)); api(getState).post(`/api/v1/accounts/${id}/unpin`).then(response => { - dispatch(unpinAccountSuccess(response.data)); + dispatch(unpinAccountSuccess({ relationship: response.data })); }).catch(error => { dispatch(unpinAccountFail(error)); }); @@ -751,13 +641,6 @@ export function pinAccountRequest(id) { }; } -export function pinAccountSuccess(relationship) { - return { - type: ACCOUNT_PIN_SUCCESS, - relationship, - }; -} - export function pinAccountFail(error) { return { type: ACCOUNT_PIN_FAIL, @@ -772,21 +655,9 @@ export function unpinAccountRequest(id) { }; } -export function unpinAccountSuccess(relationship) { - return { - type: ACCOUNT_UNPIN_SUCCESS, - relationship, - }; -} - export function unpinAccountFail(error) { return { type: ACCOUNT_UNPIN_FAIL, error, }; } - -export const revealAccount = id => ({ - type: ACCOUNT_REVEAL, - id, -}); diff --git a/app/javascript/mastodon/actions/accounts_typed.ts b/app/javascript/mastodon/actions/accounts_typed.ts new file mode 100644 index 00000000000000..b908e7528eeb60 --- /dev/null +++ b/app/javascript/mastodon/actions/accounts_typed.ts @@ -0,0 +1,97 @@ +import { createAction } from '@reduxjs/toolkit'; + +import type { ApiAccountJSON } from 'mastodon/api_types/accounts'; +import type { ApiRelationshipJSON } from 'mastodon/api_types/relationships'; + +export const revealAccount = createAction<{ + id: string; +}>('accounts/revealAccount'); + +export const importAccounts = createAction<{ accounts: ApiAccountJSON[] }>( + 'accounts/importAccounts', +); + +function actionWithSkipLoadingTrue(args: Args) { + return { + payload: { + ...args, + skipLoading: true, + }, + }; +} + +export const followAccountSuccess = createAction( + 'accounts/followAccountSuccess', + actionWithSkipLoadingTrue<{ + relationship: ApiRelationshipJSON; + alreadyFollowing: boolean; + }>, +); + +export const unfollowAccountSuccess = createAction( + 'accounts/unfollowAccountSuccess', + actionWithSkipLoadingTrue<{ + relationship: ApiRelationshipJSON; + statuses: unknown; + alreadyFollowing?: boolean; + }>, +); + +export const authorizeFollowRequestSuccess = createAction<{ id: string }>( + 'accounts/followRequestAuthorizeSuccess', +); + +export const rejectFollowRequestSuccess = createAction<{ id: string }>( + 'accounts/followRequestRejectSuccess', +); + +export const followAccountRequest = createAction( + 'accounts/followRequest', + actionWithSkipLoadingTrue<{ id: string; locked: boolean }>, +); + +export const followAccountFail = createAction( + 'accounts/followFail', + actionWithSkipLoadingTrue<{ id: string; error: string; locked: boolean }>, +); + +export const unfollowAccountRequest = createAction( + 'accounts/unfollowRequest', + actionWithSkipLoadingTrue<{ id: string }>, +); + +export const unfollowAccountFail = createAction( + 'accounts/unfollowFail', + actionWithSkipLoadingTrue<{ id: string; error: string }>, +); + +export const blockAccountSuccess = createAction<{ + relationship: ApiRelationshipJSON; + statuses: unknown; +}>('accounts/blockSuccess'); + +export const unblockAccountSuccess = createAction<{ + relationship: ApiRelationshipJSON; +}>('accounts/unblockSuccess'); + +export const muteAccountSuccess = createAction<{ + relationship: ApiRelationshipJSON; + statuses: unknown; +}>('accounts/muteSuccess'); + +export const unmuteAccountSuccess = createAction<{ + relationship: ApiRelationshipJSON; +}>('accounts/unmuteSuccess'); + +export const pinAccountSuccess = createAction<{ + relationship: ApiRelationshipJSON; +}>('accounts/pinSuccess'); + +export const unpinAccountSuccess = createAction<{ + relationship: ApiRelationshipJSON; +}>('accounts/unpinSuccess'); + +export const fetchRelationshipsSuccess = createAction( + 'relationships/fetchSuccess', + actionWithSkipLoadingTrue<{ relationships: ApiRelationshipJSON[] }>, +); diff --git a/app/javascript/mastodon/actions/domain_blocks.js b/app/javascript/mastodon/actions/domain_blocks.js index d06de20a2d17f5..718002613f4053 100644 --- a/app/javascript/mastodon/actions/domain_blocks.js +++ b/app/javascript/mastodon/actions/domain_blocks.js @@ -1,11 +1,13 @@ import api, { getLinks } from '../api'; +import { blockDomainSuccess, unblockDomainSuccess } from "./domain_blocks_typed"; + +export * from "./domain_blocks_typed"; + export const DOMAIN_BLOCK_REQUEST = 'DOMAIN_BLOCK_REQUEST'; -export const DOMAIN_BLOCK_SUCCESS = 'DOMAIN_BLOCK_SUCCESS'; export const DOMAIN_BLOCK_FAIL = 'DOMAIN_BLOCK_FAIL'; export const DOMAIN_UNBLOCK_REQUEST = 'DOMAIN_UNBLOCK_REQUEST'; -export const DOMAIN_UNBLOCK_SUCCESS = 'DOMAIN_UNBLOCK_SUCCESS'; export const DOMAIN_UNBLOCK_FAIL = 'DOMAIN_UNBLOCK_FAIL'; export const DOMAIN_BLOCKS_FETCH_REQUEST = 'DOMAIN_BLOCKS_FETCH_REQUEST'; @@ -24,7 +26,7 @@ export function blockDomain(domain) { 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)); + dispatch(blockDomainSuccess({ domain, accounts })); }).catch(err => { dispatch(blockDomainFail(domain, err)); }); @@ -38,14 +40,6 @@ export function blockDomainRequest(domain) { }; } -export function blockDomainSuccess(domain, accounts) { - return { - type: DOMAIN_BLOCK_SUCCESS, - domain, - accounts, - }; -} - export function blockDomainFail(domain, error) { return { type: DOMAIN_BLOCK_FAIL, @@ -61,7 +55,7 @@ export function unblockDomain(domain) { api(getState).delete('/api/v1/domain_blocks', { params: { domain } }).then(() => { 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)); + dispatch(unblockDomainSuccess({ domain, accounts })); }).catch(err => { dispatch(unblockDomainFail(domain, err)); }); @@ -75,14 +69,6 @@ export function unblockDomainRequest(domain) { }; } -export function unblockDomainSuccess(domain, accounts) { - return { - type: DOMAIN_UNBLOCK_SUCCESS, - domain, - accounts, - }; -} - export function unblockDomainFail(domain, error) { return { type: DOMAIN_UNBLOCK_FAIL, diff --git a/app/javascript/mastodon/actions/domain_blocks_typed.ts b/app/javascript/mastodon/actions/domain_blocks_typed.ts new file mode 100644 index 00000000000000..08e0b4a1788e60 --- /dev/null +++ b/app/javascript/mastodon/actions/domain_blocks_typed.ts @@ -0,0 +1,13 @@ +import { createAction } from '@reduxjs/toolkit'; + +import type { Account } from 'mastodon/models/account'; + +export const blockDomainSuccess = createAction<{ + domain: string; + accounts: Account[]; +}>('domain_blocks/blockSuccess'); + +export const unblockDomainSuccess = createAction<{ + domain: string; + accounts: Account[]; +}>('domain_blocks/unblockSuccess'); diff --git a/app/javascript/mastodon/actions/importer/index.js b/app/javascript/mastodon/actions/importer/index.js index 369be6b8fbc0ca..16f191b5846f63 100644 --- a/app/javascript/mastodon/actions/importer/index.js +++ b/app/javascript/mastodon/actions/importer/index.js @@ -1,7 +1,7 @@ -import { normalizeAccount, normalizeStatus, normalizePoll } from './normalizer'; +import { importAccounts } from '../accounts_typed'; + +import { normalizeStatus, normalizePoll } 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'; export const POLLS_IMPORT = 'POLLS_IMPORT'; @@ -13,14 +13,6 @@ function pushUnique(array, 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 }; } @@ -45,7 +37,7 @@ export function importFetchedAccounts(accounts) { const normalAccounts = []; function processAccount(account) { - pushUnique(normalAccounts, normalizeAccount(account)); + pushUnique(normalAccounts, account); if (account.moved) { processAccount(account.moved); @@ -54,7 +46,7 @@ export function importFetchedAccounts(accounts) { accounts.forEach(processAccount); - return importAccounts(normalAccounts); + return importAccounts({ accounts: normalAccounts }); } export function importFetchedStatus(status) { diff --git a/app/javascript/mastodon/actions/importer/normalizer.js b/app/javascript/mastodon/actions/importer/normalizer.js index a72142a86f70bb..b5a30343e488c4 100644 --- a/app/javascript/mastodon/actions/importer/normalizer.js +++ b/app/javascript/mastodon/actions/importer/normalizer.js @@ -2,7 +2,6 @@ import escapeTextContentForBrowser from 'escape-html'; import emojify from '../../features/emoji/emoji'; import { expandSpoilers } from '../../initial_state'; -import { unescapeHTML } from '../../utils/html'; const domParser = new DOMParser(); @@ -17,32 +16,6 @@ export function searchTextFromRawStatus (status) { return domParser.parseFromString(searchContent, 'text/html').documentElement.textContent; } -export function normalizeAccount(account) { - account = { ...account }; - - const emojiMap = makeEmojiMap(account.emojis); - const displayName = account.display_name.trim().length === 0 ? account.username : account.display_name; - - account.display_name_html = emojify(escapeTextContentForBrowser(displayName), emojiMap); - account.note_emojified = emojify(account.note, emojiMap); - account.note_plain = unescapeHTML(account.note); - - if (account.fields) { - account.fields = account.fields.map(pair => ({ - ...pair, - name_emojified: emojify(escapeTextContentForBrowser(pair.name), emojiMap), - value_emojified: emojify(pair.value, emojiMap), - value_plain: unescapeHTML(pair.value), - })); - } - - if (account.moved) { - account.moved = account.moved.id; - } - - return account; -} - export function normalizeFilterResult(result) { const normalResult = { ...result }; diff --git a/app/javascript/mastodon/actions/notifications.js b/app/javascript/mastodon/actions/notifications.js index 02fe10ba56e4a0..878ff5d89d8946 100644 --- a/app/javascript/mastodon/actions/notifications.js +++ b/app/javascript/mastodon/actions/notifications.js @@ -18,10 +18,12 @@ import { importFetchedStatuses, } from './importer'; import { submitMarkers } from './markers'; +import { notificationsUpdate } from "./notifications_typed"; import { register as registerPushNotifications } from './push_notifications'; import { saveSettings } from './settings'; -export const NOTIFICATIONS_UPDATE = 'NOTIFICATIONS_UPDATE'; +export * from "./notifications_typed"; + export const NOTIFICATIONS_UPDATE_NOOP = 'NOTIFICATIONS_UPDATE_NOOP'; export const NOTIFICATIONS_EXPAND_REQUEST = 'NOTIFICATIONS_EXPAND_REQUEST'; @@ -95,12 +97,8 @@ export function updateNotifications(notification, intlMessages, intlLocale) { dispatch(importFetchedAccount(notification.report.target_account)); } - dispatch({ - type: NOTIFICATIONS_UPDATE, - notification, - usePendingItems: preferPendingItems, - meta: (playSound && !filtered) ? { sound: 'boop' } : undefined, - }); + + dispatch(notificationsUpdate(notification, preferPendingItems, playSound && !filtered)); fetchRelatedRelationships(dispatch, [notification]); } else if (playSound && !filtered) { diff --git a/app/javascript/mastodon/actions/notifications_typed.ts b/app/javascript/mastodon/actions/notifications_typed.ts new file mode 100644 index 00000000000000..7e51fa51e7b7e8 --- /dev/null +++ b/app/javascript/mastodon/actions/notifications_typed.ts @@ -0,0 +1,23 @@ +import { createAction } from '@reduxjs/toolkit'; + +import type { ApiAccountJSON } from '../api_types/accounts'; +// To be replaced once ApiNotificationJSON type exists +interface FakeApiNotificationJSON { + type: string; + account: ApiAccountJSON; +} + +export const notificationsUpdate = createAction( + 'notifications/update', + ({ + playSound, + ...args + }: { + notification: FakeApiNotificationJSON; + usePendingItems: boolean; + playSound: boolean; + }) => ({ + payload: args, + meta: { playSound: playSound ? { sound: 'boop' } : undefined }, + }), +); diff --git a/app/javascript/mastodon/actions/store.js b/app/javascript/mastodon/actions/store.js index 682b0f5db7e4a0..8ab75cdc444620 100644 --- a/app/javascript/mastodon/actions/store.js +++ b/app/javascript/mastodon/actions/store.js @@ -11,6 +11,7 @@ const convertState = rawState => fromJS(rawState, (k, v) => Iterable.isIndexed(v) ? v.toList() : v.toMap()); + export function hydrateStore(rawState) { return dispatch => { const state = convertState(rawState); diff --git a/app/javascript/mastodon/api_types/accounts.ts b/app/javascript/mastodon/api_types/accounts.ts index 9d740c96de6977..ce55dc604ad001 100644 --- a/app/javascript/mastodon/api_types/accounts.ts +++ b/app/javascript/mastodon/api_types/accounts.ts @@ -31,9 +31,9 @@ export interface ApiAccountJSON { id: string; last_status_at: string; locked: boolean; - noindex: boolean; + noindex?: boolean; note: string; - roles: ApiAccountJSON[]; + roles?: ApiAccountJSON[]; statuses_count: number; uri: string; url: string; diff --git a/app/javascript/mastodon/components/account.jsx b/app/javascript/mastodon/components/account.jsx index e4e3c88b6f2fae..aa18ce79a5e52d 100644 --- a/app/javascript/mastodon/components/account.jsx +++ b/app/javascript/mastodon/components/account.jsx @@ -36,7 +36,7 @@ class Account extends ImmutablePureComponent { static propTypes = { size: PropTypes.number, - account: ImmutablePropTypes.map, + account: ImmutablePropTypes.record, onFollow: PropTypes.func.isRequired, onBlock: PropTypes.func.isRequired, onMute: PropTypes.func.isRequired, diff --git a/app/javascript/mastodon/components/avatar.tsx b/app/javascript/mastodon/components/avatar.tsx index 5f9bb390e82ea5..77bd4234094766 100644 --- a/app/javascript/mastodon/components/avatar.tsx +++ b/app/javascript/mastodon/components/avatar.tsx @@ -1,7 +1,8 @@ import classNames from 'classnames'; +import type { Account } from 'mastodon/models/account'; + import { useHovering } from '../../hooks/useHovering'; -import type { Account } from '../../types/resources'; import { autoPlayGif } from '../initial_state'; interface Props { diff --git a/app/javascript/mastodon/components/avatar_overlay.tsx b/app/javascript/mastodon/components/avatar_overlay.tsx index 61de9d0beba488..f98cfcc38b4eeb 100644 --- a/app/javascript/mastodon/components/avatar_overlay.tsx +++ b/app/javascript/mastodon/components/avatar_overlay.tsx @@ -1,5 +1,6 @@ +import type { Account } from 'mastodon/models/account'; + import { useHovering } from '../../hooks/useHovering'; -import type { Account } from '../../types/resources'; import { autoPlayGif } from '../initial_state'; interface Props { diff --git a/app/javascript/mastodon/components/display_name.tsx b/app/javascript/mastodon/components/display_name.tsx index 82a42bb022636c..8409244827ebb4 100644 --- a/app/javascript/mastodon/components/display_name.tsx +++ b/app/javascript/mastodon/components/display_name.tsx @@ -2,7 +2,8 @@ import React from 'react'; import type { List } from 'immutable'; -import type { Account } from '../../types/resources'; +import type { Account } from 'mastodon/models/account'; + import { autoPlayGif } from '../initial_state'; import { Skeleton } from './skeleton'; diff --git a/app/javascript/mastodon/components/inline_account.jsx b/app/javascript/mastodon/components/inline_account.jsx index f9767c29d42539..792c412287759e 100644 --- a/app/javascript/mastodon/components/inline_account.jsx +++ b/app/javascript/mastodon/components/inline_account.jsx @@ -19,7 +19,7 @@ const makeMapStateToProps = () => { class InlineAccount extends PureComponent { static propTypes = { - account: ImmutablePropTypes.map.isRequired, + account: ImmutablePropTypes.record.isRequired, }; render () { diff --git a/app/javascript/mastodon/components/status.jsx b/app/javascript/mastodon/components/status.jsx index e065b4816ac480..c141ee2da3ef19 100644 --- a/app/javascript/mastodon/components/status.jsx +++ b/app/javascript/mastodon/components/status.jsx @@ -80,7 +80,7 @@ class Status extends ImmutablePureComponent { static propTypes = { status: ImmutablePropTypes.map, - account: ImmutablePropTypes.map, + account: ImmutablePropTypes.record, previousId: PropTypes.string, nextInReplyToId: PropTypes.string, rootId: PropTypes.string, diff --git a/app/javascript/mastodon/features/account/components/account_note.jsx b/app/javascript/mastodon/features/account/components/account_note.jsx index bab523acf63bfe..272a4ee312c08d 100644 --- a/app/javascript/mastodon/features/account/components/account_note.jsx +++ b/app/javascript/mastodon/features/account/components/account_note.jsx @@ -49,7 +49,7 @@ class InlineAlert extends PureComponent { class AccountNote extends ImmutablePureComponent { static propTypes = { - account: ImmutablePropTypes.map.isRequired, + account: ImmutablePropTypes.record.isRequired, value: PropTypes.string, onSave: PropTypes.func.isRequired, intl: PropTypes.object.isRequired, diff --git a/app/javascript/mastodon/features/account/components/featured_tags.jsx b/app/javascript/mastodon/features/account/components/featured_tags.jsx index bbbec1b86470c2..4d7dd86560a34d 100644 --- a/app/javascript/mastodon/features/account/components/featured_tags.jsx +++ b/app/javascript/mastodon/features/account/components/featured_tags.jsx @@ -15,7 +15,7 @@ const messages = defineMessages({ class FeaturedTags extends ImmutablePureComponent { static propTypes = { - account: ImmutablePropTypes.map, + account: ImmutablePropTypes.record, featuredTags: ImmutablePropTypes.list, tagged: PropTypes.string, intl: PropTypes.object.isRequired, diff --git a/app/javascript/mastodon/features/account/components/follow_request_note.jsx b/app/javascript/mastodon/features/account/components/follow_request_note.jsx index 0e597a70505771..685c282df27761 100644 --- a/app/javascript/mastodon/features/account/components/follow_request_note.jsx +++ b/app/javascript/mastodon/features/account/components/follow_request_note.jsx @@ -11,7 +11,7 @@ import { Icon } from 'mastodon/components/icon'; export default class FollowRequestNote extends ImmutablePureComponent { static propTypes = { - account: ImmutablePropTypes.map.isRequired, + account: ImmutablePropTypes.record.isRequired, }; render () { diff --git a/app/javascript/mastodon/features/account/components/header.jsx b/app/javascript/mastodon/features/account/components/header.jsx index 76074225adf5c2..e546c756934656 100644 --- a/app/javascript/mastodon/features/account/components/header.jsx +++ b/app/javascript/mastodon/features/account/components/header.jsx @@ -91,7 +91,7 @@ const dateFormatOptions = { class Header extends ImmutablePureComponent { static propTypes = { - account: ImmutablePropTypes.map, + account: ImmutablePropTypes.record, identity_props: ImmutablePropTypes.list, onFollow: PropTypes.func.isRequired, onBlock: PropTypes.func.isRequired, diff --git a/app/javascript/mastodon/features/account_timeline/components/header.jsx b/app/javascript/mastodon/features/account_timeline/components/header.jsx index aede7e495744d0..7de8d3771b4683 100644 --- a/app/javascript/mastodon/features/account_timeline/components/header.jsx +++ b/app/javascript/mastodon/features/account_timeline/components/header.jsx @@ -17,7 +17,7 @@ import MovedNote from './moved_note'; class Header extends ImmutablePureComponent { static propTypes = { - account: ImmutablePropTypes.map, + account: ImmutablePropTypes.record, onFollow: PropTypes.func.isRequired, onBlock: PropTypes.func.isRequired, onMention: PropTypes.func.isRequired, diff --git a/app/javascript/mastodon/features/account_timeline/components/limited_account_hint.jsx b/app/javascript/mastodon/features/account_timeline/components/limited_account_hint.jsx deleted file mode 100644 index 59b7358233e8bd..00000000000000 --- a/app/javascript/mastodon/features/account_timeline/components/limited_account_hint.jsx +++ /dev/null @@ -1,40 +0,0 @@ -import PropTypes from 'prop-types'; -import { PureComponent } from 'react'; - -import { FormattedMessage } from 'react-intl'; - -import { connect } from 'react-redux'; - -import { revealAccount } from 'mastodon/actions/accounts'; -import { Button } from 'mastodon/components/button'; -import { domain } from 'mastodon/initial_state'; - -const mapDispatchToProps = (dispatch, { accountId }) => ({ - - reveal () { - dispatch(revealAccount(accountId)); - }, - -}); - -class LimitedAccountHint extends PureComponent { - - static propTypes = { - accountId: PropTypes.string.isRequired, - reveal: PropTypes.func, - }; - - render () { - const { reveal } = this.props; - - return ( -
-

- -
- ); - } - -} - -export default connect(() => {}, mapDispatchToProps)(LimitedAccountHint); diff --git a/app/javascript/mastodon/features/account_timeline/components/limited_account_hint.tsx b/app/javascript/mastodon/features/account_timeline/components/limited_account_hint.tsx new file mode 100644 index 00000000000000..f06bf574a77810 --- /dev/null +++ b/app/javascript/mastodon/features/account_timeline/components/limited_account_hint.tsx @@ -0,0 +1,35 @@ +import { useCallback } from 'react'; + +import { FormattedMessage } from 'react-intl'; + +import { revealAccount } from 'mastodon/actions/accounts_typed'; +import { Button } from 'mastodon/components/button'; +import { domain } from 'mastodon/initial_state'; +import { useAppDispatch } from 'mastodon/store'; + +export const LimitedAccountHint: React.FC<{ accountId: string }> = ({ + accountId, +}) => { + const dispatch = useAppDispatch(); + const reveal = useCallback(() => { + dispatch(revealAccount({ id: accountId })); + }, [dispatch, accountId]); + + return ( +
+

+ +

+ +
+ ); +}; diff --git a/app/javascript/mastodon/features/account_timeline/index.jsx b/app/javascript/mastodon/features/account_timeline/index.jsx index 5dae66b46365b0..5ec029593d5344 100644 --- a/app/javascript/mastodon/features/account_timeline/index.jsx +++ b/app/javascript/mastodon/features/account_timeline/index.jsx @@ -21,7 +21,7 @@ import { LoadingIndicator } from '../../components/loading_indicator'; import StatusList from '../../components/status_list'; import Column from '../ui/components/column'; -import LimitedAccountHint from './components/limited_account_hint'; +import { LimitedAccountHint } from './components/limited_account_hint'; import HeaderContainer from './containers/header_container'; const emptyList = ImmutableList(); diff --git a/app/javascript/mastodon/features/compose/components/action_bar.jsx b/app/javascript/mastodon/features/compose/components/action_bar.jsx index f7488cf5541857..3e2109092576f7 100644 --- a/app/javascript/mastodon/features/compose/components/action_bar.jsx +++ b/app/javascript/mastodon/features/compose/components/action_bar.jsx @@ -28,7 +28,7 @@ const messages = defineMessages({ class ActionBar extends PureComponent { static propTypes = { - account: ImmutablePropTypes.map.isRequired, + account: ImmutablePropTypes.record.isRequired, onLogout: PropTypes.func.isRequired, intl: PropTypes.object.isRequired, }; diff --git a/app/javascript/mastodon/features/compose/components/autosuggest_account.jsx b/app/javascript/mastodon/features/compose/components/autosuggest_account.jsx index ebda0590e3eb20..0a73bc1020f4f6 100644 --- a/app/javascript/mastodon/features/compose/components/autosuggest_account.jsx +++ b/app/javascript/mastodon/features/compose/components/autosuggest_account.jsx @@ -7,7 +7,7 @@ import { DisplayName } from '../../../components/display_name'; export default class AutosuggestAccount extends ImmutablePureComponent { static propTypes = { - account: ImmutablePropTypes.map.isRequired, + account: ImmutablePropTypes.record.isRequired, }; render () { diff --git a/app/javascript/mastodon/features/compose/components/navigation_bar.jsx b/app/javascript/mastodon/features/compose/components/navigation_bar.jsx index e842ab1f8d5037..2f0bb79f891827 100644 --- a/app/javascript/mastodon/features/compose/components/navigation_bar.jsx +++ b/app/javascript/mastodon/features/compose/components/navigation_bar.jsx @@ -14,7 +14,7 @@ import ActionBar from './action_bar'; export default class NavigationBar extends ImmutablePureComponent { static propTypes = { - account: ImmutablePropTypes.map.isRequired, + account: ImmutablePropTypes.record.isRequired, onLogout: PropTypes.func.isRequired, onClose: PropTypes.func, }; diff --git a/app/javascript/mastodon/features/directory/components/account_card.jsx b/app/javascript/mastodon/features/directory/components/account_card.jsx index a5e5717b329df0..ff1f8a653b5561 100644 --- a/app/javascript/mastodon/features/directory/components/account_card.jsx +++ b/app/javascript/mastodon/features/directory/components/account_card.jsx @@ -102,7 +102,7 @@ const mapDispatchToProps = (dispatch, { intl }) => ({ class AccountCard extends ImmutablePureComponent { static propTypes = { - account: ImmutablePropTypes.map.isRequired, + account: ImmutablePropTypes.record.isRequired, intl: PropTypes.object.isRequired, onFollow: PropTypes.func.isRequired, onBlock: PropTypes.func.isRequired, diff --git a/app/javascript/mastodon/features/follow_requests/components/account_authorize.jsx b/app/javascript/mastodon/features/follow_requests/components/account_authorize.jsx index b5dfe510e90946..ca2b454143a468 100644 --- a/app/javascript/mastodon/features/follow_requests/components/account_authorize.jsx +++ b/app/javascript/mastodon/features/follow_requests/components/account_authorize.jsx @@ -22,7 +22,7 @@ const messages = defineMessages({ class AccountAuthorize extends ImmutablePureComponent { static propTypes = { - account: ImmutablePropTypes.map.isRequired, + account: ImmutablePropTypes.record.isRequired, onAuthorize: PropTypes.func.isRequired, onReject: PropTypes.func.isRequired, intl: PropTypes.object.isRequired, diff --git a/app/javascript/mastodon/features/followers/index.jsx b/app/javascript/mastodon/features/followers/index.jsx index fc0ce8ab30119b..e50b2171a0a256 100644 --- a/app/javascript/mastodon/features/followers/index.jsx +++ b/app/javascript/mastodon/features/followers/index.jsx @@ -23,7 +23,7 @@ import { ColumnBackButton } from '../../components/column_back_button'; import { LoadingIndicator } from '../../components/loading_indicator'; import ScrollableList from '../../components/scrollable_list'; import AccountContainer from '../../containers/account_container'; -import LimitedAccountHint from '../account_timeline/components/limited_account_hint'; +import { LimitedAccountHint } from '../account_timeline/components/limited_account_hint'; import HeaderContainer from '../account_timeline/containers/header_container'; import Column from '../ui/components/column'; diff --git a/app/javascript/mastodon/features/following/index.jsx b/app/javascript/mastodon/features/following/index.jsx index fb02d179505d94..73e77aadd78237 100644 --- a/app/javascript/mastodon/features/following/index.jsx +++ b/app/javascript/mastodon/features/following/index.jsx @@ -23,7 +23,7 @@ import { ColumnBackButton } from '../../components/column_back_button'; import { LoadingIndicator } from '../../components/loading_indicator'; import ScrollableList from '../../components/scrollable_list'; import AccountContainer from '../../containers/account_container'; -import LimitedAccountHint from '../account_timeline/components/limited_account_hint'; +import { LimitedAccountHint } from '../account_timeline/components/limited_account_hint'; import HeaderContainer from '../account_timeline/containers/header_container'; import Column from '../ui/components/column'; diff --git a/app/javascript/mastodon/features/list_adder/components/account.jsx b/app/javascript/mastodon/features/list_adder/components/account.jsx index 31a2e963795e12..94a90726e33342 100644 --- a/app/javascript/mastodon/features/list_adder/components/account.jsx +++ b/app/javascript/mastodon/features/list_adder/components/account.jsx @@ -21,7 +21,7 @@ const makeMapStateToProps = () => { class Account extends ImmutablePureComponent { static propTypes = { - account: ImmutablePropTypes.map.isRequired, + account: ImmutablePropTypes.record.isRequired, }; render () { diff --git a/app/javascript/mastodon/features/list_editor/components/account.jsx b/app/javascript/mastodon/features/list_editor/components/account.jsx index f38c7d93a7c8f6..18d5e905cbc0b7 100644 --- a/app/javascript/mastodon/features/list_editor/components/account.jsx +++ b/app/javascript/mastodon/features/list_editor/components/account.jsx @@ -39,7 +39,7 @@ const mapDispatchToProps = (dispatch, { accountId }) => ({ class Account extends ImmutablePureComponent { static propTypes = { - account: ImmutablePropTypes.map.isRequired, + account: ImmutablePropTypes.record.isRequired, intl: PropTypes.object.isRequired, onRemove: PropTypes.func.isRequired, onAdd: PropTypes.func.isRequired, diff --git a/app/javascript/mastodon/features/notifications/components/follow_request.jsx b/app/javascript/mastodon/features/notifications/components/follow_request.jsx index c10633beebcc76..03420b6c01e6e8 100644 --- a/app/javascript/mastodon/features/notifications/components/follow_request.jsx +++ b/app/javascript/mastodon/features/notifications/components/follow_request.jsx @@ -22,7 +22,7 @@ const messages = defineMessages({ class FollowRequest extends ImmutablePureComponent { static propTypes = { - account: ImmutablePropTypes.map.isRequired, + account: ImmutablePropTypes.record.isRequired, onAuthorize: PropTypes.func.isRequired, onReject: PropTypes.func.isRequired, intl: PropTypes.object.isRequired, diff --git a/app/javascript/mastodon/features/notifications/components/report.jsx b/app/javascript/mastodon/features/notifications/components/report.jsx index cb50b62cdc2ad1..52d6bfee9dbd84 100644 --- a/app/javascript/mastodon/features/notifications/components/report.jsx +++ b/app/javascript/mastodon/features/notifications/components/report.jsx @@ -20,7 +20,7 @@ const messages = defineMessages({ class Report extends ImmutablePureComponent { static propTypes = { - account: ImmutablePropTypes.map.isRequired, + account: ImmutablePropTypes.record.isRequired, report: ImmutablePropTypes.map.isRequired, hidden: PropTypes.bool, intl: PropTypes.object.isRequired, diff --git a/app/javascript/mastodon/features/onboarding/index.jsx b/app/javascript/mastodon/features/onboarding/index.jsx index 7b8a41faa59f13..51d4b71f246404 100644 --- a/app/javascript/mastodon/features/onboarding/index.jsx +++ b/app/javascript/mastodon/features/onboarding/index.jsx @@ -46,7 +46,7 @@ const mapStateToProps = () => { class Onboarding extends ImmutablePureComponent { static propTypes = { dispatch: PropTypes.func.isRequired, - account: ImmutablePropTypes.map, + account: ImmutablePropTypes.record, ...WithRouterPropTypes, }; diff --git a/app/javascript/mastodon/features/onboarding/share.jsx b/app/javascript/mastodon/features/onboarding/share.jsx index 8e01701eb3f394..33492442238188 100644 --- a/app/javascript/mastodon/features/onboarding/share.jsx +++ b/app/javascript/mastodon/features/onboarding/share.jsx @@ -145,7 +145,7 @@ class Share extends PureComponent { static propTypes = { onBack: PropTypes.func, - account: ImmutablePropTypes.map, + account: ImmutablePropTypes.record, intl: PropTypes.object, }; diff --git a/app/javascript/mastodon/features/picture_in_picture/components/header.jsx b/app/javascript/mastodon/features/picture_in_picture/components/header.jsx index b7fd9d1276f24e..80a13bd2e35b81 100644 --- a/app/javascript/mastodon/features/picture_in_picture/components/header.jsx +++ b/app/javascript/mastodon/features/picture_in_picture/components/header.jsx @@ -27,7 +27,7 @@ class Header extends ImmutablePureComponent { static propTypes = { accountId: PropTypes.string.isRequired, statusId: PropTypes.string.isRequired, - account: ImmutablePropTypes.map.isRequired, + account: ImmutablePropTypes.record.isRequired, onClose: PropTypes.func.isRequired, intl: PropTypes.object.isRequired, }; diff --git a/app/javascript/mastodon/features/report/thanks.jsx b/app/javascript/mastodon/features/report/thanks.jsx index 146d4b3897673e..904c4477013154 100644 --- a/app/javascript/mastodon/features/report/thanks.jsx +++ b/app/javascript/mastodon/features/report/thanks.jsx @@ -20,7 +20,7 @@ class Thanks extends PureComponent { static propTypes = { submitted: PropTypes.bool, onClose: PropTypes.func.isRequired, - account: ImmutablePropTypes.map.isRequired, + account: ImmutablePropTypes.record.isRequired, dispatch: PropTypes.func.isRequired, }; diff --git a/app/javascript/mastodon/features/ui/components/focal_point_modal.jsx b/app/javascript/mastodon/features/ui/components/focal_point_modal.jsx index 40481e2c8767dd..5353ebdb8aa404 100644 --- a/app/javascript/mastodon/features/ui/components/focal_point_modal.jsx +++ b/app/javascript/mastodon/features/ui/components/focal_point_modal.jsx @@ -110,7 +110,7 @@ class FocalPointModal extends ImmutablePureComponent { static propTypes = { media: ImmutablePropTypes.map.isRequired, - account: ImmutablePropTypes.map.isRequired, + account: ImmutablePropTypes.record.isRequired, isUploadingThumbnail: PropTypes.bool, onSave: PropTypes.func.isRequired, onChangeDescription: PropTypes.func.isRequired, diff --git a/app/javascript/mastodon/features/ui/components/report_modal.jsx b/app/javascript/mastodon/features/ui/components/report_modal.jsx index 2b6f04207ebdd1..3fd8ff127d8789 100644 --- a/app/javascript/mastodon/features/ui/components/report_modal.jsx +++ b/app/javascript/mastodon/features/ui/components/report_modal.jsx @@ -41,7 +41,7 @@ class ReportModal extends ImmutablePureComponent { statusId: PropTypes.string, dispatch: PropTypes.func.isRequired, intl: PropTypes.object.isRequired, - account: ImmutablePropTypes.map.isRequired, + account: ImmutablePropTypes.record.isRequired, }; state = { diff --git a/app/javascript/mastodon/initial_state.js b/app/javascript/mastodon/initial_state.js index bf5ce556e82305..596c9ca49f3ab1 100644 --- a/app/javascript/mastodon/initial_state.js +++ b/app/javascript/mastodon/initial_state.js @@ -1,43 +1,5 @@ // @ts-check -/** - * @typedef Emoji - * @property {string} shortcode - * @property {string} static_url - * @property {string} url - */ - -/** - * @typedef AccountField - * @property {string} name - * @property {string} value - * @property {string} verified_at - */ - -/** - * @typedef Account - * @property {string} acct - * @property {string} avatar - * @property {string} avatar_static - * @property {boolean} bot - * @property {string} created_at - * @property {boolean=} discoverable - * @property {string} display_name - * @property {Emoji[]} emojis - * @property {AccountField[]} fields - * @property {number} followers_count - * @property {number} following_count - * @property {boolean} group - * @property {string} header - * @property {string} header_static - * @property {string} id - * @property {string=} last_status_at - * @property {boolean} locked - * @property {string} note - * @property {number} statuses_count - * @property {string} url - * @property {string} username - */ /** * @typedef {[code: string, name: string, localName: string]} InitialStateLanguage @@ -85,7 +47,7 @@ /** * @typedef InitialState - * @property {Record} accounts + * @property {Record} accounts * @property {InitialStateLanguage[]} languages * @property {boolean=} critical_updates_pending * @property {InitialStateMeta} meta diff --git a/app/javascript/mastodon/models/account.ts b/app/javascript/mastodon/models/account.ts new file mode 100644 index 00000000000000..f20d2a2d3e1561 --- /dev/null +++ b/app/javascript/mastodon/models/account.ts @@ -0,0 +1,149 @@ +import type { RecordOf } from 'immutable'; +import { List, Record as ImmutableRecord } from 'immutable'; + +import escapeTextContentForBrowser from 'escape-html'; + +import type { + ApiAccountFieldJSON, + ApiAccountRoleJSON, + ApiAccountJSON, +} from 'mastodon/api_types/accounts'; +import type { ApiCustomEmojiJSON } from 'mastodon/api_types/custom_emoji'; +import emojify from 'mastodon/features/emoji/emoji'; +import { unescapeHTML } from 'mastodon/utils/html'; + +import { CustomEmojiFactory } from './custom_emoji'; +import type { CustomEmoji } from './custom_emoji'; + +// AccountField +interface AccountFieldShape extends Required { + name_emojified: string; + value_emojified: string; + value_plain: string | null; +} + +type AccountField = RecordOf; + +const AccountFieldFactory = ImmutableRecord({ + name: '', + value: '', + verified_at: null, + name_emojified: '', + value_emojified: '', + value_plain: null, +}); + +// AccountRole +export type AccountRoleShape = ApiAccountRoleJSON; +export type AccountRole = RecordOf; + +const AccountRoleFactory = ImmutableRecord({ + color: '', + id: '', + name: '', +}); + +// Account +export interface AccountShape + extends Required< + Omit + > { + emojis: List; + fields: List; + roles: List; + display_name_html: string; + note_emojified: string; + note_plain: string | null; + hidden: boolean; + moved: string | null; +} + +export type Account = RecordOf; + +export const accountDefaultValues: AccountShape = { + acct: '', + avatar: '', + avatar_static: '', + bot: false, + created_at: '', + discoverable: false, + display_name: '', + display_name_html: '', + emojis: List(), + fields: List(), + group: false, + header: '', + header_static: '', + id: '', + last_status_at: '', + locked: false, + noindex: false, + note: '', + note_emojified: '', + note_plain: 'string', + roles: List(), + uri: '', + url: '', + username: '', + followers_count: 0, + following_count: 0, + statuses_count: 0, + hidden: false, + suspended: false, + memorial: false, + limited: false, + moved: null, +}; + +const AccountFactory = ImmutableRecord(accountDefaultValues); + +type EmojiMap = Record; + +function makeEmojiMap(emojis: ApiCustomEmojiJSON[]) { + return emojis.reduce((obj, emoji) => { + obj[`:${emoji.shortcode}:`] = emoji; + return obj; + }, {}); +} + +function createAccountField( + jsonField: ApiAccountFieldJSON, + emojiMap: EmojiMap, +) { + return AccountFieldFactory({ + ...jsonField, + name_emojified: emojify( + escapeTextContentForBrowser(jsonField.name), + emojiMap, + ), + value_emojified: emojify(jsonField.value, emojiMap), + value_plain: unescapeHTML(jsonField.value), + }); +} + +export function createAccountFromServerJSON(serverJSON: ApiAccountJSON) { + const { moved, ...accountJSON } = serverJSON; + + const emojiMap = makeEmojiMap(accountJSON.emojis); + + const displayName = + accountJSON.display_name.trim().length === 0 + ? accountJSON.username + : accountJSON.display_name; + + return AccountFactory({ + ...accountJSON, + moved: moved?.id, + fields: List( + serverJSON.fields.map((field) => createAccountField(field, emojiMap)), + ), + emojis: List(serverJSON.emojis.map((emoji) => CustomEmojiFactory(emoji))), + roles: List(serverJSON.roles?.map((role) => AccountRoleFactory(role))), + display_name_html: emojify( + escapeTextContentForBrowser(displayName), + emojiMap, + ), + note_emojified: emojify(accountJSON.note, emojiMap), + note_plain: unescapeHTML(accountJSON.note), + }); +} diff --git a/app/javascript/mastodon/models/custom_emoji.ts b/app/javascript/mastodon/models/custom_emoji.ts new file mode 100644 index 00000000000000..76479f3aebf54e --- /dev/null +++ b/app/javascript/mastodon/models/custom_emoji.ts @@ -0,0 +1,15 @@ +import type { RecordOf } from 'immutable'; +import { Record } from 'immutable'; + +import type { ApiCustomEmojiJSON } from 'mastodon/api_types/custom_emoji'; + +type CustomEmojiShape = Required; // no changes from server shape +export type CustomEmoji = RecordOf; + +export const CustomEmojiFactory = Record({ + shortcode: '', + static_url: '', + url: '', + category: '', + visible_in_picker: false, +}); diff --git a/app/javascript/mastodon/models/relationship.ts b/app/javascript/mastodon/models/relationship.ts new file mode 100644 index 00000000000000..115b2787382a88 --- /dev/null +++ b/app/javascript/mastodon/models/relationship.ts @@ -0,0 +1,29 @@ +import type { RecordOf } from 'immutable'; +import { Record } from 'immutable'; + +import type { ApiRelationshipJSON } from 'mastodon/api_types/relationships'; + +type RelationshipShape = Required; // no changes from server shape +export type Relationship = RecordOf; + +const RelationshipFactory = Record({ + blocked_by: false, + blocking: false, + domain_blocking: false, + endorsed: false, + followed_by: false, + following: false, + id: '', + languages: null, + muting_notifications: false, + muting: false, + note: '', + notifying: false, + requested_by: false, + requested: false, + showing_reblogs: false, +}); + +export function createRelationship(attributes: Partial) { + return RelationshipFactory(attributes); +} diff --git a/app/javascript/mastodon/reducers/accounts.js b/app/javascript/mastodon/reducers/accounts.js deleted file mode 100644 index 76122cc63baa9f..00000000000000 --- a/app/javascript/mastodon/reducers/accounts.js +++ /dev/null @@ -1,39 +0,0 @@ -import { Map as ImmutableMap, fromJS } from 'immutable'; - -import { ACCOUNT_REVEAL } from 'mastodon/actions/accounts'; -import { ACCOUNT_IMPORT, ACCOUNTS_IMPORT } from 'mastodon/actions/importer'; - -const initialState = ImmutableMap(); - -const normalizeAccount = (state, account) => { - account = { ...account }; - - delete account.followers_count; - delete account.following_count; - delete account.statuses_count; - - account.hidden = state.getIn([account.id, 'hidden']) === false ? false : account.limited; - - return state.set(account.id, fromJS(account)); -}; - -const normalizeAccounts = (state, accounts) => { - accounts.forEach(account => { - state = normalizeAccount(state, account); - }); - - return state; -}; - -export default function accounts(state = initialState, action) { - switch(action.type) { - case ACCOUNT_IMPORT: - return normalizeAccount(state, action.account); - case ACCOUNTS_IMPORT: - return normalizeAccounts(state, action.accounts); - case ACCOUNT_REVEAL: - return state.setIn([action.id, 'hidden'], false); - default: - return state; - } -} diff --git a/app/javascript/mastodon/reducers/accounts.ts b/app/javascript/mastodon/reducers/accounts.ts new file mode 100644 index 00000000000000..4a00f21f316d0a --- /dev/null +++ b/app/javascript/mastodon/reducers/accounts.ts @@ -0,0 +1,82 @@ +import { Map as ImmutableMap } from 'immutable'; + +import type { Reducer } from 'redux'; + +import { + followAccountSuccess, + unfollowAccountSuccess, + importAccounts, + revealAccount, +} from 'mastodon/actions/accounts_typed'; +import type { ApiAccountJSON } from 'mastodon/api_types/accounts'; +import { me } from 'mastodon/initial_state'; +import type { Account } from 'mastodon/models/account'; +import { createAccountFromServerJSON } from 'mastodon/models/account'; + +const initialState = ImmutableMap(); + +const normalizeAccount = ( + state: typeof initialState, + account: ApiAccountJSON, +) => { + return state.set( + account.id, + createAccountFromServerJSON(account).set( + 'hidden', + state.get(account.id)?.hidden === false + ? false + : account.limited || false, + ), + ); +}; + +const normalizeAccounts = ( + state: typeof initialState, + accounts: ApiAccountJSON[], +) => { + accounts.forEach((account) => { + state = normalizeAccount(state, account); + }); + + return state; +}; + +export const accountsReducer: Reducer = ( + state = initialState, + action, +) => { + const currentUserId = me; + + if (!currentUserId) + throw new Error( + 'No current user (me) defined when calling `accountsReducer`', + ); + + if (revealAccount.match(action)) + return state.setIn([action.payload.id, 'hidden'], false); + else if (importAccounts.match(action)) + return normalizeAccounts(state, action.payload.accounts); + else if (followAccountSuccess.match(action)) + return state + .update( + action.payload.relationship.id, + (account) => account?.update('followers_count', (n) => n + 1), + ) + .update( + currentUserId, + (account) => account?.update('following_count', (n) => n + 1), + ); + else if (unfollowAccountSuccess.match(action)) + return state + .update( + action.payload.relationship.id, + (account) => + account?.update('followers_count', (n) => Math.max(0, n - 1)), + ) + .update( + currentUserId, + (account) => + account?.update('following_count', (n) => Math.max(0, n - 1)), + ); + else return state; +}; diff --git a/app/javascript/mastodon/reducers/accounts_counters.js b/app/javascript/mastodon/reducers/accounts_counters.js deleted file mode 100644 index eb7878deb9a632..00000000000000 --- a/app/javascript/mastodon/reducers/accounts_counters.js +++ /dev/null @@ -1,49 +0,0 @@ -import { Map as ImmutableMap, fromJS } from 'immutable'; - -import { me } from 'mastodon/initial_state'; - -import { - ACCOUNT_FOLLOW_SUCCESS, - ACCOUNT_UNFOLLOW_SUCCESS, -} from '../actions/accounts'; -import { ACCOUNT_IMPORT, ACCOUNTS_IMPORT } from '../actions/importer'; - -const normalizeAccount = (state, account) => state.set(account.id, fromJS({ - followers_count: account.followers_count, - following_count: account.following_count, - statuses_count: account.statuses_count, -})); - -const normalizeAccounts = (state, accounts) => { - accounts.forEach(account => { - state = normalizeAccount(state, account); - }); - - return state; -}; - -const incrementFollowers = (state, accountId) => - state.updateIn([accountId, 'followers_count'], num => num + 1) - .updateIn([me, 'following_count'], num => num + 1); - -const decrementFollowers = (state, accountId) => - state.updateIn([accountId, 'followers_count'], num => Math.max(0, num - 1)) - .updateIn([me, 'following_count'], num => Math.max(0, num - 1)); - -const initialState = ImmutableMap(); - -export default function accountsCounters(state = initialState, action) { - switch(action.type) { - case ACCOUNT_IMPORT: - return normalizeAccount(state, action.account); - case ACCOUNTS_IMPORT: - return normalizeAccounts(state, action.accounts); - case ACCOUNT_FOLLOW_SUCCESS: - return action.alreadyFollowing ? state : - incrementFollowers(state, action.relationship.id); - case ACCOUNT_UNFOLLOW_SUCCESS: - return decrementFollowers(state, action.relationship.id); - default: - return state; - } -} diff --git a/app/javascript/mastodon/reducers/accounts_map.js b/app/javascript/mastodon/reducers/accounts_map.js index fca0e3ce1e3bc8..d5ecad7dbfaddf 100644 --- a/app/javascript/mastodon/reducers/accounts_map.js +++ b/app/javascript/mastodon/reducers/accounts_map.js @@ -1,7 +1,7 @@ import { Map as ImmutableMap } from 'immutable'; import { ACCOUNT_LOOKUP_FAIL } from '../actions/accounts'; -import { ACCOUNT_IMPORT, ACCOUNTS_IMPORT } from '../actions/importer'; +import { importAccounts } from '../actions/accounts_typed'; export const normalizeForLookup = str => str.toLowerCase(); @@ -11,10 +11,8 @@ export default function accountsMap(state = initialState, action) { switch(action.type) { case ACCOUNT_LOOKUP_FAIL: return action.error?.response?.status === 404 ? state.set(normalizeForLookup(action.acct), null) : state; - case ACCOUNT_IMPORT: - return state.set(normalizeForLookup(action.account.acct), action.account.id); - case ACCOUNTS_IMPORT: - return state.withMutations(map => action.accounts.forEach(account => map.set(normalizeForLookup(account.acct), account.id))); + case importAccounts.type: + return state.withMutations(map => action.payload.accounts.forEach(account => map.set(normalizeForLookup(account.acct), account.id))); default: return state; } diff --git a/app/javascript/mastodon/reducers/contexts.js b/app/javascript/mastodon/reducers/contexts.js index 32e194dd42568c..f7d7419a4e3ab9 100644 --- a/app/javascript/mastodon/reducers/contexts.js +++ b/app/javascript/mastodon/reducers/contexts.js @@ -1,8 +1,8 @@ import { Map as ImmutableMap, List as ImmutableList } from 'immutable'; import { - ACCOUNT_BLOCK_SUCCESS, - ACCOUNT_MUTE_SUCCESS, + blockAccountSuccess, + muteAccountSuccess, } from '../actions/accounts'; import { CONTEXT_FETCH_SUCCESS } from '../actions/statuses'; import { TIMELINE_DELETE, TIMELINE_UPDATE } from '../actions/timelines'; @@ -92,9 +92,9 @@ const updateContext = (state, status) => { export default function replies(state = initialState, action) { switch(action.type) { - case ACCOUNT_BLOCK_SUCCESS: - case ACCOUNT_MUTE_SUCCESS: - return filterContexts(state, action.relationship, action.statuses); + case blockAccountSuccess.type: + case muteAccountSuccess.type: + return filterContexts(state, action.payload.relationship, action.payload.statuses); case CONTEXT_FETCH_SUCCESS: return normalizeContext(state, action.id, action.ancestors, action.descendants); case TIMELINE_DELETE: diff --git a/app/javascript/mastodon/reducers/conversations.js b/app/javascript/mastodon/reducers/conversations.js index 247e8a5977cd5c..3e99e680e3e7a2 100644 --- a/app/javascript/mastodon/reducers/conversations.js +++ b/app/javascript/mastodon/reducers/conversations.js @@ -1,7 +1,7 @@ import { Map as ImmutableMap, List as ImmutableList } from 'immutable'; -import { ACCOUNT_BLOCK_SUCCESS, ACCOUNT_MUTE_SUCCESS } from 'mastodon/actions/accounts'; -import { DOMAIN_BLOCK_SUCCESS } from 'mastodon/actions/domain_blocks'; +import { blockAccountSuccess, muteAccountSuccess } from 'mastodon/actions/accounts'; +import { blockDomainSuccess } from 'mastodon/actions/domain_blocks'; import { CONVERSATIONS_MOUNT, @@ -105,11 +105,11 @@ export default function conversations(state = initialState, action) { return item; })); - case ACCOUNT_BLOCK_SUCCESS: - case ACCOUNT_MUTE_SUCCESS: - return filterConversations(state, [action.relationship.id]); - case DOMAIN_BLOCK_SUCCESS: - return filterConversations(state, action.accounts); + case blockAccountSuccess.type: + case muteAccountSuccess.type: + return filterConversations(state, [action.payload.relationship.id]); + case blockDomainSuccess.type: + return filterConversations(state, action.payload.accounts); case CONVERSATIONS_DELETE_SUCCESS: return state.update('items', list => list.filterNot(item => item.get('id') === action.id)); default: diff --git a/app/javascript/mastodon/reducers/domain_lists.js b/app/javascript/mastodon/reducers/domain_lists.js index 8cdd3ba3764e55..5f63c77f5d4200 100644 --- a/app/javascript/mastodon/reducers/domain_lists.js +++ b/app/javascript/mastodon/reducers/domain_lists.js @@ -3,7 +3,7 @@ import { Map as ImmutableMap, OrderedSet as ImmutableOrderedSet } from 'immutabl import { DOMAIN_BLOCKS_FETCH_SUCCESS, DOMAIN_BLOCKS_EXPAND_SUCCESS, - DOMAIN_UNBLOCK_SUCCESS, + unblockDomainSuccess } from '../actions/domain_blocks'; const initialState = ImmutableMap({ @@ -18,8 +18,8 @@ export default function domainLists(state = initialState, action) { 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)); + case unblockDomainSuccess.type: + return state.updateIn(['blocks', 'items'], set => set.delete(action.payload.domain)); default: return state; } diff --git a/app/javascript/mastodon/reducers/index.ts b/app/javascript/mastodon/reducers/index.ts index 722f04f3700d92..ecef63387377b5 100644 --- a/app/javascript/mastodon/reducers/index.ts +++ b/app/javascript/mastodon/reducers/index.ts @@ -3,8 +3,7 @@ import { Record as ImmutableRecord } from 'immutable'; import { loadingBarReducer } from 'react-redux-loading-bar'; import { combineReducers } from 'redux-immutable'; -import accounts from './accounts'; -import accounts_counters from './accounts_counters'; +import { accountsReducer } from './accounts'; import accounts_map from './accounts_map'; import alerts from './alerts'; import announcements from './announcements'; @@ -32,7 +31,7 @@ import notifications from './notifications'; import picture_in_picture from './picture_in_picture'; import polls from './polls'; import push_notifications from './push_notifications'; -import relationships from './relationships'; +import { relationshipsReducer } from './relationships'; import search from './search'; import server from './server'; import settings from './settings'; @@ -55,11 +54,10 @@ const reducers = { user_lists, domain_lists, status_lists, - accounts, - accounts_counters, + accounts: accountsReducer, accounts_map, statuses, - relationships, + relationships: relationshipsReducer, settings, push_notifications, mutes, diff --git a/app/javascript/mastodon/reducers/notifications.js b/app/javascript/mastodon/reducers/notifications.js index 66870bec551a6b..4a7a822a35e951 100644 --- a/app/javascript/mastodon/reducers/notifications.js +++ b/app/javascript/mastodon/reducers/notifications.js @@ -1,12 +1,12 @@ import { fromJS, Map as ImmutableMap, List as ImmutableList } from 'immutable'; -import { DOMAIN_BLOCK_SUCCESS } from 'mastodon/actions/domain_blocks'; +import { blockDomainSuccess } from 'mastodon/actions/domain_blocks'; import { - ACCOUNT_BLOCK_SUCCESS, - ACCOUNT_MUTE_SUCCESS, - FOLLOW_REQUEST_AUTHORIZE_SUCCESS, - FOLLOW_REQUEST_REJECT_SUCCESS, + authorizeFollowRequestSuccess, + blockAccountSuccess, + muteAccountSuccess, + rejectFollowRequestSuccess, } from '../actions/accounts'; import { focusApp, @@ -16,7 +16,7 @@ import { MARKERS_FETCH_SUCCESS, } from '../actions/markers'; import { - NOTIFICATIONS_UPDATE, + notificationsUpdate, NOTIFICATIONS_EXPAND_SUCCESS, NOTIFICATIONS_EXPAND_REQUEST, NOTIFICATIONS_EXPAND_FAIL, @@ -274,19 +274,19 @@ export default function notifications(state = initialState, action) { return state.set('items', ImmutableList()).set('pendingItems', ImmutableList()).set('hasMore', true); case NOTIFICATIONS_SCROLL_TOP: return updateTop(state, action.top); - case NOTIFICATIONS_UPDATE: - return normalizeNotification(state, action.notification, action.usePendingItems); + case notificationsUpdate.type: + return normalizeNotification(state, action.payload.notification, action.payload.usePendingItems); case NOTIFICATIONS_EXPAND_SUCCESS: return expandNormalizedNotifications(state, action.notifications, action.next, action.isLoadingMore, action.isLoadingRecent, action.usePendingItems); - case ACCOUNT_BLOCK_SUCCESS: - return filterNotifications(state, [action.relationship.id]); - case ACCOUNT_MUTE_SUCCESS: - return action.relationship.muting_notifications ? filterNotifications(state, [action.relationship.id]) : state; - case DOMAIN_BLOCK_SUCCESS: - return filterNotifications(state, action.accounts); - case FOLLOW_REQUEST_AUTHORIZE_SUCCESS: - case FOLLOW_REQUEST_REJECT_SUCCESS: - return filterNotifications(state, [action.id], 'follow_request'); + case blockAccountSuccess.type: + return filterNotifications(state, [action.payload.relationship.id]); + case muteAccountSuccess.type: + return action.relationship.muting_notifications ? filterNotifications(state, [action.payload.relationship.id]) : state; + case blockDomainSuccess.type: + return filterNotifications(state, action.payload.accounts); + case authorizeFollowRequestSuccess.type: + case rejectFollowRequestSuccess.type: + return filterNotifications(state, [action.payload.id], 'follow_request'); case NOTIFICATIONS_CLEAR: return state.set('items', ImmutableList()).set('pendingItems', ImmutableList()).set('hasMore', false); case TIMELINE_DELETE: diff --git a/app/javascript/mastodon/reducers/relationships.js b/app/javascript/mastodon/reducers/relationships.js deleted file mode 100644 index 32b4b4f371b209..00000000000000 --- a/app/javascript/mastodon/reducers/relationships.js +++ /dev/null @@ -1,88 +0,0 @@ -import { Map as ImmutableMap, fromJS } from 'immutable'; - -import { - submitAccountNote, -} from '../actions/account_notes'; -import { - ACCOUNT_FOLLOW_SUCCESS, - ACCOUNT_FOLLOW_REQUEST, - ACCOUNT_FOLLOW_FAIL, - ACCOUNT_UNFOLLOW_SUCCESS, - ACCOUNT_UNFOLLOW_REQUEST, - ACCOUNT_UNFOLLOW_FAIL, - ACCOUNT_BLOCK_SUCCESS, - ACCOUNT_UNBLOCK_SUCCESS, - ACCOUNT_MUTE_SUCCESS, - ACCOUNT_UNMUTE_SUCCESS, - ACCOUNT_PIN_SUCCESS, - ACCOUNT_UNPIN_SUCCESS, - RELATIONSHIPS_FETCH_SUCCESS, - FOLLOW_REQUEST_AUTHORIZE_SUCCESS, - FOLLOW_REQUEST_REJECT_SUCCESS, -} from '../actions/accounts'; -import { - DOMAIN_BLOCK_SUCCESS, - DOMAIN_UNBLOCK_SUCCESS, -} from '../actions/domain_blocks'; -import { - NOTIFICATIONS_UPDATE, -} from '../actions/notifications'; - - -const normalizeRelationship = (state, relationship) => state.set(relationship.id, fromJS(relationship)); - -const normalizeRelationships = (state, relationships) => { - relationships.forEach(relationship => { - state = normalizeRelationship(state, relationship); - }); - - 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) { - switch(action.type) { - case FOLLOW_REQUEST_AUTHORIZE_SUCCESS: - return state.setIn([action.id, 'followed_by'], true).setIn([action.id, 'requested_by'], false); - case FOLLOW_REQUEST_REJECT_SUCCESS: - return state.setIn([action.id, 'followed_by'], false).setIn([action.id, 'requested_by'], false); - case NOTIFICATIONS_UPDATE: - return action.notification.type === 'follow_request' ? state.setIn([action.notification.account.id, 'requested_by'], true) : state; - case ACCOUNT_FOLLOW_REQUEST: - return state.getIn([action.id, 'following']) ? state : state.setIn([action.id, action.locked ? 'requested' : 'following'], true); - case ACCOUNT_FOLLOW_FAIL: - return state.setIn([action.id, action.locked ? 'requested' : 'following'], false); - case ACCOUNT_UNFOLLOW_REQUEST: - return state.setIn([action.id, 'following'], false); - case ACCOUNT_UNFOLLOW_FAIL: - return state.setIn([action.id, 'following'], true); - case ACCOUNT_FOLLOW_SUCCESS: - case ACCOUNT_UNFOLLOW_SUCCESS: - case ACCOUNT_BLOCK_SUCCESS: - case ACCOUNT_UNBLOCK_SUCCESS: - case ACCOUNT_MUTE_SUCCESS: - case ACCOUNT_UNMUTE_SUCCESS: - case ACCOUNT_PIN_SUCCESS: - case ACCOUNT_UNPIN_SUCCESS: - return normalizeRelationship(state, action.relationship); - case RELATIONSHIPS_FETCH_SUCCESS: - return normalizeRelationships(state, action.relationships); - case submitAccountNote.fulfilled: - return normalizeRelationship(state, action.payload.relationship); - case DOMAIN_BLOCK_SUCCESS: - return setDomainBlocking(state, action.accounts, true); - case DOMAIN_UNBLOCK_SUCCESS: - return setDomainBlocking(state, action.accounts, false); - default: - return state; - } -} diff --git a/app/javascript/mastodon/reducers/relationships.ts b/app/javascript/mastodon/reducers/relationships.ts new file mode 100644 index 00000000000000..2ba61839c7bffa --- /dev/null +++ b/app/javascript/mastodon/reducers/relationships.ts @@ -0,0 +1,123 @@ +import { Map as ImmutableMap } from 'immutable'; + +import { isFulfilled } from '@reduxjs/toolkit'; +import type { Reducer } from 'redux'; + +import type { ApiRelationshipJSON } from 'mastodon/api_types/relationships'; +import type { Account } from 'mastodon/models/account'; +import { createRelationship } from 'mastodon/models/relationship'; +import type { Relationship } from 'mastodon/models/relationship'; + +import { submitAccountNote } from '../actions/account_notes'; +import { + followAccountSuccess, + unfollowAccountSuccess, + authorizeFollowRequestSuccess, + rejectFollowRequestSuccess, + followAccountRequest, + followAccountFail, + unfollowAccountRequest, + unfollowAccountFail, + blockAccountSuccess, + unblockAccountSuccess, + muteAccountSuccess, + unmuteAccountSuccess, + pinAccountSuccess, + unpinAccountSuccess, + fetchRelationshipsSuccess, +} from '../actions/accounts_typed'; +import { + blockDomainSuccess, + unblockDomainSuccess, +} from '../actions/domain_blocks_typed'; +import { notificationsUpdate } from '../actions/notifications_typed'; + +const initialState = ImmutableMap(); +type State = typeof initialState; + +const normalizeRelationship = ( + state: State, + relationship: ApiRelationshipJSON, +) => state.set(relationship.id, createRelationship(relationship)); + +const normalizeRelationships = ( + state: State, + relationships: ApiRelationshipJSON[], +) => { + relationships.forEach((relationship) => { + state = normalizeRelationship(state, relationship); + }); + + return state; +}; + +const setDomainBlocking = ( + state: State, + accounts: Account[], + blocking: boolean, +) => { + return state.withMutations((map) => { + accounts.forEach((id) => { + map.setIn([id, 'domain_blocking'], blocking); + }); + }); +}; + +export const relationshipsReducer: Reducer = ( + state = initialState, + action, +) => { + if (authorizeFollowRequestSuccess.match(action)) + return state + .setIn([action.payload.id, 'followed_by'], true) + .setIn([action.payload.id, 'requested_by'], false); + else if (rejectFollowRequestSuccess.match(action)) + return state + .setIn([action.payload.id, 'followed_by'], false) + .setIn([action.payload.id, 'requested_by'], false); + else if (notificationsUpdate.match(action)) + return action.payload.notification.type === 'follow_request' + ? state.setIn( + [action.payload.notification.account.id, 'requested_by'], + true, + ) + : state; + else if (followAccountRequest.match(action)) + return state.getIn([action.payload.id, 'following']) + ? state + : state.setIn( + [ + action.payload.id, + action.payload.locked ? 'requested' : 'following', + ], + true, + ); + else if (followAccountFail.match(action)) + return state.setIn( + [action.payload.id, action.payload.locked ? 'requested' : 'following'], + false, + ); + else if (unfollowAccountRequest.match(action)) + return state.setIn([action.payload.id, 'following'], false); + else if (unfollowAccountFail.match(action)) + return state.setIn([action.payload.id, 'following'], true); + else if ( + followAccountSuccess.match(action) || + unfollowAccountSuccess.match(action) || + blockAccountSuccess.match(action) || + unblockAccountSuccess.match(action) || + muteAccountSuccess.match(action) || + unmuteAccountSuccess.match(action) || + pinAccountSuccess.match(action) || + unpinAccountSuccess.match(action) || + isFulfilled(submitAccountNote)(action) + ) + return normalizeRelationship(state, action.payload.relationship); + else if (fetchRelationshipsSuccess.match(action)) + return normalizeRelationships(state, action.payload.relationships); + else if (blockDomainSuccess.match(action)) + return setDomainBlocking(state, action.payload.accounts, true); + else if (unblockDomainSuccess.match(action)) + return setDomainBlocking(state, action.payload.accounts, false); + else return state; +}; diff --git a/app/javascript/mastodon/reducers/status_lists.js b/app/javascript/mastodon/reducers/status_lists.js index 41cc07341c4969..6cb6a937bb915e 100644 --- a/app/javascript/mastodon/reducers/status_lists.js +++ b/app/javascript/mastodon/reducers/status_lists.js @@ -1,8 +1,8 @@ import { Map as ImmutableMap, OrderedSet as ImmutableOrderedSet } from 'immutable'; import { - ACCOUNT_BLOCK_SUCCESS, - ACCOUNT_MUTE_SUCCESS, + blockAccountSuccess, + muteAccountSuccess, } from '../actions/accounts'; import { BOOKMARKED_STATUSES_FETCH_REQUEST, @@ -142,9 +142,9 @@ export default function statusLists(state = initialState, action) { return prependOneToList(state, 'pins', action.status); case UNPIN_SUCCESS: return removeOneFromList(state, 'pins', action.status); - case ACCOUNT_BLOCK_SUCCESS: - case ACCOUNT_MUTE_SUCCESS: - return state.updateIn(['trending', 'items'], ImmutableOrderedSet(), list => list.filterNot(statusId => action.statuses.getIn([statusId, 'account']) === action.relationship.id)); + case blockAccountSuccess.type: + case muteAccountSuccess.type: + return state.updateIn(['trending', 'items'], ImmutableOrderedSet(), list => list.filterNot(statusId => action.payload.statuses.getIn([statusId, 'account']) === action.payload.relationship.id)); default: return state; } diff --git a/app/javascript/mastodon/reducers/suggestions.js b/app/javascript/mastodon/reducers/suggestions.js index ce1bcc774064dc..0f224ff4b94f34 100644 --- a/app/javascript/mastodon/reducers/suggestions.js +++ b/app/javascript/mastodon/reducers/suggestions.js @@ -1,7 +1,7 @@ import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable'; -import { ACCOUNT_BLOCK_SUCCESS, ACCOUNT_MUTE_SUCCESS } from 'mastodon/actions/accounts'; -import { DOMAIN_BLOCK_SUCCESS } from 'mastodon/actions/domain_blocks'; +import { blockAccountSuccess, muteAccountSuccess } from 'mastodon/actions/accounts'; +import { blockDomainSuccess } from 'mastodon/actions/domain_blocks'; import { SUGGESTIONS_FETCH_REQUEST, @@ -29,11 +29,11 @@ export default function suggestionsReducer(state = initialState, action) { return state.set('isLoading', false); case SUGGESTIONS_DISMISS: return state.update('items', list => list.filterNot(x => x.account === action.id)); - case ACCOUNT_BLOCK_SUCCESS: - case ACCOUNT_MUTE_SUCCESS: - return state.update('items', list => list.filterNot(x => x.account === action.relationship.id)); - case DOMAIN_BLOCK_SUCCESS: - return state.update('items', list => list.filterNot(x => action.accounts.includes(x.account))); + case blockAccountSuccess.type: + case muteAccountSuccess.type: + return state.update('items', list => list.filterNot(x => x.account === action.payload.relationship.id)); + case blockDomainSuccess.type: + return state.update('items', list => list.filterNot(x => action.payload.accounts.includes(x.account))); default: return state; } diff --git a/app/javascript/mastodon/reducers/timelines.js b/app/javascript/mastodon/reducers/timelines.js index cb3da50727a6b4..43dedd6e6d8cc8 100644 --- a/app/javascript/mastodon/reducers/timelines.js +++ b/app/javascript/mastodon/reducers/timelines.js @@ -1,9 +1,9 @@ import { Map as ImmutableMap, List as ImmutableList, OrderedSet as ImmutableOrderedSet, fromJS } from 'immutable'; import { - ACCOUNT_BLOCK_SUCCESS, - ACCOUNT_MUTE_SUCCESS, - ACCOUNT_UNFOLLOW_SUCCESS, + blockAccountSuccess, + muteAccountSuccess, + unfollowAccountSuccess } from '../actions/accounts'; import { TIMELINE_UPDATE, @@ -200,11 +200,11 @@ export default function timelines(state = initialState, action) { return deleteStatus(state, action.id, action.references, action.reblogOf); case TIMELINE_CLEAR: return clearTimeline(state, action.timeline); - case ACCOUNT_BLOCK_SUCCESS: - case ACCOUNT_MUTE_SUCCESS: - return filterTimelines(state, action.relationship, action.statuses); - case ACCOUNT_UNFOLLOW_SUCCESS: - return filterTimeline('home', state, action.relationship, action.statuses); + case blockAccountSuccess.type: + case muteAccountSuccess.type: + return filterTimelines(state, action.payload.relationship, action.payload.statuses); + case unfollowAccountSuccess.type: + return filterTimeline('home', state, action.payload.relationship, action.payload.statuses); case TIMELINE_SCROLL_TOP: return updateTop(state, action.timeline, action.top); case TIMELINE_CONNECT: diff --git a/app/javascript/mastodon/reducers/user_lists.js b/app/javascript/mastodon/reducers/user_lists.js index 089899398ed872..2f17fed5fdb64a 100644 --- a/app/javascript/mastodon/reducers/user_lists.js +++ b/app/javascript/mastodon/reducers/user_lists.js @@ -33,8 +33,8 @@ import { FOLLOW_REQUESTS_EXPAND_REQUEST, FOLLOW_REQUESTS_EXPAND_SUCCESS, FOLLOW_REQUESTS_EXPAND_FAIL, - FOLLOW_REQUEST_AUTHORIZE_SUCCESS, - FOLLOW_REQUEST_REJECT_SUCCESS, + authorizeFollowRequestSuccess, + rejectFollowRequestSuccess, } from '../actions/accounts'; import { BLOCKS_FETCH_REQUEST, @@ -66,11 +66,7 @@ import { MUTES_EXPAND_SUCCESS, MUTES_EXPAND_FAIL, } from '../actions/mutes'; -import { - NOTIFICATIONS_UPDATE, -} from '../actions/notifications'; - - +import { notificationsUpdate } from '../actions/notifications'; const initialListState = ImmutableMap({ next: null, @@ -163,8 +159,8 @@ export default function userLists(state = initialState, action) { case FAVOURITES_FETCH_FAIL: case FAVOURITES_EXPAND_FAIL: return state.setIn(['favourited_by', action.id, 'isLoading'], false); - case NOTIFICATIONS_UPDATE: - return action.notification.type === 'follow_request' ? normalizeFollowRequest(state, action.notification) : state; + case notificationsUpdate.type: + return action.payload.notification.type === 'follow_request' ? normalizeFollowRequest(state, action.payload.notification) : state; case FOLLOW_REQUESTS_FETCH_SUCCESS: return normalizeList(state, ['follow_requests'], action.accounts, action.next); case FOLLOW_REQUESTS_EXPAND_SUCCESS: @@ -175,9 +171,9 @@ export default function userLists(state = initialState, action) { case FOLLOW_REQUESTS_FETCH_FAIL: case FOLLOW_REQUESTS_EXPAND_FAIL: return state.setIn(['follow_requests', 'isLoading'], false); - case FOLLOW_REQUEST_AUTHORIZE_SUCCESS: - case FOLLOW_REQUEST_REJECT_SUCCESS: - return state.updateIn(['follow_requests', 'items'], list => list.filterNot(item => item === action.id)); + case authorizeFollowRequestSuccess.type: + case rejectFollowRequestSuccess.type: + return state.updateIn(['follow_requests', 'items'], list => list.filterNot(item => item === action.payload.id)); case BLOCKS_FETCH_SUCCESS: return normalizeList(state, ['blocks'], action.accounts, action.next); case BLOCKS_EXPAND_SUCCESS: diff --git a/app/javascript/mastodon/selectors/accounts.ts b/app/javascript/mastodon/selectors/accounts.ts new file mode 100644 index 00000000000000..66193136c45012 --- /dev/null +++ b/app/javascript/mastodon/selectors/accounts.ts @@ -0,0 +1,47 @@ +import { Record as ImmutableRecord } from 'immutable'; +import { createSelector } from 'reselect'; + +import { accountDefaultValues } from 'mastodon/models/account'; +import type { Account, AccountShape } from 'mastodon/models/account'; +import type { Relationship } from 'mastodon/models/relationship'; +import type { RootState } from 'mastodon/store'; + +const getAccountBase = (state: RootState, id: string) => + state.accounts.get(id, null); + +const getAccountRelationship = (state: RootState, id: string) => + state.relationships.get(id, null); + +const getAccountMoved = (state: RootState, id: string) => { + const movedToId = state.accounts.get(id)?.moved; + + if (!movedToId) return undefined; + + return state.accounts.get(movedToId); +}; + +interface FullAccountShape extends Omit { + relationship: Relationship | null; + moved: Account | null; +} + +const FullAccountFactory = ImmutableRecord({ + ...accountDefaultValues, + moved: null, + relationship: null, +}); + +export function makeGetAccount() { + return createSelector( + [getAccountBase, getAccountRelationship, getAccountMoved], + (base, relationship, moved) => { + if (base === null) { + return null; + } + + return FullAccountFactory(base) + .set('relationship', relationship) + .set('moved', moved ?? null); + }, + ); +} diff --git a/app/javascript/mastodon/selectors/index.js b/app/javascript/mastodon/selectors/index.js index 0968fb090b6cfd..8a07ba774d8e3c 100644 --- a/app/javascript/mastodon/selectors/index.js +++ b/app/javascript/mastodon/selectors/index.js @@ -5,23 +5,7 @@ import { toServerSideType } from 'mastodon/utils/filters'; import { me } from '../initial_state'; -const getAccountBase = (state, id) => state.getIn(['accounts', id], null); -const getAccountCounters = (state, id) => state.getIn(['accounts_counters', id], null); -const getAccountRelationship = (state, id) => state.getIn(['relationships', id], null); -const getAccountMoved = (state, id) => state.getIn(['accounts', state.getIn(['accounts', id, 'moved'])]); - -export const makeGetAccount = () => { - return createSelector([getAccountBase, getAccountCounters, getAccountRelationship, getAccountMoved], (base, counters, relationship, moved) => { - if (base === null) { - return null; - } - - return base.merge(counters).withMutations(map => { - map.set('relationship', relationship); - map.set('moved', moved); - }); - }); -}; +export { makeGetAccount } from "./accounts"; const getFilters = (state, { contextType }) => { if (!contextType) return null; diff --git a/app/javascript/mastodon/store/store.ts b/app/javascript/mastodon/store/store.ts index 63508856803810..9f43f58a43dfa4 100644 --- a/app/javascript/mastodon/store/store.ts +++ b/app/javascript/mastodon/store/store.ts @@ -35,6 +35,5 @@ export const store = configureStore({ // Infer the `RootState` and `AppDispatch` types from the store itself export type RootState = ReturnType; -// Inferred type: {posts: PostsState, comments: CommentsState, users: UsersState} export type AppDispatch = typeof store.dispatch; export type GetState = typeof store.getState; diff --git a/app/javascript/types/resources.ts b/app/javascript/types/resources.ts deleted file mode 100644 index f3901ad150c9b9..00000000000000 --- a/app/javascript/types/resources.ts +++ /dev/null @@ -1,55 +0,0 @@ -import type { Record } from 'immutable'; - -type CustomEmoji = Record<{ - shortcode: string; - static_url: string; - url: string; -}>; - -type AccountField = Record<{ - name: string; - value: string; - verified_at: string | null; -}>; - -interface AccountApiResponseValues { - acct: string; - avatar: string; - avatar_static: string; - bot: boolean; - created_at: string; - discoverable: boolean; - display_name: string; - emojis: CustomEmoji[]; - fields: AccountField[]; - followers_count: number; - following_count: number; - group: boolean; - header: string; - header_static: string; - id: string; - last_status_at: string; - locked: boolean; - note: string; - statuses_count: number; - url: string; - uri: string; - username: string; -} - -type NormalizedAccountField = Record<{ - name_emojified: string; - value_emojified: string; - value_plain: string; -}>; - -interface NormalizedAccountValues { - display_name_html: string; - fields: NormalizedAccountField[]; - note_emojified: string; - note_plain: string; -} - -export type Account = Record< - AccountApiResponseValues & NormalizedAccountValues ->; From 7e088863b4af4ada23232ea6a7977ccdd9c51b29 Mon Sep 17 00:00:00 2001 From: KMY Date: Sat, 4 Nov 2023 10:11:06 +0900 Subject: [PATCH 5/5] Fix test --- app/javascript/mastodon/api_types/accounts.ts | 2 +- .../mastodon/api_types/custom_emoji.ts | 1 + app/javascript/mastodon/models/account.ts | 20 +++++++++++++++++++ .../mastodon/models/custom_emoji.ts | 5 +++++ app/models/concerns/account_other_settings.rb | 16 ++------------- 5 files changed, 29 insertions(+), 15 deletions(-) diff --git a/app/javascript/mastodon/api_types/accounts.ts b/app/javascript/mastodon/api_types/accounts.ts index 03da5df8d3fe35..dc2533de33686e 100644 --- a/app/javascript/mastodon/api_types/accounts.ts +++ b/app/javascript/mastodon/api_types/accounts.ts @@ -22,7 +22,7 @@ export interface ApiAccountOtherSettingsJSON { translatable_private: boolean; link_preview: boolean; allow_quote: boolean; - emoji_reaction_policy?: + emoji_reaction_policy: | 'allow' | 'outside_only' | 'following_only' diff --git a/app/javascript/mastodon/api_types/custom_emoji.ts b/app/javascript/mastodon/api_types/custom_emoji.ts index 45439f0d5a697d..9f25e6e41079c4 100644 --- a/app/javascript/mastodon/api_types/custom_emoji.ts +++ b/app/javascript/mastodon/api_types/custom_emoji.ts @@ -9,4 +9,5 @@ export interface ApiCustomEmojiJSON { height?: number; sensitive?: boolean; aliases?: string[]; + license?: string; } diff --git a/app/javascript/mastodon/models/account.ts b/app/javascript/mastodon/models/account.ts index f20d2a2d3e1561..c44d011e604288 100644 --- a/app/javascript/mastodon/models/account.ts +++ b/app/javascript/mastodon/models/account.ts @@ -6,6 +6,7 @@ import escapeTextContentForBrowser from 'escape-html'; import type { ApiAccountFieldJSON, ApiAccountRoleJSON, + ApiAccountOtherSettingsJSON, ApiAccountJSON, } from 'mastodon/api_types/accounts'; import type { ApiCustomEmojiJSON } from 'mastodon/api_types/custom_emoji'; @@ -43,6 +44,23 @@ const AccountRoleFactory = ImmutableRecord({ name: '', }); +// AccountOtherSettings +export type AccountOtherSettingsShape = ApiAccountOtherSettingsJSON; +export type AccountOtherSettings = RecordOf; + +const AccountOtherSettingsFactory = ImmutableRecord({ + noindex: false, + noai: true, + hide_network: false, + hide_followers_count: false, + hide_following_count: false, + hide_statuses_count: false, + translatable_private: false, + link_preview: true, + allow_quote: true, + emoji_reaction_policy: 'allow', +}); + // Account export interface AccountShape extends Required< @@ -93,6 +111,8 @@ export const accountDefaultValues: AccountShape = { memorial: false, limited: false, moved: null, + other_settings: AccountOtherSettingsFactory(), + subscribable: true, }; const AccountFactory = ImmutableRecord(accountDefaultValues); diff --git a/app/javascript/mastodon/models/custom_emoji.ts b/app/javascript/mastodon/models/custom_emoji.ts index 76479f3aebf54e..96ab4bc6127761 100644 --- a/app/javascript/mastodon/models/custom_emoji.ts +++ b/app/javascript/mastodon/models/custom_emoji.ts @@ -12,4 +12,9 @@ export const CustomEmojiFactory = Record({ url: '', category: '', visible_in_picker: false, + width: 32, + height: 32, + sensitive: false, + aliases: [], + license: '', }); diff --git a/app/models/concerns/account_other_settings.rb b/app/models/concerns/account_other_settings.rb index a4655e5b727b79..a351fce1e31ce3 100644 --- a/app/models/concerns/account_other_settings.rb +++ b/app/models/concerns/account_other_settings.rb @@ -97,26 +97,14 @@ def public_settings 'translatable_private' => translatable_private?, 'link_preview' => link_preview?, 'allow_quote' => allow_quote?, + 'emoji_reaction_policy' => Setting.enable_emoji_reaction ? emoji_reaction_policy : :block, } - if Setting.enable_emoji_reaction - config = config.merge({ - 'emoji_reaction_policy' => emoji_reaction_policy, - }) - end config = config.merge(settings) if settings.present? config end def public_settings_for_local - config = public_settings - - unless Setting.enable_emoji_reaction - config = config.merge({ - 'emoji_reaction_policy' => :block, - }) - end - - config + public_settings end end end