--- /dev/null
+export const HEIGHT_CACHE_SET = 'HEIGHT_CACHE_SET';
+export const HEIGHT_CACHE_CLEAR = 'HEIGHT_CACHE_CLEAR';
+
+export function setHeight (key, id, height) {
+ return {
+ type: HEIGHT_CACHE_SET,
+ key,
+ id,
+ height,
+ };
+};
+
+export function clearHeight () {
+ return {
+ type: HEIGHT_CACHE_CLEAR,
+ };
+};
export const STATUS_UNMUTE_SUCCESS = 'STATUS_UNMUTE_SUCCESS';
export const STATUS_UNMUTE_FAIL = 'STATUS_UNMUTE_FAIL';
-export const STATUS_SET_HEIGHT = 'STATUS_SET_HEIGHT';
-export const STATUSES_CLEAR_HEIGHT = 'STATUSES_CLEAR_HEIGHT';
-
export function fetchStatusRequest(id, skipLoading) {
return {
type: STATUS_FETCH_REQUEST,
error,
};
};
-
-export function setStatusHeight (id, height) {
- return {
- type: STATUS_SET_HEIGHT,
- id,
- height,
- };
-};
-
-export function clearStatusesHeight () {
- return {
- type: STATUSES_CLEAR_HEIGHT,
- };
-};
export default class IntersectionObserverArticle extends ImmutablePureComponent {
static propTypes = {
- intersectionObserverWrapper: PropTypes.object,
+ intersectionObserverWrapper: PropTypes.object.isRequired,
id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
index: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
listLength: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
+ saveHeightKey: PropTypes.string,
+ cachedHeight: PropTypes.number,
+ onHeightChange: PropTypes.func,
children: PropTypes.node,
};
}
componentDidMount () {
- if (!this.props.intersectionObserverWrapper) {
- // TODO: enable IntersectionObserver optimization for notification statuses.
- // These are managed in notifications/index.js rather than status_list.js
- return;
- }
- this.props.intersectionObserverWrapper.observe(
- this.props.id,
+ const { intersectionObserverWrapper, id } = this.props;
+
+ intersectionObserverWrapper.observe(
+ id,
this.node,
this.handleIntersection
);
}
componentWillUnmount () {
- if (this.props.intersectionObserverWrapper) {
- this.props.intersectionObserverWrapper.unobserve(this.props.id, this.node);
- }
+ const { intersectionObserverWrapper, id } = this.props;
+ intersectionObserverWrapper.unobserve(id, this.node);
this.componentMounted = false;
}
handleIntersection = (entry) => {
+ const { onHeightChange, saveHeightKey, id } = this.props;
+
if (this.node && this.node.children.length !== 0) {
// save the height of the fully-rendered element
this.height = getRectFromEntry(entry).height;
- if (this.props.onHeightChange) {
- this.props.onHeightChange(this.props.status, this.height);
+ if (onHeightChange && saveHeightKey) {
+ onHeightChange(saveHeightKey, id, this.height);
}
}
}
render () {
- const { children, id, index, listLength } = this.props;
+ const { children, id, index, listLength, cachedHeight } = this.props;
const { isIntersecting, isHidden } = this.state;
- if (!isIntersecting && isHidden) {
+ if (!isIntersecting && (isHidden || cachedHeight)) {
return (
<article
ref={this.handleRef}
aria-posinset={index}
aria-setsize={listLength}
- style={{ height: `${this.height}px`, opacity: 0, overflow: 'hidden' }}
+ style={{ height: `${this.height || cachedHeight}px`, opacity: 0, overflow: 'hidden' }}
data-id={id}
tabIndex='0'
>
import React, { PureComponent } from 'react';
import { ScrollContainer } from 'react-router-scroll';
import PropTypes from 'prop-types';
-import IntersectionObserverArticle from './intersection_observer_article';
+import IntersectionObserverArticleContainer from '../containers/intersection_observer_article_container';
import LoadMore from './load_more';
import IntersectionObserverWrapper from '../features/ui/util/intersection_observer_wrapper';
import { throttle } from 'lodash';
export default class ScrollableList extends PureComponent {
+ static contextTypes = {
+ router: PropTypes.object,
+ };
+
static propTypes = {
scrollKey: PropTypes.string.isRequired,
onScrollToBottom: PropTypes.func,
{prepend}
{React.Children.map(this.props.children, (child, index) => (
- <IntersectionObserverArticle key={child.key} id={child.key} index={index} listLength={childrenCount} intersectionObserverWrapper={this.intersectionObserverWrapper}>
+ <IntersectionObserverArticleContainer
+ key={child.key}
+ id={child.key}
+ index={index}
+ listLength={childrenCount}
+ intersectionObserverWrapper={this.intersectionObserverWrapper}
+ saveHeightKey={trackScroll ? `${this.context.router.route.location.key}:${scrollKey}` : null}
+ >
{child}
- </IntersectionObserverArticle>
+ </IntersectionObserverArticleContainer>
))}
{loadMore}
--- /dev/null
+import { connect } from 'react-redux';
+import IntersectionObserverArticle from '../components/intersection_observer_article';
+import { setHeight } from '../actions/height_cache';
+
+const makeMapStateToProps = (state, props) => ({
+ cachedHeight: state.getIn(['height_cache', props.saveHeightKey, props.id]),
+});
+
+const mapDispatchToProps = (dispatch) => ({
+
+ onHeightChange (key, id, height) {
+ dispatch(setHeight(key, id, height));
+ },
+
+});
+
+export default connect(makeMapStateToProps, mapDispatchToProps)(IntersectionObserverArticle);
blockAccount,
muteAccount,
} from '../actions/accounts';
-import { muteStatus, unmuteStatus, deleteStatus, setStatusHeight } from '../actions/statuses';
+import { muteStatus, unmuteStatus, deleteStatus } from '../actions/statuses';
import { initReport } from '../actions/reports';
import { openModal } from '../actions/modal';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
}
},
- onHeightChange (status, height) {
- dispatch(setStatusHeight(status.get('id'), height));
- },
-
});
export default injectIntl(connect(makeMapStateToProps, mapDispatchToProps)(Status));
import { uploadCompose } from '../../actions/compose';
import { refreshHomeTimeline } from '../../actions/timelines';
import { refreshNotifications } from '../../actions/notifications';
-import { clearStatusesHeight } from '../../actions/statuses';
+import { clearHeight } from '../../actions/height_cache';
import { WrappedSwitch, WrappedRoute } from './util/react_router_helpers';
import UploadArea from './components/upload_area';
import ColumnsAreaContainer from './containers/columns_area_container';
handleResize = debounce(() => {
// The cached heights are no longer accurate, invalidate
- this.props.dispatch(clearStatusesHeight());
+ this.props.dispatch(clearHeight());
this.setState({ width: window.innerWidth });
}, 500, {
--- /dev/null
+import { Map as ImmutableMap } from 'immutable';
+import { HEIGHT_CACHE_SET, HEIGHT_CACHE_CLEAR } from '../actions/height_cache';
+
+const initialState = ImmutableMap();
+
+const setHeight = (state, key, id, height) => {
+ return state.update(key, ImmutableMap(), map => map.set(id, height));
+};
+
+const clearHeights = () => {
+ return ImmutableMap();
+};
+
+export default function statuses(state = initialState, action) {
+ switch(action.type) {
+ case HEIGHT_CACHE_SET:
+ return setHeight(state, action.key, action.id, action.height);
+ case HEIGHT_CACHE_CLEAR:
+ return clearHeights();
+ default:
+ return state;
+ }
+};
import search from './search';
import media_attachments from './media_attachments';
import notifications from './notifications';
+import height_cache from './height_cache';
const reducers = {
timelines,
search,
media_attachments,
notifications,
+ height_cache,
};
export default combineReducers(reducers);
CONTEXT_FETCH_SUCCESS,
STATUS_MUTE_SUCCESS,
STATUS_UNMUTE_SUCCESS,
- STATUS_SET_HEIGHT,
- STATUSES_CLEAR_HEIGHT,
} from '../actions/statuses';
import {
TIMELINE_REFRESH_SUCCESS,
return state;
};
-const setHeight = (state, id, height) => {
- return state.update(id, ImmutableMap(), map => map.set('height', height));
-};
-
-const clearHeights = (state) => {
- state.forEach(status => {
- state = state.deleteIn([status.get('id'), 'height']);
- });
-
- return state;
-};
-
const initialState = ImmutableMap();
export default function statuses(state = initialState, action) {
return deleteStatus(state, action.id, action.references);
case ACCOUNT_BLOCK_SUCCESS:
return filterStatuses(state, action.relationship);
- case STATUS_SET_HEIGHT:
- return setHeight(state, action.id, action.height);
- case STATUSES_CLEAR_HEIGHT:
- return clearHeights(state);
default:
return state;
}