From 981ea33b8e10456bc279f36235c814305d01b243 Mon Sep 17 00:00:00 2001 From: Joram Wilander Date: Thu, 24 Nov 2016 09:35:09 -0500 Subject: PLT-4403 Add server-based channel autocomplete, search and paging (#4585) * Add more channel paging API * Add channel paging support to client * Add DB channel search functions * Add API for searching more channels * Add more channel search functionality to client * Add API for autocompleting channels * Add channel autocomplete functionality to the client * Move to be deprecated APIs to their own file * Final clean-up * Fixes related to feedback * Localization changes * Add unit as suffix to timeout constants --- .../suggestion/channel_mention_provider.jsx | 134 ++++++++++----------- .../suggestion/search_channel_provider.jsx | 80 ++++++++---- 2 files changed, 116 insertions(+), 98 deletions(-) (limited to 'webapp/components/suggestion') diff --git a/webapp/components/suggestion/channel_mention_provider.jsx b/webapp/components/suggestion/channel_mention_provider.jsx index d80433271..c644d1a9f 100644 --- a/webapp/components/suggestion/channel_mention_provider.jsx +++ b/webapp/components/suggestion/channel_mention_provider.jsx @@ -1,15 +1,16 @@ // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. -import React from 'react'; +import Suggestion from './suggestion.jsx'; + +import {autocompleteChannels} from 'actions/channel_actions.jsx'; -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'; +import AppDispatcher from 'dispatcher/app_dispatcher.jsx'; +import {Constants, ActionTypes} from 'utils/constants.jsx'; -const MaxChannelSuggestions = 40; +import React from 'react'; class ChannelMentionSuggestion extends Suggestion { render() { @@ -48,84 +49,71 @@ class ChannelMentionSuggestion extends Suggestion { } } -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); - } +export default class ChannelMentionProvider { + constructor() { + this.timeoutId = ''; } - return filtered; -} + componentWillUnmount() { + clearTimeout(this.timeoutId); + } -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; + function autocomplete() { + autocompleteChannels( + prefix, + (data) => { + const channels = data; + + // 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 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.clearSuggestions(suggestionId); - SuggestionStore.addSuggestions(suggestionId, mentions, wrapped, ChannelMentionSuggestion, captured[2]); + this.timeoutId = setTimeout( + autocomplete.bind(this), + Constants.AUTOCOMPLETE_TIMEOUT + ); } } } diff --git a/webapp/components/suggestion/search_channel_provider.jsx b/webapp/components/suggestion/search_channel_provider.jsx index 0f07b6e29..66011af9f 100644 --- a/webapp/components/suggestion/search_channel_provider.jsx +++ b/webapp/components/suggestion/search_channel_provider.jsx @@ -1,13 +1,16 @@ // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. -import React from 'react'; +import Suggestion from './suggestion.jsx'; + +import {autocompleteChannels} from 'actions/channel_actions.jsx'; import ChannelStore from 'stores/channel_store.jsx'; -import Constants from 'utils/constants.jsx'; -import SuggestionStore from 'stores/suggestion_store.jsx'; -import Suggestion from './suggestion.jsx'; +import AppDispatcher from 'dispatcher/app_dispatcher.jsx'; +import {Constants, ActionTypes} from 'utils/constants.jsx'; + +import React from 'react'; class SearchChannelSuggestion extends Suggestion { render() { @@ -30,37 +33,64 @@ class SearchChannelSuggestion extends Suggestion { } export default class SearchChannelProvider { + constructor() { + this.timeoutId = ''; + } + + componentWillUnmount() { + clearTimeout(this.timeoutId); + } + handlePretextChanged(suggestionId, pretext) { const captured = (/\b(?:in|channel):\s*(\S*)$/i).exec(pretext.toLowerCase()); if (captured) { const channelPrefix = captured[1]; - const channels = ChannelStore.getAll(); - const publicChannels = []; - const privateChannels = []; + function autocomplete() { + autocompleteChannels( + channelPrefix, + (data) => { + const publicChannels = data; - for (const id of Object.keys(channels)) { - const channel = channels[id]; + const localChannels = ChannelStore.getAll(); + const privateChannels = []; - // don't show direct channels - if (channel.type !== Constants.DM_CHANNEL && channel.name.startsWith(channelPrefix)) { - if (channel.type === Constants.OPEN_CHANNEL) { - publicChannels.push(channel); - } else { - privateChannels.push(channel); - } - } - } + for (const id of Object.keys(localChannels)) { + const channel = localChannels[id]; + if (channel.name.startsWith(channelPrefix) && channel.type === Constants.PRIVATE_CHANNEL) { + privateChannels.push(channel); + } + } - publicChannels.sort((a, b) => a.name.localeCompare(b.name)); - const publicChannelNames = publicChannels.map((channel) => channel.name); + const filteredPublicChannels = []; + publicChannels.forEach((item) => { + if (item.name.startsWith(channelPrefix)) { + filteredPublicChannels.push(item); + } + }); - privateChannels.sort((a, b) => a.name.localeCompare(b.name)); - const privateChannelNames = privateChannels.map((channel) => channel.name); + privateChannels.sort((a, b) => a.name.localeCompare(b.name)); + filteredPublicChannels.sort((a, b) => a.name.localeCompare(b.name)); + + 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 + }); + } + ); + } - SuggestionStore.clearSuggestions(suggestionId); - SuggestionStore.addSuggestions(suggestionId, publicChannelNames, publicChannels, SearchChannelSuggestion, channelPrefix); - SuggestionStore.addSuggestions(suggestionId, privateChannelNames, privateChannels, SearchChannelSuggestion, channelPrefix); + this.timeoutId = setTimeout( + autocomplete.bind(this), + Constants.AUTOCOMPLETE_TIMEOUT + ); } } } -- cgit v1.2.3-1-g7c22