diff options
author | Yi EungJun <semtlenori@gmail.com> | 2016-08-11 23:33:22 +0900 |
---|---|---|
committer | Joram Wilander <jwawilander@gmail.com> | 2016-08-11 10:33:22 -0400 |
commit | d6da7d1220fe520a162560266182dff503b95786 (patch) | |
tree | 19ceb3851fff0bce43c149737e7bcbe15c570e6c | |
parent | bb0b895bad5cddbc0297d0857d10a531a6ca6755 (diff) | |
download | chat-d6da7d1220fe520a162560266182dff503b95786.tar.gz chat-d6da7d1220fe520a162560266182dff503b95786.tar.bz2 chat-d6da7d1220fe520a162560266182dff503b95786.zip |
Change ordering of at-mention suggestions (#3698)
List members in the current channel first.
-rw-r--r-- | webapp/components/suggestion/at_mention_provider.jsx | 110 | ||||
-rw-r--r-- | webapp/components/suggestion/suggestion_list.jsx | 21 | ||||
-rw-r--r-- | webapp/i18n/en.json | 3 | ||||
-rw-r--r-- | webapp/sass/components/_suggestion-list.scss | 30 | ||||
-rw-r--r-- | webapp/utils/constants.jsx | 5 |
5 files changed, 129 insertions, 40 deletions
diff --git a/webapp/components/suggestion/at_mention_provider.jsx b/webapp/components/suggestion/at_mention_provider.jsx index b9127e8d3..87cdc6894 100644 --- a/webapp/components/suggestion/at_mention_provider.jsx +++ b/webapp/components/suggestion/at_mention_provider.jsx @@ -8,6 +8,7 @@ import ChannelStore from 'stores/channel_store.jsx'; import UserStore from 'stores/user_store.jsx'; import * as Utils from 'utils/utils.jsx'; import Client from 'client/web_client.jsx'; +import Constants from 'utils/constants.jsx'; import {FormattedMessage} from 'react-intl'; import Suggestion from './suggestion.jsx'; @@ -98,60 +99,91 @@ class AtMentionSuggestion extends Suggestion { } } +function filterUsersByPrefix(users, prefix, limit) { + const filtered = []; + + for (const id of Object.keys(users)) { + if (filtered.length >= limit) { + break; + } + + const user = users[id]; + + if (user.delete_at > 0) { + continue; + } + + if (user.username.startsWith(prefix) || + (user.first_name && user.first_name.toLowerCase().startsWith(prefix)) || + (user.last_name && user.last_name.toLowerCase().startsWith(prefix)) || + (user.nickname && user.nickname.toLowerCase().startsWith(prefix))) { + filtered.push(user); + } + } + + return filtered; +} + export default class AtMentionProvider { handlePretextChanged(suggestionId, pretext) { const captured = (/@([a-z0-9\-\._]*)$/i).exec(pretext.toLowerCase()); if (captured) { const prefix = captured[1]; + // Group users into members and nonmembers of the channel. const users = UserStore.getActiveOnlyProfiles(true); - - const filtered = []; - - for (const id of Object.keys(users)) { - const user = users[id]; - - if (user.delete_at > 0) { - continue; - } - - if (user.username.startsWith(prefix) || - (user.first_name && user.first_name.toLowerCase().startsWith(prefix)) || - (user.last_name && user.last_name.toLowerCase().startsWith(prefix)) || - (user.nickname && user.nickname.toLowerCase().startsWith(prefix))) { - filtered.push(user); - } - - if (filtered.length >= MaxUserSuggestions) { - break; + const channelMembers = {}; + const extra = ChannelStore.getCurrentExtraInfo(); + for (let i = 0; i < extra.members.length; i++) { + const id = extra.members[i].id; + if (users[id]) { + channelMembers[id] = users[id]; + Reflect.deleteProperty(users, id); } } - + const channelNonmembers = users; + + // Filter users by prefix. + const filteredMembers = filterUsersByPrefix( + channelMembers, prefix, MaxUserSuggestions); + const filteredNonmembers = filterUsersByPrefix( + channelNonmembers, prefix, MaxUserSuggestions - filteredMembers.length); + let filteredSpecialMentions = []; if (!pretext.startsWith('/msg')) { - // add dummy users to represent the @channel and @all special mentions when not using the /msg command - if ('channel'.startsWith(prefix)) { - filtered.push({username: 'channel'}); - } - if ('all'.startsWith(prefix)) { - filtered.push({username: 'all'}); - } - if ('here'.startsWith(prefix)) { - filtered.push({username: 'here'}); - } + filteredSpecialMentions = ['here', 'channel', 'all'].filter((item) => { + return item.startsWith(prefix); + }).map((name) => { + return {username: name}; + }); } - filtered.sort((a, b) => { - const aPrefix = a.username.startsWith(prefix); - const bPrefix = b.username.startsWith(prefix); + // Sort users by username. + [filteredMembers, filteredNonmembers].forEach((items) => { + items.sort((a, b) => { + const aPrefix = a.username.startsWith(prefix); + const bPrefix = b.username.startsWith(prefix); - if (aPrefix === bPrefix) { - return a.username.localeCompare(b.username); - } else if (aPrefix) { - return -1; - } + if (aPrefix === bPrefix) { + return a.username.localeCompare(b.username); + } else if (aPrefix) { + return -1; + } + + return 1; + }); + }); - return 1; + filteredMembers.forEach((item) => { + item.type = Constants.MENTION_MEMBERS; }); + filteredNonmembers.forEach((item) => { + item.type = Constants.MENTION_NONMEMBERS; + }); + filteredSpecialMentions.forEach((item) => { + item.type = Constants.MENTION_SPECIAL; + }); + + const filtered = filteredMembers.concat(filteredSpecialMentions).concat(filteredNonmembers); const mentions = filtered.map((user) => '@' + user.username); diff --git a/webapp/components/suggestion/suggestion_list.jsx b/webapp/components/suggestion/suggestion_list.jsx index 52b85b2f5..7c746ac2a 100644 --- a/webapp/components/suggestion/suggestion_list.jsx +++ b/webapp/components/suggestion/suggestion_list.jsx @@ -5,6 +5,7 @@ import $ from 'jquery'; import ReactDOM from 'react-dom'; import * as GlobalActions from 'actions/global_actions.jsx'; import SuggestionStore from 'stores/suggestion_store.jsx'; +import {FormattedMessage} from 'react-intl'; import React from 'react'; @@ -92,19 +93,39 @@ export default class SuggestionList extends React.Component { } } + renderDivider(type) { + return ( + <div + key={type + '-divider'} + className='suggestion-list__divider' + > + <span> + <FormattedMessage id={'suggestion.' + type}/> + </span> + </div> + ); + } + render() { if (this.state.items.length === 0) { return null; } const items = []; + let lastType; for (let i = 0; i < this.state.items.length; i++) { + const item = this.state.items[i]; const term = this.state.terms[i]; const isSelection = term === this.state.selection; // ReactComponent names need to be upper case when used in JSX const Component = this.state.components[i]; + if (item.type !== lastType) { + items.push(this.renderDivider(item.type)); + lastType = item.type; + } + items.push( <Component key={term} diff --git a/webapp/i18n/en.json b/webapp/i18n/en.json index 986b4d036..9e899754e 100644 --- a/webapp/i18n/en.json +++ b/webapp/i18n/en.json @@ -1538,6 +1538,9 @@ "suggestion.mention.all": "Notifies everyone in the channel, use in {townsquare} to notify the whole team", "suggestion.mention.channel": "Notifies everyone in the channel", "suggestion.mention.here": "Notifies everyone in the channel and online", + "suggestion.mention.members": "Channel Members", + "suggestion.mention.nonmembers": "Not in Channel", + "suggestion.mention.special": "Special Mentions", "suggestion.search.private": "Private Groups", "suggestion.search.public": "Public Channels", "team_export_tab.download": "download", diff --git a/webapp/sass/components/_suggestion-list.scss b/webapp/sass/components/_suggestion-list.scss index 8bee49ce6..c995d5ebf 100644 --- a/webapp/sass/components/_suggestion-list.scss +++ b/webapp/sass/components/_suggestion-list.scss @@ -44,3 +44,33 @@ .suggestion-list--bottom { position: relative; } + +.suggestion-list__divider { + line-height: 21px; + margin: 5px 0px 0px 5px; + position: relative; + + &:first-child { + margin-top: 5px; + } + + > span { + color: rgba(51,51,51,0.7); + background: #f2f4f8; + display: inline-block; + padding-right: 10px; + position: relative; + z-index: 5; + } + + &:before { + @include opacity(.2); + background: $dark-gray; + content: ''; + height: 1px; + left: 0; + position: absolute; + top: 10px; + width: 100%; + } +} diff --git a/webapp/utils/constants.jsx b/webapp/utils/constants.jsx index dbdc3e9f1..24d1d6b19 100644 --- a/webapp/utils/constants.jsx +++ b/webapp/utils/constants.jsx @@ -797,7 +797,10 @@ export const Constants = { LICENSE_GRACE_PERIOD: 1000 * 60 * 60 * 24 * 15, // 15 days PERMISSIONS_ALL: 'all', PERMISSIONS_TEAM_ADMIN: 'team_admin', - PERMISSIONS_SYSTEM_ADMIN: 'system_admin' + PERMISSIONS_SYSTEM_ADMIN: 'system_admin', + MENTION_MEMBERS: 'mention.members', + MENTION_NONMEMBERS: 'mention.nonmembers', + MENTION_SPECIAL: 'mention.special' }; export default Constants; |