From c9fd6f386c5ae0938f1f9c2d1134508e66231e23 Mon Sep 17 00:00:00 2001 From: TheInventrix Date: Mon, 7 Aug 2017 17:50:15 -0600 Subject: unify short description styling (#4548) apply same style class to the About description on both the landing page and the about/more page --- app/javascript/styles/about.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app/javascript') diff --git a/app/javascript/styles/about.scss b/app/javascript/styles/about.scss index d409c8214..62143246f 100644 --- a/app/javascript/styles/about.scss +++ b/app/javascript/styles/about.scss @@ -484,7 +484,7 @@ padding: 0; } - .learn-more-cta { + .about-short { background: darken($ui-base-color, 4%); padding: 50px 0; } -- cgit From ec3be87a2b75b6a84a013bab9bf4d80b0f6452a7 Mon Sep 17 00:00:00 2001 From: 雨宮美羽 Date: Tue, 8 Aug 2017 08:48:19 -0500 Subject: improve zh-CN translations (#4557) --- app/javascript/mastodon/locales/zh-CN.json | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) (limited to 'app/javascript') diff --git a/app/javascript/mastodon/locales/zh-CN.json b/app/javascript/mastodon/locales/zh-CN.json index d0c4b3d1b..e7c431454 100644 --- a/app/javascript/mastodon/locales/zh-CN.json +++ b/app/javascript/mastodon/locales/zh-CN.json @@ -5,7 +5,7 @@ "account.edit_profile": "修改个人资料", "account.follow": "关注", "account.followers": "关注者", - "account.follows": "正关注", + "account.follows": "正在关注", "account.follows_you": "关注你", "account.media": "Media", "account.mention": "提及 @{name}", @@ -13,19 +13,19 @@ "account.posts": "嘟文", "account.report": "举报 @{name}", "account.requested": "等待审批", - "account.share": "Share @{name}'s profile", + "account.share": "分享 @{name}的个人资料", "account.unblock": "解除对 @{name} 的屏蔽", - "account.unblock_domain": "Unhide {domain}", + "account.unblock_domain": "解除封锁 {domain}", "account.unfollow": "取消关注", "account.unmute": "取消 @{name} 的静音", - "account.view_full_profile": "View full profile", + "account.view_full_profile": "查看完整资料", "boost_modal.combo": "如你想在下次路过时显示,请按{combo},", - "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", + "bundle_column_error.body": "载入组件出错。", + "bundle_column_error.retry": "再次尝试", + "bundle_column_error.title": "网络错误", + "bundle_modal_error.close": "关闭", + "bundle_modal_error.message": "载入组件出错。", + "bundle_modal_error.retry": "再次尝试", "column.blocks": "屏蔽用户", "column.community": "本站时间轴", "column.favourites": "赞过的嘟文", @@ -34,7 +34,7 @@ "column.mutes": "被静音的用户", "column.notifications": "通知", "column.public": "跨站公共时间轴", - "column_back_button.label": "Back", + "column_back_button.label": "返回", "column_header.hide_settings": "Hide settings", "column_header.moveLeft_settings": "Move column to the left", "column_header.moveRight_settings": "Move column to the right", @@ -61,8 +61,8 @@ "confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable.", "confirmations.mute.confirm": "静音", "confirmations.mute.message": "想好了,真的要静音 {name}?", - "confirmations.unfollow.confirm": "Unfollow", - "confirmations.unfollow.message": "Are you sure you want to unfollow {name}?", + "confirmations.unfollow.confirm": "取消关注", + "confirmations.unfollow.message": "确定要取消关注 {name}吗?", "emoji_button.activity": "活动", "emoji_button.flags": "旗帜", "emoji_button.food": "食物和饮料", @@ -86,7 +86,7 @@ "getting_started.faq": "FAQ", "getting_started.heading": "开始使用", "getting_started.open_source_notice": "Mastodon 是一个开放源码的软件。你可以在官方 GitHub ({github}) 贡献或者回报问题。", - "getting_started.userguide": "User Guide", + "getting_started.userguide": "用户指南", "home.column_settings.advanced": "高端", "home.column_settings.basic": "基本", "home.column_settings.filter_regex": "使用正则表达式 (regex) 过滤", -- cgit From 61bfce5aa939add6829a216b4ae8e3c660d85791 Mon Sep 17 00:00:00 2001 From: Ondřej Hruška Date: Tue, 8 Aug 2017 22:13:04 +0200 Subject: add missing @ to the onboarding modal (#4560) --- app/javascript/mastodon/features/ui/components/onboarding_modal.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app/javascript') diff --git a/app/javascript/mastodon/features/ui/components/onboarding_modal.js b/app/javascript/mastodon/features/ui/components/onboarding_modal.js index 3d59785e2..7905bca2e 100644 --- a/app/javascript/mastodon/features/ui/components/onboarding_modal.js +++ b/app/javascript/mastodon/features/ui/components/onboarding_modal.js @@ -30,7 +30,7 @@ const PageOne = ({ acct, domain }) => (

-

{acct}@{domain} }} />

+

