about summary refs log tree commit diff
path: root/app/javascript/flavours/glitch/features
diff options
context:
space:
mode:
Diffstat (limited to 'app/javascript/flavours/glitch/features')
-rw-r--r--app/javascript/flavours/glitch/features/account/components/profile_column_header.js29
-rw-r--r--app/javascript/flavours/glitch/features/account_gallery/index.js4
-rw-r--r--app/javascript/flavours/glitch/features/account_timeline/index.js4
-rw-r--r--app/javascript/flavours/glitch/features/composer/index.js52
-rw-r--r--app/javascript/flavours/glitch/features/composer/options/index.js38
-rw-r--r--app/javascript/flavours/glitch/features/composer/poll_form/components/poll_form.js135
-rw-r--r--app/javascript/flavours/glitch/features/composer/poll_form/index.js29
-rw-r--r--app/javascript/flavours/glitch/features/followers/index.js4
-rw-r--r--app/javascript/flavours/glitch/features/following/index.js4
-rw-r--r--app/javascript/flavours/glitch/features/home_timeline/index.js2
-rw-r--r--app/javascript/flavours/glitch/features/status/components/card.js2
-rw-r--r--app/javascript/flavours/glitch/features/status/components/detailed_status.js5
-rw-r--r--app/javascript/flavours/glitch/features/status/index.js3
13 files changed, 281 insertions, 30 deletions
diff --git a/app/javascript/flavours/glitch/features/account/components/profile_column_header.js b/app/javascript/flavours/glitch/features/account/components/profile_column_header.js
new file mode 100644
index 000000000..1a6abef37
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/account/components/profile_column_header.js
@@ -0,0 +1,29 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import ColumnHeader from '../../../components/column_header';
+import { injectIntl, defineMessages } from 'react-intl';
+
+const messages = defineMessages({
+  profile: { id: 'column_header.profile', defaultMessage: 'Profile' },
+});
+
+export default @injectIntl
+class ProfileColumnHeader extends React.PureComponent {
+
+  static propTypes = {
+    intl: PropTypes.object.isRequired,
+  };
+
+  render() {
+    const { intl } = this.props;
+
+    return (
+      <ColumnHeader
+        icon='user-circle'
+        title={intl.formatMessage(messages.profile)}
+        showBackButton
+      />
+    );
+  }
+
+}
diff --git a/app/javascript/flavours/glitch/features/account_gallery/index.js b/app/javascript/flavours/glitch/features/account_gallery/index.js
index a5fa01444..a9ea5088e 100644
--- a/app/javascript/flavours/glitch/features/account_gallery/index.js
+++ b/app/javascript/flavours/glitch/features/account_gallery/index.js
@@ -6,7 +6,7 @@ import { fetchAccount } from 'flavours/glitch/actions/accounts';
 import { expandAccountMediaTimeline } from 'flavours/glitch/actions/timelines';
 import LoadingIndicator from 'flavours/glitch/components/loading_indicator';
 import Column from 'flavours/glitch/features/ui/components/column';
-import ColumnBackButton from 'flavours/glitch/components/column_back_button';
+import ProfileColumnHeader from 'flavours/glitch/features/account/components/profile_column_header';
 import ImmutablePureComponent from 'react-immutable-pure-component';
 import { getAccountGallery } from 'flavours/glitch/selectors';
 import MediaItem from './components/media_item';
