mattermost-redux
Version:
Common code (API client, Redux stores, logic, utility functions) for building a Mattermost client
355 lines (354 loc) • 14.8 kB
JavaScript
;
// 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,
};
}