@{acct}@{domain} }} />

); -- cgit From 820099813fe4ff824b939cc60c690be369997c59 Mon Sep 17 00:00:00 2001 From: Ondřej Hruška Date: Wed, 9 Aug 2017 00:21:58 +0200 Subject: add scrollTop to ui/components/column (#4563) --- app/javascript/mastodon/features/ui/components/column.js | 11 +++++++++++ 1 file changed, 11 insertions(+) (limited to 'app/javascript') diff --git a/app/javascript/mastodon/features/ui/components/column.js b/app/javascript/mastodon/features/ui/components/column.js index 9031c16fc..15538ea38 100644 --- a/app/javascript/mastodon/features/ui/components/column.js +++ b/app/javascript/mastodon/features/ui/components/column.js @@ -25,6 +25,17 @@ export default class Column extends React.PureComponent { this._interruptScrollAnimation = scrollTop(scrollable); } + scrollTop () { + const scrollable = this.node.querySelector('.scrollable'); + + if (!scrollable) { + return; + } + + this._interruptScrollAnimation = scrollTop(scrollable); + } + + handleScroll = debounce(() => { if (typeof this._interruptScrollAnimation !== 'undefined') { this._interruptScrollAnimation(); -- cgit From b1c8a702a457ea04c3800ddb915032a0800a2048 Mon Sep 17 00:00:00 2001 From: Ondřej Hruška Date: Wed, 9 Aug 2017 00:22:26 +0200 Subject: Add favourited toot to favourites column (#4562) * Add faved toot to faves column * renamed append to prepend for clarity --- app/javascript/mastodon/reducers/status_lists.js | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'app/javascript') diff --git a/app/javascript/mastodon/reducers/status_lists.js b/app/javascript/mastodon/reducers/status_lists.js index bbc973302..580cc17d2 100644 --- a/app/javascript/mastodon/reducers/status_lists.js +++ b/app/javascript/mastodon/reducers/status_lists.js @@ -3,6 +3,7 @@ import { FAVOURITED_STATUSES_EXPAND_SUCCESS, } from '../actions/favourites'; import { Map as ImmutableMap, List as ImmutableList } from 'immutable'; +import { FAVOURITE_SUCCESS } from '../actions/interactions'; const initialState = ImmutableMap({ favourites: ImmutableMap({ @@ -27,12 +28,20 @@ const appendToList = (state, listType, statuses, next) => { })); }; +const prependOneToList = (state, listType, status) => { + return state.update(listType, listMap => listMap.withMutations(map => { + map.set('items', map.get('items').unshift(status.get('id'))); + })); +}; + export default function statusLists(state = initialState, action) { switch(action.type) { case FAVOURITED_STATUSES_FETCH_SUCCESS: return normalizeList(state, 'favourites', action.statuses, action.next); case FAVOURITED_STATUSES_EXPAND_SUCCESS: return appendToList(state, 'favourites', action.statuses, action.next); + case FAVOURITE_SUCCESS: + return prependOneToList(state, 'favourites', action.status); default: return state; } -- cgit From d9a1fb134a407aaa7fcff5be285d7414a0cb43ed Mon Sep 17 00:00:00 2001 From: Yamagishi Kazutoshi Date: Thu, 10 Aug 2017 20:41:12 +0900 Subject: Fix emoji picker scrollbar style (#4572) --- app/javascript/styles/components.scss | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'app/javascript') diff --git a/app/javascript/styles/components.scss b/app/javascript/styles/components.scss index 77b06b2d0..f66be5111 100644 --- a/app/javascript/styles/components.scss +++ b/app/javascript/styles/components.scss @@ -394,6 +394,11 @@ bottom: -1px; right: 8px; } + + ::-webkit-scrollbar-track:hover, + ::-webkit-scrollbar-track:active { + background-color: rgba($base-overlay-background, 0.3); + } } } -- cgit From d0a217eb92aec7278685e17b04a1e109081785db Mon Sep 17 00:00:00 2001 From: Sylvhem Date: Sat, 12 Aug 2017 01:33:30 +0200 Subject: Minor fixes in the French translation (#4580) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Ajout de traductions manquantes Ajoute des traductions pour les chaînes n’en ayant pas en version 1.5.1. Add translations for the strings that are missing them in 1.5.1. * Remplace « ' » par « ’ » Retire de la traduction les apostrophes droites « ' » (U+0027) au profit des apostrophes typographiques « ’ » (U+2019). En typographie française, les apostrophes typographiques sont utilisées à la place des apostrophes droites. La traduction était incohérente et utilisait les deux. Remove from the translation all the vertical apostrophes (U+0027) in favor of the curly ones (U+2019). In French typography, typographic apostrophes are used instead of vertical ones. The translation was incoherent and used both. * Ajout d’espaces insécables Ajoute des espaces insécables suivant les régles nécessaires en typographie française. Add non-breaking spaces following rules of French typography. * Remplace « status » par « statut » Remplace le mot anglais « status » par sa traduction française « statut ». Replace the English word "status" by its French translation "statut". * Correction de la politique de confidentialité Apporte diverses corrections à la traduction de la politique de confidentialité. Add various fixes to the privacy policy's translation. * Remplace « mentionné » par « mentionné·e » Harmonise la traduction en remplaçant « mentionné » par sa forme épicène. Harmonize the translation by replacing "mentionné" (sure) by its epicene form. * Remplace « Coup d’œil » par « Jeter un coup d’œil… » Remplace la première traduction par une forme plus proche de la version originelle. Replace the first translation by something closer to the original version. * Remplace « Bon Appétoot ! » par « Bon appouetit ! » Remplace « Bon Appétoot ! » par « Bon appouetit ! » pour essayer de conserver le jeu de mot. Replace « Bon Appétoot ! » by « Bon appouetit ! » to keep the pun. * Remplace « Bon Appétoot ! » par « Bon appouetit ! » (2) Remplace « Bon Appétoot ! » par « Bon appouetit ! » pour essayer de conserver le jeu de mot. Replace « Bon Appétoot ! » by « Bon appouetit ! » to keep the pun.f * Corrections Corrige des fautes d’orthographe et change « appouetit » pour « appouétit ». Correct some mistakes and change "appouetit" to "appouétit". --- app/javascript/mastodon/locales/fr.json | 14 ++++++------ config/locales/fr.yml | 40 ++++++++++++++++----------------- config/locales/simple_form.fr.yml | 5 +++++ 3 files changed, 32 insertions(+), 27 deletions(-) (limited to 'app/javascript') diff --git a/app/javascript/mastodon/locales/fr.json b/app/javascript/mastodon/locales/fr.json index f3f0d0463..34a89a69f 100644 --- a/app/javascript/mastodon/locales/fr.json +++ b/app/javascript/mastodon/locales/fr.json @@ -20,11 +20,11 @@ "account.unmute": "Ne plus masquer", "account.view_full_profile": "Afficher le profil complet", "boost_modal.combo": "Vous pouvez appuyer sur {combo} pour pouvoir passer ceci, la prochaine fois", - "bundle_column_error.body": "Une erreur s'est produite lors du chargement de ce composant.", + "bundle_column_error.body": "Une erreur s’est produite lors du chargement de ce composant.", "bundle_column_error.retry": "Réessayer", "bundle_column_error.title": "Erreur réseau", "bundle_modal_error.close": "Fermer", - "bundle_modal_error.message": "Une erreur s'est produite lors du chargement de ce composant.", + "bundle_modal_error.message": "Une erreur s’est produite lors du chargement de ce composant.", "bundle_modal_error.retry": "Réessayer", "column.blocks": "Comptes bloqués", "column.community": "Fil public local", @@ -48,7 +48,7 @@ "compose_form.placeholder": "Qu’avez-vous en tête ?", "compose_form.privacy_disclaimer": "Votre statut privé va être transmis aux personnes mentionnées sur {domains}. Avez-vous confiance en {domainsCount, plural, one {ce serveur} other {ces serveurs}} pour ne pas divulguer votre statut ? Les statuts privés ne fonctionnent que sur les instances de Mastodon. Si {domains} {domainsCount, plural, one {n’est pas une instance de Mastodon} other {ne sont pas des instances de Mastodon}}, il n’y aura aucune indication que votre statut est privé, et il pourrait être partagé ou rendu visible d’une autre manière à d’autres personnes imprévues.", "compose_form.publish": "Pouet ", - "compose_form.publish_loud": "{publish}!", + "compose_form.publish_loud": "{publish} !", "compose_form.sensitive": "Marquer le média comme sensible", "compose_form.spoiler": "Masquer le texte derrière un avertissement", "compose_form.spoiler_placeholder": "Écrivez ici votre avertissement", @@ -62,7 +62,7 @@ "confirmations.mute.confirm": "Masquer", "confirmations.mute.message": "Confirmez vous le masquage de {name} ?", "confirmations.unfollow.confirm": "Ne plus suivre", - "confirmations.unfollow.message": "Vous voulez-vous arrêter de suivre {name} ?", + "confirmations.unfollow.message": "Vous voulez-vous arrêter de suivre {name} ?", "emoji_button.activity": "Activités", "emoji_button.flags": "Drapeaux", "emoji_button.food": "Boire et manger", @@ -134,8 +134,8 @@ "onboarding.page_one.welcome": "Bienvenue sur Mastodon !", "onboarding.page_six.admin": "L’administrateur⋅trice de votre instance est {admin}", "onboarding.page_six.almost_done": "Nous y sommes presque…", - "onboarding.page_six.appetoot": "Bon Appétoot!", - "onboarding.page_six.apps_available": "De nombreuses {apps} sont disponibles pour iOS, Android et autres. Et maintenant… Bon Appétoot!", + "onboarding.page_six.appetoot": "Bon appouétit !", + "onboarding.page_six.apps_available": "De nombreuses {apps} sont disponibles pour iOS, Android et autres. Et maintenant… Bon appouétit !", "onboarding.page_six.github": "Mastodon est un logiciel libre, gratuit et open-source. Vous pouvez rapporter des bogues, suggérer des fonctionnalités, ou contribuer à son développement sur {github}.", "onboarding.page_six.guidelines": "règles de la communauté", "onboarding.page_six.read_guidelines": "S’il vous plaît, n’oubliez pas de lire les {guidelines} !", @@ -159,7 +159,7 @@ "report.target": "Signalement", "search.placeholder": "Rechercher", "search_results.total": "{count, number} {count, plural, one {résultat} other {résultats}}", - "standalone.public_title": "Coup d'œil", + "standalone.public_title": "Jeter un coup d’œil…", "status.cannot_reblog": "Cette publication ne peut être boostée", "status.delete": "Effacer", "status.favourite": "Ajouter aux favoris", diff --git a/config/locales/fr.yml b/config/locales/fr.yml index d7aa41497..38be6dce8 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -187,7 +187,7 @@ fr: nsfw_off: NSFW OFF nsfw_on: NSFW ON execute: Exécuter - failed_to_execute: Erreur d'exécution + failed_to_execute: Erreur d’exécution media: hide: Masquer les médias show: Montrer les médias @@ -231,11 +231,11 @@ fr: error: Malheureusement, il y a eu une erreur en cherchant les détails du compte distant follow: Suivre follow_request: 'Vous avez demandé à suivre:' - following: 'Youpi! Vous suivez :' + following: 'Youpi ! Vous suivez :' post_follow: close: Ou bien, vous pouvez fermer cette fenêtre. - return: Retour au profil de l'utilisateur⋅trice - web: Retour à l'interface web + return: Retour au profil de l’utilisateur⋅trice + web: Retour à l’interface web title: Suivre %{acct} datetime: distance_in_words: @@ -282,7 +282,7 @@ fr: storage: Médias stockés followers: domain: Domaine - explanation_html: Si vous voulez être sûr⋅e que vos status restent privés, vous devez savoir qui vous suit. Vos status privés seront diffusés à toutes les instances des utilisateur⋅ice⋅s qui vous suivent. Vous voudrez peut-être les passer en revue et les supprimer si vous n’êtes pas sûr⋅e que votre vie privée sera respectée par l’administration ou le logiciel de ces instances. + explanation_html: Si vous voulez être sûr⋅e que vos statuts restent privés, vous devez savoir qui vous suit. Vos statuts privés seront diffusés à toutes les instances des utilisateur⋅ice⋅s qui vous suivent. Vous voudrez peut-être les passer en revue et les supprimer si vous n’êtes pas sûr⋅e que votre vie privée sera respectée par l’administration ou le logiciel de ces instances. followers_count: Nombre d’abonné⋅es lock_link: Rendez votre compte privé purge: Retirer de la liste d’abonné⋅es @@ -290,7 +290,7 @@ fr: one: Suppression des abonné⋅es venant d’un domaine en cours… other: Suppression des abonné⋅es venant de %{count} domaines en cours… true_privacy_html: Soyez conscient⋅es qu’une vraie confidentialité ne peut être atteinte que par un chiffrement de bout-en-bout. - unlocked_warning_html: N’importe qui peut vous suivre et voir vos status privés. %{lock_link} afin de pouvoir vérifier et rejeter des abonné⋅es. + unlocked_warning_html: N’importe qui peut vous suivre et voir vos statuts privés. %{lock_link} afin de pouvoir vérifier et rejeter des abonné⋅es. unlocked_warning_title: Votre compte n’est pas privé generic: changes_saved_msg: Les modifications ont été enregistrées avec succès ! @@ -311,7 +311,7 @@ fr: landing_strip_signup_html: Si ce n’est pas le cas, vous pouvez en créer un ici. media_attachments: validations: - images_and_video: Impossible de joindre une vidéo à un status contenant déjà des images + images_and_video: Impossible de joindre une vidéo à un statut contenant déjà des images too_many: Impossible de joindre plus de 4 fichiers notification_mailer: digest: @@ -334,30 +334,30 @@ fr: subject: 'Abonné⋅es en attente : %{name}' mention: body: "%{name} vous a mentionné⋅e dans :" - subject: "%{name} vous a mentionné" + subject: "%{name} vous a mentionné·e" reblog: - body: "%{name} a partagé votre status :" - subject: "%{name} a partagé votre status" + body: "%{name} a partagé votre statut :" + subject: "%{name} a partagé votre statut" pagination: next: Suivant prev: Précédent push_notifications: favourite: - title: "%{name} à mis votre status en favori" + title: "%{name} à mis votre statut en favori" follow: title: "%{name} vous suit" mention: action_boost: Partager action_expand: Montrer plus action_favourite: Ajouter aux favoris - title: "%{name} vous a mentionné" + title: "%{name} vous a mentionné·e" reblog: - title: "%{name} a partagé⋅e votre status" + title: "%{name} a partagé⋅e votre statut" subscribed: body: Vous pouvez désormais recevoir des notifications push. title: Abonnements aux notifications push remote_follow: - acct: Entrez votre pseudo@instance depuis lequel vous voulez suivre ce⋅tte utilisateur⋅trice + acct: Entrez votre pseudo@instance depuis lequel vous voulez suivre ce⋅tte utilisateur⋅rice missing_resource: L’URL de redirection n’a pas pu être trouvée proceed: Continuez pour suivre prompt: 'Vous allez suivre :' @@ -417,18 +417,18 @@ fr: show_more: Afficher plus visibilities: private: Abonné⋅es uniquement - private_long: Seul⋅es vos abonné⋅es verront vos status + private_long: Seul⋅es vos abonné⋅es verront vos statuts public: Public - public_long: Tout le monde peut voir vos status + public_long: Tout le monde peut voir vos statuts unlisted: Public sans être affiché sur le fil public - unlisted_long: Tout le monde peut voir vos status mais ils ne seront pas sur listés sur les fils publics + unlisted_long: Tout le monde peut voir vos statuts mais ils ne seront pas sur listés sur les fils publics stream_entries: click_to_show: Cliquer pour afficher reblogged: partagé sensitive_content: Contenu sensible terms: - body_html: "

Politique de confidentialité

\n\n

Quelles données collectons-nous?

\n\n

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

\n\n

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

\n\n

Lors de l'inscription et de la publication de statuts, nous enregistrons l'adresse IP de laquelle le(s) status viennent. Nous pouvons également conserver des historiques serveurs qui contiendront l'adresse IP de chaque requête adressée à notre serveur.

\n\n

Que faisons-nous avec vos données?

\n\n

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

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

Comment protégeons-nous vos données?

\n \n

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

\n\n

Quelle est notre politique de conservation des données?

\n\n

Nous nous efforçons de:

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

Utilisons nous des \"cookies\"?

\n\n

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

\n\n

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

\n\n

Divulguons-nous des données à des acteurs tiers ?

\n\n

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

\n\n

Liens vers des acteurs tiers

\n\n

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

\n\n

Children's Online Privacy Protection Act

\n\n

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

\n\n

Votre consentement

\n\n

En utilisant notre site, vous consentez à la politique de confiedentialité de notre site Web.

\n\n

Changements de notre politique de confidentialité

\n\n

Si nous décidons d'apporter des changements à notre politique de confidentialité, nous les mettrons à disposition sur cette page.

\n\n

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

\n\n

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

.\n" - title: "%{instance} Conditions d'utilisations et Politique de confidentialité" + body_html: "

Politique de confidentialité

\n\n

Quelles données collectons-nous ?

\n\n

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

\n\n

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

\n\n

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

\n\n

Que faisons-nous avec vos données ?

\n\n

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

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

Comment protégeons-nous vos données ?

\n \n

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

\n\n

Quelle est notre politique de conservation des données ?

\n\n

Nous nous efforçons de :

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

Utilisons nous des « cookies » ?

\n\n

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

\n\n

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

\n\n

Divulguons-nous des données à des acteurs tiers ?

\n\n

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

\n\n

Liens vers des acteurs tiers

\n\n

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

\n\n

Children's Online Privacy Protection Act

\n\n

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

\n\n

Votre consentement

\n\n

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

\n\n

Changements de notre politique de confidentialité

\n\n

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

\n\n

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

\n\n

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

\n" + title: "%{instance} Conditions d’utilisations et politique de confidentialité" time: formats: default: "%d %b %Y, %H:%M" @@ -451,4 +451,4 @@ fr: users: invalid_email: L’adresse courriel est invalide invalid_otp_token: Le code d’authentification à deux facteurs est invalide - signed_in_as: 'Connecté·e en tant que :' + signed_in_as: 'Connecté·e en tant que :' diff --git a/config/locales/simple_form.fr.yml b/config/locales/simple_form.fr.yml index 8717a4abd..adfb1a875 100644 --- a/config/locales/simple_form.fr.yml +++ b/config/locales/simple_form.fr.yml @@ -12,6 +12,7 @@ fr: note: one: 1 caractère restant other: %{count} caractères restants + setting_noindex: Affecte votre profil public ainsi que vos statuts imports: data: Un fichier CSV généré par une autre instance de Mastodon sessions: @@ -27,6 +28,7 @@ fr: data: Données display_name: Nom public email: Adresse courriel + filtered_languages: Langues filtrées header: Image d’en-tête locale: Langue locked: Verrouiller le compte @@ -37,8 +39,11 @@ fr: setting_auto_play_gif: Lire automatiquement les GIFs animés setting_boost_modal: Afficher un dialogue de confirmation avant de partager setting_default_privacy: Confidentialité des statuts + setting_default_sensitive: Toujours marquer les médias comme sensibles setting_delete_modal: Afficher un dialogue de confirmation avant de supprimer un pouet + setting_noindex: Demander aux moteurs de recherche de ne pas indexer vos informations personnelles setting_system_font_ui: Utiliser la police par défaut du système + setting_unfollow_modal: Afficher un dialogue de confirmation avant de vous désabonner d’un compte severity: Séverité type: Type d’import username: Identifiant -- cgit From 3c6503038ecad20f1b8fa0c9ea7e46087c6e3f22 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 14 Aug 2017 04:53:31 +0200 Subject: Add protocol handler. Handle follow intents (#4511) * Add protocol handler. Handle follow intents * Add share intent * Improve code in intents controller * Adjust share form CSS --- app/controllers/intents_controller.rb | 18 ++++++++++ app/controllers/shares_controller.rb | 25 ++++++++++++++ .../mastodon/containers/compose_container.js | 39 ++++++++++++++++++++++ app/javascript/mastodon/containers/mastodon.js | 5 +++ .../mastodon/features/standalone/compose/index.js | 18 ++++++++++ app/javascript/mastodon/reducers/compose.js | 12 ++++++- app/javascript/packs/share.js | 24 +++++++++++++ app/javascript/styles/containers.scss | 15 +++++++++ app/presenters/initial_state_presenter.rb | 3 +- app/serializers/initial_state_serializer.rb | 2 ++ app/views/shares/show.html.haml | 5 +++ config/routes.rb | 4 ++- 12 files changed, 167 insertions(+), 3 deletions(-) create mode 100644 app/controllers/intents_controller.rb create mode 100644 app/controllers/shares_controller.rb create mode 100644 app/javascript/mastodon/containers/compose_container.js create mode 100644 app/javascript/mastodon/features/standalone/compose/index.js create mode 100644 app/javascript/packs/share.js create mode 100644 app/views/shares/show.html.haml (limited to 'app/javascript') diff --git a/app/controllers/intents_controller.rb b/app/controllers/intents_controller.rb new file mode 100644 index 000000000..504befd1f --- /dev/null +++ b/app/controllers/intents_controller.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +class IntentsController < ApplicationController + def show + uri = Addressable::URI.parse(params[:uri]) + + if uri.scheme == 'web+mastodon' + case uri.host + when 'follow' + return redirect_to authorize_follow_path(acct: uri.query_values['uri'].gsub(/\Aacct:/, '')) + when 'share' + return redirect_to share_path(text: uri.query_values['text']) + end + end + + not_found + end +end diff --git a/app/controllers/shares_controller.rb b/app/controllers/shares_controller.rb new file mode 100644 index 000000000..d70d66ff8 --- /dev/null +++ b/app/controllers/shares_controller.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +class SharesController < ApplicationController + layout 'public' + + before_action :authenticate_user! + + def show + serializable_resource = ActiveModelSerializers::SerializableResource.new(InitialStatePresenter.new(initial_state_params), serializer: InitialStateSerializer) + @initial_state_json = serializable_resource.to_json + end + + private + + def initial_state_params + { + settings: Web::Setting.find_by(user: current_user)&.data || {}, + push_subscription: current_account.user.web_push_subscription(current_session), + current_account: current_account, + token: current_session.token, + admin: Account.find_local(Setting.site_contact_username), + text: params[:text], + } + end +end diff --git a/app/javascript/mastodon/containers/compose_container.js b/app/javascript/mastodon/containers/compose_container.js new file mode 100644 index 000000000..db452d03a --- /dev/null +++ b/app/javascript/mastodon/containers/compose_container.js @@ -0,0 +1,39 @@ +import React from 'react'; +import { Provider } from 'react-redux'; +import PropTypes from 'prop-types'; +import configureStore from '../store/configureStore'; +import { hydrateStore } from '../actions/store'; +import { IntlProvider, addLocaleData } from 'react-intl'; +import { getLocale } from '../locales'; +import Compose from '../features/standalone/compose'; + +const { localeData, messages } = getLocale(); +addLocaleData(localeData); + +const store = configureStore(); +const initialStateContainer = document.getElementById('initial-state'); + +if (initialStateContainer !== null) { + const initialState = JSON.parse(initialStateContainer.textContent); + store.dispatch(hydrateStore(initialState)); +} + +export default class TimelineContainer extends React.PureComponent { + + static propTypes = { + locale: PropTypes.string.isRequired, + }; + + render () { + const { locale } = this.props; + + return ( + + + + + + ); + } + +} diff --git a/app/javascript/mastodon/containers/mastodon.js b/app/javascript/mastodon/containers/mastodon.js index 87ab6023c..fe534d1c1 100644 --- a/app/javascript/mastodon/containers/mastodon.js +++ b/app/javascript/mastodon/containers/mastodon.js @@ -89,6 +89,11 @@ export default class Mastodon extends React.PureComponent { Notification.requestPermission(); } + if (typeof navigator.registerProtocolHandler !== 'undefined') { + const handlerUrl = window.location.protocol + '//' + window.location.host + '/intent?uri=%s'; + navigator.registerProtocolHandler('web+mastodon', handlerUrl, 'Mastodon'); + } + store.dispatch(showOnboardingOnce()); } diff --git a/app/javascript/mastodon/features/standalone/compose/index.js b/app/javascript/mastodon/features/standalone/compose/index.js new file mode 100644 index 000000000..96d07fefb --- /dev/null +++ b/app/javascript/mastodon/features/standalone/compose/index.js @@ -0,0 +1,18 @@ +import React from 'react'; +import ComposeFormContainer from '../../compose/containers/compose_form_container'; +import NotificationsContainer from '../../ui/containers/notifications_container'; +import LoadingBarContainer from '../../ui/containers/loading_bar_container'; + +export default class Compose extends React.PureComponent { + + render () { + return ( +
+ + + +
+ ); + } + +} diff --git a/app/javascript/mastodon/reducers/compose.js b/app/javascript/mastodon/reducers/compose.js index e137b774e..34f5dab7f 100644 --- a/app/javascript/mastodon/reducers/compose.js +++ b/app/javascript/mastodon/reducers/compose.js @@ -141,10 +141,20 @@ const privacyPreference = (a, b) => { } }; +const hydrate = (state, hydratedState) => { + state = clearAll(state.merge(hydratedState)); + + if (hydratedState.has('text')) { + state = state.set('text', hydratedState.get('text')); + } + + return state; +}; + export default function compose(state = initialState, action) { switch(action.type) { case STORE_HYDRATE: - return clearAll(state.merge(action.state.get('compose'))); + return hydrate(state, action.state.get('compose')); case COMPOSE_MOUNT: return state.set('mounted', true); case COMPOSE_UNMOUNT: diff --git a/app/javascript/packs/share.js b/app/javascript/packs/share.js new file mode 100644 index 000000000..51e4ae38b --- /dev/null +++ b/app/javascript/packs/share.js @@ -0,0 +1,24 @@ +import loadPolyfills from '../mastodon/load_polyfills'; + +require.context('../images/', true); + +function loaded() { + const ComposeContainer = require('../mastodon/containers/compose_container').default; + const React = require('react'); + const ReactDOM = require('react-dom'); + const mountNode = document.getElementById('mastodon-compose'); + + if (mountNode !== null) { + const props = JSON.parse(mountNode.getAttribute('data-props')); + ReactDOM.render(, mountNode); + } +} + +function main() { + const ready = require('../mastodon/ready').default; + ready(loaded); +} + +loadPolyfills().then(main).catch(error => { + console.error(error); +}); diff --git a/app/javascript/styles/containers.scss b/app/javascript/styles/containers.scss index 536f4e5a1..063db44db 100644 --- a/app/javascript/styles/containers.scss +++ b/app/javascript/styles/containers.scss @@ -44,6 +44,21 @@ } } +.compose-standalone { + .compose-form { + width: 400px; + margin: 0 auto; + padding: 20px 0; + margin-top: 40px; + box-sizing: border-box; + + @media screen and (max-width: 400px) { + margin-top: 0; + padding: 20px; + } + } +} + .account-header { width: 400px; margin: 0 auto; diff --git a/app/presenters/initial_state_presenter.rb b/app/presenters/initial_state_presenter.rb index 9507aad4a..70c496be8 100644 --- a/app/presenters/initial_state_presenter.rb +++ b/app/presenters/initial_state_presenter.rb @@ -1,5 +1,6 @@ # frozen_string_literal: true class InitialStatePresenter < ActiveModelSerializers::Model - attributes :settings, :push_subscription, :token, :current_account, :admin + attributes :settings, :push_subscription, :token, + :current_account, :admin, :text end diff --git a/app/serializers/initial_state_serializer.rb b/app/serializers/initial_state_serializer.rb index 0191948b1..0ac5e8319 100644 --- a/app/serializers/initial_state_serializer.rb +++ b/app/serializers/initial_state_serializer.rb @@ -34,6 +34,8 @@ class InitialStateSerializer < ActiveModel::Serializer store[:default_sensitive] = object.current_account.user.setting_default_sensitive end + store[:text] = object.text if object.text + store end diff --git a/app/views/shares/show.html.haml b/app/views/shares/show.html.haml new file mode 100644 index 000000000..44b6f145f --- /dev/null +++ b/app/views/shares/show.html.haml @@ -0,0 +1,5 @@ +- content_for :header_tags do + %script#initial-state{ type: 'application/json' }!= json_escape(@initial_state_json) + = javascript_pack_tag 'share', integrity: true, crossorigin: 'anonymous' + +#mastodon-compose{ data: { props: Oj.dump(default_props) } } diff --git a/config/routes.rb b/config/routes.rb index a1b206716..f75de5304 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -20,6 +20,7 @@ Rails.application.routes.draw do get '.well-known/host-meta', to: 'well_known/host_meta#show', as: :host_meta, defaults: { format: 'xml' } get '.well-known/webfinger', to: 'well_known/webfinger#show', as: :webfinger get 'manifest', to: 'manifests#show', defaults: { format: 'json' } + get 'intent', to: 'intents#show' devise_for :users, path: 'auth', controllers: { sessions: 'auth/sessions', @@ -86,12 +87,13 @@ Rails.application.routes.draw do # Remote follow resource :authorize_follow, only: [:show, :create] + resource :share, only: [:show, :create] namespace :admin do resources :subscriptions, only: [:index] resources :domain_blocks, only: [:index, :new, :create, :show, :destroy] resource :settings, only: [:edit, :update] - + resources :instances, only: [:index] do collection do post :resubscribe -- cgit From f814661fcaed99221bf0b250dbe14349cb702833 Mon Sep 17 00:00:00 2001 From: Clworld Date: Wed, 16 Aug 2017 23:48:44 +0900 Subject: Make share intent modal to make "signed in as" shown. (#4611) * Make share intent modal to make "signed in as" shown. * fix glitch on mobile. --- app/controllers/shares_controller.rb | 7 ++++++- app/javascript/styles/containers.scss | 1 + 2 files changed, 7 insertions(+), 1 deletion(-) (limited to 'app/javascript') diff --git a/app/controllers/shares_controller.rb b/app/controllers/shares_controller.rb index d70d66ff8..994742c3d 100644 --- a/app/controllers/shares_controller.rb +++ b/app/controllers/shares_controller.rb @@ -1,9 +1,10 @@ # frozen_string_literal: true class SharesController < ApplicationController - layout 'public' + layout 'modal' before_action :authenticate_user! + before_action :set_body_classes def show serializable_resource = ActiveModelSerializers::SerializableResource.new(InitialStatePresenter.new(initial_state_params), serializer: InitialStateSerializer) @@ -22,4 +23,8 @@ class SharesController < ApplicationController text: params[:text], } end + + def set_body_classes + @body_classes = 'compose-standalone' + end end diff --git a/app/javascript/styles/containers.scss b/app/javascript/styles/containers.scss index 063db44db..cfe8ea643 100644 --- a/app/javascript/styles/containers.scss +++ b/app/javascript/styles/containers.scss @@ -53,6 +53,7 @@ box-sizing: border-box; @media screen and (max-width: 400px) { + width: 100%; margin-top: 0; padding: 20px; } -- cgit From ca7ea1aba92f97e93f3c49e972f686a78779fd71 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Wed, 16 Aug 2017 17:12:58 +0200 Subject: Redesign public profiles (#4608) * Redesign public profiles * Responsive design * Change public profile status filtering defaults and add options - No longer displays private/direct toots even if you are permitted access - By default omits replies - "With replies" option - "Media only" option * Redesign account grid cards * Fix style issues --- app/controllers/accounts_controller.rb | 41 +++++- app/helpers/application_helper.rb | 4 + app/javascript/styles/accounts.scss | 230 ++++++++++++++++++++++-------- app/javascript/styles/landing_strip.scss | 13 ++ app/javascript/styles/stream_entries.scss | 17 +++ app/models/account.rb | 1 + app/views/accounts/_grid_card.html.haml | 11 +- app/views/accounts/_header.html.haml | 57 +++++--- app/views/accounts/show.html.haml | 7 +- app/views/shared/_landing_strip.html.haml | 9 +- config/locales/en.yml | 6 +- config/routes.rb | 2 + 12 files changed, 310 insertions(+), 88 deletions(-) (limited to 'app/javascript') diff --git a/app/controllers/accounts_controller.rb b/app/controllers/accounts_controller.rb index 4dc0a783d..c6b98628e 100644 --- a/app/controllers/accounts_controller.rb +++ b/app/controllers/accounts_controller.rb @@ -7,8 +7,14 @@ class AccountsController < ApplicationController def show respond_to do |format| format.html do - @statuses = @account.statuses.permitted_for(@account, current_account).paginate_by_max_id(20, params[:max_id], params[:since_id]) + if current_account && @account.blocking?(current_account) + @statuses = [] + return + end + + @statuses = filtered_statuses.paginate_by_max_id(20, params[:max_id], params[:since_id]) @statuses = cache_collection(@statuses, Status) + @next_url = next_url unless @statuses.empty? end format.atom do @@ -24,7 +30,40 @@ class AccountsController < ApplicationController private + def filtered_statuses + default_statuses.tap do |statuses| + statuses.merge!(only_media_scope) if request.path.ends_with?('/media') + statuses.merge!(no_replies_scope) unless request.path.ends_with?('/with_replies') + end + end + + def default_statuses + @account.statuses.where(visibility: [:public, :unlisted]) + end + + def only_media_scope + Status.where(id: account_media_status_ids) + end + + def account_media_status_ids + @account.media_attachments.attached.reorder(nil).select(:status_id).distinct + end + + def no_replies_scope + Status.without_replies + end + def set_account @account = Account.find_local!(params[:username]) end + + def next_url + if request.path.ends_with?('/media') + short_account_media_url(@account, max_id: @statuses.last.id) + elsif request.path.ends_with?('/with_replies') + short_account_with_replies_url(@account, max_id: @statuses.last.id) + else + short_account_url(@account, max_id: @statuses.last.id) + end + end end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 9f50d8bdb..61d4442c1 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -5,6 +5,10 @@ module ApplicationHelper current_page?(path) ? 'active' : '' end + def active_link_to(label, path, options = {}) + link_to label, path, options.merge(class: active_nav_class(path)) + end + def show_landing_strip? !user_signed_in? && !single_user_mode? end diff --git a/app/javascript/styles/accounts.scss b/app/javascript/styles/accounts.scss index 66da75828..f1fbe873b 100644 --- a/app/javascript/styles/accounts.scss +++ b/app/javascript/styles/accounts.scss @@ -1,21 +1,15 @@ .card { - background: $ui-base-color; + background-color: lighten($ui-base-color, 4%); background-size: cover; background-position: center; - padding: 60px 0; - padding-bottom: 0; border-radius: 4px 4px 0 0; box-shadow: 0 0 15px rgba($base-shadow-color, 0.2); overflow: hidden; position: relative; - - @media screen and (max-width: 740px) { - border-radius: 0; - box-shadow: none; - } + display: flex; &::after { - background: linear-gradient(rgba($base-shadow-color, 0.5), rgba($base-shadow-color, 0.8)); + background: rgba(darken($ui-base-color, 8%), 0.5); display: block; content: ""; position: absolute; @@ -26,6 +20,31 @@ z-index: 1; } + @media screen and (max-width: 740px) { + border-radius: 0; + box-shadow: none; + } + + .card__illustration { + padding: 60px 0; + position: relative; + flex: 1 1 auto; + display: flex; + justify-content: center; + align-items: center; + } + + .card__bio { + max-width: 260px; + flex: 1 1 auto; + display: flex; + flex-direction: column; + justify-content: space-between; + background: rgba(darken($ui-base-color, 8%), 0.8); + position: relative; + z-index: 2; + } + &.compact { padding: 30px 0; border-radius: 4px; @@ -44,11 +63,12 @@ font-size: 20px; line-height: 18px * 1.5; color: $primary-text-color; + padding: 10px 15px; + padding-bottom: 0; font-weight: 500; - text-align: center; position: relative; z-index: 2; - text-shadow: 0 0 2px $base-shadow-color; + margin-bottom: 30px; small { display: block; @@ -61,7 +81,6 @@ .avatar { width: 120px; margin: 0 auto; - margin-bottom: 15px; position: relative; z-index: 2; @@ -70,43 +89,68 @@ height: 120px; display: block; border-radius: 120px; + box-shadow: 0 0 15px rgba($base-shadow-color, 0.2); } } .controls { position: absolute; - top: 10px; - right: 10px; + top: 15px; + left: 15px; z-index: 2; + + .icon-button { + color: rgba($white, 0.8); + text-decoration: none; + font-size: 13px; + line-height: 13px; + font-weight: 500; + + .fa { + font-weight: 400; + margin-right: 5px; + } + + &:hover, + &:active, + &:focus { + color: $white; + } + } } - .details { - display: flex; - margin-top: 30px; - position: relative; - z-index: 2; - flex-direction: row; + .roles { + margin-bottom: 30px; + padding: 0 15px; } .details-counters { + margin-top: 30px; display: flex; flex-direction: row; - order: 0; + width: 100%; } .counter { - width: 80px; + width: 33.3%; + box-sizing: border-box; + flex: 0 0 auto; color: $ui-primary-color; padding: 5px 10px 0; margin-bottom: 10px; - border-right: 1px solid $ui-primary-color; + border-right: 1px solid lighten($ui-base-color, 4%); cursor: default; + text-align: center; position: relative; a { display: block; } + &:last-child { + border-right: 0; + } + &::after { display: block; content: ""; @@ -116,7 +160,7 @@ width: 100%; border-bottom: 4px solid $ui-primary-color; opacity: 0.5; - transition: all 0.8s ease; + transition: all 400ms ease; } &.active { @@ -129,7 +173,7 @@ &:hover { &::after { opacity: 1; - transition-duration: 0.2s; + transition-duration: 100ms; } } @@ -140,44 +184,40 @@ .counter-label { font-size: 12px; - text-transform: uppercase; display: block; margin-bottom: 5px; - text-shadow: 0 0 2px $base-shadow-color; } .counter-number { font-weight: 500; font-size: 18px; color: $primary-text-color; + font-family: 'mastodon-font-display', sans-serif; } } .bio { - flex: 1; font-size: 14px; line-height: 18px; - padding: 5px 10px; + padding: 0 15px; color: $ui-secondary-color; - order: 1; } @media screen and (max-width: 480px) { - .details { - display: block; - } + display: block; - .bio { - text-align: center; - margin-bottom: 20px; + .card__bio { + max-width: none; } - .counter { - flex: 1 1 auto; + .name, + .roles { + text-align: center; + margin-bottom: 15px; } - .counter:last-child { - border-right: none; + .bio { + margin-bottom: 15px; } } } @@ -264,13 +304,15 @@ .accounts-grid { box-shadow: 0 0 15px rgba($base-shadow-color, 0.2); - background: $simple-background-color; + background: darken($simple-background-color, 8%); border-radius: 0 0 4px 4px; padding: 20px 10px; padding-bottom: 10px; overflow: hidden; display: flex; flex-wrap: wrap; + z-index: 2; + position: relative; @media screen and (max-width: 740px) { border-radius: 0; @@ -280,10 +322,11 @@ .account-grid-card { box-sizing: border-box; width: 335px; - border: 1px solid $ui-secondary-color; + background: $simple-background-color; border-radius: 4px; color: $ui-base-color; margin-bottom: 10px; + position: relative; &:nth-child(odd) { margin-right: 10px; @@ -291,26 +334,52 @@ .account-grid-card__header { overflow: hidden; - padding: 10px; - border-bottom: 1px solid $ui-secondary-color; + height: 100px; + border-radius: 4px 4px 0 0; + background-color: lighten($ui-base-color, 4%); + background-size: cover; + background-position: center; + position: relative; + + &::after { + background: rgba(darken($ui-base-color, 8%), 0.5); + display: block; + content: ""; + position: absolute; + left: 0; + top: 0; + width: 100%; + height: 100%; + z-index: 1; + } + } + + .account-grid-card__avatar { + box-sizing: border-box; + padding: 15px; + position: absolute; + z-index: 2; + top: 100px - (40px + 2px); + left: -2px; } .avatar { - width: 60px; - height: 60px; - float: left; - margin-right: 15px; + width: 80px; + height: 80px; img { display: block; - width: 60px; - height: 60px; - border-radius: 60px; + width: 80px; + height: 80px; + border-radius: 80px; + border: 2px solid $simple-background-color; } } .name { + padding: 15px; padding-top: 10px; + padding-left: 15px + 80px + 15px; a { display: block; @@ -318,6 +387,7 @@ text-decoration: none; text-overflow: ellipsis; overflow: hidden; + font-weight: 500; &:hover { .display_name { @@ -328,30 +398,36 @@ } .display_name { - font-size: 14px; + font-size: 16px; display: block; } .username { - color: $ui-highlight-color; + color: lighten($ui-base-color, 34%); + font-size: 14px; + font-weight: 400; } .note { - padding: 10px; + padding: 10px 15px; padding-top: 15px; - color: $ui-primary-color; + box-sizing: border-box; + color: lighten($ui-base-color, 26%); word-wrap: break-word; + min-height: 80px; } } } .nothing-here { + width: 100%; + display: block; color: $ui-primary-color; font-size: 14px; font-weight: 500; text-align: center; - padding: 15px 0; - padding-bottom: 25px; + padding: 60px 0; + padding-top: 55px; cursor: default; } @@ -416,3 +492,43 @@ color: $ui-base-color; } } + +.activity-stream-tabs { + background: $simple-background-color; + border-bottom: 1px solid $ui-secondary-color; + position: relative; + z-index: 2; + + a { + display: inline-block; + padding: 15px; + text-decoration: none; + color: $ui-highlight-color; + text-transform: uppercase; + font-weight: 500; + + &:hover, + &:active, + &:focus { + color: lighten($ui-highlight-color, 8%); + } + + &.active { + color: $ui-base-color; + cursor: default; + } + } +} + +.account-role { + display: inline-block; + padding: 4px 6px; + cursor: default; + border-radius: 3px; + font-size: 12px; + line-height: 12px; + font-weight: 500; + color: $success-green; + background-color: rgba($success-green, 0.1); + border: 1px solid rgba($success-green, 0.5); +} diff --git a/app/javascript/styles/landing_strip.scss b/app/javascript/styles/landing_strip.scss index d2ac5b822..15ff84912 100644 --- a/app/javascript/styles/landing_strip.scss +++ b/app/javascript/styles/landing_strip.scss @@ -5,6 +5,8 @@ padding: 14px; border-radius: 4px; margin-bottom: 20px; + display: flex; + align-items: center; strong, a { @@ -15,4 +17,15 @@ color: inherit; text-decoration: underline; } + + .logo { + width: 30px; + height: 30px; + flex: 0 0 auto; + margin-right: 15px; + } + + @media screen and (max-width: 740px) { + margin-bottom: 0; + } } diff --git a/app/javascript/styles/stream_entries.scss b/app/javascript/styles/stream_entries.scss index 9e062c57e..1192e2a80 100644 --- a/app/javascript/styles/stream_entries.scss +++ b/app/javascript/styles/stream_entries.scss @@ -8,6 +8,7 @@ .detailed-status.light, .status.light { border-bottom: 1px solid $ui-secondary-color; + animation: none; } &:last-child { @@ -34,6 +35,14 @@ } } } + + @media screen and (max-width: 740px) { + &, + .detailed-status.light, + .status.light { + border-radius: 0 !important; + } + } } &.with-header { @@ -44,6 +53,14 @@ .status.light { border-radius: 0; } + + &:last-child { + &, + .detailed-status.light, + .status.light { + border-radius: 0 0 4px 4px; + } + } } } } diff --git a/app/models/account.rb b/app/models/account.rb index a7264353e..c4c168160 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -105,6 +105,7 @@ class Account < ApplicationRecord :current_sign_in_ip, :current_sign_in_at, :confirmed?, + :admin?, :locale, to: :user, prefix: true, diff --git a/app/views/accounts/_grid_card.html.haml b/app/views/accounts/_grid_card.html.haml index 0571d1d5e..305eb2c44 100644 --- a/app/views/accounts/_grid_card.html.haml +++ b/app/views/accounts/_grid_card.html.haml @@ -1,8 +1,9 @@ .account-grid-card - .account-grid-card__header + .account-grid-card__header{ style: "background-image: url(#{account.header.url(:original)})" } + .account-grid-card__avatar .avatar= image_tag account.avatar.url(:original) - .name - = link_to TagManager.instance.url_for(account) do - %span.display_name.emojify= display_name(account) - %span.username @#{account.acct} + .name + = link_to TagManager.instance.url_for(account) do + %span.display_name.emojify= display_name(account) + %span.username @#{account.acct} %p.note.emojify= truncate(strip_tags(account.note), length: 150) diff --git a/app/views/accounts/_header.html.haml b/app/views/accounts/_header.html.haml index 6451a5573..8009e903e 100644 --- a/app/views/accounts/_header.html.haml +++ b/app/views/accounts/_header.html.haml @@ -1,34 +1,51 @@ .card.h-card.p-author{ style: "background-image: url(#{account.header.url(:original)})" } - - if user_signed_in? && current_account.id != account.id && !current_account.requested?(account) - .controls - - if current_account.following?(account) - = link_to t('accounts.unfollow'), account_unfollow_path(account), data: { method: :post }, class: 'button' - - else - = link_to t('accounts.follow'), account_follow_path(account), data: { method: :post }, class: 'button' - - elsif !user_signed_in? - .controls - .remote-follow - = link_to t('accounts.remote_follow'), account_remote_follow_path(account), class: 'button' - .avatar= image_tag account.avatar.url(:original), class: 'u-photo' - %h1.name - %span.p-name.emojify= display_name(account) - %small - %span @#{account.username} - = fa_icon('lock') if account.locked? - .details + .card__illustration + - if user_signed_in? && current_account.id != account.id && !current_account.requested?(account) + .controls + - if current_account.following?(account) + = link_to account_unfollow_path(account), data: { method: :post }, class: 'icon-button' do + = fa_icon 'user-times' + = t('accounts.unfollow') + - else + = link_to account_follow_path(account), data: { method: :post }, class: 'icon-button' do + = fa_icon 'user-plus' + = t('accounts.follow') + - elsif !user_signed_in? + .controls + .remote-follow + = link_to account_remote_follow_path(account), class: 'icon-button' do + = fa_icon 'user-plus' + = t('accounts.remote_follow') + + .avatar= image_tag account.avatar.url(:original), class: 'u-photo' + + .card__bio + %h1.name + %span.p-name.emojify= display_name(account) + %small + %span @#{account.local_username_and_domain} + = fa_icon('lock') if account.locked? + + - if account.user_admin? + .roles + .account-role + = t 'accounts.roles.admin' + .bio .account__header__content.p-note.emojify= Formatter.instance.simplified_format(account) .details-counters .counter{ class: active_nav_class(short_account_url(account)) } = link_to short_account_url(account), class: 'u-url u-uid' do - %span.counter-label= t('accounts.posts') %span.counter-number= number_with_delimiter account.statuses_count + %span.counter-label= t('accounts.posts') + .counter{ class: active_nav_class(account_following_index_url(account)) } = link_to account_following_index_url(account) do - %span.counter-label= t('accounts.following') %span.counter-number= number_with_delimiter account.following_count + %span.counter-label= t('accounts.following') + .counter{ class: active_nav_class(account_followers_url(account)) } = link_to account_followers_url(account) do - %span.counter-label= t('accounts.followers') %span.counter-number= number_with_delimiter account.followers_count + %span.counter-label= t('accounts.followers') diff --git a/app/views/accounts/show.html.haml b/app/views/accounts/show.html.haml index 74e695fc3..ec44f4c74 100644 --- a/app/views/accounts/show.html.haml +++ b/app/views/accounts/show.html.haml @@ -20,6 +20,11 @@ = render 'header', account: @account + .activity-stream-tabs + = active_link_to t('accounts.posts'), short_account_url(@account) + = active_link_to t('accounts.posts_with_replies'), short_account_with_replies_url(@account) + = active_link_to t('accounts.media'), short_account_media_url(@account) + - if @statuses.empty? .accounts-grid = render 'nothing_here' @@ -29,4 +34,4 @@ - if @statuses.size == 20 .pagination - = link_to safe_join([t('pagination.next'), fa_icon('chevron-right')], ' '), short_account_url(@account, max_id: @statuses.last.id), class: 'next', rel: 'next' + = link_to safe_join([t('pagination.next'), fa_icon('chevron-right')], ' '), @next_url, class: 'next', rel: 'next' diff --git a/app/views/shared/_landing_strip.html.haml b/app/views/shared/_landing_strip.html.haml index 35461a8cb..ae26fc1ff 100644 --- a/app/views/shared/_landing_strip.html.haml +++ b/app/views/shared/_landing_strip.html.haml @@ -1,5 +1,8 @@ .landing-strip - = t('landing_strip_html', name: content_tag(:span, display_name(account), class: :emojify), link_to_root_path: link_to(content_tag(:strong, site_hostname), root_path)) + = image_tag asset_pack_path('logo.svg'), class: 'logo' - - if open_registrations? - = t('landing_strip_signup_html', sign_up_path: new_user_registration_path) + %div + = t('landing_strip_html', name: content_tag(:span, display_name(account), class: :emojify), link_to_root_path: link_to(content_tag(:strong, site_hostname), root_path)) + + - if open_registrations? + = t('landing_strip_signup_html', sign_up_path: new_user_registration_path) diff --git a/config/locales/en.yml b/config/locales/en.yml index 210bfc5b4..97f46c3af 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -40,7 +40,11 @@ en: nothing_here: There is nothing here! people_followed_by: People whom %{name} follows people_who_follow: People who follow %{name} - posts: Posts + posts: Toots + posts_with_replies: Toots with replies + media: Media + roles: + admin: Admin remote_follow: Remote follow reserved_username: The username is reserved unfollow: Unfollow diff --git a/config/routes.rb b/config/routes.rb index f75de5304..1a39dfeac 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -56,6 +56,8 @@ Rails.application.routes.draw do end get '/@:username', to: 'accounts#show', as: :short_account + get '/@:username/with_replies', to: 'accounts#show', as: :short_account_with_replies + get '/@:username/media', to: 'accounts#show', as: :short_account_media get '/@:account_username/:id', to: 'statuses#show', as: :short_account_status namespace :settings do -- cgit From 2a04bdc87a6b4ed662db1e3f6cbba25fa722e123 Mon Sep 17 00:00:00 2001 From: m4sk1n Date: Wed, 16 Aug 2017 22:14:23 +0200 Subject: i18n: Update Polish translation (#4613) * i18n: Update Polish translation * Update pl.json --- app/javascript/mastodon/locales/pl.json | 2 +- config/locales/pl.yml | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) (limited to 'app/javascript') diff --git a/app/javascript/mastodon/locales/pl.json b/app/javascript/mastodon/locales/pl.json index 542230f11..dfa5c3f90 100644 --- a/app/javascript/mastodon/locales/pl.json +++ b/app/javascript/mastodon/locales/pl.json @@ -85,7 +85,7 @@ "getting_started.appsshort": "Aplikacje", "getting_started.faq": "FAQ", "getting_started.heading": "Naucz się korzystać", - "getting_started.open_source_notice": "Mastodon jest oprogramowaniem o otwartym źródle. Możesz pomóc w rozwoju lub zgłaszać błędy na GitHubie tutaj {github}.", + "getting_started.open_source_notice": "Mastodon jest oprogramowaniem o otwartym źródle. Możesz pomóc w rozwoju lub zgłaszać błędy na GitHubie tutaj: {github}.", "getting_started.userguide": "Podręcznik użytkownika", "home.column_settings.advanced": "Zaawansowane", "home.column_settings.basic": "Podstawowe", diff --git a/config/locales/pl.yml b/config/locales/pl.yml index b8b5ace14..97c1f05ed 100644 --- a/config/locales/pl.yml +++ b/config/locales/pl.yml @@ -41,7 +41,11 @@ pl: people_followed_by: Konta śledzone przez %{name} people_who_follow: Osoby, które śledzą konto %{name} posts: Wpisy - remote_follow: Zdalne śledzenie + posts_with_replies: Wpisy + media: Zawartość multimedialna + roles: + admin: Administrator + remote_follow: Śledź zdalnie reserved_username: Ta nazwa użytkownika jest zarezerwowana. unfollow: Przestań śledzić admin: -- cgit From d5acf4275f7033bf2997bfcbbbc3a63c9606f101 Mon Sep 17 00:00:00 2001 From: takayamaki Date: Sun, 20 Aug 2017 20:27:14 +0900 Subject: Improve about ja translation standalone.public_title (#4641) --- app/javascript/mastodon/locales/ja.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app/javascript') diff --git a/app/javascript/mastodon/locales/ja.json b/app/javascript/mastodon/locales/ja.json index 4c98086bb..757190c90 100644 --- a/app/javascript/mastodon/locales/ja.json +++ b/app/javascript/mastodon/locales/ja.json @@ -159,7 +159,7 @@ "report.target": "{target} を通報する", "search.placeholder": "検索", "search_results.total": "{count, number}件の結果", - "standalone.public_title": "連合タイムライン", + "standalone.public_title": "今こんな話をしています", "status.cannot_reblog": "この投稿はブーストできません", "status.delete": "削除", "status.favourite": "お気に入り", -- cgit From 23792f5a7cfc0ba2f3f9181f7e9b8aa7647e9cb3 Mon Sep 17 00:00:00 2001 From: abcang Date: Mon, 21 Aug 2017 00:12:06 +0900 Subject: Fix hasUnread on HashtagTimeline (#4644) --- app/javascript/mastodon/features/hashtag_timeline/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'app/javascript') diff --git a/app/javascript/mastodon/features/hashtag_timeline/index.js b/app/javascript/mastodon/features/hashtag_timeline/index.js index b17e8e1a5..10b5c57e9 100644 --- a/app/javascript/mastodon/features/hashtag_timeline/index.js +++ b/app/javascript/mastodon/features/hashtag_timeline/index.js @@ -14,8 +14,8 @@ import { addColumn, removeColumn, moveColumn } from '../../actions/columns'; import { FormattedMessage } from 'react-intl'; import createStream from '../../stream'; -const mapStateToProps = state => ({ - hasUnread: state.getIn(['timelines', 'tag', 'unread']) > 0, +const mapStateToProps = (state, props) => ({ + hasUnread: state.getIn(['timelines', `hashtag:${props.params.id}`, 'unread']) > 0, streamingAPIBaseURL: state.getIn(['meta', 'streaming_api_base_url']), accessToken: state.getIn(['meta', 'access_token']), }); -- cgit From f26758dc019a24cd7e87078e2a19350d0a2d083c Mon Sep 17 00:00:00 2001 From: unarist Date: Mon, 21 Aug 2017 03:45:44 +0900 Subject: Fix .information-board style for Safari (#4602) flex-basis: 0 allows make flexbox smaller than its contents on Safari <10. https://github.com/philipwalton/flexbugs#1-minimum-content-sizing-of-flex-items-not-honored --- app/javascript/styles/about.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app/javascript') diff --git a/app/javascript/styles/about.scss b/app/javascript/styles/about.scss index 62143246f..9477335be 100644 --- a/app/javascript/styles/about.scss +++ b/app/javascript/styles/about.scss @@ -171,7 +171,7 @@ } .section { - flex: 1 0 0; + flex: 1 0 auto; font: 16px/28px 'mastodon-font-sans-serif', sans-serif; text-align: right; padding: 10px 15px; -- cgit From 110227ac5e77c2be51ef8be4bca614d357c0eb13 Mon Sep 17 00:00:00 2001 From: Yamagishi Kazutoshi Date: Mon, 21 Aug 2017 06:23:05 +0900 Subject: Remove status from favorites list when unfavorited (#4597) --- app/javascript/mastodon/reducers/status_lists.js | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) (limited to 'app/javascript') diff --git a/app/javascript/mastodon/reducers/status_lists.js b/app/javascript/mastodon/reducers/status_lists.js index 580cc17d2..2ce27a454 100644 --- a/app/javascript/mastodon/reducers/status_lists.js +++ b/app/javascript/mastodon/reducers/status_lists.js @@ -3,7 +3,10 @@ import { FAVOURITED_STATUSES_EXPAND_SUCCESS, } from '../actions/favourites'; import { Map as ImmutableMap, List as ImmutableList } from 'immutable'; -import { FAVOURITE_SUCCESS } from '../actions/interactions'; +import { + FAVOURITE_SUCCESS, + UNFAVOURITE_SUCCESS, +} from '../actions/interactions'; const initialState = ImmutableMap({ favourites: ImmutableMap({ @@ -34,6 +37,12 @@ const prependOneToList = (state, listType, status) => { })); }; +const removeOneFromList = (state, listType, status) => { + return state.update(listType, listMap => listMap.withMutations(map => { + map.set('items', map.get('items').filter(item => item !== status.get('id'))); + })); +}; + export default function statusLists(state = initialState, action) { switch(action.type) { case FAVOURITED_STATUSES_FETCH_SUCCESS: @@ -42,6 +51,8 @@ export default function statusLists(state = initialState, action) { return appendToList(state, 'favourites', action.statuses, action.next); case FAVOURITE_SUCCESS: return prependOneToList(state, 'favourites', action.status); + case UNFAVOURITE_SUCCESS: + return removeOneFromList(state, 'favourites', action.status); default: return state; } -- cgit From 4c23544714c05258af8feab50da243039ddbefb6 Mon Sep 17 00:00:00 2001 From: m4sk1n Date: Mon, 21 Aug 2017 00:57:28 +0200 Subject: i18n: Minor changes in Polish translation (#4649) * i18n: Minor changes in Polish translation * i18n: pl --- app/javascript/mastodon/locales/pl.json | 10 +++++----- config/locales/pl.yml | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) (limited to 'app/javascript') diff --git a/app/javascript/mastodon/locales/pl.json b/app/javascript/mastodon/locales/pl.json index dfa5c3f90..af38bbb6c 100644 --- a/app/javascript/mastodon/locales/pl.json +++ b/app/javascript/mastodon/locales/pl.json @@ -70,7 +70,7 @@ "emoji_button.nature": "Natura", "emoji_button.objects": "Objekty", "emoji_button.people": "Ludzie", - "emoji_button.search": "Szukaj...", + "emoji_button.search": "Szukaj…", "emoji_button.symbols": "Symbole", "emoji_button.travel": "Podróże i miejsca", "empty_column.community": "Lokalna oś czasu jest pusta. Napisz coś publicznie, aby zagaić!", @@ -96,7 +96,7 @@ "lightbox.close": "Zamknij", "lightbox.next": "Następne", "lightbox.previous": "Poprzednie", - "loading_indicator.label": "Ładowanie...", + "loading_indicator.label": "Ładowanie…", "media_gallery.toggle_visible": "Przełącz widoczność", "missing_indicator.label": "Nie znaleziono", "navigation_bar.blocks": "Zablokowani użytkownicy", @@ -116,12 +116,12 @@ "notifications.clear": "Wyczyść powiadomienia", "notifications.clear_confirmation": "Czy na pewno chcesz bezpowrotnie usunąć wszystkie powiadomienia?", "notifications.column_settings.alert": "Powiadomienia na pulpicie", - "notifications.column_settings.favourite": "Ulubione:", + "notifications.column_settings.favourite": "Dodanie do ulubionych:", "notifications.column_settings.follow": "Nowi śledzący:", - "notifications.column_settings.mention": "Wspomniali:", + "notifications.column_settings.mention": "Wspomnienia:", "notifications.column_settings.push": "Powiadomienia push", "notifications.column_settings.push_meta": "To urządzenie", - "notifications.column_settings.reblog": "Podbili:", + "notifications.column_settings.reblog": "Podbicia:", "notifications.column_settings.show": "Pokaż w kolumnie", "notifications.column_settings.sound": "Odtwarzaj dźwięk", "onboarding.done": "Gotowe", diff --git a/config/locales/pl.yml b/config/locales/pl.yml index 97c1f05ed..c005cdb01 100644 --- a/config/locales/pl.yml +++ b/config/locales/pl.yml @@ -41,7 +41,7 @@ pl: people_followed_by: Konta śledzone przez %{name} people_who_follow: Osoby, które śledzą konto %{name} posts: Wpisy - posts_with_replies: Wpisy + posts_with_replies: Wpisy z odpowiedziami media: Zawartość multimedialna roles: admin: Administrator -- cgit From ea958cae7f6c960bdb54214c12de2083ab0e25b0 Mon Sep 17 00:00:00 2001 From: abcang Date: Mon, 21 Aug 2017 22:04:34 +0900 Subject: Refactoring streaming connections (#4645) --- app/javascript/mastodon/actions/streaming.js | 94 ++++++++++++++++++++++ app/javascript/mastodon/containers/mastodon.js | 72 ++--------------- .../mastodon/features/community_timeline/index.js | 50 ++---------- .../mastodon/features/hashtag_timeline/index.js | 31 ++----- .../mastodon/features/public_timeline/index.js | 50 ++---------- 5 files changed, 116 insertions(+), 181 deletions(-) create mode 100644 app/javascript/mastodon/actions/streaming.js (limited to 'app/javascript') diff --git a/app/javascript/mastodon/actions/streaming.js b/app/javascript/mastodon/actions/streaming.js new file mode 100644 index 000000000..7802694a3 --- /dev/null +++ b/app/javascript/mastodon/actions/streaming.js @@ -0,0 +1,94 @@ +import createStream from '../stream'; +import { + updateTimeline, + deleteFromTimelines, + refreshHomeTimeline, + connectTimeline, + disconnectTimeline, +} from './timelines'; +import { updateNotifications, refreshNotifications } from './notifications'; +import { getLocale } from '../locales'; + +const { messages } = getLocale(); + +export function connectTimelineStream (timelineId, path, pollingRefresh = null) { + return (dispatch, getState) => { + const streamingAPIBaseURL = getState().getIn(['meta', 'streaming_api_base_url']); + const accessToken = getState().getIn(['meta', 'access_token']); + const locale = getState().getIn(['meta', 'locale']); + let polling = null; + + const setupPolling = () => { + polling = setInterval(() => { + pollingRefresh(dispatch); + }, 20000); + }; + + const clearPolling = () => { + if (polling) { + clearInterval(polling); + polling = null; + } + }; + + const subscription = createStream(streamingAPIBaseURL, accessToken, path, { + + connected () { + if (pollingRefresh) { + clearPolling(); + } + dispatch(connectTimeline(timelineId)); + }, + + disconnected () { + if (pollingRefresh) { + setupPolling(); + } + dispatch(disconnectTimeline(timelineId)); + }, + + received (data) { + switch(data.event) { + case 'update': + dispatch(updateTimeline(timelineId, JSON.parse(data.payload))); + break; + case 'delete': + dispatch(deleteFromTimelines(data.payload)); + break; + case 'notification': + dispatch(updateNotifications(JSON.parse(data.payload), messages, locale)); + break; + } + }, + + reconnected () { + if (pollingRefresh) { + clearPolling(); + pollingRefresh(dispatch); + } + dispatch(connectTimeline(timelineId)); + }, + + }); + + const disconnect = () => { + if (subscription) { + subscription.close(); + } + clearPolling(); + }; + + return disconnect; + }; +} + +function refreshHomeTimelineAndNotification (dispatch) { + dispatch(refreshHomeTimeline()); + dispatch(refreshNotifications()); +} + +export const connectUserStream = () => connectTimelineStream('home', 'user', refreshHomeTimelineAndNotification); +export const connectCommunityStream = () => connectTimelineStream('community', 'public:local'); +export const connectMediaStream = () => connectTimelineStream('community', 'public:local'); +export const connectPublicStream = () => connectTimelineStream('public', 'public'); +export const connectHashtagStream = (tag) => connectTimelineStream(`hashtag:${tag}`, `hashtag&tag=${tag}`); diff --git a/app/javascript/mastodon/containers/mastodon.js b/app/javascript/mastodon/containers/mastodon.js index fe534d1c1..47180c506 100644 --- a/app/javascript/mastodon/containers/mastodon.js +++ b/app/javascript/mastodon/containers/mastodon.js @@ -2,21 +2,13 @@ import React from 'react'; import { Provider } from 'react-redux'; import PropTypes from 'prop-types'; import configureStore from '../store/configureStore'; -import { - updateTimeline, - deleteFromTimelines, - refreshHomeTimeline, - connectTimeline, - disconnectTimeline, -} from '../actions/timelines'; import { showOnboardingOnce } from '../actions/onboarding'; -import { updateNotifications, refreshNotifications } from '../actions/notifications'; import BrowserRouter from 'react-router-dom/BrowserRouter'; import Route from 'react-router-dom/Route'; import ScrollContext from 'react-router-scroll/lib/ScrollBehaviorContext'; import UI from '../features/ui'; import { hydrateStore } from '../actions/store'; -import createStream from '../stream'; +import { connectUserStream } from '../actions/streaming'; import { IntlProvider, addLocaleData } from 'react-intl'; import { getLocale } from '../locales'; const { localeData, messages } = getLocale(); @@ -33,56 +25,7 @@ export default class Mastodon extends React.PureComponent { }; componentDidMount() { - const { locale } = this.props; - const streamingAPIBaseURL = store.getState().getIn(['meta', 'streaming_api_base_url']); - const accessToken = store.getState().getIn(['meta', 'access_token']); - - const setupPolling = () => { - this.polling = setInterval(() => { - store.dispatch(refreshHomeTimeline()); - store.dispatch(refreshNotifications()); - }, 20000); - }; - - const clearPolling = () => { - clearInterval(this.polling); - this.polling = undefined; - }; - - this.subscription = createStream(streamingAPIBaseURL, accessToken, 'user', { - - connected () { - clearPolling(); - store.dispatch(connectTimeline('home')); - }, - - disconnected () { - setupPolling(); - store.dispatch(disconnectTimeline('home')); - }, - - received (data) { - switch(data.event) { - case 'update': - store.dispatch(updateTimeline('home', JSON.parse(data.payload))); - break; - case 'delete': - store.dispatch(deleteFromTimelines(data.payload)); - break; - case 'notification': - store.dispatch(updateNotifications(JSON.parse(data.payload), messages, locale)); - break; - } - }, - - reconnected () { - clearPolling(); - store.dispatch(connectTimeline('home')); - store.dispatch(refreshHomeTimeline()); - store.dispatch(refreshNotifications()); - }, - - }); + this.disconnect = store.dispatch(connectUserStream()); // Desktop notifications if (typeof window.Notification !== 'undefined' && Notification.permission === 'default') { @@ -98,14 +41,9 @@ export default class Mastodon extends React.PureComponent { } componentWillUnmount () { - if (typeof this.subscription !== 'undefined') { - this.subscription.close(); - this.subscription = null; - } - - if (typeof this.polling !== 'undefined') { - clearInterval(this.polling); - this.polling = null; + if (this.disconnect) { + this.disconnect(); + this.disconnect = null; } } diff --git a/app/javascript/mastodon/features/community_timeline/index.js b/app/javascript/mastodon/features/community_timeline/index.js index 0e2300f8c..596a89412 100644 --- a/app/javascript/mastodon/features/community_timeline/index.js +++ b/app/javascript/mastodon/features/community_timeline/index.js @@ -7,15 +7,11 @@ import ColumnHeader from '../../components/column_header'; import { refreshCommunityTimeline, expandCommunityTimeline, - updateTimeline, - deleteFromTimelines, - connectTimeline, - disconnectTimeline, } from '../../actions/timelines'; import { addColumn, removeColumn, moveColumn } from '../../actions/columns'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import ColumnSettingsContainer from './containers/column_settings_container'; -import createStream from '../../stream'; +import { connectCommunityStream } from '../../actions/streaming'; const messages = defineMessages({ title: { id: 'column.community', defaultMessage: 'Local timeline' }, @@ -23,8 +19,6 @@ const messages = defineMessages({ const mapStateToProps = state => ({ hasUnread: state.getIn(['timelines', 'community', 'unread']) > 0, - streamingAPIBaseURL: state.getIn(['meta', 'streaming_api_base_url']), - accessToken: state.getIn(['meta', 'access_token']), }); @connect(mapStateToProps) @@ -35,8 +29,6 @@ export default class CommunityTimeline extends React.PureComponent { dispatch: PropTypes.func.isRequired, columnId: PropTypes.string, intl: PropTypes.object.isRequired, - streamingAPIBaseURL: PropTypes.string.isRequired, - accessToken: PropTypes.string.isRequired, hasUnread: PropTypes.bool, multiColumn: PropTypes.bool, }; @@ -61,46 +53,16 @@ export default class CommunityTimeline extends React.PureComponent { } componentDidMount () { - const { dispatch, streamingAPIBaseURL, accessToken } = this.props; + const { dispatch } = this.props; dispatch(refreshCommunityTimeline()); - - if (typeof this._subscription !== 'undefined') { - return; - } - - this._subscription = createStream(streamingAPIBaseURL, accessToken, 'public:local', { - - connected () { - dispatch(connectTimeline('community')); - }, - - reconnected () { - dispatch(connectTimeline('community')); - }, - - disconnected () { - dispatch(disconnectTimeline('community')); - }, - - received (data) { - switch(data.event) { - case 'update': - dispatch(updateTimeline('community', JSON.parse(data.payload))); - break; - case 'delete': - dispatch(deleteFromTimelines(data.payload)); - break; - } - }, - - }); + this.disconnect = dispatch(connectCommunityStream()); } componentWillUnmount () { - if (typeof this._subscription !== 'undefined') { - this._subscription.close(); - this._subscription = null; + if (this.disconnect) { + this.disconnect(); + this.disconnect = null; } } diff --git a/app/javascript/mastodon/features/hashtag_timeline/index.js b/app/javascript/mastodon/features/hashtag_timeline/index.js index 10b5c57e9..5fe21ce90 100644 --- a/app/javascript/mastodon/features/hashtag_timeline/index.js +++ b/app/javascript/mastodon/features/hashtag_timeline/index.js @@ -7,17 +7,13 @@ import ColumnHeader from '../../components/column_header'; import { refreshHashtagTimeline, expandHashtagTimeline, - updateTimeline, - deleteFromTimelines, } from '../../actions/timelines'; import { addColumn, removeColumn, moveColumn } from '../../actions/columns'; import { FormattedMessage } from 'react-intl'; -import createStream from '../../stream'; +import { connectHashtagStream } from '../../actions/streaming'; const mapStateToProps = (state, props) => ({ hasUnread: state.getIn(['timelines', `hashtag:${props.params.id}`, 'unread']) > 0, - streamingAPIBaseURL: state.getIn(['meta', 'streaming_api_base_url']), - accessToken: state.getIn(['meta', 'access_token']), }); @connect(mapStateToProps) @@ -27,8 +23,6 @@ export default class HashtagTimeline extends React.PureComponent { params: PropTypes.object.isRequired, columnId: PropTypes.string, dispatch: PropTypes.func.isRequired, - streamingAPIBaseURL: PropTypes.string.isRequired, - accessToken: PropTypes.string.isRequired, hasUnread: PropTypes.bool, multiColumn: PropTypes.bool, }; @@ -53,28 +47,13 @@ export default class HashtagTimeline extends React.PureComponent { } _subscribe (dispatch, id) { - const { streamingAPIBaseURL, accessToken } = this.props; - - this.subscription = createStream(streamingAPIBaseURL, accessToken, `hashtag&tag=${id}`, { - - received (data) { - switch(data.event) { - case 'update': - dispatch(updateTimeline(`hashtag:${id}`, JSON.parse(data.payload))); - break; - case 'delete': - dispatch(deleteFromTimelines(data.payload)); - break; - } - }, - - }); + this.disconnect = dispatch(connectHashtagStream(id)); } _unsubscribe () { - if (typeof this.subscription !== 'undefined') { - this.subscription.close(); - this.subscription = null; + if (this.disconnect) { + this.disconnect(); + this.disconnect = null; } } diff --git a/app/javascript/mastodon/features/public_timeline/index.js b/app/javascript/mastodon/features/public_timeline/index.js index c6cad02d6..193489c63 100644 --- a/app/javascript/mastodon/features/public_timeline/index.js +++ b/app/javascript/mastodon/features/public_timeline/index.js @@ -7,15 +7,11 @@ import ColumnHeader from '../../components/column_header'; import { refreshPublicTimeline, expandPublicTimeline, - updateTimeline, - deleteFromTimelines, - connectTimeline, - disconnectTimeline, } from '../../actions/timelines'; import { addColumn, removeColumn, moveColumn } from '../../actions/columns'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import ColumnSettingsContainer from './containers/column_settings_container'; -import createStream from '../../stream'; +import { connectPublicStream } from '../../actions/streaming'; const messages = defineMessages({ title: { id: 'column.public', defaultMessage: 'Federated timeline' }, @@ -23,8 +19,6 @@ const messages = defineMessages({ const mapStateToProps = state => ({ hasUnread: state.getIn(['timelines', 'public', 'unread']) > 0, - streamingAPIBaseURL: state.getIn(['meta', 'streaming_api_base_url']), - accessToken: state.getIn(['meta', 'access_token']), }); @connect(mapStateToProps) @@ -36,8 +30,6 @@ export default class PublicTimeline extends React.PureComponent { intl: PropTypes.object.isRequired, columnId: PropTypes.string, multiColumn: PropTypes.bool, - streamingAPIBaseURL: PropTypes.string.isRequired, - accessToken: PropTypes.string.isRequired, hasUnread: PropTypes.bool, }; @@ -61,46 +53,16 @@ export default class PublicTimeline extends React.PureComponent { } componentDidMount () { - const { dispatch, streamingAPIBaseURL, accessToken } = this.props; + const { dispatch } = this.props; dispatch(refreshPublicTimeline()); - - if (typeof this._subscription !== 'undefined') { - return; - } - - this._subscription = createStream(streamingAPIBaseURL, accessToken, 'public', { - - connected () { - dispatch(connectTimeline('public')); - }, - - reconnected () { - dispatch(connectTimeline('public')); - }, - - disconnected () { - dispatch(disconnectTimeline('public')); - }, - - received (data) { - switch(data.event) { - case 'update': - dispatch(updateTimeline('public', JSON.parse(data.payload))); - break; - case 'delete': - dispatch(deleteFromTimelines(data.payload)); - break; - } - }, - - }); + this.disconnect = dispatch(connectPublicStream()); } componentWillUnmount () { - if (typeof this._subscription !== 'undefined') { - this._subscription.close(); - this._subscription = null; + if (this.disconnect) { + this.disconnect(); + this.disconnect = null; } } -- cgit From 4cbb6386049f4037c146ed3cf52c852cc3d8f9d5 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 21 Aug 2017 17:59:34 +0200 Subject: Fix visual line-break glitch with .invisible parts of links (#4655) --- app/javascript/styles/components.scss | 2 ++ 1 file changed, 2 insertions(+) (limited to 'app/javascript') diff --git a/app/javascript/styles/components.scss b/app/javascript/styles/components.scss index f66be5111..ef1797e72 100644 --- a/app/javascript/styles/components.scss +++ b/app/javascript/styles/components.scss @@ -237,6 +237,8 @@ line-height: 0; display: inline-block; width: 0; + height: 0; + position: absolute; } .ellipsis { -- cgit From 143b77e10d984d3790382758c0b797215850b024 Mon Sep 17 00:00:00 2001 From: Lynx Kotoura Date: Tue, 22 Aug 2017 04:59:03 +0900 Subject: Increase contrast in landing pages (#4567) * Increase contrast in about and about/more page * Lighten em color in landing pages * Increase contrast in landing pages Fix about.scss --- app/javascript/styles/about.scss | 16 ++++++++-------- app/javascript/styles/forms.scss | 4 ++-- 2 files changed, 10 insertions(+), 10 deletions(-) (limited to 'app/javascript') diff --git a/app/javascript/styles/about.scss b/app/javascript/styles/about.scss index 9477335be..020842f7d 100644 --- a/app/javascript/styles/about.scss +++ b/app/javascript/styles/about.scss @@ -139,7 +139,7 @@ font-size: 14px; line-height: 24px; font-weight: 500; - color: $ui-base-lighter-color; + color: $ui-primary-color; padding-bottom: 5px; margin-bottom: 15px; border-bottom: 1px solid lighten($ui-base-color, 4%); @@ -150,7 +150,7 @@ a, span { font-weight: 400; - color: lighten($ui-base-color, 34%); + color: darken($ui-primary-color, 10%); } a { @@ -262,11 +262,11 @@ .text { font-size: 16px; line-height: 30px; - color: $ui-base-lighter-color; + color: $ui-primary-color; h6 { font-weight: 500; - color: $ui-primary-color; + color: $ui-secondary-color; } } } @@ -516,7 +516,7 @@ font: 16px/28px 'mastodon-font-sans-serif', sans-serif; font-weight: 400; margin-bottom: 12px; - color: $ui-base-lighter-color; + color: $ui-primary-color; a { color: $ui-highlight-color; @@ -531,13 +531,13 @@ line-height: 24px; font-weight: 500; margin-bottom: 20px; - color: $ui-primary-color; + color: $ui-secondary-color; } p { font-size: 16px; line-height: 30px; - color: $ui-base-lighter-color; + color: $ui-primary-color; } .features { @@ -623,7 +623,7 @@ font-family: inherit; font-size: inherit; line-height: inherit; - color: $ui-primary-color; + color: lighten($ui-primary-color, 10%); } h1 { diff --git a/app/javascript/styles/forms.scss b/app/javascript/styles/forms.scss index 62094e98e..8e41bb002 100644 --- a/app/javascript/styles/forms.scss +++ b/app/javascript/styles/forms.scss @@ -32,10 +32,10 @@ code { line-height: 18px; margin-top: 15px; margin-bottom: 0; - color: $ui-base-lighter-color; + color: $ui-primary-color; a { - color: $ui-primary-color; + color: $ui-highlight-color; } } } -- cgit From e4c761f902579c2122eb4d531d1596ff5376d96d Mon Sep 17 00:00:00 2001 From: Quent-in Date: Thu, 24 Aug 2017 09:16:32 +0200 Subject: l18n update OC new strings (#4664) (#4680) * New strings * Update Thin non breaking spaces * Update Thin non breaking spaces * Update Thin non breaking spaces --- app/javascript/mastodon/locales/oc.json | 42 ++++++++--------- config/locales/devise.oc.yml | 14 +++--- config/locales/doorkeeper.oc.yml | 8 ++-- config/locales/oc.yml | 84 ++++++++++++++++++--------------- 4 files changed, 78 insertions(+), 70 deletions(-) (limited to 'app/javascript') diff --git a/app/javascript/mastodon/locales/oc.json b/app/javascript/mastodon/locales/oc.json index e2a5d7c59..5e5e28af0 100644 --- a/app/javascript/mastodon/locales/oc.json +++ b/app/javascript/mastodon/locales/oc.json @@ -45,24 +45,24 @@ "column_subheading.settings": "Paramètres", "compose_form.lock_disclaimer": "Vòstre compte es pas {locked}. Tot lo mond pòt vos sègre e veire los estatuts reservats als seguidors.", "compose_form.lock_disclaimer.lock": "clavat", - "compose_form.placeholder": "A de qué pensatz ?", - "compose_form.privacy_disclaimer": "Vòstre estatut privat serà enviat a las personas mencionadas sus {domains}. Vos fisatz d’aqueste {domainsCount, plural, one { servidor} other {s servidors}} per divulgar pas vòstre estatut ? Los estatuts privats foncionan pas que sus las instàncias de Mastodon. Se {domains} {domainsCount, plural, one {es pas una instància a Mastodon} other {son pas d'instàncias a Mastodon}}, i aurà pas d’indicacion disent que vòstre estatut es privat e poirà èsser partejat o èsser visible a de mond pas prevists", + "compose_form.placeholder": "A de qué pensatz ?", + "compose_form.privacy_disclaimer": "Vòstre estatut privat serà enviat a las personas mencionadas sus {domains}. Vos fisatz d’aqueste {domainsCount, plural, one { servidor} other {s servidors}} per divulgar pas vòstre estatut ? Los estatuts privats foncionan pas que sus las instàncias de Mastodon. Se {domains} {domainsCount, plural, one {es pas una instància a Mastodon} other {son pas d'instàncias a Mastodon}}, i aurà pas d’indicacion disent que vòstre estatut es privat e poirà èsser partejat o èsser visible a de mond pas prevists", "compose_form.publish": "Tut", - "compose_form.publish_loud": "{publish} !", + "compose_form.publish_loud": "{publish} !", "compose_form.sensitive": "Marcar lo mèdia coma sensible", "compose_form.spoiler": "Rescondre lo tèxte darrièr un avertiment", "compose_form.spoiler_placeholder": "Escrivètz l’avertiment aquí", "confirmation_modal.cancel": "Anullar", "confirmations.block.confirm": "Blocar", - "confirmations.block.message": "Sètz segur de voler blocar {name} ?", + "confirmations.block.message": "Sètz segur de voler blocar {name} ?", "confirmations.delete.confirm": "Suprimir", - "confirmations.delete.message": "Sètz segur de voler suprimir l’estatut ?", + "confirmations.delete.message": "Sètz segur de voler suprimir l’estatut ?", "confirmations.domain_block.confirm": "Amagar tot lo domeni", - "confirmations.domain_block.message": "Sètz segur segur de voler blocar completament {domain} ? De còps cal pas que blocar o rescondre unas personas solament.", + "confirmations.domain_block.message": "Sètz segur segur de voler blocar completament {domain} ? De còps cal pas que blocar o rescondre unas personas solament.", "confirmations.mute.confirm": "Metre en silenci", - "confirmations.mute.message": "Sètz segur de voler metre en silenci {name} ?", + "confirmations.mute.message": "Sètz segur de voler metre en silenci {name} ?", "confirmations.unfollow.confirm": "Quitar de sègre", - "confirmations.unfollow.message": "Volètz vertadièrament quitar de sègre {name} ?", + "confirmations.unfollow.message": "Volètz vertadièrament quitar de sègre {name} ?", "emoji_button.activity": "Activitats", "emoji_button.flags": "Drapèus", "emoji_button.food": "Beure e manjar", @@ -73,13 +73,13 @@ "emoji_button.search": "Cercar…", "emoji_button.symbols": "Simbòls", "emoji_button.travel": "Viatges & lòcs", - "empty_column.community": "Lo flux public local es void. Escrivètz quicòm per lo garnir !", + "empty_column.community": "Lo flux public local es void. Escrivètz quicòm per lo garnir !", "empty_column.hashtag": "I a pas encara de contengut ligat a aqueste hashtag", "empty_column.home": "Pel moment seguètz pas degun. Visitatz {public} o utilizatz la recèrca per vos connectar a d’autras personas.", "empty_column.home.inactivity": "Vòstra pagina d’acuèlh es voida. Se sètz estat inactiu per un moment, serà tornada generar per vos dins una estona.", "empty_column.home.public_timeline": "lo flux public", "empty_column.notifications": "Avètz pas encara de notificacions. Respondètz a qualqu’un per començar una conversacion.", - "empty_column.public": "I a pas res aquí ! Escrivètz quicòm de public, o seguètz de personas d’autras instàncias per garnir lo flux public.", + "empty_column.public": "I a pas res aquí ! Escrivètz quicòm de public, o seguètz de personas d’autras instàncias per garnir lo flux public.", "follow_request.authorize": "Autorizar", "follow_request.reject": "Regetar", "getting_started.appsshort": "Apps", @@ -109,19 +109,19 @@ "navigation_bar.mutes": "Personas rescondudas", "navigation_bar.preferences": "Preferéncias", "navigation_bar.public_timeline": "Flux public global", - "notification.favourite": "{name} a ajustat a sos favorits :", + "notification.favourite": "{name} a ajustat a sos favorits :", "notification.follow": "{name} vos sèc", - "notification.mention": "{name} vos a mencionat :", - "notification.reblog": "{name} a partejat vòstre estatut :", + "notification.mention": "{name} vos a mencionat :", + "notification.reblog": "{name} a partejat vòstre estatut :", "notifications.clear": "Escafar", - "notifications.clear_confirmation": "Volètz vertadièrament escafar totas vòstras las notificacions ?", + "notifications.clear_confirmation": "Volètz vertadièrament escafar totas vòstras las notificacions ?", "notifications.column_settings.alert": "Notificacions localas", - "notifications.column_settings.favourite": "Favorits :", - "notifications.column_settings.follow": "Nòus seguidors :", - "notifications.column_settings.mention": "Mencions :", + "notifications.column_settings.favourite": "Favorits :", + "notifications.column_settings.follow": "Nòus seguidors :", + "notifications.column_settings.mention": "Mencions :", "notifications.column_settings.push": "Notificacions", "notifications.column_settings.push_meta": "Aqueste periferic", - "notifications.column_settings.reblog": "Partatges :", + "notifications.column_settings.reblog": "Partatges :", "notifications.column_settings.show": "Mostrar dins la colomna", "notifications.column_settings.sound": "Emetre un son", "onboarding.done": "Fach", @@ -131,14 +131,14 @@ "onboarding.page_four.notifications": "La colomna de notificacions vos fa veire quand qualqu’un interagís amb vos", "onboarding.page_one.federation": "Mastodon es un malhum de servidors independents que comunican per bastir un malhum ma larg. Òm los apèla instàncias.", "onboarding.page_one.handle": "Sètz sus {domain}, doncas vòstre identificant complet es {handle}", - "onboarding.page_one.welcome": "Benvengut a Mastodon !", + "onboarding.page_one.welcome": "Benvengut a Mastodon !", "onboarding.page_six.admin": "Vòstre administrator d’instància es {admin}.", "onboarding.page_six.almost_done": "Gaireben acabat…", "onboarding.page_six.appetoot": "Bon Appetut!", "onboarding.page_six.apps_available": "I a d’aplicacions per mobil per iOS, Android e mai.", "onboarding.page_six.github": "Mastodon es un logicial liure e open-source. Podètz senhalar de bugs, demandar de foncionalitats e contribuir al còdi sus {github}.", "onboarding.page_six.guidelines": "guida de la comunitat", - "onboarding.page_six.read_guidelines": "Mercés de legir la {guidelines} a {domain} !", + "onboarding.page_six.read_guidelines": "Mercés de legir la {guidelines} a {domain} !", "onboarding.page_six.various_app": "aplicacions per mobil", "onboarding.page_three.profile": "Modificatz vòstre perfil per cambiar vòstre avatar, bio e escais-nom. I a enlà totas las preferéncias.", "onboarding.page_three.search": "Emplegatz la barra de recèrca per trobar de mond e engachatz las etiquetas coma {illustration} e {introductions}. Per trobar una persona d’una autra instància, picatz son identificant complet.", @@ -169,7 +169,7 @@ "status.mute_conversation": "Rescondre la conversacion", "status.open": "Desplegar aqueste estatut", "status.reblog": "Partejar", - "status.reblogged_by": "{name} a partejat :", + "status.reblogged_by": "{name} a partejat :", "status.reply": "Respondre", "status.replyAll": "Respondre a la conversacion", "status.report": "Senhalar @{name}", diff --git a/config/locales/devise.oc.yml b/config/locales/devise.oc.yml index 77740f230..99e62a10e 100644 --- a/config/locales/devise.oc.yml +++ b/config/locales/devise.oc.yml @@ -19,11 +19,11 @@ oc: confirmation_instructions: subject: "Mercés de confirmar vòstra inscripcion sus %{instance}" password_change: - subject: 'Mastodon : senhal cambiat' + subject: 'Mastodon : senhal cambiat' reset_password_instructions: - subject: 'Mastodon : instruccions per reïnicializar lo senhal' + subject: 'Mastodon : instruccions per reïnicializar lo senhal' unlock_instructions: - subject: 'Mastodon : instuccions de desblocatge' + subject: 'Mastodon : instuccions de desblocatge' omniauth_callbacks: failure: Fracàs al moment de vos autentificar de %{kind} perque "%{reason}". success: Sètz ben autentificat dempuèi lo compte %{kind}. @@ -34,8 +34,8 @@ oc: updated: Vòstre senhal es ben estat cambiat. Sètz ara connectat. updated_not_active: Vòstre senhal es ben estat cambiat. registrations: - destroyed: Adiu ! Vòstra inscripcion es estada anullada amb succès. Esperem vos tornar veire lèu. - signed_up: La benvenguda ! Sètz ben marcat al malhum. + destroyed: Adiu ! Vòstra inscripcion es estada anullada amb succès. Esperem vos tornar veire lèu. + signed_up: La benvenguda ! Sètz ben marcat al malhum. signed_up_but_inactive: Sètz ben marcat. Pasmens, avèm pas pogut vos connectar perque vòstre compte es pas encara validat. signed_up_but_locked: Sètz ben marcat. Pasmens, avèm pas pogut vos connectar perque vòstre compte es pas encara blocat. signed_up_but_unconfirmed: Un messatge amb un ligam de confirmacion es estat enviat a vòstra adreça de corrièl. Clicatz sul ligam per activar vòstre compte. Mercés de verificar tanben vòstre dorsièr de corrièls indesirables. @@ -57,5 +57,5 @@ oc: not_found: pas trobat not_locked: èra pas blocat not_saved: - one: '1 error defend aquesta %{resource} d’èsser salvagardada :' - other: "%{count} errors defendon aquesta %{resource} d’èsser salvagardadas :" + one: '1 error defend aquesta %{resource} d’èsser salvagardada :' + other: "%{count} errors defendon aquesta %{resource} d’èsser salvagardadas :" diff --git a/config/locales/doorkeeper.oc.yml b/config/locales/doorkeeper.oc.yml index 9f5d3fe55..3d12c9588 100644 --- a/config/locales/doorkeeper.oc.yml +++ b/config/locales/doorkeeper.oc.yml @@ -23,11 +23,11 @@ oc: edit: Modificar submit: Mandar confirmations: - destroy: Sètz segur ? + destroy: Sètz segur ? edit: title: Modificar l’aplicacion form: - error: Ops ! Verificatz vòstre formulari + error: Ops ! Verificatz vòstre formulari help: native_redirect_uri: Emplegatz %{native_redirect_uri} per d’ensages locales redirect_uri: Utilizatz una linha per URI @@ -45,7 +45,7 @@ oc: callback_urls: urls de rapèls scopes: Encastres secret: Secret - title: 'Aplicacion : %{name}' + title: 'Aplicacion : %{name}' authorizations: buttons: authorize: Autorizar @@ -62,7 +62,7 @@ oc: buttons: revoke: Revocar confirmations: - revoke: Ne sètz segur ? + revoke: Ne sètz segur ? index: application: Aplicacion created_at: Creada lo diff --git a/config/locales/oc.yml b/config/locales/oc.yml index 65ea4525a..35eb79b33 100644 --- a/config/locales/oc.yml +++ b/config/locales/oc.yml @@ -7,7 +7,7 @@ oc: contact: Contacte contact_missing: Pas parametrat contact_unavailable: Pas disponible - description_headline: Qué es %{domain} ? + description_headline: Qué es %{domain} ? domain_count_after: autras instàncias domain_count_before: Connectat a extended_description_html: | @@ -32,13 +32,13 @@ oc: status_count_before: qu’an escrich user_count_after: personas user_count_before: Ostal de - what_is_mastodon: Qu’es Mastodon ? + what_is_mastodon: Qu’es Mastodon ? accounts: follow: Sègre followers: Seguidors following: Abonaments media: Mèdias - nothing_here: I a pas res aquí ! + nothing_here: I a pas res aquí ! people_followed_by: Lo mond que %{name} sèc people_who_follow: Lo mond que sègon %{name} posts: Tuts @@ -50,7 +50,7 @@ oc: unfollow: Quitar de sègre admin: accounts: - are_you_sure: Sètz segur ? + are_you_sure: Sètz segur ? confirm: Confirmar confirmed: Confirmat disable_two_factor_authentication: Desactivar 2FA @@ -143,7 +143,7 @@ oc: title: Instàncias conegudas reports: action_taken_by: Mesura menada per - are_you_sure: Es segur ? + are_you_sure: Es segur ? comment: label: Comentari none: Pas cap @@ -222,18 +222,24 @@ oc: subject: Novèl senhalament per %{instance} (#%{id}) application_mailer: salutation: "%{name}," - settings: 'Cambiar las preferéncias de corrièl : %{link}' + settings: 'Cambiar las preferéncias de corrièl : %{link}' signature: Notificacion de Mastodon sus %{instance} - view: 'Veire :' + view: 'Veire :' applications: + created: Aplicacion ben creada + destroyed: Aplication ben suprimida invalid_url: L’URL donada es invalida + regenerate_token: Tornar generar lo geton d’accès + token_regenerated: Geton d’accès ben regenerat + warning: Mèfi ! Agachatz de partejar aquela donada amb degun ! + your_token: Vòstre geton d’accès auth: agreement_html: En vos marcar acceptatz nòstres tèrmes de servici e politica de confidencialitat. change_password: Seguretat delete_account: Suprimir lo compte delete_account_html: Se volètz suprimir vòstre compte, podètz o far aquí. Vos demandarem que confirmetz. - didnt_get_confirmation: Avètz pas recebut las instruccions de confirmacion ? - forgot_password: Senhal oblidat ? + didnt_get_confirmation: Avètz pas recebut las instruccions de confirmacion ? + forgot_password: Senhal oblidat ? invalid_reset_password_token: Lo geton de reïnicializacion es invalid o acabat. Tornatz demandar un geton se vos plai. login: Se connectar logout: Se desconnectar @@ -244,8 +250,8 @@ oc: authorize_follow: error: O planhèm, i a agut una error al moment de cercar lo compte follow: Sègre - follow_request: 'Avètz demandat de sègre :' - following: 'Felicitacion ! Seguètz ara :' + follow_request: 'Avètz demandat de sègre :' + following: 'Felicitacion ! Seguètz ara :' post_follow: close: O podètz tampar aquesta fenèstra. return: Tornar al perfil @@ -344,7 +350,7 @@ oc: one: Fa un an other: Fa %{count} ans deletes: - bad_password_msg: Ben ensajat pirata ! Senhal incorrècte + bad_password_msg: Ben ensajat pirata ! Senhal incorrècte confirm_password: Picatz vòstre senhal actual per verificar vòstra identitat description_html: Aquò suprimirà definitivament e sens possibilitat de retorn lo contengut de vòstre compte e lo desactivarà. Lo nom d’utilizaire serà gardat per evitar una futura impostura. proceed: Suprimir lo compte @@ -356,7 +362,7 @@ oc: '404': La pagina que recercatz existís pas. '410': La pagina que cercatz existís pas mai. '422': - content: Verificacion de seguretat fracassada. Blocatz los cookies ? + content: Verificacion de seguretat fracassada. Blocatz los cookies ? title: Verificacion de seguretat fracassada '429': Lo servidor mòla (subrecargada) noscript: Per utilizar l’aplicacion Mastodon, mercés d’activar JavaScript. Autrament podètz utilizar una aplicacion nativa Mastodon per vòstra plataforma. @@ -373,18 +379,18 @@ oc: lock_link: Clavar vòstre compte purge: Tirar dels seguidors success: - one: Soi a blocar los seguidors d’un domeni... - other: Soi a blocar los seguidors de %{count} domenis... + one: Soi a blocar los seguidors d’un domeni… + other: Soi a blocar los seguidors de %{count} domenis… true_privacy_html: Mèfi que la vertadièra confidencialitat pòt solament èsser amb un chiframent del cap a la fin (end-to-end). unlocked_warning_html: Tot lo mond pòt vos sègre e veire sulpic vòstres estatuts privats. %{lock_link} per poder repassar e regetar los seguidors. unlocked_warning_title: Vòstre compte es pas clavat generic: - changes_saved_msg: Cambiaments ben realizats ! + changes_saved_msg: Cambiaments ben realizats ! powered_by: propulsat per %{link} save_changes: Salvagardar los cambiaments validation_errors: - one: I a quicòm que truca ! Mercés de corregir l’error çai-jos - other: I a quicòm que truca ! Mercés de corregir las %{count} errors çai-jos + one: I a quicòm que truca ! Mercés de corregir l’error çai-jos + other: I a quicòm que truca ! Mercés de corregir las %{count} errors çai-jos imports: preface: Podètz importar qualques donadas coma lo mond que seguètz o blocatz a-n aquesta instància d’un fichièr creat d’una autra instància. success: Vòstras donadas son ben estadas mandadas e seràn tractadas tre que possible @@ -402,27 +408,27 @@ oc: notification_mailer: digest: body: 'Trobatz aquí un resumit de çò qu’avètz mancat dempuèi vòstra darrièra visita lo %{since}:' - mention: "%{name} vos a mencionat dins :" + mention: "%{name} vos a mencionat dins :" new_followers_summary: - one: Avètz un nòu seguidor ! Ouà ! - other: Avètz %{count} nòus seguidors ! Qué crane ! + one: Avètz un nòu seguidor ! Ouà   + other: Avètz %{count} nòus seguidors ! Qué crane ! subject: one: "Una nòva notificacion dempuèi vòstra darrièra visita \U0001F418" other: "%{count} nòvas notificacions dempuèi vòstra darrièra visita \U0001F418" favourite: - body: "%{name} a mes vòstre estatut en favorit :" + body: "%{name} a mes vòstre estatut en favorit :" subject: "%{name} a mes vòstre estatut en favorit" follow: - body: "%{name} vos sèc ara !" + body: "%{name} vos sèc ara !" subject: "%{name} vos sèc ara" follow_request: body: "%{name} a demandat a vos sègre" - subject: 'Demanda d’abonament : %{name}' + subject: 'Demanda d’abonament : %{name}' mention: - body: "%{name} vos a mencionat dins :" + body: "%{name} vos a mencionat dins :" subject: "%{name} vos a mencionat" reblog: - body: "%{name} a tornat partejar vòstre estatut :" + body: "%{name} a tornat partejar vòstre estatut :" subject: "%{name} a tornat partejar vòstre estatut" pagination: next: Seguent @@ -444,12 +450,12 @@ oc: title: "%{name} a partejat vòstre estatut" subscribed: body: Podètz ara recebre las notificacions push. - title: Abonament enregistrat ! + title: Abonament enregistrat ! remote_follow: acct: Picatz vòstre utilizaire@instància que cal utilizar per sègre aqueste utilizaire missing_resource: URL de redireccion pas trobada proceed: Contunhatz per sègre - prompt: 'Sètz per sègre :' + prompt: 'Sètz per sègre :' sessions: activity: Darrièra activitat browser: Navigator @@ -493,6 +499,7 @@ oc: authorized_apps: Aplicacions autorizadas back: Tornar a Mastodon delete: Supression de compte + development: Desvolopament edit_profile: Modificar lo perfil export: Export donadas followers: Seguidors autorizats @@ -500,6 +507,7 @@ oc: preferences: Preferéncias settings: Paramètres two_factor_authentication: Autentificacion en dos temps + your_apps: Vòstras aplicacions statuses: open_in_web: Dobrir sul web over_character_limit: limit de %{max} caractèrs passat @@ -519,7 +527,7 @@ oc: body_html: |

Politica de confidencialitat

-

Quinas informacions reculhèm ?

+

Quinas informacions reculhèm ?

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

@@ -527,7 +535,7 @@ oc:

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

-

Qué fasèm de vòstras informacions ?

+

Qué fasèm de vòstras informacions ?

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

@@ -538,26 +546,26 @@ oc:
  • Per enviar periodicament de corrièls — Podèm utilizar l’adreça qu’avètz donada per vos enviar d’informacions e de notificacions que demandatz tocant de cambiaments dins los subjèctes del forum o en responsa a vòstre nom d’utilizaire, en responsa a una demanda, e/o tota autra question.
  • -

    Cossí protegèm vòstras informacions ?

    +

    Cossí protegèm vòstras informacions ?

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

    -

    Quala es vòstra politica de conservacion de donadas ?

    +

    Quala es vòstra politica de conservacion de donadas ?

    -

    Farem esfòrces per :

    +

    Farem esfòrces per :

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

    Empleguem de cookies ?

    +

    Empleguem de cookies ?

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

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

    -

    Divulguem d’informacions a de tèrces ?

    +

    Divulguem d’informacions a de tèrces ?

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

    @@ -593,16 +601,16 @@ oc: description_html: S’activatz l’autentificacion two-factor, vos caldrà vòstre mobil per vos connectar perque generarà un geton per vos daissar dintrar. disable: Desactivar enable: Activar - enabled_success: Autentificacion en dos temps Two-factor ben activada + enabled: L’autentificacion en dos temps es activada generate_recovery_codes: Generar los còdis de recuperacion instructions_html: "Escanatz aqueste còdi QR amb Google Authenticator o una aplicacion similària sus vòstre mobil. A partir d’ara, aquesta aplicacion generarà un geton que vos caldrà picar per vos connectar." lost_recovery_codes: Los còdi de recuperacion vos permeton d’accedir a vòstre compte se perdètz vòstre mobil. S’avètz perdut vòstres còdis de recuperacion los podètz tornar generar aquí. Los ancians còdis seràn pas mai valides. - manual_instructions: 'Se podètz pas numerizar lo còdi QR e que vos cal picar lo còdi a la man, vaquí lo còdi en clar :' + manual_instructions: 'Se podètz pas numerizar lo còdi QR e que vos cal picar lo còdi a la man, vaquí lo còdi en clar :' recovery_codes: Salvar los còdis de recuperacion recovery_codes_regenerated: Los còdis de recuperacion son ben estats tornats generar recovery_instructions_html: Se vos arriba de perdre vòstre mobil, podètz utilizar un dels còdis de recuperacion cai-jos per poder tornar accedir a vòstre compte. Gardatz los còdis en seguretat, per exemple, imprimissètz los e gardatz los amb vòstres documents importants. setup: Paramètres - wrong_code: Lo còdi picat es invalid ! L’ora es la bona sul servidor e lo mobil ? + wrong_code: Lo còdi picat es invalid ! L’ora es la bona sul servidor e lo mobil ? users: invalid_email: L’adreça de corrièl es invalida invalid_otp_token: Còdi d’autentificacion en dos temps invalid -- cgit From fbe1115114edb2d46c05b5065398b7234b9fb6a0 Mon Sep 17 00:00:00 2001 From: Yamagishi Kazutoshi Date: Thu, 24 Aug 2017 19:15:36 +0900 Subject: Remove eslint-disable comments (#4681) Do not reject console.error and console.warn with ESLint rules. --- .eslintrc.yml | 1 + app/javascript/mastodon/features/ui/index.js | 2 +- app/javascript/mastodon/web_push_subscription.js | 4 ---- 3 files changed, 2 insertions(+), 5 deletions(-) (limited to 'app/javascript') diff --git a/.eslintrc.yml b/.eslintrc.yml index fd2ba46dd..1c60cbdb3 100644 --- a/.eslintrc.yml +++ b/.eslintrc.yml @@ -49,6 +49,7 @@ rules: - warn - allow: - error + - warn no-fallthrough: error no-irregular-whitespace: error no-mixed-spaces-and-tabs: warn diff --git a/app/javascript/mastodon/features/ui/index.js b/app/javascript/mastodon/features/ui/index.js index a791f8947..44243d370 100644 --- a/app/javascript/mastodon/features/ui/index.js +++ b/app/javascript/mastodon/features/ui/index.js @@ -135,7 +135,7 @@ export default class UI extends React.PureComponent { if (data.type === 'navigate') { this.context.router.history.push(data.path); } else { - console.warn('Unknown message type:', data.type); // eslint-disable-line no-console + console.warn('Unknown message type:', data.type); } } diff --git a/app/javascript/mastodon/web_push_subscription.js b/app/javascript/mastodon/web_push_subscription.js index 96ac63b52..3dbed09ea 100644 --- a/app/javascript/mastodon/web_push_subscription.js +++ b/app/javascript/mastodon/web_push_subscription.js @@ -48,7 +48,6 @@ export function register () { if (supportsPushNotifications) { if (!getApplicationServerKey()) { - // eslint-disable-next-line no-console console.error('The VAPID public key is not set. You will not be able to receive Web Push Notifications.'); return; } @@ -84,10 +83,8 @@ export function register () { }) .catch(error => { if (error.code === 20 && error.name === 'AbortError') { - // eslint-disable-next-line no-console console.warn('Your browser supports Web Push Notifications, but does not seem to implement the VAPID protocol.'); } else if (error.code === 5 && error.name === 'InvalidCharacterError') { - // eslint-disable-next-line no-console console.error('The VAPID public key seems to be invalid:', getApplicationServerKey()); } @@ -103,7 +100,6 @@ export function register () { } }); } else { - // eslint-disable-next-line no-console console.warn('Your browser does not support Web Push Notifications.'); } } -- cgit From f72ed21cd62541881c0ad19bac1b78e82f514413 Mon Sep 17 00:00:00 2001 From: Damien Erambert Date: Thu, 24 Aug 2017 10:28:49 -0700 Subject: Don't load Roboto webfont when system font is used in the app (#4591) * Don't load Roboto webfont when system font is used in the app * remove trailing whitespace --- app/javascript/styles/basics.scss | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) (limited to 'app/javascript') diff --git a/app/javascript/styles/basics.scss b/app/javascript/styles/basics.scss index 4e51b555c..e524b7f26 100644 --- a/app/javascript/styles/basics.scss +++ b/app/javascript/styles/basics.scss @@ -1,5 +1,4 @@ body { - font-family: 'mastodon-font-sans-serif', sans-serif; background: $ui-base-color; background-size: cover; background-attachment: fixed; @@ -14,6 +13,11 @@ body { -webkit-tap-highlight-color: rgba(0,0,0,0); -webkit-tap-highlight-color: transparent; + // This is done because we want to use mastodon-font-sans-serif (a.k.a Roboto) on the `.ui` element in the app UI + &:not(.app-body) { + font-family: 'mastodon-font-sans-serif', sans-serif; + } + &.app-body { position: fixed; width: 100%; @@ -69,7 +73,7 @@ button { justify-content: center; } -.system-font { +.ui.system-font { // system-ui => standard property (Chrome/Android WebView 56+, Opera 43+, Safari 11+) // -apple-system => Safari <11 specific // BlinkMacSystemFont => Chrome <56 on macOS specific @@ -83,3 +87,7 @@ button { // mastodon-font-sans-serif => web-font (Roboto) fallback and newer Androids (>=4.0) font-family: system-ui, -apple-system,BlinkMacSystemFont, "Segoe UI","Oxygen", "Ubuntu","Cantarell","Fira Sans","Droid Sans","Helvetica Neue",mastodon-font-sans-serif, sans-serif; } + +.ui:not(.system-font) { + font-family: 'mastodon-font-sans-serif', sans-serif; +} -- cgit From c5157ef07bbae5c3a307d6a005aef0f1c0452af3 Mon Sep 17 00:00:00 2001 From: Ratmir Karabut Date: Fri, 25 Aug 2017 01:11:06 +0300 Subject: Update Russian translation (#4685) * Add Russian translation (ru) * Fix a missing comma * Fix the wording for better consistency * Update Russian translation * Arrange Russian setting alphabetically * Fix syntax error * Update Russian translation * Fix formatting error * Update Russian translation * Update Russian translation * Update ru.jsx * Fix syntax error * Remove two_factor_auth.warning (appears obsolete) * Add missing strings in ru.yml A lot of new strings translated, especially for the newly added admin section * Fix translation consistency * Update Russian translation * Update Russian translation (pluralizations) * Update Russian translation * Update Russian translation * Update Russian translation (pin) * Update Russian translation (account deletion) * Fix extra line * Update Russian translation (sessions) * Update Russian translation * Update Russian translation * Fix merge conflicts (revert) --- app/javascript/mastodon/locales/ru.json | 38 ++++++++++++++++----------------- 1 file changed, 19 insertions(+), 19 deletions(-) (limited to 'app/javascript') diff --git a/app/javascript/mastodon/locales/ru.json b/app/javascript/mastodon/locales/ru.json index 1abfb4370..af38fc723 100644 --- a/app/javascript/mastodon/locales/ru.json +++ b/app/javascript/mastodon/locales/ru.json @@ -1,7 +1,7 @@ { "account.block": "Блокировать", "account.block_domain": "Блокировать все с {domain}", - "account.disclaimer_full": "Information below may reflect the user's profile incompletely.", + "account.disclaimer_full": "Нижеуказанная информация может не полностью отражать профиль пользователя.", "account.edit_profile": "Изменить профиль", "account.follow": "Подписаться", "account.followers": "Подписаны", @@ -13,19 +13,19 @@ "account.posts": "Посты", "account.report": "Пожаловаться", "account.requested": "Ожидает подтверждения", - "account.share": "Share @{name}'s profile", + "account.share": "Поделиться профилем @{name}", "account.unblock": "Разблокировать", "account.unblock_domain": "Разблокировать {domain}", "account.unfollow": "Отписаться", "account.unmute": "Снять глушение", - "account.view_full_profile": "View full profile", + "account.view_full_profile": "Показать полный профиль", "boost_modal.combo": "Нажмите {combo}, чтобы пропустить это в следующий раз", - "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", + "bundle_column_error.body": "Что-то пошло не так при загрузке этого компонента.", + "bundle_column_error.retry": "Попробовать снова", + "bundle_column_error.title": "Ошибка сети", + "bundle_modal_error.close": "Закрыть", + "bundle_modal_error.message": "Что-то пошло не так при загрузке этого компонента.", + "bundle_modal_error.retry": "Попробовать снова", "column.blocks": "Список блокировки", "column.community": "Локальная лента", "column.favourites": "Понравившееся", @@ -35,11 +35,11 @@ "column.notifications": "Уведомления", "column.public": "Глобальная лента", "column_back_button.label": "Назад", - "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.hide_settings": "Скрыть настройки", + "column_header.moveLeft_settings": "Передвинуть колонку влево", + "column_header.moveRight_settings": "Передвинуть колонку вправо", "column_header.pin": "Закрепить", - "column_header.show_settings": "Show settings", + "column_header.show_settings": "Показать настройки", "column_header.unpin": "Открепить", "column_subheading.navigation": "Навигация", "column_subheading.settings": "Настройки", @@ -61,8 +61,8 @@ "confirmations.domain_block.message": "Вы на самом деле уверены, что хотите блокировать весь {domain}? В большинстве случаев нескольких отдельных блокировок или глушений достаточно.", "confirmations.mute.confirm": "Заглушить", "confirmations.mute.message": "Вы уверены, что хотите заглушить {name}?", - "confirmations.unfollow.confirm": "Unfollow", - "confirmations.unfollow.message": "Are you sure you want to unfollow {name}?", + "confirmations.unfollow.confirm": "Отписаться", + "confirmations.unfollow.message": "Вы уверены, что хотите отписаться от {name}?", "emoji_button.activity": "Занятия", "emoji_button.flags": "Флаги", "emoji_button.food": "Еда и напитки", @@ -94,8 +94,8 @@ "home.column_settings.show_replies": "Показывать ответы", "home.settings": "Настройки колонки", "lightbox.close": "Закрыть", - "lightbox.next": "Next", - "lightbox.previous": "Previous", + "lightbox.next": "Далее", + "lightbox.previous": "Назад", "loading_indicator.label": "Загрузка...", "media_gallery.toggle_visible": "Показать/скрыть", "missing_indicator.label": "Не найдено", @@ -119,8 +119,8 @@ "notifications.column_settings.favourite": "Нравится:", "notifications.column_settings.follow": "Новые подписчики:", "notifications.column_settings.mention": "Упоминания:", - "notifications.column_settings.push": "Push notifications", - "notifications.column_settings.push_meta": "This device", + "notifications.column_settings.push": "Push-уведомления", + "notifications.column_settings.push_meta": "Это устройство", "notifications.column_settings.reblog": "Продвижения:", "notifications.column_settings.show": "Показывать в колонке", "notifications.column_settings.sound": "Проигрывать звук", -- cgit From 9caa90025fd9f1ef46a74f31cefd19335e291e76 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Fri, 25 Aug 2017 01:41:18 +0200 Subject: Pinned statuses (#4675) * Pinned statuses * yarn manage:translations --- app/controllers/accounts_controller.rb | 25 +++++-- .../api/v1/accounts/statuses_controller.rb | 5 ++ app/controllers/api/v1/statuses/pins_controller.rb | 28 ++++++++ app/javascript/mastodon/actions/interactions.js | 78 ++++++++++++++++++++++ app/javascript/mastodon/components/status.js | 1 + .../mastodon/components/status_action_bar.js | 11 +++ .../mastodon/containers/status_container.js | 10 +++ .../features/status/components/action_bar.js | 11 +++ app/javascript/mastodon/features/status/index.js | 11 +++ app/javascript/mastodon/locales/ar.json | 2 + app/javascript/mastodon/locales/bg.json | 2 + app/javascript/mastodon/locales/ca.json | 2 + app/javascript/mastodon/locales/de.json | 2 + .../mastodon/locales/defaultMessages.json | 16 +++++ app/javascript/mastodon/locales/en.json | 2 + app/javascript/mastodon/locales/eo.json | 2 + app/javascript/mastodon/locales/es.json | 2 + app/javascript/mastodon/locales/fa.json | 2 + app/javascript/mastodon/locales/fi.json | 2 + app/javascript/mastodon/locales/fr.json | 2 + app/javascript/mastodon/locales/he.json | 2 + app/javascript/mastodon/locales/hr.json | 2 + app/javascript/mastodon/locales/hu.json | 2 + app/javascript/mastodon/locales/id.json | 2 + app/javascript/mastodon/locales/io.json | 2 + app/javascript/mastodon/locales/it.json | 2 + app/javascript/mastodon/locales/ja.json | 2 + app/javascript/mastodon/locales/ko.json | 2 + app/javascript/mastodon/locales/nl.json | 2 + app/javascript/mastodon/locales/no.json | 2 + app/javascript/mastodon/locales/oc.json | 2 + app/javascript/mastodon/locales/pl.json | 2 + app/javascript/mastodon/locales/pt-BR.json | 2 + app/javascript/mastodon/locales/pt.json | 2 + app/javascript/mastodon/locales/ru.json | 2 + app/javascript/mastodon/locales/th.json | 2 + app/javascript/mastodon/locales/tr.json | 2 + app/javascript/mastodon/locales/uk.json | 2 + app/javascript/mastodon/locales/zh-CN.json | 2 + app/javascript/mastodon/locales/zh-HK.json | 2 + app/javascript/mastodon/locales/zh-TW.json | 2 + app/javascript/mastodon/reducers/statuses.js | 4 ++ app/models/account.rb | 4 ++ app/models/concerns/account_interactions.rb | 4 ++ app/models/status.rb | 4 ++ app/models/status_pin.rb | 16 +++++ app/presenters/status_relationships_presenter.rb | 19 ++++-- app/serializers/rest/status_serializer.rb | 16 +++++ app/validators/status_pin_validator.rb | 9 +++ app/views/accounts/show.html.haml | 3 + app/views/stream_entries/_status.html.haml | 7 ++ config/locales/en.yml | 5 ++ config/routes.rb | 13 ++-- db/migrate/20170823162448_create_status_pins.rb | 10 +++ db/schema.rb | 12 +++- .../api/v1/accounts/statuses_controller_spec.rb | 36 +++++++--- .../api/v1/statuses/pins_controller_spec.rb | 57 ++++++++++++++++ spec/fabricators/status_pin_fabricator.rb | 4 ++ spec/models/status_pin_spec.rb | 41 ++++++++++++ 59 files changed, 493 insertions(+), 29 deletions(-) create mode 100644 app/controllers/api/v1/statuses/pins_controller.rb create mode 100644 app/models/status_pin.rb create mode 100644 app/validators/status_pin_validator.rb create mode 100644 db/migrate/20170823162448_create_status_pins.rb create mode 100644 spec/controllers/api/v1/statuses/pins_controller_spec.rb create mode 100644 spec/fabricators/status_pin_fabricator.rb create mode 100644 spec/models/status_pin_spec.rb (limited to 'app/javascript') diff --git a/app/controllers/accounts_controller.rb b/app/controllers/accounts_controller.rb index c6b98628e..f4ca239ba 100644 --- a/app/controllers/accounts_controller.rb +++ b/app/controllers/accounts_controller.rb @@ -7,14 +7,17 @@ class AccountsController < ApplicationController def show respond_to do |format| format.html do + @pinned_statuses = [] + if current_account && @account.blocking?(current_account) @statuses = [] return end - @statuses = filtered_statuses.paginate_by_max_id(20, params[:max_id], params[:since_id]) - @statuses = cache_collection(@statuses, Status) - @next_url = next_url unless @statuses.empty? + @pinned_statuses = cache_collection(@account.pinned_statuses.limit(1), Status) unless media_requested? + @statuses = filtered_statuses.paginate_by_max_id(20, params[:max_id], params[:since_id]) + @statuses = cache_collection(@statuses, Status) + @next_url = next_url unless @statuses.empty? end format.atom do @@ -32,8 +35,8 @@ class AccountsController < ApplicationController def filtered_statuses default_statuses.tap do |statuses| - statuses.merge!(only_media_scope) if request.path.ends_with?('/media') - statuses.merge!(no_replies_scope) unless request.path.ends_with?('/with_replies') + statuses.merge!(only_media_scope) if media_requested? + statuses.merge!(no_replies_scope) unless replies_requested? end end @@ -58,12 +61,20 @@ class AccountsController < ApplicationController end def next_url - if request.path.ends_with?('/media') + if media_requested? short_account_media_url(@account, max_id: @statuses.last.id) - elsif request.path.ends_with?('/with_replies') + elsif replies_requested? short_account_with_replies_url(@account, max_id: @statuses.last.id) else short_account_url(@account, max_id: @statuses.last.id) end end + + def media_requested? + request.path.ends_with?('/media') + end + + def replies_requested? + request.path.ends_with?('/with_replies') + end end diff --git a/app/controllers/api/v1/accounts/statuses_controller.rb b/app/controllers/api/v1/accounts/statuses_controller.rb index d9ae5c089..095f6937b 100644 --- a/app/controllers/api/v1/accounts/statuses_controller.rb +++ b/app/controllers/api/v1/accounts/statuses_controller.rb @@ -29,6 +29,7 @@ class Api::V1::Accounts::StatusesController < Api::BaseController def account_statuses default_statuses.tap do |statuses| statuses.merge!(only_media_scope) if params[:only_media] + statuses.merge!(pinned_scope) if params[:pinned] statuses.merge!(no_replies_scope) if params[:exclude_replies] end end @@ -53,6 +54,10 @@ class Api::V1::Accounts::StatusesController < Api::BaseController @account.media_attachments.attached.reorder(nil).select(:status_id).distinct end + def pinned_scope + @account.pinned_statuses + end + def no_replies_scope Status.without_replies end diff --git a/app/controllers/api/v1/statuses/pins_controller.rb b/app/controllers/api/v1/statuses/pins_controller.rb new file mode 100644 index 000000000..3de1009b8 --- /dev/null +++ b/app/controllers/api/v1/statuses/pins_controller.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +class Api::V1::Statuses::PinsController < Api::BaseController + include Authorization + + before_action -> { doorkeeper_authorize! :write } + before_action :require_user! + before_action :set_status + + respond_to :json + + def create + StatusPin.create!(account: current_account, status: @status) + render json: @status, serializer: REST::StatusSerializer + end + + def destroy + pin = StatusPin.find_by(account: current_account, status: @status) + pin&.destroy! + render json: @status, serializer: REST::StatusSerializer + end + + private + + def set_status + @status = Status.find(params[:status_id]) + end +end diff --git a/app/javascript/mastodon/actions/interactions.js b/app/javascript/mastodon/actions/interactions.js index 36eec4934..7b5f4bd9c 100644 --- a/app/javascript/mastodon/actions/interactions.js +++ b/app/javascript/mastodon/actions/interactions.js @@ -24,6 +24,14 @@ export const FAVOURITES_FETCH_REQUEST = 'FAVOURITES_FETCH_REQUEST'; export const FAVOURITES_FETCH_SUCCESS = 'FAVOURITES_FETCH_SUCCESS'; export const FAVOURITES_FETCH_FAIL = 'FAVOURITES_FETCH_FAIL'; +export const PIN_REQUEST = 'PIN_REQUEST'; +export const PIN_SUCCESS = 'PIN_SUCCESS'; +export const PIN_FAIL = 'PIN_FAIL'; + +export const UNPIN_REQUEST = 'UNPIN_REQUEST'; +export const UNPIN_SUCCESS = 'UNPIN_SUCCESS'; +export const UNPIN_FAIL = 'UNPIN_FAIL'; + export function reblog(status) { return function (dispatch, getState) { dispatch(reblogRequest(status)); @@ -233,3 +241,73 @@ export function fetchFavouritesFail(id, error) { error, }; }; + +export function pin(status) { + return (dispatch, getState) => { + dispatch(pinRequest(status)); + + api(getState).post(`/api/v1/statuses/${status.get('id')}/pin`).then(response => { + dispatch(pinSuccess(status, response.data)); + }).catch(error => { + dispatch(pinFail(status, error)); + }); + }; +}; + +export function pinRequest(status) { + return { + type: PIN_REQUEST, + status, + }; +}; + +export function pinSuccess(status, response) { + return { + type: PIN_SUCCESS, + status, + response, + }; +}; + +export function pinFail(status, error) { + return { + type: PIN_FAIL, + status, + error, + }; +}; + +export function unpin (status) { + return (dispatch, getState) => { + dispatch(unpinRequest(status)); + + api(getState).post(`/api/v1/statuses/${status.get('id')}/unpin`).then(response => { + dispatch(unpinSuccess(status, response.data)); + }).catch(error => { + dispatch(unpinFail(status, error)); + }); + }; +}; + +export function unpinRequest(status) { + return { + type: UNPIN_REQUEST, + status, + }; +}; + +export function unpinSuccess(status, response) { + return { + type: UNPIN_SUCCESS, + status, + response, + }; +}; + +export function unpinFail(status, error) { + return { + type: UNPIN_FAIL, + status, + error, + }; +}; diff --git a/app/javascript/mastodon/components/status.js b/app/javascript/mastodon/components/status.js index 38a4aafc1..b4f523f72 100644 --- a/app/javascript/mastodon/components/status.js +++ b/app/javascript/mastodon/components/status.js @@ -31,6 +31,7 @@ export default class Status extends ImmutablePureComponent { onFavourite: PropTypes.func, onReblog: PropTypes.func, onDelete: PropTypes.func, + onPin: PropTypes.func, onOpenMedia: PropTypes.func, onOpenVideo: PropTypes.func, onBlock: PropTypes.func, diff --git a/app/javascript/mastodon/components/status_action_bar.js b/app/javascript/mastodon/components/status_action_bar.js index 0d8c9add4..6436d6ebe 100644 --- a/app/javascript/mastodon/components/status_action_bar.js +++ b/app/javascript/mastodon/components/status_action_bar.js @@ -21,6 +21,8 @@ const messages = defineMessages({ report: { id: 'status.report', defaultMessage: 'Report @{name}' }, muteConversation: { id: 'status.mute_conversation', defaultMessage: 'Mute conversation' }, unmuteConversation: { id: 'status.unmute_conversation', defaultMessage: 'Unmute conversation' }, + pin: { id: 'status.pin', defaultMessage: 'Pin on profile' }, + unpin: { id: 'status.unpin', defaultMessage: 'Unpin from profile' }, }); @injectIntl @@ -41,6 +43,7 @@ export default class StatusActionBar extends ImmutablePureComponent { onBlock: PropTypes.func, onReport: PropTypes.func, onMuteConversation: PropTypes.func, + onPin: PropTypes.func, me: PropTypes.number, withDismiss: PropTypes.bool, intl: PropTypes.object.isRequired, @@ -77,6 +80,10 @@ export default class StatusActionBar extends ImmutablePureComponent { this.props.onDelete(this.props.status); } + handlePinClick = () => { + this.props.onPin(this.props.status); + } + handleMentionClick = () => { this.props.onMention(this.props.status.get('account'), this.context.router.history); } @@ -121,6 +128,10 @@ export default class StatusActionBar extends ImmutablePureComponent { } if (status.getIn(['account', 'id']) === me) { + if (['public', 'unlisted'].indexOf(status.get('visibility')) !== -1) { + menu.push({ text: intl.formatMessage(status.get('pinned') ? messages.unpin : messages.pin), action: this.handlePinClick }); + } + menu.push({ text: intl.formatMessage(messages.delete), action: this.handleDeleteClick }); } else { menu.push({ text: intl.formatMessage(messages.mention, { name: status.getIn(['account', 'username']) }), action: this.handleMentionClick }); diff --git a/app/javascript/mastodon/containers/status_container.js b/app/javascript/mastodon/containers/status_container.js index b150165aa..c488b6ce7 100644 --- a/app/javascript/mastodon/containers/status_container.js +++ b/app/javascript/mastodon/containers/status_container.js @@ -11,6 +11,8 @@ import { favourite, unreblog, unfavourite, + pin, + unpin, } from '../actions/interactions'; import { blockAccount, @@ -72,6 +74,14 @@ const mapDispatchToProps = (dispatch, { intl }) => ({ } }, + onPin (status) { + if (status.get('pinned')) { + dispatch(unpin(status)); + } else { + dispatch(pin(status)); + } + }, + onDelete (status) { if (!this.deleteModal) { dispatch(deleteStatus(status.get('id'))); diff --git a/app/javascript/mastodon/features/status/components/action_bar.js b/app/javascript/mastodon/features/status/components/action_bar.js index 91ac64de2..c4a614677 100644 --- a/app/javascript/mastodon/features/status/components/action_bar.js +++ b/app/javascript/mastodon/features/status/components/action_bar.js @@ -14,6 +14,8 @@ const messages = defineMessages({ favourite: { id: 'status.favourite', defaultMessage: 'Favourite' }, report: { id: 'status.report', defaultMessage: 'Report @{name}' }, share: { id: 'status.share', defaultMessage: 'Share' }, + pin: { id: 'status.pin', defaultMessage: 'Pin on profile' }, + unpin: { id: 'status.unpin', defaultMessage: 'Unpin from profile' }, }); @injectIntl @@ -31,6 +33,7 @@ export default class ActionBar extends React.PureComponent { onDelete: PropTypes.func.isRequired, onMention: PropTypes.func.isRequired, onReport: PropTypes.func, + onPin: PropTypes.func, me: PropTypes.number.isRequired, intl: PropTypes.object.isRequired, }; @@ -59,6 +62,10 @@ export default class ActionBar extends React.PureComponent { this.props.onReport(this.props.status); } + handlePinClick = () => { + this.props.onPin(this.props.status); + } + handleShare = () => { navigator.share({ text: this.props.status.get('search_index'), @@ -72,6 +79,10 @@ export default class ActionBar extends React.PureComponent { let menu = []; if (me === status.getIn(['account', 'id'])) { + if (['public', 'unlisted'].indexOf(status.get('visibility')) !== -1) { + menu.push({ text: intl.formatMessage(status.get('pinned') ? messages.unpin : messages.pin), action: this.handlePinClick }); + } + menu.push({ text: intl.formatMessage(messages.delete), action: this.handleDeleteClick }); } else { menu.push({ text: intl.formatMessage(messages.mention, { name: status.getIn(['account', 'username']) }), action: this.handleMentionClick }); diff --git a/app/javascript/mastodon/features/status/index.js b/app/javascript/mastodon/features/status/index.js index cbabdd5bc..84e717a12 100644 --- a/app/javascript/mastodon/features/status/index.js +++ b/app/javascript/mastodon/features/status/index.js @@ -12,6 +12,8 @@ import { unfavourite, reblog, unreblog, + pin, + unpin, } from '../../actions/interactions'; import { replyCompose, @@ -87,6 +89,14 @@ export default class Status extends ImmutablePureComponent { } } + handlePin = (status) => { + if (status.get('pinned')) { + this.props.dispatch(unpin(status)); + } else { + this.props.dispatch(pin(status)); + } + } + handleReplyClick = (status) => { this.props.dispatch(replyCompose(status, this.context.router.history)); } @@ -187,6 +197,7 @@ export default class Status extends ImmutablePureComponent { onDelete={this.handleDeleteClick} onMention={this.handleMentionClick} onReport={this.handleReport} + onPin={this.handlePin} /> {descendants} diff --git a/app/javascript/mastodon/locales/ar.json b/app/javascript/mastodon/locales/ar.json index f5cf77f92..fa8cda97d 100644 --- a/app/javascript/mastodon/locales/ar.json +++ b/app/javascript/mastodon/locales/ar.json @@ -168,6 +168,7 @@ "status.mention": "أذكُر @{name}", "status.mute_conversation": "Mute conversation", "status.open": "وسع هذه المشاركة", + "status.pin": "Pin on profile", "status.reblog": "رَقِّي", "status.reblogged_by": "{name} رقى", "status.reply": "ردّ", @@ -179,6 +180,7 @@ "status.show_less": "إعرض أقلّ", "status.show_more": "أظهر المزيد", "status.unmute_conversation": "Unmute conversation", + "status.unpin": "Unpin from profile", "tabs_bar.compose": "تحرير", "tabs_bar.federated_timeline": "الموحَّد", "tabs_bar.home": "الرئيسية", diff --git a/app/javascript/mastodon/locales/bg.json b/app/javascript/mastodon/locales/bg.json index e6788f9eb..4aa097d31 100644 --- a/app/javascript/mastodon/locales/bg.json +++ b/app/javascript/mastodon/locales/bg.json @@ -168,6 +168,7 @@ "status.mention": "Споменаване", "status.mute_conversation": "Mute conversation", "status.open": "Expand this status", + "status.pin": "Pin on profile", "status.reblog": "Споделяне", "status.reblogged_by": "{name} сподели", "status.reply": "Отговор", @@ -179,6 +180,7 @@ "status.show_less": "Show less", "status.show_more": "Show more", "status.unmute_conversation": "Unmute conversation", + "status.unpin": "Unpin from profile", "tabs_bar.compose": "Съставяне", "tabs_bar.federated_timeline": "Federated", "tabs_bar.home": "Начало", diff --git a/app/javascript/mastodon/locales/ca.json b/app/javascript/mastodon/locales/ca.json index 95b3c60bf..d9cb7c7a3 100644 --- a/app/javascript/mastodon/locales/ca.json +++ b/app/javascript/mastodon/locales/ca.json @@ -168,6 +168,7 @@ "status.mention": "Esmentar @{name}", "status.mute_conversation": "Silenciar conversació", "status.open": "Ampliar aquest estat", + "status.pin": "Pin on profile", "status.reblog": "Boost", "status.reblogged_by": "{name} ha retootejat", "status.reply": "Respondre", @@ -179,6 +180,7 @@ "status.show_less": "Mostra menys", "status.show_more": "Mostra més", "status.unmute_conversation": "Activar conversació", + "status.unpin": "Unpin from profile", "tabs_bar.compose": "Compondre", "tabs_bar.federated_timeline": "Federada", "tabs_bar.home": "Inici", diff --git a/app/javascript/mastodon/locales/de.json b/app/javascript/mastodon/locales/de.json index 67a99b765..a5232552f 100644 --- a/app/javascript/mastodon/locales/de.json +++ b/app/javascript/mastodon/locales/de.json @@ -168,6 +168,7 @@ "status.mention": "Erwähnen", "status.mute_conversation": "Mute conversation", "status.open": "Öffnen", + "status.pin": "Pin on profile", "status.reblog": "Teilen", "status.reblogged_by": "{name} teilte", "status.reply": "Antworten", @@ -179,6 +180,7 @@ "status.show_less": "Weniger anzeigen", "status.show_more": "Mehr anzeigen", "status.unmute_conversation": "Unmute conversation", + "status.unpin": "Unpin from profile", "tabs_bar.compose": "Schreiben", "tabs_bar.federated_timeline": "Föderation", "tabs_bar.home": "Home", diff --git a/app/javascript/mastodon/locales/defaultMessages.json b/app/javascript/mastodon/locales/defaultMessages.json index ef76f6e5b..fdb8aefe1 100644 --- a/app/javascript/mastodon/locales/defaultMessages.json +++ b/app/javascript/mastodon/locales/defaultMessages.json @@ -189,6 +189,14 @@ { "defaultMessage": "Unmute conversation", "id": "status.unmute_conversation" + }, + { + "defaultMessage": "Pin on profile", + "id": "status.pin" + }, + { + "defaultMessage": "Unpin from profile", + "id": "status.unpin" } ], "path": "app/javascript/mastodon/components/status_action_bar.json" @@ -1035,6 +1043,14 @@ { "defaultMessage": "Share", "id": "status.share" + }, + { + "defaultMessage": "Pin on profile", + "id": "status.pin" + }, + { + "defaultMessage": "Unpin from profile", + "id": "status.unpin" } ], "path": "app/javascript/mastodon/features/status/components/action_bar.json" diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json index 2ea2062d3..595063888 100644 --- a/app/javascript/mastodon/locales/en.json +++ b/app/javascript/mastodon/locales/en.json @@ -168,6 +168,7 @@ "status.mention": "Mention @{name}", "status.mute_conversation": "Mute conversation", "status.open": "Expand this status", + "status.pin": "Pin on profile", "status.reblog": "Boost", "status.reblogged_by": "{name} boosted", "status.reply": "Reply", @@ -179,6 +180,7 @@ "status.show_less": "Show less", "status.show_more": "Show more", "status.unmute_conversation": "Unmute conversation", + "status.unpin": "Unpin from profile", "tabs_bar.compose": "Compose", "tabs_bar.federated_timeline": "Federated", "tabs_bar.home": "Home", diff --git a/app/javascript/mastodon/locales/eo.json b/app/javascript/mastodon/locales/eo.json index 960d747ec..ed323f406 100644 --- a/app/javascript/mastodon/locales/eo.json +++ b/app/javascript/mastodon/locales/eo.json @@ -168,6 +168,7 @@ "status.mention": "Mencii @{name}", "status.mute_conversation": "Mute conversation", "status.open": "Expand this status", + "status.pin": "Pin on profile", "status.reblog": "Diskonigi", "status.reblogged_by": "{name} diskonigita", "status.reply": "Respondi", @@ -179,6 +180,7 @@ "status.show_less": "Show less", "status.show_more": "Show more", "status.unmute_conversation": "Unmute conversation", + "status.unpin": "Unpin from profile", "tabs_bar.compose": "Ekskribi", "tabs_bar.federated_timeline": "Federated", "tabs_bar.home": "Hejmo", diff --git a/app/javascript/mastodon/locales/es.json b/app/javascript/mastodon/locales/es.json index 212d16639..2fee29148 100644 --- a/app/javascript/mastodon/locales/es.json +++ b/app/javascript/mastodon/locales/es.json @@ -168,6 +168,7 @@ "status.mention": "Mencionar", "status.mute_conversation": "Mute conversation", "status.open": "Expandir estado", + "status.pin": "Pin on profile", "status.reblog": "Retoot", "status.reblogged_by": "Retooteado por {name}", "status.reply": "Responder", @@ -179,6 +180,7 @@ "status.show_less": "Mostrar menos", "status.show_more": "Mostrar más", "status.unmute_conversation": "Unmute conversation", + "status.unpin": "Unpin from profile", "tabs_bar.compose": "Redactar", "tabs_bar.federated_timeline": "Federated", "tabs_bar.home": "Inicio", diff --git a/app/javascript/mastodon/locales/fa.json b/app/javascript/mastodon/locales/fa.json index 5ada62f93..89fa014e4 100644 --- a/app/javascript/mastodon/locales/fa.json +++ b/app/javascript/mastodon/locales/fa.json @@ -168,6 +168,7 @@ "status.mention": "نام‌بردن از @{name}", "status.mute_conversation": "بی‌صداکردن گفتگو", "status.open": "این نوشته را باز کن", + "status.pin": "Pin on profile", "status.reblog": "بازبوقیدن", "status.reblogged_by": "‫{name}‬ بازبوقید", "status.reply": "پاسخ", @@ -179,6 +180,7 @@ "status.show_less": "نهفتن", "status.show_more": "نمایش", "status.unmute_conversation": "باصداکردن گفتگو", + "status.unpin": "Unpin from profile", "tabs_bar.compose": "بنویسید", "tabs_bar.federated_timeline": "همگانی", "tabs_bar.home": "خانه", diff --git a/app/javascript/mastodon/locales/fi.json b/app/javascript/mastodon/locales/fi.json index cb9e9c2a6..1c1334899 100644 --- a/app/javascript/mastodon/locales/fi.json +++ b/app/javascript/mastodon/locales/fi.json @@ -168,6 +168,7 @@ "status.mention": "Mainitse @{name}", "status.mute_conversation": "Mute conversation", "status.open": "Expand this status", + "status.pin": "Pin on profile", "status.reblog": "Buustaa", "status.reblogged_by": "{name} buustasi", "status.reply": "Vastaa", @@ -179,6 +180,7 @@ "status.show_less": "Show less", "status.show_more": "Show more", "status.unmute_conversation": "Unmute conversation", + "status.unpin": "Unpin from profile", "tabs_bar.compose": "Luo", "tabs_bar.federated_timeline": "Federated", "tabs_bar.home": "Koti", diff --git a/app/javascript/mastodon/locales/fr.json b/app/javascript/mastodon/locales/fr.json index 34a89a69f..479b8de7d 100644 --- a/app/javascript/mastodon/locales/fr.json +++ b/app/javascript/mastodon/locales/fr.json @@ -168,6 +168,7 @@ "status.mention": "Mentionner", "status.mute_conversation": "Masquer la conversation", "status.open": "Déplier ce statut", + "status.pin": "Pin on profile", "status.reblog": "Partager", "status.reblogged_by": "{name} a partagé :", "status.reply": "Répondre", @@ -179,6 +180,7 @@ "status.show_less": "Replier", "status.show_more": "Déplier", "status.unmute_conversation": "Ne plus masquer la conversation", + "status.unpin": "Unpin from profile", "tabs_bar.compose": "Composer", "tabs_bar.federated_timeline": "Fil public global", "tabs_bar.home": "Accueil", diff --git a/app/javascript/mastodon/locales/he.json b/app/javascript/mastodon/locales/he.json index 34266d8e1..1e221af9c 100644 --- a/app/javascript/mastodon/locales/he.json +++ b/app/javascript/mastodon/locales/he.json @@ -168,6 +168,7 @@ "status.mention": "פניה אל @{name}", "status.mute_conversation": "השתקת שיחה", "status.open": "הרחבת הודעה", + "status.pin": "Pin on profile", "status.reblog": "הדהוד", "status.reblogged_by": "הודהד על ידי {name}", "status.reply": "תגובה", @@ -179,6 +180,7 @@ "status.show_less": "הראה פחות", "status.show_more": "הראה יותר", "status.unmute_conversation": "הסרת השתקת שיחה", + "status.unpin": "Unpin from profile", "tabs_bar.compose": "חיבור", "tabs_bar.federated_timeline": "ציר זמן בין-קהילתי", "tabs_bar.home": "בבית", diff --git a/app/javascript/mastodon/locales/hr.json b/app/javascript/mastodon/locales/hr.json index f69b096d4..2effecb1e 100644 --- a/app/javascript/mastodon/locales/hr.json +++ b/app/javascript/mastodon/locales/hr.json @@ -168,6 +168,7 @@ "status.mention": "Spomeni @{name}", "status.mute_conversation": "Utišaj razgovor", "status.open": "Proširi ovaj status", + "status.pin": "Pin on profile", "status.reblog": "Podigni", "status.reblogged_by": "{name} je podigao", "status.reply": "Odgovori", @@ -179,6 +180,7 @@ "status.show_less": "Pokaži manje", "status.show_more": "Pokaži više", "status.unmute_conversation": "Poništi utišavanje razgovora", + "status.unpin": "Unpin from profile", "tabs_bar.compose": "Sastavi", "tabs_bar.federated_timeline": "Federalni", "tabs_bar.home": "Dom", diff --git a/app/javascript/mastodon/locales/hu.json b/app/javascript/mastodon/locales/hu.json index 4d2a50963..59a7b8deb 100644 --- a/app/javascript/mastodon/locales/hu.json +++ b/app/javascript/mastodon/locales/hu.json @@ -168,6 +168,7 @@ "status.mention": "Említés", "status.mute_conversation": "Mute conversation", "status.open": "Expand this status", + "status.pin": "Pin on profile", "status.reblog": "Reblog", "status.reblogged_by": "{name} reblogolta", "status.reply": "Válasz", @@ -179,6 +180,7 @@ "status.show_less": "Show less", "status.show_more": "Show more", "status.unmute_conversation": "Unmute conversation", + "status.unpin": "Unpin from profile", "tabs_bar.compose": "Összeállítás", "tabs_bar.federated_timeline": "Federated", "tabs_bar.home": "Kezdőlap", diff --git a/app/javascript/mastodon/locales/id.json b/app/javascript/mastodon/locales/id.json index 532739e3c..9dd66b6cd 100644 --- a/app/javascript/mastodon/locales/id.json +++ b/app/javascript/mastodon/locales/id.json @@ -168,6 +168,7 @@ "status.mention": "Balasan @{name}", "status.mute_conversation": "Mute conversation", "status.open": "Tampilkan status ini", + "status.pin": "Pin on profile", "status.reblog": "Boost", "status.reblogged_by": "di-boost {name}", "status.reply": "Balas", @@ -179,6 +180,7 @@ "status.show_less": "Tampilkan lebih sedikit", "status.show_more": "Tampilkan semua", "status.unmute_conversation": "Unmute conversation", + "status.unpin": "Unpin from profile", "tabs_bar.compose": "Tulis", "tabs_bar.federated_timeline": "Gabungan", "tabs_bar.home": "Beranda", diff --git a/app/javascript/mastodon/locales/io.json b/app/javascript/mastodon/locales/io.json index a5e363e40..07184ae81 100644 --- a/app/javascript/mastodon/locales/io.json +++ b/app/javascript/mastodon/locales/io.json @@ -168,6 +168,7 @@ "status.mention": "Mencionar @{name}", "status.mute_conversation": "Mute conversation", "status.open": "Detaligar ca mesajo", + "status.pin": "Pin on profile", "status.reblog": "Repetar", "status.reblogged_by": "{name} repetita", "status.reply": "Respondar", @@ -179,6 +180,7 @@ "status.show_less": "Montrar mine", "status.show_more": "Montrar plue", "status.unmute_conversation": "Unmute conversation", + "status.unpin": "Unpin from profile", "tabs_bar.compose": "Kompozar", "tabs_bar.federated_timeline": "Federata", "tabs_bar.home": "Hemo", diff --git a/app/javascript/mastodon/locales/it.json b/app/javascript/mastodon/locales/it.json index 329eb82ca..369ae7f32 100644 --- a/app/javascript/mastodon/locales/it.json +++ b/app/javascript/mastodon/locales/it.json @@ -168,6 +168,7 @@ "status.mention": "Nomina @{name}", "status.mute_conversation": "Mute conversation", "status.open": "Espandi questo post", + "status.pin": "Pin on profile", "status.reblog": "Condividi", "status.reblogged_by": "{name} ha condiviso", "status.reply": "Rispondi", @@ -179,6 +180,7 @@ "status.show_less": "Mostra meno", "status.show_more": "Mostra di più", "status.unmute_conversation": "Unmute conversation", + "status.unpin": "Unpin from profile", "tabs_bar.compose": "Scrivi", "tabs_bar.federated_timeline": "Federazione", "tabs_bar.home": "Home", diff --git a/app/javascript/mastodon/locales/ja.json b/app/javascript/mastodon/locales/ja.json index 757190c90..c35b0def3 100644 --- a/app/javascript/mastodon/locales/ja.json +++ b/app/javascript/mastodon/locales/ja.json @@ -168,6 +168,7 @@ "status.mention": "返信", "status.mute_conversation": "会話をミュート", "status.open": "詳細を表示", + "status.pin": "Pin on profile", "status.reblog": "ブースト", "status.reblogged_by": "{name}さんにブーストされました", "status.reply": "返信", @@ -179,6 +180,7 @@ "status.show_less": "隠す", "status.show_more": "もっと見る", "status.unmute_conversation": "会話のミュートを解除", + "status.unpin": "Unpin from profile", "tabs_bar.compose": "投稿", "tabs_bar.federated_timeline": "連合", "tabs_bar.home": "ホーム", diff --git a/app/javascript/mastodon/locales/ko.json b/app/javascript/mastodon/locales/ko.json index 47d0d4087..52ba1e70f 100644 --- a/app/javascript/mastodon/locales/ko.json +++ b/app/javascript/mastodon/locales/ko.json @@ -168,6 +168,7 @@ "status.mention": "답장", "status.mute_conversation": "이 대화를 뮤트", "status.open": "상세 정보 표시", + "status.pin": "Pin on profile", "status.reblog": "부스트", "status.reblogged_by": "{name}님이 부스트 했습니다", "status.reply": "답장", @@ -179,6 +180,7 @@ "status.show_less": "숨기기", "status.show_more": "더 보기", "status.unmute_conversation": "이 대화의 뮤트 해제하기", + "status.unpin": "Unpin from profile", "tabs_bar.compose": "포스트", "tabs_bar.federated_timeline": "연합", "tabs_bar.home": "홈", diff --git a/app/javascript/mastodon/locales/nl.json b/app/javascript/mastodon/locales/nl.json index 4d68c7992..fb4127831 100644 --- a/app/javascript/mastodon/locales/nl.json +++ b/app/javascript/mastodon/locales/nl.json @@ -168,6 +168,7 @@ "status.mention": "Vermeld @{name}", "status.mute_conversation": "Negeer conversatie", "status.open": "Toot volledig tonen", + "status.pin": "Pin on profile", "status.reblog": "Boost", "status.reblogged_by": "{name} boostte", "status.reply": "Reageren", @@ -179,6 +180,7 @@ "status.show_less": "Minder tonen", "status.show_more": "Meer tonen", "status.unmute_conversation": "Conversatie niet meer negeren", + "status.unpin": "Unpin from profile", "tabs_bar.compose": "Schrijven", "tabs_bar.federated_timeline": "Globaal", "tabs_bar.home": "Start", diff --git a/app/javascript/mastodon/locales/no.json b/app/javascript/mastodon/locales/no.json index 9453e65ff..2d6224c48 100644 --- a/app/javascript/mastodon/locales/no.json +++ b/app/javascript/mastodon/locales/no.json @@ -168,6 +168,7 @@ "status.mention": "Nevn @{name}", "status.mute_conversation": "Demp samtale", "status.open": "Utvid denne statusen", + "status.pin": "Pin on profile", "status.reblog": "Fremhev", "status.reblogged_by": "Fremhevd av {name}", "status.reply": "Svar", @@ -179,6 +180,7 @@ "status.show_less": "Vis mindre", "status.show_more": "Vis mer", "status.unmute_conversation": "Ikke demp samtale", + "status.unpin": "Unpin from profile", "tabs_bar.compose": "Komponer", "tabs_bar.federated_timeline": "Felles", "tabs_bar.home": "Hjem", diff --git a/app/javascript/mastodon/locales/oc.json b/app/javascript/mastodon/locales/oc.json index 5e5e28af0..34e1a8c47 100644 --- a/app/javascript/mastodon/locales/oc.json +++ b/app/javascript/mastodon/locales/oc.json @@ -168,6 +168,7 @@ "status.mention": "Mencionar", "status.mute_conversation": "Rescondre la conversacion", "status.open": "Desplegar aqueste estatut", + "status.pin": "Pin on profile", "status.reblog": "Partejar", "status.reblogged_by": "{name} a partejat :", "status.reply": "Respondre", @@ -179,6 +180,7 @@ "status.show_less": "Tornar plegar", "status.show_more": "Desplegar", "status.unmute_conversation": "Conversacions amb silenci levat", + "status.unpin": "Unpin from profile", "tabs_bar.compose": "Compausar", "tabs_bar.federated_timeline": "Flux public global", "tabs_bar.home": "Acuèlh", diff --git a/app/javascript/mastodon/locales/pl.json b/app/javascript/mastodon/locales/pl.json index af38bbb6c..8a8d0f38a 100644 --- a/app/javascript/mastodon/locales/pl.json +++ b/app/javascript/mastodon/locales/pl.json @@ -168,6 +168,7 @@ "status.mention": "Wspomnij o @{name}", "status.mute_conversation": "Wycisz konwersację", "status.open": "Rozszerz ten status", + "status.pin": "Pin on profile", "status.reblog": "Podbij", "status.reblogged_by": "{name} podbił", "status.reply": "Odpowiedz", @@ -179,6 +180,7 @@ "status.show_less": "Pokaż mniej", "status.show_more": "Pokaż więcej", "status.unmute_conversation": "Cofnij wyciszenie konwersacji", + "status.unpin": "Unpin from profile", "tabs_bar.compose": "Napisz", "tabs_bar.federated_timeline": "Globalne", "tabs_bar.home": "Strona główna", diff --git a/app/javascript/mastodon/locales/pt-BR.json b/app/javascript/mastodon/locales/pt-BR.json index 55d2f05de..8a299e272 100644 --- a/app/javascript/mastodon/locales/pt-BR.json +++ b/app/javascript/mastodon/locales/pt-BR.json @@ -168,6 +168,7 @@ "status.mention": "Mencionar @{name}", "status.mute_conversation": "Mute conversation", "status.open": "Expandir", + "status.pin": "Pin on profile", "status.reblog": "Partilhar", "status.reblogged_by": "{name} partilhou", "status.reply": "Responder", @@ -179,6 +180,7 @@ "status.show_less": "Mostrar menos", "status.show_more": "Mostrar mais", "status.unmute_conversation": "Unmute conversation", + "status.unpin": "Unpin from profile", "tabs_bar.compose": "Criar", "tabs_bar.federated_timeline": "Global", "tabs_bar.home": "Home", diff --git a/app/javascript/mastodon/locales/pt.json b/app/javascript/mastodon/locales/pt.json index 55d2f05de..8a299e272 100644 --- a/app/javascript/mastodon/locales/pt.json +++ b/app/javascript/mastodon/locales/pt.json @@ -168,6 +168,7 @@ "status.mention": "Mencionar @{name}", "status.mute_conversation": "Mute conversation", "status.open": "Expandir", + "status.pin": "Pin on profile", "status.reblog": "Partilhar", "status.reblogged_by": "{name} partilhou", "status.reply": "Responder", @@ -179,6 +180,7 @@ "status.show_less": "Mostrar menos", "status.show_more": "Mostrar mais", "status.unmute_conversation": "Unmute conversation", + "status.unpin": "Unpin from profile", "tabs_bar.compose": "Criar", "tabs_bar.federated_timeline": "Global", "tabs_bar.home": "Home", diff --git a/app/javascript/mastodon/locales/ru.json b/app/javascript/mastodon/locales/ru.json index af38fc723..822f116c7 100644 --- a/app/javascript/mastodon/locales/ru.json +++ b/app/javascript/mastodon/locales/ru.json @@ -168,6 +168,7 @@ "status.mention": "Упомянуть @{name}", "status.mute_conversation": "Заглушить тред", "status.open": "Развернуть статус", + "status.pin": "Pin on profile", "status.reblog": "Продвинуть", "status.reblogged_by": "{name} продвинул(а)", "status.reply": "Ответить", @@ -179,6 +180,7 @@ "status.show_less": "Свернуть", "status.show_more": "Развернуть", "status.unmute_conversation": "Снять глушение с треда", + "status.unpin": "Unpin from profile", "tabs_bar.compose": "Написать", "tabs_bar.federated_timeline": "Глобальная", "tabs_bar.home": "Главная", diff --git a/app/javascript/mastodon/locales/th.json b/app/javascript/mastodon/locales/th.json index aa0929f82..9c985eec9 100644 --- a/app/javascript/mastodon/locales/th.json +++ b/app/javascript/mastodon/locales/th.json @@ -168,6 +168,7 @@ "status.mention": "Mention @{name}", "status.mute_conversation": "Mute conversation", "status.open": "Expand this status", + "status.pin": "Pin on profile", "status.reblog": "Boost", "status.reblogged_by": "{name} boosted", "status.reply": "Reply", @@ -179,6 +180,7 @@ "status.show_less": "Show less", "status.show_more": "Show more", "status.unmute_conversation": "Unmute conversation", + "status.unpin": "Unpin from profile", "tabs_bar.compose": "Compose", "tabs_bar.federated_timeline": "Federated", "tabs_bar.home": "Home", diff --git a/app/javascript/mastodon/locales/tr.json b/app/javascript/mastodon/locales/tr.json index 37ce8597e..41c9d44a7 100644 --- a/app/javascript/mastodon/locales/tr.json +++ b/app/javascript/mastodon/locales/tr.json @@ -168,6 +168,7 @@ "status.mention": "Bahset @{name}", "status.mute_conversation": "Mute conversation", "status.open": "Bu gönderiyi genişlet", + "status.pin": "Pin on profile", "status.reblog": "Boost'la", "status.reblogged_by": "{name} boost etti", "status.reply": "Cevapla", @@ -179,6 +180,7 @@ "status.show_less": "Daha azı", "status.show_more": "Daha fazlası", "status.unmute_conversation": "Unmute conversation", + "status.unpin": "Unpin from profile", "tabs_bar.compose": "Oluştur", "tabs_bar.federated_timeline": "Federe", "tabs_bar.home": "Ana sayfa", diff --git a/app/javascript/mastodon/locales/uk.json b/app/javascript/mastodon/locales/uk.json index fea7bd94e..6087e3a1e 100644 --- a/app/javascript/mastodon/locales/uk.json +++ b/app/javascript/mastodon/locales/uk.json @@ -168,6 +168,7 @@ "status.mention": "Згадати", "status.mute_conversation": "Заглушити діалог", "status.open": "Розгорнути допис", + "status.pin": "Pin on profile", "status.reblog": "Передмухнути", "status.reblogged_by": "{name} передмухнув(-ла)", "status.reply": "Відповісти", @@ -179,6 +180,7 @@ "status.show_less": "Згорнути", "status.show_more": "Розгорнути", "status.unmute_conversation": "Зняти глушення з діалогу", + "status.unpin": "Unpin from profile", "tabs_bar.compose": "Написати", "tabs_bar.federated_timeline": "Глобальна", "tabs_bar.home": "Головна", diff --git a/app/javascript/mastodon/locales/zh-CN.json b/app/javascript/mastodon/locales/zh-CN.json index e7c431454..2e3b4b0b8 100644 --- a/app/javascript/mastodon/locales/zh-CN.json +++ b/app/javascript/mastodon/locales/zh-CN.json @@ -168,6 +168,7 @@ "status.mention": "提及 @{name}", "status.mute_conversation": "Mute conversation", "status.open": "展开嘟文", + "status.pin": "Pin on profile", "status.reblog": "转嘟", "status.reblogged_by": "{name} 转嘟", "status.reply": "回应", @@ -179,6 +180,7 @@ "status.show_less": "减少显示", "status.show_more": "显示更多", "status.unmute_conversation": "Unmute conversation", + "status.unpin": "Unpin from profile", "tabs_bar.compose": "撰写", "tabs_bar.federated_timeline": "跨站", "tabs_bar.home": "主页", diff --git a/app/javascript/mastodon/locales/zh-HK.json b/app/javascript/mastodon/locales/zh-HK.json index 7312aae82..1ab3b3f9d 100644 --- a/app/javascript/mastodon/locales/zh-HK.json +++ b/app/javascript/mastodon/locales/zh-HK.json @@ -168,6 +168,7 @@ "status.mention": "提及 @{name}", "status.mute_conversation": "Mute conversation", "status.open": "展開文章", + "status.pin": "Pin on profile", "status.reblog": "轉推", "status.reblogged_by": "{name} 轉推", "status.reply": "回應", @@ -179,6 +180,7 @@ "status.show_less": "減少顯示", "status.show_more": "顯示更多", "status.unmute_conversation": "Unmute conversation", + "status.unpin": "Unpin from profile", "tabs_bar.compose": "撰寫", "tabs_bar.federated_timeline": "跨站", "tabs_bar.home": "主頁", diff --git a/app/javascript/mastodon/locales/zh-TW.json b/app/javascript/mastodon/locales/zh-TW.json index 1c2e35272..571a2383d 100644 --- a/app/javascript/mastodon/locales/zh-TW.json +++ b/app/javascript/mastodon/locales/zh-TW.json @@ -168,6 +168,7 @@ "status.mention": "提到 @{name}", "status.mute_conversation": "消音對話", "status.open": "展開這個狀態", + "status.pin": "Pin on profile", "status.reblog": "轉推", "status.reblogged_by": "{name} 轉推了", "status.reply": "回應", @@ -179,6 +180,7 @@ "status.show_less": "看少點", "status.show_more": "看更多", "status.unmute_conversation": "不消音對話", + "status.unpin": "Unpin from profile", "tabs_bar.compose": "編輯", "tabs_bar.federated_timeline": "聯盟", "tabs_bar.home": "家", diff --git a/app/javascript/mastodon/reducers/statuses.js b/app/javascript/mastodon/reducers/statuses.js index 3e40b0b42..38691dc43 100644 --- a/app/javascript/mastodon/reducers/statuses.js +++ b/app/javascript/mastodon/reducers/statuses.js @@ -7,6 +7,8 @@ import { FAVOURITE_SUCCESS, FAVOURITE_FAIL, UNFAVOURITE_SUCCESS, + PIN_SUCCESS, + UNPIN_SUCCESS, } from '../actions/interactions'; import { STATUS_FETCH_SUCCESS, @@ -114,6 +116,8 @@ export default function statuses(state = initialState, action) { case UNREBLOG_SUCCESS: case FAVOURITE_SUCCESS: case UNFAVOURITE_SUCCESS: + case PIN_SUCCESS: + case UNPIN_SUCCESS: return normalizeStatus(state, action.response); case FAVOURITE_REQUEST: return state.setIn([action.status.get('id'), 'favourited'], true); diff --git a/app/models/account.rb b/app/models/account.rb index 0c9c6aed4..b83aa1159 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -77,6 +77,10 @@ class Account < ApplicationRecord has_many :mentions, inverse_of: :account, dependent: :destroy has_many :notifications, inverse_of: :account, dependent: :destroy + # Pinned statuses + has_many :status_pins, inverse_of: :account, dependent: :destroy + has_many :pinned_statuses, through: :status_pins, class_name: 'Status', source: :status + # Media has_many :media_attachments, dependent: :destroy diff --git a/app/models/concerns/account_interactions.rb b/app/models/concerns/account_interactions.rb index 9ffed2910..b26520f5b 100644 --- a/app/models/concerns/account_interactions.rb +++ b/app/models/concerns/account_interactions.rb @@ -138,4 +138,8 @@ module AccountInteractions def reblogged?(status) status.proper.reblogs.where(account: self).exists? end + + def pinned?(status) + status_pins.where(status: status).exists? + end end diff --git a/app/models/status.rb b/app/models/status.rb index 24eaf7071..3dc83ad1f 100644 --- a/app/models/status.rb +++ b/app/models/status.rb @@ -164,6 +164,10 @@ class Status < ApplicationRecord ConversationMute.select('conversation_id').where(conversation_id: conversation_ids).where(account_id: account_id).map { |m| [m.conversation_id, true] }.to_h end + def pins_map(status_ids, account_id) + StatusPin.select('status_id').where(status_id: status_ids).where(account_id: account_id).map { |p| [p.status_id, true] }.to_h + end + def reload_stale_associations!(cached_items) account_ids = [] diff --git a/app/models/status_pin.rb b/app/models/status_pin.rb new file mode 100644 index 000000000..c9a669344 --- /dev/null +++ b/app/models/status_pin.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true +# == Schema Information +# +# Table name: status_pins +# +# id :integer not null, primary key +# account_id :integer not null +# status_id :integer not null +# + +class StatusPin < ApplicationRecord + belongs_to :account, required: true + belongs_to :status, required: true + + validates_with StatusPinValidator +end diff --git a/app/presenters/status_relationships_presenter.rb b/app/presenters/status_relationships_presenter.rb index 03294015f..10b449504 100644 --- a/app/presenters/status_relationships_presenter.rb +++ b/app/presenters/status_relationships_presenter.rb @@ -1,19 +1,24 @@ # frozen_string_literal: true class StatusRelationshipsPresenter - attr_reader :reblogs_map, :favourites_map, :mutes_map + attr_reader :reblogs_map, :favourites_map, :mutes_map, :pins_map - def initialize(statuses, current_account_id = nil, reblogs_map: {}, favourites_map: {}, mutes_map: {}) + def initialize(statuses, current_account_id = nil, options = {}) if current_account_id.nil? @reblogs_map = {} @favourites_map = {} @mutes_map = {} + @pins_map = {} else - status_ids = statuses.compact.flat_map { |s| [s.id, s.reblog_of_id] }.uniq - conversation_ids = statuses.compact.map(&:conversation_id).compact.uniq - @reblogs_map = Status.reblogs_map(status_ids, current_account_id).merge(reblogs_map) - @favourites_map = Status.favourites_map(status_ids, current_account_id).merge(favourites_map) - @mutes_map = Status.mutes_map(conversation_ids, current_account_id).merge(mutes_map) + statuses = statuses.compact + status_ids = statuses.flat_map { |s| [s.id, s.reblog_of_id] }.uniq + conversation_ids = statuses.map(&:conversation_id).compact.uniq + pinnable_status_ids = statuses.map(&:proper).select { |s| s.account_id == current_account_id && %w(public unlisted).include?(s.visibility) }.map(&:id) + + @reblogs_map = Status.reblogs_map(status_ids, current_account_id).merge(options[:reblogs_map] || {}) + @favourites_map = Status.favourites_map(status_ids, current_account_id).merge(options[:favourites_map] || {}) + @mutes_map = Status.mutes_map(conversation_ids, current_account_id).merge(options[:mutes_map] || {}) + @pins_map = Status.pins_map(pinnable_status_ids, current_account_id).merge(options[:pins_map] || {}) end end end diff --git a/app/serializers/rest/status_serializer.rb b/app/serializers/rest/status_serializer.rb index 246b12a90..298a3bb40 100644 --- a/app/serializers/rest/status_serializer.rb +++ b/app/serializers/rest/status_serializer.rb @@ -8,6 +8,7 @@ class REST::StatusSerializer < ActiveModel::Serializer attribute :favourited, if: :current_user? attribute :reblogged, if: :current_user? attribute :muted, if: :current_user? + attribute :pinned, if: :pinnable? belongs_to :reblog, serializer: REST::StatusSerializer belongs_to :application @@ -57,6 +58,21 @@ class REST::StatusSerializer < ActiveModel::Serializer end end + def pinned + if instance_options && instance_options[:relationships] + instance_options[:relationships].pins_map[object.id] || false + else + current_user.account.pinned?(object) + end + end + + def pinnable? + current_user? && + current_user.account_id == object.account_id && + !object.reblog? && + %w(public unlisted).include?(object.visibility) + end + class ApplicationSerializer < ActiveModel::Serializer attributes :name, :website end diff --git a/app/validators/status_pin_validator.rb b/app/validators/status_pin_validator.rb new file mode 100644 index 000000000..f557df6af --- /dev/null +++ b/app/validators/status_pin_validator.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class StatusPinValidator < ActiveModel::Validator + def validate(pin) + pin.errors.add(:status, I18n.t('statuses.pin_errors.reblog')) if pin.status.reblog? + pin.errors.add(:status, I18n.t('statuses.pin_errors.ownership')) if pin.account_id != pin.status.account_id + pin.errors.add(:status, I18n.t('statuses.pin_errors.private')) unless %w(public unlisted).include?(pin.status.visibility) + end +end diff --git a/app/views/accounts/show.html.haml b/app/views/accounts/show.html.haml index ec44f4c74..e0f9f869a 100644 --- a/app/views/accounts/show.html.haml +++ b/app/views/accounts/show.html.haml @@ -30,6 +30,9 @@ = render 'nothing_here' - else .activity-stream.with-header + - if params[:page].to_i.zero? + = render partial: 'stream_entries/status', collection: @pinned_statuses, as: :status, locals: { pinned: true } + = render partial: 'stream_entries/status', collection: @statuses, as: :status - if @statuses.size == 20 diff --git a/app/views/stream_entries/_status.html.haml b/app/views/stream_entries/_status.html.haml index 50a373743..e2e1fdd12 100644 --- a/app/views/stream_entries/_status.html.haml +++ b/app/views/stream_entries/_status.html.haml @@ -1,4 +1,5 @@ :ruby + pinned ||= false include_threads ||= false is_predecessor ||= false is_successor ||= false @@ -25,6 +26,12 @@ = link_to TagManager.instance.url_for(status.account), class: 'status__display-name muted' do %strong.emojify= display_name(status.account) = t('stream_entries.reblogged') + - elsif pinned + .pre-header + .pre-header__icon + = fa_icon('thumb-tack fw') + %span + = t('stream_entries.pinned') = render (centered ? 'stream_entries/detailed_status' : 'stream_entries/simple_status'), status: status.proper diff --git a/config/locales/en.yml b/config/locales/en.yml index 97bb14186..96d08e6b2 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -434,6 +434,10 @@ en: statuses: open_in_web: Open in web over_character_limit: character limit of %{max} exceeded + pin_errors: + ownership: Someone else's toot cannot be pinned + private: Non-public toot cannot be pinned + reblog: A boost cannot be pinned show_more: Show more visibilities: private: Followers-only @@ -444,6 +448,7 @@ en: unlisted_long: Everyone can see, but not listed on public timelines stream_entries: click_to_show: Click to show + pinned: Pinned toot reblogged: boosted sensitive_content: Sensitive content terms: diff --git a/config/routes.rb b/config/routes.rb index 94a4ac88e..7588805c0 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -162,6 +162,9 @@ Rails.application.routes.draw do resource :mute, only: :create post :unmute, to: 'mutes#destroy' + + resource :pin, only: :create + post :unpin, to: 'pins#destroy' end member do @@ -175,7 +178,8 @@ Rails.application.routes.draw do resource :public, only: :show, controller: :public resources :tag, only: :show end - resources :streaming, only: [:index] + + resources :streaming, only: [:index] get '/search', to: 'search#index', as: :search @@ -210,6 +214,7 @@ Rails.application.routes.draw do resource :search, only: :show, controller: :search resources :relationships, only: :index end + resources :accounts, only: [:show] do resources :statuses, only: :index, controller: 'accounts/statuses' resources :followers, only: :index, controller: 'accounts/follower_accounts' @@ -245,7 +250,7 @@ Rails.application.routes.draw do root 'home#index' match '*unmatched_route', - via: :all, - to: 'application#raise_not_found', - format: false + via: :all, + to: 'application#raise_not_found', + format: false end diff --git a/db/migrate/20170823162448_create_status_pins.rb b/db/migrate/20170823162448_create_status_pins.rb new file mode 100644 index 000000000..9a6d4a7b9 --- /dev/null +++ b/db/migrate/20170823162448_create_status_pins.rb @@ -0,0 +1,10 @@ +class CreateStatusPins < ActiveRecord::Migration[5.1] + def change + create_table :status_pins do |t| + t.belongs_to :account, foreign_key: { on_delete: :cascade }, null: false + t.belongs_to :status, foreign_key: { on_delete: :cascade }, null: false + end + + add_index :status_pins, [:account_id, :status_id], unique: true + end +end diff --git a/db/schema.rb b/db/schema.rb index 98b07e282..d0e72be0f 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: 20170720000000) do +ActiveRecord::Schema.define(version: 20170823162448) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -282,6 +282,14 @@ ActiveRecord::Schema.define(version: 20170720000000) do t.index ["thing_type", "thing_id", "var"], name: "index_settings_on_thing_type_and_thing_id_and_var", unique: true end + create_table "status_pins", force: :cascade do |t| + t.bigint "account_id", null: false + t.bigint "status_id", null: false + t.index ["account_id", "status_id"], name: "index_status_pins_on_account_id_and_status_id", unique: true + t.index ["account_id"], name: "index_status_pins_on_account_id" + t.index ["status_id"], name: "index_status_pins_on_status_id" + end + create_table "statuses", force: :cascade do |t| t.string "uri" t.integer "account_id", null: false @@ -430,6 +438,8 @@ ActiveRecord::Schema.define(version: 20170720000000) do add_foreign_key "reports", "accounts", on_delete: :cascade add_foreign_key "session_activations", "oauth_access_tokens", column: "access_token_id", on_delete: :cascade add_foreign_key "session_activations", "users", on_delete: :cascade + add_foreign_key "status_pins", "accounts", on_delete: :cascade + add_foreign_key "status_pins", "statuses", on_delete: :cascade add_foreign_key "statuses", "accounts", column: "in_reply_to_account_id", on_delete: :nullify add_foreign_key "statuses", "accounts", on_delete: :cascade add_foreign_key "statuses", "statuses", column: "in_reply_to_id", on_delete: :nullify diff --git a/spec/controllers/api/v1/accounts/statuses_controller_spec.rb b/spec/controllers/api/v1/accounts/statuses_controller_spec.rb index 8b4fd6a5b..c49a77ac3 100644 --- a/spec/controllers/api/v1/accounts/statuses_controller_spec.rb +++ b/spec/controllers/api/v1/accounts/statuses_controller_spec.rb @@ -18,21 +18,37 @@ describe Api::V1::Accounts::StatusesController do expect(response).to have_http_status(:success) expect(response.headers['Link'].links.size).to eq(2) end - end - describe 'GET #index with only media' do - it 'returns http success' do - get :index, params: { account_id: user.account.id, only_media: true } + context 'with only media' do + it 'returns http success' do + get :index, params: { account_id: user.account.id, only_media: true } - expect(response).to have_http_status(:success) + expect(response).to have_http_status(:success) + end end - end - describe 'GET #index with exclude replies' do - it 'returns http success' do - get :index, params: { account_id: user.account.id, exclude_replies: true } + context 'with exclude replies' do + before do + Fabricate(:status, account: user.account, thread: Fabricate(:status)) + end - expect(response).to have_http_status(:success) + it 'returns http success' do + get :index, params: { account_id: user.account.id, exclude_replies: true } + + expect(response).to have_http_status(:success) + end + end + + context 'with only pinned' do + before do + Fabricate(:status_pin, account: user.account, status: Fabricate(:status, account: user.account)) + end + + it 'returns http success' do + get :index, params: { account_id: user.account.id, pinned: true } + + expect(response).to have_http_status(:success) + end end end end diff --git a/spec/controllers/api/v1/statuses/pins_controller_spec.rb b/spec/controllers/api/v1/statuses/pins_controller_spec.rb new file mode 100644 index 000000000..2e170da24 --- /dev/null +++ b/spec/controllers/api/v1/statuses/pins_controller_spec.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe Api::V1::Statuses::PinsController do + render_views + + let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice')) } + let(:app) { Fabricate(:application, name: 'Test app', website: 'http://testapp.com') } + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'write', application: app) } + + context 'with an oauth token' do + before do + allow(controller).to receive(:doorkeeper_token) { token } + end + + describe 'POST #create' do + let(:status) { Fabricate(:status, account: user.account) } + + before do + post :create, params: { status_id: status.id } + end + + it 'returns http success' do + expect(response).to have_http_status(:success) + end + + it 'updates the pinned attribute' do + expect(user.account.pinned?(status)).to be true + end + + it 'return json with updated attributes' do + hash_body = body_as_json + + expect(hash_body[:id]).to eq status.id + expect(hash_body[:pinned]).to be true + end + end + + describe 'POST #destroy' do + let(:status) { Fabricate(:status, account: user.account) } + + before do + Fabricate(:status_pin, status: status, account: user.account) + post :destroy, params: { status_id: status.id } + end + + it 'returns http success' do + expect(response).to have_http_status(:success) + end + + it 'updates the pinned attribute' do + expect(user.account.pinned?(status)).to be false + end + end + end +end diff --git a/spec/fabricators/status_pin_fabricator.rb b/spec/fabricators/status_pin_fabricator.rb new file mode 100644 index 000000000..6a9006c9f --- /dev/null +++ b/spec/fabricators/status_pin_fabricator.rb @@ -0,0 +1,4 @@ +Fabricator(:status_pin) do + account + status +end diff --git a/spec/models/status_pin_spec.rb b/spec/models/status_pin_spec.rb new file mode 100644 index 000000000..6f54f80f9 --- /dev/null +++ b/spec/models/status_pin_spec.rb @@ -0,0 +1,41 @@ +require 'rails_helper' + +RSpec.describe StatusPin, type: :model do + describe 'validations' do + it 'allows pins of own statuses' do + account = Fabricate(:account) + status = Fabricate(:status, account: account) + + expect(StatusPin.new(account: account, status: status).save).to be true + end + + it 'does not allow pins of statuses by someone else' do + account = Fabricate(:account) + status = Fabricate(:status) + + expect(StatusPin.new(account: account, status: status).save).to be false + end + + it 'does not allow pins of reblogs' do + account = Fabricate(:account) + status = Fabricate(:status, account: account) + reblog = Fabricate(:status, reblog: status) + + expect(StatusPin.new(account: account, status: reblog).save).to be false + end + + it 'does not allow pins of private statuses' do + account = Fabricate(:account) + status = Fabricate(:status, account: account, visibility: :private) + + expect(StatusPin.new(account: account, status: status).save).to be false + end + + it 'does not allow pins of direct statuses' do + account = Fabricate(:account) + status = Fabricate(:status, account: account, visibility: :direct) + + expect(StatusPin.new(account: account, status: status).save).to be false + end + end +end -- cgit From 409051c22c033160a62997831660d9dda2db7459 Mon Sep 17 00:00:00 2001 From: m4sk1n Date: Fri, 25 Aug 2017 10:58:31 +0200 Subject: i18n: Update Polish translation #4675 (#4692) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Marcin Mikołajczak --- app/javascript/mastodon/locales/pl.json | 4 ++-- config/locales/pl.yml | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) (limited to 'app/javascript') diff --git a/app/javascript/mastodon/locales/pl.json b/app/javascript/mastodon/locales/pl.json index 8a8d0f38a..555b76f8e 100644 --- a/app/javascript/mastodon/locales/pl.json +++ b/app/javascript/mastodon/locales/pl.json @@ -168,7 +168,7 @@ "status.mention": "Wspomnij o @{name}", "status.mute_conversation": "Wycisz konwersację", "status.open": "Rozszerz ten status", - "status.pin": "Pin on profile", + "status.pin": "Przypnij do profilu", "status.reblog": "Podbij", "status.reblogged_by": "{name} podbił", "status.reply": "Odpowiedz", @@ -180,7 +180,7 @@ "status.show_less": "Pokaż mniej", "status.show_more": "Pokaż więcej", "status.unmute_conversation": "Cofnij wyciszenie konwersacji", - "status.unpin": "Unpin from profile", + "status.unpin": "Odepnij z profilu", "tabs_bar.compose": "Napisz", "tabs_bar.federated_timeline": "Globalne", "tabs_bar.home": "Strona główna", diff --git a/config/locales/pl.yml b/config/locales/pl.yml index 182cbf65e..9f0d9bb29 100644 --- a/config/locales/pl.yml +++ b/config/locales/pl.yml @@ -438,6 +438,10 @@ pl: statuses: open_in_web: Otwórz w przeglądarce over_character_limit: limit %{max} znaków przekroczony + pin_errors: + ownership: Nie możesz przypiąć cudzego wpisu + private: Nie możesz przypiąć niepublicznego wpisu + reblog: Nie możesz przypiąć podbicia wpisu show_more: Pokaż więcej visibilities: private: Tylko dla śledzących @@ -448,6 +452,7 @@ pl: unlisted_long: Widoczne dla wszystkich, ale nie wyświetlane na publicznych osiach czasu stream_entries: click_to_show: Naciśnij aby wyświetlić + pinned: Przypięty wpis reblogged: podbił sensitive_content: Wrażliwa zawartość terms: -- cgit From 04c3fb2189315d03e87f000a40da973cf863cd30 Mon Sep 17 00:00:00 2001 From: Quent-in Date: Fri, 25 Aug 2017 16:04:52 +0200 Subject: i18n Updated strings (#4675 - pinned toot) (#4695) * Added string for pinned toots * Pinned toot #4675 + missing string Somehow I deleted it "enabled_success" * update after advice --- app/javascript/mastodon/locales/oc.json | 4 ++-- config/locales/oc.yml | 10 ++++++++-- 2 files changed, 10 insertions(+), 4 deletions(-) (limited to 'app/javascript') diff --git a/app/javascript/mastodon/locales/oc.json b/app/javascript/mastodon/locales/oc.json index 34e1a8c47..44e200d68 100644 --- a/app/javascript/mastodon/locales/oc.json +++ b/app/javascript/mastodon/locales/oc.json @@ -168,7 +168,7 @@ "status.mention": "Mencionar", "status.mute_conversation": "Rescondre la conversacion", "status.open": "Desplegar aqueste estatut", - "status.pin": "Pin on profile", + "status.pin": "Penjar al perfil", "status.reblog": "Partejar", "status.reblogged_by": "{name} a partejat :", "status.reply": "Respondre", @@ -180,7 +180,7 @@ "status.show_less": "Tornar plegar", "status.show_more": "Desplegar", "status.unmute_conversation": "Conversacions amb silenci levat", - "status.unpin": "Unpin from profile", + "status.unpin": "Despenjar del perfil", "tabs_bar.compose": "Compausar", "tabs_bar.federated_timeline": "Flux public global", "tabs_bar.home": "Acuèlh", diff --git a/config/locales/oc.yml b/config/locales/oc.yml index 35eb79b33..99c377f18 100644 --- a/config/locales/oc.yml +++ b/config/locales/oc.yml @@ -511,6 +511,10 @@ oc: statuses: open_in_web: Dobrir sul web over_character_limit: limit de %{max} caractèrs passat + pin_errors: + ownership: Se pòt pas penjar lo tut de qualqu’un mai + private: Se pòt pas penjar los tuts pas publics + reblog: Se pòt pas penjar un tut partejat show_more: Ne veire mai visibilities: private: Seguidors solament @@ -520,7 +524,8 @@ oc: unlisted: Pas listat unlisted_long: Tot lo mond pòt veire mai serà pas visible sul flux public stream_entries: - click_to_show: Clicatz per afichar + click_to_show: Clicatz per veire + pinned: Tut penjat reblogged: a partejat sensitive_content: Contengut sensible terms: @@ -601,7 +606,8 @@ oc: description_html: S’activatz l’autentificacion two-factor, vos caldrà vòstre mobil per vos connectar perque generarà un geton per vos daissar dintrar. disable: Desactivar enable: Activar - enabled: L’autentificacion en dos temps es activada + enabled: Autentificacion en dos temps activada + enabled_success: L’autentificacion en dos temps es ben activada generate_recovery_codes: Generar los còdis de recuperacion instructions_html: "Escanatz aqueste còdi QR amb Google Authenticator o una aplicacion similària sus vòstre mobil. A partir d’ara, aquesta aplicacion generarà un geton que vos caldrà picar per vos connectar." lost_recovery_codes: Los còdi de recuperacion vos permeton d’accedir a vòstre compte se perdètz vòstre mobil. S’avètz perdut vòstres còdis de recuperacion los podètz tornar generar aquí. Los ancians còdis seràn pas mai valides. -- cgit From 18f69fb964f73dce1e921b3fd1a391167d638e8a Mon Sep 17 00:00:00 2001 From: Lynx Kotoura Date: Sat, 26 Aug 2017 00:19:35 +0900 Subject: Adjust styles of landing pages. (#4682) * Adjust about.scss * Delete trailing whitespace. --- app/javascript/styles/about.scss | 856 ++++++++++++++++++--------------------- 1 file changed, 395 insertions(+), 461 deletions(-) (limited to 'app/javascript') diff --git a/app/javascript/styles/about.scss b/app/javascript/styles/about.scss index 020842f7d..28924738a 100644 --- a/app/javascript/styles/about.scss +++ b/app/javascript/styles/about.scss @@ -1,52 +1,96 @@ -.about-body { - .wrapper { - max-width: 600px; - margin: 0 auto; +.landing-page { + p, + li { + font-family: 'mastodon-font-sans-serif', sans-serif; + font-size: 16px; + font-weight: 400; + font-size: 16px; + line-height: 30px; + margin-bottom: 12px; color: $ui-primary-color; - padding-top: 50px; - padding-bottom: 50px; - &.thicc { - max-width: 800px; + a { + color: $ui-highlight-color; + text-decoration: underline; } } + em { + display: inline; + margin: 0; + padding: 0; + font-weight: 500; + background: transparent; + font-family: inherit; + font-size: inherit; + line-height: inherit; + color: lighten($ui-primary-color, 10%); + } + h1 { - font: 46px/52px 'mastodon-font-sans-serif', sans-serif; - font-weight: 600; + font-family: 'mastodon-font-display', sans-serif; + font-size: 26px; + line-height: 30px; + font-weight: 500; margin-bottom: 20px; - color: $ui-highlight-color; - padding: 20px 0; + color: $ui-secondary-color; - img { - margin-bottom: -5px; - margin-right: 5px; - width: 46px; - height: 46px; + small { + font-family: 'mastodon-font-sans-serif', sans-serif; + display: block; + font-size: 18px; + font-weight: 400; + color: $ui-base-lighter-color; } } h2 { font-family: 'mastodon-font-display', sans-serif; - font-size: 24px; - line-height: 28px; - font-weight: 400; + font-size: 22px; + line-height: 26px; + font-weight: 500; margin-bottom: 20px; - color: $primary-text-color; + color: $ui-secondary-color; } h3 { font-family: 'mastodon-font-display', sans-serif; - font-size: 20px; - line-height: 28px; - font-weight: 400; + font-size: 18px; + line-height: 24px; + font-weight: 500; + margin-bottom: 20px; + color: $ui-secondary-color; + } + + h4 { + font-family: 'mastodon-font-display', sans-serif; + font-size: 16px; + line-height: 24px; + font-weight: 500; + margin-bottom: 20px; + color: $ui-secondary-color; + } + + h5 { + font-family: 'mastodon-font-display', sans-serif; + font-size: 14px; + line-height: 24px; + font-weight: 500; + margin-bottom: 20px; + color: $ui-secondary-color; + } + + h6 { + font-family: 'mastodon-font-display', sans-serif; + font-size: 12px; + line-height: 24px; + font-weight: 500; margin-bottom: 20px; color: $ui-secondary-color; } ul, ol { - list-style: inherit; margin-left: 20px; &[type='a'] { @@ -58,220 +102,30 @@ } } - li > ol, - li > ul { - margin-top: 20px; + ul { + list-style: disc; } - p, - li { - font: 16px/28px 'mastodon-font-sans-serif', sans-serif; - font-weight: 400; - margin-bottom: 12px; - - a { - color: $ui-highlight-color; - text-decoration: underline; - } - } - - em { - display: inline-block; - padding: 7px 7px 5px; - margin: 0 2px; - background: $ui-primary-color; - color: $ui-base-color; - font: 16px/16px 'mastodon-font-sans-serif', sans-serif; - font-weight: 300; - } - - .screenshot { - box-shadow: 0 0 15px rgba($base-shadow-color, 0.4); - margin-bottom: 26px; - - img { - max-width: 100%; - height: auto; - display: block; - } - } - - .actions { - overflow: hidden; - margin-bottom: 20px; - - .info { - float: right; - text-align: right; - line-height: 36px; - - a { - color: $ui-primary-color; - text-decoration: underline; - } - } + ol { + list-style: decimal; } - @media screen and (max-width: 625px) { - .wrapper { - padding: 20px; - } + li > ol, + li > ul { + margin-top: 6px; } -} -.information-board { - background: darken($ui-base-color, 4%); - padding: 20px 0; - - .panel { - position: absolute; - width: 280px; - box-sizing: border-box; - background: darken($ui-base-color, 8%); - padding: 20px; - padding-top: 10px; - border-radius: 4px 4px 0 0; - right: 0; - bottom: -40px; - - .panel-header { - font-family: 'mastodon-font-display', sans-serif; - font-size: 14px; - line-height: 24px; - font-weight: 500; - color: $ui-primary-color; - padding-bottom: 5px; - margin-bottom: 15px; - border-bottom: 1px solid lighten($ui-base-color, 4%); - text-overflow: ellipsis; - white-space: nowrap; - overflow: hidden; - - a, - span { - font-weight: 400; - color: darken($ui-primary-color, 10%); - } - - a { - text-decoration: none; - } - } + hr { + border-color: rgba($ui-base-lighter-color, .6); } .container { - position: relative; - padding-right: 280px + 15px; - } - - .information-board-sections { - display: flex; - justify-content: space-between; - flex-wrap: wrap; - } - - .section { - flex: 1 0 auto; - font: 16px/28px 'mastodon-font-sans-serif', sans-serif; - text-align: right; - padding: 10px 15px; - - span, - strong { - display: block; - } - - span { - font-size: 16px; - - &:last-child { - color: $ui-secondary-color; - } - } - - strong { - font-weight: 500; - font-size: 32px; - line-height: 48px; - color: $primary-text-color; - } - } -} - -.owner { - text-align: center; - - .avatar { - width: 80px; - height: 80px; + width: 100%; + box-sizing: border-box; + max-width: 800px; margin: 0 auto; - margin-bottom: 15px; - - img { - display: block; - width: 80px; - height: 80px; - border-radius: 48px; - } - } - - .name { - font-size: 14px; - - a { - display: block; - color: $primary-text-color; - text-decoration: none; - - &:hover { - .display_name { - text-decoration: underline; - } - } - } - - .username { - display: block; - color: $ui-primary-color; - } - } -} - -.features-list__row { - display: flex; - padding: 10px 0; - justify-content: space-between; - - &:first-child { - padding-top: 0; - } - - .visual { - flex: 0 0 auto; - display: flex; - align-items: center; - margin-left: 15px; - - .fa { - display: block; - color: $ui-primary-color; - font-size: 48px; - } } - .text { - font-size: 16px; - line-height: 30px; - color: $ui-primary-color; - - h6 { - font-weight: 500; - color: $ui-secondary-color; - } - } -} - -.landing-page { .header-wrapper { padding-top: 15px; background: $ui-base-color; @@ -284,6 +138,17 @@ .hero .heading { padding-bottom: 30px; + font-family: 'mastodon-font-sans-serif', sans-serif; + font-size: 16px; + font-weight: 400; + font-size: 16px; + line-height: 30px; + color: $ui-primary-color; + + a { + color: $ui-highlight-color; + text-decoration: underline; + } } } @@ -307,17 +172,6 @@ } } - p, - li { - font: inherit; - font-weight: inherit; - margin-bottom: 0; - } - - hr { - border-color: rgba($ui-base-lighter-color, .6); - } - .header { line-height: 30px; overflow: hidden; @@ -327,6 +181,62 @@ justify-content: space-between; } + .links { + position: relative; + z-index: 4; + + a { + display: flex; + justify-content: center; + align-items: center; + color: $ui-primary-color; + text-decoration: none; + padding: 12px 16px; + line-height: 32px; + font-family: 'mastodon-font-display', sans-serif; + font-weight: 500; + font-size: 14px; + + &:hover { + color: $ui-secondary-color; + } + } + + .brand { + a { + padding-left: 0; + padding-right: 0; + color: $white; + } + + img { + height: 32px; + position: relative; + top: 4px; + left: -10px; + } + } + + ul { + list-style: none; + margin: 0; + + li { + display: inline-block; + vertical-align: bottom; + margin: 0; + + &:first-child a { + padding-left: 0; + } + + &:last-child a { + padding-right: 0; + } + } + } + } + .hero { margin-top: 50px; align-items: center; @@ -379,6 +289,12 @@ } } + .heading { + position: relative; + z-index: 4; + padding-bottom: 150px; + } + .simple_form, .closed-registrations-message { background: darken($ui-base-color, 4%); @@ -400,12 +316,6 @@ } } - .heading { - position: relative; - z-index: 4; - padding-bottom: 150px; - } - .closed-registrations-message { min-height: 330px; display: flex; @@ -413,233 +323,263 @@ justify-content: space-between; } } + } - ul { - list-style: none; - margin: 0; + .about-short { + background: darken($ui-base-color, 4%); + padding: 50px 0; + font-family: 'mastodon-font-sans-serif', sans-serif; + font-size: 16px; + font-weight: 400; + font-size: 16px; + line-height: 30px; + color: $ui-primary-color; - li { - display: inline-block; - vertical-align: bottom; - margin: 0; + a { + color: $ui-highlight-color; + text-decoration: underline; + } + } - &:first-child a { - padding-left: 0; - } + .information-board { + background: darken($ui-base-color, 4%); + padding: 20px 0; - &:last-child a { - padding-right: 0; - } - } + .container { + position: relative; + padding-right: 280px + 15px; } - .links { - position: relative; - z-index: 4; + .information-board-sections { + display: flex; + justify-content: space-between; + flex-wrap: wrap; + } - a { - display: flex; - justify-content: center; - align-items: center; - color: $ui-primary-color; - text-decoration: none; - padding: 12px 16px; - line-height: 32px; - font-family: 'mastodon-font-display', sans-serif; - font-weight: 500; - font-size: 14px; + .section { + flex: 1 0 0; + font-family: 'mastodon-font-sans-serif', sans-serif; + font-size: 16px; + line-height: 28px; + color: $primary-text-color; + text-align: right; + padding: 10px 15px; - &:hover { - color: $ui-secondary-color; - } + span, + strong { + display: block; } - .brand { - a { - padding-left: 0; - padding-right: 0; - color: $white; + span { + &:last-child { + color: $ui-secondary-color; } + } - img { - height: 32px; - position: relative; - top: 4px; - left: -10px; - } + strong { + font-weight: 500; + font-size: 32px; + line-height: 48px; } } - } - .container { - width: 100%; - box-sizing: border-box; - max-width: 800px; - margin: 0 auto; - } - - .wrapper { - max-width: 800px; - margin: 0 auto; - padding: 0; - } + .panel { + position: absolute; + width: 280px; + box-sizing: border-box; + background: darken($ui-base-color, 8%); + padding: 20px; + padding-top: 10px; + border-radius: 4px 4px 0 0; + right: 0; + bottom: -40px; - .about-short { - background: darken($ui-base-color, 4%); - padding: 50px 0; - } + .panel-header { + font-family: 'mastodon-font-display', sans-serif; + font-size: 14px; + line-height: 24px; + font-weight: 500; + color: $ui-primary-color; + padding-bottom: 5px; + margin-bottom: 15px; + border-bottom: 1px solid lighten($ui-base-color, 4%); + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + + a, + span { + font-weight: 400; + color: darken($ui-primary-color, 10%); + } - .extended-description { - padding: 50px 0; + a { + text-decoration: none; + } + } + } - ul, - ol { - list-style: inherit; - margin-left: 20px; + .owner { + text-align: center; - &[type='a'] { - list-style-type: lower-alpha; - } + .avatar { + width: 80px; + height: 80px; + margin: 0 auto; + margin-bottom: 15px; - &[type='i'] { - list-style-type: lower-roman; + img { + display: block; + width: 80px; + height: 80px; + border-radius: 48px; + } } - } - li > ol, - li > ul { - margin-top: 20px; - } + .name { + font-size: 14px; - p, - li { - font: 16px/28px 'mastodon-font-sans-serif', sans-serif; - font-weight: 400; - margin-bottom: 12px; - color: $ui-primary-color; + a { + display: block; + color: $primary-text-color; + text-decoration: none; + + &:hover { + .display_name { + text-decoration: underline; + } + } + } - a { - color: $ui-highlight-color; - text-decoration: underline; + .username { + display: block; + color: $ui-primary-color; + } } } } - h3 { - font-family: 'mastodon-font-display', sans-serif; - font-size: 16px; - line-height: 24px; - font-weight: 500; - margin-bottom: 20px; - color: $ui-secondary-color; - } - - p { - font-size: 16px; - line-height: 30px; - color: $ui-primary-color; - } - .features { padding: 50px 0; .container { display: flex; } - } - #mastodon-timeline { - display: flex; - -webkit-overflow-scrolling: touch; - -ms-overflow-style: -ms-autohiding-scrollbar; - font-family: 'mastodon-font-sans-serif', sans-serif; - font-size: 13px; - line-height: 18px; - font-weight: 400; - color: $primary-text-color; - width: 330px; - margin-right: 30px; - flex: 0 0 auto; - background: $ui-base-color; - overflow: hidden; - box-shadow: 0 0 6px rgba($black, 0.1); + #mastodon-timeline { + display: flex; + -webkit-overflow-scrolling: touch; + -ms-overflow-style: -ms-autohiding-scrollbar; + font-family: 'mastodon-font-sans-serif', sans-serif; + font-size: 13px; + line-height: 18px; + font-weight: 400; + color: $primary-text-color; + width: 330px; + margin-right: 30px; + flex: 0 0 auto; + background: $ui-base-color; + overflow: hidden; + box-shadow: 0 0 6px rgba($black, 0.1); + + .column-header { + color: inherit; + font-family: inherit; + font-size: 16px; + line-height: inherit; + font-weight: inherit; + margin: 0; + padding: 15px; + } - .column-header { - color: inherit; - font-family: inherit; - font-size: 16px; - line-height: inherit; - font-weight: inherit; - margin: 0; - padding: 15px; - } + .column { + padding: 0; + border-radius: 4px; + overflow: hidden; + } - .column { - padding: 0; - border-radius: 4px; - overflow: hidden; - } + .scrollable { + height: 400px; + } - .scrollable { - height: 400px; - } + p { + font-size: inherit; + line-height: inherit; + font-weight: inherit; + color: $primary-text-color; + margin-bottom: 20px; - p { - font-size: inherit; - line-height: inherit; - font-weight: inherit; - color: $primary-text-color; - margin-bottom: 20px; + &:last-child { + margin-bottom: 0; + } - &:last-child { - margin-bottom: 0; + a { + color: $ui-secondary-color; + text-decoration: none; + } } + } - a { - color: $ui-secondary-color; - text-decoration: none; + .about-mastodon { + max-width: 675px; + + p { + margin-bottom: 20px; } - } - } - .about-mastodon { - max-width: 675px; + .features-list { + margin-top: 20px; - p { - margin-bottom: 20px; - } + .features-list__row { + display: flex; + padding: 10px 0; + justify-content: space-between; - .features-list { - margin-top: 20px; - } - } + &:first-child { + padding-top: 0; + } - em { - display: inline; - margin: 0; - padding: 0; - font-weight: 500; - background: transparent; - font-family: inherit; - font-size: inherit; - line-height: inherit; - color: lighten($ui-primary-color, 10%); + .visual { + flex: 0 0 auto; + display: flex; + align-items: center; + margin-left: 15px; + + .fa { + display: block; + color: $ui-primary-color; + font-size: 48px; + } + } + + .text { + font-size: 16px; + line-height: 30px; + color: $ui-primary-color; + + h6 { + font-size: inherit; + line-height: inherit; + margin-bottom: 0; + } + } + } + } + } } - h1 { - font-family: 'mastodon-font-display', sans-serif; - font-size: 26px; + .extended-description { + padding: 50px 0; + font-family: 'mastodon-font-sans-serif', sans-serif; + font-size: 16px; + font-weight: 400; + font-size: 16px; line-height: 30px; - margin-bottom: 0; - font-weight: 500; - color: $ui-secondary-color; + color: $ui-primary-color; - small { - font-family: 'mastodon-font-sans-serif', sans-serif; - display: block; - font-size: 18px; - font-weight: 400; - color: $ui-base-lighter-color; + a { + color: $ui-highlight-color; + text-decoration: underline; } } @@ -663,8 +603,15 @@ padding: 0 20px; } - .information-board .container { - padding-right: 20px; + .information-board { + + .container { + padding-right: 20px; + } + + .section { + text-align: center; + } .panel { position: static; @@ -678,10 +625,6 @@ } } - .information-board .section { - text-align: center; - } - .header-wrapper .mascot { left: 20px; } @@ -699,6 +642,7 @@ &.compact .hero .heading { padding-bottom: 20px; + text-align: initial; } } @@ -707,51 +651,41 @@ display: block; } - .links { - padding-top: 15px; - background: darken($ui-base-color, 4%); - } - .header { - .hero { - margin-top: 30px; - padding: 0; + .links { + padding-top: 15px; + background: darken($ui-base-color, 4%); - .heading { - padding: 0 20px 20px; + a { + padding: 12px 8px; } - } - .floats { - display: none; - } - - .heading, - .nav { - text-align: center; - } + .nav { + display: flex; + flex-flow: row wrap; + justify-content: space-around; + } - .nav { - display: flex; - flex-flow: row wrap; - justify-content: space-around; + .brand img { + left: 0; + top: 0; + } } - .links a { - padding: 12px 8px; - } + .hero { + margin-top: 30px; + padding: 0; - .heading h1 { - padding: 30px 0; - } + .floats { + display: none; + } - .links .brand img { - left: 0; - top: 0; - } + .heading { + padding: 30px 20px; + text-align: center; + } - .hero { .simple_form, .closed-registrations-message { background: darken($ui-base-color, 8%); @@ -762,7 +696,7 @@ } } - #mastodon-timeline { + .features #mastodon-timeline { height: 70vh; width: 100%; margin-bottom: 50px; -- cgit From fb8aa2b3bac122f819b07c7f9461b26cc369c621 Mon Sep 17 00:00:00 2001 From: unarist Date: Sat, 26 Aug 2017 00:21:16 +0900 Subject: Apply user timezone for the title attribute of .time-ago (#4693) --- app/javascript/packs/public.js | 1 + 1 file changed, 1 insertion(+) (limited to 'app/javascript') diff --git a/app/javascript/packs/public.js b/app/javascript/packs/public.js index da1f550fc..d8a0f4eee 100644 --- a/app/javascript/packs/public.js +++ b/app/javascript/packs/public.js @@ -35,6 +35,7 @@ function main() { [].forEach.call(document.querySelectorAll('time.time-ago'), (content) => { const datetime = new Date(content.getAttribute('datetime')); + content.title = dateTimeFormat.format(datetime); content.textContent = relativeFormat.format(datetime); }); }); -- cgit From 21bb4a6c3b21f3d2a60d2ebf6475fc0b0e9fee5d Mon Sep 17 00:00:00 2001 From: Lynx Kotoura Date: Sat, 26 Aug 2017 03:02:44 +0900 Subject: Fix ar.json (#4699) Remove ! from compose_form.publish --- app/javascript/mastodon/locales/ar.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app/javascript') diff --git a/app/javascript/mastodon/locales/ar.json b/app/javascript/mastodon/locales/ar.json index fa8cda97d..2efa1fba3 100644 --- a/app/javascript/mastodon/locales/ar.json +++ b/app/javascript/mastodon/locales/ar.json @@ -47,7 +47,7 @@ "compose_form.lock_disclaimer.lock": "مقفل", "compose_form.placeholder": "فيمَ تفكّر؟", "compose_form.privacy_disclaimer": "Your private status will be delivered to mentioned users on {domains}. Do you trust {domainsCount, plural, one {that server} other {those servers}}? Post privacy only works on Mastodon instances. If {domains} {domainsCount, plural, one {is not a Mastodon instance} other {are not Mastodon instances}}, there will be no indication that your post is private, and it may be boosted or otherwise made visible to unintended recipients.", - "compose_form.publish": "بوّق !", + "compose_form.publish": "بوّق", "compose_form.publish_loud": "{publish}!", "compose_form.sensitive": "ضع علامة على الوسيط باعتباره حسّاس", "compose_form.spoiler": "أخفِ النص واعرض تحذيرا", -- cgit From 1cebfed23e03b9d31796cdc139acde1b6dccd9f3 Mon Sep 17 00:00:00 2001 From: Anna e só Date: Sat, 26 Aug 2017 08:45:35 -0300 Subject: Added new translations of error messages, block and mute domains and users, privacy disclaimers, etc (#4700) * Added new translations of error messages, block and mute domains and users * Added new translations of error messages, block and mute domains and users --- app/javascript/mastodon/locales/pt-BR.json | 76 +++++++++++++++--------------- 1 file changed, 38 insertions(+), 38 deletions(-) (limited to 'app/javascript') diff --git a/app/javascript/mastodon/locales/pt-BR.json b/app/javascript/mastodon/locales/pt-BR.json index 8a299e272..5f3797fee 100644 --- a/app/javascript/mastodon/locales/pt-BR.json +++ b/app/javascript/mastodon/locales/pt-BR.json @@ -1,68 +1,68 @@ { "account.block": "Bloquear @{name}", - "account.block_domain": "Hide everything from {domain}", - "account.disclaimer_full": "Information below may reflect the user's profile incompletely.", + "account.block_domain": "Esconder tudo de {domain}", + "account.disclaimer_full": "As informações abaixo podem refletir o perfil do usuário de maneira incompleta.", "account.edit_profile": "Editar perfil", "account.follow": "Seguir", "account.followers": "Seguidores", "account.follows": "Segue", - "account.follows_you": "É teu seguidor", - "account.media": "Media", + "account.follows_you": "É seu seguidor", + "account.media": "Mídia", "account.mention": "Mencionar @{name}", "account.mute": "Silenciar @{name}", "account.posts": "Posts", "account.report": "Denunciar @{name}", - "account.requested": "A aguardar aprovação", - "account.share": "Share @{name}'s profile", + "account.requested": "Aguardando aprovação", + "account.share": "Compartilhar perfil de @{name}", "account.unblock": "Não bloquear @{name}", - "account.unblock_domain": "Unhide {domain}", + "account.unblock_domain": "Desbloquear {domain}", "account.unfollow": "Deixar de seguir", "account.unmute": "Não silenciar @{name}", - "account.view_full_profile": "View full profile", + "account.view_full_profile": "Ver perfil completo", "boost_modal.combo": "Pode clicar {combo} para não voltar a ver", "bundle_column_error.body": "Something went wrong while loading this component.", - "bundle_column_error.retry": "Try again", + "bundle_column_error.retry": "Tente novamente", "bundle_column_error.title": "Network error", - "bundle_modal_error.close": "Close", + "bundle_modal_error.close": "Fechar", "bundle_modal_error.message": "Something went wrong while loading this component.", - "bundle_modal_error.retry": "Try again", - "column.blocks": "Utilizadores Bloqueados", + "bundle_modal_error.retry": "Tente novamente", + "column.blocks": "Usuários bloqueados", "column.community": "Local", "column.favourites": "Favoritos", - "column.follow_requests": "Seguidores Pendentes", - "column.home": "Home", - "column.mutes": "Utilizadores silenciados", + "column.follow_requests": "Seguidores pendentes", + "column.home": "Página inicial", + "column.mutes": "Usuários silenciados", "column.notifications": "Notificações", "column.public": "Global", "column_back_button.label": "Voltar", - "column_header.hide_settings": "Hide settings", - "column_header.moveLeft_settings": "Move column to the left", - "column_header.moveRight_settings": "Move column to the right", - "column_header.pin": "Pin", - "column_header.show_settings": "Show settings", - "column_header.unpin": "Unpin", - "column_subheading.navigation": "Navigation", - "column_subheading.settings": "Settings", - "compose_form.lock_disclaimer": "Your account is not {locked}. Anyone can follow you to view your follower-only posts.", + "column_header.hide_settings": "Esconder configurações", + "column_header.moveLeft_settings": "Mover coluna para a esquerda", + "column_header.moveRight_settings": "Mover coluna para a direita", + "column_header.pin": "Fixar", + "column_header.show_settings": "Mostrar configurações", + "column_header.unpin": "Desafixar", + "column_subheading.navigation": "Navegação", + "column_subheading.settings": "Configurações", + "compose_form.lock_disclaimer": "A sua conta não está {locked}. Qualquer pessoa pode te seguir e visualizar as suas postagens só para seguidores.", "compose_form.lock_disclaimer.lock": "locked", - "compose_form.placeholder": "Em que estás a pensar?", - "compose_form.privacy_disclaimer": "O teu conteúdo privado vai ser partilhado com os utilizadores do {domains}. Confias {domainsCount, plural, one {neste servidor} other {nestes servidores}}? A privacidade só funciona em instâncias do Mastodon. Se {domains} {domainsCount, plural, one {não é uma instância} other {não são instâncias}}, não existem indicadores da privacidade da tua partilha, e podem ser partilhados com outros.", + "compose_form.placeholder": "No que você está pensando?", + "compose_form.privacy_disclaimer": "O seu conteúdo privado será compartilhado com os usuários do {domains}. Você confia {domainsCount, plural, one {neste servidor} other {nestes servidores}}? As configurações de privacidade só funcionam em instâncias do Mastodon. Se {domains} {domainsCount, plural, one {não é uma instância} other {não são instâncias}}, não há como garantir a privacidade de suas postagens, e elas podem ser compartilhadas com outros.", "compose_form.publish": "Publicar", "compose_form.publish_loud": "{publish}!", - "compose_form.sensitive": "Marcar media como conteúdo sensível", + "compose_form.sensitive": "Marcar mídia como conteúdo sensível", "compose_form.spoiler": "Esconder texto com aviso", "compose_form.spoiler_placeholder": "Aviso de conteúdo", - "confirmation_modal.cancel": "Cancel", - "confirmations.block.confirm": "Block", - "confirmations.block.message": "Are you sure you want to block {name}?", - "confirmations.delete.confirm": "Delete", - "confirmations.delete.message": "Are you sure you want to delete this status?", - "confirmations.domain_block.confirm": "Hide entire domain", - "confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable.", - "confirmations.mute.confirm": "Mute", - "confirmations.mute.message": "Are you sure you want to mute {name}?", - "confirmations.unfollow.confirm": "Unfollow", - "confirmations.unfollow.message": "Are you sure you want to unfollow {name}?", + "confirmation_modal.cancel": "Cancelar", + "confirmations.block.confirm": "Bloquear", + "confirmations.block.message": "Você tem certeza de que quer bloquear {name}?", + "confirmations.delete.confirm": "Excluir", + "confirmations.delete.message": "Você tem certeza de que quer excluir este status?", + "confirmations.domain_block.confirm": "Esconder o domínio inteiro", + "confirmations.domain_block.message": "Você quer mesmo bloquear {domain} inteiro? Na maioria dos casos, silenciar ou bloquear alguns usuários é o suficiente e o recomendado.", + "confirmations.mute.confirm": "Silenciar", + "confirmations.mute.message": "Você tem certeza de que quer silenciar {name}?", + "confirmations.unfollow.confirm": "Deixar de seguir", + "confirmations.unfollow.message": "Você tem certeza de que quer deixar de seguir {name}?", "emoji_button.activity": "Activity", "emoji_button.flags": "Flags", "emoji_button.food": "Food & Drink", -- cgit From 8ecfdd8795624b74d14df27d5468580734e5aede Mon Sep 17 00:00:00 2001 From: Lynx Kotoura Date: Sat, 26 Aug 2017 21:23:20 +0900 Subject: Set margin between character-counter and compose-form__buttons (#4698) For some languages publish translation is long. --- app/javascript/styles/components.scss | 2 +- app/javascript/styles/rtl.scss | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'app/javascript') diff --git a/app/javascript/styles/components.scss b/app/javascript/styles/components.scss index ef1797e72..32cf450b7 100644 --- a/app/javascript/styles/components.scss +++ b/app/javascript/styles/components.scss @@ -1872,7 +1872,7 @@ .character-counter__wrapper { line-height: 36px; - margin-right: 16px; + margin: 0 16px 0 8px; padding-top: 10px; } diff --git a/app/javascript/styles/rtl.scss b/app/javascript/styles/rtl.scss index 4966fbc21..c8872c732 100644 --- a/app/javascript/styles/rtl.scss +++ b/app/javascript/styles/rtl.scss @@ -8,7 +8,7 @@ body.rtl { } .character-counter__wrapper { - margin-right: 0; + margin-right: 8px; margin-left: 16px; } -- cgit From 26402ee2cb57b4c0695c584fa24acfe429c14547 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Sun, 27 Aug 2017 13:35:18 +0200 Subject: Adjust RTL styles (#4712) --- app/javascript/styles/rtl.scss | 127 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 119 insertions(+), 8 deletions(-) (limited to 'app/javascript') diff --git a/app/javascript/styles/rtl.scss b/app/javascript/styles/rtl.scss index c8872c732..6c003d69a 100644 --- a/app/javascript/styles/rtl.scss +++ b/app/javascript/styles/rtl.scss @@ -32,6 +32,11 @@ body.rtl { right: auto; } + .column-header__back-button { + padding-left: 5px; + padding-right: 0; + } + .column-header__setting-arrows { float: left; } @@ -54,25 +59,64 @@ body.rtl { right: 10px; } - .status { + .status, + .activity-stream .status.light { padding-left: 10px; padding-right: 68px; } - .status__info .status__display-name { + .status__info .status__display-name, + .activity-stream .status.light .status__display-name { padding-left: 25px; padding-right: 0; } + .activity-stream .pre-header { + padding-right: 68px; + padding-left: 0; + } + + .status__prepend { + margin-left: 0; + margin-right: 68px; + } + + .status__prepend-icon-wrapper { + left: auto; + right: -26px; + } + + .activity-stream .pre-header .pre-header__icon { + left: auto; + right: 42px; + } + + .account__avatar-overlay-overlay { + right: auto; + left: 0; + } + .column-back-button--slim-button { right: auto; left: 0; } - .status__relative-time { + .status__relative-time, + .activity-stream .status.light .status__header .status__meta { float: left; } + .activity-stream .detailed-status.light .detailed-status__display-name > div { + float: right; + margin-right: 0; + margin-left: 10px; + } + + .activity-stream .detailed-status.light .detailed-status__meta span > span { + margin-left: 0; + margin-right: 6px; + } + .status__action-bar-button { float: right; margin-right: 0; @@ -129,6 +173,78 @@ body.rtl { right: -2.14285714em; } + .admin-wrapper .sidebar ul a i.fa, + a.table-action-link i.fa { + margin-right: 0; + margin-left: 5px; + } + + .simple_form .check_boxes .checkbox label, + .simple_form .input.with_label.boolean label.checkbox { + padding-left: 0; + padding-right: 25px; + } + + .simple_form .check_boxes .checkbox input[type="checkbox"], + .simple_form .input.boolean input[type="checkbox"] { + left: auto; + right: 0; + } + + .simple_form .input-with-append .input input { + padding-left: 127px; + padding-right: 0; + } + + .simple_form .input-with-append .append { + right: auto; + left: 0; + } + + .table th, + .table td { + text-align: right; + } + + .filters .filter-subset { + margin-right: 0; + margin-left: 45px; + } + + .landing-page .header-wrapper .mascot { + right: 60px; + left: auto; + } + + .landing-page .header .hero .floats .float-1 { + left: -120px; + right: auto; + } + + .landing-page .header .hero .floats .float-2 { + left: 210px; + right: auto; + } + + .landing-page .header .hero .floats .float-3 { + left: 110px; + right: auto; + } + + .landing-page .header .links .brand img { + left: 0; + } + + .landing-page .fa-external-link { + padding-right: 5px; + padding-left: 0 !important; + } + + .landing-page .features #mastodon-timeline { + margin-right: 0; + margin-left: 30px; + } + @media screen and (min-width: 1025px) { .column, .drawer { @@ -139,11 +255,6 @@ body.rtl { padding-left: 5px; padding-right: 10px; } - - &:last-child { - padding-right: 0; - padding-left: 10px; - } } .columns-area > div { -- cgit From f92d991e529686a3bd402b957a2c3727a0094b85 Mon Sep 17 00:00:00 2001 From: mayaeh Date: Mon, 28 Aug 2017 00:03:27 +0900 Subject: Add japanese translations for Pinned statuses based on pawoo. (#4717) Add japanese translations for pin_errors. --- app/javascript/mastodon/locales/ja.json | 4 ++-- config/locales/ja.yml | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) (limited to 'app/javascript') diff --git a/app/javascript/mastodon/locales/ja.json b/app/javascript/mastodon/locales/ja.json index c35b0def3..4877490f0 100644 --- a/app/javascript/mastodon/locales/ja.json +++ b/app/javascript/mastodon/locales/ja.json @@ -168,7 +168,7 @@ "status.mention": "返信", "status.mute_conversation": "会話をミュート", "status.open": "詳細を表示", - "status.pin": "Pin on profile", + "status.pin": "プロフィールに固定表示", "status.reblog": "ブースト", "status.reblogged_by": "{name}さんにブーストされました", "status.reply": "返信", @@ -180,7 +180,7 @@ "status.show_less": "隠す", "status.show_more": "もっと見る", "status.unmute_conversation": "会話のミュートを解除", - "status.unpin": "Unpin from profile", + "status.unpin": "プロフィールの固定表示を解除", "tabs_bar.compose": "投稿", "tabs_bar.federated_timeline": "連合", "tabs_bar.home": "ホーム", diff --git a/config/locales/ja.yml b/config/locales/ja.yml index b847708d3..f671f594f 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -433,6 +433,10 @@ ja: statuses: open_in_web: Webで開く over_character_limit: 上限は %{max}文字までです + pin_errors: + ownership: 他人のトゥートを固定することはできません + private: 非公開のトゥートを固定することはできません + reblog: ブーストされたトゥートを固定することはできません show_more: もっと見る visibilities: private: 非公開 @@ -443,6 +447,7 @@ ja: unlisted_long: 誰でも見ることができますが、公開タイムラインには表示されません stream_entries: click_to_show: クリックして表示 + pinned: 固定されたトゥート reblogged: さんにブーストされました sensitive_content: 閲覧注意 terms: -- cgit From bab9afaa092e0eb8b3f11fdcdaf7a75d3ce94566 Mon Sep 17 00:00:00 2001 From: Lynx Kotoura Date: Mon, 28 Aug 2017 06:59:51 +0900 Subject: Adjust public profile pages (#4713) * Adjust account-grid in public profiles Full-width card on mobile UI. Set break-word for long name and ID. Fix margin. * Reduce padding-bottom of public profiles * Revive next prev buttons in mobile public profiles In followers followees pages. * Revert break-word for username * Fix overflow of display_name Need re-setting text-overflow and overflow in display: block; --- app/javascript/styles/accounts.scss | 14 +++++++++----- app/javascript/styles/basics.scss | 2 +- 2 files changed, 10 insertions(+), 6 deletions(-) (limited to 'app/javascript') diff --git a/app/javascript/styles/accounts.scss b/app/javascript/styles/accounts.scss index f1fbe873b..744650554 100644 --- a/app/javascript/styles/accounts.scss +++ b/app/javascript/styles/accounts.scss @@ -296,7 +296,9 @@ } .next, - .prev { + .prev, + .next a, + .prev a { display: inline-block; } } @@ -306,7 +308,7 @@ box-shadow: 0 0 15px rgba($base-shadow-color, 0.2); background: darken($simple-background-color, 8%); border-radius: 0 0 4px 4px; - padding: 20px 10px; + padding: 20px 5px; padding-bottom: 10px; overflow: hidden; display: flex; @@ -325,11 +327,11 @@ background: $simple-background-color; border-radius: 4px; color: $ui-base-color; - margin-bottom: 10px; + margin: 0 5px 10px; position: relative; - &:nth-child(odd) { - margin-right: 10px; + @media screen and (max-width: 740px) { + width: calc(100% - 10px); } .account-grid-card__header { @@ -400,6 +402,8 @@ .display_name { font-size: 16px; display: block; + text-overflow: ellipsis; + overflow: hidden; } .username { diff --git a/app/javascript/styles/basics.scss b/app/javascript/styles/basics.scss index e524b7f26..6e87157ec 100644 --- a/app/javascript/styles/basics.scss +++ b/app/javascript/styles/basics.scss @@ -6,7 +6,7 @@ body { line-height: 18px; font-weight: 400; color: $primary-text-color; - padding-bottom: 140px; + padding-bottom: 40px; text-rendering: optimizelegibility; font-feature-settings: "kern"; text-size-adjust: none; -- cgit From 07994eed002375025f0c377079500d25e87cb641 Mon Sep 17 00:00:00 2001 From: Lynx Kotoura Date: Mon, 28 Aug 2017 07:01:07 +0900 Subject: Adjust "signed in as" pages (#4720) * Adjust "signed in as" pages Fix min-width Set width of .account-header .name To apply text-overflow and overflow settings Set overflow for detailed-status__display-name * Remove trailing whitespace --- app/javascript/styles/components.scss | 2 ++ app/javascript/styles/containers.scss | 6 +++++- app/javascript/styles/forms.scss | 4 ++++ 3 files changed, 11 insertions(+), 1 deletion(-) (limited to 'app/javascript') diff --git a/app/javascript/styles/components.scss b/app/javascript/styles/components.scss index 32cf450b7..3a6672b9f 100644 --- a/app/javascript/styles/components.scss +++ b/app/javascript/styles/components.scss @@ -1057,6 +1057,8 @@ strong, span { display: block; + text-overflow: ellipsis; + overflow: hidden; } strong { diff --git a/app/javascript/styles/containers.scss b/app/javascript/styles/containers.scss index cfe8ea643..af2589e23 100644 --- a/app/javascript/styles/containers.scss +++ b/app/javascript/styles/containers.scss @@ -72,7 +72,7 @@ margin-bottom: -30px; margin-top: 40px; - @media screen and (max-width: 400px) { + @media screen and (max-width: 440px) { width: 100%; margin: 0; margin-bottom: 10px; @@ -97,10 +97,13 @@ .name { flex: 1 1 auto; color: $ui-secondary-color; + width: calc(100% - 88px); .username { display: block; font-weight: 500; + text-overflow: ellipsis; + overflow: hidden; } } @@ -108,5 +111,6 @@ display: block; font-size: 32px; line-height: 40px; + margin-left: 8px; } } diff --git a/app/javascript/styles/forms.scss b/app/javascript/styles/forms.scss index 8e41bb002..78f13270a 100644 --- a/app/javascript/styles/forms.scss +++ b/app/javascript/styles/forms.scss @@ -359,6 +359,10 @@ code { color: $ui-secondary-color; font-weight: 500; } + + @media screen and (max-width: 740px) and (min-width: 441px) { + margin-top: 40px; + } } .qr-wrapper { -- cgit From 0827c09c448ea8d61e62534dd3547719e148a4ae Mon Sep 17 00:00:00 2001 From: abcang Date: Tue, 29 Aug 2017 05:23:44 +0900 Subject: Generalized the infinite scrollable list (#4697) --- app/javascript/mastodon/components/account.js | 12 +- .../components/intersection_observer_article.js | 122 ++++++++++++++ .../mastodon/components/scrollable_list.js | 179 +++++++++++++++++++++ app/javascript/mastodon/components/status.js | 112 ++----------- app/javascript/mastodon/components/status_list.js | 157 ++---------------- .../mastodon/features/favourited_statuses/index.js | 5 +- .../notifications/components/notification.js | 10 +- .../mastodon/features/notifications/index.js | 105 ++++-------- 8 files changed, 379 insertions(+), 323 deletions(-) create mode 100644 app/javascript/mastodon/components/intersection_observer_article.js create mode 100644 app/javascript/mastodon/components/scrollable_list.js (limited to 'app/javascript') diff --git a/app/javascript/mastodon/components/account.js b/app/javascript/mastodon/components/account.js index 69cc63d10..6456c12ba 100644 --- a/app/javascript/mastodon/components/account.js +++ b/app/javascript/mastodon/components/account.js @@ -26,6 +26,7 @@ export default class Account extends ImmutablePureComponent { onBlock: PropTypes.func.isRequired, onMute: PropTypes.func.isRequired, intl: PropTypes.object.isRequired, + hidden: PropTypes.bool, }; handleFollow = () => { @@ -41,12 +42,21 @@ export default class Account extends ImmutablePureComponent { } render () { - const { account, me, intl } = this.props; + const { account, me, intl, hidden } = this.props; if (!account) { return
    ; } + if (hidden) { + return ( +
    + {account.get('display_name')} + {account.get('username')} +
    + ); + } + let buttons; if (account.get('id') !== me && account.get('relationship', null) !== null) { diff --git a/app/javascript/mastodon/components/intersection_observer_article.js b/app/javascript/mastodon/components/intersection_observer_article.js new file mode 100644 index 000000000..347767818 --- /dev/null +++ b/app/javascript/mastodon/components/intersection_observer_article.js @@ -0,0 +1,122 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import ImmutablePureComponent from 'react-immutable-pure-component'; +import scheduleIdleTask from '../features/ui/util/schedule_idle_task'; +import getRectFromEntry from '../features/ui/util/get_rect_from_entry'; + +export default class IntersectionObserverArticle extends ImmutablePureComponent { + + static propTypes = { + intersectionObserverWrapper: PropTypes.object, + id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), + index: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), + listLength: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), + children: PropTypes.node, + }; + + state = { + isHidden: false, // set to true in requestIdleCallback to trigger un-render + } + + shouldComponentUpdate (nextProps, nextState) { + if (!nextState.isIntersecting && nextState.isHidden) { + // It's only if we're not intersecting (i.e. offscreen) and isHidden is true + // that either "isIntersecting" or "isHidden" matter, and then they're + // the only things that matter (and updated ARIA attributes). + return this.state.isIntersecting || !this.state.isHidden || nextProps.listLength !== this.props.listLength; + } else if (nextState.isIntersecting && !this.state.isIntersecting) { + // If we're going from a non-intersecting state to an intersecting state, + // (i.e. offscreen to onscreen), then we definitely need to re-render + return true; + } + // Otherwise, diff based on "updateOnProps" and "updateOnStates" + return super.shouldComponentUpdate(nextProps, nextState); + } + + componentDidMount () { + if (!this.props.intersectionObserverWrapper) { + // TODO: enable IntersectionObserver optimization for notification statuses. + // These are managed in notifications/index.js rather than status_list.js + return; + } + this.props.intersectionObserverWrapper.observe( + this.props.id, + this.node, + this.handleIntersection + ); + + this.componentMounted = true; + } + + componentWillUnmount () { + if (this.props.intersectionObserverWrapper) { + this.props.intersectionObserverWrapper.unobserve(this.props.id, this.node); + } + + this.componentMounted = false; + } + + handleIntersection = (entry) => { + if (this.node && this.node.children.length !== 0) { + // save the height of the fully-rendered element + this.height = getRectFromEntry(entry).height; + + if (this.props.onHeightChange) { + this.props.onHeightChange(this.props.status, this.height); + } + } + + this.setState((prevState) => { + if (prevState.isIntersecting && !entry.isIntersecting) { + scheduleIdleTask(this.hideIfNotIntersecting); + } + return { + isIntersecting: entry.isIntersecting, + isHidden: false, + }; + }); + } + + hideIfNotIntersecting = () => { + if (!this.componentMounted) { + return; + } + + // When the browser gets a chance, test if we're still not intersecting, + // and if so, set our isHidden to true to trigger an unrender. The point of + // this is to save DOM nodes and avoid using up too much memory. + // See: https://github.com/tootsuite/mastodon/issues/2900 + this.setState((prevState) => ({ isHidden: !prevState.isIntersecting })); + } + + handleRef = (node) => { + this.node = node; + } + + render () { + const { children, id, index, listLength } = this.props; + const { isIntersecting, isHidden } = this.state; + + if (!isIntersecting && isHidden) { + return ( +
    + {children && React.cloneElement(children, { hidden: true })} +
    + ); + } + + return ( +
    + {children && React.cloneElement(children, { hidden: false })} +
    + ); + } + +} diff --git a/app/javascript/mastodon/components/scrollable_list.js b/app/javascript/mastodon/components/scrollable_list.js new file mode 100644 index 000000000..1a122dbe5 --- /dev/null +++ b/app/javascript/mastodon/components/scrollable_list.js @@ -0,0 +1,179 @@ +import React, { PureComponent } from 'react'; +import { ScrollContainer } from 'react-router-scroll'; +import PropTypes from 'prop-types'; +import IntersectionObserverArticle from './intersection_observer_article'; +import LoadMore from './load_more'; +import IntersectionObserverWrapper from '../features/ui/util/intersection_observer_wrapper'; +import { throttle } from 'lodash'; + +export default class ScrollableList extends PureComponent { + + static propTypes = { + scrollKey: PropTypes.string.isRequired, + onScrollToBottom: PropTypes.func, + onScrollToTop: PropTypes.func, + onScroll: PropTypes.func, + trackScroll: PropTypes.bool, + shouldUpdateScroll: PropTypes.func, + isLoading: PropTypes.bool, + hasMore: PropTypes.bool, + prepend: PropTypes.node, + emptyMessage: PropTypes.node, + children: PropTypes.node, + }; + + static defaultProps = { + trackScroll: true, + }; + + intersectionObserverWrapper = new IntersectionObserverWrapper(); + + handleScroll = throttle(() => { + if (this.node) { + const { scrollTop, scrollHeight, clientHeight } = this.node; + const offset = scrollHeight - scrollTop - clientHeight; + this._oldScrollPosition = scrollHeight - scrollTop; + + if (400 > offset && this.props.onScrollToBottom && !this.props.isLoading) { + this.props.onScrollToBottom(); + } else if (scrollTop < 100 && this.props.onScrollToTop) { + this.props.onScrollToTop(); + } else if (this.props.onScroll) { + this.props.onScroll(); + } + } + }, 150, { + trailing: true, + }); + + componentDidMount () { + this.attachScrollListener(); + this.attachIntersectionObserver(); + + // Handle initial scroll posiiton + this.handleScroll(); + } + + componentDidUpdate (prevProps) { + // Reset the scroll position when a new child comes in in order not to + // jerk the scrollbar around if you're already scrolled down the page. + if (React.Children.count(prevProps.children) < React.Children.count(this.props.children) && this._oldScrollPosition && this.node.scrollTop > 0) { + if (this.getFirstChildKey(prevProps) !== this.getFirstChildKey(this.props)) { + const newScrollTop = this.node.scrollHeight - this._oldScrollPosition; + if (this.node.scrollTop !== newScrollTop) { + this.node.scrollTop = newScrollTop; + } + } else { + this._oldScrollPosition = this.node.scrollHeight - this.node.scrollTop; + } + } + } + + componentWillUnmount () { + this.detachScrollListener(); + this.detachIntersectionObserver(); + } + + attachIntersectionObserver () { + this.intersectionObserverWrapper.connect({ + root: this.node, + rootMargin: '300% 0px', + }); + } + + detachIntersectionObserver () { + this.intersectionObserverWrapper.disconnect(); + } + + attachScrollListener () { + this.node.addEventListener('scroll', this.handleScroll); + } + + detachScrollListener () { + this.node.removeEventListener('scroll', this.handleScroll); + } + + getFirstChildKey (props) { + const { children } = props; + const firstChild = Array.isArray(children) ? children[0] : children; + return firstChild && firstChild.key; + } + + setRef = (c) => { + this.node = c; + } + + handleLoadMore = (e) => { + e.preventDefault(); + this.props.onScrollToBottom(); + } + + handleKeyDown = (e) => { + if (['PageDown', 'PageUp'].includes(e.key) || (e.ctrlKey && ['End', 'Home'].includes(e.key))) { + const article = (() => { + switch (e.key) { + case 'PageDown': + return e.target.nodeName === 'ARTICLE' && e.target.nextElementSibling; + case 'PageUp': + return e.target.nodeName === 'ARTICLE' && e.target.previousElementSibling; + case 'End': + return this.node.querySelector('[role="feed"] > article:last-of-type'); + case 'Home': + return this.node.querySelector('[role="feed"] > article:first-of-type'); + default: + return null; + } + })(); + + + if (article) { + e.preventDefault(); + article.focus(); + article.scrollIntoView(); + } + } + } + + render () { + const { children, scrollKey, trackScroll, shouldUpdateScroll, isLoading, hasMore, prepend, emptyMessage } = this.props; + const childrenCount = React.Children.count(children); + + const loadMore = 0 && hasMore} onClick={this.handleLoadMore} />; + let scrollableArea = null; + + if (isLoading || childrenCount > 0 || !emptyMessage) { + scrollableArea = ( +
    +
    + {prepend} + + {React.Children.map(this.props.children, (child, index) => ( + + {child} + + ))} + + {loadMore} +
    +
    + ); + } else { + scrollableArea = ( +
    + {emptyMessage} +
    + ); + } + + if (trackScroll) { + return ( + + {scrollableArea} + + ); + } else { + return scrollableArea; + } + } + +} diff --git a/app/javascript/mastodon/components/status.js b/app/javascript/mastodon/components/status.js index b4f523f72..4ab40d6bf 100644 --- a/app/javascript/mastodon/components/status.js +++ b/app/javascript/mastodon/components/status.js @@ -9,13 +9,11 @@ import StatusContent from './status_content'; import StatusActionBar from './status_action_bar'; import { FormattedMessage } from 'react-intl'; import ImmutablePureComponent from 'react-immutable-pure-component'; -import scheduleIdleTask from '../features/ui/util/schedule_idle_task'; import { MediaGallery, VideoPlayer } from '../features/ui/util/async-components'; // We use the component (and not the container) since we do not want // to use the progress bar to show download progress import Bundle from '../features/ui/components/bundle'; -import getRectFromEntry from '../features/ui/util/get_rect_from_entry'; export default class Status extends ImmutablePureComponent { @@ -26,7 +24,6 @@ export default class Status extends ImmutablePureComponent { static propTypes = { status: ImmutablePropTypes.map, account: ImmutablePropTypes.map, - wrapped: PropTypes.bool, onReply: PropTypes.func, onFavourite: PropTypes.func, onReblog: PropTypes.func, @@ -40,14 +37,11 @@ export default class Status extends ImmutablePureComponent { boostModal: PropTypes.bool, autoPlayGif: PropTypes.bool, muted: PropTypes.bool, - intersectionObserverWrapper: PropTypes.object, - index: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), - listLength: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), + hidden: PropTypes.bool, }; state = { isExpanded: false, - isHidden: false, // set to true in requestIdleCallback to trigger un-render } // Avoid checking props that are functions (and whose equality will always @@ -55,91 +49,15 @@ export default class Status extends ImmutablePureComponent { updateOnProps = [ 'status', 'account', - 'wrapped', 'me', 'boostModal', 'autoPlayGif', 'muted', - 'listLength', + 'hidden', ] updateOnStates = ['isExpanded'] - shouldComponentUpdate (nextProps, nextState) { - if (!nextState.isIntersecting && nextState.isHidden) { - // It's only if we're not intersecting (i.e. offscreen) and isHidden is true - // that either "isIntersecting" or "isHidden" matter, and then they're - // the only things that matter (and updated ARIA attributes). - return this.state.isIntersecting || !this.state.isHidden || nextProps.listLength !== this.props.listLength; - } else if (nextState.isIntersecting && !this.state.isIntersecting) { - // If we're going from a non-intersecting state to an intersecting state, - // (i.e. offscreen to onscreen), then we definitely need to re-render - return true; - } - // Otherwise, diff based on "updateOnProps" and "updateOnStates" - return super.shouldComponentUpdate(nextProps, nextState); - } - - componentDidMount () { - if (!this.props.intersectionObserverWrapper) { - // TODO: enable IntersectionObserver optimization for notification statuses. - // These are managed in notifications/index.js rather than status_list.js - return; - } - this.props.intersectionObserverWrapper.observe( - this.props.id, - this.node, - this.handleIntersection - ); - - this.componentMounted = true; - } - - componentWillUnmount () { - if (this.props.intersectionObserverWrapper) { - this.props.intersectionObserverWrapper.unobserve(this.props.id, this.node); - } - - this.componentMounted = false; - } - - handleIntersection = (entry) => { - if (this.node && this.node.children.length !== 0) { - // save the height of the fully-rendered element - this.height = getRectFromEntry(entry).height; - - if (this.props.onHeightChange) { - this.props.onHeightChange(this.props.status, this.height); - } - } - - this.setState((prevState) => { - if (prevState.isIntersecting && !entry.isIntersecting) { - scheduleIdleTask(this.hideIfNotIntersecting); - } - return { - isIntersecting: entry.isIntersecting, - isHidden: false, - }; - }); - } - - hideIfNotIntersecting = () => { - if (!this.componentMounted) { - return; - } - - // When the browser gets a chance, test if we're still not intersecting, - // and if so, set our isHidden to true to trigger an unrender. The point of - // this is to save DOM nodes and avoid using up too much memory. - // See: https://github.com/tootsuite/mastodon/issues/2900 - this.setState((prevState) => ({ isHidden: !prevState.isIntersecting })); - } - - handleRef = (node) => { - this.node = node; - } - handleClick = () => { if (!this.context.router) { return; @@ -173,25 +91,19 @@ export default class Status extends ImmutablePureComponent { let media = null; let statusAvatar; - // Exclude intersectionObserverWrapper from `other` variable - // because intersection is managed in here. - const { status, account, intersectionObserverWrapper, index, listLength, wrapped, ...other } = this.props; - const { isExpanded, isIntersecting, isHidden } = this.state; + const { status, account, hidden, ...other } = this.props; + const { isExpanded } = this.state; if (status === null) { return null; } - const hasIntersectionObserverWrapper = !!this.props.intersectionObserverWrapper; - const isHiddenForSure = isIntersecting === false && isHidden; - const visibilityUnknownButHeightIsCached = isIntersecting === undefined && status.has('height'); - - if (hasIntersectionObserverWrapper && (isHiddenForSure || visibilityUnknownButHeightIsCached)) { + if (hidden) { return ( -
    +
    {status.getIn(['account', 'display_name']) || status.getIn(['account', 'username'])} {status.get('content')} -
    +
    ); } @@ -199,14 +111,14 @@ export default class Status extends ImmutablePureComponent { const display_name_html = { __html: status.getIn(['account', 'display_name_html']) }; return ( -
    +
    }} />
    - -
    + + ); } @@ -235,7 +147,7 @@ export default class Status extends ImmutablePureComponent { } return ( -
    +
    @@ -253,7 +165,7 @@ export default class Status extends ImmutablePureComponent { {media} -
    + ); } diff --git a/app/javascript/mastodon/components/status_list.js b/app/javascript/mastodon/components/status_list.js index ca443c286..cbae28afe 100644 --- a/app/javascript/mastodon/components/status_list.js +++ b/app/javascript/mastodon/components/status_list.js @@ -1,12 +1,9 @@ import React from 'react'; import ImmutablePropTypes from 'react-immutable-proptypes'; -import { ScrollContainer } from 'react-router-scroll'; import PropTypes from 'prop-types'; import StatusContainer from '../containers/status_container'; -import LoadMore from './load_more'; import ImmutablePureComponent from 'react-immutable-pure-component'; -import IntersectionObserverWrapper from '../features/ui/util/intersection_observer_wrapper'; -import { throttle } from 'lodash'; +import ScrollableList from './scrollable_list'; export default class StatusList extends ImmutablePureComponent { @@ -28,145 +25,21 @@ export default class StatusList extends ImmutablePureComponent { trackScroll: true, }; - intersectionObserverWrapper = new IntersectionObserverWrapper(); - - handleScroll = throttle(() => { - if (this.node) { - const { scrollTop, scrollHeight, clientHeight } = this.node; - const offset = scrollHeight - scrollTop - clientHeight; - this._oldScrollPosition = scrollHeight - scrollTop; - - if (400 > offset && this.props.onScrollToBottom && !this.props.isLoading) { - this.props.onScrollToBottom(); - } else if (scrollTop < 100 && this.props.onScrollToTop) { - this.props.onScrollToTop(); - } else if (this.props.onScroll) { - this.props.onScroll(); - } - } - }, 150, { - trailing: true, - }); - - componentDidMount () { - this.attachScrollListener(); - this.attachIntersectionObserver(); - - // Handle initial scroll posiiton - this.handleScroll(); - } - - componentDidUpdate (prevProps) { - // Reset the scroll position when a new toot comes in in order not to - // jerk the scrollbar around if you're already scrolled down the page. - if (prevProps.statusIds.size < this.props.statusIds.size && this._oldScrollPosition && this.node.scrollTop > 0) { - if (prevProps.statusIds.first() !== this.props.statusIds.first()) { - let newScrollTop = this.node.scrollHeight - this._oldScrollPosition; - if (this.node.scrollTop !== newScrollTop) { - this.node.scrollTop = newScrollTop; - } - } else { - this._oldScrollPosition = this.node.scrollHeight - this.node.scrollTop; - } - } - } - - componentWillUnmount () { - this.detachScrollListener(); - this.detachIntersectionObserver(); - } - - attachIntersectionObserver () { - this.intersectionObserverWrapper.connect({ - root: this.node, - rootMargin: '300% 0px', - }); - } - - detachIntersectionObserver () { - this.intersectionObserverWrapper.disconnect(); - } - - attachScrollListener () { - this.node.addEventListener('scroll', this.handleScroll); - } - - detachScrollListener () { - this.node.removeEventListener('scroll', this.handleScroll); - } - - setRef = (c) => { - this.node = c; - } - - handleLoadMore = (e) => { - e.preventDefault(); - this.props.onScrollToBottom(); - } - - handleKeyDown = (e) => { - if (['PageDown', 'PageUp'].includes(e.key) || (e.ctrlKey && ['End', 'Home'].includes(e.key))) { - const article = (() => { - switch (e.key) { - case 'PageDown': - return e.target.nodeName === 'ARTICLE' && e.target.nextElementSibling; - case 'PageUp': - return e.target.nodeName === 'ARTICLE' && e.target.previousElementSibling; - case 'End': - return this.node.querySelector('[role="feed"] > article:last-of-type'); - case 'Home': - return this.node.querySelector('[role="feed"] > article:first-of-type'); - default: - return null; - } - })(); - - - if (article) { - e.preventDefault(); - article.focus(); - article.scrollIntoView(); - } - } - } - render () { - const { statusIds, scrollKey, trackScroll, shouldUpdateScroll, isLoading, hasMore, prepend, emptyMessage } = this.props; - - const loadMore = 0 && hasMore} onClick={this.handleLoadMore} />; - let scrollableArea = null; - - if (isLoading || statusIds.size > 0 || !emptyMessage) { - scrollableArea = ( -
    -
    - {prepend} - - {statusIds.map((statusId, index) => { - return ; - })} - - {loadMore} -
    -
    - ); - } else { - scrollableArea = ( -
    - {emptyMessage} -
    - ); - } - - if (trackScroll) { - return ( - - {scrollableArea} - - ); - } else { - return scrollableArea; - } + const { statusIds, ...other } = this.props; + const { isLoading } = other; + + const scrollableContent = (isLoading || statusIds.size > 0) ? ( + statusIds.map((statusId) => ( + + )) + ) : null; + + return ( + + {scrollableContent} + + ); } } diff --git a/app/javascript/mastodon/features/favourited_statuses/index.js b/app/javascript/mastodon/features/favourited_statuses/index.js index d9ad9bc1f..82b16b369 100644 --- a/app/javascript/mastodon/features/favourited_statuses/index.js +++ b/app/javascript/mastodon/features/favourited_statuses/index.js @@ -16,6 +16,7 @@ const messages = defineMessages({ const mapStateToProps = state => ({ statusIds: state.getIn(['status_lists', 'favourites', 'items']), + hasMore: !!state.getIn(['status_lists', 'favourites', 'next']), }); @connect(mapStateToProps) @@ -28,6 +29,7 @@ export default class Favourites extends ImmutablePureComponent { intl: PropTypes.object.isRequired, columnId: PropTypes.string, multiColumn: PropTypes.bool, + hasMore: PropTypes.bool, }; componentWillMount () { @@ -62,7 +64,7 @@ export default class Favourites extends ImmutablePureComponent { } render () { - const { intl, statusIds, columnId, multiColumn } = this.props; + const { intl, statusIds, columnId, multiColumn, hasMore } = this.props; const pinned = !!columnId; return ( @@ -81,6 +83,7 @@ export default class Favourites extends ImmutablePureComponent { trackScroll={!pinned} statusIds={statusIds} scrollKey={`favourited_statuses-${columnId}`} + hasMore={hasMore} onScrollToBottom={this.handleScrollToBottom} /> diff --git a/app/javascript/mastodon/features/notifications/components/notification.js b/app/javascript/mastodon/features/notifications/components/notification.js index 2992185fd..a608a5223 100644 --- a/app/javascript/mastodon/features/notifications/components/notification.js +++ b/app/javascript/mastodon/features/notifications/components/notification.js @@ -1,4 +1,5 @@ import React from 'react'; +import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; import StatusContainer from '../../../containers/status_container'; import AccountContainer from '../../../containers/account_container'; @@ -10,6 +11,7 @@ export default class Notification extends ImmutablePureComponent { static propTypes = { notification: ImmutablePropTypes.map.isRequired, + hidden: PropTypes.bool, }; renderFollow (account, link) { @@ -23,13 +25,13 @@ export default class Notification extends ImmutablePureComponent { - +