diff --git a/app/assets/images/emoji.png b/app/assets/images/emoji.png index 6f1a34a559127963b82b87cf7bc00fa634545a78..5dcd9c09b70f8e6cefc41e9d1b8ce2773fc954b6 100644 Binary files a/app/assets/images/emoji.png and b/app/assets/images/emoji.png differ diff --git a/app/assets/images/emoji@2x.png b/app/assets/images/emoji@2x.png index dc9cae1d44cf0282dab99202430b4ae0c710d37a..b0fa9e1139eac2e52be6d31e25b16d2bb61ee640 100644 Binary files a/app/assets/images/emoji@2x.png and b/app/assets/images/emoji@2x.png differ diff --git a/app/assets/javascripts/awards_handler.js b/app/assets/javascripts/awards_handler.js index a4ccb30e447aa220faae5529ef0e1b13cada0974..38732abaa86f51797384b1dac5cc96528c54d7dd 100644 --- a/app/assets/javascripts/awards_handler.js +++ b/app/assets/javascripts/awards_handler.js @@ -1,380 +1,519 @@ -/* eslint-disable func-names, space-before-function-paren, wrap-iife, max-len, no-var, prefer-arrow-callback, consistent-return, one-var, one-var-declaration-per-line, no-unused-vars, no-else-return, prefer-template, quotes, comma-dangle, no-param-reassign, no-void, brace-style, no-underscore-dangle, no-return-assign, camelcase */ /* global Cookies */ -var emojiAliases = require('emoji-aliases'); - -(function() { - this.AwardsHandler = (function() { - var FROM_SENTENCE_REGEX = /(?:, and | and |, )/; // For separating lists produced by ruby's Array#toSentence - function AwardsHandler() { - this.aliases = emojiAliases; - $(document).off('click', '.js-add-award').on('click', '.js-add-award', (function(_this) { - return function(e) { - e.stopPropagation(); - e.preventDefault(); - return _this.showEmojiMenu($(e.currentTarget)); - }; - })(this)); - $('html').on('click', function(e) { - var $target; - $target = $(e.target); - if (!$target.closest('.emoji-menu-content').length) { - $('.js-awards-block.current').removeClass('current'); - } - if (!$target.closest('.emoji-menu').length) { - if ($('.emoji-menu').is(':visible')) { - $('.js-add-award.is-active').removeClass('is-active'); - return $('.emoji-menu').removeClass('is-visible'); - } - } - }); - $(document).off('click', '.js-emoji-btn').on('click', '.js-emoji-btn', (function(_this) { - return function(e) { - var $target, emoji; - e.preventDefault(); - $target = $(e.currentTarget); - emoji = $target.find('.icon').data('emoji'); - $target.closest('.js-awards-block').addClass('current'); - return _this.addAward(_this.getVotesBlock(), _this.getAwardUrl(), emoji); - }; - })(this)); +const emojiMap = require('emoji-map'); +const emojiAliases = require('emoji-aliases'); +const glEmoji = require('./behaviors/gl_emoji'); + +const glEmojiTag = glEmoji.glEmojiTag; + +const animationEndEventString = 'animationend webkitAnimationEnd MSAnimationEnd oAnimationEnd'; +const requestAnimationFrame = window.requestAnimationFrame || + window.webkitRequestAnimationFrame || + window.mozRequestAnimationFrame || + window.setTimeout; + +const FROM_SENTENCE_REGEX = /(?:, and | and |, )/; // For separating lists produced by ruby's Array#toSentence + +let categoryMap = null; + +const categoryLabelMap = { + activity: 'Activity', + people: 'People', + nature: 'Nature', + food: 'Food', + travel: 'Travel', + objects: 'Objects', + symbols: 'Symbols', + flags: 'Flags', +}; + +function buildCategoryMap() { + return Object.keys(emojiMap).reduce((currentCategoryMap, emojiNameKey) => { + const emojiInfo = emojiMap[emojiNameKey]; + if (currentCategoryMap[emojiInfo.category]) { + currentCategoryMap[emojiInfo.category].push(emojiNameKey); } - AwardsHandler.prototype.showEmojiMenu = function($addBtn) { - var $holder, $menu, url; - $menu = $('.emoji-menu'); - if ($addBtn.hasClass('js-note-emoji')) { - $addBtn.closest('.note').find('.js-awards-block').addClass('current'); - } else { - $addBtn.closest('.js-awards-block').addClass('current'); - } - if ($menu.length) { - $holder = $addBtn.closest('.js-award-holder'); - if ($menu.is('.is-visible')) { - $addBtn.removeClass('is-active'); - $menu.removeClass('is-visible'); - return $('#emoji_search').blur(); - } else { - $addBtn.addClass('is-active'); - this.positionMenu($menu, $addBtn); - $menu.addClass('is-visible'); - return $('#emoji_search').focus(); - } - } else { - $addBtn.addClass('is-loading is-active'); - url = this.getAwardMenuUrl(); - return this.createEmojiMenu(url, (function(_this) { - return function() { - $addBtn.removeClass('is-loading'); - $menu = $('.emoji-menu'); - _this.positionMenu($menu, $addBtn); - if (!_this.frequentEmojiBlockRendered) { - _this.renderFrequentlyUsedBlock(); - } - return setTimeout(function() { - $menu.addClass('is-visible'); - $('#emoji_search').focus(); - return _this.setupSearch(); - }, 200); - }; - })(this)); - } - }; - - AwardsHandler.prototype.createEmojiMenu = function(awardMenuUrl, callback) { - return $.get(awardMenuUrl, function(response) { - $('body').append(response); - return callback(); + return currentCategoryMap; + }, { + activity: [], + people: [], + nature: [], + food: [], + travel: [], + objects: [], + symbols: [], + flags: [], + }); +} + +function renderCategory(name, emojiList) { + return ` +
+ ${name} +
+ + `; +} + +function AwardsHandler() { + 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'); + if ($menu.length === 0) { + requestAnimationFrame(() => { + this.createEmojiMenu(); }); - }; - - AwardsHandler.prototype.positionMenu = function($menu, $addBtn) { - var css, position; - position = $addBtn.data('position'); - // The menu could potentially be off-screen or in a hidden overflow element - // So we position the element absolute in the body - css = { - top: ($addBtn.offset().top + $addBtn.outerHeight()) + "px" - }; - if (position === 'right') { - css.left = (($addBtn.offset().left - $menu.outerWidth()) + 20) + "px"; - $menu.addClass('is-aligned-right'); - } else { - css.left = ($addBtn.offset().left) + "px"; - $menu.removeClass('is-aligned-right'); - } - return $menu.css(css); - }; - - AwardsHandler.prototype.addAward = function(votesBlock, awardUrl, emoji, checkMutuality, callback) { - if (checkMutuality == null) { - checkMutuality = true; - } - emoji = this.normilizeEmojiName(emoji); - this.postEmoji(awardUrl, emoji, (function(_this) { - return function() { - _this.addAwardToEmojiBar(votesBlock, emoji, checkMutuality); - return typeof callback === "function" ? callback() : void 0; - }; - })(this)); - return $('.emoji-menu').removeClass('is-visible'); - }; - - AwardsHandler.prototype.addAwardToEmojiBar = function(votesBlock, emoji, checkForMutuality) { - var $emojiButton, counter; - if (checkForMutuality == null) { - checkForMutuality = true; - } - if (checkForMutuality) { - this.checkMutuality(votesBlock, emoji); - } - this.addEmojiToFrequentlyUsedList(emoji); - emoji = this.normilizeEmojiName(emoji); - $emojiButton = this.findEmojiIcon(votesBlock, emoji).parent(); - if ($emojiButton.length > 0) { - if (this.isActive($emojiButton)) { - return this.decrementCounter($emojiButton, emoji); - } else { - counter = $emojiButton.find('.js-counter'); - counter.text(parseInt(counter.text(), 10) + 1); - $emojiButton.addClass('active'); - this.addYouToUserList(votesBlock, emoji); - return this.animateEmoji($emojiButton); - } - } else { - votesBlock.removeClass('hidden'); - return this.createEmoji(votesBlock, emoji); - } - }; - - AwardsHandler.prototype.getVotesBlock = function() { - var currentBlock; - currentBlock = $('.js-awards-block.current'); - if (currentBlock.length) { - return currentBlock; - } else { - return $('.js-awards-block').eq(0); - } - }; - - AwardsHandler.prototype.getAwardUrl = function() { - return this.getVotesBlock().data('award-url'); - }; - - AwardsHandler.prototype.checkMutuality = function(votesBlock, emoji) { - var $emojiButton, awardUrl, isAlreadyVoted, mutualVote; - awardUrl = this.getAwardUrl(); - if (emoji === 'thumbsup' || emoji === 'thumbsdown') { - mutualVote = emoji === 'thumbsup' ? 'thumbsdown' : 'thumbsup'; - $emojiButton = votesBlock.find("[data-emoji=" + mutualVote + "]").parent(); - isAlreadyVoted = $emojiButton.hasClass('active'); - if (isAlreadyVoted) { - this.addAward(votesBlock, awardUrl, mutualVote, false); - } - } - }; - - AwardsHandler.prototype.isActive = function($emojiButton) { - return $emojiButton.hasClass('active'); - }; - - AwardsHandler.prototype.decrementCounter = function($emojiButton, emoji) { - var counter, counterNumber; - counter = $('.js-counter', $emojiButton); - counterNumber = parseInt(counter.text(), 10); - if (counterNumber > 1) { - counter.text(counterNumber - 1); - this.removeYouFromUserList($emojiButton, emoji); - } else if (emoji === 'thumbsup' || emoji === 'thumbsdown') { - $emojiButton.tooltip('destroy'); - counter.text('0'); - this.removeYouFromUserList($emojiButton, emoji); - if ($emojiButton.parents('.note').length) { - this.removeEmoji($emojiButton); - } - } else { - this.removeEmoji($emojiButton); - } - return $emojiButton.removeClass('active'); - }; - - AwardsHandler.prototype.removeEmoji = function($emojiButton) { - var $votesBlock; - $emojiButton.tooltip('destroy'); - $emojiButton.remove(); - $votesBlock = this.getVotesBlock(); - if ($votesBlock.find('.js-emoji-btn').length === 0) { - return $votesBlock.addClass('hidden'); - } - }; - - AwardsHandler.prototype.getAwardTooltip = function($awardBlock) { - return $awardBlock.attr('data-original-title') || $awardBlock.attr('data-title') || ''; - }; - - AwardsHandler.prototype.toSentence = function(list) { - if (list.length <= 2) { - return list.join(' and '); + } + // Prebuild the categoryMap + categoryMap = categoryMap || buildCategoryMap(); + }); + this.registerEventListener('on', $(document), 'click', '.js-add-award', (e) => { + e.stopPropagation(); + e.preventDefault(); + this.showEmojiMenu($(e.currentTarget)); + }); + + this.registerEventListener('on', $('html'), 'click', (e) => { + const $target = $(e.target); + if (!$target.closest('.emoji-menu-content').length) { + $('.js-awards-block.current').removeClass('current'); + } + if (!$target.closest('.emoji-menu').length) { + if ($('.emoji-menu').is(':visible')) { + $('.js-add-award.is-active').removeClass('is-active'); + $('.emoji-menu').removeClass('is-visible'); } - else { - return list.slice(0, -1).join(', ') + ', and ' + list[list.length - 1]; + } + }); + this.registerEventListener('on', $(document), 'click', '.js-emoji-btn', (e) => { + e.preventDefault(); + const $target = $(e.currentTarget); + const $glEmojiElement = $target.find('gl-emoji'); + const $spriteIconElement = $target.find('.icon'); + const emoji = ($glEmojiElement.length ? $glEmojiElement : $spriteIconElement).data('name'); + $target.closest('.js-awards-block').addClass('current'); + return this.addAward(this.getVotesBlock(), this.getAwardUrl(), emoji); + }); +} + +AwardsHandler.prototype.registerEventListener = function registerEventListener(method = 'on', element, ...args) { + element[method].call(element, ...args); + this.eventListeners.push({ + element, + args, + }); +}; + +AwardsHandler.prototype.showEmojiMenu = function showEmojiMenu($addBtn) { + if ($addBtn.hasClass('js-note-emoji')) { + $addBtn.closest('.note').find('.js-awards-block').addClass('current'); + } else { + $addBtn.closest('.js-awards-block').addClass('current'); + } + + const $menu = $('.emoji-menu'); + if ($menu.length) { + if ($menu.is('.is-visible')) { + $addBtn.removeClass('is-active'); + $menu.removeClass('is-visible'); + $('#emoji_search').blur(); + } else { + $addBtn.addClass('is-active'); + this.positionMenu($menu, $addBtn); + $menu.addClass('is-visible'); + $('#emoji_search').focus(); + } + } else { + $addBtn.addClass('is-loading is-active'); + this.createEmojiMenu(() => { + const $createdMenu = $('.emoji-menu'); + $addBtn.removeClass('is-loading'); + this.positionMenu($createdMenu, $addBtn); + if (!this.frequentEmojiBlockRendered) { + this.renderFrequentlyUsedBlock(); } - }; - - AwardsHandler.prototype.removeYouFromUserList = function($emojiButton, emoji) { - var authors, awardBlock, newAuthors, originalTitle; - awardBlock = $emojiButton; - originalTitle = this.getAwardTooltip(awardBlock); - authors = originalTitle.split(FROM_SENTENCE_REGEX); - authors.splice(authors.indexOf('You'), 1); - return awardBlock - .closest('.js-emoji-btn') - .removeData('title') - .removeAttr('data-title') - .removeAttr('data-original-title') - .attr('title', this.toSentence(authors)) - .tooltip('fixTitle'); - }; - - AwardsHandler.prototype.addYouToUserList = function(votesBlock, emoji) { - var awardBlock, origTitle, users; - awardBlock = this.findEmojiIcon(votesBlock, emoji).parent(); - origTitle = this.getAwardTooltip(awardBlock); - users = []; - if (origTitle) { - users = origTitle.trim().split(FROM_SENTENCE_REGEX); + return setTimeout(() => { + $createdMenu.addClass('is-visible'); + $('#emoji_search').focus(); + }, 200); + }); + } +}; + +// Create the emoji menu with the first category of emojis. +// Then after the emoji menu has been expanded(and CSS transition has ended), +// render the remaining categories of emojis one by one to avoid jank. +AwardsHandler.prototype.createEmojiMenu = function createEmojiMenu(callback) { + if (this.isCreatingEmojiMenu) { + return; + } + this.isCreatingEmojiMenu = true; + + // Render the first category + categoryMap = categoryMap || buildCategoryMap(); + const categoryNameKey = Object.keys(categoryMap)[0]; + const emojisInCategory = categoryMap[categoryNameKey]; + const firstCategory = renderCategory(categoryLabelMap[categoryNameKey], emojisInCategory); + + const emojiMenuMarkup = ` +
+ + +
+ ${firstCategory} +
+
+ `; + + document.body.insertAdjacentHTML('beforeend', emojiMenuMarkup); + + this.addRemainingEmojiMenuCategories(); + this.setupSearch(); + if (callback) { + callback(); + } +}; + +AwardsHandler + .prototype + .addRemainingEmojiMenuCategories = function addRemainingEmojiMenuCategories() { + if (this.isAddingRemainingEmojiMenuCategories) { + return; + } + this.isAddingRemainingEmojiMenuCategories = true; + + categoryMap = categoryMap || buildCategoryMap(); + + // Avoid the jank and render the remaining categories separately + // This will take more time, but makes UI more responsive + const emojiContentElement = document.querySelector('.emoji-menu .emoji-menu-content'); + const remainingCategories = Object.keys(categoryMap).slice(1); + const allCategoriesAddedPromise = remainingCategories.reduce( + (promiseChain, categoryNameKey) => + promiseChain.then(() => + new Promise((resolve) => { + const emojisInCategory = categoryMap[categoryNameKey]; + const categoryMarkup = renderCategory( + categoryLabelMap[categoryNameKey], + emojisInCategory, + ); + requestAnimationFrame(() => { + emojiContentElement.insertAdjacentHTML('beforeend', categoryMarkup); + resolve(); + }); + }), + ), + Promise.resolve(), + ); + + allCategoriesAddedPromise.then(() => { + // Used for tests + // We check for the menu in case it was destroyed in the meantime + const menu = document.querySelector('.emoji-menu'); + if (menu) { + menu.dispatchEvent(new CustomEvent('build-emoji-menu-finish')); } - users.unshift('You'); - return awardBlock - .attr('title', this.toSentence(users)) - .tooltip('fixTitle'); - }; - - AwardsHandler.prototype.createEmoji_ = function(votesBlock, emoji) { - var $emojiButton, buttonHtml, emojiCssClass; - emojiCssClass = this.resolveNameToCssClass(emoji); - buttonHtml = ""; - $emojiButton = $(buttonHtml); - $emojiButton.insertBefore(votesBlock.find('.js-award-holder')).find('.emoji-icon').data('emoji', emoji); + }); + }; + +AwardsHandler.prototype.positionMenu = function positionMenu($menu, $addBtn) { + const position = $addBtn.data('position'); + // The menu could potentially be off-screen or in a hidden overflow element + // So we position the element absolute in the body + const css = { + top: `${$addBtn.offset().top + $addBtn.outerHeight()}px`, + }; + if (position === 'right') { + css.left = `${($addBtn.offset().left - $menu.outerWidth()) + 20}px`; + $menu.addClass('is-aligned-right'); + } else { + css.left = `${$addBtn.offset().left}px`; + $menu.removeClass('is-aligned-right'); + } + return $menu.css(css); +}; + +AwardsHandler.prototype.addAward = function addAward( + votesBlock, + awardUrl, + emoji, + checkMutuality, + callback, +) { + const normalizedEmoji = this.normalizeEmojiName(emoji); + this.postEmoji(awardUrl, normalizedEmoji, () => { + this.addAwardToEmojiBar(votesBlock, normalizedEmoji, checkMutuality); + return typeof callback === 'function' ? callback() : undefined; + }); + return $('.emoji-menu').removeClass('is-visible'); +}; + +AwardsHandler.prototype.addAwardToEmojiBar = function addAwardToEmojiBar( + votesBlock, + emoji, + checkForMutuality, +) { + if (checkForMutuality || checkForMutuality === null) { + this.checkMutuality(votesBlock, emoji); + } + this.addEmojiToFrequentlyUsedList(emoji); + const normalizedEmoji = this.normalizeEmojiName(emoji); + const $emojiButton = this.findEmojiIcon(votesBlock, normalizedEmoji).parent(); + if ($emojiButton.length > 0) { + if (this.isActive($emojiButton)) { + this.decrementCounter($emojiButton, normalizedEmoji); + } else { + const counter = $emojiButton.find('.js-counter'); + counter.text(parseInt(counter.text(), 10) + 1); + $emojiButton.addClass('active'); + this.addYouToUserList(votesBlock, normalizedEmoji); this.animateEmoji($emojiButton); - $('.award-control').tooltip(); - return votesBlock.removeClass('current'); - }; - - AwardsHandler.prototype.animateEmoji = function($emoji) { - var className = 'pulse animated once short'; - $emoji.addClass(className); + } + } else { + votesBlock.removeClass('hidden'); + this.createEmoji(votesBlock, normalizedEmoji); + } +}; + +AwardsHandler.prototype.getVotesBlock = function getVotesBlock() { + const currentBlock = $('.js-awards-block.current'); + let resultantVotesBlock = currentBlock; + if (currentBlock.length === 0) { + resultantVotesBlock = $('.js-awards-block').eq(0); + } + + return resultantVotesBlock; +}; + +AwardsHandler.prototype.getAwardUrl = function getAwardUrl() { + return this.getVotesBlock().data('award-url'); +}; + +AwardsHandler.prototype.checkMutuality = function checkMutuality(votesBlock, emoji) { + const awardUrl = this.getAwardUrl(); + if (emoji === 'thumbsup' || emoji === 'thumbsdown') { + const mutualVote = emoji === 'thumbsup' ? 'thumbsdown' : 'thumbsup'; + const $emojiButton = votesBlock.find(`[data-name="${mutualVote}"]`).parent(); + const isAlreadyVoted = $emojiButton.hasClass('active'); + if (isAlreadyVoted) { + this.addAward(votesBlock, awardUrl, mutualVote, false); + } + } +}; + +AwardsHandler.prototype.isActive = function isActive($emojiButton) { + return $emojiButton.hasClass('active'); +}; + +AwardsHandler.prototype.decrementCounter = function decrementCounter($emojiButton, emoji) { + const counter = $('.js-counter', $emojiButton); + const counterNumber = parseInt(counter.text(), 10); + if (counterNumber > 1) { + counter.text(counterNumber - 1); + this.removeYouFromUserList($emojiButton); + } else if (emoji === 'thumbsup' || emoji === 'thumbsdown') { + $emojiButton.tooltip('destroy'); + counter.text('0'); + this.removeYouFromUserList($emojiButton); + if ($emojiButton.parents('.note').length) { + this.removeEmoji($emojiButton); + } + } else { + this.removeEmoji($emojiButton); + } + return $emojiButton.removeClass('active'); +}; + +AwardsHandler.prototype.removeEmoji = function removeEmoji($emojiButton) { + $emojiButton.tooltip('destroy'); + $emojiButton.remove(); + const $votesBlock = this.getVotesBlock(); + if ($votesBlock.find('.js-emoji-btn').length === 0) { + $votesBlock.addClass('hidden'); + } +}; + +AwardsHandler.prototype.getAwardTooltip = function getAwardTooltip($awardBlock) { + return $awardBlock.attr('data-original-title') || $awardBlock.attr('data-title') || ''; +}; + +AwardsHandler.prototype.toSentence = function toSentence(list) { + let sentence; + if (list.length <= 2) { + sentence = list.join(' and '); + } else { + sentence = `${list.slice(0, -1).join(', ')}, and ${list[list.length - 1]}`; + } + + return sentence; +}; + +AwardsHandler.prototype.removeYouFromUserList = function removeYouFromUserList($emojiButton) { + const awardBlock = $emojiButton; + const originalTitle = this.getAwardTooltip(awardBlock); + const authors = originalTitle.split(FROM_SENTENCE_REGEX); + authors.splice(authors.indexOf('You'), 1); + return awardBlock + .closest('.js-emoji-btn') + .removeData('title') + .removeAttr('data-title') + .removeAttr('data-original-title') + .attr('title', this.toSentence(authors)) + .tooltip('fixTitle'); +}; + +AwardsHandler.prototype.addYouToUserList = function addYouToUserList(votesBlock, emoji) { + const awardBlock = this.findEmojiIcon(votesBlock, emoji).parent(); + const origTitle = this.getAwardTooltip(awardBlock); + let users = []; + if (origTitle) { + users = origTitle.trim().split(FROM_SENTENCE_REGEX); + } + users.unshift('You'); + return awardBlock + .attr('title', this.toSentence(users)) + .tooltip('fixTitle'); +}; + +AwardsHandler + .prototype + .createAwardButtonForVotesBlock = function createAwardButtonForVotesBlock(votesBlock, emojiName) { + const buttonHtml = ` + + `; + const $emojiButton = $(buttonHtml); + $emojiButton.insertBefore(votesBlock.find('.js-award-holder')).find('.emoji-icon').data('name', emojiName); + this.animateEmoji($emojiButton); + $('.award-control').tooltip(); + votesBlock.removeClass('current'); + }; + +AwardsHandler.prototype.animateEmoji = function animateEmoji($emoji) { + const className = 'pulse animated once short'; + $emoji.addClass(className); + + this.registerEventListener('on', $emoji, animationEndEventString, (e) => { + $(e.currentTarget).removeClass(className); + }); +}; + +AwardsHandler.prototype.createEmoji = function createEmoji(votesBlock, emoji) { + if ($('.emoji-menu').length) { + this.createAwardButtonForVotesBlock(votesBlock, emoji); + } + this.createEmojiMenu(() => { + this.createAwardButtonForVotesBlock(votesBlock, emoji); + }); +}; + +AwardsHandler.prototype.postEmoji = function postEmoji(awardUrl, emoji, callback) { + return $.post(awardUrl, { + name: emoji, + }, (data) => { + if (data.ok) { + callback(); + } + }); +}; + +AwardsHandler.prototype.findEmojiIcon = function findEmojiIcon(votesBlock, emoji) { + return votesBlock.find(`.js-emoji-btn [data-name="${emoji}"]`); +}; + +AwardsHandler.prototype.scrollToAwards = function scrollToAwards() { + const options = { + scrollTop: $('.awards').offset().top - 110, + }; + return $('body, html').animate(options, 200); +}; + +AwardsHandler.prototype.normalizeEmojiName = function normalizeEmojiName(emoji) { + return this.aliases[emoji] || emoji; +}; + +AwardsHandler + .prototype + .addEmojiToFrequentlyUsedList = function addEmojiToFrequentlyUsedList(emoji) { + const frequentlyUsedEmojis = this.getFrequentlyUsedEmojis(); + frequentlyUsedEmojis.push(emoji); + Cookies.set('frequently_used_emojis', frequentlyUsedEmojis.join(','), { expires: 365 }); + }; + +AwardsHandler.prototype.getFrequentlyUsedEmojis = function getFrequentlyUsedEmojis() { + const frequentlyUsedEmojis = (Cookies.get('frequently_used_emojis') || '').split(','); + return _.compact(_.uniq(frequentlyUsedEmojis)); +}; + +AwardsHandler.prototype.renderFrequentlyUsedBlock = function renderFrequentlyUsedBlock() { + if (Cookies.get('frequently_used_emojis')) { + const frequentlyUsedEmojis = this.getFrequentlyUsedEmojis(); + const ul = $('