forked from Ivasoft/mattermost-mobile
* WIP: slash suggestion autocomplete
* WIP: patched styles a bit
* WIP: Adding styles
* Adding active state to autocomplete items
* Fixing style for channel mention item
* Fixing bugs + styling issues for Android
* Updating snapshot
* Fixing autocomplete to render on top of post draft
- Misc style fixes
* Renaming props, patching slash suggestion icon
* Fixing tests and lint errors
* Resolving post-merge issue with slash commands
* Fixing android positioning for autocomplete
* Fixing autocomplete not scrolling in edit_channel_info
* WIP: Fixing things according to UX Review
* UX Fixes to autocomplete
* Updating snapshots
* Updating snapshots, replacing slash-command icons
* Fixing android scrolling and positioning issues
* Fixing issues with date_suggestion not rendering
* Making use of the "ShowFullName" config in at_mention_item
* Removing top border on first autocomplete section
* Allowing autocomplete to be smaller than its maxWidth
* Fixing slash_suggestion padding
* removing "componentWillReceiveProps" from date_suggestion
* Changing edit_channel_info autocomplete offset
* Replacing toUpperCase() with textTransform: uppercase
* Fixing odd border issues + prop validation warning
* Restore section header background & add paddingBottom
* Patching up padding on channel mentions
- Reverting previous incorrect padding adjustments
* Removing inline 'completeSuggestion' function
* Removing brackets from style prop
(cherry picked from commit 59045e3bb0)
Co-authored-by: Andre Vasconcelos <andre.onogoro@gmail.com>
This commit is contained in:
@@ -9,7 +9,6 @@ import {RequestStatus} from '@mm-redux/constants';
|
||||
|
||||
import {AT_MENTION_REGEX, AT_MENTION_SEARCH_REGEX} from 'app/constants/autocomplete';
|
||||
import AtMentionItem from 'app/components/autocomplete/at_mention_item';
|
||||
import AutocompleteDivider from 'app/components/autocomplete/autocomplete_divider';
|
||||
import AutocompleteSectionHeader from 'app/components/autocomplete/autocomplete_section_header';
|
||||
import SpecialMentionItem from 'app/components/autocomplete/special_mention_item';
|
||||
import GroupMentionItem from 'app/components/autocomplete/at_mention_group/at_mention_group';
|
||||
@@ -67,7 +66,7 @@ export default class AtMention extends PureComponent {
|
||||
mentionComplete: false,
|
||||
sections: [],
|
||||
});
|
||||
|
||||
this.props.onResultCountChange(0);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -208,12 +207,14 @@ export default class AtMention extends PureComponent {
|
||||
};
|
||||
|
||||
renderSectionHeader = ({section}) => {
|
||||
const isFirstSection = section.id === this.state.sections[0].id;
|
||||
return (
|
||||
<AutocompleteSectionHeader
|
||||
id={section.id}
|
||||
defaultMessage={section.defaultMessage}
|
||||
theme={this.props.theme}
|
||||
isLandscape={this.props.isLandscape}
|
||||
isFirstSection={isFirstSection}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -270,7 +271,6 @@ export default class AtMention extends PureComponent {
|
||||
sections={sections}
|
||||
renderItem={this.renderItem}
|
||||
renderSectionHeader={this.renderSectionHeader}
|
||||
ItemSeparatorComponent={AutocompleteDivider}
|
||||
initialNumToRender={10}
|
||||
nestedScrollEnabled={nestedScrollEnabled}
|
||||
/>
|
||||
@@ -282,6 +282,7 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
listView: {
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
borderRadius: 4,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
@@ -12,7 +12,7 @@ import ProfilePicture from 'app/components/profile_picture';
|
||||
import {paddingHorizontal as padding} from 'app/components/safe_area_view/iphone_x_spacing';
|
||||
import {BotTag, GuestTag} from 'app/components/tag';
|
||||
import TouchableWithFeedback from 'app/components/touchable_with_feedback';
|
||||
import {makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
import {makeStyleSheetFromTheme, changeOpacity} from 'app/utils/theme';
|
||||
import FormattedText from 'app/components/formatted_text';
|
||||
|
||||
export default class AtMentionItem extends PureComponent {
|
||||
@@ -28,6 +28,7 @@ export default class AtMentionItem extends PureComponent {
|
||||
theme: PropTypes.object.isRequired,
|
||||
isLandscape: PropTypes.bool.isRequired,
|
||||
isCurrentUser: PropTypes.bool.isRequired,
|
||||
showFullName: PropTypes.string,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
@@ -40,11 +41,24 @@ export default class AtMentionItem extends PureComponent {
|
||||
onPress(username);
|
||||
};
|
||||
|
||||
renderNameBlock = () => {
|
||||
let name = '';
|
||||
const {showFullName, firstName, lastName, nickname} = this.props;
|
||||
const hasNickname = nickname.length > 0;
|
||||
|
||||
if (showFullName === 'true') {
|
||||
name += `${firstName} ${lastName} `;
|
||||
}
|
||||
|
||||
if (hasNickname) {
|
||||
name += `(${nickname})`;
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
firstName,
|
||||
lastName,
|
||||
nickname,
|
||||
userId,
|
||||
username,
|
||||
theme,
|
||||
@@ -55,46 +69,52 @@ export default class AtMentionItem extends PureComponent {
|
||||
} = this.props;
|
||||
|
||||
const style = getStyleFromTheme(theme);
|
||||
const hasFullName = firstName.length > 0 && lastName.length > 0;
|
||||
const hasNickname = nickname.length > 0;
|
||||
const name = this.renderNameBlock();
|
||||
|
||||
return (
|
||||
<TouchableWithFeedback
|
||||
key={userId}
|
||||
onPress={this.completeMention}
|
||||
style={[style.row, padding(isLandscape)]}
|
||||
type={'opacity'}
|
||||
style={padding(isLandscape)}
|
||||
underlayColor={changeOpacity(theme.buttonBg, 0.08)}
|
||||
type={'native'}
|
||||
>
|
||||
<View style={style.rowPicture}>
|
||||
<ProfilePicture
|
||||
userId={userId}
|
||||
<View style={style.row}>
|
||||
<View style={style.rowPicture}>
|
||||
<ProfilePicture
|
||||
userId={userId}
|
||||
theme={theme}
|
||||
size={24}
|
||||
status={null}
|
||||
showStatus={false}
|
||||
/>
|
||||
</View>
|
||||
<BotTag
|
||||
show={isBot}
|
||||
theme={theme}
|
||||
size={20}
|
||||
status={null}
|
||||
/>
|
||||
<GuestTag
|
||||
show={isGuest}
|
||||
theme={theme}
|
||||
/>
|
||||
<Text
|
||||
style={style.rowFullname}
|
||||
numberOfLines={1}
|
||||
>
|
||||
{name}
|
||||
{isCurrentUser &&
|
||||
<FormattedText
|
||||
id='suggestion.mention.you'
|
||||
defaultMessage='(you)'
|
||||
/>}
|
||||
</Text>
|
||||
<Text
|
||||
numberOfLines={1}
|
||||
style={style.rowUsername}
|
||||
>
|
||||
{` @${username}`}
|
||||
</Text>
|
||||
</View>
|
||||
<Text style={style.rowUsername}>{`@${username}`}</Text>
|
||||
<BotTag
|
||||
show={isBot}
|
||||
theme={theme}
|
||||
/>
|
||||
<GuestTag
|
||||
show={isGuest}
|
||||
theme={theme}
|
||||
/>
|
||||
{hasFullName && <Text style={style.rowUsername}>{' - '}</Text>}
|
||||
<Text
|
||||
style={style.rowFullname}
|
||||
numberOfLines={1}
|
||||
>
|
||||
{hasFullName && `${firstName} ${lastName}`}
|
||||
{hasNickname && ` (${nickname}) `}
|
||||
{isCurrentUser &&
|
||||
<FormattedText
|
||||
id='suggestion.mention.you'
|
||||
defaultMessage='(you)'
|
||||
/>}
|
||||
</Text>
|
||||
</TouchableWithFeedback>
|
||||
);
|
||||
}
|
||||
@@ -103,24 +123,28 @@ export default class AtMentionItem extends PureComponent {
|
||||
const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
row: {
|
||||
height: 40,
|
||||
paddingVertical: 8,
|
||||
paddingTop: 4,
|
||||
paddingHorizontal: 16,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
},
|
||||
rowPicture: {
|
||||
marginHorizontal: 8,
|
||||
width: 20,
|
||||
marginRight: 10,
|
||||
marginLeft: 2,
|
||||
width: 24,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
rowUsername: {
|
||||
fontSize: 13,
|
||||
rowFullname: {
|
||||
fontSize: 15,
|
||||
color: theme.centerChannelColor,
|
||||
},
|
||||
rowFullname: {
|
||||
rowUsername: {
|
||||
color: theme.centerChannelColor,
|
||||
opacity: 0.6,
|
||||
fontSize: 15,
|
||||
opacity: 0.56,
|
||||
flex: 1,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -6,6 +6,7 @@ import {connect} from 'react-redux';
|
||||
import {getCurrentUserId, getUser} from '@mm-redux/selectors/entities/users';
|
||||
|
||||
import {getTheme} from '@mm-redux/selectors/entities/preferences';
|
||||
import {getConfig} from '@mm-redux/selectors/entities/general';
|
||||
|
||||
import AtMentionItem from './at_mention_item';
|
||||
|
||||
@@ -14,12 +15,13 @@ import {isGuest} from 'app/utils/users';
|
||||
|
||||
function mapStateToProps(state, ownProps) {
|
||||
const user = getUser(state, ownProps.userId);
|
||||
|
||||
const config = getConfig(state);
|
||||
return {
|
||||
firstName: user.first_name,
|
||||
lastName: user.last_name,
|
||||
nickname: user.nickname,
|
||||
username: user.username,
|
||||
showFullName: config.ShowFullName,
|
||||
isBot: Boolean(user.is_bot),
|
||||
isGuest: isGuest(user),
|
||||
theme: getTheme(state),
|
||||
|
||||
@@ -36,8 +36,9 @@ export default class Autocomplete extends PureComponent {
|
||||
valueEvent: PropTypes.string,
|
||||
cursorPositionEvent: PropTypes.string,
|
||||
nestedScrollEnabled: PropTypes.bool,
|
||||
expandDown: PropTypes.bool,
|
||||
onVisible: PropTypes.func,
|
||||
offsetY: PropTypes.number,
|
||||
onKeyboardOffsetChanged: PropTypes.func,
|
||||
style: ViewPropTypes.style,
|
||||
};
|
||||
|
||||
@@ -47,6 +48,8 @@ export default class Autocomplete extends PureComponent {
|
||||
enableDateSuggestion: false,
|
||||
nestedScrollEnabled: false,
|
||||
onVisible: emptyFunction,
|
||||
onKeyboardOffsetChanged: emptyFunction,
|
||||
offsetY: 80,
|
||||
};
|
||||
|
||||
static getDerivedStateFromProps(props, state) {
|
||||
@@ -149,10 +152,12 @@ export default class Autocomplete extends PureComponent {
|
||||
keyboardDidShow = (e) => {
|
||||
const {height} = e.endCoordinates;
|
||||
this.setState({keyboardOffset: height});
|
||||
this.props.onKeyboardOffsetChanged(height);
|
||||
};
|
||||
|
||||
keyboardDidHide = () => {
|
||||
this.setState({keyboardOffset: 0});
|
||||
this.props.onKeyboardOffsetChanged(0);
|
||||
};
|
||||
|
||||
maxListHeight() {
|
||||
@@ -166,42 +171,37 @@ export default class Autocomplete extends PureComponent {
|
||||
offset = 90;
|
||||
}
|
||||
|
||||
maxHeight = this.props.deviceHeight - offset - this.state.keyboardOffset;
|
||||
maxHeight = (this.props.deviceHeight / 2) - offset;
|
||||
}
|
||||
|
||||
return maxHeight;
|
||||
}
|
||||
|
||||
render() {
|
||||
const {theme, isSearch, expandDown} = this.props;
|
||||
const {atMentionCount, channelMentionCount, emojiCount, commandCount, dateCount, cursorPosition, value} = this.state;
|
||||
const {theme, isSearch, offsetY} = this.props;
|
||||
const style = getStyleFromTheme(theme);
|
||||
|
||||
const maxListHeight = this.maxListHeight();
|
||||
const wrapperStyles = [];
|
||||
const containerStyles = [];
|
||||
const containerStyles = [style.borders];
|
||||
|
||||
if (Platform.OS === 'ios') {
|
||||
wrapperStyles.push(style.shadow);
|
||||
}
|
||||
|
||||
if (isSearch) {
|
||||
wrapperStyles.push(style.base, style.searchContainer);
|
||||
containerStyles.push(style.content);
|
||||
wrapperStyles.push(style.base, style.searchContainer, {height: maxListHeight});
|
||||
} else {
|
||||
const containerStyle = expandDown ? style.containerExpandDown : style.container;
|
||||
const containerStyle = {bottom: offsetY};
|
||||
containerStyles.push(style.base, containerStyle);
|
||||
}
|
||||
|
||||
// We always need to render something, but we only draw the borders when we have results to show
|
||||
const {atMentionCount, channelMentionCount, emojiCount, commandCount, dateCount, cursorPosition, value} = this.state;
|
||||
if (atMentionCount + channelMentionCount + emojiCount + commandCount + dateCount > 0) {
|
||||
if (this.props.isSearch) {
|
||||
wrapperStyles.push(style.bordersSearch);
|
||||
} else {
|
||||
containerStyles.push(style.borders);
|
||||
}
|
||||
// Hide when there are no active autocompletes
|
||||
if (atMentionCount + channelMentionCount + emojiCount + commandCount + dateCount === 0) {
|
||||
wrapperStyles.push(style.hidden);
|
||||
containerStyles.push(style.hidden);
|
||||
}
|
||||
|
||||
if (this.props.style) {
|
||||
containerStyles.push(this.props.style);
|
||||
}
|
||||
|
||||
const maxListHeight = this.maxListHeight();
|
||||
|
||||
return (
|
||||
<View style={wrapperStyles}>
|
||||
<View
|
||||
@@ -261,39 +261,37 @@ export default class Autocomplete extends PureComponent {
|
||||
const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
base: {
|
||||
left: 0,
|
||||
overflow: 'hidden',
|
||||
left: 8,
|
||||
position: 'absolute',
|
||||
right: 0,
|
||||
right: 8,
|
||||
},
|
||||
borders: {
|
||||
borderWidth: 1,
|
||||
borderColor: changeOpacity(theme.centerChannelColor, 0.2),
|
||||
borderBottomWidth: 0,
|
||||
overflow: 'hidden',
|
||||
borderRadius: 4,
|
||||
},
|
||||
bordersSearch: {
|
||||
borderWidth: 1,
|
||||
borderColor: changeOpacity(theme.centerChannelColor, 0.2),
|
||||
},
|
||||
container: {
|
||||
bottom: 0,
|
||||
},
|
||||
containerExpandDown: {
|
||||
top: 0,
|
||||
},
|
||||
content: {
|
||||
flex: 1,
|
||||
hidden: {
|
||||
display: 'none',
|
||||
},
|
||||
searchContainer: {
|
||||
flex: 1,
|
||||
...Platform.select({
|
||||
android: {
|
||||
top: 46,
|
||||
top: 42,
|
||||
},
|
||||
ios: {
|
||||
top: 44,
|
||||
top: 55,
|
||||
},
|
||||
}),
|
||||
},
|
||||
shadow: {
|
||||
shadowColor: '#000',
|
||||
shadowOpacity: 0.12,
|
||||
shadowRadius: 8,
|
||||
shadowOffset: {
|
||||
width: 0,
|
||||
height: 8,
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {PureComponent} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {View} from 'react-native';
|
||||
|
||||
import {makeStyleSheetFromTheme, changeOpacity} from 'app/utils/theme';
|
||||
|
||||
export default class AutocompleteDivider extends PureComponent {
|
||||
static propTypes = {
|
||||
theme: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
render() {
|
||||
const {theme} = this.props;
|
||||
const style = getStyleFromTheme(theme);
|
||||
|
||||
return (
|
||||
<View style={style.divider}/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
divider: {
|
||||
height: 1,
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.2),
|
||||
},
|
||||
};
|
||||
});
|
||||
@@ -1,16 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {getTheme} from '@mm-redux/selectors/entities/preferences';
|
||||
|
||||
import AutocompleteDivider from './autocomplete_divider';
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
theme: getTheme(state),
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(AutocompleteDivider);
|
||||
@@ -16,6 +16,7 @@ export default class AutocompleteSectionHeader extends PureComponent {
|
||||
loading: PropTypes.bool,
|
||||
theme: PropTypes.object.isRequired,
|
||||
isLandscape: PropTypes.bool.isRequired,
|
||||
isFirstSection: PropTypes.bool,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
@@ -23,12 +24,17 @@ export default class AutocompleteSectionHeader extends PureComponent {
|
||||
};
|
||||
|
||||
render() {
|
||||
const {defaultMessage, id, loading, theme, isLandscape} = this.props;
|
||||
const {defaultMessage, id, loading, theme, isLandscape, isFirstSection} = this.props;
|
||||
const style = getStyleFromTheme(theme);
|
||||
const sectionStyles = [style.section, padding(isLandscape)];
|
||||
|
||||
if (!isFirstSection) {
|
||||
sectionStyles.push(style.borderTop);
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={style.sectionWrapper}>
|
||||
<View style={[style.section, padding(isLandscape)]}>
|
||||
<View style={sectionStyles}>
|
||||
<FormattedText
|
||||
id={id}
|
||||
defaultMessage={defaultMessage}
|
||||
@@ -48,18 +54,24 @@ export default class AutocompleteSectionHeader extends PureComponent {
|
||||
|
||||
const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
section: {
|
||||
justifyContent: 'center',
|
||||
paddingHorizontal: 8,
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.1),
|
||||
borderTop: {
|
||||
borderTopWidth: 1,
|
||||
borderTopColor: changeOpacity(theme.centerChannelColor, 0.2),
|
||||
},
|
||||
section: {
|
||||
justifyContent: 'center',
|
||||
position: 'relative',
|
||||
top: -1,
|
||||
flexDirection: 'row',
|
||||
},
|
||||
sectionText: {
|
||||
fontSize: 12,
|
||||
color: changeOpacity(theme.centerChannelColor, 0.7),
|
||||
paddingVertical: 7,
|
||||
fontWeight: '600',
|
||||
textTransform: 'uppercase',
|
||||
color: changeOpacity(theme.centerChannelColor, 0.56),
|
||||
paddingTop: 16,
|
||||
paddingBottom: 8,
|
||||
paddingHorizontal: 16,
|
||||
flex: 1,
|
||||
},
|
||||
sectionWrapper: {
|
||||
|
||||
@@ -184,6 +184,7 @@ export default class ChannelMention extends PureComponent {
|
||||
};
|
||||
|
||||
renderSectionHeader = ({section}) => {
|
||||
const isFirstSection = section.id === this.state.sections[0].id;
|
||||
return (
|
||||
<AutocompleteSectionHeader
|
||||
id={section.id}
|
||||
@@ -191,6 +192,7 @@ export default class ChannelMention extends PureComponent {
|
||||
loading={!section.hideLoadingIndicator && this.props.requestStatus === RequestStatus.STARTED}
|
||||
theme={this.props.theme}
|
||||
isLandscape={this.props.isLandscape}
|
||||
isFirstSection={isFirstSection}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -235,6 +237,7 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
listView: {
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
borderRadius: 4,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
@@ -5,14 +5,15 @@ import React, {PureComponent} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
Text,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import VectorIcon from 'app/components/vector_icon.js';
|
||||
|
||||
import {General} from '@mm-redux/constants';
|
||||
import AutocompleteDivider from 'app/components/autocomplete/autocomplete_divider';
|
||||
import {BotTag, GuestTag} from 'app/components/tag';
|
||||
import {paddingHorizontal as padding} from 'app/components/safe_area_view/iphone_x_spacing';
|
||||
import TouchableWithFeedback from 'app/components/touchable_with_feedback';
|
||||
import {makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
import {makeStyleSheetFromTheme, changeOpacity} from 'app/utils/theme';
|
||||
|
||||
export default class ChannelMentionItem extends PureComponent {
|
||||
static propTypes = {
|
||||
@@ -49,8 +50,12 @@ export default class ChannelMentionItem extends PureComponent {
|
||||
} = this.props;
|
||||
|
||||
const style = getStyleFromTheme(theme);
|
||||
|
||||
let iconName = 'public';
|
||||
let component;
|
||||
if (type === General.PRIVATE_CHANNEL) {
|
||||
iconName = 'private';
|
||||
}
|
||||
|
||||
if (type === General.DM_CHANNEL || type === General.GM_CHANNEL) {
|
||||
if (!displayName) {
|
||||
return null;
|
||||
@@ -79,11 +84,19 @@ export default class ChannelMentionItem extends PureComponent {
|
||||
<TouchableWithFeedback
|
||||
key={channelId}
|
||||
onPress={this.completeMention}
|
||||
style={[style.row, padding(isLandscape)]}
|
||||
type={'opacity'}
|
||||
style={padding(isLandscape)}
|
||||
underlayColor={changeOpacity(theme.buttonBg, 0.08)}
|
||||
type={'native'}
|
||||
>
|
||||
<Text style={style.rowDisplayName}>{displayName}</Text>
|
||||
<Text style={style.rowName}>{` (~${name})`}</Text>
|
||||
<View style={style.row}>
|
||||
<VectorIcon
|
||||
name={iconName}
|
||||
type={'mattermost'}
|
||||
style={style.icon}
|
||||
/>
|
||||
<Text style={style.rowDisplayName}>{displayName}</Text>
|
||||
<Text style={style.rowName}>{` ~${name}`}</Text>
|
||||
</View>
|
||||
</TouchableWithFeedback>
|
||||
);
|
||||
}
|
||||
@@ -91,7 +104,6 @@ export default class ChannelMentionItem extends PureComponent {
|
||||
return (
|
||||
<React.Fragment>
|
||||
{component}
|
||||
<AutocompleteDivider/>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
@@ -99,19 +111,26 @@ export default class ChannelMentionItem extends PureComponent {
|
||||
|
||||
const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
icon: {
|
||||
fontSize: 18,
|
||||
marginRight: 11,
|
||||
color: theme.centerChannelColor,
|
||||
opacity: 0.56,
|
||||
},
|
||||
row: {
|
||||
padding: 8,
|
||||
paddingHorizontal: 16,
|
||||
height: 40,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
},
|
||||
rowDisplayName: {
|
||||
fontSize: 13,
|
||||
fontSize: 15,
|
||||
color: theme.centerChannelColor,
|
||||
},
|
||||
rowName: {
|
||||
fontSize: 15,
|
||||
color: theme.centerChannelColor,
|
||||
opacity: 0.6,
|
||||
opacity: 0.56,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {PureComponent} from 'react';
|
||||
import {Dimensions, Platform, StyleSheet} from 'react-native';
|
||||
import {Dimensions, Platform, View} from 'react-native';
|
||||
import PropTypes from 'prop-types';
|
||||
import {CalendarList, LocaleConfig} from 'react-native-calendars';
|
||||
import {intlShape} from 'react-intl';
|
||||
@@ -10,7 +10,7 @@ import {intlShape} from 'react-intl';
|
||||
import {memoizeResult} from '@mm-redux/utils/helpers';
|
||||
|
||||
import {DATE_MENTION_SEARCH_REGEX, ALL_SEARCH_FLAGS_REGEX} from 'app/constants/autocomplete';
|
||||
import {changeOpacity} from 'app/utils/theme';
|
||||
import {makeStyleSheetFromTheme, changeOpacity} from '@utils/theme';
|
||||
|
||||
export default class DateSuggestion extends PureComponent {
|
||||
static propTypes = {
|
||||
@@ -37,6 +37,7 @@ export default class DateSuggestion extends PureComponent {
|
||||
|
||||
this.state = {
|
||||
mentionComplete: false,
|
||||
active: false,
|
||||
sections: [],
|
||||
};
|
||||
}
|
||||
@@ -45,18 +46,37 @@ export default class DateSuggestion extends PureComponent {
|
||||
this.setCalendarLocale();
|
||||
}
|
||||
|
||||
onLayout = (e) => {
|
||||
this.setState({calendarWidth: e.nativeEvent.layout.width});
|
||||
};
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
const {locale, matchTerm} = this.props;
|
||||
const {locale, matchTerm, enableDateSuggestion} = this.props;
|
||||
const {mentionComplete} = this.state;
|
||||
|
||||
if ((matchTerm !== prevProps.matchTerm && matchTerm === null) || this.state.mentionComplete) {
|
||||
this.resetComponent();
|
||||
}
|
||||
|
||||
if (matchTerm === null || mentionComplete || !enableDateSuggestion) {
|
||||
this.setCalendarActive(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (matchTerm !== null) {
|
||||
this.props.onResultCountChange(1);
|
||||
this.setCalendarActive(true);
|
||||
}
|
||||
|
||||
if (locale !== prevProps.locale) {
|
||||
this.setCalendarLocale();
|
||||
}
|
||||
}
|
||||
|
||||
setCalendarActive = (active) => {
|
||||
this.setState({active});
|
||||
}
|
||||
|
||||
completeMention = (day) => {
|
||||
const mention = day.dateString;
|
||||
const {cursorPosition, onChangeText, value} = this.props;
|
||||
@@ -118,10 +138,11 @@ export default class DateSuggestion extends PureComponent {
|
||||
};
|
||||
|
||||
render() {
|
||||
const {mentionComplete} = this.state;
|
||||
const {matchTerm, enableDateSuggestion, theme} = this.props;
|
||||
const {active, calendarWidth} = this.state;
|
||||
const {theme} = this.props;
|
||||
const styles = getStyleFromTheme(theme);
|
||||
|
||||
if (matchTerm === null || mentionComplete || !enableDateSuggestion) {
|
||||
if (!active) {
|
||||
// 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;
|
||||
@@ -131,22 +152,29 @@ export default class DateSuggestion extends PureComponent {
|
||||
const calendarStyle = calendarTheme(theme);
|
||||
|
||||
return (
|
||||
<CalendarList
|
||||
<View
|
||||
onLayout={this.onLayout}
|
||||
style={styles.calList}
|
||||
current={currentDate}
|
||||
maxDate={currentDate}
|
||||
pastScrollRange={24}
|
||||
futureScrollRange={0}
|
||||
scrollingEnabled={true}
|
||||
pagingEnabled={true}
|
||||
hideArrows={false}
|
||||
horizontal={true}
|
||||
showScrollIndicator={true}
|
||||
onDayPress={this.completeMention}
|
||||
showWeekNumbers={false}
|
||||
theme={calendarStyle}
|
||||
keyboardShouldPersistTaps='always'
|
||||
/>
|
||||
>
|
||||
{Boolean(calendarWidth) &&
|
||||
<CalendarList
|
||||
current={currentDate}
|
||||
maxDate={currentDate}
|
||||
pastScrollRange={24}
|
||||
futureScrollRange={0}
|
||||
scrollingEnabled={true}
|
||||
calendarWidth={calendarWidth}
|
||||
pagingEnabled={true}
|
||||
hideArrows={false}
|
||||
horizontal={true}
|
||||
showScrollIndicator={true}
|
||||
onDayPress={this.completeMention}
|
||||
showWeekNumbers={false}
|
||||
theme={calendarStyle}
|
||||
keyboardShouldPersistTaps='always'
|
||||
/>
|
||||
}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -197,9 +225,13 @@ const calendarTheme = memoizeResult((theme) => ({
|
||||
},
|
||||
}));
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
calList: {
|
||||
height: 1700,
|
||||
paddingTop: 5,
|
||||
},
|
||||
const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
calList: {
|
||||
paddingTop: 5,
|
||||
width: '100%',
|
||||
borderRadius: 4,
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
@@ -4,7 +4,6 @@ exports[`components/autocomplete/emoji_suggestion should match snapshot 1`] = `n
|
||||
|
||||
exports[`components/autocomplete/emoji_suggestion should match snapshot 2`] = `
|
||||
<FlatList
|
||||
ItemSeparatorComponent={[Function]}
|
||||
data={
|
||||
Array [
|
||||
"+1",
|
||||
@@ -3044,6 +3043,8 @@ exports[`components/autocomplete/emoji_suggestion should match snapshot 2`] = `
|
||||
Array [
|
||||
Object {
|
||||
"backgroundColor": "#ffffff",
|
||||
"borderRadius": 4,
|
||||
"paddingTop": 16,
|
||||
},
|
||||
Object {
|
||||
"maxHeight": undefined,
|
||||
|
||||
@@ -11,12 +11,11 @@ import {
|
||||
} from 'react-native';
|
||||
import Fuse from 'fuse.js';
|
||||
|
||||
import AutocompleteDivider from '@components/autocomplete/autocomplete_divider';
|
||||
import Emoji from '@components/emoji';
|
||||
import TouchableWithFeedback from '@components/touchable_with_feedback';
|
||||
import {BuiltInEmojis} from '@utils/emojis';
|
||||
import {getEmojiByName, compareEmojis} from '@utils/emoji_utils';
|
||||
import {makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {makeStyleSheetFromTheme, changeOpacity} from '@utils/theme';
|
||||
|
||||
const EMOJI_REGEX = /(^|\s|^\+|^-)(:([^:\s]*))$/i;
|
||||
const EMOJI_REGEX_WITHOUT_PREFIX = /\B(:([^:\s]*))$/i;
|
||||
@@ -150,21 +149,23 @@ export default class EmojiSuggestion extends PureComponent {
|
||||
|
||||
renderItem = ({item}) => {
|
||||
const style = getStyleFromTheme(this.props.theme);
|
||||
|
||||
const completeSuggestion = () => this.completeSuggestion(item);
|
||||
return (
|
||||
<TouchableWithFeedback
|
||||
onPress={() => this.completeSuggestion(item)}
|
||||
style={style.row}
|
||||
type={'opacity'}
|
||||
onPress={completeSuggestion}
|
||||
underlayColor={changeOpacity(this.props.theme.buttonBg, 0.08)}
|
||||
type={'native'}
|
||||
>
|
||||
<View style={style.emoji}>
|
||||
<Emoji
|
||||
emojiName={item}
|
||||
textStyle={style.emojiText}
|
||||
size={20}
|
||||
/>
|
||||
<View style={style.row}>
|
||||
<View style={style.emoji}>
|
||||
<Emoji
|
||||
emojiName={item}
|
||||
textStyle={style.emojiText}
|
||||
size={24}
|
||||
/>
|
||||
</View>
|
||||
<Text style={style.emojiName}>{`:${item}:`}</Text>
|
||||
</View>
|
||||
<Text style={style.emojiName}>{`:${item}:`}</Text>
|
||||
</TouchableWithFeedback>
|
||||
);
|
||||
};
|
||||
@@ -199,12 +200,14 @@ export default class EmojiSuggestion extends PureComponent {
|
||||
return values;
|
||||
}, []);
|
||||
const data = results.sort(sorter);
|
||||
this.props.onResultCountChange(data.length);
|
||||
this.setState({
|
||||
active: data.length > 0,
|
||||
dataSource: data,
|
||||
});
|
||||
}, 100);
|
||||
} else {
|
||||
this.props.onResultCountChange(emojis.length);
|
||||
this.setState({
|
||||
active: emojis.length > 0,
|
||||
dataSource: emojis.sort(sorter),
|
||||
@@ -229,7 +232,6 @@ export default class EmojiSuggestion extends PureComponent {
|
||||
data={this.state.dataSource}
|
||||
keyExtractor={this.keyExtractor}
|
||||
renderItem={this.renderItem}
|
||||
ItemSeparatorComponent={AutocompleteDivider}
|
||||
pageSize={10}
|
||||
initialListSize={10}
|
||||
nestedScrollEnabled={nestedScrollEnabled}
|
||||
@@ -244,7 +246,7 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
||||
marginRight: 5,
|
||||
},
|
||||
emojiName: {
|
||||
fontSize: 13,
|
||||
fontSize: 15,
|
||||
color: theme.centerChannelColor,
|
||||
},
|
||||
emojiText: {
|
||||
@@ -252,14 +254,17 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
listView: {
|
||||
paddingTop: 16,
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
borderRadius: 4,
|
||||
},
|
||||
row: {
|
||||
height: 40,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
paddingHorizontal: 8,
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
overflow: 'hidden',
|
||||
paddingBottom: 8,
|
||||
paddingHorizontal: 16,
|
||||
height: 40,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
@@ -8,7 +8,6 @@ import {
|
||||
Platform,
|
||||
} from 'react-native';
|
||||
|
||||
import AutocompleteDivider from 'app/components/autocomplete/autocomplete_divider';
|
||||
import {makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
|
||||
import SlashSuggestionItem from './slash_suggestion_item';
|
||||
@@ -211,7 +210,6 @@ export default class SlashSuggestion extends PureComponent {
|
||||
data={this.state.dataSource}
|
||||
keyExtractor={this.keyExtractor}
|
||||
renderItem={this.renderItem}
|
||||
ItemSeparatorComponent={AutocompleteDivider}
|
||||
pageSize={10}
|
||||
initialListSize={10}
|
||||
nestedScrollEnabled={nestedScrollEnabled}
|
||||
@@ -225,6 +223,8 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
||||
listView: {
|
||||
flex: 1,
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
paddingTop: 8,
|
||||
borderRadius: 4,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
@@ -3,11 +3,12 @@
|
||||
|
||||
import React, {PureComponent} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {Text} from 'react-native';
|
||||
import {Image, Text, View} from 'react-native';
|
||||
|
||||
import {paddingHorizontal as padding} from 'app/components/safe_area_view/iphone_x_spacing';
|
||||
import TouchableWithFeedback from 'app/components/touchable_with_feedback';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
import {paddingHorizontal as padding} from '@components/safe_area_view/iphone_x_spacing';
|
||||
import TouchableWithFeedback from '@components/touchable_with_feedback';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import slashIcon from '@assets/images/autocomplete/slash_command.png';
|
||||
|
||||
export default class SlashSuggestionItem extends PureComponent {
|
||||
static propTypes = {
|
||||
@@ -39,11 +40,30 @@ export default class SlashSuggestionItem extends PureComponent {
|
||||
return (
|
||||
<TouchableWithFeedback
|
||||
onPress={this.completeSuggestion}
|
||||
style={[style.row, padding(isLandscape)]}
|
||||
type={'opacity'}
|
||||
style={padding(isLandscape)}
|
||||
underlayColor={changeOpacity(theme.buttonBg, 0.08)}
|
||||
type={'native'}
|
||||
>
|
||||
<Text style={style.suggestionName}>{`${suggestion} ${hint}`}</Text>
|
||||
<Text style={style.suggestionDescription}>{description}</Text>
|
||||
<View style={style.container}>
|
||||
<View style={style.icon}>
|
||||
<Image
|
||||
style={style.iconColor}
|
||||
width={10}
|
||||
height={16}
|
||||
source={slashIcon}
|
||||
/>
|
||||
</View>
|
||||
<View style={style.suggestionContainer}>
|
||||
<Text style={style.suggestionName}>{`${suggestion.substring(1)} ${hint}`}</Text>
|
||||
<Text
|
||||
ellipsizeMode='tail'
|
||||
numberOfLines={1}
|
||||
style={style.suggestionDescription}
|
||||
>
|
||||
{description}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</TouchableWithFeedback>
|
||||
);
|
||||
}
|
||||
@@ -51,32 +71,38 @@ export default class SlashSuggestionItem extends PureComponent {
|
||||
|
||||
const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
row: {
|
||||
paddingVertical: 8,
|
||||
icon: {
|
||||
fontSize: 24,
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.08),
|
||||
width: 35,
|
||||
height: 35,
|
||||
marginRight: 12,
|
||||
borderRadius: 4,
|
||||
justifyContent: 'center',
|
||||
paddingHorizontal: 8,
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
borderLeftWidth: 1,
|
||||
borderLeftColor: changeOpacity(theme.centerChannelColor, 0.2),
|
||||
borderRightWidth: 1,
|
||||
borderRightColor: changeOpacity(theme.centerChannelColor, 0.2),
|
||||
alignItems: 'center',
|
||||
marginTop: 8,
|
||||
},
|
||||
rowDisplayName: {
|
||||
fontSize: 13,
|
||||
color: theme.centerChannelColor,
|
||||
iconColor: {
|
||||
tintColor: theme.centerChannelColor,
|
||||
},
|
||||
rowName: {
|
||||
color: theme.centerChannelColor,
|
||||
opacity: 0.6,
|
||||
container: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
paddingBottom: 8,
|
||||
paddingHorizontal: 16,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
suggestionContainer: {
|
||||
flex: 1,
|
||||
},
|
||||
suggestionDescription: {
|
||||
fontSize: 11,
|
||||
color: changeOpacity(theme.centerChannelColor, 0.6),
|
||||
fontSize: 12,
|
||||
color: changeOpacity(theme.centerChannelColor, 0.56),
|
||||
},
|
||||
suggestionName: {
|
||||
fontSize: 13,
|
||||
fontSize: 15,
|
||||
color: theme.centerChannelColor,
|
||||
marginBottom: 5,
|
||||
marginBottom: 4,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
@@ -42,25 +42,27 @@ export default class SpecialMentionItem extends PureComponent {
|
||||
return (
|
||||
<TouchableWithFeedback
|
||||
onPress={this.completeMention}
|
||||
style={style.row}
|
||||
type={'opacity'}
|
||||
underlayColor={changeOpacity(theme.buttonBg, 0.08)}
|
||||
type={'native'}
|
||||
>
|
||||
<View style={style.rowPicture}>
|
||||
<Icon
|
||||
name='users'
|
||||
style={style.rowIcon}
|
||||
/>
|
||||
<View style={style.row}>
|
||||
<View style={style.rowPicture}>
|
||||
<Icon
|
||||
name='users'
|
||||
style={style.rowIcon}
|
||||
/>
|
||||
</View>
|
||||
<Text style={style.textWrapper}>
|
||||
<Text style={style.rowUsername}>{`@${completeHandle}`}</Text>
|
||||
<Text style={style.rowUsername}>{' - '}</Text>
|
||||
<FormattedText
|
||||
id={id}
|
||||
defaultMessage={defaultMessage}
|
||||
values={values}
|
||||
style={style.rowFullname}
|
||||
/>
|
||||
</Text>
|
||||
</View>
|
||||
<Text style={style.textWrapper}>
|
||||
<Text style={style.rowUsername}>{`@${completeHandle}`}</Text>
|
||||
<Text style={style.rowUsername}>{' - '}</Text>
|
||||
<FormattedText
|
||||
id={id}
|
||||
defaultMessage={defaultMessage}
|
||||
values={values}
|
||||
style={style.rowFullname}
|
||||
/>
|
||||
</Text>
|
||||
</TouchableWithFeedback>
|
||||
);
|
||||
}
|
||||
@@ -68,10 +70,11 @@ export default class SpecialMentionItem extends PureComponent {
|
||||
const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
row: {
|
||||
height: 40,
|
||||
paddingVertical: 8,
|
||||
paddingHorizontal: 9,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
},
|
||||
rowPicture: {
|
||||
marginHorizontal: 8,
|
||||
@@ -81,10 +84,10 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
||||
},
|
||||
rowIcon: {
|
||||
color: changeOpacity(theme.centerChannelColor, 0.7),
|
||||
fontSize: 14,
|
||||
fontSize: 18,
|
||||
},
|
||||
rowUsername: {
|
||||
fontSize: 13,
|
||||
fontSize: 15,
|
||||
color: theme.centerChannelColor,
|
||||
},
|
||||
rowFullname: {
|
||||
|
||||
@@ -309,18 +309,28 @@ exports[`EditChannelInfo should match snapshot 1`] = `
|
||||
</View>
|
||||
</TouchableWithoutFeedback>
|
||||
</KeyboardAwareScrollView>
|
||||
<KeyboardTrackingView
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"justifyContent": "flex-end",
|
||||
}
|
||||
Array [
|
||||
Object {
|
||||
"flex": 1,
|
||||
"justifyContent": "flex-end",
|
||||
"position": "absolute",
|
||||
"width": "100%",
|
||||
},
|
||||
Object {
|
||||
"bottom": 0,
|
||||
},
|
||||
]
|
||||
}
|
||||
>
|
||||
<Connect(Autocomplete)
|
||||
cursorPosition={6}
|
||||
maxHeight={200}
|
||||
nestedScrollEnabled={true}
|
||||
offsetY={8}
|
||||
onChangeText={[Function]}
|
||||
onKeyboardOffsetChanged={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"position": undefined,
|
||||
@@ -328,6 +338,6 @@ exports[`EditChannelInfo should match snapshot 1`] = `
|
||||
}
|
||||
value="header"
|
||||
/>
|
||||
</KeyboardTrackingView>
|
||||
</View>
|
||||
</React.Fragment>
|
||||
`;
|
||||
|
||||
@@ -9,11 +9,10 @@ import {
|
||||
View,
|
||||
} from 'react-native';
|
||||
import {KeyboardAwareScrollView} from 'react-native-keyboard-aware-scrollview';
|
||||
import {KeyboardTrackingView} from 'react-native-keyboard-tracking-view';
|
||||
|
||||
import {General} from '@mm-redux/constants';
|
||||
|
||||
import Autocomplete from 'app/components/autocomplete';
|
||||
import Autocomplete, {AUTOCOMPLETE_MAX_HEIGHT} from 'app/components/autocomplete';
|
||||
import ErrorText from 'app/components/error_text';
|
||||
import FormattedText from 'app/components/formatted_text';
|
||||
import Loading from 'app/components/loading';
|
||||
@@ -71,6 +70,7 @@ export default class EditChannelInfo extends PureComponent {
|
||||
|
||||
this.state = {
|
||||
keyboardVisible: false,
|
||||
keyboardPosition: 0,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -174,6 +174,10 @@ export default class EditChannelInfo extends PureComponent {
|
||||
this.setState({keyboardVisible: false});
|
||||
}
|
||||
|
||||
onKeyboardOffsetChanged = (keyboardPosition) => {
|
||||
this.setState({keyboardPosition});
|
||||
}
|
||||
|
||||
onHeaderFocus = () => {
|
||||
if (this.state.keyboardVisible) {
|
||||
this.scrollHeaderToTop();
|
||||
@@ -201,8 +205,13 @@ export default class EditChannelInfo extends PureComponent {
|
||||
error,
|
||||
saving,
|
||||
} = this.props;
|
||||
const {keyboardVisible} = this.state;
|
||||
|
||||
const {keyboardVisible, keyboardPosition} = this.state;
|
||||
const bottomStyle = {
|
||||
bottom: Platform.select({
|
||||
ios: keyboardPosition,
|
||||
android: 0,
|
||||
}),
|
||||
};
|
||||
const style = getStyleSheet(theme);
|
||||
|
||||
const displayHeaderOnly = channelType === General.DM_CHANNEL ||
|
||||
@@ -354,16 +363,18 @@ export default class EditChannelInfo extends PureComponent {
|
||||
</View>
|
||||
</TouchableWithoutFeedback>
|
||||
</KeyboardAwareScrollView>
|
||||
<KeyboardTrackingView style={style.autocompleteContainer}>
|
||||
<View style={[style.autocompleteContainer, bottomStyle]}>
|
||||
<Autocomplete
|
||||
cursorPosition={header.length}
|
||||
maxHeight={200}
|
||||
maxHeight={AUTOCOMPLETE_MAX_HEIGHT}
|
||||
onChangeText={this.onHeaderChangeText}
|
||||
value={header}
|
||||
nestedScrollEnabled={true}
|
||||
onKeyboardOffsetChanged={this.onKeyboardOffsetChanged}
|
||||
offsetY={8}
|
||||
style={style.autocomplete}
|
||||
/>
|
||||
</KeyboardTrackingView>
|
||||
</View>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
@@ -375,6 +386,9 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
position: undefined,
|
||||
},
|
||||
autocompleteContainer: {
|
||||
position: 'absolute',
|
||||
width: '100%',
|
||||
flex: 1,
|
||||
justifyContent: 'flex-end',
|
||||
},
|
||||
container: {
|
||||
|
||||
@@ -415,16 +415,6 @@ export default class DraftInput extends PureComponent {
|
||||
theme={theme}
|
||||
registerTypingAnimation={registerTypingAnimation}
|
||||
/>
|
||||
{Platform.OS === 'android' &&
|
||||
<Autocomplete
|
||||
cursorPositionEvent={cursorPositionEvent}
|
||||
maxHeight={Math.min(this.state.top - AUTOCOMPLETE_MARGIN, AUTOCOMPLETE_MAX_HEIGHT)}
|
||||
onChangeText={this.handleInputQuickAction}
|
||||
valueEvent={valueEvent}
|
||||
rootId={rootId}
|
||||
channelId={channelId}
|
||||
/>
|
||||
}
|
||||
<View
|
||||
style={[style.inputWrapper, padding(isLandscape)]}
|
||||
onLayout={this.handleLayout}
|
||||
@@ -475,6 +465,16 @@ export default class DraftInput extends PureComponent {
|
||||
</View>
|
||||
</ScrollView>
|
||||
</View>
|
||||
{Platform.OS === 'android' &&
|
||||
<Autocomplete
|
||||
cursorPositionEvent={cursorPositionEvent}
|
||||
maxHeight={Math.min(this.state.top - AUTOCOMPLETE_MARGIN, AUTOCOMPLETE_MAX_HEIGHT)}
|
||||
onChangeText={this.handleInputQuickAction}
|
||||
valueEvent={valueEvent}
|
||||
rootId={rootId}
|
||||
channelId={channelId}
|
||||
/>
|
||||
}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -513,4 +513,4 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
borderTopColor: changeOpacity(theme.centerChannelColor, 0.20),
|
||||
},
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
@@ -64,15 +64,6 @@ export default class ChannelIOS extends ChannelBase {
|
||||
updateNativeScrollView={this.updateNativeScrollView}
|
||||
registerTypingAnimation={this.registerTypingAnimation}
|
||||
/>
|
||||
<View nativeID={ACCESSORIES_CONTAINER_NATIVE_ID}>
|
||||
<Autocomplete
|
||||
maxHeight={AUTOCOMPLETE_MAX_HEIGHT}
|
||||
onChangeText={this.handleAutoComplete}
|
||||
cursorPositionEvent={CHANNEL_POST_TEXTBOX_CURSOR_CHANGE}
|
||||
valueEvent={CHANNEL_POST_TEXTBOX_VALUE_CHANGE}
|
||||
channelId={currentChannelId}
|
||||
/>
|
||||
</View>
|
||||
{LocalConfig.EnableMobileClientUpgrade && <ClientUpgradeListener/>}
|
||||
</>
|
||||
);
|
||||
@@ -102,6 +93,15 @@ export default class ChannelIOS extends ChannelBase {
|
||||
valueEvent={CHANNEL_POST_TEXTBOX_VALUE_CHANGE}
|
||||
/>
|
||||
}
|
||||
<View nativeID={ACCESSORIES_CONTAINER_NATIVE_ID}>
|
||||
<Autocomplete
|
||||
maxHeight={AUTOCOMPLETE_MAX_HEIGHT}
|
||||
onChangeText={this.handleAutoComplete}
|
||||
cursorPositionEvent={CHANNEL_POST_TEXTBOX_CURSOR_CHANGE}
|
||||
valueEvent={CHANNEL_POST_TEXTBOX_VALUE_CHANGE}
|
||||
channelId={currentChannelId}
|
||||
/>
|
||||
</View>
|
||||
</>
|
||||
);
|
||||
|
||||
|
||||
@@ -87,6 +87,7 @@ exports[`EditPost should match snapshot 1`] = `
|
||||
cursorPosition={0}
|
||||
maxHeight={200}
|
||||
nestedScrollEnabled={true}
|
||||
offsetY={8}
|
||||
onChangeText={[Function]}
|
||||
onVisible={[Function]}
|
||||
style={
|
||||
|
||||
@@ -281,6 +281,7 @@ export default class EditPost extends PureComponent {
|
||||
value={message}
|
||||
nestedScrollEnabled={true}
|
||||
onVisible={this.onAutocompleteVisible}
|
||||
offsetY={8}
|
||||
style={style.autocomplete}
|
||||
/>
|
||||
</KeyboardTrackingView>
|
||||
|
||||
@@ -48,18 +48,6 @@ exports[`thread should match snapshot, has root post 1`] = `
|
||||
scrollViewNativeID="threadPostList"
|
||||
/>
|
||||
</ForwardRef(AnimatedComponentWrapper)>
|
||||
<View
|
||||
nativeID="threadAccessoriesContainer"
|
||||
>
|
||||
<Connect(Autocomplete)
|
||||
channelId="channel_id"
|
||||
cursorPositionEvent="onThreadTextBoxCursorChange"
|
||||
maxHeight={200}
|
||||
onChangeText={[Function]}
|
||||
rootId="root_id"
|
||||
valueEvent="onThreadTextBoxValueChange"
|
||||
/>
|
||||
</View>
|
||||
</React.Fragment>
|
||||
</Connect(SafeAreaIos)>
|
||||
<Connect(PostDraft)
|
||||
@@ -72,6 +60,18 @@ exports[`thread should match snapshot, has root post 1`] = `
|
||||
scrollViewNativeID="threadPostList"
|
||||
valueEvent="onThreadTextBoxValueChange"
|
||||
/>
|
||||
<View
|
||||
nativeID="threadAccessoriesContainer"
|
||||
>
|
||||
<Connect(Autocomplete)
|
||||
channelId="channel_id"
|
||||
cursorPositionEvent="onThreadTextBoxCursorChange"
|
||||
maxHeight={200}
|
||||
onChangeText={[Function]}
|
||||
rootId="root_id"
|
||||
valueEvent="onThreadTextBoxValueChange"
|
||||
/>
|
||||
</View>
|
||||
</React.Fragment>
|
||||
`;
|
||||
|
||||
@@ -96,6 +96,18 @@ exports[`thread should match snapshot, no root post, loading 1`] = `
|
||||
style={Object {}}
|
||||
/>
|
||||
</Connect(SafeAreaIos)>
|
||||
<View
|
||||
nativeID="threadAccessoriesContainer"
|
||||
>
|
||||
<Connect(Autocomplete)
|
||||
channelId="channel_id"
|
||||
cursorPositionEvent="onThreadTextBoxCursorChange"
|
||||
maxHeight={200}
|
||||
onChangeText={[Function]}
|
||||
rootId="root_id"
|
||||
valueEvent="onThreadTextBoxValueChange"
|
||||
/>
|
||||
</View>
|
||||
</React.Fragment>
|
||||
`;
|
||||
|
||||
@@ -166,5 +178,17 @@ exports[`thread should match snapshot, render footer 3`] = `
|
||||
style={Object {}}
|
||||
/>
|
||||
</Connect(SafeAreaIos)>
|
||||
<View
|
||||
nativeID="threadAccessoriesContainer"
|
||||
>
|
||||
<Connect(Autocomplete)
|
||||
channelId="channel_id"
|
||||
cursorPositionEvent="onThreadTextBoxCursorChange"
|
||||
maxHeight={200}
|
||||
onChangeText={[Function]}
|
||||
rootId="root_id"
|
||||
valueEvent="onThreadTextBoxValueChange"
|
||||
/>
|
||||
</View>
|
||||
</React.Fragment>
|
||||
`;
|
||||
|
||||
@@ -56,16 +56,6 @@ export default class ThreadIOS extends ThreadBase {
|
||||
scrollViewNativeID={SCROLLVIEW_NATIVE_ID}
|
||||
/>
|
||||
</Animated.View>
|
||||
<View nativeID={ACCESSORIES_CONTAINER_NATIVE_ID}>
|
||||
<Autocomplete
|
||||
maxHeight={AUTOCOMPLETE_MAX_HEIGHT}
|
||||
onChangeText={this.handleAutoComplete}
|
||||
cursorPositionEvent={THREAD_POST_TEXTBOX_CURSOR_CHANGE}
|
||||
valueEvent={THREAD_POST_TEXTBOX_VALUE_CHANGE}
|
||||
rootId={rootId}
|
||||
channelId={channelId}
|
||||
/>
|
||||
</View>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -101,6 +91,16 @@ export default class ThreadIOS extends ThreadBase {
|
||||
{content}
|
||||
</SafeAreaView>
|
||||
{postDraft}
|
||||
<View nativeID={ACCESSORIES_CONTAINER_NATIVE_ID}>
|
||||
<Autocomplete
|
||||
maxHeight={AUTOCOMPLETE_MAX_HEIGHT}
|
||||
onChangeText={this.handleAutoComplete}
|
||||
cursorPositionEvent={THREAD_POST_TEXTBOX_CURSOR_CHANGE}
|
||||
valueEvent={THREAD_POST_TEXTBOX_VALUE_CHANGE}
|
||||
rootId={rootId}
|
||||
channelId={channelId}
|
||||
/>
|
||||
</View>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
BIN
assets/base/images/autocomplete/slash_command.png
Normal file
BIN
assets/base/images/autocomplete/slash_command.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 256 B |
BIN
assets/base/images/autocomplete/slash_command@2x.png
Normal file
BIN
assets/base/images/autocomplete/slash_command@2x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 360 B |
BIN
assets/base/images/autocomplete/slash_command@3x.png
Normal file
BIN
assets/base/images/autocomplete/slash_command@3x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 516 B |
Reference in New Issue
Block a user