Commit 5ea6f4b0 authored by Eric Eastwood's avatar Eric Eastwood

Merge branch '26371-native-emojis-v3-code' into '26371-native-emojis-v3'

Native Unicode Emojis

See merge request !9570
parents f911b948 ca132c73
app/assets/images/emoji.png

1.04 MB | W: | H:

app/assets/images/emoji.png

1.16 MB | W: | H:

app/assets/images/emoji.png
app/assets/images/emoji.png
app/assets/images/emoji.png
app/assets/images/emoji.png
  • 2-up
  • Swipe
  • Onion skin
app/assets/images/emoji@2x.png

2.53 MB | W: | H:

app/assets/images/emoji@2x.png

2.84 MB | W: | H:

app/assets/images/emoji@2x.png
app/assets/images/emoji@2x.png
app/assets/images/emoji@2x.png
app/assets/images/emoji@2x.png
  • 2-up
  • Swipe
  • Onion skin
/* 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 */ /* global Cookies */
var emojiAliases = require('emoji-aliases'); 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);
}
(function() { return currentCategoryMap;
this.AwardsHandler = (function() { }, {
var FROM_SENTENCE_REGEX = /(?:, and | and |, )/; // For separating lists produced by ruby's Array#toSentence activity: [],
function AwardsHandler() { people: [],
nature: [],
food: [],
travel: [],
objects: [],
symbols: [],
flags: [],
});
}
function renderCategory(name, emojiList) {
return `
<h5 class="emoji-menu-title">
${name}
</h5>
<ul class="clearfix emoji-menu-list">
${emojiList.map(emojiName => `
<li class="emoji-menu-list-item">
<button class="emoji-menu-btn text-center js-emoji-btn" type="button">
${glEmojiTag(emojiName, {
sprite: true,
})}
</button>
</li>
`).join('\n')}
</ul>
`;
}
function AwardsHandler() {
this.eventListeners = [];
this.aliases = emojiAliases; this.aliases = emojiAliases;
$(document).off('click', '.js-add-award').on('click', '.js-add-award', (function(_this) { // If the user shows intent let's pre-build the menu
return function(e) { this.registerEventListener('one', $(document), 'mouseenter focus', '.js-add-award', 'mouseenter focus', () => {
const $menu = $('.emoji-menu');
if ($menu.length === 0) {
requestAnimationFrame(() => {
this.createEmojiMenu();
});
}
// Prebuild the categoryMap
categoryMap = categoryMap || buildCategoryMap();
});
this.registerEventListener('on', $(document), 'click', '.js-add-award', (e) => {
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
return _this.showEmojiMenu($(e.currentTarget)); this.showEmojiMenu($(e.currentTarget));
}; });
})(this));
$('html').on('click', function(e) { this.registerEventListener('on', $('html'), 'click', (e) => {
var $target; const $target = $(e.target);
$target = $(e.target);
if (!$target.closest('.emoji-menu-content').length) { if (!$target.closest('.emoji-menu-content').length) {
$('.js-awards-block.current').removeClass('current'); $('.js-awards-block.current').removeClass('current');
} }
if (!$target.closest('.emoji-menu').length) { if (!$target.closest('.emoji-menu').length) {
if ($('.emoji-menu').is(':visible')) { if ($('.emoji-menu').is(':visible')) {
$('.js-add-award.is-active').removeClass('is-active'); $('.js-add-award.is-active').removeClass('is-active');
return $('.emoji-menu').removeClass('is-visible'); $('.emoji-menu').removeClass('is-visible');
} }
} }
}); });
$(document).off('click', '.js-emoji-btn').on('click', '.js-emoji-btn', (function(_this) { this.registerEventListener('on', $(document), 'click', '.js-emoji-btn', (e) => {
return function(e) {
var $target, emoji;
e.preventDefault(); e.preventDefault();
$target = $(e.currentTarget); const $target = $(e.currentTarget);
emoji = $target.find('.icon').data('emoji'); 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'); $target.closest('.js-awards-block').addClass('current');
return _this.addAward(_this.getVotesBlock(), _this.getAwardUrl(), emoji); return this.addAward(this.getVotesBlock(), this.getAwardUrl(), emoji);
}; });
})(this)); }
}
AwardsHandler.prototype.showEmojiMenu = function($addBtn) { AwardsHandler.prototype.registerEventListener = function registerEventListener(method = 'on', element, ...args) {
var $holder, $menu, url; element[method].call(element, ...args);
$menu = $('.emoji-menu'); this.eventListeners.push({
element,
args,
});
};
AwardsHandler.prototype.showEmojiMenu = function showEmojiMenu($addBtn) {
if ($addBtn.hasClass('js-note-emoji')) { if ($addBtn.hasClass('js-note-emoji')) {
$addBtn.closest('.note').find('.js-awards-block').addClass('current'); $addBtn.closest('.note').find('.js-awards-block').addClass('current');
} else { } else {
$addBtn.closest('.js-awards-block').addClass('current'); $addBtn.closest('.js-awards-block').addClass('current');
} }
const $menu = $('.emoji-menu');
if ($menu.length) { if ($menu.length) {
$holder = $addBtn.closest('.js-award-holder');
if ($menu.is('.is-visible')) { if ($menu.is('.is-visible')) {
$addBtn.removeClass('is-active'); $addBtn.removeClass('is-active');
$menu.removeClass('is-visible'); $menu.removeClass('is-visible');
return $('#emoji_search').blur(); $('#emoji_search').blur();
} else { } else {
$addBtn.addClass('is-active'); $addBtn.addClass('is-active');
this.positionMenu($menu, $addBtn); this.positionMenu($menu, $addBtn);
$menu.addClass('is-visible'); $menu.addClass('is-visible');
return $('#emoji_search').focus(); $('#emoji_search').focus();
} }
} else { } else {
$addBtn.addClass('is-loading is-active'); $addBtn.addClass('is-loading is-active');
url = this.getAwardMenuUrl(); this.createEmojiMenu(() => {
return this.createEmojiMenu(url, (function(_this) { const $createdMenu = $('.emoji-menu');
return function() {
$addBtn.removeClass('is-loading'); $addBtn.removeClass('is-loading');
$menu = $('.emoji-menu'); this.positionMenu($createdMenu, $addBtn);
_this.positionMenu($menu, $addBtn); if (!this.frequentEmojiBlockRendered) {
if (!_this.frequentEmojiBlockRendered) { this.renderFrequentlyUsedBlock();
_this.renderFrequentlyUsedBlock();
} }
return setTimeout(function() { return setTimeout(() => {
$menu.addClass('is-visible'); $createdMenu.addClass('is-visible');
$('#emoji_search').focus(); $('#emoji_search').focus();
return _this.setupSearch();
}, 200); }, 200);
}; });
})(this));
} }
}; };
AwardsHandler.prototype.createEmojiMenu = function(awardMenuUrl, callback) { // Create the emoji menu with the first category of emojis.
return $.get(awardMenuUrl, function(response) { // Then render the remaining categories of emojis one by one to avoid jank.
$('body').append(response); AwardsHandler.prototype.createEmojiMenu = function createEmojiMenu(callback) {
return 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 = `
<div class="emoji-menu">
<input type="text" name="emoji_search" id="emoji_search" value="" class="emoji-search search-input form-control" placeholder="Search emoji" />
<div class="emoji-menu-content">
${firstCategory}
</div>
</div>
`;
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 menu = document.querySelector('.emoji-menu');
const emojiContentElement = menu.querySelector('.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
if (menu) {
menu.dispatchEvent(new CustomEvent('build-emoji-menu-finish'));
}
}); });
}; };
AwardsHandler.prototype.positionMenu = function($menu, $addBtn) { AwardsHandler.prototype.positionMenu = function positionMenu($menu, $addBtn) {
var css, position; const position = $addBtn.data('position');
position = $addBtn.data('position');
// The menu could potentially be off-screen or in a hidden overflow element // The menu could potentially be off-screen or in a hidden overflow element
// So we position the element absolute in the body // So we position the element absolute in the body
css = { const css = {
top: ($addBtn.offset().top + $addBtn.outerHeight()) + "px" top: `${$addBtn.offset().top + $addBtn.outerHeight()}px`,
}; };
if (position === 'right') { if (position === 'right') {
css.left = (($addBtn.offset().left - $menu.outerWidth()) + 20) + "px"; css.left = `${($addBtn.offset().left - $menu.outerWidth()) + 20}px`;
$menu.addClass('is-aligned-right'); $menu.addClass('is-aligned-right');
} else { } else {
css.left = ($addBtn.offset().left) + "px"; css.left = `${$addBtn.offset().left}px`;
$menu.removeClass('is-aligned-right'); $menu.removeClass('is-aligned-right');
} }
return $menu.css(css); return $menu.css(css);
}; };
AwardsHandler.prototype.addAward = function(votesBlock, awardUrl, emoji, checkMutuality, callback) { AwardsHandler.prototype.addAward = function addAward(
if (checkMutuality == null) { votesBlock,
checkMutuality = true; awardUrl,
} emoji,
emoji = this.normilizeEmojiName(emoji); checkMutuality,
this.postEmoji(awardUrl, emoji, (function(_this) { callback,
return function() { ) {
_this.addAwardToEmojiBar(votesBlock, emoji, checkMutuality); const normalizedEmoji = this.normalizeEmojiName(emoji);
return typeof callback === "function" ? callback() : void 0; this.postEmoji(awardUrl, normalizedEmoji, () => {
}; this.addAwardToEmojiBar(votesBlock, normalizedEmoji, checkMutuality);
})(this)); return typeof callback === 'function' ? callback() : undefined;
});
return $('.emoji-menu').removeClass('is-visible'); return $('.emoji-menu').removeClass('is-visible');
}; };
AwardsHandler.prototype.addAwardToEmojiBar = function(votesBlock, emoji, checkForMutuality) { AwardsHandler.prototype.addAwardToEmojiBar = function addAwardToEmojiBar(
var $emojiButton, counter; votesBlock,
if (checkForMutuality == null) { emoji,
checkForMutuality = true; checkForMutuality,
} ) {
if (checkForMutuality) { if (checkForMutuality || checkForMutuality === null) {
this.checkMutuality(votesBlock, emoji); this.checkMutuality(votesBlock, emoji);
} }
this.addEmojiToFrequentlyUsedList(emoji); this.addEmojiToFrequentlyUsedList(emoji);
emoji = this.normilizeEmojiName(emoji); const normalizedEmoji = this.normalizeEmojiName(emoji);
$emojiButton = this.findEmojiIcon(votesBlock, emoji).parent(); const $emojiButton = this.findEmojiIcon(votesBlock, normalizedEmoji).parent();
if ($emojiButton.length > 0) { if ($emojiButton.length > 0) {
if (this.isActive($emojiButton)) { if (this.isActive($emojiButton)) {
return this.decrementCounter($emojiButton, emoji); this.decrementCounter($emojiButton, normalizedEmoji);
} else { } else {
counter = $emojiButton.find('.js-counter'); const counter = $emojiButton.find('.js-counter');
counter.text(parseInt(counter.text(), 10) + 1); counter.text(parseInt(counter.text(), 10) + 1);
$emojiButton.addClass('active'); $emojiButton.addClass('active');
this.addYouToUserList(votesBlock, emoji); this.addYouToUserList(votesBlock, normalizedEmoji);
return this.animateEmoji($emojiButton); this.animateEmoji($emojiButton);
} }
} else { } else {
votesBlock.removeClass('hidden'); votesBlock.removeClass('hidden');
return this.createEmoji(votesBlock, emoji); this.createEmoji(votesBlock, normalizedEmoji);
} }
}; };
AwardsHandler.prototype.getVotesBlock = function() { AwardsHandler.prototype.getVotesBlock = function getVotesBlock() {
var currentBlock; const currentBlock = $('.js-awards-block.current');
currentBlock = $('.js-awards-block.current'); let resultantVotesBlock = currentBlock;
if (currentBlock.length) { if (currentBlock.length === 0) {
return currentBlock; resultantVotesBlock = $('.js-awards-block').eq(0);
} else {
return $('.js-awards-block').eq(0);
} }
};
AwardsHandler.prototype.getAwardUrl = function() { return resultantVotesBlock;
};
AwardsHandler.prototype.getAwardUrl = function getAwardUrl() {
return this.getVotesBlock().data('award-url'); return this.getVotesBlock().data('award-url');
}; };
AwardsHandler.prototype.checkMutuality = function(votesBlock, emoji) { AwardsHandler.prototype.checkMutuality = function checkMutuality(votesBlock, emoji) {
var $emojiButton, awardUrl, isAlreadyVoted, mutualVote; const awardUrl = this.getAwardUrl();
awardUrl = this.getAwardUrl();
if (emoji === 'thumbsup' || emoji === 'thumbsdown') { if (emoji === 'thumbsup' || emoji === 'thumbsdown') {
mutualVote = emoji === 'thumbsup' ? 'thumbsdown' : 'thumbsup'; const mutualVote = emoji === 'thumbsup' ? 'thumbsdown' : 'thumbsup';
$emojiButton = votesBlock.find("[data-emoji=" + mutualVote + "]").parent(); const $emojiButton = votesBlock.find(`[data-name="${mutualVote}"]`).parent();
isAlreadyVoted = $emojiButton.hasClass('active'); const isAlreadyVoted = $emojiButton.hasClass('active');
if (isAlreadyVoted) { if (isAlreadyVoted) {
this.addAward(votesBlock, awardUrl, mutualVote, false); this.addAward(votesBlock, awardUrl, mutualVote, false);
} }
} }
}; };
AwardsHandler.prototype.isActive = function($emojiButton) { AwardsHandler.prototype.isActive = function isActive($emojiButton) {
return $emojiButton.hasClass('active'); return $emojiButton.hasClass('active');
}; };
AwardsHandler.prototype.decrementCounter = function($emojiButton, emoji) { AwardsHandler.prototype.decrementCounter = function decrementCounter($emojiButton, emoji) {
var counter, counterNumber; const counter = $('.js-counter', $emojiButton);
counter = $('.js-counter', $emojiButton); const counterNumber = parseInt(counter.text(), 10);
counterNumber = parseInt(counter.text(), 10);
if (counterNumber > 1) { if (counterNumber > 1) {
counter.text(counterNumber - 1); counter.text(counterNumber - 1);
this.removeYouFromUserList($emojiButton, emoji); this.removeYouFromUserList($emojiButton);
} else if (emoji === 'thumbsup' || emoji === 'thumbsdown') { } else if (emoji === 'thumbsup' || emoji === 'thumbsdown') {
$emojiButton.tooltip('destroy'); $emojiButton.tooltip('destroy');
counter.text('0'); counter.text('0');
this.removeYouFromUserList($emojiButton, emoji); this.removeYouFromUserList($emojiButton);
if ($emojiButton.parents('.note').length) { if ($emojiButton.parents('.note').length) {
this.removeEmoji($emojiButton); this.removeEmoji($emojiButton);
} }
...@@ -196,36 +334,36 @@ var emojiAliases = require('emoji-aliases'); ...@@ -196,36 +334,36 @@ var emojiAliases = require('emoji-aliases');
this.removeEmoji($emojiButton); this.removeEmoji($emojiButton);
} }
return $emojiButton.removeClass('active'); return $emojiButton.removeClass('active');
}; };
AwardsHandler.prototype.removeEmoji = function($emojiButton) { AwardsHandler.prototype.removeEmoji = function removeEmoji($emojiButton) {
var $votesBlock;
$emojiButton.tooltip('destroy'); $emojiButton.tooltip('destroy');
$emojiButton.remove(); $emojiButton.remove();
$votesBlock = this.getVotesBlock(); const $votesBlock = this.getVotesBlock();
if ($votesBlock.find('.js-emoji-btn').length === 0) { if ($votesBlock.find('.js-emoji-btn').length === 0) {
return $votesBlock.addClass('hidden'); $votesBlock.addClass('hidden');
} }
}; };
AwardsHandler.prototype.getAwardTooltip = function($awardBlock) { AwardsHandler.prototype.getAwardTooltip = function getAwardTooltip($awardBlock) {
return $awardBlock.attr('data-original-title') || $awardBlock.attr('data-title') || ''; return $awardBlock.attr('data-original-title') || $awardBlock.attr('data-title') || '';
}; };
AwardsHandler.prototype.toSentence = function(list) { AwardsHandler.prototype.toSentence = function toSentence(list) {
let sentence;
if (list.length <= 2) { if (list.length <= 2) {
return list.join(' and '); sentence = list.join(' and ');
} } else {
else { sentence = `${list.slice(0, -1).join(', ')}, and ${list[list.length - 1]}`;
return list.slice(0, -1).join(', ') + ', and ' + list[list.length - 1];
} }
};
AwardsHandler.prototype.removeYouFromUserList = function($emojiButton, emoji) { return sentence;
var authors, awardBlock, newAuthors, originalTitle; };
awardBlock = $emojiButton;
originalTitle = this.getAwardTooltip(awardBlock); AwardsHandler.prototype.removeYouFromUserList = function removeYouFromUserList($emojiButton) {
authors = originalTitle.split(FROM_SENTENCE_REGEX); const awardBlock = $emojiButton;
const originalTitle = this.getAwardTooltip(awardBlock);
const authors = originalTitle.split(FROM_SENTENCE_REGEX);
authors.splice(authors.indexOf('You'), 1); authors.splice(authors.indexOf('You'), 1);
return awardBlock return awardBlock
.closest('.js-emoji-btn') .closest('.js-emoji-btn')
...@@ -234,13 +372,12 @@ var emojiAliases = require('emoji-aliases'); ...@@ -234,13 +372,12 @@ var emojiAliases = require('emoji-aliases');
.removeAttr('data-original-title') .removeAttr('data-original-title')
.attr('title', this.toSentence(authors)) .attr('title', this.toSentence(authors))
.tooltip('fixTitle'); .tooltip('fixTitle');
}; };
AwardsHandler.prototype.addYouToUserList = function(votesBlock, emoji) { AwardsHandler.prototype.addYouToUserList = function addYouToUserList(votesBlock, emoji) {
var awardBlock, origTitle, users; const awardBlock = this.findEmojiIcon(votesBlock, emoji).parent();
awardBlock = this.findEmojiIcon(votesBlock, emoji).parent(); const origTitle = this.getAwardTooltip(awardBlock);
origTitle = this.getAwardTooltip(awardBlock); let users = [];
users = [];
if (origTitle) { if (origTitle) {
users = origTitle.trim().split(FROM_SENTENCE_REGEX); users = origTitle.trim().split(FROM_SENTENCE_REGEX);
} }
...@@ -248,133 +385,134 @@ var emojiAliases = require('emoji-aliases'); ...@@ -248,133 +385,134 @@ var emojiAliases = require('emoji-aliases');
return awardBlock return awardBlock
.attr('title', this.toSentence(users)) .attr('title', this.toSentence(users))
.tooltip('fixTitle'); .tooltip('fixTitle');
}; };
AwardsHandler.prototype.createEmoji_ = function(votesBlock, emoji) { AwardsHandler
var $emojiButton, buttonHtml, emojiCssClass; .prototype
emojiCssClass = this.resolveNameToCssClass(emoji); .createAwardButtonForVotesBlock = function createAwardButtonForVotesBlock(votesBlock, emojiName) {
buttonHtml = "<button class='btn award-control js-emoji-btn has-tooltip active' title='You' data-placement='bottom'> <div class='icon emoji-icon " + emojiCssClass + "' data-emoji='" + emoji + "'></div> <span class='award-control-text js-counter'>1</span> </button>"; const buttonHtml = `
$emojiButton = $(buttonHtml); <button class="btn award-control js-emoji-btn has-tooltip active" title="You" data-placement="bottom">
$emojiButton.insertBefore(votesBlock.find('.js-award-holder')).find('.emoji-icon').data('emoji', emoji); ${glEmojiTag(emojiName)}
<span class="award-control-text js-counter">1</span>
</button>
`;
const $emojiButton = $(buttonHtml);
$emojiButton.insertBefore(votesBlock.find('.js-award-holder')).find('.emoji-icon').data('name', emojiName);
this.animateEmoji($emojiButton); this.animateEmoji($emojiButton);
$('.award-control').tooltip(); $('.award-control').tooltip();
return votesBlock.removeClass('current'); votesBlock.removeClass('current');
}; };
AwardsHandler.prototype.animateEmoji = function($emoji) { AwardsHandler.prototype.animateEmoji = function animateEmoji($emoji) {
var className = 'pulse animated once short'; const className = 'pulse animated once short';
$emoji.addClass(className); $emoji.addClass(className);
$emoji.on('webkitAnimationEnd animationEnd', function() { this.registerEventListener('on', $emoji, animationEndEventString, (e) => {
$(this).removeClass(className); $(e.currentTarget).removeClass(className);
}); });
}; };
AwardsHandler.prototype.createEmoji = function(votesBlock, emoji) { AwardsHandler.prototype.createEmoji = function createEmoji(votesBlock, emoji) {
if ($('.emoji-menu').length) { if ($('.emoji-menu').length) {
return this.createEmoji_(votesBlock, emoji); this.createAwardButtonForVotesBlock(votesBlock, emoji);
}
return this.createEmojiMenu(this.getAwardMenuUrl(), (function(_this) {
return function() {
return _this.createEmoji_(votesBlock, emoji);
};
})(this));
};
AwardsHandler.prototype.getAwardMenuUrl = function() {
return gon.award_menu_url;
};
AwardsHandler.prototype.resolveNameToCssClass = function(emoji) {
var emojiIcon, unicodeName;
emojiIcon = $(".emoji-menu-content [data-emoji='" + emoji + "']");
if (emojiIcon.length > 0) {
unicodeName = emojiIcon.data('unicode-name');
} else {
// Find by alias
unicodeName = $(".emoji-menu-content [data-aliases*=':" + emoji + ":']").data('unicode-name');
} }
return "emoji-" + unicodeName; this.createEmojiMenu(() => {
}; this.createAwardButtonForVotesBlock(votesBlock, emoji);
});
};
AwardsHandler.prototype.postEmoji = function(awardUrl, emoji, callback) { AwardsHandler.prototype.postEmoji = function postEmoji(awardUrl, emoji, callback) {
return $.post(awardUrl, { return $.post(awardUrl, {
name: emoji name: emoji,
}, function(data) { }, (data) => {
if (data.ok) { if (data.ok) {
return callback(); callback();
} }
}); });
}; };
AwardsHandler.prototype.findEmojiIcon = function(votesBlock, emoji) { AwardsHandler.prototype.findEmojiIcon = function findEmojiIcon(votesBlock, emoji) {
return votesBlock.find(".js-emoji-btn [data-emoji='" + emoji + "']"); return votesBlock.find(`.js-emoji-btn [data-name="${emoji}"]`);
}; };
AwardsHandler.prototype.scrollToAwards = function() { AwardsHandler.prototype.scrollToAwards = function scrollToAwards() {
var options; const options = {
options = { scrollTop: $('.awards').offset().top - 110,
scrollTop: $('.awards').offset().top - 110
}; };
return $('body, html').animate(options, 200); return $('body, html').animate(options, 200);
}; };
AwardsHandler.prototype.normilizeEmojiName = function(emoji) { AwardsHandler.prototype.normalizeEmojiName = function normalizeEmojiName(emoji) {
return this.aliases[emoji] || emoji; return Object.prototype.hasOwnProperty.call(this.aliases, emoji) ? this.aliases[emoji] : emoji;
}; };
AwardsHandler.prototype.addEmojiToFrequentlyUsedList = function(emoji) { AwardsHandler
var frequentlyUsedEmojis; .prototype
frequentlyUsedEmojis = this.getFrequentlyUsedEmojis(); .addEmojiToFrequentlyUsedList = function addEmojiToFrequentlyUsedList(emoji) {
const frequentlyUsedEmojis = this.getFrequentlyUsedEmojis();
frequentlyUsedEmojis.push(emoji); frequentlyUsedEmojis.push(emoji);
Cookies.set('frequently_used_emojis', frequentlyUsedEmojis.join(','), { expires: 365 }); Cookies.set('frequently_used_emojis', frequentlyUsedEmojis.join(','), { expires: 365 });
}; };
AwardsHandler.prototype.getFrequentlyUsedEmojis = function() { AwardsHandler.prototype.getFrequentlyUsedEmojis = function getFrequentlyUsedEmojis() {
var frequentlyUsedEmojis; const frequentlyUsedEmojis = (Cookies.get('frequently_used_emojis') || '').split(',');
frequentlyUsedEmojis = (Cookies.get('frequently_used_emojis') || '').split(',');
return _.compact(_.uniq(frequentlyUsedEmojis)); return _.compact(_.uniq(frequentlyUsedEmojis));
}; };
AwardsHandler.prototype.renderFrequentlyUsedBlock = function() { AwardsHandler.prototype.renderFrequentlyUsedBlock = function renderFrequentlyUsedBlock() {
var emoji, frequentlyUsedEmojis, i, len, ul;
if (Cookies.get('frequently_used_emojis')) { if (Cookies.get('frequently_used_emojis')) {
frequentlyUsedEmojis = this.getFrequentlyUsedEmojis(); const frequentlyUsedEmojis = this.getFrequentlyUsedEmojis();
ul = $("<ul class='clearfix emoji-menu-list frequent-emojis'>"); const ul = $('<ul class="clearfix emoji-menu-list frequent-emojis">');
for (i = 0, len = frequentlyUsedEmojis.length; i < len; i += 1) { for (let i = 0, len = frequentlyUsedEmojis.length; i < len; i += 1) {
emoji = frequentlyUsedEmojis[i]; const emoji = frequentlyUsedEmojis[i];
$(".emoji-menu-content [data-emoji='" + emoji + "']").closest('li').clone().appendTo(ul); $(`.emoji-menu-content [data-name="${emoji}"]`).closest('li').clone().appendTo(ul);
} }
$('.emoji-menu-content').prepend(ul).prepend($('<h5>').text('Frequently used')); $('.emoji-menu-content').prepend(ul).prepend($('<h5>').text('Frequently used'));
} }
return this.frequentEmojiBlockRendered = true; this.frequentEmojiBlockRendered = true;
}; };
AwardsHandler.prototype.setupSearch = function() { AwardsHandler.prototype.setupSearch = function setupSearch() {
return $('input.emoji-search').on('keyup', (function(_this) { this.registerEventListener('on', $('input.emoji-search'), 'input', (e) => {
return function(ev) { const term = $(e.target).val().trim();
var found_emojis, h5, term, ul;
term = $(ev.target).val();
// Clean previous search results // Clean previous search results
$('ul.emoji-menu-search, h5.emoji-search').remove(); $('ul.emoji-menu-search, h5.emoji-search').remove();
if (term) { if (term.length > 0) {
// Generate a search result block // Generate a search result block
h5 = $('<h5 class="emoji-search" />').text('Search results'); const h5 = $('<h5 class="emoji-search" />').text('Search results');
found_emojis = _this.searchEmojis(term).show(); const foundEmojis = this.searchEmojis(term).show();
ul = $('<ul>').addClass('emoji-menu-list emoji-menu-search').append(found_emojis); const ul = $('<ul>').addClass('emoji-menu-list emoji-menu-search').append(foundEmojis);
$('.emoji-menu-content ul, .emoji-menu-content h5').hide(); $('.emoji-menu-content ul, .emoji-menu-content h5').hide();
return $('.emoji-menu-content').append(h5).append(ul); $('.emoji-menu-content').append(h5).append(ul);
} else { } else {
return $('.emoji-menu-content').children().show(); $('.emoji-menu-content').children().show();
} }
}; });
})(this)); };
};
AwardsHandler.prototype.searchEmojis = function(term) { AwardsHandler.prototype.searchEmojis = function searchEmojis(term) {
return $(".emoji-menu-list:not(.frequent-emojis) [data-emoji*='" + term + "']").closest('li').clone(); 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}"]`)),
$([]),
);
return $matchingElements.closest('li').clone();
};
AwardsHandler.prototype.destroy = function destroy() {
this.eventListeners.forEach((entry) => {
entry.element.off.call(entry.element, ...entry.args);
});
$('.emoji-menu').remove();
};
return AwardsHandler; module.exports = AwardsHandler;
})();
}).call(window);
const installCustomElements = require('document-register-element');
const emojiMap = require('emoji-map');
const emojiAliases = require('emoji-aliases');
const generatedUnicodeSupportMap = require('./gl_emoji/unicode_support_map');
const spreadString = require('./gl_emoji/spread_string');
installCustomElements(window);
function emojiImageTag(name, src) {
return `<img class="emoji" title=":${name}:" alt=":${name}:" src="${src}" width="20" height="20" align="absmiddle" />`;
}
function assembleFallbackImageSrc(inputName) {
const name = Object.prototype.hasOwnProperty.call(emojiAliases, inputName) ?
emojiAliases[inputName] : inputName;
const 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);
const name = Object.prototype.hasOwnProperty.call(emojiAliases, inputName) ?
emojiAliases[inputName] : inputName;
const 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}"
>
${contents}
</gl-emoji>
`;
}
// 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 && spreadString(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) {
return spreadString(emojiUnicode)[0].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;
spreadString(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);
}
const GlEmojiElementProto = Object.create(HTMLElement.prototype);
GlEmojiElementProto.createdCallback = function createdCallback() {
const emojiUnicode = this.textContent.trim();
const {
name,
unicodeVersion,
fallbackSrc,
fallbackSpriteClass,
} = this.dataset;
const isEmojiUnicode = this.childNodes && Array.prototype.every.call(
this.childNodes,
childNode => childNode.nodeType === 3,
);
const hasImageFallback = fallbackSrc && fallbackSrc.length > 0;
const hasCssSpriteFalback = fallbackSpriteClass && fallbackSpriteClass.length > 0;
if (
isEmojiUnicode &&
!isEmojiUnicodeSupported(generatedUnicodeSupportMap, emojiUnicode, unicodeVersion)
) {
// CSS sprite fallback takes precedence over image fallback
if (hasCssSpriteFalback) {
// IE 11 doesn't like adding multiple at once :(
this.classList.add('emoji-icon');
this.classList.add(fallbackSpriteClass);
} else if (hasImageFallback) {
this.innerHTML = emojiImageTag(name, fallbackSrc);
} else {
const src = assembleFallbackImageSrc(name);
this.innerHTML = emojiImageTag(name, src);
}
}
};
document.registerElement('gl-emoji', {
prototype: GlEmojiElementProto,
});
module.exports = {
emojiImageTag,
glEmojiTag,
isEmojiUnicodeSupported,
isFlagEmoji,
isKeycapEmoji,
isSkinToneComboEmoji,
isHorceRacingSkinToneComboEmoji,
isPersonZwjEmoji,
};
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/charCodeAt#Fixing_charCodeAt()_to_handle_non-Basic-Multilingual-Plane_characters_if_their_presence_earlier_in_the_string_is_known
function knownCharCodeAt(givenString, index) {
const str = `${givenString}`;
const end = str.length;
const surrogatePairs = /[\uD800-\uDBFF][\uDC00-\uDFFF]/g;
let idx = index;
while ((surrogatePairs.exec(str)) != null) {
const li = surrogatePairs.lastIndex;
if (li - 2 < idx) {
idx += 1;
} else {
break;
}
}
if (idx >= end || idx < 0) {
return NaN;
}
const code = str.charCodeAt(idx);
let high;
let low;
if (code >= 0xD800 && code <= 0xDBFF) {
high = code;
low = str.charCodeAt(idx + 1);
// Go one further, since one of the "characters" is part of a surrogate pair
return ((high - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000;
}
return code;
}
// See http://stackoverflow.com/a/38901550/796832
// ES5/PhantomJS compatible version of spreading a string
//
// [...'foo'] -> ['f', 'o', 'o']
// [...'🖐🏿'] -> ['🖐', '🏿']
function spreadString(str) {
const arr = [];
let i = 0;
while (!isNaN(knownCharCodeAt(str, i))) {
const codePoint = knownCharCodeAt(str, i);
arr.push(String.fromCodePoint(codePoint));
i += 1;
}
return arr;
}
module.exports = spreadString;
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 testUnicodeSupportMap(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;
}
let unicodeSupportMap;
const 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 = testUnicodeSupportMap(unicodeSupportTestMap);
window.localStorage.setItem('gl-emoji-user-agent', navigator.userAgent);
window.localStorage.setItem('gl-emoji-unicode-support-map', JSON.stringify(unicodeSupportMap));
}
module.exports = unicodeSupportMap;
...@@ -49,6 +49,9 @@ require('./lib/utils/common_utils'); ...@@ -49,6 +49,9 @@ require('./lib/utils/common_utils');
'img.emoji'(el, text) { 'img.emoji'(el, text) {
return el.getAttribute('alt'); return el.getAttribute('alt');
}, },
'gl-emoji'(el, text) {
return `:${el.getAttribute('data-name')}:`;
},
}, },
ImageLinkFilter: { ImageLinkFilter: {
'a.no-attachment-icon'(el, text) { 'a.no-attachment-icon'(el, text) {
......
require('string.prototype.codepointat');
require('string.fromcodepoint');
/* eslint-disable func-names, space-before-function-paren, no-template-curly-in-string, comma-dangle, object-shorthand, quotes, dot-notation, no-else-return, one-var, no-var, no-underscore-dangle, one-var-declaration-per-line, no-param-reassign, no-useless-escape, prefer-template, consistent-return, wrap-iife, prefer-arrow-callback, camelcase, no-unused-vars, no-useless-return, vars-on-top, max-len */ /* eslint-disable func-names, space-before-function-paren, no-template-curly-in-string, comma-dangle, object-shorthand, quotes, dot-notation, no-else-return, one-var, no-var, no-underscore-dangle, one-var-declaration-per-line, no-param-reassign, no-useless-escape, prefer-template, consistent-return, wrap-iife, prefer-arrow-callback, camelcase, no-unused-vars, no-useless-return, vars-on-top, max-len */
const emojiMap = require('emoji-map');
const emojiAliases = require('emoji-aliases');
const glEmoji = require('./behaviors/gl_emoji');
const glEmojiTag = glEmoji.glEmojiTag;
// Creates the variables for setting up GFM auto-completion // Creates the variables for setting up GFM auto-completion
(function() { (function() {
if (window.gl == null) { if (window.gl == null) {
...@@ -26,7 +32,12 @@ ...@@ -26,7 +32,12 @@
}, },
// Emoji // Emoji
Emoji: { Emoji: {
template: '<li>${name} <img alt="${name}" height="20" src="${path}" width="20" /></li>' templateFunction: function(name) {
return `<li>
${name} ${glEmojiTag(name)}
</li>
`;
}
}, },
// Team Members // Team Members
Members: { Members: {
...@@ -113,7 +124,7 @@ ...@@ -113,7 +124,7 @@
$input.atwho({ $input.atwho({
at: ':', at: ':',
displayTpl: function(value) { displayTpl: function(value) {
return value.path != null ? this.Emoji.template : this.Loading.template; return value && value.name ? this.Emoji.templateFunction(value.name) : this.Loading.template;
}.bind(this), }.bind(this),
insertTpl: ':${name}:', insertTpl: ':${name}:',
skipSpecialCharacterTest: true, skipSpecialCharacterTest: true,
...@@ -355,6 +366,8 @@ ...@@ -355,6 +366,8 @@
this.isLoadingData[at] = true; this.isLoadingData[at] = true;
if (this.cachedData[at]) { if (this.cachedData[at]) {
this.loadData($input, at, this.cachedData[at]); this.loadData($input, at, this.cachedData[at]);
} else if (this.atTypeMap[at] === 'emojis') {
this.loadData($input, at, Object.keys(emojiMap).concat(Object.keys(emojiAliases)));
} else { } else {
$.getJSON(this.dataSources[this.atTypeMap[at]], (data) => { $.getJSON(this.dataSources[this.atTypeMap[at]], (data) => {
this.loadData($input, at, data); this.loadData($input, at, data);
......
...@@ -3,7 +3,6 @@ ...@@ -3,7 +3,6 @@
/* global Cookies */ /* global Cookies */
/* global Flash */ /* global Flash */
/* global ConfirmDangerModal */ /* global ConfirmDangerModal */
/* global AwardsHandler */
/* global Aside */ /* global Aside */
import jQuery from 'jquery'; import jQuery from 'jquery';
...@@ -19,6 +18,15 @@ require('mousetrap/plugins/pause/mousetrap-pause'); ...@@ -19,6 +18,15 @@ require('mousetrap/plugins/pause/mousetrap-pause');
require('vendor/fuzzaldrin-plus'); require('vendor/fuzzaldrin-plus');
require('es6-promise').polyfill(); require('es6-promise').polyfill();
// extensions
require('./extensions/string');
require('./extensions/array');
require('./extensions/custom_event');
require('./extensions/element');
require('./extensions/jquery');
require('./extensions/object');
require('es6-promise').polyfill();
// expose common libraries as globals (TODO: remove these) // expose common libraries as globals (TODO: remove these)
window.jQuery = jQuery; window.jQuery = jQuery;
window.$ = jQuery; window.$ = jQuery;
...@@ -61,13 +69,6 @@ require('./templates/issuable_template_selectors'); ...@@ -61,13 +69,6 @@ require('./templates/issuable_template_selectors');
require('./commit/file.js'); require('./commit/file.js');
require('./commit/image_file.js'); require('./commit/image_file.js');
// extensions
require('./extensions/array');
require('./extensions/custom_event');
require('./extensions/element');
require('./extensions/jquery');
require('./extensions/object');
// lib/utils // lib/utils
require('./lib/utils/animate'); require('./lib/utils/animate');
require('./lib/utils/bootstrap_linked_tabs'); require('./lib/utils/bootstrap_linked_tabs');
...@@ -99,7 +100,7 @@ require('./ajax_loading_spinner'); ...@@ -99,7 +100,7 @@ require('./ajax_loading_spinner');
require('./api'); require('./api');
require('./aside'); require('./aside');
require('./autosave'); require('./autosave');
require('./awards_handler'); const AwardsHandler = require('./awards_handler');
require('./breakpoints'); require('./breakpoints');
require('./broadcast_message'); require('./broadcast_message');
require('./build'); require('./build');
......
...@@ -44,5 +44,6 @@ ...@@ -44,5 +44,6 @@
@import "framework/images.scss"; @import "framework/images.scss";
@import "framework/broadcast-messages"; @import "framework/broadcast-messages";
@import "framework/emojis.scss"; @import "framework/emojis.scss";
@import "framework/emoji-sprites.scss";
@import "framework/icons.scss"; @import "framework/icons.scss";
@import "framework/snippets.scss"; @import "framework/snippets.scss";
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
.emoji-menu { .emoji-menu {
position: absolute; position: absolute;
top: 0;
margin-top: 3px; margin-top: 3px;
padding: $gl-padding; padding: $gl-padding;
z-index: 9; z-index: 9;
...@@ -20,7 +21,7 @@ ...@@ -20,7 +21,7 @@
opacity: 0; opacity: 0;
transform: scale(.2); transform: scale(.2);
transform-origin: 0 -45px; transform-origin: 0 -45px;
transition: .3s cubic-bezier(.87,-.41,.19,1.44); transition: .3s cubic-bezier(.67,.06,.19,1.44);
transition-property: transform, opacity; transition-property: transform, opacity;
&.is-aligned-right { &.is-aligned-right {
...@@ -47,12 +48,13 @@ ...@@ -47,12 +48,13 @@
} }
.emoji-menu-list { .emoji-menu-list {
list-style: none;
padding-left: 0;
margin-bottom: 0; margin-bottom: 0;
padding-left: 0;
list-style: none;
} }
.emoji-menu-list-item { .emoji-menu-list-item {
float: left;
padding: 3px; padding: 3px;
margin-left: 1px; margin-left: 1px;
margin-right: 1px; margin-right: 1px;
...@@ -97,6 +99,8 @@ ...@@ -97,6 +99,8 @@
padding: 5px 6px; padding: 5px 6px;
outline: 0; outline: 0;
line-height: 1;
&.disabled { &.disabled {
cursor: default; cursor: default;
......
This source diff could not be displayed because it is too large. You can view the blob instead.
.emoji-0023-20E3 { background-position: 0 0; } gl-emoji {
.emoji-002A-20E3 { background-position: -20px 0; } display: inline-block;
.emoji-0030-20E3 { background-position: 0 -20px; } display: inline-flex;
.emoji-0031-20E3 { background-position: -20px -20px; } vertical-align: middle;
.emoji-0032-20E3 { background-position: -40px 0; } font-size: 1.5em;
.emoji-0033-20E3 { background-position: -40px -20px; }
.emoji-0034-20E3 { background-position: 0 -40px; }
.emoji-0035-20E3 { background-position: -20px -40px; }
.emoji-0036-20E3 { background-position: -40px -40px; }
.emoji-0037-20E3 { background-position: -60px 0; }
.emoji-0038-20E3 { background-position: -60px -20px; }
.emoji-0039-20E3 { background-position: -60px -40px; }
.emoji-00A9 { background-position: 0 -60px; }
.emoji-00AE { background-position: -20px -60px; }
.emoji-1F004 { background-position: -40px -60px; }
.emoji-1F0CF { background-position: -60px -60px; }
.emoji-1F170 { background-position: -80px 0; }
.emoji-1F171 { background-position: -80px -20px; }
.emoji-1F17E { background-position: -80px -40px; }
.emoji-1F17F { background-position: -80px -60px; }
.emoji-1F18E { background-position: 0 -80px; }
.emoji-1F191 { background-position: -20px -80px; }
.emoji-1F192 { background-position: -40px -80px; }
.emoji-1F193 { background-position: -60px -80px; }
.emoji-1F194 { background-position: -80px -80px; }
.emoji-1F195 { background-position: -100px 0; }
.emoji-1F196 { background-position: -100px -20px; }
.emoji-1F197 { background-position: -100px -40px; }
.emoji-1F198 { background-position: -100px -60px; }
.emoji-1F199 { background-position: -100px -80px; }
.emoji-1F19A { background-position: 0 -100px; }
.emoji-1F1E6-1F1E8 { background-position: -20px -100px; }
.emoji-1F1E6-1F1E9 { background-position: -40px -100px; }
.emoji-1F1E6-1F1EA { background-position: -60px -100px; }
.emoji-1F1E6-1F1EB { background-position: -80px -100px; }
.emoji-1F1E6-1F1EC { background-position: -100px -100px; }
.emoji-1F1E6-1F1EE { background-position: -120px 0; }
.emoji-1F1E6-1F1F1 { background-position: -120px -20px; }
.emoji-1F1E6-1F1F2 { background-position: -120px -40px; }
.emoji-1F1E6-1F1F4 { background-position: -120px -60px; }
.emoji-1F1E6-1F1F6 { background-position: -120px -80px; }
.emoji-1F1E6-1F1F7 { background-position: -120px -100px; }
.emoji-1F1E6-1F1F8 { background-position: 0 -120px; }
.emoji-1F1E6-1F1F9 { background-position: -20px -120px; }
.emoji-1F1E6-1F1FA { background-position: -40px -120px; }
.emoji-1F1E6-1F1FC { background-position: -60px -120px; }
.emoji-1F1E6-1F1FD { background-position: -80px -120px; }
.emoji-1F1E6-1F1FF { background-position: -100px -120px; }
.emoji-1F1E7-1F1E6 { background-position: -120px -120px; }
.emoji-1F1E7-1F1E7 { background-position: -140px 0; }
.emoji-1F1E7-1F1E9 { background-position: -140px -20px; }
.emoji-1F1E7-1F1EA { background-position: -140px -40px; }
.emoji-1F1E7-1F1EB { background-position: -140px -60px; }
.emoji-1F1E7-1F1EC { background-position: -140px -80px; }
.emoji-1F1E7-1F1ED { background-position: -140px -100px; }
.emoji-1F1E7-1F1EE { background-position: -140px -120px; }
.emoji-1F1E7-1F1EF { background-position: 0 -140px; }
.emoji-1F1E7-1F1F1 { background-position: -20px -140px; }
.emoji-1F1E7-1F1F2 { background-position: -40px -140px; }
.emoji-1F1E7-1F1F3 { background-position: -60px -140px; }
.emoji-1F1E7-1F1F4 { background-position: -80px -140px; }
.emoji-1F1E7-1F1F6 { background-position: -100px -140px; }
.emoji-1F1E7-1F1F7 { background-position: -120px -140px; }
.emoji-1F1E7-1F1F8 { background-position: -140px -140px; }
.emoji-1F1E7-1F1F9 { background-position: -160px 0; }
.emoji-1F1E7-1F1FB { background-position: -160px -20px; }
.emoji-1F1E7-1F1FC { background-position: -160px -40px; }
.emoji-1F1E7-1F1FE { background-position: -160px -60px; }
.emoji-1F1E7-1F1FF { background-position: -160px -80px; }
.emoji-1F1E8-1F1E6 { background-position: -160px -100px; }
.emoji-1F1E8-1F1E8 { background-position: -160px -120px; }
.emoji-1F1E8-1F1E9 { background-position: -160px -140px; }
.emoji-1F1E8-1F1EB { background-position: 0 -160px; }
.emoji-1F1E8-1F1EC { background-position: -20px -160px; }
.emoji-1F1E8-1F1ED { background-position: -40px -160px; }
.emoji-1F1E8-1F1EE { background-position: -60px -160px; }
.emoji-1F1E8-1F1F0 { background-position: -80px -160px; }
.emoji-1F1E8-1F1F1 { background-position: -100px -160px; }
.emoji-1F1E8-1F1F2 { background-position: -120px -160px; }
.emoji-1F1E8-1F1F3 { background-position: -140px -160px; }
.emoji-1F1E8-1F1F4 { background-position: -160px -160px; }
.emoji-1F1E8-1F1F5 { background-position: -180px 0; }
.emoji-1F1E8-1F1F7 { background-position: -180px -20px; }
.emoji-1F1E8-1F1FA { background-position: -180px -40px; }
.emoji-1F1E8-1F1FB { background-position: -180px -60px; }
.emoji-1F1E8-1F1FC { background-position: -180px -80px; }
.emoji-1F1E8-1F1FD { background-position: -180px -100px; }
.emoji-1F1E8-1F1FE { background-position: -180px -120px; }
.emoji-1F1E8-1F1FF { background-position: -180px -140px; }
.emoji-1F1E9-1F1EA { background-position: -180px -160px; }
.emoji-1F1E9-1F1EC { background-position: 0 -180px; }
.emoji-1F1E9-1F1EF { background-position: -20px -180px; }
.emoji-1F1E9-1F1F0 { background-position: -40px -180px; }
.emoji-1F1E9-1F1F2 { background-position: -60px -180px; }
.emoji-1F1E9-1F1F4 { background-position: -80px -180px; }
.emoji-1F1E9-1F1FF { background-position: -100px -180px; }
.emoji-1F1EA-1F1E6 { background-position: -120px -180px; }
.emoji-1F1EA-1F1E8 { background-position: -140px -180px; }
.emoji-1F1EA-1F1EA { background-position: -160px -180px; }
.emoji-1F1EA-1F1EC { background-position: -180px -180px; }
.emoji-1F1EA-1F1ED { background-position: -200px 0; }
.emoji-1F1EA-1F1F7 { background-position: -200px -20px; }
.emoji-1F1EA-1F1F8 { background-position: -200px -40px; }
.emoji-1F1EA-1F1F9 { background-position: -200px -60px; }
.emoji-1F1EA-1F1FA { background-position: -200px -80px; }
.emoji-1F1EB-1F1EE { background-position: -200px -100px; }
.emoji-1F1EB-1F1EF { background-position: -200px -120px; }
.emoji-1F1EB-1F1F0 { background-position: -200px -140px; }
.emoji-1F1EB-1F1F2 { background-position: -200px -160px; }
.emoji-1F1EB-1F1F4 { background-position: -200px -180px; }
.emoji-1F1EB-1F1F7 { background-position: 0 -200px; }
.emoji-1F1EC-1F1E6 { background-position: -20px -200px; }
.emoji-1F1EC-1F1E7 { background-position: -40px -200px; }
.emoji-1F1EC-1F1E9 { background-position: -60px -200px; }
.emoji-1F1EC-1F1EA { background-position: -80px -200px; }
.emoji-1F1EC-1F1EB { background-position: -100px -200px; }
.emoji-1F1EC-1F1EC { background-position: -120px -200px; }
.emoji-1F1EC-1F1ED { background-position: -140px -200px; }
.emoji-1F1EC-1F1EE { background-position: -160px -200px; }
.emoji-1F1EC-1F1F1 { background-position: -180px -200px; }
.emoji-1F1EC-1F1F2 { background-position: -200px -200px; }
.emoji-1F1EC-1F1F3 { background-position: -220px 0; }
.emoji-1F1EC-1F1F5 { background-position: -220px -20px; }
.emoji-1F1EC-1F1F6 { background-position: -220px -40px; }
.emoji-1F1EC-1F1F7 { background-position: -220px -60px; }
.emoji-1F1EC-1F1F8 { background-position: -220px -80px; }
.emoji-1F1EC-1F1F9 { background-position: -220px -100px; }
.emoji-1F1EC-1F1FA { background-position: -220px -120px; }
.emoji-1F1EC-1F1FC { background-position: -220px -140px; }
.emoji-1F1EC-1F1FE { background-position: -220px -160px; }
.emoji-1F1ED-1F1F0 { background-position: -220px -180px; }
.emoji-1F1ED-1F1F2 { background-position: -220px -200px; }
.emoji-1F1ED-1F1F3 { background-position: 0 -220px; }
.emoji-1F1ED-1F1F7 { background-position: -20px -220px; }
.emoji-1F1ED-1F1F9 { background-position: -40px -220px; }
.emoji-1F1ED-1F1FA { background-position: -60px -220px; }
.emoji-1F1EE-1F1E8 { background-position: -80px -220px; }
.emoji-1F1EE-1F1E9 { background-position: -100px -220px; }
.emoji-1F1EE-1F1EA { background-position: -120px -220px; }
.emoji-1F1EE-1F1F1 { background-position: -140px -220px; }
.emoji-1F1EE-1F1F2 { background-position: -160px -220px; }
.emoji-1F1EE-1F1F3 { background-position: -180px -220px; }
.emoji-1F1EE-1F1F4 { background-position: -200px -220px; }
.emoji-1F1EE-1F1F6 { background-position: -220px -220px; }
.emoji-1F1EE-1F1F7 { background-position: -240px 0; }
.emoji-1F1EE-1F1F8 { background-position: -240px -20px; }
.emoji-1F1EE-1F1F9 { background-position: -240px -40px; }
.emoji-1F1EF-1F1EA { background-position: -240px -60px; }
.emoji-1F1EF-1F1F2 { background-position: -240px -80px; }
.emoji-1F1EF-1F1F4 { background-position: -240px -100px; }
.emoji-1F1EF-1F1F5 { background-position: -240px -120px; }
.emoji-1F1F0-1F1EA { background-position: -240px -140px; }
.emoji-1F1F0-1F1EC { background-position: -240px -160px; }
.emoji-1F1F0-1F1ED { background-position: -240px -180px; }
.emoji-1F1F0-1F1EE { background-position: -240px -200px; }
.emoji-1F1F0-1F1F2 { background-position: -240px -220px; }
.emoji-1F1F0-1F1F3 { background-position: 0 -240px; }
.emoji-1F1F0-1F1F5 { background-position: -20px -240px; }
.emoji-1F1F0-1F1F7 { background-position: -40px -240px; }
.emoji-1F1F0-1F1FC { background-position: -60px -240px; }
.emoji-1F1F0-1F1FE { background-position: -80px -240px; }
.emoji-1F1F0-1F1FF { background-position: -100px -240px; }
.emoji-1F1F1-1F1E6 { background-position: -120px -240px; }
.emoji-1F1F1-1F1E7 { background-position: -140px -240px; }
.emoji-1F1F1-1F1E8 { background-position: -160px -240px; }
.emoji-1F1F1-1F1EE { background-position: -180px -240px; }
.emoji-1F1F1-1F1F0 { background-position: -200px -240px; }
.emoji-1F1F1-1F1F7 { background-position: -220px -240px; }
.emoji-1F1F1-1F1F8 { background-position: -240px -240px; }
.emoji-1F1F1-1F1F9 { background-position: -260px 0; }
.emoji-1F1F1-1F1FA { background-position: -260px -20px; }
.emoji-1F1F1-1F1FB { background-position: -260px -40px; }
.emoji-1F1F1-1F1FE { background-position: -260px -60px; }
.emoji-1F1F2-1F1E6 { background-position: -260px -80px; }
.emoji-1F1F2-1F1E8 { background-position: -260px -100px; }
.emoji-1F1F2-1F1E9 { background-position: -260px -120px; }
.emoji-1F1F2-1F1EA { background-position: -260px -140px; }
.emoji-1F1F2-1F1EB { background-position: -260px -160px; }
.emoji-1F1F2-1F1EC { background-position: -260px -180px; }
.emoji-1F1F2-1F1ED { background-position: -260px -200px; }
.emoji-1F1F2-1F1F0 { background-position: -260px -220px; }
.emoji-1F1F2-1F1F1 { background-position: -260px -240px; }
.emoji-1F1F2-1F1F2 { background-position: 0 -260px; }
.emoji-1F1F2-1F1F3 { background-position: -20px -260px; }
.emoji-1F1F2-1F1F4 { background-position: -40px -260px; }
.emoji-1F1F2-1F1F5 { background-position: -60px -260px; }
.emoji-1F1F2-1F1F6 { background-position: -80px -260px; }
.emoji-1F1F2-1F1F7 { background-position: -100px -260px; }
.emoji-1F1F2-1F1F8 { background-position: -120px -260px; }
.emoji-1F1F2-1F1F9 { background-position: -140px -260px; }
.emoji-1F1F2-1F1FA { background-position: -160px -260px; }
.emoji-1F1F2-1F1FB { background-position: -180px -260px; }
.emoji-1F1F2-1F1FC { background-position: -200px -260px; }
.emoji-1F1F2-1F1FD { background-position: -220px -260px; }
.emoji-1F1F2-1F1FE { background-position: -240px -260px; }
.emoji-1F1F2-1F1FF { background-position: -260px -260px; }
.emoji-1F1F3-1F1E6 { background-position: -280px 0; }
.emoji-1F1F3-1F1E8 { background-position: -280px -20px; }
.emoji-1F1F3-1F1EA { background-position: -280px -40px; }
.emoji-1F1F3-1F1EB { background-position: -280px -60px; }
.emoji-1F1F3-1F1EC { background-position: -280px -80px; }
.emoji-1F1F3-1F1EE { background-position: -280px -100px; }
.emoji-1F1F3-1F1F1 { background-position: -280px -120px; }
.emoji-1F1F3-1F1F4 { background-position: -280px -140px; }
.emoji-1F1F3-1F1F5 { background-position: -280px -160px; }
.emoji-1F1F3-1F1F7 { background-position: -280px -180px; }
.emoji-1F1F3-1F1FA { background-position: -280px -200px; }
.emoji-1F1F3-1F1FF { background-position: -280px -220px; }
.emoji-1F1F4-1F1F2 { background-position: -280px -240px; }
.emoji-1F1F5-1F1E6 { background-position: -280px -260px; }
.emoji-1F1F5-1F1EA { background-position: 0 -280px; }
.emoji-1F1F5-1F1EB { background-position: -20px -280px; }
.emoji-1F1F5-1F1EC { background-position: -40px -280px; }
.emoji-1F1F5-1F1ED { background-position: -60px -280px; }
.emoji-1F1F5-1F1F0 { background-position: -80px -280px; }
.emoji-1F1F5-1F1F1 { background-position: -100px -280px; }
.emoji-1F1F5-1F1F2 { background-position: -120px -280px; }
.emoji-1F1F5-1F1F3 { background-position: -140px -280px; }
.emoji-1F1F5-1F1F7 { background-position: -160px -280px; }
.emoji-1F1F5-1F1F8 { background-position: -180px -280px; }
.emoji-1F1F5-1F1F9 { background-position: -200px -280px; }
.emoji-1F1F5-1F1FC { background-position: -220px -280px; }
.emoji-1F1F5-1F1FE { background-position: -240px -280px; }
.emoji-1F1F6-1F1E6 { background-position: -260px -280px; }
.emoji-1F1F7-1F1EA { background-position: -280px -280px; }
.emoji-1F1F7-1F1F4 { background-position: -300px 0; }
.emoji-1F1F7-1F1F8 { background-position: -300px -20px; }
.emoji-1F1F7-1F1FA { background-position: -300px -40px; }
.emoji-1F1F7-1F1FC { background-position: -300px -60px; }
.emoji-1F1F8-1F1E6 { background-position: -300px -80px; }
.emoji-1F1F8-1F1E7 { background-position: -300px -100px; }
.emoji-1F1F8-1F1E8 { background-position: -300px -120px; }
.emoji-1F1F8-1F1E9 { background-position: -300px -140px; }
.emoji-1F1F8-1F1EA { background-position: -300px -160px; }
.emoji-1F1F8-1F1EC { background-position: -300px -180px; }
.emoji-1F1F8-1F1ED { background-position: -300px -200px; }
.emoji-1F1F8-1F1EE { background-position: -300px -220px; }
.emoji-1F1F8-1F1EF { background-position: -300px -240px; }
.emoji-1F1F8-1F1F0 { background-position: -300px -260px; }
.emoji-1F1F8-1F1F1 { background-position: -300px -280px; }
.emoji-1F1F8-1F1F2 { background-position: 0 -300px; }
.emoji-1F1F8-1F1F3 { background-position: -20px -300px; }
.emoji-1F1F8-1F1F4 { background-position: -40px -300px; }
.emoji-1F1F8-1F1F7 { background-position: -60px -300px; }
.emoji-1F1F8-1F1F8 { background-position: -80px -300px; }
.emoji-1F1F8-1F1F9 { background-position: -100px -300px; }
.emoji-1F1F8-1F1FB { background-position: -120px -300px; }
.emoji-1F1F8-1F1FD { background-position: -140px -300px; }
.emoji-1F1F8-1F1FE { background-position: -160px -300px; }
.emoji-1F1F8-1F1FF { background-position: -180px -300px; }
.emoji-1F1F9-1F1E6 { background-position: -200px -300px; }
.emoji-1F1F9-1F1E8 { background-position: -220px -300px; }
.emoji-1F1F9-1F1E9 { background-position: -240px -300px; }
.emoji-1F1F9-1F1EB { background-position: -260px -300px; }
.emoji-1F1F9-1F1EC { background-position: -280px -300px; }
.emoji-1F1F9-1F1ED { background-position: -300px -300px; }
.emoji-1F1F9-1F1EF { background-position: -320px 0; }
.emoji-1F1F9-1F1F0 { background-position: -320px -20px; }
.emoji-1F1F9-1F1F1 { background-position: -320px -40px; }
.emoji-1F1F9-1F1F2 { background-position: -320px -60px; }
.emoji-1F1F9-1F1F3 { background-position: -320px -80px; }
.emoji-1F1F9-1F1F4 { background-position: -320px -100px; }
.emoji-1F1F9-1F1F7 { background-position: -320px -120px; }
.emoji-1F1F9-1F1F9 { background-position: -320px -140px; }
.emoji-1F1F9-1F1FB { background-position: -320px -160px; }
.emoji-1F1F9-1F1FC { background-position: -320px -180px; }
.emoji-1F1F9-1F1FF { background-position: -320px -200px; }
.emoji-1F1FA-1F1E6 { background-position: -320px -220px; }
.emoji-1F1FA-1F1EC { background-position: -320px -240px; }
.emoji-1F1FA-1F1F2 { background-position: -320px -260px; }
.emoji-1F1FA-1F1F8 { background-position: -320px -280px; }
.emoji-1F1FA-1F1FE { background-position: -320px -300px; }
.emoji-1F1FA-1F1FF { background-position: 0 -320px; }
.emoji-1F1FB-1F1E6 { background-position: -20px -320px; }
.emoji-1F1FB-1F1E8 { background-position: -40px -320px; }
.emoji-1F1FB-1F1EA { background-position: -60px -320px; }
.emoji-1F1FB-1F1EC { background-position: -80px -320px; }
.emoji-1F1FB-1F1EE { background-position: -100px -320px; }
.emoji-1F1FB-1F1F3 { background-position: -120px -320px; }
.emoji-1F1FB-1F1FA { background-position: -140px -320px; }
.emoji-1F1FC-1F1EB { background-position: -160px -320px; }
.emoji-1F1FC-1F1F8 { background-position: -180px -320px; }
.emoji-1F1FD-1F1F0 { background-position: -200px -320px; }
.emoji-1F1FE-1F1EA { background-position: -220px -320px; }
.emoji-1F1FE-1F1F9 { background-position: -240px -320px; }
.emoji-1F1FF-1F1E6 { background-position: -260px -320px; }
.emoji-1F1FF-1F1F2 { background-position: -280px -320px; }
.emoji-1F1FF-1F1FC { background-position: -300px -320px; }
.emoji-1F201 { background-position: -320px -320px; }
.emoji-1F202 { background-position: -340px 0; }
.emoji-1F21A { background-position: -340px -20px; }
.emoji-1F22F { background-position: -340px -40px; }
.emoji-1F232 { background-position: -340px -60px; }
.emoji-1F233 { background-position: -340px -80px; }
.emoji-1F234 { background-position: -340px -100px; }
.emoji-1F235 { background-position: -340px -120px; }
.emoji-1F236 { background-position: -340px -140px; }
.emoji-1F237 { background-position: -340px -160px; }
.emoji-1F238 { background-position: -340px -180px; }
.emoji-1F239 { background-position: -340px -200px; }
.emoji-1F23A { background-position: -340px -220px; }
.emoji-1F250 { background-position: -340px -240px; }
.emoji-1F251 { background-position: -340px -260px; }
.emoji-1F300 { background-position: -340px -280px; }
.emoji-1F301 { background-position: -340px -300px; }
.emoji-1F302 { background-position: -340px -320px; }
.emoji-1F303 { background-position: 0 -340px; }
.emoji-1F304 { background-position: -20px -340px; }
.emoji-1F305 { background-position: -40px -340px; }
.emoji-1F306 { background-position: -60px -340px; }
.emoji-1F307 { background-position: -80px -340px; }
.emoji-1F308 { background-position: -100px -340px; }
.emoji-1F309 { background-position: -120px -340px; }
.emoji-1F30A { background-position: -140px -340px; }
.emoji-1F30B { background-position: -160px -340px; }
.emoji-1F30C { background-position: -180px -340px; }
.emoji-1F30D { background-position: -200px -340px; }
.emoji-1F30E { background-position: -220px -340px; }
.emoji-1F30F { background-position: -240px -340px; }
.emoji-1F310 { background-position: -260px -340px; }
.emoji-1F311 { background-position: -280px -340px; }
.emoji-1F312 { background-position: -300px -340px; }
.emoji-1F313 { background-position: -320px -340px; }
.emoji-1F314 { background-position: -340px -340px; }
.emoji-1F315 { background-position: -360px 0; }
.emoji-1F316 { background-position: -360px -20px; }
.emoji-1F317 { background-position: -360px -40px; }
.emoji-1F318 { background-position: -360px -60px; }
.emoji-1F319 { background-position: -360px -80px; }
.emoji-1F31A { background-position: -360px -100px; }
.emoji-1F31B { background-position: -360px -120px; }
.emoji-1F31C { background-position: -360px -140px; }
.emoji-1F31D { background-position: -360px -160px; }
.emoji-1F31E { background-position: -360px -180px; }
.emoji-1F31F { background-position: -360px -200px; }
.emoji-1F320 { background-position: -360px -220px; }
.emoji-1F321 { background-position: -360px -240px; }
.emoji-1F324 { background-position: -360px -260px; }
.emoji-1F325 { background-position: -360px -280px; }
.emoji-1F326 { background-position: -360px -300px; }
.emoji-1F327 { background-position: -360px -320px; }
.emoji-1F328 { background-position: -360px -340px; }
.emoji-1F329 { background-position: 0 -360px; }
.emoji-1F32A { background-position: -20px -360px; }
.emoji-1F32B { background-position: -40px -360px; }
.emoji-1F32C { background-position: -60px -360px; }
.emoji-1F32D { background-position: -80px -360px; }
.emoji-1F32E { background-position: -100px -360px; }
.emoji-1F32F { background-position: -120px -360px; }
.emoji-1F330 { background-position: -140px -360px; }
.emoji-1F331 { background-position: -160px -360px; }
.emoji-1F332 { background-position: -180px -360px; }
.emoji-1F333 { background-position: -200px -360px; }
.emoji-1F334 { background-position: -220px -360px; }
.emoji-1F335 { background-position: -240px -360px; }
.emoji-1F336 { background-position: -260px -360px; }
.emoji-1F337 { background-position: -280px -360px; }
.emoji-1F338 { background-position: -300px -360px; }
.emoji-1F339 { background-position: -320px -360px; }
.emoji-1F33A { background-position: -340px -360px; }
.emoji-1F33B { background-position: -360px -360px; }
.emoji-1F33C { background-position: -380px 0; }
.emoji-1F33D { background-position: -380px -20px; }
.emoji-1F33E { background-position: -380px -40px; }
.emoji-1F33F { background-position: -380px -60px; }
.emoji-1F340 { background-position: -380px -80px; }
.emoji-1F341 { background-position: -380px -100px; }
.emoji-1F342 { background-position: -380px -120px; }
.emoji-1F343 { background-position: -380px -140px; }
.emoji-1F344 { background-position: -380px -160px; }
.emoji-1F345 { background-position: -380px -180px; }
.emoji-1F346 { background-position: -380px -200px; }
.emoji-1F347 { background-position: -380px -220px; }
.emoji-1F348 { background-position: -380px -240px; }
.emoji-1F349 { background-position: -380px -260px; }
.emoji-1F34A { background-position: -380px -280px; }
.emoji-1F34B { background-position: -380px -300px; }
.emoji-1F34C { background-position: -380px -320px; }
.emoji-1F34D { background-position: -380px -340px; }
.emoji-1F34E { background-position: -380px -360px; }
.emoji-1F34F { background-position: 0 -380px; }
.emoji-1F350 { background-position: -20px -380px; }
.emoji-1F351 { background-position: -40px -380px; }
.emoji-1F352 { background-position: -60px -380px; }
.emoji-1F353 { background-position: -80px -380px; }
.emoji-1F354 { background-position: -100px -380px; }
.emoji-1F355 { background-position: -120px -380px; }
.emoji-1F356 { background-position: -140px -380px; }
.emoji-1F357 { background-position: -160px -380px; }
.emoji-1F358 { background-position: -180px -380px; }
.emoji-1F359 { background-position: -200px -380px; }
.emoji-1F35A { background-position: -220px -380px; }
.emoji-1F35B { background-position: -240px -380px; }
.emoji-1F35C { background-position: -260px -380px; }
.emoji-1F35D { background-position: -280px -380px; }
.emoji-1F35E { background-position: -300px -380px; }
.emoji-1F35F { background-position: -320px -380px; }
.emoji-1F360 { background-position: -340px -380px; }
.emoji-1F361 { background-position: -360px -380px; }
.emoji-1F362 { background-position: -380px -380px; }
.emoji-1F363 { background-position: -400px 0; }
.emoji-1F364 { background-position: -400px -20px; }
.emoji-1F365 { background-position: -400px -40px; }
.emoji-1F366 { background-position: -400px -60px; }
.emoji-1F367 { background-position: -400px -80px; }
.emoji-1F368 { background-position: -400px -100px; }
.emoji-1F369 { background-position: -400px -120px; }
.emoji-1F36A { background-position: -400px -140px; }
.emoji-1F36B { background-position: -400px -160px; }
.emoji-1F36C { background-position: -400px -180px; }
.emoji-1F36D { background-position: -400px -200px; }
.emoji-1F36E { background-position: -400px -220px; }
.emoji-1F36F { background-position: -400px -240px; }
.emoji-1F370 { background-position: -400px -260px; }
.emoji-1F371 { background-position: -400px -280px; }
.emoji-1F372 { background-position: -400px -300px; }
.emoji-1F373 { background-position: -400px -320px; }
.emoji-1F374 { background-position: -400px -340px; }
.emoji-1F375 { background-position: -400px -360px; }
.emoji-1F376 { background-position: -400px -380px; }
.emoji-1F377 { background-position: 0 -400px; }
.emoji-1F378 { background-position: -20px -400px; }
.emoji-1F379 { background-position: -40px -400px; }
.emoji-1F37A { background-position: -60px -400px; }
.emoji-1F37B { background-position: -80px -400px; }
.emoji-1F37C { background-position: -100px -400px; }
.emoji-1F37D { background-position: -120px -400px; }
.emoji-1F37E { background-position: -140px -400px; }
.emoji-1F37F { background-position: -160px -400px; }
.emoji-1F380 { background-position: -180px -400px; }
.emoji-1F381 { background-position: -200px -400px; }
.emoji-1F382 { background-position: -220px -400px; }
.emoji-1F383 { background-position: -240px -400px; }
.emoji-1F384 { background-position: -260px -400px; }
.emoji-1F385 { background-position: -280px -400px; }
.emoji-1F385-1F3FB { background-position: -300px -400px; }
.emoji-1F385-1F3FC { background-position: -320px -400px; }
.emoji-1F385-1F3FD { background-position: -340px -400px; }
.emoji-1F385-1F3FE { background-position: -360px -400px; }
.emoji-1F385-1F3FF { background-position: -380px -400px; }
.emoji-1F386 { background-position: -400px -400px; }
.emoji-1F387 { background-position: -420px 0; }
.emoji-1F388 { background-position: -420px -20px; }
.emoji-1F389 { background-position: -420px -40px; }
.emoji-1F38A { background-position: -420px -60px; }
.emoji-1F38B { background-position: -420px -80px; }
.emoji-1F38C { background-position: -420px -100px; }
.emoji-1F38D { background-position: -420px -120px; }
.emoji-1F38E { background-position: -420px -140px; }
.emoji-1F38F { background-position: -420px -160px; }
.emoji-1F390 { background-position: -420px -180px; }
.emoji-1F391 { background-position: -420px -200px; }
.emoji-1F392 { background-position: -420px -220px; }
.emoji-1F393 { background-position: -420px -240px; }
.emoji-1F396 { background-position: -420px -260px; }
.emoji-1F397 { background-position: -420px -280px; }
.emoji-1F399 { background-position: -420px -300px; }
.emoji-1F39A { background-position: -420px -320px; }
.emoji-1F39B { background-position: -420px -340px; }
.emoji-1F39E { background-position: -420px -360px; }
.emoji-1F39F { background-position: -420px -380px; }
.emoji-1F3A0 { background-position: -420px -400px; }
.emoji-1F3A1 { background-position: 0 -420px; }
.emoji-1F3A2 { background-position: -20px -420px; }
.emoji-1F3A3 { background-position: -40px -420px; }
.emoji-1F3A4 { background-position: -60px -420px; }
.emoji-1F3A5 { background-position: -80px -420px; }
.emoji-1F3A6 { background-position: -100px -420px; }
.emoji-1F3A7 { background-position: -120px -420px; }
.emoji-1F3A8 { background-position: -140px -420px; }
.emoji-1F3A9 { background-position: -160px -420px; }
.emoji-1F3AA { background-position: -180px -420px; }
.emoji-1F3AB { background-position: -200px -420px; }
.emoji-1F3AC { background-position: -220px -420px; }
.emoji-1F3AD { background-position: -240px -420px; }
.emoji-1F3AE { background-position: -260px -420px; }
.emoji-1F3AF { background-position: -280px -420px; }
.emoji-1F3B0 { background-position: -300px -420px; }
.emoji-1F3B1 { background-position: -320px -420px; }
.emoji-1F3B2 { background-position: -340px -420px; }
.emoji-1F3B3 { background-position: -360px -420px; }
.emoji-1F3B4 { background-position: -380px -420px; }
.emoji-1F3B5 { background-position: -400px -420px; }
.emoji-1F3B6 { background-position: -420px -420px; }
.emoji-1F3B7 { background-position: -440px 0; }
.emoji-1F3B8 { background-position: -440px -20px; }
.emoji-1F3B9 { background-position: -440px -40px; }
.emoji-1F3BA { background-position: -440px -60px; }
.emoji-1F3BB { background-position: -440px -80px; }
.emoji-1F3BC { background-position: -440px -100px; }
.emoji-1F3BD { background-position: -440px -120px; }
.emoji-1F3BE { background-position: -440px -140px; }
.emoji-1F3BF { background-position: -440px -160px; }
.emoji-1F3C0 { background-position: -440px -180px; }
.emoji-1F3C1 { background-position: -440px -200px; }
.emoji-1F3C2 { background-position: -440px -220px; }
.emoji-1F3C3 { background-position: -440px -240px; }
.emoji-1F3C3-1F3FB { background-position: -440px -260px; }
.emoji-1F3C3-1F3FC { background-position: -440px -280px; }
.emoji-1F3C3-1F3FD { background-position: -440px -300px; }
.emoji-1F3C3-1F3FE { background-position: -440px -320px; }
.emoji-1F3C3-1F3FF { background-position: -440px -340px; }
.emoji-1F3C4 { background-position: -440px -360px; }
.emoji-1F3C4-1F3FB { background-position: -440px -380px; }
.emoji-1F3C4-1F3FC { background-position: -440px -400px; }
.emoji-1F3C4-1F3FD { background-position: -440px -420px; }
.emoji-1F3C4-1F3FE { background-position: 0 -440px; }
.emoji-1F3C4-1F3FF { background-position: -20px -440px; }
.emoji-1F3C5 { background-position: -40px -440px; }
.emoji-1F3C6 { background-position: -60px -440px; }
.emoji-1F3C7 { background-position: -80px -440px; }
.emoji-1F3C7-1F3FB { background-position: -100px -440px; }
.emoji-1F3C7-1F3FC { background-position: -120px -440px; }
.emoji-1F3C7-1F3FD { background-position: -140px -440px; }
.emoji-1F3C7-1F3FE { background-position: -160px -440px; }
.emoji-1F3C7-1F3FF { background-position: -180px -440px; }
.emoji-1F3C8 { background-position: -200px -440px; }
.emoji-1F3C9 { background-position: -220px -440px; }
.emoji-1F3CA { background-position: -240px -440px; }
.emoji-1F3CA-1F3FB { background-position: -260px -440px; }
.emoji-1F3CA-1F3FC { background-position: -280px -440px; }
.emoji-1F3CA-1F3FD { background-position: -300px -440px; }
.emoji-1F3CA-1F3FE { background-position: -320px -440px; }
.emoji-1F3CA-1F3FF { background-position: -340px -440px; }
.emoji-1F3CB { background-position: -360px -440px; }
.emoji-1F3CB-1F3FB { background-position: -380px -440px; }
.emoji-1F3CB-1F3FC { background-position: -400px -440px; }
.emoji-1F3CB-1F3FD { background-position: -420px -440px; }
.emoji-1F3CB-1F3FE { background-position: -440px -440px; }
.emoji-1F3CB-1F3FF { background-position: -460px 0; }
.emoji-1F3CC { background-position: -460px -20px; }
.emoji-1F3CD { background-position: -460px -40px; }
.emoji-1F3CE { background-position: -460px -60px; }
.emoji-1F3CF { background-position: -460px -80px; }
.emoji-1F3D0 { background-position: -460px -100px; }
.emoji-1F3D1 { background-position: -460px -120px; }
.emoji-1F3D2 { background-position: -460px -140px; }
.emoji-1F3D3 { background-position: -460px -160px; }
.emoji-1F3D4 { background-position: -460px -180px; }
.emoji-1F3D5 { background-position: -460px -200px; }
.emoji-1F3D6 { background-position: -460px -220px; }
.emoji-1F3D7 { background-position: -460px -240px; }
.emoji-1F3D8 { background-position: -460px -260px; }
.emoji-1F3D9 { background-position: -460px -280px; }
.emoji-1F3DA { background-position: -460px -300px; }
.emoji-1F3DB { background-position: -460px -320px; }
.emoji-1F3DC { background-position: -460px -340px; }
.emoji-1F3DD { background-position: -460px -360px; }
.emoji-1F3DE { background-position: -460px -380px; }
.emoji-1F3DF { background-position: -460px -400px; }
.emoji-1F3E0 { background-position: -460px -420px; }
.emoji-1F3E1 { background-position: -460px -440px; }
.emoji-1F3E2 { background-position: 0 -460px; }
.emoji-1F3E3 { background-position: -20px -460px; }
.emoji-1F3E4 { background-position: -40px -460px; }
.emoji-1F3E5 { background-position: -60px -460px; }
.emoji-1F3E6 { background-position: -80px -460px; }
.emoji-1F3E7 { background-position: -100px -460px; }
.emoji-1F3E8 { background-position: -120px -460px; }
.emoji-1F3E9 { background-position: -140px -460px; }
.emoji-1F3EA { background-position: -160px -460px; }
.emoji-1F3EB { background-position: -180px -460px; }
.emoji-1F3EC { background-position: -200px -460px; }
.emoji-1F3ED { background-position: -220px -460px; }
.emoji-1F3EE { background-position: -240px -460px; }
.emoji-1F3EF { background-position: -260px -460px; }
.emoji-1F3F0 { background-position: -280px -460px; }
.emoji-1F3F3 { background-position: -300px -460px; }
.emoji-1F3F4 { background-position: -320px -460px; }
.emoji-1F3F5 { background-position: -340px -460px; }
.emoji-1F3F7 { background-position: -360px -460px; }
.emoji-1F3F8 { background-position: -380px -460px; }
.emoji-1F3F9 { background-position: -400px -460px; }
.emoji-1F3FA { background-position: -420px -460px; }
.emoji-1F3FB { background-position: -440px -460px; }
.emoji-1F3FC { background-position: -460px -460px; }
.emoji-1F3FD { background-position: -480px 0; }
.emoji-1F3FE { background-position: -480px -20px; }
.emoji-1F3FF { background-position: -480px -40px; }
.emoji-1F400 { background-position: -480px -60px; }
.emoji-1F401 { background-position: -480px -80px; }
.emoji-1F402 { background-position: -480px -100px; }
.emoji-1F403 { background-position: -480px -120px; }
.emoji-1F404 { background-position: -480px -140px; }
.emoji-1F405 { background-position: -480px -160px; }
.emoji-1F406 { background-position: -480px -180px; }
.emoji-1F407 { background-position: -480px -200px; }
.emoji-1F408 { background-position: -480px -220px; }
.emoji-1F409 { background-position: -480px -240px; }
.emoji-1F40A { background-position: -480px -260px; }
.emoji-1F40B { background-position: -480px -280px; }
.emoji-1F40C { background-position: -480px -300px; }
.emoji-1F40D { background-position: -480px -320px; }
.emoji-1F40E { background-position: -480px -340px; }
.emoji-1F40F { background-position: -480px -360px; }
.emoji-1F410 { background-position: -480px -380px; }
.emoji-1F411 { background-position: -480px -400px; }
.emoji-1F412 { background-position: -480px -420px; }
.emoji-1F413 { background-position: -480px -440px; }
.emoji-1F414 { background-position: -480px -460px; }
.emoji-1F415 { background-position: 0 -480px; }
.emoji-1F416 { background-position: -20px -480px; }
.emoji-1F417 { background-position: -40px -480px; }
.emoji-1F418 { background-position: -60px -480px; }
.emoji-1F419 { background-position: -80px -480px; }
.emoji-1F41A { background-position: -100px -480px; }
.emoji-1F41B { background-position: -120px -480px; }
.emoji-1F41C { background-position: -140px -480px; }
.emoji-1F41D { background-position: -160px -480px; }
.emoji-1F41E { background-position: -180px -480px; }
.emoji-1F41F { background-position: -200px -480px; }
.emoji-1F420 { background-position: -220px -480px; }
.emoji-1F421 { background-position: -240px -480px; }
.emoji-1F422 { background-position: -260px -480px; }
.emoji-1F423 { background-position: -280px -480px; }
.emoji-1F424 { background-position: -300px -480px; }
.emoji-1F425 { background-position: -320px -480px; }
.emoji-1F426 { background-position: -340px -480px; }
.emoji-1F427 { background-position: -360px -480px; }
.emoji-1F428 { background-position: -380px -480px; }
.emoji-1F429 { background-position: -400px -480px; }
.emoji-1F42A { background-position: -420px -480px; }
.emoji-1F42B { background-position: -440px -480px; }
.emoji-1F42C { background-position: -460px -480px; }
.emoji-1F42D { background-position: -480px -480px; }
.emoji-1F42E { background-position: -500px 0; }
.emoji-1F42F { background-position: -500px -20px; }
.emoji-1F430 { background-position: -500px -40px; }
.emoji-1F431 { background-position: -500px -60px; }
.emoji-1F432 { background-position: -500px -80px; }
.emoji-1F433 { background-position: -500px -100px; }
.emoji-1F434 { background-position: -500px -120px; }
.emoji-1F435 { background-position: -500px -140px; }
.emoji-1F436 { background-position: -500px -160px; }
.emoji-1F437 { background-position: -500px -180px; }
.emoji-1F438 { background-position: -500px -200px; }
.emoji-1F439 { background-position: -500px -220px; }
.emoji-1F43A { background-position: -500px -240px; }
.emoji-1F43B { background-position: -500px -260px; }
.emoji-1F43C { background-position: -500px -280px; }
.emoji-1F43D { background-position: -500px -300px; }
.emoji-1F43E { background-position: -500px -320px; }
.emoji-1F43F { background-position: -500px -340px; }
.emoji-1F440 { background-position: -500px -360px; }
.emoji-1F441 { background-position: -500px -380px; }
.emoji-1F441-1F5E8 { background-position: -500px -400px; }
.emoji-1F442 { background-position: -500px -420px; }
.emoji-1F442-1F3FB { background-position: -500px -440px; }
.emoji-1F442-1F3FC { background-position: -500px -460px; }
.emoji-1F442-1F3FD { background-position: -500px -480px; }
.emoji-1F442-1F3FE { background-position: 0 -500px; }
.emoji-1F442-1F3FF { background-position: -20px -500px; }
.emoji-1F443 { background-position: -40px -500px; }
.emoji-1F443-1F3FB { background-position: -60px -500px; }
.emoji-1F443-1F3FC { background-position: -80px -500px; }
.emoji-1F443-1F3FD { background-position: -100px -500px; }
.emoji-1F443-1F3FE { background-position: -120px -500px; }
.emoji-1F443-1F3FF { background-position: -140px -500px; }
.emoji-1F444 { background-position: -160px -500px; }
.emoji-1F445 { background-position: -180px -500px; }
.emoji-1F446 { background-position: -200px -500px; }
.emoji-1F446-1F3FB { background-position: -220px -500px; }
.emoji-1F446-1F3FC { background-position: -240px -500px; }
.emoji-1F446-1F3FD { background-position: -260px -500px; }
.emoji-1F446-1F3FE { background-position: -280px -500px; }
.emoji-1F446-1F3FF { background-position: -300px -500px; }
.emoji-1F447 { background-position: -320px -500px; }
.emoji-1F447-1F3FB { background-position: -340px -500px; }
.emoji-1F447-1F3FC { background-position: -360px -500px; }
.emoji-1F447-1F3FD { background-position: -380px -500px; }
.emoji-1F447-1F3FE { background-position: -400px -500px; }
.emoji-1F447-1F3FF { background-position: -420px -500px; }
.emoji-1F448 { background-position: -440px -500px; }
.emoji-1F448-1F3FB { background-position: -460px -500px; }
.emoji-1F448-1F3FC { background-position: -480px -500px; }
.emoji-1F448-1F3FD { background-position: -500px -500px; }
.emoji-1F448-1F3FE { background-position: -520px 0; }
.emoji-1F448-1F3FF { background-position: -520px -20px; }
.emoji-1F449 { background-position: -520px -40px; }
.emoji-1F449-1F3FB { background-position: -520px -60px; }
.emoji-1F449-1F3FC { background-position: -520px -80px; }
.emoji-1F449-1F3FD { background-position: -520px -100px; }
.emoji-1F449-1F3FE { background-position: -520px -120px; }
.emoji-1F449-1F3FF { background-position: -520px -140px; }
.emoji-1F44A { background-position: -520px -160px; }
.emoji-1F44A-1F3FB { background-position: -520px -180px; }
.emoji-1F44A-1F3FC { background-position: -520px -200px; }
.emoji-1F44A-1F3FD { background-position: -520px -220px; }
.emoji-1F44A-1F3FE { background-position: -520px -240px; }
.emoji-1F44A-1F3FF { background-position: -520px -260px; }
.emoji-1F44B { background-position: -520px -280px; }
.emoji-1F44B-1F3FB { background-position: -520px -300px; }
.emoji-1F44B-1F3FC { background-position: -520px -320px; }
.emoji-1F44B-1F3FD { background-position: -520px -340px; }
.emoji-1F44B-1F3FE { background-position: -520px -360px; }
.emoji-1F44B-1F3FF { background-position: -520px -380px; }
.emoji-1F44C { background-position: -520px -400px; }
.emoji-1F44C-1F3FB { background-position: -520px -420px; }
.emoji-1F44C-1F3FC { background-position: -520px -440px; }
.emoji-1F44C-1F3FD { background-position: -520px -460px; }
.emoji-1F44C-1F3FE { background-position: -520px -480px; }
.emoji-1F44C-1F3FF { background-position: -520px -500px; }
.emoji-1F44D { background-position: 0 -520px; }
.emoji-1F44D-1F3FB { background-position: -20px -520px; }
.emoji-1F44D-1F3FC { background-position: -40px -520px; }
.emoji-1F44D-1F3FD { background-position: -60px -520px; }
.emoji-1F44D-1F3FE { background-position: -80px -520px; }
.emoji-1F44D-1F3FF { background-position: -100px -520px; }
.emoji-1F44E { background-position: -120px -520px; }
.emoji-1F44E-1F3FB { background-position: -140px -520px; }
.emoji-1F44E-1F3FC { background-position: -160px -520px; }
.emoji-1F44E-1F3FD { background-position: -180px -520px; }
.emoji-1F44E-1F3FE { background-position: -200px -520px; }
.emoji-1F44E-1F3FF { background-position: -220px -520px; }
.emoji-1F44F { background-position: -240px -520px; }
.emoji-1F44F-1F3FB { background-position: -260px -520px; }
.emoji-1F44F-1F3FC { background-position: -280px -520px; }
.emoji-1F44F-1F3FD { background-position: -300px -520px; }
.emoji-1F44F-1F3FE { background-position: -320px -520px; }
.emoji-1F44F-1F3FF { background-position: -340px -520px; }
.emoji-1F450 { background-position: -360px -520px; }
.emoji-1F450-1F3FB { background-position: -380px -520px; }
.emoji-1F450-1F3FC { background-position: -400px -520px; }
.emoji-1F450-1F3FD { background-position: -420px -520px; }
.emoji-1F450-1F3FE { background-position: -440px -520px; }
.emoji-1F450-1F3FF { background-position: -460px -520px; }
.emoji-1F451 { background-position: -480px -520px; }
.emoji-1F452 { background-position: -500px -520px; }
.emoji-1F453 { background-position: -520px -520px; }
.emoji-1F454 { background-position: -540px 0; }
.emoji-1F455 { background-position: -540px -20px; }
.emoji-1F456 { background-position: -540px -40px; }
.emoji-1F457 { background-position: -540px -60px; }
.emoji-1F458 { background-position: -540px -80px; }
.emoji-1F459 { background-position: -540px -100px; }
.emoji-1F45A { background-position: -540px -120px; }
.emoji-1F45B { background-position: -540px -140px; }
.emoji-1F45C { background-position: -540px -160px; }
.emoji-1F45D { background-position: -540px -180px; }
.emoji-1F45E { background-position: -540px -200px; }
.emoji-1F45F { background-position: -540px -220px; }
.emoji-1F460 { background-position: -540px -240px; }
.emoji-1F461 { background-position: -540px -260px; }
.emoji-1F462 { background-position: -540px -280px; }
.emoji-1F463 { background-position: -540px -300px; }
.emoji-1F464 { background-position: -540px -320px; }
.emoji-1F465 { background-position: -540px -340px; }
.emoji-1F466 { background-position: -540px -360px; }
.emoji-1F466-1F3FB { background-position: -540px -380px; }
.emoji-1F466-1F3FC { background-position: -540px -400px; }
.emoji-1F466-1F3FD { background-position: -540px -420px; }
.emoji-1F466-1F3FE { background-position: -540px -440px; }
.emoji-1F466-1F3FF { background-position: -540px -460px; }
.emoji-1F467 { background-position: -540px -480px; }
.emoji-1F467-1F3FB { background-position: -540px -500px; }
.emoji-1F467-1F3FC { background-position: -540px -520px; }
.emoji-1F467-1F3FD { background-position: 0 -540px; }
.emoji-1F467-1F3FE { background-position: -20px -540px; }
.emoji-1F467-1F3FF { background-position: -40px -540px; }
.emoji-1F468 { background-position: -60px -540px; }
.emoji-1F468-1F3FB { background-position: -80px -540px; }
.emoji-1F468-1F3FC { background-position: -100px -540px; }
.emoji-1F468-1F3FD { background-position: -120px -540px; }
.emoji-1F468-1F3FE { background-position: -140px -540px; }
.emoji-1F468-1F3FF { background-position: -160px -540px; }
.emoji-1F468-1F468-1F466 { background-position: -180px -540px; }
.emoji-1F468-1F468-1F466-1F466 { background-position: -200px -540px; }
.emoji-1F468-1F468-1F467 { background-position: -220px -540px; }
.emoji-1F468-1F468-1F467-1F466 { background-position: -240px -540px; }
.emoji-1F468-1F468-1F467-1F467 { background-position: -260px -540px; }
.emoji-1F468-1F469-1F466-1F466 { background-position: -280px -540px; }
.emoji-1F468-1F469-1F467 { background-position: -300px -540px; }
.emoji-1F468-1F469-1F467-1F466 { background-position: -320px -540px; }
.emoji-1F468-1F469-1F467-1F467 { background-position: -340px -540px; }
.emoji-1F468-2764-1F468 { background-position: -360px -540px; }
.emoji-1F468-2764-1F48B-1F468 { background-position: -380px -540px; }
.emoji-1F469 { background-position: -400px -540px; }
.emoji-1F469-1F3FB { background-position: -420px -540px; }
.emoji-1F469-1F3FC { background-position: -440px -540px; }
.emoji-1F469-1F3FD { background-position: -460px -540px; }
.emoji-1F469-1F3FE { background-position: -480px -540px; }
.emoji-1F469-1F3FF { background-position: -500px -540px; }
.emoji-1F469-1F469-1F466 { background-position: -520px -540px; }
.emoji-1F469-1F469-1F466-1F466 { background-position: -540px -540px; }
.emoji-1F469-1F469-1F467 { background-position: -560px 0; }
.emoji-1F469-1F469-1F467-1F466 { background-position: -560px -20px; }
.emoji-1F469-1F469-1F467-1F467 { background-position: -560px -40px; }
.emoji-1F469-2764-1F469 { background-position: -560px -60px; }
.emoji-1F469-2764-1F48B-1F469 { background-position: -560px -80px; }
.emoji-1F46A { background-position: -560px -100px; }
.emoji-1F46B { background-position: -560px -120px; }
.emoji-1F46C { background-position: -560px -140px; }
.emoji-1F46D { background-position: -560px -160px; }
.emoji-1F46E { background-position: -560px -180px; }
.emoji-1F46E-1F3FB { background-position: -560px -200px; }
.emoji-1F46E-1F3FC { background-position: -560px -220px; }
.emoji-1F46E-1F3FD { background-position: -560px -240px; }
.emoji-1F46E-1F3FE { background-position: -560px -260px; }
.emoji-1F46E-1F3FF { background-position: -560px -280px; }
.emoji-1F46F { background-position: -560px -300px; }
.emoji-1F470 { background-position: -560px -320px; }
.emoji-1F470-1F3FB { background-position: -560px -340px; }
.emoji-1F470-1F3FC { background-position: -560px -360px; }
.emoji-1F470-1F3FD { background-position: -560px -380px; }
.emoji-1F470-1F3FE { background-position: -560px -400px; }
.emoji-1F470-1F3FF { background-position: -560px -420px; }
.emoji-1F471 { background-position: -560px -440px; }
.emoji-1F471-1F3FB { background-position: -560px -460px; }
.emoji-1F471-1F3FC { background-position: -560px -480px; }
.emoji-1F471-1F3FD { background-position: -560px -500px; }
.emoji-1F471-1F3FE { background-position: -560px -520px; }
.emoji-1F471-1F3FF { background-position: -560px -540px; }
.emoji-1F472 { background-position: 0 -560px; }
.emoji-1F472-1F3FB { background-position: -20px -560px; }
.emoji-1F472-1F3FC { background-position: -40px -560px; }
.emoji-1F472-1F3FD { background-position: -60px -560px; }
.emoji-1F472-1F3FE { background-position: -80px -560px; }
.emoji-1F472-1F3FF { background-position: -100px -560px; }
.emoji-1F473 { background-position: -120px -560px; }
.emoji-1F473-1F3FB { background-position: -140px -560px; }
.emoji-1F473-1F3FC { background-position: -160px -560px; }
.emoji-1F473-1F3FD { background-position: -180px -560px; }
.emoji-1F473-1F3FE { background-position: -200px -560px; }
.emoji-1F473-1F3FF { background-position: -220px -560px; }
.emoji-1F474 { background-position: -240px -560px; }
.emoji-1F474-1F3FB { background-position: -260px -560px; }
.emoji-1F474-1F3FC { background-position: -280px -560px; }
.emoji-1F474-1F3FD { background-position: -300px -560px; }
.emoji-1F474-1F3FE { background-position: -320px -560px; }
.emoji-1F474-1F3FF { background-position: -340px -560px; }
.emoji-1F475 { background-position: -360px -560px; }
.emoji-1F475-1F3FB { background-position: -380px -560px; }
.emoji-1F475-1F3FC { background-position: -400px -560px; }
.emoji-1F475-1F3FD { background-position: -420px -560px; }
.emoji-1F475-1F3FE { background-position: -440px -560px; }
.emoji-1F475-1F3FF { background-position: -460px -560px; }
.emoji-1F476 { background-position: -480px -560px; }
.emoji-1F476-1F3FB { background-position: -500px -560px; }
.emoji-1F476-1F3FC { background-position: -520px -560px; }
.emoji-1F476-1F3FD { background-position: -540px -560px; }
.emoji-1F476-1F3FE { background-position: -560px -560px; }
.emoji-1F476-1F3FF { background-position: -580px 0; }
.emoji-1F477 { background-position: -580px -20px; }
.emoji-1F477-1F3FB { background-position: -580px -40px; }
.emoji-1F477-1F3FC { background-position: -580px -60px; }
.emoji-1F477-1F3FD { background-position: -580px -80px; }
.emoji-1F477-1F3FE { background-position: -580px -100px; }
.emoji-1F477-1F3FF { background-position: -580px -120px; }
.emoji-1F478 { background-position: -580px -140px; }
.emoji-1F478-1F3FB { background-position: -580px -160px; }
.emoji-1F478-1F3FC { background-position: -580px -180px; }
.emoji-1F478-1F3FD { background-position: -580px -200px; }
.emoji-1F478-1F3FE { background-position: -580px -220px; }
.emoji-1F478-1F3FF { background-position: -580px -240px; }
.emoji-1F479 { background-position: -580px -260px; }
.emoji-1F47A { background-position: -580px -280px; }
.emoji-1F47B { background-position: -580px -300px; }
.emoji-1F47C { background-position: -580px -320px; }
.emoji-1F47C-1F3FB { background-position: -580px -340px; }
.emoji-1F47C-1F3FC { background-position: -580px -360px; }
.emoji-1F47C-1F3FD { background-position: -580px -380px; }
.emoji-1F47C-1F3FE { background-position: -580px -400px; }
.emoji-1F47C-1F3FF { background-position: -580px -420px; }
.emoji-1F47D { background-position: -580px -440px; }
.emoji-1F47E { background-position: -580px -460px; }
.emoji-1F47F { background-position: -580px -480px; }
.emoji-1F480 { background-position: -580px -500px; }
.emoji-1F481 { background-position: -580px -520px; }
.emoji-1F481-1F3FB { background-position: -580px -540px; }
.emoji-1F481-1F3FC { background-position: -580px -560px; }
.emoji-1F481-1F3FD { background-position: 0 -580px; }
.emoji-1F481-1F3FE { background-position: -20px -580px; }
.emoji-1F481-1F3FF { background-position: -40px -580px; }
.emoji-1F482 { background-position: -60px -580px; }
.emoji-1F482-1F3FB { background-position: -80px -580px; }
.emoji-1F482-1F3FC { background-position: -100px -580px; }
.emoji-1F482-1F3FD { background-position: -120px -580px; }
.emoji-1F482-1F3FE { background-position: -140px -580px; }
.emoji-1F482-1F3FF { background-position: -160px -580px; }
.emoji-1F483 { background-position: -180px -580px; }
.emoji-1F483-1F3FB { background-position: -200px -580px; }
.emoji-1F483-1F3FC { background-position: -220px -580px; }
.emoji-1F483-1F3FD { background-position: -240px -580px; }
.emoji-1F483-1F3FE { background-position: -260px -580px; }
.emoji-1F483-1F3FF { background-position: -280px -580px; }
.emoji-1F484 { background-position: -300px -580px; }
.emoji-1F485 { background-position: -320px -580px; }
.emoji-1F485-1F3FB { background-position: -340px -580px; }
.emoji-1F485-1F3FC { background-position: -360px -580px; }
.emoji-1F485-1F3FD { background-position: -380px -580px; }
.emoji-1F485-1F3FE { background-position: -400px -580px; }
.emoji-1F485-1F3FF { background-position: -420px -580px; }
.emoji-1F486 { background-position: -440px -580px; }
.emoji-1F486-1F3FB { background-position: -460px -580px; }
.emoji-1F486-1F3FC { background-position: -480px -580px; }
.emoji-1F486-1F3FD { background-position: -500px -580px; }
.emoji-1F486-1F3FE { background-position: -520px -580px; }
.emoji-1F486-1F3FF { background-position: -540px -580px; }
.emoji-1F487 { background-position: -560px -580px; }
.emoji-1F487-1F3FB { background-position: -580px -580px; }
.emoji-1F487-1F3FC { background-position: -600px 0; }
.emoji-1F487-1F3FD { background-position: -600px -20px; }
.emoji-1F487-1F3FE { background-position: -600px -40px; }
.emoji-1F487-1F3FF { background-position: -600px -60px; }
.emoji-1F488 { background-position: -600px -80px; }
.emoji-1F489 { background-position: -600px -100px; }
.emoji-1F48A { background-position: -600px -120px; }
.emoji-1F48B { background-position: -600px -140px; }
.emoji-1F48C { background-position: -600px -160px; }
.emoji-1F48D { background-position: -600px -180px; }
.emoji-1F48E { background-position: -600px -200px; }
.emoji-1F48F { background-position: -600px -220px; }
.emoji-1F490 { background-position: -600px -240px; }
.emoji-1F491 { background-position: -600px -260px; }
.emoji-1F492 { background-position: -600px -280px; }
.emoji-1F493 { background-position: -600px -300px; }
.emoji-1F494 { background-position: -600px -320px; }
.emoji-1F495 { background-position: -600px -340px; }
.emoji-1F496 { background-position: -600px -360px; }
.emoji-1F497 { background-position: -600px -380px; }
.emoji-1F498 { background-position: -600px -400px; }
.emoji-1F499 { background-position: -600px -420px; }
.emoji-1F49A { background-position: -600px -440px; }
.emoji-1F49B { background-position: -600px -460px; }
.emoji-1F49C { background-position: -600px -480px; }
.emoji-1F49D { background-position: -600px -500px; }
.emoji-1F49E { background-position: -600px -520px; }
.emoji-1F49F { background-position: -600px -540px; }
.emoji-1F4A0 { background-position: -600px -560px; }
.emoji-1F4A1 { background-position: -600px -580px; }
.emoji-1F4A2 { background-position: 0 -600px; }
.emoji-1F4A3 { background-position: -20px -600px; }
.emoji-1F4A4 { background-position: -40px -600px; }
.emoji-1F4A5 { background-position: -60px -600px; }
.emoji-1F4A6 { background-position: -80px -600px; }
.emoji-1F4A7 { background-position: -100px -600px; }
.emoji-1F4A8 { background-position: -120px -600px; }
.emoji-1F4A9 { background-position: -140px -600px; }
.emoji-1F4AA { background-position: -160px -600px; }
.emoji-1F4AA-1F3FB { background-position: -180px -600px; }
.emoji-1F4AA-1F3FC { background-position: -200px -600px; }
.emoji-1F4AA-1F3FD { background-position: -220px -600px; }
.emoji-1F4AA-1F3FE { background-position: -240px -600px; }
.emoji-1F4AA-1F3FF { background-position: -260px -600px; }
.emoji-1F4AB { background-position: -280px -600px; }
.emoji-1F4AC { background-position: -300px -600px; }
.emoji-1F4AD { background-position: -320px -600px; }
.emoji-1F4AE { background-position: -340px -600px; }
.emoji-1F4AF { background-position: -360px -600px; }
.emoji-1F4B0 { background-position: -380px -600px; }
.emoji-1F4B1 { background-position: -400px -600px; }
.emoji-1F4B2 { background-position: -420px -600px; }
.emoji-1F4B3 { background-position: -440px -600px; }
.emoji-1F4B4 { background-position: -460px -600px; }
.emoji-1F4B5 { background-position: -480px -600px; }
.emoji-1F4B6 { background-position: -500px -600px; }
.emoji-1F4B7 { background-position: -520px -600px; }
.emoji-1F4B8 { background-position: -540px -600px; }
.emoji-1F4B9 { background-position: -560px -600px; }
.emoji-1F4BA { background-position: -580px -600px; }
.emoji-1F4BB { background-position: -600px -600px; }
.emoji-1F4BC { background-position: -620px 0; }
.emoji-1F4BD { background-position: -620px -20px; }
.emoji-1F4BE { background-position: -620px -40px; }
.emoji-1F4BF { background-position: -620px -60px; }
.emoji-1F4C0 { background-position: -620px -80px; }
.emoji-1F4C1 { background-position: -620px -100px; }
.emoji-1F4C2 { background-position: -620px -120px; }
.emoji-1F4C3 { background-position: -620px -140px; }
.emoji-1F4C4 { background-position: -620px -160px; }
.emoji-1F4C5 { background-position: -620px -180px; }
.emoji-1F4C6 { background-position: -620px -200px; }
.emoji-1F4C7 { background-position: -620px -220px; }
.emoji-1F4C8 { background-position: -620px -240px; }
.emoji-1F4C9 { background-position: -620px -260px; }
.emoji-1F4CA { background-position: -620px -280px; }
.emoji-1F4CB { background-position: -620px -300px; }
.emoji-1F4CC { background-position: -620px -320px; }
.emoji-1F4CD { background-position: -620px -340px; }
.emoji-1F4CE { background-position: -620px -360px; }
.emoji-1F4CF { background-position: -620px -380px; }
.emoji-1F4D0 { background-position: -620px -400px; }
.emoji-1F4D1 { background-position: -620px -420px; }
.emoji-1F4D2 { background-position: -620px -440px; }
.emoji-1F4D3 { background-position: -620px -460px; }
.emoji-1F4D4 { background-position: -620px -480px; }
.emoji-1F4D5 { background-position: -620px -500px; }
.emoji-1F4D6 { background-position: -620px -520px; }
.emoji-1F4D7 { background-position: -620px -540px; }
.emoji-1F4D8 { background-position: -620px -560px; }
.emoji-1F4D9 { background-position: -620px -580px; }
.emoji-1F4DA { background-position: -620px -600px; }
.emoji-1F4DB { background-position: 0 -620px; }
.emoji-1F4DC { background-position: -20px -620px; }
.emoji-1F4DD { background-position: -40px -620px; }
.emoji-1F4DE { background-position: -60px -620px; }
.emoji-1F4DF { background-position: -80px -620px; }
.emoji-1F4E0 { background-position: -100px -620px; }
.emoji-1F4E1 { background-position: -120px -620px; }
.emoji-1F4E2 { background-position: -140px -620px; }
.emoji-1F4E3 { background-position: -160px -620px; }
.emoji-1F4E4 { background-position: -180px -620px; }
.emoji-1F4E5 { background-position: -200px -620px; }
.emoji-1F4E6 { background-position: -220px -620px; }
.emoji-1F4E7 { background-position: -240px -620px; }
.emoji-1F4E8 { background-position: -260px -620px; }
.emoji-1F4E9 { background-position: -280px -620px; }
.emoji-1F4EA { background-position: -300px -620px; }
.emoji-1F4EB { background-position: -320px -620px; }
.emoji-1F4EC { background-position: -340px -620px; }
.emoji-1F4ED { background-position: -360px -620px; }
.emoji-1F4EE { background-position: -380px -620px; }
.emoji-1F4EF { background-position: -400px -620px; }
.emoji-1F4F0 { background-position: -420px -620px; }
.emoji-1F4F1 { background-position: -440px -620px; }
.emoji-1F4F2 { background-position: -460px -620px; }
.emoji-1F4F3 { background-position: -480px -620px; }
.emoji-1F4F4 { background-position: -500px -620px; }
.emoji-1F4F5 { background-position: -520px -620px; }
.emoji-1F4F6 { background-position: -540px -620px; }
.emoji-1F4F7 { background-position: -560px -620px; }
.emoji-1F4F8 { background-position: -580px -620px; }
.emoji-1F4F9 { background-position: -600px -620px; }
.emoji-1F4FA { background-position: -620px -620px; }
.emoji-1F4FB { background-position: -640px 0; }
.emoji-1F4FC { background-position: -640px -20px; }
.emoji-1F4FD { background-position: -640px -40px; }
.emoji-1F4FF { background-position: -640px -60px; }
.emoji-1F500 { background-position: -640px -80px; }
.emoji-1F501 { background-position: -640px -100px; }
.emoji-1F502 { background-position: -640px -120px; }
.emoji-1F503 { background-position: -640px -140px; }
.emoji-1F504 { background-position: -640px -160px; }
.emoji-1F505 { background-position: -640px -180px; }
.emoji-1F506 { background-position: -640px -200px; }
.emoji-1F507 { background-position: -640px -220px; }
.emoji-1F508 { background-position: -640px -240px; }
.emoji-1F509 { background-position: -640px -260px; }
.emoji-1F50A { background-position: -640px -280px; }
.emoji-1F50B { background-position: -640px -300px; }
.emoji-1F50C { background-position: -640px -320px; }
.emoji-1F50D { background-position: -640px -340px; }
.emoji-1F50E { background-position: -640px -360px; }
.emoji-1F50F { background-position: -640px -380px; }
.emoji-1F510 { background-position: -640px -400px; }
.emoji-1F511 { background-position: -640px -420px; }
.emoji-1F512 { background-position: -640px -440px; }
.emoji-1F513 { background-position: -640px -460px; }
.emoji-1F514 { background-position: -640px -480px; }
.emoji-1F515 { background-position: -640px -500px; }
.emoji-1F516 { background-position: -640px -520px; }
.emoji-1F517 { background-position: -640px -540px; }
.emoji-1F518 { background-position: -640px -560px; }
.emoji-1F519 { background-position: -640px -580px; }
.emoji-1F51A { background-position: -640px -600px; }
.emoji-1F51B { background-position: -640px -620px; }
.emoji-1F51C { background-position: 0 -640px; }
.emoji-1F51D { background-position: -20px -640px; }
.emoji-1F51E { background-position: -40px -640px; }
.emoji-1F51F { background-position: -60px -640px; }
.emoji-1F520 { background-position: -80px -640px; }
.emoji-1F521 { background-position: -100px -640px; }
.emoji-1F522 { background-position: -120px -640px; }
.emoji-1F523 { background-position: -140px -640px; }
.emoji-1F524 { background-position: -160px -640px; }
.emoji-1F525 { background-position: -180px -640px; }
.emoji-1F526 { background-position: -200px -640px; }
.emoji-1F527 { background-position: -220px -640px; }
.emoji-1F528 { background-position: -240px -640px; }
.emoji-1F529 { background-position: -260px -640px; }
.emoji-1F52A { background-position: -280px -640px; }
.emoji-1F52B { background-position: -300px -640px; }
.emoji-1F52C { background-position: -320px -640px; }
.emoji-1F52D { background-position: -340px -640px; }
.emoji-1F52E { background-position: -360px -640px; }
.emoji-1F52F { background-position: -380px -640px; }
.emoji-1F530 { background-position: -400px -640px; }
.emoji-1F531 { background-position: -420px -640px; }
.emoji-1F532 { background-position: -440px -640px; }
.emoji-1F533 { background-position: -460px -640px; }
.emoji-1F534 { background-position: -480px -640px; }
.emoji-1F535 { background-position: -500px -640px; }
.emoji-1F536 { background-position: -520px -640px; }
.emoji-1F537 { background-position: -540px -640px; }
.emoji-1F538 { background-position: -560px -640px; }
.emoji-1F539 { background-position: -580px -640px; }
.emoji-1F53A { background-position: -600px -640px; }
.emoji-1F53B { background-position: -620px -640px; }
.emoji-1F53C { background-position: -640px -640px; }
.emoji-1F53D { background-position: -660px 0; }
.emoji-1F549 { background-position: -660px -20px; }
.emoji-1F54A { background-position: -660px -40px; }
.emoji-1F54B { background-position: -660px -60px; }
.emoji-1F54C { background-position: -660px -80px; }
.emoji-1F54D { background-position: -660px -100px; }
.emoji-1F54E { background-position: -660px -120px; }
.emoji-1F550 { background-position: -660px -140px; }
.emoji-1F551 { background-position: -660px -160px; }
.emoji-1F552 { background-position: -660px -180px; }
.emoji-1F553 { background-position: -660px -200px; }
.emoji-1F554 { background-position: -660px -220px; }
.emoji-1F555 { background-position: -660px -240px; }
.emoji-1F556 { background-position: -660px -260px; }
.emoji-1F557 { background-position: -660px -280px; }
.emoji-1F558 { background-position: -660px -300px; }
.emoji-1F559 { background-position: -660px -320px; }
.emoji-1F55A { background-position: -660px -340px; }
.emoji-1F55B { background-position: -660px -360px; }
.emoji-1F55C { background-position: -660px -380px; }
.emoji-1F55D { background-position: -660px -400px; }
.emoji-1F55E { background-position: -660px -420px; }
.emoji-1F55F { background-position: -660px -440px; }
.emoji-1F560 { background-position: -660px -460px; }
.emoji-1F561 { background-position: -660px -480px; }
.emoji-1F562 { background-position: -660px -500px; }
.emoji-1F563 { background-position: -660px -520px; }
.emoji-1F564 { background-position: -660px -540px; }
.emoji-1F565 { background-position: -660px -560px; }
.emoji-1F566 { background-position: -660px -580px; }
.emoji-1F567 { background-position: -660px -600px; }
.emoji-1F56F { background-position: -660px -620px; }
.emoji-1F570 { background-position: -660px -640px; }
.emoji-1F573 { background-position: 0 -660px; }
.emoji-1F574 { background-position: -20px -660px; }
.emoji-1F575 { background-position: -40px -660px; }
.emoji-1F575-1F3FB { background-position: -60px -660px; }
.emoji-1F575-1F3FC { background-position: -80px -660px; }
.emoji-1F575-1F3FD { background-position: -100px -660px; }
.emoji-1F575-1F3FE { background-position: -120px -660px; }
.emoji-1F575-1F3FF { background-position: -140px -660px; }
.emoji-1F576 { background-position: -160px -660px; }
.emoji-1F577 { background-position: -180px -660px; }
.emoji-1F578 { background-position: -200px -660px; }
.emoji-1F579 { background-position: -220px -660px; }
.emoji-1F57A { background-position: -240px -660px; }
.emoji-1F57A-1F3FB { background-position: -260px -660px; }
.emoji-1F57A-1F3FC { background-position: -280px -660px; }
.emoji-1F57A-1F3FD { background-position: -300px -660px; }
.emoji-1F57A-1F3FE { background-position: -320px -660px; }
.emoji-1F57A-1F3FF { background-position: -340px -660px; }
.emoji-1F587 { background-position: -360px -660px; }
.emoji-1F58A { background-position: -380px -660px; }
.emoji-1F58B { background-position: -400px -660px; }
.emoji-1F58C { background-position: -420px -660px; }
.emoji-1F58D { background-position: -440px -660px; }
.emoji-1F590 { background-position: -460px -660px; }
.emoji-1F590-1F3FB { background-position: -480px -660px; }
.emoji-1F590-1F3FC { background-position: -500px -660px; }
.emoji-1F590-1F3FD { background-position: -520px -660px; }
.emoji-1F590-1F3FE { background-position: -540px -660px; }
.emoji-1F590-1F3FF { background-position: -560px -660px; }
.emoji-1F595 { background-position: -580px -660px; }
.emoji-1F595-1F3FB { background-position: -600px -660px; }
.emoji-1F595-1F3FC { background-position: -620px -660px; }
.emoji-1F595-1F3FD { background-position: -640px -660px; }
.emoji-1F595-1F3FE { background-position: -660px -660px; }
.emoji-1F595-1F3FF { background-position: -680px 0; }
.emoji-1F596 { background-position: -680px -20px; }
.emoji-1F596-1F3FB { background-position: -680px -40px; }
.emoji-1F596-1F3FC { background-position: -680px -60px; }
.emoji-1F596-1F3FD { background-position: -680px -80px; }
.emoji-1F596-1F3FE { background-position: -680px -100px; }
.emoji-1F596-1F3FF { background-position: -680px -120px; }
.emoji-1F5A4 { background-position: -680px -140px; }
.emoji-1F5A5 { background-position: -680px -160px; }
.emoji-1F5A8 { background-position: -680px -180px; }
.emoji-1F5B1 { background-position: -680px -200px; }
.emoji-1F5B2 { background-position: -680px -220px; }
.emoji-1F5BC { background-position: -680px -240px; }
.emoji-1F5C2 { background-position: -680px -260px; }
.emoji-1F5C3 { background-position: -680px -280px; }
.emoji-1F5C4 { background-position: -680px -300px; }
.emoji-1F5D1 { background-position: -680px -320px; }
.emoji-1F5D2 { background-position: -680px -340px; }
.emoji-1F5D3 { background-position: -680px -360px; }
.emoji-1F5DC { background-position: -680px -380px; }
.emoji-1F5DD { background-position: -680px -400px; }
.emoji-1F5DE { background-position: -680px -420px; }
.emoji-1F5E1 { background-position: -680px -440px; }
.emoji-1F5E3 { background-position: -680px -460px; }
.emoji-1F5EF { background-position: -680px -480px; }
.emoji-1F5F3 { background-position: -680px -500px; }
.emoji-1F5FA { background-position: -680px -520px; }
.emoji-1F5FB { background-position: -680px -540px; }
.emoji-1F5FC { background-position: -680px -560px; }
.emoji-1F5FD { background-position: -680px -580px; }
.emoji-1F5FE { background-position: -680px -600px; }
.emoji-1F5FF { background-position: -680px -620px; }
.emoji-1F600 { background-position: -680px -640px; }
.emoji-1F601 { background-position: -680px -660px; }
.emoji-1F602 { background-position: 0 -680px; }
.emoji-1F603 { background-position: -20px -680px; }
.emoji-1F604 { background-position: -40px -680px; }
.emoji-1F605 { background-position: -60px -680px; }
.emoji-1F606 { background-position: -80px -680px; }
.emoji-1F607 { background-position: -100px -680px; }
.emoji-1F608 { background-position: -120px -680px; }
.emoji-1F609 { background-position: -140px -680px; }
.emoji-1F60A { background-position: -160px -680px; }
.emoji-1F60B { background-position: -180px -680px; }
.emoji-1F60C { background-position: -200px -680px; }
.emoji-1F60D { background-position: -220px -680px; }
.emoji-1F60E { background-position: -240px -680px; }
.emoji-1F60F { background-position: -260px -680px; }
.emoji-1F610 { background-position: -280px -680px; }
.emoji-1F611 { background-position: -300px -680px; }
.emoji-1F612 { background-position: -320px -680px; }
.emoji-1F613 { background-position: -340px -680px; }
.emoji-1F614 { background-position: -360px -680px; }
.emoji-1F615 { background-position: -380px -680px; }
.emoji-1F616 { background-position: -400px -680px; }
.emoji-1F617 { background-position: -420px -680px; }
.emoji-1F618 { background-position: -440px -680px; }
.emoji-1F619 { background-position: -460px -680px; }
.emoji-1F61A { background-position: -480px -680px; }
.emoji-1F61B { background-position: -500px -680px; }
.emoji-1F61C { background-position: -520px -680px; }
.emoji-1F61D { background-position: -540px -680px; }
.emoji-1F61E { background-position: -560px -680px; }
.emoji-1F61F { background-position: -580px -680px; }
.emoji-1F620 { background-position: -600px -680px; }
.emoji-1F621 { background-position: -620px -680px; }
.emoji-1F622 { background-position: -640px -680px; }
.emoji-1F623 { background-position: -660px -680px; }
.emoji-1F624 { background-position: -680px -680px; }
.emoji-1F625 { background-position: -700px 0; }
.emoji-1F626 { background-position: -700px -20px; }
.emoji-1F627 { background-position: -700px -40px; }
.emoji-1F628 { background-position: -700px -60px; }
.emoji-1F629 { background-position: -700px -80px; }
.emoji-1F62A { background-position: -700px -100px; }
.emoji-1F62B { background-position: -700px -120px; }
.emoji-1F62C { background-position: -700px -140px; }
.emoji-1F62D { background-position: -700px -160px; }
.emoji-1F62E { background-position: -700px -180px; }
.emoji-1F62F { background-position: -700px -200px; }
.emoji-1F630 { background-position: -700px -220px; }
.emoji-1F631 { background-position: -700px -240px; }
.emoji-1F632 { background-position: -700px -260px; }
.emoji-1F633 { background-position: -700px -280px; }
.emoji-1F634 { background-position: -700px -300px; }
.emoji-1F635 { background-position: -700px -320px; }
.emoji-1F636 { background-position: -700px -340px; }
.emoji-1F637 { background-position: -700px -360px; }
.emoji-1F638 { background-position: -700px -380px; }
.emoji-1F639 { background-position: -700px -400px; }
.emoji-1F63A { background-position: -700px -420px; }
.emoji-1F63B { background-position: -700px -440px; }
.emoji-1F63C { background-position: -700px -460px; }
.emoji-1F63D { background-position: -700px -480px; }
.emoji-1F63E { background-position: -700px -500px; }
.emoji-1F63F { background-position: -700px -520px; }
.emoji-1F640 { background-position: -700px -540px; }
.emoji-1F641 { background-position: -700px -560px; }
.emoji-1F642 { background-position: -700px -580px; }
.emoji-1F643 { background-position: -700px -600px; }
.emoji-1F644 { background-position: -700px -620px; }
.emoji-1F645 { background-position: -700px -640px; }
.emoji-1F645-1F3FB { background-position: -700px -660px; }
.emoji-1F645-1F3FC { background-position: -700px -680px; }
.emoji-1F645-1F3FD { background-position: 0 -700px; }
.emoji-1F645-1F3FE { background-position: -20px -700px; }
.emoji-1F645-1F3FF { background-position: -40px -700px; }
.emoji-1F646 { background-position: -60px -700px; }
.emoji-1F646-1F3FB { background-position: -80px -700px; }
.emoji-1F646-1F3FC { background-position: -100px -700px; }
.emoji-1F646-1F3FD { background-position: -120px -700px; }
.emoji-1F646-1F3FE { background-position: -140px -700px; }
.emoji-1F646-1F3FF { background-position: -160px -700px; }
.emoji-1F647 { background-position: -180px -700px; }
.emoji-1F647-1F3FB { background-position: -200px -700px; }
.emoji-1F647-1F3FC { background-position: -220px -700px; }
.emoji-1F647-1F3FD { background-position: -240px -700px; }
.emoji-1F647-1F3FE { background-position: -260px -700px; }
.emoji-1F647-1F3FF { background-position: -280px -700px; }
.emoji-1F648 { background-position: -300px -700px; }
.emoji-1F649 { background-position: -320px -700px; }
.emoji-1F64A { background-position: -340px -700px; }
.emoji-1F64B { background-position: -360px -700px; }
.emoji-1F64B-1F3FB { background-position: -380px -700px; }
.emoji-1F64B-1F3FC { background-position: -400px -700px; }
.emoji-1F64B-1F3FD { background-position: -420px -700px; }
.emoji-1F64B-1F3FE { background-position: -440px -700px; }
.emoji-1F64B-1F3FF { background-position: -460px -700px; }
.emoji-1F64C { background-position: -480px -700px; }
.emoji-1F64C-1F3FB { background-position: -500px -700px; }
.emoji-1F64C-1F3FC { background-position: -520px -700px; }
.emoji-1F64C-1F3FD { background-position: -540px -700px; }
.emoji-1F64C-1F3FE { background-position: -560px -700px; }
.emoji-1F64C-1F3FF { background-position: -580px -700px; }
.emoji-1F64D { background-position: -600px -700px; }
.emoji-1F64D-1F3FB { background-position: -620px -700px; }
.emoji-1F64D-1F3FC { background-position: -640px -700px; }
.emoji-1F64D-1F3FD { background-position: -660px -700px; }
.emoji-1F64D-1F3FE { background-position: -680px -700px; }
.emoji-1F64D-1F3FF { background-position: -700px -700px; }
.emoji-1F64E { background-position: -720px 0; }
.emoji-1F64E-1F3FB { background-position: -720px -20px; }
.emoji-1F64E-1F3FC { background-position: -720px -40px; }
.emoji-1F64E-1F3FD { background-position: -720px -60px; }
.emoji-1F64E-1F3FE { background-position: -720px -80px; }
.emoji-1F64E-1F3FF { background-position: -720px -100px; }
.emoji-1F64F { background-position: -720px -120px; }
.emoji-1F64F-1F3FB { background-position: -720px -140px; }
.emoji-1F64F-1F3FC { background-position: -720px -160px; }
.emoji-1F64F-1F3FD { background-position: -720px -180px; }
.emoji-1F64F-1F3FE { background-position: -720px -200px; }
.emoji-1F64F-1F3FF { background-position: -720px -220px; }
.emoji-1F680 { background-position: -720px -240px; }
.emoji-1F681 { background-position: -720px -260px; }
.emoji-1F682 { background-position: -720px -280px; }
.emoji-1F683 { background-position: -720px -300px; }
.emoji-1F684 { background-position: -720px -320px; }
.emoji-1F685 { background-position: -720px -340px; }
.emoji-1F686 { background-position: -720px -360px; }
.emoji-1F687 { background-position: -720px -380px; }
.emoji-1F688 { background-position: -720px -400px; }
.emoji-1F689 { background-position: -720px -420px; }
.emoji-1F68A { background-position: -720px -440px; }
.emoji-1F68B { background-position: -720px -460px; }
.emoji-1F68C { background-position: -720px -480px; }
.emoji-1F68D { background-position: -720px -500px; }
.emoji-1F68E { background-position: -720px -520px; }
.emoji-1F68F { background-position: -720px -540px; }
.emoji-1F690 { background-position: -720px -560px; }
.emoji-1F691 { background-position: -720px -580px; }
.emoji-1F692 { background-position: -720px -600px; }
.emoji-1F693 { background-position: -720px -620px; }
.emoji-1F694 { background-position: -720px -640px; }
.emoji-1F695 { background-position: -720px -660px; }
.emoji-1F696 { background-position: -720px -680px; }
.emoji-1F697 { background-position: -720px -700px; }
.emoji-1F698 { background-position: 0 -720px; }
.emoji-1F699 { background-position: -20px -720px; }
.emoji-1F69A { background-position: -40px -720px; }
.emoji-1F69B { background-position: -60px -720px; }
.emoji-1F69C { background-position: -80px -720px; }
.emoji-1F69D { background-position: -100px -720px; }
.emoji-1F69E { background-position: -120px -720px; }
.emoji-1F69F { background-position: -140px -720px; }
.emoji-1F6A0 { background-position: -160px -720px; }
.emoji-1F6A1 { background-position: -180px -720px; }
.emoji-1F6A2 { background-position: -200px -720px; }
.emoji-1F6A3 { background-position: -220px -720px; }
.emoji-1F6A3-1F3FB { background-position: -240px -720px; }
.emoji-1F6A3-1F3FC { background-position: -260px -720px; }
.emoji-1F6A3-1F3FD { background-position: -280px -720px; }
.emoji-1F6A3-1F3FE { background-position: -300px -720px; }
.emoji-1F6A3-1F3FF { background-position: -320px -720px; }
.emoji-1F6A4 { background-position: -340px -720px; }
.emoji-1F6A5 { background-position: -360px -720px; }
.emoji-1F6A6 { background-position: -380px -720px; }
.emoji-1F6A7 { background-position: -400px -720px; }
.emoji-1F6A8 { background-position: -420px -720px; }
.emoji-1F6A9 { background-position: -440px -720px; }
.emoji-1F6AA { background-position: -460px -720px; }
.emoji-1F6AB { background-position: -480px -720px; }
.emoji-1F6AC { background-position: -500px -720px; }
.emoji-1F6AD { background-position: -520px -720px; }
.emoji-1F6AE { background-position: -540px -720px; }
.emoji-1F6AF { background-position: -560px -720px; }
.emoji-1F6B0 { background-position: -580px -720px; }
.emoji-1F6B1 { background-position: -600px -720px; }
.emoji-1F6B2 { background-position: -620px -720px; }
.emoji-1F6B3 { background-position: -640px -720px; }
.emoji-1F6B4 { background-position: -660px -720px; }
.emoji-1F6B4-1F3FB { background-position: -680px -720px; }
.emoji-1F6B4-1F3FC { background-position: -700px -720px; }
.emoji-1F6B4-1F3FD { background-position: -720px -720px; }
.emoji-1F6B4-1F3FE { background-position: -740px 0; }
.emoji-1F6B4-1F3FF { background-position: -740px -20px; }
.emoji-1F6B5 { background-position: -740px -40px; }
.emoji-1F6B5-1F3FB { background-position: -740px -60px; }
.emoji-1F6B5-1F3FC { background-position: -740px -80px; }
.emoji-1F6B5-1F3FD { background-position: -740px -100px; }
.emoji-1F6B5-1F3FE { background-position: -740px -120px; }
.emoji-1F6B5-1F3FF { background-position: -740px -140px; }
.emoji-1F6B6 { background-position: -740px -160px; }
.emoji-1F6B6-1F3FB { background-position: -740px -180px; }
.emoji-1F6B6-1F3FC { background-position: -740px -200px; }
.emoji-1F6B6-1F3FD { background-position: -740px -220px; }
.emoji-1F6B6-1F3FE { background-position: -740px -240px; }
.emoji-1F6B6-1F3FF { background-position: -740px -260px; }
.emoji-1F6B7 { background-position: -740px -280px; }
.emoji-1F6B8 { background-position: -740px -300px; }
.emoji-1F6B9 { background-position: -740px -320px; }
.emoji-1F6BA { background-position: -740px -340px; }
.emoji-1F6BB { background-position: -740px -360px; }
.emoji-1F6BC { background-position: -740px -380px; }
.emoji-1F6BD { background-position: -740px -400px; }
.emoji-1F6BE { background-position: -740px -420px; }
.emoji-1F6BF { background-position: -740px -440px; }
.emoji-1F6C0 { background-position: -740px -460px; }
.emoji-1F6C0-1F3FB { background-position: -740px -480px; }
.emoji-1F6C0-1F3FC { background-position: -740px -500px; }
.emoji-1F6C0-1F3FD { background-position: -740px -520px; }
.emoji-1F6C0-1F3FE { background-position: -740px -540px; }
.emoji-1F6C0-1F3FF { background-position: -740px -560px; }
.emoji-1F6C1 { background-position: -740px -580px; }
.emoji-1F6C2 { background-position: -740px -600px; }
.emoji-1F6C3 { background-position: -740px -620px; }
.emoji-1F6C4 { background-position: -740px -640px; }
.emoji-1F6C5 { background-position: -740px -660px; }
.emoji-1F6CB { background-position: -740px -680px; }
.emoji-1F6CC { background-position: -740px -700px; }
.emoji-1F6CD { background-position: -740px -720px; }
.emoji-1F6CE { background-position: 0 -740px; }
.emoji-1F6CF { background-position: -20px -740px; }
.emoji-1F6D0 { background-position: -40px -740px; }
.emoji-1F6D1 { background-position: -60px -740px; }
.emoji-1F6D2 { background-position: -80px -740px; }
.emoji-1F6E0 { background-position: -100px -740px; }
.emoji-1F6E1 { background-position: -120px -740px; }
.emoji-1F6E2 { background-position: -140px -740px; }
.emoji-1F6E3 { background-position: -160px -740px; }
.emoji-1F6E4 { background-position: -180px -740px; }
.emoji-1F6E5 { background-position: -200px -740px; }
.emoji-1F6E9 { background-position: -220px -740px; }
.emoji-1F6EB { background-position: -240px -740px; }
.emoji-1F6EC { background-position: -260px -740px; }
.emoji-1F6F0 { background-position: -280px -740px; }
.emoji-1F6F3 { background-position: -300px -740px; }
.emoji-1F6F4 { background-position: -320px -740px; }
.emoji-1F6F5 { background-position: -340px -740px; }
.emoji-1F6F6 { background-position: -360px -740px; }
.emoji-1F910 { background-position: -380px -740px; }
.emoji-1F911 { background-position: -400px -740px; }
.emoji-1F912 { background-position: -420px -740px; }
.emoji-1F913 { background-position: -440px -740px; }
.emoji-1F914 { background-position: -460px -740px; }
.emoji-1F915 { background-position: -480px -740px; }
.emoji-1F916 { background-position: -500px -740px; }
.emoji-1F917 { background-position: -520px -740px; }
.emoji-1F918 { background-position: -540px -740px; }
.emoji-1F918-1F3FB { background-position: -560px -740px; }
.emoji-1F918-1F3FC { background-position: -580px -740px; }
.emoji-1F918-1F3FD { background-position: -600px -740px; }
.emoji-1F918-1F3FE { background-position: -620px -740px; }
.emoji-1F918-1F3FF { background-position: -640px -740px; }
.emoji-1F919 { background-position: -660px -740px; }
.emoji-1F919-1F3FB { background-position: -680px -740px; }
.emoji-1F919-1F3FC { background-position: -700px -740px; }
.emoji-1F919-1F3FD { background-position: -720px -740px; }
.emoji-1F919-1F3FE { background-position: -740px -740px; }
.emoji-1F919-1F3FF { background-position: -760px 0; }
.emoji-1F91A { background-position: -760px -20px; }
.emoji-1F91A-1F3FB { background-position: -760px -40px; }
.emoji-1F91A-1F3FC { background-position: -760px -60px; }
.emoji-1F91A-1F3FD { background-position: -760px -80px; }
.emoji-1F91A-1F3FE { background-position: -760px -100px; }
.emoji-1F91A-1F3FF { background-position: -760px -120px; }
.emoji-1F91B { background-position: -760px -140px; }
.emoji-1F91B-1F3FB { background-position: -760px -160px; }
.emoji-1F91B-1F3FC { background-position: -760px -180px; }
.emoji-1F91B-1F3FD { background-position: -760px -200px; }
.emoji-1F91B-1F3FE { background-position: -760px -220px; }
.emoji-1F91B-1F3FF { background-position: -760px -240px; }
.emoji-1F91C { background-position: -760px -260px; }
.emoji-1F91C-1F3FB { background-position: -760px -280px; }
.emoji-1F91C-1F3FC { background-position: -760px -300px; }
.emoji-1F91C-1F3FD { background-position: -760px -320px; }
.emoji-1F91C-1F3FE { background-position: -760px -340px; }
.emoji-1F91C-1F3FF { background-position: -760px -360px; }
.emoji-1F91D { background-position: -760px -380px; }
.emoji-1F91D-1F3FB { background-position: -760px -400px; }
.emoji-1F91D-1F3FC { background-position: -760px -420px; }
.emoji-1F91D-1F3FD { background-position: -760px -440px; }
.emoji-1F91D-1F3FE { background-position: -760px -460px; }
.emoji-1F91D-1F3FF { background-position: -760px -480px; }
.emoji-1F91E { background-position: -760px -500px; }
.emoji-1F91E-1F3FB { background-position: -760px -520px; }
.emoji-1F91E-1F3FC { background-position: -760px -540px; }
.emoji-1F91E-1F3FD { background-position: -760px -560px; }
.emoji-1F91E-1F3FE { background-position: -760px -580px; }
.emoji-1F91E-1F3FF { background-position: -760px -600px; }
.emoji-1F920 { background-position: -760px -620px; }
.emoji-1F921 { background-position: -760px -640px; }
.emoji-1F922 { background-position: -760px -660px; }
.emoji-1F923 { background-position: -760px -680px; }
.emoji-1F924 { background-position: -760px -700px; }
.emoji-1F925 { background-position: -760px -720px; }
.emoji-1F926 { background-position: -760px -740px; }
.emoji-1F926-1F3FB { background-position: 0 -760px; }
.emoji-1F926-1F3FC { background-position: -20px -760px; }
.emoji-1F926-1F3FD { background-position: -40px -760px; }
.emoji-1F926-1F3FE { background-position: -60px -760px; }
.emoji-1F926-1F3FF { background-position: -80px -760px; }
.emoji-1F927 { background-position: -100px -760px; }
.emoji-1F930 { background-position: -120px -760px; }
.emoji-1F930-1F3FB { background-position: -140px -760px; }
.emoji-1F930-1F3FC { background-position: -160px -760px; }
.emoji-1F930-1F3FD { background-position: -180px -760px; }
.emoji-1F930-1F3FE { background-position: -200px -760px; }
.emoji-1F930-1F3FF { background-position: -220px -760px; }
.emoji-1F933 { background-position: -240px -760px; }
.emoji-1F933-1F3FB { background-position: -260px -760px; }
.emoji-1F933-1F3FC { background-position: -280px -760px; }
.emoji-1F933-1F3FD { background-position: -300px -760px; }
.emoji-1F933-1F3FE { background-position: -320px -760px; }
.emoji-1F933-1F3FF { background-position: -340px -760px; }
.emoji-1F934 { background-position: -360px -760px; }
.emoji-1F934-1F3FB { background-position: -380px -760px; }
.emoji-1F934-1F3FC { background-position: -400px -760px; }
.emoji-1F934-1F3FD { background-position: -420px -760px; }
.emoji-1F934-1F3FE { background-position: -440px -760px; }
.emoji-1F934-1F3FF { background-position: -460px -760px; }
.emoji-1F935 { background-position: -480px -760px; }
.emoji-1F935-1F3FB { background-position: -500px -760px; }
.emoji-1F935-1F3FC { background-position: -520px -760px; }
.emoji-1F935-1F3FD { background-position: -540px -760px; }
.emoji-1F935-1F3FE { background-position: -560px -760px; }
.emoji-1F935-1F3FF { background-position: -580px -760px; }
.emoji-1F936 { background-position: -600px -760px; }
.emoji-1F936-1F3FB { background-position: -620px -760px; }
.emoji-1F936-1F3FC { background-position: -640px -760px; }
.emoji-1F936-1F3FD { background-position: -660px -760px; }
.emoji-1F936-1F3FE { background-position: -680px -760px; }
.emoji-1F936-1F3FF { background-position: -700px -760px; }
.emoji-1F937 { background-position: -720px -760px; }
.emoji-1F937-1F3FB { background-position: -740px -760px; }
.emoji-1F937-1F3FC { background-position: -760px -760px; }
.emoji-1F937-1F3FD { background-position: -780px 0; }
.emoji-1F937-1F3FE { background-position: -780px -20px; }
.emoji-1F937-1F3FF { background-position: -780px -40px; }
.emoji-1F938 { background-position: -780px -60px; }
.emoji-1F938-1F3FB { background-position: -780px -80px; }
.emoji-1F938-1F3FC { background-position: -780px -100px; }
.emoji-1F938-1F3FD { background-position: -780px -120px; }
.emoji-1F938-1F3FE { background-position: -780px -140px; }
.emoji-1F938-1F3FF { background-position: -780px -160px; }
.emoji-1F939 { background-position: -780px -180px; }
.emoji-1F939-1F3FB { background-position: -780px -200px; }
.emoji-1F939-1F3FC { background-position: -780px -220px; }
.emoji-1F939-1F3FD { background-position: -780px -240px; }
.emoji-1F939-1F3FE { background-position: -780px -260px; }
.emoji-1F939-1F3FF { background-position: -780px -280px; }
.emoji-1F93A { background-position: -780px -300px; }
.emoji-1F93C { background-position: -780px -320px; }
.emoji-1F93C-1F3FB { background-position: -780px -340px; }
.emoji-1F93C-1F3FC { background-position: -780px -360px; }
.emoji-1F93C-1F3FD { background-position: -780px -380px; }
.emoji-1F93C-1F3FE { background-position: -780px -400px; }
.emoji-1F93C-1F3FF { background-position: -780px -420px; }
.emoji-1F93D { background-position: -780px -440px; }
.emoji-1F93D-1F3FB { background-position: -780px -460px; }
.emoji-1F93D-1F3FC { background-position: -780px -480px; }
.emoji-1F93D-1F3FD { background-position: -780px -500px; }
.emoji-1F93D-1F3FE { background-position: -780px -520px; }
.emoji-1F93D-1F3FF { background-position: -780px -540px; }
.emoji-1F93E { background-position: -780px -560px; }
.emoji-1F93E-1F3FB { background-position: -780px -580px; }
.emoji-1F93E-1F3FC { background-position: -780px -600px; }
.emoji-1F93E-1F3FD { background-position: -780px -620px; }
.emoji-1F93E-1F3FE { background-position: -780px -640px; }
.emoji-1F93E-1F3FF { background-position: -780px -660px; }
.emoji-1F940 { background-position: -780px -680px; }
.emoji-1F941 { background-position: -780px -700px; }
.emoji-1F942 { background-position: -780px -720px; }
.emoji-1F943 { background-position: -780px -740px; }
.emoji-1F944 { background-position: -780px -760px; }
.emoji-1F945 { background-position: 0 -780px; }
.emoji-1F947 { background-position: -20px -780px; }
.emoji-1F948 { background-position: -40px -780px; }
.emoji-1F949 { background-position: -60px -780px; }
.emoji-1F94A { background-position: -80px -780px; }
.emoji-1F94B { background-position: -100px -780px; }
.emoji-1F950 { background-position: -120px -780px; }
.emoji-1F951 { background-position: -140px -780px; }
.emoji-1F952 { background-position: -160px -780px; }
.emoji-1F953 { background-position: -180px -780px; }
.emoji-1F954 { background-position: -200px -780px; }
.emoji-1F955 { background-position: -220px -780px; }
.emoji-1F956 { background-position: -240px -780px; }
.emoji-1F957 { background-position: -260px -780px; }
.emoji-1F958 { background-position: -280px -780px; }
.emoji-1F959 { background-position: -300px -780px; }
.emoji-1F95A { background-position: -320px -780px; }
.emoji-1F95B { background-position: -340px -780px; }
.emoji-1F95C { background-position: -360px -780px; }
.emoji-1F95D { background-position: -380px -780px; }
.emoji-1F95E { background-position: -400px -780px; }
.emoji-1F980 { background-position: -420px -780px; }
.emoji-1F981 { background-position: -440px -780px; }
.emoji-1F982 { background-position: -460px -780px; }
.emoji-1F983 { background-position: -480px -780px; }
.emoji-1F984 { background-position: -500px -780px; }
.emoji-1F985 { background-position: -520px -780px; }
.emoji-1F986 { background-position: -540px -780px; }
.emoji-1F987 { background-position: -560px -780px; }
.emoji-1F988 { background-position: -580px -780px; }
.emoji-1F989 { background-position: -600px -780px; }
.emoji-1F98A { background-position: -620px -780px; }
.emoji-1F98B { background-position: -640px -780px; }
.emoji-1F98C { background-position: -660px -780px; }
.emoji-1F98D { background-position: -680px -780px; }
.emoji-1F98E { background-position: -700px -780px; }
.emoji-1F98F { background-position: -720px -780px; }
.emoji-1F990 { background-position: -740px -780px; }
.emoji-1F991 { background-position: -760px -780px; }
.emoji-1F9C0 { background-position: -780px -780px; }
.emoji-203C { background-position: -800px 0; }
.emoji-2049 { background-position: -800px -20px; }
.emoji-2122 { background-position: -800px -40px; }
.emoji-2139 { background-position: -800px -60px; }
.emoji-2194 { background-position: -800px -80px; }
.emoji-2195 { background-position: -800px -100px; }
.emoji-2196 { background-position: -800px -120px; }
.emoji-2197 { background-position: -800px -140px; }
.emoji-2198 { background-position: -800px -160px; }
.emoji-2199 { background-position: -800px -180px; }
.emoji-21A9 { background-position: -800px -200px; }
.emoji-21AA { background-position: -800px -220px; }
.emoji-231A { background-position: -800px -240px; }
.emoji-231B { background-position: -800px -260px; }
.emoji-2328 { background-position: -800px -280px; }
.emoji-23CF { background-position: -800px -300px; }
.emoji-23E9 { background-position: -800px -320px; }
.emoji-23EA { background-position: -800px -340px; }
.emoji-23EB { background-position: -800px -360px; }
.emoji-23EC { background-position: -800px -380px; }
.emoji-23ED { background-position: -800px -400px; }
.emoji-23EE { background-position: -800px -420px; }
.emoji-23EF { background-position: -800px -440px; }
.emoji-23F0 { background-position: -800px -460px; }
.emoji-23F1 { background-position: -800px -480px; }
.emoji-23F2 { background-position: -800px -500px; }
.emoji-23F3 { background-position: -800px -520px; }
.emoji-23F8 { background-position: -800px -540px; }
.emoji-23F9 { background-position: -800px -560px; }
.emoji-23FA { background-position: -800px -580px; }
.emoji-24C2 { background-position: -800px -600px; }
.emoji-25AA { background-position: -800px -620px; }
.emoji-25AB { background-position: -800px -640px; }
.emoji-25B6 { background-position: -800px -660px; }
.emoji-25C0 { background-position: -800px -680px; }
.emoji-25FB { background-position: -800px -700px; }
.emoji-25FC { background-position: -800px -720px; }
.emoji-25FD { background-position: -800px -740px; }
.emoji-25FE { background-position: -800px -760px; }
.emoji-2600 { background-position: -800px -780px; }
.emoji-2601 { background-position: 0 -800px; }
.emoji-2602 { background-position: -20px -800px; }
.emoji-2603 { background-position: -40px -800px; }
.emoji-2604 { background-position: -60px -800px; }
.emoji-260E { background-position: -80px -800px; }
.emoji-2611 { background-position: -100px -800px; }
.emoji-2614 { background-position: -120px -800px; }
.emoji-2615 { background-position: -140px -800px; }
.emoji-2618 { background-position: -160px -800px; }
.emoji-261D { background-position: -180px -800px; }
.emoji-261D-1F3FB { background-position: -200px -800px; }
.emoji-261D-1F3FC { background-position: -220px -800px; }
.emoji-261D-1F3FD { background-position: -240px -800px; }
.emoji-261D-1F3FE { background-position: -260px -800px; }
.emoji-261D-1F3FF { background-position: -280px -800px; }
.emoji-2620 { background-position: -300px -800px; }
.emoji-2622 { background-position: -320px -800px; }
.emoji-2623 { background-position: -340px -800px; }
.emoji-2626 { background-position: -360px -800px; }
.emoji-262A { background-position: -380px -800px; }
.emoji-262E { background-position: -400px -800px; }
.emoji-262F { background-position: -420px -800px; }
.emoji-2638 { background-position: -440px -800px; }
.emoji-2639 { background-position: -460px -800px; }
.emoji-263A { background-position: -480px -800px; }
.emoji-2648 { background-position: -500px -800px; }
.emoji-2649 { background-position: -520px -800px; }
.emoji-264A { background-position: -540px -800px; }
.emoji-264B { background-position: -560px -800px; }
.emoji-264C { background-position: -580px -800px; }
.emoji-264D { background-position: -600px -800px; }
.emoji-264E { background-position: -620px -800px; }
.emoji-264F { background-position: -640px -800px; }
.emoji-2650 { background-position: -660px -800px; }
.emoji-2651 { background-position: -680px -800px; }
.emoji-2652 { background-position: -700px -800px; }
.emoji-2653 { background-position: -720px -800px; }
.emoji-2660 { background-position: -740px -800px; }
.emoji-2663 { background-position: -760px -800px; }
.emoji-2665 { background-position: -780px -800px; }
.emoji-2666 { background-position: -800px -800px; }
.emoji-2668 { background-position: -820px 0; }
.emoji-267B { background-position: -820px -20px; }
.emoji-267F { background-position: -820px -40px; }
.emoji-2692 { background-position: -820px -60px; }
.emoji-2693 { background-position: -820px -80px; }
.emoji-2694 { background-position: -820px -100px; }
.emoji-2696 { background-position: -820px -120px; }
.emoji-2697 { background-position: -820px -140px; }
.emoji-2699 { background-position: -820px -160px; }
.emoji-269B { background-position: -820px -180px; }
.emoji-269C { background-position: -820px -200px; }
.emoji-26A0 { background-position: -820px -220px; }
.emoji-26A1 { background-position: -820px -240px; }
.emoji-26AA { background-position: -820px -260px; }
.emoji-26AB { background-position: -820px -280px; }
.emoji-26B0 { background-position: -820px -300px; }
.emoji-26B1 { background-position: -820px -320px; }
.emoji-26BD { background-position: -820px -340px; }
.emoji-26BE { background-position: -820px -360px; }
.emoji-26C4 { background-position: -820px -380px; }
.emoji-26C5 { background-position: -820px -400px; }
.emoji-26C8 { background-position: -820px -420px; }
.emoji-26CE { background-position: -820px -440px; }
.emoji-26CF { background-position: -820px -460px; }
.emoji-26D1 { background-position: -820px -480px; }
.emoji-26D3 { background-position: -820px -500px; }
.emoji-26D4 { background-position: -820px -520px; }
.emoji-26E9 { background-position: -820px -540px; }
.emoji-26EA { background-position: -820px -560px; }
.emoji-26F0 { background-position: -820px -580px; }
.emoji-26F1 { background-position: -820px -600px; }
.emoji-26F2 { background-position: -820px -620px; }
.emoji-26F3 { background-position: -820px -640px; }
.emoji-26F4 { background-position: -820px -660px; }
.emoji-26F5 { background-position: -820px -680px; }
.emoji-26F7 { background-position: -820px -700px; }
.emoji-26F8 { background-position: -820px -720px; }
.emoji-26F9 { background-position: -820px -740px; }
.emoji-26F9-1F3FB { background-position: -820px -760px; }
.emoji-26F9-1F3FC { background-position: -820px -780px; }
.emoji-26F9-1F3FD { background-position: -820px -800px; }
.emoji-26F9-1F3FE { background-position: 0 -820px; }
.emoji-26F9-1F3FF { background-position: -20px -820px; }
.emoji-26FA { background-position: -40px -820px; }
.emoji-26FD { background-position: -60px -820px; }
.emoji-2702 { background-position: -80px -820px; }
.emoji-2705 { background-position: -100px -820px; }
.emoji-2708 { background-position: -120px -820px; }
.emoji-2709 { background-position: -140px -820px; }
.emoji-270A { background-position: -160px -820px; }
.emoji-270A-1F3FB { background-position: -180px -820px; }
.emoji-270A-1F3FC { background-position: -200px -820px; }
.emoji-270A-1F3FD { background-position: -220px -820px; }
.emoji-270A-1F3FE { background-position: -240px -820px; }
.emoji-270A-1F3FF { background-position: -260px -820px; }
.emoji-270B { background-position: -280px -820px; }
.emoji-270B-1F3FB { background-position: -300px -820px; }
.emoji-270B-1F3FC { background-position: -320px -820px; }
.emoji-270B-1F3FD { background-position: -340px -820px; }
.emoji-270B-1F3FE { background-position: -360px -820px; }
.emoji-270B-1F3FF { background-position: -380px -820px; }
.emoji-270C { background-position: -400px -820px; }
.emoji-270C-1F3FB { background-position: -420px -820px; }
.emoji-270C-1F3FC { background-position: -440px -820px; }
.emoji-270C-1F3FD { background-position: -460px -820px; }
.emoji-270C-1F3FE { background-position: -480px -820px; }
.emoji-270C-1F3FF { background-position: -500px -820px; }
.emoji-270D { background-position: -520px -820px; }
.emoji-270D-1F3FB { background-position: -540px -820px; }
.emoji-270D-1F3FC { background-position: -560px -820px; }
.emoji-270D-1F3FD { background-position: -580px -820px; }
.emoji-270D-1F3FE { background-position: -600px -820px; }
.emoji-270D-1F3FF { background-position: -620px -820px; }
.emoji-270F { background-position: -640px -820px; }
.emoji-2712 { background-position: -660px -820px; }
.emoji-2714 { background-position: -680px -820px; }
.emoji-2716 { background-position: -700px -820px; }
.emoji-271D { background-position: -720px -820px; }
.emoji-2721 { background-position: -740px -820px; }
.emoji-2728 { background-position: -760px -820px; }
.emoji-2733 { background-position: -780px -820px; }
.emoji-2734 { background-position: -800px -820px; }
.emoji-2744 { background-position: -820px -820px; }
.emoji-2747 { background-position: -840px 0; }
.emoji-274C { background-position: -840px -20px; }
.emoji-274E { background-position: -840px -40px; }
.emoji-2753 { background-position: -840px -60px; }
.emoji-2754 { background-position: -840px -80px; }
.emoji-2755 { background-position: -840px -100px; }
.emoji-2757 { background-position: -840px -120px; }
.emoji-2763 { background-position: -840px -140px; }
.emoji-2764 { background-position: -840px -160px; }
.emoji-2795 { background-position: -840px -180px; }
.emoji-2796 { background-position: -840px -200px; }
.emoji-2797 { background-position: -840px -220px; }
.emoji-27A1 { background-position: -840px -240px; }
.emoji-27B0 { background-position: -840px -260px; }
.emoji-27BF { background-position: -840px -280px; }
.emoji-2934 { background-position: -840px -300px; }
.emoji-2935 { background-position: -840px -320px; }
.emoji-2B05 { background-position: -840px -340px; }
.emoji-2B06 { background-position: -840px -360px; }
.emoji-2B07 { background-position: -840px -380px; }
.emoji-2B1B { background-position: -840px -400px; }
.emoji-2B1C { background-position: -840px -420px; }
.emoji-2B50 { background-position: -840px -440px; }
.emoji-2B55 { background-position: -840px -460px; }
.emoji-3030 { background-position: -840px -480px; }
.emoji-303D { background-position: -840px -500px; }
.emoji-3297 { background-position: -840px -520px; }
.emoji-3299 { background-position: -840px -540px; }
.emoji-icon {
background-image: image-url('emoji.png');
background-repeat: no-repeat;
height: 20px;
width: 20px;
@media only screen and (-webkit-min-device-pixel-ratio: 2),
only screen and (min--moz-device-pixel-ratio: 2),
only screen and (-o-min-device-pixel-ratio: 2/1),
only screen and (min-device-pixel-ratio: 2),
only screen and (min-resolution: 192dpi),
only screen and (min-resolution: 2dppx) {
background-image: image-url('emoji@2x.png');
background-size: 860px 840px;
}
} }
...@@ -147,6 +147,9 @@ ...@@ -147,6 +147,9 @@
} }
.atwho-view { .atwho-view {
overflow-y: auto;
overflow-x: hidden;
small.description { small.description {
float: right; float: right;
padding: 3px 5px; padding: 3px 5px;
...@@ -162,4 +165,8 @@ ...@@ -162,4 +165,8 @@
@include disableAllAnimation; @include disableAllAnimation;
} }
} }
ul > li {
white-space: nowrap;
}
} }
...@@ -248,7 +248,7 @@ $diff-view-modes-border: #c1c1c1; ...@@ -248,7 +248,7 @@ $diff-view-modes-border: #c1c1c1;
* Fonts * Fonts
*/ */
$monospace_font: 'Menlo', 'Liberation Mono', 'Consolas', 'DejaVu Sans Mono', 'Ubuntu Mono', 'Courier New', 'andale mono', 'lucida console', monospace; $monospace_font: 'Menlo', 'Liberation Mono', 'Consolas', 'DejaVu Sans Mono', 'Ubuntu Mono', 'Courier New', 'andale mono', 'lucida console', monospace;
$regular_font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; $regular_font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
/* /*
* Dropdowns * Dropdowns
......
class EmojisController < ApplicationController
layout false
def index
end
end
class Projects::AutocompleteSourcesController < Projects::ApplicationController class Projects::AutocompleteSourcesController < Projects::ApplicationController
before_action :load_autocomplete_service, except: [:emojis, :members] before_action :load_autocomplete_service, except: [:members]
def emojis
render json: Gitlab::AwardEmoji.urls
end
def members def members
render json: ::Projects::ParticipantsService.new(@project, current_user).execute(noteable) render json: ::Projects::ParticipantsService.new(@project, current_user).execute(noteable)
......
module EmojiHelper
def emoji_icon(*args)
raw Gitlab::Emoji.gl_emoji_tag(*args)
end
end
...@@ -87,34 +87,6 @@ module IssuesHelper ...@@ -87,34 +87,6 @@ module IssuesHelper
icon('eye-slash') if issue.confidential? icon('eye-slash') if issue.confidential?
end end
def emoji_icon(name, unicode = nil, aliases = [], sprite: true)
unicode ||= Gitlab::Emoji.emoji_filename(name) rescue ""
data = {
aliases: aliases.join(" "),
emoji: name,
unicode_name: unicode
}
if sprite
# Emoji icons for the emoji menu, these use a spritesheet.
content_tag :div, "",
class: "icon emoji-icon emoji-#{unicode}",
title: name,
data: data
else
# Emoji icons displayed separately, used for the awards already given
# to an issue or merge request.
content_tag :img, "",
class: "icon emoji",
title: name,
height: "20px",
width: "20px",
src: url_to_image("#{unicode}.png"),
data: data
end
end
def award_user_list(awards, current_user, limit: 10) def award_user_list(awards, current_user, limit: 10)
names = awards.map do |award| names = awards.map do |award|
award.user == current_user ? 'You' : award.user.name award.user == current_user ? 'You' : award.user.name
......
...@@ -101,6 +101,6 @@ module Awardable ...@@ -101,6 +101,6 @@ module Awardable
private private
def normalize_name(name) def normalize_name(name)
Gitlab::AwardEmoji.normalize_emoji_name(name) Gitlab::Emoji.normalize_emoji_name(name)
end end
end end
...@@ -16,4 +16,4 @@ ...@@ -16,4 +16,4 @@
- else - else
.empty-state .empty-state
.text-center .text-center
%h4 There are no abuse reports! #{emoji_icon 'tada'} %h4 There are no abuse reports! #{emoji_icon('tada')}
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
%button.btn.award-control.js-emoji-btn.has-tooltip{ type: "button", %button.btn.award-control.js-emoji-btn.has-tooltip{ type: "button",
class: (award_state_class(awards, current_user)), class: (award_state_class(awards, current_user)),
data: { placement: "bottom", title: award_user_list(awards, current_user) } } data: { placement: "bottom", title: award_user_list(awards, current_user) } }
= emoji_icon(emoji, sprite: false) = emoji_icon(emoji)
%span.award-control-text.js-counter %span.award-control-text.js-counter
= awards.count = awards.count
......
.emoji-menu
= text_field_tag :emoji_search, "", class: "emoji-search search-input form-control", placeholder: "Search emoji"
.emoji-menu-content
- Gitlab::AwardEmoji.emoji_by_category.each do |category, emojis|
%h5.emoji-menu-title
= Gitlab::AwardEmoji::CATEGORIES[category]
%ul.clearfix.emoji-menu-list
- emojis.each do |emoji|
%li.pull-left.text-center.emoji-menu-list-item
%button.emoji-menu-btn.text-center.js-emoji-btn{ type: "button" }
= emoji_icon(emoji["name"], emoji["unicode"], emoji["aliases"])
...@@ -4,7 +4,6 @@ ...@@ -4,7 +4,6 @@
- if project - if project
:javascript :javascript
gl.GfmAutoComplete.dataSources = { gl.GfmAutoComplete.dataSources = {
emojis: "#{emojis_namespace_project_autocomplete_sources_path(project.namespace, project)}",
members: "#{members_namespace_project_autocomplete_sources_path(project.namespace, project, type: noteable_type, type_id: params[:id])}", members: "#{members_namespace_project_autocomplete_sources_path(project.namespace, project, type: noteable_type, type_id: params[:id])}",
issues: "#{issues_namespace_project_autocomplete_sources_path(project.namespace, project)}", issues: "#{issues_namespace_project_autocomplete_sources_path(project.namespace, project)}",
mergeRequests: "#{merge_requests_namespace_project_autocomplete_sources_path(project.namespace, project)}", mergeRequests: "#{merge_requests_namespace_project_autocomplete_sources_path(project.namespace, project)}",
......
---
title: Use native unicode emojis
merge_request:
author:
...@@ -91,7 +91,6 @@ module Gitlab ...@@ -91,7 +91,6 @@ module Gitlab
# Enable the asset pipeline # Enable the asset pipeline
config.assets.enabled = true config.assets.enabled = true
config.assets.paths << Gemojione.images_path
config.assets.paths << "vendor/assets/fonts" config.assets.paths << "vendor/assets/fonts"
config.assets.precompile << "*.png" config.assets.precompile << "*.png"
config.assets.precompile << "print.css" config.assets.precompile << "print.css"
......
...@@ -27,9 +27,6 @@ Rails.application.routes.draw do ...@@ -27,9 +27,6 @@ Rails.application.routes.draw do
get '/autocomplete/users/:id' => 'autocomplete#user' get '/autocomplete/users/:id' => 'autocomplete#user'
get '/autocomplete/projects' => 'autocomplete#projects' get '/autocomplete/projects' => 'autocomplete#projects'
# Emojis
resources :emojis, only: :index
# Search # Search
get 'search' => 'search#show' get 'search' => 'search#show'
get 'search/autocomplete' => 'search#autocomplete', as: :search_autocomplete get 'search/autocomplete' => 'search#autocomplete', as: :search_autocomplete
......
...@@ -13,7 +13,6 @@ constraints(ProjectUrlConstrainer.new) do ...@@ -13,7 +13,6 @@ constraints(ProjectUrlConstrainer.new) do
resources :autocomplete_sources, only: [] do resources :autocomplete_sources, only: [] do
collection do collection do
get 'emojis'
get 'members' get 'members'
get 'issues' get 'issues'
get 'merge_requests' get 'merge_requests'
......
...@@ -132,6 +132,7 @@ var config = { ...@@ -132,6 +132,7 @@ var config = {
extensions: ['.js', '.es6', '.js.es6'], extensions: ['.js', '.es6', '.js.es6'],
alias: { alias: {
'~': path.join(ROOT_PATH, 'app/assets/javascripts'), '~': path.join(ROOT_PATH, 'app/assets/javascripts'),
'emoji-map$': path.join(ROOT_PATH, 'fixtures/emojis/digests.json'),
'emoji-aliases$': path.join(ROOT_PATH, 'fixtures/emojis/aliases.json'), 'emoji-aliases$': path.join(ROOT_PATH, 'fixtures/emojis/aliases.json'),
'icons': path.join(ROOT_PATH, 'app/views/shared/icons'), 'icons': path.join(ROOT_PATH, 'app/views/shared/icons'),
'vendor': path.join(ROOT_PATH, 'vendor/assets/javascripts'), 'vendor': path.join(ROOT_PATH, 'vendor/assets/javascripts'),
......
require './spec/support/sidekiq' require './spec/support/sidekiq'
Gitlab::Seeder.quiet do Gitlab::Seeder.quiet do
emoji = Gitlab::AwardEmoji.emojis.keys emoji = Gitlab::Emoji.emojis.keys
Issue.order(Gitlab::Database.random).limit(Issue.count / 2).each do |issue| Issue.order(Gitlab::Database.random).limit(Issue.count / 2).each do |issue|
project = issue.project project = issue.project
......
...@@ -90,7 +90,7 @@ class Spinach::Features::AwardEmoji < Spinach::FeatureSteps ...@@ -90,7 +90,7 @@ class Spinach::Features::AwardEmoji < Spinach::FeatureSteps
step 'I see search result for "hand"' do step 'I see search result for "hand"' do
page.within '.emoji-menu-content' do page.within '.emoji-menu-content' do
expect(page).to have_selector '[data-emoji="raised_hand"]' expect(page).to have_selector '[data-name="raised_hand"]'
end end
end end
......
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -17,8 +17,8 @@ module Banzai ...@@ -17,8 +17,8 @@ module Banzai
next unless content.include?(':') || node.text.match(emoji_unicode_pattern) next unless content.include?(':') || node.text.match(emoji_unicode_pattern)
html = emoji_name_image_filter(content) html = emoji_unicode_element_unicode_filter(content)
html = emoji_unicode_image_filter(html) html = emoji_name_element_unicode_filter(html)
next if html == content next if html == content
...@@ -27,33 +27,30 @@ module Banzai ...@@ -27,33 +27,30 @@ module Banzai
doc doc
end end
# Replace :emoji: with corresponding images. # Replace :emoji: with corresponding gl-emoji unicode.
# #
# text - String text to replace :emoji: in. # text - String text to replace :emoji: in.
# #
# Returns a String with :emoji: replaced with images. # Returns a String with :emoji: replaced with gl-emoji unicode.
def emoji_name_image_filter(text) def emoji_name_element_unicode_filter(text)
text.gsub(emoji_pattern) do |match| text.gsub(emoji_pattern) do |match|
name = $1 name = $1
emoji_image_tag(name, emoji_url(name)) Gitlab::Emoji.gl_emoji_tag(name)
end end
end end
# Replace unicode emoji with corresponding images if they exist. # Replace unicode emoji with corresponding gl-emoji unicode.
# #
# text - String text to replace unicode emoji in. # text - String text to replace unicode emoji in.
# #
# Returns a String with unicode emoji replaced with images. # Returns a String with unicode emoji replaced with gl-emoji unicode.
def emoji_unicode_image_filter(text) def emoji_unicode_element_unicode_filter(text)
text.gsub(emoji_unicode_pattern) do |moji| text.gsub(emoji_unicode_pattern) do |moji|
emoji_image_tag(Gitlab::Emoji.emojis_by_moji[moji]['name'], emoji_unicode_url(moji)) emoji_info = Gitlab::Emoji.emojis_by_moji[moji]
Gitlab::Emoji.gl_emoji_tag(emoji_info['name'])
end end
end end
def emoji_image_tag(emoji_name, emoji_url)
"<img class='emoji' title=':#{emoji_name}:' alt=':#{emoji_name}:' src='#{emoji_url}' height='20' width='20' align='absmiddle' />"
end
# Build a regexp that matches all valid :emoji: names. # Build a regexp that matches all valid :emoji: names.
def self.emoji_pattern def self.emoji_pattern
@emoji_pattern ||= /:(#{Gitlab::Emoji.emojis_names.map { |name| Regexp.escape(name) }.join('|')}):/ @emoji_pattern ||= /:(#{Gitlab::Emoji.emojis_names.map { |name| Regexp.escape(name) }.join('|')}):/
...@@ -66,52 +63,13 @@ module Banzai ...@@ -66,52 +63,13 @@ module Banzai
private private
def emoji_url(name)
emoji_path = emoji_filename(name)
if context[:asset_host]
# Asset host is specified.
url_to_image(emoji_path)
elsif context[:asset_root]
# Gitlab url is specified
File.join(context[:asset_root], url_to_image(emoji_path))
else
# All other cases
url_to_image(emoji_path)
end
end
def emoji_unicode_url(moji)
emoji_unicode_path = emoji_unicode_filename(moji)
if context[:asset_host]
url_to_image(emoji_unicode_path)
elsif context[:asset_root]
File.join(context[:asset_root], url_to_image(emoji_unicode_path))
else
url_to_image(emoji_unicode_path)
end
end
def url_to_image(image)
ActionController::Base.helpers.url_to_image(image)
end
def emoji_pattern def emoji_pattern
self.class.emoji_pattern self.class.emoji_pattern
end end
def emoji_filename(name)
"#{Gitlab::Emoji.emoji_filename(name)}.png"
end
def emoji_unicode_pattern def emoji_unicode_pattern
self.class.emoji_unicode_pattern self.class.emoji_unicode_pattern
end end
def emoji_unicode_filename(name)
"#{Gitlab::Emoji.emoji_unicode_filename(name)}.png"
end
end end
end end
end end
module Gitlab
class AwardEmoji
CATEGORIES = {
objects: "Objects",
travel: "Travel",
symbols: "Symbols",
nature: "Nature",
people: "People",
activity: "Activity",
flags: "Flags",
food: "Food"
}.with_indifferent_access
def self.normalize_emoji_name(name)
aliases[name] || name
end
def self.emoji_by_category
unless @emoji_by_category
@emoji_by_category = Hash.new { |h, key| h[key] = [] }
emojis.each do |emoji_name, data|
data["name"] = emoji_name
# Skip Fitzpatrick(tone) modifiers
next if data["category"] == "modifier"
category = data["category"]
@emoji_by_category[category] << data
end
@emoji_by_category = @emoji_by_category.sort.to_h
end
@emoji_by_category
end
def self.emojis
@emojis ||=
begin
json_path = File.join(Rails.root, 'fixtures', 'emojis', 'index.json' )
JSON.parse(File.read(json_path))
end
end
def self.aliases
@aliases ||=
begin
json_path = File.join(Rails.root, 'fixtures', 'emojis', 'aliases.json')
JSON.parse(File.read(json_path))
end
end
# Returns an Array of Emoji names and their asset URLs.
def self.urls
@urls ||= begin
path = File.join(Rails.root, 'fixtures', 'emojis', 'digests.json')
# Construct the full asset path ourselves because
# ActionView::Helpers::AssetUrlHelper.asset_url is slow for hundreds
# of entries since it has to do a lot of extra work (e.g. regexps).
prefix = Gitlab::Application.config.assets.prefix
digest = Gitlab::Application.config.assets.digest
base =
if defined?(Gitlab::Application.config.relative_url_root) && Gitlab::Application.config.relative_url_root
Gitlab::Application.config.relative_url_root
else
''
end
JSON.parse(File.read(path)).map do |hash|
fname =
if digest
"#{hash['unicode']}-#{hash['digest']}"
else
hash['unicode']
end
{ name: hash['name'], path: File.join(base, prefix, "#{fname}.png") }
end
end
end
end
end
...@@ -18,6 +18,10 @@ module Gitlab ...@@ -18,6 +18,10 @@ module Gitlab
emojis.keys emojis.keys
end end
def emojis_aliases
@emoji_aliases ||= JSON.parse(File.read(Rails.root.join('fixtures', 'emojis', 'aliases.json')))
end
def emoji_filename(name) def emoji_filename(name)
emojis[name]["unicode"] emojis[name]["unicode"]
end end
...@@ -25,5 +29,42 @@ module Gitlab ...@@ -25,5 +29,42 @@ module Gitlab
def emoji_unicode_filename(moji) def emoji_unicode_filename(moji)
emojis_by_moji[moji]["unicode"] emojis_by_moji[moji]["unicode"]
end end
def emoji_unicode_version(name)
@emoji_unicode_versions_by_name ||= JSON.parse(File.read(Rails.root.join('node_modules', 'emoji-unicode-version', 'emoji-unicode-version-map.json')))
@emoji_unicode_versions_by_name[name]
end
def normalize_emoji_name(name)
emojis_aliases[name] || name
end
def emoji_image_tag(name, src)
"<img class='emoji' title=':#{name}:' alt=':#{name}:' src='#{src}' height='20' width='20' align='absmiddle' />"
end
# CSS sprite fallback takes precedence over image fallback
def gl_emoji_tag(name, image: false, sprite: false, force_fallback: false)
emoji_name = emojis_aliases[name] || name
emoji_info = emojis[emoji_name]
emoji_fallback_image_source = ActionController::Base.helpers.url_to_image("emoji/#{emoji_info['name']}.png")
emoji_fallback_sprite_class = "emoji-#{emoji_name}"
data = {
name: emoji_name,
unicode_version: emoji_unicode_version(emoji_name)
}
data[:fallback_src] = emoji_fallback_image_source if image
data[:fallback_sprite_class] = emoji_fallback_sprite_class if sprite
ActionController::Base.helpers.content_tag 'gl-emoji',
class: ("emoji-icon #{emoji_fallback_sprite_class}" if force_fallback && sprite),
data: data do
if force_fallback && !sprite
emoji_image_tag(emoji_name, emoji_fallback_image_source)
else
emoji_info['moji']
end
end
end
end end
end end
...@@ -4,10 +4,10 @@ module Gitlab ...@@ -4,10 +4,10 @@ module Gitlab
gon.api_version = 'v3' # v4 Is not officially released yet, therefore can't be considered as "frozen" gon.api_version = 'v3' # v4 Is not officially released yet, therefore can't be considered as "frozen"
gon.default_avatar_url = URI.join(Gitlab.config.gitlab.url, ActionController::Base.helpers.image_path('no_avatar.png')).to_s gon.default_avatar_url = URI.join(Gitlab.config.gitlab.url, ActionController::Base.helpers.image_path('no_avatar.png')).to_s
gon.max_file_size = current_application_settings.max_attachment_size gon.max_file_size = current_application_settings.max_attachment_size
gon.asset_host = ActionController::Base.asset_host
gon.relative_url_root = Gitlab.config.gitlab.relative_url_root gon.relative_url_root = Gitlab.config.gitlab.relative_url_root
gon.shortcuts_path = help_page_path('shortcuts') gon.shortcuts_path = help_page_path('shortcuts')
gon.user_color_scheme = Gitlab::ColorSchemes.for_user(current_user).css_class gon.user_color_scheme = Gitlab::ColorSchemes.for_user(current_user).css_class
gon.award_menu_url = emojis_path
gon.katex_css_url = ActionController::Base.helpers.asset_path('katex.css') gon.katex_css_url = ActionController::Base.helpers.asset_path('katex.css')
gon.katex_js_url = ActionController::Base.helpers.asset_path('katex.js') gon.katex_js_url = ActionController::Base.helpers.asset_path('katex.js')
......
...@@ -5,29 +5,29 @@ namespace :gemojione do ...@@ -5,29 +5,29 @@ namespace :gemojione do
require 'json' require 'json'
dir = Gemojione.images_path dir = Gemojione.images_path
digests = [] resultant_emoji_map = {}
aliases = Hash.new { |hash, key| hash[key] = [] }
aliases_path = File.join(Rails.root, 'fixtures', 'emojis', 'aliases.json')
JSON.parse(File.read(aliases_path)).each do |alias_name, real_name| Gitlab::Emoji.emojis.each do |name, emoji_hash|
aliases[real_name] << alias_name # Ignore aliases
end unless Gitlab::Emoji.emojis_aliases.key?(name)
Gitlab::AwardEmoji.emojis.map do |name, emoji_hash|
fpath = File.join(dir, "#{emoji_hash['unicode']}.png") fpath = File.join(dir, "#{emoji_hash['unicode']}.png")
digest = Digest::SHA256.file(fpath).hexdigest hash_digest = Digest::SHA256.file(fpath).hexdigest
digests << { name: name, unicode: emoji_hash['unicode'], digest: digest } entry = {
category: emoji_hash['category'],
moji: emoji_hash['moji'],
unicodeVersion: Gitlab::Emoji.emoji_unicode_version(name),
digest: hash_digest,
}
aliases[name].each do |alias_name| resultant_emoji_map[name] = entry
digests << { name: alias_name, unicode: emoji_hash['unicode'], digest: digest }
end end
end end
out = File.join(Rails.root, 'fixtures', 'emojis', 'digests.json') out = File.join(Rails.root, 'fixtures', 'emojis', 'digests.json')
File.open(out, 'w') do |handle| File.open(out, 'w') do |handle|
handle.write(JSON.pretty_generate(digests)) handle.write(JSON.pretty_generate(resultant_emoji_map))
end end
end end
...@@ -55,21 +55,40 @@ namespace :gemojione do ...@@ -55,21 +55,40 @@ namespace :gemojione do
SPRITESHEET_WIDTH = 860 SPRITESHEET_WIDTH = 860
SPRITESHEET_HEIGHT = 840 SPRITESHEET_HEIGHT = 840
# Setup a map to rename image files
emoji_unicode_string_to_name_map = {}
Gitlab::Emoji.emojis.each do |name, emoji_hash|
# Ignore aliases
unless Gitlab::Emoji.emojis_aliases.key?(name)
emoji_unicode_string_to_name_map[emoji_hash['unicode']] = name
end
end
# Copy the Gemojione assets to the temporary folder for renaming
emoji_dir = "app/assets/images/emoji"
FileUtils.rm_rf(emoji_dir)
FileUtils.mkdir_p(emoji_dir, mode: 0700)
FileUtils.cp_r(File.join(Gemojione.images_path, '.'), emoji_dir)
Dir[File.join(emoji_dir, "**/*.png")].each do |png|
image_path = png
rename_to_named_emoji_image!(emoji_unicode_string_to_name_map, image_path)
end
Dir.mktmpdir do |tmpdir| Dir.mktmpdir do |tmpdir|
# Copy the Gemojione assets to the temporary folder for resizing FileUtils.cp_r(File.join(emoji_dir, '.'), tmpdir)
FileUtils.cp_r(Gemojione.images_path, tmpdir)
Dir.chdir(tmpdir) do Dir.chdir(tmpdir) do
Dir["**/*.png"].each do |png| Dir["**/*.png"].each do |png|
resize!(File.join(tmpdir, png), SIZE) tmp_image_path = File.join(tmpdir, png)
resize!(tmp_image_path, SIZE)
end end
end end
style_path = Rails.root.join(*%w(app assets stylesheets pages emojis.scss)) style_path = Rails.root.join(*%w(app assets stylesheets framework emoji-sprites.scss))
# Combine the resized assets into a packed sprite and re-generate the SCSS # Combine the resized assets into a packed sprite and re-generate the SCSS
SpriteFactory.cssurl = "image-url('$IMAGE')" SpriteFactory.cssurl = "image-url('$IMAGE')"
SpriteFactory.run!(File.join(tmpdir, 'png'), { SpriteFactory.run!(tmpdir, {
output_style: style_path, output_style: style_path,
output_image: "app/assets/images/emoji.png", output_image: "app/assets/images/emoji.png",
selector: '.emoji-', selector: '.emoji-',
...@@ -83,7 +102,7 @@ namespace :gemojione do ...@@ -83,7 +102,7 @@ namespace :gemojione do
# let's simplify it # let's simplify it
system(%Q(sed -i '' "s/width: #{SIZE}px; height: #{SIZE}px; background: image-url('emoji.png')/background-position:/" #{style_path})) system(%Q(sed -i '' "s/width: #{SIZE}px; height: #{SIZE}px; background: image-url('emoji.png')/background-position:/" #{style_path}))
system(%Q(sed -i '' "s/ no-repeat//" #{style_path})) system(%Q(sed -i '' "s/ no-repeat//" #{style_path}))
system(%Q(sed -i '' "s/ 0px/ 0/" #{style_path})) system(%Q(sed -i '' "s/ 0px/ 0/g" #{style_path}))
# Append a generic rule that applies to all Emojis # Append a generic rule that applies to all Emojis
File.open(style_path, 'a') do |f| File.open(style_path, 'a') do |f|
...@@ -92,6 +111,8 @@ namespace :gemojione do ...@@ -92,6 +111,8 @@ namespace :gemojione do
.emoji-icon { .emoji-icon {
background-image: image-url('emoji.png'); background-image: image-url('emoji.png');
background-repeat: no-repeat; background-repeat: no-repeat;
color: transparent;
text-indent: -99em;
height: #{SIZE}px; height: #{SIZE}px;
width: #{SIZE}px; width: #{SIZE}px;
...@@ -112,16 +133,17 @@ namespace :gemojione do ...@@ -112,16 +133,17 @@ namespace :gemojione do
# Now do it again but for Retina # Now do it again but for Retina
Dir.mktmpdir do |tmpdir| Dir.mktmpdir do |tmpdir|
# Copy the Gemojione assets to the temporary folder for resizing # Copy the Gemojione assets to the temporary folder for resizing
FileUtils.cp_r(Gemojione.images_path, tmpdir) FileUtils.cp_r(File.join(emoji_dir, '.'), tmpdir)
Dir.chdir(tmpdir) do Dir.chdir(tmpdir) do
Dir["**/*.png"].each do |png| Dir["**/*.png"].each do |png|
resize!(File.join(tmpdir, png), RETINA) tmp_image_path = File.join(tmpdir, png)
resize!(tmp_image_path, RETINA)
end end
end end
# Combine the resized assets into a packed sprite and re-generate the SCSS # Combine the resized assets into a packed sprite and re-generate the SCSS
SpriteFactory.run!(File.join(tmpdir), { SpriteFactory.run!(tmpdir, {
output_image: "app/assets/images/emoji@2x.png", output_image: "app/assets/images/emoji@2x.png",
style: false, style: false,
nocomments: true, nocomments: true,
...@@ -155,4 +177,20 @@ namespace :gemojione do ...@@ -155,4 +177,20 @@ namespace :gemojione do
image.write(image_path) { self.quality = 100 } image.write(image_path) { self.quality = 100 }
image.destroy! image.destroy!
end end
EMOJI_IMAGE_PATH_RE = /(.*?)(([0-9a-f]-?)+)\.png$/i
def rename_to_named_emoji_image!(emoji_unicode_string_to_name_map, image_path)
# Rename file from unicode to emoji name
matches = EMOJI_IMAGE_PATH_RE.match(image_path)
preceding_path = matches[1]
unicode_string = matches[2]
name = emoji_unicode_string_to_name_map[unicode_string]
if name
new_png_path = File.join(preceding_path, "#{name}.png")
FileUtils.mv(image_path, new_png_path)
new_png_path
else
puts "Warning: emoji_unicode_string_to_name_map missing entry for #{unicode_string}. Full path: #{image_path}"
end
end
end end
...@@ -57,7 +57,7 @@ describe "User Feed", feature: true do ...@@ -57,7 +57,7 @@ describe "User Feed", feature: true do
end end
it 'has XHTML summaries in notes' do it 'has XHTML summaries in notes' do
expect(body).to match /Bug confirmed <img[^>]*\/>/ expect(body).to match /Bug confirmed <gl-emoji[^>]*>/
end end
it 'has XHTML summaries in merge request descriptions' do it 'has XHTML summaries in merge request descriptions' do
......
...@@ -105,7 +105,7 @@ feature 'Group', feature: true do ...@@ -105,7 +105,7 @@ feature 'Group', feature: true do
visit path visit path
expect(page).to have_css('.group-home-desc > p > img') expect(page).to have_css('.group-home-desc > p > gl-emoji')
end end
it 'sanitizes unwanted tags' do it 'sanitizes unwanted tags' do
......
...@@ -25,14 +25,14 @@ describe 'Awards Emoji', feature: true do ...@@ -25,14 +25,14 @@ describe 'Awards Emoji', feature: true do
end end
it 'increments the thumbsdown emoji', js: true do it 'increments the thumbsdown emoji', js: true do
find('[data-emoji="thumbsdown"]').click find('[data-name="thumbsdown"]').click
wait_for_ajax wait_for_ajax
expect(thumbsdown_emoji).to have_text("1") expect(thumbsdown_emoji).to have_text("1")
end end
context 'click the thumbsup emoji' do context 'click the thumbsup emoji' do
it 'increments the thumbsup emoji', js: true do it 'increments the thumbsup emoji', js: true do
find('[data-emoji="thumbsup"]').click find('[data-name="thumbsup"]').click
wait_for_ajax wait_for_ajax
expect(thumbsup_emoji).to have_text("1") expect(thumbsup_emoji).to have_text("1")
end end
...@@ -44,7 +44,7 @@ describe 'Awards Emoji', feature: true do ...@@ -44,7 +44,7 @@ describe 'Awards Emoji', feature: true do
context 'click the thumbsdown emoji' do context 'click the thumbsdown emoji' do
it 'increments the thumbsdown emoji', js: true do it 'increments the thumbsdown emoji', js: true do
find('[data-emoji="thumbsdown"]').click find('[data-name="thumbsdown"]').click
wait_for_ajax wait_for_ajax
expect(thumbsdown_emoji).to have_text("1") expect(thumbsdown_emoji).to have_text("1")
end end
...@@ -123,9 +123,9 @@ describe 'Awards Emoji', feature: true do ...@@ -123,9 +123,9 @@ describe 'Awards Emoji', feature: true do
end end
unless status unless status
first('[data-emoji="smiley"]').click first('[data-name="smiley"]').click
else else
find('[data-emoji="smiley"]').click find('[data-name="smiley"]').click
end end
wait_for_ajax wait_for_ajax
......
...@@ -18,7 +18,7 @@ feature 'Project', feature: true do ...@@ -18,7 +18,7 @@ feature 'Project', feature: true do
it 'passes through html-pipeline' do it 'passes through html-pipeline' do
project.update_attribute(:description, 'This project is the :poop:') project.update_attribute(:description, 'This project is the :poop:')
visit path visit path
expect(page).to have_css('.project-home-desc > p > img') expect(page).to have_css('.project-home-desc > p > gl-emoji')
end end
it 'sanitizes unwanted tags' do it 'sanitizes unwanted tags' do
......
...@@ -113,7 +113,7 @@ describe GitlabMarkdownHelper do ...@@ -113,7 +113,7 @@ describe GitlabMarkdownHelper do
it 'replaces commit message with emoji to link' do it 'replaces commit message with emoji to link' do
actual = link_to_gfm(':book:Book', '/foo') actual = link_to_gfm(':book:Book', '/foo')
expect(actual). expect(actual).
to eq %Q(<img class="emoji" title=":book:" alt=":book:" src="http://#{Gitlab.config.gitlab.host}/assets/1F4D6.png" height="20" width="20" align="absmiddle"><a href="/foo">Book</a>) to eq '<gl-emoji data-name="book" data-unicode-version="6.0">📖</gl-emoji><a href="/foo">Book</a>'
end end
end end
......
/* eslint-disable space-before-function-paren, no-var, one-var, one-var-declaration-per-line, no-unused-expressions, comma-dangle, new-parens, no-unused-vars, quotes, jasmine/no-spec-dupes, prefer-template, max-len */ /* eslint-disable space-before-function-paren, no-var, one-var, one-var-declaration-per-line, no-unused-expressions, comma-dangle, new-parens, no-unused-vars, quotes, jasmine/no-spec-dupes, prefer-template, max-len */
/* global AwardsHandler */
require('~/awards_handler'); require('es6-promise').polyfill();
require('./fixtures/emoji_menu');
const AwardsHandler = require('~/awards_handler');
(function() { (function() {
var awardsHandler, lazyAssert, urlRoot; var awardsHandler, lazyAssert, urlRoot, openAndWaitForEmojiMenu;
awardsHandler = null; awardsHandler = null;
...@@ -13,14 +13,6 @@ require('./fixtures/emoji_menu'); ...@@ -13,14 +13,6 @@ require('./fixtures/emoji_menu');
window.gon || (window.gon = {}); window.gon || (window.gon = {});
gl.emojiAliases = function() {
return {
'+1': 'thumbsup',
'-1': 'thumbsdown'
};
};
gon.award_menu_url = '/emojis';
urlRoot = gon.relative_url_root; urlRoot = gon.relative_url_root;
lazyAssert = function(done, assertFn) { lazyAssert = function(done, assertFn) {
...@@ -32,22 +24,40 @@ require('./fixtures/emoji_menu'); ...@@ -32,22 +24,40 @@ require('./fixtures/emoji_menu');
}; };
describe('AwardsHandler', function() { describe('AwardsHandler', function() {
preloadFixtures('issues/open-issue.html.raw'); preloadFixtures('issues/issue_with_comment.html.raw');
beforeEach(function() { beforeEach(function() {
loadFixtures('issues/open-issue.html.raw'); loadFixtures('issues/issue_with_comment.html.raw');
awardsHandler = new AwardsHandler; awardsHandler = new AwardsHandler;
spyOn(awardsHandler, 'postEmoji').and.callFake((function(_this) { spyOn(awardsHandler, 'postEmoji').and.callFake((function(_this) {
return function(url, emoji, cb) { return function(url, emoji, cb) {
return cb(); return cb();
}; };
})(this)); })(this));
spyOn(jQuery, 'get').and.callFake(function(req, cb) {
return cb(window.emojiMenu); let isEmojiMenuBuilt = false;
openAndWaitForEmojiMenu = function() {
return new Promise((resolve, reject) => {
if (isEmojiMenuBuilt) {
resolve();
} else {
$('.js-add-award').eq(0).click();
const $menu = $('.emoji-menu');
$menu.one('build-emoji-menu-finish', () => {
isEmojiMenuBuilt = true;
resolve();
});
// Fail after 1 second
setTimeout(reject, 1000);
}
}); });
};
}); });
afterEach(function() { afterEach(function() {
// restore original url root value // restore original url root value
gon.relative_url_root = urlRoot; gon.relative_url_root = urlRoot;
awardsHandler.destroy();
}); });
describe('::showEmojiMenu', function() { describe('::showEmojiMenu', function() {
it('should show emoji menu when Add emoji button clicked', function(done) { it('should show emoji menu when Add emoji button clicked', function(done) {
...@@ -62,10 +72,9 @@ require('./fixtures/emoji_menu'); ...@@ -62,10 +72,9 @@ require('./fixtures/emoji_menu');
}); });
}); });
it('should also show emoji menu for the smiley icon in notes', function(done) { it('should also show emoji menu for the smiley icon in notes', function(done) {
$('.note-action-button').click(); $('.js-add-award.note-action-button').click();
return lazyAssert(done, function() { return lazyAssert(done, function() {
var $emojiMenu; var $emojiMenu = $('.emoji-menu');
$emojiMenu = $('.emoji-menu');
return expect($emojiMenu.length).toBe(1); return expect($emojiMenu.length).toBe(1);
}); });
}); });
...@@ -86,7 +95,7 @@ require('./fixtures/emoji_menu'); ...@@ -86,7 +95,7 @@ require('./fixtures/emoji_menu');
var $emojiButton, $votesBlock; var $emojiButton, $votesBlock;
$votesBlock = $('.js-awards-block').eq(0); $votesBlock = $('.js-awards-block').eq(0);
awardsHandler.addAwardToEmojiBar($votesBlock, 'heart', false); awardsHandler.addAwardToEmojiBar($votesBlock, 'heart', false);
$emojiButton = $votesBlock.find('[data-emoji=heart]'); $emojiButton = $votesBlock.find('[data-name=heart]');
expect($emojiButton.length).toBe(1); expect($emojiButton.length).toBe(1);
expect($emojiButton.next('.js-counter').text()).toBe('1'); expect($emojiButton.next('.js-counter').text()).toBe('1');
return expect($votesBlock.hasClass('hidden')).toBe(false); return expect($votesBlock.hasClass('hidden')).toBe(false);
...@@ -96,14 +105,14 @@ require('./fixtures/emoji_menu'); ...@@ -96,14 +105,14 @@ require('./fixtures/emoji_menu');
$votesBlock = $('.js-awards-block').eq(0); $votesBlock = $('.js-awards-block').eq(0);
awardsHandler.addAwardToEmojiBar($votesBlock, 'heart', false); awardsHandler.addAwardToEmojiBar($votesBlock, 'heart', false);
awardsHandler.addAwardToEmojiBar($votesBlock, 'heart', false); awardsHandler.addAwardToEmojiBar($votesBlock, 'heart', false);
$emojiButton = $votesBlock.find('[data-emoji=heart]'); $emojiButton = $votesBlock.find('[data-name=heart]');
return expect($emojiButton.length).toBe(0); return expect($emojiButton.length).toBe(0);
}); });
return it('should decrement the emoji counter', function() { return it('should decrement the emoji counter', function() {
var $emojiButton, $votesBlock; var $emojiButton, $votesBlock;
$votesBlock = $('.js-awards-block').eq(0); $votesBlock = $('.js-awards-block').eq(0);
awardsHandler.addAwardToEmojiBar($votesBlock, 'heart', false); awardsHandler.addAwardToEmojiBar($votesBlock, 'heart', false);
$emojiButton = $votesBlock.find('[data-emoji=heart]'); $emojiButton = $votesBlock.find('[data-name=heart]');
$emojiButton.next('.js-counter').text(5); $emojiButton.next('.js-counter').text(5);
awardsHandler.addAwardToEmojiBar($votesBlock, 'heart', false); awardsHandler.addAwardToEmojiBar($votesBlock, 'heart', false);
expect($emojiButton.length).toBe(1); expect($emojiButton.length).toBe(1);
...@@ -120,8 +129,8 @@ require('./fixtures/emoji_menu'); ...@@ -120,8 +129,8 @@ require('./fixtures/emoji_menu');
var $thumbsDownEmoji, $thumbsUpEmoji, $votesBlock, awardUrl; var $thumbsDownEmoji, $thumbsUpEmoji, $votesBlock, awardUrl;
awardUrl = awardsHandler.getAwardUrl(); awardUrl = awardsHandler.getAwardUrl();
$votesBlock = $('.js-awards-block').eq(0); $votesBlock = $('.js-awards-block').eq(0);
$thumbsUpEmoji = $votesBlock.find('[data-emoji=thumbsup]').parent(); $thumbsUpEmoji = $votesBlock.find('[data-name=thumbsup]').parent();
$thumbsDownEmoji = $votesBlock.find('[data-emoji=thumbsdown]').parent(); $thumbsDownEmoji = $votesBlock.find('[data-name=thumbsdown]').parent();
awardsHandler.addAward($votesBlock, awardUrl, 'thumbsup', false); awardsHandler.addAward($votesBlock, awardUrl, 'thumbsup', false);
expect($thumbsUpEmoji.hasClass('active')).toBe(true); expect($thumbsUpEmoji.hasClass('active')).toBe(true);
expect($thumbsDownEmoji.hasClass('active')).toBe(false); expect($thumbsDownEmoji.hasClass('active')).toBe(false);
...@@ -138,9 +147,9 @@ require('./fixtures/emoji_menu'); ...@@ -138,9 +147,9 @@ require('./fixtures/emoji_menu');
awardUrl = awardsHandler.getAwardUrl(); awardUrl = awardsHandler.getAwardUrl();
$votesBlock = $('.js-awards-block').eq(0); $votesBlock = $('.js-awards-block').eq(0);
awardsHandler.addAward($votesBlock, awardUrl, 'fire', false); awardsHandler.addAward($votesBlock, awardUrl, 'fire', false);
expect($votesBlock.find('[data-emoji=fire]').length).toBe(1); expect($votesBlock.find('[data-name=fire]').length).toBe(1);
awardsHandler.removeEmoji($votesBlock.find('[data-emoji=fire]').closest('button')); awardsHandler.removeEmoji($votesBlock.find('[data-name=fire]').closest('button'));
return expect($votesBlock.find('[data-emoji=fire]').length).toBe(0); return expect($votesBlock.find('[data-name=fire]').length).toBe(0);
}); });
}); });
describe('::addYouToUserList', function() { describe('::addYouToUserList', function() {
...@@ -148,7 +157,7 @@ require('./fixtures/emoji_menu'); ...@@ -148,7 +157,7 @@ require('./fixtures/emoji_menu');
var $thumbsUpEmoji, $votesBlock, awardUrl; var $thumbsUpEmoji, $votesBlock, awardUrl;
awardUrl = awardsHandler.getAwardUrl(); awardUrl = awardsHandler.getAwardUrl();
$votesBlock = $('.js-awards-block').eq(0); $votesBlock = $('.js-awards-block').eq(0);
$thumbsUpEmoji = $votesBlock.find('[data-emoji=thumbsup]').parent(); $thumbsUpEmoji = $votesBlock.find('[data-name=thumbsup]').parent();
$thumbsUpEmoji.attr('data-title', 'sam, jerry, max, and andy'); $thumbsUpEmoji.attr('data-title', 'sam, jerry, max, and andy');
awardsHandler.addAward($votesBlock, awardUrl, 'thumbsup', false); awardsHandler.addAward($votesBlock, awardUrl, 'thumbsup', false);
$thumbsUpEmoji.tooltip(); $thumbsUpEmoji.tooltip();
...@@ -158,7 +167,7 @@ require('./fixtures/emoji_menu'); ...@@ -158,7 +167,7 @@ require('./fixtures/emoji_menu');
var $thumbsUpEmoji, $votesBlock, awardUrl; var $thumbsUpEmoji, $votesBlock, awardUrl;
awardUrl = awardsHandler.getAwardUrl(); awardUrl = awardsHandler.getAwardUrl();
$votesBlock = $('.js-awards-block').eq(0); $votesBlock = $('.js-awards-block').eq(0);
$thumbsUpEmoji = $votesBlock.find('[data-emoji=thumbsup]').parent(); $thumbsUpEmoji = $votesBlock.find('[data-name=thumbsup]').parent();
$thumbsUpEmoji.attr('data-title', 'sam'); $thumbsUpEmoji.attr('data-title', 'sam');
awardsHandler.addAward($votesBlock, awardUrl, 'thumbsup', false); awardsHandler.addAward($votesBlock, awardUrl, 'thumbsup', false);
$thumbsUpEmoji.tooltip(); $thumbsUpEmoji.tooltip();
...@@ -170,7 +179,7 @@ require('./fixtures/emoji_menu'); ...@@ -170,7 +179,7 @@ require('./fixtures/emoji_menu');
var $thumbsUpEmoji, $votesBlock, awardUrl; var $thumbsUpEmoji, $votesBlock, awardUrl;
awardUrl = awardsHandler.getAwardUrl(); awardUrl = awardsHandler.getAwardUrl();
$votesBlock = $('.js-awards-block').eq(0); $votesBlock = $('.js-awards-block').eq(0);
$thumbsUpEmoji = $votesBlock.find('[data-emoji=thumbsup]').parent(); $thumbsUpEmoji = $votesBlock.find('[data-name=thumbsup]').parent();
$thumbsUpEmoji.attr('data-title', 'You, sam, jerry, max, and andy'); $thumbsUpEmoji.attr('data-title', 'You, sam, jerry, max, and andy');
$thumbsUpEmoji.addClass('active'); $thumbsUpEmoji.addClass('active');
awardsHandler.addAward($votesBlock, awardUrl, 'thumbsup', false); awardsHandler.addAward($votesBlock, awardUrl, 'thumbsup', false);
...@@ -181,7 +190,7 @@ require('./fixtures/emoji_menu'); ...@@ -181,7 +190,7 @@ require('./fixtures/emoji_menu');
var $thumbsUpEmoji, $votesBlock, awardUrl; var $thumbsUpEmoji, $votesBlock, awardUrl;
awardUrl = awardsHandler.getAwardUrl(); awardUrl = awardsHandler.getAwardUrl();
$votesBlock = $('.js-awards-block').eq(0); $votesBlock = $('.js-awards-block').eq(0);
$thumbsUpEmoji = $votesBlock.find('[data-emoji=thumbsup]').parent(); $thumbsUpEmoji = $votesBlock.find('[data-name=thumbsup]').parent();
$thumbsUpEmoji.attr('data-title', 'You and sam'); $thumbsUpEmoji.attr('data-title', 'You and sam');
$thumbsUpEmoji.addClass('active'); $thumbsUpEmoji.addClass('active');
awardsHandler.addAward($votesBlock, awardUrl, 'thumbsup', false); awardsHandler.addAward($votesBlock, awardUrl, 'thumbsup', false);
...@@ -190,42 +199,58 @@ require('./fixtures/emoji_menu'); ...@@ -190,42 +199,58 @@ require('./fixtures/emoji_menu');
}); });
}); });
describe('search', function() { describe('search', function() {
return it('should filter the emoji', function() { return it('should filter the emoji', function(done) {
$('.js-add-award').eq(0).click(); return openAndWaitForEmojiMenu()
expect($('[data-emoji=angel]').is(':visible')).toBe(true); .then(() => {
expect($('[data-emoji=anger]').is(':visible')).toBe(true); expect($('[data-name=angel]').is(':visible')).toBe(true);
$('#emoji_search').val('ali').trigger('keyup'); expect($('[data-name=anger]').is(':visible')).toBe(true);
expect($('[data-emoji=angel]').is(':visible')).toBe(false); $('#emoji_search').val('ali').trigger('input');
expect($('[data-emoji=anger]').is(':visible')).toBe(false); expect($('[data-name=angel]').is(':visible')).toBe(false);
return expect($('[data-emoji=alien]').is(':visible')).toBe(true); expect($('[data-name=anger]').is(':visible')).toBe(false);
}); expect($('[data-name=alien]').is(':visible')).toBe(true);
}); })
return describe('emoji menu', function() { .then(done)
var openEmojiMenuAndAddEmoji, selector; .catch(() => {
selector = '[data-emoji=sunglasses]'; done.fail('Failed to open and build emoji menu');
openEmojiMenuAndAddEmoji = function() { });
var $block, $emoji, $menu; });
$('.js-add-award').eq(0).click(); });
$menu = $('.emoji-menu'); describe('emoji menu', function() {
$block = $('.js-awards-block'); const emojiSelector = '[data-name="sunglasses"]';
$emoji = $menu.find('.emoji-menu-list:not(.frequent-emojis) ' + selector); const openEmojiMenuAndAddEmoji = function() {
return openAndWaitForEmojiMenu()
.then(() => {
const $menu = $('.emoji-menu');
const $block = $('.js-awards-block');
const $emoji = $menu.find('.emoji-menu-list:not(.frequent-emojis) ' + emojiSelector);
expect($emoji.length).toBe(1); expect($emoji.length).toBe(1);
expect($block.find(selector).length).toBe(0); expect($block.find(emojiSelector).length).toBe(0);
$emoji.click(); $emoji.click();
expect($menu.hasClass('.is-visible')).toBe(false); expect($menu.hasClass('.is-visible')).toBe(false);
return expect($block.find(selector).length).toBe(1); expect($block.find(emojiSelector).length).toBe(1);
});
}; };
it('should add selected emoji to awards block', function() { it('should add selected emoji to awards block', function(done) {
return openEmojiMenuAndAddEmoji(); return openEmojiMenuAndAddEmoji()
.then(done)
.catch(() => {
done.fail('Failed to open and build emoji menu');
}); });
return it('should remove already selected emoji', function() { });
var $block, $emoji; it('should remove already selected emoji', function(done) {
openEmojiMenuAndAddEmoji(); return openEmojiMenuAndAddEmoji()
.then(() => {
$('.js-add-award').eq(0).click(); $('.js-add-award').eq(0).click();
$block = $('.js-awards-block'); const $block = $('.js-awards-block');
$emoji = $('.emoji-menu').find(".emoji-menu-list:not(.frequent-emojis) " + selector); const $emoji = $('.emoji-menu').find(`.emoji-menu-list:not(.frequent-emojis) ${emojiSelector}`);
$emoji.click(); $emoji.click();
return expect($block.find(selector).length).toBe(0); expect($block.find(emojiSelector).length).toBe(0);
})
.then(done)
.catch((err) => {
done.fail('Failed to open and build emoji menu');
});
}); });
}); });
}); });
......
/* eslint-disable space-before-function-paren */
(function() {
window.emojiMenu = "<div class='emoji-menu'>\n <input type=\"text\" name=\"emoji_search\" id=\"emoji_search\" value=\"\" class=\"emoji-search search-input form-control\" />\n <div class='emoji-menu-content'>\n <h5 class='emoji-menu-title'>\n Emoticons\n </h5>\n <ul class='clearfix emoji-menu-list'>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F47D\" title=\"alien\" data-aliases=\"\" data-emoji=\"alien\" data-unicode-name=\"1F47D\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F47C\" title=\"angel\" data-aliases=\"\" data-emoji=\"angel\" data-unicode-name=\"1F47C\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F4A2\" title=\"anger\" data-aliases=\"\" data-emoji=\"anger\" data-unicode-name=\"1F4A2\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F620\" title=\"angry\" data-aliases=\"\" data-emoji=\"angry\" data-unicode-name=\"1F620\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F627\" title=\"anguished\" data-aliases=\"\" data-emoji=\"anguished\" data-unicode-name=\"1F627\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F632\" title=\"astonished\" data-aliases=\"\" data-emoji=\"astonished\" data-unicode-name=\"1F632\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F45F\" title=\"athletic_shoe\" data-aliases=\"\" data-emoji=\"athletic_shoe\" data-unicode-name=\"1F45F\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F476\" title=\"baby\" data-aliases=\"\" data-emoji=\"baby\" data-unicode-name=\"1F476\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F459\" title=\"bikini\" data-aliases=\"\" data-emoji=\"bikini\" data-unicode-name=\"1F459\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F499\" title=\"blue_heart\" data-aliases=\"\" data-emoji=\"blue_heart\" data-unicode-name=\"1F499\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F60A\" title=\"blush\" data-aliases=\"\" data-emoji=\"blush\" data-unicode-name=\"1F60A\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F4A5\" title=\"boom\" data-aliases=\"\" data-emoji=\"boom\" data-unicode-name=\"1F4A5\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F462\" title=\"boot\" data-aliases=\"\" data-emoji=\"boot\" data-unicode-name=\"1F462\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F647\" title=\"bow\" data-aliases=\"\" data-emoji=\"bow\" data-unicode-name=\"1F647\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F466\" title=\"boy\" data-aliases=\"\" data-emoji=\"boy\" data-unicode-name=\"1F466\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F470\" title=\"bride_with_veil\" data-aliases=\"\" data-emoji=\"bride_with_veil\" data-unicode-name=\"1F470\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F4BC\" title=\"briefcase\" data-aliases=\"\" data-emoji=\"briefcase\" data-unicode-name=\"1F4BC\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F494\" title=\"broken_heart\" data-aliases=\"\" data-emoji=\"broken_heart\" data-unicode-name=\"1F494\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F464\" title=\"bust_in_silhouette\" data-aliases=\"\" data-emoji=\"bust_in_silhouette\" data-unicode-name=\"1F464\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F465\" title=\"busts_in_silhouette\" data-aliases=\"\" data-emoji=\"busts_in_silhouette\" data-unicode-name=\"1F465\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F44F\" title=\"clap\" data-aliases=\"\" data-emoji=\"clap\" data-unicode-name=\"1F44F\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F302\" title=\"closed_umbrella\" data-aliases=\"\" data-emoji=\"closed_umbrella\" data-unicode-name=\"1F302\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F630\" title=\"cold_sweat\" data-aliases=\"\" data-emoji=\"cold_sweat\" data-unicode-name=\"1F630\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F616\" title=\"confounded\" data-aliases=\"\" data-emoji=\"confounded\" data-unicode-name=\"1F616\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F615\" title=\"confused\" data-aliases=\"\" data-emoji=\"confused\" data-unicode-name=\"1F615\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F477\" title=\"construction_worker\" data-aliases=\"\" data-emoji=\"construction_worker\" data-unicode-name=\"1F477\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F46E\" title=\"cop\" data-aliases=\"\" data-emoji=\"cop\" data-unicode-name=\"1F46E\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F46B\" title=\"couple\" data-aliases=\"\" data-emoji=\"couple\" data-unicode-name=\"1F46B\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F491\" title=\"couple_with_heart\" data-aliases=\"\" data-emoji=\"couple_with_heart\" data-unicode-name=\"1F491\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F48F\" title=\"couplekiss\" data-aliases=\"\" data-emoji=\"couplekiss\" data-unicode-name=\"1F48F\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F451\" title=\"crown\" data-aliases=\"\" data-emoji=\"crown\" data-unicode-name=\"1F451\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F622\" title=\"cry\" data-aliases=\"\" data-emoji=\"cry\" data-unicode-name=\"1F622\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F63F\" title=\"crying_cat_face\" data-aliases=\"\" data-emoji=\"crying_cat_face\" data-unicode-name=\"1F63F\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F498\" title=\"cupid\" data-aliases=\"\" data-emoji=\"cupid\" data-unicode-name=\"1F498\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F483\" title=\"dancer\" data-aliases=\"\" data-emoji=\"dancer\" data-unicode-name=\"1F483\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F46F\" title=\"dancers\" data-aliases=\"\" data-emoji=\"dancers\" data-unicode-name=\"1F46F\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F4A8\" title=\"dash\" data-aliases=\"\" data-emoji=\"dash\" data-unicode-name=\"1F4A8\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F61E\" title=\"disappointed\" data-aliases=\"\" data-emoji=\"disappointed\" data-unicode-name=\"1F61E\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F625\" title=\"disappointed_relieved\" data-aliases=\"\" data-emoji=\"disappointed_relieved\" data-unicode-name=\"1F625\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F4AB\" title=\"dizzy\" data-aliases=\"\" data-emoji=\"dizzy\" data-unicode-name=\"1F4AB\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F635\" title=\"dizzy_face\" data-aliases=\"\" data-emoji=\"dizzy_face\" data-unicode-name=\"1F635\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F457\" title=\"dress\" data-aliases=\"\" data-emoji=\"dress\" data-unicode-name=\"1F457\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F4A7\" title=\"droplet\" data-aliases=\"\" data-emoji=\"droplet\" data-unicode-name=\"1F4A7\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F442\" title=\"ear\" data-aliases=\"\" data-emoji=\"ear\" data-unicode-name=\"1F442\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F611\" title=\"expressionless\" data-aliases=\"\" data-emoji=\"expressionless\" data-unicode-name=\"1F611\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F453\" title=\"eyeglasses\" data-aliases=\"\" data-emoji=\"eyeglasses\" data-unicode-name=\"1F453\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F440\" title=\"eyes\" data-aliases=\"\" data-emoji=\"eyes\" data-unicode-name=\"1F440\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F46A\" title=\"family\" data-aliases=\"\" data-emoji=\"family\" data-unicode-name=\"1F46A\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F628\" title=\"fearful\" data-aliases=\"\" data-emoji=\"fearful\" data-unicode-name=\"1F628\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F525\" title=\"fire\" data-aliases=\":flame:\" data-emoji=\"fire\" data-unicode-name=\"1F525\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-270A\" title=\"fist\" data-aliases=\"\" data-emoji=\"fist\" data-unicode-name=\"270A\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F633\" title=\"flushed\" data-aliases=\"\" data-emoji=\"flushed\" data-unicode-name=\"1F633\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F463\" title=\"footprints\" data-aliases=\"\" data-emoji=\"footprints\" data-unicode-name=\"1F463\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F626\" title=\"frowning\" data-aliases=\":anguished:\" data-emoji=\"frowning\" data-unicode-name=\"1F626\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F48E\" title=\"gem\" data-aliases=\"\" data-emoji=\"gem\" data-unicode-name=\"1F48E\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F467\" title=\"girl\" data-aliases=\"\" data-emoji=\"girl\" data-unicode-name=\"1F467\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F49A\" title=\"green_heart\" data-aliases=\"\" data-emoji=\"green_heart\" data-unicode-name=\"1F49A\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F62C\" title=\"grimacing\" data-aliases=\"\" data-emoji=\"grimacing\" data-unicode-name=\"1F62C\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F601\" title=\"grin\" data-aliases=\"\" data-emoji=\"grin\" data-unicode-name=\"1F601\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F600\" title=\"grinning\" data-aliases=\"\" data-emoji=\"grinning\" data-unicode-name=\"1F600\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F482\" title=\"guardsman\" data-aliases=\"\" data-emoji=\"guardsman\" data-unicode-name=\"1F482\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F487\" title=\"haircut\" data-aliases=\"\" data-emoji=\"haircut\" data-unicode-name=\"1F487\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F45C\" title=\"handbag\" data-aliases=\"\" data-emoji=\"handbag\" data-unicode-name=\"1F45C\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F649\" title=\"hear_no_evil\" data-aliases=\"\" data-emoji=\"hear_no_evil\" data-unicode-name=\"1F649\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-2764\" title=\"heart\" data-aliases=\"\" data-emoji=\"heart\" data-unicode-name=\"2764\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F60D\" title=\"heart_eyes\" data-aliases=\"\" data-emoji=\"heart_eyes\" data-unicode-name=\"1F60D\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F63B\" title=\"heart_eyes_cat\" data-aliases=\"\" data-emoji=\"heart_eyes_cat\" data-unicode-name=\"1F63B\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F493\" title=\"heartbeat\" data-aliases=\"\" data-emoji=\"heartbeat\" data-unicode-name=\"1F493\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F497\" title=\"heartpulse\" data-aliases=\"\" data-emoji=\"heartpulse\" data-unicode-name=\"1F497\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F460\" title=\"high_heel\" data-aliases=\"\" data-emoji=\"high_heel\" data-unicode-name=\"1F460\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F62F\" title=\"hushed\" data-aliases=\"\" data-emoji=\"hushed\" data-unicode-name=\"1F62F\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F47F\" title=\"imp\" data-aliases=\"\" data-emoji=\"imp\" data-unicode-name=\"1F47F\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F481\" title=\"information_desk_person\" data-aliases=\"\" data-emoji=\"information_desk_person\" data-unicode-name=\"1F481\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F607\" title=\"innocent\" data-aliases=\"\" data-emoji=\"innocent\" data-unicode-name=\"1F607\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F47A\" title=\"japanese_goblin\" data-aliases=\"\" data-emoji=\"japanese_goblin\" data-unicode-name=\"1F47A\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F479\" title=\"japanese_ogre\" data-aliases=\"\" data-emoji=\"japanese_ogre\" data-unicode-name=\"1F479\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F456\" title=\"jeans\" data-aliases=\"\" data-emoji=\"jeans\" data-unicode-name=\"1F456\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F602\" title=\"joy\" data-aliases=\"\" data-emoji=\"joy\" data-unicode-name=\"1F602\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F639\" title=\"joy_cat\" data-aliases=\"\" data-emoji=\"joy_cat\" data-unicode-name=\"1F639\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F458\" title=\"kimono\" data-aliases=\"\" data-emoji=\"kimono\" data-unicode-name=\"1F458\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F48B\" title=\"kiss\" data-aliases=\"\" data-emoji=\"kiss\" data-unicode-name=\"1F48B\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F617\" title=\"kissing\" data-aliases=\"\" data-emoji=\"kissing\" data-unicode-name=\"1F617\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F63D\" title=\"kissing_cat\" data-aliases=\"\" data-emoji=\"kissing_cat\" data-unicode-name=\"1F63D\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F61A\" title=\"kissing_closed_eyes\" data-aliases=\"\" data-emoji=\"kissing_closed_eyes\" data-unicode-name=\"1F61A\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F618\" title=\"kissing_heart\" data-aliases=\"\" data-emoji=\"kissing_heart\" data-unicode-name=\"1F618\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F619\" title=\"kissing_smiling_eyes\" data-aliases=\"\" data-emoji=\"kissing_smiling_eyes\" data-unicode-name=\"1F619\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F606\" title=\"laughing\" data-aliases=\":satisfied:\" data-emoji=\"laughing\" data-unicode-name=\"1F606\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F444\" title=\"lips\" data-aliases=\"\" data-emoji=\"lips\" data-unicode-name=\"1F444\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F484\" title=\"lipstick\" data-aliases=\"\" data-emoji=\"lipstick\" data-unicode-name=\"1F484\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F48C\" title=\"love_letter\" data-aliases=\"\" data-emoji=\"love_letter\" data-unicode-name=\"1F48C\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F468\" title=\"man\" data-aliases=\"\" data-emoji=\"man\" data-unicode-name=\"1F468\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F472\" title=\"man_with_gua_pi_mao\" data-aliases=\"\" data-emoji=\"man_with_gua_pi_mao\" data-unicode-name=\"1F472\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F473\" title=\"man_with_turban\" data-aliases=\"\" data-emoji=\"man_with_turban\" data-unicode-name=\"1F473\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F45E\" title=\"mans_shoe\" data-aliases=\"\" data-emoji=\"mans_shoe\" data-unicode-name=\"1F45E\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F637\" title=\"mask\" data-aliases=\"\" data-emoji=\"mask\" data-unicode-name=\"1F637\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F486\" title=\"massage\" data-aliases=\"\" data-emoji=\"massage\" data-unicode-name=\"1F486\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F4AA\" title=\"muscle\" data-aliases=\"\" data-emoji=\"muscle\" data-unicode-name=\"1F4AA\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F485\" title=\"nail_care\" data-aliases=\"\" data-emoji=\"nail_care\" data-unicode-name=\"1F485\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F454\" title=\"necktie\" data-aliases=\"\" data-emoji=\"necktie\" data-unicode-name=\"1F454\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F610\" title=\"neutral_face\" data-aliases=\"\" data-emoji=\"neutral_face\" data-unicode-name=\"1F610\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F645\" title=\"no_good\" data-aliases=\"\" data-emoji=\"no_good\" data-unicode-name=\"1F645\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F636\" title=\"no_mouth\" data-aliases=\"\" data-emoji=\"no_mouth\" data-unicode-name=\"1F636\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F443\" title=\"nose\" data-aliases=\"\" data-emoji=\"nose\" data-unicode-name=\"1F443\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F44C\" title=\"ok_hand\" data-aliases=\"\" data-emoji=\"ok_hand\" data-unicode-name=\"1F44C\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F646\" title=\"ok_woman\" data-aliases=\"\" data-emoji=\"ok_woman\" data-unicode-name=\"1F646\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F474\" title=\"older_man\" data-aliases=\"\" data-emoji=\"older_man\" data-unicode-name=\"1F474\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F475\" title=\"older_woman\" data-aliases=\":grandma:\" data-emoji=\"older_woman\" data-unicode-name=\"1F475\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F450\" title=\"open_hands\" data-aliases=\"\" data-emoji=\"open_hands\" data-unicode-name=\"1F450\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F62E\" title=\"open_mouth\" data-aliases=\"\" data-emoji=\"open_mouth\" data-unicode-name=\"1F62E\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F614\" title=\"pensive\" data-aliases=\"\" data-emoji=\"pensive\" data-unicode-name=\"1F614\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F623\" title=\"persevere\" data-aliases=\"\" data-emoji=\"persevere\" data-unicode-name=\"1F623\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F64D\" title=\"person_frowning\" data-aliases=\"\" data-emoji=\"person_frowning\" data-unicode-name=\"1F64D\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F471\" title=\"person_with_blond_hair\" data-aliases=\"\" data-emoji=\"person_with_blond_hair\" data-unicode-name=\"1F471\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F64E\" title=\"person_with_pouting_face\" data-aliases=\"\" data-emoji=\"person_with_pouting_face\" data-unicode-name=\"1F64E\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F447\" title=\"point_down\" data-aliases=\"\" data-emoji=\"point_down\" data-unicode-name=\"1F447\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F448\" title=\"point_left\" data-aliases=\"\" data-emoji=\"point_left\" data-unicode-name=\"1F448\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F449\" title=\"point_right\" data-aliases=\"\" data-emoji=\"point_right\" data-unicode-name=\"1F449\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-261D\" title=\"point_up\" data-aliases=\"\" data-emoji=\"point_up\" data-unicode-name=\"261D\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F446\" title=\"point_up_2\" data-aliases=\"\" data-emoji=\"point_up_2\" data-unicode-name=\"1F446\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F4A9\" title=\"poop\" data-aliases=\":shit: :hankey: :poo:\" data-emoji=\"poop\" data-unicode-name=\"1F4A9\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F45D\" title=\"pouch\" data-aliases=\"\" data-emoji=\"pouch\" data-unicode-name=\"1F45D\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F63E\" title=\"pouting_cat\" data-aliases=\"\" data-emoji=\"pouting_cat\" data-unicode-name=\"1F63E\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F64F\" title=\"pray\" data-aliases=\"\" data-emoji=\"pray\" data-unicode-name=\"1F64F\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F478\" title=\"princess\" data-aliases=\"\" data-emoji=\"princess\" data-unicode-name=\"1F478\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F44A\" title=\"punch\" data-aliases=\"\" data-emoji=\"punch\" data-unicode-name=\"1F44A\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F49C\" title=\"purple_heart\" data-aliases=\"\" data-emoji=\"purple_heart\" data-unicode-name=\"1F49C\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F45B\" title=\"purse\" data-aliases=\"\" data-emoji=\"purse\" data-unicode-name=\"1F45B\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F621\" title=\"rage\" data-aliases=\"\" data-emoji=\"rage\" data-unicode-name=\"1F621\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-270B\" title=\"raised_hand\" data-aliases=\"\" data-emoji=\"raised_hand\" data-unicode-name=\"270B\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F64C\" title=\"raised_hands\" data-aliases=\"\" data-emoji=\"raised_hands\" data-unicode-name=\"1F64C\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F64B\" title=\"raising_hand\" data-aliases=\"\" data-emoji=\"raising_hand\" data-unicode-name=\"1F64B\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-263A\" title=\"relaxed\" data-aliases=\"\" data-emoji=\"relaxed\" data-unicode-name=\"263A\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F60C\" title=\"relieved\" data-aliases=\"\" data-emoji=\"relieved\" data-unicode-name=\"1F60C\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F49E\" title=\"revolving_hearts\" data-aliases=\"\" data-emoji=\"revolving_hearts\" data-unicode-name=\"1F49E\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F380\" title=\"ribbon\" data-aliases=\"\" data-emoji=\"ribbon\" data-unicode-name=\"1F380\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F48D\" title=\"ring\" data-aliases=\"\" data-emoji=\"ring\" data-unicode-name=\"1F48D\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F3C3\" title=\"runner\" data-aliases=\"\" data-emoji=\"runner\" data-unicode-name=\"1F3C3\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F3BD\" title=\"running_shirt_with_sash\" data-aliases=\"\" data-emoji=\"running_shirt_with_sash\" data-unicode-name=\"1F3BD\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F461\" title=\"sandal\" data-aliases=\"\" data-emoji=\"sandal\" data-unicode-name=\"1F461\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F631\" title=\"scream\" data-aliases=\"\" data-emoji=\"scream\" data-unicode-name=\"1F631\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F640\" title=\"scream_cat\" data-aliases=\"\" data-emoji=\"scream_cat\" data-unicode-name=\"1F640\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F648\" title=\"see_no_evil\" data-aliases=\"\" data-emoji=\"see_no_evil\" data-unicode-name=\"1F648\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F455\" title=\"shirt\" data-aliases=\"\" data-emoji=\"shirt\" data-unicode-name=\"1F455\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F480\" title=\"skull\" data-aliases=\":skeleton:\" data-emoji=\"skull\" data-unicode-name=\"1F480\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F634\" title=\"sleeping\" data-aliases=\"\" data-emoji=\"sleeping\" data-unicode-name=\"1F634\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F62A\" title=\"sleepy\" data-aliases=\"\" data-emoji=\"sleepy\" data-unicode-name=\"1F62A\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F604\" title=\"smile\" data-aliases=\"\" data-emoji=\"smile\" data-unicode-name=\"1F604\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F638\" title=\"smile_cat\" data-aliases=\"\" data-emoji=\"smile_cat\" data-unicode-name=\"1F638\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F603\" title=\"smiley\" data-aliases=\"\" data-emoji=\"smiley\" data-unicode-name=\"1F603\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F63A\" title=\"smiley_cat\" data-aliases=\"\" data-emoji=\"smiley_cat\" data-unicode-name=\"1F63A\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F608\" title=\"smiling_imp\" data-aliases=\"\" data-emoji=\"smiling_imp\" data-unicode-name=\"1F608\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F60F\" title=\"smirk\" data-aliases=\"\" data-emoji=\"smirk\" data-unicode-name=\"1F60F\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F63C\" title=\"smirk_cat\" data-aliases=\"\" data-emoji=\"smirk_cat\" data-unicode-name=\"1F63C\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F62D\" title=\"sob\" data-aliases=\"\" data-emoji=\"sob\" data-unicode-name=\"1F62D\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-2728\" title=\"sparkles\" data-aliases=\"\" data-emoji=\"sparkles\" data-unicode-name=\"2728\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F496\" title=\"sparkling_heart\" data-aliases=\"\" data-emoji=\"sparkling_heart\" data-unicode-name=\"1F496\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F64A\" title=\"speak_no_evil\" data-aliases=\"\" data-emoji=\"speak_no_evil\" data-unicode-name=\"1F64A\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F4AC\" title=\"speech_balloon\" data-aliases=\"\" data-emoji=\"speech_balloon\" data-unicode-name=\"1F4AC\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F31F\" title=\"star2\" data-aliases=\"\" data-emoji=\"star2\" data-unicode-name=\"1F31F\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F61B\" title=\"stuck_out_tongue\" data-aliases=\"\" data-emoji=\"stuck_out_tongue\" data-unicode-name=\"1F61B\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F61D\" title=\"stuck_out_tongue_closed_eyes\" data-aliases=\"\" data-emoji=\"stuck_out_tongue_closed_eyes\" data-unicode-name=\"1F61D\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F61C\" title=\"stuck_out_tongue_winking_eye\" data-aliases=\"\" data-emoji=\"stuck_out_tongue_winking_eye\" data-unicode-name=\"1F61C\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F60E\" title=\"sunglasses\" data-aliases=\"\" data-emoji=\"sunglasses\" data-unicode-name=\"1F60E\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F613\" title=\"sweat\" data-aliases=\"\" data-emoji=\"sweat\" data-unicode-name=\"1F613\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F4A6\" title=\"sweat_drops\" data-aliases=\"\" data-emoji=\"sweat_drops\" data-unicode-name=\"1F4A6\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F605\" title=\"sweat_smile\" data-aliases=\"\" data-emoji=\"sweat_smile\" data-unicode-name=\"1F605\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F4AD\" title=\"thought_balloon\" data-aliases=\"\" data-emoji=\"thought_balloon\" data-unicode-name=\"1F4AD\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F44E\" title=\"thumbsdown\" data-aliases=\":-1:\" data-emoji=\"thumbsdown\" data-unicode-name=\"1F44E\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F44D\" title=\"thumbsup\" data-aliases=\":+1:\" data-emoji=\"thumbsup\" data-unicode-name=\"1F44D\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F62B\" title=\"tired_face\" data-aliases=\"\" data-emoji=\"tired_face\" data-unicode-name=\"1F62B\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F445\" title=\"tongue\" data-aliases=\"\" data-emoji=\"tongue\" data-unicode-name=\"1F445\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F3A9\" title=\"tophat\" data-aliases=\"\" data-emoji=\"tophat\" data-unicode-name=\"1F3A9\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F624\" title=\"triumph\" data-aliases=\"\" data-emoji=\"triumph\" data-unicode-name=\"1F624\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F495\" title=\"two_hearts\" data-aliases=\"\" data-emoji=\"two_hearts\" data-unicode-name=\"1F495\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F46C\" title=\"two_men_holding_hands\" data-aliases=\"\" data-emoji=\"two_men_holding_hands\" data-unicode-name=\"1F46C\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F46D\" title=\"two_women_holding_hands\" data-aliases=\"\" data-emoji=\"two_women_holding_hands\" data-unicode-name=\"1F46D\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F612\" title=\"unamused\" data-aliases=\"\" data-emoji=\"unamused\" data-unicode-name=\"1F612\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-270C\" title=\"v\" data-aliases=\"\" data-emoji=\"v\" data-unicode-name=\"270C\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F6B6\" title=\"walking\" data-aliases=\"\" data-emoji=\"walking\" data-unicode-name=\"1F6B6\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F44B\" title=\"wave\" data-aliases=\"\" data-emoji=\"wave\" data-unicode-name=\"1F44B\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F629\" title=\"weary\" data-aliases=\"\" data-emoji=\"weary\" data-unicode-name=\"1F629\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F609\" title=\"wink\" data-aliases=\"\" data-emoji=\"wink\" data-unicode-name=\"1F609\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F469\" title=\"woman\" data-aliases=\"\" data-emoji=\"woman\" data-unicode-name=\"1F469\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F45A\" title=\"womans_clothes\" data-aliases=\"\" data-emoji=\"womans_clothes\" data-unicode-name=\"1F45A\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F452\" title=\"womans_hat\" data-aliases=\"\" data-emoji=\"womans_hat\" data-unicode-name=\"1F452\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F61F\" title=\"worried\" data-aliases=\"\" data-emoji=\"worried\" data-unicode-name=\"1F61F\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F49B\" title=\"yellow_heart\" data-aliases=\"\" data-emoji=\"yellow_heart\" data-unicode-name=\"1F49B\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F60B\" title=\"yum\" data-aliases=\"\" data-emoji=\"yum\" data-unicode-name=\"1F60B\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F4A4\" title=\"zzz\" data-aliases=\"\" data-emoji=\"zzz\" data-unicode-name=\"1F4A4\"></div>\n </button>\n </li>\n </ul>\n </div>\n</div>";
}).call(window);
require('~/extensions/string');
require('~/extensions/array');
const glEmoji = require('~/behaviors/gl_emoji');
const glEmojiTag = glEmoji.glEmojiTag;
const isEmojiUnicodeSupported = glEmoji.isEmojiUnicodeSupported;
const isFlagEmoji = glEmoji.isFlagEmoji;
const isKeycapEmoji = glEmoji.isKeycapEmoji;
const isSkinToneComboEmoji = glEmoji.isSkinToneComboEmoji;
const isHorceRacingSkinToneComboEmoji = glEmoji.isHorceRacingSkinToneComboEmoji;
const isPersonZwjEmoji = glEmoji.isPersonZwjEmoji;
const emptySupportMap = {
personZwj: false,
horseRacing: false,
flag: false,
skinToneModifier: false,
'9.0': false,
'8.0': false,
'7.0': false,
6.1: false,
'6.0': false,
5.2: false,
5.1: false,
4.1: false,
'4.0': false,
3.2: false,
'3.0': false,
1.1: false,
};
const emojiFixtureMap = {
bomb: {
name: 'bomb',
moji: '💣',
unicodeVersion: '6.0',
},
construction_worker_tone5: {
name: 'construction_worker_tone5',
moji: '👷🏿',
unicodeVersion: '8.0',
},
five: {
name: 'five',
moji: '5️⃣',
unicodeVersion: '3.0',
},
};
function markupToDomElement(markup) {
const div = document.createElement('div');
div.innerHTML = markup;
return div.firstElementChild;
}
function testGlEmojiImageFallback(element, name, src) {
expect(element.tagName.toLowerCase()).toBe('img');
expect(element.getAttribute('src')).toBe(src);
expect(element.getAttribute('title')).toBe(`:${name}:`);
expect(element.getAttribute('alt')).toBe(`:${name}:`);
}
const defaults = {
forceFallback: false,
sprite: false,
};
function testGlEmojiElement(element, name, unicodeVersion, unicodeMoji, options = {}) {
const opts = Object.assign({}, defaults, options);
expect(element.tagName.toLowerCase()).toBe('gl-emoji');
expect(element.dataset.name).toBe(name);
expect(element.dataset.fallbackSrc.length).toBeGreaterThan(0);
expect(element.dataset.unicodeVersion).toBe(unicodeVersion);
const fallbackSpriteClass = `emoji-${name}`;
if (opts.sprite) {
expect(element.dataset.fallbackSpriteClass).toBe(fallbackSpriteClass);
}
if (opts.forceFallback && opts.sprite) {
expect(element.getAttribute('class')).toBe(`emoji-icon ${fallbackSpriteClass}`);
}
if (opts.forceFallback && !opts.sprite) {
// Check for image fallback
testGlEmojiImageFallback(element.firstElementChild, name, element.dataset.fallbackSrc);
} else {
// Otherwise make sure things are still unicode text
expect(element.textContent.trim()).toBe(unicodeMoji);
}
}
describe('gl_emoji', () => {
describe('glEmojiTag', () => {
it('bomb emoji', () => {
const emojiKey = 'bomb';
const markup = glEmojiTag(emojiFixtureMap[emojiKey].name);
const glEmojiElement = markupToDomElement(markup);
testGlEmojiElement(
glEmojiElement,
emojiFixtureMap[emojiKey].name,
emojiFixtureMap[emojiKey].unicodeVersion,
emojiFixtureMap[emojiKey].moji,
);
});
it('bomb emoji with image fallback', () => {
const emojiKey = 'bomb';
const markup = glEmojiTag(emojiFixtureMap[emojiKey].name, {
forceFallback: true,
});
const glEmojiElement = markupToDomElement(markup);
testGlEmojiElement(
glEmojiElement,
emojiFixtureMap[emojiKey].name,
emojiFixtureMap[emojiKey].unicodeVersion,
emojiFixtureMap[emojiKey].moji,
{
forceFallback: true,
},
);
});
it('bomb emoji with sprite fallback readiness', () => {
const emojiKey = 'bomb';
const markup = glEmojiTag(emojiFixtureMap[emojiKey].name, {
sprite: true,
});
const glEmojiElement = markupToDomElement(markup);
testGlEmojiElement(
glEmojiElement,
emojiFixtureMap[emojiKey].name,
emojiFixtureMap[emojiKey].unicodeVersion,
emojiFixtureMap[emojiKey].moji,
{
sprite: true,
},
);
});
it('bomb emoji with sprite fallback', () => {
const emojiKey = 'bomb';
const markup = glEmojiTag(emojiFixtureMap[emojiKey].name, {
forceFallback: true,
sprite: true,
});
const glEmojiElement = markupToDomElement(markup);
testGlEmojiElement(
glEmojiElement,
emojiFixtureMap[emojiKey].name,
emojiFixtureMap[emojiKey].unicodeVersion,
emojiFixtureMap[emojiKey].moji,
{
forceFallback: true,
sprite: true,
},
);
});
});
describe('isFlagEmoji', () => {
it('should detect flag_ac', () => {
expect(isFlagEmoji('🇦🇨')).toBeTruthy();
});
it('should detect flag_us', () => {
expect(isFlagEmoji('🇺🇸')).toBeTruthy();
});
it('should detect flag_zw', () => {
expect(isFlagEmoji('🇿🇼')).toBeTruthy();
});
it('should not detect flags', () => {
expect(isFlagEmoji('🎏')).toBeFalsy();
});
it('should not detect triangular_flag_on_post', () => {
expect(isFlagEmoji('🚩')).toBeFalsy();
});
it('should not detect single letter', () => {
expect(isFlagEmoji('🇦')).toBeFalsy();
});
it('should not detect >2 letters', () => {
expect(isFlagEmoji('🇦🇧🇨')).toBeFalsy();
});
});
describe('isKeycapEmoji', () => {
it('should detect one(keycap)', () => {
expect(isKeycapEmoji('1️⃣')).toBeTruthy();
});
it('should detect nine(keycap)', () => {
expect(isKeycapEmoji('9️⃣')).toBeTruthy();
});
it('should not detect ten(keycap)', () => {
expect(isKeycapEmoji('🔟')).toBeFalsy();
});
it('should not detect hash(keycap)', () => {
expect(isKeycapEmoji('#⃣')).toBeFalsy();
});
});
describe('isSkinToneComboEmoji', () => {
it('should detect hand_splayed_tone5', () => {
expect(isSkinToneComboEmoji('🖐🏿')).toBeTruthy();
});
it('should not detect hand_splayed', () => {
expect(isSkinToneComboEmoji('🖐')).toBeFalsy();
});
it('should detect lifter_tone1', () => {
expect(isSkinToneComboEmoji('🏋🏻')).toBeTruthy();
});
it('should not detect lifter', () => {
expect(isSkinToneComboEmoji('🏋')).toBeFalsy();
});
it('should detect rowboat_tone4', () => {
expect(isSkinToneComboEmoji('🚣🏾')).toBeTruthy();
});
it('should not detect rowboat', () => {
expect(isSkinToneComboEmoji('🚣')).toBeFalsy();
});
it('should not detect individual tone emoji', () => {
expect(isSkinToneComboEmoji('🏻')).toBeFalsy();
});
});
describe('isHorceRacingSkinToneComboEmoji', () => {
it('should detect horse_racing_tone2', () => {
expect(isHorceRacingSkinToneComboEmoji('🏇🏼')).toBeTruthy();
});
it('should not detect horse_racing', () => {
expect(isHorceRacingSkinToneComboEmoji('🏇')).toBeFalsy();
});
});
describe('isPersonZwjEmoji', () => {
it('should detect couple_mm', () => {
expect(isPersonZwjEmoji('👨‍❤️‍👨')).toBeTruthy();
});
it('should not detect couple_with_heart', () => {
expect(isPersonZwjEmoji('💑')).toBeFalsy();
});
it('should not detect couplekiss', () => {
expect(isPersonZwjEmoji('💏')).toBeFalsy();
});
it('should detect family_mmb', () => {
expect(isPersonZwjEmoji('👨‍👨‍👦')).toBeTruthy();
});
it('should detect family_mwgb', () => {
expect(isPersonZwjEmoji('👨‍👩‍👧‍👦')).toBeTruthy();
});
it('should not detect family', () => {
expect(isPersonZwjEmoji('👪')).toBeFalsy();
});
it('should detect kiss_ww', () => {
expect(isPersonZwjEmoji('👩‍❤️‍💋‍👩')).toBeTruthy();
});
it('should not detect girl', () => {
expect(isPersonZwjEmoji('👧')).toBeFalsy();
});
it('should not detect girl_tone5', () => {
expect(isPersonZwjEmoji('👧🏿')).toBeFalsy();
});
it('should not detect man', () => {
expect(isPersonZwjEmoji('👨')).toBeFalsy();
});
it('should not detect woman', () => {
expect(isPersonZwjEmoji('👩')).toBeFalsy();
});
});
describe('isEmojiUnicodeSupported', () => {
it('bomb(6.0) with 6.0 support', () => {
const emojiKey = 'bomb';
const unicodeSupportMap = Object.assign({}, emptySupportMap, {
'6.0': true,
});
const isSupported = isEmojiUnicodeSupported(
unicodeSupportMap,
emojiFixtureMap[emojiKey].moji,
emojiFixtureMap[emojiKey].unicodeVersion,
);
expect(isSupported).toBeTruthy();
});
it('bomb(6.0) without 6.0 support', () => {
const emojiKey = 'bomb';
const unicodeSupportMap = emptySupportMap;
const isSupported = isEmojiUnicodeSupported(
unicodeSupportMap,
emojiFixtureMap[emojiKey].moji,
emojiFixtureMap[emojiKey].unicodeVersion,
);
expect(isSupported).toBeFalsy();
});
it('bomb(6.0) without 6.0 but with 9.0 support', () => {
const emojiKey = 'bomb';
const unicodeSupportMap = Object.assign({}, emptySupportMap, {
'9.0': true,
});
const isSupported = isEmojiUnicodeSupported(
unicodeSupportMap,
emojiFixtureMap[emojiKey].moji,
emojiFixtureMap[emojiKey].unicodeVersion,
);
expect(isSupported).toBeFalsy();
});
it('construction_worker_tone5(8.0) without skin tone modifier support', () => {
const emojiKey = 'construction_worker_tone5';
const unicodeSupportMap = Object.assign({}, emptySupportMap, {
skinToneModifier: false,
'9.0': true,
'8.0': true,
'7.0': true,
6.1: true,
'6.0': true,
5.2: true,
5.1: true,
4.1: true,
'4.0': true,
3.2: true,
'3.0': true,
1.1: true,
});
const isSupported = isEmojiUnicodeSupported(
unicodeSupportMap,
emojiFixtureMap[emojiKey].moji,
emojiFixtureMap[emojiKey].unicodeVersion,
);
expect(isSupported).toBeFalsy();
});
it('use native keycap on >=57 chrome', () => {
const emojiKey = 'five';
const unicodeSupportMap = Object.assign({}, emptySupportMap, {
'3.0': true,
meta: {
isChrome: true,
chromeVersion: 57,
},
});
const isSupported = isEmojiUnicodeSupported(
unicodeSupportMap,
emojiFixtureMap[emojiKey].moji,
emojiFixtureMap[emojiKey].unicodeVersion,
);
expect(isSupported).toBeTruthy();
});
it('fallback keycap on <57 chrome', () => {
const emojiKey = 'five';
const unicodeSupportMap = Object.assign({}, emptySupportMap, {
'3.0': true,
meta: {
isChrome: true,
chromeVersion: 50,
},
});
const isSupported = isEmojiUnicodeSupported(
unicodeSupportMap,
emojiFixtureMap[emojiKey].moji,
emojiFixtureMap[emojiKey].unicodeVersion,
);
expect(isSupported).toBeFalsy();
});
});
});
...@@ -14,12 +14,12 @@ describe Banzai::Filter::EmojiFilter, lib: true do ...@@ -14,12 +14,12 @@ describe Banzai::Filter::EmojiFilter, lib: true do
it 'replaces supported name emoji' do it 'replaces supported name emoji' do
doc = filter('<p>:heart:</p>') doc = filter('<p>:heart:</p>')
expect(doc.css('img').first.attr('src')).to eq 'https://foo.com/assets/2764.png' expect(doc.css('gl-emoji').first.text).to eq '❤'
end end
it 'replaces supported unicode emoji' do it 'replaces supported unicode emoji' do
doc = filter('<p>❤️</p>') doc = filter('<p>❤️</p>')
expect(doc.css('img').first.attr('src')).to eq 'https://foo.com/assets/2764.png' expect(doc.css('gl-emoji').first.text).to eq '❤'
end end
it 'ignores unsupported emoji' do it 'ignores unsupported emoji' do
...@@ -30,152 +30,78 @@ describe Banzai::Filter::EmojiFilter, lib: true do ...@@ -30,152 +30,78 @@ describe Banzai::Filter::EmojiFilter, lib: true do
it 'correctly encodes the URL' do it 'correctly encodes the URL' do
doc = filter('<p>:+1:</p>') doc = filter('<p>:+1:</p>')
expect(doc.css('img').first.attr('src')).to eq 'https://foo.com/assets/1F44D.png' expect(doc.css('gl-emoji').first.text).to eq '👍'
end end
it 'correctly encodes unicode to the URL' do it 'correctly encodes unicode to the URL' do
doc = filter('<p>👍</p>') doc = filter('<p>👍</p>')
expect(doc.css('img').first.attr('src')).to eq 'https://foo.com/assets/1F44D.png' expect(doc.css('gl-emoji').first.text).to eq '👍'
end end
it 'matches at the start of a string' do it 'matches at the start of a string' do
doc = filter(':+1:') doc = filter(':+1:')
expect(doc.css('img').size).to eq 1 expect(doc.css('gl-emoji').size).to eq 1
end end
it 'unicode matches at the start of a string' do it 'unicode matches at the start of a string' do
doc = filter("'👍'") doc = filter("'👍'")
expect(doc.css('img').size).to eq 1 expect(doc.css('gl-emoji').size).to eq 1
end end
it 'matches at the end of a string' do it 'matches at the end of a string' do
doc = filter('This gets a :-1:') doc = filter('This gets a :-1:')
expect(doc.css('img').size).to eq 1 expect(doc.css('gl-emoji').size).to eq 1
end end
it 'unicode matches at the end of a string' do it 'unicode matches at the end of a string' do
doc = filter('This gets a 👍') doc = filter('This gets a 👍')
expect(doc.css('img').size).to eq 1 expect(doc.css('gl-emoji').size).to eq 1
end end
it 'matches with adjacent text' do it 'matches with adjacent text' do
doc = filter('+1 (:+1:)') doc = filter('+1 (:+1:)')
expect(doc.css('img').size).to eq 1 expect(doc.css('gl-emoji').size).to eq 1
end end
it 'unicode matches with adjacent text' do it 'unicode matches with adjacent text' do
doc = filter('+1 (👍)') doc = filter('+1 (👍)')
expect(doc.css('img').size).to eq 1 expect(doc.css('gl-emoji').size).to eq 1
end end
it 'matches multiple emoji in a row' do it 'matches multiple emoji in a row' do
doc = filter(':see_no_evil::hear_no_evil::speak_no_evil:') doc = filter(':see_no_evil::hear_no_evil::speak_no_evil:')
expect(doc.css('img').size).to eq 3 expect(doc.css('gl-emoji').size).to eq 3
end end
it 'unicode matches multiple emoji in a row' do it 'unicode matches multiple emoji in a row' do
doc = filter("'🙈🙉🙊'") doc = filter("'🙈🙉🙊'")
expect(doc.css('img').size).to eq 3 expect(doc.css('gl-emoji').size).to eq 3
end end
it 'mixed matches multiple emoji in a row' do it 'mixed matches multiple emoji in a row' do
doc = filter("'🙈:see_no_evil:🙉:hear_no_evil:🙊:speak_no_evil:'") doc = filter("'🙈:see_no_evil:🙉:hear_no_evil:🙊:speak_no_evil:'")
expect(doc.css('img').size).to eq 6 expect(doc.css('gl-emoji').size).to eq 6
end end
it 'has a title attribute' do it 'has a data-name attribute' do
doc = filter(':-1:') doc = filter(':-1:')
expect(doc.css('img').first.attr('title')).to eq ':-1:' expect(doc.css('gl-emoji').first.attr('data-name')).to eq 'thumbsdown'
end end
it 'unicode has a title attribute' do it 'has a data-unicode-version attribute' do
doc = filter("'👎'")
expect(doc.css('img').first.attr('title')).to eq ':thumbsdown:'
end
it 'has an alt attribute' do
doc = filter(':-1:') doc = filter(':-1:')
expect(doc.css('img').first.attr('alt')).to eq ':-1:' expect(doc.css('gl-emoji').first.attr('data-unicode-version')).to eq '6.0'
end
it 'unicode has an alt attribute' do
doc = filter("'👎'")
expect(doc.css('img').first.attr('alt')).to eq ':thumbsdown:'
end
it 'has an align attribute' do
doc = filter(':8ball:')
expect(doc.css('img').first.attr('align')).to eq 'absmiddle'
end
it 'unicode has an align attribute' do
doc = filter("'🎱'")
expect(doc.css('img').first.attr('align')).to eq 'absmiddle'
end
it 'has an emoji class' do
doc = filter(':cat:')
expect(doc.css('img').first.attr('class')).to eq 'emoji'
end
it 'unicode has an emoji class' do
doc = filter("'🐱'")
expect(doc.css('img').first.attr('class')).to eq 'emoji'
end
it 'has height and width attributes' do
doc = filter(':dog:')
img = doc.css('img').first
expect(img.attr('width')).to eq '20'
expect(img.attr('height')).to eq '20'
end
it 'unicode has height and width attributes' do
doc = filter("'🐶'")
img = doc.css('img').first
expect(img.attr('width')).to eq '20'
expect(img.attr('height')).to eq '20'
end end
it 'keeps whitespace intact' do it 'keeps whitespace intact' do
doc = filter('This deserves a :+1:, big time.') doc = filter('This deserves a :+1:, big time.')
expect(doc.to_html).to match(/^This deserves a <img.+>, big time\.\z/) expect(doc.to_html).to match(/^This deserves a <gl-emoji.+>, big time\.\z/)
end end
it 'unicode keeps whitespace intact' do it 'unicode keeps whitespace intact' do
doc = filter('This deserves a 🎱, big time.') doc = filter('This deserves a 🎱, big time.')
expect(doc.to_html).to match(/^This deserves a <img.+>, big time\.\z/) expect(doc.to_html).to match(/^This deserves a <gl-emoji.+>, big time\.\z/)
end
it 'uses a custom asset_root context' do
root = Gitlab.config.gitlab.url + 'gitlab/root'
doc = filter(':smile:', asset_root: root)
expect(doc.css('img').first.attr('src')).to start_with(root)
end
it 'uses a custom asset_host context' do
ActionController::Base.asset_host = 'https://cdn.example.com'
doc = filter(':frowning:', asset_host: 'https://this-is-ignored-i-guess?')
expect(doc.css('img').first.attr('src')).to start_with('https://cdn.example.com')
end
it 'uses a custom asset_root context' do
root = Gitlab.config.gitlab.url + 'gitlab/root'
doc = filter("'🎱'", asset_root: root)
expect(doc.css('img').first.attr('src')).to start_with(root)
end
it 'uses a custom asset_host context' do
ActionController::Base.asset_host = 'https://cdn.example.com'
doc = filter("'🎱'", asset_host: 'https://this-is-ignored-i-guess?')
expect(doc.css('img').first.attr('src')).to start_with('https://cdn.example.com')
end end
end end
require 'spec_helper'
describe Gitlab::AwardEmoji do
describe '.urls' do
after do
Gitlab::AwardEmoji.instance_variable_set(:@urls, nil)
end
subject { Gitlab::AwardEmoji.urls }
it { is_expected.to be_an_instance_of(Array) }
it { is_expected.not_to be_empty }
context 'every Hash in the Array' do
it 'has the correct keys and values' do
subject.each do |hash|
expect(hash[:name]).to be_an_instance_of(String)
expect(hash[:path]).to be_an_instance_of(String)
end
end
end
context 'handles relative root' do
it 'includes the full path' do
allow(Gitlab::Application.config).to receive(:relative_url_root).and_return('/gitlab')
subject.each do |hash|
expect(hash[:name]).to be_an_instance_of(String)
expect(hash[:path]).to start_with('/gitlab')
end
end
end
end
describe '.emoji_by_category' do
it "only contains known categories" do
undefined_categories = Gitlab::AwardEmoji.emoji_by_category.keys - Gitlab::AwardEmoji::CATEGORIES.keys
expect(undefined_categories).to be_empty
end
end
end
...@@ -120,7 +120,6 @@ describe 'project routing' do ...@@ -120,7 +120,6 @@ describe 'project routing' do
end end
end end
# emojis_namespace_project_autocomplete_sources_path GET /:project_id/autocomplete_sources/emojis(.:format) projects/autocomplete_sources#emojis
# members_namespace_project_autocomplete_sources_path GET /:project_id/autocomplete_sources/members(.:format) projects/autocomplete_sources#members # members_namespace_project_autocomplete_sources_path GET /:project_id/autocomplete_sources/members(.:format) projects/autocomplete_sources#members
# issues_namespace_project_autocomplete_sources_path GET /:project_id/autocomplete_sources/issues(.:format) projects/autocomplete_sources#issues # issues_namespace_project_autocomplete_sources_path GET /:project_id/autocomplete_sources/issues(.:format) projects/autocomplete_sources#issues
# merge_requests_namespace_project_autocomplete_sources_path GET /:project_id/autocomplete_sources/merge_requests(.:format) projects/autocomplete_sources#merge_requests # merge_requests_namespace_project_autocomplete_sources_path GET /:project_id/autocomplete_sources/merge_requests(.:format) projects/autocomplete_sources#merge_requests
...@@ -128,7 +127,7 @@ describe 'project routing' do ...@@ -128,7 +127,7 @@ describe 'project routing' do
# milestones_namespace_project_autocomplete_sources_path GET /:project_id/autocomplete_sources/milestones(.:format) projects/autocomplete_sources#milestones # milestones_namespace_project_autocomplete_sources_path GET /:project_id/autocomplete_sources/milestones(.:format) projects/autocomplete_sources#milestones
# commands_namespace_project_autocomplete_sources_path GET /:project_id/autocomplete_sources/commands(.:format) projects/autocomplete_sources#commands # commands_namespace_project_autocomplete_sources_path GET /:project_id/autocomplete_sources/commands(.:format) projects/autocomplete_sources#commands
describe Projects::AutocompleteSourcesController, 'routing' do describe Projects::AutocompleteSourcesController, 'routing' do
[:emojis, :members, :issues, :merge_requests, :labels, :milestones, :commands].each do |action| [:members, :issues, :merge_requests, :labels, :milestones, :commands].each do |action|
it "to ##{action}" do it "to ##{action}" do
expect(get("/gitlab/gitlabhq/autocomplete_sources/#{action}")).to route_to("projects/autocomplete_sources##{action}", namespace_id: 'gitlab', project_id: 'gitlabhq') expect(get("/gitlab/gitlabhq/autocomplete_sources/#{action}")).to route_to("projects/autocomplete_sources##{action}", namespace_id: 'gitlab', project_id: 'gitlabhq')
end end
......
...@@ -26,10 +26,11 @@ module MarkdownMatchers ...@@ -26,10 +26,11 @@ module MarkdownMatchers
set_default_markdown_messages set_default_markdown_messages
match do |actual| match do |actual|
expect(actual).to have_selector('img.emoji', count: 10) expect(actual).to have_selector('gl-emoji', count: 10)
image = actual.at_css('img.emoji') emoji_element = actual.at_css('gl-emoji')
expect(image['src'].to_s).to start_with(Gitlab.config.gitlab.url + '/assets') expect(emoji_element['data-name'].to_s).not_to be_empty
expect(emoji_element['data-unicode-version'].to_s).not_to be_empty
end end
end end
......
...@@ -1395,6 +1395,10 @@ doctrine@1.5.0, doctrine@^1.2.2: ...@@ -1395,6 +1395,10 @@ doctrine@1.5.0, doctrine@^1.2.2:
esutils "^2.0.2" esutils "^2.0.2"
isarray "^1.0.0" isarray "^1.0.0"
document-register-element@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/document-register-element/-/document-register-element-1.3.0.tgz#fb3babb523c74662be47be19c6bc33e71990d940"
dom-serialize@^2.2.0: dom-serialize@^2.2.0:
version "2.2.1" version "2.2.1"
resolved "https://registry.yarnpkg.com/dom-serialize/-/dom-serialize-2.2.1.tgz#562ae8999f44be5ea3076f5419dcd59eb43ac95b" resolved "https://registry.yarnpkg.com/dom-serialize/-/dom-serialize-2.2.1.tgz#562ae8999f44be5ea3076f5419dcd59eb43ac95b"
...@@ -1439,6 +1443,10 @@ elliptic@^6.0.0: ...@@ -1439,6 +1443,10 @@ elliptic@^6.0.0:
hash.js "^1.0.0" hash.js "^1.0.0"
inherits "^2.0.1" inherits "^2.0.1"
emoji-unicode-version@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/emoji-unicode-version/-/emoji-unicode-version-0.2.1.tgz#0ebf3666b5414097971d34994e299fce75cdbafc"
emojis-list@^2.0.0: emojis-list@^2.0.0:
version "2.1.0" version "2.1.0"
resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-2.1.0.tgz#4daa4d9db00f9819880c79fa457ae5b09a1fd389" resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-2.1.0.tgz#4daa4d9db00f9819880c79fa457ae5b09a1fd389"
...@@ -4115,6 +4123,14 @@ string-width@^2.0.0: ...@@ -4115,6 +4123,14 @@ string-width@^2.0.0:
is-fullwidth-code-point "^2.0.0" is-fullwidth-code-point "^2.0.0"
strip-ansi "^3.0.0" strip-ansi "^3.0.0"
string.fromcodepoint@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/string.fromcodepoint/-/string.fromcodepoint-0.2.1.tgz#8d978333c0bc92538f50f383e4888f3e5619d653"
string.prototype.codepointat@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/string.prototype.codepointat/-/string.prototype.codepointat-0.2.0.tgz#6b26e9bd3afcaa7be3b4269b526de1b82000ac78"
string_decoder@^0.10.25, string_decoder@~0.10.x: string_decoder@^0.10.25, string_decoder@~0.10.x:
version "0.10.31" version "0.10.31"
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94"
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment