about summary refs log tree commit diff
path: root/app/javascript
diff options
context:
space:
mode:
authorpluralcafe-docker <git@plural.cafe>2018-10-13 01:09:02 +0000
committerpluralcafe-docker <git@plural.cafe>2018-10-13 01:09:02 +0000
commit7c96ee7815c216d6ac3b748d7dd6959376d3914e (patch)
treefd36bade02afa1536198e7f3beafb208973b68c5 /app/javascript
parentf9275cb762a311cbf298b3929552a153703c0726 (diff)
parent70d346ea951ebfa002225759310d72882a435a5c (diff)
Merge branch 'glitch'
Diffstat (limited to 'app/javascript')
-rw-r--r--app/javascript/core/settings.js2
-rw-r--r--app/javascript/flavours/glitch/components/media_gallery.js4
-rw-r--r--app/javascript/flavours/glitch/components/permalink.js15
-rw-r--r--app/javascript/flavours/glitch/components/scrollable_list.js1
-rw-r--r--app/javascript/flavours/glitch/containers/status_container.js15
-rw-r--r--app/javascript/flavours/glitch/features/account_gallery/components/media_item.js40
-rw-r--r--app/javascript/flavours/glitch/features/account_gallery/index.js3
-rw-r--r--app/javascript/flavours/glitch/features/blocks/index.js3
-rw-r--r--app/javascript/flavours/glitch/features/favourites/index.js1
-rw-r--r--app/javascript/flavours/glitch/features/follow_requests/index.js1
-rw-r--r--app/javascript/flavours/glitch/features/followers/index.js1
-rw-r--r--app/javascript/flavours/glitch/features/following/index.js3
-rw-r--r--app/javascript/flavours/glitch/features/getting_started/index.js9
-rw-r--r--app/javascript/flavours/glitch/features/local_settings/navigation/index.js8
-rw-r--r--app/javascript/flavours/glitch/features/local_settings/navigation/item/index.js8
-rw-r--r--app/javascript/flavours/glitch/features/local_settings/page/index.js32
-rw-r--r--app/javascript/flavours/glitch/features/local_settings/page/item/index.js95
-rw-r--r--app/javascript/flavours/glitch/features/mutes/index.js1
-rw-r--r--app/javascript/flavours/glitch/features/reblogs/index.js1
-rw-r--r--app/javascript/flavours/glitch/features/status/index.js18
-rw-r--r--app/javascript/flavours/glitch/features/ui/components/onboarding_modal.js2
-rw-r--r--app/javascript/flavours/glitch/features/ui/index.js2
-rw-r--r--app/javascript/flavours/glitch/features/video/index.js4
-rw-r--r--app/javascript/flavours/glitch/styles/about.scss46
-rw-r--r--app/javascript/flavours/glitch/styles/admin.scss2
-rw-r--r--app/javascript/flavours/glitch/styles/basics.scss6
-rw-r--r--app/javascript/flavours/glitch/styles/components/accounts.scss11
-rw-r--r--app/javascript/flavours/glitch/styles/components/local_settings.scss50
-rw-r--r--app/javascript/flavours/glitch/styles/containers.scss4
-rw-r--r--app/javascript/flavours/glitch/styles/dashboard.scss2
-rw-r--r--app/javascript/flavours/glitch/styles/forms.scss8
-rw-r--r--app/javascript/flavours/glitch/styles/mastodon-light/diff.scss3
-rw-r--r--app/javascript/flavours/glitch/styles/rtl.scss27
-rw-r--r--app/javascript/flavours/glitch/styles/tables.scss2
-rw-r--r--app/javascript/flavours/glitch/styles/variables.scss4
-rw-r--r--app/javascript/flavours/glitch/util/content_warning.js5
-rw-r--r--app/javascript/flavours/glitch/util/initial_state.js1
-rw-r--r--app/javascript/mastodon/actions/conversations.js59
-rw-r--r--app/javascript/mastodon/actions/importer/normalizer.js3
-rw-r--r--app/javascript/mastodon/actions/streaming.js4
-rw-r--r--app/javascript/mastodon/actions/timelines.js1
-rw-r--r--app/javascript/mastodon/components/__tests__/__snapshots__/autosuggest_emoji-test.js.snap27
-rw-r--r--app/javascript/mastodon/components/__tests__/autosuggest_emoji-test.js29
-rw-r--r--app/javascript/mastodon/components/display_name.js11
-rw-r--r--app/javascript/mastodon/components/media_gallery.js4
-rw-r--r--app/javascript/mastodon/components/status.js2
-rw-r--r--app/javascript/mastodon/components/status_content.js40
-rw-r--r--app/javascript/mastodon/containers/status_container.js15
-rw-r--r--app/javascript/mastodon/features/account_gallery/components/media_item.js4
-rw-r--r--app/javascript/mastodon/features/direct_timeline/components/conversation.js85
-rw-r--r--app/javascript/mastodon/features/direct_timeline/components/conversations_list.js68
-rw-r--r--app/javascript/mastodon/features/direct_timeline/containers/conversation_container.js15
-rw-r--r--app/javascript/mastodon/features/direct_timeline/containers/conversations_list_container.js15
-rw-r--r--app/javascript/mastodon/features/direct_timeline/index.js18
-rw-r--r--app/javascript/mastodon/features/getting_started/index.js2
-rw-r--r--app/javascript/mastodon/features/notifications/components/column_settings.js9
-rw-r--r--app/javascript/mastodon/features/notifications/components/notification.js5
-rw-r--r--app/javascript/mastodon/features/notifications/components/setting_toggle.js4
-rw-r--r--app/javascript/mastodon/features/status/index.js15
-rw-r--r--app/javascript/mastodon/features/ui/components/onboarding_modal.js2
-rw-r--r--app/javascript/mastodon/features/ui/index.js8
-rw-r--r--app/javascript/mastodon/features/video/index.js15
-rw-r--r--app/javascript/mastodon/initial_state.js3
-rw-r--r--app/javascript/mastodon/locales/ar.json1
-rw-r--r--app/javascript/mastodon/locales/ast.json1
-rw-r--r--app/javascript/mastodon/locales/bg.json1
-rw-r--r--app/javascript/mastodon/locales/ca.json1
-rw-r--r--app/javascript/mastodon/locales/co.json1
-rw-r--r--app/javascript/mastodon/locales/cs.json1
-rw-r--r--app/javascript/mastodon/locales/cy.json668
-rw-r--r--app/javascript/mastodon/locales/da.json1
-rw-r--r--app/javascript/mastodon/locales/de.json1
-rw-r--r--app/javascript/mastodon/locales/defaultMessages.json28
-rw-r--r--app/javascript/mastodon/locales/el.json1
-rw-r--r--app/javascript/mastodon/locales/en.json4
-rw-r--r--app/javascript/mastodon/locales/eo.json1
-rw-r--r--app/javascript/mastodon/locales/es.json1
-rw-r--r--app/javascript/mastodon/locales/eu.json1
-rw-r--r--app/javascript/mastodon/locales/fa.json1
-rw-r--r--app/javascript/mastodon/locales/fi.json1
-rw-r--r--app/javascript/mastodon/locales/fr.json1
-rw-r--r--app/javascript/mastodon/locales/gl.json1
-rw-r--r--app/javascript/mastodon/locales/he.json1
-rw-r--r--app/javascript/mastodon/locales/hr.json1
-rw-r--r--app/javascript/mastodon/locales/hu.json1
-rw-r--r--app/javascript/mastodon/locales/hy.json1
-rw-r--r--app/javascript/mastodon/locales/id.json1
-rw-r--r--app/javascript/mastodon/locales/io.json1
-rw-r--r--app/javascript/mastodon/locales/it.json23
-rw-r--r--app/javascript/mastodon/locales/ja.json4
-rw-r--r--app/javascript/mastodon/locales/ka.json1
-rw-r--r--app/javascript/mastodon/locales/ko.json1
-rw-r--r--app/javascript/mastodon/locales/nl.json1
-rw-r--r--app/javascript/mastodon/locales/no.json1
-rw-r--r--app/javascript/mastodon/locales/oc.json1
-rw-r--r--app/javascript/mastodon/locales/pl.json6
-rw-r--r--app/javascript/mastodon/locales/pt-BR.json1
-rw-r--r--app/javascript/mastodon/locales/pt.json1
-rw-r--r--app/javascript/mastodon/locales/ro.json1
-rw-r--r--app/javascript/mastodon/locales/ru.json1
-rw-r--r--app/javascript/mastodon/locales/sk.json1
-rw-r--r--app/javascript/mastodon/locales/sl.json1
-rw-r--r--app/javascript/mastodon/locales/sr-Latn.json1
-rw-r--r--app/javascript/mastodon/locales/sr.json1
-rw-r--r--app/javascript/mastodon/locales/sv.json1
-rw-r--r--app/javascript/mastodon/locales/ta.json1
-rw-r--r--app/javascript/mastodon/locales/te.json1
-rw-r--r--app/javascript/mastodon/locales/th.json1
-rw-r--r--app/javascript/mastodon/locales/tr.json1
-rw-r--r--app/javascript/mastodon/locales/uk.json1
-rw-r--r--app/javascript/mastodon/locales/zh-CN.json1
-rw-r--r--app/javascript/mastodon/locales/zh-HK.json1
-rw-r--r--app/javascript/mastodon/locales/zh-TW.json1
-rw-r--r--app/javascript/mastodon/reducers/conversations.js79
-rw-r--r--app/javascript/mastodon/reducers/index.js2
-rw-r--r--app/javascript/mastodon/reducers/notifications.js2
-rw-r--r--app/javascript/mastodon/service_worker/web_push_notifications.js2
-rw-r--r--app/javascript/styles/mailer.scss2
-rw-r--r--app/javascript/styles/mastodon/about.scss46
-rw-r--r--app/javascript/styles/mastodon/admin.scss2
-rw-r--r--app/javascript/styles/mastodon/basics.scss6
-rw-r--r--app/javascript/styles/mastodon/components.scss75
-rw-r--r--app/javascript/styles/mastodon/containers.scss4
-rw-r--r--app/javascript/styles/mastodon/dashboard.scss2
-rw-r--r--app/javascript/styles/mastodon/forms.scss8
-rw-r--r--app/javascript/styles/mastodon/rtl.scss35
-rw-r--r--app/javascript/styles/mastodon/tables.scss2
-rw-r--r--app/javascript/styles/mastodon/variables.scss4
128 files changed, 1323 insertions, 628 deletions
diff --git a/app/javascript/core/settings.js b/app/javascript/core/settings.js
index 377c899dc..af97c84f9 100644
--- a/app/javascript/core/settings.js
+++ b/app/javascript/core/settings.js
@@ -56,7 +56,7 @@ delegate(document, '.input-copy input', 'click', ({ target }) => {
 });
 
 delegate(document, '.input-copy button', 'click', ({ target }) => {
-  const input = target.parentNode.querySelector('input');
+  const input = target.parentNode.querySelector('.input-copy__wrapper input');
 
   input.focus();
   input.select();
diff --git a/app/javascript/flavours/glitch/components/media_gallery.js b/app/javascript/flavours/glitch/components/media_gallery.js
index 605a2862b..613318102 100644
--- a/app/javascript/flavours/glitch/components/media_gallery.js
+++ b/app/javascript/flavours/glitch/components/media_gallery.js
@@ -6,7 +6,7 @@ import IconButton from './icon_button';
 import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
 import { isIOS } from 'flavours/glitch/util/is_mobile';
 import classNames from 'classnames';
-import { autoPlayGif, displaySensitiveMedia } from 'flavours/glitch/util/initial_state';
+import { autoPlayGif, displayMedia } from 'flavours/glitch/util/initial_state';
 
 const messages = defineMessages({
   hidden: {
@@ -226,7 +226,7 @@ export default class MediaGallery extends React.PureComponent {
   };
 
   state = {
-    visible: this.props.revealed === undefined ? (!this.props.sensitive || displaySensitiveMedia) : this.props.revealed,
+    visible: this.props.revealed === undefined ? (displayMedia !== 'hide_all' && !this.props.sensitive || displayMedia === 'show_all') : this.props.revealed,
   };
 
   componentWillReceiveProps (nextProps) {
diff --git a/app/javascript/flavours/glitch/components/permalink.js b/app/javascript/flavours/glitch/components/permalink.js
index d6556b584..1ea6a2915 100644
--- a/app/javascript/flavours/glitch/components/permalink.js
+++ b/app/javascript/flavours/glitch/components/permalink.js
@@ -12,12 +12,20 @@ export default class Permalink extends React.PureComponent {
     href: PropTypes.string.isRequired,
     to: PropTypes.string.isRequired,
     children: PropTypes.node,
+    onInterceptClick: PropTypes.func,
   };
 
   handleClick = (e) => {
-    if (this.context.router && e.button === 0 && !(e.ctrlKey || e.metaKey)) {
-      e.preventDefault();
-      this.context.router.history.push(this.props.to);
+    if (e.button === 0 && !(e.ctrlKey || e.metaKey)) {
+      if (this.props.onInterceptClick && this.props.onInterceptClick()) {
+        e.preventDefault();
+        return;
+      }
+
+      if (this.context.router) {
+        e.preventDefault();
+        this.context.router.history.push(this.props.to);
+      }
     }
   }
 
@@ -27,6 +35,7 @@ export default class Permalink extends React.PureComponent {
       className,
       href,
       to,
+      onInterceptClick,
       ...other
     } = this.props;
 
diff --git a/app/javascript/flavours/glitch/components/scrollable_list.js b/app/javascript/flavours/glitch/components/scrollable_list.js
index a677cbf5b..3ee710dc9 100644
--- a/app/javascript/flavours/glitch/components/scrollable_list.js
+++ b/app/javascript/flavours/glitch/components/scrollable_list.js
@@ -150,6 +150,7 @@ export default class ScrollableList extends PureComponent {
   }
 
   defaultShouldUpdateScroll = (prevRouterProps, { location }) => {
+    if ((((prevRouterProps || {}).location || {}).state || {}).mastodonModalOpen) return false;
     return !(location.state && location.state.mastodonModalOpen);
   }
 
diff --git a/app/javascript/flavours/glitch/containers/status_container.js b/app/javascript/flavours/glitch/containers/status_container.js
index 5ac92ea39..663bfbebc 100644
--- a/app/javascript/flavours/glitch/containers/status_container.js
+++ b/app/javascript/flavours/glitch/containers/status_container.js
@@ -31,6 +31,8 @@ const messages = defineMessages({
   redraftConfirm: { id: 'confirmations.redraft.confirm', defaultMessage: 'Delete & redraft' },
   redraftMessage: { id: 'confirmations.redraft.message', defaultMessage: 'Are you sure you want to delete this status and re-draft it? You will lose all replies, boosts and favourites to it.' },
   blockConfirm: { id: 'confirmations.block.confirm', defaultMessage: 'Block' },
+  replyConfirm: { id: 'confirmations.reply.confirm', defaultMessage: 'Reply' },
+  replyMessage: { id: 'confirmations.reply.message', defaultMessage: 'Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?' },
 });
 
 const makeMapStateToProps = () => {
@@ -67,7 +69,18 @@ const makeMapStateToProps = () => {
 const mapDispatchToProps = (dispatch, { intl }) => ({
 
   onReply (status, router) {
-    dispatch(replyCompose(status, router));
+    dispatch((_, getState) => {
+      let state = getState();
+      if (state.getIn(['compose', 'text']).trim().length !== 0) {
+        dispatch(openModal('CONFIRM', {
+          message: intl.formatMessage(messages.replyMessage),
+          confirm: intl.formatMessage(messages.replyConfirm),
+          onConfirm: () => dispatch(replyCompose(status, router)),
+        }));
+      } else {
+        dispatch(replyCompose(status, router));
+      }
+    });
   },
 
   onModalReblog (status) {
diff --git a/app/javascript/flavours/glitch/features/account_gallery/components/media_item.js b/app/javascript/flavours/glitch/features/account_gallery/components/media_item.js
index c2cf48d7b..89778e123 100644
--- a/app/javascript/flavours/glitch/features/account_gallery/components/media_item.js
+++ b/app/javascript/flavours/glitch/features/account_gallery/components/media_item.js
@@ -2,6 +2,7 @@ import React from 'react';
 import ImmutablePropTypes from 'react-immutable-proptypes';
 import ImmutablePureComponent from 'react-immutable-pure-component';
 import Permalink from 'flavours/glitch/components/permalink';
+import { displayMedia } from 'flavours/glitch/util/initial_state';
 
 export default class MediaItem extends ImmutablePureComponent {
 
@@ -9,8 +10,22 @@ export default class MediaItem extends ImmutablePureComponent {
     media: ImmutablePropTypes.map.isRequired,
   };
 
+  state = {
+    visible: displayMedia !== 'hide_all' && !this.props.media.getIn(['status', 'sensitive']) || displayMedia === 'show_all',
+  };
+
+  handleClick = () => {
+    if (!this.state.visible) {
+      this.setState({ visible: true });
+      return true;
+    }
+
+    return false;
+  }
+
   render () {
     const { media } = this.props;
+    const { visible } = this.state;
     const status = media.get('status');
     const focusX = media.getIn(['meta', 'focus', 'x']);
     const focusY = media.getIn(['meta', 'focus', 'y']);
@@ -18,21 +33,36 @@ export default class MediaItem extends ImmutablePureComponent {
     const y = ((focusY / -2) + .5) * 100;
     const style = {};
 
-    let content;
+    let label, icon, title;
 
     if (media.get('type') === 'gifv') {
-      content = <span className='media-gallery__gifv__label'>GIF</span>;
+      label = <span className='media-gallery__gifv__label'>GIF</span>;
     }
 
-    if (!status.get('sensitive')) {
+    if (visible) {
       style.backgroundImage    = `url(${media.get('preview_url')})`;
       style.backgroundPosition = `${x}% ${y}%`;
+      title                    = media.get('description');
+    } else {
+      icon = (
+        <span className='account-gallery__item__icons'>
+          <i className='fa fa-eye-slash' />
+        </span>
+      );
+      title = status.get('spoiler_text') || media.get('description');
     }
 
     return (
       <div className='account-gallery__item'>
-        <Permalink to={`/statuses/${status.get('id')}`} href={status.get('url')} style={style}>
-          {content}
+        <Permalink
+          to={`/statuses/${status.get('id')}`}
+          href={status.get('url')}
+          style={style}
+          title={title}
+          onInterceptClick={this.handleClick}
+        >
+          {icon}
+          {label}
         </Permalink>
       </div>
     );
diff --git a/app/javascript/flavours/glitch/features/account_gallery/index.js b/app/javascript/flavours/glitch/features/account_gallery/index.js
index de8318964..53b906d16 100644
--- a/app/javascript/flavours/glitch/features/account_gallery/index.js
+++ b/app/javascript/flavours/glitch/features/account_gallery/index.js
@@ -90,7 +90,8 @@ export default class AccountGallery extends ImmutablePureComponent {
   }
 
   shouldUpdateScroll = (prevRouterProps, { location }) => {
-    return !(location.state && location.state.mastodonModalOpen)
+    if ((((prevRouterProps || {}).location || {}).state || {}).mastodonModalOpen) return false;
+    return !(location.state && location.state.mastodonModalOpen);
   }
 
   render () {
diff --git a/app/javascript/flavours/glitch/features/blocks/index.js b/app/javascript/flavours/glitch/features/blocks/index.js
index 4c8b16504..386a0ce63 100644
--- a/app/javascript/flavours/glitch/features/blocks/index.js
+++ b/app/javascript/flavours/glitch/features/blocks/index.js
@@ -43,7 +43,8 @@ export default class Blocks extends ImmutablePureComponent {
   }
 
   shouldUpdateScroll = (prevRouterProps, { location }) => {
-    return !(location.state && location.state.mastodonModalOpen)
+    if ((((prevRouterProps || {}).location || {}).state || {}).mastodonModalOpen) return false;
+    return !(location.state && location.state.mastodonModalOpen);
   }
 
   render () {
diff --git a/app/javascript/flavours/glitch/features/favourites/index.js b/app/javascript/flavours/glitch/features/favourites/index.js
index cf8b31eb3..65cd4a19b 100644
--- a/app/javascript/flavours/glitch/features/favourites/index.js
+++ b/app/javascript/flavours/glitch/features/favourites/index.js
@@ -34,6 +34,7 @@ export default class Favourites extends ImmutablePureComponent {
   }
 
   shouldUpdateScroll = (prevRouterProps, { location }) => {
+    if ((((prevRouterProps || {}).location || {}).state || {}).mastodonModalOpen) return false;
     return !(location.state && location.state.mastodonModalOpen);
   }
 
diff --git a/app/javascript/flavours/glitch/features/follow_requests/index.js b/app/javascript/flavours/glitch/features/follow_requests/index.js
index 1e4633984..bce6338ea 100644
--- a/app/javascript/flavours/glitch/features/follow_requests/index.js
+++ b/app/javascript/flavours/glitch/features/follow_requests/index.js
@@ -43,6 +43,7 @@ export default class FollowRequests extends ImmutablePureComponent {
   }
 
   shouldUpdateScroll = (prevRouterProps, { location }) => {
+    if ((((prevRouterProps || {}).location || {}).state || {}).mastodonModalOpen) return false;
     return !(location.state && location.state.mastodonModalOpen);
   }
 
diff --git a/app/javascript/flavours/glitch/features/followers/index.js b/app/javascript/flavours/glitch/features/followers/index.js
index cdde1775c..a977142ed 100644
--- a/app/javascript/flavours/glitch/features/followers/index.js
+++ b/app/javascript/flavours/glitch/features/followers/index.js
@@ -57,6 +57,7 @@ export default class Followers extends ImmutablePureComponent {
   }
 
   shouldUpdateScroll = (prevRouterProps, { location }) => {
+    if ((((prevRouterProps || {}).location || {}).state || {}).mastodonModalOpen) return false;
     return !(location.state && location.state.mastodonModalOpen);
   }
 
diff --git a/app/javascript/flavours/glitch/features/following/index.js b/app/javascript/flavours/glitch/features/following/index.js
index e7a72d036..70aeefaad 100644
--- a/app/javascript/flavours/glitch/features/following/index.js
+++ b/app/javascript/flavours/glitch/features/following/index.js
@@ -57,7 +57,8 @@ export default class Following extends ImmutablePureComponent {
   }
 
   shouldUpdateScroll = (prevRouterProps, { location }) => {
-    return !(location.state && location.state.mastodonModalOpen)
+    if ((((prevRouterProps || {}).location || {}).state || {}).mastodonModalOpen) return false;
+    return !(location.state && location.state.mastodonModalOpen);
   }
 
   render () {
diff --git a/app/javascript/flavours/glitch/features/getting_started/index.js b/app/javascript/flavours/glitch/features/getting_started/index.js
index 09dcbe716..c1897cc33 100644
--- a/app/javascript/flavours/glitch/features/getting_started/index.js
+++ b/app/javascript/flavours/glitch/features/getting_started/index.js
@@ -165,13 +165,10 @@ export default class GettingStarted extends ImmutablePureComponent {
           <div className='getting-started__footer'>
             <div className='static-content getting-started'>
               <p>
-                <a href='https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/FAQ.md' rel='noopener' target='_blank'>
-                  <FormattedMessage id='getting_started.faq' defaultMessage='FAQ' />
+                <a href='https://docs.joinmastodon.org' target='_blank'>
+                  <FormattedMessage id='getting_started.documentation' defaultMessage='Documentation' />
                 </a>&nbsp;•&nbsp;
-                <a href='https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/User-guide.md' rel='noopener' target='_blank'>
-                  <FormattedMessage id='getting_started.userguide' defaultMessage='User Guide' />
-                </a>&nbsp;•&nbsp;
-                <a href='https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/Apps.md' rel='noopener' target='_blank'>
+                <a href='https://joinmastodon.org/apps' target='_blank' rel='noopener'>
                   <FormattedMessage id='getting_started.appsshort' defaultMessage='Apps' />
                 </a>
               </p>
diff --git a/app/javascript/flavours/glitch/features/local_settings/navigation/index.js b/app/javascript/flavours/glitch/features/local_settings/navigation/index.js
index a992b1ffc..cf02101cf 100644
--- a/app/javascript/flavours/glitch/features/local_settings/navigation/index.js
+++ b/app/javascript/flavours/glitch/features/local_settings/navigation/index.js
@@ -38,37 +38,42 @@ export default class LocalSettingsNavigation extends React.PureComponent {
           active={index === 0}
           index={0}
           onNavigate={onNavigate}
+          icon='cogs'
           title={intl.formatMessage(messages.general)}
         />
         <LocalSettingsNavigationItem
           active={index === 1}
           index={1}
           onNavigate={onNavigate}
+          icon='pencil'
           title={intl.formatMessage(messages.compose)}
         />
         <LocalSettingsNavigationItem
           active={index === 2}
           index={2}
           onNavigate={onNavigate}
+          textIcon='CW'
           title={intl.formatMessage(messages.content_warnings)}
         />
         <LocalSettingsNavigationItem
           active={index === 3}
           index={3}
           onNavigate={onNavigate}
+          icon='angle-double-up'
           title={intl.formatMessage(messages.collapsed)}
         />
         <LocalSettingsNavigationItem
           active={index === 4}
           index={4}
           onNavigate={onNavigate}
+          icon='image'
           title={intl.formatMessage(messages.media)}
         />
         <LocalSettingsNavigationItem
           active={index === 5}
           href='/settings/preferences'
           index={5}
-          icon='cog'
+          icon='sliders'
           title={intl.formatMessage(messages.preferences)}
         />
         <LocalSettingsNavigationItem
@@ -76,6 +81,7 @@ export default class LocalSettingsNavigation extends React.PureComponent {
           className='close'
           index={6}
           onNavigate={onClose}
+          icon='times'
           title={intl.formatMessage(messages.close)}
         />
       </nav>
diff --git a/app/javascript/flavours/glitch/features/local_settings/navigation/item/index.js b/app/javascript/flavours/glitch/features/local_settings/navigation/item/index.js
index b67d479e7..68a998b6c 100644
--- a/app/javascript/flavours/glitch/features/local_settings/navigation/item/index.js
+++ b/app/javascript/flavours/glitch/features/local_settings/navigation/item/index.js
@@ -12,6 +12,7 @@ export default class LocalSettingsPage extends React.PureComponent {
     className: PropTypes.string,
     href: PropTypes.string,
     icon: PropTypes.string,
+    textIcon: PropTypes.string,
     index: PropTypes.number.isRequired,
     onNavigate: PropTypes.func,
     title: PropTypes.string,
@@ -32,6 +33,7 @@ export default class LocalSettingsPage extends React.PureComponent {
       className,
       href,
       icon,
+      textIcon,
       onNavigate,
       title,
     } = this.props;
@@ -40,14 +42,14 @@ export default class LocalSettingsPage extends React.PureComponent {
       active,
     }, className);
 
-    const iconElem = icon ? <i className={`fa fa-fw fa-${icon}`} /> : null;
+    const iconElem = icon ? <i className={`fa fa-fw fa-${icon}`} /> : (textIcon ? <span className='text-icon-button'>{textIcon}</span> : null);
 
     if (href) return (
       <a
         href={href}
         className={finalClassName}
       >
-        {iconElem} {title}
+        {iconElem} <span>{title}</span>
       </a>
     );
     else if (onNavigate) return (
@@ -57,7 +59,7 @@ export default class LocalSettingsPage extends React.PureComponent {
         tabIndex='0'
         className={finalClassName}
       >
-        {iconElem} {title}
+        {iconElem} <span>{title}</span>
       </a>
     );
     else return null;
diff --git a/app/javascript/flavours/glitch/features/local_settings/page/index.js b/app/javascript/flavours/glitch/features/local_settings/page/index.js
index ece80c4da..4f1b8525f 100644
--- a/app/javascript/flavours/glitch/features/local_settings/page/index.js
+++ b/app/javascript/flavours/glitch/features/local_settings/page/index.js
@@ -50,7 +50,8 @@ export default class LocalSettingsPage extends React.PureComponent {
             id='mastodon-settings--notifications-tab_badge'
             onChange={onChange}
           >
-            <FormattedMessage id='settings.notifications.tab_badge' defaultMessage="Display a badge for unread notifications if the notifications column isn't open" />
+            <FormattedMessage id='settings.notifications.tab_badge' defaultMessage="Unread notifications badge" />
+            <span className='hint'><FormattedMessage id='settings.notifications.tab_badge.hint' defaultMessage="Display a badge for unread notifications in the column icons when the notifications column isn't open" /></span>
           </LocalSettingsPageItem>
           <LocalSettingsPageItem
             settings={settings}
@@ -58,7 +59,8 @@ export default class LocalSettingsPage extends React.PureComponent {
             id='mastodon-settings--notifications-favicon_badge'
             onChange={onChange}
           >
-            <FormattedMessage id='settings.notifications.favicon_badge' defaultMessage='Display unread notifications count in the favicon' />
+            <FormattedMessage id='settings.notifications.favicon_badge' defaultMessage='Unread notifications favicon badge' />
+            <span className='hint'><FormattedMessage id='settings.notifications.favicon_badge.hint' defaultMessage="Add a badge for unread notifications to the favicon" /></span>
           </LocalSettingsPageItem>
         </section>
         <section>
@@ -83,6 +85,7 @@ export default class LocalSettingsPage extends React.PureComponent {
             onChange={onChange}
           >
             <FormattedMessage id='settings.wide_view' defaultMessage='Wide view (Desktop mode only)' />
+            <span className='hint'><FormattedMessage id='settings.wide_view_hint' defaultMessage='Stretches columns to better fill the available space.' /></span>
           </LocalSettingsPageItem>
           <LocalSettingsPageItem
             settings={settings}
@@ -112,7 +115,8 @@ export default class LocalSettingsPage extends React.PureComponent {
           id='mastodon-settings--preselect_on_reply'
           onChange={onChange}
         >
-          <FormattedMessage id='settings.preselect_on_reply' defaultMessage='Pre-select usernames past the first when replying to a toot with multiple participants' />
+          <FormattedMessage id='settings.preselect_on_reply' defaultMessage='Pre-select usernames on reply' />
+          <span className='hint'><FormattedMessage id='settings.preselect_on_reply_hint' defaultMessage='When replying to a conversation with multiple participants, pre-select usernames past the first' /></span>
         </LocalSettingsPageItem>
         <LocalSettingsPageItem
           settings={settings}
@@ -186,6 +190,15 @@ export default class LocalSettingsPage extends React.PureComponent {
         >
           <FormattedMessage id='settings.enable_collapsed' defaultMessage='Enable collapsed toots' />
         </LocalSettingsPageItem>
+        <LocalSettingsPageItem
+          settings={settings}
+          item={['collapsed', 'show_action_bar']}
+          id='mastodon-settings--collapsed-show-action-bar'
+          onChange={onChange}
+          dependsOn={[['collapsed', 'enabled']]}
+        >
+          <FormattedMessage id='settings.show_action_bar' defaultMessage='Show action buttons in collapsed toots' />
+        </LocalSettingsPageItem>
         <section>
           <h2><FormattedMessage id='settings.auto_collapse' defaultMessage='Automatic collapsing' /></h2>
           <LocalSettingsPageItem
@@ -269,18 +282,6 @@ export default class LocalSettingsPage extends React.PureComponent {
             <FormattedMessage id='settings.image_backgrounds_media' defaultMessage='Preview collapsed toot media' />
           </LocalSettingsPageItem>
         </section>
-        <section>
-          <h2></h2>
-          <LocalSettingsPageItem
-            settings={settings}
-            item={['collapsed', 'show_action_bar']}
-            id='mastodon-settings--collapsed-show-action-bar'
-            onChange={onChange}
-            dependsOn={[['collapsed', 'enabled']]}
-          >
-            <FormattedMessage id='settings.show_action_bar' defaultMessage='Show action buttons in collapsed toots' />
-          </LocalSettingsPageItem>
-        </section>
       </div>
     ),
     ({ onChange, settings }) => (
@@ -293,6 +294,7 @@ export default class LocalSettingsPage extends React.PureComponent {
           onChange={onChange}
         >
           <FormattedMessage id='settings.media_letterbox' defaultMessage='Letterbox media' />
+          <span className='hint'><FormattedMessage id='settings.media_letterbox_hint' defaultMessage='Scale down and letterbox media to fill the image containers instead of stretching and cropping them' /></span>
         </LocalSettingsPageItem>
         <LocalSettingsPageItem
           settings={settings}
diff --git a/app/javascript/flavours/glitch/features/local_settings/page/item/index.js b/app/javascript/flavours/glitch/features/local_settings/page/item/index.js
index fe237f11e..66b937365 100644
--- a/app/javascript/flavours/glitch/features/local_settings/page/item/index.js
+++ b/app/javascript/flavours/glitch/features/local_settings/page/item/index.js
@@ -17,6 +17,7 @@ export default class LocalSettingsPageItem extends React.PureComponent {
     options: PropTypes.arrayOf(PropTypes.shape({
       value: PropTypes.string.isRequired,
       message: PropTypes.string.isRequired,
+      hint: PropTypes.string,
     })),
     settings: ImmutablePropTypes.map.isRequired,
     placeholder: PropTypes.string,
@@ -48,57 +49,63 @@ export default class LocalSettingsPageItem extends React.PureComponent {
 
     if (options && options.length > 0) {
       const currentValue = settings.getIn(item);
-      const optionElems = options && options.length > 0 && options.map((opt) => (
-        <option
-          key={opt.value}
-          value={opt.value}
-        >
-          {opt.message}
-        </option>
-      ));
-      return (
-        <label className='glitch local-settings__page__item' htmlFor={id}>
-          <p>{children}</p>
-          <p>
-            <select
-              id={id}
-              disabled={!enabled}
+      const optionElems = options && options.length > 0 && options.map((opt) => {
+        let optionId = `${id}--${opt.value}`;
+        return (
+          <label htmlFor={optionId}>
+            <input type='radio'
+              name={id}
+              id={optionId}
+              value={opt.value}
               onBlur={handleChange}
               onChange={handleChange}
-              value={currentValue}
-            >
-              {optionElems}
-            </select>
-          </p>
-        </label>
+              checked={ currentValue === opt.value }
+              disabled={!enabled}
+            />
+            {opt.message}
+            {opt.hint && <span class='hint'>{opt.hint}</span>}
+          </label>
+        );
+      });
+      return (
+        <div class='glitch local-settings__page__item radio_buttons'>
+          <fieldset>
+            <legend>{children}</legend>
+            {optionElems}
+          </fieldset>
+        </div>
       );
     } else if (placeholder) {
       return (
-        <label className='glitch local-settings__page__item' htmlFor={id}>
-          <p>{children}</p>
-          <p>
-            <input
-              id={id}
-              type='text'
-              value={settings.getIn(item)}
-              placeholder={placeholder}
-              onChange={handleChange}
-              disabled={!enabled}
-            />
-          </p>
-        </label>
+        <div className='glitch local-settings__page__item string'>
+          <label htmlFor={id}>
+            <p>{children}</p>
+            <p>
+              <input
+                id={id}
+                type='text'
+                value={settings.getIn(item)}
+                placeholder={placeholder}
+                onChange={handleChange}
+                disabled={!enabled}
+              />
+            </p>
+          </label>
+        </div>
       );
     } else return (
-      <label className='glitch local-settings__page__item' htmlFor={id}>
-        <input
-          id={id}
-          type='checkbox'
-          checked={settings.getIn(item)}
-          onChange={handleChange}
-          disabled={!enabled}
-        />
-        {children}
-      </label>
+      <div className='glitch local-settings__page__item boolean'>
+        <label htmlFor={id}>
+          <input
+            id={id}
+            type='checkbox'
+            checked={settings.getIn(item)}
+            onChange={handleChange}
+            disabled={!enabled}
+          />
+          {children}
+        </label>
+      </div>
     );
   }
 
diff --git a/app/javascript/flavours/glitch/features/mutes/index.js b/app/javascript/flavours/glitch/features/mutes/index.js
index d94c1d8ad..bbcbea701 100644
--- a/app/javascript/flavours/glitch/features/mutes/index.js
+++ b/app/javascript/flavours/glitch/features/mutes/index.js
@@ -43,6 +43,7 @@ export default class Mutes extends ImmutablePureComponent {
   }
 
   shouldUpdateScroll = (prevRouterProps, { location }) => {
+    if ((((prevRouterProps || {}).location || {}).state || {}).mastodonModalOpen) return false;
     return !(location.state && location.state.mastodonModalOpen);
   }
 
diff --git a/app/javascript/flavours/glitch/features/reblogs/index.js b/app/javascript/flavours/glitch/features/reblogs/index.js
index c0a65d1de..75f8390a1 100644
--- a/app/javascript/flavours/glitch/features/reblogs/index.js
+++ b/app/javascript/flavours/glitch/features/reblogs/index.js
@@ -34,6 +34,7 @@ export default class Reblogs extends ImmutablePureComponent {
   }
 
   shouldUpdateScroll = (prevRouterProps, { location }) => {
+    if ((((prevRouterProps || {}).location || {}).state || {}).mastodonModalOpen) return false;
     return !(location.state && location.state.mastodonModalOpen);
   }
 
diff --git a/app/javascript/flavours/glitch/features/status/index.js b/app/javascript/flavours/glitch/features/status/index.js
index 5759a575c..4382748d5 100644
--- a/app/javascript/flavours/glitch/features/status/index.js
+++ b/app/javascript/flavours/glitch/features/status/index.js
@@ -50,6 +50,8 @@ const messages = defineMessages({
   revealAll: { id: 'status.show_more_all', defaultMessage: 'Show more for all' },
   hideAll: { id: 'status.show_less_all', defaultMessage: 'Show less for all' },
   detailedStatus: { id: 'status.detailed_status', defaultMessage: 'Detailed conversation view' },
+  replyConfirm: { id: 'confirmations.reply.confirm', defaultMessage: 'Reply' },
+  replyMessage: { id: 'confirmations.reply.message', defaultMessage: 'Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?' },
 });
 
 const makeMapStateToProps = () => {
@@ -60,6 +62,7 @@ const makeMapStateToProps = () => {
     settings: state.get('local_settings'),
     ancestorsIds: state.getIn(['contexts', 'ancestors', props.params.statusId]),
     descendantsIds: state.getIn(['contexts', 'descendants', props.params.statusId]),
+    askReplyConfirmation: state.getIn(['compose', 'text']).trim().length !== 0,
   });
 
   return mapStateToProps;
@@ -81,6 +84,7 @@ export default class Status extends ImmutablePureComponent {
     ancestorsIds: ImmutablePropTypes.list,
     descendantsIds: ImmutablePropTypes.list,
     intl: PropTypes.object.isRequired,
+    askReplyConfirmation: PropTypes.bool,
   };
 
   state = {
@@ -140,7 +144,16 @@ export default class Status extends ImmutablePureComponent {
   }
 
   handleReplyClick = (status) => {
-    this.props.dispatch(replyCompose(status, this.context.router.history));
+    let { askReplyConfirmation, dispatch, intl } = this.props;
+    if (askReplyConfirmation) {
+      dispatch(openModal('CONFIRM', {
+        message: intl.formatMessage(messages.replyMessage),
+        confirm: intl.formatMessage(messages.replyConfirm),
+        onConfirm: () => dispatch(replyCompose(status, this.context.router.history)),
+      }));
+    } else {
+      dispatch(replyCompose(status, this.context.router.history));
+    }
   }
 
   handleModalReblog = (status) => {
@@ -351,7 +364,8 @@ export default class Status extends ImmutablePureComponent {
   }
 
   shouldUpdateScroll = (prevRouterProps, { location }) => {
-    return !(location.state && location.state.mastodonModalOpen)
+    if ((((prevRouterProps || {}).location || {}).state || {}).mastodonModalOpen) return false;
+    return !(location.state && location.state.mastodonModalOpen);
   }
 
   render () {
diff --git a/app/javascript/flavours/glitch/features/ui/components/onboarding_modal.js b/app/javascript/flavours/glitch/features/ui/components/onboarding_modal.js
index 4c910daec..16355a446 100644
--- a/app/javascript/flavours/glitch/features/ui/components/onboarding_modal.js
+++ b/app/javascript/flavours/glitch/features/ui/components/onboarding_modal.js
@@ -133,7 +133,7 @@ const PageSix = ({ admin, domain }) => {
       <h1><FormattedMessage id='onboarding.page_six.almost_done' defaultMessage='Almost done...' /></h1>
       {adminSection}
       <p><FormattedMessage id='onboarding.page_six.github' defaultMessage='{domain} runs on Glitchsoc. Glitchsoc is a friendly {fork} of {Mastodon}. Glitchsoc is fully compatible with all Mastodon apps and instances. Glitchsoc is free open-source software. You can report bugs, request features, or contribute to the code on {github}.' values={{ domain, fork: <a href='https://en.wikipedia.org/wiki/Fork_(software_development)' target='_blank' rel='noopener'>fork</a>, Mastodon: <a href='https://github.com/tootsuite/mastodon' target='_blank' rel='noopener'>Mastodon</a>, github: <a href='https://github.com/glitch-soc/mastodon' target='_blank' rel='noopener'>GitHub</a> }} /></p>
-      <p><FormattedMessage id='onboarding.page_six.apps_available' defaultMessage='There are {apps} available for iOS, Android and other platforms.' values={{ domain, apps: <a href='https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/Apps.md' target='_blank' rel='noopener'><FormattedMessage id='onboarding.page_six.various_app' defaultMessage='mobile apps' /></a> }} /></p>
+      <p><FormattedMessage id='onboarding.page_six.apps_available' defaultMessage='There are {apps} available for iOS, Android and other platforms.' values={{ domain, apps: <a href='https://joinmastodon.org/apps' target='_blank' rel='noopener'><FormattedMessage id='onboarding.page_six.various_app' defaultMessage='mobile apps' /></a> }} /></p>
       <p><em><FormattedMessage id='onboarding.page_six.appetoot' defaultMessage='Bon Appetoot!' /></em></p>
     </div>
   );
diff --git a/app/javascript/flavours/glitch/features/ui/index.js b/app/javascript/flavours/glitch/features/ui/index.js
index ecbac1f8f..510bb9540 100644
--- a/app/javascript/flavours/glitch/features/ui/index.js
+++ b/app/javascript/flavours/glitch/features/ui/index.js
@@ -456,7 +456,7 @@ export default class UI extends React.Component {
     };
 
     return (
-      <HotKeys keyMap={keyMap} handlers={handlers} ref={this.setHotkeysRef}>
+      <HotKeys keyMap={keyMap} handlers={handlers} ref={this.setHotkeysRef} attach={window} focused>
         <div className={className} ref={this.setRef} style={{ pointerEvents: dropdownMenuIsOpen ? 'none' : null }}>
           {navbarUnder ? null : (<TabsBar />)}
 
diff --git a/app/javascript/flavours/glitch/features/video/index.js b/app/javascript/flavours/glitch/features/video/index.js
index 5cbe01f26..227f298e4 100644
--- a/app/javascript/flavours/glitch/features/video/index.js
+++ b/app/javascript/flavours/glitch/features/video/index.js
@@ -5,7 +5,7 @@ import { fromJS } from 'immutable';
 import { throttle } from 'lodash';
 import classNames from 'classnames';
 import { isFullscreen, requestFullscreen, exitFullscreen } from 'flavours/glitch/util/fullscreen';
-import { displaySensitiveMedia } from 'flavours/glitch/util/initial_state';
+import { displayMedia } from 'flavours/glitch/util/initial_state';
 
 const messages = defineMessages({
   play: { id: 'video.play', defaultMessage: 'Play' },
@@ -114,7 +114,7 @@ export default class Video extends React.PureComponent {
     fullscreen: false,
     hovered: false,
     muted: false,
-    revealed: this.props.revealed === undefined ? (!this.props.sensitive || displaySensitiveMedia) : this.props.revealed,
+    revealed: this.props.revealed === undefined ? (displayMedia !== 'hide_all' && !this.props.sensitive || displayMedia === 'show_all') : this.props.revealed,
   };
 
   setPlayerRef = c => {
diff --git a/app/javascript/flavours/glitch/styles/about.scss b/app/javascript/flavours/glitch/styles/about.scss
index ba46c65c5..f676a8c77 100644
--- a/app/javascript/flavours/glitch/styles/about.scss
+++ b/app/javascript/flavours/glitch/styles/about.scss
@@ -16,7 +16,7 @@ $small-breakpoint: 960px;
 }
 
 .rich-formatting {
-  font-family: 'mastodon-font-sans-serif', sans-serif;
+  font-family: $font-sans-serif, sans-serif;
   font-size: 16px;
   font-weight: 400;
   font-size: 16px;
@@ -31,7 +31,7 @@ $small-breakpoint: 960px;
 
   p,
   li {
-    font-family: 'mastodon-font-sans-serif', sans-serif;
+    font-family: $font-sans-serif, sans-serif;
     font-size: 16px;
     font-weight: 400;
     font-size: 16px;
@@ -62,7 +62,7 @@ $small-breakpoint: 960px;
   }
 
   h1 {
-    font-family: 'mastodon-font-display', sans-serif;
+    font-family: $font-display, sans-serif;
     font-size: 26px;
     line-height: 30px;
     font-weight: 500;
@@ -70,7 +70,7 @@ $small-breakpoint: 960px;
     color: $secondary-text-color;
 
     small {
-      font-family: 'mastodon-font-sans-serif', sans-serif;
+      font-family: $font-sans-serif, sans-serif;
       display: block;
       font-size: 18px;
       font-weight: 400;
@@ -79,7 +79,7 @@ $small-breakpoint: 960px;
   }
 
   h2 {
-    font-family: 'mastodon-font-display', sans-serif;
+    font-family: $font-display, sans-serif;
     font-size: 22px;
     line-height: 26px;
     font-weight: 500;
@@ -88,7 +88,7 @@ $small-breakpoint: 960px;
   }
 
   h3 {
-    font-family: 'mastodon-font-display', sans-serif;
+    font-family: $font-display, sans-serif;
     font-size: 18px;
     line-height: 24px;
     font-weight: 500;
@@ -97,7 +97,7 @@ $small-breakpoint: 960px;
   }
 
   h4 {
-    font-family: 'mastodon-font-display', sans-serif;
+    font-family: $font-display, sans-serif;
     font-size: 16px;
     line-height: 24px;
     font-weight: 500;
@@ -106,7 +106,7 @@ $small-breakpoint: 960px;
   }
 
   h5 {
-    font-family: 'mastodon-font-display', sans-serif;
+    font-family: $font-display, sans-serif;
     font-size: 14px;
     line-height: 24px;
     font-weight: 500;
@@ -115,7 +115,7 @@ $small-breakpoint: 960px;
   }
 
   h6 {
-    font-family: 'mastodon-font-display', sans-serif;
+    font-family: $font-display, sans-serif;
     font-size: 12px;
     line-height: 24px;
     font-weight: 500;
@@ -180,7 +180,7 @@ $small-breakpoint: 960px;
 
   &__section {
     flex: 1 0 0;
-    font-family: 'mastodon-font-sans-serif', sans-serif;
+    font-family: $font-sans-serif, sans-serif;
     font-size: 16px;
     line-height: 28px;
     color: $primary-text-color;
@@ -221,7 +221,7 @@ $small-breakpoint: 960px;
     bottom: -40px;
 
     .panel-header {
-      font-family: 'mastodon-font-display', sans-serif;
+      font-family: $font-display, sans-serif;
       font-size: 14px;
       line-height: 24px;
       font-weight: 500;
@@ -452,7 +452,7 @@ $small-breakpoint: 960px;
 
   p,
   li {
-    font-family: 'mastodon-font-sans-serif', sans-serif;
+    font-family: $font-sans-serif, sans-serif;
     font-size: 16px;
     font-weight: 400;
     font-size: 16px;
@@ -501,7 +501,7 @@ $small-breakpoint: 960px;
   }
 
   h1 {
-    font-family: 'mastodon-font-display', sans-serif;
+    font-family: $font-display, sans-serif;
     font-size: 26px;
     line-height: 30px;
     font-weight: 500;
@@ -509,7 +509,7 @@ $small-breakpoint: 960px;
     color: $secondary-text-color;
 
     small {
-      font-family: 'mastodon-font-sans-serif', sans-serif;
+      font-family: $font-sans-serif, sans-serif;
       display: block;
       font-size: 18px;
       font-weight: 400;
@@ -518,7 +518,7 @@ $small-breakpoint: 960px;
   }
 
   h2 {
-    font-family: 'mastodon-font-display', sans-serif;
+    font-family: $font-display, sans-serif;
     font-size: 22px;
     line-height: 26px;
     font-weight: 500;
@@ -527,7 +527,7 @@ $small-breakpoint: 960px;
   }
 
   h3 {
-    font-family: 'mastodon-font-display', sans-serif;
+    font-family: $font-display, sans-serif;
     font-size: 18px;
     line-height: 24px;
     font-weight: 500;
@@ -536,7 +536,7 @@ $small-breakpoint: 960px;
   }
 
   h4 {
-    font-family: 'mastodon-font-display', sans-serif;
+    font-family: $font-display, sans-serif;
     font-size: 16px;
     line-height: 24px;
     font-weight: 500;
@@ -545,7 +545,7 @@ $small-breakpoint: 960px;
   }
 
   h5 {
-    font-family: 'mastodon-font-display', sans-serif;
+    font-family: $font-display, sans-serif;
     font-size: 14px;
     line-height: 24px;
     font-weight: 500;
@@ -554,7 +554,7 @@ $small-breakpoint: 960px;
   }
 
   h6 {
-    font-family: 'mastodon-font-display', sans-serif;
+    font-family: $font-display, sans-serif;
     font-size: 12px;
     line-height: 24px;
     font-weight: 500;
@@ -621,7 +621,7 @@ $small-breakpoint: 960px;
 
       .hero .heading {
         padding-bottom: 20px;
-        font-family: 'mastodon-font-sans-serif', sans-serif;
+        font-family: $font-sans-serif, sans-serif;
         font-size: 16px;
         font-weight: 400;
         font-size: 16px;
@@ -672,7 +672,7 @@ $small-breakpoint: 960px;
         text-decoration: none;
         padding: 12px 16px;
         line-height: 32px;
-        font-family: 'mastodon-font-display', sans-serif;
+        font-family: $font-display, sans-serif;
         font-weight: 500;
         font-size: 14px;
 
@@ -745,7 +745,7 @@ $small-breakpoint: 960px;
   .about-short {
     background: darken($ui-base-color, 4%);
     padding: 50px 0 30px;
-    font-family: 'mastodon-font-sans-serif', sans-serif;
+    font-family: $font-sans-serif, sans-serif;
     font-size: 16px;
     font-weight: 400;
     font-size: 16px;
@@ -1015,7 +1015,7 @@ $small-breakpoint: 960px;
     display: flex;
     -webkit-overflow-scrolling: touch;
     -ms-overflow-style: -ms-autohiding-scrollbar;
-    font-family: 'mastodon-font-sans-serif', sans-serif;
+    font-family: $font-sans-serif, sans-serif;
     font-size: 13px;
     line-height: 18px;
     font-weight: 400;
diff --git a/app/javascript/flavours/glitch/styles/admin.scss b/app/javascript/flavours/glitch/styles/admin.scss
index b8cc33039..e16920dd4 100644
--- a/app/javascript/flavours/glitch/styles/admin.scss
+++ b/app/javascript/flavours/glitch/styles/admin.scss
@@ -443,7 +443,7 @@ $no-columns-breakpoint: 600px;
     border-radius: 0 0 4px 4px;
     padding: 10px;
     color: $darker-text-color;
-    font-family: 'mastodon-font-monospace', monospace;
+    font-family: $font-monospace, monospace;
     font-size: 12px;
     word-wrap: break-word;
     min-height: 20px;
diff --git a/app/javascript/flavours/glitch/styles/basics.scss b/app/javascript/flavours/glitch/styles/basics.scss
index 9c2499ac4..550b7fdfc 100644
--- a/app/javascript/flavours/glitch/styles/basics.scss
+++ b/app/javascript/flavours/glitch/styles/basics.scss
@@ -6,7 +6,7 @@
 }
 
 body {
-  font-family: 'mastodon-font-sans-serif', sans-serif;
+  font-family: $font-sans-serif, sans-serif;
   background: darken($ui-base-color, 8%);
   font-size: 13px;
   line-height: 18px;
@@ -29,8 +29,8 @@ body {
     // Fira Sans => Firefox OS
     // Droid Sans => Older Androids (<4.0)
     // Helvetica Neue => Older macOS <10.11
-    // 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;
+    // $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", $font-sans-serif, sans-serif;
   }
 
   &.app-body {
diff --git a/app/javascript/flavours/glitch/styles/components/accounts.scss b/app/javascript/flavours/glitch/styles/components/accounts.scss
index 3eddd7fb4..d87cd9c43 100644
--- a/app/javascript/flavours/glitch/styles/components/accounts.scss
+++ b/app/javascript/flavours/glitch/styles/components/accounts.scss
@@ -415,7 +415,7 @@
     background-size: cover;
     background-position: center;
     position: absolute;
-    color: inherit;
+    color: $ui-primary-color;
     text-decoration: none;
     border-radius: 4px;
 
@@ -423,6 +423,7 @@
     &:active,
     &:focus {
       outline: 0;
+      color: $ui-secondary-color;
 
       &::before {
         content: "";
@@ -434,6 +435,14 @@
       }
     }
   }
+
+  &__icons {
+    position: absolute;
+    top: 50%;
+    left: 50%;
+    transform: translate(-50%, -50%);
+    font-size: 24px;
+  }
 }
 
 .account__section-headline {
diff --git a/app/javascript/flavours/glitch/styles/components/local_settings.scss b/app/javascript/flavours/glitch/styles/components/local_settings.scss
index 9cd4e1fbe..0b7a74575 100644
--- a/app/javascript/flavours/glitch/styles/components/local_settings.scss
+++ b/app/javascript/flavours/glitch/styles/components/local_settings.scss
@@ -11,8 +11,26 @@
   max-height: 450px;
   overflow: hidden;
 
-  label {
+  label, legend {
     display: block;
+    font-size: 14px;
+  }
+
+  .boolean label, .radio_buttons label {
+    position: relative;
+    padding-left: 28px;
+    padding-top: 3px;
+
+    input {
+      position: absolute;
+      left: 0;
+      top: 0;
+    }
+  }
+
+  span.hint {
+    display: block;
+    color: $lighter-text-color;
   }
 
   h1 {
@@ -42,6 +60,11 @@
   outline: none;
   transition: background .3s;
 
+  .text-icon-button {
+    color: inherit;
+    transition: unset;
+  }
+
   &:hover {
     background: $ui-secondary-color;
   }
@@ -59,7 +82,7 @@
 
 .glitch.local-settings__navigation {
   background: lighten($ui-secondary-color, 8%);
-  width: 200px;
+  width: 212px;
   font-size: 15px;
   line-height: 20px;
   overflow-y: auto;
@@ -74,7 +97,26 @@
 }
 
 .glitch.local-settings__page__item {
-  select {
-    margin-bottom: 5px;
+  margin-bottom: 2px;
+}
+
+.glitch.local-settings__page__item.string,
+.glitch.local-settings__page__item.radio_buttons {
+  margin-top: 10px;
+  margin-bottom: 10px;
+}
+
+@media screen and (max-width: 630px) {
+  .glitch.local-settings__navigation {
+    width: 40px;
+    flex-shrink: 0;
+  }
+
+  .glitch.local-settings__navigation__item {
+    padding: 10px;
+
+    span:last-of-type {
+      display: none;
+    }
   }
 }
diff --git a/app/javascript/flavours/glitch/styles/containers.scss b/app/javascript/flavours/glitch/styles/containers.scss
index d1b9934d7..398458e47 100644
--- a/app/javascript/flavours/glitch/styles/containers.scss
+++ b/app/javascript/flavours/glitch/styles/containers.scss
@@ -37,7 +37,7 @@
       outline: 0;
       padding: 12px 16px;
       line-height: 32px;
-      font-family: 'mastodon-font-display', sans-serif;
+      font-family: $font-display, sans-serif;
       font-weight: 500;
       font-size: 14px;
     }
@@ -633,7 +633,7 @@
             font-size: 18px;
             margin-bottom: 5px;
             color: $primary-text-color;
-            font-family: 'mastodon-font-display', sans-serif;
+            font-family: $font-display, sans-serif;
           }
         }
 
diff --git a/app/javascript/flavours/glitch/styles/dashboard.scss b/app/javascript/flavours/glitch/styles/dashboard.scss
index 949ca733f..86cf6c61b 100644
--- a/app/javascript/flavours/glitch/styles/dashboard.scss
+++ b/app/javascript/flavours/glitch/styles/dashboard.scss
@@ -35,7 +35,7 @@
     font-weight: 500;
     font-size: 24px;
     color: $primary-text-color;
-    font-family: 'mastodon-font-display', sans-serif;
+    font-family: $font-display, sans-serif;
     margin-bottom: 20px;
   }
 
diff --git a/app/javascript/flavours/glitch/styles/forms.scss b/app/javascript/flavours/glitch/styles/forms.scss
index cbd3de94c..be2bf7cea 100644
--- a/app/javascript/flavours/glitch/styles/forms.scss
+++ b/app/javascript/flavours/glitch/styles/forms.scss
@@ -1,7 +1,7 @@
 $no-columns-breakpoint: 600px;
 
 code {
-  font-family: 'mastodon-font-monospace', monospace;
+  font-family: $font-monospace, monospace;
   font-weight: 400;
 }
 
@@ -474,7 +474,7 @@ code {
     width: 100%;
     border: none;
     padding: 10px;
-    font-family: 'mastodon-font-monospace', monospace;
+    font-family: $font-monospace, monospace;
     background: $ui-base-color;
     color: $primary-text-color;
     font-size: 14px;
@@ -718,7 +718,7 @@ code {
 .form_admin_settings_custom_css,
 .form_admin_settings_closed_registrations_message {
   textarea {
-    font-family: 'mastodon-font-monospace', monospace;
+    font-family: $font-monospace, monospace;
   }
 }
 
@@ -742,7 +742,7 @@ code {
     border: 0;
     padding: 10px;
     font-size: 14px;
-    font-family: 'mastodon-font-monospace', monospace;
+    font-family: $font-monospace, monospace;
   }
 
   button {
diff --git a/app/javascript/flavours/glitch/styles/mastodon-light/diff.scss b/app/javascript/flavours/glitch/styles/mastodon-light/diff.scss
index aba8baf70..55a8983e5 100644
--- a/app/javascript/flavours/glitch/styles/mastodon-light/diff.scss
+++ b/app/javascript/flavours/glitch/styles/mastodon-light/diff.scss
@@ -84,7 +84,8 @@
 
 // Change the background colors of media and video spoilers
 .media-spoiler,
-.video-player__spoiler {
+.video-player__spoiler,
+.account-gallery__item a {
   background: $ui-base-color;
 }
 
diff --git a/app/javascript/flavours/glitch/styles/rtl.scss b/app/javascript/flavours/glitch/styles/rtl.scss
index b8c0efad8..70aaa5bb1 100644
--- a/app/javascript/flavours/glitch/styles/rtl.scss
+++ b/app/javascript/flavours/glitch/styles/rtl.scss
@@ -145,6 +145,19 @@ body.rtl {
     margin-right: 6px;
   }
 
+  .status__action-bar {
+
+    &__counter {
+      margin-right: 0;
+      margin-left: 11px;
+
+      .status__action-bar-button {
+        margin-right: 0;
+        margin-left: 4px;
+      }
+    }
+  }
+
   .status__action-bar-button {
     float: right;
     margin-right: 0;
@@ -285,4 +298,18 @@ body.rtl {
       }
     }
   }
+
+  .public-layout {
+    .header {
+      .nav-button {
+        margin-left: 8px;
+        margin-right: 0;
+      }
+    }
+
+    .public-account-header__tabs {
+      margin-left: 0;
+      margin-right: 20px;
+    }
+  }
 }
diff --git a/app/javascript/flavours/glitch/styles/tables.scss b/app/javascript/flavours/glitch/styles/tables.scss
index fa876e603..9fd0b95bb 100644
--- a/app/javascript/flavours/glitch/styles/tables.scss
+++ b/app/javascript/flavours/glitch/styles/tables.scss
@@ -90,7 +90,7 @@
 }
 
 samp {
-  font-family: 'mastodon-font-monospace', monospace;
+  font-family: $font-monospace, monospace;
 }
 
 button.table-action-link {
diff --git a/app/javascript/flavours/glitch/styles/variables.scss b/app/javascript/flavours/glitch/styles/variables.scss
index 715ecf98f..1ed1a5778 100644
--- a/app/javascript/flavours/glitch/styles/variables.scss
+++ b/app/javascript/flavours/glitch/styles/variables.scss
@@ -51,6 +51,10 @@ $media-modal-media-max-height: 80%;
 
 $no-gap-breakpoint: 415px;
 
+$font-sans-serif: 'mastodon-font-sans-serif' !default;
+$font-display: 'mastodon-font-display' !default;
+$font-monospace: 'mastodon-font-monospace' !default;
+
 // Avatar border size (8% default, 100% for rounded avatars)
 $ui-avatar-border-size: 8%;
 
diff --git a/app/javascript/flavours/glitch/util/content_warning.js b/app/javascript/flavours/glitch/util/content_warning.js
index 29e221c8e..5e874a49c 100644
--- a/app/javascript/flavours/glitch/util/content_warning.js
+++ b/app/javascript/flavours/glitch/util/content_warning.js
@@ -4,6 +4,11 @@ export function autoUnfoldCW (settings, status) {
   }
 
   const rawRegex = settings.getIn(['content_warnings', 'filter']);
+
+  if (!rawRegex) {
+    return true;
+  }
+
   let regex      = null;
 
   try {
diff --git a/app/javascript/flavours/glitch/util/initial_state.js b/app/javascript/flavours/glitch/util/initial_state.js
index fdf004527..0aaf65904 100644
--- a/app/javascript/flavours/glitch/util/initial_state.js
+++ b/app/javascript/flavours/glitch/util/initial_state.js
@@ -14,6 +14,7 @@ const getMeta = (prop) => initialState && initialState.meta && initialState.meta
 export const reduceMotion = getMeta('reduce_motion');
 export const autoPlayGif = getMeta('auto_play_gif');
 export const displaySensitiveMedia = getMeta('display_sensitive_media');
+export const displayMedia = getMeta('display_media') || (getMeta('display_sensitive_media') ? 'show_all' : 'default');
 export const unfollowModal = getMeta('unfollow_modal');
 export const boostModal = getMeta('boost_modal');
 export const favouriteModal = getMeta('favourite_modal');
diff --git a/app/javascript/mastodon/actions/conversations.js b/app/javascript/mastodon/actions/conversations.js
new file mode 100644
index 000000000..3840d23ca
--- /dev/null
+++ b/app/javascript/mastodon/actions/conversations.js
@@ -0,0 +1,59 @@
+import api, { getLinks } from '../api';
+import {
+  importFetchedAccounts,
+  importFetchedStatuses,
+  importFetchedStatus,
+} from './importer';
+
+export const CONVERSATIONS_FETCH_REQUEST = 'CONVERSATIONS_FETCH_REQUEST';
+export const CONVERSATIONS_FETCH_SUCCESS = 'CONVERSATIONS_FETCH_SUCCESS';
+export const CONVERSATIONS_FETCH_FAIL    = 'CONVERSATIONS_FETCH_FAIL';
+export const CONVERSATIONS_UPDATE        = 'CONVERSATIONS_UPDATE';
+
+export const expandConversations = ({ maxId } = {}) => (dispatch, getState) => {
+  dispatch(expandConversationsRequest());
+
+  const params = { max_id: maxId };
+
+  if (!maxId) {
+    params.since_id = getState().getIn(['conversations', 0, 'last_status']);
+  }
+
+  api(getState).get('/api/v1/conversations', { params })
+    .then(response => {
+      const next = getLinks(response).refs.find(link => link.rel === 'next');
+
+      dispatch(importFetchedAccounts(response.data.reduce((aggr, item) => aggr.concat(item.accounts), [])));
+      dispatch(importFetchedStatuses(response.data.map(item => item.last_status).filter(x => !!x)));
+      dispatch(expandConversationsSuccess(response.data, next ? next.uri : null));
+    })
+    .catch(err => dispatch(expandConversationsFail(err)));
+};
+
+export const expandConversationsRequest = () => ({
+  type: CONVERSATIONS_FETCH_REQUEST,
+});
+
+export const expandConversationsSuccess = (conversations, next) => ({
+  type: CONVERSATIONS_FETCH_SUCCESS,
+  conversations,
+  next,
+});
+
+export const expandConversationsFail = error => ({
+  type: CONVERSATIONS_FETCH_FAIL,
+  error,
+});
+
+export const updateConversations = conversation => dispatch => {
+  dispatch(importFetchedAccounts(conversation.accounts));
+
+  if (conversation.last_status) {
+    dispatch(importFetchedStatus(conversation.last_status));
+  }
+
+  dispatch({
+    type: CONVERSATIONS_UPDATE,
+    conversation,
+  });
+};
diff --git a/app/javascript/mastodon/actions/importer/normalizer.js b/app/javascript/mastodon/actions/importer/normalizer.js
index 10a39e050..a2af3222e 100644
--- a/app/javascript/mastodon/actions/importer/normalizer.js
+++ b/app/javascript/mastodon/actions/importer/normalizer.js
@@ -1,6 +1,7 @@
 import escapeTextContentForBrowser from 'escape-html';
 import emojify from '../../features/emoji/emoji';
 import { unescapeHTML } from '../../utils/html';
+import { expandSpoilers } from '../../initial_state';
 
 const domParser = new DOMParser();
 
@@ -57,7 +58,7 @@ export function normalizeStatus(status, normalOldStatus) {
     normalStatus.search_index = domParser.parseFromString(searchContent, 'text/html').documentElement.textContent;
     normalStatus.contentHtml  = emojify(normalStatus.content, emojiMap);
     normalStatus.spoilerHtml  = emojify(escapeTextContentForBrowser(spoilerText), emojiMap);
-    normalStatus.hidden       = spoilerText.length > 0 || normalStatus.sensitive;
+    normalStatus.hidden       = expandSpoilers ? false : spoilerText.length > 0 || normalStatus.sensitive;
   }
 
   return normalStatus;
diff --git a/app/javascript/mastodon/actions/streaming.js b/app/javascript/mastodon/actions/streaming.js
index 32fc67e67..8cf055540 100644
--- a/app/javascript/mastodon/actions/streaming.js
+++ b/app/javascript/mastodon/actions/streaming.js
@@ -6,6 +6,7 @@ import {
   disconnectTimeline,
 } from './timelines';
 import { updateNotifications, expandNotifications } from './notifications';
+import { updateConversations } from './conversations';
 import { fetchFilters } from './filters';
 import { getLocale } from '../locales';
 
@@ -31,6 +32,9 @@ export function connectTimelineStream (timelineId, path, pollingRefresh = null)
         case 'notification':
           dispatch(updateNotifications(JSON.parse(data.payload), messages, locale));
           break;
+        case 'conversation':
+          dispatch(updateConversations(JSON.parse(data.payload)));
+          break;
         case 'filters_changed':
           dispatch(fetchFilters());
           break;
diff --git a/app/javascript/mastodon/actions/timelines.js b/app/javascript/mastodon/actions/timelines.js
index e8fd441e1..c4fc6448c 100644
--- a/app/javascript/mastodon/actions/timelines.js
+++ b/app/javascript/mastodon/actions/timelines.js
@@ -76,7 +76,6 @@ export function expandTimeline(timelineId, path, params = {}, done = noOp) {
 export const expandHomeTimeline            = ({ maxId } = {}, done = noOp) => expandTimeline('home', '/api/v1/timelines/home', { max_id: maxId }, done);
 export const expandPublicTimeline          = ({ maxId, onlyMedia } = {}, done = noOp) => expandTimeline(`public${onlyMedia ? ':media' : ''}`, '/api/v1/timelines/public', { max_id: maxId, only_media: !!onlyMedia }, done);
 export const expandCommunityTimeline       = ({ maxId, onlyMedia } = {}, done = noOp) => expandTimeline(`community${onlyMedia ? ':media' : ''}`, '/api/v1/timelines/public', { local: true, max_id: maxId, only_media: !!onlyMedia }, done);
-export const expandDirectTimeline          = ({ maxId } = {}, done = noOp) => expandTimeline('direct', '/api/v1/timelines/direct', { max_id: maxId }, done);
 export const expandAccountTimeline         = (accountId, { maxId, withReplies } = {}) => expandTimeline(`account:${accountId}${withReplies ? ':with_replies' : ''}`, `/api/v1/accounts/${accountId}/statuses`, { exclude_replies: !withReplies, max_id: maxId });
 export const expandAccountFeaturedTimeline = accountId => expandTimeline(`account:${accountId}:pinned`, `/api/v1/accounts/${accountId}/statuses`, { pinned: true });
 export const expandAccountMediaTimeline    = (accountId, { maxId } = {}) => expandTimeline(`account:${accountId}:media`, `/api/v1/accounts/${accountId}/statuses`, { max_id: maxId, only_media: true });
diff --git a/app/javascript/mastodon/components/__tests__/__snapshots__/autosuggest_emoji-test.js.snap b/app/javascript/mastodon/components/__tests__/__snapshots__/autosuggest_emoji-test.js.snap
new file mode 100644
index 000000000..1c3727848
--- /dev/null
+++ b/app/javascript/mastodon/components/__tests__/__snapshots__/autosuggest_emoji-test.js.snap
@@ -0,0 +1,27 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`<AutosuggestEmoji /> renders emoji with custom url 1`] = `
+<div
+  className="autosuggest-emoji"
+>
+  <img
+    alt="foobar"
+    className="emojione"
+    src="http://example.com/emoji.png"
+  />
+  :foobar:
+</div>
+`;
+
+exports[`<AutosuggestEmoji /> renders native emoji 1`] = `
+<div
+  className="autosuggest-emoji"
+>
+  <img
+    alt="💙"
+    className="emojione"
+    src="/emoji/1f499.svg"
+  />
+  :foobar:
+</div>
+`;
diff --git a/app/javascript/mastodon/components/__tests__/autosuggest_emoji-test.js b/app/javascript/mastodon/components/__tests__/autosuggest_emoji-test.js
new file mode 100644
index 000000000..05616e444
--- /dev/null
+++ b/app/javascript/mastodon/components/__tests__/autosuggest_emoji-test.js
@@ -0,0 +1,29 @@
+import React from 'react';
+import renderer from 'react-test-renderer';
+import AutosuggestEmoji from '../autosuggest_emoji';
+
+describe('<AutosuggestEmoji />', () => {
+  it('renders native emoji', () => {
+    const emoji = {
+      native: '💙',
+      colons: ':foobar:',
+    };
+    const component = renderer.create(<AutosuggestEmoji emoji={emoji} />);
+    const tree      = component.toJSON();
+
+    expect(tree).toMatchSnapshot();
+  });
+
+  it('renders emoji with custom url', () => {
+    const emoji = {
+      custom: true,
+      imageUrl: 'http://example.com/emoji.png',
+      native: 'foobar',
+      colons: ':foobar:',
+    };
+    const component = renderer.create(<AutosuggestEmoji emoji={emoji} />);
+    const tree      = component.toJSON();
+
+    expect(tree).toMatchSnapshot();
+  });
+});
diff --git a/app/javascript/mastodon/components/display_name.js b/app/javascript/mastodon/components/display_name.js
index a1c56ae35..c3a9ab921 100644
--- a/app/javascript/mastodon/components/display_name.js
+++ b/app/javascript/mastodon/components/display_name.js
@@ -1,18 +1,25 @@
 import React from 'react';
 import ImmutablePropTypes from 'react-immutable-proptypes';
+import PropTypes from 'prop-types';
 
 export default class DisplayName extends React.PureComponent {
 
   static propTypes = {
     account: ImmutablePropTypes.map.isRequired,
+    withAcct: PropTypes.bool,
+  };
+
+  static defaultProps = {
+    withAcct: true,
   };
 
   render () {
-    const displayNameHtml = { __html: this.props.account.get('display_name_html') };
+    const { account, withAcct } = this.props;
+    const displayNameHtml = { __html: account.get('display_name_html') };
 
     return (
       <span className='display-name'>
-        <bdi><strong className='display-name__html' dangerouslySetInnerHTML={displayNameHtml} /></bdi> <span className='display-name__account'>@{this.props.account.get('acct')}</span>
+        <bdi><strong className='display-name__html' dangerouslySetInnerHTML={displayNameHtml} /></bdi> {withAcct && <span className='display-name__account'>@{account.get('acct')}</span>}
       </span>
     );
   }
diff --git a/app/javascript/mastodon/components/media_gallery.js b/app/javascript/mastodon/components/media_gallery.js
index a1785196f..ed0e4ff1b 100644
--- a/app/javascript/mastodon/components/media_gallery.js
+++ b/app/javascript/mastodon/components/media_gallery.js
@@ -6,7 +6,7 @@ import IconButton from './icon_button';
 import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
 import { isIOS } from '../is_mobile';
 import classNames from 'classnames';
-import { autoPlayGif, displaySensitiveMedia } from '../initial_state';
+import { autoPlayGif, displayMedia } from '../initial_state';
 
 const messages = defineMessages({
   toggle_visible: { id: 'media_gallery.toggle_visible', defaultMessage: 'Toggle visibility' },
@@ -197,7 +197,7 @@ class MediaGallery extends React.PureComponent {
   };
 
   state = {
-    visible: !this.props.sensitive || displaySensitiveMedia,
+    visible: displayMedia !== 'hide_all' && !this.props.sensitive || displayMedia === 'show_all',
   };
 
   componentWillReceiveProps (nextProps) {
diff --git a/app/javascript/mastodon/components/status.js b/app/javascript/mastodon/components/status.js
index 6c595c712..90c689a75 100644
--- a/app/javascript/mastodon/components/status.js
+++ b/app/javascript/mastodon/components/status.js
@@ -285,7 +285,7 @@ class Status extends ImmutablePureComponent {
               </a>
             </div>
 
-            <StatusContent status={status} onClick={this.handleClick} expanded={!status.get('hidden')} onExpandedToggle={this.handleExpandedToggle} />
+            <StatusContent status={status} onClick={this.handleClick} expanded={!status.get('hidden')} onExpandedToggle={this.handleExpandedToggle} collapsable />
 
             {media}
 
diff --git a/app/javascript/mastodon/components/status_content.js b/app/javascript/mastodon/components/status_content.js
index 81013747e..eda7d6ac3 100644
--- a/app/javascript/mastodon/components/status_content.js
+++ b/app/javascript/mastodon/components/status_content.js
@@ -6,6 +6,8 @@ import { FormattedMessage } from 'react-intl';
 import Permalink from './permalink';
 import classnames from 'classnames';
 
+const MAX_HEIGHT = 642; // 20px * 32 (+ 2px padding at the top)
+
 export default class StatusContent extends React.PureComponent {
 
   static contextTypes = {
@@ -17,10 +19,12 @@ export default class StatusContent extends React.PureComponent {
     expanded: PropTypes.bool,
     onExpandedToggle: PropTypes.func,
     onClick: PropTypes.func,
+    collapsable: PropTypes.bool,
   };
 
   state = {
     hidden: true,
+    collapsed: null, //  `collapsed: null` indicates that an element doesn't need collapsing, while `true` or `false` indicates that it does (and is/isn't).
   };
 
   _updateStatusLinks () {
@@ -53,6 +57,16 @@ export default class StatusContent extends React.PureComponent {
       link.setAttribute('target', '_blank');
       link.setAttribute('rel', 'noopener');
     }
+
+    if (
+      this.props.collapsable
+      && this.props.onClick
+      && this.state.collapsed === null
+      && node.clientHeight > MAX_HEIGHT
+      && this.props.status.get('spoiler_text').length === 0
+    ) {
+      this.setState({ collapsed: true });
+    }
   }
 
   componentDidMount () {
@@ -113,6 +127,11 @@ export default class StatusContent extends React.PureComponent {
     }
   }
 
+  handleCollapsedClick = (e) => {
+    e.preventDefault();
+    this.setState({ collapsed: !this.state.collapsed });
+  }
+
   setRef = (c) => {
     this.node = c;
   }
@@ -132,12 +151,19 @@ export default class StatusContent extends React.PureComponent {
     const classNames = classnames('status__content', {
       'status__content--with-action': this.props.onClick && this.context.router,
       'status__content--with-spoiler': status.get('spoiler_text').length > 0,
+      'status__content--collapsed': this.state.collapsed === true,
     });
 
     if (isRtl(status.get('search_index'))) {
       directionStyle.direction = 'rtl';
     }
 
+    const readMoreButton = (
+      <button className='status__content__read-more-button' onClick={this.props.onClick}>
+        <FormattedMessage id='status.read_more' defaultMessage='Read more' /><i className='fa fa-fw fa-angle-right' />
+      </button>
+    );
+
     if (status.get('spoiler_text').length > 0) {
       let mentionsPlaceholder = '';
 
@@ -167,17 +193,23 @@ export default class StatusContent extends React.PureComponent {
         </div>
       );
     } else if (this.props.onClick) {
-      return (
+      const output = [
         <div
           ref={this.setRef}
           tabIndex='0'
           className={classNames}
           style={directionStyle}
+          dangerouslySetInnerHTML={content}
           onMouseDown={this.handleMouseDown}
           onMouseUp={this.handleMouseUp}
-          dangerouslySetInnerHTML={content}
-        />
-      );
+        />,
+      ];
+
+      if (this.state.collapsed) {
+        output.push(readMoreButton);
+      }
+
+      return output;
     } else {
       return (
         <div
diff --git a/app/javascript/mastodon/containers/status_container.js b/app/javascript/mastodon/containers/status_container.js
index bbc0d5e96..b3555c76e 100644
--- a/app/javascript/mastodon/containers/status_container.js
+++ b/app/javascript/mastodon/containers/status_container.js
@@ -36,6 +36,8 @@ const messages = defineMessages({
   redraftConfirm: { id: 'confirmations.redraft.confirm', defaultMessage: 'Delete & redraft' },
   redraftMessage: { id: 'confirmations.redraft.message', defaultMessage: 'Are you sure you want to delete this status and re-draft it? Favourites and boosts will be lost, and replies to the original post will be orphaned.' },
   blockConfirm: { id: 'confirmations.block.confirm', defaultMessage: 'Block' },
+  replyConfirm: { id: 'confirmations.reply.confirm', defaultMessage: 'Reply' },
+  replyMessage: { id: 'confirmations.reply.message', defaultMessage: 'Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?' },
 });
 
 const makeMapStateToProps = () => {
@@ -51,7 +53,18 @@ const makeMapStateToProps = () => {
 const mapDispatchToProps = (dispatch, { intl }) => ({
 
   onReply (status, router) {
-    dispatch(replyCompose(status, router));
+    dispatch((_, getState) => {
+      let state = getState();
+      if (state.getIn(['compose', 'text']).trim().length !== 0) {
+        dispatch(openModal('CONFIRM', {
+          message: intl.formatMessage(messages.replyMessage),
+          confirm: intl.formatMessage(messages.replyConfirm),
+          onConfirm: () => dispatch(replyCompose(status, router)),
+        }));
+      } else {
+        dispatch(replyCompose(status, router));
+      }
+    });
   },
 
   onModalReblog (status) {
diff --git a/app/javascript/mastodon/features/account_gallery/components/media_item.js b/app/javascript/mastodon/features/account_gallery/components/media_item.js
index f7a802dc7..7c330c430 100644
--- a/app/javascript/mastodon/features/account_gallery/components/media_item.js
+++ b/app/javascript/mastodon/features/account_gallery/components/media_item.js
@@ -2,7 +2,7 @@ import React from 'react';
 import ImmutablePropTypes from 'react-immutable-proptypes';
 import ImmutablePureComponent from 'react-immutable-pure-component';
 import Permalink from '../../../components/permalink';
-import { displaySensitiveMedia } from '../../../initial_state';
+import { displayMedia } from '../../../initial_state';
 
 export default class MediaItem extends ImmutablePureComponent {
 
@@ -11,7 +11,7 @@ export default class MediaItem extends ImmutablePureComponent {
   };
 
   state = {
-    visible: !this.props.media.getIn(['status', 'sensitive']) || displaySensitiveMedia,
+    visible: displayMedia !== 'hide_all' && !this.props.media.getIn(['status', 'sensitive']) || displayMedia === 'show_all',
   };
 
   handleClick = () => {
diff --git a/app/javascript/mastodon/features/direct_timeline/components/conversation.js b/app/javascript/mastodon/features/direct_timeline/components/conversation.js
new file mode 100644
index 000000000..f9a8d4f72
--- /dev/null
+++ b/app/javascript/mastodon/features/direct_timeline/components/conversation.js
@@ -0,0 +1,85 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+import StatusContent from '../../../components/status_content';
+import RelativeTimestamp from '../../../components/relative_timestamp';
+import DisplayName from '../../../components/display_name';
+import Avatar from '../../../components/avatar';
+import AttachmentList from '../../../components/attachment_list';
+import { HotKeys } from 'react-hotkeys';
+
+export default class Conversation extends ImmutablePureComponent {
+
+  static contextTypes = {
+    router: PropTypes.object,
+  };
+
+  static propTypes = {
+    conversationId: PropTypes.string.isRequired,
+    accounts: ImmutablePropTypes.list.isRequired,
+    lastStatus: ImmutablePropTypes.map.isRequired,
+    onMoveUp: PropTypes.func,
+    onMoveDown: PropTypes.func,
+  };
+
+  handleClick = () => {
+    if (!this.context.router) {
+      return;
+    }
+
+    const { lastStatus } = this.props;
+    this.context.router.history.push(`/statuses/${lastStatus.get('id')}`);
+  }
+
+  handleHotkeyMoveUp = () => {
+    this.props.onMoveUp(this.props.conversationId);
+  }
+
+  handleHotkeyMoveDown = () => {
+    this.props.onMoveDown(this.props.conversationId);
+  }
+
+  render () {
+    const { accounts, lastStatus, lastAccount } = this.props;
+
+    if (lastStatus === null) {
+      return null;
+    }
+
+    const handlers = {
+      moveDown: this.handleHotkeyMoveDown,
+      moveUp: this.handleHotkeyMoveUp,
+      open: this.handleClick,
+    };
+
+    let media;
+
+    if (lastStatus.get('media_attachments').size > 0) {
+      media = <AttachmentList compact media={lastStatus.get('media_attachments')} />;
+    }
+
+    return (
+      <HotKeys handlers={handlers}>
+        <div className='conversation focusable' tabIndex='0' onClick={this.handleClick} role='button'>
+          <div className='conversation__header'>
+            <div className='conversation__avatars'>
+              <div>{accounts.map(account => <Avatar key={account.get('id')} size={36} account={account} />)}</div>
+            </div>
+
+            <div className='conversation__time'>
+              <RelativeTimestamp timestamp={lastStatus.get('created_at')} />
+              <br />
+              <DisplayName account={lastAccount} withAcct={false} />
+            </div>
+          </div>
+
+          <StatusContent status={lastStatus} onClick={this.handleClick} />
+
+          {media}
+        </div>
+      </HotKeys>
+    );
+  }
+
+}
diff --git a/app/javascript/mastodon/features/direct_timeline/components/conversations_list.js b/app/javascript/mastodon/features/direct_timeline/components/conversations_list.js
new file mode 100644
index 000000000..4684548e0
--- /dev/null
+++ b/app/javascript/mastodon/features/direct_timeline/components/conversations_list.js
@@ -0,0 +1,68 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+import ConversationContainer from '../containers/conversation_container';
+import ScrollableList from '../../../components/scrollable_list';
+import { debounce } from 'lodash';
+
+export default class ConversationsList extends ImmutablePureComponent {
+
+  static propTypes = {
+    conversationIds: ImmutablePropTypes.list.isRequired,
+    hasMore: PropTypes.bool,
+    isLoading: PropTypes.bool,
+    onLoadMore: PropTypes.func,
+    shouldUpdateScroll: PropTypes.func,
+  };
+
+  getCurrentIndex = id => this.props.conversationIds.indexOf(id)
+
+  handleMoveUp = id => {
+    const elementIndex = this.getCurrentIndex(id) - 1;
+    this._selectChild(elementIndex);
+  }
+
+  handleMoveDown = id => {
+    const elementIndex = this.getCurrentIndex(id) + 1;
+    this._selectChild(elementIndex);
+  }
+
+  _selectChild (index) {
+    const element = this.node.node.querySelector(`article:nth-of-type(${index + 1}) .focusable`);
+
+    if (element) {
+      element.focus();
+    }
+  }
+
+  setRef = c => {
+    this.node = c;
+  }
+
+  handleLoadOlder = debounce(() => {
+    const last = this.props.conversationIds.last();
+
+    if (last) {
+      this.props.onLoadMore(last);
+    }
+  }, 300, { leading: true })
+
+  render () {
+    const { conversationIds, onLoadMore, ...other } = this.props;
+
+    return (
+      <ScrollableList {...other} onLoadMore={onLoadMore && this.handleLoadOlder} scrollKey='direct' ref={this.setRef}>
+        {conversationIds.map(item => (
+          <ConversationContainer
+            key={item}
+            conversationId={item}
+            onMoveUp={this.handleMoveUp}
+            onMoveDown={this.handleMoveDown}
+          />
+        ))}
+      </ScrollableList>
+    );
+  }
+
+}
diff --git a/app/javascript/mastodon/features/direct_timeline/containers/conversation_container.js b/app/javascript/mastodon/features/direct_timeline/containers/conversation_container.js
new file mode 100644
index 000000000..4166ee2ac
--- /dev/null
+++ b/app/javascript/mastodon/features/direct_timeline/containers/conversation_container.js
@@ -0,0 +1,15 @@
+import { connect } from 'react-redux';
+import Conversation from '../components/conversation';
+
+const mapStateToProps = (state, { conversationId }) => {
+  const conversation = state.getIn(['conversations', 'items']).find(x => x.get('id') === conversationId);
+  const lastStatus   = state.getIn(['statuses', conversation.get('last_status')], null);
+
+  return {
+    accounts: conversation.get('accounts').map(accountId => state.getIn(['accounts', accountId], null)),
+    lastStatus,
+    lastAccount: lastStatus === null ? null : state.getIn(['accounts', lastStatus.get('account')], null),
+  };
+};
+
+export default connect(mapStateToProps)(Conversation);
diff --git a/app/javascript/mastodon/features/direct_timeline/containers/conversations_list_container.js b/app/javascript/mastodon/features/direct_timeline/containers/conversations_list_container.js
new file mode 100644
index 000000000..81ea812ad
--- /dev/null
+++ b/app/javascript/mastodon/features/direct_timeline/containers/conversations_list_container.js
@@ -0,0 +1,15 @@
+import { connect } from 'react-redux';
+import ConversationsList from '../components/conversations_list';
+import { expandConversations } from '../../../actions/conversations';
+
+const mapStateToProps = state => ({
+  conversationIds: state.getIn(['conversations', 'items']).map(x => x.get('id')),
+  isLoading: state.getIn(['conversations', 'isLoading'], true),
+  hasMore: state.getIn(['conversations', 'hasMore'], false),
+});
+
+const mapDispatchToProps = dispatch => ({
+  onLoadMore: maxId => dispatch(expandConversations({ maxId })),
+});
+
+export default connect(mapStateToProps, mapDispatchToProps)(ConversationsList);
diff --git a/app/javascript/mastodon/features/direct_timeline/index.js b/app/javascript/mastodon/features/direct_timeline/index.js
index 3c7e2d007..41ec73d98 100644
--- a/app/javascript/mastodon/features/direct_timeline/index.js
+++ b/app/javascript/mastodon/features/direct_timeline/index.js
@@ -1,23 +1,19 @@
 import React from 'react';
 import { connect } from 'react-redux';
 import PropTypes from 'prop-types';
-import StatusListContainer from '../ui/containers/status_list_container';
 import Column from '../../components/column';
 import ColumnHeader from '../../components/column_header';
-import { expandDirectTimeline } from '../../actions/timelines';
+import { expandConversations } from '../../actions/conversations';
 import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
 import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
 import { connectDirectStream } from '../../actions/streaming';
+import ConversationsListContainer from './containers/conversations_list_container';
 
 const messages = defineMessages({
   title: { id: 'column.direct', defaultMessage: 'Direct messages' },
 });
 
-const mapStateToProps = state => ({
-  hasUnread: state.getIn(['timelines', 'direct', 'unread']) > 0,
-});
-
-export default @connect(mapStateToProps)
+export default @connect()
 @injectIntl
 class DirectTimeline extends React.PureComponent {
 
@@ -52,7 +48,7 @@ class DirectTimeline extends React.PureComponent {
   componentDidMount () {
     const { dispatch } = this.props;
 
-    dispatch(expandDirectTimeline());
+    dispatch(expandConversations());
     this.disconnect = dispatch(connectDirectStream());
   }
 
@@ -68,11 +64,11 @@ class DirectTimeline extends React.PureComponent {
   }
 
   handleLoadMore = maxId => {
-    this.props.dispatch(expandDirectTimeline({ maxId }));
+    this.props.dispatch(expandConversations({ maxId }));
   }
 
   render () {
-    const { intl, shouldUpdateScroll, hasUnread, columnId, multiColumn } = this.props;
+    const { intl, hasUnread, columnId, multiColumn, shouldUpdateScroll } = this.props;
     const pinned = !!columnId;
 
     return (
@@ -88,7 +84,7 @@ class DirectTimeline extends React.PureComponent {
           multiColumn={multiColumn}
         />
 
-        <StatusListContainer
+        <ConversationsListContainer
           trackScroll={!pinned}
           scrollKey={`direct_timeline-${columnId}`}
           timelineId='direct'
diff --git a/app/javascript/mastodon/features/getting_started/index.js b/app/javascript/mastodon/features/getting_started/index.js
index d5ba5cbce..0fd9badac 100644
--- a/app/javascript/mastodon/features/getting_started/index.js
+++ b/app/javascript/mastodon/features/getting_started/index.js
@@ -144,7 +144,7 @@ class GettingStarted extends ImmutablePureComponent {
               <li><a href='https://joinmastodon.org/apps' target='_blank'><FormattedMessage id='navigation_bar.apps' defaultMessage='Mobile apps' /></a> · </li>
               <li><a href='/terms' target='_blank'><FormattedMessage id='getting_started.terms' defaultMessage='Terms of service' /></a> · </li>
               <li><a href='/settings/applications' target='_blank'><FormattedMessage id='getting_started.developers' defaultMessage='Developers' /></a> · </li>
-              <li><a href='https://github.com/tootsuite/documentation#documentation' target='_blank'><FormattedMessage id='getting_started.documentation' defaultMessage='Documentation' /></a> · </li>
+              <li><a href='https://docs.joinmastodon.org' target='_blank'><FormattedMessage id='getting_started.documentation' defaultMessage='Documentation' /></a> · </li>
               <li><a href='/auth/sign_out' data-method='delete'><FormattedMessage id='navigation_bar.logout' defaultMessage='Logout' /></a></li>
             </ul>
 
diff --git a/app/javascript/mastodon/features/notifications/components/column_settings.js b/app/javascript/mastodon/features/notifications/components/column_settings.js
index d9638aaf3..fcdf5c6e6 100644
--- a/app/javascript/mastodon/features/notifications/components/column_settings.js
+++ b/app/javascript/mastodon/features/notifications/components/column_settings.js
@@ -27,7 +27,6 @@ export default class ColumnSettings extends React.PureComponent {
 
     const showPushSettings = pushSettings.get('browserSupport') && pushSettings.get('isSubscribed');
     const pushStr = showPushSettings && <FormattedMessage id='notifications.column_settings.push' defaultMessage='Push notifications' />;
-    const pushMeta = showPushSettings && <FormattedMessage id='notifications.column_settings.push_meta' defaultMessage='This device' />;
 
     return (
       <div>
@@ -40,7 +39,7 @@ export default class ColumnSettings extends React.PureComponent {
 
           <div className='column-settings__row'>
             <SettingToggle prefix='notifications_desktop' settings={settings} settingPath={['alerts', 'follow']} onChange={onChange} label={alertStr} />
-            {showPushSettings && <SettingToggle prefix='notifications_push' settings={pushSettings} settingPath={['alerts', 'follow']} meta={pushMeta} onChange={this.onPushChange} label={pushStr} />}
+            {showPushSettings && <SettingToggle prefix='notifications_push' settings={pushSettings} settingPath={['alerts', 'follow']} onChange={this.onPushChange} label={pushStr} />}
             <SettingToggle prefix='notifications' settings={settings} settingPath={['shows', 'follow']} onChange={onChange} label={showStr} />
             <SettingToggle prefix='notifications' settings={settings} settingPath={['sounds', 'follow']} onChange={onChange} label={soundStr} />
           </div>
@@ -51,7 +50,7 @@ export default class ColumnSettings extends React.PureComponent {
 
           <div className='column-settings__row'>
             <SettingToggle prefix='notifications_desktop' settings={settings} settingPath={['alerts', 'favourite']} onChange={onChange} label={alertStr} />
-            {showPushSettings && <SettingToggle prefix='notifications_push' settings={pushSettings} settingPath={['alerts', 'favourite']} meta={pushMeta} onChange={this.onPushChange} label={pushStr} />}
+            {showPushSettings && <SettingToggle prefix='notifications_push' settings={pushSettings} settingPath={['alerts', 'favourite']} onChange={this.onPushChange} label={pushStr} />}
             <SettingToggle prefix='notifications' settings={settings} settingPath={['shows', 'favourite']} onChange={onChange} label={showStr} />
             <SettingToggle prefix='notifications' settings={settings} settingPath={['sounds', 'favourite']} onChange={onChange} label={soundStr} />
           </div>
@@ -62,7 +61,7 @@ export default class ColumnSettings extends React.PureComponent {
 
           <div className='column-settings__row'>
             <SettingToggle prefix='notifications_desktop' settings={settings} settingPath={['alerts', 'mention']} onChange={onChange} label={alertStr} />
-            {showPushSettings && <SettingToggle prefix='notifications_push' settings={pushSettings} settingPath={['alerts', 'mention']} meta={pushMeta} onChange={this.onPushChange} label={pushStr} />}
+            {showPushSettings && <SettingToggle prefix='notifications_push' settings={pushSettings} settingPath={['alerts', 'mention']} onChange={this.onPushChange} label={pushStr} />}
             <SettingToggle prefix='notifications' settings={settings} settingPath={['shows', 'mention']} onChange={onChange} label={showStr} />
             <SettingToggle prefix='notifications' settings={settings} settingPath={['sounds', 'mention']} onChange={onChange} label={soundStr} />
           </div>
@@ -73,7 +72,7 @@ export default class ColumnSettings extends React.PureComponent {
 
           <div className='column-settings__row'>
             <SettingToggle prefix='notifications_desktop' settings={settings} settingPath={['alerts', 'reblog']} onChange={onChange} label={alertStr} />
-            {showPushSettings && <SettingToggle prefix='notifications_push' settings={pushSettings} settingPath={['alerts', 'reblog']} meta={pushMeta} onChange={this.onPushChange} label={pushStr} />}
+            {showPushSettings && <SettingToggle prefix='notifications_push' settings={pushSettings} settingPath={['alerts', 'reblog']} onChange={this.onPushChange} label={pushStr} />}
             <SettingToggle prefix='notifications' settings={settings} settingPath={['shows', 'reblog']} onChange={onChange} label={showStr} />
             <SettingToggle prefix='notifications' settings={settings} settingPath={['sounds', 'reblog']} onChange={onChange} label={soundStr} />
           </div>
diff --git a/app/javascript/mastodon/features/notifications/components/notification.js b/app/javascript/mastodon/features/notifications/components/notification.js
index ed4a44ca6..8df6830c5 100644
--- a/app/javascript/mastodon/features/notifications/components/notification.js
+++ b/app/javascript/mastodon/features/notifications/components/notification.js
@@ -85,8 +85,9 @@ class Notification extends ImmutablePureComponent {
             <div className='notification__favourite-icon-wrapper'>
               <i className='fa fa-fw fa-user-plus' />
             </div>
-
-            <FormattedMessage id='notification.follow' defaultMessage='{name} followed you' values={{ name: link }} />
+            <span title={notification.get('created_at')}>
+              <FormattedMessage id='notification.follow' defaultMessage='{name} followed you' values={{ name: link }} />
+            </span>
           </div>
 
           <AccountContainer id={account.get('id')} withNote={false} hidden={this.props.hidden} />
diff --git a/app/javascript/mastodon/features/notifications/components/setting_toggle.js b/app/javascript/mastodon/features/notifications/components/setting_toggle.js
index ac2211e48..7aec16d2e 100644
--- a/app/javascript/mastodon/features/notifications/components/setting_toggle.js
+++ b/app/javascript/mastodon/features/notifications/components/setting_toggle.js
@@ -10,7 +10,6 @@ export default class SettingToggle extends React.PureComponent {
     settings: ImmutablePropTypes.map.isRequired,
     settingPath: PropTypes.array.isRequired,
     label: PropTypes.node.isRequired,
-    meta: PropTypes.node,
     onChange: PropTypes.func.isRequired,
   }
 
@@ -19,14 +18,13 @@ export default class SettingToggle extends React.PureComponent {
   }
 
   render () {
-    const { prefix, settings, settingPath, label, meta } = this.props;
+    const { prefix, settings, settingPath, label } = this.props;
     const id = ['setting-toggle', prefix, ...settingPath].filter(Boolean).join('-');
 
     return (
       <div className='setting-toggle'>
         <Toggle id={id} checked={settings.getIn(settingPath)} onChange={this.onChange} onKeyDown={this.onKeyDown} />
         <label htmlFor={id} className='setting-toggle__label'>{label}</label>
-        {meta && <span className='setting-meta__label'>{meta}</span>}
       </div>
     );
   }
diff --git a/app/javascript/mastodon/features/status/index.js b/app/javascript/mastodon/features/status/index.js
index 7d1bc2ca4..2cd17b805 100644
--- a/app/javascript/mastodon/features/status/index.js
+++ b/app/javascript/mastodon/features/status/index.js
@@ -54,6 +54,8 @@ const messages = defineMessages({
   revealAll: { id: 'status.show_more_all', defaultMessage: 'Show more for all' },
   hideAll: { id: 'status.show_less_all', defaultMessage: 'Show less for all' },
   detailedStatus: { id: 'status.detailed_status', defaultMessage: 'Detailed conversation view' },
+  replyConfirm: { id: 'confirmations.reply.confirm', defaultMessage: 'Reply' },
+  replyMessage: { id: 'confirmations.reply.message', defaultMessage: 'Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?' },
 });
 
 const makeMapStateToProps = () => {
@@ -98,6 +100,7 @@ const makeMapStateToProps = () => {
       status,
       ancestorsIds,
       descendantsIds,
+      askReplyConfirmation: state.getIn(['compose', 'text']).trim().length !== 0,
     };
   };
 
@@ -119,6 +122,7 @@ class Status extends ImmutablePureComponent {
     ancestorsIds: ImmutablePropTypes.list,
     descendantsIds: ImmutablePropTypes.list,
     intl: PropTypes.object.isRequired,
+    askReplyConfirmation: PropTypes.bool,
   };
 
   state = {
@@ -157,7 +161,16 @@ class Status extends ImmutablePureComponent {
   }
 
   handleReplyClick = (status) => {
-    this.props.dispatch(replyCompose(status, this.context.router.history));
+    let { askReplyConfirmation, dispatch, intl } = this.props;
+    if (askReplyConfirmation) {
+      dispatch(openModal('CONFIRM', {
+        message: intl.formatMessage(messages.replyMessage),
+        confirm: intl.formatMessage(messages.replyConfirm),
+        onConfirm: () => dispatch(replyCompose(status, this.context.router.history)),
+      }));
+    } else {
+      dispatch(replyCompose(status, this.context.router.history));
+    }
   }
 
   handleModalReblog = (status) => {
diff --git a/app/javascript/mastodon/features/ui/components/onboarding_modal.js b/app/javascript/mastodon/features/ui/components/onboarding_modal.js
index 25241bcd0..4a5b249c9 100644
--- a/app/javascript/mastodon/features/ui/components/onboarding_modal.js
+++ b/app/javascript/mastodon/features/ui/components/onboarding_modal.js
@@ -160,7 +160,7 @@ const PageSix = ({ admin, domain }) => {
       <h1><FormattedMessage id='onboarding.page_six.almost_done' defaultMessage='Almost done...' /></h1>
       {adminSection}
       <p><FormattedMessage id='onboarding.page_six.github' defaultMessage='Mastodon is free open-source software. You can report bugs, request features, or contribute to the code on {github}.' values={{ github: <a href='https://github.com/tootsuite/mastodon' target='_blank' rel='noopener'>GitHub</a> }} /></p>
-      <p><FormattedMessage id='onboarding.page_six.apps_available' defaultMessage='There are {apps} available for iOS, Android and other platforms.' values={{ apps: <a href='https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/Apps.md' target='_blank' rel='noopener'><FormattedMessage id='onboarding.page_six.various_app' defaultMessage='mobile apps' /></a> }} /></p>
+      <p><FormattedMessage id='onboarding.page_six.apps_available' defaultMessage='There are {apps} available for iOS, Android and other platforms.' values={{ apps: <a href='https://joinmastodon.org/apps' target='_blank' rel='noopener'><FormattedMessage id='onboarding.page_six.various_app' defaultMessage='mobile apps' /></a> }} /></p>
       <p><em><FormattedMessage id='onboarding.page_six.appetoot' defaultMessage='Bon Appetoot!' /></em></p>
     </div>
   );
diff --git a/app/javascript/mastodon/features/ui/index.js b/app/javascript/mastodon/features/ui/index.js
index 2781c5d64..fb6f675f4 100644
--- a/app/javascript/mastodon/features/ui/index.js
+++ b/app/javascript/mastodon/features/ui/index.js
@@ -59,7 +59,8 @@ const messages = defineMessages({
 
 const mapStateToProps = state => ({
   isComposing: state.getIn(['compose', 'is_composing']),
-  hasComposingText: state.getIn(['compose', 'text']) !== '',
+  hasComposingText: state.getIn(['compose', 'text']).trim().length !== 0,
+  hasMediaAttachments: state.getIn(['compose', 'media_attachments']).size > 0,
   dropdownMenuIsOpen: state.getIn(['dropdown_menu', 'openId']) !== null,
 });
 
@@ -201,6 +202,7 @@ class UI extends React.PureComponent {
     children: PropTypes.node,
     isComposing: PropTypes.bool,
     hasComposingText: PropTypes.bool,
+    hasMediaAttachments: PropTypes.bool,
     location: PropTypes.object,
     intl: PropTypes.object.isRequired,
     dropdownMenuIsOpen: PropTypes.bool,
@@ -211,9 +213,9 @@ class UI extends React.PureComponent {
   };
 
   handleBeforeUnload = (e) => {
-    const { intl, isComposing, hasComposingText } = this.props;
+    const { intl, isComposing, hasComposingText, hasMediaAttachments } = this.props;
 
-    if (isComposing && hasComposingText) {
+    if (isComposing && (hasComposingText || hasMediaAttachments)) {
       // Setting returnValue to any string causes confirmation dialog.
       // Many browsers no longer display this text to users,
       // but we set user-friendly message for other browsers, e.g. Edge.
diff --git a/app/javascript/mastodon/features/video/index.js b/app/javascript/mastodon/features/video/index.js
index d17253957..67f7580b9 100644
--- a/app/javascript/mastodon/features/video/index.js
+++ b/app/javascript/mastodon/features/video/index.js
@@ -5,7 +5,7 @@ import { fromJS } from 'immutable';
 import { throttle } from 'lodash';
 import classNames from 'classnames';
 import { isFullscreen, requestFullscreen, exitFullscreen } from '../ui/util/fullscreen';
-import { displaySensitiveMedia } from '../../initial_state';
+import { displayMedia } from '../../initial_state';
 
 const messages = defineMessages({
   play: { id: 'video.play', defaultMessage: 'Play' },
@@ -111,7 +111,7 @@ class Video extends React.PureComponent {
     fullscreen: false,
     hovered: false,
     muted: false,
-    revealed: !this.props.sensitive || displaySensitiveMedia,
+    revealed: displayMedia !== 'hide_all' && !this.props.sensitive || displayMedia === 'show_all',
   };
 
   setPlayerRef = c => {
@@ -272,7 +272,7 @@ class Video extends React.PureComponent {
   }
 
   render () {
-    const { preview, src, inline, startTime, onOpenVideo, onCloseVideo, intl, alt, detailed } = this.props;
+    const { preview, src, inline, startTime, onOpenVideo, onCloseVideo, intl, alt, detailed, sensitive } = this.props;
     const { containerWidth, currentTime, duration, buffer, dragging, paused, fullscreen, hovered, muted, revealed } = this.state;
     const progress = (currentTime / duration) * 100;
     const playerStyle = {};
@@ -296,6 +296,13 @@ class Video extends React.PureComponent {
       preload = 'none';
     }
 
+    let warning;
+    if (sensitive) {
+      warning = <FormattedMessage id='status.sensitive_warning' defaultMessage='Sensitive content' />;
+    } else {
+      warning = <FormattedMessage id='status.media_hidden' defaultMessage='Media hidden' />;
+    }
+
     return (
       <div
         role='menuitem'
@@ -328,7 +335,7 @@ class Video extends React.PureComponent {
         />
 
         <button type='button' className={classNames('video-player__spoiler', { active: !revealed })} onClick={this.toggleReveal}>
-          <span className='video-player__spoiler__title'><FormattedMessage id='status.sensitive_warning' defaultMessage='Sensitive content' /></span>
+          <span className='video-player__spoiler__title'>{warning}</span>
           <span className='video-player__spoiler__subtitle'><FormattedMessage id='status.sensitive_toggle' defaultMessage='Click to view' /></span>
         </button>
 
diff --git a/app/javascript/mastodon/initial_state.js b/app/javascript/mastodon/initial_state.js
index 4c6922f9b..1d3d84f9d 100644
--- a/app/javascript/mastodon/initial_state.js
+++ b/app/javascript/mastodon/initial_state.js
@@ -5,7 +5,8 @@ const getMeta = (prop) => initialState && initialState.meta && initialState.meta
 
 export const reduceMotion = getMeta('reduce_motion');
 export const autoPlayGif = getMeta('auto_play_gif');
-export const displaySensitiveMedia = getMeta('display_sensitive_media');
+export const displayMedia = getMeta('display_media');
+export const expandSpoilers = getMeta('expand_spoilers');
 export const unfollowModal = getMeta('unfollow_modal');
 export const boostModal = getMeta('boost_modal');
 export const deleteModal = getMeta('delete_modal');
diff --git a/app/javascript/mastodon/locales/ar.json b/app/javascript/mastodon/locales/ar.json
index 56d78ae64..a74207db6 100644
--- a/app/javascript/mastodon/locales/ar.json
+++ b/app/javascript/mastodon/locales/ar.json
@@ -216,7 +216,6 @@
   "notifications.column_settings.follow": "متابعُون جُدُد :",
   "notifications.column_settings.mention": "الإشارات :",
   "notifications.column_settings.push": "الإخطارات المدفوعة",
-  "notifications.column_settings.push_meta": "هذا الجهاز",
   "notifications.column_settings.reblog": "الترقيّات:",
   "notifications.column_settings.show": "إعرِضها في عمود",
   "notifications.column_settings.sound": "أصدر صوتا",
diff --git a/app/javascript/mastodon/locales/ast.json b/app/javascript/mastodon/locales/ast.json
index e3cfabee3..147c5ad2a 100644
--- a/app/javascript/mastodon/locales/ast.json
+++ b/app/javascript/mastodon/locales/ast.json
@@ -216,7 +216,6 @@
   "notifications.column_settings.follow": "Siguidores nuevos:",
   "notifications.column_settings.mention": "Menciones:",
   "notifications.column_settings.push": "Push notifications",
-  "notifications.column_settings.push_meta": "Esti preséu",
   "notifications.column_settings.reblog": "Boosts:",
   "notifications.column_settings.show": "Amosar en columna",
   "notifications.column_settings.sound": "Reproducir soníu",
diff --git a/app/javascript/mastodon/locales/bg.json b/app/javascript/mastodon/locales/bg.json
index 5730c1354..756c33393 100644
--- a/app/javascript/mastodon/locales/bg.json
+++ b/app/javascript/mastodon/locales/bg.json
@@ -216,7 +216,6 @@
   "notifications.column_settings.follow": "Нови последователи:",
   "notifications.column_settings.mention": "Споменавания:",
   "notifications.column_settings.push": "Push notifications",
-  "notifications.column_settings.push_meta": "This device",
   "notifications.column_settings.reblog": "Споделяния:",
   "notifications.column_settings.show": "Покажи в колона",
   "notifications.column_settings.sound": "Play sound",
diff --git a/app/javascript/mastodon/locales/ca.json b/app/javascript/mastodon/locales/ca.json
index 0364d0bba..4701c9316 100644
--- a/app/javascript/mastodon/locales/ca.json
+++ b/app/javascript/mastodon/locales/ca.json
@@ -216,7 +216,6 @@
   "notifications.column_settings.follow": "Nous seguidors:",
   "notifications.column_settings.mention": "Mencions:",
   "notifications.column_settings.push": "Push notificacions",
-  "notifications.column_settings.push_meta": "Aquest dispositiu",
   "notifications.column_settings.reblog": "Impulsos:",
   "notifications.column_settings.show": "Mostrar en la columna",
   "notifications.column_settings.sound": "Reproduïr so",
diff --git a/app/javascript/mastodon/locales/co.json b/app/javascript/mastodon/locales/co.json
index 8bf083235..62976c98e 100644
--- a/app/javascript/mastodon/locales/co.json
+++ b/app/javascript/mastodon/locales/co.json
@@ -216,7 +216,6 @@
   "notifications.column_settings.follow": "Abbunati novi:",
   "notifications.column_settings.mention": "Minzione:",
   "notifications.column_settings.push": "Nutificazione Push",
-  "notifications.column_settings.push_meta": "Quess'apparechju",
   "notifications.column_settings.reblog": "Spartere:",
   "notifications.column_settings.show": "Mustrà indè a colonna",
   "notifications.column_settings.sound": "Sunà",
diff --git a/app/javascript/mastodon/locales/cs.json b/app/javascript/mastodon/locales/cs.json
index 46aa954e8..2c34fd34c 100644
--- a/app/javascript/mastodon/locales/cs.json
+++ b/app/javascript/mastodon/locales/cs.json
@@ -216,7 +216,6 @@
   "notifications.column_settings.follow": "Noví sledovatelé:",
   "notifications.column_settings.mention": "Zmínky:",
   "notifications.column_settings.push": "Push oznámení",
-  "notifications.column_settings.push_meta": "Toto zařízení",
   "notifications.column_settings.reblog": "Boosty:",
   "notifications.column_settings.show": "Zobrazit ve sloupci",
   "notifications.column_settings.sound": "Přehrát zvuk",
diff --git a/app/javascript/mastodon/locales/cy.json b/app/javascript/mastodon/locales/cy.json
index a2eea600a..90f3f8e30 100644
--- a/app/javascript/mastodon/locales/cy.json
+++ b/app/javascript/mastodon/locales/cy.json
@@ -1,337 +1,335 @@
 {
-  "account.badges.bot": "Bot",
-  "account.block": "Blociwch @{name}",
-  "account.block_domain": "Cuddiwch bopeth rhag {domain}",
-  "account.blocked": "Blociwyd",
-  "account.direct": "Neges breifat @{name}",
-  "account.disclaimer_full": "Gall y wybodaeth isod adlewyrchu darlun anghyflawn o broffil defnyddiwr.",
-  "account.domain_blocked": "Domain hidden",
-  "account.edit_profile": "Golygu proffil",
-  "account.endorse": "Feature on profile",
-  "account.follow": "Dilyn",
-  "account.followers": "Dilynwyr",
-  "account.followers.empty": "Nid oes neb yn dilyn y defnyddiwr hwn eto.",
-  "account.follows": "Yn dilyn",
-  "account.follows.empty": "Nid yw'r defnyddiwr hwn yn dilyn unrhyw un eto.",
-  "account.follows_you": "Yn eich dilyn chi",
-  "account.hide_reblogs": "Hide boosts from @{name}",
-  "account.link_verified_on": "Ownership of this link was checked on {date}",
-  "account.media": "Cyfryngau",
-  "account.mention": "Crybwyll @{name}",
-  "account.moved_to": "Mae @{name} wedi symud i:",
-  "account.mute": "Mute @{name}",
-  "account.mute_notifications": "Mute notifications from @{name}",
-  "account.muted": "Distewyd",
-  "account.posts": "Tŵtiau",
-  "account.posts_with_replies": "Toots and replies",
-  "account.report": "Adroddwch @{name}",
-  "account.requested": "Awaiting approval. Click to cancel follow request",
-  "account.share": "Rhannwch broffil @{name}",
-  "account.show_reblogs": "Show boosts from @{name}",
-  "account.unblock": "Dadflociwch @{name}",
-  "account.unblock_domain": "Dadguddiwch {domain}",
-  "account.unendorse": "Don't feature on profile",
-  "account.unfollow": "Daddilynwch",
-  "account.unmute": "Unmute @{name}",
-  "account.unmute_notifications": "Unmute notifications from @{name}",
-  "account.view_full_profile": "View full profile",
-  "alert.unexpected.message": "Digwyddodd gwall annisgwyl.",
-  "alert.unexpected.title": "Wps!",
-  "boost_modal.combo": "You can press {combo} to skip this next time",
-  "bundle_column_error.body": "Aeth rhywbeth o'i le tra'n llwytho'r elfen hon.",
-  "bundle_column_error.retry": "Ceisiwch eto",
-  "bundle_column_error.title": "Gwall rhwydwaith",
-  "bundle_modal_error.close": "Close",
-  "bundle_modal_error.message": "Aeth rhywbeth o'i le tra'n llwytho'r elfen hon.",
-  "bundle_modal_error.retry": "Ceiswich eto",
-  "column.blocks": "Defnyddwyr a flociwyd",
-  "column.community": "Llinell amser lleol",
-  "column.direct": "Negeseuon preifat",
-  "column.domain_blocks": "Parthau cuddiedig",
-  "column.favourites": "Ffefrynnau",
-  "column.follow_requests": "Ceisiadau dilyn",
-  "column.home": "Hafan",
-  "column.lists": "Rhestrau",
-  "column.mutes": "Defnyddwyr a ddistewyd",
-  "column.notifications": "Hysbysiadau",
-  "column.pins": "Pinned toot",
-  "column.public": "Federated timeline",
-  "column_back_button.label": "Nôl",
-  "column_header.hide_settings": "Hide settings",
-  "column_header.moveLeft_settings": "Symudwch y golofn i'r chwith",
-  "column_header.moveRight_settings": "Symudwch y golofn i'r dde",
-  "column_header.pin": "Piniwch",
-  "column_header.show_settings": "Dangos gosodiadau",
-  "column_header.unpin": "Dadbiniwch",
-  "column_subheading.settings": "Gosodiadau",
-  "community.column_settings.media_only": "Cyfryngau yn unig",
-  "compose_form.direct_message_warning": "Mi fydd y tŵt hwn ond yn cael ei anfon at y defnyddwyr sy'n cael eu crybwyll.",
-  "compose_form.direct_message_warning_learn_more": "Dysgwch fwy",
-  "compose_form.hashtag_warning": "Ni fydd y tŵt hwn wedi ei restru o dan unrhyw hashnod gan ei fod heb ei restru. Dim ond tŵtiau cyhoeddus gellid chwilota amdanynt drwy hashnod.",
-  "compose_form.lock_disclaimer": "Nid yw eich cyfri wedi'i {locked}. Gall unrhyw un eich dilyn i weld eich POSTS dilynwyr-yn-unig.",
-  "compose_form.lock_disclaimer.lock": "wedi ei gloi",
-  "compose_form.placeholder": "Be syd ar eich meddwl?",
-  "compose_form.publish": "Tŵt",
-  "compose_form.publish_loud": "{publish}!",
-  "compose_form.sensitive.marked": "Media is marked as sensitive",
-  "compose_form.sensitive.unmarked": "Media is not marked as sensitive",
-  "compose_form.spoiler.marked": "Testun wedi ei guddio gan rybudd",
-  "compose_form.spoiler.unmarked": "Nid yw'r testun wedi ei guddio",
-  "compose_form.spoiler_placeholder": "Ysgrifenwch eich rhybudd yma",
-  "confirmation_modal.cancel": "Cancel",
-  "confirmations.block.confirm": "Blociwch",
-  "confirmations.block.message": "Ydych chi'n sicr eich bod eisiau blocio {name}?",
-  "confirmations.delete.confirm": "Dileu",
-  "confirmations.delete.message": "Ydych chi'n sicr eich bod eisiau dileu y statws hwn?",
-  "confirmations.delete_list.confirm": "Dileu",
-  "confirmations.delete_list.message": "Ydych chi'n sicr eich bod eisiau dileu y rhestr hwn am byth?",
-  "confirmations.domain_block.confirm": "Hide entire domain",
-  "confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable. You will not see content from that domain in any public timelines or your notifications. Your followers from that domain will be removed.",
-  "confirmations.mute.confirm": "Mute",
-  "confirmations.mute.message": "Ydych chi'n sicr eich bod am ddistewi {name}?",
-  "confirmations.redraft.confirm": "Delete & redraft",
-  "confirmations.redraft.message": "Are you sure you want to delete this status and re-draft it? Favourites and boosts will be lost, and replies to the original post will be orphaned.",
-  "confirmations.unfollow.confirm": "Dad-ddilynwch",
-  "confirmations.unfollow.message": "Ydych chi'n sicr eich bod am ddad-ddilyn {name}?",
-  "embed.instructions": "Embed this status on your website by copying the code below.",
-  "embed.preview": "Dyma sut olwg fydd arno:",
-  "emoji_button.activity": "Gweithgarwch",
-  "emoji_button.custom": "Custom",
-  "emoji_button.flags": "Baneri",
-  "emoji_button.food": "Bwyd a Diod",
-  "emoji_button.label": "Mewnosodwch emoji",
-  "emoji_button.nature": "Natur",
-  "emoji_button.not_found": "Dim emojos!! (╯°□°)╯︵ ┻━┻",
-  "emoji_button.objects": "Gwrthrychau",
-  "emoji_button.people": "Pobl",
-  "emoji_button.recent": "Defnyddir yn aml",
-  "emoji_button.search": "Chwilio...",
-  "emoji_button.search_results": "Canlyniadau chwilio",
-  "emoji_button.symbols": "Symbolau",
-  "emoji_button.travel": "Teithio & Llefydd",
-  "empty_column.blocks": "Nid ydych wedi blocio unrhyw ddefnyddwyr eto.",
-  "empty_column.community": "The local timeline is empty. Write something publicly to get the ball rolling!",
-  "empty_column.direct": "Nid oes gennych unrhyw negeseuon preifat eto. Pan y byddwch yn anfon neu derbyn un, mi fydd yn ymddangos yma.",
-  "empty_column.domain_blocks": "Nid oes yna unrhyw barthau cuddiedig eto.",
-  "empty_column.favourited_statuses": "Nid oes gennych unrhyw hoff dwtiau eto. Pan y byddwch yn hoffi un, mi fydd yn ymddangos yma.",
-  "empty_column.favourites": "Nid oes neb wedi hoffi'r tŵt yma eto. Pan bydd rhywun yn ei hoffi, mi fyddent yn ymddangos yma.",
-  "empty_column.follow_requests": "You don't have any follow requests yet. When you receive one, it will show up here.",
-  "empty_column.hashtag": "Nid oes dim ar yr hashnod hwn eto.",
-  "empty_column.home": "Your home timeline is empty! Visit {public} or use search to get started and meet other users.",
-  "empty_column.home.public_timeline": "the public timeline",
-  "empty_column.list": "There is nothing in this list yet. When members of this list post new statuses, they will appear here.",
-  "empty_column.lists": "You don't have any lists yet. When you create one, it will show up here.",
-  "empty_column.mutes": "You haven't muted any users yet.",
-  "empty_column.notifications": "You don't have any notifications yet. Interact with others to start the conversation.",
-  "empty_column.public": "There is nothing here! Write something publicly, or manually follow users from other instances to fill it up",
-  "follow_request.authorize": "Caniatau",
-  "follow_request.reject": "Gwrthod",
-  "getting_started.developers": "Datblygwyr",
-  "getting_started.documentation": "Dogfennaeth",
-  "getting_started.find_friends": "Canfod ffrindiau o Twitter",
-  "getting_started.heading": "Dechrau",
-  "getting_started.invite": "Gwahoddwch bobl",
-  "getting_started.open_source_notice": "Mastodon is open source software. You can contribute or report issues on GitHub at {github}.",
-  "getting_started.security": "Diogelwch",
-  "getting_started.terms": "Terms of service",
-  "home.column_settings.basic": "Syml",
-  "home.column_settings.show_reblogs": "Show boosts",
-  "home.column_settings.show_replies": "Dangoswch ymatebion",
-  "keyboard_shortcuts.back": "to navigate back",
-  "keyboard_shortcuts.blocked": "to open blocked users list",
-  "keyboard_shortcuts.boost": "to boost",
-  "keyboard_shortcuts.column": "to focus a status in one of the columns",
-  "keyboard_shortcuts.compose": "to focus the compose textarea",
-  "keyboard_shortcuts.description": "Disgrifiad",
-  "keyboard_shortcuts.direct": "i agor colofn negeseuon preifat",
-  "keyboard_shortcuts.down": "i symud lawr yn y rhestr",
-  "keyboard_shortcuts.enter": "i agor statws",
-  "keyboard_shortcuts.favourite": "i hoffi",
-  "keyboard_shortcuts.favourites": "i agor rhestr hoffi",
-  "keyboard_shortcuts.federated": "to open federated timeline",
-  "keyboard_shortcuts.heading": "Keyboard Shortcuts",
-  "keyboard_shortcuts.home": "to open home timeline",
-  "keyboard_shortcuts.hotkey": "Hotkey",
-  "keyboard_shortcuts.legend": "to display this legend",
-  "keyboard_shortcuts.local": "to open local timeline",
-  "keyboard_shortcuts.mention": "i grybwyll yr awdur",
-  "keyboard_shortcuts.muted": "to open muted users list",
-  "keyboard_shortcuts.my_profile": "i agor eich proffil",
-  "keyboard_shortcuts.notifications": "i agor colofn hysbysiadau",
-  "keyboard_shortcuts.pinned": "to open pinned toots list",
-  "keyboard_shortcuts.profile": "i agor proffil yr awdur",
-  "keyboard_shortcuts.reply": "i ateb",
-  "keyboard_shortcuts.requests": "to open follow requests list",
-  "keyboard_shortcuts.search": "to focus search",
-  "keyboard_shortcuts.start": "to open \"get started\" column",
-  "keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW",
-  "keyboard_shortcuts.toot": "i ddechrau tŵt newydd sbon",
-  "keyboard_shortcuts.unfocus": "to un-focus compose textarea/search",
-  "keyboard_shortcuts.up": "to move up in the list",
-  "lightbox.close": "Cau",
-  "lightbox.next": "Nesaf",
-  "lightbox.previous": "Previous",
-  "lists.account.add": "Ychwanegwch at restr",
-  "lists.account.remove": "Remove from list",
-  "lists.delete": "Dileu rhestr",
-  "lists.edit": "Golygwch restr",
-  "lists.new.create": "Ychwanegwch restr",
-  "lists.new.title_placeholder": "New list title",
-  "lists.search": "Search among people you follow",
-  "lists.subheading": "Eich rhestrau",
-  "loading_indicator.label": "Llwytho...",
-  "media_gallery.toggle_visible": "Toggle visibility",
-  "missing_indicator.label": "Heb ei ganfod",
-  "missing_indicator.sublabel": "Ni ellid canfod yr adnodd hwn",
-  "mute_modal.hide_notifications": "Cuddiwch hysbysiadau rhag y defnyddiwr hwn?",
-  "navigation_bar.apps": "Apiau symudol",
-  "navigation_bar.blocks": "Defnyddwyr wedi eu blocio",
-  "navigation_bar.community_timeline": "Local timeline",
-  "navigation_bar.compose": "Cyfansoddwch dŵt newydd",
-  "navigation_bar.direct": "Negeseuon preifat",
-  "navigation_bar.discover": "Darganfyddwch",
-  "navigation_bar.domain_blocks": "Parthau cuddiedig",
-  "navigation_bar.edit_profile": "Golygu proffil",
-  "navigation_bar.favourites": "Ffefrynnau",
-  "navigation_bar.filters": "Muted words",
-  "navigation_bar.follow_requests": "Ceisiadau dilyn",
-  "navigation_bar.info": "About this instance",
-  "navigation_bar.keyboard_shortcuts": "Hotkeys",
-  "navigation_bar.lists": "Rhestrau",
-  "navigation_bar.logout": "Allgofnodi",
-  "navigation_bar.mutes": "Muted users",
-  "navigation_bar.personal": "Personol",
-  "navigation_bar.pins": "Tŵtiau wedi eu pinio",
-  "navigation_bar.preferences": "Dewisiadau",
-  "navigation_bar.public_timeline": "Federated timeline",
-  "navigation_bar.security": "Diogelwch",
-  "notification.favourite": "hoffodd {name} eich statws",
-  "notification.follow": "dilynodd {name} chi",
-  "notification.mention": "{name} mentioned you",
-  "notification.reblog": "{name} boosted your status",
-  "notifications.clear": "Clirio hysbysiadau",
-  "notifications.clear_confirmation": "Are you sure you want to permanently clear all your notifications?",
-  "notifications.column_settings.alert": "Desktop notifications",
-  "notifications.column_settings.favourite": "Ffefrynnau:",
-  "notifications.column_settings.follow": "Dilynwyr newydd:",
-  "notifications.column_settings.mention": "Mentions:",
-  "notifications.column_settings.push": "Push notifications",
-  "notifications.column_settings.push_meta": "This device",
-  "notifications.column_settings.reblog": "Boosts:",
-  "notifications.column_settings.show": "Show in column",
-  "notifications.column_settings.sound": "Chwarae sain",
-  "notifications.group": "{count} o hysbysiadau",
-  "onboarding.done": "Done",
-  "onboarding.next": "Nesaf",
-  "onboarding.page_five.public_timelines": "The local timeline shows public posts from everyone on {domain}. The federated timeline shows public posts from everyone who people on {domain} follow. These are the Public Timelines, a great way to discover new people.",
-  "onboarding.page_four.home": "The home timeline shows posts from people you follow.",
-  "onboarding.page_four.notifications": "The notifications column shows when someone interacts with you.",
-  "onboarding.page_one.federation": "Mastodon is a network of independent servers joining up to make one larger social network. We call these servers instances.",
-  "onboarding.page_one.full_handle": "Your full handle",
-  "onboarding.page_one.handle_hint": "This is what you would tell your friends to search for.",
-  "onboarding.page_one.welcome": "Croeso i Mastodon!",
-  "onboarding.page_six.admin": "Your instance's admin is {admin}.",
-  "onboarding.page_six.almost_done": "Almost done...",
-  "onboarding.page_six.appetoot": "Bon Apetŵt!",
-  "onboarding.page_six.apps_available": "There are {apps} available for iOS, Android and other platforms.",
-  "onboarding.page_six.github": "Mastodon is free open-source software. You can report bugs, request features, or contribute to the code on {github}.",
-  "onboarding.page_six.guidelines": "canllawiau cymunedol",
-  "onboarding.page_six.read_guidelines": "Please read {domain}'s {guidelines}!",
-  "onboarding.page_six.various_app": "apiau symudol",
-  "onboarding.page_three.profile": "Edit your profile to change your avatar, bio, and display name. There, you will also find other preferences.",
-  "onboarding.page_three.search": "Use the search bar to find people and look at hashtags, such as {illustration} and {introductions}. To look for a person who is not on this instance, use their full handle.",
-  "onboarding.page_two.compose": "Write posts from the compose column. You can upload images, change privacy settings, and add content warnings with the icons below.",
-  "onboarding.skip": "Skip",
-  "privacy.change": "Adjust status privacy",
-  "privacy.direct.long": "Post to mentioned users only",
-  "privacy.direct.short": "Direct",
-  "privacy.private.long": "Post to followers only",
-  "privacy.private.short": "Dilynwyr-yn-unig",
-  "privacy.public.long": "Post to public timelines",
-  "privacy.public.short": "Cyhoeddus",
-  "privacy.unlisted.long": "Do not show in public timelines",
-  "privacy.unlisted.short": "Heb ei restru",
-  "regeneration_indicator.label": "Llwytho…",
-  "regeneration_indicator.sublabel": "Mae eich ffrwd cartref yn cael ei baratoi!",
-  "relative_time.days": "{number}d",
-  "relative_time.hours": "{number}h",
-  "relative_time.just_now": "nawr",
-  "relative_time.minutes": "{number}m",
-  "relative_time.seconds": "{number}s",
-  "reply_indicator.cancel": "Canslo",
-  "report.forward": "Forward to {target}",
-  "report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
-  "report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
-  "report.placeholder": "Sylwadau ychwanegol",
-  "report.submit": "Submit",
-  "report.target": "Report {target}",
-  "search.placeholder": "Chwilio",
-  "search_popout.search_format": "Advanced search format",
-  "search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
-  "search_popout.tips.hashtag": "hashnod",
-  "search_popout.tips.status": "statws",
-  "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags",
-  "search_popout.tips.user": "defnyddiwr",
-  "search_results.accounts": "Pobl",
-  "search_results.hashtags": "Hanshnodau",
-  "search_results.statuses": "Twtiau",
-  "search_results.total": "{count, number} {count, plural, one {result} other {results}}",
-  "standalone.public_title": "A look inside...",
-  "status.block": "Block @{name}",
-  "status.cancel_reblog_private": "Unboost",
-  "status.cannot_reblog": "This post cannot be boosted",
-  "status.delete": "Dileu",
-  "status.detailed_status": "Detailed conversation view",
-  "status.direct": "Neges breifat @{name}",
-  "status.embed": "Plannu",
-  "status.favourite": "Favourite",
-  "status.filtered": "Filtered",
-  "status.load_more": "Llwythwch mwy",
-  "status.media_hidden": "Media hidden",
-  "status.mention": "Mention @{name}",
-  "status.more": "Mwy",
-  "status.mute": "Mute @{name}",
-  "status.mute_conversation": "Mute conversation",
-  "status.open": "Expand this status",
-  "status.pin": "Pin on profile",
-  "status.pinned": "Pinned toot",
-  "status.reblog": "Boost",
-  "status.reblog_private": "Boost to original audience",
-  "status.reblogged_by": "{name} boosted",
-  "status.reblogs.empty": "No one has boosted this toot yet. When someone does, they will show up here.",
-  "status.redraft": "Delete & re-draft",
-  "status.reply": "Ateb",
-  "status.replyAll": "Ateb i edefyn",
-  "status.report": "Report @{name}",
-  "status.sensitive_toggle": "Click to view",
-  "status.sensitive_warning": "Cynnwys sensitif",
-  "status.share": "Rhannwch",
-  "status.show_less": "Dangoswch lai",
-  "status.show_less_all": "Dangoswch lai i bawb",
-  "status.show_more": "Dangoswch fwy",
-  "status.show_more_all": "Show more for all",
-  "status.unmute_conversation": "Unmute conversation",
-  "status.unpin": "Unpin from profile",
-  "tabs_bar.federated_timeline": "Federated",
-  "tabs_bar.home": "Hafan",
-  "tabs_bar.local_timeline": "Lleol",
-  "tabs_bar.notifications": "Hysbysiadau",
-  "tabs_bar.search": "Chwilio",
-  "trends.count_by_accounts": "{count} {rawCount, plural, one {person} other {people}} talking",
-  "ui.beforeunload": "Your draft will be lost if you leave Mastodon.",
-  "upload_area.title": "Drag & drop to upload",
-  "upload_button.label": "Add media (JPEG, PNG, GIF, WebM, MP4, MOV)",
-  "upload_form.description": "Describe for the visually impaired",
-  "upload_form.focus": "Crop",
-  "upload_form.undo": "Delete",
-  "upload_progress.label": "Uwchlwytho...",
-  "video.close": "Close video",
-  "video.exit_fullscreen": "Exit full screen",
-  "video.expand": "Expand video",
-  "video.fullscreen": "Full screen",
-  "video.hide": "Hide video",
-  "video.mute": "Mute sound",
-  "video.pause": "Pause",
-  "video.play": "Chwarae",
-  "video.unmute": "Unmute sound"
+    "account.badges.bot": "Bot",
+    "account.block": "Blociwch @{name}",
+    "account.block_domain": "Cuddiwch bopeth rhag {domain}",
+    "account.blocked": "Blociwyd",
+    "account.direct": "Neges breifat @{name}",
+    "account.disclaimer_full": "Gall y wybodaeth isod adlewyrchu darlun anghyflawn o broffil defnyddiwr.",
+    "account.domain_blocked": "Parth wedi ei guddio",
+    "account.edit_profile": "Golygu proffil",
+    "account.endorse": "Arddangos ar fy mhroffil",
+    "account.follow": "Dilyn",
+    "account.followers": "Dilynwyr",
+    "account.followers.empty": "Nid oes neb yn dilyn y defnyddiwr hwn eto.",
+    "account.follows": "Yn dilyn",
+    "account.follows.empty": "Nid yw'r defnyddiwr hwn yn dilyn unrhyw un eto.",
+    "account.follows_you": "Yn eich dilyn chi",
+    "account.hide_reblogs": "Cuddio bwstiau o @{name}",
+    "account.media": "Cyfryngau",
+    "account.mention": "Crybwyll @{name}",
+    "account.moved_to": "Mae @{name} wedi symud i:",
+    "account.mute": "Tawelu @{name}",
+    "account.mute_notifications": "Cuddio hysbysiadau o @{name}",
+    "account.muted": "Distewyd",
+    "account.posts": "Tŵtiau",
+    "account.posts_with_replies": "Tŵtiau ac atebion",
+    "account.report": "Adroddwch @{name}",
+    "account.requested": "Aros am gymeradwyaeth. Cliciwch er mwyn canslo cais dilyn",
+    "account.share": "Rhannwch broffil @{name}",
+    "account.show_reblogs": "Dangoswch bwstiau o @{name}",
+    "account.unblock": "Dadflociwch @{name}",
+    "account.unblock_domain": "Dadguddiwch {domain}",
+    "account.unendorse": "Peidwch a'i arddangos ar fy mhroffil",
+    "account.unfollow": "Daddilynwch",
+    "account.unmute": "Dad-dawelu @{name}",
+    "account.unmute_notifications": "Dad-dawelu hysbysiadau o @{name}",
+    "account.view_full_profile": "Gweld proffil llawn",
+    "alert.unexpected.message": "Digwyddodd gwall annisgwyl.",
+    "alert.unexpected.title": "Wps!",
+    "boost_modal.combo": "Mae modd gwasgu {combo} er mwyn sgipio hyn tro nesa",
+    "bundle_column_error.body": "Aeth rhywbeth o'i le tra'n llwytho'r elfen hon.",
+    "bundle_column_error.retry": "Ceisiwch eto",
+    "bundle_column_error.title": "Gwall rhwydwaith",
+    "bundle_modal_error.close": "Cau",
+    "bundle_modal_error.message": "Aeth rhywbeth o'i le tra'n llwytho'r elfen hon.",
+    "bundle_modal_error.retry": "Ceiswich eto",
+    "column.blocks": "Defnyddwyr a flociwyd",
+    "column.community": "Llinell amser lleol",
+    "column.direct": "Negeseuon preifat",
+    "column.domain_blocks": "Parthau cuddiedig",
+    "column.favourites": "Ffefrynnau",
+    "column.follow_requests": "Ceisiadau dilyn",
+    "column.home": "Hafan",
+    "column.lists": "Rhestrau",
+    "column.mutes": "Defnyddwyr a ddistewyd",
+    "column.notifications": "Hysbysiadau",
+    "column.pins": "Tŵtiau wedi eu pinio",
+    "column.public": "",
+    "column_back_button.label": "Nôl",
+    "column_header.hide_settings": "Cuddiwch dewisiadau",
+    "column_header.moveLeft_settings": "Symudwch y golofn i'r chwith",
+    "column_header.moveRight_settings": "Symudwch y golofn i'r dde",
+    "column_header.pin": "Piniwch",
+    "column_header.show_settings": "Dangos gosodiadau",
+    "column_header.unpin": "Dadbiniwch",
+    "column_subheading.settings": "Gosodiadau",
+    "community.column_settings.media_only": "Cyfryngau yn unig",
+    "compose_form.direct_message_warning": "Mi fydd y tŵt hwn ond yn cael ei anfon at y defnyddwyr sy'n cael eu crybwyll.",
+    "compose_form.direct_message_warning_learn_more": "Dysgwch fwy",
+    "compose_form.hashtag_warning": "Ni fydd y tŵt hwn wedi ei restru o dan unrhyw hashnod gan ei fod heb ei restru. Dim ond tŵtiau cyhoeddus gellid chwilota amdanynt drwy hashnod.",
+    "compose_form.lock_disclaimer": "Nid yw eich cyfri wedi'i {locked}. Gall unrhyw un eich dilyn i weld eich POSTS dilynwyr-yn-unig.",
+    "compose_form.lock_disclaimer.lock": "wedi ei gloi",
+    "compose_form.placeholder": "Be syd ar eich meddwl?",
+    "compose_form.publish": "Tŵt",
+    "compose_form.publish_loud": "{publish}!",
+    "compose_form.sensitive.marked": "",
+    "compose_form.sensitive.unmarked": "",
+    "compose_form.spoiler.marked": "Testun wedi ei guddio gan rybudd",
+    "compose_form.spoiler.unmarked": "Nid yw'r testun wedi ei guddio",
+    "compose_form.spoiler_placeholder": "Ysgrifenwch eich rhybudd yma",
+    "confirmation_modal.cancel": "Canslo",
+    "confirmations.block.confirm": "Blociwch",
+    "confirmations.block.message": "Ydych chi'n sicr eich bod eisiau blocio {name}?",
+    "confirmations.delete.confirm": "Dileu",
+    "confirmations.delete.message": "Ydych chi'n sicr eich bod eisiau dileu y statws hwn?",
+    "confirmations.delete_list.confirm": "Dileu",
+    "confirmations.delete_list.message": "Ydych chi'n sicr eich bod eisiau dileu y rhestr hwn am byth?",
+    "confirmations.domain_block.confirm": "",
+    "confirmations.domain_block.message": "",
+    "confirmations.mute.confirm": "Tawelu",
+    "confirmations.mute.message": "Ydych chi'n sicr eich bod am ddistewi {name}?",
+    "confirmations.redraft.confirm": "Dilëwch & ailddrafftio",
+    "confirmations.redraft.message": "Ydych chi'n siwr eich bod eisiau dileu y statws hwn a'i ailddrafftio? Bydd ffefrynnau a bwstiau'n cael ei colli, a bydd ymatebion i'r statws gwreiddiol yn cael eu hamddifadu.",
+    "confirmations.unfollow.confirm": "Dad-ddilynwch",
+    "confirmations.unfollow.message": "Ydych chi'n sicr eich bod am ddad-ddilyn {name}?",
+    "embed.instructions": "Mewnblannwch y statws hwn ar eich gwefan drwy gopïo'r côd isod.",
+    "embed.preview": "Dyma sut olwg fydd arno:",
+    "emoji_button.activity": "Gweithgarwch",
+    "emoji_button.custom": "",
+    "emoji_button.flags": "Baneri",
+    "emoji_button.food": "Bwyd a Diod",
+    "emoji_button.label": "Mewnosodwch emoji",
+    "emoji_button.nature": "Natur",
+    "emoji_button.not_found": "Dim emojos!! (╯°□°)╯︵ ┻━┻",
+    "emoji_button.objects": "Gwrthrychau",
+    "emoji_button.people": "Pobl",
+    "emoji_button.recent": "Defnyddir yn aml",
+    "emoji_button.search": "Chwilio...",
+    "emoji_button.search_results": "Canlyniadau chwilio",
+    "emoji_button.symbols": "Symbolau",
+    "emoji_button.travel": "Teithio & Llefydd",
+    "empty_column.blocks": "Nid ydych wedi blocio unrhyw ddefnyddwyr eto.",
+    "empty_column.community": "",
+    "empty_column.direct": "Nid oes gennych unrhyw negeseuon preifat eto. Pan y byddwch yn anfon neu derbyn un, mi fydd yn ymddangos yma.",
+    "empty_column.domain_blocks": "Nid oes yna unrhyw barthau cuddiedig eto.",
+    "empty_column.favourited_statuses": "Nid oes gennych unrhyw hoff dwtiau eto. Pan y byddwch yn hoffi un, mi fydd yn ymddangos yma.",
+    "empty_column.favourites": "Nid oes neb wedi hoffi'r tŵt yma eto. Pan bydd rhywun yn ei hoffi, mi fyddent yn ymddangos yma.",
+    "empty_column.follow_requests": "Nid oes gennych unrhyw geisiadau dilyn eto. Pan dderbyniwch chi un, bydd yn ymddangos yma.",
+    "empty_column.hashtag": "Nid oes dim ar yr hashnod hwn eto.",
+    "empty_column.home": "",
+    "empty_column.home.public_timeline": "y ffrwd cyhoeddus",
+    "empty_column.list": "Nid oes dim yn y rhestr yma eto. Pan y bydd aelodau'r rhestr yn cyhoeddi statws newydd, mi fydd yn ymddangos yma.",
+    "empty_column.lists": "Nid oes gennych unrhyw restrau eto. Pan grëwch chi un, mi fydd yn ymddangos yma.",
+    "empty_column.mutes": "Nid ydych wedi tawelu unrhyw ddefnyddwyr eto.",
+    "empty_column.notifications": "Nid oes gennych unrhyw hysbysiadau eto. Rhyngweithiwch ac eraill i ddechrau'r sgwrs.",
+    "empty_column.public": "Does dim byd yma! Ysgrifennwch rhywbeth yn gyhoeddus, neu dilynwch ddefnyddwyr o INSTANCES eraill i'w lenwi",
+    "follow_request.authorize": "Caniatau",
+    "follow_request.reject": "Gwrthod",
+    "getting_started.developers": "Datblygwyr",
+    "getting_started.documentation": "Dogfennaeth",
+    "getting_started.find_friends": "Canfod ffrindiau o Twitter",
+    "getting_started.heading": "Dechrau",
+    "getting_started.invite": "Gwahoddwch bobl",
+    "getting_started.open_source_notice": "Mae Mastodon yn feddalwedd côd agored. Mae modd cyfrannu neu adrodd materion ar GitHUb ar {github}.",
+    "getting_started.security": "Diogelwch",
+    "getting_started.terms": "Telerau Gwasanaeth",
+    "home.column_settings.basic": "Syml",
+    "home.column_settings.show_reblogs": "",
+    "home.column_settings.show_replies": "Dangoswch ymatebion",
+    "keyboard_shortcuts.back": "",
+    "keyboard_shortcuts.blocked": "i agor rhestr defnyddwyr a flociwyd",
+    "keyboard_shortcuts.boost": "",
+    "keyboard_shortcuts.column": "",
+    "keyboard_shortcuts.compose": "",
+    "keyboard_shortcuts.description": "Disgrifiad",
+    "keyboard_shortcuts.direct": "i agor colofn negeseuon preifat",
+    "keyboard_shortcuts.down": "i symud lawr yn y rhestr",
+    "keyboard_shortcuts.enter": "i agor statws",
+    "keyboard_shortcuts.favourite": "i hoffi",
+    "keyboard_shortcuts.favourites": "i agor rhestr hoffi",
+    "keyboard_shortcuts.federated": "",
+    "keyboard_shortcuts.heading": "",
+    "keyboard_shortcuts.home": "i agor ffrwd cartref",
+    "keyboard_shortcuts.hotkey": "Hotkey",
+    "keyboard_shortcuts.legend": "",
+    "keyboard_shortcuts.local": "i agor ffrwd lleol",
+    "keyboard_shortcuts.mention": "i grybwyll yr awdur",
+    "keyboard_shortcuts.muted": "i agor rhestr defnyddwyr a dawelwyd",
+    "keyboard_shortcuts.my_profile": "i agor eich proffil",
+    "keyboard_shortcuts.notifications": "i agor colofn hysbysiadau",
+    "keyboard_shortcuts.pinned": "",
+    "keyboard_shortcuts.profile": "i agor proffil yr awdur",
+    "keyboard_shortcuts.reply": "i ateb",
+    "keyboard_shortcuts.requests": "i agor rhestr ceisiadau dilyn",
+    "keyboard_shortcuts.search": "",
+    "keyboard_shortcuts.start": "",
+    "keyboard_shortcuts.toggle_hidden": "",
+    "keyboard_shortcuts.toot": "i ddechrau tŵt newydd sbon",
+    "keyboard_shortcuts.unfocus": "",
+    "keyboard_shortcuts.up": "i symud yn uwch yn y rhestr",
+    "lightbox.close": "Cau",
+    "lightbox.next": "Nesaf",
+    "lightbox.previous": "",
+    "lists.account.add": "Ychwanegwch at restr",
+    "lists.account.remove": "",
+    "lists.delete": "Dileu rhestr",
+    "lists.edit": "Golygwch restr",
+    "lists.new.create": "Ychwanegwch restr",
+    "lists.new.title_placeholder": "Teitl rhestr newydd",
+    "lists.search": "",
+    "lists.subheading": "Eich rhestrau",
+    "loading_indicator.label": "Llwytho...",
+    "media_gallery.toggle_visible": "",
+    "missing_indicator.label": "Heb ei ganfod",
+    "missing_indicator.sublabel": "Ni ellid canfod yr adnodd hwn",
+    "mute_modal.hide_notifications": "Cuddiwch hysbysiadau rhag y defnyddiwr hwn?",
+    "navigation_bar.apps": "Apiau symudol",
+    "navigation_bar.blocks": "Defnyddwyr wedi eu blocio",
+    "navigation_bar.community_timeline": "",
+    "navigation_bar.compose": "Cyfansoddwch dŵt newydd",
+    "navigation_bar.direct": "Negeseuon preifat",
+    "navigation_bar.discover": "Darganfyddwch",
+    "navigation_bar.domain_blocks": "Parthau cuddiedig",
+    "navigation_bar.edit_profile": "Golygu proffil",
+    "navigation_bar.favourites": "Ffefrynnau",
+    "navigation_bar.filters": "Geiriau a dawelwyd",
+    "navigation_bar.follow_requests": "Ceisiadau dilyn",
+    "navigation_bar.info": "",
+    "navigation_bar.keyboard_shortcuts": "",
+    "navigation_bar.lists": "Rhestrau",
+    "navigation_bar.logout": "Allgofnodi",
+    "navigation_bar.mutes": "Defnyddwyr a dawelwyd",
+    "navigation_bar.personal": "Personol",
+    "navigation_bar.pins": "Tŵtiau wedi eu pinio",
+    "navigation_bar.preferences": "Dewisiadau",
+    "navigation_bar.public_timeline": "",
+    "navigation_bar.security": "Diogelwch",
+    "notification.favourite": "hoffodd {name} eich statws",
+    "notification.follow": "dilynodd {name} chi",
+    "notification.mention": "Soniodd {name} amdanoch chi",
+    "notification.reblog": "",
+    "notifications.clear": "Clirio hysbysiadau",
+    "notifications.clear_confirmation": "",
+    "notifications.column_settings.alert": "",
+    "notifications.column_settings.favourite": "Ffefrynnau:",
+    "notifications.column_settings.follow": "Dilynwyr newydd:",
+    "notifications.column_settings.mention": "",
+    "notifications.column_settings.push": "Hysbysiadau push",
+    "notifications.column_settings.reblog": "",
+    "notifications.column_settings.show": "",
+    "notifications.column_settings.sound": "Chwarae sain",
+    "notifications.group": "{count} o hysbysiadau",
+    "onboarding.done": "Wedi'i wneud",
+    "onboarding.next": "Nesaf",
+    "onboarding.page_five.public_timelines": "",
+    "onboarding.page_four.home": "Mae'r ffrwd gartref yn dangos twtiau o bobl yr ydych yn dilyn.",
+    "onboarding.page_four.notifications": "",
+    "onboarding.page_one.federation": "",
+    "onboarding.page_one.full_handle": "",
+    "onboarding.page_one.handle_hint": "",
+    "onboarding.page_one.welcome": "Croeso i Mastodon!",
+    "onboarding.page_six.admin": "",
+    "onboarding.page_six.almost_done": "Bron a gorffen...",
+    "onboarding.page_six.appetoot": "Bon Apetŵt!",
+    "onboarding.page_six.apps_available": "Mae yna {apps} ar gael i iOS, Android a platfformau eraill.",
+    "onboarding.page_six.github": "Mae Mastodon yn feddalwedd côd agored rhad ac am ddim. Mae modd adrodd bygiau, gwneud ceisiadau am nodweddion penodol, neu gyfrannu i'r côd ar {github}.",
+    "onboarding.page_six.guidelines": "canllawiau cymunedol",
+    "onboarding.page_six.read_guidelines": "Darllenwch {guidelines} y {domain} os gwelwch yn dda!",
+    "onboarding.page_six.various_app": "apiau symudol",
+    "onboarding.page_three.profile": "",
+    "onboarding.page_three.search": "",
+    "onboarding.page_two.compose": "",
+    "onboarding.skip": "Sgipiwch",
+    "privacy.change": "",
+    "privacy.direct.long": "",
+    "privacy.direct.short": "Uniongyrchol",
+    "privacy.private.long": "Cyhoeddi i ddilynwyr yn unig",
+    "privacy.private.short": "Dilynwyr-yn-unig",
+    "privacy.public.long": "Cyhoeddi i ffrydiau cyhoeddus",
+    "privacy.public.short": "Cyhoeddus",
+    "privacy.unlisted.long": "Peidio a cyhoeddi i ffrydiau cyhoeddus",
+    "privacy.unlisted.short": "Heb ei restru",
+    "regeneration_indicator.label": "Llwytho…",
+    "regeneration_indicator.sublabel": "Mae eich ffrwd cartref yn cael ei baratoi!",
+    "relative_time.days": "{number}d",
+    "relative_time.hours": "{number}h",
+    "relative_time.just_now": "nawr",
+    "relative_time.minutes": "{number}m",
+    "relative_time.seconds": "{number}s",
+    "reply_indicator.cancel": "Canslo",
+    "report.forward": "",
+    "report.forward_hint": "",
+    "report.hint": "",
+    "report.placeholder": "Sylwadau ychwanegol",
+    "report.submit": "Cyflwyno",
+    "report.target": "",
+    "search.placeholder": "Chwilio",
+    "search_popout.search_format": "Fformat chwilio uwch",
+    "search_popout.tips.full_text": "",
+    "search_popout.tips.hashtag": "hashnod",
+    "search_popout.tips.status": "statws",
+    "search_popout.tips.text": "",
+    "search_popout.tips.user": "defnyddiwr",
+    "search_results.accounts": "Pobl",
+    "search_results.hashtags": "Hanshnodau",
+    "search_results.statuses": "Twtiau",
+    "search_results.total": "",
+    "standalone.public_title": "Golwg tu fewn...",
+    "status.block": "Blociwch @{name}",
+    "status.cancel_reblog_private": "",
+    "status.cannot_reblog": "",
+    "status.delete": "Dileu",
+    "status.detailed_status": "",
+    "status.direct": "Neges breifat @{name}",
+    "status.embed": "Plannu",
+    "status.favourite": "",
+    "status.filtered": "",
+    "status.load_more": "Llwythwch mwy",
+    "status.media_hidden": "",
+    "status.mention": "",
+    "status.more": "Mwy",
+    "status.mute": "Tawelu @{name}",
+    "status.mute_conversation": "",
+    "status.open": "",
+    "status.pin": "",
+    "status.pinned": "",
+    "status.reblog": "",
+    "status.reblog_private": "",
+    "status.reblogged_by": "",
+    "status.reblogs.empty": "",
+    "status.redraft": "Dilëwh & ailddrafftio",
+    "status.reply": "Ateb",
+    "status.replyAll": "Ateb i edefyn",
+    "status.report": "",
+    "status.sensitive_toggle": "",
+    "status.sensitive_warning": "Cynnwys sensitif",
+    "status.share": "Rhannwch",
+    "status.show_less": "Dangoswch lai",
+    "status.show_less_all": "Dangoswch lai i bawb",
+    "status.show_more": "Dangoswch fwy",
+    "status.show_more_all": "",
+    "status.unmute_conversation": "Dad-dawelu sgwrs",
+    "status.unpin": "",
+    "tabs_bar.federated_timeline": "",
+    "tabs_bar.home": "Hafan",
+    "tabs_bar.local_timeline": "Lleol",
+    "tabs_bar.notifications": "Hysbysiadau",
+    "tabs_bar.search": "Chwilio",
+    "trends.count_by_accounts": "{count} {rawCount, plural, one {person} other {people}} yn siarad",
+    "ui.beforeunload": "Mi fyddwch yn colli eich drafft os gadewch Mastodon.",
+    "upload_area.title": "Llusgwch & gollwing i uwchlwytho",
+    "upload_button.label": "Ychwanegwch gyfryngau (JPEG, PNG, GIF, WebM, MP4, MOV)",
+    "upload_form.description": "",
+    "upload_form.focus": "",
+    "upload_form.undo": "Dileu",
+    "upload_progress.label": "Uwchlwytho...",
+    "video.close": "Cau fideo",
+    "video.exit_fullscreen": "Gadael sgrîn llawn",
+    "video.expand": "Ymestyn fideo",
+    "video.fullscreen": "Sgrîn llawn",
+    "video.hide": "Cuddio fideo",
+    "video.mute": "Tawelu sain",
+    "video.pause": "Oedi",
+    "video.play": "Chwarae",
+    "video.unmute": "Dad-dawelu sain"
 }
diff --git a/app/javascript/mastodon/locales/da.json b/app/javascript/mastodon/locales/da.json
index 959a50377..e3d040ea8 100644
--- a/app/javascript/mastodon/locales/da.json
+++ b/app/javascript/mastodon/locales/da.json
@@ -216,7 +216,6 @@
   "notifications.column_settings.follow": "Nye følgere:",
   "notifications.column_settings.mention": "Omtale:",
   "notifications.column_settings.push": "Push notifikationer",
-  "notifications.column_settings.push_meta": "Denne enhed",
   "notifications.column_settings.reblog": "Fremhævelser:",
   "notifications.column_settings.show": "Vis i kolonne",
   "notifications.column_settings.sound": "Afspil lyd",
diff --git a/app/javascript/mastodon/locales/de.json b/app/javascript/mastodon/locales/de.json
index 4f1b2c8a0..d798878fb 100644
--- a/app/javascript/mastodon/locales/de.json
+++ b/app/javascript/mastodon/locales/de.json
@@ -216,7 +216,6 @@
   "notifications.column_settings.follow": "Neue Folgende:",
   "notifications.column_settings.mention": "Erwähnungen:",
   "notifications.column_settings.push": "Push-Benachrichtigungen",
-  "notifications.column_settings.push_meta": "Auf diesem Gerät",
   "notifications.column_settings.reblog": "Geteilte Beiträge:",
   "notifications.column_settings.show": "In der Spalte anzeigen",
   "notifications.column_settings.sound": "Ton abspielen",
diff --git a/app/javascript/mastodon/locales/defaultMessages.json b/app/javascript/mastodon/locales/defaultMessages.json
index 5215bfb4e..b2ed49309 100644
--- a/app/javascript/mastodon/locales/defaultMessages.json
+++ b/app/javascript/mastodon/locales/defaultMessages.json
@@ -309,6 +309,10 @@
   {
     "descriptors": [
       {
+        "defaultMessage": "Read more",
+        "id": "status.read_more"
+      },
+      {
         "defaultMessage": "Show more",
         "id": "status.show_more"
       },
@@ -398,6 +402,14 @@
         "id": "confirmations.block.confirm"
       },
       {
+        "defaultMessage": "Reply",
+        "id": "confirmations.reply.confirm"
+      },
+      {
+        "defaultMessage": "Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?",
+        "id": "confirmations.reply.message"
+      },
+      {
         "defaultMessage": "Are you sure you want to block {name}?",
         "id": "confirmations.block.message"
       }
@@ -1542,10 +1554,6 @@
         "id": "notifications.column_settings.push"
       },
       {
-        "defaultMessage": "This device",
-        "id": "notifications.column_settings.push_meta"
-      },
-      {
         "defaultMessage": "New followers:",
         "id": "notifications.column_settings.follow"
       },
@@ -1772,6 +1780,14 @@
         "id": "status.detailed_status"
       },
       {
+        "defaultMessage": "Reply",
+        "id": "confirmations.reply.confirm"
+      },
+      {
+        "defaultMessage": "Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?",
+        "id": "confirmations.reply.message"
+      },
+      {
         "defaultMessage": "Are you sure you want to block {name}?",
         "id": "confirmations.block.message"
       }
@@ -2135,6 +2151,10 @@
         "id": "status.sensitive_warning"
       },
       {
+        "defaultMessage": "Media hidden",
+        "id": "status.media_hidden"
+      },
+      {
         "defaultMessage": "Click to view",
         "id": "status.sensitive_toggle"
       }
diff --git a/app/javascript/mastodon/locales/el.json b/app/javascript/mastodon/locales/el.json
index 9c2a31722..4e4b733ec 100644
--- a/app/javascript/mastodon/locales/el.json
+++ b/app/javascript/mastodon/locales/el.json
@@ -216,7 +216,6 @@
   "notifications.column_settings.follow": "Νέοι ακόλουθοι:",
   "notifications.column_settings.mention": "Αναφορές:",
   "notifications.column_settings.push": "Άμεσες ειδοποιήσεις",
-  "notifications.column_settings.push_meta": "Αυτή η συσκευή",
   "notifications.column_settings.reblog": "Προωθήσεις:",
   "notifications.column_settings.show": "Εμφάνισε σε στήλη",
   "notifications.column_settings.sound": "Ηχητική ειδοποίηση",
diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json
index b117dad95..0d85ec744 100644
--- a/app/javascript/mastodon/locales/en.json
+++ b/app/javascript/mastodon/locales/en.json
@@ -95,6 +95,8 @@
   "confirmations.mute.message": "Are you sure you want to mute {name}?",
   "confirmations.redraft.confirm": "Delete & redraft",
   "confirmations.redraft.message": "Are you sure you want to delete this status and re-draft it? Favourites and boosts will be lost, and replies to the original post will be orphaned.",
+  "confirmations.reply.confirm": "Reply",
+  "confirmations.reply.message": "Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?",
   "confirmations.unfollow.confirm": "Unfollow",
   "confirmations.unfollow.message": "Are you sure you want to unfollow {name}?",
   "embed.instructions": "Embed this status on your website by copying the code below.",
@@ -221,7 +223,6 @@
   "notifications.column_settings.follow": "New followers:",
   "notifications.column_settings.mention": "Mentions:",
   "notifications.column_settings.push": "Push notifications",
-  "notifications.column_settings.push_meta": "This device",
   "notifications.column_settings.reblog": "Boosts:",
   "notifications.column_settings.show": "Show in column",
   "notifications.column_settings.sound": "Play sound",
@@ -300,6 +301,7 @@
   "status.open": "Expand this status",
   "status.pin": "Pin on profile",
   "status.pinned": "Pinned toot",
+  "status.read_more": "Read more",
   "status.reblog": "Boost",
   "status.reblog_private": "Boost to original audience",
   "status.reblogged_by": "{name} boosted",
diff --git a/app/javascript/mastodon/locales/eo.json b/app/javascript/mastodon/locales/eo.json
index dcc565eb9..86bee46b1 100644
--- a/app/javascript/mastodon/locales/eo.json
+++ b/app/javascript/mastodon/locales/eo.json
@@ -216,7 +216,6 @@
   "notifications.column_settings.follow": "Novaj sekvantoj:",
   "notifications.column_settings.mention": "Mencioj:",
   "notifications.column_settings.push": "Puŝsciigoj",
-  "notifications.column_settings.push_meta": "Ĉi tiu aparato",
   "notifications.column_settings.reblog": "Diskonigoj:",
   "notifications.column_settings.show": "Montri en kolumno",
   "notifications.column_settings.sound": "Eligi sonon",
diff --git a/app/javascript/mastodon/locales/es.json b/app/javascript/mastodon/locales/es.json
index 355c901fa..63f197c28 100644
--- a/app/javascript/mastodon/locales/es.json
+++ b/app/javascript/mastodon/locales/es.json
@@ -216,7 +216,6 @@
   "notifications.column_settings.follow": "Nuevos seguidores:",
   "notifications.column_settings.mention": "Menciones:",
   "notifications.column_settings.push": "Notificaciones push",
-  "notifications.column_settings.push_meta": "Este dispositivo",
   "notifications.column_settings.reblog": "Retoots:",
   "notifications.column_settings.show": "Mostrar en columna",
   "notifications.column_settings.sound": "Reproducir sonido",
diff --git a/app/javascript/mastodon/locales/eu.json b/app/javascript/mastodon/locales/eu.json
index 1dc42ae68..e4b1154b7 100644
--- a/app/javascript/mastodon/locales/eu.json
+++ b/app/javascript/mastodon/locales/eu.json
@@ -216,7 +216,6 @@
   "notifications.column_settings.follow": "Jarraitzaile berriak:",
   "notifications.column_settings.mention": "Aipamenak:",
   "notifications.column_settings.push": "Push jakinarazpenak",
-  "notifications.column_settings.push_meta": "Gailu hau",
   "notifications.column_settings.reblog": "Bultzadak:",
   "notifications.column_settings.show": "Erakutsi zutabean",
   "notifications.column_settings.sound": "Jo soinua",
diff --git a/app/javascript/mastodon/locales/fa.json b/app/javascript/mastodon/locales/fa.json
index 2e3999918..fb4ded11c 100644
--- a/app/javascript/mastodon/locales/fa.json
+++ b/app/javascript/mastodon/locales/fa.json
@@ -216,7 +216,6 @@
   "notifications.column_settings.follow": "پیگیران تازه:",
   "notifications.column_settings.mention": "نام‌بردن‌ها:",
   "notifications.column_settings.push": "اعلان‌ها از سمت سرور",
-  "notifications.column_settings.push_meta": "این دستگاه",
   "notifications.column_settings.reblog": "بازبوق‌ها:",
   "notifications.column_settings.show": "نمایش در ستون",
   "notifications.column_settings.sound": "پخش صدا",
diff --git a/app/javascript/mastodon/locales/fi.json b/app/javascript/mastodon/locales/fi.json
index f6c0469be..caf949e8c 100644
--- a/app/javascript/mastodon/locales/fi.json
+++ b/app/javascript/mastodon/locales/fi.json
@@ -216,7 +216,6 @@
   "notifications.column_settings.follow": "Uudet seuraajat:",
   "notifications.column_settings.mention": "Maininnat:",
   "notifications.column_settings.push": "Push-ilmoitukset",
-  "notifications.column_settings.push_meta": "Tämä laite",
   "notifications.column_settings.reblog": "Buustit:",
   "notifications.column_settings.show": "Näytä sarakkeessa",
   "notifications.column_settings.sound": "Äänimerkki",
diff --git a/app/javascript/mastodon/locales/fr.json b/app/javascript/mastodon/locales/fr.json
index 075bdcf30..ff09a1402 100644
--- a/app/javascript/mastodon/locales/fr.json
+++ b/app/javascript/mastodon/locales/fr.json
@@ -216,7 +216,6 @@
   "notifications.column_settings.follow": "Nouveaux⋅elles abonné⋅e·s :",
   "notifications.column_settings.mention": "Mentions :",
   "notifications.column_settings.push": "Notifications",
-  "notifications.column_settings.push_meta": "Cet appareil",
   "notifications.column_settings.reblog": "Partages :",
   "notifications.column_settings.show": "Afficher dans la colonne",
   "notifications.column_settings.sound": "Émettre un son",
diff --git a/app/javascript/mastodon/locales/gl.json b/app/javascript/mastodon/locales/gl.json
index 8b830ae7b..6afa21c9f 100644
--- a/app/javascript/mastodon/locales/gl.json
+++ b/app/javascript/mastodon/locales/gl.json
@@ -216,7 +216,6 @@
   "notifications.column_settings.follow": "Novos seguidores:",
   "notifications.column_settings.mention": "Mencións:",
   "notifications.column_settings.push": "Enviar notificacións",
-  "notifications.column_settings.push_meta": "Este aparello",
   "notifications.column_settings.reblog": "Promocións:",
   "notifications.column_settings.show": "Mostrar en columna",
   "notifications.column_settings.sound": "Reproducir son",
diff --git a/app/javascript/mastodon/locales/he.json b/app/javascript/mastodon/locales/he.json
index 4aee82212..d670d8a55 100644
--- a/app/javascript/mastodon/locales/he.json
+++ b/app/javascript/mastodon/locales/he.json
@@ -216,7 +216,6 @@
   "notifications.column_settings.follow": "עוקבים חדשים:",
   "notifications.column_settings.mention": "פניות:",
   "notifications.column_settings.push": "הודעות בדחיפה",
-  "notifications.column_settings.push_meta": "מכשיר זה",
   "notifications.column_settings.reblog": "הדהודים:",
   "notifications.column_settings.show": "הצגה בטור",
   "notifications.column_settings.sound": "שמע מופעל",
diff --git a/app/javascript/mastodon/locales/hr.json b/app/javascript/mastodon/locales/hr.json
index a138cf589..b76b82e1b 100644
--- a/app/javascript/mastodon/locales/hr.json
+++ b/app/javascript/mastodon/locales/hr.json
@@ -216,7 +216,6 @@
   "notifications.column_settings.follow": "Novi sljedbenici:",
   "notifications.column_settings.mention": "Spominjanja:",
   "notifications.column_settings.push": "Push notifications",
-  "notifications.column_settings.push_meta": "This device",
   "notifications.column_settings.reblog": "Boostovi:",
   "notifications.column_settings.show": "Prikaži u stupcu",
   "notifications.column_settings.sound": "Sviraj zvuk",
diff --git a/app/javascript/mastodon/locales/hu.json b/app/javascript/mastodon/locales/hu.json
index 6a4d0ecf0..57a8b7cfa 100644
--- a/app/javascript/mastodon/locales/hu.json
+++ b/app/javascript/mastodon/locales/hu.json
@@ -216,7 +216,6 @@
   "notifications.column_settings.follow": "Új követők:",
   "notifications.column_settings.mention": "Megemítéseim:",
   "notifications.column_settings.push": "Push értesítések",
-  "notifications.column_settings.push_meta": "Ezen eszköz",
   "notifications.column_settings.reblog": "Rebloggolások:",
   "notifications.column_settings.show": "Oszlopban mutatás",
   "notifications.column_settings.sound": "Hang lejátszása",
diff --git a/app/javascript/mastodon/locales/hy.json b/app/javascript/mastodon/locales/hy.json
index 7d7090236..077748a0a 100644
--- a/app/javascript/mastodon/locales/hy.json
+++ b/app/javascript/mastodon/locales/hy.json
@@ -216,7 +216,6 @@
   "notifications.column_settings.follow": "Նոր հետեւողներ՝",
   "notifications.column_settings.mention": "Նշումներ՝",
   "notifications.column_settings.push": "Հրելու ծանուցումներ",
-  "notifications.column_settings.push_meta": "Այս սարքը",
   "notifications.column_settings.reblog": "Տարածածներից՝",
   "notifications.column_settings.show": "Ցուցադրել սյունում",
   "notifications.column_settings.sound": "Ձայն հանել",
diff --git a/app/javascript/mastodon/locales/id.json b/app/javascript/mastodon/locales/id.json
index c8ed00e9f..3d80c0949 100644
--- a/app/javascript/mastodon/locales/id.json
+++ b/app/javascript/mastodon/locales/id.json
@@ -216,7 +216,6 @@
   "notifications.column_settings.follow": "Pengikut baru:",
   "notifications.column_settings.mention": "Balasan:",
   "notifications.column_settings.push": "Push notifications",
-  "notifications.column_settings.push_meta": "This device",
   "notifications.column_settings.reblog": "Boost:",
   "notifications.column_settings.show": "Tampilkan dalam kolom",
   "notifications.column_settings.sound": "Mainkan suara",
diff --git a/app/javascript/mastodon/locales/io.json b/app/javascript/mastodon/locales/io.json
index c92ceccd1..9059b3a2b 100644
--- a/app/javascript/mastodon/locales/io.json
+++ b/app/javascript/mastodon/locales/io.json
@@ -216,7 +216,6 @@
   "notifications.column_settings.follow": "Nova sequanti:",
   "notifications.column_settings.mention": "Mencioni:",
   "notifications.column_settings.push": "Push notifications",
-  "notifications.column_settings.push_meta": "This device",
   "notifications.column_settings.reblog": "Repeti:",
   "notifications.column_settings.show": "Montrar en kolumno",
   "notifications.column_settings.sound": "Plear sono",
diff --git a/app/javascript/mastodon/locales/it.json b/app/javascript/mastodon/locales/it.json
index e9eb62e71..5d8e3fe4a 100644
--- a/app/javascript/mastodon/locales/it.json
+++ b/app/javascript/mastodon/locales/it.json
@@ -3,7 +3,7 @@
   "account.block": "Blocca @{name}",
   "account.block_domain": "Nascondi tutto da {domain}",
   "account.blocked": "Bloccato",
-  "account.direct": "Direct Message @{name}",
+  "account.direct": "Invia messaggio diretto a @{name}",
   "account.disclaimer_full": "Il profilo dell'utente mostrato qui sotto potrebbe essere incompleto.",
   "account.domain_blocked": "Dominio nascosto",
   "account.edit_profile": "Modifica profilo",
@@ -54,7 +54,7 @@
   "column.lists": "Liste",
   "column.mutes": "Utenti silenziati",
   "column.notifications": "Notifiche",
-  "column.pins": "Pinned toot",
+  "column.pins": "Toot fissati in cima",
   "column.public": "Timeline federata",
   "column_back_button.label": "Indietro",
   "column_header.hide_settings": "Nascondi impostazioni",
@@ -79,11 +79,11 @@
   "compose_form.spoiler.unmarked": "Il testo non è nascosto",
   "compose_form.spoiler_placeholder": "Content warning",
   "confirmation_modal.cancel": "Annulla",
-  "confirmations.block.confirm": "Block",
+  "confirmations.block.confirm": "Blocca",
   "confirmations.block.message": "Sei sicuro di voler bloccare {name}?",
-  "confirmations.delete.confirm": "Delete",
+  "confirmations.delete.confirm": "Cancella",
   "confirmations.delete.message": "Sei sicuro di voler cancellare questo status?",
-  "confirmations.delete_list.confirm": "Delete",
+  "confirmations.delete_list.confirm": "Cancella",
   "confirmations.delete_list.message": "Sei sicuro di voler cancellare definitivamente questa lista?",
   "confirmations.domain_block.confirm": "Nascondi intero dominio",
   "confirmations.domain_block.message": "Sei davvero sicuro che vuoi bloccare l'intero {domain}? Nella maggior parte dei casi, pochi blocchi o silenziamenti mirati sono sufficienti e preferibili. Non vedrai nessun contenuto di quel dominio né nelle timeline pubbliche né nelle notifiche. I tuoi seguaci di quel dominio saranno eliminati.",
@@ -119,7 +119,7 @@
   "empty_column.hashtag": "Non c'è ancora nessun post con questo hashtag.",
   "empty_column.home": "Non stai ancora seguendo nessuno. Visita {public} o usa la ricerca per incontrare nuove persone.",
   "empty_column.home.public_timeline": "la timeline pubblica",
-  "empty_column.list": "Non c'è niente in questo elenco ancora. Quando i membri di questo elenco postano nuovi stati, questi appariranno qui.",
+  "empty_column.list": "Non c'è ancora niente in questa lista. Quando i membri di questa lista pubblicheranno nuovi stati, appariranno qui.",
   "empty_column.lists": "Non hai ancora nessuna lista. Quando ne creerai qualcuna, comparirà qui.",
   "empty_column.mutes": "Non hai ancora silenziato nessun utente.",
   "empty_column.notifications": "Non hai ancora nessuna notifica. Interagisci con altri per iniziare conversazioni.",
@@ -179,7 +179,7 @@
   "lists.new.title_placeholder": "Titolo della nuova lista",
   "lists.search": "Cerca tra le persone che segui",
   "lists.subheading": "Le tue liste",
-  "loading_indicator.label": "Carico...",
+  "loading_indicator.label": "Caricamento...",
   "media_gallery.toggle_visible": "Imposta visibilità",
   "missing_indicator.label": "Non trovato",
   "missing_indicator.sublabel": "Risorsa non trovata",
@@ -216,7 +216,6 @@
   "notifications.column_settings.follow": "Nuovi seguaci:",
   "notifications.column_settings.mention": "Menzioni:",
   "notifications.column_settings.push": "Notifiche push",
-  "notifications.column_settings.push_meta": "Questo dispositivo",
   "notifications.column_settings.reblog": "Post condivisi:",
   "notifications.column_settings.show": "Mostra in colonna",
   "notifications.column_settings.sound": "Riproduci suono",
@@ -242,7 +241,7 @@
   "onboarding.page_three.search": "Usa la barra di ricerca per trovare persone e hashtag, come {illustration} e {introductions}. Per trovare una persona che non è su questa istanza, usa il suo nome utente completo.",
   "onboarding.page_two.compose": "Puoi scrivere dei post dalla colonna di composizione. Puoi caricare immagini, modificare le impostazioni di privacy, e aggiungere avvisi sul contenuto con le icone qui sotto.",
   "onboarding.skip": "Salta",
-  "privacy.change": "Modifica privacy post",
+  "privacy.change": "Modifica privacy del post",
   "privacy.direct.long": "Invia solo a utenti menzionati",
   "privacy.direct.short": "Diretto",
   "privacy.private.long": "Invia solo ai seguaci",
@@ -261,7 +260,7 @@
   "reply_indicator.cancel": "Annulla",
   "report.forward": "Inoltra a {target}",
   "report.forward_hint": "Questo account appartiene a un altro server. Mandare anche là una copia anonima del rapporto?",
-  "report.hint": "La segnalazione sara' invata ai tuoi moderatori di istanza. Di seguito, puoi fornire  il motivo per il quale stai segnalando questo account:",
+  "report.hint": "La segnalazione sarà inviata ai moderatori della tua istanza. Di seguito, puoi fornire il motivo per il quale stai segnalando questo account:",
   "report.placeholder": "Commenti aggiuntivi",
   "report.submit": "Invia",
   "report.target": "Invio la segnalazione {target}",
@@ -328,10 +327,10 @@
   "video.close": "Chiudi video",
   "video.exit_fullscreen": "Esci da modalità a schermo intero",
   "video.expand": "Espandi video",
-  "video.fullscreen": "Full screen",
+  "video.fullscreen": "Schermo intero",
   "video.hide": "Nascondi video",
   "video.mute": "Silenzia suono",
-  "video.pause": "Pause",
+  "video.pause": "Pausa",
   "video.play": "Avvia",
   "video.unmute": "Riattiva suono"
 }
diff --git a/app/javascript/mastodon/locales/ja.json b/app/javascript/mastodon/locales/ja.json
index 04a2e4692..ae2c7e3c4 100644
--- a/app/javascript/mastodon/locales/ja.json
+++ b/app/javascript/mastodon/locales/ja.json
@@ -95,6 +95,8 @@
   "confirmations.mute.message": "本当に{name}さんをミュートしますか?",
   "confirmations.redraft.confirm": "削除して下書きに戻す",
   "confirmations.redraft.message": "本当にこのトゥートを削除して下書きに戻しますか? このトゥートへのお気に入り登録やブーストは失われ、返信は孤立することになります。",
+  "confirmations.reply.confirm": "返信",
+  "confirmations.reply.message": "今返信すると現在作成中のメッセージが上書きされます。本当に実行しますか?",
   "confirmations.unfollow.confirm": "フォロー解除",
   "confirmations.unfollow.message": "本当に{name}さんのフォローを解除しますか?",
   "embed.instructions": "下記のコードをコピーしてウェブサイトに埋め込みます。",
@@ -221,7 +223,6 @@
   "notifications.column_settings.follow": "新しいフォロワー:",
   "notifications.column_settings.mention": "返信:",
   "notifications.column_settings.push": "プッシュ通知",
-  "notifications.column_settings.push_meta": "このデバイス",
   "notifications.column_settings.reblog": "ブースト:",
   "notifications.column_settings.show": "カラムに表示",
   "notifications.column_settings.sound": "通知音を再生",
@@ -300,6 +301,7 @@
   "status.open": "詳細を表示",
   "status.pin": "プロフィールに固定表示",
   "status.pinned": "固定されたトゥート",
+  "status.read_more": "もっと見る",
   "status.reblog": "ブースト",
   "status.reblog_private": "ブースト",
   "status.reblogged_by": "{name}さんがブースト",
diff --git a/app/javascript/mastodon/locales/ka.json b/app/javascript/mastodon/locales/ka.json
index ec6e211c6..21cd7d644 100644
--- a/app/javascript/mastodon/locales/ka.json
+++ b/app/javascript/mastodon/locales/ka.json
@@ -216,7 +216,6 @@
   "notifications.column_settings.follow": "ახალი მიმდევრები:",
   "notifications.column_settings.mention": "ხსენებები:",
   "notifications.column_settings.push": "ფუშ შეტყობინებები",
-  "notifications.column_settings.push_meta": "ეს მოწყობილობა",
   "notifications.column_settings.reblog": "ბუსტები:",
   "notifications.column_settings.show": "გამოჩნდეს სვეტში",
   "notifications.column_settings.sound": "ხმის დაკვრა",
diff --git a/app/javascript/mastodon/locales/ko.json b/app/javascript/mastodon/locales/ko.json
index 15e8524e0..8e0f9f59f 100644
--- a/app/javascript/mastodon/locales/ko.json
+++ b/app/javascript/mastodon/locales/ko.json
@@ -216,7 +216,6 @@
   "notifications.column_settings.follow": "새 팔로워:",
   "notifications.column_settings.mention": "답글:",
   "notifications.column_settings.push": "푸시 알림",
-  "notifications.column_settings.push_meta": "이 장치",
   "notifications.column_settings.reblog": "부스트:",
   "notifications.column_settings.show": "컬럼에 표시",
   "notifications.column_settings.sound": "효과음 재생",
diff --git a/app/javascript/mastodon/locales/nl.json b/app/javascript/mastodon/locales/nl.json
index f829146f1..e6b85692c 100644
--- a/app/javascript/mastodon/locales/nl.json
+++ b/app/javascript/mastodon/locales/nl.json
@@ -216,7 +216,6 @@
   "notifications.column_settings.follow": "Nieuwe volgers:",
   "notifications.column_settings.mention": "Vermeldingen:",
   "notifications.column_settings.push": "Pushmeldingen",
-  "notifications.column_settings.push_meta": "Dit apparaat",
   "notifications.column_settings.reblog": "Boosts:",
   "notifications.column_settings.show": "In kolom tonen",
   "notifications.column_settings.sound": "Geluid afspelen",
diff --git a/app/javascript/mastodon/locales/no.json b/app/javascript/mastodon/locales/no.json
index e531789a9..4a8176e82 100644
--- a/app/javascript/mastodon/locales/no.json
+++ b/app/javascript/mastodon/locales/no.json
@@ -216,7 +216,6 @@
   "notifications.column_settings.follow": "Nye følgere:",
   "notifications.column_settings.mention": "Nevnt:",
   "notifications.column_settings.push": "Push varsler",
-  "notifications.column_settings.push_meta": "Denne enheten",
   "notifications.column_settings.reblog": "Fremhevet:",
   "notifications.column_settings.show": "Vis i kolonne",
   "notifications.column_settings.sound": "Spill lyd",
diff --git a/app/javascript/mastodon/locales/oc.json b/app/javascript/mastodon/locales/oc.json
index 182710038..64cbaef55 100644
--- a/app/javascript/mastodon/locales/oc.json
+++ b/app/javascript/mastodon/locales/oc.json
@@ -216,7 +216,6 @@
   "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.show": "Mostrar dins la colomna",
   "notifications.column_settings.sound": "Emetre un son",
diff --git a/app/javascript/mastodon/locales/pl.json b/app/javascript/mastodon/locales/pl.json
index aa2535bf7..76b340af3 100644
--- a/app/javascript/mastodon/locales/pl.json
+++ b/app/javascript/mastodon/locales/pl.json
@@ -15,7 +15,7 @@
   "account.follows.empty": "Ten użytkownik nie śledzi jeszcze nikogo.",
   "account.follows_you": "Śledzi Cię",
   "account.hide_reblogs": "Ukryj podbicia od @{name}",
-  "account.link_verified_on": "Ownership of this link was checked on {date}",
+  "account.link_verified_on": "Własność tego odnośnika została potwierdzona {date}",
   "account.media": "Zawartość multimedialna",
   "account.mention": "Wspomnij o @{name}",
   "account.moved_to": "{name} przeniósł(-osła) się do:",
@@ -95,6 +95,8 @@
   "confirmations.mute.message": "Czy na pewno chcesz wyciszyć {name}?",
   "confirmations.redraft.confirm": "Usuń i przeredaguj",
   "confirmations.redraft.message": "Czy na pewno chcesz usunąć i przeredagować ten wpis? Polubienia i podbicia zostaną utracone, a odpowiedzi do oryginalnego wpisu zostaną osierocone.",
+  "confirmations.reply.confirm": "Odpowiedz",
+  "confirmations.reply.message": "W ten sposób utracisz wpis który obecnie tworzysz. Czy na pewno chcesz to zrobić?",
   "confirmations.unfollow.confirm": "Przestań śledzić",
   "confirmations.unfollow.message": "Czy na pewno zamierzasz przestać śledzić {name}?",
   "embed.instructions": "Osadź ten wpis na swojej stronie wklejając poniższy kod.",
@@ -221,7 +223,6 @@
   "notifications.column_settings.follow": "Nowi śledzący:",
   "notifications.column_settings.mention": "Wspomnienia:",
   "notifications.column_settings.push": "Powiadomienia push",
-  "notifications.column_settings.push_meta": "To urządzenie",
   "notifications.column_settings.reblog": "Podbicia:",
   "notifications.column_settings.show": "Pokaż w kolumnie",
   "notifications.column_settings.sound": "Odtwarzaj dźwięk",
@@ -300,6 +301,7 @@
   "status.open": "Rozszerz ten wpis",
   "status.pin": "Przypnij do profilu",
   "status.pinned": "Przypięty wpis",
+  "status.read_more": "Czytaj dalej",
   "status.reblog": "Podbij",
   "status.reblog_private": "Podbij dla odbiorców oryginalnego wpisu",
   "status.reblogged_by": "{name} podbił(a)",
diff --git a/app/javascript/mastodon/locales/pt-BR.json b/app/javascript/mastodon/locales/pt-BR.json
index 9c48f0bc1..3e5b5da8e 100644
--- a/app/javascript/mastodon/locales/pt-BR.json
+++ b/app/javascript/mastodon/locales/pt-BR.json
@@ -216,7 +216,6 @@
   "notifications.column_settings.follow": "Novos seguidores:",
   "notifications.column_settings.mention": "Menções:",
   "notifications.column_settings.push": "Enviar notificações",
-  "notifications.column_settings.push_meta": "Este aparelho",
   "notifications.column_settings.reblog": "Compartilhamento:",
   "notifications.column_settings.show": "Mostrar nas colunas",
   "notifications.column_settings.sound": "Reproduzir som",
diff --git a/app/javascript/mastodon/locales/pt.json b/app/javascript/mastodon/locales/pt.json
index 096fded95..e9d91f631 100644
--- a/app/javascript/mastodon/locales/pt.json
+++ b/app/javascript/mastodon/locales/pt.json
@@ -216,7 +216,6 @@
   "notifications.column_settings.follow": "Novos seguidores:",
   "notifications.column_settings.mention": "Menções:",
   "notifications.column_settings.push": "Notificações Push",
-  "notifications.column_settings.push_meta": "Este dispositivo",
   "notifications.column_settings.reblog": "Partilhas:",
   "notifications.column_settings.show": "Mostrar nas colunas",
   "notifications.column_settings.sound": "Reproduzir som",
diff --git a/app/javascript/mastodon/locales/ro.json b/app/javascript/mastodon/locales/ro.json
index fda82136e..1d6e73bfd 100644
--- a/app/javascript/mastodon/locales/ro.json
+++ b/app/javascript/mastodon/locales/ro.json
@@ -216,7 +216,6 @@
   "notifications.column_settings.follow": "Noi urmăritori:",
   "notifications.column_settings.mention": "Mențiuni:",
   "notifications.column_settings.push": "Notificări push",
-  "notifications.column_settings.push_meta": "Acest dispozitiv",
   "notifications.column_settings.reblog": "Redistribuite:",
   "notifications.column_settings.show": "Arată în coloană",
   "notifications.column_settings.sound": "Redă sunet",
diff --git a/app/javascript/mastodon/locales/ru.json b/app/javascript/mastodon/locales/ru.json
index 8961136a3..15fbfac3f 100644
--- a/app/javascript/mastodon/locales/ru.json
+++ b/app/javascript/mastodon/locales/ru.json
@@ -216,7 +216,6 @@
   "notifications.column_settings.follow": "Новые подписчики:",
   "notifications.column_settings.mention": "Упоминания:",
   "notifications.column_settings.push": "Push-уведомления",
-  "notifications.column_settings.push_meta": "Это устройство",
   "notifications.column_settings.reblog": "Продвижения:",
   "notifications.column_settings.show": "Показывать в колонке",
   "notifications.column_settings.sound": "Проигрывать звук",
diff --git a/app/javascript/mastodon/locales/sk.json b/app/javascript/mastodon/locales/sk.json
index 1264b6d08..11a6b76c1 100644
--- a/app/javascript/mastodon/locales/sk.json
+++ b/app/javascript/mastodon/locales/sk.json
@@ -216,7 +216,6 @@
   "notifications.column_settings.follow": "Noví následujúci:",
   "notifications.column_settings.mention": "Zmienenia:",
   "notifications.column_settings.push": "Push notifikácie",
-  "notifications.column_settings.push_meta": "Toto zariadenie",
   "notifications.column_settings.reblog": "Boosty:",
   "notifications.column_settings.show": "Zobraziť v stĺpci",
   "notifications.column_settings.sound": "Prehrať zvuk",
diff --git a/app/javascript/mastodon/locales/sl.json b/app/javascript/mastodon/locales/sl.json
index 6250aab62..f715abe85 100644
--- a/app/javascript/mastodon/locales/sl.json
+++ b/app/javascript/mastodon/locales/sl.json
@@ -216,7 +216,6 @@
   "notifications.column_settings.follow": "New followers:",
   "notifications.column_settings.mention": "Mentions:",
   "notifications.column_settings.push": "Push notifications",
-  "notifications.column_settings.push_meta": "This device",
   "notifications.column_settings.reblog": "Boosts:",
   "notifications.column_settings.show": "Show in column",
   "notifications.column_settings.sound": "Play sound",
diff --git a/app/javascript/mastodon/locales/sr-Latn.json b/app/javascript/mastodon/locales/sr-Latn.json
index 0ef07a170..7131d3044 100644
--- a/app/javascript/mastodon/locales/sr-Latn.json
+++ b/app/javascript/mastodon/locales/sr-Latn.json
@@ -216,7 +216,6 @@
   "notifications.column_settings.follow": "Novi pratioci:",
   "notifications.column_settings.mention": "Pominjanja:",
   "notifications.column_settings.push": "Guraj obaveštenja",
-  "notifications.column_settings.push_meta": "Ovaj uređaj",
   "notifications.column_settings.reblog": "Podrški:",
   "notifications.column_settings.show": "Prikaži u koloni",
   "notifications.column_settings.sound": "Puštaj zvuk",
diff --git a/app/javascript/mastodon/locales/sr.json b/app/javascript/mastodon/locales/sr.json
index ca2c1b225..806c0acb3 100644
--- a/app/javascript/mastodon/locales/sr.json
+++ b/app/javascript/mastodon/locales/sr.json
@@ -216,7 +216,6 @@
   "notifications.column_settings.follow": "Нови пратиоци:",
   "notifications.column_settings.mention": "Помињања:",
   "notifications.column_settings.push": "Гурај обавештења",
-  "notifications.column_settings.push_meta": "Овај уређај",
   "notifications.column_settings.reblog": "Подршки:",
   "notifications.column_settings.show": "Прикажи у колони",
   "notifications.column_settings.sound": "Пуштај звук",
diff --git a/app/javascript/mastodon/locales/sv.json b/app/javascript/mastodon/locales/sv.json
index c7df96cf5..f9129d368 100644
--- a/app/javascript/mastodon/locales/sv.json
+++ b/app/javascript/mastodon/locales/sv.json
@@ -216,7 +216,6 @@
   "notifications.column_settings.follow": "Nya följare:",
   "notifications.column_settings.mention": "Omnämningar:",
   "notifications.column_settings.push": "Push meddelanden",
-  "notifications.column_settings.push_meta": "Denna anordning",
   "notifications.column_settings.reblog": "Knuffar:",
   "notifications.column_settings.show": "Visa i kolumnen",
   "notifications.column_settings.sound": "Spela upp ljud",
diff --git a/app/javascript/mastodon/locales/ta.json b/app/javascript/mastodon/locales/ta.json
index e0237c023..427e9a3dc 100644
--- a/app/javascript/mastodon/locales/ta.json
+++ b/app/javascript/mastodon/locales/ta.json
@@ -216,7 +216,6 @@
   "notifications.column_settings.follow": "New followers:",
   "notifications.column_settings.mention": "Mentions:",
   "notifications.column_settings.push": "Push notifications",
-  "notifications.column_settings.push_meta": "This device",
   "notifications.column_settings.reblog": "Boosts:",
   "notifications.column_settings.show": "Show in column",
   "notifications.column_settings.sound": "Play sound",
diff --git a/app/javascript/mastodon/locales/te.json b/app/javascript/mastodon/locales/te.json
index 49f3ce2d9..bc13b02f1 100644
--- a/app/javascript/mastodon/locales/te.json
+++ b/app/javascript/mastodon/locales/te.json
@@ -216,7 +216,6 @@
   "notifications.column_settings.follow": "క్రొత్త అనుచరులు:",
   "notifications.column_settings.mention": "ప్రస్తావనలు:",
   "notifications.column_settings.push": "పుష్ ప్రకటనలు",
-  "notifications.column_settings.push_meta": "ఈ పరికరం",
   "notifications.column_settings.reblog": "బూస్ట్ లు:",
   "notifications.column_settings.show": "నిలువు వరుసలో చూపు",
   "notifications.column_settings.sound": "ధ్వనిని ప్లే చేయి",
diff --git a/app/javascript/mastodon/locales/th.json b/app/javascript/mastodon/locales/th.json
index 2af5bfca1..3114bca60 100644
--- a/app/javascript/mastodon/locales/th.json
+++ b/app/javascript/mastodon/locales/th.json
@@ -216,7 +216,6 @@
   "notifications.column_settings.follow": "New followers:",
   "notifications.column_settings.mention": "Mentions:",
   "notifications.column_settings.push": "Push notifications",
-  "notifications.column_settings.push_meta": "This device",
   "notifications.column_settings.reblog": "Boosts:",
   "notifications.column_settings.show": "Show in column",
   "notifications.column_settings.sound": "Play sound",
diff --git a/app/javascript/mastodon/locales/tr.json b/app/javascript/mastodon/locales/tr.json
index 68dc37e6b..a661b022b 100644
--- a/app/javascript/mastodon/locales/tr.json
+++ b/app/javascript/mastodon/locales/tr.json
@@ -216,7 +216,6 @@
   "notifications.column_settings.follow": "Yeni takipçiler:",
   "notifications.column_settings.mention": "Bahsedilenler:",
   "notifications.column_settings.push": "Push notifications",
-  "notifications.column_settings.push_meta": "This device",
   "notifications.column_settings.reblog": "Boost’lar:",
   "notifications.column_settings.show": "Bildirimlerde göster",
   "notifications.column_settings.sound": "Ses çal",
diff --git a/app/javascript/mastodon/locales/uk.json b/app/javascript/mastodon/locales/uk.json
index 5517abdf8..116dfc489 100644
--- a/app/javascript/mastodon/locales/uk.json
+++ b/app/javascript/mastodon/locales/uk.json
@@ -216,7 +216,6 @@
   "notifications.column_settings.follow": "Нові підписники:",
   "notifications.column_settings.mention": "Згадки:",
   "notifications.column_settings.push": "Push-сповіщення",
-  "notifications.column_settings.push_meta": "Цей пристрій",
   "notifications.column_settings.reblog": "Передмухи:",
   "notifications.column_settings.show": "Показати в колонці",
   "notifications.column_settings.sound": "Відтворювати звуки",
diff --git a/app/javascript/mastodon/locales/zh-CN.json b/app/javascript/mastodon/locales/zh-CN.json
index 254beab42..69ecd9431 100644
--- a/app/javascript/mastodon/locales/zh-CN.json
+++ b/app/javascript/mastodon/locales/zh-CN.json
@@ -216,7 +216,6 @@
   "notifications.column_settings.follow": "当有人关注你时:",
   "notifications.column_settings.mention": "当有人在嘟文中提及你时:",
   "notifications.column_settings.push": "推送通知",
-  "notifications.column_settings.push_meta": "此设备",
   "notifications.column_settings.reblog": "当有人转嘟了你的嘟文时:",
   "notifications.column_settings.show": "在通知栏显示",
   "notifications.column_settings.sound": "播放音效",
diff --git a/app/javascript/mastodon/locales/zh-HK.json b/app/javascript/mastodon/locales/zh-HK.json
index ce141a8a7..fc5376699 100644
--- a/app/javascript/mastodon/locales/zh-HK.json
+++ b/app/javascript/mastodon/locales/zh-HK.json
@@ -216,7 +216,6 @@
   "notifications.column_settings.follow": "關注你:",
   "notifications.column_settings.mention": "提及你:",
   "notifications.column_settings.push": "推送通知",
-  "notifications.column_settings.push_meta": "這臺設備",
   "notifications.column_settings.reblog": "轉推你的文章:",
   "notifications.column_settings.show": "在通知欄顯示",
   "notifications.column_settings.sound": "播放音效",
diff --git a/app/javascript/mastodon/locales/zh-TW.json b/app/javascript/mastodon/locales/zh-TW.json
index fe4a81170..458af6b95 100644
--- a/app/javascript/mastodon/locales/zh-TW.json
+++ b/app/javascript/mastodon/locales/zh-TW.json
@@ -216,7 +216,6 @@
   "notifications.column_settings.follow": "新的關注者:",
   "notifications.column_settings.mention": "提到:",
   "notifications.column_settings.push": "推送通知",
-  "notifications.column_settings.push_meta": "這臺設備",
   "notifications.column_settings.reblog": "轉嘟:",
   "notifications.column_settings.show": "顯示在欄位中",
   "notifications.column_settings.sound": "播放音效",
diff --git a/app/javascript/mastodon/reducers/conversations.js b/app/javascript/mastodon/reducers/conversations.js
new file mode 100644
index 000000000..f339abf56
--- /dev/null
+++ b/app/javascript/mastodon/reducers/conversations.js
@@ -0,0 +1,79 @@
+import { Map as ImmutableMap, List as ImmutableList } from 'immutable';
+import {
+  CONVERSATIONS_FETCH_REQUEST,
+  CONVERSATIONS_FETCH_SUCCESS,
+  CONVERSATIONS_FETCH_FAIL,
+  CONVERSATIONS_UPDATE,
+} from '../actions/conversations';
+import compareId from '../compare_id';
+
+const initialState = ImmutableMap({
+  items: ImmutableList(),
+  isLoading: false,
+  hasMore: true,
+});
+
+const conversationToMap = item => ImmutableMap({
+  id: item.id,
+  accounts: ImmutableList(item.accounts.map(a => a.id)),
+  last_status: item.last_status.id,
+});
+
+const updateConversation = (state, item) => state.update('items', list => {
+  const index   = list.findIndex(x => x.get('id') === item.id);
+  const newItem = conversationToMap(item);
+
+  if (index === -1) {
+    return list.unshift(newItem);
+  } else {
+    return list.set(index, newItem);
+  }
+});
+
+const expandNormalizedConversations = (state, conversations, next) => {
+  let items = ImmutableList(conversations.map(conversationToMap));
+
+  return state.withMutations(mutable => {
+    if (!items.isEmpty()) {
+      mutable.update('items', list => {
+        list = list.map(oldItem => {
+          const newItemIndex = items.findIndex(x => x.get('id') === oldItem.get('id'));
+
+          if (newItemIndex === -1) {
+            return oldItem;
+          }
+
+          const newItem = items.get(newItemIndex);
+          items = items.delete(newItemIndex);
+
+          return newItem;
+        });
+
+        list = list.concat(items);
+
+        return list.sortBy(x => x.get('last_status'), (a, b) => compareId(a, b) * -1);
+      });
+    }
+
+    if (!next) {
+      mutable.set('hasMore', false);
+    }
+
+    mutable.set('isLoading', false);
+  });
+};
+
+export default function conversations(state = initialState, action) {
+  switch (action.type) {
+  case CONVERSATIONS_FETCH_REQUEST:
+    return state.set('isLoading', true);
+  case CONVERSATIONS_FETCH_FAIL:
+    return state.set('isLoading', false);
+  case CONVERSATIONS_FETCH_SUCCESS:
+    return expandNormalizedConversations(state, action.conversations, action.next);
+  case CONVERSATIONS_UPDATE:
+    return updateConversation(state, action.conversation);
+  default:
+    return state;
+  }
+};
diff --git a/app/javascript/mastodon/reducers/index.js b/app/javascript/mastodon/reducers/index.js
index 4a981fada..d3b98d4f6 100644
--- a/app/javascript/mastodon/reducers/index.js
+++ b/app/javascript/mastodon/reducers/index.js
@@ -27,6 +27,7 @@ import custom_emojis from './custom_emojis';
 import lists from './lists';
 import listEditor from './list_editor';
 import filters from './filters';
+import conversations from './conversations';
 
 const reducers = {
   dropdown_menu,
@@ -57,6 +58,7 @@ const reducers = {
   lists,
   listEditor,
   filters,
+  conversations,
 };
 
 export default combineReducers(reducers);
diff --git a/app/javascript/mastodon/reducers/notifications.js b/app/javascript/mastodon/reducers/notifications.js
index 0b29f19fa..d71ae00ae 100644
--- a/app/javascript/mastodon/reducers/notifications.js
+++ b/app/javascript/mastodon/reducers/notifications.js
@@ -69,7 +69,7 @@ const expandNormalizedNotifications = (state, notifications, next) => {
     }
 
     if (!next) {
-      mutable.set('hasMore', true);
+      mutable.set('hasMore', false);
     }
 
     mutable.set('isLoading', false);
diff --git a/app/javascript/mastodon/service_worker/web_push_notifications.js b/app/javascript/mastodon/service_worker/web_push_notifications.js
index d61d916b1..80a4fb329 100644
--- a/app/javascript/mastodon/service_worker/web_push_notifications.js
+++ b/app/javascript/mastodon/service_worker/web_push_notifications.js
@@ -168,7 +168,7 @@ const openUrl = url =>
 
       if (webClients.length !== 0) {
         const client       = findBestClient(webClients);
-        const { pathname } = new URL(url);
+        const { pathname } = new URL(url, self.location);
 
         if (pathname.startsWith('/web/')) {
           return client.focus().then(client => client.postMessage({
diff --git a/app/javascript/styles/mailer.scss b/app/javascript/styles/mailer.scss
index dbe070f9a..d83bd4d96 100644
--- a/app/javascript/styles/mailer.scss
+++ b/app/javascript/styles/mailer.scss
@@ -169,7 +169,7 @@ p {
   font-family: Helvetica, Arial, sans-serif;
 
   @media only screen {
-    font-family: 'mastodon-font-sans-serif', sans-serif !important;
+    font-family: $font-sans-serif, sans-serif !important;
   }
 }
 
diff --git a/app/javascript/styles/mastodon/about.scss b/app/javascript/styles/mastodon/about.scss
index 228dd96f0..dc456be3e 100644
--- a/app/javascript/styles/mastodon/about.scss
+++ b/app/javascript/styles/mastodon/about.scss
@@ -16,7 +16,7 @@ $small-breakpoint: 960px;
 }
 
 .rich-formatting {
-  font-family: 'mastodon-font-sans-serif', sans-serif;
+  font-family: $font-sans-serif, sans-serif;
   font-size: 16px;
   font-weight: 400;
   font-size: 16px;
@@ -31,7 +31,7 @@ $small-breakpoint: 960px;
 
   p,
   li {
-    font-family: 'mastodon-font-sans-serif', sans-serif;
+    font-family: $font-sans-serif, sans-serif;
     font-size: 16px;
     font-weight: 400;
     font-size: 16px;
@@ -62,7 +62,7 @@ $small-breakpoint: 960px;
   }
 
   h1 {
-    font-family: 'mastodon-font-display', sans-serif;
+    font-family: $font-display, sans-serif;
     font-size: 26px;
     line-height: 30px;
     font-weight: 500;
@@ -70,7 +70,7 @@ $small-breakpoint: 960px;
     color: $secondary-text-color;
 
     small {
-      font-family: 'mastodon-font-sans-serif', sans-serif;
+      font-family: $font-sans-serif, sans-serif;
       display: block;
       font-size: 18px;
       font-weight: 400;
@@ -79,7 +79,7 @@ $small-breakpoint: 960px;
   }
 
   h2 {
-    font-family: 'mastodon-font-display', sans-serif;
+    font-family: $font-display, sans-serif;
     font-size: 22px;
     line-height: 26px;
     font-weight: 500;
@@ -88,7 +88,7 @@ $small-breakpoint: 960px;
   }
 
   h3 {
-    font-family: 'mastodon-font-display', sans-serif;
+    font-family: $font-display, sans-serif;
     font-size: 18px;
     line-height: 24px;
     font-weight: 500;
@@ -97,7 +97,7 @@ $small-breakpoint: 960px;
   }
 
   h4 {
-    font-family: 'mastodon-font-display', sans-serif;
+    font-family: $font-display, sans-serif;
     font-size: 16px;
     line-height: 24px;
     font-weight: 500;
@@ -106,7 +106,7 @@ $small-breakpoint: 960px;
   }
 
   h5 {
-    font-family: 'mastodon-font-display', sans-serif;
+    font-family: $font-display, sans-serif;
     font-size: 14px;
     line-height: 24px;
     font-weight: 500;
@@ -115,7 +115,7 @@ $small-breakpoint: 960px;
   }
 
   h6 {
-    font-family: 'mastodon-font-display', sans-serif;
+    font-family: $font-display, sans-serif;
     font-size: 12px;
     line-height: 24px;
     font-weight: 500;
@@ -180,7 +180,7 @@ $small-breakpoint: 960px;
 
   &__section {
     flex: 1 0 0;
-    font-family: 'mastodon-font-sans-serif', sans-serif;
+    font-family: $font-sans-serif, sans-serif;
     font-size: 16px;
     line-height: 28px;
     color: $primary-text-color;
@@ -221,7 +221,7 @@ $small-breakpoint: 960px;
     bottom: -40px;
 
     .panel-header {
-      font-family: 'mastodon-font-display', sans-serif;
+      font-family: $font-display, sans-serif;
       font-size: 14px;
       line-height: 24px;
       font-weight: 500;
@@ -450,7 +450,7 @@ $small-breakpoint: 960px;
 
   p,
   li {
-    font-family: 'mastodon-font-sans-serif', sans-serif;
+    font-family: $font-sans-serif, sans-serif;
     font-size: 16px;
     font-weight: 400;
     font-size: 16px;
@@ -499,7 +499,7 @@ $small-breakpoint: 960px;
   }
 
   h1 {
-    font-family: 'mastodon-font-display', sans-serif;
+    font-family: $font-display, sans-serif;
     font-size: 26px;
     line-height: 30px;
     font-weight: 500;
@@ -507,7 +507,7 @@ $small-breakpoint: 960px;
     color: $secondary-text-color;
 
     small {
-      font-family: 'mastodon-font-sans-serif', sans-serif;
+      font-family: $font-sans-serif, sans-serif;
       display: block;
       font-size: 18px;
       font-weight: 400;
@@ -516,7 +516,7 @@ $small-breakpoint: 960px;
   }
 
   h2 {
-    font-family: 'mastodon-font-display', sans-serif;
+    font-family: $font-display, sans-serif;
     font-size: 22px;
     line-height: 26px;
     font-weight: 500;
@@ -525,7 +525,7 @@ $small-breakpoint: 960px;
   }
 
   h3 {
-    font-family: 'mastodon-font-display', sans-serif;
+    font-family: $font-display, sans-serif;
     font-size: 18px;
     line-height: 24px;
     font-weight: 500;
@@ -534,7 +534,7 @@ $small-breakpoint: 960px;
   }
 
   h4 {
-    font-family: 'mastodon-font-display', sans-serif;
+    font-family: $font-display, sans-serif;
     font-size: 16px;
     line-height: 24px;
     font-weight: 500;
@@ -543,7 +543,7 @@ $small-breakpoint: 960px;
   }
 
   h5 {
-    font-family: 'mastodon-font-display', sans-serif;
+    font-family: $font-display, sans-serif;
     font-size: 14px;
     line-height: 24px;
     font-weight: 500;
@@ -552,7 +552,7 @@ $small-breakpoint: 960px;
   }
 
   h6 {
-    font-family: 'mastodon-font-display', sans-serif;
+    font-family: $font-display, sans-serif;
     font-size: 12px;
     line-height: 24px;
     font-weight: 500;
@@ -619,7 +619,7 @@ $small-breakpoint: 960px;
 
       .hero .heading {
         padding-bottom: 20px;
-        font-family: 'mastodon-font-sans-serif', sans-serif;
+        font-family: $font-sans-serif, sans-serif;
         font-size: 16px;
         font-weight: 400;
         font-size: 16px;
@@ -670,7 +670,7 @@ $small-breakpoint: 960px;
         text-decoration: none;
         padding: 12px 16px;
         line-height: 32px;
-        font-family: 'mastodon-font-display', sans-serif;
+        font-family: $font-display, sans-serif;
         font-weight: 500;
         font-size: 14px;
 
@@ -743,7 +743,7 @@ $small-breakpoint: 960px;
   .about-short {
     background: darken($ui-base-color, 4%);
     padding: 50px 0 30px;
-    font-family: 'mastodon-font-sans-serif', sans-serif;
+    font-family: $font-sans-serif, sans-serif;
     font-size: 16px;
     font-weight: 400;
     font-size: 16px;
@@ -1012,7 +1012,7 @@ $small-breakpoint: 960px;
     display: flex;
     -webkit-overflow-scrolling: touch;
     -ms-overflow-style: -ms-autohiding-scrollbar;
-    font-family: 'mastodon-font-sans-serif', sans-serif;
+    font-family: $font-sans-serif, sans-serif;
     font-size: 13px;
     line-height: 18px;
     font-weight: 400;
diff --git a/app/javascript/styles/mastodon/admin.scss b/app/javascript/styles/mastodon/admin.scss
index 9dfd89dc2..b6c771abf 100644
--- a/app/javascript/styles/mastodon/admin.scss
+++ b/app/javascript/styles/mastodon/admin.scss
@@ -432,7 +432,7 @@ $no-columns-breakpoint: 600px;
     border-radius: 0 0 4px 4px;
     padding: 10px;
     color: $darker-text-color;
-    font-family: 'mastodon-font-monospace', monospace;
+    font-family: $font-monospace, monospace;
     font-size: 12px;
     word-wrap: break-word;
     min-height: 20px;
diff --git a/app/javascript/styles/mastodon/basics.scss b/app/javascript/styles/mastodon/basics.scss
index 3bbb31e6e..746def625 100644
--- a/app/javascript/styles/mastodon/basics.scss
+++ b/app/javascript/styles/mastodon/basics.scss
@@ -6,7 +6,7 @@
 }
 
 body {
-  font-family: 'mastodon-font-sans-serif', sans-serif;
+  font-family: $font-sans-serif, sans-serif;
   background: darken($ui-base-color, 8%);
   font-size: 13px;
   line-height: 18px;
@@ -29,8 +29,8 @@ body {
     // Fira Sans => Firefox OS
     // Droid Sans => Older Androids (<4.0)
     // Helvetica Neue => Older macOS <10.11
-    // 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;
+    // $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", $font-sans-serif, sans-serif;
   }
 
   &.app-body {
diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss
index 490e26ccc..6aabf5777 100644
--- a/app/javascript/styles/mastodon/components.scss
+++ b/app/javascript/styles/mastodon/components.scss
@@ -552,7 +552,7 @@
 
       .character-counter {
         cursor: default;
-        font-family: 'mastodon-font-sans-serif', sans-serif;
+        font-family: $font-sans-serif, sans-serif;
         font-size: 14px;
         font-weight: 600;
         color: $lighter-text-color;
@@ -631,11 +631,13 @@
 
 .status__content,
 .reply-indicator__content {
+  position: relative;
   font-size: 15px;
   line-height: 20px;
   word-wrap: break-word;
   font-weight: 400;
   overflow: hidden;
+  text-overflow: ellipsis;
   white-space: pre-wrap;
   padding-top: 2px;
   color: $primary-text-color;
@@ -721,6 +723,26 @@
   }
 }
 
+.status__content.status__content--collapsed {
+  max-height: 20px * 15; // 15 lines is roughly above 500 characters
+}
+
+.status__content__read-more-button {
+  display: block;
+  font-size: 15px;
+  line-height: 20px;
+  color: lighten($ui-highlight-color, 8%);
+  border: 0;
+  background: transparent;
+  padding: 0;
+  padding-top: 8px;
+
+  &:hover,
+  &:active {
+    text-decoration: underline;
+  }
+}
+
 .status__content__spoiler-link {
   display: inline-block;
   border-radius: 2px;
@@ -803,6 +825,7 @@
 
   &.status-direct {
     background: lighten($ui-base-color, 8%);
+    border-bottom-color: lighten($ui-base-color, 12%);
   }
 
   &.light {
@@ -3009,8 +3032,7 @@ a.status-card {
   line-height: 24px;
 }
 
-.setting-toggle__label,
-.setting-meta__label {
+.setting-toggle__label {
   color: $darker-text-color;
   display: inline-block;
   margin-bottom: 14px;
@@ -3018,10 +3040,6 @@ a.status-card {
   vertical-align: middle;
 }
 
-.setting-meta__label {
-  float: right;
-}
-
 .empty-column-indicator,
 .error-column {
   color: $dark-text-color;
@@ -5122,7 +5140,7 @@ noscript {
       width: 100%;
       border: none;
       padding: 10px;
-      font-family: 'mastodon-font-monospace', monospace;
+      font-family: $font-monospace, monospace;
       background: $ui-base-color;
       color: $primary-text-color;
       font-size: 14px;
@@ -5479,3 +5497,44 @@ noscript {
     }
   }
 }
+
+.conversation {
+  padding: 14px 10px;
+  border-bottom: 1px solid lighten($ui-base-color, 8%);
+  cursor: pointer;
+
+  &__header {
+    display: flex;
+    margin-bottom: 15px;
+  }
+
+  &__avatars {
+    overflow: hidden;
+    flex: 1 1 auto;
+
+    & > div {
+      display: flex;
+      flex-wrap: none;
+      width: 900px;
+    }
+
+    .account__avatar {
+      margin-right: 10px;
+    }
+  }
+
+  &__time {
+    flex: 0 0 auto;
+    font-size: 14px;
+    color: $darker-text-color;
+    text-align: right;
+
+    .display-name {
+      color: $secondary-text-color;
+    }
+  }
+
+  .attachment-list.compact {
+    margin-top: 15px;
+  }
+}
diff --git a/app/javascript/styles/mastodon/containers.scss b/app/javascript/styles/mastodon/containers.scss
index 47582f323..44fc1e538 100644
--- a/app/javascript/styles/mastodon/containers.scss
+++ b/app/javascript/styles/mastodon/containers.scss
@@ -37,7 +37,7 @@
       outline: 0;
       padding: 12px 16px;
       line-height: 32px;
-      font-family: 'mastodon-font-display', sans-serif;
+      font-family: $font-display, sans-serif;
       font-weight: 500;
       font-size: 14px;
     }
@@ -627,7 +627,7 @@
             font-size: 18px;
             margin-bottom: 5px;
             color: $primary-text-color;
-            font-family: 'mastodon-font-display', sans-serif;
+            font-family: $font-display, sans-serif;
           }
         }
 
diff --git a/app/javascript/styles/mastodon/dashboard.scss b/app/javascript/styles/mastodon/dashboard.scss
index 949ca733f..86cf6c61b 100644
--- a/app/javascript/styles/mastodon/dashboard.scss
+++ b/app/javascript/styles/mastodon/dashboard.scss
@@ -35,7 +35,7 @@
     font-weight: 500;
     font-size: 24px;
     color: $primary-text-color;
-    font-family: 'mastodon-font-display', sans-serif;
+    font-family: $font-display, sans-serif;
     margin-bottom: 20px;
   }
 
diff --git a/app/javascript/styles/mastodon/forms.scss b/app/javascript/styles/mastodon/forms.scss
index cbd3de94c..be2bf7cea 100644
--- a/app/javascript/styles/mastodon/forms.scss
+++ b/app/javascript/styles/mastodon/forms.scss
@@ -1,7 +1,7 @@
 $no-columns-breakpoint: 600px;
 
 code {
-  font-family: 'mastodon-font-monospace', monospace;
+  font-family: $font-monospace, monospace;
   font-weight: 400;
 }
 
@@ -474,7 +474,7 @@ code {
     width: 100%;
     border: none;
     padding: 10px;
-    font-family: 'mastodon-font-monospace', monospace;
+    font-family: $font-monospace, monospace;
     background: $ui-base-color;
     color: $primary-text-color;
     font-size: 14px;
@@ -718,7 +718,7 @@ code {
 .form_admin_settings_custom_css,
 .form_admin_settings_closed_registrations_message {
   textarea {
-    font-family: 'mastodon-font-monospace', monospace;
+    font-family: $font-monospace, monospace;
   }
 }
 
@@ -742,7 +742,7 @@ code {
     border: 0;
     padding: 10px;
     font-size: 14px;
-    font-family: 'mastodon-font-monospace', monospace;
+    font-family: $font-monospace, monospace;
   }
 
   button {
diff --git a/app/javascript/styles/mastodon/rtl.scss b/app/javascript/styles/mastodon/rtl.scss
index b8c0efad8..9644f8e02 100644
--- a/app/javascript/styles/mastodon/rtl.scss
+++ b/app/javascript/styles/mastodon/rtl.scss
@@ -46,8 +46,8 @@ body.rtl {
   .column-header__buttons {
     left: 0;
     right: auto;
-    margin-left: -15px;
-    margin-right: 0;
+    margin-left: 0;
+    margin-right: -15px;
   }
 
   .column-inline-form .icon-button {
@@ -78,10 +78,6 @@ body.rtl {
     margin-right: 8px;
   }
 
-  .setting-meta__label {
-    float: left;
-  }
-
   .status__avatar {
     left: auto;
     right: 10px;
@@ -145,6 +141,19 @@ body.rtl {
     margin-right: 6px;
   }
 
+  .status__action-bar {
+
+    &__counter {
+      margin-right: 0;
+      margin-left: 11px;
+
+      .status__action-bar-button {
+        margin-right: 0;
+        margin-left: 4px;
+      }
+    }
+  }
+
   .status__action-bar-button {
     float: right;
     margin-right: 0;
@@ -285,4 +294,18 @@ body.rtl {
       }
     }
   }
+
+  .public-layout {
+    .header {
+      .nav-button {
+        margin-left: 8px;
+        margin-right: 0;
+      }
+    }
+
+    .public-account-header__tabs {
+      margin-left: 0;
+      margin-right: 20px;
+    }
+  }
 }
diff --git a/app/javascript/styles/mastodon/tables.scss b/app/javascript/styles/mastodon/tables.scss
index c2206cf55..adb75afe5 100644
--- a/app/javascript/styles/mastodon/tables.scss
+++ b/app/javascript/styles/mastodon/tables.scss
@@ -90,7 +90,7 @@
 }
 
 samp {
-  font-family: 'mastodon-font-monospace', monospace;
+  font-family: $font-monospace, monospace;
 }
 
 button.table-action-link {
diff --git a/app/javascript/styles/mastodon/variables.scss b/app/javascript/styles/mastodon/variables.scss
index 009f0a3c9..a82c44229 100644
--- a/app/javascript/styles/mastodon/variables.scss
+++ b/app/javascript/styles/mastodon/variables.scss
@@ -48,3 +48,7 @@ $media-modal-media-max-width: 100%;
 $media-modal-media-max-height: 80%;
 
 $no-gap-breakpoint: 415px;
+
+$font-sans-serif: 'mastodon-font-sans-serif' !default;
+$font-display: 'mastodon-font-display' !default;
+$font-monospace: 'mastodon-font-monospace' !default;