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
Этот коммит содержится в:
Joram Wilander
2016-11-24 09:35:09 -05:00
коммит произвёл Harrison Healey
родитель c96ecae6da
Коммит 981ea33b8e
27 изменённых файлов: 1337 добавлений и 381 удалений

Просмотреть файл

@@ -1,15 +1,16 @@
// 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;
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() {
@@ -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();
function autocomplete() {
autocompleteChannels(
prefix,
(data) => {
const channels = data;
// Remove private channels from the list.
const publicChannels = channels.filter((channel) => {
return channel.type === 'O';
});
// 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;
}
// Filter channels by prefix.
const filteredChannels = filterChannelsByPrefix(
publicChannels, prefix, MaxChannelSuggestions);
const filteredMoreChannels = filterChannelsByPrefix(
moreChannels, prefix, MaxChannelSuggestions - filteredChannels.length);
wrappedMoreChannels.push({
type: Constants.MENTION_MORE_CHANNELS,
channel: item
});
// 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);
moreChannels.push(item);
});
if (aPrefix === bPrefix) {
return a.display_name.localeCompare(b.display_name);
} else if (aPrefix) {
return -1;
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
);
}
}
}

Просмотреть файл

@@ -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);
}
}
const filteredPublicChannels = [];
publicChannels.forEach((item) => {
if (item.name.startsWith(channelPrefix)) {
filteredPublicChannels.push(item);
}
});
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
});
}
}
);
}
publicChannels.sort((a, b) => a.name.localeCompare(b.name));
const publicChannelNames = publicChannels.map((channel) => channel.name);
privateChannels.sort((a, b) => a.name.localeCompare(b.name));
const privateChannelNames = privateChannels.map((channel) => channel.name);
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
);
}
}
}