about summary refs log tree commit diff
path: root/app/javascript
diff options
context:
space:
mode:
authorThibG <thib@sitedethib.com>2020-03-22 17:59:47 +0100
committerGitHub <noreply@github.com>2020-03-22 17:59:47 +0100
commitd101438b9d1bd827c191605cb21d58f83de2124d (patch)
treeb36d73f45175add7aa114bb42fca5a528a9b24a4 /app/javascript
parent7115b0b8c99b7e88aee264be75945e592dec33e4 (diff)
parent9da4bd098cac88c3e44157765adcff01445ba31a (diff)
Merge pull request #1305 from ThibG/glitch-soc/merge-upstream
Merge upstream changes
Diffstat (limited to 'app/javascript')
-rw-r--r--app/javascript/core/admin.js2
-rw-r--r--app/javascript/core/public.js2
-rw-r--r--app/javascript/core/settings.js2
-rw-r--r--app/javascript/flavours/glitch/actions/compose.js23
-rw-r--r--app/javascript/flavours/glitch/components/domain.js2
-rw-r--r--app/javascript/flavours/glitch/containers/domain_container.js2
-rw-r--r--app/javascript/flavours/glitch/features/account/components/header.js4
-rw-r--r--app/javascript/flavours/glitch/features/domain_blocks/index.js6
-rw-r--r--app/javascript/flavours/glitch/features/getting_started/components/announcements.js4
-rw-r--r--app/javascript/flavours/glitch/packs/common.js2
-rw-r--r--app/javascript/flavours/glitch/packs/public.js2
-rw-r--r--app/javascript/flavours/glitch/packs/settings.js2
-rw-r--r--app/javascript/flavours/glitch/util/log_out.js2
-rw-r--r--app/javascript/mastodon/actions/compose.js23
-rw-r--r--app/javascript/mastodon/common.js2
-rw-r--r--app/javascript/mastodon/components/domain.js2
-rw-r--r--app/javascript/mastodon/components/scrollable_list.js20
-rw-r--r--app/javascript/mastodon/containers/domain_container.js2
-rw-r--r--app/javascript/mastodon/features/account/components/header.js4
-rw-r--r--app/javascript/mastodon/features/compose/components/action_bar.js2
-rw-r--r--app/javascript/mastodon/features/domain_blocks/index.js6
-rw-r--r--app/javascript/mastodon/features/getting_started/components/announcements.js4
-rw-r--r--app/javascript/mastodon/features/status/components/detailed_status.js2
-rw-r--r--app/javascript/mastodon/locales/defaultMessages.json16
-rw-r--r--app/javascript/mastodon/locales/en.json10
-rw-r--r--app/javascript/mastodon/utils/log_out.js2
-rw-r--r--app/javascript/packs/public.js2
27 files changed, 100 insertions, 52 deletions
diff --git a/app/javascript/core/admin.js b/app/javascript/core/admin.js
index e4d683dd0..09da9efd3 100644
--- a/app/javascript/core/admin.js
+++ b/app/javascript/core/admin.js
@@ -1,6 +1,6 @@
 //  This file will be loaded on admin pages, regardless of theme.
 
-import { delegate } from 'rails-ujs';
+import { delegate } from '@rails/ujs';
 import ready from '../mastodon/ready';
 
 const batchCheckboxClassName = '.batch-checkbox input[type="checkbox"]';
diff --git a/app/javascript/core/public.js b/app/javascript/core/public.js
index 0f4222139..344c05181 100644
--- a/app/javascript/core/public.js
+++ b/app/javascript/core/public.js
@@ -3,7 +3,7 @@
 import createHistory from 'history/createBrowserHistory';
 import ready from '../mastodon/ready';
 
