about summary refs log tree commit diff
path: root/app/javascript
diff options
context:
space:
mode:
Diffstat (limited to 'app/javascript')
-rw-r--r--app/javascript/mastodon/actions/accounts.js74
-rw-r--r--app/javascript/mastodon/containers/mastodon.js7
-rw-r--r--app/javascript/mastodon/features/account/components/action_bar.js6
-rw-r--r--app/javascript/mastodon/features/account_timeline/components/header.js6
-rw-r--r--app/javascript/mastodon/features/account_timeline/containers/header_container.js10
-rw-r--r--app/javascript/mastodon/locales/pl.json24
-rw-r--r--app/javascript/mastodon/locales/ru.json44
-rw-r--r--app/javascript/mastodon/reducers/relationships.js4
-rw-r--r--app/javascript/packs/public.js9
-rw-r--r--app/javascript/styles/mastodon/about.scss418
-rw-r--r--app/javascript/styles/mastodon/containers.scss90
-rw-r--r--app/javascript/styles/mastodon/widgets.scss83
12 files changed, 596 insertions, 179 deletions
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;