about summary refs log tree commit diff
diff options
context:
space:
mode:
authorReverite <github@reverite.sh>2019-07-14 17:11:17 -0700
committerReverite <github@reverite.sh>2019-07-14 17:11:17 -0700
commit40fd56351f0a466e0e3785cca4ee27f2a9060e8a (patch)
treee4fa448c7b88988be3b8cb595d0671b21b49ab65
parent7d99f12fd03cf2f861d0747c3bbcd4a8cf454d99 (diff)
parent5ccd011cc3bafc00a557dabbb47da004f3d4a381 (diff)
Merge branch 'glitch' into production
-rw-r--r--Gemfile6
-rw-r--r--Gemfile.lock34
-rw-r--r--app/javascript/flavours/glitch/components/spoilers.js50
-rw-r--r--app/javascript/flavours/glitch/components/status.js20
-rw-r--r--app/javascript/flavours/glitch/components/status_action_bar.js11
-rw-r--r--app/javascript/flavours/glitch/containers/status_container.js55
-rw-r--r--app/javascript/flavours/glitch/features/local_settings/navigation/index.js20
-rw-r--r--app/javascript/flavours/glitch/features/local_settings/page/index.js23
-rw-r--r--app/javascript/flavours/glitch/reducers/compose.js1
-rw-r--r--app/javascript/flavours/glitch/reducers/local_settings.js1
-rw-r--r--app/javascript/flavours/glitch/reducers/reports.js13
-rw-r--r--app/javascript/flavours/glitch/selectors/index.js30
-rw-r--r--app/javascript/flavours/glitch/styles/components/error_boundary.scss36
-rw-r--r--app/javascript/flavours/glitch/styles/components/modal.scss39
-rw-r--r--app/javascript/flavours/glitch/styles/components/status.scss15
-rw-r--r--app/javascript/flavours/glitch/util/backend_links.js1
-rw-r--r--app/models/status.rb2
-rw-r--r--app/services/backup_service.rb2
-rw-r--r--app/services/block_service.rb2
-rw-r--r--app/workers/web/push_notification_worker.rb2
-rw-r--r--package.json12
-rw-r--r--yarn.lock199
22 files changed, 441 insertions, 133 deletions
diff --git a/Gemfile b/Gemfile
index 4389f8ebd..9e28b68a1 100644
--- a/Gemfile
+++ b/Gemfile
@@ -15,7 +15,7 @@ gem 'makara', '~> 0.4'
 gem 'pghero', '~> 2.2'
 gem 'dotenv-rails', '~> 2.7'
 
-gem 'aws-sdk-s3', '~> 1.43', require: false
+gem 'aws-sdk-s3', '~> 1.45', require: false
 gem 'fog-core', '<= 2.1.0'
 gem 'fog-openstack', '~> 0.3', require: false
 gem 'paperclip', '~> 6.0'
@@ -117,7 +117,7 @@ group :test do
   gem 'microformats', '~> 4.1'
   gem 'rails-controller-testing', '~> 1.0'
   gem 'rspec-sidekiq', '~> 3.0'
-  gem 'simplecov', '~> 0.16', require: false
+  gem 'simplecov', '~> 0.17', require: false
   gem 'webmock', '~> 3.6'
   gem 'parallel_tests', '~> 2.29'
 end
@@ -132,7 +132,7 @@ group :development do
   gem 'letter_opener_web', '~> 1.3'
   gem 'memory_profiler'
   gem 'rubocop', '~> 0.72', require: false
-  gem 'rubocop-rails', '~> 2.0', require: false
+  gem 'rubocop-rails', '~> 2.2', require: false
   gem 'brakeman', '~> 4.5', require: false
   gem 'bundler-audit', '~> 0.6', require: false
 
diff --git a/Gemfile.lock b/Gemfile.lock
index c8e00d7a1..2e2946f12 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -76,17 +76,17 @@ GEM
     av (0.9.0)
       cocaine (~> 0.5.3)
     aws-eventstream (1.0.3)
-    aws-partitions (1.177.0)
-    aws-sdk-core (3.56.0)
+    aws-partitions (1.184.0)
+    aws-sdk-core (3.59.0)
       aws-eventstream (~> 1.0, >= 1.0.2)
       aws-partitions (~> 1.0)
       aws-sigv4 (~> 1.1)
       jmespath (~> 1.0)
-    aws-sdk-kms (1.22.0)
-      aws-sdk-core (~> 3, >= 3.56.0)
+    aws-sdk-kms (1.23.0)
+      aws-sdk-core (~> 3, >= 3.58.0)
       aws-sigv4 (~> 1.1)
-    aws-sdk-s3 (1.43.0)
-      aws-sdk-core (~> 3, >= 3.56.0)
+    aws-sdk-s3 (1.45.0)
+      aws-sdk-core (~> 3, >= 3.58.0)
       aws-sdk-kms (~> 1)
       aws-sigv4 (~> 1.1)
     aws-sigv4 (1.1.0)
@@ -183,7 +183,7 @@ GEM
       devise (>= 4.0.0)
       rpam2 (~> 4.0)
     diff-lcs (1.3)
-    docile (1.3.0)
+    docile (1.3.2)
     domain_name (0.5.20180417)
       unf (>= 0.0.5, < 1.0.0)
     doorkeeper (5.1.0)
@@ -208,7 +208,7 @@ GEM
       tzinfo
     excon (0.62.0)
     fabrication (2.20.2)
-    faker (1.9.3)
+    faker (1.9.6)
       i18n (>= 0.7)
     faraday (0.15.0)
       multipart-post (>= 1.2, < 3)
@@ -231,7 +231,7 @@ GEM
     fugit (1.1.6)
       et-orbi (~> 1.1, >= 1.1.6)
       raabro (~> 1.1)
-    fuubar (2.4.0)
+    fuubar (2.4.1)
       rspec-core (~> 3.0)
       ruby-progressbar (~> 1.4)
     get_process_mem (0.2.3)
@@ -291,7 +291,7 @@ GEM
     iso-639 (0.2.8)
     jaro_winkler (1.5.3)
     jmespath (1.4.0)
-    json (2.1.0)
+    json (2.2.0)
     json-ld (3.0.2)
       multi_json (~> 1.12)
       rdf (>= 2.2.8, < 4.0)
@@ -534,9 +534,9 @@ GEM
       rainbow (>= 2.2.2, < 4.0)
       ruby-progressbar (~> 1.7)
       unicode-display_width (>= 1.4.0, < 1.7)
-    rubocop-rails (2.0.1)
+    rubocop-rails (2.2.0)
       rack (>= 1.1)
-      rubocop (>= 0.70.0)
+      rubocop (>= 0.72.0)
     ruby-progressbar (1.10.1)
     ruby-saml (1.9.0)
       nokogiri (>= 1.5.10)
@@ -568,7 +568,7 @@ GEM
     simple_form (4.1.0)
       actionpack (>= 5.0)
       activemodel (>= 5.0)
-    simplecov (0.16.1)
+    simplecov (0.17.0)
       docile (~> 1.1)
       json (>= 1.8, < 3)
       simplecov-html (~> 0.10.0)
@@ -615,7 +615,7 @@ GEM
       unf (~> 0.1.0)
     tzinfo (1.2.5)
       thread_safe (~> 0.1)
-    tzinfo-data (1.2019.1)
+    tzinfo-data (1.2019.2)
       tzinfo (>= 1.0.0)
     unf (0.1.4)
       unf_ext
@@ -650,7 +650,7 @@ DEPENDENCIES
   active_record_query_trace (~> 1.6)
   addressable (~> 2.6)
   annotate (~> 2.7)
-  aws-sdk-s3 (~> 1.43)
+  aws-sdk-s3 (~> 1.45)
   better_errors (~> 2.5)
   binding_of_caller (~> 0.7)
   blurhash (~> 0.1)
@@ -744,7 +744,7 @@ DEPENDENCIES
   rspec-rails (~> 3.8)
   rspec-sidekiq (~> 3.0)
   rubocop (~> 0.72)
