From 940363cf36669437b763d8b04d2bbab49b94ecaf Mon Sep 17 00:00:00 2001 From: Chris Duarte Date: Wed, 15 Feb 2017 06:57:30 -0800 Subject: [PATCH] Mention autocomplete bug fixes (#253) * Mention autocomplete bug fixes * Review feedback --- .../autocomplete/at_mention/at_mention.js | 170 +++++++++--------- .../autocomplete/at_mention/index.js | 1 + app/components/post/post.js | 3 +- 3 files changed, 86 insertions(+), 88 deletions(-) diff --git a/app/components/autocomplete/at_mention/at_mention.js b/app/components/autocomplete/at_mention/at_mention.js index 97c62e981..a17f79873 100644 --- a/app/components/autocomplete/at_mention/at_mention.js +++ b/app/components/autocomplete/at_mention/at_mention.js @@ -89,6 +89,7 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => { export default class AtMention extends Component { static propTypes = { + currentUserId: PropTypes.string.isRequired, currentChannelId: PropTypes.string.isRequired, currentTeamId: PropTypes.string.isRequired, cursorPosition: PropTypes.number.isRequired, @@ -115,10 +116,7 @@ export default class AtMention extends Component { sectionHeaderHasChanged: (s1, s2) => s1 !== s2, rowHasChanged: (r1, r2) => r1 !== r2 }); - const data = { - inChannel: [], - notInChannel: [] - }; + const data = {}; this.state = { active: false, dataSource: ds.cloneWithRowsAndSections(data) @@ -128,10 +126,11 @@ export default class AtMention extends Component { componentWillReceiveProps(nextProps) { const match = nextProps.postDraft.substring(0, this.props.cursorPosition).match(AT_MENTION_REGEX); - if (!match) { + if (!match || this.state.mentionComplete) { this.setState({ active: false, - matchTerm: null + matchTerm: null, + mentionComplete: false }); return; } @@ -148,28 +147,66 @@ export default class AtMention extends Component { } if (nextProps.requestStatus !== RequestStatus.STARTED) { - const membersInChannel = nextProps.autocompleteUsersInCurrentChannel.in_channel || []; + let membersInChannel = nextProps.autocompleteUsersInCurrentChannel.in_channel || []; const membersOutOfChannel = nextProps.autocompleteUsersInCurrentChannel.out_of_channel || []; - const data = { - inChannel: membersInChannel, - notInChannel: membersOutOfChannel - }; + + // filter the current user out + membersInChannel = membersInChannel.filter((m) => m.id !== this.props.currentUserId); + + let data = {}; + if (membersInChannel.length > 0) { + data = Object.assign({}, data, {inChannel: membersInChannel}); + } + if (this.checkSpecialMentions(matchTerm)) { + data = Object.assign({}, data, {specialMentions: this.getSpecialMentions()}); + } + if (membersOutOfChannel.length > 0) { + data = Object.assign({}, data, {notInChannel: membersOutOfChannel}); + } + this.setState({ - active: true, + active: data.hasOwnProperty('inChannel') || data.hasOwnProperty('specialMentions') || data.hasOwnProperty('notInChannel'), dataSource: this.state.dataSource.cloneWithRowsAndSections(data) }); } } + getSpecialMentions = () => { + return [{ + completeHandle: 'all', + id: '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: 'suggestion.mention.channel', + defaultMessage: 'Notifies everyone in the channel' + }, { + completeHandle: 'here', + id: 'suggestions.mention.here', + defaultMessage: 'Notifies everyone in the channel and online' + }]; + } + + checkSpecialMentions = (term) => { + return this.getSpecialMentions().filter((m) => m.completeHandle.includes(term)).length > 0; + } + completeMention = (mention) => { const mentionPart = this.props.postDraft.substring(0, this.props.cursorPosition); - let completedDraft = mentionPart.replace(AT_MENTION_REGEX, `@${mention}`); + let completedDraft = mentionPart.replace(AT_MENTION_REGEX, `@${mention} `); if (this.props.postDraft.length > this.props.cursorPosition) { completedDraft += this.props.postDraft.substring(this.props.cursorPosition); } this.props.actions.changePostDraft(this.props.currentChannelId, completedDraft); + this.setState({ + active: false, + mentionComplete: true + }); } renderSectionHeader = (sectionData, sectionId) => { @@ -183,6 +220,10 @@ export default class AtMention extends Component { notInChannel: { id: 'suggestion.mention.nonmembers', defaultMessage: 'Not in Channel' + }, + specialMentions: { + id: 'suggestion.mention.special', + defaultMessage: 'Special Mentions' } }; @@ -199,7 +240,11 @@ export default class AtMention extends Component { ); } - renderRow = (data) => { + renderRow = (data, sectionId) => { + if (sectionId === 'specialMentions') { + return this.renderSpecialMentions(data); + } + const style = getStyleFromTheme(this.props.theme); const hasFullName = data.first_name.length > 0 && data.last_name.length > 0; @@ -223,96 +268,47 @@ export default class AtMention extends Component { ); } - renderFooter = () => { + renderSpecialMentions = (data) => { const style = getStyleFromTheme(this.props.theme); return ( - - - this.completeMention(data.completeHandle)} + style={style.row} + > + + - this.completeMention('all')} - style={style.row} - > - - - - - {'@all'} - {' - '} - - - - this.completeMention('channel')} - style={style.row} - > - - - - - {'@channel'} - {' - '} - - - - this.completeMention('here')} - style={style.row} - > - - - - - {'@here'} - {' - '} - - - - + + {`@${data.completeHandle}`} + {' - '} + + + ); } render() { - if (!this.state.active) { + const {autocompleteUsersInCurrentChannel, requestStatus} = this.props; + if (!this.state.active && (requestStatus !== RequestStatus.STARTED || requestStatus !== RequestStatus.SUCCESS)) { // If we are not in an active state return null so nothing is rendered // other components are not blocked. return null; } const style = getStyleFromTheme(this.props.theme); - const {autocompleteUsersInCurrentChannel, requestStatus} = this.props; if ( - (!autocompleteUsersInCurrentChannel.in_channel || !autocompleteUsersInCurrentChannel.in_channel.length) && - (!autocompleteUsersInCurrentChannel.out_of_channel || !autocompleteUsersInCurrentChannel.out_of_channel.length) && + !autocompleteUsersInCurrentChannel.in_channel && + !autocompleteUsersInCurrentChannel.out_of_channel && requestStatus === RequestStatus.STARTED ) { return ( diff --git a/app/components/autocomplete/at_mention/index.js b/app/components/autocomplete/at_mention/index.js index 9bb1f22e5..cab3f86d9 100644 --- a/app/components/autocomplete/at_mention/index.js +++ b/app/components/autocomplete/at_mention/index.js @@ -16,6 +16,7 @@ function mapStateToProps(state) { const currentChannelId = state.entities.channels.currentId; const postDraft = state.views.channel.drafts[currentChannelId]; return { + currentUserId: state.entities.users.currentId, currentChannelId, currentTeamId: state.entities.teams.currentId, defaultChannel: getDefaultChannel(state), diff --git a/app/components/post/post.js b/app/components/post/post.js index 4fcb0bbdc..52ba36a3e 100644 --- a/app/components/post/post.js +++ b/app/components/post/post.js @@ -46,7 +46,8 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => { backgroundColor: theme.centerChannelColor, opacity: 0.1, marginRight: 10, - width: 3 + width: 3, + flexBasis: 3 }, replyBarFirst: { marginTop: 10