UNPKG

mattermost-redux

Version:

Common code (API client, Redux stores, logic, utility functions) for building a Mattermost client

879 lines 53.3 kB
"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 [.