Commit e915e9a4 authored by GitLab Bot's avatar GitLab Bot

Automatic merge of gitlab-org/gitlab-ce master

parents 981758df ac78b6dd

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);
} }
...@@ -3,7 +3,6 @@ import Timeago from 'timeago.js'; ...@@ -3,7 +3,6 @@ import Timeago from 'timeago.js';
import _ from 'underscore'; import _ from 'underscore';
import { GlTooltipDirective } from '@gitlab/ui'; import { GlTooltipDirective } from '@gitlab/ui';
import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue'; import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
import { humanize } from '~/lib/utils/text_utility';
import Icon from '~/vue_shared/components/icon.vue'; import Icon from '~/vue_shared/components/icon.vue';
import ActionsComponent from './environment_actions.vue'; import ActionsComponent from './environment_actions.vue';
import ExternalUrlComponent from './environment_external_url.vue'; import ExternalUrlComponent from './environment_external_url.vue';
...@@ -156,7 +155,7 @@ export default { ...@@ -156,7 +155,7 @@ export default {
const combinedActions = (manualActions || []).concat(scheduledActions || []); const combinedActions = (manualActions || []).concat(scheduledActions || []);
return combinedActions.map(action => ({ return combinedActions.map(action => ({
...action, ...action,
name: humanize(action.name), name: action.name,
})); }));
}, },
......
...@@ -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;
})
); );
} }
} }
...@@ -494,9 +494,15 @@ class GfmAutoComplete { ...@@ -494,9 +494,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;
......
import { viewerInformationForPath } from '~/vue_shared/components/content_viewer/lib/viewer_utils';
import { decorateData, sortTree } from '../stores/utils';
export const splitParent = path => {
const idx = path.lastIndexOf('/');
return {
parent: idx >= 0 ? path.substring(0, idx) : null,
name: idx >= 0 ? path.substring(idx + 1) : path,
};
};
/**
* Create file objects from a list of file paths.
*/
export const decorateFiles = ({
data,
projectId,
branchId,
tempFile = false,
content = '',
base64 = false,
}) => {
const treeList = [];
const entries = {};
// These mutable variable references end up being exported and used by `createTempEntry`
let file;
let parentPath;
const insertParent = path => {
if (!path) {
return null;
} else if (entries[path]) {
return entries[path];
}
const { parent, name } = splitParent(path);
const parentFolder = parent && insertParent(parent);
parentPath = parentFolder && parentFolder.path;
const tree = decorateData({
projectId,
branchId,
id: path,
name,
path,
url: `/${projectId}/tree/${branchId}/-/${path}/`,
type: 'tree',
parentTreeUrl: parentFolder ? parentFolder.url : `/${projectId}/tree/${branchId}/`,
tempFile,
changed: tempFile,
opened: tempFile,
parentPath,
});
Object.assign(entries, {
[path]: tree,
});
if (parentFolder) {
parentFolder.tree.push(tree);
} else {
treeList.push(tree);
}
return tree;
};
data.forEach(path => {
const { parent, name } = splitParent(path);
const fileFolder = parent && insertParent(parent);
if (name) {
parentPath = fileFolder && fileFolder.path;
file = decorateData({
projectId,
branchId,
id: path,
name,
path,
url: `/${projectId}/blob/${branchId}/-/${path}`,
type: 'blob',
parentTreeUrl: fileFolder ? fileFolder.url : `/${projectId}/blob/${branchId}`,
tempFile,
changed: tempFile,
content,
base64,
previewMode: viewerInformationForPath(name),
parentPath,
});
Object.assign(entries, {
[path]: file,
});
if (fileFolder) {
fileFolder.tree.push(file);
} else {
treeList.push(file);
}
}
});
return {
entries,
treeList: sortTree(treeList),
file,
parentPath,
};
};
...@@ -3,7 +3,7 @@ import Vue from 'vue'; ...@@ -3,7 +3,7 @@ import Vue from 'vue';
import { visitUrl } from '~/lib/utils/url_utility'; import { visitUrl } from '~/lib/utils/url_utility';
import flash from '~/flash'; import flash from '~/flash';
import * as types from './mutation_types'; import * as types from './mutation_types';
import FilesDecoratorWorker from './workers/files_decorator_worker'; import { decorateFiles } from '../lib/files';
import { stageKeys } from '../constants'; import { stageKeys } from '../constants';
export const redirectToUrl = (_, url) => visitUrl(url); export const redirectToUrl = (_, url) => visitUrl(url);
...@@ -56,7 +56,6 @@ export const createTempEntry = ( ...@@ -56,7 +56,6 @@ export const createTempEntry = (
{ name, type, content = '', base64 = false }, { name, type, content = '', base64 = false },
) => ) =>
new Promise(resolve => { new Promise(resolve => {
const worker = new FilesDecoratorWorker();
const fullName = name.slice(-1) !== '/' && type === 'tree' ? `${name}/` : name; const fullName = name.slice(-1) !== '/' && type === 'tree' ? `${name}/` : name;
if (state.entries[name]) { if (state.entries[name]) {
...@@ -74,31 +73,7 @@ export const createTempEntry = ( ...@@ -74,31 +73,7 @@ export const createTempEntry = (
return null; return null;
} }
worker.addEventListener('message', ({ data }) => { const data = decorateFiles({
const { file, parentPath } = data;
worker.terminate();
commit(types.CREATE_TMP_ENTRY, {
data,
projectId: state.currentProjectId,
branchId: state.currentBranchId,
});
if (type === 'blob') {
commit(types.TOGGLE_FILE_OPEN, file.path);
commit(types.ADD_FILE_TO_CHANGED, file.path);
dispatch('setFileActive', file.path);
}
if (parentPath && !state.entries[parentPath].opened) {
commit(types.TOGGLE_TREE_OPEN, parentPath);
}
resolve(file);
});
worker.postMessage({
data: [fullName], data: [fullName],
projectId: state.currentProjectId, projectId: state.currentProjectId,
branchId: state.currentBranchId, branchId: state.currentBranchId,
...@@ -107,6 +82,25 @@ export const createTempEntry = ( ...@@ -107,6 +82,25 @@ export const createTempEntry = (
base64, base64,
content, content,
}); });
const { file, parentPath } = data;
commit(types.CREATE_TMP_ENTRY, {
data,
projectId: state.currentProjectId,
branchId: state.currentBranchId,
});
if (type === 'blob') {
commit(types.TOGGLE_FILE_OPEN, file.path);
commit(types.ADD_FILE_TO_CHANGED, file.path);
dispatch('setFileActive', file.path);
}
if (parentPath && !state.entries[parentPath].opened) {
commit(types.TOGGLE_TREE_OPEN, parentPath);
}
resolve(file);
return null; return null;
}); });
......
import _ from 'underscore';
import { __ } from '../../../locale'; import { __ } from '../../../locale';
import service from '../../services'; import service from '../../services';
import * as types from '../mutation_types'; import * as types from '../mutation_types';
import FilesDecoratorWorker from '../workers/files_decorator_worker'; import { decorateFiles } from '../../lib/files';
export const toggleTreeOpen = ({ commit }, path) => { export const toggleTreeOpen = ({ commit }, path) => {
commit(types.TOGGLE_TREE_OPEN, path); commit(types.TOGGLE_TREE_OPEN, path);
...@@ -32,6 +33,19 @@ export const handleTreeEntryAction = ({ commit, dispatch }, row) => { ...@@ -32,6 +33,19 @@ export const handleTreeEntryAction = ({ commit, dispatch }, row) => {
dispatch('showTreeEntry', row.path); dispatch('showTreeEntry', row.path);
}; };
export const setDirectoryData = ({ state, commit }, { projectId, branchId, treeList }) => {
const selectedTree = state.trees[`${projectId}/${branchId}`];
commit(types.SET_DIRECTORY_DATA, {
treePath: `${projectId}/${branchId}`,
data: treeList,
});
commit(types.TOGGLE_LOADING, {
entry: selectedTree,
forceValue: false,
});
};
export const getFiles = ({ state, commit, dispatch }, { projectId, branchId } = {}) => export const getFiles = ({ state, commit, dispatch }, { projectId, branchId } = {}) =>
new Promise((resolve, reject) => { new Promise((resolve, reject) => {
if ( if (
...@@ -45,31 +59,19 @@ export const getFiles = ({ state, commit, dispatch }, { projectId, branchId } = ...@@ -45,31 +59,19 @@ export const getFiles = ({ state, commit, dispatch }, { projectId, branchId } =
service service
.getFiles(selectedProject.web_url, branchId) .getFiles(selectedProject.web_url, branchId)
.then(({ data }) => { .then(({ data }) => {
const worker = new FilesDecoratorWorker(); const { entries, treeList } = decorateFiles({
worker.addEventListener('message', e => {
const { entries, treeList } = e.data;
const selectedTree = state.trees[`${projectId}/${branchId}`];
commit(types.SET_ENTRIES, entries);
commit(types.SET_DIRECTORY_DATA, {
treePath: `${projectId}/${branchId}`,
data: treeList,
});
commit(types.TOGGLE_LOADING, {
entry: selectedTree,
forceValue: false,
});
worker.terminate();
resolve();
});
worker.postMessage({
data, data,
projectId, projectId,
branchId, branchId,
}); });
commit(types.SET_ENTRIES, entries);
// Defer setting the directory data because this triggers some intense rendering.
// The entries is all we need to load the file editor.
_.defer(() => dispatch('setDirectoryData', { projectId, branchId, treeList }));
resolve();
}) })
.catch(e => { .catch(e => {
if (e.response.status === 404) { if (e.response.status === 404) {
......
...@@ -75,8 +75,7 @@ export const decorateData = entity => { ...@@ -75,8 +75,7 @@ export const decorateData = entity => {
parentPath = '', parentPath = '',
} = entity; } = entity;
return { return Object.assign(dataStructure(), {
...dataStructure(),
id, id,
projectId, projectId,
branchId, branchId,
...@@ -97,7 +96,7 @@ export const decorateData = entity => { ...@@ -97,7 +96,7 @@ export const decorateData = entity => {
file_lock, file_lock,
html, html,
parentPath, parentPath,
}; });
}; };
export const findEntry = (tree, type, name, prop = 'name') => export const findEntry = (tree, type, name, prop = 'name') =>
......
import { viewerInformationForPath } from '~/vue_shared/components/content_viewer/lib/viewer_utils';
import { decorateData, sortTree } from '../utils';
// eslint-disable-next-line no-restricted-globals
self.addEventListener('message', e => {
const { data, projectId, branchId, tempFile = false, content = '', base64 = false } = e.data;
const treeList = [];
let file;
let parentPath;
const entries = data.reduce((acc, path) => {
const pathSplit = path.split('/');
const blobName = pathSplit.pop().trim();
if (pathSplit.length > 0) {
pathSplit.reduce((pathAcc, folderName) => {
const parentFolder = acc[pathAcc[pathAcc.length - 1]];
const folderPath = `${parentFolder ? `${parentFolder.path}/` : ''}${folderName}`;
const foundEntry = acc[folderPath];
if (!foundEntry) {
parentPath = parentFolder ? parentFolder.path : null;
const tree = decorateData({
projectId,
branchId,
id: folderPath,
name: folderName,
path: folderPath,
url: `/${projectId}/tree/${branchId}/-/${folderPath}/`,
type: 'tree',
parentTreeUrl: parentFolder ? parentFolder.url : `/${projectId}/tree/${branchId}/`,
tempFile,
changed: tempFile,
opened: tempFile,
parentPath,
});
Object.assign(acc, {
[folderPath]: tree,
});
if (parentFolder) {
parentFolder.tree.push(tree);
} else {
treeList.push(tree);
}
pathAcc.push(tree.path);
} else {
pathAcc.push(foundEntry.path);
}
return pathAcc;
}, []);
}
if (blobName !== '') {
const fileFolder = acc[pathSplit.join('/')];
parentPath = fileFolder ? fileFolder.path : null;
file = decorateData({
projectId,
branchId,
id: path,
name: blobName,
path,
url: `/${projectId}/blob/${branchId}/-/${path}`,
type: 'blob',
parentTreeUrl: fileFolder ? fileFolder.url : `/${projectId}/blob/${branchId}`,
tempFile,
changed: tempFile,
content,
base64,
previewMode: viewerInformationForPath(blobName),
parentPath,
});
Object.assign(acc, {
[path]: file,
});
if (fileFolder) {
fileFolder.tree.push(file);
} else {
treeList.push(file);
}
}
return acc;
}, {});
// eslint-disable-next-line no-restricted-globals
self.postMessage({
entries,
treeList: sortTree(treeList),
file,
parentPath,
});
});
...@@ -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.')));
}, },
......
...@@ -20,7 +20,6 @@ export default { ...@@ -20,7 +20,6 @@ export default {
<div> <div>
<gl-dropdown <gl-dropdown
right right
no-caret
text="Use an existing commit message" text="Use an existing commit message"
variant="link" variant="link"
class="mr-commit-dropdown" class="mr-commit-dropdown"
......
...@@ -333,7 +333,7 @@ export default { ...@@ -333,7 +333,7 @@ export default {
> >
<ul class="border-top content-list commits-list flex-list"> <ul class="border-top content-list commits-list flex-list">
<commit-edit <commit-edit
v-if="squashBeforeMerge" v-if="squashBeforeMerge && shouldShowSquashBeforeMerge"
v-model="squashCommitMessage" v-model="squashCommitMessage"
:label="__('Squash commit message')" :label="__('Squash commit message')"
input-id="squash-message-edit" input-id="squash-message-edit"
......
...@@ -11,4 +11,4 @@ ...@@ -11,4 +11,4 @@
- next unless can?(current_user, :update_build, action) - next unless can?(current_user, :update_build, action)
%li %li
= link_to [:play, @project.namespace.becomes(Namespace), @project, action], method: :post, rel: 'nofollow', class: 'btn' do = link_to [:play, @project.namespace.becomes(Namespace), @project, action], method: :post, rel: 'nofollow', class: 'btn' do
%span= action.name.humanize %span= action.name
---
title: Display job names consistently on pipelines and environments list
merge_request: 24984
author:
type: fixed
---
title: Improve Web IDE launch performance
merge_request: 25700
author:
type: performance
---
title: Resolve showing squash commit edit issue when only single commit is present
merge_request: 25807
author:
type: fixed
...@@ -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
......
...@@ -3601,6 +3601,9 @@ msgstr "" ...@@ -3601,6 +3601,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