diff options
Diffstat (limited to 'webapp/components/suggestion')
-rw-r--r-- | webapp/components/suggestion/at_mention_provider.jsx | 165 | ||||
-rw-r--r-- | webapp/components/suggestion/channel_mention_provider.jsx | 143 | ||||
-rw-r--r-- | webapp/components/suggestion/command_provider.jsx | 41 | ||||
-rw-r--r-- | webapp/components/suggestion/emoticon_provider.jsx | 131 | ||||
-rw-r--r-- | webapp/components/suggestion/provider.jsx | 38 | ||||
-rw-r--r-- | webapp/components/suggestion/search_channel_provider.jsx | 91 | ||||
-rw-r--r-- | webapp/components/suggestion/search_suggestion_list.jsx | 98 | ||||
-rw-r--r-- | webapp/components/suggestion/search_user_provider.jsx | 91 | ||||
-rw-r--r-- | webapp/components/suggestion/suggestion.jsx | 30 | ||||
-rw-r--r-- | webapp/components/suggestion/suggestion_box.jsx | 441 | ||||
-rw-r--r-- | webapp/components/suggestion/suggestion_list.jsx | 177 | ||||
-rw-r--r-- | webapp/components/suggestion/switch_channel_provider.jsx | 283 | ||||
-rw-r--r-- | webapp/components/suggestion/switch_team_provider.jsx | 96 |
13 files changed, 0 insertions, 1825 deletions
diff --git a/webapp/components/suggestion/at_mention_provider.jsx b/webapp/components/suggestion/at_mention_provider.jsx deleted file mode 100644 index 9cf32440f..000000000 --- a/webapp/components/suggestion/at_mention_provider.jsx +++ /dev/null @@ -1,165 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -import Suggestion from './suggestion.jsx'; -import Provider from './provider.jsx'; - -import UserStore from 'stores/user_store.jsx'; - -import {autocompleteUsersInChannel} from 'actions/user_actions.jsx'; - -import AppDispatcher from 'dispatcher/app_dispatcher.jsx'; -import * as Utils from 'utils/utils.jsx'; -import {Constants, ActionTypes} from 'utils/constants.jsx'; - -import React from 'react'; -import {FormattedMessage} from 'react-intl'; -import XRegExp from 'xregexp'; - -class AtMentionSuggestion extends Suggestion { - render() { - const isSelection = this.props.isSelection; - const user = this.props.item; - - let username; - let description; - let icon; - if (user.username === 'all') { - username = 'all'; - description = ( - <FormattedMessage - id='suggestion.mention.all' - defaultMessage='CAUTION: This mentions everyone in channel' - /> - ); - icon = <i className='mention__image fa fa-users fa-2x'/>; - } else if (user.username === 'channel') { - username = 'channel'; - description = ( - <FormattedMessage - id='suggestion.mention.channel' - defaultMessage='Notifies everyone in the channel' - /> - ); - icon = <i className='mention__image fa fa-users fa-2x'/>; - } else if (user.username === 'here') { - username = 'here'; - description = ( - <FormattedMessage - id='suggestion.mention.here' - defaultMessage='Notifies everyone in the channel and online' - /> - ); - icon = <i className='mention__image fa fa-users fa-2x'/>; - } else { - username = user.username; - - if ((user.first_name || user.last_name) && user.nickname) { - description = `- ${Utils.getFullName(user)} (${user.nickname})`; - } else if (user.nickname) { - description = `- (${user.nickname})`; - } else if (user.first_name || user.last_name) { - description = `- ${Utils.getFullName(user)}`; - } - - icon = ( - <img - className='mention__image' - src={Utils.imageURLForUser(user)} - /> - ); - } - - let className = 'mentions__name'; - if (isSelection) { - className += ' suggestion--selected'; - } - - return ( - <div - className={className} - onClick={this.handleClick} - > - <div className='pull-left'> - {icon} - </div> - <div className='pull-left mention--align'> - <span> - {'@' + username} - </span> - <span className='mention__fullname'> - {' '} - {description} - </span> - </div> - </div> - ); - } -} - -export default class AtMentionProvider extends Provider { - constructor(channelId) { - super(); - - this.channelId = channelId; - } - - handlePretextChanged(suggestionId, pretext) { - const captured = XRegExp.cache('(?:^|\\W)@([\\pL\\d\\-_.]*)$', 'i').exec(pretext.toLowerCase()); - if (!captured) { - return false; - } - - const prefix = captured[1]; - - this.startNewRequest(suggestionId, prefix); - - autocompleteUsersInChannel( - prefix, - this.channelId, - (data) => { - if (this.shouldCancelDispatch(prefix)) { - return; - } - - const members = Object.assign([], data.users); - for (const id of Object.keys(members)) { - members[id] = {...members[id], type: Constants.MENTION_MEMBERS}; - } - - const nonmembers = data.out_of_channel || []; - for (const id of Object.keys(nonmembers)) { - nonmembers[id] = {...nonmembers[id], type: Constants.MENTION_NONMEMBERS}; - } - - let specialMentions = []; - if (!pretext.startsWith('/msg')) { - specialMentions = ['here', 'channel', 'all'].filter((item) => { - return item.startsWith(prefix); - }).map((name) => { - return {username: name, type: Constants.MENTION_SPECIAL}; - }); - } - - let users = members.concat(specialMentions).concat(nonmembers); - const me = UserStore.getCurrentUser(); - users = users.filter((user) => { - return user.id !== me.id; - }); - - const mentions = users.map((user) => '@' + user.username); - - AppDispatcher.handleServerAction({ - type: ActionTypes.SUGGESTION_RECEIVED_SUGGESTIONS, - id: suggestionId, - matchedPretext: `@${captured[1]}`, - terms: mentions, - items: users, - component: AtMentionSuggestion - }); - } - ); - - return true; - } -} diff --git a/webapp/components/suggestion/channel_mention_provider.jsx b/webapp/components/suggestion/channel_mention_provider.jsx deleted file mode 100644 index 1d85d8082..000000000 --- a/webapp/components/suggestion/channel_mention_provider.jsx +++ /dev/null @@ -1,143 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -import Suggestion from './suggestion.jsx'; -import Provider from './provider.jsx'; - -import {autocompleteChannels} from 'actions/channel_actions.jsx'; - -import ChannelStore from 'stores/channel_store.jsx'; - -import AppDispatcher from 'dispatcher/app_dispatcher.jsx'; -import {Constants, ActionTypes} from 'utils/constants.jsx'; - -import React from 'react'; - -class ChannelMentionSuggestion extends Suggestion { - render() { - const isSelection = this.props.isSelection; - const item = this.props.item; - - const channelName = item.channel.display_name; - const purpose = item.channel.purpose; - - let className = 'mentions__name'; - if (isSelection) { - className += ' suggestion--selected'; - } - - const description = '(~' + item.channel.name + ')'; - - return ( - <div - className={className} - onClick={this.handleClick} - > - <div className='mention__align'> - <span> - {channelName} - </span> - <span className='mention__channelname'> - {' '} - {description} - </span> - </div> - <div className='mention__purpose'> - {purpose} - </div> - </div> - ); - } -} - -export default class ChannelMentionProvider extends Provider { - constructor() { - super(); - - this.lastTermWithNoResults = ''; - this.lastCompletedWord = ''; - } - - handlePretextChanged(suggestionId, pretext) { - const captured = (/(^|\s)(~([^~\r\n]*))$/i).exec(pretext.toLowerCase()); - - if (!captured) { - // Not a channel mention - return false; - } - - if (this.lastTermWithNoResults && pretext.startsWith(this.lastTermWithNoResults)) { - // Just give up since we know it won't return any results - return false; - } - - if (this.lastCompletedWord && captured[0].startsWith(this.lastCompletedWord)) { - // It appears we're still matching a channel handle that we already completed - return false; - } - - // Clear the last completed word since we've started to match new text - this.lastCompletedWord = ''; - - const prefix = captured[3]; - - this.startNewRequest(suggestionId, prefix); - - autocompleteChannels( - prefix, - (channels) => { - if (this.shouldCancelDispatch(prefix)) { - return; - } - - if (channels.length === 0) { - this.lastTermWithNoResults = pretext; - } - - // Wrap channels in an outer object to avoid overwriting the 'type' property. - const wrappedChannels = []; - const wrappedMoreChannels = []; - const moreChannels = []; - channels.forEach((item) => { - if (ChannelStore.get(item.id)) { - wrappedChannels.push({ - type: Constants.MENTION_CHANNELS, - channel: item - }); - return; - } - - wrappedMoreChannels.push({ - type: Constants.MENTION_MORE_CHANNELS, - channel: item - }); - - moreChannels.push(item); - }); - - const wrapped = wrappedChannels.concat(wrappedMoreChannels); - const mentions = wrapped.map((item) => '~' + item.channel.name); - - AppDispatcher.handleServerAction({ - type: ActionTypes.RECEIVED_MORE_CHANNELS, - channels: moreChannels - }); - - AppDispatcher.handleServerAction({ - type: ActionTypes.SUGGESTION_RECEIVED_SUGGESTIONS, - id: suggestionId, - matchedPretext: captured[2], - terms: mentions, - items: wrapped, - component: ChannelMentionSuggestion - }); - } - ); - - return true; - } - - handleCompleteWord(term) { - this.lastCompletedWord = term; - } -} diff --git a/webapp/components/suggestion/command_provider.jsx b/webapp/components/suggestion/command_provider.jsx deleted file mode 100644 index 4f74ebd55..000000000 --- a/webapp/components/suggestion/command_provider.jsx +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -import React from 'react'; - -import Suggestion from './suggestion.jsx'; - -import {getSuggestedCommands} from 'actions/integration_actions.jsx'; - -class CommandSuggestion extends Suggestion { - render() { - const {item, isSelection} = this.props; - - let className = 'command'; - if (isSelection) { - className += ' suggestion--selected'; - } - - return ( - <div - className={className} - onClick={this.handleClick} - > - <div className='command__title'> - <string>{item.suggestion} {item.hint}</string> - </div> - <div className='command__desc'> - {item.description} - </div> - </div> - ); - } -} - -export default class CommandProvider { - handlePretextChanged(suggestionId, pretext) { - if (pretext.startsWith('/')) { - getSuggestedCommands(pretext.toLowerCase(), suggestionId, CommandSuggestion, pretext.toLowerCase()); - } - } -} diff --git a/webapp/components/suggestion/emoticon_provider.jsx b/webapp/components/suggestion/emoticon_provider.jsx deleted file mode 100644 index 1de35dc20..000000000 --- a/webapp/components/suggestion/emoticon_provider.jsx +++ /dev/null @@ -1,131 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -import React from 'react'; - -import {default as EmojiStore, EmojiMap} from 'stores/emoji_store.jsx'; -import * as Emoticons from 'utils/emoticons.jsx'; -import SuggestionStore from 'stores/suggestion_store.jsx'; - -import Suggestion from './suggestion.jsx'; - -import store from 'stores/redux_store.jsx'; -import {getCustomEmojisByName} from 'mattermost-redux/selectors/entities/emojis'; - -const MIN_EMOTICON_LENGTH = 2; - -class EmoticonSuggestion extends Suggestion { - render() { - const text = this.props.term; - const emoji = this.props.item.emoji; - - let className = 'emoticon-suggestion'; - if (this.props.isSelection) { - className += ' suggestion--selected'; - } - - return ( - <div - className={className} - onClick={this.handleClick} - > - <div className='pull-left'> - <img - alt={text} - className='emoticon-suggestion__image' - src={EmojiStore.getEmojiImageUrl(emoji)} - title={text} - /> - </div> - <div className='pull-left'> - {text} - </div> - </div> - ); - } -} - -export default class EmoticonProvider { - handlePretextChanged(suggestionId, pretext) { - let hasSuggestions = false; - - // look for the potential emoticons at the start of the text, after whitespace, and at the start of emoji reaction commands - const captured = (/(^|\s|^\+|^-)(:([^:\s]*))$/g).exec(pretext); - if (captured) { - const prefix = captured[1]; - const text = captured[2]; - const partialName = captured[3]; - - if (partialName.length < MIN_EMOTICON_LENGTH) { - SuggestionStore.clearSuggestions(suggestionId); - return false; - } - - const matched = []; - - // check for text emoticons if this isn't for an emoji reaction - if (prefix !== '-' && prefix !== '+') { - for (const emoticon of Object.keys(Emoticons.emoticonPatterns)) { - if (Emoticons.emoticonPatterns[emoticon].test(text)) { - SuggestionStore.addSuggestion(suggestionId, text, EmojiStore.get(emoticon), EmoticonSuggestion, text); - - hasSuggestions = true; - } - } - } - - const emojis = new EmojiMap(getCustomEmojisByName(store.getState())); - - // check for named emoji - for (const [name, emoji] of emojis) { - if (emoji.aliases) { - // This is a system emoji so it may have multiple names - for (const alias of emoji.aliases) { - if (alias.indexOf(partialName) !== -1) { - matched.push({name: alias, emoji}); - break; - } - } - } else if (name.indexOf(partialName) !== -1) { - // This is a custom emoji so it only has one name - matched.push({name, emoji}); - } - } - - // sort the emoticons so that emoticons starting with the entered text come first - matched.sort((a, b) => { - const aName = a.name; - const bName = b.name; - - const aPrefix = aName.startsWith(partialName); - const bPrefix = bName.startsWith(partialName); - - if (aPrefix === bPrefix) { - return aName.localeCompare(bName); - } else if (aPrefix) { - return -1; - } - - return 1; - }); - - const terms = matched.map((item) => ':' + item.name + ':'); - - SuggestionStore.clearSuggestions(suggestionId); - if (terms.length > 0) { - SuggestionStore.addSuggestions(suggestionId, terms, matched, EmoticonSuggestion, text); - - hasSuggestions = true; - } - } - - if (hasSuggestions) { - // force the selection to be cleared since the order of elements may have changed - SuggestionStore.clearSelection(suggestionId); - - return true; - } - - return false; - } -} diff --git a/webapp/components/suggestion/provider.jsx b/webapp/components/suggestion/provider.jsx deleted file mode 100644 index a5b54fb26..000000000 --- a/webapp/components/suggestion/provider.jsx +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -import SuggestionStore from 'stores/suggestion_store.jsx'; - -export default class Provider { - constructor() { - this.latestPrefix = ''; - this.latestComplete = true; - this.disableDispatches = false; - } - - handlePretextChanged(suggestionId, pretext) { // eslint-disable-line no-unused-vars - // NO-OP for inherited classes to override - } - - startNewRequest(suggestionId, prefix) { - this.latestPrefix = prefix; - this.latestComplete = false; - - // Don't use the dispatcher here since this is only called while handling an event - SuggestionStore.setSuggestionsPending(suggestionId, true); - } - - shouldCancelDispatch(prefix) { - if (this.disableDispatches) { - return true; - } - - if (prefix === this.latestPrefix) { - this.latestComplete = true; - } else if (this.latestComplete) { - return true; - } - - return false; - } -} diff --git a/webapp/components/suggestion/search_channel_provider.jsx b/webapp/components/suggestion/search_channel_provider.jsx deleted file mode 100644 index 650ec6973..000000000 --- a/webapp/components/suggestion/search_channel_provider.jsx +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -import Suggestion from './suggestion.jsx'; -import Provider from './provider.jsx'; - -import {autocompleteChannels} from 'actions/channel_actions.jsx'; - -import ChannelStore from 'stores/channel_store.jsx'; - -import AppDispatcher from 'dispatcher/app_dispatcher.jsx'; -import {Constants, ActionTypes} from 'utils/constants.jsx'; -import {sortChannelsByDisplayName} from 'utils/channel_utils.jsx'; - -import React from 'react'; - -class SearchChannelSuggestion extends Suggestion { - render() { - const {item, isSelection} = this.props; - - let className = 'search-autocomplete__item'; - if (isSelection) { - className += ' selected'; - } - - return ( - <div - onClick={this.handleClick} - className={className} - > - <i className='fa fa fa-plus-square'/>{item.name} - </div> - ); - } -} - -export default class SearchChannelProvider extends Provider { - handlePretextChanged(suggestionId, pretext) { - const captured = (/\b(?:in|channel):\s*(\S*)$/i).exec(pretext.toLowerCase()); - if (captured) { - const channelPrefix = captured[1]; - - this.startNewRequest(suggestionId, channelPrefix); - - autocompleteChannels( - channelPrefix, - (data) => { - if (this.shouldCancelDispatch(channelPrefix)) { - return; - } - - const publicChannels = data; - - const localChannels = ChannelStore.getAll(); - let privateChannels = []; - - for (const id of Object.keys(localChannels)) { - const channel = localChannels[id]; - if (channel.name.startsWith(channelPrefix) && channel.type === Constants.PRIVATE_CHANNEL) { - privateChannels.push(channel); - } - } - - let filteredPublicChannels = []; - publicChannels.forEach((item) => { - if (item.name.startsWith(channelPrefix)) { - filteredPublicChannels.push(item); - } - }); - - privateChannels = privateChannels.sort(sortChannelsByDisplayName); - filteredPublicChannels = filteredPublicChannels.sort(sortChannelsByDisplayName); - - const channels = filteredPublicChannels.concat(privateChannels); - const channelNames = channels.map((channel) => channel.name); - - AppDispatcher.handleServerAction({ - type: ActionTypes.SUGGESTION_RECEIVED_SUGGESTIONS, - id: suggestionId, - matchedPretext: channelPrefix, - terms: channelNames, - items: channels, - component: SearchChannelSuggestion - }); - } - ); - } - - return Boolean(captured); - } -} diff --git a/webapp/components/suggestion/search_suggestion_list.jsx b/webapp/components/suggestion/search_suggestion_list.jsx deleted file mode 100644 index 944ddaeca..000000000 --- a/webapp/components/suggestion/search_suggestion_list.jsx +++ /dev/null @@ -1,98 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -import $ from 'jquery'; -import React from 'react'; -import ReactDOM from 'react-dom'; -import Constants from 'utils/constants.jsx'; -import SuggestionList from './suggestion_list.jsx'; - -import {FormattedMessage} from 'react-intl'; - -import {Popover} from 'react-bootstrap'; - -export default class SearchSuggestionList extends SuggestionList { - static propTypes = { - ...SuggestionList.propTypes - }; - - getContent() { - return $(ReactDOM.findDOMNode(this.refs.popover)).find('.popover-content'); - } - - renderChannelDivider(type) { - let text; - if (type === Constants.OPEN_CHANNEL) { - text = ( - <FormattedMessage - id='suggestion.search.public' - defaultMessage='Public Channels' - /> - ); - } else { - text = ( - <FormattedMessage - id='suggestion.search.private' - defaultMessage='Private Channels' - /> - ); - } - - return ( - <div - key={type + '-divider'} - className='search-autocomplete__divider' - > - <span>{text}</span> - </div> - ); - } - - render() { - if (this.state.items.length === 0) { - return null; - } - - const items = []; - 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]; - - // temporary hack to add dividers between public and private channels in the search suggestion list - if (i === 0 || item.type !== this.state.items[i - 1].type) { - if (item.type === Constants.OPEN_CHANNEL) { - items.push(this.renderChannelDivider(Constants.OPEN_CHANNEL)); - } else if (item.type === Constants.PRIVATE_CHANNEL) { - items.push(this.renderChannelDivider(Constants.PRIVATE_CHANNEL)); - } - } - - items.push( - <Component - key={term} - ref={term} - item={item} - term={term} - matchedPretext={this.state.matchedPretext[i]} - isSelection={isSelection} - onClick={this.props.onCompleteWord} - /> - ); - } - - return ( - <Popover - ref='popover' - id='search-autocomplete__popover' - className='search-help-popover autocomplete visible' - placement='bottom' - > - {items} - </Popover> - ); - } -} diff --git a/webapp/components/suggestion/search_user_provider.jsx b/webapp/components/suggestion/search_user_provider.jsx deleted file mode 100644 index f3191c408..000000000 --- a/webapp/components/suggestion/search_user_provider.jsx +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -import Suggestion from './suggestion.jsx'; -import Provider from './provider.jsx'; - -import {autocompleteUsersInTeam} from 'actions/user_actions.jsx'; - -import AppDispatcher from 'dispatcher/app_dispatcher.jsx'; -import * as Utils from 'utils/utils.jsx'; -import {ActionTypes} from 'utils/constants.jsx'; - -import React from 'react'; - -class SearchUserSuggestion extends Suggestion { - render() { - const {item, isSelection} = this.props; - - let className = 'search-autocomplete__item'; - if (isSelection) { - className += ' selected'; - } - - const username = item.username; - let description = ''; - - if ((item.first_name || item.last_name) && item.nickname) { - description = `- ${Utils.getFullName(item)} (${item.nickname})`; - } else if (item.nickname) { - description = `- (${item.nickname})`; - } else if (item.first_name || item.last_name) { - description = `- ${Utils.getFullName(item)}`; - } - - return ( - <div - className={className} - onClick={this.handleClick} - > - <i className='fa fa fa-plus-square'/> - <img - className='profile-img rounded' - src={Utils.imageURLForUser(item)} - /> - <div className='mention--align'> - <span> - {username} - </span> - <span className='mention__fullname'> - {' '} - {description} - </span> - </div> - </div> - ); - } -} - -export default class SearchUserProvider extends Provider { - handlePretextChanged(suggestionId, pretext) { - const captured = (/\bfrom:\s*(\S*)$/i).exec(pretext.toLowerCase()); - if (captured) { - const usernamePrefix = captured[1]; - - this.startNewRequest(suggestionId, usernamePrefix); - - autocompleteUsersInTeam( - usernamePrefix, - (data) => { - if (this.shouldCancelDispatch(usernamePrefix)) { - return; - } - - const users = Object.assign([], data.users); - const mentions = users.map((user) => user.username); - - AppDispatcher.handleServerAction({ - type: ActionTypes.SUGGESTION_RECEIVED_SUGGESTIONS, - id: suggestionId, - matchedPretext: usernamePrefix, - terms: mentions, - items: users, - component: SearchUserSuggestion - }); - } - ); - } - - return Boolean(captured); - } -} diff --git a/webapp/components/suggestion/suggestion.jsx b/webapp/components/suggestion/suggestion.jsx deleted file mode 100644 index ddfdabc7d..000000000 --- a/webapp/components/suggestion/suggestion.jsx +++ /dev/null @@ -1,30 +0,0 @@ -import PropTypes from 'prop-types'; - -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -import React from 'react'; - -export default class Suggestion extends React.Component { - static get propTypes() { - return { - item: PropTypes.object.isRequired, - term: PropTypes.string.isRequired, - matchedPretext: PropTypes.string.isRequired, - isSelection: PropTypes.bool, - onClick: PropTypes.func - }; - } - - constructor(props) { - super(props); - - this.handleClick = this.handleClick.bind(this); - } - - handleClick(e) { - e.preventDefault(); - - this.props.onClick(this.props.term, this.props.matchedPretext); - } -} diff --git a/webapp/components/suggestion/suggestion_box.jsx b/webapp/components/suggestion/suggestion_box.jsx deleted file mode 100644 index 8f50a23ef..000000000 --- a/webapp/components/suggestion/suggestion_box.jsx +++ /dev/null @@ -1,441 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -import Constants from 'utils/constants.jsx'; -import * as GlobalActions from 'actions/global_actions.jsx'; -import SuggestionStore from 'stores/suggestion_store.jsx'; -import * as Utils from 'utils/utils.jsx'; - -import AutosizeTextarea from 'components/autosize_textarea.jsx'; - -const KeyCodes = Constants.KeyCodes; - -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 gains focus - */ - onFocus: PropTypes.func, - - /** - * 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, - - /** - * Flags if the suggestion_box is for the RHS (Reply). - */ - isRHS: PropTypes.bool, - - /** - * Function called when @mention is clicked - */ - popoverMentionKeyClick: PropTypes.bool, - - /** - * The number of characters required to show the suggestion list, defaults to 1 - */ - requiredCharacters: PropTypes.number, - - /** - * If true, the suggestion box is opened on focus, default to false - */ - openOnFocus: PropTypes.bool - } - - static defaultProps = { - type: 'input', - listStyle: 'top', - renderDividers: false, - completeOnTab: true, - isRHS: false, - requiredCharacters: 1, - openOnFocus: false - } - - constructor(props) { - super(props); - - this.handleBlur = this.handleBlur.bind(this); - this.handleFocus = this.handleFocus.bind(this); - - this.handlePopoverMentionKeyClick = this.handlePopoverMentionKeyClick.bind(this); - this.handleCompleteWord = this.handleCompleteWord.bind(this); - this.handleChange = this.handleChange.bind(this); - this.handleCompositionStart = this.handleCompositionStart.bind(this); - this.handleCompositionUpdate = this.handleCompositionUpdate.bind(this); - this.handleCompositionEnd = this.handleCompositionEnd.bind(this); - this.handleKeyDown = this.handleKeyDown.bind(this); - this.handlePretextChanged = this.handlePretextChanged.bind(this); - this.blur = this.blur.bind(this); - - this.suggestionId = Utils.generateId(); - SuggestionStore.registerSuggestionBox(this.suggestionId); - - // Keep track of whether we're composing a CJK character so we can make suggestions for partial characters - this.composing = false; - } - - componentDidMount() { - if (this.props.popoverMentionKeyClick) { - SuggestionStore.addPopoverMentionKeyClickListener(this.props.isRHS, this.handlePopoverMentionKeyClick); - } - SuggestionStore.addPretextChangedListener(this.suggestionId, this.handlePretextChanged); - } - - componentWillUnmount() { - if (this.props.popoverMentionKeyClick) { - SuggestionStore.removePopoverMentionKeyClickListener(this.props.isRHS, this.handlePopoverMentionKeyClick); - } - SuggestionStore.removePretextChangedListener(this.suggestionId, this.handlePretextChanged); - - 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' && this.refs.textbox) { - const node = this.refs.textbox.getDOMNode(); - return node; - } - - return this.refs.textbox; - } - - recalculateSize() { - if (this.props.type === 'textarea') { - this.refs.textbox.recalculateSize(); - } - } - - handleBlur() { - setTimeout(() => { - // Delay this slightly so that we don't clear the suggestions before we run click handlers on SuggestionList - GlobalActions.emitClearSuggestions(this.suggestionId); - }, 200); - - if (this.props.onBlur) { - this.props.onBlur(); - } - } - - handleFocus() { - if (this.props.openOnFocus) { - setTimeout(() => { - const textbox = this.getTextbox(); - if (textbox) { - const pretext = textbox.value.substring(0, textbox.selectionEnd); - if (pretext.length >= this.props.requiredCharacters) { - GlobalActions.emitSuggestionPretextChanged(this.suggestionId, pretext); - } - } - }); - } - - if (this.props.onFocus) { - this.props.onFocus(); - } - } - - handleChange(e) { - const textbox = this.getTextbox(); - const pretext = textbox.value.substring(0, textbox.selectionEnd); - - if (!this.composing && SuggestionStore.getPretext(this.suggestionId) !== pretext && pretext.length >= this.props.requiredCharacters) { - GlobalActions.emitSuggestionPretextChanged(this.suggestionId, pretext); - } - - if (this.props.onChange) { - this.props.onChange(e); - } - } - - handleCompositionStart() { - this.composing = true; - } - - handleCompositionUpdate(e) { - if (!e.data) { - return; - } - - // The caret appears before the CJK character currently being composed, so re-add it to the pretext - const textbox = this.getTextbox(); - const pretext = textbox.value.substring(0, textbox.selectionStart) + e.data; - - if (SuggestionStore.getPretext(this.suggestionId) !== pretext) { - GlobalActions.emitSuggestionPretextChanged(this.suggestionId, pretext); - } - } - - handleCompositionEnd() { - this.composing = false; - } - - handlePopoverMentionKeyClick(mentionKey) { - let insertText = '@' + mentionKey; - - // if the current text does not end with a whitespace, then insert a space - if (this.refs.textbox.value && (/[^\s]$/).test(this.refs.textbox.value)) { - insertText = ' ' + insertText; - } - - this.handleCompleteWord(insertText, '', false); - } - - handleCompleteWord(term, matchedPretext, shouldEmitWordSuggestion = true) { - const textbox = this.getTextbox(); - const caret = textbox.selectionEnd; - const text = this.props.value; - const pretext = textbox.value.substring(0, textbox.selectionEnd); - - let prefix; - if (pretext.endsWith(matchedPretext)) { - prefix = pretext.substring(0, pretext.length - matchedPretext.length); - } else { - // the pretext has changed since we got a term to complete so see if the term still fits the pretext - const termWithoutMatched = term.substring(matchedPretext.length); - const overlap = SuggestionBox.findOverlap(pretext, termWithoutMatched); - - prefix = pretext.substring(0, pretext.length - overlap.length - matchedPretext.length); - } - - const suffix = text.substring(caret); - - const newValue = prefix + term + ' ' + suffix; - this.refs.textbox.value = newValue; - - if (this.props.onChange) { - // fake an input event to send back to parent components - const e = { - target: this.refs.textbox - }; - - // don't call handleChange or we'll get into an event loop - this.props.onChange(e); - } - - if (this.props.onItemSelected) { - const items = SuggestionStore.getItems(this.suggestionId); - const terms = SuggestionStore.getTerms(this.suggestionId); - for (let i = 0; i < terms.length; i++) { - if (terms[i] === term) { - this.props.onItemSelected(items[i]); - break; - } - } - } - - textbox.focus(); - - // set the caret position after the next rendering - window.requestAnimationFrame(() => { - if (textbox.value === newValue) { - Utils.setCaretPosition(textbox, prefix.length + term.length + 1); - } - }); - - for (const provider of this.props.providers) { - if (provider.handleCompleteWord) { - provider.handleCompleteWord(term, matchedPretext); - } - } - if (shouldEmitWordSuggestion) { - GlobalActions.emitCompleteWordSuggestion(this.suggestionId); - } - } - - handleKeyDown(e) { - if (this.props.value && SuggestionStore.hasSuggestions(this.suggestionId)) { - if (e.which === KeyCodes.UP) { - GlobalActions.emitSelectPreviousSuggestion(this.suggestionId); - e.preventDefault(); - } else if (e.which === KeyCodes.DOWN) { - GlobalActions.emitSelectNextSuggestion(this.suggestionId); - e.preventDefault(); - } else if (e.which === KeyCodes.ENTER || (this.props.completeOnTab && e.which === KeyCodes.TAB)) { - this.handleCompleteWord(SuggestionStore.getSelection(this.suggestionId), SuggestionStore.getSelectedMatchedPretext(this.suggestionId)); - if (this.props.onKeyDown) { - this.props.onKeyDown(e); - } - e.preventDefault(); - } else if (e.which === KeyCodes.ESCAPE) { - GlobalActions.emitClearSuggestions(this.suggestionId); - e.preventDefault(); - e.stopPropagation(); - } else if (this.props.onKeyDown) { - this.props.onKeyDown(e); - } - } else if (this.props.onKeyDown) { - this.props.onKeyDown(e); - } - } - - handlePretextChanged(pretext) { - let handled = false; - for (const provider of this.props.providers) { - handled = provider.handlePretextChanged(this.suggestionId, pretext) || handled; - } - - if (!handled) { - SuggestionStore.clearSuggestions(this.suggestionId); - } - } - - blur() { - this.refs.textbox.blur(); - } - - render() { - const { - type, - listComponent, - listStyle, - renderDividers, - ...props - } = this.props; - - // Don't pass props used by SuggestionBox - Reflect.deleteProperty(props, 'providers'); - Reflect.deleteProperty(props, 'onChange'); // We use onInput instead of onChange on the actual input - Reflect.deleteProperty(props, 'onItemSelected'); - Reflect.deleteProperty(props, 'completeOnTab'); - Reflect.deleteProperty(props, 'isRHS'); - Reflect.deleteProperty(props, 'popoverMentionKeyClick'); - Reflect.deleteProperty(props, 'requiredCharacters'); - Reflect.deleteProperty(props, 'openOnFocus'); - - const childProps = { - ref: 'textbox', - onBlur: this.handleBlur, - onFocus: this.handleFocus, - onInput: this.handleChange, - onChange() { /* this is only here to suppress warnings about onChange not being implemented for read-write inputs */ }, - onCompositionStart: this.handleCompositionStart, - onCompositionUpdate: this.handleCompositionUpdate, - onCompositionEnd: this.handleCompositionEnd, - onKeyDown: this.handleKeyDown - }; - - let textbox = null; - if (type === 'input') { - textbox = ( - <input - type='text' - autoComplete='off' - {...props} - {...childProps} - /> - ); - } else if (type === 'search') { - textbox = ( - <input - type='search' - autoComplete='off' - {...props} - {...childProps} - /> - ); - } else if (type === 'textarea') { - textbox = ( - <AutosizeTextarea - {...props} - {...childProps} - /> - ); - } - - // This needs to be upper case so React doesn't think it's an html tag - const SuggestionListComponent = listComponent; - - return ( - <div ref='container'> - {textbox} - {this.props.value.length >= this.props.requiredCharacters && - <SuggestionListComponent - suggestionId={this.suggestionId} - location={listStyle} - renderDividers={renderDividers} - onCompleteWord={this.handleCompleteWord} - /> - } - </div> - ); - } - - // Finds the longest substring that's at both the end of b and the start of a. For example, - // if a = "firepit" and b = "pitbull", findOverlap would return "pit". - static findOverlap(a, b) { - for (let i = b.length; i > 0; i--) { - const substring = b.substring(0, i); - - if (a.endsWith(substring)) { - return substring; - } - } - - return ''; - } -} diff --git a/webapp/components/suggestion/suggestion_list.jsx b/webapp/components/suggestion/suggestion_list.jsx deleted file mode 100644 index 64e8713c5..000000000 --- a/webapp/components/suggestion/suggestion_list.jsx +++ /dev/null @@ -1,177 +0,0 @@ -// 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 React from 'react'; -import ReactDOM from 'react-dom'; -import PropTypes from 'prop-types'; -import {FormattedMessage} from 'react-intl'; - -export default class SuggestionList extends React.Component { - static propTypes = { - suggestionId: PropTypes.string.isRequired, - location: PropTypes.string, - renderDividers: PropTypes.bool, - onCompleteWord: PropTypes.func.isRequired - }; - - static defaultProps = { - renderDividers: false - }; - - constructor(props) { - super(props); - - this.getStateFromStores = this.getStateFromStores.bind(this); - - this.getContent = this.getContent.bind(this); - - this.handleSuggestionsChanged = this.handleSuggestionsChanged.bind(this); - - this.scrollToItem = this.scrollToItem.bind(this); - - this.state = this.getStateFromStores(props.suggestionId); - } - - getStateFromStores(suggestionId) { - const suggestions = SuggestionStore.getSuggestions(suggestionId || this.props.suggestionId); - - return { - matchedPretext: suggestions.matchedPretext, - items: suggestions.items, - terms: suggestions.terms, - components: suggestions.components, - selection: suggestions.selection - }; - } - - componentDidMount() { - SuggestionStore.addSuggestionsChangedListener(this.props.suggestionId, this.handleSuggestionsChanged); - } - - componentDidUpdate(prevProps, prevState) { - if (this.state.selection !== prevState.selection && this.state.selection) { - this.scrollToItem(this.state.selection); - } - } - - componentWillUnmount() { - SuggestionStore.removeSuggestionsChangedListener(this.props.suggestionId, this.handleSuggestionsChanged); - } - - getContent() { - return $(ReactDOM.findDOMNode(this.refs.content)); - } - - handleSuggestionsChanged() { - this.setState(this.getStateFromStores()); - } - - scrollToItem(term) { - const content = this.getContent(); - if (!content || content.length === 0) { - return; - } - - const visibleContentHeight = content[0].clientHeight; - const actualContentHeight = content[0].scrollHeight; - - if (visibleContentHeight < actualContentHeight) { - const contentTop = content.scrollTop(); - const contentTopPadding = parseInt(content.css('padding-top'), 10); - const contentBottomPadding = parseInt(content.css('padding-top'), 10); - - const item = $(ReactDOM.findDOMNode(this.refs[term])); - const itemTop = item[0].offsetTop - parseInt(item.css('margin-top'), 10); - const itemBottomMargin = parseInt(item.css('margin-bottom'), 10) + parseInt(item.css('padding-bottom'), 10); - const itemBottom = item[0].offsetTop + item.height() + itemBottomMargin; - - if (itemTop - contentTopPadding < contentTop) { - // the item is off the top of the visible space - content.scrollTop(itemTop - contentTopPadding); - } else if (itemBottom + contentTopPadding + contentBottomPadding > contentTop + visibleContentHeight) { - // the item has gone off the bottom of the visible space - content.scrollTop((itemBottom - visibleContentHeight) + contentTopPadding + contentBottomPadding); - } - } - } - - renderDivider(type) { - return ( - <div - key={type + '-divider'} - className='suggestion-list__divider' - > - <span> - <FormattedMessage id={'suggestion.' + type}/> - </span> - </div> - ); - } - - 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; - } - - 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 (this.props.renderDividers && item.type !== lastType) { - items.push(this.renderDivider(item.type)); - lastType = item.type; - } - - if (item.loading) { - items.push(this.renderLoading(item.type)); - continue; - } - - items.push( - <Component - key={term} - ref={term} - item={this.state.items[i]} - term={term} - matchedPretext={this.state.matchedPretext[i]} - isSelection={isSelection} - onClick={this.props.onCompleteWord} - /> - ); - } - - const mainClass = 'suggestion-list suggestion-list--' + this.props.location; - const contentClass = 'suggestion-list__content suggestion-list__content--' + this.props.location; - - return ( - <div className={mainClass}> - <div - ref='content' - className={contentClass} - > - {items} - </div> - </div> - ); - } -} diff --git a/webapp/components/suggestion/switch_channel_provider.jsx b/webapp/components/suggestion/switch_channel_provider.jsx deleted file mode 100644 index ac4af9c8b..000000000 --- a/webapp/components/suggestion/switch_channel_provider.jsx +++ /dev/null @@ -1,283 +0,0 @@ -// Copyright (c) 2015-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 {Constants, ActionTypes} from 'utils/constants.jsx'; -import * as Utils from 'utils/utils.jsx'; -import {sortChannelsByDisplayName, getChannelDisplayName} from 'utils/channel_utils.jsx'; - -import React from 'react'; - -import store from 'stores/redux_store.jsx'; -const getState = store.getState; - -import {Client4} from 'mattermost-redux/client'; - -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; - const globeIcon = Constants.GLOBE_ICON_SVG; - const lockIcon = Constants.LOCK_ICON_SVG; - - let className = 'mentions__name'; - if (isSelection) { - className += ' suggestion--selected'; - } - - let displayName = channel.display_name; - let icon = null; - if (channel.type === Constants.OPEN_CHANNEL) { - icon = ( - <span - className='icon icon__globe icon--body' - dangerouslySetInnerHTML={{__html: globeIcon}} - /> - ); - } else if (channel.type === Constants.PRIVATE_CHANNEL) { - icon = ( - <span - className='icon icon__lock icon--body' - dangerouslySetInnerHTML={{__html: lockIcon}} - /> - ); - } 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={Utils.imageURLForUser(channel)} - /> - </div> - ); - } - - return ( - <div - onClick={this.handleClick} - className={className} - > - {icon} - {displayName} - </div> - ); - } -} - -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); - - // Dispatch suggestions for local data - const channels = getChannelsInCurrentTeam(getState()).concat(getGroupChannels(getState())); - const users = Object.assign([], searchProfiles(getState(), channelPrefix, true)); - this.formatChannelsAndDispatch(channelPrefix, suggestionId, channels, users, true); - - // Fetch data from the server and dispatch - this.fetchUsersAndChannels(channelPrefix, suggestionId); - - return true; - } - - return false; - } - - async fetchUsersAndChannels(channelPrefix, suggestionId) { - let teamId = ''; - if (global.window.mm_config.RestrictDirectMessage === 'team') { - teamId = store.getState().entities.teams.currentTeamId; - } - const usersAsync = Client4.autocompleteUsers(channelPrefix, teamId, ''); - const channelsAsync = Client4.searchChannels(getCurrentTeamId(getState()), channelPrefix); - - let usersFromServer = []; - let channelsFromServer = []; - try { - usersFromServer = await usersAsync; - channelsFromServer = await channelsAsync; - } catch (err) { - AppDispatcher.handleServerAction({ - type: ActionTypes.RECEIVED_ERROR, - err - }); - } - - if (this.shouldCancelDispatch(channelPrefix)) { - return; - } - - const users = Object.assign([], searchProfiles(getState(), channelPrefix, true), usersFromServer.users); - const channels = getChannelsInCurrentTeam(getState()).concat(getGroupChannels(getState())).concat(channelsFromServer); - 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()); - - const completedChannels = {}; - - for (const id of Object.keys(allChannels)) { - const channel = allChannels[id]; - - if (completedChannels[channel.id]) { - continue; - } - - 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; - } - } - } else if (member) { - wrappedChannel.type = Constants.MENTION_CHANNELS; - } else { - wrappedChannel.type = Constants.MENTION_MORE_CHANNELS; - if (skipNotInChannel || !newChannel.display_name.toLowerCase().startsWith(channelPrefix)) { - continue; - } - } - - completedChannels[channel.id] = true; - channels.push(wrappedChannel); - } - } - - for (let i = 0; i < users.length; i++) { - const user = users[i]; - - if (completedChannels[user.id]) { - continue; - } - - 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, - last_picture_update: user.last_picture_update || 0 - }, - name: user.username - }; - - if (isDMVisible) { - wrappedChannel.type = Constants.MENTION_CHANNELS; - } else { - wrappedChannel.type = Constants.MENTION_MORE_CHANNELS; - if (skipNotInChannel) { - continue; - } - } - - completedChannels[user.id] = true; - 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); - } -} diff --git a/webapp/components/suggestion/switch_team_provider.jsx b/webapp/components/suggestion/switch_team_provider.jsx deleted file mode 100644 index ff2a8f24b..000000000 --- a/webapp/components/suggestion/switch_team_provider.jsx +++ /dev/null @@ -1,96 +0,0 @@ -// 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; - } -} |