From 8443ca58289055cde25a3cdaaa3987c6f8cfde4a Mon Sep 17 00:00:00 2001 From: George Goldberg Date: Mon, 19 Sep 2016 13:21:22 +0100 Subject: PLT-1759 - Auto-complete for !channels when posting messages. (#3890) * Auto-complete for !channels when posting messages. This is part 1 of the fix for PLT-1759 to make channels linkable. Still to do: - Make the !channels clickable when they appear in messages. This is blocked until PR #3865 is resolved as it looks like that refactors some of the code that would be touched by making this change. - Unit tests. Again, I think the above referenced PR should be merged before tackling this. * Fix style problems. * Highlighting of !channel-names in messages. This only identifies the !channel-name (not the display name). The implementation of the auto-complete on channel names now needs to be modified to convert to the channel handle before sending the message. * Display !channel-name as !Display Name. When we encounter !channel-name in a message, display it as a link using the channel's actual name rather than it's handle (name). * Match on names and display name, and use name. * Autocomplete channels matching on both the name and the the display name. * Use the name as the text we fill in instead of the display name. It's potentially a bit ugly, but it minimises complexity for now as otherwise we'd have to do complicated things to the message box. * Fix style issues. * Load more channels everywhere. Whenever we load the list of channels, we should also load the list of more channels. This is to enable auto-completing and auto-linking of all channels whether or not the user is in them currently. * Include more channels in the map for linking. * Listen for channel list updates for autolinking. * Remove accidental console.log. * Autocomplete on more channels too. * i18n for channel autocomplete. * Link directly to channels in !channel mentions. This currently does not work if you aren't a member of that channel. Need to decide what the correct behaviour is in that case. * Fix style issues. * Show channel name and handle in suggestion. * Match channels only at start or after space. * Better matching in text-formatting. Only match channels after a space-type character or at the start in the posts list too. * Move the route construction to make tests work. Moves route-construction out of text_formatting.jsx and into utils.jsx so that the unit tests work once again. --- .../suggestion/channel_mention_provider.jsx | 130 +++++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 webapp/components/suggestion/channel_mention_provider.jsx (limited to 'webapp/components/suggestion/channel_mention_provider.jsx') 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 ( +
+
+ + {channelName} + + + {' '} + {description} + +
+
+ {purpose} +
+
+ ); + } +} + +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]); + } + } +} -- cgit v1.2.3-1-g7c22