Change ordering of at-mention suggestions (#3698)

List members in the current channel first.
This commit is contained in:
Yi EungJun
2016-08-11 23:33:22 +09:00
committed by Joram Wilander
parent bb0b895bad
commit d6da7d1220
5 changed files with 128 additions and 39 deletions

View File

@@ -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,61 +99,92 @@ 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);
SuggestionStore.addSuggestions(suggestionId, mentions, filtered, AtMentionSuggestion, captured[0]);

View File

@@ -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}

View File

@@ -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",

View File

@@ -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%;
}
}

View File

@@ -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;