Link to autocomplete component and fix issues

This commit is contained in:
Daniel Espino García 2022-03-18 13:56:03 +01:00
parent 4f9eb6a3d9
commit 7ffcba44d6
10 changed files with 210 additions and 172 deletions

View file

@ -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}

View file

@ -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;

View file

@ -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,
};

View file

@ -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.

View file

@ -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.

View file

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 288 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 419 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 625 B

10
types/api/apps.d.ts vendored
View file

@ -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 & {