debian-mirror-gitlab/app/assets/javascripts/emoji/index.js

226 lines
6.2 KiB
JavaScript
Raw Normal View History

2021-03-11 19:13:27 +05:30
import { escape, minBy } from 'lodash';
2017-09-10 17:25:29 +05:30
import emojiAliases from 'emojis/aliases.json';
2020-07-28 23:09:34 +05:30
import AccessorUtilities from '../lib/utils/accessor';
2021-03-11 19:13:27 +05:30
import axios from '../lib/utils/axios_utils';
2021-04-17 20:07:23 +05:30
import { CATEGORY_ICON_MAP, FREQUENTLY_USED_KEY } from './constants';
2020-07-28 23:09:34 +05:30
let emojiMap = null;
let validEmojiNames = null;
2021-03-11 19:13:27 +05:30
export const FALLBACK_EMOJI_KEY = 'grey_question';
2020-07-28 23:09:34 +05:30
export const EMOJI_VERSION = '1';
const isLocalStorageAvailable = AccessorUtilities.isLocalStorageAccessSafe();
2021-01-03 14:25:43 +05:30
async function loadEmoji() {
if (
isLocalStorageAvailable &&
window.localStorage.getItem('gl-emoji-map-version') === EMOJI_VERSION &&
window.localStorage.getItem('gl-emoji-map')
) {
return JSON.parse(window.localStorage.getItem('gl-emoji-map'));
}
// We load the JSON file direct from the server
// because it can't be loaded from a CDN due to
// cross domain problems with JSON
const { data } = await axios.get(
`${gon.relative_url_root || ''}/-/emojis/${EMOJI_VERSION}/emojis.json`,
);
window.localStorage.setItem('gl-emoji-map-version', EMOJI_VERSION);
window.localStorage.setItem('gl-emoji-map', JSON.stringify(data));
return data;
}
2021-03-11 19:13:27 +05:30
async function loadEmojiWithNames() {
return Object.entries(await loadEmoji()).reduce((acc, [key, value]) => {
acc[key] = { ...value, name: key };
2021-01-03 14:25:43 +05:30
2021-03-11 19:13:27 +05:30
return acc;
}, {});
}
2021-01-03 14:25:43 +05:30
2021-03-11 19:13:27 +05:30
async function prepareEmojiMap() {
emojiMap = await loadEmojiWithNames();
validEmojiNames = [...Object.keys(emojiMap), ...Object.keys(emojiAliases)];
2021-01-03 14:25:43 +05:30
}
2020-07-28 23:09:34 +05:30
2021-01-03 14:25:43 +05:30
export function initEmojiMap() {
initEmojiMap.promise = initEmojiMap.promise || prepareEmojiMap();
return initEmojiMap.promise;
2020-07-28 23:09:34 +05:30
}
2017-09-10 17:25:29 +05:30
export function normalizeEmojiName(name) {
return Object.prototype.hasOwnProperty.call(emojiAliases, name) ? emojiAliases[name] : name;
}
2020-07-28 23:09:34 +05:30
export function getValidEmojiNames() {
return validEmojiNames;
}
2017-09-10 17:25:29 +05:30
export function isEmojiNameValid(name) {
2021-03-11 19:13:27 +05:30
if (!emojiMap) {
// eslint-disable-next-line @gitlab/require-i18n-strings
throw new Error('The emoji map is uninitialized or initialization has not completed');
}
return name in emojiMap || name in emojiAliases;
2017-09-10 17:25:29 +05:30
}
2021-01-03 14:25:43 +05:30
export function getAllEmoji() {
return emojiMap;
}
2021-03-11 19:13:27 +05:30
function getAliasesMatchingQuery(query) {
return Object.keys(emojiAliases)
.filter((alias) => alias.includes(query))
.reduce((map, alias) => {
const emojiName = emojiAliases[alias];
const score = alias.indexOf(query);
const prev = map.get(emojiName);
// overwrite if we beat the previous score or we're more alphabetical
const shouldSet =
!prev ||
prev.score > score ||
(prev.score === score && prev.alias.localeCompare(alias) > 0);
if (shouldSet) {
map.set(emojiName, { score, alias });
}
2021-01-03 14:25:43 +05:30
2021-03-11 19:13:27 +05:30
return map;
}, new Map());
}
function getUnicodeMatch(emoji, query) {
if (emoji.e === query) {
return { score: 0, field: 'e', fieldValue: emoji.name, emoji };
2021-01-03 14:25:43 +05:30
}
2021-03-11 19:13:27 +05:30
return null;
}
2021-01-03 14:25:43 +05:30
2021-03-11 19:13:27 +05:30
function getDescriptionMatch(emoji, query) {
if (emoji.d.includes(query)) {
return { score: emoji.d.indexOf(query), field: 'd', fieldValue: emoji.d, emoji };
2021-01-03 14:25:43 +05:30
}
2021-03-11 19:13:27 +05:30
return null;
2017-09-10 17:25:29 +05:30
}
2021-03-11 19:13:27 +05:30
function getAliasMatch(emoji, matchingAliases) {
if (matchingAliases.has(emoji.name)) {
const { score, alias } = matchingAliases.get(emoji.name);
2021-01-03 14:25:43 +05:30
2021-03-11 19:13:27 +05:30
return { score, field: 'alias', fieldValue: alias, emoji };
2021-01-03 14:25:43 +05:30
}
2021-03-11 19:13:27 +05:30
return null;
}
2021-01-03 14:25:43 +05:30
2021-03-11 19:13:27 +05:30
function getNameMatch(emoji, query) {
if (emoji.name.includes(query)) {
return {
score: emoji.name.indexOf(query),
field: 'name',
fieldValue: emoji.name,
emoji,
};
2021-01-03 14:25:43 +05:30
}
2021-03-11 19:13:27 +05:30
return null;
}
2021-01-03 14:25:43 +05:30
2021-03-11 19:13:27 +05:30
export function searchEmoji(query) {
const lowercaseQuery = query ? `${query}`.toLowerCase() : '';
2021-01-03 14:25:43 +05:30
2021-03-11 19:13:27 +05:30
const matchingAliases = getAliasesMatchingQuery(lowercaseQuery);
2021-01-03 14:25:43 +05:30
2021-03-11 19:13:27 +05:30
return Object.values(emojiMap)
.map((emoji) => {
const matches = [
getUnicodeMatch(emoji, query),
getDescriptionMatch(emoji, lowercaseQuery),
getAliasMatch(emoji, matchingAliases),
getNameMatch(emoji, lowercaseQuery),
].filter(Boolean);
2021-01-03 14:25:43 +05:30
2021-03-11 19:13:27 +05:30
return minBy(matches, (x) => x.score);
})
.filter(Boolean);
}
export function sortEmoji(items) {
// Sort results by index of and string comparison
return [...items].sort((a, b) => a.score - b.score || a.fieldValue.localeCompare(b.fieldValue));
2017-09-10 17:25:29 +05:30
}
2021-04-17 20:07:23 +05:30
export const CATEGORY_NAMES = Object.keys(CATEGORY_ICON_MAP);
2017-09-10 17:25:29 +05:30
let emojiCategoryMap;
export function getEmojiCategoryMap() {
if (!emojiCategoryMap) {
2021-04-17 20:07:23 +05:30
emojiCategoryMap = CATEGORY_NAMES.reduce((acc, category) => {
if (category === FREQUENTLY_USED_KEY) {
return acc;
}
return { ...acc, [category]: [] };
}, {});
2021-03-08 18:12:59 +05:30
Object.keys(emojiMap).forEach((name) => {
2017-09-10 17:25:29 +05:30
const emoji = emojiMap[name];
2020-07-28 23:09:34 +05:30
if (emojiCategoryMap[emoji.c]) {
emojiCategoryMap[emoji.c].push(name);
2017-09-10 17:25:29 +05:30
}
});
}
return emojiCategoryMap;
}
2021-03-11 19:13:27 +05:30
/**
* Retrieves an emoji by name
*
* @param {String} query The emoji name
* @param {Boolean} fallback If true, a fallback emoji will be returned if the
* named emoji does not exist.
* @returns {Object} The matching emoji.
*/
export function getEmojiInfo(query, fallback = true) {
if (!emojiMap) {
// eslint-disable-next-line @gitlab/require-i18n-strings
throw new Error('The emoji map is uninitialized or initialization has not completed');
}
const lowercaseQuery = query ? `${query}`.toLowerCase() : '';
const name = normalizeEmojiName(lowercaseQuery);
if (name in emojiMap) {
return emojiMap[name];
}
return fallback ? emojiMap[FALLBACK_EMOJI_KEY] : null;
2017-09-10 17:25:29 +05:30
}
export function emojiFallbackImageSrc(inputName) {
2020-07-28 23:09:34 +05:30
const { name } = getEmojiInfo(inputName);
2021-03-08 18:12:59 +05:30
return `${gon.asset_host || ''}${
gon.relative_url_root || ''
}/-/emojis/${EMOJI_VERSION}/${name}.png`;
2017-09-10 17:25:29 +05:30
}
export function emojiImageTag(name, src) {
return `<img class="emoji" title=":${name}:" alt=":${name}:" src="${src}" width="20" height="20" align="absmiddle" />`;
}
export function glEmojiTag(inputName, options) {
2020-07-28 23:09:34 +05:30
const opts = { sprite: false, ...options };
const name = normalizeEmojiName(inputName);
2017-09-10 17:25:29 +05:30
const fallbackSpriteClass = `emoji-${name}`;
2018-10-15 14:42:47 +05:30
const fallbackSpriteAttribute = opts.sprite
2021-03-11 19:13:27 +05:30
? `data-fallback-sprite-class="${escape(fallbackSpriteClass)}" `
2018-10-15 14:42:47 +05:30
: '';
2017-09-10 17:25:29 +05:30
2021-03-11 19:13:27 +05:30
return `<gl-emoji ${fallbackSpriteAttribute}data-name="${escape(name)}"></gl-emoji>`;
2017-09-10 17:25:29 +05:30
}