Link to autocomplete component and fix issues
This commit is contained in:
parent
4f9eb6a3d9
commit
7ffcba44d6
10 changed files with 210 additions and 172 deletions
|
|
@ -12,6 +12,8 @@ import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
|||
import AtMention from './at_mention/';
|
||||
import ChannelMention from './channel_mention/';
|
||||
import EmojiSuggestion from './emoji_suggestion/';
|
||||
import SlashSuggestion from './slash_suggestion/';
|
||||
import AppSlashSuggestion from './slash_suggestion/app_slash_suggestion/';
|
||||
|
||||
const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
|
|
@ -92,13 +94,14 @@ const Autocomplete = ({
|
|||
const [showingAtMention, setShowingAtMention] = useState(false);
|
||||
const [showingChannelMention, setShowingChannelMention] = useState(false);
|
||||
const [showingEmoji, setShowingEmoji] = useState(false);
|
||||
const [showingCommand, setShowingCommand] = useState(false);
|
||||
const [showingAppCommand, setShowingAppCommand] = useState(false);
|
||||
|
||||
// const [showingCommand, setShowingCommand] = useState(false);
|
||||
// const [showingAppCommand, setShowingAppCommand] = useState(false);
|
||||
// const [showingDate, setShowingDate] = useState(false);
|
||||
|
||||
const hasElements = showingChannelMention || showingEmoji || showingAtMention; // || showingCommand || showingAppCommand || showingDate;
|
||||
const appsTakeOver = false; // showingAppCommand;
|
||||
const hasElements = showingChannelMention || showingEmoji || showingAtMention || showingCommand || showingAppCommand; // || showingDate;
|
||||
const appsTakeOver = showingAppCommand;
|
||||
const showCommands = !(showingChannelMention || showingEmoji || showingAtMention);
|
||||
|
||||
const maxListHeight = useMemo(() => {
|
||||
if (maxHeightOverride) {
|
||||
|
|
@ -151,15 +154,17 @@ const Autocomplete = ({
|
|||
testID='autocomplete'
|
||||
style={containerStyles}
|
||||
>
|
||||
{/* {isAppsEnabled && (
|
||||
{isAppsEnabled && (
|
||||
<AppSlashSuggestion
|
||||
maxListHeight={maxListHeight}
|
||||
updateValue={updateValue}
|
||||
onResultCountChange={setShowingAppCommand}
|
||||
onShowingChange={setShowingAppCommand}
|
||||
value={value || ''}
|
||||
nestedScrollEnabled={nestedScrollEnabled}
|
||||
channelId={channelId}
|
||||
rootId={rootId}
|
||||
/>
|
||||
)} */}
|
||||
)}
|
||||
{(!appsTakeOver || !isAppsEnabled) && (<>
|
||||
<AtMention
|
||||
cursorPosition={cursorPosition}
|
||||
|
|
@ -192,14 +197,18 @@ const Autocomplete = ({
|
|||
hasFilesAttached={hasFilesAttached}
|
||||
/>
|
||||
}
|
||||
{/* <SlashSuggestion
|
||||
{showCommands &&
|
||||
<SlashSuggestion
|
||||
maxListHeight={maxListHeight}
|
||||
updateValue={updateValue}
|
||||
onResultCountChange={setShowingCommand}
|
||||
onShowingChange={setShowingCommand}
|
||||
value={value || ''}
|
||||
nestedScrollEnabled={nestedScrollEnabled}
|
||||
channelId={channelId}
|
||||
rootId={rootId}
|
||||
/>
|
||||
{(isSearch && enableDateSuggestion) &&
|
||||
}
|
||||
{/* {(isSearch && enableDateSuggestion) &&
|
||||
<DateSuggestion
|
||||
cursorPosition={cursorPosition}
|
||||
updateValue={updateValue}
|
||||
|
|
|
|||
|
|
@ -728,11 +728,11 @@ export class AppCommandParser {
|
|||
|
||||
if (base.startsWith(command)) {
|
||||
result.push({
|
||||
complete: binding.label,
|
||||
suggestion: base,
|
||||
description: binding.description || '',
|
||||
hint: binding.hint || '',
|
||||
iconData: binding.icon || '',
|
||||
Complete: binding.label,
|
||||
Suggestion: base,
|
||||
Description: binding.description || '',
|
||||
Hint: binding.hint || '',
|
||||
IconData: binding.icon || '',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -794,27 +794,27 @@ export class AppCommandParser {
|
|||
|
||||
getNoMatchingSuggestion = (): AutocompleteSuggestion[] => {
|
||||
return [{
|
||||
complete: '',
|
||||
suggestion: '',
|
||||
hint: this.intl.formatMessage({
|
||||
Complete: '',
|
||||
Suggestion: '',
|
||||
Hint: this.intl.formatMessage({
|
||||
id: 'apps.suggestion.no_suggestion',
|
||||
defaultMessage: 'No matching suggestions.',
|
||||
}),
|
||||
iconData: COMMAND_SUGGESTION_ERROR,
|
||||
description: '',
|
||||
IconData: COMMAND_SUGGESTION_ERROR,
|
||||
Description: '',
|
||||
}];
|
||||
};
|
||||
|
||||
getErrorSuggestion = (parsed: ParsedCommand): AutocompleteSuggestion[] => {
|
||||
return [{
|
||||
complete: '',
|
||||
suggestion: '',
|
||||
hint: this.intl.formatMessage({
|
||||
Complete: '',
|
||||
Suggestion: '',
|
||||
Hint: this.intl.formatMessage({
|
||||
id: 'apps.suggestion.errors.parser_error',
|
||||
defaultMessage: 'Parsing error',
|
||||
}),
|
||||
iconData: COMMAND_SUGGESTION_ERROR,
|
||||
description: parsed.error,
|
||||
IconData: COMMAND_SUGGESTION_ERROR,
|
||||
Description: parsed.error,
|
||||
}];
|
||||
};
|
||||
|
||||
|
|
@ -940,22 +940,21 @@ export class AppCommandParser {
|
|||
|
||||
// decorateSuggestionComplete applies the necessary modifications for a suggestion to be processed
|
||||
private decorateSuggestionComplete = (parsed: ParsedCommand, choice: AutocompleteSuggestion): AutocompleteSuggestion => {
|
||||
if (choice.complete && choice.complete.endsWith(EXECUTE_CURRENT_COMMAND_ITEM_ID)) {
|
||||
if (choice.Complete && choice.Complete.endsWith(EXECUTE_CURRENT_COMMAND_ITEM_ID)) {
|
||||
return choice as AutocompleteSuggestion;
|
||||
}
|
||||
|
||||
let goBackSpace = 0;
|
||||
if (choice.complete === '') {
|
||||
if (choice.Complete === '') {
|
||||
goBackSpace = 1;
|
||||
}
|
||||
let complete = parsed.command.substring(0, parsed.incompleteStart - goBackSpace);
|
||||
complete += choice.complete === undefined ? choice.suggestion : choice.complete;
|
||||
choice.hint = choice.hint || '';
|
||||
complete += choice.Complete === undefined ? choice.Suggestion : choice.Complete;
|
||||
complete = complete.substring(1);
|
||||
|
||||
return {
|
||||
...choice,
|
||||
complete,
|
||||
Complete: complete,
|
||||
};
|
||||
};
|
||||
|
||||
|
|
@ -1097,11 +1096,11 @@ export class AppCommandParser {
|
|||
bindings.forEach((b) => {
|
||||
if (b.label.toLowerCase().startsWith(parsed.incomplete.toLowerCase())) {
|
||||
result.push({
|
||||
complete: b.label,
|
||||
suggestion: b.label,
|
||||
description: b.description || '',
|
||||
hint: b.hint || '',
|
||||
iconData: b.icon || '',
|
||||
Complete: b.label,
|
||||
Suggestion: b.label,
|
||||
Description: b.description || '',
|
||||
Hint: b.hint || '',
|
||||
IconData: b.icon || '',
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
@ -1187,11 +1186,11 @@ export class AppCommandParser {
|
|||
if (applicable) {
|
||||
return applicable.map((f) => {
|
||||
return {
|
||||
complete: prefix + (f.label || f.name),
|
||||
suggestion: '--' + (f.label || f.name),
|
||||
description: f.description || '',
|
||||
hint: f.hint || '',
|
||||
iconData: parsed.binding?.icon || '',
|
||||
Complete: prefix + (f.label || f.name),
|
||||
Suggestion: '--' + (f.label || f.name),
|
||||
Description: f.description || '',
|
||||
Hint: f.hint || '',
|
||||
IconData: parsed.binding?.icon || '',
|
||||
};
|
||||
});
|
||||
}
|
||||
|
|
@ -1232,11 +1231,11 @@ export class AppCommandParser {
|
|||
|
||||
const fieldName = parsed.field.modal_label || parsed.field.label || parsed.field.name;
|
||||
return [{
|
||||
complete,
|
||||
suggestion: `${fieldName}: ${delimiter || '"'}${parsed.incomplete}${delimiter || '"'}`,
|
||||
description: f.description || '',
|
||||
hint: '',
|
||||
iconData: parsed.binding?.icon || '',
|
||||
Complete: complete,
|
||||
Suggestion: `${fieldName}: ${delimiter || '"'}${parsed.incomplete}${delimiter || '"'}`,
|
||||
Description: f.description || '',
|
||||
Hint: '',
|
||||
IconData: parsed.binding?.icon || '',
|
||||
}];
|
||||
};
|
||||
|
||||
|
|
@ -1246,14 +1245,14 @@ export class AppCommandParser {
|
|||
const opts = f.options?.filter((opt) => opt.label.toLowerCase().startsWith(parsed.incomplete.toLowerCase()));
|
||||
if (!opts?.length) {
|
||||
return [{
|
||||
complete: '',
|
||||
suggestion: '',
|
||||
hint: this.intl.formatMessage({
|
||||
Complete: '',
|
||||
Suggestion: '',
|
||||
Hint: this.intl.formatMessage({
|
||||
id: 'apps.suggestion.no_static',
|
||||
defaultMessage: 'No matching options.',
|
||||
}),
|
||||
description: '',
|
||||
iconData: COMMAND_SUGGESTION_ERROR,
|
||||
Description: '',
|
||||
IconData: COMMAND_SUGGESTION_ERROR,
|
||||
}];
|
||||
}
|
||||
return opts.map((opt) => {
|
||||
|
|
@ -1264,11 +1263,11 @@ export class AppCommandParser {
|
|||
complete = '`' + complete + '`';
|
||||
}
|
||||
return {
|
||||
complete,
|
||||
suggestion: opt.label,
|
||||
hint: f.hint || '',
|
||||
description: f.description || '',
|
||||
iconData: opt.icon_data || parsed.binding?.icon || '',
|
||||
Complete: complete,
|
||||
Suggestion: opt.label,
|
||||
Hint: f.hint || '',
|
||||
Description: f.description || '',
|
||||
IconData: opt.icon_data || parsed.binding?.icon || '',
|
||||
};
|
||||
});
|
||||
};
|
||||
|
|
@ -1331,14 +1330,14 @@ export class AppCommandParser {
|
|||
items = items?.filter(filterEmptyOptions);
|
||||
if (!items?.length) {
|
||||
return [{
|
||||
complete: '',
|
||||
suggestion: '',
|
||||
hint: this.intl.formatMessage({
|
||||
Complete: '',
|
||||
Suggestion: '',
|
||||
Hint: this.intl.formatMessage({
|
||||
id: 'apps.suggestion.no_static',
|
||||
defaultMessage: 'No matching options.',
|
||||
}),
|
||||
iconData: '',
|
||||
description: this.intl.formatMessage({
|
||||
IconData: '',
|
||||
Description: this.intl.formatMessage({
|
||||
id: 'apps.suggestion.no_dynamic',
|
||||
defaultMessage: 'No data was returned for dynamic suggestions',
|
||||
}),
|
||||
|
|
@ -1353,11 +1352,11 @@ export class AppCommandParser {
|
|||
complete = '`' + complete + '`';
|
||||
}
|
||||
return ({
|
||||
complete,
|
||||
description: s.label || s.value,
|
||||
suggestion: s.value,
|
||||
hint: '',
|
||||
iconData: s.icon_data || parsed.binding?.icon || '',
|
||||
Complete: complete,
|
||||
Description: s.label || s.value,
|
||||
Suggestion: s.value,
|
||||
Hint: '',
|
||||
IconData: s.icon_data || parsed.binding?.icon || '',
|
||||
});
|
||||
});
|
||||
};
|
||||
|
|
@ -1370,14 +1369,14 @@ export class AppCommandParser {
|
|||
error: message,
|
||||
});
|
||||
return [{
|
||||
complete: '',
|
||||
suggestion: '',
|
||||
hint: this.intl.formatMessage({
|
||||
Complete: '',
|
||||
Suggestion: '',
|
||||
Hint: this.intl.formatMessage({
|
||||
id: 'apps.suggestion.dynamic.error',
|
||||
defaultMessage: 'Dynamic select error',
|
||||
}),
|
||||
iconData: COMMAND_SUGGESTION_ERROR,
|
||||
description: errMsg,
|
||||
IconData: COMMAND_SUGGESTION_ERROR,
|
||||
Description: errMsg,
|
||||
}];
|
||||
};
|
||||
|
||||
|
|
@ -1405,20 +1404,20 @@ export class AppCommandParser {
|
|||
|
||||
if ('true'.startsWith(parsed.incomplete)) {
|
||||
suggestions.push({
|
||||
complete: 'true',
|
||||
suggestion: 'true',
|
||||
description: parsed.field?.description || '',
|
||||
hint: parsed.field?.hint || '',
|
||||
iconData: parsed.binding?.icon || '',
|
||||
Complete: 'true',
|
||||
Suggestion: 'true',
|
||||
Description: parsed.field?.description || '',
|
||||
Hint: parsed.field?.hint || '',
|
||||
IconData: parsed.binding?.icon || '',
|
||||
});
|
||||
}
|
||||
if ('false'.startsWith(parsed.incomplete)) {
|
||||
suggestions.push({
|
||||
complete: 'false',
|
||||
suggestion: 'false',
|
||||
description: parsed.field?.description || '',
|
||||
hint: parsed.field?.hint || '',
|
||||
iconData: parsed.binding?.icon || '',
|
||||
Complete: 'false',
|
||||
Suggestion: 'false',
|
||||
Description: parsed.field?.description || '',
|
||||
Hint: parsed.field?.hint || '',
|
||||
IconData: parsed.binding?.icon || '',
|
||||
});
|
||||
}
|
||||
return suggestions;
|
||||
|
|
|
|||
|
|
@ -13,11 +13,11 @@ export async function inTextMentionSuggestions(serverUrl: string, pretext: strin
|
|||
const res = await searchUsers(serverUrl, lastWord.substring(1), channelID);
|
||||
const users = await getUserSuggestions(res.users);
|
||||
users.forEach((u) => {
|
||||
let complete = incompleteLessLastWord ? incompleteLessLastWord + ' ' + u.complete : u.complete;
|
||||
let complete = incompleteLessLastWord ? incompleteLessLastWord + ' ' + u.Complete : u.Complete;
|
||||
if (delimiter) {
|
||||
complete = delimiter + complete;
|
||||
}
|
||||
u.complete = complete;
|
||||
u.Complete = complete;
|
||||
});
|
||||
return users;
|
||||
}
|
||||
|
|
@ -26,11 +26,11 @@ export async function inTextMentionSuggestions(serverUrl: string, pretext: strin
|
|||
const res = await searchChannels(serverUrl, lastWord.substring(1));
|
||||
const channels = await getChannelSuggestions(res.channels);
|
||||
channels.forEach((c) => {
|
||||
let complete = incompleteLessLastWord ? incompleteLessLastWord + ' ' + c.complete : c.complete;
|
||||
let complete = incompleteLessLastWord ? incompleteLessLastWord + ' ' + c.Complete : c.Complete;
|
||||
if (delimiter) {
|
||||
complete = delimiter + complete;
|
||||
}
|
||||
c.complete = complete;
|
||||
c.Complete = complete;
|
||||
});
|
||||
return channels;
|
||||
}
|
||||
|
|
@ -40,11 +40,11 @@ export async function inTextMentionSuggestions(serverUrl: string, pretext: strin
|
|||
|
||||
export async function getUserSuggestions(usersAutocomplete?: {users: UserProfile[]; out_of_channel?: UserProfile[]}): Promise<AutocompleteSuggestion[]> {
|
||||
const notFoundSuggestions = [{
|
||||
complete: '',
|
||||
suggestion: '',
|
||||
description: 'No user found',
|
||||
hint: '',
|
||||
iconData: '',
|
||||
Complete: '',
|
||||
Suggestion: '',
|
||||
Description: 'No user found',
|
||||
Hint: '',
|
||||
IconData: '',
|
||||
}];
|
||||
if (!usersAutocomplete) {
|
||||
return notFoundSuggestions;
|
||||
|
|
@ -67,11 +67,11 @@ export async function getUserSuggestions(usersAutocomplete?: {users: UserProfile
|
|||
|
||||
export async function getChannelSuggestions(channels?: Channel[]): Promise<AutocompleteSuggestion[]> {
|
||||
const notFoundSuggestion = [{
|
||||
complete: '',
|
||||
suggestion: '',
|
||||
description: 'No channel found',
|
||||
hint: '',
|
||||
iconData: '',
|
||||
Complete: '',
|
||||
Suggestion: '',
|
||||
Description: 'No channel found',
|
||||
Hint: '',
|
||||
IconData: '',
|
||||
}];
|
||||
if (!channels) {
|
||||
return notFoundSuggestion;
|
||||
|
|
@ -82,11 +82,11 @@ export async function getChannelSuggestions(channels?: Channel[]): Promise<Autoc
|
|||
|
||||
const items = channels.map((c) => {
|
||||
return {
|
||||
complete: '~' + c.name,
|
||||
suggestion: '',
|
||||
description: '',
|
||||
hint: '',
|
||||
iconData: '',
|
||||
Complete: '~' + c.name,
|
||||
Suggestion: '',
|
||||
Description: '',
|
||||
Hint: '',
|
||||
IconData: '',
|
||||
type: COMMAND_SUGGESTION_CHANNEL,
|
||||
item: c,
|
||||
};
|
||||
|
|
@ -97,11 +97,11 @@ export async function getChannelSuggestions(channels?: Channel[]): Promise<Autoc
|
|||
|
||||
function getUserSuggestion(u: UserProfile) {
|
||||
return {
|
||||
complete: '@' + u.username,
|
||||
suggestion: '',
|
||||
description: '',
|
||||
hint: '',
|
||||
iconData: '',
|
||||
Complete: '@' + u.username,
|
||||
Suggestion: '',
|
||||
Description: '',
|
||||
Hint: '',
|
||||
IconData: '',
|
||||
type: COMMAND_SUGGESTION_USER,
|
||||
item: u,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useEffect, useRef, useState} from 'react';
|
||||
import {debounce} from 'lodash';
|
||||
import React, {useEffect, useMemo, useRef, useState} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {
|
||||
FlatList,
|
||||
|
|
@ -25,16 +26,16 @@ export type Props = {
|
|||
currentTeamId: string;
|
||||
isSearch?: boolean;
|
||||
maxListHeight?: number;
|
||||
onChangeText: (text: string) => void;
|
||||
onResultCountChange: (count: number) => void;
|
||||
updateValue: (text: string) => void;
|
||||
onShowingChange: (c: boolean) => void;
|
||||
value: string;
|
||||
nestedScrollEnabled?: boolean;
|
||||
rootId?: string;
|
||||
channelId: string;
|
||||
appsEnabled: boolean;
|
||||
isAppsEnabled: boolean;
|
||||
};
|
||||
|
||||
const keyExtractor = (item: ExtendedAutocompleteSuggestion): string => (item.suggestion || '') + item.type + item.item;
|
||||
const keyExtractor = (item: ExtendedAutocompleteSuggestion): string => item.Suggestion + item.type + item.item;
|
||||
|
||||
const getStyleFromTheme = makeStyleSheetFromTheme((theme: Theme) => {
|
||||
return {
|
||||
|
|
@ -47,28 +48,40 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme: Theme) => {
|
|||
};
|
||||
});
|
||||
|
||||
const emptySuggestonList: AutocompleteSuggestion[] = [];
|
||||
|
||||
const AppSlashSuggestion = ({
|
||||
channelId,
|
||||
currentTeamId,
|
||||
rootId,
|
||||
value = '',
|
||||
appsEnabled,
|
||||
isAppsEnabled,
|
||||
maxListHeight,
|
||||
nestedScrollEnabled,
|
||||
onChangeText,
|
||||
onResultCountChange,
|
||||
updateValue,
|
||||
onShowingChange,
|
||||
}: Props) => {
|
||||
const intl = useIntl();
|
||||
const theme = useTheme();
|
||||
const serverUrl = useServerUrl();
|
||||
const appCommandParser = useRef<AppCommandParser>(new AppCommandParser(serverUrl, intl, channelId, currentTeamId, rootId, theme));
|
||||
const [dataSource, setDataSource] = useState<AutocompleteSuggestion[]>([]);
|
||||
const active = appsEnabled && Boolean(dataSource.length);
|
||||
const [dataSource, setDataSource] = useState<AutocompleteSuggestion[]>(emptySuggestonList);
|
||||
const active = isAppsEnabled && Boolean(dataSource.length);
|
||||
const style = getStyleFromTheme(theme);
|
||||
const mounted = useRef(false);
|
||||
|
||||
const fetchAndShowAppCommandSuggestions = useMemo(() => debounce(async (pretext: string, cId: string, tId = '', rId?: string) => {
|
||||
appCommandParser.current.setChannelContext(cId, tId, rId);
|
||||
const suggestions = await appCommandParser.current.getSuggestions(pretext);
|
||||
if (!mounted.current) {
|
||||
return;
|
||||
}
|
||||
updateSuggestions(suggestions);
|
||||
}), []);
|
||||
|
||||
const updateSuggestions = (matches: ExtendedAutocompleteSuggestion[]) => {
|
||||
setDataSource(matches);
|
||||
onResultCountChange(matches.length);
|
||||
onShowingChange(Boolean(matches.length));
|
||||
};
|
||||
|
||||
const completeSuggestion = (command: string) => {
|
||||
|
|
@ -81,13 +94,13 @@ const AppSlashSuggestion = ({
|
|||
completedDraft = `//${command} `;
|
||||
}
|
||||
|
||||
onChangeText(completedDraft);
|
||||
updateValue(completedDraft);
|
||||
|
||||
if (Platform.OS === 'ios') {
|
||||
// This is the second part of the hack were we replace the double / with just one
|
||||
// after the auto correct vanished
|
||||
setTimeout(() => {
|
||||
onChangeText(completedDraft.replace(`//${command} `, `/${command} `));
|
||||
updateValue(completedDraft.replace(`//${command} `, `/${command} `));
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
@ -113,7 +126,7 @@ const AppSlashSuggestion = ({
|
|||
return (
|
||||
<AtMentionItem
|
||||
user={item.item as UserProfile | UserModel}
|
||||
onPress={completeUserSuggestion(item.complete)}
|
||||
onPress={completeUserSuggestion(item.Complete)}
|
||||
testID={`autocomplete.at_mention.item.${item.item}`}
|
||||
/>
|
||||
);
|
||||
|
|
@ -124,19 +137,19 @@ const AppSlashSuggestion = ({
|
|||
return (
|
||||
<ChannelMentionItem
|
||||
channel={item.item as Channel | ChannelModel}
|
||||
onPress={completeChannelMention(item.complete)}
|
||||
onPress={completeChannelMention(item.Complete)}
|
||||
testID={`autocomplete.channel_mention.item.${item.item}`}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
return (
|
||||
<SlashSuggestionItem
|
||||
description={item.description}
|
||||
hint={item.hint}
|
||||
description={item.Description}
|
||||
hint={item.Hint}
|
||||
onPress={completeSuggestion}
|
||||
suggestion={item.suggestion}
|
||||
complete={item.complete}
|
||||
icon={item.iconData}
|
||||
suggestion={item.Suggestion}
|
||||
complete={item.Complete}
|
||||
icon={item.IconData}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
@ -147,31 +160,35 @@ const AppSlashSuggestion = ({
|
|||
return appCommandParser.current.isAppCommand(pretext);
|
||||
};
|
||||
|
||||
const fetchAndShowAppCommandSuggestions = async (pretext: string, channelID: string, teamID = '', rootID?: string) => {
|
||||
appCommandParser.current.setChannelContext(channelID, teamID, rootID);
|
||||
const suggestions = await appCommandParser.current.getSuggestions(pretext);
|
||||
updateSuggestions(suggestions);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (value[0] !== '/') {
|
||||
setDataSource([]);
|
||||
onResultCountChange(0);
|
||||
fetchAndShowAppCommandSuggestions.cancel();
|
||||
updateSuggestions(emptySuggestonList);
|
||||
return;
|
||||
}
|
||||
|
||||
if (value.indexOf(' ') === -1) {
|
||||
setDataSource([]);
|
||||
// Let slash command suggestions handle base commands.
|
||||
fetchAndShowAppCommandSuggestions.cancel();
|
||||
updateSuggestions(emptySuggestonList);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isAppCommand(value, channelId, currentTeamId, rootId)) {
|
||||
setDataSource([]);
|
||||
fetchAndShowAppCommandSuggestions.cancel();
|
||||
updateSuggestions(emptySuggestonList);
|
||||
return;
|
||||
}
|
||||
fetchAndShowAppCommandSuggestions(value, channelId, currentTeamId, rootId);
|
||||
}, [value]);
|
||||
|
||||
useEffect(() => {
|
||||
mounted.current = true;
|
||||
return () => {
|
||||
mounted.current = false;
|
||||
};
|
||||
}, []);
|
||||
|
||||
if (!active) {
|
||||
// If we are not in an active state return null so nothing is rendered
|
||||
// other components are not blocked.
|
||||
|
|
|
|||
|
|
@ -47,30 +47,29 @@ const filterCommands = (matchTerm: string, commands: Command[]): AutocompleteSug
|
|||
|
||||
return command.display_name.startsWith(matchTerm) || command.trigger.startsWith(matchTerm);
|
||||
});
|
||||
return data.map((item) => {
|
||||
return data.map((command) => {
|
||||
return {
|
||||
complete: item.trigger,
|
||||
suggestion: '/' + item.trigger,
|
||||
hint: item.auto_complete_hint,
|
||||
description: item.auto_complete_desc,
|
||||
iconData: item.icon_url || item.autocomplete_icon_data || '',
|
||||
Complete: command.trigger,
|
||||
Suggestion: '/' + command.trigger,
|
||||
Hint: command.auto_complete_hint,
|
||||
Description: command.auto_complete_desc,
|
||||
IconData: command.icon_url || command.autocomplete_icon_data || '',
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const keyExtractor = (item: Command & AutocompleteSuggestion): string => item.id || item.suggestion || '';
|
||||
const keyExtractor = (item: Command & AutocompleteSuggestion): string => item.id || item.Suggestion;
|
||||
|
||||
type Props = {
|
||||
currentTeamId: string;
|
||||
commands: Command[];
|
||||
maxListHeight?: number;
|
||||
onChangeText: (text: string) => void;
|
||||
onResultCountChange: (count: number) => void;
|
||||
updateValue: (text: string) => void;
|
||||
onShowingChange: (c: boolean) => void;
|
||||
value: string;
|
||||
nestedScrollEnabled?: boolean;
|
||||
rootId?: string;
|
||||
channelId: string;
|
||||
appsEnabled: boolean;
|
||||
isAppsEnabled: boolean;
|
||||
};
|
||||
|
||||
const emptyCommandList: Command[] = [];
|
||||
|
|
@ -80,11 +79,11 @@ const SlashSuggestion = ({
|
|||
channelId,
|
||||
currentTeamId,
|
||||
rootId,
|
||||
onResultCountChange,
|
||||
appsEnabled,
|
||||
onShowingChange,
|
||||
isAppsEnabled,
|
||||
maxListHeight,
|
||||
nestedScrollEnabled,
|
||||
onChangeText,
|
||||
updateValue,
|
||||
value = '',
|
||||
}: Props) => {
|
||||
const intl = useIntl();
|
||||
|
|
@ -92,6 +91,7 @@ const SlashSuggestion = ({
|
|||
const style = getStyleFromTheme(theme);
|
||||
const serverUrl = useServerUrl();
|
||||
const appCommandParser = useRef<AppCommandParser>(new AppCommandParser(serverUrl, intl, channelId, currentTeamId, rootId, theme));
|
||||
const mounted = useRef(false);
|
||||
|
||||
const [dataSource, setDataSource] = useState<AutocompleteSuggestion[]>(emptySuggestionList);
|
||||
const [commands, setCommands] = useState<Command[]>();
|
||||
|
|
@ -100,12 +100,15 @@ const SlashSuggestion = ({
|
|||
|
||||
const updateSuggestions = useCallback((matches: AutocompleteSuggestion[]) => {
|
||||
setDataSource(matches);
|
||||
onResultCountChange(matches.length);
|
||||
}, [onResultCountChange]);
|
||||
onShowingChange(Boolean(matches.length));
|
||||
}, [onShowingChange]);
|
||||
|
||||
const runFetch = useMemo(() => debounce(async (sUrl: string, term: string, tId: string, cId: string, rId?: string) => {
|
||||
try {
|
||||
const res = await fetchSuggestions(sUrl, term, tId, cId, rId);
|
||||
if (!mounted.current) {
|
||||
return;
|
||||
}
|
||||
if (res.error) {
|
||||
updateSuggestions(emptySuggestionList);
|
||||
} else if (res.suggestions.length === 0) {
|
||||
|
|
@ -127,7 +130,7 @@ const SlashSuggestion = ({
|
|||
const showBaseCommands = (text: string) => {
|
||||
let matches: AutocompleteSuggestion[] = [];
|
||||
|
||||
if (appsEnabled) {
|
||||
if (isAppsEnabled) {
|
||||
const appCommands = getAppBaseCommandSuggestions(text);
|
||||
matches = matches.concat(appCommands);
|
||||
}
|
||||
|
|
@ -135,10 +138,10 @@ const SlashSuggestion = ({
|
|||
matches = matches.concat(filterCommands(text.substring(1), commands!));
|
||||
|
||||
matches.sort((match1, match2) => {
|
||||
if (match1.suggestion === match2.suggestion) {
|
||||
if (match1.Suggestion === match2.Suggestion) {
|
||||
return 0;
|
||||
}
|
||||
return match1.suggestion > match2.suggestion ? 1 : -1;
|
||||
return match1.Suggestion > match2.Suggestion ? 1 : -1;
|
||||
});
|
||||
|
||||
updateSuggestions(matches);
|
||||
|
|
@ -154,30 +157,31 @@ const SlashSuggestion = ({
|
|||
completedDraft = `//${command} `;
|
||||
}
|
||||
|
||||
onChangeText(completedDraft);
|
||||
updateValue(completedDraft);
|
||||
|
||||
if (Platform.OS === 'ios') {
|
||||
// This is the second part of the hack were we replace the double / with just one
|
||||
// after the auto correct vanished
|
||||
setTimeout(() => {
|
||||
onChangeText(completedDraft.replace(`//${command} `, `/${command} `));
|
||||
updateValue(completedDraft.replace(`//${command} `, `/${command} `));
|
||||
});
|
||||
}
|
||||
}, [onChangeText, serverUrl]);
|
||||
}, [updateValue, serverUrl]);
|
||||
|
||||
const renderItem = useCallback(({item}: {item: AutocompleteSuggestion}) => (
|
||||
<SlashSuggestionItem
|
||||
description={item.description}
|
||||
hint={item.hint}
|
||||
description={item.Description}
|
||||
hint={item.Hint}
|
||||
onPress={completeSuggestion}
|
||||
suggestion={item.suggestion}
|
||||
complete={item.complete}
|
||||
icon={item.iconData}
|
||||
suggestion={item.Suggestion}
|
||||
complete={item.Complete}
|
||||
icon={item.IconData}
|
||||
/>
|
||||
), [completeSuggestion]);
|
||||
|
||||
useEffect(() => {
|
||||
if (value[0] !== '/') {
|
||||
runFetch.cancel();
|
||||
updateSuggestions(emptySuggestionList);
|
||||
return;
|
||||
}
|
||||
|
|
@ -194,6 +198,7 @@ const SlashSuggestion = ({
|
|||
}
|
||||
|
||||
if (value.indexOf(' ') === -1) {
|
||||
runFetch.cancel();
|
||||
showBaseCommands(value);
|
||||
return;
|
||||
}
|
||||
|
|
@ -201,6 +206,13 @@ const SlashSuggestion = ({
|
|||
runFetch(serverUrl, value, currentTeamId, channelId, rootId);
|
||||
}, [value, commands]);
|
||||
|
||||
useEffect(() => {
|
||||
mounted.current = true;
|
||||
return () => {
|
||||
mounted.current = false;
|
||||
};
|
||||
}, []);
|
||||
|
||||
if (!active) {
|
||||
// If we are not in an active state return null so nothing is rendered
|
||||
// other components are not blocked.
|
||||
|
|
|
|||
|
|
@ -59,11 +59,11 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme: Theme) => {
|
|||
|
||||
type Props = {
|
||||
complete: string;
|
||||
description?: string;
|
||||
hint?: string;
|
||||
description: string;
|
||||
hint: string;
|
||||
onPress: (complete: string) => void;
|
||||
suggestion?: string;
|
||||
icon?: string;
|
||||
suggestion: string;
|
||||
icon: string;
|
||||
}
|
||||
|
||||
const SlashSuggestionItem = ({
|
||||
|
|
@ -112,15 +112,16 @@ const SlashSuggestionItem = ({
|
|||
source={bangIcon}
|
||||
/>
|
||||
);
|
||||
} else if (icon && icon.startsWith('http')) {
|
||||
} else if (icon.startsWith('http')) {
|
||||
image = (
|
||||
<FastImage
|
||||
source={{uri: icon}}
|
||||
style={style.uriIcon}
|
||||
/>
|
||||
);
|
||||
} else if (icon && icon.startsWith('data:')) {
|
||||
} else if (icon.startsWith('data:')) {
|
||||
if (icon.startsWith('data:image/svg+xml')) {
|
||||
// TODO: What base64 library should we use? Security implications on doing things like this?
|
||||
const xml = ''; // base64.decode(icon.substring('data:image/svg+xml;base64,'.length));
|
||||
image = (
|
||||
<SvgXml
|
||||
|
|
|
|||
BIN
assets/base/images/autocomplete/slash_command_error.png
Normal file
BIN
assets/base/images/autocomplete/slash_command_error.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 288 B |
BIN
assets/base/images/autocomplete/slash_command_error@2x.png
Normal file
BIN
assets/base/images/autocomplete/slash_command_error@2x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 419 B |
BIN
assets/base/images/autocomplete/slash_command_error@3x.png
Normal file
BIN
assets/base/images/autocomplete/slash_command_error@3x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 625 B |
10
types/api/apps.d.ts
vendored
10
types/api/apps.d.ts
vendored
|
|
@ -178,11 +178,11 @@ type AppField = {
|
|||
};
|
||||
|
||||
type AutocompleteSuggestion = {
|
||||
suggestion: string;
|
||||
complete: string;
|
||||
description?: string;
|
||||
hint?: string;
|
||||
iconData?: string;
|
||||
Suggestion: string;
|
||||
Complete: string;
|
||||
Description: string;
|
||||
Hint: string;
|
||||
IconData: string;
|
||||
};
|
||||
|
||||
type AutocompleteSuggestionWithComplete = AutocompleteSuggestion & {
|
||||
|
|
|
|||
Loading…
Reference in a new issue