mattermost-mobile/app/components/autocomplete/at_mention/at_mention.js
Harrison Healey 4e251d392a
MM-12189 Set autocomplete max height based off available space (#2218)
* MM-12189 Set autocomplete max height based off available space

* Have each autocomplete type determine its own max height since DateSuggestion needs more space

* Remove unused props and ref
2018-10-05 09:58:05 -04:00

239 lines
8.2 KiB
JavaScript

// 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 {SectionList} from 'react-native';
import {RequestStatus} from 'mattermost-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 {makeStyleSheetFromTheme} from 'app/utils/theme';
import {t} from 'app/utils/i18n';
export default class AtMention extends PureComponent {
static propTypes = {
actions: PropTypes.shape({
autocompleteUsers: PropTypes.func.isRequired,
}).isRequired,
currentChannelId: PropTypes.string,
currentTeamId: PropTypes.string.isRequired,
cursorPosition: PropTypes.number.isRequired,
defaultChannel: PropTypes.object,
inChannel: PropTypes.array,
isSearch: PropTypes.bool,
matchTerm: PropTypes.string,
maxListHeight: PropTypes.number,
onChangeText: PropTypes.func.isRequired,
onResultCountChange: PropTypes.func.isRequired,
outChannel: PropTypes.array,
requestStatus: PropTypes.string.isRequired,
teamMembers: PropTypes.array,
theme: PropTypes.object.isRequired,
value: PropTypes.string,
};
static defaultProps = {
defaultChannel: {},
isSearch: false,
value: '',
};
constructor(props) {
super(props);
this.state = {
sections: [],
};
}
componentWillReceiveProps(nextProps) {
const {inChannel, outChannel, teamMembers, isSearch, matchTerm, requestStatus} = nextProps;
if ((matchTerm !== this.props.matchTerm && matchTerm === null) || this.state.mentionComplete) {
// if the term changes but is null or the mention has been completed we render this component as null
this.setState({
mentionComplete: false,
sections: [],
});
this.props.onResultCountChange(0);
return;
} else if (matchTerm === null) {
// if the terms did not change but is null then we don't need to do anything
return;
}
if (matchTerm !== this.props.matchTerm) {
// if the term changed and we haven't made the request do that first
const {currentTeamId, currentChannelId} = this.props;
const channelId = isSearch ? '' : currentChannelId;
this.props.actions.autocompleteUsers(matchTerm, currentTeamId, channelId);
return;
}
if (requestStatus !== RequestStatus.STARTED &&
(inChannel !== this.props.inChannel || outChannel !== this.props.outChannel || teamMembers !== this.props.teamMembers)) {
// if the request is complete and the term is not null we show the autocomplete
const sections = [];
if (isSearch) {
sections.push({
id: t('mobile.suggestion.members'),
defaultMessage: 'Members',
data: teamMembers,
key: 'teamMembers',
});
} else {
if (inChannel.length) {
sections.push({
id: t('suggestion.mention.members'),
defaultMessage: 'Channel Members',
data: inChannel,
key: 'inChannel',
});
}
if (this.checkSpecialMentions(matchTerm)) {
sections.push({
id: t('suggestion.mention.special'),
defaultMessage: 'Special Mentions',
data: this.getSpecialMentions(),
key: 'special',
renderItem: this.renderSpecialMentions,
});
}
if (outChannel.length) {
sections.push({
id: t('suggestion.mention.nonmembers'),
defaultMessage: 'Not in Channel',
data: outChannel,
key: 'outChannel',
});
}
}
this.setState({
sections,
});
this.props.onResultCountChange(sections.reduce((total, section) => total + section.data.length, 0));
}
}
keyExtractor = (item) => {
return item.id || item;
};
getSpecialMentions = () => {
return [{
completeHandle: 'all',
id: t('suggestion.mention.all'),
defaultMessage: 'Notifies everyone in the channel, use in {townsquare} to notify the whole team',
values: {
townsquare: this.props.defaultChannel.display_name,
},
}, {
completeHandle: 'channel',
id: t('suggestion.mention.channel'),
defaultMessage: 'Notifies everyone in the channel',
}, {
completeHandle: 'here',
id: t('suggestion.mention.here'),
defaultMessage: 'Notifies everyone in the channel and online',
}];
};
checkSpecialMentions = (term) => {
return this.getSpecialMentions().filter((m) => m.completeHandle.startsWith(term)).length > 0;
};
completeMention = (mention) => {
const {cursorPosition, isSearch, onChangeText, value} = this.props;
const mentionPart = value.substring(0, cursorPosition);
let completedDraft;
if (isSearch) {
completedDraft = mentionPart.replace(AT_MENTION_SEARCH_REGEX, `from: ${mention} `);
} else {
completedDraft = mentionPart.replace(AT_MENTION_REGEX, `@${mention} `);
}
if (value.length > cursorPosition) {
completedDraft += value.substring(cursorPosition);
}
onChangeText(completedDraft);
this.setState({mentionComplete: true});
};
renderSectionHeader = ({section}) => {
return (
<AutocompleteSectionHeader
id={section.id}
defaultMessage={section.defaultMessage}
theme={this.props.theme}
/>
);
};
renderItem = ({item}) => {
return (
<AtMentionItem
onPress={this.completeMention}
userId={item}
/>
);
};
renderSpecialMentions = ({item}) => {
return (
<SpecialMentionItem
completeHandle={item.completeHandle}
defaultMessage={item.defaultMessage}
id={item.id}
onPress={this.completeMention}
theme={this.props.theme}
values={item.values}
/>
);
};
render() {
const {maxListHeight, theme} = this.props;
const {mentionComplete, sections} = this.state;
if (sections.length === 0 || mentionComplete) {
// 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;
}
const style = getStyleFromTheme(theme);
return (
<SectionList
keyboardShouldPersistTaps='always'
keyExtractor={this.keyExtractor}
style={[style.listView, {maxHeight: maxListHeight}]}
sections={sections}
renderItem={this.renderItem}
renderSectionHeader={this.renderSectionHeader}
ItemSeparatorComponent={AutocompleteDivider}
initialNumToRender={10}
/>
);
}
}
const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
return {
listView: {
backgroundColor: theme.centerChannelBg,
},
};
});