PLT-3077 Add group messaging (#5489)
* Implement server changes for group messaging * Majority of client-side implementation * Some server updates * Added new React multiselect component * Fix style issues * Add custom renderer for options * Fix model test * Update ENTER functionality for multiselect control * Remove buttons from multiselect UI control * Updating group messaging UI (#5524) * Move filter controls up a component level * Scroll with arrow keys * Updating mobile layout for multiselect (#5534) * Fix race condition when backspacing quickly * Hidden or new GMs show up for regular messages * Add overriding of number remaining text * Add UI filtering for team if config setting set * Add icon to channel switcher and class prop to status icon * Minor updates per feedback * Improving group messaging UI (#5563) * UX changes per feedback * Update email for group messages * UI fixes for group messaging (#5587) * Fix missing localization string * Add maximum users message when adding members to GM * Fix input clearing on Android * Updating group messaging UI (#5603) * Updating UI for group messaging (#5604)
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
8c5cee9521
Коммит
3a91d4e5e4
@@ -9,7 +9,7 @@ import ChannelStore from 'stores/channel_store.jsx';
|
||||
import * as ChannelUtils from 'utils/channel_utils.jsx';
|
||||
import PreferenceStore from 'stores/preference_store.jsx';
|
||||
|
||||
import {loadProfilesAndTeamMembersForDMSidebar} from 'actions/user_actions.jsx';
|
||||
import {loadProfilesForSidebar} from 'actions/user_actions.jsx';
|
||||
import {trackEvent} from 'actions/diagnostics_actions.jsx';
|
||||
|
||||
import Client from 'client/web_client.jsx';
|
||||
@@ -22,8 +22,12 @@ import {browserHistory} from 'react-router/es6';
|
||||
|
||||
export function goToChannel(channel) {
|
||||
if (channel.fake) {
|
||||
const user = UserStore.getProfileByUsername(channel.display_name);
|
||||
if (!user) {
|
||||
return;
|
||||
}
|
||||
openDirectChannelToUser(
|
||||
UserStore.getProfileByUsername(channel.display_name),
|
||||
user.id,
|
||||
() => {
|
||||
browserHistory.push(TeamStore.getCurrentTeamRelativeUrl() + '/channels/' + channel.name);
|
||||
},
|
||||
@@ -167,18 +171,18 @@ export function makeUserChannelMember(channelId, userId, success, error) {
|
||||
);
|
||||
}
|
||||
|
||||
export function openDirectChannelToUser(user, success, error) {
|
||||
const channelName = Utils.getDirectChannelName(UserStore.getCurrentId(), user.id);
|
||||
export function openDirectChannelToUser(userId, success, error) {
|
||||
const channelName = Utils.getDirectChannelName(UserStore.getCurrentId(), userId);
|
||||
const channel = ChannelStore.getByName(channelName);
|
||||
|
||||
if (channel) {
|
||||
trackEvent('api', 'api_channels_join_direct');
|
||||
PreferenceStore.setPreference(Preferences.CATEGORY_DIRECT_CHANNEL_SHOW, user.id, 'true');
|
||||
loadProfilesAndTeamMembersForDMSidebar();
|
||||
PreferenceStore.setPreference(Preferences.CATEGORY_DIRECT_CHANNEL_SHOW, userId, 'true');
|
||||
loadProfilesForSidebar();
|
||||
|
||||
AsyncClient.savePreference(
|
||||
Preferences.CATEGORY_DIRECT_CHANNEL_SHOW,
|
||||
user.id,
|
||||
userId,
|
||||
'true'
|
||||
);
|
||||
|
||||
@@ -190,7 +194,7 @@ export function openDirectChannelToUser(user, success, error) {
|
||||
}
|
||||
|
||||
Client.createDirectChannel(
|
||||
user.id,
|
||||
userId,
|
||||
(data) => {
|
||||
Client.getChannel(
|
||||
data.id,
|
||||
@@ -201,12 +205,12 @@ export function openDirectChannelToUser(user, success, error) {
|
||||
member: data2.member
|
||||
});
|
||||
|
||||
PreferenceStore.setPreference(Preferences.CATEGORY_DIRECT_CHANNEL_SHOW, user.id, 'true');
|
||||
loadProfilesAndTeamMembersForDMSidebar();
|
||||
PreferenceStore.setPreference(Preferences.CATEGORY_DIRECT_CHANNEL_SHOW, userId, 'true');
|
||||
loadProfilesForSidebar();
|
||||
|
||||
AsyncClient.savePreference(
|
||||
Preferences.CATEGORY_DIRECT_CHANNEL_SHOW,
|
||||
user.id,
|
||||
userId,
|
||||
'true'
|
||||
);
|
||||
|
||||
@@ -225,6 +229,43 @@ export function openDirectChannelToUser(user, success, error) {
|
||||
);
|
||||
}
|
||||
|
||||
export function openGroupChannelToUsers(userIds, success, error) {
|
||||
Client.createGroupChannel(
|
||||
userIds,
|
||||
(data) => {
|
||||
Client.getChannelMember(
|
||||
data.id,
|
||||
UserStore.getCurrentId(),
|
||||
(data2) => {
|
||||
AppDispatcher.handleServerAction({
|
||||
type: ActionTypes.RECEIVED_CHANNEL,
|
||||
channel: data,
|
||||
member: data2
|
||||
});
|
||||
|
||||
PreferenceStore.setPreference(Preferences.CATEGORY_GROUP_CHANNEL_SHOW, data.id, 'true');
|
||||
loadProfilesForSidebar();
|
||||
|
||||
AsyncClient.savePreference(
|
||||
Preferences.CATEGORY_GROUP_CHANNEL_SHOW,
|
||||
data.id,
|
||||
'true'
|
||||
);
|
||||
|
||||
if (success) {
|
||||
success(data);
|
||||
}
|
||||
}
|
||||
);
|
||||
},
|
||||
() => {
|
||||
if (error) {
|
||||
error();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export function markFavorite(channelId) {
|
||||
trackEvent('api', 'api_channels_favorited');
|
||||
AsyncClient.savePreference(Preferences.CATEGORY_FAVORITE_CHANNEL, channelId, 'true');
|
||||
|
||||
@@ -13,7 +13,7 @@ import PreferenceStore from 'stores/preference_store.jsx';
|
||||
import SearchStore from 'stores/search_store.jsx';
|
||||
|
||||
import {handleNewPost, loadPosts, loadPostsBefore, loadPostsAfter} from 'actions/post_actions.jsx';
|
||||
import {loadProfilesAndTeamMembersForDMSidebar} from 'actions/user_actions.jsx';
|
||||
import {loadProfilesForSidebar} from 'actions/user_actions.jsx';
|
||||
import {loadChannelsForCurrentUser} from 'actions/channel_actions.jsx';
|
||||
import {stopPeriodicStatusUpdates} from 'actions/status_actions.jsx';
|
||||
import * as WebsocketActions from 'actions/websocket_actions.jsx';
|
||||
@@ -387,7 +387,7 @@ export function emitPreferenceChangedEvent(preference) {
|
||||
});
|
||||
|
||||
if (preference.category === Constants.Preferences.CATEGORY_DIRECT_CHANNEL_SHOW) {
|
||||
loadProfilesAndTeamMembersForDMSidebar();
|
||||
loadProfilesForSidebar();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import PostStore from 'stores/post_store.jsx';
|
||||
import UserStore from 'stores/user_store.jsx';
|
||||
|
||||
import {loadStatusesForChannel} from 'actions/status_actions.jsx';
|
||||
import {loadNewDMIfNeeded} from 'actions/user_actions.jsx';
|
||||
import {loadNewDMIfNeeded, loadNewGMIfNeeded} from 'actions/user_actions.jsx';
|
||||
import {trackEvent} from 'actions/diagnostics_actions.jsx';
|
||||
|
||||
import Client from 'client/web_client.jsx';
|
||||
@@ -24,8 +24,12 @@ export function handleNewPost(post, msg) {
|
||||
websocketMessageProps = msg.data;
|
||||
}
|
||||
|
||||
if (msg && msg.data && msg.data.channel_type === Constants.DM_CHANNEL) {
|
||||
loadNewDMIfNeeded(post.user_id);
|
||||
if (msg && msg.data) {
|
||||
if (msg.data.channel_type === Constants.DM_CHANNEL) {
|
||||
loadNewDMIfNeeded(post.user_id);
|
||||
} else if (msg.data.channel_type === Constants.GM_CHANNEL) {
|
||||
loadNewGMIfNeeded(post.channel_id, post.user_id);
|
||||
}
|
||||
}
|
||||
|
||||
if (post.root_id && PostStore.getPost(post.channel_id, post.root_id) == null) {
|
||||
|
||||
@@ -225,63 +225,150 @@ function populateDMChannelsWithProfiles(userIds) {
|
||||
}
|
||||
}
|
||||
|
||||
function populateChannelWithProfiles(channelId, userIds) {
|
||||
for (let i = 0; i < userIds.length; i++) {
|
||||
UserStore.saveUserIdInChannel(channelId, userIds[i]);
|
||||
}
|
||||
UserStore.emitInChannelChange();
|
||||
}
|
||||
|
||||
export function loadNewDMIfNeeded(userId) {
|
||||
if (userId === UserStore.getCurrentId()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const pref = PreferenceStore.get(Preferences.CATEGORY_DIRECT_CHANNEL_SHOW, userId, 'false');
|
||||
if (pref === 'false') {
|
||||
const pref = PreferenceStore.getBool(Preferences.CATEGORY_DIRECT_CHANNEL_SHOW, userId, false);
|
||||
if (pref === false) {
|
||||
PreferenceStore.setPreference(Preferences.CATEGORY_DIRECT_CHANNEL_SHOW, userId, 'true');
|
||||
AsyncClient.savePreference(Preferences.CATEGORY_DIRECT_CHANNEL_SHOW, userId, 'true');
|
||||
loadProfilesAndTeamMembersForDMSidebar();
|
||||
loadProfilesForDM();
|
||||
}
|
||||
}
|
||||
|
||||
export function loadProfilesAndTeamMembersForDMSidebar() {
|
||||
const dmPrefs = PreferenceStore.getCategory(Preferences.CATEGORY_DIRECT_CHANNEL_SHOW);
|
||||
const teamId = TeamStore.getCurrentId();
|
||||
const profilesToLoad = [];
|
||||
const membersToLoad = [];
|
||||
export function loadNewGMIfNeeded(channelId, userId) {
|
||||
if (userId === UserStore.getCurrentId()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const [key, value] of dmPrefs) {
|
||||
if (value === 'true') {
|
||||
if (!UserStore.hasProfile(key)) {
|
||||
profilesToLoad.push(key);
|
||||
}
|
||||
membersToLoad.push(key);
|
||||
function checkPreference() {
|
||||
const pref = PreferenceStore.getBool(Preferences.CATEGORY_GROUP_CHANNEL_SHOW, channelId, false);
|
||||
if (pref === false) {
|
||||
PreferenceStore.setPreference(Preferences.CATEGORY_GROUP_CHANNEL_SHOW, channelId, 'true');
|
||||
AsyncClient.savePreference(Preferences.CATEGORY_GROUP_CHANNEL_SHOW, channelId, 'true');
|
||||
loadProfilesForGM();
|
||||
}
|
||||
}
|
||||
|
||||
const channelMembers = ChannelStore.getMyMembers();
|
||||
const channel = ChannelStore.get(channelId);
|
||||
if (channel) {
|
||||
checkPreference();
|
||||
} else {
|
||||
Client.getChannel(
|
||||
channelId,
|
||||
(data) => {
|
||||
AppDispatcher.handleServerAction({
|
||||
type: ActionTypes.RECEIVED_CHANNEL,
|
||||
channel: data.channel,
|
||||
member: data.member
|
||||
});
|
||||
|
||||
checkPreference();
|
||||
},
|
||||
(err) => {
|
||||
AsyncClient.dispatchError(err, 'getChannel');
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function loadProfilesForSidebar() {
|
||||
loadProfilesForDM();
|
||||
loadProfilesForGM();
|
||||
}
|
||||
|
||||
export function loadProfilesForGM() {
|
||||
const channels = ChannelStore.getChannels();
|
||||
const newPreferences = [];
|
||||
|
||||
for (let i = 0; i < channels.length; i++) {
|
||||
const channel = channels[i];
|
||||
if (channel.type !== Constants.GM_CHANNEL) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (UserStore.getProfileListInChannel(channel.id).length >= Constants.MIN_USERS_IN_GM) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const isVisible = PreferenceStore.getBool(Preferences.CATEGORY_GROUP_CHANNEL_SHOW, channel.id);
|
||||
|
||||
if (!isVisible) {
|
||||
const member = ChannelStore.getMyMember(channel.id);
|
||||
if (!member || (member.mention_count === 0 && member.msg_count < member.total_msg_count)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
newPreferences.push({
|
||||
user_id: UserStore.getCurrentId(),
|
||||
category: Preferences.CATEGORY_GROUP_CHANNEL_SHOW,
|
||||
name: channel.id,
|
||||
value: 'true'
|
||||
});
|
||||
}
|
||||
|
||||
Client.getProfilesInChannel(
|
||||
channel.id,
|
||||
0,
|
||||
Constants.MAX_USERS_IN_GM,
|
||||
(data) => {
|
||||
AppDispatcher.handleServerAction({
|
||||
type: ActionTypes.RECEIVED_PROFILES,
|
||||
profiles: data
|
||||
});
|
||||
|
||||
populateChannelWithProfiles(channel.id, Object.keys(data));
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if (newPreferences.length > 0) {
|
||||
AsyncClient.savePreferences(newPreferences);
|
||||
}
|
||||
}
|
||||
|
||||
export function loadProfilesForDM() {
|
||||
const channels = ChannelStore.getChannels();
|
||||
const newPreferences = [];
|
||||
const profilesToLoad = [];
|
||||
const profileIds = [];
|
||||
|
||||
for (let i = 0; i < channels.length; i++) {
|
||||
const channel = channels[i];
|
||||
if (channel.type !== Constants.DM_CHANNEL) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const member = channelMembers[channel.id];
|
||||
if (!member) {
|
||||
continue;
|
||||
}
|
||||
const teammateId = channel.name.replace(UserStore.getCurrentId(), '').replace('__', '');
|
||||
const isVisible = PreferenceStore.getBool(Preferences.CATEGORY_DIRECT_CHANNEL_SHOW, teammateId);
|
||||
|
||||
const teammateId = channel.name.replace(member.user_id, '').replace('__', '');
|
||||
if (!isVisible) {
|
||||
const member = ChannelStore.getMyMember(channel.id);
|
||||
if (!member || member.mention_count === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (member.mention_count > 0 && membersToLoad.indexOf(teammateId) === -1) {
|
||||
membersToLoad.push(teammateId);
|
||||
newPreferences.push({
|
||||
user_id: UserStore.getCurrentId(),
|
||||
category: Preferences.CATEGORY_DIRECT_CHANNEL_SHOW,
|
||||
name: teammateId,
|
||||
value: 'true'
|
||||
});
|
||||
|
||||
if (!UserStore.hasProfile(teammateId)) {
|
||||
profilesToLoad.push(teammateId);
|
||||
}
|
||||
}
|
||||
|
||||
if (!UserStore.hasProfile(teammateId)) {
|
||||
profilesToLoad.push(teammateId);
|
||||
}
|
||||
profileIds.push(teammateId);
|
||||
}
|
||||
|
||||
if (newPreferences.length > 0) {
|
||||
@@ -298,44 +385,14 @@ export function loadProfilesAndTeamMembersForDMSidebar() {
|
||||
});
|
||||
|
||||
// Use membersToLoad so we get all the DM profiles even if they were already loaded
|
||||
populateDMChannelsWithProfiles(membersToLoad);
|
||||
populateDMChannelsWithProfiles(profileIds);
|
||||
},
|
||||
(err) => {
|
||||
AsyncClient.dispatchError(err, 'getProfilesByIds');
|
||||
}
|
||||
);
|
||||
} else {
|
||||
populateDMChannelsWithProfiles(membersToLoad);
|
||||
}
|
||||
|
||||
if (membersToLoad.length > 0) {
|
||||
Client.getTeamMembersByIds(
|
||||
teamId,
|
||||
membersToLoad,
|
||||
(data) => {
|
||||
const memberMap = {};
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
memberMap[data[i].user_id] = data[i];
|
||||
}
|
||||
|
||||
const nonMembersMap = {};
|
||||
for (let i = 0; i < membersToLoad.length; i++) {
|
||||
if (!memberMap[membersToLoad[i]]) {
|
||||
nonMembersMap[membersToLoad[i]] = true;
|
||||
}
|
||||
}
|
||||
|
||||
AppDispatcher.handleServerAction({
|
||||
type: ActionTypes.RECEIVED_MEMBERS_IN_TEAM,
|
||||
team_id: teamId,
|
||||
team_members: memberMap,
|
||||
non_team_members: nonMembersMap
|
||||
});
|
||||
},
|
||||
(err) => {
|
||||
AsyncClient.dispatchError(err, 'getTeamMembersByIds');
|
||||
}
|
||||
);
|
||||
populateDMChannelsWithProfiles(profileIds);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -801,3 +858,27 @@ export function uploadProfileImage(userPicture, success, error) {
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export function loadProfiles(offset = UserStore.getPagingOffset(), limit = Constants.PROFILE_CHUNK_SIZE, success, error) {
|
||||
Client.getProfiles(
|
||||
offset,
|
||||
limit,
|
||||
(data) => {
|
||||
AppDispatcher.handleServerAction({
|
||||
type: ActionTypes.RECEIVED_PROFILES,
|
||||
profiles: data
|
||||
});
|
||||
|
||||
if (success) {
|
||||
success(data);
|
||||
}
|
||||
},
|
||||
(err) => {
|
||||
AsyncClient.dispatchError(err, 'getProfiles');
|
||||
|
||||
if (error) {
|
||||
error(err);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ import {getSiteURL} from 'utils/url.jsx';
|
||||
|
||||
import * as GlobalActions from 'actions/global_actions.jsx';
|
||||
import {handleNewPost, loadPosts, loadProfilesForPosts} from 'actions/post_actions.jsx';
|
||||
import {loadProfilesAndTeamMembersForDMSidebar} from 'actions/user_actions.jsx';
|
||||
import {loadProfilesForSidebar} from 'actions/user_actions.jsx';
|
||||
import {loadChannelsForCurrentUser} from 'actions/channel_actions.jsx';
|
||||
import * as StatusActions from 'actions/status_actions.jsx';
|
||||
|
||||
@@ -244,7 +244,7 @@ function handleUpdateTeamEvent(msg) {
|
||||
function handleDirectAddedEvent(msg) {
|
||||
AsyncClient.getChannel(msg.broadcast.channel_id);
|
||||
PreferenceStore.setPreference(Preferences.CATEGORY_DIRECT_CHANNEL_SHOW, msg.data.teammate_id, 'true');
|
||||
loadProfilesAndTeamMembersForDMSidebar();
|
||||
loadProfilesForSidebar();
|
||||
}
|
||||
|
||||
function handleUserAddedEvent(msg) {
|
||||
|
||||
Ссылка в новой задаче
Block a user