about summary refs log tree commit diff
path: root/app/javascript/flavours/glitch/containers
diff options
context:
space:
mode:
Diffstat (limited to 'app/javascript/flavours/glitch/containers')
-rw-r--r--app/javascript/flavours/glitch/containers/account_container.js72
-rw-r--r--app/javascript/flavours/glitch/containers/admin_component.js26
-rw-r--r--app/javascript/flavours/glitch/containers/compose_container.js41
-rw-r--r--app/javascript/flavours/glitch/containers/domain_container.js33
-rw-r--r--app/javascript/flavours/glitch/containers/dropdown_menu_container.js28
-rw-r--r--app/javascript/flavours/glitch/containers/intersection_observer_article_container.js17
-rw-r--r--app/javascript/flavours/glitch/containers/mastodon.js101
-rw-r--r--app/javascript/flavours/glitch/containers/media_container.js121
-rw-r--r--app/javascript/flavours/glitch/containers/notification_purge_buttons_container.js49
-rw-r--r--app/javascript/flavours/glitch/containers/poll_container.js25
-rw-r--r--app/javascript/flavours/glitch/containers/scroll_container.js18
-rw-r--r--app/javascript/flavours/glitch/containers/status_container.js257
12 files changed, 788 insertions, 0 deletions
diff --git a/app/javascript/flavours/glitch/containers/account_container.js b/app/javascript/flavours/glitch/containers/account_container.js
new file mode 100644
index 000000000..5b57d730f
--- /dev/null
+++ b/app/javascript/flavours/glitch/containers/account_container.js
@@ -0,0 +1,72 @@
+import React from 'react';
+import { connect } from 'react-redux';
+import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
+import { makeGetAccount } from 'flavours/glitch/selectors';
+import Account from 'flavours/glitch/components/account';
+import {
+  followAccount,
+  unfollowAccount,
+  blockAccount,
+  unblockAccount,
+  muteAccount,
+  unmuteAccount,
+} from 'flavours/glitch/actions/accounts';
+import { openModal } from 'flavours/glitch/actions/modal';
+import { initMuteModal } from 'flavours/glitch/actions/mutes';
+import { unfollowModal } from 'flavours/glitch/initial_state';
+
+const messages = defineMessages({
+  unfollowConfirm: { id: 'confirmations.unfollow.confirm', defaultMessage: 'Unfollow' },
+});
+
+const makeMapStateToProps = () => {
+  const getAccount = makeGetAccount();
+
+  const mapStateToProps = (state, props) => ({
+    account: getAccount(state, props.id),
+  });
+
+  return mapStateToProps;
+};
+
+const mapDispatchToProps = (dispatch, { intl }) => ({
+
+  onFollow (account) {
+    if (account.getIn(['relationship', 'following']) || account.getIn(['relationship', 'requested'])) {
+      if (unfollowModal) {
+        dispatch(openModal('CONFIRM', {
+          message: <FormattedMessage id='confirmations.unfollow.message' defaultMessage='Are you sure you want to unfollow {name}?' values={{ name: <strong>@{account.get('acct')}</strong> }} />,
+          confirm: intl.formatMessage(messages.unfollowConfirm),
+          onConfirm: () => dispatch(unfollowAccount(account.get('id'))),
+        }));
+      } else {
+        dispatch(unfollowAccount(account.get('id')));
+      }
+    } else {
+      dispatch(followAccount(account.get('id')));
+    }
+  },
+
+  onBlock (account) {
+    if (account.getIn(['relationship', 'blocking'])) {
+      dispatch(unblockAccount(account.get('id')));
+    } else {
+      dispatch(blockAccount(account.get('id')));
+    }
+  },
+
+  onMute (account) {
+    if (account.getIn(['relationship', 'muting'])) {
+      dispatch(unmuteAccount(account.get('id')));
+    } else {
+      dispatch(initMuteModal(account));
+    }
+  },
+
+
+  onMuteNotifications (account, notifications) {
+    dispatch(muteAccount(account.get('id'), notifications));
+  },
+});
+
+export default injectIntl(connect(makeMapStateToProps, mapDispatchToProps)(Account));
diff --git a/app/javascript/flavours/glitch/containers/admin_component.js b/app/javascript/flavours/glitch/containers/admin_component.js
new file mode 100644
index 000000000..64dabac8b
--- /dev/null
+++ b/app/javascript/flavours/glitch/containers/admin_component.js
@@ -0,0 +1,26 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { IntlProvider, addLocaleData } from 'react-intl';
+import { getLocale } from 'mastodon/locales';
+
+const { localeData, messages } = getLocale();
+addLocaleData(localeData);
+
+export default class AdminComponent extends React.PureComponent {
+
+  static propTypes = {
+    locale: PropTypes.string.isRequired,
+    children: PropTypes.node.isRequired,
+  };
+
+  render () {
+    const { locale, children } = this.props;
+
+    return (
+      <IntlProvider locale={locale} messages={messages}>
+        {children}
+      </IntlProvider>
+    );
+  }
+
+}
diff --git a/app/javascript/flavours/glitch/containers/compose_container.js b/app/javascript/flavours/glitch/containers/compose_container.js
new file mode 100644
index 000000000..1e49b89a0
--- /dev/null
+++ b/app/javascript/flavours/glitch/containers/compose_container.js
@@ -0,0 +1,41 @@
+import React from 'react';
+import { Provider } from 'react-redux';
+import PropTypes from 'prop-types';
+import configureStore from 'flavours/glitch/store/configureStore';
+import { hydrateStore } from 'flavours/glitch/actions/store';
+import { IntlProvider, addLocaleData } from 'react-intl';
+import { getLocale } from 'mastodon/locales';
+import Compose from 'flavours/glitch/features/standalone/compose';
+import initialState from 'flavours/glitch/initial_state';
+import { fetchCustomEmojis } from 'flavours/glitch/actions/custom_emojis';
+
+const { localeData, messages } = getLocale();
+addLocaleData(localeData);
+
+const store = configureStore();
+
+if (initialState) {
+  store.dispatch(hydrateStore(initialState));
+}
+
+store.dispatch(fetchCustomEmojis());
+
+export default class TimelineContainer extends React.PureComponent {
+
+  static propTypes = {
+    locale: PropTypes.string.isRequired,
+  };
+
+  render () {
+    const { locale } = this.props;
+
+    return (
+      <IntlProvider locale={locale} messages={messages}>
+        <Provider store={store}>
+          <Compose />
+        </Provider>
+      </IntlProvider>
+    );
+  }
+
+}
diff --git a/app/javascript/flavours/glitch/containers/domain_container.js b/app/javascript/flavours/glitch/containers/domain_container.js
new file mode 100644
index 000000000..e92e102ab
--- /dev/null
+++ b/app/javascript/flavours/glitch/containers/domain_container.js
@@ -0,0 +1,33 @@
+import React from 'react';
+import { connect } from 'react-redux';
+import { blockDomain, unblockDomain } from '../actions/domain_blocks';
+import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
+import Domain from '../components/domain';
+import { openModal } from '../actions/modal';
+
+const messages = defineMessages({
+  blockDomainConfirm: { id: 'confirmations.domain_block.confirm', defaultMessage: 'Block entire domain' },
+});
+
+const makeMapStateToProps = () => {
+  const mapStateToProps = (state, { }) => ({
+  });
+
+  return mapStateToProps;
+};
+
+const mapDispatchToProps = (dispatch, { intl }) => ({
+  onBlockDomain (domain) {
+    dispatch(openModal('CONFIRM', {
+      message: <FormattedMessage id='confirmations.domain_block.message' defaultMessage='Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable.' values={{ domain: <strong>{domain}</strong> }} />,
+      confirm: intl.formatMessage(messages.blockDomainConfirm),
+      onConfirm: () => dispatch(blockDomain(domain)),
+    }));
+  },
+
+  onUnblockDomain (domain) {
+    dispatch(unblockDomain(domain));
+  },
+});
+
+export default injectIntl(connect(makeMapStateToProps, mapDispatchToProps)(Domain));
diff --git a/app/javascript/flavours/glitch/containers/dropdown_menu_container.js b/app/javascript/flavours/glitch/containers/dropdown_menu_container.js
new file mode 100644
index 000000000..b2dff63db
--- /dev/null
+++ b/app/javascript/flavours/glitch/containers/dropdown_menu_container.js
@@ -0,0 +1,28 @@
+import { openDropdownMenu, closeDropdownMenu } from 'flavours/glitch/actions/dropdown_menu';
+import { openModal, closeModal } from 'flavours/glitch/actions/modal';
+import { connect } from 'react-redux';
+import DropdownMenu from 'flavours/glitch/components/dropdown_menu';
+import { isUserTouching } from '../is_mobile';
+
+const mapStateToProps = state => ({
+  dropdownPlacement: state.getIn(['dropdown_menu', 'placement']),
+  openDropdownId: state.getIn(['dropdown_menu', 'openId']),
+  openedViaKeyboard: state.getIn(['dropdown_menu', 'keyboard']),
+});
+
+const mapDispatchToProps = (dispatch, { status, items, scrollKey }) => ({
+  onOpen(id, onItemClick, dropdownPlacement, keyboard) {
+    dispatch(isUserTouching() ? openModal('ACTIONS', {
+      status,
+      actions: items,
+      onClick: onItemClick,
+    }) : openDropdownMenu(id, dropdownPlacement, keyboard, scrollKey));
+  },
+
+  onClose(id) {
+    dispatch(closeModal('ACTIONS'));
+    dispatch(closeDropdownMenu(id));
+  },
+});
+
+export default connect(mapStateToProps, mapDispatchToProps)(DropdownMenu);
diff --git a/app/javascript/flavours/glitch/containers/intersection_observer_article_container.js b/app/javascript/flavours/glitch/containers/intersection_observer_article_container.js
new file mode 100644
index 000000000..f2741f2d4
--- /dev/null
+++ b/app/javascript/flavours/glitch/containers/intersection_observer_article_container.js
@@ -0,0 +1,17 @@
+import { connect } from 'react-redux';
+import IntersectionObserverArticle from 'flavours/glitch/components/intersection_observer_article';
+import { setHeight } from 'flavours/glitch/actions/height_cache';
+
+const makeMapStateToProps = (state, props) => ({
+  cachedHeight: state.getIn(['height_cache', props.saveHeightKey, props.id]),
+});
+
+const mapDispatchToProps = (dispatch) => ({
+
+  onHeightChange (key, id, height) {
+    dispatch(setHeight(key, id, height));
+  },
+
+});
+
+export default connect(makeMapStateToProps, mapDispatchToProps)(IntersectionObserverArticle);
diff --git a/app/javascript/flavours/glitch/containers/mastodon.js b/app/javascript/flavours/glitch/containers/mastodon.js
new file mode 100644
index 000000000..6c92376d8
--- /dev/null
+++ b/app/javascript/flavours/glitch/containers/mastodon.js
@@ -0,0 +1,101 @@
+import PropTypes from 'prop-types';
+import React from 'react';
+import { Helmet } from 'react-helmet';
+import { IntlProvider, addLocaleData } from 'react-intl';
+import { Provider as ReduxProvider } from 'react-redux';
+import { BrowserRouter, Route } from 'react-router-dom';
+import { ScrollContext } from 'react-router-scroll-4';
+import configureStore from 'flavours/glitch/store/configureStore';
+import UI from 'flavours/glitch/features/ui';
+import { fetchCustomEmojis } from 'flavours/glitch/actions/custom_emojis';
+import { hydrateStore } from 'flavours/glitch/actions/store';
+import { checkDeprecatedLocalSettings } from 'flavours/glitch/actions/local_settings';
+import { connectUserStream } from 'flavours/glitch/actions/streaming';
+import ErrorBoundary from 'flavours/glitch/components/error_boundary';
+import initialState, { title as siteTitle } from 'flavours/glitch/initial_state';
+import { getLocale } from 'locales';
+
+const { localeData, messages } = getLocale();
+addLocaleData(localeData);
+
+const title = process.env.NODE_ENV === 'production' ? siteTitle : `${siteTitle} (Dev)`;
+
+export const store = configureStore();
+const hydrateAction = hydrateStore(initialState);
+store.dispatch(hydrateAction);
+
+// check for deprecated local settings
+store.dispatch(checkDeprecatedLocalSettings());
+
+// load custom emojis
+store.dispatch(fetchCustomEmojis());
+
+const createIdentityContext = state => ({
+  signedIn: !!state.meta.me,
+  accountId: state.meta.me,
+  disabledAccountId: state.meta.disabled_account_id,
+  accessToken: state.meta.access_token,
+  permissions: state.role ? state.role.permissions : 0,
+});
+
+export default class Mastodon extends React.PureComponent {
+
+  static propTypes = {
+    locale: PropTypes.string.isRequired,
+  };
+
+  static childContextTypes = {
+    identity: PropTypes.shape({
+      signedIn: PropTypes.bool.isRequired,
+      accountId: PropTypes.string,
+      disabledAccountId: PropTypes.string,
+      accessToken: PropTypes.string,
+    }).isRequired,
+  };
+
+  identity = createIdentityContext(initialState);
+
+  getChildContext() {
+    return {
+      identity: this.identity,
+    };
+  }
+
+  componentDidMount() {
+    if (this.identity.signedIn) {
+      this.disconnect = store.dispatch(connectUserStream());
+    }
+  }
+
+  componentWillUnmount () {
+    if (this.disconnect) {
+      this.disconnect();
+      this.disconnect = null;
+    }
+  }
+
+  shouldUpdateScroll (_, { location }) {
+    return !(location.state?.mastodonModalKey);
+  }
+
+  render () {
+    const { locale } = this.props;
+
+    return (
+      <IntlProvider locale={locale} messages={messages}>
+        <ReduxProvider store={store}>
+          <ErrorBoundary>
+            <BrowserRouter>
+              <ScrollContext shouldUpdateScroll={this.shouldUpdateScroll}>
+                <Route path='/' component={UI} />
+              </ScrollContext>
+            </BrowserRouter>
+
+            <Helmet defaultTitle={title} titleTemplate={`%s - ${title}`} />
+          </ErrorBoundary>
+        </ReduxProvider>
+      </IntlProvider>
+    );
+  }
+
+}
diff --git a/app/javascript/flavours/glitch/containers/media_container.js b/app/javascript/flavours/glitch/containers/media_container.js
new file mode 100644
index 000000000..f1e8534aa
--- /dev/null
+++ b/app/javascript/flavours/glitch/containers/media_container.js
@@ -0,0 +1,121 @@
+import React, { PureComponent, Fragment } from 'react';
+import ReactDOM from 'react-dom';
+import PropTypes from 'prop-types';
+import { IntlProvider, addLocaleData } from 'react-intl';
+import { fromJS } from 'immutable';
+import { getLocale } from 'mastodon/locales';
+import { getScrollbarWidth } from 'flavours/glitch/utils/scrollbar';
+import MediaGallery from 'flavours/glitch/components/media_gallery';
+import Poll from 'flavours/glitch/components/poll';
+import { ImmutableHashtag as Hashtag } from 'flavours/glitch/components/hashtag';
+import ModalRoot from 'flavours/glitch/components/modal_root';
+import MediaModal from 'flavours/glitch/features/ui/components/media_modal';
+import Video from 'flavours/glitch/features/video';
+import Card from 'flavours/glitch/features/status/components/card';
+import Audio from 'flavours/glitch/features/audio';
+
+const { localeData, messages } = getLocale();
+addLocaleData(localeData);
+
+const MEDIA_COMPONENTS = { MediaGallery, Video, Card, Poll, Hashtag, Audio };
+
+export default class MediaContainer extends PureComponent {
+
+  static propTypes = {
+    locale: PropTypes.string.isRequired,
+    components: PropTypes.object.isRequired,
+  };
+
+  state = {
+    media: null,
+    index: null,
+    time: null,
+    backgroundColor: null,
+    options: null,
+  };
+
+  handleOpenMedia = (media, index) => {
+    document.body.classList.add('with-modals--active');
+    document.documentElement.style.marginRight = `${getScrollbarWidth()}px`;
+
+    this.setState({ media, index });
+  }
+
+  handleOpenVideo = (options) => {
+    const { components } = this.props;
+    const { media } = JSON.parse(components[options.componentIndex].getAttribute('data-props'));
+    const mediaList = fromJS(media);
+
+    document.body.classList.add('with-modals--active');
+    document.documentElement.style.marginRight = `${getScrollbarWidth()}px`;
+
+    this.setState({ media: mediaList, options });
+  }
+
+  handleCloseMedia = () => {
+    document.body.classList.remove('with-modals--active');
+    document.documentElement.style.marginRight = 0;
+
+    this.setState({
+      media: null,
+      index: null,
+      time: null,
+      backgroundColor: null,
+      options: null,
+    });
+  }
+
+  setBackgroundColor = color => {
+    this.setState({ backgroundColor: color });
+  }
+
+  render () {
+    const { locale, components } = this.props;
+
+    return (
+      <IntlProvider locale={locale} messages={messages}>
+        <Fragment>
+          {[].map.call(components, (component, i) => {
+            const componentName = component.getAttribute('data-component');
+            const Component = MEDIA_COMPONENTS[componentName];
+            const { media, card, poll, hashtag, ...props } = JSON.parse(component.getAttribute('data-props'));
+
+            Object.assign(props, {
+              ...(media   ? { media:   fromJS(media)   } : {}),
+              ...(card    ? { card:    fromJS(card)    } : {}),
+              ...(poll    ? { poll:    fromJS(poll)    } : {}),
+              ...(hashtag ? { hashtag: fromJS(hashtag) } : {}),
+
+              ...(componentName === 'Video' ? {
+                componentIndex: i,
+                onOpenVideo: this.handleOpenVideo,
+              } : {
+                onOpenMedia: this.handleOpenMedia,
+              }),
+            });
+
+            return ReactDOM.createPortal(
+              <Component {...props} key={`media-${i}`} />,
+              component,
+            );
+          })}
+
+          <ModalRoot backgroundColor={this.state.backgroundColor} onClose={this.handleCloseMedia}>
+            {this.state.media && (
+              <MediaModal
+                media={this.state.media}
+                index={this.state.index || 0}
+                currentTime={this.state.options?.startTime}
+                autoPlay={this.state.options?.autoPlay}
+                volume={this.state.options?.defaultVolume}
+                onClose={this.handleCloseMedia}
+                onChangeBackgroundColor={this.setBackgroundColor}
+              />
+            )}
+          </ModalRoot>
+        </Fragment>
+      </IntlProvider>
+    );
+  }
+
+}
diff --git a/app/javascript/flavours/glitch/containers/notification_purge_buttons_container.js b/app/javascript/flavours/glitch/containers/notification_purge_buttons_container.js
new file mode 100644
index 000000000..2570cf4a5
--- /dev/null
+++ b/app/javascript/flavours/glitch/containers/notification_purge_buttons_container.js
@@ -0,0 +1,49 @@
+//  Package imports.
+import { connect } from 'react-redux';
+import { defineMessages, injectIntl } from 'react-intl';
+
+//  Our imports.
+import NotificationPurgeButtons from 'flavours/glitch/components/notification_purge_buttons';
+import {
+  deleteMarkedNotifications,
+  enterNotificationClearingMode,
+  markAllNotifications,
+} from 'flavours/glitch/actions/notifications';
+import { openModal } from 'flavours/glitch/actions/modal';
+
+const messages = defineMessages({
+  clearMessage: { id: 'notifications.marked_clear_confirmation', defaultMessage: 'Are you sure you want to permanently clear all selected notifications?' },
+  clearConfirm: { id: 'notifications.marked_clear', defaultMessage: 'Clear selected notifications' },
+});
+
+const mapDispatchToProps = (dispatch, { intl }) => ({
+  onEnterCleaningMode(yes) {
+    dispatch(enterNotificationClearingMode(yes));
+  },
+
+  onDeleteMarked() {
+    dispatch(openModal('CONFIRM', {
+      message: intl.formatMessage(messages.clearMessage),
+      confirm: intl.formatMessage(messages.clearConfirm),
+      onConfirm: () => dispatch(deleteMarkedNotifications()),
+    }));
+  },
+
+  onMarkAll() {
+    dispatch(markAllNotifications(true));
+  },
+
+  onMarkNone() {
+    dispatch(markAllNotifications(false));
+  },
+
+  onInvert() {
+    dispatch(markAllNotifications(null));
+  },
+});
+
+const mapStateToProps = state => ({
+  markNewForDelete: state.getIn(['notifications', 'markNewForDelete']),
+});
+
+export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(NotificationPurgeButtons));
diff --git a/app/javascript/flavours/glitch/containers/poll_container.js b/app/javascript/flavours/glitch/containers/poll_container.js
new file mode 100644
index 000000000..345351cc6
--- /dev/null
+++ b/app/javascript/flavours/glitch/containers/poll_container.js
@@ -0,0 +1,25 @@
+import { connect } from 'react-redux';
+import { debounce } from 'lodash';
+
+import Poll from 'flavours/glitch/components/poll';
+import { fetchPoll, vote } from 'flavours/glitch/actions/polls';
+
+const mapDispatchToProps = (dispatch, { pollId }) => ({
+  refresh: debounce(
+    () => {
+      dispatch(fetchPoll(pollId));
+    },
+    1000,
+    { leading: true },
+  ),
+
+  onVote (choices) {
+    dispatch(vote(pollId, choices));
+  },
+});
+
+const mapStateToProps = (state, { pollId }) => ({
+  poll: state.getIn(['polls', pollId]),
+});
+
+export default connect(mapStateToProps, mapDispatchToProps)(Poll);
diff --git a/app/javascript/flavours/glitch/containers/scroll_container.js b/app/javascript/flavours/glitch/containers/scroll_container.js
new file mode 100644
index 000000000..d21ff6368
--- /dev/null
+++ b/app/javascript/flavours/glitch/containers/scroll_container.js
@@ -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,
+  };
+
+}
diff --git a/app/javascript/flavours/glitch/containers/status_container.js b/app/javascript/flavours/glitch/containers/status_container.js
new file mode 100644
index 000000000..c12b2e614
--- /dev/null
+++ b/app/javascript/flavours/glitch/containers/status_container.js
@@ -0,0 +1,257 @@
+import { connect } from 'react-redux';
+import Status from 'flavours/glitch/components/status';
+import { List as ImmutableList } from 'immutable';
+import { makeGetStatus } from 'flavours/glitch/selectors';
+import {
+  replyCompose,
+  mentionCompose,
+  directCompose,
+} from 'flavours/glitch/actions/compose';
+import {
+  reblog,
+  favourite,
+  bookmark,
+  unreblog,
+  unfavourite,
+  unbookmark,
+  pin,
+  unpin,
+} from 'flavours/glitch/actions/interactions';
+import {
+  muteStatus,
+  unmuteStatus,
+  deleteStatus,
+  hideStatus,
+  revealStatus,
+  editStatus
+} from 'flavours/glitch/actions/statuses';
+import {
+  initAddFilter,
+} from 'flavours/glitch/actions/filters';
+import { initMuteModal } from 'flavours/glitch/actions/mutes';
+import { initBlockModal } from 'flavours/glitch/actions/blocks';
+import { initReport } from 'flavours/glitch/actions/reports';
+import { initBoostModal } from 'flavours/glitch/actions/boosts';
+import { openModal } from 'flavours/glitch/actions/modal';
+import { deployPictureInPicture } from 'flavours/glitch/actions/picture_in_picture';
+import { changeLocalSetting } from 'flavours/glitch/actions/local_settings';
+import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
+import { boostModal, favouriteModal, deleteModal } from 'flavours/glitch/initial_state';
+import { filterEditLink } from 'flavours/glitch/utils/backend_links';
+import { showAlertForError } from '../actions/alerts';
+import AccountContainer from 'flavours/glitch/containers/account_container';
+import Spoilers from '../components/spoilers';
+import Icon from 'flavours/glitch/components/icon';
+
+const messages = defineMessages({
+  deleteConfirm: { id: 'confirmations.delete.confirm', defaultMessage: 'Delete' },
+  deleteMessage: { id: 'confirmations.delete.message', defaultMessage: 'Are you sure you want to delete this status?' },
+  redraftConfirm: { id: 'confirmations.redraft.confirm', defaultMessage: 'Delete & redraft' },
+  redraftMessage: { id: 'confirmations.redraft.message', defaultMessage: 'Are you sure you want to delete this status and re-draft it? You will lose all replies, boosts and favourites to it.' },
+  replyConfirm: { id: 'confirmations.reply.confirm', defaultMessage: 'Reply' },
+  replyMessage: { id: 'confirmations.reply.message', defaultMessage: 'Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?' },
+  unfilterConfirm: { id: 'confirmations.unfilter.confirm', defaultMessage: 'Show' },
+  author: { id: 'confirmations.unfilter.author', defaultMessage: 'Author' },
+  matchingFilters: { id: 'confirmations.unfilter.filters', defaultMessage: 'Matching {count, plural, one {filter} other {filters}}' },
+  editFilter: { id: 'confirmations.unfilter.edit_filter', defaultMessage: 'Edit filter' },
+});
+
+const makeMapStateToProps = () => {
+  const getStatus = makeGetStatus();
+
+  const mapStateToProps = (state, props) => {
+
+    let status = getStatus(state, props);
+    let reblogStatus = status ? status.get('reblog', null) : null;
+    let account = undefined;
+    let prepend = undefined;
+
+    if (props.featured && status) {
+      account = status.get('account');
+      prepend = 'featured';
+    } else if (reblogStatus !== null && typeof reblogStatus === 'object') {
+      account = status.get('account');
+      status = reblogStatus;
+      prepend = 'reblogged_by';
+    }
+
+    return {
+      containerId: props.containerId || props.id,  //  Should match reblogStatus's id for reblogs
+      status: status,
+      account: account || props.account,
+      settings: state.get('local_settings'),
+      prepend: prepend || props.prepend,
+
+      pictureInPicture: {
+        inUse: state.getIn(['meta', 'layout']) !== 'mobile' && state.get('picture_in_picture').statusId === props.id,
+        available: state.getIn(['meta', 'layout']) !== 'mobile',
+      },
+    };
+  };
+
+  return mapStateToProps;
+};
+
+const mapDispatchToProps = (dispatch, { intl, contextType }) => ({
+
+  onReply (status, router) {
+    dispatch((_, getState) => {
+      let state = getState();
+
+      if (state.getIn(['local_settings', 'confirm_before_clearing_draft']) && state.getIn(['compose', 'text']).trim().length !== 0) {
+        dispatch(openModal('CONFIRM', {
+          message: intl.formatMessage(messages.replyMessage),
+          confirm: intl.formatMessage(messages.replyConfirm),
+          onDoNotAsk: () => dispatch(changeLocalSetting(['confirm_before_clearing_draft'], false)),
+          onConfirm: () => dispatch(replyCompose(status, router)),
+        }));
+      } else {
+        dispatch(replyCompose(status, router));
+      }
+    });
+  },
+
+  onModalReblog (status, privacy) {
+    if (status.get('reblogged')) {
+      dispatch(unreblog(status));
+    } else {
+      dispatch(reblog(status, privacy));
+    }
+  },
+
+  onReblog (status, e) {
+    dispatch((_, getState) => {
+      let state = getState();
+      if (state.getIn(['local_settings', 'confirm_boost_missing_media_description']) && status.get('media_attachments').some(item => !item.get('description')) && !status.get('reblogged')) {
+        dispatch(initBoostModal({ status, onReblog: this.onModalReblog, missingMediaDescription: true }));
+      } else if (e.shiftKey || !boostModal) {
+        this.onModalReblog(status);
+      } else {
+        dispatch(initBoostModal({ status, onReblog: this.onModalReblog }));
+      }
+    });
+  },
+
+  onBookmark (status) {
+    if (status.get('bookmarked')) {
+      dispatch(unbookmark(status));
+    } else {
+      dispatch(bookmark(status));
+    }
+  },
+
+  onModalFavourite (status) {
+    dispatch(favourite(status));
+  },
+
+  onFavourite (status, e) {
+    if (status.get('favourited')) {
+      dispatch(unfavourite(status));
+    } else {
+      if (e.shiftKey || !favouriteModal) {
+        this.onModalFavourite(status);
+      } else {
+        dispatch(openModal('FAVOURITE', { status, onFavourite: this.onModalFavourite }));
+      }
+    }
+  },
+
+  onPin (status) {
+    if (status.get('pinned')) {
+      dispatch(unpin(status));
+    } else {
+      dispatch(pin(status));
+    }
+  },
+
+  onEmbed (status) {
+    dispatch(openModal('EMBED', {
+      url: status.get('url'),
+      onError: error => dispatch(showAlertForError(error)),
+    }));
+  },
+
+  onDelete (status, history, withRedraft = false) {
+    if (!deleteModal) {
+      dispatch(deleteStatus(status.get('id'), history, withRedraft));
+    } else {
+      dispatch(openModal('CONFIRM', {
+        message: intl.formatMessage(withRedraft ? messages.redraftMessage : messages.deleteMessage),
+        confirm: intl.formatMessage(withRedraft ? messages.redraftConfirm : messages.deleteConfirm),
+        onConfirm: () => dispatch(deleteStatus(status.get('id'), history, withRedraft)),
+      }));
+    }
+  },
+
+  onEdit (status, history) {
+    dispatch(editStatus(status.get('id'), history));
+  },
+
+  onDirect (account, router) {
+    dispatch(directCompose(account, router));
+  },
+
+  onMention (account, router) {
+    dispatch(mentionCompose(account, router));
+  },
+
+  onOpenMedia (statusId, media, index) {
+    dispatch(openModal('MEDIA', { statusId, media, index }));
+  },
+
+  onOpenVideo (statusId, media, options) {
+    dispatch(openModal('VIDEO', { statusId, media, options }));
+  },
+
+  onBlock (status) {
+    const account = status.get('account');
+    dispatch(initBlockModal(account));
+  },
+
+  onReport (status) {
+    dispatch(initReport(status.get('account'), status));
+  },
+
+  onAddFilter (status) {
+    dispatch(initAddFilter(status, { contextType }));
+  },
+
+  onMute (account) {
+    dispatch(initMuteModal(account));
+  },
+
+  onMuteConversation (status) {
+    if (status.get('muted')) {
+      dispatch(unmuteStatus(status.get('id')));
+    } else {
+      dispatch(muteStatus(status.get('id')));
+    }
+  },
+
+  onToggleHidden (status) {
+    if (status.get('hidden')) {
+      dispatch(revealStatus(status.get('id')));
+    } else {
+      dispatch(hideStatus(status.get('id')));
+    }
+  },
+
+  deployPictureInPicture (status, type, mediaProps) {
+    dispatch((_, getState) => {
+      if (getState().getIn(['local_settings', 'media', 'pop_in_player'])) {
+        dispatch(deployPictureInPicture(status.get('id'), status.getIn(['account', 'id']), type, mediaProps));
+      }
+    });
+  },
+
+  onInteractionModal (type, status) {
+    dispatch(openModal('INTERACTION', {
+      type,
+      accountId: status.getIn(['account', 'id']),
+      url: status.get('url'),
+    }));
+  },
+
+});
+
+export default injectIntl(connect(makeMapStateToProps, mapDispatchToProps)(Status));