mattermost-redux
Version:
Common code (API client, Redux stores, logic, utility functions) for building a Mattermost client
252 lines (251 loc) • 9.2 kB
JavaScript
"use strict";
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
Object.defineProperty(exports, "__esModule", { value: true });
exports.unreadThreadsInTeamReducer = exports.threadsInTeamReducer = void 0;
exports.handleReceivedThread = handleReceivedThread;
exports.handleFollowChanged = handleFollowChanged;
const action_types_1 = require("mattermost-redux/action_types");
// return true only if it's 'newer' than other threads
// older threads will be added by scrolling so no need to manually add.
// furthermore manually adding older thread will BREAK pagination
function shouldAddThreadId(ids, thread, threads) {
return ids.some((id) => {
const t = threads[id];
return thread.last_reply_at > t.last_reply_at;
});
}
function handlePostRemoved(state, action) {
const post = action.data;
if (post.root_id) {
return state;
}
const teams = Object.keys(state).
filter((id) => state[id].indexOf(post.id) !== -1);
if (!teams?.length) {
return state;
}
const teamState = {};
for (let i = 0; i < teams.length; i++) {
const teamId = teams[i];
const index = state[teamId].indexOf(post.id);
teamState[teamId] = [
...state[teamId].slice(0, index),
...state[teamId].slice(index + 1),
];
}
return {
...state,
...teamState,
};
}
// adds thread to all teams in state
function handleAllTeamsReceivedThread(state, thread, teamId, extra) {
const teamIds = Object.keys(state);
let newState = { ...state };
for (const teamId of teamIds) {
newState = handleSingleTeamReceivedThread(newState, thread, teamId, extra);
}
return newState;
}
// adds thread to single team
function handleSingleTeamReceivedThread(state, thread, teamId, extra) {
const nextSet = new Set(state[teamId] || []);
// thread exists in state
if (nextSet.has(thread.id)) {
return state;
}
// check if thread is newer than any of the existing threads
const shouldAdd = shouldAddThreadId([...nextSet], thread, extra.threads);
if (shouldAdd) {
nextSet.add(thread.id);
return {
...state,
[teamId]: [...nextSet],
};
}
return state;
}
function handleReceivedThread(state, action, extra) {
const { thread, team_id: teamId } = action.data;
if (!teamId) {
return handleAllTeamsReceivedThread(state, thread, teamId, extra);
}
return handleSingleTeamReceivedThread(state, thread, teamId, extra);
}
// add the thread only if it's 'newer' than other threads
// older threads will be added by scrolling so no need to manually add.
// furthermore manually adding older thread will BREAK pagination
function handleFollowChanged(state, action, extra) {
const { id, team_id: teamId, following } = action.data;
const nextSet = new Set(state[teamId] || []);
const thread = extra.threads[id];
if (!thread) {
return state;
}
// thread exists in state
if (nextSet.has(id)) {
// remove it if we unfollowed
if (!following) {
nextSet.delete(id);
return {
...state,
[teamId]: [...nextSet],
};
}
return state;
}
// check if thread is newer than any of the existing threads
const shouldAdd = shouldAddThreadId([...nextSet], thread, extra.threads);
if (shouldAdd && following) {
nextSet.add(thread.id);
return {
...state,
[teamId]: [...nextSet],
};
}
return state;
}
function handleReceiveThreads(state, action) {
const nextSet = new Set(state[action.data.team_id] || []);
action.data.threads.forEach((thread) => {
nextSet.add(thread.id);
});
return {
...state,
[action.data.team_id]: [...nextSet],
};
}
function handleLeaveChannel(state, action, extra) {
if (!extra.threadsToDelete || extra.threadsToDelete.length === 0) {
return state;
}
const teamId = action.data.team_id;
let threadDeleted = false;
// Remove entries for any thread in the channel
const nextState = { ...state };
for (const thread of extra.threadsToDelete) {
if (nextState[teamId]) {
const index = nextState[teamId].indexOf(thread.id);
if (index !== -1) {
nextState[teamId] = [...nextState[teamId].slice(0, index), ...nextState[teamId].slice(index + 1)];
threadDeleted = true;
}
}
}
if (!threadDeleted) {
// Nothing was actually removed
return state;
}
return nextState;
}
function handleLeaveTeam(state, action) {
const team = action.data;
if (!state[team.id]) {
return state;
}
const nextState = { ...state };
Reflect.deleteProperty(nextState, team.id);
return nextState;
}
function handleSingleTeamThreadRead(state, action, teamId, extra) {
const { id, newUnreadMentions, newUnreadReplies, } = action.data;
const team = state[teamId] || [];
const index = team.indexOf(id);
// the thread is not in the unread list
if (index === -1) {
const thread = extra.threads[id];
// the thread is unread
if (thread && (newUnreadReplies > 0 || newUnreadMentions > 0)) {
// if it's newer add it, we don't care about ordering here since we order on the selector
if (shouldAddThreadId(team, thread, extra.threads)) {
return {
...state,
[teamId]: [
...team,
id,
],
};
}
}
// do nothing when the thread is read
return state;
}
// do nothing when the thread exists and it's unread
if (newUnreadReplies > 0 || newUnreadMentions > 0) {
return state;
}
// if the thread is read remove it
return {
...state,
[teamId]: [
...team.slice(0, index),
...team.slice(index + 1),
],
};
}
const threadsInTeamReducer = (state = {}, action, extra) => {
switch (action.type) {
case action_types_1.ThreadTypes.RECEIVED_THREAD:
return handleReceivedThread(state, action, extra);
case action_types_1.PostTypes.POST_DELETED:
case action_types_1.PostTypes.POST_REMOVED:
return handlePostRemoved(state, action);
case action_types_1.ThreadTypes.RECEIVED_THREADS:
return handleReceiveThreads(state, action);
case action_types_1.TeamTypes.LEAVE_TEAM:
return handleLeaveTeam(state, action);
case action_types_1.UserTypes.LOGOUT_SUCCESS:
return {};
case action_types_1.ChannelTypes.RECEIVED_CHANNEL_DELETED:
case action_types_1.ChannelTypes.LEAVE_CHANNEL:
return handleLeaveChannel(state, action, extra);
}
return state;
};
exports.threadsInTeamReducer = threadsInTeamReducer;
const unreadThreadsInTeamReducer = (state = {}, action, extra) => {
switch (action.type) {
case action_types_1.ThreadTypes.READ_CHANGED_THREAD: {
const { teamId } = action.data;
if (teamId === '') {
const teamIds = Object.keys(state);
let newState = { ...state };
for (const teamId of teamIds) {
newState = handleSingleTeamThreadRead(newState, action, teamId, extra);
}
return newState;
}
return handleSingleTeamThreadRead(state, action, teamId, extra);
}
case action_types_1.ThreadTypes.RECEIVED_THREAD:
if (action.data.thread.unread_replies > 0 || action.data.thread.unread_mentions > 0) {
return handleReceivedThread(state, action, extra);
}
return state;
case action_types_1.ThreadTypes.RECEIVED_THREADS:
return handleReceiveThreads(state, {
...action,
data: {
...action.data,
threads: action.data.threads.filter((thread) => thread.unread_replies > 0 || thread.unread_mentions > 0),
},
});
case action_types_1.PostTypes.POST_DELETED:
case action_types_1.PostTypes.POST_REMOVED:
return handlePostRemoved(state, action);
case action_types_1.ThreadTypes.RECEIVED_UNREAD_THREADS:
return handleReceiveThreads(state, action);
case action_types_1.TeamTypes.LEAVE_TEAM:
return handleLeaveTeam(state, action);
case action_types_1.UserTypes.LOGOUT_SUCCESS:
return {};
case action_types_1.ChannelTypes.RECEIVED_CHANNEL_DELETED:
case action_types_1.ChannelTypes.LEAVE_CHANNEL:
return handleLeaveChannel(state, action, extra);
case action_types_1.ThreadTypes.FOLLOW_CHANGED_THREAD:
return handleFollowChanged(state, action, extra);
}
return state;
};
exports.unreadThreadsInTeamReducer = unreadThreadsInTeamReducer;