about summary refs log tree commit diff
path: root/app/javascript/flavours
diff options
context:
space:
mode:
Diffstat (limited to 'app/javascript/flavours')
-rw-r--r--app/javascript/flavours/glitch/actions/compose.js5
-rw-r--r--app/javascript/flavours/glitch/actions/reports.js9
-rw-r--r--app/javascript/flavours/glitch/actions/timelines.js19
-rw-r--r--app/javascript/flavours/glitch/components/status_content.js11
-rw-r--r--app/javascript/flavours/glitch/components/status_list.js19
-rw-r--r--app/javascript/flavours/glitch/components/status_prepend.js10
-rw-r--r--app/javascript/flavours/glitch/containers/status_container.js5
-rw-r--r--app/javascript/flavours/glitch/features/account/components/action_bar.js4
-rw-r--r--app/javascript/flavours/glitch/features/account_gallery/components/media_item.js16
-rw-r--r--app/javascript/flavours/glitch/features/account_gallery/index.js5
-rw-r--r--app/javascript/flavours/glitch/features/account_timeline/components/header.js13
-rw-r--r--app/javascript/flavours/glitch/features/account_timeline/index.js36
-rw-r--r--app/javascript/flavours/glitch/features/composer/index.js7
-rw-r--r--app/javascript/flavours/glitch/features/followers/index.js2
-rw-r--r--app/javascript/flavours/glitch/features/following/index.js2
-rw-r--r--app/javascript/flavours/glitch/features/getting_started/index.js51
-rw-r--r--app/javascript/flavours/glitch/features/report/components/status_check_box.js46
-rw-r--r--app/javascript/flavours/glitch/features/ui/components/column_link.js8
-rw-r--r--app/javascript/flavours/glitch/features/ui/components/report_modal.js47
-rw-r--r--app/javascript/flavours/glitch/features/ui/index.js1
-rw-r--r--app/javascript/flavours/glitch/reducers/compose.js3
-rw-r--r--app/javascript/flavours/glitch/reducers/reports.js4
-rw-r--r--app/javascript/flavours/glitch/styles/components/accounts.scss96
-rw-r--r--app/javascript/flavours/glitch/styles/components/index.scss11
-rw-r--r--app/javascript/flavours/glitch/styles/components/media.scss1
-rw-r--r--app/javascript/flavours/glitch/styles/components/modal.scss89
-rw-r--r--app/javascript/flavours/glitch/styles/components/status.scss29
-rw-r--r--app/javascript/flavours/glitch/util/react_router_helpers.js9
28 files changed, 425 insertions, 133 deletions
diff --git a/app/javascript/flavours/glitch/actions/compose.js b/app/javascript/flavours/glitch/actions/compose.js
index c46387104..1e9386859 100644
--- a/app/javascript/flavours/glitch/actions/compose.js
+++ b/app/javascript/flavours/glitch/actions/compose.js
@@ -102,8 +102,9 @@ export function mentionCompose(account, router) {
 export function submitCompose() {
   return function (dispatch, getState) {
     let status = getState().getIn(['compose', 'text'], '');
+    let media  = getState().getIn(['compose', 'media_attachments']);
 
-    if (!status || !status.length) {
+    if ((!status || !status.length) && media.size === 0) {
       return;
     }
 
@@ -114,7 +115,7 @@ export function submitCompose() {
     api(getState).post('/api/v1/statuses', {
       status,
       in_reply_to_id: getState().getIn(['compose', 'in_reply_to'], null),
-      media_ids: getState().getIn(['compose', 'media_attachments']).map(item => item.get('id')),
+      media_ids: media.map(item => item.get('id')),
       sensitive: getState().getIn(['compose', 'sensitive']),
       spoiler_text: getState().getIn(['compose', 'spoiler_text'], ''),
       visibility: getState().getIn(['compose', 'privacy']),
diff --git a/app/javascript/flavours/glitch/actions/reports.js b/app/javascript/flavours/glitch/actions/reports.js
index ad4fd18a9..80c3b3280 100644
--- a/app/javascript/flavours/glitch/actions/reports.js
+++ b/app/javascript/flavours/glitch/actions/reports.js
@@ -10,6 +10,7 @@ export const REPORT_SUBMIT_FAIL    = 'REPORT_SUBMIT_FAIL';
 
 export const REPORT_STATUS_TOGGLE  = 'REPORT_STATUS_TOGGLE';
 export const REPORT_COMMENT_CHANGE = 'REPORT_COMMENT_CHANGE';
+export const REPORT_FORWARD_CHANGE = 'REPORT_FORWARD_CHANGE';
 
 export function initReport(account, status) {
   return dispatch => {
@@ -45,6 +46,7 @@ export function submitReport() {
       account_id: getState().getIn(['reports', 'new', 'account_id']),
       status_ids: getState().getIn(['reports', 'new', 'status_ids']),
       comment: getState().getIn(['reports', 'new', 'comment']),
+      forward: getState().getIn(['reports', 'new', 'forward']),
     }).then(response => {
       dispatch(closeModal());
       dispatch(submitReportSuccess(response.data));
@@ -78,3 +80,10 @@ export function changeReportComment(comment) {
     comment,
   };
 };
+
+export function changeReportForward(forward) {
+  return {
+    type: REPORT_FORWARD_CHANGE,
+    forward,
+  };
+};
diff --git a/app/javascript/flavours/glitch/actions/timelines.js b/app/javascript/flavours/glitch/actions/timelines.js
index 3a9d64084..d99c6d98b 100644
--- a/app/javascript/flavours/glitch/actions/timelines.js
+++ b/app/javascript/flavours/glitch/actions/timelines.js
@@ -117,14 +117,15 @@ export function refreshTimeline(timelineId, path, params = {}) {
   };
 };
 
-export const refreshHomeTimeline         = () => refreshTimeline('home', '/api/v1/timelines/home');
-export const refreshPublicTimeline       = () => refreshTimeline('public', '/api/v1/timelines/public');
-export const refreshCommunityTimeline    = () => refreshTimeline('community', '/api/v1/timelines/public', { local: true });
-export const refreshDirectTimeline       = () => refreshTimeline('direct', '/api/v1/timelines/direct');
-export const refreshAccountTimeline      = accountId => refreshTimeline(`account:${accountId}`, `/api/v1/accounts/${accountId}/statuses`);
-export const refreshAccountMediaTimeline = accountId => refreshTimeline(`account:${accountId}:media`, `/api/v1/accounts/${accountId}/statuses`, { only_media: true });
-export const refreshHashtagTimeline      = hashtag => refreshTimeline(`hashtag:${hashtag}`, `/api/v1/timelines/tag/${hashtag}`);
-export const refreshListTimeline         = id => refreshTimeline(`list:${id}`, `/api/v1/timelines/list/${id}`);
+export const refreshHomeTimeline            = () => refreshTimeline('home', '/api/v1/timelines/home');
+export const refreshPublicTimeline          = () => refreshTimeline('public', '/api/v1/timelines/public');
+export const refreshCommunityTimeline       = () => refreshTimeline('community', '/api/v1/timelines/public', { local: true });
+export const refreshDirectTimeline          = () => refreshTimeline('direct', '/api/v1/timelines/direct');
+export const refreshAccountTimeline         = (accountId, withReplies) => refreshTimeline(`account:${accountId}${withReplies ? ':with_replies' : ''}`, `/api/v1/accounts/${accountId}/statuses`, { exclude_replies: !withReplies });
+export const refreshAccountFeaturedTimeline = accountId => refreshTimeline(`account:${accountId}:pinned`, `/api/v1/accounts/${accountId}/statuses`, { pinned: true });
+export const refreshAccountMediaTimeline    = accountId => refreshTimeline(`account:${accountId}:media`, `/api/v1/accounts/${accountId}/statuses`, { only_media: true });
+export const refreshHashtagTimeline         = hashtag => refreshTimeline(`hashtag:${hashtag}`, `/api/v1/timelines/tag/${hashtag}`);
+export const refreshListTimeline            = id => refreshTimeline(`list:${id}`, `/api/v1/timelines/list/${id}`);
 
 export function refreshTimelineFail(timeline, error, skipLoading) {
   return {
@@ -163,7 +164,7 @@ export const expandHomeTimeline         = () => expandTimeline('home', '/api/v1/
 export const expandPublicTimeline       = () => expandTimeline('public', '/api/v1/timelines/public');
 export const expandCommunityTimeline    = () => expandTimeline('community', '/api/v1/timelines/public', { local: true });
 export const expandDirectTimeline       = () => expandTimeline('direct', '/api/v1/timelines/direct');
-export const expandAccountTimeline      = accountId => expandTimeline(`account:${accountId}`, `/api/v1/accounts/${accountId}/statuses`);
+export const expandAccountTimeline      = (accountId, withReplies) => expandTimeline(`account:${accountId}${withReplies ? ':with_replies' : ''}`, `/api/v1/accounts/${accountId}/statuses`, { exclude_replies: !withReplies })
 export const expandAccountMediaTimeline = accountId => expandTimeline(`account:${accountId}:media`, `/api/v1/accounts/${accountId}/statuses`, { only_media: true });
 export const expandHashtagTimeline      = hashtag => expandTimeline(`hashtag:${hashtag}`, `/api/v1/timelines/tag/${hashtag}`);
 export const expandListTimeline         = id => expandTimeline(`list:${id}`, `/api/v1/timelines/list/${id}`);
diff --git a/app/javascript/flavours/glitch/components/status_content.js b/app/javascript/flavours/glitch/components/status_content.js
index 0c40e62cc..a15bf7c52 100644
--- a/app/javascript/flavours/glitch/components/status_content.js
+++ b/app/javascript/flavours/glitch/components/status_content.js
@@ -23,7 +23,12 @@ export default class StatusContent extends React.PureComponent {
   };
 
   _updateStatusLinks () {
-    const node  = this.node;
+    const node = this.node;
+
+    if (!node) {
+      return;
+    }
+
     const links = node.querySelectorAll('a');
 
     for (var i = 0; i < links.length; ++i) {
@@ -126,6 +131,10 @@ export default class StatusContent extends React.PureComponent {
       disabled,
     } = this.props;
 
+    if (status.get('content').length === 0) {
+      return null;
+    }
+
     const hidden = this.props.setExpansion ? !this.props.expanded : this.state.hidden;
 
     const content = { __html: status.get('contentHtml') };
diff --git a/app/javascript/flavours/glitch/components/status_list.js b/app/javascript/flavours/glitch/components/status_list.js
index f253f0fdc..ea40463da 100644
--- a/app/javascript/flavours/glitch/components/status_list.js
+++ b/app/javascript/flavours/glitch/components/status_list.js
@@ -11,6 +11,7 @@ export default class StatusList extends ImmutablePureComponent {
   static propTypes = {
     scrollKey: PropTypes.string.isRequired,
     statusIds: ImmutablePropTypes.list.isRequired,
+    featuredStatusIds: ImmutablePropTypes.list,
     onScrollToBottom: PropTypes.func,
     onScrollToTop: PropTypes.func,
     onScroll: PropTypes.func,
@@ -50,7 +51,7 @@ export default class StatusList extends ImmutablePureComponent {
   }
 
   render () {
-    const { statusIds, ...other }  = this.props;
+    const { statusIds, featuredStatusIds, ...other }  = this.props;
     const { isLoading, isPartial } = other;
 
     if (isPartial) {
@@ -68,8 +69,8 @@ export default class StatusList extends ImmutablePureComponent {
       );
     }
 
-    const scrollableContent = (isLoading || statusIds.size > 0) ? (
-      statusIds.map((statusId) => (
+    let scrollableContent = (isLoading || statusIds.size > 0) ? (
+      statusIds.map(statusId => (
         <StatusContainer
           key={statusId}
           id={statusId}
@@ -79,6 +80,18 @@ export default class StatusList extends ImmutablePureComponent {
       ))
     ) : null;
 
+    if (scrollableContent && featuredStatusIds) {
+      scrollableContent = featuredStatusIds.map(statusId => (
+        <StatusContainer
+          key={`f-${statusId}`}
+          id={statusId}
+          featured
+          onMoveUp={this.handleMoveUp}
+          onMoveDown={this.handleMoveDown}
+        />
+      )).concat(scrollableContent);
+    }
+
     return (
       <ScrollableList {...other} ref={this.setRef}>
         {scrollableContent}
diff --git a/app/javascript/flavours/glitch/components/status_prepend.js b/app/javascript/flavours/glitch/components/status_prepend.js
index bd2559e46..f4ef83135 100644
--- a/app/javascript/flavours/glitch/components/status_prepend.js
+++ b/app/javascript/flavours/glitch/components/status_prepend.js
@@ -34,6 +34,10 @@ export default class StatusPrepend extends React.PureComponent {
       </a>
     );
     switch (type) {
+    case 'featured':
+      return (
+        <FormattedMessage id='status.pinned' defaultMessage='Pinned toot' />
+      );
     case 'reblogged_by':
       return (
         <FormattedMessage
@@ -67,11 +71,11 @@ export default class StatusPrepend extends React.PureComponent {
     const { type } = this.props;
 
     return !type ? null : (
-      <aside className={type === 'reblogged_by' ? 'status__prepend' : 'notification__message'}>
-        <div className={type === 'reblogged_by' ? 'status__prepend-icon-wrapper' : 'notification__favourite-icon-wrapper'}>
+      <aside className={type === 'reblogged_by' || type === 'featured' ? 'status__prepend' : 'notification__message'}>
+        <div className={type === 'reblogged_by' || type === 'featured' ? 'status__prepend-icon-wrapper' : 'notification__favourite-icon-wrapper'}>
           <i
             className={`fa fa-fw fa-${
-              type === 'favourite' ? 'star star-icon' : 'retweet'
+              type === 'favourite' ? 'star star-icon' : (type === 'featured' ? 'thumb-tack' : 'retweet')
             } status__prepend-icon`}
           />
         </div>
diff --git a/app/javascript/flavours/glitch/containers/status_container.js b/app/javascript/flavours/glitch/containers/status_container.js
index c0b9b5800..f3db05ae6 100644
--- a/app/javascript/flavours/glitch/containers/status_container.js
+++ b/app/javascript/flavours/glitch/containers/status_container.js
@@ -38,7 +38,10 @@ const makeMapStateToProps = () => {
     let account = undefined;
     let prepend = undefined;
 
-    if (reblogStatus !== null && typeof reblogStatus === 'object') {
+    if (props.featured) {
+      account = status.get('account');
+      prepend = 'featured';
+    } else if (reblogStatus !== null && typeof reblogStatus === 'object') {
       account = status.get('account');
       status = reblogStatus;
       prepend = 'reblogged_by';
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 df8cb3733..fb90722f3 100644
--- a/app/javascript/flavours/glitch/features/account/components/action_bar.js
+++ b/app/javascript/flavours/glitch/features/account/components/action_bar.js
@@ -53,11 +53,11 @@ export default class ActionBar extends React.PureComponent {
     let extraInfo = '';
 
     menu.push({ text: intl.formatMessage(messages.mention, { name: account.get('username') }), action: this.props.onMention });
+
     if ('share' in navigator) {
       menu.push({ text: intl.formatMessage(messages.share, { name: account.get('username') }), action: this.handleShare });
     }
-    menu.push(null);
-    menu.push({ text: intl.formatMessage(messages.media), to: `/accounts/${account.get('id')}/media` });
+
     menu.push(null);
 
     if (account.get('id') === me) {
diff --git a/app/javascript/flavours/glitch/features/account_gallery/components/media_item.js b/app/javascript/flavours/glitch/features/account_gallery/components/media_item.js
index e52d3b0bb..c2cf48d7b 100644
--- a/app/javascript/flavours/glitch/features/account_gallery/components/media_item.js
+++ b/app/javascript/flavours/glitch/features/account_gallery/components/media_item.js
@@ -12,24 +12,26 @@ export default class MediaItem extends ImmutablePureComponent {
   render () {
     const { media } = this.props;
     const status = media.get('status');
+    const focusX = media.getIn(['meta', 'focus', 'x']);
+    const focusY = media.getIn(['meta', 'focus', 'y']);
+    const x = ((focusX /  2) + .5) * 100;
+    const y = ((focusY / -2) + .5) * 100;
+    const style = {};
 
-    let content, style;
+    let content;
 
     if (media.get('type') === 'gifv') {
       content = <span className='media-gallery__gifv__label'>GIF</span>;
     }
 
     if (!status.get('sensitive')) {
-      style = { backgroundImage: `url(${media.get('preview_url')})` };
+      style.backgroundImage    = `url(${media.get('preview_url')})`;
+      style.backgroundPosition = `${x}% ${y}%`;
     }
 
     return (
       <div className='account-gallery__item'>
-        <Permalink
-          to={`/statuses/${status.get('id')}`}
-          href={status.get('url')}
-          style={style}
-        >
+        <Permalink to={`/statuses/${status.get('id')}`} href={status.get('url')} style={style}>
           {content}
         </Permalink>
       </div>
diff --git a/app/javascript/flavours/glitch/features/account_gallery/index.js b/app/javascript/flavours/glitch/features/account_gallery/index.js
index df66b3b21..63ff98deb 100644
--- a/app/javascript/flavours/glitch/features/account_gallery/index.js
+++ b/app/javascript/flavours/glitch/features/account_gallery/index.js
@@ -11,7 +11,6 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
 import { getAccountGallery } from 'flavours/glitch/selectors';
 import MediaItem from './components/media_item';
 import HeaderContainer from 'flavours/glitch/features/account_timeline/containers/header_container';
-import { FormattedMessage } from 'react-intl';
 import { ScrollContainer } from 'react-router-scroll-4';
 import LoadMore from 'flavours/glitch/components/load_more';
 
@@ -89,10 +88,6 @@ export default class AccountGallery extends ImmutablePureComponent {
           <div className='scrollable' onScroll={this.handleScroll}>
             <HeaderContainer accountId={this.props.params.accountId} />
 
-            <div className='account-section-headline'>
-              <FormattedMessage id='account.media' defaultMessage='Media' />
-            </div>
-
             <div className='account-gallery__container'>
               {medias.map(media =>
                 (<MediaItem
diff --git a/app/javascript/flavours/glitch/features/account_timeline/components/header.js b/app/javascript/flavours/glitch/features/account_timeline/components/header.js
index 092034664..43fa68ce2 100644
--- a/app/javascript/flavours/glitch/features/account_timeline/components/header.js
+++ b/app/javascript/flavours/glitch/features/account_timeline/components/header.js
@@ -5,6 +5,8 @@ import InnerHeader from 'flavours/glitch/features/account/components/header';
 import ActionBar from 'flavours/glitch/features/account/components/action_bar';
 import MissingIndicator from 'flavours/glitch/components/missing_indicator';
 import ImmutablePureComponent from 'react-immutable-pure-component';
+import { FormattedMessage } from 'react-intl';
+import { NavLink } from 'react-router-dom';
 
 export default class Header extends ImmutablePureComponent {
 
@@ -18,6 +20,7 @@ export default class Header extends ImmutablePureComponent {
     onMute: PropTypes.func.isRequired,
     onBlockDomain: PropTypes.func.isRequired,
     onUnblockDomain: PropTypes.func.isRequired,
+    hideTabs: PropTypes.bool,
   };
 
   static contextTypes = {
@@ -65,7 +68,7 @@ export default class Header extends ImmutablePureComponent {
   }
 
   render () {
-    const { account } = this.props;
+    const { account, hideTabs } = this.props;
 
     if (account === null) {
       return <MissingIndicator />;
@@ -89,6 +92,14 @@ export default class Header extends ImmutablePureComponent {
           onBlockDomain={this.handleBlockDomain}
           onUnblockDomain={this.handleUnblockDomain}
         />
+
+        {!hideTabs && (
+          <div className='account__section-headline'>
+            <NavLink exact to={`/accounts/${account.get('id')}`}><FormattedMessage id='account.posts' defaultMessage='Toots' /></NavLink>
+            <NavLink exact to={`/accounts/${account.get('id')}/with_replies`}><FormattedMessage id='account.posts_with_replies' defaultMessage='Toots with replies' /></NavLink>
+            <NavLink exact to={`/accounts/${account.get('id')}/media`}><FormattedMessage id='account.media' defaultMessage='Media' /></NavLink>
+          </div>
+        )}
       </div>
     );
   }
diff --git a/app/javascript/flavours/glitch/features/account_timeline/index.js b/app/javascript/flavours/glitch/features/account_timeline/index.js
index 75dba5049..fbb16dff9 100644
--- a/app/javascript/flavours/glitch/features/account_timeline/index.js
+++ b/app/javascript/flavours/glitch/features/account_timeline/index.js
@@ -3,7 +3,7 @@ import { connect } from 'react-redux';
 import ImmutablePropTypes from 'react-immutable-proptypes';
 import PropTypes from 'prop-types';
 import { fetchAccount } from 'flavours/glitch/actions/accounts';
-import { refreshAccountTimeline, expandAccountTimeline } from 'flavours/glitch/actions/timelines';
+import { refreshAccountTimeline, refreshAccountFeaturedTimeline, expandAccountTimeline } from 'flavours/glitch/actions/timelines';
 import StatusList from '../../components/status_list';
 import LoadingIndicator from '../../components/loading_indicator';
 import Column from '../ui/components/column';
@@ -12,11 +12,16 @@ import ColumnBackButton from '../../components/column_back_button';
 import { List as ImmutableList } from 'immutable';
 import ImmutablePureComponent from 'react-immutable-pure-component';
 
-const mapStateToProps = (state, props) => ({
-  statusIds: state.getIn(['timelines', `account:${props.params.accountId}`, 'items'], ImmutableList()),
-  isLoading: state.getIn(['timelines', `account:${props.params.accountId}`, 'isLoading']),
-  hasMore: !!state.getIn(['timelines', `account:${props.params.accountId}`, 'next']),
-});
+const mapStateToProps = (state, { params: { accountId }, withReplies = false }) => {
+  const path = withReplies ? `${accountId}:with_replies` : accountId;
+
+  return {
+    statusIds: state.getIn(['timelines', `account:${path}`, 'items'], ImmutableList()),
+    featuredStatusIds: withReplies ? ImmutableList() : state.getIn(['timelines', `account:${accountId}:pinned`, 'items'], ImmutableList()),
+    isLoading: state.getIn(['timelines', `account:${path}`, 'isLoading']),
+    hasMore: !!state.getIn(['timelines', `account:${path}`, 'next']),
+  };
+};
 
 @connect(mapStateToProps)
 export default class AccountTimeline extends ImmutablePureComponent {
@@ -25,30 +30,36 @@ export default class AccountTimeline extends ImmutablePureComponent {
     params: PropTypes.object.isRequired,
     dispatch: PropTypes.func.isRequired,
     statusIds: ImmutablePropTypes.list,
+    featuredStatusIds: ImmutablePropTypes.list,
     isLoading: PropTypes.bool,
     hasMore: PropTypes.bool,
+    withReplies: PropTypes.bool,
   };
 
   componentWillMount () {
-    this.props.dispatch(fetchAccount(this.props.params.accountId));
-    this.props.dispatch(refreshAccountTimeline(this.props.params.accountId));
+    const { params: { accountId }, withReplies } = this.props;
+
+    this.props.dispatch(fetchAccount(accountId));
+    this.props.dispatch(refreshAccountFeaturedTimeline(accountId));
+    this.props.dispatch(refreshAccountTimeline(accountId, withReplies));
   }
 
   componentWillReceiveProps (nextProps) {
-    if (nextProps.params.accountId !== this.props.params.accountId && nextProps.params.accountId) {
+    if ((nextProps.params.accountId !== this.props.params.accountId && nextProps.params.accountId) || nextProps.withReplies !== this.props.withReplies) {
       this.props.dispatch(fetchAccount(nextProps.params.accountId));
-      this.props.dispatch(refreshAccountTimeline(nextProps.params.accountId));
+      this.props.dispatch(refreshAccountFeaturedTimeline(nextProps.params.accountId));
+      this.props.dispatch(refreshAccountTimeline(nextProps.params.accountId, nextProps.params.withReplies));
     }
   }
 
   handleScrollToBottom = () => {
     if (!this.props.isLoading && this.props.hasMore) {
-      this.props.dispatch(expandAccountTimeline(this.props.params.accountId));
+      this.props.dispatch(expandAccountTimeline(this.props.params.accountId, this.props.withReplies));
     }
   }
 
   render () {
-    const { statusIds, isLoading, hasMore } = this.props;
+    const { statusIds, featuredStatusIds, isLoading, hasMore } = this.props;
 
     if (!statusIds && isLoading) {
       return (
@@ -66,6 +77,7 @@ export default class AccountTimeline extends ImmutablePureComponent {
           prepend={<HeaderContainer accountId={this.props.params.accountId} />}
           scrollKey='account_timeline'
           statusIds={statusIds}
+          featuredStatusIds={featuredStatusIds}
           isLoading={isLoading}
           hasMore={hasMore}
           onScrollToBottom={this.handleScrollToBottom}
diff --git a/app/javascript/flavours/glitch/features/composer/index.js b/app/javascript/flavours/glitch/features/composer/index.js
index e50f3ec3f..792ed79a3 100644
--- a/app/javascript/flavours/glitch/features/composer/index.js
+++ b/app/javascript/flavours/glitch/features/composer/index.js
@@ -73,6 +73,7 @@ function mapStateToProps (state) {
     suggestionToken: state.getIn(['compose', 'suggestion_token']),
     suggestions: state.getIn(['compose', 'suggestions']),
     text: state.getIn(['compose', 'text']),
+    anyMedia: state.getIn(['compose', 'media_attachments']).size > 0,
   };
 };
 
@@ -272,6 +273,7 @@ class Composer extends React.Component {
       acceptContentTypes,
       advancedOptions,
       amUnlocked,
+      anyMedia,
       intl,
       isSubmitting,
       isUploading,
@@ -305,6 +307,8 @@ class Composer extends React.Component {
       text,
     } = this.props;
 
+    let disabledButton = isSubmitting || isUploading || (!!text.length && !text.trim().length && !anyMedia);
+
     return (
       <div className='composer'>
         <ComposerSpoiler
@@ -374,7 +378,7 @@ class Composer extends React.Component {
         />
         <ComposerPublisher
           countText={`${spoilerText}${countableText(text)}${advancedOptions && advancedOptions.get('do_not_federate') ? ' 👁️' : ''}`}
-          disabled={isSubmitting || isUploading || !!text.length && !text.trim().length}
+          disabled={disabledButton}
           intl={intl}
           onSecondarySubmit={handleSecondarySubmit}
           onSubmit={handleSubmit}
@@ -436,6 +440,7 @@ Composer.propTypes = {
   onUndoUpload: PropTypes.func,
   onUnmount: PropTypes.func,
   onUpload: PropTypes.func,
+  anyMedia: PropTypes.bool,
 };
 
 //  Connecting and export.
diff --git a/app/javascript/flavours/glitch/features/followers/index.js b/app/javascript/flavours/glitch/features/followers/index.js
index f0ef29ff6..c42e0386c 100644
--- a/app/javascript/flavours/glitch/features/followers/index.js
+++ b/app/javascript/flavours/glitch/features/followers/index.js
@@ -80,7 +80,7 @@ export default class Followers extends ImmutablePureComponent {
         <ScrollContainer scrollKey='followers'>
           <div className='scrollable' onScroll={this.handleScroll}>
             <div className='followers'>
-              <HeaderContainer accountId={this.props.params.accountId} />
+              <HeaderContainer accountId={this.props.params.accountId} hideTabs />
               {accountIds.map(id => <AccountContainer key={id} id={id} withNote={false} />)}
               {loadMore}
             </div>
diff --git a/app/javascript/flavours/glitch/features/following/index.js b/app/javascript/flavours/glitch/features/following/index.js
index f30f7b0d9..c05742d4f 100644
--- a/app/javascript/flavours/glitch/features/following/index.js
+++ b/app/javascript/flavours/glitch/features/following/index.js
@@ -80,7 +80,7 @@ export default class Following extends ImmutablePureComponent {
         <ScrollContainer scrollKey='following'>
           <div className='scrollable' onScroll={this.handleScroll}>
             <div className='following'>
-              <HeaderContainer accountId={this.props.params.accountId} />
+              <HeaderContainer accountId={this.props.params.accountId} hideTabs />
               {accountIds.map(id => <AccountContainer key={id} id={id} withNote={false} />)}
               {loadMore}
             </div>
diff --git a/app/javascript/flavours/glitch/features/getting_started/index.js b/app/javascript/flavours/glitch/features/getting_started/index.js
index 0077f193b..bf9fe118a 100644
--- a/app/javascript/flavours/glitch/features/getting_started/index.js
+++ b/app/javascript/flavours/glitch/features/getting_started/index.js
@@ -9,6 +9,8 @@ import PropTypes from 'prop-types';
 import ImmutablePropTypes from 'react-immutable-proptypes';
 import ImmutablePureComponent from 'react-immutable-pure-component';
 import { me } from 'flavours/glitch/util/initial_state';
+import { fetchFollowRequests } from 'flavours/glitch/actions/accounts';
+import { List as ImmutableList } from 'immutable';
 import { createSelector } from 'reselect';
 import { fetchLists } from 'flavours/glitch/actions/lists';
 
@@ -45,13 +47,31 @@ const makeMapStateToProps = () => {
     lists: getOrderedLists(state),
     myAccount: state.getIn(['accounts', me]),
     columns: state.getIn(['settings', 'columns']),
+    unreadFollowRequests: state.getIn(['user_lists', 'follow_requests', 'items'], ImmutableList()).size,
+    unreadNotifications: state.getIn(['notifications', 'unread']),
   });
 
   return mapStateToProps;
 };
 
+const mapDispatchToProps = dispatch => ({
+  fetchFollowRequests: () => dispatch(fetchFollowRequests()),
+  fetchLists: () => dispatch(fetchLists()),
+  openSettings: () => dispatch(openModal('SETTINGS', {})),
+});
+
+const badgeDisplay = (number, limit) => {
+  if (number === 0) {
+    return undefined;
+  } else if (limit && number >= limit) {
+    return `${limit}+`;
+  } else {
+    return number;
+  }
+};
+
+@connect(makeMapStateToProps, mapDispatchToProps)
 @injectIntl
-@connect(makeMapStateToProps)
 export default class GettingStarted extends ImmutablePureComponent {
 
   static propTypes = {
@@ -59,25 +79,28 @@ export default class GettingStarted extends ImmutablePureComponent {
     myAccount: ImmutablePropTypes.map.isRequired,
     columns: ImmutablePropTypes.list,
     multiColumn: PropTypes.bool,
-    dispatch: PropTypes.func.isRequired,
+    fetchFollowRequests: PropTypes.func.isRequired,
+    unreadFollowRequests: PropTypes.number,
+    unreadNotifications: PropTypes.number,
     lists: ImmutablePropTypes.list,
+    fetchLists: PropTypes.func.isRequired,
+    openSettings: PropTypes.func.isRequired,
   };
 
-  openSettings = () => {
-    this.props.dispatch(openModal('SETTINGS', {}));
+  componentWillMount () {
+    this.props.fetchLists();
   }
 
-  openOnboardingModal = (e) => {
-    e.preventDefault();
-    this.props.dispatch(openModal('ONBOARDING'));
-  }
+  componentDidMount () {
+    const { myAccount, fetchFollowRequests } = this.props;
 
-  componentWillMount () {
-    this.props.dispatch(fetchLists());
+    if (myAccount.get('locked')) {
+      fetchFollowRequests();
+    }
   }
 
   render () {
-    const { intl, myAccount, columns, multiColumn, lists } = this.props;
+    const { intl, myAccount, columns, multiColumn, unreadFollowRequests, unreadNotifications, lists, openSettings } = this.props;
 
     const navItems = [];
     let listItems = [];
@@ -88,7 +111,7 @@ export default class GettingStarted extends ImmutablePureComponent {
       }
 
       if (!columns.find(item => item.get('id') === 'NOTIFICATIONS')) {
-        navItems.push(<ColumnLink key='1' icon='bell' text={intl.formatMessage(messages.notifications)} to='/notifications' />);
+        navItems.push(<ColumnLink key='1' icon='bell' text={intl.formatMessage(messages.notifications)} badge={badgeDisplay(unreadNotifications)} to='/notifications' />);
       }
 
       if (!columns.find(item => item.get('id') === 'COMMUNITY')) {
@@ -105,7 +128,7 @@ export default class GettingStarted extends ImmutablePureComponent {
     }
 
     if (myAccount.get('locked')) {
-      navItems.push(<ColumnLink key='5' icon='users' text={intl.formatMessage(messages.follow_requests)} to='/follow_requests' />);
+      navItems.push(<ColumnLink key='5' icon='users' text={intl.formatMessage(messages.follow_requests)} badge={badgeDisplay(unreadFollowRequests, 40)} to='/follow_requests' />);
     }
 
     navItems.push(<ColumnLink key='6' icon='ellipsis-h' text={intl.formatMessage(messages.misc)} to='/getting-started-misc' />);
@@ -129,7 +152,7 @@ export default class GettingStarted extends ImmutablePureComponent {
             {listItems}
             <ColumnSubheading text={intl.formatMessage(messages.settings_subheading)} />
             <ColumnLink icon='cog' text={intl.formatMessage(messages.preferences)} href='/settings/preferences' />
-            <ColumnLink icon='cogs' text={intl.formatMessage(messages.settings)} onClick={this.openSettings} />
+            <ColumnLink icon='cogs' text={intl.formatMessage(messages.settings)} onClick={openSettings} />
             <ColumnLink icon='sign-out' text={intl.formatMessage(messages.sign_out)} href='/auth/sign_out' method='delete' />
           </div>
 
diff --git a/app/javascript/flavours/glitch/features/report/components/status_check_box.js b/app/javascript/flavours/glitch/features/report/components/status_check_box.js
index cc9232201..d72a0fd07 100644
--- a/app/javascript/flavours/glitch/features/report/components/status_check_box.js
+++ b/app/javascript/flavours/glitch/features/report/components/status_check_box.js
@@ -2,6 +2,10 @@ import React from 'react';
 import PropTypes from 'prop-types';
 import ImmutablePropTypes from 'react-immutable-proptypes';
 import Toggle from 'react-toggle';
+import noop from 'lodash/noop';
+import StatusContent from 'flavours/glitch/components/status_content';
+import { MediaGallery, Video } from 'flavours/glitch/util/async-components';
+import Bundle from 'flavours/glitch/features/ui/components/bundle';
 
 export default class StatusCheckBox extends React.PureComponent {
 
@@ -14,18 +18,50 @@ export default class StatusCheckBox extends React.PureComponent {
 
   render () {
     const { status, checked, onToggle, disabled } = this.props;
-    const content = { __html: status.get('contentHtml') };
+    let media = null;
 
     if (status.get('reblog')) {
       return null;
     }
 
+    if (status.get('media_attachments').size > 0) {
+      if (status.get('media_attachments').some(item => item.get('type') === 'unknown')) {
+
+      } else if (status.getIn(['media_attachments', 0, 'type']) === 'video') {
+        const video = status.getIn(['media_attachments', 0]);
+
+        media = (
+          <Bundle fetchComponent={Video} loading={this.renderLoadingVideoPlayer} >
+            {Component => (
+              <Component
+                preview={video.get('preview_url')}
+                src={video.get('url')}
+                width={239}
+                height={110}
+                inline
+                sensitive={status.get('sensitive')}
+                onOpenVideo={noop}
+              />
+            )}
+          </Bundle>
+        );
+      } else {
+        media = (
+          <Bundle fetchComponent={MediaGallery} loading={this.renderLoadingMediaGallery} >
+            {Component => <Component media={status.get('media_attachments')} sensitive={status.get('sensitive')} height={110} onOpenMedia={noop} />}
+          </Bundle>
+        );
+      }
+    }
+
     return (
       <div className='status-check-box'>
-        <div
-          className='status__content'
-          dangerouslySetInnerHTML={content}
-        />
+        <div className='status-check-box__status'>
+          <StatusContent
+            status={status}
+            media={media}
+          />
+        </div>
 
         <div className='status-check-box-toggle'>
           <Toggle checked={checked} onChange={onToggle} disabled={disabled} />
diff --git a/app/javascript/flavours/glitch/features/ui/components/column_link.js b/app/javascript/flavours/glitch/features/ui/components/column_link.js
index b845d1895..b058aa963 100644
--- a/app/javascript/flavours/glitch/features/ui/components/column_link.js
+++ b/app/javascript/flavours/glitch/features/ui/components/column_link.js
@@ -2,12 +2,15 @@ import React from 'react';
 import PropTypes from 'prop-types';
 import { Link } from 'react-router-dom';
 
-const ColumnLink = ({ icon, text, to, onClick, href, method }) => {
+const ColumnLink = ({ icon, text, to, onClick, href, method, badge }) => {
+  const badgeElement = typeof badge !== 'undefined' ? <span className='column-link__badge'>{badge}</span> : null;
+
   if (href) {
     return (
       <a href={href} className='column-link' data-method={method}>
         <i className={`fa fa-fw fa-${icon} column-link__icon`} />
         {text}
+        {badgeElement}
       </a>
     );
   } else if (to) {
@@ -15,6 +18,7 @@ const ColumnLink = ({ icon, text, to, onClick, href, method }) => {
       <Link to={to} className='column-link'>
         <i className={`fa fa-fw fa-${icon} column-link__icon`} />
         {text}
+        {badgeElement}
       </Link>
     );
   } else {
@@ -22,6 +26,7 @@ const ColumnLink = ({ icon, text, to, onClick, href, method }) => {
       <a onClick={onClick} className='column-link' role='button' tabIndex='0' data-method={method}>
         <i className={`fa fa-fw fa-${icon} column-link__icon`} />
         {text}
+        {badgeElement}
       </a>
     );
   }
@@ -34,6 +39,7 @@ ColumnLink.propTypes = {
   onClick: PropTypes.func,
   href: PropTypes.string,
   method: PropTypes.string,
+  badge: PropTypes.node,
 };
 
 export default ColumnLink;
diff --git a/app/javascript/flavours/glitch/features/ui/components/report_modal.js b/app/javascript/flavours/glitch/features/ui/components/report_modal.js
index b4dc1e3d6..b5fc33d03 100644
--- a/app/javascript/flavours/glitch/features/ui/components/report_modal.js
+++ b/app/javascript/flavours/glitch/features/ui/components/report_modal.js
@@ -1,6 +1,6 @@
 import React from 'react';
 import { connect } from 'react-redux';
-import { changeReportComment, submitReport } from 'flavours/glitch/actions/reports';
+import { changeReportComment, changeReportForward, submitReport } from 'flavours/glitch/actions/reports';
 import { refreshAccountTimeline } from 'flavours/glitch/actions/timelines';
 import PropTypes from 'prop-types';
 import ImmutablePropTypes from 'react-immutable-proptypes';
@@ -10,8 +10,11 @@ import StatusCheckBox from 'flavours/glitch/features/report/containers/status_ch
 import { OrderedSet } from 'immutable';
 import ImmutablePureComponent from 'react-immutable-pure-component';
 import Button from 'flavours/glitch/components/button';
+import Toggle from 'react-toggle';
+import IconButton from '../../../components/icon_button';
 
 const messages = defineMessages({
+  close: { id: 'lightbox.close', defaultMessage: 'Close' },
   placeholder: { id: 'report.placeholder', defaultMessage: 'Additional comments' },
   submit: { id: 'report.submit', defaultMessage: 'Submit' },
 });
@@ -26,6 +29,7 @@ const makeMapStateToProps = () => {
       isSubmitting: state.getIn(['reports', 'new', 'isSubmitting']),
       account: getAccount(state, accountId),
       comment: state.getIn(['reports', 'new', 'comment']),
+      forward: state.getIn(['reports', 'new', 'forward']),
       statusIds: OrderedSet(state.getIn(['timelines', `account:${accountId}`, 'items'])).union(state.getIn(['reports', 'new', 'status_ids'])),
     };
   };
@@ -42,14 +46,19 @@ export default class ReportModal extends ImmutablePureComponent {
     account: ImmutablePropTypes.map,
     statusIds: ImmutablePropTypes.orderedSet.isRequired,
     comment: PropTypes.string.isRequired,
+    forward: PropTypes.bool,
     dispatch: PropTypes.func.isRequired,
     intl: PropTypes.object.isRequired,
   };
 
-  handleCommentChange = (e) => {
+  handleCommentChange = e => {
     this.props.dispatch(changeReportComment(e.target.value));
   }
 
+  handleForwardChange = e => {
+    this.props.dispatch(changeReportForward(e.target.checked));
+  }
+
   handleSubmit = () => {
     this.props.dispatch(submitReport());
   }
@@ -65,26 +74,25 @@ export default class ReportModal extends ImmutablePureComponent {
   }
 
   render () {
-    const { account, comment, intl, statusIds, isSubmitting } = this.props;
+    const { account, comment, intl, statusIds, isSubmitting, forward, onClose } = this.props;
 
     if (!account) {
       return null;
     }
 
+    const domain = account.get('acct').split('@')[1];
+
     return (
       <div className='modal-root__modal report-modal'>
         <div className='report-modal__target'>
+          <IconButton className='media-modal__close' title={intl.formatMessage(messages.close)} icon='times' onClick={onClose} size={16} />
           <FormattedMessage id='report.target' defaultMessage='Report {target}' values={{ target: <strong>{account.get('acct')}</strong> }} />
         </div>
 
         <div className='report-modal__container'>
-          <div className='report-modal__statuses'>
-            <div>
-              {statusIds.map(statusId => <StatusCheckBox id={statusId} key={statusId} disabled={isSubmitting} />)}
-            </div>
-          </div>
-
           <div className='report-modal__comment'>
+            <p><FormattedMessage id='report.hint' defaultMessage='The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:' /></p>
+
             <textarea
               className='setting-text light'
               placeholder={intl.formatMessage(messages.placeholder)}
@@ -92,11 +100,26 @@ export default class ReportModal extends ImmutablePureComponent {
               onChange={this.handleCommentChange}
               disabled={isSubmitting}
             />
+
+            {domain && (
+              <div>
+                <p><FormattedMessage id='report.forward_hint' defaultMessage='The account is from another server. Send an anonymized copy of the report there as well?' /></p>
+
+                <div className='setting-toggle'>
+                  <Toggle id='report-forward' checked={forward} disabled={isSubmitting} onChange={this.handleForwardChange} />
+                  <label htmlFor='report-forward' className='setting-toggle__label'><FormattedMessage id='report.forward' defaultMessage='Forward to {target}' values={{ target: domain }} /></label>
+                </div>
+              </div>
+            )}
+
+            <Button disabled={isSubmitting} text={intl.formatMessage(messages.submit)} onClick={this.handleSubmit} />
           </div>
-        </div>
 
-        <div className='report-modal__action-bar'>
-          <Button disabled={isSubmitting} text={intl.formatMessage(messages.submit)} onClick={this.handleSubmit} />
+          <div className='report-modal__statuses'>
+            <div>
+              {statusIds.map(statusId => <StatusCheckBox id={statusId} key={statusId} disabled={isSubmitting} />)}
+            </div>
+          </div>
         </div>
       </div>
     );
diff --git a/app/javascript/flavours/glitch/features/ui/index.js b/app/javascript/flavours/glitch/features/ui/index.js
index fae705deb..0b031a7f0 100644
--- a/app/javascript/flavours/glitch/features/ui/index.js
+++ b/app/javascript/flavours/glitch/features/ui/index.js
@@ -431,6 +431,7 @@ export default class UI extends React.Component {
               <WrappedRoute path='/statuses/:statusId/favourites' component={Favourites} content={children} />
 
               <WrappedRoute path='/accounts/:accountId' exact component={AccountTimeline} content={children} />
+              <WrappedRoute path='/accounts/:accountId/with_replies' component={AccountTimeline} content={children} componentParams={{ withReplies: true }} />
               <WrappedRoute path='/accounts/:accountId/followers' component={Followers} content={children} />
               <WrappedRoute path='/accounts/:accountId/following' component={Following} content={children} />
               <WrappedRoute path='/accounts/:accountId/media' component={AccountGallery} content={children} />
diff --git a/app/javascript/flavours/glitch/reducers/compose.js b/app/javascript/flavours/glitch/reducers/compose.js
index 722670cf1..8973c7713 100644
--- a/app/javascript/flavours/glitch/reducers/compose.js
+++ b/app/javascript/flavours/glitch/reducers/compose.js
@@ -158,7 +158,6 @@ function appendMedia(state, media) {
     map.update('media_attachments', list => list.push(media));
     map.set('is_uploading', false);
     map.set('resetFileKey', Math.floor((Math.random() * 0x10000)));
-    map.update('text', oldText => `${oldText.trim()} ${media.get('text_url')}`);
     map.set('focusDate', new Date());
     map.set('idempotencyKey', uuid());
 
@@ -169,12 +168,10 @@ function appendMedia(state, media) {
 };
 
 function removeMedia(state, mediaId) {
-  const media    = state.get('media_attachments').find(item => item.get('id') === mediaId);
   const prevSize = state.get('media_attachments').size;
 
   return state.withMutations(map => {
     map.update('media_attachments', list => list.filterNot(item => item.get('id') === mediaId));
-    map.update('text', text => text.replace(media.get('text_url'), '').trim());
     map.set('idempotencyKey', uuid());
 
     if (prevSize === 1) {
diff --git a/app/javascript/flavours/glitch/reducers/reports.js b/app/javascript/flavours/glitch/reducers/reports.js
index c18fbcdc6..fdcfb14a0 100644
--- a/app/javascript/flavours/glitch/reducers/reports.js
+++ b/app/javascript/flavours/glitch/reducers/reports.js
@@ -6,6 +6,7 @@ import {
   REPORT_CANCEL,
   REPORT_STATUS_TOGGLE,
   REPORT_COMMENT_CHANGE,
+  REPORT_FORWARD_CHANGE,
 } from 'flavours/glitch/actions/reports';
 import { Map as ImmutableMap, Set as ImmutableSet } from 'immutable';
 
@@ -15,6 +16,7 @@ const initialState = ImmutableMap({
     account_id: null,
     status_ids: ImmutableSet(),
     comment: '',
+    forward: false,
   }),
 });
 
@@ -42,6 +44,8 @@ export default function reports(state = initialState, action) {
     });
   case REPORT_COMMENT_CHANGE:
     return state.setIn(['new', 'comment'], action.comment);
+  case REPORT_FORWARD_CHANGE:
+    return state.setIn(['new', 'forward'], action.forward);
   case REPORT_SUBMIT_REQUEST:
     return state.setIn(['new', 'isSubmitting'], true);
   case REPORT_SUBMIT_FAIL:
diff --git a/app/javascript/flavours/glitch/styles/components/accounts.scss b/app/javascript/flavours/glitch/styles/components/accounts.scss
index 2bc894d25..a86120e08 100644
--- a/app/javascript/flavours/glitch/styles/components/accounts.scss
+++ b/app/javascript/flavours/glitch/styles/components/accounts.scss
@@ -380,64 +380,96 @@
 }
 
 .account-gallery__container {
-  margin: -2px;
-  padding: 4px;
   display: flex;
+  justify-content: center;
   flex-wrap: wrap;
+  padding: 2px;
 }
 
 .account-gallery__item {
-  flex: 1 1 auto;
-  width: calc(100% / 3 - 4px);
-  height: 95px;
-  margin: 2px;
+  flex-grow: 1;
+  width: 50%;
+  overflow: hidden;
+  position: relative;
+
+  &::before {
+    content: "";
+    display: block;
+    padding-top: 100%;
+  }
 
   a {
     display: block;
-    width: 100%;
-    height: 100%;
+    width: calc(100% - 4px);
+    height: calc(100% - 4px);
+    margin: 2px;
+    top: 0;
+    left: 0;
     background-color: $base-overlay-background;
     background-size: cover;
     background-position: center;
-    position: relative;
+    position: absolute;
     color: inherit;
     text-decoration: none;
+    border-radius: 4px;
 
     &:hover,
     &:active,
     &:focus {
       outline: 0;
+
+      &::before {
+        content: "";
+        display: block;
+        width: 100%;
+        height: 100%;
+        background: rgba($base-overlay-background, 0.3);
+        border-radius: 4px;
+      }
     }
   }
 }
 
-.account-section-headline {
-  color: $ui-base-lighter-color;
-  background: lighten($ui-base-color, 2%);
-  border-bottom: 1px solid lighten($ui-base-color, 4%);
-  padding: 15px 10px;
-  font-size: 14px;
-  font-weight: 500;
-  position: relative;
+.account__section-headline {
+  background: darken($ui-base-color, 4%);
+  border-bottom: 1px solid lighten($ui-base-color, 8%);
   cursor: default;
+  display: flex;
 
-  &::before,
-  &::after {
+  a {
     display: block;
-    content: "";
-    position: absolute;
-    bottom: 0;
-    left: 18px;
-    width: 0;
-    height: 0;
-    border-style: solid;
-    border-width: 0 10px 10px;
-    border-color: transparent transparent lighten($ui-base-color, 4%);
-  }
+    flex: 1 1 auto;
+    color: $ui-primary-color;
+    padding: 15px 0;
+    font-size: 14px;
+    font-weight: 500;
+    text-align: center;
+    text-decoration: none;
+    position: relative;
 
-  &::after {
-    bottom: -1px;
-    border-color: transparent transparent $ui-base-color;
+    &.active {
+      color: $ui-secondary-color;
+
+      &::before,
+      &::after {
+        display: block;
+        content: "";
+        position: absolute;
+        bottom: 0;
+        left: 50%;
+        width: 0;
+        height: 0;
+        transform: translateX(-50%);
+        border-style: solid;
+        border-width: 0 10px 10px;
+        border-color: transparent transparent lighten($ui-base-color, 8%);
+      }
+
+      &::after {
+        bottom: -1px;
+        border-color: transparent transparent $ui-base-color;
+      }
+    }
   }
 }
 
diff --git a/app/javascript/flavours/glitch/styles/components/index.scss b/app/javascript/flavours/glitch/styles/components/index.scss
index 10cad345e..3d16c856d 100644
--- a/app/javascript/flavours/glitch/styles/components/index.scss
+++ b/app/javascript/flavours/glitch/styles/components/index.scss
@@ -753,6 +753,17 @@
   }
 }
 
+.column-link__badge {
+  display: inline-block;
+  border-radius: 4px;
+  font-size: 12px;
+  line-height: 19px;
+  font-weight: 500;
+  background: $ui-base-color;
+  padding: 4px 8px;
+  margin: -6px 10px;
+}
+
 .keyboard-shortcuts {
   padding: 8px 0 0;
   overflow: hidden;
diff --git a/app/javascript/flavours/glitch/styles/components/media.scss b/app/javascript/flavours/glitch/styles/components/media.scss
index 4dd748227..d7407cdaf 100644
--- a/app/javascript/flavours/glitch/styles/components/media.scss
+++ b/app/javascript/flavours/glitch/styles/components/media.scss
@@ -102,6 +102,7 @@
   &.standalone {
     .media-gallery__item-gifv-thumbnail {
       transform: none;
+      top: 0;
     }
   }
 }
diff --git a/app/javascript/flavours/glitch/styles/components/modal.scss b/app/javascript/flavours/glitch/styles/components/modal.scss
index 1ac399fa1..d424b1eda 100644
--- a/app/javascript/flavours/glitch/styles/components/modal.scss
+++ b/app/javascript/flavours/glitch/styles/components/modal.scss
@@ -476,8 +476,7 @@
 .boost-modal__action-bar,
 .favourite-modal__action-bar,
 .confirmation-modal__action-bar,
-.mute-modal__action-bar,
-.report-modal__action-bar {
+.mute-modal__action-bar {
   display: flex;
   justify-content: space-between;
   background: $ui-secondary-color;
@@ -523,21 +522,103 @@
   vertical-align: middle;
 }
 
+.report-modal {
+  width: 90vw;
+  max-width: 700px;
+}
+
+.report-modal__container {
+  display: flex;
+  border-top: 1px solid $ui-secondary-color;
+
+  @media screen and (max-width: 480px) {
+    flex-wrap: wrap;
+    overflow-y: auto;
+  }
+}
+
 .report-modal__statuses,
 .report-modal__comment {
-  padding: 10px;
+  box-sizing: border-box;
+  width: 50%;
+
+  @media screen and (max-width: 480px) {
+    width: 100%;
+  }
 }
 
 .report-modal__statuses {
+  flex: 1 1 auto;
   min-height: 20vh;
   max-height: 40vh;
   overflow-y: auto;
   overflow-x: hidden;
+
+  @media screen and (max-width: 480px) {
+    max-height: 10vh;
+  }
 }
 
 .report-modal__comment {
+  padding: 20px;
+  border-right: 1px solid $ui-secondary-color;
+  max-width: 320px;
+
+  p {
+    font-size: 14px;
+    line-height: 20px;
+    margin-bottom: 20px;
+  }
+
   .setting-text {
-    margin-top: 10px;
+    display: block;
+    box-sizing: border-box;
+    width: 100%;
+    margin: 0;
+    color: $ui-base-color;
+    background: $white;
+    padding: 10px;
+    font-family: inherit;
+    font-size: 14px;
+    resize: vertical;
+    border: 0;
+    outline: 0;
+    border-radius: 4px;
+    border: 1px solid $ui-secondary-color;
+    margin-bottom: 20px;
+
+    &:focus {
+      border: 1px solid darken($ui-secondary-color, 8%);
+    }
+  }
+
+  .setting-toggle {
+    margin-top: 20px;
+    margin-bottom: 24px;
+
+    &__label {
+      color: $ui-base-color;
+      font-size: 14px;
+    }
+  }
+
+  @media screen and (max-width: 480px) {
+    padding: 10px;
+    max-width: 100%;
+    order: 2;
+
+    .setting-toggle {
+      margin-bottom: 4px;
+    }
+  }
+}
+
+.report-modal__target {
+  padding: 20px;
+
+  .media-modal__close {
+    top: 19px;
+    right: 15px;
   }
 }
 
diff --git a/app/javascript/flavours/glitch/styles/components/status.scss b/app/javascript/flavours/glitch/styles/components/status.scss
index 1c5e294f9..15727155b 100644
--- a/app/javascript/flavours/glitch/styles/components/status.scss
+++ b/app/javascript/flavours/glitch/styles/components/status.scss
@@ -8,7 +8,6 @@
   padding: 0 12px;
   font-size: 15px;
   line-height: 20px;
-  color: $primary-text-color;
   word-wrap: break-word;
   font-weight: 400;
   overflow: visible;
@@ -322,18 +321,26 @@
   border-bottom: 1px solid $ui-secondary-color;
   display: flex;
 
-  .status__content {
-    flex: 1 1 auto;
-    padding: 10px;
-    overflow: hidden;
-    text-overflow: ellipsis;
-    white-space: nowrap;
+  .status-check-box__status {
+    margin: 10px 0 10px 10px;
+    flex: 1;
+
+    .media-gallery {
+      max-width: 250px;
+    }
 
     .status__content {
-      color: #3a3a3a;
-      a {
-        color: #005aa9;
-      }
+      padding: 0;
+      white-space: normal;
+    }
+
+    .video-player {
+      margin-top: 8px;
+      max-width: 250px;
+    }
+
+    .media-gallery__item-thumbnail {
+      cursor: default;
     }
   }
 }
diff --git a/app/javascript/flavours/glitch/util/react_router_helpers.js b/app/javascript/flavours/glitch/util/react_router_helpers.js
index 1dba5e9bb..e36c512f3 100644
--- a/app/javascript/flavours/glitch/util/react_router_helpers.js
+++ b/app/javascript/flavours/glitch/util/react_router_helpers.js
@@ -35,14 +35,19 @@ export class WrappedRoute extends React.Component {
     component: PropTypes.func.isRequired,
     content: PropTypes.node,
     multiColumn: PropTypes.bool,
+    componentParams: PropTypes.object,
   }
 
+  static defaultProps = {
+    componentParams: {},
+  };
+
   renderComponent = ({ match }) => {
-    const { component, content, multiColumn } = this.props;
+    const { component, content, multiColumn, componentParams } = this.props;
 
     return (
       <BundleContainer fetchComponent={component} loading={this.renderLoading} error={this.renderError}>
-        {Component => <Component params={match.params} multiColumn={multiColumn}>{content}</Component>}
+        {Component => <Component params={match.params} multiColumn={multiColumn} {...componentParams}>{content}</Component>}
       </BundleContainer>
     );
   }