-  rubocop-rails (~> 2.0)
+  rubocop-rails (~> 2.2)
   sanitize (~> 5.0)
   sidekiq (~> 5.2)
   sidekiq-bulk (~> 0.2.0)
@@ -752,7 +752,7 @@ DEPENDENCIES
   sidekiq-unique-jobs (~> 6.0)
   simple-navigation (~> 4.0)
   simple_form (~> 4.1)
-  simplecov (~> 0.16)
+  simplecov (~> 0.17)
   sprockets-rails (~> 3.2)
   stackprof
   stoplight (~> 2.1.3)
diff --git a/app/javascript/flavours/glitch/components/spoilers.js b/app/javascript/flavours/glitch/components/spoilers.js
new file mode 100644
index 000000000..8527403c1
--- /dev/null
+++ b/app/javascript/flavours/glitch/components/spoilers.js
@@ -0,0 +1,50 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { FormattedMessage } from 'react-intl';
+
+export default
+class Spoilers extends React.PureComponent {
+  static propTypes = {
+    spoilerText: PropTypes.string,
+    children: PropTypes.node,
+  };
+
+  state = {
+    hidden: true,
+  }
+
+  handleSpoilerClick = () => {
+    this.setState({ hidden: !this.state.hidden });
+  }
+
+  render () {
+    const { spoilerText, children } = this.props;
+    const { hidden } = this.state;
+
+      const toggleText = hidden ?
+        <FormattedMessage
+          id='status.show_more'
+          defaultMessage='Show more'
+          key='0'
+        /> :
+        <FormattedMessage
+          id='status.show_less'
+          defaultMessage='Show less'
+          key='0'
+        />;
+
+    return ([
+      <p className='spoiler__text'>
+        {spoilerText}
+        {' '}
+        <button tabIndex='0' className='status__content__spoiler-link' onClick={this.handleSpoilerClick}>
+          {toggleText}
+        </button>
+      </p>,
+      <div className={`status__content__spoiler ${!hidden ? 'status__content__spoiler--visible' : ''}`}>
+        {children}
+      </div>
+    ]);
+  }
+}
+
diff --git a/app/javascript/flavours/glitch/components/status.js b/app/javascript/flavours/glitch/components/status.js
index 022ae6de8..e94ce6dfe 100644
--- a/app/javascript/flavours/glitch/components/status.js
+++ b/app/javascript/flavours/glitch/components/status.js
@@ -106,6 +106,7 @@ class Status extends ImmutablePureComponent {
     statusId: undefined,
     revealBehindCW: undefined,
     showCard: false,
+    forceFilter: undefined,
   }
 
   // Avoid checking props that are functions (and whose equality will always
@@ -126,6 +127,7 @@ class Status extends ImmutablePureComponent {
     'isExpanded',
     'isCollapsed',
     'showMedia',
+    'forceFilter',
   ]
 
   //  If our settings have changed to disable collapsed statuses, then we
@@ -427,6 +429,15 @@ class Status extends ImmutablePureComponent {
     this.handleToggleMediaVisibility();
   }
 
+  handleUnfilterClick = e => {
+    const { onUnfilter, status } = this.props;
+    onUnfilter(status.get('reblog') ? status.get('reblog') : status, () => this.setState({ forceFilter: false }));
+  }
+
+  handleFilterClick = () => {
+    this.setState({ forceFilter: true });
+  }
+
   handleRef = c => {
     this.node = c;
   }
@@ -485,7 +496,7 @@ class Status extends ImmutablePureComponent {
       );
     }
 
