summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/behaviors
diff options
context:
space:
mode:
authorTim Zallmann <tzallmann@gitlab.com>2017-06-28 05:51:30 +0000
committerTim Zallmann <tzallmann@gitlab.com>2017-06-28 05:51:30 +0000
commit08ad0af49c017d740b43588c0809b3811d25a448 (patch)
treeb6dbccf4e0c62bd4a8bc79e51934f008122cbe17 /app/assets/javascripts/behaviors
parenta7f114b1368fd11dba036f67b2ef66c2bf39f02a (diff)
parent88e12ac94b13bc4ee57a60adca64af5cf7d14030 (diff)
downloadgitlab-ce-08ad0af49c017d740b43588c0809b3811d25a448.tar.gz
Merge branch 'refactor-emoji-utils' into 'master'
Refactor emoji helpers in preparation for async loading See merge request !12432
Diffstat (limited to 'app/assets/javascripts/behaviors')
-rw-r--r--app/assets/javascripts/behaviors/gl_emoji.js81
-rw-r--r--app/assets/javascripts/behaviors/gl_emoji/is_emoji_name_valid.js11
-rw-r--r--app/assets/javascripts/behaviors/gl_emoji/is_emoji_unicode_supported.js120
-rw-r--r--app/assets/javascripts/behaviors/gl_emoji/unicode_support_map.js172
-rw-r--r--app/assets/javascripts/behaviors/index.js2
5 files changed, 6 insertions, 380 deletions
diff --git a/app/assets/javascripts/behaviors/gl_emoji.js b/app/assets/javascripts/behaviors/gl_emoji.js
index 36ce4fddb72..8156e491a42 100644
--- a/app/assets/javascripts/behaviors/gl_emoji.js
+++ b/app/assets/javascripts/behaviors/gl_emoji.js
@@ -1,75 +1,10 @@
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 { emojiImageTag, emojiFallbackImageSrc } from '../emoji';
+import isEmojiUnicodeSupported from '../emoji/support';
installCustomElements(window);
-const generatedUnicodeSupportMap = getUnicodeSupportMap();
-
-function emojiImageTag(name, src) {
- return `<img class="emoji" title=":${name}:" alt=":${name}:" src="${src}" width="20" height="20" align="absmiddle" />`;
-}
-
-function assembleFallbackImageSrc(inputName) {
- let name = Object.prototype.hasOwnProperty.call(emojiAliases, inputName) ?
- emojiAliases[inputName] : 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;
-}
-const glEmojiTagDefaults = {
- sprite: false,
- forceFallback: false,
-};
-function glEmojiTag(inputName, options) {
- const opts = Object.assign({}, glEmojiTagDefaults, options);
- let name = Object.prototype.hasOwnProperty.call(emojiAliases, inputName) ?
- emojiAliases[inputName] : 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 `
- <gl-emoji
- ${classAttribute}
- data-name="${name}"
- data-fallback-src="${fallbackImageSrc}"
- ${fallbackSpriteAttribute}
- data-unicode-version="${emojiInfo.unicodeVersion}"
- title="${emojiInfo.description}"
- >
- ${contents}
- </gl-emoji>
- `;
-}
-
-function installGlEmojiElement() {
+export default function installGlEmojiElement() {
const GlEmojiElementProto = Object.create(HTMLElement.prototype);
GlEmojiElementProto.createdCallback = function createdCallback() {
const emojiUnicode = this.textContent.trim();
@@ -90,7 +25,7 @@ function installGlEmojiElement() {
if (
emojiUnicode &&
isEmojiUnicode &&
- !isEmojiUnicodeSupported(generatedUnicodeSupportMap, emojiUnicode, unicodeVersion)
+ !isEmojiUnicodeSupported(emojiUnicode, unicodeVersion)
) {
// CSS sprite fallback takes precedence over image fallback
if (hasCssSpriteFalback) {
@@ -100,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);
}
}
@@ -110,9 +45,3 @@ function installGlEmojiElement() {
prototype: GlEmojiElementProto,
});
}
-
-export {
- installGlEmojiElement,
- glEmojiTag,
- emojiImageTag,
-};
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,
-};
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';