Skip to content
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
20 changes: 16 additions & 4 deletions apps/meteor/app/emoji-native/lib/generateEmojiData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ function hexFromEmoji(emoji: string): string {
return [...emoji].map((cp) => cp.codePointAt(0)!.toString(16)).join('-');
}

function isRegionalIndicator(hexcode: string): boolean {
if (hexcode.includes('-')) return false;
const cp = parseInt(hexcode, 16);
return cp >= 0x1f1e6 && cp <= 0x1f1ff;
}

function getShortcodes(hexcode: string): string[] {
const entry = (shortcodes as Record<string, string | string[]>)[hexcode];
if (!entry) return [];
Expand All @@ -55,7 +61,8 @@ function buildEmojiData() {
// Skip component group (skin tones, hair styles)
if (emojiData.group === 2) continue;

const category = groupToCategory[emojiData.group ?? -1];
const isRegional = isRegionalIndicator(emojiData.hexcode);
const category = groupToCategory[emojiData.group ?? -1] ?? (isRegional ? 'flags' : undefined);
if (!category) continue;

const codes = getShortcodes(emojiData.hexcode);
Expand All @@ -80,7 +87,7 @@ function buildEmojiData() {
emojiList[key] = entry;

// Only add to category if it's NOT a skin tone variant
if (!emojiData.tone) {
if (!emojiData.tone && !isRegional) {
emojisByCategory[category].push(primaryShortcode);
}

Expand All @@ -98,10 +105,9 @@ function buildEmojiData() {
if (!skin.tone) continue;

const tones = Array.isArray(skin.tone) ? skin.tone : [skin.tone];
const toneKey = `:${primaryShortcode}_tone${tones.join('-')}:`;
const skinHex = hexFromEmoji(skin.emoji);

emojiList[toneKey] = {
const skinEntry: EmojiEntry = {
uc_base: skinHex,
uc_output: skinHex,
uc_match: skinHex,
Expand All @@ -111,6 +117,12 @@ function buildEmojiData() {
emojiPackage: 'native',
unicode: skin.emoji,
};

const toneShortcodes = getShortcodes(skin.hexcode);
const fallbackShortcode = `${primaryShortcode}_tone${tones.join('-')}`;
for (const code of [...toneShortcodes, fallbackShortcode]) {
emojiList[`:${code}:`] = skinEntry;
}
}
}
}
Expand Down
17 changes: 17 additions & 0 deletions apps/meteor/app/emoji-native/lib/legacyEmojioneMap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1407,4 +1407,21 @@ export const legacyEmojioneMap: Record<string, string> = {
'za': '🇿🇦',
'zm': '🇿🇲',
'zw': '🇿🇼',
'digit_zero': '0️⃣',
'digit_one': '1️⃣',
'digit_two': '2️⃣',
'digit_three': '3️⃣',
'digit_four': '4️⃣',
'digit_five': '5️⃣',
'digit_six': '6️⃣',
'digit_seven': '7️⃣',
'digit_eight': '8️⃣',
'digit_nine': '9️⃣',
'pound_symbol': '#️⃣',
'asterisk_symbol': '*️⃣',
'tone1': '🏻',
'tone2': '🏼',
'tone3': '🏽',
'tone4': '🏾',
'tone5': '🏿',
};
56 changes: 56 additions & 0 deletions apps/meteor/tests/unit/app/emoji/emojiNativeParser.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { expect } from 'chai';
import { describe, it } from 'mocha';

import { getEmojiData } from '../../../../app/emoji-native/lib/generateEmojiData';
import { shortnameToUnicode } from '../../../../app/emoji-native/lib/shortnameToUnicode';

describe('emoji-native shortcode resolution', () => {
describe('shortnameToUnicode', () => {
it('resolves skin-tone variants stored under emojione alternate names', () => {
expect(shortnameToUnicode(':thumbsup_tone3:')).to.equal('👍🏽');
expect(shortnameToUnicode(':yes_tone3:')).to.equal('👍🏽');
expect(shortnameToUnicode(':clapping_hands_tone2:')).to.equal('👏🏼');
expect(shortnameToUnicode(':pray_tone4:')).to.equal('🙏🏾');
});

it('still resolves the emojibase primary skin-tone name', () => {
expect(shortnameToUnicode(':+1_tone3:')).to.equal('👍🏽');
});

it('resolves regional indicator letters', () => {
expect(shortnameToUnicode(':regional_indicator_a:')).to.equal('🇦');
expect(shortnameToUnicode(':regional_indicator_z:')).to.equal('🇿');
});

it('resolves legacy emojione base aliases', () => {
expect(shortnameToUnicode(':digit_zero:')).to.equal('0️⃣');
expect(shortnameToUnicode(':digit_nine:')).to.equal('9️⃣');
expect(shortnameToUnicode(':pound_symbol:')).to.equal('#️⃣');
expect(shortnameToUnicode(':asterisk_symbol:')).to.equal('*️⃣');
expect(shortnameToUnicode(':tone3:')).to.equal('🏽');
});

it('leaves unknown shortcodes untouched', () => {
expect(shortnameToUnicode(':not_a_real_emoji:')).to.equal(':not_a_real_emoji:');
});

it('resolves multiple shortcodes within a single string', () => {
expect(shortnameToUnicode('hi :thumbsup_tone3: and :regional_indicator_a:')).to.equal('hi 👍🏽 and 🇦');
});
});

describe('getEmojiData', () => {
it('registers regional indicators in emojiList but keeps them out of the picker', () => {
const { emojiList, emojisByCategory } = getEmojiData();
expect(emojiList[':regional_indicator_a:']?.unicode).to.equal('🇦');
expect(emojisByCategory.flags).to.not.include('regional_indicator_a');
});

it('registers alternate skin-tone shortnames alongside the primary', () => {
const { emojiList } = getEmojiData();
expect(emojiList[':thumbsup_tone3:']?.unicode).to.equal('👍🏽');
expect(emojiList[':yes_tone3:']?.unicode).to.equal('👍🏽');
expect(emojiList[':+1_tone3:']?.unicode).to.equal('👍🏽');
});
});
});
12 changes: 12 additions & 0 deletions packages/message-parser/tests/emoji.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,18 @@ test.each([
[':smile:', [bigEmoji([emoji('smile')])]],
['Hi :+1:', [paragraph([plain('Hi '), emoji('+1')])]],
['Hi :+1_tone4:', [paragraph([plain('Hi '), emoji('+1_tone4')])]],
['Hi :thumbsup_tone3:', [paragraph([plain('Hi '), emoji('thumbsup_tone3')])]],
['Hi :yes_tone3:', [paragraph([plain('Hi '), emoji('yes_tone3')])]],
['Hi :clapping_hands_tone2:', [paragraph([plain('Hi '), emoji('clapping_hands_tone2')])]],
['Hi :pray_tone4:', [paragraph([plain('Hi '), emoji('pray_tone4')])]],
['Hi :regional_indicator_a:', [paragraph([plain('Hi '), emoji('regional_indicator_a')])]],
['Hi :digit_zero:', [paragraph([plain('Hi '), emoji('digit_zero')])]],
['Hi :pound_symbol:', [paragraph([plain('Hi '), emoji('pound_symbol')])]],
['Hi :asterisk_symbol:', [paragraph([plain('Hi '), emoji('asterisk_symbol')])]],
['Hi :tone3:', [paragraph([plain('Hi '), emoji('tone3')])]],
[':thumbsup_tone3:', [bigEmoji([emoji('thumbsup_tone3')])]],
[':regional_indicator_a:', [bigEmoji([emoji('regional_indicator_a')])]],
[':digit_zero:', [bigEmoji([emoji('digit_zero')])]],
])('parses %p', (input, output) => {
expect(parse(input)).toEqual(output);
});
Expand Down
Loading