Commit 8dcdb314 authored by Clement Ho's avatar Clement Ho

Merge branch 'tz-optimize-emoji-sprite' into 'master'

Optimize Emoji Sprite Handling

See merge request gitlab-org/gitlab-ce!18576
parents c7c9f38d 77c47389
......@@ -7,27 +7,24 @@ export default function installGlEmojiElement() {
const GlEmojiElementProto = Object.create(HTMLElement.prototype);
GlEmojiElementProto.createdCallback = function createdCallback() {
const emojiUnicode = this.textContent.trim();
const {
name,
unicodeVersion,
fallbackSrc,
fallbackSpriteClass,
} = this.dataset;
const { name, unicodeVersion, fallbackSrc, fallbackSpriteClass } = this.dataset;
const isEmojiUnicode = this.childNodes && Array.prototype.every.call(
this.childNodes,
childNode => childNode.nodeType === 3,
);
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)
) {
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);
......
......@@ -34,7 +34,7 @@ export function getEmojiCategoryMap() {
symbols: [],
flags: [],
};
Object.keys(emojiMap).forEach((name) => {
Object.keys(emojiMap).forEach(name => {
const emoji = emojiMap[name];
if (emojiCategoryMap[emoji.category]) {
emojiCategoryMap[emoji.category].push(name);
......@@ -79,7 +79,9 @@ export function glEmojiTag(inputName, options) {
classList.push(fallbackSpriteClass);
}
const classAttribute = classList.length > 0 ? `class="${classList.join(' ')}"` : '';
const fallbackSpriteAttribute = opts.sprite ? `data-fallback-sprite-class="${fallbackSpriteClass}"` : '';
const fallbackSpriteAttribute = opts.sprite
? `data-fallback-sprite-class="${fallbackSpriteClass}"`
: '';
let contents = emojiInfo.moji;
if (opts.forceFallback && !opts.sprite) {
contents = emojiImageTag(name, fallbackImageSrc);
......
......@@ -54,7 +54,8 @@ const unicodeSupportTestMap = {
function checkPixelInImageDataArray(pixelOffset, imageDataArray) {
// `4 *` because RGBA
const indexOffset = 4 * pixelOffset;
const hasColor = imageDataArray[indexOffset + 0] ||
const hasColor =
imageDataArray[indexOffset + 0] ||
imageDataArray[indexOffset + 1] ||
imageDataArray[indexOffset + 2];
const isVisible = imageDataArray[indexOffset + 3];
......@@ -75,23 +76,23 @@ const chromeVersion = chromeMatches && chromeMatches[1] && parseInt(chromeMatche
const fontSize = 16;
function generateUnicodeSupportMap(testMap) {
const testMapKeys = Object.keys(testMap);
const numTestEntries = testMapKeys
.reduce((list, testKey) => list.concat(testMap[testKey]), []).length;
const numTestEntries = testMapKeys.reduce((list, testKey) => list.concat(testMap[testKey]), [])
.length;
const canvas = document.createElement('canvas');
(window.gl || window).testEmojiUnicodeSupportMapCanvas = canvas;
const ctx = canvas.getContext('2d');
canvas.width = (2 * fontSize);
canvas.height = (numTestEntries * fontSize);
canvas.width = 2 * fontSize;
canvas.height = numTestEntries * fontSize;
ctx.fillStyle = '#000000';
ctx.textBaseline = 'middle';
ctx.font = `${fontSize}px "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"`;
// Write each emoji to the canvas vertically
let writeIndex = 0;
testMapKeys.forEach((testKey) => {
testMapKeys.forEach(testKey => {
const testEntry = testMap[testKey];
[].concat(testEntry).forEach((emojiUnicode) => {
ctx.fillText(emojiUnicode, 0, (writeIndex * fontSize) + (fontSize / 2));
[].concat(testEntry).forEach(emojiUnicode => {
ctx.fillText(emojiUnicode, 0, writeIndex * fontSize + fontSize / 2);
writeIndex += 1;
});
});
......@@ -99,29 +100,25 @@ function generateUnicodeSupportMap(testMap) {
// Read from the canvas
const resultMap = {};
let readIndex = 0;
testMapKeys.forEach((testKey) => {
testMapKeys.forEach(testKey => {
const testEntry = testMap[testKey];
// This needs to be a `reduce` instead of `every` because we need to
// keep the `readIndex` in sync from the writes by running all entries
const isTestSatisfied = [].concat(testEntry).reduce((isSatisfied) => {
const isTestSatisfied = [].concat(testEntry).reduce(isSatisfied => {
// Sample along the vertical-middle for a couple of characters
const imageData = ctx.getImageData(
0,
(readIndex * fontSize) + (fontSize / 2),
2 * fontSize,
1,
).data;
const imageData = ctx.getImageData(0, readIndex * fontSize + fontSize / 2, 2 * fontSize, 1)
.data;
let isValidEmoji = false;
for (let currentPixel = 0; currentPixel < 64; currentPixel += 1) {
const isLookingAtFirstChar = currentPixel < fontSize;
const isLookingAtSecondChar = currentPixel >= (fontSize + (fontSize / 2));
const isLookingAtSecondChar = currentPixel >= fontSize + fontSize / 2;
// Check for the emoji somewhere along the row
if (isLookingAtFirstChar && checkPixelInImageDataArray(currentPixel, imageData)) {
isValidEmoji = true;
// Check to see that nothing is rendered next to the first character
// to ensure that the ZWJ sequence rendered as one piece
// Check to see that nothing is rendered next to the first character
// to ensure that the ZWJ sequence rendered as one piece
} else if (isLookingAtSecondChar && checkPixelInImageDataArray(currentPixel, imageData)) {
isValidEmoji = false;
break;
......@@ -170,7 +167,10 @@ export default function getUnicodeSupportMap() {
if (isLocalStorageAvailable) {
window.localStorage.setItem('gl-emoji-version', GL_EMOJI_VERSION);
window.localStorage.setItem('gl-emoji-user-agent', navigator.userAgent);
window.localStorage.setItem('gl-emoji-unicode-support-map', JSON.stringify(unicodeSupportMap));
window.localStorage.setItem(
'gl-emoji-unicode-support-map',
JSON.stringify(unicodeSupportMap),
);
}
}
......
This source diff could not be displayed because it is too large. You can view the blob instead.
@import "framework/variables";
@import "framework/mixins";
@import 'framework/variables';
@import 'framework/mixins';
@import 'framework/tw_bootstrap_variables';
@import 'framework/tw_bootstrap';
@import "framework/layout";
@import 'framework/layout';
@import "framework/animations";
@import "framework/vue_transitions";
@import "framework/avatar";
@import "framework/asciidoctor";
@import "framework/banner";
@import "framework/blocks";
@import "framework/buttons";
@import "framework/badges";
@import "framework/calendar";
@import "framework/callout";
@import "framework/common";
@import "framework/dropdowns";
@import "framework/files";
@import "framework/filters";
@import "framework/flash";
@import "framework/forms";
@import "framework/gfm";
@import "framework/gitlab_theme";
@import "framework/header";
@import "framework/highlight";
@import "framework/issue_box";
@import "framework/jquery";
@import "framework/lists";
@import "framework/logo";
@import "framework/markdown_area";
@import "framework/media_object";
@import "framework/mobile";
@import "framework/modal";
@import "framework/pagination";
@import "framework/panels";
@import "framework/popup";
@import "framework/secondary_navigation_elements";
@import "framework/selects";
@import "framework/sidebar";
@import "framework/contextual_sidebar";
@import "framework/tables";
@import "framework/notes";
@import "framework/tabs";
@import "framework/timeline";
@import "framework/tooltips";
@import "framework/toggle";
@import "framework/typography";
@import "framework/zen";
@import "framework/blank";
@import "framework/wells";
@import "framework/page_header";
@import "framework/awards";
@import "framework/images";
@import "framework/broadcast_messages";
@import "framework/emojis";
@import "framework/emoji_sprites";
@import "framework/icons";
@import "framework/snippets";
@import "framework/memory_graph";
@import "framework/responsive_tables";
@import "framework/stacked_progress_bar";
@import "framework/ci_variable_list";
@import "framework/feature_highlight";
@import 'framework/animations';
@import 'framework/vue_transitions';
@import 'framework/avatar';
@import 'framework/asciidoctor';
@import 'framework/banner';
@import 'framework/blocks';
@import 'framework/buttons';
@import 'framework/badges';
@import 'framework/calendar';
@import 'framework/callout';
@import 'framework/common';
@import 'framework/dropdowns';
@import 'framework/files';
@import 'framework/filters';
@import 'framework/flash';
@import 'framework/forms';
@import 'framework/gfm';
@import 'framework/gitlab_theme';
@import 'framework/header';
@import 'framework/highlight';
@import 'framework/issue_box';
@import 'framework/jquery';
@import 'framework/lists';
@import 'framework/logo';
@import 'framework/markdown_area';
@import 'framework/media_object';
@import 'framework/mobile';
@import 'framework/modal';
@import 'framework/pagination';
@import 'framework/panels';
@import 'framework/popup';
@import 'framework/secondary_navigation_elements';
@import 'framework/selects';
@import 'framework/sidebar';
@import 'framework/contextual_sidebar';
@import 'framework/tables';
@import 'framework/notes';
@import 'framework/tabs';
@import 'framework/timeline';
@import 'framework/tooltips';
@import 'framework/toggle';
@import 'framework/typography';
@import 'framework/zen';
@import 'framework/blank';
@import 'framework/wells';
@import 'framework/page_header';
@import 'framework/awards';
@import 'framework/images';
@import 'framework/broadcast_messages';
@import 'framework/emojis';
@import 'framework/icons';
@import 'framework/snippets';
@import 'framework/memory_graph';
@import 'framework/responsive_tables';
@import 'framework/stacked_progress_bar';
@import 'framework/ci_variable_list';
@import 'framework/feature_highlight';
This source diff could not be displayed because it is too large. You can view the blob instead.
.project-refs-form,
.project-refs-target-form {
display: inline-block;
}
.fade-enter,
.fade-leave-to {
opacity: 0;
}
.commit-message {
@include str-truncated(250px);
}
.editable-mode {
display: inline-block;
}
.ide-view {
display: flex;
height: calc(100vh - #{$header-height});
margin-top: 40px;
color: $almost-black;
border-top: 1px solid $white-dark;
border-bottom: 1px solid $white-dark;
&.is-collapsed {
.ide-file-list {
max-width: 250px;
}
}
.file-status-icon {
width: 10px;
height: 10px;
}
}
.ide-file-list {
flex: 1;
.file {
cursor: pointer;
&.file-open {
background: $white-normal;
}
.ide-file-name {
flex: 1;
white-space: nowrap;
text-overflow: ellipsis;
svg {
vertical-align: middle;
margin-right: 2px;
}
.loading-container {
margin-right: 4px;
display: inline-block;
}
}
.ide-file-changed-icon {
margin-left: auto;
}
.ide-new-btn {
display: none;
margin-bottom: -4px;
margin-right: -8px;
}
&:hover {
.ide-new-btn {
display: block;
}
}
&.folder {
svg {
fill: $gl-text-color-secondary;
}
}
}
a {
color: $gl-text-color;
}
th {
position: sticky;
top: 0;
}
}
.file-name,
.file-col-commit-message {
display: flex;
overflow: visible;
padding: 6px 12px;
}
.multi-file-loading-container {
margin-top: 10px;
padding: 10px;
.animation-container {
background: $gray-light;
div {
background: $gray-light;
}
}
}
.multi-file-table-col-commit-message {
white-space: nowrap;
width: 50%;
}
.multi-file-edit-pane {
display: flex;
flex-direction: column;
flex: 1;
border-left: 1px solid $white-dark;
overflow: hidden;
}
.multi-file-tabs {
display: flex;
background-color: $white-normal;
box-shadow: inset 0 -1px $white-dark;
> ul {
display: flex;
overflow-x: auto;
}
li {
position: relative;
}
.dropdown {
display: flex;
margin-left: auto;
margin-bottom: 1px;
padding: 0 $grid-size;
border-left: 1px solid $white-dark;
background-color: $white-light;
&.shadow {
box-shadow: 0 0 10px $dropdown-shadow-color;
}
.btn {
margin-top: auto;
margin-bottom: auto;
}
}
}
.multi-file-tab {
@include str-truncated(150px);
padding: ($gl-padding / 2) ($gl-padding + 12) ($gl-padding / 2) $gl-padding;
background-color: $gray-normal;
border-right: 1px solid $white-dark;
border-bottom: 1px solid $white-dark;
cursor: pointer;
svg {
vertical-align: middle;
}
&.active {
background-color: $white-light;
border-bottom-color: $white-light;
}
}
.multi-file-tab-close {
position: absolute;
right: 8px;
top: 50%;
width: 16px;
height: 16px;
padding: 0;
background: none;
border: 0;
border-radius: $border-radius-default;
color: $theme-gray-900;
transform: translateY(-50%);
svg {
position: relative;
top: -1px;
}
&:hover {
background-color: $theme-gray-200;
}
&:focus {
background-color: $blue-500;
color: $white-light;
outline: 0;
svg {
fill: currentColor;
}
}
}
.multi-file-edit-pane-content {
flex: 1;
height: 0;
}
.blob-editor-container {
flex: 1;
height: 0;
display: flex;
flex-direction: column;
justify-content: center;
.vertical-center {
min-height: auto;
}
.monaco-editor .lines-content .cigr {
display: none;
}
.monaco-diff-editor.vs {
.editor.modified {
box-shadow: none;
}
.diagonal-fill {
display: none !important;
}
.diffOverview {
background-color: $white-light;
border-left: 1px solid $white-dark;
cursor: ns-resize;
}
.diffViewport {
display: none;
}
.char-insert {
background-color: $line-added-dark;
}
.char-delete {
background-color: $line-removed-dark;
}
.line-numbers {
color: $black-transparent;
}
.view-overlays {
.line-insert {
background-color: $line-added;
}
.line-delete {
background-color: $line-removed;
}
}
.margin {
background-color: $gray-light;
border-right: 1px solid $white-normal;
.line-insert {
border-right: 1px solid $line-added-dark;
}
.line-delete {
border-right: 1px solid $line-removed-dark;
}
}
.margin-view-overlays .insert-sign,
.margin-view-overlays .delete-sign {
opacity: 0.4;
}
.cursors-layer {
display: none;
}
}
}
.multi-file-editor-holder {
height: 100%;
}
.multi-file-editor-btn-group {
padding: $gl-bar-padding $gl-padding;
border-top: 1px solid $white-dark;
border-bottom: 1px solid $white-dark;
background: $white-light;
}
.ide-status-bar {
padding: $gl-bar-padding $gl-padding;
background: $white-light;
display: flex;
justify-content: space-between;
svg {
vertical-align: middle;
}
}
// Not great, but this is to deal with our current output
.multi-file-preview-holder {
height: 100%;
overflow: scroll;
.file-content.code {
display: flex;
i {
margin-left: -10px;
}
}
.line-numbers {
min-width: 50px;
}
.file-content,
.line-numbers,
.blob-content,
.code {
min-height: 100%;
}
}
.file-content.blob-no-preview {
a {
margin-left: auto;
margin-right: auto;
}
}
.multi-file-commit-panel {
display: flex;
position: relative;
flex-direction: column;
width: 340px;
padding: 0;
background-color: $gray-light;
padding-right: 3px;
.projects-sidebar {
display: flex;
flex-direction: column;
.context-header {
width: auto;
margin-right: 0;
}
}
.multi-file-commit-panel-inner {
display: flex;
flex: 1;
flex-direction: column;
}
.multi-file-commit-panel-inner-scroll {
display: flex;
flex: 1;
flex-direction: column;
overflow: auto;
}
&.is-collapsed {
width: 60px;
.multi-file-commit-list {
padding-top: $gl-padding;
overflow: hidden;
}
.multi-file-context-bar-icon {
align-items: center;
svg {
float: none;
margin: 0;
}
}
}
.branch-container {
border-left: 4px solid $indigo-700;
margin-bottom: $gl-bar-padding;
}
.branch-header {
background: $white-dark;
display: flex;
}
.branch-header-title {
flex: 1;
padding: $grid-size $gl-padding;
color: $indigo-700;
font-weight: $gl-font-weight-bold;
svg {
vertical-align: middle;
}
}
.branch-header-btns {
padding: $gl-vert-padding $gl-padding;
}
.left-collapse-btn {
display: none;
background: $gray-light;
text-align: left;
border-top: 1px solid $white-dark;
svg {
vertical-align: middle;
}
}
}
.multi-file-context-bar-icon {
padding: 10px;
svg {
margin-right: 10px;
float: left;
}
}
.multi-file-commit-panel-section {
display: flex;
flex-direction: column;
flex: 1;
}
.multi-file-commit-empty-state-container {
align-items: center;
justify-content: center;
}
.multi-file-commit-panel-header {
display: flex;
align-items: center;
margin-bottom: 12px;
border-bottom: 1px solid $white-dark;
padding: $gl-btn-padding 0;
&.is-collapsed {
border-bottom: 1px solid $white-dark;
svg {
margin-left: auto;
margin-right: auto;
}
.multi-file-commit-panel-collapse-btn {
margin-right: auto;
margin-left: auto;
border-left: 0;
}
}
}
.multi-file-commit-panel-header-title {
display: flex;
flex: 1;
padding: 0 $gl-btn-padding;
svg {
margin-right: $gl-btn-padding;
}
}
.multi-file-commit-panel-collapse-btn {
border-left: 1px solid $white-dark;
}
.multi-file-commit-list {
flex: 1;
overflow: auto;
padding: $gl-padding 0;
min-height: 60px;
}
.multi-file-commit-list-item {
display: flex;
padding: 0;
align-items: center;
.multi-file-discard-btn {
display: none;
margin-left: auto;
color: $gl-link-color;
padding: 0 2px;
&:focus,
&:hover {
text-decoration: underline;
}
}
&:hover {
background: $white-normal;
.multi-file-discard-btn {
display: block;
}
}
}
.multi-file-addition {
fill: $green-500;
}
.multi-file-modified {
fill: $orange-500;
}
.multi-file-commit-list-collapsed {
display: flex;
flex-direction: column;
> svg {
margin-left: auto;
margin-right: auto;
}
.file-status-icon {
width: 10px;
height: 10px;
margin-left: 3px;
}
}
.multi-file-commit-list-path {
padding: $grid-size / 2;
padding-left: $gl-padding;
background: none;
border: 0;
text-align: left;
width: 100%;
min-width: 0;
svg {
min-width: 16px;
vertical-align: middle;
display: inline-block;
}
&:hover,
&:focus {
outline: 0;
}
}
.multi-file-commit-list-file-path {
@include str-truncated(100%);
&:hover {
text-decoration: underline;
}
&:active {
text-decoration: none;
}
}
.multi-file-commit-form {
padding: $gl-padding;
border-top: 1px solid $white-dark;
.btn {
font-size: $gl-font-size;
}
}
.multi-file-commit-message.form-control {
height: 160px;
resize: none;
}
.dirty-diff {
// !important need to override monaco inline style
width: 4px !important;
left: 0 !important;
&-modified {
background-color: $blue-500;
}
&-added {
background-color: $green-600;
}
&-removed {
height: 0 !important;
width: 0 !important;
bottom: -2px;
border-style: solid;
border-width: 5px;
border-color: transparent transparent transparent $red-500;
&::before {
content: '';
position: absolute;
left: 0;
top: 0;
width: 100px;
height: 1px;
background-color: rgba($red-500, 0.5);
}
}
}
.ide-loading {
display: flex;
height: 100vh;
align-items: center;
justify-content: center;
}
.ide-empty-state {
display: flex;
height: 100vh;
align-items: center;
justify-content: center;
}
.ide-new-btn {
.dropdown-toggle svg {
margin-top: -2px;
margin-bottom: 2px;
}
.dropdown-menu {
left: auto;
right: 0;
label {
font-weight: $gl-font-weight-normal;
padding: 5px 8px;
margin-bottom: 0;
}
}
}
.ide {
overflow: hidden;
&.nav-only {
.flash-container {
margin-top: $header-height;
margin-bottom: 0;
}
.alert-wrapper .flash-container .flash-alert:last-child,
.alert-wrapper .flash-container .flash-notice:last-child {
margin-bottom: 0;
}
.content-wrapper {
margin-top: $header-height;
padding-bottom: 0;
}
&.flash-shown {
.content-wrapper {
margin-top: 0;
}
.ide-view {
height: calc(100vh - #{$header-height + $flash-height});
}
}
.projects-sidebar {
.multi-file-commit-panel-inner-scroll {
flex: 1;
}
}
}
}
.with-performance-bar .ide.nav-only {
.flash-container {
margin-top: #{$header-height + $performance-bar-height};
}
.content-wrapper {
margin-top: #{$header-height + $performance-bar-height};
padding-bottom: 0;
}
.ide-view {
height: calc(100vh - #{$header-height + $performance-bar-height});
}
&.flash-shown {
.content-wrapper {
margin-top: 0;
}
.ide-view {
height: calc(
100vh - #{$header-height + $performance-bar-height + $flash-height}
);
}
}
}
.dragHandle {
position: absolute;
top: 0;
bottom: 0;
width: 3px;
background-color: $white-dark;
&.dragright {
right: 0;
}
&.dragleft {
left: 0;
}
}
.ide-commit-radios {
label {
font-weight: normal;
}
.help-block {
margin-top: 0;
line-height: 0;
}
}
.ide-commit-new-branch {
margin-left: 25px;
}
.ide-external-links {
p {
margin: 0;
}
}
.ide-sidebar-link {
padding: $gl-padding-8 $gl-padding;
background: $indigo-700;
color: $white-light;
text-decoration: none;
display: flex;
align-items: center;
&:focus,
&:hover {
color: $white-light;
text-decoration: underline;
background: $indigo-500;
}
&:active {
background: $indigo-800;
}
}
......@@ -115,6 +115,7 @@ module Gitlab
config.assets.precompile << "test.css"
config.assets.precompile << "snippets.css"
config.assets.precompile << "locale/**/app.js"
config.assets.precompile << "emoji_sprites.css"
# Import gitlab-svgs directly from vendored directory
config.assets.paths << "#{config.root}/node_modules/@gitlab-org/gitlab-svgs/dist"
......
......@@ -19,6 +19,7 @@ module Gitlab
gon.gitlab_logo = ActionController::Base.helpers.asset_path('gitlab_logo.png')
gon.sprite_icons = IconsHelper.sprite_icon_path
gon.sprite_file_icons = IconsHelper.sprite_file_icons_path
gon.emoji_sprites_css_path = ActionController::Base.helpers.stylesheet_path('emoji_sprites')
gon.test_env = Rails.env.test?
gon.suggested_label_colors = LabelsHelper.suggested_colors
......
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