Merge branch 'develop' into groups-updates
* develop: Updated status component to use properStatus when going to status page Added floating action button to ui/index Removed floating action button from columns area Fix issue with notification badge number not showing on mobile Updated scrollable_list to use documentElement for (primary/only) scrolling functionality Added onScroll props to status_list Added timeline scrollTop action, added to status_list_container, scrollable_list Removed unnecessary scrollContainer in status, account_gallery Added missing isLoading prop to ScrollableList Updated scrollable_list intersectionObserverWrapper Updated floatingActionButton to only show if someone is logged in Updated timeline_queue_button_header Removed focus of compose/cw after submit or spoiler change Removed set height of 100% on body Removed unused redirect after compose submit Patch Fix for hidden poll choices and results on light theme. Updated notification badge number formatter Fixed status/repost functionality to show status if owned by given username admin tool for editing pro status of accounts
This commit is contained in:
@@ -1,14 +1,17 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Icon from 'gabsocial/components/icon';
|
||||
import { shortNumberFormat } from 'gabsocial/utils/numbers';
|
||||
|
||||
const formatNumber = num => num > 40 ? '40+' : num;
|
||||
const IconWithBadge = ({ id, count, className }) => {
|
||||
if (count < 1) return null;
|
||||
|
||||
const IconWithBadge = ({ id, count, className }) => (
|
||||
<i className='icon-with-badge'>
|
||||
{count > 0 && <i className='icon-with-badge__badge'>{formatNumber(count)}</i>}
|
||||
</i>
|
||||
);
|
||||
return (
|
||||
<i className='icon-with-badge'>
|
||||
{count > 0 && <i className='icon-with-badge__badge'>{shortNumberFormat(count)}</i>}
|
||||
</i>
|
||||
)
|
||||
};
|
||||
|
||||
IconWithBadge.propTypes = {
|
||||
id: PropTypes.string.isRequired,
|
||||
@@ -16,4 +19,4 @@ IconWithBadge.propTypes = {
|
||||
className: PropTypes.string,
|
||||
};
|
||||
|
||||
export default IconWithBadge;
|
||||
export default IconWithBadge;
|
||||
|
||||
@@ -26,6 +26,8 @@ export default class ScrollableList extends PureComponent {
|
||||
alwaysPrepend: PropTypes.bool,
|
||||
emptyMessage: PropTypes.node,
|
||||
children: PropTypes.node,
|
||||
onScrollToTop: PropTypes.func,
|
||||
onScroll: PropTypes.func,
|
||||
};
|
||||
|
||||
state = {
|
||||
@@ -40,9 +42,9 @@ export default class ScrollableList extends PureComponent {
|
||||
scrollToTopOnMouseIdle = false;
|
||||
|
||||
setScrollTop = newScrollTop => {
|
||||
if (this.node.scrollTop !== newScrollTop) {
|
||||
if (this.documentElement.scrollTop !== newScrollTop) {
|
||||
this.lastScrollWasSynthetic = true;
|
||||
this.node.scrollTop = newScrollTop;
|
||||
this.documentElement.scrollTop = newScrollTop;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -60,7 +62,7 @@ export default class ScrollableList extends PureComponent {
|
||||
this.clearMouseIdleTimer();
|
||||
this.mouseIdleTimer = setTimeout(this.handleMouseIdle, MOUSE_IDLE_DELAY);
|
||||
|
||||
if (!this.mouseMovedRecently && this.node.scrollTop === 0) {
|
||||
if (!this.mouseMovedRecently && this.documentElement.scrollTop === 0) {
|
||||
// Only set if we just started moving and are scrolled to the top.
|
||||
this.scrollToTopOnMouseIdle = true;
|
||||
}
|
||||
@@ -79,19 +81,25 @@ export default class ScrollableList extends PureComponent {
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
this.window = window;
|
||||
this.documentElement = document.documentElement;
|
||||
|
||||
this.attachScrollListener();
|
||||
this.attachIntersectionObserver();
|
||||
// Handle initial scroll posiiton
|
||||
this.handleScroll();
|
||||
}
|
||||
|
||||
getScrollPosition = () => {
|
||||
if (this.node && (this.node.scrollTop > 0 || this.mouseMovedRecently)) {
|
||||
return { height: this.node.scrollHeight, top: this.node.scrollTop };
|
||||
if (this.documentElement && (this.documentElement.scrollTop > 0 || this.mouseMovedRecently)) {
|
||||
return { height: this.documentElement.scrollHeight, top: this.documentElement.scrollTop };
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
updateScrollBottom = (snapshot) => {
|
||||
const newScrollTop = this.node.scrollHeight - snapshot;
|
||||
const newScrollTop = this.documentElement.scrollHeight - snapshot;
|
||||
|
||||
this.setScrollTop(newScrollTop);
|
||||
}
|
||||
@@ -100,7 +108,61 @@ export default class ScrollableList extends PureComponent {
|
||||
// Reset the scroll position when a new child comes in in order not to
|
||||
// jerk the scrollbar around if you're already scrolled down the page.
|
||||
if (snapshot !== null) {
|
||||
this.setScrollTop(this.node.scrollHeight - snapshot);
|
||||
this.setScrollTop(this.documentElement.scrollHeight - snapshot);
|
||||
}
|
||||
}
|
||||
|
||||
attachScrollListener () {
|
||||
this.window.addEventListener('scroll', this.handleScroll);
|
||||
this.window.addEventListener('wheel', this.handleWheel);
|
||||
}
|
||||
|
||||
detachScrollListener () {
|
||||
this.window.removeEventListener('scroll', this.handleScroll);
|
||||
this.window.removeEventListener('wheel', this.handleWheel);
|
||||
}
|
||||
|
||||
handleScroll = throttle(() => {
|
||||
if (this.window) {
|
||||
const { scrollTop, scrollHeight, clientHeight } = this.documentElement;
|
||||
const offset = scrollHeight - scrollTop - clientHeight;
|
||||
|
||||
if (600 > offset && this.props.onLoadMore && this.props.hasMore && !this.props.isLoading) {
|
||||
this.props.onLoadMore();
|
||||
}
|
||||
|
||||
if (scrollTop < 100 && this.props.onScrollToTop) {
|
||||
this.props.onScrollToTop();
|
||||
} else if (this.props.onScroll) {
|
||||
this.props.onScroll();
|
||||
}
|
||||
|
||||
if (!this.lastScrollWasSynthetic) {
|
||||
// If the last scroll wasn't caused by setScrollTop(), assume it was
|
||||
// intentional and cancel any pending scroll reset on mouse idle
|
||||
this.scrollToTopOnMouseIdle = false;
|
||||
}
|
||||
this.lastScrollWasSynthetic = false;
|
||||
}
|
||||
}, 150, {
|
||||
trailing: true,
|
||||
});
|
||||
|
||||
handleWheel = throttle(() => {
|
||||
this.scrollToTopOnMouseIdle = false;
|
||||
}, 150, {
|
||||
trailing: true,
|
||||
});
|
||||
|
||||
getSnapshotBeforeUpdate (prevProps) {
|
||||
const someItemInserted = React.Children.count(prevProps.children) > 0 &&
|
||||
React.Children.count(prevProps.children) < React.Children.count(this.props.children) &&
|
||||
this.getFirstChildKey(prevProps) !== this.getFirstChildKey(this.props);
|
||||
|
||||
if (someItemInserted && (this.documentElement.scrollTop > 0 || this.mouseMovedRecently)) {
|
||||
return this.documentElement.scrollHeight - this.documentElement.scrollTop;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,10 +178,7 @@ export default class ScrollableList extends PureComponent {
|
||||
}
|
||||
|
||||
attachIntersectionObserver () {
|
||||
this.intersectionObserverWrapper.connect({
|
||||
root: this.node,
|
||||
rootMargin: '300% 0px',
|
||||
});
|
||||
this.intersectionObserverWrapper.connect();
|
||||
}
|
||||
|
||||
detachIntersectionObserver () {
|
||||
@@ -139,10 +198,6 @@ export default class ScrollableList extends PureComponent {
|
||||
return firstChild && firstChild.key;
|
||||
}
|
||||
|
||||
setRef = (c) => {
|
||||
this.node = c;
|
||||
}
|
||||
|
||||
handleLoadMore = e => {
|
||||
e.preventDefault();
|
||||
this.props.onLoadMore();
|
||||
@@ -159,7 +214,7 @@ export default class ScrollableList extends PureComponent {
|
||||
|
||||
if (showLoading) {
|
||||
scrollableArea = (
|
||||
<div className='slist slist--flex' ref={this.setRef}>
|
||||
<div className='slist slist--flex'>
|
||||
<div role='feed' className='item-list'>
|
||||
{prepend}
|
||||
</div>
|
||||
|
||||
@@ -168,8 +168,7 @@ class Status extends ImmutablePureComponent {
|
||||
return;
|
||||
}
|
||||
|
||||
const { status } = this.props;
|
||||
this.context.router.history.push(`/${status.getIn(['account', 'acct'])}/posts/${status.getIn(['reblog', 'id'], status.get('id'))}`);
|
||||
this.context.router.history.push(`/${this._properStatus().getIn(['account', 'acct'])}/posts/${this._properStatus().get('id')}`);
|
||||
}
|
||||
|
||||
handleExpandClick = (e) => {
|
||||
@@ -178,8 +177,7 @@ class Status extends ImmutablePureComponent {
|
||||
return;
|
||||
}
|
||||
|
||||
const { status } = this.props;
|
||||
this.context.router.history.push(`/${status.getIn(['account', 'acct'])}/posts/${status.getIn(['reblog', 'id'], status.get('id'))}`);
|
||||
this.context.router.history.push(`/${this._properStatus().getIn(['account', 'acct'])}/posts/${this._properStatus().get('id')}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -218,7 +216,7 @@ class Status extends ImmutablePureComponent {
|
||||
}
|
||||
|
||||
handleHotkeyOpen = () => {
|
||||
this.context.router.history.push(`/${status.getIn(['account', 'acct'])}/posts/${this._properStatus().get('id')}`);
|
||||
this.context.router.history.push(`/${this._properStatus().getIn(['account', 'acct'])}/posts/${this._properStatus().get('id')}`);
|
||||
}
|
||||
|
||||
handleHotkeyOpenProfile = () => {
|
||||
|
||||
@@ -26,6 +26,8 @@ export default class StatusList extends ImmutablePureComponent {
|
||||
queuedItemSize: PropTypes.number,
|
||||
onDequeueTimeline: PropTypes.func,
|
||||
withGroupAdmin: PropTypes.bool,
|
||||
onScrollToTop: PropTypes.func,
|
||||
onScroll: PropTypes.func,
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
@@ -135,7 +137,7 @@ export default class StatusList extends ImmutablePureComponent {
|
||||
|
||||
return [
|
||||
<TimelineQueueButtonHeader key='timeline-queue-button-header' onClick={this.handleDequeueTimeline} count={totalQueuedItemsCount} itemType='gab' />,
|
||||
<ScrollableList key='scrollable-list' {...other} showLoading={isLoading && statusIds.size === 0} onLoadMore={onLoadMore && this.handleLoadOlder} ref={this.setRef}>
|
||||
<ScrollableList key='scrollable-list' {...other} isLoading={isLoading} showLoading={isLoading && statusIds.size === 0} onLoadMore={onLoadMore && this.handleLoadOlder} ref={this.setRef}>
|
||||
{scrollableContent}
|
||||
</ScrollableList>
|
||||
];
|
||||
|
||||
@@ -2,6 +2,7 @@ import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { shortNumberFormat } from '../utils/numbers';
|
||||
import classNames from 'classnames';
|
||||
|
||||
export default class TimelineQueueButtonHeader extends React.PureComponent {
|
||||
static propTypes = {
|
||||
@@ -18,19 +19,21 @@ export default class TimelineQueueButtonHeader extends React.PureComponent {
|
||||
render () {
|
||||
const { count, itemType, onClick } = this.props;
|
||||
|
||||
if (count <= 0) return null;
|
||||
const classes = classNames('timeline-queue-header', {
|
||||
'hidden': (count <= 0)
|
||||
});
|
||||
|
||||
return (
|
||||
<div className='timeline-queue-header'>
|
||||
<div className={classes}>
|
||||
<a className='timeline-queue-header__btn' onClick={onClick}>
|
||||
<FormattedMessage
|
||||
{(count > 0) && <FormattedMessage
|
||||
id='timeline_queue.label'
|
||||
defaultMessage='Click to see {count} new {type}'
|
||||
values={{
|
||||
count: shortNumberFormat(count),
|
||||
type: count == 1 ? itemType : `${itemType}s`,
|
||||
}}
|
||||
/>
|
||||
/>}
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user