about summary refs log tree commit diff
path: root/app/javascript/flavours/glitch
diff options
context:
space:
mode:
Diffstat (limited to 'app/javascript/flavours/glitch')
-rw-r--r--app/javascript/flavours/glitch/actions/columns.js20
-rw-r--r--app/javascript/flavours/glitch/actions/notifications.js24
-rw-r--r--app/javascript/flavours/glitch/actions/streaming.js17
-rw-r--r--app/javascript/flavours/glitch/actions/timelines.js43
-rw-r--r--app/javascript/flavours/glitch/components/column_header.js29
-rw-r--r--app/javascript/flavours/glitch/components/modal_root.js6
-rw-r--r--app/javascript/flavours/glitch/components/scrollable_list.js2
-rw-r--r--app/javascript/flavours/glitch/components/status_list.js2
-rw-r--r--app/javascript/flavours/glitch/containers/status_container.js4
-rw-r--r--app/javascript/flavours/glitch/features/account/components/action_bar.js2
-rw-r--r--app/javascript/flavours/glitch/features/community_timeline/components/column_settings.js6
-rw-r--r--app/javascript/flavours/glitch/features/community_timeline/containers/column_settings_container.js30
-rw-r--r--app/javascript/flavours/glitch/features/community_timeline/index.js59
-rw-r--r--app/javascript/flavours/glitch/features/composer/index.js2
-rw-r--r--app/javascript/flavours/glitch/features/direct_timeline/components/column_settings.js35
-rw-r--r--app/javascript/flavours/glitch/features/direct_timeline/containers/column_settings_container.js2
-rw-r--r--app/javascript/flavours/glitch/features/drawer/index.js17
-rw-r--r--app/javascript/flavours/glitch/features/getting_started/index.js1
-rw-r--r--app/javascript/flavours/glitch/features/hashtag_timeline/components/column_settings.js102
-rw-r--r--app/javascript/flavours/glitch/features/hashtag_timeline/containers/column_settings_container.js31
-rw-r--r--app/javascript/flavours/glitch/features/hashtag_timeline/index.js73
-rw-r--r--app/javascript/flavours/glitch/features/local_settings/page/index.js8
-rw-r--r--app/javascript/flavours/glitch/features/notifications/components/column_settings.js18
-rw-r--r--app/javascript/flavours/glitch/features/notifications/components/filter_bar.js93
-rw-r--r--app/javascript/flavours/glitch/features/notifications/containers/column_settings_container.js4
-rw-r--r--app/javascript/flavours/glitch/features/notifications/containers/filter_bar_container.js16
-rw-r--r--app/javascript/flavours/glitch/features/notifications/index.js23
-rw-r--r--app/javascript/flavours/glitch/features/public_timeline/containers/column_settings_container.js31
-rw-r--r--app/javascript/flavours/glitch/features/public_timeline/index.js58
-rw-r--r--app/javascript/flavours/glitch/features/standalone/hashtag_timeline/index.js2
-rw-r--r--app/javascript/flavours/glitch/features/status/index.js4
-rw-r--r--app/javascript/flavours/glitch/features/ui/components/columns_area.js3
-rw-r--r--app/javascript/flavours/glitch/features/ui/components/confirmation_modal.js30
-rw-r--r--app/javascript/flavours/glitch/features/ui/index.js2
-rw-r--r--app/javascript/flavours/glitch/reducers/accounts_counters.js4
-rw-r--r--app/javascript/flavours/glitch/reducers/local_settings.js1
-rw-r--r--app/javascript/flavours/glitch/reducers/notifications.js3
-rw-r--r--app/javascript/flavours/glitch/reducers/settings.js23
-rw-r--r--app/javascript/flavours/glitch/reducers/timelines.js7
-rw-r--r--app/javascript/flavours/glitch/styles/_mixins.scss31
-rw-r--r--app/javascript/flavours/glitch/styles/admin.scss4
-rw-r--r--app/javascript/flavours/glitch/styles/components/accounts.scss29
-rw-r--r--app/javascript/flavours/glitch/styles/components/drawer.scss83
-rw-r--r--app/javascript/flavours/glitch/styles/components/modal.scss12
-rw-r--r--app/javascript/flavours/glitch/styles/components/search.scss28
-rw-r--r--app/javascript/flavours/glitch/styles/containers.scss6
-rw-r--r--app/javascript/flavours/glitch/styles/mastodon-light/diff.scss2
-rw-r--r--app/javascript/flavours/glitch/styles/widgets.scss43
-rw-r--r--app/javascript/flavours/glitch/util/initial_state.js1
49 files changed, 873 insertions, 203 deletions
diff --git a/app/javascript/flavours/glitch/actions/columns.js b/app/javascript/flavours/glitch/actions/columns.js
index bcb0cdf98..9b87415fb 100644
--- a/app/javascript/flavours/glitch/actions/columns.js
+++ b/app/javascript/flavours/glitch/actions/columns.js
@@ -1,8 +1,9 @@
 import { saveSettings } from './settings';
 
