[Glitch] Add option to disable real-time updates in web UI
Port 729723f857d11434c0f78d63fe16537d77f1c77c to glitch-soc Signed-off-by: Thibaut Girka <thib@sitedethib.com>
This commit is contained in:
		
							parent
							
								
									c8a47595fb
								
							
						
					
					
						commit
						e91bf82083
					
				@ -12,6 +12,8 @@ import { defineMessages } from 'react-intl';
 | 
				
			|||||||
import { List as ImmutableList } from 'immutable';
 | 
					import { List as ImmutableList } from 'immutable';
 | 
				
			||||||
import { unescapeHTML } from 'flavours/glitch/util/html';
 | 
					import { unescapeHTML } from 'flavours/glitch/util/html';
 | 
				
			||||||
import { getFiltersRegex } from 'flavours/glitch/selectors';
 | 
					import { getFiltersRegex } from 'flavours/glitch/selectors';
 | 
				
			||||||
 | 
					import { usePendingItems as preferPendingItems } from 'flavours/glitch/util/initial_state';
 | 
				
			||||||
 | 
					import compareId from 'flavours/glitch/util/compare_id';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const NOTIFICATIONS_UPDATE = 'NOTIFICATIONS_UPDATE';
 | 
					export const NOTIFICATIONS_UPDATE = 'NOTIFICATIONS_UPDATE';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -32,8 +34,9 @@ export const NOTIFICATIONS_EXPAND_FAIL    = 'NOTIFICATIONS_EXPAND_FAIL';
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
export const NOTIFICATIONS_FILTER_SET = 'NOTIFICATIONS_FILTER_SET';
 | 
					export const NOTIFICATIONS_FILTER_SET = 'NOTIFICATIONS_FILTER_SET';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const NOTIFICATIONS_CLEAR      = 'NOTIFICATIONS_CLEAR';
 | 
					export const NOTIFICATIONS_CLEAR        = 'NOTIFICATIONS_CLEAR';
 | 
				
			||||||
export const NOTIFICATIONS_SCROLL_TOP = 'NOTIFICATIONS_SCROLL_TOP';
 | 
					export const NOTIFICATIONS_SCROLL_TOP   = 'NOTIFICATIONS_SCROLL_TOP';
 | 
				
			||||||
 | 
					export const NOTIFICATIONS_LOAD_PENDING = 'NOTIFICATIONS_LOAD_PENDING';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const NOTIFICATIONS_MOUNT   = 'NOTIFICATIONS_MOUNT';
 | 
					export const NOTIFICATIONS_MOUNT   = 'NOTIFICATIONS_MOUNT';
 | 
				
			||||||
export const NOTIFICATIONS_UNMOUNT = 'NOTIFICATIONS_UNMOUNT';
 | 
					export const NOTIFICATIONS_UNMOUNT = 'NOTIFICATIONS_UNMOUNT';
 | 
				
			||||||
