Merge branch 'glitch' into thread-icon

This commit is contained in:
Spencer Alves
2018-05-31 21:33:16 -07:00
453 changed files with 9721 additions and 4161 deletions

View File

@@ -0,0 +1,33 @@
import React from 'react';
import PropTypes from 'prop-types';
import { injectIntl, defineMessages } from 'react-intl';
const messages = defineMessages({
load_more: { id: 'status.load_more', defaultMessage: 'Load more' },
});
@injectIntl
export default class LoadGap extends React.PureComponent {
static propTypes = {
disabled: PropTypes.bool,
maxId: PropTypes.string,
onClick: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
};
handleClick = () => {
this.props.onClick(this.props.maxId);
}
render () {
const { disabled, intl } = this.props;
return (
<button className='load-more load-gap' disabled={disabled} onClick={this.handleClick} aria-label={intl.formatMessage(messages.load_more)}>
<i className='fa fa-ellipsis-h' />
</button>
);
}
}

View File

@@ -6,6 +6,7 @@ export default class LoadMore extends React.PureComponent {
static propTypes = {
onClick: PropTypes.func,
disabled: PropTypes.bool,
visible: PropTypes.bool,
}
@@ -14,10 +15,10 @@ export default class LoadMore extends React.PureComponent {
}
render() {
const { visible } = this.props;
const { disabled, visible } = this.props;
return (
<button className='load-more' disabled={!visible} style={{ visibility: visible ? 'visible' : 'hidden' }} onClick={this.props.onClick}>
<button className='load-more' disabled={disabled || !visible} style={{ visibility: visible ? 'visible' : 'hidden' }} onClick={this.props.onClick}>
<FormattedMessage id='status.load_more' defaultMessage='Load more' />
</button>
);

View File

@@ -40,6 +40,7 @@ class Item extends React.PureComponent {
size: PropTypes.number.isRequired,
letterbox: PropTypes.bool,
onClick: PropTypes.func.isRequired,
displayWidth: PropTypes.number,
};
static defaultProps = {
@@ -78,7 +79,7 @@ class Item extends React.PureComponent {
}
render () {
const { attachment, index, size, standalone, letterbox } = this.props;
const { attachment, index, size, standalone, letterbox, displayWidth } = this.props;
let width = 50;
let height = 100;
@@ -141,7 +142,7 @@ class Item extends React.PureComponent {
const hasSize = typeof originalWidth === 'number' && typeof previewWidth === 'number';
const srcSet = hasSize ? `${originalUrl} ${originalWidth}w, ${previewUrl} ${previewWidth}w` : null;
const sizes = hasSize ? `(min-width: 1025px) ${320 * (width / 100)}px, ${width}vw` : null;
const sizes = hasSize ? `${displayWidth * (width / 100)}px` : null;
const focusX = attachment.getIn(['meta', 'focus', 'x']) || 0;
const focusY = attachment.getIn(['meta', 'focus', 'y']) || 0;
@@ -235,7 +236,7 @@ export default class MediaGallery extends React.PureComponent {
}
handleRef = (node) => {
if (node && this.isStandaloneEligible()) {
if (node /*&& this.isStandaloneEligible()*/) {
// offsetWidth triggers a layout, so only calculate when we need to
this.setState({
width: node.offsetWidth,
@@ -272,9 +273,9 @@ export default class MediaGallery extends React.PureComponent {
);
} else {
if (this.isStandaloneEligible()) {
children = <Item standalone attachment={media.get(0)} onClick={this.handleClick} />;
children = <Item standalone attachment={media.get(0)} onClick={this.handleClick} displayWidth={width} />;
} else {
children = media.take(4).map((attachment, i) => <Item key={attachment.get('id')} onClick={this.handleClick} attachment={attachment} index={i} size={size} letterbox={letterbox} />);
children = media.take(4).map((attachment, i) => <Item key={attachment.get('id')} onClick={this.handleClick} attachment={attachment} index={i} size={size} letterbox={letterbox} displayWidth={width} />);
}
}

View File

@@ -6,6 +6,7 @@ export default class ModalRoot extends React.PureComponent {
static propTypes = {
children: PropTypes.node,
onClose: PropTypes.func.isRequired,
noEsc: PropTypes.bool,
};
state = {
@@ -16,7 +17,7 @@ export default class ModalRoot extends React.PureComponent {
handleKeyUp = (e) => {
if ((e.key === 'Escape' || e.key === 'Esc' || e.keyCode === 27)
&& !!this.props.children && !this.props.props.noEsc) {
&& !!this.props.children && !this.props.noEsc) {
this.props.onClose();
}
}

View File

@@ -17,7 +17,7 @@ export default class ScrollableList extends PureComponent {
static propTypes = {
scrollKey: PropTypes.string.isRequired,
onScrollToBottom: PropTypes.func,
onLoadMore: PropTypes.func,
onScrollToTop: PropTypes.func,
onScroll: PropTypes.func,
trackScroll: PropTypes.bool,
@@ -44,9 +44,11 @@ export default class ScrollableList extends PureComponent {
const { scrollTop, scrollHeight, clientHeight } = this.node;
const offset = scrollHeight - scrollTop - clientHeight;
if (400 > offset && this.props.onScrollToBottom && !this.props.isLoading) {
this.props.onScrollToBottom();
} else if (scrollTop < 100 && this.props.onScrollToTop) {
if (400 > offset && this.props.onLoadMore && !this.props.isLoading) {
this.props.onLoadMore();
}
if (scrollTop < 100 && this.props.onScrollToTop) {
this.props.onScrollToTop();
} else if (this.props.onScroll) {
this.props.onScroll();
@@ -144,15 +146,15 @@ export default class ScrollableList extends PureComponent {
handleLoadMore = (e) => {
e.preventDefault();
this.props.onScrollToBottom();
this.props.onLoadMore();
}
render () {
const { children, scrollKey, trackScroll, shouldUpdateScroll, isLoading, hasMore, prepend, emptyMessage } = this.props;
const { children, scrollKey, trackScroll, shouldUpdateScroll, isLoading, hasMore, prepend, emptyMessage, onLoadMore } = this.props;
const { fullscreen } = this.state;
const childrenCount = React.Children.count(children);
const loadMore = (hasMore && childrenCount > 0) ? <LoadMore visible={!isLoading} onClick={this.handleLoadMore} /> : null;
const loadMore = (hasMore && childrenCount > 0 && onLoadMore) ? <LoadMore visible={!isLoading} onClick={this.handleLoadMore} /> : null;
let scrollableArea = null;
if (isLoading || childrenCount > 0 || !emptyMessage) {

View File

@@ -32,6 +32,8 @@ export default class Status extends ImmutablePureComponent {
onFavourite: PropTypes.func,
onReblog: PropTypes.func,
onDelete: PropTypes.func,
onDirect: PropTypes.func,
onMention: PropTypes.func,
onPin: PropTypes.func,
onOpenMedia: PropTypes.func,
onOpenVideo: PropTypes.func,
@@ -257,8 +259,8 @@ export default class Status extends ImmutablePureComponent {
}
};
handleOpenVideo = startTime => {
this.props.onOpenVideo(this.props.status.getIn(['media_attachments', 0]), startTime);
handleOpenVideo = (media, startTime) => {
this.props.onOpenVideo(media, startTime);
}
handleHotkeyReply = e => {

View File

@@ -10,6 +10,7 @@ import RelativeTimestamp from './relative_timestamp';
const messages = defineMessages({
delete: { id: 'status.delete', defaultMessage: 'Delete' },
direct: { id: 'status.direct', defaultMessage: 'Direct message @{name}' },
mention: { id: 'status.mention', defaultMessage: 'Mention @{name}' },
mute: { id: 'account.mute', defaultMessage: 'Mute @{name}' },
block: { id: 'account.block', defaultMessage: 'Block @{name}' },
@@ -44,6 +45,7 @@ export default class StatusActionBar extends ImmutablePureComponent {
onFavourite: PropTypes.func,
onReblog: PropTypes.func,
onDelete: PropTypes.func,
onDirect: PropTypes.func,
onMention: PropTypes.func,
onMute: PropTypes.func,
onBlock: PropTypes.func,
@@ -98,6 +100,10 @@ export default class StatusActionBar extends ImmutablePureComponent {
this.props.onMention(this.props.status.get('account'), this.context.router.history);
}
handleDirectClick = () => {
this.props.onDirect(this.props.status.get('account'), this.context.router.history);
}
handleMuteClick = () => {
this.props.onMute(this.props.status.get('account'));
}
@@ -157,6 +163,7 @@ export default class StatusActionBar extends ImmutablePureComponent {
menu.push({ text: intl.formatMessage(messages.delete), action: this.handleDeleteClick });
} else {
menu.push({ text: intl.formatMessage(messages.mention, { name: status.getIn(['account', 'username']) }), action: this.handleMentionClick });
menu.push({ text: intl.formatMessage(messages.direct, { name: status.getIn(['account', 'username']) }), action: this.handleDirectClick });
menu.push(null);
menu.push({ text: intl.formatMessage(messages.mute, { name: status.getIn(['account', 'username']) }), action: this.handleMuteClick });
menu.push({ text: intl.formatMessage(messages.block, { name: status.getIn(['account', 'username']) }), action: this.handleBlockClick });

View File

@@ -98,7 +98,7 @@ export default class StatusContent extends React.PureComponent {
const [ startX, startY ] = this.startXY;
const [ deltaX, deltaY ] = [Math.abs(e.clientX - startX), Math.abs(e.clientY - startY)];
if (e.target.localName === 'button' || e.target.localName === 'a' || (e.target.parentNode && (e.target.parentNode.localName === 'button' || e.target.parentNode.localName === 'a'))) {
if (e.target.localName === 'button' || e.target.localName == 'video' || e.target.localName === 'a' || (e.target.parentNode && (e.target.parentNode.localName === 'button' || e.target.parentNode.localName === 'a'))) {
return;
}
@@ -188,11 +188,9 @@ export default class StatusContent extends React.PureComponent {
}
return (
<div className={classNames} tabIndex='0'>
<div className={classNames} tabIndex='0' onMouseDown={this.handleMouseDown} onMouseUp={this.handleMouseUp}>
<p
style={{ marginBottom: hidden && status.get('mentions').isEmpty() ? '0px' : null }}
onMouseDown={this.handleMouseDown}
onMouseUp={this.handleMouseUp}
>
<span dangerouslySetInnerHTML={spoilerContent} />
{' '}
@@ -208,8 +206,6 @@ export default class StatusContent extends React.PureComponent {
ref={this.setRef}
style={directionStyle}
tabIndex={!hidden ? 0 : null}
onMouseDown={this.handleMouseDown}
onMouseUp={this.handleMouseUp}
dangerouslySetInnerHTML={content}
/>
{media}
@@ -222,12 +218,12 @@ export default class StatusContent extends React.PureComponent {
<div
className={classNames}
style={directionStyle}
onMouseDown={this.handleMouseDown}
onMouseUp={this.handleMouseUp}
tabIndex='0'
>
<div
ref={this.setRef}
onMouseDown={this.handleMouseDown}
onMouseUp={this.handleMouseUp}
dangerouslySetInnerHTML={content}
tabIndex='0'
/>

View File

@@ -1,8 +1,10 @@
import { debounce } from 'lodash';
import React from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types';
import StatusContainer from 'flavours/glitch/containers/status_container';
import ImmutablePureComponent from 'react-immutable-pure-component';
import LoadGap from './load_gap';
import ScrollableList from './scrollable_list';
import { FormattedMessage } from 'react-intl';
@@ -12,7 +14,7 @@ export default class StatusList extends ImmutablePureComponent {
scrollKey: PropTypes.string.isRequired,
statusIds: ImmutablePropTypes.list.isRequired,
featuredStatusIds: ImmutablePropTypes.list,
onScrollToBottom: PropTypes.func,
onLoadMore: PropTypes.func,
onScrollToTop: PropTypes.func,
onScroll: PropTypes.func,
trackScroll: PropTypes.bool,
@@ -50,6 +52,10 @@ export default class StatusList extends ImmutablePureComponent {
this._selectChild(elementIndex);
}
handleLoadOlder = debounce(() => {
this.props.onLoadMore(this.props.statusIds.last());
}, 300, { leading: true })
_selectChild (index) {
const element = this.node.node.querySelector(`article:nth-of-type(${index + 1}) .focusable`);
@@ -63,7 +69,7 @@ export default class StatusList extends ImmutablePureComponent {
}
render () {
const { statusIds, featuredStatusIds, ...other } = this.props;
const { statusIds, featuredStatusIds, onLoadMore, ...other } = this.props;
const { isLoading, isPartial } = other;
if (isPartial) {
@@ -82,7 +88,14 @@ export default class StatusList extends ImmutablePureComponent {
}
let scrollableContent = (isLoading || statusIds.size > 0) ? (
statusIds.map(statusId => (
statusIds.map((statusId, index) => statusId === null ? (
<LoadGap
key={'gap:' + statusIds.get(index + 1)}
disabled={isLoading}
maxId={index > 0 ? statusIds.get(index - 1) : null}
onClick={onLoadMore}
/>
) : (
<StatusContainer
key={statusId}
id={statusId}
@@ -105,7 +118,7 @@ export default class StatusList extends ImmutablePureComponent {
}
return (
<ScrollableList {...other} ref={this.setRef}>
<ScrollableList {...other} onLoadMore={onLoadMore && this.handleLoadOlder} ref={this.setRef}>
{scrollableContent}
</ScrollableList>
);