@@ -113,7 +113,7 @@ export default class AccountGallery extends ImmutablePureComponent {
 
     return (
       <Column>
-        <ColumnBackButton />
+        <ProfileColumnHeader />
 
         <ScrollContainer scrollKey='account_gallery' shouldUpdateScroll={this.shouldUpdateScroll}>
           <div className='scrollable scrollable--flex' onScroll={this.handleScroll}>
diff --git a/app/javascript/flavours/glitch/features/account_timeline/index.js b/app/javascript/flavours/glitch/features/account_timeline/index.js
index 6f887a145..415e3be20 100644
--- a/app/javascript/flavours/glitch/features/account_timeline/index.js
+++ b/app/javascript/flavours/glitch/features/account_timeline/index.js
@@ -7,8 +7,8 @@ import { expandAccountFeaturedTimeline, expandAccountTimeline } from 'flavours/g
 import StatusList from '../../components/status_list';
 import LoadingIndicator from '../../components/loading_indicator';
 import Column from '../ui/components/column';
+import ProfileColumnHeader from 'flavours/glitch/features/account/components/profile_column_header';
 import HeaderContainer from './containers/header_container';
-import ColumnBackButton from '../../components/column_back_button';
 import { List as ImmutableList } from 'immutable';
 import ImmutablePureComponent from 'react-immutable-pure-component';
 import { FormattedMessage } from 'react-intl';
@@ -74,7 +74,7 @@ export default class AccountTimeline extends ImmutablePureComponent {
 
     return (
       <Column name='account'>
-        <ColumnBackButton />
+        <ProfileColumnHeader />
 
         <StatusList
           prepend={<HeaderContainer accountId={this.props.params.accountId} />}
diff --git a/app/javascript/flavours/glitch/features/composer/index.js b/app/javascript/flavours/glitch/features/composer/index.js
index ec0e405a4..9d2e0b3da 100644
--- a/app/javascript/flavours/glitch/features/composer/index.js
+++ b/app/javascript/flavours/glitch/features/composer/index.js
@@ -31,6 +31,7 @@ import {
   openModal,
 } from 'flavours/glitch/actions/modal';
 import { changeLocalSetting } from 'flavours/glitch/actions/local_settings';
+import { addPoll, removePoll } from 'flavours/glitch/actions/compose';
 
 //  Components.
 import ComposerOptions from './options';
@@ -39,6 +40,7 @@ import ComposerReply from './reply';
 import ComposerSpoiler from './spoiler';
 import ComposerTextarea from './textarea';
 import ComposerUploadForm from './upload_form';
+import ComposerPollForm from './poll_form';
 import ComposerWarning from './warning';
 import ComposerHashtagWarning from './hashtag_warning';
 import ComposerDirectWarning from './direct_warning';
@@ -102,6 +104,7 @@ function mapStateToProps (state) {
     suggestions: state.getIn(['compose', 'suggestions']),
     text: state.getIn(['compose', 'text']),
     anyMedia: state.getIn(['compose', 'media_attachments']).size > 0,
+    poll: state.getIn(['compose', 'poll']),
     spoilersAlwaysOn: spoilersAlwaysOn,
     mediaDescriptionConfirmation: state.getIn(['local_settings', 'confirm_missing_media_description']),
     preselectOnReply: state.getIn(['local_settings', 'preselect_on_reply']),
@@ -134,6 +137,15 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
   onChangeVisibility(value) {
     dispatch(changeComposeVisibility(value));
   },
+  onTogglePoll() {
+    dispatch((_, getState) => {
+      if (getState().getIn(['compose', 'poll'])) {
+        dispatch(removePoll());
+      } else {
+        dispatch(addPoll());
+      }
+    });
+  },
   onClearSuggestions() {
     dispatch(clearComposeSuggestions());
   },
@@ -394,6 +406,7 @@ class Composer extends React.Component {
       isUploading,
       layout,
       media,
+      poll,
       onCancelReply,
       onChangeAdvancedOption,
       onChangeDescription,
@@ -401,6 +414,7 @@ class Composer extends React.Component {
       onChangeSpoilerness,
       onChangeText,
       onChangeVisibility,
+      onTogglePoll,
       onClearSuggestions,
       onCloseModal,
       onFetchSuggestions,
@@ -463,30 +477,38 @@ class Composer extends React.Component {
           suggestions={suggestions}
           value={text}
         />
-        {isUploading || media && media.size ? (
-          <ComposerUploadForm
-            intl={intl}
-            media={media}
-            onChangeDescription={onChangeDescription}
-            onOpenFocalPointModal={onOpenFocalPointModal}
-            onRemove={onUndoUpload}
-            progress={progress}
-            uploading={isUploading}
-            handleRef={handleRefUploadForm}
-          />
-        ) : null}
+        <div className='compose-form__modifiers'>
+          {isUploading || media && media.size ? (
+            <ComposerUploadForm
+              intl={intl}
+              media={media}
+              onChangeDescription={onChangeDescription}
+              onOpenFocalPointModal={onOpenFocalPointModal}
+              onRemove={onUndoUpload}
+              progress={progress}
+              uploading={isUploading}
+              handleRef={handleRefUploadForm}
+            />
+          ) : null}
+          {!!poll && (
+            <ComposerPollForm />
+          )}
+        </div>
         <ComposerOptions
           acceptContentTypes={acceptContentTypes}
           advancedOptions={advancedOptions}
           disabled={isSubmitting}
-          full={media ? media.size >= 4 || media.some(
-            item => item.get('type') === 'video'
-          ) : false}
+          allowMedia={!poll && (media ? media.size < 4 && !media.some(
+              item => item.get('type') === 'video'
+            ) : true)}
           hasMedia={media && !!media.size}
+          allowPoll={!(media && !!media.size)}
+          hasPoll={!!poll}
           intl={intl}
           onChangeAdvancedOption={onChangeAdvancedOption}
           onChangeSensitivity={onChangeSensitivity}
           onChangeVisibility={onChangeVisibility}
+          onTogglePoll={onTogglePoll}
           onDoodleOpen={onOpenDoodleModal}
           onModalClose={onCloseModal}
           onModalOpen={onOpenActionsModal}
diff --git a/app/javascript/flavours/glitch/features/composer/options/index.js b/app/javascript/flavours/glitch/features/composer/options/index.js
index 5b4a7444c..7c7f01dc2 100644
--- a/app/javascript/flavours/glitch/features/composer/options/index.js
+++ b/app/javascript/flavours/glitch/features/composer/options/index.js
@@ -19,6 +19,7 @@ import {
   assignHandlers,
   hiddenComponent,
 } from 'flavours/glitch/util/react_helpers';
+import { pollLimits } from 'flavours/glitch/util/initial_state';
 
 //  Messages.
 const messages = defineMessages({
@@ -98,6 +99,14 @@ const messages = defineMessages({
     defaultMessage: 'Upload a file',
     id: 'compose.attach.upload',
   },
+  add_poll: {
+    defaultMessage: 'Add a poll',
+    id: 'poll_button.add_poll',
+  },
+  remove_poll: {
+    defaultMessage: 'Remove poll',
+    id: 'poll_button.remove_poll',
+  },
 });
 
 //  Handlers.
@@ -160,12 +169,15 @@ export default class ComposerOptions extends React.PureComponent {
       acceptContentTypes,
       advancedOptions,
       disabled,
-      full,
+      allowMedia,
       hasMedia,
+      allowPoll,
+      hasPoll,
       intl,
       onChangeAdvancedOption,
       onChangeSensitivity,
       onChangeVisibility,
+      onTogglePoll,
       onModalClose,
       onModalOpen,
       onToggleSpoiler,
@@ -209,7 +221,7 @@ export default class ComposerOptions extends React.PureComponent {
       <div className='composer--options'>
         <input
           accept={acceptContentTypes}
-          disabled={disabled || full}
+          disabled={disabled || !allowMedia}
           key={resetFileKey}
           onChange={handleChangeFiles}
           ref={handleRefFileElement}
@@ -218,7 +230,7 @@ export default class ComposerOptions extends React.PureComponent {
           {...hiddenComponent}
         />
         <Dropdown
-          disabled={disabled || full}
+          disabled={disabled || !allowMedia}
           icon='paperclip'
           items={[
             {
@@ -237,6 +249,21 @@ export default class ComposerOptions extends React.PureComponent {
           onModalOpen={onModalOpen}
           title={intl.formatMessage(messages.attach)}
         />
+        {!!pollLimits && (
+          <IconButton
+            active={hasPoll}
+            disabled={disabled || !allowPoll}
+            icon='tasks'
+            inverted
+            onClick={onTogglePoll}
+            size={18}
+            style={{
+              height: null,
+              lineHeight: null,
+            }}
+            title={intl.formatMessage(hasPoll ? messages.remove_poll : messages.add_poll)}
+          />
+        )}
         <Motion
           defaultStyle={{ scale: 0.87 }}
           style={{
@@ -329,12 +356,15 @@ ComposerOptions.propTypes = {
   acceptContentTypes: PropTypes.string,
   advancedOptions: ImmutablePropTypes.map,
   disabled: PropTypes.bool,
-  full: PropTypes.bool,
+  allowMedia: PropTypes.bool,
   hasMedia: PropTypes.bool,
+  allowPoll: PropTypes.bool,
+  hasPoll: PropTypes.bool,
   intl: PropTypes.object.isRequired,
   onChangeAdvancedOption: PropTypes.func,
   onChangeSensitivity: PropTypes.func,
   onChangeVisibility: PropTypes.func,
+  onTogglePoll: PropTypes.func,
   onDoodleOpen: PropTypes.func,
   onModalClose: PropTypes.func,
   onModalOpen: PropTypes.func,
diff --git a/app/javascript/flavours/glitch/features/composer/poll_form/components/poll_form.js b/app/javascript/flavours/glitch/features/composer/poll_form/components/poll_form.js
new file mode 100644
index 000000000..7ee28e304
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/composer/poll_form/components/poll_form.js
@@ -0,0 +1,135 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
+import IconButton from 'flavours/glitch/components/icon_button';
+import Icon from 'flavours/glitch/components/icon';
+import classNames from 'classnames';
+import { pollLimits } from 'flavours/glitch/util/initial_state';
+
+const messages = defineMessages({
+  option_placeholder: { id: 'compose_form.poll.option_placeholder', defaultMessage: 'Choice {number}' },
+  add_option: { id: 'compose_form.poll.add_option', defaultMessage: 'Add a choice' },
+  remove_option: { id: 'compose_form.poll.remove_option', defaultMessage: 'Remove this choice' },
+  poll_duration: { id: 'compose_form.poll.duration', defaultMessage: 'Poll duration' },
+  single_choice: { id: 'compose_form.poll.single_choice', defaultMessage: 'Allow one choice' },
+  multiple_choices: { id: 'compose_form.poll.multiple_choices', defaultMessage: 'Allow multiple choices' },
+  minutes: { id: 'intervals.full.minutes', defaultMessage: '{number, plural, one {# minute} other {# minutes}}' },
+  hours: { id: 'intervals.full.hours', defaultMessage: '{number, plural, one {# hour} other {# hours}}' },
+  days: { id: 'intervals.full.days', defaultMessage: '{number, plural, one {# day} other {# days}}' },
+});
+
+@injectIntl
+class Option extends React.PureComponent {
+
+  static propTypes = {
+    title: PropTypes.string.isRequired,
+    index: PropTypes.number.isRequired,
+    isPollMultiple: PropTypes.bool,
+    onChange: PropTypes.func.isRequired,
+    onRemove: PropTypes.func.isRequired,
+    intl: PropTypes.object.isRequired,
+  };
+
+  handleOptionTitleChange = e => {
+    this.props.onChange(this.props.index, e.target.value);
+  };
+
+  handleOptionRemove = () => {
+    this.props.onRemove(this.props.index);
+  };
+
+  render () {
+    const { isPollMultiple, title, index, intl } = this.props;
+
+    return (
+      <li>
+        <label className='poll__text editable'>
+          <span className={classNames('poll__input', { checkbox: isPollMultiple })} />
+
+          <input
+            type='text'
+            placeholder={intl.formatMessage(messages.option_placeholder, { number: index + 1 })}
+            maxlength={pollLimits.max_option_chars}
+            value={title}
+            onChange={this.handleOptionTitleChange}
+          />
+        </label>
+
+        <div className='poll__cancel'>
+          <IconButton disabled={index <= 1} title={intl.formatMessage(messages.remove_option)} icon='times' onClick={this.handleOptionRemove} />
+        </div>
+      </li>
+    );
+  }
+
+}
+
+export default
+@injectIntl
+class PollForm extends ImmutablePureComponent {
+
+  static propTypes = {
+    options: ImmutablePropTypes.list,
+    expiresIn: PropTypes.number,
+    isMultiple: PropTypes.bool,
+    onChangeOption: PropTypes.func.isRequired,
+    onAddOption: PropTypes.func.isRequired,
+    onRemoveOption: PropTypes.func.isRequired,
+    onChangeSettings: PropTypes.func.isRequired,
+    intl: PropTypes.object.isRequired,
+  };
+
+  handleAddOption = () => {
+    this.props.onAddOption('');
+  };
+
+  handleSelectDuration = e => {
+    this.props.onChangeSettings(e.target.value, this.props.isMultiple);
+  };
+
+  handleSelectMultiple = e => {
+    this.props.onChangeSettings(this.props.expiresIn, e.target.value === 'true');
+  };
+
+  render () {
+    const { options, expiresIn, isMultiple, onChangeOption, onRemoveOption, intl } = this.props;
+
+    if (!options) {
+      return null;
+    }
+
+    return (
+      <div className='compose-form__poll-wrapper'>
+        <ul>
+          {options.map((title, i) => <Option title={title} key={i} index={i} onChange={onChangeOption} onRemove={onRemoveOption} isPollMultiple={isMultiple} />)}
+          {options.size < pollLimits.max_options && (
+            <label className='poll__text editable'>
+              <span className={classNames('poll__input')} style={{ opacity: 0 }} />
+              <button className='button button-secondary' onClick={this.handleAddOption}><Icon icon='plus' /> <FormattedMessage {...messages.add_option} /></button>
+            </label>
+          )}
+        </ul>
+
+        <div className='poll__footer'>
+          <select value={isMultiple ? 'true' : 'false'} onChange={this.handleSelectMultiple}>
+            <option value='false'>{intl.formatMessage(messages.single_choice)}</option>
+            <option value='true'>{intl.formatMessage(messages.multiple_choices)}</option>
+          </select>
+
+          <select value={expiresIn} onChange={this.handleSelectDuration}>
+            <option value={300}>{intl.formatMessage(messages.minutes, { number: 5 })}</option>
+            <option value={1800}>{intl.formatMessage(messages.minutes, { number: 30 })}</option>
+            <option value={3600}>{intl.formatMessage(messages.hours, { number: 1 })}</option>
+            <option value={21600}>{intl.formatMessage(messages.hours, { number: 6 })}</option>
+            <option value={86400}>{intl.formatMessage(messages.days, { number: 1 })}</option>
+            <option value={259200}>{intl.formatMessage(messages.days, { number: 3 })}</option>
+            <option value={604800}>{intl.formatMessage(messages.days, { number: 7 })}</option>
+          </select>
+        </div>
+      </div>
+    );
+  }
+
+}
diff --git a/app/javascript/flavours/glitch/features/composer/poll_form/index.js b/app/javascript/flavours/glitch/features/composer/poll_form/index.js
new file mode 100644
index 000000000..5232c3b31
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/composer/poll_form/index.js
@@ -0,0 +1,29 @@
+import { connect } from 'react-redux';
+import PollForm from './components/poll_form';
+import { addPollOption, removePollOption, changePollOption, changePollSettings } from '../../../actions/compose';
+
+const mapStateToProps = state => ({
+  options: state.getIn(['compose', 'poll', 'options']),
+  expiresIn: state.getIn(['compose', 'poll', 'expires_in']),
+  isMultiple: state.getIn(['compose', 'poll', 'multiple']),
+});
+
+const mapDispatchToProps = dispatch => ({
+  onAddOption(title) {
+    dispatch(addPollOption(title));
+  },
+
+  onRemoveOption(index) {
+    dispatch(removePollOption(index));
+  },
+
+  onChangeOption(index, title) {
+    dispatch(changePollOption(index, title));
+  },
+
+  onChangeSettings(expiresIn, isMultiple) {
+    dispatch(changePollSettings(expiresIn, isMultiple));
+  },
+});
+
+export default connect(mapStateToProps, mapDispatchToProps)(PollForm);
diff --git a/app/javascript/flavours/glitch/features/followers/index.js b/app/javascript/flavours/glitch/features/followers/index.js
index a977142ed..124004cb6 100644
--- a/app/javascript/flavours/glitch/features/followers/index.js
+++ b/app/javascript/flavours/glitch/features/followers/index.js
@@ -11,9 +11,9 @@ import {
 import { ScrollContainer } from 'react-router-scroll-4';
 import AccountContainer from 'flavours/glitch/containers/account_container';
 import Column from 'flavours/glitch/features/ui/components/column';
+import ProfileColumnHeader from 'flavours/glitch/features/account/components/profile_column_header';
 import HeaderContainer from 'flavours/glitch/features/account_timeline/containers/header_container';
 import LoadMore from 'flavours/glitch/components/load_more';
-import ColumnBackButton from 'flavours/glitch/components/column_back_button';
 import ImmutablePureComponent from 'react-immutable-pure-component';
 
 const mapStateToProps = (state, props) => ({
@@ -80,7 +80,7 @@ export default class Followers extends ImmutablePureComponent {
 
     return (
       <Column>
-        <ColumnBackButton />
+        <ProfileColumnHeader />
 
         <ScrollContainer scrollKey='followers' shouldUpdateScroll={this.shouldUpdateScroll}>
           <div className='scrollable' onScroll={this.handleScroll}>
diff --git a/app/javascript/flavours/glitch/features/following/index.js b/app/javascript/flavours/glitch/features/following/index.js
index 70aeefaad..656100dad 100644
--- a/app/javascript/flavours/glitch/features/following/index.js
+++ b/app/javascript/flavours/glitch/features/following/index.js
@@ -11,9 +11,9 @@ import {
 import { ScrollContainer } from 'react-router-scroll-4';
 import AccountContainer from 'flavours/glitch/containers/account_container';
 import Column from 'flavours/glitch/features/ui/components/column';
+import ProfileColumnHeader from 'flavours/glitch/features/account/components/profile_column_header';
 import HeaderContainer from 'flavours/glitch/features/account_timeline/containers/header_container';
 import LoadMore from 'flavours/glitch/components/load_more';
-import ColumnBackButton from 'flavours/glitch/components/column_back_button';
 import ImmutablePureComponent from 'react-immutable-pure-component';
 
 const mapStateToProps = (state, props) => ({
@@ -80,7 +80,7 @@ export default class Following extends ImmutablePureComponent {
 
     return (
       <Column>
-        <ColumnBackButton />
+        <ProfileColumnHeader />
 
         <ScrollContainer scrollKey='following' shouldUpdateScroll={this.shouldUpdateScroll}>
           <div className='scrollable' onScroll={this.handleScroll}>
diff --git a/app/javascript/flavours/glitch/features/home_timeline/index.js b/app/javascript/flavours/glitch/features/home_timeline/index.js
index 7d124ba01..8eb79fa60 100644
--- a/app/javascript/flavours/glitch/features/home_timeline/index.js
+++ b/app/javascript/flavours/glitch/features/home_timeline/index.js
@@ -16,7 +16,7 @@ const messages = defineMessages({
 
 const mapStateToProps = state => ({
   hasUnread: state.getIn(['timelines', 'home', 'unread']) > 0,
-  isPartial: state.getIn(['timelines', 'home', 'items', 0], null) === null,
+  isPartial: state.getIn(['timelines', 'home', 'isPartial']),
 });
 
 @connect(mapStateToProps)
diff --git a/app/javascript/flavours/glitch/features/status/components/card.js b/app/javascript/flavours/glitch/features/status/components/card.js
index e405a5ef0..f974a87a1 100644
--- a/app/javascript/flavours/glitch/features/status/components/card.js
+++ b/app/javascript/flavours/glitch/features/status/components/card.js
@@ -75,7 +75,7 @@ export default class Card extends React.PureComponent {
   };
 
   componentWillReceiveProps (nextProps) {
-    if (this.props.card !== nextProps.card) {
+    if (!Immutable.is(this.props.card, nextProps.card)) {
       this.setState({ embedded: false });
     }
   }
diff --git a/app/javascript/flavours/glitch/features/status/components/detailed_status.js b/app/javascript/flavours/glitch/features/status/components/detailed_status.js
index 120ae6817..ad60320ef 100644
--- a/app/javascript/flavours/glitch/features/status/components/detailed_status.js
+++ b/app/javascript/flavours/glitch/features/status/components/detailed_status.js
@@ -14,6 +14,7 @@ import Video from 'flavours/glitch/features/video';
 import VisibilityIcon from 'flavours/glitch/components/status_visibility_icon';
 import scheduleIdleTask from 'flavours/glitch/util/schedule_idle_task';
 import classNames from 'classnames';
+import PollContainer from 'flavours/glitch/containers/poll_container';
 
 export default class DetailedStatus extends ImmutablePureComponent {
 
@@ -118,7 +119,9 @@ export default class DetailedStatus extends ImmutablePureComponent {
       outerStyle.height = `${this.state.height}px`;
     }
 
-    if (status.get('media_attachments').size > 0) {
+    if (status.get('poll')) {
+      media = <PollContainer pollId={status.get('poll')} />;
+    } else if (status.get('media_attachments').size > 0) {
       if (status.get('media_attachments').some(item => item.get('type') === 'unknown')) {
         media = <AttachmentList media={status.get('media_attachments')} />;
       } else if (status.getIn(['media_attachments', 0, 'type']) === 'video') {
diff --git a/app/javascript/flavours/glitch/features/status/index.js b/app/javascript/flavours/glitch/features/status/index.js
index 86c4db283..880372de5 100644
--- a/app/javascript/flavours/glitch/features/status/index.js
+++ b/app/javascript/flavours/glitch/features/status/index.js
@@ -54,6 +54,7 @@ const messages = defineMessages({
   detailedStatus: { id: 'status.detailed_status', defaultMessage: 'Detailed conversation view' },
   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?' },
+  tootHeading: { id: 'column.toot', defaultMessage: 'Toots and replies' },
 });
 
 const makeMapStateToProps = () => {
@@ -453,6 +454,8 @@ export default class Status extends ImmutablePureComponent {
     return (
       <Column label={intl.formatMessage(messages.detailedStatus)}>
         <ColumnHeader
+          icon='comment'
+          title={intl.formatMessage(messages.tootHeading)}
           showBackButton
           extraButton={(
             <button className='column-header__button' title={intl.formatMessage(!isExpanded ? messages.revealAll : messages.hideAll)} aria-label={intl.formatMessage(!isExpanded ? messages.revealAll : messages.hideAll)} onClick={this.handleToggleAll} aria-pressed={!isExpanded ? 'false' : 'true'}><i className={`fa fa-${!isExpanded ? 'eye-slash' : 'eye'}`} /></button>