Merge upstream (#81)

This commit is contained in:
kibigo!
2017-07-15 14:33:15 -07:00
213 changed files with 2714 additions and 1364 deletions

View File

@ -140,7 +140,8 @@ export default class ComposeForm extends ImmutablePureComponent {
handleEmojiPick = (data) => {
const position = this.autosuggestTextarea.textarea.selectionStart;
this._restoreCaret = position + data.shortname.length + 1;
const emojiChar = String.fromCodePoint(parseInt(data.unicode, 16));
this._restoreCaret = position + emojiChar.length + 1;
this.props.onPickEmoji(position, data);
}

View File

@ -109,11 +109,12 @@ export default class EmojiPickerDropdown extends React.PureComponent {
<Dropdown ref={this.setRef} className='emoji-picker__dropdown' onShow={this.onShowDropdown} onHide={this.onHideDropdown}>
<DropdownTrigger className='emoji-button' title={intl.formatMessage(messages.emoji)}>
<img
draggable='false'
className={`emojione ${active && loading ? 'pulse-loading' : ''}`}
alt='🙂' src='/emoji/1f602.svg'
alt='🙂'
src='/emoji/1f602.svg'
/>
</DropdownTrigger>
<DropdownContent className='dropdown__left'>
{
this.state.active && !this.state.loading &&

View File

@ -2,11 +2,11 @@ import React from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import LoadingIndicator from '../../components/loading_indicator';
import { fetchFavouritedStatuses, expandFavouritedStatuses } from '../../actions/favourites';
import Column from '../ui/components/column';
import ColumnHeader from '../../components/column_header';
import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
import StatusList from '../../components/status_list';
import ColumnBackButtonSlim from '../../components/column_back_button_slim';
import { defineMessages, injectIntl } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component';
@ -16,8 +16,6 @@ const messages = defineMessages({
const mapStateToProps = state => ({
statusIds: state.getIn(['status_lists', 'favourites', 'items']),
loaded: state.getIn(['status_lists', 'favourites', 'loaded']),
me: state.getIn(['meta', 'me']),
});
@connect(mapStateToProps)
@ -27,34 +25,64 @@ export default class Favourites extends ImmutablePureComponent {
static propTypes = {
dispatch: PropTypes.func.isRequired,
statusIds: ImmutablePropTypes.list.isRequired,
loaded: PropTypes.bool,
intl: PropTypes.object.isRequired,
me: PropTypes.number.isRequired,
columnId: PropTypes.string,
multiColumn: PropTypes.bool,
};
componentWillMount () {
this.props.dispatch(fetchFavouritedStatuses());
}
handlePin = () => {
const { columnId, dispatch } = this.props;
if (columnId) {
dispatch(removeColumn(columnId));
} else {
dispatch(addColumn('FAVOURITES', {}));
}
}
handleMove = (dir) => {
const { columnId, dispatch } = this.props;
dispatch(moveColumn(columnId, dir));
}
handleHeaderClick = () => {
this.column.scrollTop();
}
setRef = c => {
this.column = c;
}
handleScrollToBottom = () => {
this.props.dispatch(expandFavouritedStatuses());
}
render () {
const { loaded, intl } = this.props;
if (!loaded) {
return (
<Column>
<LoadingIndicator />
</Column>
);
}
const { intl, statusIds, columnId, multiColumn } = this.props;
const pinned = !!columnId;
return (
<Column icon='star' heading={intl.formatMessage(messages.heading)}>
<ColumnBackButtonSlim />
<StatusList {...this.props} scrollKey='favourited_statuses' onScrollToBottom={this.handleScrollToBottom} />
<Column ref={this.setRef}>
<ColumnHeader
icon='star'
title={intl.formatMessage(messages.heading)}
onPin={this.handlePin}
onMove={this.handleMove}
onClick={this.handleHeaderClick}
pinned={pinned}
multiColumn={multiColumn}
/>
<StatusList
trackScroll={!pinned}
statusIds={statusIds}
scrollKey={`favourited_statuses-${columnId}`}
onScrollToBottom={this.handleScrollToBottom}
/>
</Column>
);
}

View File

@ -9,18 +9,27 @@ export default class ColumnSettings extends React.PureComponent {
static propTypes = {
settings: ImmutablePropTypes.map.isRequired,
pushSettings: ImmutablePropTypes.map.isRequired,
onChange: PropTypes.func.isRequired,
onSave: PropTypes.func.isRequired,
onClear: PropTypes.func.isRequired,
};
onPushChange = (key, checked) => {
this.props.onChange(['push', ...key], checked);
}
render () {
const { settings, onChange, onClear } = this.props;
const { settings, pushSettings, onChange, onClear } = this.props;
const alertStr = <FormattedMessage id='notifications.column_settings.alert' defaultMessage='Desktop notifications' />;
const showStr = <FormattedMessage id='notifications.column_settings.show' defaultMessage='Show in column' />;
const soundStr = <FormattedMessage id='notifications.column_settings.sound' defaultMessage='Play sound' />;
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>
<div className='column-settings__row'>
@ -30,7 +39,8 @@ export default class ColumnSettings extends React.PureComponent {
<span className='column-settings__section'><FormattedMessage id='notifications.column_settings.follow' defaultMessage='New followers:' /></span>
<div className='column-settings__row'>
<SettingToggle prefix='notifications' settings={settings} settingKey={['alerts', 'follow']} onChange={onChange} label={alertStr} />
<SettingToggle prefix='notifications_desktop' settings={settings} settingKey={['alerts', 'follow']} onChange={onChange} label={alertStr} />
{showPushSettings && <SettingToggle prefix='notifications_push' settings={pushSettings} settingKey={['alerts', 'follow']} meta={pushMeta} onChange={this.onPushChange} label={pushStr} />}
<SettingToggle prefix='notifications' settings={settings} settingKey={['shows', 'follow']} onChange={onChange} label={showStr} />
<SettingToggle prefix='notifications' settings={settings} settingKey={['sounds', 'follow']} onChange={onChange} label={soundStr} />
</div>
@ -38,7 +48,8 @@ export default class ColumnSettings extends React.PureComponent {
<span className='column-settings__section'><FormattedMessage id='notifications.column_settings.favourite' defaultMessage='Favourites:' /></span>
<div className='column-settings__row'>
<SettingToggle prefix='notifications' settings={settings} settingKey={['alerts', 'favourite']} onChange={onChange} label={alertStr} />
<SettingToggle prefix='notifications_desktop' settings={settings} settingKey={['alerts', 'favourite']} onChange={onChange} label={alertStr} />
{showPushSettings && <SettingToggle prefix='notifications_push' settings={pushSettings} settingKey={['alerts', 'favourite']} meta={pushMeta} onChange={this.onPushChange} label={pushStr} />}
<SettingToggle prefix='notifications' settings={settings} settingKey={['shows', 'favourite']} onChange={onChange} label={showStr} />
<SettingToggle prefix='notifications' settings={settings} settingKey={['sounds', 'favourite']} onChange={onChange} label={soundStr} />
</div>
@ -46,7 +57,8 @@ export default class ColumnSettings extends React.PureComponent {
<span className='column-settings__section'><FormattedMessage id='notifications.column_settings.mention' defaultMessage='Mentions:' /></span>
<div className='column-settings__row'>
<SettingToggle prefix='notifications' settings={settings} settingKey={['alerts', 'mention']} onChange={onChange} label={alertStr} />
<SettingToggle prefix='notifications_desktop' settings={settings} settingKey={['alerts', 'mention']} onChange={onChange} label={alertStr} />
{showPushSettings && <SettingToggle prefix='notifications_push' settings={pushSettings} settingKey={['alerts', 'mention']} meta={pushMeta} onChange={this.onPushChange} label={pushStr} />}
<SettingToggle prefix='notifications' settings={settings} settingKey={['shows', 'mention']} onChange={onChange} label={showStr} />
<SettingToggle prefix='notifications' settings={settings} settingKey={['sounds', 'mention']} onChange={onChange} label={soundStr} />
</div>
@ -54,7 +66,8 @@ export default class ColumnSettings extends React.PureComponent {
<span className='column-settings__section'><FormattedMessage id='notifications.column_settings.reblog' defaultMessage='Boosts:' /></span>
<div className='column-settings__row'>
<SettingToggle prefix='notifications' settings={settings} settingKey={['alerts', 'reblog']} onChange={onChange} label={alertStr} />
<SettingToggle prefix='notifications_desktop' settings={settings} settingKey={['alerts', 'reblog']} onChange={onChange} label={alertStr} />
{showPushSettings && <SettingToggle prefix='notifications_push' settings={pushSettings} settingKey={['alerts', 'reblog']} meta={pushMeta} onChange={this.onPushChange} label={pushStr} />}
<SettingToggle prefix='notifications' settings={settings} settingKey={['shows', 'reblog']} onChange={onChange} label={showStr} />
<SettingToggle prefix='notifications' settings={settings} settingKey={['sounds', 'reblog']} onChange={onChange} label={soundStr} />
</div>

View File

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

View File

@ -3,6 +3,7 @@ import { defineMessages, injectIntl } from 'react-intl';
import ColumnSettings from '../components/column_settings';
import { changeSetting, saveSettings } from '../../../actions/settings';
import { clearNotifications } from '../../../actions/notifications';
import { changeAlerts as changePushNotifications, saveSettings as savePushNotificationSettings } from '../../../actions/push_notifications';
import { openModal } from '../../../actions/modal';
const messages = defineMessages({
@ -12,16 +13,22 @@ const messages = defineMessages({
const mapStateToProps = state => ({
settings: state.getIn(['settings', 'notifications']),
pushSettings: state.get('push_notifications'),
});
const mapDispatchToProps = (dispatch, { intl }) => ({
onChange (key, checked) {
dispatch(changeSetting(['notifications', ...key], checked));
if (key[0] === 'push') {
dispatch(changePushNotifications(key.slice(1), checked));
} else {
dispatch(changeSetting(['notifications', ...key], checked));
}
},
onSave () {
dispatch(saveSettings());
dispatch(savePushNotificationSettings());
},
onClear () {

View File

@ -9,7 +9,7 @@ import { links, getIndex, getLink } from './tabs_bar';
import BundleContainer from '../containers/bundle_container';
import ColumnLoading from './column_loading';
import BundleColumnError from './bundle_column_error';
import { Compose, Notifications, HomeTimeline, CommunityTimeline, PublicTimeline, HashtagTimeline } from '../../ui/util/async-components';
import { Compose, Notifications, HomeTimeline, CommunityTimeline, PublicTimeline, HashtagTimeline, FavouritedStatuses } from '../../ui/util/async-components';
const componentMap = {
'COMPOSE': Compose,
@ -18,6 +18,7 @@ const componentMap = {
'PUBLIC': PublicTimeline,
'COMMUNITY': CommunityTimeline,
'HASHTAG': HashtagTimeline,
'FAVOURITES': FavouritedStatuses,
};
export default class ColumnsArea extends ImmutablePureComponent {
@ -32,12 +33,33 @@ export default class ColumnsArea extends ImmutablePureComponent {
children: PropTypes.node,
};
state = {
shouldAnimate: false,
}
componentWillReceiveProps() {
this.setState({ shouldAnimate: false });
}
componentDidMount() {
this.lastIndex = getIndex(this.context.router.history.location.pathname);
this.setState({ shouldAnimate: true });
}
componentDidUpdate() {
this.lastIndex = getIndex(this.context.router.history.location.pathname);
this.setState({ shouldAnimate: true });
}
handleSwipe = (index) => {
window.requestAnimationFrame(() => {
window.requestAnimationFrame(() => {
this.context.router.history.push(getLink(index));
});
});
this.pendingIndex = index;
}
handleAnimationEnd = () => {
if (typeof this.pendingIndex === 'number') {
this.context.router.history.push(getLink(this.pendingIndex));
this.pendingIndex = null;
}
}
renderView = (link, index) => {
@ -66,12 +88,14 @@ export default class ColumnsArea extends ImmutablePureComponent {
render () {
const { columns, children, singleColumn } = this.props;
const { shouldAnimate } = this.state;
const columnIndex = getIndex(this.context.router.history.location.pathname);
this.pendingIndex = null;
if (singleColumn) {
return columnIndex !== -1 ? (
<ReactSwipeableViews index={columnIndex} onChangeIndex={this.handleSwipe} animateTransitions={false} style={{ height: '100%' }}>
<ReactSwipeableViews index={columnIndex} onChangeIndex={this.handleSwipe} onTransitionEnd={this.handleAnimationEnd} animateTransitions={shouldAnimate} springConfig={{ duration: '400ms', delay: '0s', easeFunction: 'ease' }} style={{ height: '100%' }}>
{links.map(this.renderView)}
</ReactSwipeableViews>
) : <div className='columns-area'>{children}</div>;

View File

@ -65,8 +65,6 @@ export default class MediaModal extends ImmutablePureComponent {
const { media, intl, onClose } = this.props;
const index = this.getIndex();
const attachment = media.get(index);
const url = attachment.get('url');
let leftNav, rightNav, content;
@ -77,16 +75,18 @@ export default class MediaModal extends ImmutablePureComponent {
rightNav = <div role='button' tabIndex='0' className='modal-container__nav modal-container__nav--right' onClick={this.handleNextClick}><i className='fa fa-fw fa-chevron-right' /></div>;
}
if (attachment.get('type') === 'image') {
content = media.map((image) => {
const width = image.getIn(['meta', 'original', 'width']) || null;
const height = image.getIn(['meta', 'original', 'height']) || null;
content = media.map((image) => {
const width = image.getIn(['meta', 'original', 'width']) || null;
const height = image.getIn(['meta', 'original', 'height']) || null;
if (image.get('type') === 'image') {
return <ImageLoader previewSrc={image.get('preview_url')} src={image.get('url')} width={width} height={height} key={image.get('preview_url')} />;
}).toArray();
} else if (attachment.get('type') === 'gifv') {
content = <ExtendedVideoPlayer src={url} muted controls={false} />;
}
} else if (image.get('type') === 'gifv') {
return <ExtendedVideoPlayer src={image.get('url')} muted controls={false} width={width} height={height} key={image.get('preview_url')} />;
}
return null;
}).toArray();
return (
<div className='modal-root__modal media-modal'>

View File

@ -56,12 +56,6 @@ export default class ModalRoot extends React.PureComponent {
return { opacity: spring(0), scale: spring(0.98) };
}
renderModal = (SpecificComponent) => {
const { props, onClose } = this.props;
return <SpecificComponent {...props} onClose={onClose} />;
}
renderLoading = () => {
return <ModalLoading />;
}
@ -97,7 +91,9 @@ export default class ModalRoot extends React.PureComponent {
<div key={key} style={{ pointerEvents: visible ? 'auto' : 'none' }}>
<div role='presentation' className='modal-root__overlay' style={{ opacity: style.opacity }} onClick={onClose} />
<div className='modal-root__container' style={{ opacity: style.opacity, transform: `translateZ(0px) scale(${style.scale})` }}>
<BundleContainer fetchComponent={MODAL_COMPONENTS[type]} loading={this.renderLoading} error={this.renderError} renderDelay={200}>{this.renderModal}</BundleContainer>
<BundleContainer fetchComponent={MODAL_COMPONENTS[type]} loading={this.renderLoading} error={this.renderError} renderDelay={200}>
{(SpecificComponent) => <SpecificComponent {...props} onClose={onClose} />}
</BundleContainer>
</div>
</div>
))}