-const { delegate } = require('rails-ujs');
+const { delegate } = require('@rails/ujs');
 const { length } = require('stringz');
 
 delegate(document, '.webapp-btn', 'click', ({ target, button }) => {
diff --git a/app/javascript/core/settings.js b/app/javascript/core/settings.js
index e0cb944e0..e02c91cc7 100644
--- a/app/javascript/core/settings.js
+++ b/app/javascript/core/settings.js
@@ -1,7 +1,7 @@
 //  This file will be loaded on settings pages, regardless of theme.
 
 import escapeTextContentForBrowser from 'escape-html';
-const { delegate } = require('rails-ujs');
+const { delegate } = require('@rails/ujs');
 import emojify from '../mastodon/features/emoji/emoji';
 
 delegate(document, '#account_display_name', 'input', ({ target }) => {
diff --git a/app/javascript/flavours/glitch/actions/compose.js b/app/javascript/flavours/glitch/actions/compose.js
index 0be746048..f98cb7bf8 100644
--- a/app/javascript/flavours/glitch/actions/compose.js
+++ b/app/javascript/flavours/glitch/actions/compose.js
@@ -259,12 +259,31 @@ export function uploadCompose(files) {
         // Account for disparity in size of original image and resized data
         total += file.size - f.size;
 
-        return api(getState).post('/api/v1/media', data, {
+        return api(getState).post('/api/v2/media', data, {
           onUploadProgress: function({ loaded }){
             progress[i] = loaded;
             dispatch(uploadComposeProgress(progress.reduce((a, v) => a + v, 0), total));
           },
-        }).then(({ data }) => dispatch(uploadComposeSuccess(data, f)));
+        }).then(({ status, data }) => {
+          // If server-side processing of the media attachment has not completed yet,
+          // poll the server until it is, before showing the media attachment as uploaded
+
+          if (status === 200) {
+            dispatch(uploadComposeSuccess(data, f));
+          } else if (status === 202) {
+            const poll = () => {
+              api(getState).get(`/api/v1/media/${data.id}`).then(response => {
+                if (response.status === 200) {
+                  dispatch(uploadComposeSuccess(response.data, f));
+                } else if (response.status === 206) {
+                  setTimeout(() => poll(), 1000);
+                }
+              }).catch(error => dispatch(uploadComposeFail(error)));
+            };
+
+            poll();
+          }
+        });
       }).catch(error => dispatch(uploadComposeFail(error)));
     };
   };
diff --git a/app/javascript/flavours/glitch/components/domain.js b/app/javascript/flavours/glitch/components/domain.js
index 85729ca94..697065d87 100644
--- a/app/javascript/flavours/glitch/components/domain.js
+++ b/app/javascript/flavours/glitch/components/domain.js
@@ -5,7 +5,7 @@ import { defineMessages, injectIntl } from 'react-intl';
 import ImmutablePureComponent from 'react-immutable-pure-component';
 
 const messages = defineMessages({
-  unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unhide {domain}' },
+  unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unblock domain {domain}' },
 });
 
 export default @injectIntl
diff --git a/app/javascript/flavours/glitch/containers/domain_container.js b/app/javascript/flavours/glitch/containers/domain_container.js
index 52d5c1613..e92e102ab 100644
--- a/app/javascript/flavours/glitch/containers/domain_container.js
+++ b/app/javascript/flavours/glitch/containers/domain_container.js
@@ -6,7 +6,7 @@ import Domain from '../components/domain';
 import { openModal } from '../actions/modal';
 
 const messages = defineMessages({
-  blockDomainConfirm: { id: 'confirmations.domain_block.confirm', defaultMessage: 'Hide entire domain' },
+  blockDomainConfirm: { id: 'confirmations.domain_block.confirm', defaultMessage: 'Block entire domain' },
 });
 
 const makeMapStateToProps = () => {
diff --git a/app/javascript/flavours/glitch/features/account/components/header.js b/app/javascript/flavours/glitch/features/account/components/header.js
index efcb243ba..fb0f165ff 100644
--- a/app/javascript/flavours/glitch/features/account/components/header.js
+++ b/app/javascript/flavours/glitch/features/account/components/header.js
@@ -40,7 +40,7 @@ const messages = defineMessages({
   favourites: { id: 'navigation_bar.favourites', defaultMessage: 'Favourites' },
   lists: { id: 'navigation_bar.lists', defaultMessage: 'Lists' },
   blocks: { id: 'navigation_bar.blocks', defaultMessage: 'Blocked users' },
-  domain_blocks: { id: 'navigation_bar.domain_blocks', defaultMessage: 'Hidden domains' },
+  domain_blocks: { id: 'navigation_bar.domain_blocks', defaultMessage: 'Blocked domains' },
   mutes: { id: 'navigation_bar.mutes', defaultMessage: 'Muted users' },
   endorse: { id: 'account.endorse', defaultMessage: 'Feature on profile' },
   unendorse: { id: 'account.unendorse', defaultMessage: 'Don\'t feature on profile' },
@@ -136,7 +136,7 @@ class Header extends ImmutablePureComponent {
     if (me !== account.get('id') && account.getIn(['relationship', 'muting'])) {
       info.push(<span className='relationship-tag'><FormattedMessage id='account.muted' defaultMessage='Muted' /></span>);
     } else if (me !== account.get('id') && account.getIn(['relationship', 'domain_blocking'])) {
-      info.push(<span className='relationship-tag'><FormattedMessage id='account.domain_blocked' defaultMessage='Domain hidden' /></span>);
+      info.push(<span className='relationship-tag'><FormattedMessage id='account.domain_blocked' defaultMessage='Domain blocked' /></span>);
     }
 
     if (me !== account.get('id')) {
diff --git a/app/javascript/flavours/glitch/features/domain_blocks/index.js b/app/javascript/flavours/glitch/features/domain_blocks/index.js
index c53d32ebb..acce87d5a 100644
--- a/app/javascript/flavours/glitch/features/domain_blocks/index.js
+++ b/app/javascript/flavours/glitch/features/domain_blocks/index.js
@@ -13,8 +13,8 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
 import ScrollableList from 'flavours/glitch/components/scrollable_list';
 
 const messages = defineMessages({
-  heading: { id: 'column.domain_blocks', defaultMessage: 'Hidden domains' },
-  unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unhide {domain}' },
+  heading: { id: 'column.domain_blocks', defaultMessage: 'Blocked domains' },
+  unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unblock domain {domain}' },
 });
 
 const mapStateToProps = state => ({
@@ -54,7 +54,7 @@ class Blocks extends ImmutablePureComponent {
       );
     }
 
-    const emptyMessage = <FormattedMessage id='empty_column.domain_blocks' defaultMessage='There are no hidden domains yet.' />;
+    const emptyMessage = <FormattedMessage id='empty_column.domain_blocks' defaultMessage='There are no blocked domains yet.' />;
 
     return (
       <Column bindToDocument={!multiColumn} icon='minus-circle' heading={intl.formatMessage(messages.heading)}>
diff --git a/app/javascript/flavours/glitch/features/getting_started/components/announcements.js b/app/javascript/flavours/glitch/features/getting_started/components/announcements.js
index 48e1766e2..5f6e6d84c 100644
--- a/app/javascript/flavours/glitch/features/getting_started/components/announcements.js
+++ b/app/javascript/flavours/glitch/features/getting_started/components/announcements.js
@@ -389,7 +389,7 @@ class Announcements extends ImmutablePureComponent {
   _markAnnouncementAsRead () {
     const { dismissAnnouncement, announcements } = this.props;
     const { index } = this.state;
-    const announcement = announcements.get(index);
+    const announcement = announcements.get(index) || announcements.get(index - 1);
     if (!announcement.get('read')) dismissAnnouncement(announcement.get('id'));
   }
 
@@ -407,7 +407,7 @@ class Announcements extends ImmutablePureComponent {
 
   render () {
     const { announcements, intl } = this.props;
-    const { index } = this.state;
+    const index = this.state.index < announcements.size ? this.state.index : announcements.size - 1;
 
     if (announcements.isEmpty()) {
       return null;
diff --git a/app/javascript/flavours/glitch/packs/common.js b/app/javascript/flavours/glitch/packs/common.js
index 94a4e6ee4..1fedc890a 100644
--- a/app/javascript/flavours/glitch/packs/common.js
+++ b/app/javascript/flavours/glitch/packs/common.js
@@ -1,4 +1,4 @@
-import { start } from 'rails-ujs';
+import { start } from '@rails/ujs';
 
 start();
 
diff --git a/app/javascript/flavours/glitch/packs/public.js b/app/javascript/flavours/glitch/packs/public.js
index d8a97704f..066479fa6 100644
--- a/app/javascript/flavours/glitch/packs/public.js
+++ b/app/javascript/flavours/glitch/packs/public.js
@@ -5,7 +5,7 @@ import loadKeyboardExtensions from 'flavours/glitch/util/load_keyboard_extension
 function main() {
   const IntlMessageFormat = require('intl-messageformat').default;
   const { timeAgoString } = require('flavours/glitch/components/relative_timestamp');
-  const { delegate } = require('rails-ujs');
+  const { delegate } = require('@rails/ujs');
   const emojify = require('flavours/glitch/util/emoji').default;
   const { getLocale } = require('locales');
   const { messages } = getLocale();
diff --git a/app/javascript/flavours/glitch/packs/settings.js b/app/javascript/flavours/glitch/packs/settings.js
index edf1b82e0..8a9f23505 100644
--- a/app/javascript/flavours/glitch/packs/settings.js
+++ b/app/javascript/flavours/glitch/packs/settings.js
@@ -3,7 +3,7 @@ import ready from 'flavours/glitch/util/ready';
 import loadKeyboardExtensions from 'flavours/glitch/util/load_keyboard_extensions';
 
 function main() {
-  const { delegate } = require('rails-ujs');
+  const { delegate } = require('@rails/ujs');
 
   delegate(document, '.sidebar__toggle__icon', 'click', () => {
     const target = document.querySelector('.sidebar ul');
diff --git a/app/javascript/flavours/glitch/util/log_out.js b/app/javascript/flavours/glitch/util/log_out.js
index 8e1659293..42dcee03e 100644
--- a/app/javascript/flavours/glitch/util/log_out.js
+++ b/app/javascript/flavours/glitch/util/log_out.js
@@ -1,4 +1,4 @@
-import Rails from 'rails-ujs';
+import Rails from '@rails/ujs';
 import { signOutLink } from 'flavours/glitch/util/backend_links';
 
 export const logOut = () => {
diff --git a/app/javascript/mastodon/actions/compose.js b/app/javascript/mastodon/actions/compose.js
index c3c6ff1a1..6b73fc90e 100644
--- a/app/javascript/mastodon/actions/compose.js
+++ b/app/javascript/mastodon/actions/compose.js
@@ -230,12 +230,31 @@ export function uploadCompose(files) {
         // Account for disparity in size of original image and resized data
         total += file.size - f.size;
 
-        return api(getState).post('/api/v1/media', data, {
+        return api(getState).post('/api/v2/media', data, {
           onUploadProgress: function({ loaded }){
             progress[i] = loaded;
             dispatch(uploadComposeProgress(progress.reduce((a, v) => a + v, 0), total));
           },
-        }).then(({ data }) => dispatch(uploadComposeSuccess(data, f)));
+        }).then(({ status, data }) => {
+          // If server-side processing of the media attachment has not completed yet,
+          // poll the server until it is, before showing the media attachment as uploaded
+
+          if (status === 200) {
+            dispatch(uploadComposeSuccess(data, f));
+          } else if (status === 202) {
+            const poll = () => {
+              api(getState).get(`/api/v1/media/${data.id}`).then(response => {
+                if (response.status === 200) {
+                  dispatch(uploadComposeSuccess(response.data, f));
+                } else if (response.status === 206) {
+                  setTimeout(() => poll(), 1000);
+                }
+              }).catch(error => dispatch(uploadComposeFail(error)));
+            };
+
+            poll();
+          }
+        });
       }).catch(error => dispatch(uploadComposeFail(error)));
     };
   };
diff --git a/app/javascript/mastodon/common.js b/app/javascript/mastodon/common.js
index fba21316a..6818aa5d5 100644
--- a/app/javascript/mastodon/common.js
+++ b/app/javascript/mastodon/common.js
@@ -1,4 +1,4 @@
-import Rails from 'rails-ujs';
+import Rails from '@rails/ujs';
 
 export function start() {
   require('font-awesome/css/font-awesome.css');
diff --git a/app/javascript/mastodon/components/domain.js b/app/javascript/mastodon/components/domain.js
index 85729ca94..697065d87 100644
--- a/app/javascript/mastodon/components/domain.js
+++ b/app/javascript/mastodon/components/domain.js
@@ -5,7 +5,7 @@ import { defineMessages, injectIntl } from 'react-intl';
 import ImmutablePureComponent from 'react-immutable-pure-component';
 
 const messages = defineMessages({
-  unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unhide {domain}' },
+  unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unblock domain {domain}' },
 });
 
 export default @injectIntl
diff --git a/app/javascript/mastodon/components/scrollable_list.js b/app/javascript/mastodon/components/scrollable_list.js
index 3a490e78e..65ca43911 100644
--- a/app/javascript/mastodon/components/scrollable_list.js
+++ b/app/javascript/mastodon/components/scrollable_list.js
@@ -82,15 +82,19 @@ export default class ScrollableList extends PureComponent {
   lastScrollWasSynthetic = false;
   scrollToTopOnMouseIdle = false;
 
+  _getScrollingElement = () => {
+    if (this.props.bindToDocument) {
+      return (document.scrollingElement || document.body);
+    } else {
+      return this.node;
+    }
+  }
+
   setScrollTop = newScrollTop => {
     if (this.getScrollTop() !== newScrollTop) {
       this.lastScrollWasSynthetic = true;
 
-      if (this.props.bindToDocument) {
-        document.scrollingElement.scrollTop = newScrollTop;
-      } else {
-        this.node.scrollTop = newScrollTop;
-      }
+      this._getScrollingElement().scrollTop = newScrollTop;
     }
   };
 
@@ -151,15 +155,15 @@ export default class ScrollableList extends PureComponent {
   }
 
   getScrollTop = () => {
-    return this.props.bindToDocument ? document.scrollingElement.scrollTop : this.node.scrollTop;
+    return this._getScrollingElement().scrollTop;
   }
 
   getScrollHeight = () => {
-    return this.props.bindToDocument ? document.scrollingElement.scrollHeight : this.node.scrollHeight;
+    return this._getScrollingElement().scrollHeight;
   }
 
   getClientHeight = () => {
-    return this.props.bindToDocument ? document.scrollingElement.clientHeight : this.node.clientHeight;
+    return this._getScrollingElement().clientHeight;
   }
 
   updateScrollBottom = (snapshot) => {
diff --git a/app/javascript/mastodon/containers/domain_container.js b/app/javascript/mastodon/containers/domain_container.js
index 813178bbf..8a8ba1df1 100644
--- a/app/javascript/mastodon/containers/domain_container.js
+++ b/app/javascript/mastodon/containers/domain_container.js
@@ -6,7 +6,7 @@ import Domain from '../components/domain';
 import { openModal } from '../actions/modal';
 
 const messages = defineMessages({
-  blockDomainConfirm: { id: 'confirmations.domain_block.confirm', defaultMessage: 'Hide entire domain' },
+  blockDomainConfirm: { id: 'confirmations.domain_block.confirm', defaultMessage: 'Block entire domain' },
 });
 
 const makeMapStateToProps = () => {
diff --git a/app/javascript/mastodon/features/account/components/header.js b/app/javascript/mastodon/features/account/components/header.js
index 35cc3952f..92780a70b 100644
--- a/app/javascript/mastodon/features/account/components/header.js
+++ b/app/javascript/mastodon/features/account/components/header.js
@@ -39,7 +39,7 @@ const messages = defineMessages({
   favourites: { id: 'navigation_bar.favourites', defaultMessage: 'Favourites' },
   lists: { id: 'navigation_bar.lists', defaultMessage: 'Lists' },
   blocks: { id: 'navigation_bar.blocks', defaultMessage: 'Blocked users' },
-  domain_blocks: { id: 'navigation_bar.domain_blocks', defaultMessage: 'Hidden domains' },
+  domain_blocks: { id: 'navigation_bar.domain_blocks', defaultMessage: 'Blocked domains' },
   mutes: { id: 'navigation_bar.mutes', defaultMessage: 'Muted users' },
   endorse: { id: 'account.endorse', defaultMessage: 'Feature on profile' },
   unendorse: { id: 'account.unendorse', defaultMessage: 'Don\'t feature on profile' },
@@ -142,7 +142,7 @@ class Header extends ImmutablePureComponent {
     if (me !== account.get('id') && account.getIn(['relationship', 'muting'])) {
       info.push(<span key='muted' className='relationship-tag'><FormattedMessage id='account.muted' defaultMessage='Muted' /></span>);
     } else if (me !== account.get('id') && account.getIn(['relationship', 'domain_blocking'])) {
-      info.push(<span key='domain_blocked' className='relationship-tag'><FormattedMessage id='account.domain_blocked' defaultMessage='Domain hidden' /></span>);
+      info.push(<span key='domain_blocked' className='relationship-tag'><FormattedMessage id='account.domain_blocked' defaultMessage='Domain blocked' /></span>);
     }
 
     if (me !== account.get('id')) {
diff --git a/app/javascript/mastodon/features/compose/components/action_bar.js b/app/javascript/mastodon/features/compose/components/action_bar.js
index dd2632796..07d92bb7e 100644
--- a/app/javascript/mastodon/features/compose/components/action_bar.js
+++ b/app/javascript/mastodon/features/compose/components/action_bar.js
@@ -16,6 +16,7 @@ const messages = defineMessages({
   mutes: { id: 'navigation_bar.mutes', defaultMessage: 'Muted users' },
   filters: { id: 'navigation_bar.filters', defaultMessage: 'Muted words' },
   logout: { id: 'navigation_bar.logout', defaultMessage: 'Logout' },
+  bookmarks: { id: 'navigation_bar.bookmarks', defaultMessage: 'Bookmarks' },
 });
 
 export default @injectIntl
@@ -42,6 +43,7 @@ class ActionBar extends React.PureComponent {
     menu.push(null);
     menu.push({ text: intl.formatMessage(messages.follow_requests), to: '/follow_requests' });
     menu.push({ text: intl.formatMessage(messages.favourites), to: '/favourites' });
+    menu.push({ text: intl.formatMessage(messages.bookmarks), to: '/bookmarks' });
     menu.push({ text: intl.formatMessage(messages.lists), to: '/lists' });
     menu.push(null);
     menu.push({ text: intl.formatMessage(messages.mutes), to: '/mutes' });
diff --git a/app/javascript/mastodon/features/domain_blocks/index.js b/app/javascript/mastodon/features/domain_blocks/index.js
index 06533d5ac..a6d988912 100644
--- a/app/javascript/mastodon/features/domain_blocks/index.js
+++ b/app/javascript/mastodon/features/domain_blocks/index.js
@@ -13,8 +13,8 @@ import { fetchDomainBlocks, expandDomainBlocks } from '../../actions/domain_bloc
 import ScrollableList from '../../components/scrollable_list';
 
 const messages = defineMessages({
-  heading: { id: 'column.domain_blocks', defaultMessage: 'Hidden domains' },
-  unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unhide {domain}' },
+  heading: { id: 'column.domain_blocks', defaultMessage: 'Blocked domains' },
+  unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unblock domain {domain}' },
 });
 
 const mapStateToProps = state => ({
@@ -55,7 +55,7 @@ class Blocks extends ImmutablePureComponent {
       );
     }
 
-    const emptyMessage = <FormattedMessage id='empty_column.domain_blocks' defaultMessage='There are no hidden domains yet.' />;
+    const emptyMessage = <FormattedMessage id='empty_column.domain_blocks' defaultMessage='There are no blocked domains yet.' />;
 
     return (
       <Column bindToDocument={!multiColumn} icon='minus-circle' heading={intl.formatMessage(messages.heading)}>
diff --git a/app/javascript/mastodon/features/getting_started/components/announcements.js b/app/javascript/mastodon/features/getting_started/components/announcements.js
index fb4a6bf0d..ec57c41b2 100644
--- a/app/javascript/mastodon/features/getting_started/components/announcements.js
+++ b/app/javascript/mastodon/features/getting_started/components/announcements.js
@@ -389,7 +389,7 @@ class Announcements extends ImmutablePureComponent {
   _markAnnouncementAsRead () {
     const { dismissAnnouncement, announcements } = this.props;
     const { index } = this.state;
-    const announcement = announcements.get(index);
+    const announcement = announcements.get(index) || announcements.get(index - 1);
     if (!announcement.get('read')) dismissAnnouncement(announcement.get('id'));
   }
 
@@ -407,7 +407,7 @@ class Announcements extends ImmutablePureComponent {
 
   render () {
     const { announcements, intl } = this.props;
-    const { index } = this.state;
+    const index = this.state.index < announcements.size ? this.state.index : announcements.size - 1;
 
     if (announcements.isEmpty()) {
       return null;
diff --git a/app/javascript/mastodon/features/status/components/detailed_status.js b/app/javascript/mastodon/features/status/components/detailed_status.js
index 2fec247c1..7a82fa13a 100644
--- a/app/javascript/mastodon/features/status/components/detailed_status.js
+++ b/app/javascript/mastodon/features/status/components/detailed_status.js
@@ -166,7 +166,7 @@ export default class DetailedStatus extends ImmutablePureComponent {
       reblogIcon = 'lock';
     }
 
-    if (status.get('visibility') === 'private') {
+    if (['private', 'direct'].includes(status.get('visibility'))) {
       reblogLink = <Icon id={reblogIcon} />;
     } else if (this.context.router) {
       reblogLink = (
diff --git a/app/javascript/mastodon/locales/defaultMessages.json b/app/javascript/mastodon/locales/defaultMessages.json
index 93cfa2431..225b7ade2 100644
--- a/app/javascript/mastodon/locales/defaultMessages.json
+++ b/app/javascript/mastodon/locales/defaultMessages.json
@@ -523,7 +523,7 @@
   {
     "descriptors": [
       {
-        "defaultMessage": "Hide entire domain",
+        "defaultMessage": "Block entire domain",
         "id": "confirmations.domain_block.confirm"
       },
       {
@@ -737,7 +737,7 @@
         "id": "navigation_bar.blocks"
       },
       {
-        "defaultMessage": "Hidden domains",
+        "defaultMessage": "Blocked domains",
         "id": "navigation_bar.domain_blocks"
       },
       {
@@ -773,7 +773,7 @@
         "id": "account.muted"
       },
       {
-        "defaultMessage": "Domain hidden",
+        "defaultMessage": "Domain blocked",
         "id": "account.domain_blocked"
       },
       {
@@ -917,6 +917,10 @@
       {
         "defaultMessage": "Logout",
         "id": "navigation_bar.logout"
+      },
+      {
+        "defaultMessage": "Bookmarks",
+        "id": "navigation_bar.bookmarks"
       }
     ],
     "path": "app/javascript/mastodon/features/compose/components/action_bar.json"
@@ -1466,7 +1470,7 @@
   {
     "descriptors": [
       {
-        "defaultMessage": "Hidden domains",
+        "defaultMessage": "Blocked domains",
         "id": "column.domain_blocks"
       },
       {
@@ -1474,7 +1478,7 @@
         "id": "account.unblock_domain"
       },
       {
-        "defaultMessage": "There are no hidden domains yet.",
+        "defaultMessage": "There are no blocked domains yet.",
         "id": "empty_column.domain_blocks"
       }
     ],
@@ -2957,4 +2961,4 @@
     ],
     "path": "app/javascript/mastodon/features/video/index.json"
   }
-]
+]
\ No newline at end of file
diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json
index 3ec951903..703bbcaac 100644
--- a/app/javascript/mastodon/locales/en.json
+++ b/app/javascript/mastodon/locales/en.json
@@ -7,7 +7,7 @@
   "account.blocked": "Blocked",
   "account.cancel_follow_request": "Cancel follow request",
   "account.direct": "Direct message @{name}",
-  "account.domain_blocked": "Domain hidden",
+  "account.domain_blocked": "Domain blocked",
   "account.edit_profile": "Edit profile",
   "account.endorse": "Feature on profile",
   "account.follow": "Follow",
@@ -57,7 +57,7 @@
   "column.community": "Local timeline",
   "column.direct": "Direct messages",
   "column.directory": "Browse profiles",
-  "column.domain_blocks": "Hidden domains",
+  "column.domain_blocks": "Blocked domains",
   "column.favourites": "Favourites",
   "column.follow_requests": "Follow requests",
   "column.home": "Home",
@@ -107,7 +107,7 @@
   "confirmations.delete.message": "Are you sure you want to delete this status?",
   "confirmations.delete_list.confirm": "Delete",
   "confirmations.delete_list.message": "Are you sure you want to permanently delete this list?",
-  "confirmations.domain_block.confirm": "Hide entire domain",
+  "confirmations.domain_block.confirm": "Block 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.logout.confirm": "Log out",
   "confirmations.logout.message": "Are you sure you want to log out?",
@@ -150,7 +150,7 @@
   "empty_column.bookmarked_statuses": "You don't have any bookmarked toots yet. When you bookmark one, it will show up here.",
   "empty_column.community": "The local timeline is empty. Write something publicly to get the ball rolling!",
   "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.",
-  "empty_column.domain_blocks": "There are no hidden domains yet.",
+  "empty_column.domain_blocks": "There are no blocked domains yet.",
   "empty_column.favourited_statuses": "You don't have any favourite toots yet. When you favourite one, it will show up here.",
   "empty_column.favourites": "No one has favourited this toot yet. When someone does, they will show up here.",
   "empty_column.follow_requests": "You don't have any follow requests yet. When you receive one, it will show up here.",
@@ -269,7 +269,7 @@
   "navigation_bar.compose": "Compose new toot",
   "navigation_bar.direct": "Direct messages",
   "navigation_bar.discover": "Discover",
-  "navigation_bar.domain_blocks": "Hidden domains",
+  "navigation_bar.domain_blocks": "Blocked domains",
   "navigation_bar.edit_profile": "Edit profile",
   "navigation_bar.favourites": "Favourites",
   "navigation_bar.filters": "Muted words",
diff --git a/app/javascript/mastodon/utils/log_out.js b/app/javascript/mastodon/utils/log_out.js
index b43417f4b..3a4cc8ecb 100644
--- a/app/javascript/mastodon/utils/log_out.js
+++ b/app/javascript/mastodon/utils/log_out.js
@@ -1,4 +1,4 @@
-import Rails from 'rails-ujs';
+import Rails from '@rails/ujs';
 
 export const logOut = () => {
   const form = document.createElement('form');
diff --git a/app/javascript/packs/public.js b/app/javascript/packs/public.js
index 13cb5d548..dc85164d0 100644
--- a/app/javascript/packs/public.js
+++ b/app/javascript/packs/public.js
@@ -8,7 +8,7 @@ start();
 function main() {
   const IntlMessageFormat = require('intl-messageformat').default;
   const { timeAgoString } = require('../mastodon/components/relative_timestamp');
-  const { delegate } = require('rails-ujs');
+  const { delegate } = require('@rails/ujs');
   const emojify = require('../mastodon/features/emoji/emoji').default;
   const { getLocale } = require('../mastodon/locales');
   const { messages } = getLocale();