Merge upstream (#81)
This commit is contained in:
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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 &&
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
@ -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 () {
|
||||
|
@ -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>;
|
||||
|
@ -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'>
|
||||
|
@ -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>
|
||||
))}
|
||||
|
Reference in New Issue
Block a user