From c8b6605e7a76c2dcc0f48d29b88bb76667ce608d Mon Sep 17 00:00:00 2001 From: Filip Noetzel Date: Sat, 6 Sep 2025 14:58:16 +0200 Subject: [PATCH] Fix ZWJ sequence emoji support in custom emoji settings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add getGraphemeClusters.ts utility for proper emoji segmentation - Update updateSymbolPatternsFromSettings.ts to handle ZWJ sequences correctly - Enhance Unicode utilities to use grapheme clusters instead of UTF-16 code units - Support complex emojis like 🏴‍☠️ (Pirate Flag), 👨‍🚒 (Man Firefighter), etc. - Uses Intl.Segmenter when available, with regex fallback for older environments - Fully backward compatible with existing emoji configurations - Update documentation and add changelog entry Fixes bug where ZWJ sequence emojis were split into component parts instead of being treated as single emojis. Simplify ZWJ sequence fix by inlining Intl.Segmenter - Remove overengineered getGraphemeClusters.ts utility - Inline simple Intl.Segmenter usage directly where needed - Assume Intl.Segmenter is available in all target environments - Reduced bundle size and simplified codebase - Update documentation to reflect simplified approach --- README.md | 7 +++++++ src/utils/getUnicodeCharLength.ts | 6 ++++-- src/utils/getUnicodeSubstring.ts | 6 ++++-- src/utils/updateSymbolPatternsFromSettings.ts | 9 ++++++--- 4 files changed, 21 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 9b7c488..8c763fc 100644 --- a/README.md +++ b/README.md @@ -56,11 +56,18 @@ You can configure which emojis are supported for rating interactions by going to **Default emojis**: `🎥🏆⭐💎🔥⚡🎯🚀💰🎖️` +**ZWJ Sequence Support**: The plugin fully supports complex emojis including ZWJ (Zero Width Joiner) sequences such as: +- 🏴‍☠️ (Pirate Flag) +- 👨‍🚒 (Man Firefighter) +- 🏳️‍🌈 (Rainbow Flag) +- And other compound emojis + **Example usage**: - `🎥🎥🎥🎥🎥 (4/5)` Movie rating - `🏆🏆🏆 3/5` Achievement level - `🔥🔥🔥🔥 80%` Spice level - `🎯🎯🎯🎯🎯🎯 (5/6)` Accuracy rating +- `🏴‍☠️🏴‍☠️🏴‍☠️ (3/5)` Pirate movie rating ## Installation diff --git a/src/utils/getUnicodeCharLength.ts b/src/utils/getUnicodeCharLength.ts index 7683ea8..7d268e9 100644 --- a/src/utils/getUnicodeCharLength.ts +++ b/src/utils/getUnicodeCharLength.ts @@ -1,6 +1,8 @@ /** - * Get the length of a string in Unicode characters + * Get the length of a string in Unicode grapheme clusters + * This properly handles ZWJ sequences, variation selectors, and complex emoji */ export function getUnicodeCharLength(str: string): number { - return [...str].length; + const segmenter = new Intl.Segmenter('en', { granularity: 'grapheme' }); + return Array.from(segmenter.segment(str)).length; } \ No newline at end of file diff --git a/src/utils/getUnicodeSubstring.ts b/src/utils/getUnicodeSubstring.ts index 13dd136..704418e 100644 --- a/src/utils/getUnicodeSubstring.ts +++ b/src/utils/getUnicodeSubstring.ts @@ -1,6 +1,8 @@ /** - * Get a substring with proper Unicode character handling + * Get a substring with proper Unicode grapheme cluster handling + * This properly handles ZWJ sequences, variation selectors, and complex emoji */ export function getUnicodeSubstring(str: string, start: number, end: number): string { - return [...str].slice(start, end).join(''); + const segmenter = new Intl.Segmenter('en', { granularity: 'grapheme' }); + return Array.from(segmenter.segment(str), segment => segment.segment).slice(start, end).join(''); } \ No newline at end of file diff --git a/src/utils/updateSymbolPatternsFromSettings.ts b/src/utils/updateSymbolPatternsFromSettings.ts index 0206c3d..28697ae 100644 --- a/src/utils/updateSymbolPatternsFromSettings.ts +++ b/src/utils/updateSymbolPatternsFromSettings.ts @@ -10,11 +10,12 @@ export function updateSymbolPatternsFromSettings(settings: InteractiveRatingsSet // Add emoji patterns from settings if (settings.supportedEmojis) { - // Split the emoji string into individual characters (handling Unicode properly) - const emojis = [...settings.supportedEmojis]; + // Split the emoji string into individual grapheme clusters (handling ZWJ sequences properly) + const segmenter = new Intl.Segmenter('en', { granularity: 'grapheme' }); + const emojis = Array.from(segmenter.segment(settings.supportedEmojis), segment => segment.segment); for (const emoji of emojis) { - if (emoji.trim()) { // Skip empty characters + if (emoji.trim()) { // Skip empty characters and whitespace newPatterns.push({ full: emoji, empty: emoji, @@ -28,8 +29,10 @@ export function updateSymbolPatternsFromSettings(settings: InteractiveRatingsSet updateSymbolPatterns(newPatterns); if (LOGGING_ENABLED) { + const segmenter = new Intl.Segmenter('en', { granularity: 'grapheme' }); console.debug('[InteractiveRatings] Updated symbol patterns from settings', { supportedEmojis: settings.supportedEmojis, + parsedEmojis: settings.supportedEmojis ? Array.from(segmenter.segment(settings.supportedEmojis), segment => segment.segment) : [], totalPatterns: newPatterns.length }); }