Improve modal flow and back button handling (#16499)
* Refactor shouldUpdateScroll passing So far, shouldUpdateScroll has been manually passed down from the very top of the React component hierarchy even though it is a static function common to all ScrollContainer instances, so replaced that with a custom class extending ScrollContainer. * Generalize “press back to close modal” to any modal and to public pages * Fix boost confirmation modal closing media modal
This commit is contained in:
		@@ -1,6 +1,7 @@
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import PropTypes from 'prop-types';
 | 
			
		||||
import 'wicg-inert';
 | 
			
		||||
import { createBrowserHistory } from 'history';
 | 
			
		||||
import { multiply } from 'color-blend';
 | 
			
		||||
 | 
			
		||||
export default class ModalRoot extends React.PureComponent {
 | 
			
		||||
@@ -48,6 +49,7 @@ export default class ModalRoot extends React.PureComponent {
 | 
			
		||||
  componentDidMount () {
 | 
			
		||||
    window.addEventListener('keyup', this.handleKeyUp, false);
 | 
			
		||||
    window.addEventListener('keydown', this.handleKeyDown, false);
 | 
			
		||||
    this.history = this.context.router ? this.context.router.history : createBrowserHistory();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  componentWillReceiveProps (nextProps) {
 | 
			
		||||
@@ -69,6 +71,14 @@ export default class ModalRoot extends React.PureComponent {
 | 
			
		||||
        this.activeElement.focus({ preventScroll: true });
 | 
			
		||||
        this.activeElement = null;
 | 
			
		||||
      }).catch(console.error);
 | 
			
		||||
 | 
			
		||||
      this._handleModalClose();
 | 
			
		||||
    }
 | 
			
		||||
    if (this.props.children && !prevProps.children) {
 | 
			
		||||
      this._handleModalOpen();
 | 
			
		||||
    }
 | 
			
		||||
    if (this.props.children) {
 | 
			
		||||
      this._ensureHistoryBuffer();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -77,6 +87,32 @@ export default class ModalRoot extends React.PureComponent {
 | 
			
		||||
    window.removeEventListener('keydown', this.handleKeyDown);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  _handleModalOpen () {
 | 
			
		||||
    this._modalHistoryKey = Date.now();
 | 
			
		||||
    this.unlistenHistory = this.history.listen((_, action) => {
 | 
			
		||||
      if (action === 'POP') {
 | 
			
		||||
        this.props.onClose();
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  _handleModalClose () {
 | 
			
		||||
    if (this.unlistenHistory) {
 | 
			
		||||
      this.unlistenHistory();
 | 
			
		||||
    }
 | 
			
		||||
    const { state } = this.history.location;
 | 
			
		||||
    if (state && state.mastodonModalKey === this._modalHistoryKey) {
 | 
			
		||||
      this.history.goBack();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  _ensureHistoryBuffer () {
 | 
			
		||||
    const { pathname, state } = this.history.location;
 | 
			
		||||
    if (!state || state.mastodonModalKey !== this._modalHistoryKey) {
 | 
			
		||||
      this.history.push(pathname, { ...state, mastodonModalKey: this._modalHistoryKey });
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  getSiblings = () => {
 | 
			
		||||
    return Array(...this.node.parentElement.childNodes).filter(node => node !== this.node);
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
import React, { PureComponent } from 'react';
 | 
			
		||||
import { ScrollContainer } from 'react-router-scroll-4';
 | 
			
		||||
import ScrollContainer from 'mastodon/containers/scroll_container';
 | 
			
		||||
import PropTypes from 'prop-types';
 | 
			
		||||
import IntersectionObserverArticleContainer from '../containers/intersection_observer_article_container';
 | 
			
		||||
import LoadMore from './load_more';
 | 
			
		||||
@@ -34,7 +34,6 @@ class ScrollableList extends PureComponent {
 | 
			
		||||
    onScrollToTop: PropTypes.func,
 | 
			
		||||
    onScroll: PropTypes.func,
 | 
			
		||||
    trackScroll: PropTypes.bool,
 | 
			
		||||
    shouldUpdateScroll: PropTypes.func,
 | 
			
		||||
    isLoading: PropTypes.bool,
 | 
			
		||||
    showLoading: PropTypes.bool,
 | 
			
		||||
    hasMore: PropTypes.bool,
 | 
			
		||||
@@ -290,7 +289,7 @@ class ScrollableList extends PureComponent {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  render () {
 | 
			
		||||
    const { children, scrollKey, trackScroll, shouldUpdateScroll, showLoading, isLoading, hasMore, numPending, prepend, alwaysPrepend, append, emptyMessage, onLoadMore } = this.props;
 | 
			
		||||
    const { children, scrollKey, trackScroll, showLoading, isLoading, hasMore, numPending, prepend, alwaysPrepend, append, emptyMessage, onLoadMore } = this.props;
 | 
			
		||||
    const { fullscreen } = this.state;
 | 
			
		||||
    const childrenCount = React.Children.count(children);
 | 
			
		||||
 | 
			
		||||
@@ -356,7 +355,7 @@ class ScrollableList extends PureComponent {
 | 
			
		||||
 | 
			
		||||
    if (trackScroll) {
 | 
			
		||||
      return (
 | 
			
		||||
        <ScrollContainer scrollKey={scrollKey} shouldUpdateScroll={shouldUpdateScroll}>
 | 
			
		||||
        <ScrollContainer scrollKey={scrollKey}>
 | 
			
		||||
          {scrollableArea}
 | 
			
		||||
        </ScrollContainer>
 | 
			
		||||
      );
 | 
			
		||||
 
 | 
			
		||||
@@ -18,7 +18,6 @@ export default class StatusList extends ImmutablePureComponent {
 | 
			
		||||
    onScrollToTop: PropTypes.func,
 | 
			
		||||
    onScroll: PropTypes.func,
 | 
			
		||||
    trackScroll: PropTypes.bool,
 | 
			
		||||
    shouldUpdateScroll: PropTypes.func,
 | 
			
		||||
    isLoading: PropTypes.bool,
 | 
			
		||||
    isPartial: PropTypes.bool,
 | 
			
		||||
    hasMore: PropTypes.bool,
 | 
			
		||||
@@ -77,7 +76,7 @@ export default class StatusList extends ImmutablePureComponent {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  render () {
 | 
			
		||||
    const { statusIds, featuredStatusIds, shouldUpdateScroll, onLoadMore, timelineId, ...other }  = this.props;
 | 
			
		||||
    const { statusIds, featuredStatusIds, onLoadMore, timelineId, ...other }  = this.props;
 | 
			
		||||
    const { isLoading, isPartial } = other;
 | 
			
		||||
 | 
			
		||||
    if (isPartial) {
 | 
			
		||||
@@ -120,7 +119,7 @@ export default class StatusList extends ImmutablePureComponent {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
      <ScrollableList {...other} showLoading={isLoading && statusIds.size === 0} onLoadMore={onLoadMore && this.handleLoadOlder} shouldUpdateScroll={shouldUpdateScroll} ref={this.setRef}>
 | 
			
		||||
      <ScrollableList {...other} showLoading={isLoading && statusIds.size === 0} onLoadMore={onLoadMore && this.handleLoadOlder} ref={this.setRef}>
 | 
			
		||||
        {scrollableContent}
 | 
			
		||||
      </ScrollableList>
 | 
			
		||||
    );
 | 
			
		||||
 
 | 
			
		||||
@@ -10,8 +10,6 @@ import { hydrateStore } from '../actions/store';
 | 
			
		||||
import { connectUserStream } from '../actions/streaming';
 | 
			
		||||
import { IntlProvider, addLocaleData } from 'react-intl';
 | 
			
		||||
import { getLocale } from '../locales';
 | 
			
		||||
import { previewState as previewMediaState } from 'mastodon/features/ui/components/media_modal';
 | 
			
		||||
import { previewState as previewVideoState } from 'mastodon/features/ui/components/video_modal';
 | 
			
		||||
import initialState from '../initial_state';
 | 
			
		||||
import ErrorBoundary from '../components/error_boundary';
 | 
			
		||||
 | 
			
		||||
@@ -41,8 +39,8 @@ export default class Mastodon extends React.PureComponent {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  shouldUpdateScroll (_, { location }) {
 | 
			
		||||
    return location.state !== previewMediaState && location.state !== previewVideoState;
 | 
			
		||||
  shouldUpdateScroll (prevRouterProps, { location }) {
 | 
			
		||||
    return !(location.state?.mastodonModalKey && location.state?.mastodonModalKey !== prevRouterProps?.location?.state?.mastodonModalKey);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  render () {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										18
									
								
								app/javascript/mastodon/containers/scroll_container.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								app/javascript/mastodon/containers/scroll_container.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,18 @@
 | 
			
		||||
import { ScrollContainer as OriginalScrollContainer } from 'react-router-scroll-4';
 | 
			
		||||
 | 
			
		||||
// ScrollContainer is used to automatically scroll to the top when pushing a
 | 
			
		||||
// new history state and remembering the scroll position when going back.
 | 
			
		||||
// There are a few things we need to do differently, though.
 | 
			
		||||
const defaultShouldUpdateScroll = (prevRouterProps, { location }) => {
 | 
			
		||||
  // If the change is caused by opening a modal, do not scroll to top
 | 
			
		||||
  return !(location.state?.mastodonModalKey && location.state?.mastodonModalKey !== prevRouterProps?.location?.state?.mastodonModalKey);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default
 | 
			
		||||
class ScrollContainer extends OriginalScrollContainer {
 | 
			
		||||
 | 
			
		||||
  static defaultProps = {
 | 
			
		||||
    shouldUpdateScroll: defaultShouldUpdateScroll,
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -11,7 +11,7 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
 | 
			
		||||
import { getAccountGallery } from 'mastodon/selectors';
 | 
			
		||||
import MediaItem from './components/media_item';
 | 
			
		||||
import HeaderContainer from '../account_timeline/containers/header_container';
 | 
			
		||||
import { ScrollContainer } from 'react-router-scroll-4';
 | 
			
		||||
import ScrollContainer from 'mastodon/containers/scroll_container';
 | 
			
		||||
import LoadMore from 'mastodon/components/load_more';
 | 
			
		||||
import MissingIndicator from 'mastodon/components/missing_indicator';
 | 
			
		||||
import { openModal } from 'mastodon/actions/modal';
 | 
			
		||||
@@ -29,7 +29,6 @@ const mapStateToProps = (state, props) => ({
 | 
			
		||||
class LoadMoreMedia extends ImmutablePureComponent {
 | 
			
		||||
 | 
			
		||||
  static propTypes = {
 | 
			
		||||
    shouldUpdateScroll: PropTypes.func,
 | 
			
		||||
    maxId: PropTypes.string,
 | 
			
		||||
    onLoadMore: PropTypes.func.isRequired,
 | 
			
		||||
  };
 | 
			
		||||
@@ -127,7 +126,7 @@ class AccountGallery extends ImmutablePureComponent {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  render () {
 | 
			
		||||
    const { attachments, shouldUpdateScroll, isLoading, hasMore, isAccount, multiColumn, blockedBy, suspended } = this.props;
 | 
			
		||||
    const { attachments, isLoading, hasMore, isAccount, multiColumn, blockedBy, suspended } = this.props;
 | 
			
		||||
    const { width } = this.state;
 | 
			
		||||
 | 
			
		||||
    if (!isAccount) {
 | 
			
		||||
@@ -164,7 +163,7 @@ class AccountGallery extends ImmutablePureComponent {
 | 
			
		||||
      <Column>
 | 
			
		||||
        <ColumnBackButton multiColumn={multiColumn} />
 | 
			
		||||
 | 
			
		||||
        <ScrollContainer scrollKey='account_gallery' shouldUpdateScroll={shouldUpdateScroll}>
 | 
			
		||||
        <ScrollContainer scrollKey='account_gallery'>
 | 
			
		||||
          <div className='scrollable scrollable--flex' onScroll={this.handleScroll}>
 | 
			
		||||
            <HeaderContainer accountId={this.props.params.accountId} />
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -50,7 +50,6 @@ class AccountTimeline extends ImmutablePureComponent {
 | 
			
		||||
  static propTypes = {
 | 
			
		||||
    params: PropTypes.object.isRequired,
 | 
			
		||||
    dispatch: PropTypes.func.isRequired,
 | 
			
		||||
    shouldUpdateScroll: PropTypes.func,
 | 
			
		||||
    statusIds: ImmutablePropTypes.list,
 | 
			
		||||
    featuredStatusIds: ImmutablePropTypes.list,
 | 
			
		||||
    isLoading: PropTypes.bool,
 | 
			
		||||
@@ -115,7 +114,7 @@ class AccountTimeline extends ImmutablePureComponent {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  render () {
 | 
			
		||||
    const { shouldUpdateScroll, statusIds, featuredStatusIds, isLoading, hasMore, blockedBy, suspended, isAccount, multiColumn, remote, remoteUrl } = this.props;
 | 
			
		||||
    const { statusIds, featuredStatusIds, isLoading, hasMore, blockedBy, suspended, isAccount, multiColumn, remote, remoteUrl } = this.props;
 | 
			
		||||
 | 
			
		||||
    if (!isAccount) {
 | 
			
		||||
      return (
 | 
			
		||||
@@ -162,7 +161,6 @@ class AccountTimeline extends ImmutablePureComponent {
 | 
			
		||||
          isLoading={isLoading}
 | 
			
		||||
          hasMore={hasMore}
 | 
			
		||||
          onLoadMore={this.handleLoadMore}
 | 
			
		||||
          shouldUpdateScroll={shouldUpdateScroll}
 | 
			
		||||
          emptyMessage={emptyMessage}
 | 
			
		||||
          bindToDocument={!multiColumn}
 | 
			
		||||
          timelineId='account'
 | 
			
		||||
 
 | 
			
		||||
@@ -29,7 +29,6 @@ class Blocks extends ImmutablePureComponent {
 | 
			
		||||
  static propTypes = {
 | 
			
		||||
    params: PropTypes.object.isRequired,
 | 
			
		||||
    dispatch: PropTypes.func.isRequired,
 | 
			
		||||
    shouldUpdateScroll: PropTypes.func,
 | 
			
		||||
    accountIds: ImmutablePropTypes.list,
 | 
			
		||||
    hasMore: PropTypes.bool,
 | 
			
		||||
    isLoading: PropTypes.bool,
 | 
			
		||||
@@ -46,7 +45,7 @@ class Blocks extends ImmutablePureComponent {
 | 
			
		||||
  }, 300, { leading: true });
 | 
			
		||||
 | 
			
		||||
  render () {
 | 
			
		||||
    const { intl, accountIds, shouldUpdateScroll, hasMore, multiColumn, isLoading } = this.props;
 | 
			
		||||
    const { intl, accountIds, hasMore, multiColumn, isLoading } = this.props;
 | 
			
		||||
 | 
			
		||||
    if (!accountIds) {
 | 
			
		||||
      return (
 | 
			
		||||
@@ -66,7 +65,6 @@ class Blocks extends ImmutablePureComponent {
 | 
			
		||||
          onLoadMore={this.handleLoadMore}
 | 
			
		||||
          hasMore={hasMore}
 | 
			
		||||
          isLoading={isLoading}
 | 
			
		||||
          shouldUpdateScroll={shouldUpdateScroll}
 | 
			
		||||
          emptyMessage={emptyMessage}
 | 
			
		||||
          bindToDocument={!multiColumn}
 | 
			
		||||
        >
 | 
			
		||||
 
 | 
			
		||||
@@ -27,7 +27,6 @@ class Bookmarks extends ImmutablePureComponent {
 | 
			
		||||
 | 
			
		||||
  static propTypes = {
 | 
			
		||||
    dispatch: PropTypes.func.isRequired,
 | 
			
		||||
    shouldUpdateScroll: PropTypes.func,
 | 
			
		||||
    statusIds: ImmutablePropTypes.list.isRequired,
 | 
			
		||||
    intl: PropTypes.object.isRequired,
 | 
			
		||||
    columnId: PropTypes.string,
 | 
			
		||||
@@ -68,7 +67,7 @@ class Bookmarks extends ImmutablePureComponent {
 | 
			
		||||
  }, 300, { leading: true })
 | 
			
		||||
 | 
			
		||||
  render () {
 | 
			
		||||
    const { intl, shouldUpdateScroll, statusIds, columnId, multiColumn, hasMore, isLoading } = this.props;
 | 
			
		||||
    const { intl, statusIds, columnId, multiColumn, hasMore, isLoading } = this.props;
 | 
			
		||||
    const pinned = !!columnId;
 | 
			
		||||
 | 
			
		||||
    const emptyMessage = <FormattedMessage id='empty_column.bookmarked_statuses' defaultMessage="You don't have any bookmarked toots yet. When you bookmark one, it will show up here." />;
 | 
			
		||||
@@ -93,7 +92,6 @@ class Bookmarks extends ImmutablePureComponent {
 | 
			
		||||
          hasMore={hasMore}
 | 
			
		||||
          isLoading={isLoading}
 | 
			
		||||
          onLoadMore={this.handleLoadMore}
 | 
			
		||||
          shouldUpdateScroll={shouldUpdateScroll}
 | 
			
		||||
          emptyMessage={emptyMessage}
 | 
			
		||||
          bindToDocument={!multiColumn}
 | 
			
		||||
        />
 | 
			
		||||
 
 | 
			
		||||
@@ -41,7 +41,6 @@ class CommunityTimeline extends React.PureComponent {
 | 
			
		||||
 | 
			
		||||
  static propTypes = {
 | 
			
		||||
    dispatch: PropTypes.func.isRequired,
 | 
			
		||||
    shouldUpdateScroll: PropTypes.func,
 | 
			
		||||
    columnId: PropTypes.string,
 | 
			
		||||
    intl: PropTypes.object.isRequired,
 | 
			
		||||
    hasUnread: PropTypes.bool,
 | 
			
		||||
@@ -103,7 +102,7 @@ class CommunityTimeline extends React.PureComponent {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  render () {
 | 
			
		||||
    const { intl, shouldUpdateScroll, hasUnread, columnId, multiColumn, onlyMedia } = this.props;
 | 
			
		||||
    const { intl, hasUnread, columnId, multiColumn, onlyMedia } = this.props;
 | 
			
		||||
    const pinned = !!columnId;
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
@@ -127,7 +126,6 @@ class CommunityTimeline extends React.PureComponent {
 | 
			
		||||
          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!' />}
 | 
			
		||||
          shouldUpdateScroll={shouldUpdateScroll}
 | 
			
		||||
          bindToDocument={!multiColumn}
 | 
			
		||||
        />
 | 
			
		||||
      </Column>
 | 
			
		||||
 
 | 
			
		||||
@@ -14,7 +14,6 @@ export default class ConversationsList extends ImmutablePureComponent {
 | 
			
		||||
    hasMore: PropTypes.bool,
 | 
			
		||||
    isLoading: PropTypes.bool,
 | 
			
		||||
    onLoadMore: PropTypes.func,
 | 
			
		||||
    shouldUpdateScroll: PropTypes.func,
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  getCurrentIndex = id => this.props.conversations.findIndex(x => x.get('id') === id)
 | 
			
		||||
 
 | 
			
		||||
@@ -19,7 +19,6 @@ class DirectTimeline extends React.PureComponent {
 | 
			
		||||
 | 
			
		||||
  static propTypes = {
 | 
			
		||||
    dispatch: PropTypes.func.isRequired,
 | 
			
		||||
    shouldUpdateScroll: PropTypes.func,
 | 
			
		||||
    columnId: PropTypes.string,
 | 
			
		||||
    intl: PropTypes.object.isRequired,
 | 
			
		||||
    hasUnread: PropTypes.bool,
 | 
			
		||||
@@ -71,7 +70,7 @@ class DirectTimeline extends React.PureComponent {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  render () {
 | 
			
		||||
    const { intl, hasUnread, columnId, multiColumn, shouldUpdateScroll } = this.props;
 | 
			
		||||
    const { intl, hasUnread, columnId, multiColumn } = this.props;
 | 
			
		||||
    const pinned = !!columnId;
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
@@ -93,7 +92,6 @@ class DirectTimeline extends React.PureComponent {
 | 
			
		||||
          timelineId='direct'
 | 
			
		||||
          onLoadMore={this.handleLoadMore}
 | 
			
		||||
          emptyMessage={<FormattedMessage id='empty_column.direct' defaultMessage="You don't have any direct messages yet. When you send or receive one, it will show up here." />}
 | 
			
		||||
          shouldUpdateScroll={shouldUpdateScroll}
 | 
			
		||||
        />
 | 
			
		||||
      </Column>
 | 
			
		||||
    );
 | 
			
		||||
 
 | 
			
		||||
@@ -12,7 +12,7 @@ import AccountCard from './components/account_card';
 | 
			
		||||
import RadioButton from 'mastodon/components/radio_button';
 | 
			
		||||
import classNames from 'classnames';
 | 
			
		||||
import LoadMore from 'mastodon/components/load_more';
 | 
			
		||||
import { ScrollContainer } from 'react-router-scroll-4';
 | 
			
		||||
import ScrollContainer from 'mastodon/containers/scroll_container';
 | 
			
		||||
 | 
			
		||||
const messages = defineMessages({
 | 
			
		||||
  title: { id: 'column.directory', defaultMessage: 'Browse profiles' },
 | 
			
		||||
@@ -40,7 +40,6 @@ class Directory extends React.PureComponent {
 | 
			
		||||
    isLoading: PropTypes.bool,
 | 
			
		||||
    accountIds: ImmutablePropTypes.list.isRequired,
 | 
			
		||||
    dispatch: PropTypes.func.isRequired,
 | 
			
		||||
    shouldUpdateScroll: PropTypes.func,
 | 
			
		||||
    columnId: PropTypes.string,
 | 
			
		||||
    intl: PropTypes.object.isRequired,
 | 
			
		||||
    multiColumn: PropTypes.bool,
 | 
			
		||||
@@ -125,7 +124,7 @@ class Directory extends React.PureComponent {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  render () {
 | 
			
		||||
    const { isLoading, accountIds, intl, columnId, multiColumn, domain, shouldUpdateScroll } = this.props;
 | 
			
		||||
    const { isLoading, accountIds, intl, columnId, multiColumn, domain } = this.props;
 | 
			
		||||
    const { order, local }  = this.getParams(this.props, this.state);
 | 
			
		||||
    const pinned = !!columnId;
 | 
			
		||||
 | 
			
		||||
@@ -163,7 +162,7 @@ class Directory extends React.PureComponent {
 | 
			
		||||
          multiColumn={multiColumn}
 | 
			
		||||
        />
 | 
			
		||||
 | 
			
		||||
        {multiColumn && !pinned ? <ScrollContainer scrollKey='directory' shouldUpdateScroll={shouldUpdateScroll}>{scrollableArea}</ScrollContainer> : scrollableArea}
 | 
			
		||||
        {multiColumn && !pinned ? <ScrollContainer scrollKey='directory'>{scrollableArea}</ScrollContainer> : scrollableArea}
 | 
			
		||||
      </Column>
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -29,7 +29,6 @@ class Blocks extends ImmutablePureComponent {
 | 
			
		||||
  static propTypes = {
 | 
			
		||||
    params: PropTypes.object.isRequired,
 | 
			
		||||
    dispatch: PropTypes.func.isRequired,
 | 
			
		||||
    shouldUpdateScroll: PropTypes.func,
 | 
			
		||||
    hasMore: PropTypes.bool,
 | 
			
		||||
    domains: ImmutablePropTypes.orderedSet,
 | 
			
		||||
    intl: PropTypes.object.isRequired,
 | 
			
		||||
@@ -45,7 +44,7 @@ class Blocks extends ImmutablePureComponent {
 | 
			
		||||
  }, 300, { leading: true });
 | 
			
		||||
 | 
			
		||||
  render () {
 | 
			
		||||
    const { intl, domains, shouldUpdateScroll, hasMore, multiColumn } = this.props;
 | 
			
		||||
    const { intl, domains, hasMore, multiColumn } = this.props;
 | 
			
		||||
 | 
			
		||||
    if (!domains) {
 | 
			
		||||
      return (
 | 
			
		||||
@@ -64,7 +63,6 @@ class Blocks extends ImmutablePureComponent {
 | 
			
		||||
          scrollKey='domain_blocks'
 | 
			
		||||
          onLoadMore={this.handleLoadMore}
 | 
			
		||||
          hasMore={hasMore}
 | 
			
		||||
          shouldUpdateScroll={shouldUpdateScroll}
 | 
			
		||||
          emptyMessage={emptyMessage}
 | 
			
		||||
          bindToDocument={!multiColumn}
 | 
			
		||||
        >
 | 
			
		||||
 
 | 
			
		||||
@@ -27,7 +27,6 @@ class Favourites extends ImmutablePureComponent {
 | 
			
		||||
 | 
			
		||||
  static propTypes = {
 | 
			
		||||
    dispatch: PropTypes.func.isRequired,
 | 
			
		||||
    shouldUpdateScroll: PropTypes.func,
 | 
			
		||||
    statusIds: ImmutablePropTypes.list.isRequired,
 | 
			
		||||
    intl: PropTypes.object.isRequired,
 | 
			
		||||
    columnId: PropTypes.string,
 | 
			
		||||
@@ -68,7 +67,7 @@ class Favourites extends ImmutablePureComponent {
 | 
			
		||||
  }, 300, { leading: true })
 | 
			
		||||
 | 
			
		||||
  render () {
 | 
			
		||||
    const { intl, shouldUpdateScroll, statusIds, columnId, multiColumn, hasMore, isLoading } = this.props;
 | 
			
		||||
    const { intl, statusIds, columnId, multiColumn, hasMore, isLoading } = this.props;
 | 
			
		||||
    const pinned = !!columnId;
 | 
			
		||||
 | 
			
		||||
    const emptyMessage = <FormattedMessage id='empty_column.favourited_statuses' defaultMessage="You don't have any favourite toots yet. When you favourite one, it will show up here." />;
 | 
			
		||||
@@ -93,7 +92,6 @@ class Favourites extends ImmutablePureComponent {
 | 
			
		||||
          hasMore={hasMore}
 | 
			
		||||
          isLoading={isLoading}
 | 
			
		||||
          onLoadMore={this.handleLoadMore}
 | 
			
		||||
          shouldUpdateScroll={shouldUpdateScroll}
 | 
			
		||||
          emptyMessage={emptyMessage}
 | 
			
		||||
          bindToDocument={!multiColumn}
 | 
			
		||||
        />
 | 
			
		||||
 
 | 
			
		||||
@@ -27,7 +27,6 @@ class Favourites extends ImmutablePureComponent {
 | 
			
		||||
  static propTypes = {
 | 
			
		||||
    params: PropTypes.object.isRequired,
 | 
			
		||||
    dispatch: PropTypes.func.isRequired,
 | 
			
		||||
    shouldUpdateScroll: PropTypes.func,
 | 
			
		||||
    accountIds: ImmutablePropTypes.list,
 | 
			
		||||
    multiColumn: PropTypes.bool,
 | 
			
		||||
    intl: PropTypes.object.isRequired,
 | 
			
		||||
@@ -50,7 +49,7 @@ class Favourites extends ImmutablePureComponent {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  render () {
 | 
			
		||||
    const { intl, shouldUpdateScroll, accountIds, multiColumn } = this.props;
 | 
			
		||||
    const { intl, accountIds, multiColumn } = this.props;
 | 
			
		||||
 | 
			
		||||
    if (!accountIds) {
 | 
			
		||||
      return (
 | 
			
		||||
@@ -74,7 +73,6 @@ class Favourites extends ImmutablePureComponent {
 | 
			
		||||
 | 
			
		||||
        <ScrollableList
 | 
			
		||||
          scrollKey='favourites'
 | 
			
		||||
          shouldUpdateScroll={shouldUpdateScroll}
 | 
			
		||||
          emptyMessage={emptyMessage}
 | 
			
		||||
          bindToDocument={!multiColumn}
 | 
			
		||||
        >
 | 
			
		||||
 
 | 
			
		||||
@@ -32,7 +32,6 @@ class FollowRequests extends ImmutablePureComponent {
 | 
			
		||||
  static propTypes = {
 | 
			
		||||
    params: PropTypes.object.isRequired,
 | 
			
		||||
    dispatch: PropTypes.func.isRequired,
 | 
			
		||||
    shouldUpdateScroll: PropTypes.func,
 | 
			
		||||
    hasMore: PropTypes.bool,
 | 
			
		||||
    isLoading: PropTypes.bool,
 | 
			
		||||
    accountIds: ImmutablePropTypes.list,
 | 
			
		||||
@@ -51,7 +50,7 @@ class FollowRequests extends ImmutablePureComponent {
 | 
			
		||||
  }, 300, { leading: true });
 | 
			
		||||
 | 
			
		||||
  render () {
 | 
			
		||||
    const { intl, shouldUpdateScroll, accountIds, hasMore, multiColumn, locked, domain, isLoading } = this.props;
 | 
			
		||||
    const { intl, accountIds, hasMore, multiColumn, locked, domain, isLoading } = this.props;
 | 
			
		||||
 | 
			
		||||
    if (!accountIds) {
 | 
			
		||||
      return (
 | 
			
		||||
@@ -80,7 +79,6 @@ class FollowRequests extends ImmutablePureComponent {
 | 
			
		||||
          onLoadMore={this.handleLoadMore}
 | 
			
		||||
          hasMore={hasMore}
 | 
			
		||||
          isLoading={isLoading}
 | 
			
		||||
          shouldUpdateScroll={shouldUpdateScroll}
 | 
			
		||||
          emptyMessage={emptyMessage}
 | 
			
		||||
          bindToDocument={!multiColumn}
 | 
			
		||||
          prepend={unlockedPrependMessage}
 | 
			
		||||
 
 | 
			
		||||
@@ -43,7 +43,6 @@ class Followers extends ImmutablePureComponent {
 | 
			
		||||
  static propTypes = {
 | 
			
		||||
    params: PropTypes.object.isRequired,
 | 
			
		||||
    dispatch: PropTypes.func.isRequired,
 | 
			
		||||
    shouldUpdateScroll: PropTypes.func,
 | 
			
		||||
    accountIds: ImmutablePropTypes.list,
 | 
			
		||||
    hasMore: PropTypes.bool,
 | 
			
		||||
    isLoading: PropTypes.bool,
 | 
			
		||||
@@ -73,7 +72,7 @@ class Followers extends ImmutablePureComponent {
 | 
			
		||||
  }, 300, { leading: true });
 | 
			
		||||
 | 
			
		||||
  render () {
 | 
			
		||||
    const { shouldUpdateScroll, accountIds, hasMore, blockedBy, isAccount, multiColumn, isLoading, remote, remoteUrl } = this.props;
 | 
			
		||||
    const { accountIds, hasMore, blockedBy, isAccount, multiColumn, isLoading, remote, remoteUrl } = this.props;
 | 
			
		||||
 | 
			
		||||
    if (!isAccount) {
 | 
			
		||||
      return (
 | 
			
		||||
@@ -112,7 +111,6 @@ class Followers extends ImmutablePureComponent {
 | 
			
		||||
          hasMore={hasMore}
 | 
			
		||||
          isLoading={isLoading}
 | 
			
		||||
          onLoadMore={this.handleLoadMore}
 | 
			
		||||
          shouldUpdateScroll={shouldUpdateScroll}
 | 
			
		||||
          prepend={<HeaderContainer accountId={this.props.params.accountId} hideTabs />}
 | 
			
		||||
          alwaysPrepend
 | 
			
		||||
          append={remoteMessage}
 | 
			
		||||
 
 | 
			
		||||
@@ -43,7 +43,6 @@ class Following extends ImmutablePureComponent {
 | 
			
		||||
  static propTypes = {
 | 
			
		||||
    params: PropTypes.object.isRequired,
 | 
			
		||||
    dispatch: PropTypes.func.isRequired,
 | 
			
		||||
    shouldUpdateScroll: PropTypes.func,
 | 
			
		||||
    accountIds: ImmutablePropTypes.list,
 | 
			
		||||
    hasMore: PropTypes.bool,
 | 
			
		||||
    isLoading: PropTypes.bool,
 | 
			
		||||
@@ -73,7 +72,7 @@ class Following extends ImmutablePureComponent {
 | 
			
		||||
  }, 300, { leading: true });
 | 
			
		||||
 | 
			
		||||
  render () {
 | 
			
		||||
    const { shouldUpdateScroll, accountIds, hasMore, blockedBy, isAccount, multiColumn, isLoading, remote, remoteUrl } = this.props;
 | 
			
		||||
    const { accountIds, hasMore, blockedBy, isAccount, multiColumn, isLoading, remote, remoteUrl } = this.props;
 | 
			
		||||
 | 
			
		||||
    if (!isAccount) {
 | 
			
		||||
      return (
 | 
			
		||||
@@ -112,7 +111,6 @@ class Following extends ImmutablePureComponent {
 | 
			
		||||
          hasMore={hasMore}
 | 
			
		||||
          isLoading={isLoading}
 | 
			
		||||
          onLoadMore={this.handleLoadMore}
 | 
			
		||||
          shouldUpdateScroll={shouldUpdateScroll}
 | 
			
		||||
          prepend={<HeaderContainer accountId={this.props.params.accountId} hideTabs />}
 | 
			
		||||
          alwaysPrepend
 | 
			
		||||
          append={remoteMessage}
 | 
			
		||||
 
 | 
			
		||||
@@ -24,7 +24,6 @@ class HashtagTimeline extends React.PureComponent {
 | 
			
		||||
    params: PropTypes.object.isRequired,
 | 
			
		||||
    columnId: PropTypes.string,
 | 
			
		||||
    dispatch: PropTypes.func.isRequired,
 | 
			
		||||
    shouldUpdateScroll: PropTypes.func,
 | 
			
		||||
    hasUnread: PropTypes.bool,
 | 
			
		||||
    multiColumn: PropTypes.bool,
 | 
			
		||||
  };
 | 
			
		||||
@@ -130,7 +129,7 @@ class HashtagTimeline extends React.PureComponent {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  render () {
 | 
			
		||||
    const { shouldUpdateScroll, hasUnread, columnId, multiColumn } = this.props;
 | 
			
		||||
    const { hasUnread, columnId, multiColumn } = this.props;
 | 
			
		||||
    const { id, local } = this.props.params;
 | 
			
		||||
    const pinned = !!columnId;
 | 
			
		||||
 | 
			
		||||
@@ -156,7 +155,6 @@ class HashtagTimeline extends React.PureComponent {
 | 
			
		||||
          timelineId={`hashtag:${id}${local ? ':local' : ''}`}
 | 
			
		||||
          onLoadMore={this.handleLoadMore}
 | 
			
		||||
          emptyMessage={<FormattedMessage id='empty_column.hashtag' defaultMessage='There is nothing in this hashtag yet.' />}
 | 
			
		||||
          shouldUpdateScroll={shouldUpdateScroll}
 | 
			
		||||
          bindToDocument={!multiColumn}
 | 
			
		||||
        />
 | 
			
		||||
      </Column>
 | 
			
		||||
 
 | 
			
		||||
@@ -34,7 +34,6 @@ class HomeTimeline extends React.PureComponent {
 | 
			
		||||
 | 
			
		||||
  static propTypes = {
 | 
			
		||||
    dispatch: PropTypes.func.isRequired,
 | 
			
		||||
    shouldUpdateScroll: PropTypes.func,
 | 
			
		||||
    intl: PropTypes.object.isRequired,
 | 
			
		||||
    hasUnread: PropTypes.bool,
 | 
			
		||||
    isPartial: PropTypes.bool,
 | 
			
		||||
@@ -112,7 +111,7 @@ class HomeTimeline extends React.PureComponent {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  render () {
 | 
			
		||||
    const { intl, shouldUpdateScroll, hasUnread, columnId, multiColumn, hasAnnouncements, unreadAnnouncements, showAnnouncements } = this.props;
 | 
			
		||||
    const { intl, hasUnread, columnId, multiColumn, hasAnnouncements, unreadAnnouncements, showAnnouncements } = this.props;
 | 
			
		||||
    const pinned = !!columnId;
 | 
			
		||||
 | 
			
		||||
    let announcementsButton = null;
 | 
			
		||||
@@ -154,7 +153,6 @@ class HomeTimeline extends React.PureComponent {
 | 
			
		||||
          onLoadMore={this.handleLoadMore}
 | 
			
		||||
          timelineId='home'
 | 
			
		||||
          emptyMessage={<FormattedMessage id='empty_column.home' defaultMessage='Your home timeline is empty! Follow more people to fill it up. {suggestions}' values={{ suggestions: <Link to='/start'><FormattedMessage id='empty_column.home.suggestions' defaultMessage='See some suggestions' /></Link> }} />}
 | 
			
		||||
          shouldUpdateScroll={shouldUpdateScroll}
 | 
			
		||||
          bindToDocument={!multiColumn}
 | 
			
		||||
        />
 | 
			
		||||
      </Column>
 | 
			
		||||
 
 | 
			
		||||
@@ -41,7 +41,6 @@ class ListTimeline extends React.PureComponent {
 | 
			
		||||
  static propTypes = {
 | 
			
		||||
    params: PropTypes.object.isRequired,
 | 
			
		||||
    dispatch: PropTypes.func.isRequired,
 | 
			
		||||
    shouldUpdateScroll: PropTypes.func,
 | 
			
		||||
    columnId: PropTypes.string,
 | 
			
		||||
    hasUnread: PropTypes.bool,
 | 
			
		||||
    multiColumn: PropTypes.bool,
 | 
			
		||||
@@ -142,7 +141,7 @@ class ListTimeline extends React.PureComponent {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  render () {
 | 
			
		||||
    const { shouldUpdateScroll, hasUnread, columnId, multiColumn, list, intl } = this.props;
 | 
			
		||||
    const { hasUnread, columnId, multiColumn, list, intl } = this.props;
 | 
			
		||||
    const { id } = this.props.params;
 | 
			
		||||
    const pinned = !!columnId;
 | 
			
		||||
    const title  = list ? list.get('title') : id;
 | 
			
		||||
@@ -207,7 +206,6 @@ class ListTimeline extends React.PureComponent {
 | 
			
		||||
          timelineId={`list:${id}`}
 | 
			
		||||
          onLoadMore={this.handleLoadMore}
 | 
			
		||||
          emptyMessage={<FormattedMessage id='empty_column.list' defaultMessage='There is nothing in this list yet. When members of this list post new statuses, they will appear here.' />}
 | 
			
		||||
          shouldUpdateScroll={shouldUpdateScroll}
 | 
			
		||||
          bindToDocument={!multiColumn}
 | 
			
		||||
        />
 | 
			
		||||
      </Column>
 | 
			
		||||
 
 | 
			
		||||
@@ -48,7 +48,7 @@ class Lists extends ImmutablePureComponent {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  render () {
 | 
			
		||||
    const { intl, shouldUpdateScroll, lists, multiColumn } = this.props;
 | 
			
		||||
    const { intl, lists, multiColumn } = this.props;
 | 
			
		||||
 | 
			
		||||
    if (!lists) {
 | 
			
		||||
      return (
 | 
			
		||||
@@ -68,7 +68,6 @@ class Lists extends ImmutablePureComponent {
 | 
			
		||||
 | 
			
		||||
        <ScrollableList
 | 
			
		||||
          scrollKey='lists'
 | 
			
		||||
          shouldUpdateScroll={shouldUpdateScroll}
 | 
			
		||||
          emptyMessage={emptyMessage}
 | 
			
		||||
          prepend={<ColumnSubheading text={intl.formatMessage(messages.subheading)} />}
 | 
			
		||||
          bindToDocument={!multiColumn}
 | 
			
		||||
 
 | 
			
		||||
@@ -29,7 +29,6 @@ class Mutes extends ImmutablePureComponent {
 | 
			
		||||
  static propTypes = {
 | 
			
		||||
    params: PropTypes.object.isRequired,
 | 
			
		||||
    dispatch: PropTypes.func.isRequired,
 | 
			
		||||
    shouldUpdateScroll: PropTypes.func,
 | 
			
		||||
    hasMore: PropTypes.bool,
 | 
			
		||||
    isLoading: PropTypes.bool,
 | 
			
		||||
    accountIds: ImmutablePropTypes.list,
 | 
			
		||||
@@ -46,7 +45,7 @@ class Mutes extends ImmutablePureComponent {
 | 
			
		||||
  }, 300, { leading: true });
 | 
			
		||||
 | 
			
		||||
  render () {
 | 
			
		||||
    const { intl, shouldUpdateScroll, hasMore, accountIds, multiColumn, isLoading } = this.props;
 | 
			
		||||
    const { intl, hasMore, accountIds, multiColumn, isLoading } = this.props;
 | 
			
		||||
 | 
			
		||||
    if (!accountIds) {
 | 
			
		||||
      return (
 | 
			
		||||
@@ -66,7 +65,6 @@ class Mutes extends ImmutablePureComponent {
 | 
			
		||||
          onLoadMore={this.handleLoadMore}
 | 
			
		||||
          hasMore={hasMore}
 | 
			
		||||
          isLoading={isLoading}
 | 
			
		||||
          shouldUpdateScroll={shouldUpdateScroll}
 | 
			
		||||
          emptyMessage={emptyMessage}
 | 
			
		||||
          bindToDocument={!multiColumn}
 | 
			
		||||
        >
 | 
			
		||||
 
 | 
			
		||||
@@ -74,7 +74,6 @@ class Notifications extends React.PureComponent {
 | 
			
		||||
    notifications: ImmutablePropTypes.list.isRequired,
 | 
			
		||||
    showFilterBar: PropTypes.bool.isRequired,
 | 
			
		||||
    dispatch: PropTypes.func.isRequired,
 | 
			
		||||
    shouldUpdateScroll: PropTypes.func,
 | 
			
		||||
    intl: PropTypes.object.isRequired,
 | 
			
		||||
    isLoading: PropTypes.bool,
 | 
			
		||||
    isUnread: PropTypes.bool,
 | 
			
		||||
@@ -176,7 +175,7 @@ class Notifications extends React.PureComponent {
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  render () {
 | 
			
		||||
    const { intl, notifications, shouldUpdateScroll, isLoading, isUnread, columnId, multiColumn, hasMore, numPending, showFilterBar, lastReadId, canMarkAsRead, needsNotificationPermission } = this.props;
 | 
			
		||||
    const { intl, notifications, isLoading, isUnread, columnId, multiColumn, hasMore, numPending, showFilterBar, lastReadId, canMarkAsRead, needsNotificationPermission } = this.props;
 | 
			
		||||
    const pinned = !!columnId;
 | 
			
		||||
    const emptyMessage = <FormattedMessage id='empty_column.notifications' defaultMessage="You don't have any notifications yet. When other people interact with you, you will see it here." />;
 | 
			
		||||
 | 
			
		||||
@@ -227,7 +226,6 @@ class Notifications extends React.PureComponent {
 | 
			
		||||
        onLoadPending={this.handleLoadPending}
 | 
			
		||||
        onScrollToTop={this.handleScrollToTop}
 | 
			
		||||
        onScroll={this.handleScroll}
 | 
			
		||||
        shouldUpdateScroll={shouldUpdateScroll}
 | 
			
		||||
        bindToDocument={!multiColumn}
 | 
			
		||||
      >
 | 
			
		||||
        {scrollableContent}
 | 
			
		||||
 
 | 
			
		||||
@@ -24,7 +24,6 @@ class PinnedStatuses extends ImmutablePureComponent {
 | 
			
		||||
 | 
			
		||||
  static propTypes = {
 | 
			
		||||
    dispatch: PropTypes.func.isRequired,
 | 
			
		||||
    shouldUpdateScroll: PropTypes.func,
 | 
			
		||||
    statusIds: ImmutablePropTypes.list.isRequired,
 | 
			
		||||
    intl: PropTypes.object.isRequired,
 | 
			
		||||
    hasMore: PropTypes.bool.isRequired,
 | 
			
		||||
@@ -44,7 +43,7 @@ class PinnedStatuses extends ImmutablePureComponent {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  render () {
 | 
			
		||||
    const { intl, shouldUpdateScroll, statusIds, hasMore, multiColumn } = this.props;
 | 
			
		||||
    const { intl, statusIds, hasMore, multiColumn } = this.props;
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
      <Column bindToDocument={!multiColumn} icon='thumb-tack' heading={intl.formatMessage(messages.heading)} ref={this.setRef}>
 | 
			
		||||
@@ -53,7 +52,6 @@ class PinnedStatuses extends ImmutablePureComponent {
 | 
			
		||||
          statusIds={statusIds}
 | 
			
		||||
          scrollKey='pinned_statuses'
 | 
			
		||||
          hasMore={hasMore}
 | 
			
		||||
          shouldUpdateScroll={shouldUpdateScroll}
 | 
			
		||||
          bindToDocument={!multiColumn}
 | 
			
		||||
        />
 | 
			
		||||
      </Column>
 | 
			
		||||
 
 | 
			
		||||
@@ -43,7 +43,6 @@ class PublicTimeline extends React.PureComponent {
 | 
			
		||||
 | 
			
		||||
  static propTypes = {
 | 
			
		||||
    dispatch: PropTypes.func.isRequired,
 | 
			
		||||
    shouldUpdateScroll: PropTypes.func,
 | 
			
		||||
    intl: PropTypes.object.isRequired,
 | 
			
		||||
    columnId: PropTypes.string,
 | 
			
		||||
    multiColumn: PropTypes.bool,
 | 
			
		||||
@@ -106,7 +105,7 @@ class PublicTimeline extends React.PureComponent {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  render () {
 | 
			
		||||
    const { intl, shouldUpdateScroll, columnId, hasUnread, multiColumn, onlyMedia, onlyRemote } = this.props;
 | 
			
		||||
    const { intl, columnId, hasUnread, multiColumn, onlyMedia, onlyRemote } = this.props;
 | 
			
		||||
    const pinned = !!columnId;
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
@@ -130,7 +129,6 @@ class PublicTimeline extends React.PureComponent {
 | 
			
		||||
          trackScroll={!pinned}
 | 
			
		||||
          scrollKey={`public_timeline-${columnId}`}
 | 
			
		||||
          emptyMessage={<FormattedMessage id='empty_column.public' defaultMessage='There is nothing here! Write something publicly, or manually follow users from other servers to fill it up' />}
 | 
			
		||||
          shouldUpdateScroll={shouldUpdateScroll}
 | 
			
		||||
          bindToDocument={!multiColumn}
 | 
			
		||||
        />
 | 
			
		||||
      </Column>
 | 
			
		||||
 
 | 
			
		||||
@@ -27,7 +27,6 @@ class Reblogs extends ImmutablePureComponent {
 | 
			
		||||
  static propTypes = {
 | 
			
		||||
    params: PropTypes.object.isRequired,
 | 
			
		||||
    dispatch: PropTypes.func.isRequired,
 | 
			
		||||
    shouldUpdateScroll: PropTypes.func,
 | 
			
		||||
    accountIds: ImmutablePropTypes.list,
 | 
			
		||||
    multiColumn: PropTypes.bool,
 | 
			
		||||
    intl: PropTypes.object.isRequired,
 | 
			
		||||
@@ -50,7 +49,7 @@ class Reblogs extends ImmutablePureComponent {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  render () {
 | 
			
		||||
    const { intl, shouldUpdateScroll, accountIds, multiColumn } = this.props;
 | 
			
		||||
    const { intl, accountIds, multiColumn } = this.props;
 | 
			
		||||
 | 
			
		||||
    if (!accountIds) {
 | 
			
		||||
      return (
 | 
			
		||||
@@ -74,7 +73,6 @@ class Reblogs extends ImmutablePureComponent {
 | 
			
		||||
 | 
			
		||||
        <ScrollableList
 | 
			
		||||
          scrollKey='reblogs'
 | 
			
		||||
          shouldUpdateScroll={shouldUpdateScroll}
 | 
			
		||||
          emptyMessage={emptyMessage}
 | 
			
		||||
          bindToDocument={!multiColumn}
 | 
			
		||||
        >
 | 
			
		||||
 
 | 
			
		||||
@@ -45,7 +45,7 @@ import { initBlockModal } from '../../actions/blocks';
 | 
			
		||||
import { initBoostModal } from '../../actions/boosts';
 | 
			
		||||
import { initReport } from '../../actions/reports';
 | 
			
		||||
import { makeGetStatus, makeGetPictureInPicture } from '../../selectors';
 | 
			
		||||
import { ScrollContainer } from 'react-router-scroll-4';
 | 
			
		||||
import ScrollContainer from 'mastodon/containers/scroll_container';
 | 
			
		||||
import ColumnBackButton from '../../components/column_back_button';
 | 
			
		||||
import ColumnHeader from '../../components/column_header';
 | 
			
		||||
import StatusContainer from '../../containers/status_container';
 | 
			
		||||
@@ -498,7 +498,7 @@ class Status extends ImmutablePureComponent {
 | 
			
		||||
 | 
			
		||||
  render () {
 | 
			
		||||
    let ancestors, descendants;
 | 
			
		||||
    const { shouldUpdateScroll, status, ancestorsIds, descendantsIds, intl, domain, multiColumn, pictureInPicture } = this.props;
 | 
			
		||||
    const { status, ancestorsIds, descendantsIds, intl, domain, multiColumn, pictureInPicture } = this.props;
 | 
			
		||||
    const { fullscreen } = this.state;
 | 
			
		||||
 | 
			
		||||
    if (status === null) {
 | 
			
		||||
@@ -541,7 +541,7 @@ class Status extends ImmutablePureComponent {
 | 
			
		||||
          )}
 | 
			
		||||
        />
 | 
			
		||||
 | 
			
		||||
        <ScrollContainer scrollKey='thread' shouldUpdateScroll={shouldUpdateScroll}>
 | 
			
		||||
        <ScrollContainer scrollKey='thread'>
 | 
			
		||||
          <div className={classNames('scrollable', { fullscreen })} ref={this.setRef}>
 | 
			
		||||
            {ancestors}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,6 @@ import PropTypes from 'prop-types';
 | 
			
		||||
import Audio from 'mastodon/features/audio';
 | 
			
		||||
import { connect } from 'react-redux';
 | 
			
		||||
import ImmutablePureComponent from 'react-immutable-pure-component';
 | 
			
		||||
import { previewState } from './video_modal';
 | 
			
		||||
import Footer from 'mastodon/features/picture_in_picture/components/footer';
 | 
			
		||||
 | 
			
		||||
const mapStateToProps = (state, { statusId }) => ({
 | 
			
		||||
@@ -25,32 +24,6 @@ class AudioModal extends ImmutablePureComponent {
 | 
			
		||||
    onChangeBackgroundColor: PropTypes.func.isRequired,
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  static contextTypes = {
 | 
			
		||||
    router: PropTypes.object,
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  componentDidMount () {
 | 
			
		||||
    if (this.context.router) {
 | 
			
		||||
      const history = this.context.router.history;
 | 
			
		||||
 | 
			
		||||
      history.push(history.location.pathname, previewState);
 | 
			
		||||
 | 
			
		||||
      this.unlistenHistory = history.listen(() => {
 | 
			
		||||
        this.props.onClose();
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  componentWillUnmount () {
 | 
			
		||||
    if (this.context.router) {
 | 
			
		||||
      this.unlistenHistory();
 | 
			
		||||
 | 
			
		||||
      if (this.context.router.history.location.state === previewState) {
 | 
			
		||||
        this.context.router.history.goBack();
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  render () {
 | 
			
		||||
    const { media, accountStaticAvatar, statusId, onClose } = this.props;
 | 
			
		||||
    const options = this.props.options || {};
 | 
			
		||||
 
 | 
			
		||||
@@ -20,8 +20,6 @@ const messages = defineMessages({
 | 
			
		||||
  next: { id: 'lightbox.next', defaultMessage: 'Next' },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
export const previewState = 'previewMediaModal';
 | 
			
		||||
 | 
			
		||||
export default @injectIntl
 | 
			
		||||
class MediaModal extends ImmutablePureComponent {
 | 
			
		||||
 | 
			
		||||
@@ -37,10 +35,6 @@ class MediaModal extends ImmutablePureComponent {
 | 
			
		||||
    volume: PropTypes.number,
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  static contextTypes = {
 | 
			
		||||
    router: PropTypes.object,
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  state = {
 | 
			
		||||
    index: null,
 | 
			
		||||
    navigationHidden: false,
 | 
			
		||||
@@ -98,16 +92,6 @@ class MediaModal extends ImmutablePureComponent {
 | 
			
		||||
  componentDidMount () {
 | 
			
		||||
    window.addEventListener('keydown', this.handleKeyDown, false);
 | 
			
		||||
 | 
			
		||||
    if (this.context.router) {
 | 
			
		||||
      const history = this.context.router.history;
 | 
			
		||||
 | 
			
		||||
      history.push(history.location.pathname, previewState);
 | 
			
		||||
 | 
			
		||||
      this.unlistenHistory = history.listen(() => {
 | 
			
		||||
        this.props.onClose();
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    this._sendBackgroundColor();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -131,14 +115,6 @@ class MediaModal extends ImmutablePureComponent {
 | 
			
		||||
  componentWillUnmount () {
 | 
			
		||||
    window.removeEventListener('keydown', this.handleKeyDown);
 | 
			
		||||
 | 
			
		||||
    if (this.context.router) {
 | 
			
		||||
      this.unlistenHistory();
 | 
			
		||||
 | 
			
		||||
      if (this.context.router.history.location.state === previewState) {
 | 
			
		||||
        this.context.router.history.goBack();
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    this.props.onChangeBackgroundColor(null);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -6,8 +6,6 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
 | 
			
		||||
import Footer from 'mastodon/features/picture_in_picture/components/footer';
 | 
			
		||||
import { getAverageFromBlurhash } from 'mastodon/blurhash';
 | 
			
		||||
 | 
			
		||||
export const previewState = 'previewVideoModal';
 | 
			
		||||
 | 
			
		||||
export default class VideoModal extends ImmutablePureComponent {
 | 
			
		||||
 | 
			
		||||
  static propTypes = {
 | 
			
		||||
@@ -22,19 +20,9 @@ export default class VideoModal extends ImmutablePureComponent {
 | 
			
		||||
    onChangeBackgroundColor: PropTypes.func.isRequired,
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  static contextTypes = {
 | 
			
		||||
    router: PropTypes.object,
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  componentDidMount () {
 | 
			
		||||
    const { router } = this.context;
 | 
			
		||||
    const { media, onChangeBackgroundColor, onClose } = this.props;
 | 
			
		||||
 | 
			
		||||
    if (router) {
 | 
			
		||||
      router.history.push(router.history.location.pathname, previewState);
 | 
			
		||||
      this.unlistenHistory = router.history.listen(() => onClose());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const backgroundColor = getAverageFromBlurhash(media.get('blurhash'));
 | 
			
		||||
 | 
			
		||||
    if (backgroundColor) {
 | 
			
		||||
@@ -42,18 +30,6 @@ export default class VideoModal extends ImmutablePureComponent {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  componentWillUnmount () {
 | 
			
		||||
    const { router } = this.context;
 | 
			
		||||
 | 
			
		||||
    if (router) {
 | 
			
		||||
      this.unlistenHistory();
 | 
			
		||||
 | 
			
		||||
      if (router.history.location.state === previewState) {
 | 
			
		||||
        router.history.goBack();
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  render () {
 | 
			
		||||
    const { media, statusId, onClose } = this.props;
 | 
			
		||||
    const options = this.props.options || {};
 | 
			
		||||
 
 | 
			
		||||
@@ -3,8 +3,8 @@ import { closeModal } from '../../../actions/modal';
 | 
			
		||||
import ModalRoot from '../components/modal_root';
 | 
			
		||||
 | 
			
		||||
const mapStateToProps = state => ({
 | 
			
		||||
  type: state.get('modal').modalType,
 | 
			
		||||
  props: state.get('modal').modalProps,
 | 
			
		||||
  type: state.getIn(['modal', 0, 'modalType'], null),
 | 
			
		||||
  props: state.getIn(['modal', 0, 'modalProps'], {}),
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const mapDispatchToProps = dispatch => ({
 | 
			
		||||
 
 | 
			
		||||
@@ -54,8 +54,6 @@ import {
 | 
			
		||||
  FollowRecommendations,
 | 
			
		||||
} from './util/async-components';
 | 
			
		||||
import { me } from '../../initial_state';
 | 
			
		||||
import { previewState as previewMediaState } from './components/media_modal';
 | 
			
		||||
import { previewState as previewVideoState } from './components/video_modal';
 | 
			
		||||
import { closeOnboarding, INTRODUCTION_VERSION } from 'mastodon/actions/onboarding';
 | 
			
		||||
 | 
			
		||||
// Dummy import, to make sure that <Status /> ends up in the application bundle.
 | 
			
		||||
@@ -138,10 +136,6 @@ class SwitchingColumnsArea extends React.PureComponent {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  shouldUpdateScroll (_, { location }) {
 | 
			
		||||
    return location.state !== previewMediaState && location.state !== previewVideoState;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  setRef = c => {
 | 
			
		||||
    if (c) {
 | 
			
		||||
      this.node = c.getWrappedInstance();
 | 
			
		||||
@@ -158,38 +152,38 @@ class SwitchingColumnsArea extends React.PureComponent {
 | 
			
		||||
          {redirect}
 | 
			
		||||
          <WrappedRoute path='/getting-started' component={GettingStarted} content={children} />
 | 
			
		||||
          <WrappedRoute path='/keyboard-shortcuts' component={KeyboardShortcuts} content={children} />
 | 
			
		||||
          <WrappedRoute path='/timelines/home' component={HomeTimeline} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
 | 
			
		||||
          <WrappedRoute path='/timelines/public' exact component={PublicTimeline} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
 | 
			
		||||
          <WrappedRoute path='/timelines/public/local' exact component={CommunityTimeline} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
 | 
			
		||||
          <WrappedRoute path='/timelines/direct' component={DirectTimeline} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
 | 
			
		||||
          <WrappedRoute path='/timelines/tag/:id' component={HashtagTimeline} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
 | 
			
		||||
          <WrappedRoute path='/timelines/list/:id' component={ListTimeline} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
 | 
			
		||||
          <WrappedRoute path='/timelines/home' component={HomeTimeline} content={children} />
 | 
			
		||||
          <WrappedRoute path='/timelines/public' exact component={PublicTimeline} 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} />
 | 
			
		||||
 | 
			
		||||
          <WrappedRoute path='/notifications' component={Notifications} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
 | 
			
		||||
          <WrappedRoute path='/favourites' component={FavouritedStatuses} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
 | 
			
		||||
          <WrappedRoute path='/notifications' component={Notifications} content={children} />
 | 
			
		||||
          <WrappedRoute path='/favourites' component={FavouritedStatuses} content={children} />
 | 
			
		||||
          <WrappedRoute path='/bookmarks' component={BookmarkedStatuses} content={children} />
 | 
			
		||||
          <WrappedRoute path='/pinned' component={PinnedStatuses} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
 | 
			
		||||
          <WrappedRoute path='/pinned' component={PinnedStatuses} content={children} />
 | 
			
		||||
 | 
			
		||||
          <WrappedRoute path='/start' component={FollowRecommendations} content={children} />
 | 
			
		||||
          <WrappedRoute path='/search' component={Search} content={children} />
 | 
			
		||||
          <WrappedRoute path='/directory' component={Directory} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
 | 
			
		||||
          <WrappedRoute path='/directory' component={Directory} content={children} />
 | 
			
		||||
 | 
			
		||||
          <WrappedRoute path='/statuses/new' component={Compose} content={children} />
 | 
			
		||||
          <WrappedRoute path='/statuses/:statusId' exact component={Status} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
 | 
			
		||||
          <WrappedRoute path='/statuses/:statusId/reblogs' component={Reblogs} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
 | 
			
		||||
          <WrappedRoute path='/statuses/:statusId/favourites' component={Favourites} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
 | 
			
		||||
          <WrappedRoute path='/statuses/:statusId' exact component={Status} content={children} />
 | 
			
		||||
          <WrappedRoute path='/statuses/:statusId/reblogs' component={Reblogs} content={children} />
 | 
			
		||||
          <WrappedRoute path='/statuses/:statusId/favourites' component={Favourites} content={children} />
 | 
			
		||||
 | 
			
		||||
          <WrappedRoute path='/accounts/:accountId' exact component={AccountTimeline} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
 | 
			
		||||
          <WrappedRoute path='/accounts/:accountId/with_replies' component={AccountTimeline} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll, withReplies: true }} />
 | 
			
		||||
          <WrappedRoute path='/accounts/:accountId/followers' component={Followers} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
 | 
			
		||||
          <WrappedRoute path='/accounts/:accountId/following' component={Following} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
 | 
			
		||||
          <WrappedRoute path='/accounts/:accountId/media' component={AccountGallery} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
 | 
			
		||||
          <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} />
 | 
			
		||||
 | 
			
		||||
          <WrappedRoute path='/follow_requests' component={FollowRequests} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
 | 
			
		||||
          <WrappedRoute path='/blocks' component={Blocks} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
 | 
			
		||||
          <WrappedRoute path='/domain_blocks' component={DomainBlocks} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
 | 
			
		||||
          <WrappedRoute path='/mutes' component={Mutes} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
 | 
			
		||||
          <WrappedRoute path='/lists' component={Lists} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
 | 
			
		||||
          <WrappedRoute path='/follow_requests' component={FollowRequests} content={children} />
 | 
			
		||||
          <WrappedRoute path='/blocks' component={Blocks} content={children} />
 | 
			
		||||
          <WrappedRoute path='/domain_blocks' component={DomainBlocks} content={children} />
 | 
			
		||||
          <WrappedRoute path='/mutes' component={Mutes} content={children} />
 | 
			
		||||
          <WrappedRoute path='/lists' component={Lists} content={children} />
 | 
			
		||||
 | 
			
		||||
          <WrappedRoute component={GenericNotFound} content={children} />
 | 
			
		||||
        </WrappedSwitch>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,19 +1,15 @@
 | 
			
		||||
import { MODAL_OPEN, MODAL_CLOSE } from '../actions/modal';
 | 
			
		||||
import { TIMELINE_DELETE } from '../actions/timelines';
 | 
			
		||||
import { Stack as ImmutableStack, Map as ImmutableMap } from 'immutable';
 | 
			
		||||
 | 
			
		||||
const initialState = {
 | 
			
		||||
  modalType: null,
 | 
			
		||||
  modalProps: {},
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default function modal(state = initialState, action) {
 | 
			
		||||
export default function modal(state = ImmutableStack(), action) {
 | 
			
		||||
  switch(action.type) {
 | 
			
		||||
  case MODAL_OPEN:
 | 
			
		||||
    return { modalType: action.modalType, modalProps: action.modalProps };
 | 
			
		||||
        return state.unshift(ImmutableMap({ modalType: action.modalType, modalProps: action.modalProps }));
 | 
			
		||||
  case MODAL_CLOSE:
 | 
			
		||||
    return (action.modalType === undefined || action.modalType === state.modalType) ? initialState : state;
 | 
			
		||||
    return (action.modalType === undefined || action.modalType === state.getIn([0, 'modalType'])) ? state.shift() : state;
 | 
			
		||||
  case TIMELINE_DELETE:
 | 
			
		||||
    return (state.modalProps.statusId === action.id) ? initialState : state;
 | 
			
		||||
        return state.filterNot((modal) => modal.get('modalProps').statusId === action.id);
 | 
			
		||||
  default:
 | 
			
		||||
    return state;
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user