mattermost-redux
Version:
Common code (API client, Redux stores, logic, utility functions) for building a Mattermost client
879 lines • 53.3 kB
JavaScript
"use strict";
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.getMyActiveChannelIds = exports.getRedirectChannelNameForTeam = exports.getRedirectChannelNameForCurrentTeam = exports.getMyFirstChannelForTeams = exports.getDefaultChannelForTeams = exports.getChannelsWithUserProfiles = exports.getDirectAndGroupChannels = exports.getSortedAllTeamsUnreadChannels = exports.sortUnreadChannels = exports.getUnsortedAllTeamsUnreadChannels = exports.getUnreadChannels = exports.getAllTeamsUnreadChannelIds = exports.getUnreadChannelIds = exports.getChannelIdsForAllTeams = exports.getChannelIdsInAllTeams = exports.getChannelIdsForCurrentTeam = exports.getChannelIdsInCurrentTeam = exports.getAllDirectChannelIds = exports.canManageChannelMembers = exports.getUnreadStatusInCurrentTeam = exports.getTeamsUnreadStatuses = exports.getUnreadStatus = exports.getMembersInCurrentChannel = exports.getOtherChannels = exports.getMyChannels = exports.getGroupChannels = exports.getAllDirectChannelsNameMapInCurrentTeam = exports.getAllDirectChannels = exports.getChannelNameToDisplayNameMap = exports.getChannelsNameMapInCurrentTeam = exports.getChannelsNameMapInTeam = exports.getChannelsInAllTeams = exports.getChannelsInCurrentTeam = exports.getChannelSetForAllTeams = exports.getChannelSetInCurrentTeam = exports.countCurrentChannelUnreadMessages = exports.isCurrentChannelDefault = exports.isCurrentChannelArchived = exports.isMutedChannel = exports.isCurrentChannelMuted = exports.getCurrentChannelStats = exports.getMyChannelMember = exports.getChannelNameForSearchShortcut = exports.getCurrentChannelNameForSearchShortcut = exports.getCurrentChannel = exports.getDirectChannelsSet = exports.getAllDmChannels = exports.getMyCurrentChannelMembership = exports.getMyChannelMemberships = exports.getCurrentChannelId = void 0;
exports.isDeactivatedDirectChannel = exports.getRecentProfilesFromDMs = void 0;
exports.getAllChannels = getAllChannels;
exports.getAllChannelStats = getAllChannelStats;
exports.getChannelsMemberCount = getChannelsMemberCount;
exports.getChannelsInTeam = getChannelsInTeam;
exports.getChannelsInPolicy = getChannelsInPolicy;
exports.getChannelMembersInChannels = getChannelMembersInChannels;
exports.getChannelMember = getChannelMember;
exports.makeGetChannel = makeGetChannel;
exports.getChannel = getChannel;
exports.getDirectChannel = getDirectChannel;
exports.getMyChannelMembership = getMyChannelMembership;
exports.makeGetChannelsForIds = makeGetChannelsForIds;
exports.isCurrentChannelFavorite = isCurrentChannelFavorite;
exports.isCurrentChannelReadOnly = isCurrentChannelReadOnly;
exports.isChannelReadOnlyById = isChannelReadOnlyById;
exports.isChannelReadOnly = isChannelReadOnly;
exports.getChannelMessageCounts = getChannelMessageCounts;
exports.getChannelMessageCount = getChannelMessageCount;
exports.makeGetChannelUnreadCount = makeGetChannelUnreadCount;
exports.getChannelByName = getChannelByName;
exports.getChannelByTeamIdAndChannelName = getChannelByTeamIdAndChannelName;
exports.basicUnreadMeta = basicUnreadMeta;
exports.canManageAnyChannelMembersInCurrentTeam = canManageAnyChannelMembersInCurrentTeam;
exports.isManuallyUnread = isManuallyUnread;
exports.getChannelModerations = getChannelModerations;
exports.getChannelMemberCountsByGroup = getChannelMemberCountsByGroup;
exports.isFavoriteChannel = isFavoriteChannel;
exports.filterChannelList = filterChannelList;
exports.searchChannelsInPolicy = searchChannelsInPolicy;
exports.getDirectTeammate = getDirectTeammate;
exports.makeGetGmChannelMemberCount = makeGetGmChannelMemberCount;
exports.getChannelBanner = getChannelBanner;
exports.isChannelAutotranslated = isChannelAutotranslated;
exports.isMyChannelAutotranslated = isMyChannelAutotranslated;
exports.isUserLanguageSupportedForAutotranslation = isUserLanguageSupportedForAutotranslation;
exports.hasAutotranslationBecomeEnabled = hasAutotranslationBecomeEnabled;
const max_1 = __importDefault(require("lodash/max"));
const constants_1 = require("mattermost-redux/constants");
const channel_categories_1 = require("mattermost-redux/constants/channel_categories");
const create_selector_1 = require("mattermost-redux/selectors/create_selector");
const admin_1 = require("mattermost-redux/selectors/entities/admin");
const channel_categories_2 = require("mattermost-redux/selectors/entities/channel_categories");
const common_1 = require("mattermost-redux/selectors/entities/common");
const general_1 = require("mattermost-redux/selectors/entities/general");
const preferences_1 = require("mattermost-redux/selectors/entities/preferences");
const roles_1 = require("mattermost-redux/selectors/entities/roles");
const teams_1 = require("mattermost-redux/selectors/entities/teams");
const users_1 = require("mattermost-redux/selectors/entities/users");
const channel_utils_1 = require("mattermost-redux/utils/channel_utils");
const helpers_1 = require("mattermost-redux/utils/helpers");
const i18n_1 = require("./i18n");
const posts_1 = require("./posts");
const threads_1 = require("./threads");
// Re-define these types to ensure that these are typed correctly when mattermost-redux is published
exports.getCurrentChannelId = common_1.getCurrentChannelId;
exports.getMyChannelMemberships = common_1.getMyChannelMemberships;
exports.getMyCurrentChannelMembership = common_1.getMyCurrentChannelMembership;
function getAllChannels(state) {
return state.entities.channels.channels;
}
exports.getAllDmChannels = (0, create_selector_1.createSelector)('getAllDmChannels', getAllChannels, (allChannels) => {
let allDmChannels = {};
Object.values(allChannels).forEach((channel) => {
if (channel.type === constants_1.General.DM_CHANNEL) {
allDmChannels = { ...allDmChannels, [channel.name]: channel };
}
});
return allDmChannels;
});
function getAllChannelStats(state) {
return state.entities.channels.stats;
}
function getChannelsMemberCount(state) {
return state.entities.channels.channelsMemberCount;
}
function getChannelsInTeam(state) {
return state.entities.channels.channelsInTeam;
}
function getChannelsInPolicy() {
return (0, create_selector_1.createSelector)('getChannelsInPolicy', getAllChannels, (state, props) => (0, admin_1.getDataRetentionCustomPolicy)(state, props.policyId), (getAllChannels, policy) => {
if (!policy) {
return [];
}
const policyChannels = [];
Object.entries(getAllChannels).forEach((channelEntry) => {
const [, channel] = channelEntry;
if (channel.policy_id === policy.id) {
policyChannels.push(channel);
}
});
return policyChannels;
});
}
exports.getDirectChannelsSet = (0, create_selector_1.createSelector)('getDirectChannelsSet', getChannelsInTeam, (channelsInTeam) => {
if (!channelsInTeam) {
return new Set();
}
return new Set(channelsInTeam['']);
});
function getChannelMembersInChannels(state) {
return state.entities.channels.membersInChannel;
}
function getChannelMember(state, channelId, userId) {
return getChannelMembersInChannels(state)[channelId]?.[userId];
}
// makeGetChannel returns a selector that returns a channel from the store with the following filled in for DM/GM channels:
// - The display_name set to the other user(s) names, following the Teammate Name Display setting
// - The teammate_id for DM channels
// - The status of the other user in a DM channel
function makeGetChannel() {
return (0, create_selector_1.createSelector)('makeGetChannel', users_1.getCurrentUserId, (state) => state.entities.users.profiles, (state) => state.entities.users.profilesInChannel, (state, channelId) => {
const id = typeof channelId === 'string' ? channelId : channelId.id;
const channel = getChannel(state, id);
if (!channel || !(0, channel_utils_1.isDirectChannel)(channel)) {
return '';
}
const currentUserId = (0, users_1.getCurrentUserId)(state);
const teammateId = (0, channel_utils_1.getUserIdFromChannelName)(currentUserId, channel.name);
const teammateStatus = (0, users_1.getStatusForUserId)(state, teammateId);
return teammateStatus || 'offline';
}, (state, channelId) => {
const id = typeof channelId === 'string' ? channelId : channelId.id;
return getChannel(state, id);
}, preferences_1.getTeammateNameDisplaySetting, (currentUserId, profiles, profilesInChannel, teammateStatus, channel, teammateNameDisplay) => {
if (channel) {
return (0, channel_utils_1.newCompleteDirectChannelInfo)(currentUserId, profiles, profilesInChannel, teammateStatus, teammateNameDisplay, channel);
}
return channel;
});
}
// getChannel returns a channel as it exists in the store without filling in any additional details such as the
// display_name for DM/GM channels.
function getChannel(state, id) {
return getAllChannels(state)[id];
}
// getDirectChannel returns a direct channel channel as it exists in the store filling in any additional details such as the
// display_name or teammate_id.
function getDirectChannel(state, id) {
const channel = getAllChannels(state)[id];
if (channel && channel.type === 'D') {
return (0, channel_utils_1.completeDirectChannelInfo)(state.entities.users, (0, preferences_1.getTeammateNameDisplaySetting)(state), channel);
}
return undefined;
}
function getMyChannelMembership(state, channelId) {
return (0, exports.getMyChannelMemberships)(state)[channelId];
}
// makeGetChannelsForIds returns a selector that, given an array of channel IDs, returns a list of the corresponding
// channels. Channels are returned in the same order as the given IDs with undefined entries replacing any invalid IDs.
// Note that memoization will fail if an array literal is passed in.
function makeGetChannelsForIds() {
return (0, create_selector_1.createSelector)('makeGetChannelsForIds', getAllChannels, (state, ids) => ids, (allChannels, ids) => {
return ids.map((id) => allChannels[id]);
});
}
exports.getCurrentChannel = (0, create_selector_1.createSelector)('getCurrentChannel', getAllChannels, exports.getCurrentChannelId, (state) => state.entities.users, preferences_1.getTeammateNameDisplaySetting, (allChannels, currentChannelId, users, teammateNameDisplay) => {
const channel = allChannels[currentChannelId];
if (channel) {
return (0, channel_utils_1.completeDirectChannelInfo)(users, teammateNameDisplay, channel);
}
return channel;
});
const getChannelNameForSearch = (channel, users) => {
if (!channel) {
return undefined;
}
// Only get the extra info from users if we need it
if (channel.type === constants_1.General.DM_CHANNEL) {
const dmChannelWithInfo = (0, channel_utils_1.completeDirectChannelInfo)(users, constants_1.Preferences.DISPLAY_PREFER_USERNAME, channel);
return `@${dmChannelWithInfo.display_name}`;
}
// Replace spaces in GM channel names
if (channel.type === constants_1.General.GM_CHANNEL) {
const gmChannelWithInfo = (0, channel_utils_1.completeDirectGroupInfo)(users, constants_1.Preferences.DISPLAY_PREFER_USERNAME, channel, false);
return `@${gmChannelWithInfo.display_name.replace(/\s/g, '')}`;
}
return channel.name;
};
exports.getCurrentChannelNameForSearchShortcut = (0, create_selector_1.createSelector)('getCurrentChannelNameForSearchShortcut', getAllChannels, exports.getCurrentChannelId, (state) => state.entities.users, (allChannels, currentChannelId, users) => {
const channel = allChannels[currentChannelId];
return getChannelNameForSearch(channel, users);
});
exports.getChannelNameForSearchShortcut = (0, create_selector_1.createSelector)('getChannelNameForSearchShortcut', getAllChannels, (state) => state.entities.users, (state, channelId) => channelId, (allChannels, users, channelId) => {
const channel = allChannels[channelId];
return getChannelNameForSearch(channel, users);
});
exports.getMyChannelMember = (0, create_selector_1.createSelector)('getMyChannelMember', exports.getMyChannelMemberships, (state, channelId) => channelId, (channelMemberships, channelId) => {
return channelMemberships[channelId];
});
exports.getCurrentChannelStats = (0, create_selector_1.createSelector)('getCurrentChannelStats', getAllChannelStats, exports.getCurrentChannelId, (allChannelStats, currentChannelId) => {
return allChannelStats[currentChannelId];
});
function isCurrentChannelFavorite(state) {
const currentChannelId = (0, exports.getCurrentChannelId)(state);
return isFavoriteChannel(state, currentChannelId);
}
exports.isCurrentChannelMuted = (0, create_selector_1.createSelector)('isCurrentChannelMuted', exports.getMyCurrentChannelMembership, (membership) => {
if (!membership) {
return false;
}
return (0, channel_utils_1.isChannelMuted)(membership);
});
exports.isMutedChannel = (0, create_selector_1.createSelector)('isMutedChannel', (state, channelId) => getMyChannelMembership(state, channelId), (membership) => {
if (!membership) {
return false;
}
return (0, channel_utils_1.isChannelMuted)(membership);
});
exports.isCurrentChannelArchived = (0, create_selector_1.createSelector)('isCurrentChannelArchived', exports.getCurrentChannel, (channel) => channel?.delete_at !== 0);
exports.isCurrentChannelDefault = (0, create_selector_1.createSelector)('isCurrentChannelDefault', exports.getCurrentChannel, (channel) => (0, channel_utils_1.isDefault)(channel));
function isCurrentChannelReadOnly(state) {
return isChannelReadOnly(state, (0, exports.getCurrentChannel)(state));
}
function isChannelReadOnlyById(state, channelId) {
return isChannelReadOnly(state, getChannel(state, channelId));
}
function isChannelReadOnly(state, channel) {
return Boolean(channel && channel.name === constants_1.General.DEFAULT_CHANNEL && !(0, users_1.isCurrentUserSystemAdmin)(state));
}
function getChannelMessageCounts(state) {
return state.entities.channels.messageCounts;
}
function getChannelMessageCount(state, channelId) {
return getChannelMessageCounts(state)[channelId];
}
function getCurrentChannelMessageCount(state) {
return getChannelMessageCount(state, (0, exports.getCurrentChannelId)(state));
}
exports.countCurrentChannelUnreadMessages = (0, create_selector_1.createSelector)('countCurrentChannelUnreadMessages', getCurrentChannelMessageCount, exports.getMyCurrentChannelMembership, preferences_1.isCollapsedThreadsEnabled, (messageCount, membership, crtEnabled) => {
if (!membership || !messageCount) {
return 0;
}
return crtEnabled ? messageCount.root - membership.msg_count_root : messageCount.total - membership.msg_count;
});
function makeGetChannelUnreadCount() {
return (0, create_selector_1.createSelector)('makeGetChannelUnreadCount', (state, channelId) => getChannelMessageCount(state, channelId), (state, channelId) => getMyChannelMembership(state, channelId), preferences_1.isCollapsedThreadsEnabled, (messageCount, member, crtEnabled) => (0, channel_utils_1.calculateUnreadCount)(messageCount, member, crtEnabled));
}
function getChannelByName(state, channelName) {
return (0, channel_utils_1.getChannelByName)(getAllChannels(state), channelName);
}
function getChannelByTeamIdAndChannelName(state, teamId, channelName) {
return Object.values(getAllChannels(state)).find((channel) => channel.team_id === teamId && channel.name === channelName);
}
exports.getChannelSetInCurrentTeam = (0, create_selector_1.createSelector)('getChannelSetInCurrentTeam', teams_1.getCurrentTeamId, getChannelsInTeam, (currentTeamId, channelsInTeam) => {
return (channelsInTeam && channelsInTeam[currentTeamId]) || new Set();
});
exports.getChannelSetForAllTeams = (0, create_selector_1.createSelector)('getChannelSetForAllTeams', getAllChannels, (allChannels) => {
const channelSet = [];
Object.values(allChannels).forEach((channel) => {
if (channel.type !== constants_1.General.GM_CHANNEL && channel.type !== constants_1.General.DM_CHANNEL) {
channelSet.push(channel.id);
}
});
return channelSet;
});
function sortAndInjectChannels(channels, channelSet, locale) {
const currentChannels = [];
if (typeof channelSet === 'undefined') {
return currentChannels;
}
channelSet.forEach((c) => {
currentChannels.push(channels[c]);
});
return currentChannels.sort(channel_utils_1.sortChannelsByDisplayName.bind(null, locale));
}
exports.getChannelsInCurrentTeam = (0, create_selector_1.createSelector)('getChannelsInCurrentTeam', getAllChannels, exports.getChannelSetInCurrentTeam, common_1.getCurrentUser, (channels, currentTeamChannelSet, currentUser) => {
let locale = constants_1.General.DEFAULT_LOCALE;
if (currentUser && currentUser.locale) {
locale = currentUser.locale;
}
return sortAndInjectChannels(channels, currentTeamChannelSet, locale);
});
exports.getChannelsInAllTeams = (0, create_selector_1.createSelector)('getChannelsInAllTeams', getAllChannels, exports.getChannelSetForAllTeams, common_1.getCurrentUser, (channels, getChannelSetForAllTeams, currentUser) => {
const locale = currentUser?.locale || constants_1.General.DEFAULT_LOCALE;
return sortAndInjectChannels(channels, getChannelSetForAllTeams, locale);
});
exports.getChannelsNameMapInTeam = (0, create_selector_1.createSelector)('getChannelsNameMapInTeam', getAllChannels, getChannelsInTeam, (state, teamId) => teamId, (channels, channelsInTeams, teamId) => {
const channelsInTeam = channelsInTeams[teamId] || new Set();
const channelMap = {};
channelsInTeam.forEach((id) => {
const channel = channels[id];
channelMap[channel.name] = channel;
});
return channelMap;
});
exports.getChannelsNameMapInCurrentTeam = (0, create_selector_1.createSelector)('getChannelsNameMapInCurrentTeam', getAllChannels, exports.getChannelSetInCurrentTeam, (channels, currentTeamChannelSet) => {
const channelMap = {};
currentTeamChannelSet.forEach((id) => {
const channel = channels[id];
channelMap[channel.name] = channel;
});
return channelMap;
});
exports.getChannelNameToDisplayNameMap = (0, helpers_1.createIdsSelector)('getChannelNameToDisplayNameMap', getAllChannels, exports.getChannelSetInCurrentTeam, (channels, currentTeamChannelSet) => {
const channelMap = {};
for (const id of currentTeamChannelSet) {
const channel = channels[id];
channelMap[channel.name] = channel.display_name;
}
return channelMap;
});
// Returns both DMs and GMs
exports.getAllDirectChannels = (0, create_selector_1.createSelector)('getAllDirectChannels', getAllChannels, exports.getDirectChannelsSet, (state) => state.entities.users, preferences_1.getTeammateNameDisplaySetting, (channels, channelSet, users, teammateNameDisplay) => {
const dmChannels = [];
channelSet.forEach((c) => {
dmChannels.push((0, channel_utils_1.completeDirectChannelInfo)(users, teammateNameDisplay, channels[c]));
});
return dmChannels;
});
exports.getAllDirectChannelsNameMapInCurrentTeam = (0, create_selector_1.createSelector)('getAllDirectChannelsNameMapInCurrentTeam', getAllChannels, exports.getDirectChannelsSet, (state) => state.entities.users, preferences_1.getTeammateNameDisplaySetting, (channels, channelSet, users, teammateNameDisplay) => {
const channelMap = {};
channelSet.forEach((id) => {
const channel = channels[id];
channelMap[channel.name] = (0, channel_utils_1.completeDirectChannelInfo)(users, teammateNameDisplay, channel);
});
return channelMap;
});
// Returns only GMs
exports.getGroupChannels = (0, create_selector_1.createSelector)('getGroupChannels', getAllChannels, exports.getDirectChannelsSet, (state) => state.entities.users, preferences_1.getTeammateNameDisplaySetting, (channels, channelSet, users, teammateNameDisplay) => {
const gmChannels = [];
channelSet.forEach((id) => {
const channel = channels[id];
if (channel.type === constants_1.General.GM_CHANNEL) {
gmChannels.push((0, channel_utils_1.completeDirectChannelInfo)(users, teammateNameDisplay, channel));
}
});
return gmChannels;
});
exports.getMyChannels = (0, create_selector_1.createSelector)('getMyChannels', exports.getChannelsInCurrentTeam, exports.getAllDirectChannels, exports.getMyChannelMemberships, (channels, directChannels, myMembers) => {
return [...channels, ...directChannels].filter((c) => Object.hasOwn(myMembers, c.id));
});
exports.getOtherChannels = (0, create_selector_1.createSelector)('getOtherChannels', exports.getChannelsInCurrentTeam, exports.getMyChannelMemberships, (state, archived = true) => archived, (channels, myMembers, archived) => {
return channels.filter((c) => !Object.hasOwn(myMembers, c.id) && c.type === constants_1.General.OPEN_CHANNEL && (archived ? true : c.delete_at === 0));
});
exports.getMembersInCurrentChannel = (0, create_selector_1.createSelector)('getMembersInCurrentChannel', exports.getCurrentChannelId, getChannelMembersInChannels, (currentChannelId, members) => {
return members[currentChannelId];
});
function basicUnreadMeta(unreadStatus) {
return {
isUnread: Boolean(unreadStatus),
unreadMentionCount: (typeof unreadStatus === 'number' && unreadStatus) || 0,
};
}
// muted channel will not be counted towards unreads
exports.getUnreadStatus = (0, create_selector_1.createSelector)('getUnreadStatus', getAllChannels, exports.getMyChannelMemberships, getChannelMessageCounts, common_1.getUsers, users_1.getCurrentUserId, teams_1.getCurrentTeamId, preferences_1.isCollapsedThreadsEnabled, threads_1.getThreadCounts, threads_1.getThreadCountsIncludingDirect, (channels, myMembers, messageCounts, users, currentUserId, currentTeamId, collapsedThreads, threadCounts, threadCountsIncludingDirect) => {
const { messages: unreadMessages, mentions: unreadMentions, } = Object.entries(myMembers).reduce((counts, [channelId, membership]) => {
const channel = channels[channelId];
if (!channel || !membership) {
return counts;
}
const channelExists = channel.type === constants_1.General.DM_CHANNEL ? users[(0, channel_utils_1.getUserIdFromChannelName)(currentUserId, channel.name)]?.delete_at === 0 : channel.delete_at === 0;
if (!channelExists) {
return counts;
}
if ((0, channel_utils_1.isChannelMuted)(membership)) {
return counts;
}
const mentions = collapsedThreads ? membership.mention_count_root : membership.mention_count;
if (mentions) {
counts.mentions += mentions;
}
const unreadCount = (0, channel_utils_1.calculateUnreadCount)(messageCounts[channelId], myMembers[channelId], collapsedThreads);
if (unreadCount.showUnread) {
counts.messages += unreadCount.messages;
}
return counts;
}, {
messages: 0,
mentions: 0,
});
const totalUnreadMessages = unreadMessages;
let totalUnreadMentions = unreadMentions;
let anyUnreadThreads = false;
// when collapsed threads are enabled, we start with root-post counts from channels, then
// add the same thread-reply counts from the global threads view
if (collapsedThreads) {
Object.keys(threadCounts).forEach((teamId) => {
const c = threadCounts[teamId];
if (teamId === currentTeamId) {
const currentTeamDirectCounts = threadCountsIncludingDirect[currentTeamId] || 0;
anyUnreadThreads = Boolean(currentTeamDirectCounts.total_unread_threads);
totalUnreadMentions += currentTeamDirectCounts.total_unread_mentions;
}
else {
anyUnreadThreads = anyUnreadThreads || Boolean(c.total_unread_threads);
totalUnreadMentions += c.total_unread_mentions;
}
});
}
return totalUnreadMentions || anyUnreadThreads || Boolean(totalUnreadMessages);
});
/**
* Return a tuple of
* - Set of team IDs that have unread messages
* - Map with team IDs as keys and unread mentions counts as values
*/
exports.getTeamsUnreadStatuses = (0, create_selector_1.createSelector)('getTeamsUnreadStatuses', getAllChannels, exports.getMyChannelMemberships, getChannelMessageCounts, preferences_1.isCollapsedThreadsEnabled, threads_1.getThreadCounts, (channels, channelMemberships, channelMessageCounts, collapsedThreadsEnabled, teamThreadCounts) => {
const teamUnreadsSet = new Set();
const teamMentionsMap = new Map();
const teamHasUrgentMap = new Map();
for (const [channelId, channelMembership] of Object.entries(channelMemberships)) {
const channel = channels[channelId];
if (!channel || !channelMembership) {
continue;
}
// if channel is muted, we skip its count
if ((0, channel_utils_1.isChannelMuted)(channelMembership)) {
continue;
}
// We skip DMs and GMs in counting since they are accesible through Direct messages across teams
if (channel.type === constants_1.General.DM_CHANNEL || channel.type === constants_1.General.GM_CHANNEL) {
continue;
}
// If other user is deleted in a DM channel, we skip its count
// TODO: This is a check overlap with the above condition, so it can never execute.
// Evaluate if it some logic should be changed or if this branch should be removed.
// if (channel.type === General.DM_CHANNEL) {
// const otherUserId = getUserIdFromChannelName(currentUserId, channel.name);
// if (users[otherUserId]?.delete_at !== 0) {
// continue;
// }
// }
// If channel is deleted, we skip its count
if (channel.delete_at !== 0) {
continue;
}
// Add read/unread from channel membership
const unreadCountObjectForChannel = (0, channel_utils_1.calculateUnreadCount)(channelMessageCounts[channelId], channelMembership, collapsedThreadsEnabled);
if (unreadCountObjectForChannel.showUnread) {
teamUnreadsSet.add(channel.team_id);
}
// Add mentions count from channel membership
if (unreadCountObjectForChannel.mentions > 0) {
const previousMentionsInTeam = teamMentionsMap.has(channel.team_id) ? teamMentionsMap.get(channel.team_id) : 0;
if (previousMentionsInTeam === 0) {
teamMentionsMap.set(channel.team_id, unreadCountObjectForChannel.mentions);
}
else {
teamMentionsMap.set(channel.team_id, unreadCountObjectForChannel.mentions + previousMentionsInTeam);
}
}
// Add has urgent mentions count from channel membership
if (unreadCountObjectForChannel.hasUrgent) {
const previousHasUrgetInTeam = teamHasUrgentMap.has(channel.team_id) ? teamHasUrgentMap.get(channel.team_id) : false;
if (!previousHasUrgetInTeam) {
teamHasUrgentMap.set(channel.team_id, unreadCountObjectForChannel.hasUrgent);
}
}
}
if (collapsedThreadsEnabled) {
for (const teamId of Object.keys(teamThreadCounts)) {
const threadCountsObjectForTeam = teamThreadCounts[teamId];
// Add read/unread from global threads view for team
if (threadCountsObjectForTeam.total_unread_threads > 0) {
teamUnreadsSet.add(teamId);
}
// Add mentions count from global threads view for team
if (threadCountsObjectForTeam.total_unread_mentions > 0) {
const previousMentionsInTeam = teamMentionsMap.has(teamId) ? teamMentionsMap.get(teamId) : 0;
if (previousMentionsInTeam === 0) {
teamMentionsMap.set(teamId, threadCountsObjectForTeam.total_unread_mentions);
}
else {
teamMentionsMap.set(teamId, threadCountsObjectForTeam.total_unread_mentions + previousMentionsInTeam);
}
}
// Add mentions count from global threads view for team
if (threadCountsObjectForTeam.total_unread_urgent_mentions) {
const previousHasUrgetInTeam = teamHasUrgentMap.has(teamId) ? teamHasUrgentMap.get(teamId) : false;
if (!previousHasUrgetInTeam) {
teamHasUrgentMap.set(teamId, Boolean(threadCountsObjectForTeam.total_unread_urgent_mentions));
}
}
}
}
return [teamUnreadsSet, teamMentionsMap, teamHasUrgentMap];
});
exports.getUnreadStatusInCurrentTeam = (0, create_selector_1.createSelector)('getUnreadStatusInCurrentTeam', exports.getCurrentChannelId, exports.getMyChannels, exports.getMyChannelMemberships, getChannelMessageCounts, common_1.getUsers, users_1.getCurrentUserId, teams_1.getCurrentTeamId, preferences_1.isCollapsedThreadsEnabled, threads_1.getThreadCountsIncludingDirect, posts_1.isPostPriorityEnabled, (currentChannelId, channels, myMembers, messageCounts, users, currentUserId, currentTeamId, collapsedThreadsEnabled, threadCounts, postPriorityEnabled) => {
const { messages: currentTeamUnreadMessages, mentions: currentTeamUnreadMentions, } = channels.reduce((counts, channel) => {
const m = myMembers[channel.id];
if (!m || channel.id === currentChannelId) {
return counts;
}
const channelExists = channel.type === constants_1.General.DM_CHANNEL ? users[(0, channel_utils_1.getUserIdFromChannelName)(currentUserId, channel.name)]?.delete_at === 0 : channel.delete_at === 0;
if (!channelExists) {
return counts;
}
const mentions = collapsedThreadsEnabled ? m.mention_count_root : m.mention_count;
if (mentions) {
counts.mentions += mentions;
}
if (postPriorityEnabled) {
counts.urgentMentions += m.urgent_mention_count;
}
const unreadCount = (0, channel_utils_1.calculateUnreadCount)(messageCounts[channel.id], m, collapsedThreadsEnabled);
if (unreadCount.showUnread) {
counts.messages += unreadCount.messages;
}
return counts;
}, {
messages: 0,
mentions: 0,
urgentMentions: 0,
});
let totalUnreadMentions = currentTeamUnreadMentions;
let anyUnreadThreads = false;
// when collapsed threads are enabled, we start with root-post counts from channels, then
// add the same thread-reply counts from the global threads view IF we're not in global threads
if (collapsedThreadsEnabled && currentChannelId) {
const c = threadCounts[currentTeamId];
if (c) {
anyUnreadThreads = anyUnreadThreads || Boolean(c.total_unread_threads);
totalUnreadMentions += c.total_unread_mentions;
}
}
return totalUnreadMentions || anyUnreadThreads || Boolean(currentTeamUnreadMessages);
});
exports.canManageChannelMembers = (0, create_selector_1.createSelector)('canManageChannelMembers', exports.getCurrentChannel, (state) => (0, roles_1.haveICurrentChannelPermission)(state, constants_1.Permissions.MANAGE_PRIVATE_CHANNEL_MEMBERS), (state) => (0, roles_1.haveICurrentChannelPermission)(state, constants_1.Permissions.MANAGE_PUBLIC_CHANNEL_MEMBERS), (channel, managePrivateMembers, managePublicMembers) => {
if (!channel) {
return false;
}
if (channel.delete_at !== 0) {
return false;
}
if (channel.type === constants_1.General.DM_CHANNEL || channel.type === constants_1.General.GM_CHANNEL || channel.name === constants_1.General.DEFAULT_CHANNEL) {
return false;
}
if (channel.type === constants_1.General.OPEN_CHANNEL) {
return managePublicMembers;
}
else if (channel.type === constants_1.General.PRIVATE_CHANNEL) {
return managePrivateMembers;
}
return true;
});
// Determine if the user has permissions to manage members in at least one channel of the current team
function canManageAnyChannelMembersInCurrentTeam(state) {
const myChannelMemberships = (0, exports.getMyChannelMemberships)(state);
const myChannelsIds = Object.keys(myChannelMemberships);
const currentTeamId = (0, teams_1.getCurrentTeamId)(state);
for (const channelId of myChannelsIds) {
const channel = getChannel(state, channelId);
if (!channel || channel.team_id !== currentTeamId) {
continue;
}
if (channel.type === constants_1.General.OPEN_CHANNEL && (0, roles_1.haveIChannelPermission)(state, currentTeamId, channelId, constants_1.Permissions.MANAGE_PUBLIC_CHANNEL_MEMBERS)) {
return true;
}
else if (channel.type === constants_1.General.PRIVATE_CHANNEL && (0, roles_1.haveIChannelPermission)(state, currentTeamId, channelId, constants_1.Permissions.MANAGE_PRIVATE_CHANNEL_MEMBERS)) {
return true;
}
}
return false;
}
exports.getAllDirectChannelIds = (0, helpers_1.createIdsSelector)('getAllDirectChannelIds', exports.getDirectChannelsSet, (directIds) => {
return Array.from(directIds);
});
exports.getChannelIdsInCurrentTeam = (0, helpers_1.createIdsSelector)('getChannelIdsInCurrentTeam', teams_1.getCurrentTeamId, getChannelsInTeam, (currentTeamId, channelsInTeam) => {
return Array.from(channelsInTeam[currentTeamId] || []);
});
exports.getChannelIdsForCurrentTeam = (0, helpers_1.createIdsSelector)('getChannelIdsForCurrentTeam', exports.getChannelIdsInCurrentTeam, exports.getAllDirectChannelIds, (channels, direct) => {
return [...channels, ...direct];
});
exports.getChannelIdsInAllTeams = (0, helpers_1.createIdsSelector)('getChannelIdsInAllTeams', exports.getChannelSetForAllTeams, (channels) => {
return Array.from(channels || []);
});
exports.getChannelIdsForAllTeams = (0, helpers_1.createIdsSelector)('getChannelIdsForAllTeams', exports.getChannelIdsInAllTeams, exports.getAllDirectChannelIds, (channels, direct) => {
return [...channels, ...direct];
});
exports.getUnreadChannelIds = (0, helpers_1.createIdsSelector)('getUnreadChannelIds', preferences_1.isCollapsedThreadsEnabled, exports.getMyChannelMemberships, getChannelMessageCounts, exports.getChannelIdsForCurrentTeam, (state, lastUnreadChannel = null) => lastUnreadChannel, (collapsedThreads, members, messageCounts, teamChannelIds, lastUnreadChannel) => {
const unreadIds = teamChannelIds.filter((id) => {
return (0, channel_utils_1.calculateUnreadCount)(messageCounts[id], members[id], collapsedThreads).showUnread;
});
if (lastUnreadChannel && members[lastUnreadChannel.id] && !unreadIds.includes(lastUnreadChannel.id)) {
unreadIds.push(lastUnreadChannel.id);
}
return unreadIds;
});
exports.getAllTeamsUnreadChannelIds = (0, helpers_1.createIdsSelector)('getAllTeamsUnreadChannelIds', preferences_1.isCollapsedThreadsEnabled, exports.getMyChannelMemberships, getChannelMessageCounts, exports.getChannelIdsForAllTeams, (collapsedThreads, members, messageCounts, allTeamsChannelIds) => {
return allTeamsChannelIds.filter((id) => {
return (0, channel_utils_1.calculateUnreadCount)(messageCounts[id], members[id], collapsedThreads).showUnread;
});
});
exports.getUnreadChannels = (0, helpers_1.createIdsSelector)('getUnreadChannels', common_1.getCurrentUser, common_1.getUsers, users_1.getUserIdsInChannels, getAllChannels, exports.getUnreadChannelIds, preferences_1.getTeammateNameDisplaySetting, (currentUser, profiles, userIdsInChannels, channels, unreadIds, settings) => {
// If we receive an unread for a channel and then a mention the channel
// won't be sorted correctly until we receive a message in another channel
if (!currentUser) {
return [];
}
const allUnreadChannels = unreadIds.filter((id) => channels[id] && channels[id].delete_at === 0).map((id) => {
const c = channels[id];
if (c.type === constants_1.General.DM_CHANNEL || c.type === constants_1.General.GM_CHANNEL) {
return (0, channel_utils_1.completeDirectChannelDisplayName)(currentUser.id, profiles, userIdsInChannels[id], settings, c);
}
return c;
});
return allUnreadChannels;
});
exports.getUnsortedAllTeamsUnreadChannels = (0, create_selector_1.createSelector)('getAllTeamsUnreadChannels', common_1.getCurrentUser, common_1.getUsers, users_1.getUserIdsInChannels, getAllChannels, exports.getAllTeamsUnreadChannelIds, preferences_1.getTeammateNameDisplaySetting, (currentUser, profiles, userIdsInChannels, channels, allTeamsUnreadChannelIds, settings) => {
// If we receive an unread for a channel and then a mention the channel
// won't be sorted correctly until we receive a message in another channel
if (!currentUser) {
return [];
}
return allTeamsUnreadChannelIds.filter((id) => channels[id] && channels[id].delete_at === 0).map((id) => {
const c = channels[id];
if (c.type === constants_1.General.DM_CHANNEL || c.type === constants_1.General.GM_CHANNEL) {
return (0, channel_utils_1.completeDirectChannelDisplayName)(currentUser.id, profiles, userIdsInChannels[id], settings, c);
}
return c;
});
});
const sortUnreadChannels = (channels, myMembers, lastUnreadChannel, crtEnabled) => {
function isMuted(channel) {
return (0, channel_utils_1.isChannelMuted)(myMembers[channel.id]);
}
function hasMentions(channel) {
if (lastUnreadChannel && channel.id === lastUnreadChannel.id && lastUnreadChannel.hadMentions) {
return true;
}
const member = myMembers[channel.id];
return member?.mention_count !== 0;
}
// Sort channels with mentions first and then sort by recency
return [...channels].sort((a, b) => {
// Sort muted channels last
if (isMuted(a) && !isMuted(b)) {
return 1;
}
else if (!isMuted(a) && isMuted(b)) {
return -1;
}
// Sort non-muted mentions first
if (hasMentions(a) && !hasMentions(b)) {
return -1;
}
else if (!hasMentions(a) && hasMentions(b)) {
return 1;
}
const aLastPostAt = (0, max_1.default)([crtEnabled ? a.last_root_post_at : a.last_post_at, a.create_at]) || 0;
const bLastPostAt = (0, max_1.default)([crtEnabled ? b.last_root_post_at : b.last_post_at, b.create_at]) || 0;
return bLastPostAt - aLastPostAt;
});
};
exports.sortUnreadChannels = sortUnreadChannels;
exports.getSortedAllTeamsUnreadChannels = (0, create_selector_1.createSelector)('getSortedAllTeamsUnreadChannels', exports.getUnsortedAllTeamsUnreadChannels, exports.getMyChannelMemberships, preferences_1.isCollapsedThreadsEnabled, (channels, myMembers, crtEnabled) => {
return (0, exports.sortUnreadChannels)(channels, myMembers, null, crtEnabled);
});
// getDirectAndGroupChannels returns all direct and group channels, even if they have been manually
// or automatically closed.
//
// This is similar to the getAllDirectChannels above which actually also returns group channels,
// but suppresses manually closed group channels but not manually closed direct channels. This
// method does away with all the suppression, since the webapp client downstream uses this for
// the channel switcher and puts such suppressed channels in a separate category.
exports.getDirectAndGroupChannels = (0, create_selector_1.createSelector)('getDirectAndGroupChannels', common_1.getCurrentUser, common_1.getUsers, users_1.getUserIdsInChannels, getAllChannels, preferences_1.getTeammateNameDisplaySetting, (currentUser, profiles, userIdsInChannels, channels, settings) => {
if (!currentUser) {
return [];
}
return Object.keys(channels).
map((key) => channels[key]).
filter((channel) => Boolean(channel)).
filter((channel) => channel.type === constants_1.General.DM_CHANNEL || channel.type === constants_1.General.GM_CHANNEL).
map((channel) => (0, channel_utils_1.completeDirectChannelDisplayName)(currentUser.id, profiles, userIdsInChannels[channel.id], settings, channel));
});
const getProfiles = (currentUserId, usersIdsInChannel, users) => {
const profiles = [];
usersIdsInChannel.forEach((userId) => {
if (userId !== currentUserId) {
profiles.push(users[userId]);
}
});
return profiles;
};
/**
* Returns an array of unsorted group channels, each with an array of the user profiles in the channel attached to them.
*/
exports.getChannelsWithUserProfiles = (0, create_selector_1.createSelector)('getChannelsWithUserProfiles', users_1.getUserIdsInChannels, common_1.getUsers, exports.getGroupChannels, users_1.getCurrentUserId, (channelUserMap, users, channels, currentUserId) => {
return channels.map((channel) => {
const profiles = getProfiles(currentUserId, channelUserMap[channel.id] || new Set(), users);
return {
...channel,
profiles,
};
});
});
exports.getDefaultChannelForTeams = (0, create_selector_1.createSelector)('getDefaultChannelForTeams', getAllChannels, (channels) => {
const result = {};
for (const channel of Object.keys(channels).map((key) => channels[key])) {
if (channel && channel.name === constants_1.General.DEFAULT_CHANNEL) {
result[channel.team_id] = channel;
}
}
return result;
});
exports.getMyFirstChannelForTeams = (0, create_selector_1.createSelector)('getMyFirstChannelForTeams', getAllChannels, exports.getMyChannelMemberships, teams_1.getMyTeams, common_1.getCurrentUser, (allChannels, myChannelMemberships, myTeams, currentUser) => {
const locale = currentUser.locale || constants_1.General.DEFAULT_LOCALE;
const result = {};
for (const team of myTeams) {
// Get a sorted array of all channels in the team that the current user is a member of
const teamChannels = Object.values(allChannels).filter((channel) => channel && channel.team_id === team.id && Boolean(myChannelMemberships[channel.id])).sort(channel_utils_1.sortChannelsByDisplayName.bind(null, locale));
if (teamChannels.length === 0) {
continue;
}
result[team.id] = teamChannels[0];
}
return result;
});
const getRedirectChannelNameForCurrentTeam = (state) => {
const currentTeamId = (0, teams_1.getCurrentTeamId)(state);
return (0, exports.getRedirectChannelNameForTeam)(state, currentTeamId);
};
exports.getRedirectChannelNameForCurrentTeam = getRedirectChannelNameForCurrentTeam;
const getRedirectChannelNameForTeam = (state, teamId) => {
const defaultChannelForTeam = (0, exports.getDefaultChannelForTeams)(state)[teamId];
const canIJoinPublicChannelsInTeam = (0, roles_1.haveITeamPermission)(state, teamId, constants_1.Permissions.JOIN_PUBLIC_CHANNELS);
const myChannelMemberships = (0, exports.getMyChannelMemberships)(state);
const iAmMemberOfTheTeamDefaultChannel = Boolean(defaultChannelForTeam && myChannelMemberships[defaultChannelForTeam.id]);
if (iAmMemberOfTheTeamDefaultChannel || canIJoinPublicChannelsInTeam) {
return constants_1.General.DEFAULT_CHANNEL;
}
const myFirstChannelForTeam = (0, exports.getMyFirstChannelForTeams)(state)[teamId];
return (myFirstChannelForTeam && myFirstChannelForTeam.name) || constants_1.General.DEFAULT_CHANNEL;
};
exports.getRedirectChannelNameForTeam = getRedirectChannelNameForTeam;
// isManually unread looks into state if the provided channelId is marked as unread by the user.
function isManuallyUnread(state, channelId) {
if (!channelId) {
return false;
}
return Boolean(state.entities.channels.manuallyUnread[channelId]);
}
function getChannelModerations(state, channelId) {
return state.entities.channels.channelModerations[channelId];
}
const EMPTY_OBJECT = {};
function getChannelMemberCountsByGroup(state, channelId) {
if (!channelId) {
return EMPTY_OBJECT;
}
return state.entities.channels.channelMemberCountsByGroup[channelId] || EMPTY_OBJECT;
}
function isFavoriteChannel(state, channelId) {
const channel = getChannel(state, channelId);
if (!channel) {
return false;
}
const category = (0, channel_categories_2.getCategoryInTeamByType)(state, channel.team_id || (0, teams_1.getCurrentTeamId)(state), channel_categories_1.CategoryTypes.FAVORITES);
if (!category) {
return false;
}
return category.channel_ids.includes(channel.id);
}
function filterChannelList(channelList, filters) {
if (!filters || (!filters.private && !filters.public && !filters.deleted && !filters.team_ids)) {
return channelList;
}
let result = [];
const channelType = [];
const channels = channelList;
if (filters.public) {
channelType.push(constants_1.General.OPEN_CHANNEL);
}
if (filters.private) {
channelType.push(constants_1.General.PRIVATE_CHANNEL);
}
if (filters.deleted) {
channelType.push(constants_1.General.ARCHIVED_CHANNEL);
}
channelType.forEach((type) => {
result = result.concat(channels.filter((channel) => channel.type === type));
});
if (filters.team_ids && filters.team_ids.length > 0) {
let teamResult = [];
filters.team_ids.forEach((id) => {
if (channelType.length > 0) {
const filterResult = result.filter((channel) => channel.team_id === id);
teamResult = teamResult.concat(filterResult);
}
else {
teamResult = teamResult.concat(channels.filter((channel) => channel.team_id === id));
}
});
result = teamResult;
}
return result;
}
function searchChannelsInPolicy(state, policyId, term, filters) {
const channelsInPolicy = getChannelsInPolicy();
const channelArray = channelsInPolicy(state, { policyId });
let channels = filterChannelList(channelArray, filters);
channels = (0, channel_utils_1.filterChannelsMatchingTerm)(channels, term);
return channels;
}
function getDirectTeammate(state, channelId) {
const channel = getChannel(state, channelId);
if (!channel || channel.type !== 'D') {
return undefined;
}
const userIds = channel.name.split('__');
const currentUserId = (0, users_1.getCurrentUserId)(state);
if (userIds.length !== 2 || userIds.indexOf(currentUserId) === -1) {
return undefined;
}
if (userIds[0] === userIds[1]) {
return (0, users_1.getUser)(state, userIds[0]);
}
for (const id of userIds) {
if (id !== currentUserId) {
return (0, users_1.getUser)(state, id);
}
}
return undefined;
}
function makeGetGmChannelMemberCount() {
return (0, create_selector_1.createSelector)('getChannelMemberCount', users_1.getUserIdsInChannels, users_1.getCurrentUserId, (_state, channel) => channel, (memberIds, userId, channel) => {
let membersCount = 0;
if (memberIds && memberIds[channel.id]) {
const groupMemberIds = memberIds[channel.id];
membersCount = groupMemberIds.size;
if (groupMemberIds.has(userId)) {
membersCount--;
}
}
return membersCount;
});
}
exports.getMyActiveChannelIds = (0, create_selector_1.createSelector)('getMyActiveChannels', exports.getMyChannels, (channels) => channels.flatMap((chan) => {
if (chan.delete_at > 0) {
return [];
}
return chan.id;
}));
exports.getRecentProfilesFromDMs = (0, create_selector_1.createSelector)('getRecentProfilesFromDMs', getAllChannels, common_1.getUsers, common_1.getCurrentUser, exports.getMyChannelMemberships, (allChannels, users, currentUser, memberships) => {
if (!allChannels || !users) {
return [];
}
const recentChannelIds = Object.values(memberships).sort((aMembership, bMembership) => {
return bMembership.last_viewed_at - aMembership.last_viewed_at;
}).map((membership) => membership.channel_id);
const groupChannels = Object.values(allChannels).filter((channel) => channel.type === constants_1.General.GM_CHANNEL);
const dmChannels = Object.values(allChannels).filter((channel) => channel.type === constants_1.General.DM_CHANNEL);
const userProfilesByChannel = {};
dmChannels.forEach((channel) => {
if (channel.name) {
const otherUserId = (0, channel_utils_1.getUserIdFromChannelName)(currentUser.id, channel.name);
const userProfile = users[otherUserId];
if (userProfile) {
userProfilesByChannel[channel.id] = [userProfile];
}
}
});
groupChannels.forEach((channel) => {
if (channel.display_name) {
const memberUsernames = channel.display_name.split(',').map((username) => username.trim()).filter((username) => username !== currentUser.username).sort();
const memberProfiles = memberUsernames.map((username) => {
return Object.values(users).find((profile) => profile.username === username);
});
if (memberProfiles) {
userProfilesByChannel[channel.id] = memberProfiles;
}
}
});
const sortedUserProfiles = new Set();
recentChannelIds.forEach((cid) => {
if (userProfilesByChannel[cid]) {
userProfilesByChannel[cid].forEach((user) => sortedUserProfiles.add(user));
}
});
return [.