diff options
author | Thibaut Girka <thib@sitedethib.com> | 2018-08-10 15:39:06 +0200 |
---|---|---|
committer | Thibaut Girka <thib@sitedethib.com> | 2018-08-10 16:22:31 +0200 |
commit | e5a603206dfa76e2cd1b8a7fbb940fbd844b436c (patch) | |
tree | c3271f86b91ce2d13aea4f111dad6da4d07dcc04 /app | |
parent | 90b492143dae5cbaf884b12fbfd823c9f65d392f (diff) | |
parent | 5b8603879f06573d51c5c33edda6f410d1af7a02 (diff) |
Merge branch 'master' into glitch-soc/merge-upstream
Conflicts: app/controllers/accounts_controller.rb app/javascript/mastodon/locales/pl.json app/views/about/more.html.haml Conflicts in `accounts_controller.rb` resolved by taking upstream's version + our `use_pack`. Conflicts in `pl.json` resolved by taking upstream's changes. Conflicts in `aboute/more.html.haml` resolved by taking upstream's changes.
Diffstat (limited to 'app')
30 files changed, 778 insertions, 274 deletions
diff --git a/app/controllers/about_controller.rb b/app/controllers/about_controller.rb index a620d7c5f..ce1e8293c 100644 --- a/app/controllers/about_controller.rb +++ b/app/controllers/about_controller.rb @@ -10,9 +10,13 @@ class AboutController < ApplicationController @initial_state_json = serializable_resource.to_json end - def more; end + def more + render layout: 'public' + end - def terms; end + def terms + render layout: 'public' + end private diff --git a/app/controllers/accounts_controller.rb b/app/controllers/accounts_controller.rb index 35ee5cca9..3d20f0e88 100644 --- a/app/controllers/accounts_controller.rb +++ b/app/controllers/accounts_controller.rb @@ -11,8 +11,9 @@ class AccountsController < ApplicationController respond_to do |format| format.html do use_pack 'public' - @body_classes = 'with-modals' - @pinned_statuses = [] + @body_classes = 'with-modals' + @pinned_statuses = [] + @endorsed_accounts = @account.endorsed_accounts.to_a.sample(4) if current_account && @account.blocking?(current_account) @statuses = [] diff --git a/app/controllers/api/v1/accounts/pins_controller.rb b/app/controllers/api/v1/accounts/pins_controller.rb new file mode 100644 index 000000000..0a0239c42 --- /dev/null +++ b/app/controllers/api/v1/accounts/pins_controller.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +class Api::V1::Accounts::PinsController < Api::BaseController + include Authorization + + before_action -> { doorkeeper_authorize! :write, :'write:accounts' } + before_action :require_user! + before_action :set_account + + respond_to :json + + def create + AccountPin.create!(account: current_account, target_account: @account) + render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships_presenter + end + + def destroy + pin = AccountPin.find_by(account: current_account, target_account: @account) + pin&.destroy! + render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships_presenter + end + + private + + def set_account + @account = Account.find(params[:account_id]) + end + + def relationships_presenter + AccountRelationshipsPresenter.new([@account.id], current_user.account_id) + end +end diff --git a/app/helpers/home_helper.rb b/app/helpers/home_helper.rb index d3c6b13a6..8449f6c8a 100644 --- a/app/helpers/home_helper.rb +++ b/app/helpers/home_helper.rb @@ -6,4 +6,36 @@ module HomeHelper locale: I18n.locale, } end + + def account_link_to(account, button = '') + content_tag(:div, class: 'account') do + content_tag(:div, class: 'account__wrapper') do + section = if account.nil? + content_tag(:div, class: 'account__display-name') do + content_tag(:div, class: 'account__avatar-wrapper') do + content_tag(:div, '', class: 'account__avatar', style: "background-image: url(#{full_asset_url('avatars/original/missing.png', skip_pipeline: true)})") + end + + content_tag(:span, class: 'display-name') do + content_tag(:strong, t('about.contact_missing')) + + content_tag(:span, t('about.contact_unavailable'), class: 'display-name__account') + end + end + else + link_to(TagManager.instance.url_for(account), class: 'account__display-name') do + content_tag(:div, class: 'account__avatar-wrapper') do + content_tag(:div, '', class: 'account__avatar', style: "background-image: url(#{account.avatar.url})") + end + + content_tag(:span, class: 'display-name') do + content_tag(:bdi) do + content_tag(:strong, display_name(account, custom_emojify: true), class: 'display-name__html emojify') + end + + content_tag(:span, "@#{account.acct}", class: 'display-name__account') + end + end + end + + section + button + end + end + end end diff --git a/app/javascript/mastodon/actions/accounts.js b/app/javascript/mastodon/actions/accounts.js index c9e4afcfc..cbae62a0f 100644 --- a/app/javascript/mastodon/actions/accounts.js +++ b/app/javascript/mastodon/actions/accounts.js @@ -30,6 +30,14 @@ export const ACCOUNT_UNMUTE_REQUEST = 'ACCOUNT_UNMUTE_REQUEST'; export const ACCOUNT_UNMUTE_SUCCESS = 'ACCOUNT_UNMUTE_SUCCESS'; export const ACCOUNT_UNMUTE_FAIL = 'ACCOUNT_UNMUTE_FAIL'; +export const ACCOUNT_PIN_REQUEST = 'ACCOUNT_PIN_REQUEST'; +export const ACCOUNT_PIN_SUCCESS = 'ACCOUNT_PIN_SUCCESS'; +export const ACCOUNT_PIN_FAIL = 'ACCOUNT_PIN_FAIL'; + +export const ACCOUNT_UNPIN_REQUEST = 'ACCOUNT_UNPIN_REQUEST'; +export const ACCOUNT_UNPIN_SUCCESS = 'ACCOUNT_UNPIN_SUCCESS'; +export const ACCOUNT_UNPIN_FAIL = 'ACCOUNT_UNPIN_FAIL'; + export const FOLLOWERS_FETCH_REQUEST = 'FOLLOWERS_FETCH_REQUEST'; export const FOLLOWERS_FETCH_SUCCESS = 'FOLLOWERS_FETCH_SUCCESS'; export const FOLLOWERS_FETCH_FAIL = 'FOLLOWERS_FETCH_FAIL'; @@ -694,3 +702,69 @@ export function rejectFollowRequestFail(id, error) { error, }; }; + +export function pinAccount(id) { + return (dispatch, getState) => { + dispatch(pinAccountRequest(id)); + + api(getState).post(`/api/v1/accounts/${id}/pin`).then(response => { + dispatch(pinAccountSuccess(response.data)); + }).catch(error => { + dispatch(pinAccountFail(error)); + }); + }; +}; + +export function unpinAccount(id) { + return (dispatch, getState) => { + dispatch(unpinAccountRequest(id)); + + api(getState).post(`/api/v1/accounts/${id}/unpin`).then(response => { + dispatch(unpinAccountSuccess(response.data)); + }).catch(error => { + dispatch(unpinAccountFail(error)); + }); + }; +}; + +export function pinAccountRequest(id) { + return { + type: ACCOUNT_PIN_REQUEST, + id, + }; +}; + +export function pinAccountSuccess(relationship) { + return { + type: ACCOUNT_PIN_SUCCESS, + relationship, + }; +}; + +export function pinAccountFail(error) { + return { + type: ACCOUNT_PIN_FAIL, + error, + }; +}; + +export function unpinAccountRequest(id) { + return { + type: ACCOUNT_UNPIN_REQUEST, + id, + }; +}; + +export function unpinAccountSuccess(relationship) { + return { + type: ACCOUNT_UNPIN_SUCCESS, + relationship, + }; +}; + +export function unpinAccountFail(error) { + return { + type: ACCOUNT_UNPIN_FAIL, + error, + }; +}; diff --git a/app/javascript/mastodon/containers/mastodon.js b/app/javascript/mastodon/containers/mastodon.js index b29898d3b..b2b0265aa 100644 --- a/app/javascript/mastodon/containers/mastodon.js +++ b/app/javascript/mastodon/containers/mastodon.js @@ -38,13 +38,6 @@ export default class Mastodon extends React.PureComponent { window.setTimeout(() => Notification.requestPermission(), 60 * 1000); } - // Protocol handler - // Ask after 5 minutes - if (typeof navigator.registerProtocolHandler !== 'undefined') { - const handlerUrl = window.location.protocol + '//' + window.location.host + '/intent?uri=%s'; - window.setTimeout(() => navigator.registerProtocolHandler('web+mastodon', handlerUrl, 'Mastodon'), 5 * 60 * 1000); - } - store.dispatch(showOnboardingOnce()); } diff --git a/app/javascript/mastodon/features/account/components/action_bar.js b/app/javascript/mastodon/features/account/components/action_bar.js index e3f2d0f55..43b4811e1 100644 --- a/app/javascript/mastodon/features/account/components/action_bar.js +++ b/app/javascript/mastodon/features/account/components/action_bar.js @@ -32,6 +32,8 @@ const messages = defineMessages({ blocks: { id: 'navigation_bar.blocks', defaultMessage: 'Blocked users' }, domain_blocks: { id: 'navigation_bar.domain_blocks', defaultMessage: 'Hidden domains' }, mutes: { id: 'navigation_bar.mutes', defaultMessage: 'Muted users' }, + endorse: { id: 'account.endorse', defaultMessage: 'Feature on profile' }, + unendorse: { id: 'account.unendorse', defaultMessage: 'Don\'t feature on profile' }, }); @injectIntl @@ -48,6 +50,7 @@ export default class ActionBar extends React.PureComponent { onMute: PropTypes.func.isRequired, onBlockDomain: PropTypes.func.isRequired, onUnblockDomain: PropTypes.func.isRequired, + onEndorseToggle: PropTypes.func.isRequired, intl: PropTypes.object.isRequired, }; @@ -93,6 +96,9 @@ export default class ActionBar extends React.PureComponent { } else { menu.push({ text: intl.formatMessage(messages.showReblogs, { name: account.get('username') }), action: this.props.onReblogToggle }); } + + menu.push({ text: intl.formatMessage(account.getIn(['relationship', 'endorsed']) ? messages.unendorse : messages.endorse), action: this.props.onEndorseToggle }); + menu.push(null); } if (account.getIn(['relationship', 'muting'])) { diff --git a/app/javascript/mastodon/features/account_timeline/components/header.js b/app/javascript/mastodon/features/account_timeline/components/header.js index 1ae5126e6..ab29e4bdf 100644 --- a/app/javascript/mastodon/features/account_timeline/components/header.js +++ b/app/javascript/mastodon/features/account_timeline/components/header.js @@ -22,6 +22,7 @@ export default class Header extends ImmutablePureComponent { onMute: PropTypes.func.isRequired, onBlockDomain: PropTypes.func.isRequired, onUnblockDomain: PropTypes.func.isRequired, + onEndorseToggle: PropTypes.func.isRequired, hideTabs: PropTypes.bool, }; @@ -73,6 +74,10 @@ export default class Header extends ImmutablePureComponent { this.props.onUnblockDomain(domain); } + handleEndorseToggle = () => { + this.props.onEndorseToggle(this.props.account); + } + render () { const { account, hideTabs } = this.props; @@ -100,6 +105,7 @@ export default class Header extends ImmutablePureComponent { onMute={this.handleMute} onBlockDomain={this.handleBlockDomain} onUnblockDomain={this.handleUnblockDomain} + onEndorseToggle={this.handleEndorseToggle} /> {!hideTabs && ( diff --git a/app/javascript/mastodon/features/account_timeline/containers/header_container.js b/app/javascript/mastodon/features/account_timeline/containers/header_container.js index 7681430b7..02803893d 100644 --- a/app/javascript/mastodon/features/account_timeline/containers/header_container.js +++ b/app/javascript/mastodon/features/account_timeline/containers/header_container.js @@ -8,6 +8,8 @@ import { blockAccount, unblockAccount, unmuteAccount, + pinAccount, + unpinAccount, } from '../../../actions/accounts'; import { mentionCompose, @@ -82,6 +84,14 @@ const mapDispatchToProps = (dispatch, { intl }) => ({ } }, + onEndorseToggle (account) { + if (account.getIn(['relationship', 'endorsed'])) { + dispatch(unpinAccount(account.get('id'))); + } else { + dispatch(pinAccount(account.get('id'))); + } + }, + onReport (account) { dispatch(initReport(account)); }, diff --git a/app/javascript/mastodon/locales/pl.json b/app/javascript/mastodon/locales/pl.json index 67711d1bd..3f3a7dd41 100644 --- a/app/javascript/mastodon/locales/pl.json +++ b/app/javascript/mastodon/locales/pl.json @@ -2,7 +2,7 @@ "account.badges.bot": "Bot", "account.block": "Blokuj @{name}", "account.block_domain": "Blokuj wszystko z {domain}", - "account.blocked": "Zablokowany", + "account.blocked": "Zablokowany(-a)", "account.direct": "Wyślij wiadomość bezpośrednią do @{name}", "account.disclaimer_full": "Poniższe informacje mogą nie odwzorowywać bezbłędnie profilu użytkownika.", "account.domain_blocked": "Ukryto domenę", @@ -14,7 +14,7 @@ "account.hide_reblogs": "Ukryj podbicia od @{name}", "account.media": "Zawartość multimedialna", "account.mention": "Wspomnij o @{name}", - "account.moved_to": "{name} przeniósł się do:", + "account.moved_to": "{name} przeniósł(-osła) się do:", "account.mute": "Wycisz @{name}", "account.mute_notifications": "Wycisz powiadomienia o @{name}", "account.muted": "Wyciszony", @@ -109,8 +109,8 @@ "emoji_button.symbols": "Symbole", "emoji_button.travel": "Podróże i miejsca", "empty_column.community": "Lokalna oś czasu jest pusta. Napisz coś publicznie, aby zagaić!", - "empty_column.direct": "Nie masz żadnych wiadomości bezpośrednich. Jeżeli wyślesz lub otrzymasz jakąś, będzie tu widoczna.", - "empty_column.hashtag": "Nie ma wpisów oznaczonych tym hashtagiem. Możesz napisać pierwszy!", + "empty_column.direct": "Nie masz żadnych wiadomości bezpośrednich. Kiedy dostaniesz lub wyślesz jakąś, pojawi się ona tutaj.", + "empty_column.hashtag": "Nie ma wpisów oznaczonych tym hashtagiem. Możesz napisać pierwszy(-a)!", "empty_column.home": "Nie śledzisz nikogo. Odwiedź globalną oś czasu lub użyj wyszukiwarki, aby znaleźć interesujące Cię profile.", "empty_column.home.public_timeline": "globalna oś czasu", "empty_column.list": "Nie ma nic na tej liście. Kiedy członkowie listy dodadzą nowe wpisy, pojawia się one tutaj.", @@ -139,7 +139,7 @@ "keyboard_shortcuts.favourite": "aby dodać do ulubionych", "keyboard_shortcuts.heading": "Skróty klawiszowe", "keyboard_shortcuts.hotkey": "Klawisz", - "keyboard_shortcuts.legend": "aby wyświetlić tą legendę", + "keyboard_shortcuts.legend": "aby wyświetlić tę legendę", "keyboard_shortcuts.mention": "aby wspomnieć o autorze", "keyboard_shortcuts.profile": "aby przejść do profilu autora wpisu", "keyboard_shortcuts.reply": "aby odpowiedzieć", @@ -184,10 +184,10 @@ "navigation_bar.preferences": "Preferencje", "navigation_bar.public_timeline": "Globalna oś czasu", "navigation_bar.security": "Bezpieczeństwo", - "notification.favourite": "{name} dodał Twój wpis do ulubionych", - "notification.follow": "{name} zaczął Cię śledzić", - "notification.mention": "{name} wspomniał o tobie", - "notification.reblog": "{name} podbił Twój wpis", + "notification.favourite": "{name} dodał(a) Twój wpis do ulubionych", + "notification.follow": "{name} zaczął(-ęła) Cię śledzić", + "notification.mention": "{name} wspomniał(a) o tobie", + "notification.reblog": "{name} podbił(a) Twój wpis", "notifications.clear": "Wyczyść powiadomienia", "notifications.clear_confirmation": "Czy na pewno chcesz bezpowrotnie usunąć wszystkie powiadomienia?", "notifications.column_settings.alert": "Powiadomienia na pulpicie", @@ -246,7 +246,7 @@ "report.target": "Zgłaszanie {target}", "search.placeholder": "Szukaj", "search_popout.search_format": "Zaawansowane wyszukiwanie", - "search_popout.tips.full_text": "Pozwala na wyszukiwanie wpisów które napisałeś, dodałeś do ulubionych, podbiłeś w których o Tobie wspomniano, oraz pasujące nazwy użytkowników, pełne nazwy i hashtagi.", + "search_popout.tips.full_text": "Pozwala na wyszukiwanie wpisów które napisałeś(-aś), dodałeś(-aś) do ulubionych lub podbiłeś(-aś), w których o Tobie wspomniano, oraz pasujące nazwy użytkowników, pełne nazwy i hashtagi.", "search_popout.tips.hashtag": "hashtag", "search_popout.tips.status": "wpis", "search_popout.tips.text": "Proste wyszukiwanie pasujących pseudonimów, nazw użytkowników i hashtagów", @@ -263,7 +263,7 @@ "status.direct": "Wyślij wiadomość bezpośrednią do @{name}", "status.embed": "Osadź", "status.favourite": "Dodaj do ulubionych", - "status.filtered": "Filtrowany", + "status.filtered": "Filtrowany(-a)", "status.load_more": "Załaduj więcej", "status.media_hidden": "Zawartość multimedialna ukryta", "status.mention": "Wspomnij o @{name}", @@ -275,7 +275,7 @@ "status.pinned": "Przypięty wpis", "status.reblog": "Podbij", "status.reblog_private": "Podbij dla odbiorców oryginalnego wpisu", - "status.reblogged_by": "{name} podbił", + "status.reblogged_by": "{name} podbił(a)", "status.redraft": "Usuń i przeredaguj", "status.reply": "Odpowiedz", "status.replyAll": "Odpowiedz na wątek", diff --git a/app/javascript/mastodon/locales/ru.json b/app/javascript/mastodon/locales/ru.json index ad2dcda12..2706d5b71 100644 --- a/app/javascript/mastodon/locales/ru.json +++ b/app/javascript/mastodon/locales/ru.json @@ -12,7 +12,7 @@ "account.follows": "Подписки", "account.follows_you": "Подписан(а) на Вас", "account.hide_reblogs": "Скрыть продвижения от @{name}", - "account.media": "Медиаконтент", + "account.media": "Медиа", "account.mention": "Упомянуть", "account.moved_to": "Ищите {name} здесь:", "account.mute": "Заглушить", @@ -59,9 +59,9 @@ "column_header.show_settings": "Показать настройки", "column_header.unpin": "Открепить", "column_subheading.settings": "Настройки", - "community.column_settings.media_only": "Media Only", + "community.column_settings.media_only": "Только медиа", "compose_form.direct_message_warning": "Этот статус будет виден только упомянутым пользователям.", - "compose_form.direct_message_warning_learn_more": "Learn more", + "compose_form.direct_message_warning_learn_more": "Узнать больше", "compose_form.hashtag_warning": "Этот пост не будет показывается в поиске по хэштегу, т.к. он непубличный. Только публичные посты можно найти в поиске по хэштегу.", "compose_form.lock_disclaimer": "Ваш аккаунт не {locked}. Любой человек может подписаться на Вас и просматривать посты для подписчиков.", "compose_form.lock_disclaimer.lock": "закрыт", @@ -84,8 +84,8 @@ "confirmations.domain_block.message": "Вы на самом деле уверены, что хотите блокировать весь {domain}? В большинстве случаев нескольких отдельных блокировок или глушений достаточно.", "confirmations.mute.confirm": "Заглушить", "confirmations.mute.message": "Вы уверены, что хотите заглушить {name}?", - "confirmations.redraft.confirm": "Delete & redraft", - "confirmations.redraft.message": "Are you sure you want to delete this status and re-draft it? You will lose all replies, boosts and favourites to it.", + "confirmations.redraft.confirm": "Удалить и исправить", + "confirmations.redraft.message": "Вы уверены, что хотите удалить этот статус и превратить в черновик? Вы потеряете все ответы, продвижения и отметки 'нравится' к нему.", "confirmations.unfollow.confirm": "Отписаться", "confirmations.unfollow.message": "Вы уверены, что хотите отписаться от {name}?", "embed.instructions": "Встройте этот статус на Вашем сайте, скопировав код внизу.", @@ -114,14 +114,14 @@ "empty_column.public": "Здесь ничего нет! Опубликуйте что-нибудь или подпишитесь на пользователей с других узлов, чтобы заполнить ленту.", "follow_request.authorize": "Авторизовать", "follow_request.reject": "Отказать", - "getting_started.developers": "Developers", - "getting_started.documentation": "Documentation", - "getting_started.find_friends": "Find friends from Twitter", + "getting_started.developers": "Для разработчиков", + "getting_started.documentation": "Документация", + "getting_started.find_friends": "Найти друзей из Twitter", "getting_started.heading": "Добро пожаловать", - "getting_started.invite": "Invite people", - "getting_started.open_source_notice": "Mastodon - программа с открытым исходным кодом. Вы можете помочь проекту или сообщить о проблемах на GitHub по адресу {github}.", - "getting_started.security": "Security", - "getting_started.terms": "Terms of service", + "getting_started.invite": "Пригласить людей", + "getting_started.open_source_notice": "Mastodon - сервис с открытым исходным кодом. Вы можете помочь проекту или сообщить о проблемах на GitHub по адресу {github}.", + "getting_started.security": "Безопасность", + "getting_started.terms": "Условия использования", "home.column_settings.basic": "Основные", "home.column_settings.show_reblogs": "Показывать продвижения", "home.column_settings.show_replies": "Показывать ответы", @@ -137,7 +137,7 @@ "keyboard_shortcuts.hotkey": "Гор. клавиша", "keyboard_shortcuts.legend": "показать это окно", "keyboard_shortcuts.mention": "упомянуть автора поста", - "keyboard_shortcuts.profile": "to open author's profile", + "keyboard_shortcuts.profile": "перейти к профилю автора", "keyboard_shortcuts.reply": "ответить", "keyboard_shortcuts.search": "перейти к поиску", "keyboard_shortcuts.toggle_hidden": "показать/скрыть текст за предупреждением", @@ -163,22 +163,22 @@ "navigation_bar.blocks": "Список блокировки", "navigation_bar.community_timeline": "Локальная лента", "navigation_bar.direct": "Личные сообщения", - "navigation_bar.discover": "Discover", + "navigation_bar.discover": "Изучайте", "navigation_bar.domain_blocks": "Скрытые домены", "navigation_bar.edit_profile": "Изменить профиль", "navigation_bar.favourites": "Понравившееся", - "navigation_bar.filters": "Muted words", + "navigation_bar.filters": "Заглушенные слова", "navigation_bar.follow_requests": "Запросы на подписку", "navigation_bar.info": "Об узле", "navigation_bar.keyboard_shortcuts": "Сочетания клавиш", "navigation_bar.lists": "Списки", "navigation_bar.logout": "Выйти", "navigation_bar.mutes": "Список глушения", - "navigation_bar.personal": "Personal", + "navigation_bar.personal": "Личное", "navigation_bar.pins": "Закреплённые посты", "navigation_bar.preferences": "Опции", "navigation_bar.public_timeline": "Глобальная лента", - "navigation_bar.security": "Security", + "navigation_bar.security": "Безопасность", "notification.favourite": "{name} понравился Ваш статус", "notification.follow": "{name} подписался(-лась) на Вас", "notification.mention": "{name} упомянул(а) Вас", @@ -194,7 +194,7 @@ "notifications.column_settings.reblog": "Продвижения:", "notifications.column_settings.show": "Показывать в колонке", "notifications.column_settings.sound": "Проигрывать звук", - "notifications.group": "{count} notifications", + "notifications.group": "{count} уведомл.", "onboarding.done": "Готово", "onboarding.next": "Далее", "onboarding.page_five.public_timelines": "Локальная лента показывает публичные посты всех пользователей {domain}. Глобальная лента показывает публичные посты всех людей, на которых подписаны пользователи {domain}. Это - публичные ленты, отличный способ найти новые знакомства.", @@ -258,9 +258,9 @@ "status.direct": "Написать @{name}", "status.embed": "Встроить", "status.favourite": "Нравится", - "status.filtered": "Filtered", + "status.filtered": "Отфильтровано", "status.load_more": "Показать еще", - "status.media_hidden": "Медиаконтент скрыт", + "status.media_hidden": "Медиа скрыто", "status.mention": "Упомянуть @{name}", "status.more": "Больше", "status.mute": "Заглушить @{name}", @@ -271,7 +271,7 @@ "status.reblog": "Продвинуть", "status.reblog_private": "Продвинуть для своей аудитории", "status.reblogged_by": "{name} продвинул(а)", - "status.redraft": "Delete & re-draft", + "status.redraft": "Удалить и повторить", "status.reply": "Ответить", "status.replyAll": "Ответить на тред", "status.report": "Пожаловаться", @@ -289,7 +289,7 @@ "tabs_bar.local_timeline": "Локальная", "tabs_bar.notifications": "Уведомления", "tabs_bar.search": "Поиск", - "trends.count_by_accounts": "{count} {rawCount, plural, one {person} other {people}} talking", + "trends.count_by_accounts": "Популярно у {count} {rawCount, plural, one {человека} few {человек} many {человек} other {человек}}", "ui.beforeunload": "Ваш черновик будет утерян, если вы покинете Mastodon.", "upload_area.title": "Перетащите сюда, чтобы загрузить", "upload_button.label": "Добавить медиаконтент", diff --git a/app/javascript/mastodon/reducers/relationships.js b/app/javascript/mastodon/reducers/relationships.js index d1caabc1c..f46049297 100644 --- a/app/javascript/mastodon/reducers/relationships.js +++ b/app/javascript/mastodon/reducers/relationships.js @@ -5,6 +5,8 @@ import { ACCOUNT_UNBLOCK_SUCCESS, ACCOUNT_MUTE_SUCCESS, ACCOUNT_UNMUTE_SUCCESS, + ACCOUNT_PIN_SUCCESS, + ACCOUNT_UNPIN_SUCCESS, RELATIONSHIPS_FETCH_SUCCESS, } from '../actions/accounts'; import { @@ -41,6 +43,8 @@ export default function relationships(state = initialState, action) { case ACCOUNT_UNBLOCK_SUCCESS: case ACCOUNT_MUTE_SUCCESS: case ACCOUNT_UNMUTE_SUCCESS: + case ACCOUNT_PIN_SUCCESS: + case ACCOUNT_UNPIN_SUCCESS: return normalizeRelationship(state, action.relationship); case RELATIONSHIPS_FETCH_SUCCESS: return normalizeRelationships(state, action.relationships); diff --git a/app/javascript/packs/public.js b/app/javascript/packs/public.js index 3a1ca1a7b..7d632776e 100644 --- a/app/javascript/packs/public.js +++ b/app/javascript/packs/public.js @@ -15,6 +15,7 @@ function main() { const React = require('react'); const ReactDOM = require('react-dom'); const Rellax = require('rellax'); + const createHistory = require('history').createBrowserHistory; ready(() => { const locale = document.documentElement.lang; @@ -70,6 +71,14 @@ function main() { } new Rellax('.parallax', { speed: -1 }); + + const history = createHistory(); + const detailedStatuses = document.querySelectorAll('.public-layout .detailed-status'); + const location = history.location; + if (detailedStatuses.length === 1 && (!location.state || !location.state.scrolledToDetailedStatus)) { + detailedStatuses[0].scrollIntoView(); + history.replace(location.pathname, { ...location.state, scrolledToDetailedStatus: true }); + } }); } diff --git a/app/javascript/styles/mastodon/about.scss b/app/javascript/styles/mastodon/about.scss index b9544bb33..228dd96f0 100644 --- a/app/javascript/styles/mastodon/about.scss +++ b/app/javascript/styles/mastodon/about.scss @@ -15,6 +15,276 @@ $small-breakpoint: 960px; } } +.rich-formatting { + font-family: 'mastodon-font-sans-serif', sans-serif; + font-size: 16px; + font-weight: 400; + font-size: 16px; + line-height: 30px; + color: $darker-text-color; + padding-right: 10px; + + a { + color: $highlight-text-color; + text-decoration: underline; + } + + 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: $darker-text-color; + + a { + color: $highlight-text-color; + text-decoration: underline; + } + + &:last-child { + margin-bottom: 0; + } + } + + em { + display: inline; + margin: 0; + padding: 0; + font-weight: 700; + background: transparent; + font-family: inherit; + font-size: inherit; + line-height: inherit; + color: lighten($darker-text-color, 10%); + } + + h1 { + font-family: 'mastodon-font-display', sans-serif; + font-size: 26px; + line-height: 30px; + font-weight: 500; + margin-bottom: 20px; + color: $secondary-text-color; + + small { + font-family: 'mastodon-font-sans-serif', sans-serif; + display: block; + font-size: 18px; + font-weight: 400; + color: lighten($darker-text-color, 10%); + } + } + + h2 { + font-family: 'mastodon-font-display', sans-serif; + font-size: 22px; + line-height: 26px; + font-weight: 500; + margin-bottom: 20px; + color: $secondary-text-color; + } + + h3 { + font-family: 'mastodon-font-display', sans-serif; + font-size: 18px; + line-height: 24px; + font-weight: 500; + margin-bottom: 20px; + color: $secondary-text-color; + } + + h4 { + font-family: 'mastodon-font-display', sans-serif; + font-size: 16px; + line-height: 24px; + font-weight: 500; + margin-bottom: 20px; + color: $secondary-text-color; + } + + h5 { + font-family: 'mastodon-font-display', sans-serif; + font-size: 14px; + line-height: 24px; + font-weight: 500; + margin-bottom: 20px; + color: $secondary-text-color; + } + + h6 { + font-family: 'mastodon-font-display', sans-serif; + font-size: 12px; + line-height: 24px; + font-weight: 500; + margin-bottom: 20px; + color: $secondary-text-color; + } + + ul, + ol { + margin-left: 20px; + + &[type='a'] { + list-style-type: lower-alpha; + } + + &[type='i'] { + list-style-type: lower-roman; + } + } + + ul { + list-style: disc; + } + + ol { + list-style: decimal; + } + + li > ol, + li > ul { + margin-top: 6px; + } + + hr { + width: 100%; + height: 0; + border: 0; + border-bottom: 1px solid rgba($ui-base-lighter-color, .6); + margin: 20px 0; + + &.spacer { + height: 1px; + border: 0; + } + } +} + +.information-board { + background: darken($ui-base-color, 4%); + padding: 20px 0; + + .container-alt { + position: relative; + padding-right: 280px + 15px; + } + + &__sections { + display: flex; + justify-content: space-between; + flex-wrap: wrap; + } + + &__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; + + span, + strong { + display: block; + } + + span { + &:last-child { + color: $secondary-text-color; + } + } + + strong { + font-weight: 500; + font-size: 32px; + line-height: 48px; + } + + @media screen and (max-width: $column-breakpoint) { + text-align: center; + } + } + + .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: $darker-text-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($darker-text-color, 10%); + } + + a { + text-decoration: none; + } + } + } + + .owner { + text-align: center; + + .avatar { + width: 80px; + height: 80px; + 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: $darker-text-color; + } + } + } +} + .landing-page { .grid { display: grid; @@ -486,128 +756,6 @@ $small-breakpoint: 960px; } } - .information-board { - background: darken($ui-base-color, 4%); - padding: 20px 0; - - .container-alt { - position: relative; - padding-right: 280px + 15px; - } - - &__sections { - display: flex; - justify-content: space-between; - flex-wrap: wrap; - } - - &__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; - - span, - strong { - display: block; - } - - span { - &:last-child { - color: $secondary-text-color; - } - } - - strong { - font-weight: 500; - font-size: 32px; - line-height: 48px; - } - - @media screen and (max-width: $column-breakpoint) { - text-align: center; - } - } - - .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: $darker-text-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($darker-text-color, 10%); - } - - a { - text-decoration: none; - } - } - } - - .owner { - text-align: center; - - .avatar { - width: 80px; - height: 80px; - 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: $darker-text-color; - } - } - } - } - &.alternative { padding: 10px 0; @@ -642,8 +790,10 @@ $small-breakpoint: 960px; border-radius: 4px; padding: 25px 40px; overflow: hidden; + box-sizing: border-box; .row { + width: 100%; display: flex; flex-direction: row-reverse; flex-wrap: wrap; @@ -660,11 +810,20 @@ $small-breakpoint: 960px; flex: 1 0 auto; padding: 0 10px; } + + @media screen and (max-width: $no-gap-breakpoint) { + width: 100%; + justify-content: space-between; + } } .row__mascot { flex: 1; margin: 10px -50px 0 0; + + @media screen and (max-width: $no-gap-breakpoint) { + display: none; + } } } @@ -983,21 +1142,6 @@ $small-breakpoint: 960px; } } - .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; - color: $darker-text-color; - - a { - color: $highlight-text-color; - text-decoration: underline; - } - } - .footer-links { padding-bottom: 50px; text-align: right; diff --git a/app/javascript/styles/mastodon/containers.scss b/app/javascript/styles/mastodon/containers.scss index 7b339277f..8ecedd2cb 100644 --- a/app/javascript/styles/mastodon/containers.scss +++ b/app/javascript/styles/mastodon/containers.scss @@ -115,6 +115,83 @@ } } +.grid-3 { + display: grid; + grid-gap: 10px; + grid-template-columns: 3fr 1fr; + grid-auto-columns: 25%; + grid-auto-rows: max-content; + + .column-0 { + grid-column: 1/3; + grid-row: 1; + } + + .column-1 { + grid-column: 1; + grid-row: 2; + } + + .column-2 { + grid-column: 2; + grid-row: 2; + } + + .column-3 { + grid-column: 1/3; + grid-row: 3; + } + + .landing-page__call-to-action { + min-height: 100%; + } + + @media screen and (max-width: 738px) { + grid-template-columns: minmax(0, 50%) minmax(0, 50%); + + .landing-page__call-to-action { + padding: 20px; + display: flex; + align-items: center; + justify-content: center; + } + + .row__information-board { + width: 100%; + justify-content: center; + align-items: center; + } + + .row__mascot { + display: none; + } + } + + @media screen and (max-width: $no-gap-breakpoint) { + grid-gap: 0; + grid-template-columns: minmax(0, 100%); + + .column-0 { + grid-column: 1; + } + + .column-1 { + grid-column: 1; + grid-row: 3; + } + + .column-2 { + grid-column: 1; + grid-row: 2; + } + + .column-3 { + grid-column: 1; + grid-row: 4; + } + } +} + .public-layout { @media screen and (max-width: $no-gap-breakpoint) { padding-top: 48px; @@ -300,6 +377,19 @@ } } + &--no-bar { + margin-bottom: 0; + + .public-account-header__image, + .public-account-header__image img { + border-radius: 4px; + + @media screen and (max-width: $no-gap-breakpoint) { + border-radius: 0; + } + } + } + @media screen and (max-width: $no-gap-breakpoint) { margin-bottom: 0; box-shadow: none; diff --git a/app/javascript/styles/mastodon/widgets.scss b/app/javascript/styles/mastodon/widgets.scss index d37a6f458..f843f0b42 100644 --- a/app/javascript/styles/mastodon/widgets.scss +++ b/app/javascript/styles/mastodon/widgets.scss @@ -71,6 +71,84 @@ } } +.endorsements-widget { + margin-bottom: 10px; + padding-bottom: 10px; + + h4 { + padding: 10px; + text-transform: uppercase; + font-weight: 700; + font-size: 13px; + color: $darker-text-color; + } + + .account { + padding: 10px 0; + + &:last-child { + border-bottom: 0; + } + + .account__display-name { + display: flex; + align-items: center; + } + + .account__avatar { + width: 44px; + height: 44px; + background-size: 44px 44px; + } + } +} + +.box-widget { + padding: 20px; + border-radius: 4px; + background: $ui-base-color; + box-shadow: 0 0 15px rgba($base-shadow-color, 0.2); +} + +.contact-widget, +.landing-page__information.contact-widget { + box-sizing: border-box; + padding: 20px; + min-height: 100%; + border-radius: 4px; + background: $ui-base-color; + box-shadow: 0 0 15px rgba($base-shadow-color, 0.2); +} + +.contact-widget { + font-size: 15px; + color: $darker-text-color; + line-height: 20px; + word-wrap: break-word; + font-weight: 400; + + strong { + font-weight: 500; + } + + p { + margin-bottom: 10px; + + &:last-child { + margin-bottom: 0; + } + } + + &__mail { + margin-top: 10px; + + a { + color: $primary-text-color; + text-decoration: none; + } + } +} + .moved-account-widget { padding: 15px; padding-bottom: 20px; @@ -152,7 +230,10 @@ } .moved-account-widget, -.memoriam-widget { +.memoriam-widget, +.box-widget, +.contact-widget, +.landing-page__information.contact-widget { @media screen and (max-width: $no-gap-breakpoint) { margin-bottom: 0; box-shadow: none; diff --git a/app/lib/ostatus/atom_serializer.rb b/app/lib/ostatus/atom_serializer.rb index 5c6ff4f9b..1a0a635b3 100644 --- a/app/lib/ostatus/atom_serializer.rb +++ b/app/lib/ostatus/atom_serializer.rb @@ -352,7 +352,7 @@ class OStatus::AtomSerializer append_element(entry, 'link', nil, rel: :alternate, type: 'application/activity+json', href: ActivityPub::TagManager.instance.uri_for(status)) if status.account.local? append_element(entry, 'summary', status.spoiler_text, 'xml:lang': status.language) if status.spoiler_text? - append_element(entry, 'content', Formatter.instance.format(status).to_str, type: 'html', 'xml:lang': status.language) + append_element(entry, 'content', Formatter.instance.format(status).to_str || '.', type: 'html', 'xml:lang': status.language) status.mentions.sort_by(&:id).each do |mentioned| append_element(entry, 'link', nil, rel: :mentioned, 'ostatus:object-type': OStatus::TagManager::TYPES[:person], href: OStatus::TagManager.instance.uri_for(mentioned.account)) diff --git a/app/models/account.rb b/app/models/account.rb index 041eda6f4..9daf063a5 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -92,6 +92,10 @@ class Account < ApplicationRecord has_many :status_pins, inverse_of: :account, dependent: :destroy has_many :pinned_statuses, -> { reorder('status_pins.created_at DESC') }, through: :status_pins, class_name: 'Status', source: :status + # Endorsements + has_many :account_pins, inverse_of: :account, dependent: :destroy + has_many :endorsed_accounts, through: :account_pins, class_name: 'Account', source: :target_account + # Media has_many :media_attachments, dependent: :destroy diff --git a/app/models/account_pin.rb b/app/models/account_pin.rb new file mode 100644 index 000000000..9a21c3405 --- /dev/null +++ b/app/models/account_pin.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true +# == Schema Information +# +# Table name: account_pins +# +# id :bigint(8) not null, primary key +# account_id :bigint(8) +# target_account_id :bigint(8) +# created_at :datetime not null +# updated_at :datetime not null +# + +class AccountPin < ApplicationRecord + include RelationshipCacheable + + belongs_to :account + belongs_to :target_account, class_name: 'Account' + + validate :validate_follow_relationship + + private + + def validate_follow_relationship + errors.add(:base, I18n.t('accounts.pin_errors.following')) unless account.following?(target_account) + end +end diff --git a/app/models/concerns/account_interactions.rb b/app/models/concerns/account_interactions.rb index cacee54e0..ff57a884b 100644 --- a/app/models/concerns/account_interactions.rb +++ b/app/models/concerns/account_interactions.rb @@ -40,6 +40,10 @@ module AccountInteractions end end + def endorsed_map(target_account_ids, account_id) + follow_mapping(AccountPin.where(account_id: account_id, target_account_id: target_account_ids), :target_account_id) + end + def domain_blocking_map(target_account_ids, account_id) accounts_map = Account.where(id: target_account_ids).select('id, domain').map { |a| [a.id, a.domain] }.to_h blocked_domains = domain_blocking_map_by_domain(accounts_map.values.compact, account_id) @@ -194,6 +198,10 @@ module AccountInteractions status_pins.where(status: status).exists? end + def endorsed?(account) + account_pins.where(target_account: account).exists? + end + def followers_for_local_distribution followers.local .joins(:user) diff --git a/app/models/follow.rb b/app/models/follow.rb index eaf8445f3..3fce14b9a 100644 --- a/app/models/follow.rb +++ b/app/models/follow.rb @@ -33,10 +33,15 @@ class Follow < ApplicationRecord end before_validation :set_uri, only: :create + after_destroy :remove_endorsements private def set_uri self.uri = ActivityPub::TagManager.instance.generate_uri_for(self) if uri.nil? end + + def remove_endorsements + AccountPin.where(target_account_id: target_account_id, account_id: account_id).delete_all + end end diff --git a/app/presenters/account_relationships_presenter.rb b/app/presenters/account_relationships_presenter.rb index b1e99b31b..e4aaa65f6 100644 --- a/app/presenters/account_relationships_presenter.rb +++ b/app/presenters/account_relationships_presenter.rb @@ -2,7 +2,8 @@ class AccountRelationshipsPresenter attr_reader :following, :followed_by, :blocking, - :muting, :requested, :domain_blocking + :muting, :requested, :domain_blocking, + :endorsed def initialize(account_ids, current_account_id, **options) @account_ids = account_ids.map { |a| a.is_a?(Account) ? a.id : a } @@ -14,6 +15,7 @@ class AccountRelationshipsPresenter @muting = cached[:muting].merge(Account.muting_map(@uncached_account_ids, @current_account_id)) @requested = cached[:requested].merge(Account.requested_map(@uncached_account_ids, @current_account_id)) @domain_blocking = cached[:domain_blocking].merge(Account.domain_blocking_map(@uncached_account_ids, @current_account_id)) + @endorsed = cached[:endorsed].merge(Account.endorsed_map(@uncached_account_ids, @current_account_id)) cache_uncached! @@ -23,6 +25,7 @@ class AccountRelationshipsPresenter @muting.merge!(options[:muting_map] || {}) @requested.merge!(options[:requested_map] || {}) @domain_blocking.merge!(options[:domain_blocking_map] || {}) + @endorsed.merge!(options[:endorsed_map] || {}) end private @@ -37,6 +40,7 @@ class AccountRelationshipsPresenter muting: {}, requested: {}, domain_blocking: {}, + endorsed: {}, } @uncached_account_ids = [] @@ -63,6 +67,7 @@ class AccountRelationshipsPresenter muting: { account_id => muting[account_id] }, requested: { account_id => requested[account_id] }, domain_blocking: { account_id => domain_blocking[account_id] }, + endorsed: { account_id => endorsed[account_id] }, } Rails.cache.write("relationship:#{@current_account_id}:#{account_id}", maps_for_account, expires_in: 1.day) diff --git a/app/serializers/rest/relationship_serializer.rb b/app/serializers/rest/relationship_serializer.rb index 45bfd4d6e..c6c722a54 100644 --- a/app/serializers/rest/relationship_serializer.rb +++ b/app/serializers/rest/relationship_serializer.rb @@ -2,7 +2,8 @@ class REST::RelationshipSerializer < ActiveModel::Serializer attributes :id, :following, :showing_reblogs, :followed_by, :blocking, - :muting, :muting_notifications, :requested, :domain_blocking + :muting, :muting_notifications, :requested, :domain_blocking, + :endorsed def id object.id.to_s @@ -41,4 +42,8 @@ class REST::RelationshipSerializer < ActiveModel::Serializer def domain_blocking instance_options[:relationships].domain_blocking[object.id] || false end + + def endorsed + instance_options[:relationships].endorsed[object.id] || false + end end diff --git a/app/views/about/_administration.html.haml b/app/views/about/_administration.html.haml deleted file mode 100644 index 02286d68b..000000000 --- a/app/views/about/_administration.html.haml +++ /dev/null @@ -1,19 +0,0 @@ -.account - .account__wrapper - - if @instance_presenter.contact_account - = link_to TagManager.instance.url_for(@instance_presenter.contact_account), class: 'account__display-name' do - .account__avatar-wrapper - .account__avatar{ style: "background-image: url(#{@instance_presenter.contact_account.avatar.url})" } - %span.display-name - %bdi - %strong.display-name__html.emojify= display_name(@instance_presenter.contact_account, custom_emojify: true) - %span.display-name__account @#{@instance_presenter.contact_account.acct} - - else - .account__display-name - .account__avatar-wrapper - .account__avatar{ style: "background-image: url(#{full_asset_url('avatars/original/missing.png', skip_pipeline: true)})" } - %span.display-name - %strong= t 'about.contact_missing' - %span.display-name__account= t 'about.contact_unavailable' - - = link_to t('about.learn_more'), about_more_path, class: 'button button-alternative' diff --git a/app/views/about/_contact.html.haml b/app/views/about/_contact.html.haml deleted file mode 100644 index 3215d50b5..000000000 --- a/app/views/about/_contact.html.haml +++ /dev/null @@ -1,22 +0,0 @@ -.panel - .panel-header - = succeed ':' do - = t 'about.contact' - - if contact.site_contact_email.present? - = mail_to contact.site_contact_email, nil, title: contact.site_contact_email - - else - %span= t 'about.contact_unavailable' - .panel-body - - if contact.contact_account - .owner - .avatar= image_tag contact.contact_account.avatar.url - .name - = link_to TagManager.instance.url_for(contact.contact_account) do - %span.display_name.emojify= display_name(contact.contact_account, custom_emojify: true) - %span.username @#{contact.contact_account.acct} - - else - .owner - .avatar= image_tag full_asset_url('avatars/original/missing.png', skip_pipeline: true) - .name - %span.display_name= t 'about.contact_missing' - %span.username= t 'about.contact_unavailable' diff --git a/app/views/about/more.html.haml b/app/views/about/more.html.haml index f86051fbf..bafe618cc 100644 --- a/app/views/about/more.html.haml +++ b/app/views/about/more.html.haml @@ -4,43 +4,43 @@ - content_for :header_tags do = render partial: 'shared/og' -.landing-page - .header-wrapper.compact - .header - = render 'links' +.grid-3 + .column-0 + .public-account-header.public-account-header--no-bar + .public-account-header__image + = image_tag @instance_presenter.hero&.file&.url || @instance_presenter.thumbnail&.file&.url || asset_pack_path('preview.jpg'), alt: @instance_presenter.site_title, class: 'parallax' - .container-alt.hero - .heading - %h3= t('about.description_headline', domain: site_hostname) - %p= @instance_presenter.site_description.html_safe.presence || t('about.generic_description', domain: site_hostname) + .column-1 + .landing-page__call-to-action{ dir: 'ltr' } + .row + .row__information-board + .information-board__section + %span= t 'about.user_count_before' + %strong= number_with_delimiter @instance_presenter.user_count + %span= t 'about.user_count_after' + .information-board__section + %span= t 'about.status_count_before' + %strong= number_with_delimiter @instance_presenter.status_count + %span= t 'about.status_count_after' + .row__mascot + .landing-page__mascot + = image_tag asset_pack_path('elephant_ui_plane.svg') - .information-board - .container-alt - .information-board__sections - .information-board__section - %span= t 'about.user_count_before' - %strong= number_with_delimiter @instance_presenter.user_count - %span= t 'about.user_count_after' - .information-board__section - %span= t 'about.status_count_before' - %strong= number_with_delimiter @instance_presenter.status_count - %span= t 'about.status_count_after' - .information-board__section - %span= t 'about.domain_count_before' - %strong= number_with_delimiter @instance_presenter.domain_count - %span= t 'about.domain_count_after' - = render 'contact', contact: @instance_presenter + .column-2 + .landing-page__information.contact-widget + %p + %strong= t 'about.administered_by' - .extended-description - .container-alt - = @instance_presenter.site_extended_description.html_safe.presence || t('about.extended_description_html') + = account_link_to(@instance_presenter.contact_account) - .footer-links - .container-alt - %p - = link_to t('about.source_code'), @instance_presenter.source_url - - if @instance_presenter.commit_hash == "" - %strong= " (#{@instance_presenter.version_number})" - - else - %strong= "#{@instance_presenter.version_number}, " - %strong= "#{@instance_presenter.commit_hash}" + - if @instance_presenter.site_contact_email.present? + %p.contact-widget__mail + %strong + = succeed ':' do + = t 'about.contact' + %br/ + = mail_to @instance_presenter.site_contact_email, nil, title: @instance_presenter.site_contact_email + + .column-3 + .box-widget + .rich-formatting= @instance_presenter.site_extended_description.html_safe.presence || t('about.extended_description_html') diff --git a/app/views/about/show.html.haml b/app/views/about/show.html.haml index e6d4cd10e..9d40eceeb 100644 --- a/app/views/about/show.html.haml +++ b/app/views/about/show.html.haml @@ -109,7 +109,7 @@ %p= t 'about.about_mastodon_html' %div.contact %h3= t 'about.administered_by' - = render 'administration' + = account_link_to(@instance_presenter.contact_account, link_to(t('about.learn_more'), about_more_path, class: 'button button-alternative')) = render 'features' @@ -130,7 +130,7 @@ %p= t 'about.about_mastodon_html' %div.contact %h3= t 'about.administered_by' - = render 'administration' + = account_link_to(@instance_presenter.contact_account, link_to(t('about.learn_more'), about_more_path, class: 'button button-alternative')) = render 'features' diff --git a/app/views/about/terms.html.haml b/app/views/about/terms.html.haml index c7d36ed47..9d076a91b 100644 --- a/app/views/about/terms.html.haml +++ b/app/views/about/terms.html.haml @@ -1,11 +1,9 @@ - content_for :page_title do = t('terms.title', instance: site_hostname) -.landing-page - .header-wrapper.compact - .header - = render 'links' - - .extended-description - .container-alt - = @instance_presenter.site_terms.html_safe.presence || t('terms.body_html') +.grid + .column-0 + .box-widget + .rich-formatting= @instance_presenter.site_terms.html_safe.presence || t('terms.body_html') + .column-1 + = render 'application/sidebar' diff --git a/app/views/accounts/_header.html.haml b/app/views/accounts/_header.html.haml index e343be820..d3b9893c4 100644 --- a/app/views/accounts/_header.html.haml +++ b/app/views/accounts/_header.html.haml @@ -7,7 +7,7 @@ .public-account-header__tabs .public-account-header__tabs__name %h1 - = display_name(account) + = display_name(account, custom_emojify: true) %small = acct(account) = fa_icon('lock') if account.locked? diff --git a/app/views/accounts/show.html.haml b/app/views/accounts/show.html.haml index b30755d94..e398fc29b 100644 --- a/app/views/accounts/show.html.haml +++ b/app/views/accounts/show.html.haml @@ -55,4 +55,12 @@ = render 'moved', account: @account = render 'bio', account: @account + + - unless @endorsed_accounts.empty? + .endorsements-widget + %h4= t 'accounts.choices_html', name: content_tag(:bdi, display_name(@account, custom_emojify: true)) + + - @endorsed_accounts.each do |account| + = account_link_to account + = render 'application/sidebar' |