Merge 2.5.0rc1 from upstream
This commit is contained in:
commit
c29f828897
520 changed files with 15340 additions and 5799 deletions
|
|
@ -32,6 +32,8 @@ const messages = defineMessages({
|
|||
blocks: { id: 'navigation_bar.blocks', defaultMessage: 'Blocked users' },
|
||||
domain_blocks: { id: 'navigation_bar.domain_blocks', defaultMessage: 'Hidden domains' },
|
||||
mutes: { id: 'navigation_bar.mutes', defaultMessage: 'Muted users' },
|
||||
endorse: { id: 'account.endorse', defaultMessage: 'Feature on profile' },
|
||||
unendorse: { id: 'account.unendorse', defaultMessage: 'Don\'t feature on profile' },
|
||||
});
|
||||
|
||||
@injectIntl
|
||||
|
|
@ -48,6 +50,7 @@ export default class ActionBar extends React.PureComponent {
|
|||
onMute: PropTypes.func.isRequired,
|
||||
onBlockDomain: PropTypes.func.isRequired,
|
||||
onUnblockDomain: PropTypes.func.isRequired,
|
||||
onEndorseToggle: PropTypes.func.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
|
|
@ -93,6 +96,9 @@ export default class ActionBar extends React.PureComponent {
|
|||
} else {
|
||||
menu.push({ text: intl.formatMessage(messages.showReblogs, { name: account.get('username') }), action: this.props.onReblogToggle });
|
||||
}
|
||||
|
||||
menu.push({ text: intl.formatMessage(account.getIn(['relationship', 'endorsed']) ? messages.unendorse : messages.endorse), action: this.props.onEndorseToggle });
|
||||
menu.push(null);
|
||||
}
|
||||
|
||||
if (account.getIn(['relationship', 'muting'])) {
|
||||
|
|
@ -141,18 +147,18 @@ 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')}`}>
|
||||
<span><FormattedMessage id='account.posts' defaultMessage='Toots' /></span>
|
||||
<Link 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>
|
||||
|
||||
<Link className='account__action-bar__tab' to={`/accounts/${account.get('id')}/following`}>
|
||||
<span><FormattedMessage id='account.follows' defaultMessage='Follows' /></span>
|
||||
<Link 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>
|
||||
|
||||
<Link className='account__action-bar__tab' to={`/accounts/${account.get('id')}/followers`}>
|
||||
<span><FormattedMessage id='account.followers' defaultMessage='Followers' /></span>
|
||||
<Link 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>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -104,7 +104,9 @@ export default class Header extends ImmutablePureComponent {
|
|||
}
|
||||
|
||||
if (me !== account.get('id')) {
|
||||
if (account.getIn(['relationship', 'requested'])) {
|
||||
if (!account.get('relationship')) { // Wait until the relationship is loaded
|
||||
actionBtn = '';
|
||||
} else if (account.getIn(['relationship', 'requested'])) {
|
||||
actionBtn = (
|
||||
<div className='account--action-button'>
|
||||
<IconButton size={26} active icon='hourglass' title={intl.formatMessage(messages.requested)} onClick={this.props.onFollow} />
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ const mapStateToProps = (state, props) => ({
|
|||
class LoadMoreMedia extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
shouldUpdateScroll: PropTypes.func,
|
||||
maxId: PropTypes.string,
|
||||
onLoadMore: PropTypes.func.isRequired,
|
||||
};
|
||||
|
|
@ -90,7 +91,7 @@ export default class AccountGallery extends ImmutablePureComponent {
|
|||
}
|
||||
|
||||
render () {
|
||||
const { medias, isLoading, hasMore } = this.props;
|
||||
const { medias, shouldUpdateScroll, isLoading, hasMore } = this.props;
|
||||
|
||||
let loadOlder = null;
|
||||
|
||||
|
|
@ -110,7 +111,7 @@ export default class AccountGallery extends ImmutablePureComponent {
|
|||
<Column>
|
||||
<ColumnBackButton />
|
||||
|
||||
<ScrollContainer scrollKey='account_gallery'>
|
||||
<ScrollContainer scrollKey='account_gallery' shouldUpdateScroll={shouldUpdateScroll}>
|
||||
<div className='scrollable' onScroll={this.handleScroll}>
|
||||
<HeaderContainer accountId={this.props.params.accountId} />
|
||||
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ export default class Header extends ImmutablePureComponent {
|
|||
onMute: PropTypes.func.isRequired,
|
||||
onBlockDomain: PropTypes.func.isRequired,
|
||||
onUnblockDomain: PropTypes.func.isRequired,
|
||||
onEndorseToggle: PropTypes.func.isRequired,
|
||||
hideTabs: PropTypes.bool,
|
||||
};
|
||||
|
||||
|
|
@ -73,6 +74,10 @@ export default class Header extends ImmutablePureComponent {
|
|||
this.props.onUnblockDomain(domain);
|
||||
}
|
||||
|
||||
handleEndorseToggle = () => {
|
||||
this.props.onEndorseToggle(this.props.account);
|
||||
}
|
||||
|
||||
render () {
|
||||
const { account, hideTabs } = this.props;
|
||||
|
||||
|
|
@ -100,6 +105,7 @@ export default class Header extends ImmutablePureComponent {
|
|||
onMute={this.handleMute}
|
||||
onBlockDomain={this.handleBlockDomain}
|
||||
onUnblockDomain={this.handleUnblockDomain}
|
||||
onEndorseToggle={this.handleEndorseToggle}
|
||||
/>
|
||||
|
||||
{!hideTabs && (
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ import {
|
|||
blockAccount,
|
||||
unblockAccount,
|
||||
unmuteAccount,
|
||||
pinAccount,
|
||||
unpinAccount,
|
||||
} from '../../../actions/accounts';
|
||||
import {
|
||||
mentionCompose,
|
||||
|
|
@ -82,6 +84,14 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
|
|||
}
|
||||
},
|
||||
|
||||
onEndorseToggle (account) {
|
||||
if (account.getIn(['relationship', 'endorsed'])) {
|
||||
dispatch(unpinAccount(account.get('id')));
|
||||
} else {
|
||||
dispatch(pinAccount(account.get('id')));
|
||||
}
|
||||
},
|
||||
|
||||
onReport (account) {
|
||||
dispatch(initReport(account));
|
||||
},
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ export default class AccountTimeline extends ImmutablePureComponent {
|
|||
static propTypes = {
|
||||
params: PropTypes.object.isRequired,
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
shouldUpdateScroll: PropTypes.func,
|
||||
statusIds: ImmutablePropTypes.list,
|
||||
featuredStatusIds: ImmutablePropTypes.list,
|
||||
isLoading: PropTypes.bool,
|
||||
|
|
@ -61,7 +62,7 @@ export default class AccountTimeline extends ImmutablePureComponent {
|
|||
}
|
||||
|
||||
render () {
|
||||
const { statusIds, featuredStatusIds, isLoading, hasMore } = this.props;
|
||||
const { shouldUpdateScroll, statusIds, featuredStatusIds, isLoading, hasMore } = this.props;
|
||||
|
||||
if (!statusIds && isLoading) {
|
||||
return (
|
||||
|
|
@ -83,6 +84,7 @@ export default class AccountTimeline extends ImmutablePureComponent {
|
|||
isLoading={isLoading}
|
||||
hasMore={hasMore}
|
||||
onLoadMore={this.handleLoadMore}
|
||||
shouldUpdateScroll={shouldUpdateScroll}
|
||||
/>
|
||||
</Column>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,15 +1,16 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { debounce } from 'lodash';
|
||||
import PropTypes from 'prop-types';
|
||||
import LoadingIndicator from '../../components/loading_indicator';
|
||||
import { ScrollContainer } from 'react-router-scroll-4';
|
||||
import Column from '../ui/components/column';
|
||||
import ColumnBackButtonSlim from '../../components/column_back_button_slim';
|
||||
import AccountContainer from '../../containers/account_container';
|
||||
import { fetchBlocks, expandBlocks } from '../../actions/blocks';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import ScrollableList from '../../components/scrollable_list';
|
||||
|
||||
const messages = defineMessages({
|
||||
heading: { id: 'column.blocks', defaultMessage: 'Blocked users' },
|
||||
|
|
@ -26,6 +27,7 @@ export default class Blocks extends ImmutablePureComponent {
|
|||
static propTypes = {
|
||||
params: PropTypes.object.isRequired,
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
shouldUpdateScroll: PropTypes.func,
|
||||
accountIds: ImmutablePropTypes.list,
|
||||
intl: PropTypes.object.isRequired,
|
||||
};
|
||||
|
|
@ -34,16 +36,12 @@ export default class Blocks extends ImmutablePureComponent {
|
|||
this.props.dispatch(fetchBlocks());
|
||||
}
|
||||
|
||||
handleScroll = (e) => {
|
||||
const { scrollTop, scrollHeight, clientHeight } = e.target;
|
||||
|
||||
if (scrollTop === scrollHeight - clientHeight) {
|
||||
this.props.dispatch(expandBlocks());
|
||||
}
|
||||
}
|
||||
handleLoadMore = debounce(() => {
|
||||
this.props.dispatch(expandBlocks());
|
||||
}, 300, { leading: true });
|
||||
|
||||
render () {
|
||||
const { intl, accountIds } = this.props;
|
||||
const { intl, accountIds, shouldUpdateScroll } = this.props;
|
||||
|
||||
if (!accountIds) {
|
||||
return (
|
||||
|
|
@ -53,16 +51,21 @@ export default class Blocks extends ImmutablePureComponent {
|
|||
);
|
||||
}
|
||||
|
||||
const emptyMessage = <FormattedMessage id='empty_column.blocks' defaultMessage="You haven't blocked any users yet." />;
|
||||
|
||||
return (
|
||||
<Column icon='ban' heading={intl.formatMessage(messages.heading)}>
|
||||
<ColumnBackButtonSlim />
|
||||
<ScrollContainer scrollKey='blocks'>
|
||||
<div className='scrollable' onScroll={this.handleScroll}>
|
||||
{accountIds.map(id =>
|
||||
<AccountContainer key={id} id={id} />
|
||||
)}
|
||||
</div>
|
||||
</ScrollContainer>
|
||||
<ScrollableList
|
||||
scrollKey='blocks'
|
||||
onLoadMore={this.handleLoadMore}
|
||||
shouldUpdateScroll={shouldUpdateScroll}
|
||||
emptyMessage={emptyMessage}
|
||||
>
|
||||
{accountIds.map(id =>
|
||||
<AccountContainer key={id} id={id} />
|
||||
)}
|
||||
</ScrollableList>
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ export default class CommunityTimeline extends React.PureComponent {
|
|||
|
||||
static propTypes = {
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
shouldUpdateScroll: PropTypes.func,
|
||||
columnId: PropTypes.string,
|
||||
intl: PropTypes.object.isRequired,
|
||||
hasUnread: PropTypes.bool,
|
||||
|
|
@ -100,11 +101,11 @@ export default class CommunityTimeline extends React.PureComponent {
|
|||
}
|
||||
|
||||
render () {
|
||||
const { intl, hasUnread, columnId, multiColumn, onlyMedia } = this.props;
|
||||
const { intl, shouldUpdateScroll, hasUnread, columnId, multiColumn, onlyMedia } = this.props;
|
||||
const pinned = !!columnId;
|
||||
|
||||
return (
|
||||
<Column ref={this.setRef}>
|
||||
<Column ref={this.setRef} label={intl.formatMessage(messages.title)}>
|
||||
<ColumnHeader
|
||||
icon='users'
|
||||
active={hasUnread}
|
||||
|
|
@ -124,6 +125,7 @@ export default class CommunityTimeline extends React.PureComponent {
|
|||
timelineId={`community${onlyMedia ? ':media' : ''}`}
|
||||
onLoadMore={this.handleLoadMore}
|
||||
emptyMessage={<FormattedMessage id='empty_column.community' defaultMessage='The local timeline is empty. Write something publicly to get the ball rolling!' />}
|
||||
shouldUpdateScroll={shouldUpdateScroll}
|
||||
/>
|
||||
</Column>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ class PrivacyDropdownMenu extends React.PureComponent {
|
|||
style: PropTypes.object,
|
||||
items: PropTypes.array.isRequired,
|
||||
value: PropTypes.string.isRequired,
|
||||
placement: PropTypes.string.isRequired,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
};
|
||||
|
|
@ -119,7 +120,7 @@ class PrivacyDropdownMenu extends React.PureComponent {
|
|||
|
||||
render () {
|
||||
const { mounted } = this.state;
|
||||
const { style, items, value } = this.props;
|
||||
const { style, items, placement, value } = this.props;
|
||||
|
||||
return (
|
||||
<Motion defaultStyle={{ opacity: 0, scaleX: 0.85, scaleY: 0.75 }} style={{ opacity: spring(1, { damping: 35, stiffness: 400 }), scaleX: spring(1, { damping: 35, stiffness: 400 }), scaleY: spring(1, { damping: 35, stiffness: 400 }) }}>
|
||||
|
|
@ -127,7 +128,7 @@ class PrivacyDropdownMenu extends React.PureComponent {
|
|||
// It should not be transformed when mounting because the resulting
|
||||
// size will be used to determine the coordinate of the menu by
|
||||
// react-overlays
|
||||
<div className='privacy-dropdown__dropdown' style={{ ...style, opacity: opacity, transform: mounted ? `scale(${scaleX}, ${scaleY})` : null }} role='listbox' ref={this.setRef}>
|
||||
<div className={`privacy-dropdown__dropdown ${placement}`} style={{ ...style, opacity: opacity, transform: mounted ? `scale(${scaleX}, ${scaleY})` : null }} role='listbox' ref={this.setRef}>
|
||||
{items.map(item => (
|
||||
<div role='option' tabIndex='0' key={item.value} data-index={item.value} onKeyDown={this.handleKeyDown} onClick={this.handleClick} className={classNames('privacy-dropdown__option', { active: item.value === value })} aria-selected={item.value === value} ref={item.value === value ? this.setFocusRef : null}>
|
||||
<div className='privacy-dropdown__option__icon'>
|
||||
|
|
@ -226,7 +227,7 @@ export default class PrivacyDropdown extends React.PureComponent {
|
|||
const valueOption = this.options.find(item => item.value === value);
|
||||
|
||||
return (
|
||||
<div className={classNames('privacy-dropdown', { active: open })} onKeyDown={this.handleKeyDown}>
|
||||
<div className={classNames('privacy-dropdown', placement, { active: open })} onKeyDown={this.handleKeyDown}>
|
||||
<div className={classNames('privacy-dropdown__value', { active: this.options.indexOf(valueOption) === 0 })}>
|
||||
<IconButton
|
||||
className='privacy-dropdown__value-icon'
|
||||
|
|
@ -247,6 +248,7 @@ export default class PrivacyDropdown extends React.PureComponent {
|
|||
value={value}
|
||||
onClose={this.handleClose}
|
||||
onChange={this.handleChange}
|
||||
placement={placement}
|
||||
/>
|
||||
</Overlay>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ export default class ReplyIndicator extends ImmutablePureComponent {
|
|||
}
|
||||
|
||||
handleAccountClick = (e) => {
|
||||
if (e.button === 0) {
|
||||
if (e.button === 0 && !(e.ctrlKey || e.metaKey)) {
|
||||
e.preventDefault();
|
||||
this.context.router.history.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ export default class Upload extends ImmutablePureComponent {
|
|||
onUndo: PropTypes.func.isRequired,
|
||||
onDescriptionChange: PropTypes.func.isRequired,
|
||||
onOpenFocalPoint: PropTypes.func.isRequired,
|
||||
onSubmit: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
state = {
|
||||
|
|
@ -28,6 +29,17 @@ export default class Upload extends ImmutablePureComponent {
|
|||
dirtyDescription: null,
|
||||
};
|
||||
|
||||
handleKeyDown = (e) => {
|
||||
if (e.keyCode === 13 && (e.ctrlKey || e.metaKey)) {
|
||||
this.handleSubmit();
|
||||
}
|
||||
}
|
||||
|
||||
handleSubmit = () => {
|
||||
this.handleInputBlur();
|
||||
this.props.onSubmit();
|
||||
}
|
||||
|
||||
handleUndoClick = () => {
|
||||
this.props.onUndo(this.props.media.get('id'));
|
||||
}
|
||||
|
|
@ -93,6 +105,7 @@ export default class Upload extends ImmutablePureComponent {
|
|||
onFocus={this.handleInputFocus}
|
||||
onChange={this.handleInputChange}
|
||||
onBlur={this.handleInputBlur}
|
||||
onKeyDown={this.handleKeyDown}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
|
|||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
|
||||
const messages = defineMessages({
|
||||
upload: { id: 'upload_button.label', defaultMessage: 'Add media' },
|
||||
upload: { id: 'upload_button.label', defaultMessage: 'Add media (JPEG, PNG, GIF, WebM, MP4)' },
|
||||
});
|
||||
|
||||
const makeMapStateToProps = () => {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { connect } from 'react-redux';
|
|||
import Upload from '../components/upload';
|
||||
import { undoUploadCompose, changeUploadCompose } from '../../../actions/compose';
|
||||
import { openModal } from '../../../actions/modal';
|
||||
import { submitCompose } from '../../../actions/compose';
|
||||
|
||||
const mapStateToProps = (state, { id }) => ({
|
||||
media: state.getIn(['compose', 'media_attachments']).find(item => item.get('id') === id),
|
||||
|
|
@ -21,6 +22,10 @@ const mapDispatchToProps = dispatch => ({
|
|||
dispatch(openModal('FOCAL_POINT', { id }));
|
||||
},
|
||||
|
||||
onSubmit () {
|
||||
dispatch(submitCompose());
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(Upload);
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ const messages = defineMessages({
|
|||
community: { id: 'navigation_bar.community_timeline', defaultMessage: 'Local timeline' },
|
||||
preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' },
|
||||
logout: { id: 'navigation_bar.logout', defaultMessage: 'Logout' },
|
||||
compose: { id: 'navigation_bar.compose', defaultMessage: 'Compose new toot' },
|
||||
});
|
||||
|
||||
const mapStateToProps = (state, ownProps) => ({
|
||||
|
|
@ -95,7 +96,7 @@ export default class Compose extends React.PureComponent {
|
|||
}
|
||||
|
||||
return (
|
||||
<div className='drawer'>
|
||||
<div className='drawer' role='region' aria-label={intl.formatMessage(messages.compose)}>
|
||||
{header}
|
||||
|
||||
{(multiColumn || isSearchPage) && <SearchContainer /> }
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ export default class DirectTimeline extends React.PureComponent {
|
|||
|
||||
static propTypes = {
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
shouldUpdateScroll: PropTypes.func,
|
||||
columnId: PropTypes.string,
|
||||
intl: PropTypes.object.isRequired,
|
||||
hasUnread: PropTypes.bool,
|
||||
|
|
@ -71,11 +72,11 @@ export default class DirectTimeline extends React.PureComponent {
|
|||
}
|
||||
|
||||
render () {
|
||||
const { intl, hasUnread, columnId, multiColumn } = this.props;
|
||||
const { intl, shouldUpdateScroll, hasUnread, columnId, multiColumn } = this.props;
|
||||
const pinned = !!columnId;
|
||||
|
||||
return (
|
||||
<Column ref={this.setRef}>
|
||||
<Column ref={this.setRef} label={intl.formatMessage(messages.title)}>
|
||||
<ColumnHeader
|
||||
icon='envelope'
|
||||
active={hasUnread}
|
||||
|
|
@ -93,6 +94,7 @@ export default class DirectTimeline extends React.PureComponent {
|
|||
timelineId='direct'
|
||||
onLoadMore={this.handleLoadMore}
|
||||
emptyMessage={<FormattedMessage id='empty_column.direct' defaultMessage="You don't have any direct messages yet. When you send or receive one, it will show up here." />}
|
||||
shouldUpdateScroll={shouldUpdateScroll}
|
||||
/>
|
||||
</Column>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import PropTypes from 'prop-types';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { debounce } from 'lodash';
|
||||
import LoadingIndicator from '../../components/loading_indicator';
|
||||
import Column from '../ui/components/column';
|
||||
import ColumnBackButtonSlim from '../../components/column_back_button_slim';
|
||||
import DomainContainer from '../../containers/domain_container';
|
||||
import { fetchDomainBlocks, expandDomainBlocks } from '../../actions/domain_blocks';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { debounce } from 'lodash';
|
||||
import ScrollableList from '../../components/scrollable_list';
|
||||
|
||||
const messages = defineMessages({
|
||||
|
|
@ -28,6 +28,7 @@ export default class Blocks extends ImmutablePureComponent {
|
|||
static propTypes = {
|
||||
params: PropTypes.object.isRequired,
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
shouldUpdateScroll: PropTypes.func,
|
||||
domains: ImmutablePropTypes.orderedSet,
|
||||
intl: PropTypes.object.isRequired,
|
||||
};
|
||||
|
|
@ -41,7 +42,7 @@ export default class Blocks extends ImmutablePureComponent {
|
|||
}, 300, { leading: true });
|
||||
|
||||
render () {
|
||||
const { intl, domains } = this.props;
|
||||
const { intl, domains, shouldUpdateScroll } = this.props;
|
||||
|
||||
if (!domains) {
|
||||
return (
|
||||
|
|
@ -51,10 +52,17 @@ export default class Blocks extends ImmutablePureComponent {
|
|||
);
|
||||
}
|
||||
|
||||
const emptyMessage = <FormattedMessage id='empty_column.domain_blocks' defaultMessage='There are no hidden domains yet.' />;
|
||||
|
||||
return (
|
||||
<Column icon='minus-circle' heading={intl.formatMessage(messages.heading)}>
|
||||
<ColumnBackButtonSlim />
|
||||
<ScrollableList scrollKey='domain_blocks' onLoadMore={this.handleLoadMore}>
|
||||
<ScrollableList
|
||||
scrollKey='domain_blocks'
|
||||
onLoadMore={this.handleLoadMore}
|
||||
shouldUpdateScroll={shouldUpdateScroll}
|
||||
emptyMessage={emptyMessage}
|
||||
>
|
||||
{domains.map(domain =>
|
||||
<DomainContainer key={domain} domain={domain} />
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ 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 { defineMessages, injectIntl } from 'react-intl';
|
||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { debounce } from 'lodash';
|
||||
|
||||
|
|
@ -27,6 +27,7 @@ export default class Favourites extends ImmutablePureComponent {
|
|||
|
||||
static propTypes = {
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
shouldUpdateScroll: PropTypes.func,
|
||||
statusIds: ImmutablePropTypes.list.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
columnId: PropTypes.string,
|
||||
|
|
@ -67,11 +68,13 @@ export default class Favourites extends ImmutablePureComponent {
|
|||
}, 300, { leading: true })
|
||||
|
||||
render () {
|
||||
const { intl, statusIds, columnId, multiColumn, hasMore, isLoading } = this.props;
|
||||
const { intl, shouldUpdateScroll, statusIds, columnId, multiColumn, hasMore, isLoading } = this.props;
|
||||
const pinned = !!columnId;
|
||||
|
||||
const emptyMessage = <FormattedMessage id='empty_column.favourited_statuses' defaultMessage="You don't have any favourite toots yet. When you favourite one, it will show up here." />;
|
||||
|
||||
return (
|
||||
<Column ref={this.setRef}>
|
||||
<Column ref={this.setRef} label={intl.formatMessage(messages.heading)}>
|
||||
<ColumnHeader
|
||||
icon='star'
|
||||
title={intl.formatMessage(messages.heading)}
|
||||
|
|
@ -90,6 +93,8 @@ export default class Favourites extends ImmutablePureComponent {
|
|||
hasMore={hasMore}
|
||||
isLoading={isLoading}
|
||||
onLoadMore={this.handleLoadMore}
|
||||
shouldUpdateScroll={shouldUpdateScroll}
|
||||
emptyMessage={emptyMessage}
|
||||
/>
|
||||
</Column>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,14 +1,15 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import PropTypes from 'prop-types';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import LoadingIndicator from '../../components/loading_indicator';
|
||||
import { fetchFavourites } from '../../actions/interactions';
|
||||
import { ScrollContainer } from 'react-router-scroll-4';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import AccountContainer from '../../containers/account_container';
|
||||
import Column from '../ui/components/column';
|
||||
import ColumnBackButton from '../../components/column_back_button';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import ScrollableList from '../../components/scrollable_list';
|
||||
|
||||
const mapStateToProps = (state, props) => ({
|
||||
accountIds: state.getIn(['user_lists', 'favourited_by', props.params.statusId]),
|
||||
|
|
@ -20,6 +21,7 @@ export default class Favourites extends ImmutablePureComponent {
|
|||
static propTypes = {
|
||||
params: PropTypes.object.isRequired,
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
shouldUpdateScroll: PropTypes.func,
|
||||
accountIds: ImmutablePropTypes.list,
|
||||
};
|
||||
|
||||
|
|
@ -34,7 +36,7 @@ export default class Favourites extends ImmutablePureComponent {
|
|||
}
|
||||
|
||||
render () {
|
||||
const { accountIds } = this.props;
|
||||
const { shouldUpdateScroll, accountIds } = this.props;
|
||||
|
||||
if (!accountIds) {
|
||||
return (
|
||||
|
|
@ -44,15 +46,21 @@ export default class Favourites extends ImmutablePureComponent {
|
|||
);
|
||||
}
|
||||
|
||||
const emptyMessage = <FormattedMessage id='empty_column.favourites' defaultMessage='No one has favourited this toot yet. When someone does, they will show up here.' />;
|
||||
|
||||
return (
|
||||
<Column>
|
||||
<ColumnBackButton />
|
||||
|
||||
<ScrollContainer scrollKey='favourites'>
|
||||
<div className='scrollable'>
|
||||
{accountIds.map(id => <AccountContainer key={id} id={id} withNote={false} />)}
|
||||
</div>
|
||||
</ScrollContainer>
|
||||
<ScrollableList
|
||||
scrollKey='favourites'
|
||||
shouldUpdateScroll={shouldUpdateScroll}
|
||||
emptyMessage={emptyMessage}
|
||||
>
|
||||
{accountIds.map(id =>
|
||||
<AccountContainer key={id} id={id} withNote={false} />
|
||||
)}
|
||||
</ScrollableList>
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,16 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import PropTypes from 'prop-types';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { debounce } from 'lodash';
|
||||
import LoadingIndicator from '../../components/loading_indicator';
|
||||
import { ScrollContainer } from 'react-router-scroll-4';
|
||||
import Column from '../ui/components/column';
|
||||
import ColumnBackButtonSlim from '../../components/column_back_button_slim';
|
||||
import AccountAuthorizeContainer from './containers/account_authorize_container';
|
||||
import { fetchFollowRequests, expandFollowRequests } from '../../actions/accounts';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import ScrollableList from '../../components/scrollable_list';
|
||||
|
||||
const messages = defineMessages({
|
||||
heading: { id: 'column.follow_requests', defaultMessage: 'Follow requests' },
|
||||
|
|
@ -26,6 +27,7 @@ export default class FollowRequests extends ImmutablePureComponent {
|
|||
static propTypes = {
|
||||
params: PropTypes.object.isRequired,
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
shouldUpdateScroll: PropTypes.func,
|
||||
accountIds: ImmutablePropTypes.list,
|
||||
intl: PropTypes.object.isRequired,
|
||||
};
|
||||
|
|
@ -34,16 +36,12 @@ export default class FollowRequests extends ImmutablePureComponent {
|
|||
this.props.dispatch(fetchFollowRequests());
|
||||
}
|
||||
|
||||
handleScroll = (e) => {
|
||||
const { scrollTop, scrollHeight, clientHeight } = e.target;
|
||||
|
||||
if (scrollTop === scrollHeight - clientHeight) {
|
||||
this.props.dispatch(expandFollowRequests());
|
||||
}
|
||||
}
|
||||
handleLoadMore = debounce(() => {
|
||||
this.props.dispatch(expandFollowRequests());
|
||||
}, 300, { leading: true });
|
||||
|
||||
render () {
|
||||
const { intl, accountIds } = this.props;
|
||||
const { intl, shouldUpdateScroll, accountIds } = this.props;
|
||||
|
||||
if (!accountIds) {
|
||||
return (
|
||||
|
|
@ -53,17 +51,21 @@ export default class FollowRequests extends ImmutablePureComponent {
|
|||
);
|
||||
}
|
||||
|
||||
const emptyMessage = <FormattedMessage id='empty_column.follow_requests' defaultMessage="You don't have any follow requests yet. When you receive one, it will show up here." />;
|
||||
|
||||
return (
|
||||
<Column icon='users' heading={intl.formatMessage(messages.heading)}>
|
||||
<ColumnBackButtonSlim />
|
||||
|
||||
<ScrollContainer scrollKey='follow_requests'>
|
||||
<div className='scrollable' onScroll={this.handleScroll}>
|
||||
{accountIds.map(id =>
|
||||
<AccountAuthorizeContainer key={id} id={id} />
|
||||
)}
|
||||
</div>
|
||||
</ScrollContainer>
|
||||
<ScrollableList
|
||||
scrollKey='follow_requests'
|
||||
onLoadMore={this.handleLoadMore}
|
||||
shouldUpdateScroll={shouldUpdateScroll}
|
||||
emptyMessage={emptyMessage}
|
||||
>
|
||||
{accountIds.map(id =>
|
||||
<AccountAuthorizeContainer key={id} id={id} />
|
||||
)}
|
||||
</ScrollableList>
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,20 +1,21 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import PropTypes from 'prop-types';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { debounce } from 'lodash';
|
||||
import LoadingIndicator from '../../components/loading_indicator';
|
||||
import {
|
||||
fetchAccount,
|
||||
fetchFollowers,
|
||||
expandFollowers,
|
||||
} from '../../actions/accounts';
|
||||
import { ScrollContainer } from 'react-router-scroll-4';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import AccountContainer from '../../containers/account_container';
|
||||
import Column from '../ui/components/column';
|
||||
import HeaderContainer from '../account_timeline/containers/header_container';
|
||||
import LoadMore from '../../components/load_more';
|
||||
import ColumnBackButton from '../../components/column_back_button';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import ScrollableList from '../../components/scrollable_list';
|
||||
|
||||
const mapStateToProps = (state, props) => ({
|
||||
accountIds: state.getIn(['user_lists', 'followers', props.params.accountId, 'items']),
|
||||
|
|
@ -27,6 +28,7 @@ export default class Followers extends ImmutablePureComponent {
|
|||
static propTypes = {
|
||||
params: PropTypes.object.isRequired,
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
shouldUpdateScroll: PropTypes.func,
|
||||
accountIds: ImmutablePropTypes.list,
|
||||
hasMore: PropTypes.bool,
|
||||
};
|
||||
|
|
@ -43,23 +45,12 @@ export default class Followers extends ImmutablePureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
handleScroll = (e) => {
|
||||
const { scrollTop, scrollHeight, clientHeight } = e.target;
|
||||
|
||||
if (scrollTop === scrollHeight - clientHeight && this.props.hasMore) {
|
||||
this.props.dispatch(expandFollowers(this.props.params.accountId));
|
||||
}
|
||||
}
|
||||
|
||||
handleLoadMore = (e) => {
|
||||
e.preventDefault();
|
||||
handleLoadMore = debounce(() => {
|
||||
this.props.dispatch(expandFollowers(this.props.params.accountId));
|
||||
}
|
||||
}, 300, { leading: true });
|
||||
|
||||
render () {
|
||||
const { accountIds, hasMore } = this.props;
|
||||
|
||||
let loadMore = null;
|
||||
const { shouldUpdateScroll, accountIds, hasMore } = this.props;
|
||||
|
||||
if (!accountIds) {
|
||||
return (
|
||||
|
|
@ -69,23 +60,25 @@ export default class Followers extends ImmutablePureComponent {
|
|||
);
|
||||
}
|
||||
|
||||
if (hasMore) {
|
||||
loadMore = <LoadMore onClick={this.handleLoadMore} />;
|
||||
}
|
||||
const emptyMessage = <FormattedMessage id='account.followers.empty' defaultMessage='No one follows this user yet.' />;
|
||||
|
||||
return (
|
||||
<Column>
|
||||
<ColumnBackButton />
|
||||
|
||||
<ScrollContainer scrollKey='followers'>
|
||||
<div className='scrollable' onScroll={this.handleScroll}>
|
||||
<div className='followers'>
|
||||
<HeaderContainer accountId={this.props.params.accountId} hideTabs />
|
||||
{accountIds.map(id => <AccountContainer key={id} id={id} withNote={false} />)}
|
||||
{loadMore}
|
||||
</div>
|
||||
</div>
|
||||
</ScrollContainer>
|
||||
<HeaderContainer accountId={this.props.params.accountId} hideTabs />
|
||||
|
||||
<ScrollableList
|
||||
scrollKey='followers'
|
||||
hasMore={hasMore}
|
||||
onLoadMore={this.handleLoadMore}
|
||||
shouldUpdateScroll={shouldUpdateScroll}
|
||||
emptyMessage={emptyMessage}
|
||||
>
|
||||
{accountIds.map(id =>
|
||||
<AccountContainer key={id} id={id} withNote={false} />
|
||||
)}
|
||||
</ScrollableList>
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,20 +1,21 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import PropTypes from 'prop-types';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { debounce } from 'lodash';
|
||||
import LoadingIndicator from '../../components/loading_indicator';
|
||||
import {
|
||||
fetchAccount,
|
||||
fetchFollowing,
|
||||
expandFollowing,
|
||||
} from '../../actions/accounts';
|
||||
import { ScrollContainer } from 'react-router-scroll-4';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import AccountContainer from '../../containers/account_container';
|
||||
import Column from '../ui/components/column';
|
||||
import HeaderContainer from '../account_timeline/containers/header_container';
|
||||
import LoadMore from '../../components/load_more';
|
||||
import ColumnBackButton from '../../components/column_back_button';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import ScrollableList from '../../components/scrollable_list';
|
||||
|
||||
const mapStateToProps = (state, props) => ({
|
||||
accountIds: state.getIn(['user_lists', 'following', props.params.accountId, 'items']),
|
||||
|
|
@ -27,6 +28,7 @@ export default class Following extends ImmutablePureComponent {
|
|||
static propTypes = {
|
||||
params: PropTypes.object.isRequired,
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
shouldUpdateScroll: PropTypes.func,
|
||||
accountIds: ImmutablePropTypes.list,
|
||||
hasMore: PropTypes.bool,
|
||||
};
|
||||
|
|
@ -43,23 +45,12 @@ export default class Following extends ImmutablePureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
handleScroll = (e) => {
|
||||
const { scrollTop, scrollHeight, clientHeight } = e.target;
|
||||
|
||||
if (scrollTop === scrollHeight - clientHeight && this.props.hasMore) {
|
||||
this.props.dispatch(expandFollowing(this.props.params.accountId));
|
||||
}
|
||||
}
|
||||
|
||||
handleLoadMore = (e) => {
|
||||
e.preventDefault();
|
||||
handleLoadMore = debounce(() => {
|
||||
this.props.dispatch(expandFollowing(this.props.params.accountId));
|
||||
}
|
||||
}, 300, { leading: true });
|
||||
|
||||
render () {
|
||||
const { accountIds, hasMore } = this.props;
|
||||
|
||||
let loadMore = null;
|
||||
const { shouldUpdateScroll, accountIds, hasMore } = this.props;
|
||||
|
||||
if (!accountIds) {
|
||||
return (
|
||||
|
|
@ -69,23 +60,25 @@ export default class Following extends ImmutablePureComponent {
|
|||
);
|
||||
}
|
||||
|
||||
if (hasMore) {
|
||||
loadMore = <LoadMore onClick={this.handleLoadMore} />;
|
||||
}
|
||||
const emptyMessage = <FormattedMessage id='account.follows.empty' defaultMessage="This user doesn't follow anyone yet." />;
|
||||
|
||||
return (
|
||||
<Column>
|
||||
<ColumnBackButton />
|
||||
|
||||
<ScrollContainer scrollKey='following'>
|
||||
<div className='scrollable' onScroll={this.handleScroll}>
|
||||
<div className='following'>
|
||||
<HeaderContainer accountId={this.props.params.accountId} hideTabs />
|
||||
{accountIds.map(id => <AccountContainer key={id} id={id} withNote={false} />)}
|
||||
{loadMore}
|
||||
</div>
|
||||
</div>
|
||||
</ScrollContainer>
|
||||
<HeaderContainer accountId={this.props.params.accountId} hideTabs />
|
||||
|
||||
<ScrollableList
|
||||
scrollKey='following'
|
||||
hasMore={hasMore}
|
||||
onLoadMore={this.handleLoadMore}
|
||||
shouldUpdateScroll={shouldUpdateScroll}
|
||||
emptyMessage={emptyMessage}
|
||||
>
|
||||
{accountIds.map(id =>
|
||||
<AccountContainer key={id} id={id} withNote={false} />
|
||||
)}
|
||||
</ScrollableList>
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import { connect } from 'react-redux';
|
|||
import PropTypes from 'prop-types';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { me, invitesEnabled } from '../../initial_state';
|
||||
import { me, invitesEnabled, version } from '../../initial_state';
|
||||
import { fetchFollowRequests } from '../../actions/accounts';
|
||||
import { List as ImmutableList } from 'immutable';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
|
@ -31,6 +31,7 @@ const messages = defineMessages({
|
|||
discover: { id: 'navigation_bar.discover', defaultMessage: 'Discover' },
|
||||
personal: { id: 'navigation_bar.personal', defaultMessage: 'Personal' },
|
||||
security: { id: 'navigation_bar.security', defaultMessage: 'Security' },
|
||||
menu: { id: 'getting_started.heading', defaultMessage: 'Getting started' },
|
||||
});
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
|
|
@ -115,7 +116,7 @@ export default class GettingStarted extends ImmutablePureComponent {
|
|||
}
|
||||
|
||||
return (
|
||||
<Column>
|
||||
<Column label={intl.formatMessage(messages.menu)}>
|
||||
{multiColumn && <div className='column-header__wrapper'>
|
||||
<h1 className='column-header'>
|
||||
<button>
|
||||
|
|
@ -139,6 +140,7 @@ export default class GettingStarted extends ImmutablePureComponent {
|
|||
{multiColumn && <li><Link to='/keyboard-shortcuts'><FormattedMessage id='navigation_bar.keyboard_shortcuts' defaultMessage='Hotkeys' /></Link> · </li>}
|
||||
<li><a href='/auth/edit'><FormattedMessage id='getting_started.security' defaultMessage='Security' /></a> · </li>
|
||||
<li><a href='/about/more' target='_blank'><FormattedMessage id='navigation_bar.info' defaultMessage='About this instance' /></a> · </li>
|
||||
<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>
|
||||
|
|
@ -149,7 +151,7 @@ export default class GettingStarted extends ImmutablePureComponent {
|
|||
<FormattedMessage
|
||||
id='getting_started.open_source_notice'
|
||||
defaultMessage='Mastodon is open source software. You can contribute or report issues on GitHub at {github}.'
|
||||
values={{ github: <a href='https://github.com/tootsuite/mastodon' rel='noopener' target='_blank'>tootsuite/mastodon</a> }}
|
||||
values={{ github: <span><a href='https://github.com/tootsuite/mastodon' rel='noopener' target='_blank'>tootsuite/mastodon</a> (v{version})</span> }}
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ export default class HashtagTimeline extends React.PureComponent {
|
|||
params: PropTypes.object.isRequired,
|
||||
columnId: PropTypes.string,
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
shouldUpdateScroll: PropTypes.func,
|
||||
hasUnread: PropTypes.bool,
|
||||
multiColumn: PropTypes.bool,
|
||||
};
|
||||
|
|
@ -83,12 +84,12 @@ export default class HashtagTimeline extends React.PureComponent {
|
|||
}
|
||||
|
||||
render () {
|
||||
const { hasUnread, columnId, multiColumn } = this.props;
|
||||
const { shouldUpdateScroll, hasUnread, columnId, multiColumn } = this.props;
|
||||
const { id } = this.props.params;
|
||||
const pinned = !!columnId;
|
||||
|
||||
return (
|
||||
<Column ref={this.setRef}>
|
||||
<Column ref={this.setRef} label={`#${id}`}>
|
||||
<ColumnHeader
|
||||
icon='hashtag'
|
||||
active={hasUnread}
|
||||
|
|
@ -107,6 +108,7 @@ export default class HashtagTimeline extends React.PureComponent {
|
|||
timelineId={`hashtag:${id}`}
|
||||
onLoadMore={this.handleLoadMore}
|
||||
emptyMessage={<FormattedMessage id='empty_column.hashtag' defaultMessage='There is nothing in this hashtag yet.' />}
|
||||
shouldUpdateScroll={shouldUpdateScroll}
|
||||
/>
|
||||
</Column>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ export default class HomeTimeline extends React.PureComponent {
|
|||
|
||||
static propTypes = {
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
shouldUpdateScroll: PropTypes.func,
|
||||
intl: PropTypes.object.isRequired,
|
||||
hasUnread: PropTypes.bool,
|
||||
isPartial: PropTypes.bool,
|
||||
|
|
@ -93,11 +94,11 @@ export default class HomeTimeline extends React.PureComponent {
|
|||
}
|
||||
|
||||
render () {
|
||||
const { intl, hasUnread, columnId, multiColumn } = this.props;
|
||||
const { intl, shouldUpdateScroll, hasUnread, columnId, multiColumn } = this.props;
|
||||
const pinned = !!columnId;
|
||||
|
||||
return (
|
||||
<Column ref={this.setRef}>
|
||||
<Column ref={this.setRef} label={intl.formatMessage(messages.title)}>
|
||||
<ColumnHeader
|
||||
icon='home'
|
||||
active={hasUnread}
|
||||
|
|
@ -117,6 +118,7 @@ export default class HomeTimeline extends React.PureComponent {
|
|||
onLoadMore={this.handleLoadMore}
|
||||
timelineId='home'
|
||||
emptyMessage={<FormattedMessage id='empty_column.home' defaultMessage='Your home timeline is empty! Visit {public} or use search to get started and meet other users.' values={{ public: <Link to='/timelines/public'><FormattedMessage id='empty_column.home.public_timeline' defaultMessage='the public timeline' /></Link> }} />}
|
||||
shouldUpdateScroll={shouldUpdateScroll}
|
||||
/>
|
||||
</Column>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -40,6 +40,10 @@ export default class KeyboardShortcuts extends ImmutablePureComponent {
|
|||
<td><kbd>m</kbd></td>
|
||||
<td><FormattedMessage id='keyboard_shortcuts.mention' defaultMessage='to mention author' /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><kbd>p</kbd></td>
|
||||
<td><FormattedMessage id='keyboard_shortcuts.profile' defaultMessage="to open author's profile" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><kbd>f</kbd></td>
|
||||
<td><FormattedMessage id='keyboard_shortcuts.favourite' defaultMessage='to favourite' /></td>
|
||||
|
|
@ -49,7 +53,7 @@ export default class KeyboardShortcuts extends ImmutablePureComponent {
|
|||
<td><FormattedMessage id='keyboard_shortcuts.boost' defaultMessage='to boost' /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><kbd>enter</kbd></td>
|
||||
<td><kbd>enter</kbd>, <kbd>o</kbd></td>
|
||||
<td><FormattedMessage id='keyboard_shortcuts.enter' defaultMessage='to open status' /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
|
@ -57,11 +61,11 @@ export default class KeyboardShortcuts extends ImmutablePureComponent {
|
|||
<td><FormattedMessage id='keyboard_shortcuts.toggle_hidden' defaultMessage='to show/hide text behind CW' /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><kbd>up</kbd></td>
|
||||
<td><kbd>up</kbd>, <kbd>k</kbd></td>
|
||||
<td><FormattedMessage id='keyboard_shortcuts.up' defaultMessage='to move up in the list' /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><kbd>down</kbd></td>
|
||||
<td><kbd>down</kbd>, <kbd>j</kbd></td>
|
||||
<td><FormattedMessage id='keyboard_shortcuts.down' defaultMessage='to move down in the list' /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
|
@ -88,6 +92,54 @@ export default class KeyboardShortcuts extends ImmutablePureComponent {
|
|||
<td><kbd>esc</kbd></td>
|
||||
<td><FormattedMessage id='keyboard_shortcuts.unfocus' defaultMessage='to un-focus compose textarea/search' /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><kbd>g</kbd>+<kbd>h</kbd></td>
|
||||
<td><FormattedMessage id='keyboard_shortcuts.home' defaultMessage='to open home timeline' /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><kbd>g</kbd>+<kbd>n</kbd></td>
|
||||
<td><FormattedMessage id='keyboard_shortcuts.notifications' defaultMessage='to open notifications column' /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><kbd>g</kbd>+<kbd>l</kbd></td>
|
||||
<td><FormattedMessage id='keyboard_shortcuts.local' defaultMessage='to open local timeline' /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><kbd>g</kbd>+<kbd>t</kbd></td>
|
||||
<td><FormattedMessage id='keyboard_shortcuts.federated' defaultMessage='to open federated timeline' /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><kbd>g</kbd>+<kbd>d</kbd></td>
|
||||
<td><FormattedMessage id='keyboard_shortcuts.direct' defaultMessage='to open direct messages column' /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><kbd>g</kbd>+<kbd>s</kbd></td>
|
||||
<td><FormattedMessage id='keyboard_shortcuts.start' defaultMessage='to open "get started" column' /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><kbd>g</kbd>+<kbd>f</kbd></td>
|
||||
<td><FormattedMessage id='keyboard_shortcuts.favourites' defaultMessage='to open favourites list' /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><kbd>g</kbd>+<kbd>p</kbd></td>
|
||||
<td><FormattedMessage id='keyboard_shortcuts.pinned' defaultMessage='to open pinned toots list' /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><kbd>g</kbd>+<kbd>u</kbd></td>
|
||||
<td><FormattedMessage id='keyboard_shortcuts.my_profile' defaultMessage='to open your profile' /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><kbd>g</kbd>+<kbd>b</kbd></td>
|
||||
<td><FormattedMessage id='keyboard_shortcuts.blocked' defaultMessage='to open blocked users list' /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><kbd>g</kbd>+<kbd>m</kbd></td>
|
||||
<td><FormattedMessage id='keyboard_shortcuts.muted' defaultMessage='to open muted users list' /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><kbd>g</kbd>+<kbd>r</kbd></td>
|
||||
<td><FormattedMessage id='keyboard_shortcuts.requests' defaultMessage='to open follow requests list' /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><kbd>?</kbd></td>
|
||||
<td><FormattedMessage id='keyboard_shortcuts.legend' defaultMessage='to display this legend' /></td>
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ export default class ListTimeline extends React.PureComponent {
|
|||
static propTypes = {
|
||||
params: PropTypes.object.isRequired,
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
shouldUpdateScroll: PropTypes.func,
|
||||
columnId: PropTypes.string,
|
||||
hasUnread: PropTypes.bool,
|
||||
multiColumn: PropTypes.bool,
|
||||
|
|
@ -112,7 +113,7 @@ export default class ListTimeline extends React.PureComponent {
|
|||
}
|
||||
|
||||
render () {
|
||||
const { hasUnread, columnId, multiColumn, list } = this.props;
|
||||
const { shouldUpdateScroll, hasUnread, columnId, multiColumn, list } = this.props;
|
||||
const { id } = this.props.params;
|
||||
const pinned = !!columnId;
|
||||
const title = list ? list.get('title') : id;
|
||||
|
|
@ -136,7 +137,7 @@ export default class ListTimeline extends React.PureComponent {
|
|||
}
|
||||
|
||||
return (
|
||||
<Column ref={this.setRef}>
|
||||
<Column ref={this.setRef} label={title}>
|
||||
<ColumnHeader
|
||||
icon='list-ul'
|
||||
active={hasUnread}
|
||||
|
|
@ -166,6 +167,7 @@ export default class ListTimeline extends React.PureComponent {
|
|||
timelineId={`list:${id}`}
|
||||
onLoadMore={this.handleLoadMore}
|
||||
emptyMessage={<FormattedMessage id='empty_column.list' defaultMessage='There is nothing in this list yet. When members of this list post new statuses, they will appear here.' />}
|
||||
shouldUpdateScroll={shouldUpdateScroll}
|
||||
/>
|
||||
</Column>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -6,12 +6,13 @@ import LoadingIndicator from '../../components/loading_indicator';
|
|||
import Column from '../ui/components/column';
|
||||
import ColumnBackButtonSlim from '../../components/column_back_button_slim';
|
||||
import { fetchLists } from '../../actions/lists';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import ColumnLink from '../ui/components/column_link';
|
||||
import ColumnSubheading from '../ui/components/column_subheading';
|
||||
import NewListForm from './components/new_list_form';
|
||||
import { createSelector } from 'reselect';
|
||||
import ScrollableList from '../../components/scrollable_list';
|
||||
|
||||
const messages = defineMessages({
|
||||
heading: { id: 'column.lists', defaultMessage: 'Lists' },
|
||||
|
|
@ -46,7 +47,7 @@ export default class Lists extends ImmutablePureComponent {
|
|||
}
|
||||
|
||||
render () {
|
||||
const { intl, lists } = this.props;
|
||||
const { intl, shouldUpdateScroll, lists } = this.props;
|
||||
|
||||
if (!lists) {
|
||||
return (
|
||||
|
|
@ -56,19 +57,24 @@ export default class Lists extends ImmutablePureComponent {
|
|||
);
|
||||
}
|
||||
|
||||
const emptyMessage = <FormattedMessage id='empty_column.lists' defaultMessage="You don't have any lists yet. When you create one, it will show up here." />;
|
||||
|
||||
return (
|
||||
<Column icon='list-ul' heading={intl.formatMessage(messages.heading)}>
|
||||
<ColumnBackButtonSlim />
|
||||
|
||||
<NewListForm />
|
||||
|
||||
<div className='scrollable'>
|
||||
<ColumnSubheading text={intl.formatMessage(messages.subheading)} />
|
||||
|
||||
<ColumnSubheading text={intl.formatMessage(messages.subheading)} />
|
||||
<ScrollableList
|
||||
scrollKey='lists'
|
||||
shouldUpdateScroll={shouldUpdateScroll}
|
||||
emptyMessage={emptyMessage}
|
||||
>
|
||||
{lists.map(list =>
|
||||
<ColumnLink key={list.get('id')} to={`/timelines/list/${list.get('id')}`} icon='list-ul' text={list.get('title')} />
|
||||
)}
|
||||
</div>
|
||||
</ScrollableList>
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,16 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import PropTypes from 'prop-types';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { debounce } from 'lodash';
|
||||
import LoadingIndicator from '../../components/loading_indicator';
|
||||
import { ScrollContainer } from 'react-router-scroll-4';
|
||||
import Column from '../ui/components/column';
|
||||
import ColumnBackButtonSlim from '../../components/column_back_button_slim';
|
||||
import AccountContainer from '../../containers/account_container';
|
||||
import { fetchMutes, expandMutes } from '../../actions/mutes';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import ScrollableList from '../../components/scrollable_list';
|
||||
|
||||
const messages = defineMessages({
|
||||
heading: { id: 'column.mutes', defaultMessage: 'Muted users' },
|
||||
|
|
@ -26,6 +27,7 @@ export default class Mutes extends ImmutablePureComponent {
|
|||
static propTypes = {
|
||||
params: PropTypes.object.isRequired,
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
shouldUpdateScroll: PropTypes.func,
|
||||
accountIds: ImmutablePropTypes.list,
|
||||
intl: PropTypes.object.isRequired,
|
||||
};
|
||||
|
|
@ -34,16 +36,12 @@ export default class Mutes extends ImmutablePureComponent {
|
|||
this.props.dispatch(fetchMutes());
|
||||
}
|
||||
|
||||
handleScroll = (e) => {
|
||||
const { scrollTop, scrollHeight, clientHeight } = e.target;
|
||||
|
||||
if (scrollTop === scrollHeight - clientHeight) {
|
||||
this.props.dispatch(expandMutes());
|
||||
}
|
||||
}
|
||||
handleLoadMore = debounce(() => {
|
||||
this.props.dispatch(expandMutes());
|
||||
}, 300, { leading: true });
|
||||
|
||||
render () {
|
||||
const { intl, accountIds } = this.props;
|
||||
const { intl, shouldUpdateScroll, accountIds } = this.props;
|
||||
|
||||
if (!accountIds) {
|
||||
return (
|
||||
|
|
@ -53,16 +51,21 @@ export default class Mutes extends ImmutablePureComponent {
|
|||
);
|
||||
}
|
||||
|
||||
const emptyMessage = <FormattedMessage id='empty_column.mutes' defaultMessage="You haven't muted any users yet." />;
|
||||
|
||||
return (
|
||||
<Column icon='volume-off' heading={intl.formatMessage(messages.heading)}>
|
||||
<ColumnBackButtonSlim />
|
||||
<ScrollContainer scrollKey='mutes'>
|
||||
<div className='scrollable mutes' onScroll={this.handleScroll}>
|
||||
{accountIds.map(id =>
|
||||
<AccountContainer key={id} id={id} />
|
||||
)}
|
||||
</div>
|
||||
</ScrollContainer>
|
||||
<ScrollableList
|
||||
scrollKey='mutes'
|
||||
onLoadMore={this.handleLoadMore}
|
||||
shouldUpdateScroll={shouldUpdateScroll}
|
||||
emptyMessage={emptyMessage}
|
||||
>
|
||||
{accountIds.map(id =>
|
||||
<AccountContainer key={id} id={id} />
|
||||
)}
|
||||
</ScrollableList>
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,11 +3,20 @@ import PropTypes from 'prop-types';
|
|||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import StatusContainer from '../../../containers/status_container';
|
||||
import AccountContainer from '../../../containers/account_container';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { injectIntl, FormattedMessage } from 'react-intl';
|
||||
import Permalink from '../../../components/permalink';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { HotKeys } from 'react-hotkeys';
|
||||
|
||||
const notificationForScreenReader = (intl, message, timestamp) => {
|
||||
const output = [message];
|
||||
|
||||
output.push(intl.formatDate(timestamp, { hour: '2-digit', minute: '2-digit', month: 'short', day: 'numeric' }));
|
||||
|
||||
return output.join(', ');
|
||||
};
|
||||
|
||||
@injectIntl
|
||||
export default class Notification extends ImmutablePureComponent {
|
||||
|
||||
static contextTypes = {
|
||||
|
|
@ -20,6 +29,7 @@ export default class Notification extends ImmutablePureComponent {
|
|||
onMoveUp: PropTypes.func.isRequired,
|
||||
onMoveDown: PropTypes.func.isRequired,
|
||||
onMention: PropTypes.func.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
handleMoveUp = () => {
|
||||
|
|
@ -65,10 +75,12 @@ export default class Notification extends ImmutablePureComponent {
|
|||
};
|
||||
}
|
||||
|
||||
renderFollow (account, link) {
|
||||
renderFollow (notification, account, link) {
|
||||
const { intl } = this.props;
|
||||
|
||||
return (
|
||||
<HotKeys handlers={this.getHandlers()}>
|
||||
<div className='notification notification-follow focusable' tabIndex='0'>
|
||||
<div className='notification notification-follow focusable' tabIndex='0' aria-label={notificationForScreenReader(intl, intl.formatMessage({ id: 'notification.follow', defaultMessage: '{name} followed you' }, { name: account.get('acct') }), notification.get('created_at'))}>
|
||||
<div className='notification__message'>
|
||||
<div className='notification__favourite-icon-wrapper'>
|
||||
<i className='fa fa-fw fa-user-plus' />
|
||||
|
|
@ -97,9 +109,11 @@ export default class Notification extends ImmutablePureComponent {
|
|||
}
|
||||
|
||||
renderFavourite (notification, link) {
|
||||
const { intl } = this.props;
|
||||
|
||||
return (
|
||||
<HotKeys handlers={this.getHandlers()}>
|
||||
<div className='notification notification-favourite focusable' tabIndex='0'>
|
||||
<div className='notification notification-favourite focusable' tabIndex='0' aria-label={notificationForScreenReader(intl, intl.formatMessage({ id: 'notification.favourite', defaultMessage: '{name} favourited your status' }, { name: notification.getIn(['account', 'acct']) }), notification.get('created_at'))}>
|
||||
<div className='notification__message'>
|
||||
<div className='notification__favourite-icon-wrapper'>
|
||||
<i className='fa fa-fw fa-star star-icon' />
|
||||
|
|
@ -114,9 +128,11 @@ export default class Notification extends ImmutablePureComponent {
|
|||
}
|
||||
|
||||
renderReblog (notification, link) {
|
||||
const { intl } = this.props;
|
||||
|
||||
return (
|
||||
<HotKeys handlers={this.getHandlers()}>
|
||||
<div className='notification notification-reblog focusable' tabIndex='0'>
|
||||
<div className='notification notification-reblog focusable' tabIndex='0' aria-label={notificationForScreenReader(intl, intl.formatMessage({ id: 'notification.reblog', defaultMessage: '{name} boosted your status' }, { name: notification.getIn(['account', 'acct']) }), notification.get('created_at'))}>
|
||||
<div className='notification__message'>
|
||||
<div className='notification__favourite-icon-wrapper'>
|
||||
<i className='fa fa-fw fa-retweet' />
|
||||
|
|
@ -138,7 +154,7 @@ export default class Notification extends ImmutablePureComponent {
|
|||
|
||||
switch(notification.get('type')) {
|
||||
case 'follow':
|
||||
return this.renderFollow(account, link);
|
||||
return this.renderFollow(notification, account, link);
|
||||
case 'mention':
|
||||
return this.renderMention(notification);
|
||||
case 'favourite':
|
||||
|
|
|
|||
|
|
@ -165,7 +165,7 @@ export default class Notifications extends React.PureComponent {
|
|||
);
|
||||
|
||||
return (
|
||||
<Column ref={this.setColumnRef}>
|
||||
<Column ref={this.setColumnRef} label={intl.formatMessage(messages.title)}>
|
||||
<ColumnHeader
|
||||
icon='bell'
|
||||
active={isUnread}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ export default class PinnedStatuses extends ImmutablePureComponent {
|
|||
|
||||
static propTypes = {
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
shouldUpdateScroll: PropTypes.func,
|
||||
statusIds: ImmutablePropTypes.list.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
hasMore: PropTypes.bool.isRequired,
|
||||
|
|
@ -42,7 +43,7 @@ export default class PinnedStatuses extends ImmutablePureComponent {
|
|||
}
|
||||
|
||||
render () {
|
||||
const { intl, statusIds, hasMore } = this.props;
|
||||
const { intl, shouldUpdateScroll, statusIds, hasMore } = this.props;
|
||||
|
||||
return (
|
||||
<Column icon='thumb-tack' heading={intl.formatMessage(messages.heading)} ref={this.setRef}>
|
||||
|
|
@ -51,6 +52,7 @@ export default class PinnedStatuses extends ImmutablePureComponent {
|
|||
statusIds={statusIds}
|
||||
scrollKey='pinned_statuses'
|
||||
hasMore={hasMore}
|
||||
shouldUpdateScroll={shouldUpdateScroll}
|
||||
/>
|
||||
</Column>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ export default class PublicTimeline extends React.PureComponent {
|
|||
|
||||
static propTypes = {
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
shouldUpdateScroll: PropTypes.func,
|
||||
intl: PropTypes.object.isRequired,
|
||||
columnId: PropTypes.string,
|
||||
multiColumn: PropTypes.bool,
|
||||
|
|
@ -107,11 +108,11 @@ export default class PublicTimeline extends React.PureComponent {
|
|||
}
|
||||
|
||||
render () {
|
||||
const { intl, columnId, hasUnread, multiColumn, onlyMedia } = this.props;
|
||||
const { intl, shouldUpdateScroll, columnId, hasUnread, multiColumn, onlyMedia } = this.props;
|
||||
const pinned = !!columnId;
|
||||
|
||||
return (
|
||||
<Column ref={this.setRef}>
|
||||
<Column ref={this.setRef} label={intl.formatMessage(messages.title)}>
|
||||
<ColumnHeader
|
||||
icon='globe'
|
||||
active={hasUnread}
|
||||
|
|
@ -131,6 +132,7 @@ export default class PublicTimeline extends React.PureComponent {
|
|||
trackScroll={!pinned}
|
||||
scrollKey={`public_timeline-${columnId}`}
|
||||
emptyMessage={<FormattedMessage id='empty_column.public' defaultMessage='There is nothing here! Write something publicly, or manually follow users from other instances to fill it up' />}
|
||||
shouldUpdateScroll={shouldUpdateScroll}
|
||||
/>
|
||||
</Column>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,14 +1,15 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import PropTypes from 'prop-types';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import LoadingIndicator from '../../components/loading_indicator';
|
||||
import { fetchReblogs } from '../../actions/interactions';
|
||||
import { ScrollContainer } from 'react-router-scroll-4';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import AccountContainer from '../../containers/account_container';
|
||||
import Column from '../ui/components/column';
|
||||
import ColumnBackButton from '../../components/column_back_button';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import ScrollableList from '../../components/scrollable_list';
|
||||
|
||||
const mapStateToProps = (state, props) => ({
|
||||
accountIds: state.getIn(['user_lists', 'reblogged_by', props.params.statusId]),
|
||||
|
|
@ -20,6 +21,7 @@ export default class Reblogs extends ImmutablePureComponent {
|
|||
static propTypes = {
|
||||
params: PropTypes.object.isRequired,
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
shouldUpdateScroll: PropTypes.func,
|
||||
accountIds: ImmutablePropTypes.list,
|
||||
};
|
||||
|
||||
|
|
@ -34,7 +36,7 @@ export default class Reblogs extends ImmutablePureComponent {
|
|||
}
|
||||
|
||||
render () {
|
||||
const { accountIds } = this.props;
|
||||
const { shouldUpdateScroll, accountIds } = this.props;
|
||||
|
||||
if (!accountIds) {
|
||||
return (
|
||||
|
|
@ -44,15 +46,21 @@ export default class Reblogs extends ImmutablePureComponent {
|
|||
);
|
||||
}
|
||||
|
||||
const emptyMessage = <FormattedMessage id='status.reblogs.empty' defaultMessage='No one has boosted this toot yet. When someone does, they will show up here.' />;
|
||||
|
||||
return (
|
||||
<Column>
|
||||
<ColumnBackButton />
|
||||
|
||||
<ScrollContainer scrollKey='reblogs'>
|
||||
<div className='scrollable reblogs'>
|
||||
{accountIds.map(id => <AccountContainer key={id} id={id} withNote={false} />)}
|
||||
</div>
|
||||
</ScrollContainer>
|
||||
<ScrollableList
|
||||
scrollKey='reblogs'
|
||||
shouldUpdateScroll={shouldUpdateScroll}
|
||||
emptyMessage={emptyMessage}
|
||||
>
|
||||
{accountIds.map(id =>
|
||||
<AccountContainer key={id} id={id} withNote={false} />
|
||||
)}
|
||||
</ScrollableList>
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ export default class StatusCheckBox extends React.PureComponent {
|
|||
<Component
|
||||
preview={video.get('preview_url')}
|
||||
src={video.get('url')}
|
||||
alt={video.get('description')}
|
||||
width={239}
|
||||
height={110}
|
||||
inline
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ export default class CommunityTimeline extends React.PureComponent {
|
|||
const { intl } = this.props;
|
||||
|
||||
return (
|
||||
<Column ref={this.setRef}>
|
||||
<Column ref={this.setRef} label={intl.formatMessage(messages.title)}>
|
||||
<ColumnHeader
|
||||
icon='users'
|
||||
title={intl.formatMessage(messages.title)}
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ export default class PublicTimeline extends React.PureComponent {
|
|||
const { intl } = this.props;
|
||||
|
||||
return (
|
||||
<Column ref={this.setRef}>
|
||||
<Column ref={this.setRef} label={intl.formatMessage(messages.title)}>
|
||||
<ColumnHeader
|
||||
icon='globe'
|
||||
title={intl.formatMessage(messages.title)}
|
||||
|
|
|
|||
|
|
@ -65,11 +65,11 @@ export default class ActionBar extends React.PureComponent {
|
|||
}
|
||||
|
||||
handleDeleteClick = () => {
|
||||
this.props.onDelete(this.props.status);
|
||||
this.props.onDelete(this.props.status, this.context.router.history);
|
||||
}
|
||||
|
||||
handleRedraftClick = () => {
|
||||
this.props.onDelete(this.props.status, true);
|
||||
this.props.onDelete(this.props.status, this.context.router.history, true);
|
||||
}
|
||||
|
||||
handleDirectClick = () => {
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ export default class DetailedStatus extends ImmutablePureComponent {
|
|||
};
|
||||
|
||||
handleAccountClick = (e) => {
|
||||
if (e.button === 0) {
|
||||
if (e.button === 0 && !(e.ctrlKey || e.metaKey)) {
|
||||
e.preventDefault();
|
||||
this.context.router.history.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`);
|
||||
}
|
||||
|
|
@ -60,6 +60,7 @@ export default class DetailedStatus extends ImmutablePureComponent {
|
|||
<Video
|
||||
preview={video.get('preview_url')}
|
||||
src={video.get('url')}
|
||||
alt={video.get('description')}
|
||||
width={300}
|
||||
height={150}
|
||||
inline
|
||||
|
|
|
|||
|
|
@ -42,16 +42,18 @@ import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
|||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { HotKeys } from 'react-hotkeys';
|
||||
import { boostModal, deleteModal } from '../../initial_state';
|
||||
import { attachFullscreenListener, detachFullscreenListener, isFullscreen } from '../../features/ui/util/fullscreen';
|
||||
import { attachFullscreenListener, detachFullscreenListener, isFullscreen } from '../ui/util/fullscreen';
|
||||
import { textForScreenReader } from '../../components/status';
|
||||
|
||||
const messages = defineMessages({
|
||||
deleteConfirm: { id: 'confirmations.delete.confirm', defaultMessage: 'Delete' },
|
||||
deleteMessage: { id: 'confirmations.delete.message', defaultMessage: 'Are you sure you want to delete this status?' },
|
||||
redraftConfirm: { id: 'confirmations.redraft.confirm', defaultMessage: 'Delete & redraft' },
|
||||
redraftMessage: { id: 'confirmations.redraft.message', defaultMessage: 'Are you sure you want to delete this status and re-draft it? You will lose all replies, boosts and favourites to it.' },
|
||||
redraftMessage: { id: 'confirmations.redraft.message', defaultMessage: 'Are you sure you want to delete this status and re-draft it? Favourites and boosts will be lost, and replies to the original post will be orphaned.' },
|
||||
blockConfirm: { id: 'confirmations.block.confirm', defaultMessage: 'Block' },
|
||||
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' },
|
||||
});
|
||||
|
||||
const makeMapStateToProps = () => {
|
||||
|
|
@ -174,16 +176,16 @@ export default class Status extends ImmutablePureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
handleDeleteClick = (status, withRedraft = false) => {
|
||||
handleDeleteClick = (status, history, withRedraft = false) => {
|
||||
const { dispatch, intl } = this.props;
|
||||
|
||||
if (!deleteModal) {
|
||||
dispatch(deleteStatus(status.get('id'), withRedraft));
|
||||
dispatch(deleteStatus(status.get('id'), history, withRedraft));
|
||||
} else {
|
||||
dispatch(openModal('CONFIRM', {
|
||||
message: intl.formatMessage(withRedraft ? messages.redraftMessage : messages.deleteMessage),
|
||||
confirm: intl.formatMessage(withRedraft ? messages.redraftConfirm : messages.deleteConfirm),
|
||||
onConfirm: () => dispatch(deleteStatus(status.get('id'), withRedraft)),
|
||||
onConfirm: () => dispatch(deleteStatus(status.get('id'), history, withRedraft)),
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
|
@ -355,7 +357,9 @@ export default class Status extends ImmutablePureComponent {
|
|||
if (status && ancestorsIds && ancestorsIds.size > 0) {
|
||||
const element = this.node.querySelectorAll('.focusable')[ancestorsIds.size - 1];
|
||||
|
||||
element.scrollIntoView(true);
|
||||
window.requestAnimationFrame(() => {
|
||||
element.scrollIntoView(true);
|
||||
});
|
||||
this._scrolledIntoView = true;
|
||||
}
|
||||
}
|
||||
|
|
@ -370,7 +374,7 @@ export default class Status extends ImmutablePureComponent {
|
|||
|
||||
render () {
|
||||
let ancestors, descendants;
|
||||
const { status, ancestorsIds, descendantsIds, intl } = this.props;
|
||||
const { shouldUpdateScroll, status, ancestorsIds, descendantsIds, intl } = this.props;
|
||||
const { fullscreen } = this.state;
|
||||
|
||||
if (status === null) {
|
||||
|
|
@ -402,7 +406,7 @@ export default class Status extends ImmutablePureComponent {
|
|||
};
|
||||
|
||||
return (
|
||||
<Column>
|
||||
<Column label={intl.formatMessage(messages.detailedStatus)}>
|
||||
<ColumnHeader
|
||||
showBackButton
|
||||
extraButton={(
|
||||
|
|
@ -410,12 +414,12 @@ export default class Status extends ImmutablePureComponent {
|
|||
)}
|
||||
/>
|
||||
|
||||
<ScrollContainer scrollKey='thread'>
|
||||
<ScrollContainer scrollKey='thread' shouldUpdateScroll={shouldUpdateScroll}>
|
||||
<div className={classNames('scrollable', 'detailed-status__wrapper', { fullscreen })} ref={this.setRef}>
|
||||
{ancestors}
|
||||
|
||||
<HotKeys handlers={handlers}>
|
||||
<div className='focusable' tabIndex='0'>
|
||||
<div className='focusable' tabIndex='0' aria-label={textForScreenReader(intl, status, false, !status.get('hidden'))}>
|
||||
<DetailedStatus
|
||||
status={status}
|
||||
onOpenVideo={this.handleOpenVideo}
|
||||
|
|
|
|||
|
|
@ -1,66 +0,0 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { connect } from 'react-redux';
|
||||
import { injectIntl, defineMessages } from 'react-intl';
|
||||
import Column from '../ui/components/column';
|
||||
import ColumnHeader from '../../components/column_header';
|
||||
import Hashtag from '../../components/hashtag';
|
||||
import classNames from 'classnames';
|
||||
import { fetchTrends } from '../../actions/trends';
|
||||
|
||||
const messages = defineMessages({
|
||||
title: { id: 'trends.header', defaultMessage: 'Trending now' },
|
||||
refreshTrends: { id: 'trends.refresh', defaultMessage: 'Refresh trends' },
|
||||
});
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
trends: state.getIn(['trends', 'items']),
|
||||
loading: state.getIn(['trends', 'isLoading']),
|
||||
});
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
fetchTrends: () => dispatch(fetchTrends()),
|
||||
});
|
||||
|
||||
@connect(mapStateToProps, mapDispatchToProps)
|
||||
@injectIntl
|
||||
export default class Trends extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
intl: PropTypes.object.isRequired,
|
||||
trends: ImmutablePropTypes.list,
|
||||
fetchTrends: PropTypes.func.isRequired,
|
||||
loading: PropTypes.bool,
|
||||
};
|
||||
|
||||
componentDidMount () {
|
||||
this.props.fetchTrends();
|
||||
}
|
||||
|
||||
handleRefresh = () => {
|
||||
this.props.fetchTrends();
|
||||
}
|
||||
|
||||
render () {
|
||||
const { trends, loading, intl } = this.props;
|
||||
|
||||
return (
|
||||
<Column>
|
||||
<ColumnHeader
|
||||
icon='fire'
|
||||
title={intl.formatMessage(messages.title)}
|
||||
extraButton={(
|
||||
<button className='column-header__button' title={intl.formatMessage(messages.refreshTrends)} aria-label={intl.formatMessage(messages.refreshTrends)} onClick={this.handleRefresh}><i className={classNames('fa', 'fa-refresh', { 'fa-spin': loading })} /></button>
|
||||
)}
|
||||
/>
|
||||
|
||||
<div className='scrollable'>
|
||||
{trends && trends.map(hashtag => <Hashtag key={hashtag.get('name')} hashtag={hashtag} />)}
|
||||
</div>
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -37,7 +37,7 @@ export default class BoostModal extends ImmutablePureComponent {
|
|||
}
|
||||
|
||||
handleAccountClick = (e) => {
|
||||
if (e.button === 0) {
|
||||
if (e.button === 0 && !(e.ctrlKey || e.metaKey)) {
|
||||
e.preventDefault();
|
||||
this.props.onClose();
|
||||
this.context.router.history.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import { LoadingBar } from 'react-redux-loading-bar';
|
||||
import ZoomableImage from './zoomable_image';
|
||||
|
||||
export default class ImageLoader extends React.PureComponent {
|
||||
|
|
@ -23,6 +24,7 @@ export default class ImageLoader extends React.PureComponent {
|
|||
state = {
|
||||
loading: true,
|
||||
error: false,
|
||||
width: null,
|
||||
}
|
||||
|
||||
removers = [];
|
||||
|
|
@ -122,6 +124,7 @@ export default class ImageLoader extends React.PureComponent {
|
|||
|
||||
setCanvasRef = c => {
|
||||
this.canvas = c;
|
||||
if (c) this.setState({ width: c.offsetWidth });
|
||||
}
|
||||
|
||||
render () {
|
||||
|
|
@ -135,6 +138,7 @@ export default class ImageLoader extends React.PureComponent {
|
|||
|
||||
return (
|
||||
<div className={className}>
|
||||
<LoadingBar loading={loading ? 1 : 0} className='loading-bar' style={{ width: this.state.width || width }} />
|
||||
{loading ? (
|
||||
<canvas
|
||||
className='image-loader__preview-canvas'
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ const messages = defineMessages({
|
|||
next: { id: 'lightbox.next', defaultMessage: 'Next' },
|
||||
});
|
||||
|
||||
const previewState = 'previewMediaModal';
|
||||
export const previewState = 'previewMediaModal';
|
||||
|
||||
@injectIntl
|
||||
export default class MediaModal extends ImmutablePureComponent {
|
||||
|
|
@ -54,19 +54,23 @@ export default class MediaModal extends ImmutablePureComponent {
|
|||
this.setState({ index: index % this.props.media.size });
|
||||
}
|
||||
|
||||
handleKeyUp = (e) => {
|
||||
handleKeyDown = (e) => {
|
||||
switch(e.key) {
|
||||
case 'ArrowLeft':
|
||||
this.handlePrevClick();
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
break;
|
||||
case 'ArrowRight':
|
||||
this.handleNextClick();
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
window.addEventListener('keyup', this.handleKeyUp, false);
|
||||
window.addEventListener('keydown', this.handleKeyDown, false);
|
||||
if (this.context.router) {
|
||||
const history = this.context.router.history;
|
||||
history.push(history.location.pathname, previewState);
|
||||
|
|
@ -77,7 +81,7 @@ export default class MediaModal extends ImmutablePureComponent {
|
|||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
window.removeEventListener('keyup', this.handleKeyUp);
|
||||
window.removeEventListener('keydown', this.handleKeyDown);
|
||||
if (this.context.router) {
|
||||
this.unlistenHistory();
|
||||
|
||||
|
|
|
|||
|
|
@ -41,14 +41,15 @@ export default class ModalRoot extends React.PureComponent {
|
|||
};
|
||||
|
||||
getSnapshotBeforeUpdate () {
|
||||
const visible = !!this.props.type;
|
||||
return {
|
||||
overflowY: visible ? 'hidden' : null,
|
||||
};
|
||||
return { visible: !!this.props.type };
|
||||
}
|
||||
|
||||
componentDidUpdate (prevProps, prevState, { overflowY }) {
|
||||
document.body.style.overflowY = overflowY;
|
||||
componentDidUpdate (prevProps, prevState, { visible }) {
|
||||
if (visible) {
|
||||
document.body.classList.add('with-modals--active');
|
||||
} else {
|
||||
document.body.classList.remove('with-modals--active');
|
||||
}
|
||||
}
|
||||
|
||||
renderLoading = modalId => () => {
|
||||
|
|
|
|||
|
|
@ -1,12 +1,14 @@
|
|||
import classNames from 'classnames';
|
||||
import React from 'react';
|
||||
import NotificationsContainer from './containers/notifications_container';
|
||||
import { HotKeys } from 'react-hotkeys';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import { connect } from 'react-redux';
|
||||
import { Redirect, withRouter } from 'react-router-dom';
|
||||
import PropTypes from 'prop-types';
|
||||
import NotificationsContainer from './containers/notifications_container';
|
||||
import LoadingBarContainer from './containers/loading_bar_container';
|
||||
import TabsBar from './components/tabs_bar';
|
||||
import ModalContainer from './containers/modal_container';
|
||||
import { connect } from 'react-redux';
|
||||
import { Redirect, withRouter } from 'react-router-dom';
|
||||
import { isMobile } from '../../is_mobile';
|
||||
import { debounce } from 'lodash';
|
||||
import { uploadCompose, resetCompose } from '../../actions/compose';
|
||||
|
|
@ -44,9 +46,8 @@ import {
|
|||
PinnedStatuses,
|
||||
Lists,
|
||||
} from './util/async-components';
|
||||
import { HotKeys } from 'react-hotkeys';
|
||||
import { me } from '../../initial_state';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import { previewState } from './components/media_modal';
|
||||
|
||||
// Dummy import, to make sure that <Status /> ends up in the application bundle.
|
||||
// Without this it ends up in ~8 very commonly used bundles.
|
||||
|
|
@ -88,6 +89,7 @@ const keyMap = {
|
|||
goToProfile: 'g u',
|
||||
goToBlocked: 'g b',
|
||||
goToMuted: 'g m',
|
||||
goToRequests: 'g r',
|
||||
toggleHidden: 'x',
|
||||
};
|
||||
|
||||
|
|
@ -117,6 +119,10 @@ class SwitchingColumnsArea extends React.PureComponent {
|
|||
window.removeEventListener('resize', this.handleResize);
|
||||
}
|
||||
|
||||
shouldUpdateScroll (_, { location }) {
|
||||
return location.state !== previewState;
|
||||
}
|
||||
|
||||
handleResize = debounce(() => {
|
||||
// The cached heights are no longer accurate, invalidate
|
||||
this.props.onLayoutChange();
|
||||
|
|
@ -141,37 +147,37 @@ class SwitchingColumnsArea extends React.PureComponent {
|
|||
{redirect}
|
||||
<WrappedRoute path='/getting-started' component={GettingStarted} content={children} />
|
||||
<WrappedRoute path='/keyboard-shortcuts' component={KeyboardShortcuts} content={children} />
|
||||
<WrappedRoute path='/timelines/home' component={HomeTimeline} content={children} />
|
||||
<WrappedRoute path='/timelines/public' exact component={PublicTimeline} content={children} />
|
||||
<WrappedRoute path='/timelines/public/media' component={PublicTimeline} content={children} componentParams={{ onlyMedia: true }} />
|
||||
<WrappedRoute path='/timelines/public/local' exact component={CommunityTimeline} content={children} />
|
||||
<WrappedRoute path='/timelines/public/local/media' component={CommunityTimeline} content={children} componentParams={{ onlyMedia: true }} />
|
||||
<WrappedRoute path='/timelines/direct' component={DirectTimeline} content={children} />
|
||||
<WrappedRoute path='/timelines/tag/:id' component={HashtagTimeline} content={children} />
|
||||
<WrappedRoute path='/timelines/list/:id' component={ListTimeline} content={children} />
|
||||
<WrappedRoute path='/timelines/home' component={HomeTimeline} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
|
||||
<WrappedRoute path='/timelines/public' exact component={PublicTimeline} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
|
||||
<WrappedRoute path='/timelines/public/media' component={PublicTimeline} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll, onlyMedia: true }} />
|
||||
<WrappedRoute path='/timelines/public/local' exact component={CommunityTimeline} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
|
||||
<WrappedRoute path='/timelines/public/local/media' component={CommunityTimeline} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll, onlyMedia: true }} />
|
||||
<WrappedRoute path='/timelines/direct' component={DirectTimeline} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
|
||||
<WrappedRoute path='/timelines/tag/:id' component={HashtagTimeline} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
|
||||
<WrappedRoute path='/timelines/list/:id' component={ListTimeline} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
|
||||
|
||||
<WrappedRoute path='/notifications' component={Notifications} content={children} />
|
||||
<WrappedRoute path='/favourites' component={FavouritedStatuses} content={children} />
|
||||
<WrappedRoute path='/pinned' component={PinnedStatuses} content={children} />
|
||||
<WrappedRoute path='/notifications' component={Notifications} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
|
||||
<WrappedRoute path='/favourites' component={FavouritedStatuses} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
|
||||
<WrappedRoute path='/pinned' component={PinnedStatuses} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
|
||||
|
||||
<WrappedRoute path='/search' component={Compose} content={children} componentParams={{ isSearchPage: true }} />
|
||||
|
||||
<WrappedRoute path='/statuses/new' component={Compose} content={children} />
|
||||
<WrappedRoute path='/statuses/:statusId' exact component={Status} content={children} />
|
||||
<WrappedRoute path='/statuses/:statusId/reblogs' component={Reblogs} content={children} />
|
||||
<WrappedRoute path='/statuses/:statusId/favourites' component={Favourites} content={children} />
|
||||
<WrappedRoute path='/statuses/:statusId' exact component={Status} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
|
||||
<WrappedRoute path='/statuses/:statusId/reblogs' component={Reblogs} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
|
||||
<WrappedRoute path='/statuses/:statusId/favourites' component={Favourites} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
|
||||
|
||||
<WrappedRoute path='/accounts/:accountId' exact component={AccountTimeline} content={children} />
|
||||
<WrappedRoute path='/accounts/:accountId/with_replies' component={AccountTimeline} content={children} componentParams={{ withReplies: true }} />
|
||||
<WrappedRoute path='/accounts/:accountId/followers' component={Followers} content={children} />
|
||||
<WrappedRoute path='/accounts/:accountId/following' component={Following} content={children} />
|
||||
<WrappedRoute path='/accounts/:accountId/media' component={AccountGallery} content={children} />
|
||||
<WrappedRoute path='/accounts/:accountId' exact component={AccountTimeline} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
|
||||
<WrappedRoute path='/accounts/:accountId/with_replies' component={AccountTimeline} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll, withReplies: true }} />
|
||||
<WrappedRoute path='/accounts/:accountId/followers' component={Followers} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
|
||||
<WrappedRoute path='/accounts/:accountId/following' component={Following} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
|
||||
<WrappedRoute path='/accounts/:accountId/media' component={AccountGallery} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
|
||||
|
||||
<WrappedRoute path='/follow_requests' component={FollowRequests} content={children} />
|
||||
<WrappedRoute path='/blocks' component={Blocks} content={children} />
|
||||
<WrappedRoute path='/domain_blocks' component={DomainBlocks} content={children} />
|
||||
<WrappedRoute path='/mutes' component={Mutes} content={children} />
|
||||
<WrappedRoute path='/lists' component={Lists} content={children} />
|
||||
<WrappedRoute path='/follow_requests' component={FollowRequests} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
|
||||
<WrappedRoute path='/blocks' component={Blocks} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
|
||||
<WrappedRoute path='/domain_blocks' component={DomainBlocks} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
|
||||
<WrappedRoute path='/mutes' component={Mutes} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
|
||||
<WrappedRoute path='/lists' component={Lists} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
|
||||
|
||||
<WrappedRoute component={GenericNotFound} content={children} />
|
||||
</WrappedSwitch>
|
||||
|
|
@ -422,6 +428,10 @@ export default class UI extends React.PureComponent {
|
|||
this.context.router.history.push('/mutes');
|
||||
}
|
||||
|
||||
handleHotkeyGoToRequests = () => {
|
||||
this.context.router.history.push('/follow_requests');
|
||||
}
|
||||
|
||||
render () {
|
||||
const { draggingOver } = this.state;
|
||||
const { children, isComposing, location, dropdownMenuIsOpen } = this.props;
|
||||
|
|
@ -444,6 +454,7 @@ export default class UI extends React.PureComponent {
|
|||
goToProfile: this.handleHotkeyGoToProfile,
|
||||
goToBlocked: this.handleHotkeyGoToBlocked,
|
||||
goToMuted: this.handleHotkeyGoToMuted,
|
||||
goToRequests: this.handleHotkeyGoToRequests,
|
||||
};
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -158,6 +158,9 @@ export default class Video extends React.PureComponent {
|
|||
this.setState({ dragging: true });
|
||||
this.video.pause();
|
||||
this.handleMouseMove(e);
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
|
||||
handleMouseUp = () => {
|
||||
|
|
@ -174,8 +177,10 @@ export default class Video extends React.PureComponent {
|
|||
const { x } = getPointerPosition(this.seek, e);
|
||||
const currentTime = Math.floor(this.video.duration * x);
|
||||
|
||||
this.video.currentTime = currentTime;
|
||||
this.setState({ currentTime });
|
||||
if (!isNaN(currentTime)) {
|
||||
this.video.currentTime = currentTime;
|
||||
this.setState({ currentTime });
|
||||
}
|
||||
}, 60);
|
||||
|
||||
togglePlay = () => {
|
||||
|
|
@ -281,6 +286,15 @@ export default class Video extends React.PureComponent {
|
|||
playerStyle.height = height;
|
||||
}
|
||||
|
||||
let preload;
|
||||
if (startTime || fullscreen || dragging) {
|
||||
preload = 'auto';
|
||||
} else if (detailed) {
|
||||
preload = 'metadata';
|
||||
} else {
|
||||
preload = 'none';
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
role='menuitem'
|
||||
|
|
@ -296,11 +310,12 @@ export default class Video extends React.PureComponent {
|
|||
ref={this.setVideoRef}
|
||||
src={src}
|
||||
poster={preview}
|
||||
preload={startTime ? 'auto' : 'none'}
|
||||
preload={preload}
|
||||
loop
|
||||
role='button'
|
||||
tabIndex='0'
|
||||
aria-label={alt}
|
||||
title={alt}
|
||||
width={width}
|
||||
height={height}
|
||||
onClick={this.togglePlay}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue