Skip to content
This repository has been archived by the owner on Sep 11, 2024. It is now read-only.

Use "frequently used emojis" for autocompletion in composer #8998

Merged
merged 8 commits into from
Jul 21, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 16 additions & 3 deletions src/autocomplete/EmojiProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -73,6 +74,7 @@ function colonsTrimmed(str: string): string {
export default class EmojiProvider extends AutocompleteProvider {
matcher: QueryMatcher<ISortedEmoji>;
nameMatcher: QueryMatcher<ISortedEmoji>;
private readonly recentlyUsed: IEmoji[];

constructor(room: Room, renderingType?: TimelineRenderingType) {
super({ commandRegex: EMOJI_REGEX, renderingType });
Expand All @@ -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(
Expand All @@ -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 || ""));

Expand All @@ -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: (
Expand All @@ -138,7 +151,7 @@ export default class EmojiProvider extends AutocompleteProvider {
</PillCompletion>
),
range,
})).slice(0, LIMIT);
}));
}
return completions;
}
Expand Down
22 changes: 22 additions & 0 deletions test/autocomplete/EmojiProvider-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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);
Expand All @@ -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:");
});
});