diff --git a/app/soapbox/features/emoji/__tests__/emoji.test.ts b/app/soapbox/features/emoji/__tests__/emoji.test.ts
index ffe1b1b1f..cd849d0f9 100644
--- a/app/soapbox/features/emoji/__tests__/emoji.test.ts
+++ b/app/soapbox/features/emoji/__tests__/emoji.test.ts
@@ -51,12 +51,12 @@ describe('emoji', () => {
});
it('does an emoji that has no shortcode', () => {
- expect(emojify('๐โ๐จ')).toEqual('');
+ expect(emojify('๐โ๐จ')).toEqual('');
});
it('skips the textual presentation VS15 character', () => {
expect(emojify('โด๏ธ')) // This is U+2734 EIGHT POINTED BLACK STAR then U+FE0E VARIATION SELECTOR-15
- .toEqual('');
+ .toEqual('');
});
it('full v14 unicode emoji map', () => {
diff --git a/app/soapbox/features/emoji/__tests__/emoji_index.test.ts b/app/soapbox/features/emoji/__tests__/emoji_index.test.ts
index bf059d204..d01028cdb 100644
--- a/app/soapbox/features/emoji/__tests__/emoji_index.test.ts
+++ b/app/soapbox/features/emoji/__tests__/emoji_index.test.ts
@@ -1,7 +1,7 @@
-// import { emojiIndex } from 'emoji-mart';
+import { List, Map } from 'immutable';
import pick from 'lodash/pick';
-import search from '../search';
+import search, { addCustomToPool } from '../search';
const trimEmojis = (emoji: any) => pick(emoji, ['id', 'unified', 'native', 'custom']);
@@ -38,72 +38,60 @@ describe('emoji_index', () => {
expect(search('apple').map(trimEmojis)).toEqual(expected);
});
- it('(different behavior from emoji-mart) do not erases custom emoji if not passed again', () => {
+ it('handles custom emojis', () => {
const custom = [
{
id: 'mastodon',
name: 'mastodon',
- short_names: ['mastodon'],
- text: '',
- emoticons: [],
keywords: ['mastodon'],
- imageUrl: 'http://example.com',
- custom: true,
+ skins: { src: 'http://example.com' },
},
];
- search('', { custom });
- // emojiIndex.search('', { custom });
- // const expected = [];
+
+ const custom_emojis = List([
+ Map({ static_url: 'http://example.com', shortcode: 'mastodon' }),
+ ]);
+
const lightExpected = [
{
id: 'mastodon',
custom: true,
},
];
- expect(search('masto').map(trimEmojis)).toEqual(lightExpected);
- });
- it('(different behavior from emoji-mart) erases custom emoji if another is passed', () => {
- const custom = [
- {
- id: 'mastodon',
- name: 'mastodon',
- short_names: ['mastodon'],
- text: '',
- emoticons: [],
- keywords: ['mastodon'],
- imageUrl: 'http://example.com',
- custom: true,
- },
- ];
- search('', { custom });
- // emojiIndex.search('', { custom });
- const expected = [];
- expect(search('masto', { custom: [] }).map(trimEmojis)).toEqual(expected);
+ addCustomToPool(custom);
+ expect(search('masto', {}, custom_emojis).map(trimEmojis)).toEqual(lightExpected);
});
- it('handles custom emoji', () => {
+ it('updates custom emoji if another is passed', () => {
const custom = [
{
id: 'mastodon',
name: 'mastodon',
- short_names: ['mastodon'],
- text: '',
- emoticons: [],
keywords: ['mastodon'],
- imageUrl: 'http://example.com',
- custom: true,
+ skins: { src: 'http://example.com' },
},
];
- search('', { custom });
- // emojiIndex.search('', { custom });
- const expected = [
+
+ addCustomToPool(custom);
+
+ const custom2 = [
{
- id: 'mastodon',
- custom: true,
+ id: 'pleroma',
+ name: 'pleroma',
+ keywords: ['pleroma'],
+ skins: { src: 'http://example.com' },
},
];
- expect(search('masto', { custom }).map(trimEmojis)).toEqual(expected);
+
+ addCustomToPool(custom2);
+
+ const custom_emojis = List([
+ Map({ static_url: 'http://example.com', shortcode: 'pleroma' }),
+ ]);
+
+ const expected = [];
+ expect(search('masto', {}, custom_emojis).map(trimEmojis)).toEqual(expected);
});
it('does an emoji whose unified name is irregular', () => {
diff --git a/app/soapbox/features/emoji/index.ts b/app/soapbox/features/emoji/index.ts
index b31c85266..c7c32451f 100644
--- a/app/soapbox/features/emoji/index.ts
+++ b/app/soapbox/features/emoji/index.ts
@@ -99,7 +99,11 @@ export const emojifyText = (str: string, customEmojis = {}) => {
stack = '';
};
- for (const c of split(str)) {
+ for (let c of split(str)) {
+ if (c.codePointAt(c.length - 1) === 65038) {
+ c = c.slice(0, -1) + String.fromCodePoint(65039);
+ }
+
const unqualified = c + String.fromCodePoint(65039);
if (c in unicodeMapping) {
diff --git a/app/soapbox/features/emoji/mapping.ts b/app/soapbox/features/emoji/mapping.ts
index b8c239535..baf16590d 100644
--- a/app/soapbox/features/emoji/mapping.ts
+++ b/app/soapbox/features/emoji/mapping.ts
@@ -20,14 +20,13 @@ interface UnicodeMap {
* - fe0f is NOT removed for 1f441-fe0f-200d-1f5e8-fe0f even though it has a 200d
*
* this is all wrong
- */
+ */
const blacklist = {
'1f441-fe0f-200d-1f5e8-fe0f': true,
};
const tweaks = {
- '๐โ๐จ๏ธ': ['1f441-200d-1f5e8', 'eye-in-speech-bubble'],
'#โฃ': ['23-20e3', 'hash'],
'*โฃ': ['2a-20e3', 'keycap_star'],
'0โฃ': ['30-20e3', 'zero'],
@@ -40,37 +39,68 @@ const tweaks = {
'7โฃ': ['37-20e3', 'seven'],
'8โฃ': ['38-20e3', 'eight'],
'9โฃ': ['39-20e3', 'nine'],
- '๐ณโ๐': ['1f3f3-fe0f-200d-1f308', 'rainbow-flag'],
+ 'โคโ๐ฅ': ['2764-fe0f-200d-1f525', 'heart_on_fire'],
+ 'โคโ๐ฉน': ['2764-fe0f-200d-1fa79', 'mending_heart'],
+ '๐โ๐จ๏ธ': ['1f441-fe0f-200d-1f5e8-fe0f', 'eye-in-speech-bubble'],
+ '๐๏ธโ๐จ': ['1f441-fe0f-200d-1f5e8-fe0f', 'eye-in-speech-bubble'],
+ '๐โ๐จ': ['1f441-fe0f-200d-1f5e8-fe0f', 'eye-in-speech-bubble'],
+ '๐ตโโ๏ธ': ['1f575-fe0f-200d-2642-fe0f', 'male-detective'],
+ '๐ต๏ธโโ': ['1f575-fe0f-200d-2642-fe0f', 'male-detective'],
+ '๐ตโโ': ['1f575-fe0f-200d-2642-fe0f', 'male-detective'],
+ '๐ตโโ๏ธ': ['1f575-fe0f-200d-2640-fe0f', 'female-detective'],
+ '๐ต๏ธโโ': ['1f575-fe0f-200d-2640-fe0f', 'female-detective'],
+ '๐ตโโ': ['1f575-fe0f-200d-2640-fe0f', 'female-detective'],
+ '๐โโ๏ธ': ['1f3cc-fe0f-200d-2642-fe0f', 'man-golfing'],
+ '๐๏ธโโ': ['1f3cc-fe0f-200d-2642-fe0f', 'man-golfing'],
+ '๐โโ': ['1f3cc-fe0f-200d-2642-fe0f', 'man-golfing'],
+ '๐โโ๏ธ': ['1f3cc-fe0f-200d-2640-fe0f', 'woman-golfing'],
+ '๐๏ธโโ': ['1f3cc-fe0f-200d-2640-fe0f', 'woman-golfing'],
+ '๐โโ': ['1f3cc-fe0f-200d-2640-fe0f', 'woman-golfing'],
+ 'โนโโ๏ธ': ['26f9-fe0f-200d-2642-fe0f', 'man-bouncing-ball'],
+ 'โน๏ธโโ': ['26f9-fe0f-200d-2642-fe0f', 'man-bouncing-ball'],
+ 'โนโโ': ['26f9-fe0f-200d-2642-fe0f', 'man-bouncing-ball'],
+ 'โนโโ๏ธ': ['26f9-fe0f-200d-2640-fe0f', 'woman-bouncing-ball'],
+ 'โน๏ธโโ': ['26f9-fe0f-200d-2640-fe0f', 'woman-bouncing-ball'],
+ 'โนโโ': ['26f9-fe0f-200d-2640-fe0f', 'woman-bouncing-ball'],
+ '๐โโ๏ธ': ['1f3cb-fe0f-200d-2642-fe0f', 'man-lifting-weights'],
+ '๐๏ธโโ': ['1f3cb-fe0f-200d-2642-fe0f', 'man-lifting-weights'],
+ '๐โโ': ['1f3cb-fe0f-200d-2642-fe0f', 'man-lifting-weights'],
+ '๐โโ๏ธ': ['1f3cb-fe0f-200d-2640-fe0f', 'woman-lifting-weights'],
+ '๐๏ธโโ': ['1f3cb-fe0f-200d-2640-fe0f', 'woman-lifting-weights'],
+ '๐โโ': ['1f3cb-fe0f-200d-2640-fe0f', 'woman-lifting-weights'],
+ '๐ณโ๐': ['1f3f3-fe0f-200d-1f308', 'rainbow_flag'],
'๐ณโโง๏ธ': ['1f3f3-fe0f-200d-26a7-fe0f', 'transgender_flag'],
+ '๐ณ๏ธโโง': ['1f3f3-fe0f-200d-26a7-fe0f', 'transgender_flag'],
'๐ณโโง': ['1f3f3-fe0f-200d-26a7-fe0f', 'transgender_flag'],
- 'โด๏ธ': ['2734', 'eight_pointed_black_star'],
+};
+
+const stripcodes = (unified: string, native: string) => {
+ const stripped = unified.replace(stripLeadingZeros, '');
+
+ if (unified.includes('200d') && !(unified in blacklist)) {
+ return stripped;
+ } else {
+ return replaceAll(stripped, '-fe0f', '');
+ }
};
export const generateMappings = (data: EmojiData): UnicodeMap => {
- const result = {};
+ const result: UnicodeMap = {};
const emojis = Object.values(data.emojis ?? {});
for (const value of emojis) {
- // @ts-ignore
for (const item of value.skins) {
const { unified, native } = item;
- const stripped = unified.replace(stripLeadingZeros, '');
-
- if (unified.includes('200d') && !(unified in blacklist)) {
- // @ts-ignore
- result[native] = { unified: stripped, shortcode: value.id };
- } else {
- const twemojiCode = replaceAll(stripped, '-fe0f', '').replace('fe0e', '');
+ const stripped = stripcodes(unified, native);
- // @ts-ignore
- result[native] = { unified: twemojiCode, shortcode: value.id };
- }
+ result[native] = { unified: stripped, shortcode: value.id };
}
}
- for (const [key, value] of Object.entries(tweaks)) {
- // @ts-ignore
- result[key] = { unified: value[0], shortcode: value[1] };
+ for (const [native, [unified, shortcode]] of Object.entries(tweaks)) {
+ const stripped = stripcodes(unified, native);
+
+ result[native] = { unified: stripped, shortcode };
}
return result;