-export const COLUMN_ADD    = 'COLUMN_ADD';
-export const COLUMN_REMOVE = 'COLUMN_REMOVE';
-export const COLUMN_MOVE   = 'COLUMN_MOVE';
+export const COLUMN_ADD           = 'COLUMN_ADD';
+export const COLUMN_REMOVE        = 'COLUMN_REMOVE';
+export const COLUMN_MOVE          = 'COLUMN_MOVE';
+export const COLUMN_PARAMS_CHANGE = 'COLUMN_PARAMS_CHANGE';
 
 export function addColumn(id, params) {
   return dispatch => {
@@ -38,3 +39,16 @@ export function moveColumn(uuid, direction) {
     dispatch(saveSettings());
   };
 };
+
+export function changeColumnParams(uuid, path, value) {
+  return dispatch => {
+    dispatch({
+      type: COLUMN_PARAMS_CHANGE,
+      uuid,
+      path,
+      value,
+    });
+
+    dispatch(saveSettings());
+  };
+}
diff --git a/app/javascript/flavours/glitch/actions/notifications.js b/app/javascript/flavours/glitch/actions/notifications.js
index 0184d9c80..3cfad90a1 100644
--- a/app/javascript/flavours/glitch/actions/notifications.js
+++ b/app/javascript/flavours/glitch/actions/notifications.js
@@ -2,6 +2,7 @@ import api, { getLinks } from 'flavours/glitch/util/api';
 import IntlMessageFormat from 'intl-messageformat';
 import { fetchRelationships } from './accounts';
 import { defineMessages } from 'react-intl';
+import { List as ImmutableList } from 'immutable';
 import { unescapeHTML } from 'flavours/glitch/util/html';
 import { getFilters, regexFromFilters } from 'flavours/glitch/selectors';
 
@@ -22,6 +23,8 @@ export const NOTIFICATIONS_EXPAND_REQUEST = 'NOTIFICATIONS_EXPAND_REQUEST';
 export const NOTIFICATIONS_EXPAND_SUCCESS = 'NOTIFICATIONS_EXPAND_SUCCESS';
 export const NOTIFICATIONS_EXPAND_FAIL    = 'NOTIFICATIONS_EXPAND_FAIL';
 
+export const NOTIFICATIONS_FILTER_SET = 'NOTIFICATIONS_FILTER_SET';
+
 export const NOTIFICATIONS_CLEAR      = 'NOTIFICATIONS_CLEAR';
 export const NOTIFICATIONS_SCROLL_TOP = 'NOTIFICATIONS_SCROLL_TOP';
 
@@ -84,10 +87,16 @@ export function updateNotifications(notification, intlMessages, intlLocale) {
 const excludeTypesFromSettings = state => state.getIn(['settings', 'notifications', 'shows']).filter(enabled => !enabled).keySeq().toJS();
 
 
+const excludeTypesFromFilter = filter => {
+  const allTypes = ImmutableList(['follow', 'favourite', 'reblog', 'mention']);
+  return allTypes.filterNot(item => item === filter).toJS();
+};
+
 const noOp = () => {};
 
 export function expandNotifications({ maxId } = {}, done = noOp) {
   return (dispatch, getState) => {
+    const activeFilter = getState().getIn(['settings', 'notifications', 'quickFilter', 'active']);
     const notifications = getState().get('notifications');
     const isLoadingMore = !!maxId;
 
@@ -98,7 +107,9 @@ export function expandNotifications({ maxId } = {}, done = noOp) {
 
     const params = {
       max_id: maxId,
-      exclude_types: excludeTypesFromSettings(getState()),
+      exclude_types: activeFilter === 'all'
+        ? excludeTypesFromSettings(getState())
+        : excludeTypesFromFilter(activeFilter),
     };
 
     if (!maxId && notifications.get('items').size > 0) {
@@ -244,3 +255,14 @@ export function notificationsSetVisibility(visibility) {
     visibility: visibility,
   };
 };
+
+export function setFilter (filterType) {
+  return dispatch => {
+    dispatch({
+      type: NOTIFICATIONS_FILTER_SET,
+      path: ['notifications', 'quickFilter', 'active'],
+      value: filterType,
+    });
+    dispatch(expandNotifications());
+  };
+};
diff --git a/app/javascript/flavours/glitch/actions/streaming.js b/app/javascript/flavours/glitch/actions/streaming.js
index 00bbb78ae..8c1bd1f08 100644
--- a/app/javascript/flavours/glitch/actions/streaming.js
+++ b/app/javascript/flavours/glitch/actions/streaming.js
@@ -11,7 +11,7 @@ import { getLocale } from 'mastodon/locales';
 
 const { messages } = getLocale();
 
-export function connectTimelineStream (timelineId, path, pollingRefresh = null) {
+export function connectTimelineStream (timelineId, path, pollingRefresh = null, accept = null) {
 
   return connectStream (path, pollingRefresh, (dispatch, getState) => {
     const locale = getState().getIn(['meta', 'locale']);
@@ -23,7 +23,7 @@ export function connectTimelineStream (timelineId, path, pollingRefresh = null)
       onReceive (data) {
         switch(data.event) {
         case 'update':
-          dispatch(updateTimeline(timelineId, JSON.parse(data.payload)));
+          dispatch(updateTimeline(timelineId, JSON.parse(data.payload), accept));
           break;
         case 'delete':
           dispatch(deleteFromTimelines(data.payload));
@@ -44,10 +44,9 @@ const refreshHomeTimelineAndNotification = (dispatch, done) => {
   dispatch(expandHomeTimeline({}, () => dispatch(expandNotifications({}, done))));
 };
 
-export const connectUserStream = () => connectTimelineStream('home', 'user', refreshHomeTimelineAndNotification);
-export const connectCommunityStream = () => connectTimelineStream('community', 'public:local');
-export const connectMediaStream = () => connectTimelineStream('community', 'public:local');
-export const connectPublicStream = () => connectTimelineStream('public', 'public');
-export const connectHashtagStream = (tag) => connectTimelineStream(`hashtag:${tag}`, `hashtag&tag=${tag}`);
-export const connectDirectStream = () => connectTimelineStream('direct', 'direct');
-export const connectListStream = (id) => connectTimelineStream(`list:${id}`, `list&list=${id}`);
+export const connectUserStream      = () => connectTimelineStream('home', 'user', refreshHomeTimelineAndNotification);
+export const connectCommunityStream = ({ onlyMedia } = {}) => connectTimelineStream(`community${onlyMedia ? ':media' : ''}`, `public:local${onlyMedia ? ':media' : ''}`);
+export const connectPublicStream    = ({ onlyMedia } = {}) => connectTimelineStream(`public${onlyMedia ? ':media' : ''}`, `public${onlyMedia ? ':media' : ''}`);
+export const connectHashtagStream   = (id, tag, accept) => connectTimelineStream(`hashtag:${id}`, `hashtag&tag=${tag}`, null, accept);
+export const connectDirectStream    = () => connectTimelineStream('direct', 'direct');
+export const connectListStream      = id => connectTimelineStream(`list:${id}`, `list&list=${id}`);
diff --git a/app/javascript/flavours/glitch/actions/timelines.js b/app/javascript/flavours/glitch/actions/timelines.js
index d13d66516..bc21b4d5e 100644
--- a/app/javascript/flavours/glitch/actions/timelines.js
+++ b/app/javascript/flavours/glitch/actions/timelines.js
@@ -3,6 +3,7 @@ import { Map as ImmutableMap, List as ImmutableList } from 'immutable';
 
 export const TIMELINE_UPDATE  = 'TIMELINE_UPDATE';
 export const TIMELINE_DELETE  = 'TIMELINE_DELETE';
+export const TIMELINE_CLEAR   = 'TIMELINE_CLEAR';
 
 export const TIMELINE_EXPAND_REQUEST = 'TIMELINE_EXPAND_REQUEST';
 export const TIMELINE_EXPAND_SUCCESS = 'TIMELINE_EXPAND_SUCCESS';
@@ -12,8 +13,12 @@ export const TIMELINE_SCROLL_TOP = 'TIMELINE_SCROLL_TOP';
 
 export const TIMELINE_DISCONNECT = 'TIMELINE_DISCONNECT';
 
-export function updateTimeline(timeline, status) {
+export function updateTimeline(timeline, status, accept) {
   return (dispatch, getState) => {
+    if (typeof accept === 'function' && !accept(status)) {
+      return;
+    }
+
     dispatch({
       type: TIMELINE_UPDATE,
       timeline,
@@ -38,8 +43,20 @@ export function deleteFromTimelines(id) {
   };
 };
 
+export function clearTimeline(timeline) {
+  return (dispatch) => {
+    dispatch({ type: TIMELINE_CLEAR, timeline });
+  };
+};
+
 const noOp = () => {};
 
+const parseTags = (tags = {}, mode) => {
+  return (tags[mode] || []).map((tag) => {
+    return tag.value;
+  });
+};
+
 export function expandTimeline(timelineId, path, params = {}, done = noOp) {
   return (dispatch, getState) => {
     const timeline = getState().getIn(['timelines', timelineId], ImmutableMap());
@@ -69,15 +86,23 @@ 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 } = {}, done = noOp) => expandTimeline('public', '/api/v1/timelines/public', { max_id: maxId }, done);
-export const expandCommunityTimeline    = ({ maxId } = {}, done = noOp) => expandTimeline('community', '/api/v1/timelines/public', { local: true, max_id: maxId }, 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 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 });
-export const expandHashtagTimeline      = (hashtag, { maxId } = {}, done = noOp) => expandTimeline(`hashtag:${hashtag}`, `/api/v1/timelines/tag/${hashtag}`, { max_id: maxId }, done);
-export const expandListTimeline         = (id, { maxId } = {}, done = noOp) => expandTimeline(`list:${id}`, `/api/v1/timelines/list/${id}`, { max_id: maxId }, done);
+export const expandAccountMediaTimeline    = (accountId, { maxId } = {}) => expandTimeline(`account:${accountId}:media`, `/api/v1/accounts/${accountId}/statuses`, { max_id: maxId, only_media: true });
+export const expandListTimeline            = (id, { maxId } = {}, done = noOp) => expandTimeline(`list:${id}`, `/api/v1/timelines/list/${id}`, { max_id: maxId }, done);
+
+export const expandHashtagTimeline       = (hashtag, { maxId, tags } = {}, done = noOp) => {
+  return expandTimeline(`hashtag:${hashtag}`, `/api/v1/timelines/tag/${hashtag}`, {
+    max_id: maxId,
+    any: parseTags(tags, 'any'),
+    all: parseTags(tags, 'all'),
+    none: parseTags(tags, 'none'),
+  }, done);
+};
 
 export function expandTimelineRequest(timeline, isLoadingMore) {
   return {
diff --git a/app/javascript/flavours/glitch/components/column_header.js b/app/javascript/flavours/glitch/components/column_header.js
index 72207637d..87e848a59 100644
--- a/app/javascript/flavours/glitch/components/column_header.js
+++ b/app/javascript/flavours/glitch/components/column_header.js
@@ -47,6 +47,15 @@ export default class ColumnHeader extends React.PureComponent {
     animatingNCD: false,
   };
 
+  historyBack = () => {
+    // if history is exhausted, or we would leave mastodon, just go to root.
+    if (window.history.state) {
+      this.context.router.history.goBack();
+    } else {
+      this.context.router.history.push('/');
+    }
+  }
+
   handleToggleClick = (e) => {
     e.stopPropagation();
     this.setState({ collapsed: !this.state.collapsed, animating: true });
@@ -65,12 +74,7 @@ export default class ColumnHeader extends React.PureComponent {
   }
 
   handleBackClick = () => {
-    // if history is exhausted, or we would leave mastodon, just go to root.
-    if (window.history.state) {
-      this.context.router.history.goBack();
-    } else {
-      this.context.router.history.push('/');
-    }
+    this.historyBack();
   }
 
   handleTransitionEnd = () => {
@@ -81,13 +85,20 @@ export default class ColumnHeader extends React.PureComponent {
     this.setState({ animatingNCD: false });
   }
 
+  handlePin = () => {
+    if (!this.props.pinned) {
+      this.historyBack();
+    }
+    this.props.onPin();
+  }
+
   onEnterCleaningMode = () => {
     this.setState({ animatingNCD: true });
     this.props.onEnterCleaningMode(!this.props.notifCleaningActive);
   }
 
   render () {
-    const { intl, icon, active, children, pinned, onPin, multiColumn, extraButton, showBackButton, intl: { formatMessage }, notifCleaning, notifCleaningActive } = this.props;
+    const { intl, icon, active, children, pinned, multiColumn, extraButton, showBackButton, intl: { formatMessage }, notifCleaning, notifCleaningActive } = this.props;
     const { collapsed, animating, animatingNCD } = this.state;
 
     let title = this.props.title;
@@ -132,7 +143,7 @@ export default class ColumnHeader extends React.PureComponent {
     }
 
     if (multiColumn && pinned) {
-      pinButton = <button key='pin-button' className='text-btn column-header__setting-btn' onClick={onPin}><i className='fa fa fa-times' /> <FormattedMessage id='column_header.unpin' defaultMessage='Unpin' /></button>;
+      pinButton = <button key='pin-button' className='text-btn column-header__setting-btn' onClick={this.handlePin}><i className='fa fa fa-times' /> <FormattedMessage id='column_header.unpin' defaultMessage='Unpin' /></button>;
 
       moveButtons = (
         <div key='move-buttons' className='column-header__setting-arrows'>
@@ -141,7 +152,7 @@ export default class ColumnHeader extends React.PureComponent {
         </div>
       );
     } else if (multiColumn) {
-      pinButton = <button key='pin-button' className='text-btn column-header__setting-btn' onClick={onPin}><i className='fa fa fa-plus' /> <FormattedMessage id='column_header.pin' defaultMessage='Pin' /></button>;
+      pinButton = <button key='pin-button' className='text-btn column-header__setting-btn' onClick={this.handlePin}><i className='fa fa fa-plus' /> <FormattedMessage id='column_header.pin' defaultMessage='Pin' /></button>;
     }
 
     if (!pinned && (multiColumn || showBackButton)) {
diff --git a/app/javascript/flavours/glitch/components/modal_root.js b/app/javascript/flavours/glitch/components/modal_root.js
index cc26f6a11..7a90e6b8a 100644
--- a/app/javascript/flavours/glitch/components/modal_root.js
+++ b/app/javascript/flavours/glitch/components/modal_root.js
@@ -39,13 +39,15 @@ export default class ModalRoot extends React.PureComponent {
     } else if (!nextProps.children) {
       this.setState({ revealed: false });
     }
+    if (!nextProps.children && !!this.props.children) {
+      this.activeElement.focus();
+      this.activeElement = null;
+    }
   }
 
   componentDidUpdate (prevProps) {
     if (!this.props.children && !!prevProps.children) {
       this.getSiblings().forEach(sibling => sibling.removeAttribute('inert'));
-      this.activeElement.focus();
-      this.activeElement = null;
       this.handleModalClose();
     }
     if (this.props.children) {
diff --git a/app/javascript/flavours/glitch/components/scrollable_list.js b/app/javascript/flavours/glitch/components/scrollable_list.js
index bd922462e..21d717b81 100644
--- a/app/javascript/flavours/glitch/components/scrollable_list.js
+++ b/app/javascript/flavours/glitch/components/scrollable_list.js
@@ -47,7 +47,7 @@ export default class ScrollableList extends PureComponent {
       const { scrollTop, scrollHeight, clientHeight } = this.node;
       const offset = scrollHeight - scrollTop - clientHeight;
 
-      if (400 > offset && this.props.onLoadMore && !this.props.isLoading) {
+      if (400 > offset && this.props.onLoadMore && this.props.hasMore && !this.props.isLoading) {
         this.props.onLoadMore();
       }
 
diff --git a/app/javascript/flavours/glitch/components/status_list.js b/app/javascript/flavours/glitch/components/status_list.js
index 5249af76d..a7629bd54 100644
--- a/app/javascript/flavours/glitch/components/status_list.js
+++ b/app/javascript/flavours/glitch/components/status_list.js
@@ -25,7 +25,7 @@ export default class StatusList extends ImmutablePureComponent {
     prepend: PropTypes.node,
     alwaysPrepend: PropTypes.bool,
     emptyMessage: PropTypes.node,
-    timelineId: PropTypes.string,
+    timelineId: PropTypes.string.isRequired,
   };
 
   static defaultProps = {
diff --git a/app/javascript/flavours/glitch/containers/status_container.js b/app/javascript/flavours/glitch/containers/status_container.js
index 663bfbebc..f28dce609 100644
--- a/app/javascript/flavours/glitch/containers/status_container.js
+++ b/app/javascript/flavours/glitch/containers/status_container.js
@@ -22,6 +22,7 @@ import { muteStatus, unmuteStatus, deleteStatus } from 'flavours/glitch/actions/
 import { initMuteModal } from 'flavours/glitch/actions/mutes';
 import { initReport } from 'flavours/glitch/actions/reports';
 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';
 
@@ -71,10 +72,11 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
   onReply (status, router) {
     dispatch((_, getState) => {
       let state = getState();
-      if (state.getIn(['compose', 'text']).trim().length !== 0) {
+      if (state.getIn(['local_settings', 'confirm_before_clearing_draft']) && state.getIn(['compose', 'text']).trim().length !== 0) {
         dispatch(openModal('CONFIRM', {
           message: intl.formatMessage(messages.replyMessage),
           confirm: intl.formatMessage(messages.replyConfirm),
+          onDoNotAsk: () => dispatch(changeLocalSetting(['confirm_before_clearing_draft'], false)),
           onConfirm: () => dispatch(replyCompose(status, router)),
         }));
       } else {
diff --git a/app/javascript/flavours/glitch/features/account/components/action_bar.js b/app/javascript/flavours/glitch/features/account/components/action_bar.js
index ffa5b7e5e..fdacb7298 100644
--- a/app/javascript/flavours/glitch/features/account/components/action_bar.js
+++ b/app/javascript/flavours/glitch/features/account/components/action_bar.js
@@ -164,7 +164,7 @@ export default class ActionBar extends React.PureComponent {
 
             <NavLink exact activeClassName='active' className='account__action-bar__tab' to={`/accounts/${account.get('id')}/followers`}>
               <FormattedMessage id='account.followers' defaultMessage='Followers' />
-              <strong><FormattedNumber value={account.get('followers_count')} /></strong>
+              <strong>{ account.get('followers_count') < 0 ? '-' : <FormattedNumber value={account.get('followers_count')} /> }</strong>
             </NavLink>
           </div>
         </div>
diff --git a/app/javascript/flavours/glitch/features/community_timeline/components/column_settings.js b/app/javascript/flavours/glitch/features/community_timeline/components/column_settings.js
index aad5f3976..96db003ce 100644
--- a/app/javascript/flavours/glitch/features/community_timeline/components/column_settings.js
+++ b/app/javascript/flavours/glitch/features/community_timeline/components/column_settings.js
@@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
 import ImmutablePropTypes from 'react-immutable-proptypes';
 import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
 import SettingText from 'flavours/glitch/components/setting_text';
+import SettingToggle from 'flavours/glitch/features/notifications/components/setting_toggle';
 
 const messages = defineMessages({
   filter_regex: { id: 'home.column_settings.filter_regex', defaultMessage: 'Filter out by regular expressions' },
@@ -16,6 +17,7 @@ export default class ColumnSettings extends React.PureComponent {
     settings: ImmutablePropTypes.map.isRequired,
     onChange: PropTypes.func.isRequired,
     intl: PropTypes.object.isRequired,
+    columnId: PropTypes.string,
   };
 
   render () {
@@ -23,6 +25,10 @@ export default class ColumnSettings extends React.PureComponent {
 
     return (
       <div>
+        <div className='column-settings__row'>
+          <SettingToggle settings={settings} settingPath={['other', 'onlyMedia']} onChange={onChange} label={<FormattedMessage id='community.column_settings.media_only' defaultMessage='Media Only' />} />
+        </div>
+
         <span className='column-settings__section'><FormattedMessage id='home.column_settings.advanced' defaultMessage='Advanced' /></span>
 
         <div className='column-settings__row'>
diff --git a/app/javascript/flavours/glitch/features/community_timeline/containers/column_settings_container.js b/app/javascript/flavours/glitch/features/community_timeline/containers/column_settings_container.js
index 39387abb9..16a963dde 100644
--- a/app/javascript/flavours/glitch/features/community_timeline/containers/column_settings_container.js
+++ b/app/javascript/flavours/glitch/features/community_timeline/containers/column_settings_container.js
@@ -2,16 +2,26 @@ import { connect } from 'react-redux';
 import ColumnSettings from '../components/column_settings';
 import { changeSetting } from 'flavours/glitch/actions/settings';
 
-const mapStateToProps = state => ({
-  settings: state.getIn(['settings', 'community']),
-});
+const mapStateToProps = (state, { columnId }) => {
+  const uuid = columnId;
+  const columns = state.getIn(['settings', 'columns']);
+  const index = columns.findIndex(c => c.get('uuid') === uuid);
 
-const mapDispatchToProps = dispatch => ({
-
-  onChange (path, checked) {
-    dispatch(changeSetting(['community', ...path], checked));
-  },
-
-});
+  return {
+    settings: (uuid && index >= 0) ? columns.get(index).get('params') : state.getIn(['settings', 'community']),
+  };
+};
+ 
+const mapDispatchToProps = (dispatch, { columnId }) => {
+  return {
+    onChange (key, checked) {
+      if (columnId) {
+        dispatch(changeColumnParams(columnId, key, checked));
+      } else {
+        dispatch(changeSetting(['community', ...key], checked));
+      }
+    },
+  };
+};
 
 export default connect(mapStateToProps, mapDispatchToProps)(ColumnSettings);
diff --git a/app/javascript/flavours/glitch/features/community_timeline/index.js b/app/javascript/flavours/glitch/features/community_timeline/index.js
index ddcca2dc0..2c0fbff36 100644
--- a/app/javascript/flavours/glitch/features/community_timeline/index.js
+++ b/app/javascript/flavours/glitch/features/community_timeline/index.js
@@ -1,12 +1,12 @@
 import React from 'react';
 import { connect } from 'react-redux';
+import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
 import PropTypes from 'prop-types';
 import StatusListContainer from 'flavours/glitch/features/ui/containers/status_list_container';
 import Column from 'flavours/glitch/components/column';
 import ColumnHeader from 'flavours/glitch/components/column_header';
 import { expandCommunityTimeline } from 'flavours/glitch/actions/timelines';
 import { addColumn, removeColumn, moveColumn } from 'flavours/glitch/actions/columns';
-import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
 import ColumnSettingsContainer from './containers/column_settings_container';
 import { connectCommunityStream } from 'flavours/glitch/actions/streaming';
 
@@ -14,29 +14,45 @@ const messages = defineMessages({
   title: { id: 'column.community', defaultMessage: 'Local timeline' },
 });
 
-const mapStateToProps = state => ({
-  hasUnread: state.getIn(['timelines', 'community', 'unread']) > 0,
-});
+const mapStateToProps = (state, { onlyMedia, columnId }) => {
+  const uuid = columnId;
+  const columns = state.getIn(['settings', 'columns']);
+  const index = columns.findIndex(c => c.get('uuid') === uuid);
+
+  return {
+    hasUnread: state.getIn(['timelines', `community${onlyMedia ? ':media' : ''}`, 'unread']) > 0,
+    onlyMedia: (columnId && index >= 0) ? columns.get(index).getIn(['params', 'other', 'onlyMedia']) : state.getIn(['settings', 'community', 'other', 'onlyMedia']),
+  };
+};
 
 @connect(mapStateToProps)
 @injectIntl
 export default class CommunityTimeline extends React.PureComponent {
 
+  static defaultProps = {
+    onlyMedia: false,
+  };
+
+  static contextTypes = {
+    router: PropTypes.object,
+  };
+
   static propTypes = {
     dispatch: PropTypes.func.isRequired,
     columnId: PropTypes.string,
     intl: PropTypes.object.isRequired,
     hasUnread: PropTypes.bool,
     multiColumn: PropTypes.bool,
+    onlyMedia: PropTypes.bool,
   };
 
   handlePin = () => {
-    const { columnId, dispatch } = this.props;
+    const { columnId, dispatch, onlyMedia } = this.props;
 
     if (columnId) {
       dispatch(removeColumn(columnId));
     } else {
-      dispatch(addColumn('COMMUNITY', {}));
+      dispatch(addColumn('COMMUNITY', { other: { onlyMedia } }));
     }
   }
 
@@ -50,10 +66,20 @@ export default class CommunityTimeline extends React.PureComponent {
   }
 
   componentDidMount () {
-    const { dispatch } = this.props;
+    const { dispatch, onlyMedia } = this.props;
 
-    dispatch(expandCommunityTimeline());
-    this.disconnect = dispatch(connectCommunityStream());
+    dispatch(expandCommunityTimeline({ onlyMedia }));
+    this.disconnect = dispatch(connectCommunityStream({ onlyMedia }));
+  }
+
+  componentDidUpdate (prevProps) {
+    if (prevProps.onlyMedia !== this.props.onlyMedia) {
+      const { dispatch, onlyMedia } = this.props;
+
+      this.disconnect();
+      dispatch(expandCommunityTimeline({ onlyMedia }));
+      this.disconnect = dispatch(connectCommunityStream({ onlyMedia }));
+    }
   }
 
   componentWillUnmount () {
@@ -68,11 +94,17 @@ export default class CommunityTimeline extends React.PureComponent {
   }
 
   handleLoadMore = maxId => {
-    this.props.dispatch(expandCommunityTimeline({ maxId }));
+    const { dispatch, onlyMedia } = this.props;
+
+    dispatch(expandCommunityTimeline({ maxId, onlyMedia }));
+  }
+
+  shouldUpdateScroll = (prevRouterProps, { location }) => {
+    return !(location.state && location.state.mastodonModalOpen)
   }
 
   render () {
-    const { intl, hasUnread, columnId, multiColumn } = this.props;
+    const { intl, hasUnread, columnId, multiColumn, onlyMedia } = this.props;
     const pinned = !!columnId;
 
     return (
@@ -87,13 +119,14 @@ export default class CommunityTimeline extends React.PureComponent {
           pinned={pinned}
           multiColumn={multiColumn}
         >
-          <ColumnSettingsContainer />
+          <ColumnSettingsContainer columnId={columnId} />
         </ColumnHeader>
 
         <StatusListContainer
           trackScroll={!pinned}
           scrollKey={`community_timeline-${columnId}`}
-          timelineId='community'
+          shouldUpdateScroll={this.shouldUpdateScroll}
+          timelineId={`community${onlyMedia ? ':media' : ''}`}
           onLoadMore={this.handleLoadMore}
           emptyMessage={<FormattedMessage id='empty_column.community' defaultMessage='The local timeline is empty. Write something publicly to get the ball rolling!' />}
         />
diff --git a/app/javascript/flavours/glitch/features/composer/index.js b/app/javascript/flavours/glitch/features/composer/index.js
index a8f5a5c3c..ec0e405a4 100644
--- a/app/javascript/flavours/glitch/features/composer/index.js
+++ b/app/javascript/flavours/glitch/features/composer/index.js
@@ -30,6 +30,7 @@ import {
   closeModal,
   openModal,
 } from 'flavours/glitch/actions/modal';
+import { changeLocalSetting } from 'flavours/glitch/actions/local_settings';
 
 //  Components.
 import ComposerOptions from './options';
@@ -165,6 +166,7 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
       message: intl.formatMessage(messages.missingDescriptionMessage),
       confirm: intl.formatMessage(messages.missingDescriptionConfirm),
       onConfirm: () => dispatch(submitCompose(routerHistory)),
+      onDoNotAsk: () => dispatch(changeLocalSetting(['confirm_missing_media_description'], false)),
     }));
   },
   onSubmit(routerHistory) {
diff --git a/app/javascript/flavours/glitch/features/direct_timeline/components/column_settings.js b/app/javascript/flavours/glitch/features/direct_timeline/components/column_settings.js
new file mode 100644
index 000000000..a992b27bb
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/direct_timeline/components/column_settings.js
@@ -0,0 +1,35 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
+import SettingText from '../../../components/setting_text';
+
+const messages = defineMessages({
+  filter_regex: { id: 'home.column_settings.filter_regex', defaultMessage: 'Filter out by regular expressions' },
+  settings: { id: 'home.settings', defaultMessage: 'Column settings' },
+});
+
+@injectIntl
+export default class ColumnSettings extends React.PureComponent {
+
+  static propTypes = {
+    settings: ImmutablePropTypes.map.isRequired,
+    onChange: PropTypes.func.isRequired,
+    intl: PropTypes.object.isRequired,
+  };
+
+  render () {
+    const { settings, onChange, intl } = this.props;
+
+    return (
+      <div>
+        <span className='column-settings__section'><FormattedMessage id='home.column_settings.advanced' defaultMessage='Advanced' /></span>
+
+        <div className='column-settings__row'>
+          <SettingText settings={settings} settingKey={['regex', 'body']} onChange={onChange} label={intl.formatMessage(messages.filter_regex)} />
+        </div>
+      </div>
+    );
+  }
+
+}
diff --git a/app/javascript/flavours/glitch/features/direct_timeline/containers/column_settings_container.js b/app/javascript/flavours/glitch/features/direct_timeline/containers/column_settings_container.js
index 7292af264..6385d30a4 100644
--- a/app/javascript/flavours/glitch/features/direct_timeline/containers/column_settings_container.js
+++ b/app/javascript/flavours/glitch/features/direct_timeline/containers/column_settings_container.js
@@ -1,5 +1,5 @@
 import { connect } from 'react-redux';
-import ColumnSettings from 'flavours/glitch/features/community_timeline/components/column_settings';
+import ColumnSettings from '../components/column_settings';
 import { changeSetting } from 'flavours/glitch/actions/settings';
 
 const mapStateToProps = state => ({
diff --git a/app/javascript/flavours/glitch/features/drawer/index.js b/app/javascript/flavours/glitch/features/drawer/index.js
index 038a2513e..c8121b8e5 100644
--- a/app/javascript/flavours/glitch/features/drawer/index.js
+++ b/app/javascript/flavours/glitch/features/drawer/index.js
@@ -23,7 +23,7 @@ import DrawerResults from './results';
 import DrawerSearch from './search';
 
 //  Utils.
-import { me } from 'flavours/glitch/util/initial_state';
+import { me, mascot } from 'flavours/glitch/util/initial_state';
 import { wrap } from 'flavours/glitch/util/redux_helpers';
 
 //  Messages.
@@ -121,10 +121,17 @@ class Drawer extends React.Component {
             submitted={submitted}
             value={searchValue}
           /> }
-        <div className='contents'>
-          {!isSearchPage && <DrawerAccount account={account} />}
-          {!isSearchPage && <Composer />}
-          {multiColumn && <button className='mastodon' onClick={onClickElefriend} />}
+        <div className='drawer__pager'>
+          {!isSearchPage && <div className='drawer__inner'>
+            <DrawerAccount account={account} />
+            <Composer />
+            {multiColumn && (
+              <div className='drawer__inner__mastodon'>
+                {mascot ? <img alt='' draggable='false' src={mascot} /> : <button className='mastodon' onClick={onClickElefriend} />}
+              </div>
+            )}
+          </div>}
+
           {(multiColumn || isSearchPage) &&
             <DrawerResults
               results={results}
diff --git a/app/javascript/flavours/glitch/features/getting_started/index.js b/app/javascript/flavours/glitch/features/getting_started/index.js
index 0fd6437f5..126350813 100644
--- a/app/javascript/flavours/glitch/features/getting_started/index.js
+++ b/app/javascript/flavours/glitch/features/getting_started/index.js
@@ -165,7 +165,6 @@ export default class GettingStarted extends ImmutablePureComponent {
 
           <div className='getting-started__footer'>
             <ul>
-              <li><a href='https://bridge.joinmastodon.org/' target='_blank'><FormattedMessage id='getting_started.find_friends' defaultMessage='Find friends from Twitter' /></a> · </li>
               {invitesEnabled && <li><a href='/invites' target='_blank'><FormattedMessage id='getting_started.invite' defaultMessage='Invite people' /></a> · </li>}
               <li><a href='/about/more' target='_blank'><FormattedMessage id='navigation_bar.info' defaultMessage='About this instance' /></a> · </li>
               <li><a href='https://joinmastodon.org/apps' target='_blank'><FormattedMessage id='navigation_bar.apps' defaultMessage='Mobile apps' /></a> · </li>
diff --git a/app/javascript/flavours/glitch/features/hashtag_timeline/components/column_settings.js b/app/javascript/flavours/glitch/features/hashtag_timeline/components/column_settings.js
new file mode 100644
index 000000000..82936c838
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/hashtag_timeline/components/column_settings.js
@@ -0,0 +1,102 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import { injectIntl, FormattedMessage } from 'react-intl';
+import Toggle from 'react-toggle';
+import AsyncSelect from 'react-select/lib/Async';
+
+@injectIntl
+export default class ColumnSettings extends React.PureComponent {
+
+  static propTypes = {
+    settings: ImmutablePropTypes.map.isRequired,
+    onChange: PropTypes.func.isRequired,
+    onLoad: PropTypes.func.isRequired,
+    intl: PropTypes.object.isRequired,
+  };
+
+  state = {
+    open: this.hasTags(),
+  };
+
+  hasTags () {
+    return ['all', 'any', 'none'].map(mode => this.tags(mode).length > 0).includes(true);
+  }
+
+  tags (mode) {
+    let tags = this.props.settings.getIn(['tags', mode]) || [];
+    if (tags.toJSON) {
+      return tags.toJSON();
+    } else {
+      return tags;
+    }
+  };
+
+  onSelect = (mode) => {
+    return (value) => {
+      this.props.onChange(['tags', mode], value);
+    };
+  };
+
+  onToggle = () => {
+    if (this.state.open && this.hasTags()) {
+      this.props.onChange('tags', {});
+    }
+    this.setState({ open: !this.state.open });
+  };
+
+  modeSelect (mode) {
+    return (
+      <div className='column-settings__section'>
+        {this.modeLabel(mode)}
+        <AsyncSelect
+          isMulti
+          autoFocus
+          value={this.tags(mode)}
+          settings={this.props.settings}
+          settingPath={['tags', mode]}
+          onChange={this.onSelect(mode)}
+          loadOptions={this.props.onLoad}
+          classNamePrefix='column-settings__hashtag-select'
+          name='tags'
+        />
+      </div>
+    );
+  }
+
+  modeLabel (mode) {
+    switch(mode) {
+    case 'any':  return <FormattedMessage id='hashtag.column_settings.tag_mode.any' defaultMessage='Any of these' />;
+    case 'all':  return <FormattedMessage id='hashtag.column_settings.tag_mode.all' defaultMessage='All of these' />;
+    case 'none': return <FormattedMessage id='hashtag.column_settings.tag_mode.none' defaultMessage='None of these' />;
+    }
+    return '';
+  };
+
+  render () {
+    return (
+      <div>
+        <div className='column-settings__row'>
+          <div className='setting-toggle'>
+            <Toggle
+              id='hashtag.column_settings.tag_toggle'
+              onChange={this.onToggle}
+              checked={this.state.open}
+            />
+            <span className='setting-toggle__label'>
+              <FormattedMessage id='hashtag.column_settings.tag_toggle' defaultMessage='Include additional tags in this column' />
+            </span>
+          </div>
+        </div>
+        {this.state.open &&
+          <div className='column-settings__hashtags'>
+            {this.modeSelect('any')}
+            {this.modeSelect('all')}
+            {this.modeSelect('none')}
+          </div>
+        }
+      </div>
+    );
+  }
+
+}
diff --git a/app/javascript/flavours/glitch/features/hashtag_timeline/containers/column_settings_container.js b/app/javascript/flavours/glitch/features/hashtag_timeline/containers/column_settings_container.js
new file mode 100644
index 000000000..757cd48fb
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/hashtag_timeline/containers/column_settings_container.js
@@ -0,0 +1,31 @@
+import { connect } from 'react-redux';
+import ColumnSettings from '../components/column_settings';
+import { changeColumnParams } from 'flavours/glitch/actions/columns';
+import api from 'flavours/glitch/util/api';
+
+const mapStateToProps = (state, { columnId }) => {
+  const columns = state.getIn(['settings', 'columns']);
+  const index   = columns.findIndex(c => c.get('uuid') === columnId);
+
+  if (!(columnId && index >= 0)) {
+    return {};
+  }
+
+  return { settings: columns.get(index).get('params') };
+};
+
+const mapDispatchToProps = (dispatch, { columnId }) => ({
+  onChange (key, value) {
+    dispatch(changeColumnParams(columnId, key, value));
+  },
+
+  onLoad (value) {
+    return api().get('/api/v2/search', { params: { q: value } }).then(response => {
+      return (response.data.hashtags || []).map((tag) => {
+        return { value: tag.name, label: `#${tag.name}` };
+      });
+    });
+  },
+});
+
+export default connect(mapStateToProps, mapDispatchToProps)(ColumnSettings);
diff --git a/app/javascript/flavours/glitch/features/hashtag_timeline/index.js b/app/javascript/flavours/glitch/features/hashtag_timeline/index.js
index 311fabb63..d04e9cafa 100644
--- a/app/javascript/flavours/glitch/features/hashtag_timeline/index.js
+++ b/app/javascript/flavours/glitch/features/hashtag_timeline/index.js
@@ -4,10 +4,12 @@ import PropTypes from 'prop-types';
 import StatusListContainer from 'flavours/glitch/features/ui/containers/status_list_container';
 import Column from 'flavours/glitch/components/column';
 import ColumnHeader from 'flavours/glitch/components/column_header';
-import { expandHashtagTimeline } from 'flavours/glitch/actions/timelines';
+import ColumnSettingsContainer from './containers/column_settings_container';
+import { expandHashtagTimeline, clearTimeline } from 'flavours/glitch/actions/timelines';
 import { addColumn, removeColumn, moveColumn } from 'flavours/glitch/actions/columns';
 import { FormattedMessage } from 'react-intl';
 import { connectHashtagStream } from 'flavours/glitch/actions/streaming';
+import { isEqual } from 'lodash';
 
 const mapStateToProps = (state, props) => ({
   hasUnread: state.getIn(['timelines', `hashtag:${props.params.id}`, 'unread']) > 0,
@@ -16,6 +18,8 @@ const mapStateToProps = (state, props) => ({
 @connect(mapStateToProps)
 export default class HashtagTimeline extends React.PureComponent {
 
+  disconnects = [];
+
   static propTypes = {
     params: PropTypes.object.isRequired,
     columnId: PropTypes.string,
@@ -34,6 +38,30 @@ export default class HashtagTimeline extends React.PureComponent {
     }
   }
 
+  title = () => {
+    let title = [this.props.params.id];
+    if (this.additionalFor('any')) {
+      title.push(' ', <FormattedMessage id='hashtag.column_header.tag_mode.any'  values={{ additional: this.additionalFor('any') }} defaultMessage='or {additional}' />);
+    }
+    if (this.additionalFor('all')) {
+      title.push(' ', <FormattedMessage id='hashtag.column_header.tag_mode.all'  values={{ additional: this.additionalFor('all') }} defaultMessage='and {additional}' />);
+    }
+    if (this.additionalFor('none')) {
+      title.push(' ', <FormattedMessage id='hashtag.column_header.tag_mode.none' values={{ additional: this.additionalFor('none') }} defaultMessage='without {additional}' />);
+    }
+    return title;
+  }
+
+  additionalFor = (mode) => {
+    const { tags } = this.props.params;
+
+    if (tags && (tags[mode] || []).length > 0) {
+      return tags[mode].map(tag => tag.value).join('/');
+    } else {
+      return '';
+    }
+  }
+
   handleMove = (dir) => {
     const { columnId, dispatch } = this.props;
     dispatch(moveColumn(columnId, dir));
@@ -43,30 +71,40 @@ export default class HashtagTimeline extends React.PureComponent {
     this.column.scrollTop();
   }
 
-  _subscribe (dispatch, id) {
-    this.disconnect = dispatch(connectHashtagStream(id));
+  _subscribe (dispatch, id, tags = {}) {
+    let any  = (tags.any || []).map(tag => tag.value);
+    let all  = (tags.all || []).map(tag => tag.value);
+    let none = (tags.none || []).map(tag => tag.value);
+
+    [id, ...any].map((tag) => {
+      this.disconnects.push(dispatch(connectHashtagStream(id, tag, (status) => {
+        let tags = status.tags.map(tag => tag.name);
+        return all.filter(tag => tags.includes(tag)).length === all.length &&
+               none.filter(tag => tags.includes(tag)).length === 0;
+      })));
+    });
   }
 
   _unsubscribe () {
-    if (this.disconnect) {
-      this.disconnect();
-      this.disconnect = null;
-    }
+    this.disconnects.map(disconnect => disconnect());
+    this.disconnects = [];
   }
 
   componentDidMount () {
     const { dispatch } = this.props;
-    const { id } = this.props.params;
+    const { id, tags } = this.props.params;
 
-    dispatch(expandHashtagTimeline(id));
-    this._subscribe(dispatch, id);
+    dispatch(expandHashtagTimeline(id, { tags }));
   }
 
   componentWillReceiveProps (nextProps) {
-    if (nextProps.params.id !== this.props.params.id) {
-      this.props.dispatch(expandHashtagTimeline(nextProps.params.id));
+    const { dispatch, params } = this.props;
+    const { id, tags } = nextProps.params;
+    if (id !== params.id || !isEqual(tags, params.tags)) {
       this._unsubscribe();
-      this._subscribe(this.props.dispatch, nextProps.params.id);
+      this._subscribe(dispatch, id, tags);
+      this.props.dispatch(clearTimeline(`hashtag:${id}`));
+      this.props.dispatch(expandHashtagTimeline(id, { tags }));
     }
   }
 
@@ -79,7 +117,8 @@ export default class HashtagTimeline extends React.PureComponent {
   }
 
   handleLoadMore = maxId => {
-    this.props.dispatch(expandHashtagTimeline(this.props.params.id, { maxId }));
+    const { id, tags } = this.props.params;
+    this.props.dispatch(expandHashtagTimeline(id, { maxId, tags }));
   }
 
   render () {
@@ -92,14 +131,16 @@ export default class HashtagTimeline extends React.PureComponent {
         <ColumnHeader
           icon='hashtag'
           active={hasUnread}
-          title={id}
+          title={this.title()}
           onPin={this.handlePin}
           onMove={this.handleMove}
           onClick={this.handleHeaderClick}
           pinned={pinned}
           multiColumn={multiColumn}
           showBackButton
-        />
+        >
+          {columnId && <ColumnSettingsContainer columnId={columnId} />}
+        </ColumnHeader>
 
         <StatusListContainer
           trackScroll={!pinned}
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 6defdfbb6..4477a4e80 100644
--- a/app/javascript/flavours/glitch/features/local_settings/page/index.js
+++ b/app/javascript/flavours/glitch/features/local_settings/page/index.js
@@ -128,6 +128,14 @@ export default class LocalSettingsPage extends React.PureComponent {
         </LocalSettingsPageItem>
         <LocalSettingsPageItem
           settings={settings}
+          item={['confirm_before_clearing_draft']}
+          id='mastodon-settings--confirm_before_clearing_draft'
+          onChange={onChange}
+        >
+          <FormattedMessage id='settings.confirm_before_clearing_draft' defaultMessage='Show confirmation dialog before overwriting the message being composed' />
+        </LocalSettingsPageItem>
+        <LocalSettingsPageItem
+          settings={settings}
           item={['side_arm']}
           id='mastodon-settings--side_arm'
           options={[
diff --git a/app/javascript/flavours/glitch/features/notifications/components/column_settings.js b/app/javascript/flavours/glitch/features/notifications/components/column_settings.js
index d9638aaf3..4e35d5b4e 100644
--- a/app/javascript/flavours/glitch/features/notifications/components/column_settings.js
+++ b/app/javascript/flavours/glitch/features/notifications/components/column_settings.js
@@ -21,9 +21,11 @@ export default class ColumnSettings extends React.PureComponent {
   render () {
     const { settings, pushSettings, onChange, onClear } = this.props;
 
-    const alertStr = <FormattedMessage id='notifications.column_settings.alert' defaultMessage='Desktop notifications' />;
-    const showStr  = <FormattedMessage id='notifications.column_settings.show' defaultMessage='Show in column' />;
-    const soundStr = <FormattedMessage id='notifications.column_settings.sound' defaultMessage='Play sound' />;
+    const filterShowStr = <FormattedMessage id='notifications.column_settings.filter_bar.show' defaultMessage='Show' />;
+    const filterAdvancedStr = <FormattedMessage id='notifications.column_settings.filter_bar.advanced' defaultMessage='Display all categories' />;
+    const alertStr  = <FormattedMessage id='notifications.column_settings.alert' defaultMessage='Desktop notifications' />;
+    const showStr   = <FormattedMessage id='notifications.column_settings.show' defaultMessage='Show in column' />;
+    const soundStr  = <FormattedMessage id='notifications.column_settings.sound' defaultMessage='Play sound' />;
 
     const showPushSettings = pushSettings.get('browserSupport') && pushSettings.get('isSubscribed');
     const pushStr = showPushSettings && <FormattedMessage id='notifications.column_settings.push' defaultMessage='Push notifications' />;
@@ -35,6 +37,16 @@ export default class ColumnSettings extends React.PureComponent {
           <ClearColumnButton onClick={onClear} />
         </div>
 
+        <div role='group' aria-labelledby='notifications-filter-bar'>
+          <span id='notifications-filter-bar' className='column-settings__section'>
+            <FormattedMessage id='notifications.column_settings.filter_bar.category' defaultMessage='Quick filter bar' />
+          </span>
+          <div className='column-settings__row'>
+            <SettingToggle id='show-filter-bar' prefix='notifications' settings={settings} settingPath={['quickFilter', 'show']} onChange={onChange} label={filterShowStr} />
+            <SettingToggle id='show-filter-bar' prefix='notifications' settings={settings} settingPath={['quickFilter', 'advanced']} onChange={onChange} label={filterAdvancedStr} />
+          </div>
+        </div>
+
         <div role='group' aria-labelledby='notifications-follow'>
           <span id='notifications-follow' className='column-settings__section'><FormattedMessage id='notifications.column_settings.follow' defaultMessage='New followers:' /></span>
 
diff --git a/app/javascript/flavours/glitch/features/notifications/components/filter_bar.js b/app/javascript/flavours/glitch/features/notifications/components/filter_bar.js
new file mode 100644
index 000000000..f95a2c9de
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/notifications/components/filter_bar.js
@@ -0,0 +1,93 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
+
+const tooltips = defineMessages({
+  mentions: { id: 'notifications.filter.mentions', defaultMessage: 'Mentions' },
+  favourites: { id: 'notifications.filter.favourites', defaultMessage: 'Favourites' },
+  boosts: { id: 'notifications.filter.boosts', defaultMessage: 'Boosts' },
+  follows: { id: 'notifications.filter.follows', defaultMessage: 'Follows' },
+});
+
+export default @injectIntl
+class FilterBar extends React.PureComponent {
+
+  static propTypes = {
+    selectFilter: PropTypes.func.isRequired,
+    selectedFilter: PropTypes.string.isRequired,
+    advancedMode: PropTypes.bool.isRequired,
+    intl: PropTypes.object.isRequired,
+  };
+
+  onClick (notificationType) {
+    return () => this.props.selectFilter(notificationType);
+  }
+
+  render () {
+    const { selectedFilter, advancedMode, intl } = this.props;
+    const renderedElement = !advancedMode ? (
+      <div className='notification__filter-bar'>
+        <button
+          className={selectedFilter === 'all' ? 'active' : ''}
+          onClick={this.onClick('all')}
+        >
+          <FormattedMessage
+            id='notifications.filter.all'
+            defaultMessage='All'
+          />
+        </button>
+        <button
+          className={selectedFilter === 'mention' ? 'active' : ''}
+          onClick={this.onClick('mention')}
+        >
+          <FormattedMessage
+            id='notifications.filter.mentions'
+            defaultMessage='Mentions'
+          />
+        </button>
+      </div>
+    ) : (
+      <div className='notification__filter-bar'>
+        <button
+          className={selectedFilter === 'all' ? 'active' : ''}
+          onClick={this.onClick('all')}
+        >
+          <FormattedMessage
+            id='notifications.filter.all'
+            defaultMessage='All'
+          />
+        </button>
+        <button
+          className={selectedFilter === 'mention' ? 'active' : ''}
+          onClick={this.onClick('mention')}
+          title={intl.formatMessage(tooltips.mentions)}
+        >
+          <i className='fa fa-fw fa-at' />
+        </button>
+        <button
+          className={selectedFilter === 'favourite' ? 'active' : ''}
+          onClick={this.onClick('favourite')}
+          title={intl.formatMessage(tooltips.favourites)}
+        >
+          <i className='fa fa-fw fa-star' />
+        </button>
+        <button
+          className={selectedFilter === 'reblog' ? 'active' : ''}
+          onClick={this.onClick('reblog')}
+          title={intl.formatMessage(tooltips.boosts)}
+        >
+          <i className='fa fa-fw fa-retweet' />
+        </button>
+        <button
+          className={selectedFilter === 'follow' ? 'active' : ''}
+          onClick={this.onClick('follow')}
+          title={intl.formatMessage(tooltips.follows)}
+        >
+          <i className='fa fa-fw fa-user-plus' />
+        </button>
+      </div>
+    );
+    return renderedElement;
+  }
+
+}
diff --git a/app/javascript/flavours/glitch/features/notifications/containers/column_settings_container.js b/app/javascript/flavours/glitch/features/notifications/containers/column_settings_container.js
index 9585ea556..4b863712a 100644
--- a/app/javascript/flavours/glitch/features/notifications/containers/column_settings_container.js
+++ b/app/javascript/flavours/glitch/features/notifications/containers/column_settings_container.js
@@ -2,6 +2,7 @@ import { connect } from 'react-redux';
 import { defineMessages, injectIntl } from 'react-intl';
 import ColumnSettings from '../components/column_settings';
 import { changeSetting } from 'flavours/glitch/actions/settings';
+import { setFilter } from 'flavours/glitch/actions/notifications';
 import { clearNotifications } from 'flavours/glitch/actions/notifications';
 import { changeAlerts as changePushNotifications } from 'flavours/glitch/actions/push_notifications';
 import { openModal } from 'flavours/glitch/actions/modal';
@@ -21,6 +22,9 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
   onChange (path, checked) {
     if (path[0] === 'push') {
       dispatch(changePushNotifications(path.slice(1), checked));
+    } else if (path[0] === 'quickFilter') {
+      dispatch(changeSetting(['notifications', ...path], checked));
+      dispatch(setFilter('all'));
     } else {
       dispatch(changeSetting(['notifications', ...path], checked));
     }
diff --git a/app/javascript/flavours/glitch/features/notifications/containers/filter_bar_container.js b/app/javascript/flavours/glitch/features/notifications/containers/filter_bar_container.js
new file mode 100644
index 000000000..4d495c290
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/notifications/containers/filter_bar_container.js
@@ -0,0 +1,16 @@
+import { connect } from 'react-redux';
+import FilterBar from '../components/filter_bar';
+import { setFilter } from '../../../actions/notifications';
+
+const makeMapStateToProps = state => ({
+  selectedFilter: state.getIn(['settings', 'notifications', 'quickFilter', 'active']),
+  advancedMode: state.getIn(['settings', 'notifications', 'quickFilter', 'advanced']),
+});
+
+const mapDispatchToProps = (dispatch) => ({
+  selectFilter (newActiveFilter) {
+    dispatch(setFilter(newActiveFilter));
+  },
+});
+
+export default connect(makeMapStateToProps, mapDispatchToProps)(FilterBar);
diff --git a/app/javascript/flavours/glitch/features/notifications/index.js b/app/javascript/flavours/glitch/features/notifications/index.js
index 0e73f02d8..6a149927c 100644
--- a/app/javascript/flavours/glitch/features/notifications/index.js
+++ b/app/javascript/flavours/glitch/features/notifications/index.js
@@ -15,6 +15,7 @@ import { addColumn, removeColumn, moveColumn } from 'flavours/glitch/actions/col
 import NotificationContainer from './containers/notification_container';
 import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
 import ColumnSettingsContainer from './containers/column_settings_container';
+import FilterBarContainer from './containers/filter_bar_container';
 import { createSelector } from 'reselect';
 import { List as ImmutableList } from 'immutable';
 import { debounce } from 'lodash';
@@ -26,11 +27,22 @@ const messages = defineMessages({
 });
 
 const getNotifications = createSelector([
+  state => state.getIn(['settings', 'notifications', 'quickFilter', 'show']),
+  state => state.getIn(['settings', 'notifications', 'quickFilter', 'active']),
   state => ImmutableList(state.getIn(['settings', 'notifications', 'shows']).filter(item => !item).keys()),
   state => state.getIn(['notifications', 'items']),
-], (excludedTypes, notifications) => notifications.filterNot(item => item !== null && excludedTypes.includes(item.get('type'))));
+], (showFilterBar, allowedType, excludedTypes, notifications) => {
+  if (!showFilterBar || allowedType === 'all') {
+    // used if user changed the notification settings after loading the notifications from the server
+    // otherwise a list of notifications will come pre-filtered from the backend
+    // we need to turn it off for FilterBar in order not to block ourselves from seeing a specific category
+    return notifications.filterNot(item => item !== null && excludedTypes.includes(item.get('type')));
+  }
+  return notifications.filter(item => item !== null && allowedType === item.get('type'));
+});
 
 const mapStateToProps = state => ({
+  showFilterBar: state.getIn(['settings', 'notifications', 'quickFilter', 'show']),
   notifications: getNotifications(state),
   localSettings:  state.get('local_settings'),
   isLoading: state.getIn(['notifications', 'isLoading'], true),
@@ -60,6 +72,7 @@ export default class Notifications extends React.PureComponent {
   static propTypes = {
     columnId: PropTypes.string,
     notifications: ImmutablePropTypes.list.isRequired,
+    showFilterBar: PropTypes.bool.isRequired,
     dispatch: PropTypes.func.isRequired,
     shouldUpdateScroll: PropTypes.func,
     intl: PropTypes.object.isRequired,
@@ -151,12 +164,16 @@ export default class Notifications extends React.PureComponent {
   }
 
   render () {
-    const { intl, notifications, shouldUpdateScroll, isLoading, isUnread, columnId, multiColumn, hasMore } = this.props;
+    const { intl, notifications, shouldUpdateScroll, isLoading, isUnread, columnId, multiColumn, hasMore, showFilterBar } = this.props;
     const pinned = !!columnId;
     const emptyMessage = <FormattedMessage id='empty_column.notifications' defaultMessage="You don't have any notifications yet. Interact with others to start the conversation." />;
 
     let scrollableContent = null;
 
+    const filterBarContainer = showFilterBar
+      ? (<FilterBarContainer />)
+      : null;
+
     if (isLoading && this.scrollableContent) {
       scrollableContent = this.scrollableContent;
     } else if (notifications.size > 0 || hasMore) {
@@ -222,7 +239,7 @@ export default class Notifications extends React.PureComponent {
         >
           <ColumnSettingsContainer />
         </ColumnHeader>
-
+        {filterBarContainer}
         {scrollContainer}
       </Column>
     );
diff --git a/app/javascript/flavours/glitch/features/public_timeline/containers/column_settings_container.js b/app/javascript/flavours/glitch/features/public_timeline/containers/column_settings_container.js
index f042adbe6..ec4d74737 100644
--- a/app/javascript/flavours/glitch/features/public_timeline/containers/column_settings_container.js
+++ b/app/javascript/flavours/glitch/features/public_timeline/containers/column_settings_container.js
@@ -1,17 +1,28 @@
 import { connect } from 'react-redux';
 import ColumnSettings from 'flavours/glitch/features/community_timeline/components/column_settings';
 import { changeSetting } from 'flavours/glitch/actions/settings';
+import { changeColumnParams } from 'flavours/glitch/actions/columns';
+ 
+const mapStateToProps = (state, { columnId }) => {
+  const uuid = columnId;
+  const columns = state.getIn(['settings', 'columns']);
+  const index = columns.findIndex(c => c.get('uuid') === uuid);
 
-const mapStateToProps = state => ({
-  settings: state.getIn(['settings', 'public']),
-});
+  return {
+    settings: (uuid && index >= 0) ? columns.get(index).get('params') : state.getIn(['settings', 'public']),
+  };
+};
 
-const mapDispatchToProps = dispatch => ({
-
-  onChange (path, checked) {
-    dispatch(changeSetting(['public', ...path], checked));
-  },
-
-});
+const mapDispatchToProps = (dispatch, { columnId }) => {
+  return {
+    onChange (key, checked) {
+      if (columnId) {
+        dispatch(changeColumnParams(columnId, key, checked));
+      } else {
+        dispatch(changeSetting(['public', ...key], checked));
+      }
+    },
+  };
+};
 
 export default connect(mapStateToProps, mapDispatchToProps)(ColumnSettings);
diff --git a/app/javascript/flavours/glitch/features/public_timeline/index.js b/app/javascript/flavours/glitch/features/public_timeline/index.js
index 53f2836f1..477d3b8c7 100644
--- a/app/javascript/flavours/glitch/features/public_timeline/index.js
+++ b/app/javascript/flavours/glitch/features/public_timeline/index.js
@@ -1,12 +1,12 @@
 import React from 'react';
 import { connect } from 'react-redux';
+import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
 import PropTypes from 'prop-types';
 import StatusListContainer from 'flavours/glitch/features/ui/containers/status_list_container';
 import Column from 'flavours/glitch/components/column';
 import ColumnHeader from 'flavours/glitch/components/column_header';
 import { expandPublicTimeline } from 'flavours/glitch/actions/timelines';
 import { addColumn, removeColumn, moveColumn } from 'flavours/glitch/actions/columns';
-import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
 import ColumnSettingsContainer from './containers/column_settings_container';
 import { connectPublicStream } from 'flavours/glitch/actions/streaming';
 
@@ -14,29 +14,45 @@ const messages = defineMessages({
   title: { id: 'column.public', defaultMessage: 'Federated timeline' },
 });
 
-const mapStateToProps = state => ({
-  hasUnread: state.getIn(['timelines', 'public', 'unread']) > 0,
-});
+const mapStateToProps = (state, { onlyMedia, columnId }) => {
+  const uuid = columnId;
+  const columns = state.getIn(['settings', 'columns']);
+  const index = columns.findIndex(c => c.get('uuid') === uuid);
+
+  return {
+    hasUnread: state.getIn(['timelines', `public${onlyMedia ? ':media' : ''}`, 'unread']) > 0,
+    onlyMedia: (columnId && index >= 0) ? columns.get(index).getIn(['params', 'other', 'onlyMedia']) : state.getIn(['settings', 'public', 'other', 'onlyMedia']),
+  };
+};
 
 @connect(mapStateToProps)
 @injectIntl
 export default class PublicTimeline extends React.PureComponent {
 
+  static defaultProps = {
+    onlyMedia: false,
+  };
+
+  static contextTypes = {
+    router: PropTypes.object,
+  };
+
   static propTypes = {
     dispatch: PropTypes.func.isRequired,
     intl: PropTypes.object.isRequired,
     columnId: PropTypes.string,
     multiColumn: PropTypes.bool,
     hasUnread: PropTypes.bool,
+    onlyMedia: PropTypes.bool,
   };
 
   handlePin = () => {
-    const { columnId, dispatch } = this.props;
+    const { columnId, dispatch, onlyMedia } = this.props;
 
     if (columnId) {
       dispatch(removeColumn(columnId));
     } else {
-      dispatch(addColumn('PUBLIC', {}));
+      dispatch(addColumn('PUBLIC', { other: { onlyMedia } }));
     }
   }
 
@@ -50,10 +66,20 @@ export default class PublicTimeline extends React.PureComponent {
   }
 
   componentDidMount () {
-    const { dispatch } = this.props;
+    const { dispatch, onlyMedia } = this.props;
 
-    dispatch(expandPublicTimeline());
-    this.disconnect = dispatch(connectPublicStream());
+    dispatch(expandPublicTimeline({ onlyMedia }));
+    this.disconnect = dispatch(connectPublicStream({ onlyMedia }));
+  }
+
+  componentDidUpdate (prevProps) {
+    if (prevProps.onlyMedia !== this.props.onlyMedia) {
+      const { dispatch, onlyMedia } = this.props;
+
+      this.disconnect();
+      dispatch(expandPublicTimeline({ onlyMedia }));
+      this.disconnect = dispatch(connectPublicStream({ onlyMedia }));
+    }
   }
 
   componentWillUnmount () {
@@ -68,11 +94,17 @@ export default class PublicTimeline extends React.PureComponent {
   }
 
   handleLoadMore = maxId => {
-    this.props.dispatch(expandPublicTimeline({ maxId }));
+    const { dispatch, onlyMedia } = this.props;
+
+    dispatch(expandPublicTimeline({ maxId, onlyMedia }));
+  }
+
+  shouldUpdateScroll = (prevRouterProps, { location }) => {
+    return !(location.state && location.state.mastodonModalOpen)
   }
 
   render () {
-    const { intl, columnId, hasUnread, multiColumn } = this.props;
+    const { intl, columnId, hasUnread, multiColumn, onlyMedia } = this.props;
     const pinned = !!columnId;
 
     return (
@@ -87,11 +119,11 @@ export default class PublicTimeline extends React.PureComponent {
           pinned={pinned}
           multiColumn={multiColumn}
         >
-          <ColumnSettingsContainer />
+          <ColumnSettingsContainer columnId={columnId} />
         </ColumnHeader>
 
         <StatusListContainer
-          timelineId='public'
+          timelineId={`public${onlyMedia ? ':media' : ''}`}
           onLoadMore={this.handleLoadMore}
           trackScroll={!pinned}
           scrollKey={`public_timeline-${columnId}`}
diff --git a/app/javascript/flavours/glitch/features/standalone/hashtag_timeline/index.js b/app/javascript/flavours/glitch/features/standalone/hashtag_timeline/index.js
index dc02f1c91..44ba8db92 100644
--- a/app/javascript/flavours/glitch/features/standalone/hashtag_timeline/index.js
+++ b/app/javascript/flavours/glitch/features/standalone/hashtag_timeline/index.js
@@ -27,7 +27,7 @@ export default class HashtagTimeline extends React.PureComponent {
     const { dispatch, hashtag } = this.props;
 
     dispatch(expandHashtagTimeline(hashtag));
-    this.disconnect = dispatch(connectHashtagStream(hashtag));
+    this.disconnect = dispatch(connectHashtagStream(hashtag, hashtag));
   }
 
   componentWillUnmount () {
diff --git a/app/javascript/flavours/glitch/features/status/index.js b/app/javascript/flavours/glitch/features/status/index.js
index d2d5a05c8..aa508c483 100644
--- a/app/javascript/flavours/glitch/features/status/index.js
+++ b/app/javascript/flavours/glitch/features/status/index.js
@@ -24,6 +24,7 @@ import {
   mentionCompose,
   directCompose,
 } from 'flavours/glitch/actions/compose';
+import { changeLocalSetting } from 'flavours/glitch/actions/local_settings';
 import { blockAccount } from 'flavours/glitch/actions/accounts';
 import { muteStatus, unmuteStatus, deleteStatus } from 'flavours/glitch/actions/statuses';
 import { initMuteModal } from 'flavours/glitch/actions/mutes';
@@ -98,7 +99,7 @@ const makeMapStateToProps = () => {
       ancestorsIds,
       descendantsIds,
       settings: state.get('local_settings'),
-      askReplyConfirmation: state.getIn(['compose', 'text']).trim().length !== 0,
+      askReplyConfirmation: state.getIn(['local_settings', 'confirm_before_clearing_draft']) && state.getIn(['compose', 'text']).trim().length !== 0,
     };
   };
 
@@ -196,6 +197,7 @@ export default class Status extends ImmutablePureComponent {
       dispatch(openModal('CONFIRM', {
         message: intl.formatMessage(messages.replyMessage),
         confirm: intl.formatMessage(messages.replyConfirm),
+        onDoNotAsk: () => dispatch(changeLocalSetting(['confirm_before_clearing_draft'], false)),
         onConfirm: () => dispatch(replyCompose(status, this.context.router.history)),
       }));
     } else {
diff --git a/app/javascript/flavours/glitch/features/ui/components/columns_area.js b/app/javascript/flavours/glitch/features/ui/components/columns_area.js
index 71cb7e8c9..65a63294b 100644
--- a/app/javascript/flavours/glitch/features/ui/components/columns_area.js
+++ b/app/javascript/flavours/glitch/features/ui/components/columns_area.js
@@ -179,10 +179,11 @@ export default class ColumnsArea extends ImmutablePureComponent {
       <div className='columns-area' ref={this.setRef}>
         {columns.map(column => {
           const params = column.get('params', null) === null ? null : column.get('params').toJS();
+          const other  = params && params.other ? params.other : {};
 
           return (
             <BundleContainer key={column.get('uuid')} fetchComponent={componentMap[column.get('id')]} loading={this.renderLoading(column.get('id'))} error={this.renderError}>
-              {SpecificComponent => <SpecificComponent columnId={column.get('uuid')} params={params} multiColumn />}
+              {SpecificComponent => <SpecificComponent columnId={column.get('uuid')} params={params} multiColumn {...other} />}
             </BundleContainer>
           );
         })}
diff --git a/app/javascript/flavours/glitch/features/ui/components/confirmation_modal.js b/app/javascript/flavours/glitch/features/ui/components/confirmation_modal.js
index d4d1e587e..07281f818 100644
--- a/app/javascript/flavours/glitch/features/ui/components/confirmation_modal.js
+++ b/app/javascript/flavours/glitch/features/ui/components/confirmation_modal.js
@@ -11,6 +11,7 @@ export default class ConfirmationModal extends React.PureComponent {
     confirm: PropTypes.string.isRequired,
     onClose: PropTypes.func.isRequired,
     onConfirm: PropTypes.func.isRequired,
+    onDoNotAsk: PropTypes.func,
     intl: PropTypes.object.isRequired,
   };
 
@@ -21,6 +22,9 @@ export default class ConfirmationModal extends React.PureComponent {
   handleClick = () => {
     this.props.onClose();
     this.props.onConfirm();
+    if (this.props.onDoNotAsk && this.doNotAskCheckbox.checked) {
+      this.props.onDoNotAsk();
+    }
   }
 
   handleCancel = () => {
@@ -31,8 +35,12 @@ export default class ConfirmationModal extends React.PureComponent {
     this.button = c;
   }
 
+  setDoNotAskRef = (c) => {
+    this.doNotAskCheckbox = c;
+  }
+
   render () {
-    const { message, confirm } = this.props;
+    const { message, confirm, onDoNotAsk } = this.props;
 
     return (
       <div className='modal-root__modal confirmation-modal'>
@@ -40,11 +48,21 @@ export default class ConfirmationModal extends React.PureComponent {
           {message}
         </div>
 
-        <div className='confirmation-modal__action-bar'>
-          <Button onClick={this.handleCancel} className='confirmation-modal__cancel-button'>
-            <FormattedMessage id='confirmation_modal.cancel' defaultMessage='Cancel' />
-          </Button>
-          <Button text={confirm} onClick={this.handleClick} ref={this.setRef} />
+        <div>
+          { onDoNotAsk && (
+            <div className='confirmation-modal__do_not_ask_again'>
+              <input type='checkbox' id='confirmation-modal__do_not_ask_again-checkbox' ref={this.setDoNotAskRef} />
+              <label for='confirmation-modal__do_not_ask_again-checkbox'>
+                <FormattedMessage id='confirmation_modal.do_not_ask_again' defaultMessage='Do not ask for confirmation again' />
+              </label>
+            </div>
+          )}
+          <div className='confirmation-modal__action-bar'>
+            <Button onClick={this.handleCancel} className='confirmation-modal__cancel-button'>
+              <FormattedMessage id='confirmation_modal.cancel' defaultMessage='Cancel' />
+            </Button>
+            <Button text={confirm} onClick={this.handleClick} ref={this.setRef} />
+          </div>
         </div>
       </div>
     );
diff --git a/app/javascript/flavours/glitch/features/ui/index.js b/app/javascript/flavours/glitch/features/ui/index.js
index 510bb9540..5d82f0dd7 100644
--- a/app/javascript/flavours/glitch/features/ui/index.js
+++ b/app/javascript/flavours/glitch/features/ui/index.js
@@ -467,7 +467,7 @@ export default class UI extends React.Component {
               <WrappedRoute path='/keyboard-shortcuts' component={KeyboardShortcuts} content={children} />
               <WrappedRoute path='/timelines/home' component={HomeTimeline} content={children} />
               <WrappedRoute path='/timelines/public' exact component={PublicTimeline} content={children} />
-              <WrappedRoute path='/timelines/public/local' component={CommunityTimeline} content={children} />
+              <WrappedRoute path='/timelines/public/local' exact component={CommunityTimeline} content={children} />
               <WrappedRoute path='/timelines/direct' component={DirectTimeline} content={children} />
               <WrappedRoute path='/timelines/tag/:id' component={HashtagTimeline} content={children} />
               <WrappedRoute path='/timelines/list/:id' component={ListTimeline} content={children} />
diff --git a/app/javascript/flavours/glitch/reducers/accounts_counters.js b/app/javascript/flavours/glitch/reducers/accounts_counters.js
index 64dff9b55..acf363ca5 100644
--- a/app/javascript/flavours/glitch/reducers/accounts_counters.js
+++ b/app/javascript/flavours/glitch/reducers/accounts_counters.js
@@ -141,9 +141,9 @@ export default function accountsCounters(state = initialState, action) {
     if (action.alreadyFollowing) {
       return state;
     }
-    return state.updateIn([action.relationship.id, 'followers_count'], num => num + 1);
+    return state.updateIn([action.relationship.id, 'followers_count'], num => num < 0 ? num : num + 1);
   case ACCOUNT_UNFOLLOW_SUCCESS:
-    return state.updateIn([action.relationship.id, 'followers_count'], num => Math.max(0, num - 1));
+    return state.updateIn([action.relationship.id, 'followers_count'], num => num < 0 ? num : Math.max(0, num - 1));
   default:
     return state;
   }
diff --git a/app/javascript/flavours/glitch/reducers/local_settings.js b/app/javascript/flavours/glitch/reducers/local_settings.js
index bf42810e9..15239f28e 100644
--- a/app/javascript/flavours/glitch/reducers/local_settings.js
+++ b/app/javascript/flavours/glitch/reducers/local_settings.js
@@ -14,6 +14,7 @@ const initialState = ImmutableMap({
   show_reply_count : false,
   always_show_spoilers_field: false,
   confirm_missing_media_description: false,
+  confirm_before_clearing_draft: true,
   preselect_on_reply: true,
   inline_preview_cards: true,
   content_warnings : ImmutableMap({
diff --git a/app/javascript/flavours/glitch/reducers/notifications.js b/app/javascript/flavours/glitch/reducers/notifications.js
index b65c51f32..6667966c0 100644
--- a/app/javascript/flavours/glitch/reducers/notifications.js
+++ b/app/javascript/flavours/glitch/reducers/notifications.js
@@ -6,6 +6,7 @@ import {
   NOTIFICATIONS_EXPAND_SUCCESS,
   NOTIFICATIONS_EXPAND_REQUEST,
   NOTIFICATIONS_EXPAND_FAIL,
+  NOTIFICATIONS_FILTER_SET,
   NOTIFICATIONS_CLEAR,
   NOTIFICATIONS_SCROLL_TOP,
   NOTIFICATIONS_DELETE_MARKED_REQUEST,
@@ -197,6 +198,8 @@ export default function notifications(state = initialState, action) {
   case NOTIFICATIONS_DELETE_MARKED_FAIL:
   case NOTIFICATIONS_EXPAND_FAIL:
     return state.set('isLoading', false);
+  case NOTIFICATIONS_FILTER_SET:
+    return state.set('items', ImmutableList()).set('hasMore', true);
   case NOTIFICATIONS_SCROLL_TOP:
     return updateTop(state, action.top);
   case NOTIFICATIONS_UPDATE:
diff --git a/app/javascript/flavours/glitch/reducers/settings.js b/app/javascript/flavours/glitch/reducers/settings.js
index c04f262da..384ff2d44 100644
--- a/app/javascript/flavours/glitch/reducers/settings.js
+++ b/app/javascript/flavours/glitch/reducers/settings.js
@@ -1,5 +1,6 @@
 import { SETTING_CHANGE, SETTING_SAVE } from 'flavours/glitch/actions/settings';
-import { COLUMN_ADD, COLUMN_REMOVE, COLUMN_MOVE } from 'flavours/glitch/actions/columns';
+import { NOTIFICATIONS_FILTER_SET } from 'flavours/glitch/actions/notifications';
+import { COLUMN_ADD, COLUMN_REMOVE, COLUMN_MOVE, COLUMN_PARAMS_CHANGE } from 'flavours/glitch/actions/columns';
 import { STORE_HYDRATE } from 'flavours/glitch/actions/store';
 import { EMOJI_USE } from 'flavours/glitch/actions/emojis';
 import { LIST_DELETE_SUCCESS, LIST_FETCH_FAIL } from '../actions/lists';
@@ -34,6 +35,12 @@ const initialState = ImmutableMap({
       mention: true,
     }),
 
+    quickFilter: ImmutableMap({
+      active: 'all',
+      show: true,
+      advanced: false,
+    }),
+
     shows: ImmutableMap({
       follow: true,
       favourite: true,
@@ -91,6 +98,17 @@ const moveColumn = (state, uuid, direction) => {
     .set('saved', false);
 };
 
+const changeColumnParams = (state, uuid, path, value) => {
+  const columns = state.get('columns');
+  const index   = columns.findIndex(item => item.get('uuid') === uuid);
+
+  const newColumns = columns.update(index, column => column.updateIn(['params', ...path], () => value));
+
+  return state
+    .set('columns', newColumns)
+    .set('saved', false);
+};
+
 const updateFrequentEmojis = (state, emoji) => state.update('frequentlyUsedEmojis', ImmutableMap(), map => map.update(emoji.id, 0, count => count + 1)).set('saved', false);
 
 const filterDeadListColumns = (state, listId) => state.update('columns', columns => columns.filterNot(column => column.get('id') === 'LIST' && column.get('params').get('id') === listId));
@@ -99,6 +117,7 @@ export default function settings(state = initialState, action) {
   switch(action.type) {
   case STORE_HYDRATE:
     return hydrate(state, action.state.get('settings'));
+  case NOTIFICATIONS_FILTER_SET:
   case SETTING_CHANGE:
     return state
       .setIn(action.path, action.value)
@@ -113,6 +132,8 @@ export default function settings(state = initialState, action) {
       .set('saved', false);
   case COLUMN_MOVE:
     return moveColumn(state, action.uuid, action.direction);
+  case COLUMN_PARAMS_CHANGE:
+    return changeColumnParams(state, action.uuid, action.path, action.value);
   case EMOJI_USE:
     return updateFrequentEmojis(state, action.emoji);
   case SETTING_SAVE:
diff --git a/app/javascript/flavours/glitch/reducers/timelines.js b/app/javascript/flavours/glitch/reducers/timelines.js
index a9eaae26e..acd127833 100644
--- a/app/javascript/flavours/glitch/reducers/timelines.js
+++ b/app/javascript/flavours/glitch/reducers/timelines.js
@@ -1,6 +1,7 @@
 import {
   TIMELINE_UPDATE,
   TIMELINE_DELETE,
+  TIMELINE_CLEAR,
   TIMELINE_EXPAND_SUCCESS,
   TIMELINE_EXPAND_REQUEST,
   TIMELINE_EXPAND_FAIL,
@@ -81,6 +82,10 @@ const deleteStatus = (state, id, accountId, references) => {
   return state;
 };
 
+const clearTimeline = (state, timeline) => {
+  return state.updateIn([timeline, 'items'], list => list.clear());
+};
+
 const filterTimelines = (state, relationship, statuses) => {
   let references;
 
@@ -121,6 +126,8 @@ export default function timelines(state = initialState, action) {
     return updateTimeline(state, action.timeline, fromJS(action.status));
   case TIMELINE_DELETE:
     return deleteStatus(state, action.id, action.accountId, action.references, action.reblogOf);
+  case TIMELINE_CLEAR:
+    return clearTimeline(state, action.timeline);
   case ACCOUNT_BLOCK_SUCCESS:
   case ACCOUNT_MUTE_SUCCESS:
     return filterTimelines(state, action.relationship, action.statuses);
diff --git a/app/javascript/flavours/glitch/styles/_mixins.scss b/app/javascript/flavours/glitch/styles/_mixins.scss
index 2e317c382..c46d7260d 100644
--- a/app/javascript/flavours/glitch/styles/_mixins.scss
+++ b/app/javascript/flavours/glitch/styles/_mixins.scss
@@ -51,3 +51,34 @@
     border-radius: 0px;
   }
 }
+
+@mixin search-input() {
+  outline: 0;
+  box-sizing: border-box;
+  width: 100%;
+  border: none;
+  box-shadow: none;
+  font-family: inherit;
+  background: $ui-base-color;
+  color: $darker-text-color;
+  font-size: 14px;
+  margin: 0;
+
+  &::-moz-focus-inner {
+    border: 0;
+  }
+
+  &::-moz-focus-inner,
+  &:focus,
+  &:active {
+    outline: 0 !important;
+  }
+
+  &:focus {
+    background: lighten($ui-base-color, 4%);
+  }
+
+  @media screen and (max-width: 600px) {
+    font-size: 16px;
+  }
+}
diff --git a/app/javascript/flavours/glitch/styles/admin.scss b/app/javascript/flavours/glitch/styles/admin.scss
index e16920dd4..635888a2c 100644
--- a/app/javascript/flavours/glitch/styles/admin.scss
+++ b/app/javascript/flavours/glitch/styles/admin.scss
@@ -553,6 +553,10 @@ a.name-tag,
     border-left-color: lighten($error-red, 12%);
   }
 
+  &.warning {
+    border-left-color: $gold-star;
+  }
+
   &__bubble {
     padding: 16px;
     padding-left: 14px;
diff --git a/app/javascript/flavours/glitch/styles/components/accounts.scss b/app/javascript/flavours/glitch/styles/components/accounts.scss
index d87cd9c43..ce6cc8b29 100644
--- a/app/javascript/flavours/glitch/styles/components/accounts.scss
+++ b/app/javascript/flavours/glitch/styles/components/accounts.scss
@@ -339,6 +339,26 @@
   display: block;
   font-weight: 500;
   margin-bottom: 10px;
+
+  .column-settings__hashtag-select {
+    &__control {
+      @include search-input();
+    }
+
+    &__multi-value {
+      background: lighten($ui-base-color, 8%);
+    }
+
+    &__multi-value__label,
+    &__input {
+      color: $darker-text-color;
+    }
+
+    &__indicator-separator,
+    &__dropdown-indicator {
+      display: none;
+    }
+  }
 }
 
 .column-settings__row {
@@ -445,12 +465,21 @@
   }
 }
 
+.notification__filter-bar,
 .account__section-headline {
   background: darken($ui-base-color, 4%);
   border-bottom: 1px solid lighten($ui-base-color, 8%);
   cursor: default;
   display: flex;
+  flex-shrink: 0;
+
+  button {
+    background: darken($ui-base-color, 4%);
+    border: 0;
+    margin: 0;
+  }
 
+  button,
   a {
     display: block;
     flex: 1 1 auto;
diff --git a/app/javascript/flavours/glitch/styles/components/drawer.scss b/app/javascript/flavours/glitch/styles/components/drawer.scss
index a5c9d0130..2821deec7 100644
--- a/app/javascript/flavours/glitch/styles/components/drawer.scss
+++ b/app/javascript/flavours/glitch/styles/components/drawer.scss
@@ -1,9 +1,10 @@
 .drawer {
+  width: 300px;
+  box-sizing: border-box;
   display: flex;
   flex-direction: column;
-  box-sizing: border-box;
+  overflow-y: hidden;
   padding: 10px 5px;
-  width: 300px;
   flex: none;
 
   &:first-child {
@@ -37,41 +38,6 @@
   }
 
   .react-swipeable-view-container & { height: 100% }
-
-  & > .contents {
-    display: flex;
-    position: relative;
-    flex-direction: column;
-    padding: 0;
-    flex-grow: 1;
-    background: lighten($ui-base-color, 13%);
-    overflow-x: hidden;
-    overflow-y: auto;
-
-    & > .mastodon {
-      flex: 1;
-      border: none;
-      cursor: inherit;
-    }
-  }
-
-  @for $i from 0 through 3 {
-    &.mbstobon-#{$i} > .contents {
-      @if $i == 3 {
-        background: url('~flavours/glitch/images/wave-drawer.png') no-repeat bottom / 100% auto, lighten($ui-base-color, 13%);
-      } @else {
-        background: url('~flavours/glitch/images/wave-drawer-glitched.png') no-repeat bottom / 100% auto, lighten($ui-base-color, 13%);
-      }
-
-      & > .mastodon {
-        background: url("~flavours/glitch/images/mbstobon-ui-#{$i}.png") no-repeat left bottom / contain;
-
-        @if $i != 3 {
-          filter: contrast(50%) brightness(50%);
-        }
-      }
-    }
-  }
 }
 
 .drawer--header {
@@ -341,6 +307,31 @@
   }
 }
 
+.drawer__inner__mastodon {
+  background: lighten($ui-base-color, 13%) url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 234.80078 31.757813" width="234.80078" height="31.757812"><path d="M19.599609 0c-1.05 0-2.10039.375-2.90039 1.125L0 16.925781v14.832031h234.80078V17.025391l-16.5-15.900391c-1.6-1.5-4.20078-1.5-5.80078 0l-13.80078 13.099609c-1.6 1.5-4.19883 1.5-5.79883 0L179.09961 1.125c-1.6-1.5-4.19883-1.5-5.79883 0L159.5 14.224609c-1.6 1.5-4.20078 1.5-5.80078 0L139.90039 1.125c-1.6-1.5-4.20078-1.5-5.80078 0l-13.79883 13.099609c-1.6 1.5-4.20078 1.5-5.80078 0L100.69922 1.125c-1.600001-1.5-4.198829-1.5-5.798829 0l-13.59961 13.099609c-1.6 1.5-4.200781 1.5-5.800781 0L61.699219 1.125c-1.6-1.5-4.198828-1.5-5.798828 0L42.099609 14.224609c-1.6 1.5-4.198828 1.5-5.798828 0L22.5 1.125C21.7.375 20.649609 0 19.599609 0z" fill="#{hex-color($ui-base-color)}"/></svg>') no-repeat bottom / 100% auto;
+  flex: 1;
+  min-height: 47px;
+
+  > img {
+    display: block;
+    object-fit: contain;
+    object-position: bottom left;
+    width: 100%;
+    height: 100%;
+    pointer-events: none;
+    user-drag: none;
+    user-select: none;
+  }
+
+  > .mastodon {
+    display: block;
+    width: 100%;
+    height: 100%;
+    border: none;
+    cursor: inherit;
+  }
+}
+
 .pseudo-drawer {
   background: lighten($ui-base-color, 13%);
   font-size: 13px;
@@ -356,3 +347,21 @@
   height: 100%;
   background: rgba($base-overlay-background, 0.5);
 }
+
+@for $i from 0 through 3 {
+  .mbstobon-#{$i} .drawer__inner__mastodon {
+    @if $i == 3 {
+      background: url('~flavours/glitch/images/wave-drawer.png') no-repeat bottom / 100% auto, lighten($ui-base-color, 13%);
+    } @else {
+      background: url('~flavours/glitch/images/wave-drawer-glitched.png') no-repeat bottom / 100% auto, lighten($ui-base-color, 13%);
+    }
+
+    & > .mastodon {
+      background: url("~flavours/glitch/images/mbstobon-ui-#{$i}.png") no-repeat left bottom / contain;
+
+      @if $i != 3 {
+        filter: contrast(50%) brightness(50%);
+      }
+    }
+  }
+}
diff --git a/app/javascript/flavours/glitch/styles/components/modal.scss b/app/javascript/flavours/glitch/styles/components/modal.scss
index 1bfedc383..dc26ca3b5 100644
--- a/app/javascript/flavours/glitch/styles/components/modal.scss
+++ b/app/javascript/flavours/glitch/styles/components/modal.scss
@@ -686,6 +686,18 @@
   }
 }
 
+.confirmation-modal__do_not_ask_again {
+  padding-left: 20px;
+  padding-right: 20px;
+  padding-bottom: 10px;
+
+  font-size: 14px;
+
+  label, input {
+    vertical-align: middle;
+  }
+}
+
 .confirmation-modal__container,
 .mute-modal__container,
 .report-modal__target {
diff --git a/app/javascript/flavours/glitch/styles/components/search.scss b/app/javascript/flavours/glitch/styles/components/search.scss
index f9e4b5883..3746fbad2 100644
--- a/app/javascript/flavours/glitch/styles/components/search.scss
+++ b/app/javascript/flavours/glitch/styles/components/search.scss
@@ -3,36 +3,10 @@
 }
 
 .search__input {
-  outline: 0;
-  box-sizing: border-box;
   display: block;
-  width: 100%;
-  border: none;
   padding: 10px;
   padding-right: 30px;
-  font-family: inherit;
-  background: $ui-base-color;
-  color: $darker-text-color;
-  font-size: 14px;
-  margin: 0;
-
-  &::-moz-focus-inner {
-    border: 0;
-  }
-
-  &::-moz-focus-inner,
-  &:focus,
-  &:active {
-    outline: 0 !important;
-  }
-
-  &:focus {
-    background: lighten($ui-base-color, 4%);
-  }
-
-  @media screen and (max-width: 600px) {
-    font-size: 16px;
-  }
+  @include search-input();
 }
 
 .search__icon {
diff --git a/app/javascript/flavours/glitch/styles/containers.scss b/app/javascript/flavours/glitch/styles/containers.scss
index 398458e47..82d4050d7 100644
--- a/app/javascript/flavours/glitch/styles/containers.scss
+++ b/app/javascript/flavours/glitch/styles/containers.scss
@@ -296,6 +296,12 @@
         text-decoration: underline;
         color: $primary-text-color;
       }
+
+      @media screen and (max-width: $no-gap-breakpoint) {
+        &.optional {
+          display: none;
+        }
+      }
     }
 
     .nav-button {
diff --git a/app/javascript/flavours/glitch/styles/mastodon-light/diff.scss b/app/javascript/flavours/glitch/styles/mastodon-light/diff.scss
index 55a8983e5..339ee7cf0 100644
--- a/app/javascript/flavours/glitch/styles/mastodon-light/diff.scss
+++ b/app/javascript/flavours/glitch/styles/mastodon-light/diff.scss
@@ -58,7 +58,7 @@
   background: $ui-base-color;
 }
 
-.drawer > .contents {
+.drawer__inner__mastodon {
   background: $ui-base-color url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 234.80078 31.757813" width="234.80078" height="31.757812"><path d="M19.599609 0c-1.05 0-2.10039.375-2.90039 1.125L0 16.925781v14.832031h234.80078V17.025391l-16.5-15.900391c-1.6-1.5-4.20078-1.5-5.80078 0l-13.80078 13.099609c-1.6 1.5-4.19883 1.5-5.79883 0L179.09961 1.125c-1.6-1.5-4.19883-1.5-5.79883 0L159.5 14.224609c-1.6 1.5-4.20078 1.5-5.80078 0L139.90039 1.125c-1.6-1.5-4.20078-1.5-5.80078 0l-13.79883 13.099609c-1.6 1.5-4.20078 1.5-5.80078 0L100.69922 1.125c-1.600001-1.5-4.198829-1.5-5.798829 0l-13.59961 13.099609c-1.6 1.5-4.200781 1.5-5.800781 0L61.699219 1.125c-1.6-1.5-4.198828-1.5-5.798828 0L42.099609 14.224609c-1.6 1.5-4.198828 1.5-5.798828 0L22.5 1.125C21.7.375 20.649609 0 19.599609 0z" fill="#{hex-color(darken($ui-base-color, 13%))}"/></svg>') no-repeat bottom / 100% auto !important;
 
   .mastodon {
diff --git a/app/javascript/flavours/glitch/styles/widgets.scss b/app/javascript/flavours/glitch/styles/widgets.scss
index c863e3b4f..87e633c70 100644
--- a/app/javascript/flavours/glitch/styles/widgets.scss
+++ b/app/javascript/flavours/glitch/styles/widgets.scss
@@ -229,18 +229,6 @@
   margin-bottom: 10px;
 }
 
-.moved-account-widget,
-.memoriam-widget,
-.box-widget,
-.contact-widget,
-.landing-page__information.contact-widget {
-  @media screen and (max-width: $no-gap-breakpoint) {
-    margin-bottom: 0;
-    box-shadow: none;
-    border-radius: 0;
-  }
-}
-
 .page-header {
   background: lighten($ui-base-color, 8%);
   box-shadow: 0 0 15px rgba($base-shadow-color, 0.2);
@@ -261,11 +249,20 @@
     font-size: 15px;
     color: $darker-text-color;
   }
+
+  @media screen and (max-width: $no-gap-breakpoint) {
+    margin-top: 0;
+    background: lighten($ui-base-color, 4%);
+
+    h1 {
+      font-size: 24px;
+    }
+  }
 }
 
 .directory {
   background: $ui-base-color;
-  border-radius: 0 0 4px 4px;
+  border-radius: 4px;
   box-shadow: 0 0 15px rgba($base-shadow-color, 0.2);
 
   &__tag {
@@ -407,4 +404,24 @@
       font-size: 14px;
     }
   }
+
+  @media screen and (max-width: $no-gap-breakpoint) {
+    tbody td.optional {
+      display: none;
+    }
+  }
+}
+
+.moved-account-widget,
+.memoriam-widget,
+.box-widget,
+.contact-widget,
+.landing-page__information.contact-widget,
+.directory,
+.page-header {
+  @media screen and (max-width: $no-gap-breakpoint) {
+    margin-bottom: 0;
+    box-shadow: none;
+    border-radius: 0;
+  }
 }
diff --git a/app/javascript/flavours/glitch/util/initial_state.js b/app/javascript/flavours/glitch/util/initial_state.js
index d12c05c0d..a3c65563c 100644
--- a/app/javascript/flavours/glitch/util/initial_state.js
+++ b/app/javascript/flavours/glitch/util/initial_state.js
@@ -24,6 +24,7 @@ export const searchEnabled = getMeta('search_enabled');
 export const maxChars = (initialState && initialState.max_toot_chars) || 500;
 export const invitesEnabled = getMeta('invites_enabled');
 export const version = getMeta('version');
+export const mascot = getMeta('mascot');
 export const isStaff = getMeta('is_staff');
 
 export default initialState;