Use "frequently used emojis" for autocompletion in composer (#8998)
Co-authored-by: grimhilt <grimhilt@users.noreply.github.com>pull/28788/head^2
parent
9edd49818c
commit
35ba389d61
|
@ -29,8 +29,9 @@ import QueryMatcher from './QueryMatcher';
|
||||||
import { PillCompletion } from './Components';
|
import { PillCompletion } from './Components';
|
||||||
import { ICompletion, ISelectionRange } from './Autocompleter';
|
import { ICompletion, ISelectionRange } from './Autocompleter';
|
||||||
import SettingsStore from "../settings/SettingsStore";
|
import SettingsStore from "../settings/SettingsStore";
|
||||||
import { EMOJI, IEmoji } from '../emoji';
|
import { EMOJI, IEmoji, getEmojiFromUnicode } from '../emoji';
|
||||||
import { TimelineRenderingType } from '../contexts/RoomContext';
|
import { TimelineRenderingType } from '../contexts/RoomContext';
|
||||||
|
import * as recent from '../emojipicker/recent';
|
||||||
|
|
||||||
const LIMIT = 20;
|
const LIMIT = 20;
|
||||||
|
|
||||||
|
@ -73,6 +74,7 @@ function colonsTrimmed(str: string): string {
|
||||||
export default class EmojiProvider extends AutocompleteProvider {
|
export default class EmojiProvider extends AutocompleteProvider {
|
||||||
matcher: QueryMatcher<ISortedEmoji>;
|
matcher: QueryMatcher<ISortedEmoji>;
|
||||||
nameMatcher: QueryMatcher<ISortedEmoji>;
|
nameMatcher: QueryMatcher<ISortedEmoji>;
|
||||||
|
private readonly recentlyUsed: IEmoji[];
|
||||||
|
|
||||||
constructor(room: Room, renderingType?: TimelineRenderingType) {
|
constructor(room: Room, renderingType?: TimelineRenderingType) {
|
||||||
super({ commandRegex: EMOJI_REGEX, renderingType });
|
super({ commandRegex: EMOJI_REGEX, renderingType });
|
||||||
|
@ -87,6 +89,8 @@ export default class EmojiProvider extends AutocompleteProvider {
|
||||||
// For removing punctuation
|
// For removing punctuation
|
||||||
shouldMatchWordsOnly: true,
|
shouldMatchWordsOnly: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.recentlyUsed = Array.from(new Set(recent.get().map(getEmojiFromUnicode).filter(Boolean)));
|
||||||
}
|
}
|
||||||
|
|
||||||
async getCompletions(
|
async getCompletions(
|
||||||
|
@ -109,7 +113,7 @@ export default class EmojiProvider extends AutocompleteProvider {
|
||||||
// Do second match with shouldMatchWordsOnly in order to match against 'name'
|
// Do second match with shouldMatchWordsOnly in order to match against 'name'
|
||||||
completions = completions.concat(this.nameMatcher.match(matchedString));
|
completions = completions.concat(this.nameMatcher.match(matchedString));
|
||||||
|
|
||||||
const sorters = [];
|
let sorters = [];
|
||||||
// make sure that emoticons come first
|
// make sure that emoticons come first
|
||||||
sorters.push(c => score(matchedString, c.emoji.emoticon || ""));
|
sorters.push(c => score(matchedString, c.emoji.emoticon || ""));
|
||||||
|
|
||||||
|
@ -130,6 +134,15 @@ export default class EmojiProvider extends AutocompleteProvider {
|
||||||
sorters.push(c => c._orderBy);
|
sorters.push(c => c._orderBy);
|
||||||
completions = sortBy(uniq(completions), sorters);
|
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 => ({
|
completions = completions.map(c => ({
|
||||||
completion: c.emoji.unicode,
|
completion: c.emoji.unicode,
|
||||||
component: (
|
component: (
|
||||||
|
@ -138,7 +151,7 @@ export default class EmojiProvider extends AutocompleteProvider {
|
||||||
</PillCompletion>
|
</PillCompletion>
|
||||||
),
|
),
|
||||||
range,
|
range,
|
||||||
})).slice(0, LIMIT);
|
}));
|
||||||
}
|
}
|
||||||
return completions;
|
return completions;
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,9 @@ limitations under the License.
|
||||||
|
|
||||||
import EmojiProvider from '../../src/autocomplete/EmojiProvider';
|
import EmojiProvider from '../../src/autocomplete/EmojiProvider';
|
||||||
import { mkStubRoom } from '../test-utils/test-utils';
|
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 = [
|
const EMOJI_SHORTCODES = [
|
||||||
":+1",
|
":+1",
|
||||||
|
@ -42,6 +45,8 @@ const TOO_SHORT_EMOJI_SHORTCODE = [
|
||||||
|
|
||||||
describe('EmojiProvider', function() {
|
describe('EmojiProvider', function() {
|
||||||
const testRoom = mkStubRoom(undefined, undefined, undefined);
|
const testRoom = mkStubRoom(undefined, undefined, undefined);
|
||||||
|
stubClient();
|
||||||
|
MatrixClientPeg.get();
|
||||||
|
|
||||||
it.each(EMOJI_SHORTCODES)('Returns consistent results after final colon %s', async function(emojiShortcode) {
|
it.each(EMOJI_SHORTCODES)('Returns consistent results after final colon %s', async function(emojiShortcode) {
|
||||||
const ep = new EmojiProvider(testRoom);
|
const ep = new EmojiProvider(testRoom);
|
||||||
|
@ -64,4 +69,21 @@ describe('EmojiProvider', function() {
|
||||||
|
|
||||||
expect(completions[0].completion).toEqual(expectedEmoji);
|
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:");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue