Merge tag 'v2.6.0rc1' into instance_only_statuses

This commit is contained in:
Renato "Lond" Cerqueira
2018-10-23 08:32:55 +02:00
570 changed files with 11506 additions and 5693 deletions

View File

@ -2,7 +2,7 @@ import React from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types';
import DropdownMenuContainer from '../../../containers/dropdown_menu_container';
import { Link } from 'react-router-dom';
import { NavLink } from 'react-router-dom';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import { me } from '../../../initial_state';
import { shortNumberFormat } from '../../../utils/numbers';
@ -36,8 +36,8 @@ const messages = defineMessages({
unendorse: { id: 'account.unendorse', defaultMessage: 'Don\'t feature on profile' },
});
@injectIntl
export default class ActionBar extends React.PureComponent {
export default @injectIntl
class ActionBar extends React.PureComponent {
static propTypes = {
account: ImmutablePropTypes.map.isRequired,
@ -60,6 +60,13 @@ export default class ActionBar extends React.PureComponent {
});
}
isStatusesPageActive = (match, location) => {
if (!match) {
return false;
}
return !location.pathname.match(/\/(followers|following)\/?$/);
}
render () {
const { account, intl } = this.props;
@ -147,20 +154,20 @@ export default class ActionBar extends React.PureComponent {
<div className='account__action-bar'>
<div className='account__action-bar-links'>
<Link className='account__action-bar__tab' to={`/accounts/${account.get('id')}`} title={intl.formatNumber(account.get('statuses_count'))}>
<NavLink isActive={this.isStatusesPageActive} activeClassName='active' className='account__action-bar__tab' to={`/accounts/${account.get('id')}`} title={intl.formatNumber(account.get('statuses_count'))}>
<FormattedMessage id='account.posts' defaultMessage='Toots' />
<strong>{shortNumberFormat(account.get('statuses_count'))}</strong>
</Link>
</NavLink>
<Link className='account__action-bar__tab' to={`/accounts/${account.get('id')}/following`} title={intl.formatNumber(account.get('following_count'))}>
<NavLink exact activeClassName='active' className='account__action-bar__tab' to={`/accounts/${account.get('id')}/following`} title={intl.formatNumber(account.get('following_count'))}>
<FormattedMessage id='account.follows' defaultMessage='Follows' />
<strong>{shortNumberFormat(account.get('following_count'))}</strong>
</Link>
</NavLink>
<Link className='account__action-bar__tab' to={`/accounts/${account.get('id')}/followers`} title={intl.formatNumber(account.get('followers_count'))}>
<NavLink exact activeClassName='active' className='account__action-bar__tab' to={`/accounts/${account.get('id')}/followers`} title={intl.formatNumber(account.get('followers_count'))}>
<FormattedMessage id='account.followers' defaultMessage='Followers' />
<strong>{shortNumberFormat(account.get('followers_count'))}</strong>
</Link>
</NavLink>
</div>
<div className='account__action-bar-dropdown'>

View File

@ -15,8 +15,18 @@ const messages = defineMessages({
requested: { id: 'account.requested', defaultMessage: 'Awaiting approval. Click to cancel follow request' },
unblock: { id: 'account.unblock', defaultMessage: 'Unblock @{name}' },
edit_profile: { id: 'account.edit_profile', defaultMessage: 'Edit profile' },
linkVerifiedOn: { id: 'account.link_verified_on', defaultMessage: 'Ownership of this link was checked on {date}' },
});
const dateFormatOptions = {
month: 'short',
day: 'numeric',
year: 'numeric',
hour12: false,
hour: '2-digit',
minute: '2-digit',
};
class Avatar extends ImmutablePureComponent {
static propTypes = {
@ -65,8 +75,8 @@ class Avatar extends ImmutablePureComponent {
}
@injectIntl
export default class Header extends ImmutablePureComponent {
export default @injectIntl
class Header extends ImmutablePureComponent {
static propTypes = {
account: ImmutablePropTypes.map,
@ -163,7 +173,10 @@ export default class Header extends ImmutablePureComponent {
{fields.map((pair, i) => (
<dl key={i}>
<dt dangerouslySetInnerHTML={{ __html: pair.get('name_emojified') }} title={pair.get('name')} />
<dd dangerouslySetInnerHTML={{ __html: pair.get('value_emojified') }} title={pair.get('value_plain')} />
<dd className={pair.get('verified_at') && 'verified'} title={pair.get('value_plain')}>
{pair.get('verified_at') && <span title={intl.formatMessage(messages.linkVerifiedOn, { date: intl.formatDate(pair.get('verified_at'), dateFormatOptions) })}><i className='fa fa-check verified__mark' /></span>} <span dangerouslySetInnerHTML={{ __html: pair.get('value_emojified') }} />
</dd>
</dl>
))}
</div>

View File

@ -2,7 +2,7 @@ import React from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
import Permalink from '../../../components/permalink';
import { displaySensitiveMedia } from '../../../initial_state';
import { displayMedia } from '../../../initial_state';
export default class MediaItem extends ImmutablePureComponent {
@ -11,7 +11,7 @@ export default class MediaItem extends ImmutablePureComponent {
};
state = {
visible: !this.props.media.getIn(['status', 'sensitive']) || displaySensitiveMedia,
visible: displayMedia !== 'hide_all' && !this.props.media.getIn(['status', 'sensitive']) || displayMedia === 'show_all',
};
handleClick = () => {

View File

@ -43,8 +43,8 @@ class LoadMoreMedia extends ImmutablePureComponent {
}
@connect(mapStateToProps)
export default class AccountGallery extends ImmutablePureComponent {
export default @connect(mapStateToProps)
class AccountGallery extends ImmutablePureComponent {
static propTypes = {
params: PropTypes.object.isRequired,

View File

@ -23,8 +23,8 @@ const mapStateToProps = (state, { params: { accountId }, withReplies = false })
};
};
@connect(mapStateToProps)
export default class AccountTimeline extends ImmutablePureComponent {
export default @connect(mapStateToProps)
class AccountTimeline extends ImmutablePureComponent {
static propTypes = {
params: PropTypes.object.isRequired,

View File

@ -20,9 +20,9 @@ const mapStateToProps = state => ({
accountIds: state.getIn(['user_lists', 'blocks', 'items']),
});
@connect(mapStateToProps)
export default @connect(mapStateToProps)
@injectIntl
export default class Blocks extends ImmutablePureComponent {
class Blocks extends ImmutablePureComponent {
static propTypes = {
params: PropTypes.object.isRequired,

View File

@ -4,8 +4,8 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
import { injectIntl, FormattedMessage } from 'react-intl';
import SettingToggle from '../../notifications/components/setting_toggle';
@injectIntl
export default class ColumnSettings extends React.PureComponent {
export default @injectIntl
class ColumnSettings extends React.PureComponent {
static propTypes = {
settings: ImmutablePropTypes.map.isRequired,

View File

@ -25,9 +25,9 @@ const mapStateToProps = (state, { onlyMedia, columnId }) => {
};
};
@connect(mapStateToProps)
export default @connect(mapStateToProps)
@injectIntl
export default class CommunityTimeline extends React.PureComponent {
class CommunityTimeline extends React.PureComponent {
static contextTypes = {
router: PropTypes.object,

View File

@ -17,8 +17,8 @@ const messages = defineMessages({
filters: { id: 'navigation_bar.filters', defaultMessage: 'Muted words' },
});
@injectIntl
export default class ActionBar extends React.PureComponent {
export default @injectIntl
class ActionBar extends React.PureComponent {
static propTypes = {
account: ImmutablePropTypes.map.isRequired,

View File

@ -28,8 +28,12 @@ const messages = defineMessages({
publishLoud: { id: 'compose_form.publish_loud', defaultMessage: '{publish}!' },
});
@injectIntl
export default class ComposeForm extends ImmutablePureComponent {
export default @injectIntl
class ComposeForm extends ImmutablePureComponent {
static contextTypes = {
router: PropTypes.object,
};
static propTypes = {
intl: PropTypes.object.isRequired,
@ -86,7 +90,7 @@ export default class ComposeForm extends ImmutablePureComponent {
return;
}
this.props.onSubmit();
this.props.onSubmit(this.context.router.history);
}
onSuggestionsClearRequested = () => {

View File

@ -261,6 +261,7 @@ class EmojiPickerMenu extends React.PureComponent {
skin={skinTone}
showPreview={false}
backgroundImageFn={backgroundImageFn}
autoFocus
emojiTooltip
/>
@ -277,8 +278,8 @@ class EmojiPickerMenu extends React.PureComponent {
}
@injectIntl
export default class EmojiPickerDropdown extends React.PureComponent {
export default @injectIntl
class EmojiPickerDropdown extends React.PureComponent {
static propTypes = {
custom_emojis: ImmutablePropTypes.list,

View File

@ -149,8 +149,8 @@ class PrivacyDropdownMenu extends React.PureComponent {
}
@injectIntl
export default class PrivacyDropdown extends React.PureComponent {
export default @injectIntl
class PrivacyDropdown extends React.PureComponent {
static propTypes = {
isUserTouching: PropTypes.func,
@ -164,7 +164,7 @@ export default class PrivacyDropdown extends React.PureComponent {
state = {
open: false,
placement: null,
placement: 'bottom',
};
handleToggle = ({ target }) => {

View File

@ -12,8 +12,8 @@ const messages = defineMessages({
cancel: { id: 'reply_indicator.cancel', defaultMessage: 'Cancel' },
});
@injectIntl
export default class ReplyIndicator extends ImmutablePureComponent {
export default @injectIntl
class ReplyIndicator extends ImmutablePureComponent {
static contextTypes = {
router: PropTypes.object,

View File

@ -43,8 +43,8 @@ class SearchPopout extends React.PureComponent {
}
@injectIntl
export default class Search extends React.PureComponent {
export default @injectIntl
class Search extends React.PureComponent {
static propTypes = {
value: PropTypes.string.isRequired,

View File

@ -11,8 +11,12 @@ const messages = defineMessages({
description: { id: 'upload_form.description', defaultMessage: 'Describe for the visually impaired' },
});
@injectIntl
export default class Upload extends ImmutablePureComponent {
export default @injectIntl
class Upload extends ImmutablePureComponent {
static contextTypes = {
router: PropTypes.object,
};
static propTypes = {
media: ImmutablePropTypes.map.isRequired,
@ -37,7 +41,7 @@ export default class Upload extends ImmutablePureComponent {
handleSubmit = () => {
this.handleInputBlur();
this.props.onSubmit();
this.props.onSubmit(this.context.router.history);
}
handleUndoClick = () => {

View File

@ -23,9 +23,9 @@ const iconStyle = {
lineHeight: '27px',
};
@connect(makeMapStateToProps)
export default @connect(makeMapStateToProps)
@injectIntl
export default class UploadButton extends ImmutablePureComponent {
class UploadButton extends ImmutablePureComponent {
static propTypes = {
disabled: PropTypes.bool,

View File

@ -34,8 +34,8 @@ const mapDispatchToProps = (dispatch) => ({
dispatch(changeCompose(text));
},
onSubmit () {
dispatch(submitCompose());
onSubmit (router) {
dispatch(submitCompose(router));
},
onClearSuggestions () {

View File

@ -22,8 +22,8 @@ const mapDispatchToProps = dispatch => ({
dispatch(openModal('FOCAL_POINT', { id }));
},
onSubmit () {
dispatch(submitCompose());
onSubmit (router) {
dispatch(submitCompose(router));
},
});

View File

@ -13,6 +13,7 @@ import spring from 'react-motion/lib/spring';
import SearchResultsContainer from './containers/search_results_container';
import { changeComposing } from '../../actions/compose';
import elephantUIPlane from '../../../images/elephant_ui_plane.svg';
import { mascot } from '../../initial_state';
const messages = defineMessages({
start: { id: 'getting_started.heading', defaultMessage: 'Getting started' },
@ -30,9 +31,9 @@ const mapStateToProps = (state, ownProps) => ({
showSearch: ownProps.multiColumn ? state.getIn(['search', 'submitted']) && !state.getIn(['search', 'hidden']) : ownProps.isSearchPage,
});
@connect(mapStateToProps)
export default @connect(mapStateToProps)
@injectIntl
export default class Compose extends React.PureComponent {
class Compose extends React.PureComponent {
static propTypes = {
dispatch: PropTypes.func.isRequired,
@ -107,7 +108,7 @@ export default class Compose extends React.PureComponent {
<ComposeFormContainer />
{multiColumn && (
<div className='drawer__inner__mastodon'>
<img alt='' draggable='false' src={elephantUIPlane} />
<img alt='' draggable='false' src={mascot || elephantUIPlane} />
</div>
)}
</div>}

View File

@ -170,7 +170,7 @@ export const urlRegex = (function() {
')' +
'\\)',
'i');
// Valid end-of-path chracters (so /foo. does not gobble the period).
// Valid end-of-path characters (so /foo. does not gobble the period).
// 1. Allow =&# for empty URL parameters and other URL-join artifacts
regexen.validUrlPathEndingChars = regexSupplant(/[^#{spaces_group}\(\)\?!\*';:=\,\.\$%\[\]#{pd}~&\|@]|(?:#{validUrlBalancedParens})/i);
// Allow @ in a url, but only in the middle. Catch things like http://example.com/@user/

View File

@ -9,8 +9,8 @@ const messages = defineMessages({
settings: { id: 'home.settings', defaultMessage: 'Column settings' },
});
@injectIntl
export default class ColumnSettings extends React.PureComponent {
export default @injectIntl
class ColumnSettings extends React.PureComponent {
static propTypes = {
settings: ImmutablePropTypes.map.isRequired,

View File

@ -0,0 +1,63 @@
import React from 'react';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
import StatusContainer from '../../../containers/status_container';
export default class Conversation extends ImmutablePureComponent {
static contextTypes = {
router: PropTypes.object,
};
static propTypes = {
conversationId: PropTypes.string.isRequired,
accounts: ImmutablePropTypes.list.isRequired,
lastStatusId: PropTypes.string,
unread:PropTypes.bool.isRequired,
onMoveUp: PropTypes.func,
onMoveDown: PropTypes.func,
markRead: PropTypes.func.isRequired,
};
handleClick = () => {
if (!this.context.router) {
return;
}
const { lastStatusId, unread, markRead } = this.props;
if (unread) {
markRead();
}
this.context.router.history.push(`/statuses/${lastStatusId}`);
}
handleHotkeyMoveUp = () => {
this.props.onMoveUp(this.props.conversationId);
}
handleHotkeyMoveDown = () => {
this.props.onMoveDown(this.props.conversationId);
}
render () {
const { accounts, lastStatusId, unread } = this.props;
if (lastStatusId === null) {
return null;
}
return (
<StatusContainer
id={lastStatusId}
unread={unread}
otherAccounts={accounts}
onMoveUp={this.handleHotkeyMoveUp}
onMoveDown={this.handleHotkeyMoveDown}
/>
);
}
}

View File

@ -0,0 +1,68 @@
import React from 'react';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
import ConversationContainer from '../containers/conversation_container';
import ScrollableList from '../../../components/scrollable_list';
import { debounce } from 'lodash';
export default class ConversationsList extends ImmutablePureComponent {
static propTypes = {
conversationIds: ImmutablePropTypes.list.isRequired,
hasMore: PropTypes.bool,
isLoading: PropTypes.bool,
onLoadMore: PropTypes.func,
shouldUpdateScroll: PropTypes.func,
};
getCurrentIndex = id => this.props.conversationIds.indexOf(id)
handleMoveUp = id => {
const elementIndex = this.getCurrentIndex(id) - 1;
this._selectChild(elementIndex);
}
handleMoveDown = id => {
const elementIndex = this.getCurrentIndex(id) + 1;
this._selectChild(elementIndex);
}
_selectChild (index) {
const element = this.node.node.querySelector(`article:nth-of-type(${index + 1}) .focusable`);
if (element) {
element.focus();
}
}
setRef = c => {
this.node = c;
}
handleLoadOlder = debounce(() => {
const last = this.props.conversationIds.last();
if (last) {
this.props.onLoadMore(last);
}
}, 300, { leading: true })
render () {
const { conversationIds, onLoadMore, ...other } = this.props;
return (
<ScrollableList {...other} onLoadMore={onLoadMore && this.handleLoadOlder} scrollKey='direct' ref={this.setRef}>
{conversationIds.map(item => (
<ConversationContainer
key={item}
conversationId={item}
onMoveUp={this.handleMoveUp}
onMoveDown={this.handleMoveDown}
/>
))}
</ScrollableList>
);
}
}

View File

@ -0,0 +1,19 @@
import { connect } from 'react-redux';
import Conversation from '../components/conversation';
import { markConversationRead } from '../../../actions/conversations';
const mapStateToProps = (state, { conversationId }) => {
const conversation = state.getIn(['conversations', 'items']).find(x => x.get('id') === conversationId);
return {
accounts: conversation.get('accounts').map(accountId => state.getIn(['accounts', accountId], null)),
unread: conversation.get('unread'),
lastStatusId: conversation.get('last_status', null),
};
};
const mapDispatchToProps = (dispatch, { conversationId }) => ({
markRead: () => dispatch(markConversationRead(conversationId)),
});
export default connect(mapStateToProps, mapDispatchToProps)(Conversation);

View File

@ -0,0 +1,15 @@
import { connect } from 'react-redux';
import ConversationsList from '../components/conversations_list';
import { expandConversations } from '../../../actions/conversations';
const mapStateToProps = state => ({
conversationIds: state.getIn(['conversations', 'items']).map(x => x.get('id')),
isLoading: state.getIn(['conversations', 'isLoading'], true),
hasMore: state.getIn(['conversations', 'hasMore'], false),
});
const mapDispatchToProps = dispatch => ({
onLoadMore: maxId => dispatch(expandConversations({ maxId })),
});
export default connect(mapStateToProps, mapDispatchToProps)(ConversationsList);

View File

@ -1,25 +1,21 @@
import React from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import StatusListContainer from '../ui/containers/status_list_container';
import Column from '../../components/column';
import ColumnHeader from '../../components/column_header';
import { expandDirectTimeline } from '../../actions/timelines';
import { mountConversations, unmountConversations, expandConversations } from '../../actions/conversations';
import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import { connectDirectStream } from '../../actions/streaming';
import ConversationsListContainer from './containers/conversations_list_container';
const messages = defineMessages({
title: { id: 'column.direct', defaultMessage: 'Direct messages' },
});
const mapStateToProps = state => ({
hasUnread: state.getIn(['timelines', 'direct', 'unread']) > 0,
});
@connect(mapStateToProps)
export default @connect()
@injectIntl
export default class DirectTimeline extends React.PureComponent {
class DirectTimeline extends React.PureComponent {
static propTypes = {
dispatch: PropTypes.func.isRequired,
@ -52,11 +48,14 @@ export default class DirectTimeline extends React.PureComponent {
componentDidMount () {
const { dispatch } = this.props;
dispatch(expandDirectTimeline());
dispatch(mountConversations());
dispatch(expandConversations());
this.disconnect = dispatch(connectDirectStream());
}
componentWillUnmount () {
this.props.dispatch(unmountConversations());
if (this.disconnect) {
this.disconnect();
this.disconnect = null;
@ -68,11 +67,11 @@ export default class DirectTimeline extends React.PureComponent {
}
handleLoadMore = maxId => {
this.props.dispatch(expandDirectTimeline({ maxId }));
this.props.dispatch(expandConversations({ maxId }));
}
render () {
const { intl, shouldUpdateScroll, hasUnread, columnId, multiColumn } = this.props;
const { intl, hasUnread, columnId, multiColumn, shouldUpdateScroll } = this.props;
const pinned = !!columnId;
return (
@ -88,7 +87,7 @@ export default class DirectTimeline extends React.PureComponent {
multiColumn={multiColumn}
/>
<StatusListContainer
<ConversationsListContainer
trackScroll={!pinned}
scrollKey={`direct_timeline-${columnId}`}
timelineId='direct'

View File

@ -21,9 +21,9 @@ const mapStateToProps = state => ({
domains: state.getIn(['domain_lists', 'blocks', 'items']),
});
@connect(mapStateToProps)
export default @connect(mapStateToProps)
@injectIntl
export default class Blocks extends ImmutablePureComponent {
class Blocks extends ImmutablePureComponent {
static propTypes = {
params: PropTypes.object.isRequired,

View File

@ -21,9 +21,9 @@ const mapStateToProps = state => ({
hasMore: !!state.getIn(['status_lists', 'favourites', 'next']),
});
@connect(mapStateToProps)
export default @connect(mapStateToProps)
@injectIntl
export default class Favourites extends ImmutablePureComponent {
class Favourites extends ImmutablePureComponent {
static propTypes = {
dispatch: PropTypes.func.isRequired,

View File

@ -15,8 +15,8 @@ const mapStateToProps = (state, props) => ({
accountIds: state.getIn(['user_lists', 'favourited_by', props.params.statusId]),
});
@connect(mapStateToProps)
export default class Favourites extends ImmutablePureComponent {
export default @connect(mapStateToProps)
class Favourites extends ImmutablePureComponent {
static propTypes = {
params: PropTypes.object.isRequired,

View File

@ -13,8 +13,8 @@ const messages = defineMessages({
reject: { id: 'follow_request.reject', defaultMessage: 'Reject' },
});
@injectIntl
export default class AccountAuthorize extends ImmutablePureComponent {
export default @injectIntl
class AccountAuthorize extends ImmutablePureComponent {
static propTypes = {
account: ImmutablePropTypes.map.isRequired,

View File

@ -20,9 +20,9 @@ const mapStateToProps = state => ({
accountIds: state.getIn(['user_lists', 'follow_requests', 'items']),
});
@connect(mapStateToProps)
export default @connect(mapStateToProps)
@injectIntl
export default class FollowRequests extends ImmutablePureComponent {
class FollowRequests extends ImmutablePureComponent {
static propTypes = {
params: PropTypes.object.isRequired,

View File

@ -22,8 +22,8 @@ const mapStateToProps = (state, props) => ({
hasMore: !!state.getIn(['user_lists', 'followers', props.params.accountId, 'next']),
});
@connect(mapStateToProps)
export default class Followers extends ImmutablePureComponent {
export default @connect(mapStateToProps)
class Followers extends ImmutablePureComponent {
static propTypes = {
params: PropTypes.object.isRequired,

View File

@ -22,8 +22,8 @@ const mapStateToProps = (state, props) => ({
hasMore: !!state.getIn(['user_lists', 'following', props.params.accountId, 'next']),
});
@connect(mapStateToProps)
export default class Following extends ImmutablePureComponent {
export default @connect(mapStateToProps)
class Following extends ImmutablePureComponent {
static propTypes = {
params: PropTypes.object.isRequired,

View File

@ -53,9 +53,9 @@ const badgeDisplay = (number, limit) => {
}
};
@connect(mapStateToProps, mapDispatchToProps)
export default @connect(mapStateToProps, mapDispatchToProps)
@injectIntl
export default class GettingStarted extends ImmutablePureComponent {
class GettingStarted extends ImmutablePureComponent {
static propTypes = {
intl: PropTypes.object.isRequired,
@ -144,7 +144,7 @@ export default class GettingStarted extends ImmutablePureComponent {
<li><a href='https://joinmastodon.org/apps' target='_blank'><FormattedMessage id='navigation_bar.apps' defaultMessage='Mobile apps' /></a> · </li>
<li><a href='/terms' target='_blank'><FormattedMessage id='getting_started.terms' defaultMessage='Terms of service' /></a> · </li>
<li><a href='/settings/applications' target='_blank'><FormattedMessage id='getting_started.developers' defaultMessage='Developers' /></a> · </li>
<li><a href='https://github.com/tootsuite/documentation#documentation' target='_blank'><FormattedMessage id='getting_started.documentation' defaultMessage='Documentation' /></a> · </li>
<li><a href='https://docs.joinmastodon.org' target='_blank'><FormattedMessage id='getting_started.documentation' defaultMessage='Documentation' /></a> · </li>
<li><a href='/auth/sign_out' data-method='delete'><FormattedMessage id='navigation_bar.logout' defaultMessage='Logout' /></a></li>
</ul>

View File

@ -13,8 +13,8 @@ const mapStateToProps = (state, props) => ({
hasUnread: state.getIn(['timelines', `hashtag:${props.params.id}`, 'unread']) > 0,
});
@connect(mapStateToProps)
export default class HashtagTimeline extends React.PureComponent {
export default @connect(mapStateToProps)
class HashtagTimeline extends React.PureComponent {
static propTypes = {
params: PropTypes.object.isRequired,

View File

@ -4,8 +4,8 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
import { injectIntl, FormattedMessage } from 'react-intl';
import SettingToggle from '../../notifications/components/setting_toggle';
@injectIntl
export default class ColumnSettings extends React.PureComponent {
export default @injectIntl
class ColumnSettings extends React.PureComponent {
static propTypes = {
settings: ImmutablePropTypes.map.isRequired,

View File

@ -19,9 +19,9 @@ const mapStateToProps = state => ({
isPartial: state.getIn(['timelines', 'home', 'items', 0], null) === null,
});
@connect(mapStateToProps)
export default @connect(mapStateToProps)
@injectIntl
export default class HomeTimeline extends React.PureComponent {
class HomeTimeline extends React.PureComponent {
static propTypes = {
dispatch: PropTypes.func.isRequired,

View File

@ -9,8 +9,8 @@ const messages = defineMessages({
heading: { id: 'keyboard_shortcuts.heading', defaultMessage: 'Keyboard Shortcuts' },
});
@injectIntl
export default class KeyboardShortcuts extends ImmutablePureComponent {
export default @injectIntl
class KeyboardShortcuts extends ImmutablePureComponent {
static propTypes = {
intl: PropTypes.object.isRequired,

View File

@ -31,9 +31,9 @@ const mapDispatchToProps = (dispatch, { accountId }) => ({
onAdd: () => dispatch(addToListEditor(accountId)),
});
@connect(makeMapStateToProps, mapDispatchToProps)
export default @connect(makeMapStateToProps, mapDispatchToProps)
@injectIntl
export default class Account extends ImmutablePureComponent {
class Account extends ImmutablePureComponent {
static propTypes = {
account: ImmutablePropTypes.map.isRequired,

View File

@ -19,9 +19,9 @@ const mapDispatchToProps = dispatch => ({
onChange: value => dispatch(changeListSuggestions(value)),
});
@connect(mapStateToProps, mapDispatchToProps)
export default @connect(mapStateToProps, mapDispatchToProps)
@injectIntl
export default class Search extends React.PureComponent {
class Search extends React.PureComponent {
static propTypes = {
intl: PropTypes.object.isRequired,

View File

@ -22,9 +22,9 @@ const mapDispatchToProps = dispatch => ({
onReset: () => dispatch(resetListEditor()),
});
@connect(mapStateToProps, mapDispatchToProps)
export default @connect(mapStateToProps, mapDispatchToProps)
@injectIntl
export default class ListEditor extends ImmutablePureComponent {
class ListEditor extends ImmutablePureComponent {
static propTypes = {
listId: PropTypes.string.isRequired,

View File

@ -25,9 +25,9 @@ const mapStateToProps = (state, props) => ({
hasUnread: state.getIn(['timelines', `list:${props.params.id}`, 'unread']) > 0,
});
@connect(mapStateToProps)
export default @connect(mapStateToProps)
@injectIntl
export default class ListTimeline extends React.PureComponent {
class ListTimeline extends React.PureComponent {
static contextTypes = {
router: PropTypes.object,

View File

@ -20,9 +20,9 @@ const mapDispatchToProps = dispatch => ({
onSubmit: () => dispatch(submitListEditor(true)),
});
@connect(mapStateToProps, mapDispatchToProps)
export default @connect(mapStateToProps, mapDispatchToProps)
@injectIntl
export default class NewListForm extends React.PureComponent {
class NewListForm extends React.PureComponent {
static propTypes = {
value: PropTypes.string.isRequired,

View File

@ -31,9 +31,9 @@ const mapStateToProps = state => ({
lists: getOrderedLists(state),
});
@connect(mapStateToProps)
export default @connect(mapStateToProps)
@injectIntl
export default class Lists extends ImmutablePureComponent {
class Lists extends ImmutablePureComponent {
static propTypes = {
params: PropTypes.object.isRequired,

View File

@ -20,9 +20,9 @@ const mapStateToProps = state => ({
accountIds: state.getIn(['user_lists', 'mutes', 'items']),
});
@connect(mapStateToProps)
export default @connect(mapStateToProps)
@injectIntl
export default class Mutes extends ImmutablePureComponent {
class Mutes extends ImmutablePureComponent {
static propTypes = {
params: PropTypes.object.isRequired,

View File

@ -27,7 +27,6 @@ export default class ColumnSettings extends React.PureComponent {
const showPushSettings = pushSettings.get('browserSupport') && pushSettings.get('isSubscribed');
const pushStr = showPushSettings && <FormattedMessage id='notifications.column_settings.push' defaultMessage='Push notifications' />;
const pushMeta = showPushSettings && <FormattedMessage id='notifications.column_settings.push_meta' defaultMessage='This device' />;
return (
<div>
@ -40,7 +39,7 @@ export default class ColumnSettings extends React.PureComponent {
<div className='column-settings__row'>
<SettingToggle prefix='notifications_desktop' settings={settings} settingPath={['alerts', 'follow']} onChange={onChange} label={alertStr} />
{showPushSettings && <SettingToggle prefix='notifications_push' settings={pushSettings} settingPath={['alerts', 'follow']} meta={pushMeta} onChange={this.onPushChange} label={pushStr} />}
{showPushSettings && <SettingToggle prefix='notifications_push' settings={pushSettings} settingPath={['alerts', 'follow']} onChange={this.onPushChange} label={pushStr} />}
<SettingToggle prefix='notifications' settings={settings} settingPath={['shows', 'follow']} onChange={onChange} label={showStr} />
<SettingToggle prefix='notifications' settings={settings} settingPath={['sounds', 'follow']} onChange={onChange} label={soundStr} />
</div>
@ -51,7 +50,7 @@ export default class ColumnSettings extends React.PureComponent {
<div className='column-settings__row'>
<SettingToggle prefix='notifications_desktop' settings={settings} settingPath={['alerts', 'favourite']} onChange={onChange} label={alertStr} />
{showPushSettings && <SettingToggle prefix='notifications_push' settings={pushSettings} settingPath={['alerts', 'favourite']} meta={pushMeta} onChange={this.onPushChange} label={pushStr} />}
{showPushSettings && <SettingToggle prefix='notifications_push' settings={pushSettings} settingPath={['alerts', 'favourite']} onChange={this.onPushChange} label={pushStr} />}
<SettingToggle prefix='notifications' settings={settings} settingPath={['shows', 'favourite']} onChange={onChange} label={showStr} />
<SettingToggle prefix='notifications' settings={settings} settingPath={['sounds', 'favourite']} onChange={onChange} label={soundStr} />
</div>
@ -62,7 +61,7 @@ export default class ColumnSettings extends React.PureComponent {
<div className='column-settings__row'>
<SettingToggle prefix='notifications_desktop' settings={settings} settingPath={['alerts', 'mention']} onChange={onChange} label={alertStr} />
{showPushSettings && <SettingToggle prefix='notifications_push' settings={pushSettings} settingPath={['alerts', 'mention']} meta={pushMeta} onChange={this.onPushChange} label={pushStr} />}
{showPushSettings && <SettingToggle prefix='notifications_push' settings={pushSettings} settingPath={['alerts', 'mention']} onChange={this.onPushChange} label={pushStr} />}
<SettingToggle prefix='notifications' settings={settings} settingPath={['shows', 'mention']} onChange={onChange} label={showStr} />
<SettingToggle prefix='notifications' settings={settings} settingPath={['sounds', 'mention']} onChange={onChange} label={soundStr} />
</div>
@ -73,7 +72,7 @@ export default class ColumnSettings extends React.PureComponent {
<div className='column-settings__row'>
<SettingToggle prefix='notifications_desktop' settings={settings} settingPath={['alerts', 'reblog']} onChange={onChange} label={alertStr} />
{showPushSettings && <SettingToggle prefix='notifications_push' settings={pushSettings} settingPath={['alerts', 'reblog']} meta={pushMeta} onChange={this.onPushChange} label={pushStr} />}
{showPushSettings && <SettingToggle prefix='notifications_push' settings={pushSettings} settingPath={['alerts', 'reblog']} onChange={this.onPushChange} label={pushStr} />}
<SettingToggle prefix='notifications' settings={settings} settingPath={['shows', 'reblog']} onChange={onChange} label={showStr} />
<SettingToggle prefix='notifications' settings={settings} settingPath={['sounds', 'reblog']} onChange={onChange} label={soundStr} />
</div>

View File

@ -16,8 +16,8 @@ const notificationForScreenReader = (intl, message, timestamp) => {
return output.join(', ');
};
@injectIntl
export default class Notification extends ImmutablePureComponent {
export default @injectIntl
class Notification extends ImmutablePureComponent {
static contextTypes = {
router: PropTypes.object,
@ -85,8 +85,9 @@ export default class Notification extends ImmutablePureComponent {
<div className='notification__favourite-icon-wrapper'>
<i className='fa fa-fw fa-user-plus' />
</div>
<FormattedMessage id='notification.follow' defaultMessage='{name} followed you' values={{ name: link }} />
<span title={notification.get('created_at')}>
<FormattedMessage id='notification.follow' defaultMessage='{name} followed you' values={{ name: link }} />
</span>
</div>
<AccountContainer id={account.get('id')} withNote={false} hidden={this.props.hidden} />

View File

@ -10,7 +10,6 @@ export default class SettingToggle extends React.PureComponent {
settings: ImmutablePropTypes.map.isRequired,
settingPath: PropTypes.array.isRequired,
label: PropTypes.node.isRequired,
meta: PropTypes.node,
onChange: PropTypes.func.isRequired,
}
@ -19,14 +18,13 @@ export default class SettingToggle extends React.PureComponent {
}
render () {
const { prefix, settings, settingPath, label, meta } = this.props;
const { prefix, settings, settingPath, label } = this.props;
const id = ['setting-toggle', prefix, ...settingPath].filter(Boolean).join('-');
return (
<div className='setting-toggle'>
<Toggle id={id} checked={settings.getIn(settingPath)} onChange={this.onChange} onKeyDown={this.onKeyDown} />
<label htmlFor={id} className='setting-toggle__label'>{label}</label>
{meta && <span className='setting-meta__label'>{meta}</span>}
</div>
);
}

View File

@ -31,9 +31,9 @@ const mapStateToProps = state => ({
hasMore: state.getIn(['notifications', 'hasMore']),
});
@connect(mapStateToProps)
export default @connect(mapStateToProps)
@injectIntl
export default class Notifications extends React.PureComponent {
class Notifications extends React.PureComponent {
static propTypes = {
columnId: PropTypes.string,

View File

@ -18,9 +18,9 @@ const mapStateToProps = state => ({
hasMore: !!state.getIn(['status_lists', 'pins', 'next']),
});
@connect(mapStateToProps)
export default @connect(mapStateToProps)
@injectIntl
export default class PinnedStatuses extends ImmutablePureComponent {
class PinnedStatuses extends ImmutablePureComponent {
static propTypes = {
dispatch: PropTypes.func.isRequired,

View File

@ -25,9 +25,9 @@ const mapStateToProps = (state, { onlyMedia, columnId }) => {
};
};
@connect(mapStateToProps)
export default @connect(mapStateToProps)
@injectIntl
export default class PublicTimeline extends React.PureComponent {
class PublicTimeline extends React.PureComponent {
static contextTypes = {
router: PropTypes.object,

View File

@ -15,8 +15,8 @@ const mapStateToProps = (state, props) => ({
accountIds: state.getIn(['user_lists', 'reblogged_by', props.params.statusId]),
});
@connect(mapStateToProps)
export default class Reblogs extends ImmutablePureComponent {
export default @connect(mapStateToProps)
class Reblogs extends ImmutablePureComponent {
static propTypes = {
params: PropTypes.object.isRequired,

View File

@ -12,9 +12,9 @@ const messages = defineMessages({
title: { id: 'standalone.public_title', defaultMessage: 'A look inside...' },
});
@connect()
export default @connect()
@injectIntl
export default class CommunityTimeline extends React.PureComponent {
class CommunityTimeline extends React.PureComponent {
static propTypes = {
dispatch: PropTypes.func.isRequired,

View File

@ -7,8 +7,8 @@ import Column from '../../../components/column';
import ColumnHeader from '../../../components/column_header';
import { connectHashtagStream } from '../../../actions/streaming';
@connect()
export default class HashtagTimeline extends React.PureComponent {
export default @connect()
class HashtagTimeline extends React.PureComponent {
static propTypes = {
dispatch: PropTypes.func.isRequired,

View File

@ -12,9 +12,9 @@ const messages = defineMessages({
title: { id: 'standalone.public_title', defaultMessage: 'A look inside...' },
});
@connect()
export default @connect()
@injectIntl
export default class PublicTimeline extends React.PureComponent {
class PublicTimeline extends React.PureComponent {
static propTypes = {
dispatch: PropTypes.func.isRequired,

View File

@ -28,8 +28,8 @@ const messages = defineMessages({
embed: { id: 'status.embed', defaultMessage: 'Embed' },
});
@injectIntl
export default class ActionBar extends React.PureComponent {
export default @injectIntl
class ActionBar extends React.PureComponent {
static contextTypes = {
router: PropTypes.object,

View File

@ -54,6 +54,8 @@ const messages = defineMessages({
revealAll: { id: 'status.show_more_all', defaultMessage: 'Show more for all' },
hideAll: { id: 'status.show_less_all', defaultMessage: 'Show less for all' },
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?' },
});
const makeMapStateToProps = () => {
@ -98,15 +100,16 @@ const makeMapStateToProps = () => {
status,
ancestorsIds,
descendantsIds,
askReplyConfirmation: state.getIn(['compose', 'text']).trim().length !== 0,
};
};
return mapStateToProps;
};
@injectIntl
export default @injectIntl
@connect(makeMapStateToProps)
export default class Status extends ImmutablePureComponent {
class Status extends ImmutablePureComponent {
static contextTypes = {
router: PropTypes.object,
@ -119,6 +122,7 @@ export default class Status extends ImmutablePureComponent {
ancestorsIds: ImmutablePropTypes.list,
descendantsIds: ImmutablePropTypes.list,
intl: PropTypes.object.isRequired,
askReplyConfirmation: PropTypes.bool,
};
state = {
@ -157,7 +161,16 @@ export default class Status extends ImmutablePureComponent {
}
handleReplyClick = (status) => {
this.props.dispatch(replyCompose(status, this.context.router.history));
let { askReplyConfirmation, dispatch, intl } = this.props;
if (askReplyConfirmation) {
dispatch(openModal('CONFIRM', {
message: intl.formatMessage(messages.replyMessage),
confirm: intl.formatMessage(messages.replyConfirm),
onConfirm: () => dispatch(replyCompose(status, this.context.router.history)),
}));
} else {
dispatch(replyCompose(status, this.context.router.history));
}
}
handleModalReblog = (status) => {
@ -168,7 +181,7 @@ export default class Status extends ImmutablePureComponent {
if (status.get('reblogged')) {
this.props.dispatch(unreblog(status));
} else {
if (e.shiftKey || !boostModal) {
if ((e && e.shiftKey) || !boostModal) {
this.handleModalReblog(status);
} else {
this.props.dispatch(openModal('BOOST', { status, onReblog: this.handleModalReblog }));

View File

@ -13,8 +13,8 @@ const messages = defineMessages({
reblog: { id: 'status.reblog', defaultMessage: 'Boost' },
});
@injectIntl
export default class BoostModal extends ImmutablePureComponent {
export default @injectIntl
class BoostModal extends ImmutablePureComponent {
static contextTypes = {
router: PropTypes.object,

View File

@ -35,8 +35,8 @@ const messages = defineMessages({
const shouldHideFAB = path => path.match(/^\/statuses\//);
@component => injectIntl(component, { withRef: true })
export default class ColumnsArea extends ImmutablePureComponent {
export default @(component => injectIntl(component, { withRef: true }))
class ColumnsArea extends ImmutablePureComponent {
static contextTypes = {
router: PropTypes.object.isRequired,

View File

@ -3,8 +3,8 @@ import PropTypes from 'prop-types';
import { injectIntl, FormattedMessage } from 'react-intl';
import Button from '../../../components/button';
@injectIntl
export default class ConfirmationModal extends React.PureComponent {
export default @injectIntl
class ConfirmationModal extends React.PureComponent {
static propTypes = {
message: PropTypes.node.isRequired,

View File

@ -4,8 +4,8 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
import { FormattedMessage, injectIntl } from 'react-intl';
import api from '../../../api';
@injectIntl
export default class EmbedModal extends ImmutablePureComponent {
export default @injectIntl
class EmbedModal extends ImmutablePureComponent {
static propTypes = {
url: PropTypes.string.isRequired,

View File

@ -19,8 +19,8 @@ const mapDispatchToProps = (dispatch, { id }) => ({
});
@connect(mapStateToProps, mapDispatchToProps)
export default class FocalPointModal extends ImmutablePureComponent {
export default @connect(mapStateToProps, mapDispatchToProps)
class FocalPointModal extends ImmutablePureComponent {
static propTypes = {
media: ImmutablePropTypes.map.isRequired,

View File

@ -18,8 +18,8 @@ const messages = defineMessages({
export const previewState = 'previewMediaModal';
@injectIntl
export default class MediaModal extends ImmutablePureComponent {
export default @injectIntl
class MediaModal extends ImmutablePureComponent {
static propTypes = {
media: ImmutablePropTypes.list.isRequired,
@ -149,7 +149,7 @@ export default class MediaModal extends ImmutablePureComponent {
startTime={time || 0}
onCloseVideo={onClose}
detailed
description={image.get('description')}
alt={image.get('description')}
key={image.get('url')}
/>
);

View File

@ -33,9 +33,9 @@ const mapDispatchToProps = dispatch => {
};
};
@connect(mapStateToProps, mapDispatchToProps)
export default @connect(mapStateToProps, mapDispatchToProps)
@injectIntl
export default class MuteModal extends React.PureComponent {
class MuteModal extends React.PureComponent {
static propTypes = {
isSubmitting: PropTypes.bool.isRequired,

View File

@ -160,7 +160,7 @@ const PageSix = ({ admin, domain }) => {
<h1><FormattedMessage id='onboarding.page_six.almost_done' defaultMessage='Almost done...' /></h1>
{adminSection}
<p><FormattedMessage id='onboarding.page_six.github' defaultMessage='Mastodon is free open-source software. You can report bugs, request features, or contribute to the code on {github}.' values={{ github: <a href='https://github.com/tootsuite/mastodon' target='_blank' rel='noopener'>GitHub</a> }} /></p>
<p><FormattedMessage id='onboarding.page_six.apps_available' defaultMessage='There are {apps} available for iOS, Android and other platforms.' values={{ apps: <a href='https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/Apps.md' target='_blank' rel='noopener'><FormattedMessage id='onboarding.page_six.various_app' defaultMessage='mobile apps' /></a> }} /></p>
<p><FormattedMessage id='onboarding.page_six.apps_available' defaultMessage='There are {apps} available for iOS, Android and other platforms.' values={{ apps: <a href='https://joinmastodon.org/apps' target='_blank' rel='noopener'><FormattedMessage id='onboarding.page_six.various_app' defaultMessage='mobile apps' /></a> }} /></p>
<p><em><FormattedMessage id='onboarding.page_six.appetoot' defaultMessage='Bon Appetoot!' /></em></p>
</div>
);
@ -177,9 +177,9 @@ const mapStateToProps = state => ({
domain: state.getIn(['meta', 'domain']),
});
@connect(mapStateToProps)
export default @connect(mapStateToProps)
@injectIntl
export default class OnboardingModal extends React.PureComponent {
class OnboardingModal extends React.PureComponent {
static propTypes = {
onClose: PropTypes.func.isRequired,

View File

@ -37,9 +37,9 @@ const makeMapStateToProps = () => {
return mapStateToProps;
};
@connect(makeMapStateToProps)
export default @connect(makeMapStateToProps)
@injectIntl
export default class ReportModal extends ImmutablePureComponent {
class ReportModal extends ImmutablePureComponent {
static propTypes = {
isSubmitting: PropTypes.bool,
@ -106,6 +106,7 @@ export default class ReportModal extends ImmutablePureComponent {
onChange={this.handleCommentChange}
onKeyDown={this.handleKeyDown}
disabled={isSubmitting}
autoFocus
/>
{domain && (

View File

@ -24,9 +24,9 @@ export function getLink (index) {
return links[index].props.to;
}
@injectIntl
export default @injectIntl
@withRouter
export default class TabsBar extends React.PureComponent {
class TabsBar extends React.PureComponent {
static propTypes = {
intl: PropTypes.object.isRequired,

View File

@ -24,7 +24,7 @@ export default class VideoModal extends ImmutablePureComponent {
startTime={time}
onCloseVideo={onClose}
detailed
description={media.get('description')}
alt={media.get('description')}
/>
</div>
</div>

View File

@ -137,6 +137,7 @@ export default class ZoomableImage extends React.PureComponent {
role='presentation'
ref={this.setImageRef}
alt={alt}
title={alt}
src={src}
style={{
transform: `scale(${scale})`,

View File

@ -59,7 +59,8 @@ const messages = defineMessages({
const mapStateToProps = state => ({
isComposing: state.getIn(['compose', 'is_composing']),
hasComposingText: state.getIn(['compose', 'text']) !== '',
hasComposingText: state.getIn(['compose', 'text']).trim().length !== 0,
hasMediaAttachments: state.getIn(['compose', 'media_attachments']).size > 0,
dropdownMenuIsOpen: state.getIn(['dropdown_menu', 'openId']) !== null,
});
@ -187,10 +188,10 @@ class SwitchingColumnsArea extends React.PureComponent {
}
@connect(mapStateToProps)
export default @connect(mapStateToProps)
@injectIntl
@withRouter
export default class UI extends React.PureComponent {
class UI extends React.PureComponent {
static contextTypes = {
router: PropTypes.object.isRequired,
@ -201,6 +202,7 @@ export default class UI extends React.PureComponent {
children: PropTypes.node,
isComposing: PropTypes.bool,
hasComposingText: PropTypes.bool,
hasMediaAttachments: PropTypes.bool,
location: PropTypes.object,
intl: PropTypes.object.isRequired,
dropdownMenuIsOpen: PropTypes.bool,
@ -211,9 +213,9 @@ export default class UI extends React.PureComponent {
};
handleBeforeUnload = (e) => {
const { intl, isComposing, hasComposingText } = this.props;
const { intl, isComposing, hasComposingText, hasMediaAttachments } = this.props;
if (isComposing && hasComposingText) {
if (isComposing && (hasComposingText || hasMediaAttachments)) {
// Setting returnValue to any string causes confirmation dialog.
// Many browsers no longer display this text to users,
// but we set user-friendly message for other browsers, e.g. Edge.
@ -458,7 +460,7 @@ export default class UI extends React.PureComponent {
};
return (
<HotKeys keyMap={keyMap} handlers={handlers} ref={this.setHotkeysRef}>
<HotKeys keyMap={keyMap} handlers={handlers} ref={this.setHotkeysRef} attach={window} focused>
<div className={classNames('ui', { 'is-composing': isComposing })} ref={this.setRef} style={{ pointerEvents: dropdownMenuIsOpen ? 'none' : null }}>
<TabsBar />

View File

@ -26,7 +26,7 @@ WrappedSwitch.propTypes = {
children: PropTypes.node,
};
// Small Wraper to extract the params from the route and pass
// Small Wrapper to extract the params from the route and pass
// them to the rendered component, together with the content to
// be rendered inside (the children)
export class WrappedRoute extends React.Component {

View File

@ -5,7 +5,7 @@ import { fromJS } from 'immutable';
import { throttle } from 'lodash';
import classNames from 'classnames';
import { isFullscreen, requestFullscreen, exitFullscreen } from '../ui/util/fullscreen';
import { displaySensitiveMedia } from '../../initial_state';
import { displayMedia } from '../../initial_state';
const messages = defineMessages({
play: { id: 'video.play', defaultMessage: 'Play' },
@ -84,8 +84,8 @@ export const getPointerPosition = (el, event) => {
return position;
};
@injectIntl
export default class Video extends React.PureComponent {
export default @injectIntl
class Video extends React.PureComponent {
static propTypes = {
preview: PropTypes.string,
@ -111,7 +111,7 @@ export default class Video extends React.PureComponent {
fullscreen: false,
hovered: false,
muted: false,
revealed: !this.props.sensitive || displaySensitiveMedia,
revealed: displayMedia !== 'hide_all' && !this.props.sensitive || displayMedia === 'show_all',
};
setPlayerRef = c => {
@ -252,11 +252,12 @@ export default class Video extends React.PureComponent {
}
handleOpenVideo = () => {
const { src, preview, width, height } = this.props;
const { src, preview, width, height, alt } = this.props;
const media = fromJS({
type: 'video',
url: src,
preview_url: preview,
description: alt,
width,
height,
});
@ -271,7 +272,7 @@ export default class Video extends React.PureComponent {
}
render () {
const { preview, src, inline, startTime, onOpenVideo, onCloseVideo, intl, alt, detailed } = this.props;
const { preview, src, inline, startTime, onOpenVideo, onCloseVideo, intl, alt, detailed, sensitive } = this.props;
const { containerWidth, currentTime, duration, buffer, dragging, paused, fullscreen, hovered, muted, revealed } = this.state;
const progress = (currentTime / duration) * 100;
const playerStyle = {};
@ -295,6 +296,13 @@ export default class Video extends React.PureComponent {
preload = 'none';
}
let warning;
if (sensitive) {
warning = <FormattedMessage id='status.sensitive_warning' defaultMessage='Sensitive content' />;
} else {
warning = <FormattedMessage id='status.media_hidden' defaultMessage='Media hidden' />;
}
return (
<div
role='menuitem'
@ -327,7 +335,7 @@ export default class Video extends React.PureComponent {
/>
<button type='button' className={classNames('video-player__spoiler', { active: !revealed })} onClick={this.toggleReveal}>
<span className='video-player__spoiler__title'><FormattedMessage id='status.sensitive_warning' defaultMessage='Sensitive content' /></span>
<span className='video-player__spoiler__title'>{warning}</span>
<span className='video-player__spoiler__subtitle'><FormattedMessage id='status.sensitive_toggle' defaultMessage='Click to view' /></span>
</button>