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

Commit d8c4ab5

Browse files
authored
Merge pull request #6347 from robintown/emojibase-updates
2 parents 770de8f + cc40dd7 commit d8c4ab5

File tree

9 files changed

+67
-87
lines changed

9 files changed

+67
-87
lines changed

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,8 @@
6565
"counterpart": "^0.18.6",
6666
"diff-dom": "^4.2.2",
6767
"diff-match-patch": "^1.0.5",
68-
"emojibase-data": "^5.1.1",
69-
"emojibase-regex": "^4.1.1",
68+
"emojibase-data": "^6.2.0",
69+
"emojibase-regex": "^5.1.3",
7070
"escape-html": "^1.0.3",
7171
"file-saver": "^2.0.5",
7272
"filesize": "6.1.0",
Binary file not shown.
Binary file not shown.

src/HtmlUtils.tsx

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ import { IExtendedSanitizeOptions } from './@types/sanitize-html';
3333
import linkifyMatrix from './linkify-matrix';
3434
import SettingsStore from './settings/SettingsStore';
3535
import { tryTransformPermalinkToLocalHref } from "./utils/permalinks/Permalinks";
36-
import { SHORTCODE_TO_EMOJI, getEmojiFromUnicode } from "./emoji";
36+
import { getEmojiFromUnicode } from "./emoji";
3737
import ReplyThread from "./components/views/elements/ReplyThread";
3838
import { mediaFromMxc } from "./customisations/Media";
3939

