Change ordering of at-mention suggestions (#3698)
List members in the current channel first.
Этот коммит содержится в:
коммит произвёл
Joram Wilander
родитель
bb0b895bad
Коммит
d6da7d1220
@@ -8,6 +8,7 @@ import ChannelStore from 'stores/channel_store.jsx';
|
|||||||
import UserStore from 'stores/user_store.jsx';
|
import UserStore from 'stores/user_store.jsx';
|
||||||
import * as Utils from 'utils/utils.jsx';
|
import * as Utils from 'utils/utils.jsx';
|
||||||
import Client from 'client/web_client.jsx';
|
import Client from 'client/web_client.jsx';
|
||||||
|
import Constants from 'utils/constants.jsx';
|
||||||
|
|
||||||
import {FormattedMessage} from 'react-intl';
|
import {FormattedMessage} from 'react-intl';
|
||||||
import Suggestion from './suggestion.jsx';
|
import Suggestion from './suggestion.jsx';
|
||||||
@@ -98,17 +99,14 @@ class AtMentionSuggestion extends Suggestion {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class AtMentionProvider {
|
function filterUsersByPrefix(users, prefix, limit) {
|
||||||
handlePretextChanged(suggestionId, pretext) {
|
|
||||||
const captured = (/@([a-z0-9\-\._]*)$/i).exec(pretext.toLowerCase());
|
|
||||||
if (captured) {
|
|
||||||
const prefix = captured[1];
|
|
||||||
|
|
||||||
const users = UserStore.getActiveOnlyProfiles(true);
|
|
||||||
|
|
||||||
const filtered = [];
|
const filtered = [];
|
||||||
|
|
||||||
for (const id of Object.keys(users)) {
|
for (const id of Object.keys(users)) {
|
||||||
|
if (filtered.length >= limit) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
const user = users[id];
|
const user = users[id];
|
||||||
|
|
||||||
if (user.delete_at > 0) {
|
if (user.delete_at > 0) {
|
||||||
@@ -121,26 +119,47 @@ export default class AtMentionProvider {
|
|||||||
(user.nickname && user.nickname.toLowerCase().startsWith(prefix))) {
|
(user.nickname && user.nickname.toLowerCase().startsWith(prefix))) {
|
||||||
filtered.push(user);
|
filtered.push(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (filtered.length >= MaxUserSuggestions) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 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')) {
|
if (!pretext.startsWith('/msg')) {
|
||||||
// add dummy users to represent the @channel and @all special mentions when not using the /msg command
|
filteredSpecialMentions = ['here', 'channel', 'all'].filter((item) => {
|
||||||
if ('channel'.startsWith(prefix)) {
|
return item.startsWith(prefix);
|
||||||
filtered.push({username: 'channel'});
|
}).map((name) => {
|
||||||
}
|
return {username: name};
|
||||||
if ('all'.startsWith(prefix)) {
|
});
|
||||||
filtered.push({username: 'all'});
|
|
||||||
}
|
|
||||||
if ('here'.startsWith(prefix)) {
|
|
||||||
filtered.push({username: 'here'});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
filtered.sort((a, b) => {
|
// Sort users by username.
|
||||||
|
[filteredMembers, filteredNonmembers].forEach((items) => {
|
||||||
|
items.sort((a, b) => {
|
||||||
const aPrefix = a.username.startsWith(prefix);
|
const aPrefix = a.username.startsWith(prefix);
|
||||||
const bPrefix = b.username.startsWith(prefix);
|
const bPrefix = b.username.startsWith(prefix);
|
||||||
|
|
||||||
@@ -152,6 +171,19 @@ export default class AtMentionProvider {
|
|||||||
|
|
||||||
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);
|
const mentions = filtered.map((user) => '@' + user.username);
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import $ from 'jquery';
|
|||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import * as GlobalActions from 'actions/global_actions.jsx';
|
import * as GlobalActions from 'actions/global_actions.jsx';
|
||||||
import SuggestionStore from 'stores/suggestion_store.jsx';
|
import SuggestionStore from 'stores/suggestion_store.jsx';
|
||||||
|
import {FormattedMessage} from 'react-intl';
|
||||||
|
|
||||||
import React from 'react';
|
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() {
|
render() {
|
||||||
if (this.state.items.length === 0) {
|
if (this.state.items.length === 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const items = [];
|
const items = [];
|
||||||
|
let lastType;
|
||||||
for (let i = 0; i < this.state.items.length; i++) {
|
for (let i = 0; i < this.state.items.length; i++) {
|
||||||
|
const item = this.state.items[i];
|
||||||
const term = this.state.terms[i];
|
const term = this.state.terms[i];
|
||||||
const isSelection = term === this.state.selection;
|
const isSelection = term === this.state.selection;
|
||||||
|
|
||||||
// ReactComponent names need to be upper case when used in JSX
|
// ReactComponent names need to be upper case when used in JSX
|
||||||
const Component = this.state.components[i];
|
const Component = this.state.components[i];
|
||||||
|
|
||||||
|
if (item.type !== lastType) {
|
||||||
|
items.push(this.renderDivider(item.type));
|
||||||
|
lastType = item.type;
|
||||||
|
}
|
||||||
|
|
||||||
items.push(
|
items.push(
|
||||||
<Component
|
<Component
|
||||||
key={term}
|
key={term}
|
||||||
|
|||||||
@@ -1538,6 +1538,9 @@
|
|||||||
"suggestion.mention.all": "Notifies everyone in the channel, use in {townsquare} to notify the whole team",
|
"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.channel": "Notifies everyone in the channel",
|
||||||
"suggestion.mention.here": "Notifies everyone in the channel and online",
|
"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.private": "Private Groups",
|
||||||
"suggestion.search.public": "Public Channels",
|
"suggestion.search.public": "Public Channels",
|
||||||
"team_export_tab.download": "download",
|
"team_export_tab.download": "download",
|
||||||
|
|||||||
@@ -44,3 +44,33 @@
|
|||||||
.suggestion-list--bottom {
|
.suggestion-list--bottom {
|
||||||
position: relative;
|
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%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -797,7 +797,10 @@ export const Constants = {
|
|||||||
LICENSE_GRACE_PERIOD: 1000 * 60 * 60 * 24 * 15, // 15 days
|
LICENSE_GRACE_PERIOD: 1000 * 60 * 60 * 24 * 15, // 15 days
|
||||||
PERMISSIONS_ALL: 'all',
|
PERMISSIONS_ALL: 'all',
|
||||||
PERMISSIONS_TEAM_ADMIN: 'team_admin',
|
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;
|
export default Constants;
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user