2020-11-25 21:22:37 +00:00
|
|
|
import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable'
|
|
|
|
import compareId from '../utils/compare_id'
|
2019-07-02 08:10:25 +01:00
|
|
|
import {
|
|
|
|
TIMELINE_UPDATE,
|
|
|
|
TIMELINE_DELETE,
|
|
|
|
TIMELINE_CLEAR,
|
|
|
|
TIMELINE_EXPAND_SUCCESS,
|
|
|
|
TIMELINE_EXPAND_REQUEST,
|
|
|
|
TIMELINE_EXPAND_FAIL,
|
|
|
|
TIMELINE_CONNECT,
|
|
|
|
TIMELINE_DISCONNECT,
|
Added redux functionality for queueing/dequeueing timelines
using streaming.js, when a status comes in to the current page, it queues up using updateTimelineQueue action, it then goes to the reducer to add "queuedItems" to state (up to max:40) and to tally up all count in that timeilne state "totalQueuedItemsCount".
the dequeueTimeline action takes in a "timelineId", "expandFunc", and "optionalExpandArgs". when clicking on the "click to load more" it passes in the timelineId (e.g. "home", "community", etc.) and the "handleLoadMore" function from the timeline component. if within the range of the max: 40, it pushes them to the dom, if over the max: 40 it clears the timeline and refreshes the page/timeline to show the most recent 20 statuses. Then, it resets the "queuedItems" and "totalQueuedItemsCount" in timeline state.
if no expandFunc is added, and timeline is "home" or "community" it expands those timelines with "optionalExpandArgs". Otherwise, it queues up to any other timeline (e.g. "hashtags", etc.)
2019-07-11 17:09:41 +01:00
|
|
|
TIMELINE_UPDATE_QUEUE,
|
|
|
|
TIMELINE_DEQUEUE,
|
|
|
|
MAX_QUEUED_ITEMS,
|
2019-07-17 23:59:50 +01:00
|
|
|
TIMELINE_SCROLL_TOP,
|
2020-11-25 21:22:37 +00:00
|
|
|
} from '../actions/timelines'
|
2019-07-02 08:10:25 +01:00
|
|
|
import {
|
|
|
|
ACCOUNT_BLOCK_SUCCESS,
|
|
|
|
ACCOUNT_MUTE_SUCCESS,
|
|
|
|
ACCOUNT_UNFOLLOW_SUCCESS,
|
2020-11-25 21:22:37 +00:00
|
|
|
} from '../actions/accounts'
|
|
|
|
import {
|
|
|
|
GROUP_REMOVE_STATUS_SUCCESS,
|
|
|
|
GROUP_UNPIN_STATUS_SUCCESS,
|
|
|
|
} from '../actions/groups'
|
|
|
|
import { UNPIN_SUCCESS } from '../actions/interactions'
|
2019-07-02 08:10:25 +01:00
|
|
|
|
2020-11-25 21:22:37 +00:00
|
|
|
const initialState = ImmutableMap()
|
2019-07-02 08:10:25 +01:00
|
|
|
|
|
|
|
const initialTimeline = ImmutableMap({
|
|
|
|
unread: 0,
|
|
|
|
online: false,
|
|
|
|
top: true,
|
|
|
|
isLoading: false,
|
2020-11-15 18:48:32 +00:00
|
|
|
isError: false,
|
2019-07-02 08:10:25 +01:00
|
|
|
hasMore: true,
|
|
|
|
items: ImmutableList(),
|
Added redux functionality for queueing/dequeueing timelines
using streaming.js, when a status comes in to the current page, it queues up using updateTimelineQueue action, it then goes to the reducer to add "queuedItems" to state (up to max:40) and to tally up all count in that timeilne state "totalQueuedItemsCount".
the dequeueTimeline action takes in a "timelineId", "expandFunc", and "optionalExpandArgs". when clicking on the "click to load more" it passes in the timelineId (e.g. "home", "community", etc.) and the "handleLoadMore" function from the timeline component. if within the range of the max: 40, it pushes them to the dom, if over the max: 40 it clears the timeline and refreshes the page/timeline to show the most recent 20 statuses. Then, it resets the "queuedItems" and "totalQueuedItemsCount" in timeline state.
if no expandFunc is added, and timeline is "home" or "community" it expands those timelines with "optionalExpandArgs". Otherwise, it queues up to any other timeline (e.g. "hashtags", etc.)
2019-07-11 17:09:41 +01:00
|
|
|
queuedItems: ImmutableList(), //max= MAX_QUEUED_ITEMS
|
|
|
|
totalQueuedItemsCount: 0, //used for queuedItems overflow for MAX_QUEUED_ITEMS+
|
2020-11-25 21:22:37 +00:00
|
|
|
})
|
2019-07-02 08:10:25 +01:00
|
|
|
|
|
|
|
const expandNormalizedTimeline = (state, timeline, statuses, next, isPartial, isLoadingRecent) => {
|
2020-11-25 21:22:37 +00:00
|
|
|
return state.update(timeline, initialTimeline, map => map.withMutations((mMap) => {
|
|
|
|
mMap.set('isLoading', false)
|
|
|
|
mMap.set('isPartial', isPartial)
|
2019-07-02 08:10:25 +01:00
|
|
|
|
2020-11-25 21:22:37 +00:00
|
|
|
if (!next && !isLoadingRecent) mMap.set('hasMore', false)
|
2019-07-02 08:10:25 +01:00
|
|
|
|
|
|
|
if (!statuses.isEmpty()) {
|
|
|
|
mMap.update('items', ImmutableList(), oldIds => {
|
|
|
|
const newIds = statuses.map(status => status.get('id'));
|
|
|
|
|
|
|
|
if (timeline.indexOf(':pinned') !== -1) {
|
|
|
|
return newIds;
|
|
|
|
}
|
|
|
|
|
2020-11-15 18:48:32 +00:00
|
|
|
// const realtime = ['home']
|
|
|
|
// if no realtime, do all that, otherwise just concat
|
|
|
|
// return oldIds.concat(newIds);
|
|
|
|
|
2019-07-02 08:10:25 +01:00
|
|
|
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);
|
|
|
|
|
|
|
|
if (firstIndex < 0) {
|
|
|
|
return (isPartial ? newIds.unshift(null) : newIds).concat(oldIds.skip(lastIndex));
|
|
|
|
}
|
|
|
|
|
|
|
|
return oldIds.take(firstIndex + 1).concat(
|
|
|
|
isPartial && oldIds.get(firstIndex) !== null ? newIds.unshift(null) : newIds,
|
|
|
|
oldIds.skip(lastIndex)
|
|
|
|
);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}));
|
|
|
|
};
|
|
|
|
|
|
|
|
const updateTimeline = (state, timeline, status) => {
|
|
|
|
const top = state.getIn([timeline, 'top']);
|
|
|
|
const ids = state.getIn([timeline, 'items'], ImmutableList());
|
|
|
|
const includesId = ids.includes(status.get('id'));
|
|
|
|
const unread = state.getIn([timeline, 'unread'], 0);
|
|
|
|
|
|
|
|
if (includesId) {
|
|
|
|
return state;
|
|
|
|
}
|
|
|
|
|
|
|
|
let newIds = ids;
|
|
|
|
|
|
|
|
return state.update(timeline, initialTimeline, map => map.withMutations(mMap => {
|
|
|
|
if (!top) mMap.set('unread', unread + 1);
|
|
|
|
if (top && ids.size > 40) newIds = newIds.take(20);
|
|
|
|
mMap.set('items', newIds.unshift(status.get('id')));
|
|
|
|
}));
|
|
|
|
};
|
|
|
|
|
Added redux functionality for queueing/dequeueing timelines
using streaming.js, when a status comes in to the current page, it queues up using updateTimelineQueue action, it then goes to the reducer to add "queuedItems" to state (up to max:40) and to tally up all count in that timeilne state "totalQueuedItemsCount".
the dequeueTimeline action takes in a "timelineId", "expandFunc", and "optionalExpandArgs". when clicking on the "click to load more" it passes in the timelineId (e.g. "home", "community", etc.) and the "handleLoadMore" function from the timeline component. if within the range of the max: 40, it pushes them to the dom, if over the max: 40 it clears the timeline and refreshes the page/timeline to show the most recent 20 statuses. Then, it resets the "queuedItems" and "totalQueuedItemsCount" in timeline state.
if no expandFunc is added, and timeline is "home" or "community" it expands those timelines with "optionalExpandArgs". Otherwise, it queues up to any other timeline (e.g. "hashtags", etc.)
2019-07-11 17:09:41 +01:00
|
|
|
const updateTimelineQueue = (state, timeline, status) => {
|
|
|
|
const queuedStatuses = state.getIn([timeline, 'queuedItems'], ImmutableList());
|
|
|
|
const listedStatuses = state.getIn([timeline, 'items'], ImmutableList());
|
|
|
|
const totalQueuedItemsCount = state.getIn([timeline, 'totalQueuedItemsCount'], 0);
|
|
|
|
|
|
|
|
let alreadyExists = queuedStatuses.find(existingQueuedStatus => existingQueuedStatus.get('id') === status.get('id'));
|
|
|
|
if (!alreadyExists) alreadyExists = listedStatuses.find(existingListedStatusId => existingListedStatusId === status.get('id'));
|
|
|
|
|
2020-05-04 19:44:37 +01:00
|
|
|
const isReply = !!status.get('in_reply_to_id')
|
|
|
|
if (alreadyExists || isReply) {
|
Added redux functionality for queueing/dequeueing timelines
using streaming.js, when a status comes in to the current page, it queues up using updateTimelineQueue action, it then goes to the reducer to add "queuedItems" to state (up to max:40) and to tally up all count in that timeilne state "totalQueuedItemsCount".
the dequeueTimeline action takes in a "timelineId", "expandFunc", and "optionalExpandArgs". when clicking on the "click to load more" it passes in the timelineId (e.g. "home", "community", etc.) and the "handleLoadMore" function from the timeline component. if within the range of the max: 40, it pushes them to the dom, if over the max: 40 it clears the timeline and refreshes the page/timeline to show the most recent 20 statuses. Then, it resets the "queuedItems" and "totalQueuedItemsCount" in timeline state.
if no expandFunc is added, and timeline is "home" or "community" it expands those timelines with "optionalExpandArgs". Otherwise, it queues up to any other timeline (e.g. "hashtags", etc.)
2019-07-11 17:09:41 +01:00
|
|
|
return state;
|
|
|
|
}
|
|
|
|
|
|
|
|
let newQueuedStatuses = queuedStatuses;
|
|
|
|
|
|
|
|
return state.update(timeline, initialTimeline, map => map.withMutations(mMap => {
|
|
|
|
if (totalQueuedItemsCount <= MAX_QUEUED_ITEMS) {
|
|
|
|
mMap.set('queuedItems', newQueuedStatuses.push(status));
|
|
|
|
}
|
|
|
|
mMap.set('totalQueuedItemsCount', totalQueuedItemsCount + 1);
|
|
|
|
}));
|
|
|
|
};
|
|
|
|
|
2019-07-02 08:10:25 +01:00
|
|
|
const deleteStatus = (state, id, accountId, references, exclude_account = null) => {
|
|
|
|
state.keySeq().forEach(timeline => {
|
2020-12-31 23:55:00 +00:00
|
|
|
if (exclude_account === null || (!!timeline && timeline !== `account:${exclude_account}` && !timeline.startsWith(`account:${exclude_account}:`)))
|
2019-07-02 08:10:25 +01:00
|
|
|
state = state.updateIn([timeline, 'items'], list => list.filterNot(item => item === id));
|
|
|
|
});
|
|
|
|
|
|
|
|
// Remove reblogs of deleted status
|
|
|
|
references.forEach(ref => {
|
|
|
|
state = deleteStatus(state, ref[0], ref[1], [], exclude_account);
|
|
|
|
});
|
|
|
|
|
|
|
|
return state;
|
|
|
|
};
|
|
|
|
|
|
|
|
const clearTimeline = (state, timeline) => {
|
|
|
|
return state.set(timeline, initialTimeline);
|
|
|
|
};
|
|
|
|
|
|
|
|
const filterTimelines = (state, relationship, statuses) => {
|
|
|
|
let references;
|
|
|
|
|
|
|
|
statuses.forEach(status => {
|
|
|
|
if (status.get('account') !== relationship.id) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
references = statuses.filter(item => item.get('reblog') === status.get('id')).map(item => [item.get('id'), item.get('account')]);
|
|
|
|
state = deleteStatus(state, status.get('id'), status.get('account'), references, relationship.id);
|
|
|
|
});
|
|
|
|
|
|
|
|
return state;
|
|
|
|
};
|
|
|
|
|
2019-07-17 23:59:50 +01:00
|
|
|
const updateTop = (state, timeline, top) => {
|
|
|
|
return state.update(timeline, initialTimeline, map => map.withMutations(mMap => {
|
|
|
|
if (top) mMap.set('unread', 0);
|
|
|
|
mMap.set('top', top);
|
|
|
|
}));
|
|
|
|
};
|
|
|
|
|
2019-07-02 08:10:25 +01:00
|
|
|
const filterTimeline = (timeline, state, relationship, statuses) =>
|
|
|
|
state.updateIn([timeline, 'items'], ImmutableList(), list =>
|
|
|
|
list.filterNot(statusId =>
|
|
|
|
statuses.getIn([statusId, 'account']) === relationship.id
|
|
|
|
));
|
|
|
|
|
2019-07-16 23:42:26 +01:00
|
|
|
const removeStatusFromGroup = (state, groupId, statusId) => {
|
2020-11-25 21:22:37 +00:00
|
|
|
return state.updateIn([`group:${groupId}`, 'items'], list => list.filterNot(item => item === statusId))
|
|
|
|
}
|
|
|
|
|
|
|
|
const removeStatusFromGroupPins = (state, groupId, statusId) => {
|
|
|
|
return state.updateIn([`group:${groupId}:pinned`, 'items'], list => list.filterNot(item => item === statusId))
|
|
|
|
}
|
|
|
|
|
|
|
|
const removeStatusFromAccountPins = (state, accountId, statusId) => {
|
|
|
|
return state.updateIn([`account:${accountId}:pinned`, 'items'], list => list.filterNot(item => item === statusId))
|
|
|
|
}
|
2019-07-16 23:42:26 +01:00
|
|
|
|
2019-07-02 08:10:25 +01:00
|
|
|
export default function timelines(state = initialState, action) {
|
|
|
|
switch(action.type) {
|
|
|
|
case TIMELINE_EXPAND_REQUEST:
|
|
|
|
return state.update(action.timeline, initialTimeline, map => map.set('isLoading', true));
|
|
|
|
case TIMELINE_EXPAND_FAIL:
|
2020-11-15 18:48:32 +00:00
|
|
|
return state.update(action.timeline, initialTimeline, map => map.withMutations(mMap => {
|
2021-01-14 03:46:40 +00:00
|
|
|
mMap.set('isLoading', false)
|
|
|
|
mMap.set('isError', true)
|
2020-11-15 18:48:32 +00:00
|
|
|
}));
|
2019-07-02 08:10:25 +01:00
|
|
|
case TIMELINE_EXPAND_SUCCESS:
|
|
|
|
return expandNormalizedTimeline(state, action.timeline, fromJS(action.statuses), action.next, action.partial, action.isLoadingRecent);
|
|
|
|
case TIMELINE_UPDATE:
|
|
|
|
return updateTimeline(state, action.timeline, fromJS(action.status));
|
Added redux functionality for queueing/dequeueing timelines
using streaming.js, when a status comes in to the current page, it queues up using updateTimelineQueue action, it then goes to the reducer to add "queuedItems" to state (up to max:40) and to tally up all count in that timeilne state "totalQueuedItemsCount".
the dequeueTimeline action takes in a "timelineId", "expandFunc", and "optionalExpandArgs". when clicking on the "click to load more" it passes in the timelineId (e.g. "home", "community", etc.) and the "handleLoadMore" function from the timeline component. if within the range of the max: 40, it pushes them to the dom, if over the max: 40 it clears the timeline and refreshes the page/timeline to show the most recent 20 statuses. Then, it resets the "queuedItems" and "totalQueuedItemsCount" in timeline state.
if no expandFunc is added, and timeline is "home" or "community" it expands those timelines with "optionalExpandArgs". Otherwise, it queues up to any other timeline (e.g. "hashtags", etc.)
2019-07-11 17:09:41 +01:00
|
|
|
case TIMELINE_UPDATE_QUEUE:
|
|
|
|
return updateTimelineQueue(state, action.timeline, fromJS(action.status));
|
|
|
|
case TIMELINE_DEQUEUE:
|
|
|
|
return state.update(action.timeline, initialTimeline, map => map.withMutations(mMap => {
|
|
|
|
mMap.set('queuedItems', ImmutableList())
|
|
|
|
mMap.set('totalQueuedItemsCount', 0)
|
|
|
|
}));
|
2019-07-02 08:10:25 +01:00
|
|
|
case TIMELINE_DELETE:
|
|
|
|
return deleteStatus(state, action.id, action.accountId, action.references, action.reblogOf);
|
|
|
|
case TIMELINE_CLEAR:
|
|
|
|
return clearTimeline(state, action.timeline);
|
|
|
|
case ACCOUNT_BLOCK_SUCCESS:
|
|
|
|
case ACCOUNT_MUTE_SUCCESS:
|
|
|
|
return filterTimelines(state, action.relationship, action.statuses);
|
|
|
|
case ACCOUNT_UNFOLLOW_SUCCESS:
|
|
|
|
return filterTimeline('home', state, action.relationship, action.statuses);
|
|
|
|
case TIMELINE_CONNECT:
|
|
|
|
return state.update(action.timeline, initialTimeline, map => map.set('online', true));
|
2019-07-17 23:59:50 +01:00
|
|
|
case TIMELINE_SCROLL_TOP:
|
|
|
|
return updateTop(state, action.timeline, action.top);
|
2019-07-02 08:10:25 +01:00
|
|
|
case TIMELINE_DISCONNECT:
|
|
|
|
return state.update(
|
|
|
|
action.timeline,
|
|
|
|
initialTimeline,
|
|
|
|
map => map.set('online', false).update('items', items => items.first() ? items.unshift(null) : items)
|
2020-11-25 21:22:37 +00:00
|
|
|
)
|
|
|
|
case UNPIN_SUCCESS:
|
|
|
|
return removeStatusFromAccountPins(state, action.accountId, action.status.get('id'))
|
2019-07-16 23:42:26 +01:00
|
|
|
case GROUP_REMOVE_STATUS_SUCCESS:
|
2020-11-25 21:22:37 +00:00
|
|
|
return removeStatusFromGroup(state, action.groupId, action.statusId)
|
|
|
|
case GROUP_UNPIN_STATUS_SUCCESS:
|
|
|
|
return removeStatusFromGroupPins(state, action.groupId, action.statusId)
|
2019-07-02 08:10:25 +01:00
|
|
|
default:
|
2020-11-25 21:22:37 +00:00
|
|
|
return state
|
2019-07-02 08:10:25 +01:00
|
|
|
}
|
2020-11-25 21:22:37 +00:00
|
|
|
}
|