Merge tag 'v2.7.2' into instance_only_statuses

This commit is contained in:
Renato "Lond" Cerqueira
2019-02-19 21:07:43 +01:00
76 changed files with 1440 additions and 577 deletions

View File

@ -132,7 +132,7 @@ class Header extends ImmutablePureComponent {
} else if (account.getIn(['relationship', 'blocking'])) {
actionBtn = (
<div className='account--action-button'>
<IconButton size={26} icon='unlock-alt' title={intl.formatMessage(messages.unblock, { name: account.get('username') })} onClick={this.props.onBlock} />
<IconButton size={26} icon='unlock' title={intl.formatMessage(messages.unblock, { name: account.get('username') })} onClick={this.props.onBlock} />
</div>
);
}

View File

@ -18,6 +18,7 @@ const messages = defineMessages({
const mapStateToProps = state => ({
accountIds: state.getIn(['user_lists', 'blocks', 'items']),
hasMore: !!state.getIn(['user_lists', 'blocks', 'next']),
});
export default @connect(mapStateToProps)
@ -29,6 +30,7 @@ class Blocks extends ImmutablePureComponent {
dispatch: PropTypes.func.isRequired,
shouldUpdateScroll: PropTypes.func,
accountIds: ImmutablePropTypes.list,
hasMore: PropTypes.bool,
intl: PropTypes.object.isRequired,
};
@ -41,7 +43,7 @@ class Blocks extends ImmutablePureComponent {
}, 300, { leading: true });
render () {
const { intl, accountIds, shouldUpdateScroll } = this.props;
const { intl, accountIds, shouldUpdateScroll, hasMore } = this.props;
if (!accountIds) {
return (
@ -59,6 +61,7 @@ class Blocks extends ImmutablePureComponent {
<ScrollableList
scrollKey='blocks'
onLoadMore={this.handleLoadMore}
hasMore={hasMore}
shouldUpdateScroll={shouldUpdateScroll}
emptyMessage={emptyMessage}
>

View File

@ -181,7 +181,7 @@ class ComposeForm extends ImmutablePureComponent {
<div className={`spoiler-input ${this.props.spoiler ? 'spoiler-input--visible' : ''}`}>
<label>
<span style={{ display: 'none' }}>{intl.formatMessage(messages.spoiler_placeholder)}</span>
<input placeholder={intl.formatMessage(messages.spoiler_placeholder)} value={this.props.spoiler_text} onChange={this.handleChangeSpoilerText} onKeyDown={this.handleKeyDown} type='text' className='spoiler-input__input' id='cw-spoiler-input' ref={this.setSpoilerText} />
<input placeholder={intl.formatMessage(messages.spoiler_placeholder)} value={this.props.spoiler_text} onChange={this.handleChangeSpoilerText} onKeyDown={this.handleKeyDown} tabIndex={this.props.spoiler ? 0 : -1} type='text' className='spoiler-input__input' id='cw-spoiler-input' ref={this.setSpoilerText} />
</label>
</div>

View File

@ -214,7 +214,7 @@ class PrivacyDropdown extends React.PureComponent {
this.options = [
{ icon: 'globe', value: 'public', text: formatMessage(messages.public_short), meta: formatMessage(messages.public_long) },
{ icon: 'unlock-alt', value: 'unlisted', text: formatMessage(messages.unlisted_short), meta: formatMessage(messages.unlisted_long) },
{ icon: 'unlock', value: 'unlisted', text: formatMessage(messages.unlisted_short), meta: formatMessage(messages.unlisted_long) },
{ icon: 'lock', value: 'private', text: formatMessage(messages.private_short), meta: formatMessage(messages.private_long) },
{ icon: 'envelope', value: 'direct', text: formatMessage(messages.direct_short), meta: formatMessage(messages.direct_long) },
];

View File

@ -107,9 +107,8 @@ class Upload extends ImmutablePureComponent {
<label>
<span style={{ display: 'none' }}>{intl.formatMessage(messages.description)}</span>
<input
<textarea
placeholder={intl.formatMessage(messages.description)}
type='text'
value={description}
maxLength={420}
onFocus={this.handleInputFocus}

View File

@ -19,6 +19,7 @@ const messages = defineMessages({
const mapStateToProps = state => ({
domains: state.getIn(['domain_lists', 'blocks', 'items']),
hasMore: !!state.getIn(['domain_lists', 'blocks', 'next']),
});
export default @connect(mapStateToProps)
@ -29,6 +30,7 @@ class Blocks extends ImmutablePureComponent {
params: PropTypes.object.isRequired,
dispatch: PropTypes.func.isRequired,
shouldUpdateScroll: PropTypes.func,
hasMore: PropTypes.bool,
domains: ImmutablePropTypes.orderedSet,
intl: PropTypes.object.isRequired,
};
@ -42,7 +44,7 @@ class Blocks extends ImmutablePureComponent {
}, 300, { leading: true });
render () {
const { intl, domains, shouldUpdateScroll } = this.props;
const { intl, domains, shouldUpdateScroll, hasMore } = this.props;
if (!domains) {
return (
@ -60,6 +62,7 @@ class Blocks extends ImmutablePureComponent {
<ScrollableList
scrollKey='domain_blocks'
onLoadMore={this.handleLoadMore}
hasMore={hasMore}
shouldUpdateScroll={shouldUpdateScroll}
emptyMessage={emptyMessage}
>

View File

@ -18,6 +18,7 @@ const messages = defineMessages({
const mapStateToProps = state => ({
accountIds: state.getIn(['user_lists', 'follow_requests', 'items']),
hasMore: !!state.getIn(['user_lists', 'follow_requests', 'next']),
});
export default @connect(mapStateToProps)
@ -28,6 +29,7 @@ class FollowRequests extends ImmutablePureComponent {
params: PropTypes.object.isRequired,
dispatch: PropTypes.func.isRequired,
shouldUpdateScroll: PropTypes.func,
hasMore: PropTypes.bool,
accountIds: ImmutablePropTypes.list,
intl: PropTypes.object.isRequired,
};
@ -41,7 +43,7 @@ class FollowRequests extends ImmutablePureComponent {
}, 300, { leading: true });
render () {
const { intl, shouldUpdateScroll, accountIds } = this.props;
const { intl, shouldUpdateScroll, accountIds, hasMore } = this.props;
if (!accountIds) {
return (
@ -59,6 +61,7 @@ class FollowRequests extends ImmutablePureComponent {
<ScrollableList
scrollKey='follow_requests'
onLoadMore={this.handleLoadMore}
hasMore={hasMore}
shouldUpdateScroll={shouldUpdateScroll}
emptyMessage={emptyMessage}
>

View File

@ -1,10 +1,15 @@
import React from 'react';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { injectIntl, FormattedMessage } from 'react-intl';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import Toggle from 'react-toggle';
import AsyncSelect from 'react-select/lib/Async';
const messages = defineMessages({
placeholder: { id: 'hashtag.column_settings.select.placeholder', defaultMessage: 'Enter hashtags…' },
noOptions: { id: 'hashtag.column_settings.select.no_options_message', defaultMessage: 'No suggestions found' },
});
export default @injectIntl
class ColumnSettings extends React.PureComponent {
@ -25,6 +30,7 @@ class ColumnSettings extends React.PureComponent {
tags (mode) {
let tags = this.props.settings.getIn(['tags', mode]) || [];
if (tags.toJSON) {
return tags.toJSON();
} else {
@ -32,33 +38,36 @@ class ColumnSettings extends React.PureComponent {
}
};
onSelect = (mode) => {
return (value) => {
this.props.onChange(['tags', mode], value);
};
};
onSelect = mode => value => this.props.onChange(['tags', mode], value);
onToggle = () => {
if (this.state.open && this.hasTags()) {
this.props.onChange('tags', {});
}
this.setState({ open: !this.state.open });
};
noOptionsMessage = () => this.props.intl.formatMessage(messages.noOptions);
modeSelect (mode) {
return (
<div className='column-settings__section'>
{this.modeLabel(mode)}
<div className='column-settings__row'>
<span className='column-settings__section'>
{this.modeLabel(mode)}
</span>
<AsyncSelect
isMulti
autoFocus
value={this.tags(mode)}
settings={this.props.settings}
settingPath={['tags', mode]}
onChange={this.onSelect(mode)}
loadOptions={this.props.onLoad}
classNamePrefix='column-settings__hashtag-select'
className='column-select__container'
classNamePrefix='column-select'
name='tags'
placeholder={this.props.intl.formatMessage(messages.placeholder)}
noOptionsMessage={this.noOptionsMessage}
/>
</div>
);
@ -66,11 +75,15 @@ class ColumnSettings extends React.PureComponent {
modeLabel (mode) {
switch(mode) {
case 'any': return <FormattedMessage id='hashtag.column_settings.tag_mode.any' defaultMessage='Any of these' />;
case 'all': return <FormattedMessage id='hashtag.column_settings.tag_mode.all' defaultMessage='All of these' />;
case 'none': return <FormattedMessage id='hashtag.column_settings.tag_mode.none' defaultMessage='None of these' />;
case 'any':
return <FormattedMessage id='hashtag.column_settings.tag_mode.any' defaultMessage='Any of these' />;
case 'all':
return <FormattedMessage id='hashtag.column_settings.tag_mode.all' defaultMessage='All of these' />;
case 'none':
return <FormattedMessage id='hashtag.column_settings.tag_mode.none' defaultMessage='None of these' />;
default:
return '';
}
return '';
};
render () {
@ -78,23 +91,21 @@ class ColumnSettings extends React.PureComponent {
<div>
<div className='column-settings__row'>
<div className='setting-toggle'>
<Toggle
id='hashtag.column_settings.tag_toggle'
onChange={this.onToggle}
checked={this.state.open}
/>
<Toggle id='hashtag.column_settings.tag_toggle' onChange={this.onToggle} checked={this.state.open} />
<span className='setting-toggle__label'>
<FormattedMessage id='hashtag.column_settings.tag_toggle' defaultMessage='Include additional tags in this column' />
</span>
</div>
</div>
{this.state.open &&
{this.state.open && (
<div className='column-settings__hashtags'>
{this.modeSelect('any')}
{this.modeSelect('all')}
{this.modeSelect('none')}
</div>
}
)}
</div>
);
}

View File

@ -41,15 +41,19 @@ class HashtagTimeline extends React.PureComponent {
title = () => {
let title = [this.props.params.id];
if (this.additionalFor('any')) {
title.push(' ', <FormattedMessage id='hashtag.column_header.tag_mode.any' values={{ additional: this.additionalFor('any') }} defaultMessage='or {additional}' />);
title.push(' ', <FormattedMessage key='any' id='hashtag.column_header.tag_mode.any' values={{ additional: this.additionalFor('any') }} defaultMessage='or {additional}' />);
}
if (this.additionalFor('all')) {
title.push(' ', <FormattedMessage id='hashtag.column_header.tag_mode.all' values={{ additional: this.additionalFor('all') }} defaultMessage='and {additional}' />);
title.push(' ', <FormattedMessage key='all' id='hashtag.column_header.tag_mode.all' values={{ additional: this.additionalFor('all') }} defaultMessage='and {additional}' />);
}
if (this.additionalFor('none')) {
title.push(' ', <FormattedMessage id='hashtag.column_header.tag_mode.none' values={{ additional: this.additionalFor('none') }} defaultMessage='without {additional}' />);
title.push(' ', <FormattedMessage key='none' id='hashtag.column_header.tag_mode.none' values={{ additional: this.additionalFor('none') }} defaultMessage='without {additional}' />);
}
return title;
}
@ -77,9 +81,10 @@ class HashtagTimeline extends React.PureComponent {
let all = (tags.all || []).map(tag => tag.value);
let none = (tags.none || []).map(tag => tag.value);
[id, ...any].map((tag) => {
this.disconnects.push(dispatch(connectHashtagStream(id, tag, (status) => {
[id, ...any].map(tag => {
this.disconnects.push(dispatch(connectHashtagStream(id, tag, status => {
let tags = status.tags.map(tag => tag.name);
return all.filter(tag => tags.includes(tag)).length === all.length &&
none.filter(tag => tags.includes(tag)).length === 0;
})));
@ -95,12 +100,14 @@ class HashtagTimeline extends React.PureComponent {
const { dispatch } = this.props;
const { id, tags } = this.props.params;
this._subscribe(dispatch, id, tags);
dispatch(expandHashtagTimeline(id, { tags }));
}
componentWillReceiveProps (nextProps) {
const { dispatch, params } = this.props;
const { id, tags } = nextProps.params;
if (id !== params.id || !isEqual(tags, params.tags)) {
this._unsubscribe();
this._subscribe(dispatch, id, tags);

View File

@ -18,6 +18,7 @@ const messages = defineMessages({
const mapStateToProps = state => ({
accountIds: state.getIn(['user_lists', 'mutes', 'items']),
hasMore: !!state.getIn(['user_lists', 'mutes', 'next']),
});
export default @connect(mapStateToProps)
@ -28,6 +29,7 @@ class Mutes extends ImmutablePureComponent {
params: PropTypes.object.isRequired,
dispatch: PropTypes.func.isRequired,
shouldUpdateScroll: PropTypes.func,
hasMore: PropTypes.bool,
accountIds: ImmutablePropTypes.list,
intl: PropTypes.object.isRequired,
};
@ -41,7 +43,7 @@ class Mutes extends ImmutablePureComponent {
}, 300, { leading: true });
render () {
const { intl, shouldUpdateScroll, accountIds } = this.props;
const { intl, shouldUpdateScroll, hasMore, accountIds } = this.props;
if (!accountIds) {
return (
@ -59,6 +61,7 @@ class Mutes extends ImmutablePureComponent {
<ScrollableList
scrollKey='mutes'
onLoadMore={this.handleLoadMore}
hasMore={hasMore}
shouldUpdateScroll={shouldUpdateScroll}
emptyMessage={emptyMessage}
>

View File

@ -34,6 +34,10 @@ class Notification extends ImmutablePureComponent {
onToggleHidden: PropTypes.func.isRequired,
status: PropTypes.option,
intl: PropTypes.object.isRequired,
getScrollPosition: PropTypes.func,
updateScrollBottom: PropTypes.func,
cacheMediaWidth: PropTypes.func,
cachedMediaWidth: PropTypes.number,
};
handleMoveUp = () => {
@ -128,6 +132,10 @@ class Notification extends ImmutablePureComponent {
onMoveDown={this.handleMoveDown}
onMoveUp={this.handleMoveUp}
contextType='notifications'
getScrollPosition={this.props.getScrollPosition}
updateScrollBottom={this.props.updateScrollBottom}
cachedMediaWidth={this.props.cachedMediaWidth}
cacheMediaWidth={this.props.cacheMediaWidth}
/>
);
}
@ -148,7 +156,17 @@ class Notification extends ImmutablePureComponent {
</span>
</div>
<StatusContainer id={notification.get('status')} account={notification.get('account')} muted withDismiss hidden={!!this.props.hidden} />
<StatusContainer
id={notification.get('status')}
account={notification.get('account')}
muted
withDismiss
hidden={!!this.props.hidden}
getScrollPosition={this.props.getScrollPosition}
updateScrollBottom={this.props.updateScrollBottom}
cachedMediaWidth={this.props.cachedMediaWidth}
cacheMediaWidth={this.props.cacheMediaWidth}
/>
</div>
</HotKeys>
);
@ -170,7 +188,17 @@ class Notification extends ImmutablePureComponent {
</span>
</div>
<StatusContainer id={notification.get('status')} account={notification.get('account')} muted withDismiss hidden={this.props.hidden} />
<StatusContainer
id={notification.get('status')}
account={notification.get('account')}
muted
withDismiss
hidden={this.props.hidden}
getScrollPosition={this.props.getScrollPosition}
updateScrollBottom={this.props.updateScrollBottom}
cachedMediaWidth={this.props.cachedMediaWidth}
cacheMediaWidth={this.props.cacheMediaWidth}
/>
</div>
</HotKeys>
);

View File

@ -60,6 +60,8 @@ export default class Card extends React.PureComponent {
maxDescription: PropTypes.number,
onOpenMedia: PropTypes.func.isRequired,
compact: PropTypes.bool,
defaultWidth: PropTypes.number,
cacheWidth: PropTypes.func,
};
static defaultProps = {
@ -68,7 +70,7 @@ export default class Card extends React.PureComponent {
};
state = {
width: 280,
width: this.props.defaultWidth || 280,
embedded: false,
};
@ -111,6 +113,7 @@ export default class Card extends React.PureComponent {
setRef = c => {
if (c) {
if (this.props.cacheWidth) this.props.cacheWidth(c.offsetWidth);
this.setState({ width: c.offsetWidth });
}
}

View File

@ -91,7 +91,7 @@ export default class DetailedStatus extends ImmutablePureComponent {
}
render () {
const status = this.props.status.get('reblog') ? this.props.status.get('reblog') : this.props.status;
const status = (this.props.status && this.props.status.get('reblog')) ? this.props.status.get('reblog') : this.props.status;
const intl = this.props.intl;
const outerStyle = { boxSizing: 'border-box' };
const { compact } = this.props;

View File

@ -99,6 +99,7 @@ class Video extends React.PureComponent {
onCloseVideo: PropTypes.func,
detailed: PropTypes.bool,
inline: PropTypes.bool,
cacheWidth: PropTypes.func,
intl: PropTypes.object.isRequired,
};
@ -108,7 +109,7 @@ class Video extends React.PureComponent {
volume: 0.5,
paused: true,
dragging: false,
containerWidth: false,
containerWidth: this.props.width,
fullscreen: false,
hovered: false,
muted: false,
@ -128,6 +129,7 @@ class Video extends React.PureComponent {
this.player = c;
if (c) {
if (this.props.cacheWidth) this.props.cacheWidth(this.player.offsetWidth);
this.setState({
containerWidth: c.offsetWidth,
});