summaryrefslogtreecommitdiffstats
path: root/webapp/utils
diff options
context:
space:
mode:
authorJoram Wilander <jwawilander@gmail.com>2017-03-02 17:48:56 -0500
committerGitHub <noreply@github.com>2017-03-02 17:48:56 -0500
commit3a91d4e5e419a43ff19a0736ce697f8d611d36e3 (patch)
treee57ad85d49f8768a575f27c89d338a4ccaeda521 /webapp/utils
parent8c5cee9521656bcffb371aad9dae4bea8fc70e29 (diff)
downloadchat-3a91d4e5e419a43ff19a0736ce697f8d611d36e3.tar.gz
chat-3a91d4e5e419a43ff19a0736ce697f8d611d36e3.tar.bz2
chat-3a91d4e5e419a43ff19a0736ce697f8d611d36e3.zip
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)
Diffstat (limited to 'webapp/utils')
-rw-r--r--webapp/utils/channel_intro_messages.jsx63
-rw-r--r--webapp/utils/channel_utils.jsx54
-rw-r--r--webapp/utils/constants.jsx5
-rw-r--r--webapp/utils/utils.jsx4
4 files changed, 102 insertions, 24 deletions
diff --git a/webapp/utils/channel_intro_messages.jsx b/webapp/utils/channel_intro_messages.jsx
index 991bf54e8..390ce6d28 100644
--- a/webapp/utils/channel_intro_messages.jsx
+++ b/webapp/utils/channel_intro_messages.jsx
@@ -23,8 +23,10 @@ export function createChannelIntroMessage(channel, fullWidthIntro) {
centeredIntro = 'channel-intro--centered';
}
- if (channel.type === 'D') {
+ if (channel.type === Constants.DM_CHANNEL) {
return createDMIntroMessage(channel, centeredIntro);
+ } else if (channel.type === Constants.GM_CHANNEL) {
+ return createGMIntroMessage(channel, centeredIntro);
} else if (ChannelStore.isDefault(channel)) {
return createDefaultIntroMessage(channel, centeredIntro);
} else if (channel.name === Constants.OFFTOPIC_CHANNEL) {
@@ -35,6 +37,65 @@ export function createChannelIntroMessage(channel, fullWidthIntro) {
return null;
}
+export function createGMIntroMessage(channel, centeredIntro) {
+ const profiles = UserStore.getProfileListInChannel(channel.id, true);
+
+ if (profiles.length > 0) {
+ const pictures = [];
+ let names = '';
+ for (let i = 0; i < profiles.length; i++) {
+ const profile = profiles[i];
+
+ pictures.push(
+ <ProfilePicture
+ key={'introprofilepicture' + profile.id}
+ src={Client.getUsersRoute() + '/' + profile.id + '/image?time=' + profile.last_picture_update}
+ width='50'
+ height='50'
+ user={profile}
+ />
+ );
+
+ if (i === profiles.length - 1) {
+ names += Utils.displayUsernameForUser(profile);
+ } else if (i === profiles.length - 2) {
+ names += Utils.displayUsernameForUser(profile) + ' and ';
+ } else {
+ names += Utils.displayUsernameForUser(profile) + ', ';
+ }
+ }
+
+ return (
+ <div className={'channel-intro ' + centeredIntro}>
+ <div className='post-profile-img__container channel-intro-img'>
+ {pictures}
+ </div>
+ <p className='channel-intro-text'>
+ <FormattedHTMLMessage
+ id='intro_messages.GM'
+ defaultMessage='This is the start of your group message history with {names}.<br />Messages and files shared here are not shown to people outside this area.'
+ values={{
+ names
+ }}
+ />
+ </p>
+ {createSetHeaderButton(channel)}
+ </div>
+ );
+ }
+
+ return (
+ <div className={'channel-intro ' + centeredIntro}>
+ <p className='channel-intro-text'>
+ <FormattedMessage
+ id='intro_messages.group_message'
+ defaultMessage='This is the start of your group message history with these teammates. Messages and files shared here are not shown to people outside this area.'
+ />
+ </p>
+ </div>
+ );
+}
+
export function createDMIntroMessage(channel, centeredIntro) {
var teammate = Utils.getDirectTeammate(channel.id);
diff --git a/webapp/utils/channel_utils.jsx b/webapp/utils/channel_utils.jsx
index 22c428cb8..2bb30af5c 100644
--- a/webapp/utils/channel_utils.jsx
+++ b/webapp/utils/channel_utils.jsx
@@ -5,7 +5,6 @@ const Preferences = Constants.Preferences;
import * as Utils from 'utils/utils.jsx';
import UserStore from 'stores/user_store.jsx';
-import TeamStore from 'stores/team_store.jsx';
import PreferenceStore from 'stores/preference_store.jsx';
import LocalizationStore from 'stores/localization_store.jsx';
@@ -15,30 +14,28 @@ import LocalizationStore from 'stores/localization_store.jsx';
* Example: {
* publicChannels: [...],
* privateChannels: [...],
- * directChannels: [...],
- * directNonTeamChannels: [...],
+ * directAndGroupChannels: [...],
* favoriteChannels: [...]
* }
*/
export function buildDisplayableChannelList(persistentChannels) {
- const missingDMChannels = createMissingDirectChannels(persistentChannels);
+ const missingDirectChannels = createMissingDirectChannels(persistentChannels);
const channels = persistentChannels.
- concat(missingDMChannels).
+ concat(missingDirectChannels).
map(completeDirectChannelInfo).
filter(isNotDeletedChannel).
sort(sortChannelsByDisplayName);
const favoriteChannels = channels.filter(isFavoriteChannel);
const notFavoriteChannels = channels.filter(not(isFavoriteChannel));
- const directChannels = notFavoriteChannels.filter(andX(isDirectChannel, isDirectChannelVisible));
+ const directAndGroupChannels = notFavoriteChannels.filter(orX(andX(isGroupChannel, isGroupChannelVisible), andX(isDirectChannel, isDirectChannelVisible)));
return {
favoriteChannels,
publicChannels: notFavoriteChannels.filter(isOpenChannel),
privateChannels: notFavoriteChannels.filter(isPrivateChannel),
- directChannels: directChannels.filter(isConnectedToTeamMember),
- directNonTeamChannels: directChannels.filter(isNotConnectedToTeamMember)
+ directAndGroupChannels
};
}
@@ -62,6 +59,14 @@ export function isPrivateChannel(channel) {
return channel.type === Constants.PRIVATE_CHANNEL;
}
+export function isGroupChannel(channel) {
+ return channel.type === Constants.GM_CHANNEL;
+}
+
+export function isGroupChannelVisible(channel) {
+ return PreferenceStore.getBool(Preferences.CATEGORY_GROUP_CHANNEL_SHOW, channel.id);
+}
+
export function isDirectChannel(channel) {
return channel.type === Constants.DM_CHANNEL;
}
@@ -88,12 +93,12 @@ export function completeDirectChannelInfo(channel) {
}
const defaultPrefix = 'D'; // fallback for future types
-const typeToPrefixMap = {[Constants.OPEN_CHANNEL]: 'A', [Constants.PRIVATE_CHANNEL]: 'B', [Constants.DM_CHANNEL]: 'C'};
+const typeToPrefixMap = {[Constants.OPEN_CHANNEL]: 'A', [Constants.PRIVATE_CHANNEL]: 'B', [Constants.DM_CHANNEL]: 'C', [Constants.GM_CHANNEL]: 'C'};
export function sortChannelsByDisplayName(a, b) {
const locale = LocalizationStore.getLocale();
- if (a.type !== b.type) {
+ if (a.type !== b.type && typeToPrefixMap[a.type] !== typeToPrefixMap[b.type]) {
return (typeToPrefixMap[a.type] || defaultPrefix).localeCompare((typeToPrefixMap[b.type] || defaultPrefix), locale);
}
@@ -186,6 +191,19 @@ export function showDeleteOption(channel, isAdmin, isSystemAdmin, isChannelAdmin
return true;
}
+export function buildGroupChannelName(channelId) {
+ const profiles = UserStore.getProfileListInChannel(channelId, true);
+ let displayName = '';
+ for (let i = 0; i < profiles.length; i++) {
+ displayName += Utils.displayUsernameForUser(profiles[i]);
+ if (i !== profiles.length - 1) {
+ displayName += ', ';
+ }
+ }
+
+ return displayName;
+}
+
/*
* not exported helpers
*/
@@ -215,22 +233,14 @@ function createFakeChannelCurried(userId) {
return (otherUserId) => createFakeChannel(userId, otherUserId);
}
-function isConnectedToTeamMember(channel) {
- return isTeamMember(channel.teammate_id);
-}
-
-function isTeamMember(userId) {
- return TeamStore.hasActiveMemberInTeam(TeamStore.getCurrentId(), userId);
-}
-
-function isNotConnectedToTeamMember(channel) {
- return TeamStore.hasMemberNotInTeam(TeamStore.getCurrentId(), channel.teammate_id);
-}
-
function not(f) {
return (...args) => !f(...args);
}
+function orX(...fns) {
+ return (...args) => fns.some((f) => f(...args));
+}
+
function andX(...fns) {
return (...args) => fns.every((f) => f(...args));
}
diff --git a/webapp/utils/constants.jsx b/webapp/utils/constants.jsx
index 0f3e217b9..fafad9f44 100644
--- a/webapp/utils/constants.jsx
+++ b/webapp/utils/constants.jsx
@@ -35,6 +35,7 @@ import windows10ThemeImage from 'images/themes/windows_dark.png';
export const Preferences = {
CATEGORY_DIRECT_CHANNEL_SHOW: 'direct_channel_show',
+ CATEGORY_GROUP_CHANNEL_SHOW: 'group_channel_show',
CATEGORY_DISPLAY_SETTINGS: 'display_settings',
DISPLAY_PREFER_NICKNAME: 'nickname_full_name',
DISPLAY_PREFER_FULL_NAME: 'full_name',
@@ -164,6 +165,7 @@ export const ActionTypes = keyMirror({
TOGGLE_GET_POST_LINK_MODAL: null,
TOGGLE_GET_TEAM_INVITE_LINK_MODAL: null,
TOGGLE_GET_PUBLIC_LINK_MODAL: null,
+ TOGGLE_DM_MODAL: null,
SUGGESTION_PRETEXT_CHANGED: null,
SUGGESTION_RECEIVED_SUGGESTIONS: null,
@@ -390,8 +392,11 @@ export const Constants = {
],
MONTHS: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
MAX_DMS: 20,
+ MAX_USERS_IN_GM: 8,
+ MIN_USERS_IN_GM: 3,
MAX_CHANNEL_POPOVER_COUNT: 100,
DM_CHANNEL: 'D',
+ GM_CHANNEL: 'G',
OPEN_CHANNEL: 'O',
PRIVATE_CHANNEL: 'P',
INVITE_TEAM: 'I',
diff --git a/webapp/utils/utils.jsx b/webapp/utils/utils.jsx
index 3d7941158..7573eb887 100644
--- a/webapp/utils/utils.jsx
+++ b/webapp/utils/utils.jsx
@@ -477,6 +477,7 @@ export function applyTheme(theme) {
changeCss('.sidebar--left .nav-pills__container li>h4, .sidebar--left .add-channel-btn', 'color:' + changeOpacity(theme.sidebarText, 0.6));
changeCss('.sidebar--left .add-channel-btn:hover, .sidebar--left .add-channel-btn:focus', 'color:' + theme.sidebarText);
changeCss('.sidebar--left .status .offline--icon', 'fill:' + theme.sidebarText);
+ changeCss('.sidebar--left .status.status--group', 'background:' + changeOpacity(theme.sidebarText, 0.3));
changeCss('@media(max-width: 768px){.app__body .modal .settings-modal .settings-table .nav>li>a, .app__body .sidebar--menu .divider', 'border-color:' + changeOpacity(theme.sidebarText, 0.2));
changeCss('@media(max-width: 768px){.sidebar--left .add-channel-btn:hover, .sidebar--left .add-channel-btn:focus', 'color:' + changeOpacity(theme.sidebarText, 0.6));
}
@@ -569,6 +570,7 @@ export function applyTheme(theme) {
}
if (theme.centerChannelColor) {
+ changeCss('.app__body .mentions__name .status.status--group, .app__body .multi-select__note', 'background:' + changeOpacity(theme.centerChannelColor, 0.12));
changeCss('.app__body .post-list__arrows, .app__body .post .flag-icon__container', 'fill:' + changeOpacity(theme.centerChannelColor, 0.3));
changeCss('.app__body .modal .status .offline--icon, .app__body .channel-header__links .icon, .app__body .sidebar--right .sidebar--right__subheader .usage__icon', 'fill:' + theme.centerChannelColor);
changeCss('@media(min-width: 768px){.app__body .post:hover .post__header .col__reply, .app__body .post.post--hovered .post__header .col__reply', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2));
@@ -618,7 +620,7 @@ export function applyTheme(theme) {
changeCss('@media(max-width: 1800px){.app__body .inner-wrap.move--left .post.post--comment.same--root', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.07));
changeCss('.app__body .post.post--hovered', 'background:' + changeOpacity(theme.centerChannelColor, 0.08));
changeCss('@media(min-width: 768px){.app__body .post:hover, .app__body .more-modal__list .more-modal__row:hover, .app__body .modal .settings-modal .settings-table .settings-content .section-min:hover', 'background:' + changeOpacity(theme.centerChannelColor, 0.08));
- changeCss('.app__body .date-separator.hovered--before:after, .app__body .date-separator.hovered--after:before, .app__body .new-separator.hovered--after:before, .app__body .new-separator.hovered--before:after', 'background:' + changeOpacity(theme.centerChannelColor, 0.07));
+ changeCss('.app__body .more-modal__row.more-modal__row--selected, .app__body .date-separator.hovered--before:after, .app__body .date-separator.hovered--after:before, .app__body .new-separator.hovered--after:before, .app__body .new-separator.hovered--before:after', 'background:' + changeOpacity(theme.centerChannelColor, 0.07));
changeCss('@media(min-width: 768px){.app__body .suggestion-list__content .command:hover, .app__body .mentions__name:hover, .app__body .dropdown-menu>li>a:focus, .app__body .dropdown-menu>li>a:hover', 'background:' + changeOpacity(theme.centerChannelColor, 0.15));
changeCss('.app__body .suggestion--selected, .app__body .bot-indicator', 'background:' + changeOpacity(theme.centerChannelColor, 0.15), 1);
changeCss('code, .app__body .form-control[disabled], .app__body .form-control[readonly], .app__body fieldset[disabled] .form-control', 'background:' + changeOpacity(theme.centerChannelColor, 0.1));