diff options
author | Tim Zallmann <tzallmann@gitlab.com> | 2017-06-28 05:51:30 +0000 |
---|---|---|
committer | Tim Zallmann <tzallmann@gitlab.com> | 2017-06-28 05:51:30 +0000 |
commit | 08ad0af49c017d740b43588c0809b3811d25a448 (patch) | |
tree | b6dbccf4e0c62bd4a8bc79e51934f008122cbe17 | |
parent | a7f114b1368fd11dba036f67b2ef66c2bf39f02a (diff) | |
parent | 88e12ac94b13bc4ee57a60adca64af5cf7d14030 (diff) | |
download | gitlab-ce-08ad0af49c017d740b43588c0809b3811d25a448.tar.gz |
Merge branch 'refactor-emoji-utils' into 'master'
Refactor emoji helpers in preparation for async loading
See merge request !12432
-rw-r--r-- | app/assets/javascripts/awards_handler.js | 85 | ||||
-rw-r--r-- | app/assets/javascripts/behaviors/gl_emoji.js | 81 | ||||
-rw-r--r-- | app/assets/javascripts/behaviors/gl_emoji/is_emoji_name_valid.js | 11 | ||||
-rw-r--r-- | app/assets/javascripts/behaviors/index.js | 2 | ||||
-rw-r--r-- | app/assets/javascripts/emoji/index.js | 99 | ||||
-rw-r--r-- | app/assets/javascripts/emoji/support/index.js | 10 | ||||
-rw-r--r-- | app/assets/javascripts/emoji/support/is_emoji_unicode_supported.js (renamed from app/assets/javascripts/behaviors/gl_emoji/is_emoji_unicode_supported.js) | 2 | ||||
-rw-r--r-- | app/assets/javascripts/emoji/support/unicode_support_map.js (renamed from app/assets/javascripts/behaviors/gl_emoji/unicode_support_map.js) | 7 | ||||
-rw-r--r-- | app/assets/javascripts/gfm_auto_complete.js | 10 | ||||
-rw-r--r-- | spec/javascripts/behaviors/gl_emoji/unicode_support_map_spec.js | 2 | ||||
-rw-r--r-- | spec/javascripts/emoji_spec.js (renamed from spec/javascripts/gl_emoji_spec.js) | 7 |
11 files changed, 141 insertions, 175 deletions
diff --git a/app/assets/javascripts/awards_handler.js b/app/assets/javascripts/awards_handler.js index ebe722061d7..c34d80f0601 100644 --- a/app/assets/javascripts/awards_handler.js +++ b/app/assets/javascripts/awards_handler.js @@ -2,11 +2,7 @@ /* global Flash */ import Cookies from 'js-cookie'; - -import emojiMap from 'emojis/digests.json'; -import emojiAliases from 'emojis/aliases.json'; -import { glEmojiTag } from './behaviors/gl_emoji'; -import isEmojiNameValid from './behaviors/gl_emoji/is_emoji_name_valid'; +import * as Emoji from './emoji'; const animationEndEventString = 'animationend webkitAnimationEnd MSAnimationEnd oAnimationEnd'; const transitionEndEventString = 'transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd'; @@ -17,8 +13,6 @@ const requestAnimationFrame = window.requestAnimationFrame || const FROM_SENTENCE_REGEX = /(?:, and | and |, )/; // For separating lists produced by ruby's Array#toSentence -let categoryMap = null; - const categoryLabelMap = { activity: 'Activity', people: 'People', @@ -30,26 +24,6 @@ const categoryLabelMap = { flags: 'Flags', }; -function buildCategoryMap() { - return Object.keys(emojiMap).reduce((currentCategoryMap, emojiNameKey) => { - const emojiInfo = emojiMap[emojiNameKey]; - if (currentCategoryMap[emojiInfo.category]) { - currentCategoryMap[emojiInfo.category].push(emojiNameKey); - } - - return currentCategoryMap; - }, { - activity: [], - people: [], - nature: [], - food: [], - travel: [], - objects: [], - symbols: [], - flags: [], - }); -} - function renderCategory(name, emojiList, opts = {}) { return ` <h5 class="emoji-menu-title"> @@ -59,7 +33,7 @@ function renderCategory(name, emojiList, opts = {}) { ${emojiList.map(emojiName => ` <li class="emoji-menu-list-item"> <button class="emoji-menu-btn text-center js-emoji-btn" type="button"> - ${glEmojiTag(emojiName, { + ${Emoji.glEmojiTag(emojiName, { sprite: true, })} </button> @@ -72,7 +46,6 @@ function renderCategory(name, emojiList, opts = {}) { export default class AwardsHandler { constructor() { this.eventListeners = []; - this.aliases = emojiAliases; // If the user shows intent let's pre-build the menu this.registerEventListener('one', $(document), 'mouseenter focus', '.js-add-award', 'mouseenter focus', () => { const $menu = $('.emoji-menu'); @@ -81,8 +54,6 @@ export default class AwardsHandler { this.createEmojiMenu(); }); } - // Prebuild the categoryMap - categoryMap = categoryMap || buildCategoryMap(); }); this.registerEventListener('on', $(document), 'click', '.js-add-award', (e) => { e.stopPropagation(); @@ -168,7 +139,7 @@ export default class AwardsHandler { this.isCreatingEmojiMenu = true; // Render the first category - categoryMap = categoryMap || buildCategoryMap(); + const categoryMap = Emoji.getEmojiCategoryMap(); const categoryNameKey = Object.keys(categoryMap)[0]; const emojisInCategory = categoryMap[categoryNameKey]; const firstCategory = renderCategory(categoryLabelMap[categoryNameKey], emojisInCategory); @@ -208,7 +179,7 @@ export default class AwardsHandler { } this.isAddingRemainingEmojiMenuCategories = true; - categoryMap = categoryMap || buildCategoryMap(); + const categoryMap = Emoji.getEmojiCategoryMap(); // Avoid the jank and render the remaining categories separately // This will take more time, but makes UI more responsive @@ -262,14 +233,8 @@ export default class AwardsHandler { return $menu.css(css); } - addAward( - votesBlock, - awardUrl, - emoji, - checkMutuality, - callback, - ) { - const normalizedEmoji = this.normalizeEmojiName(emoji); + addAward(votesBlock, awardUrl, emoji, checkMutuality, callback) { + const normalizedEmoji = Emoji.normalizeEmojiName(emoji); const $emojiButton = this.findEmojiIcon(votesBlock, normalizedEmoji).parent(); this.postEmoji($emojiButton, awardUrl, normalizedEmoji, () => { this.addAwardToEmojiBar(votesBlock, normalizedEmoji, checkMutuality); @@ -279,16 +244,12 @@ export default class AwardsHandler { $('.js-add-award.is-active').removeClass('is-active'); } - addAwardToEmojiBar( - votesBlock, - emoji, - checkForMutuality, - ) { + addAwardToEmojiBar(votesBlock, emoji, checkForMutuality) { if (checkForMutuality || checkForMutuality === null) { this.checkMutuality(votesBlock, emoji); } this.addEmojiToFrequentlyUsedList(emoji); - const normalizedEmoji = this.normalizeEmojiName(emoji); + const normalizedEmoji = Emoji.normalizeEmojiName(emoji); const $emojiButton = this.findEmojiIcon(votesBlock, normalizedEmoji).parent(); if ($emojiButton.length > 0) { if (this.isActive($emojiButton)) { @@ -413,7 +374,7 @@ export default class AwardsHandler { createAwardButtonForVotesBlock(votesBlock, emojiName) { const buttonHtml = ` <button class="btn award-control js-emoji-btn has-tooltip active" title="You" data-placement="bottom"> - ${glEmojiTag(emojiName)} + ${Emoji.glEmojiTag(emojiName)} <span class="award-control-text js-counter">1</span> </button> `; @@ -478,12 +439,8 @@ export default class AwardsHandler { return $('body, html').animate(options, 200); } - normalizeEmojiName(emoji) { - return Object.prototype.hasOwnProperty.call(this.aliases, emoji) ? this.aliases[emoji] : emoji; - } - addEmojiToFrequentlyUsedList(emoji) { - if (isEmojiNameValid(emoji)) { + if (Emoji.isEmojiNameValid(emoji)) { this.frequentlyUsedEmojis = _.uniq(this.getFrequentlyUsedEmojis().concat(emoji)); Cookies.set('frequently_used_emojis', this.frequentlyUsedEmojis.join(','), { expires: 365 }); } @@ -493,7 +450,7 @@ export default class AwardsHandler { return this.frequentlyUsedEmojis || (() => { const frequentlyUsedEmojis = _.uniq((Cookies.get('frequently_used_emojis') || '').split(',')); this.frequentlyUsedEmojis = frequentlyUsedEmojis.filter( - inputName => isEmojiNameValid(inputName), + inputName => Emoji.isEmojiNameValid(inputName), ); return this.frequentlyUsedEmojis; @@ -535,21 +492,11 @@ export default class AwardsHandler { } } - findMatchingEmojiElements(term) { - const safeTerm = term.toLowerCase(); - - const namesMatchingAlias = []; - Object.keys(emojiAliases).forEach((alias) => { - if (alias.indexOf(safeTerm) >= 0) { - namesMatchingAlias.push(emojiAliases[alias]); - } - }); - const $matchingElements = namesMatchingAlias.concat(safeTerm) - .reduce( - ($result, searchTerm) => - $result.add($(`.emoji-menu-list:not(.frequent-emojis) [data-name*="${searchTerm}"]`)), - $([]), - ); + findMatchingEmojiElements(query) { + const emojiMatches = Emoji.filterEmojiNamesByAlias(query); + const $emojiElements = $('.emoji-menu-list:not(.frequent-emojis) [data-name]'); + const $matchingElements = $emojiElements + .filter((i, elm) => emojiMatches.indexOf(elm.dataset.name) >= 0); return $matchingElements.closest('li').clone(); } 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/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'; diff --git a/app/assets/javascripts/emoji/index.js b/app/assets/javascripts/emoji/index.js new file mode 100644 index 00000000000..cac35d6eed5 --- /dev/null +++ b/app/assets/javascripts/emoji/index.js @@ -0,0 +1,99 @@ +import emojiMap from 'emojis/digests.json'; +import emojiAliases from 'emojis/aliases.json'; + +export const validEmojiNames = [...Object.keys(emojiMap), ...Object.keys(emojiAliases)]; + +export function normalizeEmojiName(name) { + return Object.prototype.hasOwnProperty.call(emojiAliases, name) ? emojiAliases[name] : name; +} + +export function isEmojiNameValid(name) { + return validEmojiNames.indexOf(name) >= 0; +} + +export function filterEmojiNames(filter) { + const match = filter.toLowerCase(); + return validEmojiNames.filter(name => name.indexOf(match) >= 0); +} + +export function filterEmojiNamesByAlias(filter) { + return _.uniq(filterEmojiNames(filter).map(name => normalizeEmojiName(name))); +} + +let emojiCategoryMap; +export function getEmojiCategoryMap() { + if (!emojiCategoryMap) { + emojiCategoryMap = { + activity: [], + people: [], + nature: [], + food: [], + travel: [], + objects: [], + symbols: [], + flags: [], + }; + Object.keys(emojiMap).forEach((name) => { + const emoji = emojiMap[name]; + if (emojiCategoryMap[emoji.category]) { + emojiCategoryMap[emoji.category].push(name); + } + }); + } + return emojiCategoryMap; +} + +export function getEmojiInfo(query) { + let name = normalizeEmojiName(query); + let emojiInfo = emojiMap[name]; + + // Fallback to question mark for unknown emojis + if (!emojiInfo) { + name = 'grey_question'; + emojiInfo = emojiMap[name]; + } + + return { ...emojiInfo, name }; +} + +export function emojiFallbackImageSrc(inputName) { + const { name, digest } = getEmojiInfo(inputName); + return `${gon.asset_host || ''}${gon.relative_url_root || ''}/assets/emoji/${name}-${digest}.png`; +} + +export function emojiImageTag(name, src) { + return `<img class="emoji" title=":${name}:" alt=":${name}:" src="${src}" width="20" height="20" align="absmiddle" />`; +} + +export function glEmojiTag(inputName, options) { + const opts = { sprite: false, forceFallback: false, ...options }; + const { name, ...emojiInfo } = getEmojiInfo(inputName); + + const fallbackImageSrc = emojiFallbackImageSrc(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> + `; +} diff --git a/app/assets/javascripts/emoji/support/index.js b/app/assets/javascripts/emoji/support/index.js new file mode 100644 index 00000000000..1f7852dd487 --- /dev/null +++ b/app/assets/javascripts/emoji/support/index.js @@ -0,0 +1,10 @@ +import isEmojiUnicodeSupported from './is_emoji_unicode_supported'; +import getUnicodeSupportMap from './unicode_support_map'; + +// cache browser support map between calls +let browserUnicodeSupportMap; + +export default function isEmojiUnicodeSupportedByBrowser(emojiUnicode, unicodeVersion) { + browserUnicodeSupportMap = browserUnicodeSupportMap || getUnicodeSupportMap(); + return isEmojiUnicodeSupported(browserUnicodeSupportMap, emojiUnicode, unicodeVersion); +} diff --git a/app/assets/javascripts/behaviors/gl_emoji/is_emoji_unicode_supported.js b/app/assets/javascripts/emoji/support/is_emoji_unicode_supported.js index 4f8884d05ac..3fd23efa9f8 100644 --- a/app/assets/javascripts/behaviors/gl_emoji/is_emoji_unicode_supported.js +++ b/app/assets/javascripts/emoji/support/is_emoji_unicode_supported.js @@ -111,7 +111,7 @@ function isEmojiUnicodeSupported(unicodeSupportMap = {}, emojiUnicode, unicodeVe } export { - isEmojiUnicodeSupported, + isEmojiUnicodeSupported as default, isFlagEmoji, isKeycapEmoji, isSkinToneComboEmoji, diff --git a/app/assets/javascripts/behaviors/gl_emoji/unicode_support_map.js b/app/assets/javascripts/emoji/support/unicode_support_map.js index 257df55e54f..755381c2f95 100644 --- a/app/assets/javascripts/behaviors/gl_emoji/unicode_support_map.js +++ b/app/assets/javascripts/emoji/support/unicode_support_map.js @@ -140,7 +140,7 @@ function generateUnicodeSupportMap(testMap) { return resultMap; } -function getUnicodeSupportMap() { +export default function getUnicodeSupportMap() { let unicodeSupportMap; let userAgentFromCache; @@ -165,8 +165,3 @@ function getUnicodeSupportMap() { return unicodeSupportMap; } - -export { - getUnicodeSupportMap, - generateUnicodeSupportMap, -}; diff --git a/app/assets/javascripts/gfm_auto_complete.js b/app/assets/javascripts/gfm_auto_complete.js index 105762cb1ba..f99bac7da1a 100644 --- a/app/assets/javascripts/gfm_auto_complete.js +++ b/app/assets/javascripts/gfm_auto_complete.js @@ -1,8 +1,6 @@ -import emojiMap from 'emojis/digests.json'; -import emojiAliases from 'emojis/aliases.json'; -import { glEmojiTag } from '~/behaviors/gl_emoji'; -import glRegexp from '~/lib/utils/regexp'; -import AjaxCache from '~/lib/utils/ajax_cache'; +import { validEmojiNames, glEmojiTag } from './emoji'; +import glRegexp from './lib/utils/regexp'; +import AjaxCache from './lib/utils/ajax_cache'; function sanitize(str) { return str.replace(/<(?:.|\n)*?>/gm, ''); @@ -375,7 +373,7 @@ class GfmAutoComplete { if (this.cachedData[at]) { this.loadData($input, at, this.cachedData[at]); } else if (GfmAutoComplete.atTypeMap[at] === 'emojis') { - this.loadData($input, at, Object.keys(emojiMap).concat(Object.keys(emojiAliases))); + this.loadData($input, at, validEmojiNames); } else { AjaxCache.retrieve(this.dataSources[GfmAutoComplete.atTypeMap[at]], true) .then((data) => { diff --git a/spec/javascripts/behaviors/gl_emoji/unicode_support_map_spec.js b/spec/javascripts/behaviors/gl_emoji/unicode_support_map_spec.js index 1ed96a67478..ec2c549e032 100644 --- a/spec/javascripts/behaviors/gl_emoji/unicode_support_map_spec.js +++ b/spec/javascripts/behaviors/gl_emoji/unicode_support_map_spec.js @@ -1,4 +1,4 @@ -import { getUnicodeSupportMap } from '~/behaviors/gl_emoji/unicode_support_map'; +import getUnicodeSupportMap from '~/emoji/support/unicode_support_map'; import AccessorUtilities from '~/lib/utils/accessor'; describe('Unicode Support Map', () => { diff --git a/spec/javascripts/gl_emoji_spec.js b/spec/javascripts/emoji_spec.js index a09e0072fa8..fa11c602ec3 100644 --- a/spec/javascripts/gl_emoji_spec.js +++ b/spec/javascripts/emoji_spec.js @@ -1,12 +1,11 @@ -import { glEmojiTag } from '~/behaviors/gl_emoji'; -import { - isEmojiUnicodeSupported, +import { glEmojiTag } from '~/emoji'; +import isEmojiUnicodeSupported, { isFlagEmoji, isKeycapEmoji, isSkinToneComboEmoji, isHorceRacingSkinToneComboEmoji, isPersonZwjEmoji, -} from '~/behaviors/gl_emoji/is_emoji_unicode_supported'; +} from '~/emoji/support/is_emoji_unicode_supported'; const emptySupportMap = { personZwj: false, |