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
Этот коммит содержится в:
коммит произвёл
Harrison Healey
родитель
c96ecae6da
Коммит
981ea33b8e
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ссылка в новой задаче
Block a user