From 5a044dc25b1c04da6253577a3934e857a0c0bd0d Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Fri, 23 Jun 2017 13:26:59 -0500 Subject: refactor option defaults --- app/assets/javascripts/behaviors/gl_emoji.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) (limited to 'app/assets/javascripts/behaviors') diff --git a/app/assets/javascripts/behaviors/gl_emoji.js b/app/assets/javascripts/behaviors/gl_emoji.js index 36ce4fddb72..ca6117c4fd6 100644 --- a/app/assets/javascripts/behaviors/gl_emoji.js +++ b/app/assets/javascripts/behaviors/gl_emoji.js @@ -25,12 +25,9 @@ function assembleFallbackImageSrc(inputName) { return fallbackImageSrc; } -const glEmojiTagDefaults = { - sprite: false, - forceFallback: false, -}; + function glEmojiTag(inputName, options) { - const opts = Object.assign({}, glEmojiTagDefaults, options); + const opts = { sprite: false, forceFallback: false, ...options }; let name = Object.prototype.hasOwnProperty.call(emojiAliases, inputName) ? emojiAliases[inputName] : inputName; let emojiInfo = emojiMap[name]; -- cgit v1.2.3 From d099744dd41af983e5a02f64375b60a8cf6c539f Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Fri, 23 Jun 2017 14:08:06 -0500 Subject: centralize emoji helper methods --- app/assets/javascripts/behaviors/gl_emoji.js | 5 +- .../behaviors/gl_emoji/is_emoji_name_valid.js | 11 -- .../gl_emoji/is_emoji_unicode_supported.js | 120 -------------- .../behaviors/gl_emoji/unicode_support_map.js | 172 --------------------- 4 files changed, 1 insertion(+), 307 deletions(-) delete mode 100644 app/assets/javascripts/behaviors/gl_emoji/is_emoji_name_valid.js delete mode 100644 app/assets/javascripts/behaviors/gl_emoji/is_emoji_unicode_supported.js delete mode 100644 app/assets/javascripts/behaviors/gl_emoji/unicode_support_map.js (limited to 'app/assets/javascripts/behaviors') diff --git a/app/assets/javascripts/behaviors/gl_emoji.js b/app/assets/javascripts/behaviors/gl_emoji.js index ca6117c4fd6..06eb698378c 100644 --- a/app/assets/javascripts/behaviors/gl_emoji.js +++ b/app/assets/javascripts/behaviors/gl_emoji.js @@ -1,8 +1,5 @@ import installCustomElements from 'document-register-element'; -import emojiMap from 'emojis/digests.json'; -import emojiAliases from 'emojis/aliases.json'; -import { getUnicodeSupportMap } from './gl_emoji/unicode_support_map'; -import { isEmojiUnicodeSupported } from './gl_emoji/is_emoji_unicode_supported'; +import { emojiMap, emojiAliases, isEmojiUnicodeSupported, getUnicodeSupportMap } from '../emoji'; installCustomElements(window); diff --git a/app/assets/javascripts/behaviors/gl_emoji/is_emoji_name_valid.js b/app/assets/javascripts/behaviors/gl_emoji/is_emoji_name_valid.js deleted file mode 100644 index be4aeb32c46..00000000000 --- a/app/assets/javascripts/behaviors/gl_emoji/is_emoji_name_valid.js +++ /dev/null @@ -1,11 +0,0 @@ -import emojiMap from 'emojis/digests.json'; -import emojiAliases from 'emojis/aliases.json'; - -function isEmojiNameValid(inputName) { - const name = Object.prototype.hasOwnProperty.call(emojiAliases, inputName) ? - emojiAliases[inputName] : inputName; - - return name && emojiMap[name]; -} - -export default isEmojiNameValid; diff --git a/app/assets/javascripts/behaviors/gl_emoji/is_emoji_unicode_supported.js b/app/assets/javascripts/behaviors/gl_emoji/is_emoji_unicode_supported.js deleted file mode 100644 index 4f8884d05ac..00000000000 --- a/app/assets/javascripts/behaviors/gl_emoji/is_emoji_unicode_supported.js +++ /dev/null @@ -1,120 +0,0 @@ -// On Windows, flags render as two-letter country codes, see http://emojipedia.org/flags/ -const flagACodePoint = 127462; // parseInt('1F1E6', 16) -const flagZCodePoint = 127487; // parseInt('1F1FF', 16) -function isFlagEmoji(emojiUnicode) { - const cp = emojiUnicode.codePointAt(0); - // Length 4 because flags are made of 2 characters which are surrogate pairs - return emojiUnicode.length === 4 && cp >= flagACodePoint && cp <= flagZCodePoint; -} - -// Chrome <57 renders keycaps oddly -// See https://bugs.chromium.org/p/chromium/issues/detail?id=632294 -// Same issue on Windows also fixed in Chrome 57, http://i.imgur.com/rQF7woO.png -function isKeycapEmoji(emojiUnicode) { - return emojiUnicode.length === 3 && emojiUnicode[2] === '\u20E3'; -} - -// Check for a skin tone variation emoji which aren't always supported -const tone1 = 127995;// parseInt('1F3FB', 16) -const tone5 = 127999;// parseInt('1F3FF', 16) -function isSkinToneComboEmoji(emojiUnicode) { - return emojiUnicode.length > 2 && Array.from(emojiUnicode).some((char) => { - const cp = char.codePointAt(0); - return cp >= tone1 && cp <= tone5; - }); -} - -// macOS supports most skin tone emoji's but -// doesn't support the skin tone versions of horse racing -const horseRacingCodePoint = 127943;// parseInt('1F3C7', 16) -function isHorceRacingSkinToneComboEmoji(emojiUnicode) { - const firstCharacter = Array.from(emojiUnicode)[0]; - return firstCharacter && firstCharacter.codePointAt(0) === horseRacingCodePoint && - isSkinToneComboEmoji(emojiUnicode); -} - -// Check for `family_*`, `kiss_*`, `couple_*` -// For ex. Windows 8.1 Firefox 51.0.1, doesn't support these -const zwj = 8205; // parseInt('200D', 16) -const personStartCodePoint = 128102; // parseInt('1F466', 16) -const personEndCodePoint = 128105; // parseInt('1F469', 16) -function isPersonZwjEmoji(emojiUnicode) { - let hasPersonEmoji = false; - let hasZwj = false; - Array.from(emojiUnicode).forEach((character) => { - const cp = character.codePointAt(0); - if (cp === zwj) { - hasZwj = true; - } else if (cp >= personStartCodePoint && cp <= personEndCodePoint) { - hasPersonEmoji = true; - } - }); - - return hasPersonEmoji && hasZwj; -} - -// Helper so we don't have to run `isFlagEmoji` twice -// in `isEmojiUnicodeSupported` logic -function checkFlagEmojiSupport(unicodeSupportMap, emojiUnicode) { - const isFlagResult = isFlagEmoji(emojiUnicode); - return ( - (unicodeSupportMap.flag && isFlagResult) || - !isFlagResult - ); -} - -// Helper so we don't have to run `isSkinToneComboEmoji` twice -// in `isEmojiUnicodeSupported` logic -function checkSkinToneModifierSupport(unicodeSupportMap, emojiUnicode) { - const isSkinToneResult = isSkinToneComboEmoji(emojiUnicode); - return ( - (unicodeSupportMap.skinToneModifier && isSkinToneResult) || - !isSkinToneResult - ); -} - -// Helper func so we don't have to run `isHorceRacingSkinToneComboEmoji` twice -// in `isEmojiUnicodeSupported` logic -function checkHorseRacingSkinToneComboEmojiSupport(unicodeSupportMap, emojiUnicode) { - const isHorseRacingSkinToneResult = isHorceRacingSkinToneComboEmoji(emojiUnicode); - return ( - (unicodeSupportMap.horseRacing && isHorseRacingSkinToneResult) || - !isHorseRacingSkinToneResult - ); -} - -// Helper so we don't have to run `isPersonZwjEmoji` twice -// in `isEmojiUnicodeSupported` logic -function checkPersonEmojiSupport(unicodeSupportMap, emojiUnicode) { - const isPersonZwjResult = isPersonZwjEmoji(emojiUnicode); - return ( - (unicodeSupportMap.personZwj && isPersonZwjResult) || - !isPersonZwjResult - ); -} - -// Takes in a support map and determines whether -// the given unicode emoji is supported on the platform. -// -// Combines all the edge case tests into a one-stop shop method -function isEmojiUnicodeSupported(unicodeSupportMap = {}, emojiUnicode, unicodeVersion) { - const isOlderThanChrome57 = unicodeSupportMap.meta && unicodeSupportMap.meta.isChrome && - unicodeSupportMap.meta.chromeVersion < 57; - - // For comments about each scenario, see the comments above each individual respective function - return unicodeSupportMap[unicodeVersion] && - !(isOlderThanChrome57 && isKeycapEmoji(emojiUnicode)) && - checkFlagEmojiSupport(unicodeSupportMap, emojiUnicode) && - checkSkinToneModifierSupport(unicodeSupportMap, emojiUnicode) && - checkHorseRacingSkinToneComboEmojiSupport(unicodeSupportMap, emojiUnicode) && - checkPersonEmojiSupport(unicodeSupportMap, emojiUnicode); -} - -export { - isEmojiUnicodeSupported, - isFlagEmoji, - isKeycapEmoji, - isSkinToneComboEmoji, - isHorceRacingSkinToneComboEmoji, - isPersonZwjEmoji, -}; diff --git a/app/assets/javascripts/behaviors/gl_emoji/unicode_support_map.js b/app/assets/javascripts/behaviors/gl_emoji/unicode_support_map.js deleted file mode 100644 index 257df55e54f..00000000000 --- a/app/assets/javascripts/behaviors/gl_emoji/unicode_support_map.js +++ /dev/null @@ -1,172 +0,0 @@ -import AccessorUtilities from '../../lib/utils/accessor'; - -const unicodeSupportTestMap = { - // man, student (emojione does not have any of these yet), http://emojipedia.org/emoji-zwj-sequences/ - // occupationZwj: '\u{1F468}\u{200D}\u{1F393}', - // woman, biking (emojione does not have any of these yet), http://emojipedia.org/emoji-zwj-sequences/ - // sexZwj: '\u{1F6B4}\u{200D}\u{2640}', - // family_mwgb - // Windows 8.1, Firefox 51.0.1 does not support `family_`, `kiss_`, `couple_` - personZwj: '\u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F467}\u{200D}\u{1F466}', - // horse_racing_tone5 - // Special case that is not supported on macOS 10.12 even though `skinToneModifier` succeeds - horseRacing: '\u{1F3C7}\u{1F3FF}', - // US flag, http://emojipedia.org/flags/ - flag: '\u{1F1FA}\u{1F1F8}', - // http://emojipedia.org/modifiers/ - skinToneModifier: [ - // spy_tone5 - '\u{1F575}\u{1F3FF}', - // person_with_ball_tone5 - '\u{26F9}\u{1F3FF}', - // angel_tone5 - '\u{1F47C}\u{1F3FF}', - ], - // rofl, http://emojipedia.org/unicode-9.0/ - '9.0': '\u{1F923}', - // metal, http://emojipedia.org/unicode-8.0/ - '8.0': '\u{1F918}', - // spy, http://emojipedia.org/unicode-7.0/ - '7.0': '\u{1F575}', - // expressionless, http://emojipedia.org/unicode-6.1/ - 6.1: '\u{1F611}', - // japanese_goblin, http://emojipedia.org/unicode-6.0/ - '6.0': '\u{1F47A}', - // sailboat, http://emojipedia.org/unicode-5.2/ - 5.2: '\u{26F5}', - // mahjong, http://emojipedia.org/unicode-5.1/ - 5.1: '\u{1F004}', - // gear, http://emojipedia.org/unicode-4.1/ - 4.1: '\u{2699}', - // zap, http://emojipedia.org/unicode-4.0/ - '4.0': '\u{26A1}', - // recycle, http://emojipedia.org/unicode-3.2/ - 3.2: '\u{267B}', - // information_source, http://emojipedia.org/unicode-3.0/ - '3.0': '\u{2139}', - // heart, http://emojipedia.org/unicode-1.1/ - 1.1: '\u{2764}', -}; - -function checkPixelInImageDataArray(pixelOffset, imageDataArray) { - // `4 *` because RGBA - const indexOffset = 4 * pixelOffset; - const hasColor = imageDataArray[indexOffset + 0] || - imageDataArray[indexOffset + 1] || - imageDataArray[indexOffset + 2]; - const isVisible = imageDataArray[indexOffset + 3]; - // Check for some sort of color other than black - if (hasColor && isVisible) { - return true; - } - return false; -} - -const chromeMatches = navigator.userAgent.match(/Chrom(?:e|ium)\/([0-9]+)\./); -const isChrome = chromeMatches && chromeMatches.length > 0; -const chromeVersion = chromeMatches && chromeMatches[1] && parseInt(chromeMatches[1], 10); - -// We use 16px because mobile Safari (iOS 9.3) doesn't properly scale emojis :/ -// See 32px, https://i.imgur.com/htY6Zym.png -// See 16px, https://i.imgur.com/FPPsIF8.png -const fontSize = 16; -function generateUnicodeSupportMap(testMap) { - const testMapKeys = Object.keys(testMap); - const numTestEntries = testMapKeys - .reduce((list, testKey) => list.concat(testMap[testKey]), []).length; - - const canvas = document.createElement('canvas'); - (window.gl || window).testEmojiUnicodeSupportMapCanvas = canvas; - const ctx = canvas.getContext('2d'); - canvas.width = (2 * fontSize); - canvas.height = (numTestEntries * fontSize); - ctx.fillStyle = '#000000'; - ctx.textBaseline = 'middle'; - ctx.font = `${fontSize}px "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"`; - // Write each emoji to the canvas vertically - let writeIndex = 0; - testMapKeys.forEach((testKey) => { - const testEntry = testMap[testKey]; - [].concat(testEntry).forEach((emojiUnicode) => { - ctx.fillText(emojiUnicode, 0, (writeIndex * fontSize) + (fontSize / 2)); - writeIndex += 1; - }); - }); - - // Read from the canvas - const resultMap = {}; - let readIndex = 0; - testMapKeys.forEach((testKey) => { - const testEntry = testMap[testKey]; - // This needs to be a `reduce` instead of `every` because we need to - // keep the `readIndex` in sync from the writes by running all entries - const isTestSatisfied = [].concat(testEntry).reduce((isSatisfied) => { - // Sample along the vertical-middle for a couple of characters - const imageData = ctx.getImageData( - 0, - (readIndex * fontSize) + (fontSize / 2), - 2 * fontSize, - 1, - ).data; - - let isValidEmoji = false; - for (let currentPixel = 0; currentPixel < 64; currentPixel += 1) { - const isLookingAtFirstChar = currentPixel < fontSize; - const isLookingAtSecondChar = currentPixel >= (fontSize + (fontSize / 2)); - // Check for the emoji somewhere along the row - if (isLookingAtFirstChar && checkPixelInImageDataArray(currentPixel, imageData)) { - isValidEmoji = true; - - // Check to see that nothing is rendered next to the first character - // to ensure that the ZWJ sequence rendered as one piece - } else if (isLookingAtSecondChar && checkPixelInImageDataArray(currentPixel, imageData)) { - isValidEmoji = false; - break; - } - } - - readIndex += 1; - return isSatisfied && isValidEmoji; - }, true); - - resultMap[testKey] = isTestSatisfied; - }); - - resultMap.meta = { - isChrome, - chromeVersion, - }; - - return resultMap; -} - -function getUnicodeSupportMap() { - let unicodeSupportMap; - let userAgentFromCache; - - const isLocalStorageAvailable = AccessorUtilities.isLocalStorageAccessSafe(); - - if (isLocalStorageAvailable) userAgentFromCache = window.localStorage.getItem('gl-emoji-user-agent'); - - try { - unicodeSupportMap = JSON.parse(window.localStorage.getItem('gl-emoji-unicode-support-map')); - } catch (err) { - // swallow - } - - if (!unicodeSupportMap || userAgentFromCache !== navigator.userAgent) { - unicodeSupportMap = generateUnicodeSupportMap(unicodeSupportTestMap); - - if (isLocalStorageAvailable) { - window.localStorage.setItem('gl-emoji-user-agent', navigator.userAgent); - window.localStorage.setItem('gl-emoji-unicode-support-map', JSON.stringify(unicodeSupportMap)); - } - } - - return unicodeSupportMap; -} - -export { - getUnicodeSupportMap, - generateUnicodeSupportMap, -}; -- cgit v1.2.3 From 527e7edbc4251644867ef02a2e055815d3f28a82 Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Mon, 26 Jun 2017 17:05:43 -0500 Subject: replace emojiAlias references with normalizeEmojiName helper --- app/assets/javascripts/behaviors/gl_emoji.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) (limited to 'app/assets/javascripts/behaviors') diff --git a/app/assets/javascripts/behaviors/gl_emoji.js b/app/assets/javascripts/behaviors/gl_emoji.js index 06eb698378c..3a29254cf99 100644 --- a/app/assets/javascripts/behaviors/gl_emoji.js +++ b/app/assets/javascripts/behaviors/gl_emoji.js @@ -1,5 +1,5 @@ import installCustomElements from 'document-register-element'; -import { emojiMap, emojiAliases, isEmojiUnicodeSupported, getUnicodeSupportMap } from '../emoji'; +import { emojiMap, normalizeEmojiName, isEmojiUnicodeSupported, getUnicodeSupportMap } from '../emoji'; installCustomElements(window); @@ -10,8 +10,7 @@ function emojiImageTag(name, src) { } function assembleFallbackImageSrc(inputName) { - let name = Object.prototype.hasOwnProperty.call(emojiAliases, inputName) ? - emojiAliases[inputName] : inputName; + let name = normalizeEmojiName(inputName); let emojiInfo = emojiMap[name]; // Fallback to question mark for unknown emojis if (!emojiInfo) { @@ -25,8 +24,7 @@ function assembleFallbackImageSrc(inputName) { function glEmojiTag(inputName, options) { const opts = { sprite: false, forceFallback: false, ...options }; - let name = Object.prototype.hasOwnProperty.call(emojiAliases, inputName) ? - emojiAliases[inputName] : inputName; + let name = normalizeEmojiName(inputName); let emojiInfo = emojiMap[name]; // Fallback to question mark for unknown emojis if (!emojiInfo) { -- cgit v1.2.3 From 27a1348f20dee09758e053b465a2d2ff37ffb649 Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Tue, 27 Jun 2017 00:10:17 -0500 Subject: split emoji support methods from the async module --- app/assets/javascripts/behaviors/gl_emoji.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) (limited to 'app/assets/javascripts/behaviors') diff --git a/app/assets/javascripts/behaviors/gl_emoji.js b/app/assets/javascripts/behaviors/gl_emoji.js index 3a29254cf99..17422f5cece 100644 --- a/app/assets/javascripts/behaviors/gl_emoji.js +++ b/app/assets/javascripts/behaviors/gl_emoji.js @@ -1,10 +1,9 @@ import installCustomElements from 'document-register-element'; -import { emojiMap, normalizeEmojiName, isEmojiUnicodeSupported, getUnicodeSupportMap } from '../emoji'; +import { emojiMap, normalizeEmojiName } from '../emoji'; +import isEmojiUnicodeSupported from '../emoji/support'; installCustomElements(window); -const generatedUnicodeSupportMap = getUnicodeSupportMap(); - function emojiImageTag(name, src) { return `:${name}:`; } @@ -82,7 +81,7 @@ function installGlEmojiElement() { if ( emojiUnicode && isEmojiUnicode && - !isEmojiUnicodeSupported(generatedUnicodeSupportMap, emojiUnicode, unicodeVersion) + !isEmojiUnicodeSupported(emojiUnicode, unicodeVersion) ) { // CSS sprite fallback takes precedence over image fallback if (hasCssSpriteFalback) { @@ -106,5 +105,4 @@ function installGlEmojiElement() { export { installGlEmojiElement, glEmojiTag, - emojiImageTag, }; -- cgit v1.2.3 From 9d6bbc92c978beeec70bec1f2d4a65791fa3f985 Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Tue, 27 Jun 2017 00:54:34 -0500 Subject: move glEmojiTag method to emoji helper --- app/assets/javascripts/behaviors/gl_emoji.js | 67 ++-------------------------- app/assets/javascripts/behaviors/index.js | 2 +- 2 files changed, 4 insertions(+), 65 deletions(-) (limited to 'app/assets/javascripts/behaviors') diff --git a/app/assets/javascripts/behaviors/gl_emoji.js b/app/assets/javascripts/behaviors/gl_emoji.js index 17422f5cece..8156e491a42 100644 --- a/app/assets/javascripts/behaviors/gl_emoji.js +++ b/app/assets/javascripts/behaviors/gl_emoji.js @@ -1,66 +1,10 @@ import installCustomElements from 'document-register-element'; -import { emojiMap, normalizeEmojiName } from '../emoji'; +import { emojiImageTag, emojiFallbackImageSrc } from '../emoji'; import isEmojiUnicodeSupported from '../emoji/support'; installCustomElements(window); -function emojiImageTag(name, src) { - return `:${name}:`; -} - -function assembleFallbackImageSrc(inputName) { - let name = normalizeEmojiName(inputName); - let emojiInfo = emojiMap[name]; - // Fallback to question mark for unknown emojis - if (!emojiInfo) { - name = 'grey_question'; - emojiInfo = emojiMap[name]; - } - const fallbackImageSrc = `${gon.asset_host || ''}${gon.relative_url_root || ''}/assets/emoji/${name}-${emojiInfo.digest}.png`; - - return fallbackImageSrc; -} - -function glEmojiTag(inputName, options) { - const opts = { sprite: false, forceFallback: false, ...options }; - let name = normalizeEmojiName(inputName); - let emojiInfo = emojiMap[name]; - // Fallback to question mark for unknown emojis - if (!emojiInfo) { - name = 'grey_question'; - emojiInfo = emojiMap[name]; - } - - const fallbackImageSrc = assembleFallbackImageSrc(name); - const fallbackSpriteClass = `emoji-${name}`; - - const classList = []; - if (opts.forceFallback && opts.sprite) { - classList.push('emoji-icon'); - classList.push(fallbackSpriteClass); - } - const classAttribute = classList.length > 0 ? `class="${classList.join(' ')}"` : ''; - const fallbackSpriteAttribute = opts.sprite ? `data-fallback-sprite-class="${fallbackSpriteClass}"` : ''; - let contents = emojiInfo.moji; - if (opts.forceFallback && !opts.sprite) { - contents = emojiImageTag(name, fallbackImageSrc); - } - - return ` - - ${contents} - - `; -} - -function installGlEmojiElement() { +export default function installGlEmojiElement() { const GlEmojiElementProto = Object.create(HTMLElement.prototype); GlEmojiElementProto.createdCallback = function createdCallback() { const emojiUnicode = this.textContent.trim(); @@ -91,7 +35,7 @@ function installGlEmojiElement() { } else if (hasImageFallback) { this.innerHTML = emojiImageTag(name, fallbackSrc); } else { - const src = assembleFallbackImageSrc(name); + const src = emojiFallbackImageSrc(name); this.innerHTML = emojiImageTag(name, src); } } @@ -101,8 +45,3 @@ function installGlEmojiElement() { prototype: GlEmojiElementProto, }); } - -export { - installGlEmojiElement, - glEmojiTag, -}; diff --git a/app/assets/javascripts/behaviors/index.js b/app/assets/javascripts/behaviors/index.js index 5b931e6cfa6..44b2c974b9e 100644 --- a/app/assets/javascripts/behaviors/index.js +++ b/app/assets/javascripts/behaviors/index.js @@ -1,7 +1,7 @@ import './autosize'; import './bind_in_out'; import './details_behavior'; -import { installGlEmojiElement } from './gl_emoji'; +import installGlEmojiElement from './gl_emoji'; import './quick_submit'; import './requires_input'; import './toggler_behavior'; -- cgit v1.2.3