@@ -79,20 +79,8 @@ function mightContainEmoji(str: string): boolean {
7979
* @return {String} The shortcode (such as :thumbup:)
8080
*/
8181
export function unicodeToShortcode(char: string): string {
82-
const data = getEmojiFromUnicode(char);
83-
return (data && data.shortcodes ? `:${data.shortcodes[0]}:` : '');
84-
}
85-
86-
/**
87-
* Returns the unicode character for an emoji shortcode
88-
*
89-
* @param {String} shortcode The shortcode (such as :thumbup:)
90-
* @return {String} The emoji character; null if none exists
91-
*/
92-
export function shortcodeToUnicode(shortcode: string): string {
93-
shortcode = shortcode.slice(1, shortcode.length - 1);
94-
const data = SHORTCODE_TO_EMOJI.get(shortcode);
95-
return data ? data.unicode : null;
82+
const shortcodes = getEmojiFromUnicode(char).shortcodes;
83+
return shortcodes.length > 0 ? `:${shortcodes[0]}:` : '';
9684
}
9785

9886
export function processHtmlForSending(html: string): string {

src/autocomplete/EmojiProvider.tsx

Lines changed: 26 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ import { PillCompletion } from './Components';
2525
import { ICompletion, ISelectionRange } from './Autocompleter';
2626
import { uniq, sortBy } from 'lodash';
2727
import SettingsStore from "../settings/SettingsStore";
28-
import { shortcodeToUnicode } from '../HtmlUtils';
2928
import { EMOJI, IEmoji } from '../emoji';
3029

3130
import EMOTICON_REGEX from 'emojibase-regex/emoticon';
@@ -36,20 +35,18 @@ const LIMIT = 20;
3635
// anchored to only match from the start of parts otherwise it'll show emoji suggestions whilst typing matrix IDs
3736
const EMOJI_REGEX = new RegExp('(' + EMOTICON_REGEX.source + '|(?:^|\\s):[+-\\w]*:?)$', 'g');
3837

39-
interface IEmojiShort {
38+
interface ISortedEmoji {
4039
emoji: IEmoji;
41-
shortname: string;
4240
_orderBy: number;
4341
}
4442

45-
const EMOJI_SHORTNAMES: IEmojiShort[] = EMOJI.sort((a, b) => {
43+
const SORTED_EMOJI: ISortedEmoji[] = EMOJI.sort((a, b) => {
4644
if (a.group === b.group) {
4745
return a.order - b.order;
4846
}
4947
return a.group - b.group;
5048
}).map((emoji, index) => ({
5149
emoji,
52-
shortname: `:${emoji.shortcodes[0]}:`,
5350
// Include the index so that we can preserve the original order
5451
_orderBy: index,
5552
}));
@@ -64,20 +61,18 @@ function score(query, space) {
6461
}
6562

6663
export default class EmojiProvider extends AutocompleteProvider {
67-
matcher: QueryMatcher<IEmojiShort>;
68-
nameMatcher: QueryMatcher<IEmojiShort>;
64+
matcher: QueryMatcher<ISortedEmoji>;
65+
nameMatcher: QueryMatcher<ISortedEmoji>;
6966

7067
constructor() {
7168
super(EMOJI_REGEX);
72-
this.matcher = new QueryMatcher<IEmojiShort>(EMOJI_SHORTNAMES, {
73-
keys: ['emoji.emoticon', 'shortname'],
74-
funcs: [
75-
(o) => o.emoji.shortcodes.length > 1 ? o.emoji.shortcodes.slice(1).map(s => `:${s}:`).join(" ") : "", // aliases
76-
],
69+
this.matcher = new QueryMatcher<ISortedEmoji>(SORTED_EMOJI, {
70+
keys: ['emoji.emoticon'],
71+
funcs: [o => o.emoji.shortcodes.map(s => `:${s}:`)],
7772
// For matching against ascii equivalents
7873
shouldMatchWordsOnly: false,
7974
});
80-
this.nameMatcher = new QueryMatcher(EMOJI_SHORTNAMES, {
75+
this.nameMatcher = new QueryMatcher(SORTED_EMOJI, {
8176
keys: ['emoji.annotation'],
8277
// For removing punctuation
8378
shouldMatchWordsOnly: true,
@@ -105,34 +100,33 @@ export default class EmojiProvider extends AutocompleteProvider {
105100

106101
const sorters = [];
107102
// make sure that emoticons come first
108-
sorters.push((c) => score(matchedString, c.emoji.emoticon || ""));
103+
sorters.push(c => score(matchedString, c.emoji.emoticon || ""));
109104

110-
// then sort by score (Infinity if matchedString not in shortname)
111-
sorters.push((c) => score(matchedString, c.shortname));
105+
// then sort by score (Infinity if matchedString not in shortcode)
106+
sorters.push(c => score(matchedString, c.emoji.shortcodes[0]));
112107
// then sort by max score of all shortcodes, trim off the `:`
113-
sorters.push((c) => Math.min(...c.emoji.shortcodes.map(s => score(matchedString.substring(1), s))));
114-
// If the matchedString is not empty, sort by length of shortname. Example:
108+
sorters.push(c => Math.min(
109+
...c.emoji.shortcodes.map(s => score(matchedString.substring(1), s)),
110+
));
111+
// If the matchedString is not empty, sort by length of shortcode. Example:
115112
// matchedString = ":bookmark"
116113
// completions = [":bookmark:", ":bookmark_tabs:", ...]
117114
if (matchedString.length > 1) {
118-
sorters.push((c) => c.shortname.length);
115+
sorters.push(c => c.emoji.shortcodes[0].length);
119116
}
120117
// Finally, sort by original ordering
121-
sorters.push((c) => c._orderBy);
118+
sorters.push(c => c._orderBy);
122119
completions = sortBy(uniq(completions), sorters);
123120

124-
completions = completions.map(({ shortname }) => {
125-
const unicode = shortcodeToUnicode(shortname);
126-
return {
127-
completion: unicode,
128-
component: (
129-
<PillCompletion title={shortname} aria-label={unicode}>
130-
<span>{ unicode }</span>
131-
</PillCompletion>
132-
),
133-
range,
134-
};
135-
}).slice(0, LIMIT);
121+
completions = completions.map(c => ({
122+
completion: c.emoji.unicode,
123+
component: (
124+
<PillCompletion title={`:${c.emoji.shortcodes[0]}:`} aria-label={c.emoji.unicode}>
125+
<span>{ c.emoji.unicode }</span>
126+
</PillCompletion>
127+
),
128+
range,
129+
})).slice(0, LIMIT);
136130
}
137131
return completions;
138132
}

src/components/views/emojipicker/EmojiPicker.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ export const CATEGORY_HEADER_HEIGHT = 22;
3232
export const EMOJI_HEIGHT = 37;
3333
export const EMOJIS_PER_ROW = 8;
3434

35+
const ZERO_WIDTH_JOINER = "\u200D";
36+
3537
interface IProps {
3638
selectedEmojis?: Set<string>;
3739
showQuickReactions?: boolean;
@@ -180,7 +182,7 @@ class EmojiPicker extends React.Component<IProps, IState> {
180182
} else {
181183
emojis = cat.id === "recent" ? this.recentlyUsed : DATA_BY_CATEGORY[cat.id];
182184
}
183-
emojis = emojis.filter(emoji => emoji.filterString.includes(filter));
185+
emojis = emojis.filter(emoji => this.emojiMatchesFilter(emoji, filter));
184186
this.memoizedDataByCategory[cat.id] = emojis;
185187
cat.enabled = emojis.length > 0;
186188
// The setState below doesn't re-render the header and we already have the refs for updateVisibility, so...
@@ -192,6 +194,10 @@ class EmojiPicker extends React.Component<IProps, IState> {
192194
setTimeout(this.updateVisibility, 0);
193195
};
194196

197+
private emojiMatchesFilter = (emoji: IEmoji, filter: string): boolean =>
198+
[emoji.annotation, ...emoji.shortcodes, emoji.emoticon, ...emoji.unicode.split(ZERO_WIDTH_JOINER)]
199+
.some(x => x?.includes(filter));
200+
195201
private onEnterFilter = () => {
196202
const btn = this.bodyRef.current.querySelector<HTMLButtonElement>(".mx_EmojiPicker_item");
197203
if (btn) {

src/components/views/emojipicker/Preview.tsx

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,7 @@ interface IProps {
2727
@replaceableComponent("views.emojipicker.Preview")
2828
class Preview extends React.PureComponent<IProps> {
2929
render() {
30-
const {
31-
unicode = "",
32-
annotation = "",
33-
shortcodes: [shortcode = ""],
34-
} = this.props.emoji || {};
30+
const { unicode, annotation, shortcodes: [shortcode] } = this.props.emoji;
3531

3632
return (
3733
<div className="mx_EmojiPicker_footer mx_EmojiPicker_preview">

src/emoji.ts

Lines changed: 20 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -15,26 +15,23 @@ limitations under the License.
1515
*/
1616

1717
import EMOJIBASE from 'emojibase-data/en/compact.json';
18+
import SHORTCODES from 'emojibase-data/en/shortcodes/iamcal.json';
1819

1920
export interface IEmoji {
2021
annotation: string;
21-
group: number;
22+
group?: number;
2223
hexcode: string;
23-
order: number;
24+
order?: number;
2425
shortcodes: string[];
25-
tags: string[];
26+
tags?: string[];
2627
unicode: string;
28+
skins?: Omit<IEmoji, "shortcodes" | "tags">[]; // Currently unused
2729
emoticon?: string;
2830
}
2931

30-
interface IEmojiWithFilterString extends IEmoji {
31-
filterString?: string;
32-
}
33-
3432
// The unicode is stored without the variant selector
35-
const UNICODE_TO_EMOJI = new Map<string, IEmojiWithFilterString>(); // not exported as gets for it are handled by getEmojiFromUnicode
36-
export const EMOTICON_TO_EMOJI = new Map<string, IEmojiWithFilterString>();
37-
export const SHORTCODE_TO_EMOJI = new Map<string, IEmojiWithFilterString>();
33+
const UNICODE_TO_EMOJI = new Map<string, IEmoji>(); // not exported as gets for it are handled by getEmojiFromUnicode
34+
export const EMOTICON_TO_EMOJI = new Map<string, IEmoji>();
3835

3936
export const getEmojiFromUnicode = unicode => UNICODE_TO_EMOJI.get(stripVariation(unicode));
4037

@@ -62,17 +59,23 @@ export const DATA_BY_CATEGORY = {
6259
"flags": [],
6360
};
6461

65-
const ZERO_WIDTH_JOINER = "\u200D";
66-
6762
// Store various mappings from unicode/emoticon/shortcode to the Emoji objects
68-
EMOJIBASE.forEach((emoji: IEmojiWithFilterString) => {
63+
export const EMOJI: IEmoji[] = EMOJIBASE.map((emojiData: Omit<IEmoji, "shortcodes">) => {
64+
// If there's ever a gap in shortcode coverage, we fudge it by
65+
// filling it in with the emoji's CLDR annotation
66+
const shortcodeData = SHORTCODES[emojiData.hexcode] ??
67+
[emojiData.annotation.toLowerCase().replace(/ /g, "_")];
68+
69+
const emoji: IEmoji = {
70+
...emojiData,
71+
// Homogenize shortcodes by ensuring that everything is an array
72+
shortcodes: typeof shortcodeData === "string" ? [shortcodeData] : shortcodeData,
73+
};
74+
6975
const categoryId = EMOJIBASE_GROUP_ID_TO_CATEGORY[emoji.group];
7076
if (DATA_BY_CATEGORY.hasOwnProperty(categoryId)) {
7177
DATA_BY_CATEGORY[categoryId].push(emoji);
7278
}
73-
// This is used as the string to match the query against when filtering emojis
74-
emoji.filterString = (`${emoji.annotation}\n${emoji.shortcodes.join('\n')}}\n${emoji.emoticon || ''}\n` +
75-
`${emoji.unicode.split(ZERO_WIDTH_JOINER).join("\n")}`).toLowerCase();
7679

7780
// Add mapping from unicode to Emoji object
7881
// The 'unicode' field that we use in emojibase has either
@@ -88,12 +91,7 @@ EMOJIBASE.forEach((emoji: IEmojiWithFilterString) => {
8891
EMOTICON_TO_EMOJI.set(emoji.emoticon, emoji);
8992
}
9093

91-
if (emoji.shortcodes) {
92-
// Add mapping from each shortcode to Emoji object
93-
emoji.shortcodes.forEach(shortcode => {
94-
SHORTCODE_TO_EMOJI.set(shortcode, emoji);
95-
});
96-
}
94+
return emoji;
9795
});
9896

9997
/**
@@ -107,5 +105,3 @@ EMOJIBASE.forEach((emoji: IEmojiWithFilterString) => {
107105
function stripVariation(str) {
108106
return str.replace(/[\uFE00-\uFE0F]$/, "");
109107
}
110-
111-
export const EMOJI: IEmoji[] = EMOJIBASE;

yarn.lock

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3027,15 +3027,15 @@ emoji-regex@^8.0.0:
30273027
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
30283028
integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==
30293029

3030-
emojibase-data@^5.1.1:
3031-
version "5.1.1"
3032-
resolved "https://registry.yarnpkg.com/emojibase-data/-/emojibase-data-5.1.1.tgz#0a0d63dd07ce1376b3d27642f28cafa46f651de6"
3033-
integrity sha512-za/ma5SfogHjwUmGFnDbTvSfm8GGFvFaPS27GPti16YZSp5EPgz+UDsZCATXvJGit+oRNBbG/FtybXHKi2UQgQ==
3030+
emojibase-data@^6.2.0:
3031+
version "6.2.0"
3032+
resolved "https://registry.yarnpkg.com/emojibase-data/-/emojibase-data-6.2.0.tgz#db6c75c36905284fa623f4aa5f468d2be6ed364a"
3033+
integrity sha512-SWKaXD2QeQs06IE7qfJftsI5924Dqzp+V9xaa5RzZIEWhmlrG6Jt2iKwfgOPHu+5S8MEtOI7GdpKsXj46chXOw==
30343034

3035-
emojibase-regex@^4.1.1:
3036-
version "4.1.1"
3037-
resolved "https://registry.yarnpkg.com/emojibase-regex/-/emojibase-regex-4.1.1.tgz#6e781aca520281600fe7a177f1582c33cf1fc545"
3038-
integrity sha512-KSigB1zQkNKFygLZ5bAfHs87LJa1ni8QTQtq8lc53Y74NF3Dk2r7kfa8MpooTO8JBb5Xz660X4tSjDB+I+7elA==
3035+
emojibase-regex@^5.1.3:
3036+
version "5.1.3"
3037+
resolved "https://registry.yarnpkg.com/emojibase-regex/-/emojibase-regex-5.1.3.tgz#f0ef621ed6ec624becd2326f999fd4ea01b94554"
3038+
integrity sha512-gT8T9LxLA8VJdI+8KQtyykB9qKzd7WuUL3M2yw6y9tplFeufOUANg3UKVaKUvkMcRNvZsSElWhxcJrx8WPE12g==
30393039

30403040
encoding@^0.1.11:
30413041
version "0.1.13"

0 commit comments

Comments
 (0)