Commit 57b54138 authored by Phil Hughes's avatar Phil Hughes

Merge branch 'tz-reorganise-digests-json' into 'master'

Reorganise Setup of Emojis and loading of digests.json

Closes #39000

See merge request gitlab-org/gitlab-ce!24543
parents b7998f60 d07919de

Too many changes to show.

To preserve performance only 1000 of 1000+ files are displayed.

...@@ -615,10 +615,18 @@ export class AwardsHandler { ...@@ -615,10 +615,18 @@ export class AwardsHandler {
let awardsHandlerPromise = null; let awardsHandlerPromise = null;
export default function loadAwardsHandler(reload = false) { export default function loadAwardsHandler(reload = false) {
if (!awardsHandlerPromise || reload) { if (!awardsHandlerPromise || reload) {
awardsHandlerPromise = import(/* webpackChunkName: 'emoji' */ './emoji').then(Emoji => { awardsHandlerPromise = new Promise((resolve, reject) => {
const awardsHandler = new AwardsHandler(Emoji); import(/* webpackChunkName: 'emoji' */ './emoji')
awardsHandler.bindEvents(); .then(Emoji => {
return awardsHandler; Emoji.initEmojiMap()
.then(() => {
const awardsHandler = new AwardsHandler(Emoji);
awardsHandler.bindEvents();
resolve(awardsHandler);
})
.catch(() => reject);
})
.catch(() => reject);
}); });
} }
return awardsHandlerPromise; return awardsHandlerPromise;
......
import 'document-register-element'; import 'document-register-element';
import isEmojiUnicodeSupported from '../emoji/support'; import isEmojiUnicodeSupported from '../emoji/support';
import { initEmojiMap, getEmojiInfo, emojiFallbackImageSrc, emojiImageTag } from '../emoji';
class GlEmoji extends HTMLElement { class GlEmoji extends HTMLElement {
constructor() { constructor() {
super(); super();
const emojiUnicode = this.textContent.trim(); let emojiUnicode = this.textContent.trim();
const { name, unicodeVersion, fallbackSrc, fallbackSpriteClass } = this.dataset; const { fallbackSpriteClass, fallbackSrc, forceFallback } = this.dataset;
let { name, unicodeVersion } = this.dataset;
const isEmojiUnicode =
this.childNodes && initEmojiMap()
Array.prototype.every.call(this.childNodes, childNode => childNode.nodeType === 3); .then(() => {
const hasImageFallback = fallbackSrc && fallbackSrc.length > 0; if (!unicodeVersion) {
const hasCssSpriteFalback = fallbackSpriteClass && fallbackSpriteClass.length > 0; const emojiInfo = getEmojiInfo(name);
if (emojiUnicode && isEmojiUnicode && !isEmojiUnicodeSupported(emojiUnicode, unicodeVersion)) { if (emojiInfo) {
// CSS sprite fallback takes precedence over image fallback if (name !== emojiInfo.name) {
if (hasCssSpriteFalback) { ({ name } = emojiInfo);
if (!gon.emoji_sprites_css_added && gon.emoji_sprites_css_path) { this.dataset.name = emojiInfo.name;
const emojiSpriteLinkTag = document.createElement('link'); }
emojiSpriteLinkTag.setAttribute('rel', 'stylesheet'); unicodeVersion = emojiInfo.u;
emojiSpriteLinkTag.setAttribute('href', gon.emoji_sprites_css_path); this.dataset.uni = unicodeVersion;
document.head.appendChild(emojiSpriteLinkTag);
gon.emoji_sprites_css_added = true; if (forceFallback === 'true' && !fallbackSpriteClass) {
} this.innerHTML = emojiImageTag(name, emojiFallbackImageSrc(name));
// IE 11 doesn't like adding multiple at once :(
this.classList.add('emoji-icon');
this.classList.add(fallbackSpriteClass);
} else {
import(/* webpackChunkName: 'emoji' */ '../emoji')
.then(({ emojiImageTag, emojiFallbackImageSrc }) => {
if (hasImageFallback) {
this.innerHTML = emojiImageTag(name, fallbackSrc);
} else { } else {
const src = emojiFallbackImageSrc(name); emojiUnicode = emojiInfo.e;
this.innerHTML = emojiImageTag(name, src); this.innerHTML = emojiInfo.e;
} }
})
.catch(() => { this.title = emojiInfo.d;
// do nothing }
}); }
}
} 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 (
emojiUnicode &&
isEmojiUnicode &&
!isEmojiUnicodeSupported(emojiUnicode, unicodeVersion)
) {
// CSS sprite fallback takes precedence over image fallback
if (hasCssSpriteFalback) {
if (!gon.emoji_sprites_css_added && gon.emoji_sprites_css_path) {
const emojiSpriteLinkTag = document.createElement('link');
emojiSpriteLinkTag.setAttribute('rel', 'stylesheet');
emojiSpriteLinkTag.setAttribute('href', gon.emoji_sprites_css_path);
document.head.appendChild(emojiSpriteLinkTag);
gon.emoji_sprites_css_added = true;
}
// 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 = emojiFallbackImageSrc(name);
this.innerHTML = emojiImageTag(name, src);
}
}
})
.catch(error => {
// Only reject is already handled in initEmojiMap
throw error;
});
} }
} }
......
import _ from 'underscore'; import _ from 'underscore';
import emojiMap from 'emojis/digests.json'; import createFlash from '~/flash';
import { s__ } from '~/locale';
import emojiAliases from 'emojis/aliases.json'; import emojiAliases from 'emojis/aliases.json';
import axios from '../lib/utils/axios_utils';
export const validEmojiNames = [...Object.keys(emojiMap), ...Object.keys(emojiAliases)]; import AccessorUtilities from '../lib/utils/accessor';
let emojiMap = null;
let validEmojiNames = null;
export const EMOJI_VERSION = '1';
const EMOJI_VERSION_LOCALSTORAGE = `EMOJIS_${EMOJI_VERSION}`;
const isLocalStorageAvailable = AccessorUtilities.isLocalStorageAccessSafe();
export function initEmojiMap() {
return new Promise((resolve, reject) => {
if (emojiMap) {
resolve(emojiMap);
} else if (isLocalStorageAvailable && window.localStorage.getItem(EMOJI_VERSION_LOCALSTORAGE)) {
emojiMap = JSON.parse(window.localStorage.getItem(EMOJI_VERSION_LOCALSTORAGE));
validEmojiNames = [...Object.keys(emojiMap), ...Object.keys(emojiAliases)];
resolve(emojiMap);
} else {
// We load the JSON from server
axios
.get(
`${gon.asset_host || ''}${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(EMOJI_VERSION_LOCALSTORAGE, JSON.stringify(emojiMap));
}
})
.catch(err => {
createFlash(s__('Emojis|Something went wrong while loading emojis.'));
reject(err);
});
}
});
}
export function normalizeEmojiName(name) { export function normalizeEmojiName(name) {
return Object.prototype.hasOwnProperty.call(emojiAliases, name) ? emojiAliases[name] : name; return Object.prototype.hasOwnProperty.call(emojiAliases, name) ? emojiAliases[name] : name;
} }
export function getValidEmojiNames() {
return validEmojiNames;
}
export function isEmojiNameValid(name) { export function isEmojiNameValid(name) {
return validEmojiNames.indexOf(name) >= 0; return validEmojiNames.indexOf(name) >= 0;
} }
...@@ -36,8 +81,8 @@ export function getEmojiCategoryMap() { ...@@ -36,8 +81,8 @@ export function getEmojiCategoryMap() {
}; };
Object.keys(emojiMap).forEach(name => { Object.keys(emojiMap).forEach(name => {
const emoji = emojiMap[name]; const emoji = emojiMap[name];
if (emojiCategoryMap[emoji.category]) { if (emojiCategoryMap[emoji.c]) {
emojiCategoryMap[emoji.category].push(name); emojiCategoryMap[emoji.c].push(name);
} }
}); });
} }
...@@ -58,8 +103,9 @@ export function getEmojiInfo(query) { ...@@ -58,8 +103,9 @@ export function getEmojiInfo(query) {
} }
export function emojiFallbackImageSrc(inputName) { export function emojiFallbackImageSrc(inputName) {
const { name, digest } = getEmojiInfo(inputName); const { name } = getEmojiInfo(inputName);
return `${gon.asset_host || ''}${gon.relative_url_root || ''}/assets/emoji/${name}-${digest}.png`; return `${gon.asset_host || ''}${gon.relative_url_root ||
''}/-/emojis/${EMOJI_VERSION}/${name}.png`;
} }
export function emojiImageTag(name, src) { export function emojiImageTag(name, src) {
...@@ -68,9 +114,8 @@ export function emojiImageTag(name, src) { ...@@ -68,9 +114,8 @@ export function emojiImageTag(name, src) {
export function glEmojiTag(inputName, options) { export function glEmojiTag(inputName, options) {
const opts = { sprite: false, forceFallback: false, ...options }; const opts = { sprite: false, forceFallback: false, ...options };
const { name, ...emojiInfo } = getEmojiInfo(inputName); const name = normalizeEmojiName(inputName);
const fallbackImageSrc = emojiFallbackImageSrc(name);
const fallbackSpriteClass = `emoji-${name}`; const fallbackSpriteClass = `emoji-${name}`;
const classList = []; const classList = [];
...@@ -79,24 +124,19 @@ export function glEmojiTag(inputName, options) { ...@@ -79,24 +124,19 @@ export function glEmojiTag(inputName, options) {
classList.push(fallbackSpriteClass); classList.push(fallbackSpriteClass);
} }
const classAttribute = classList.length > 0 ? `class="${classList.join(' ')}"` : ''; const classAttribute = classList.length > 0 ? `class="${classList.join(' ')}"` : '';
const fallbackSpriteAttribute = opts.sprite const fallbackSpriteAttribute = opts.sprite
? `data-fallback-sprite-class="${fallbackSpriteClass}"` ? `data-fallback-sprite-class="${fallbackSpriteClass}"`
: ''; : '';
let contents = emojiInfo.moji; const forceFallbackAttribute = opts.forceFallback ? 'data-force-fallback="true"' : '';
if (opts.forceFallback && !opts.sprite) {
contents = emojiImageTag(name, fallbackImageSrc);
}
return ` return `
<gl-emoji <gl-emoji
${classAttribute} ${classAttribute}
data-name="${name}" data-name="${name}"
data-fallback-src="${fallbackImageSrc}"
${fallbackSpriteAttribute} ${fallbackSpriteAttribute}
data-unicode-version="${emojiInfo.unicodeVersion}" ${forceFallbackAttribute}
title="${emojiInfo.description}"
> >
${contents}
</gl-emoji> </gl-emoji>
`; `;
} }
...@@ -5,6 +5,9 @@ import getUnicodeSupportMap from './unicode_support_map'; ...@@ -5,6 +5,9 @@ import getUnicodeSupportMap from './unicode_support_map';
let browserUnicodeSupportMap; let browserUnicodeSupportMap;
export default function isEmojiUnicodeSupportedByBrowser(emojiUnicode, unicodeVersion) { export default function isEmojiUnicodeSupportedByBrowser(emojiUnicode, unicodeVersion) {
// Our Spec browser would fail producing emoji maps
if (/\bHeadlessChrome\//.test(navigator.userAgent)) return true;
browserUnicodeSupportMap = browserUnicodeSupportMap || getUnicodeSupportMap(); browserUnicodeSupportMap = browserUnicodeSupportMap || getUnicodeSupportMap();
return isEmojiUnicodeSupported(browserUnicodeSupportMap, emojiUnicode, unicodeVersion); return isEmojiUnicodeSupported(browserUnicodeSupportMap, emojiUnicode, unicodeVersion);
} }
...@@ -102,15 +102,24 @@ export default class VisualTokenValue { ...@@ -102,15 +102,24 @@ export default class VisualTokenValue {
return ( return (
import(/* webpackChunkName: 'emoji' */ '../emoji') import(/* webpackChunkName: 'emoji' */ '../emoji')
.then(Emoji => { .then(Emoji => {
if (!Emoji.isEmojiNameValid(value)) { Emoji.initEmojiMap()
return; .then(() => {
} if (!Emoji.isEmojiNameValid(value)) {
return;
container.dataset.originalValue = value; }
element.innerHTML = Emoji.glEmojiTag(value);
container.dataset.originalValue = value;
element.innerHTML = Emoji.glEmojiTag(value);
})
// ignore error and leave emoji name in the search bar
.catch(err => {
throw err;
});
}) })
// ignore error and leave emoji name in the search bar // ignore error and leave emoji name in the search bar
.catch(() => {}) .catch(importError => {
throw importError;
})
); );
} }
} }
...@@ -487,9 +487,15 @@ class GfmAutoComplete { ...@@ -487,9 +487,15 @@ class GfmAutoComplete {
this.loadData($input, at, this.cachedData[at]); this.loadData($input, at, this.cachedData[at]);
} else if (GfmAutoComplete.atTypeMap[at] === 'emojis') { } else if (GfmAutoComplete.atTypeMap[at] === 'emojis') {
import(/* webpackChunkName: 'emoji' */ './emoji') import(/* webpackChunkName: 'emoji' */ './emoji')
.then(({ validEmojiNames, glEmojiTag }) => { .then(({ initEmojiMap, getValidEmojiNames, glEmojiTag }) => {
this.loadData($input, at, validEmojiNames); initEmojiMap()
GfmAutoComplete.glEmojiTag = glEmojiTag; .then(() => {
this.loadData($input, at, getValidEmojiNames());
GfmAutoComplete.glEmojiTag = glEmojiTag;
})
.catch(() => {
this.isLoadingData[at] = false;
});
}) })
.catch(() => { .catch(() => {
this.isLoadingData[at] = false; this.isLoadingData[at] = false;
......
...@@ -56,30 +56,34 @@ document.addEventListener('DOMContentLoaded', () => { ...@@ -56,30 +56,34 @@ document.addEventListener('DOMContentLoaded', () => {
import(/* webpackChunkName: 'emoji' */ '~/emoji') import(/* webpackChunkName: 'emoji' */ '~/emoji')
.then(Emoji => { .then(Emoji => {
const emojiMenu = new EmojiMenu( Emoji.initEmojiMap()
Emoji, .then(() => {
toggleEmojiMenuButtonSelector, const emojiMenu = new EmojiMenu(
'js-status-emoji-menu', Emoji,
selectEmojiCallback, toggleEmojiMenuButtonSelector,
); 'js-status-emoji-menu',
emojiMenu.bindEvents(); selectEmojiCallback,
);
emojiMenu.bindEvents();
const defaultEmojiTag = Emoji.glEmojiTag(defaultStatusEmoji); const defaultEmojiTag = Emoji.glEmojiTag(defaultStatusEmoji);
statusMessageField.addEventListener('input', () => { statusMessageField.addEventListener('input', () => {
const hasStatusMessage = statusMessageField.value.trim() !== ''; const hasStatusMessage = statusMessageField.value.trim() !== '';
const statusEmoji = findStatusEmoji(); const statusEmoji = findStatusEmoji();
if (hasStatusMessage && statusEmoji) { if (hasStatusMessage && statusEmoji) {
return; return;
} }
if (hasStatusMessage) { if (hasStatusMessage) {
toggleNoEmojiPlaceholder(false); toggleNoEmojiPlaceholder(false);
toggleEmojiMenuButton.innerHTML += defaultEmojiTag; toggleEmojiMenuButton.innerHTML += defaultEmojiTag;
} else if (statusEmoji.dataset.name === defaultStatusEmoji) { } else if (statusEmoji.dataset.name === defaultStatusEmoji) {
toggleNoEmojiPlaceholder(true); toggleNoEmojiPlaceholder(true);
removeStatusEmoji(); removeStatusEmoji();
} }
}); });
})
.catch(() => createFlash('Failed to load emoji list.'));
}) })
.catch(() => createFlash('Failed to load emoji list.')); .catch(() => createFlash('Failed to load emoji list.'));
}); });
...@@ -66,19 +66,23 @@ export default { ...@@ -66,19 +66,23 @@ export default {
import(/* webpackChunkName: 'emoji' */ '~/emoji') import(/* webpackChunkName: 'emoji' */ '~/emoji')
.then(Emoji => { .then(Emoji => {
if (this.emoji) { Emoji.initEmojiMap()
this.emojiTag = Emoji.glEmojiTag(this.emoji); .then(() => {
} if (this.emoji) {
this.noEmoji = this.emoji === ''; this.emojiTag = Emoji.glEmojiTag(this.emoji);
this.defaultEmojiTag = Emoji.glEmojiTag('speech_balloon'); }
this.noEmoji = this.emoji === '';
this.defaultEmojiTag = Emoji.glEmojiTag('speech_balloon');
this.emojiMenu = new EmojiMenuInModal( this.emojiMenu = new EmojiMenuInModal(
Emoji, Emoji,
toggleEmojiMenuButtonSelector, toggleEmojiMenuButtonSelector,
emojiMenuClass, emojiMenuClass,
this.setEmoji, this.setEmoji,
this.$refs.userStatusForm, this.$refs.userStatusForm,
); );
})
.catch(() => createFlash(__('Failed to load emoji list.')));
}) })
.catch(() => createFlash(__('Failed to load emoji list.'))); .catch(() => createFlash(__('Failed to load emoji list.')));
}, },
......
...@@ -30,33 +30,28 @@ namespace :gemojione do ...@@ -30,33 +30,28 @@ namespace :gemojione do
# We don't have `node_modules` available in built versions of GitLab # We don't have `node_modules` available in built versions of GitLab
FileUtils.cp_r(Rails.root.join('node_modules', 'emoji-unicode-version', 'emoji-unicode-version-map.json'), File.join(Rails.root, 'fixtures', 'emojis')) FileUtils.cp_r(Rails.root.join('node_modules', 'emoji-unicode-version', 'emoji-unicode-version-map.json'), File.join(Rails.root, 'fixtures', 'emojis'))
dir = Gemojione.images_path
resultant_emoji_map = {} resultant_emoji_map = {}
Gitlab::Emoji.emojis.each do |name, emoji_hash| Gitlab::Emoji.emojis.each do |name, emoji_hash|
# Ignore aliases # Ignore aliases
unless Gitlab::Emoji.emojis_aliases.key?(name) unless Gitlab::Emoji.emojis_aliases.key?(name)
fpath = File.join(dir, "#{emoji_hash['unicode']}.png")
hash_digest = Digest::SHA256.file(fpath).hexdigest
category = emoji_hash['category'] category = emoji_hash['category']
if name == 'gay_pride_flag' if name == 'gay_pride_flag'
category = 'flags' category = 'flags'
end end
entry = { entry = {
category: category, c: category,
moji: emoji_hash['moji'], e: emoji_hash['moji'],
description: emoji_hash['description'], d: emoji_hash['description'],
unicodeVersion: Gitlab::Emoji.emoji_unicode_version(name), u: Gitlab::Emoji.emoji_unicode_version(name)
digest: hash_digest
} }
resultant_emoji_map[name] = entry resultant_emoji_map[name] = entry
end end
end end
out = File.join(Rails.root, 'fixtures', 'emojis', 'digests.json') out = File.join(Rails.root, 'public', '-', 'emojis', '1', 'emojis.json')
File.open(out, 'w') do |handle| File.open(out, 'w') do |handle|
handle.write(JSON.pretty_generate(resultant_emoji_map)) handle.write(JSON.pretty_generate(resultant_emoji_map))
end end
......
...@@ -2968,6 +2968,9 @@ msgstr "" ...@@ -2968,6 +2968,9 @@ msgstr ""
msgid "Embed" msgid "Embed"
msgstr "" msgstr ""
msgid "Emojis|Something went wrong while loading emojis."
msgstr ""
msgid "Empty file" msgid "Empty file"
msgstr "" msgstr ""
......
This diff is collapsed.
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