diff --git a/app/components/autocomplete/autocomplete.tsx b/app/components/autocomplete/autocomplete.tsx
index 0bc196f31e..cb445712cd 100644
--- a/app/components/autocomplete/autocomplete.tsx
+++ b/app/components/autocomplete/autocomplete.tsx
@@ -1,14 +1,15 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
-import React, {useEffect, useMemo, useState} from 'react';
-import {Keyboard, KeyboardEvent, Platform, useWindowDimensions, View} from 'react-native';
+import React, {useMemo, useState} from 'react';
+import {Platform, useWindowDimensions, View} from 'react-native';
+import {LIST_BOTTOM, MAX_LIST_DIFF, MAX_LIST_HEIGHT, MAX_LIST_TABLET_DIFF, OFFSET_TABLET} from '@constants/autocomplete';
import {useTheme} from '@context/theme';
import {useIsTablet} from '@hooks/device';
-import useHeaderHeight from '@hooks/header';
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
+import ChannelMention from './channel_mention/';
import EmojiSuggestion from './emoji_suggestion/';
const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
@@ -22,7 +23,8 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
borderWidth: 1,
borderColor: changeOpacity(theme.centerChannelColor, 0.2),
overflow: 'hidden',
- borderRadius: 4,
+ borderRadius: 8,
+ elevation: 3,
},
hidden: {
display: 'none',
@@ -40,10 +42,10 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
shadow: {
shadowColor: '#000',
shadowOpacity: 0.12,
- shadowRadius: 8,
+ shadowRadius: 6,
shadowOffset: {
width: 0,
- height: 8,
+ height: 6,
},
},
};
@@ -58,15 +60,11 @@ type Props = {
value: string;
enableDateSuggestion?: boolean;
isAppsEnabled: boolean;
- offsetY?: number;
nestedScrollEnabled?: boolean;
updateValue: (v: string) => void;
hasFilesAttached: boolean;
}
-const OFFSET_IPAD = 60;
-const AUTOCOMPLETE_MARGIN = 20;
-
const Autocomplete = ({
cursorPosition,
postInputTop,
@@ -78,52 +76,46 @@ const Autocomplete = ({
//enableDateSuggestion = false,
isAppsEnabled,
- offsetY = 60,
nestedScrollEnabled = false,
updateValue,
hasFilesAttached,
}: Props) => {
const theme = useTheme();
const isTablet = useIsTablet();
+ const dimensions = useWindowDimensions();
const style = getStyleFromTheme(theme);
- const {height: deviceHeight} = useWindowDimensions();
- const {defaultHeight: headerHeight} = useHeaderHeight(false, true, false);
- const [keyboardHeight, setKeyboardHeight] = useState(0);
// const [showingAtMention, setShowingAtMention] = useState(false);
- // const [showingChannelMention, setShowingChannelMention] = useState(false);
+ const [showingChannelMention, setShowingChannelMention] = useState(false);
const [showingEmoji, setShowingEmoji] = useState(false);
// const [showingCommand, setShowingCommand] = useState(false);
// const [showingAppCommand, setShowingAppCommand] = useState(false);
// const [showingDate, setShowingDate] = useState(false);
- const hasElements = showingEmoji; // || showingAtMention || showingChannelMention || showingCommand || showingAppCommand || showingDate;
+ const hasElements = showingChannelMention || showingEmoji; // || showingAtMention || showingCommand || showingAppCommand || showingDate;
const appsTakeOver = false; // showingAppCommand;
const maxListHeight = useMemo(() => {
- const postInputHeight = deviceHeight - postInputTop;
- let offset = 0;
- if (Platform.OS === 'ios' && isTablet) {
- offset = OFFSET_IPAD;
+ const isLandscape = dimensions.width > dimensions.height;
+ const offset = isTablet && isLandscape ? OFFSET_TABLET : 0;
+ let postInputDiff = 0;
+ if (isTablet && postInputTop && isLandscape) {
+ postInputDiff = MAX_LIST_TABLET_DIFF;
+ } else if (postInputTop) {
+ postInputDiff = MAX_LIST_DIFF;
}
-
- if (keyboardHeight) {
- return (deviceHeight - (postInputHeight + headerHeight + AUTOCOMPLETE_MARGIN + offset));
- }
- return (deviceHeight - (postInputHeight + headerHeight + AUTOCOMPLETE_MARGIN + offset)) / 2;
- }, [postInputTop, deviceHeight, headerHeight, isTablet]); // We don't depend on keyboardHeight to avoid visual artifacts due to postInputTop and keyboardHeight not being updated in the same render.
+ return MAX_LIST_HEIGHT - postInputDiff - offset;
+ }, [postInputTop, isTablet, dimensions.width]);
const wrapperStyles = useMemo(() => {
const s = [];
if (Platform.OS === 'ios') {
s.push(style.shadow);
}
-
if (isSearch) {
s.push(style.base, style.searchContainer, {height: maxListHeight});
}
-
if (!hasElements) {
s.push(style.hidden);
}
@@ -133,26 +125,14 @@ const Autocomplete = ({
const containerStyles = useMemo(() => {
const s = [style.borders];
if (!isSearch) {
- s.push(style.base, {bottom: offsetY});
+ const offset = isTablet ? -OFFSET_TABLET : 0;
+ s.push(style.base, {bottom: postInputTop + LIST_BOTTOM + offset});
}
if (!hasElements) {
s.push(style.hidden);
}
return s;
- }, [!isSearch && offsetY, hasElements]);
-
- useEffect(() => {
- const keyboardEvent = (event: KeyboardEvent) => {
- setKeyboardHeight(event.endCoordinates.height);
- };
- const shown = Keyboard.addListener('keyboardDidShow', keyboardEvent);
- const hidden = Keyboard.addListener('keyboardDidHide', keyboardEvent);
-
- return () => {
- shown.remove();
- hidden.remove();
- };
- }, []);
+ }, [!isSearch, isTablet, hasElements, postInputTop]);
return (
+ /> */}
*/}
+ isSearch={isSearch}
+ />
{!isSearch &&
{
+ return {
+ section: {
+ justifyContent: 'center',
+ position: 'relative',
+ top: -1,
+ flexDirection: 'row',
+ },
+ sectionText: {
+ fontSize: 12,
+ fontWeight: '600',
+ textTransform: 'uppercase',
+ color: changeOpacity(theme.centerChannelColor, 0.56),
+ paddingTop: 16,
+ paddingBottom: 8,
+ paddingHorizontal: 16,
+ flex: 1,
+ },
+ sectionWrapper: {
+ backgroundColor: theme.centerChannelBg,
+ },
+ };
+});
+
+type Props = {
+ defaultMessage: string;
+ id: string;
+ loading: boolean;
+}
+
+const AutocompleteSectionHeader = ({
+ defaultMessage,
+ id,
+ loading,
+}: Props) => {
+ const insets = useSafeAreaInsets();
+ const theme = useTheme();
+ const style = getStyleFromTheme(theme);
+
+ const sectionStyles = useMemo(() => {
+ return [style.section, {marginLeft: insets.left, marginRight: insets.right}];
+ }, [style, insets]);
+
+ return (
+
+
+
+ {loading &&
+
+ }
+
+
+ );
+};
+
+export default AutocompleteSectionHeader;
diff --git a/app/components/autocomplete/channel_mention/channel_mention.tsx b/app/components/autocomplete/channel_mention/channel_mention.tsx
new file mode 100644
index 0000000000..bc6dcaa64e
--- /dev/null
+++ b/app/components/autocomplete/channel_mention/channel_mention.tsx
@@ -0,0 +1,315 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+import {debounce} from 'lodash';
+import React, {useCallback, useEffect, useMemo, useState} from 'react';
+import {Platform, SectionList, SectionListData} from 'react-native';
+
+import {searchChannels} from '@actions/remote/channel';
+import AutocompleteSectionHeader from '@components/autocomplete/autocomplete_section_header';
+import ChannelMentionItem from '@components/autocomplete/channel_mention_item';
+import {General} from '@constants';
+import {CHANNEL_MENTION_REGEX, CHANNEL_MENTION_SEARCH_REGEX} from '@constants/autocomplete';
+import {useServerUrl} from '@context/server';
+import {useTheme} from '@context/theme';
+import useDidUpdate from '@hooks/did_update';
+import {t} from '@i18n';
+import {makeStyleSheetFromTheme} from '@utils/theme';
+
+import type MyChannelModel from '@typings/database/models/servers/my_channel';
+
+const keyExtractor = (item: Channel) => {
+ return item.id;
+};
+
+const getMatchTermForChannelMention = (() => {
+ let lastMatchTerm: string | null = null;
+ let lastValue: string;
+ let lastIsSearch: boolean;
+ return (value: string, isSearch: boolean) => {
+ if (value !== lastValue || isSearch !== lastIsSearch) {
+ const regex = isSearch ? CHANNEL_MENTION_SEARCH_REGEX : CHANNEL_MENTION_REGEX;
+ const match = value.match(regex);
+ lastValue = value;
+ lastIsSearch = isSearch;
+ if (match) {
+ if (isSearch) {
+ lastMatchTerm = match[1].toLowerCase();
+ } else if (match.index && match.index > 0 && value[match.index - 1] === '~') {
+ lastMatchTerm = null;
+ } else {
+ lastMatchTerm = match[2].toLowerCase();
+ }
+ } else {
+ lastMatchTerm = null;
+ }
+ }
+ return lastMatchTerm;
+ };
+})();
+
+const reduceChannelsForSearch = (channels: Channel[], members: MyChannelModel[]) => {
+ return channels.reduce(([pubC, priC, dms], c) => {
+ switch (c.type) {
+ case General.OPEN_CHANNEL:
+ if (members.find((m) => c.id === m.id)) {
+ pubC.push(c);
+ }
+ break;
+ case General.PRIVATE_CHANNEL:
+ priC.push(c);
+ break;
+ case General.DM_CHANNEL:
+ case General.GM_CHANNEL:
+ dms.push(c);
+ }
+ return [pubC, priC, dms];
+ }, [[], [], []]);
+};
+
+const reduceChannelsForAutocomplete = (channels: Channel[], members: MyChannelModel[]) => {
+ return channels.reduce(([myC, otherC], c) => {
+ if (members.find((m) => c.id === m.id)) {
+ myC.push(c);
+ } else {
+ otherC.push(c);
+ }
+ return [myC, otherC];
+ }, [[], []]);
+};
+
+const makeSections = (channels: Channel[], myMembers: MyChannelModel[], isSearch = false) => {
+ const newSections = [];
+ if (isSearch) {
+ const [publicChannels, privateChannels, directAndGroupMessages] = reduceChannelsForSearch(channels, myMembers);
+ if (publicChannels.length) {
+ newSections.push({
+ id: t('suggestion.search.public'),
+ defaultMessage: 'Public Channels',
+ data: publicChannels,
+ key: 'publicChannels',
+ hideLoadingIndicator: true,
+ });
+ }
+
+ if (privateChannels.length) {
+ newSections.push({
+ id: t('suggestion.search.private'),
+ defaultMessage: 'Private Channels',
+ data: privateChannels,
+ key: 'privateChannels',
+ hideLoadingIndicator: true,
+ });
+ }
+
+ if (directAndGroupMessages.length) {
+ newSections.push({
+ id: t('suggestion.search.direct'),
+ defaultMessage: 'Direct Messages',
+ data: directAndGroupMessages,
+ key: 'directAndGroupMessages',
+ hideLoadingIndicator: true,
+ });
+ }
+ } else {
+ const [myChannels, otherChannels] = reduceChannelsForAutocomplete(channels, myMembers);
+ if (myChannels.length) {
+ newSections.push({
+ id: t('suggestion.mention.channels'),
+ defaultMessage: 'My Channels',
+ data: myChannels,
+ key: 'myChannels',
+ hideLoadingIndicator: true,
+ });
+ }
+
+ if (otherChannels.length) {
+ newSections.push({
+ id: t('suggestion.mention.morechannels'),
+ defaultMessage: 'Other Channels',
+ data: otherChannels,
+ key: 'otherChannels',
+ hideLoadingIndicator: true,
+ });
+ }
+ }
+
+ const nSections = newSections.length;
+ if (nSections) {
+ newSections[nSections - 1].hideLoadingIndicator = false;
+ }
+
+ return newSections;
+};
+type Props = {
+ cursorPosition: number;
+ isSearch: boolean;
+ maxListHeight: number;
+ myMembers: MyChannelModel[];
+ updateValue: (v: string) => void;
+ onShowingChange: (c: boolean) => void;
+ value: string;
+ nestedScrollEnabled: boolean;
+}
+
+const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
+ return {
+ listView: {
+ backgroundColor: theme.centerChannelBg,
+ borderRadius: 4,
+ },
+ };
+});
+
+const ChannelMention = ({
+ cursorPosition,
+ isSearch,
+ maxListHeight,
+ myMembers,
+ updateValue,
+ onShowingChange,
+ value,
+ nestedScrollEnabled,
+}: Props) => {
+ const serverUrl = useServerUrl();
+ const theme = useTheme();
+ const style = getStyleFromTheme(theme);
+
+ const [sections, setSections] = useState>>([]);
+ const [channels, setChannels] = useState([]);
+ const [loading, setLoading] = useState(false);
+ const [noResultsTerm, setNoResultsTerm] = useState(null);
+ const [localCursorPosition, setLocalCursorPosition] = useState(cursorPosition); // To avoid errors due to delay between value changes and cursor position changes.
+
+ const listStyle = useMemo(() =>
+ [style.listView, {maxHeight: maxListHeight}]
+ , [style, maxListHeight]);
+
+ const runSearch = useMemo(() => debounce(async (sUrl: string, term: string) => {
+ setLoading(true);
+ const {channels: receivedChannels, error} = await searchChannels(sUrl, term);
+ if (!error) {
+ setChannels(receivedChannels!);
+ }
+ setLoading(false);
+ }, 200), []);
+
+ const matchTerm = getMatchTermForChannelMention(value.substring(0, localCursorPosition), isSearch);
+ const resetState = () => {
+ setChannels([]);
+ setSections([]);
+ runSearch.cancel();
+ };
+
+ const completeMention = useCallback((mention: string) => {
+ const mentionPart = value.substring(0, localCursorPosition);
+
+ let completedDraft: string;
+ if (isSearch) {
+ const channelOrIn = mentionPart.includes('in:') ? 'in:' : 'channel:';
+ completedDraft = mentionPart.replace(CHANNEL_MENTION_SEARCH_REGEX, `${channelOrIn} ${mention} `);
+ } else if (Platform.OS === 'ios') {
+ // We are going to set a double ~ on iOS to prevent the auto correct from taking over and replacing it
+ // with the wrong value, this is a hack but I could not found another way to solve it
+ completedDraft = mentionPart.replace(CHANNEL_MENTION_REGEX, `~~${mention} `);
+ } else {
+ completedDraft = mentionPart.replace(CHANNEL_MENTION_REGEX, `~${mention} `);
+ }
+
+ const newCursorPosition = completedDraft.length - 1;
+
+ if (value.length > localCursorPosition) {
+ completedDraft += value.substring(localCursorPosition);
+ }
+
+ updateValue(completedDraft);
+ setLocalCursorPosition(newCursorPosition);
+
+ 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(() => {
+ updateValue(completedDraft.replace(`~~${mention} `, `~${mention} `));
+ });
+ }
+
+ onShowingChange(false);
+ setNoResultsTerm(mention);
+ setSections([]);
+ }, [value, localCursorPosition, isSearch]);
+
+ const renderItem = useCallback(({item}) => {
+ return (
+
+ );
+ }, [completeMention]);
+
+ const renderSectionHeader = useCallback(({section}) => {
+ return (
+
+ );
+ }, [loading]);
+
+ useEffect(() => {
+ if (localCursorPosition !== cursorPosition) {
+ setLocalCursorPosition(cursorPosition);
+ }
+ }, [cursorPosition]);
+
+ useEffect(() => {
+ if (matchTerm === null) {
+ resetState();
+ onShowingChange(false);
+ return;
+ }
+
+ if (noResultsTerm != null && matchTerm.startsWith(noResultsTerm)) {
+ return;
+ }
+
+ setNoResultsTerm(null);
+ runSearch(serverUrl, matchTerm);
+ }, [matchTerm]);
+
+ useDidUpdate(() => {
+ const newSections = makeSections(channels, myMembers, isSearch);
+ const nSections = newSections.length;
+
+ if (!loading && !nSections && noResultsTerm == null) {
+ setNoResultsTerm(matchTerm);
+ }
+ setSections(newSections);
+ onShowingChange(Boolean(nSections));
+ }, [channels, myMembers, loading]);
+
+ if (sections.length === 0 || noResultsTerm != null) {
+ // If we are not in an active state or the mention has been completed return null so nothing is rendered
+ // other components are not blocked.
+ return null;
+ }
+
+ return (
+
+ );
+};
+
+export default ChannelMention;
diff --git a/app/components/autocomplete/channel_mention/index.ts b/app/components/autocomplete/channel_mention/index.ts
new file mode 100644
index 0000000000..42fe679107
--- /dev/null
+++ b/app/components/autocomplete/channel_mention/index.ts
@@ -0,0 +1,21 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
+import withObservables from '@nozbe/with-observables';
+
+import {MM_TABLES} from '@constants/database';
+
+import ChannelMention from './channel_mention';
+
+import type {WithDatabaseArgs} from '@typings/database/database';
+import type MyChannelModel from '@typings/database/models/servers/my_channel';
+
+const {SERVER: {MY_CHANNEL}} = MM_TABLES;
+const enhanced = withObservables([], ({database}: WithDatabaseArgs) => {
+ return {
+ myMembers: database.get(MY_CHANNEL).query().observe(),
+ };
+});
+
+export default withDatabase(enhanced(ChannelMention));
diff --git a/app/components/autocomplete/channel_mention_item/channel_mention_item.tsx b/app/components/autocomplete/channel_mention_item/channel_mention_item.tsx
new file mode 100644
index 0000000000..aa0a741306
--- /dev/null
+++ b/app/components/autocomplete/channel_mention_item/channel_mention_item.tsx
@@ -0,0 +1,129 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+import React, {useMemo} from 'react';
+import {Text, View} from 'react-native';
+import {useSafeAreaInsets} from 'react-native-safe-area-context';
+
+import ChannelIcon from '@app/components/channel_icon';
+import {BotTag, GuestTag} from '@components/tag';
+import TouchableWithFeedback from '@components/touchable_with_feedback';
+import {General} from '@constants';
+import {useTheme} from '@context/theme';
+import {makeStyleSheetFromTheme, changeOpacity} from '@utils/theme';
+
+const getStyleFromTheme = makeStyleSheetFromTheme((theme: Theme) => {
+ return {
+ icon: {
+ marginRight: 11,
+ opacity: 0.56,
+ },
+ row: {
+ paddingHorizontal: 16,
+ height: 40,
+ flexDirection: 'row',
+ alignItems: 'center',
+ },
+ rowDisplayName: {
+ fontSize: 15,
+ color: theme.centerChannelColor,
+ },
+ rowName: {
+ fontSize: 15,
+ color: theme.centerChannelColor,
+ opacity: 0.56,
+ },
+ };
+});
+
+type Props = {
+ channel: Channel;
+ displayName?: string;
+ isBot: boolean;
+ isGuest: boolean;
+ onPress: (name?: string) => void;
+ testID?: string;
+};
+
+const ChannelMentionItem = ({
+ channel,
+ displayName,
+ isBot,
+ isGuest,
+ onPress,
+ testID,
+}: Props) => {
+ const insets = useSafeAreaInsets();
+ const theme = useTheme();
+
+ const completeMention = () => {
+ if (channel.type === General.DM_CHANNEL || channel.type === General.GM_CHANNEL) {
+ onPress('@' + displayName?.replace(/ /g, ''));
+ } else {
+ onPress(channel.name);
+ }
+ };
+
+ const style = getStyleFromTheme(theme);
+ const margins = useMemo(() => {
+ return {marginLeft: insets.left, marginRight: insets.right};
+ }, [insets]);
+ const rowStyle = useMemo(() => {
+ return [style.row, margins];
+ }, [margins, style]);
+
+ let component;
+
+ if (channel.type === General.DM_CHANNEL || channel.type === General.GM_CHANNEL) {
+ if (!displayName) {
+ return null;
+ }
+
+ component = (
+
+ {'@' + displayName}
+
+
+
+ );
+ } else {
+ component = (
+
+
+ 0}
+ size={18}
+ style={style.icon}
+ />
+ {displayName}
+ {` ~${channel.name}`}
+
+
+ );
+ }
+
+ return component;
+};
+
+export default ChannelMentionItem;
diff --git a/app/components/autocomplete/channel_mention_item/index.ts b/app/components/autocomplete/channel_mention_item/index.ts
new file mode 100644
index 0000000000..8231897d2a
--- /dev/null
+++ b/app/components/autocomplete/channel_mention_item/index.ts
@@ -0,0 +1,39 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
+import withObservables from '@nozbe/with-observables';
+import {of as of$} from 'rxjs';
+import {switchMap} from 'rxjs/operators';
+
+import {General} from '@constants';
+import {MM_TABLES} from '@constants/database';
+
+import ChannelMentionItem from './channel_mention_item';
+
+import type {WithDatabaseArgs} from '@typings/database/database';
+import type UserModel from '@typings/database/models/servers/user';
+
+type OwnProps = {
+ channel: Channel;
+}
+
+const {SERVER: {USER}} = MM_TABLES;
+const enhanced = withObservables([], ({database, channel}: WithDatabaseArgs & OwnProps) => {
+ let user = of$(undefined);
+ if (channel.type === General.DM_CHANNEL) {
+ user = database.get(USER).findAndObserve(channel.teammate_id!);
+ }
+
+ const isBot = user.pipe(switchMap((u) => of$(u ? u.isBot : false)));
+ const isGuest = user.pipe(switchMap((u) => of$(u ? u.isGuest : false)));
+ const displayName = user.pipe(switchMap((u) => of$(u ? u.username : channel.display_name)));
+
+ return {
+ isBot,
+ isGuest,
+ displayName,
+ };
+});
+
+export default withDatabase(enhanced(ChannelMentionItem));
diff --git a/app/components/autocomplete/emoji_suggestion/emoji_suggestion.tsx b/app/components/autocomplete/emoji_suggestion/emoji_suggestion.tsx
index f0eaf86535..d08f114c38 100644
--- a/app/components/autocomplete/emoji_suggestion/emoji_suggestion.tsx
+++ b/app/components/autocomplete/emoji_suggestion/emoji_suggestion.tsx
@@ -49,7 +49,7 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
listView: {
paddingTop: 16,
backgroundColor: theme.centerChannelBg,
- borderRadius: 4,
+ borderRadius: 8,
},
row: {
flexDirection: 'row',
@@ -167,6 +167,7 @@ const EmojiSuggestion = ({
const renderItem = useCallback(({item}: {item: string}) => {
const completeItemSuggestion = () => completeSuggestion(item);
+
return (
{
- updatePostInputTop(e.nativeEvent.layout.y);
+ updatePostInputTop(e.nativeEvent.layout.height);
}, []);
// Render
diff --git a/app/components/post_draft/post_draft.tsx b/app/components/post_draft/post_draft.tsx
index 8b44128101..288ebd9ee3 100644
--- a/app/components/post_draft/post_draft.tsx
+++ b/app/components/post_draft/post_draft.tsx
@@ -112,7 +112,6 @@ export default function PostDraft({
updateValue={setValue}
rootId={rootId}
channelId={channelId}
- offsetY={0}
cursorPosition={cursorPosition}
value={value}
isSearch={isSearch}
@@ -123,17 +122,14 @@ export default function PostDraft({
if (Platform.OS === 'android') {
return (
<>
- {autoComplete}
{draftHandler}
+ {autoComplete}
>
);
}
return (
<>
-
- {autoComplete}
-
{draftHandler}
+
+ {autoComplete}
+
>
);
}
diff --git a/app/components/post_draft/post_input/post_input.tsx b/app/components/post_draft/post_input/post_input.tsx
index 0489dbdf76..f508d72c7a 100644
--- a/app/components/post_draft/post_input/post_input.tsx
+++ b/app/components/post_draft/post_input/post_input.tsx
@@ -265,7 +265,6 @@ export default function PostInput({
// May change when we implement Fabric
input.current?.setNativeProps({
text: value,
- selection: {start: cursorPosition},
});
lastNativeValue.current = value;
}
diff --git a/app/constants/autocomplete.ts b/app/constants/autocomplete.ts
index 5b99e31781..c6897ef4e4 100644
--- a/app/constants/autocomplete.ts
+++ b/app/constants/autocomplete.ts
@@ -1,6 +1,8 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
+import {Platform} from 'react-native';
+
export const AT_MENTION_REGEX = /\B(@([^@\r\n]*))$/i;
export const AT_MENTION_REGEX_GLOBAL = /\B(@([^@\r\n]*))/gi;
@@ -17,6 +19,13 @@ export const ALL_SEARCH_FLAGS_REGEX = /\b\w+:/g;
export const CODE_REGEX = /(`+)([^`]|[^`][\s\S]*?[^`])\1(?!`)| *(`{3,}|~{3,})[ .]*(\S+)? *\n([\s\S]*?\s*)\3 *(?:\n+|$)/g;
+export const LIST_BOTTOM = Platform.select({ios: 30, default: -5});
+
+export const MAX_LIST_HEIGHT = 280;
+export const MAX_LIST_DIFF = 50;
+export const MAX_LIST_TABLET_DIFF = 140;
+export const OFFSET_TABLET = 35;
+
export default {
ALL_SEARCH_FLAGS_REGEX,
AT_MENTION_REGEX,
@@ -26,4 +35,8 @@ export default {
CHANNEL_MENTION_SEARCH_REGEX,
CODE_REGEX,
DATE_MENTION_SEARCH_REGEX,
+ LIST_BOTTOM,
+ MAX_LIST_HEIGHT,
+ MAX_LIST_DIFF,
+ OFFSET_TABLET,
};