Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'app/assets/javascripts/emoji/index.js')
-rw-r--r--app/assets/javascripts/emoji/index.js125
1 files changed, 83 insertions, 42 deletions
diff --git a/app/assets/javascripts/emoji/index.js b/app/assets/javascripts/emoji/index.js
index 4567c807c40..1bdc7b3a8b5 100644
--- a/app/assets/javascripts/emoji/index.js
+++ b/app/assets/javascripts/emoji/index.js
@@ -1,53 +1,58 @@
import { uniq } from 'lodash';
+import fuzzaldrinPlus from 'fuzzaldrin-plus';
import emojiAliases from 'emojis/aliases.json';
import axios from '../lib/utils/axios_utils';
-
import AccessorUtilities from '../lib/utils/accessor';
let emojiMap = null;
-let emojiPromise = null;
let validEmojiNames = null;
export const EMOJI_VERSION = '1';
const isLocalStorageAvailable = AccessorUtilities.isLocalStorageAccessSafe();
-export function initEmojiMap() {
- emojiPromise =
- emojiPromise ||
- new Promise((resolve, reject) => {
- if (emojiMap) {
- resolve(emojiMap);
- } else if (
- isLocalStorageAvailable &&
- window.localStorage.getItem('gl-emoji-map-version') === EMOJI_VERSION &&
- window.localStorage.getItem('gl-emoji-map')
- ) {
- emojiMap = JSON.parse(window.localStorage.getItem('gl-emoji-map'));
- validEmojiNames = [...Object.keys(emojiMap), ...Object.keys(emojiAliases)];
- resolve(emojiMap);
- } else {
- // 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
- axios
- .get(`${gon.relative_url_root || ''}/-/emojis/${EMOJI_VERSION}/emojis.json`)
- .then(({ data }) => {
- emojiMap = data;
- validEmojiNames = [...Object.keys(emojiMap), ...Object.keys(emojiAliases)];
- resolve(emojiMap);
- if (isLocalStorageAvailable) {
- window.localStorage.setItem('gl-emoji-map-version', EMOJI_VERSION);
- window.localStorage.setItem('gl-emoji-map', JSON.stringify(emojiMap));
- }
- })
- .catch(err => {
- reject(err);
- });
- }
- });
+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'));
+ }
- return emojiPromise;
+ // 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;
+}
+
+async function prepareEmojiMap() {
+ emojiMap = await loadEmoji();
+
+ validEmojiNames = [...Object.keys(emojiMap), ...Object.keys(emojiAliases)];
+
+ Object.keys(emojiMap).forEach(name => {
+ emojiMap[name].aliases = [];
+ emojiMap[name].name = name;
+ });
+ Object.entries(emojiAliases).forEach(([alias, name]) => {
+ // This check, `if (name in emojiMap)` is necessary during testing. In
+ // production, it shouldn't be necessary, because at no point should there
+ // be an entry in aliases.json with no corresponding entry in emojis.json.
+ // However, during testing, the endpoint for emojis.json is mocked with a
+ // small dataset, whereas aliases.json is always `import`ed directly.
+ if (name in emojiMap) emojiMap[name].aliases.push(alias);
+ });
+}
+
+export function initEmojiMap() {
+ initEmojiMap.promise = initEmojiMap.promise || prepareEmojiMap();
+ return initEmojiMap.promise;
}
export function normalizeEmojiName(name) {
@@ -62,13 +67,49 @@ export function isEmojiNameValid(name) {
return validEmojiNames.indexOf(name) >= 0;
}
-export function filterEmojiNames(filter) {
- const match = filter.toLowerCase();
- return validEmojiNames.filter(name => name.indexOf(match) >= 0);
+/**
+ * Search emoji by name or alias. Returns a normalized, deduplicated list of
+ * names.
+ *
+ * Calling with an empty filter returns an empty array.
+ *
+ * @param {String}
+ * @returns {Array}
+ */
+export function queryEmojiNames(filter) {
+ const matches = fuzzaldrinPlus.filter(validEmojiNames, filter);
+ return uniq(matches.map(name => normalizeEmojiName(name)));
}
-export function filterEmojiNamesByAlias(filter) {
- return uniq(filterEmojiNames(filter).map(name => normalizeEmojiName(name)));
+/**
+ * Searches emoji by name, alias, description, and unicode value and returns an
+ * array of matches.
+ *
+ * Note: `initEmojiMap` must have been called and completed before this method
+ * can safely be called.
+ *
+ * @param {String} query The search query
+ * @returns {Object[]} A list of emoji that match the query
+ */
+export function searchEmoji(query) {
+ if (!emojiMap)
+ // eslint-disable-next-line @gitlab/require-i18n-strings
+ throw new Error('The emoji map is uninitialized or initialization has not completed');
+
+ const matches = s => fuzzaldrinPlus.score(s, query) > 0;
+
+ // Search emoji
+ return Object.values(emojiMap).filter(
+ emoji =>
+ // by name
+ matches(emoji.name) ||
+ // by alias
+ emoji.aliases.some(matches) ||
+ // by description
+ matches(emoji.d) ||
+ // by unicode value
+ query === emoji.e,
+ );
}
let emojiCategoryMap;