diff options
Diffstat (limited to 'webapp/components')
4 files changed, 154 insertions, 6 deletions
diff --git a/webapp/components/post_view/components/post_message_container.jsx b/webapp/components/post_view/components/post_message_container.jsx index 4ab556fca..749af4ecc 100644 --- a/webapp/components/post_view/components/post_message_container.jsx +++ b/webapp/components/post_view/components/post_message_container.jsx @@ -3,6 +3,7 @@ import React from 'react'; +import ChannelStore from 'stores/channel_store.jsx'; import EmojiStore from 'stores/emoji_store.jsx'; import PreferenceStore from 'stores/preference_store.jsx'; import {Preferences} from 'utils/constants.jsx'; @@ -26,6 +27,7 @@ export default class PostMessageContainer extends React.Component { this.onEmojiChange = this.onEmojiChange.bind(this); this.onPreferenceChange = this.onPreferenceChange.bind(this); this.onUserChange = this.onUserChange.bind(this); + this.onChannelChange = this.onChannelChange.bind(this); const mentionKeys = UserStore.getCurrentMentionKeys(); mentionKeys.push('@here'); @@ -34,7 +36,8 @@ export default class PostMessageContainer extends React.Component { emojis: EmojiStore.getEmojis(), enableFormatting: PreferenceStore.getBool(Preferences.CATEGORY_ADVANCED_SETTINGS, 'formatting', true), mentionKeys, - usernameMap: UserStore.getProfilesUsernameMap() + usernameMap: UserStore.getProfilesUsernameMap(), + channelNamesMap: ChannelStore.getChannelNamesMap() }; } @@ -42,12 +45,16 @@ export default class PostMessageContainer extends React.Component { EmojiStore.addChangeListener(this.onEmojiChange); PreferenceStore.addChangeListener(this.onPreferenceChange); UserStore.addChangeListener(this.onUserChange); + ChannelStore.addChangeListener(this.onChannelChange); + ChannelStore.addMoreChangeListener(this.onChannelChange); } componentWillUnmount() { EmojiStore.removeChangeListener(this.onEmojiChange); PreferenceStore.removeChangeListener(this.onPreferenceChange); UserStore.removeChangeListener(this.onUserChange); + ChannelStore.removeChangeListener(this.onChannelChange); + ChannelStore.removeMoreChangeListener(this.onChannelChange); } onEmojiChange() { @@ -72,6 +79,12 @@ export default class PostMessageContainer extends React.Component { }); } + onChannelChange() { + this.setState({ + channelNamesMap: ChannelStore.getChannelNamesMap() + }); + } + render() { return ( <PostMessageView @@ -81,7 +94,8 @@ export default class PostMessageContainer extends React.Component { enableFormatting={this.state.enableFormatting} mentionKeys={this.state.mentionKeys} usernameMap={this.state.usernameMap} + channelNamesMap={this.state.channelNamesMap} /> ); } -}
\ No newline at end of file +} diff --git a/webapp/components/post_view/components/post_message_view.jsx b/webapp/components/post_view/components/post_message_view.jsx index 99589c973..5242e6648 100644 --- a/webapp/components/post_view/components/post_message_view.jsx +++ b/webapp/components/post_view/components/post_message_view.jsx @@ -13,7 +13,8 @@ export default class PostMessageView extends React.Component { emojis: React.PropTypes.object.isRequired, enableFormatting: React.PropTypes.bool.isRequired, mentionKeys: React.PropTypes.arrayOf(React.PropTypes.string).isRequired, - usernameMap: React.PropTypes.object.isRequired + usernameMap: React.PropTypes.object.isRequired, + channelNamesMap: React.PropTypes.object.isRequired }; shouldComponentUpdate(nextProps) { @@ -40,6 +41,7 @@ export default class PostMessageView extends React.Component { // Don't check if props.usernameMap changes since it is very large and inefficient to do so. // This mimics previous behaviour, but could be changed if we decide it's worth it. + // The same choice (and reasoning) is also applied to the this.props.channelNamesMap. return false; } @@ -53,7 +55,8 @@ export default class PostMessageView extends React.Component { emojis: this.props.emojis, siteURL: Utils.getSiteURL(), mentionKeys: this.props.mentionKeys, - usernameMap: this.props.usernameMap + usernameMap: this.props.usernameMap, + channelNamesMap: this.props.channelNamesMap }); return ( @@ -63,4 +66,4 @@ export default class PostMessageView extends React.Component { /> ); } -}
\ No newline at end of file +} diff --git a/webapp/components/suggestion/channel_mention_provider.jsx b/webapp/components/suggestion/channel_mention_provider.jsx new file mode 100644 index 000000000..9e8a7b47b --- /dev/null +++ b/webapp/components/suggestion/channel_mention_provider.jsx @@ -0,0 +1,130 @@ +// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import React from 'react'; + +import SuggestionStore from 'stores/suggestion_store.jsx'; +import ChannelStore from 'stores/channel_store.jsx'; +import Constants from 'utils/constants.jsx'; + +import Suggestion from './suggestion.jsx'; + +const MaxChannelSuggestions = 40; + +class ChannelMentionSuggestion extends Suggestion { + render() { + const isSelection = this.props.isSelection; + const item = this.props.item; + + const channelName = item.channel.display_name; + let 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> + ); + } +} + +function filterChannelsByPrefix(channels, prefix, limit) { + const filtered = []; + + for (const id of Object.keys(channels)) { + if (filtered.length >= limit) { + break; + } + + const channel = channels[id]; + + if (channel.delete_at > 0) { + continue; + } + + if (channel.display_name.toLowerCase().startsWith(prefix) || channel.name.startsWith(prefix)) { + filtered.push(channel); + } + } + + return filtered; +} + +export default class ChannelMentionProvider { + handlePretextChanged(suggestionId, pretext) { + const captured = (/(^|\s)(!([^!]*))$/i).exec(pretext.toLowerCase()); + if (captured) { + const prefix = captured[3]; + + const channels = ChannelStore.getAll(); + const moreChannels = ChannelStore.getMoreAll(); + + // Remove private channels from the list. + const publicChannels = channels.filter((channel) => { + return channel.type === 'O'; + }); + + // Filter channels by prefix. + const filteredChannels = filterChannelsByPrefix( + publicChannels, prefix, MaxChannelSuggestions); + const filteredMoreChannels = filterChannelsByPrefix( + moreChannels, prefix, MaxChannelSuggestions - filteredChannels.length); + + // Sort channels by display name. + [filteredChannels, filteredMoreChannels].forEach((items) => { + items.sort((a, b) => { + const aPrefix = a.display_name.startsWith(prefix); + const bPrefix = b.display_name.startsWith(prefix); + + if (aPrefix === bPrefix) { + return a.display_name.localeCompare(b.display_name); + } else if (aPrefix) { + return -1; + } + + return 1; + }); + }); + + // Wrap channels in an outer object to avoid overwriting the 'type' property. + const wrappedChannels = filteredChannels.map((item) => { + return { + type: Constants.MENTION_CHANNELS, + channel: item + }; + }); + const wrappedMoreChannels = filteredMoreChannels.map((item) => { + return { + type: Constants.MENTION_MORE_CHANNELS, + channel: item + }; + }); + + const wrapped = wrappedChannels.concat(wrappedMoreChannels); + + const mentions = wrapped.map((item) => '!' + item.channel.name); + + SuggestionStore.addSuggestions(suggestionId, mentions, wrapped, ChannelMentionSuggestion, captured[2]); + } + } +} diff --git a/webapp/components/textbox.jsx b/webapp/components/textbox.jsx index 12f111833..5c1d823b5 100644 --- a/webapp/components/textbox.jsx +++ b/webapp/components/textbox.jsx @@ -3,6 +3,7 @@ import $ from 'jquery'; import AtMentionProvider from './suggestion/at_mention_provider.jsx'; +import ChannelMentionProvider from './suggestion/channel_mention_provider.jsx'; import CommandProvider from './suggestion/command_provider.jsx'; import EmoticonProvider from './suggestion/emoticon_provider.jsx'; import SuggestionList from './suggestion/suggestion_list.jsx'; @@ -35,7 +36,7 @@ export default class Textbox extends React.Component { connection: '' }; - this.suggestionProviders = [new AtMentionProvider(), new EmoticonProvider()]; + this.suggestionProviders = [new AtMentionProvider(), new ChannelMentionProvider(), new EmoticonProvider()]; if (props.supportsCommands) { this.suggestionProviders.push(new CommandProvider()); } |