diff --git a/src/autocomplete/EmojiProvider.tsx b/src/autocomplete/EmojiProvider.tsx index 4c051a7126..4a2c37988a 100644 --- a/src/autocomplete/EmojiProvider.tsx +++ b/src/autocomplete/EmojiProvider.tsx @@ -29,8 +29,9 @@ import QueryMatcher from './QueryMatcher'; import { PillCompletion } from './Components'; import { ICompletion, ISelectionRange } from './Autocompleter'; import SettingsStore from "../settings/SettingsStore"; -import { EMOJI, IEmoji } from '../emoji'; +import { EMOJI, IEmoji, getEmojiFromUnicode } from '../emoji'; import { TimelineRenderingType } from '../contexts/RoomContext'; +import * as recent from '../emojipicker/recent'; const LIMIT = 20; @@ -73,6 +74,7 @@ function colonsTrimmed(str: string): string { export default class EmojiProvider extends AutocompleteProvider { matcher: QueryMatcher; nameMatcher: QueryMatcher; + private readonly recentlyUsed: IEmoji[]; constructor(room: Room, renderingType?: TimelineRenderingType) { super({ commandRegex: EMOJI_REGEX, renderingType }); @@ -87,6 +89,8 @@ export default class EmojiProvider extends AutocompleteProvider { // For removing punctuation shouldMatchWordsOnly: true, }); + + this.recentlyUsed = Array.from(new Set(recent.get().map(getEmojiFromUnicode).filter(Boolean))); } async getCompletions( @@ -109,7 +113,7 @@ export default class EmojiProvider extends AutocompleteProvider { // Do second match with shouldMatchWordsOnly in order to match against 'name' completions = completions.concat(this.nameMatcher.match(matchedString)); - const sorters = []; + let sorters = []; // make sure that emoticons come first sorters.push(c => score(matchedString, c.emoji.emoticon || "")); @@ -130,6 +134,15 @@ export default class EmojiProvider extends AutocompleteProvider { sorters.push(c => c._orderBy); completions = sortBy(uniq(completions), sorters); + completions = completions.slice(0, LIMIT); + + // Do a second sort to place emoji matching with frequently used one on top + sorters = []; + this.recentlyUsed.forEach(emoji => { + sorters.push(c => score(emoji.shortcodes[0], c.emoji.shortcodes[0])); + }); + completions = sortBy(uniq(completions), sorters); + completions = completions.map(c => ({ completion: c.emoji.unicode, component: ( @@ -138,7 +151,7 @@ export default class EmojiProvider extends AutocompleteProvider { ), range, - })).slice(0, LIMIT); + })); } return completions; } diff --git a/test/autocomplete/EmojiProvider-test.ts b/test/autocomplete/EmojiProvider-test.ts index a64312fa29..914ca42901 100644 --- a/test/autocomplete/EmojiProvider-test.ts +++ b/test/autocomplete/EmojiProvider-test.ts @@ -16,6 +16,9 @@ limitations under the License. import EmojiProvider from '../../src/autocomplete/EmojiProvider'; import { mkStubRoom } from '../test-utils/test-utils'; +import { add } from "../../src/emojipicker/recent"; +import { stubClient } from "../test-utils"; +import { MatrixClientPeg } from '../../src/MatrixClientPeg'; const EMOJI_SHORTCODES = [ ":+1", @@ -42,6 +45,8 @@ const TOO_SHORT_EMOJI_SHORTCODE = [ describe('EmojiProvider', function() { const testRoom = mkStubRoom(undefined, undefined, undefined); + stubClient(); + MatrixClientPeg.get(); it.each(EMOJI_SHORTCODES)('Returns consistent results after final colon %s', async function(emojiShortcode) { const ep = new EmojiProvider(testRoom); @@ -64,4 +69,21 @@ describe('EmojiProvider', function() { expect(completions[0].completion).toEqual(expectedEmoji); }); + + it('Returns correct autocompletion based on recently used emoji', async function() { + add("😘"); //kissing_heart + add("😘"); + add("😚"); //kissing_closed_eyes + const emojiProvider = new EmojiProvider(null); + + let completionsList = await emojiProvider.getCompletions(":kis", { beginning: true, end: 3, start: 3 }); + expect(completionsList[0].component.props.title).toEqual(":kissing_heart:"); + expect(completionsList[1].component.props.title).toEqual(":kissing_closed_eyes:"); + + completionsList = await emojiProvider.getCompletions(":kissing_c", { beginning: true, end: 3, start: 3 }); + expect(completionsList[0].component.props.title).toEqual(":kissing_closed_eyes:"); + + completionsList = await emojiProvider.getCompletions(":so", { beginning: true, end: 2, start: 2 }); + expect(completionsList[0].component.props.title).toEqual(":sob:"); + }); });