diff --git a/src/components/emoji_input/emoji_input.js b/src/components/emoji_input/emoji_input.js index 87303d08..7fe25ff4 100644 --- a/src/components/emoji_input/emoji_input.js +++ b/src/components/emoji_input/emoji_input.js @@ -114,7 +114,8 @@ const EmojiInput = { showPicker: false, temporarilyHideSuggestions: false, keepOpen: false, - disableClickOutside: false + disableClickOutside: false, + suggestions: [] } }, components: { @@ -124,21 +125,6 @@ const EmojiInput = { padEmoji () { return this.$store.getters.mergedConfig.padEmoji }, - suggestions () { - const firstchar = this.textAtCaret.charAt(0) - if (this.textAtCaret === firstchar) { return [] } - const matchedSuggestions = this.suggest(this.textAtCaret) - if (matchedSuggestions.length <= 0) { - return [] - } - return take(matchedSuggestions, 5) - .map(({ imageUrl, ...rest }, index) => ({ - ...rest, - // eslint-disable-next-line camelcase - img: imageUrl || '', - highlighted: index === this.highlighted - })) - }, showSuggestions () { return this.focused && this.suggestions && @@ -187,7 +173,25 @@ const EmojiInput = { }, watch: { showSuggestions: function (newValue) { + console.log('showSuggestions watch', newValue, this.suggestions) this.$emit('shown', newValue) + }, + textAtCaret: async function (textAtCaret) { + const firstchar = textAtCaret.charAt(0) + this.suggestions = [] + if (textAtCaret === firstchar) return + const matchedSuggestions = await this.suggest(textAtCaret) + // Async, cancel if textAtCaret has been updated while waiting + if (this.textAtCaret !== textAtCaret) return + if (matchedSuggestions.length <= 0) return + this.suggestions = take(matchedSuggestions, 10) + .map(({ imageUrl, ...rest }, index) => ({ + ...rest, + // eslint-disable-next-line camelcase + img: imageUrl || '', + highlighted: index === this.highlighted + })) + this.scrollIntoView() } }, methods: { @@ -341,6 +345,7 @@ const EmojiInput = { const { offsetHeight } = this.input.elm const { picker } = this.$refs const pickerBottom = picker.$el.getBoundingClientRect().bottom + console.log('setting bottom', pickerBottom > window.innerHeight) if (pickerBottom > window.innerHeight) { picker.$el.style.top = 'auto' picker.$el.style.bottom = offsetHeight + 'px' diff --git a/src/components/emoji_input/suggestor.js b/src/components/emoji_input/suggestor.js index 8330345b..9bf53183 100644 --- a/src/components/emoji_input/suggestor.js +++ b/src/components/emoji_input/suggestor.js @@ -1,4 +1,3 @@ -import { debounce } from 'lodash' /** * suggest - generates a suggestor function to be used by emoji-input * data: object providing source information for specific types of suggestions: @@ -11,19 +10,19 @@ import { debounce } from 'lodash' * doesn't support user linking you can just provide only emoji. */ -const debounceUserSearch = debounce((data, input) => { - data.updateUsersList(input) -}, 500) - -export default data => input => { - const firstChar = input[0] - if (firstChar === ':' && data.emoji) { - return suggestEmoji(data.emoji)(input) +export default data => { + const emojiCurry = suggestEmoji(data.emoji) + const usersCurry = suggestUsers(data.dispatch) + return input => { + const firstChar = input[0] + if (firstChar === ':' && data.emoji) { + return emojiCurry(input) + } + if (firstChar === '@' && data.dispatch) { + return usersCurry(input) + } + return [] } - if (firstChar === '@' && data.users) { - return suggestUsers(data)(input) - } - return [] } export const suggestEmoji = emojis => input => { @@ -57,50 +56,67 @@ export const suggestEmoji = emojis => input => { }) } -export const suggestUsers = data => input => { - const noPrefix = input.toLowerCase().substr(1) - const users = data.users +export const suggestUsers = (dispatch) => { + let suggestions = [] + let previousQuery = '' + let timeout = null - const newUsers = users.filter( - user => - user.screen_name.toLowerCase().startsWith(noPrefix) || - user.name.toLowerCase().startsWith(noPrefix) - - /* taking only 20 results so that sorting is a bit cheaper, we display - * only 5 anyway. could be inaccurate, but we ideally we should query - * backend anyway - */ - ).slice(0, 20).sort((a, b) => { - let aScore = 0 - let bScore = 0 - - // Matches on screen name (i.e. user@instance) makes a priority - aScore += a.screen_name.toLowerCase().startsWith(noPrefix) ? 2 : 0 - bScore += b.screen_name.toLowerCase().startsWith(noPrefix) ? 2 : 0 - - // Matches on name takes second priority - aScore += a.name.toLowerCase().startsWith(noPrefix) ? 1 : 0 - bScore += b.name.toLowerCase().startsWith(noPrefix) ? 1 : 0 - - const diff = (bScore - aScore) * 10 - - // Then sort alphabetically - const nameAlphabetically = a.name > b.name ? 1 : -1 - const screenNameAlphabetically = a.screen_name > b.screen_name ? 1 : -1 - - return diff + nameAlphabetically + screenNameAlphabetically - /* eslint-disable camelcase */ - }).map(({ screen_name, name, profile_image_url_original }) => ({ - displayText: screen_name, - detailText: name, - imageUrl: profile_image_url_original, - replacement: '@' + screen_name + ' ' - })) - - // BE search users to get more comprehensive results - if (data.updateUsersList) { - debounceUserSearch(data, noPrefix) + const userSearch = (query) => dispatch('searchUsers', { query, saveUsers: false }) + const debounceUserSearch = (query) => { + return new Promise((resolve, reject) => { + clearTimeout(timeout) + timeout = setTimeout(() => { + userSearch(query).then(resolve).catch(reject) + }, 300) + }) + } + + return async input => { + const noPrefix = input.toLowerCase().substr(1) + if (previousQuery === noPrefix) return suggestions + + suggestions = [] + previousQuery = noPrefix + const users = await debounceUserSearch(noPrefix) + + const newUsers = users.filter( + user => + user.screen_name.toLowerCase().startsWith(noPrefix) || + user.name.toLowerCase().startsWith(noPrefix) + + /* taking only 20 results so that sorting is a bit cheaper, we display + * only 5 anyway. could be inaccurate, but we ideally we should query + * backend anyway + */ + ).slice(0, 20).sort((a, b) => { + let aScore = 0 + let bScore = 0 + + // Matches on screen name (i.e. user@instance) makes a priority + aScore += a.screen_name.toLowerCase().startsWith(noPrefix) ? 2 : 0 + bScore += b.screen_name.toLowerCase().startsWith(noPrefix) ? 2 : 0 + + // Matches on name takes second priority + aScore += a.name.toLowerCase().startsWith(noPrefix) ? 1 : 0 + bScore += b.name.toLowerCase().startsWith(noPrefix) ? 1 : 0 + + const diff = (bScore - aScore) * 10 + + // Then sort alphabetically + const nameAlphabetically = a.name > b.name ? 1 : -1 + const screenNameAlphabetically = a.screen_name > b.screen_name ? 1 : -1 + + return diff + nameAlphabetically + screenNameAlphabetically + /* eslint-disable camelcase */ + }).map(({ screen_name, name, profile_image_url_original }) => ({ + displayText: screen_name, + detailText: name, + imageUrl: profile_image_url_original, + replacement: '@' + screen_name + ' ' + })) + + suggestions = newUsers || [] + return suggestions + /* eslint-enable camelcase */ } - return newUsers - /* eslint-enable camelcase */ } diff --git a/src/components/post_status_form/post_status_form.js b/src/components/post_status_form/post_status_form.js index 3ff4a3c8..e38f1c0c 100644 --- a/src/components/post_status_form/post_status_form.js +++ b/src/components/post_status_form/post_status_form.js @@ -159,8 +159,7 @@ const PostStatusForm = { ...this.$store.state.instance.emoji, ...this.$store.state.instance.customEmoji ], - users: this.$store.state.users.users, - updateUsersList: (query) => this.$store.dispatch('searchUsers', { query }) + dispatch: this.$store.dispatch }) }, emojiSuggestor () { diff --git a/src/modules/users.js b/src/modules/users.js index 9245db5c..13df9df4 100644 --- a/src/modules/users.js +++ b/src/modules/users.js @@ -440,10 +440,10 @@ const users = { store.commit('setUserForNotification', notification) }) }, - searchUsers ({ rootState, commit }, { query }) { + searchUsers ({ rootState, commit }, { query, saveUsers = true }) { return rootState.api.backendInteractor.searchUsers({ query }) .then((users) => { - commit('addNewUsers', users) + if (saveUsers) commit('addNewUsers', users) return users }) },