@ -52,6 +55,10 @@ const fetchRelatedRelationships = (dispatch, notifications) => {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const loadPending = () => ({
 | 
				
			||||||
 | 
					  type: NOTIFICATIONS_LOAD_PENDING,
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function updateNotifications(notification, intlMessages, intlLocale) {
 | 
					export function updateNotifications(notification, intlMessages, intlLocale) {
 | 
				
			||||||
  return (dispatch, getState) => {
 | 
					  return (dispatch, getState) => {
 | 
				
			||||||
    const showInColumn = getState().getIn(['settings', 'notifications', 'shows', notification.type], true);
 | 
					    const showInColumn = getState().getIn(['settings', 'notifications', 'shows', notification.type], true);
 | 
				
			||||||
@ -83,6 +90,7 @@ export function updateNotifications(notification, intlMessages, intlLocale) {
 | 
				
			|||||||
      dispatch({
 | 
					      dispatch({
 | 
				
			||||||
        type: NOTIFICATIONS_UPDATE,
 | 
					        type: NOTIFICATIONS_UPDATE,
 | 
				
			||||||
        notification,
 | 
					        notification,
 | 
				
			||||||
 | 
					        usePendingItems: preferPendingItems,
 | 
				
			||||||
        meta: (playSound && !filtered) ? { sound: 'boop' } : undefined,
 | 
					        meta: (playSound && !filtered) ? { sound: 'boop' } : undefined,
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -136,10 +144,19 @@ export function expandNotifications({ maxId } = {}, done = noOp) {
 | 
				
			|||||||
        : excludeTypesFromFilter(activeFilter),
 | 
					        : excludeTypesFromFilter(activeFilter),
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (!maxId && notifications.get('items').size > 0) {
 | 
					    if (!params.max_id && (notifications.get('items', ImmutableList()).size + notifications.get('pendingItems', ImmutableList()).size) > 0) {
 | 
				
			||||||
      params.since_id = notifications.getIn(['items', 0, 'id']);
 | 
					      const a = notifications.getIn(['pendingItems', 0, 'id']);
 | 
				
			||||||
 | 
					      const b = notifications.getIn(['items', 0, 'id']);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (a && b && compareId(a, b) > 0) {
 | 
				
			||||||
 | 
					        params.since_id = a;
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        params.since_id = b || a;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const isLoadingRecent = !!params.since_id;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    dispatch(expandNotificationsRequest(isLoadingMore));
 | 
					    dispatch(expandNotificationsRequest(isLoadingMore));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    api(getState).get('/api/v1/notifications', { params }).then(response => {
 | 
					    api(getState).get('/api/v1/notifications', { params }).then(response => {
 | 
				
			||||||
@ -148,7 +165,7 @@ export function expandNotifications({ maxId } = {}, done = noOp) {
 | 
				
			|||||||
      dispatch(importFetchedAccounts(response.data.map(item => item.account)));
 | 
					      dispatch(importFetchedAccounts(response.data.map(item => item.account)));
 | 
				
			||||||
      dispatch(importFetchedStatuses(response.data.map(item => item.status).filter(status => !!status)));
 | 
					      dispatch(importFetchedStatuses(response.data.map(item => item.status).filter(status => !!status)));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      dispatch(expandNotificationsSuccess(response.data, next ? next.uri : null, isLoadingMore));
 | 
					      dispatch(expandNotificationsSuccess(response.data, next ? next.uri : null, isLoadingMore, isLoadingRecent && preferPendingItems));
 | 
				
			||||||
      fetchRelatedRelationships(dispatch, response.data);
 | 
					      fetchRelatedRelationships(dispatch, response.data);
 | 
				
			||||||
      done();
 | 
					      done();
 | 
				
			||||||
    }).catch(error => {
 | 
					    }).catch(error => {
 | 
				
			||||||
@ -165,13 +182,12 @@ export function expandNotificationsRequest(isLoadingMore) {
 | 
				
			|||||||
  };
 | 
					  };
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function expandNotificationsSuccess(notifications, next, isLoadingMore) {
 | 
					export function expandNotificationsSuccess(notifications, next, isLoadingMore, usePendingItems) {
 | 
				
			||||||
  return {
 | 
					  return {
 | 
				
			||||||
    type: NOTIFICATIONS_EXPAND_SUCCESS,
 | 
					    type: NOTIFICATIONS_EXPAND_SUCCESS,
 | 
				
			||||||
    notifications,
 | 
					    notifications,
 | 
				
			||||||
    accounts: notifications.map(item => item.account),
 | 
					 | 
				
			||||||
    statuses: notifications.map(item => item.status).filter(status => !!status),
 | 
					 | 
				
			||||||
    next,
 | 
					    next,
 | 
				
			||||||
 | 
					    usePendingItems,
 | 
				
			||||||
    skipLoading: !isLoadingMore,
 | 
					    skipLoading: !isLoadingMore,
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
@ -1,6 +1,8 @@
 | 
				
			|||||||
import { importFetchedStatus, importFetchedStatuses } from './importer';
 | 
					import { importFetchedStatus, importFetchedStatuses } from './importer';
 | 
				
			||||||
import api, { getLinks } from 'flavours/glitch/util/api';
 | 
					import api, { getLinks } from 'flavours/glitch/util/api';
 | 
				
			||||||
import { Map as ImmutableMap, List as ImmutableList } from 'immutable';
 | 
					import { Map as ImmutableMap, List as ImmutableList } from 'immutable';
 | 
				
			||||||
 | 
					import compareId from 'flavours/glitch/util/compare_id';
 | 
				
			||||||
 | 
					import { usePendingItems as preferPendingItems } from 'flavours/glitch/util/initial_state';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const TIMELINE_UPDATE  = 'TIMELINE_UPDATE';
 | 
					export const TIMELINE_UPDATE  = 'TIMELINE_UPDATE';
 | 
				
			||||||
export const TIMELINE_DELETE  = 'TIMELINE_DELETE';
 | 
					export const TIMELINE_DELETE  = 'TIMELINE_DELETE';
 | 
				
			||||||
@ -10,10 +12,15 @@ export const TIMELINE_EXPAND_REQUEST = 'TIMELINE_EXPAND_REQUEST';
 | 
				
			|||||||
export const TIMELINE_EXPAND_SUCCESS = 'TIMELINE_EXPAND_SUCCESS';
 | 
					export const TIMELINE_EXPAND_SUCCESS = 'TIMELINE_EXPAND_SUCCESS';
 | 
				
			||||||
export const TIMELINE_EXPAND_FAIL    = 'TIMELINE_EXPAND_FAIL';
 | 
					export const TIMELINE_EXPAND_FAIL    = 'TIMELINE_EXPAND_FAIL';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const TIMELINE_SCROLL_TOP = 'TIMELINE_SCROLL_TOP';
 | 
					export const TIMELINE_SCROLL_TOP   = 'TIMELINE_SCROLL_TOP';
 | 
				
			||||||
 | 
					export const TIMELINE_LOAD_PENDING = 'TIMELINE_LOAD_PENDING';
 | 
				
			||||||
 | 
					export const TIMELINE_DISCONNECT   = 'TIMELINE_DISCONNECT';
 | 
				
			||||||
 | 
					export const TIMELINE_CONNECT      = 'TIMELINE_CONNECT';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const TIMELINE_CONNECT    = 'TIMELINE_CONNECT';
 | 
					export const loadPending = timeline => ({
 | 
				
			||||||
export const TIMELINE_DISCONNECT = 'TIMELINE_DISCONNECT';
 | 
					  type: TIMELINE_LOAD_PENDING,
 | 
				
			||||||
 | 
					  timeline,
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function updateTimeline(timeline, status, accept) {
 | 
					export function updateTimeline(timeline, status, accept) {
 | 
				
			||||||
  return dispatch => {
 | 
					  return dispatch => {
 | 
				
			||||||
@ -27,6 +34,7 @@ export function updateTimeline(timeline, status, accept) {
 | 
				
			|||||||
      type: TIMELINE_UPDATE,
 | 
					      type: TIMELINE_UPDATE,
 | 
				
			||||||
      timeline,
 | 
					      timeline,
 | 
				
			||||||
      status,
 | 
					      status,
 | 
				
			||||||
 | 
					      usePendingItems: preferPendingItems,
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
@ -71,8 +79,15 @@ export function expandTimeline(timelineId, path, params = {}, done = noOp) {
 | 
				
			|||||||
      return;
 | 
					      return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (!params.max_id && !params.pinned && timeline.get('items', ImmutableList()).size > 0) {
 | 
					    if (!params.max_id && !params.pinned && (timeline.get('items', ImmutableList()).size + timeline.get('pendingItems', ImmutableList()).size) > 0) {
 | 
				
			||||||
      params.since_id = timeline.getIn(['items', 0]);
 | 
					      const a = timeline.getIn(['pendingItems', 0]);
 | 
				
			||||||
 | 
					      const b = timeline.getIn(['items', 0]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (a && b && compareId(a, b) > 0) {
 | 
				
			||||||
 | 
					        params.since_id = a;
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        params.since_id = b || a;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const isLoadingRecent = !!params.since_id;
 | 
					    const isLoadingRecent = !!params.since_id;
 | 
				
			||||||
@ -82,7 +97,7 @@ export function expandTimeline(timelineId, path, params = {}, done = noOp) {
 | 
				
			|||||||
    api(getState).get(path, { params }).then(response => {
 | 
					    api(getState).get(path, { params }).then(response => {
 | 
				
			||||||
      const next = getLinks(response).refs.find(link => link.rel === 'next');
 | 
					      const next = getLinks(response).refs.find(link => link.rel === 'next');
 | 
				
			||||||
      dispatch(importFetchedStatuses(response.data));
 | 
					      dispatch(importFetchedStatuses(response.data));
 | 
				
			||||||
      dispatch(expandTimelineSuccess(timelineId, response.data, next ? next.uri : null, response.code === 206, isLoadingRecent, isLoadingMore));
 | 
					      dispatch(expandTimelineSuccess(timelineId, response.data, next ? next.uri : null, response.code === 206, isLoadingRecent, isLoadingMore, isLoadingRecent && preferPendingItems));
 | 
				
			||||||
      done();
 | 
					      done();
 | 
				
			||||||
    }).catch(error => {
 | 
					    }).catch(error => {
 | 
				
			||||||
      dispatch(expandTimelineFail(timelineId, error, isLoadingMore));
 | 
					      dispatch(expandTimelineFail(timelineId, error, isLoadingMore));
 | 
				
			||||||
@ -117,7 +132,7 @@ export function expandTimelineRequest(timeline, isLoadingMore) {
 | 
				
			|||||||
  };
 | 
					  };
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function expandTimelineSuccess(timeline, statuses, next, partial, isLoadingRecent, isLoadingMore) {
 | 
					export function expandTimelineSuccess(timeline, statuses, next, partial, isLoadingRecent, isLoadingMore, usePendingItems) {
 | 
				
			||||||
  return {
 | 
					  return {
 | 
				
			||||||
    type: TIMELINE_EXPAND_SUCCESS,
 | 
					    type: TIMELINE_EXPAND_SUCCESS,
 | 
				
			||||||
    timeline,
 | 
					    timeline,
 | 
				
			||||||
@ -125,6 +140,7 @@ export function expandTimelineSuccess(timeline, statuses, next, partial, isLoadi
 | 
				
			|||||||
    next,
 | 
					    next,
 | 
				
			||||||
    partial,
 | 
					    partial,
 | 
				
			||||||
    isLoadingRecent,
 | 
					    isLoadingRecent,
 | 
				
			||||||
 | 
					    usePendingItems,
 | 
				
			||||||
    skipLoading: !isLoadingMore,
 | 
					    skipLoading: !isLoadingMore,
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
@ -153,9 +169,8 @@ export function connectTimeline(timeline) {
 | 
				
			|||||||
  };
 | 
					  };
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function disconnectTimeline(timeline) {
 | 
					export const disconnectTimeline = timeline => ({
 | 
				
			||||||
  return {
 | 
					  type: TIMELINE_DISCONNECT,
 | 
				
			||||||
    type: TIMELINE_DISCONNECT,
 | 
					  timeline,
 | 
				
			||||||
    timeline,
 | 
					  usePendingItems: preferPendingItems,
 | 
				
			||||||
  };
 | 
					});
 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										22
									
								
								app/javascript/flavours/glitch/components/load_pending.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								app/javascript/flavours/glitch/components/load_pending.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,22 @@
 | 
				
			|||||||
 | 
					import React from 'react';
 | 
				
			||||||
 | 
					import { FormattedMessage } from 'react-intl';
 | 
				
			||||||
 | 
					import PropTypes from 'prop-types';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default class LoadPending extends React.PureComponent {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  static propTypes = {
 | 
				
			||||||
 | 
					    onClick: PropTypes.func,
 | 
				
			||||||
 | 
					    count: PropTypes.number,
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  render() {
 | 
				
			||||||
 | 
					    const { count } = this.props;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					      <button className='load-more load-gap' onClick={this.props.onClick}>
 | 
				
			||||||
 | 
					        <FormattedMessage id='load_pending' defaultMessage='{count, plural, one {# new item} other {# new items}}' values={{ count }} />
 | 
				
			||||||
 | 
					      </button>
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -3,6 +3,7 @@ import { ScrollContainer } from 'react-router-scroll-4';
 | 
				
			|||||||
import PropTypes from 'prop-types';
 | 
					import PropTypes from 'prop-types';
 | 
				
			||||||
import IntersectionObserverArticleContainer from 'flavours/glitch/containers/intersection_observer_article_container';
 | 
					import IntersectionObserverArticleContainer from 'flavours/glitch/containers/intersection_observer_article_container';
 | 
				
			||||||
import LoadMore from './load_more';
 | 
					import LoadMore from './load_more';
 | 
				
			||||||
 | 
					import LoadPending from './load_pending';
 | 
				
			||||||
import IntersectionObserverWrapper from 'flavours/glitch/util/intersection_observer_wrapper';
 | 
					import IntersectionObserverWrapper from 'flavours/glitch/util/intersection_observer_wrapper';
 | 
				
			||||||
import { throttle } from 'lodash';
 | 
					import { throttle } from 'lodash';
 | 
				
			||||||
import { List as ImmutableList } from 'immutable';
 | 
					import { List as ImmutableList } from 'immutable';
 | 
				
			||||||
@ -21,6 +22,7 @@ export default class ScrollableList extends PureComponent {
 | 
				
			|||||||
  static propTypes = {
 | 
					  static propTypes = {
 | 
				
			||||||
    scrollKey: PropTypes.string.isRequired,
 | 
					    scrollKey: PropTypes.string.isRequired,
 | 
				
			||||||
    onLoadMore: PropTypes.func,
 | 
					    onLoadMore: PropTypes.func,
 | 
				
			||||||
 | 
					    onLoadPending: PropTypes.func,
 | 
				
			||||||
    onScrollToTop: PropTypes.func,
 | 
					    onScrollToTop: PropTypes.func,
 | 
				
			||||||
    onScroll: PropTypes.func,
 | 
					    onScroll: PropTypes.func,
 | 
				
			||||||
    trackScroll: PropTypes.bool,
 | 
					    trackScroll: PropTypes.bool,
 | 
				
			||||||
@ -28,6 +30,7 @@ export default class ScrollableList extends PureComponent {
 | 
				
			|||||||
    isLoading: PropTypes.bool,
 | 
					    isLoading: PropTypes.bool,
 | 
				
			||||||
    showLoading: PropTypes.bool,
 | 
					    showLoading: PropTypes.bool,
 | 
				
			||||||
    hasMore: PropTypes.bool,
 | 
					    hasMore: PropTypes.bool,
 | 
				
			||||||
 | 
					    numPending: PropTypes.number,
 | 
				
			||||||
    prepend: PropTypes.node,
 | 
					    prepend: PropTypes.node,
 | 
				
			||||||
    alwaysPrepend: PropTypes.bool,
 | 
					    alwaysPrepend: PropTypes.bool,
 | 
				
			||||||
    emptyMessage: PropTypes.node,
 | 
					    emptyMessage: PropTypes.node,
 | 
				
			||||||
@ -222,12 +225,18 @@ export default class ScrollableList extends PureComponent {
 | 
				
			|||||||
    return !(location.state && location.state.mastodonModalOpen);
 | 
					    return !(location.state && location.state.mastodonModalOpen);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  handleLoadPending = e => {
 | 
				
			||||||
 | 
					    e.preventDefault();
 | 
				
			||||||
 | 
					    this.props.onLoadPending();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  render () {
 | 
					  render () {
 | 
				
			||||||
    const { children, scrollKey, trackScroll, shouldUpdateScroll, showLoading, isLoading, hasMore, prepend, alwaysPrepend, emptyMessage, onLoadMore } = this.props;
 | 
					    const { children, scrollKey, trackScroll, shouldUpdateScroll, showLoading, isLoading, hasMore, numPending, prepend, alwaysPrepend, emptyMessage, onLoadMore } = this.props;
 | 
				
			||||||
    const { fullscreen } = this.state;
 | 
					    const { fullscreen } = this.state;
 | 
				
			||||||
    const childrenCount = React.Children.count(children);
 | 
					    const childrenCount = React.Children.count(children);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const loadMore     = (hasMore && onLoadMore) ? <LoadMore visible={!isLoading} onClick={this.handleLoadMore} /> : null;
 | 
					    const loadMore     = (hasMore && onLoadMore) ? <LoadMore visible={!isLoading} onClick={this.handleLoadMore} /> : null;
 | 
				
			||||||
 | 
					    const loadPending  = (numPending > 0) ? <LoadPending count={numPending} onClick={this.handleLoadPending} /> : null;
 | 
				
			||||||
    let scrollableArea = null;
 | 
					    let scrollableArea = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (showLoading) {
 | 
					    if (showLoading) {
 | 
				
			||||||
@ -248,6 +257,8 @@ export default class ScrollableList extends PureComponent {
 | 
				
			|||||||
          <div role='feed' className='item-list'>
 | 
					          <div role='feed' className='item-list'>
 | 
				
			||||||
            {prepend}
 | 
					            {prepend}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            {loadPending}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            {React.Children.map(this.props.children, (child, index) => (
 | 
					            {React.Children.map(this.props.children, (child, index) => (
 | 
				
			||||||
              <IntersectionObserverArticleContainer
 | 
					              <IntersectionObserverArticleContainer
 | 
				
			||||||
                key={child.key}
 | 
					                key={child.key}
 | 
				
			||||||
 | 
				
			|||||||
@ -26,7 +26,7 @@ export default class ColumnSettings extends React.PureComponent {
 | 
				
			|||||||
    return (
 | 
					    return (
 | 
				
			||||||
      <div>
 | 
					      <div>
 | 
				
			||||||
        <div className='column-settings__row'>
 | 
					        <div className='column-settings__row'>
 | 
				
			||||||
          <SettingToggle settings={settings} settingPath={['other', 'onlyMedia']} onChange={onChange} label={<FormattedMessage id='community.column_settings.media_only' defaultMessage='Media Only' />} />
 | 
					          <SettingToggle settings={settings} settingPath={['other', 'onlyMedia']} onChange={onChange} label={<FormattedMessage id='community.column_settings.media_only' defaultMessage='Media only' />} />
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <span className='column-settings__section'><FormattedMessage id='home.column_settings.advanced' defaultMessage='Advanced' /></span>
 | 
					        <span className='column-settings__section'><FormattedMessage id='home.column_settings.advanced' defaultMessage='Advanced' /></span>
 | 
				
			||||||
 | 
				
			|||||||
@ -12,6 +12,7 @@ export default class SettingToggle extends React.PureComponent {
 | 
				
			|||||||
    label: PropTypes.node.isRequired,
 | 
					    label: PropTypes.node.isRequired,
 | 
				
			||||||
    meta: PropTypes.node,
 | 
					    meta: PropTypes.node,
 | 
				
			||||||
    onChange: PropTypes.func.isRequired,
 | 
					    onChange: PropTypes.func.isRequired,
 | 
				
			||||||
 | 
					    defaultValue: PropTypes.bool,
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  onChange = ({ target }) => {
 | 
					  onChange = ({ target }) => {
 | 
				
			||||||
@ -19,12 +20,12 @@ export default class SettingToggle extends React.PureComponent {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  render () {
 | 
					  render () {
 | 
				
			||||||
    const { prefix, settings, settingPath, label, meta } = this.props;
 | 
					    const { prefix, settings, settingPath, label, meta, defaultValue } = this.props;
 | 
				
			||||||
    const id = ['setting-toggle', prefix, ...settingPath].filter(Boolean).join('-');
 | 
					    const id = ['setting-toggle', prefix, ...settingPath].filter(Boolean).join('-');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
      <div className='setting-toggle'>
 | 
					      <div className='setting-toggle'>
 | 
				
			||||||
        <Toggle id={id} checked={settings.getIn(settingPath)} onChange={this.onChange} onKeyDown={this.onKeyDown} />
 | 
					        <Toggle id={id} checked={settings.getIn(settingPath, defaultValue)} onChange={this.onChange} onKeyDown={this.onKeyDown} />
 | 
				
			||||||
        <label htmlFor={id} className='setting-toggle__label'>{label}</label>
 | 
					        <label htmlFor={id} className='setting-toggle__label'>{label}</label>
 | 
				
			||||||
        {meta && <span className='setting-meta__label'>{meta}</span>}
 | 
					        {meta && <span className='setting-meta__label'>{meta}</span>}
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
 | 
				
			|||||||
@ -10,6 +10,7 @@ import {
 | 
				
			|||||||
  scrollTopNotifications,
 | 
					  scrollTopNotifications,
 | 
				
			||||||
  mountNotifications,
 | 
					  mountNotifications,
 | 
				
			||||||
  unmountNotifications,
 | 
					  unmountNotifications,
 | 
				
			||||||
 | 
					  loadPending,
 | 
				
			||||||
} from 'flavours/glitch/actions/notifications';
 | 
					} from 'flavours/glitch/actions/notifications';
 | 
				
			||||||
import { addColumn, removeColumn, moveColumn } from 'flavours/glitch/actions/columns';
 | 
					import { addColumn, removeColumn, moveColumn } from 'flavours/glitch/actions/columns';
 | 
				
			||||||
import NotificationContainer from './containers/notification_container';
 | 
					import NotificationContainer from './containers/notification_container';
 | 
				
			||||||
@ -48,6 +49,7 @@ const mapStateToProps = state => ({
 | 
				
			|||||||
  isLoading: state.getIn(['notifications', 'isLoading'], true),
 | 
					  isLoading: state.getIn(['notifications', 'isLoading'], true),
 | 
				
			||||||
  isUnread: state.getIn(['notifications', 'unread']) > 0,
 | 
					  isUnread: state.getIn(['notifications', 'unread']) > 0,
 | 
				
			||||||
  hasMore: state.getIn(['notifications', 'hasMore']),
 | 
					  hasMore: state.getIn(['notifications', 'hasMore']),
 | 
				
			||||||
 | 
					  numPending: state.getIn(['notifications', 'pendingItems'], ImmutableList()).size,
 | 
				
			||||||
  notifCleaningActive: state.getIn(['notifications', 'cleaningMode']),
 | 
					  notifCleaningActive: state.getIn(['notifications', 'cleaningMode']),
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -80,6 +82,7 @@ export default class Notifications extends React.PureComponent {
 | 
				
			|||||||
    isUnread: PropTypes.bool,
 | 
					    isUnread: PropTypes.bool,
 | 
				
			||||||
    multiColumn: PropTypes.bool,
 | 
					    multiColumn: PropTypes.bool,
 | 
				
			||||||
    hasMore: PropTypes.bool,
 | 
					    hasMore: PropTypes.bool,
 | 
				
			||||||
 | 
					    numPending: PropTypes.number,
 | 
				
			||||||
    localSettings: ImmutablePropTypes.map,
 | 
					    localSettings: ImmutablePropTypes.map,
 | 
				
			||||||
    notifCleaningActive: PropTypes.bool,
 | 
					    notifCleaningActive: PropTypes.bool,
 | 
				
			||||||
    onEnterCleaningMode: PropTypes.func,
 | 
					    onEnterCleaningMode: PropTypes.func,
 | 
				
			||||||
@ -100,6 +103,10 @@ export default class Notifications extends React.PureComponent {
 | 
				
			|||||||
    this.props.dispatch(expandNotifications({ maxId: last && last.get('id') }));
 | 
					    this.props.dispatch(expandNotifications({ maxId: last && last.get('id') }));
 | 
				
			||||||
  }, 300, { leading: true });
 | 
					  }, 300, { leading: true });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  handleLoadPending = () => {
 | 
				
			||||||
 | 
					    this.props.dispatch(loadPending());
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  handleScrollToTop = debounce(() => {
 | 
					  handleScrollToTop = debounce(() => {
 | 
				
			||||||
    this.props.dispatch(scrollTopNotifications(true));
 | 
					    this.props.dispatch(scrollTopNotifications(true));
 | 
				
			||||||
  }, 100);
 | 
					  }, 100);
 | 
				
			||||||
@ -170,7 +177,7 @@ export default class Notifications extends React.PureComponent {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  render () {
 | 
					  render () {
 | 
				
			||||||
    const { intl, notifications, shouldUpdateScroll, isLoading, isUnread, columnId, multiColumn, hasMore, showFilterBar } = this.props;
 | 
					    const { intl, notifications, shouldUpdateScroll, isLoading, isUnread, columnId, multiColumn, hasMore, numPending, showFilterBar } = this.props;
 | 
				
			||||||
    const pinned = !!columnId;
 | 
					    const pinned = !!columnId;
 | 
				
			||||||
    const emptyMessage = <FormattedMessage id='empty_column.notifications' defaultMessage="You don't have any notifications yet. Interact with others to start the conversation." />;
 | 
					    const emptyMessage = <FormattedMessage id='empty_column.notifications' defaultMessage="You don't have any notifications yet. Interact with others to start the conversation." />;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -212,8 +219,10 @@ export default class Notifications extends React.PureComponent {
 | 
				
			|||||||
        isLoading={isLoading}
 | 
					        isLoading={isLoading}
 | 
				
			||||||
        showLoading={isLoading && notifications.size === 0}
 | 
					        showLoading={isLoading && notifications.size === 0}
 | 
				
			||||||
        hasMore={hasMore}
 | 
					        hasMore={hasMore}
 | 
				
			||||||
 | 
					        numPending={numPending}
 | 
				
			||||||
        emptyMessage={emptyMessage}
 | 
					        emptyMessage={emptyMessage}
 | 
				
			||||||
        onLoadMore={this.handleLoadOlder}
 | 
					        onLoadMore={this.handleLoadOlder}
 | 
				
			||||||
 | 
					        onLoadPending={this.handleLoadPending}
 | 
				
			||||||
        onScrollToTop={this.handleScrollToTop}
 | 
					        onScrollToTop={this.handleScrollToTop}
 | 
				
			||||||
        onScroll={this.handleScroll}
 | 
					        onScroll={this.handleScroll}
 | 
				
			||||||
        shouldUpdateScroll={shouldUpdateScroll}
 | 
					        shouldUpdateScroll={shouldUpdateScroll}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,6 +1,6 @@
 | 
				
			|||||||
import { connect } from 'react-redux';
 | 
					import { connect } from 'react-redux';
 | 
				
			||||||
import StatusList from 'flavours/glitch/components/status_list';
 | 
					import StatusList from 'flavours/glitch/components/status_list';
 | 
				
			||||||
import { scrollTopTimeline } from 'flavours/glitch/actions/timelines';
 | 
					import { scrollTopTimeline, loadPending } from 'flavours/glitch/actions/timelines';
 | 
				
			||||||
import { Map as ImmutableMap, List as ImmutableList } from 'immutable';
 | 
					import { Map as ImmutableMap, List as ImmutableList } from 'immutable';
 | 
				
			||||||
import { createSelector } from 'reselect';
 | 
					import { createSelector } from 'reselect';
 | 
				
			||||||
import { debounce } from 'lodash';
 | 
					import { debounce } from 'lodash';
 | 
				
			||||||
@ -62,6 +62,7 @@ const makeMapStateToProps = () => {
 | 
				
			|||||||
    isLoading: state.getIn(['timelines', timelineId, 'isLoading'], true),
 | 
					    isLoading: state.getIn(['timelines', timelineId, 'isLoading'], true),
 | 
				
			||||||
    isPartial: state.getIn(['timelines', timelineId, 'isPartial'], false),
 | 
					    isPartial: state.getIn(['timelines', timelineId, 'isPartial'], false),
 | 
				
			||||||
    hasMore:   state.getIn(['timelines', timelineId, 'hasMore']),
 | 
					    hasMore:   state.getIn(['timelines', timelineId, 'hasMore']),
 | 
				
			||||||
 | 
					    numPending: state.getIn(['timelines', timelineId, 'pendingItems'], ImmutableList()).size,
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return mapStateToProps;
 | 
					  return mapStateToProps;
 | 
				
			||||||
@ -77,6 +78,8 @@ const mapDispatchToProps = (dispatch, { timelineId }) => ({
 | 
				
			|||||||
    dispatch(scrollTopTimeline(timelineId, false));
 | 
					    dispatch(scrollTopTimeline(timelineId, false));
 | 
				
			||||||
  }, 100),
 | 
					  }, 100),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  onLoadPending: () => dispatch(loadPending(timelineId)),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default connect(makeMapStateToProps, mapDispatchToProps)(StatusList);
 | 
					export default connect(makeMapStateToProps, mapDispatchToProps)(StatusList);
 | 
				
			||||||
 | 
				
			|||||||
@ -9,6 +9,7 @@ import {
 | 
				
			|||||||
  NOTIFICATIONS_FILTER_SET,
 | 
					  NOTIFICATIONS_FILTER_SET,
 | 
				
			||||||
  NOTIFICATIONS_CLEAR,
 | 
					  NOTIFICATIONS_CLEAR,
 | 
				
			||||||
  NOTIFICATIONS_SCROLL_TOP,
 | 
					  NOTIFICATIONS_SCROLL_TOP,
 | 
				
			||||||
 | 
					  NOTIFICATIONS_LOAD_PENDING,
 | 
				
			||||||
  NOTIFICATIONS_DELETE_MARKED_REQUEST,
 | 
					  NOTIFICATIONS_DELETE_MARKED_REQUEST,
 | 
				
			||||||
  NOTIFICATIONS_DELETE_MARKED_SUCCESS,
 | 
					  NOTIFICATIONS_DELETE_MARKED_SUCCESS,
 | 
				
			||||||
  NOTIFICATION_MARK_FOR_DELETE,
 | 
					  NOTIFICATION_MARK_FOR_DELETE,
 | 
				
			||||||
@ -25,6 +26,7 @@ import { Map as ImmutableMap, List as ImmutableList } from 'immutable';
 | 
				
			|||||||
import compareId from 'flavours/glitch/util/compare_id';
 | 
					import compareId from 'flavours/glitch/util/compare_id';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const initialState = ImmutableMap({
 | 
					const initialState = ImmutableMap({
 | 
				
			||||||
 | 
					  pendingItems: ImmutableList(),
 | 
				
			||||||
  items: ImmutableList(),
 | 
					  items: ImmutableList(),
 | 
				
			||||||
  hasMore: true,
 | 
					  hasMore: true,
 | 
				
			||||||
  top: false,
 | 
					  top: false,
 | 
				
			||||||
@ -46,7 +48,11 @@ const notificationToMap = (state, notification) => ImmutableMap({
 | 
				
			|||||||
  status: notification.status ? notification.status.id : null,
 | 
					  status: notification.status ? notification.status.id : null,
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const normalizeNotification = (state, notification) => {
 | 
					const normalizeNotification = (state, notification, usePendingItems) => {
 | 
				
			||||||
 | 
					  if (usePendingItems) {
 | 
				
			||||||
 | 
					    return state.update('pendingItems', list => list.unshift(notificationToMap(state, notification)));
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const top = !shouldCountUnreadNotifications(state);
 | 
					  const top = !shouldCountUnreadNotifications(state);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (top) {
 | 
					  if (top) {
 | 
				
			||||||
@ -64,7 +70,7 @@ const normalizeNotification = (state, notification) => {
 | 
				
			|||||||
  });
 | 
					  });
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const expandNormalizedNotifications = (state, notifications, next) => {
 | 
					const expandNormalizedNotifications = (state, notifications, next, usePendingItems) => {
 | 
				
			||||||
  const top = !(shouldCountUnreadNotifications(state));
 | 
					  const top = !(shouldCountUnreadNotifications(state));
 | 
				
			||||||
  const lastReadId = state.get('lastReadId');
 | 
					  const lastReadId = state.get('lastReadId');
 | 
				
			||||||
  let items = ImmutableList();
 | 
					  let items = ImmutableList();
 | 
				
			||||||
@ -75,7 +81,7 @@ const expandNormalizedNotifications = (state, notifications, next) => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  return state.withMutations(mutable => {
 | 
					  return state.withMutations(mutable => {
 | 
				
			||||||
    if (!items.isEmpty()) {
 | 
					    if (!items.isEmpty()) {
 | 
				
			||||||
      mutable.update('items', list => {
 | 
					      mutable.update(usePendingItems ? 'pendingItems' : 'items', list => {
 | 
				
			||||||
        const lastIndex = 1 + list.findLastIndex(
 | 
					        const lastIndex = 1 + list.findLastIndex(
 | 
				
			||||||
          item => item !== null && (compareId(item.get('id'), items.last().get('id')) > 0 || item.get('id') === items.last().get('id'))
 | 
					          item => item !== null && (compareId(item.get('id'), items.last().get('id')) > 0 || item.get('id') === items.last().get('id'))
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
@ -105,7 +111,8 @@ const expandNormalizedNotifications = (state, notifications, next) => {
 | 
				
			|||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const filterNotifications = (state, relationship) => {
 | 
					const filterNotifications = (state, relationship) => {
 | 
				
			||||||
  return state.update('items', list => list.filterNot(item => item !== null && item.get('account') === relationship.id));
 | 
					  const helper = list => list.filterNot(item => item !== null && item.get('account') === relationship.id);
 | 
				
			||||||
 | 
					  return state.update('items', helper).update('pendingItems', helper);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const clearUnread = (state) => {
 | 
					const clearUnread = (state) => {
 | 
				
			||||||
@ -131,7 +138,8 @@ const deleteByStatus = (state, statusId) => {
 | 
				
			|||||||
    const deletedUnread = state.get('items').filter(item => item !== null && item.get('status') === statusId && compareId(item.get('id'), lastReadId) > 0);
 | 
					    const deletedUnread = state.get('items').filter(item => item !== null && item.get('status') === statusId && compareId(item.get('id'), lastReadId) > 0);
 | 
				
			||||||
    state = state.update('unread', unread => unread - deletedUnread.size);
 | 
					    state = state.update('unread', unread => unread - deletedUnread.size);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  return state.update('items', list => list.filterNot(item => item !== null && item.get('status') === statusId));
 | 
					  const helper = list => list.filterNot(item => item !== null && item.get('status') === statusId);
 | 
				
			||||||
 | 
					  return state.update('items', helper).update('pendingItems', helper);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const markForDelete = (state, notificationId, yes) => {
 | 
					const markForDelete = (state, notificationId, yes) => {
 | 
				
			||||||
@ -192,6 +200,8 @@ export default function notifications(state = initialState, action) {
 | 
				
			|||||||
    return state.update('mounted', count => count - 1);
 | 
					    return state.update('mounted', count => count - 1);
 | 
				
			||||||
  case NOTIFICATIONS_SET_VISIBILITY:
 | 
					  case NOTIFICATIONS_SET_VISIBILITY:
 | 
				
			||||||
    return updateVisibility(state, action.visibility);
 | 
					    return updateVisibility(state, action.visibility);
 | 
				
			||||||
 | 
					  case NOTIFICATIONS_LOAD_PENDING:
 | 
				
			||||||
 | 
					    return state.update('items', list => state.get('pendingItems').concat(list.take(40))).set('pendingItems', ImmutableList()).set('unread', 0);
 | 
				
			||||||
  case NOTIFICATIONS_EXPAND_REQUEST:
 | 
					  case NOTIFICATIONS_EXPAND_REQUEST:
 | 
				
			||||||
  case NOTIFICATIONS_DELETE_MARKED_REQUEST:
 | 
					  case NOTIFICATIONS_DELETE_MARKED_REQUEST:
 | 
				
			||||||
    return state.set('isLoading', true);
 | 
					    return state.set('isLoading', true);
 | 
				
			||||||
@ -203,20 +213,20 @@ export default function notifications(state = initialState, action) {
 | 
				
			|||||||
  case NOTIFICATIONS_SCROLL_TOP:
 | 
					  case NOTIFICATIONS_SCROLL_TOP:
 | 
				
			||||||
    return updateTop(state, action.top);
 | 
					    return updateTop(state, action.top);
 | 
				
			||||||
  case NOTIFICATIONS_UPDATE:
 | 
					  case NOTIFICATIONS_UPDATE:
 | 
				
			||||||
    return normalizeNotification(state, action.notification);
 | 
					    return normalizeNotification(state, action.notification, action.usePendingItems);
 | 
				
			||||||
  case NOTIFICATIONS_EXPAND_SUCCESS:
 | 
					  case NOTIFICATIONS_EXPAND_SUCCESS:
 | 
				
			||||||
    return expandNormalizedNotifications(state, action.notifications, action.next);
 | 
					    return expandNormalizedNotifications(state, action.notifications, action.next, action.usePendingItems);
 | 
				
			||||||
  case ACCOUNT_BLOCK_SUCCESS:
 | 
					  case ACCOUNT_BLOCK_SUCCESS:
 | 
				
			||||||
    return filterNotifications(state, action.relationship);
 | 
					    return filterNotifications(state, action.relationship);
 | 
				
			||||||
  case ACCOUNT_MUTE_SUCCESS:
 | 
					  case ACCOUNT_MUTE_SUCCESS:
 | 
				
			||||||
    return action.relationship.muting_notifications ? filterNotifications(state, action.relationship) : state;
 | 
					    return action.relationship.muting_notifications ? filterNotifications(state, action.relationship) : state;
 | 
				
			||||||
  case NOTIFICATIONS_CLEAR:
 | 
					  case NOTIFICATIONS_CLEAR:
 | 
				
			||||||
    return state.set('items', ImmutableList()).set('hasMore', false);
 | 
					    return state.set('items', ImmutableList()).set('pendingItems', ImmutableList()).set('hasMore', false);
 | 
				
			||||||
  case TIMELINE_DELETE:
 | 
					  case TIMELINE_DELETE:
 | 
				
			||||||
    return deleteByStatus(state, action.id);
 | 
					    return deleteByStatus(state, action.id);
 | 
				
			||||||
  case TIMELINE_DISCONNECT:
 | 
					  case TIMELINE_DISCONNECT:
 | 
				
			||||||
    return action.timeline === 'home' ?
 | 
					    return action.timeline === 'home' ?
 | 
				
			||||||
      state.update('items', items => items.first() ? items.unshift(null) : items) :
 | 
					      state.update(action.usePendingItems ? 'pendingItems' : 'items', items => items.first() ? items.unshift(null) : items) :
 | 
				
			||||||
      state;
 | 
					      state;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  case NOTIFICATION_MARK_FOR_DELETE:
 | 
					  case NOTIFICATION_MARK_FOR_DELETE:
 | 
				
			||||||
 | 
				
			|||||||
@ -8,6 +8,7 @@ import {
 | 
				
			|||||||
  TIMELINE_SCROLL_TOP,
 | 
					  TIMELINE_SCROLL_TOP,
 | 
				
			||||||
  TIMELINE_CONNECT,
 | 
					  TIMELINE_CONNECT,
 | 
				
			||||||
  TIMELINE_DISCONNECT,
 | 
					  TIMELINE_DISCONNECT,
 | 
				
			||||||
 | 
					  TIMELINE_LOAD_PENDING,
 | 
				
			||||||
} from 'flavours/glitch/actions/timelines';
 | 
					} from 'flavours/glitch/actions/timelines';
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  ACCOUNT_BLOCK_SUCCESS,
 | 
					  ACCOUNT_BLOCK_SUCCESS,
 | 
				
			||||||
@ -25,10 +26,11 @@ const initialTimeline = ImmutableMap({
 | 
				
			|||||||
  top: true,
 | 
					  top: true,
 | 
				
			||||||
  isLoading: false,
 | 
					  isLoading: false,
 | 
				
			||||||
  hasMore: true,
 | 
					  hasMore: true,
 | 
				
			||||||
 | 
					  pendingItems: ImmutableList(),
 | 
				
			||||||
  items: ImmutableList(),
 | 
					  items: ImmutableList(),
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const expandNormalizedTimeline = (state, timeline, statuses, next, isPartial, isLoadingRecent) => {
 | 
					const expandNormalizedTimeline = (state, timeline, statuses, next, isPartial, isLoadingRecent, usePendingItems) => {
 | 
				
			||||||
  return state.update(timeline, initialTimeline, map => map.withMutations(mMap => {
 | 
					  return state.update(timeline, initialTimeline, map => map.withMutations(mMap => {
 | 
				
			||||||
    mMap.set('isLoading', false);
 | 
					    mMap.set('isLoading', false);
 | 
				
			||||||
    mMap.set('isPartial', isPartial);
 | 
					    mMap.set('isPartial', isPartial);
 | 
				
			||||||
@ -38,7 +40,7 @@ const expandNormalizedTimeline = (state, timeline, statuses, next, isPartial, is
 | 
				
			|||||||
    if (timeline.endsWith(':pinned')) {
 | 
					    if (timeline.endsWith(':pinned')) {
 | 
				
			||||||
      mMap.set('items', statuses.map(status => status.get('id')));
 | 
					      mMap.set('items', statuses.map(status => status.get('id')));
 | 
				
			||||||
    } else if (!statuses.isEmpty()) {
 | 
					    } else if (!statuses.isEmpty()) {
 | 
				
			||||||
      mMap.update('items', ImmutableList(), oldIds => {
 | 
					      mMap.update(usePendingItems ? 'pendingItems' : 'items', ImmutableList(), oldIds => {
 | 
				
			||||||
        const newIds = statuses.map(status => status.get('id'));
 | 
					        const newIds = statuses.map(status => status.get('id'));
 | 
				
			||||||
        const lastIndex = oldIds.findLastIndex(id => id !== null && compareId(id, newIds.last()) >= 0) + 1;
 | 
					        const lastIndex = oldIds.findLastIndex(id => id !== null && compareId(id, newIds.last()) >= 0) + 1;
 | 
				
			||||||
        const firstIndex = oldIds.take(lastIndex).findLastIndex(id => id !== null && compareId(id, newIds.first()) > 0);
 | 
					        const firstIndex = oldIds.take(lastIndex).findLastIndex(id => id !== null && compareId(id, newIds.first()) > 0);
 | 
				
			||||||
@ -56,7 +58,15 @@ const expandNormalizedTimeline = (state, timeline, statuses, next, isPartial, is
 | 
				
			|||||||
  }));
 | 
					  }));
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const updateTimeline = (state, timeline, status) => {
 | 
					const updateTimeline = (state, timeline, status, usePendingItems) => {
 | 
				
			||||||
 | 
					  if (usePendingItems) {
 | 
				
			||||||
 | 
					    if (state.getIn([timeline, 'pendingItems'], ImmutableList()).includes(status.get('id')) || state.getIn([timeline, 'items'], ImmutableList()).includes(status.get('id'))) {
 | 
				
			||||||
 | 
					      return state;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return state.update(timeline, initialTimeline, map => map.update('pendingItems', list => list.unshift(status.get('id'))));
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const top        = state.getIn([timeline, 'top']);
 | 
					  const top        = state.getIn([timeline, 'top']);
 | 
				
			||||||
  const ids        = state.getIn([timeline, 'items'], ImmutableList());
 | 
					  const ids        = state.getIn([timeline, 'items'], ImmutableList());
 | 
				
			||||||
  const includesId = ids.includes(status.get('id'));
 | 
					  const includesId = ids.includes(status.get('id'));
 | 
				
			||||||
@ -77,8 +87,10 @@ const updateTimeline = (state, timeline, status) => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
const deleteStatus = (state, id, accountId, references, exclude_account = null) => {
 | 
					const deleteStatus = (state, id, accountId, references, exclude_account = null) => {
 | 
				
			||||||
  state.keySeq().forEach(timeline => {
 | 
					  state.keySeq().forEach(timeline => {
 | 
				
			||||||
    if (exclude_account === null || (timeline !== `account:${exclude_account}` && !timeline.startsWith(`account:${exclude_account}:`)))
 | 
					    if (exclude_account === null || (timeline !== `account:${exclude_account}` && !timeline.startsWith(`account:${exclude_account}:`))) {
 | 
				
			||||||
      state = state.updateIn([timeline, 'items'], list => list.filterNot(item => item === id));
 | 
					      const helper = list => list.filterNot(item => item === id);
 | 
				
			||||||
 | 
					      state = state.updateIn([timeline, 'items'], helper).updateIn([timeline, 'pendingItems'], helper);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Remove reblogs of deleted status
 | 
					  // Remove reblogs of deleted status
 | 
				
			||||||
@ -108,11 +120,10 @@ const filterTimelines = (state, relationship, statuses) => {
 | 
				
			|||||||
  return state;
 | 
					  return state;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const filterTimeline = (timeline, state, relationship, statuses) =>
 | 
					const filterTimeline = (timeline, state, relationship, statuses) => {
 | 
				
			||||||
  state.updateIn([timeline, 'items'], ImmutableList(), list =>
 | 
					  const helper = list => list.filterNot(statusId => statuses.getIn([statusId, 'account']) === relationship.id);
 | 
				
			||||||
    list.filterNot(statusId =>
 | 
					  return state.updateIn([timeline, 'items'], ImmutableList(), helper).updateIn([timeline, 'pendingItems'], ImmutableList(), helper);
 | 
				
			||||||
      statuses.getIn([statusId, 'account']) === relationship.id
 | 
					};
 | 
				
			||||||
    ));
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const updateTop = (state, timeline, top) => {
 | 
					const updateTop = (state, timeline, top) => {
 | 
				
			||||||
  return state.update(timeline, initialTimeline, map => map.withMutations(mMap => {
 | 
					  return state.update(timeline, initialTimeline, map => map.withMutations(mMap => {
 | 
				
			||||||
@ -123,14 +134,17 @@ const updateTop = (state, timeline, top) => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
export default function timelines(state = initialState, action) {
 | 
					export default function timelines(state = initialState, action) {
 | 
				
			||||||
  switch(action.type) {
 | 
					  switch(action.type) {
 | 
				
			||||||
 | 
					  case TIMELINE_LOAD_PENDING:
 | 
				
			||||||
 | 
					    return state.update(action.timeline, initialTimeline, map =>
 | 
				
			||||||
 | 
					      map.update('items', list => map.get('pendingItems').concat(list.take(40))).set('pendingItems', ImmutableList()).set('unread', 0));
 | 
				
			||||||
  case TIMELINE_EXPAND_REQUEST:
 | 
					  case TIMELINE_EXPAND_REQUEST:
 | 
				
			||||||
    return state.update(action.timeline, initialTimeline, map => map.set('isLoading', true));
 | 
					    return state.update(action.timeline, initialTimeline, map => map.set('isLoading', true));
 | 
				
			||||||
  case TIMELINE_EXPAND_FAIL:
 | 
					  case TIMELINE_EXPAND_FAIL:
 | 
				
			||||||
    return state.update(action.timeline, initialTimeline, map => map.set('isLoading', false));
 | 
					    return state.update(action.timeline, initialTimeline, map => map.set('isLoading', false));
 | 
				
			||||||
  case TIMELINE_EXPAND_SUCCESS:
 | 
					  case TIMELINE_EXPAND_SUCCESS:
 | 
				
			||||||
    return expandNormalizedTimeline(state, action.timeline, fromJS(action.statuses), action.next, action.partial, action.isLoadingRecent);
 | 
					    return expandNormalizedTimeline(state, action.timeline, fromJS(action.statuses), action.next, action.partial, action.isLoadingRecent, action.usePendingItems);
 | 
				
			||||||
  case TIMELINE_UPDATE:
 | 
					  case TIMELINE_UPDATE:
 | 
				
			||||||
    return updateTimeline(state, action.timeline, fromJS(action.status));
 | 
					    return updateTimeline(state, action.timeline, fromJS(action.status), action.usePendingItems);
 | 
				
			||||||
  case TIMELINE_DELETE:
 | 
					  case TIMELINE_DELETE:
 | 
				
			||||||
    return deleteStatus(state, action.id, action.accountId, action.references, action.reblogOf);
 | 
					    return deleteStatus(state, action.id, action.accountId, action.references, action.reblogOf);
 | 
				
			||||||
  case TIMELINE_CLEAR:
 | 
					  case TIMELINE_CLEAR:
 | 
				
			||||||
@ -148,7 +162,7 @@ export default function timelines(state = initialState, action) {
 | 
				
			|||||||
    return state.update(
 | 
					    return state.update(
 | 
				
			||||||
      action.timeline,
 | 
					      action.timeline,
 | 
				
			||||||
      initialTimeline,
 | 
					      initialTimeline,
 | 
				
			||||||
      map => map.set('online', false).update('items', items => items.first() ? items.unshift(null) : items)
 | 
					      map => map.set('online', false).update(action.usePendingItems ? 'pendingItems' : 'items', items => items.first() ? items.unshift(null) : items)
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
  default:
 | 
					  default:
 | 
				
			||||||
    return state;
 | 
					    return state;
 | 
				
			||||||
 | 
				
			|||||||
@ -1,10 +1,11 @@
 | 
				
			|||||||
export default function compareId(id1, id2) {
 | 
					export default function compareId (id1, id2) {
 | 
				
			||||||
  if (id1 === id2) {
 | 
					  if (id1 === id2) {
 | 
				
			||||||
    return 0;
 | 
					    return 0;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (id1.length === id2.length) {
 | 
					  if (id1.length === id2.length) {
 | 
				
			||||||
    return id1 > id2 ? 1 : -1;
 | 
					    return id1 > id2 ? 1 : -1;
 | 
				
			||||||
  } else {
 | 
					  } else {
 | 
				
			||||||
    return id1.length > id2.length ? 1 : -1;
 | 
					    return id1.length > id2.length ? 1 : -1;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
@ -30,5 +30,6 @@ export const isStaff = getMeta('is_staff');
 | 
				
			|||||||
export const defaultContentType = getMeta('default_content_type');
 | 
					export const defaultContentType = getMeta('default_content_type');
 | 
				
			||||||
export const forceSingleColumn = getMeta('advanced_layout') === false;
 | 
					export const forceSingleColumn = getMeta('advanced_layout') === false;
 | 
				
			||||||
export const useBlurhash = getMeta('use_blurhash');
 | 
					export const useBlurhash = getMeta('use_blurhash');
 | 
				
			||||||
 | 
					export const usePendingItems = getMeta('use_pending_items');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default initialState;
 | 
					export default initialState;
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user