debian-mirror-gitlab/app/assets/javascripts/awards_handler.js

649 lines
20 KiB
JavaScript
Raw Normal View History

2020-04-22 19:07:51 +05:30
/* eslint-disable class-methods-use-this, @gitlab/require-i18n-strings */
2018-05-09 12:01:36 +05:30
2020-03-13 15:44:24 +05:30
import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils';
2021-03-11 19:13:27 +05:30
import $ from 'jquery';
import { uniq } from 'lodash';
2022-07-23 23:45:48 +05:30
import { getEmojiScoreWithIntent } from '~/emoji/utils';
2022-04-04 11:22:00 +05:30
import { getCookie, setCookie, scrollToElement } from '~/lib/utils/common_utils';
2020-07-28 23:09:34 +05:30
import * as Emoji from '~/emoji';
2021-01-29 00:20:46 +05:30
import { dispose, fixTitle } from '~/tooltips';
2021-09-30 23:02:18 +05:30
import createFlash from './flash';
2021-03-11 19:13:27 +05:30
import axios from './lib/utils/axios_utils';
import { isInVueNoteablePage } from './lib/utils/dom_utils';
import { __ } from './locale';
2017-08-17 22:00:37 +05:30
const animationEndEventString = 'animationend webkitAnimationEnd MSAnimationEnd oAnimationEnd';
const transitionEndEventString = 'transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd';
const FROM_SENTENCE_REGEX = /(?:, and | and |, )/; // For separating lists produced by ruby's Array#toSentence
const categoryLabelMap = {
activity: 'Activity',
people: 'People',
nature: 'Nature',
food: 'Food',
travel: 'Travel',
objects: 'Objects',
symbols: 'Symbols',
flags: 'Flags',
};
2018-03-17 18:26:18 +05:30
const IS_VISIBLE = 'is-visible';
const IS_RENDERED = 'is-rendered';
2018-11-18 11:00:15 +05:30
export class AwardsHandler {
2017-09-10 17:25:29 +05:30
constructor(emoji) {
this.emoji = emoji;
this.eventListeners = [];
2018-11-18 11:00:15 +05:30
this.toggleButtonSelector = '.js-add-award';
this.menuClass = 'js-award-emoji-menu';
}
bindEvents() {
2018-12-05 23:21:45 +05:30
const $parentEl = this.targetContainerEl ? $(this.targetContainerEl) : $(document);
2017-09-10 17:25:29 +05:30
// If the user shows intent let's pre-build the menu
2018-11-08 19:23:39 +05:30
this.registerEventListener(
'one',
2018-12-05 23:21:45 +05:30
$parentEl,
2018-11-08 19:23:39 +05:30
'mouseenter focus',
2018-11-18 11:00:15 +05:30
this.toggleButtonSelector,
2018-11-08 19:23:39 +05:30
'mouseenter focus',
() => {
2018-11-18 11:00:15 +05:30
const $menu = $(`.${this.menuClass}`);
2018-11-08 19:23:39 +05:30
if ($menu.length === 0) {
requestAnimationFrame(() => {
this.createEmojiMenu();
});
}
},
);
2021-03-08 18:12:59 +05:30
this.registerEventListener('on', $parentEl, 'click', this.toggleButtonSelector, (e) => {
2017-09-10 17:25:29 +05:30
e.stopPropagation();
e.preventDefault();
this.showEmojiMenu($(e.currentTarget));
});
2017-08-17 22:00:37 +05:30
2021-03-08 18:12:59 +05:30
this.registerEventListener('on', $('html'), 'click', (e) => {
2017-09-10 17:25:29 +05:30
const $target = $(e.target);
2018-11-18 11:00:15 +05:30
if (!$target.closest(`.${this.menuClass}`).length) {
2018-03-27 19:54:05 +05:30
$('.js-awards-block.current').removeClass('current');
2018-11-18 11:00:15 +05:30
if ($(`.${this.menuClass}`).is(':visible')) {
$(`${this.toggleButtonSelector}.is-active`).removeClass('is-active');
this.hideMenuElement($(`.${this.menuClass}`));
2017-09-10 17:25:29 +05:30
}
}
});
2018-11-18 11:00:15 +05:30
const emojiButtonSelector = `.js-awards-block .js-emoji-btn, .${this.menuClass} .js-emoji-btn`;
2021-03-08 18:12:59 +05:30
this.registerEventListener('on', $parentEl, 'click', emojiButtonSelector, (e) => {
2017-09-10 17:25:29 +05:30
e.preventDefault();
const $target = $(e.currentTarget);
const $glEmojiElement = $target.find('gl-emoji');
const $spriteIconElement = $target.find('.icon');
2018-11-08 19:23:39 +05:30
const emojiName = ($glEmojiElement.length ? $glEmojiElement : $spriteIconElement).data(
'name',
);
2017-09-10 17:25:29 +05:30
$target.closest('.js-awards-block').addClass('current');
this.addAward(this.getVotesBlock(), this.getAwardUrl(), emojiName);
});
}
2017-08-17 22:00:37 +05:30
2017-09-10 17:25:29 +05:30
registerEventListener(method = 'on', element, ...args) {
element[method].call(element, ...args);
this.eventListeners.push({
element,
args,
});
}
2017-08-17 22:00:37 +05:30
2017-09-10 17:25:29 +05:30
showEmojiMenu($addBtn) {
if ($addBtn.hasClass('js-note-emoji')) {
2021-03-08 18:12:59 +05:30
$addBtn.closest('.note').find('.js-awards-block').addClass('current');
2017-08-17 22:00:37 +05:30
} else {
2017-09-10 17:25:29 +05:30
$addBtn.closest('.js-awards-block').addClass('current');
2017-08-17 22:00:37 +05:30
}
2018-11-18 11:00:15 +05:30
const $menu = $(`.${this.menuClass}`);
2017-09-10 17:25:29 +05:30
if ($menu.length) {
if ($menu.is('.is-visible')) {
$addBtn.removeClass('is-active');
2018-03-17 18:26:18 +05:30
this.hideMenuElement($menu);
2017-09-10 17:25:29 +05:30
$('.js-emoji-menu-search').blur();
} else {
$addBtn.addClass('is-active');
this.positionMenu($menu, $addBtn);
2018-03-17 18:26:18 +05:30
this.showMenuElement($menu);
2017-09-10 17:25:29 +05:30
$('.js-emoji-menu-search').focus();
}
} else {
$addBtn.addClass('is-loading is-active');
this.createEmojiMenu(() => {
2018-11-18 11:00:15 +05:30
const $createdMenu = $(`.${this.menuClass}`);
2017-09-10 17:25:29 +05:30
$addBtn.removeClass('is-loading');
this.positionMenu($createdMenu, $addBtn);
return setTimeout(() => {
2018-03-17 18:26:18 +05:30
this.showMenuElement($createdMenu);
2017-09-10 17:25:29 +05:30
$('.js-emoji-menu-search').focus();
}, 200);
});
}
2017-08-17 22:00:37 +05:30
}
2017-09-10 17:25:29 +05:30
// Create the emoji menu with the first category of emojis.
// Then render the remaining categories of emojis one by one to avoid jank.
createEmojiMenu(callback) {
if (this.isCreatingEmojiMenu) {
return;
}
this.isCreatingEmojiMenu = true;
// Render the first category
const categoryMap = this.emoji.getEmojiCategoryMap();
const categoryNameKey = Object.keys(categoryMap)[0];
const emojisInCategory = categoryMap[categoryNameKey];
const firstCategory = this.renderCategory(categoryLabelMap[categoryNameKey], emojisInCategory);
// Render the frequently used
const frequentlyUsedEmojis = this.getFrequentlyUsedEmojis();
let frequentlyUsedCatgegory = '';
if (frequentlyUsedEmojis.length > 0) {
frequentlyUsedCatgegory = this.renderCategory('Frequently used', frequentlyUsedEmojis, {
menuListClass: 'frequent-emojis',
});
}
const emojiMenuMarkup = `
2018-11-18 11:00:15 +05:30
<div class="emoji-menu ${this.menuClass}">
2017-09-10 17:25:29 +05:30
<input type="text" name="emoji-menu-search" value="" class="js-emoji-menu-search emoji-search search-input form-control" placeholder="Search emoji" />
2017-08-17 22:00:37 +05:30
2017-09-10 17:25:29 +05:30
<div class="emoji-menu-content">
${frequentlyUsedCatgegory}
${firstCategory}
</div>
2017-08-17 22:00:37 +05:30
</div>
2017-09-10 17:25:29 +05:30
`;
2017-08-17 22:00:37 +05:30
2018-12-05 23:21:45 +05:30
const targetEl = this.targetContainerEl ? this.targetContainerEl : document.body;
2022-10-11 01:57:18 +05:30
// eslint-disable-next-line no-unsanitized/method
2018-12-05 23:21:45 +05:30
targetEl.insertAdjacentHTML('beforeend', emojiMenuMarkup);
2017-08-17 22:00:37 +05:30
2017-09-10 17:25:29 +05:30
this.addRemainingEmojiMenuCategories();
this.setupSearch();
if (callback) {
callback();
}
2017-08-17 22:00:37 +05:30
}
2017-09-10 17:25:29 +05:30
addRemainingEmojiMenuCategories() {
2017-08-17 22:00:37 +05:30
if (this.isAddingRemainingEmojiMenuCategories) {
return;
}
this.isAddingRemainingEmojiMenuCategories = true;
2017-09-10 17:25:29 +05:30
const categoryMap = this.emoji.getEmojiCategoryMap();
2017-08-17 22:00:37 +05:30
// Avoid the jank and render the remaining categories separately
// This will take more time, but makes UI more responsive
2018-11-18 11:00:15 +05:30
const menu = document.querySelector(`.${this.menuClass}`);
2017-08-17 22:00:37 +05:30
const emojiContentElement = menu.querySelector('.emoji-menu-content');
const remainingCategories = Object.keys(categoryMap).slice(1);
const allCategoriesAddedPromise = remainingCategories.reduce(
(promiseChain, categoryNameKey) =>
2018-11-08 19:23:39 +05:30
promiseChain.then(
() =>
2021-03-08 18:12:59 +05:30
new Promise((resolve) => {
2018-11-08 19:23:39 +05:30
const emojisInCategory = categoryMap[categoryNameKey];
const categoryMarkup = this.renderCategory(
categoryLabelMap[categoryNameKey],
emojisInCategory,
);
requestAnimationFrame(() => {
2022-10-11 01:57:18 +05:30
// eslint-disable-next-line no-unsanitized/method
2018-11-08 19:23:39 +05:30
emojiContentElement.insertAdjacentHTML('beforeend', categoryMarkup);
resolve();
});
}),
),
2017-08-17 22:00:37 +05:30
Promise.resolve(),
);
2018-11-08 19:23:39 +05:30
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'));
}
})
2021-03-08 18:12:59 +05:30
.catch((err) => {
2018-11-08 19:23:39 +05:30
emojiContentElement.insertAdjacentHTML(
'beforeend',
'<p>We encountered an error while adding the remaining categories</p>',
);
throw new Error(`Error occurred in addRemainingEmojiMenuCategories: ${err.message}`);
});
2017-09-10 17:25:29 +05:30
}
2017-08-17 22:00:37 +05:30
2017-09-10 17:25:29 +05:30
renderCategory(name, emojiList, opts = {}) {
return `
<h5 class="emoji-menu-title">
${name}
</h5>
<ul class="clearfix emoji-menu-list ${opts.menuListClass || ''}">
2018-11-08 19:23:39 +05:30
${emojiList
.map(
2021-03-08 18:12:59 +05:30
(emojiName) => `
2017-09-10 17:25:29 +05:30
<li class="emoji-menu-list-item">
<button class="emoji-menu-btn text-center js-emoji-btn" type="button">
${this.emoji.glEmojiTag(emojiName, {
sprite: true,
})}
</button>
</li>
2018-11-08 19:23:39 +05:30
`,
)
.join('\n')}
2017-09-10 17:25:29 +05:30
</ul>
`;
}
2017-08-17 22:00:37 +05:30
2017-09-10 17:25:29 +05:30
positionMenu($menu, $addBtn) {
2018-12-05 23:21:45 +05:30
if (this.targetContainerEl) {
return $menu.css({
top: `${$addBtn.outerHeight()}px`,
});
}
2017-09-10 17:25:29 +05:30
const position = $addBtn.data('position');
// The menu could potentially be off-screen or in a hidden overflow element
// So we position the element absolute in the body
const css = {
top: `${$addBtn.offset().top + $addBtn.outerHeight()}px`,
};
2019-07-07 11:18:12 +05:30
// for xs screen we position the element on center
2020-03-13 15:44:24 +05:30
if (bp.getBreakpointSize() === 'xs' || bp.getBreakpointSize() === 'sm') {
2019-07-07 11:18:12 +05:30
css.left = '5%';
} else if (position === 'right') {
2018-11-08 19:23:39 +05:30
css.left = `${$addBtn.offset().left - $menu.outerWidth() + 20}px`;
2017-09-10 17:25:29 +05:30
$menu.addClass('is-aligned-right');
2017-08-17 22:00:37 +05:30
} else {
2017-09-10 17:25:29 +05:30
css.left = `${$addBtn.offset().left}px`;
$menu.removeClass('is-aligned-right');
2017-08-17 22:00:37 +05:30
}
2017-09-10 17:25:29 +05:30
return $menu.css(css);
2017-08-17 22:00:37 +05:30
}
2017-09-10 17:25:29 +05:30
addAward(votesBlock, awardUrl, emoji, checkMutuality, callback) {
2018-03-27 19:54:05 +05:30
const isMainAwardsBlock = votesBlock.closest('.js-noteable-awards').length;
2018-03-17 18:26:18 +05:30
2018-05-09 12:01:36 +05:30
if (isInVueNoteablePage() && !isMainAwardsBlock) {
2018-03-17 18:26:18 +05:30
const id = votesBlock.attr('id').replace('note_', '');
2018-11-18 11:00:15 +05:30
this.hideMenuElement($(`.${this.menuClass}`));
2018-03-17 18:26:18 +05:30
2018-11-18 11:00:15 +05:30
$(`${this.toggleButtonSelector}.is-active`).removeClass('is-active');
2018-03-17 18:26:18 +05:30
const toggleAwardEvent = new CustomEvent('toggleAward', {
detail: {
awardName: emoji,
noteId: id,
},
});
document.querySelector('.js-vue-notes-event').dispatchEvent(toggleAwardEvent);
}
2017-09-10 17:25:29 +05:30
const normalizedEmoji = this.emoji.normalizeEmojiName(emoji);
const $emojiButton = this.findEmojiIcon(votesBlock, normalizedEmoji).parent();
2018-03-17 18:26:18 +05:30
2017-09-10 17:25:29 +05:30
this.postEmoji($emojiButton, awardUrl, normalizedEmoji, () => {
this.addAwardToEmojiBar(votesBlock, normalizedEmoji, checkMutuality);
return typeof callback === 'function' ? callback() : undefined;
});
2018-03-17 18:26:18 +05:30
2018-11-18 11:00:15 +05:30
this.hideMenuElement($(`.${this.menuClass}`));
2018-03-17 18:26:18 +05:30
2018-11-18 11:00:15 +05:30
return $(`${this.toggleButtonSelector}.is-active`).removeClass('is-active');
2017-08-17 22:00:37 +05:30
}
2017-09-10 17:25:29 +05:30
addAwardToEmojiBar(votesBlock, emoji, checkForMutuality) {
if (checkForMutuality || checkForMutuality === null) {
this.checkMutuality(votesBlock, emoji);
}
this.addEmojiToFrequentlyUsedList(emoji);
const normalizedEmoji = this.emoji.normalizeEmojiName(emoji);
const $emojiButton = this.findEmojiIcon(votesBlock, normalizedEmoji).parent();
if ($emojiButton.length > 0) {
if (this.isActive($emojiButton)) {
this.decrementCounter($emojiButton, normalizedEmoji);
} else {
const counter = $emojiButton.find('.js-counter');
counter.text(parseInt(counter.text(), 10) + 1);
$emojiButton.addClass('active');
this.addYouToUserList(votesBlock, normalizedEmoji);
this.animateEmoji($emojiButton);
}
} else {
votesBlock.removeClass('hidden');
this.createEmoji(votesBlock, normalizedEmoji);
}
}
2017-08-17 22:00:37 +05:30
2017-09-10 17:25:29 +05:30
getVotesBlock() {
2018-05-09 12:01:36 +05:30
if (isInVueNoteablePage()) {
2018-11-18 11:00:15 +05:30
const $el = $(`${this.toggleButtonSelector}.is-active`).closest('.note.timeline-entry');
2018-03-17 18:26:18 +05:30
if ($el.length) {
return $el;
}
}
2017-09-10 17:25:29 +05:30
const currentBlock = $('.js-awards-block.current');
let resultantVotesBlock = currentBlock;
if (currentBlock.length === 0) {
resultantVotesBlock = $('.js-awards-block').eq(0);
}
2017-08-17 22:00:37 +05:30
2017-09-10 17:25:29 +05:30
return resultantVotesBlock;
}
getAwardUrl() {
2018-03-27 19:54:05 +05:30
return this.getVotesBlock().data('awardUrl');
2017-09-10 17:25:29 +05:30
}
checkMutuality(votesBlock, emoji) {
const awardUrl = this.getAwardUrl();
if (emoji === 'thumbsup' || emoji === 'thumbsdown') {
const mutualVote = emoji === 'thumbsup' ? 'thumbsdown' : 'thumbsup';
const $emojiButton = votesBlock.find(`[data-name="${mutualVote}"]`).parent();
const isAlreadyVoted = $emojiButton.hasClass('active');
if (isAlreadyVoted) {
this.addAward(votesBlock, awardUrl, mutualVote, false);
}
2017-08-17 22:00:37 +05:30
}
}
2017-09-10 17:25:29 +05:30
isActive($emojiButton) {
return $emojiButton.hasClass('active');
}
2017-08-17 22:00:37 +05:30
2017-09-10 17:25:29 +05:30
decrementCounter($emojiButton, emoji) {
const counter = $('.js-counter', $emojiButton);
const counterNumber = parseInt(counter.text(), 10);
if (counterNumber > 1) {
counter.text(counterNumber - 1);
this.removeYouFromUserList($emojiButton);
} else if (emoji === 'thumbsup' || emoji === 'thumbsdown') {
2021-01-29 00:20:46 +05:30
dispose($emojiButton);
2017-09-10 17:25:29 +05:30
counter.text('0');
this.removeYouFromUserList($emojiButton);
if ($emojiButton.parents('.note').length) {
this.removeEmoji($emojiButton);
}
} else {
2017-08-17 22:00:37 +05:30
this.removeEmoji($emojiButton);
}
2017-09-10 17:25:29 +05:30
return $emojiButton.removeClass('active');
2017-08-17 22:00:37 +05:30
}
2017-09-10 17:25:29 +05:30
removeEmoji($emojiButton) {
2021-01-29 00:20:46 +05:30
dispose($emojiButton);
2017-09-10 17:25:29 +05:30
$emojiButton.remove();
const $votesBlock = this.getVotesBlock();
if ($votesBlock.find('.js-emoji-btn').length === 0) {
$votesBlock.addClass('hidden');
}
2017-08-17 22:00:37 +05:30
}
2017-09-10 17:25:29 +05:30
getAwardTooltip($awardBlock) {
return $awardBlock.attr('data-original-title') || $awardBlock.attr('data-title') || '';
2017-08-17 22:00:37 +05:30
}
2017-09-10 17:25:29 +05:30
toSentence(list) {
let sentence;
if (list.length <= 2) {
sentence = list.join(' and ');
} else {
sentence = `${list.slice(0, -1).join(', ')}, and ${list[list.length - 1]}`;
}
2017-08-17 22:00:37 +05:30
2017-09-10 17:25:29 +05:30
return sentence;
}
2017-08-17 22:00:37 +05:30
2017-09-10 17:25:29 +05:30
removeYouFromUserList($emojiButton) {
const awardBlock = $emojiButton;
const originalTitle = this.getAwardTooltip(awardBlock);
const authors = originalTitle.split(FROM_SENTENCE_REGEX);
authors.splice(authors.indexOf('You'), 1);
2021-01-29 00:20:46 +05:30
awardBlock
2017-09-10 17:25:29 +05:30
.closest('.js-emoji-btn')
.removeData('title')
.removeAttr('data-title')
.removeAttr('data-original-title')
2021-01-29 00:20:46 +05:30
.attr('title', this.toSentence(authors));
fixTitle(awardBlock);
return awardBlock;
2017-09-10 17:25:29 +05:30
}
addYouToUserList(votesBlock, emoji) {
const awardBlock = this.findEmojiIcon(votesBlock, emoji).parent();
const origTitle = this.getAwardTooltip(awardBlock);
let users = [];
if (origTitle) {
users = origTitle.trim().split(FROM_SENTENCE_REGEX);
}
users.unshift('You');
2021-01-29 00:20:46 +05:30
awardBlock.attr('title', this.toSentence(users));
fixTitle(awardBlock);
return awardBlock;
2017-09-10 17:25:29 +05:30
}
2017-08-17 22:00:37 +05:30
2017-09-10 17:25:29 +05:30
createAwardButtonForVotesBlock(votesBlock, emojiName) {
2017-08-17 22:00:37 +05:30
const buttonHtml = `
2021-04-29 21:17:54 +05:30
<button class="gl-button btn btn-default award-control js-emoji-btn has-tooltip active" title="You">
2017-09-10 17:25:29 +05:30
${this.emoji.glEmojiTag(emojiName)}
2017-08-17 22:00:37 +05:30
<span class="award-control-text js-counter">1</span>
</button>
`;
const $emojiButton = $(buttonHtml);
2018-11-08 19:23:39 +05:30
$emojiButton
.insertBefore(votesBlock.find('.js-award-holder'))
.find('.emoji-icon')
.data('name', emojiName);
2017-08-17 22:00:37 +05:30
this.animateEmoji($emojiButton);
2021-01-29 00:20:46 +05:30
2017-08-17 22:00:37 +05:30
votesBlock.removeClass('current');
2017-09-10 17:25:29 +05:30
}
2017-08-17 22:00:37 +05:30
2017-09-10 17:25:29 +05:30
animateEmoji($emoji) {
const className = 'pulse animated once short';
$emoji.addClass(className);
2017-08-17 22:00:37 +05:30
2021-03-08 18:12:59 +05:30
this.registerEventListener('on', $emoji, animationEndEventString, (e) => {
2017-09-10 17:25:29 +05:30
$(e.currentTarget).removeClass(className);
});
2017-08-17 22:00:37 +05:30
}
2017-09-10 17:25:29 +05:30
createEmoji(votesBlock, emoji) {
2018-11-18 11:00:15 +05:30
if ($(`.${this.menuClass}`).length) {
2017-09-10 17:25:29 +05:30
this.createAwardButtonForVotesBlock(votesBlock, emoji);
}
this.createEmojiMenu(() => {
this.createAwardButtonForVotesBlock(votesBlock, emoji);
});
2017-08-17 22:00:37 +05:30
}
2017-09-10 17:25:29 +05:30
postEmoji($emojiButton, awardUrl, emoji, callback) {
2018-11-20 20:47:30 +05:30
axios
.post(awardUrl, {
name: emoji,
})
.then(({ data }) => {
if (data.ok) {
callback();
}
})
2021-09-30 23:02:18 +05:30
.catch(() =>
createFlash({
message: __('Something went wrong on our end.'),
}),
);
2017-09-10 17:25:29 +05:30
}
2017-08-17 22:00:37 +05:30
2017-09-10 17:25:29 +05:30
findEmojiIcon(votesBlock, emoji) {
return votesBlock.find(`.js-emoji-btn [data-name="${emoji}"]`);
}
2017-08-17 22:00:37 +05:30
2017-09-10 17:25:29 +05:30
scrollToAwards() {
2021-04-17 20:07:23 +05:30
scrollToElement('.awards', { offset: -110 });
2017-09-10 17:25:29 +05:30
}
2017-08-17 22:00:37 +05:30
2017-09-10 17:25:29 +05:30
addEmojiToFrequentlyUsedList(emoji) {
if (this.emoji.isEmojiNameValid(emoji)) {
2020-05-24 23:13:21 +05:30
this.frequentlyUsedEmojis = uniq(this.getFrequentlyUsedEmojis().concat(emoji));
2022-04-04 11:22:00 +05:30
setCookie('frequently_used_emojis', this.frequentlyUsedEmojis.join(','));
2017-08-17 22:00:37 +05:30
}
2017-09-10 17:25:29 +05:30
}
2016-09-29 09:46:39 +05:30
2017-09-10 17:25:29 +05:30
getFrequentlyUsedEmojis() {
2018-11-08 19:23:39 +05:30
return (
this.frequentlyUsedEmojis ||
(() => {
2022-04-04 11:22:00 +05:30
const frequentlyUsedEmojis = uniq((getCookie('frequently_used_emojis') || '').split(','));
2021-03-08 18:12:59 +05:30
this.frequentlyUsedEmojis = frequentlyUsedEmojis.filter((inputName) =>
2018-11-08 19:23:39 +05:30
this.emoji.isEmojiNameValid(inputName),
);
return this.frequentlyUsedEmojis;
})()
);
2017-09-10 17:25:29 +05:30
}
2016-09-13 17:45:13 +05:30
2017-09-10 17:25:29 +05:30
setupSearch() {
const $search = $('.js-emoji-menu-search');
2016-09-13 17:45:13 +05:30
2021-03-08 18:12:59 +05:30
this.registerEventListener('on', $search, 'input', (e) => {
const term = $(e.target).val().trim();
2017-09-10 17:25:29 +05:30
this.searchEmojis(term);
});
2017-08-17 22:00:37 +05:30
2018-11-18 11:00:15 +05:30
const $menu = $(`.${this.menuClass}`);
2021-03-08 18:12:59 +05:30
this.registerEventListener('on', $menu, transitionEndEventString, (e) => {
2017-09-10 17:25:29 +05:30
if (e.target === e.currentTarget) {
// Clear the search
this.searchEmojis('');
}
});
2017-08-17 22:00:37 +05:30
}
2017-09-10 17:25:29 +05:30
searchEmojis(term) {
const $search = $('.js-emoji-menu-search');
$search.val(term);
// Clean previous search results
$('ul.emoji-menu-search, h5.emoji-search-title').remove();
if (term.length > 0) {
// Generate a search result block
const h5 = $('<h5 class="emoji-search-title"/>').text('Search results');
const foundEmojis = this.findMatchingEmojiElements(term).show();
2021-03-08 18:12:59 +05:30
const ul = $('<ul>').addClass('emoji-menu-list emoji-menu-search').append(foundEmojis);
2017-09-10 17:25:29 +05:30
$('.emoji-menu-content ul, .emoji-menu-content h5').hide();
2021-03-08 18:12:59 +05:30
$('.emoji-menu-content').append(h5).append(ul);
2017-09-10 17:25:29 +05:30
} else {
2021-03-08 18:12:59 +05:30
$('.emoji-menu-content').children().show();
2017-08-17 22:00:37 +05:30
}
2017-09-10 17:25:29 +05:30
}
2017-08-17 22:00:37 +05:30
2022-07-23 23:45:48 +05:30
getEmojiScore(emojis, value) {
const elem = $(value).find('[data-name]').get(0);
const emoji = emojis.filter((x) => x.emoji.name === elem.dataset.name)[0];
elem.dataset.score = emoji.score;
return emoji.score;
}
sortEmojiElements(emojis, $elements) {
const scores = new WeakMap();
return $elements.sort((a, b) => {
let aScore = scores.get(a);
let bScore = scores.get(b);
if (!aScore) {
aScore = this.getEmojiScore(emojis, a);
scores.set(a, aScore);
}
if (!bScore) {
bScore = this.getEmojiScore(emojis, b);
scores.set(b, bScore);
}
return aScore - bScore;
});
}
2017-09-10 17:25:29 +05:30
findMatchingEmojiElements(query) {
2022-07-23 23:45:48 +05:30
const matchingEmoji = this.emoji
.searchEmoji(query)
.map((x) => ({ ...x, score: getEmojiScoreWithIntent(x.emoji.name, x.score) }));
const matchingEmojiNames = matchingEmoji.map((x) => x.emoji.name);
2017-09-10 17:25:29 +05:30
const $emojiElements = $('.emoji-menu-list:not(.frequent-emojis) [data-name]');
2018-11-08 19:23:39 +05:30
const $matchingElements = $emojiElements.filter(
2022-07-23 23:45:48 +05:30
(i, elm) => matchingEmojiNames.indexOf(elm.dataset.name) >= 0,
2018-11-08 19:23:39 +05:30
);
2022-07-23 23:45:48 +05:30
return this.sortEmojiElements(matchingEmoji, $matchingElements.closest('li').clone());
2017-09-10 17:25:29 +05:30
}
2018-03-17 18:26:18 +05:30
/* showMenuElement and hideMenuElement are performance optimizations. We use
* opacity to show/hide the emoji menu, because we can animate it. But opacity
* leaves hidden elements in the render tree, which is unacceptable given the number
* of emoji elements in the emoji menu (5k+). To get the best of both worlds, we separately
* apply IS_RENDERED to add/remove the menu from the render tree and IS_VISIBLE to animate
* the menu being opened and closed. */
showMenuElement($emojiMenu) {
$emojiMenu.addClass(IS_RENDERED);
// enqueues animation as a microtask, so it begins ASAP once IS_RENDERED added
2018-11-08 19:23:39 +05:30
return Promise.resolve().then(() => $emojiMenu.addClass(IS_VISIBLE));
2018-03-17 18:26:18 +05:30
}
hideMenuElement($emojiMenu) {
2021-03-08 18:12:59 +05:30
$emojiMenu.on(transitionEndEventString, (e) => {
2018-03-17 18:26:18 +05:30
if (e.currentTarget === e.target) {
2021-02-22 17:27:13 +05:30
// eslint-disable-next-line @gitlab/no-global-event-off
2018-11-08 19:23:39 +05:30
$emojiMenu.removeClass(IS_RENDERED).off(transitionEndEventString);
2018-03-17 18:26:18 +05:30
}
});
$emojiMenu.removeClass(IS_VISIBLE);
}
2017-09-10 17:25:29 +05:30
destroy() {
2021-03-08 18:12:59 +05:30
this.eventListeners.forEach((entry) => {
2017-09-10 17:25:29 +05:30
entry.element.off.call(entry.element, ...entry.args);
});
2018-11-18 11:00:15 +05:30
$(`.${this.menuClass}`).remove();
2017-09-10 17:25:29 +05:30
}
}
2017-08-17 22:00:37 +05:30
2017-09-10 17:25:29 +05:30
let awardsHandlerPromise = null;
export default function loadAwardsHandler(reload = false) {
if (!awardsHandlerPromise || reload) {
2020-07-28 23:09:34 +05:30
awardsHandlerPromise = Emoji.initEmojiMap().then(() => {
2018-12-05 23:21:45 +05:30
const awardsHandler = new AwardsHandler(Emoji);
awardsHandler.bindEvents();
return awardsHandler;
});
2017-09-10 17:25:29 +05:30
}
return awardsHandlerPromise;
}