UNPKG

mattermost-redux

Version:

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

355 lines (354 loc) 14.8 kB
"use strict"; // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. Object.defineProperty(exports, "__esModule", { value: true }); exports.completeDirectChannelInfo = completeDirectChannelInfo; exports.splitRoles = splitRoles; exports.newCompleteDirectChannelInfo = newCompleteDirectChannelInfo; exports.completeDirectChannelDisplayName = completeDirectChannelDisplayName; exports.cleanUpUrlable = cleanUpUrlable; exports.getChannelByName = getChannelByName; exports.getDirectChannelName = getDirectChannelName; exports.getUserIdFromChannelName = getUserIdFromChannelName; exports.isDirectChannel = isDirectChannel; exports.isGroupChannel = isGroupChannel; exports.getChannelsIdForTeam = getChannelsIdForTeam; exports.getGroupDisplayNameFromUserIds = getGroupDisplayNameFromUserIds; exports.isDefault = isDefault; exports.completeDirectGroupInfo = completeDirectGroupInfo; exports.isOpenChannel = isOpenChannel; exports.isPrivateChannel = isPrivateChannel; exports.sortChannelsByTypeListAndDisplayName = sortChannelsByTypeListAndDisplayName; exports.sortChannelsByTypeAndDisplayName = sortChannelsByTypeAndDisplayName; exports.sortChannelsByDisplayName = sortChannelsByDisplayName; exports.sortChannelsByDisplayNameAndMuted = sortChannelsByDisplayNameAndMuted; exports.sortChannelsByRecency = sortChannelsByRecency; exports.isChannelMuted = isChannelMuted; exports.areChannelMentionsIgnored = areChannelMentionsIgnored; exports.filterChannelsMatchingTerm = filterChannelsMatchingTerm; exports.channelListToMap = channelListToMap; exports.calculateUnreadCount = calculateUnreadCount; const channels_1 = require("mattermost-redux/constants/channels"); const user_utils_1 = require("./user_utils"); const constants_1 = require("../constants"); const channelTypeOrder = { [constants_1.General.OPEN_CHANNEL]: 0, [constants_1.General.PRIVATE_CHANNEL]: 1, [constants_1.General.DM_CHANNEL]: 2, [constants_1.General.GM_CHANNEL]: 3, }; function completeDirectChannelInfo(usersState, teammateNameDisplay, channel) { if (isDirectChannel(channel)) { const teammateId = getUserIdFromChannelName(usersState.currentUserId, channel.name); // return empty string instead of `someone` default string for display_name return { ...channel, display_name: (0, user_utils_1.displayUsername)(usersState.profiles[teammateId], teammateNameDisplay, false), teammate_id: teammateId, status: usersState.statuses[teammateId] || 'offline', }; } else if (isGroupChannel(channel)) { return completeDirectGroupInfo(usersState, teammateNameDisplay, channel); } return channel; } function splitRoles(roles) { return roles ? new Set(roles.split(' ')) : new Set([]); } // newCompleteDirectChannelInfo is a variant of completeDirectChannelInfo that accepts the minimal // data required instead of depending on the entirety of state.entities.users. This allows the // calling selector to have fewer dependencies, reducing its need to recompute when memoized. // // Ideally, this would replace completeDirectChannelInfo altogether, but is currently factored out // to minimize changes while addressing a critical performance issue. function newCompleteDirectChannelInfo(currentUserId, profiles, profilesInChannel, teammateStatus, teammateNameDisplay, channel) { if (isDirectChannel(channel)) { const teammateId = getUserIdFromChannelName(currentUserId, channel.name); // return empty string instead of `someone` default string for display_name return { ...channel, display_name: (0, user_utils_1.displayUsername)(profiles[teammateId], teammateNameDisplay, false), teammate_id: teammateId, status: teammateStatus, }; } else if (isGroupChannel(channel)) { return newCompleteDirectGroupInfo(currentUserId, profiles, profilesInChannel, teammateNameDisplay, channel); } return channel; } function completeDirectChannelDisplayName(currentUserId, profiles, userIdsInChannel, teammateNameDisplay, channel) { if (isDirectChannel(channel)) { const dmChannelClone = { ...channel }; const teammateId = getUserIdFromChannelName(currentUserId, channel.name); return Object.assign(dmChannelClone, { display_name: (0, user_utils_1.displayUsername)(profiles[teammateId], teammateNameDisplay) }); } else if (isGroupChannel(channel) && userIdsInChannel && userIdsInChannel.size > 0) { const displayName = getGroupDisplayNameFromUserIds(userIdsInChannel, profiles, currentUserId, teammateNameDisplay); return { ...channel, display_name: displayName }; } return channel; } function cleanUpUrlable(input) { let cleaned = input.trim().replace(/-/g, ' ').replace(/[^\w\s]/gi, '').toLowerCase().replace(/\s/g, '-'); cleaned = cleaned.replace(/-{2,}/, '-'); cleaned = cleaned.replace(/^-+/, ''); cleaned = cleaned.replace(/-+$/, ''); return cleaned; } function getChannelByName(channels, name) { return Object.values(channels).find((channel) => channel.name === name); } function getDirectChannelName(id, otherId) { let handle; if (otherId > id) { handle = id + '__' + otherId; } else { handle = otherId + '__' + id; } return handle; } function getUserIdFromChannelName(userId, channelName) { const ids = channelName.split('__'); let otherUserId = ''; if (ids[0] === userId) { otherUserId = ids[1]; } else { otherUserId = ids[0]; } return otherUserId; } function isDirectChannel(channel) { return channel.type === constants_1.General.DM_CHANNEL; } function isGroupChannel(channel) { return channel.type === constants_1.General.GM_CHANNEL; } function getChannelsIdForTeam(state, teamId) { const { channels } = state.entities.channels; return Object.keys(channels).map((key) => channels[key]).reduce((res, channel) => { if (channel.team_id === teamId) { res.push(channel.id); } return res; }, []); } function getGroupDisplayNameFromUserIds(userIds, profiles, currentUserId, teammateNameDisplay, omitCurrentUser = true) { const names = []; userIds.forEach((id) => { if (!(id === currentUserId && omitCurrentUser)) { names.push((0, user_utils_1.displayUsername)(profiles[id], teammateNameDisplay)); } }); function sortUsernames(a, b) { const locale = getUserLocale(currentUserId, profiles); return a.localeCompare(b, locale, { numeric: true }); } return names.sort(sortUsernames).join(', '); } function isDefault(channel) { return channel?.name === constants_1.General.DEFAULT_CHANNEL; } function completeDirectGroupInfo(usersState, teammateNameDisplay, channel, omitCurrentUser = true) { const { currentUserId, profiles, profilesInChannel } = usersState; const profilesIds = profilesInChannel[channel.id]; const gm = { ...channel }; if (profilesIds) { // sometimes the current user is not part of the profilesInChannel if (!omitCurrentUser) { profilesIds.add(currentUserId); } gm.display_name = getGroupDisplayNameFromUserIds(profilesIds, profiles, currentUserId, teammateNameDisplay, omitCurrentUser); return gm; } const usernames = gm.display_name.split(', '); const users = Object.keys(profiles).map((key) => profiles[key]); const userIds = new Set(); usernames.forEach((username) => { const u = users.find((p) => p.username === username); if (u) { userIds.add(u.id); } }); if (usernames.length === userIds.size) { gm.display_name = getGroupDisplayNameFromUserIds(userIds, profiles, currentUserId, teammateNameDisplay); return gm; } return channel; } // newCompleteDirectGroupInfo is a variant of completeDirectGroupInfo that accepts the minimal // data required instead of depending on the entirety of state.entities.users. This allows the // calling selector to have fewer dependencies, reducing its need to recompute when memoized. // // See also newCompleteDirectChannelInfo. function newCompleteDirectGroupInfo(currentUserId, profiles, profilesInChannel, teammateNameDisplay, channel) { const profilesIds = profilesInChannel[channel.id]; const gm = { ...channel }; if (profilesIds) { gm.display_name = getGroupDisplayNameFromUserIds(profilesIds, profiles, currentUserId, teammateNameDisplay); return gm; } const usernames = gm.display_name.split(', '); const users = Object.keys(profiles).map((key) => profiles[key]); const userIds = new Set(); usernames.forEach((username) => { const u = users.find((p) => p.username === username); if (u) { userIds.add(u.id); } }); if (usernames.length === userIds.size) { gm.display_name = getGroupDisplayNameFromUserIds(userIds, profiles, currentUserId, teammateNameDisplay); return gm; } return channel; } function isOpenChannel(channel) { return channel.type === constants_1.General.OPEN_CHANNEL; } function isPrivateChannel(channel) { return channel.type === constants_1.General.PRIVATE_CHANNEL; } function sortChannelsByTypeListAndDisplayName(locale, typeList, a, b) { const idxA = typeList.indexOf(a.type); const idxB = typeList.indexOf(b.type); if (idxA === -1 && idxB !== -1) { return 1; } if (idxB === -1 && idxA !== -1) { return -1; } if (idxA !== idxB) { if (idxA < idxB) { return -1; } return 1; } const aDisplayName = filterName(a.display_name); const bDisplayName = filterName(b.display_name); if (aDisplayName !== bDisplayName) { return aDisplayName.toLowerCase().localeCompare(bDisplayName.toLowerCase(), locale, { numeric: true }); } return a.name.toLowerCase().localeCompare(b.name.toLowerCase(), locale, { numeric: true }); } function sortChannelsByTypeAndDisplayName(locale, a, b) { if (channelTypeOrder[a.type] !== channelTypeOrder[b.type]) { if (channelTypeOrder[a.type] < channelTypeOrder[b.type]) { return -1; } return 1; } const aDisplayName = filterName(a.display_name); const bDisplayName = filterName(b.display_name); if (aDisplayName !== bDisplayName) { return aDisplayName.toLowerCase().localeCompare(bDisplayName.toLowerCase(), locale, { numeric: true }); } return a.name.toLowerCase().localeCompare(b.name.toLowerCase(), locale, { numeric: true }); } function filterName(name) { return name.replace(/[.,'"\/#!$%\^&\*;:{}=\-_`~()]/g, ''); // eslint-disable-line no-useless-escape } function sortChannelsByDisplayName(locale, a, b) { // if both channels have the display_name defined if (a.display_name && b.display_name && a.display_name !== b.display_name) { return a.display_name.toLowerCase().localeCompare(b.display_name.toLowerCase(), locale, { numeric: true }); } return a.name.toLowerCase().localeCompare(b.name.toLowerCase(), locale, { numeric: true }); } function sortChannelsByDisplayNameAndMuted(locale, members, a, b) { const aMember = members[a.id]; const bMember = members[b.id]; if (isChannelMuted(bMember) === isChannelMuted(aMember)) { return sortChannelsByDisplayName(locale, a, b); } if (!isChannelMuted(bMember) && isChannelMuted(aMember)) { return 1; } return -1; } function sortChannelsByRecency(lastPosts, a, b) { let aLastPostAt = a.last_post_at; if (lastPosts[a.id] && lastPosts[a.id].create_at > a.last_post_at) { aLastPostAt = lastPosts[a.id].create_at; } let bLastPostAt = b.last_post_at; if (lastPosts[b.id] && lastPosts[b.id].create_at > b.last_post_at) { bLastPostAt = lastPosts[b.id].create_at; } return bLastPostAt - aLastPostAt; } function isChannelMuted(member) { return member?.notify_props ? (member.notify_props.mark_unread === channels_1.MarkUnread.MENTION) : false; } function areChannelMentionsIgnored(channelMemberNotifyProps, currentUserNotifyProps) { let ignoreChannelMentionsDefault = constants_1.Users.IGNORE_CHANNEL_MENTIONS_OFF; if (currentUserNotifyProps.channel && currentUserNotifyProps.channel === 'false') { ignoreChannelMentionsDefault = constants_1.Users.IGNORE_CHANNEL_MENTIONS_ON; } let ignoreChannelMentions = channelMemberNotifyProps && channelMemberNotifyProps.ignore_channel_mentions; if (!ignoreChannelMentions || ignoreChannelMentions === constants_1.Users.IGNORE_CHANNEL_MENTIONS_DEFAULT) { ignoreChannelMentions = ignoreChannelMentionsDefault; } return ignoreChannelMentions !== constants_1.Users.IGNORE_CHANNEL_MENTIONS_OFF; } function getUserLocale(userId, profiles) { let locale = constants_1.General.DEFAULT_LOCALE; if (profiles && profiles[userId] && profiles[userId].locale) { locale = profiles[userId].locale; } return locale; } function filterChannelsMatchingTerm(channels, term) { const lowercasedTerm = term.toLowerCase(); return channels.filter((channel) => { if (!channel) { return false; } const name = (channel.name || '').toLowerCase(); const displayName = (channel.display_name || '').toLowerCase(); return name.startsWith(lowercasedTerm) || displayName.startsWith(lowercasedTerm); }); } function channelListToMap(channelList) { const channels = {}; for (let i = 0; i < channelList.length; i++) { channels[channelList[i].id] = channelList[i]; } return channels; } // calculateUnreadCount returns an object containing the number of unread mentions/mesasges in a channel and whether // or not that channel would be shown as unread in the sidebar. function calculateUnreadCount(messageCount, member, crtEnabled) { if (!member || !messageCount) { return { showUnread: false, hasUrgent: false, mentions: 0, messages: 0, }; } let messages; let mentions; let hasUrgent = false; if (crtEnabled) { messages = messageCount.root - member.msg_count_root; mentions = member.mention_count_root; } else { mentions = member.mention_count; messages = messageCount.total - member.msg_count; } if (member.urgent_mention_count) { hasUrgent = true; } return { showUnread: mentions > 0 || (!isChannelMuted(member) && messages > 0), messages, mentions, hasUrgent, }; }