From d305d8747d203497448725faadb344d196641a20 Mon Sep 17 00:00:00 2001
From: "Renato \"Lond\" Cerqueira"
Date: Sat, 13 Apr 2019 14:23:41 +0200
Subject: Add new pt-br translations (#10564)
---
app/javascript/mastodon/locales/pt-BR.json | 2 +-
config/locales/activerecord.pt-BR.yml | 5 +++--
config/locales/pt-BR.yml | 11 +++++++++++
config/locales/simple_form.pt-BR.yml | 5 +++++
4 files changed, 20 insertions(+), 3 deletions(-)
diff --git a/app/javascript/mastodon/locales/pt-BR.json b/app/javascript/mastodon/locales/pt-BR.json
index 5b07c2295..e469344ec 100644
--- a/app/javascript/mastodon/locales/pt-BR.json
+++ b/app/javascript/mastodon/locales/pt-BR.json
@@ -117,7 +117,7 @@
"emoji_button.symbols": "Símbolos",
"emoji_button.travel": "Viagens & Lugares",
"empty_column.account_timeline": "Não há toots aqui!",
- "empty_column.account_unavailable": "Profile unavailable",
+ "empty_column.account_unavailable": "Perfil indisponível",
"empty_column.blocks": "Você ainda não bloqueou nenhum usuário.",
"empty_column.community": "A timeline local está vazia. Escreva algo publicamente para começar!",
"empty_column.direct": "Você não tem nenhuma mensagem direta ainda. Quando você enviar ou receber uma, as mensagens aparecerão por aqui.",
diff --git a/config/locales/activerecord.pt-BR.yml b/config/locales/activerecord.pt-BR.yml
index ddea7bbb4..85150c1e7 100644
--- a/config/locales/activerecord.pt-BR.yml
+++ b/config/locales/activerecord.pt-BR.yml
@@ -2,8 +2,9 @@
pt-BR:
activerecord:
attributes:
- status:
- owned_poll: enquete
+ poll:
+ expires_at: Expira em
+ options: Escolhas
errors:
models:
account:
diff --git a/config/locales/pt-BR.yml b/config/locales/pt-BR.yml
index 2d1171288..22a3918a1 100644
--- a/config/locales/pt-BR.yml
+++ b/config/locales/pt-BR.yml
@@ -68,6 +68,7 @@ pt-BR:
admin: Administrador
bot: Robô
moderator: Moderador
+ unavailable: Perfil indisponível
unfollow: Deixar de seguir
admin:
account_actions:
@@ -80,6 +81,7 @@ pt-BR:
destroyed_msg: Nota de moderação excluída com sucesso!
accounts:
approve: Aprovar
+ approve_all: Aprovar tudo
are_you_sure: Você tem certeza?
avatar: Avatar
by_domain: Domínio
@@ -132,6 +134,7 @@ pt-BR:
moderation_notes: Notas de moderação
most_recent_activity: Atividade mais recente
most_recent_ip: IP mais recente
+ no_account_selected: Nenhuma conta foi modificada, pois nenhuma conta foi selecionada
no_limits_imposed: Nenhum limite imposto
not_subscribed: Não está inscrito
outbox_url: URL da caixa de saída
@@ -144,6 +147,7 @@ pt-BR:
push_subscription_expires: Inscrição PuSH expira
redownload: Atualizar perfil
reject: Rejeitar
+ reject_all: Rejeitar tudo
remove_avatar: Remover avatar
remove_header: Remover cabeçalho
resend_confirmation:
@@ -330,6 +334,8 @@ pt-BR:
expired: Expirados
title: Filtro
title: Convites
+ pending_accounts:
+ title: Contas pendentes (%{count})
relays:
add_new: Adicionar novo repetidor
delete: Excluir
@@ -854,6 +860,9 @@ pt-BR:
revoke_success: Sessão revogada com sucesso
title: Sessões
settings:
+ account: Conta
+ account_settings: Configurações da conta
+ appearance: Aparência
authorized_apps: Apps autorizados
back: Voltar para o Mastodon
delete: Exclusão de conta
@@ -863,9 +872,11 @@ pt-BR:
featured_tags: Hashtags em destaque
identity_proofs: Provas de identidade
import: Importar
+ import_and_export: Importar e exportar
migrate: Migração de conta
notifications: Notificações
preferences: Preferências
+ profile: Perfil
relationships: Seguindo e seguidores
two_factor_authentication: Autenticação em dois passos
statuses:
diff --git a/config/locales/simple_form.pt-BR.yml b/config/locales/simple_form.pt-BR.yml
index 6cd6c7c85..734bd21d0 100644
--- a/config/locales/simple_form.pt-BR.yml
+++ b/config/locales/simple_form.pt-BR.yml
@@ -41,6 +41,8 @@ pt-BR:
name: 'Você pode querer usar um destes:'
imports:
data: Arquivo CSV exportado de outra instância do Mastodon
+ invite_request:
+ text: Isso vai nos ajudar a revisar sua aplicação
sessions:
otp: 'Insira o código de autenticação gerado pelo app no seu celular ou use um dos códigos de recuperação:'
user:
@@ -118,12 +120,15 @@ pt-BR:
must_be_follower: Bloquear notificações de não-seguidores
must_be_following: Bloquear notificações de pessoas que você não segue
must_be_following_dm: Bloquear mensagens diretas de pessoas que você não segue
+ invite_request:
+ text: Por que você quer se cadastrar?
notification_emails:
digest: Mandar e-mails com relatórios
favourite: Mandar um e-mail quando alguém favoritar suas postagens
follow: Mandar um e-mail quando alguém te seguir
follow_request: Mandar um e-maill quando alguém solicitar ser seu seguidor
mention: Mandar um e-mail quando alguém te mencionar
+ pending_account: Mandar um -mail quando uma nova conta precisar ser revisada
reblog: Mandar um e-mail quando alguém compartilhar suas postagens
report: Mandar um e-mail quando uma nova denúncia é submetida
'no': Não
--
cgit
From 43a215457582f0012363e76188093a41db4cbfea Mon Sep 17 00:00:00 2001
From: Marek Ľach
Date: Sat, 13 Apr 2019 14:23:52 +0200
Subject: New strings added into the Slovak translation (#10562)
* New strings added into the Slovak translation
* Update sk.yml
* Update sk.yml
* Update sk.yml
* Update sk.yml
---
config/locales/sk.yml | 69 ++++++++++++++++++++++++++++-----------------------
1 file changed, 38 insertions(+), 31 deletions(-)
diff --git a/config/locales/sk.yml b/config/locales/sk.yml
index bf7898ed7..7c496f4f9 100644
--- a/config/locales/sk.yml
+++ b/config/locales/sk.yml
@@ -72,6 +72,7 @@ sk:
admin: Administrátor
bot: Automat
moderator: Moderátor
+ unavailable: Profil je nedostupný
unfollow: Prestaň sledovať
admin:
account_actions:
@@ -172,7 +173,7 @@ sk:
silence: Stíš
silenced: Utíšený/é
statuses: Príspevky
- subscribe: Odoberať
+ subscribe: Odoberaj
suspended: Zablokovaní
title: Účty
unconfirmed_email: Nepotvrdený email
@@ -300,7 +301,7 @@ sk:
email_domain_blocks:
add_new: Pridaj nový
created_msg: Emailová doména bola úspešne pridaná do zoznamu zakázaných
- delete: Zmazať
+ delete: Vymaž
destroyed_msg: Emailová doména bola úspešne vymazaná zo zoznamu zakázaných
domain: Doména
new:
@@ -636,10 +637,10 @@ sk:
other: Niečo ešte stále nieje v poriadku! Prosím skontroluj všetky %{count} nižšie uvedené pochybenia
imports:
modes:
- merge: Spojiť dohromady
+ merge: Spoj dohromady
merge_long: Ponechaj existujúce záznamy a pridaj k nim nové
overwrite: Prepíš
- overwrite_long: Nahraď súčasné záznamy s novými
+ overwrite_long: Nahraď súčasné záznamy novými
preface: Môžeš nahrať dáta ktoré si exportoval/a z iného Mastodon serveru, ako sú napríklad zoznamy ľudí ktorých sleduješ, alebo blokuješ.
success: Tvoje dáta boli nahraté úspešne, a teraz budú spracované v danom čase
types:
@@ -647,10 +648,10 @@ sk:
domain_blocking: Zoznam blokovaných domén
following: Zoznam sledovaných
muting: Zoznam ignorovaných
- upload: Nahrať
+ upload: Nahraj
in_memoriam_html: V pamäti.
invites:
- delete: Deaktivovať
+ delete: Deaktivuj
expired: Neplatné
expires_in:
'1800': 30 minút
@@ -661,13 +662,13 @@ sk:
'86400': 1 deň
expires_in_prompt: Nikdy
generate: Vygeneruj
- invited_by: 'Bol/a si pozvan/á užívateľom:'
+ invited_by: 'Bol/a si pozvaný/á užívateľom:'
max_uses:
few: "%{count} použitia"
one: jedno použitie
other: "%{count} použití"
- max_uses_prompt: Bez limitov
- prompt: Vygeneruj a zdieľaj linky s ostatnými aby mali umožnený prístup k tomuto serveru
+ max_uses_prompt: Bez obmedzení
+ prompt: Vygeneruj a zdieľaj linky s ostatnými, aby mali umožnený prístup k tomuto serveru
table:
expires_at: Vyprší
uses: Používa
@@ -692,16 +693,16 @@ sk:
body: Tu nájdete krátky súhrn správ ktoré ste zmeškali od svojej poslednj návštevi od %{since}
mention: "%{name} ťa spomenul/a v:"
new_followers_summary:
- few: Taktiež, získal/a si %{count} nových následovníkov za tú dobu čo si bol/a preč. Yay!
- one: Taktiež, získal/a si jedného nového následovníka zatiaľ čo si bol/a preč. Yay!
- other: Taktiež, získal/a si %{count} nových následovníkov za tú dobu čo si bol/a preč. Yay!
+ few: Tiež si získal/a %{count} nových následovateľov za tú dobu čo si bol/a preč. Yay!
+ one: Tiež si získal/a jedného nového následovateľa zatiaľ čo si bol/a preč. Yay!
+ other: Tiež si získal/a %{count} nových následovateľov za tú dobu čo si bol/a preč. Yay!
subject:
few: "%{count} nové notifikácie od tvojej poslednej návštevy \U0001F418"
- one: "1 nová notifikácia od tvojej poslednej návštevy \U0001F418"
- other: "%{count} nových notifikácií od tvojej poslednej návštevy \U0001F418"
+ one: "1 nové oboznámenie od tvojej poslednej návštevy \U0001F418"
+ other: "%{count} nových oboznámení od tvojej poslednej návštevy \U0001F418"
title: Zatiaľ čo si bol/a preč…
favourite:
- body: 'Tvoj príspevok bol uložený medi obľúbené užívateľa %{name}:'
+ body: 'Tvoj príspevok bol uložený medzi obľúbené užívateľa %{name}:'
subject: "%{name} si obľúbil/a tvoj príspevok"
title: Nové obľúbené
follow:
@@ -712,16 +713,16 @@ sk:
action: Spravuj žiadosti o sledovanie
body: "%{name} žiada povolenie ťa následovať"
subject: "%{name} ťa žiadá o možnosť sledovania"
- title: Nová žiadosť o sledovanie
+ title: Nová žiadosť o následovanie
mention:
action: Odpovedať
body: "%{name} ťa spomenul/a v:"
subject: Bol/a si spomenutý/á užívateľom %{name}
title: Novo spomenutý/á
reblog:
- body: 'Tvoj príspevok bol pozdvihnutý užívateľom %{name}:'
- subject: "%{name} pozdvihli tvoj príspevok"
- title: Novo pozdvyhnuté
+ body: 'Tvoj príspevok bol vyzdvihnutý užívateľom %{name}:'
+ subject: "%{name} vyzdvihli tvoj príspevok"
+ title: Novo vyzdvyhnuté
number:
human:
decimal_units:
@@ -820,17 +821,23 @@ sk:
revoke_success: Sezóna úspešne zamietnutá
title: Sezóny
settings:
+ account: Účet
+ account_settings: Nastavenia účtu
+ appearance: Vzhľad
authorized_apps: Povolené aplikácie
back: Späť na Mastodon
delete: Vymazanie účtu
development: Vývoj
edit_profile: Uprav profil
- export: Exportovať dáta
- featured_tags: Popredne zvýraznené haštagy
- import: Importovať
- migrate: Presunutie účtu
- notifications: Oznámenia
+ export: Exportuj dáta
+ featured_tags: Zvýraznené haštagy
+ import: Importuj
+ import_and_export: Import a export
+ migrate: Presuň účet
+ notifications: Oboznámenia
preferences: Voľby
+ profile: Profil
+ relationships: Následovaní a následovatelia
two_factor_authentication: Dvoj-faktorové overenie
statuses:
attached:
@@ -846,9 +853,9 @@ sk:
boosted_from_html: Povýšené od %{acct_link}
content_warning: 'Varovanie o obsahu: %{warning}'
disallowed_hashtags:
- few: 'obsahoval nepovolené hashtagy: %{tags}'
- one: 'obsahoval nepovolený hashtag: %{tags}'
- other: 'obsahoval nepovolené hashtagy: %{tags}'
+ few: 'obsahoval nepovolené haštagy: %{tags}'
+ one: 'obsahoval nepovolený haštag: %{tags}'
+ other: 'obsahoval nepovolené haštagy: %{tags}'
language_detection: Zisti automaticky
open_in_web: Otvor v okne na webe
over_character_limit: limit %{max} znakov bol presiahnutý
@@ -856,7 +863,7 @@ sk:
limit: Už si si pripol ten najvyšší možný počet hlášok
ownership: Nieje možné pripnúť hlášku od niekoho iného
private: Neverejné príspevky nemôžu byť pripnuté
- reblog: Pozdvihnutie sa nedá pripnúť
+ reblog: Vyzdvihnutie sa nedá pripnúť
poll:
total_votes:
few: "%{count} hlas(y)ov"
@@ -874,14 +881,14 @@ sk:
unlisted: Nezaradené
unlisted_long: Všetci môžu vidieť, ale nieje zaradené do verejnej osi
stream_entries:
- pinned: Pripnutý toot
+ pinned: Pripnutý príspevok
reblogged: vyzdvihnutý
sensitive_content: Senzitívny obsah
terms:
body_html: |
Podmienky súkromia
- Aké informácie zbierame?
+ Aké informácie sú zbierané?
Základné informácie o účte : Ak sa na tomto serveri zaregistruješ, budeš môcť byť požiadaný/á zadať prezývku, emailovú adresu a heslo. Budeš tiež môcť zadať aj ďalšie profilové údaje, ako napríklad meno a životopis, a nahrať profilovú fotku aj obrázok v záhlaví. Tvoja prezývka, meno, životopis, profilová fotka a obrázok v záhlaví sú vždy zobrazené verejne.Príspevky, sledovania a iné verejné informácie :
@@ -973,7 +980,7 @@ sk:
invalid_otp_token: Neplatný kód pre dvojfaktorovú autentikáciu
otp_lost_help_html: Pokiaľ si stratil/a prístup k obom, môžeš dať vedieť %{email}
seamless_external_login: Si prihlásená/ý cez externú službu, takže nastavenia hesla a emailu ti niesú prístupné.
- signed_in_as: 'Prihlásený ako:'
+ signed_in_as: 'Prihlásená/ý ako:'
verification:
explanation_html: 'Môžeš sa overiť ako majiteľ odkazov v metadátach tvojho profilu . Na to musí ale odkazovaná stránka obsahovať odkaz späť na tvoj Mastodon profil. Tento spätný odkaz musí mať prívlastok rel="me"
. Na texte odkazu nezáleží. Tu je príklad:'
verification: Overenie
--
cgit
From b4acbd1a306ffb74ca75f3379a8f71ba83195020 Mon Sep 17 00:00:00 2001
From: Marek Ľach
Date: Sat, 13 Apr 2019 14:24:00 +0200
Subject: Small SK locale fixes (#10561)
* Small SK locale fixes
* Update devise.sk.yml
---
config/locales/devise.sk.yml | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/config/locales/devise.sk.yml b/config/locales/devise.sk.yml
index 0052bc0bc..4a43b2b68 100644
--- a/config/locales/devise.sk.yml
+++ b/config/locales/devise.sk.yml
@@ -11,9 +11,9 @@ sk:
invalid: Nesprávny %{authentication_keys}, alebo heslo.
last_attempt: Máš posledný pokus pred zamknutím tvojho účtu.
locked: Tvoj účet je zamknutý.
- not_found_in_database: Nesprávny %{authentication_keys} alebo heslo.
+ not_found_in_database: Nesprávny %{authentication_keys}, alebo heslo.
pending: Tvoj účet je stále prehodnocovaný.
- timeout: Vaša aktívna sezóna vypršala. Pre pokračovanie sa prosím znovu prihláste.
+ timeout: Tvoja aktívna sezóna vypršala. Pre pokračovanie sa prosím prihlás znovu.
unauthenticated: K pokračovaniu sa musíš zaregistrovať alebo prihlásiť.
unconfirmed: Pred pokračovaním musíš potvrdiť svoj email.
mailer:
@@ -48,7 +48,7 @@ sk:
unlock_instructions:
subject: 'Mastodon: Inštrukcie pre odomknutie účtu'
omniauth_callbacks:
- failure: Nebolo možné ťa overiť z dôvodu,%{kind} že "%{reason}".
+ failure: Nebolo možné ťa overiť %{kind}, z dôvodu, že "%{reason}".
success: Úspešné overenie z účtu %{kind}.
passwords:
no_token: Túto stránku nemôžete navštíviť pokiaľ neprichádzate z emailu s inštrukciami na obnovu hesla. Pokiaľ prichádzate z tohto emailu, prosím uistite sa že ste použili celú URL z emailu.
--
cgit
From bce757fdd8e19aae6dcdec3625a388709015ca11 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]"
Date: Sat, 13 Apr 2019 21:24:19 +0900
Subject: Bump capybara from 3.16.1 to 3.16.2 (#10552)
Bumps [capybara](https://github.com/teamcapybara/capybara) from 3.16.1 to 3.16.2.
- [Release notes](https://github.com/teamcapybara/capybara/releases)
- [Changelog](https://github.com/teamcapybara/capybara/blob/master/History.md)
- [Commits](https://github.com/teamcapybara/capybara/compare/3.16.1...3.16.2)
Signed-off-by: dependabot[bot]
---
Gemfile.lock | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/Gemfile.lock b/Gemfile.lock
index afe403dec..0c61bca8e 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -127,7 +127,7 @@ GEM
sshkit (~> 1.3)
capistrano-yarn (2.0.2)
capistrano (~> 3.0)
- capybara (3.16.1)
+ capybara (3.16.2)
addressable
mini_mime (>= 0.1.3)
nokogiri (~> 1.8)
@@ -496,7 +496,7 @@ GEM
redis-store (>= 1.2, < 2)
redis-store (1.5.0)
redis (>= 2.2, < 5)
- regexp_parser (1.3.0)
+ regexp_parser (1.4.0)
request_store (1.4.1)
rack (>= 1.4)
responders (2.4.1)
--
cgit
From 9588cf82ce9234dcdd9050e7e9930b48ebf94818 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]"
Date: Sat, 13 Apr 2019 21:24:44 +0900
Subject: Bump rubocop from 0.67.1 to 0.67.2 (#10500)
Bumps [rubocop](https://github.com/rubocop-hq/rubocop) from 0.67.1 to 0.67.2.
- [Release notes](https://github.com/rubocop-hq/rubocop/releases)
- [Changelog](https://github.com/rubocop-hq/rubocop/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rubocop-hq/rubocop/compare/v0.67.1...v0.67.2)
Signed-off-by: dependabot[bot]
---
Gemfile.lock | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/Gemfile.lock b/Gemfile.lock
index 0c61bca8e..19f64d4dc 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -393,7 +393,7 @@ GEM
parallel (1.17.0)
parallel_tests (2.28.0)
parallel
- parser (2.6.2.0)
+ parser (2.6.2.1)
ast (~> 2.4.0)
pastel (0.7.2)
equatable (~> 0.5.0)
@@ -526,7 +526,7 @@ GEM
rspec-core (~> 3.0, >= 3.0.0)
sidekiq (>= 2.4.0)
rspec-support (3.8.0)
- rubocop (0.67.1)
+ rubocop (0.67.2)
jaro_winkler (~> 1.5.1)
parallel (~> 1.10)
parser (>= 2.5, != 2.5.1.1)
--
cgit
From d20b1bab24aa104fbe41dbd43d9165268f522ce9 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]"
Date: Mon, 15 Apr 2019 19:51:10 +0900
Subject: Bump oj from 3.7.11 to 3.7.12 (#10581)
Bumps [oj](https://github.com/ohler55/oj) from 3.7.11 to 3.7.12.
- [Release notes](https://github.com/ohler55/oj/releases)
- [Changelog](https://github.com/ohler55/oj/blob/master/CHANGELOG.md)
- [Commits](https://github.com/ohler55/oj/compare/v3.7.11...v3.7.12)
Signed-off-by: dependabot[bot]
---
Gemfile.lock | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Gemfile.lock b/Gemfile.lock
index 19f64d4dc..a033b2ab6 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -364,7 +364,7 @@ GEM
concurrent-ruby (~> 1.0, >= 1.0.2)
sidekiq (>= 3.5)
statsd-ruby (~> 1.4, >= 1.4.0)
- oj (3.7.11)
+ oj (3.7.12)
omniauth (1.9.0)
hashie (>= 3.4.6, < 3.7.0)
rack (>= 1.6.2, < 3)
--
cgit
From 4ca402ffbddb698f178087630c66b306d07c2eab Mon Sep 17 00:00:00 2001
From: Alix Rossi
Date: Mon, 15 Apr 2019 15:18:27 +0200
Subject: i18n: Update Corsican translation (#10583)
---
config/locales/co.yml | 1 +
1 file changed, 1 insertion(+)
diff --git a/config/locales/co.yml b/config/locales/co.yml
index 1b0d8ff6a..aa68336f1 100644
--- a/config/locales/co.yml
+++ b/config/locales/co.yml
@@ -862,6 +862,7 @@ co:
settings:
account: Contu
account_settings: Parametri di u contu
+ appearance: Apparenza
authorized_apps: Applicazione auturizate
back: Ritornu nant’à Mastodon
delete: Suppressione di u contu
--
cgit
From b2d9dcf5e503f71aefd046b6296a7e91b59cde3a Mon Sep 17 00:00:00 2001
From: Marek Ľach
Date: Mon, 15 Apr 2019 15:18:51 +0200
Subject: Updates the Slovak translation further (#10584)
---
config/locales/activerecord.sk.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/config/locales/activerecord.sk.yml b/config/locales/activerecord.sk.yml
index 9d59edd5b..26f6c9737 100644
--- a/config/locales/activerecord.sk.yml
+++ b/config/locales/activerecord.sk.yml
@@ -3,7 +3,7 @@ sk:
activerecord:
attributes:
poll:
- expires_at: Uzávierka
+ expires_at: Trvá do
options: Voľby
status:
owned_poll: Anketa
--
cgit
From 9254bbe87571407c1593aaf0abc0ae0d1d68dc7b Mon Sep 17 00:00:00 2001
From: Marek Ľach
Date: Tue, 16 Apr 2019 00:16:12 +0200
Subject: Important Slovak grammar fixes (#10585)
* Important grammar fixes for SK Devise translation
* Important Slovak grammar fixes
* Update devise.sk.yml
* Important Slovak grammar fixes
Last batch for today :-)
* Update devise.sk.yml
* Update sk.yml
* Update sk.yml
* Update sk.yml
---
config/locales/devise.sk.yml | 38 ++++++++++-----------
config/locales/sk.yml | 80 ++++++++++++++++++++++----------------------
2 files changed, 59 insertions(+), 59 deletions(-)
diff --git a/config/locales/devise.sk.yml b/config/locales/devise.sk.yml
index 4a43b2b68..5ce04ba7a 100644
--- a/config/locales/devise.sk.yml
+++ b/config/locales/devise.sk.yml
@@ -3,7 +3,7 @@ sk:
devise:
confirmations:
confirmed: Tvoja emailová adresa bola úspešne overená.
- send_instructions: O niekoľko minút obdržíš email s inštrukciami ako potvrdiť svoj účet. Prosím, skontroluj si aj zložku spam, ak sa k tebe toto potvrdenie nedostalo.
+ send_instructions: O niekoľko minút obdržíš email s pokynmi ako potvrdiť svoj účet. Prosím, skontroluj si aj zložku spam, ak sa k tebe toto potvrdenie nedostalo.
send_paranoid_instructions: Ak sa tvoja emailová adresa nachádza v našej databázi, o niekoľko minút obdržíš email s pokynmi ako potvrdiť svoj účet. Prosím, skontroluj aj zložku spam, ak sa k tebe toto potvrdenie nedostalo.
failure:
already_authenticated: Už si prihlásený/á.
@@ -18,20 +18,20 @@ sk:
unconfirmed: Pred pokračovaním musíš potvrdiť svoj email.
mailer:
confirmation_instructions:
- action: Potvŕď emailovú adresu
+ action: Potvrď emailovú adresu
action_with_app: Potvrď a vráť sa na %{app}
explanation: S touto emailovou adresou si si vytvoril/a účet na %{host}. Si iba jeden klik od jeho aktivácie. Pokiaľ si to ale nebol/a ty, prosím ignoruj tento email.
- extra_html: Prosím pozri sa aj na pravidlá tohto servera, a naše užívaťeľské podiemky .
- subject: 'Mastodon: Potvrdzovacie inštrukcie pre %{instance}'
+ extra_html: Prosím, pozri sa aj na pravidlá tohto servera, a naše užívaťeľské podiemky .
+ subject: 'Mastodon: Potvrdzovacie pokyny pre %{instance}'
title: Potvrď emailovú adresu
email_changed:
explanation: 'Emailová adresa tvojho účtu bude zmenená na:'
- extra: Pokiaľ si nezmenil/a svoj email, je pravdepodobné že niekto iný získal prístup k tvojmu účtu. Naliehavo preto prosím zmeň svoje heslo, alebo kontaktuj administrátora tohto serveru pokiaľ si vymknutý/á zo svojho účtu.
+ extra: Ak si nezmenil/a svoj email, je pravdepodobné, že niekto iný získal prístup k tvojmu účtu. Naliehavo preto prosím zmeň svoje heslo, alebo kontaktuj administrátora tohto serveru, pokiaľ si vymknutý/á zo svojho účtu.
subject: 'Mastodon: Emailová adresa bola zmenená'
title: Nová emailová adresa
password_change:
explanation: Heslo k tvojmu účtu bolo zmenené.
- extra: Ak si heslo nezmenil/a, je pravdepodobné že niekto iný získal prístup k tvojmu účtu. Naliehavo preto prosím zmeň svoje heslo, alebo kontaktuj administrátora tohto serveru pokiaľ si vymknutý/á zo svojho účtu.
+ extra: Ak si heslo nezmenil/a, je pravdepodobné, že niekto iný získal prístup k tvojmu účtu. Naliehavo preto prosím zmeň svoje heslo, alebo kontaktuj administrátora tohto serveru, pokiaľ si vymknutý/á zo svojho účtu.
subject: 'Mastodon: Heslo bolo zmenené'
title: Heslo bolo zmenené
reconfirmation_instructions:
@@ -42,17 +42,17 @@ sk:
reset_password_instructions:
action: Zmeň svoje heslo
explanation: Vyžiadal/a si si nové heslo pre svoj účet.
- extra: Pokiaľ si túto akciu nevyžiadal/a, prosím ignoruj tento email. Tvoje heslo nebude zmenené pokiaľ nepostúpiš na adresu uvedenú vyššie a vytvoríš si nové.
- subject: 'Mastodon: Inštrukcie pre obnovu hesla'
+ extra: Ak si túto akciu nevyžiadal/a, prosím ignoruj tento email. Tvoje heslo nebude zmenené pokiaľ nepostúpiš na adresu uvedenú vyššie a vytvoríš si nové.
+ subject: 'Mastodon: Pokyny pre obnovu hesla'
title: Nastav nové heslo
unlock_instructions:
- subject: 'Mastodon: Inštrukcie pre odomknutie účtu'
+ subject: 'Mastodon: Pokyny na odomknutie účtu'
omniauth_callbacks:
- failure: Nebolo možné ťa overiť %{kind}, z dôvodu, že "%{reason}".
+ failure: Nebolo možné ťa overiť z %{kind}, lebo "%{reason}".
success: Úspešné overenie z účtu %{kind}.
passwords:
- no_token: Túto stránku nemôžete navštíviť pokiaľ neprichádzate z emailu s inštrukciami na obnovu hesla. Pokiaľ prichádzate z tohto emailu, prosím uistite sa že ste použili celú URL z emailu.
- send_instructions: Pokiaľ sa tvoja emailová adresa nachádza v databázi, tak o niekoľko minút obdržíš email s inštrukciami ako nastaviť nové heslo. Ak máš pocit, že si email neobdržal/a, prosím skontroluj aj svoju spam zložku.
+ no_token: Túto stránku nemôžeš navštíviť, ak neprichádzaš z emailu s pokynmi na obnovu hesla. Pokiaľ prichádzaš z tohto emailu, prosím uisti sa že si použil/a celú URL adresu z emailu.
+ send_instructions: Ak sa tvoja emailová adresa nachádza v databázi, tak o niekoľko minút obdržíš email s pokynmi ako nastaviť nové heslo. Ak máš pocit, že si email neobdržal/a, prosím skontroluj aj svoju spam zložku.
send_paranoid_instructions: Ak sa tvoja emailová adresa nachádza v databázi, za chvíľu obdržíš odkaz pre obnovu hesla na svoj email. Skontroluj ale prosím aj svoj spam, ak tento email nevidíš.
updated: Tvoje heslo bolo úspešne zmenené. Teraz si prihlásený/á.
updated_not_active: Tvoje heslo bolo úspešne zmenené.
@@ -66,9 +66,9 @@ sk:
update_needs_confirmation: Účet bol úspešne pozmenený, ale ešte potrebujeme overiť tvoju novú emailovú adresu. Pre overenie prosím klikni na link v správe ktorú si dostal/a na email. Takisto ale skontroluj aj svoju spam zložku, ak sa ti zdá, že si tento email nedostal/a.
updated: Tvoj účet bol úspešne aktualizovaný.
sessions:
- already_signed_out: Odhlásil/a si sa úspešné.
- signed_in: Prihlásil/a si sa úspešné.
- signed_out: Odhlásil/a si sa úspešné.
+ already_signed_out: Už si sa úspešne odhlásil/a.
+ signed_in: Prihlásil/a si sa úspešne.
+ signed_out: Odhlásil/a si sa úspešne.
unlocks:
send_instructions: O niekoľko minút obdržíš email s pokynmi, ako nastaviť nové heslo. Prosím, skontroluj ale aj svoju spam zložku, pokiaľ sa ti zdá, že si tento email nedostal/a.
send_paranoid_instructions: Ak tvoj účet existuje, o niekoľko minút obdržíš email s pokynmi ako si ho odomknúť. Prosím, skontroluj ale aj svoju spam zložku, pokiaľ sa ti zdá, že si tento email nedostal/a.
@@ -79,8 +79,8 @@ sk:
confirmation_period_expired: musí byť potvrdený do %{period}, prosím požiadaj o nový
expired: vypŕšal, prosím, vyžiadaj si nový
not_found: nenájdený
- not_locked: nebol uzamknutý
+ not_locked: nebol zamknutý
not_saved:
- few: "%{resource} nebol uložený kôli %{count} chybám:"
- one: "%{resource} nebol uložený kôli chybe:"
- other: "%{resource} nebol uložený kôli %{count} chybám:"
+ few: "%{resource} nebol uložený kvôli %{count} chybám:"
+ one: "%{resource} nebol uložený kvôli chybe:"
+ other: "%{resource} nebol uložený kvôli %{count} chybám:"
diff --git a/config/locales/sk.yml b/config/locales/sk.yml
index 7c496f4f9..961cfbb76 100644
--- a/config/locales/sk.yml
+++ b/config/locales/sk.yml
@@ -20,9 +20,9 @@ sk:
extended_description_html: |
Pravidlá
Žiadne zatiaľ uvedené nie sú
- federation_hint_html: S účtom na %{instance} budeš môcť následovať ľúdí na hociakom inom Mastodon serveri, ale aj inde.
+ federation_hint_html: S účtom na %{instance} budeš môcť následovať ľúdí na hociakom Mastodon serveri, ale aj inde.
generic_description: "%{domain} je jeden server v sieti"
- get_apps: Vyskúšaj mobilnú aplikáciu
+ get_apps: Vyskúšaj aplikácie
hosted_on: Mastodon hostovaný na %{domain}
learn_more: Zisti viac
privacy_policy: Ustanovenia o súkromí
@@ -32,22 +32,22 @@ sk:
status_count_after:
few: príspevkov
one: príspevok
- other: príspevkov
+ other: príspevky
status_count_before: Ktorí napísali
tagline: Následuj kamarátov, a objavuj nových
terms: Podmienky užívania
user_count_after:
- few: užívatelia
+ few: užívateľov
one: užívateľ
- other: užívateľov
+ other: užívatelia
user_count_before: Domov pre
what_is_mastodon: Čo je Mastodon?
accounts:
choices_html: "%{name}vé voľby:"
- follow: Sleduj
+ follow: Následuj
followers:
- few: Sledovatelia
- one: Sledujúci
+ few: Sledovateľov
+ one: Sledovateľ
other: Sledovatelia
following: Sledovaní
joined: Pridal/a sa v %{date}
@@ -70,9 +70,9 @@ sk:
reserved_username: Prihlasovacie meno je rezervované
roles:
admin: Administrátor
- bot: Automat
+ bot: Bot
moderator: Moderátor
- unavailable: Profil je nedostupný
+ unavailable: Profil nieje dostupný
unfollow: Prestaň sledovať
admin:
account_actions:
@@ -90,7 +90,7 @@ sk:
by_domain: Doména
change_email:
changed_msg: Email k tomuto účtu bol úspešne zmenený!
- current_email: Súčastný email
+ current_email: Súčasný email
label: Zmeň email
new_email: Nový email
submit: Zmeň email
@@ -103,32 +103,32 @@ sk:
disable: Zablokuj
disable_two_factor_authentication: Zakáž 2FA
disabled: Blokovaný
- display_name: Zobraziť meno
+ display_name: Ukáž meno
domain: Doména
edit: Uprav
email: Email
email_status: Stav emailu
- enable: Povoliť
+ enable: Povoľ
enabled: Povolený
- feed_url: URL časovej osi
+ feed_url: URL adresa časovej osi
followers: Sledujúci
- followers_url: URL sledujúcich
+ followers_url: URL adresa sledujúcich
follows: Sledovania
header: Hlavička
- inbox_url: URL prijatých správ
+ inbox_url: URL adresa prijatých správ
invited_by: Pozvaný/á užívateľom
- ip: IP
+ ip: IP adresa
joined: Pridal/a sa
location:
all: Všetko
local: Miestne
remote: Federované
- title: Lokácia
+ title: Umiestnenie
login_status: Stav prihlásenia
media_attachments: Prílohy
- memorialize: Zmeniť na "Navždy budeme spomínať"
+ memorialize: Zmeň na "Navždy budeme spomínať"
moderation:
- active: Aktívny
+ active: Aktívny/a
all: Všetko
pending: Čakajúci
silenced: Umlčané
@@ -136,21 +136,21 @@ sk:
title: Moderácia
moderation_notes: Moderátorské poznámky
most_recent_activity: Posledná aktivita
- most_recent_ip: Posledná IP
+ most_recent_ip: Posledná IP adresa
no_limits_imposed: Nie sú stanovené žiadné obmedzenia
not_subscribed: Neodoberá
outbox_url: URL poslaných
pending: Vyžaduje posúdenie
perform_full_suspension: Vylúč
- profile_url: URL profilu
+ profile_url: URL adresa profilu
promote: Povýš
protocol: Protokol
- public: Verejná os
+ public: Verejná časová os
push_subscription_expires: PuSH odoberanie expiruje
redownload: Obnov profil
reject: Odmietni
- remove_avatar: Odstrániť avatár
- remove_header: Odstráň hlavičku
+ remove_avatar: Vymaž avatar
+ remove_header: Vymaž hlavičku
resend_confirmation:
already_confirmed: Tento užívateľ je už potvrdený
send: Odošli potvrdzovací email znovu
@@ -165,7 +165,7 @@ sk:
staff: Člen
user: Užívateľ
salmon_url: Salmon adresa
- search: Hľadať
+ search: Hľadaj
shared_inbox_url: URL zdieľanej schránky
show:
created_reports: Vytvorené hlásenia
@@ -177,11 +177,11 @@ sk:
suspended: Zablokovaní
title: Účty
unconfirmed_email: Nepotvrdený email
- undo_silenced: Zrušiť stíšenie
+ undo_silenced: Zruš stíšenie
undo_suspension: Zruš blokovanie
unsubscribe: Prestaň odoberať
username: Prezývka
- warn: Varovať
+ warn: Varuj
web: Web
action_logs:
actions:
@@ -228,7 +228,7 @@ sk:
disable: Zakázať
disabled_msg: Emoji bolo úspešne zakázané
emoji: Emotikony
- enable: Povoliť
+ enable: Povoľ
enabled_msg: Emoji bolo úspešne povolené
image_hint: PNG do 50KB
listed: V zozname
@@ -241,7 +241,7 @@ sk:
unlisted: Nie je na zozname
update_failed_msg: Nebolo možné aktualizovať toto emoji
updated_msg: Emoji bolo úspešne aktualizované!
- upload: Nahrať
+ upload: Nahraj
dashboard:
backlog: odložené aktivity
config: Nastavenia
@@ -253,7 +253,7 @@ sk:
features: Vymoženosti
hidden_service: Federácia so skrytými službami
open_reports: otvorené hlásenia
- recent_users: Nedávny užívatelia
+ recent_users: Nedávni užívatelia
search: Celofrázové vyhľadávanie
single_user_mode: Jednouživateľské rozhranie
software: Softvér
@@ -266,7 +266,7 @@ sk:
week_users_new: užívateľov počas tohto týždňa
domain_blocks:
add_new: Pridaj nové doménové blokovanie
- created_msg: Doména je v procese blokovania
+ created_msg: Doména je v štádiu blokovania
destroyed_msg: Blokovanie domény bolo zrušené
domain: Doména
new:
@@ -285,7 +285,7 @@ sk:
rejecting_media: odmietanie médiálnych súborov
rejecting_reports: odmietané hlásenia
severity:
- silence: utíšené
+ silence: stíšený
suspend: vylúčený
show:
affected_accounts:
@@ -329,15 +329,15 @@ sk:
total_reported: Nahlásenia o nich
total_storage: Mediálne prílohy
invites:
- deactivate_all: Pozastaviť všetky
+ deactivate_all: Pozastav všetky
filter:
all: Všetky
available: Dostupné
expired: Vypršalo
- title: Filtrovať
+ title: Filtruj
title: Pozvánky
relays:
- add_new: Pridaj novú priechodnú oporu
+ add_new: Pridaj nový federovací mostík
delete: Vymaž
description_html: "Federovací mostík je prechodný server ktorý obmieňa veľké množstvá verejných príspevkov medzi tými servermi ktoré na od neho odoberajú, aj doňho prispievajú. Môže to pomôcť malým a stredným instanciám objavovať federovaný obsah , čo inak vyžaduje aby miestni užívatelia ručne následovali iných ľudí zo vzdialených instancií."
disable: Pozastav
@@ -345,7 +345,7 @@ sk:
enable: Povoľ
enable_hint: Ak povolíš, tvoj server bude odoberať všetky verejné príspevky z tohto mostu, a začne posielať verejné príspevky tvojho servera na tento most.
enabled: Povolené
- inbox_url: URL mostu
+ inbox_url: URL adresa mostu
pending: Čakám na povolenie od prechodného mostu
save_and_enable: Uložiť a povoliť
setup: Nastav prepojenie s mostom
@@ -360,7 +360,7 @@ sk:
report: nahlás
action_taken_by: Zákrok vykonal/a
are_you_sure: Si si istý/á?
- assign_to_self: Priraď k sebe
+ assign_to_self: Priraď sebe
assigned: Priradený moderátor
comment:
none: Žiadne
@@ -380,7 +380,7 @@ sk:
resolved: Vyriešené
resolved_msg: Hlásenie úspešne vyriešené!
status: Stav
- title: Reporty
+ title: Hlásenia
unassign: Odobrať
unresolved: Nevyriešené
updated_at: Aktualizované
@@ -392,7 +392,7 @@ sk:
desc_html: Ak je prezývok viacero, každú oddeľte čiarkou. Možno zadať iba miestne, odomknuté účty. Pokiaľ necháte prázdne, je to pre všetkých miestnych administrátorov.
title: Štandardní následovníci nových užívateľov
contact_information:
- email: Pracovný e-mail
+ email: Pracovný email
username: Kontaktné užívateľské meno
custom_css:
desc_html: Uprav vzhľad pomocou CSS, ktoré je načítané na každej stránke
--
cgit
From 7f75792bf305eea297c63731600af3b02968aea4 Mon Sep 17 00:00:00 2001
From: ThibG
Date: Tue, 16 Apr 2019 01:23:07 +0200
Subject: Allow modal secondary button to shrink and allow wider confirmation
modals (#10586)
Fixes #10531
---
app/javascript/styles/mastodon/components.scss | 12 ++++--------
1 file changed, 4 insertions(+), 8 deletions(-)
diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss
index 4b3f5153c..e697ba554 100644
--- a/app/javascript/styles/mastodon/components.scss
+++ b/app/javascript/styles/mastodon/components.scss
@@ -3961,14 +3961,6 @@ a.status-card.compact:hover {
font-size: 14px;
}
-.confirmation-modal {
- max-width: 85vw;
-
- @media screen and (min-width: 480px) {
- max-width: 380px;
- }
-}
-
.mute-modal {
line-height: 24px;
}
@@ -4147,6 +4139,10 @@ a.status-card.compact:hover {
color: darken($lighter-text-color, 4%);
}
}
+
+ .confirmation-modal__secondary-button {
+ flex-shrink: 1;
+ }
}
.confirmation-modal__container,
--
cgit
From a8c6feb7ee3dc6d566e67bbbb5a8fdebf8884b20 Mon Sep 17 00:00:00 2001
From: Marek Ľach
Date: Tue, 16 Apr 2019 14:59:54 +0200
Subject: Update sk.yml translation again (#10589)
* Update sk.yml
* Update sk.yml
* Update sk.yml
---
config/locales/sk.yml | 16 +++++++++-------
1 file changed, 9 insertions(+), 7 deletions(-)
diff --git a/config/locales/sk.yml b/config/locales/sk.yml
index 961cfbb76..dddd79ff0 100644
--- a/config/locales/sk.yml
+++ b/config/locales/sk.yml
@@ -10,7 +10,7 @@ sk:
api: API
apps: Aplikácie
apps_platforms: Uživaj Mastodon z iOSu, Androidu a iných platforiem
- browse_directory: Prehľadávaj databázu profilov a filtruj ju podľa záujmov
+ browse_directory: Prehľadávaj databázu profilov, filtruj podľa záujmov
browse_public_posts: Prebádaj naživo prúd verejných príspevkov na Mastodone
contact: Kontakt
contact_missing: Nezadaný
@@ -143,7 +143,7 @@ sk:
pending: Vyžaduje posúdenie
perform_full_suspension: Vylúč
profile_url: URL adresa profilu
- promote: Povýš
+ promote: Vyzdvihni
protocol: Protokol
public: Verejná časová os
push_subscription_expires: PuSH odoberanie expiruje
@@ -509,11 +509,13 @@ sk:
invalid_url: Zadaná URL adresa je nesprávna
regenerate_token: Znovu vygenerovať prístupový token
token_regenerated: Prístupový token bol úspešne vygenerovaný znova
- warning: Na tieto údaje dávajte ohromný pozor. Nikdy ich s nikým nezďieľajte!
- your_token: Váš prístupový token
+ warning: Na tieto údaje dávaj ohromný pozor. Nikdy ich s nikým nezďieľaj!
+ your_token: Tvoj prístupový token
auth:
+ apply_for_account: Vyžiadaj si pozvánku
change_password: Heslo
- confirm_email: Potvrdiť email
+ checkbox_agreement_html: Súhlasím s pravidlami servera , aj s prevoznými podmienkami
+ confirm_email: Potvrď email
delete_account: Vymaž účet
delete_account_html: Pokiaľ chceš svoj účet odtiaľto vymazať, môžeš tak urobiť tu . Budeš požiadaný/á o potvrdenie tohto kroku.
didnt_get_confirmation: Neobdržal/a si kroky na potvrdenie?
@@ -529,9 +531,9 @@ sk:
saml: SAML
register: Zaregistruj sa
resend_confirmation: Poslať potvrdzujúce pokyny znovu
- reset_password: Resetovať heslo
+ reset_password: Obnov heslo
security: Zabezpečenie
- set_new_password: Nastaviť nové heslo
+ set_new_password: Nastav nové heslo
authorize_follow:
already_following: Tento účet už následuješ
error: Naneštastie nastala chyba pri hľadaní vzdialeného účtu
--
cgit
From c469e48e0309efae5a593b0f3ffdd22bf30ea466 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]"
Date: Tue, 16 Apr 2019 22:00:15 +0900
Subject: Bump sidekiq-unique-jobs from 6.0.12 to 6.0.13 (#10580)
Bumps [sidekiq-unique-jobs](https://github.com/mhenrixon/sidekiq-unique-jobs) from 6.0.12 to 6.0.13.
- [Release notes](https://github.com/mhenrixon/sidekiq-unique-jobs/releases)
- [Changelog](https://github.com/mhenrixon/sidekiq-unique-jobs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mhenrixon/sidekiq-unique-jobs/compare/v6.0.12...v6.0.13)
Signed-off-by: dependabot[bot]
---
Gemfile.lock | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Gemfile.lock b/Gemfile.lock
index a033b2ab6..83f622eb6 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -564,7 +564,7 @@ GEM
rufus-scheduler (~> 3.2)
sidekiq (>= 3)
tilt (>= 1.4.0)
- sidekiq-unique-jobs (6.0.12)
+ sidekiq-unique-jobs (6.0.13)
concurrent-ruby (~> 1.0, >= 1.0.5)
sidekiq (>= 4.0, < 7.0)
thor (~> 0)
--
cgit
From 15ec1a738ef1ecc15df81ed9d408c26d09f5bef1 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]"
Date: Tue, 16 Apr 2019 22:00:56 +0900
Subject: Bump sidekiq from 5.2.5 to 5.2.6 (#10582)
Bumps [sidekiq](https://github.com/mperham/sidekiq) from 5.2.5 to 5.2.6.
- [Release notes](https://github.com/mperham/sidekiq/releases)
- [Changelog](https://github.com/mperham/sidekiq/blob/master/Changes.md)
- [Commits](https://github.com/mperham/sidekiq/compare/v5.2.5...v5.2.6)
Signed-off-by: dependabot[bot]
---
Gemfile.lock | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Gemfile.lock b/Gemfile.lock
index 83f622eb6..b7371b92c 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -552,7 +552,7 @@ GEM
scss_lint (0.57.1)
rake (>= 0.9, < 13)
sass (~> 3.5, >= 3.5.5)
- sidekiq (5.2.5)
+ sidekiq (5.2.6)
connection_pool (~> 2.2, >= 2.2.2)
rack (>= 1.5.0)
rack-protection (>= 1.5.0)
--
cgit
From f11ad4023c542f0b9012f60c75b0b82d7dc77e93 Mon Sep 17 00:00:00 2001
From: Izalia Mae
Date: Thu, 11 Apr 2019 07:23:31 -0400
Subject: add env var for max length of audio uploads
---
lib/paperclip/audio_transcoder.rb | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/lib/paperclip/audio_transcoder.rb b/lib/paperclip/audio_transcoder.rb
index 631ccb0be..323ec7bfe 100644
--- a/lib/paperclip/audio_transcoder.rb
+++ b/lib/paperclip/audio_transcoder.rb
@@ -3,10 +3,12 @@
module Paperclip
class AudioTranscoder < Paperclip::Processor
def make
+ max_aud_len = (ENV['MAX_AUDIO_LENGTH'] || 60.0).to_f
+
meta = ::Av.cli.identify(@file.path)
# {:length=>"0:00:02.14", :duration=>2.14, :audio_encode=>"mp3", :audio_bitrate=>"44100 Hz", :audio_channels=>"mono"}
- if meta[:duration] > 60.0
- raise Mastodon::ValidationError, "Audio uploads must be less than 60 seconds in length."
+ if meta[:duration] > max_aud_len
+ raise Mastodon::ValidationError, "Audio uploads must be less than #{max_aud_len} seconds in length."
end
final_file = Paperclip::Transcoder.make(file, options, attachment)
--
cgit
From fbec0edf08ce686e1b4c8332fad4481986e2dad5 Mon Sep 17 00:00:00 2001
From: Thibaut Girka
Date: Tue, 16 Apr 2019 18:45:04 +0200
Subject: Fix opening/closing gifv sometimes making the timeline scroll
---
app/javascript/flavours/glitch/components/media_gallery.js | 6 ------
app/javascript/flavours/glitch/components/modal_root.js | 2 +-
app/javascript/flavours/glitch/styles/components/media.scss | 1 +
3 files changed, 2 insertions(+), 7 deletions(-)
diff --git a/app/javascript/flavours/glitch/components/media_gallery.js b/app/javascript/flavours/glitch/components/media_gallery.js
index 6be2b4700..b7360bae4 100644
--- a/app/javascript/flavours/glitch/components/media_gallery.js
+++ b/app/javascript/flavours/glitch/components/media_gallery.js
@@ -82,11 +82,6 @@ class Item extends React.PureComponent {
e.stopPropagation();
}
- handleMouseDown = (e) => {
- e.preventDefault();
- e.stopPropagation();
- }
-
render () {
const { attachment, index, size, standalone, letterbox, displayWidth } = this.props;
@@ -190,7 +185,6 @@ class Item extends React.PureComponent {
onClick={this.handleClick}
onMouseEnter={this.handleMouseEnter}
onMouseLeave={this.handleMouseLeave}
- onMouseDown={this.handleMouseDown}
autoPlay={autoPlay}
loop
muted
diff --git a/app/javascript/flavours/glitch/components/modal_root.js b/app/javascript/flavours/glitch/components/modal_root.js
index 7a90e6b8a..4e8648b49 100644
--- a/app/javascript/flavours/glitch/components/modal_root.js
+++ b/app/javascript/flavours/glitch/components/modal_root.js
@@ -40,7 +40,7 @@ export default class ModalRoot extends React.PureComponent {
this.setState({ revealed: false });
}
if (!nextProps.children && !!this.props.children) {
- this.activeElement.focus();
+ this.activeElement.focus({ preventScroll: true });
this.activeElement = null;
}
}
diff --git a/app/javascript/flavours/glitch/styles/components/media.scss b/app/javascript/flavours/glitch/styles/components/media.scss
index e8011bde9..fabef2a56 100644
--- a/app/javascript/flavours/glitch/styles/components/media.scss
+++ b/app/javascript/flavours/glitch/styles/components/media.scss
@@ -147,6 +147,7 @@
position: relative;
z-index: 1;
object-fit: contain;
+ user-select: none;
&:not(.letterbox) {
height: 100%;
--
cgit
From 8d57c0e70ea76b2f482c0919fc815d40352ef477 Mon Sep 17 00:00:00 2001
From: Thibaut Girka
Date: Mon, 15 Apr 2019 20:40:05 +0200
Subject: When selecting a toot via keyboard, ensure it is scrolled into view
---
app/javascript/flavours/glitch/components/status_list.js | 14 ++++++++++----
.../flavours/glitch/features/notifications/index.js | 14 ++++++++++----
app/javascript/flavours/glitch/features/ui/index.js | 9 +++++++--
3 files changed, 27 insertions(+), 10 deletions(-)
diff --git a/app/javascript/flavours/glitch/components/status_list.js b/app/javascript/flavours/glitch/components/status_list.js
index a7629bd54..c1f51b307 100644
--- a/app/javascript/flavours/glitch/components/status_list.js
+++ b/app/javascript/flavours/glitch/components/status_list.js
@@ -46,22 +46,28 @@ export default class StatusList extends ImmutablePureComponent {
handleMoveUp = (id, featured) => {
const elementIndex = this.getCurrentStatusIndex(id, featured) - 1;
- this._selectChild(elementIndex);
+ this._selectChild(elementIndex, true);
}
handleMoveDown = (id, featured) => {
const elementIndex = this.getCurrentStatusIndex(id, featured) + 1;
- this._selectChild(elementIndex);
+ this._selectChild(elementIndex, false);
}
handleLoadOlder = debounce(() => {
this.props.onLoadMore(this.props.statusIds.size > 0 ? this.props.statusIds.last() : undefined);
}, 300, { leading: true })
- _selectChild (index) {
- const element = this.node.node.querySelector(`article:nth-of-type(${index + 1}) .focusable`);
+ _selectChild (index, align_top) {
+ const container = this.node.node;
+ const element = container.querySelector(`article:nth-of-type(${index + 1}) .focusable`);
if (element) {
+ if (align_top && container.scrollTop > element.offsetTop) {
+ element.scrollIntoView(true);
+ } else if (!align_top && container.scrollTop + container.clientHeight < element.offsetTop + element.offsetHeight) {
+ element.scrollIntoView(false);
+ }
element.focus();
}
}
diff --git a/app/javascript/flavours/glitch/features/notifications/index.js b/app/javascript/flavours/glitch/features/notifications/index.js
index 6a149927c..f2a1ccc3b 100644
--- a/app/javascript/flavours/glitch/features/notifications/index.js
+++ b/app/javascript/flavours/glitch/features/notifications/index.js
@@ -133,18 +133,24 @@ export default class Notifications extends React.PureComponent {
handleMoveUp = id => {
const elementIndex = this.props.notifications.findIndex(item => item !== null && item.get('id') === id) - 1;
- this._selectChild(elementIndex);
+ this._selectChild(elementIndex, true);
}
handleMoveDown = id => {
const elementIndex = this.props.notifications.findIndex(item => item !== null && item.get('id') === id) + 1;
- this._selectChild(elementIndex);
+ this._selectChild(elementIndex, false);
}
- _selectChild (index) {
- const element = this.column.node.querySelector(`article:nth-of-type(${index + 1}) .focusable`);
+ _selectChild (index, align_top) {
+ const container = this.column.node;
+ const element = container.querySelector(`article:nth-of-type(${index + 1}) .focusable`);
if (element) {
+ if (align_top && container.scrollTop > element.offsetTop) {
+ element.scrollIntoView(true);
+ } else if (!align_top && container.scrollTop + container.clientHeight < element.offsetTop + element.offsetHeight) {
+ element.scrollIntoView(false);
+ }
element.focus();
}
}
diff --git a/app/javascript/flavours/glitch/features/ui/index.js b/app/javascript/flavours/glitch/features/ui/index.js
index a19b3abf1..348125c97 100644
--- a/app/javascript/flavours/glitch/features/ui/index.js
+++ b/app/javascript/flavours/glitch/features/ui/index.js
@@ -341,11 +341,16 @@ export default class UI extends React.Component {
handleHotkeyFocusColumn = e => {
const index = (e.key * 1) + 1; // First child is drawer, skip that
const column = this.node.querySelector(`.column:nth-child(${index})`);
+ if (!column) return;
+ const container = column.querySelector('.scrollable');
- if (column) {
- const status = column.querySelector('.focusable');
+ if (container) {
+ const status = container.querySelector('.focusable');
if (status) {
+ if (container.scrollTop > status.offsetTop) {
+ status.scrollIntoView(true);
+ }
status.focus();
}
}
--
cgit
From e3c1472040105651fe55158b741c7ba92c1a7332 Mon Sep 17 00:00:00 2001
From: Thibaut Girka
Date: Mon, 15 Apr 2019 22:23:05 +0200
Subject: Shift+click on column Back button to return to last pinable column
---
.../flavours/glitch/components/column_back_button.js | 9 +++++++--
.../glitch/components/column_back_button_slim.js | 9 +++++++--
.../flavours/glitch/components/column_header.js | 13 +++++++++----
app/javascript/flavours/glitch/components/permalink.js | 4 +++-
app/javascript/flavours/glitch/components/status.js | 18 ++++++++++++++----
.../flavours/glitch/components/status_action_bar.js | 4 +++-
.../features/account_timeline/components/moved_note.js | 4 +++-
.../features/status/components/detailed_status.js | 8 ++++++--
.../flavours/glitch/features/status/index.js | 4 +++-
.../glitch/features/ui/components/boost_modal.js | 4 +++-
.../glitch/features/ui/components/favourite_modal.js | 4 +++-
11 files changed, 61 insertions(+), 20 deletions(-)
diff --git a/app/javascript/flavours/glitch/components/column_back_button.js b/app/javascript/flavours/glitch/components/column_back_button.js
index a562ef9b9..82556d22e 100644
--- a/app/javascript/flavours/glitch/components/column_back_button.js
+++ b/app/javascript/flavours/glitch/components/column_back_button.js
@@ -8,10 +8,15 @@ export default class ColumnBackButton extends React.PureComponent {
router: PropTypes.object,
};
- handleClick = () => {
+ handleClick = (event) => {
// if history is exhausted, or we would leave mastodon, just go to root.
if (window.history.state) {
- this.context.router.history.goBack();
+ const state = this.context.router.history.location.state;
+ if (event.shiftKey && state && state.mastodonBackSteps) {
+ this.context.router.history.go(-state.mastodonBackSteps);
+ } else {
+ this.context.router.history.goBack();
+ }
} else {
this.context.router.history.push('/');
}
diff --git a/app/javascript/flavours/glitch/components/column_back_button_slim.js b/app/javascript/flavours/glitch/components/column_back_button_slim.js
index c99c202af..38afd3df3 100644
--- a/app/javascript/flavours/glitch/components/column_back_button_slim.js
+++ b/app/javascript/flavours/glitch/components/column_back_button_slim.js
@@ -8,10 +8,15 @@ export default class ColumnBackButtonSlim extends React.PureComponent {
router: PropTypes.object,
};
- handleClick = () => {
+ handleClick = (event) => {
// if history is exhausted, or we would leave mastodon, just go to root.
if (window.history.state) {
- this.context.router.history.goBack();
+ const state = this.context.router.history.location.state;
+ if (event.shiftKey && state && state.mastodonBackSteps) {
+ this.context.router.history.go(-state.mastodonBackSteps);
+ } else {
+ this.context.router.history.goBack();
+ }
} else {
this.context.router.history.push('/');
}
diff --git a/app/javascript/flavours/glitch/components/column_header.js b/app/javascript/flavours/glitch/components/column_header.js
index 87e848a59..a0ff09986 100644
--- a/app/javascript/flavours/glitch/components/column_header.js
+++ b/app/javascript/flavours/glitch/components/column_header.js
@@ -47,10 +47,15 @@ export default class ColumnHeader extends React.PureComponent {
animatingNCD: false,
};
- historyBack = () => {
+ historyBack = (skip) => {
// if history is exhausted, or we would leave mastodon, just go to root.
if (window.history.state) {
- this.context.router.history.goBack();
+ const state = this.context.router.history.location.state;
+ if (skip && state && state.mastodonBackSteps) {
+ this.context.router.history.go(-state.mastodonBackSteps);
+ } else {
+ this.context.router.history.goBack();
+ }
} else {
this.context.router.history.push('/');
}
@@ -73,8 +78,8 @@ export default class ColumnHeader extends React.PureComponent {
this.props.onMove(1);
}
- handleBackClick = () => {
- this.historyBack();
+ handleBackClick = (event) => {
+ this.historyBack(event.shiftKey);
}
handleTransitionEnd = () => {
diff --git a/app/javascript/flavours/glitch/components/permalink.js b/app/javascript/flavours/glitch/components/permalink.js
index 1ea6a2915..718b02115 100644
--- a/app/javascript/flavours/glitch/components/permalink.js
+++ b/app/javascript/flavours/glitch/components/permalink.js
@@ -24,7 +24,9 @@ export default class Permalink extends React.PureComponent {
if (this.context.router) {
e.preventDefault();
- this.context.router.history.push(this.props.to);
+ let state = {...this.context.router.history.location.state};
+ state.mastodonBackSteps = (state.mastodonBackSteps || 0) + 1;
+ this.context.router.history.push(this.props.to, state);
}
}
}
diff --git a/app/javascript/flavours/glitch/components/status.js b/app/javascript/flavours/glitch/components/status.js
index c8bf75f79..cd22fb593 100644
--- a/app/javascript/flavours/glitch/components/status.js
+++ b/app/javascript/flavours/glitch/components/status.js
@@ -295,7 +295,11 @@ export default class Status extends ImmutablePureComponent {
else if (e.shiftKey) {
this.setCollapsed(true);
document.getSelection().removeAllRanges();
- } else router.history.push(destination);
+ } else {
+ let state = {...router.history.location.state};
+ state.mastodonBackSteps = (state.mastodonBackSteps || 0) + 1;
+ router.history.push(destination, state);
+ }
e.preventDefault();
}
}
@@ -304,7 +308,9 @@ export default class Status extends ImmutablePureComponent {
if (this.context.router && e.button === 0) {
const id = e.currentTarget.getAttribute('data-id');
e.preventDefault();
- this.context.router.history.push(`/accounts/${id}`);
+ let state = {...this.context.router.history.location.state};
+ state.mastodonBackSteps = (state.mastodonBackSteps || 0) + 1;
+ this.context.router.history.push(`/accounts/${id}`, state);
}
}
@@ -337,11 +343,15 @@ export default class Status extends ImmutablePureComponent {
}
handleHotkeyOpen = () => {
- this.context.router.history.push(`/statuses/${this.props.status.get('id')}`);
+ let state = {...this.context.router.history.location.state};
+ state.mastodonBackSteps = (state.mastodonBackSteps || 0) + 1;
+ this.context.router.history.push(`/statuses/${this.props.status.get('id')}`, state);
}
handleHotkeyOpenProfile = () => {
- this.context.router.history.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`);
+ let state = {...this.context.router.history.location.state};
+ state.mastodonBackSteps = (state.mastodonBackSteps || 0) + 1;
+ this.context.router.history.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`, state);
}
handleHotkeyMoveUp = e => {
diff --git a/app/javascript/flavours/glitch/components/status_action_bar.js b/app/javascript/flavours/glitch/components/status_action_bar.js
index e0cc652d2..6d1f54c60 100644
--- a/app/javascript/flavours/glitch/components/status_action_bar.js
+++ b/app/javascript/flavours/glitch/components/status_action_bar.js
@@ -150,7 +150,9 @@ export default class StatusActionBar extends ImmutablePureComponent {
}
handleOpen = () => {
- this.context.router.history.push(`/statuses/${this.props.status.get('id')}`);
+ let state = {...this.context.router.history.location.state};
+ state.mastodonBackSteps = (state.mastodonBackSteps || 0) + 1;
+ this.context.router.history.push(`/statuses/${this.props.status.get('id')}`, state);
}
handleEmbed = () => {
diff --git a/app/javascript/flavours/glitch/features/account_timeline/components/moved_note.js b/app/javascript/flavours/glitch/features/account_timeline/components/moved_note.js
index 280389bba..1fab083db 100644
--- a/app/javascript/flavours/glitch/features/account_timeline/components/moved_note.js
+++ b/app/javascript/flavours/glitch/features/account_timeline/components/moved_note.js
@@ -20,7 +20,9 @@ export default class MovedNote extends ImmutablePureComponent {
handleAccountClick = e => {
if (e.button === 0) {
e.preventDefault();
- this.context.router.history.push(`/accounts/${this.props.to.get('id')}`);
+ let state = {...this.context.router.history.location.state};
+ state.mastodonBackSteps = (state.mastodonBackSteps || 0) + 1;
+ this.context.router.history.push(`/accounts/${this.props.to.get('id')}`, state);
}
e.stopPropagation();
diff --git a/app/javascript/flavours/glitch/features/status/components/detailed_status.js b/app/javascript/flavours/glitch/features/status/components/detailed_status.js
index e9130b1b0..69b646427 100644
--- a/app/javascript/flavours/glitch/features/status/components/detailed_status.js
+++ b/app/javascript/flavours/glitch/features/status/components/detailed_status.js
@@ -42,7 +42,9 @@ export default class DetailedStatus extends ImmutablePureComponent {
handleAccountClick = (e) => {
if (e.button === 0 && !(e.ctrlKey || e.altKey || e.metaKey) && this.context.router) {
e.preventDefault();
- this.context.router.history.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`);
+ let state = {...this.context.router.history.location.state};
+ state.mastodonBackSteps = (state.mastodonBackSteps || 0) + 1;
+ this.context.router.history.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`, state);
}
e.stopPropagation();
@@ -51,7 +53,9 @@ export default class DetailedStatus extends ImmutablePureComponent {
parseClick = (e, destination) => {
if (e.button === 0 && !(e.ctrlKey || e.altKey || e.metaKey) && this.context.router) {
e.preventDefault();
- this.context.router.history.push(destination);
+ let state = {...this.context.router.history.location.state};
+ state.mastodonBackSteps = (state.mastodonBackSteps || 0) + 1;
+ this.context.router.history.push(destination, state);
}
e.stopPropagation();
diff --git a/app/javascript/flavours/glitch/features/status/index.js b/app/javascript/flavours/glitch/features/status/index.js
index 7f8f02188..a0a9b986c 100644
--- a/app/javascript/flavours/glitch/features/status/index.js
+++ b/app/javascript/flavours/glitch/features/status/index.js
@@ -331,7 +331,9 @@ export default class Status extends ImmutablePureComponent {
}
handleHotkeyOpenProfile = () => {
- this.context.router.history.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`);
+ let state = {...this.context.router.history.location.state};
+ state.mastodonBackSteps = (state.mastodonBackSteps || 0) + 1;
+ this.context.router.history.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`, state);
}
handleMoveUp = id => {
diff --git a/app/javascript/flavours/glitch/features/ui/components/boost_modal.js b/app/javascript/flavours/glitch/features/ui/components/boost_modal.js
index 9652bcb2d..0a914dce2 100644
--- a/app/javascript/flavours/glitch/features/ui/components/boost_modal.js
+++ b/app/javascript/flavours/glitch/features/ui/components/boost_modal.js
@@ -40,7 +40,9 @@ export default class BoostModal extends ImmutablePureComponent {
if (e.button === 0) {
e.preventDefault();
this.props.onClose();
- this.context.router.history.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`);
+ let state = {...this.context.router.history.location.state};
+ state.mastodonBackSteps = (state.mastodonBackSteps || 0) + 1;
+ this.context.router.history.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`, state);
}
}
diff --git a/app/javascript/flavours/glitch/features/ui/components/favourite_modal.js b/app/javascript/flavours/glitch/features/ui/components/favourite_modal.js
index 70722411d..e0037a15f 100644
--- a/app/javascript/flavours/glitch/features/ui/components/favourite_modal.js
+++ b/app/javascript/flavours/glitch/features/ui/components/favourite_modal.js
@@ -40,7 +40,9 @@ export default class FavouriteModal extends ImmutablePureComponent {
if (e.button === 0) {
e.preventDefault();
this.props.onClose();
- this.context.router.history.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`);
+ let state = {...this.context.router.history.location.state};
+ state.mastodonBackSteps = (state.mastodonBackSteps || 0) + 1;
+ this.context.router.history.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`, state);
}
}
--
cgit
From 3d70ec470aa99b93e518f7b1e7938687be56748e Mon Sep 17 00:00:00 2001
From: Marek Ľach
Date: Thu, 18 Apr 2019 08:08:59 +0200
Subject: Some new strings for Slovak (#10596)
---
config/locales/sk.yml | 11 +++++++----
1 file changed, 7 insertions(+), 4 deletions(-)
diff --git a/config/locales/sk.yml b/config/locales/sk.yml
index dddd79ff0..d3580c981 100644
--- a/config/locales/sk.yml
+++ b/config/locales/sk.yml
@@ -85,11 +85,12 @@ sk:
destroyed_msg: Moderátorska poznámka bola úspešne zmazaná!
accounts:
approve: Schváľ
+ approve_all: Schváľ všetky
are_you_sure: Si si istý/á?
avatar: Maskot
by_domain: Doména
change_email:
- changed_msg: Email k tomuto účtu bol úspešne zmenený!
+ changed_msg: Email pre tento účet bol úspešne zmenený!
current_email: Súčasný email
label: Zmeň email
new_email: Nový email
@@ -98,8 +99,8 @@ sk:
confirm: Potvrď
confirmed: Potvrdený
confirming: Potvrdzujúci
- deleted: Zmazané
- demote: Degradovať
+ deleted: Vymazané
+ demote: Degraduj
disable: Zablokuj
disable_two_factor_authentication: Zakáž 2FA
disabled: Blokovaný
@@ -148,7 +149,8 @@ sk:
public: Verejná časová os
push_subscription_expires: PuSH odoberanie expiruje
redownload: Obnov profil
- reject: Odmietni
+ reject: Zamietni
+ reject_all: Zamietni všetky
remove_avatar: Vymaž avatar
remove_header: Vymaž hlavičku
resend_confirmation:
@@ -250,6 +252,7 @@ sk:
feature_profile_directory: Katalóg profilov
feature_registrations: Registrácie
feature_relay: Federovací mostík
+ feature_timeline_preview: Náhľad časovej osi
features: Vymoženosti
hidden_service: Federácia so skrytými službami
open_reports: otvorené hlásenia
--
cgit
From d83068016bbf230d4a6f767c9e57d254e1592f7c Mon Sep 17 00:00:00 2001
From: Thibaut Girka
Date: Wed, 17 Apr 2019 21:50:18 +0200
Subject: Allow turning keybase off instance-wide
---
app/controllers/settings/identity_proofs_controller.rb | 5 +++++
app/controllers/well_known/keybase_proof_config_controller.rb | 8 ++++++++
app/models/form/admin_settings.rb | 2 ++
app/views/admin/dashboard/index.html.haml | 2 ++
app/views/admin/settings/edit.html.haml | 3 +++
config/locales/en.yml | 1 +
config/settings.yml | 1 +
7 files changed, 22 insertions(+)
diff --git a/app/controllers/settings/identity_proofs_controller.rb b/app/controllers/settings/identity_proofs_controller.rb
index e22b4d9be..4d0938545 100644
--- a/app/controllers/settings/identity_proofs_controller.rb
+++ b/app/controllers/settings/identity_proofs_controller.rb
@@ -5,6 +5,7 @@ class Settings::IdentityProofsController < Settings::BaseController
before_action :authenticate_user!
before_action :check_required_params, only: :new
+ before_action :check_enabled, only: :new
def index
@proofs = AccountIdentityProof.where(account: current_account).order(provider: :asc, provider_username: :asc)
@@ -41,6 +42,10 @@ class Settings::IdentityProofsController < Settings::BaseController
private
+ def check_enabled
+ not_found unless Setting.enable_keybase
+ end
+
def check_required_params
redirect_to settings_identity_proofs_path unless [:provider, :provider_username, :username, :token].all? { |k| params[k].present? }
end
diff --git a/app/controllers/well_known/keybase_proof_config_controller.rb b/app/controllers/well_known/keybase_proof_config_controller.rb
index eb41e586f..c78683a8d 100644
--- a/app/controllers/well_known/keybase_proof_config_controller.rb
+++ b/app/controllers/well_known/keybase_proof_config_controller.rb
@@ -2,8 +2,16 @@
module WellKnown
class KeybaseProofConfigController < ActionController::Base
+ before_action :check_enabled
+
def show
render json: {}, serializer: ProofProvider::Keybase::ConfigSerializer
end
+
+ private
+
+ def check_enabled
+ head 404 unless Setting.enable_keybase
+ end
end
end
diff --git a/app/models/form/admin_settings.rb b/app/models/form/admin_settings.rb
index 83d303c33..f81776346 100644
--- a/app/models/form/admin_settings.rb
+++ b/app/models/form/admin_settings.rb
@@ -27,6 +27,7 @@ class Form::AdminSettings
custom_css
profile_directory
hide_followers_count
+ enable_keybase
flavour_and_skin
thumbnail
hero
@@ -43,6 +44,7 @@ class Form::AdminSettings
preview_sensitive_media
profile_directory
hide_followers_count
+ enable_keybase
).freeze
UPLOAD_KEYS = %i(
diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml
index d448e3862..21bdcae04 100644
--- a/app/views/admin/dashboard/index.html.haml
+++ b/app/views/admin/dashboard/index.html.haml
@@ -49,6 +49,8 @@
= feature_hint(link_to(t('admin.dashboard.feature_profile_directory'), edit_admin_settings_path), @profile_directory)
%li
= feature_hint(link_to(t('admin.dashboard.feature_timeline_preview'), edit_admin_settings_path), @timeline_preview)
+ %li
+ = feature_hint(link_to(t('admin.dashboard.keybase'), edit_admin_settings_path), @timeline_preview)
%li
= feature_hint(link_to(t('admin.dashboard.feature_relay'), admin_relays_path), @relay_enabled)
diff --git a/app/views/admin/settings/edit.html.haml b/app/views/admin/settings/edit.html.haml
index 475fb3a2f..8f7acde5f 100644
--- a/app/views/admin/settings/edit.html.haml
+++ b/app/views/admin/settings/edit.html.haml
@@ -69,6 +69,9 @@
.fields-group
= f.input :hide_followers_count, as: :boolean, wrapper: :with_label, label: t('admin.settings.hide_followers_count.title'), hint: t('admin.settings.hide_followers_count.desc_html')
+ .fields-group
+ = f.input :enable_keybase, as: :boolean, wrapper: :with_label, label: t('admin.settings.enable_keybase.title'), hint: t('admin.settings.enable_keybase.desc_html')
+
%hr.spacer/
.fields-group
diff --git a/config/locales/en.yml b/config/locales/en.yml
index a2cd84fc5..09b0212a1 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -252,6 +252,7 @@ en:
feature_timeline_preview: Timeline preview
features: Features
hidden_service: Federation with hidden services
+ keybase: Keybase integration
open_reports: open reports
recent_users: Recent users
search: Full-text search
diff --git a/config/settings.yml b/config/settings.yml
index 4aa52dbf2..f0bfa9b69 100644
--- a/config/settings.yml
+++ b/config/settings.yml
@@ -31,6 +31,7 @@ defaults: &defaults
system_font_ui: false
noindex: false
hide_followers_count: false
+ enable_keybase: true
flavour: 'glitch'
skin: 'default'
aggregate_reblogs: true
--
cgit
From 93199c9566ae8ae7a5a87b8567fec27f0f34b364 Mon Sep 17 00:00:00 2001
From: Thibaut Girka
Date: Fri, 19 Apr 2019 00:47:23 +0200
Subject: Fix keybase indication on dashboard
---
app/controllers/admin/dashboard_controller.rb | 1 +
app/views/admin/dashboard/index.html.haml | 2 +-
2 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/app/controllers/admin/dashboard_controller.rb b/app/controllers/admin/dashboard_controller.rb
index f23ed1508..aedfeb70e 100644
--- a/app/controllers/admin/dashboard_controller.rb
+++ b/app/controllers/admin/dashboard_controller.rb
@@ -30,6 +30,7 @@ module Admin
@trending_hashtags = TrendingTags.get(7)
@profile_directory = Setting.profile_directory
@timeline_preview = Setting.timeline_preview
+ @keybase_integration = Setting.enable_keybase
end
private
diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml
index 21bdcae04..76dbf4388 100644
--- a/app/views/admin/dashboard/index.html.haml
+++ b/app/views/admin/dashboard/index.html.haml
@@ -50,7 +50,7 @@
%li
= feature_hint(link_to(t('admin.dashboard.feature_timeline_preview'), edit_admin_settings_path), @timeline_preview)
%li
- = feature_hint(link_to(t('admin.dashboard.keybase'), edit_admin_settings_path), @timeline_preview)
+ = feature_hint(link_to(t('admin.dashboard.keybase'), edit_admin_settings_path), @keybase_integration)
%li
= feature_hint(link_to(t('admin.dashboard.feature_relay'), admin_relays_path), @relay_enabled)
--
cgit
From 546688aa1d7355b732f10a60d4b27cb9306eee32 Mon Sep 17 00:00:00 2001
From: Thibaut Girka
Date: Fri, 19 Apr 2019 00:49:15 +0200
Subject: Add forgotten text
---
config/locales/en.yml | 3 +++
1 file changed, 3 insertions(+)
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 09b0212a1..5ec70a675 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -398,6 +398,9 @@ en:
custom_css:
desc_html: Modify the look with CSS loaded on every page
title: Custom CSS
+ enable_keybase:
+ desc_html: Allow your users to prove their identity via keybase
+ title: Enable keybase integration
hero:
desc_html: Displayed on the frontpage. At least 600x100px recommended. When not set, falls back to server thumbnail
title: Hero image
--
cgit
From 8b7b65e995849550957ecfda7fc1bf5fa9c8fcdf Mon Sep 17 00:00:00 2001
From: Thibaut Girka
Date: Fri, 19 Apr 2019 10:48:54 +0200
Subject: [Glitch] Allow modal secondary button to shrink and allow wider
confirmation modals
Port 7f75792bf305eea297c63731600af3b02968aea4 to glitch-soc
---
app/javascript/flavours/glitch/styles/components/modal.scss | 12 ++++--------
1 file changed, 4 insertions(+), 8 deletions(-)
diff --git a/app/javascript/flavours/glitch/styles/components/modal.scss b/app/javascript/flavours/glitch/styles/components/modal.scss
index fece8593b..05af34680 100644
--- a/app/javascript/flavours/glitch/styles/components/modal.scss
+++ b/app/javascript/flavours/glitch/styles/components/modal.scss
@@ -488,14 +488,6 @@
font-size: 14px;
}
-.confirmation-modal {
- max-width: 85vw;
-
- @media screen and (min-width: 480px) {
- max-width: 380px;
- }
-}
-
.mute-modal {
line-height: 24px;
}
@@ -685,6 +677,10 @@
color: darken($lighter-text-color, 4%);
}
}
+
+ .confirmation-modal__secondary-button {
+ flex-shrink: 1;
+ }
}
.confirmation-modal__do_not_ask_again {
--
cgit
From 3e6c7f3617b99db616142ebcd9f78094d4c6fca3 Mon Sep 17 00:00:00 2001
From: Sho Kusano
Date: Sun, 21 Apr 2019 11:41:34 +0900
Subject: Configrationable repository url (#10600)
* config: Add GITHUB_REPOSITORY for repository name
* config: Add SOURCE_BASE_URL for repository url
* Show source_url and repository name on getting started
---
app/javascript/mastodon/features/getting_started/index.js | 4 ++--
app/javascript/mastodon/initial_state.js | 2 ++
app/serializers/initial_state_serializer.rb | 2 ++
lib/mastodon/version.rb | 4 ++--
4 files changed, 8 insertions(+), 4 deletions(-)
diff --git a/app/javascript/mastodon/features/getting_started/index.js b/app/javascript/mastodon/features/getting_started/index.js
index e1f84de27..77c27ac6b 100644
--- a/app/javascript/mastodon/features/getting_started/index.js
+++ b/app/javascript/mastodon/features/getting_started/index.js
@@ -7,7 +7,7 @@ import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
-import { me, invitesEnabled, version, profile_directory } from '../../initial_state';
+import { me, invitesEnabled, version, profile_directory, repository, source_url } from '../../initial_state';
import { fetchFollowRequests } from '../../actions/accounts';
import { List as ImmutableList } from 'immutable';
import { Link } from 'react-router-dom';
@@ -172,7 +172,7 @@ class GettingStarted extends ImmutablePureComponent {
tootsuite/mastodon (v{version}) }}
+ values={{ github: {repository} (v{version}) }}
/>
diff --git a/app/javascript/mastodon/initial_state.js b/app/javascript/mastodon/initial_state.js
index 8c2e9d2de..74bcfee58 100644
--- a/app/javascript/mastodon/initial_state.js
+++ b/app/javascript/mastodon/initial_state.js
@@ -13,6 +13,8 @@ export const deleteModal = getMeta('delete_modal');
export const me = getMeta('me');
export const searchEnabled = getMeta('search_enabled');
export const invitesEnabled = getMeta('invites_enabled');
+export const repository = getMeta('repository');
+export const source_url = getMeta('source_url');
export const version = getMeta('version');
export const mascot = getMeta('mascot');
export const profile_directory = getMeta('profile_directory');
diff --git a/app/serializers/initial_state_serializer.rb b/app/serializers/initial_state_serializer.rb
index a7a3d770c..0c9fc625f 100644
--- a/app/serializers/initial_state_serializer.rb
+++ b/app/serializers/initial_state_serializer.rb
@@ -14,6 +14,8 @@ class InitialStateSerializer < ActiveModel::Serializer
domain: Rails.configuration.x.local_domain,
admin: object.admin&.id&.to_s,
search_enabled: Chewy.enabled?,
+ repository: Mastodon::Version.repository,
+ source_url: Mastodon::Version.source_url,
version: Mastodon::Version.to_s,
invites_enabled: Setting.min_invite_role == 'user',
mascot: instance_presenter.mascot&.file&.url,
diff --git a/lib/mastodon/version.rb b/lib/mastodon/version.rb
index b53205ee4..a656031b1 100644
--- a/lib/mastodon/version.rb
+++ b/lib/mastodon/version.rb
@@ -33,11 +33,11 @@ module Mastodon
end
def repository
- 'tootsuite/mastodon'
+ ENV.fetch('GITHUB_REPOSITORY') { 'tootsuite/mastodon' }
end
def source_base_url
- "https://github.com/#{repository}"
+ ENV.fetch('SOURCE_BASE_URL') { "https://github.com/#{repository}" }
end
# specify git tag or commit hash here
--
cgit
From 6e620dcab1d350e13d25ba34abcc31b32e64da95 Mon Sep 17 00:00:00 2001
From: ThibG
Date: Sun, 21 Apr 2019 04:42:02 +0200
Subject: Use correct local names for fonts (#10594)
---
app/javascript/styles/fonts/montserrat.scss | 2 +-
app/javascript/styles/fonts/roboto.scss | 6 +++---
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/app/javascript/styles/fonts/montserrat.scss b/app/javascript/styles/fonts/montserrat.scss
index 206f1865e..8079dc6fc 100644
--- a/app/javascript/styles/fonts/montserrat.scss
+++ b/app/javascript/styles/fonts/montserrat.scss
@@ -10,7 +10,7 @@
@font-face {
font-family: 'mastodon-font-display';
- src: local('Montserrat'),
+ src: local('Montserrat Medium'),
url('../fonts/montserrat/Montserrat-Medium.ttf') format('truetype');
font-weight: 500;
font-style: normal;
diff --git a/app/javascript/styles/fonts/roboto.scss b/app/javascript/styles/fonts/roboto.scss
index 345d9ad50..f9c7c50fe 100644
--- a/app/javascript/styles/fonts/roboto.scss
+++ b/app/javascript/styles/fonts/roboto.scss
@@ -1,6 +1,6 @@
@font-face {
font-family: 'mastodon-font-sans-serif';
- src: local('Roboto'),
+ src: local('Roboto Italic'),
url('../fonts/roboto/roboto-italic-webfont.woff2') format('woff2'),
url('../fonts/roboto/roboto-italic-webfont.woff') format('woff'),
url('../fonts/roboto/roboto-italic-webfont.ttf') format('truetype'),
@@ -11,7 +11,7 @@
@font-face {
font-family: 'mastodon-font-sans-serif';
- src: local('Roboto'),
+ src: local('Roboto Bold'),
url('../fonts/roboto/roboto-bold-webfont.woff2') format('woff2'),
url('../fonts/roboto/roboto-bold-webfont.woff') format('woff'),
url('../fonts/roboto/roboto-bold-webfont.ttf') format('truetype'),
@@ -22,7 +22,7 @@
@font-face {
font-family: 'mastodon-font-sans-serif';
- src: local('Roboto'),
+ src: local('Roboto Medium'),
url('../fonts/roboto/roboto-medium-webfont.woff2') format('woff2'),
url('../fonts/roboto/roboto-medium-webfont.woff') format('woff'),
url('../fonts/roboto/roboto-medium-webfont.ttf') format('truetype'),
--
cgit
From 951f8d5b44f5e38c163aae5b9bd330d92ea4d954 Mon Sep 17 00:00:00 2001
From: jeroenpraat <41594439+jeroenpraat@users.noreply.github.com>
Date: Sun, 21 Apr 2019 04:46:08 +0200
Subject: Update NL (Dutch) translation (#10601)
* Update NL language strings
Have to do it this way, cause this file is locked on Weblate
* fix
* Update simple_form.nl.yml
* Update nl.yml
---
app/javascript/mastodon/locales/nl.json | 2 +-
config/locales/nl.yml | 22 ++++++++++++++++++++++
config/locales/simple_form.nl.yml | 5 +++++
3 files changed, 28 insertions(+), 1 deletion(-)
diff --git a/app/javascript/mastodon/locales/nl.json b/app/javascript/mastodon/locales/nl.json
index 5fb445209..96e39356b 100644
--- a/app/javascript/mastodon/locales/nl.json
+++ b/app/javascript/mastodon/locales/nl.json
@@ -11,7 +11,7 @@
"account.follow": "Volgen",
"account.followers": "Volgers",
"account.followers.empty": "Niemand volgt nog deze gebruiker.",
- "account.follows": "Volgt",
+ "account.follows": "Volgend",
"account.follows.empty": "Deze gebruiker volgt nog niemand.",
"account.follows_you": "Volgt jou",
"account.hide_reblogs": "Verberg boosts van @{name}",
diff --git a/config/locales/nl.yml b/config/locales/nl.yml
index ae274ad70..f11a174b1 100644
--- a/config/locales/nl.yml
+++ b/config/locales/nl.yml
@@ -68,6 +68,7 @@ nl:
admin: Beheerder
bot: Bot
moderator: Moderator
+ unavailable: Profiel niet beschikbaar
unfollow: Ontvolgen
admin:
account_actions:
@@ -80,6 +81,7 @@ nl:
destroyed_msg: Verwijderen van opmerking voor moderatoren geslaagd!
accounts:
approve: Goedkeuren
+ approve_all: Alles goedkeuren
are_you_sure: Weet je het zeker?
avatar: Avatar
by_domain: Domein
@@ -132,6 +134,7 @@ nl:
moderation_notes: Opmerkingen voor moderatoren
most_recent_activity: Laatst actief
most_recent_ip: Laatst gebruikt IP-adres
+ no_account_selected: Er zijn geen accounts veranderd, omdat er geen een was geselecteerd
no_limits_imposed: Geen limieten ingesteld
not_subscribed: Niet geabonneerd
outbox_url: Outbox-URL
@@ -144,6 +147,7 @@ nl:
push_subscription_expires: PuSH-abonnement verloopt op
redownload: Profiel vernieuwen
reject: Afkeuren
+ reject_all: Alles afkeuren
remove_avatar: Avatar verwijderen
remove_header: Omslagfoto verwijderen
resend_confirmation:
@@ -245,6 +249,7 @@ nl:
feature_profile_directory: Gebruikersgids
feature_registrations: Registraties
feature_relay: Federatierelay
+ feature_timeline_preview: Voorvertoning van tijdlijn
features: Functies
hidden_service: Federatie met verborgen diensten
open_reports: onopgeloste rapportages
@@ -329,6 +334,8 @@ nl:
expired: Verlopen
title: Filter
title: Uitnodigingen
+ pending_accounts:
+ title: Accounts in afwachting (%{count})
relays:
add_new: Nieuwe relayserver toevoegen
delete: Verwijderen
@@ -585,6 +592,9 @@ nl:
content: Het spijt ons, er is aan onze kant iets fout gegaan.
title: Er is iets mis
noscript_html: Schakel JavaScript in om de webapp van Mastodon te kunnen gebruiken. Als alternatief kan je een Mastodon-app zoeken voor jouw platform.
+ existing_username_validator:
+ not_found: Kon geen lokale gebruiker met die gebruikersnaam vinden
+ not_found_multiple: Kon %{usernames} niet vinden
exports:
archive_takeout:
date: Datum
@@ -628,10 +638,13 @@ nl:
all: Alles
changes_saved_msg: Wijzigingen succesvol opgeslagen!
copy: Kopiëren
+ order_by: Sorteer op
save_changes: Wijzigingen opslaan
validation_errors:
one: Er is iets niet helemaal goed! Bekijk onderstaande fout
other: Er is iets niet helemaal goed! Bekijk onderstaande %{count} fouten
+ html_validator:
+ invalid_markup: 'bevat ongeldige HTML-opmaak: %{error}'
identity_proofs:
active: Actief
authorize: Ja, autoriseren
@@ -646,6 +659,8 @@ nl:
i_am_html: Ik ben %{username} op %{service}.
identity: Identiteit
inactive: Inactief
+ publicize_checkbox: 'En toot dit:'
+ publicize_toot: 'Het is bewezen! Ik ben %{username} op %{service}: %{url}'
status: Verificatiestatus
view_proof: Bekijk bewijs
imports:
@@ -768,6 +783,8 @@ nl:
relationships:
activity: Accountactiviteit
dormant: Sluimerend
+ last_active: Laatst actief
+ most_recent: Recentelijk gevolgd
moved: Verhuisd
mutual: Wederzijds
primary: Primair
@@ -843,6 +860,9 @@ nl:
revoke_success: Sessie succesvol ingetrokken
title: Sessies
settings:
+ account: Account
+ account_settings: Accountinstellingen
+ appearance: Uiterlijk
authorized_apps: Geautoriseerde apps
back: Terug naar Mastodon
delete: Account verwijderen
@@ -852,9 +872,11 @@ nl:
featured_tags: Uitgelichte hashtags
identity_proofs: Identiteitsbewijzen
import: Importeren
+ import_and_export: Importeren en exporteren
migrate: Accountmigratie
notifications: Meldingen
preferences: Voorkeuren
+ profile: Profiel
relationships: Volgers en gevolgden
two_factor_authentication: Tweestapsverificatie
statuses:
diff --git a/config/locales/simple_form.nl.yml b/config/locales/simple_form.nl.yml
index 0d7d1a847..09bd4e856 100644
--- a/config/locales/simple_form.nl.yml
+++ b/config/locales/simple_form.nl.yml
@@ -41,6 +41,8 @@ nl:
name: 'Je wilt misschien een van deze gebruiken:'
imports:
data: CSV-bestand dat op een andere Mastodonserver werd geëxporteerd
+ invite_request:
+ text: Dit helpt ons om jouw aanvraag te beoordelen
sessions:
otp: 'Voer de tweestaps-aanmeldcode vanaf jouw mobiele telefoon in of gebruik een van jouw herstelcodes:'
user:
@@ -118,12 +120,15 @@ nl:
must_be_follower: Meldingen van mensen die jou niet volgen blokkeren
must_be_following: Meldingen van mensen die jij niet volgt blokkeren
must_be_following_dm: Directe berichten van mensen die jij niet volgt blokkeren
+ invite_request:
+ text: Waarom wil jij je aanmelden?
notification_emails:
digest: Periodiek e-mails met een samenvatting versturen
favourite: Een e-mail versturen wanneer iemand jouw toot aan hun favorieten heeft toegevoegd
follow: Een e-mail versturen wanneer iemand jou volgt
follow_request: Een e-mail versturen wanneer iemand jou wil volgen
mention: Een e-mail versturen wanneer iemand jou vermeld
+ pending_account: Een e-mail verzenden wanneer een nieuw account moet worden beoordeeld
reblog: Een e-mail versturen wanneer iemand jouw toot heeft geboost
report: Verstuur een e-mail wanneer een nieuw rapportage is ingediend
'no': Nee
--
cgit
From 01b1c377b1fea841b9823a3134e8f41ccc4b0f29 Mon Sep 17 00:00:00 2001
From: Jessica <46502909+hyenagirl64@users.noreply.github.com>
Date: Sat, 20 Apr 2019 19:47:39 -0700
Subject: Animate avatar GIFs on-hover on public profiles (#10549)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* Third time is the charm?
* Use full asset URL for data-static and data-original
̀image_tag` expands to the full asset URL, we have to do the same in `data` attributes so that it can work when assets and user data are stored on a different host
---
app/javascript/packs/public.js | 15 +++++++++++++++
app/views/accounts/_header.html.haml | 2 +-
2 files changed, 16 insertions(+), 1 deletion(-)
diff --git a/app/javascript/packs/public.js b/app/javascript/packs/public.js
index 6a8cf9c2f..93379cdb3 100644
--- a/app/javascript/packs/public.js
+++ b/app/javascript/packs/public.js
@@ -173,6 +173,21 @@ function main() {
avatar.src = url;
});
+ const getProfileAvatarAnimationHandler = (swapTo) => {
+ //animate avatar gifs on the profile page when moused over
+ return ({ target }) => {
+ const swapSrc = target.getAttribute(swapTo);
+ //only change the img source if autoplay is off and the image src is actually different
+ if(target.getAttribute('data-autoplay') === 'false' && target.src !== swapSrc) {
+ target.src = swapSrc;
+ }
+ };
+ };
+
+ delegate(document, 'img#profile_page_avatar', 'mouseover', getProfileAvatarAnimationHandler('data-original'));
+
+ delegate(document, 'img#profile_page_avatar', 'mouseout', getProfileAvatarAnimationHandler('data-static'));
+
delegate(document, '#account_header', 'change', ({ target }) => {
const header = document.querySelector('.card .card__img img');
const [file] = target.files || [];
diff --git a/app/views/accounts/_header.html.haml b/app/views/accounts/_header.html.haml
index 370e7e470..4ef9f9478 100644
--- a/app/views/accounts/_header.html.haml
+++ b/app/views/accounts/_header.html.haml
@@ -3,7 +3,7 @@
= image_tag (current_account&.user&.setting_auto_play_gif ? account.header_original_url : account.header_static_url), class: 'parallax'
.public-account-header__bar
= link_to short_account_url(account), class: 'avatar' do
- = image_tag (current_account&.user&.setting_auto_play_gif ? account.avatar_original_url : account.avatar_static_url)
+ = image_tag (current_account&.user&.setting_auto_play_gif ? account.avatar_original_url : account.avatar_static_url), id: 'profile_page_avatar', data: {original: full_asset_url(account.avatar_original_url), static: full_asset_url(account.avatar_static_url), autoplay: current_account&.user&.setting_auto_play_gif}
.public-account-header__tabs
.public-account-header__tabs__name
%h1
--
cgit
From 10bdd912d64a45d8b445f33cd10e498d8c071e56 Mon Sep 17 00:00:00 2001
From: Daniel Aleksandersen
Date: Sun, 21 Apr 2019 04:48:19 +0200
Subject: Treat meta[property] as a space-separated list (#10604)
The @property attribute in HTML is a space-separated list of values.
This change normalizes whitespace and finds the desired value in
the list instead of requiring an exact single-value match.
More details:
https://www.ctrl.blog/entry/rdfa-socialmedia-metadata.html
---
app/services/fetch_link_card_service.rb | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/services/fetch_link_card_service.rb b/app/services/fetch_link_card_service.rb
index 7979c312e..494aaed75 100644
--- a/app/services/fetch_link_card_service.rb
+++ b/app/services/fetch_link_card_service.rb
@@ -165,7 +165,7 @@ class FetchLinkCardService < BaseService
end
def meta_property(page, property)
- page.at_xpath("//meta[@property=\"#{property}\"]")&.attribute('content')&.value || page.at_xpath("//meta[@name=\"#{property}\"]")&.attribute('content')&.value
+ page.at_xpath("//meta[contains(concat(' ', normalize-space(@property), ' '), ' #{property} ')]")&.attribute('content')&.value || page.at_xpath("//meta[@name=\"#{property}\"]")&.attribute('content')&.value
end
def lock_options
--
cgit
From d210d0a655bbf900bba627bfed9d1d80c682bc29 Mon Sep 17 00:00:00 2001
From: partev
Date: Sat, 20 Apr 2019 22:48:47 -0400
Subject: Update hy.json (#10591)
---
app/javascript/mastodon/locales/hy.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/javascript/mastodon/locales/hy.json b/app/javascript/mastodon/locales/hy.json
index c3971b09e..d155619c9 100644
--- a/app/javascript/mastodon/locales/hy.json
+++ b/app/javascript/mastodon/locales/hy.json
@@ -239,7 +239,7 @@
"navigation_bar.lists": "Ցանկեր",
"navigation_bar.logout": "Դուրս գալ",
"navigation_bar.mutes": "Լռեցրած օգտատերեր",
- "navigation_bar.personal": "Personal",
+ "navigation_bar.personal": "Անձնական",
"navigation_bar.pins": "Ամրացված թթեր",
"navigation_bar.preferences": "Նախապատվություններ",
"navigation_bar.public_timeline": "Դաշնային հոսք",
--
cgit
From a5c0aae984a38f6f6670e821cc4c0d755cf55ad1 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]"
Date: Sun, 21 Apr 2019 04:49:05 +0200
Subject: Bump lograge from 0.10.0 to 0.11.0 (#10588)
Bumps [lograge](https://github.com/roidrage/lograge) from 0.10.0 to 0.11.0.
- [Release notes](https://github.com/roidrage/lograge/releases)
- [Changelog](https://github.com/roidrage/lograge/blob/master/CHANGELOG.md)
- [Commits](https://github.com/roidrage/lograge/compare/v0.10.0...v0.11.0)
Signed-off-by: dependabot[bot]
---
Gemfile | 2 +-
Gemfile.lock | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/Gemfile b/Gemfile
index 735920a66..b2d6bc3fc 100644
--- a/Gemfile
+++ b/Gemfile
@@ -142,7 +142,7 @@ group :development do
end
group :production do
- gem 'lograge', '~> 0.10'
+ gem 'lograge', '~> 0.11'
gem 'redis-rails', '~> 5.0'
end
diff --git a/Gemfile.lock b/Gemfile.lock
index b7371b92c..9c87b0e02 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -318,7 +318,7 @@ GEM
letter_opener (~> 1.0)
railties (>= 3.2)
link_header (0.0.8)
- lograge (0.10.0)
+ lograge (0.11.0)
actionpack (>= 4)
activesupport (>= 4)
railties (>= 4)
@@ -706,7 +706,7 @@ DEPENDENCIES
letter_opener (~> 1.7)
letter_opener_web (~> 1.3)
link_header (~> 0.0)
- lograge (~> 0.10)
+ lograge (~> 0.11)
makara (~> 0.4)
mario-redis-lock (~> 1.2)
memory_profiler
--
cgit
From aa484e2cb3c63ce701cc1b800dceb9df49e19f3c Mon Sep 17 00:00:00 2001
From: "dependabot[bot]"
Date: Sun, 21 Apr 2019 04:49:44 +0200
Subject: Bump webpush from 0.3.7 to 0.3.8 (#10592)
Bumps [webpush](https://github.com/zaru/webpush) from 0.3.7 to 0.3.8.
- [Release notes](https://github.com/zaru/webpush/releases)
- [Changelog](https://github.com/zaru/webpush/blob/master/CHANGELOG.md)
- [Commits](https://github.com/zaru/webpush/compare/v0.3.7...v0.3.8)
Signed-off-by: dependabot[bot]
---
Gemfile.lock | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Gemfile.lock b/Gemfile.lock
index 9c87b0e02..73a3e1e93 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -640,7 +640,7 @@ GEM
activesupport (>= 4.2)
rack-proxy (>= 0.6.1)
railties (>= 4.2)
- webpush (0.3.7)
+ webpush (0.3.8)
hkdf (~> 0.2)
jwt (~> 2.0)
websocket-driver (0.7.0)
--
cgit
From 80c9cb0eb344d13770066e8fa76214b9830afb5a Mon Sep 17 00:00:00 2001
From: Yamagishi Kazutoshi
Date: Sun, 21 Apr 2019 11:52:20 +0900
Subject: Add hi.json (#10573)
---
app/javascript/mastodon/locales/hi.json | 384 ++++++++++++++++++++++
app/javascript/mastodon/locales/ru.json | 1 -
app/javascript/mastodon/locales/whitelist_hi.json | 2 +
3 files changed, 386 insertions(+), 1 deletion(-)
create mode 100644 app/javascript/mastodon/locales/hi.json
create mode 100644 app/javascript/mastodon/locales/whitelist_hi.json
diff --git a/app/javascript/mastodon/locales/hi.json b/app/javascript/mastodon/locales/hi.json
new file mode 100644
index 000000000..9d507346a
--- /dev/null
+++ b/app/javascript/mastodon/locales/hi.json
@@ -0,0 +1,384 @@
+{
+ "account.add_or_remove_from_list": "Add or Remove from lists",
+ "account.badges.bot": "Bot",
+ "account.block": "Block @{name}",
+ "account.block_domain": "Hide everything from {domain}",
+ "account.blocked": "Blocked",
+ "account.direct": "Direct message @{name}",
+ "account.domain_blocked": "Domain hidden",
+ "account.edit_profile": "Edit profile",
+ "account.endorse": "Feature on profile",
+ "account.follow": "Follow",
+ "account.followers": "Followers",
+ "account.followers.empty": "No one follows this user yet.",
+ "account.follows": "Follows",
+ "account.follows.empty": "This user doesn't follow anyone yet.",
+ "account.follows_you": "Follows you",
+ "account.hide_reblogs": "Hide boosts from @{name}",
+ "account.link_verified_on": "Ownership of this link was checked on {date}",
+ "account.locked_info": "This account privacy status is set to locked. The owner manually reviews who can follow them.",
+ "account.media": "Media",
+ "account.mention": "Mention @{name}",
+ "account.moved_to": "{name} has moved to:",
+ "account.mute": "Mute @{name}",
+ "account.mute_notifications": "Mute notifications from @{name}",
+ "account.muted": "Muted",
+ "account.posts": "Toots",
+ "account.posts_with_replies": "Toots and replies",
+ "account.report": "Report @{name}",
+ "account.requested": "Awaiting approval. Click to cancel follow request",
+ "account.share": "Share @{name}'s profile",
+ "account.show_reblogs": "Show boosts from @{name}",
+ "account.unblock": "Unblock @{name}",
+ "account.unblock_domain": "Unhide {domain}",
+ "account.unendorse": "Don't feature on profile",
+ "account.unfollow": "Unfollow",
+ "account.unmute": "Unmute @{name}",
+ "account.unmute_notifications": "Unmute notifications from @{name}",
+ "alert.unexpected.message": "An unexpected error occurred.",
+ "alert.unexpected.title": "Oops!",
+ "boost_modal.combo": "You can press {combo} to skip this next time",
+ "bundle_column_error.body": "Something went wrong while loading this component.",
+ "bundle_column_error.retry": "Try again",
+ "bundle_column_error.title": "Network error",
+ "bundle_modal_error.close": "Close",
+ "bundle_modal_error.message": "Something went wrong while loading this component.",
+ "bundle_modal_error.retry": "Try again",
+ "column.blocks": "Blocked users",
+ "column.community": "Local timeline",
+ "column.direct": "Direct messages",
+ "column.domain_blocks": "Hidden domains",
+ "column.favourites": "Favourites",
+ "column.follow_requests": "Follow requests",
+ "column.home": "Home",
+ "column.lists": "Lists",
+ "column.mutes": "Muted users",
+ "column.notifications": "Notifications",
+ "column.pins": "Pinned toot",
+ "column.public": "Federated timeline",
+ "column_back_button.label": "Back",
+ "column_header.hide_settings": "Hide settings",
+ "column_header.moveLeft_settings": "Move column to the left",
+ "column_header.moveRight_settings": "Move column to the right",
+ "column_header.pin": "Pin",
+ "column_header.show_settings": "Show settings",
+ "column_header.unpin": "Unpin",
+ "column_subheading.settings": "Settings",
+ "community.column_settings.media_only": "Media Only",
+ "compose_form.direct_message_warning": "This toot will only be sent to all the mentioned users.",
+ "compose_form.direct_message_warning_learn_more": "Learn more",
+ "compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.",
+ "compose_form.lock_disclaimer": "Your account is not {locked}. Anyone can follow you to view your follower-only posts.",
+ "compose_form.lock_disclaimer.lock": "locked",
+ "compose_form.placeholder": "What is on your mind?",
+ "compose_form.poll.add_option": "Add a choice",
+ "compose_form.poll.duration": "Poll duration",
+ "compose_form.poll.option_placeholder": "Choice {number}",
+ "compose_form.poll.remove_option": "Remove this choice",
+ "compose_form.publish": "Toot",
+ "compose_form.publish_loud": "{publish}!",
+ "compose_form.sensitive.marked": "Media is marked as sensitive",
+ "compose_form.sensitive.unmarked": "Media is not marked as sensitive",
+ "compose_form.spoiler.marked": "Text is hidden behind warning",
+ "compose_form.spoiler.unmarked": "Text is not hidden",
+ "compose_form.spoiler_placeholder": "Write your warning here",
+ "confirmation_modal.cancel": "Cancel",
+ "confirmations.block.block_and_report": "Block & Report",
+ "confirmations.block.confirm": "Block",
+ "confirmations.block.message": "Are you sure you want to block {name}?",
+ "confirmations.delete.confirm": "Delete",
+ "confirmations.delete.message": "Are you sure you want to delete this status?",
+ "confirmations.delete_list.confirm": "Delete",
+ "confirmations.delete_list.message": "Are you sure you want to permanently delete this list?",
+ "confirmations.domain_block.confirm": "Hide entire domain",
+ "confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable. You will not see content from that domain in any public timelines or your notifications. Your followers from that domain will be removed.",
+ "confirmations.mute.confirm": "Mute",
+ "confirmations.mute.message": "Are you sure you want to mute {name}?",
+ "confirmations.redraft.confirm": "Delete & redraft",
+ "confirmations.redraft.message": "Are you sure you want to delete this status and re-draft it? Favourites and boosts will be lost, and replies to the original post will be orphaned.",
+ "confirmations.reply.confirm": "Reply",
+ "confirmations.reply.message": "Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?",
+ "confirmations.unfollow.confirm": "Unfollow",
+ "confirmations.unfollow.message": "Are you sure you want to unfollow {name}?",
+ "embed.instructions": "Embed this status on your website by copying the code below.",
+ "embed.preview": "Here is what it will look like:",
+ "emoji_button.activity": "Activity",
+ "emoji_button.custom": "Custom",
+ "emoji_button.flags": "Flags",
+ "emoji_button.food": "Food & Drink",
+ "emoji_button.label": "Insert emoji",
+ "emoji_button.nature": "Nature",
+ "emoji_button.not_found": "No emojos!! (╯°□°)╯︵ ┻━┻",
+ "emoji_button.objects": "Objects",
+ "emoji_button.people": "People",
+ "emoji_button.recent": "Frequently used",
+ "emoji_button.search": "Search...",
+ "emoji_button.search_results": "Search results",
+ "emoji_button.symbols": "Symbols",
+ "emoji_button.travel": "Travel & Places",
+ "empty_column.account_timeline": "No toots here!",
+ "empty_column.account_unavailable": "Profile unavailable",
+ "empty_column.blocks": "You haven't blocked any users yet.",
+ "empty_column.community": "The local timeline is empty. Write something publicly to get the ball rolling!",
+ "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.",
+ "empty_column.domain_blocks": "There are no hidden domains yet.",
+ "empty_column.favourited_statuses": "You don't have any favourite toots yet. When you favourite one, it will show up here.",
+ "empty_column.favourites": "No one has favourited this toot yet. When someone does, they will show up here.",
+ "empty_column.follow_requests": "You don't have any follow requests yet. When you receive one, it will show up here.",
+ "empty_column.hashtag": "There is nothing in this hashtag yet.",
+ "empty_column.home": "Your home timeline is empty! Visit {public} or use search to get started and meet other users.",
+ "empty_column.home.public_timeline": "the public timeline",
+ "empty_column.list": "There is nothing in this list yet. When members of this list post new statuses, they will appear here.",
+ "empty_column.lists": "You don't have any lists yet. When you create one, it will show up here.",
+ "empty_column.mutes": "You haven't muted any users yet.",
+ "empty_column.notifications": "You don't have any notifications yet. Interact with others to start the conversation.",
+ "empty_column.public": "There is nothing here! Write something publicly, or manually follow users from other servers to fill it up",
+ "follow_request.authorize": "Authorize",
+ "follow_request.reject": "Reject",
+ "getting_started.developers": "Developers",
+ "getting_started.directory": "Profile directory",
+ "getting_started.documentation": "Documentation",
+ "getting_started.heading": "Getting started",
+ "getting_started.invite": "Invite people",
+ "getting_started.open_source_notice": "Mastodon is open source software. You can contribute or report issues on GitHub at {github}.",
+ "getting_started.security": "Security",
+ "getting_started.terms": "Terms of service",
+ "hashtag.column_header.tag_mode.all": "and {additional}",
+ "hashtag.column_header.tag_mode.any": "or {additional}",
+ "hashtag.column_header.tag_mode.none": "without {additional}",
+ "hashtag.column_settings.select.no_options_message": "No suggestions found",
+ "hashtag.column_settings.select.placeholder": "Enter hashtags…",
+ "hashtag.column_settings.tag_mode.all": "All of these",
+ "hashtag.column_settings.tag_mode.any": "Any of these",
+ "hashtag.column_settings.tag_mode.none": "None of these",
+ "hashtag.column_settings.tag_toggle": "Include additional tags in this column",
+ "home.column_settings.basic": "Basic",
+ "home.column_settings.show_reblogs": "Show boosts",
+ "home.column_settings.show_replies": "Show replies",
+ "intervals.full.days": "{number, plural, one {# day} other {# days}}",
+ "intervals.full.hours": "{number, plural, one {# hour} other {# hours}}",
+ "intervals.full.minutes": "{number, plural, one {# minute} other {# minutes}}",
+ "introduction.federation.action": "Next",
+ "introduction.federation.federated.headline": "Federated",
+ "introduction.federation.federated.text": "Public posts from other servers of the fediverse will appear in the federated timeline.",
+ "introduction.federation.home.headline": "Home",
+ "introduction.federation.home.text": "Posts from people you follow will appear in your home feed. You can follow anyone on any server!",
+ "introduction.federation.local.headline": "Local",
+ "introduction.federation.local.text": "Public posts from people on the same server as you will appear in the local timeline.",
+ "introduction.interactions.action": "Finish toot-orial!",
+ "introduction.interactions.favourite.headline": "Favourite",
+ "introduction.interactions.favourite.text": "You can save a toot for later, and let the author know that you liked it, by favouriting it.",
+ "introduction.interactions.reblog.headline": "Boost",
+ "introduction.interactions.reblog.text": "You can share other people's toots with your followers by boosting them.",
+ "introduction.interactions.reply.headline": "Reply",
+ "introduction.interactions.reply.text": "You can reply to other people's and your own toots, which will chain them together in a conversation.",
+ "introduction.welcome.action": "Let's go!",
+ "introduction.welcome.headline": "First steps",
+ "introduction.welcome.text": "Welcome to the fediverse! In a few moments, you'll be able to broadcast messages and talk to your friends across a wide variety of servers. But this server, {domain}, is special—it hosts your profile, so remember its name.",
+ "keyboard_shortcuts.back": "to navigate back",
+ "keyboard_shortcuts.blocked": "to open blocked users list",
+ "keyboard_shortcuts.boost": "to boost",
+ "keyboard_shortcuts.column": "to focus a status in one of the columns",
+ "keyboard_shortcuts.compose": "to focus the compose textarea",
+ "keyboard_shortcuts.description": "Description",
+ "keyboard_shortcuts.direct": "to open direct messages column",
+ "keyboard_shortcuts.down": "to move down in the list",
+ "keyboard_shortcuts.enter": "to open status",
+ "keyboard_shortcuts.favourite": "to favourite",
+ "keyboard_shortcuts.favourites": "to open favourites list",
+ "keyboard_shortcuts.federated": "to open federated timeline",
+ "keyboard_shortcuts.heading": "Keyboard Shortcuts",
+ "keyboard_shortcuts.home": "to open home timeline",
+ "keyboard_shortcuts.hotkey": "Hotkey",
+ "keyboard_shortcuts.legend": "to display this legend",
+ "keyboard_shortcuts.local": "to open local timeline",
+ "keyboard_shortcuts.mention": "to mention author",
+ "keyboard_shortcuts.muted": "to open muted users list",
+ "keyboard_shortcuts.my_profile": "to open your profile",
+ "keyboard_shortcuts.notifications": "to open notifications column",
+ "keyboard_shortcuts.pinned": "to open pinned toots list",
+ "keyboard_shortcuts.profile": "to open author's profile",
+ "keyboard_shortcuts.reply": "to reply",
+ "keyboard_shortcuts.requests": "to open follow requests list",
+ "keyboard_shortcuts.search": "to focus search",
+ "keyboard_shortcuts.start": "to open \"get started\" column",
+ "keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW",
+ "keyboard_shortcuts.toot": "to start a brand new toot",
+ "keyboard_shortcuts.unfocus": "to un-focus compose textarea/search",
+ "keyboard_shortcuts.up": "to move up in the list",
+ "lightbox.close": "Close",
+ "lightbox.next": "Next",
+ "lightbox.previous": "Previous",
+ "lists.account.add": "Add to list",
+ "lists.account.remove": "Remove from list",
+ "lists.delete": "Delete list",
+ "lists.edit": "Edit list",
+ "lists.edit.submit": "Change title",
+ "lists.new.create": "Add list",
+ "lists.new.title_placeholder": "New list title",
+ "lists.search": "Search among people you follow",
+ "lists.subheading": "Your lists",
+ "loading_indicator.label": "Loading...",
+ "media_gallery.toggle_visible": "Toggle visibility",
+ "missing_indicator.label": "Not found",
+ "missing_indicator.sublabel": "This resource could not be found",
+ "mute_modal.hide_notifications": "Hide notifications from this user?",
+ "navigation_bar.apps": "Mobile apps",
+ "navigation_bar.blocks": "Blocked users",
+ "navigation_bar.community_timeline": "Local timeline",
+ "navigation_bar.compose": "Compose new toot",
+ "navigation_bar.direct": "Direct messages",
+ "navigation_bar.discover": "Discover",
+ "navigation_bar.domain_blocks": "Hidden domains",
+ "navigation_bar.edit_profile": "Edit profile",
+ "navigation_bar.favourites": "Favourites",
+ "navigation_bar.filters": "Muted words",
+ "navigation_bar.follow_requests": "Follow requests",
+ "navigation_bar.info": "About this server",
+ "navigation_bar.keyboard_shortcuts": "Hotkeys",
+ "navigation_bar.lists": "Lists",
+ "navigation_bar.logout": "Logout",
+ "navigation_bar.mutes": "Muted users",
+ "navigation_bar.personal": "Personal",
+ "navigation_bar.pins": "Pinned toots",
+ "navigation_bar.preferences": "Preferences",
+ "navigation_bar.public_timeline": "Federated timeline",
+ "navigation_bar.security": "Security",
+ "notification.favourite": "{name} favourited your status",
+ "notification.follow": "{name} followed you",
+ "notification.mention": "{name} mentioned you",
+ "notification.poll": "A poll you have voted in has ended",
+ "notification.reblog": "{name} boosted your status",
+ "notifications.clear": "Clear notifications",
+ "notifications.clear_confirmation": "Are you sure you want to permanently clear all your notifications?",
+ "notifications.column_settings.alert": "Desktop notifications",
+ "notifications.column_settings.favourite": "Favourites:",
+ "notifications.column_settings.filter_bar.advanced": "Display all categories",
+ "notifications.column_settings.filter_bar.category": "Quick filter bar",
+ "notifications.column_settings.filter_bar.show": "Show",
+ "notifications.column_settings.follow": "New followers:",
+ "notifications.column_settings.mention": "Mentions:",
+ "notifications.column_settings.poll": "Poll results:",
+ "notifications.column_settings.push": "Push notifications",
+ "notifications.column_settings.reblog": "Boosts:",
+ "notifications.column_settings.show": "Show in column",
+ "notifications.column_settings.sound": "Play sound",
+ "notifications.filter.all": "All",
+ "notifications.filter.boosts": "Boosts",
+ "notifications.filter.favourites": "Favourites",
+ "notifications.filter.follows": "Follows",
+ "notifications.filter.mentions": "Mentions",
+ "notifications.filter.polls": "Poll results",
+ "notifications.group": "{count} notifications",
+ "poll.closed": "Closed",
+ "poll.refresh": "Refresh",
+ "poll.total_votes": "{count, plural, one {# vote} other {# votes}}",
+ "poll.vote": "Vote",
+ "poll_button.add_poll": "Add a poll",
+ "poll_button.remove_poll": "Remove poll",
+ "privacy.change": "Adjust status privacy",
+ "privacy.direct.long": "Post to mentioned users only",
+ "privacy.direct.short": "Direct",
+ "privacy.private.long": "Post to followers only",
+ "privacy.private.short": "Followers-only",
+ "privacy.public.long": "Post to public timelines",
+ "privacy.public.short": "Public",
+ "privacy.unlisted.long": "Do not show in public timelines",
+ "privacy.unlisted.short": "Unlisted",
+ "regeneration_indicator.label": "Loading…",
+ "regeneration_indicator.sublabel": "Your home feed is being prepared!",
+ "relative_time.days": "{number}d",
+ "relative_time.hours": "{number}h",
+ "relative_time.just_now": "now",
+ "relative_time.minutes": "{number}m",
+ "relative_time.seconds": "{number}s",
+ "reply_indicator.cancel": "Cancel",
+ "report.forward": "Forward to {target}",
+ "report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
+ "report.hint": "The report will be sent to your server moderators. You can provide an explanation of why you are reporting this account below:",
+ "report.placeholder": "Additional comments",
+ "report.submit": "Submit",
+ "report.target": "Report {target}",
+ "search.placeholder": "Search",
+ "search_popout.search_format": "Advanced search format",
+ "search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
+ "search_popout.tips.hashtag": "hashtag",
+ "search_popout.tips.status": "status",
+ "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags",
+ "search_popout.tips.user": "user",
+ "search_results.accounts": "People",
+ "search_results.hashtags": "Hashtags",
+ "search_results.statuses": "Toots",
+ "search_results.total": "{count, number} {count, plural, one {result} other {results}}",
+ "status.admin_account": "Open moderation interface for @{name}",
+ "status.admin_status": "Open this status in the moderation interface",
+ "status.block": "Block @{name}",
+ "status.cancel_reblog_private": "Unboost",
+ "status.cannot_reblog": "This post cannot be boosted",
+ "status.copy": "Copy link to status",
+ "status.delete": "Delete",
+ "status.detailed_status": "Detailed conversation view",
+ "status.direct": "Direct message @{name}",
+ "status.embed": "Embed",
+ "status.favourite": "Favourite",
+ "status.filtered": "Filtered",
+ "status.load_more": "Load more",
+ "status.media_hidden": "Media hidden",
+ "status.mention": "Mention @{name}",
+ "status.more": "More",
+ "status.mute": "Mute @{name}",
+ "status.mute_conversation": "Mute conversation",
+ "status.open": "Expand this status",
+ "status.pin": "Pin on profile",
+ "status.pinned": "Pinned toot",
+ "status.read_more": "Read more",
+ "status.reblog": "Boost",
+ "status.reblog_private": "Boost to original audience",
+ "status.reblogged_by": "{name} boosted",
+ "status.reblogs.empty": "No one has boosted this toot yet. When someone does, they will show up here.",
+ "status.redraft": "Delete & re-draft",
+ "status.reply": "Reply",
+ "status.replyAll": "Reply to thread",
+ "status.report": "Report @{name}",
+ "status.sensitive_toggle": "Click to view",
+ "status.sensitive_warning": "Sensitive content",
+ "status.share": "Share",
+ "status.show_less": "Show less",
+ "status.show_less_all": "Show less for all",
+ "status.show_more": "Show more",
+ "status.show_more_all": "Show more for all",
+ "status.show_thread": "Show thread",
+ "status.unmute_conversation": "Unmute conversation",
+ "status.unpin": "Unpin from profile",
+ "suggestions.dismiss": "Dismiss suggestion",
+ "suggestions.header": "You might be interested in…",
+ "tabs_bar.federated_timeline": "Federated",
+ "tabs_bar.home": "Home",
+ "tabs_bar.local_timeline": "Local",
+ "tabs_bar.notifications": "Notifications",
+ "tabs_bar.search": "Search",
+ "time_remaining.days": "{number, plural, one {# day} other {# days}} left",
+ "time_remaining.hours": "{number, plural, one {# hour} other {# hours}} left",
+ "time_remaining.minutes": "{number, plural, one {# minute} other {# minutes}} left",
+ "time_remaining.moments": "Moments remaining",
+ "time_remaining.seconds": "{number, plural, one {# second} other {# seconds}} left",
+ "trends.count_by_accounts": "{count} {rawCount, plural, one {person} other {people}} talking",
+ "ui.beforeunload": "Your draft will be lost if you leave Mastodon.",
+ "upload_area.title": "Drag & drop to upload",
+ "upload_button.label": "Add media (JPEG, PNG, GIF, WebM, MP4, MOV)",
+ "upload_error.limit": "File upload limit exceeded.",
+ "upload_error.poll": "File upload not allowed with polls.",
+ "upload_form.description": "Describe for the visually impaired",
+ "upload_form.focus": "Crop",
+ "upload_form.undo": "Delete",
+ "upload_progress.label": "Uploading...",
+ "video.close": "Close video",
+ "video.exit_fullscreen": "Exit full screen",
+ "video.expand": "Expand video",
+ "video.fullscreen": "Full screen",
+ "video.hide": "Hide video",
+ "video.mute": "Mute sound",
+ "video.pause": "Pause",
+ "video.play": "Play",
+ "video.unmute": "Unmute sound"
+}
diff --git a/app/javascript/mastodon/locales/ru.json b/app/javascript/mastodon/locales/ru.json
index 13f511cbf..475899797 100644
--- a/app/javascript/mastodon/locales/ru.json
+++ b/app/javascript/mastodon/locales/ru.json
@@ -118,7 +118,6 @@
"emoji_button.travel": "Путешествия",
"empty_column.account_timeline": "Статусов нет!",
"empty_column.account_unavailable": "Профиль недоступен",
- "empty_column.account_timeline_blocked": "Вы заблокированы",
"empty_column.blocks": "Вы ещё никого не заблокировали.",
"empty_column.community": "Локальная лента пуста. Напишите что-нибудь, чтобы разогреть народ!",
"empty_column.direct": "У Вас пока нет личных сообщений. Когда Вы начнёте их отправлять или получать, они появятся здесь.",
diff --git a/app/javascript/mastodon/locales/whitelist_hi.json b/app/javascript/mastodon/locales/whitelist_hi.json
new file mode 100644
index 000000000..0d4f101c7
--- /dev/null
+++ b/app/javascript/mastodon/locales/whitelist_hi.json
@@ -0,0 +1,2 @@
+[
+]
--
cgit
From be8692b938cb354923288af513ceef932ca27760 Mon Sep 17 00:00:00 2001
From: Ben Lubar
Date: Sat, 20 Apr 2019 21:53:24 -0500
Subject: Default to the web domain (eg. mastodon.lubar.me) instead of the
local domain (eg. lubar.me) for keybase proofs (#10565)
---
app/lib/proof_provider/keybase.rb | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/lib/proof_provider/keybase.rb b/app/lib/proof_provider/keybase.rb
index 9680b90ee..8e51d7146 100644
--- a/app/lib/proof_provider/keybase.rb
+++ b/app/lib/proof_provider/keybase.rb
@@ -2,7 +2,7 @@
class ProofProvider::Keybase
BASE_URL = ENV.fetch('KEYBASE_BASE_URL', 'https://keybase.io')
- DOMAIN = ENV.fetch('KEYBASE_DOMAIN', Rails.configuration.x.local_domain)
+ DOMAIN = ENV.fetch('KEYBASE_DOMAIN', Rails.configuration.x.web_domain)
class Error < StandardError; end
--
cgit
From a430f2a03e28bdd1b7185ae5c47e102c4219a618 Mon Sep 17 00:00:00 2001
From: Yamagishi Kazutoshi
Date: Sun, 21 Apr 2019 11:53:45 +0900
Subject: i18n: Update Catalan translation (#10559)
* Updated Catalan strings
* Update ca.yml
* Update ca.yml
* Update ca.yml
* Update ca.yml
* Update ca.yml
* Update ca.yml
* Update ca.yml
* Update simple_form.ca.yml
* Update simple_form.ca.yml
* Update simple_form.ca.yml
* bundle exec i18n-tasks
* Update ca.json
* Update simple_form.ca.yml
* i18n: Update Catalan translations
* i18n: Update Catalan translations
* i18n: Update Catalan translations
* i18n: Update Catalan translations
* i18n: Update Catalan translations
* i18n: Update Catalan translations
* i18n: Update Catalan translations
* i18n: Update Catalan translations
* i18n: Update Catalan translations
---
config/locales/ca.yml | 17 ++++++++++++++---
1 file changed, 14 insertions(+), 3 deletions(-)
diff --git a/config/locales/ca.yml b/config/locales/ca.yml
index 17a5d9d0c..6169767da 100644
--- a/config/locales/ca.yml
+++ b/config/locales/ca.yml
@@ -68,6 +68,7 @@ ca:
admin: Administrador
bot: Bot
moderator: Moderador
+ unavailable: Perfil inaccessible
unfollow: Deixa de seguir
admin:
account_actions:
@@ -80,6 +81,7 @@ ca:
destroyed_msg: Nota de moderació destruïda amb èxit!
accounts:
approve: Aprova
+ approve_all: Aprova'ls tots
are_you_sure: N'estàs segur?
avatar: Avatar
by_domain: Domini
@@ -132,6 +134,7 @@ ca:
moderation_notes: Notes de moderació
most_recent_activity: Activitat més recent
most_recent_ip: IP més recent
+ no_account_selected: No s'han canviat els comptes perque no s'han seleccionat
no_limits_imposed: Sense límits imposats
not_subscribed: No subscrit
outbox_url: URL de la bústia de sortida
@@ -144,6 +147,7 @@ ca:
push_subscription_expires: La subscripció PuSH expira
redownload: Actualitza el perfil
reject: Rebutja
+ reject_all: Rebutja'ls tots
remove_avatar: Eliminar avatar
remove_header: Treu la capçalera
resend_confirmation:
@@ -330,6 +334,8 @@ ca:
expired: Caducat
title: Filtre
title: Convida
+ pending_accounts:
+ title: Comptes pendents (%{count})
relays:
add_new: Afegiu un nou relay
delete: Esborra
@@ -854,18 +860,23 @@ ca:
revoke_success: S'ha revocat la sessió amb èxit
title: Sessions
settings:
+ account: Compte
+ account_settings: Ajustos del compte
+ appearance: Aparènça
authorized_apps: Aplicacions autoritzades
- back: Torna a l'inici
+ back: Torna a Mastodon
delete: Eliminació del compte
development: Desenvolupament
edit_profile: Editar perfil
- export: Exportar informació
+ export: Exportar dades
featured_tags: Etiquetes destacades
identity_proofs: Proves d'identitat
import: Importar
+ import_and_export: Importar i exportar
migrate: Migració del compte
notifications: Notificacions
preferences: Preferències
+ profile: Perfil
relationships: Seguits i seguidors
two_factor_authentication: Autenticació de dos factors
statuses:
@@ -1040,7 +1051,7 @@ ca:
welcome:
edit_profile_action: Configurar perfil
edit_profile_step: Pots personalitzar el teu perfil penjant un avatar, un encapçalament, canviant el teu nom de visualització i molt més. Si prefereixes revisar els seguidors nous abans de que et puguin seguir, pots blocar el teu compte.
- explanation: Aquests són alguns consells per començar
+ explanation: Aquests són alguns consells per a començar
final_action: Comença a publicar
final_step: 'Comença a publicar! Fins i tot sense seguidors, els altres poden veure els teus missatges públics, per exemple, a la línia de temps local i a les etiquetes ("hashtags"). És possible que vulguis presentar-te amb l''etiqueta #introductions.'
full_handle: El teu nom d'usuari sencer
--
cgit
From cf5414e5d374329f94edddae4a5a9aaa0e5a35df Mon Sep 17 00:00:00 2001
From: jeroenpraat <41594439+jeroenpraat@users.noreply.github.com>
Date: Sun, 21 Apr 2019 18:44:51 +0200
Subject: Small fix Dutch translation (#10612)
* Small fix Dutch translation
* Better descriptive titles
See #10611
---
config/locales/nl.yml | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/config/locales/nl.yml b/config/locales/nl.yml
index f11a174b1..e07e7b133 100644
--- a/config/locales/nl.yml
+++ b/config/locales/nl.yml
@@ -435,14 +435,14 @@ nl:
desc_html: Medewerkersbadge op profielpagina tonen
title: Medewerkersbadge tonen
site_description:
- desc_html: Dit wordt als een alinea op de voorpagina getoond. Beschrijf wat er speciaal is aan deze server en andere zaken die van belang zijn. Je kan HTML gebruiken, zoals <a>
en <em>
.
- title: Omschrijving Mastodonserver
+ desc_html: Introductie-alinea voor de API. Beschrijf wat er speciaal is aan deze server en andere zaken die van belang zijn. Je kan HTML gebruiken, zoals <a>
en <em>
.
+ title: Omschrijving Mastodonserver (API)
site_description_extended:
desc_html: Een goede plek voor je gedragscode, regels, richtlijnen en andere zaken die jouw server uniek maken. Je kan ook hier HTML gebruiken
title: Uitgebreide omschrijving Mastodonserver
site_short_description:
- desc_html: Dit wordt in de zijbalk getoond als en als metatag in de paginabron. Beschrijf in één alinea wat Mastodon is en wat deze server speciaal maakt. De (langere) omschrijving van de Mastodonserver wordt gebruikt wanneer dit veld wordt leeg gelaten.
- title: Korte omschrijving Mastodonserver
+ desc_html: Dit wordt gebruikt op de voorpagina, in de zijbalk op profielpagina's en als metatag in de paginabron. Beschrijf in één alinea wat Mastodon is en wat deze server speciaal maakt.
+ title: Omschrijving Mastodonserver (website)
site_terms:
desc_html: Je kan hier jouw eigen privacybeleid, gebruiksvoorwaarden en ander juridisch jargon kwijt. Je kan HTML gebruiken
title: Aangepaste gebruiksvoorwaarden
--
cgit
From 46321dc993c78be18b73bf2d240cb78e05d6490b Mon Sep 17 00:00:00 2001
From: marcin mikołajczak
Date: Sun, 21 Apr 2019 22:30:04 +0200
Subject: i18n: Fix misleading strings in Polish translation (#10615)
---
config/locales/simple_form.pl.yml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/config/locales/simple_form.pl.yml b/config/locales/simple_form.pl.yml
index abc2c17a9..e1cbee91c 100644
--- a/config/locales/simple_form.pl.yml
+++ b/config/locales/simple_form.pl.yml
@@ -29,8 +29,8 @@ pl:
setting_aggregate_reblogs: Nie pokazuj nowych podbić dla wpisów, które zostały niedawno podbite (dotyczy tylko nowo otrzymanych podbić)
setting_default_language: Język Twoich wpisów może być wykrywany automatycznie, ale nie zawsze jest to dokładne
setting_display_media_default: Ukrywaj zawartość oznaczoną jako wrażliwa
- setting_display_media_hide_all: Zawsze ukrywaj zawartość multimedialną
- setting_display_media_show_all: Zawsze pokazuj zawartość multimedialną jako wrażliwą
+ setting_display_media_hide_all: Zawsze oznaczaj zawartość multimedialną jako wrażliwą
+ setting_display_media_show_all: Nie ukrywaj zawartości multimedialnej oznaczonej jako wrażliwa
setting_hide_network: Informacje o tym, kto Cię śledzi i kogo śledzisz nie będą widoczne
setting_noindex: Wpływa na widoczność strony profilu i Twoich wpisów
setting_show_application: W informacjach o wpisie będzie widoczna informacja o aplikacji, z której został wysłany
--
cgit
From c9441bf82ecf00d144fe7d26d95bae104ac7968c Mon Sep 17 00:00:00 2001
From: "dependabot[bot]"
Date: Mon, 22 Apr 2019 14:54:19 +0200
Subject: Bump cld3 from 3.2.3 to 3.2.4 (#10617)
Bumps [cld3](https://github.com/akihikodaki/cld3-ruby) from 3.2.3 to 3.2.4.
- [Release notes](https://github.com/akihikodaki/cld3-ruby/releases)
- [Commits](https://github.com/akihikodaki/cld3-ruby/compare/v3.2.3...v3.2.4)
Signed-off-by: dependabot[bot]
---
Gemfile | 2 +-
Gemfile.lock | 8 ++++----
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/Gemfile b/Gemfile
index b2d6bc3fc..78d2a1d34 100644
--- a/Gemfile
+++ b/Gemfile
@@ -29,7 +29,7 @@ gem 'browser'
gem 'charlock_holmes', '~> 0.7.6'
gem 'iso-639'
gem 'chewy', '~> 5.0'
-gem 'cld3', '~> 3.2.3'
+gem 'cld3', '~> 3.2.4'
gem 'devise', '~> 4.6'
gem 'devise-two-factor', '~> 3.0'
diff --git a/Gemfile.lock b/Gemfile.lock
index 73a3e1e93..c9e46f27a 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -143,8 +143,8 @@ GEM
elasticsearch (>= 2.0.0)
elasticsearch-dsl
chunky_png (1.3.10)
- cld3 (3.2.3)
- ffi (>= 1.1.0, < 1.10.0)
+ cld3 (3.2.4)
+ ffi (>= 1.1.0, < 1.11.0)
climate_control (0.2.0)
cocaine (0.5.8)
climate_control (>= 0.0.3, < 1.0)
@@ -212,7 +212,7 @@ GEM
multipart-post (>= 1.2, < 3)
fast_blank (1.0.0)
fastimage (2.1.5)
- ffi (1.9.25)
+ ffi (1.10.0)
fog-core (2.1.0)
builder
excon (~> 0.58)
@@ -673,7 +673,7 @@ DEPENDENCIES
capybara (~> 3.16)
charlock_holmes (~> 0.7.6)
chewy (~> 5.0)
- cld3 (~> 3.2.3)
+ cld3 (~> 3.2.4)
climate_control (~> 0.2)
concurrent-ruby
derailed_benchmarks
--
cgit
From bdec58b514a18779c931d20f947e7ca80b29e8b4 Mon Sep 17 00:00:00 2001
From: ThibG
Date: Mon, 22 Apr 2019 14:55:24 +0200
Subject: Minor code cleanup (#10613)
---
.../features/compose/components/compose_form.js | 25 +++++++++++-----------
.../mastodon/features/compose/components/search.js | 8 ++-----
.../compose/containers/compose_form_container.js | 13 ++++++-----
3 files changed, 20 insertions(+), 26 deletions(-)
diff --git a/app/javascript/mastodon/features/compose/components/compose_form.js b/app/javascript/mastodon/features/compose/components/compose_form.js
index 4b0bde659..ddb610a89 100644
--- a/app/javascript/mastodon/features/compose/components/compose_form.js
+++ b/app/javascript/mastodon/features/compose/components/compose_form.js
@@ -40,17 +40,16 @@ class ComposeForm extends ImmutablePureComponent {
static propTypes = {
intl: PropTypes.object.isRequired,
text: PropTypes.string.isRequired,
- suggestion_token: PropTypes.string,
suggestions: ImmutablePropTypes.list,
spoiler: PropTypes.bool,
privacy: PropTypes.string,
- spoiler_text: PropTypes.string,
+ spoilerText: PropTypes.string,
focusDate: PropTypes.instanceOf(Date),
caretPosition: PropTypes.number,
preselectDate: PropTypes.instanceOf(Date),
- is_submitting: PropTypes.bool,
- is_changing_upload: PropTypes.bool,
- is_uploading: PropTypes.bool,
+ isSubmitting: PropTypes.bool,
+ isChangingUpload: PropTypes.bool,
+ isUploading: PropTypes.bool,
onChange: PropTypes.func.isRequired,
onSubmit: PropTypes.func.isRequired,
onClearSuggestions: PropTypes.func.isRequired,
@@ -85,10 +84,10 @@ class ComposeForm extends ImmutablePureComponent {
}
// Submit disabled:
- const { is_submitting, is_changing_upload, is_uploading, anyMedia } = this.props;
- const fulltext = [this.props.spoiler_text, countableText(this.props.text)].join('');
+ const { isSubmitting, isChangingUpload, isUploading, anyMedia } = this.props;
+ const fulltext = [this.props.spoilerText, countableText(this.props.text)].join('');
- if (is_submitting || is_uploading || is_changing_upload || length(fulltext) > 500 || (fulltext.length !== 0 && fulltext.trim().length === 0 && !anyMedia)) {
+ if (isSubmitting || isUploading || isChangingUpload || length(fulltext) > 500 || (fulltext.length !== 0 && fulltext.trim().length === 0 && !anyMedia)) {
return;
}
@@ -133,7 +132,7 @@ class ComposeForm extends ImmutablePureComponent {
this.autosuggestTextarea.textarea.setSelectionRange(selectionStart, selectionEnd);
this.autosuggestTextarea.textarea.focus();
- } else if(prevProps.is_submitting && !this.props.is_submitting) {
+ } else if(prevProps.isSubmitting && !this.props.isSubmitting) {
this.autosuggestTextarea.textarea.focus();
} else if (this.props.spoiler !== prevProps.spoiler) {
if (this.props.spoiler) {
@@ -162,9 +161,9 @@ class ComposeForm extends ImmutablePureComponent {
render () {
const { intl, onPaste, showSearch, anyMedia } = this.props;
- const disabled = this.props.is_submitting;
- const text = [this.props.spoiler_text, countableText(this.props.text)].join('');
- const disabledButton = disabled || this.props.is_uploading || this.props.is_changing_upload || length(text) > 500 || (text.length !== 0 && text.trim().length === 0 && !anyMedia);
+ const disabled = this.props.isSubmitting;
+ const text = [this.props.spoilerText, countableText(this.props.text)].join('');
+ const disabledButton = disabled || this.props.isUploading || this.props.isChangingUpload || length(text) > 500 || (text.length !== 0 && text.trim().length === 0 && !anyMedia);
let publishText = '';
if (this.props.privacy === 'private' || this.props.privacy === 'direct') {
@@ -182,7 +181,7 @@ class ComposeForm extends ImmutablePureComponent {
{intl.formatMessage(messages.spoiler_placeholder)}
-
+
diff --git a/app/javascript/mastodon/features/compose/components/search.js b/app/javascript/mastodon/features/compose/components/search.js
index 1ab197ac5..774658b1b 100644
--- a/app/javascript/mastodon/features/compose/components/search.js
+++ b/app/javascript/mastodon/features/compose/components/search.js
@@ -73,7 +73,7 @@ class Search extends React.PureComponent {
}
}
- handleKeyDown = (e) => {
+ handleKeyUp = (e) => {
if (e.key === 'Enter') {
e.preventDefault();
this.props.onSubmit();
@@ -82,10 +82,6 @@ class Search extends React.PureComponent {
}
}
- noop () {
-
- }
-
handleFocus = () => {
this.setState({ expanded: true });
this.props.onShow();
@@ -110,7 +106,7 @@ class Search extends React.PureComponent {
placeholder={intl.formatMessage(messages.placeholder)}
value={value}
onChange={this.handleChange}
- onKeyUp={this.handleKeyDown}
+ onKeyUp={this.handleKeyUp}
onFocus={this.handleFocus}
onBlur={this.handleBlur}
/>
diff --git a/app/javascript/mastodon/features/compose/containers/compose_form_container.js b/app/javascript/mastodon/features/compose/containers/compose_form_container.js
index b4a1c4b44..f9f1fba36 100644
--- a/app/javascript/mastodon/features/compose/containers/compose_form_container.js
+++ b/app/javascript/mastodon/features/compose/containers/compose_form_container.js
@@ -1,6 +1,5 @@
import { connect } from 'react-redux';
import ComposeForm from '../components/compose_form';
-import { uploadCompose } from '../../../actions/compose';
import {
changeCompose,
submitCompose,
@@ -9,21 +8,21 @@ import {
selectComposeSuggestion,
changeComposeSpoilerText,
insertEmojiCompose,
+ uploadCompose,
} from '../../../actions/compose';
const mapStateToProps = state => ({
text: state.getIn(['compose', 'text']),
- suggestion_token: state.getIn(['compose', 'suggestion_token']),
suggestions: state.getIn(['compose', 'suggestions']),
spoiler: state.getIn(['compose', 'spoiler']),
- spoiler_text: state.getIn(['compose', 'spoiler_text']),
+ spoilerText: state.getIn(['compose', 'spoiler_text']),
privacy: state.getIn(['compose', 'privacy']),
focusDate: state.getIn(['compose', 'focusDate']),
caretPosition: state.getIn(['compose', 'caretPosition']),
preselectDate: state.getIn(['compose', 'preselectDate']),
is_submitting: state.getIn(['compose', 'is_submitting']),
- is_changing_upload: state.getIn(['compose', 'is_changing_upload']),
- is_uploading: state.getIn(['compose', 'is_uploading']),
+ isChangingUpload: state.getIn(['compose', 'is_changing_upload']),
+ isUploading: state.getIn(['compose', 'is_uploading']),
showSearch: state.getIn(['search', 'submitted']) && !state.getIn(['search', 'hidden']),
anyMedia: state.getIn(['compose', 'media_attachments']).size > 0,
});
@@ -46,8 +45,8 @@ const mapDispatchToProps = (dispatch) => ({
dispatch(fetchComposeSuggestions(token));
},
- onSuggestionSelected (position, token, accountId) {
- dispatch(selectComposeSuggestion(position, token, accountId));
+ onSuggestionSelected (position, token, suggestion) {
+ dispatch(selectComposeSuggestion(position, token, suggestion));
},
onChangeSpoilerText (checked) {
--
cgit
From d763d39d2628bef123cdc801b2a3a3922b7e37f2 Mon Sep 17 00:00:00 2001
From: kedama
Date: Mon, 22 Apr 2019 21:55:50 +0900
Subject: Fix modal items cannot scroll on touch devices (#10605)
---
app/javascript/mastodon/features/ui/components/actions_modal.js | 2 +-
app/javascript/styles/mastodon/components.scss | 5 +++++
2 files changed, 6 insertions(+), 1 deletion(-)
diff --git a/app/javascript/mastodon/features/ui/components/actions_modal.js b/app/javascript/mastodon/features/ui/components/actions_modal.js
index 9792eba5f..00280f7a6 100644
--- a/app/javascript/mastodon/features/ui/components/actions_modal.js
+++ b/app/javascript/mastodon/features/ui/components/actions_modal.js
@@ -64,7 +64,7 @@ export default class ActionsModal extends ImmutablePureComponent {
{status}
-
+
{this.props.actions.map(this.renderAction)}
diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss
index e697ba554..0b1fd3652 100644
--- a/app/javascript/styles/mastodon/components.scss
+++ b/app/javascript/styles/mastodon/components.scss
@@ -4085,6 +4085,11 @@ a.status-card.compact:hover {
ul {
overflow-y: auto;
flex-shrink: 0;
+ max-height: 80vh;
+
+ &.with-status {
+ max-height: calc(80vh - 75px);
+ }
li:empty {
margin: 0;
--
cgit
From 0e78862b617bf5501b38d8ea7704fa53dd5912cf Mon Sep 17 00:00:00 2001
From: ThibG
Date: Mon, 22 Apr 2019 14:56:14 +0200
Subject: Allow switching between singe-option and multiple-option polls
(#10603)
---
.../features/compose/components/poll_form.js | 20 ++++++++++++++++++--
1 file changed, 18 insertions(+), 2 deletions(-)
diff --git a/app/javascript/mastodon/features/compose/components/poll_form.js b/app/javascript/mastodon/features/compose/components/poll_form.js
index 4fb95f3c9..383e37eb6 100644
--- a/app/javascript/mastodon/features/compose/components/poll_form.js
+++ b/app/javascript/mastodon/features/compose/components/poll_form.js
@@ -26,6 +26,7 @@ class Option extends React.PureComponent {
isPollMultiple: PropTypes.bool,
onChange: PropTypes.func.isRequired,
onRemove: PropTypes.func.isRequired,
+ onToggleMultiple: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
};
@@ -37,13 +38,24 @@ class Option extends React.PureComponent {
this.props.onRemove(this.props.index);
};
+ handleToggleMultiple = e => {
+ this.props.onToggleMultiple();
+ e.preventDefault();
+ e.stopPropagation();
+ };
+
render () {
const { isPollMultiple, title, index, intl } = this.props;
return (
-
+
{
+ this.props.onChangeSettings(this.props.expiresIn, !this.props.isMultiple);
+ };
+
render () {
const { options, expiresIn, isMultiple, onChangeOption, onRemoveOption, intl } = this.props;
@@ -96,7 +112,7 @@ class PollForm extends ImmutablePureComponent {
return (
- {options.map((title, i) => )}
+ {options.map((title, i) => )}
--
cgit
From 5c34163dbc8523c65597b44c3880cc2bfd30cd75 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]"
Date: Mon, 22 Apr 2019 15:02:59 +0200
Subject: Bump doorkeeper from 5.0.2 to 5.1.0 (#10598)
Bumps [doorkeeper](https://github.com/doorkeeper-gem/doorkeeper) from 5.0.2 to 5.1.0.
- [Release notes](https://github.com/doorkeeper-gem/doorkeeper/releases)
- [Changelog](https://github.com/doorkeeper-gem/doorkeeper/blob/master/NEWS.md)
- [Commits](https://github.com/doorkeeper-gem/doorkeeper/compare/v5.0.2...v5.1.0)
Signed-off-by: dependabot[bot]
---
Gemfile | 2 +-
Gemfile.lock | 6 +++---
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/Gemfile b/Gemfile
index 78d2a1d34..93e495de3 100644
--- a/Gemfile
+++ b/Gemfile
@@ -42,7 +42,7 @@ gem 'omniauth-cas', '~> 1.1'
gem 'omniauth-saml', '~> 1.10'
gem 'omniauth', '~> 1.9'
-gem 'doorkeeper', '~> 5.0'
+gem 'doorkeeper', '~> 5.1'
gem 'fast_blank', '~> 1.0'
gem 'fastimage'
gem 'goldfinger', '~> 2.1'
diff --git a/Gemfile.lock b/Gemfile.lock
index c9e46f27a..da1211a64 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -184,8 +184,8 @@ GEM
docile (1.3.0)
domain_name (0.5.20180417)
unf (>= 0.0.5, < 1.0.0)
- doorkeeper (5.0.2)
- railties (>= 4.2)
+ doorkeeper (5.1.0)
+ railties (>= 5)
dotenv (2.7.2)
dotenv-rails (2.7.2)
dotenv (= 2.7.2)
@@ -680,7 +680,7 @@ DEPENDENCIES
devise (~> 4.6)
devise-two-factor (~> 3.0)
devise_pam_authenticatable2 (~> 9.2)
- doorkeeper (~> 5.0)
+ doorkeeper (~> 5.1)
dotenv-rails (~> 2.7)
fabrication (~> 2.20)
faker (~> 1.9)
--
cgit
From fd9f5a467f9af857338380bf78b6b7037a12f171 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]"
Date: Mon, 22 Apr 2019 15:03:20 +0200
Subject: Bump aws-sdk-s3 from 1.36.0 to 1.36.1 (#10618)
Bumps [aws-sdk-s3](https://github.com/aws/aws-sdk-ruby) from 1.36.0 to 1.36.1.
- [Release notes](https://github.com/aws/aws-sdk-ruby/releases)
- [Changelog](https://github.com/aws/aws-sdk-ruby/blob/master/gems/aws-sdk-s3/CHANGELOG.md)
- [Commits](https://github.com/aws/aws-sdk-ruby/compare/v1.36.0...v1.36.1)
Signed-off-by: dependabot[bot]
---
Gemfile.lock | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/Gemfile.lock b/Gemfile.lock
index da1211a64..80942a8db 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -76,16 +76,16 @@ GEM
av (0.9.0)
cocaine (~> 0.5.3)
aws-eventstream (1.0.2)
- aws-partitions (1.147.0)
- aws-sdk-core (3.48.3)
+ aws-partitions (1.151.0)
+ aws-sdk-core (3.48.4)
aws-eventstream (~> 1.0, >= 1.0.2)
aws-partitions (~> 1.0)
aws-sigv4 (~> 1.1)
jmespath (~> 1.0)
- aws-sdk-kms (1.16.0)
+ aws-sdk-kms (1.17.0)
aws-sdk-core (~> 3, >= 3.48.2)
aws-sigv4 (~> 1.1)
- aws-sdk-s3 (1.36.0)
+ aws-sdk-s3 (1.36.1)
aws-sdk-core (~> 3, >= 3.48.2)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.0)
--
cgit
From eed2c9dd44d7f58bbb54554c2271c465b67b827c Mon Sep 17 00:00:00 2001
From: Thibaut Girka
Date: Fri, 19 Apr 2019 20:14:32 +0200
Subject: Rename flavours/glitch/features/drawer to
flavours/glitch/features/compose
---
.../glitch/features/compose/account/index.js | 76 +++++++++
.../glitch/features/compose/header/index.js | 127 +++++++++++++++
.../flavours/glitch/features/compose/index.js | 166 +++++++++++++++++++
.../glitch/features/compose/results/index.js | 117 ++++++++++++++
.../glitch/features/compose/search/index.js | 152 ++++++++++++++++++
.../glitch/features/compose/search/popout/index.js | 109 +++++++++++++
.../glitch/features/drawer/account/index.js | 76 ---------
.../glitch/features/drawer/header/index.js | 127 ---------------
.../flavours/glitch/features/drawer/index.js | 175 ---------------------
.../glitch/features/drawer/results/index.js | 117 --------------
.../glitch/features/drawer/search/index.js | 152 ------------------
.../glitch/features/drawer/search/popout/index.js | 109 -------------
.../glitch/features/ui/components/columns_area.js | 4 +-
.../features/ui/components/onboarding_modal.js | 4 +-
.../flavours/glitch/features/ui/index.js | 6 +-
app/javascript/flavours/glitch/theme.yml | 2 +-
.../flavours/glitch/util/async-components.js | 4 +-
17 files changed, 757 insertions(+), 766 deletions(-)
create mode 100644 app/javascript/flavours/glitch/features/compose/account/index.js
create mode 100644 app/javascript/flavours/glitch/features/compose/header/index.js
create mode 100644 app/javascript/flavours/glitch/features/compose/index.js
create mode 100644 app/javascript/flavours/glitch/features/compose/results/index.js
create mode 100644 app/javascript/flavours/glitch/features/compose/search/index.js
create mode 100644 app/javascript/flavours/glitch/features/compose/search/popout/index.js
delete mode 100644 app/javascript/flavours/glitch/features/drawer/account/index.js
delete mode 100644 app/javascript/flavours/glitch/features/drawer/header/index.js
delete mode 100644 app/javascript/flavours/glitch/features/drawer/index.js
delete mode 100644 app/javascript/flavours/glitch/features/drawer/results/index.js
delete mode 100644 app/javascript/flavours/glitch/features/drawer/search/index.js
delete mode 100644 app/javascript/flavours/glitch/features/drawer/search/popout/index.js
diff --git a/app/javascript/flavours/glitch/features/compose/account/index.js b/app/javascript/flavours/glitch/features/compose/account/index.js
new file mode 100644
index 000000000..552848641
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/compose/account/index.js
@@ -0,0 +1,76 @@
+// Package imports.
+import React from 'react';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import {
+ FormattedMessage,
+ defineMessages,
+} from 'react-intl';
+
+// Components.
+import Avatar from 'flavours/glitch/components/avatar';
+import Permalink from 'flavours/glitch/components/permalink';
+
+// Utils.
+import { hiddenComponent } from 'flavours/glitch/util/react_helpers';
+import { profileLink } from 'flavours/glitch/util/backend_links';
+
+// Messages.
+const messages = defineMessages({
+ edit: {
+ defaultMessage: 'Edit profile',
+ id: 'navigation_bar.edit_profile',
+ },
+});
+
+// The component.
+export default function DrawerAccount ({ account }) {
+
+ // We need an account to render.
+ if (!account) {
+ return (
+
+ { profileLink !== undefined && (
+
+
+
+ )}
+
+ );
+ }
+
+ // The result.
+ return (
+
+
+ {account.get('acct')}
+
+
+
+ @{account.get('acct')}
+
+ { profileLink !== undefined && (
+
+ )}
+
+ );
+}
+
+// Props.
+DrawerAccount.propTypes = { account: ImmutablePropTypes.map };
diff --git a/app/javascript/flavours/glitch/features/compose/header/index.js b/app/javascript/flavours/glitch/features/compose/header/index.js
new file mode 100644
index 000000000..da5599732
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/compose/header/index.js
@@ -0,0 +1,127 @@
+// Package imports.
+import PropTypes from 'prop-types';
+import React from 'react';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import { defineMessages } from 'react-intl';
+import { Link } from 'react-router-dom';
+
+// Components.
+import Icon from 'flavours/glitch/components/icon';
+
+// Utils.
+import { conditionalRender } from 'flavours/glitch/util/react_helpers';
+import { signOutLink } from 'flavours/glitch/util/backend_links';
+
+// Messages.
+const messages = defineMessages({
+ community: {
+ defaultMessage: 'Local timeline',
+ id: 'navigation_bar.community_timeline',
+ },
+ home_timeline: {
+ defaultMessage: 'Home',
+ id: 'tabs_bar.home',
+ },
+ logout: {
+ defaultMessage: 'Logout',
+ id: 'navigation_bar.logout',
+ },
+ notifications: {
+ defaultMessage: 'Notifications',
+ id: 'tabs_bar.notifications',
+ },
+ public: {
+ defaultMessage: 'Federated timeline',
+ id: 'navigation_bar.public_timeline',
+ },
+ settings: {
+ defaultMessage: 'App settings',
+ id: 'navigation_bar.app_settings',
+ },
+ start: {
+ defaultMessage: 'Getting started',
+ id: 'getting_started.heading',
+ },
+});
+
+// The component.
+export default function DrawerHeader ({
+ columns,
+ unreadNotifications,
+ showNotificationsBadge,
+ intl,
+ onSettingsClick,
+}) {
+
+ // Only renders the component if the column isn't being shown.
+ const renderForColumn = conditionalRender.bind(null,
+ columnId => !columns || !columns.some(
+ column => column.get('id') === columnId
+ )
+ );
+
+ // The result.
+ return (
+
+
+ {renderForColumn('HOME', (
+
+ ))}
+ {renderForColumn('NOTIFICATIONS', (
+
+
+
+ { showNotificationsBadge && unreadNotifications > 0 &&
}
+
+
+ ))}
+ {renderForColumn('COMMUNITY', (
+
+ ))}
+ {renderForColumn('PUBLIC', (
+
+ ))}
+
+
+
+ );
+}
+
+// Props.
+DrawerHeader.propTypes = {
+ columns: ImmutablePropTypes.list,
+ unreadNotifications: PropTypes.number,
+ showNotificationsBadge: PropTypes.bool,
+ intl: PropTypes.object,
+ onSettingsClick: PropTypes.func,
+};
diff --git a/app/javascript/flavours/glitch/features/compose/index.js b/app/javascript/flavours/glitch/features/compose/index.js
new file mode 100644
index 000000000..cb261f9d6
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/compose/index.js
@@ -0,0 +1,166 @@
+// Package imports.
+import React from 'react';
+import { connect } from 'react-redux';
+import PropTypes from 'prop-types';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import { injectIntl, defineMessages } from 'react-intl';
+import classNames from 'classnames';
+
+// Actions.
+import { openModal } from 'flavours/glitch/actions/modal';
+import {
+ changeSearch,
+ clearSearch,
+ showSearch,
+ submitSearch,
+} from 'flavours/glitch/actions/search';
+import { cycleElefriendCompose } from 'flavours/glitch/actions/compose';
+
+// Components.
+import Composer from 'flavours/glitch/features/composer';
+import DrawerAccount from './account';
+import DrawerHeader from './header';
+import DrawerResults from './results';
+import DrawerSearch from './search';
+
+// Utils.
+import { me, mascot } from 'flavours/glitch/util/initial_state';
+import { wrap } from 'flavours/glitch/util/redux_helpers';
+
+// Messages.
+const messages = defineMessages({
+ compose: { id: 'navigation_bar.compose', defaultMessage: 'Compose new toot' },
+});
+
+// State mapping.
+const mapStateToProps = state => ({
+ account: state.getIn(['accounts', me]),
+ columns: state.getIn(['settings', 'columns']),
+ elefriend: state.getIn(['compose', 'elefriend']),
+ results: state.getIn(['search', 'results']),
+ searchHidden: state.getIn(['search', 'hidden']),
+ searchValue: state.getIn(['search', 'value']),
+ submitted: state.getIn(['search', 'submitted']),
+ unreadNotifications: state.getIn(['notifications', 'unread']),
+ showNotificationsBadge: state.getIn(['local_settings', 'notifications', 'tab_badge']),
+});
+
+// Dispatch mapping.
+const mapDispatchToProps = (dispatch, { intl }) => ({
+ onChange (value) {
+ dispatch(changeSearch(value));
+ },
+ onClear () {
+ dispatch(clearSearch());
+ },
+ onClickElefriend () {
+ dispatch(cycleElefriendCompose());
+ },
+ onShow () {
+ dispatch(showSearch());
+ },
+ onSubmit () {
+ dispatch(submitSearch());
+ },
+ onOpenSettings (e) {
+ e.preventDefault();
+ e.stopPropagation();
+ dispatch(openModal('SETTINGS', {}));
+ },
+});
+
+// The component.
+export default @connect(mapStateToProps, mapDispatchToProps)
+@injectIntl
+class Compose extends React.PureComponent {
+ static propTypes = {
+ intl: PropTypes.object.isRequired,
+ isSearchPage: PropTypes.bool,
+ multiColumn: PropTypes.bool,
+
+ // State props.
+ account: ImmutablePropTypes.map,
+ columns: ImmutablePropTypes.list,
+ results: ImmutablePropTypes.map,
+ elefriend: PropTypes.number,
+ searchHidden: PropTypes.bool,
+ searchValue: PropTypes.string,
+ submitted: PropTypes.bool,
+ unreadNotifications: PropTypes.number,
+ showNotificationsBadge: PropTypes.bool,
+
+ // Dispatch props.
+ onChange: PropTypes.func,
+ onClear: PropTypes.func,
+ onClickElefriend: PropTypes.func,
+ onShow: PropTypes.func,
+ onSubmit: PropTypes.func,
+ onOpenSettings: PropTypes.func,
+ };
+
+ // Rendering.
+ render () {
+ const {
+ account,
+ columns,
+ elefriend,
+ intl,
+ multiColumn,
+ onChange,
+ onClear,
+ onClickElefriend,
+ onOpenSettings,
+ onShow,
+ onSubmit,
+ results,
+ searchHidden,
+ searchValue,
+ submitted,
+ isSearchPage,
+ unreadNotifications,
+ showNotificationsBadge,
+ } = this.props;
+ const computedClass = classNames('drawer', `mbstobon-${elefriend}`);
+
+ // The result.
+ return (
+
+ {multiColumn && (
+
+ )}
+ {(multiColumn || isSearchPage) &&
}
+
+ {!isSearchPage &&
+
+
+ {multiColumn && (
+
+ {mascot ?
:
}
+
+ )}
+
}
+
+ {(multiColumn || isSearchPage) &&
+
}
+
+
+ );
+ }
+}
diff --git a/app/javascript/flavours/glitch/features/compose/results/index.js b/app/javascript/flavours/glitch/features/compose/results/index.js
new file mode 100644
index 000000000..4574c0e1e
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/compose/results/index.js
@@ -0,0 +1,117 @@
+// Package imports.
+import PropTypes from 'prop-types';
+import React from 'react';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import {
+ FormattedMessage,
+ defineMessages,
+} from 'react-intl';
+import spring from 'react-motion/lib/spring';
+import { Link } from 'react-router-dom';
+
+// Components.
+import Icon from 'flavours/glitch/components/icon';
+import AccountContainer from 'flavours/glitch/containers/account_container';
+import StatusContainer from 'flavours/glitch/containers/status_container';
+import Hashtag from 'flavours/glitch/components/hashtag';
+
+// Utils.
+import Motion from 'flavours/glitch/util/optional_motion';
+
+// Messages.
+const messages = defineMessages({
+ total: {
+ defaultMessage: '{count, number} {count, plural, one {result} other {results}}',
+ id: 'search_results.total',
+ },
+});
+
+// The component.
+export default function DrawerResults ({
+ results,
+ visible,
+}) {
+ const accounts = results ? results.get('accounts') : null;
+ const statuses = results ? results.get('statuses') : null;
+ const hashtags = results ? results.get('hashtags') : null;
+
+ // This gets the total number of items.
+ const count = [accounts, statuses, hashtags].reduce(function (size, item) {
+ if (item && item.size) {
+ return size + item.size;
+ }
+ return size;
+ }, 0);
+
+ // The result.
+ return (
+
+ {({ x }) => (
+
+
+ {accounts && accounts.size ? (
+
+
+
+ {accounts.map(
+ accountId => (
+
+ )
+ )}
+
+ ) : null}
+ {statuses && statuses.size ? (
+
+
+
+ {statuses.map(
+ statusId => (
+
+ )
+ )}
+
+ ) : null}
+ {hashtags && hashtags.size ? (
+
+
+
+ {hashtags.map(hashtag => )}
+
+ ) : null}
+
+ )}
+
+ );
+}
+
+// Props.
+DrawerResults.propTypes = {
+ results: ImmutablePropTypes.map,
+ visible: PropTypes.bool,
+};
diff --git a/app/javascript/flavours/glitch/features/compose/search/index.js b/app/javascript/flavours/glitch/features/compose/search/index.js
new file mode 100644
index 000000000..8cbb0906c
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/compose/search/index.js
@@ -0,0 +1,152 @@
+// Package imports.
+import classNames from 'classnames';
+import PropTypes from 'prop-types';
+import React from 'react';
+import {
+ FormattedMessage,
+ defineMessages,
+} from 'react-intl';
+import Overlay from 'react-overlays/lib/Overlay';
+
+// Components.
+import Icon from 'flavours/glitch/components/icon';
+import DrawerSearchPopout from './popout';
+
+// Utils.
+import { focusRoot } from 'flavours/glitch/util/dom_helpers';
+import {
+ assignHandlers,
+ hiddenComponent,
+} from 'flavours/glitch/util/react_helpers';
+
+// Messages.
+const messages = defineMessages({
+ placeholder: {
+ defaultMessage: 'Search',
+ id: 'search.placeholder',
+ },
+});
+
+// Handlers.
+const handlers = {
+
+ handleBlur () {
+ this.setState({ expanded: false });
+ },
+
+ handleChange ({ target: { value } }) {
+ const { onChange } = this.props;
+ if (onChange) {
+ onChange(value);
+ }
+ },
+
+ handleClear (e) {
+ const {
+ onClear,
+ submitted,
+ value,
+ } = this.props;
+ e.preventDefault(); // Prevents focus change ??
+ if (onClear && (submitted || value && value.length)) {
+ onClear();
+ }
+ },
+
+ handleFocus () {
+ const { onShow } = this.props;
+ this.setState({ expanded: true });
+ if (onShow) {
+ onShow();
+ }
+ },
+
+ handleKeyUp (e) {
+ const { onSubmit } = this.props;
+ switch (e.key) {
+ case 'Enter':
+ if (onSubmit) {
+ onSubmit();
+ }
+ break;
+ case 'Escape':
+ focusRoot();
+ }
+ },
+};
+
+// The component.
+export default class DrawerSearch extends React.PureComponent {
+
+ // Constructor.
+ constructor (props) {
+ super(props);
+ assignHandlers(this, handlers);
+ this.state = { expanded: false };
+ }
+
+ // Rendering.
+ render () {
+ const {
+ handleBlur,
+ handleChange,
+ handleClear,
+ handleFocus,
+ handleKeyUp,
+ } = this.handlers;
+ const {
+ intl,
+ submitted,
+ value,
+ } = this.props;
+ const { expanded } = this.state;
+ const active = value && value.length || submitted;
+ const computedClass = classNames('drawer--search', { active });
+
+ return (
+
+ );
+ }
+
+}
+
+// Props.
+DrawerSearch.propTypes = {
+ value: PropTypes.string,
+ submitted: PropTypes.bool,
+ onChange: PropTypes.func,
+ onSubmit: PropTypes.func,
+ onClear: PropTypes.func,
+ onShow: PropTypes.func,
+ intl: PropTypes.object,
+};
diff --git a/app/javascript/flavours/glitch/features/compose/search/popout/index.js b/app/javascript/flavours/glitch/features/compose/search/popout/index.js
new file mode 100644
index 000000000..fec090b64
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/compose/search/popout/index.js
@@ -0,0 +1,109 @@
+// Package imports.
+import PropTypes from 'prop-types';
+import React from 'react';
+import {
+ FormattedMessage,
+ defineMessages,
+} from 'react-intl';
+import spring from 'react-motion/lib/spring';
+
+// Utils.
+import Motion from 'flavours/glitch/util/optional_motion';
+import { searchEnabled } from 'flavours/glitch/util/initial_state';
+
+// Messages.
+const messages = defineMessages({
+ format: {
+ defaultMessage: 'Advanced search format',
+ id: 'search_popout.search_format',
+ },
+ hashtag: {
+ defaultMessage: 'hashtag',
+ id: 'search_popout.tips.hashtag',
+ },
+ status: {
+ defaultMessage: 'status',
+ id: 'search_popout.tips.status',
+ },
+ text: {
+ defaultMessage: 'Simple text returns matching display names, usernames and hashtags',
+ id: 'search_popout.tips.text',
+ },
+ full_text: {
+ defaultMessage: 'Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.',
+ id: 'search_popout.tips.full_text',
+ },
+ user: {
+ defaultMessage: 'user',
+ id: 'search_popout.tips.user',
+ },
+});
+
+// The spring used by our motion.
+const motionSpring = spring(1, { damping: 35, stiffness: 400 });
+
+// The component.
+export default function DrawerSearchPopout ({ style }) {
+
+ // The result.
+ return (
+
+
+ {({ opacity, scaleX, scaleY }) => (
+
+
+
+
+ #example
+ {' '}
+
+
+
+ @username@domain
+ {' '}
+
+
+
+ URL
+ {' '}
+
+
+
+ URL
+ {' '}
+
+
+
+ { searchEnabled ?
:
}
+
+ )}
+
+
+ );
+}
+
+// Props.
+DrawerSearchPopout.propTypes = { style: PropTypes.object };
diff --git a/app/javascript/flavours/glitch/features/drawer/account/index.js b/app/javascript/flavours/glitch/features/drawer/account/index.js
deleted file mode 100644
index 552848641..000000000
--- a/app/javascript/flavours/glitch/features/drawer/account/index.js
+++ /dev/null
@@ -1,76 +0,0 @@
-// Package imports.
-import React from 'react';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import {
- FormattedMessage,
- defineMessages,
-} from 'react-intl';
-
-// Components.
-import Avatar from 'flavours/glitch/components/avatar';
-import Permalink from 'flavours/glitch/components/permalink';
-
-// Utils.
-import { hiddenComponent } from 'flavours/glitch/util/react_helpers';
-import { profileLink } from 'flavours/glitch/util/backend_links';
-
-// Messages.
-const messages = defineMessages({
- edit: {
- defaultMessage: 'Edit profile',
- id: 'navigation_bar.edit_profile',
- },
-});
-
-// The component.
-export default function DrawerAccount ({ account }) {
-
- // We need an account to render.
- if (!account) {
- return (
-
- { profileLink !== undefined && (
-
-
-
- )}
-
- );
- }
-
- // The result.
- return (
-
-
- {account.get('acct')}
-
-
-
- @{account.get('acct')}
-
- { profileLink !== undefined && (
-
- )}
-
- );
-}
-
-// Props.
-DrawerAccount.propTypes = { account: ImmutablePropTypes.map };
diff --git a/app/javascript/flavours/glitch/features/drawer/header/index.js b/app/javascript/flavours/glitch/features/drawer/header/index.js
deleted file mode 100644
index da5599732..000000000
--- a/app/javascript/flavours/glitch/features/drawer/header/index.js
+++ /dev/null
@@ -1,127 +0,0 @@
-// Package imports.
-import PropTypes from 'prop-types';
-import React from 'react';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import { defineMessages } from 'react-intl';
-import { Link } from 'react-router-dom';
-
-// Components.
-import Icon from 'flavours/glitch/components/icon';
-
-// Utils.
-import { conditionalRender } from 'flavours/glitch/util/react_helpers';
-import { signOutLink } from 'flavours/glitch/util/backend_links';
-
-// Messages.
-const messages = defineMessages({
- community: {
- defaultMessage: 'Local timeline',
- id: 'navigation_bar.community_timeline',
- },
- home_timeline: {
- defaultMessage: 'Home',
- id: 'tabs_bar.home',
- },
- logout: {
- defaultMessage: 'Logout',
- id: 'navigation_bar.logout',
- },
- notifications: {
- defaultMessage: 'Notifications',
- id: 'tabs_bar.notifications',
- },
- public: {
- defaultMessage: 'Federated timeline',
- id: 'navigation_bar.public_timeline',
- },
- settings: {
- defaultMessage: 'App settings',
- id: 'navigation_bar.app_settings',
- },
- start: {
- defaultMessage: 'Getting started',
- id: 'getting_started.heading',
- },
-});
-
-// The component.
-export default function DrawerHeader ({
- columns,
- unreadNotifications,
- showNotificationsBadge,
- intl,
- onSettingsClick,
-}) {
-
- // Only renders the component if the column isn't being shown.
- const renderForColumn = conditionalRender.bind(null,
- columnId => !columns || !columns.some(
- column => column.get('id') === columnId
- )
- );
-
- // The result.
- return (
-
-
- {renderForColumn('HOME', (
-
- ))}
- {renderForColumn('NOTIFICATIONS', (
-
-
-
- { showNotificationsBadge && unreadNotifications > 0 &&
}
-
-
- ))}
- {renderForColumn('COMMUNITY', (
-
- ))}
- {renderForColumn('PUBLIC', (
-
- ))}
-
-
-
- );
-}
-
-// Props.
-DrawerHeader.propTypes = {
- columns: ImmutablePropTypes.list,
- unreadNotifications: PropTypes.number,
- showNotificationsBadge: PropTypes.bool,
- intl: PropTypes.object,
- onSettingsClick: PropTypes.func,
-};
diff --git a/app/javascript/flavours/glitch/features/drawer/index.js b/app/javascript/flavours/glitch/features/drawer/index.js
deleted file mode 100644
index c8121b8e5..000000000
--- a/app/javascript/flavours/glitch/features/drawer/index.js
+++ /dev/null
@@ -1,175 +0,0 @@
-// Package imports.
-import PropTypes from 'prop-types';
-import React from 'react';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import { defineMessages } from 'react-intl';
-import classNames from 'classnames';
-
-// Actions.
-import { openModal } from 'flavours/glitch/actions/modal';
-import {
- changeSearch,
- clearSearch,
- showSearch,
- submitSearch,
-} from 'flavours/glitch/actions/search';
-import { cycleElefriendCompose } from 'flavours/glitch/actions/compose';
-
-// Components.
-import Composer from 'flavours/glitch/features/composer';
-import DrawerAccount from './account';
-import DrawerHeader from './header';
-import DrawerResults from './results';
-import DrawerSearch from './search';
-
-// Utils.
-import { me, mascot } from 'flavours/glitch/util/initial_state';
-import { wrap } from 'flavours/glitch/util/redux_helpers';
-
-// Messages.
-const messages = defineMessages({
- compose: { id: 'navigation_bar.compose', defaultMessage: 'Compose new toot' },
-});
-
-// State mapping.
-const mapStateToProps = state => ({
- account: state.getIn(['accounts', me]),
- columns: state.getIn(['settings', 'columns']),
- elefriend: state.getIn(['compose', 'elefriend']),
- results: state.getIn(['search', 'results']),
- searchHidden: state.getIn(['search', 'hidden']),
- searchValue: state.getIn(['search', 'value']),
- submitted: state.getIn(['search', 'submitted']),
- unreadNotifications: state.getIn(['notifications', 'unread']),
- showNotificationsBadge: state.getIn(['local_settings', 'notifications', 'tab_badge']),
-});
-
-// Dispatch mapping.
-const mapDispatchToProps = (dispatch, { intl }) => ({
- onChange (value) {
- dispatch(changeSearch(value));
- },
- onClear () {
- dispatch(clearSearch());
- },
- onClickElefriend () {
- dispatch(cycleElefriendCompose());
- },
- onShow () {
- dispatch(showSearch());
- },
- onSubmit () {
- dispatch(submitSearch());
- },
- onOpenSettings (e) {
- e.preventDefault();
- e.stopPropagation();
- dispatch(openModal('SETTINGS', {}));
- },
-});
-
-// The component.
-class Drawer extends React.Component {
-
- // Constructor.
- constructor (props) {
- super(props);
- }
-
- // Rendering.
- render () {
- const {
- account,
- columns,
- elefriend,
- intl,
- multiColumn,
- onChange,
- onClear,
- onClickElefriend,
- onOpenSettings,
- onShow,
- onSubmit,
- results,
- searchHidden,
- searchValue,
- submitted,
- isSearchPage,
- unreadNotifications,
- showNotificationsBadge,
- } = this.props;
- const computedClass = classNames('drawer', `mbstobon-${elefriend}`);
-
- // The result.
- return (
-
- {multiColumn ? (
-
- ) : null}
- {(multiColumn || isSearchPage) &&
}
-
- {!isSearchPage &&
-
-
- {multiColumn && (
-
- {mascot ?
:
}
-
- )}
-
}
-
- {(multiColumn || isSearchPage) &&
-
}
-
-
- );
- }
-
-}
-
-// Props.
-Drawer.propTypes = {
- intl: PropTypes.object.isRequired,
- isSearchPage: PropTypes.bool,
- multiColumn: PropTypes.bool,
-
- // State props.
- account: ImmutablePropTypes.map,
- columns: ImmutablePropTypes.list,
- results: ImmutablePropTypes.map,
- elefriend: PropTypes.number,
- searchHidden: PropTypes.bool,
- searchValue: PropTypes.string,
- submitted: PropTypes.bool,
- unreadNotifications: PropTypes.number,
- showNotificationsBadge: PropTypes.bool,
-
- // Dispatch props.
- onChange: PropTypes.func,
- onClear: PropTypes.func,
- onClickElefriend: PropTypes.func,
- onShow: PropTypes.func,
- onSubmit: PropTypes.func,
- onOpenSettings: PropTypes.func,
-};
-
-// Connecting and export.
-export { Drawer as WrappedComponent };
-export default wrap(Drawer, mapStateToProps, mapDispatchToProps, true);
diff --git a/app/javascript/flavours/glitch/features/drawer/results/index.js b/app/javascript/flavours/glitch/features/drawer/results/index.js
deleted file mode 100644
index 4574c0e1e..000000000
--- a/app/javascript/flavours/glitch/features/drawer/results/index.js
+++ /dev/null
@@ -1,117 +0,0 @@
-// Package imports.
-import PropTypes from 'prop-types';
-import React from 'react';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import {
- FormattedMessage,
- defineMessages,
-} from 'react-intl';
-import spring from 'react-motion/lib/spring';
-import { Link } from 'react-router-dom';
-
-// Components.
-import Icon from 'flavours/glitch/components/icon';
-import AccountContainer from 'flavours/glitch/containers/account_container';
-import StatusContainer from 'flavours/glitch/containers/status_container';
-import Hashtag from 'flavours/glitch/components/hashtag';
-
-// Utils.
-import Motion from 'flavours/glitch/util/optional_motion';
-
-// Messages.
-const messages = defineMessages({
- total: {
- defaultMessage: '{count, number} {count, plural, one {result} other {results}}',
- id: 'search_results.total',
- },
-});
-
-// The component.
-export default function DrawerResults ({
- results,
- visible,
-}) {
- const accounts = results ? results.get('accounts') : null;
- const statuses = results ? results.get('statuses') : null;
- const hashtags = results ? results.get('hashtags') : null;
-
- // This gets the total number of items.
- const count = [accounts, statuses, hashtags].reduce(function (size, item) {
- if (item && item.size) {
- return size + item.size;
- }
- return size;
- }, 0);
-
- // The result.
- return (
-
- {({ x }) => (
-
-
- {accounts && accounts.size ? (
-
-
-
- {accounts.map(
- accountId => (
-
- )
- )}
-
- ) : null}
- {statuses && statuses.size ? (
-
-
-
- {statuses.map(
- statusId => (
-
- )
- )}
-
- ) : null}
- {hashtags && hashtags.size ? (
-
-
-
- {hashtags.map(hashtag => )}
-
- ) : null}
-
- )}
-
- );
-}
-
-// Props.
-DrawerResults.propTypes = {
- results: ImmutablePropTypes.map,
- visible: PropTypes.bool,
-};
diff --git a/app/javascript/flavours/glitch/features/drawer/search/index.js b/app/javascript/flavours/glitch/features/drawer/search/index.js
deleted file mode 100644
index 8cbb0906c..000000000
--- a/app/javascript/flavours/glitch/features/drawer/search/index.js
+++ /dev/null
@@ -1,152 +0,0 @@
-// Package imports.
-import classNames from 'classnames';
-import PropTypes from 'prop-types';
-import React from 'react';
-import {
- FormattedMessage,
- defineMessages,
-} from 'react-intl';
-import Overlay from 'react-overlays/lib/Overlay';
-
-// Components.
-import Icon from 'flavours/glitch/components/icon';
-import DrawerSearchPopout from './popout';
-
-// Utils.
-import { focusRoot } from 'flavours/glitch/util/dom_helpers';
-import {
- assignHandlers,
- hiddenComponent,
-} from 'flavours/glitch/util/react_helpers';
-
-// Messages.
-const messages = defineMessages({
- placeholder: {
- defaultMessage: 'Search',
- id: 'search.placeholder',
- },
-});
-
-// Handlers.
-const handlers = {
-
- handleBlur () {
- this.setState({ expanded: false });
- },
-
- handleChange ({ target: { value } }) {
- const { onChange } = this.props;
- if (onChange) {
- onChange(value);
- }
- },
-
- handleClear (e) {
- const {
- onClear,
- submitted,
- value,
- } = this.props;
- e.preventDefault(); // Prevents focus change ??
- if (onClear && (submitted || value && value.length)) {
- onClear();
- }
- },
-
- handleFocus () {
- const { onShow } = this.props;
- this.setState({ expanded: true });
- if (onShow) {
- onShow();
- }
- },
-
- handleKeyUp (e) {
- const { onSubmit } = this.props;
- switch (e.key) {
- case 'Enter':
- if (onSubmit) {
- onSubmit();
- }
- break;
- case 'Escape':
- focusRoot();
- }
- },
-};
-
-// The component.
-export default class DrawerSearch extends React.PureComponent {
-
- // Constructor.
- constructor (props) {
- super(props);
- assignHandlers(this, handlers);
- this.state = { expanded: false };
- }
-
- // Rendering.
- render () {
- const {
- handleBlur,
- handleChange,
- handleClear,
- handleFocus,
- handleKeyUp,
- } = this.handlers;
- const {
- intl,
- submitted,
- value,
- } = this.props;
- const { expanded } = this.state;
- const active = value && value.length || submitted;
- const computedClass = classNames('drawer--search', { active });
-
- return (
-
- );
- }
-
-}
-
-// Props.
-DrawerSearch.propTypes = {
- value: PropTypes.string,
- submitted: PropTypes.bool,
- onChange: PropTypes.func,
- onSubmit: PropTypes.func,
- onClear: PropTypes.func,
- onShow: PropTypes.func,
- intl: PropTypes.object,
-};
diff --git a/app/javascript/flavours/glitch/features/drawer/search/popout/index.js b/app/javascript/flavours/glitch/features/drawer/search/popout/index.js
deleted file mode 100644
index fec090b64..000000000
--- a/app/javascript/flavours/glitch/features/drawer/search/popout/index.js
+++ /dev/null
@@ -1,109 +0,0 @@
-// Package imports.
-import PropTypes from 'prop-types';
-import React from 'react';
-import {
- FormattedMessage,
- defineMessages,
-} from 'react-intl';
-import spring from 'react-motion/lib/spring';
-
-// Utils.
-import Motion from 'flavours/glitch/util/optional_motion';
-import { searchEnabled } from 'flavours/glitch/util/initial_state';
-
-// Messages.
-const messages = defineMessages({
- format: {
- defaultMessage: 'Advanced search format',
- id: 'search_popout.search_format',
- },
- hashtag: {
- defaultMessage: 'hashtag',
- id: 'search_popout.tips.hashtag',
- },
- status: {
- defaultMessage: 'status',
- id: 'search_popout.tips.status',
- },
- text: {
- defaultMessage: 'Simple text returns matching display names, usernames and hashtags',
- id: 'search_popout.tips.text',
- },
- full_text: {
- defaultMessage: 'Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.',
- id: 'search_popout.tips.full_text',
- },
- user: {
- defaultMessage: 'user',
- id: 'search_popout.tips.user',
- },
-});
-
-// The spring used by our motion.
-const motionSpring = spring(1, { damping: 35, stiffness: 400 });
-
-// The component.
-export default function DrawerSearchPopout ({ style }) {
-
- // The result.
- return (
-
-
- {({ opacity, scaleX, scaleY }) => (
-
-
-
-
- #example
- {' '}
-
-
-
- @username@domain
- {' '}
-
-
-
- URL
- {' '}
-
-
-
- URL
- {' '}
-
-
-
- { searchEnabled ?
:
}
-
- )}
-
-
- );
-}
-
-// Props.
-DrawerSearchPopout.propTypes = { style: PropTypes.object };
diff --git a/app/javascript/flavours/glitch/features/ui/components/columns_area.js b/app/javascript/flavours/glitch/features/ui/components/columns_area.js
index 83b797305..0fe580b9b 100644
--- a/app/javascript/flavours/glitch/features/ui/components/columns_area.js
+++ b/app/javascript/flavours/glitch/features/ui/components/columns_area.js
@@ -12,13 +12,13 @@ import BundleContainer from '../containers/bundle_container';
import ColumnLoading from './column_loading';
import DrawerLoading from './drawer_loading';
import BundleColumnError from './bundle_column_error';
-import { Drawer, Notifications, HomeTimeline, CommunityTimeline, PublicTimeline, HashtagTimeline, DirectTimeline, FavouritedStatuses, BookmarkedStatuses, ListTimeline } from 'flavours/glitch/util/async-components';
+import { Compose, Notifications, HomeTimeline, CommunityTimeline, PublicTimeline, HashtagTimeline, DirectTimeline, FavouritedStatuses, BookmarkedStatuses, ListTimeline } from 'flavours/glitch/util/async-components';
import detectPassiveEvents from 'detect-passive-events';
import { scrollRight } from 'flavours/glitch/util/scroll';
const componentMap = {
- 'COMPOSE': Drawer,
+ 'COMPOSE': Compose,
'HOME': HomeTimeline,
'NOTIFICATIONS': Notifications,
'PUBLIC': PublicTimeline,
diff --git a/app/javascript/flavours/glitch/features/ui/components/onboarding_modal.js b/app/javascript/flavours/glitch/features/ui/components/onboarding_modal.js
index 16355a446..e9c634a50 100644
--- a/app/javascript/flavours/glitch/features/ui/components/onboarding_modal.js
+++ b/app/javascript/flavours/glitch/features/ui/components/onboarding_modal.js
@@ -7,8 +7,8 @@ import ReactSwipeableViews from 'react-swipeable-views';
import classNames from 'classnames';
import Permalink from 'flavours/glitch/components/permalink';
import { WrappedComponent as RawComposer } from 'flavours/glitch/features/composer';
-import DrawerAccount from 'flavours/glitch/features/drawer/account';
-import DrawerSearch from 'flavours/glitch/features/drawer/search';
+import DrawerAccount from 'flavours/glitch/features/compose/account';
+import DrawerSearch from 'flavours/glitch/features/compose/search';
import ColumnHeader from './column_header';
import { me } from 'flavours/glitch/util/initial_state';
diff --git a/app/javascript/flavours/glitch/features/ui/index.js b/app/javascript/flavours/glitch/features/ui/index.js
index 348125c97..dd527d528 100644
--- a/app/javascript/flavours/glitch/features/ui/index.js
+++ b/app/javascript/flavours/glitch/features/ui/index.js
@@ -19,7 +19,7 @@ import ColumnsAreaContainer from './containers/columns_area_container';
import classNames from 'classnames';
import Favico from 'favico.js';
import {
- Drawer,
+ Compose,
Status,
GettingStarted,
KeyboardShortcuts,
@@ -488,9 +488,9 @@ export default class UI extends React.Component {
-
+
-
+
diff --git a/app/javascript/flavours/glitch/theme.yml b/app/javascript/flavours/glitch/theme.yml
index 587cc0f1e..06e26ade2 100644
--- a/app/javascript/flavours/glitch/theme.yml
+++ b/app/javascript/flavours/glitch/theme.yml
@@ -11,7 +11,7 @@ pack:
home:
filename: packs/home.js
preload:
- - flavours/glitch/async/drawer
+ - flavours/glitch/async/compose
- flavours/glitch/async/getting_started
- flavours/glitch/async/home_timeline
- flavours/glitch/async/notifications
diff --git a/app/javascript/flavours/glitch/util/async-components.js b/app/javascript/flavours/glitch/util/async-components.js
index e96af845f..094952204 100644
--- a/app/javascript/flavours/glitch/util/async-components.js
+++ b/app/javascript/flavours/glitch/util/async-components.js
@@ -2,8 +2,8 @@ export function EmojiPicker () {
return import(/* webpackChunkName: "flavours/glitch/async/emoji_picker" */'flavours/glitch/util/emoji/emoji_picker');
}
-export function Drawer () {
- return import(/* webpackChunkName: "flavours/glitch/async/drawer" */'flavours/glitch/features/drawer');
+export function Compose () {
+ return import(/* webpackChunkName: "flavours/glitch/async/compose" */'flavours/glitch/features/compose');
}
export function Notifications () {
--
cgit
From c19b983986653c653f8cbc61b9a945bab993244a Mon Sep 17 00:00:00 2001
From: Thibaut Girka
Date: Fri, 19 Apr 2019 20:57:43 +0200
Subject: Refactor a bit DrawerSearch to make it closer to upstream
---
.../glitch/features/compose/search/index.js | 108 ++++++++-------------
1 file changed, 41 insertions(+), 67 deletions(-)
diff --git a/app/javascript/flavours/glitch/features/compose/search/index.js b/app/javascript/flavours/glitch/features/compose/search/index.js
index 8cbb0906c..8f9e19b7b 100644
--- a/app/javascript/flavours/glitch/features/compose/search/index.js
+++ b/app/javascript/flavours/glitch/features/compose/search/index.js
@@ -2,8 +2,9 @@
import classNames from 'classnames';
import PropTypes from 'prop-types';
import React from 'react';
+import { connect } from 'react-redux';
import {
- FormattedMessage,
+ injectIntl,
defineMessages,
} from 'react-intl';
import Overlay from 'react-overlays/lib/Overlay';
@@ -14,10 +15,6 @@ import DrawerSearchPopout from './popout';
// Utils.
import { focusRoot } from 'flavours/glitch/util/dom_helpers';
-import {
- assignHandlers,
- hiddenComponent,
-} from 'flavours/glitch/util/react_helpers';
// Messages.
const messages = defineMessages({
@@ -27,21 +24,32 @@ const messages = defineMessages({
},
});
-// Handlers.
-const handlers = {
+// The component.
+export default @injectIntl
+class DrawerSearch extends React.PureComponent {
- handleBlur () {
- this.setState({ expanded: false });
- },
+ static propTypes = {
+ value: PropTypes.string.isRequired,
+ submitted: PropTypes.bool,
+ onChange: PropTypes.func.isRequired,
+ onSubmit: PropTypes.func.isRequired,
+ onClear: PropTypes.func.isRequired,
+ onShow: PropTypes.func.isRequired,
+ intl: PropTypes.object.isRequired,
+ };
+
+ state = {
+ expanded: false,
+ };
- handleChange ({ target: { value } }) {
+ handleChange = (e) => {
const { onChange } = this.props;
if (onChange) {
- onChange(value);
+ onChange(e.target.value);
}
- },
+ }
- handleClear (e) {
+ handleClear = (e) => {
const {
onClear,
submitted,
@@ -51,17 +59,21 @@ const handlers = {
if (onClear && (submitted || value && value.length)) {
onClear();
}
- },
+ }
+
+ handleBlur () {
+ this.setState({ expanded: false });
+ }
- handleFocus () {
+ handleFocus = () => {
const { onShow } = this.props;
this.setState({ expanded: true });
if (onShow) {
onShow();
}
- },
+ }
- handleKeyUp (e) {
+ handleKeyUp = (e) => {
const { onSubmit } = this.props;
switch (e.key) {
case 'Enter':
@@ -72,81 +84,43 @@ const handlers = {
case 'Escape':
focusRoot();
}
- },
-};
-
-// The component.
-export default class DrawerSearch extends React.PureComponent {
-
- // Constructor.
- constructor (props) {
- super(props);
- assignHandlers(this, handlers);
- this.state = { expanded: false };
}
- // Rendering.
render () {
- const {
- handleBlur,
- handleChange,
- handleClear,
- handleFocus,
- handleKeyUp,
- } = this.handlers;
- const {
- intl,
- submitted,
- value,
- } = this.props;
+ const { intl, value, submitted } = this.props;
const { expanded } = this.state;
- const active = value && value.length || submitted;
+ const active = value.length > 0 || submitted;
const computedClass = classNames('drawer--search', { active });
return (
-
-
-
+ {intl.formatMessage(messages.placeholder)}
-
+
+
+
);
}
}
-
-// Props.
-DrawerSearch.propTypes = {
- value: PropTypes.string,
- submitted: PropTypes.bool,
- onChange: PropTypes.func,
- onSubmit: PropTypes.func,
- onClear: PropTypes.func,
- onShow: PropTypes.func,
- intl: PropTypes.object,
-};
--
cgit
From c92ab35b1925647ef8f7f3d6df6a667becc0271c Mon Sep 17 00:00:00 2001
From: Thibaut Girka
Date: Fri, 19 Apr 2019 21:05:18 +0200
Subject: Inline DrawerSearchPopout in DrawerSearch
---
.../glitch/features/compose/search/index.js | 48 +++++++--
.../glitch/features/compose/search/popout/index.js | 109 ---------------------
2 files changed, 40 insertions(+), 117 deletions(-)
delete mode 100644 app/javascript/flavours/glitch/features/compose/search/popout/index.js
diff --git a/app/javascript/flavours/glitch/features/compose/search/index.js b/app/javascript/flavours/glitch/features/compose/search/index.js
index 8f9e19b7b..06b99dcf0 100644
--- a/app/javascript/flavours/glitch/features/compose/search/index.js
+++ b/app/javascript/flavours/glitch/features/compose/search/index.js
@@ -3,27 +3,59 @@ import classNames from 'classnames';
import PropTypes from 'prop-types';
import React from 'react';
import { connect } from 'react-redux';
+import spring from 'react-motion/lib/spring';
import {
injectIntl,
+ FormattedMessage,
defineMessages,
} from 'react-intl';
import Overlay from 'react-overlays/lib/Overlay';
// Components.
import Icon from 'flavours/glitch/components/icon';
-import DrawerSearchPopout from './popout';
// Utils.
import { focusRoot } from 'flavours/glitch/util/dom_helpers';
+import { searchEnabled } from 'flavours/glitch/util/initial_state';
+import Motion from 'flavours/glitch/util/optional_motion';
-// Messages.
const messages = defineMessages({
- placeholder: {
- defaultMessage: 'Search',
- id: 'search.placeholder',
- },
+ placeholder: { id: 'search.placeholder', defaultMessage: 'Search' },
});
+class SearchPopout extends React.PureComponent {
+
+ static propTypes = {
+ style: PropTypes.object,
+ };
+
+ render () {
+ const { style } = this.props;
+ const extraInformation = searchEnabled ? : ;
+ return (
+
+
+ {({ opacity, scaleX, scaleY }) => (
+
+
+
+
+ #example
+ @username@domain
+ URL
+ URL
+
+
+ {extraInformation}
+
+ )}
+
+
+ );
+ }
+
+}
+
// The component.
export default @injectIntl
class DrawerSearch extends React.PureComponent {
@@ -61,7 +93,7 @@ class DrawerSearch extends React.PureComponent {
}
}
- handleBlur () {
+ handleBlur = () => {
this.setState({ expanded: false });
}
@@ -117,7 +149,7 @@ class DrawerSearch extends React.PureComponent {
-
+
);
diff --git a/app/javascript/flavours/glitch/features/compose/search/popout/index.js b/app/javascript/flavours/glitch/features/compose/search/popout/index.js
deleted file mode 100644
index fec090b64..000000000
--- a/app/javascript/flavours/glitch/features/compose/search/popout/index.js
+++ /dev/null
@@ -1,109 +0,0 @@
-// Package imports.
-import PropTypes from 'prop-types';
-import React from 'react';
-import {
- FormattedMessage,
- defineMessages,
-} from 'react-intl';
-import spring from 'react-motion/lib/spring';
-
-// Utils.
-import Motion from 'flavours/glitch/util/optional_motion';
-import { searchEnabled } from 'flavours/glitch/util/initial_state';
-
-// Messages.
-const messages = defineMessages({
- format: {
- defaultMessage: 'Advanced search format',
- id: 'search_popout.search_format',
- },
- hashtag: {
- defaultMessage: 'hashtag',
- id: 'search_popout.tips.hashtag',
- },
- status: {
- defaultMessage: 'status',
- id: 'search_popout.tips.status',
- },
- text: {
- defaultMessage: 'Simple text returns matching display names, usernames and hashtags',
- id: 'search_popout.tips.text',
- },
- full_text: {
- defaultMessage: 'Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.',
- id: 'search_popout.tips.full_text',
- },
- user: {
- defaultMessage: 'user',
- id: 'search_popout.tips.user',
- },
-});
-
-// The spring used by our motion.
-const motionSpring = spring(1, { damping: 35, stiffness: 400 });
-
-// The component.
-export default function DrawerSearchPopout ({ style }) {
-
- // The result.
- return (
-
-
- {({ opacity, scaleX, scaleY }) => (
-
-
-
-
- #example
- {' '}
-
-
-
- @username@domain
- {' '}
-
-
-
- URL
- {' '}
-
-
-
- URL
- {' '}
-
-
-
- { searchEnabled ?
:
}
-
- )}
-
-
- );
-}
-
-// Props.
-DrawerSearchPopout.propTypes = { style: PropTypes.object };
--
cgit
From ab3e8fc542f53ce9c7a8d1b39ae57bdb60667dac Mon Sep 17 00:00:00 2001
From: Thibaut Girka
Date: Sat, 20 Apr 2019 17:50:12 +0200
Subject: Move DrawerSearch to Search + SearchContainer
---
.../glitch/features/compose/components/search.js | 158 +++++++++++++++++++++
.../compose/containers/search_container.js | 35 +++++
.../flavours/glitch/features/compose/index.js | 41 +-----
.../glitch/features/compose/search/index.js | 158 ---------------------
.../features/ui/components/onboarding_modal.js | 12 +-
5 files changed, 205 insertions(+), 199 deletions(-)
create mode 100644 app/javascript/flavours/glitch/features/compose/components/search.js
create mode 100644 app/javascript/flavours/glitch/features/compose/containers/search_container.js
delete mode 100644 app/javascript/flavours/glitch/features/compose/search/index.js
diff --git a/app/javascript/flavours/glitch/features/compose/components/search.js b/app/javascript/flavours/glitch/features/compose/components/search.js
new file mode 100644
index 000000000..5fed1567a
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/compose/components/search.js
@@ -0,0 +1,158 @@
+// Package imports.
+import classNames from 'classnames';
+import PropTypes from 'prop-types';
+import React from 'react';
+import { connect } from 'react-redux';
+import spring from 'react-motion/lib/spring';
+import {
+ injectIntl,
+ FormattedMessage,
+ defineMessages,
+} from 'react-intl';
+import Overlay from 'react-overlays/lib/Overlay';
+
+// Components.
+import Icon from 'flavours/glitch/components/icon';
+
+// Utils.
+import { focusRoot } from 'flavours/glitch/util/dom_helpers';
+import { searchEnabled } from 'flavours/glitch/util/initial_state';
+import Motion from 'flavours/glitch/util/optional_motion';
+
+const messages = defineMessages({
+ placeholder: { id: 'search.placeholder', defaultMessage: 'Search' },
+});
+
+class SearchPopout extends React.PureComponent {
+
+ static propTypes = {
+ style: PropTypes.object,
+ };
+
+ render () {
+ const { style } = this.props;
+ const extraInformation = searchEnabled ? : ;
+ return (
+
+
+ {({ opacity, scaleX, scaleY }) => (
+
+
+
+
+ #example
+ @username@domain
+ URL
+ URL
+
+
+ {extraInformation}
+
+ )}
+
+
+ );
+ }
+
+}
+
+// The component.
+export default @injectIntl
+class Search extends React.PureComponent {
+
+ static propTypes = {
+ value: PropTypes.string.isRequired,
+ submitted: PropTypes.bool,
+ onChange: PropTypes.func.isRequired,
+ onSubmit: PropTypes.func.isRequired,
+ onClear: PropTypes.func.isRequired,
+ onShow: PropTypes.func.isRequired,
+ intl: PropTypes.object.isRequired,
+ };
+
+ state = {
+ expanded: false,
+ };
+
+ handleChange = (e) => {
+ const { onChange } = this.props;
+ if (onChange) {
+ onChange(e.target.value);
+ }
+ }
+
+ handleClear = (e) => {
+ const {
+ onClear,
+ submitted,
+ value,
+ } = this.props;
+ e.preventDefault(); // Prevents focus change ??
+ if (onClear && (submitted || value && value.length)) {
+ onClear();
+ }
+ }
+
+ handleBlur = () => {
+ this.setState({ expanded: false });
+ }
+
+ handleFocus = () => {
+ const { onShow } = this.props;
+ this.setState({ expanded: true });
+ if (onShow) {
+ onShow();
+ }
+ }
+
+ handleKeyUp = (e) => {
+ const { onSubmit } = this.props;
+ switch (e.key) {
+ case 'Enter':
+ if (onSubmit) {
+ onSubmit();
+ }
+ break;
+ case 'Escape':
+ focusRoot();
+ }
+ }
+
+ render () {
+ const { intl, value, submitted } = this.props;
+ const { expanded } = this.state;
+ const active = value.length > 0 || submitted;
+ const computedClass = classNames('drawer--search', { active });
+
+ return (
+
+
+ {intl.formatMessage(messages.placeholder)}
+
+
+
+
+
+
+
+
+
+
+ );
+ }
+
+}
diff --git a/app/javascript/flavours/glitch/features/compose/containers/search_container.js b/app/javascript/flavours/glitch/features/compose/containers/search_container.js
new file mode 100644
index 000000000..8f4bfcf08
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/compose/containers/search_container.js
@@ -0,0 +1,35 @@
+import { connect } from 'react-redux';
+import {
+ changeSearch,
+ clearSearch,
+ submitSearch,
+ showSearch,
+} from 'flavours/glitch/actions/search';
+import Search from '../components/search';
+
+const mapStateToProps = state => ({
+ value: state.getIn(['search', 'value']),
+ submitted: state.getIn(['search', 'submitted']),
+});
+
+const mapDispatchToProps = dispatch => ({
+
+ onChange (value) {
+ dispatch(changeSearch(value));
+ },
+
+ onClear () {
+ dispatch(clearSearch());
+ },
+
+ onSubmit () {
+ dispatch(submitSearch());
+ },
+
+ onShow () {
+ dispatch(showSearch());
+ },
+
+});
+
+export default connect(mapStateToProps, mapDispatchToProps)(Search);
diff --git a/app/javascript/flavours/glitch/features/compose/index.js b/app/javascript/flavours/glitch/features/compose/index.js
index cb261f9d6..83c5d82b0 100644
--- a/app/javascript/flavours/glitch/features/compose/index.js
+++ b/app/javascript/flavours/glitch/features/compose/index.js
@@ -8,12 +8,6 @@ import classNames from 'classnames';
// Actions.
import { openModal } from 'flavours/glitch/actions/modal';
-import {
- changeSearch,
- clearSearch,
- showSearch,
- submitSearch,
-} from 'flavours/glitch/actions/search';
import { cycleElefriendCompose } from 'flavours/glitch/actions/compose';
// Components.
@@ -21,7 +15,7 @@ import Composer from 'flavours/glitch/features/composer';
import DrawerAccount from './account';
import DrawerHeader from './header';
import DrawerResults from './results';
-import DrawerSearch from './search';
+import SearchContainer from './containers/search_container';
// Utils.
import { me, mascot } from 'flavours/glitch/util/initial_state';
@@ -39,7 +33,6 @@ const mapStateToProps = state => ({
elefriend: state.getIn(['compose', 'elefriend']),
results: state.getIn(['search', 'results']),
searchHidden: state.getIn(['search', 'hidden']),
- searchValue: state.getIn(['search', 'value']),
submitted: state.getIn(['search', 'submitted']),
unreadNotifications: state.getIn(['notifications', 'unread']),
showNotificationsBadge: state.getIn(['local_settings', 'notifications', 'tab_badge']),
@@ -47,21 +40,9 @@ const mapStateToProps = state => ({
// Dispatch mapping.
const mapDispatchToProps = (dispatch, { intl }) => ({
- onChange (value) {
- dispatch(changeSearch(value));
- },
- onClear () {
- dispatch(clearSearch());
- },
onClickElefriend () {
dispatch(cycleElefriendCompose());
},
- onShow () {
- dispatch(showSearch());
- },
- onSubmit () {
- dispatch(submitSearch());
- },
onOpenSettings (e) {
e.preventDefault();
e.stopPropagation();
@@ -84,17 +65,12 @@ class Compose extends React.PureComponent {
results: ImmutablePropTypes.map,
elefriend: PropTypes.number,
searchHidden: PropTypes.bool,
- searchValue: PropTypes.string,
submitted: PropTypes.bool,
unreadNotifications: PropTypes.number,
showNotificationsBadge: PropTypes.bool,
// Dispatch props.
- onChange: PropTypes.func,
- onClear: PropTypes.func,
onClickElefriend: PropTypes.func,
- onShow: PropTypes.func,
- onSubmit: PropTypes.func,
onOpenSettings: PropTypes.func,
};
@@ -106,15 +82,10 @@ class Compose extends React.PureComponent {
elefriend,
intl,
multiColumn,
- onChange,
- onClear,
onClickElefriend,
onOpenSettings,
- onShow,
- onSubmit,
results,
searchHidden,
- searchValue,
submitted,
isSearchPage,
unreadNotifications,
@@ -134,15 +105,7 @@ class Compose extends React.PureComponent {
onSettingsClick={onOpenSettings}
/>
)}
- {(multiColumn || isSearchPage) && }
+ {(multiColumn || isSearchPage) && }
{!isSearchPage &&
diff --git a/app/javascript/flavours/glitch/features/compose/search/index.js b/app/javascript/flavours/glitch/features/compose/search/index.js
deleted file mode 100644
index 06b99dcf0..000000000
--- a/app/javascript/flavours/glitch/features/compose/search/index.js
+++ /dev/null
@@ -1,158 +0,0 @@
-// Package imports.
-import classNames from 'classnames';
-import PropTypes from 'prop-types';
-import React from 'react';
-import { connect } from 'react-redux';
-import spring from 'react-motion/lib/spring';
-import {
- injectIntl,
- FormattedMessage,
- defineMessages,
-} from 'react-intl';
-import Overlay from 'react-overlays/lib/Overlay';
-
-// Components.
-import Icon from 'flavours/glitch/components/icon';
-
-// Utils.
-import { focusRoot } from 'flavours/glitch/util/dom_helpers';
-import { searchEnabled } from 'flavours/glitch/util/initial_state';
-import Motion from 'flavours/glitch/util/optional_motion';
-
-const messages = defineMessages({
- placeholder: { id: 'search.placeholder', defaultMessage: 'Search' },
-});
-
-class SearchPopout extends React.PureComponent {
-
- static propTypes = {
- style: PropTypes.object,
- };
-
- render () {
- const { style } = this.props;
- const extraInformation = searchEnabled ?
:
;
- return (
-
-
- {({ opacity, scaleX, scaleY }) => (
-
-
-
-
- #example
- @username@domain
- URL
- URL
-
-
- {extraInformation}
-
- )}
-
-
- );
- }
-
-}
-
-// The component.
-export default @injectIntl
-class DrawerSearch extends React.PureComponent {
-
- static propTypes = {
- value: PropTypes.string.isRequired,
- submitted: PropTypes.bool,
- onChange: PropTypes.func.isRequired,
- onSubmit: PropTypes.func.isRequired,
- onClear: PropTypes.func.isRequired,
- onShow: PropTypes.func.isRequired,
- intl: PropTypes.object.isRequired,
- };
-
- state = {
- expanded: false,
- };
-
- handleChange = (e) => {
- const { onChange } = this.props;
- if (onChange) {
- onChange(e.target.value);
- }
- }
-
- handleClear = (e) => {
- const {
- onClear,
- submitted,
- value,
- } = this.props;
- e.preventDefault(); // Prevents focus change ??
- if (onClear && (submitted || value && value.length)) {
- onClear();
- }
- }
-
- handleBlur = () => {
- this.setState({ expanded: false });
- }
-
- handleFocus = () => {
- const { onShow } = this.props;
- this.setState({ expanded: true });
- if (onShow) {
- onShow();
- }
- }
-
- handleKeyUp = (e) => {
- const { onSubmit } = this.props;
- switch (e.key) {
- case 'Enter':
- if (onSubmit) {
- onSubmit();
- }
- break;
- case 'Escape':
- focusRoot();
- }
- }
-
- render () {
- const { intl, value, submitted } = this.props;
- const { expanded } = this.state;
- const active = value.length > 0 || submitted;
- const computedClass = classNames('drawer--search', { active });
-
- return (
-
-
- {intl.formatMessage(messages.placeholder)}
-
-
-
-
-
-
-
-
-
-
- );
- }
-
-}
diff --git a/app/javascript/flavours/glitch/features/ui/components/onboarding_modal.js b/app/javascript/flavours/glitch/features/ui/components/onboarding_modal.js
index e9c634a50..38e9b63ec 100644
--- a/app/javascript/flavours/glitch/features/ui/components/onboarding_modal.js
+++ b/app/javascript/flavours/glitch/features/ui/components/onboarding_modal.js
@@ -8,10 +8,12 @@ import classNames from 'classnames';
import Permalink from 'flavours/glitch/components/permalink';
import { WrappedComponent as RawComposer } from 'flavours/glitch/features/composer';
import DrawerAccount from 'flavours/glitch/features/compose/account';
-import DrawerSearch from 'flavours/glitch/features/compose/search';
+import Search from 'flavours/glitch/features/compose/components/search';
import ColumnHeader from './column_header';
import { me } from 'flavours/glitch/util/initial_state';
+const noop = () => { };
+
const messages = defineMessages({
home_title: { id: 'column.home', defaultMessage: 'Home' },
notifications_title: { id: 'column.notifications', defaultMessage: 'Notifications' },
@@ -63,7 +65,13 @@ PageTwo.propTypes = {
const PageThree = ({ intl, myAccount }) => (
-
+
--
cgit
From 67771e6d65ea209dcb9156a7495b7ef64c1a762b Mon Sep 17 00:00:00 2001
From: Thibaut Girka
Date: Sat, 20 Apr 2019 18:21:11 +0200
Subject: Rework DrawerResults to make them closer to upstream
---
.../flavours/glitch/features/compose/index.js | 20 ++-
.../glitch/features/compose/results/index.js | 153 ++++++++-------------
.../flavours/glitch/styles/components/drawer.scss | 6 -
3 files changed, 70 insertions(+), 109 deletions(-)
diff --git a/app/javascript/flavours/glitch/features/compose/index.js b/app/javascript/flavours/glitch/features/compose/index.js
index 83c5d82b0..6dc786c8b 100644
--- a/app/javascript/flavours/glitch/features/compose/index.js
+++ b/app/javascript/flavours/glitch/features/compose/index.js
@@ -16,10 +16,11 @@ import DrawerAccount from './account';
import DrawerHeader from './header';
import DrawerResults from './results';
import SearchContainer from './containers/search_container';
+import spring from 'react-motion/lib/spring';
// Utils.
import { me, mascot } from 'flavours/glitch/util/initial_state';
-import { wrap } from 'flavours/glitch/util/redux_helpers';
+import Motion from 'flavours/glitch/util/optional_motion';
// Messages.
const messages = defineMessages({
@@ -27,13 +28,14 @@ const messages = defineMessages({
});
// State mapping.
-const mapStateToProps = state => ({
+const mapStateToProps = (state, ownProps) => ({
account: state.getIn(['accounts', me]),
columns: state.getIn(['settings', 'columns']),
elefriend: state.getIn(['compose', 'elefriend']),
results: state.getIn(['search', 'results']),
searchHidden: state.getIn(['search', 'hidden']),
submitted: state.getIn(['search', 'submitted']),
+ showSearch: ownProps.multiColumn ? state.getIn(['search', 'submitted']) && !state.getIn(['search', 'hidden']) : ownProps.isSearchPage,
unreadNotifications: state.getIn(['notifications', 'unread']),
showNotificationsBadge: state.getIn(['local_settings', 'notifications', 'tab_badge']),
});
@@ -58,6 +60,7 @@ class Compose extends React.PureComponent {
intl: PropTypes.object.isRequired,
isSearchPage: PropTypes.bool,
multiColumn: PropTypes.bool,
+ showSearch: PropTypes.bool,
// State props.
account: ImmutablePropTypes.map,
@@ -90,6 +93,7 @@ class Compose extends React.PureComponent {
isSearchPage,
unreadNotifications,
showNotificationsBadge,
+ showSearch,
} = this.props;
const computedClass = classNames('drawer', `mbstobon-${elefriend}`);
@@ -117,11 +121,13 @@ class Compose extends React.PureComponent {
)}
}
- {(multiColumn || isSearchPage) &&
-
}
+
+ {({ x }) => (
+
+
+
+ )}
+
);
diff --git a/app/javascript/flavours/glitch/features/compose/results/index.js b/app/javascript/flavours/glitch/features/compose/results/index.js
index 4574c0e1e..162d14913 100644
--- a/app/javascript/flavours/glitch/features/compose/results/index.js
+++ b/app/javascript/flavours/glitch/features/compose/results/index.js
@@ -1,12 +1,9 @@
// Package imports.
import PropTypes from 'prop-types';
import React from 'react';
+import ImmutablePureComponent from 'react-immutable-pure-component';
import ImmutablePropTypes from 'react-immutable-proptypes';
-import {
- FormattedMessage,
- defineMessages,
-} from 'react-intl';
-import spring from 'react-motion/lib/spring';
+import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
import { Link } from 'react-router-dom';
// Components.
@@ -15,103 +12,67 @@ import AccountContainer from 'flavours/glitch/containers/account_container';
import StatusContainer from 'flavours/glitch/containers/status_container';
import Hashtag from 'flavours/glitch/components/hashtag';
-// Utils.
-import Motion from 'flavours/glitch/util/optional_motion';
-
// Messages.
-const messages = defineMessages({
- total: {
- defaultMessage: '{count, number} {count, plural, one {result} other {results}}',
- id: 'search_results.total',
- },
-});
-
// The component.
-export default function DrawerResults ({
- results,
- visible,
-}) {
- const accounts = results ? results.get('accounts') : null;
- const statuses = results ? results.get('statuses') : null;
- const hashtags = results ? results.get('hashtags') : null;
+export default @injectIntl
+class DrawerResults extends ImmutablePureComponent {
+
+ static propTypes = {
+ results: ImmutablePropTypes.map.isRequired,
+ intl: PropTypes.object.isRequired,
+ };
+
+ render() {
+ const { intl, results } = this.props;
+
+ let accounts, statuses, hashtags;
+ let count = 0;
+
+ if (results.get('accounts') && results.get('accounts').size > 0) {
+ count += results.get('accounts').size;
+ accounts = (
+
+
- // This gets the total number of items.
- const count = [accounts, statuses, hashtags].reduce(function (size, item) {
- if (item && item.size) {
- return size + item.size;
+ {results.get('accounts').map(accountId => )}
+
+ );
}
- return size;
- }, 0);
- // The result.
- return (
-
- {({ x }) => (
-
-
- {accounts && accounts.size ? (
-
-
+ if (results.get('statuses') && results.get('statuses').size > 0) {
+ count += results.get('statuses').size;
+ statuses = (
+
+
- {accounts.map(
- accountId => (
-
- )
- )}
-
- ) : null}
- {statuses && statuses.size ? (
-
-
+ {results.get('statuses').map(statusId => )}
+
+ );
+ }
- {statuses.map(
- statusId => (
-
- )
- )}
-
- ) : null}
- {hashtags && hashtags.size ? (
-
-
+ if (results.get('hashtags') && results.get('hashtags').size > 0) {
+ count += results.get('hashtags').size;
+ hashtags = (
+
+
- {hashtags.map(hashtag => )}
-
- ) : null}
-
- )}
-
- );
-}
+ {results.get('hashtags').map(hashtag =>
)}
+
+ );
+ }
-// Props.
-DrawerResults.propTypes = {
- results: ImmutablePropTypes.map,
- visible: PropTypes.bool,
-};
+ // The result.
+ return (
+
+
+
+ {accounts}
+ {statuses}
+ {hashtags}
+
+ );
+ };
+}
diff --git a/app/javascript/flavours/glitch/styles/components/drawer.scss b/app/javascript/flavours/glitch/styles/components/drawer.scss
index d22783b94..41c794790 100644
--- a/app/javascript/flavours/glitch/styles/components/drawer.scss
+++ b/app/javascript/flavours/glitch/styles/components/drawer.scss
@@ -185,12 +185,6 @@
}
.drawer--results {
- position: absolute;
- top: 0;
- bottom: 0;
- left: 0;
- right: 0;
- padding: 0;
background: $ui-base-color;
overflow-x: hidden;
overflow-y: auto;
--
cgit
From 149aa07409ef7cd17098a28510e515530b173f13 Mon Sep 17 00:00:00 2001
From: Thibaut Girka
Date: Sat, 20 Apr 2019 19:18:26 +0200
Subject: DrawerResults → SearchResults + SearchResultsContainer
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../features/compose/components/search_results.js | 72 ++++++++++++++++++++
.../compose/containers/search_results_container.js | 8 +++
.../flavours/glitch/features/compose/index.js | 13 +---
.../glitch/features/compose/results/index.js | 78 ----------------------
4 files changed, 82 insertions(+), 89 deletions(-)
create mode 100644 app/javascript/flavours/glitch/features/compose/components/search_results.js
create mode 100644 app/javascript/flavours/glitch/features/compose/containers/search_results_container.js
delete mode 100644 app/javascript/flavours/glitch/features/compose/results/index.js
diff --git a/app/javascript/flavours/glitch/features/compose/components/search_results.js b/app/javascript/flavours/glitch/features/compose/components/search_results.js
new file mode 100644
index 000000000..3d29675b4
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/compose/components/search_results.js
@@ -0,0 +1,72 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
+import AccountContainer from 'flavours/glitch/containers/account_container';
+import StatusContainer from 'flavours/glitch/containers/status_container';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+import Hashtag from 'flavours/glitch/components/hashtag';
+import Icon from 'flavours/glitch/components/icon';
+
+export default @injectIntl
+class SearchResults extends ImmutablePureComponent {
+
+ static propTypes = {
+ results: ImmutablePropTypes.map.isRequired,
+ intl: PropTypes.object.isRequired,
+ };
+
+ render() {
+ const { intl, results } = this.props;
+
+ let accounts, statuses, hashtags;
+ let count = 0;
+
+ if (results.get('accounts') && results.get('accounts').size > 0) {
+ count += results.get('accounts').size;
+ accounts = (
+
+
+
+ {results.get('accounts').map(accountId => )}
+
+ );
+ }
+
+ if (results.get('statuses') && results.get('statuses').size > 0) {
+ count += results.get('statuses').size;
+ statuses = (
+
+
+
+ {results.get('statuses').map(statusId => )}
+
+ );
+ }
+
+ if (results.get('hashtags') && results.get('hashtags').size > 0) {
+ count += results.get('hashtags').size;
+ hashtags = (
+
+
+
+ {results.get('hashtags').map(hashtag => )}
+
+ );
+ }
+
+ // The result.
+ return (
+
+
+
+ {accounts}
+ {statuses}
+ {hashtags}
+
+ );
+ };
+}
diff --git a/app/javascript/flavours/glitch/features/compose/containers/search_results_container.js b/app/javascript/flavours/glitch/features/compose/containers/search_results_container.js
new file mode 100644
index 000000000..16d95d417
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/compose/containers/search_results_container.js
@@ -0,0 +1,8 @@
+import { connect } from 'react-redux';
+import SearchResults from '../components/search_results';
+
+const mapStateToProps = state => ({
+ results: state.getIn(['search', 'results']),
+});
+
+export default connect(mapStateToProps)(SearchResults);
diff --git a/app/javascript/flavours/glitch/features/compose/index.js b/app/javascript/flavours/glitch/features/compose/index.js
index 6dc786c8b..0865a7ff9 100644
--- a/app/javascript/flavours/glitch/features/compose/index.js
+++ b/app/javascript/flavours/glitch/features/compose/index.js
@@ -14,8 +14,8 @@ import { cycleElefriendCompose } from 'flavours/glitch/actions/compose';
import Composer from 'flavours/glitch/features/composer';
import DrawerAccount from './account';
import DrawerHeader from './header';
-import DrawerResults from './results';
import SearchContainer from './containers/search_container';
+import SearchResultsContainer from './containers/search_results_container';
import spring from 'react-motion/lib/spring';
// Utils.
@@ -32,9 +32,6 @@ const mapStateToProps = (state, ownProps) => ({
account: state.getIn(['accounts', me]),
columns: state.getIn(['settings', 'columns']),
elefriend: state.getIn(['compose', 'elefriend']),
- results: state.getIn(['search', 'results']),
- searchHidden: state.getIn(['search', 'hidden']),
- submitted: state.getIn(['search', 'submitted']),
showSearch: ownProps.multiColumn ? state.getIn(['search', 'submitted']) && !state.getIn(['search', 'hidden']) : ownProps.isSearchPage,
unreadNotifications: state.getIn(['notifications', 'unread']),
showNotificationsBadge: state.getIn(['local_settings', 'notifications', 'tab_badge']),
@@ -65,10 +62,7 @@ class Compose extends React.PureComponent {
// State props.
account: ImmutablePropTypes.map,
columns: ImmutablePropTypes.list,
- results: ImmutablePropTypes.map,
elefriend: PropTypes.number,
- searchHidden: PropTypes.bool,
- submitted: PropTypes.bool,
unreadNotifications: PropTypes.number,
showNotificationsBadge: PropTypes.bool,
@@ -87,9 +81,6 @@ class Compose extends React.PureComponent {
multiColumn,
onClickElefriend,
onOpenSettings,
- results,
- searchHidden,
- submitted,
isSearchPage,
unreadNotifications,
showNotificationsBadge,
@@ -124,7 +115,7 @@ class Compose extends React.PureComponent {
{({ x }) => (
-
+
)}
diff --git a/app/javascript/flavours/glitch/features/compose/results/index.js b/app/javascript/flavours/glitch/features/compose/results/index.js
deleted file mode 100644
index 162d14913..000000000
--- a/app/javascript/flavours/glitch/features/compose/results/index.js
+++ /dev/null
@@ -1,78 +0,0 @@
-// Package imports.
-import PropTypes from 'prop-types';
-import React from 'react';
-import ImmutablePureComponent from 'react-immutable-pure-component';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
-import { Link } from 'react-router-dom';
-
-// Components.
-import Icon from 'flavours/glitch/components/icon';
-import AccountContainer from 'flavours/glitch/containers/account_container';
-import StatusContainer from 'flavours/glitch/containers/status_container';
-import Hashtag from 'flavours/glitch/components/hashtag';
-
-// Messages.
-// The component.
-export default @injectIntl
-class DrawerResults extends ImmutablePureComponent {
-
- static propTypes = {
- results: ImmutablePropTypes.map.isRequired,
- intl: PropTypes.object.isRequired,
- };
-
- render() {
- const { intl, results } = this.props;
-
- let accounts, statuses, hashtags;
- let count = 0;
-
- if (results.get('accounts') && results.get('accounts').size > 0) {
- count += results.get('accounts').size;
- accounts = (
-
-
-
- {results.get('accounts').map(accountId => )}
-
- );
- }
-
- if (results.get('statuses') && results.get('statuses').size > 0) {
- count += results.get('statuses').size;
- statuses = (
-
-
-
- {results.get('statuses').map(statusId => )}
-
- );
- }
-
- if (results.get('hashtags') && results.get('hashtags').size > 0) {
- count += results.get('hashtags').size;
- hashtags = (
-
-
-
- {results.get('hashtags').map(hashtag => )}
-
- );
- }
-
- // The result.
- return (
-
-
-
- {accounts}
- {statuses}
- {hashtags}
-
- );
- };
-}
--
cgit
From 9b9816aba6c97eae9ea35698b185fe3deb3a870a Mon Sep 17 00:00:00 2001
From: Eugen Rochko
Date: Tue, 23 Oct 2018 00:08:39 +0200
Subject: [Glitch] Show suggested follows on search screen in mobile layout
Port ad510db3a19640267f94062756d558a45472af14 to glitch-soc
---
.../flavours/glitch/actions/suggestions.js | 52 ++++++++++++++++++++++
.../flavours/glitch/components/account.js | 14 +++++-
.../features/compose/components/search_results.js | 36 ++++++++++++++-
.../compose/containers/search_results_container.js | 9 +++-
app/javascript/flavours/glitch/reducers/index.js | 2 +
.../flavours/glitch/reducers/suggestions.js | 30 +++++++++++++
6 files changed, 140 insertions(+), 3 deletions(-)
create mode 100644 app/javascript/flavours/glitch/actions/suggestions.js
create mode 100644 app/javascript/flavours/glitch/reducers/suggestions.js
diff --git a/app/javascript/flavours/glitch/actions/suggestions.js b/app/javascript/flavours/glitch/actions/suggestions.js
new file mode 100644
index 000000000..3687136ff
--- /dev/null
+++ b/app/javascript/flavours/glitch/actions/suggestions.js
@@ -0,0 +1,52 @@
+import api from 'flavours/glitch/util/api';
+import { importFetchedAccounts } from './importer';
+
+export const SUGGESTIONS_FETCH_REQUEST = 'SUGGESTIONS_FETCH_REQUEST';
+export const SUGGESTIONS_FETCH_SUCCESS = 'SUGGESTIONS_FETCH_SUCCESS';
+export const SUGGESTIONS_FETCH_FAIL = 'SUGGESTIONS_FETCH_FAIL';
+
+export const SUGGESTIONS_DISMISS = 'SUGGESTIONS_DISMISS';
+
+export function fetchSuggestions() {
+ return (dispatch, getState) => {
+ dispatch(fetchSuggestionsRequest());
+
+ api(getState).get('/api/v1/suggestions').then(response => {
+ dispatch(importFetchedAccounts(response.data));
+ dispatch(fetchSuggestionsSuccess(response.data));
+ }).catch(error => dispatch(fetchSuggestionsFail(error)));
+ };
+};
+
+export function fetchSuggestionsRequest() {
+ return {
+ type: SUGGESTIONS_FETCH_REQUEST,
+ skipLoading: true,
+ };
+};
+
+export function fetchSuggestionsSuccess(accounts) {
+ return {
+ type: SUGGESTIONS_FETCH_SUCCESS,
+ accounts,
+ skipLoading: true,
+ };
+};
+
+export function fetchSuggestionsFail(error) {
+ return {
+ type: SUGGESTIONS_FETCH_FAIL,
+ error,
+ skipLoading: true,
+ skipAlert: true,
+ };
+};
+
+export const dismissSuggestion = accountId => (dispatch, getState) => {
+ dispatch({
+ type: SUGGESTIONS_DISMISS,
+ id: accountId,
+ });
+
+ api(getState).delete(`/api/v1/suggestions/${accountId}`);
+};
diff --git a/app/javascript/flavours/glitch/components/account.js b/app/javascript/flavours/glitch/components/account.js
index 4fcafc509..3fc18cb72 100644
--- a/app/javascript/flavours/glitch/components/account.js
+++ b/app/javascript/flavours/glitch/components/account.js
@@ -31,6 +31,9 @@ export default class Account extends ImmutablePureComponent {
intl: PropTypes.object.isRequired,
hidden: PropTypes.bool,
small: PropTypes.bool,
+ actionIcon: PropTypes.string,
+ actionTitle: PropTypes.string,
+ onActionClick: PropTypes.func,
};
handleFollow = () => {
@@ -53,12 +56,19 @@ export default class Account extends ImmutablePureComponent {
this.props.onMuteNotifications(this.props.account, false);
}
+ handleAction = () => {
+ this.props.onActionClick(this.props.account);
+ }
+
render () {
const {
account,
hidden,
intl,
small,
+ onActionClick,
+ actionIcon,
+ actionTitle,
} = this.props;
if (!account) {
@@ -76,7 +86,9 @@ export default class Account extends ImmutablePureComponent {
let buttons;
- if (account.get('id') !== me && !small && account.get('relationship', null) !== null) {
+ if (onActionClick && actionIcon) {
+ buttons = ;
+ } else if (account.get('id') !== me && !small && account.get('relationship', null) !== null) {
const following = account.getIn(['relationship', 'following']);
const requested = account.getIn(['relationship', 'requested']);
const blocking = account.getIn(['relationship', 'blocking']);
diff --git a/app/javascript/flavours/glitch/features/compose/components/search_results.js b/app/javascript/flavours/glitch/features/compose/components/search_results.js
index 3d29675b4..69df8cdc9 100644
--- a/app/javascript/flavours/glitch/features/compose/components/search_results.js
+++ b/app/javascript/flavours/glitch/features/compose/components/search_results.js
@@ -8,16 +8,50 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
import Hashtag from 'flavours/glitch/components/hashtag';
import Icon from 'flavours/glitch/components/icon';
+const messages = defineMessages({
+ dismissSuggestion: { id: 'suggestions.dismiss', defaultMessage: 'Dismiss suggestion' },
+});
+
export default @injectIntl
class SearchResults extends ImmutablePureComponent {
static propTypes = {
results: ImmutablePropTypes.map.isRequired,
+ suggestions: ImmutablePropTypes.list.isRequired,
+ fetchSuggestions: PropTypes.func.isRequired,
+ dismissSuggestion: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
};
+ componentDidMount () {
+ this.props.fetchSuggestions();
+ }
+
render() {
- const { intl, results } = this.props;
+ const { intl, results, suggestions, dismissSuggestion } = this.props;
+
+ if (results.isEmpty() && !suggestions.isEmpty()) {
+ return (
+
+
+
+
+
+
+
+ {suggestions && suggestions.map(accountId => (
+
+ ))}
+
+
+ );
+ }
let accounts, statuses, hashtags;
let count = 0;
diff --git a/app/javascript/flavours/glitch/features/compose/containers/search_results_container.js b/app/javascript/flavours/glitch/features/compose/containers/search_results_container.js
index 16d95d417..f9637861a 100644
--- a/app/javascript/flavours/glitch/features/compose/containers/search_results_container.js
+++ b/app/javascript/flavours/glitch/features/compose/containers/search_results_container.js
@@ -1,8 +1,15 @@
import { connect } from 'react-redux';
import SearchResults from '../components/search_results';
+import { fetchSuggestions, dismissSuggestion } from '../../../actions/suggestions';
const mapStateToProps = state => ({
results: state.getIn(['search', 'results']),
+ suggestions: state.getIn(['suggestions', 'items']),
});
-export default connect(mapStateToProps)(SearchResults);
+const mapDispatchToProps = dispatch => ({
+ fetchSuggestions: () => dispatch(fetchSuggestions()),
+ dismissSuggestion: account => dispatch(dismissSuggestion(account.get('id'))),
+});
+
+export default connect(mapStateToProps, mapDispatchToProps)(SearchResults);
diff --git a/app/javascript/flavours/glitch/reducers/index.js b/app/javascript/flavours/glitch/reducers/index.js
index 76b38adb4..45b93b92c 100644
--- a/app/javascript/flavours/glitch/reducers/index.js
+++ b/app/javascript/flavours/glitch/reducers/index.js
@@ -28,6 +28,7 @@ import lists from './lists';
import listEditor from './list_editor';
import listAdder from './list_adder';
import filters from './filters';
+import suggestions from './suggestions';
import pinnedAccountsEditor from './pinned_accounts_editor';
import polls from './polls';
import identity_proofs from './identity_proofs';
@@ -63,6 +64,7 @@ const reducers = {
listEditor,
listAdder,
filters,
+ suggestions,
pinnedAccountsEditor,
polls,
};
diff --git a/app/javascript/flavours/glitch/reducers/suggestions.js b/app/javascript/flavours/glitch/reducers/suggestions.js
new file mode 100644
index 000000000..9f4b89d58
--- /dev/null
+++ b/app/javascript/flavours/glitch/reducers/suggestions.js
@@ -0,0 +1,30 @@
+import {
+ SUGGESTIONS_FETCH_REQUEST,
+ SUGGESTIONS_FETCH_SUCCESS,
+ SUGGESTIONS_FETCH_FAIL,
+ SUGGESTIONS_DISMISS,
+} from '../actions/suggestions';
+import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable';
+
+const initialState = ImmutableMap({
+ items: ImmutableList(),
+ isLoading: false,
+});
+
+export default function suggestionsReducer(state = initialState, action) {
+ switch(action.type) {
+ case SUGGESTIONS_FETCH_REQUEST:
+ return state.set('isLoading', true);
+ case SUGGESTIONS_FETCH_SUCCESS:
+ return state.withMutations(map => {
+ map.set('items', fromJS(action.accounts.map(x => x.id)));
+ map.set('isLoading', false);
+ });
+ case SUGGESTIONS_FETCH_FAIL:
+ return state.set('isLoading', false);
+ case SUGGESTIONS_DISMISS:
+ return state.update('items', list => list.filterNot(id => id === action.id));
+ default:
+ return state;
+ }
+};
--
cgit
From 9a2f10fe8b24d48f0b7c5dde545b2df3c8952330 Mon Sep 17 00:00:00 2001
From: Thibaut Girka
Date: Sat, 20 Apr 2019 20:32:16 +0200
Subject: DrawerAccount → NavigationBar + NavigationContainer
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../glitch/features/compose/account/index.js | 76 ----------------------
.../features/compose/components/navigation_bar.js | 36 ++++++++++
.../compose/containers/navigation_container.js | 11 ++++
.../flavours/glitch/features/compose/index.js | 7 +-
.../features/ui/components/onboarding_modal.js | 2 +-
5 files changed, 50 insertions(+), 82 deletions(-)
delete mode 100644 app/javascript/flavours/glitch/features/compose/account/index.js
create mode 100644 app/javascript/flavours/glitch/features/compose/components/navigation_bar.js
create mode 100644 app/javascript/flavours/glitch/features/compose/containers/navigation_container.js
diff --git a/app/javascript/flavours/glitch/features/compose/account/index.js b/app/javascript/flavours/glitch/features/compose/account/index.js
deleted file mode 100644
index 552848641..000000000
--- a/app/javascript/flavours/glitch/features/compose/account/index.js
+++ /dev/null
@@ -1,76 +0,0 @@
-// Package imports.
-import React from 'react';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import {
- FormattedMessage,
- defineMessages,
-} from 'react-intl';
-
-// Components.
-import Avatar from 'flavours/glitch/components/avatar';
-import Permalink from 'flavours/glitch/components/permalink';
-
-// Utils.
-import { hiddenComponent } from 'flavours/glitch/util/react_helpers';
-import { profileLink } from 'flavours/glitch/util/backend_links';
-
-// Messages.
-const messages = defineMessages({
- edit: {
- defaultMessage: 'Edit profile',
- id: 'navigation_bar.edit_profile',
- },
-});
-
-// The component.
-export default function DrawerAccount ({ account }) {
-
- // We need an account to render.
- if (!account) {
- return (
-
- { profileLink !== undefined && (
-
-
-
- )}
-
- );
- }
-
- // The result.
- return (
-
-
- {account.get('acct')}
-
-
-
- @{account.get('acct')}
-
- { profileLink !== undefined && (
-
- )}
-
- );
-}
-
-// Props.
-DrawerAccount.propTypes = { account: ImmutablePropTypes.map };
diff --git a/app/javascript/flavours/glitch/features/compose/components/navigation_bar.js b/app/javascript/flavours/glitch/features/compose/components/navigation_bar.js
new file mode 100644
index 000000000..59172bb23
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/compose/components/navigation_bar.js
@@ -0,0 +1,36 @@
+import React from 'react';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import Avatar from 'flavours/glitch/components/avatar';
+import Permalink from 'flavours/glitch/components/permalink';
+import { FormattedMessage } from 'react-intl';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+import { profileLink } from 'flavours/glitch/util/backend_links';
+
+export default class NavigationBar extends ImmutablePureComponent {
+
+ static propTypes = {
+ account: ImmutablePropTypes.map.isRequired,
+ };
+
+ render () {
+ return (
+
+
+ {this.props.account.get('acct')}
+
+
+
+
+ @{this.props.account.get('acct')}
+
+
+ { profileLink !== undefined && (
+
+ )}
+
+ );
+ };
+}
diff --git a/app/javascript/flavours/glitch/features/compose/containers/navigation_container.js b/app/javascript/flavours/glitch/features/compose/containers/navigation_container.js
new file mode 100644
index 000000000..eb630ffbb
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/compose/containers/navigation_container.js
@@ -0,0 +1,11 @@
+import { connect } from 'react-redux';
+import NavigationBar from '../components/navigation_bar';
+import { me } from 'flavours/glitch/util/initial_state';
+
+const mapStateToProps = state => {
+ return {
+ account: state.getIn(['accounts', me]),
+ };
+};
+
+export default connect(mapStateToProps)(NavigationBar);
diff --git a/app/javascript/flavours/glitch/features/compose/index.js b/app/javascript/flavours/glitch/features/compose/index.js
index 0865a7ff9..9fe510028 100644
--- a/app/javascript/flavours/glitch/features/compose/index.js
+++ b/app/javascript/flavours/glitch/features/compose/index.js
@@ -12,10 +12,10 @@ import { cycleElefriendCompose } from 'flavours/glitch/actions/compose';
// Components.
import Composer from 'flavours/glitch/features/composer';
-import DrawerAccount from './account';
import DrawerHeader from './header';
import SearchContainer from './containers/search_container';
import SearchResultsContainer from './containers/search_results_container';
+import NavigationContainer from './containers/navigation_container';
import spring from 'react-motion/lib/spring';
// Utils.
@@ -29,7 +29,6 @@ const messages = defineMessages({
// State mapping.
const mapStateToProps = (state, ownProps) => ({
- account: state.getIn(['accounts', me]),
columns: state.getIn(['settings', 'columns']),
elefriend: state.getIn(['compose', 'elefriend']),
showSearch: ownProps.multiColumn ? state.getIn(['search', 'submitted']) && !state.getIn(['search', 'hidden']) : ownProps.isSearchPage,
@@ -60,7 +59,6 @@ class Compose extends React.PureComponent {
showSearch: PropTypes.bool,
// State props.
- account: ImmutablePropTypes.map,
columns: ImmutablePropTypes.list,
elefriend: PropTypes.number,
unreadNotifications: PropTypes.number,
@@ -74,7 +72,6 @@ class Compose extends React.PureComponent {
// Rendering.
render () {
const {
- account,
columns,
elefriend,
intl,
@@ -103,7 +100,7 @@ class Compose extends React.PureComponent {
{(multiColumn || isSearchPage) && }
{!isSearchPage &&
-
+
{multiColumn && (
diff --git a/app/javascript/flavours/glitch/features/ui/components/onboarding_modal.js b/app/javascript/flavours/glitch/features/ui/components/onboarding_modal.js
index 38e9b63ec..a8ac83366 100644
--- a/app/javascript/flavours/glitch/features/ui/components/onboarding_modal.js
+++ b/app/javascript/flavours/glitch/features/ui/components/onboarding_modal.js
@@ -7,7 +7,7 @@ import ReactSwipeableViews from 'react-swipeable-views';
import classNames from 'classnames';
import Permalink from 'flavours/glitch/components/permalink';
import { WrappedComponent as RawComposer } from 'flavours/glitch/features/composer';
-import DrawerAccount from 'flavours/glitch/features/compose/account';
+import DrawerAccount from 'flavours/glitch/features/compose/components/navigation_bar';
import Search from 'flavours/glitch/features/compose/components/search';
import ColumnHeader from './column_header';
import { me } from 'flavours/glitch/util/initial_state';
--
cgit
From 281a82d8784fec7e79e309095cbe61428173b44f Mon Sep 17 00:00:00 2001
From: Thibaut Girka
Date: Sat, 20 Apr 2019 20:52:07 +0200
Subject: DrawerHeader → Header + HeaderContainer
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../glitch/features/compose/components/header.js | 124 ++++++++++++++++++++
.../compose/containers/header_container.js | 21 ++++
.../glitch/features/compose/header/index.js | 127 ---------------------
.../flavours/glitch/features/compose/index.js | 30 +----
4 files changed, 148 insertions(+), 154 deletions(-)
create mode 100644 app/javascript/flavours/glitch/features/compose/components/header.js
create mode 100644 app/javascript/flavours/glitch/features/compose/containers/header_container.js
delete mode 100644 app/javascript/flavours/glitch/features/compose/header/index.js
diff --git a/app/javascript/flavours/glitch/features/compose/components/header.js b/app/javascript/flavours/glitch/features/compose/components/header.js
new file mode 100644
index 000000000..2e29084f2
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/compose/components/header.js
@@ -0,0 +1,124 @@
+// Package imports.
+import PropTypes from 'prop-types';
+import React from 'react';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import { injectIntl, defineMessages } from 'react-intl';
+import { Link } from 'react-router-dom';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+
+// Components.
+import Icon from 'flavours/glitch/components/icon';
+
+// Utils.
+import { conditionalRender } from 'flavours/glitch/util/react_helpers';
+import { signOutLink } from 'flavours/glitch/util/backend_links';
+
+// Messages.
+const messages = defineMessages({
+ community: {
+ defaultMessage: 'Local timeline',
+ id: 'navigation_bar.community_timeline',
+ },
+ home_timeline: {
+ defaultMessage: 'Home',
+ id: 'tabs_bar.home',
+ },
+ logout: {
+ defaultMessage: 'Logout',
+ id: 'navigation_bar.logout',
+ },
+ notifications: {
+ defaultMessage: 'Notifications',
+ id: 'tabs_bar.notifications',
+ },
+ public: {
+ defaultMessage: 'Federated timeline',
+ id: 'navigation_bar.public_timeline',
+ },
+ settings: {
+ defaultMessage: 'App settings',
+ id: 'navigation_bar.app_settings',
+ },
+ start: {
+ defaultMessage: 'Getting started',
+ id: 'getting_started.heading',
+ },
+});
+
+export default @injectIntl
+class Header extends ImmutablePureComponent {
+ static propTypes = {
+ columns: ImmutablePropTypes.list,
+ unreadNotifications: PropTypes.number,
+ showNotificationsBadge: PropTypes.bool,
+ intl: PropTypes.object,
+ onSettingsClick: PropTypes.func,
+ };
+
+ render () {
+ const { intl, columns, unreadNotifications, showNotificationsBadge, onSettingsClick } = this.props;
+
+ // Only renders the component if the column isn't being shown.
+ const renderForColumn = conditionalRender.bind(null,
+ columnId => !columns || !columns.some(
+ column => column.get('id') === columnId
+ )
+ );
+
+ // The result.
+ return (
+
+
+ {renderForColumn('HOME', (
+
+ ))}
+ {renderForColumn('NOTIFICATIONS', (
+
+
+
+ { showNotificationsBadge && unreadNotifications > 0 &&
}
+
+
+ ))}
+ {renderForColumn('COMMUNITY', (
+
+ ))}
+ {renderForColumn('PUBLIC', (
+
+ ))}
+
+
+
+ );
+ };
+}
diff --git a/app/javascript/flavours/glitch/features/compose/containers/header_container.js b/app/javascript/flavours/glitch/features/compose/containers/header_container.js
new file mode 100644
index 000000000..6f1978807
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/compose/containers/header_container.js
@@ -0,0 +1,21 @@
+import { openModal } from 'flavours/glitch/actions/modal';
+import { connect } from 'react-redux';
+import Header from '../components/header';
+
+const mapStateToProps = state => {
+ return {
+ columns: state.getIn(['settings', 'columns']),
+ unreadNotifications: state.getIn(['notifications', 'unread']),
+ showNotificationsBadge: state.getIn(['local_settings', 'notifications', 'tab_badge']),
+ };
+};
+
+const mapDispatchToProps = (dispatch, { intl }) => ({
+ onOpenSettings (e) {
+ e.preventDefault();
+ e.stopPropagation();
+ dispatch(openModal('SETTINGS', {}));
+ },
+});
+
+export default connect(mapStateToProps, mapDispatchToProps)(Header);
diff --git a/app/javascript/flavours/glitch/features/compose/header/index.js b/app/javascript/flavours/glitch/features/compose/header/index.js
deleted file mode 100644
index da5599732..000000000
--- a/app/javascript/flavours/glitch/features/compose/header/index.js
+++ /dev/null
@@ -1,127 +0,0 @@
-// Package imports.
-import PropTypes from 'prop-types';
-import React from 'react';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import { defineMessages } from 'react-intl';
-import { Link } from 'react-router-dom';
-
-// Components.
-import Icon from 'flavours/glitch/components/icon';
-
-// Utils.
-import { conditionalRender } from 'flavours/glitch/util/react_helpers';
-import { signOutLink } from 'flavours/glitch/util/backend_links';
-
-// Messages.
-const messages = defineMessages({
- community: {
- defaultMessage: 'Local timeline',
- id: 'navigation_bar.community_timeline',
- },
- home_timeline: {
- defaultMessage: 'Home',
- id: 'tabs_bar.home',
- },
- logout: {
- defaultMessage: 'Logout',
- id: 'navigation_bar.logout',
- },
- notifications: {
- defaultMessage: 'Notifications',
- id: 'tabs_bar.notifications',
- },
- public: {
- defaultMessage: 'Federated timeline',
- id: 'navigation_bar.public_timeline',
- },
- settings: {
- defaultMessage: 'App settings',
- id: 'navigation_bar.app_settings',
- },
- start: {
- defaultMessage: 'Getting started',
- id: 'getting_started.heading',
- },
-});
-
-// The component.
-export default function DrawerHeader ({
- columns,
- unreadNotifications,
- showNotificationsBadge,
- intl,
- onSettingsClick,
-}) {
-
- // Only renders the component if the column isn't being shown.
- const renderForColumn = conditionalRender.bind(null,
- columnId => !columns || !columns.some(
- column => column.get('id') === columnId
- )
- );
-
- // The result.
- return (
-
-
- {renderForColumn('HOME', (
-
- ))}
- {renderForColumn('NOTIFICATIONS', (
-
-
-
- { showNotificationsBadge && unreadNotifications > 0 &&
}
-
-
- ))}
- {renderForColumn('COMMUNITY', (
-
- ))}
- {renderForColumn('PUBLIC', (
-
- ))}
-
-
-
- );
-}
-
-// Props.
-DrawerHeader.propTypes = {
- columns: ImmutablePropTypes.list,
- unreadNotifications: PropTypes.number,
- showNotificationsBadge: PropTypes.bool,
- intl: PropTypes.object,
- onSettingsClick: PropTypes.func,
-};
diff --git a/app/javascript/flavours/glitch/features/compose/index.js b/app/javascript/flavours/glitch/features/compose/index.js
index 9fe510028..923d379ec 100644
--- a/app/javascript/flavours/glitch/features/compose/index.js
+++ b/app/javascript/flavours/glitch/features/compose/index.js
@@ -7,12 +7,11 @@ import { injectIntl, defineMessages } from 'react-intl';
import classNames from 'classnames';
// Actions.
-import { openModal } from 'flavours/glitch/actions/modal';
import { cycleElefriendCompose } from 'flavours/glitch/actions/compose';
// Components.
import Composer from 'flavours/glitch/features/composer';
-import DrawerHeader from './header';
+import HeaderContainer from './containers/header_container';
import SearchContainer from './containers/search_container';
import SearchResultsContainer from './containers/search_results_container';
import NavigationContainer from './containers/navigation_container';
@@ -29,11 +28,8 @@ const messages = defineMessages({
// State mapping.
const mapStateToProps = (state, ownProps) => ({
- columns: state.getIn(['settings', 'columns']),
elefriend: state.getIn(['compose', 'elefriend']),
showSearch: ownProps.multiColumn ? state.getIn(['search', 'submitted']) && !state.getIn(['search', 'hidden']) : ownProps.isSearchPage,
- unreadNotifications: state.getIn(['notifications', 'unread']),
- showNotificationsBadge: state.getIn(['local_settings', 'notifications', 'tab_badge']),
});
// Dispatch mapping.
@@ -41,11 +37,6 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
onClickElefriend () {
dispatch(cycleElefriendCompose());
},
- onOpenSettings (e) {
- e.preventDefault();
- e.stopPropagation();
- dispatch(openModal('SETTINGS', {}));
- },
});
// The component.
@@ -59,28 +50,21 @@ class Compose extends React.PureComponent {
showSearch: PropTypes.bool,
// State props.
- columns: ImmutablePropTypes.list,
elefriend: PropTypes.number,
unreadNotifications: PropTypes.number,
- showNotificationsBadge: PropTypes.bool,
// Dispatch props.
onClickElefriend: PropTypes.func,
- onOpenSettings: PropTypes.func,
};
// Rendering.
render () {
const {
- columns,
elefriend,
intl,
multiColumn,
onClickElefriend,
- onOpenSettings,
isSearchPage,
- unreadNotifications,
- showNotificationsBadge,
showSearch,
} = this.props;
const computedClass = classNames('drawer', `mbstobon-${elefriend}`);
@@ -88,16 +72,8 @@ class Compose extends React.PureComponent {
// The result.
return (
- {multiColumn && (
-
- )}
- {(multiColumn || isSearchPage) &&
}
+ {multiColumn &&
}
+ {(multiColumn || isSearchPage) &&
}
{!isSearchPage &&
--
cgit
From 1bc4b8a0a57a4046364f4afbb741f2d4e7d48dcb Mon Sep 17 00:00:00 2001
From: Thibaut Girka
Date: Sat, 20 Apr 2019 21:28:03 +0200
Subject: features/composer/index.js → ComposeForm
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../features/compose/components/compose_form.js | 415 ++++++++++++++
.../compose/containers/compose_form_container.js | 175 ++++++
.../flavours/glitch/features/compose/index.js | 7 +-
.../flavours/glitch/features/composer/index.js | 600 ---------------------
.../glitch/features/standalone/compose/index.js | 4 +-
.../features/ui/components/onboarding_modal.js | 5 +-
6 files changed, 599 insertions(+), 607 deletions(-)
create mode 100644 app/javascript/flavours/glitch/features/compose/components/compose_form.js
create mode 100644 app/javascript/flavours/glitch/features/compose/containers/compose_form_container.js
delete mode 100644 app/javascript/flavours/glitch/features/composer/index.js
diff --git a/app/javascript/flavours/glitch/features/compose/components/compose_form.js b/app/javascript/flavours/glitch/features/compose/components/compose_form.js
new file mode 100644
index 000000000..0f9b11fa3
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/compose/components/compose_form.js
@@ -0,0 +1,415 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import { defineMessages, injectIntl } from 'react-intl';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+
+const APPROX_HASHTAG_RE = /(?:^|[^\/\)\w])#(\S+)/i;
+
+// Components.
+import ComposerOptions from '../../composer/options';
+import ComposerPublisher from '../../composer/publisher';
+import ComposerReply from '../../composer/reply';
+import ComposerSpoiler from '../../composer/spoiler';
+import ComposerTextarea from '../../composer/textarea';
+import ComposerUploadForm from '../../composer/upload_form';
+import ComposerPollForm from '../../composer/poll_form';
+import ComposerWarning from '../../composer/warning';
+import ComposerHashtagWarning from '../../composer/hashtag_warning';
+import ComposerDirectWarning from '../../composer/direct_warning';
+
+// Utils.
+import { countableText } from 'flavours/glitch/util/counter';
+import { isMobile } from 'flavours/glitch/util/is_mobile';
+
+const messages = defineMessages({
+ missingDescriptionMessage: { id: 'confirmations.missing_media_description.message',
+ defaultMessage: 'At least one media attachment is lacking a description. Consider describing all media attachments for the visually impaired before sending your toot.' },
+ missingDescriptionConfirm: { id: 'confirmations.missing_media_description.confirm',
+ defaultMessage: 'Send anyway' },
+});
+
+export default @injectIntl
+class ComposeForm extends ImmutablePureComponent {
+
+ static contextTypes = {
+ router: PropTypes.object,
+ };
+
+ static propTypes = {
+ intl: PropTypes.object.isRequired,
+
+ // State props.
+ acceptContentTypes: PropTypes.string,
+ advancedOptions: ImmutablePropTypes.map,
+ amUnlocked: PropTypes.bool,
+ focusDate: PropTypes.instanceOf(Date),
+ caretPosition: PropTypes.number,
+ isSubmitting: PropTypes.bool,
+ isChangingUpload: PropTypes.bool,
+ isUploading: PropTypes.bool,
+ layout: PropTypes.string,
+ media: ImmutablePropTypes.list,
+ preselectDate: PropTypes.instanceOf(Date),
+ privacy: PropTypes.string,
+ progress: PropTypes.number,
+ inReplyTo: ImmutablePropTypes.map,
+ resetFileKey: PropTypes.number,
+ sideArm: PropTypes.string,
+ sensitive: PropTypes.bool,
+ showSearch: PropTypes.bool,
+ spoiler: PropTypes.bool,
+ spoilerText: PropTypes.string,
+ suggestionToken: PropTypes.string,
+ suggestions: ImmutablePropTypes.list,
+ text: PropTypes.string,
+ anyMedia: PropTypes.bool,
+ spoilersAlwaysOn: PropTypes.bool,
+ mediaDescriptionConfirmation: PropTypes.bool,
+ preselectOnReply: PropTypes.bool,
+
+ // Dispatch props.
+ onCancelReply: PropTypes.func,
+ onChangeAdvancedOption: PropTypes.func,
+ onChangeDescription: PropTypes.func,
+ onChangeSensitivity: PropTypes.func,
+ onChangeSpoilerText: PropTypes.func,
+ onChangeSpoilerness: PropTypes.func,
+ onChangeText: PropTypes.func,
+ onChangeVisibility: PropTypes.func,
+ onClearSuggestions: PropTypes.func,
+ onCloseModal: PropTypes.func,
+ onFetchSuggestions: PropTypes.func,
+ onInsertEmoji: PropTypes.func,
+ onMount: PropTypes.func,
+ onOpenActionsModal: PropTypes.func,
+ onOpenDoodleModal: PropTypes.func,
+ onSelectSuggestion: PropTypes.func,
+ onSubmit: PropTypes.func,
+ onUndoUpload: PropTypes.func,
+ onUnmount: PropTypes.func,
+ onUpload: PropTypes.func,
+ onMediaDescriptionConfirm: PropTypes.func,
+ };
+
+ // Changes the text value of the spoiler.
+ handleChangeSpoiler = ({ target: { value } }) => {
+ const { onChangeSpoilerText } = this.props;
+ if (onChangeSpoilerText) {
+ onChangeSpoilerText(value);
+ }
+ }
+
+ // Inserts an emoji at the caret.
+ handleEmoji = (data) => {
+ const { textarea: { selectionStart } } = this;
+ const { onInsertEmoji } = this.props;
+ if (onInsertEmoji) {
+ onInsertEmoji(selectionStart, data);
+ }
+ }
+
+ // Handles the secondary submit button.
+ handleSecondarySubmit = () => {
+ const { handleSubmit } = this.handlers;
+ const {
+ onChangeVisibility,
+ sideArm,
+ } = this.props;
+ if (sideArm !== 'none' && onChangeVisibility) {
+ onChangeVisibility(sideArm);
+ }
+ handleSubmit();
+ }
+
+ // Selects a suggestion from the autofill.
+ handleSelect = (tokenStart, token, value) => {
+ const { onSelectSuggestion } = this.props;
+ if (onSelectSuggestion) {
+ onSelectSuggestion(tokenStart, token, value);
+ }
+ }
+
+ // Submits the status.
+ handleSubmit = () => {
+ const { textarea: { value }, uploadForm } = this;
+ const {
+ onChangeText,
+ onSubmit,
+ isSubmitting,
+ isChangingUpload,
+ isUploading,
+ media,
+ anyMedia,
+ text,
+ mediaDescriptionConfirmation,
+ onMediaDescriptionConfirm,
+ } = this.props;
+
+ // If something changes inside the textarea, then we update the
+ // state before submitting.
+ if (onChangeText && text !== value) {
+ onChangeText(value);
+ }
+
+ // Submit disabled:
+ if (isSubmitting || isUploading || isChangingUpload || (!text.trim().length && !anyMedia)) {
+ return;
+ }
+
+ // Submit unless there are media with missing descriptions
+ if (mediaDescriptionConfirmation && onMediaDescriptionConfirm && media && media.some(item => !item.get('description'))) {
+ const firstWithoutDescription = media.findIndex(item => !item.get('description'));
+ if (uploadForm) {
+ const inputs = uploadForm.querySelectorAll('.composer--upload_form--item input');
+ if (inputs.length == media.size && firstWithoutDescription !== -1) {
+ inputs[firstWithoutDescription].focus();
+ }
+ }
+ onMediaDescriptionConfirm(this.context.router ? this.context.router.history : null);
+ } else if (onSubmit) {
+ onSubmit(this.context.router ? this.context.router.history : null);
+ }
+ }
+
+ // Sets a reference to the upload form.
+ handleRefUploadForm = (uploadFormComponent) => {
+ this.uploadForm = uploadFormComponent;
+ }
+
+ // Sets a reference to the textarea.
+ handleRefTextarea = (textareaComponent) => {
+ if (textareaComponent) {
+ this.textarea = textareaComponent.textarea;
+ }
+ }
+
+ // Sets a reference to the CW field.
+ handleRefSpoilerText = (spoilerComponent) => {
+ if (spoilerComponent) {
+ this.spoilerText = spoilerComponent.spoilerText;
+ }
+ }
+
+ // Tells our state the composer has been mounted.
+ componentDidMount () {
+ const { onMount } = this.props;
+ if (onMount) {
+ onMount();
+ }
+ }
+
+ // Tells our state the composer has been unmounted.
+ componentWillUnmount () {
+ const { onUnmount } = this.props;
+ if (onUnmount) {
+ onUnmount();
+ }
+ }
+
+ // This statement does several things:
+ // - If we're beginning a reply, and,
+ // - Replying to zero or one users, places the cursor at the end
+ // of the textbox.
+ // - Replying to more than one user, selects any usernames past
+ // the first; this provides a convenient shortcut to drop
+ // everyone else from the conversation.
+ componentDidUpdate (prevProps) {
+ const {
+ textarea,
+ spoilerText,
+ } = this;
+ const {
+ focusDate,
+ caretPosition,
+ isSubmitting,
+ preselectDate,
+ text,
+ preselectOnReply,
+ } = this.props;
+ let selectionEnd, selectionStart;
+
+ // Caret/selection handling.
+ if (focusDate !== prevProps.focusDate) {
+ switch (true) {
+ case preselectDate !== prevProps.preselectDate && preselectOnReply:
+ selectionStart = text.search(/\s/) + 1;
+ selectionEnd = text.length;
+ break;
+ case !isNaN(caretPosition) && caretPosition !== null:
+ selectionStart = selectionEnd = caretPosition;
+ break;
+ default:
+ selectionStart = selectionEnd = text.length;
+ }
+ if (textarea) {
+ textarea.setSelectionRange(selectionStart, selectionEnd);
+ textarea.focus();
+ textarea.scrollIntoView();
+ }
+
+ // Refocuses the textarea after submitting.
+ } else if (textarea && prevProps.isSubmitting && !isSubmitting) {
+ textarea.focus();
+ } else if (this.props.spoiler !== prevProps.spoiler) {
+ if (this.props.spoiler) {
+ if (spoilerText) {
+ spoilerText.focus();
+ }
+ } else {
+ if (textarea) {
+ textarea.focus();
+ }
+ }
+ }
+ }
+
+ render () {
+ const {
+ handleChangeSpoiler,
+ handleEmoji,
+ handleSecondarySubmit,
+ handleSelect,
+ handleSubmit,
+ handleRefUploadForm,
+ handleRefTextarea,
+ handleRefSpoilerText,
+ } = this;
+ const {
+ acceptContentTypes,
+ advancedOptions,
+ amUnlocked,
+ anyMedia,
+ intl,
+ isSubmitting,
+ isChangingUpload,
+ isUploading,
+ layout,
+ media,
+ poll,
+ onCancelReply,
+ onChangeAdvancedOption,
+ onChangeDescription,
+ onChangeSensitivity,
+ onChangeSpoilerness,
+ onChangeText,
+ onChangeVisibility,
+ onTogglePoll,
+ onClearSuggestions,
+ onCloseModal,
+ onFetchSuggestions,
+ onOpenActionsModal,
+ onOpenDoodleModal,
+ onOpenFocalPointModal,
+ onUndoUpload,
+ onUpload,
+ privacy,
+ progress,
+ inReplyTo,
+ resetFileKey,
+ sensitive,
+ showSearch,
+ sideArm,
+ spoiler,
+ spoilerText,
+ suggestions,
+ text,
+ spoilersAlwaysOn,
+ } = this.props;
+
+ let disabledButton = isSubmitting || isUploading || isChangingUpload || (!text.trim().length && !anyMedia);
+
+ return (
+
+ {privacy === 'direct' ?
: null}
+ {privacy === 'private' && amUnlocked ?
: null}
+ {privacy !== 'public' && APPROX_HASHTAG_RE.test(text) ?
: null}
+ {inReplyTo && (
+
+ )}
+
+
+
+ {isUploading || media && media.size ? (
+
+ ) : null}
+ {!!poll && (
+
+ )}
+
+
item.get('type') === 'video'
+ ) : true)}
+ hasMedia={media && !!media.size}
+ allowPoll={!(media && !!media.size)}
+ hasPoll={!!poll}
+ intl={intl}
+ onChangeAdvancedOption={onChangeAdvancedOption}
+ onChangeSensitivity={onChangeSensitivity}
+ onChangeVisibility={onChangeVisibility}
+ onTogglePoll={onTogglePoll}
+ onDoodleOpen={onOpenDoodleModal}
+ onModalClose={onCloseModal}
+ onModalOpen={onOpenActionsModal}
+ onToggleSpoiler={spoilersAlwaysOn ? null : onChangeSpoilerness}
+ onUpload={onUpload}
+ privacy={privacy}
+ resetFileKey={resetFileKey}
+ sensitive={sensitive || (spoilersAlwaysOn && spoilerText && spoilerText.length > 0)}
+ spoiler={spoilersAlwaysOn ? (spoilerText && spoilerText.length > 0) : spoiler}
+ />
+
+
+ );
+ }
+
+}
diff --git a/app/javascript/flavours/glitch/features/compose/containers/compose_form_container.js b/app/javascript/flavours/glitch/features/compose/containers/compose_form_container.js
new file mode 100644
index 000000000..18fc31dce
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/compose/containers/compose_form_container.js
@@ -0,0 +1,175 @@
+import { connect } from 'react-redux';
+import ComposeForm from '../components/compose_form';
+import {
+ cancelReplyCompose,
+ changeCompose,
+ changeComposeAdvancedOption,
+ changeComposeSensitivity,
+ changeComposeSpoilerText,
+ changeComposeSpoilerness,
+ changeComposeVisibility,
+ changeUploadCompose,
+ clearComposeSuggestions,
+ fetchComposeSuggestions,
+ insertEmojiCompose,
+ mountCompose,
+ selectComposeSuggestion,
+ submitCompose,
+ undoUploadCompose,
+ unmountCompose,
+ uploadCompose,
+} from 'flavours/glitch/actions/compose';
+import {
+ closeModal,
+ openModal,
+} from 'flavours/glitch/actions/modal';
+import { changeLocalSetting } from 'flavours/glitch/actions/local_settings';
+import { addPoll, removePoll } from 'flavours/glitch/actions/compose';
+
+import { privacyPreference } from 'flavours/glitch/util/privacy_preference';
+import { me } from 'flavours/glitch/util/initial_state';
+
+const messages = defineMessages({
+ missingDescriptionMessage: { id: 'confirmations.missing_media_description.message',
+ defaultMessage: 'At least one media attachment is lacking a description. Consider describing all media attachments for the visually impaired before sending your toot.' },
+ missingDescriptionConfirm: { id: 'confirmations.missing_media_description.confirm',
+ defaultMessage: 'Send anyway' },
+});
+import { defineMessages } from 'react-intl';
+
+// State mapping.
+function mapStateToProps (state) {
+ const spoilersAlwaysOn = state.getIn(['local_settings', 'always_show_spoilers_field']);
+ const inReplyTo = state.getIn(['compose', 'in_reply_to']);
+ const replyPrivacy = inReplyTo ? state.getIn(['statuses', inReplyTo, 'visibility']) : null;
+ const sideArmBasePrivacy = state.getIn(['local_settings', 'side_arm']);
+ const sideArmRestrictedPrivacy = replyPrivacy ? privacyPreference(replyPrivacy, sideArmBasePrivacy) : null;
+ let sideArmPrivacy = null;
+ switch (state.getIn(['local_settings', 'side_arm_reply_mode'])) {
+ case 'copy':
+ sideArmPrivacy = replyPrivacy;
+ break;
+ case 'restrict':
+ sideArmPrivacy = sideArmRestrictedPrivacy;
+ break;
+ }
+ sideArmPrivacy = sideArmPrivacy || sideArmBasePrivacy;
+ return {
+ acceptContentTypes: state.getIn(['media_attachments', 'accept_content_types']).toArray().join(','),
+ advancedOptions: state.getIn(['compose', 'advanced_options']),
+ amUnlocked: !state.getIn(['accounts', me, 'locked']),
+ focusDate: state.getIn(['compose', 'focusDate']),
+ caretPosition: state.getIn(['compose', 'caretPosition']),
+ isSubmitting: state.getIn(['compose', 'is_submitting']),
+ isChangingUpload: state.getIn(['compose', 'is_changing_upload']),
+ isUploading: state.getIn(['compose', 'is_uploading']),
+ layout: state.getIn(['local_settings', 'layout']),
+ media: state.getIn(['compose', 'media_attachments']),
+ preselectDate: state.getIn(['compose', 'preselectDate']),
+ privacy: state.getIn(['compose', 'privacy']),
+ progress: state.getIn(['compose', 'progress']),
+ inReplyTo: inReplyTo ? state.getIn(['statuses', inReplyTo]) : null,
+ replyAccount: inReplyTo ? state.getIn(['statuses', inReplyTo, 'account']) : null,
+ replyContent: inReplyTo ? state.getIn(['statuses', inReplyTo, 'contentHtml']) : null,
+ resetFileKey: state.getIn(['compose', 'resetFileKey']),
+ sideArm: sideArmPrivacy,
+ sensitive: state.getIn(['compose', 'sensitive']),
+ showSearch: state.getIn(['search', 'submitted']) && !state.getIn(['search', 'hidden']),
+ spoiler: spoilersAlwaysOn || state.getIn(['compose', 'spoiler']),
+ spoilerText: state.getIn(['compose', 'spoiler_text']),
+ suggestionToken: state.getIn(['compose', 'suggestion_token']),
+ suggestions: state.getIn(['compose', 'suggestions']),
+ text: state.getIn(['compose', 'text']),
+ anyMedia: state.getIn(['compose', 'media_attachments']).size > 0,
+ poll: state.getIn(['compose', 'poll']),
+ spoilersAlwaysOn: spoilersAlwaysOn,
+ mediaDescriptionConfirmation: state.getIn(['local_settings', 'confirm_missing_media_description']),
+ preselectOnReply: state.getIn(['local_settings', 'preselect_on_reply']),
+ };
+};
+
+// Dispatch mapping.
+const mapDispatchToProps = (dispatch, { intl }) => ({
+ onCancelReply() {
+ dispatch(cancelReplyCompose());
+ },
+ onChangeAdvancedOption(option, value) {
+ dispatch(changeComposeAdvancedOption(option, value));
+ },
+ onChangeDescription(id, description) {
+ dispatch(changeUploadCompose(id, { description }));
+ },
+ onChangeSensitivity() {
+ dispatch(changeComposeSensitivity());
+ },
+ onChangeSpoilerText(text) {
+ dispatch(changeComposeSpoilerText(text));
+ },
+ onChangeSpoilerness() {
+ dispatch(changeComposeSpoilerness());
+ },
+ onChangeText(text) {
+ dispatch(changeCompose(text));
+ },
+ onChangeVisibility(value) {
+ dispatch(changeComposeVisibility(value));
+ },
+ onTogglePoll() {
+ dispatch((_, getState) => {
+ if (getState().getIn(['compose', 'poll'])) {
+ dispatch(removePoll());
+ } else {
+ dispatch(addPoll());
+ }
+ });
+ },
+ onClearSuggestions() {
+ dispatch(clearComposeSuggestions());
+ },
+ onCloseModal() {
+ dispatch(closeModal());
+ },
+ onFetchSuggestions(token) {
+ dispatch(fetchComposeSuggestions(token));
+ },
+ onInsertEmoji(position, emoji) {
+ dispatch(insertEmojiCompose(position, emoji));
+ },
+ onMount() {
+ dispatch(mountCompose());
+ },
+ onOpenActionsModal(props) {
+ dispatch(openModal('ACTIONS', props));
+ },
+ onOpenDoodleModal() {
+ dispatch(openModal('DOODLE', { noEsc: true }));
+ },
+ onOpenFocalPointModal(id) {
+ dispatch(openModal('FOCAL_POINT', { id }));
+ },
+ onSelectSuggestion(position, token, suggestion) {
+ dispatch(selectComposeSuggestion(position, token, suggestion));
+ },
+ onMediaDescriptionConfirm(routerHistory) {
+ dispatch(openModal('CONFIRM', {
+ message: intl.formatMessage(messages.missingDescriptionMessage),
+ confirm: intl.formatMessage(messages.missingDescriptionConfirm),
+ onConfirm: () => dispatch(submitCompose(routerHistory)),
+ onDoNotAsk: () => dispatch(changeLocalSetting(['confirm_missing_media_description'], false)),
+ }));
+ },
+ onSubmit(routerHistory) {
+ dispatch(submitCompose(routerHistory));
+ },
+ onUndoUpload(id) {
+ dispatch(undoUploadCompose(id));
+ },
+ onUnmount() {
+ dispatch(unmountCompose());
+ },
+ onUpload(files) {
+ dispatch(uploadCompose(files));
+ },
+});
+
+export default connect(mapStateToProps, mapDispatchToProps)(ComposeForm);
diff --git a/app/javascript/flavours/glitch/features/compose/index.js b/app/javascript/flavours/glitch/features/compose/index.js
index 923d379ec..01e7d1906 100644
--- a/app/javascript/flavours/glitch/features/compose/index.js
+++ b/app/javascript/flavours/glitch/features/compose/index.js
@@ -10,7 +10,7 @@ import classNames from 'classnames';
import { cycleElefriendCompose } from 'flavours/glitch/actions/compose';
// Components.
-import Composer from 'flavours/glitch/features/composer';
+import ComposeFormContainer from './containers/compose_form_container';
import HeaderContainer from './containers/header_container';
import SearchContainer from './containers/search_container';
import SearchResultsContainer from './containers/search_results_container';
@@ -73,11 +73,13 @@ class Compose extends React.PureComponent {
return (
{multiColumn &&
}
+
{(multiColumn || isSearchPage) &&
}
+
{!isSearchPage &&
-
+
{multiColumn && (
{mascot ?
:
}
@@ -96,4 +98,5 @@ class Compose extends React.PureComponent {
);
}
+
}
diff --git a/app/javascript/flavours/glitch/features/composer/index.js b/app/javascript/flavours/glitch/features/composer/index.js
deleted file mode 100644
index 9d2e0b3da..000000000
--- a/app/javascript/flavours/glitch/features/composer/index.js
+++ /dev/null
@@ -1,600 +0,0 @@
-// Package imports.
-import PropTypes from 'prop-types';
-import React from 'react';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import { defineMessages } from 'react-intl';
-
-const APPROX_HASHTAG_RE = /(?:^|[^\/\)\w])#(\S+)/i;
-
-// Actions.
-import {
- cancelReplyCompose,
- changeCompose,
- changeComposeAdvancedOption,
- changeComposeSensitivity,
- changeComposeSpoilerText,
- changeComposeSpoilerness,
- changeComposeVisibility,
- changeUploadCompose,
- clearComposeSuggestions,
- fetchComposeSuggestions,
- insertEmojiCompose,
- mountCompose,
- selectComposeSuggestion,
- submitCompose,
- undoUploadCompose,
- unmountCompose,
- uploadCompose,
-} from 'flavours/glitch/actions/compose';
-import {
- closeModal,
- openModal,
-} from 'flavours/glitch/actions/modal';
-import { changeLocalSetting } from 'flavours/glitch/actions/local_settings';
-import { addPoll, removePoll } from 'flavours/glitch/actions/compose';
-
-// Components.
-import ComposerOptions from './options';
-import ComposerPublisher from './publisher';
-import ComposerReply from './reply';
-import ComposerSpoiler from './spoiler';
-import ComposerTextarea from './textarea';
-import ComposerUploadForm from './upload_form';
-import ComposerPollForm from './poll_form';
-import ComposerWarning from './warning';
-import ComposerHashtagWarning from './hashtag_warning';
-import ComposerDirectWarning from './direct_warning';
-
-// Utils.
-import { countableText } from 'flavours/glitch/util/counter';
-import { me } from 'flavours/glitch/util/initial_state';
-import { isMobile } from 'flavours/glitch/util/is_mobile';
-import { assignHandlers } from 'flavours/glitch/util/react_helpers';
-import { wrap } from 'flavours/glitch/util/redux_helpers';
-import { privacyPreference } from 'flavours/glitch/util/privacy_preference';
-
-const messages = defineMessages({
- missingDescriptionMessage: { id: 'confirmations.missing_media_description.message',
- defaultMessage: 'At least one media attachment is lacking a description. Consider describing all media attachments for the visually impaired before sending your toot.' },
- missingDescriptionConfirm: { id: 'confirmations.missing_media_description.confirm',
- defaultMessage: 'Send anyway' },
-});
-
-// State mapping.
-function mapStateToProps (state) {
- const spoilersAlwaysOn = state.getIn(['local_settings', 'always_show_spoilers_field']);
- const inReplyTo = state.getIn(['compose', 'in_reply_to']);
- const replyPrivacy = inReplyTo ? state.getIn(['statuses', inReplyTo, 'visibility']) : null;
- const sideArmBasePrivacy = state.getIn(['local_settings', 'side_arm']);
- const sideArmRestrictedPrivacy = replyPrivacy ? privacyPreference(replyPrivacy, sideArmBasePrivacy) : null;
- let sideArmPrivacy = null;
- switch (state.getIn(['local_settings', 'side_arm_reply_mode'])) {
- case 'copy':
- sideArmPrivacy = replyPrivacy;
- break;
- case 'restrict':
- sideArmPrivacy = sideArmRestrictedPrivacy;
- break;
- }
- sideArmPrivacy = sideArmPrivacy || sideArmBasePrivacy;
- return {
- acceptContentTypes: state.getIn(['media_attachments', 'accept_content_types']).toArray().join(','),
- advancedOptions: state.getIn(['compose', 'advanced_options']),
- amUnlocked: !state.getIn(['accounts', me, 'locked']),
- focusDate: state.getIn(['compose', 'focusDate']),
- caretPosition: state.getIn(['compose', 'caretPosition']),
- isSubmitting: state.getIn(['compose', 'is_submitting']),
- isChangingUpload: state.getIn(['compose', 'is_changing_upload']),
- isUploading: state.getIn(['compose', 'is_uploading']),
- layout: state.getIn(['local_settings', 'layout']),
- media: state.getIn(['compose', 'media_attachments']),
- preselectDate: state.getIn(['compose', 'preselectDate']),
- privacy: state.getIn(['compose', 'privacy']),
- progress: state.getIn(['compose', 'progress']),
- inReplyTo: inReplyTo ? state.getIn(['statuses', inReplyTo]) : null,
- replyAccount: inReplyTo ? state.getIn(['statuses', inReplyTo, 'account']) : null,
- replyContent: inReplyTo ? state.getIn(['statuses', inReplyTo, 'contentHtml']) : null,
- resetFileKey: state.getIn(['compose', 'resetFileKey']),
- sideArm: sideArmPrivacy,
- sensitive: state.getIn(['compose', 'sensitive']),
- showSearch: state.getIn(['search', 'submitted']) && !state.getIn(['search', 'hidden']),
- spoiler: spoilersAlwaysOn || state.getIn(['compose', 'spoiler']),
- spoilerText: state.getIn(['compose', 'spoiler_text']),
- suggestionToken: state.getIn(['compose', 'suggestion_token']),
- suggestions: state.getIn(['compose', 'suggestions']),
- text: state.getIn(['compose', 'text']),
- anyMedia: state.getIn(['compose', 'media_attachments']).size > 0,
- poll: state.getIn(['compose', 'poll']),
- spoilersAlwaysOn: spoilersAlwaysOn,
- mediaDescriptionConfirmation: state.getIn(['local_settings', 'confirm_missing_media_description']),
- preselectOnReply: state.getIn(['local_settings', 'preselect_on_reply']),
- };
-};
-
-// Dispatch mapping.
-const mapDispatchToProps = (dispatch, { intl }) => ({
- onCancelReply() {
- dispatch(cancelReplyCompose());
- },
- onChangeAdvancedOption(option, value) {
- dispatch(changeComposeAdvancedOption(option, value));
- },
- onChangeDescription(id, description) {
- dispatch(changeUploadCompose(id, { description }));
- },
- onChangeSensitivity() {
- dispatch(changeComposeSensitivity());
- },
- onChangeSpoilerText(text) {
- dispatch(changeComposeSpoilerText(text));
- },
- onChangeSpoilerness() {
- dispatch(changeComposeSpoilerness());
- },
- onChangeText(text) {
- dispatch(changeCompose(text));
- },
- onChangeVisibility(value) {
- dispatch(changeComposeVisibility(value));
- },
- onTogglePoll() {
- dispatch((_, getState) => {
- if (getState().getIn(['compose', 'poll'])) {
- dispatch(removePoll());
- } else {
- dispatch(addPoll());
- }
- });
- },
- onClearSuggestions() {
- dispatch(clearComposeSuggestions());
- },
- onCloseModal() {
- dispatch(closeModal());
- },
- onFetchSuggestions(token) {
- dispatch(fetchComposeSuggestions(token));
- },
- onInsertEmoji(position, emoji) {
- dispatch(insertEmojiCompose(position, emoji));
- },
- onMount() {
- dispatch(mountCompose());
- },
- onOpenActionsModal(props) {
- dispatch(openModal('ACTIONS', props));
- },
- onOpenDoodleModal() {
- dispatch(openModal('DOODLE', { noEsc: true }));
- },
- onOpenFocalPointModal(id) {
- dispatch(openModal('FOCAL_POINT', { id }));
- },
- onSelectSuggestion(position, token, suggestion) {
- dispatch(selectComposeSuggestion(position, token, suggestion));
- },
- onMediaDescriptionConfirm(routerHistory) {
- dispatch(openModal('CONFIRM', {
- message: intl.formatMessage(messages.missingDescriptionMessage),
- confirm: intl.formatMessage(messages.missingDescriptionConfirm),
- onConfirm: () => dispatch(submitCompose(routerHistory)),
- onDoNotAsk: () => dispatch(changeLocalSetting(['confirm_missing_media_description'], false)),
- }));
- },
- onSubmit(routerHistory) {
- dispatch(submitCompose(routerHistory));
- },
- onUndoUpload(id) {
- dispatch(undoUploadCompose(id));
- },
- onUnmount() {
- dispatch(unmountCompose());
- },
- onUpload(files) {
- dispatch(uploadCompose(files));
- },
-});
-
-// Handlers.
-const handlers = {
-
- // Changes the text value of the spoiler.
- handleChangeSpoiler ({ target: { value } }) {
- const { onChangeSpoilerText } = this.props;
- if (onChangeSpoilerText) {
- onChangeSpoilerText(value);
- }
- },
-
- // Inserts an emoji at the caret.
- handleEmoji (data) {
- const { textarea: { selectionStart } } = this;
- const { onInsertEmoji } = this.props;
- if (onInsertEmoji) {
- onInsertEmoji(selectionStart, data);
- }
- },
-
- // Handles the secondary submit button.
- handleSecondarySubmit () {
- const { handleSubmit } = this.handlers;
- const {
- onChangeVisibility,
- sideArm,
- } = this.props;
- if (sideArm !== 'none' && onChangeVisibility) {
- onChangeVisibility(sideArm);
- }
- handleSubmit();
- },
-
- // Selects a suggestion from the autofill.
- handleSelect (tokenStart, token, value) {
- const { onSelectSuggestion } = this.props;
- if (onSelectSuggestion) {
- onSelectSuggestion(tokenStart, token, value);
- }
- },
-
- // Submits the status.
- handleSubmit () {
- const { textarea: { value }, uploadForm } = this;
- const {
- onChangeText,
- onSubmit,
- isSubmitting,
- isChangingUpload,
- isUploading,
- media,
- anyMedia,
- text,
- mediaDescriptionConfirmation,
- onMediaDescriptionConfirm,
- } = this.props;
-
- // If something changes inside the textarea, then we update the
- // state before submitting.
- if (onChangeText && text !== value) {
- onChangeText(value);
- }
-
- // Submit disabled:
- if (isSubmitting || isUploading || isChangingUpload || (!text.trim().length && !anyMedia)) {
- return;
- }
-
- // Submit unless there are media with missing descriptions
- if (mediaDescriptionConfirmation && onMediaDescriptionConfirm && media && media.some(item => !item.get('description'))) {
- const firstWithoutDescription = media.findIndex(item => !item.get('description'));
- if (uploadForm) {
- const inputs = uploadForm.querySelectorAll('.composer--upload_form--item input');
- if (inputs.length == media.size && firstWithoutDescription !== -1) {
- inputs[firstWithoutDescription].focus();
- }
- }
- onMediaDescriptionConfirm(this.context.router ? this.context.router.history : null);
- } else if (onSubmit) {
- onSubmit(this.context.router ? this.context.router.history : null);
- }
- },
-
- // Sets a reference to the upload form.
- handleRefUploadForm (uploadFormComponent) {
- this.uploadForm = uploadFormComponent;
- },
-
- // Sets a reference to the textarea.
- handleRefTextarea (textareaComponent) {
- if (textareaComponent) {
- this.textarea = textareaComponent.textarea;
- }
- },
-
- // Sets a reference to the CW field.
- handleRefSpoilerText (spoilerComponent) {
- if (spoilerComponent) {
- this.spoilerText = spoilerComponent.spoilerText;
- }
- }
-};
-
-// The component.
-class Composer extends React.Component {
-
- // Constructor.
- constructor (props) {
- super(props);
- assignHandlers(this, handlers);
-
- // Instance variables.
- this.textarea = null;
- this.spoilerText = null;
- }
-
- // Tells our state the composer has been mounted.
- componentDidMount () {
- const { onMount } = this.props;
- if (onMount) {
- onMount();
- }
- }
-
- // Tells our state the composer has been unmounted.
- componentWillUnmount () {
- const { onUnmount } = this.props;
- if (onUnmount) {
- onUnmount();
- }
- }
-
- // This statement does several things:
- // - If we're beginning a reply, and,
- // - Replying to zero or one users, places the cursor at the end
- // of the textbox.
- // - Replying to more than one user, selects any usernames past
- // the first; this provides a convenient shortcut to drop
- // everyone else from the conversation.
- componentDidUpdate (prevProps) {
- const {
- textarea,
- spoilerText,
- } = this;
- const {
- focusDate,
- caretPosition,
- isSubmitting,
- preselectDate,
- text,
- preselectOnReply,
- } = this.props;
- let selectionEnd, selectionStart;
-
- // Caret/selection handling.
- if (focusDate !== prevProps.focusDate) {
- switch (true) {
- case preselectDate !== prevProps.preselectDate && preselectOnReply:
- selectionStart = text.search(/\s/) + 1;
- selectionEnd = text.length;
- break;
- case !isNaN(caretPosition) && caretPosition !== null:
- selectionStart = selectionEnd = caretPosition;
- break;
- default:
- selectionStart = selectionEnd = text.length;
- }
- if (textarea) {
- textarea.setSelectionRange(selectionStart, selectionEnd);
- textarea.focus();
- textarea.scrollIntoView();
- }
-
- // Refocuses the textarea after submitting.
- } else if (textarea && prevProps.isSubmitting && !isSubmitting) {
- textarea.focus();
- } else if (this.props.spoiler !== prevProps.spoiler) {
- if (this.props.spoiler) {
- if (spoilerText) {
- spoilerText.focus();
- }
- } else {
- if (textarea) {
- textarea.focus();
- }
- }
- }
- }
-
- render () {
- const {
- handleChangeSpoiler,
- handleEmoji,
- handleSecondarySubmit,
- handleSelect,
- handleSubmit,
- handleRefUploadForm,
- handleRefTextarea,
- handleRefSpoilerText,
- } = this.handlers;
- const {
- acceptContentTypes,
- advancedOptions,
- amUnlocked,
- anyMedia,
- intl,
- isSubmitting,
- isChangingUpload,
- isUploading,
- layout,
- media,
- poll,
- onCancelReply,
- onChangeAdvancedOption,
- onChangeDescription,
- onChangeSensitivity,
- onChangeSpoilerness,
- onChangeText,
- onChangeVisibility,
- onTogglePoll,
- onClearSuggestions,
- onCloseModal,
- onFetchSuggestions,
- onOpenActionsModal,
- onOpenDoodleModal,
- onOpenFocalPointModal,
- onUndoUpload,
- onUpload,
- privacy,
- progress,
- inReplyTo,
- resetFileKey,
- sensitive,
- showSearch,
- sideArm,
- spoiler,
- spoilerText,
- suggestions,
- text,
- spoilersAlwaysOn,
- } = this.props;
-
- let disabledButton = isSubmitting || isUploading || isChangingUpload || (!text.trim().length && !anyMedia);
-
- return (
-
- {privacy === 'direct' ?
: null}
- {privacy === 'private' && amUnlocked ?
: null}
- {privacy !== 'public' && APPROX_HASHTAG_RE.test(text) ?
: null}
- {inReplyTo && (
-
- )}
-
-
-
- {isUploading || media && media.size ? (
-
- ) : null}
- {!!poll && (
-
- )}
-
-
item.get('type') === 'video'
- ) : true)}
- hasMedia={media && !!media.size}
- allowPoll={!(media && !!media.size)}
- hasPoll={!!poll}
- intl={intl}
- onChangeAdvancedOption={onChangeAdvancedOption}
- onChangeSensitivity={onChangeSensitivity}
- onChangeVisibility={onChangeVisibility}
- onTogglePoll={onTogglePoll}
- onDoodleOpen={onOpenDoodleModal}
- onModalClose={onCloseModal}
- onModalOpen={onOpenActionsModal}
- onToggleSpoiler={spoilersAlwaysOn ? null : onChangeSpoilerness}
- onUpload={onUpload}
- privacy={privacy}
- resetFileKey={resetFileKey}
- sensitive={sensitive || (spoilersAlwaysOn && spoilerText && spoilerText.length > 0)}
- spoiler={spoilersAlwaysOn ? (spoilerText && spoilerText.length > 0) : spoiler}
- />
-
-
- );
- }
-
-}
-
-// Props.
-Composer.propTypes = {
- intl: PropTypes.object.isRequired,
-
- // State props.
- acceptContentTypes: PropTypes.string,
- advancedOptions: ImmutablePropTypes.map,
- amUnlocked: PropTypes.bool,
- focusDate: PropTypes.instanceOf(Date),
- caretPosition: PropTypes.number,
- isSubmitting: PropTypes.bool,
- isChangingUpload: PropTypes.bool,
- isUploading: PropTypes.bool,
- layout: PropTypes.string,
- media: ImmutablePropTypes.list,
- preselectDate: PropTypes.instanceOf(Date),
- privacy: PropTypes.string,
- progress: PropTypes.number,
- inReplyTo: ImmutablePropTypes.map,
- resetFileKey: PropTypes.number,
- sideArm: PropTypes.string,
- sensitive: PropTypes.bool,
- showSearch: PropTypes.bool,
- spoiler: PropTypes.bool,
- spoilerText: PropTypes.string,
- suggestionToken: PropTypes.string,
- suggestions: ImmutablePropTypes.list,
- text: PropTypes.string,
- anyMedia: PropTypes.bool,
- spoilersAlwaysOn: PropTypes.bool,
- mediaDescriptionConfirmation: PropTypes.bool,
- preselectOnReply: PropTypes.bool,
-
- // Dispatch props.
- onCancelReply: PropTypes.func,
- onChangeAdvancedOption: PropTypes.func,
- onChangeDescription: PropTypes.func,
- onChangeSensitivity: PropTypes.func,
- onChangeSpoilerText: PropTypes.func,
- onChangeSpoilerness: PropTypes.func,
- onChangeText: PropTypes.func,
- onChangeVisibility: PropTypes.func,
- onClearSuggestions: PropTypes.func,
- onCloseModal: PropTypes.func,
- onFetchSuggestions: PropTypes.func,
- onInsertEmoji: PropTypes.func,
- onMount: PropTypes.func,
- onOpenActionsModal: PropTypes.func,
- onOpenDoodleModal: PropTypes.func,
- onSelectSuggestion: PropTypes.func,
- onSubmit: PropTypes.func,
- onUndoUpload: PropTypes.func,
- onUnmount: PropTypes.func,
- onUpload: PropTypes.func,
- onMediaDescriptionConfirm: PropTypes.func,
-};
-
-Composer.contextTypes = {
- router: PropTypes.object,
-};
-
-// Connecting and export.
-export { Composer as WrappedComponent };
-export default wrap(Composer, mapStateToProps, mapDispatchToProps, true);
diff --git a/app/javascript/flavours/glitch/features/standalone/compose/index.js b/app/javascript/flavours/glitch/features/standalone/compose/index.js
index a77b59448..b33c21cb5 100644
--- a/app/javascript/flavours/glitch/features/standalone/compose/index.js
+++ b/app/javascript/flavours/glitch/features/standalone/compose/index.js
@@ -1,5 +1,5 @@
import React from 'react';
-import Composer from 'flavours/glitch/features/composer';
+import ComposeFormContainer from 'flavours/glitch/features/compose/containers/compose_form_container';
import NotificationsContainer from 'flavours/glitch/features/ui/containers/notifications_container';
import LoadingBarContainer from 'flavours/glitch/features/ui/containers/loading_bar_container';
import ModalContainer from 'flavours/glitch/features/ui/containers/modal_container';
@@ -9,7 +9,7 @@ export default class Compose extends React.PureComponent {
render () {
return (
-
+
diff --git a/app/javascript/flavours/glitch/features/ui/components/onboarding_modal.js b/app/javascript/flavours/glitch/features/ui/components/onboarding_modal.js
index a8ac83366..f13e2e645 100644
--- a/app/javascript/flavours/glitch/features/ui/components/onboarding_modal.js
+++ b/app/javascript/flavours/glitch/features/ui/components/onboarding_modal.js
@@ -6,7 +6,7 @@ import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import ReactSwipeableViews from 'react-swipeable-views';
import classNames from 'classnames';
import Permalink from 'flavours/glitch/components/permalink';
-import { WrappedComponent as RawComposer } from 'flavours/glitch/features/composer';
+import { ComposeForm } from 'flavours/glitch/features/compose/components/compose_form';
import DrawerAccount from 'flavours/glitch/features/compose/components/navigation_bar';
import Search from 'flavours/glitch/features/compose/components/search';
import ColumnHeader from './column_header';
@@ -45,8 +45,7 @@ const PageTwo = ({ intl, myAccount }) => (
-
--
cgit
From f72af5794da52d22fbb2a77e0fcbc111633fcab2 Mon Sep 17 00:00:00 2001
From: Thibaut Girka
Date: Sat, 20 Apr 2019 22:05:09 +0200
Subject: Refactor Compose*Warning → ContainerWarning
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Regression: only one warning at a time
---
.../features/compose/components/compose_form.js | 13 ++---
.../glitch/features/compose/components/warning.js | 26 ++++++++++
.../compose/containers/warning_container.js | 44 ++++++++++++++++
.../features/composer/direct_warning/index.js | 55 --------------------
.../features/composer/hashtag_warning/index.js | 49 ------------------
.../glitch/features/composer/warning/index.js | 59 ----------------------
6 files changed, 75 insertions(+), 171 deletions(-)
create mode 100644 app/javascript/flavours/glitch/features/compose/components/warning.js
create mode 100644 app/javascript/flavours/glitch/features/compose/containers/warning_container.js
delete mode 100644 app/javascript/flavours/glitch/features/composer/direct_warning/index.js
delete mode 100644 app/javascript/flavours/glitch/features/composer/hashtag_warning/index.js
delete mode 100644 app/javascript/flavours/glitch/features/composer/warning/index.js
diff --git a/app/javascript/flavours/glitch/features/compose/components/compose_form.js b/app/javascript/flavours/glitch/features/compose/components/compose_form.js
index 0f9b11fa3..1f37a1da5 100644
--- a/app/javascript/flavours/glitch/features/compose/components/compose_form.js
+++ b/app/javascript/flavours/glitch/features/compose/components/compose_form.js
@@ -4,8 +4,6 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
import { defineMessages, injectIntl } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component';
-const APPROX_HASHTAG_RE = /(?:^|[^\/\)\w])#(\S+)/i;
-
// Components.
import ComposerOptions from '../../composer/options';
import ComposerPublisher from '../../composer/publisher';
@@ -14,9 +12,7 @@ import ComposerSpoiler from '../../composer/spoiler';
import ComposerTextarea from '../../composer/textarea';
import ComposerUploadForm from '../../composer/upload_form';
import ComposerPollForm from '../../composer/poll_form';
-import ComposerWarning from '../../composer/warning';
-import ComposerHashtagWarning from '../../composer/hashtag_warning';
-import ComposerDirectWarning from '../../composer/direct_warning';
+import WarningContainer from '../containers/warning_container';
// Utils.
import { countableText } from 'flavours/glitch/util/counter';
@@ -321,9 +317,8 @@ class ComposeForm extends ImmutablePureComponent {
return (
- {privacy === 'direct' ?
: null}
- {privacy === 'private' && amUnlocked ?
: null}
- {privacy !== 'public' && APPROX_HASHTAG_RE.test(text) ?
: null}
+
+
{inReplyTo && (
)}
+
+
+ {({ opacity, scaleX, scaleY }) => (
+
+ {message}
+
+ )}
+
+ );
+ }
+
+}
diff --git a/app/javascript/flavours/glitch/features/compose/containers/warning_container.js b/app/javascript/flavours/glitch/features/compose/containers/warning_container.js
new file mode 100644
index 000000000..fdd21f114
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/compose/containers/warning_container.js
@@ -0,0 +1,44 @@
+import React from 'react';
+import { connect } from 'react-redux';
+import Warning from '../components/warning';
+import PropTypes from 'prop-types';
+import { FormattedMessage } from 'react-intl';
+import { me } from 'flavours/glitch/util/initial_state';
+
+const APPROX_HASHTAG_RE = /(?:^|[^\/\)\w])#(\w*[a-zA-Z·]\w*)/i;
+
+const mapStateToProps = state => ({
+ needsLockWarning: state.getIn(['compose', 'privacy']) === 'private' && !state.getIn(['accounts', me, 'locked']),
+ hashtagWarning: state.getIn(['compose', 'privacy']) !== 'public' && APPROX_HASHTAG_RE.test(state.getIn(['compose', 'text'])),
+ directMessageWarning: state.getIn(['compose', 'privacy']) === 'direct',
+});
+
+const WarningWrapper = ({ needsLockWarning, hashtagWarning, directMessageWarning }) => {
+ if (needsLockWarning) {
+ return }} />} />;
+ }
+
+ if (hashtagWarning) {
+ return } />;
+ }
+
+ if (directMessageWarning) {
+ const message = (
+
+
+
+ );
+
+ return ;
+ }
+
+ return null;
+};
+
+WarningWrapper.propTypes = {
+ needsLockWarning: PropTypes.bool,
+ hashtagWarning: PropTypes.bool,
+ directMessageWarning: PropTypes.bool,
+};
+
+export default connect(mapStateToProps)(WarningWrapper);
diff --git a/app/javascript/flavours/glitch/features/composer/direct_warning/index.js b/app/javascript/flavours/glitch/features/composer/direct_warning/index.js
deleted file mode 100644
index 3b1369acd..000000000
--- a/app/javascript/flavours/glitch/features/composer/direct_warning/index.js
+++ /dev/null
@@ -1,55 +0,0 @@
-import React from 'react';
-import Motion from 'flavours/glitch/util/optional_motion';
-import spring from 'react-motion/lib/spring';
-import { defineMessages, FormattedMessage } from 'react-intl';
-import { termsLink} from 'flavours/glitch/util/backend_links';
-
-// This is the spring used with our motion.
-const motionSpring = spring(1, { damping: 35, stiffness: 400 });
-
-// Messages.
-const messages = defineMessages({
- disclaimer: {
- defaultMessage: 'This toot will only be sent to all the mentioned users.',
- id: 'compose_form.direct_message_warning',
- },
- learn_more: {
- defaultMessage: 'Learn more',
- id: 'compose_form.direct_message_warning_learn_more'
- }
-});
-
-// The component.
-export default function ComposerDirectWarning () {
- return (
-
- {({ opacity, scaleX, scaleY }) => (
-
-
-
- { termsLink !== undefined && }
-
-
- )}
-
- );
-}
-
-ComposerDirectWarning.propTypes = {};
diff --git a/app/javascript/flavours/glitch/features/composer/hashtag_warning/index.js b/app/javascript/flavours/glitch/features/composer/hashtag_warning/index.js
deleted file mode 100644
index 716028e4c..000000000
--- a/app/javascript/flavours/glitch/features/composer/hashtag_warning/index.js
+++ /dev/null
@@ -1,49 +0,0 @@
-import React from 'react';
-import Motion from 'flavours/glitch/util/optional_motion';
-import spring from 'react-motion/lib/spring';
-import { defineMessages, FormattedMessage } from 'react-intl';
-
-// This is the spring used with our motion.
-const motionSpring = spring(1, { damping: 35, stiffness: 400 });
-
-// Messages.
-const messages = defineMessages({
- disclaimer: {
- defaultMessage: 'This toot won\'t be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.',
- id: 'compose_form.hashtag_warning',
- },
-});
-
-// The component.
-export default function ComposerHashtagWarning () {
- return (
-
- {({ opacity, scaleX, scaleY }) => (
-
-
-
- )}
-
- );
-}
-
-ComposerHashtagWarning.propTypes = {};
diff --git a/app/javascript/flavours/glitch/features/composer/warning/index.js b/app/javascript/flavours/glitch/features/composer/warning/index.js
deleted file mode 100644
index 8be8acbce..000000000
--- a/app/javascript/flavours/glitch/features/composer/warning/index.js
+++ /dev/null
@@ -1,59 +0,0 @@
-import React from 'react';
-import Motion from 'flavours/glitch/util/optional_motion';
-import spring from 'react-motion/lib/spring';
-import { defineMessages, FormattedMessage } from 'react-intl';
-import { profileLink } from 'flavours/glitch/util/backend_links';
-
-// This is the spring used with our motion.
-const motionSpring = spring(1, { damping: 35, stiffness: 400 });
-
-// Messages.
-const messages = defineMessages({
- disclaimer: {
- defaultMessage: 'Your account is not {locked}. Anyone can follow you to view your follower-only posts.',
- id: 'compose_form.lock_disclaimer',
- },
- locked: {
- defaultMessage: 'locked',
- id: 'compose_form.lock_disclaimer.lock',
- },
-});
-
-// The component.
-export default function ComposerWarning () {
- let lockedLink = ;
- if (profileLink !== undefined) {
- lockedLink = {lockedLink} ;
- }
- return (
-
- {({ opacity, scaleX, scaleY }) => (
-
-
-
- )}
-
- );
-}
-
-ComposerWarning.propTypes = {};
--
cgit
From 8fd599fb40a5a078f26b5f450d88cf12609d9c14 Mon Sep 17 00:00:00 2001
From: Thibaut Girka
Date: Sat, 20 Apr 2019 22:21:28 +0200
Subject: ComposerReply → ReplyIndicator
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../features/compose/components/compose_form.js | 14 +---
.../features/compose/components/reply_indicator.js | 86 +++++++++++++++++++
.../compose/containers/compose_form_container.js | 7 --
.../containers/reply_indicator_container.js | 22 +++++
.../glitch/features/composer/reply/index.js | 96 ----------------------
5 files changed, 110 insertions(+), 115 deletions(-)
create mode 100644 app/javascript/flavours/glitch/features/compose/components/reply_indicator.js
create mode 100644 app/javascript/flavours/glitch/features/compose/containers/reply_indicator_container.js
delete mode 100644 app/javascript/flavours/glitch/features/composer/reply/index.js
diff --git a/app/javascript/flavours/glitch/features/compose/components/compose_form.js b/app/javascript/flavours/glitch/features/compose/components/compose_form.js
index 1f37a1da5..10b51d920 100644
--- a/app/javascript/flavours/glitch/features/compose/components/compose_form.js
+++ b/app/javascript/flavours/glitch/features/compose/components/compose_form.js
@@ -7,12 +7,12 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
// Components.
import ComposerOptions from '../../composer/options';
import ComposerPublisher from '../../composer/publisher';
-import ComposerReply from '../../composer/reply';
import ComposerSpoiler from '../../composer/spoiler';
import ComposerTextarea from '../../composer/textarea';
import ComposerUploadForm from '../../composer/upload_form';
import ComposerPollForm from '../../composer/poll_form';
import WarningContainer from '../containers/warning_container';
+import ReplyIndicatorContainer from '../containers/reply_indicator_container';
// Utils.
import { countableText } from 'flavours/glitch/util/counter';
@@ -49,7 +49,6 @@ class ComposeForm extends ImmutablePureComponent {
preselectDate: PropTypes.instanceOf(Date),
privacy: PropTypes.string,
progress: PropTypes.number,
- inReplyTo: ImmutablePropTypes.map,
resetFileKey: PropTypes.number,
sideArm: PropTypes.string,
sensitive: PropTypes.bool,
@@ -65,7 +64,6 @@ class ComposeForm extends ImmutablePureComponent {
preselectOnReply: PropTypes.bool,
// Dispatch props.
- onCancelReply: PropTypes.func,
onChangeAdvancedOption: PropTypes.func,
onChangeDescription: PropTypes.func,
onChangeSensitivity: PropTypes.func,
@@ -283,7 +281,6 @@ class ComposeForm extends ImmutablePureComponent {
layout,
media,
poll,
- onCancelReply,
onChangeAdvancedOption,
onChangeDescription,
onChangeSensitivity,
@@ -301,7 +298,6 @@ class ComposeForm extends ImmutablePureComponent {
onUpload,
privacy,
progress,
- inReplyTo,
resetFileKey,
sensitive,
showSearch,
@@ -319,13 +315,7 @@ class ComposeForm extends ImmutablePureComponent {
- {inReplyTo && (
-
- )}
+
{
+ const { onCancel } = this.props;
+ if (onCancel) {
+ onCancel();
+ }
+ }
+
+ // Rendering.
+ render () {
+ const { status, intl } = this.props;
+
+ if (!status) {
+ return null;
+ }
+
+ const account = status.get('account');
+ const content = status.get('content');
+ const attachments = status.get('media_attachments');
+
+ // The result.
+ return (
+
+
+
+ {account && (
+
+ )}
+
+
+ {attachments.size > 0 && (
+
+ )}
+
+ );
+ }
+
+}
diff --git a/app/javascript/flavours/glitch/features/compose/containers/compose_form_container.js b/app/javascript/flavours/glitch/features/compose/containers/compose_form_container.js
index 18fc31dce..3293cc226 100644
--- a/app/javascript/flavours/glitch/features/compose/containers/compose_form_container.js
+++ b/app/javascript/flavours/glitch/features/compose/containers/compose_form_container.js
@@ -1,7 +1,6 @@
import { connect } from 'react-redux';
import ComposeForm from '../components/compose_form';
import {
- cancelReplyCompose,
changeCompose,
changeComposeAdvancedOption,
changeComposeSensitivity,
@@ -68,9 +67,6 @@ function mapStateToProps (state) {
preselectDate: state.getIn(['compose', 'preselectDate']),
privacy: state.getIn(['compose', 'privacy']),
progress: state.getIn(['compose', 'progress']),
- inReplyTo: inReplyTo ? state.getIn(['statuses', inReplyTo]) : null,
- replyAccount: inReplyTo ? state.getIn(['statuses', inReplyTo, 'account']) : null,
- replyContent: inReplyTo ? state.getIn(['statuses', inReplyTo, 'contentHtml']) : null,
resetFileKey: state.getIn(['compose', 'resetFileKey']),
sideArm: sideArmPrivacy,
sensitive: state.getIn(['compose', 'sensitive']),
@@ -90,9 +86,6 @@ function mapStateToProps (state) {
// Dispatch mapping.
const mapDispatchToProps = (dispatch, { intl }) => ({
- onCancelReply() {
- dispatch(cancelReplyCompose());
- },
onChangeAdvancedOption(option, value) {
dispatch(changeComposeAdvancedOption(option, value));
},
diff --git a/app/javascript/flavours/glitch/features/compose/containers/reply_indicator_container.js b/app/javascript/flavours/glitch/features/compose/containers/reply_indicator_container.js
new file mode 100644
index 000000000..395a9aa5b
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/compose/containers/reply_indicator_container.js
@@ -0,0 +1,22 @@
+import { connect } from 'react-redux';
+import { cancelReplyCompose } from 'flavours/glitch/actions/compose';
+import { makeGetStatus } from 'flavours/glitch/selectors';
+import ReplyIndicator from '../components/reply_indicator';
+
+function makeMapStateToProps (state) {
+ const inReplyTo = state.getIn(['compose', 'in_reply_to']);
+
+ return {
+ status: inReplyTo ? state.getIn(['statuses', inReplyTo]) : null,
+ };
+};
+
+const mapDispatchToProps = dispatch => ({
+
+ onCancel () {
+ dispatch(cancelReplyCompose());
+ },
+
+});
+
+export default connect(makeMapStateToProps, mapDispatchToProps)(ReplyIndicator);
diff --git a/app/javascript/flavours/glitch/features/composer/reply/index.js b/app/javascript/flavours/glitch/features/composer/reply/index.js
deleted file mode 100644
index 56e9e96a5..000000000
--- a/app/javascript/flavours/glitch/features/composer/reply/index.js
+++ /dev/null
@@ -1,96 +0,0 @@
-// Package imports.
-import PropTypes from 'prop-types';
-import React from 'react';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import { defineMessages } from 'react-intl';
-
-// Components.
-import AccountContainer from 'flavours/glitch/containers/account_container';
-import IconButton from 'flavours/glitch/components/icon_button';
-import AttachmentList from 'flavours/glitch/components/attachment_list';
-
-// Utils.
-import { assignHandlers } from 'flavours/glitch/util/react_helpers';
-import { isRtl } from 'flavours/glitch/util/rtl';
-
-// Messages.
-const messages = defineMessages({
- cancel: {
- defaultMessage: 'Cancel',
- id: 'reply_indicator.cancel',
- },
-});
-
-// Handlers.
-const handlers = {
-
- // Handles a click on the "close" button.
- handleClick () {
- const { onCancel } = this.props;
- if (onCancel) {
- onCancel();
- }
- },
-};
-
-// The component.
-export default class ComposerReply extends React.PureComponent {
-
- // Constructor.
- constructor (props) {
- super(props);
- assignHandlers(this, handlers);
- }
-
- // Rendering.
- render () {
- const { handleClick } = this.handlers;
- const {
- status,
- intl,
- } = this.props;
-
- const account = status.get('account');
- const content = status.get('content');
- const attachments = status.get('media_attachments');
-
- // The result.
- return (
-
-
-
- {account && (
-
- )}
-
-
- {attachments.size > 0 && (
-
- )}
-
- );
- }
-
-}
-
-ComposerReply.propTypes = {
- status: ImmutablePropTypes.map.isRequired,
- intl: PropTypes.object.isRequired,
- onCancel: PropTypes.func,
-};
--
cgit
From f1a22e33e26f124cb1b3131e56678001b9e43bc3 Mon Sep 17 00:00:00 2001
From: Thibaut Girka
Date: Sat, 20 Apr 2019 23:02:09 +0200
Subject: Inline spoiler input
---
.../features/compose/components/compose_form.js | 51 +++++++---
.../glitch/features/composer/spoiler/index.js | 107 ---------------------
2 files changed, 38 insertions(+), 120 deletions(-)
delete mode 100644 app/javascript/flavours/glitch/features/composer/spoiler/index.js
diff --git a/app/javascript/flavours/glitch/features/compose/components/compose_form.js b/app/javascript/flavours/glitch/features/compose/components/compose_form.js
index 10b51d920..fdc4401f6 100644
--- a/app/javascript/flavours/glitch/features/compose/components/compose_form.js
+++ b/app/javascript/flavours/glitch/features/compose/components/compose_form.js
@@ -7,7 +7,6 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
// Components.
import ComposerOptions from '../../composer/options';
import ComposerPublisher from '../../composer/publisher';
-import ComposerSpoiler from '../../composer/spoiler';
import ComposerTextarea from '../../composer/textarea';
import ComposerUploadForm from '../../composer/upload_form';
import ComposerPollForm from '../../composer/poll_form';
@@ -23,6 +22,7 @@ const messages = defineMessages({
defaultMessage: 'At least one media attachment is lacking a description. Consider describing all media attachments for the visually impaired before sending your toot.' },
missingDescriptionConfirm: { id: 'confirmations.missing_media_description.confirm',
defaultMessage: 'Send anyway' },
+ spoiler_placeholder: { id: 'compose_form.spoiler_placeholder', defaultMessage: 'Write your warning here' },
});
export default @injectIntl
@@ -124,6 +124,25 @@ class ComposeForm extends ImmutablePureComponent {
}
}
+ handleKeyDown = ({ ctrlKey, keyCode, metaKey, altKey }) => {
+ // We submit the status on control/meta + enter.
+ if (keyCode === 13 && (ctrlKey || metaKey)) {
+ handleSubmit();
+ }
+
+ // Submit the status with secondary visibility on alt + enter.
+ if (keyCode === 13 && altKey) {
+ handleSecondarySubmit();
+ }
+ }
+
+ // When the escape key is released, we focus the UI.
+ handleKeyUp = ({ key }) => {
+ if (key === 'Escape') {
+ document.querySelector('.ui').parentElement.focus();
+ }
+ }
+
// Submits the status.
handleSubmit = () => {
const { textarea: { value }, uploadForm } = this;
@@ -181,7 +200,7 @@ class ComposeForm extends ImmutablePureComponent {
// Sets a reference to the CW field.
handleRefSpoilerText = (spoilerComponent) => {
if (spoilerComponent) {
- this.spoilerText = spoilerComponent.spoilerText;
+ this.spoilerText = spoilerComponent;
}
}
@@ -260,14 +279,12 @@ class ComposeForm extends ImmutablePureComponent {
render () {
const {
- handleChangeSpoiler,
handleEmoji,
handleSecondarySubmit,
handleSelect,
handleSubmit,
handleRefUploadForm,
handleRefTextarea,
- handleRefSpoilerText,
} = this;
const {
acceptContentTypes,
@@ -317,15 +334,23 @@ class ComposeForm extends ImmutablePureComponent {
-
+
+
+ {intl.formatMessage(messages.spoiler_placeholder)}
+
+
+
-
-
-
-
-
-
-
- );
- }
-
-}
-
-// Props.
-ComposerSpoiler.propTypes = {
- hidden: PropTypes.bool,
- intl: PropTypes.object.isRequired,
- onChange: PropTypes.func,
- onSubmit: PropTypes.func,
- onSecondarySubmit: PropTypes.func,
- text: PropTypes.string,
-};
--
cgit
From c5f49a92dce9157debf3a68487dd30b6f0af6c4a Mon Sep 17 00:00:00 2001
From: Thibaut Girka
Date: Sat, 20 Apr 2019 23:27:20 +0200
Subject: Move PollForm from features/composer to features/compose
---
.../features/compose/components/compose_form.js | 6 +-
.../features/compose/components/poll_form.js | 135 +++++++++++++++++++++
.../compose/containers/poll_form_container.js | 29 +++++
.../composer/poll_form/components/poll_form.js | 135 ---------------------
.../glitch/features/composer/poll_form/index.js | 29 -----
5 files changed, 166 insertions(+), 168 deletions(-)
create mode 100644 app/javascript/flavours/glitch/features/compose/components/poll_form.js
create mode 100644 app/javascript/flavours/glitch/features/compose/containers/poll_form_container.js
delete mode 100644 app/javascript/flavours/glitch/features/composer/poll_form/components/poll_form.js
delete mode 100644 app/javascript/flavours/glitch/features/composer/poll_form/index.js
diff --git a/app/javascript/flavours/glitch/features/compose/components/compose_form.js b/app/javascript/flavours/glitch/features/compose/components/compose_form.js
index fdc4401f6..ccbcba571 100644
--- a/app/javascript/flavours/glitch/features/compose/components/compose_form.js
+++ b/app/javascript/flavours/glitch/features/compose/components/compose_form.js
@@ -9,7 +9,7 @@ import ComposerOptions from '../../composer/options';
import ComposerPublisher from '../../composer/publisher';
import ComposerTextarea from '../../composer/textarea';
import ComposerUploadForm from '../../composer/upload_form';
-import ComposerPollForm from '../../composer/poll_form';
+import PollFormContainer from '../containers/poll_form_container';
import WarningContainer from '../containers/warning_container';
import ReplyIndicatorContainer from '../containers/reply_indicator_container';
@@ -382,9 +382,7 @@ class ComposeForm extends ImmutablePureComponent {
handleRef={handleRefUploadForm}
/>
) : null}
- {!!poll && (
-
- )}
+
{
+ this.props.onChange(this.props.index, e.target.value);
+ };
+
+ handleOptionRemove = () => {
+ this.props.onRemove(this.props.index);
+ };
+
+ render () {
+ const { isPollMultiple, title, index, intl } = this.props;
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ );
+ }
+
+}
+
+export default
+@injectIntl
+class PollForm extends ImmutablePureComponent {
+
+ static propTypes = {
+ options: ImmutablePropTypes.list,
+ expiresIn: PropTypes.number,
+ isMultiple: PropTypes.bool,
+ onChangeOption: PropTypes.func.isRequired,
+ onAddOption: PropTypes.func.isRequired,
+ onRemoveOption: PropTypes.func.isRequired,
+ onChangeSettings: PropTypes.func.isRequired,
+ intl: PropTypes.object.isRequired,
+ };
+
+ handleAddOption = () => {
+ this.props.onAddOption('');
+ };
+
+ handleSelectDuration = e => {
+ this.props.onChangeSettings(e.target.value, this.props.isMultiple);
+ };
+
+ handleSelectMultiple = e => {
+ this.props.onChangeSettings(this.props.expiresIn, e.target.value === 'true');
+ };
+
+ render () {
+ const { options, expiresIn, isMultiple, onChangeOption, onRemoveOption, intl } = this.props;
+
+ if (!options) {
+ return null;
+ }
+
+ return (
+
+
+ {options.map((title, i) => )}
+ {options.size < pollLimits.max_options && (
+
+
+
+
+ )}
+
+
+
+
+ {intl.formatMessage(messages.single_choice)}
+ {intl.formatMessage(messages.multiple_choices)}
+
+
+
+ {intl.formatMessage(messages.minutes, { number: 5 })}
+ {intl.formatMessage(messages.minutes, { number: 30 })}
+ {intl.formatMessage(messages.hours, { number: 1 })}
+ {intl.formatMessage(messages.hours, { number: 6 })}
+ {intl.formatMessage(messages.days, { number: 1 })}
+ {intl.formatMessage(messages.days, { number: 3 })}
+ {intl.formatMessage(messages.days, { number: 7 })}
+
+
+
+ );
+ }
+
+}
diff --git a/app/javascript/flavours/glitch/features/compose/containers/poll_form_container.js b/app/javascript/flavours/glitch/features/compose/containers/poll_form_container.js
new file mode 100644
index 000000000..01df024c8
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/compose/containers/poll_form_container.js
@@ -0,0 +1,29 @@
+import { connect } from 'react-redux';
+import PollForm from '../components/poll_form';
+import { addPollOption, removePollOption, changePollOption, changePollSettings } from 'flavours/glitch/actions/compose';
+
+const mapStateToProps = state => ({
+ options: state.getIn(['compose', 'poll', 'options']),
+ expiresIn: state.getIn(['compose', 'poll', 'expires_in']),
+ isMultiple: state.getIn(['compose', 'poll', 'multiple']),
+});
+
+const mapDispatchToProps = dispatch => ({
+ onAddOption(title) {
+ dispatch(addPollOption(title));
+ },
+
+ onRemoveOption(index) {
+ dispatch(removePollOption(index));
+ },
+
+ onChangeOption(index, title) {
+ dispatch(changePollOption(index, title));
+ },
+
+ onChangeSettings(expiresIn, isMultiple) {
+ dispatch(changePollSettings(expiresIn, isMultiple));
+ },
+});
+
+export default connect(mapStateToProps, mapDispatchToProps)(PollForm);
diff --git a/app/javascript/flavours/glitch/features/composer/poll_form/components/poll_form.js b/app/javascript/flavours/glitch/features/composer/poll_form/components/poll_form.js
deleted file mode 100644
index 1915b62d5..000000000
--- a/app/javascript/flavours/glitch/features/composer/poll_form/components/poll_form.js
+++ /dev/null
@@ -1,135 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import ImmutablePureComponent from 'react-immutable-pure-component';
-import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
-import IconButton from 'flavours/glitch/components/icon_button';
-import Icon from 'flavours/glitch/components/icon';
-import classNames from 'classnames';
-import { pollLimits } from 'flavours/glitch/util/initial_state';
-
-const messages = defineMessages({
- option_placeholder: { id: 'compose_form.poll.option_placeholder', defaultMessage: 'Choice {number}' },
- add_option: { id: 'compose_form.poll.add_option', defaultMessage: 'Add a choice' },
- remove_option: { id: 'compose_form.poll.remove_option', defaultMessage: 'Remove this choice' },
- poll_duration: { id: 'compose_form.poll.duration', defaultMessage: 'Poll duration' },
- single_choice: { id: 'compose_form.poll.single_choice', defaultMessage: 'Allow one choice' },
- multiple_choices: { id: 'compose_form.poll.multiple_choices', defaultMessage: 'Allow multiple choices' },
- minutes: { id: 'intervals.full.minutes', defaultMessage: '{number, plural, one {# minute} other {# minutes}}' },
- hours: { id: 'intervals.full.hours', defaultMessage: '{number, plural, one {# hour} other {# hours}}' },
- days: { id: 'intervals.full.days', defaultMessage: '{number, plural, one {# day} other {# days}}' },
-});
-
-@injectIntl
-class Option extends React.PureComponent {
-
- static propTypes = {
- title: PropTypes.string.isRequired,
- index: PropTypes.number.isRequired,
- isPollMultiple: PropTypes.bool,
- onChange: PropTypes.func.isRequired,
- onRemove: PropTypes.func.isRequired,
- intl: PropTypes.object.isRequired,
- };
-
- handleOptionTitleChange = e => {
- this.props.onChange(this.props.index, e.target.value);
- };
-
- handleOptionRemove = () => {
- this.props.onRemove(this.props.index);
- };
-
- render () {
- const { isPollMultiple, title, index, intl } = this.props;
-
- return (
-
-
-
-
-
-
-
-
-
-
-
- );
- }
-
-}
-
-export default
-@injectIntl
-class PollForm extends ImmutablePureComponent {
-
- static propTypes = {
- options: ImmutablePropTypes.list,
- expiresIn: PropTypes.number,
- isMultiple: PropTypes.bool,
- onChangeOption: PropTypes.func.isRequired,
- onAddOption: PropTypes.func.isRequired,
- onRemoveOption: PropTypes.func.isRequired,
- onChangeSettings: PropTypes.func.isRequired,
- intl: PropTypes.object.isRequired,
- };
-
- handleAddOption = () => {
- this.props.onAddOption('');
- };
-
- handleSelectDuration = e => {
- this.props.onChangeSettings(e.target.value, this.props.isMultiple);
- };
-
- handleSelectMultiple = e => {
- this.props.onChangeSettings(this.props.expiresIn, e.target.value === 'true');
- };
-
- render () {
- const { options, expiresIn, isMultiple, onChangeOption, onRemoveOption, intl } = this.props;
-
- if (!options) {
- return null;
- }
-
- return (
-
-
- {options.map((title, i) => )}
- {options.size < pollLimits.max_options && (
-
-
-
-
- )}
-
-
-
-
- {intl.formatMessage(messages.single_choice)}
- {intl.formatMessage(messages.multiple_choices)}
-
-
-
- {intl.formatMessage(messages.minutes, { number: 5 })}
- {intl.formatMessage(messages.minutes, { number: 30 })}
- {intl.formatMessage(messages.hours, { number: 1 })}
- {intl.formatMessage(messages.hours, { number: 6 })}
- {intl.formatMessage(messages.days, { number: 1 })}
- {intl.formatMessage(messages.days, { number: 3 })}
- {intl.formatMessage(messages.days, { number: 7 })}
-
-
-
- );
- }
-
-}
diff --git a/app/javascript/flavours/glitch/features/composer/poll_form/index.js b/app/javascript/flavours/glitch/features/composer/poll_form/index.js
deleted file mode 100644
index 5232c3b31..000000000
--- a/app/javascript/flavours/glitch/features/composer/poll_form/index.js
+++ /dev/null
@@ -1,29 +0,0 @@
-import { connect } from 'react-redux';
-import PollForm from './components/poll_form';
-import { addPollOption, removePollOption, changePollOption, changePollSettings } from '../../../actions/compose';
-
-const mapStateToProps = state => ({
- options: state.getIn(['compose', 'poll', 'options']),
- expiresIn: state.getIn(['compose', 'poll', 'expires_in']),
- isMultiple: state.getIn(['compose', 'poll', 'multiple']),
-});
-
-const mapDispatchToProps = dispatch => ({
- onAddOption(title) {
- dispatch(addPollOption(title));
- },
-
- onRemoveOption(index) {
- dispatch(removePollOption(index));
- },
-
- onChangeOption(index, title) {
- dispatch(changePollOption(index, title));
- },
-
- onChangeSettings(expiresIn, isMultiple) {
- dispatch(changePollSettings(expiresIn, isMultiple));
- },
-});
-
-export default connect(mapStateToProps, mapDispatchToProps)(PollForm);
--
cgit
From a243567a3e6100d65477162308e2c1bb5e056c21 Mon Sep 17 00:00:00 2001
From: Thibaut Girka
Date: Sun, 21 Apr 2019 12:09:52 +0200
Subject: ComposerUploadForm → UploadForm + UploadFormContainer
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../features/compose/components/compose_form.js | 28 +--
.../glitch/features/compose/components/upload.js | 131 +++++++++++++
.../features/compose/components/upload_form.js | 28 +++
.../features/compose/components/upload_progress.js | 42 +++++
.../compose/containers/compose_form_container.js | 12 --
.../compose/containers/upload_container.js | 31 ++++
.../compose/containers/upload_form_container.js | 8 +
.../containers/upload_progress_container.js | 9 +
.../glitch/features/composer/upload_form/index.js | 60 ------
.../features/composer/upload_form/item/index.js | 202 ---------------------
.../composer/upload_form/progress/index.js | 52 ------
11 files changed, 251 insertions(+), 352 deletions(-)
create mode 100644 app/javascript/flavours/glitch/features/compose/components/upload.js
create mode 100644 app/javascript/flavours/glitch/features/compose/components/upload_form.js
create mode 100644 app/javascript/flavours/glitch/features/compose/components/upload_progress.js
create mode 100644 app/javascript/flavours/glitch/features/compose/containers/upload_container.js
create mode 100644 app/javascript/flavours/glitch/features/compose/containers/upload_form_container.js
create mode 100644 app/javascript/flavours/glitch/features/compose/containers/upload_progress_container.js
delete mode 100644 app/javascript/flavours/glitch/features/composer/upload_form/index.js
delete mode 100644 app/javascript/flavours/glitch/features/composer/upload_form/item/index.js
delete mode 100644 app/javascript/flavours/glitch/features/composer/upload_form/progress/index.js
diff --git a/app/javascript/flavours/glitch/features/compose/components/compose_form.js b/app/javascript/flavours/glitch/features/compose/components/compose_form.js
index ccbcba571..ecd1aed69 100644
--- a/app/javascript/flavours/glitch/features/compose/components/compose_form.js
+++ b/app/javascript/flavours/glitch/features/compose/components/compose_form.js
@@ -8,7 +8,7 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
import ComposerOptions from '../../composer/options';
import ComposerPublisher from '../../composer/publisher';
import ComposerTextarea from '../../composer/textarea';
-import ComposerUploadForm from '../../composer/upload_form';
+import UploadFormContainer from '../containers/upload_form_container';
import PollFormContainer from '../containers/poll_form_container';
import WarningContainer from '../containers/warning_container';
import ReplyIndicatorContainer from '../containers/reply_indicator_container';
@@ -48,7 +48,6 @@ class ComposeForm extends ImmutablePureComponent {
media: ImmutablePropTypes.list,
preselectDate: PropTypes.instanceOf(Date),
privacy: PropTypes.string,
- progress: PropTypes.number,
resetFileKey: PropTypes.number,
sideArm: PropTypes.string,
sensitive: PropTypes.bool,
@@ -65,7 +64,6 @@ class ComposeForm extends ImmutablePureComponent {
// Dispatch props.
onChangeAdvancedOption: PropTypes.func,
- onChangeDescription: PropTypes.func,
onChangeSensitivity: PropTypes.func,
onChangeSpoilerText: PropTypes.func,
onChangeSpoilerness: PropTypes.func,
@@ -80,7 +78,6 @@ class ComposeForm extends ImmutablePureComponent {
onOpenDoodleModal: PropTypes.func,
onSelectSuggestion: PropTypes.func,
onSubmit: PropTypes.func,
- onUndoUpload: PropTypes.func,
onUnmount: PropTypes.func,
onUpload: PropTypes.func,
onMediaDescriptionConfirm: PropTypes.func,
@@ -185,11 +182,6 @@ class ComposeForm extends ImmutablePureComponent {
}
}
- // Sets a reference to the upload form.
- handleRefUploadForm = (uploadFormComponent) => {
- this.uploadForm = uploadFormComponent;
- }
-
// Sets a reference to the textarea.
handleRefTextarea = (textareaComponent) => {
if (textareaComponent) {
@@ -283,7 +275,6 @@ class ComposeForm extends ImmutablePureComponent {
handleSecondarySubmit,
handleSelect,
handleSubmit,
- handleRefUploadForm,
handleRefTextarea,
} = this;
const {
@@ -299,7 +290,6 @@ class ComposeForm extends ImmutablePureComponent {
media,
poll,
onChangeAdvancedOption,
- onChangeDescription,
onChangeSensitivity,
onChangeSpoilerness,
onChangeText,
@@ -310,11 +300,8 @@ class ComposeForm extends ImmutablePureComponent {
onFetchSuggestions,
onOpenActionsModal,
onOpenDoodleModal,
- onOpenFocalPointModal,
- onUndoUpload,
onUpload,
privacy,
- progress,
resetFileKey,
sensitive,
showSearch,
@@ -370,18 +357,7 @@ class ComposeForm extends ImmutablePureComponent {
value={text}
/>
- {isUploading || media && media.size ? (
-
- ) : null}
+
{
+ if (e.keyCode === 13 && (e.ctrlKey || e.metaKey)) {
+ this.handleSubmit();
+ }
+ }
+
+ handleSubmit = () => {
+ this.handleInputBlur();
+ this.props.onSubmit(this.context.router.history);
+ }
+
+ handleUndoClick = e => {
+ e.stopPropagation();
+ this.props.onUndo(this.props.media.get('id'));
+ }
+
+ handleFocalPointClick = e => {
+ e.stopPropagation();
+ this.props.onOpenFocalPoint(this.props.media.get('id'));
+ }
+
+ handleInputChange = e => {
+ this.setState({ dirtyDescription: e.target.value });
+ }
+
+ handleMouseEnter = () => {
+ this.setState({ hovered: true });
+ }
+
+ handleMouseLeave = () => {
+ this.setState({ hovered: false });
+ }
+
+ handleInputFocus = () => {
+ this.setState({ focused: true });
+ }
+
+ handleClick = () => {
+ this.setState({ focused: true });
+ }
+
+ handleInputBlur = () => {
+ const { dirtyDescription } = this.state;
+
+ this.setState({ focused: false, dirtyDescription: null });
+
+ if (dirtyDescription !== null) {
+ this.props.onDescriptionChange(this.props.media.get('id'), dirtyDescription);
+ }
+ }
+
+ render () {
+ const { intl, media } = this.props;
+ const active = this.state.hovered || this.state.focused || isUserTouching();
+ const description = this.state.dirtyDescription || (this.state.dirtyDescription !== '' && media.get('description')) || '';
+ const computedClass = classNames('composer--upload_form--item', { active });
+ const focusX = media.getIn(['meta', 'focus', 'x']);
+ const focusY = media.getIn(['meta', 'focus', 'y']);
+ const x = ((focusX / 2) + .5) * 100;
+ const y = ((focusY / -2) + .5) * 100;
+
+ return (
+
+
+ {({ scale }) => (
+
+
+
+ {media.get('type') === 'image' && }
+
+
+
+
+ {intl.formatMessage(messages.description)}
+
+
+
+
+ )}
+
+
+ );
+ }
+
+}
diff --git a/app/javascript/flavours/glitch/features/compose/components/upload_form.js b/app/javascript/flavours/glitch/features/compose/components/upload_form.js
new file mode 100644
index 000000000..a126cc7e4
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/compose/components/upload_form.js
@@ -0,0 +1,28 @@
+import React from 'react';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import UploadProgressContainer from '../containers/upload_progress_container';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+import UploadContainer from '../containers/upload_container';
+
+export default class UploadForm extends ImmutablePureComponent {
+ static propTypes = {
+ mediaIds: ImmutablePropTypes.list.isRequired,
+ };
+
+ render () {
+ const { mediaIds } = this.props;
+
+ return (
+
+
+
+
+ {mediaIds.map(id => (
+
+ ))}
+
+
+ );
+ }
+
+}
diff --git a/app/javascript/flavours/glitch/features/compose/components/upload_progress.js b/app/javascript/flavours/glitch/features/compose/components/upload_progress.js
new file mode 100644
index 000000000..264c563f2
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/compose/components/upload_progress.js
@@ -0,0 +1,42 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import Motion from 'flavours/glitch/util/optional_motion';
+import spring from 'react-motion/lib/spring';
+import { FormattedMessage } from 'react-intl';
+import Icon from 'flavours/glitch/components/icon';
+
+export default class UploadProgress extends React.PureComponent {
+
+ static propTypes = {
+ active: PropTypes.bool,
+ progress: PropTypes.number,
+ };
+
+ render () {
+ const { active, progress } = this.props;
+
+ if (!active) {
+ return null;
+ }
+
+ return (
+
+
+
+
+
+
+
+
+ {({ width }) =>
+ (
)
+ }
+
+
+
+
+ );
+ }
+
+}
diff --git a/app/javascript/flavours/glitch/features/compose/containers/compose_form_container.js b/app/javascript/flavours/glitch/features/compose/containers/compose_form_container.js
index 3293cc226..4716d9435 100644
--- a/app/javascript/flavours/glitch/features/compose/containers/compose_form_container.js
+++ b/app/javascript/flavours/glitch/features/compose/containers/compose_form_container.js
@@ -7,14 +7,12 @@ import {
changeComposeSpoilerText,
changeComposeSpoilerness,
changeComposeVisibility,
- changeUploadCompose,
clearComposeSuggestions,
fetchComposeSuggestions,
insertEmojiCompose,
mountCompose,
selectComposeSuggestion,
submitCompose,
- undoUploadCompose,
unmountCompose,
uploadCompose,
} from 'flavours/glitch/actions/compose';
@@ -66,7 +64,6 @@ function mapStateToProps (state) {
media: state.getIn(['compose', 'media_attachments']),
preselectDate: state.getIn(['compose', 'preselectDate']),
privacy: state.getIn(['compose', 'privacy']),
- progress: state.getIn(['compose', 'progress']),
resetFileKey: state.getIn(['compose', 'resetFileKey']),
sideArm: sideArmPrivacy,
sensitive: state.getIn(['compose', 'sensitive']),
@@ -89,9 +86,6 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
onChangeAdvancedOption(option, value) {
dispatch(changeComposeAdvancedOption(option, value));
},
- onChangeDescription(id, description) {
- dispatch(changeUploadCompose(id, { description }));
- },
onChangeSensitivity() {
dispatch(changeComposeSensitivity());
},
@@ -137,9 +131,6 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
onOpenDoodleModal() {
dispatch(openModal('DOODLE', { noEsc: true }));
},
- onOpenFocalPointModal(id) {
- dispatch(openModal('FOCAL_POINT', { id }));
- },
onSelectSuggestion(position, token, suggestion) {
dispatch(selectComposeSuggestion(position, token, suggestion));
},
@@ -154,9 +145,6 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
onSubmit(routerHistory) {
dispatch(submitCompose(routerHistory));
},
- onUndoUpload(id) {
- dispatch(undoUploadCompose(id));
- },
onUnmount() {
dispatch(unmountCompose());
},
diff --git a/app/javascript/flavours/glitch/features/compose/containers/upload_container.js b/app/javascript/flavours/glitch/features/compose/containers/upload_container.js
new file mode 100644
index 000000000..d6bff63ac
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/compose/containers/upload_container.js
@@ -0,0 +1,31 @@
+import { connect } from 'react-redux';
+import Upload from '../components/upload';
+import { undoUploadCompose, changeUploadCompose } from 'flavours/glitch/actions/compose';
+import { openModal } from 'flavours/glitch/actions/modal';
+import { submitCompose } from 'flavours/glitch/actions/compose';
+
+const mapStateToProps = (state, { id }) => ({
+ media: state.getIn(['compose', 'media_attachments']).find(item => item.get('id') === id),
+});
+
+const mapDispatchToProps = dispatch => ({
+
+ onUndo: id => {
+ dispatch(undoUploadCompose(id));
+ },
+
+ onDescriptionChange: (id, description) => {
+ dispatch(changeUploadCompose(id, { description }));
+ },
+
+ onOpenFocalPoint: id => {
+ dispatch(openModal('FOCAL_POINT', { id }));
+ },
+
+ onSubmit (router) {
+ dispatch(submitCompose(router));
+ },
+
+});
+
+export default connect(mapStateToProps, mapDispatchToProps)(Upload);
diff --git a/app/javascript/flavours/glitch/features/compose/containers/upload_form_container.js b/app/javascript/flavours/glitch/features/compose/containers/upload_form_container.js
new file mode 100644
index 000000000..a6798bf51
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/compose/containers/upload_form_container.js
@@ -0,0 +1,8 @@
+import { connect } from 'react-redux';
+import UploadForm from '../components/upload_form';
+
+const mapStateToProps = state => ({
+ mediaIds: state.getIn(['compose', 'media_attachments']).map(item => item.get('id')),
+});
+
+export default connect(mapStateToProps)(UploadForm);
diff --git a/app/javascript/flavours/glitch/features/compose/containers/upload_progress_container.js b/app/javascript/flavours/glitch/features/compose/containers/upload_progress_container.js
new file mode 100644
index 000000000..0cfee96da
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/compose/containers/upload_progress_container.js
@@ -0,0 +1,9 @@
+import { connect } from 'react-redux';
+import UploadProgress from '../components/upload_progress';
+
+const mapStateToProps = state => ({
+ active: state.getIn(['compose', 'is_uploading']),
+ progress: state.getIn(['compose', 'progress']),
+});
+
+export default connect(mapStateToProps)(UploadProgress);
diff --git a/app/javascript/flavours/glitch/features/composer/upload_form/index.js b/app/javascript/flavours/glitch/features/composer/upload_form/index.js
deleted file mode 100644
index c2ff66623..000000000
--- a/app/javascript/flavours/glitch/features/composer/upload_form/index.js
+++ /dev/null
@@ -1,60 +0,0 @@
-// Package imports.
-import classNames from 'classnames';
-import PropTypes from 'prop-types';
-import React from 'react';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-
-// Components.
-import ComposerUploadFormItem from './item';
-import ComposerUploadFormProgress from './progress';
-
-// The component.
-export default function ComposerUploadForm ({
- intl,
- media,
- onChangeDescription,
- onOpenFocalPointModal,
- onRemove,
- progress,
- uploading,
- handleRef,
-}) {
- const computedClass = classNames('composer--upload_form', { uploading });
-
- // The result.
- return (
-
- {uploading ?
: null}
- {media ? (
-
- {media.map(item => (
-
- ))}
-
- ) : null}
-
- );
-}
-
-// Props.
-ComposerUploadForm.propTypes = {
- intl: PropTypes.object.isRequired,
- media: ImmutablePropTypes.list,
- onChangeDescription: PropTypes.func.isRequired,
- onRemove: PropTypes.func.isRequired,
- progress: PropTypes.number,
- uploading: PropTypes.bool,
- handleRef: PropTypes.func,
-};
diff --git a/app/javascript/flavours/glitch/features/composer/upload_form/item/index.js b/app/javascript/flavours/glitch/features/composer/upload_form/item/index.js
deleted file mode 100644
index 4f5f66f04..000000000
--- a/app/javascript/flavours/glitch/features/composer/upload_form/item/index.js
+++ /dev/null
@@ -1,202 +0,0 @@
-// Package imports.
-import classNames from 'classnames';
-import PropTypes from 'prop-types';
-import React from 'react';
-import {
- FormattedMessage,
- defineMessages,
-} from 'react-intl';
-import spring from 'react-motion/lib/spring';
-
-// Components.
-import IconButton from 'flavours/glitch/components/icon_button';
-
-// Utils.
-import Motion from 'flavours/glitch/util/optional_motion';
-import { assignHandlers } from 'flavours/glitch/util/react_helpers';
-import { isUserTouching } from 'flavours/glitch/util/is_mobile';
-
-// Messages.
-const messages = defineMessages({
- undo: {
- defaultMessage: 'Undo',
- id: 'upload_form.undo',
- },
- description: {
- defaultMessage: 'Describe for the visually impaired',
- id: 'upload_form.description',
- },
- crop: {
- defaultMessage: 'Crop',
- id: 'upload_form.focus',
- },
-});
-
-// Handlers.
-const handlers = {
-
- // On blur, we save the description for the media item.
- handleBlur () {
- const {
- id,
- onChangeDescription,
- } = this.props;
- const { dirtyDescription } = this.state;
-
- this.setState({ dirtyDescription: null, focused: false });
-
- if (id && onChangeDescription && dirtyDescription !== null) {
- onChangeDescription(id, dirtyDescription);
- }
- },
-
- // When the value of our description changes, we store it in the
- // temp value `dirtyDescription` in our state.
- handleChange ({ target: { value } }) {
- this.setState({ dirtyDescription: value });
- },
-
- // Records focus on the media item.
- handleFocus () {
- this.setState({ focused: true });
- },
-
- // Records the start of a hover over the media item.
- handleMouseEnter () {
- this.setState({ hovered: true });
- },
-
- // Records the end of a hover over the media item.
- handleMouseLeave () {
- this.setState({ hovered: false });
- },
-
- // Removes the media item.
- handleRemove () {
- const {
- id,
- onRemove,
- } = this.props;
- if (id && onRemove) {
- onRemove(id);
- }
- },
-
- // Opens the focal point modal.
- handleFocalPointClick () {
- const {
- id,
- onOpenFocalPointModal,
- } = this.props;
- if (id && onOpenFocalPointModal) {
- onOpenFocalPointModal(id);
- }
- },
-};
-
-// The component.
-export default class ComposerUploadFormItem extends React.PureComponent {
-
- // Constructor.
- constructor (props) {
- super(props);
- assignHandlers(this, handlers);
- this.state = {
- hovered: false,
- focused: false,
- dirtyDescription: null,
- };
- }
-
- // Rendering.
- render () {
- const {
- handleBlur,
- handleChange,
- handleFocus,
- handleMouseEnter,
- handleMouseLeave,
- handleRemove,
- handleFocalPointClick,
- } = this.handlers;
- const {
- intl,
- preview,
- focusX,
- focusY,
- mediaType,
- } = this.props;
- const {
- focused,
- hovered,
- dirtyDescription,
- } = this.state;
- const active = hovered || focused || isUserTouching();
- const computedClass = classNames('composer--upload_form--item', { active });
- const x = ((focusX / 2) + .5) * 100;
- const y = ((focusY / -2) + .5) * 100;
- const description = dirtyDescription || (dirtyDescription !== '' && this.props.description) || '';
-
- // The result.
- return (
-
-
- {({ scale }) => (
-
-
-
-
-
- {mediaType === 'image' && }
-
-
-
-
-
-
- )}
-
-
- );
- }
-
-}
-
-// Props.
-ComposerUploadFormItem.propTypes = {
- description: PropTypes.string,
- id: PropTypes.string,
- intl: PropTypes.object.isRequired,
- onChangeDescription: PropTypes.func.isRequired,
- onOpenFocalPointModal: PropTypes.func.isRequired,
- onRemove: PropTypes.func.isRequired,
- focusX: PropTypes.number,
- focusY: PropTypes.number,
- mediaType: PropTypes.string,
- preview: PropTypes.string,
-};
diff --git a/app/javascript/flavours/glitch/features/composer/upload_form/progress/index.js b/app/javascript/flavours/glitch/features/composer/upload_form/progress/index.js
deleted file mode 100644
index 8c4b0eea6..000000000
--- a/app/javascript/flavours/glitch/features/composer/upload_form/progress/index.js
+++ /dev/null
@@ -1,52 +0,0 @@
-// Package imports.
-import PropTypes from 'prop-types';
-import React from 'react';
-import {
- defineMessages,
- FormattedMessage,
-} from 'react-intl';
-import spring from 'react-motion/lib/spring';
-
-// Components.
-import Icon from 'flavours/glitch/components/icon';
-
-// Utils.
-import Motion from 'flavours/glitch/util/optional_motion';
-
-// Messages.
-const messages = defineMessages({
- upload: {
- defaultMessage: 'Uploading...',
- id: 'upload_progress.label',
- },
-});
-
-// The component.
-export default function ComposerUploadFormProgress ({ progress }) {
-
- // The result.
- return (
-
-
-
-
-
-
- {({ width }) =>
- (
)
- }
-
-
-
-
- );
-}
-
-// Props.
-ComposerUploadFormProgress.propTypes = { progress: PropTypes.number };
--
cgit
From 47faf47ed5a20d7d959110caefe6839d12343ec7 Mon Sep 17 00:00:00 2001
From: Thibaut Girka
Date: Sun, 21 Apr 2019 12:44:30 +0200
Subject: ComposerTextarea → AutosuggestTextarea
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../glitch/components/autosuggest_emoji.js | 42 +++
.../glitch/components/autosuggest_textarea.js | 224 +++++++++++++++
.../compose/components/autosuggest_account.js | 24 ++
.../features/compose/components/compose_form.js | 54 ++--
.../containers/autosuggest_account_container.js | 15 +
.../glitch/features/composer/textarea/index.js | 312 ---------------------
.../composer/textarea/suggestions/index.js | 43 ---
.../composer/textarea/suggestions/item/index.js | 118 --------
.../glitch/styles/components/composer.scss | 13 +-
.../glitch/styles/mastodon-light/diff.scss | 4 +-
10 files changed, 350 insertions(+), 499 deletions(-)
create mode 100644 app/javascript/flavours/glitch/components/autosuggest_emoji.js
create mode 100644 app/javascript/flavours/glitch/components/autosuggest_textarea.js
create mode 100644 app/javascript/flavours/glitch/features/compose/components/autosuggest_account.js
create mode 100644 app/javascript/flavours/glitch/features/compose/containers/autosuggest_account_container.js
delete mode 100644 app/javascript/flavours/glitch/features/composer/textarea/index.js
delete mode 100644 app/javascript/flavours/glitch/features/composer/textarea/suggestions/index.js
delete mode 100644 app/javascript/flavours/glitch/features/composer/textarea/suggestions/item/index.js
diff --git a/app/javascript/flavours/glitch/components/autosuggest_emoji.js b/app/javascript/flavours/glitch/components/autosuggest_emoji.js
new file mode 100644
index 000000000..c8609e48f
--- /dev/null
+++ b/app/javascript/flavours/glitch/components/autosuggest_emoji.js
@@ -0,0 +1,42 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import unicodeMapping from 'flavours/glitch/util/emoji/emoji_unicode_mapping_light';
+
+const assetHost = process.env.CDN_HOST || '';
+
+export default class AutosuggestEmoji extends React.PureComponent {
+
+ static propTypes = {
+ emoji: PropTypes.object.isRequired,
+ };
+
+ render () {
+ const { emoji } = this.props;
+ let url;
+
+ if (emoji.custom) {
+ url = emoji.imageUrl;
+ } else {
+ const mapping = unicodeMapping[emoji.native] || unicodeMapping[emoji.native.replace(/\uFE0F$/, '')];
+
+ if (!mapping) {
+ return null;
+ }
+
+ url = `${assetHost}/emoji/${mapping.filename}.svg`;
+ }
+
+ return (
+
+
+
+ {emoji.colons}
+
+ );
+ }
+
+}
diff --git a/app/javascript/flavours/glitch/components/autosuggest_textarea.js b/app/javascript/flavours/glitch/components/autosuggest_textarea.js
new file mode 100644
index 000000000..af8fbe406
--- /dev/null
+++ b/app/javascript/flavours/glitch/components/autosuggest_textarea.js
@@ -0,0 +1,224 @@
+import React from 'react';
+import AutosuggestAccountContainer from 'flavours/glitch/features/compose/containers/autosuggest_account_container';
+import AutosuggestEmoji from './autosuggest_emoji';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import PropTypes from 'prop-types';
+import { isRtl } from 'flavours/glitch/util/rtl';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+import Textarea from 'react-textarea-autosize';
+import classNames from 'classnames';
+
+const textAtCursorMatchesToken = (str, caretPosition) => {
+ let word;
+
+ let left = str.slice(0, caretPosition).search(/[^\s\u200B]+$/);
+ let right = str.slice(caretPosition).search(/[\s\u200B]/);
+
+ if (right < 0) {
+ word = str.slice(left);
+ } else {
+ word = str.slice(left, right + caretPosition);
+ }
+
+ if (!word || word.trim().length < 3 || ['@', ':', '#'].indexOf(word[0]) === -1) {
+ return [null, null];
+ }
+
+ word = word.trim().toLowerCase();
+
+ if (word.length > 0) {
+ return [left, word];
+ } else {
+ return [null, null];
+ }
+};
+
+export default class AutosuggestTextarea extends ImmutablePureComponent {
+
+ static propTypes = {
+ value: PropTypes.string,
+ suggestions: ImmutablePropTypes.list,
+ disabled: PropTypes.bool,
+ placeholder: PropTypes.string,
+ onSuggestionSelected: PropTypes.func.isRequired,
+ onSuggestionsClearRequested: PropTypes.func.isRequired,
+ onSuggestionsFetchRequested: PropTypes.func.isRequired,
+ onChange: PropTypes.func.isRequired,
+ onKeyUp: PropTypes.func,
+ onKeyDown: PropTypes.func,
+ onPaste: PropTypes.func.isRequired,
+ autoFocus: PropTypes.bool,
+ };
+
+ static defaultProps = {
+ autoFocus: true,
+ };
+
+ state = {
+ suggestionsHidden: false,
+ selectedSuggestion: 0,
+ lastToken: null,
+ tokenStart: 0,
+ };
+
+ onChange = (e) => {
+ const [ tokenStart, token ] = textAtCursorMatchesToken(e.target.value, e.target.selectionStart);
+
+ if (token !== null && this.state.lastToken !== token) {
+ this.setState({ lastToken: token, selectedSuggestion: 0, tokenStart });
+ this.props.onSuggestionsFetchRequested(token);
+ } else if (token === null) {
+ this.setState({ lastToken: null });
+ this.props.onSuggestionsClearRequested();
+ }
+
+ this.props.onChange(e);
+ }
+
+ onKeyDown = (e) => {
+ const { suggestions, disabled } = this.props;
+ const { selectedSuggestion, suggestionsHidden } = this.state;
+
+ if (disabled) {
+ e.preventDefault();
+ return;
+ }
+
+ if (e.which === 229 || e.isComposing) {
+ // Ignore key events during text composition
+ // e.key may be a name of the physical key even in this case (e.x. Safari / Chrome on Mac)
+ return;
+ }
+
+ switch(e.key) {
+ case 'Escape':
+ if (suggestions.size === 0 || suggestionsHidden) {
+ document.querySelector('.ui').parentElement.focus();
+ } else {
+ e.preventDefault();
+ this.setState({ suggestionsHidden: true });
+ }
+
+ break;
+ case 'ArrowDown':
+ if (suggestions.size > 0 && !suggestionsHidden) {
+ e.preventDefault();
+ this.setState({ selectedSuggestion: Math.min(selectedSuggestion + 1, suggestions.size - 1) });
+ }
+
+ break;
+ case 'ArrowUp':
+ if (suggestions.size > 0 && !suggestionsHidden) {
+ e.preventDefault();
+ this.setState({ selectedSuggestion: Math.max(selectedSuggestion - 1, 0) });
+ }
+
+ break;
+ case 'Enter':
+ case 'Tab':
+ // Select suggestion
+ if (this.state.lastToken !== null && suggestions.size > 0 && !suggestionsHidden) {
+ e.preventDefault();
+ e.stopPropagation();
+ this.props.onSuggestionSelected(this.state.tokenStart, this.state.lastToken, suggestions.get(selectedSuggestion));
+ }
+
+ break;
+ }
+
+ if (e.defaultPrevented || !this.props.onKeyDown) {
+ return;
+ }
+
+ this.props.onKeyDown(e);
+ }
+
+ onBlur = () => {
+ this.setState({ suggestionsHidden: true });
+ }
+
+ onSuggestionClick = (e) => {
+ const suggestion = this.props.suggestions.get(e.currentTarget.getAttribute('data-index'));
+ e.preventDefault();
+ this.props.onSuggestionSelected(this.state.tokenStart, this.state.lastToken, suggestion);
+ this.textarea.focus();
+ }
+
+ componentWillReceiveProps (nextProps) {
+ if (nextProps.suggestions !== this.props.suggestions && nextProps.suggestions.size > 0 && this.state.suggestionsHidden) {
+ this.setState({ suggestionsHidden: false });
+ }
+ }
+
+ setTextarea = (c) => {
+ this.textarea = c;
+ }
+
+ onPaste = (e) => {
+ if (e.clipboardData && e.clipboardData.files.length === 1) {
+ this.props.onPaste(e.clipboardData.files);
+ e.preventDefault();
+ }
+ }
+
+ renderSuggestion = (suggestion, i) => {
+ const { selectedSuggestion } = this.state;
+ let inner, key;
+
+ if (typeof suggestion === 'object') {
+ inner = ;
+ key = suggestion.id;
+ } else if (suggestion[0] === '#') {
+ inner = suggestion;
+ key = suggestion;
+ } else {
+ inner = ;
+ key = suggestion;
+ }
+
+ return (
+
+ {inner}
+
+ );
+ }
+
+ render () {
+ const { value, suggestions, disabled, placeholder, onKeyUp, autoFocus } = this.props;
+ const { suggestionsHidden } = this.state;
+ const style = { direction: 'ltr' };
+
+ if (isRtl(value)) {
+ style.direction = 'rtl';
+ }
+
+ return (
+
+
+ {placeholder}
+
+
+
+
+
+ {suggestions.map(this.renderSuggestion)}
+
+
+ );
+ }
+
+}
diff --git a/app/javascript/flavours/glitch/features/compose/components/autosuggest_account.js b/app/javascript/flavours/glitch/features/compose/components/autosuggest_account.js
new file mode 100644
index 000000000..fb9bb5035
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/compose/components/autosuggest_account.js
@@ -0,0 +1,24 @@
+import React from 'react';
+import Avatar from 'flavours/glitch/components/avatar';
+import DisplayName from 'flavours/glitch/components/display_name';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+
+export default class AutosuggestAccount extends ImmutablePureComponent {
+
+ static propTypes = {
+ account: ImmutablePropTypes.map.isRequired,
+ };
+
+ render () {
+ const { account } = this.props;
+
+ return (
+
+ );
+ }
+
+}
diff --git a/app/javascript/flavours/glitch/features/compose/components/compose_form.js b/app/javascript/flavours/glitch/features/compose/components/compose_form.js
index ecd1aed69..2ea137965 100644
--- a/app/javascript/flavours/glitch/features/compose/components/compose_form.js
+++ b/app/javascript/flavours/glitch/features/compose/components/compose_form.js
@@ -7,17 +7,20 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
// Components.
import ComposerOptions from '../../composer/options';
import ComposerPublisher from '../../composer/publisher';
-import ComposerTextarea from '../../composer/textarea';
+import ComposerTextareaIcons from '../../composer/textarea/icons';
import UploadFormContainer from '../containers/upload_form_container';
import PollFormContainer from '../containers/poll_form_container';
import WarningContainer from '../containers/warning_container';
import ReplyIndicatorContainer from '../containers/reply_indicator_container';
+import EmojiPicker from 'flavours/glitch/features/emoji_picker';
+import AutosuggestTextarea from '../../../components/autosuggest_textarea';
// Utils.
import { countableText } from 'flavours/glitch/util/counter';
import { isMobile } from 'flavours/glitch/util/is_mobile';
const messages = defineMessages({
+ placeholder: { id: 'compose_form.placeholder', defaultMessage: 'What is on your mind?' },
missingDescriptionMessage: { id: 'confirmations.missing_media_description.message',
defaultMessage: 'At least one media attachment is lacking a description. Consider describing all media attachments for the visually impaired before sending your toot.' },
missingDescriptionConfirm: { id: 'confirmations.missing_media_description.confirm',
@@ -183,7 +186,7 @@ class ComposeForm extends ImmutablePureComponent {
}
// Sets a reference to the textarea.
- handleRefTextarea = (textareaComponent) => {
+ setAutosuggestTextarea = (textareaComponent) => {
if (textareaComponent) {
this.textarea = textareaComponent.textarea;
}
@@ -269,6 +272,10 @@ class ComposeForm extends ImmutablePureComponent {
}
}
+ handleChange = (e) => {
+ this.props.onChangeText(e.target.value);
+ }
+
render () {
const {
handleEmoji,
@@ -339,27 +346,35 @@ class ComposeForm extends ImmutablePureComponent {
-
+
+
+
0)}
spoiler={spoilersAlwaysOn ? (spoilerText && spoilerText.length > 0) : spoiler}
/>
+
{
+ const getAccount = makeGetAccount();
+
+ const mapStateToProps = (state, { id }) => ({
+ account: getAccount(state, id),
+ });
+
+ return mapStateToProps;
+};
+
+export default connect(makeMapStateToProps)(AutosuggestAccount);
diff --git a/app/javascript/flavours/glitch/features/composer/textarea/index.js b/app/javascript/flavours/glitch/features/composer/textarea/index.js
deleted file mode 100644
index 50e46fc78..000000000
--- a/app/javascript/flavours/glitch/features/composer/textarea/index.js
+++ /dev/null
@@ -1,312 +0,0 @@
-// Package imports.
-import PropTypes from 'prop-types';
-import React from 'react';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import {
- defineMessages,
- FormattedMessage,
-} from 'react-intl';
-import Textarea from 'react-textarea-autosize';
-
-// Components.
-import EmojiPicker from 'flavours/glitch/features/emoji_picker';
-import ComposerTextareaIcons from './icons';
-import ComposerTextareaSuggestions from './suggestions';
-
-// Utils.
-import { isRtl } from 'flavours/glitch/util/rtl';
-import {
- assignHandlers,
- hiddenComponent,
-} from 'flavours/glitch/util/react_helpers';
-
-// Messages.
-const messages = defineMessages({
- placeholder: {
- defaultMessage: 'What is on your mind?',
- id: 'compose_form.placeholder',
- },
-});
-
-// Handlers.
-const handlers = {
-
- // When blurring the textarea, suggestions are hidden.
- handleBlur () {
- this.setState({ suggestionsHidden: true });
- },
-
- // When the contents of the textarea change, we have to pull up new
- // autosuggest suggestions if applicable, and also change the value
- // of the textarea in our store.
- handleChange ({
- target: {
- selectionStart,
- value,
- },
- }) {
- const {
- onChange,
- onSuggestionsFetchRequested,
- onSuggestionsClearRequested,
- } = this.props;
- const { lastToken } = this.state;
-
- // This gets the token at the caret location, if it begins with an
- // `@` (mentions) or `:` (shortcodes).
- const left = value.slice(0, selectionStart).search(/[^\s\u200B]+$/);
- const right = value.slice(selectionStart).search(/[\s\u200B]/);
- const token = function () {
- switch (true) {
- case left < 0 || !/[@:#]/.test(value[left]):
- return null;
- case right < 0:
- return value.slice(left);
- default:
- return value.slice(left, right + selectionStart).trim().toLowerCase();
- }
- }();
-
- // We only request suggestions for tokens which are at least 3
- // characters long.
- if (onSuggestionsFetchRequested && token && token.length >= 3) {
- if (lastToken !== token) {
- this.setState({
- lastToken: token,
- selectedSuggestion: 0,
- tokenStart: left,
- });
- onSuggestionsFetchRequested(token);
- }
- } else {
- this.setState({ lastToken: null });
- if (onSuggestionsClearRequested) {
- onSuggestionsClearRequested();
- }
- }
-
- // Updates the value of the textarea.
- if (onChange) {
- onChange(value);
- }
- },
-
- // Handles a click on an autosuggestion.
- handleClickSuggestion (index) {
- const { textarea } = this;
- const {
- onSuggestionSelected,
- suggestions,
- } = this.props;
- const {
- lastToken,
- tokenStart,
- } = this.state;
- onSuggestionSelected(tokenStart, lastToken, suggestions.get(index));
- textarea.focus();
- },
-
- // Handles a keypress. If the autosuggestions are visible, we need
- // to allow keypresses to navigate and sleect them.
- handleKeyDown (e) {
- const {
- disabled,
- onSubmit,
- onSecondarySubmit,
- onSuggestionSelected,
- suggestions,
- } = this.props;
- const {
- lastToken,
- suggestionsHidden,
- selectedSuggestion,
- tokenStart,
- } = this.state;
-
- // Keypresses do nothing if the composer is disabled.
- if (disabled) {
- e.preventDefault();
- return;
- }
-
- // We submit the status on control/meta + enter.
- if (onSubmit && e.keyCode === 13 && (e.ctrlKey || e.metaKey)) {
- onSubmit();
- }
-
- // Submit the status with secondary visibility on alt + enter.
- if (onSecondarySubmit && e.keyCode === 13 && e.altKey) {
- onSecondarySubmit();
- }
-
- // Switches over the pressed key.
- switch(e.key) {
-
- // On arrow down, we pick the next suggestion.
- case 'ArrowDown':
- if (suggestions && suggestions.size > 0 && !suggestionsHidden) {
- e.preventDefault();
- this.setState({ selectedSuggestion: Math.min(selectedSuggestion + 1, suggestions.size - 1) });
- }
- return;
-
- // On arrow up, we pick the previous suggestion.
- case 'ArrowUp':
- if (suggestions && suggestions.size > 0 && !suggestionsHidden) {
- e.preventDefault();
- this.setState({ selectedSuggestion: Math.max(selectedSuggestion - 1, 0) });
- }
- return;
-
- // On enter or tab, we select the suggestion.
- case 'Enter':
- case 'Tab':
- if (onSuggestionSelected && lastToken !== null && suggestions && suggestions.size > 0 && !suggestionsHidden) {
- e.preventDefault();
- e.stopPropagation();
- onSuggestionSelected(tokenStart, lastToken, suggestions.get(selectedSuggestion));
- }
- return;
- }
- },
-
- // When the escape key is released, we either close the suggestions
- // window or focus the UI.
- handleKeyUp ({ key }) {
- const { suggestionsHidden } = this.state;
- if (key === 'Escape') {
- if (!suggestionsHidden) {
- this.setState({ suggestionsHidden: true });
- } else {
- document.querySelector('.ui').parentElement.focus();
- }
- }
- },
-
- // Handles the pasting of images into the composer.
- handlePaste (e) {
- const { onPaste } = this.props;
- let d;
- if (onPaste && (d = e.clipboardData) && (d = d.files).length === 1) {
- onPaste(d);
- e.preventDefault();
- }
- },
-
- // Saves a reference to the textarea.
- handleRefTextarea (textarea) {
- this.textarea = textarea;
- },
-};
-
-// The component.
-export default class ComposerTextarea extends React.Component {
-
- // Constructor.
- constructor (props) {
- super(props);
- assignHandlers(this, handlers);
- this.state = {
- suggestionsHidden: false,
- selectedSuggestion: 0,
- lastToken: null,
- tokenStart: 0,
- };
-
- // Instance variables.
- this.textarea = null;
- }
-
- // When we receive new suggestions, we unhide the suggestions window
- // if we didn't have any suggestions before.
- componentWillReceiveProps (nextProps) {
- const { suggestions } = this.props;
- const { suggestionsHidden } = this.state;
- if (nextProps.suggestions && nextProps.suggestions !== suggestions && nextProps.suggestions.size > 0 && suggestionsHidden) {
- this.setState({ suggestionsHidden: false });
- }
- }
-
- // Rendering.
- render () {
- const {
- handleBlur,
- handleChange,
- handleClickSuggestion,
- handleKeyDown,
- handleKeyUp,
- handlePaste,
- handleRefTextarea,
- } = this.handlers;
- const {
- advancedOptions,
- autoFocus,
- disabled,
- intl,
- onPickEmoji,
- suggestions,
- value,
- } = this.props;
- const {
- selectedSuggestion,
- suggestionsHidden,
- } = this.state;
-
- // The result.
- return (
-
-
-
-
-
-
-
-
-
- );
- }
-
-}
-
-// Props.
-ComposerTextarea.propTypes = {
- advancedOptions: ImmutablePropTypes.map,
- autoFocus: PropTypes.bool,
- disabled: PropTypes.bool,
- intl: PropTypes.object.isRequired,
- onChange: PropTypes.func,
- onPaste: PropTypes.func,
- onPickEmoji: PropTypes.func,
- onSubmit: PropTypes.func,
- onSecondarySubmit: PropTypes.func,
- onSuggestionsClearRequested: PropTypes.func,
- onSuggestionsFetchRequested: PropTypes.func,
- onSuggestionSelected: PropTypes.func,
- suggestions: ImmutablePropTypes.list,
- value: PropTypes.string,
-};
-
-// Default props.
-ComposerTextarea.defaultProps = { autoFocus: true };
diff --git a/app/javascript/flavours/glitch/features/composer/textarea/suggestions/index.js b/app/javascript/flavours/glitch/features/composer/textarea/suggestions/index.js
deleted file mode 100644
index dc72585f2..000000000
--- a/app/javascript/flavours/glitch/features/composer/textarea/suggestions/index.js
+++ /dev/null
@@ -1,43 +0,0 @@
-// Package imports.
-import PropTypes from 'prop-types';
-import React from 'react';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-
-// Components.
-import ComposerTextareaSuggestionsItem from './item';
-
-// The component.
-export default function ComposerTextareaSuggestions ({
- hidden,
- onSuggestionClick,
- suggestions,
- value,
-}) {
-
- // The result.
- return (
-
- {!hidden && suggestions ? suggestions.map(
- (suggestion, index) => (
-
- )
- ) : null}
-
- );
-}
-
-ComposerTextareaSuggestions.propTypes = {
- hidden: PropTypes.bool,
- onSuggestionClick: PropTypes.func,
- suggestions: ImmutablePropTypes.list,
- value: PropTypes.number,
-};
diff --git a/app/javascript/flavours/glitch/features/composer/textarea/suggestions/item/index.js b/app/javascript/flavours/glitch/features/composer/textarea/suggestions/item/index.js
deleted file mode 100644
index 1b7ae8904..000000000
--- a/app/javascript/flavours/glitch/features/composer/textarea/suggestions/item/index.js
+++ /dev/null
@@ -1,118 +0,0 @@
-// Package imports.
-import classNames from 'classnames';
-import PropTypes from 'prop-types';
-import React from 'react';
-
-// Components.
-import AccountContainer from 'flavours/glitch/containers/account_container';
-
-// Utils.
-import { unicodeMapping } from 'flavours/glitch/util/emoji';
-import { assignHandlers } from 'flavours/glitch/util/react_helpers';
-
-// Gets our asset host from the environment, if available.
-const assetHost = process.env.CDN_HOST || '';
-
-// Handlers.
-const handlers = {
-
- // Handles a click on a suggestion.
- handleClick (e) {
- const {
- index,
- onClick,
- } = this.props;
- if (onClick) {
- e.preventDefault();
- e.stopPropagation(); // Prevents following account links
- onClick(index);
- }
- },
-
- // This prevents the focus from changing, which would mess with
- // our suggestion code.
- handleMouseDown (e) {
- e.preventDefault();
- },
-};
-
-// The component.
-export default class ComposerTextareaSuggestionsItem extends React.Component {
-
- // Constructor.
- constructor (props) {
- super(props);
- assignHandlers(this, handlers);
- }
-
- // Rendering.
- render () {
- const {
- handleMouseDown,
- handleClick,
- } = this.handlers;
- const {
- selected,
- suggestion,
- } = this.props;
- const computedClass = classNames('composer--textarea--suggestions--item', { selected });
-
- // If the suggestion is an object, then we render an emoji.
- // Otherwise, we render a hashtag if it starts with #, or an account.
- let inner;
- if (typeof suggestion === 'object') {
- let url;
- if (suggestion.custom) {
- url = suggestion.imageUrl;
- } else {
- const mapping = unicodeMapping[suggestion.native] || unicodeMapping[suggestion.native.replace(/\uFE0F$/, '')];
- if (mapping) {
- url = `${assetHost}/emoji/${mapping.filename}.svg`;
- }
- }
- if (url) {
- inner = (
-
-
- {suggestion.colons}
-
- );
- }
- } else if (suggestion[0] === '#') {
- inner = suggestion;
- } else {
- inner = (
-
- );
- }
-
- // The result.
- return (
-
- { inner }
-
- );
- }
-
-}
-
-// Props.
-ComposerTextareaSuggestionsItem.propTypes = {
- index: PropTypes.number,
- onClick: PropTypes.func,
- selected: PropTypes.bool,
- suggestion: PropTypes.oneOfType([PropTypes.object, PropTypes.string]),
-};
diff --git a/app/javascript/flavours/glitch/styles/components/composer.scss b/app/javascript/flavours/glitch/styles/components/composer.scss
index f0729bedc..466b654de 100644
--- a/app/javascript/flavours/glitch/styles/components/composer.scss
+++ b/app/javascript/flavours/glitch/styles/components/composer.scss
@@ -131,8 +131,8 @@
.composer--textarea {
position: relative;
- & > label {
- .textarea {
+ label {
+ .autosuggest-textarea__textarea {
display: block;
box-sizing: border-box;
margin: 0;
@@ -186,7 +186,7 @@
}
}
-.composer--textarea--suggestions {
+.autosuggest-textarea__suggestions {
display: block;
position: absolute;
box-sizing: border-box;
@@ -199,11 +199,14 @@
box-shadow: 4px 4px 6px rgba($base-shadow-color, 0.4);
font-size: 14px;
z-index: 99;
+ display: none;
+}
- &[hidden] { display: none }
+.autosuggest-textarea__suggestions--visible {
+ display: block;
}
-.composer--textarea--suggestions--item {
+.autosuggest-textarea__suggestions__item {
display: flex;
flex-direction: row;
align-items: center;
diff --git a/app/javascript/flavours/glitch/styles/mastodon-light/diff.scss b/app/javascript/flavours/glitch/styles/mastodon-light/diff.scss
index 6f105d3fa..224272f24 100644
--- a/app/javascript/flavours/glitch/styles/mastodon-light/diff.scss
+++ b/app/javascript/flavours/glitch/styles/mastodon-light/diff.scss
@@ -196,11 +196,11 @@
border-color: $ui-base-color;
}
-.composer--textarea--suggestions {
+.autosuggest-textarea__suggestions {
background: lighten($ui-base-color, 10%)
}
-.composer--textarea--suggestions--item {
+.autosuggest-textarea__suggestions__item {
&:hover,
&:focus,
&:active,
--
cgit
From 4c6221929f608764c616c2b909eccdc8a17e8909 Mon Sep 17 00:00:00 2001
From: Thibaut Girka
Date: Sun, 21 Apr 2019 15:57:06 +0200
Subject: Move ComposerTextareaIcons to TextareaIcons
---
.../features/compose/components/compose_form.js | 7 +--
.../features/compose/components/textarea_icons.js | 59 +++++++++++++++++++++
.../features/composer/textarea/icons/index.js | 60 ----------------------
3 files changed, 61 insertions(+), 65 deletions(-)
create mode 100644 app/javascript/flavours/glitch/features/compose/components/textarea_icons.js
delete mode 100644 app/javascript/flavours/glitch/features/composer/textarea/icons/index.js
diff --git a/app/javascript/flavours/glitch/features/compose/components/compose_form.js b/app/javascript/flavours/glitch/features/compose/components/compose_form.js
index 2ea137965..ae22f4d6d 100644
--- a/app/javascript/flavours/glitch/features/compose/components/compose_form.js
+++ b/app/javascript/flavours/glitch/features/compose/components/compose_form.js
@@ -7,7 +7,7 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
// Components.
import ComposerOptions from '../../composer/options';
import ComposerPublisher from '../../composer/publisher';
-import ComposerTextareaIcons from '../../composer/textarea/icons';
+import TextareaIcons from './textarea_icons';
import UploadFormContainer from '../containers/upload_form_container';
import PollFormContainer from '../containers/poll_form_container';
import WarningContainer from '../containers/warning_container';
@@ -347,10 +347,7 @@ class ComposeForm extends ImmutablePureComponent {
-
+
+ {advancedOptions ? iconMap.map(
+ ([key, icon, message]) => advancedOptions.get(key) ? (
+
+
+
+ ) : null
+ ) : null}
+
+ );
+ }
+}
diff --git a/app/javascript/flavours/glitch/features/composer/textarea/icons/index.js b/app/javascript/flavours/glitch/features/composer/textarea/icons/index.js
deleted file mode 100644
index 049cdd5cd..000000000
--- a/app/javascript/flavours/glitch/features/composer/textarea/icons/index.js
+++ /dev/null
@@ -1,60 +0,0 @@
-// Package imports.
-import PropTypes from 'prop-types';
-import React from 'react';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import { defineMessages } from 'react-intl';
-
-// Components.
-import Icon from 'flavours/glitch/components/icon';
-
-// Messages.
-const messages = defineMessages({
- localOnly: {
- defaultMessage: 'This post is local-only',
- id: 'advanced_options.local-only.tooltip',
- },
- threadedMode: {
- defaultMessage: 'Threaded mode enabled',
- id: 'advanced_options.threaded_mode.tooltip',
- },
-});
-
-// We use an array of tuples here instead of an object because it
-// preserves order.
-const iconMap = [
- ['do_not_federate', 'home', messages.localOnly],
- ['threaded_mode', 'comments', messages.threadedMode],
-];
-
-// The component.
-export default function ComposerTextareaIcons ({
- advancedOptions,
- intl,
-}) {
-
- // The result. We just map every active option to its icon.
- return (
-
- {advancedOptions ? iconMap.map(
- ([key, icon, message]) => advancedOptions.get(key) ? (
-
-
-
- ) : null
- ) : null}
-
- );
-}
-
-// Props.
-ComposerTextareaIcons.propTypes = {
- advancedOptions: ImmutablePropTypes.map,
- intl: PropTypes.object.isRequired,
-};
--
cgit
From 14028655df1b94a1722d6e329174947db45db477 Mon Sep 17 00:00:00 2001
From: Thibaut Girka
Date: Sun, 21 Apr 2019 17:17:10 +0200
Subject: Move composer Dropdown from features/composer to features/compose
---
.../features/compose/components/compose_form.js | 5 +-
.../glitch/features/compose/components/dropdown.js | 229 +++++++++++++
.../features/compose/components/dropdown_menu.js | 219 ++++++++++++
.../glitch/features/compose/components/options.js | 352 +++++++++++++++++++
.../composer/options/dropdown/content/index.js | 146 --------
.../options/dropdown/content/item/index.js | 129 -------
.../features/composer/options/dropdown/index.js | 229 -------------
.../glitch/features/composer/options/index.js | 377 ---------------------
8 files changed, 802 insertions(+), 884 deletions(-)
create mode 100644 app/javascript/flavours/glitch/features/compose/components/dropdown.js
create mode 100644 app/javascript/flavours/glitch/features/compose/components/dropdown_menu.js
create mode 100644 app/javascript/flavours/glitch/features/compose/components/options.js
delete mode 100644 app/javascript/flavours/glitch/features/composer/options/dropdown/content/index.js
delete mode 100644 app/javascript/flavours/glitch/features/composer/options/dropdown/content/item/index.js
delete mode 100644 app/javascript/flavours/glitch/features/composer/options/dropdown/index.js
delete mode 100644 app/javascript/flavours/glitch/features/composer/options/index.js
diff --git a/app/javascript/flavours/glitch/features/compose/components/compose_form.js b/app/javascript/flavours/glitch/features/compose/components/compose_form.js
index ae22f4d6d..a9be9c751 100644
--- a/app/javascript/flavours/glitch/features/compose/components/compose_form.js
+++ b/app/javascript/flavours/glitch/features/compose/components/compose_form.js
@@ -5,7 +5,7 @@ import { defineMessages, injectIntl } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component';
// Components.
-import ComposerOptions from '../../composer/options';
+import Options from './options';
import ComposerPublisher from '../../composer/publisher';
import TextareaIcons from './textarea_icons';
import UploadFormContainer from '../containers/upload_form_container';
@@ -372,7 +372,7 @@ class ComposeForm extends ImmutablePureComponent {
-
({
+ ...rest,
+ active: value && name === value,
+ name,
+ onClick (e) {
+ e.preventDefault(); // Prevents focus from changing
+ onModalClose();
+ onChange(name);
+ },
+ onPassiveClick (e) {
+ e.preventDefault(); // Prevents focus from changing
+ onChange(name);
+ component.setState({ needsModalUpdate: true });
+ },
+ })
+ ),
+ };
+ },
+
+ // Toggles opening and closing the dropdown.
+ handleToggle ({ target }) {
+ const { handleMakeModal } = this.handlers;
+ const { onModalOpen } = this.props;
+ const { open } = this.state;
+
+ // If this is a touch device, we open a modal instead of the
+ // dropdown.
+ if (isUserTouching()) {
+
+ // This gets the modal to open.
+ const modal = handleMakeModal();
+
+ // If we can, we then open the modal.
+ if (modal && onModalOpen) {
+ onModalOpen(modal);
+ return;
+ }
+ }
+
+ const { top } = target.getBoundingClientRect();
+ this.setState({ placement: top * 2 < innerHeight ? 'bottom' : 'top' });
+ // Otherwise, we just set our state to open.
+ this.setState({ open: !open });
+ },
+
+ // If our modal is open and our props update, we need to also update
+ // the modal.
+ handleUpdate () {
+ const { handleMakeModal } = this.handlers;
+ const { onModalOpen } = this.props;
+ const { needsModalUpdate } = this.state;
+
+ // Gets our modal object.
+ const modal = handleMakeModal();
+
+ // Reopens the modal with the new object.
+ if (needsModalUpdate && modal && onModalOpen) {
+ onModalOpen(modal);
+ }
+ },
+};
+
+// The component.
+export default class ComposerOptionsDropdown extends React.PureComponent {
+
+ // Constructor.
+ constructor (props) {
+ super(props);
+ assignHandlers(this, handlers);
+ this.state = {
+ needsModalUpdate: false,
+ open: false,
+ placement: 'bottom',
+ };
+ }
+
+ // Updates our modal as necessary.
+ componentDidUpdate (prevProps) {
+ const { handleUpdate } = this.handlers;
+ const { items } = this.props;
+ const { needsModalUpdate } = this.state;
+ if (needsModalUpdate && items.find(
+ (item, i) => item.on !== prevProps.items[i].on
+ )) {
+ handleUpdate();
+ this.setState({ needsModalUpdate: false });
+ }
+ }
+
+ // Rendering.
+ render () {
+ const {
+ handleClose,
+ handleKeyDown,
+ handleToggle,
+ } = this.handlers;
+ const {
+ active,
+ disabled,
+ title,
+ icon,
+ items,
+ onChange,
+ value,
+ } = this.props;
+ const { open, placement } = this.state;
+ const computedClass = classNames('composer--options--dropdown', {
+ active,
+ open,
+ top: placement === 'top',
+ });
+
+ // The result.
+ return (
+
+
+
+
+
+
+ );
+ }
+
+}
+
+// Props.
+ComposerOptionsDropdown.propTypes = {
+ active: PropTypes.bool,
+ disabled: PropTypes.bool,
+ icon: PropTypes.string,
+ items: PropTypes.arrayOf(PropTypes.shape({
+ icon: PropTypes.string,
+ meta: PropTypes.node,
+ name: PropTypes.string.isRequired,
+ on: PropTypes.bool,
+ text: PropTypes.node,
+ })).isRequired,
+ onChange: PropTypes.func,
+ onModalClose: PropTypes.func,
+ onModalOpen: PropTypes.func,
+ title: PropTypes.string,
+ value: PropTypes.string,
+};
diff --git a/app/javascript/flavours/glitch/features/compose/components/dropdown_menu.js b/app/javascript/flavours/glitch/features/compose/components/dropdown_menu.js
new file mode 100644
index 000000000..b4e2ec07b
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/compose/components/dropdown_menu.js
@@ -0,0 +1,219 @@
+// Package imports.
+import PropTypes from 'prop-types';
+import React from 'react';
+import spring from 'react-motion/lib/spring';
+import Toggle from 'react-toggle';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+import classNames from 'classnames';
+
+// Components.
+import Icon from 'flavours/glitch/components/icon';
+
+// Utils.
+import { withPassive } from 'flavours/glitch/util/dom_helpers';
+import Motion from 'flavours/glitch/util/optional_motion';
+import { assignHandlers } from 'flavours/glitch/util/react_helpers';
+
+class ComposerOptionsDropdownContentItem extends ImmutablePureComponent {
+
+ static propTypes = {
+ active: PropTypes.bool,
+ name: PropTypes.string,
+ onChange: PropTypes.func,
+ onClose: PropTypes.func,
+ options: PropTypes.shape({
+ icon: PropTypes.string,
+ meta: PropTypes.node,
+ on: PropTypes.bool,
+ text: PropTypes.node,
+ }),
+ };
+
+ handleActivate = (e) => {
+ const {
+ name,
+ onChange,
+ onClose,
+ options: { on },
+ } = this.props;
+
+ // If the escape key was pressed, we close the dropdown.
+ if (e.key === 'Escape' && onClose) {
+ onClose();
+
+ // Otherwise, we both close the dropdown and change the value.
+ } else if (onChange && (!e.key || e.key === 'Enter')) {
+ e.preventDefault(); // Prevents change in focus on click
+ if ((on === null || typeof on === 'undefined') && onClose) {
+ onClose();
+ }
+ onChange(name);
+ }
+ }
+
+ // Rendering.
+ render () {
+ const {
+ active,
+ options: {
+ icon,
+ meta,
+ on,
+ text,
+ },
+ } = this.props;
+ const computedClass = classNames('composer--options--dropdown--content--item', {
+ active,
+ lengthy: meta,
+ 'toggled-off': !on && on !== null && typeof on !== 'undefined',
+ 'toggled-on': on,
+ 'with-icon': icon,
+ });
+
+ let prefix = null;
+
+ if (on !== null && typeof on !== 'undefined') {
+ prefix = ;
+ } else if (icon) {
+ prefix =
+ }
+
+ // The result.
+ return (
+
+ {prefix}
+
+
+ {text}
+ {meta ? meta : nil}
+
+
+ );
+ }
+
+};
+
+// The spring to use with our motion.
+const springMotion = spring(1, {
+ damping: 35,
+ stiffness: 400,
+});
+
+// The component.
+export default class ComposerOptionsDropdownContent extends React.PureComponent {
+
+ static propTypes = {
+ items: PropTypes.arrayOf(PropTypes.shape({
+ icon: PropTypes.string,
+ meta: PropTypes.node,
+ name: PropTypes.string.isRequired,
+ on: PropTypes.bool,
+ text: PropTypes.node,
+ })),
+ onChange: PropTypes.func,
+ onClose: PropTypes.func,
+ style: PropTypes.object,
+ value: PropTypes.string,
+ };
+
+ static defaultProps = {
+ style: {},
+ };
+
+ state = {
+ mounted: false,
+ };
+
+ // When the document is clicked elsewhere, we close the dropdown.
+ handleDocumentClick = ({ target }) => {
+ const { node } = this;
+ const { onClose } = this.props;
+ if (onClose && node && !node.contains(target)) {
+ onClose();
+ }
+ }
+
+ // Stores our node in `this.node`.
+ handleRef = (node) => {
+ this.node = node;
+ }
+
+ // On mounting, we add our listeners.
+ componentDidMount () {
+ document.addEventListener('click', this.handleDocumentClick, false);
+ document.addEventListener('touchend', this.handleDocumentClick, withPassive);
+ this.setState({ mounted: true });
+ }
+
+ // On unmounting, we remove our listeners.
+ componentWillUnmount () {
+ document.removeEventListener('click', this.handleDocumentClick, false);
+ document.removeEventListener('touchend', this.handleDocumentClick, withPassive);
+ }
+
+ // Rendering.
+ render () {
+ const { mounted } = this.state;
+ const {
+ items,
+ onChange,
+ onClose,
+ style,
+ value,
+ } = this.props;
+
+ // The result.
+ return (
+
+ {({ opacity, scaleX, scaleY }) => (
+ // It should not be transformed when mounting because the resulting
+ // size will be used to determine the coordinate of the menu by
+ // react-overlays
+
+ {items ? items.map(
+ ({
+ name,
+ ...rest
+ }) => (
+
+ )
+ ) : null}
+
+ )}
+
+ );
+ }
+
+}
diff --git a/app/javascript/flavours/glitch/features/compose/components/options.js b/app/javascript/flavours/glitch/features/compose/components/options.js
new file mode 100644
index 000000000..8a760bd15
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/compose/components/options.js
@@ -0,0 +1,352 @@
+// Package imports.
+import PropTypes from 'prop-types';
+import React from 'react';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
+import spring from 'react-motion/lib/spring';
+
+// Components.
+import IconButton from 'flavours/glitch/components/icon_button';
+import TextIconButton from 'flavours/glitch/components/text_icon_button';
+import Dropdown from './dropdown';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+
+// Utils.
+import Motion from 'flavours/glitch/util/optional_motion';
+import { pollLimits } from 'flavours/glitch/util/initial_state';
+
+// Messages.
+const messages = defineMessages({
+ advanced_options_icon_title: {
+ defaultMessage: 'Advanced options',
+ id: 'advanced_options.icon_title',
+ },
+ attach: {
+ defaultMessage: 'Attach...',
+ id: 'compose.attach',
+ },
+ change_privacy: {
+ defaultMessage: 'Adjust status privacy',
+ id: 'privacy.change',
+ },
+ direct_long: {
+ defaultMessage: 'Post to mentioned users only',
+ id: 'privacy.direct.long',
+ },
+ direct_short: {
+ defaultMessage: 'Direct',
+ id: 'privacy.direct.short',
+ },
+ doodle: {
+ defaultMessage: 'Draw something',
+ id: 'compose.attach.doodle',
+ },
+ local_only_long: {
+ defaultMessage: 'Do not post to other instances',
+ id: 'advanced_options.local-only.long',
+ },
+ local_only_short: {
+ defaultMessage: 'Local-only',
+ id: 'advanced_options.local-only.short',
+ },
+ private_long: {
+ defaultMessage: 'Post to followers only',
+ id: 'privacy.private.long',
+ },
+ private_short: {
+ defaultMessage: 'Followers-only',
+ id: 'privacy.private.short',
+ },
+ public_long: {
+ defaultMessage: 'Post to public timelines',
+ id: 'privacy.public.long',
+ },
+ public_short: {
+ defaultMessage: 'Public',
+ id: 'privacy.public.short',
+ },
+ sensitive: {
+ defaultMessage: 'Mark media as sensitive',
+ id: 'compose_form.sensitive',
+ },
+ spoiler: {
+ defaultMessage: 'Hide text behind warning',
+ id: 'compose_form.spoiler',
+ },
+ threaded_mode_long: {
+ defaultMessage: 'Automatically opens a reply on posting',
+ id: 'advanced_options.threaded_mode.long',
+ },
+ threaded_mode_short: {
+ defaultMessage: 'Threaded mode',
+ id: 'advanced_options.threaded_mode.short',
+ },
+ unlisted_long: {
+ defaultMessage: 'Do not show in public timelines',
+ id: 'privacy.unlisted.long',
+ },
+ unlisted_short: {
+ defaultMessage: 'Unlisted',
+ id: 'privacy.unlisted.short',
+ },
+ upload: {
+ defaultMessage: 'Upload a file',
+ id: 'compose.attach.upload',
+ },
+ add_poll: {
+ defaultMessage: 'Add a poll',
+ id: 'poll_button.add_poll',
+ },
+ remove_poll: {
+ defaultMessage: 'Remove poll',
+ id: 'poll_button.remove_poll',
+ },
+});
+
+export default @injectIntl
+class ComposerOptions extends ImmutablePureComponent {
+
+ static propTypes = {
+ acceptContentTypes: PropTypes.string,
+ advancedOptions: ImmutablePropTypes.map,
+ disabled: PropTypes.bool,
+ allowMedia: PropTypes.bool,
+ hasMedia: PropTypes.bool,
+ allowPoll: PropTypes.bool,
+ hasPoll: PropTypes.bool,
+ intl: PropTypes.object.isRequired,
+ onChangeAdvancedOption: PropTypes.func,
+ onChangeSensitivity: PropTypes.func,
+ onChangeVisibility: PropTypes.func,
+ onTogglePoll: PropTypes.func,
+ onDoodleOpen: PropTypes.func,
+ onModalClose: PropTypes.func,
+ onModalOpen: PropTypes.func,
+ onToggleSpoiler: PropTypes.func,
+ onUpload: PropTypes.func,
+ privacy: PropTypes.string,
+ resetFileKey: PropTypes.number,
+ sensitive: PropTypes.bool,
+ spoiler: PropTypes.bool,
+ };
+
+ // Handles file selection.
+ handleChangeFiles = ({ target: { files } }) => {
+ const { onUpload } = this.props;
+ if (files.length && onUpload) {
+ onUpload(files);
+ }
+ }
+
+ // Handles attachment clicks.
+ handleClickAttach = (name) => {
+ const { fileElement } = this;
+ const { onDoodleOpen } = this.props;
+
+ // We switch over the name of the option.
+ switch (name) {
+ case 'upload':
+ if (fileElement) {
+ fileElement.click();
+ }
+ return;
+ case 'doodle':
+ if (onDoodleOpen) {
+ onDoodleOpen();
+ }
+ return;
+ }
+ }
+
+ // Handles a ref to the file input.
+ handleRefFileElement = (fileElement) => {
+ this.fileElement = fileElement;
+ }
+
+ // Rendering.
+ render () {
+ const {
+ acceptContentTypes,
+ advancedOptions,
+ disabled,
+ allowMedia,
+ hasMedia,
+ allowPoll,
+ hasPoll,
+ intl,
+ onChangeAdvancedOption,
+ onChangeSensitivity,
+ onChangeVisibility,
+ onTogglePoll,
+ onModalClose,
+ onModalOpen,
+ onToggleSpoiler,
+ privacy,
+ resetFileKey,
+ sensitive,
+ spoiler,
+ } = this.props;
+
+ // We predefine our privacy items so that we can easily pick the
+ // dropdown icon later.
+ const privacyItems = {
+ direct: {
+ icon: 'envelope',
+ meta: ,
+ name: 'direct',
+ text: ,
+ },
+ private: {
+ icon: 'lock',
+ meta: ,
+ name: 'private',
+ text: ,
+ },
+ public: {
+ icon: 'globe',
+ meta: ,
+ name: 'public',
+ text: ,
+ },
+ unlisted: {
+ icon: 'unlock',
+ meta: ,
+ name: 'unlisted',
+ text: ,
+ },
+ };
+
+ // The result.
+ return (
+
+
+
,
+ },
+ {
+ icon: 'paint-brush',
+ name: 'doodle',
+ text:
,
+ },
+ ]}
+ onChange={this.handleClickAttach}
+ onModalClose={onModalClose}
+ onModalOpen={onModalOpen}
+ title={intl.formatMessage(messages.attach)}
+ />
+ {!!pollLimits && (
+
+ )}
+
+ {({ scale }) => (
+
+
+
+ )}
+
+
+
+ {onToggleSpoiler && (
+
+ )}
+
!!value)}
+ disabled={disabled}
+ icon='ellipsis-h'
+ items={advancedOptions ? [
+ {
+ meta: ,
+ name: 'do_not_federate',
+ on: advancedOptions.get('do_not_federate'),
+ text: ,
+ },
+ {
+ meta: ,
+ name: 'threaded_mode',
+ on: advancedOptions.get('threaded_mode'),
+ text: ,
+ },
+ ] : null}
+ onChange={onChangeAdvancedOption}
+ onModalClose={onModalClose}
+ onModalOpen={onModalOpen}
+ title={intl.formatMessage(messages.advanced_options_icon_title)}
+ />
+
+ );
+ }
+
+}
diff --git a/app/javascript/flavours/glitch/features/composer/options/dropdown/content/index.js b/app/javascript/flavours/glitch/features/composer/options/dropdown/content/index.js
deleted file mode 100644
index b76410561..000000000
--- a/app/javascript/flavours/glitch/features/composer/options/dropdown/content/index.js
+++ /dev/null
@@ -1,146 +0,0 @@
-// Package imports.
-import PropTypes from 'prop-types';
-import React from 'react';
-import spring from 'react-motion/lib/spring';
-
-// Components.
-import ComposerOptionsDropdownContentItem from './item';
-
-// Utils.
-import { withPassive } from 'flavours/glitch/util/dom_helpers';
-import Motion from 'flavours/glitch/util/optional_motion';
-import { assignHandlers } from 'flavours/glitch/util/react_helpers';
-
-// Handlers.
-const handlers = {
- // When the document is clicked elsewhere, we close the dropdown.
- handleDocumentClick ({ target }) {
- const { node } = this;
- const { onClose } = this.props;
- if (onClose && node && !node.contains(target)) {
- onClose();
- }
- },
-
- // Stores our node in `this.node`.
- handleRef (node) {
- this.node = node;
- },
-};
-
-// The spring to use with our motion.
-const springMotion = spring(1, {
- damping: 35,
- stiffness: 400,
-});
-
-// The component.
-export default class ComposerOptionsDropdownContent extends React.PureComponent {
-
- // Constructor.
- constructor (props) {
- super(props);
- assignHandlers(this, handlers);
-
- // Instance variables.
- this.node = null;
-
- this.state = {
- mounted: false,
- };
- }
-
- // On mounting, we add our listeners.
- componentDidMount () {
- const { handleDocumentClick } = this.handlers;
- document.addEventListener('click', handleDocumentClick, false);
- document.addEventListener('touchend', handleDocumentClick, withPassive);
- this.setState({ mounted: true });
- }
-
- // On unmounting, we remove our listeners.
- componentWillUnmount () {
- const { handleDocumentClick } = this.handlers;
- document.removeEventListener('click', handleDocumentClick, false);
- document.removeEventListener('touchend', handleDocumentClick, withPassive);
- }
-
- // Rendering.
- render () {
- const { mounted } = this.state;
- const { handleRef } = this.handlers;
- const {
- items,
- onChange,
- onClose,
- style,
- value,
- } = this.props;
-
- // The result.
- return (
-
- {({ opacity, scaleX, scaleY }) => (
- // It should not be transformed when mounting because the resulting
- // size will be used to determine the coordinate of the menu by
- // react-overlays
-
- {items ? items.map(
- ({
- name,
- ...rest
- }) => (
-
- )
- ) : null}
-
- )}
-
- );
- }
-
-}
-
-// Props.
-ComposerOptionsDropdownContent.propTypes = {
- items: PropTypes.arrayOf(PropTypes.shape({
- icon: PropTypes.string,
- meta: PropTypes.node,
- name: PropTypes.string.isRequired,
- on: PropTypes.bool,
- text: PropTypes.node,
- })),
- onChange: PropTypes.func,
- onClose: PropTypes.func,
- style: PropTypes.object,
- value: PropTypes.string,
-};
-
-// Default props.
-ComposerOptionsDropdownContent.defaultProps = { style: {} };
diff --git a/app/javascript/flavours/glitch/features/composer/options/dropdown/content/item/index.js b/app/javascript/flavours/glitch/features/composer/options/dropdown/content/item/index.js
deleted file mode 100644
index 68a52083f..000000000
--- a/app/javascript/flavours/glitch/features/composer/options/dropdown/content/item/index.js
+++ /dev/null
@@ -1,129 +0,0 @@
-// Package imports.
-import classNames from 'classnames';
-import PropTypes from 'prop-types';
-import React from 'react';
-import Toggle from 'react-toggle';
-
-// Components.
-import Icon from 'flavours/glitch/components/icon';
-
-// Utils.
-import { assignHandlers } from 'flavours/glitch/util/react_helpers';
-
-// Handlers.
-const handlers = {
-
- // This function activates the dropdown item.
- handleActivate (e) {
- const {
- name,
- onChange,
- onClose,
- options: { on },
- } = this.props;
-
- // If the escape key was pressed, we close the dropdown.
- if (e.key === 'Escape' && onClose) {
- onClose();
-
- // Otherwise, we both close the dropdown and change the value.
- } else if (onChange && (!e.key || e.key === 'Enter')) {
- e.preventDefault(); // Prevents change in focus on click
- if ((on === null || typeof on === 'undefined') && onClose) {
- onClose();
- }
- onChange(name);
- }
- },
-};
-
-// The component.
-export default class ComposerOptionsDropdownContentItem extends React.PureComponent {
-
- // Constructor.
- constructor (props) {
- super(props);
- assignHandlers(this, handlers);
- }
-
- // Rendering.
- render () {
- const { handleActivate } = this.handlers;
- const {
- active,
- options: {
- icon,
- meta,
- on,
- text,
- },
- } = this.props;
- const computedClass = classNames('composer--options--dropdown--content--item', {
- active,
- lengthy: meta,
- 'toggled-off': !on && on !== null && typeof on !== 'undefined',
- 'toggled-on': on,
- 'with-icon': icon,
- });
-
- // The result.
- return (
-
- {function () {
-
- // We render a `
` if we were provided an `on`
- // property, and otherwise show an `` if available.
- switch (true) {
- case on !== null && typeof on !== 'undefined':
- return (
-
- );
- case !!icon:
- return (
-
- );
- default:
- return null;
- }
- }()}
- {meta ? (
-
- {text}
- {meta}
-
- ) :
-
- {text}
-
}
-
- );
- }
-
-};
-
-// Props.
-ComposerOptionsDropdownContentItem.propTypes = {
- active: PropTypes.bool,
- name: PropTypes.string,
- onChange: PropTypes.func,
- onClose: PropTypes.func,
- options: PropTypes.shape({
- icon: PropTypes.string,
- meta: PropTypes.node,
- on: PropTypes.bool,
- text: PropTypes.node,
- }),
-};
diff --git a/app/javascript/flavours/glitch/features/composer/options/dropdown/index.js b/app/javascript/flavours/glitch/features/composer/options/dropdown/index.js
deleted file mode 100644
index 7817cc964..000000000
--- a/app/javascript/flavours/glitch/features/composer/options/dropdown/index.js
+++ /dev/null
@@ -1,229 +0,0 @@
-// Package imports.
-import classNames from 'classnames';
-import PropTypes from 'prop-types';
-import React from 'react';
-import Overlay from 'react-overlays/lib/Overlay';
-
-// Components.
-import IconButton from 'flavours/glitch/components/icon_button';
-import ComposerOptionsDropdownContent from './content';
-
-// Utils.
-import { isUserTouching } from 'flavours/glitch/util/is_mobile';
-import { assignHandlers } from 'flavours/glitch/util/react_helpers';
-
-// Handlers.
-const handlers = {
-
- // Closes the dropdown.
- handleClose () {
- this.setState({ open: false });
- },
-
- // The enter key toggles the dropdown's open state, and the escape
- // key closes it.
- handleKeyDown ({ key }) {
- const {
- handleClose,
- handleToggle,
- } = this.handlers;
- switch (key) {
- case 'Enter':
- handleToggle(key);
- break;
- case 'Escape':
- handleClose();
- break;
- }
- },
-
- // Creates an action modal object.
- handleMakeModal () {
- const component = this;
- const {
- items,
- onChange,
- onModalOpen,
- onModalClose,
- value,
- } = this.props;
-
- // Required props.
- if (!(onChange && onModalOpen && onModalClose && items)) {
- return null;
- }
-
- // The object.
- return {
- actions: items.map(
- ({
- name,
- ...rest
- }) => ({
- ...rest,
- active: value && name === value,
- name,
- onClick (e) {
- e.preventDefault(); // Prevents focus from changing
- onModalClose();
- onChange(name);
- },
- onPassiveClick (e) {
- e.preventDefault(); // Prevents focus from changing
- onChange(name);
- component.setState({ needsModalUpdate: true });
- },
- })
- ),
- };
- },
-
- // Toggles opening and closing the dropdown.
- handleToggle ({ target }) {
- const { handleMakeModal } = this.handlers;
- const { onModalOpen } = this.props;
- const { open } = this.state;
-
- // If this is a touch device, we open a modal instead of the
- // dropdown.
- if (isUserTouching()) {
-
- // This gets the modal to open.
- const modal = handleMakeModal();
-
- // If we can, we then open the modal.
- if (modal && onModalOpen) {
- onModalOpen(modal);
- return;
- }
- }
-
- const { top } = target.getBoundingClientRect();
- this.setState({ placement: top * 2 < innerHeight ? 'bottom' : 'top' });
- // Otherwise, we just set our state to open.
- this.setState({ open: !open });
- },
-
- // If our modal is open and our props update, we need to also update
- // the modal.
- handleUpdate () {
- const { handleMakeModal } = this.handlers;
- const { onModalOpen } = this.props;
- const { needsModalUpdate } = this.state;
-
- // Gets our modal object.
- const modal = handleMakeModal();
-
- // Reopens the modal with the new object.
- if (needsModalUpdate && modal && onModalOpen) {
- onModalOpen(modal);
- }
- },
-};
-
-// The component.
-export default class ComposerOptionsDropdown extends React.PureComponent {
-
- // Constructor.
- constructor (props) {
- super(props);
- assignHandlers(this, handlers);
- this.state = {
- needsModalUpdate: false,
- open: false,
- placement: 'bottom',
- };
- }
-
- // Updates our modal as necessary.
- componentDidUpdate (prevProps) {
- const { handleUpdate } = this.handlers;
- const { items } = this.props;
- const { needsModalUpdate } = this.state;
- if (needsModalUpdate && items.find(
- (item, i) => item.on !== prevProps.items[i].on
- )) {
- handleUpdate();
- this.setState({ needsModalUpdate: false });
- }
- }
-
- // Rendering.
- render () {
- const {
- handleClose,
- handleKeyDown,
- handleToggle,
- } = this.handlers;
- const {
- active,
- disabled,
- title,
- icon,
- items,
- onChange,
- value,
- } = this.props;
- const { open, placement } = this.state;
- const computedClass = classNames('composer--options--dropdown', {
- active,
- open,
- top: placement === 'top',
- });
-
- // The result.
- return (
-
-
-
-
-
-
- );
- }
-
-}
-
-// Props.
-ComposerOptionsDropdown.propTypes = {
- active: PropTypes.bool,
- disabled: PropTypes.bool,
- icon: PropTypes.string,
- items: PropTypes.arrayOf(PropTypes.shape({
- icon: PropTypes.string,
- meta: PropTypes.node,
- name: PropTypes.string.isRequired,
- on: PropTypes.bool,
- text: PropTypes.node,
- })).isRequired,
- onChange: PropTypes.func,
- onModalClose: PropTypes.func,
- onModalOpen: PropTypes.func,
- title: PropTypes.string,
- value: PropTypes.string,
-};
diff --git a/app/javascript/flavours/glitch/features/composer/options/index.js b/app/javascript/flavours/glitch/features/composer/options/index.js
deleted file mode 100644
index 7c7f01dc2..000000000
--- a/app/javascript/flavours/glitch/features/composer/options/index.js
+++ /dev/null
@@ -1,377 +0,0 @@
-// Package imports.
-import PropTypes from 'prop-types';
-import React from 'react';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import {
- FormattedMessage,
- defineMessages,
-} from 'react-intl';
-import spring from 'react-motion/lib/spring';
-
-// Components.
-import IconButton from 'flavours/glitch/components/icon_button';
-import TextIconButton from 'flavours/glitch/components/text_icon_button';
-import Dropdown from './dropdown';
-
-// Utils.
-import Motion from 'flavours/glitch/util/optional_motion';
-import {
- assignHandlers,
- hiddenComponent,
-} from 'flavours/glitch/util/react_helpers';
-import { pollLimits } from 'flavours/glitch/util/initial_state';
-
-// Messages.
-const messages = defineMessages({
- advanced_options_icon_title: {
- defaultMessage: 'Advanced options',
- id: 'advanced_options.icon_title',
- },
- attach: {
- defaultMessage: 'Attach...',
- id: 'compose.attach',
- },
- change_privacy: {
- defaultMessage: 'Adjust status privacy',
- id: 'privacy.change',
- },
- direct_long: {
- defaultMessage: 'Post to mentioned users only',
- id: 'privacy.direct.long',
- },
- direct_short: {
- defaultMessage: 'Direct',
- id: 'privacy.direct.short',
- },
- doodle: {
- defaultMessage: 'Draw something',
- id: 'compose.attach.doodle',
- },
- local_only_long: {
- defaultMessage: 'Do not post to other instances',
- id: 'advanced_options.local-only.long',
- },
- local_only_short: {
- defaultMessage: 'Local-only',
- id: 'advanced_options.local-only.short',
- },
- private_long: {
- defaultMessage: 'Post to followers only',
- id: 'privacy.private.long',
- },
- private_short: {
- defaultMessage: 'Followers-only',
- id: 'privacy.private.short',
- },
- public_long: {
- defaultMessage: 'Post to public timelines',
- id: 'privacy.public.long',
- },
- public_short: {
- defaultMessage: 'Public',
- id: 'privacy.public.short',
- },
- sensitive: {
- defaultMessage: 'Mark media as sensitive',
- id: 'compose_form.sensitive',
- },
- spoiler: {
- defaultMessage: 'Hide text behind warning',
- id: 'compose_form.spoiler',
- },
- threaded_mode_long: {
- defaultMessage: 'Automatically opens a reply on posting',
- id: 'advanced_options.threaded_mode.long',
- },
- threaded_mode_short: {
- defaultMessage: 'Threaded mode',
- id: 'advanced_options.threaded_mode.short',
- },
- unlisted_long: {
- defaultMessage: 'Do not show in public timelines',
- id: 'privacy.unlisted.long',
- },
- unlisted_short: {
- defaultMessage: 'Unlisted',
- id: 'privacy.unlisted.short',
- },
- upload: {
- defaultMessage: 'Upload a file',
- id: 'compose.attach.upload',
- },
- add_poll: {
- defaultMessage: 'Add a poll',
- id: 'poll_button.add_poll',
- },
- remove_poll: {
- defaultMessage: 'Remove poll',
- id: 'poll_button.remove_poll',
- },
-});
-
-// Handlers.
-const handlers = {
-
- // Handles file selection.
- handleChangeFiles ({ target: { files } }) {
- const { onUpload } = this.props;
- if (files.length && onUpload) {
- onUpload(files);
- }
- },
-
- // Handles attachment clicks.
- handleClickAttach (name) {
- const { fileElement } = this;
- const { onDoodleOpen } = this.props;
-
- // We switch over the name of the option.
- switch (name) {
- case 'upload':
- if (fileElement) {
- fileElement.click();
- }
- return;
- case 'doodle':
- if (onDoodleOpen) {
- onDoodleOpen();
- }
- return;
- }
- },
-
- // Handles a ref to the file input.
- handleRefFileElement (fileElement) {
- this.fileElement = fileElement;
- },
-};
-
-// The component.
-export default class ComposerOptions extends React.PureComponent {
-
- // Constructor.
- constructor (props) {
- super(props);
- assignHandlers(this, handlers);
-
- // Instance variables.
- this.fileElement = null;
- }
-
- // Rendering.
- render () {
- const {
- handleChangeFiles,
- handleClickAttach,
- handleRefFileElement,
- } = this.handlers;
- const {
- acceptContentTypes,
- advancedOptions,
- disabled,
- allowMedia,
- hasMedia,
- allowPoll,
- hasPoll,
- intl,
- onChangeAdvancedOption,
- onChangeSensitivity,
- onChangeVisibility,
- onTogglePoll,
- onModalClose,
- onModalOpen,
- onToggleSpoiler,
- privacy,
- resetFileKey,
- sensitive,
- spoiler,
- } = this.props;
-
- // We predefine our privacy items so that we can easily pick the
- // dropdown icon later.
- const privacyItems = {
- direct: {
- icon: 'envelope',
- meta: ,
- name: 'direct',
- text: ,
- },
- private: {
- icon: 'lock',
- meta: ,
- name: 'private',
- text: ,
- },
- public: {
- icon: 'globe',
- meta: ,
- name: 'public',
- text: ,
- },
- unlisted: {
- icon: 'unlock',
- meta: ,
- name: 'unlisted',
- text: ,
- },
- };
-
- // The result.
- return (
-
-
-
,
- },
- {
- icon: 'paint-brush',
- name: 'doodle',
- text:
,
- },
- ]}
- onChange={handleClickAttach}
- onModalClose={onModalClose}
- onModalOpen={onModalOpen}
- title={intl.formatMessage(messages.attach)}
- />
- {!!pollLimits && (
-
- )}
-
- {({ scale }) => (
-
-
-
- )}
-
-
-
- {onToggleSpoiler && (
-
- )}
-
!!value)}
- disabled={disabled}
- icon='ellipsis-h'
- items={advancedOptions ? [
- {
- meta: ,
- name: 'do_not_federate',
- on: advancedOptions.get('do_not_federate'),
- text: ,
- },
- {
- meta: ,
- name: 'threaded_mode',
- on: advancedOptions.get('threaded_mode'),
- text: ,
- },
- ] : null}
- onChange={onChangeAdvancedOption}
- onModalClose={onModalClose}
- onModalOpen={onModalOpen}
- title={intl.formatMessage(messages.advanced_options_icon_title)}
- />
-
- );
- }
-
-}
-
-// Props.
-ComposerOptions.propTypes = {
- acceptContentTypes: PropTypes.string,
- advancedOptions: ImmutablePropTypes.map,
- disabled: PropTypes.bool,
- allowMedia: PropTypes.bool,
- hasMedia: PropTypes.bool,
- allowPoll: PropTypes.bool,
- hasPoll: PropTypes.bool,
- intl: PropTypes.object.isRequired,
- onChangeAdvancedOption: PropTypes.func,
- onChangeSensitivity: PropTypes.func,
- onChangeVisibility: PropTypes.func,
- onTogglePoll: PropTypes.func,
- onDoodleOpen: PropTypes.func,
- onModalClose: PropTypes.func,
- onModalOpen: PropTypes.func,
- onToggleSpoiler: PropTypes.func,
- onUpload: PropTypes.func,
- privacy: PropTypes.string,
- resetFileKey: PropTypes.number,
- sensitive: PropTypes.bool,
- spoiler: PropTypes.bool,
-};
--
cgit
From df951c319c83d905307b8dc8abda837c651820e1 Mon Sep 17 00:00:00 2001
From: Thibaut Girka
Date: Sun, 21 Apr 2019 18:31:26 +0200
Subject: Add OptionsContainer
---
.../features/compose/components/compose_form.js | 34 +------------
.../compose/containers/compose_form_container.js | 31 ------------
.../compose/containers/options_container.js | 57 ++++++++++++++++++++++
3 files changed, 59 insertions(+), 63 deletions(-)
create mode 100644 app/javascript/flavours/glitch/features/compose/containers/options_container.js
diff --git a/app/javascript/flavours/glitch/features/compose/components/compose_form.js b/app/javascript/flavours/glitch/features/compose/components/compose_form.js
index a9be9c751..76a5117bc 100644
--- a/app/javascript/flavours/glitch/features/compose/components/compose_form.js
+++ b/app/javascript/flavours/glitch/features/compose/components/compose_form.js
@@ -5,7 +5,7 @@ import { defineMessages, injectIntl } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component';
// Components.
-import Options from './options';
+import OptionsContainer from '../containers/options_container';
import ComposerPublisher from '../../composer/publisher';
import TextareaIcons from './textarea_icons';
import UploadFormContainer from '../containers/upload_form_container';
@@ -39,7 +39,6 @@ class ComposeForm extends ImmutablePureComponent {
intl: PropTypes.object.isRequired,
// State props.
- acceptContentTypes: PropTypes.string,
advancedOptions: ImmutablePropTypes.map,
amUnlocked: PropTypes.bool,
focusDate: PropTypes.instanceOf(Date),
@@ -51,7 +50,6 @@ class ComposeForm extends ImmutablePureComponent {
media: ImmutablePropTypes.list,
preselectDate: PropTypes.instanceOf(Date),
privacy: PropTypes.string,
- resetFileKey: PropTypes.number,
sideArm: PropTypes.string,
sensitive: PropTypes.bool,
showSearch: PropTypes.bool,
@@ -66,19 +64,14 @@ class ComposeForm extends ImmutablePureComponent {
preselectOnReply: PropTypes.bool,
// Dispatch props.
- onChangeAdvancedOption: PropTypes.func,
- onChangeSensitivity: PropTypes.func,
onChangeSpoilerText: PropTypes.func,
onChangeSpoilerness: PropTypes.func,
onChangeText: PropTypes.func,
onChangeVisibility: PropTypes.func,
onClearSuggestions: PropTypes.func,
- onCloseModal: PropTypes.func,
onFetchSuggestions: PropTypes.func,
onInsertEmoji: PropTypes.func,
onMount: PropTypes.func,
- onOpenActionsModal: PropTypes.func,
- onOpenDoodleModal: PropTypes.func,
onSelectSuggestion: PropTypes.func,
onSubmit: PropTypes.func,
onUnmount: PropTypes.func,
@@ -285,7 +278,6 @@ class ComposeForm extends ImmutablePureComponent {
handleRefTextarea,
} = this;
const {
- acceptContentTypes,
advancedOptions,
amUnlocked,
anyMedia,
@@ -295,21 +287,13 @@ class ComposeForm extends ImmutablePureComponent {
isUploading,
layout,
media,
- poll,
- onChangeAdvancedOption,
- onChangeSensitivity,
onChangeSpoilerness,
onChangeText,
onChangeVisibility,
- onTogglePoll,
onClearSuggestions,
- onCloseModal,
onFetchSuggestions,
- onOpenActionsModal,
- onOpenDoodleModal,
onUpload,
privacy,
- resetFileKey,
sensitive,
showSearch,
sideArm,
@@ -372,27 +356,13 @@ class ComposeForm extends ImmutablePureComponent {
-
item.get('type') === 'video'
- ) : true)}
- hasMedia={media && !!media.size}
- allowPoll={!(media && !!media.size)}
- hasPoll={!!poll}
- onChangeAdvancedOption={onChangeAdvancedOption}
- onChangeSensitivity={onChangeSensitivity}
onChangeVisibility={onChangeVisibility}
- onTogglePoll={onTogglePoll}
- onDoodleOpen={onOpenDoodleModal}
- onModalClose={onCloseModal}
- onModalOpen={onOpenActionsModal}
onToggleSpoiler={spoilersAlwaysOn ? null : onChangeSpoilerness}
onUpload={onUpload}
privacy={privacy}
- resetFileKey={resetFileKey}
sensitive={sensitive || (spoilersAlwaysOn && spoilerText && spoilerText.length > 0)}
spoiler={spoilersAlwaysOn ? (spoilerText && spoilerText.length > 0) : spoiler}
/>
diff --git a/app/javascript/flavours/glitch/features/compose/containers/compose_form_container.js b/app/javascript/flavours/glitch/features/compose/containers/compose_form_container.js
index 4716d9435..f4161aa8f 100644
--- a/app/javascript/flavours/glitch/features/compose/containers/compose_form_container.js
+++ b/app/javascript/flavours/glitch/features/compose/containers/compose_form_container.js
@@ -2,8 +2,6 @@ import { connect } from 'react-redux';
import ComposeForm from '../components/compose_form';
import {
changeCompose,
- changeComposeAdvancedOption,
- changeComposeSensitivity,
changeComposeSpoilerText,
changeComposeSpoilerness,
changeComposeVisibility,
@@ -17,11 +15,9 @@ import {
uploadCompose,
} from 'flavours/glitch/actions/compose';
import {
- closeModal,
openModal,
} from 'flavours/glitch/actions/modal';
import { changeLocalSetting } from 'flavours/glitch/actions/local_settings';
-import { addPoll, removePoll } from 'flavours/glitch/actions/compose';
import { privacyPreference } from 'flavours/glitch/util/privacy_preference';
import { me } from 'flavours/glitch/util/initial_state';
@@ -52,7 +48,6 @@ function mapStateToProps (state) {
}
sideArmPrivacy = sideArmPrivacy || sideArmBasePrivacy;
return {
- acceptContentTypes: state.getIn(['media_attachments', 'accept_content_types']).toArray().join(','),
advancedOptions: state.getIn(['compose', 'advanced_options']),
amUnlocked: !state.getIn(['accounts', me, 'locked']),
focusDate: state.getIn(['compose', 'focusDate']),
@@ -64,7 +59,6 @@ function mapStateToProps (state) {
media: state.getIn(['compose', 'media_attachments']),
preselectDate: state.getIn(['compose', 'preselectDate']),
privacy: state.getIn(['compose', 'privacy']),
- resetFileKey: state.getIn(['compose', 'resetFileKey']),
sideArm: sideArmPrivacy,
sensitive: state.getIn(['compose', 'sensitive']),
showSearch: state.getIn(['search', 'submitted']) && !state.getIn(['search', 'hidden']),
@@ -74,7 +68,6 @@ function mapStateToProps (state) {
suggestions: state.getIn(['compose', 'suggestions']),
text: state.getIn(['compose', 'text']),
anyMedia: state.getIn(['compose', 'media_attachments']).size > 0,
- poll: state.getIn(['compose', 'poll']),
spoilersAlwaysOn: spoilersAlwaysOn,
mediaDescriptionConfirmation: state.getIn(['local_settings', 'confirm_missing_media_description']),
preselectOnReply: state.getIn(['local_settings', 'preselect_on_reply']),
@@ -83,12 +76,6 @@ function mapStateToProps (state) {
// Dispatch mapping.
const mapDispatchToProps = (dispatch, { intl }) => ({
- onChangeAdvancedOption(option, value) {
- dispatch(changeComposeAdvancedOption(option, value));
- },
- onChangeSensitivity() {
- dispatch(changeComposeSensitivity());
- },
onChangeSpoilerText(text) {
dispatch(changeComposeSpoilerText(text));
},
@@ -101,21 +88,9 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
onChangeVisibility(value) {
dispatch(changeComposeVisibility(value));
},
- onTogglePoll() {
- dispatch((_, getState) => {
- if (getState().getIn(['compose', 'poll'])) {
- dispatch(removePoll());
- } else {
- dispatch(addPoll());
- }
- });
- },
onClearSuggestions() {
dispatch(clearComposeSuggestions());
},
- onCloseModal() {
- dispatch(closeModal());
- },
onFetchSuggestions(token) {
dispatch(fetchComposeSuggestions(token));
},
@@ -125,12 +100,6 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
onMount() {
dispatch(mountCompose());
},
- onOpenActionsModal(props) {
- dispatch(openModal('ACTIONS', props));
- },
- onOpenDoodleModal() {
- dispatch(openModal('DOODLE', { noEsc: true }));
- },
onSelectSuggestion(position, token, suggestion) {
dispatch(selectComposeSuggestion(position, token, suggestion));
},
diff --git a/app/javascript/flavours/glitch/features/compose/containers/options_container.js b/app/javascript/flavours/glitch/features/compose/containers/options_container.js
new file mode 100644
index 000000000..e846cfbd5
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/compose/containers/options_container.js
@@ -0,0 +1,57 @@
+import { connect } from 'react-redux';
+import Options from '../components/options';
+import {
+ changeComposeAdvancedOption,
+ changeComposeSensitivity,
+} from 'flavours/glitch/actions/compose';
+import { addPoll, removePoll } from 'flavours/glitch/actions/compose';
+import { closeModal, openModal } from 'flavours/glitch/actions/modal';
+
+function mapStateToProps (state) {
+ const spoilersAlwaysOn = state.getIn(['local_settings', 'always_show_spoilers_field']);
+ const poll = state.getIn(['compose', 'poll']);
+ const media = state.getIn(['compose', 'media_attachments']);
+ return {
+ acceptContentTypes: state.getIn(['media_attachments', 'accept_content_types']).toArray().join(','),
+ resetFileKey: state.getIn(['compose', 'resetFileKey']),
+ hasPoll: !!poll,
+ allowMedia: !poll && (media ? media.size < 4 && !media.some(item => item.get('type') === 'video') : true),
+ hasMedia: media && !!media.size,
+ allowPoll: !(media && !!media.size),
+ };
+};
+
+const mapDispatchToProps = (dispatch) => ({
+
+ onChangeAdvancedOption(option, value) {
+ dispatch(changeComposeAdvancedOption(option, value));
+ },
+
+ onChangeSensitivity() {
+ dispatch(changeComposeSensitivity());
+ },
+
+ onTogglePoll() {
+ dispatch((_, getState) => {
+ if (getState().getIn(['compose', 'poll'])) {
+ dispatch(removePoll());
+ } else {
+ dispatch(addPoll());
+ }
+ });
+ },
+
+ onDoodleOpen() {
+ dispatch(openModal('DOODLE', { noEsc: true }));
+ },
+
+ onModalClose() {
+ dispatch(closeModal());
+ },
+
+ onModalOpen(props) {
+ dispatch(openModal('ACTIONS', props));
+ },
+});
+
+export default connect(mapStateToProps, mapDispatchToProps)(Options);
--
cgit
From faff152ae52a406a1280b716543d737fd034badd Mon Sep 17 00:00:00 2001
From: Thibaut Girka
Date: Sun, 21 Apr 2019 18:48:33 +0200
Subject: Move ComposerPublisher to Compose
---
.../features/compose/components/compose_form.js | 5 +-
.../features/compose/components/publisher.js | 115 +++++++++++++++++++
.../glitch/features/composer/publisher/index.js | 122 ---------------------
3 files changed, 117 insertions(+), 125 deletions(-)
create mode 100644 app/javascript/flavours/glitch/features/compose/components/publisher.js
delete mode 100644 app/javascript/flavours/glitch/features/composer/publisher/index.js
diff --git a/app/javascript/flavours/glitch/features/compose/components/compose_form.js b/app/javascript/flavours/glitch/features/compose/components/compose_form.js
index 76a5117bc..2b9d2dca6 100644
--- a/app/javascript/flavours/glitch/features/compose/components/compose_form.js
+++ b/app/javascript/flavours/glitch/features/compose/components/compose_form.js
@@ -6,7 +6,7 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
// Components.
import OptionsContainer from '../containers/options_container';
-import ComposerPublisher from '../../composer/publisher';
+import Publisher from './publisher';
import TextareaIcons from './textarea_icons';
import UploadFormContainer from '../containers/upload_form_container';
import PollFormContainer from '../containers/poll_form_container';
@@ -367,10 +367,9 @@ class ComposeForm extends ImmutablePureComponent {
spoiler={spoilersAlwaysOn ? (spoilerText && spoilerText.length > 0) : spoiler}
/>
-
+ {diff}
+ {sideArm && sideArm !== 'none' ? (
+
+
+
+ }
+ title={`${intl.formatMessage(messages.publish)}: ${intl.formatMessage({ id: `privacy.${sideArm}.short` })}`}
+ />
+ ) : null}
+
+
+ {' '}
+
+
+ );
+ case privacy === 'public':
+ return (
+
+ }}
+ />
+
+ );
+ default:
+ return ;
+ }
+ }()}
+ title={`${intl.formatMessage(messages.publish)}: ${intl.formatMessage({ id: `privacy.${privacy}.short` })}`}
+ onClick={onSubmit}
+ disabled={disabled || diff < 0}
+ />
+
+ );
+ };
+}
diff --git a/app/javascript/flavours/glitch/features/composer/publisher/index.js b/app/javascript/flavours/glitch/features/composer/publisher/index.js
deleted file mode 100644
index dc9c8f8eb..000000000
--- a/app/javascript/flavours/glitch/features/composer/publisher/index.js
+++ /dev/null
@@ -1,122 +0,0 @@
-// Package imports.
-import classNames from 'classnames';
-import PropTypes from 'prop-types';
-import React from 'react';
-import {
- defineMessages,
- FormattedMessage,
-} from 'react-intl';
-import { length } from 'stringz';
-
-// Components.
-import Button from 'flavours/glitch/components/button';
-import Icon from 'flavours/glitch/components/icon';
-
-// Utils.
-import { maxChars } from 'flavours/glitch/util/initial_state';
-
-// Messages.
-const messages = defineMessages({
- publish: {
- defaultMessage: 'Toot',
- id: 'compose_form.publish',
- },
- publishLoud: {
- defaultMessage: '{publish}!',
- id: 'compose_form.publish_loud',
- },
-});
-
-// The component.
-export default function ComposerPublisher ({
- countText,
- disabled,
- intl,
- onSecondarySubmit,
- onSubmit,
- privacy,
- sideArm,
-}) {
- const diff = maxChars - length(countText || '');
- const computedClass = classNames('composer--publisher', {
- disabled: disabled || diff < 0,
- over: diff < 0,
- });
-
- // The result.
- return (
-
- {diff}
- {sideArm && sideArm !== 'none' ? (
-
-
-
- }
- title={`${intl.formatMessage(messages.publish)}: ${intl.formatMessage({ id: `privacy.${sideArm}.short` })}`}
- />
- ) : null}
-
-
- {' '}
-
-
- );
- case privacy === 'public':
- return (
-
- }}
- />
-
- );
- default:
- return ;
- }
- }()}
- title={`${intl.formatMessage(messages.publish)}: ${intl.formatMessage({ id: `privacy.${privacy}.short` })}`}
- onClick={onSubmit}
- disabled={disabled || diff < 0}
- />
-
- );
-}
-
-// Props.
-ComposerPublisher.propTypes = {
- countText: PropTypes.string,
- disabled: PropTypes.bool,
- intl: PropTypes.object.isRequired,
- onSecondarySubmit: PropTypes.func,
- onSubmit: PropTypes.func,
- privacy: PropTypes.oneOf(['direct', 'private', 'unlisted', 'public']),
- sideArm: PropTypes.oneOf(['none', 'direct', 'private', 'unlisted', 'public']),
-};
--
cgit
From e949861176b633f0a9cdf827c9727f027e7ef037 Mon Sep 17 00:00:00 2001
From: Thibaut Girka
Date: Sun, 21 Apr 2019 19:07:48 +0200
Subject: Cleanup
---
.../features/compose/components/compose_form.js | 173 ++++++++++-----------
.../compose/containers/compose_form_container.js | 61 +++++---
.../flavours/glitch/features/compose/index.js | 36 ++---
3 files changed, 127 insertions(+), 143 deletions(-)
diff --git a/app/javascript/flavours/glitch/features/compose/components/compose_form.js b/app/javascript/flavours/glitch/features/compose/components/compose_form.js
index 2b9d2dca6..88dce5098 100644
--- a/app/javascript/flavours/glitch/features/compose/components/compose_form.js
+++ b/app/javascript/flavours/glitch/features/compose/components/compose_form.js
@@ -1,23 +1,19 @@
import React from 'react';
-import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
+import PropTypes from 'prop-types';
+import ReplyIndicatorContainer from '../containers/reply_indicator_container';
+import AutosuggestTextarea from '../../../components/autosuggest_textarea';
import { defineMessages, injectIntl } from 'react-intl';
+import EmojiPicker from 'flavours/glitch/features/emoji_picker';
+import PollFormContainer from '../containers/poll_form_container';
+import UploadFormContainer from '../containers/upload_form_container';
+import WarningContainer from '../containers/warning_container';
+import { isMobile } from 'flavours/glitch/util/is_mobile';
import ImmutablePureComponent from 'react-immutable-pure-component';
-
-// Components.
+import { countableText } from 'flavours/glitch/util/counter';
import OptionsContainer from '../containers/options_container';
import Publisher from './publisher';
import TextareaIcons from './textarea_icons';
-import UploadFormContainer from '../containers/upload_form_container';
-import PollFormContainer from '../containers/poll_form_container';
-import WarningContainer from '../containers/warning_container';
-import ReplyIndicatorContainer from '../containers/reply_indicator_container';
-import EmojiPicker from 'flavours/glitch/features/emoji_picker';
-import AutosuggestTextarea from '../../../components/autosuggest_textarea';
-
-// Utils.
-import { countableText } from 'flavours/glitch/util/counter';
-import { isMobile } from 'flavours/glitch/util/is_mobile';
const messages = defineMessages({
placeholder: { id: 'compose_form.placeholder', defaultMessage: 'What is on your mind?' },
@@ -37,84 +33,50 @@ class ComposeForm extends ImmutablePureComponent {
static propTypes = {
intl: PropTypes.object.isRequired,
-
- // State props.
- advancedOptions: ImmutablePropTypes.map,
- amUnlocked: PropTypes.bool,
+ text: PropTypes.string,
+ suggestions: ImmutablePropTypes.list,
+ spoiler: PropTypes.bool,
+ privacy: PropTypes.string,
+ spoilerText: PropTypes.string,
focusDate: PropTypes.instanceOf(Date),
caretPosition: PropTypes.number,
+ preselectDate: PropTypes.instanceOf(Date),
isSubmitting: PropTypes.bool,
isChangingUpload: PropTypes.bool,
isUploading: PropTypes.bool,
+ onChange: PropTypes.func,
+ onSubmit: PropTypes.func,
+ onClearSuggestions: PropTypes.func,
+ onFetchSuggestions: PropTypes.func,
+ onSuggestionSelected: PropTypes.func,
+ onChangeSpoilerText: PropTypes.func,
+ onPaste: PropTypes.func,
+ onPickEmoji: PropTypes.func,
+ showSearch: PropTypes.bool,
+ anyMedia: PropTypes.bool,
+
+ advancedOptions: ImmutablePropTypes.map,
layout: PropTypes.string,
media: ImmutablePropTypes.list,
- preselectDate: PropTypes.instanceOf(Date),
- privacy: PropTypes.string,
sideArm: PropTypes.string,
sensitive: PropTypes.bool,
- showSearch: PropTypes.bool,
- spoiler: PropTypes.bool,
- spoilerText: PropTypes.string,
- suggestionToken: PropTypes.string,
- suggestions: ImmutablePropTypes.list,
- text: PropTypes.string,
- anyMedia: PropTypes.bool,
spoilersAlwaysOn: PropTypes.bool,
mediaDescriptionConfirmation: PropTypes.bool,
preselectOnReply: PropTypes.bool,
-
- // Dispatch props.
- onChangeSpoilerText: PropTypes.func,
onChangeSpoilerness: PropTypes.func,
- onChangeText: PropTypes.func,
onChangeVisibility: PropTypes.func,
- onClearSuggestions: PropTypes.func,
- onFetchSuggestions: PropTypes.func,
- onInsertEmoji: PropTypes.func,
onMount: PropTypes.func,
- onSelectSuggestion: PropTypes.func,
- onSubmit: PropTypes.func,
onUnmount: PropTypes.func,
- onUpload: PropTypes.func,
+ onPaste: PropTypes.func,
onMediaDescriptionConfirm: PropTypes.func,
};
- // Changes the text value of the spoiler.
- handleChangeSpoiler = ({ target: { value } }) => {
- const { onChangeSpoilerText } = this.props;
- if (onChangeSpoilerText) {
- onChangeSpoilerText(value);
- }
- }
-
- // Inserts an emoji at the caret.
- handleEmoji = (data) => {
- const { textarea: { selectionStart } } = this;
- const { onInsertEmoji } = this.props;
- if (onInsertEmoji) {
- onInsertEmoji(selectionStart, data);
- }
- }
-
- // Handles the secondary submit button.
- handleSecondarySubmit = () => {
- const { handleSubmit } = this.handlers;
- const {
- onChangeVisibility,
- sideArm,
- } = this.props;
- if (sideArm !== 'none' && onChangeVisibility) {
- onChangeVisibility(sideArm);
- }
- handleSubmit();
- }
+ static defaultProps = {
+ showSearch: false,
+ };
- // Selects a suggestion from the autofill.
- handleSelect = (tokenStart, token, value) => {
- const { onSelectSuggestion } = this.props;
- if (onSelectSuggestion) {
- onSelectSuggestion(tokenStart, token, value);
- }
+ handleChange = (e) => {
+ this.props.onChange(e.target.value);
}
handleKeyDown = ({ ctrlKey, keyCode, metaKey, altKey }) => {
@@ -129,18 +91,10 @@ class ComposeForm extends ImmutablePureComponent {
}
}
- // When the escape key is released, we focus the UI.
- handleKeyUp = ({ key }) => {
- if (key === 'Escape') {
- document.querySelector('.ui').parentElement.focus();
- }
- }
-
- // Submits the status.
handleSubmit = () => {
const { textarea: { value }, uploadForm } = this;
const {
- onChangeText,
+ onChange,
onSubmit,
isSubmitting,
isChangingUpload,
@@ -154,8 +108,8 @@ class ComposeForm extends ImmutablePureComponent {
// If something changes inside the textarea, then we update the
// state before submitting.
- if (onChangeText && text !== value) {
- onChangeText(value);
+ if (onChange && text !== value) {
+ onChange(value);
}
// Submit disabled:
@@ -178,6 +132,48 @@ class ComposeForm extends ImmutablePureComponent {
}
}
+ // Changes the text value of the spoiler.
+ handleChangeSpoiler = ({ target: { value } }) => {
+ const { onChangeSpoilerText } = this.props;
+ if (onChangeSpoilerText) {
+ onChangeSpoilerText(value);
+ }
+ }
+
+ // Inserts an emoji at the caret.
+ handleEmoji = (data) => {
+ const { textarea: { selectionStart } } = this;
+ const { onPickEmoji } = this.props;
+ if (onPickEmoji) {
+ onPickEmoji(selectionStart, data);
+ }
+ }
+
+ // Handles the secondary submit button.
+ handleSecondarySubmit = () => {
+ const { handleSubmit } = this.handlers;
+ const {
+ onChangeVisibility,
+ sideArm,
+ } = this.props;
+ if (sideArm !== 'none' && onChangeVisibility) {
+ onChangeVisibility(sideArm);
+ }
+ handleSubmit();
+ }
+
+ // Selects a suggestion from the autofill.
+ onSuggestionSelected = (tokenStart, token, value) => {
+ this.props.onSuggestionSelected(tokenStart, token, value);
+ }
+
+ // When the escape key is released, we focus the UI.
+ handleKeyUp = ({ key }) => {
+ if (key === 'Escape') {
+ document.querySelector('.ui').parentElement.focus();
+ }
+ }
+
// Sets a reference to the textarea.
setAutosuggestTextarea = (textareaComponent) => {
if (textareaComponent) {
@@ -265,9 +261,6 @@ class ComposeForm extends ImmutablePureComponent {
}
}
- handleChange = (e) => {
- this.props.onChangeText(e.target.value);
- }
render () {
const {
@@ -279,7 +272,6 @@ class ComposeForm extends ImmutablePureComponent {
} = this;
const {
advancedOptions,
- amUnlocked,
anyMedia,
intl,
isSubmitting,
@@ -288,11 +280,10 @@ class ComposeForm extends ImmutablePureComponent {
layout,
media,
onChangeSpoilerness,
- onChangeText,
onChangeVisibility,
onClearSuggestions,
onFetchSuggestions,
- onUpload,
+ onPaste,
privacy,
sensitive,
showSearch,
@@ -343,8 +334,8 @@ class ComposeForm extends ImmutablePureComponent {
onKeyDown={this.handleKeyDown}
onSuggestionsFetchRequested={onFetchSuggestions}
onSuggestionsClearRequested={onClearSuggestions}
- onSuggestionSelected={this.handleSelect}
- onPaste={onUpload}
+ onSuggestionSelected={this.onSuggestionSelected}
+ onPaste={onPaste}
autoFocus={!showSearch && !isMobile(window.innerWidth, layout)}
/>
@@ -361,7 +352,7 @@ class ComposeForm extends ImmutablePureComponent {
disabled={isSubmitting}
onChangeVisibility={onChangeVisibility}
onToggleSpoiler={spoilersAlwaysOn ? null : onChangeSpoilerness}
- onUpload={onUpload}
+ onPaste={onPaste}
privacy={privacy}
sensitive={sensitive || (spoilersAlwaysOn && spoilerText && spoilerText.length > 0)}
spoiler={spoilersAlwaysOn ? (spoilerText && spoilerText.length > 0) : spoiler}
diff --git a/app/javascript/flavours/glitch/features/compose/containers/compose_form_container.js b/app/javascript/flavours/glitch/features/compose/containers/compose_form_container.js
index f4161aa8f..5458e0919 100644
--- a/app/javascript/flavours/glitch/features/compose/containers/compose_form_container.js
+++ b/app/javascript/flavours/glitch/features/compose/containers/compose_form_container.js
@@ -1,4 +1,5 @@
import { connect } from 'react-redux';
+import { defineMessages } from 'react-intl';
import ComposeForm from '../components/compose_form';
import {
changeCompose,
@@ -20,7 +21,6 @@ import {
import { changeLocalSetting } from 'flavours/glitch/actions/local_settings';
import { privacyPreference } from 'flavours/glitch/util/privacy_preference';
-import { me } from 'flavours/glitch/util/initial_state';
const messages = defineMessages({
missingDescriptionMessage: { id: 'confirmations.missing_media_description.message',
@@ -28,7 +28,6 @@ const messages = defineMessages({
missingDescriptionConfirm: { id: 'confirmations.missing_media_description.confirm',
defaultMessage: 'Send anyway' },
});
-import { defineMessages } from 'react-intl';
// State mapping.
function mapStateToProps (state) {
@@ -49,7 +48,6 @@ function mapStateToProps (state) {
sideArmPrivacy = sideArmPrivacy || sideArmBasePrivacy;
return {
advancedOptions: state.getIn(['compose', 'advanced_options']),
- amUnlocked: !state.getIn(['accounts', me, 'locked']),
focusDate: state.getIn(['compose', 'focusDate']),
caretPosition: state.getIn(['compose', 'caretPosition']),
isSubmitting: state.getIn(['compose', 'is_submitting']),
@@ -64,7 +62,6 @@ function mapStateToProps (state) {
showSearch: state.getIn(['search', 'submitted']) && !state.getIn(['search', 'hidden']),
spoiler: spoilersAlwaysOn || state.getIn(['compose', 'spoiler']),
spoilerText: state.getIn(['compose', 'spoiler_text']),
- suggestionToken: state.getIn(['compose', 'suggestion_token']),
suggestions: state.getIn(['compose', 'suggestions']),
text: state.getIn(['compose', 'text']),
anyMedia: state.getIn(['compose', 'media_attachments']).size > 0,
@@ -76,33 +73,55 @@ function mapStateToProps (state) {
// Dispatch mapping.
const mapDispatchToProps = (dispatch, { intl }) => ({
- onChangeSpoilerText(text) {
- dispatch(changeComposeSpoilerText(text));
- },
- onChangeSpoilerness() {
- dispatch(changeComposeSpoilerness());
- },
- onChangeText(text) {
+
+ onChange(text) {
dispatch(changeCompose(text));
},
- onChangeVisibility(value) {
- dispatch(changeComposeVisibility(value));
+
+ onSubmit(routerHistory) {
+ dispatch(submitCompose(routerHistory));
},
+
onClearSuggestions() {
dispatch(clearComposeSuggestions());
},
+
onFetchSuggestions(token) {
dispatch(fetchComposeSuggestions(token));
},
- onInsertEmoji(position, emoji) {
+
+ onSuggestionSelected(position, token, suggestion) {
+ dispatch(selectComposeSuggestion(position, token, suggestion));
+ },
+
+ onChangeSpoilerText(text) {
+ dispatch(changeComposeSpoilerText(text));
+ },
+
+ onPaste(files) {
+ dispatch(uploadCompose(files));
+ },
+
+ onPickEmoji(position, emoji) {
dispatch(insertEmojiCompose(position, emoji));
},
+
+ onChangeSpoilerness() {
+ dispatch(changeComposeSpoilerness());
+ },
+
+ onChangeVisibility(value) {
+ dispatch(changeComposeVisibility(value));
+ },
+
onMount() {
dispatch(mountCompose());
},
- onSelectSuggestion(position, token, suggestion) {
- dispatch(selectComposeSuggestion(position, token, suggestion));
+
+ onUnmount() {
+ dispatch(unmountCompose());
},
+
onMediaDescriptionConfirm(routerHistory) {
dispatch(openModal('CONFIRM', {
message: intl.formatMessage(messages.missingDescriptionMessage),
@@ -111,15 +130,7 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
onDoNotAsk: () => dispatch(changeLocalSetting(['confirm_missing_media_description'], false)),
}));
},
- onSubmit(routerHistory) {
- dispatch(submitCompose(routerHistory));
- },
- onUnmount() {
- dispatch(unmountCompose());
- },
- onUpload(files) {
- dispatch(uploadCompose(files));
- },
+
});
export default connect(mapStateToProps, mapDispatchToProps)(ComposeForm);
diff --git a/app/javascript/flavours/glitch/features/compose/index.js b/app/javascript/flavours/glitch/features/compose/index.js
index 01e7d1906..a7795a04d 100644
--- a/app/javascript/flavours/glitch/features/compose/index.js
+++ b/app/javascript/flavours/glitch/features/compose/index.js
@@ -1,63 +1,46 @@
-// Package imports.
import React from 'react';
-import { connect } from 'react-redux';
+import ComposeFormContainer from './containers/compose_form_container';
+import NavigationContainer from './containers/navigation_container';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
+import { connect } from 'react-redux';
import { injectIntl, defineMessages } from 'react-intl';
import classNames from 'classnames';
-
-// Actions.
-import { cycleElefriendCompose } from 'flavours/glitch/actions/compose';
-
-// Components.
-import ComposeFormContainer from './containers/compose_form_container';
-import HeaderContainer from './containers/header_container';
import SearchContainer from './containers/search_container';
-import SearchResultsContainer from './containers/search_results_container';
-import NavigationContainer from './containers/navigation_container';
+import Motion from 'flavours/glitch/util/optional_motion';
import spring from 'react-motion/lib/spring';
-
-// Utils.
+import SearchResultsContainer from './containers/search_results_container';
import { me, mascot } from 'flavours/glitch/util/initial_state';
-import Motion from 'flavours/glitch/util/optional_motion';
+import { cycleElefriendCompose } from 'flavours/glitch/actions/compose';
+import HeaderContainer from './containers/header_container';
-// Messages.
const messages = defineMessages({
compose: { id: 'navigation_bar.compose', defaultMessage: 'Compose new toot' },
});
-// State mapping.
const mapStateToProps = (state, ownProps) => ({
elefriend: state.getIn(['compose', 'elefriend']),
showSearch: ownProps.multiColumn ? state.getIn(['search', 'submitted']) && !state.getIn(['search', 'hidden']) : ownProps.isSearchPage,
});
-// Dispatch mapping.
const mapDispatchToProps = (dispatch, { intl }) => ({
onClickElefriend () {
dispatch(cycleElefriendCompose());
},
});
-// The component.
export default @connect(mapStateToProps, mapDispatchToProps)
@injectIntl
class Compose extends React.PureComponent {
static propTypes = {
- intl: PropTypes.object.isRequired,
- isSearchPage: PropTypes.bool,
multiColumn: PropTypes.bool,
showSearch: PropTypes.bool,
-
- // State props.
+ isSearchPage: PropTypes.bool,
elefriend: PropTypes.number,
- unreadNotifications: PropTypes.number,
-
- // Dispatch props.
onClickElefriend: PropTypes.func,
+ intl: PropTypes.object.isRequired,
};
- // Rendering.
render () {
const {
elefriend,
@@ -69,7 +52,6 @@ class Compose extends React.PureComponent {
} = this.props;
const computedClass = classNames('drawer', `mbstobon-${elefriend}`);
- // The result.
return (
{multiColumn &&
}
--
cgit
From d779ec1f0e68ffdfa3ada2992bb2b1ef066655ef Mon Sep 17 00:00:00 2001
From: Thibaut Girka
Date: Sun, 21 Apr 2019 22:59:58 +0200
Subject: Fix broken reference to handleSubmit
---
.../flavours/glitch/features/compose/components/compose_form.js | 7 +++----
1 file changed, 3 insertions(+), 4 deletions(-)
diff --git a/app/javascript/flavours/glitch/features/compose/components/compose_form.js b/app/javascript/flavours/glitch/features/compose/components/compose_form.js
index 88dce5098..1433ed8c7 100644
--- a/app/javascript/flavours/glitch/features/compose/components/compose_form.js
+++ b/app/javascript/flavours/glitch/features/compose/components/compose_form.js
@@ -82,12 +82,12 @@ class ComposeForm extends ImmutablePureComponent {
handleKeyDown = ({ ctrlKey, keyCode, metaKey, altKey }) => {
// We submit the status on control/meta + enter.
if (keyCode === 13 && (ctrlKey || metaKey)) {
- handleSubmit();
+ this.handleSubmit();
}
// Submit the status with secondary visibility on alt + enter.
if (keyCode === 13 && altKey) {
- handleSecondarySubmit();
+ this.handleSecondarySubmit();
}
}
@@ -151,7 +151,6 @@ class ComposeForm extends ImmutablePureComponent {
// Handles the secondary submit button.
handleSecondarySubmit = () => {
- const { handleSubmit } = this.handlers;
const {
onChangeVisibility,
sideArm,
@@ -159,7 +158,7 @@ class ComposeForm extends ImmutablePureComponent {
if (sideArm !== 'none' && onChangeVisibility) {
onChangeVisibility(sideArm);
}
- handleSubmit();
+ this.handleSubmit();
}
// Selects a suggestion from the autofill.
--
cgit
From c3fa4e8e07e5bcc685163959833a989fb15e8029 Mon Sep 17 00:00:00 2001
From: Thibaut Girka
Date: Mon, 22 Apr 2019 15:41:40 +0200
Subject: Fix ReferenceError in dropdown
---
.../flavours/glitch/features/compose/components/dropdown_menu.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/javascript/flavours/glitch/features/compose/components/dropdown_menu.js b/app/javascript/flavours/glitch/features/compose/components/dropdown_menu.js
index b4e2ec07b..19d35a8f4 100644
--- a/app/javascript/flavours/glitch/features/compose/components/dropdown_menu.js
+++ b/app/javascript/flavours/glitch/features/compose/components/dropdown_menu.js
@@ -91,7 +91,7 @@ class ComposerOptionsDropdownContentItem extends ImmutablePureComponent {
{text}
- {meta ? meta : nil}
+ {meta}
);
--
cgit
From 039e35560c04e4af1bb35b8ab11b9e145c8a4985 Mon Sep 17 00:00:00 2001
From: kedama
Date: Mon, 22 Apr 2019 21:55:50 +0900
Subject: [Glitch] Fix modal items cannot scroll on touch devices
Port d763d39d2628bef123cdc801b2a3a3922b7e37f2 to glitch-soc
---
.../flavours/glitch/features/ui/components/actions_modal.js | 2 +-
app/javascript/flavours/glitch/styles/components/modal.scss | 5 +++++
2 files changed, 6 insertions(+), 1 deletion(-)
diff --git a/app/javascript/flavours/glitch/features/ui/components/actions_modal.js b/app/javascript/flavours/glitch/features/ui/components/actions_modal.js
index 9ac6dcf49..724f1c764 100644
--- a/app/javascript/flavours/glitch/features/ui/components/actions_modal.js
+++ b/app/javascript/flavours/glitch/features/ui/components/actions_modal.js
@@ -114,7 +114,7 @@ export default class ActionsModal extends ImmutablePureComponent {
{status}
-
+
{this.props.actions.map(this.renderAction)}
diff --git a/app/javascript/flavours/glitch/styles/components/modal.scss b/app/javascript/flavours/glitch/styles/components/modal.scss
index 05af34680..2e5b7be55 100644
--- a/app/javascript/flavours/glitch/styles/components/modal.scss
+++ b/app/javascript/flavours/glitch/styles/components/modal.scss
@@ -621,6 +621,11 @@
ul {
overflow-y: auto;
flex-shrink: 0;
+ max-height: 80vh;
+
+ &.with-status {
+ max-height: calc(80vh - 75px);
+ }
li:empty {
margin: 0;
--
cgit
From 2e3640ecbb9050405f9bcfefc9ae92ebbffd23cd Mon Sep 17 00:00:00 2001
From: Thibaut Girka
Date: Wed, 17 Apr 2019 14:23:36 +0200
Subject: Accept richer text from remote statuses
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Support abbr, del, pre, blockquote, code, strong, b, em, i, and h1…h5
HTML elements in remote statuses, add corresponding CSS.
---
.../flavours/glitch/styles/components/status.scss | 29 +++++++++++++++++++++-
app/lib/sanitize_config.rb | 11 +++++---
2 files changed, 35 insertions(+), 5 deletions(-)
diff --git a/app/javascript/flavours/glitch/styles/components/status.scss b/app/javascript/flavours/glitch/styles/components/status.scss
index b73dd3d09..ce07bd64a 100644
--- a/app/javascript/flavours/glitch/styles/components/status.scss
+++ b/app/javascript/flavours/glitch/styles/components/status.scss
@@ -22,7 +22,7 @@
margin: -3px 0 0;
}
- p {
+ p, pre, blockquote {
margin-bottom: 20px;
white-space: pre-wrap;
@@ -31,6 +31,33 @@
}
}
+ h1, h2, h3, h4, h5 {
+ margin-top: 20px;
+ margin-bottom: 20px;
+ }
+
+ h1, h2 {
+ font-weight: 500;
+ font-size: 18px;
+ }
+
+ h2 {
+ font-size: 16px;
+ }
+
+ blockquote {
+ margin-left: 20px;
+ color: $dark-text-color;
+ }
+
+ b, strong {
+ font-weight: 500;
+ }
+
+ em, i {
+ font-style: italic;
+ }
+
a {
color: $secondary-text-color;
text-decoration: none;
diff --git a/app/lib/sanitize_config.rb b/app/lib/sanitize_config.rb
index 1bba4a5a6..7c376c412 100644
--- a/app/lib/sanitize_config.rb
+++ b/app/lib/sanitize_config.rb
@@ -20,11 +20,13 @@ class Sanitize
end
MASTODON_STRICT ||= freeze_config(
- elements: %w(p br span a),
+ elements: %w(p br span a abbr del pre blockquote code b strong i em h1 h2 h3 h4 h5),
attributes: {
- 'a' => %w(href rel class),
- 'span' => %w(class),
+ 'a' => %w(href rel class title),
+ 'span' => %w(class),
+ 'abbr' => %w(title),
+ 'blockquote' => %w(cite),
},
add_attributes: {
@@ -35,7 +37,8 @@ class Sanitize
},
protocols: {
- 'a' => { 'href' => HTTP_PROTOCOLS },
+ 'a' => { 'href' => HTTP_PROTOCOLS },
+ 'blockquote' => { 'cite' => HTTP_PROTOCOLS },
},
transformers: [
--
cgit
From 659f972bed63d9e8f18791ba4e0f779169f5fb4e Mon Sep 17 00:00:00 2001
From: Thibaut Girka
Date: Sun, 21 Apr 2019 21:44:31 +0200
Subject: Do not switch to italic for status media buttons
---
.../flavours/glitch/components/status_content.js | 4 ++-
.../flavours/glitch/styles/components/status.scss | 42 +++++++++++-----------
2 files changed, 25 insertions(+), 21 deletions(-)
diff --git a/app/javascript/flavours/glitch/components/status_content.js b/app/javascript/flavours/glitch/components/status_content.js
index ae14c949a..fc11b8091 100644
--- a/app/javascript/flavours/glitch/components/status_content.js
+++ b/app/javascript/flavours/glitch/components/status_content.js
@@ -213,6 +213,7 @@ export default class StatusContent extends React.PureComponent {
style={directionStyle}
tabIndex={!hidden ? 0 : null}
dangerouslySetInnerHTML={content}
+ className='status__text'
lang={status.get('language')}
/>
{media}
@@ -233,6 +234,7 @@ export default class StatusContent extends React.PureComponent {
ref={this.setRef}
dangerouslySetInnerHTML={content}
lang={status.get('language')}
+ className='status__text'
tabIndex='0'
/>
{media}
@@ -245,7 +247,7 @@ export default class StatusContent extends React.PureComponent {
style={directionStyle}
tabIndex='0'
>
-
+
{media}
);
diff --git a/app/javascript/flavours/glitch/styles/components/status.scss b/app/javascript/flavours/glitch/styles/components/status.scss
index ce07bd64a..91467c886 100644
--- a/app/javascript/flavours/glitch/styles/components/status.scss
+++ b/app/javascript/flavours/glitch/styles/components/status.scss
@@ -31,31 +31,33 @@
}
}
- h1, h2, h3, h4, h5 {
- margin-top: 20px;
- margin-bottom: 20px;
- }
+ .status__text {
+ h1, h2, h3, h4, h5 {
+ margin-top: 20px;
+ margin-bottom: 20px;
+ }
- h1, h2 {
- font-weight: 500;
- font-size: 18px;
- }
+ h1, h2 {
+ font-weight: 500;
+ font-size: 18px;
+ }
- h2 {
- font-size: 16px;
- }
+ h2 {
+ font-size: 16px;
+ }
- blockquote {
- margin-left: 20px;
- color: $dark-text-color;
- }
+ blockquote {
+ margin-left: 20px;
+ color: $dark-text-color;
+ }
- b, strong {
- font-weight: 500;
- }
+ b, strong {
+ font-weight: 500;
+ }
- em, i {
- font-style: italic;
+ em, i {
+ font-style: italic;
+ }
}
a {
--
cgit
From 27e8354914cb9f6725c911d5825399fc265852fe Mon Sep 17 00:00:00 2001
From: Thibaut Girka
Date: Sun, 21 Apr 2019 22:04:19 +0200
Subject: Add support for lists in statuses
---
app/javascript/flavours/glitch/styles/components/status.scss | 12 ++++++++++++
app/lib/sanitize_config.rb | 2 +-
2 files changed, 13 insertions(+), 1 deletion(-)
diff --git a/app/javascript/flavours/glitch/styles/components/status.scss b/app/javascript/flavours/glitch/styles/components/status.scss
index 91467c886..c09e3398d 100644
--- a/app/javascript/flavours/glitch/styles/components/status.scss
+++ b/app/javascript/flavours/glitch/styles/components/status.scss
@@ -58,6 +58,18 @@
em, i {
font-style: italic;
}
+
+ ul, ol {
+ margin-left: 1em;
+ }
+
+ ul {
+ list-style-type: disc;
+ }
+
+ ol {
+ list-style-type: decimal;
+ }
}
a {
diff --git a/app/lib/sanitize_config.rb b/app/lib/sanitize_config.rb
index 7c376c412..0a87bf6df 100644
--- a/app/lib/sanitize_config.rb
+++ b/app/lib/sanitize_config.rb
@@ -20,7 +20,7 @@ class Sanitize
end
MASTODON_STRICT ||= freeze_config(
- elements: %w(p br span a abbr del pre blockquote code b strong i em h1 h2 h3 h4 h5),
+ elements: %w(p br span a abbr del pre blockquote code b strong i em h1 h2 h3 h4 h5 ul ol li),
attributes: {
'a' => %w(href rel class title),
--
cgit
From 2db0506226d0de38115e912a632d7a30afeb651e Mon Sep 17 00:00:00 2001
From: Thibaut Girka
Date: Thu, 4 Apr 2019 19:38:48 +0200
Subject: Load custom CSS rules after standard ones
Fixes #933
Due to glitch-soc's theming system, JS and CSS files are imported in a slightly
different order, and custom CSS rules were loaded *before* everything else.
They will now be loaded *after* everything else, which is a bit different from
upstream but should work better overall.
---
app/views/layouts/application.html.haml | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml
index 7d3daf6c9..34c25a7d1 100755
--- a/app/views/layouts/application.html.haml
+++ b/app/views/layouts/application.html.haml
@@ -28,14 +28,14 @@
= javascript_pack_tag "locales/#{@theme[:flavour]}/en", integrity: true, crossorigin: 'anonymous'
= csrf_meta_tags
- - if Setting.custom_css.present?
- = stylesheet_link_tag custom_css_path, media: 'all'
-
= yield :header_tags
-# These must come after :header_tags to ensure our initial state has been defined.
= render partial: 'layouts/theme', object: @core
= render partial: 'layouts/theme', object: @theme
+ - if Setting.custom_css.present?
+ = stylesheet_link_tag custom_css_path, media: 'all'
+
%body{ class: body_classes }
= content_for?(:content) ? yield(:content) : yield
--
cgit
From 08afd6b69cdafc2b7c054042bb737e95d8ded568 Mon Sep 17 00:00:00 2001
From: Thibaut Girka
Date: Mon, 22 Apr 2019 19:10:00 +0200
Subject: Do not leak local-only toots to remote mentioned users
---
app/services/process_mentions_service.rb | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/app/services/process_mentions_service.rb b/app/services/process_mentions_service.rb
index 2595c5fd3..1d9448e21 100644
--- a/app/services/process_mentions_service.rb
+++ b/app/services/process_mentions_service.rb
@@ -48,9 +48,9 @@ class ProcessMentionsService < BaseService
if mentioned_account.local?
LocalNotificationWorker.perform_async(mentioned_account.id, mention.id, mention.class.name)
- elsif mentioned_account.ostatus? && !@status.stream_entry.hidden?
+ elsif mentioned_account.ostatus? && !@status.stream_entry.hidden? && !@status.local_only?
NotificationWorker.perform_async(ostatus_xml, @status.account_id, mentioned_account.id)
- elsif mentioned_account.activitypub?
+ elsif mentioned_account.activitypub? && !@status.local_only?
ActivityPub::DeliveryWorker.perform_async(activitypub_json, mention.status.account_id, mentioned_account.inbox_url)
end
end
--
cgit
From 50b36ef59de4bac5375c68f41d49e16132eb1f86 Mon Sep 17 00:00:00 2001
From: Fabian Schlenz
Date: Tue, 23 Apr 2019 00:54:26 +0200
Subject: Fixed Healthchecks in docker-compose.yml (#10553)
---
docker-compose.yml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/docker-compose.yml b/docker-compose.yml
index faa066149..47662d470 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -43,7 +43,7 @@ services:
- external_network
- internal_network
healthcheck:
- test: ["CMD-SHELL", "wget -q --spider --header 'x-forwarded-proto: https' --proxy off localhost:3000/api/v1/instance || exit 1"]
+ test: ["CMD-SHELL", "wget -q --spider --header 'x-forwarded-proto: https' --proxy=off localhost:3000/api/v1/instance || exit 1"]
ports:
- "127.0.0.1:3000:3000"
depends_on:
@@ -63,7 +63,7 @@ services:
- external_network
- internal_network
healthcheck:
- test: ["CMD-SHELL", "wget -q --spider --header 'x-forwarded-proto: https' --proxy off localhost:4000/api/v1/streaming/health || exit 1"]
+ test: ["CMD-SHELL", "wget -q --spider --header 'x-forwarded-proto: https' --proxy=off localhost:4000/api/v1/streaming/health || exit 1"]
ports:
- "127.0.0.1:4000:4000"
depends_on:
--
cgit
From 703cdf2a0cfb283b9d96389e9f76f54c46f6b15d Mon Sep 17 00:00:00 2001
From: "dependabot[bot]"
Date: Tue, 23 Apr 2019 00:54:51 +0200
Subject: Bump capybara from 3.16.2 to 3.17.0 (#10602)
Bumps [capybara](https://github.com/teamcapybara/capybara) from 3.16.2 to 3.17.0.
- [Release notes](https://github.com/teamcapybara/capybara/releases)
- [Changelog](https://github.com/teamcapybara/capybara/blob/master/History.md)
- [Commits](https://github.com/teamcapybara/capybara/compare/3.16.2...3.17.0)
Signed-off-by: dependabot[bot]
---
Gemfile | 2 +-
Gemfile.lock | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/Gemfile b/Gemfile
index 93e495de3..005bdf4ab 100644
--- a/Gemfile
+++ b/Gemfile
@@ -107,7 +107,7 @@ group :production, :test do
end
group :test do
- gem 'capybara', '~> 3.16'
+ gem 'capybara', '~> 3.17'
gem 'climate_control', '~> 0.2'
gem 'faker', '~> 1.9'
gem 'microformats', '~> 4.1'
diff --git a/Gemfile.lock b/Gemfile.lock
index 80942a8db..8d52a1b3c 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -127,7 +127,7 @@ GEM
sshkit (~> 1.3)
capistrano-yarn (2.0.2)
capistrano (~> 3.0)
- capybara (3.16.2)
+ capybara (3.17.0)
addressable
mini_mime (>= 0.1.3)
nokogiri (~> 1.8)
@@ -670,7 +670,7 @@ DEPENDENCIES
capistrano-rails (~> 1.4)
capistrano-rbenv (~> 2.1)
capistrano-yarn (~> 2.0)
- capybara (~> 3.16)
+ capybara (~> 3.17)
charlock_holmes (~> 0.7.6)
chewy (~> 5.0)
cld3 (~> 3.2.4)
--
cgit
From f99bfb9496482c7f3734789b3c16fc19e95e0c75 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]"
Date: Tue, 23 Apr 2019 10:02:58 +0900
Subject: [Security] Bump nokogiri from 1.10.2 to 1.10.3 (#10620)
Bumps [nokogiri](https://github.com/sparklemotion/nokogiri) from 1.10.2 to 1.10.3. **This update includes security fixes.**
- [Release notes](https://github.com/sparklemotion/nokogiri/releases)
- [Changelog](https://github.com/sparklemotion/nokogiri/blob/master/CHANGELOG.md)
- [Commits](https://github.com/sparklemotion/nokogiri/compare/v1.10.2...v1.10.3)
Signed-off-by: dependabot[bot]
---
Gemfile.lock | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Gemfile.lock b/Gemfile.lock
index 8d52a1b3c..3f87f3b6e 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -355,7 +355,7 @@ GEM
net-ssh (>= 2.6.5)
net-ssh (5.0.2)
nio4r (2.3.1)
- nokogiri (1.10.2)
+ nokogiri (1.10.3)
mini_portile2 (~> 2.4.0)
nokogumbo (2.0.0)
nokogiri (~> 1.8, >= 1.8.4)
--
cgit
From 041fb0e840d85a6e9be2ff97fb80c3bf36f94644 Mon Sep 17 00:00:00 2001
From: Eugen Rochko
Date: Tue, 23 Apr 2019 04:39:21 +0200
Subject: Fix sign up button not saying sign up when invite is used (#10623)
Fix #10616
---
app/views/auth/registrations/new.html.haml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/views/auth/registrations/new.html.haml b/app/views/auth/registrations/new.html.haml
index bd6e3a13f..b4a7cced5 100644
--- a/app/views/auth/registrations/new.html.haml
+++ b/app/views/auth/registrations/new.html.haml
@@ -36,6 +36,6 @@
= f.input :agreement, as: :boolean, wrapper: :with_label, label: t('auth.checkbox_agreement_html', rules_path: about_more_path, terms_path: terms_path)
.actions
- = f.button :button, sign_up_message, type: :submit
+ = f.button :button, @invite.present? ? t('auth.register') : sign_up_message, type: :submit
.form-footer= render 'auth/shared/links'
--
cgit
From 8a0d677cde361b271b42c84056a80535f22549cc Mon Sep 17 00:00:00 2001
From: Eugen Rochko
Date: Tue, 23 Apr 2019 04:39:48 +0200
Subject: Fix stoplight logging to stderr separate from Rails logger (#10624)
---
config/initializers/stoplight.rb | 1 +
1 file changed, 1 insertion(+)
diff --git a/config/initializers/stoplight.rb b/config/initializers/stoplight.rb
index 1bd4ee6e7..7384b2e9a 100644
--- a/config/initializers/stoplight.rb
+++ b/config/initializers/stoplight.rb
@@ -1,3 +1,4 @@
require 'stoplight'
Stoplight::Light.default_data_store = Stoplight::DataStore::Redis.new(Redis.current)
+Stoplight::Light.default_notifiers = [Stoplight::Notifier::Logger.new(Rails.logger)]
--
cgit
From db61383f82b6c75443c026c1aaf2b0d9be48766e Mon Sep 17 00:00:00 2001
From: "dependabot[bot]"
Date: Tue, 23 Apr 2019 18:53:52 +0900
Subject: Bump capybara from 3.17.0 to 3.18.0 (#10626)
Bumps [capybara](https://github.com/teamcapybara/capybara) from 3.17.0 to 3.18.0.
- [Release notes](https://github.com/teamcapybara/capybara/releases)
- [Changelog](https://github.com/teamcapybara/capybara/blob/master/History.md)
- [Commits](https://github.com/teamcapybara/capybara/compare/3.17.0...3.18.0)
Signed-off-by: dependabot[bot]
---
Gemfile | 2 +-
Gemfile.lock | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/Gemfile b/Gemfile
index 005bdf4ab..6fe97412b 100644
--- a/Gemfile
+++ b/Gemfile
@@ -107,7 +107,7 @@ group :production, :test do
end
group :test do
- gem 'capybara', '~> 3.17'
+ gem 'capybara', '~> 3.18'
gem 'climate_control', '~> 0.2'
gem 'faker', '~> 1.9'
gem 'microformats', '~> 4.1'
diff --git a/Gemfile.lock b/Gemfile.lock
index 3f87f3b6e..49bddd3dc 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -127,7 +127,7 @@ GEM
sshkit (~> 1.3)
capistrano-yarn (2.0.2)
capistrano (~> 3.0)
- capybara (3.17.0)
+ capybara (3.18.0)
addressable
mini_mime (>= 0.1.3)
nokogiri (~> 1.8)
@@ -670,7 +670,7 @@ DEPENDENCIES
capistrano-rails (~> 1.4)
capistrano-rbenv (~> 2.1)
capistrano-yarn (~> 2.0)
- capybara (~> 3.17)
+ capybara (~> 3.18)
charlock_holmes (~> 0.7.6)
chewy (~> 5.0)
cld3 (~> 3.2.4)
--
cgit
From 80bbf40ff1871b34bba4c0b133a573da459cf72e Mon Sep 17 00:00:00 2001
From: "dependabot[bot]"
Date: Tue, 23 Apr 2019 16:39:06 +0200
Subject: Bump sidekiq from 5.2.6 to 5.2.7 (#10627)
Bumps [sidekiq](https://github.com/mperham/sidekiq) from 5.2.6 to 5.2.7.
- [Release notes](https://github.com/mperham/sidekiq/releases)
- [Changelog](https://github.com/mperham/sidekiq/blob/master/Changes.md)
- [Commits](https://github.com/mperham/sidekiq/compare/v5.2.6...v5.2.7)
Signed-off-by: dependabot[bot]
---
Gemfile.lock | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Gemfile.lock b/Gemfile.lock
index 49bddd3dc..da984ccae 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -552,7 +552,7 @@ GEM
scss_lint (0.57.1)
rake (>= 0.9, < 13)
sass (~> 3.5, >= 3.5.5)
- sidekiq (5.2.6)
+ sidekiq (5.2.7)
connection_pool (~> 2.2, >= 2.2.2)
rack (>= 1.5.0)
rack-protection (>= 1.5.0)
--
cgit
From 3d722b0c4e14d6d766d2e8814f42c76cfd0f2e2c Mon Sep 17 00:00:00 2001
From: Thibaut Girka
Date: Tue, 23 Apr 2019 18:19:35 +0200
Subject: Fix settings modal
---
.../flavours/glitch/features/compose/containers/header_container.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/javascript/flavours/glitch/features/compose/containers/header_container.js b/app/javascript/flavours/glitch/features/compose/containers/header_container.js
index 6f1978807..ce1dea319 100644
--- a/app/javascript/flavours/glitch/features/compose/containers/header_container.js
+++ b/app/javascript/flavours/glitch/features/compose/containers/header_container.js
@@ -11,7 +11,7 @@ const mapStateToProps = state => {
};
const mapDispatchToProps = (dispatch, { intl }) => ({
- onOpenSettings (e) {
+ onSettingsClick (e) {
e.preventDefault();
e.stopPropagation();
dispatch(openModal('SETTINGS', {}));
--
cgit
From 416aa832e5074d21a6497f66163311658b9da5fb Mon Sep 17 00:00:00 2001
From: Thibaut Girka
Date: Wed, 24 Apr 2019 07:51:35 +0200
Subject: Fix file upload
---
.../flavours/glitch/features/compose/components/compose_form.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/javascript/flavours/glitch/features/compose/components/compose_form.js b/app/javascript/flavours/glitch/features/compose/components/compose_form.js
index 1433ed8c7..a5e34e3a2 100644
--- a/app/javascript/flavours/glitch/features/compose/components/compose_form.js
+++ b/app/javascript/flavours/glitch/features/compose/components/compose_form.js
@@ -351,7 +351,7 @@ class ComposeForm extends ImmutablePureComponent {
disabled={isSubmitting}
onChangeVisibility={onChangeVisibility}
onToggleSpoiler={spoilersAlwaysOn ? null : onChangeSpoilerness}
- onPaste={onPaste}
+ onUpload={onPaste}
privacy={privacy}
sensitive={sensitive || (spoilersAlwaysOn && spoilerText && spoilerText.length > 0)}
spoiler={spoilersAlwaysOn ? (spoilerText && spoilerText.length > 0) : spoiler}
--
cgit
From ffab9d626a41ecb05f8f61f7615005753a0ee060 Mon Sep 17 00:00:00 2001
From: Thibaut Girka
Date: Wed, 24 Apr 2019 15:35:27 +0200
Subject: Hide media upload form if no media are attached
---
.../glitch/features/compose/components/upload_form.js | 12 +++++++-----
.../flavours/glitch/styles/components/composer.scss | 6 ++----
2 files changed, 9 insertions(+), 9 deletions(-)
diff --git a/app/javascript/flavours/glitch/features/compose/components/upload_form.js b/app/javascript/flavours/glitch/features/compose/components/upload_form.js
index a126cc7e4..4864043a8 100644
--- a/app/javascript/flavours/glitch/features/compose/components/upload_form.js
+++ b/app/javascript/flavours/glitch/features/compose/components/upload_form.js
@@ -16,11 +16,13 @@ export default class UploadForm extends ImmutablePureComponent {
-
- {mediaIds.map(id => (
-
- ))}
-
+ {mediaIds.size > 0 && (
+
+ {mediaIds.map(id => (
+
+ ))}
+
+ )}
);
}
diff --git a/app/javascript/flavours/glitch/styles/components/composer.scss b/app/javascript/flavours/glitch/styles/components/composer.scss
index 466b654de..c7851b561 100644
--- a/app/javascript/flavours/glitch/styles/components/composer.scss
+++ b/app/javascript/flavours/glitch/styles/components/composer.scss
@@ -241,16 +241,14 @@
}
.composer--upload_form {
- padding: 5px;
- color: $inverted-text-color;
- background: $simple-background-color;
- font-size: 14px;
+ overflow: hidden;
& > .content {
display: flex;
flex-direction: row;
flex-wrap: wrap;
font-family: inherit;
+ padding: 5px;
overflow: hidden;
}
}
--
cgit
From ef1504d6259c6d656885b4d5c036d498b471530d Mon Sep 17 00:00:00 2001
From: Thibaut Girka
Date: Wed, 24 Apr 2019 17:59:11 +0200
Subject: Fix upload progressbar when image resizing is involved
---
app/javascript/flavours/glitch/actions/compose.js | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/app/javascript/flavours/glitch/actions/compose.js b/app/javascript/flavours/glitch/actions/compose.js
index ac09adceb..ef2500629 100644
--- a/app/javascript/flavours/glitch/actions/compose.js
+++ b/app/javascript/flavours/glitch/actions/compose.js
@@ -227,8 +227,8 @@ export function uploadCompose(files) {
return function (dispatch, getState) {
const uploadLimit = 4;
const media = getState().getIn(['compose', 'media_attachments']);
- const total = Array.from(files).reduce((a, v) => a + v.size, 0);
const progress = new Array(files.length).fill(0);
+ let total = Array.from(files).reduce((a, v) => a + v.size, 0);
if (files.length + media.size > uploadLimit) {
dispatch(showAlert(undefined, messages.uploadErrorLimit));
@@ -248,6 +248,8 @@ export function uploadCompose(files) {
resizeImage(f).then(file => {
const data = new FormData();
data.append('file', file);
+ // Account for disparity in size of original image and resized data
+ total += file.size - f.size;
return api(getState).post('/api/v1/media', data, {
onUploadProgress: function({ loaded }){
--
cgit
From adbb174c9cecc53bf26b46f4e0cc3c5ce4ebf987 Mon Sep 17 00:00:00 2001
From: Thibaut Girka
Date: Wed, 24 Apr 2019 16:39:49 +0200
Subject: Fix status header display in toot mobile menu
---
app/javascript/flavours/glitch/styles/components/modal.scss | 13 ++++++++++---
1 file changed, 10 insertions(+), 3 deletions(-)
diff --git a/app/javascript/flavours/glitch/styles/components/modal.scss b/app/javascript/flavours/glitch/styles/components/modal.scss
index 2e5b7be55..65b2e75f0 100644
--- a/app/javascript/flavours/glitch/styles/components/modal.scss
+++ b/app/javascript/flavours/glitch/styles/components/modal.scss
@@ -415,14 +415,21 @@
position: relative;
flex-direction: column;
+ .status__relative-time {
+ color: $dark-text-color;
+ float: right;
+ font-size: 14px;
+ width: auto;
+ margin: initial;
+ padding: initial;
+ }
+
.status__display-name {
display: flex;
}
.status__avatar {
- height: 28px;
- left: 10px;
- top: 10px;
+ height: 48px;
width: 48px;
}
--
cgit
From ef249a2718f69be4d73ee81f5e3ca523f7a45e1b Mon Sep 17 00:00:00 2001
From: Thibaut Girka
Date: Wed, 24 Apr 2019 18:27:44 +0200
Subject: Rename status__text to status__content__text for consistency with
upstream
---
app/javascript/flavours/glitch/components/status_content.js | 6 +++---
app/javascript/flavours/glitch/styles/components/status.scss | 2 +-
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/app/javascript/flavours/glitch/components/status_content.js b/app/javascript/flavours/glitch/components/status_content.js
index fc11b8091..07a0d1d5d 100644
--- a/app/javascript/flavours/glitch/components/status_content.js
+++ b/app/javascript/flavours/glitch/components/status_content.js
@@ -213,7 +213,7 @@ export default class StatusContent extends React.PureComponent {
style={directionStyle}
tabIndex={!hidden ? 0 : null}
dangerouslySetInnerHTML={content}
- className='status__text'
+ className='status__content__text'
lang={status.get('language')}
/>
{media}
@@ -234,7 +234,7 @@ export default class StatusContent extends React.PureComponent {
ref={this.setRef}
dangerouslySetInnerHTML={content}
lang={status.get('language')}
- className='status__text'
+ className='status__content__text'
tabIndex='0'
/>
{media}
@@ -247,7 +247,7 @@ export default class StatusContent extends React.PureComponent {
style={directionStyle}
tabIndex='0'
>
-
+
{media}
);
diff --git a/app/javascript/flavours/glitch/styles/components/status.scss b/app/javascript/flavours/glitch/styles/components/status.scss
index c09e3398d..9fe3aa349 100644
--- a/app/javascript/flavours/glitch/styles/components/status.scss
+++ b/app/javascript/flavours/glitch/styles/components/status.scss
@@ -31,7 +31,7 @@
}
}
- .status__text {
+ .status__content__text {
h1, h2, h3, h4, h5 {
margin-top: 20px;
margin-bottom: 20px;
--
cgit
From 5a8495dabe8ab9571bc028bc02ddc9372275b1f5 Mon Sep 17 00:00:00 2001
From: Thibaut Girka
Date: Wed, 24 Apr 2019 18:28:33 +0200
Subject: Remove margins around `p` tags in lists
---
app/javascript/flavours/glitch/styles/components/status.scss | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/app/javascript/flavours/glitch/styles/components/status.scss b/app/javascript/flavours/glitch/styles/components/status.scss
index 9fe3aa349..c1afe0832 100644
--- a/app/javascript/flavours/glitch/styles/components/status.scss
+++ b/app/javascript/flavours/glitch/styles/components/status.scss
@@ -61,6 +61,10 @@
ul, ol {
margin-left: 1em;
+
+ p {
+ margin: 0;
+ }
}
ul {
--
cgit
From 9b86707b1e15c1fbfee408f1199cc96156d5c0f0 Mon Sep 17 00:00:00 2001
From: Thibaut Girka
Date: Wed, 24 Apr 2019 18:29:25 +0200
Subject: Make bold text bolder
---
app/javascript/flavours/glitch/styles/components/status.scss | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/javascript/flavours/glitch/styles/components/status.scss b/app/javascript/flavours/glitch/styles/components/status.scss
index c1afe0832..65a41e947 100644
--- a/app/javascript/flavours/glitch/styles/components/status.scss
+++ b/app/javascript/flavours/glitch/styles/components/status.scss
@@ -52,7 +52,7 @@
}
b, strong {
- font-weight: 500;
+ font-weight: 700;
}
em, i {
--
cgit
From 3631c3314a7708f1e29e7d1b0fbc6cbb3d802af3 Mon Sep 17 00:00:00 2001
From: Thibaut Girka
Date: Wed, 24 Apr 2019 18:30:55 +0200
Subject: Remove extra margin in paragraphs inside blockquotes
---
app/javascript/flavours/glitch/styles/components/status.scss | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/app/javascript/flavours/glitch/styles/components/status.scss b/app/javascript/flavours/glitch/styles/components/status.scss
index 65a41e947..f16b43186 100644
--- a/app/javascript/flavours/glitch/styles/components/status.scss
+++ b/app/javascript/flavours/glitch/styles/components/status.scss
@@ -49,6 +49,10 @@
blockquote {
margin-left: 20px;
color: $dark-text-color;
+
+ p:last-child {
+ margin-bottom: 0;
+ }
}
b, strong {
--
cgit
From 8753e5317fb77ac25a120b1baba7043ded9106b9 Mon Sep 17 00:00:00 2001
From: Thibaut Girka
Date: Wed, 24 Apr 2019 18:31:50 +0200
Subject: Better distinguish titles from text
---
app/javascript/flavours/glitch/styles/components/status.scss | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/app/javascript/flavours/glitch/styles/components/status.scss b/app/javascript/flavours/glitch/styles/components/status.scss
index f16b43186..f91fb2027 100644
--- a/app/javascript/flavours/glitch/styles/components/status.scss
+++ b/app/javascript/flavours/glitch/styles/components/status.scss
@@ -38,7 +38,7 @@
}
h1, h2 {
- font-weight: 500;
+ font-weight: 700;
font-size: 18px;
}
@@ -46,6 +46,10 @@
font-size: 16px;
}
+ h3, h4, h5 {
+ font-weight: 500;
+ }
+
blockquote {
margin-left: 20px;
color: $dark-text-color;
--
cgit
From e4b65e6349bf7fa6ca2cdb45c9d0169a6727b9b7 Mon Sep 17 00:00:00 2001
From: Thibaut Girka
Date: Wed, 24 Apr 2019 18:42:17 +0200
Subject: Redesign blockquote styling
---
app/javascript/flavours/glitch/styles/components/status.scss | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/app/javascript/flavours/glitch/styles/components/status.scss b/app/javascript/flavours/glitch/styles/components/status.scss
index f91fb2027..ef4e113dd 100644
--- a/app/javascript/flavours/glitch/styles/components/status.scss
+++ b/app/javascript/flavours/glitch/styles/components/status.scss
@@ -51,8 +51,10 @@
}
blockquote {
- margin-left: 20px;
- color: $dark-text-color;
+ padding-left: 10px;
+ border-left: 3px solid $darker-text-color;
+ color: $darker-text-color;
+ white-space: normal;
p:last-child {
margin-bottom: 0;
--
cgit
From b13c90f3e35cd2ed3d6cfee218b8a8ec62c3b131 Mon Sep 17 00:00:00 2001
From: Thibaut Girka
Date: Wed, 24 Apr 2019 20:30:02 +0200
Subject: Fix color of muted statuses content not wrapped in a p tag
---
app/javascript/flavours/glitch/styles/components/status.scss | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/app/javascript/flavours/glitch/styles/components/status.scss b/app/javascript/flavours/glitch/styles/components/status.scss
index ef4e113dd..889bcaec0 100644
--- a/app/javascript/flavours/glitch/styles/components/status.scss
+++ b/app/javascript/flavours/glitch/styles/components/status.scss
@@ -647,7 +647,8 @@
.muted {
.status__content p,
- .status__content a {
+ .status__content a,
+ .status__content_text {
color: $dark-text-color;
}
--
cgit
From 1c0402c1038ca5fe85689b38ad44921e5aff9149 Mon Sep 17 00:00:00 2001
From: Thibaut Girka
Date: Wed, 24 Apr 2019 17:55:55 +0200
Subject: Do not ask to register web intent handler
---
app/javascript/flavours/glitch/containers/mastodon.js | 7 -------
1 file changed, 7 deletions(-)
diff --git a/app/javascript/flavours/glitch/containers/mastodon.js b/app/javascript/flavours/glitch/containers/mastodon.js
index 4fb6be476..59eef6636 100644
--- a/app/javascript/flavours/glitch/containers/mastodon.js
+++ b/app/javascript/flavours/glitch/containers/mastodon.js
@@ -39,13 +39,6 @@ export default class Mastodon extends React.PureComponent {
window.setTimeout(() => Notification.requestPermission(), 60 * 1000);
}
- // Protocol handler
- // Ask after 5 minutes
- if (typeof navigator.registerProtocolHandler !== 'undefined') {
- const handlerUrl = window.location.protocol + '//' + window.location.host + '/intent?uri=%s';
- window.setTimeout(() => navigator.registerProtocolHandler('web+mastodon', handlerUrl, 'Mastodon'), 5 * 60 * 1000);
- }
-
store.dispatch(showOnboardingOnce());
}
--
cgit
From b1a0322a06e6f8eca736132b1f7f2af8dc179afb Mon Sep 17 00:00:00 2001
From: ThibG
Date: Thu, 25 Apr 2019 02:47:33 +0200
Subject: Reject follow requests of blocked users (#10633)
---
app/services/block_service.rb | 1 +
1 file changed, 1 insertion(+)
diff --git a/app/services/block_service.rb b/app/services/block_service.rb
index 140b238df..10ed470e0 100644
--- a/app/services/block_service.rb
+++ b/app/services/block_service.rb
@@ -6,6 +6,7 @@ class BlockService < BaseService
UnfollowService.new.call(account, target_account) if account.following?(target_account)
UnfollowService.new.call(target_account, account) if target_account.following?(account)
+ RejectFollowService.new.call(account, target_account) if target_account.requested?(account)
block = account.block!(target_account)
--
cgit
From 852ccea676bbb8a91a0d1bc66570e68e6de2c9e1 Mon Sep 17 00:00:00 2001
From: ThibG
Date: Thu, 25 Apr 2019 02:48:54 +0200
Subject: Fix upload progressbar when image resizing is involved (#10632)
---
app/javascript/mastodon/actions/compose.js | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/app/javascript/mastodon/actions/compose.js b/app/javascript/mastodon/actions/compose.js
index d65d41048..0ee663766 100644
--- a/app/javascript/mastodon/actions/compose.js
+++ b/app/javascript/mastodon/actions/compose.js
@@ -203,8 +203,8 @@ export function uploadCompose(files) {
return function (dispatch, getState) {
const uploadLimit = 4;
const media = getState().getIn(['compose', 'media_attachments']);
- const total = Array.from(files).reduce((a, v) => a + v.size, 0);
const progress = new Array(files.length).fill(0);
+ let total = Array.from(files).reduce((a, v) => a + v.size, 0);
if (files.length + media.size > uploadLimit) {
dispatch(showAlert(undefined, messages.uploadErrorLimit));
@@ -224,6 +224,8 @@ export function uploadCompose(files) {
resizeImage(f).then(file => {
const data = new FormData();
data.append('file', file);
+ // Account for disparity in size of original image and resized data
+ total += file.size - f.size;
return api(getState).post('/api/v1/media', data, {
onUploadProgress: function({ loaded }){
--
cgit
From f27d7093513c0265010d019adc01b3f7ea02ef47 Mon Sep 17 00:00:00 2001
From: Eugen Rochko
Date: Thu, 25 Apr 2019 02:49:06 +0200
Subject: Fix not being able to save e-mail preference for new pending accounts
(#10622)
---
app/controllers/settings/notifications_controller.rb | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/controllers/settings/notifications_controller.rb b/app/controllers/settings/notifications_controller.rb
index da8a03d96..b2ce83e42 100644
--- a/app/controllers/settings/notifications_controller.rb
+++ b/app/controllers/settings/notifications_controller.rb
@@ -25,7 +25,7 @@ class Settings::NotificationsController < Settings::BaseController
def user_settings_params
params.require(:user).permit(
- notification_emails: %i(follow follow_request reblog favourite mention digest report),
+ notification_emails: %i(follow follow_request reblog favourite mention digest report pending_account),
interactions: %i(must_be_follower must_be_following must_be_following_dm)
)
end
--
cgit
From e451ba0e837eb5b3d4f7fe75ca3e16680afaf129 Mon Sep 17 00:00:00 2001
From: Eugen Rochko
Date: Thu, 25 Apr 2019 02:49:25 +0200
Subject: Fix LDAP/PAM/SAML/CAS users not being approved instantly (#10621)
---
app/models/concerns/ldap_authenticable.rb | 1 +
app/models/concerns/omniauthable.rb | 1 +
app/models/concerns/pam_authenticable.rb | 1 +
app/models/user.rb | 7 ++++++-
4 files changed, 9 insertions(+), 1 deletion(-)
diff --git a/app/models/concerns/ldap_authenticable.rb b/app/models/concerns/ldap_authenticable.rb
index e1b5e3832..84ff84c4b 100644
--- a/app/models/concerns/ldap_authenticable.rb
+++ b/app/models/concerns/ldap_authenticable.rb
@@ -6,6 +6,7 @@ module LdapAuthenticable
def ldap_setup(_attributes)
self.confirmed_at = Time.now.utc
self.admin = false
+ self.external = true
save!
end
diff --git a/app/models/concerns/omniauthable.rb b/app/models/concerns/omniauthable.rb
index 1b28b8162..283033083 100644
--- a/app/models/concerns/omniauthable.rb
+++ b/app/models/concerns/omniauthable.rb
@@ -66,6 +66,7 @@ module Omniauthable
email: email || "#{TEMP_EMAIL_PREFIX}-#{auth.uid}-#{auth.provider}.com",
password: Devise.friendly_token[0, 20],
agreement: true,
+ external: true,
account_attributes: {
username: ensure_unique_username(auth.uid),
display_name: display_name,
diff --git a/app/models/concerns/pam_authenticable.rb b/app/models/concerns/pam_authenticable.rb
index 2f651c1a3..6169d4dfa 100644
--- a/app/models/concerns/pam_authenticable.rb
+++ b/app/models/concerns/pam_authenticable.rb
@@ -34,6 +34,7 @@ module PamAuthenticable
self.confirmed_at = Time.now.utc
self.admin = false
self.account = account
+ self.external = true
account.destroy! unless save
end
diff --git a/app/models/user.rb b/app/models/user.rb
index 135baae12..9a0671006 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -107,6 +107,7 @@ class User < ApplicationRecord
:expand_spoilers, :default_language, :aggregate_reblogs, :show_application, to: :settings, prefix: :setting, allow_nil: false
attr_reader :invite_code
+ attr_writer :external
def confirmed?
confirmed_at.present?
@@ -273,13 +274,17 @@ class User < ApplicationRecord
private
def set_approved
- self.approved = open_registrations? || invited?
+ self.approved = open_registrations? || invited? || external?
end
def open_registrations?
Setting.registrations_mode == 'open'
end
+ def external?
+ @external
+ end
+
def sanitize_languages
return if chosen_languages.nil?
chosen_languages.reject!(&:blank?)
--
cgit
From 8f4c34669fc3117ee7526f606a7a3fb7f80ab5cd Mon Sep 17 00:00:00 2001
From: "dependabot[bot]"
Date: Thu, 25 Apr 2019 13:36:56 +0200
Subject: Bump bootsnap from 1.4.3 to 1.4.4 (#10634)
Bumps [bootsnap](https://github.com/Shopify/bootsnap) from 1.4.3 to 1.4.4.
- [Release notes](https://github.com/Shopify/bootsnap/releases)
- [Changelog](https://github.com/Shopify/bootsnap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Shopify/bootsnap/compare/v1.4.3...v1.4.4)
Signed-off-by: dependabot[bot]
---
Gemfile.lock | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/Gemfile.lock b/Gemfile.lock
index da984ccae..66fc8d1f4 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -99,7 +99,7 @@ GEM
rack (>= 0.9.0)
binding_of_caller (0.8.0)
debug_inspector (>= 0.0.1)
- bootsnap (1.4.3)
+ bootsnap (1.4.4)
msgpack (~> 1.0)
brakeman (4.5.0)
browser (2.5.3)
@@ -346,7 +346,7 @@ GEM
mini_mime (1.0.1)
mini_portile2 (2.4.0)
minitest (5.11.3)
- msgpack (1.2.9)
+ msgpack (1.2.10)
multi_json (1.13.1)
multipart-post (2.0.0)
necromancer (0.4.0)
--
cgit
From c008911249a2fc0efaf22b83e51ea8510e67acac Mon Sep 17 00:00:00 2001
From: Marek Ľach
Date: Fri, 26 Apr 2019 18:07:36 +0200
Subject: New string added for Slovak translation (#10637)
---
config/locales/sk.yml | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/config/locales/sk.yml b/config/locales/sk.yml
index d3580c981..b6a966fa7 100644
--- a/config/locales/sk.yml
+++ b/config/locales/sk.yml
@@ -527,16 +527,17 @@ sk:
login: Prihlás sa
logout: Odhlás sa
migrate_account: Presúvam sa na iný účet
- migrate_account_html: Pokiaľ si želáš presmerovať tento účet na nejaký iný, môžeš si to nastaviť tu .
- or_log_in_with: Alebo prihlásiť z
+ migrate_account_html: Ak si želáš presmerovať tento účet na nejaký iný, môžeš si to nastaviť tu .
+ or_log_in_with: Alebo prihlás s
providers:
cas: CAS
saml: SAML
register: Zaregistruj sa
- resend_confirmation: Poslať potvrdzujúce pokyny znovu
+ resend_confirmation: Zašli potvrdzujúce pokyny znovu
reset_password: Obnov heslo
security: Zabezpečenie
set_new_password: Nastav nové heslo
+ trouble_logging_in: Problém s prihlásením?
authorize_follow:
already_following: Tento účet už následuješ
error: Naneštastie nastala chyba pri hľadaní vzdialeného účtu
--
cgit
From 3a671470ec53edad206d9022e8796a1f6d3e92fd Mon Sep 17 00:00:00 2001
From: Thibaut Girka
Date: Thu, 11 Apr 2019 17:07:06 +0200
Subject: Refactor selectComposeSuggestion so that different paths can be
updated
---
app/javascript/flavours/glitch/actions/compose.js | 3 ++-
.../features/compose/containers/compose_form_container.js | 2 +-
app/javascript/flavours/glitch/reducers/compose.js | 14 ++++++++------
3 files changed, 11 insertions(+), 8 deletions(-)
diff --git a/app/javascript/flavours/glitch/actions/compose.js b/app/javascript/flavours/glitch/actions/compose.js
index ef2500629..f117ce771 100644
--- a/app/javascript/flavours/glitch/actions/compose.js
+++ b/app/javascript/flavours/glitch/actions/compose.js
@@ -407,7 +407,7 @@ export function readyComposeSuggestionsAccounts(token, accounts) {
};
};
-export function selectComposeSuggestion(position, token, suggestion) {
+export function selectComposeSuggestion(position, token, suggestion, path) {
return (dispatch, getState) => {
let completion;
if (typeof suggestion === 'object' && suggestion.id) {
@@ -424,6 +424,7 @@ export function selectComposeSuggestion(position, token, suggestion) {
position,
token,
completion,
+ path,
});
};
};
diff --git a/app/javascript/flavours/glitch/features/compose/containers/compose_form_container.js b/app/javascript/flavours/glitch/features/compose/containers/compose_form_container.js
index 5458e0919..780a10f81 100644
--- a/app/javascript/flavours/glitch/features/compose/containers/compose_form_container.js
+++ b/app/javascript/flavours/glitch/features/compose/containers/compose_form_container.js
@@ -91,7 +91,7 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
},
onSuggestionSelected(position, token, suggestion) {
- dispatch(selectComposeSuggestion(position, token, suggestion));
+ dispatch(selectComposeSuggestion(position, token, suggestion, ['text']));
},
onChangeSpoilerText(text) {
diff --git a/app/javascript/flavours/glitch/reducers/compose.js b/app/javascript/flavours/glitch/reducers/compose.js
index a79b0dd24..b1dba0a18 100644
--- a/app/javascript/flavours/glitch/reducers/compose.js
+++ b/app/javascript/flavours/glitch/reducers/compose.js
@@ -41,7 +41,7 @@ import {
import { TIMELINE_DELETE } from 'flavours/glitch/actions/timelines';
import { STORE_HYDRATE } from 'flavours/glitch/actions/store';
import { REDRAFT } from 'flavours/glitch/actions/statuses';
-import { Map as ImmutableMap, List as ImmutableList, OrderedSet as ImmutableOrderedSet, fromJS } from 'immutable';
+import { Map as ImmutableMap, List as ImmutableList, OrderedSet as ImmutableOrderedSet, fromJS, is } from 'immutable';
import uuid from 'flavours/glitch/util/uuid';
import { privacyPreference } from 'flavours/glitch/util/privacy_preference';
import { me } from 'flavours/glitch/util/initial_state';
@@ -214,12 +214,14 @@ function removeMedia(state, mediaId) {
});
};
-const insertSuggestion = (state, position, token, completion) => {
+const insertSuggestion = (state, position, token, completion, path) => {
return state.withMutations(map => {
- map.update('text', oldText => `${oldText.slice(0, position)}${completion}${completion[0] === ':' ? '\u200B' : ' '}${oldText.slice(position + token.length)}`);
+ map.updateIn(path, oldText => `${oldText.slice(0, position)}${completion}${completion[0] === ':' ? '\u200B' : ' '}${oldText.slice(position + token.length)}`);
map.set('suggestion_token', null);
- map.update('suggestions', ImmutableList(), list => list.clear());
- map.set('focusDate', new Date());
+ map.set('suggestions', ImmutableList());
+ if (is(path, ImmutableList(['text']))) {
+ map.set('focusDate', new Date());
+ }
map.set('caretPosition', position + completion.length + 1);
map.set('idempotencyKey', uuid());
});
@@ -397,7 +399,7 @@ export default function compose(state = initialState, action) {
case COMPOSE_SUGGESTIONS_READY:
return state.set('suggestions', ImmutableList(action.accounts ? action.accounts.map(item => item.id) : action.emojis)).set('suggestion_token', action.token);
case COMPOSE_SUGGESTION_SELECT:
- return insertSuggestion(state, action.position, action.token, action.completion);
+ return insertSuggestion(state, action.position, action.token, action.completion, action.path);
case COMPOSE_SUGGESTION_TAGS_UPDATE:
return updateSuggestionTags(state, action.token);
case COMPOSE_TAG_HISTORY_UPDATE:
--
cgit
From df52004fe6de0a8f21e97967f9d9d8a5fc945465 Mon Sep 17 00:00:00 2001
From: Thibaut Girka
Date: Thu, 11 Apr 2019 17:18:55 +0200
Subject: Add suggestions in CW field
---
.../glitch/components/autosuggest_input.js | 227 +++++++++++++++++++++
.../glitch/components/autosuggest_textarea.js | 10 +-
.../features/compose/components/compose_form.js | 40 ++--
.../compose/containers/compose_form_container.js | 4 +-
4 files changed, 260 insertions(+), 21 deletions(-)
create mode 100644 app/javascript/flavours/glitch/components/autosuggest_input.js
diff --git a/app/javascript/flavours/glitch/components/autosuggest_input.js b/app/javascript/flavours/glitch/components/autosuggest_input.js
new file mode 100644
index 000000000..7de9f2307
--- /dev/null
+++ b/app/javascript/flavours/glitch/components/autosuggest_input.js
@@ -0,0 +1,227 @@
+import React from 'react';
+import AutosuggestAccountContainer from 'flavours/glitch/features/compose/containers/autosuggest_account_container';
+import AutosuggestEmoji from './autosuggest_emoji';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import PropTypes from 'prop-types';
+import { isRtl } from 'flavours/glitch/util/rtl';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+import classNames from 'classnames';
+import { List as ImmutableList } from 'immutable';
+
+const textAtCursorMatchesToken = (str, caretPosition, searchTokens) => {
+ let word;
+
+ let left = str.slice(0, caretPosition).search(/[^\s\u200B]+$/);
+ let right = str.slice(caretPosition).search(/[\s\u200B]/);
+
+ if (right < 0) {
+ word = str.slice(left);
+ } else {
+ word = str.slice(left, right + caretPosition);
+ }
+
+ if (!word || word.trim().length < 3 || searchTokens.indexOf(word[0]) === -1) {
+ return [null, null];
+ }
+
+ word = word.trim().toLowerCase();
+
+ if (word.length > 0) {
+ return [left, word];
+ } else {
+ return [null, null];
+ }
+};
+
+export default class AutosuggestInput extends ImmutablePureComponent {
+
+ static propTypes = {
+ value: PropTypes.string,
+ suggestions: ImmutablePropTypes.list,
+ disabled: PropTypes.bool,
+ placeholder: PropTypes.string,
+ onSuggestionSelected: PropTypes.func.isRequired,
+ onSuggestionsClearRequested: PropTypes.func.isRequired,
+ onSuggestionsFetchRequested: PropTypes.func.isRequired,
+ onChange: PropTypes.func.isRequired,
+ onKeyUp: PropTypes.func,
+ onKeyDown: PropTypes.func,
+ autoFocus: PropTypes.bool,
+ className: PropTypes.string,
+ id: PropTypes.string,
+ searchTokens: PropTypes.list,
+ };
+
+ static defaultProps = {
+ autoFocus: true,
+ searchTokens: ImmutableList(['@', ':', '#']),
+ };
+
+ state = {
+ suggestionsHidden: false,
+ focused: false,
+ selectedSuggestion: 0,
+ lastToken: null,
+ tokenStart: 0,
+ };
+
+ onChange = (e) => {
+ const [ tokenStart, token ] = textAtCursorMatchesToken(e.target.value, e.target.selectionStart, this.props.searchTokens);
+
+ if (token !== null && this.state.lastToken !== token) {
+ this.setState({ lastToken: token, selectedSuggestion: 0, tokenStart });
+ this.props.onSuggestionsFetchRequested(token);
+ } else if (token === null) {
+ this.setState({ lastToken: null });
+ this.props.onSuggestionsClearRequested();
+ }
+
+ this.props.onChange(e);
+ }
+
+ onKeyDown = (e) => {
+ const { suggestions, disabled } = this.props;
+ const { selectedSuggestion, suggestionsHidden } = this.state;
+
+ if (disabled) {
+ e.preventDefault();
+ return;
+ }
+
+ if (e.which === 229 || e.isComposing) {
+ // Ignore key events during text composition
+ // e.key may be a name of the physical key even in this case (e.x. Safari / Chrome on Mac)
+ return;
+ }
+
+ switch(e.key) {
+ case 'Escape':
+ if (suggestions.size === 0 || suggestionsHidden) {
+ document.querySelector('.ui').parentElement.focus();
+ } else {
+ e.preventDefault();
+ this.setState({ suggestionsHidden: true });
+ }
+
+ break;
+ case 'ArrowDown':
+ if (suggestions.size > 0 && !suggestionsHidden) {
+ e.preventDefault();
+ this.setState({ selectedSuggestion: Math.min(selectedSuggestion + 1, suggestions.size - 1) });
+ }
+
+ break;
+ case 'ArrowUp':
+ if (suggestions.size > 0 && !suggestionsHidden) {
+ e.preventDefault();
+ this.setState({ selectedSuggestion: Math.max(selectedSuggestion - 1, 0) });
+ }
+
+ break;
+ case 'Enter':
+ case 'Tab':
+ // Select suggestion
+ if (this.state.lastToken !== null && suggestions.size > 0 && !suggestionsHidden) {
+ e.preventDefault();
+ e.stopPropagation();
+ this.props.onSuggestionSelected(this.state.tokenStart, this.state.lastToken, suggestions.get(selectedSuggestion));
+ }
+
+ break;
+ }
+
+ if (e.defaultPrevented || !this.props.onKeyDown) {
+ return;
+ }
+
+ this.props.onKeyDown(e);
+ }
+
+ onBlur = () => {
+ this.setState({ suggestionsHidden: true, focused: false });
+ }
+
+ onFocus = () => {
+ this.setState({ focused: true });
+ }
+
+ onSuggestionClick = (e) => {
+ const suggestion = this.props.suggestions.get(e.currentTarget.getAttribute('data-index'));
+ e.preventDefault();
+ this.props.onSuggestionSelected(this.state.tokenStart, this.state.lastToken, suggestion);
+ this.input.focus();
+ }
+
+ componentWillReceiveProps (nextProps) {
+ if (nextProps.suggestions !== this.props.suggestions && nextProps.suggestions.size > 0 && this.state.suggestionsHidden && this.state.focused) {
+ this.setState({ suggestionsHidden: false });
+ }
+ }
+
+ setInput = (c) => {
+ this.input = c;
+ }
+
+ renderSuggestion = (suggestion, i) => {
+ const { selectedSuggestion } = this.state;
+ let inner, key;
+
+ if (typeof suggestion === 'object') {
+ inner = ;
+ key = suggestion.id;
+ } else if (suggestion[0] === '#') {
+ inner = suggestion;
+ key = suggestion;
+ } else {
+ inner = ;
+ key = suggestion;
+ }
+
+ return (
+
+ {inner}
+
+ );
+ }
+
+ render () {
+ const { value, suggestions, disabled, placeholder, onKeyUp, autoFocus, className, id } = this.props;
+ const { suggestionsHidden } = this.state;
+ const style = { direction: 'ltr' };
+
+ if (isRtl(value)) {
+ style.direction = 'rtl';
+ }
+
+ return (
+
+
+ {placeholder}
+
+
+
+
+
+ {suggestions.map(this.renderSuggestion)}
+
+
+ );
+ }
+
+}
diff --git a/app/javascript/flavours/glitch/components/autosuggest_textarea.js b/app/javascript/flavours/glitch/components/autosuggest_textarea.js
index af8fbe406..2be29feb4 100644
--- a/app/javascript/flavours/glitch/components/autosuggest_textarea.js
+++ b/app/javascript/flavours/glitch/components/autosuggest_textarea.js
@@ -56,6 +56,7 @@ export default class AutosuggestTextarea extends ImmutablePureComponent {
state = {
suggestionsHidden: false,
+ focused: false,
selectedSuggestion: 0,
lastToken: null,
tokenStart: 0,
@@ -134,7 +135,11 @@ export default class AutosuggestTextarea extends ImmutablePureComponent {
}
onBlur = () => {
- this.setState({ suggestionsHidden: true });
+ this.setState({ suggestionsHidden: true, focused: false });
+ }
+
+ onFocus = () => {
+ this.setState({ focused: true });
}
onSuggestionClick = (e) => {
@@ -145,7 +150,7 @@ export default class AutosuggestTextarea extends ImmutablePureComponent {
}
componentWillReceiveProps (nextProps) {
- if (nextProps.suggestions !== this.props.suggestions && nextProps.suggestions.size > 0 && this.state.suggestionsHidden) {
+ if (nextProps.suggestions !== this.props.suggestions && nextProps.suggestions.size > 0 && this.state.suggestionsHidden && this.state.focused) {
this.setState({ suggestionsHidden: false });
}
}
@@ -207,6 +212,7 @@ export default class AutosuggestTextarea extends ImmutablePureComponent {
onChange={this.onChange}
onKeyDown={this.onKeyDown}
onKeyUp={onKeyUp}
+ onFocus={this.onFocus}
onBlur={this.onBlur}
onPaste={this.onPaste}
style={style}
diff --git a/app/javascript/flavours/glitch/features/compose/components/compose_form.js b/app/javascript/flavours/glitch/features/compose/components/compose_form.js
index a5e34e3a2..bd6d5b1fa 100644
--- a/app/javascript/flavours/glitch/features/compose/components/compose_form.js
+++ b/app/javascript/flavours/glitch/features/compose/components/compose_form.js
@@ -3,6 +3,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types';
import ReplyIndicatorContainer from '../containers/reply_indicator_container';
import AutosuggestTextarea from '../../../components/autosuggest_textarea';
+import AutosuggestInput from '../../../components/autosuggest_input';
import { defineMessages, injectIntl } from 'react-intl';
import EmojiPicker from 'flavours/glitch/features/emoji_picker';
import PollFormContainer from '../containers/poll_form_container';
@@ -163,7 +164,11 @@ class ComposeForm extends ImmutablePureComponent {
// Selects a suggestion from the autofill.
onSuggestionSelected = (tokenStart, token, value) => {
- this.props.onSuggestionSelected(tokenStart, token, value);
+ this.props.onSuggestionSelected(tokenStart, token, value, ['text']);
+ }
+
+ onSpoilerSuggestionSelected = (tokenStart, token, value) => {
+ this.props.onSuggestionSelected(tokenStart, token, value, ['spoiler_text']);
}
// When the escape key is released, we focus the UI.
@@ -183,7 +188,7 @@ class ComposeForm extends ImmutablePureComponent {
// Sets a reference to the CW field.
handleRefSpoilerText = (spoilerComponent) => {
if (spoilerComponent) {
- this.spoilerText = spoilerComponent;
+ this.spoilerText = spoilerComponent.input;
}
}
@@ -303,21 +308,22 @@ class ComposeForm extends ImmutablePureComponent {
diff --git a/app/javascript/flavours/glitch/features/compose/containers/compose_form_container.js b/app/javascript/flavours/glitch/features/compose/containers/compose_form_container.js
index 780a10f81..2da0770d3 100644
--- a/app/javascript/flavours/glitch/features/compose/containers/compose_form_container.js
+++ b/app/javascript/flavours/glitch/features/compose/containers/compose_form_container.js
@@ -90,8 +90,8 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
dispatch(fetchComposeSuggestions(token));
},
- onSuggestionSelected(position, token, suggestion) {
- dispatch(selectComposeSuggestion(position, token, suggestion, ['text']));
+ onSuggestionSelected(position, token, suggestion, path) {
+ dispatch(selectComposeSuggestion(position, token, suggestion, path));
},
onChangeSpoilerText(text) {
--
cgit
From d7e4be285abfacfbf7ea6c50d2e2090128ef9b5d Mon Sep 17 00:00:00 2001
From: Thibaut Girka
Date: Thu, 11 Apr 2019 17:59:38 +0200
Subject: Add emoji suggestion to poll options
---
.../glitch/components/autosuggest_input.js | 4 ++-
.../features/compose/components/poll_form.js | 33 +++++++++++++++++++---
.../compose/containers/poll_form_container.js | 19 +++++++++++++
3 files changed, 51 insertions(+), 5 deletions(-)
diff --git a/app/javascript/flavours/glitch/components/autosuggest_input.js b/app/javascript/flavours/glitch/components/autosuggest_input.js
index 7de9f2307..4797767b0 100644
--- a/app/javascript/flavours/glitch/components/autosuggest_input.js
+++ b/app/javascript/flavours/glitch/components/autosuggest_input.js
@@ -50,6 +50,7 @@ export default class AutosuggestInput extends ImmutablePureComponent {
className: PropTypes.string,
id: PropTypes.string,
searchTokens: PropTypes.list,
+ maxLength: PropTypes.number,
};
static defaultProps = {
@@ -185,7 +186,7 @@ export default class AutosuggestInput extends ImmutablePureComponent {
}
render () {
- const { value, suggestions, disabled, placeholder, onKeyUp, autoFocus, className, id } = this.props;
+ const { value, suggestions, disabled, placeholder, onKeyUp, autoFocus, className, id, maxLength } = this.props;
const { suggestionsHidden } = this.state;
const style = { direction: 'ltr' };
@@ -214,6 +215,7 @@ export default class AutosuggestInput extends ImmutablePureComponent {
aria-autocomplete='list'
id={id}
className={className}
+ maxLength={maxLength}
/>
diff --git a/app/javascript/flavours/glitch/features/compose/components/poll_form.js b/app/javascript/flavours/glitch/features/compose/components/poll_form.js
index 1915b62d5..21b5d3d73 100644
--- a/app/javascript/flavours/glitch/features/compose/components/poll_form.js
+++ b/app/javascript/flavours/glitch/features/compose/components/poll_form.js
@@ -5,6 +5,7 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import IconButton from 'flavours/glitch/components/icon_button';
import Icon from 'flavours/glitch/components/icon';
+import AutosuggestInput from 'flavours/glitch/components/autosuggest_input';
import classNames from 'classnames';
import { pollLimits } from 'flavours/glitch/util/initial_state';
@@ -29,6 +30,10 @@ class Option extends React.PureComponent {
isPollMultiple: PropTypes.bool,
onChange: PropTypes.func.isRequired,
onRemove: PropTypes.func.isRequired,
+ suggestions: ImmutablePropTypes.list,
+ onClearSuggestions: PropTypes.func.isRequired,
+ onFetchSuggestions: PropTypes.func.isRequired,
+ onSuggestionSelected: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
};
@@ -40,6 +45,18 @@ class Option extends React.PureComponent {
this.props.onRemove(this.props.index);
};
+ onSuggestionsClearRequested = () => {
+ this.props.onClearSuggestions();
+ }
+
+ onSuggestionsFetchRequested = (token) => {
+ this.props.onFetchSuggestions(token);
+ }
+
+ onSuggestionSelected = (tokenStart, token, value) => {
+ this.props.onSuggestionSelected(tokenStart, token, value, ['poll', 'options', this.props.index]);
+ }
+
render () {
const { isPollMultiple, title, index, intl } = this.props;
@@ -48,12 +65,16 @@ class Option extends React.PureComponent {
-
@@ -78,6 +99,10 @@ class PollForm extends ImmutablePureComponent {
onAddOption: PropTypes.func.isRequired,
onRemoveOption: PropTypes.func.isRequired,
onChangeSettings: PropTypes.func.isRequired,
+ suggestions: ImmutablePropTypes.list,
+ onClearSuggestions: PropTypes.func.isRequired,
+ onFetchSuggestions: PropTypes.func.isRequired,
+ onSuggestionSelected: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
};
@@ -94,7 +119,7 @@ class PollForm extends ImmutablePureComponent {
};
render () {
- const { options, expiresIn, isMultiple, onChangeOption, onRemoveOption, intl } = this.props;
+ const { options, expiresIn, isMultiple, onChangeOption, onRemoveOption, intl, ...other } = this.props;
if (!options) {
return null;
@@ -103,7 +128,7 @@ class PollForm extends ImmutablePureComponent {
return (
- {options.map((title, i) => )}
+ {options.map((title, i) => )}
{options.size < pollLimits.max_options && (
diff --git a/app/javascript/flavours/glitch/features/compose/containers/poll_form_container.js b/app/javascript/flavours/glitch/features/compose/containers/poll_form_container.js
index 01df024c8..e87e58771 100644
--- a/app/javascript/flavours/glitch/features/compose/containers/poll_form_container.js
+++ b/app/javascript/flavours/glitch/features/compose/containers/poll_form_container.js
@@ -1,8 +1,14 @@
import { connect } from 'react-redux';
import PollForm from '../components/poll_form';
import { addPollOption, removePollOption, changePollOption, changePollSettings } from 'flavours/glitch/actions/compose';
+import {
+ clearComposeSuggestions,
+ fetchComposeSuggestions,
+ selectComposeSuggestion,
+} from 'flavours/glitch/actions/compose';
const mapStateToProps = state => ({
+ suggestions: state.getIn(['compose', 'suggestions']),
options: state.getIn(['compose', 'poll', 'options']),
expiresIn: state.getIn(['compose', 'poll', 'expires_in']),
isMultiple: state.getIn(['compose', 'poll', 'multiple']),
@@ -24,6 +30,19 @@ const mapDispatchToProps = dispatch => ({
onChangeSettings(expiresIn, isMultiple) {
dispatch(changePollSettings(expiresIn, isMultiple));
},
+
+ onClearSuggestions () {
+ dispatch(clearComposeSuggestions());
+ },
+
+ onFetchSuggestions (token) {
+ dispatch(fetchComposeSuggestions(token));
+ },
+
+ onSuggestionSelected (position, token, accountId, path) {
+ dispatch(selectComposeSuggestion(position, token, accountId, path));
+ },
+
});
export default connect(mapStateToProps, mapDispatchToProps)(PollForm);
--
cgit
From fddd8251ddec420286dcca06c0be2bea457e4799 Mon Sep 17 00:00:00 2001
From: Thibaut Girka
Date: Thu, 11 Apr 2019 18:14:28 +0200
Subject: Attempt to fix CSS
---
app/javascript/flavours/glitch/styles/components/composer.scss | 3 ++-
app/javascript/flavours/glitch/styles/polls.scss | 8 ++++++--
2 files changed, 8 insertions(+), 3 deletions(-)
diff --git a/app/javascript/flavours/glitch/styles/components/composer.scss b/app/javascript/flavours/glitch/styles/components/composer.scss
index c7851b561..e5eb6e64f 100644
--- a/app/javascript/flavours/glitch/styles/components/composer.scss
+++ b/app/javascript/flavours/glitch/styles/components/composer.scss
@@ -128,7 +128,8 @@
}
}
-.composer--textarea {
+.composer--textarea,
+.autosuggest-input {
position: relative;
label {
diff --git a/app/javascript/flavours/glitch/styles/polls.scss b/app/javascript/flavours/glitch/styles/polls.scss
index 6030e1e98..315fd5782 100644
--- a/app/javascript/flavours/glitch/styles/polls.scss
+++ b/app/javascript/flavours/glitch/styles/polls.scss
@@ -37,11 +37,14 @@
display: none;
}
+ .autossugest-input {
+ flex: 1 1 auto;
+ }
+
input[type=text] {
display: block;
box-sizing: border-box;
- flex: 1 1 auto;
- width: 20px;
+ width: 100%;
font-size: 14px;
color: $inverted-text-color;
display: block;
@@ -64,6 +67,7 @@
&.editable {
display: flex;
align-items: center;
+ overflow: visible;
}
}
--
cgit
From aa1832ac4d53b2f650af1aff49de57039e590601 Mon Sep 17 00:00:00 2001
From: Thibaut Girka
Date: Wed, 24 Apr 2019 20:34:33 +0200
Subject: Hide suggestions by default
They will be enabled if the input has focus
---
app/javascript/flavours/glitch/components/autosuggest_input.js | 2 +-
app/javascript/flavours/glitch/components/autosuggest_textarea.js | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/app/javascript/flavours/glitch/components/autosuggest_input.js b/app/javascript/flavours/glitch/components/autosuggest_input.js
index 4797767b0..ca0dcb64f 100644
--- a/app/javascript/flavours/glitch/components/autosuggest_input.js
+++ b/app/javascript/flavours/glitch/components/autosuggest_input.js
@@ -59,7 +59,7 @@ export default class AutosuggestInput extends ImmutablePureComponent {
};
state = {
- suggestionsHidden: false,
+ suggestionsHidden: true,
focused: false,
selectedSuggestion: 0,
lastToken: null,
diff --git a/app/javascript/flavours/glitch/components/autosuggest_textarea.js b/app/javascript/flavours/glitch/components/autosuggest_textarea.js
index 2be29feb4..e1ded2b3a 100644
--- a/app/javascript/flavours/glitch/components/autosuggest_textarea.js
+++ b/app/javascript/flavours/glitch/components/autosuggest_textarea.js
@@ -55,7 +55,7 @@ export default class AutosuggestTextarea extends ImmutablePureComponent {
};
state = {
- suggestionsHidden: false,
+ suggestionsHidden: true,
focused: false,
selectedSuggestion: 0,
lastToken: null,
--
cgit
From fba96c808d25d2fc35ec63ee6745a1e55a95d707 Mon Sep 17 00:00:00 2001
From: Eugen Rochko
Date: Sat, 27 Apr 2019 03:24:09 +0200
Subject: Add blurhash (#10630)
* Add blurhash
* Use fallback color for spoiler when blurhash missing
* Federate the blurhash and accept it as long as it's at most 5x5
* Display unknown media attachments as blurhash placeholders
* Improve style of embed actions and spoiler button
* Change blurhash resolution from 3x3 to 4x4
* Improve dependency definitions
* Fix code style issues
---
Gemfile | 1 +
Gemfile.lock | 3 +
.../mastodon/components/media_gallery.js | 96 ++++++++++++++++------
app/javascript/mastodon/components/status.js | 3 +-
.../features/report/components/status_check_box.js | 1 +
.../features/status/components/detailed_status.js | 6 +-
.../mastodon/features/ui/components/media_modal.js | 1 +
.../mastodon/features/ui/components/video_modal.js | 1 +
app/javascript/mastodon/features/video/index.js | 49 +++++++++--
app/javascript/styles/mastodon/components.scss | 70 +++++++++++++---
app/lib/activitypub/activity/create.rb | 7 +-
app/lib/activitypub/adapter.rb | 1 +
app/models/media_attachment.rb | 15 +++-
app/serializers/activitypub/note_serializer.rb | 4 +-
.../rest/media_attachment_serializer.rb | 2 +-
.../stream_entries/_detailed_status.html.haml | 2 +-
app/views/stream_entries/_simple_status.html.haml | 2 +-
...0420025523_add_blurhash_to_media_attachments.rb | 5 ++
db/schema.rb | 3 +-
lib/paperclip/blurhash_transcoder.rb | 16 ++++
package.json | 1 +
yarn.lock | 5 ++
22 files changed, 234 insertions(+), 60 deletions(-)
create mode 100644 db/migrate/20190420025523_add_blurhash_to_media_attachments.rb
create mode 100644 lib/paperclip/blurhash_transcoder.rb
diff --git a/Gemfile b/Gemfile
index 6fe97412b..fa8478d89 100644
--- a/Gemfile
+++ b/Gemfile
@@ -21,6 +21,7 @@ gem 'fog-openstack', '~> 0.3', require: false
gem 'paperclip', '~> 6.0'
gem 'paperclip-av-transcoder', '~> 0.6'
gem 'streamio-ffmpeg', '~> 3.0'
+gem 'blurhash', '~> 0.1'
gem 'active_model_serializers', '~> 0.10'
gem 'addressable', '~> 2.6'
diff --git a/Gemfile.lock b/Gemfile.lock
index 66fc8d1f4..0148cb5ea 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -99,6 +99,8 @@ GEM
rack (>= 0.9.0)
binding_of_caller (0.8.0)
debug_inspector (>= 0.0.1)
+ blurhash (0.1.2)
+ ffi (~> 1.10.0)
bootsnap (1.4.4)
msgpack (~> 1.0)
brakeman (4.5.0)
@@ -661,6 +663,7 @@ DEPENDENCIES
aws-sdk-s3 (~> 1.36)
better_errors (~> 2.5)
binding_of_caller (~> 0.7)
+ blurhash (~> 0.1)
bootsnap (~> 1.4)
brakeman (~> 4.5)
browser
diff --git a/app/javascript/mastodon/components/media_gallery.js b/app/javascript/mastodon/components/media_gallery.js
index a2bc95255..f548296d0 100644
--- a/app/javascript/mastodon/components/media_gallery.js
+++ b/app/javascript/mastodon/components/media_gallery.js
@@ -7,6 +7,7 @@ import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import { isIOS } from '../is_mobile';
import classNames from 'classnames';
import { autoPlayGif, displayMedia } from '../initial_state';
+import { decode } from 'blurhash';
const messages = defineMessages({
toggle_visible: { id: 'media_gallery.toggle_visible', defaultMessage: 'Toggle visibility' },
@@ -21,6 +22,7 @@ class Item extends React.PureComponent {
size: PropTypes.number.isRequired,
onClick: PropTypes.func.isRequired,
displayWidth: PropTypes.number,
+ visible: PropTypes.bool.isRequired,
};
static defaultProps = {
@@ -29,6 +31,10 @@ class Item extends React.PureComponent {
size: 1,
};
+ state = {
+ loaded: false,
+ };
+
handleMouseEnter = (e) => {
if (this.hoverToPlay()) {
e.target.play();
@@ -62,8 +68,40 @@ class Item extends React.PureComponent {
e.stopPropagation();
}
+ componentDidMount () {
+ if (this.props.attachment.get('blurhash')) {
+ this._decode();
+ }
+ }
+
+ componentDidUpdate (prevProps) {
+ if (prevProps.attachment.get('blurhash') !== this.props.attachment.get('blurhash') && this.props.attachment.get('blurhash')) {
+ this._decode();
+ }
+ }
+
+ _decode () {
+ const hash = this.props.attachment.get('blurhash');
+ const pixels = decode(hash, 32, 32);
+
+ if (pixels) {
+ const ctx = this.canvas.getContext('2d');
+ const imageData = new ImageData(pixels, 32, 32);
+
+ ctx.putImageData(imageData, 0, 0);
+ }
+ }
+
+ setCanvasRef = c => {
+ this.canvas = c;
+ }
+
+ handleImageLoad = () => {
+ this.setState({ loaded: true });
+ }
+
render () {
- const { attachment, index, size, standalone, displayWidth } = this.props;
+ const { attachment, index, size, standalone, displayWidth, visible } = this.props;
let width = 50;
let height = 100;
@@ -116,12 +154,20 @@ class Item extends React.PureComponent {
let thumbnail = '';
- if (attachment.get('type') === 'image') {
+ if (attachment.get('type') === 'unknown') {
+ return (
+
+ );
+ } else if (attachment.get('type') === 'image') {
const previewUrl = attachment.get('preview_url');
const previewWidth = attachment.getIn(['meta', 'small', 'width']);
- const originalUrl = attachment.get('url');
- const originalWidth = attachment.getIn(['meta', 'original', 'width']);
+ const originalUrl = attachment.get('url');
+ const originalWidth = attachment.getIn(['meta', 'original', 'width']);
const hasSize = typeof originalWidth === 'number' && typeof previewWidth === 'number';
@@ -147,6 +193,7 @@ class Item extends React.PureComponent {
alt={attachment.get('description')}
title={attachment.get('description')}
style={{ objectPosition: `${x}% ${y}%` }}
+ onLoad={this.handleImageLoad}
/>
);
@@ -176,7 +223,8 @@ class Item extends React.PureComponent {
return (
- {thumbnail}
+
+ {visible && thumbnail}
);
}
@@ -225,6 +273,7 @@ class MediaGallery extends React.PureComponent {
if (node /*&& this.isStandaloneEligible()*/) {
// offsetWidth triggers a layout, so only calculate when we need to
if (this.props.cacheWidth) this.props.cacheWidth(node.offsetWidth);
+
this.setState({
width: node.offsetWidth,
});
@@ -242,7 +291,7 @@ class MediaGallery extends React.PureComponent {
const width = this.state.width || defaultWidth;
- let children;
+ let children, spoilerButton;
const style = {};
@@ -256,35 +305,28 @@ class MediaGallery extends React.PureComponent {
style.height = height;
}
- if (!visible) {
- let warning;
+ const size = media.take(4).size;
- if (sensitive) {
- warning = ;
- } else {
- warning = ;
- }
+ if (this.isStandaloneEligible()) {
+ children = ;
+ } else {
+ children = media.take(4).map((attachment, i) => );
+ }
- children = (
-
- {warning}
-
+ if (visible) {
+ spoilerButton = ;
+ } else {
+ spoilerButton = (
+
+ {sensitive ? : }
);
- } else {
- const size = media.take(4).size;
-
- if (this.isStandaloneEligible()) {
- children = ;
- } else {
- children = media.take(4).map((attachment, i) => );
- }
}
return (
-
-
+
+ {spoilerButton}
{children}
diff --git a/app/javascript/mastodon/components/status.js b/app/javascript/mastodon/components/status.js
index cea9a0c2e..95ca4a548 100644
--- a/app/javascript/mastodon/components/status.js
+++ b/app/javascript/mastodon/components/status.js
@@ -274,7 +274,7 @@ class Status extends ImmutablePureComponent {
if (status.get('poll')) {
media =
;
} else if (status.get('media_attachments').size > 0) {
- if (this.props.muted || status.get('media_attachments').some(item => item.get('type') === 'unknown')) {
+ if (this.props.muted) {
media = (
(
(
;
} else if (status.get('media_attachments').size > 0) {
- if (status.get('media_attachments').some(item => item.get('type') === 'unknown')) {
- media = ;
- } else if (status.getIn(['media_attachments', 0, 'type']) === 'video') {
+ if (status.getIn(['media_attachments', 0, 'type']) === 'video') {
const video = status.getIn(['media_attachments', 0]);
media = (
{
this.video = c;
+
if (this.video) {
this.setState({ volume: this.video.volume, muted: this.video.muted });
}
@@ -152,6 +155,10 @@ class Video extends React.PureComponent {
this.volume = c;
}
+ setCanvasRef = c => {
+ this.canvas = c;
+ }
+
handleClickRoot = e => e.stopPropagation();
handlePlay = () => {
@@ -170,7 +177,6 @@ class Video extends React.PureComponent {
}
handleVolumeMouseDown = e => {
-
document.addEventListener('mousemove', this.handleMouseVolSlide, true);
document.addEventListener('mouseup', this.handleVolumeMouseUp, true);
document.addEventListener('touchmove', this.handleMouseVolSlide, true);
@@ -190,7 +196,6 @@ class Video extends React.PureComponent {
}
handleMouseVolSlide = throttle(e => {
-
const rect = this.volume.getBoundingClientRect();
const x = (e.clientX - rect.left) / this.volWidth; //x position within the element.
@@ -261,6 +266,10 @@ class Video extends React.PureComponent {
document.addEventListener('webkitfullscreenchange', this.handleFullscreenChange, true);
document.addEventListener('mozfullscreenchange', this.handleFullscreenChange, true);
document.addEventListener('MSFullscreenChange', this.handleFullscreenChange, true);
+
+ if (this.props.blurhash) {
+ this._decode();
+ }
}
componentWillUnmount () {
@@ -270,6 +279,24 @@ class Video extends React.PureComponent {
document.removeEventListener('MSFullscreenChange', this.handleFullscreenChange, true);
}
+ componentDidUpdate (prevProps) {
+ if (prevProps.blurhash !== this.props.blurhash && this.props.blurhash) {
+ this._decode();
+ }
+ }
+
+ _decode () {
+ const hash = this.props.blurhash;
+ const pixels = decode(hash, 32, 32);
+
+ if (pixels) {
+ const ctx = this.canvas.getContext('2d');
+ const imageData = new ImageData(pixels, 32, 32);
+
+ ctx.putImageData(imageData, 0, 0);
+ }
+ }
+
handleFullscreenChange = () => {
this.setState({ fullscreen: isFullscreen() });
}
@@ -314,6 +341,7 @@ class Video extends React.PureComponent {
handleOpenVideo = () => {
const { src, preview, width, height, alt } = this.props;
+
const media = fromJS({
type: 'video',
url: src,
@@ -351,6 +379,7 @@ class Video extends React.PureComponent {
}
let preload;
+
if (startTime || fullscreen || dragging) {
preload = 'auto';
} else if (detailed) {
@@ -360,6 +389,7 @@ class Video extends React.PureComponent {
}
let warning;
+
if (sensitive) {
warning = ;
} else {
@@ -377,7 +407,9 @@ class Video extends React.PureComponent {
onClick={this.handleClickRoot}
tabIndex={0}
>
-
+
+ {revealed &&
+ />}
-
- {warning}
-
-
+
+
+ {warning}
+
+
diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss
index 0b1fd3652..48970f8bd 100644
--- a/app/javascript/styles/mastodon/components.scss
+++ b/app/javascript/styles/mastodon/components.scss
@@ -2412,7 +2412,7 @@ a.account__display-name {
& > div {
background: rgba($base-shadow-color, 0.6);
- border-radius: 4px;
+ border-radius: 8px;
padding: 12px 9px;
flex: 0 0 auto;
display: flex;
@@ -2423,19 +2423,18 @@ a.account__display-name {
button,
a {
display: inline;
- color: $primary-text-color;
+ color: $secondary-text-color;
background: transparent;
border: 0;
- padding: 0 5px;
+ padding: 0 8px;
text-decoration: none;
- opacity: 0.6;
font-size: 18px;
line-height: 18px;
&:hover,
&:active,
&:focus {
- opacity: 1;
+ color: $primary-text-color;
}
}
@@ -2932,15 +2931,49 @@ a.status-card.compact:hover {
}
.spoiler-button {
- display: none;
- left: 4px;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
position: absolute;
- text-shadow: 0 1px 1px $base-shadow-color, 1px 0 1px $base-shadow-color;
- top: 4px;
z-index: 100;
- &.spoiler-button--visible {
+ &--minified {
display: block;
+ left: 4px;
+ top: 4px;
+ width: auto;
+ height: auto;
+ }
+
+ &--hidden {
+ display: none;
+ }
+
+ &__overlay {
+ display: block;
+ background: transparent;
+ width: 100%;
+ height: 100%;
+ border: 0;
+
+ &__label {
+ display: inline-block;
+ background: rgba($base-overlay-background, 0.5);
+ border-radius: 8px;
+ padding: 8px 12px;
+ color: $primary-text-color;
+ font-weight: 500;
+ font-size: 14px;
+ }
+
+ &:hover,
+ &:focus,
+ &:active {
+ .spoiler-button__overlay__label {
+ background: rgba($base-overlay-background, 0.8);
+ }
+ }
}
}
@@ -4313,6 +4346,8 @@ a.status-card.compact:hover {
text-decoration: none;
color: $secondary-text-color;
line-height: 0;
+ position: relative;
+ z-index: 1;
&,
img {
@@ -4325,6 +4360,21 @@ a.status-card.compact:hover {
}
}
+.media-gallery__preview {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ position: absolute;
+ top: 0;
+ left: 0;
+ z-index: 0;
+ background: $base-overlay-background;
+
+ &--hidden {
+ display: none;
+ }
+}
+
.media-gallery__gifv {
height: 100%;
overflow: hidden;
diff --git a/app/lib/activitypub/activity/create.rb b/app/lib/activitypub/activity/create.rb
index dabdcbcf7..6b16c9986 100644
--- a/app/lib/activitypub/activity/create.rb
+++ b/app/lib/activitypub/activity/create.rb
@@ -194,7 +194,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
next if attachment['url'].blank?
href = Addressable::URI.parse(attachment['url']).normalize.to_s
- media_attachment = MediaAttachment.create(account: @account, remote_url: href, description: attachment['name'].presence, focus: attachment['focalPoint'])
+ media_attachment = MediaAttachment.create(account: @account, remote_url: href, description: attachment['name'].presence, focus: attachment['focalPoint'], blurhash: supported_blurhash?(attachment['blurhash']) ? attachment['blurhash'] : nil)
media_attachments << media_attachment
next if unsupported_media_type?(attachment['mediaType']) || skip_download?
@@ -369,6 +369,11 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
mime_type.present? && !(MediaAttachment::IMAGE_MIME_TYPES + MediaAttachment::VIDEO_MIME_TYPES).include?(mime_type)
end
+ def supported_blurhash?(blurhash)
+ components = blurhash.blank? ? nil : Blurhash.components(blurhash)
+ components.present? && components.none? { |comp| comp > 5 }
+ end
+
def skip_download?
return @skip_download if defined?(@skip_download)
@skip_download ||= DomainBlock.find_by(domain: @account.domain)&.reject_media?
diff --git a/app/lib/activitypub/adapter.rb b/app/lib/activitypub/adapter.rb
index 94eb2899c..c259c96f4 100644
--- a/app/lib/activitypub/adapter.rb
+++ b/app/lib/activitypub/adapter.rb
@@ -19,6 +19,7 @@ class ActivityPub::Adapter < ActiveModelSerializers::Adapter::Base
conversation: { 'ostatus' => 'http://ostatus.org#', 'inReplyToAtomUri' => 'ostatus:inReplyToAtomUri', 'conversation' => 'ostatus:conversation' },
focal_point: { 'toot' => 'http://joinmastodon.org/ns#', 'focalPoint' => { '@container' => '@list', '@id' => 'toot:focalPoint' } },
identity_proof: { 'toot' => 'http://joinmastodon.org/ns#', 'IdentityProof' => 'toot:IdentityProof' },
+ blurhash: { 'toot' => 'http://joinmastodon.org/ns#', 'blurhash' => 'toot:blurhash' },
}.freeze
def self.default_key_transform
diff --git a/app/models/media_attachment.rb b/app/models/media_attachment.rb
index a57ba0b2e..ab794faa0 100644
--- a/app/models/media_attachment.rb
+++ b/app/models/media_attachment.rb
@@ -18,6 +18,7 @@
# account_id :bigint(8)
# description :text
# scheduled_status_id :bigint(8)
+# blurhash :string
#
class MediaAttachment < ApplicationRecord
@@ -32,6 +33,11 @@ class MediaAttachment < ApplicationRecord
VIDEO_MIME_TYPES = ['video/webm', 'video/mp4', 'video/quicktime'].freeze
VIDEO_CONVERTIBLE_MIME_TYPES = ['video/webm', 'video/quicktime'].freeze
+ BLURHASH_OPTIONS = {
+ x_comp: 4,
+ y_comp: 4,
+ }.freeze
+
IMAGE_STYLES = {
original: {
pixels: 1_638_400, # 1280x1280px
@@ -41,6 +47,7 @@ class MediaAttachment < ApplicationRecord
small: {
pixels: 160_000, # 400x400px
file_geometry_parser: FastGeometryParser,
+ blurhash: BLURHASH_OPTIONS,
},
}.freeze
@@ -53,6 +60,8 @@ class MediaAttachment < ApplicationRecord
},
format: 'png',
time: 0,
+ file_geometry_parser: FastGeometryParser,
+ blurhash: BLURHASH_OPTIONS,
},
}.freeze
@@ -166,11 +175,11 @@ class MediaAttachment < ApplicationRecord
def file_processors(f)
if f.file_content_type == 'image/gif'
- [:gif_transcoder]
+ [:gif_transcoder, :blurhash_transcoder]
elsif VIDEO_MIME_TYPES.include? f.file_content_type
- [:video_transcoder]
+ [:video_transcoder, :blurhash_transcoder]
else
- [:lazy_thumbnail]
+ [:lazy_thumbnail, :blurhash_transcoder]
end
end
end
diff --git a/app/serializers/activitypub/note_serializer.rb b/app/serializers/activitypub/note_serializer.rb
index d11cfa59a..67f596e78 100644
--- a/app/serializers/activitypub/note_serializer.rb
+++ b/app/serializers/activitypub/note_serializer.rb
@@ -2,7 +2,7 @@
class ActivityPub::NoteSerializer < ActivityPub::Serializer
context_extensions :atom_uri, :conversation, :sensitive,
- :hashtag, :emoji, :focal_point
+ :hashtag, :emoji, :focal_point, :blurhash
attributes :id, :type, :summary,
:in_reply_to, :published, :url,
@@ -153,7 +153,7 @@ class ActivityPub::NoteSerializer < ActivityPub::Serializer
class MediaAttachmentSerializer < ActivityPub::Serializer
include RoutingHelper
- attributes :type, :media_type, :url, :name
+ attributes :type, :media_type, :url, :name, :blurhash
attribute :focal_point, if: :focal_point?
def type
diff --git a/app/serializers/rest/media_attachment_serializer.rb b/app/serializers/rest/media_attachment_serializer.rb
index 51011788b..1b3498ea4 100644
--- a/app/serializers/rest/media_attachment_serializer.rb
+++ b/app/serializers/rest/media_attachment_serializer.rb
@@ -5,7 +5,7 @@ class REST::MediaAttachmentSerializer < ActiveModel::Serializer
attributes :id, :type, :url, :preview_url,
:remote_url, :text_url, :meta,
- :description
+ :description, :blurhash
def id
object.id.to_s
diff --git a/app/views/stream_entries/_detailed_status.html.haml b/app/views/stream_entries/_detailed_status.html.haml
index 4459581d9..23f2920d8 100644
--- a/app/views/stream_entries/_detailed_status.html.haml
+++ b/app/views/stream_entries/_detailed_status.html.haml
@@ -28,7 +28,7 @@
- elsif !status.media_attachments.empty?
- if status.media_attachments.first.video?
- video = status.media_attachments.first
- = react_component :video, src: video.file.url(:original), preview: video.file.url(:small), sensitive: !current_account&.user&.show_all_media? && status.sensitive? || current_account&.user&.hide_all_media?, width: 670, height: 380, detailed: true, inline: true, alt: video.description do
+ = react_component :video, src: video.file.url(:original), preview: video.file.url(:small), blurhash: video.blurhash, sensitive: !current_account&.user&.show_all_media? && status.sensitive? || current_account&.user&.hide_all_media?, width: 670, height: 380, detailed: true, inline: true, alt: video.description do
= render partial: 'stream_entries/attachment_list', locals: { attachments: status.media_attachments }
- else
= react_component :media_gallery, height: 380, sensitive: !current_account&.user&.show_all_media? && status.sensitive? || current_account&.user&.hide_all_media?, standalone: true, 'autoPlayGif': current_account&.user&.setting_auto_play_gif || autoplay, 'reduceMotion': current_account&.user&.setting_reduce_motion, media: status.media_attachments.map { |a| ActiveModelSerializers::SerializableResource.new(a, serializer: REST::MediaAttachmentSerializer).as_json } do
diff --git a/app/views/stream_entries/_simple_status.html.haml b/app/views/stream_entries/_simple_status.html.haml
index ba22c5340..0df7497e1 100644
--- a/app/views/stream_entries/_simple_status.html.haml
+++ b/app/views/stream_entries/_simple_status.html.haml
@@ -32,7 +32,7 @@
- elsif !status.media_attachments.empty?
- if status.media_attachments.first.video?
- video = status.media_attachments.first
- = react_component :video, src: video.file.url(:original), preview: video.file.url(:small), sensitive: !current_account&.user&.show_all_media? && status.sensitive? || current_account&.user&.hide_all_media?, width: 610, height: 343, inline: true, alt: video.description do
+ = react_component :video, src: video.file.url(:original), preview: video.file.url(:small), blurhash: video.blurhash, sensitive: !current_account&.user&.show_all_media? && status.sensitive? || current_account&.user&.hide_all_media?, width: 610, height: 343, inline: true, alt: video.description do
= render partial: 'stream_entries/attachment_list', locals: { attachments: status.media_attachments }
- else
= react_component :media_gallery, height: 343, sensitive: !current_account&.user&.show_all_media? && status.sensitive? || current_account&.user&.hide_all_media?, 'autoPlayGif': current_account&.user&.setting_auto_play_gif || autoplay, media: status.media_attachments.map { |a| ActiveModelSerializers::SerializableResource.new(a, serializer: REST::MediaAttachmentSerializer).as_json } do
diff --git a/db/migrate/20190420025523_add_blurhash_to_media_attachments.rb b/db/migrate/20190420025523_add_blurhash_to_media_attachments.rb
new file mode 100644
index 000000000..f2bbe0a85
--- /dev/null
+++ b/db/migrate/20190420025523_add_blurhash_to_media_attachments.rb
@@ -0,0 +1,5 @@
+class AddBlurhashToMediaAttachments < ActiveRecord::Migration[5.2]
+ def change
+ add_column :media_attachments, :blurhash, :string
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 3060159d2..8613539d6 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 2019_04_09_054914) do
+ActiveRecord::Schema.define(version: 2019_04_20_025523) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -362,6 +362,7 @@ ActiveRecord::Schema.define(version: 2019_04_09_054914) do
t.bigint "account_id"
t.text "description"
t.bigint "scheduled_status_id"
+ t.string "blurhash"
t.index ["account_id"], name: "index_media_attachments_on_account_id"
t.index ["scheduled_status_id"], name: "index_media_attachments_on_scheduled_status_id"
t.index ["shortcode"], name: "index_media_attachments_on_shortcode", unique: true
diff --git a/lib/paperclip/blurhash_transcoder.rb b/lib/paperclip/blurhash_transcoder.rb
new file mode 100644
index 000000000..08925a6dd
--- /dev/null
+++ b/lib/paperclip/blurhash_transcoder.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+module Paperclip
+ class BlurhashTranscoder < Paperclip::Processor
+ def make
+ return @file unless options[:style] == :small
+
+ pixels = convert(':source RGB:-', source: File.expand_path(@file.path)).unpack('C*')
+ geometry = options.fetch(:file_geometry_parser).from_file(@file)
+
+ attachment.instance.blurhash = Blurhash.encode(geometry.width, geometry.height, pixels, options[:blurhash] || {})
+
+ @file
+ end
+ end
+end
diff --git a/package.json b/package.json
index 63cfa25b8..67396ccc3 100644
--- a/package.json
+++ b/package.json
@@ -78,6 +78,7 @@
"babel-plugin-react-intl": "^3.0.1",
"babel-plugin-transform-react-remove-prop-types": "^0.4.24",
"babel-runtime": "^6.26.0",
+ "blurhash": "^1.0.0",
"classnames": "^2.2.5",
"compression-webpack-plugin": "^2.0.0",
"cross-env": "^5.1.4",
diff --git a/yarn.lock b/yarn.lock
index 9d7f0eccb..329216cdb 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1743,6 +1743,11 @@ bluebird@^3.5.1, bluebird@^3.5.3:
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.3.tgz#7d01c6f9616c9a51ab0f8c549a79dfe6ec33efa7"
integrity sha512-/qKPUQlaW1OyR51WeCPBvRnAlnZFUJkCSG5HzGnuIqhgyJtF+T94lFnn33eiazjRm2LAHVy2guNnaq48X9SJuw==
+blurhash@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/blurhash/-/blurhash-1.0.0.tgz#9087bc5cc4d482f1305059d7410df4133adcab2e"
+ integrity sha512-x6fpZnd6AWde4U9m7xhUB44qIvGV4W6OdTAXGabYm4oZUOOGh5K1HAEoGAQn3iG4gbbPn9RSGce3VfNgGsX/Vw==
+
bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0:
version "4.11.8"
resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f"
--
cgit
From cd9a28470221f377aa9db7fb0e3e054b2633fdc1 Mon Sep 17 00:00:00 2001
From: Thibaut Girka
Date: Sat, 27 Apr 2019 21:10:46 +0200
Subject: Fix regression with textarea's caret position when selecting a
suggestion
---
app/javascript/flavours/glitch/reducers/compose.js | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/app/javascript/flavours/glitch/reducers/compose.js b/app/javascript/flavours/glitch/reducers/compose.js
index b1dba0a18..009e1fee7 100644
--- a/app/javascript/flavours/glitch/reducers/compose.js
+++ b/app/javascript/flavours/glitch/reducers/compose.js
@@ -41,7 +41,7 @@ import {
import { TIMELINE_DELETE } from 'flavours/glitch/actions/timelines';
import { STORE_HYDRATE } from 'flavours/glitch/actions/store';
import { REDRAFT } from 'flavours/glitch/actions/statuses';
-import { Map as ImmutableMap, List as ImmutableList, OrderedSet as ImmutableOrderedSet, fromJS, is } from 'immutable';
+import { Map as ImmutableMap, List as ImmutableList, OrderedSet as ImmutableOrderedSet, fromJS } from 'immutable';
import uuid from 'flavours/glitch/util/uuid';
import { privacyPreference } from 'flavours/glitch/util/privacy_preference';
import { me } from 'flavours/glitch/util/initial_state';
@@ -219,10 +219,10 @@ const insertSuggestion = (state, position, token, completion, path) => {
map.updateIn(path, oldText => `${oldText.slice(0, position)}${completion}${completion[0] === ':' ? '\u200B' : ' '}${oldText.slice(position + token.length)}`);
map.set('suggestion_token', null);
map.set('suggestions', ImmutableList());
- if (is(path, ImmutableList(['text']))) {
+ if (path.length === 1 && path[0] === 'text') {
map.set('focusDate', new Date());
+ map.set('caretPosition', position + completion.length + 1);
}
- map.set('caretPosition', position + completion.length + 1);
map.set('idempotencyKey', uuid());
});
};
--
cgit
From e18786dec769791d6ecd7f21a89ae97fb00c44cf Mon Sep 17 00:00:00 2001
From: Eugen Rochko
Date: Sat, 27 Apr 2019 23:55:16 +0200
Subject: Fix approved column being set to nil instead of false (#10642)
Fix https://github.com/tootsuite/mastodon/pull/10621#issuecomment-487316619
---
app/models/user.rb | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/models/user.rb b/app/models/user.rb
index 9a0671006..c42f6ad8d 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -282,7 +282,7 @@ class User < ApplicationRecord
end
def external?
- @external
+ !!@external
end
def sanitize_languages
--
cgit
From 5e79dd3f174524ebc1e4f0379de4c5bbe7db4b32 Mon Sep 17 00:00:00 2001
From: partev
Date: Sat, 27 Apr 2019 23:51:20 -0400
Subject: Update hy.json (#10644)
---
app/javascript/mastodon/locales/hy.json | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/app/javascript/mastodon/locales/hy.json b/app/javascript/mastodon/locales/hy.json
index d155619c9..ca7732d85 100644
--- a/app/javascript/mastodon/locales/hy.json
+++ b/app/javascript/mastodon/locales/hy.json
@@ -243,7 +243,7 @@
"navigation_bar.pins": "Ամրացված թթեր",
"navigation_bar.preferences": "Նախապատվություններ",
"navigation_bar.public_timeline": "Դաշնային հոսք",
- "navigation_bar.security": "Security",
+ "navigation_bar.security": "Անվտանգություն",
"notification.favourite": "{name} հավանեց թութդ",
"notification.follow": "{name} սկսեց հետեւել քեզ",
"notification.mention": "{name} նշեց քեզ",
@@ -309,7 +309,7 @@
"search_results.accounts": "People",
"search_results.hashtags": "Hashtags",
"search_results.statuses": "Toots",
- "search_results.total": "{count, number} {count, plural, one {result} other {results}}",
+ "search_results.total": "{count, number} {count, plural, one {արդյունք} other {արդյունք}}",
"status.admin_account": "Open moderation interface for @{name}",
"status.admin_status": "Open this status in the moderation interface",
"status.block": "Արգելափակել @{name}֊ին",
--
cgit
From f3acf8f41422e70b88371f0f6ed4a0b6d9723783 Mon Sep 17 00:00:00 2001
From: Thibaut Girka
Date: Sat, 27 Apr 2019 19:08:38 +0200
Subject: Add hotkey for bookmarking a toot
---
app/javascript/flavours/glitch/components/status.js | 6 ++++++
app/javascript/flavours/glitch/features/keyboard_shortcuts/index.js | 4 ++++
app/javascript/flavours/glitch/features/status/index.js | 5 +++++
app/javascript/flavours/glitch/features/ui/index.js | 1 +
4 files changed, 16 insertions(+)
diff --git a/app/javascript/flavours/glitch/components/status.js b/app/javascript/flavours/glitch/components/status.js
index cd22fb593..b28abbc42 100644
--- a/app/javascript/flavours/glitch/components/status.js
+++ b/app/javascript/flavours/glitch/components/status.js
@@ -53,6 +53,7 @@ export default class Status extends ImmutablePureComponent {
onReply: PropTypes.func,
onFavourite: PropTypes.func,
onReblog: PropTypes.func,
+ onBookmark: PropTypes.func,
onDelete: PropTypes.func,
onDirect: PropTypes.func,
onMention: PropTypes.func,
@@ -337,6 +338,10 @@ export default class Status extends ImmutablePureComponent {
this.props.onReblog(this.props.status, e);
}
+ handleHotkeyBookmark = e => {
+ this.props.onBookmark(this.props.status, e);
+ }
+
handleHotkeyMention = e => {
e.preventDefault();
this.props.onMention(this.props.status.get('account'), this.context.router.history);
@@ -550,6 +555,7 @@ export default class Status extends ImmutablePureComponent {
moveUp: this.handleHotkeyMoveUp,
moveDown: this.handleHotkeyMoveDown,
toggleSpoiler: this.handleExpandedToggle,
+ bookmark: this.handleHotkeyBookmark,
};
const computedClass = classNames('status', `status-${status.get('visibility')}`, {
diff --git a/app/javascript/flavours/glitch/features/keyboard_shortcuts/index.js b/app/javascript/flavours/glitch/features/keyboard_shortcuts/index.js
index 75eb5653c..465754153 100644
--- a/app/javascript/flavours/glitch/features/keyboard_shortcuts/index.js
+++ b/app/javascript/flavours/glitch/features/keyboard_shortcuts/index.js
@@ -52,6 +52,10 @@ export default class KeyboardShortcuts extends ImmutablePureComponent {
b
+
+ d
+
+
enter , o
diff --git a/app/javascript/flavours/glitch/features/status/index.js b/app/javascript/flavours/glitch/features/status/index.js
index a0a9b986c..6cf8b8151 100644
--- a/app/javascript/flavours/glitch/features/status/index.js
+++ b/app/javascript/flavours/glitch/features/status/index.js
@@ -325,6 +325,10 @@ export default class Status extends ImmutablePureComponent {
this.handleReblogClick(this.props.status);
}
+ handleHotkeyBookmark = () => {
+ this.handleBookmarkClick(this.props.status);
+ }
+
handleHotkeyMention = e => {
e.preventDefault();
this.handleMentionClick(this.props.status);
@@ -463,6 +467,7 @@ export default class Status extends ImmutablePureComponent {
reply: this.handleHotkeyReply,
favourite: this.handleHotkeyFavourite,
boost: this.handleHotkeyBoost,
+ bookmark: this.handleHotkeyBookmark,
mention: this.handleHotkeyMention,
openProfile: this.handleHotkeyOpenProfile,
toggleSpoiler: this.handleExpandedToggle,
diff --git a/app/javascript/flavours/glitch/features/ui/index.js b/app/javascript/flavours/glitch/features/ui/index.js
index dd527d528..947fdac93 100644
--- a/app/javascript/flavours/glitch/features/ui/index.js
+++ b/app/javascript/flavours/glitch/features/ui/index.js
@@ -98,6 +98,7 @@ const keyMap = {
goToMuted: 'g m',
goToRequests: 'g r',
toggleSpoiler: 'x',
+ bookmark: 'd',
};
@connect(mapStateToProps)
--
cgit
From 67fb9a867956207ce52e5d683634bd58c82340ad Mon Sep 17 00:00:00 2001
From: Thibaut Girka
Date: Sat, 27 Apr 2019 19:17:42 +0200
Subject: Add keyboard shortcut to collapse/uncollapse toots
---
app/javascript/flavours/glitch/components/status.js | 9 +++++++++
.../flavours/glitch/features/keyboard_shortcuts/index.js | 15 ++++++++++++++-
app/javascript/flavours/glitch/features/ui/index.js | 1 +
3 files changed, 24 insertions(+), 1 deletion(-)
diff --git a/app/javascript/flavours/glitch/components/status.js b/app/javascript/flavours/glitch/components/status.js
index b28abbc42..94297260b 100644
--- a/app/javascript/flavours/glitch/components/status.js
+++ b/app/javascript/flavours/glitch/components/status.js
@@ -367,6 +367,14 @@ export default class Status extends ImmutablePureComponent {
this.props.onMoveDown(this.props.containerId || this.props.id, e.target.getAttribute('data-featured'));
}
+ handleHotkeyCollapse = e => {
+ if (!this.props.settings.getIn(['collapsed', 'enabled']))
+ return;
+
+ this.setCollapsed(!this.state.isCollapsed);
+ }
+
+
handleRef = c => {
this.node = c;
}
@@ -556,6 +564,7 @@ export default class Status extends ImmutablePureComponent {
moveDown: this.handleHotkeyMoveDown,
toggleSpoiler: this.handleExpandedToggle,
bookmark: this.handleHotkeyBookmark,
+ toggleCollapse: this.handleHotkeyCollapse,
};
const computedClass = classNames('status', `status-${status.get('visibility')}`, {
diff --git a/app/javascript/flavours/glitch/features/keyboard_shortcuts/index.js b/app/javascript/flavours/glitch/features/keyboard_shortcuts/index.js
index 465754153..2935a6021 100644
--- a/app/javascript/flavours/glitch/features/keyboard_shortcuts/index.js
+++ b/app/javascript/flavours/glitch/features/keyboard_shortcuts/index.js
@@ -1,6 +1,7 @@
import React from 'react';
import Column from 'flavours/glitch/features/ui/components/column';
import ColumnBackButtonSlim from 'flavours/glitch/components/column_back_button_slim';
+import { connect } from 'react-redux';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import PropTypes from 'prop-types';
import ImmutablePureComponent from 'react-immutable-pure-component';
@@ -9,16 +10,22 @@ const messages = defineMessages({
heading: { id: 'keyboard_shortcuts.heading', defaultMessage: 'Keyboard Shortcuts' },
});
+const mapStateToProps = state => ({
+ collapseEnabled: state.getIn(['local_settings', 'collapsed', 'enabled']),
+});
+
+@connect(mapStateToProps)
@injectIntl
export default class KeyboardShortcuts extends ImmutablePureComponent {
static propTypes = {
intl: PropTypes.object.isRequired,
multiColumn: PropTypes.bool,
+ collapseEnabled: PropTypes.bool,
};
render () {
- const { intl } = this.props;
+ const { intl, collapseEnabled } = this.props;
return (
@@ -64,6 +71,12 @@ export default class KeyboardShortcuts extends ImmutablePureComponent {
x
+ {collapseEnabled && (
+
+ shift +x
+
+
+ )}
up , k
diff --git a/app/javascript/flavours/glitch/features/ui/index.js b/app/javascript/flavours/glitch/features/ui/index.js
index 947fdac93..f95571d25 100644
--- a/app/javascript/flavours/glitch/features/ui/index.js
+++ b/app/javascript/flavours/glitch/features/ui/index.js
@@ -99,6 +99,7 @@ const keyMap = {
goToRequests: 'g r',
toggleSpoiler: 'x',
bookmark: 'd',
+ toggleCollapse: 'shift+x',
};
@connect(mapStateToProps)
--
cgit
From 3e095cab83f3e88c5f5f4ca9d7029379ed5b5b56 Mon Sep 17 00:00:00 2001
From: Thibaut Girka
Date: Mon, 29 Apr 2019 19:00:27 +0200
Subject: Add support for missing formatting tags
---
app/javascript/flavours/glitch/styles/components/status.scss | 5 +++++
app/lib/sanitize_config.rb | 2 +-
2 files changed, 6 insertions(+), 1 deletion(-)
diff --git a/app/javascript/flavours/glitch/styles/components/status.scss b/app/javascript/flavours/glitch/styles/components/status.scss
index 889bcaec0..b656f0baf 100644
--- a/app/javascript/flavours/glitch/styles/components/status.scss
+++ b/app/javascript/flavours/glitch/styles/components/status.scss
@@ -69,6 +69,11 @@
font-style: italic;
}
+ sub {
+ font-size: smaller;
+ text-align: sub;
+ }
+
ul, ol {
margin-left: 1em;
diff --git a/app/lib/sanitize_config.rb b/app/lib/sanitize_config.rb
index 0a87bf6df..23d0a418f 100644
--- a/app/lib/sanitize_config.rb
+++ b/app/lib/sanitize_config.rb
@@ -20,7 +20,7 @@ class Sanitize
end
MASTODON_STRICT ||= freeze_config(
- elements: %w(p br span a abbr del pre blockquote code b strong i em h1 h2 h3 h4 h5 ul ol li),
+ elements: %w(p br span a abbr del pre blockquote code b strong u sub i em h1 h2 h3 h4 h5 ul ol li),
attributes: {
'a' => %w(href rel class title),
--
cgit
From d4c95e6a8a8b869c6f22a700c7ab37aa8a8f653a Mon Sep 17 00:00:00 2001
From: Thibaut Girka
Date: Mon, 29 Apr 2019 20:10:44 +0200
Subject: When selecting a toot via keyboard, ensure it is scrolled into view
Fixes detailed status column
---
.../flavours/glitch/features/status/index.js | 22 ++++++++++++++--------
1 file changed, 14 insertions(+), 8 deletions(-)
diff --git a/app/javascript/flavours/glitch/features/status/index.js b/app/javascript/flavours/glitch/features/status/index.js
index 6cf8b8151..57d70db1a 100644
--- a/app/javascript/flavours/glitch/features/status/index.js
+++ b/app/javascript/flavours/glitch/features/status/index.js
@@ -344,15 +344,15 @@ export default class Status extends ImmutablePureComponent {
const { status, ancestorsIds, descendantsIds } = this.props;
if (id === status.get('id')) {
- this._selectChild(ancestorsIds.size - 1);
+ this._selectChild(ancestorsIds.size - 1, true);
} else {
let index = ancestorsIds.indexOf(id);
if (index === -1) {
index = descendantsIds.indexOf(id);
- this._selectChild(ancestorsIds.size + index);
+ this._selectChild(ancestorsIds.size + index, true);
} else {
- this._selectChild(index - 1);
+ this._selectChild(index - 1, true);
}
}
}
@@ -361,23 +361,29 @@ export default class Status extends ImmutablePureComponent {
const { status, ancestorsIds, descendantsIds } = this.props;
if (id === status.get('id')) {
- this._selectChild(ancestorsIds.size + 1);
+ this._selectChild(ancestorsIds.size + 1, false);
} else {
let index = ancestorsIds.indexOf(id);
if (index === -1) {
index = descendantsIds.indexOf(id);
- this._selectChild(ancestorsIds.size + index + 2);
+ this._selectChild(ancestorsIds.size + index + 2, false);
} else {
- this._selectChild(index + 1);
+ this._selectChild(index + 1, false);
}
}
}
- _selectChild (index) {
- const element = this.node.querySelectorAll('.focusable')[index];
+ _selectChild (index, align_top) {
+ const container = this.node;
+ const element = container.querySelectorAll('.focusable')[index];
if (element) {
+ if (align_top && container.scrollTop > element.offsetTop) {
+ element.scrollIntoView(true);
+ } else if (!align_top && container.scrollTop + container.clientHeight < element.offsetTop + element.offsetHeight) {
+ element.scrollIntoView(false);
+ }
element.focus();
}
}
--
cgit
From a36affb9d6d556460e282d9acd206609825f3a78 Mon Sep 17 00:00:00 2001
From: Zac
Date: Wed, 17 Apr 2019 22:48:20 -0400
Subject: add env var for max pinned toots
---
app/validators/status_pin_validator.rb | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/validators/status_pin_validator.rb b/app/validators/status_pin_validator.rb
index 2c7bce674..01796f77b 100644
--- a/app/validators/status_pin_validator.rb
+++ b/app/validators/status_pin_validator.rb
@@ -5,6 +5,6 @@ class StatusPinValidator < ActiveModel::Validator
pin.errors.add(:base, I18n.t('statuses.pin_errors.reblog')) if pin.status.reblog?
pin.errors.add(:base, I18n.t('statuses.pin_errors.ownership')) if pin.account_id != pin.status.account_id
pin.errors.add(:base, I18n.t('statuses.pin_errors.private')) unless %w(public unlisted).include?(pin.status.visibility)
- pin.errors.add(:base, I18n.t('statuses.pin_errors.limit')) if pin.account.status_pins.count > 4 && pin.account.local?
+ pin.errors.add(:base, I18n.t('statuses.pin_errors.limit')) if pin.account.status_pins.count >= (ENV['MAX_PINNED_TOOTS'].to_i || 5) && pin.account.local?
end
end
--
cgit
From 5616200ed415fabb8d31eb5257eec7525481b819 Mon Sep 17 00:00:00 2001
From: Zac
Date: Sun, 28 Apr 2019 20:10:30 -0400
Subject: added documentation into .env.production.sample
moved max pin count into constant
---
.env.production.sample | 3 +++
app/validators/status_pin_validator.rb | 4 +++-
2 files changed, 6 insertions(+), 1 deletion(-)
diff --git a/.env.production.sample b/.env.production.sample
index efb5661fd..57779f73c 100644
--- a/.env.production.sample
+++ b/.env.production.sample
@@ -156,6 +156,9 @@ STREAMING_CLUSTER_NUM=1
# Maximum allowed character count
# MAX_TOOT_CHARS=500
+# Maximum number of pinned posts
+# MAX_PINNED_TOOTS=5
+
# LDAP authentication (optional)
# LDAP_ENABLED=true
# LDAP_HOST=localhost
diff --git a/app/validators/status_pin_validator.rb b/app/validators/status_pin_validator.rb
index 01796f77b..c88a9e5d3 100644
--- a/app/validators/status_pin_validator.rb
+++ b/app/validators/status_pin_validator.rb
@@ -1,10 +1,12 @@
# frozen_string_literal: true
class StatusPinValidator < ActiveModel::Validator
+ MAX_PINNED = (ENV['MAX_PINNED_TOOTS'].to_i - 1 || 4)
+
def validate(pin)
pin.errors.add(:base, I18n.t('statuses.pin_errors.reblog')) if pin.status.reblog?
pin.errors.add(:base, I18n.t('statuses.pin_errors.ownership')) if pin.account_id != pin.status.account_id
pin.errors.add(:base, I18n.t('statuses.pin_errors.private')) unless %w(public unlisted).include?(pin.status.visibility)
- pin.errors.add(:base, I18n.t('statuses.pin_errors.limit')) if pin.account.status_pins.count >= (ENV['MAX_PINNED_TOOTS'].to_i || 5) && pin.account.local?
+ pin.errors.add(:base, I18n.t('statuses.pin_errors.limit')) if pin.account.status_pins.count > MAX_PINNED && pin.account.local?
end
end
--
cgit
From be6ef1947e5847e57866a225207d9ac6b07b5b9c Mon Sep 17 00:00:00 2001
From: Zac
Date: Mon, 29 Apr 2019 14:35:00 -0400
Subject: changed pinned status count operator
changed default pinned count to 5 to preserve default behavior
---
app/validators/status_pin_validator.rb | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/app/validators/status_pin_validator.rb b/app/validators/status_pin_validator.rb
index c88a9e5d3..16353066c 100644
--- a/app/validators/status_pin_validator.rb
+++ b/app/validators/status_pin_validator.rb
@@ -1,12 +1,12 @@
# frozen_string_literal: true
class StatusPinValidator < ActiveModel::Validator
- MAX_PINNED = (ENV['MAX_PINNED_TOOTS'].to_i - 1 || 4)
+ MAX_PINNED = (ENV['MAX_PINNED_TOOTS'] || 5).to_i
def validate(pin)
pin.errors.add(:base, I18n.t('statuses.pin_errors.reblog')) if pin.status.reblog?
pin.errors.add(:base, I18n.t('statuses.pin_errors.ownership')) if pin.account_id != pin.status.account_id
pin.errors.add(:base, I18n.t('statuses.pin_errors.private')) unless %w(public unlisted).include?(pin.status.visibility)
- pin.errors.add(:base, I18n.t('statuses.pin_errors.limit')) if pin.account.status_pins.count > MAX_PINNED && pin.account.local?
+ pin.errors.add(:base, I18n.t('statuses.pin_errors.limit')) if pin.account.status_pins.count >= MAX_PINNED && pin.account.local?
end
end
--
cgit
From feff0fc9b2cf0a37175da23c38484759971b8db8 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]"
Date: Tue, 30 Apr 2019 18:05:22 +0200
Subject: Bump rubocop from 0.67.2 to 0.68.0 (#10654)
Bumps [rubocop](https://github.com/rubocop-hq/rubocop) from 0.67.2 to 0.68.0.
- [Release notes](https://github.com/rubocop-hq/rubocop/releases)
- [Changelog](https://github.com/rubocop-hq/rubocop/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rubocop-hq/rubocop/compare/v0.67.2...v0.68.0)
Signed-off-by: dependabot[bot]
---
Gemfile | 2 +-
Gemfile.lock | 8 +++-----
2 files changed, 4 insertions(+), 6 deletions(-)
diff --git a/Gemfile b/Gemfile
index fa8478d89..b908dc494 100644
--- a/Gemfile
+++ b/Gemfile
@@ -128,7 +128,7 @@ group :development do
gem 'letter_opener', '~> 1.7'
gem 'letter_opener_web', '~> 1.3'
gem 'memory_profiler'
- gem 'rubocop', '~> 0.67', require: false
+ gem 'rubocop', '~> 0.68', require: false
gem 'brakeman', '~> 4.5', require: false
gem 'bundler-audit', '~> 0.6', require: false
gem 'scss_lint', '~> 0.57', require: false
diff --git a/Gemfile.lock b/Gemfile.lock
index 0148cb5ea..d03ed2e60 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -395,7 +395,7 @@ GEM
parallel (1.17.0)
parallel_tests (2.28.0)
parallel
- parser (2.6.2.1)
+ parser (2.6.3.0)
ast (~> 2.4.0)
pastel (0.7.2)
equatable (~> 0.5.0)
@@ -420,7 +420,6 @@ GEM
pry (~> 0.10)
pry-rails (0.3.9)
pry (>= 0.10.4)
- psych (3.1.0)
public_suffix (3.0.3)
puma (3.12.1)
pundit (2.0.1)
@@ -528,11 +527,10 @@ GEM
rspec-core (~> 3.0, >= 3.0.0)
sidekiq (>= 2.4.0)
rspec-support (3.8.0)
- rubocop (0.67.2)
+ rubocop (0.68.0)
jaro_winkler (~> 1.5.1)
parallel (~> 1.10)
parser (>= 2.5, != 2.5.1.1)
- psych (>= 3.1.0)
rainbow (>= 2.2.2, < 4.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 1.4.0, < 1.6)
@@ -750,7 +748,7 @@ DEPENDENCIES
rqrcode (~> 0.10)
rspec-rails (~> 3.8)
rspec-sidekiq (~> 3.0)
- rubocop (~> 0.67)
+ rubocop (~> 0.68)
sanitize (~> 5.0)
scss_lint (~> 0.57)
sidekiq (~> 5.2)
--
cgit
From 7c94b190c8b65da46bb394499240c4c8569d29ca Mon Sep 17 00:00:00 2001
From: "dependabot[bot]"
Date: Tue, 30 Apr 2019 18:05:49 +0200
Subject: Bump bullet from 5.9.0 to 6.0.0 (#10635)
Bumps [bullet](https://github.com/flyerhzm/bullet) from 5.9.0 to 6.0.0.
- [Release notes](https://github.com/flyerhzm/bullet/releases)
- [Changelog](https://github.com/flyerhzm/bullet/blob/master/CHANGELOG.md)
- [Commits](https://github.com/flyerhzm/bullet/compare/5.9.0...6.0.0)
Signed-off-by: dependabot[bot]
---
Gemfile | 2 +-
Gemfile.lock | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/Gemfile b/Gemfile
index b908dc494..8078e46a0 100644
--- a/Gemfile
+++ b/Gemfile
@@ -124,7 +124,7 @@ group :development do
gem 'annotate', '~> 2.7'
gem 'better_errors', '~> 2.5'
gem 'binding_of_caller', '~> 0.7'
- gem 'bullet', '~> 5.9'
+ gem 'bullet', '~> 6.0'
gem 'letter_opener', '~> 1.7'
gem 'letter_opener_web', '~> 1.3'
gem 'memory_profiler'
diff --git a/Gemfile.lock b/Gemfile.lock
index d03ed2e60..188e99b13 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -106,7 +106,7 @@ GEM
brakeman (4.5.0)
browser (2.5.3)
builder (3.2.3)
- bullet (5.9.0)
+ bullet (6.0.0)
activesupport (>= 3.0.0)
uniform_notifier (~> 1.11)
bundler-audit (0.6.1)
@@ -665,7 +665,7 @@ DEPENDENCIES
bootsnap (~> 1.4)
brakeman (~> 4.5)
browser
- bullet (~> 5.9)
+ bullet (~> 6.0)
bundler-audit (~> 0.6)
capistrano (~> 3.11)
capistrano-rails (~> 1.4)
--
cgit
From 825d5c79b76f0be59a6123d0b26c52b61ad87132 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]"
Date: Tue, 30 Apr 2019 18:06:09 +0200
Subject: Bump annotate from 2.7.4 to 2.7.5 (#10651)
Bumps [annotate](https://github.com/ctran/annotate_models) from 2.7.4 to 2.7.5.
- [Release notes](https://github.com/ctran/annotate_models/releases)
- [Changelog](https://github.com/ctran/annotate_models/blob/develop/CHANGELOG.rdoc)
- [Commits](https://github.com/ctran/annotate_models/compare/v2.7.4...2.7.5)
Signed-off-by: dependabot[bot]
---
Gemfile.lock | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/Gemfile.lock b/Gemfile.lock
index 188e99b13..d1758cfaf 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -66,8 +66,8 @@ GEM
public_suffix (>= 2.0.2, < 4.0)
airbrussh (1.3.0)
sshkit (>= 1.6.1, != 1.7.0)
- annotate (2.7.4)
- activerecord (>= 3.2, < 6.0)
+ annotate (2.7.5)
+ activerecord (>= 3.2, < 7.0)
rake (>= 10.4, < 13.0)
arel (9.0.0)
ast (2.4.0)
--
cgit
From 699109b9545f489b7f4dcf44710c48a0fb27de8c Mon Sep 17 00:00:00 2001
From: "dependabot[bot]"
Date: Wed, 1 May 2019 15:49:16 +0900
Subject: Bump rubocop from 0.68.0 to 0.68.1 (#10658)
Bumps [rubocop](https://github.com/rubocop-hq/rubocop) from 0.68.0 to 0.68.1.
- [Release notes](https://github.com/rubocop-hq/rubocop/releases)
- [Changelog](https://github.com/rubocop-hq/rubocop/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rubocop-hq/rubocop/compare/v0.68.0...v0.68.1)
Signed-off-by: dependabot[bot]
---
Gemfile.lock | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Gemfile.lock b/Gemfile.lock
index d1758cfaf..7e7526b3b 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -527,7 +527,7 @@ GEM
rspec-core (~> 3.0, >= 3.0.0)
sidekiq (>= 2.4.0)
rspec-support (3.8.0)
- rubocop (0.68.0)
+ rubocop (0.68.1)
jaro_winkler (~> 1.5.1)
parallel (~> 1.10)
parser (>= 2.5, != 2.5.1.1)
--
cgit
From 0db269f3dc1108630b987a45a68d7b8ec04a6ba6 Mon Sep 17 00:00:00 2001
From: ThibG
Date: Wed, 1 May 2019 15:19:55 +0200
Subject: Minor fixes to the French translation (#10662)
---
config/locales/fr.yml | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/config/locales/fr.yml b/config/locales/fr.yml
index a6c806de3..d588b239f 100644
--- a/config/locales/fr.yml
+++ b/config/locales/fr.yml
@@ -260,10 +260,10 @@ fr:
title: Nouveau blocage de domaine
reject_media: Fichiers média rejetés
reject_media_hint: Supprime localement les fichiers média stockés et refuse d’en télécharger ultérieurement. Ne concerne pas les suspensions
- reject_reports: Rapports de rejet
- reject_reports_hint: Ignorez tous les rapports provenant de ce domaine. Sans objet pour les suspensions
+ reject_reports: Rejeter les signalements
+ reject_reports_hint: Ignorez tous les signalements provenant de ce domaine. Ne concerne pas les suspensions
rejecting_media: rejet des fichiers multimédia
- rejecting_reports: rejet de rapports
+ rejecting_reports: rejet des signalements
severity:
silence: silencié
suspend: suspendu
--
cgit
From ac54292d69984e8ecc38dc53321c20cecea5d421 Mon Sep 17 00:00:00 2001
From: Thibaut Girka
Date: Sat, 27 Apr 2019 17:41:49 +0200
Subject: Add high color privacy icons
Fixes #1015
---
.../glitch/features/local_settings/page/index.js | 9 +++++++++
.../flavours/glitch/features/ui/index.js | 2 ++
.../flavours/glitch/reducers/local_settings.js | 1 +
.../flavours/glitch/styles/accessibility.scss | 22 ++++++++++++++++++++++
4 files changed, 34 insertions(+)
diff --git a/app/javascript/flavours/glitch/features/local_settings/page/index.js b/app/javascript/flavours/glitch/features/local_settings/page/index.js
index bc4ad359c..a13bffa3a 100644
--- a/app/javascript/flavours/glitch/features/local_settings/page/index.js
+++ b/app/javascript/flavours/glitch/features/local_settings/page/index.js
@@ -42,6 +42,15 @@ export default class LocalSettingsPage extends React.PureComponent {
>
+
+
+
+
({
dropdownMenuIsOpen: state.getIn(['dropdown_menu', 'openId']) !== null,
unreadNotifications: state.getIn(['notifications', 'unread']),
showFaviconBadge: state.getIn(['local_settings', 'notifications', 'favicon_badge']),
+ hicolorPrivacyIcons: state.getIn(['local_settings', 'hicolor_privacy_icons']),
});
const keyMap = {
@@ -446,6 +447,7 @@ export default class UI extends React.Component {
'wide': isWide,
'system-font': this.props.systemFontUi,
'navbar-under': navbarUnder,
+ 'hicolor-privacy-icons': this.props.hicolorPrivacyIcons,
});
const handlers = {
diff --git a/app/javascript/flavours/glitch/reducers/local_settings.js b/app/javascript/flavours/glitch/reducers/local_settings.js
index ef694d4ea..93ab1a9ed 100644
--- a/app/javascript/flavours/glitch/reducers/local_settings.js
+++ b/app/javascript/flavours/glitch/reducers/local_settings.js
@@ -18,6 +18,7 @@ const initialState = ImmutableMap({
confirm_before_clearing_draft: true,
preselect_on_reply: true,
inline_preview_cards: true,
+ hicolor_privacy_icons: true,
content_warnings : ImmutableMap({
auto_unfold : false,
filter : null,
diff --git a/app/javascript/flavours/glitch/styles/accessibility.scss b/app/javascript/flavours/glitch/styles/accessibility.scss
index 4fe5c8b1c..35e91da80 100644
--- a/app/javascript/flavours/glitch/styles/accessibility.scss
+++ b/app/javascript/flavours/glitch/styles/accessibility.scss
@@ -11,3 +11,25 @@ $emojis-requiring-outlines: '8ball' 'ant' 'back' 'black_circle' 'black_heart' 'b
}
}
}
+
+.hicolor-privacy-icons {
+ .status__visibility-icon.fa-globe,
+ .composer--options--dropdown--content--item .fa-globe {
+ color: #1976D2;
+ }
+
+ .status__visibility-icon.fa-unlock,
+ .composer--options--dropdown--content--item .fa-unlock {
+ color: #388E3C;
+ }
+
+ .status__visibility-icon.fa-lock,
+ .composer--options--dropdown--content--item .fa-lock {
+ color: #FFA000;
+ }
+
+ .status__visibility-icon.fa-envelope,
+ .composer--options--dropdown--content--item .fa-envelope {
+ color: #D32F2F;
+ }
+}
--
cgit
From cc5a81b7d87fbd0db67781ebc21e3805876fbb74 Mon Sep 17 00:00:00 2001
From: Thibaut Girka
Date: Wed, 1 May 2019 20:22:44 +0200
Subject: Make hi-color privacy icons opt-in instead of opt-out
---
app/javascript/flavours/glitch/reducers/local_settings.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/javascript/flavours/glitch/reducers/local_settings.js b/app/javascript/flavours/glitch/reducers/local_settings.js
index 93ab1a9ed..8dea4d8f6 100644
--- a/app/javascript/flavours/glitch/reducers/local_settings.js
+++ b/app/javascript/flavours/glitch/reducers/local_settings.js
@@ -18,7 +18,7 @@ const initialState = ImmutableMap({
confirm_before_clearing_draft: true,
preselect_on_reply: true,
inline_preview_cards: true,
- hicolor_privacy_icons: true,
+ hicolor_privacy_icons: false,
content_warnings : ImmutableMap({
auto_unfold : false,
filter : null,
--
cgit
From fcbf557bd2c4db3734e328ffe434895457836478 Mon Sep 17 00:00:00 2001
From: Thibaut Girka
Date: Tue, 30 Apr 2019 21:51:24 +0200
Subject: Add site-wide options to show reblogs and replies in local/public
timelines
Fixes #1021
---
app/models/form/admin_settings.rb | 4 ++++
app/models/status.rb | 12 ++++++++----
app/services/fan_out_on_write_service.rb | 5 +++--
app/views/admin/settings/edit.html.haml | 6 ++++++
config/locales/en.yml | 6 ++++++
config/settings.yml | 2 ++
6 files changed, 29 insertions(+), 6 deletions(-)
diff --git a/app/models/form/admin_settings.rb b/app/models/form/admin_settings.rb
index f81776346..0e9bfb265 100644
--- a/app/models/form/admin_settings.rb
+++ b/app/models/form/admin_settings.rb
@@ -32,6 +32,8 @@ class Form::AdminSettings
thumbnail
hero
mascot
+ show_reblogs_in_public_timelines
+ show_replies_in_public_timelines
).freeze
BOOLEAN_KEYS = %i(
@@ -45,6 +47,8 @@ class Form::AdminSettings
profile_directory
hide_followers_count
enable_keybase
+ show_reblogs_in_public_timelines
+ show_replies_in_public_timelines
).freeze
UPLOAD_KEYS = %i(
diff --git a/app/models/status.rb b/app/models/status.rb
index e7fa0220b..1b905d5b8 100644
--- a/app/models/status.rb
+++ b/app/models/status.rb
@@ -327,7 +327,8 @@ class Status < ApplicationRecord
end
def as_public_timeline(account = nil, local_only = false)
- query = timeline_scope(local_only).without_replies
+ query = timeline_scope(local_only)
+ query = query.without_replies unless Setting.show_replies_in_public_timelines
apply_timeline_filters(query, account, local_only)
end
@@ -408,9 +409,12 @@ class Status < ApplicationRecord
def timeline_scope(local_only = false)
starting_scope = local_only ? Status.local : Status
- starting_scope
- .with_public_visibility
- .without_reblogs
+ starting_scope = starting_scope.with_public_visibility
+ if Setting.show_reblogs_in_public_timelines
+ starting_scope
+ else
+ starting_scope.without_reblogs
+ end
end
def apply_timeline_filters(query, account, local_only)
diff --git a/app/services/fan_out_on_write_service.rb b/app/services/fan_out_on_write_service.rb
index de7c031d8..b66dc342e 100644
--- a/app/services/fan_out_on_write_service.rb
+++ b/app/services/fan_out_on_write_service.rb
@@ -21,11 +21,12 @@ class FanOutOnWriteService < BaseService
deliver_to_lists(status)
end
- return if status.account.silenced? || !status.public_visibility? || status.reblog?
+ return if status.account.silenced? || !status.public_visibility?
+ return if status.reblog? && !Setting.show_reblogs_in_public_timelines
deliver_to_hashtags(status)
- return if status.reply? && status.in_reply_to_account_id != status.account_id
+ return if status.reply? && status.in_reply_to_account_id != status.account_id && !Setting.show_replies_in_public_timelines
deliver_to_public(status)
deliver_to_media(status) if status.media_attachments.any?
diff --git a/app/views/admin/settings/edit.html.haml b/app/views/admin/settings/edit.html.haml
index 8f7acde5f..a8c9f6a58 100644
--- a/app/views/admin/settings/edit.html.haml
+++ b/app/views/admin/settings/edit.html.haml
@@ -72,6 +72,12 @@
.fields-group
= f.input :enable_keybase, as: :boolean, wrapper: :with_label, label: t('admin.settings.enable_keybase.title'), hint: t('admin.settings.enable_keybase.desc_html')
+ .fields-group
+ = f.input :show_reblogs_in_public_timelines, as: :boolean, wrapper: :with_label, label: t('admin.settings.show_reblogs_in_public_timelines.title'), hint: t('admin.settings.show_reblogs_in_public_timelines.desc_html')
+
+ .fields-group
+ = f.input :show_replies_in_public_timelines, as: :boolean, wrapper: :with_label, label: t('admin.settings.show_replies_in_public_timelines.title'), hint: t('admin.settings.show_replies_in_public_timelines.desc_html')
+
%hr.spacer/
.fields-group
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 5ec70a675..20a75e60c 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -438,6 +438,12 @@ en:
show_known_fediverse_at_about_page:
desc_html: When toggled, it will show toots from all the known fediverse on preview. Otherwise it will only show local toots.
title: Show known fediverse on timeline preview
+ show_reblogs_in_public_timelines:
+ desc_html: Show public boosts of public toots in local and public timelines.
+ title: Show boosts in public timelines
+ show_replies_in_public_timelines:
+ desc_html: In addition to public self-replies (threads), show public replies in local and public timelines.
+ title: Show replies in public timelines
show_staff_badge:
desc_html: Show a staff badge on a user page
title: Show staff badge
diff --git a/config/settings.yml b/config/settings.yml
index f0bfa9b69..c3aeab551 100644
--- a/config/settings.yml
+++ b/config/settings.yml
@@ -62,6 +62,8 @@ defaults: &defaults
activity_api_enabled: true
peers_api_enabled: true
show_known_fediverse_at_about_page: true
+ show_reblogs_in_public_timelines: false
+ show_replies_in_public_timelines: false
development:
<<: *defaults
--
cgit
From c4f24333002a6b1cec06f53c2910700648654487 Mon Sep 17 00:00:00 2001
From: ThibG
Date: Thu, 2 May 2019 00:10:19 +0200
Subject: Disallow robots from indexing /interact/ (#10666)
This does not provide any new information and may just triple the number
of crawled pages
---
public/robots.txt | 1 +
1 file changed, 1 insertion(+)
diff --git a/public/robots.txt b/public/robots.txt
index d93648bee..771bf2160 100644
--- a/public/robots.txt
+++ b/public/robots.txt
@@ -2,3 +2,4 @@
User-agent: *
Disallow: /media_proxy/
+Disallow: /interact/
--
cgit
From 21a73c52a7d0d0149e1058aeec155fe1c87aaeff Mon Sep 17 00:00:00 2001
From: ThibG
Date: Thu, 2 May 2019 04:30:12 +0200
Subject: Check that an invite link is valid before bypassing approval mode
(#10657)
* Check that an invite link is valid before bypassing approval mode
Fixes #10656
* Add tests
* Only consider valid invite links in registration controller
* fixup
---
app/controllers/auth/registrations_controller.rb | 3 +-
app/models/user.rb | 2 +-
.../auth/registrations_controller_spec.rb | 83 ++++++++++++++++++++++
3 files changed, 86 insertions(+), 2 deletions(-)
diff --git a/app/controllers/auth/registrations_controller.rb b/app/controllers/auth/registrations_controller.rb
index 5c1ff769a..83797cf1f 100644
--- a/app/controllers/auth/registrations_controller.rb
+++ b/app/controllers/auth/registrations_controller.rb
@@ -91,7 +91,8 @@ class Auth::RegistrationsController < Devise::RegistrationsController
end
def set_invite
- @invite = invite_code.present? ? Invite.find_by(code: invite_code) : nil
+ invite = invite_code.present? ? Invite.find_by(code: invite_code) : nil
+ @invite = invite&.valid_for_use? ? invite : nil
end
def determine_layout
diff --git a/app/models/user.rb b/app/models/user.rb
index c42f6ad8d..432078651 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -114,7 +114,7 @@ class User < ApplicationRecord
end
def invited?
- invite_id.present?
+ invite_id.present? && invite.valid_for_use?
end
def disable!
diff --git a/spec/controllers/auth/registrations_controller_spec.rb b/spec/controllers/auth/registrations_controller_spec.rb
index 1095df034..a4337039e 100644
--- a/spec/controllers/auth/registrations_controller_spec.rb
+++ b/spec/controllers/auth/registrations_controller_spec.rb
@@ -107,6 +107,89 @@ RSpec.describe Auth::RegistrationsController, type: :controller do
end
end
+ context 'approval-based registrations without invite' do
+ around do |example|
+ registrations_mode = Setting.registrations_mode
+ example.run
+ Setting.registrations_mode = registrations_mode
+ end
+
+ subject do
+ Setting.registrations_mode = 'approved'
+ request.headers["Accept-Language"] = accept_language
+ post :create, params: { user: { account_attributes: { username: 'test' }, email: 'test@example.com', password: '12345678', password_confirmation: '12345678' } }
+ end
+
+ it 'redirects to login page' do
+ subject
+ expect(response).to redirect_to new_user_session_path
+ end
+
+ it 'creates user' do
+ subject
+ user = User.find_by(email: 'test@example.com')
+ expect(user).to_not be_nil
+ expect(user.locale).to eq(accept_language)
+ expect(user.approved).to eq(false)
+ end
+ end
+
+ context 'approval-based registrations with expired invite' do
+ around do |example|
+ registrations_mode = Setting.registrations_mode
+ example.run
+ Setting.registrations_mode = registrations_mode
+ end
+
+ subject do
+ Setting.registrations_mode = 'approved'
+ request.headers["Accept-Language"] = accept_language
+ invite = Fabricate(:invite, max_uses: nil, expires_at: 1.hour.ago)
+ post :create, params: { user: { account_attributes: { username: 'test' }, email: 'test@example.com', password: '12345678', password_confirmation: '12345678', 'invite_code': invite.code } }
+ end
+
+ it 'redirects to login page' do
+ subject
+ expect(response).to redirect_to new_user_session_path
+ end
+
+ it 'creates user' do
+ subject
+ user = User.find_by(email: 'test@example.com')
+ expect(user).to_not be_nil
+ expect(user.locale).to eq(accept_language)
+ expect(user.approved).to eq(false)
+ end
+ end
+
+ context 'approval-based registrations with valid invite' do
+ around do |example|
+ registrations_mode = Setting.registrations_mode
+ example.run
+ Setting.registrations_mode = registrations_mode
+ end
+
+ subject do
+ Setting.registrations_mode = 'approved'
+ request.headers["Accept-Language"] = accept_language
+ invite = Fabricate(:invite, max_uses: nil, expires_at: 1.hour.from_now)
+ post :create, params: { user: { account_attributes: { username: 'test' }, email: 'test@example.com', password: '12345678', password_confirmation: '12345678', 'invite_code': invite.code } }
+ end
+
+ it 'redirects to login page' do
+ subject
+ expect(response).to redirect_to new_user_session_path
+ end
+
+ it 'creates user' do
+ subject
+ user = User.find_by(email: 'test@example.com')
+ expect(user).to_not be_nil
+ expect(user.locale).to eq(accept_language)
+ expect(user.approved).to eq(true)
+ end
+ end
+
it 'does nothing if user already exists' do
Fabricate(:user, account: Fabricate(:account, username: 'test'))
subject
--
cgit
From 3f143606faa6181ff2745b6bd29ac8ea075088bf Mon Sep 17 00:00:00 2001
From: Eugen Rochko
Date: Thu, 2 May 2019 08:34:32 +0200
Subject: Change account gallery in web UI (#10667)
- 3 items per row instead of 2
- Use blurhash for previews
- Animate/hover-to-play GIFs and videos
- Open media modal instead of opening status
- Allow opening status instead with ctrl+click and open in new tab
---
.../mastodon/components/media_gallery.js | 2 +-
.../account_gallery/components/media_item.js | 154 ++++++++++++++++-----
.../mastodon/features/account_gallery/index.js | 73 ++++++----
app/javascript/styles/mastodon/components.scss | 60 ++------
4 files changed, 172 insertions(+), 117 deletions(-)
diff --git a/app/javascript/mastodon/components/media_gallery.js b/app/javascript/mastodon/components/media_gallery.js
index f548296d0..7c1d3c3e9 100644
--- a/app/javascript/mastodon/components/media_gallery.js
+++ b/app/javascript/mastodon/components/media_gallery.js
@@ -157,7 +157,7 @@ class Item extends React.PureComponent {
if (attachment.get('type') === 'unknown') {
return (
diff --git a/app/javascript/mastodon/features/account_gallery/components/media_item.js b/app/javascript/mastodon/features/account_gallery/components/media_item.js
index 80ac9d9ec..8d462996e 100644
--- a/app/javascript/mastodon/features/account_gallery/components/media_item.js
+++ b/app/javascript/mastodon/features/account_gallery/components/media_item.js
@@ -1,62 +1,142 @@
import React from 'react';
+import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
-import Permalink from '../../../components/permalink';
-import { displayMedia } from '../../../initial_state';
-import Icon from 'mastodon/components/icon';
+import { autoPlayGif, displayMedia } from 'mastodon/initial_state';
+import classNames from 'classnames';
+import { decode } from 'blurhash';
+import { isIOS } from 'mastodon/is_mobile';
export default class MediaItem extends ImmutablePureComponent {
static propTypes = {
- media: ImmutablePropTypes.map.isRequired,
+ attachment: ImmutablePropTypes.map.isRequired,
+ displayWidth: PropTypes.number.isRequired,
+ onOpenMedia: PropTypes.func.isRequired,
};
state = {
- visible: displayMedia !== 'hide_all' && !this.props.media.getIn(['status', 'sensitive']) || displayMedia === 'show_all',
+ visible: displayMedia !== 'hide_all' && !this.props.attachment.getIn(['status', 'sensitive']) || displayMedia === 'show_all',
+ loaded: false,
};
- handleClick = () => {
- if (!this.state.visible) {
- this.setState({ visible: true });
- return true;
+ componentDidMount () {
+ if (this.props.attachment.get('blurhash')) {
+ this._decode();
}
+ }
- return false;
+ componentDidUpdate (prevProps) {
+ if (prevProps.attachment.get('blurhash') !== this.props.attachment.get('blurhash') && this.props.attachment.get('blurhash')) {
+ this._decode();
+ }
}
- render () {
- const { media } = this.props;
- const { visible } = this.state;
- const status = media.get('status');
- const focusX = media.getIn(['meta', 'focus', 'x']);
- const focusY = media.getIn(['meta', 'focus', 'y']);
- const x = ((focusX / 2) + .5) * 100;
- const y = ((focusY / -2) + .5) * 100;
- const style = {};
-
- let label, icon;
-
- if (media.get('type') === 'gifv') {
- label = GIF ;
+ _decode () {
+ const hash = this.props.attachment.get('blurhash');
+ const pixels = decode(hash, 32, 32);
+
+ if (pixels) {
+ const ctx = this.canvas.getContext('2d');
+ const imageData = new ImageData(pixels, 32, 32);
+
+ ctx.putImageData(imageData, 0, 0);
+ }
+ }
+
+ setCanvasRef = c => {
+ this.canvas = c;
+ }
+
+ handleImageLoad = () => {
+ this.setState({ loaded: true });
+ }
+
+ handleMouseEnter = e => {
+ if (this.hoverToPlay()) {
+ e.target.play();
+ }
+ }
+
+ handleMouseLeave = e => {
+ if (this.hoverToPlay()) {
+ e.target.pause();
+ e.target.currentTime = 0;
}
+ }
+
+ hoverToPlay () {
+ return !autoPlayGif && ['gifv', 'video'].indexOf(this.props.attachment.get('type')) !== -1;
+ }
+
+ handleClick = e => {
+ if (e.button === 0 && !(e.ctrlKey || e.metaKey)) {
+ e.preventDefault();
+
+ if (this.state.visible) {
+ this.props.onOpenMedia(this.props.attachment);
+ } else {
+ this.setState({ visible: true });
+ }
+ }
+ }
+
+ render () {
+ const { attachment, displayWidth } = this.props;
+ const { visible, loaded } = this.state;
+
+ const width = `${Math.floor((displayWidth - 4) / 3) - 4}px`;
+ const height = width;
+ const status = attachment.get('status');
+
+ let thumbnail = '';
+
+ if (attachment.get('type') === 'unknown') {
+ // Skip
+ } else if (attachment.get('type') === 'image') {
+ const focusX = attachment.getIn(['meta', 'focus', 'x']) || 0;
+ const focusY = attachment.getIn(['meta', 'focus', 'y']) || 0;
+ const x = ((focusX / 2) + .5) * 100;
+ const y = ((focusY / -2) + .5) * 100;
+
+ thumbnail = (
+
+ );
+ } else if (['gifv', 'video'].indexOf(attachment.get('type')) !== -1) {
+ const autoPlay = !isIOS() && autoPlayGif;
+
+ thumbnail = (
+
+
- if (visible) {
- style.backgroundImage = `url(${media.get('preview_url')})`;
- style.backgroundPosition = `${x}% ${y}%`;
- } else {
- icon = (
-
-
-
+ GIF
+
);
}
return (
-
-
- {icon}
- {label}
-
+
);
}
diff --git a/app/javascript/mastodon/features/account_gallery/index.js b/app/javascript/mastodon/features/account_gallery/index.js
index 73be58d6a..8766473fe 100644
--- a/app/javascript/mastodon/features/account_gallery/index.js
+++ b/app/javascript/mastodon/features/account_gallery/index.js
@@ -2,24 +2,25 @@ import React from 'react';
import { connect } from 'react-redux';
import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types';
-import { fetchAccount } from '../../actions/accounts';
+import { fetchAccount } from 'mastodon/actions/accounts';
import { expandAccountMediaTimeline } from '../../actions/timelines';
-import LoadingIndicator from '../../components/loading_indicator';
+import LoadingIndicator from 'mastodon/components/loading_indicator';
import Column from '../ui/components/column';
-import ColumnBackButton from '../../components/column_back_button';
+import ColumnBackButton from 'mastodon/components/column_back_button';
import ImmutablePureComponent from 'react-immutable-pure-component';
-import { getAccountGallery } from '../../selectors';
+import { getAccountGallery } from 'mastodon/selectors';
import MediaItem from './components/media_item';
import HeaderContainer from '../account_timeline/containers/header_container';
import { ScrollContainer } from 'react-router-scroll-4';
-import LoadMore from '../../components/load_more';
+import LoadMore from 'mastodon/components/load_more';
import MissingIndicator from 'mastodon/components/missing_indicator';
+import { openModal } from 'mastodon/actions/modal';
const mapStateToProps = (state, props) => ({
isAccount: !!state.getIn(['accounts', props.params.accountId]),
- medias: getAccountGallery(state, props.params.accountId),
+ attachments: getAccountGallery(state, props.params.accountId),
isLoading: state.getIn(['timelines', `account:${props.params.accountId}:media`, 'isLoading']),
- hasMore: state.getIn(['timelines', `account:${props.params.accountId}:media`, 'hasMore']),
+ hasMore: state.getIn(['timelines', `account:${props.params.accountId}:media`, 'hasMore']),
});
class LoadMoreMedia extends ImmutablePureComponent {
@@ -51,12 +52,16 @@ class AccountGallery extends ImmutablePureComponent {
static propTypes = {
params: PropTypes.object.isRequired,
dispatch: PropTypes.func.isRequired,
- medias: ImmutablePropTypes.list.isRequired,
+ attachments: ImmutablePropTypes.list.isRequired,
isLoading: PropTypes.bool,
hasMore: PropTypes.bool,
isAccount: PropTypes.bool,
};
+ state = {
+ width: 323,
+ };
+
componentDidMount () {
this.props.dispatch(fetchAccount(this.props.params.accountId));
this.props.dispatch(expandAccountMediaTimeline(this.props.params.accountId));
@@ -71,11 +76,11 @@ class AccountGallery extends ImmutablePureComponent {
handleScrollToBottom = () => {
if (this.props.hasMore) {
- this.handleLoadMore(this.props.medias.size > 0 ? this.props.medias.last().getIn(['status', 'id']) : undefined);
+ this.handleLoadMore(this.props.attachments.size > 0 ? this.props.attachments.last().getIn(['status', 'id']) : undefined);
}
}
- handleScroll = (e) => {
+ handleScroll = e => {
const { scrollTop, scrollHeight, clientHeight } = e.target;
const offset = scrollHeight - scrollTop - clientHeight;
@@ -88,13 +93,31 @@ class AccountGallery extends ImmutablePureComponent {
this.props.dispatch(expandAccountMediaTimeline(this.props.params.accountId, { maxId }));
};
- handleLoadOlder = (e) => {
+ handleLoadOlder = e => {
e.preventDefault();
this.handleScrollToBottom();
}
+ handleOpenMedia = attachment => {
+ if (attachment.get('type') === 'video') {
+ this.props.dispatch(openModal('VIDEO', { media: attachment }));
+ } else {
+ const media = attachment.getIn(['status', 'media_attachments']);
+ const index = media.findIndex(x => x.get('id') === attachment.get('id'));
+
+ this.props.dispatch(openModal('MEDIA', { media, index }));
+ }
+ }
+
+ handleRef = c => {
+ if (c) {
+ this.setState({ width: c.offsetWidth });
+ }
+ }
+
render () {
- const { medias, shouldUpdateScroll, isLoading, hasMore, isAccount } = this.props;
+ const { attachments, shouldUpdateScroll, isLoading, hasMore, isAccount } = this.props;
+ const { width } = this.state;
if (!isAccount) {
return (
@@ -104,9 +127,7 @@ class AccountGallery extends ImmutablePureComponent {
);
}
- let loadOlder = null;
-
- if (!medias && isLoading) {
+ if (!attachments && isLoading) {
return (
@@ -114,7 +135,9 @@ class AccountGallery extends ImmutablePureComponent {
);
}
- if (hasMore && !(isLoading && medias.size === 0)) {
+ let loadOlder = null;
+
+ if (hasMore && !(isLoading && attachments.size === 0)) {
loadOlder = ;
}
@@ -126,23 +149,17 @@ class AccountGallery extends ImmutablePureComponent {
-
- {medias.map((media, index) => media === null ? (
-
0 ? medias.getIn(index - 1, 'id') : null}
- onLoadMore={this.handleLoadMore}
- />
+
+ {attachments.map((attachment, index) => attachment === null ? (
+ 0 ? attachments.getIn(index - 1, 'id') : null} onLoadMore={this.handleLoadMore} />
) : (
-
+
))}
+
{loadOlder}
- {isLoading && medias.size === 0 && (
+ {isLoading && attachments.size === 0 && (
diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss
index 48970f8bd..0e942d234 100644
--- a/app/javascript/styles/mastodon/components.scss
+++ b/app/javascript/styles/mastodon/components.scss
@@ -4233,6 +4233,7 @@ a.status-card.compact:hover {
pointer-events: none;
opacity: 0.9;
transition: opacity 0.1s ease;
+ line-height: 18px;
}
.media-gallery__gifv {
@@ -4762,62 +4763,19 @@ a.status-card.compact:hover {
.account-gallery__container {
display: flex;
- justify-content: center;
flex-wrap: wrap;
- padding: 2px;
+ justify-content: center;
+ padding: 4px 2px;
}
.account-gallery__item {
- flex-grow: 1;
- width: 50%;
- overflow: hidden;
+ border: none;
+ box-sizing: border-box;
+ display: block;
position: relative;
-
- &::before {
- content: "";
- display: block;
- padding-top: 100%;
- }
-
- a {
- display: block;
- width: calc(100% - 4px);
- height: calc(100% - 4px);
- margin: 2px;
- top: 0;
- left: 0;
- background-color: $base-overlay-background;
- background-size: cover;
- background-position: center;
- position: absolute;
- color: $darker-text-color;
- text-decoration: none;
- border-radius: 4px;
-
- &:hover,
- &:active,
- &:focus {
- outline: 0;
- color: $secondary-text-color;
-
- &::before {
- content: "";
- display: block;
- width: 100%;
- height: 100%;
- background: rgba($base-overlay-background, 0.3);
- border-radius: 4px;
- }
- }
- }
-
- &__icons {
- position: absolute;
- top: 50%;
- left: 50%;
- transform: translate(-50%, -50%);
- font-size: 24px;
- }
+ border-radius: 4px;
+ overflow: hidden;
+ margin: 2px;
}
.notification__filter-bar,
--
cgit
From 967e419f8fa87af74f4bb530d7493c1dde02fca8 Mon Sep 17 00:00:00 2001
From: Eugen Rochko
Date: Fri, 3 May 2019 04:02:55 +0200
Subject: Fix alignment of items in the account gallery in web UI and load more
per page (#10674)
---
app/javascript/mastodon/actions/timelines.js | 2 +-
app/javascript/styles/mastodon/components.scss | 1 -
2 files changed, 1 insertion(+), 2 deletions(-)
diff --git a/app/javascript/mastodon/actions/timelines.js b/app/javascript/mastodon/actions/timelines.js
index d92385e95..06c21b96b 100644
--- a/app/javascript/mastodon/actions/timelines.js
+++ b/app/javascript/mastodon/actions/timelines.js
@@ -96,7 +96,7 @@ export const expandPublicTimeline = ({ maxId, onlyMedia } = {}, done =
export const expandCommunityTimeline = ({ maxId, onlyMedia } = {}, done = noOp) => expandTimeline(`community${onlyMedia ? ':media' : ''}`, '/api/v1/timelines/public', { local: true, max_id: maxId, only_media: !!onlyMedia }, done);
export const expandAccountTimeline = (accountId, { maxId, withReplies } = {}) => expandTimeline(`account:${accountId}${withReplies ? ':with_replies' : ''}`, `/api/v1/accounts/${accountId}/statuses`, { exclude_replies: !withReplies, max_id: maxId });
export const expandAccountFeaturedTimeline = accountId => expandTimeline(`account:${accountId}:pinned`, `/api/v1/accounts/${accountId}/statuses`, { pinned: true });
-export const expandAccountMediaTimeline = (accountId, { maxId } = {}) => expandTimeline(`account:${accountId}:media`, `/api/v1/accounts/${accountId}/statuses`, { max_id: maxId, only_media: true });
+export const expandAccountMediaTimeline = (accountId, { maxId } = {}) => expandTimeline(`account:${accountId}:media`, `/api/v1/accounts/${accountId}/statuses`, { max_id: maxId, only_media: true, limit: 40 });
export const expandListTimeline = (id, { maxId } = {}, done = noOp) => expandTimeline(`list:${id}`, `/api/v1/timelines/list/${id}`, { max_id: maxId }, done);
export const expandHashtagTimeline = (hashtag, { maxId, tags } = {}, done = noOp) => {
return expandTimeline(`hashtag:${hashtag}`, `/api/v1/timelines/tag/${hashtag}`, {
diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss
index 0e942d234..ebf46074b 100644
--- a/app/javascript/styles/mastodon/components.scss
+++ b/app/javascript/styles/mastodon/components.scss
@@ -4764,7 +4764,6 @@ a.status-card.compact:hover {
.account-gallery__container {
display: flex;
flex-wrap: wrap;
- justify-content: center;
padding: 4px 2px;
}
--
cgit
From 05ef3462ba0af7b147a7cfa8de2735e99dc59ac5 Mon Sep 17 00:00:00 2001
From: Eugen Rochko
Date: Fri, 3 May 2019 04:34:55 +0200
Subject: Make the "mark media as sensitive" button more obvious in web UI
(#10673)
* Make the "mark media as sensitive" button more obvious in web UI
* Use eye-slash icon instead of eye icon to mean "hide"
---
.../mastodon/components/media_gallery.js | 2 +-
.../features/compose/components/compose_form.js | 2 --
.../features/compose/components/upload_form.js | 3 ++
.../containers/sensitive_button_container.js | 40 +++++-----------------
app/javascript/mastodon/features/video/index.js | 2 +-
app/javascript/styles/mastodon/components.scss | 5 +++
6 files changed, 19 insertions(+), 35 deletions(-)
diff --git a/app/javascript/mastodon/components/media_gallery.js b/app/javascript/mastodon/components/media_gallery.js
index 7c1d3c3e9..abd17647e 100644
--- a/app/javascript/mastodon/components/media_gallery.js
+++ b/app/javascript/mastodon/components/media_gallery.js
@@ -314,7 +314,7 @@ class MediaGallery extends React.PureComponent {
}
if (visible) {
- spoilerButton = ;
+ spoilerButton = ;
} else {
spoilerButton = (
diff --git a/app/javascript/mastodon/features/compose/components/compose_form.js b/app/javascript/mastodon/features/compose/components/compose_form.js
index ddb610a89..2b9da20d7 100644
--- a/app/javascript/mastodon/features/compose/components/compose_form.js
+++ b/app/javascript/mastodon/features/compose/components/compose_form.js
@@ -10,7 +10,6 @@ import UploadButtonContainer from '../containers/upload_button_container';
import { defineMessages, injectIntl } from 'react-intl';
import SpoilerButtonContainer from '../containers/spoiler_button_container';
import PrivacyDropdownContainer from '../containers/privacy_dropdown_container';
-import SensitiveButtonContainer from '../containers/sensitive_button_container';
import EmojiPickerDropdown from '../containers/emoji_picker_dropdown_container';
import PollFormContainer from '../containers/poll_form_container';
import UploadFormContainer from '../containers/upload_form_container';
@@ -214,7 +213,6 @@ class ComposeForm extends ImmutablePureComponent {
-
diff --git a/app/javascript/mastodon/features/compose/components/upload_form.js b/app/javascript/mastodon/features/compose/components/upload_form.js
index b7f112205..9ff2aa0fa 100644
--- a/app/javascript/mastodon/features/compose/components/upload_form.js
+++ b/app/javascript/mastodon/features/compose/components/upload_form.js
@@ -3,6 +3,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
import UploadProgressContainer from '../containers/upload_progress_container';
import ImmutablePureComponent from 'react-immutable-pure-component';
import UploadContainer from '../containers/upload_container';
+import SensitiveButtonContainer from '../containers/sensitive_button_container';
export default class UploadForm extends ImmutablePureComponent {
@@ -22,6 +23,8 @@ export default class UploadForm extends ImmutablePureComponent {
))}
+
+ {!mediaIds.isEmpty() && }
);
}
diff --git a/app/javascript/mastodon/features/compose/containers/sensitive_button_container.js b/app/javascript/mastodon/features/compose/containers/sensitive_button_container.js
index 43de8f213..50612b086 100644
--- a/app/javascript/mastodon/features/compose/containers/sensitive_button_container.js
+++ b/app/javascript/mastodon/features/compose/containers/sensitive_button_container.js
@@ -2,11 +2,9 @@ import React from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import classNames from 'classnames';
-import IconButton from '../../../components/icon_button';
-import { changeComposeSensitivity } from '../../../actions/compose';
-import Motion from '../../ui/util/optional_motion';
-import spring from 'react-motion/lib/spring';
-import { injectIntl, defineMessages } from 'react-intl';
+import { changeComposeSensitivity } from 'mastodon/actions/compose';
+import { injectIntl, defineMessages, FormattedMessage } from 'react-intl';
+import Icon from 'mastodon/components/icon';
const messages = defineMessages({
marked: { id: 'compose_form.sensitive.marked', defaultMessage: 'Media is marked as sensitive' },
@@ -14,7 +12,6 @@ const messages = defineMessages({
});
const mapStateToProps = state => ({
- visible: state.getIn(['compose', 'media_attachments']).size > 0,
active: state.getIn(['compose', 'sensitive']),
disabled: state.getIn(['compose', 'spoiler']),
});
@@ -30,7 +27,6 @@ const mapDispatchToProps = dispatch => ({
class SensitiveButton extends React.PureComponent {
static propTypes = {
- visible: PropTypes.bool,
active: PropTypes.bool,
disabled: PropTypes.bool,
onClick: PropTypes.func.isRequired,
@@ -38,32 +34,14 @@ class SensitiveButton extends React.PureComponent {
};
render () {
- const { visible, active, disabled, onClick, intl } = this.props;
+ const { active, disabled, onClick, intl } = this.props;
return (
-
- {({ scale }) => {
- const icon = active ? 'eye-slash' : 'eye';
- const className = classNames('compose-form__sensitive-button', {
- 'compose-form__sensitive-button--visible': visible,
- });
- return (
-
-
-
- );
- }}
-
+
+
+
+
+
);
}
diff --git a/app/javascript/mastodon/features/video/index.js b/app/javascript/mastodon/features/video/index.js
index 7b6113e6a..8ae26eba8 100644
--- a/app/javascript/mastodon/features/video/index.js
+++ b/app/javascript/mastodon/features/video/index.js
@@ -472,7 +472,7 @@ class Video extends React.PureComponent {
- {!onCloseVideo && }
+ {!onCloseVideo && }
{(!fullscreen && onOpenVideo) && }
{onCloseVideo && }
diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss
index ebf46074b..8aebe3432 100644
--- a/app/javascript/styles/mastodon/components.scss
+++ b/app/javascript/styles/mastodon/components.scss
@@ -264,6 +264,11 @@
.compose-form {
padding: 10px;
+ &__sensitive-button {
+ padding: 10px;
+ padding-top: 0;
+ }
+
.compose-form__warning {
color: $inverted-text-color;
margin-bottom: 10px;
--
cgit
From 5121d9c12f39e95eaef630dd6c98b736cb76c4c0 Mon Sep 17 00:00:00 2001
From: ThibG
Date: Fri, 3 May 2019 06:20:36 +0200
Subject: When selecting a toot via keyboard, ensure it is scrolled into view
(#10593)
---
app/javascript/mastodon/components/status_list.js | 14 ++++++++++----
.../components/conversations_list.js | 14 ++++++++++----
.../mastodon/features/notifications/index.js | 14 ++++++++++----
app/javascript/mastodon/features/status/index.js | 22 ++++++++++++++--------
app/javascript/mastodon/features/ui/index.js | 9 +++++++--
5 files changed, 51 insertions(+), 22 deletions(-)
diff --git a/app/javascript/mastodon/components/status_list.js b/app/javascript/mastodon/components/status_list.js
index e417f9a2b..745e6422d 100644
--- a/app/javascript/mastodon/components/status_list.js
+++ b/app/javascript/mastodon/components/status_list.js
@@ -46,22 +46,28 @@ export default class StatusList extends ImmutablePureComponent {
handleMoveUp = (id, featured) => {
const elementIndex = this.getCurrentStatusIndex(id, featured) - 1;
- this._selectChild(elementIndex);
+ this._selectChild(elementIndex, true);
}
handleMoveDown = (id, featured) => {
const elementIndex = this.getCurrentStatusIndex(id, featured) + 1;
- this._selectChild(elementIndex);
+ this._selectChild(elementIndex, false);
}
handleLoadOlder = debounce(() => {
this.props.onLoadMore(this.props.statusIds.size > 0 ? this.props.statusIds.last() : undefined);
}, 300, { leading: true })
- _selectChild (index) {
- const element = this.node.node.querySelector(`article:nth-of-type(${index + 1}) .focusable`);
+ _selectChild (index, align_top) {
+ const container = this.node.node;
+ const element = container.querySelector(`article:nth-of-type(${index + 1}) .focusable`);
if (element) {
+ if (align_top && container.scrollTop > element.offsetTop) {
+ element.scrollIntoView(true);
+ } else if (!align_top && container.scrollTop + container.clientHeight < element.offsetTop + element.offsetHeight) {
+ element.scrollIntoView(false);
+ }
element.focus();
}
}
diff --git a/app/javascript/mastodon/features/direct_timeline/components/conversations_list.js b/app/javascript/mastodon/features/direct_timeline/components/conversations_list.js
index 635c03c1d..8867bbd73 100644
--- a/app/javascript/mastodon/features/direct_timeline/components/conversations_list.js
+++ b/app/javascript/mastodon/features/direct_timeline/components/conversations_list.js
@@ -20,18 +20,24 @@ export default class ConversationsList extends ImmutablePureComponent {
handleMoveUp = id => {
const elementIndex = this.getCurrentIndex(id) - 1;
- this._selectChild(elementIndex);
+ this._selectChild(elementIndex, true);
}
handleMoveDown = id => {
const elementIndex = this.getCurrentIndex(id) + 1;
- this._selectChild(elementIndex);
+ this._selectChild(elementIndex, false);
}
- _selectChild (index) {
- const element = this.node.node.querySelector(`article:nth-of-type(${index + 1}) .focusable`);
+ _selectChild (index, align_top) {
+ const container = this.node.node;
+ const element = container.querySelector(`article:nth-of-type(${index + 1}) .focusable`);
if (element) {
+ if (align_top && container.scrollTop > element.offsetTop) {
+ element.scrollIntoView(true);
+ } else if (!align_top && container.scrollTop + container.clientHeight < element.offsetTop + element.offsetHeight) {
+ element.scrollIntoView(false);
+ }
element.focus();
}
}
diff --git a/app/javascript/mastodon/features/notifications/index.js b/app/javascript/mastodon/features/notifications/index.js
index 9430b2050..006c45657 100644
--- a/app/javascript/mastodon/features/notifications/index.js
+++ b/app/javascript/mastodon/features/notifications/index.js
@@ -113,18 +113,24 @@ class Notifications extends React.PureComponent {
handleMoveUp = id => {
const elementIndex = this.props.notifications.findIndex(item => item !== null && item.get('id') === id) - 1;
- this._selectChild(elementIndex);
+ this._selectChild(elementIndex, true);
}
handleMoveDown = id => {
const elementIndex = this.props.notifications.findIndex(item => item !== null && item.get('id') === id) + 1;
- this._selectChild(elementIndex);
+ this._selectChild(elementIndex, false);
}
- _selectChild (index) {
- const element = this.column.node.querySelector(`article:nth-of-type(${index + 1}) .focusable`);
+ _selectChild (index, align_top) {
+ const container = this.column.node;
+ const element = container.querySelector(`article:nth-of-type(${index + 1}) .focusable`);
if (element) {
+ if (align_top && container.scrollTop > element.offsetTop) {
+ element.scrollIntoView(true);
+ } else if (!align_top && container.scrollTop + container.clientHeight < element.offsetTop + element.offsetHeight) {
+ element.scrollIntoView(false);
+ }
element.focus();
}
}
diff --git a/app/javascript/mastodon/features/status/index.js b/app/javascript/mastodon/features/status/index.js
index 567af6be9..6279bb468 100644
--- a/app/javascript/mastodon/features/status/index.js
+++ b/app/javascript/mastodon/features/status/index.js
@@ -316,15 +316,15 @@ class Status extends ImmutablePureComponent {
const { status, ancestorsIds, descendantsIds } = this.props;
if (id === status.get('id')) {
- this._selectChild(ancestorsIds.size - 1);
+ this._selectChild(ancestorsIds.size - 1, true);
} else {
let index = ancestorsIds.indexOf(id);
if (index === -1) {
index = descendantsIds.indexOf(id);
- this._selectChild(ancestorsIds.size + index);
+ this._selectChild(ancestorsIds.size + index, true);
} else {
- this._selectChild(index - 1);
+ this._selectChild(index - 1, true);
}
}
}
@@ -333,23 +333,29 @@ class Status extends ImmutablePureComponent {
const { status, ancestorsIds, descendantsIds } = this.props;
if (id === status.get('id')) {
- this._selectChild(ancestorsIds.size + 1);
+ this._selectChild(ancestorsIds.size + 1, false);
} else {
let index = ancestorsIds.indexOf(id);
if (index === -1) {
index = descendantsIds.indexOf(id);
- this._selectChild(ancestorsIds.size + index + 2);
+ this._selectChild(ancestorsIds.size + index + 2, false);
} else {
- this._selectChild(index + 1);
+ this._selectChild(index + 1, false);
}
}
}
- _selectChild (index) {
- const element = this.node.querySelectorAll('.focusable')[index];
+ _selectChild (index, align_top) {
+ const container = this.node;
+ const element = container.querySelectorAll('.focusable')[index];
if (element) {
+ if (align_top && container.scrollTop > element.offsetTop) {
+ element.scrollIntoView(true);
+ } else if (!align_top && container.scrollTop + container.clientHeight < element.offsetTop + element.offsetHeight) {
+ element.scrollIntoView(false);
+ }
element.focus();
}
}
diff --git a/app/javascript/mastodon/features/ui/index.js b/app/javascript/mastodon/features/ui/index.js
index 93e45678f..c14eba992 100644
--- a/app/javascript/mastodon/features/ui/index.js
+++ b/app/javascript/mastodon/features/ui/index.js
@@ -367,11 +367,16 @@ class UI extends React.PureComponent {
handleHotkeyFocusColumn = e => {
const index = (e.key * 1) + 1; // First child is drawer, skip that
const column = this.node.querySelector(`.column:nth-child(${index})`);
+ if (!column) return;
+ const container = column.querySelector('.scrollable');
- if (column) {
- const status = column.querySelector('.focusable');
+ if (container) {
+ const status = container.querySelector('.focusable');
if (status) {
+ if (container.scrollTop > status.offsetTop) {
+ status.scrollIntoView(true);
+ }
status.focus();
}
}
--
cgit
From 153b4ffc78e07f59793b2ae7bfcd7a2324a45621 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]"
Date: Fri, 3 May 2019 15:01:38 +0900
Subject: Bump fabrication from 2.20.1 to 2.20.2 (#10677)
Bumps [fabrication](https://github.com/paulelliott/fabrication) from 2.20.1 to 2.20.2.
- [Release notes](https://github.com/paulelliott/fabrication/releases)
- [Changelog](https://github.com/paulelliott/fabrication/blob/master/Changelog.markdown)
- [Commits](https://github.com/paulelliott/fabrication/compare/2.20.1...2.20.2)
Signed-off-by: dependabot[bot]
---
Gemfile.lock | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Gemfile.lock b/Gemfile.lock
index 7e7526b3b..61dd53c5e 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -207,7 +207,7 @@ GEM
et-orbi (1.1.6)
tzinfo
excon (0.62.0)
- fabrication (2.20.1)
+ fabrication (2.20.2)
faker (1.9.3)
i18n (>= 0.7)
faraday (0.15.0)
--
cgit
From 61e28b0ccc04f7a0c72cb663962eb152ab63a998 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]"
Date: Fri, 3 May 2019 17:29:53 +0900
Subject: Bump scss_lint from 0.57.1 to 0.58.0 (#10678)
Bumps [scss_lint](https://github.com/sds/scss-lint) from 0.57.1 to 0.58.0.
- [Release notes](https://github.com/sds/scss-lint/releases)
- [Changelog](https://github.com/sds/scss-lint/blob/master/CHANGELOG.md)
- [Commits](https://github.com/sds/scss-lint/compare/v0.57.1...v0.58.0)
Signed-off-by: dependabot[bot]
---
Gemfile | 2 +-
Gemfile.lock | 10 +++++-----
2 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/Gemfile b/Gemfile
index 8078e46a0..67df76be6 100644
--- a/Gemfile
+++ b/Gemfile
@@ -131,7 +131,7 @@ group :development do
gem 'rubocop', '~> 0.68', require: false
gem 'brakeman', '~> 4.5', require: false
gem 'bundler-audit', '~> 0.6', require: false
- gem 'scss_lint', '~> 0.57', require: false
+ gem 'scss_lint', '~> 0.58', require: false
gem 'capistrano', '~> 3.11'
gem 'capistrano-rails', '~> 1.4'
diff --git a/Gemfile.lock b/Gemfile.lock
index 61dd53c5e..09b0de614 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -471,8 +471,8 @@ GEM
rainbow (3.0.0)
rake (12.3.2)
rb-fsevent (0.10.3)
- rb-inotify (0.9.10)
- ffi (>= 0.5.0, < 2)
+ rb-inotify (0.10.0)
+ ffi (~> 1.0)
rdf (3.0.9)
hamster (~> 3.0)
link_header (~> 0.0, >= 0.0.8)
@@ -544,12 +544,12 @@ GEM
crass (~> 1.0.2)
nokogiri (>= 1.8.0)
nokogumbo (~> 2.0)
- sass (3.6.0)
+ sass (3.7.4)
sass-listen (~> 4.0.0)
sass-listen (4.0.0)
rb-fsevent (~> 0.9, >= 0.9.4)
rb-inotify (~> 0.9, >= 0.9.7)
- scss_lint (0.57.1)
+ scss_lint (0.58.0)
rake (>= 0.9, < 13)
sass (~> 3.5, >= 3.5.5)
sidekiq (5.2.7)
@@ -750,7 +750,7 @@ DEPENDENCIES
rspec-sidekiq (~> 3.0)
rubocop (~> 0.68)
sanitize (~> 5.0)
- scss_lint (~> 0.57)
+ scss_lint (~> 0.58)
sidekiq (~> 5.2)
sidekiq-bulk (~> 0.2.0)
sidekiq-scheduler (~> 3.0)
--
cgit
From ecbea2e3c6e49387b1eaefbbebd2013867414ca2 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]"
Date: Fri, 3 May 2019 16:16:11 +0200
Subject: Bump rack-attack from 5.4.2 to 6.0.0 (#10599)
* Bump rack-attack from 5.4.2 to 6.0.0
Bumps [rack-attack](https://github.com/kickstarter/rack-attack) from 5.4.2 to 6.0.0.
- [Release notes](https://github.com/kickstarter/rack-attack/releases)
- [Changelog](https://github.com/kickstarter/rack-attack/blob/master/CHANGELOG.md)
- [Commits](https://github.com/kickstarter/rack-attack/compare/v5.4.2...v6.0.0)
Signed-off-by: dependabot[bot]
* fix payload[:request]
---
Gemfile | 2 +-
Gemfile.lock | 4 ++--
config/initializers/rack_attack_logging.rb | 4 +++-
3 files changed, 6 insertions(+), 4 deletions(-)
diff --git a/Gemfile b/Gemfile
index 67df76be6..db00c24fb 100644
--- a/Gemfile
+++ b/Gemfile
@@ -66,7 +66,7 @@ gem 'ox', '~> 2.10'
gem 'posix-spawn', git: 'https://github.com/rtomayko/posix-spawn', ref: '58465d2e213991f8afb13b984854a49fcdcc980c'
gem 'pundit', '~> 2.0'
gem 'premailer-rails'
-gem 'rack-attack', '~> 5.4'
+gem 'rack-attack', '~> 6.0'
gem 'rack-cors', '~> 1.0', require: 'rack/cors'
gem 'rails-i18n', '~> 5.1'
gem 'rails-settings-cached', '~> 0.6'
diff --git a/Gemfile.lock b/Gemfile.lock
index 09b0de614..7ab907f6d 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -426,7 +426,7 @@ GEM
activesupport (>= 3.0.0)
raabro (1.1.6)
rack (2.0.7)
- rack-attack (5.4.2)
+ rack-attack (6.0.0)
rack (>= 1.0, < 3)
rack-cors (1.0.3)
rack-protection (2.0.5)
@@ -735,7 +735,7 @@ DEPENDENCIES
pry-rails (~> 0.3)
puma (~> 3.12)
pundit (~> 2.0)
- rack-attack (~> 5.4)
+ rack-attack (~> 6.0)
rack-cors (~> 1.0)
rails (~> 5.2.3)
rails-controller-testing (~> 1.0)
diff --git a/config/initializers/rack_attack_logging.rb b/config/initializers/rack_attack_logging.rb
index 2ddbfb99c..c30bd8a64 100644
--- a/config/initializers/rack_attack_logging.rb
+++ b/config/initializers/rack_attack_logging.rb
@@ -1,4 +1,6 @@
-ActiveSupport::Notifications.subscribe('rack.attack') do |_name, _start, _finish, _request_id, req|
+ActiveSupport::Notifications.subscribe(/rack_attack/) do |_name, _start, _finish, _request_id, payload|
+ req = payload[:request]
+
next unless [:throttle, :blacklist].include? req.env['rack.attack.match_type']
Rails.logger.info("Rate limit hit (#{req.env['rack.attack.match_type']}): #{req.ip} #{req.request_method} #{req.fullpath}")
end
--
cgit
From eb63217210b0ab85ff1fcca9506d5e7931382a56 Mon Sep 17 00:00:00 2001
From: Eugen Rochko
Date: Fri, 3 May 2019 16:16:30 +0200
Subject: Add button to view context to media modal (#10676)
* Add "view context" button to media modal when opened from gallery
* Add "view context" button to video modal
Allow closing the video modal by navigating back in the browser,
just like the media modal
---
.../mastodon/features/account_gallery/index.js | 4 +-
.../mastodon/features/ui/components/media_modal.js | 31 ++++++++++++---
.../mastodon/features/ui/components/video_modal.js | 44 +++++++++++++++++++++-
app/javascript/mastodon/features/video/index.js | 10 +++--
app/javascript/styles/mastodon/components.scss | 42 +++++++++++++++++++++
5 files changed, 119 insertions(+), 12 deletions(-)
diff --git a/app/javascript/mastodon/features/account_gallery/index.js b/app/javascript/mastodon/features/account_gallery/index.js
index 8766473fe..5d6a53e18 100644
--- a/app/javascript/mastodon/features/account_gallery/index.js
+++ b/app/javascript/mastodon/features/account_gallery/index.js
@@ -100,12 +100,12 @@ class AccountGallery extends ImmutablePureComponent {
handleOpenMedia = attachment => {
if (attachment.get('type') === 'video') {
- this.props.dispatch(openModal('VIDEO', { media: attachment }));
+ this.props.dispatch(openModal('VIDEO', { media: attachment, status: attachment.get('status') }));
} else {
const media = attachment.getIn(['status', 'media_attachments']);
const index = media.findIndex(x => x.get('id') === attachment.get('id'));
- this.props.dispatch(openModal('MEDIA', { media, index }));
+ this.props.dispatch(openModal('MEDIA', { media, index, status: attachment.get('status') }));
}
}
diff --git a/app/javascript/mastodon/features/ui/components/media_modal.js b/app/javascript/mastodon/features/ui/components/media_modal.js
index 848cb20b3..da2ac5f26 100644
--- a/app/javascript/mastodon/features/ui/components/media_modal.js
+++ b/app/javascript/mastodon/features/ui/components/media_modal.js
@@ -2,11 +2,11 @@ import React from 'react';
import ReactSwipeableViews from 'react-swipeable-views';
import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types';
-import Video from '../../video';
-import ExtendedVideoPlayer from '../../../components/extended_video_player';
+import Video from 'mastodon/features/video';
+import ExtendedVideoPlayer from 'mastodon/components/extended_video_player';
import classNames from 'classnames';
-import { defineMessages, injectIntl } from 'react-intl';
-import IconButton from '../../../components/icon_button';
+import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
+import IconButton from 'mastodon/components/icon_button';
import ImmutablePureComponent from 'react-immutable-pure-component';
import ImageLoader from './image_loader';
import Icon from 'mastodon/components/icon';
@@ -24,6 +24,7 @@ class MediaModal extends ImmutablePureComponent {
static propTypes = {
media: ImmutablePropTypes.list.isRequired,
+ status: ImmutablePropTypes.map,
index: PropTypes.number.isRequired,
onClose: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
@@ -72,9 +73,12 @@ class MediaModal extends ImmutablePureComponent {
componentDidMount () {
window.addEventListener('keydown', this.handleKeyDown, false);
+
if (this.context.router) {
const history = this.context.router.history;
+
history.push(history.location.pathname, previewState);
+
this.unlistenHistory = history.listen(() => {
this.props.onClose();
});
@@ -83,6 +87,7 @@ class MediaModal extends ImmutablePureComponent {
componentWillUnmount () {
window.removeEventListener('keydown', this.handleKeyDown);
+
if (this.context.router) {
this.unlistenHistory();
@@ -102,8 +107,15 @@ class MediaModal extends ImmutablePureComponent {
}));
};
+ handleStatusClick = e => {
+ if (e.button === 0 && !(e.ctrlKey || e.metaKey)) {
+ e.preventDefault();
+ this.context.router.history.push(`/statuses/${this.props.status.get('id')}`);
+ }
+ }
+
render () {
- const { media, intl, onClose } = this.props;
+ const { media, status, intl, onClose } = this.props;
const { navigationHidden } = this.state;
const index = this.getIndex();
@@ -207,10 +219,19 @@ class MediaModal extends ImmutablePureComponent {
{content}
+
+
{leftNav}
{rightNav}
+
+ {status && (
+
+ )}
+
diff --git a/app/javascript/mastodon/features/ui/components/video_modal.js b/app/javascript/mastodon/features/ui/components/video_modal.js
index 52457a630..213d31316 100644
--- a/app/javascript/mastodon/features/ui/components/video_modal.js
+++ b/app/javascript/mastodon/features/ui/components/video_modal.js
@@ -1,19 +1,58 @@
import React from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types';
-import Video from '../../video';
+import Video from 'mastodon/features/video';
import ImmutablePureComponent from 'react-immutable-pure-component';
+import { FormattedMessage } from 'react-intl';
+
+export const previewState = 'previewVideoModal';
export default class VideoModal extends ImmutablePureComponent {
static propTypes = {
media: ImmutablePropTypes.map.isRequired,
+ status: ImmutablePropTypes.map,
time: PropTypes.number,
onClose: PropTypes.func.isRequired,
};
+ static contextTypes = {
+ router: PropTypes.object,
+ };
+
+ componentDidMount () {
+ if (this.context.router) {
+ const history = this.context.router.history;
+
+ history.push(history.location.pathname, previewState);
+
+ this.unlistenHistory = history.listen(() => {
+ this.props.onClose();
+ });
+ }
+ }
+
+ componentWillUnmount () {
+ if (this.context.router) {
+ this.unlistenHistory();
+
+ if (this.context.router.history.location.state === previewState) {
+ this.context.router.history.goBack();
+ }
+ }
+ }
+
+ handleStatusClick = e => {
+ if (e.button === 0 && !(e.ctrlKey || e.metaKey)) {
+ e.preventDefault();
+ this.context.router.history.push(`/statuses/${this.props.status.get('id')}`);
+ }
+ }
+
render () {
- const { media, time, onClose } = this.props;
+ const { media, status, time, onClose } = this.props;
+
+ const link = status &&
;
return (
@@ -24,6 +63,7 @@ export default class VideoModal extends ImmutablePureComponent {
src={media.get('url')}
startTime={time}
onCloseVideo={onClose}
+ link={link}
detailed
alt={media.get('description')}
/>
diff --git a/app/javascript/mastodon/features/video/index.js b/app/javascript/mastodon/features/video/index.js
index 8ae26eba8..00a63a3d9 100644
--- a/app/javascript/mastodon/features/video/index.js
+++ b/app/javascript/mastodon/features/video/index.js
@@ -104,6 +104,7 @@ class Video extends React.PureComponent {
cacheWidth: PropTypes.func,
intl: PropTypes.object.isRequired,
blurhash: PropTypes.string,
+ link: PropTypes.node,
};
state = {
@@ -361,7 +362,7 @@ class Video extends React.PureComponent {
}
render () {
- const { preview, src, inline, startTime, onOpenVideo, onCloseVideo, intl, alt, detailed, sensitive } = this.props;
+ const { preview, src, inline, startTime, onOpenVideo, onCloseVideo, intl, alt, detailed, sensitive, link } = this.props;
const { containerWidth, currentTime, duration, volume, buffer, dragging, paused, fullscreen, hovered, muted, revealed } = this.state;
const progress = (currentTime / duration) * 100;
@@ -453,6 +454,7 @@ class Video extends React.PureComponent {
+
- {(detailed || fullscreen) &&
+ {(detailed || fullscreen) && (
{formatTime(currentTime)}
/
{formatTime(duration)}
- }
+ )}
+
+ {link &&
{link} }
diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss
index 8aebe3432..c1466aa88 100644
--- a/app/javascript/styles/mastodon/components.scss
+++ b/app/javascript/styles/mastodon/components.scss
@@ -3766,6 +3766,31 @@ a.status-card.compact:hover {
pointer-events: none;
}
+.media-modal__meta {
+ text-align: center;
+ position: absolute;
+ left: 0;
+ bottom: 20px;
+ width: 100%;
+ pointer-events: none;
+
+ &--shifted {
+ bottom: 62px;
+ }
+
+ a {
+ text-decoration: none;
+ font-weight: 500;
+ color: $ui-secondary-color;
+
+ &:hover,
+ &:focus,
+ &:active {
+ text-decoration: underline;
+ }
+ }
+}
+
.media-modal__page-dot {
display: inline-block;
}
@@ -4676,6 +4701,23 @@ a.status-card.compact:hover {
}
}
+ &__link {
+ padding: 2px 10px;
+
+ a {
+ text-decoration: none;
+ font-size: 14px;
+ font-weight: 500;
+ color: $white;
+
+ &:hover,
+ &:active,
+ &:focus {
+ text-decoration: underline;
+ }
+ }
+ }
+
&__seek {
cursor: pointer;
height: 24px;
--
cgit
From 7617f78359e3767b0c0311928a786d7007687d17 Mon Sep 17 00:00:00 2001
From: Thibaut Girka
Date: Fri, 3 May 2019 18:54:06 +0200
Subject: Fix crash in onboarding modal
Fixes #1027
---
.../flavours/glitch/features/compose/components/compose_form.js | 4 ++--
.../flavours/glitch/features/ui/components/onboarding_modal.js | 4 +++-
2 files changed, 5 insertions(+), 3 deletions(-)
diff --git a/app/javascript/flavours/glitch/features/compose/components/compose_form.js b/app/javascript/flavours/glitch/features/compose/components/compose_form.js
index bd6d5b1fa..e8f000b1e 100644
--- a/app/javascript/flavours/glitch/features/compose/components/compose_form.js
+++ b/app/javascript/flavours/glitch/features/compose/components/compose_form.js
@@ -321,8 +321,8 @@ class ComposeForm extends ImmutablePureComponent {
onSuggestionsClearRequested={onClearSuggestions}
onSuggestionSelected={this.onSpoilerSuggestionSelected}
searchTokens={[':']}
- id='glitch.composer.spoiler.input'
- className='spoiler-input__input'
+ id='glitch.composer.spoiler.input'
+ className='spoiler-input__input'
/>
diff --git a/app/javascript/flavours/glitch/features/ui/components/onboarding_modal.js b/app/javascript/flavours/glitch/features/ui/components/onboarding_modal.js
index f13e2e645..3fda97afc 100644
--- a/app/javascript/flavours/glitch/features/ui/components/onboarding_modal.js
+++ b/app/javascript/flavours/glitch/features/ui/components/onboarding_modal.js
@@ -6,7 +6,7 @@ import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import ReactSwipeableViews from 'react-swipeable-views';
import classNames from 'classnames';
import Permalink from 'flavours/glitch/components/permalink';
-import { ComposeForm } from 'flavours/glitch/features/compose/components/compose_form';
+import ComposeForm from 'flavours/glitch/features/compose/components/compose_form';
import DrawerAccount from 'flavours/glitch/features/compose/components/navigation_bar';
import Search from 'flavours/glitch/features/compose/components/search';
import ColumnHeader from './column_header';
@@ -48,6 +48,8 @@ const PageTwo = ({ intl, myAccount }) => (
--
cgit
From 011b032300657ccca4a42866749afc6ec2588ecc Mon Sep 17 00:00:00 2001
From: ThibG
Date: Fri, 3 May 2019 20:36:36 +0200
Subject: Provide a link to existing domain block when trying to block an
already-blocked domain (#10663)
* When trying to block an already-blocked domain, provide a link to the block
* Fix styling for links in flash messages
* Allow blocks to be upgraded but not downgraded
---
app/controllers/admin/domain_blocks_controller.rb | 22 +++++++++++----
app/javascript/styles/mastodon/forms.scss | 11 ++++++++
app/models/domain_block.rb | 7 +++++
config/locales/en.yml | 1 +
.../admin/domain_blocks_controller_spec.rb | 13 ++++++++-
spec/models/domain_block_spec.rb | 31 ++++++++++++++++++++++
6 files changed, 79 insertions(+), 6 deletions(-)
diff --git a/app/controllers/admin/domain_blocks_controller.rb b/app/controllers/admin/domain_blocks_controller.rb
index 5f307ddee..dd3f83389 100644
--- a/app/controllers/admin/domain_blocks_controller.rb
+++ b/app/controllers/admin/domain_blocks_controller.rb
@@ -13,13 +13,25 @@ module Admin
authorize :domain_block, :create?
@domain_block = DomainBlock.new(resource_params)
+ existing_domain_block = resource_params[:domain].present? ? DomainBlock.find_by(domain: resource_params[:domain]) : nil
- if @domain_block.save
- DomainBlockWorker.perform_async(@domain_block.id)
- log_action :create, @domain_block
- redirect_to admin_instances_path(limited: '1'), notice: I18n.t('admin.domain_blocks.created_msg')
- else
+ if existing_domain_block.present? && !@domain_block.stricter_than?(existing_domain_block)
+ @domain_block.save
+ flash[:alert] = I18n.t('admin.domain_blocks.existing_domain_block_html', name: existing_domain_block.domain, unblock_url: admin_domain_block_path(existing_domain_block)).html_safe # rubocop:disable Rails/OutputSafety
+ @domain_block.errors[:domain].clear
render :new
+ else
+ if existing_domain_block.present?
+ @domain_block = existing_domain_block
+ @domain_block.update(resource_params)
+ end
+ if @domain_block.save
+ DomainBlockWorker.perform_async(@domain_block.id)
+ log_action :create, @domain_block
+ redirect_to admin_instances_path(limited: '1'), notice: I18n.t('admin.domain_blocks.created_msg')
+ else
+ render :new
+ end
end
end
diff --git a/app/javascript/styles/mastodon/forms.scss b/app/javascript/styles/mastodon/forms.scss
index 91888d305..2b8d7a682 100644
--- a/app/javascript/styles/mastodon/forms.scss
+++ b/app/javascript/styles/mastodon/forms.scss
@@ -533,6 +533,17 @@ code {
color: $error-value-color;
}
+ a {
+ display: inline-block;
+ color: $darker-text-color;
+ text-decoration: none;
+
+ &:hover {
+ color: $primary-text-color;
+ text-decoration: underline;
+ }
+ }
+
p {
margin-bottom: 15px;
}
diff --git a/app/models/domain_block.rb b/app/models/domain_block.rb
index 069cda367..0b12617c6 100644
--- a/app/models/domain_block.rb
+++ b/app/models/domain_block.rb
@@ -29,4 +29,11 @@ class DomainBlock < ApplicationRecord
def self.blocked?(domain)
where(domain: domain, severity: :suspend).exists?
end
+
+ def stricter_than?(other_block)
+ return true if suspend?
+ return false if other_block.suspend? && (silence? || noop?)
+ return false if other_block.silence? && noop?
+ (reject_media || !other_block.reject_media) && (reject_reports || !other_block.reject_reports)
+ end
end
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 405b0eda5..6d59411a5 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -269,6 +269,7 @@ en:
created_msg: Domain block is now being processed
destroyed_msg: Domain block has been undone
domain: Domain
+ existing_domain_block_html: You have already imposed stricter limits on %{name}, you need to unblock it first.
new:
create: Create block
hint: The domain block will not prevent creation of account entries in the database, but will retroactively and automatically apply specific moderation methods on those accounts.
diff --git a/spec/controllers/admin/domain_blocks_controller_spec.rb b/spec/controllers/admin/domain_blocks_controller_spec.rb
index 129bf8883..2a8675c21 100644
--- a/spec/controllers/admin/domain_blocks_controller_spec.rb
+++ b/spec/controllers/admin/domain_blocks_controller_spec.rb
@@ -37,7 +37,7 @@ RSpec.describe Admin::DomainBlocksController, type: :controller do
end
it 'renders new when failed to save' do
- Fabricate(:domain_block, domain: 'example.com')
+ Fabricate(:domain_block, domain: 'example.com', severity: 'suspend')
allow(DomainBlockWorker).to receive(:perform_async).and_return(true)
post :create, params: { domain_block: { domain: 'example.com', severity: 'silence' } }
@@ -45,6 +45,17 @@ RSpec.describe Admin::DomainBlocksController, type: :controller do
expect(DomainBlockWorker).not_to have_received(:perform_async)
expect(response).to render_template :new
end
+
+ it 'allows upgrading a block' do
+ Fabricate(:domain_block, domain: 'example.com', severity: 'silence')
+ allow(DomainBlockWorker).to receive(:perform_async).and_return(true)
+
+ post :create, params: { domain_block: { domain: 'example.com', severity: 'silence', reject_media: true, reject_reports: true } }
+
+ expect(DomainBlockWorker).to have_received(:perform_async)
+ expect(flash[:notice]).to eq I18n.t('admin.domain_blocks.created_msg')
+ expect(response).to redirect_to(admin_instances_path(limited: '1'))
+ end
end
describe 'DELETE #destroy' do
diff --git a/spec/models/domain_block_spec.rb b/spec/models/domain_block_spec.rb
index 89cadccfe..0035fd0ff 100644
--- a/spec/models/domain_block_spec.rb
+++ b/spec/models/domain_block_spec.rb
@@ -36,4 +36,35 @@ RSpec.describe DomainBlock, type: :model do
expect(DomainBlock.blocked?('domain')).to eq false
end
end
+
+ describe 'stricter_than?' do
+ it 'returns true if the new block has suspend severity while the old has lower severity' do
+ suspend = DomainBlock.new(domain: 'domain', severity: :suspend)
+ silence = DomainBlock.new(domain: 'domain', severity: :silence)
+ noop = DomainBlock.new(domain: 'domain', severity: :noop)
+ expect(suspend.stricter_than?(silence)).to be true
+ expect(suspend.stricter_than?(noop)).to be true
+ end
+
+ it 'returns false if the new block has lower severity than the old one' do
+ suspend = DomainBlock.new(domain: 'domain', severity: :suspend)
+ silence = DomainBlock.new(domain: 'domain', severity: :silence)
+ noop = DomainBlock.new(domain: 'domain', severity: :noop)
+ expect(silence.stricter_than?(suspend)).to be false
+ expect(noop.stricter_than?(suspend)).to be false
+ expect(noop.stricter_than?(silence)).to be false
+ end
+
+ it 'returns false if the new block does is less strict regarding reports' do
+ older = DomainBlock.new(domain: 'domain', severity: :silence, reject_reports: true)
+ newer = DomainBlock.new(domain: 'domain', severity: :silence, reject_reports: false)
+ expect(newer.stricter_than?(older)).to be false
+ end
+
+ it 'returns false if the new block does is less strict regarding media' do
+ older = DomainBlock.new(domain: 'domain', severity: :silence, reject_media: true)
+ newer = DomainBlock.new(domain: 'domain', severity: :silence, reject_media: false)
+ expect(newer.stricter_than?(older)).to be false
+ end
+ end
end
--
cgit
From 91634947f88fb3004b5e853598f02fbe39a55768 Mon Sep 17 00:00:00 2001
From: ThibG
Date: Fri, 3 May 2019 20:39:19 +0200
Subject: Explicitly disable storage of REST API results (#10655)
Fixes #10652
---
app/controllers/api/base_controller.rb | 6 ++++++
app/controllers/api/v1/custom_emojis_controller.rb | 2 ++
app/controllers/api/v1/instances/activity_controller.rb | 1 +
app/controllers/api/v1/instances/peers_controller.rb | 1 +
app/controllers/api/v1/instances_controller.rb | 1 +
5 files changed, 11 insertions(+)
diff --git a/app/controllers/api/base_controller.rb b/app/controllers/api/base_controller.rb
index 3a92ee4e4..eca558f42 100644
--- a/app/controllers/api/base_controller.rb
+++ b/app/controllers/api/base_controller.rb
@@ -9,6 +9,8 @@ class Api::BaseController < ApplicationController
skip_before_action :store_current_location
skip_before_action :check_user_permissions
+ before_action :set_cache_headers
+
protect_from_forgery with: :null_session
rescue_from ActiveRecord::RecordInvalid, Mastodon::ValidationError do |e|
@@ -88,4 +90,8 @@ class Api::BaseController < ApplicationController
def authorize_if_got_token!(*scopes)
doorkeeper_authorize!(*scopes) if doorkeeper_token
end
+
+ def set_cache_headers
+ response.headers['Cache-Control'] = 'no-cache, no-store, max-age=0, must-revalidate'
+ end
end
diff --git a/app/controllers/api/v1/custom_emojis_controller.rb b/app/controllers/api/v1/custom_emojis_controller.rb
index 7bac27da4..1bb19a09d 100644
--- a/app/controllers/api/v1/custom_emojis_controller.rb
+++ b/app/controllers/api/v1/custom_emojis_controller.rb
@@ -3,6 +3,8 @@
class Api::V1::CustomEmojisController < Api::BaseController
respond_to :json
+ skip_before_action :set_cache_headers
+
def index
render_cached_json('api:v1:custom_emojis', expires_in: 1.minute) do
ActiveModelSerializers::SerializableResource.new(CustomEmoji.local.where(disabled: false), each_serializer: REST::CustomEmojiSerializer)
diff --git a/app/controllers/api/v1/instances/activity_controller.rb b/app/controllers/api/v1/instances/activity_controller.rb
index e14e0aee8..09edfe365 100644
--- a/app/controllers/api/v1/instances/activity_controller.rb
+++ b/app/controllers/api/v1/instances/activity_controller.rb
@@ -2,6 +2,7 @@
class Api::V1::Instances::ActivityController < Api::BaseController
before_action :require_enabled_api!
+ skip_before_action :set_cache_headers
respond_to :json
diff --git a/app/controllers/api/v1/instances/peers_controller.rb b/app/controllers/api/v1/instances/peers_controller.rb
index 2070c487d..a8891d126 100644
--- a/app/controllers/api/v1/instances/peers_controller.rb
+++ b/app/controllers/api/v1/instances/peers_controller.rb
@@ -2,6 +2,7 @@
class Api::V1::Instances::PeersController < Api::BaseController
before_action :require_enabled_api!
+ skip_before_action :set_cache_headers
respond_to :json
diff --git a/app/controllers/api/v1/instances_controller.rb b/app/controllers/api/v1/instances_controller.rb
index 5686e8d7c..8c83a1801 100644
--- a/app/controllers/api/v1/instances_controller.rb
+++ b/app/controllers/api/v1/instances_controller.rb
@@ -2,6 +2,7 @@
class Api::V1::InstancesController < Api::BaseController
respond_to :json
+ skip_before_action :set_cache_headers
def show
render_cached_json('api:v1:instances', expires_in: 5.minutes) do
--
cgit
From 63b1388fefff9414c2d0f9883f2d33f7c73284c6 Mon Sep 17 00:00:00 2001
From: Eugen Rochko
Date: Fri, 3 May 2019 20:44:20 +0200
Subject: Change font weight of sensitive button to 500 (#10682)
---
app/javascript/styles/mastodon/components.scss | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss
index c1466aa88..2326dee38 100644
--- a/app/javascript/styles/mastodon/components.scss
+++ b/app/javascript/styles/mastodon/components.scss
@@ -267,6 +267,11 @@
&__sensitive-button {
padding: 10px;
padding-top: 0;
+
+ .icon-button {
+ font-size: 14px;
+ font-weight: 500;
+ }
}
.compose-form__warning {
--
cgit
From d77ee3f276dc42fb0219ab1b02162f2f1b90257b Mon Sep 17 00:00:00 2001
From: Eugen Rochko
Date: Fri, 3 May 2019 20:49:27 +0200
Subject: Fix accounts created through tootctl not being always pre-approved
(#10684)
Add `--approve` option to `tootctl accounts modify`
---
lib/mastodon/accounts_cli.rb | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/lib/mastodon/accounts_cli.rb b/lib/mastodon/accounts_cli.rb
index 9dc84f1b5..3131647f3 100644
--- a/lib/mastodon/accounts_cli.rb
+++ b/lib/mastodon/accounts_cli.rb
@@ -73,7 +73,7 @@ module Mastodon
def create(username)
account = Account.new(username: username)
password = SecureRandom.hex
- user = User.new(email: options[:email], password: password, agreement: true, admin: options[:role] == 'admin', moderator: options[:role] == 'moderator', confirmed_at: options[:confirmed] ? Time.now.utc : nil)
+ user = User.new(email: options[:email], password: password, agreement: true, approved: true, admin: options[:role] == 'admin', moderator: options[:role] == 'moderator', confirmed_at: options[:confirmed] ? Time.now.utc : nil)
if options[:reattach]
account = Account.find_local(username) || Account.new(username: username)
@@ -115,6 +115,7 @@ module Mastodon
option :enable, type: :boolean
option :disable, type: :boolean
option :disable_2fa, type: :boolean
+ option :approve, type: :boolean
desc 'modify USERNAME', 'Modify a user'
long_desc <<-LONG_DESC
Modify a user account.
@@ -128,6 +129,9 @@ module Mastodon
With the --disable option, lock the user out of their account. The
--enable option is the opposite.
+ With the --approve option, the account will be approved, if it was
+ previously not due to not having open registrations.
+
With the --disable-2fa option, the two-factor authentication
requirement for the user can be removed.
LONG_DESC
@@ -147,6 +151,7 @@ module Mastodon
user.email = options[:email] if options[:email]
user.disabled = false if options[:enable]
user.disabled = true if options[:disable]
+ user.approved = true if options[:approve]
user.otp_required_for_login = false if options[:disable_2fa]
user.confirm if options[:confirm]
--
cgit
From 9f25ab9792532fff9a0b067a24cfd0c20b99644b Mon Sep 17 00:00:00 2001
From: Thibaut Girka
Date: Fri, 3 May 2019 20:25:57 +0200
Subject: Fix polls icon not showing in CW button in detailed statuses
---
.../flavours/glitch/features/status/components/detailed_status.js | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/app/javascript/flavours/glitch/features/status/components/detailed_status.js b/app/javascript/flavours/glitch/features/status/components/detailed_status.js
index 69b646427..8b35de0d4 100644
--- a/app/javascript/flavours/glitch/features/status/components/detailed_status.js
+++ b/app/javascript/flavours/glitch/features/status/components/detailed_status.js
@@ -125,6 +125,7 @@ export default class DetailedStatus extends ImmutablePureComponent {
if (status.get('poll')) {
media = ;
+ mediaIcon = 'tasks';
} else if (status.get('media_attachments').size > 0) {
if (status.get('media_attachments').some(item => item.get('type') === 'unknown')) {
media = ;
@@ -161,7 +162,10 @@ export default class DetailedStatus extends ImmutablePureComponent {
);
mediaIcon = 'picture-o';
}
- } else media = ;
+ } else {
+ media = ;
+ mediaIcon = 'link';
+ }
if (status.get('application')) {
applicationLink = · {status.getIn(['application', 'name'])} ;
--
cgit
From 7cb369d4c66c4381c856a2714b4117d6204cd4bb Mon Sep 17 00:00:00 2001
From: Eugen Rochko
Date: Fri, 3 May 2019 23:44:44 +0200
Subject: Change e-mail whitelist/blacklist to not be checked when invited
(#10683)
* Change e-mail whitelist/blacklist to not be checked when invited
And only when creating an account, not when updating it later
Fix #10648
* Fix test
---
app/models/user.rb | 2 +-
app/validators/blacklisted_email_validator.rb | 5 ++++-
spec/validators/blacklisted_email_validator_spec.rb | 1 +
3 files changed, 6 insertions(+), 2 deletions(-)
diff --git a/app/models/user.rb b/app/models/user.rb
index 432078651..bce28aa5f 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -78,7 +78,7 @@ class User < ApplicationRecord
accepts_nested_attributes_for :invite_request, reject_if: ->(attributes) { attributes['text'].blank? }
validates :locale, inclusion: I18n.available_locales.map(&:to_s), if: :locale?
- validates_with BlacklistedEmailValidator, if: :email_changed?
+ validates_with BlacklistedEmailValidator, on: :create
validates_with EmailMxValidator, if: :validate_email_dns?
validates :agreement, acceptance: { allow_nil: false, accept: [true, 'true', '1'] }, on: :create
diff --git a/app/validators/blacklisted_email_validator.rb b/app/validators/blacklisted_email_validator.rb
index a2061fdd3..a288c20ef 100644
--- a/app/validators/blacklisted_email_validator.rb
+++ b/app/validators/blacklisted_email_validator.rb
@@ -2,7 +2,10 @@
class BlacklistedEmailValidator < ActiveModel::Validator
def validate(user)
+ return if user.invited?
+
@email = user.email
+
user.errors.add(:email, I18n.t('users.invalid_email')) if blocked_email?
end
@@ -13,7 +16,7 @@ class BlacklistedEmailValidator < ActiveModel::Validator
end
def on_blacklist?
- return true if EmailDomainBlock.block?(@email)
+ return true if EmailDomainBlock.block?(@email)
return false if Rails.configuration.x.email_domains_blacklist.blank?
domains = Rails.configuration.x.email_domains_blacklist.gsub('.', '\.')
diff --git a/spec/validators/blacklisted_email_validator_spec.rb b/spec/validators/blacklisted_email_validator_spec.rb
index d2e442f4a..84b0107dd 100644
--- a/spec/validators/blacklisted_email_validator_spec.rb
+++ b/spec/validators/blacklisted_email_validator_spec.rb
@@ -8,6 +8,7 @@ RSpec.describe BlacklistedEmailValidator, type: :validator do
let(:errors) { double(add: nil) }
before do
+ allow(user).to receive(:invited?) { false }
allow_any_instance_of(described_class).to receive(:blocked_email?) { blocked_email }
described_class.new.validate(user)
end
--
cgit
From b85f216cbcb0a99aac6f7f11e6254452a8f6c3dc Mon Sep 17 00:00:00 2001
From: ThibG
Date: Fri, 3 May 2019 23:45:37 +0200
Subject: Do not retry processing ActivityPub jobs raising validation errors
(#10614)
* Do not retry processing ActivityPub jobs raising validation errors
Jobs yielding validation errors most probably won't ever be accepted,
so it makes sense not to clutter the queues with retries.
* Lower RecordInvalid error reporting to debug log level
* Remove trailing whitespace
---
app/workers/activitypub/processing_worker.rb | 2 ++
1 file changed, 2 insertions(+)
diff --git a/app/workers/activitypub/processing_worker.rb b/app/workers/activitypub/processing_worker.rb
index a3abe72cf..05139f616 100644
--- a/app/workers/activitypub/processing_worker.rb
+++ b/app/workers/activitypub/processing_worker.rb
@@ -7,5 +7,7 @@ class ActivityPub::ProcessingWorker
def perform(account_id, body, delivered_to_account_id = nil)
ActivityPub::ProcessCollectionService.new.call(body, Account.find(account_id), override_timestamps: true, delivered_to_account_id: delivered_to_account_id, delivery: true)
+ rescue ActiveRecord::RecordInvalid => e
+ Rails.logger.debug "Error processing incoming ActivityPub object: #{e}"
end
end
--
cgit
From 5f9f610a23c8fac1133250a95de567e68cad183c Mon Sep 17 00:00:00 2001
From: Eugen Rochko
Date: Sat, 4 May 2019 00:31:06 +0200
Subject: Bump version to 2.8.1 (#10687)
---
CHANGELOG.md | 39 +++++++++++++++++++++++++++++++++++++++
lib/mastodon/version.rb | 2 +-
2 files changed, 40 insertions(+), 1 deletion(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 17626e027..9639aed08 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,45 @@ Changelog
All notable changes to this project will be documented in this file.
+## [2.8.1] - 2019-05-04
+### Added
+
+- Add link to existing domain block when trying to block an already-blocked domain ([ThibG](https://github.com/tootsuite/mastodon/pull/10663))
+- Add button to view context to media modal when opened from account gallery in web UI ([Gargron](https://github.com/tootsuite/mastodon/pull/10676))
+- Add ability to create multiple-choice polls in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/10603))
+- Add `GITHUB_REPOSITORY` and `SOURCE_BASE_URL` environment variables ([rosylilly](https://github.com/tootsuite/mastodon/pull/10600))
+- Add `/interact/` paths to `robots.txt` ([ThibG](https://github.com/tootsuite/mastodon/pull/10666))
+- Add `blurhash` to the Attachment entity in the REST API ([Gargron](https://github.com/tootsuite/mastodon/pull/10630))
+
+### Changed
+
+- Change hidden media to be shown as a blurhash-based colorful gradient instead of a black box in web UI ([Gargron](https://github.com/tootsuite/mastodon/pull/10630))
+- Change rejected media to be shown as a blurhash-based gradient instead of a list of filenames in web UI ([Gargron](https://github.com/tootsuite/mastodon/pull/10630))
+- Change e-mail whitelist/blacklist to not be checked when invited ([Gargron](https://github.com/tootsuite/mastodon/pull/10683))
+- Change cache header of REST API results to no-cache ([ThibG](https://github.com/tootsuite/mastodon/pull/10655))
+- Change the "mark media as sensitive" button to be more obvious in web UI ([Gargron](https://github.com/tootsuite/mastodon/pull/10673), [Gargron](https://github.com/tootsuite/mastodon/pull/10682))
+- Change account gallery in web UI to display 3 columns, open media modal ([Gargron](https://github.com/tootsuite/mastodon/pull/10667), [Gargron](https://github.com/tootsuite/mastodon/pull/10674))
+
+### Fixed
+
+- Fix LDAP/PAM/SAML/CAS users not being pre-approved ([Gargron](https://github.com/tootsuite/mastodon/pull/10621))
+- Fix accounts created through tootctl not being always pre-approved ([Gargron](https://github.com/tootsuite/mastodon/pull/10684))
+- Fix Sidekiq retrying ActivityPub processing jobs that fail validation ([ThibG](https://github.com/tootsuite/mastodon/pull/10614))
+- Fix toots not being scrolled into view sometimes through keyboard selection ([ThibG](https://github.com/tootsuite/mastodon/pull/10593))
+- Fix expired invite links being usable to bypass approval mode ([ThibG](https://github.com/tootsuite/mastodon/pull/10657))
+- Fix not being able to save e-mail preference for new pending accounts ([Gargron](https://github.com/tootsuite/mastodon/pull/10622))
+- Fix upload progressbar when image resizing is involved ([ThibG](https://github.com/tootsuite/mastodon/pull/10632))
+- Fix block action not automatically cancelling pending follow request ([ThibG](https://github.com/tootsuite/mastodon/pull/10633))
+- Fix stoplight logging to stderr separate from Rails logger ([Gargron](https://github.com/tootsuite/mastodon/pull/10624))
+- Fix sign up button not saying sign up when invite is used ([Gargron](https://github.com/tootsuite/mastodon/pull/10623))
+- Fix health checks in Docker Compose configuration ([fabianonline](https://github.com/tootsuite/mastodon/pull/10553))
+- Fix modal items not being scrollable on touch devices ([kedamaDQ](https://github.com/tootsuite/mastodon/pull/10605))
+- Fix Keybase configuration using wrong domain when a web domain is used ([BenLubar](https://github.com/tootsuite/mastodon/pull/10565))
+- Fix avatar GIFs not being animated on-hover on public profiles ([hyenagirl64](https://github.com/tootsuite/mastodon/pull/10549))
+- Fix OpenGraph parser not understanding some valid property meta tags ([da2x](https://github.com/tootsuite/mastodon/pull/10604))
+- Fix wrong fonts being displayed when Roboto is installed on user's machine ([ThibG](https://github.com/tootsuite/mastodon/pull/10594))
+- Fix confirmation modals being too narrow for a secondary action button ([ThibG](https://github.com/tootsuite/mastodon/pull/10586))
+
## [2.8.0] - 2019-04-10
### Added
diff --git a/lib/mastodon/version.rb b/lib/mastodon/version.rb
index a656031b1..fecfe8e70 100644
--- a/lib/mastodon/version.rb
+++ b/lib/mastodon/version.rb
@@ -13,7 +13,7 @@ module Mastodon
end
def patch
- 0
+ 1
end
def pre
--
cgit
From 58720aa2bdc45ec220903e0786984e803c87e8c9 Mon Sep 17 00:00:00 2001
From: Thibaut Girka
Date: Sat, 4 May 2019 00:14:49 +0200
Subject: Fix CSP when dealing with S3 hosts
---
config/initializers/content_security_policy.rb | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/config/initializers/content_security_policy.rb b/config/initializers/content_security_policy.rb
index 12b764a5a..9da38598b 100644
--- a/config/initializers/content_security_policy.rb
+++ b/config/initializers/content_security_policy.rb
@@ -7,9 +7,11 @@ if Rails.env.production?
data_hosts = [assets_host]
if ENV['S3_ENABLED'] == 'true'
- attachments_host = ENV['S3_ALIAS_HOST'] || ENV['S3_CLOUDFRONT_HOST'] || ENV['S3_HOSTNAME'] || "s3-#{ENV['S3_REGION'] || 'us-east-1'}.amazonaws.com"
+ attachments_host = "https://#{ENV['S3_ALIAS_HOST'] || ENV['S3_CLOUDFRONT_HOST'] || ENV['S3_HOSTNAME'] || "s3-#{ENV['S3_REGION'] || 'us-east-1'}.amazonaws.com"}"
+ attachments_host = "https://#{Addressable::URI.parse(attachments_host).host}"
elsif ENV['SWIFT_ENABLED'] == 'true'
attachments_host = ENV['SWIFT_OBJECT_URL']
+ attachments_host = "https://#{Addressable::URI.parse(attachments_host).host}"
else
attachments_host = nil
end
--
cgit
From 8025a41a1ff2ae1cd7c36a2baac36d3e2badb75d Mon Sep 17 00:00:00 2001
From: Eugen Rochko
Date: Sat, 4 May 2019 01:02:57 +0200
Subject: Add `tootctl cache clear` (#10689)
---
lib/cli.rb | 4 ++++
lib/mastodon/cache_cli.rb | 19 +++++++++++++++++++
2 files changed, 23 insertions(+)
create mode 100644 lib/mastodon/cache_cli.rb
diff --git a/lib/cli.rb b/lib/cli.rb
index b56c6e76f..5780e3e87 100644
--- a/lib/cli.rb
+++ b/lib/cli.rb
@@ -9,6 +9,7 @@ require_relative 'mastodon/search_cli'
require_relative 'mastodon/settings_cli'
require_relative 'mastodon/statuses_cli'
require_relative 'mastodon/domains_cli'
+require_relative 'mastodon/cache_cli'
require_relative 'mastodon/version'
module Mastodon
@@ -41,6 +42,9 @@ module Mastodon
desc 'domains SUBCOMMAND ...ARGS', 'Manage account domains'
subcommand 'domains', Mastodon::DomainsCLI
+ desc 'cache SUBCOMMAND ...ARGS', 'Manage cache'
+ subcommand 'cache', Mastodon::CacheCLI
+
option :dry_run, type: :boolean
desc 'self-destruct', 'Erase the server from the federation'
long_desc <<~LONG_DESC
diff --git a/lib/mastodon/cache_cli.rb b/lib/mastodon/cache_cli.rb
new file mode 100644
index 000000000..e9b6667b3
--- /dev/null
+++ b/lib/mastodon/cache_cli.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+require_relative '../../config/boot'
+require_relative '../../config/environment'
+require_relative 'cli_helper'
+
+module Mastodon
+ class CacheCLI < Thor
+ def self.exit_on_failure?
+ true
+ end
+
+ desc 'clear', 'Clear out the cache storage'
+ def clear
+ Rails.cache.clear
+ say('OK', :green)
+ end
+ end
+end
--
cgit
From 2c2f649200ba5b742ba9d17ac5ba553c752b59aa Mon Sep 17 00:00:00 2001
From: Thibaut Girka
Date: Sat, 4 May 2019 10:40:32 +0200
Subject: Fix CSP when PAPERCLIP_ROOT_URL is set to a different host
---
config/initializers/content_security_policy.rb | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/config/initializers/content_security_policy.rb b/config/initializers/content_security_policy.rb
index 9da38598b..2fe1a33fa 100644
--- a/config/initializers/content_security_policy.rb
+++ b/config/initializers/content_security_policy.rb
@@ -15,8 +15,16 @@ if Rails.env.production?
else
attachments_host = nil
end
+
data_hosts << attachments_host unless attachments_host.nil?
+ if ENV['PAPERCLIP_ROOT_URL']
+ url = Addressable::URI.parse(assets_host) + ENV['PAPERCLIP_ROOT_URL']
+ data_hosts << "https://#{url.host}"
+ end
+
+ data_hosts.uniq!
+
Rails.application.config.content_security_policy do |p|
p.base_uri :none
p.default_src :none
--
cgit
From c88d9e524b02cba895de9bdb7cba0e29e37703d5 Mon Sep 17 00:00:00 2001
From: Alix Rossi
Date: Sat, 4 May 2019 13:09:25 +0200
Subject: i18n: Update Corsican translation (#10692)
---
config/locales/co.yml | 1 +
1 file changed, 1 insertion(+)
diff --git a/config/locales/co.yml b/config/locales/co.yml
index aa68336f1..8c1a13e54 100644
--- a/config/locales/co.yml
+++ b/config/locales/co.yml
@@ -269,6 +269,7 @@ co:
created_msg: U blucchime di u duminiu hè attivu
destroyed_msg: U blucchime di u duminiu ùn hè più attivu
domain: Duminiu
+ existing_domain_block_html: Avete digià impostu limite più strette nant'à %{name}, duvete sbluccallu primu.
new:
create: Creà un blucchime
hint: U blucchime di duminiu ùn impedirà micca a creazione di conti indè a database, mà metudi di muderazione specifiche saranu applicati.
--
cgit
From 1149ddd3da0ca94d5dcdf3b58ca9424a5e34fc25 Mon Sep 17 00:00:00 2001
From: ThibG
Date: Fri, 3 May 2019 20:36:36 +0200
Subject: Fix glitch SCSS for links in error messages in admin interface
Port SCSS changes from 011b032300657ccca4a42866749afc6ec2588ecc
---
app/javascript/flavours/glitch/styles/forms.scss | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/app/javascript/flavours/glitch/styles/forms.scss b/app/javascript/flavours/glitch/styles/forms.scss
index 91888d305..2b8d7a682 100644
--- a/app/javascript/flavours/glitch/styles/forms.scss
+++ b/app/javascript/flavours/glitch/styles/forms.scss
@@ -533,6 +533,17 @@ code {
color: $error-value-color;
}
+ a {
+ display: inline-block;
+ color: $darker-text-color;
+ text-decoration: none;
+
+ &:hover {
+ color: $primary-text-color;
+ text-decoration: underline;
+ }
+ }
+
p {
margin-bottom: 15px;
}
--
cgit
From 4f73cde4e136320e9325d7f43232c5a614874388 Mon Sep 17 00:00:00 2001
From: ThibG
Date: Sat, 4 May 2019 17:36:43 +0200
Subject: Minor account media gallery fixes (#10695)
* Make the cursor icon consistant across media types in account media gallery
* Fix the video player modal causing scroll position to reset
---
.../mastodon/features/account_gallery/components/media_item.js | 2 +-
app/javascript/mastodon/features/ui/index.js | 5 +++--
2 files changed, 4 insertions(+), 3 deletions(-)
diff --git a/app/javascript/mastodon/features/account_gallery/components/media_item.js b/app/javascript/mastodon/features/account_gallery/components/media_item.js
index 8d462996e..5643e6449 100644
--- a/app/javascript/mastodon/features/account_gallery/components/media_item.js
+++ b/app/javascript/mastodon/features/account_gallery/components/media_item.js
@@ -133,7 +133,7 @@ export default class MediaItem extends ImmutablePureComponent {
return (
-
+
{visible && thumbnail}
diff --git a/app/javascript/mastodon/features/ui/index.js b/app/javascript/mastodon/features/ui/index.js
index c14eba992..1fcea779d 100644
--- a/app/javascript/mastodon/features/ui/index.js
+++ b/app/javascript/mastodon/features/ui/index.js
@@ -47,7 +47,8 @@ import {
Lists,
} from './util/async-components';
import { me } from '../../initial_state';
-import { previewState } from './components/media_modal';
+import { previewState as previewMediaState } from './components/media_modal';
+import { previewState as previewVideoState } from './components/video_modal';
// Dummy import, to make sure that
ends up in the application bundle.
// Without this it ends up in ~8 very commonly used bundles.
@@ -121,7 +122,7 @@ class SwitchingColumnsArea extends React.PureComponent {
}
shouldUpdateScroll (_, { location }) {
- return location.state !== previewState;
+ return location.state !== previewMediaState && location.state !== previewVideoState;
}
handleResize = debounce(() => {
--
cgit
From 56880fa76afa167aaca028faa3c3267b6521e0bc Mon Sep 17 00:00:00 2001
From: Ushitora Anqou
Date: Sun, 5 May 2019 00:39:17 +0900
Subject: Add SOURCE_TAG to show source repository's tag (#10698)
---
lib/mastodon/version.rb | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lib/mastodon/version.rb b/lib/mastodon/version.rb
index fecfe8e70..127c51048 100644
--- a/lib/mastodon/version.rb
+++ b/lib/mastodon/version.rb
@@ -42,7 +42,7 @@ module Mastodon
# specify git tag or commit hash here
def source_tag
- nil
+ ENV.fetch('SOURCE_TAG') { nil }
end
def source_url
--
cgit
From 7aa749ab46b53bc5b234332ac35acc09a636fc28 Mon Sep 17 00:00:00 2001
From: ThibG
Date: Sat, 4 May 2019 17:39:53 +0200
Subject: Fix transition: all (#10699)
---
app/javascript/styles/mastodon/admin.scss | 2 ++
app/javascript/styles/mastodon/components.scss | 6 ++++--
2 files changed, 6 insertions(+), 2 deletions(-)
diff --git a/app/javascript/styles/mastodon/admin.scss b/app/javascript/styles/mastodon/admin.scss
index fd5c08f04..dd3b47a8f 100644
--- a/app/javascript/styles/mastodon/admin.scss
+++ b/app/javascript/styles/mastodon/admin.scss
@@ -50,6 +50,7 @@ $content-width: 840px;
color: $darker-text-color;
text-decoration: none;
transition: all 200ms linear;
+ transition-property: color, background-color;
border-radius: 4px 0 0 4px;
i.fa {
@@ -60,6 +61,7 @@ $content-width: 840px;
color: $primary-text-color;
background-color: darken($ui-base-color, 5%);
transition: all 100ms linear;
+ transition-property: color, background-color;
}
&.selected {
diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss
index 2326dee38..cf8fa9392 100644
--- a/app/javascript/styles/mastodon/components.scss
+++ b/app/javascript/styles/mastodon/components.scss
@@ -1972,6 +1972,7 @@ a.account__display-name {
font-weight: 500;
border-bottom: 2px solid lighten($ui-base-color, 8%);
transition: all 50ms linear;
+ transition-property: border-bottom, background, color;
.fa {
font-weight: 400;
@@ -2137,7 +2138,7 @@ a.account__display-name {
padding: 0;
border-radius: 30px;
background-color: $ui-base-color;
- transition: all 0.2s ease;
+ transition: background-color 0.2s ease;
}
.react-toggle:hover:not(.react-toggle--disabled) .react-toggle-track {
@@ -2190,7 +2191,6 @@ a.account__display-name {
}
.react-toggle-thumb {
- transition: all 0.5s cubic-bezier(0.23, 1, 0.32, 1) 0ms;
position: absolute;
top: 1px;
left: 1px;
@@ -2201,6 +2201,7 @@ a.account__display-name {
background-color: darken($simple-background-color, 2%);
box-sizing: border-box;
transition: all 0.25s ease;
+ transition-property: border-color, left;
}
.react-toggle--checked .react-toggle-thumb {
@@ -3552,6 +3553,7 @@ a.status-card.compact:hover {
display: inline-block;
opacity: 0;
transition: all 100ms linear;
+ transition-property: transform, opacity;
font-size: 18px;
width: 18px;
height: 18px;
--
cgit
From ccf4f3240ae80f4b1410d816f03d0bef33062a71 Mon Sep 17 00:00:00 2001
From: Eugen Rochko
Date: Sat, 27 Apr 2019 03:24:09 +0200
Subject: [Glitch] Add blurhash
Port front-end changes from fba96c808d25d2fc35ec63ee6745a1e55a95d707 to glitch-soc
Signed-off-by: Thibaut Girka
---
.../flavours/glitch/components/media_gallery.js | 107 ++++++++++++++-------
.../flavours/glitch/components/status.js | 1 +
.../features/report/components/status_check_box.js | 1 +
.../features/status/components/detailed_status.js | 1 +
.../glitch/features/ui/components/media_modal.js | 1 +
.../glitch/features/ui/components/video_modal.js | 1 +
.../flavours/glitch/features/video/index.js | 48 +++++++--
.../flavours/glitch/styles/components/index.scss | 44 ++++++++-
.../flavours/glitch/styles/components/media.scss | 17 ++++
.../flavours/glitch/styles/components/status.scss | 9 +-
10 files changed, 179 insertions(+), 51 deletions(-)
diff --git a/app/javascript/flavours/glitch/components/media_gallery.js b/app/javascript/flavours/glitch/components/media_gallery.js
index b7360bae4..d1dde45b1 100644
--- a/app/javascript/flavours/glitch/components/media_gallery.js
+++ b/app/javascript/flavours/glitch/components/media_gallery.js
@@ -7,6 +7,7 @@ import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import { isIOS } from 'flavours/glitch/util/is_mobile';
import classNames from 'classnames';
import { autoPlayGif, displayMedia } from 'flavours/glitch/util/initial_state';
+import { decode } from 'blurhash';
const messages = defineMessages({
hidden: {
@@ -41,6 +42,7 @@ class Item extends React.PureComponent {
letterbox: PropTypes.bool,
onClick: PropTypes.func.isRequired,
displayWidth: PropTypes.number,
+ visible: PropTypes.bool.isRequired,
};
static defaultProps = {
@@ -49,6 +51,10 @@ class Item extends React.PureComponent {
size: 1,
};
+ state = {
+ loaded: false,
+ };
+
handleMouseEnter = (e) => {
if (this.hoverToPlay()) {
e.target.play();
@@ -82,8 +88,40 @@ class Item extends React.PureComponent {
e.stopPropagation();
}
+ componentDidMount () {
+ if (this.props.attachment.get('blurhash')) {
+ this._decode();
+ }
+ }
+
+ componentDidUpdate (prevProps) {
+ if (prevProps.attachment.get('blurhash') !== this.props.attachment.get('blurhash') && this.props.attachment.get('blurhash')) {
+ this._decode();
+ }
+ }
+
+ _decode () {
+ const hash = this.props.attachment.get('blurhash');
+ const pixels = decode(hash, 32, 32);
+
+ if (pixels) {
+ const ctx = this.canvas.getContext('2d');
+ const imageData = new ImageData(pixels, 32, 32);
+
+ ctx.putImageData(imageData, 0, 0);
+ }
+ }
+
+ setCanvasRef = c => {
+ this.canvas = c;
+ }
+
+ handleImageLoad = () => {
+ this.setState({ loaded: true });
+ }
+
render () {
- const { attachment, index, size, standalone, letterbox, displayWidth } = this.props;
+ const { attachment, index, size, standalone, letterbox, displayWidth, visible } = this.props;
let width = 50;
let height = 100;
@@ -136,12 +174,20 @@ class Item extends React.PureComponent {
let thumbnail = '';
- if (attachment.get('type') === 'image') {
+ if (attachment.get('type') === 'unknown') {
+ return (
+
+ );
+ } else if (attachment.get('type') === 'image') {
const previewUrl = attachment.get('preview_url');
const previewWidth = attachment.getIn(['meta', 'small', 'width']);
- const originalUrl = attachment.get('url');
- const originalWidth = attachment.getIn(['meta', 'original', 'width']);
+ const originalUrl = attachment.get('url');
+ const originalWidth = attachment.getIn(['meta', 'original', 'width']);
const hasSize = typeof originalWidth === 'number' && typeof previewWidth === 'number';
@@ -168,6 +214,7 @@ class Item extends React.PureComponent {
alt={attachment.get('description')}
title={attachment.get('description')}
style={{ objectPosition: letterbox ? null : `${x}% ${y}%` }}
+ onLoad={this.handleImageLoad}
/>
);
@@ -197,7 +244,8 @@ class Item extends React.PureComponent {
return (
- {thumbnail}
+
+ {visible && thumbnail}
);
}
@@ -257,6 +305,7 @@ export default class MediaGallery extends React.PureComponent {
this.node = node;
if (node && node.offsetWidth && node.offsetWidth != this.state.width) {
if (this.props.cacheWidth) this.props.cacheWidth(node.offsetWidth);
+
this.setState({
width: node.offsetWidth,
});
@@ -275,7 +324,7 @@ export default class MediaGallery extends React.PureComponent {
const width = this.state.width || defaultWidth;
- let children;
+ let children, spoilerButton;
const style = {};
@@ -289,40 +338,32 @@ export default class MediaGallery extends React.PureComponent {
return (
);
}
- if (!visible) {
- let warning = ;
+ if (this.isStandaloneEligible()) {
+ children = ;
+ } else {
+ children = media.take(4).map((attachment, i) => );
+ }
- children = (
-
- {warning}
-
+ if (visible) {
+ spoilerButton = ;
+ } else {
+ spoilerButton = (
+
+ {sensitive ? : }
);
- } else {
- if (this.isStandaloneEligible()) {
- children = ;
- } else {
- children = media.take(4).map((attachment, i) => );
- }
}
return (
- {visible ? (
-
-
- {sensitive ? (
-
-
-
- ) : null}
-
- ) : null}
+
+ {spoilerButton}
+ {visible && sensitive && (
+
+
+
+ )}
+
{children}
diff --git a/app/javascript/flavours/glitch/components/status.js b/app/javascript/flavours/glitch/components/status.js
index 94297260b..5f10e0c52 100644
--- a/app/javascript/flavours/glitch/components/status.js
+++ b/app/javascript/flavours/glitch/components/status.js
@@ -479,6 +479,7 @@ export default class Status extends ImmutablePureComponent {
{Component => ( (
{
this.video = c;
+
if (this.video) {
this.setState({ volume: this.video.volume, muted: this.video.muted });
}
@@ -160,6 +163,10 @@ export default class Video extends React.PureComponent {
this.volume = c;
}
+ setCanvasRef = c => {
+ this.canvas = c;
+ }
+
handleMouseDownRoot = e => {
e.preventDefault();
e.stopPropagation();
@@ -181,7 +188,6 @@ export default class Video extends React.PureComponent {
}
handleVolumeMouseDown = e => {
-
document.addEventListener('mousemove', this.handleMouseVolSlide, true);
document.addEventListener('mouseup', this.handleVolumeMouseUp, true);
document.addEventListener('touchmove', this.handleMouseVolSlide, true);
@@ -201,7 +207,6 @@ export default class Video extends React.PureComponent {
}
handleMouseVolSlide = throttle(e => {
-
const rect = this.volume.getBoundingClientRect();
const x = (e.clientX - rect.left) / this.volWidth; //x position within the element.
@@ -272,6 +277,10 @@ export default class Video extends React.PureComponent {
document.addEventListener('webkitfullscreenchange', this.handleFullscreenChange, true);
document.addEventListener('mozfullscreenchange', this.handleFullscreenChange, true);
document.addEventListener('MSFullscreenChange', this.handleFullscreenChange, true);
+
+ if (this.props.blurhash) {
+ this._decode();
+ }
}
componentWillUnmount () {
@@ -293,6 +302,24 @@ export default class Video extends React.PureComponent {
}
}
+ componentDidUpdate (prevProps) {
+ if (prevProps.blurhash !== this.props.blurhash && this.props.blurhash) {
+ this._decode();
+ }
+ }
+
+ _decode () {
+ const hash = this.props.blurhash;
+ const pixels = decode(hash, 32, 32);
+
+ if (pixels) {
+ const ctx = this.canvas.getContext('2d');
+ const imageData = new ImageData(pixels, 32, 32);
+
+ ctx.putImageData(imageData, 0, 0);
+ }
+ }
+
handleFullscreenChange = () => {
this.setState({ fullscreen: isFullscreen() });
}
@@ -337,6 +364,7 @@ export default class Video extends React.PureComponent {
handleOpenVideo = () => {
const { src, preview, width, height, alt } = this.props;
+
const media = fromJS({
type: 'video',
url: src,
@@ -385,6 +413,7 @@ export default class Video extends React.PureComponent {
}
let preload;
+
if (startTime || fullscreen || dragging) {
preload = 'auto';
} else if (detailed) {
@@ -403,7 +432,9 @@ export default class Video extends React.PureComponent {
onMouseDown={this.handleMouseDownRoot}
tabIndex={0}
>
-
+
+ {revealed &&
+ />}
-
- {warning}
-
-
+
+
+ {warning}
+
+
diff --git a/app/javascript/flavours/glitch/styles/components/index.scss b/app/javascript/flavours/glitch/styles/components/index.scss
index b098676b0..b323305cc 100644
--- a/app/javascript/flavours/glitch/styles/components/index.scss
+++ b/app/javascript/flavours/glitch/styles/components/index.scss
@@ -1066,15 +1066,49 @@
}
.spoiler-button {
- display: none;
- left: 4px;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
position: absolute;
- text-shadow: 0 1px 1px $base-shadow-color, 1px 0 1px $base-shadow-color;
- top: 4px;
z-index: 100;
- &.spoiler-button--visible {
+ &--minified {
+ display: block;
+ left: 4px;
+ top: 4px;
+ width: auto;
+ height: auto;
+ }
+
+ &--hidden {
+ display: none;
+ }
+
+ &__overlay {
display: block;
+ background: transparent;
+ width: 100%;
+ height: 100%;
+ border: 0;
+
+ &__label {
+ display: inline-block;
+ background: rgba($base-overlay-background, 0.5);
+ border-radius: 8px;
+ padding: 8px 12px;
+ color: $primary-text-color;
+ font-weight: 500;
+ font-size: 14px;
+ }
+
+ &:hover,
+ &:focus,
+ &:active {
+ .spoiler-button__overlay__label {
+ background: rgba($base-overlay-background, 0.8);
+ }
+ }
}
}
diff --git a/app/javascript/flavours/glitch/styles/components/media.scss b/app/javascript/flavours/glitch/styles/components/media.scss
index fabef2a56..0393a3886 100644
--- a/app/javascript/flavours/glitch/styles/components/media.scss
+++ b/app/javascript/flavours/glitch/styles/components/media.scss
@@ -117,6 +117,8 @@
text-decoration: none;
color: $secondary-text-color;
line-height: 0;
+ position: relative;
+ z-index: 1;
&,
img {
@@ -131,6 +133,21 @@
}
}
+.media-gallery__preview {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ position: absolute;
+ top: 0;
+ left: 0;
+ z-index: 0;
+ background: $base-overlay-background;
+
+ &--hidden {
+ display: none;
+ }
+}
+
.media-gallery__gifv {
height: 100%;
overflow: hidden;
diff --git a/app/javascript/flavours/glitch/styles/components/status.scss b/app/javascript/flavours/glitch/styles/components/status.scss
index b656f0baf..fb031258f 100644
--- a/app/javascript/flavours/glitch/styles/components/status.scss
+++ b/app/javascript/flavours/glitch/styles/components/status.scss
@@ -705,7 +705,7 @@
& > div {
background: rgba($base-shadow-color, 0.6);
- border-radius: 4px;
+ border-radius: 8px;
padding: 12px 9px;
flex: 0 0 auto;
display: flex;
@@ -716,19 +716,18 @@
button,
a {
display: inline;
- color: $primary-text-color;
+ color: $secondary-text-color;
background: transparent;
border: 0;
- padding: 0 5px;
+ padding: 0 8px;
text-decoration: none;
- opacity: 0.6;
font-size: 18px;
line-height: 18px;
&:hover,
&:active,
&:focus {
- opacity: 1;
+ color: $primary-text-color;
}
}
--
cgit
From 373dd1fdf167977c97041059cf009a1331132d28 Mon Sep 17 00:00:00 2001
From: Thibaut Girka
Date: Sat, 4 May 2019 18:18:15 +0200
Subject: Minor CSS fixes
---
app/javascript/flavours/glitch/styles/components/index.scss | 3 ++-
app/javascript/flavours/glitch/styles/components/sensitive.scss | 3 +--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/app/javascript/flavours/glitch/styles/components/index.scss b/app/javascript/flavours/glitch/styles/components/index.scss
index b323305cc..788bb2e0e 100644
--- a/app/javascript/flavours/glitch/styles/components/index.scss
+++ b/app/javascript/flavours/glitch/styles/components/index.scss
@@ -1074,11 +1074,12 @@
z-index: 100;
&--minified {
- display: block;
+ display: flex;
left: 4px;
top: 4px;
width: auto;
height: auto;
+ align-items: center;
}
&--hidden {
diff --git a/app/javascript/flavours/glitch/styles/components/sensitive.scss b/app/javascript/flavours/glitch/styles/components/sensitive.scss
index b0a7dfe03..44b7ec981 100644
--- a/app/javascript/flavours/glitch/styles/components/sensitive.scss
+++ b/app/javascript/flavours/glitch/styles/components/sensitive.scss
@@ -9,13 +9,12 @@
}
.sensitive-marker {
- margin: 0 3px;
border-radius: 2px;
padding: 2px 6px;
color: rgba($primary-text-color, 0.8);
background: rgba($base-overlay-background, 0.5);
font-size: 12px;
- line-height: 15px;
+ line-height: 18px;
text-transform: uppercase;
opacity: .9;
transition: opacity .1s ease;
--
cgit
From a5da59f140a2a8fb2d3f480cdd87964d0beff103 Mon Sep 17 00:00:00 2001
From: Eugen Rochko
Date: Thu, 2 May 2019 08:34:32 +0200
Subject: [Glitch] Change account gallery in web UI
Port 3f143606faa6181ff2745b6bd29ac8ea075088bf to glitch-soc
Signed-off-by: Thibaut Girka
---
.../flavours/glitch/components/media_gallery.js | 2 +-
.../account_gallery/components/media_item.js | 160 +++++++++++++++------
.../glitch/features/account_gallery/index.js | 67 +++++----
.../glitch/styles/components/accounts.scss | 59 ++------
.../flavours/glitch/styles/components/media.scss | 1 +
5 files changed, 168 insertions(+), 121 deletions(-)
diff --git a/app/javascript/flavours/glitch/components/media_gallery.js b/app/javascript/flavours/glitch/components/media_gallery.js
index d1dde45b1..ab1cccc60 100644
--- a/app/javascript/flavours/glitch/components/media_gallery.js
+++ b/app/javascript/flavours/glitch/components/media_gallery.js
@@ -177,7 +177,7 @@ class Item extends React.PureComponent {
if (attachment.get('type') === 'unknown') {
return (
diff --git a/app/javascript/flavours/glitch/features/account_gallery/components/media_item.js b/app/javascript/flavours/glitch/features/account_gallery/components/media_item.js
index 89778e123..cc35097a7 100644
--- a/app/javascript/flavours/glitch/features/account_gallery/components/media_item.js
+++ b/app/javascript/flavours/glitch/features/account_gallery/components/media_item.js
@@ -1,69 +1,141 @@
import React from 'react';
+import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
-import Permalink from 'flavours/glitch/components/permalink';
-import { displayMedia } from 'flavours/glitch/util/initial_state';
+import { autoPlayGif, displayMedia } from 'flavours/glitch/util/initial_state';
+import classNames from 'classnames';
+import { decode } from 'blurhash';
+import { isIOS } from 'flavours/glitch/util/is_mobile';
export default class MediaItem extends ImmutablePureComponent {
static propTypes = {
- media: ImmutablePropTypes.map.isRequired,
+ attachment: ImmutablePropTypes.map.isRequired,
+ displayWidth: PropTypes.number.isRequired,
+ onOpenMedia: PropTypes.func.isRequired,
};
state = {
- visible: displayMedia !== 'hide_all' && !this.props.media.getIn(['status', 'sensitive']) || displayMedia === 'show_all',
+ visible: displayMedia !== 'hide_all' && !this.props.attachment.getIn(['status', 'sensitive']) || displayMedia === 'show_all',
+ loaded: false,
};
- handleClick = () => {
- if (!this.state.visible) {
- this.setState({ visible: true });
- return true;
+ componentDidMount () {
+ if (this.props.attachment.get('blurhash')) {
+ this._decode();
}
+ }
- return false;
+ componentDidUpdate (prevProps) {
+ if (prevProps.attachment.get('blurhash') !== this.props.attachment.get('blurhash') && this.props.attachment.get('blurhash')) {
+ this._decode();
+ }
}
- render () {
- const { media } = this.props;
- const { visible } = this.state;
- const status = media.get('status');
- const focusX = media.getIn(['meta', 'focus', 'x']);
- const focusY = media.getIn(['meta', 'focus', 'y']);
- const x = ((focusX / 2) + .5) * 100;
- const y = ((focusY / -2) + .5) * 100;
- const style = {};
-
- let label, icon, title;
-
- if (media.get('type') === 'gifv') {
- label = GIF ;
+ _decode () {
+ const hash = this.props.attachment.get('blurhash');
+ const pixels = decode(hash, 32, 32);
+
+ if (pixels) {
+ const ctx = this.canvas.getContext('2d');
+ const imageData = new ImageData(pixels, 32, 32);
+
+ ctx.putImageData(imageData, 0, 0);
+ }
+ }
+
+ setCanvasRef = c => {
+ this.canvas = c;
+ }
+
+ handleImageLoad = () => {
+ this.setState({ loaded: true });
+ }
+
+ handleMouseEnter = e => {
+ if (this.hoverToPlay()) {
+ e.target.play();
+ }
+ }
+
+ handleMouseLeave = e => {
+ if (this.hoverToPlay()) {
+ e.target.pause();
+ e.target.currentTime = 0;
}
+ }
+
+ hoverToPlay () {
+ return !autoPlayGif && ['gifv', 'video'].indexOf(this.props.attachment.get('type')) !== -1;
+ }
+
+ handleClick = e => {
+ if (e.button === 0 && !(e.ctrlKey || e.metaKey)) {
+ e.preventDefault();
+
+ if (this.state.visible) {
+ this.props.onOpenMedia(this.props.attachment);
+ } else {
+ this.setState({ visible: true });
+ }
+ }
+ }
+
+ render () {
+ const { attachment, displayWidth } = this.props;
+ const { visible, loaded } = this.state;
+
+ const width = `${Math.floor((displayWidth - 4) / 3) - 4}px`;
+ const height = width;
+ const status = attachment.get('status');
+
+ let thumbnail = '';
+
+ if (attachment.get('type') === 'unknown') {
+ // Skip
+ } else if (attachment.get('type') === 'image') {
+ const focusX = attachment.getIn(['meta', 'focus', 'x']) || 0;
+ const focusY = attachment.getIn(['meta', 'focus', 'y']) || 0;
+ const x = ((focusX / 2) + .5) * 100;
+ const y = ((focusY / -2) + .5) * 100;
+
+ thumbnail = (
+
+ );
+ } else if (['gifv', 'video'].indexOf(attachment.get('type')) !== -1) {
+ const autoPlay = !isIOS() && autoPlayGif;
- if (visible) {
- style.backgroundImage = `url(${media.get('preview_url')})`;
- style.backgroundPosition = `${x}% ${y}%`;
- title = media.get('description');
- } else {
- icon = (
-
-
-
+ thumbnail = (
+
+
+ GIF
+
);
- title = status.get('spoiler_text') || media.get('description');
}
return (
-
-
- {icon}
- {label}
-
+
);
}
diff --git a/app/javascript/flavours/glitch/features/account_gallery/index.js b/app/javascript/flavours/glitch/features/account_gallery/index.js
index 3b1af108f..264aff261 100644
--- a/app/javascript/flavours/glitch/features/account_gallery/index.js
+++ b/app/javascript/flavours/glitch/features/account_gallery/index.js
@@ -14,12 +14,13 @@ import HeaderContainer from 'flavours/glitch/features/account_timeline/container
import { ScrollContainer } from 'react-router-scroll-4';
import LoadMore from 'flavours/glitch/components/load_more';
import MissingIndicator from 'flavours/glitch/components/missing_indicator';
+import { openModal } from 'flavours/glitch/actions/modal';
const mapStateToProps = (state, props) => ({
isAccount: !!state.getIn(['accounts', props.params.accountId]),
- medias: getAccountGallery(state, props.params.accountId),
+ attachments: getAccountGallery(state, props.params.accountId),
isLoading: state.getIn(['timelines', `account:${props.params.accountId}:media`, 'isLoading']),
- hasMore: state.getIn(['timelines', `account:${props.params.accountId}:media`, 'hasMore']),
+ hasMore: state.getIn(['timelines', `account:${props.params.accountId}:media`, 'hasMore']),
});
class LoadMoreMedia extends ImmutablePureComponent {
@@ -50,12 +51,16 @@ export default class AccountGallery extends ImmutablePureComponent {
static propTypes = {
params: PropTypes.object.isRequired,
dispatch: PropTypes.func.isRequired,
- medias: ImmutablePropTypes.list.isRequired,
+ attachments: ImmutablePropTypes.list.isRequired,
isLoading: PropTypes.bool,
hasMore: PropTypes.bool,
isAccount: PropTypes.bool,
};
+ state = {
+ width: 323,
+ };
+
componentDidMount () {
this.props.dispatch(fetchAccount(this.props.params.accountId));
this.props.dispatch(expandAccountMediaTimeline(this.props.params.accountId));
@@ -74,11 +79,11 @@ export default class AccountGallery extends ImmutablePureComponent {
handleScrollToBottom = () => {
if (this.props.hasMore) {
- this.handleLoadMore(this.props.medias.size > 0 ? this.props.medias.last().getIn(['status', 'id']) : undefined);
+ this.handleLoadMore(this.props.attachments.size > 0 ? this.props.attachments.last().getIn(['status', 'id']) : undefined);
}
}
- handleScroll = (e) => {
+ handleScroll = e => {
const { scrollTop, scrollHeight, clientHeight } = e.target;
const offset = scrollHeight - scrollTop - clientHeight;
@@ -91,7 +96,7 @@ export default class AccountGallery extends ImmutablePureComponent {
this.props.dispatch(expandAccountMediaTimeline(this.props.params.accountId, { maxId }));
};
- handleLoadOlder = (e) => {
+ handleLoadOlder = e => {
e.preventDefault();
this.handleScrollToBottom();
}
@@ -101,12 +106,30 @@ export default class AccountGallery extends ImmutablePureComponent {
return !(location.state && location.state.mastodonModalOpen);
}
- setRef = c => {
+ setColumnRef = c => {
this.column = c;
}
+ handleOpenMedia = attachment => {
+ if (attachment.get('type') === 'video') {
+ this.props.dispatch(openModal('VIDEO', { media: attachment }));
+ } else {
+ const media = attachment.getIn(['status', 'media_attachments']);
+ const index = media.findIndex(x => x.get('id') === attachment.get('id'));
+
+ this.props.dispatch(openModal('MEDIA', { media, index }));
+ }
+ }
+
+ handleRef = c => {
+ if (c) {
+ this.setState({ width: c.offsetWidth });
+ }
+ }
+
render () {
- const { medias, isLoading, hasMore, isAccount } = this.props;
+ const { attachments, isLoading, hasMore, isAccount } = this.props;
+ const { width } = this.state;
if (!isAccount) {
return (
@@ -116,9 +139,7 @@ export default class AccountGallery extends ImmutablePureComponent {
);
}
- let loadOlder = null;
-
- if (!medias && isLoading) {
+ if (!attachments && isLoading) {
return (
@@ -126,35 +147,31 @@ export default class AccountGallery extends ImmutablePureComponent {
);
}
- if (hasMore && !(isLoading && medias.size === 0)) {
+ let loadOlder = null;
+
+ if (hasMore && !(isLoading && attachments.size === 0)) {
loadOlder = ;
}
return (
-
+
-
- {medias.map((media, index) => media === null ? (
-
0 ? medias.getIn(index - 1, 'id') : null}
- onLoadMore={this.handleLoadMore}
- />
+
+ {attachments.map((attachment, index) => attachment === null ? (
+ 0 ? attachments.getIn(index - 1, 'id') : null} onLoadMore={this.handleLoadMore} />
) : (
-
+
))}
+
{loadOlder}
- {isLoading && medias.size === 0 && (
+ {isLoading && attachments.size === 0 && (
diff --git a/app/javascript/flavours/glitch/styles/components/accounts.scss b/app/javascript/flavours/glitch/styles/components/accounts.scss
index 00380c575..f753b7efa 100644
--- a/app/javascript/flavours/glitch/styles/components/accounts.scss
+++ b/app/javascript/flavours/glitch/styles/components/accounts.scss
@@ -331,62 +331,19 @@
.account-gallery__container {
display: flex;
- justify-content: center;
flex-wrap: wrap;
- padding: 2px;
+ justify-content: center;
+ padding: 4px 2px;
}
.account-gallery__item {
- flex-grow: 1;
- width: 50%;
- overflow: hidden;
+ border: none;
+ box-sizing: border-box;
+ display: block;
position: relative;
-
- &::before {
- content: "";
- display: block;
- padding-top: 100%;
- }
-
- a {
- display: block;
- width: calc(100% - 4px);
- height: calc(100% - 4px);
- margin: 2px;
- top: 0;
- left: 0;
- background-color: $base-overlay-background;
- background-size: cover;
- background-position: center;
- position: absolute;
- color: $ui-primary-color;
- text-decoration: none;
- border-radius: 4px;
-
- &:hover,
- &:active,
- &:focus {
- outline: 0;
- color: $ui-secondary-color;
-
- &::before {
- content: "";
- display: block;
- width: 100%;
- height: 100%;
- background: rgba($base-overlay-background, 0.3);
- border-radius: 4px;
- }
- }
- }
-
- &__icons {
- position: absolute;
- top: 50%;
- left: 50%;
- transform: translate(-50%, -50%);
- font-size: 24px;
- }
+ border-radius: 4px;
+ overflow: hidden;
+ margin: 2px;
}
.notification__filter-bar,
diff --git a/app/javascript/flavours/glitch/styles/components/media.scss b/app/javascript/flavours/glitch/styles/components/media.scss
index 0393a3886..bc241de14 100644
--- a/app/javascript/flavours/glitch/styles/components/media.scss
+++ b/app/javascript/flavours/glitch/styles/components/media.scss
@@ -58,6 +58,7 @@
pointer-events: none;
opacity: 0.9;
transition: opacity 0.1s ease;
+ line-height: 18px;
}
.media-gallery__gifv {
--
cgit
From 209c080280f8dbe7dd02fc328841135b8eade15a Mon Sep 17 00:00:00 2001
From: Eugen Rochko
Date: Fri, 3 May 2019 04:02:55 +0200
Subject: [Glitch] Fix alignment of items in the account gallery in web UI and
load more per page
Port 967e419f8fa87af74f4bb530d7493c1dde02fca8 to glitch-soc
Signed-off-by: Thibaut Girka
---
app/javascript/flavours/glitch/actions/timelines.js | 2 +-
app/javascript/flavours/glitch/styles/components/accounts.scss | 1 -
2 files changed, 1 insertion(+), 2 deletions(-)
diff --git a/app/javascript/flavours/glitch/actions/timelines.js b/app/javascript/flavours/glitch/actions/timelines.js
index f218ee06b..cca571583 100644
--- a/app/javascript/flavours/glitch/actions/timelines.js
+++ b/app/javascript/flavours/glitch/actions/timelines.js
@@ -97,7 +97,7 @@ export const expandCommunityTimeline = ({ maxId, onlyMedia } = {}, done =
export const expandDirectTimeline = ({ maxId } = {}, done = noOp) => expandTimeline('direct', '/api/v1/timelines/direct', { max_id: maxId }, done);
export const expandAccountTimeline = (accountId, { maxId, withReplies } = {}) => expandTimeline(`account:${accountId}${withReplies ? ':with_replies' : ''}`, `/api/v1/accounts/${accountId}/statuses`, { exclude_replies: !withReplies, max_id: maxId });
export const expandAccountFeaturedTimeline = accountId => expandTimeline(`account:${accountId}:pinned`, `/api/v1/accounts/${accountId}/statuses`, { pinned: true });
-export const expandAccountMediaTimeline = (accountId, { maxId } = {}) => expandTimeline(`account:${accountId}:media`, `/api/v1/accounts/${accountId}/statuses`, { max_id: maxId, only_media: true });
+export const expandAccountMediaTimeline = (accountId, { maxId } = {}) => expandTimeline(`account:${accountId}:media`, `/api/v1/accounts/${accountId}/statuses`, { max_id: maxId, only_media: true, limit: 40 });
export const expandListTimeline = (id, { maxId } = {}, done = noOp) => expandTimeline(`list:${id}`, `/api/v1/timelines/list/${id}`, { max_id: maxId }, done);
export const expandHashtagTimeline = (hashtag, { maxId, tags } = {}, done = noOp) => {
diff --git a/app/javascript/flavours/glitch/styles/components/accounts.scss b/app/javascript/flavours/glitch/styles/components/accounts.scss
index f753b7efa..518eea5fa 100644
--- a/app/javascript/flavours/glitch/styles/components/accounts.scss
+++ b/app/javascript/flavours/glitch/styles/components/accounts.scss
@@ -332,7 +332,6 @@
.account-gallery__container {
display: flex;
flex-wrap: wrap;
- justify-content: center;
padding: 4px 2px;
}
--
cgit
From bc97fd641f1f075e45f920fdc214f355ce16f53d Mon Sep 17 00:00:00 2001
From: Eugen Rochko
Date: Fri, 3 May 2019 16:16:30 +0200
Subject: [Glitch] Add button to view context to media modal
Port eb63217210b0ab85ff1fcca9506d5e7931382a56 to glitch-soc
Signed-off-by: Thibaut Girka
---
.../glitch/features/account_gallery/index.js | 4 +--
.../glitch/features/ui/components/media_modal.js | 25 +++++++++++--
.../glitch/features/ui/components/video_modal.js | 18 +++++++++-
.../flavours/glitch/features/video/index.js | 9 +++--
.../flavours/glitch/styles/components/media.scss | 42 ++++++++++++++++++++++
5 files changed, 90 insertions(+), 8 deletions(-)
diff --git a/app/javascript/flavours/glitch/features/account_gallery/index.js b/app/javascript/flavours/glitch/features/account_gallery/index.js
index 264aff261..3e4421306 100644
--- a/app/javascript/flavours/glitch/features/account_gallery/index.js
+++ b/app/javascript/flavours/glitch/features/account_gallery/index.js
@@ -112,12 +112,12 @@ export default class AccountGallery extends ImmutablePureComponent {
handleOpenMedia = attachment => {
if (attachment.get('type') === 'video') {
- this.props.dispatch(openModal('VIDEO', { media: attachment }));
+ this.props.dispatch(openModal('VIDEO', { media: attachment, status: attachment.get('status') }));
} else {
const media = attachment.getIn(['status', 'media_attachments']);
const index = media.findIndex(x => x.get('id') === attachment.get('id'));
- this.props.dispatch(openModal('MEDIA', { media, index }));
+ this.props.dispatch(openModal('MEDIA', { media, index, status: attachment.get('status') }));
}
}
diff --git a/app/javascript/flavours/glitch/features/ui/components/media_modal.js b/app/javascript/flavours/glitch/features/ui/components/media_modal.js
index 39386ee1c..ce6660480 100644
--- a/app/javascript/flavours/glitch/features/ui/components/media_modal.js
+++ b/app/javascript/flavours/glitch/features/ui/components/media_modal.js
@@ -5,7 +5,7 @@ import PropTypes from 'prop-types';
import Video from 'flavours/glitch/features/video';
import ExtendedVideoPlayer from 'flavours/glitch/components/extended_video_player';
import classNames from 'classnames';
-import { defineMessages, injectIntl } from 'react-intl';
+import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import IconButton from 'flavours/glitch/components/icon_button';
import ImmutablePureComponent from 'react-immutable-pure-component';
import ImageLoader from './image_loader';
@@ -19,8 +19,13 @@ const messages = defineMessages({
@injectIntl
export default class MediaModal extends ImmutablePureComponent {
+ static contextTypes = {
+ router: PropTypes.object,
+ };
+
static propTypes = {
media: ImmutablePropTypes.list.isRequired,
+ status: ImmutablePropTypes.map,
index: PropTypes.number.isRequired,
onClose: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
@@ -81,8 +86,15 @@ export default class MediaModal extends ImmutablePureComponent {
}));
};
+ handleStatusClick = e => {
+ if (e.button === 0 && !(e.ctrlKey || e.metaKey)) {
+ e.preventDefault();
+ this.context.router.history.push(`/statuses/${this.props.status.get('id')}`);
+ }
+ }
+
render () {
- const { media, intl, onClose } = this.props;
+ const { media, status, intl, onClose } = this.props;
const { navigationHidden } = this.state;
const index = this.getIndex();
@@ -186,10 +198,19 @@ export default class MediaModal extends ImmutablePureComponent {
{content}
+
+
{leftNav}
{rightNav}
+
+ {status && (
+
+ )}
+
diff --git a/app/javascript/flavours/glitch/features/ui/components/video_modal.js b/app/javascript/flavours/glitch/features/ui/components/video_modal.js
index 8c74d5a13..3f742c260 100644
--- a/app/javascript/flavours/glitch/features/ui/components/video_modal.js
+++ b/app/javascript/flavours/glitch/features/ui/components/video_modal.js
@@ -3,17 +3,32 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types';
import Video from 'flavours/glitch/features/video';
import ImmutablePureComponent from 'react-immutable-pure-component';
+import { FormattedMessage } from 'react-intl';
export default class VideoModal extends ImmutablePureComponent {
+ static contextTypes = {
+ router: PropTypes.object,
+ };
+
static propTypes = {
media: ImmutablePropTypes.map.isRequired,
+ status: ImmutablePropTypes.map,
time: PropTypes.number,
onClose: PropTypes.func.isRequired,
};
+ handleStatusClick = e => {
+ if (e.button === 0 && !(e.ctrlKey || e.metaKey)) {
+ e.preventDefault();
+ this.context.router.history.push(`/statuses/${this.props.status.get('id')}`);
+ }
+ }
+
render () {
- const { media, time, onClose } = this.props;
+ const { media, status, time, onClose } = this.props;
+
+ const link = status &&
;
return (
@@ -24,6 +39,7 @@ export default class VideoModal extends ImmutablePureComponent {
src={media.get('url')}
startTime={time}
onCloseVideo={onClose}
+ link={link}
detailed
alt={media.get('description')}
/>
diff --git a/app/javascript/flavours/glitch/features/video/index.js b/app/javascript/flavours/glitch/features/video/index.js
index aad24b1d9..381485802 100644
--- a/app/javascript/flavours/glitch/features/video/index.js
+++ b/app/javascript/flavours/glitch/features/video/index.js
@@ -106,6 +106,7 @@ export default class Video extends React.PureComponent {
intl: PropTypes.object.isRequired,
cacheWidth: PropTypes.func,
blurhash: PropTypes.string,
+ link: PropTypes.node,
};
state = {
@@ -384,7 +385,7 @@ export default class Video extends React.PureComponent {
}
render () {
- const { preview, src, inline, startTime, onOpenVideo, onCloseVideo, intl, alt, letterbox, fullwidth, detailed, sensitive } = this.props;
+ const { preview, src, inline, startTime, onOpenVideo, onCloseVideo, intl, alt, letterbox, fullwidth, detailed, sensitive, link } = this.props;
const { containerWidth, currentTime, duration, volume, buffer, dragging, paused, fullscreen, hovered, muted, revealed } = this.state;
const progress = (currentTime / duration) * 100;
const playerStyle = {};
@@ -487,13 +488,15 @@ export default class Video extends React.PureComponent {
/>
- {(detailed || fullscreen) &&
+ {(detailed || fullscreen) && (
{formatTime(currentTime)}
/
{formatTime(duration)}
- }
+ )}
+
+ {link &&
{link} }
diff --git a/app/javascript/flavours/glitch/styles/components/media.scss b/app/javascript/flavours/glitch/styles/components/media.scss
index bc241de14..e5927057e 100644
--- a/app/javascript/flavours/glitch/styles/components/media.scss
+++ b/app/javascript/flavours/glitch/styles/components/media.scss
@@ -270,6 +270,31 @@
pointer-events: none;
}
+.media-modal__meta {
+ text-align: center;
+ position: absolute;
+ left: 0;
+ bottom: 20px;
+ width: 100%;
+ pointer-events: none;
+
+ &--shifted {
+ bottom: 62px;
+ }
+
+ a {
+ text-decoration: none;
+ font-weight: 500;
+ color: $ui-secondary-color;
+
+ &:hover,
+ &:focus,
+ &:active {
+ text-decoration: underline;
+ }
+ }
+}
+
.media-modal__page-dot {
display: inline-block;
}
@@ -519,6 +544,23 @@
}
}
+ &__link {
+ padding: 2px 10px;
+
+ a {
+ text-decoration: none;
+ font-size: 14px;
+ font-weight: 500;
+ color: $white;
+
+ &:hover,
+ &:active,
+ &:focus {
+ text-decoration: underline;
+ }
+ }
+ }
+
&__seek {
cursor: pointer;
height: 24px;
--
cgit
From b7f69beebe2a4f7b94bc39772e7e9714540220b7 Mon Sep 17 00:00:00 2001
From: ThibG
Date: Sat, 4 May 2019 17:36:43 +0200
Subject: [Glitch] Make the cursor icon consistant across media types in
account media gallery
---
.../flavours/glitch/features/account_gallery/components/media_item.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/javascript/flavours/glitch/features/account_gallery/components/media_item.js b/app/javascript/flavours/glitch/features/account_gallery/components/media_item.js
index cc35097a7..1eae552f6 100644
--- a/app/javascript/flavours/glitch/features/account_gallery/components/media_item.js
+++ b/app/javascript/flavours/glitch/features/account_gallery/components/media_item.js
@@ -132,7 +132,7 @@ export default class MediaItem extends ImmutablePureComponent {
return (
-
+
{visible && thumbnail}
--
cgit
From 8e221cd22bf272f7de8c12e06a98bfb7226a4f1c Mon Sep 17 00:00:00 2001
From: ThibG
Date: Sat, 4 May 2019 17:39:53 +0200
Subject: [Glitch] Fix transition: all
Port 7aa749ab46b53bc5b234332ac35acc09a636fc28 to glitch-soc
---
app/javascript/flavours/glitch/styles/admin.scss | 2 ++
app/javascript/flavours/glitch/styles/components/drawer.scss | 1 +
app/javascript/flavours/glitch/styles/components/index.scss | 8 ++++++--
app/javascript/flavours/glitch/styles/components/search.scss | 1 +
4 files changed, 10 insertions(+), 2 deletions(-)
diff --git a/app/javascript/flavours/glitch/styles/admin.scss b/app/javascript/flavours/glitch/styles/admin.scss
index 05c7821e4..74f91599a 100644
--- a/app/javascript/flavours/glitch/styles/admin.scss
+++ b/app/javascript/flavours/glitch/styles/admin.scss
@@ -50,6 +50,7 @@ $content-width: 840px;
color: $darker-text-color;
text-decoration: none;
transition: all 200ms linear;
+ transition-property: color, background-color;
border-radius: 4px 0 0 4px;
i.fa {
@@ -60,6 +61,7 @@ $content-width: 840px;
color: $primary-text-color;
background-color: darken($ui-base-color, 5%);
transition: all 100ms linear;
+ transition-property: color, background-color;
}
&.selected {
diff --git a/app/javascript/flavours/glitch/styles/components/drawer.scss b/app/javascript/flavours/glitch/styles/components/drawer.scss
index 41c794790..9f426448f 100644
--- a/app/javascript/flavours/glitch/styles/components/drawer.scss
+++ b/app/javascript/flavours/glitch/styles/components/drawer.scss
@@ -125,6 +125,7 @@
cursor: default;
pointer-events: none;
transition: all 100ms linear;
+ transition-property: color, transform, opacity;
}
.fa-search {
diff --git a/app/javascript/flavours/glitch/styles/components/index.scss b/app/javascript/flavours/glitch/styles/components/index.scss
index 788bb2e0e..f12f8b7fa 100644
--- a/app/javascript/flavours/glitch/styles/components/index.scss
+++ b/app/javascript/flavours/glitch/styles/components/index.scss
@@ -25,6 +25,7 @@
text-decoration: none;
text-overflow: ellipsis;
transition: all 100ms ease-in;
+ transition-property: background-color;
white-space: nowrap;
width: auto;
@@ -33,6 +34,7 @@
&:hover {
background-color: lighten($ui-highlight-color, 7%);
transition: all 200ms ease-out;
+ transition-property: background-color;
}
&--destructive {
@@ -564,6 +566,7 @@
font-weight: 500;
border-bottom: 2px solid lighten($ui-base-color, 8%);
transition: all 200ms linear;
+ transition-property: background;
.fa {
font-weight: 400;
@@ -581,6 +584,7 @@
@include multi-columns('screen and (min-width: 631px)') {
background: lighten($ui-base-color, 14%);
transition: all 100ms linear;
+ transition-property: background;
}
}
@@ -664,7 +668,7 @@
padding: 0;
border-radius: 30px;
background-color: $ui-base-color;
- transition: all 0.2s ease;
+ transition: background-color 0.2s ease;
}
.react-toggle:hover:not(.react-toggle--disabled) .react-toggle-track {
@@ -717,7 +721,6 @@
}
.react-toggle-thumb {
- transition: all 0.5s cubic-bezier(0.23, 1, 0.32, 1) 0ms;
position: absolute;
top: 1px;
left: 1px;
@@ -728,6 +731,7 @@
background-color: darken($simple-background-color, 2%);
box-sizing: border-box;
transition: all 0.25s ease;
+ transition-property: border-color, left;
}
.react-toggle--checked .react-toggle-thumb {
diff --git a/app/javascript/flavours/glitch/styles/components/search.scss b/app/javascript/flavours/glitch/styles/components/search.scss
index 3746fbad2..f59ef019e 100644
--- a/app/javascript/flavours/glitch/styles/components/search.scss
+++ b/app/javascript/flavours/glitch/styles/components/search.scss
@@ -18,6 +18,7 @@
display: inline-block;
opacity: 0;
transition: all 100ms linear;
+ transition-property: transform, opacity;
font-size: 18px;
width: 18px;
height: 18px;
--
cgit
From cbda1b8b66270a02a3d06d1cafe0c6396466c50d Mon Sep 17 00:00:00 2001
From: Thibaut Girka
Date: Sat, 4 May 2019 20:03:37 +0200
Subject: Add back description on hover
---
.../flavours/glitch/features/account_gallery/components/media_item.js | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/app/javascript/flavours/glitch/features/account_gallery/components/media_item.js b/app/javascript/flavours/glitch/features/account_gallery/components/media_item.js
index 1eae552f6..f2a661862 100644
--- a/app/javascript/flavours/glitch/features/account_gallery/components/media_item.js
+++ b/app/javascript/flavours/glitch/features/account_gallery/components/media_item.js
@@ -88,6 +88,7 @@ export default class MediaItem extends ImmutablePureComponent {
const width = `${Math.floor((displayWidth - 4) / 3) - 4}px`;
const height = width;
const status = attachment.get('status');
+ const title = status.get('spoiler_text') || attachment.get('description');
let thumbnail = '';
@@ -132,7 +133,7 @@ export default class MediaItem extends ImmutablePureComponent {
return (
-
+
{visible && thumbnail}
--
cgit
From b1ab4d5ebe3ccb7d91e55eedd0ad08226358c446 Mon Sep 17 00:00:00 2001
From: Thibaut Girka
Date: Sat, 4 May 2019 20:06:17 +0200
Subject: Add visibility icon back in media gallery
---
.../glitch/features/account_gallery/components/media_item.js | 8 +++++++-
app/javascript/flavours/glitch/styles/components/accounts.scss | 8 ++++++++
2 files changed, 15 insertions(+), 1 deletion(-)
diff --git a/app/javascript/flavours/glitch/features/account_gallery/components/media_item.js b/app/javascript/flavours/glitch/features/account_gallery/components/media_item.js
index f2a661862..026136b2c 100644
--- a/app/javascript/flavours/glitch/features/account_gallery/components/media_item.js
+++ b/app/javascript/flavours/glitch/features/account_gallery/components/media_item.js
@@ -131,11 +131,17 @@ export default class MediaItem extends ImmutablePureComponent {
);
}
+ const icon = (
+
+
+
+ );
+
return (
);
diff --git a/app/javascript/flavours/glitch/styles/components/accounts.scss b/app/javascript/flavours/glitch/styles/components/accounts.scss
index 518eea5fa..c0340e3f8 100644
--- a/app/javascript/flavours/glitch/styles/components/accounts.scss
+++ b/app/javascript/flavours/glitch/styles/components/accounts.scss
@@ -343,6 +343,14 @@
border-radius: 4px;
overflow: hidden;
margin: 2px;
+
+ &__icons {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ font-size: 24px;
+ }
}
.notification__filter-bar,
--
cgit
From f59973cc85d9e84bd484ca7c75f108ccbb5d17df Mon Sep 17 00:00:00 2001
From: Eugen Rochko
Date: Fri, 3 May 2019 04:34:55 +0200
Subject: [Glitch] Make the "mark media as sensitive" button more obvious in
web UI
Port 05ef3462ba0af7b147a7cfa8de2735e99dc59ac5 to glitch-soc
Signed-off-by: Thibaut Girka
---
.../flavours/glitch/components/media_gallery.js | 2 +-
.../glitch/features/compose/components/options.js | 41 ----------------
.../features/compose/components/upload_form.js | 3 ++
.../compose/containers/options_container.js | 5 --
.../containers/sensitive_button_container.js | 54 ++++++++++++++++++++++
.../flavours/glitch/features/video/index.js | 2 +-
.../glitch/styles/components/composer.scss | 5 ++
7 files changed, 64 insertions(+), 48 deletions(-)
create mode 100644 app/javascript/flavours/glitch/features/compose/containers/sensitive_button_container.js
diff --git a/app/javascript/flavours/glitch/components/media_gallery.js b/app/javascript/flavours/glitch/components/media_gallery.js
index ab1cccc60..194800d52 100644
--- a/app/javascript/flavours/glitch/components/media_gallery.js
+++ b/app/javascript/flavours/glitch/components/media_gallery.js
@@ -345,7 +345,7 @@ export default class MediaGallery extends React.PureComponent {
}
if (visible) {
- spoilerButton = ;
+ spoilerButton = ;
} else {
spoilerButton = (
diff --git a/app/javascript/flavours/glitch/features/compose/components/options.js b/app/javascript/flavours/glitch/features/compose/components/options.js
index 8a760bd15..ee9730961 100644
--- a/app/javascript/flavours/glitch/features/compose/components/options.js
+++ b/app/javascript/flavours/glitch/features/compose/components/options.js
@@ -65,10 +65,6 @@ const messages = defineMessages({
defaultMessage: 'Public',
id: 'privacy.public.short',
},
- sensitive: {
- defaultMessage: 'Mark media as sensitive',
- id: 'compose_form.sensitive',
- },
spoiler: {
defaultMessage: 'Hide text behind warning',
id: 'compose_form.spoiler',
@@ -116,7 +112,6 @@ class ComposerOptions extends ImmutablePureComponent {
hasPoll: PropTypes.bool,
intl: PropTypes.object.isRequired,
onChangeAdvancedOption: PropTypes.func,
- onChangeSensitivity: PropTypes.func,
onChangeVisibility: PropTypes.func,
onTogglePoll: PropTypes.func,
onDoodleOpen: PropTypes.func,
@@ -126,7 +121,6 @@ class ComposerOptions extends ImmutablePureComponent {
onUpload: PropTypes.func,
privacy: PropTypes.string,
resetFileKey: PropTypes.number,
- sensitive: PropTypes.bool,
spoiler: PropTypes.bool,
};
@@ -175,7 +169,6 @@ class ComposerOptions extends ImmutablePureComponent {
hasPoll,
intl,
onChangeAdvancedOption,
- onChangeSensitivity,
onChangeVisibility,
onTogglePoll,
onModalClose,
@@ -183,7 +176,6 @@ class ComposerOptions extends ImmutablePureComponent {
onToggleSpoiler,
privacy,
resetFileKey,
- sensitive,
spoiler,
} = this.props;
@@ -264,39 +256,6 @@ class ComposerOptions extends ImmutablePureComponent {
title={intl.formatMessage(hasPoll ? messages.remove_poll : messages.add_poll)}
/>
)}
-
- {({ scale }) => (
-
-
-
- )}
-
)}
+
+ {!mediaIds.isEmpty() && }
);
}
diff --git a/app/javascript/flavours/glitch/features/compose/containers/options_container.js b/app/javascript/flavours/glitch/features/compose/containers/options_container.js
index e846cfbd5..2ac7ab8d8 100644
--- a/app/javascript/flavours/glitch/features/compose/containers/options_container.js
+++ b/app/javascript/flavours/glitch/features/compose/containers/options_container.js
@@ -2,7 +2,6 @@ import { connect } from 'react-redux';
import Options from '../components/options';
import {
changeComposeAdvancedOption,
- changeComposeSensitivity,
} from 'flavours/glitch/actions/compose';
import { addPoll, removePoll } from 'flavours/glitch/actions/compose';
import { closeModal, openModal } from 'flavours/glitch/actions/modal';
@@ -27,10 +26,6 @@ const mapDispatchToProps = (dispatch) => ({
dispatch(changeComposeAdvancedOption(option, value));
},
- onChangeSensitivity() {
- dispatch(changeComposeSensitivity());
- },
-
onTogglePoll() {
dispatch((_, getState) => {
if (getState().getIn(['compose', 'poll'])) {
diff --git a/app/javascript/flavours/glitch/features/compose/containers/sensitive_button_container.js b/app/javascript/flavours/glitch/features/compose/containers/sensitive_button_container.js
new file mode 100644
index 000000000..8f163979f
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/compose/containers/sensitive_button_container.js
@@ -0,0 +1,54 @@
+import React from 'react';
+import { connect } from 'react-redux';
+import PropTypes from 'prop-types';
+import classNames from 'classnames';
+import { changeComposeSensitivity } from 'flavours/glitch/actions/compose';
+import { injectIntl, defineMessages, FormattedMessage } from 'react-intl';
+import Icon from 'flavours/glitch/components/icon';
+
+const messages = defineMessages({
+ marked: { id: 'compose_form.sensitive.marked', defaultMessage: 'Media is marked as sensitive' },
+ unmarked: { id: 'compose_form.sensitive.unmarked', defaultMessage: 'Media is not marked as sensitive' },
+});
+
+const mapStateToProps = state => {
+ const spoilersAlwaysOn = state.getIn(['local_settings', 'always_show_spoilers_field']);
+ const spoilerText = state.getIn(['compose', 'spoiler_text']);
+ return {
+ active: state.getIn(['compose', 'sensitive']) || (spoilersAlwaysOn && spoilerText && spoilerText.length > 0),
+ disabled: state.getIn(['compose', 'spoiler']),
+ };
+};
+
+const mapDispatchToProps = dispatch => ({
+
+ onClick () {
+ dispatch(changeComposeSensitivity());
+ },
+
+});
+
+class SensitiveButton extends React.PureComponent {
+
+ static propTypes = {
+ active: PropTypes.bool,
+ disabled: PropTypes.bool,
+ onClick: PropTypes.func.isRequired,
+ intl: PropTypes.object.isRequired,
+ };
+
+ render () {
+ const { active, disabled, onClick, intl } = this.props;
+
+ return (
+
+
+
+
+
+ );
+ }
+
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(injectIntl(SensitiveButton));
diff --git a/app/javascript/flavours/glitch/features/video/index.js b/app/javascript/flavours/glitch/features/video/index.js
index 381485802..8291ff3c8 100644
--- a/app/javascript/flavours/glitch/features/video/index.js
+++ b/app/javascript/flavours/glitch/features/video/index.js
@@ -500,7 +500,7 @@ export default class Video extends React.PureComponent {
- {!onCloseVideo &&
}
+ {!onCloseVideo &&
}
{(!fullscreen && onOpenVideo) &&
}
{onCloseVideo &&
}
diff --git a/app/javascript/flavours/glitch/styles/components/composer.scss b/app/javascript/flavours/glitch/styles/components/composer.scss
index e5eb6e64f..81c700737 100644
--- a/app/javascript/flavours/glitch/styles/components/composer.scss
+++ b/app/javascript/flavours/glitch/styles/components/composer.scss
@@ -57,6 +57,11 @@
}
}
+.compose-form__sensitive-button {
+ padding: 10px;
+ padding-top: 0;
+}
+
.composer--reply {
margin: 0 0 10px;
border-radius: 4px;
--
cgit
From 9b1ef58c95f6b0dfa6d22572199fa3cf4337b095 Mon Sep 17 00:00:00 2001
From: Eugen Rochko
Date: Fri, 3 May 2019 20:44:20 +0200
Subject: [Glitch] Change font weight of sensitive button to 500
Port 63b1388fefff9414c2d0f9883f2d33f7c73284c6 to glitch-soc
Signed-off-by: Thibaut Girka
---
app/javascript/flavours/glitch/styles/components/composer.scss | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/app/javascript/flavours/glitch/styles/components/composer.scss b/app/javascript/flavours/glitch/styles/components/composer.scss
index 81c700737..86041da20 100644
--- a/app/javascript/flavours/glitch/styles/components/composer.scss
+++ b/app/javascript/flavours/glitch/styles/components/composer.scss
@@ -60,6 +60,11 @@
.compose-form__sensitive-button {
padding: 10px;
padding-top: 0;
+
+ .icon-button {
+ font-size: 14px;
+ font-weight: 500;
+ }
}
.composer--reply {
--
cgit
From f0865171fec447daae1e6d2eb162b3b7bfdfa85c Mon Sep 17 00:00:00 2001
From: Eugen Rochko
Date: Sat, 4 May 2019 22:52:54 +0200
Subject: Bump blurhash from 0.1.2 to 0.1.3 (#10700)
---
Gemfile.lock | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Gemfile.lock b/Gemfile.lock
index 7ab907f6d..59b34a185 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -99,7 +99,7 @@ GEM
rack (>= 0.9.0)
binding_of_caller (0.8.0)
debug_inspector (>= 0.0.1)
- blurhash (0.1.2)
+ blurhash (0.1.3)
ffi (~> 1.10.0)
bootsnap (1.4.4)
msgpack (~> 1.0)
--
cgit
From f2cf8144b427ba0639cea7976718b0ed77fbf183 Mon Sep 17 00:00:00 2001
From: Thibaut Girka
Date: Sat, 4 May 2019 23:05:43 +0200
Subject: Minor style fixes
---
app/javascript/flavours/glitch/styles/components/sensitive.scss | 1 +
1 file changed, 1 insertion(+)
diff --git a/app/javascript/flavours/glitch/styles/components/sensitive.scss b/app/javascript/flavours/glitch/styles/components/sensitive.scss
index 44b7ec981..67b01c886 100644
--- a/app/javascript/flavours/glitch/styles/components/sensitive.scss
+++ b/app/javascript/flavours/glitch/styles/components/sensitive.scss
@@ -9,6 +9,7 @@
}
.sensitive-marker {
+ margin: 0 3px;
border-radius: 2px;
padding: 2px 6px;
color: rgba($primary-text-color, 0.8);
--
cgit
From 21209c2b52d1ed1d1dee2dad2d725ffc7714701b Mon Sep 17 00:00:00 2001
From: Baptiste Gelez
Date: Sun, 5 May 2019 00:07:15 +0100
Subject: Make sure the instance banner is never cropped (#10702)
---
app/javascript/styles/mastodon/widgets.scss | 1 -
1 file changed, 1 deletion(-)
diff --git a/app/javascript/styles/mastodon/widgets.scss b/app/javascript/styles/mastodon/widgets.scss
index e736d7a7e..acaf5b024 100644
--- a/app/javascript/styles/mastodon/widgets.scss
+++ b/app/javascript/styles/mastodon/widgets.scss
@@ -4,7 +4,6 @@
&__img {
width: 100%;
- height: 167px;
position: relative;
overflow: hidden;
border-radius: 4px 4px 0 0;
--
cgit
From b7741ed732fb8ee580507085d84ec50a6e8d06a2 Mon Sep 17 00:00:00 2001
From: Aditoo17 <42938951+Aditoo17@users.noreply.github.com>
Date: Sun, 5 May 2019 08:33:33 +0200
Subject: I18n: Update Czech translation 🇨🇿 (#10704)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* I18n: Update Czech translation
* Tiny fix
---
app/javascript/mastodon/locales/cs.json | 2 ++
config/locales/cs.yml | 1 +
2 files changed, 3 insertions(+)
diff --git a/app/javascript/mastodon/locales/cs.json b/app/javascript/mastodon/locales/cs.json
index f98ea7f26..cbf303f3c 100644
--- a/app/javascript/mastodon/locales/cs.json
+++ b/app/javascript/mastodon/locales/cs.json
@@ -77,6 +77,7 @@
"compose_form.poll.remove_option": "Odstranit tuto volbu",
"compose_form.publish": "Tootnout",
"compose_form.publish_loud": "{publish}!",
+ "compose_form.sensitive.hide": "Označit média jako citlivá",
"compose_form.sensitive.marked": "Média jsou označena jako citlivá",
"compose_form.sensitive.unmarked": "Média nejsou označena jako citlivá",
"compose_form.spoiler.marked": "Text je skrytý za varováním",
@@ -209,6 +210,7 @@
"lightbox.close": "Zavřít",
"lightbox.next": "Další",
"lightbox.previous": "Předchozí",
+ "lightbox.view_context": "Zobrazit kontext",
"lists.account.add": "Přidat do seznamu",
"lists.account.remove": "Odebrat ze seznamu",
"lists.delete": "Smazat seznam",
diff --git a/config/locales/cs.yml b/config/locales/cs.yml
index ca456b7ef..5d05a13d6 100644
--- a/config/locales/cs.yml
+++ b/config/locales/cs.yml
@@ -273,6 +273,7 @@ cs:
created_msg: Blokace domény se právě vyřizuje
destroyed_msg: Blokace domény byla zrušena
domain: Doména
+ existing_domain_block_html: Pro účet %{name} jste již nastavil/a přísnější omezení, musíte jej nejdříve odblokovat .
new:
create: Vytvořit blokaci
hint: Blokace domény nezakáže vytváření záznamů účtů v databázi, ale bude na tyto účty zpětně a automaticky aplikovat specifické metody moderování.
--
cgit
From fc192b882f43d1dd3d61c68dc3fb327432b9388d Mon Sep 17 00:00:00 2001
From: Marek Ľach
Date: Sun, 5 May 2019 10:25:35 +0200
Subject: Minor Slovak locale update (#10705)
---
config/locales/sk.yml | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/config/locales/sk.yml b/config/locales/sk.yml
index b6a966fa7..d859d16b1 100644
--- a/config/locales/sk.yml
+++ b/config/locales/sk.yml
@@ -268,10 +268,11 @@ sk:
week_users_active: aktívni tento týždeň
week_users_new: užívateľov počas tohto týždňa
domain_blocks:
- add_new: Pridaj nové doménové blokovanie
+ add_new: Blokuj novú doménu
created_msg: Doména je v štádiu blokovania
destroyed_msg: Blokovanie domény bolo zrušené
domain: Doména
+ existing_domain_block_html: Pre účet %{name} si už nahodil/a přísnejšie obmedzenie, najskôr ho teda musíš odblokovať .
new:
create: Vytvor blokovanie domény
hint: Blokovanie domény stále dovolí vytvárať nové účty v databázi, ale tieto budú spätne automaticky moderované.
@@ -299,7 +300,7 @@ sk:
silence: Zruš stíšenie všetkých existujúcich účtov z tejto domény
suspend: Zruš suspendáciu všetkých existujúcich účtov z tejto domény
title: Zruš blokovanie domény %{domain}
- undo: Vrátiť späť
+ undo: Vráť späť
undo: Odvolaj blokovanie domény
email_domain_blocks:
add_new: Pridaj nový
--
cgit
From eef8802325d75b2668b131a765c5555f4be8c2ce Mon Sep 17 00:00:00 2001
From: Baptiste Gelez
Date: Sun, 5 May 2019 00:07:15 +0100
Subject: [Glitch] Make sure the instance banner is never cropped
Port 21209c2b52d1ed1d1dee2dad2d725ffc7714701b to glitch-soc
Signed-off-by: Thibaut Girka
---
app/javascript/flavours/glitch/styles/widgets.scss | 1 -
1 file changed, 1 deletion(-)
diff --git a/app/javascript/flavours/glitch/styles/widgets.scss b/app/javascript/flavours/glitch/styles/widgets.scss
index e736d7a7e..acaf5b024 100644
--- a/app/javascript/flavours/glitch/styles/widgets.scss
+++ b/app/javascript/flavours/glitch/styles/widgets.scss
@@ -4,7 +4,6 @@
&__img {
width: 100%;
- height: 167px;
position: relative;
overflow: hidden;
border-radius: 4px 4px 0 0;
--
cgit
From 8383222026354d97560825c23ed115b3c0ce3574 Mon Sep 17 00:00:00 2001
From: Kirishima21
Date: Mon, 6 May 2019 03:31:21 +0900
Subject: fix pinned columns ’s Media-only button
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Media-only toggles could not be used on pinned columns.
The commit fixes this problem.
---
.../features/community_timeline/containers/column_settings_container.js | 1 +
1 file changed, 1 insertion(+)
diff --git a/app/javascript/flavours/glitch/features/community_timeline/containers/column_settings_container.js b/app/javascript/flavours/glitch/features/community_timeline/containers/column_settings_container.js
index 16a963dde..b892f08ad 100644
--- a/app/javascript/flavours/glitch/features/community_timeline/containers/column_settings_container.js
+++ b/app/javascript/flavours/glitch/features/community_timeline/containers/column_settings_container.js
@@ -1,5 +1,6 @@
import { connect } from 'react-redux';
import ColumnSettings from '../components/column_settings';
+import { changeColumnParams } from 'flavours/glitch/actions/columns';
import { changeSetting } from 'flavours/glitch/actions/settings';
const mapStateToProps = (state, { columnId }) => {
--
cgit
From 89d2859296bc5a57a8db07be86239cc938a3f691 Mon Sep 17 00:00:00 2001
From: Thibaut Girka
Date: Sun, 5 May 2019 21:09:45 +0200
Subject: Fix content warning button displaying a link icon when no status card
is set
---
.../flavours/glitch/features/status/components/detailed_status.js | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/app/javascript/flavours/glitch/features/status/components/detailed_status.js b/app/javascript/flavours/glitch/features/status/components/detailed_status.js
index 0b162ed36..03d98fde8 100644
--- a/app/javascript/flavours/glitch/features/status/components/detailed_status.js
+++ b/app/javascript/flavours/glitch/features/status/components/detailed_status.js
@@ -112,7 +112,7 @@ export default class DetailedStatus extends ImmutablePureComponent {
return null;
}
- let media = '';
+ let media = null;
let mediaIcon = null;
let applicationLink = '';
let reblogLink = '';
@@ -163,8 +163,8 @@ export default class DetailedStatus extends ImmutablePureComponent {
);
mediaIcon = 'picture-o';
}
- } else {
- media = ;
+ } else if (status.get('card')) {
+ media = ;
mediaIcon = 'link';
}
--
cgit