PLT-5699 Improvements to channel switcher (#6486)
* Refactor channel switcher to not wait on server results * Change channel switcher to quick switcher and include team switching * Add sections, update ordering and add discoverability button * Fix styling error * Use CMD in text if on mac * Clean yarn cache on every install * Various UX updates per feedback * Add shortcut help text for team switcher * Couple more updates per feedback * Some minor fixes for GM and autocomplete race * Updating UI for channel switcher (#6504) * Updating channel switcher button (#6506) * Updating switcher modal on mobile (#6507) * Removed jQuery usage * Rename function to toggleQuickSwitchModal
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
8ce72aedc3
Коммит
5aaedb9663
@@ -7,6 +7,7 @@ export default class Provider {
|
||||
constructor() {
|
||||
this.latestPrefix = '';
|
||||
this.latestComplete = true;
|
||||
this.disableDispatches = false;
|
||||
}
|
||||
|
||||
handlePretextChanged(suggestionId, pretext) { // eslint-disable-line no-unused-vars
|
||||
@@ -22,6 +23,10 @@ export default class Provider {
|
||||
}
|
||||
|
||||
shouldCancelDispatch(prefix) {
|
||||
if (this.disableDispatches) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (prefix === this.latestPrefix) {
|
||||
this.latestComplete = true;
|
||||
} else if (this.latestComplete) {
|
||||
|
||||
@@ -15,6 +15,71 @@ import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
|
||||
export default class SuggestionBox extends React.Component {
|
||||
static propTypes = {
|
||||
|
||||
/**
|
||||
* The list component to render, usually SuggestionList
|
||||
*/
|
||||
listComponent: PropTypes.func.isRequired,
|
||||
|
||||
/**
|
||||
* The HTML input box type
|
||||
*/
|
||||
type: PropTypes.oneOf(['input', 'textarea', 'search']).isRequired,
|
||||
|
||||
/**
|
||||
* The value of in the input
|
||||
*/
|
||||
value: PropTypes.string.isRequired,
|
||||
|
||||
/**
|
||||
* Array of suggestion providers
|
||||
*/
|
||||
providers: PropTypes.arrayOf(PropTypes.object),
|
||||
|
||||
/**
|
||||
* Where the list will be displayed relative to the input box, defaults to 'top'
|
||||
*/
|
||||
listStyle: PropTypes.string,
|
||||
|
||||
/**
|
||||
* Set to true to draw dividers between types of list items, defaults to false
|
||||
*/
|
||||
renderDividers: PropTypes.bool,
|
||||
|
||||
/**
|
||||
* Set to allow TAB to select an item in the list, defaults to true
|
||||
*/
|
||||
completeOnTab: PropTypes.bool,
|
||||
|
||||
/**
|
||||
* Function called when input box loses focus
|
||||
*/
|
||||
onBlur: PropTypes.func,
|
||||
|
||||
/**
|
||||
* Function called when input box value changes
|
||||
*/
|
||||
onChange: PropTypes.func,
|
||||
|
||||
/**
|
||||
* Function called when a key is pressed and the input box is in focus
|
||||
*/
|
||||
onKeyDown: PropTypes.func,
|
||||
|
||||
/**
|
||||
* Function called when an item is selected
|
||||
*/
|
||||
onItemSelected: PropTypes.func
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
type: 'input',
|
||||
listStyle: 'top',
|
||||
renderDividers: false,
|
||||
completeOnTab: true
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
@@ -46,6 +111,14 @@ export default class SuggestionBox extends React.Component {
|
||||
SuggestionStore.unregisterSuggestionBox(this.suggestionId);
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (this.props.providers !== prevProps.providers) {
|
||||
const textbox = this.getTextbox();
|
||||
const pretext = textbox.value.substring(0, textbox.selectionEnd);
|
||||
GlobalActions.emitSuggestionPretextChanged(this.suggestionId, pretext);
|
||||
}
|
||||
}
|
||||
|
||||
getTextbox() {
|
||||
if (this.props.type === 'textarea') {
|
||||
return this.refs.textbox.getDOMNode();
|
||||
@@ -171,7 +244,7 @@ export default class SuggestionBox extends React.Component {
|
||||
} else if (e.which === KeyCodes.DOWN) {
|
||||
GlobalActions.emitSelectNextSuggestion(this.suggestionId);
|
||||
e.preventDefault();
|
||||
} else if (e.which === KeyCodes.ENTER || e.which === KeyCodes.TAB) {
|
||||
} else if (e.which === KeyCodes.ENTER || (this.props.completeOnTab && e.which === KeyCodes.TAB)) {
|
||||
this.handleCompleteWord(SuggestionStore.getSelection(this.suggestionId), SuggestionStore.getSelectedMatchedPretext(this.suggestionId));
|
||||
this.props.onKeyDown(e);
|
||||
e.preventDefault();
|
||||
@@ -281,23 +354,3 @@ export default class SuggestionBox extends React.Component {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
SuggestionBox.defaultProps = {
|
||||
type: 'input',
|
||||
listStyle: 'top'
|
||||
};
|
||||
|
||||
SuggestionBox.propTypes = {
|
||||
listComponent: PropTypes.func.isRequired,
|
||||
type: PropTypes.oneOf(['input', 'textarea', 'search']).isRequired,
|
||||
value: PropTypes.string.isRequired,
|
||||
providers: PropTypes.arrayOf(PropTypes.object),
|
||||
listStyle: PropTypes.string,
|
||||
renderDividers: PropTypes.bool,
|
||||
|
||||
// explicitly name any input event handlers we override and need to manually call
|
||||
onBlur: PropTypes.func,
|
||||
onChange: PropTypes.func,
|
||||
onKeyDown: PropTypes.func,
|
||||
onItemSelected: PropTypes.func
|
||||
};
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import SuggestionStore from 'stores/suggestion_store.jsx';
|
||||
|
||||
import $ from 'jquery';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import PropTypes from 'prop-types';
|
||||
import {FormattedMessage} from 'react-intl';
|
||||
|
||||
import SuggestionStore from 'stores/suggestion_store.jsx';
|
||||
|
||||
export default class SuggestionList extends React.Component {
|
||||
static propTypes = {
|
||||
suggestionId: PropTypes.string.isRequired,
|
||||
@@ -111,6 +111,17 @@ export default class SuggestionList extends React.Component {
|
||||
);
|
||||
}
|
||||
|
||||
renderLoading(type) {
|
||||
return (
|
||||
<div
|
||||
key={type + '-loading'}
|
||||
className='suggestion-loader'
|
||||
>
|
||||
<i className='fa fa-spinner fa-pulse fa-fw margin-bottom'/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.items.length === 0) {
|
||||
return null;
|
||||
@@ -131,6 +142,11 @@ export default class SuggestionList extends React.Component {
|
||||
lastType = item.type;
|
||||
}
|
||||
|
||||
if (item.loading) {
|
||||
items.push(this.renderLoading(item.type));
|
||||
continue;
|
||||
}
|
||||
|
||||
items.push(
|
||||
<Component
|
||||
key={term}
|
||||
|
||||
@@ -4,10 +4,6 @@
|
||||
import Suggestion from './suggestion.jsx';
|
||||
import Provider from './provider.jsx';
|
||||
|
||||
import ChannelStore from 'stores/channel_store.jsx';
|
||||
import UserStore from 'stores/user_store.jsx';
|
||||
|
||||
import {autocompleteUsers} from 'actions/user_actions.jsx';
|
||||
import Client from 'client/web_client.jsx';
|
||||
import AppDispatcher from 'dispatcher/app_dispatcher.jsx';
|
||||
import {Constants, ActionTypes} from 'utils/constants.jsx';
|
||||
@@ -16,30 +12,44 @@ import {sortChannelsByDisplayName, getChannelDisplayName} from 'utils/channel_ut
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import store from 'stores/redux_store.jsx';
|
||||
const getState = store.getState;
|
||||
const dispatch = store.dispatch;
|
||||
|
||||
import {searchChannels} from 'mattermost-redux/actions/channels';
|
||||
import {autocompleteUsers} from 'mattermost-redux/actions/users';
|
||||
|
||||
import {getCurrentUserId, searchProfiles} from 'mattermost-redux/selectors/entities/users';
|
||||
import {getChannelsInCurrentTeam, getMyChannelMemberships, getGroupChannels} from 'mattermost-redux/selectors/entities/channels';
|
||||
import {getCurrentTeamId} from 'mattermost-redux/selectors/entities/teams';
|
||||
import {getBool} from 'mattermost-redux/selectors/entities/preferences';
|
||||
import {Preferences} from 'mattermost-redux/constants';
|
||||
|
||||
class SwitchChannelSuggestion extends Suggestion {
|
||||
render() {
|
||||
const {item, isSelection} = this.props;
|
||||
const channel = item.channel;
|
||||
|
||||
let className = 'mentions__name';
|
||||
if (isSelection) {
|
||||
className += ' suggestion--selected';
|
||||
}
|
||||
|
||||
let displayName = item.display_name;
|
||||
let displayName = channel.display_name;
|
||||
let icon = null;
|
||||
if (item.type === Constants.OPEN_CHANNEL) {
|
||||
if (channel.type === Constants.OPEN_CHANNEL) {
|
||||
icon = <div className='status'><i className='fa fa-globe'/></div>;
|
||||
} else if (item.type === Constants.PRIVATE_CHANNEL) {
|
||||
} else if (channel.type === Constants.PRIVATE_CHANNEL) {
|
||||
icon = <div className='status'><i className='fa fa-lock'/></div>;
|
||||
} else if (item.type === Constants.GM_CHANNEL) {
|
||||
displayName = getChannelDisplayName(item);
|
||||
} else if (channel.type === Constants.GM_CHANNEL) {
|
||||
displayName = getChannelDisplayName(channel);
|
||||
icon = <div className='status status--group'>{'G'}</div>;
|
||||
} else {
|
||||
icon = (
|
||||
<div className='pull-left'>
|
||||
<img
|
||||
className='mention__image'
|
||||
src={Client.getUsersRoute() + '/' + item.id + '/image?time=' + item.last_picture_update}
|
||||
src={Client.getUsersRoute() + '/' + channel.id + '/image?time=' + channel.last_picture_update}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
@@ -57,83 +67,179 @@ class SwitchChannelSuggestion extends Suggestion {
|
||||
}
|
||||
}
|
||||
|
||||
let prefix = '';
|
||||
|
||||
function quickSwitchSorter(wrappedA, wrappedB) {
|
||||
if (wrappedA.type === Constants.MENTION_CHANNELS && wrappedB.type === Constants.MENTION_MORE_CHANNELS) {
|
||||
return -1;
|
||||
} else if (wrappedB.type === Constants.MENTION_CHANNELS && wrappedA.type === Constants.MENTION_MORE_CHANNELS) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
const a = wrappedA.channel;
|
||||
const b = wrappedB.channel;
|
||||
|
||||
let aDisplayName = getChannelDisplayName(a).toLowerCase();
|
||||
let bDisplayName = getChannelDisplayName(b).toLowerCase();
|
||||
|
||||
if (a.type === Constants.DM_CHANNEL) {
|
||||
aDisplayName = aDisplayName.substring(1);
|
||||
}
|
||||
|
||||
if (b.type === Constants.DM_CHANNEL) {
|
||||
bDisplayName = bDisplayName.substring(1);
|
||||
}
|
||||
|
||||
const aStartsWith = aDisplayName.startsWith(prefix);
|
||||
const bStartsWith = bDisplayName.startsWith(prefix);
|
||||
if (aStartsWith && bStartsWith) {
|
||||
return sortChannelsByDisplayName(a, b);
|
||||
} else if (!aStartsWith && !bStartsWith) {
|
||||
return sortChannelsByDisplayName(a, b);
|
||||
} else if (aStartsWith) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
export default class SwitchChannelProvider extends Provider {
|
||||
handlePretextChanged(suggestionId, channelPrefix) {
|
||||
if (channelPrefix) {
|
||||
prefix = channelPrefix;
|
||||
this.startNewRequest(suggestionId, channelPrefix);
|
||||
|
||||
const allChannels = ChannelStore.getAll();
|
||||
const channels = [];
|
||||
// Dispatch suggestions for local data
|
||||
const channels = getChannelsInCurrentTeam(getState()).concat(getGroupChannels(getState()));
|
||||
const users = Object.assign([], searchProfiles(getState(), channelPrefix, true), true);
|
||||
this.formatChannelsAndDispatch(channelPrefix, suggestionId, channels, users, true);
|
||||
|
||||
autocompleteUsers(
|
||||
channelPrefix,
|
||||
(data) => {
|
||||
const users = Object.assign([], data.users);
|
||||
// Fetch data from the server and dispatch
|
||||
this.fetchUsersAndChannels(channelPrefix, suggestionId);
|
||||
|
||||
if (this.shouldCancelDispatch(channelPrefix)) {
|
||||
return;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
const currentId = UserStore.getCurrentId();
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const id of Object.keys(allChannels)) {
|
||||
const channel = allChannels[id];
|
||||
if (channel.display_name.toLowerCase().indexOf(channelPrefix.toLowerCase()) !== -1) {
|
||||
const newChannel = Object.assign({}, channel);
|
||||
if (newChannel.type === Constants.GM_CHANNEL) {
|
||||
newChannel.name = getChannelDisplayName(newChannel);
|
||||
}
|
||||
channels.push(newChannel);
|
||||
}
|
||||
}
|
||||
async fetchUsersAndChannels(channelPrefix, suggestionId) {
|
||||
const usersAsync = autocompleteUsers(channelPrefix)(dispatch, getState);
|
||||
const channelsAsync = searchChannels(getCurrentTeamId(getState()), channelPrefix)(dispatch, getState);
|
||||
await usersAsync;
|
||||
await channelsAsync;
|
||||
|
||||
const userMap = {};
|
||||
for (let i = 0; i < users.length; i++) {
|
||||
const user = users[i];
|
||||
let displayName = `@${user.username} `;
|
||||
if (this.shouldCancelDispatch(channelPrefix)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (user.id === currentId) {
|
||||
const users = Object.assign([], searchProfiles(getState(), channelPrefix, true));
|
||||
const channels = getChannelsInCurrentTeam(getState()).concat(getGroupChannels(getState()));
|
||||
this.formatChannelsAndDispatch(channelPrefix, suggestionId, channels, users);
|
||||
}
|
||||
|
||||
formatChannelsAndDispatch(channelPrefix, suggestionId, allChannels, users, skipNotInChannel = false) {
|
||||
const channels = [];
|
||||
const members = getMyChannelMemberships(getState());
|
||||
|
||||
if (this.shouldCancelDispatch(channelPrefix)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentId = getCurrentUserId(getState());
|
||||
|
||||
for (const id of Object.keys(allChannels)) {
|
||||
const channel = allChannels[id];
|
||||
const member = members[channel.id];
|
||||
|
||||
if (channel.display_name.toLowerCase().indexOf(channelPrefix.toLowerCase()) !== -1) {
|
||||
const newChannel = Object.assign({}, channel);
|
||||
const wrappedChannel = {channel: newChannel, name: newChannel.name};
|
||||
if (newChannel.type === Constants.GM_CHANNEL) {
|
||||
newChannel.name = getChannelDisplayName(newChannel);
|
||||
wrappedChannel.name = newChannel.name;
|
||||
const isGMVisible = getBool(getState(), Preferences.CATEGORY_GROUP_CHANNEL_SHOW, newChannel.id, false);
|
||||
if (isGMVisible) {
|
||||
wrappedChannel.type = Constants.MENTION_CHANNELS;
|
||||
} else {
|
||||
wrappedChannel.type = Constants.MENTION_MORE_CHANNELS;
|
||||
if (skipNotInChannel) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((user.first_name || user.last_name) && user.nickname) {
|
||||
displayName += `- ${Utils.getFullName(user)} (${user.nickname})`;
|
||||
} else if (user.nickname) {
|
||||
displayName += `- (${user.nickname})`;
|
||||
} else if (user.first_name || user.last_name) {
|
||||
displayName += `- ${Utils.getFullName(user)}`;
|
||||
}
|
||||
|
||||
const newChannel = {
|
||||
display_name: displayName,
|
||||
name: user.username,
|
||||
id: user.id,
|
||||
update_at: user.update_at,
|
||||
type: Constants.DM_CHANNEL
|
||||
};
|
||||
channels.push(newChannel);
|
||||
userMap[user.id] = user;
|
||||
}
|
||||
|
||||
const channelNames = channels.
|
||||
sort(sortChannelsByDisplayName).
|
||||
map((channel) => channel.name);
|
||||
|
||||
AppDispatcher.handleServerAction({
|
||||
type: ActionTypes.SUGGESTION_RECEIVED_SUGGESTIONS,
|
||||
id: suggestionId,
|
||||
matchedPretext: channelPrefix,
|
||||
terms: channelNames,
|
||||
items: channels,
|
||||
component: SwitchChannelSuggestion
|
||||
});
|
||||
|
||||
AppDispatcher.handleServerAction({
|
||||
type: ActionTypes.RECEIVED_PROFILES,
|
||||
profiles: userMap
|
||||
});
|
||||
} else if (member) {
|
||||
wrappedChannel.type = Constants.MENTION_CHANNELS;
|
||||
} else {
|
||||
wrappedChannel.type = Constants.MENTION_MORE_CHANNELS;
|
||||
if (skipNotInChannel || !newChannel.display_name.startsWith(channelPrefix)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
channels.push(wrappedChannel);
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < users.length; i++) {
|
||||
const user = users[i];
|
||||
const isDMVisible = getBool(getState(), Preferences.CATEGORY_DIRECT_CHANNEL_SHOW, user.id, false);
|
||||
let displayName = `@${user.username} `;
|
||||
|
||||
if (user.id === currentId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((user.first_name || user.last_name) && user.nickname) {
|
||||
displayName += `- ${Utils.getFullName(user)} (${user.nickname})`;
|
||||
} else if (user.nickname) {
|
||||
displayName += `- (${user.nickname})`;
|
||||
} else if (user.first_name || user.last_name) {
|
||||
displayName += `- ${Utils.getFullName(user)}`;
|
||||
}
|
||||
|
||||
const wrappedChannel = {
|
||||
channel: {
|
||||
display_name: displayName,
|
||||
name: user.username,
|
||||
id: user.id,
|
||||
update_at: user.update_at,
|
||||
type: Constants.DM_CHANNEL
|
||||
},
|
||||
name: user.username
|
||||
};
|
||||
|
||||
if (isDMVisible) {
|
||||
wrappedChannel.type = Constants.MENTION_CHANNELS;
|
||||
} else {
|
||||
wrappedChannel.type = Constants.MENTION_MORE_CHANNELS;
|
||||
if (skipNotInChannel) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
channels.push(wrappedChannel);
|
||||
}
|
||||
|
||||
const channelNames = channels.
|
||||
sort(quickSwitchSorter).
|
||||
map((wrappedChannel) => wrappedChannel.channel.name);
|
||||
|
||||
if (skipNotInChannel) {
|
||||
channels.push({
|
||||
type: Constants.MENTION_MORE_CHANNELS,
|
||||
loading: true
|
||||
});
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
AppDispatcher.handleServerAction({
|
||||
type: ActionTypes.SUGGESTION_RECEIVED_SUGGESTIONS,
|
||||
id: suggestionId,
|
||||
matchedPretext: channelPrefix,
|
||||
terms: channelNames,
|
||||
items: channels,
|
||||
component: SwitchChannelSuggestion
|
||||
});
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
|
||||
96
webapp/components/suggestion/switch_team_provider.jsx
Обычный файл
96
webapp/components/suggestion/switch_team_provider.jsx
Обычный файл
@@ -0,0 +1,96 @@
|
||||
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import Suggestion from './suggestion.jsx';
|
||||
import Provider from './provider.jsx';
|
||||
|
||||
import AppDispatcher from 'dispatcher/app_dispatcher.jsx';
|
||||
import {ActionTypes} from 'utils/constants.jsx';
|
||||
import LocalizationStore from 'stores/localization_store.jsx';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
// Redux actions
|
||||
import store from 'stores/redux_store.jsx';
|
||||
const getState = store.getState;
|
||||
|
||||
import * as Selectors from 'mattermost-redux/selectors/entities/teams';
|
||||
|
||||
class SwitchTeamSuggestion extends Suggestion {
|
||||
render() {
|
||||
const {item, isSelection} = this.props;
|
||||
|
||||
let className = 'mentions__name';
|
||||
if (isSelection) {
|
||||
className += ' suggestion--selected';
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
onClick={this.handleClick}
|
||||
className={className}
|
||||
>
|
||||
<div className='status'><i className='fa fa-group'/></div>
|
||||
{item.display_name}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let prefix = '';
|
||||
|
||||
function quickSwitchSorter(a, b) {
|
||||
const aDisplayName = a.display_name.toLowerCase();
|
||||
const bDisplayName = b.display_name.toLowerCase();
|
||||
const aStartsWith = aDisplayName.startsWith(prefix);
|
||||
const bStartsWith = bDisplayName.startsWith(prefix);
|
||||
|
||||
if (aStartsWith && bStartsWith) {
|
||||
const locale = LocalizationStore.getLocale();
|
||||
|
||||
if (aDisplayName !== bDisplayName) {
|
||||
return aDisplayName.localeCompare(bDisplayName, locale, {numeric: true});
|
||||
}
|
||||
|
||||
return a.name.localeCompare(b.name, locale, {numeric: true});
|
||||
} else if (aStartsWith) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
export default class SwitchTeamProvider extends Provider {
|
||||
handlePretextChanged(suggestionId, teamPrefix) {
|
||||
if (teamPrefix) {
|
||||
prefix = teamPrefix;
|
||||
this.startNewRequest(suggestionId, teamPrefix);
|
||||
|
||||
const allTeams = Selectors.getMyTeams(getState());
|
||||
|
||||
const teams = allTeams.filter((team) => {
|
||||
return team.display_name.toLowerCase().indexOf(teamPrefix) !== -1 ||
|
||||
team.name.indexOf(teamPrefix) !== -1;
|
||||
});
|
||||
|
||||
const teamNames = teams.
|
||||
sort(quickSwitchSorter).
|
||||
map((team) => team.name);
|
||||
|
||||
setTimeout(() => {
|
||||
AppDispatcher.handleServerAction({
|
||||
type: ActionTypes.SUGGESTION_RECEIVED_SUGGESTIONS,
|
||||
id: suggestionId,
|
||||
matchedPretext: teamPrefix,
|
||||
terms: teamNames,
|
||||
items: teams,
|
||||
component: SwitchTeamSuggestion
|
||||
});
|
||||
}, 0);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Ссылка в новой задаче
Block a user