diff --git a/src/components/chatInput/index.js b/src/components/chatInput/index.js index 017b74c60d..8604fde31f 100644 --- a/src/components/chatInput/index.js +++ b/src/components/chatInput/index.js @@ -26,6 +26,8 @@ import { StyledMentionSuggestion, SuggestionsWrapper, MentionUsername, + MentionContent, + MentionName, } from './style'; import sendMessage from 'shared/graphql/mutations/message/sendMessage'; import sendDirectMessage from 'shared/graphql/mutations/message/sendDirectMessage'; @@ -38,8 +40,11 @@ import { ESC, BACKSPACE, DELETE } from 'src/helpers/keycodes'; const MentionSuggestion = ({ entry, search, focused }) => ( - - {entry.username} + + + {entry.name} + @{entry.username} + ); @@ -292,21 +297,40 @@ const ChatInput = (props: Props) => { ); }; + const sortSuggestions = (a, b, queryString) => { + const aUsernameIndex = a.username.indexOf(queryString || ''); + const bUsernameIndex = b.username.indexOf(queryString || ''); + const aNameIndex = a.filterName.indexOf(queryString || ''); + const bNameIndex = b.filterName.indexOf(queryString || ''); + if (aNameIndex === 0) return -1; + if (aUsernameIndex === 0) return -1; + if (aNameIndex === 0) return -1; + if (aUsernameIndex === 0) return -1; + return aNameIndex - bNameIndex || aUsernameIndex - bUsernameIndex; + }; + const searchUsers = async (queryString, callback) => { const filteredParticipants = props.participants ? props.participants .filter(Boolean) - .filter( - participant => participant.username.indexOf(queryString || '') > -1 - ) - .sort( - (a, b) => - a.username.indexOf(queryString || '') - - b.username.indexOf(queryString || '') - ) + .filter(participant => { + return ( + participant.username && + (participant.username.indexOf(queryString || '') > -1 || + participant.filterName.indexOf(queryString || '') > -1) + ); + }) + .sort((a, b) => { + return sortSuggestions(a, b, queryString); + }) + .slice(0, 8) : []; + callback(filteredParticipants); - if (!queryString || queryString.length === 0) return; + + if (!queryString || queryString.length === 0) + return callback(filteredParticipants); + const { data: { search }, } = await props.client.query({ @@ -316,10 +340,16 @@ const ChatInput = (props: Props) => { type: 'USERS', }, }); - if (!search || !search.searchResultsConnection) return; + + if (!search || !search.searchResultsConnection) { + if (filteredParticipants && filteredParticipants.length > 0) + return filteredParticipants; + return; + } let searchUsers = search.searchResultsConnection.edges .filter(Boolean) + .filter(edge => edge.node.username) .map(edge => { const user = edge.node; return { @@ -327,12 +357,15 @@ const ChatInput = (props: Props) => { id: user.username, display: user.username, username: user.username, + filterName: user.name.toLowerCase(), }; }); + // Prepend the filtered participants in case a user is tabbing down right now const fullResults = [...filteredParticipants, ...searchUsers]; const uniqueResults = []; const done = []; + fullResults.forEach(item => { if (done.indexOf(item.username) === -1) { uniqueResults.push(item); @@ -340,7 +373,7 @@ const ChatInput = (props: Props) => { } }); - callback(uniqueResults); + return callback(uniqueResults.slice(0, 8)); }; const networkDisabled = @@ -414,6 +447,7 @@ const ChatInput = (props: Props) => { (props.focused ? theme.brand.default : theme.text.default)}; + padding: 8px 12px; align-items: center; background: ${props => (props.focused ? theme.brand.wash : theme.bg.default)}; min-width: 156px; + line-height: 1.3; + border-bottom: 1px solid ${theme.bg.border}; +`; + +export const MentionContent = styled.div` + display: flex; + flex-direction: column; +`; + +export const MentionName = styled.span` + margin-left: 12px; + width: calc(184px - 62px); + ${Truncate}; + font-size: 14px; + font-weight: 500; + color: ${props => (props.focused ? theme.brand.default : theme.text.default)}; `; export const MentionUsername = styled.span` - margin-left: 8px; - width: calc(156px - 62px); + margin-left: 12px; + font-size: 13px; + font-weight: 400; + width: calc(184px - 62px); ${Truncate}; + color: ${props => (props.focused ? theme.brand.default : theme.text.alt)}; `; diff --git a/src/views/thread/components/messages.js b/src/views/thread/components/messages.js index 06cef1ebd7..07f3d18c5b 100644 --- a/src/views/thread/components/messages.js +++ b/src/views/thread/components/messages.js @@ -125,6 +125,14 @@ class MessagesWithData extends React.Component { componentDidMount() { this.subscribe(); + if ( + this.props.data && + this.props.data.thread && + this.props.onMessagesLoaded + ) { + this.props.onMessagesLoaded(this.props.data.thread); + } + if (this.shouldForceScrollToBottom()) { return setTimeout(() => this.props.forceScrollToBottom()); } diff --git a/src/views/thread/container.js b/src/views/thread/container.js index 0b39bb7021..77eee92908 100644 --- a/src/views/thread/container.js +++ b/src/views/thread/container.js @@ -401,14 +401,26 @@ class ThreadContainer extends React.Component { updateThreadParticipants = thread => { const { messageConnection, author } = thread; - if (!messageConnection || messageConnection.edges.length === 0) return; + + if (!messageConnection || messageConnection.edges.length === 0) + return this.setState({ + participants: [ + { ...author.user, filterName: author.user.name.toLowerCase() }, + ], + }); + const participants = messageConnection.edges .map(edge => edge.node) .map(node => node.author.user); const participantsWithAuthor = [...participants, author.user] .filter((user, index, array) => array.indexOf(user) === index) - .map(user => ({ ...user, id: user.username, display: user.username })); + .map(user => ({ + ...user, + id: user.username, + display: user.username, + filterName: user.name.toLowerCase(), + })); return this.setState({ participants: participantsWithAuthor }); };