about summary refs log tree commit diff
path: root/app/assets/javascripts/components/features
diff options
context:
space:
mode:
authorEugen <eugen@zeonfederated.com>2017-03-15 22:55:22 +0100
committerGitHub <noreply@github.com>2017-03-15 22:55:22 +0100
commite245115f47082ffba27205f508301d14e792c369 (patch)
tree21a77b788dace45b734da6e64f1b0705016192f0 /app/assets/javascripts/components/features
parent620f70e42c16c324459ca2da52c68f1def8683de (diff)
parentc1124228e857b0e85f5bf927d2c41c7fadfdf955 (diff)
Merge branch 'master' into mastodon-site-api
Diffstat (limited to 'app/assets/javascripts/components/features')
-rw-r--r--app/assets/javascripts/components/features/account/components/action_bar.jsx45
-rw-r--r--app/assets/javascripts/components/features/account_timeline/components/header.jsx8
-rw-r--r--app/assets/javascripts/components/features/account_timeline/containers/header_container.jsx12
-rw-r--r--app/assets/javascripts/components/features/community_timeline/index.jsx16
-rw-r--r--app/assets/javascripts/components/features/compose/components/character_counter.jsx2
-rw-r--r--app/assets/javascripts/components/features/compose/components/compose_form.jsx32
-rw-r--r--app/assets/javascripts/components/features/compose/components/emoji_picker_dropdown.jsx52
-rw-r--r--app/assets/javascripts/components/features/compose/containers/compose_form_container.jsx10
-rw-r--r--app/assets/javascripts/components/features/getting_started/index.jsx3
-rw-r--r--app/assets/javascripts/components/features/notifications/components/clear_column_button.jsx3
-rw-r--r--app/assets/javascripts/components/features/notifications/index.jsx7
-rw-r--r--app/assets/javascripts/components/features/public_timeline/index.jsx16
-rw-r--r--app/assets/javascripts/components/features/status/components/action_bar.jsx9
-rw-r--r--app/assets/javascripts/components/features/status/components/detailed_status.jsx2
-rw-r--r--app/assets/javascripts/components/features/ui/containers/modal_container.jsx22
15 files changed, 189 insertions, 50 deletions
diff --git a/app/assets/javascripts/components/features/account/components/action_bar.jsx b/app/assets/javascripts/components/features/account/components/action_bar.jsx
index a2ab8172b..80a32d3e2 100644
--- a/app/assets/javascripts/components/features/account/components/action_bar.jsx
+++ b/app/assets/javascripts/components/features/account/components/action_bar.jsx
@@ -5,14 +5,16 @@ import { Link } from 'react-router';
 import { defineMessages, injectIntl, FormattedMessage, FormattedNumber } from 'react-intl';
 
 const messages = defineMessages({
-  mention: { id: 'account.mention', defaultMessage: 'Mention' },
+  mention: { id: 'account.mention', defaultMessage: 'Mention @{name}' },
   edit_profile: { id: 'account.edit_profile', defaultMessage: 'Edit profile' },
-  unblock: { id: 'account.unblock', defaultMessage: 'Unblock' },
+  unblock: { id: 'account.unblock', defaultMessage: 'Unblock @{name}' },
   unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
-  block: { id: 'account.block', defaultMessage: 'Block' },
+  unmute: { id: 'account.unmute', defaultMessage: 'Unmute @{name}' },
+  block: { id: 'account.block', defaultMessage: 'Block @{name}' },
+  mute: { id: 'account.mute', defaultMessage: 'Mute @{name}' },
   follow: { id: 'account.follow', defaultMessage: 'Follow' },
-  block: { id: 'account.block', defaultMessage: 'Block' },
-  report: { id: 'account.report', defaultMessage: 'Report' }
+  report: { id: 'account.report', defaultMessage: 'Report @{name}' },
+  disclaimer: { id: 'account.disclaimer', defaultMessage: 'This user is from another instance. This number may be larger.' }
 });
 
 const outerDropdownStyle = {
@@ -35,6 +37,7 @@ const ActionBar = React.createClass({
     onBlock: React.PropTypes.func.isRequired,
     onMention: React.PropTypes.func.isRequired,
     onReport: React.PropTypes.func.isRequired,
+    onMute: React.PropTypes.func.isRequired,
     intl: React.PropTypes.object.isRequired
   },
 
@@ -44,21 +47,31 @@ const ActionBar = React.createClass({
     const { account, me, intl } = this.props;
 
     let menu = [];
+    let extraInfo = '';
 
-    menu.push({ text: intl.formatMessage(messages.mention), action: this.props.onMention });
+    menu.push({ text: intl.formatMessage(messages.mention, { name: account.get('username') }), action: this.props.onMention });
+    menu.push(null);
 
     if (account.get('id') === me) {
       menu.push({ text: intl.formatMessage(messages.edit_profile), href: '/settings/profile' });
-    } else if (account.getIn(['relationship', 'blocking'])) {
-      menu.push({ text: intl.formatMessage(messages.unblock), action: this.props.onBlock });
-    } else if (account.getIn(['relationship', 'following'])) {
-      menu.push({ text: intl.formatMessage(messages.block), action: this.props.onBlock });
     } else {
-      menu.push({ text: intl.formatMessage(messages.block), action: this.props.onBlock });
+      if (account.getIn(['relationship', 'muting'])) {
+        menu.push({ text: intl.formatMessage(messages.unmute, { name: account.get('username') }), action: this.props.onMute });
+      } else {
+        menu.push({ text: intl.formatMessage(messages.mute, { name: account.get('username') }), action: this.props.onMute });
+      }
+
+      if (account.getIn(['relationship', 'blocking'])) {
+        menu.push({ text: intl.formatMessage(messages.unblock, { name: account.get('username') }), action: this.props.onBlock });
+      } else {
+        menu.push({ text: intl.formatMessage(messages.block, { name: account.get('username') }), action: this.props.onBlock });
+      }
+
+      menu.push({ text: intl.formatMessage(messages.report, { name: account.get('username') }), action: this.props.onReport });
     }
 
-    if (account.get('id') !== me) {
-      menu.push({ text: intl.formatMessage(messages.report), action: this.props.onReport });
+    if (account.get('acct') !== account.get('username')) {
+      extraInfo = <abbr title={intl.formatMessage(messages.disclaimer)}>*</abbr>;
     }
 
     return (
@@ -70,17 +83,17 @@ const ActionBar = React.createClass({
         <div style={outerLinksStyle}>
           <Link className='account__action-bar__tab' to={`/accounts/${account.get('id')}`}>
             <span><FormattedMessage id='account.posts' defaultMessage='Posts' /></span>
-            <strong><FormattedNumber value={account.get('statuses_count')} /></strong>
+            <strong><FormattedNumber value={account.get('statuses_count')} /> {extraInfo}</strong>
           </Link>
 
           <Link className='account__action-bar__tab' to={`/accounts/${account.get('id')}/following`}>
             <span><FormattedMessage id='account.follows' defaultMessage='Follows' /></span>
-            <strong><FormattedNumber value={account.get('following_count')} /></strong>
+            <strong><FormattedNumber value={account.get('following_count')} /> {extraInfo}</strong>
           </Link>
 
           <Link className='account__action-bar__tab' to={`/accounts/${account.get('id')}/followers`}>
             <span><FormattedMessage id='account.followers' defaultMessage='Followers' /></span>
-            <strong><FormattedNumber value={account.get('followers_count')} /></strong>
+            <strong><FormattedNumber value={account.get('followers_count')} /> {extraInfo}</strong>
           </Link>
         </div>
       </div>
diff --git a/app/assets/javascripts/components/features/account_timeline/components/header.jsx b/app/assets/javascripts/components/features/account_timeline/components/header.jsx
index 2dd3ca7b1..99a10562e 100644
--- a/app/assets/javascripts/components/features/account_timeline/components/header.jsx
+++ b/app/assets/javascripts/components/features/account_timeline/components/header.jsx
@@ -15,7 +15,8 @@ const Header = React.createClass({
     onFollow: React.PropTypes.func.isRequired,
     onBlock: React.PropTypes.func.isRequired,
     onMention: React.PropTypes.func.isRequired,
-    onReport: React.PropTypes.func.isRequired
+    onReport: React.PropTypes.func.isRequired,
+    onMute: React.PropTypes.func.isRequired
   },
 
   mixins: [PureRenderMixin],
@@ -37,6 +38,10 @@ const Header = React.createClass({
     this.context.router.push('/report');
   },
 
+  handleMute() {
+    this.props.onMute(this.props.account);
+  },
+
   render () {
     const { account, me } = this.props;
 
@@ -58,6 +63,7 @@ const Header = React.createClass({
           onBlock={this.handleBlock}
           onMention={this.handleMention}
           onReport={this.handleReport}
+          onMute={this.handleMute}
         />
       </div>
     );
diff --git a/app/assets/javascripts/components/features/account_timeline/containers/header_container.jsx b/app/assets/javascripts/components/features/account_timeline/containers/header_container.jsx
index e4ce905fe..8472d25a5 100644
--- a/app/assets/javascripts/components/features/account_timeline/containers/header_container.jsx
+++ b/app/assets/javascripts/components/features/account_timeline/containers/header_container.jsx
@@ -5,7 +5,9 @@ import {
   followAccount,
   unfollowAccount,
   blockAccount,
-  unblockAccount
+  unblockAccount,
+  muteAccount,
+  unmuteAccount
 } from '../../../actions/accounts';
 import { mentionCompose } from '../../../actions/compose';
 import { initReport } from '../../../actions/reports';
@@ -44,6 +46,14 @@ const mapDispatchToProps = dispatch => ({
 
   onReport (account) {
     dispatch(initReport(account));
+  },
+
+  onMute (account) {
+    if (account.getIn(['relationship', 'muting'])) {
+      dispatch(unmuteAccount(account.get('id')));
+    } else {
+      dispatch(muteAccount(account.get('id')));
+    }
   }
 });
 
diff --git a/app/assets/javascripts/components/features/community_timeline/index.jsx b/app/assets/javascripts/components/features/community_timeline/index.jsx
index aa1b8368e..2cfd7b2fe 100644
--- a/app/assets/javascripts/components/features/community_timeline/index.jsx
+++ b/app/assets/javascripts/components/features/community_timeline/index.jsx
@@ -20,6 +20,8 @@ const mapStateToProps = state => ({
   accessToken: state.getIn(['meta', 'access_token'])
 });
 
+let subscription;
+
 const CommunityTimeline = React.createClass({
 
   propTypes: {
@@ -36,7 +38,11 @@ const CommunityTimeline = React.createClass({
 
     dispatch(refreshTimeline('community'));
 
-    this.subscription = createStream(accessToken, 'public:local', {
+    if (typeof subscription !== 'undefined') {
+      return;
+    }
+
+    subscription = createStream(accessToken, 'public:local', {
 
       received (data) {
         switch(data.event) {
@@ -53,10 +59,10 @@ const CommunityTimeline = React.createClass({
   },
 
   componentWillUnmount () {
-    if (typeof this.subscription !== 'undefined') {
-      this.subscription.close();
-      this.subscription = null;
-    }
+    // if (typeof subscription !== 'undefined') {
+    //   subscription.close();
+    //   subscription = null;
+    // }
   },
 
   render () {
diff --git a/app/assets/javascripts/components/features/compose/components/character_counter.jsx b/app/assets/javascripts/components/features/compose/components/character_counter.jsx
index f0c1b7c8d..e6b675354 100644
--- a/app/assets/javascripts/components/features/compose/components/character_counter.jsx
+++ b/app/assets/javascripts/components/features/compose/components/character_counter.jsx
@@ -10,7 +10,7 @@ const CharacterCounter = React.createClass({
   mixins: [PureRenderMixin],
 
   render () {
-    const diff = this.props.max - this.props.text.length;
+    const diff = this.props.max - this.props.text.replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g, "_").length;
 
     return (
       <span style={{ fontSize: '16px', cursor: 'default' }}>
diff --git a/app/assets/javascripts/components/features/compose/components/compose_form.jsx b/app/assets/javascripts/components/features/compose/components/compose_form.jsx
index 31ae8e034..047c974f2 100644
--- a/app/assets/javascripts/components/features/compose/components/compose_form.jsx
+++ b/app/assets/javascripts/components/features/compose/components/compose_form.jsx
@@ -15,6 +15,7 @@ import UnlistedToggleContainer from '../containers/unlisted_toggle_container';
 import SpoilerToggleContainer from '../containers/spoiler_toggle_container';
 import PrivateToggleContainer from '../containers/private_toggle_container';
 import SensitiveToggleContainer from '../containers/sensitive_toggle_container';
+import EmojiPickerDropdown from './emoji_picker_dropdown';
 
 const messages = defineMessages({
   placeholder: { id: 'compose_form.placeholder', defaultMessage: 'What is on your mind?' },
@@ -47,6 +48,8 @@ const ComposeForm = React.createClass({
     onFetchSuggestions: React.PropTypes.func.isRequired,
     onSuggestionSelected: React.PropTypes.func.isRequired,
     onChangeSpoilerText: React.PropTypes.func.isRequired,
+    onPaste: React.PropTypes.func.isRequired,
+    onPickEmoji: React.PropTypes.func.isRequired
   },
 
   mixins: [PureRenderMixin],
@@ -75,6 +78,7 @@ const ComposeForm = React.createClass({
   },
 
   onSuggestionSelected (tokenStart, token, value) {
+    this._restoreCaret = null;
     this.props.onSuggestionSelected(tokenStart, token, value);
   },
 
@@ -87,8 +91,18 @@ const ComposeForm = React.createClass({
       // If replying to zero or one users, places the cursor at the end of the textbox.
       // If replying to more than one user, selects any usernames past the first;
       // this provides a convenient shortcut to drop everyone else from the conversation.
-      const selectionEnd   = this.props.text.length;
-      const selectionStart = (this.props.preselectDate !== prevProps.preselectDate) ? (this.props.text.search(/\s/) + 1) : selectionEnd;
+      let selectionEnd, selectionStart;
+
+      if (this.props.preselectDate !== prevProps.preselectDate) {
+        selectionEnd   = this.props.text.length;
+        selectionStart = this.props.text.search(/\s/) + 1;
+      } else if (typeof this._restoreCaret === 'number') {
+        selectionStart = this._restoreCaret;
+        selectionEnd   = this._restoreCaret;
+      } else {
+        selectionEnd   = this.props.text.length;
+        selectionStart = selectionEnd;
+      }
 
       this.autosuggestTextarea.textarea.setSelectionRange(selectionStart, selectionEnd);
       this.autosuggestTextarea.textarea.focus();
@@ -99,8 +113,14 @@ const ComposeForm = React.createClass({
     this.autosuggestTextarea = c;
   },
 
+  handleEmojiPick (data) {
+    const position     = this.autosuggestTextarea.textarea.selectionStart;
+    this._restoreCaret = position + data.shortname.length + 1;
+    this.props.onPickEmoji(position, data);
+  },
+
   render () {
-    const { intl, needsPrivacyWarning, mentionedDomains } = this.props;
+    const { intl, needsPrivacyWarning, mentionedDomains, onPaste } = this.props;
     const disabled = this.props.is_submitting || this.props.is_uploading;
 
     let publishText    = '';
@@ -149,12 +169,16 @@ const ComposeForm = React.createClass({
           onSuggestionsFetchRequested={this.onSuggestionsFetchRequested}
           onSuggestionsClearRequested={this.onSuggestionsClearRequested}
           onSuggestionSelected={this.onSuggestionSelected}
+          onPaste={onPaste}
         />
 
         <div style={{ marginTop: '10px', overflow: 'hidden' }}>
           <div style={{ float: 'right' }}><Button text={publishText} onClick={this.handleSubmit} disabled={disabled} /></div>
           <div style={{ float: 'right', marginRight: '16px', lineHeight: '36px' }}><CharacterCounter max={500} text={[this.props.spoiler_text, this.props.text].join('')} /></div>
-          <UploadButtonContainer style={{ paddingTop: '4px' }} />
+          <div style={{ display: 'flex', paddingTop: '4px' }}>
+            <UploadButtonContainer />
+            <EmojiPickerDropdown onPickEmoji={this.handleEmojiPick} />
+          </div>
         </div>
 
         <SpoilerToggleContainer />
diff --git a/app/assets/javascripts/components/features/compose/components/emoji_picker_dropdown.jsx b/app/assets/javascripts/components/features/compose/components/emoji_picker_dropdown.jsx
new file mode 100644
index 000000000..3a454a5fb
--- /dev/null
+++ b/app/assets/javascripts/components/features/compose/components/emoji_picker_dropdown.jsx
@@ -0,0 +1,52 @@
+import Dropdown, { DropdownTrigger, DropdownContent } from 'react-simple-dropdown';
+import EmojiPicker from 'emojione-picker';
+import PureRenderMixin from 'react-addons-pure-render-mixin';
+import { defineMessages, injectIntl } from 'react-intl';
+
+const messages = defineMessages({
+  emoji: { id: 'emoji_button.label', defaultMessage: 'Emoji' }
+});
+
+const settings = {
+  imageType: 'png',
+  sprites: false,
+  imagePathPNG: '/emoji/'
+};
+
+const EmojiPickerDropdown = React.createClass({
+
+  propTypes: {
+    intl: React.PropTypes.object.isRequired,
+    onPickEmoji: React.PropTypes.func.isRequired
+  },
+
+  mixins: [PureRenderMixin],
+
+  setRef (c) {
+    this.dropdown = c;
+  },
+
+  handleChange (data) {
+    this.dropdown.hide();
+    this.props.onPickEmoji(data);
+  },
+
+  render () {
+    const { intl } = this.props;
+
+    return (
+      <Dropdown ref={this.setRef} style={{ marginLeft: '5px' }}>
+        <DropdownTrigger className='icon-button' title={intl.formatMessage(messages.emoji)} style={{ fontSize: `24px`, width: `24px`, lineHeight: `24px`, display: 'block', marginLeft: '2px' }}>
+          <i className={`fa fa-smile-o`} style={{ verticalAlign: 'middle' }} />
+        </DropdownTrigger>
+
+        <DropdownContent>
+          <EmojiPicker emojione={settings} onChange={this.handleChange} />
+        </DropdownContent>
+      </Dropdown>
+    );
+  }
+
+});
+
+export default injectIntl(EmojiPickerDropdown);
diff --git a/app/assets/javascripts/components/features/compose/containers/compose_form_container.jsx b/app/assets/javascripts/components/features/compose/containers/compose_form_container.jsx
index 53129af6e..a67adbdd6 100644
--- a/app/assets/javascripts/components/features/compose/containers/compose_form_container.jsx
+++ b/app/assets/javascripts/components/features/compose/containers/compose_form_container.jsx
@@ -1,5 +1,6 @@
 import { connect } from 'react-redux';
 import ComposeForm from '../components/compose_form';
+import { uploadCompose } from '../../../actions/compose';
 import { createSelector } from 'reselect';
 import {
   changeCompose,
@@ -8,6 +9,7 @@ import {
   fetchComposeSuggestions,
   selectComposeSuggestion,
   changeComposeSpoilerText,
+  insertEmojiCompose
 } from '../../../actions/compose';
 
 const getMentionedUsernames = createSelector(state => state.getIn(['compose', 'text']), text => text.match(/(?:^|[^\/\w])@([a-z0-9_]+@[a-z0-9\.\-]+)/ig));
@@ -65,6 +67,14 @@ const mapDispatchToProps = (dispatch) => ({
     dispatch(changeComposeSpoilerText(checked));
   },
 
+  onPaste (files) {
+    dispatch(uploadCompose(files));
+  },
+
+  onPickEmoji (position, data) {
+    dispatch(insertEmojiCompose(position, data));
+  },
+
 });
 
 export default connect(mapStateToProps, mapDispatchToProps)(ComposeForm);
diff --git a/app/assets/javascripts/components/features/getting_started/index.jsx b/app/assets/javascripts/components/features/getting_started/index.jsx
index f8433b8f4..48b4a6b8e 100644
--- a/app/assets/javascripts/components/features/getting_started/index.jsx
+++ b/app/assets/javascripts/components/features/getting_started/index.jsx
@@ -45,8 +45,7 @@ const GettingStarted = ({ intl, me }) => {
         <div className='static-content getting-started'>
           <p><FormattedMessage id='getting_started.about_addressing' defaultMessage='You can follow people if you know their username and the domain they are on by entering an e-mail-esque address into the form at the top of the sidebar.' /></p>
           <p><FormattedMessage id='getting_started.about_shortcuts' defaultMessage='If the target user is on the same domain as you, just the username will work. The same rule applies to mentioning people in statuses.' /></p>
-          <p><FormattedMessage id='getting_started.about_developer' defaultMessage='The developer of this project can be followed as Gargron@mastodon.social' /></p>
-          <p><FormattedMessage id='getting_started.open_source_notice' defaultMessage='Mastodon is open source software. You can contribute or report issues on github at {github}' values={{ github: <a href="https://github.com/tootsuite/mastodon" target="_blank">tootsuite/mastodon</a> }} /></p>
+          <p><FormattedMessage id='getting_started.open_source_notice' defaultMessage='Mastodon is open source software. You can contribute or report issues on github at {github}. Various apps are available.' values={{ github: <a href="https://github.com/tootsuite/mastodon" target="_blank">tootsuite/mastodon</a> }} /></p>
         </div>
       </div>
     </Column>
diff --git a/app/assets/javascripts/components/features/notifications/components/clear_column_button.jsx b/app/assets/javascripts/components/features/notifications/components/clear_column_button.jsx
index 0b7c737c6..d75149a0e 100644
--- a/app/assets/javascripts/components/features/notifications/components/clear_column_button.jsx
+++ b/app/assets/javascripts/components/features/notifications/components/clear_column_button.jsx
@@ -4,7 +4,8 @@ const iconStyle = {
   position: 'absolute',
   right: '48px',
   top: '0',
-  cursor: 'pointer'
+  cursor: 'pointer',
+  zIndex: '2'
 };
 
 const ClearColumnButton = ({ onClick }) => (
diff --git a/app/assets/javascripts/components/features/notifications/index.jsx b/app/assets/javascripts/components/features/notifications/index.jsx
index 0da3544f6..74b914ffd 100644
--- a/app/assets/javascripts/components/features/notifications/index.jsx
+++ b/app/assets/javascripts/components/features/notifications/index.jsx
@@ -13,7 +13,8 @@ import LoadMore from '../../components/load_more';
 import ClearColumnButton from './components/clear_column_button';
 
 const messages = defineMessages({
-  title: { id: 'column.notifications', defaultMessage: 'Notifications' }
+  title: { id: 'column.notifications', defaultMessage: 'Notifications' },
+  confirm: { id: 'notifications.clear_confirmation', defaultMessage: 'Are you sure you want to clear all your notifications?' }
 });
 
 const getNotifications = createSelector([
@@ -72,7 +73,9 @@ const Notifications = React.createClass({
   },
 
   handleClear () {
-    this.props.dispatch(clearNotifications());
+    if (window.confirm(this.props.intl.formatMessage(messages.confirm))) {
+      this.props.dispatch(clearNotifications());
+    }
   },
 
   setRef (c) {
diff --git a/app/assets/javascripts/components/features/public_timeline/index.jsx b/app/assets/javascripts/components/features/public_timeline/index.jsx
index ce4eacc92..b2342abbd 100644
--- a/app/assets/javascripts/components/features/public_timeline/index.jsx
+++ b/app/assets/javascripts/components/features/public_timeline/index.jsx
@@ -20,6 +20,8 @@ const mapStateToProps = state => ({
   accessToken: state.getIn(['meta', 'access_token'])
 });
 
+let subscription;
+
 const PublicTimeline = React.createClass({
 
   propTypes: {
@@ -36,7 +38,11 @@ const PublicTimeline = React.createClass({
 
     dispatch(refreshTimeline('public'));
 
-    this.subscription = createStream(accessToken, 'public', {
+    if (typeof subscription !== 'undefined') {
+      return;
+    }
+
+    subscription = createStream(accessToken, 'public', {
 
       received (data) {
         switch(data.event) {
@@ -53,10 +59,10 @@ const PublicTimeline = React.createClass({
   },
 
   componentWillUnmount () {
-    if (typeof this.subscription !== 'undefined') {
-      this.subscription.close();
-      this.subscription = null;
-    }
+    // if (typeof subscription !== 'undefined') {
+    //   subscription.close();
+    //   subscription = null;
+    // }
   },
 
   render () {
diff --git a/app/assets/javascripts/components/features/status/components/action_bar.jsx b/app/assets/javascripts/components/features/status/components/action_bar.jsx
index cc4d5cca4..2acf94274 100644
--- a/app/assets/javascripts/components/features/status/components/action_bar.jsx
+++ b/app/assets/javascripts/components/features/status/components/action_bar.jsx
@@ -6,11 +6,11 @@ import { defineMessages, injectIntl } from 'react-intl';
 
 const messages = defineMessages({
   delete: { id: 'status.delete', defaultMessage: 'Delete' },
-  mention: { id: 'status.mention', defaultMessage: 'Mention' },
+  mention: { id: 'status.mention', defaultMessage: 'Mention @{name}' },
   reply: { id: 'status.reply', defaultMessage: 'Reply' },
   reblog: { id: 'status.reblog', defaultMessage: 'Reblog' },
   favourite: { id: 'status.favourite', defaultMessage: 'Favourite' },
-  report: { id: 'status.report', defaultMessage: 'Report' }
+  report: { id: 'status.report', defaultMessage: 'Report @{name}' }
 });
 
 const ActionBar = React.createClass({
@@ -66,8 +66,9 @@ const ActionBar = React.createClass({
     if (me === status.getIn(['account', 'id'])) {
       menu.push({ text: intl.formatMessage(messages.delete), action: this.handleDeleteClick });
     } else {
-      menu.push({ text: intl.formatMessage(messages.mention), action: this.handleMentionClick });
-      menu.push({ text: intl.formatMessage(messages.report), action: this.handleReport });
+      menu.push({ text: intl.formatMessage(messages.mention, { name: status.getIn(['account', 'username']) }), action: this.handleMentionClick });
+      menu.push(null);
+      menu.push({ text: intl.formatMessage(messages.report, { name: status.getIn(['account', 'username']) }), action: this.handleReport });
     }
 
     return (
diff --git a/app/assets/javascripts/components/features/status/components/detailed_status.jsx b/app/assets/javascripts/components/features/status/components/detailed_status.jsx
index 8a7c0c5d5..caa46ff3c 100644
--- a/app/assets/javascripts/components/features/status/components/detailed_status.jsx
+++ b/app/assets/javascripts/components/features/status/components/detailed_status.jsx
@@ -39,7 +39,7 @@ const DetailedStatus = React.createClass({
 
     if (status.get('media_attachments').size > 0) {
       if (status.getIn(['media_attachments', 0, 'type']) === 'video') {
-        media = <VideoPlayer sensitive={status.get('sensitive')} media={status.getIn(['media_attachments', 0])} width={317} height={178} />;
+        media = <VideoPlayer sensitive={status.get('sensitive')} media={status.getIn(['media_attachments', 0])} width={300} height={150} autoplay />;
       } else {
         media = <MediaGallery sensitive={status.get('sensitive')} media={status.get('media_attachments')} height={300} onOpenMedia={this.props.onOpenMedia} />;
       }
diff --git a/app/assets/javascripts/components/features/ui/containers/modal_container.jsx b/app/assets/javascripts/components/features/ui/containers/modal_container.jsx
index d8301b20f..e3c4281b9 100644
--- a/app/assets/javascripts/components/features/ui/containers/modal_container.jsx
+++ b/app/assets/javascripts/components/features/ui/containers/modal_container.jsx
@@ -9,6 +9,7 @@ import ImageLoader from 'react-imageloader';
 import LoadingIndicator from '../../../components/loading_indicator';
 import PureRenderMixin from 'react-addons-pure-render-mixin';
 import ImmutablePropTypes from 'react-immutable-proptypes';
+import ExtendedVideoPlayer from '../../../components/extended_video_player';
 
 const mapStateToProps = state => ({
   media: state.getIn(['modal', 'media']),
@@ -131,27 +132,34 @@ const Modal = React.createClass({
       return null;
     }
 
-    const url = media.get(index).get('url');
+    const attachment = media.get(index);
+    const url = attachment.get('url');
 
-    let leftNav, rightNav;
+    let leftNav, rightNav, content;
 
-    leftNav = rightNav = '';
+    leftNav = rightNav = content = '';
 
     if (media.size > 1) {
       leftNav  = <div style={leftNavStyle} className='modal-container--nav' onClick={this.handlePrevClick}><i className='fa fa-fw fa-chevron-left' /></div>;
       rightNav = <div style={rightNavStyle} className='modal-container--nav' onClick={this.handleNextClick}><i className='fa fa-fw fa-chevron-right' /></div>;
     }
 
-    return (
-      <Lightbox {...other}>
-        {leftNav}
-
+    if (attachment.get('type') === 'image') {
+      content = (
         <ImageLoader
           src={url}
           preloader={preloader}
           imgProps={{ style: imageStyle }}
         />
+      );
+    } else if (attachment.get('type') === 'gifv') {
+      content = <ExtendedVideoPlayer src={url} />;
+    }
 
+    return (
+      <Lightbox {...other}>
+        {leftNav}
+        {content}
         {rightNav}
       </Lightbox>
     );