From 423a0fef677fdb6ee65fed0b435d0b756bb0194e Mon Sep 17 00:00:00 2001 From: Scott Nonnenberg Date: Tue, 9 May 2017 18:36:39 -0700 Subject: [PATCH] Jumbomoji support matching Android support FREEBIE --- components/emojijs/demo/emoji.css | 17 +++++ js/emoji_util.js | 79 ++++++++++++++++++++++- stylesheets/manifest.css | 16 +++++ test/emoji_util_test.js | 102 ++++++++++++++++++++++++++++++ test/index.html | 1 + 5 files changed, 213 insertions(+), 2 deletions(-) create mode 100644 test/emoji_util_test.js diff --git a/components/emojijs/demo/emoji.css b/components/emojijs/demo/emoji.css index 4a334edaf..25b57c884 100644 --- a/components/emojijs/demo/emoji.css +++ b/components/emojijs/demo/emoji.css @@ -44,3 +44,20 @@ img.emoji { width: 1em; height: 1em; } + +img.emoji.small { + width: 1.25em; + height: 1.25em; +} +img.emoji.medium { + width: 1.5em; + height: 1.5em; +} +img.emoji.large { + width: 1.75em; + height: 1.75em; +} +img.emoji.jumbo { + width: 2em; + height: 2em; +} diff --git a/js/emoji_util.js b/js/emoji_util.js index 50702bc25..f4659732e 100644 --- a/js/emoji_util.js +++ b/js/emoji_util.js @@ -16,9 +16,83 @@ this.img_sets.apple.path = '/images/emoji/apple/'; this.replace_mode = 'img'; }; + + EmojiConvertor.prototype.getCountOfAllMatches = function(str, regex) { + var match = regex.exec(str); + var count = 0; + + while (match) { + count += 1; + match = regex.exec(str); + } + + return count; + }; + + EmojiConvertor.prototype.hasNormalCharacters = function(str) { + var self = this; + var noEmoji = str.replace(self.rx_unified, '').trim(); + return noEmoji.length > 0; + }; + + EmojiConvertor.prototype.getSizeClass = function(str) { + var self = this; + + if (self.hasNormalCharacters(str)) { + return ''; + } + + var emojiCount = self.getCountOfAllMatches(str, self.rx_unified); + if (emojiCount > 8) { + return ''; + } + else if (emojiCount > 6) { + return 'small'; + } + else if (emojiCount > 4) { + return 'medium'; + } + else if (emojiCount > 2) { + return 'large'; + } + else { + return 'jumbo'; + } + }; + + // A stripped-down version of the original: https://github.com/WhisperSystems/Signal-Desktop/blob/aed573562018462fbacd8f2f715e9daeddcde0dd/components/emojijs/lib/emoji.js#L323-L396 + // One primary change - we inject the second parameter as an additional class + EmojiConvertor.prototype.replacement = function(idx, sizeClass, actual, wrapper, variation) { + var self = this; + var img_set = self.img_set; + + var extra = ''; + var variation_idx = 0; + if (typeof variation === 'object') { + extra = self.replacement(variation.idx, null, variation.actual, variation.wrapper); + variation_idx = idx + '-' + variation.idx; + } + + var img = self.data[idx][7] || self.img_sets[img_set].path + idx + '.png' + self.img_suffix; + var title = self.include_title ? ' title="' + (actual || self.data[idx][3][0]) + '"' : ''; + + if (variation_idx && self.variations_data[variation_idx] && self.variations_data[variation_idx][2] && !self.data[idx][7]) { + if (self.variations_data[variation_idx][2] & self.img_sets[self.img_set].mask) { + img = self.img_sets[self.img_set].path + variation_idx + '.png'; + extra = ''; + } + } + + return ''; + }; + + // Modeled after the original: https://github.com/WhisperSystems/Signal-Desktop/blob/aed573562018462fbacd8f2f715e9daeddcde0dd/components/emojijs/lib/emoji.js#L265-L286 EmojiConvertor.prototype.replace_unified = function(str) { var self = this; self.init_unified(); + + var sizeClass = self.getSizeClass(str); + return str.replace(self.rx_unified, function(m, p1, p2) { var val = self.map.unified[p1]; if (!val) { return m; } @@ -29,14 +103,14 @@ if (p2 == '\uD83C\uDFFE') { idx = '1f3fe'; } if (p2 == '\uD83C\uDFFF') { idx = '1f3ff'; } if (idx) { - return self.replacement(val, null, null, { + return self.replacement(val, sizeClass, null, null, { idx : idx, actual : p2, wrapper : ':' }); } // wrap names in :'s - return self.replacement(val, ':' + self.data[val][3][0] + ':'); + return self.replacement(val, sizeClass, ':' + self.data[val][3][0] + ':'); }); }; window.emoji = new EmojiConvertor(); @@ -46,6 +120,7 @@ if (!$el || !$el.length) { return; } + $el.html(emoji.replace_unified($el.html())); }; diff --git a/stylesheets/manifest.css b/stylesheets/manifest.css index bfd594963..93abea79f 100644 --- a/stylesheets/manifest.css +++ b/stylesheets/manifest.css @@ -704,6 +704,22 @@ img.emoji { width: 1em; height: 1em; } +img.emoji.small { + width: 1.25em; + height: 1.25em; } + +img.emoji.medium { + width: 1.5em; + height: 1.5em; } + +img.emoji.large { + width: 1.75em; + height: 1.75em; } + +img.emoji.jumbo { + width: 2em; + height: 2em; } + .settings.modal { padding: 50px; } .settings.modal .content { diff --git a/test/emoji_util_test.js b/test/emoji_util_test.js new file mode 100644 index 000000000..e306f4c00 --- /dev/null +++ b/test/emoji_util_test.js @@ -0,0 +1,102 @@ +'use strict'; + +describe('EmojiUtil', function() { + describe('getCountOfAllMatches', function() { + it('returns zero for string with no matches', function() { + var r = /s/g; + var str = 'no match'; + var actual = emoji.getCountOfAllMatches(str, r); + assert.equal(actual, 0); + }); + it('returns 1 for one match', function() { + var r = /s/g; + var str = 'just one match'; + var actual = emoji.getCountOfAllMatches(str, r); + assert.equal(actual, 1); + }); + it('returns 2 for two matches', function() { + var r = /s/g; + var str = 's + s'; + var actual = emoji.getCountOfAllMatches(str, r); + assert.equal(actual, 2); + }); + }); + + describe('hasNormalCharacters', function() { + it('returns true for all normal text', function() { + var str = 'normal'; + var actual = emoji.hasNormalCharacters(str); + assert.equal(actual, true); + }); + it('returns false for all emoji text', function() { + var str = '🔥🔥🔥🔥'; + var actual = emoji.hasNormalCharacters(str); + assert.equal(actual, false); + }); + it('returns false for emojis mixed with spaces', function() { + var str = '🔥 🔥 🔥 🔥'; + var actual = emoji.hasNormalCharacters(str); + assert.equal(actual, false); + }); + it('returns true for emojis and text', function() { + var str = '🔥 normal 🔥 🔥 🔥'; + var actual = emoji.hasNormalCharacters(str); + assert.equal(actual, true); + }); + }); + + describe('getSizeClass', function() { + it('returns nothing for non-emoji text', function() { + assert.equal(emoji.getSizeClass('normal text'), ''); + }); + it('returns nothing for emojis mixed with text', function() { + assert.equal(emoji.getSizeClass('🔥 normal 🔥'), ''); + }); + it('returns nothing for more than 8 emojis', function() { + assert.equal(emoji.getSizeClass('🔥🔥 🔥🔥 🔥🔥 🔥🔥 🔥'), ''); + }); + it('returns "small" for 7-8 emojis', function() { + assert.equal(emoji.getSizeClass('🔥🔥 🔥🔥 🔥🔥 🔥🔥'), 'small'); + assert.equal(emoji.getSizeClass('🔥🔥 🔥🔥 🔥🔥 🔥'), 'small'); + }); + it('returns "medium" for 5-6 emojis', function() { + assert.equal(emoji.getSizeClass('🔥🔥 🔥🔥 🔥🔥'), 'medium'); + assert.equal(emoji.getSizeClass('🔥🔥 🔥🔥 🔥'), 'medium'); + }); + it('returns "large" for 3-4 emojis', function() { + assert.equal(emoji.getSizeClass('🔥🔥 🔥🔥'), 'large'); + assert.equal(emoji.getSizeClass('🔥🔥 🔥'), 'large'); + }); + it('returns "jumbo" for 1-2 emojis', function() { + assert.equal(emoji.getSizeClass('🔥🔥'), 'jumbo'); + assert.equal(emoji.getSizeClass('🔥'), 'jumbo'); + }); + }); + + describe('replacement', function() { + it('returns an tag', function() { + var actual = emoji.replacement('1f525'); + assert.equal(actual, ''); + }); + it('returns an tag with provided sizeClass', function() { + var actual = emoji.replacement('1f525', 'large'); + assert.equal(actual, ''); + }); + }); + + describe('replace_unified', function() { + it('returns images for every emoji', function() { + var actual = emoji.replace_unified('🏠 🔥'); + var expected = '' + + ' '; + + assert.equal(expected, actual); + }); + it('properly hyphenates a variation', function() { + var actual = emoji.replace_unified('💪🏿'); // muscle with dark skin tone modifier + var expected = ''; + + assert.equal(expected, actual); + }); + }); +}); diff --git a/test/index.html b/test/index.html index 4f72299d2..706eb60fc 100644 --- a/test/index.html +++ b/test/index.html @@ -576,6 +576,7 @@ +