diff --git a/.gitlab/ci/rails.gitlab-ci.yml b/.gitlab/ci/rails.gitlab-ci.yml index 084143f1695..e424c4c9f6d 100644 --- a/.gitlab/ci/rails.gitlab-ci.yml +++ b/.gitlab/ci/rails.gitlab-ci.yml @@ -369,10 +369,8 @@ db:rollback geo: # EE: Canonical MR pipelines rspec foss-impact: extends: - - .rspec-base - - .as-if-foss + - .rspec-base-pg11-as-if-foss - .rails:rules:ee-mr-only - - .use-pg11 script: - install_gitlab_gem - run_timed_command "scripts/gitaly-test-build" diff --git a/.gitlab/ci/rules.gitlab-ci.yml b/.gitlab/ci/rules.gitlab-ci.yml index 4a65e9950cd..dc38dba8e94 100644 --- a/.gitlab/ci/rules.gitlab-ci.yml +++ b/.gitlab/ci/rules.gitlab-ci.yml @@ -322,11 +322,8 @@ rules: - <<: *if-not-ee when: never - - <<: *if-security-merge-request + - <<: *if-merge-request # Always run for MRs since `compile-test-assets as-if-foss` is either needed by `rspec foss-impact` or the `rspec * as-if-foss` jobs. changes: *code-backstage-qa-patterns - - <<: *if-merge-request-title-as-if-foss - - <<: *if-merge-request - changes: *ci-patterns .frontend:rules:default-frontend-jobs: rules: diff --git a/app/assets/javascripts/awards_handler.js b/app/assets/javascripts/awards_handler.js index 8381b050900..217577de7cb 100644 --- a/app/assets/javascripts/awards_handler.js +++ b/app/assets/javascripts/awards_handler.js @@ -9,6 +9,7 @@ import { updateTooltipTitle } from './lib/utils/common_utils'; import { isInVueNoteablePage } from './lib/utils/dom_utils'; import flash from './flash'; import axios from './lib/utils/axios_utils'; +import * as Emoji from '~/emoji'; const animationEndEventString = 'animationend webkitAnimationEnd MSAnimationEnd oAnimationEnd'; const transitionEndEventString = 'transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd'; @@ -619,7 +620,7 @@ export class AwardsHandler { let awardsHandlerPromise = null; export default function loadAwardsHandler(reload = false) { if (!awardsHandlerPromise || reload) { - awardsHandlerPromise = import(/* webpackChunkName: 'emoji' */ './emoji').then(Emoji => { + awardsHandlerPromise = Emoji.initEmojiMap().then(() => { const awardsHandler = new AwardsHandler(Emoji); awardsHandler.bindEvents(); return awardsHandler; diff --git a/app/assets/javascripts/behaviors/gl_emoji.js b/app/assets/javascripts/behaviors/gl_emoji.js index d1d75658181..bcf732e9522 100644 --- a/app/assets/javascripts/behaviors/gl_emoji.js +++ b/app/assets/javascripts/behaviors/gl_emoji.js @@ -1,47 +1,69 @@ import 'document-register-element'; import isEmojiUnicodeSupported from '../emoji/support'; +import { initEmojiMap, getEmojiInfo, emojiFallbackImageSrc, emojiImageTag } from '../emoji'; class GlEmoji extends HTMLElement { constructor() { super(); - const emojiUnicode = this.textContent.trim(); - const { name, unicodeVersion, fallbackSrc, fallbackSpriteClass } = this.dataset; + this.initialize(); + } + initialize() { + let emojiUnicode = this.textContent.trim(); + const { fallbackSpriteClass, fallbackSrc } = this.dataset; + let { name, unicodeVersion } = 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; + return initEmojiMap().then(() => { + if (!unicodeVersion) { + const emojiInfo = getEmojiInfo(name); - 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; + if (emojiInfo) { + if (name !== emojiInfo.name) { + ({ name } = emojiInfo); + this.dataset.name = emojiInfo.name; + } + unicodeVersion = emojiInfo.u; + this.dataset.unicodeVersion = unicodeVersion; + + emojiUnicode = emojiInfo.e; + this.innerHTML = emojiInfo.e; + + this.title = emojiInfo.d; } - // 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 { - const src = emojiFallbackImageSrc(name); - this.innerHTML = emojiImageTag(name, src); - } - }) - .catch(() => { - // do nothing - }); } - } + + const isEmojiUnicode = + this.childNodes && + Array.prototype.every.call(this.childNodes, childNode => childNode.nodeType === 3); + + if ( + emojiUnicode && + isEmojiUnicode && + !isEmojiUnicodeSupported(emojiUnicode, unicodeVersion) + ) { + const hasImageFallback = fallbackSrc && fallbackSrc.length > 0; + const hasCssSpriteFallback = fallbackSpriteClass && fallbackSpriteClass.length > 0; + + // CSS sprite fallback takes precedence over image fallback + if (hasCssSpriteFallback) { + 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); + } + } + }); } } diff --git a/app/assets/javascripts/emoji/index.js b/app/assets/javascripts/emoji/index.js index 27dff8cf9aa..4567c807c40 100644 --- a/app/assets/javascripts/emoji/index.js +++ b/app/assets/javascripts/emoji/index.js @@ -1,13 +1,63 @@ import { uniq } from 'lodash'; -import emojiMap from 'emojis/digests.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 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); + }); + } + }); + + return emojiPromise; +} export function normalizeEmojiName(name) { return Object.prototype.hasOwnProperty.call(emojiAliases, name) ? emojiAliases[name] : name; } +export function getValidEmojiNames() { + return validEmojiNames; +} + export function isEmojiNameValid(name) { return validEmojiNames.indexOf(name) >= 0; } @@ -36,8 +86,8 @@ export function getEmojiCategoryMap() { }; Object.keys(emojiMap).forEach(name => { const emoji = emojiMap[name]; - if (emojiCategoryMap[emoji.category]) { - emojiCategoryMap[emoji.category].push(name); + if (emojiCategoryMap[emoji.c]) { + emojiCategoryMap[emoji.c].push(name); } }); } @@ -58,8 +108,9 @@ export function getEmojiInfo(query) { } export function emojiFallbackImageSrc(inputName) { - const { name, digest } = getEmojiInfo(inputName); - return `${gon.asset_host || ''}${gon.relative_url_root || ''}/assets/emoji/${name}-${digest}.png`; + const { name } = getEmojiInfo(inputName); + return `${gon.asset_host || ''}${gon.relative_url_root || + ''}/-/emojis/${EMOJI_VERSION}/${name}.png`; } export function emojiImageTag(name, src) { @@ -67,36 +118,17 @@ export function emojiImageTag(name, src) { } export function glEmojiTag(inputName, options) { - const opts = { sprite: false, forceFallback: false, ...options }; - const { name, ...emojiInfo } = getEmojiInfo(inputName); - - const fallbackImageSrc = emojiFallbackImageSrc(name); + const opts = { sprite: false, ...options }; + const name = normalizeEmojiName(inputName); 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 ` - ${contents} - + data-name="${name}"> `; } diff --git a/app/assets/javascripts/filtered_search/visual_token_value.js b/app/assets/javascripts/filtered_search/visual_token_value.js index 02caf0851af..b1e6c4142e9 100644 --- a/app/assets/javascripts/filtered_search/visual_token_value.js +++ b/app/assets/javascripts/filtered_search/visual_token_value.js @@ -7,6 +7,7 @@ import DropdownUtils from '~/filtered_search/dropdown_utils'; import Flash from '~/flash'; import UsersCache from '~/lib/utils/users_cache'; import { __ } from '~/locale'; +import * as Emoji from '~/emoji'; export default class VisualTokenValue { constructor(tokenValue, tokenType, tokenOperator) { @@ -137,18 +138,13 @@ export default class VisualTokenValue { const element = tokenValueElement; const value = this.tokenValue; - return ( - import(/* webpackChunkName: 'emoji' */ '../emoji') - .then(Emoji => { - if (!Emoji.isEmojiNameValid(value)) { - return; - } + return Emoji.initEmojiMap().then(() => { + if (!Emoji.isEmojiNameValid(value)) { + return; + } - container.dataset.originalValue = value; - element.innerHTML = Emoji.glEmojiTag(value); - }) - // ignore error and leave emoji name in the search bar - .catch(() => {}) - ); + container.dataset.originalValue = value; + element.innerHTML = Emoji.glEmojiTag(value); + }); } } diff --git a/app/assets/javascripts/gfm_auto_complete.js b/app/assets/javascripts/gfm_auto_complete.js index fcd535cad73..0121fcf2859 100644 --- a/app/assets/javascripts/gfm_auto_complete.js +++ b/app/assets/javascripts/gfm_auto_complete.js @@ -5,6 +5,7 @@ import SidebarMediator from '~/sidebar/sidebar_mediator'; import glRegexp from './lib/utils/regexp'; import AjaxCache from './lib/utils/ajax_cache'; import { spriteIcon } from './lib/utils/common_utils'; +import * as Emoji from '~/emoji'; function sanitize(str) { return str.replace(/<(?:.|\n)*?>/gm, ''); @@ -586,14 +587,12 @@ class GfmAutoComplete { if (this.cachedData[at]) { this.loadData($input, at, this.cachedData[at]); } else if (GfmAutoComplete.atTypeMap[at] === 'emojis') { - import(/* webpackChunkName: 'emoji' */ './emoji') - .then(({ validEmojiNames, glEmojiTag }) => { - this.loadData($input, at, validEmojiNames); - GfmAutoComplete.glEmojiTag = glEmojiTag; + Emoji.initEmojiMap() + .then(() => { + this.loadData($input, at, Emoji.getValidEmojiNames()); + GfmAutoComplete.glEmojiTag = Emoji.glEmojiTag; }) - .catch(() => { - this.isLoadingData[at] = false; - }); + .catch(() => {}); } else if (dataSource) { AjaxCache.retrieve(dataSource, true) .then(data => { diff --git a/app/assets/javascripts/ide/components/ide_status_list.vue b/app/assets/javascripts/ide/components/ide_status_list.vue index 92d25709bd5..1354fdc3d98 100644 --- a/app/assets/javascripts/ide/components/ide_status_list.vue +++ b/app/assets/javascripts/ide/components/ide_status_list.vue @@ -1,12 +1,17 @@