-    if (status.get('filtered') || status.getIn(['reblog', 'filtered'])) {
+    if ((status.get('filtered') || status.getIn(['reblog', 'filtered'])) && (this.state.forceFilter === true || settings.get('filtering_behavior') !== 'content_warning')) {
       const minHandlers = this.props.muted ? {} : {
         moveUp: this.handleHotkeyMoveUp,
         moveDown: this.handleHotkeyMoveDown,
@@ -495,6 +506,12 @@ class Status extends ImmutablePureComponent {
         <HotKeys handlers={minHandlers}>
           <div className='status__wrapper status__wrapper--filtered focusable' tabIndex='0' ref={this.handleRef}>
             <FormattedMessage id='status.filtered' defaultMessage='Filtered' />
+            {settings.get('filtering_behavior') !== 'upstream' && ' '}
+            {settings.get('filtering_behavior') !== 'upstream' && (
+              <button className='status__wrapper--filtered__button' onClick={this.handleUnfilterClick}>
+                <FormattedMessage id='status.show_filter_reason' defaultMessage='(show why)' />
+              </button>
+            )}
           </div>
         </HotKeys>
       );
@@ -689,6 +706,7 @@ class Status extends ImmutablePureComponent {
               account={status.get('account')}
               showReplyCount={settings.get('show_reply_count')}
               directMessage={!!otherAccounts}
+              onFilter={this.handleFilterClick}
             />
           ) : null}
           {notification ? (
diff --git a/app/javascript/flavours/glitch/components/status_action_bar.js b/app/javascript/flavours/glitch/components/status_action_bar.js
index c424fbde1..4ef518f5e 100644
--- a/app/javascript/flavours/glitch/components/status_action_bar.js
+++ b/app/javascript/flavours/glitch/components/status_action_bar.js
@@ -35,6 +35,7 @@ const messages = defineMessages({
   admin_account: { id: 'status.admin_account', defaultMessage: 'Open moderation interface for @{name}' },
   admin_status: { id: 'status.admin_status', defaultMessage: 'Open this status in the moderation interface' },
   copy: { id: 'status.copy', defaultMessage: 'Copy link to status' },
+  hide: { id: 'status.hide', defaultMessage: 'Hide toot' },
 });
 
 const obfuscatedCount = count => {
@@ -69,6 +70,7 @@ export default class StatusActionBar extends ImmutablePureComponent {
     onMuteConversation: PropTypes.func,
     onPin: PropTypes.func,
     onBookmark: PropTypes.func,
+    onFilter: PropTypes.func,
     withDismiss: PropTypes.bool,
     showReplyCount: PropTypes.bool,
     directMessage: PropTypes.bool,
@@ -191,6 +193,10 @@ export default class StatusActionBar extends ImmutablePureComponent {
     }
   }
 
+  handleFilterClick = () => {
+    this.props.onFilter();
+  }
+
   render () {
     const { status, intl, withDismiss, showReplyCount, directMessage } = this.props;
 
@@ -263,6 +269,10 @@ export default class StatusActionBar extends ImmutablePureComponent {
       <IconButton className='status__action-bar-button' title={intl.formatMessage(messages.share)} icon='share-alt' onClick={this.handleShareClick} />
     );
 
+    const filterButton = status.get('filtered') && (
+      <IconButton className='status__action-bar-button' title={intl.formatMessage(messages.hide)} icon='eye' onClick={this.handleFilterClick} />
+    );
+
     let replyButton = (
       <IconButton
         className='status__action-bar-button'
@@ -288,6 +298,7 @@ export default class StatusActionBar extends ImmutablePureComponent {
           <IconButton key='favourite-button' className='status__action-bar-button star-icon' animate active={status.get('favourited')} pressed={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} />,
           shareButton,
           <IconButton key='bookmark-button' className='status__action-bar-button bookmark-icon' disabled={anonymousAccess} active={status.get('bookmarked')} pressed={status.get('bookmarked')} title={intl.formatMessage(messages.bookmark)} icon='bookmark' onClick={this.handleBookmarkClick} />,
+          filterButton,
           <div key='dropdown-button' className='status__action-bar-dropdown'>
             <DropdownMenuContainer disabled={anonymousAccess} status={status} items={menu} icon='ellipsis-h' size={18} direction='right' ariaLabel={intl.formatMessage(messages.more)} />
           </div>,
diff --git a/app/javascript/flavours/glitch/containers/status_container.js b/app/javascript/flavours/glitch/containers/status_container.js
index a6069cb90..bded66d09 100644
--- a/app/javascript/flavours/glitch/containers/status_container.js
+++ b/app/javascript/flavours/glitch/containers/status_container.js
@@ -1,7 +1,8 @@
 import React from 'react';
 import { connect } from 'react-redux';
 import Status from 'flavours/glitch/components/status';
-import { makeGetStatus } from 'flavours/glitch/selectors';
+import { List as ImmutableList } from 'immutable';
+import { makeGetStatus, regexFromFilters, toServerSideType } from 'flavours/glitch/selectors';
 import {
   replyCompose,
   mentionCompose,
@@ -25,7 +26,11 @@ import { openModal } from 'flavours/glitch/actions/modal';
 import { changeLocalSetting } from 'flavours/glitch/actions/local_settings';
 import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
 import { boostModal, favouriteModal, deleteModal } from 'flavours/glitch/util/initial_state';
+import { filterEditLink } from 'flavours/glitch/util/backend_links';
 import { showAlertForError } from '../actions/alerts';
+import AccountContainer from 'flavours/glitch/containers/account_container';
+import Spoilers from '../components/spoilers';
+import Icon from 'flavours/glitch/components/icon';
 
 const messages = defineMessages({
   deleteConfirm: { id: 'confirmations.delete.confirm', defaultMessage: 'Delete' },
@@ -36,6 +41,10 @@ const messages = defineMessages({
   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?' },
   blockAndReport: { id: 'confirmations.block.block_and_report', defaultMessage: 'Block & Report' },
+  unfilterConfirm: { id: 'confirmations.unfilter.confirm', defaultMessage: 'Show' },
+  author: { id: 'confirmations.unfilter.author', defaultMessage: 'Author' },
+  matchingFilters: { id: 'confirmations.unfilter.filters', defaultMessage: 'Matching {count, plural, one {filter} other {filters}}' },
+  editFilter: { id: 'confirmations.unfilter.edit_filter', defaultMessage: 'Edit filter' },
 });
 
 const makeMapStateToProps = () => {
@@ -69,7 +78,7 @@ const makeMapStateToProps = () => {
   return mapStateToProps;
 };
 
-const mapDispatchToProps = (dispatch, { intl }) => ({
+const mapDispatchToProps = (dispatch, { intl, contextType }) => ({
 
   onReply (status, router) {
     dispatch((_, getState) => {
@@ -189,6 +198,48 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
     }));
   },
 
+  onUnfilter (status, onConfirm) {
+    dispatch((_, getState) => {
+      let state = getState();
+      const serverSideType = toServerSideType(contextType);
+      const enabledFilters = state.get('filters', ImmutableList()).filter(filter => filter.get('context').includes(serverSideType) && (filter.get('expires_at') === null || Date.parse(filter.get('expires_at')) > (new Date()))).toArray();
+      const searchIndex = status.get('search_index');
+      const matchingFilters = enabledFilters.filter(filter => regexFromFilters([filter]).test(searchIndex));
+      dispatch(openModal('CONFIRM', {
+        message: [
+          <FormattedMessage id='confirmations.unfilter' defaultMessage='Information about this filtered toot' />,
+          <div className='filtered-status-info'>
+            <Spoilers spoilerText={intl.formatMessage(messages.author)}>
+              <AccountContainer id={status.getIn(['account', 'id'])} />
+            </Spoilers>
+            <Spoilers spoilerText={intl.formatMessage(messages.matchingFilters, {count: matchingFilters.size})}>
+              <ul>
+                {matchingFilters.map(filter => (
+                  <li>
+                    {filter.get('phrase')}
+                    {!!filterEditLink && ' '}
+                    {!!filterEditLink && (
+                      <a
+                        target='_blank'
+                        className='filtered-status-edit-link'
+                        title={intl.formatMessage(messages.editFilter)}
+                        href={filterEditLink(filter.get('id'))}
+                      >
+                        <Icon icon='pencil' />
+                      </a>
+                    )}
+                  </li>
+                ))}
+              </ul>
+            </Spoilers>
+          </div>
+        ],
+        confirm: intl.formatMessage(messages.unfilterConfirm),
+        onConfirm: onConfirm,
+      }));
+    });
+  },
+
   onReport (status) {
     dispatch(initReport(status.get('account'), status));
   },
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 01368abad..47f3d6d15 100644
--- a/app/javascript/flavours/glitch/features/local_settings/navigation/index.js
+++ b/app/javascript/flavours/glitch/features/local_settings/navigation/index.js
@@ -13,6 +13,7 @@ const messages = defineMessages({
   general: {  id: 'settings.general', defaultMessage: 'General' },
   compose: {  id: 'settings.compose_box_opts', defaultMessage: 'Compose box' },
   content_warnings: { id: 'settings.content_warnings', defaultMessage: 'Content Warnings' },
+  filters: { id: 'settings.filters', defaultMessage: 'Filters' },
   collapsed: { id: 'settings.collapsed_statuses', defaultMessage: 'Collapsed toots' },
   media: { id: 'settings.media', defaultMessage: 'Media' },
   preferences: { id: 'settings.preferences', defaultMessage: 'Preferences' },
@@ -60,27 +61,34 @@ export default class LocalSettingsNavigation extends React.PureComponent {
           active={index === 3}
           index={3}
           onNavigate={onNavigate}
-          icon='angle-double-up'
-          title={intl.formatMessage(messages.collapsed)}
+          icon='filter'
+          title={intl.formatMessage(messages.filters)}
         />
         <LocalSettingsNavigationItem
           active={index === 4}
           index={4}
           onNavigate={onNavigate}
+          icon='angle-double-up'
+          title={intl.formatMessage(messages.collapsed)}
+        />
+        <LocalSettingsNavigationItem
+          active={index === 5}
+          index={5}
+          onNavigate={onNavigate}
           icon='image'
           title={intl.formatMessage(messages.media)}
         />
         <LocalSettingsNavigationItem
-          active={index === 5}
+          active={index === 6}
           href={ preferencesLink }
-          index={5}
+          index={6}
           icon='cog'
           title={intl.formatMessage(messages.preferences)}
         />
         <LocalSettingsNavigationItem
-          active={index === 6}
+          active={index === 7}
           className='close'
-          index={6}
+          index={7}
           onNavigate={onClose}
           icon='times'
           title={intl.formatMessage(messages.close)}
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 23499455b..910cb5346 100644
--- a/app/javascript/flavours/glitch/features/local_settings/page/index.js
+++ b/app/javascript/flavours/glitch/features/local_settings/page/index.js
@@ -21,6 +21,10 @@ const messages = defineMessages({
   side_arm_copy: { id: 'settings.side_arm_reply_mode.copy', defaultMessage: 'Copy privacy setting of the toot being replied to' },
   side_arm_restrict: { id: 'settings.side_arm_reply_mode.restrict', defaultMessage: 'Restrict privacy setting to that of the toot being replied to' },
   regexp: { id: 'settings.content_warnings.regexp', defaultMessage: 'Regular expression' },
+  filters_drop: { id: 'settings.filtering_behavior.drop', defaultMessage: 'Hide filtered toots completely' },
+  filters_upstream: { id: 'settings.filtering_behavior.upstream', defaultMessage: 'Show "filtered" like vanilla Mastodon' },
+  filters_hide: { id: 'settings.filtering_behavior.hide', defaultMessage: 'Show "filtered" and add a button to display why' },
+  filters_cw: { id: 'settings.filtering_behavior.cw', defaultMessage: 'Still display the post, and add filtered words to content warning' },
 });
 
 @injectIntl
@@ -223,6 +227,25 @@ export default class LocalSettingsPage extends React.PureComponent {
         </LocalSettingsPageItem>
       </div>
     ),
+    ({ intl, onChange, settings }) => (
+      <div className='glitch local-settings__page filters'>
+        <h1><FormattedMessage id='settings.filters' defaultMessage='Filters' /></h1>
+        <LocalSettingsPageItem
+          settings={settings}
+          item={['filtering_behavior']}
+          id='mastodon-settings--filters-behavior'
+          onChange={onChange}
+          options={[
+            { value: 'drop', message: intl.formatMessage(messages.filters_drop) },
+            { value: 'upstream', message: intl.formatMessage(messages.filters_upstream) },
+            { value: 'hide', message: intl.formatMessage(messages.filters_hide) },
+            { value: 'content_warning', message: intl.formatMessage(messages.filters_cw) }
+          ]}
+        >
+          <FormattedMessage id='settings.filtering_behavior' defaultMessage='Filtering behavior' />
+        </LocalSettingsPageItem>
+      </div>
+    ),
     ({ onChange, settings }) => (
       <div className='glitch local-settings__page collapsed'>
         <h1><FormattedMessage id='settings.collapsed_statuses' defaultMessage='Collapsed toots' /></h1>
diff --git a/app/javascript/flavours/glitch/reducers/compose.js b/app/javascript/flavours/glitch/reducers/compose.js
index a47b8b7bd..5f176b832 100644
--- a/app/javascript/flavours/glitch/reducers/compose.js
+++ b/app/javascript/flavours/glitch/reducers/compose.js
@@ -182,6 +182,7 @@ function continueThread (state, status) {
     map.set('privacy', status.visibility);
     map.set('sensitive', false);
     map.update('media_attachments', list => list.clear());
+    map.set('poll', null);
     map.set('idempotencyKey', uuid());
     map.set('focusDate', new Date());
     map.set('caretPosition', null);
diff --git a/app/javascript/flavours/glitch/reducers/local_settings.js b/app/javascript/flavours/glitch/reducers/local_settings.js
index 68e1c8424..6fd3d901b 100644
--- a/app/javascript/flavours/glitch/reducers/local_settings.js
+++ b/app/javascript/flavours/glitch/reducers/local_settings.js
@@ -21,6 +21,7 @@ const initialState = ImmutableMap({
   inline_preview_cards: true,
   hicolor_privacy_icons: false,
   show_content_type_choice: false,
+  filtering_behavior: 'hide',
   content_warnings : ImmutableMap({
     auto_unfold : false,
     filter      : null,
diff --git a/app/javascript/flavours/glitch/reducers/reports.js b/app/javascript/flavours/glitch/reducers/reports.js
index fdcfb14a0..1f7f3f273 100644
--- a/app/javascript/flavours/glitch/reducers/reports.js
+++ b/app/javascript/flavours/glitch/reducers/reports.js
@@ -8,6 +8,9 @@ import {
   REPORT_COMMENT_CHANGE,
   REPORT_FORWARD_CHANGE,
 } from 'flavours/glitch/actions/reports';
+import {
+  TIMELINE_DELETE,
+} from 'flavours/glitch/actions/timelines';
 import { Map as ImmutableMap, Set as ImmutableSet } from 'immutable';
 
 const initialState = ImmutableMap({
@@ -20,6 +23,14 @@ const initialState = ImmutableMap({
   }),
 });
 
+const deleteStatus = (state, id, references) => {
+  references.forEach(ref => {
+    state = deleteStatus(state, ref[0], []);
+  });
+
+  return state.updateIn(['new', 'status_ids'], ImmutableSet(), set => set.remove(id));
+};
+
 export default function reports(state = initialState, action) {
   switch(action.type) {
   case REPORT_INIT:
@@ -58,6 +69,8 @@ export default function reports(state = initialState, action) {
       map.setIn(['new', 'comment'], '');
       map.setIn(['new', 'isSubmitting'], false);
     });
+  case TIMELINE_DELETE:
+    return deleteStatus(state, action.id, action.references);
   default:
     return state;
   }
diff --git a/app/javascript/flavours/glitch/selectors/index.js b/app/javascript/flavours/glitch/selectors/index.js
index 9e4582532..b414cd5e5 100644
--- a/app/javascript/flavours/glitch/selectors/index.js
+++ b/app/javascript/flavours/glitch/selectors/index.js
@@ -1,3 +1,4 @@
+import escapeTextContentForBrowser from 'escape-html';
 import { createSelector } from 'reselect';
 import { List as ImmutableList, is } from 'immutable';
 import { me } from 'flavours/glitch/util/initial_state';
@@ -20,7 +21,7 @@ export const makeGetAccount = () => {
   });
 };
 
-const toServerSideType = columnType => {
+export const toServerSideType = columnType => {
   switch (columnType) {
   case 'home':
   case 'notifications':
@@ -39,7 +40,7 @@ const toServerSideType = columnType => {
 const escapeRegExp = string =>
   string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
 
-const regexFromFilters = filters => {
+export const regexFromFilters = filters => {
   if (filters.size === 0) {
     return null;
   }
@@ -89,10 +90,13 @@ export const makeGetStatus = () => {
       (state, { id }) => state.getIn(['statuses', state.getIn(['statuses', id, 'reblog'])]),
       (state, { id }) => state.getIn(['accounts', state.getIn(['statuses', id, 'account'])]),
       (state, { id }) => state.getIn(['accounts', state.getIn(['statuses', state.getIn(['statuses', id, 'reblog']), 'account'])]),
+      (state, _) => state.getIn(['local_settings', 'filtering_behavior']),
+      (state, _) => state.get('filters', ImmutableList()),
+      (_, { contextType }) => contextType,
       getFiltersRegex,
     ],
 
-    (statusBase, statusReblog, accountBase, accountReblog, filtersRegex) => {
+    (statusBase, statusReblog, accountBase, accountReblog, filteringBehavior, filters, contextType, filtersRegex) => {
       if (!statusBase) {
         return null;
       }
@@ -116,6 +120,26 @@ export const makeGetStatus = () => {
 
       filtered = filtered || regex && regex.test(statusBase.get('search_index'));
 
+      if (filtered && filteringBehavior === 'drop') {
+        return null;
+      } else if (filtered && filteringBehavior === 'content_warning') {
+        let spoilerText = (statusReblog || statusBase).get('spoiler_text', '');
+        const searchIndex = (statusReblog || statusBase).get('search_index');
+        const serverSideType = toServerSideType(contextType);
+        const enabledFilters = filters.filter(filter => filter.get('context').includes(serverSideType) && (filter.get('expires_at') === null || Date.parse(filter.get('expires_at')) > (new Date()))).toArray();
+        const matchingFilters = enabledFilters.filter(filter => {
+          const regexp = regexFromFilters([filter]);
+          return regexp.test(searchIndex) && !regexp.test(spoilerText);
+        });
+        if (statusReblog) {
+          statusReblog = statusReblog.set('spoiler_text', matchingFilters.map(filter => filter.get('phrase')).concat([spoilerText]).filter(cw => !!cw).join(', '));
+          statusReblog = statusReblog.update('spoilerHtml', '', spoilerText => matchingFilters.map(filter => escapeTextContentForBrowser(filter.get('phrase'))).concat([spoilerText]).filter(cw => !!cw).join(', '));
+        } else {
+          statusBase = statusBase.set('spoiler_text', matchingFilters.map(filter => filter.get('phrase')).concat([spoilerText]).filter(cw => !!cw).join(', '));
+          statusBase = statusBase.update('spoilerHtml', '', spoilerText => matchingFilters.map(filter => escapeTextContentForBrowser(filter.get('phrase'))).concat([spoilerText]).filter(cw => !!cw).join(', '));
+        }
+      }
+
       return statusBase.withMutations(map => {
         map.set('reblog', statusReblog);
         map.set('account', accountBase);
diff --git a/app/javascript/flavours/glitch/styles/components/error_boundary.scss b/app/javascript/flavours/glitch/styles/components/error_boundary.scss
index f9bf425f8..3176690e2 100644
--- a/app/javascript/flavours/glitch/styles/components/error_boundary.scss
+++ b/app/javascript/flavours/glitch/styles/components/error_boundary.scss
@@ -1,4 +1,8 @@
 .error-boundary {
+  color: $primary-text-color;
+  font-size: 15px;
+  line-height: 20px;
+
   h1 {
     font-size: 26px;
     line-height: 36px;
@@ -6,27 +10,21 @@
     margin-bottom: 8px;
   }
 
-  p {
+  a {
     color: $primary-text-color;
-    font-size: 15px;
-    line-height: 20px;
-
-    a {
-      color: $primary-text-color;
-      text-decoration: underline;
-    }
+    text-decoration: underline;
+  }
 
-    ul {
-      list-style: disc;
-      margin-left: 0;
-      padding-left: 1em;
-    }
+  ul {
+    list-style: disc;
+    margin-left: 0;
+    padding-left: 1em;
+  }
 
-    textarea.web_app_crash-stacktrace {
-      width: 100%;
-      resize: none;
-      white-space: pre;
-      font-family: $font-monospace, monospace;
-    }
+  textarea.web_app_crash-stacktrace {
+    width: 100%;
+    resize: none;
+    white-space: pre;
+    font-family: $font-monospace, monospace;
   }
 }
diff --git a/app/javascript/flavours/glitch/styles/components/modal.scss b/app/javascript/flavours/glitch/styles/components/modal.scss
index 65b2e75f0..a98efee9f 100644
--- a/app/javascript/flavours/glitch/styles/components/modal.scss
+++ b/app/javascript/flavours/glitch/styles/components/modal.scss
@@ -820,3 +820,42 @@
     left: 0;
   }
 }
+
+.filtered-status-info {
+  text-align: start;
+
+  .spoiler__text {
+    margin-top: 20px;
+  }
+
+  .account {
+    border-bottom: 0;
+  }
+
+  .account__display-name strong {
+    color: $inverted-text-color;
+  }
+
+  .status__content__spoiler {
+    display: none;
+
+    &--visible {
+      display: flex;
+    }
+  }
+
+  ul {
+    padding: 10px;
+    margin-left: 12px;
+    list-style: disc inside;
+  }
+
+  .filtered-status-edit-link {
+    color: $action-button-color;
+    text-decoration: none;
+
+    &:hover {
+      text-decoration: underline
+    }
+  }
+}
diff --git a/app/javascript/flavours/glitch/styles/components/status.scss b/app/javascript/flavours/glitch/styles/components/status.scss
index fa115f21b..4ffbb2c21 100644
--- a/app/javascript/flavours/glitch/styles/components/status.scss
+++ b/app/javascript/flavours/glitch/styles/components/status.scss
@@ -996,3 +996,18 @@ a.status-card.compact:hover {
     }
   }
 }
+
+.status__wrapper--filtered__button {
+  display: inline;
+  color: lighten($ui-highlight-color, 8%);
+  border: 0;
+  background: transparent;
+  padding: 0;
+  font-size: inherit;
+  line-height: inherit;
+
+  &:hover,
+  &:active {
+    text-decoration: underline;
+  }
+}
diff --git a/app/javascript/flavours/glitch/util/backend_links.js b/app/javascript/flavours/glitch/util/backend_links.js
index 4fc03f919..bc82197be 100644
--- a/app/javascript/flavours/glitch/util/backend_links.js
+++ b/app/javascript/flavours/glitch/util/backend_links.js
@@ -4,3 +4,4 @@ export const signOutLink = '/auth/sign_out';
 export const termsLink = '/terms';
 export const accountAdminLink = (id) => `/admin/accounts/${id}`;
 export const statusAdminLink = (account_id, status_id) => `/admin/accounts/${account_id}/statuses/${status_id}`;
+export const filterEditLink = (id) => `/filters/${id}/edit`;
diff --git a/app/models/status.rb b/app/models/status.rb
index 5ddce72de..5adccb722 100644
--- a/app/models/status.rb
+++ b/app/models/status.rb
@@ -82,7 +82,7 @@ class Status < ApplicationRecord
   default_scope { recent }
 
   scope :recent, -> { reorder(id: :desc) }
-  scope :remote, -> { where(local: false).or(where.not(uri: nil)) }
+  scope :remote, -> { where(local: false).where.not(uri: nil) }
   scope :local,  -> { where(local: true).or(where(uri: nil)) }
 
   scope :without_replies, -> { where('statuses.reply = FALSE OR statuses.in_reply_to_account_id = statuses.account_id') }
diff --git a/app/services/backup_service.rb b/app/services/backup_service.rb
index bd9e77223..cc9fb1f4e 100644
--- a/app/services/backup_service.rb
+++ b/app/services/backup_service.rb
@@ -164,5 +164,7 @@ class BackupService < BaseService
         io.write(buffer)
       end
     end
+  rescue Errno::ENOENT
+    Rails.logger.warn "Could not backup file #{filename}: file not found"
   end
 end
diff --git a/app/services/block_service.rb b/app/services/block_service.rb
index 9050a4858..0d9a6eccd 100644
--- a/app/services/block_service.rb
+++ b/app/services/block_service.rb
@@ -8,7 +8,7 @@ class BlockService < BaseService
 
     UnfollowService.new.call(account, target_account) if account.following?(target_account)
     UnfollowService.new.call(target_account, account) if target_account.following?(account)
-    RejectFollowService.new.call(account, target_account) if target_account.requested?(account)
+    RejectFollowService.new.call(target_account, account) if target_account.requested?(account)
 
     block = account.block!(target_account)
 
diff --git a/app/workers/web/push_notification_worker.rb b/app/workers/web/push_notification_worker.rb
index 8e8a35973..901043975 100644
--- a/app/workers/web/push_notification_worker.rb
+++ b/app/workers/web/push_notification_worker.rb
@@ -3,7 +3,7 @@
 class Web::PushNotificationWorker
   include Sidekiq::Worker
 
-  sidekiq_options backtrace: true
+  sidekiq_options backtrace: true, retry: 5
 
   def perform(subscription_id, notification_id)
     subscription = ::Web::PushSubscription.find(subscription_id)
diff --git a/package.json b/package.json
index 0a4542169..afb5635b3 100644
--- a/package.json
+++ b/package.json
@@ -61,7 +61,7 @@
   "private": true,
   "dependencies": {
     "@babel/core": "^7.4.5",
-    "@babel/plugin-proposal-class-properties": "^7.4.4",
+    "@babel/plugin-proposal-class-properties": "^7.5.0",
     "@babel/plugin-proposal-decorators": "^7.4.4",
     "@babel/plugin-proposal-object-rest-spread": "^7.4.4",
     "@babel/plugin-syntax-dynamic-import": "^7.2.0",
@@ -85,7 +85,7 @@
     "babel-runtime": "^6.26.0",
     "blurhash": "^1.0.0",
     "classnames": "^2.2.5",
-    "compression-webpack-plugin": "^2.0.0",
+    "compression-webpack-plugin": "^3.0.0",
     "cross-env": "^5.1.4",
     "css-loader": "^2.1.1",
     "cssnano": "^4.1.10",
@@ -106,10 +106,10 @@
     "intersection-observer": "^0.7.0",
     "intl": "^1.2.5",
     "intl-messageformat": "^2.2.0",
-    "intl-relativeformat": "^2.2.0",
+    "intl-relativeformat": "^6.4.2",
     "is-nan": "^1.2.1",
     "js-yaml": "^3.13.1",
-    "lodash": "^4.7.11",
+    "lodash": "^4.17.13",
     "mark-loader": "^0.1.6",
     "marky": "^1.2.1",
     "mini-css-extract-plugin": "^0.7.0",
@@ -136,7 +136,7 @@
     "react-motion": "^0.5.2",
     "react-notification": "^6.8.4",
     "react-overlays": "^0.8.3",
-    "react-redux": "^6.0.1",
+    "react-redux": "^7.1.0",
     "react-redux-loading-bar": "^4.0.8",
     "react-router-dom": "^4.1.1",
     "react-router-scroll-4": "^1.0.0-beta.1",
@@ -177,7 +177,7 @@
     "eslint-plugin-import": "~2.17.3",
     "eslint-plugin-jsx-a11y": "~6.2.1",
     "eslint-plugin-promise": "~4.2.1",
-    "eslint-plugin-react": "~7.12.1",
+    "eslint-plugin-react": "~7.14.2",
     "jest": "^24.8.0",
     "raf": "^3.4.1",
     "react-intl-translations-manager": "^5.0.3",
diff --git a/yarn.lock b/yarn.lock
index a1b9f4c2b..c3ad5389f 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -103,10 +103,10 @@
     "@babel/traverse" "^7.4.4"
     "@babel/types" "^7.4.4"
 
-"@babel/helper-create-class-features-plugin@^7.4.4":
-  version "7.4.4"
-  resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.4.4.tgz#fc3d690af6554cc9efc607364a82d48f58736dba"
-  integrity sha512-UbBHIa2qeAGgyiNR9RszVF7bUHEdgS4JAUNT8SiqrAN6YJVxlOxeLr5pBzb5kan302dejJ9nla4RyKcR1XT6XA==
+"@babel/helper-create-class-features-plugin@^7.4.4", "@babel/helper-create-class-features-plugin@^7.5.0":
+  version "7.5.0"
+  resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.5.0.tgz#02edb97f512d44ba23b3227f1bf2ed43454edac5"
+  integrity sha512-EAoMc3hE5vE5LNhMqDOwB1usHvmRjCDAnH8CD4PVkX9/Yr3W/tcz8xE8QvdZxfsFBDICwZnF2UTHIqslRpvxmA==
   dependencies:
     "@babel/helper-function-name" "^7.1.0"
     "@babel/helper-member-expression-to-functions" "^7.0.0"
@@ -297,12 +297,12 @@
     "@babel/helper-remap-async-to-generator" "^7.1.0"
     "@babel/plugin-syntax-async-generators" "^7.2.0"
 
-"@babel/plugin-proposal-class-properties@^7.4.4":
-  version "7.4.4"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.4.4.tgz#93a6486eed86d53452ab9bab35e368e9461198ce"
-  integrity sha512-WjKTI8g8d5w1Bc9zgwSz2nfrsNQsXcCf9J9cdCvrJV6RF56yztwm4TmJC0MgJ9tvwO9gUA/mcYe89bLdGfiXFg==
+"@babel/plugin-proposal-class-properties@^7.5.0":
+  version "7.5.0"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.5.0.tgz#5bc6a0537d286fcb4fd4e89975adbca334987007"
+  integrity sha512-9L/JfPCT+kShiiTTzcnBJ8cOwdKVmlC1RcCf9F0F9tERVrM4iWtWnXtjWCRqNm2la2BxO1MPArWNsU9zsSJWSQ==
   dependencies:
-    "@babel/helper-create-class-features-plugin" "^7.4.4"
+    "@babel/helper-create-class-features-plugin" "^7.5.0"
     "@babel/helper-plugin-utils" "^7.0.0"
 
 "@babel/plugin-proposal-decorators@^7.4.4":
@@ -768,7 +768,7 @@
   dependencies:
     regenerator-runtime "^0.12.0"
 
-"@babel/runtime@^7.1.2", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.2", "@babel/runtime@^7.4.5":
+"@babel/runtime@^7.1.2", "@babel/runtime@^7.4.2", "@babel/runtime@^7.4.5":
   version "7.4.5"
   resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.4.5.tgz#582bb531f5f9dc67d2fcb682979894f75e253f12"
   integrity sha512-TuI4qpWZP6lGOGIuGWtp9sPluqYICmbk8T/1vpSysqJxRPkudh/ofFWyqdcMsDf2s7KvDL4/YHgKyvcS3g9CJQ==
@@ -1681,13 +1681,6 @@ async@^1.5.2:
   resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a"
   integrity sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=
 
-async@^2.5.0:
-  version "2.6.1"
-  resolved "https://registry.yarnpkg.com/async/-/async-2.6.1.tgz#b245a23ca71930044ec53fa46aa00a3e87c6a610"
-  integrity sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ==
-  dependencies:
-    lodash "^4.17.10"
-
 asynckit@^0.4.0:
   version "0.4.0"
   resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
@@ -2499,16 +2492,11 @@ combined-stream@^1.0.6, combined-stream@~1.0.6:
   dependencies:
     delayed-stream "~1.0.0"
 
-commander@^2.11.0, commander@^2.18.0, commander@^2.19.0, commander@^2.8.1:
+commander@^2.11.0, commander@^2.18.0, commander@^2.19.0, commander@^2.8.1, commander@~2.20.0:
   version "2.20.0"
   resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.0.tgz#d58bb2b5c1ee8f87b0d340027e9e94e222c5a422"
   integrity sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==
 
-commander@~2.17.1:
-  version "2.17.1"
-  resolved "https://registry.yarnpkg.com/commander/-/commander-2.17.1.tgz#bd77ab7de6de94205ceacc72f1716d29f20a77bf"
-  integrity sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==
-
 commondir@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b"
@@ -2526,13 +2514,13 @@ compressible@~2.0.16:
   dependencies:
     mime-db ">= 1.40.0 < 2"
 
-compression-webpack-plugin@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/compression-webpack-plugin/-/compression-webpack-plugin-2.0.0.tgz#46476350c1eb27f783dccc79ac2f709baa2cffbc"
-  integrity sha512-bDgd7oTUZC8EkRx8j0sjyCfeiO+e5sFcfgaFcjVhfQf5lLya7oY2BczxcJ7IUuVjz5m6fy8IECFmVFew3xLk8Q==
+compression-webpack-plugin@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/compression-webpack-plugin/-/compression-webpack-plugin-3.0.0.tgz#097d2e4d95c3a14cb5c8ed20899009ab5b9bbca0"
+  integrity sha512-ls+oKw4eRbvaSv/hj9NmctihhBcR26j76JxV0bLRLcWhrUBdQFgd06z/Kgg7exyQvtWWP484wZxs0gIUX3NO0Q==
   dependencies:
     cacache "^11.2.0"
-    find-cache-dir "^2.0.0"
+    find-cache-dir "^3.0.0"
     neo-async "^2.5.0"
     schema-utils "^1.0.0"
     serialize-javascript "^1.4.0"
@@ -3702,18 +3690,20 @@ eslint-plugin-promise@~4.2.1:
   resolved "https://registry.yarnpkg.com/eslint-plugin-promise/-/eslint-plugin-promise-4.2.1.tgz#845fd8b2260ad8f82564c1222fce44ad71d9418a"
   integrity sha512-VoM09vT7bfA7D+upt+FjeBO5eHIJQBUWki1aPvB+vbNiHS3+oGIJGIeyBtKQTME6UPXXy3vV07OL1tHd3ANuDw==
 
-eslint-plugin-react@~7.12.1:
-  version "7.12.1"
-  resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.12.1.tgz#b9c4639f72469ff317ac31e3bd630d22d0dbf8f4"
-  integrity sha512-1YyXVhp6KSB+xRC1BWzmlA4BH9Wp9jMMBE6AJizxuk+bg/KUJpQGRwsU1/q1pV8rM6oEdLCxunXn7Nfh2BOWBg==
+eslint-plugin-react@~7.14.2:
+  version "7.14.2"
+  resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.14.2.tgz#94c193cc77a899ac0ecbb2766fbef88685b7ecc1"
+  integrity sha512-jZdnKe3ip7FQOdjxks9XPN0pjUKZYq48OggNMd16Sk+8VXx6JOvXmlElxROCgp7tiUsTsze3jd78s/9AFJP2mA==
   dependencies:
     array-includes "^3.0.3"
     doctrine "^2.1.0"
     has "^1.0.3"
-    jsx-ast-utils "^2.0.1"
+    jsx-ast-utils "^2.1.0"
+    object.entries "^1.1.0"
     object.fromentries "^2.0.0"
-    prop-types "^15.6.2"
-    resolve "^1.9.0"
+    object.values "^1.1.0"
+    prop-types "^15.7.2"
+    resolve "^1.10.1"
 
 eslint-scope@3.7.1:
   version "3.7.1"
@@ -4198,6 +4188,15 @@ find-cache-dir@^2.0.0:
     make-dir "^1.0.0"
     pkg-dir "^3.0.0"
 
+find-cache-dir@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-3.0.0.tgz#cd4b7dd97b7185b7e17dbfe2d6e4115ee3eeb8fc"
+  integrity sha512-t7ulV1fmbxh5G9l/492O1p5+EBbr3uwpt6odhFTMc+nWyhmbloe+ja9BZ8pIBtqFWhOmCWVjx+pTW4zDkFoclw==
+  dependencies:
+    commondir "^1.0.1"
+    make-dir "^3.0.0"
+    pkg-dir "^4.1.0"
+
 find-root@^1.1.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/find-root/-/find-root-1.1.0.tgz#abcfc8ba76f708c42a97b3d685b7e9450bfb9ce4"
@@ -4217,6 +4216,14 @@ find-up@^3.0.0:
   dependencies:
     locate-path "^3.0.0"
 
+find-up@^4.0.0:
+  version "4.1.0"
+  resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19"
+  integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==
+  dependencies:
+    locate-path "^5.0.0"
+    path-exists "^4.0.0"
+
 findup-sync@3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-3.0.0.tgz#17b108f9ee512dfb7a5c7f3c8b27ea9e1a9c08d1"
@@ -4587,11 +4594,11 @@ handle-thing@^2.0.0:
   integrity sha512-d4sze1JNC454Wdo2fkuyzCr6aHcbL6PGGuFAz0Li/NcOm1tCHGnWDRmJP85dh9IhQErTc2svWFEX5xHIOo//kQ==
 
 handlebars@^4.1.0:
-  version "4.1.0"
-  resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.1.0.tgz#0d6a6f34ff1f63cecec8423aa4169827bf787c3a"
-  integrity sha512-l2jRuU1NAWK6AW5qqcTATWQJvNPEwkM7NEKSiv/gqOsoSQbVoWyqVEY5GS+XPQ88zLNmqASRpzfdm8d79hJS+w==
+  version "4.1.2"
+  resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.1.2.tgz#b6b37c1ced0306b221e094fc7aca3ec23b131b67"
+  integrity sha512-nvfrjqvt9xQ8Z/w0ijewdD/vvWDTOweBUm96NTr66Wfvo1mJenBLwcYmPs3TIBP5ruzYGD7Hx/DaM9RmhroGPw==
   dependencies:
-    async "^2.5.0"
+    neo-async "^2.6.0"
     optimist "^0.6.1"
     source-map "^0.6.1"
   optionalDependencies:
@@ -5083,13 +5090,18 @@ intl-messageformat@^2.0.0, intl-messageformat@^2.1.0, intl-messageformat@^2.2.0:
   dependencies:
     intl-messageformat-parser "1.4.0"
 
-intl-relativeformat@^2.1.0, intl-relativeformat@^2.2.0:
+intl-relativeformat@^2.1.0:
   version "2.2.0"
   resolved "https://registry.yarnpkg.com/intl-relativeformat/-/intl-relativeformat-2.2.0.tgz#6aca95d019ec8d30b6c5653b6629f9983ea5b6c5"
   integrity sha512-4bV/7kSKaPEmu6ArxXf9xjv1ny74Zkwuey8Pm01NH4zggPP7JHwg2STk8Y3JdspCKRDriwIyLRfEXnj2ZLr4Bw==
   dependencies:
     intl-messageformat "^2.0.0"
 
+intl-relativeformat@^6.4.2:
+  version "6.4.2"
+  resolved "https://registry.yarnpkg.com/intl-relativeformat/-/intl-relativeformat-6.4.2.tgz#431f9818449f5b48c209610ff1428d0c663c667f"
+  integrity sha512-yaOimRUQEn1wOfVGk43H+EVCrxQ5WFEvtYBx4Ffa6QpEHIi6UOuvshx6RltuqIF5UM8xdF4SkzFHXXOnYXlgBA==
+
 intl@^1.2.5:
   version "1.2.5"
   resolved "https://registry.yarnpkg.com/intl/-/intl-1.2.5.tgz#82244a2190c4e419f8371f5aa34daa3420e2abde"
@@ -6033,12 +6045,13 @@ jsprim@^1.2.2:
     json-schema "0.2.3"
     verror "1.10.0"
 
-jsx-ast-utils@^2.0.1:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-2.0.1.tgz#e801b1b39985e20fffc87b40e3748080e2dcac7f"
-  integrity sha1-6AGxs5mF4g//yHtA43SAgOLcrH8=
+jsx-ast-utils@^2.0.1, jsx-ast-utils@^2.1.0:
+  version "2.2.1"
+  resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-2.2.1.tgz#4d4973ebf8b9d2837ee91a8208cc66f3a2776cfb"
+  integrity sha512-v3FxCcAf20DayI+uxnCuw795+oOIkVu6EnJ1+kSzhqqTZHNkTZ7B66ZgLp4oLJ/gbA64cI0B7WRoHZMSRdyVRQ==
   dependencies:
     array-includes "^3.0.3"
+    object.assign "^4.1.0"
 
 keycode@^2.1.7:
   version "2.2.0"
@@ -6174,6 +6187,13 @@ locate-path@^3.0.0:
     p-locate "^3.0.0"
     path-exists "^3.0.0"
 
+locate-path@^5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0"
+  integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==
+  dependencies:
+    p-locate "^4.1.0"
+
 lodash.capitalize@^4.1.0:
   version "4.2.1"
   resolved "https://registry.yarnpkg.com/lodash.capitalize/-/lodash.capitalize-4.2.1.tgz#f826c9b4e2a8511d84e3aca29db05e1a4f3b72a9"
@@ -6244,10 +6264,10 @@ lodash.uniq@^4.5.0:
   resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
   integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=
 
-lodash@^4.0.0, lodash@^4.13.1, lodash@^4.15.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.5, lodash@^4.3.0, lodash@^4.7.11, lodash@~4.17.10:
-  version "4.17.11"
-  resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d"
-  integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==
+lodash@^4.0.0, lodash@^4.13.1, lodash@^4.15.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.5, lodash@^4.3.0, lodash@~4.17.10:
+  version "4.17.13"
+  resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.13.tgz#0bdc3a6adc873d2f4e0c4bac285df91b64fc7b93"
+  integrity sha512-vm3/XWXfWtRua0FkUyEHBZy8kCPjErNBT9fJx8Zvs+U6zjqPbTUOpkaoum3O5uiA8sm+yNMHXfYkTUHFoMxFNA==
 
 loglevel@^1.6.3:
   version "1.6.3"
@@ -6275,6 +6295,13 @@ make-dir@^1.0.0, make-dir@^1.3.0:
   dependencies:
     pify "^3.0.0"
 
+make-dir@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.0.0.tgz#1b5f39f6b9270ed33f9f054c5c0f84304989f801"
+  integrity sha512-grNJDhb8b1Jm1qeqW5R/O63wUo4UXo2v2HMic6YT9i/HBlF93S8jkMgH7yugvY9ABDShH4VZMn8I+U8+fCNegw==
+  dependencies:
+    semver "^6.0.0"
+
 makeerror@1.0.x:
   version "1.0.11"
   resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.11.tgz#e01a5c9109f2af79660e4e8b9587790184f5a96c"
@@ -6653,10 +6680,10 @@ negotiator@0.6.2:
   resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb"
   integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==
 
-neo-async@^2.5.0:
-  version "2.6.0"
-  resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.0.tgz#b9d15e4d71c6762908654b5183ed38b753340835"
-  integrity sha512-MFh0d/Wa7vkKO3Y3LlacqAEeHK0mckVqzDieUKTT+KGxi+zIpeVsFxymkIiRpbpDziHc290Xr9A1O4Om7otoRA==
+neo-async@^2.5.0, neo-async@^2.6.0:
+  version "2.6.1"
+  resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.1.tgz#ac27ada66167fa8849a6addd837f6b189ad2081c"
+  integrity sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==
 
 next-tick@1, next-tick@^1.0.0:
   version "1.0.0"
@@ -7122,6 +7149,13 @@ p-limit@^2.0.0:
   dependencies:
     p-try "^2.0.0"
 
+p-limit@^2.2.0:
+  version "2.2.0"
+  resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.2.0.tgz#417c9941e6027a9abcba5092dd2904e255b5fbc2"
+  integrity sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==
+  dependencies:
+    p-try "^2.0.0"
+
 p-locate@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43"
@@ -7136,6 +7170,13 @@ p-locate@^3.0.0:
   dependencies:
     p-limit "^2.0.0"
 
+p-locate@^4.1.0:
+  version "4.1.0"
+  resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07"
+  integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==
+  dependencies:
+    p-limit "^2.2.0"
+
 p-map@^2.0.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/p-map/-/p-map-2.1.0.tgz#310928feef9c9ecc65b68b17693018a665cea175"
@@ -7277,6 +7318,11 @@ path-exists@^3.0.0:
   resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515"
   integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=
 
+path-exists@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3"
+  integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==
+
 path-is-absolute@^1.0.0:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
@@ -7442,6 +7488,13 @@ pkg-dir@^3.0.0:
   dependencies:
     find-up "^3.0.0"
 
+pkg-dir@^4.1.0:
+  version "4.2.0"
+  resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3"
+  integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==
+  dependencies:
+    find-up "^4.0.0"
+
 pluralize@^1.2.1:
   version "1.2.1"
   resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-1.2.1.tgz#d1a21483fd22bb41e58a12fa3421823140897c45"
@@ -8197,7 +8250,7 @@ react-intl@^2.9.0:
     intl-relativeformat "^2.1.0"
     invariant "^2.1.1"
 
-react-is@^16.3.2, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.2, react-is@^16.8.4, react-is@^16.8.6:
+react-is@^16.3.2, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.4, react-is@^16.8.6:
   version "16.8.6"
   resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.8.6.tgz#5bbc1e2d29141c9fbdfed456343fe2bc430a6a16"
   integrity sha512-aUk3bHfZ2bRSVFFbbeVS4i+lNPZr3/WM5jT2J5omUVV1zzcs1nAaf3l51ctA5FFvCRbhrH0bdAsRRQddFJZPtA==
@@ -8252,17 +8305,17 @@ react-redux-loading-bar@^4.0.8:
     prop-types "^15.6.2"
     react-lifecycles-compat "^3.0.2"
 
-react-redux@^6.0.1:
-  version "6.0.1"
-  resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-6.0.1.tgz#0d423e2c1cb10ada87293d47e7de7c329623ba4d"
-  integrity sha512-T52I52Kxhbqy/6TEfBv85rQSDz6+Y28V/pf52vDWs1YRXG19mcFOGfHnY2HsNFHyhP+ST34Aih98fvt6tqwVcQ==
+react-redux@^7.1.0:
+  version "7.1.0"
+  resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.1.0.tgz#72af7cf490a74acdc516ea9c1dd80e25af9ea0b2"
+  integrity sha512-hyu/PoFK3vZgdLTg9ozbt7WF3GgX5+Yn3pZm5/96/o4UueXA+zj08aiSC9Mfj2WtD1bvpIb3C5yvskzZySzzaw==
   dependencies:
-    "@babel/runtime" "^7.3.1"
+    "@babel/runtime" "^7.4.5"
     hoist-non-react-statics "^3.3.0"
     invariant "^2.2.4"
     loose-envify "^1.4.0"
     prop-types "^15.7.2"
-    react-is "^16.8.2"
+    react-is "^16.8.6"
 
 react-router-dom@^4.1.1:
   version "4.3.1"
@@ -8748,10 +8801,10 @@ resolve@1.1.7:
   resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b"
   integrity sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=
 
-resolve@^1.10.0, resolve@^1.11.0, resolve@^1.3.2, resolve@^1.5.0, resolve@^1.8.1, resolve@^1.9.0:
-  version "1.11.0"
-  resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.11.0.tgz#4014870ba296176b86343d50b60f3b50609ce232"
-  integrity sha512-WL2pBDjqT6pGUNSUzMw00o4T7If+z4H2x3Gz893WoUQ5KW8Vr9txp00ykiP16VBaZF5+j/OcXJHZ9+PCvdiDKw==
+resolve@^1.10.0, resolve@^1.10.1, resolve@^1.11.0, resolve@^1.3.2, resolve@^1.5.0, resolve@^1.8.1:
+  version "1.11.1"
+  resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.11.1.tgz#ea10d8110376982fef578df8fc30b9ac30a07a3e"
+  integrity sha512-vIpgF6wfuJOZI7KKKSP+HmiKggadPQAdsp5HiC1mvqnfp0gF1vdwgBWZIdrVft9pgqoMFQN+R7BSWZiBxx+BBw==
   dependencies:
     path-parse "^1.0.6"
 
@@ -8975,6 +9028,11 @@ semver@4.3.2:
   resolved "https://registry.yarnpkg.com/semver/-/semver-4.3.2.tgz#c7a07158a80bedd052355b770d82d6640f803be7"
   integrity sha1-x6BxWKgL7dBSNVt3DYLWZA+AO+c=
 
+semver@^6.0.0:
+  version "6.1.3"
+  resolved "https://registry.yarnpkg.com/semver/-/semver-6.1.3.tgz#ef997a1a024f67dd48a7f155df88bb7b5c6c3fc7"
+  integrity sha512-aymF+56WJJMyXQHcd4hlK4N75rwj5RQpfW8ePlQnJsTYOBLlLbcIErR/G1s9SkIvKBqOudR3KAx4wEqP+F1hNQ==
+
 semver@^6.1.0, semver@^6.1.1:
   version "6.1.1"
   resolved "https://registry.yarnpkg.com/semver/-/semver-6.1.1.tgz#53f53da9b30b2103cd4f15eab3a18ecbcb210c9b"
@@ -8999,12 +9057,7 @@ send@0.17.1:
     range-parser "~1.2.1"
     statuses "~1.5.0"
 
-serialize-javascript@^1.4.0:
-  version "1.6.1"
-  resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-1.6.1.tgz#4d1f697ec49429a847ca6f442a2a755126c4d879"
-  integrity sha512-A5MOagrPFga4YaKQSWHryl7AXvbQkEqpw4NNYMTNYUNV51bA8ABHgYFpqKx+YFFrw59xMV1qGH1R4AgoNIVgCw==
-
-serialize-javascript@^1.7.0:
+serialize-javascript@^1.4.0, serialize-javascript@^1.7.0:
   version "1.7.0"
   resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-1.7.0.tgz#d6e0dfb2a3832a8c94468e6eb1db97e55a192a65"
   integrity sha512-ke8UG8ulpFOxO8f8gRYabHQe/ZntKlcig2Mp+8+URDP1D8vJZ0KUt7LYo07q25Z/+JVSgpr/cui9PIp5H6/+nA==
@@ -9873,11 +9926,11 @@ ua-parser-js@^0.7.18:
   integrity sha512-T3PVJ6uz8i0HzPxOF9SWzWAlfN/DavlpQqepn22xgve/5QecC+XMCAtmUNnY7C9StehaV6exjUCI801lOI7QlQ==
 
 uglify-js@^3.1.4:
-  version "3.4.9"
-  resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.4.9.tgz#af02f180c1207d76432e473ed24a28f4a782bae3"
-  integrity sha512-8CJsbKOtEbnJsTyv6LE6m6ZKniqMiFWmm9sRbopbkGs3gMPPfd3Fh8iIA4Ykv5MgaTbqHr4BaoGLJLZNhsrW1Q==
+  version "3.6.0"
+  resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.6.0.tgz#704681345c53a8b2079fb6cec294b05ead242ff5"
+  integrity sha512-W+jrUHJr3DXKhrsS7NUVxn3zqMOFn0hL/Ei6v0anCIMoKC93TjcflTagwIHLW7SfMFfiQuktQyFVCFHGUE0+yg==
   dependencies:
-    commander "~2.17.1"
+    commander "~2.20.0"
     source-map "~0.6.1"
 
 unicode-astral-regex@^1.0.1: