diff options
Diffstat (limited to 'app/assets')
164 files changed, 9318 insertions, 4878 deletions
diff --git a/app/assets/javascripts/behaviors/gl_emoji.js b/app/assets/javascripts/behaviors/gl_emoji.js index 7e98e04303a..56293d5f96f 100644 --- a/app/assets/javascripts/behaviors/gl_emoji.js +++ b/app/assets/javascripts/behaviors/gl_emoji.js @@ -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); diff --git a/app/assets/javascripts/clusters/clusters_index.js b/app/assets/javascripts/clusters/clusters_index.js index 2e3ad244375..1e5c733d151 100644 --- a/app/assets/javascripts/clusters/clusters_index.js +++ b/app/assets/javascripts/clusters/clusters_index.js @@ -1,20 +1,24 @@ -import Flash from '../flash'; -import { s__ } from '../locale'; -import setupToggleButtons from '../toggle_buttons'; +import createFlash from '~/flash'; +import { __ } from '~/locale'; +import setupToggleButtons from '~/toggle_buttons'; +import gcpSignupOffer from '~/clusters/components/gcp_signup_offer'; + import ClustersService from './services/clusters_service'; export default () => { const clusterList = document.querySelector('.js-clusters-list'); + + gcpSignupOffer(); + // The empty state won't have a clusterList if (clusterList) { - setupToggleButtons( - document.querySelector('.js-clusters-list'), - (value, toggle) => - ClustersService.updateCluster(toggle.dataset.endpoint, { cluster: { enabled: value } }) - .catch((err) => { - Flash(s__('ClusterIntegration|Something went wrong on our end.')); - throw err; - }), + setupToggleButtons(document.querySelector('.js-clusters-list'), (value, toggle) => + ClustersService.updateCluster(toggle.dataset.endpoint, { cluster: { enabled: value } }).catch( + err => { + createFlash(__('Something went wrong on our end.')); + throw err; + }, + ), ); } }; diff --git a/app/assets/javascripts/clusters/components/gcp_signup_offer.js b/app/assets/javascripts/clusters/components/gcp_signup_offer.js new file mode 100644 index 00000000000..8bc20a1c09f --- /dev/null +++ b/app/assets/javascripts/clusters/components/gcp_signup_offer.js @@ -0,0 +1,27 @@ +import $ from 'jquery'; +import axios from '~/lib/utils/axios_utils'; +import { __ } from '~/locale'; +import Flash from '~/flash'; + +export default function gcpSignupOffer() { + const alertEl = document.querySelector('.gcp-signup-offer'); + if (!alertEl) { + return; + } + + const closeButtonEl = alertEl.getElementsByClassName('close')[0]; + const { dismissEndpoint, featureId } = closeButtonEl.dataset; + + closeButtonEl.addEventListener('click', () => { + axios + .post(dismissEndpoint, { + feature_name: featureId, + }) + .then(() => { + $(alertEl).alert('close'); + }) + .catch(() => { + Flash(__('An error occurred while dismissing the alert. Refresh the page and try again.')); + }); + }); +} diff --git a/app/assets/javascripts/compare.js b/app/assets/javascripts/compare.js deleted file mode 100644 index 303a5bf4a53..00000000000 --- a/app/assets/javascripts/compare.js +++ /dev/null @@ -1,86 +0,0 @@ -/* eslint-disable func-names, space-before-function-paren, wrap-iife, quotes, no-var, object-shorthand, consistent-return, no-unused-vars, comma-dangle, vars-on-top, prefer-template, max-len */ - -import $ from 'jquery'; -import { localTimeAgo } from './lib/utils/datetime_utility'; -import axios from './lib/utils/axios_utils'; - -export default class Compare { - constructor(opts) { - this.opts = opts; - this.source_loading = $(".js-source-loading"); - this.target_loading = $(".js-target-loading"); - $('.js-compare-dropdown').each((function(_this) { - return function(i, dropdown) { - var $dropdown; - $dropdown = $(dropdown); - return $dropdown.glDropdown({ - selectable: true, - fieldName: $dropdown.data('fieldName'), - filterable: true, - id: function(obj, $el) { - return $el.data('id'); - }, - toggleLabel: function(obj, $el) { - return $el.text().trim(); - }, - clicked: function(e, el) { - if ($dropdown.is('.js-target-branch')) { - return _this.getTargetHtml(); - } else if ($dropdown.is('.js-source-branch')) { - return _this.getSourceHtml(); - } else if ($dropdown.is('.js-target-project')) { - return _this.getTargetProject(); - } - } - }); - }; - })(this)); - this.initialState(); - } - - initialState() { - this.getSourceHtml(); - this.getTargetHtml(); - } - - getTargetProject() { - $('.mr_target_commit').empty(); - - return axios.get(this.opts.targetProjectUrl, { - params: { - target_project_id: $("input[name='merge_request[target_project_id]']").val(), - }, - }).then(({ data }) => { - $('.js-target-branch-dropdown .dropdown-content').html(data); - }); - } - - getSourceHtml() { - return this.constructor.sendAjax(this.opts.sourceBranchUrl, this.source_loading, '.mr_source_commit', { - ref: $("input[name='merge_request[source_branch]']").val() - }); - } - - getTargetHtml() { - return this.constructor.sendAjax(this.opts.targetBranchUrl, this.target_loading, '.mr_target_commit', { - target_project_id: $("input[name='merge_request[target_project_id]']").val(), - ref: $("input[name='merge_request[target_branch]']").val() - }); - } - - static sendAjax(url, loading, target, params) { - const $target = $(target); - - loading.show(); - $target.empty(); - - return axios.get(url, { - params, - }).then(({ data }) => { - loading.hide(); - $target.html(data); - const className = '.' + $target[0].className.replace(' ', '.'); - localTimeAgo($('.js-timeago', className)); - }); - } -} diff --git a/app/assets/javascripts/compare_autocomplete.js b/app/assets/javascripts/compare_autocomplete.js index 260c91cac24..9c88466e576 100644 --- a/app/assets/javascripts/compare_autocomplete.js +++ b/app/assets/javascripts/compare_autocomplete.js @@ -4,8 +4,9 @@ import $ from 'jquery'; import { __ } from './locale'; import axios from './lib/utils/axios_utils'; import flash from './flash'; +import { capitalizeFirstCharacter } from './lib/utils/text_utility'; -export default function initCompareAutocomplete() { +export default function initCompareAutocomplete(limitTo = null, clickHandler = () => {}) { $('.js-compare-dropdown').each(function() { var $dropdown, selected; $dropdown = $(this); @@ -15,14 +16,27 @@ export default function initCompareAutocomplete() { const $filterInput = $('input[type="search"]', $dropdownContainer); $dropdown.glDropdown({ data: function(term, callback) { - axios.get($dropdown.data('refsUrl'), { - params: { - ref: $dropdown.data('ref'), - search: term, - }, - }).then(({ data }) => { - callback(data); - }).catch(() => flash(__('Error fetching refs'))); + const params = { + ref: $dropdown.data('ref'), + search: term, + }; + + if (limitTo) { + params.find = limitTo; + } + + axios + .get($dropdown.data('refsUrl'), { + params, + }) + .then(({ data }) => { + if (limitTo) { + callback(data[capitalizeFirstCharacter(limitTo)] || []); + } else { + callback(data); + } + }) + .catch(() => flash(__('Error fetching refs'))); }, selectable: true, filterable: true, @@ -32,9 +46,15 @@ export default function initCompareAutocomplete() { renderRow: function(ref) { var link; if (ref.header != null) { - return $('<li />').addClass('dropdown-header').text(ref.header); + return $('<li />') + .addClass('dropdown-header') + .text(ref.header); } else { - link = $('<a />').attr('href', '#').addClass(ref === selected ? 'is-active' : '').text(ref).attr('data-ref', escape(ref)); + link = $('<a />') + .attr('href', '#') + .addClass(ref === selected ? 'is-active' : '') + .text(ref) + .attr('data-ref', escape(ref)); return $('<li />').append(link); } }, @@ -43,9 +63,10 @@ export default function initCompareAutocomplete() { }, toggleLabel: function(obj, $el) { return $el.text().trim(); - } + }, + clicked: () => clickHandler($dropdown), }); - $filterInput.on('keyup', (e) => { + $filterInput.on('keyup', e => { const keyCode = e.keyCode || e.which; if (keyCode !== 13) return; const text = $filterInput.val(); @@ -54,7 +75,7 @@ export default function initCompareAutocomplete() { $dropdownContainer.removeClass('open'); }); - $dropdownContainer.on('click', '.dropdown-content a', (e) => { + $dropdownContainer.on('click', '.dropdown-content a', e => { $dropdown.prop('title', e.target.text.replace(/_+?/g, '-')); if ($dropdown.hasClass('has-tooltip')) { $dropdown.tooltip('fixTitle'); diff --git a/app/assets/javascripts/deploy_keys/components/action_btn.vue b/app/assets/javascripts/deploy_keys/components/action_btn.vue index b839b9f286f..67dda0e29cb 100644 --- a/app/assets/javascripts/deploy_keys/components/action_btn.vue +++ b/app/assets/javascripts/deploy_keys/components/action_btn.vue @@ -1,55 +1,50 @@ <script> - import eventHub from '../eventhub'; - import loadingIcon from '../../vue_shared/components/loading_icon.vue'; +import loadingIcon from '~/vue_shared/components/loading_icon.vue'; +import eventHub from '../eventhub'; - export default { - components: { - loadingIcon, +export default { + components: { + loadingIcon, + }, + props: { + deployKey: { + type: Object, + required: true, }, - props: { - deployKey: { - type: Object, - required: true, - }, - type: { - type: String, - required: true, - }, - btnCssClass: { - type: String, - required: false, - default: 'btn-default', - }, + type: { + type: String, + required: true, }, - data() { - return { - isLoading: false, - }; + btnCssClass: { + type: String, + required: false, + default: 'btn-default', }, - computed: { - text() { - return `${this.type.charAt(0).toUpperCase()}${this.type.slice(1)}`; - }, - }, - methods: { - doAction() { - this.isLoading = true; + }, + data() { + return { + isLoading: false, + }; + }, + methods: { + doAction() { + this.isLoading = true; - eventHub.$emit(`${this.type}.key`, this.deployKey, () => { - this.isLoading = false; - }); - }, + eventHub.$emit(`${this.type}.key`, this.deployKey, () => { + this.isLoading = false; + }); }, - }; + }, +}; </script> <template> <button - class="btn btn-sm prepend-left-10" + class="btn" :class="[{ disabled: isLoading }, btnCssClass]" :disabled="isLoading" @click="doAction"> - {{ text }} + <slot></slot> <loading-icon v-if="isLoading" :inline="true" diff --git a/app/assets/javascripts/deploy_keys/components/app.vue b/app/assets/javascripts/deploy_keys/components/app.vue index 5a782237b7d..c41fe55db63 100644 --- a/app/assets/javascripts/deploy_keys/components/app.vue +++ b/app/assets/javascripts/deploy_keys/components/app.vue @@ -1,80 +1,115 @@ <script> - import Flash from '../../flash'; - import eventHub from '../eventhub'; - import DeployKeysService from '../service'; - import DeployKeysStore from '../store'; - import keysPanel from './keys_panel.vue'; - import loadingIcon from '../../vue_shared/components/loading_icon.vue'; +import { s__ } from '~/locale'; +import Flash from '~/flash'; +import LoadingIcon from '~/vue_shared/components/loading_icon.vue'; +import NavigationTabs from '~/vue_shared/components/navigation_tabs.vue'; +import eventHub from '../eventhub'; +import DeployKeysService from '../service'; +import DeployKeysStore from '../store'; +import KeysPanel from './keys_panel.vue'; - export default { - components: { - keysPanel, - loadingIcon, +export default { + components: { + KeysPanel, + LoadingIcon, + NavigationTabs, + }, + props: { + endpoint: { + type: String, + required: true, }, - props: { - endpoint: { - type: String, - required: true, - }, + projectId: { + type: String, + required: true, }, - data() { - return { - isLoading: false, - store: new DeployKeysStore(), - }; + }, + data() { + return { + currentTab: 'enabled_keys', + isLoading: false, + store: new DeployKeysStore(), + }; + }, + scopes: { + enabled_keys: s__('DeployKeys|Enabled deploy keys'), + available_project_keys: s__('DeployKeys|Privately accessible deploy keys'), + public_keys: s__('DeployKeys|Publicly accessible deploy keys'), + }, + computed: { + tabs() { + return Object.keys(this.$options.scopes).map(scope => { + const count = Array.isArray(this.keys[scope]) ? this.keys[scope].length : null; + + return { + name: this.$options.scopes[scope], + scope, + isActive: scope === this.currentTab, + count, + }; + }); + }, + hasKeys() { + return Object.keys(this.keys).length; }, - computed: { - hasKeys() { - return Object.keys(this.keys).length; - }, - keys() { - return this.store.keys; - }, + keys() { + return this.store.keys; }, - created() { - this.service = new DeployKeysService(this.endpoint); + }, + created() { + this.service = new DeployKeysService(this.endpoint); - eventHub.$on('enable.key', this.enableKey); - eventHub.$on('remove.key', this.disableKey); - eventHub.$on('disable.key', this.disableKey); + eventHub.$on('enable.key', this.enableKey); + eventHub.$on('remove.key', this.disableKey); + eventHub.$on('disable.key', this.disableKey); + }, + mounted() { + this.fetchKeys(); + }, + beforeDestroy() { + eventHub.$off('enable.key', this.enableKey); + eventHub.$off('remove.key', this.disableKey); + eventHub.$off('disable.key', this.disableKey); + }, + methods: { + onChangeTab(tab) { + this.currentTab = tab; }, - mounted() { - this.fetchKeys(); + fetchKeys() { + this.isLoading = true; + + return this.service + .getKeys() + .then(data => { + this.isLoading = false; + this.store.keys = data; + }) + .catch(() => { + this.isLoading = false; + this.store.keys = {}; + return new Flash(s__('DeployKeys|Error getting deploy keys')); + }); }, - beforeDestroy() { - eventHub.$off('enable.key', this.enableKey); - eventHub.$off('remove.key', this.disableKey); - eventHub.$off('disable.key', this.disableKey); + enableKey(deployKey) { + this.service + .enableKey(deployKey.id) + .then(this.fetchKeys) + .catch(() => new Flash(s__('DeployKeys|Error enabling deploy key'))); }, - methods: { - fetchKeys() { - this.isLoading = true; - - this.service.getKeys() - .then((data) => { - this.isLoading = false; - this.store.keys = data; - }) - .catch(() => new Flash('Error getting deploy keys')); - }, - enableKey(deployKey) { - this.service.enableKey(deployKey.id) - .then(() => this.fetchKeys()) - .catch(() => new Flash('Error enabling deploy key')); - }, - disableKey(deployKey, callback) { - // eslint-disable-next-line no-alert - if (confirm('You are going to remove this deploy key. Are you sure?')) { - this.service.disableKey(deployKey.id) - .then(() => this.fetchKeys()) - .then(callback) - .catch(() => new Flash('Error removing deploy key')); - } else { - callback(); - } - }, + disableKey(deployKey, callback) { + // eslint-disable-next-line no-alert + if (confirm(s__('DeployKeys|You are going to remove this deploy key. Are you sure?'))) { + this.service + .disableKey(deployKey.id) + .then(this.fetchKeys) + .then(callback) + .catch(() => new Flash(s__('DeployKeys|Error removing deploy key'))); + } else { + callback(); + } }, - }; + }, +}; </script> <template> @@ -82,29 +117,38 @@ <loading-icon v-if="isLoading && !hasKeys" size="2" - label="Loading deploy keys" + :label="s__('DeployKeys|Loading deploy keys')" /> - <div v-else-if="hasKeys"> + <template v-else-if="hasKeys"> + <div class="top-area scrolling-tabs-container inner-page-scroll-tabs"> + <div class="fade-left"> + <i + class="fa fa-angle-left" + aria-hidden="true" + > + </i> + </div> + <div class="fade-right"> + <i + class="fa fa-angle-right" + aria-hidden="true" + > + </i> + </div> + + <navigation-tabs + :tabs="tabs" + @onChangeTab="onChangeTab" + scope="deployKeys" + /> + </div> <keys-panel - title="Enabled deploy keys for this project" class="qa-project-deploy-keys" - :keys="keys.enabled_keys" - :store="store" - :endpoint="endpoint" - /> - <keys-panel - title="Deploy keys from projects you have access to" - :keys="keys.available_project_keys" - :store="store" - :endpoint="endpoint" - /> - <keys-panel - v-if="keys.public_keys.length" - title="Public deploy keys available to any project" - :keys="keys.public_keys" + :project-id="projectId" + :keys="keys[currentTab]" :store="store" :endpoint="endpoint" /> - </div> + </template> </div> </template> diff --git a/app/assets/javascripts/deploy_keys/components/key.vue b/app/assets/javascripts/deploy_keys/components/key.vue index c6091efd62f..6c2af7fa768 100644 --- a/app/assets/javascripts/deploy_keys/components/key.vue +++ b/app/assets/javascripts/deploy_keys/components/key.vue @@ -1,111 +1,235 @@ <script> - import actionBtn from './action_btn.vue'; - import { getTimeago } from '../../lib/utils/datetime_utility'; - import tooltip from '../../vue_shared/directives/tooltip'; +import _ from 'underscore'; +import { s__, sprintf } from '~/locale'; +import icon from '~/vue_shared/components/icon.vue'; +import tooltip from '~/vue_shared/directives/tooltip'; +import timeagoMixin from '~/vue_shared/mixins/timeago'; - export default { - components: { - actionBtn, - }, - directives: { - tooltip, - }, - props: { - deployKey: { - type: Object, - required: true, - }, - store: { - type: Object, - required: true, - }, - endpoint: { - type: String, - required: true, - }, - }, - computed: { - timeagoDate() { - return getTimeago().format(this.deployKey.created_at); - }, - editDeployKeyPath() { - return `${this.endpoint}/${this.deployKey.id}/edit`; - }, - }, - methods: { - isEnabled(id) { - return this.store.findEnabledKey(id) !== undefined; - }, - tooltipTitle(project) { - return project.can_push ? 'Write access allowed' : 'Read access only'; - }, - }, - }; +import actionBtn from './action_btn.vue'; + +export default { + components: { + actionBtn, + icon, + }, + directives: { + tooltip, + }, + mixins: [timeagoMixin], + props: { + deployKey: { + type: Object, + required: true, + }, + store: { + type: Object, + required: true, + }, + endpoint: { + type: String, + required: true, + }, + projectId: { + type: String, + required: false, + default: null, + }, + }, + data() { + return { + projectsExpanded: false, + }; + }, + computed: { + editDeployKeyPath() { + return `${this.endpoint}/${this.deployKey.id}/edit`; + }, + projects() { + const projects = [...this.deployKey.deploy_keys_projects]; + + if (this.projectId !== null) { + const indexOfCurrentProject = _.findIndex( + projects, + project => + project && + project.project && + project.project.id && + project.project.id.toString() === this.projectId, + ); + + if (indexOfCurrentProject > -1) { + const currentProject = projects.splice(indexOfCurrentProject, 1); + currentProject[0].project.full_name = s__('DeployKeys|Current project'); + return currentProject.concat(projects); + } + } + return projects; + }, + firstProject() { + return _.head(this.projects); + }, + restProjects() { + return _.tail(this.projects); + }, + restProjectsTooltip() { + return sprintf(s__('DeployKeys|Expand %{count} other projects'), { + count: this.restProjects.length, + }); + }, + restProjectsLabel() { + return sprintf(s__('DeployKeys|+%{count} others'), { count: this.restProjects.length }); + }, + isEnabled() { + return this.store.isEnabled(this.deployKey.id); + }, + isRemovable() { + return ( + this.store.isEnabled(this.deployKey.id) && + this.deployKey.destroyed_when_orphaned && + this.deployKey.almost_orphaned + ); + }, + isExpandable() { + return !this.projectsExpanded && this.restProjects.length > 1; + }, + isExpanded() { + return this.projectsExpanded || this.restProjects.length === 1; + }, + }, + methods: { + projectTooltipTitle(project) { + return project.can_push + ? s__('DeployKeys|Write access allowed') + : s__('DeployKeys|Read access only'); + }, + toggleExpanded() { + this.projectsExpanded = !this.projectsExpanded; + }, + }, +}; </script> <template> - <div> - <div class="pull-left append-right-10 hidden-xs"> - <i - aria-hidden="true" - class="fa fa-key key-icon" - > - </i> + <div class="gl-responsive-table-row deploy-key"> + <div class="table-section section-40"> + <div + role="rowheader" + class="table-mobile-header"> + {{ s__('DeployKeys|Deploy key') }} + </div> + <div class="table-mobile-content"> + <strong class="title qa-key-title"> + {{ deployKey.title }} + </strong> + <div class="fingerprint qa-key-fingerprint"> + {{ deployKey.fingerprint }} + </div> + </div> </div> - <div class="deploy-key-content key-list-item-info"> - <strong class="title qa-key-title"> - {{ deployKey.title }} - </strong> - <div class="description qa-key-fingerprint"> - {{ deployKey.fingerprint }} + <div class="table-section section-30 section-wrap"> + <div + role="rowheader" + class="table-mobile-header"> + {{ s__('DeployKeys|Project usage') }} + </div> + <div class="table-mobile-content deploy-project-list"> + <template v-if="projects.length > 0"> + <a + class="label deploy-project-label" + :title="projectTooltipTitle(firstProject)" + v-tooltip + > + <span> + {{ firstProject.project.full_name }} + </span> + <icon :name="firstProject.can_push ? 'lock-open' : 'lock'"/> + </a> + <a + v-if="isExpandable" + class="label deploy-project-label" + @click="toggleExpanded" + :title="restProjectsTooltip" + v-tooltip + > + <span>{{ restProjectsLabel }}</span> + </a> + <a + v-else-if="isExpanded" + v-for="deployKeysProject in restProjects" + :key="deployKeysProject.project.full_path" + class="label deploy-project-label" + :href="deployKeysProject.project.full_path" + :title="projectTooltipTitle(deployKeysProject)" + v-tooltip + > + <span> + {{ deployKeysProject.project.full_name }} + </span> + <icon :name="deployKeysProject.can_push ? 'lock-open' : 'lock'"/> + </a> + </template> + <span + v-else + class="text-secondary">{{ __('None') }}</span> </div> </div> - <div class="deploy-key-content prepend-left-default deploy-key-projects"> - <a - v-for="(deployKeysProject, i) in deployKey.deploy_keys_projects" - :key="i" - class="label deploy-project-label" - :href="deployKeysProject.project.full_path" - :title="tooltipTitle(deployKeysProject)" - v-tooltip - > - {{ deployKeysProject.project.full_name }} - <i - v-if="!deployKeysProject.can_push" - aria-hidden="true" - class="fa fa-lock" - > - </i> - </a> + <div class="table-section section-15 text-right"> + <div + role="rowheader" + class="table-mobile-header"> + {{ __('Created') }} + </div> + <div class="table-mobile-content text-secondary key-created-at"> + <span + :title="tooltipTitle(deployKey.created_at)" + v-tooltip> + <icon name="calendar"/> + <span>{{ timeFormated(deployKey.created_at) }}</span> + </span> + </div> </div> - <div class="deploy-key-content"> - <span class="key-created-at"> - created {{ timeagoDate }} - </span> - <a - v-if="deployKey.can_edit" - class="btn btn-sm" - :href="editDeployKeyPath" - > - Edit - </a> - <action-btn - v-if="!isEnabled(deployKey.id)" - :deploy-key="deployKey" - type="enable" - /> - <action-btn - v-else-if="deployKey.destroyed_when_orphaned && deployKey.almost_orphaned" - :deploy-key="deployKey" - btn-css-class="btn-warning" - type="remove" - /> - <action-btn - v-else - :deploy-key="deployKey" - btn-css-class="btn-warning" - type="disable" - /> + <div class="table-section section-15 table-button-footer deploy-key-actions"> + <div class="btn-group table-action-buttons"> + <action-btn + v-if="!isEnabled" + :deploy-key="deployKey" + type="enable" + > + {{ __('Enable') }} + </action-btn> + <a + v-if="deployKey.can_edit" + class="btn btn-default text-secondary" + :href="editDeployKeyPath" + :title="__('Edit')" + data-container="body" + v-tooltip + > + <icon name="pencil"/> + </a> + <action-btn + v-if="isRemovable" + :deploy-key="deployKey" + btn-css-class="btn-danger" + type="remove" + :title="__('Remove')" + data-container="body" + v-tooltip + > + <icon name="remove"/> + </action-btn> + <action-btn + v-else-if="isEnabled" + :deploy-key="deployKey" + btn-css-class="btn-warning" + type="disable" + :title="__('Disable')" + data-container="body" + v-tooltip + > + <icon name="cancel"/> + </action-btn> + </div> </div> </div> </template> diff --git a/app/assets/javascripts/deploy_keys/components/keys_panel.vue b/app/assets/javascripts/deploy_keys/components/keys_panel.vue index 822b0323156..3b146c7389a 100644 --- a/app/assets/javascripts/deploy_keys/components/keys_panel.vue +++ b/app/assets/javascripts/deploy_keys/components/keys_panel.vue @@ -1,62 +1,68 @@ <script> - import key from './key.vue'; +import deployKey from './key.vue'; - export default { - components: { - key, +export default { + components: { + deployKey, + }, + props: { + keys: { + type: Array, + required: true, }, - props: { - title: { - type: String, - required: true, - }, - keys: { - type: Array, - required: true, - }, - showHelpBox: { - type: Boolean, - required: false, - default: true, - }, - store: { - type: Object, - required: true, - }, - endpoint: { - type: String, - required: true, - }, + store: { + type: Object, + required: true, }, - }; + endpoint: { + type: String, + required: true, + }, + projectId: { + type: String, + required: false, + default: null, + }, + }, +}; </script> <template> - <div class="deploy-keys-panel"> - <h5> - {{ title }} - ({{ keys.length }}) - </h5> - <ul - class="well-list" - v-if="keys.length" - > - <li + <div class="deploy-keys-panel table-holder"> + <template v-if="keys.length > 0"> + <div + role="row" + class="gl-responsive-table-row table-row-header"> + <div + role="rowheader" + class="table-section section-40"> + {{ s__('DeployKeys|Deploy key') }} + </div> + <div + role="rowheader" + class="table-section section-30"> + {{ s__('DeployKeys|Project usage') }} + </div> + <div + role="rowheader" + class="table-section section-15 text-right"> + {{ __('Created') }} + </div> + </div> + <deploy-key v-for="deployKey in keys" :key="deployKey.id" - > - <key - :deploy-key="deployKey" - :store="store" - :endpoint="endpoint" - /> - </li> - </ul> + :deploy-key="deployKey" + :store="store" + :endpoint="endpoint" + :project-id="projectId" + /> + </template> <div class="settings-message text-center" - v-else-if="showHelpBox" + v-else > - No deploy keys found. Create one with the form above. + {{ s__('DeployKeys|No deploy keys found. Create one with the form above.') }} </div> </div> </template> diff --git a/app/assets/javascripts/deploy_keys/index.js b/app/assets/javascripts/deploy_keys/index.js index b727261648c..6e439be42ae 100644 --- a/app/assets/javascripts/deploy_keys/index.js +++ b/app/assets/javascripts/deploy_keys/index.js @@ -1,21 +1,24 @@ import Vue from 'vue'; import deployKeysApp from './components/app.vue'; -export default () => new Vue({ - el: document.getElementById('js-deploy-keys'), - components: { - deployKeysApp, - }, - data() { - return { - endpoint: this.$options.el.dataset.endpoint, - }; - }, - render(createElement) { - return createElement('deploy-keys-app', { - props: { - endpoint: this.endpoint, - }, - }); - }, -}); +export default () => + new Vue({ + el: document.getElementById('js-deploy-keys'), + components: { + deployKeysApp, + }, + data() { + return { + endpoint: this.$options.el.dataset.endpoint, + projectId: this.$options.el.dataset.projectId, + }; + }, + render(createElement) { + return createElement('deploy-keys-app', { + props: { + endpoint: this.endpoint, + projectId: this.projectId, + }, + }); + }, + }); diff --git a/app/assets/javascripts/deploy_keys/service/index.js b/app/assets/javascripts/deploy_keys/service/index.js index fe6dbaa9498..194e95e4fca 100644 --- a/app/assets/javascripts/deploy_keys/service/index.js +++ b/app/assets/javascripts/deploy_keys/service/index.js @@ -7,21 +7,24 @@ export default class DeployKeysService { constructor(endpoint) { this.endpoint = endpoint; - this.resource = Vue.resource(`${this.endpoint}{/id}`, {}, { - enable: { - method: 'PUT', - url: `${this.endpoint}{/id}/enable`, + this.resource = Vue.resource( + `${this.endpoint}{/id}`, + {}, + { + enable: { + method: 'PUT', + url: `${this.endpoint}{/id}/enable`, + }, + disable: { + method: 'PUT', + url: `${this.endpoint}{/id}/disable`, + }, }, - disable: { - method: 'PUT', - url: `${this.endpoint}{/id}/disable`, - }, - }); + ); } getKeys() { - return this.resource.get() - .then(response => response.json()); + return this.resource.get().then(response => response.json()); } enableKey(id) { diff --git a/app/assets/javascripts/deploy_keys/store/index.js b/app/assets/javascripts/deploy_keys/store/index.js index 6210361af26..a350bc99a70 100644 --- a/app/assets/javascripts/deploy_keys/store/index.js +++ b/app/assets/javascripts/deploy_keys/store/index.js @@ -3,7 +3,7 @@ export default class DeployKeysStore { this.keys = {}; } - findEnabledKey(id) { - return this.keys.enabled_keys.find(key => key.id === id); + isEnabled(id) { + return this.keys.enabled_keys.some(key => key.id === id); } } diff --git a/app/assets/javascripts/emoji/index.js b/app/assets/javascripts/emoji/index.js index dc7672560ea..cd8dff40b88 100644 --- a/app/assets/javascripts/emoji/index.js +++ b/app/assets/javascripts/emoji/index.js @@ -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); diff --git a/app/assets/javascripts/emoji/support/unicode_support_map.js b/app/assets/javascripts/emoji/support/unicode_support_map.js index c18d07dad43..8c1861c56db 100644 --- a/app/assets/javascripts/emoji/support/unicode_support_map.js +++ b/app/assets/javascripts/emoji/support/unicode_support_map.js @@ -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), + ); } } diff --git a/app/assets/javascripts/environments/components/container.vue b/app/assets/javascripts/environments/components/container.vue index dbee81fa320..6bd7c6b49cb 100644 --- a/app/assets/javascripts/environments/components/container.vue +++ b/app/assets/javascripts/environments/components/container.vue @@ -43,6 +43,7 @@ <div class="environments-container"> <loading-icon + class="prepend-top-default" label="Loading environments" v-if="isLoading" size="3" diff --git a/app/assets/javascripts/environments/components/environment_actions.vue b/app/assets/javascripts/environments/components/environment_actions.vue index 16bd2f5feb3..ab9e22037d0 100644 --- a/app/assets/javascripts/environments/components/environment_actions.vue +++ b/app/assets/javascripts/environments/components/environment_actions.vue @@ -1,5 +1,5 @@ <script> - import playIconSvg from 'icons/_icon_play.svg'; + import Icon from '~/vue_shared/components/icon.vue'; import eventHub from '../event_hub'; import loadingIcon from '../../vue_shared/components/loading_icon.vue'; import tooltip from '../../vue_shared/directives/tooltip'; @@ -8,9 +8,9 @@ directives: { tooltip, }, - components: { loadingIcon, + Icon, }, props: { actions: { @@ -19,20 +19,16 @@ default: () => [], }, }, - data() { return { - playIconSvg, isLoading: false, }; }, - computed: { title() { return 'Deploy to...'; }, }, - methods: { onClickAction(endpoint) { this.isLoading = true; @@ -65,7 +61,10 @@ :disabled="isLoading" > <span> - <span v-html="playIconSvg"></span> + <icon + name="play" + :size="12" + /> <i class="fa fa-caret-down" aria-hidden="true" @@ -86,7 +85,10 @@ :class="{ disabled: isActionDisabled(action) }" :disabled="isActionDisabled(action)" > - <span v-html="playIconSvg"></span> + <icon + name="play" + :size="12" + /> <span> {{ action.name }} </span> diff --git a/app/assets/javascripts/environments/components/environment_external_url.vue b/app/assets/javascripts/environments/components/environment_external_url.vue index c9a68cface6..ea6f1168c68 100644 --- a/app/assets/javascripts/environments/components/environment_external_url.vue +++ b/app/assets/javascripts/environments/components/environment_external_url.vue @@ -1,4 +1,5 @@ <script> + import Icon from '~/vue_shared/components/icon.vue'; import tooltip from '../../vue_shared/directives/tooltip'; import { s__ } from '../../locale'; @@ -6,6 +7,9 @@ * Renders the external url link in environments table. */ export default { + components: { + Icon, + }, directives: { tooltip, }, @@ -15,7 +19,6 @@ required: true, }, }, - computed: { title() { return s__('Environments|Open'); @@ -34,10 +37,9 @@ :aria-label="title" :href="externalUrl" > - <i - class="fa fa-external-link" - aria-hidden="true" - > - </i> + <icon + name="external-link" + :size="12" + /> </a> </template> diff --git a/app/assets/javascripts/environments/components/environment_monitoring.vue b/app/assets/javascripts/environments/components/environment_monitoring.vue index 081537cf218..deada134b27 100644 --- a/app/assets/javascripts/environments/components/environment_monitoring.vue +++ b/app/assets/javascripts/environments/components/environment_monitoring.vue @@ -2,20 +2,22 @@ /** * Renders the Monitoring (Metrics) link in environments table. */ + import Icon from '~/vue_shared/components/icon.vue'; import tooltip from '../../vue_shared/directives/tooltip'; export default { + components: { + Icon, + }, directives: { tooltip, }, - props: { monitoringUrl: { type: String, required: true, }, }, - computed: { title() { return 'Monitoring'; @@ -33,10 +35,9 @@ :title="title" :aria-label="title" > - <i - class="fa fa-area-chart" - aria-hidden="true" - > - </i> + <icon + name="chart" + :size="12" + /> </a> </template> diff --git a/app/assets/javascripts/environments/components/environment_rollback.vue b/app/assets/javascripts/environments/components/environment_rollback.vue index 605a88e997e..c822fb1574c 100644 --- a/app/assets/javascripts/environments/components/environment_rollback.vue +++ b/app/assets/javascripts/environments/components/environment_rollback.vue @@ -12,7 +12,6 @@ components: { loadingIcon, }, - props: { retryUrl: { type: String, @@ -24,13 +23,11 @@ default: true, }, }, - data() { return { isLoading: false, }; }, - methods: { onClick() { this.isLoading = true; diff --git a/app/assets/javascripts/environments/components/environment_terminal_button.vue b/app/assets/javascripts/environments/components/environment_terminal_button.vue index 407d5333c0e..e8469d088ef 100644 --- a/app/assets/javascripts/environments/components/environment_terminal_button.vue +++ b/app/assets/javascripts/environments/components/environment_terminal_button.vue @@ -3,14 +3,16 @@ * Renders a terminal button to open a web terminal. * Used in environments table. */ - import terminalIconSvg from 'icons/_icon_terminal.svg'; + import Icon from '~/vue_shared/components/icon.vue'; import tooltip from '../../vue_shared/directives/tooltip'; export default { + components: { + Icon, + }, directives: { tooltip, }, - props: { terminalPath: { type: String, @@ -18,13 +20,6 @@ default: '', }, }, - - data() { - return { - terminalIconSvg, - }; - }, - computed: { title() { return 'Terminal'; @@ -40,7 +35,10 @@ :title="title" :aria-label="title" :href="terminalPath" - v-html="terminalIconSvg" > + <icon + name="terminal" + :size="12" + /> </a> </template> diff --git a/app/assets/javascripts/gfm_auto_complete.js b/app/assets/javascripts/gfm_auto_complete.js index 7e9770a9ea2..9de57db48fd 100644 --- a/app/assets/javascripts/gfm_auto_complete.js +++ b/app/assets/javascripts/gfm_auto_complete.js @@ -408,7 +408,10 @@ class GfmAutoComplete { fetchData($input, at) { if (this.isLoadingData[at]) return; + this.isLoadingData[at] = true; + const dataSource = this.dataSources[GfmAutoComplete.atTypeMap[at]]; + if (this.cachedData[at]) { this.loadData($input, at, this.cachedData[at]); } else if (GfmAutoComplete.atTypeMap[at] === 'emojis') { @@ -418,12 +421,14 @@ class GfmAutoComplete { GfmAutoComplete.glEmojiTag = glEmojiTag; }) .catch(() => { this.isLoadingData[at] = false; }); - } else { - AjaxCache.retrieve(this.dataSources[GfmAutoComplete.atTypeMap[at]], true) + } else if (dataSource) { + AjaxCache.retrieve(dataSource, true) .then((data) => { this.loadData($input, at, data); }) .catch(() => { this.isLoadingData[at] = false; }); + } else { + this.isLoadingData[at] = false; } } diff --git a/app/assets/javascripts/gpg_badges.js b/app/assets/javascripts/gpg_badges.js index 502e3569321..029fd6a67d4 100644 --- a/app/assets/javascripts/gpg_badges.js +++ b/app/assets/javascripts/gpg_badges.js @@ -7,12 +7,12 @@ import { __ } from '~/locale'; export default class GpgBadges { static fetch() { const badges = $('.js-loading-gpg-badge'); - const form = $('.commits-search-form'); + const tag = $('.js-signature-container'); badges.html('<i class="fa fa-spinner fa-spin"></i>'); - const params = parseQueryStringIntoObject(form.serialize()); - return axios.get(form.data('signaturesPath'), { params }) + const params = parseQueryStringIntoObject(tag.serialize()); + return axios.get(tag.data('signaturesPath'), { params }) .then(({ data }) => { data.signatures.forEach((signature) => { badges.filter(`[data-commit-sha="${signature.commit_sha}"]`).replaceWith(signature.html); diff --git a/app/assets/javascripts/ide/components/activity_bar.vue b/app/assets/javascripts/ide/components/activity_bar.vue new file mode 100644 index 00000000000..05dbc1410de --- /dev/null +++ b/app/assets/javascripts/ide/components/activity_bar.vue @@ -0,0 +1,106 @@ +<script> +import { mapActions, mapGetters, mapState } from 'vuex'; +import Icon from '~/vue_shared/components/icon.vue'; +import tooltip from '~/vue_shared/directives/tooltip'; +import { activityBarViews } from '../constants'; + +export default { + components: { + Icon, + }, + directives: { + tooltip, + }, + computed: { + ...mapGetters(['currentProject', 'hasChanges']), + ...mapState(['currentActivityView']), + goBackUrl() { + return document.referrer || this.currentProject.web_url; + }, + }, + methods: { + ...mapActions(['updateActivityBarView']), + }, + activityBarViews, +}; +</script> + +<template> + <nav class="ide-activity-bar"> + <ul class="list-unstyled"> + <li v-once> + <a + v-tooltip + data-container="body" + data-placement="right" + :href="goBackUrl" + class="ide-sidebar-link" + :title="s__('IDE|Go back')" + :aria-label="s__('IDE|Go back')" + > + <icon + :size="16" + name="go-back" + /> + </a> + </li> + <li> + <button + v-tooltip + data-container="body" + data-placement="right" + type="button" + class="ide-sidebar-link js-ide-edit-mode" + :class="{ + active: currentActivityView === $options.activityBarViews.edit + }" + @click.prevent="updateActivityBarView($options.activityBarViews.edit)" + :title="s__('IDE|Edit')" + :aria-label="s__('IDE|Edit')" + > + <icon + name="code" + /> + </button> + </li> + <li> + <button + v-tooltip + data-container="body" + data-placement="right" + type="button" + class="ide-sidebar-link js-ide-review-mode" + :class="{ + active: currentActivityView === $options.activityBarViews.review + }" + @click.prevent="updateActivityBarView($options.activityBarViews.review)" + :title="s__('IDE|Review')" + :aria-label="s__('IDE|Review')" + > + <icon + name="file-modified" + /> + </button> + </li> + <li v-show="hasChanges"> + <button + v-tooltip + data-container="body" + data-placement="right" + type="button" + class="ide-sidebar-link js-ide-commit-mode" + :class="{ + active: currentActivityView === $options.activityBarViews.commit + }" + @click.prevent="updateActivityBarView($options.activityBarViews.commit)" + :title="s__('IDE|Commit')" + :aria-label="s__('IDE|Commit')" + > + <icon + name="commit" + /> + </button> + </li> + </ul> + </nav> +</template> diff --git a/app/assets/javascripts/ide/components/changed_file_icon.vue b/app/assets/javascripts/ide/components/changed_file_icon.vue index 1fc11c84639..1cec84706fc 100644 --- a/app/assets/javascripts/ide/components/changed_file_icon.vue +++ b/app/assets/javascripts/ide/components/changed_file_icon.vue @@ -26,17 +26,24 @@ export default { required: false, default: false, }, + forceModifiedIcon: { + type: Boolean, + required: false, + default: false, + }, }, computed: { changedIcon() { const suffix = this.file.staged && !this.showStagedIcon ? '-solid' : ''; - return this.file.tempFile ? `file-addition${suffix}` : `file-modified${suffix}`; + return this.file.tempFile && !this.forceModifiedIcon + ? `file-addition${suffix}` + : `file-modified${suffix}`; }, stagedIcon() { return `${this.changedIcon}-solid`; }, changedIconClass() { - return `multi-${this.changedIcon} prepend-left-5 pull-left`; + return `multi-${this.changedIcon} pull-left`; }, tooltipTitle() { if (!this.showTooltip) return undefined; @@ -72,13 +79,7 @@ export default { class="ide-file-changed-icon" > <icon - v-if="file.staged && showStagedIcon" - :name="stagedIcon" - :size="12" - :css-classes="changedIconClass" - /> - <icon - v-if="file.changed || file.tempFile || (file.staged && !showStagedIcon)" + v-if="file.changed || file.tempFile || file.staged" :name="changedIcon" :size="12" :css-classes="changedIconClass" diff --git a/app/assets/javascripts/ide/components/commit_sidebar/actions.vue b/app/assets/javascripts/ide/components/commit_sidebar/actions.vue index 45321df191c..6a5790c9dff 100644 --- a/app/assets/javascripts/ide/components/commit_sidebar/actions.vue +++ b/app/assets/javascripts/ide/components/commit_sidebar/actions.vue @@ -1,5 +1,5 @@ <script> -import { mapState } from 'vuex'; +import { mapActions, mapState } from 'vuex'; import { sprintf, __ } from '~/locale'; import * as consts from '../../stores/modules/commit/constants'; import RadioGroup from './radio_group.vue'; @@ -9,7 +9,7 @@ export default { RadioGroup, }, computed: { - ...mapState(['currentBranchId']), + ...mapState(['currentBranchId', 'changedFiles', 'stagedFiles']), commitToCurrentBranchText() { return sprintf( __('Commit to %{branchName} branch'), @@ -17,6 +17,17 @@ export default { false, ); }, + disableMergeRequestRadio() { + return this.changedFiles.length > 0 && this.stagedFiles.length > 0; + }, + }, + mounted() { + if (this.disableMergeRequestRadio) { + this.updateCommitAction(consts.COMMIT_TO_CURRENT_BRANCH); + } + }, + methods: { + ...mapActions('commit', ['updateCommitAction']), }, commitToCurrentBranch: consts.COMMIT_TO_CURRENT_BRANCH, commitToNewBranch: consts.COMMIT_TO_NEW_BRANCH, @@ -44,6 +55,7 @@ export default { :value="$options.commitToNewBranchMR" :label="__('Create a new branch and merge request')" :show-input="true" + :disabled="disableMergeRequestRadio" /> </div> </template> diff --git a/app/assets/javascripts/ide/components/commit_sidebar/empty_state.vue b/app/assets/javascripts/ide/components/commit_sidebar/empty_state.vue index 6424b93ce54..d0a60d647e5 100644 --- a/app/assets/javascripts/ide/components/commit_sidebar/empty_state.vue +++ b/app/assets/javascripts/ide/components/commit_sidebar/empty_state.vue @@ -1,75 +1,27 @@ <script> -import { mapActions, mapState, mapGetters } from 'vuex'; -import Icon from '~/vue_shared/components/icon.vue'; -import tooltip from '~/vue_shared/directives/tooltip'; +import { mapState } from 'vuex'; export default { - components: { - Icon, - }, - directives: { - tooltip, - }, - props: { - noChangesStateSvgPath: { - type: String, - required: true, - }, - committedStateSvgPath: { - type: String, - required: true, - }, - }, computed: { - ...mapState(['lastCommitMsg', 'rightPanelCollapsed']), - ...mapGetters(['collapseButtonIcon', 'collapseButtonTooltip']), - statusSvg() { - return this.lastCommitMsg ? this.committedStateSvgPath : this.noChangesStateSvgPath; - }, - }, - methods: { - ...mapActions(['toggleRightPanelCollapsed']), + ...mapState(['lastCommitMsg', 'noChangesStateSvgPath']), }, }; </script> <template> <div + v-if="!lastCommitMsg" class="multi-file-commit-panel-section ide-commit-empty-state js-empty-state" > - <header - class="multi-file-commit-panel-header" - :class="{ - 'is-collapsed': rightPanelCollapsed, - }" - > - <button - v-tooltip - :title="collapseButtonTooltip" - data-container="body" - data-placement="left" - type="button" - class="btn btn-transparent multi-file-commit-panel-collapse-btn" - :aria-label="__('Toggle sidebar')" - @click.stop="toggleRightPanelCollapsed" - > - <icon - :name="collapseButtonIcon" - :size="18" - /> - </button> - </header> <div class="ide-commit-empty-state-container" - v-if="!rightPanelCollapsed" > <div class="svg-content svg-80"> - <img :src="statusSvg" /> + <img :src="noChangesStateSvgPath" /> </div> <div class="append-right-default prepend-left-default"> <div class="text-content text-center" - v-if="!lastCommitMsg" > <h4> {{ __('No changes') }} @@ -78,15 +30,6 @@ export default { {{ __('Edit files in the editor and commit changes here') }} </p> </div> - <div - class="text-content text-center" - v-else - > - <h4> - {{ __('All changes are committed') }} - </h4> - <p v-html="lastCommitMsg"></p> - </div> </div> </div> </div> diff --git a/app/assets/javascripts/ide/components/commit_sidebar/form.vue b/app/assets/javascripts/ide/components/commit_sidebar/form.vue new file mode 100644 index 00000000000..4a645c827ad --- /dev/null +++ b/app/assets/javascripts/ide/components/commit_sidebar/form.vue @@ -0,0 +1,171 @@ +<script> +import { mapState, mapActions, mapGetters } from 'vuex'; +import { sprintf, __ } from '~/locale'; +import LoadingButton from '~/vue_shared/components/loading_button.vue'; +import CommitMessageField from './message_field.vue'; +import Actions from './actions.vue'; +import SuccessMessage from './success_message.vue'; +import { activityBarViews, MAX_WINDOW_HEIGHT_COMPACT, COMMIT_ITEM_PADDING } from '../../constants'; + +export default { + components: { + Actions, + LoadingButton, + CommitMessageField, + SuccessMessage, + }, + data() { + return { + isCompact: true, + componentHeight: null, + }; + }, + computed: { + ...mapState(['changedFiles', 'stagedFiles', 'currentActivityView', 'lastCommitMsg']), + ...mapState('commit', ['commitMessage', 'submitCommitLoading']), + ...mapGetters(['hasChanges']), + ...mapGetters('commit', ['commitButtonDisabled', 'discardDraftButtonDisabled']), + overviewText() { + return sprintf( + __( + '<strong>%{changedFilesLength} unstaged</strong> and <strong>%{stagedFilesLength} staged</strong> changes', + ), + { + stagedFilesLength: this.stagedFiles.length, + changedFilesLength: this.changedFiles.length, + }, + ); + }, + }, + watch: { + currentActivityView() { + if (this.lastCommitMsg) { + this.isCompact = false; + } else { + this.isCompact = !( + this.currentActivityView === activityBarViews.commit && + window.innerHeight >= MAX_WINDOW_HEIGHT_COMPACT + ); + } + }, + lastCommitMsg() { + this.isCompact = + this.currentActivityView !== activityBarViews.commit && this.lastCommitMsg === ''; + }, + }, + methods: { + ...mapActions(['updateActivityBarView']), + ...mapActions('commit', ['updateCommitMessage', 'discardDraft', 'commitChanges']), + toggleIsSmall() { + this.updateActivityBarView(activityBarViews.commit) + .then(() => { + this.isCompact = !this.isCompact; + }) + .catch(e => { + throw e; + }); + }, + beforeEnterTransition() { + const elHeight = this.isCompact + ? this.$refs.formEl && this.$refs.formEl.offsetHeight + : this.$refs.compactEl && this.$refs.compactEl.offsetHeight; + + this.componentHeight = elHeight + COMMIT_ITEM_PADDING; + }, + enterTransition() { + this.$nextTick(() => { + const elHeight = this.isCompact + ? this.$refs.compactEl && this.$refs.compactEl.offsetHeight + : this.$refs.formEl && this.$refs.formEl.offsetHeight; + + this.componentHeight = elHeight + COMMIT_ITEM_PADDING; + }); + }, + afterEndTransition() { + this.componentHeight = null; + }, + }, + activityBarViews, +}; +</script> + +<template> + <div + class="multi-file-commit-form" + :class="{ + 'is-compact': isCompact, + 'is-full': !isCompact + }" + :style="{ + height: componentHeight ? `${componentHeight}px` : null, + }" + > + <transition + name="commit-form-slide-up" + @before-enter="beforeEnterTransition" + @enter="enterTransition" + @after-enter="afterEndTransition" + > + <div + v-if="isCompact" + class="commit-form-compact" + ref="compactEl" + > + <button + type="button" + :disabled="!hasChanges" + class="btn btn-primary btn-sm btn-block" + @click="toggleIsSmall" + > + {{ __('Commit') }} + </button> + <p + class="text-center" + v-html="overviewText" + ></p> + </div> + <form + v-if="!isCompact" + class="form-horizontal" + @submit.prevent.stop="commitChanges" + ref="formEl" + > + <transition name="fade"> + <success-message + v-show="lastCommitMsg" + /> + </transition> + <commit-message-field + :text="commitMessage" + @input="updateCommitMessage" + /> + <div class="clearfix prepend-top-15"> + <actions /> + <loading-button + :loading="submitCommitLoading" + :disabled="commitButtonDisabled" + container-class="btn btn-success btn-sm pull-left" + :label="__('Commit')" + @click="commitChanges" + /> + <button + v-if="!discardDraftButtonDisabled" + type="button" + class="btn btn-default btn-sm pull-right" + @click="discardDraft" + > + {{ __('Discard draft') }} + </button> + <button + v-else + type="button" + class="btn btn-default btn-sm pull-right" + @click="toggleIsSmall" + > + {{ __('Collapse') }} + </button> + </div> + </form> + </transition> + </div> +</template> diff --git a/app/assets/javascripts/ide/components/commit_sidebar/list.vue b/app/assets/javascripts/ide/components/commit_sidebar/list.vue index ff05ee8682a..c3ac18bfb83 100644 --- a/app/assets/javascripts/ide/components/commit_sidebar/list.vue +++ b/app/assets/javascripts/ide/components/commit_sidebar/list.vue @@ -1,16 +1,14 @@ <script> -import { mapActions, mapState, mapGetters } from 'vuex'; +import { mapActions } from 'vuex'; import { __, sprintf } from '~/locale'; import Icon from '~/vue_shared/components/icon.vue'; import tooltip from '~/vue_shared/directives/tooltip'; import ListItem from './list_item.vue'; -import ListCollapsed from './list_collapsed.vue'; export default { components: { Icon, ListItem, - ListCollapsed, }, directives: { tooltip, @@ -24,11 +22,6 @@ export default { type: Array, required: true, }, - showToggle: { - type: Boolean, - required: false, - default: true, - }, iconName: { type: String, required: true, @@ -51,9 +44,12 @@ export default { default: false, }, }, + data() { + return { + showActionButton: false, + }; + }, computed: { - ...mapState(['rightPanelCollapsed']), - ...mapGetters(['collapseButtonIcon', 'collapseButtonTooltip']), titleText() { return sprintf(__('%{title} changes'), { title: this.title, @@ -61,10 +57,13 @@ export default { }, }, methods: { - ...mapActions(['toggleRightPanelCollapsed', 'stageAllChanges', 'unstageAllChanges']), + ...mapActions(['stageAllChanges', 'unstageAllChanges']), actionBtnClicked() { this[this.action](); }, + setShowActionButton(show) { + this.showActionButton = show; + }, }, }; </script> @@ -72,19 +71,14 @@ export default { <template> <div class="ide-commit-list-container" - :class="{ - 'is-collapsed': rightPanelCollapsed, - }" > <header class="multi-file-commit-panel-header" + @mouseenter="setShowActionButton(true)" + @mouseleave="setShowActionButton(false)" > <div - v-if="!rightPanelCollapsed" class="multi-file-commit-panel-header-title" - :class="{ - 'append-right-10': showToggle, - }" > <icon v-once @@ -92,7 +86,14 @@ export default { :size="18" /> {{ titleText }} + <span + v-show="!showActionButton" + class="ide-commit-file-count" + > + {{ fileList.length }} + </span> <button + v-show="showActionButton" type="button" class="btn btn-blank btn-link ide-staged-action-btn" @click="actionBtnClicked" @@ -100,52 +101,28 @@ export default { {{ actionBtnText }} </button> </div> - <button - v-if="showToggle" - v-tooltip - :title="collapseButtonTooltip" - data-container="body" - data-placement="left" - type="button" - class="btn btn-transparent multi-file-commit-panel-collapse-btn" - :aria-label="__('Toggle sidebar')" - @click.stop="toggleRightPanelCollapsed" - > - <icon - :name="collapseButtonIcon" - :size="18" - /> - </button> </header> - <list-collapsed - v-if="rightPanelCollapsed" - :files="fileList" - :icon-name="iconName" - :title="title" - /> - <template v-else> - <ul - v-if="fileList.length" - class="multi-file-commit-list list-unstyled append-bottom-0" - > - <li - v-for="file in fileList" - :key="file.key" - > - <list-item - :file="file" - :action-component="itemActionComponent" - :key-prefix="title" - :staged-list="stagedList" - /> - </li> - </ul> - <p - v-else - class="multi-file-commit-list help-block" + <ul + v-if="fileList.length" + class="multi-file-commit-list list-unstyled append-bottom-0" + > + <li + v-for="file in fileList" + :key="file.key" > - {{ __('No changes') }} - </p> - </template> + <list-item + :file="file" + :action-component="itemActionComponent" + :key-prefix="title" + :staged-list="stagedList" + /> + </li> + </ul> + <p + v-else + class="multi-file-commit-list help-block" + > + {{ __('No changes') }} + </p> </div> </template> diff --git a/app/assets/javascripts/ide/components/commit_sidebar/list_item.vue b/app/assets/javascripts/ide/components/commit_sidebar/list_item.vue index ad4713c40d5..03f3e4de83c 100644 --- a/app/assets/javascripts/ide/components/commit_sidebar/list_item.vue +++ b/app/assets/javascripts/ide/components/commit_sidebar/list_item.vue @@ -3,6 +3,7 @@ import { mapActions } from 'vuex'; import Icon from '~/vue_shared/components/icon.vue'; import StageButton from './stage_button.vue'; import UnstageButton from './unstage_button.vue'; +import { viewerTypes } from '../../constants'; export default { components: { @@ -36,7 +37,7 @@ export default { return this.file.tempFile ? `file-addition${prefix}` : `file-modified${prefix}`; }, iconClass() { - return `multi-file-${this.file.tempFile ? 'additions' : 'modified'} append-right-8`; + return `multi-file-${this.file.tempFile ? 'addition' : 'modified'} append-right-8`; }, }, methods: { @@ -53,7 +54,7 @@ export default { keyPrefix: this.keyPrefix.toLowerCase(), }).then(changeViewer => { if (changeViewer) { - this.updateViewer('diff'); + this.updateViewer(viewerTypes.diff); } }); }, diff --git a/app/assets/javascripts/ide/components/commit_sidebar/radio_group.vue b/app/assets/javascripts/ide/components/commit_sidebar/radio_group.vue index b660a2961cb..00f2312ae51 100644 --- a/app/assets/javascripts/ide/components/commit_sidebar/radio_group.vue +++ b/app/assets/javascripts/ide/components/commit_sidebar/radio_group.vue @@ -1,5 +1,6 @@ <script> import { mapActions, mapState, mapGetters } from 'vuex'; +import { __ } from '~/locale'; import tooltip from '~/vue_shared/directives/tooltip'; export default { @@ -26,10 +27,20 @@ export default { required: false, default: false, }, + disabled: { + type: Boolean, + required: false, + default: false, + }, }, computed: { ...mapState('commit', ['commitAction']), ...mapGetters('commit', ['newBranchName']), + tooltipTitle() { + return this.disabled + ? __('This option is disabled while you still have unstaged changes') + : ''; + }, }, methods: { ...mapActions('commit', ['updateCommitAction', 'updateBranchName']), @@ -39,19 +50,28 @@ export default { <template> <fieldset> - <label> + <label + v-tooltip + :title="tooltipTitle" + :class="{ + 'is-disabled': disabled + }" + > <input type="radio" name="commit-action" :value="value" @change="updateCommitAction($event.target.value)" - :checked="checked" - v-once + :checked="commitAction === value" + :disabled="disabled" /> <span class="prepend-left-10"> - <template v-if="label"> + <span + v-if="label" + class="ide-radio-label" + > {{ label }} - </template> + </span> <slot v-else></slot> </span> </label> diff --git a/app/assets/javascripts/ide/components/commit_sidebar/success_message.vue b/app/assets/javascripts/ide/components/commit_sidebar/success_message.vue new file mode 100644 index 00000000000..a6df91b79c2 --- /dev/null +++ b/app/assets/javascripts/ide/components/commit_sidebar/success_message.vue @@ -0,0 +1,33 @@ +<script> +import { mapState } from 'vuex'; + +export default { + computed: { + ...mapState(['lastCommitMsg', 'committedStateSvgPath']), + }, +}; +</script> + +<template> + <div + class="multi-file-commit-panel-success-message" + aria-live="assertive" + > + <div class="svg-content svg-80"> + <img + :src="committedStateSvgPath" + alt="" + /> + </div> + <div class="append-right-default prepend-left-default"> + <div + class="text-content text-center" + > + <h4> + {{ __('All changes are committed') }} + </h4> + <p v-html="lastCommitMsg"></p> + </div> + </div> + </div> +</template> diff --git a/app/assets/javascripts/ide/components/editor_mode_dropdown.vue b/app/assets/javascripts/ide/components/editor_mode_dropdown.vue index 0c44a755f56..b9af4d27145 100644 --- a/app/assets/javascripts/ide/components/editor_mode_dropdown.vue +++ b/app/assets/javascripts/ide/components/editor_mode_dropdown.vue @@ -1,28 +1,15 @@ <script> -import Icon from '~/vue_shared/components/icon.vue'; import { __, sprintf } from '~/locale'; +import { viewerTypes } from '../constants'; export default { - components: { - Icon, - }, props: { - hasChanges: { - type: Boolean, - required: false, - default: false, - }, - mergeRequestId: { - type: String, - required: false, - default: '', - }, viewer: { type: String, required: true, }, - showShadow: { - type: Boolean, + mergeRequestId: { + type: Number, required: true, }, }, @@ -38,84 +25,45 @@ export default { this.$emit('click', mode); }, }, + viewerTypes, }; </script> <template> <div class="dropdown" - :class="{ - shadow: showShadow, - }" > <button type="button" - class="btn btn-primary btn-sm" - :class="{ - 'btn-inverted': hasChanges, - }" + class="btn btn-link" data-toggle="dropdown" > - <template v-if="viewer === 'mrdiff' && mergeRequestId"> - {{ mergeReviewLine }} - </template> - <template v-else-if="viewer === 'editor'"> - {{ __('Editing') }} - </template> - <template v-else> - {{ __('Reviewing') }} - </template> - <icon - name="angle-down" - :size="12" - css-classes="caret-down" - /> + {{ __('Edit') }} </button> <div class="dropdown-menu dropdown-menu-selectable dropdown-open-left"> <ul> - <template v-if="mergeRequestId"> - <li> - <a - href="#" - @click.prevent="changeMode('mrdiff')" - :class="{ - 'is-active': viewer === 'mrdiff', - }" - > - <strong class="dropdown-menu-inner-title"> - {{ mergeReviewLine }} - </strong> - <span class="dropdown-menu-inner-content"> - {{ __('Compare changes with the merge request target branch') }} - </span> - </a> - </li> - <li - role="separator" - class="divider" - > - </li> - </template> <li> <a href="#" - @click.prevent="changeMode('editor')" + @click.prevent="changeMode($options.viewerTypes.mr)" :class="{ - 'is-active': viewer === 'editor', + 'is-active': viewer === $options.viewerTypes.mr, }" > - <strong class="dropdown-menu-inner-title">{{ __('Editing') }}</strong> + <strong class="dropdown-menu-inner-title"> + {{ mergeReviewLine }} + </strong> <span class="dropdown-menu-inner-content"> - {{ __('View and edit lines') }} + {{ __('Compare changes with the merge request target branch') }} </span> </a> </li> <li> <a href="#" - @click.prevent="changeMode('diff')" + @click.prevent="changeMode($options.viewerTypes.diff)" :class="{ - 'is-active': viewer === 'diff', + 'is-active': viewer === $options.viewerTypes.diff, }" > <strong class="dropdown-menu-inner-title">{{ __('Reviewing') }}</strong> diff --git a/app/assets/javascripts/ide/components/file_finder/index.vue b/app/assets/javascripts/ide/components/file_finder/index.vue new file mode 100644 index 00000000000..ea2b13a8b21 --- /dev/null +++ b/app/assets/javascripts/ide/components/file_finder/index.vue @@ -0,0 +1,245 @@ +<script> +import { mapActions, mapGetters, mapState } from 'vuex'; +import fuzzaldrinPlus from 'fuzzaldrin-plus'; +import VirtualList from 'vue-virtual-scroll-list'; +import Item from './item.vue'; +import router from '../../ide_router'; +import { + MAX_FILE_FINDER_RESULTS, + FILE_FINDER_ROW_HEIGHT, + FILE_FINDER_EMPTY_ROW_HEIGHT, +} from '../../constants'; +import { + UP_KEY_CODE, + DOWN_KEY_CODE, + ENTER_KEY_CODE, + ESC_KEY_CODE, +} from '../../../lib/utils/keycodes'; + +export default { + components: { + Item, + VirtualList, + }, + data() { + return { + focusedIndex: 0, + searchText: '', + mouseOver: false, + cancelMouseOver: false, + }; + }, + computed: { + ...mapGetters(['allBlobs']), + ...mapState(['fileFindVisible', 'loading']), + filteredBlobs() { + const searchText = this.searchText.trim(); + + if (searchText === '') { + return this.allBlobs.slice(0, MAX_FILE_FINDER_RESULTS); + } + + return fuzzaldrinPlus + .filter(this.allBlobs, searchText, { + key: 'path', + maxResults: MAX_FILE_FINDER_RESULTS, + }) + .sort((a, b) => b.lastOpenedAt - a.lastOpenedAt); + }, + filteredBlobsLength() { + return this.filteredBlobs.length; + }, + listShowCount() { + return this.filteredBlobsLength ? Math.min(this.filteredBlobsLength, 5) : 1; + }, + listHeight() { + return this.filteredBlobsLength ? FILE_FINDER_ROW_HEIGHT : FILE_FINDER_EMPTY_ROW_HEIGHT; + }, + showClearInputButton() { + return this.searchText.trim() !== ''; + }, + }, + watch: { + fileFindVisible() { + this.$nextTick(() => { + if (!this.fileFindVisible) { + this.searchText = ''; + } else { + this.focusedIndex = 0; + + if (this.$refs.searchInput) { + this.$refs.searchInput.focus(); + } + } + }); + }, + searchText() { + this.focusedIndex = 0; + }, + focusedIndex() { + if (!this.mouseOver) { + this.$nextTick(() => { + const el = this.$refs.virtualScrollList.$el; + const scrollTop = this.focusedIndex * FILE_FINDER_ROW_HEIGHT; + const bottom = this.listShowCount * FILE_FINDER_ROW_HEIGHT; + + if (this.focusedIndex === 0) { + // if index is the first index, scroll straight to start + el.scrollTop = 0; + } else if (this.focusedIndex === this.filteredBlobsLength - 1) { + // if index is the last index, scroll to the end + el.scrollTop = this.filteredBlobsLength * FILE_FINDER_ROW_HEIGHT; + } else if (scrollTop >= bottom + el.scrollTop) { + // if element is off the bottom of the scroll list, scroll down one item + el.scrollTop = scrollTop - bottom + FILE_FINDER_ROW_HEIGHT; + } else if (scrollTop < el.scrollTop) { + // if element is off the top of the scroll list, scroll up one item + el.scrollTop = scrollTop; + } + }); + } + }, + }, + methods: { + ...mapActions(['toggleFileFinder']), + clearSearchInput() { + this.searchText = ''; + + this.$nextTick(() => { + this.$refs.searchInput.focus(); + }); + }, + onKeydown(e) { + switch (e.keyCode) { + case UP_KEY_CODE: + e.preventDefault(); + this.mouseOver = false; + this.cancelMouseOver = true; + if (this.focusedIndex > 0) { + this.focusedIndex -= 1; + } else { + this.focusedIndex = this.filteredBlobsLength - 1; + } + break; + case DOWN_KEY_CODE: + e.preventDefault(); + this.mouseOver = false; + this.cancelMouseOver = true; + if (this.focusedIndex < this.filteredBlobsLength - 1) { + this.focusedIndex += 1; + } else { + this.focusedIndex = 0; + } + break; + default: + break; + } + }, + onKeyup(e) { + switch (e.keyCode) { + case ENTER_KEY_CODE: + this.openFile(this.filteredBlobs[this.focusedIndex]); + break; + case ESC_KEY_CODE: + this.toggleFileFinder(false); + break; + default: + break; + } + }, + openFile(file) { + this.toggleFileFinder(false); + router.push(`/project${file.url}`); + }, + onMouseOver(index) { + if (!this.cancelMouseOver) { + this.mouseOver = true; + this.focusedIndex = index; + } + }, + onMouseMove(index) { + this.cancelMouseOver = false; + this.onMouseOver(index); + }, + }, +}; +</script> + +<template> + <div + class="ide-file-finder-overlay" + @mousedown.self="toggleFileFinder(false)" + > + <div + class="dropdown-menu diff-file-changes ide-file-finder show" + > + <div class="dropdown-input"> + <input + type="search" + class="dropdown-input-field" + :placeholder="__('Search files')" + autocomplete="off" + v-model="searchText" + ref="searchInput" + @keydown="onKeydown($event)" + @keyup="onKeyup($event)" + /> + <i + aria-hidden="true" + class="fa fa-search dropdown-input-search" + :class="{ + hidden: showClearInputButton + }" + ></i> + <i + role="button" + :aria-label="__('Clear search input')" + class="fa fa-times dropdown-input-clear" + :class="{ + show: showClearInputButton + }" + @click="clearSearchInput" + ></i> + </div> + <div> + <virtual-list + :size="listHeight" + :remain="listShowCount" + wtag="ul" + ref="virtualScrollList" + > + <template v-if="filteredBlobsLength"> + <li + v-for="(file, index) in filteredBlobs" + :key="file.key" + > + <item + class="disable-hover" + :file="file" + :search-text="searchText" + :focused="index === focusedIndex" + :index="index" + @click="openFile" + @mouseover="onMouseOver" + @mousemove="onMouseMove" + /> + </li> + </template> + <li + v-else + class="dropdown-menu-empty-item" + > + <div class="append-right-default prepend-left-default prepend-top-8 append-bottom-8"> + <template v-if="loading"> + {{ __('Loading...') }} + </template> + <template v-else> + {{ __('No files found.') }} + </template> + </div> + </li> + </virtual-list> + </div> + </div> + </div> +</template> diff --git a/app/assets/javascripts/ide/components/file_finder/item.vue b/app/assets/javascripts/ide/components/file_finder/item.vue new file mode 100644 index 00000000000..d4427420207 --- /dev/null +++ b/app/assets/javascripts/ide/components/file_finder/item.vue @@ -0,0 +1,113 @@ +<script> +import fuzzaldrinPlus from 'fuzzaldrin-plus'; +import FileIcon from '../../../vue_shared/components/file_icon.vue'; +import ChangedFileIcon from '../changed_file_icon.vue'; + +const MAX_PATH_LENGTH = 60; + +export default { + components: { + ChangedFileIcon, + FileIcon, + }, + props: { + file: { + type: Object, + required: true, + }, + focused: { + type: Boolean, + required: true, + }, + searchText: { + type: String, + required: true, + }, + index: { + type: Number, + required: true, + }, + }, + computed: { + pathWithEllipsis() { + const path = this.file.path; + + return path.length < MAX_PATH_LENGTH + ? path + : `...${path.substr(path.length - MAX_PATH_LENGTH)}`; + }, + nameSearchTextOccurences() { + return fuzzaldrinPlus.match(this.file.name, this.searchText); + }, + pathSearchTextOccurences() { + return fuzzaldrinPlus.match(this.pathWithEllipsis, this.searchText); + }, + }, + methods: { + clickRow() { + this.$emit('click', this.file); + }, + mouseOverRow() { + this.$emit('mouseover', this.index); + }, + mouseMove() { + this.$emit('mousemove', this.index); + }, + }, +}; +</script> + +<template> + <button + type="button" + class="diff-changed-file" + :class="{ + 'is-focused': focused, + }" + @click.prevent="clickRow" + @mouseover="mouseOverRow" + @mousemove="mouseMove" + > + <file-icon + :file-name="file.name" + :size="16" + css-classes="diff-file-changed-icon append-right-8" + /> + <span class="diff-changed-file-content append-right-8"> + <strong + class="diff-changed-file-name" + > + <span + v-for="(char, index) in file.name.split('')" + :key="index + char" + :class="{ + highlighted: nameSearchTextOccurences.indexOf(index) >= 0, + }" + v-text="char" + > + </span> + </strong> + <span + class="diff-changed-file-path prepend-top-5" + > + <span + v-for="(char, index) in pathWithEllipsis.split('')" + :key="index + char" + :class="{ + highlighted: pathSearchTextOccurences.indexOf(index) >= 0, + }" + v-text="char" + > + </span> + </span> + </span> + <span + v-if="file.changed || file.tempFile" + class="diff-changed-stats" + > + <changed-file-icon + :file="file" + /> + </span> + </button> +</template> diff --git a/app/assets/javascripts/ide/components/ide.vue b/app/assets/javascripts/ide/components/ide.vue index 1c237c0ec97..d479b705e35 100644 --- a/app/assets/javascripts/ide/components/ide.vue +++ b/app/assets/javascripts/ide/components/ide.vue @@ -1,35 +1,31 @@ <script> -import { mapState, mapGetters } from 'vuex'; -import ideSidebar from './ide_side_bar.vue'; -import ideContextbar from './ide_context_bar.vue'; -import repoTabs from './repo_tabs.vue'; -import ideStatusBar from './ide_status_bar.vue'; -import repoEditor from './repo_editor.vue'; +import Mousetrap from 'mousetrap'; +import { mapActions, mapState, mapGetters } from 'vuex'; +import IdeSidebar from './ide_side_bar.vue'; +import RepoTabs from './repo_tabs.vue'; +import IdeStatusBar from './ide_status_bar.vue'; +import RepoEditor from './repo_editor.vue'; +import FindFile from './file_finder/index.vue'; + +const originalStopCallback = Mousetrap.stopCallback; export default { components: { - ideSidebar, - ideContextbar, - repoTabs, - ideStatusBar, - repoEditor, - }, - props: { - emptyStateSvgPath: { - type: String, - required: true, - }, - noChangesStateSvgPath: { - type: String, - required: true, - }, - committedStateSvgPath: { - type: String, - required: true, - }, + IdeSidebar, + RepoTabs, + IdeStatusBar, + RepoEditor, + FindFile, }, computed: { - ...mapState(['changedFiles', 'openFiles', 'viewer', 'currentMergeRequestId']), + ...mapState([ + 'changedFiles', + 'openFiles', + 'viewer', + 'currentMergeRequestId', + 'fileFindVisible', + 'emptyStateSvgPath', + ]), ...mapGetters(['activeFile', 'hasChanges']), }, mounted() { @@ -42,67 +38,90 @@ export default { }); return returnValue; }; + + Mousetrap.bind(['t', 'command+p', 'ctrl+p'], e => { + if (e.preventDefault) { + e.preventDefault(); + } + + this.toggleFileFinder(!this.fileFindVisible); + }); + + Mousetrap.stopCallback = (e, el, combo) => this.mousetrapStopCallback(e, el, combo); + }, + methods: { + ...mapActions(['toggleFileFinder']), + mousetrapStopCallback(e, el, combo) { + if (combo === 't' && el.classList.contains('dropdown-input-field')) { + return true; + } else if (combo === 'command+p' || combo === 'ctrl+p') { + return false; + } + + return originalStopCallback(e, el, combo); + }, }, }; </script> <template> - <div - class="ide-view" - > - <ide-sidebar /> + <article class="ide"> <div - class="multi-file-edit-pane" + class="ide-view" > - <template - v-if="activeFile" + <find-file + v-show="fileFindVisible" + /> + <ide-sidebar /> + <div + class="multi-file-edit-pane" > - <repo-tabs - :active-file="activeFile" - :files="openFiles" - :viewer="viewer" - :has-changes="hasChanges" - :merge-request-id="currentMergeRequestId" - /> - <repo-editor - class="multi-file-edit-pane-content" - :file="activeFile" - /> - <ide-status-bar - :file="activeFile" - /> - </template> - <template - v-else - > - <div - v-once - class="ide-empty-state" + <template + v-if="activeFile" + > + <repo-tabs + :active-file="activeFile" + :files="openFiles" + :viewer="viewer" + :has-changes="hasChanges" + :merge-request-id="currentMergeRequestId" + /> + <repo-editor + class="multi-file-edit-pane-content" + :file="activeFile" + /> + </template> + <template + v-else > - <div class="row js-empty-state"> - <div class="col-xs-12"> - <div class="svg-content svg-250"> - <img :src="emptyStateSvgPath" /> + <div + v-once + class="ide-empty-state" + > + <div class="row js-empty-state"> + <div class="col-xs-12"> + <div class="svg-content svg-250"> + <img :src="emptyStateSvgPath" /> + </div> </div> - </div> - <div class="col-xs-12"> - <div class="text-content text-center"> - <h4> - Welcome to the GitLab IDE - </h4> - <p> - You can select a file in the left sidebar to begin - editing and use the right sidebar to commit your changes. - </p> + <div class="col-xs-12"> + <div class="text-content text-center"> + <h4> + Welcome to the GitLab IDE + </h4> + <p> + You can select a file in the left sidebar to begin + editing and use the right sidebar to commit your changes. + </p> + </div> </div> </div> </div> - </div> - </template> + </template> + </div> </div> - <ide-contextbar - :no-changes-state-svg-path="noChangesStateSvgPath" - :committed-state-svg-path="committedStateSvgPath" + <ide-status-bar + :file="activeFile" /> - </div> + </article> </template> diff --git a/app/assets/javascripts/ide/components/ide_context_bar.vue b/app/assets/javascripts/ide/components/ide_context_bar.vue deleted file mode 100644 index 627fbeb9adf..00000000000 --- a/app/assets/javascripts/ide/components/ide_context_bar.vue +++ /dev/null @@ -1,42 +0,0 @@ -<script> -import icon from '~/vue_shared/components/icon.vue'; -import panelResizer from '~/vue_shared/components/panel_resizer.vue'; -import repoCommitSection from './repo_commit_section.vue'; -import ResizablePanel from './resizable_panel.vue'; - -export default { - components: { - repoCommitSection, - icon, - panelResizer, - ResizablePanel, - }, - props: { - noChangesStateSvgPath: { - type: String, - required: true, - }, - committedStateSvgPath: { - type: String, - required: true, - }, - }, -}; -</script> - -<template> - <resizable-panel - :collapsible="true" - :initial-width="340" - side="right" - > - <div - class="multi-file-commit-panel-section" - > - <repo-commit-section - :no-changes-state-svg-path="noChangesStateSvgPath" - :committed-state-svg-path="committedStateSvgPath" - /> - </div> - </resizable-panel> -</template> diff --git a/app/assets/javascripts/ide/components/ide_external_links.vue b/app/assets/javascripts/ide/components/ide_external_links.vue deleted file mode 100644 index c6f6e0d2348..00000000000 --- a/app/assets/javascripts/ide/components/ide_external_links.vue +++ /dev/null @@ -1,43 +0,0 @@ -<script> -import icon from '~/vue_shared/components/icon.vue'; - -export default { - components: { - icon, - }, - props: { - projectUrl: { - type: String, - required: true, - }, - }, - computed: { - goBackUrl() { - return document.referrer || this.projectUrl; - }, - }, -}; -</script> - -<template> - <nav - class="ide-external-links" - v-once - > - <p> - <a - :href="goBackUrl" - class="ide-sidebar-link" - > - <icon - :size="16" - class="append-right-8" - name="go-back" - /> - <span class="ide-external-links-text"> - {{ s__('Go back') }} - </span> - </a> - </p> - </nav> -</template> diff --git a/app/assets/javascripts/ide/components/ide_project_branches_tree.vue b/app/assets/javascripts/ide/components/ide_project_branches_tree.vue deleted file mode 100644 index eb2749e6151..00000000000 --- a/app/assets/javascripts/ide/components/ide_project_branches_tree.vue +++ /dev/null @@ -1,47 +0,0 @@ -<script> - import icon from '~/vue_shared/components/icon.vue'; - import repoTree from './ide_repo_tree.vue'; - import newDropdown from './new_dropdown/index.vue'; - - export default { - components: { - repoTree, - icon, - newDropdown, - }, - props: { - projectId: { - type: String, - required: true, - }, - branch: { - type: Object, - required: true, - }, - }, - }; -</script> - -<template> - <div class="branch-container"> - <div class="branch-header"> - <div class="branch-header-title str-truncated ref-name"> - <icon - name="branch" - :size="12" - /> - {{ branch.name }} - </div> - <div class="branch-header-btns"> - <new-dropdown - :project-id="projectId" - :branch="branch.name" - path="" - /> - </div> - </div> - <repo-tree - :tree="branch.tree" - /> - </div> -</template> diff --git a/app/assets/javascripts/ide/components/ide_project_tree.vue b/app/assets/javascripts/ide/components/ide_project_tree.vue deleted file mode 100644 index a6f40286ac1..00000000000 --- a/app/assets/javascripts/ide/components/ide_project_tree.vue +++ /dev/null @@ -1,65 +0,0 @@ -<script> -import ProjectAvatarImage from '~/vue_shared/components/project_avatar/image.vue'; -import Identicon from '../../vue_shared/components/identicon.vue'; -import BranchesTree from './ide_project_branches_tree.vue'; -import ExternalLinks from './ide_external_links.vue'; - -export default { - components: { - BranchesTree, - ExternalLinks, - ProjectAvatarImage, - Identicon, - }, - props: { - project: { - type: Object, - required: true, - }, - }, -}; -</script> - -<template> - <div class="projects-sidebar"> - <div class="context-header"> - <a - :title="project.name" - :href="project.web_url" - > - <div - v-if="project.avatar_url" - class="avatar-container s40 project-avatar" - > - <project-avatar-image - class="avatar-container project-avatar" - :link-href="project.path" - :img-src="project.avatar_url" - :img-alt="project.name" - :img-size="40" - /> - </div> - <identicon - v-else - size-class="s40" - :entity-id="project.id" - :entity-name="project.name" - /> - <div class="sidebar-context-title"> - {{ project.name }} - </div> - </a> - </div> - <external-links - :project-url="project.web_url" - /> - <div class="multi-file-commit-panel-inner-scroll"> - <branches-tree - v-for="branch in project.branches" - :key="branch.name" - :project-id="project.path_with_namespace" - :branch="branch" - /> - </div> - </div> -</template> diff --git a/app/assets/javascripts/ide/components/ide_repo_tree.vue b/app/assets/javascripts/ide/components/ide_repo_tree.vue deleted file mode 100644 index e6af88e04bc..00000000000 --- a/app/assets/javascripts/ide/components/ide_repo_tree.vue +++ /dev/null @@ -1,41 +0,0 @@ -<script> -import SkeletonLoadingContainer from '~/vue_shared/components/skeleton_loading_container.vue'; -import RepoFile from './repo_file.vue'; - -export default { - components: { - RepoFile, - SkeletonLoadingContainer, - }, - props: { - tree: { - type: Object, - required: true, - }, - }, -}; -</script> - -<template> - <div - class="ide-file-list" - > - <template v-if="tree.loading"> - <div - class="multi-file-loading-container" - v-for="n in 3" - :key="n" - > - <skeleton-loading-container /> - </div> - </template> - <template v-else> - <repo-file - v-for="file in tree.tree" - :key="file.key" - :file="file" - :level="0" - /> - </template> - </div> -</template> diff --git a/app/assets/javascripts/ide/components/ide_review.vue b/app/assets/javascripts/ide/components/ide_review.vue new file mode 100644 index 00000000000..0c9ec3b00f0 --- /dev/null +++ b/app/assets/javascripts/ide/components/ide_review.vue @@ -0,0 +1,62 @@ +<script> +import { mapGetters, mapState, mapActions } from 'vuex'; +import IdeTreeList from './ide_tree_list.vue'; +import EditorModeDropdown from './editor_mode_dropdown.vue'; +import { viewerTypes } from '../constants'; + +export default { + components: { + IdeTreeList, + EditorModeDropdown, + }, + computed: { + ...mapGetters(['currentMergeRequest']), + ...mapState(['viewer']), + showLatestChangesText() { + return !this.currentMergeRequest || this.viewer === viewerTypes.diff; + }, + showMergeRequestText() { + return this.currentMergeRequest && this.viewer === viewerTypes.mr; + }, + }, + mounted() { + this.$nextTick(() => { + this.updateViewer(this.currentMergeRequest ? viewerTypes.mr : viewerTypes.diff); + }); + }, + methods: { + ...mapActions(['updateViewer']), + }, +}; +</script> + +<template> + <ide-tree-list + :viewer-type="viewer" + header-class="ide-review-header" + :disable-action-dropdown="true" + > + <template + slot="header" + > + <div class="ide-review-button-holder"> + {{ __('Review') }} + <editor-mode-dropdown + v-if="currentMergeRequest" + :viewer="viewer" + :merge-request-id="currentMergeRequest.iid" + @click="updateViewer" + /> + </div> + <div class="prepend-top-5 ide-review-sub-header"> + <template v-if="showLatestChangesText"> + {{ __('Latest changes') }} + </template> + <template v-else-if="showMergeRequestText"> + {{ __('Merge request') }} + (<a :href="currentMergeRequest.web_url">!{{ currentMergeRequest.iid }}</a>) + </template> + </div> + </template> + </ide-tree-list> +</template> diff --git a/app/assets/javascripts/ide/components/ide_side_bar.vue b/app/assets/javascripts/ide/components/ide_side_bar.vue index 8cf1ccb4fce..3f980203911 100644 --- a/app/assets/javascripts/ide/components/ide_side_bar.vue +++ b/app/assets/javascripts/ide/components/ide_side_bar.vue @@ -1,36 +1,82 @@ <script> - import { mapState, mapGetters } from 'vuex'; - import icon from '~/vue_shared/components/icon.vue'; - import panelResizer from '~/vue_shared/components/panel_resizer.vue'; - import skeletonLoadingContainer from '~/vue_shared/components/skeleton_loading_container.vue'; - import projectTree from './ide_project_tree.vue'; - import ResizablePanel from './resizable_panel.vue'; +import { mapState, mapGetters } from 'vuex'; +import ProjectAvatarImage from '~/vue_shared/components/project_avatar/image.vue'; +import Icon from '~/vue_shared/components/icon.vue'; +import tooltip from '~/vue_shared/directives/tooltip'; +import PanelResizer from '~/vue_shared/components/panel_resizer.vue'; +import SkeletonLoadingContainer from '~/vue_shared/components/skeleton_loading_container.vue'; +import Identicon from '../../vue_shared/components/identicon.vue'; +import IdeTree from './ide_tree.vue'; +import ResizablePanel from './resizable_panel.vue'; +import ActivityBar from './activity_bar.vue'; +import CommitSection from './repo_commit_section.vue'; +import CommitForm from './commit_sidebar/form.vue'; +import IdeReview from './ide_review.vue'; +import SuccessMessage from './commit_sidebar/success_message.vue'; +import { activityBarViews } from '../constants'; - export default { - components: { - projectTree, - icon, - panelResizer, - skeletonLoadingContainer, - ResizablePanel, +export default { + directives: { + tooltip, + }, + components: { + Icon, + PanelResizer, + SkeletonLoadingContainer, + ResizablePanel, + ActivityBar, + ProjectAvatarImage, + Identicon, + CommitSection, + IdeTree, + CommitForm, + IdeReview, + SuccessMessage, + }, + data() { + return { + showTooltip: false, + }; + }, + computed: { + ...mapState([ + 'loading', + 'currentBranchId', + 'currentActivityView', + 'changedFiles', + 'stagedFiles', + 'lastCommitMsg', + ]), + ...mapGetters(['currentProject', 'someUncommitedChanges']), + showSuccessMessage() { + return ( + this.currentActivityView === activityBarViews.edit && + (this.lastCommitMsg && !this.someUncommitedChanges) + ); }, - computed: { - ...mapState([ - 'loading', - ]), - ...mapGetters([ - 'projectsWithTrees', - ]), + branchTooltipTitle() { + return this.showTooltip ? this.currentBranchId : undefined; }, - }; + }, + watch: { + currentBranchId() { + this.$nextTick(() => { + this.showTooltip = this.$refs.branchId.scrollWidth > this.$refs.branchId.offsetWidth; + }); + }, + }, +}; </script> <template> <resizable-panel :collapsible="false" - :initial-width="290" + :initial-width="340" side="left" > + <activity-bar + v-if="!loading" + /> <div class="multi-file-commit-panel-inner"> <template v-if="loading"> <div @@ -41,11 +87,54 @@ <skeleton-loading-container /> </div> </template> - <project-tree - v-for="project in projectsWithTrees" - :key="project.id" - :project="project" - /> + <template v-else> + <div class="context-header ide-context-header"> + <a + :href="currentProject.web_url" + > + <div + v-if="currentProject.avatar_url" + class="avatar-container s40 project-avatar" + > + <project-avatar-image + class="avatar-container project-avatar" + :link-href="currentProject.path" + :img-src="currentProject.avatar_url" + :img-alt="currentProject.name" + :img-size="40" + /> + </div> + <identicon + v-else + size-class="s40" + :entity-id="currentProject.id" + :entity-name="currentProject.name" + /> + <div class="ide-sidebar-project-title"> + <div class="sidebar-context-title"> + {{ currentProject.name }} + </div> + <div + class="sidebar-context-title ide-sidebar-branch-title" + ref="branchId" + v-tooltip + :title="branchTooltipTitle" + > + <icon + name="branch" + css-classes="append-right-5" + />{{ currentBranchId }} + </div> + </div> + </a> + </div> + <div class="multi-file-commit-panel-inner-scroll"> + <component + :is="currentActivityView" + /> + </div> + <commit-form /> + </template> </div> </resizable-panel> </template> diff --git a/app/assets/javascripts/ide/components/ide_status_bar.vue b/app/assets/javascripts/ide/components/ide_status_bar.vue index c13eeeace3f..70c6d53c3ab 100644 --- a/app/assets/javascripts/ide/components/ide_status_bar.vue +++ b/app/assets/javascripts/ide/components/ide_status_bar.vue @@ -1,11 +1,14 @@ <script> +import { mapGetters } from 'vuex'; import icon from '~/vue_shared/components/icon.vue'; import tooltip from '~/vue_shared/directives/tooltip'; import timeAgoMixin from '~/vue_shared/mixins/timeago'; +import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue'; export default { components: { icon, + userAvatarImage, }, directives: { tooltip, @@ -14,40 +17,93 @@ export default { props: { file: { type: Object, - required: true, + required: false, + default: null, + }, + }, + data() { + return { + lastCommitFormatedAge: null, + }; + }, + computed: { + ...mapGetters(['currentProject', 'lastCommit']), + }, + mounted() { + this.startTimer(); + }, + beforeDestroy() { + if (this.intervalId) { + clearInterval(this.intervalId); + } + }, + methods: { + startTimer() { + this.intervalId = setInterval(() => { + this.commitAgeUpdate(); + }, 1000); + }, + commitAgeUpdate() { + if (this.lastCommit) { + this.lastCommitFormatedAge = this.timeFormated(this.lastCommit.committed_date); + } + }, + getCommitPath(shortSha) { + return `${this.currentProject.web_url}/commit/${shortSha}`; }, }, }; </script> <template> - <div class="ide-status-bar"> - <div> - <div v-if="file.lastCommit && file.lastCommit.id"> - Last commit: - <a - v-tooltip - :title="file.lastCommit.message" - :href="file.lastCommit.url" - > - {{ timeFormated(file.lastCommit.updatedAt) }} by - {{ file.lastCommit.author }} - </a> - </div> + <footer class="ide-status-bar"> + <div + class="ide-status-branch" + v-if="lastCommit && lastCommitFormatedAge" + > + <icon + name="commit" + /> + <a + v-tooltip + class="commit-sha" + :title="lastCommit.message" + :href="getCommitPath(lastCommit.short_id)" + >{{ lastCommit.short_id }}</a> + by + {{ lastCommit.author_name }} + <time + v-tooltip + data-placement="top" + data-container="body" + :datetime="lastCommit.committed_date" + :title="tooltipTitle(lastCommit.committed_date)" + > + {{ lastCommitFormatedAge }} + </time> </div> - <div class="text-right"> + <div + v-if="file" + class="ide-status-file" + > {{ file.name }} </div> - <div class="text-right"> + <div + v-if="file" + class="ide-status-file" + > {{ file.eol }} </div> <div - class="text-right" - v-if="!file.binary"> + class="ide-status-file" + v-if="file && !file.binary"> {{ file.editorRow }}:{{ file.editorColumn }} </div> - <div class="text-right"> + <div + v-if="file" + class="ide-status-file" + > {{ file.fileLanguage }} </div> - </div> + </footer> </template> diff --git a/app/assets/javascripts/ide/components/ide_tree.vue b/app/assets/javascripts/ide/components/ide_tree.vue new file mode 100644 index 00000000000..8fc4ebe6ca6 --- /dev/null +++ b/app/assets/javascripts/ide/components/ide_tree.vue @@ -0,0 +1,42 @@ +<script> +import { mapState, mapGetters, mapActions } from 'vuex'; +import NewDropdown from './new_dropdown/index.vue'; +import IdeTreeList from './ide_tree_list.vue'; + +export default { + components: { + NewDropdown, + IdeTreeList, + }, + computed: { + ...mapState(['currentBranchId']), + ...mapGetters(['currentProject', 'currentTree', 'activeFile']), + }, + mounted() { + if (this.activeFile && this.activeFile.pending) { + this.$router.push(`/project${this.activeFile.url}`, () => { + this.updateViewer('editor'); + }); + } + }, + methods: { + ...mapActions(['updateViewer']), + }, +}; +</script> + +<template> + <ide-tree-list + viewer-type="editor" + > + <template + slot="header" + > + {{ __('Edit') }} + <new-dropdown + :project-id="currentProject.name_with_namespace" + :branch="currentBranchId" + /> + </template> + </ide-tree-list> +</template> diff --git a/app/assets/javascripts/ide/components/ide_tree_list.vue b/app/assets/javascripts/ide/components/ide_tree_list.vue new file mode 100644 index 00000000000..e64a09fcc90 --- /dev/null +++ b/app/assets/javascripts/ide/components/ide_tree_list.vue @@ -0,0 +1,76 @@ +<script> +import { mapActions, mapGetters, mapState } from 'vuex'; +import Icon from '~/vue_shared/components/icon.vue'; +import SkeletonLoadingContainer from '~/vue_shared/components/skeleton_loading_container.vue'; +import RepoFile from './repo_file.vue'; +import NewDropdown from './new_dropdown/index.vue'; + +export default { + components: { + Icon, + RepoFile, + SkeletonLoadingContainer, + NewDropdown, + }, + props: { + viewerType: { + type: String, + required: true, + }, + headerClass: { + type: String, + required: false, + default: null, + }, + disableActionDropdown: { + type: Boolean, + required: false, + default: false, + }, + }, + computed: { + ...mapState(['currentBranchId']), + ...mapGetters(['currentProject', 'currentTree']), + showLoading() { + return !this.currentTree || this.currentTree.loading; + }, + }, + mounted() { + this.updateViewer(this.viewerType); + }, + methods: { + ...mapActions(['updateViewer']), + }, +}; +</script> + +<template> + <div + class="ide-file-list" + > + <template v-if="showLoading"> + <div + class="multi-file-loading-container" + v-for="n in 3" + :key="n" + > + <skeleton-loading-container /> + </div> + </template> + <template v-else> + <header + class="ide-tree-header" + :class="headerClass" + > + <slot name="header"></slot> + </header> + <repo-file + v-for="file in currentTree.tree" + :key="file.key" + :file="file" + :level="0" + :disable-action-dropdown="disableActionDropdown" + /> + </template> + </div> +</template> diff --git a/app/assets/javascripts/ide/components/mr_file_icon.vue b/app/assets/javascripts/ide/components/mr_file_icon.vue index 8a440902dfc..179a589d1ac 100644 --- a/app/assets/javascripts/ide/components/mr_file_icon.vue +++ b/app/assets/javascripts/ide/components/mr_file_icon.vue @@ -16,8 +16,8 @@ export default { <icon name="git-merge" v-tooltip - title="__('Part of merge request changes')" - css-classes="ide-file-changed-icon" + :title="__('Part of merge request changes')" + css-classes="append-right-8" :size="12" /> </template> diff --git a/app/assets/javascripts/ide/components/new_dropdown/index.vue b/app/assets/javascripts/ide/components/new_dropdown/index.vue index 769e9b79cad..a0ce1c9dac7 100644 --- a/app/assets/javascripts/ide/components/new_dropdown/index.vue +++ b/app/assets/javascripts/ide/components/new_dropdown/index.vue @@ -1,49 +1,55 @@ <script> - import { mapActions } from 'vuex'; - import icon from '~/vue_shared/components/icon.vue'; - import newModal from './modal.vue'; - import upload from './upload.vue'; +import { mapActions } from 'vuex'; +import icon from '~/vue_shared/components/icon.vue'; +import newModal from './modal.vue'; +import upload from './upload.vue'; - export default { - components: { - icon, - newModal, - upload, +export default { + components: { + icon, + newModal, + upload, + }, + props: { + branch: { + type: String, + required: true, }, - props: { - branch: { - type: String, - required: true, - }, - path: { - type: String, - required: true, - }, + path: { + type: String, + required: false, + default: '', }, - data() { - return { - openModal: false, - modalType: '', - dropdownOpen: false, - }; + }, + data() { + return { + openModal: false, + modalType: '', + dropdownOpen: false, + }; + }, + watch: { + dropdownOpen() { + this.$nextTick(() => { + this.$refs.dropdownMenu.scrollIntoView(); + }); }, - methods: { - ...mapActions([ - 'createTempEntry', - ]), - createNewItem(type) { - this.modalType = type; - this.openModal = true; - this.dropdownOpen = false; - }, - hideModal() { - this.openModal = false; - }, - openDropdown() { - this.dropdownOpen = !this.dropdownOpen; - }, + }, + methods: { + ...mapActions(['createTempEntry']), + createNewItem(type) { + this.modalType = type; + this.openModal = true; + this.dropdownOpen = false; }, - }; + hideModal() { + this.openModal = false; + }, + openDropdown() { + this.dropdownOpen = !this.dropdownOpen; + }, + }, +}; </script> <template> @@ -71,7 +77,10 @@ css-classes="pull-left" /> </button> - <ul class="dropdown-menu dropdown-menu-right"> + <ul + class="dropdown-menu dropdown-menu-right" + ref="dropdownMenu" + > <li> <a href="#" diff --git a/app/assets/javascripts/ide/components/new_dropdown/modal.vue b/app/assets/javascripts/ide/components/new_dropdown/modal.vue index 4b5a50785b6..a95a0225950 100644 --- a/app/assets/javascripts/ide/components/new_dropdown/modal.vue +++ b/app/assets/javascripts/ide/components/new_dropdown/modal.vue @@ -40,13 +40,6 @@ export default { return __('Create file'); }, - formLabelName() { - if (this.type === 'tree') { - return __('Directory name'); - } - - return __('File name'); - }, }, mounted() { this.$refs.fieldName.focus(); @@ -82,8 +75,8 @@ export default { @submit.prevent="createEntryInStore" > <fieldset class="form-group append-bottom-0"> - <label class="label-light col-sm-3"> - {{ formLabelName }} + <label class="label-light col-sm-3 ide-new-modal-label"> + {{ __('Name') }} </label> <div class="col-sm-9"> <input diff --git a/app/assets/javascripts/ide/components/repo_commit_section.vue b/app/assets/javascripts/ide/components/repo_commit_section.vue index 877d1b5e026..c5092d8e04d 100644 --- a/app/assets/javascripts/ide/components/repo_commit_section.vue +++ b/app/assets/javascripts/ide/components/repo_commit_section.vue @@ -3,12 +3,10 @@ import { mapState, mapActions, mapGetters } from 'vuex'; import tooltip from '~/vue_shared/directives/tooltip'; import Icon from '~/vue_shared/components/icon.vue'; import DeprecatedModal from '~/vue_shared/components/deprecated_modal.vue'; -import LoadingButton from '~/vue_shared/components/loading_button.vue'; import CommitFilesList from './commit_sidebar/list.vue'; import EmptyState from './commit_sidebar/empty_state.vue'; -import CommitMessageField from './commit_sidebar/message_field.vue'; import * as consts from '../stores/modules/commit/constants'; -import Actions from './commit_sidebar/actions.vue'; +import { activityBarViews } from '../constants'; export default { components: { @@ -16,35 +14,50 @@ export default { Icon, CommitFilesList, EmptyState, - Actions, - LoadingButton, - CommitMessageField, }, directives: { tooltip, }, - props: { - noChangesStateSvgPath: { - type: String, - required: true, + computed: { + ...mapState([ + 'changedFiles', + 'stagedFiles', + 'rightPanelCollapsed', + 'lastCommitMsg', + 'unusedSeal', + ]), + ...mapState('commit', ['commitMessage', 'submitCommitLoading']), + ...mapGetters(['lastOpenedFile', 'hasChanges', 'someUncommitedChanges']), + ...mapGetters('commit', ['commitButtonDisabled', 'discardDraftButtonDisabled']), + showStageUnstageArea() { + return !!(this.someUncommitedChanges || this.lastCommitMsg || !this.unusedSeal); }, - committedStateSvgPath: { - type: String, - required: true, + }, + watch: { + hasChanges() { + if (!this.hasChanges) { + this.updateActivityBarView(activityBarViews.edit); + } }, }, - computed: { - ...mapState(['changedFiles', 'stagedFiles', 'rightPanelCollapsed']), - ...mapState('commit', ['commitMessage', 'submitCommitLoading']), - ...mapGetters('commit', ['commitButtonDisabled', 'discardDraftButtonDisabled', 'branchName']), + mounted() { + if (this.lastOpenedFile) { + this.openPendingTab({ + file: this.lastOpenedFile, + }) + .then(changeViewer => { + if (changeViewer) { + this.updateViewer('diff'); + } + }) + .catch(e => { + throw e; + }); + } }, methods: { - ...mapActions('commit', [ - 'updateCommitMessage', - 'discardDraft', - 'commitChanges', - 'updateCommitAction', - ]), + ...mapActions(['openPendingTab', 'updateViewer', 'updateActivityBarView']), + ...mapActions('commit', ['commitChanges', 'updateCommitAction']), forceCreateNewBranch() { return this.updateCommitAction(consts.COMMIT_TO_NEW_BRANCH).then(() => this.commitChanges()); }, @@ -69,9 +82,10 @@ export default { </template> </deprecated-modal> <template - v-if="changedFiles.length || stagedFiles.length" + v-if="showStageUnstageArea" > <commit-files-list + class="is-first" icon-name="unstaged" :title="__('Unstaged')" :file-list="changedFiles" @@ -86,42 +100,11 @@ export default { action="unstageAllChanges" :action-btn-text="__('Unstage all')" item-action-component="unstage-button" - :show-toggle="false" :staged-list="true" /> - <form - class="form-horizontal multi-file-commit-form" - @submit.prevent.stop="commitChanges" - v-if="!rightPanelCollapsed" - > - <commit-message-field - :text="commitMessage" - @input="updateCommitMessage" - /> - <div class="clearfix prepend-top-15"> - <actions /> - <loading-button - :loading="submitCommitLoading" - :disabled="commitButtonDisabled" - container-class="btn btn-success btn-sm pull-left" - :label="__('Commit')" - @click="commitChanges" - /> - <button - v-if="!discardDraftButtonDisabled" - type="button" - class="btn btn-default btn-sm pull-right" - @click="discardDraft" - > - {{ __('Discard draft') }} - </button> - </div> - </form> </template> <empty-state - v-else - :no-changes-state-svg-path="noChangesStateSvgPath" - :committed-state-svg-path="committedStateSvgPath" + v-if="unusedSeal" /> </div> </template> diff --git a/app/assets/javascripts/ide/components/repo_editor.vue b/app/assets/javascripts/ide/components/repo_editor.vue index 3a04cdd8e46..ff7e546fb9c 100644 --- a/app/assets/javascripts/ide/components/repo_editor.vue +++ b/app/assets/javascripts/ide/components/repo_editor.vue @@ -3,6 +3,7 @@ import { mapState, mapGetters, mapActions } from 'vuex'; import flash from '~/flash'; import ContentViewer from '~/vue_shared/components/content_viewer/content_viewer.vue'; +import { activityBarViews, viewerTypes } from '../constants'; import monacoLoader from '../monaco_loader'; import Editor from '../lib/editor'; import IdeFileButtons from './ide_file_buttons.vue'; @@ -19,8 +20,14 @@ export default { }, }, computed: { - ...mapState(['rightPanelCollapsed', 'viewer', 'delayViewerUpdated', 'panelResizing']), - ...mapGetters(['currentMergeRequest', 'getStagedFile']), + ...mapState(['rightPanelCollapsed', 'viewer', 'panelResizing', 'currentActivityView']), + ...mapGetters([ + 'currentMergeRequest', + 'getStagedFile', + 'isEditModeActive', + 'isCommitModeActive', + 'isReviewModeActive', + ]), shouldHideEditor() { return this.file && this.file.binary && !this.file.content; }, @@ -40,6 +47,21 @@ export default { // Compare key to allow for files opened in review mode to be cached differently if (newVal.key !== this.file.key) { this.initMonaco(); + + if (this.currentActivityView !== activityBarViews.edit) { + this.setFileViewMode({ + file: this.file, + viewMode: 'edit', + }); + } + } + }, + currentActivityView() { + if (this.currentActivityView !== activityBarViews.edit) { + this.setFileViewMode({ + file: this.file, + viewMode: 'edit', + }); } }, rightPanelCollapsed() { @@ -77,7 +99,6 @@ export default { 'setFileViewMode', 'setFileEOL', 'updateViewer', - 'updateDelayViewerUpdated', ]), initMonaco() { if (this.shouldHideEditor) return; @@ -89,14 +110,6 @@ export default { baseSha: this.currentMergeRequest ? this.currentMergeRequest.baseCommitSha : '', }) .then(() => { - const viewerPromise = this.delayViewerUpdated - ? this.updateViewer(this.file.pending ? 'diff' : 'editor') - : Promise.resolve(); - - return viewerPromise; - }) - .then(() => { - this.updateDelayViewerUpdated(false); this.createEditorInstance(); }) .catch(err => { @@ -108,10 +121,10 @@ export default { this.editor.dispose(); this.$nextTick(() => { - if (this.viewer === 'editor') { + if (this.viewer === viewerTypes.edit) { this.editor.createInstance(this.$refs.editor); } else { - this.editor.createDiffInstance(this.$refs.editor); + this.editor.createDiffInstance(this.$refs.editor, !this.isReviewModeActive); } this.setupEditor(); @@ -127,7 +140,7 @@ export default { this.file.staged && this.file.key.indexOf('unstaged-') === 0 ? head : null, ); - if (this.viewer === 'mrdiff') { + if (this.viewer === viewerTypes.mr) { this.editor.attachMergeRequestModel(this.model); } else { this.editor.attachModel(this.model); @@ -168,6 +181,7 @@ export default { }); }, }, + viewerTypes, }; </script> @@ -176,16 +190,17 @@ export default { id="ide" class="blob-viewer-container blob-editor-container" > - <div class="ide-mode-tabs clearfix"> + <div class="ide-mode-tabs clearfix" > <ul class="nav-links pull-left" - v-if="!shouldHideEditor"> + v-if="!shouldHideEditor && isEditModeActive" + > <li :class="editTabCSS"> <a href="javascript:void(0);" role="button" @click.prevent="setFileViewMode({ file, viewMode: 'edit' })"> - <template v-if="viewer === 'editor'"> + <template v-if="viewer === $options.viewerTypes.edit"> {{ __('Edit') }} </template> <template v-else> @@ -212,6 +227,9 @@ export default { v-show="!shouldHideEditor && file.viewMode === 'edit'" ref="editor" class="multi-file-editor-holder" + :class="{ + 'is-readonly': isCommitModeActive, + }" > </div> <content-viewer diff --git a/app/assets/javascripts/ide/components/repo_file.vue b/app/assets/javascripts/ide/components/repo_file.vue index 8b18c7d28b4..14946f8c9fa 100644 --- a/app/assets/javascripts/ide/components/repo_file.vue +++ b/app/assets/javascripts/ide/components/repo_file.vue @@ -1,22 +1,29 @@ <script> -import { mapActions } from 'vuex'; -import skeletonLoadingContainer from '~/vue_shared/components/skeleton_loading_container.vue'; -import fileIcon from '~/vue_shared/components/file_icon.vue'; +import { mapActions, mapGetters } from 'vuex'; +import { n__, __, sprintf } from '~/locale'; +import tooltip from '~/vue_shared/directives/tooltip'; +import SkeletonLoadingContainer from '~/vue_shared/components/skeleton_loading_container.vue'; +import Icon from '~/vue_shared/components/icon.vue'; +import FileIcon from '~/vue_shared/components/file_icon.vue'; import router from '../ide_router'; -import newDropdown from './new_dropdown/index.vue'; -import fileStatusIcon from './repo_file_status_icon.vue'; -import changedFileIcon from './changed_file_icon.vue'; -import mrFileIcon from './mr_file_icon.vue'; +import NewDropdown from './new_dropdown/index.vue'; +import FileStatusIcon from './repo_file_status_icon.vue'; +import ChangedFileIcon from './changed_file_icon.vue'; +import MrFileIcon from './mr_file_icon.vue'; export default { name: 'RepoFile', + directives: { + tooltip, + }, components: { - skeletonLoadingContainer, - newDropdown, - fileStatusIcon, - fileIcon, - changedFileIcon, - mrFileIcon, + SkeletonLoadingContainer, + NewDropdown, + FileStatusIcon, + FileIcon, + ChangedFileIcon, + MrFileIcon, + Icon, }, props: { file: { @@ -27,8 +34,41 @@ export default { type: Number, required: true, }, + disableActionDropdown: { + type: Boolean, + required: false, + default: false, + }, }, computed: { + ...mapGetters([ + 'getChangesInFolder', + 'getUnstagedFilesCountForPath', + 'getStagedFilesCountForPath', + ]), + folderUnstagedCount() { + return this.getUnstagedFilesCountForPath(this.file.path); + }, + folderStagedCount() { + return this.getStagedFilesCountForPath(this.file.path); + }, + changesCount() { + return this.getChangesInFolder(this.file.path); + }, + folderChangesTooltip() { + if (this.changesCount === 0) return undefined; + + if (this.folderUnstagedCount > 0 && this.folderStagedCount === 0) { + return n__('%d unstaged change', '%d unstaged changes', this.folderUnstagedCount); + } else if (this.folderUnstagedCount === 0 && this.folderStagedCount > 0) { + return n__('%d staged change', '%d staged changes', this.folderStagedCount); + } + + return sprintf(__('%{unstaged} unstaged and %{staged} staged changes'), { + unstaged: this.folderUnstagedCount, + staged: this.folderStagedCount, + }); + }, isTree() { return this.file.type === 'tree'; }, @@ -48,23 +88,30 @@ export default { 'is-open': this.file.opened, }; }, + showTreeChangesCount() { + return this.isTree && this.changesCount > 0 && !this.file.opened; + }, + showChangedFileIcon() { + return this.file.changed || this.file.tempFile || this.file.staged; + }, }, updated() { if (this.file.type === 'blob' && this.file.active) { - this.$el.scrollIntoView(); + this.$el.scrollIntoView({ + behavior: 'smooth', + block: 'nearest', + }); } }, methods: { - ...mapActions(['toggleTreeOpen', 'updateDelayViewerUpdated']), + ...mapActions(['toggleTreeOpen']), clickFile() { // Manual Action if a tree is selected/opened if (this.isTree && this.$router.currentRoute.path === `/project${this.file.url}`) { this.toggleTreeOpen(this.file.path); } - return this.updateDelayViewerUpdated(true).then(() => { - router.push(`/project${this.file.url}`); - }); + router.push(`/project${this.file.url}`); }, }, }; @@ -97,20 +144,36 @@ export default { :file="file" /> </span> - <span class="pull-right"> + <span class="pull-right ide-file-icon-holder"> <mr-file-icon v-if="file.mrChange" /> + <span + v-if="showTreeChangesCount" + class="ide-tree-changes" + > + {{ changesCount }} + <icon + v-tooltip + :title="folderChangesTooltip" + data-container="body" + data-placement="right" + name="file-modified" + :size="12" + css-classes="prepend-left-5 multi-file-modified" + /> + </span> <changed-file-icon - v-if="file.changed || file.tempFile || file.staged" + v-else-if="showChangedFileIcon" :file="file" :show-tooltip="true" :show-staged-icon="true" - class="prepend-top-5 pull-right" + :force-modified-icon="true" + class="pull-right" /> </span> <new-dropdown - v-if="isTree" + v-if="isTree && !disableActionDropdown" :project-id="file.projectId" :branch="file.branchId" :path="file.path" diff --git a/app/assets/javascripts/ide/components/repo_tab.vue b/app/assets/javascripts/ide/components/repo_tab.vue index 35a362b01e0..fb26b973236 100644 --- a/app/assets/javascripts/ide/components/repo_tab.vue +++ b/app/assets/javascripts/ide/components/repo_tab.vue @@ -32,6 +32,8 @@ export default { return `Close ${this.tab.name}`; }, showChangedIcon() { + if (this.tab.pending) return true; + return this.fileHasChanged ? !this.tabMouseOver : false; }, fileHasChanged() { @@ -66,15 +68,32 @@ export default { <template> <li + :class="{ + active: tab.active + }" @click="clickFile(tab)" @mouseover="mouseOverTab" @mouseout="mouseOutTab" > + <div + class="multi-file-tab" + :title="tab.url" + > + <file-icon + :file-name="tab.name" + :size="16" + /> + {{ tab.name }} + <file-status-icon + :file="tab" + /> + </div> <button type="button" class="multi-file-tab-close" @click.stop.prevent="closeFile(tab)" :aria-label="closeLabel" + :disabled="tab.pending" > <icon v-if="!showChangedIcon" @@ -84,24 +103,8 @@ export default { <changed-file-icon v-else :file="tab" + :force-modified-icon="true" /> </button> - - <div - class="multi-file-tab" - :class="{ - active: tab.active - }" - :title="tab.url" - > - <file-icon - :file-name="tab.name" - :size="16" - /> - {{ tab.name }} - <file-status-icon - :file="tab" - /> - </div> </li> </template> diff --git a/app/assets/javascripts/ide/components/repo_tabs.vue b/app/assets/javascripts/ide/components/repo_tabs.vue index 7bd646ba9b0..99e51097e12 100644 --- a/app/assets/javascripts/ide/components/repo_tabs.vue +++ b/app/assets/javascripts/ide/components/repo_tabs.vue @@ -32,16 +32,6 @@ export default { default: '', }, }, - data() { - return { - showShadow: false, - }; - }, - updated() { - if (!this.$refs.tabsScroller) return; - - this.showShadow = this.$refs.tabsScroller.scrollWidth > this.$refs.tabsScroller.offsetWidth; - }, methods: { ...mapActions(['updateViewer', 'removePendingTab']), openFileViewer(viewer) { @@ -71,12 +61,5 @@ export default { :tab="tab" /> </ul> - <editor-mode - :viewer="viewer" - :show-shadow="showShadow" - :has-changes="hasChanges" - :merge-request-id="mergeRequestId" - @click="openFileViewer" - /> </div> </template> diff --git a/app/assets/javascripts/ide/constants.js b/app/assets/javascripts/ide/constants.js index b60d042e0be..48d4cc43198 100644 --- a/app/assets/javascripts/ide/constants.js +++ b/app/assets/javascripts/ide/constants.js @@ -1,3 +1,24 @@ // Fuzzy file finder +export const MAX_FILE_FINDER_RESULTS = 40; +export const FILE_FINDER_ROW_HEIGHT = 55; +export const FILE_FINDER_EMPTY_ROW_HEIGHT = 33; + +export const MAX_WINDOW_HEIGHT_COMPACT = 750; + +export const COMMIT_ITEM_PADDING = 32; + +// Commit message textarea export const MAX_TITLE_LENGTH = 50; export const MAX_BODY_LENGTH = 72; + +export const activityBarViews = { + edit: 'ide-tree', + commit: 'commit-section', + review: 'ide-review', +}; + +export const viewerTypes = { + mr: 'mrdiff', + edit: 'editor', + diff: 'diff', +}; diff --git a/app/assets/javascripts/ide/ide_router.js b/app/assets/javascripts/ide/ide_router.js index 4a0a303d5a6..adca85dc65b 100644 --- a/app/assets/javascripts/ide/ide_router.js +++ b/app/assets/javascripts/ide/ide_router.js @@ -2,6 +2,7 @@ import Vue from 'vue'; import VueRouter from 'vue-router'; import flash from '~/flash'; import store from './stores'; +import { activityBarViews } from './constants'; Vue.use(VueRouter); @@ -63,6 +64,8 @@ router.beforeEach((to, from, next) => { const fullProjectId = `${to.params.namespace}/${to.params.project}`; if (to.params.branch) { + store.dispatch('setCurrentBranchId', to.params.branch); + store.dispatch('getBranchData', { projectId: fullProjectId, branchId: to.params.branch, @@ -99,14 +102,14 @@ router.beforeEach((to, from, next) => { throw e; }); } else if (to.params.mrid) { - store.dispatch('updateViewer', 'mrdiff'); - store .dispatch('getMergeRequestData', { projectId: fullProjectId, mergeRequestId: to.params.mrid, }) .then(mr => { + store.dispatch('updateActivityBarView', activityBarViews.review); + store.dispatch('getBranchData', { projectId: fullProjectId, branchId: mr.source_branch, diff --git a/app/assets/javascripts/ide/index.js b/app/assets/javascripts/ide/index.js index cbfb3dc54f2..c5835cd3b06 100644 --- a/app/assets/javascripts/ide/index.js +++ b/app/assets/javascripts/ide/index.js @@ -4,7 +4,9 @@ import ide from './components/ide.vue'; import store from './stores'; import router from './ide_router'; -function initIde(el) { +Vue.use(Translate); + +export function initIde(el) { if (!el) return null; return new Vue({ @@ -14,20 +16,25 @@ function initIde(el) { components: { ide, }, - render(createElement) { - return createElement('ide', { - props: { - emptyStateSvgPath: el.dataset.emptyStateSvgPath, - noChangesStateSvgPath: el.dataset.noChangesStateSvgPath, - committedStateSvgPath: el.dataset.committedStateSvgPath, - }, + created() { + this.$store.dispatch('setEmptyStateSvgs', { + emptyStateSvgPath: el.dataset.emptyStateSvgPath, + noChangesStateSvgPath: el.dataset.noChangesStateSvgPath, + committedStateSvgPath: el.dataset.committedStateSvgPath, }); }, + render(createElement) { + return createElement('ide'); + }, }); } -const ideElement = document.getElementById('ide'); - -Vue.use(Translate); - -initIde(ideElement); +// tell webpack to load assets from origin so that web workers don't break +export function resetServiceWorkersPublicPath() { + // __webpack_public_path__ is a global variable that can be used to adjust + // the webpack publicPath setting at runtime. + // see: https://webpack.js.org/guides/public-path/ + const relativeRootPath = (gon && gon.relative_url_root) || ''; + const webpackAssetPath = `${relativeRootPath}/assets/webpack/`; + __webpack_public_path__ = webpackAssetPath; // eslint-disable-line camelcase +} diff --git a/app/assets/javascripts/ide/lib/editor.js b/app/assets/javascripts/ide/lib/editor.js index 2d3ee7d4f48..9c3bb9cc17d 100644 --- a/app/assets/javascripts/ide/lib/editor.js +++ b/app/assets/javascripts/ide/lib/editor.js @@ -1,10 +1,12 @@ import _ from 'underscore'; +import store from '../stores'; import DecorationsController from './decorations/controller'; import DirtyDiffController from './diff/controller'; import Disposable from './common/disposable'; import ModelManager from './common/model_manager'; import editorOptions, { defaultEditorOptions } from './editor_options'; import gitlabTheme from './themes/gl_theme'; +import keymap from './keymap.json'; export const clearDomElement = el => { if (!el || !el.firstChild) return; @@ -53,26 +55,30 @@ export default class Editor { )), ); + this.addCommands(); + window.addEventListener('resize', this.debouncedUpdate, false); } } - createDiffInstance(domElement) { + createDiffInstance(domElement, readOnly = true) { if (!this.instance) { clearDomElement(domElement); this.disposable.add( (this.instance = this.monaco.editor.createDiffEditor(domElement, { ...defaultEditorOptions, - readOnly: true, quickSuggestions: false, occurrencesHighlight: false, - renderLineHighlight: 'none', - hideCursorInOverviewRuler: true, renderSideBySide: Editor.renderSideBySide(domElement), + readOnly, + renderLineHighlight: readOnly ? 'all' : 'none', + hideCursorInOverviewRuler: !readOnly, })), ); + this.addCommands(); + window.addEventListener('resize', this.debouncedUpdate, false); } } @@ -189,4 +195,31 @@ export default class Editor { static renderSideBySide(domElement) { return domElement.offsetWidth >= 700; } + + addCommands() { + const getKeyCode = key => { + const monacoKeyMod = key.indexOf('KEY_') === 0; + + return monacoKeyMod ? this.monaco.KeyCode[key] : this.monaco.KeyMod[key]; + }; + + keymap.forEach(command => { + const keybindings = command.bindings.map(binding => { + const keys = binding.split('+'); + + // eslint-disable-next-line no-bitwise + return keys.length > 1 ? getKeyCode(keys[0]) | getKeyCode(keys[1]) : getKeyCode(keys[0]); + }); + + this.instance.addAction({ + id: command.id, + label: command.label, + keybindings, + run() { + store.dispatch(command.action.name, command.action.params); + return null; + }, + }); + }); + } } diff --git a/app/assets/javascripts/ide/lib/keymap.json b/app/assets/javascripts/ide/lib/keymap.json new file mode 100644 index 00000000000..131abfebbed --- /dev/null +++ b/app/assets/javascripts/ide/lib/keymap.json @@ -0,0 +1,11 @@ +[ + { + "id": "file-finder", + "label": "File finder", + "bindings": ["CtrlCmd+KEY_P"], + "action": { + "name": "toggleFileFinder", + "params": true + } + } +] diff --git a/app/assets/javascripts/ide/stores/actions.js b/app/assets/javascripts/ide/stores/actions.js index cecb4d215ba..1a98b42761e 100644 --- a/app/assets/javascripts/ide/stores/actions.js +++ b/app/assets/javascripts/ide/stores/actions.js @@ -33,10 +33,7 @@ export const setPanelCollapsedStatus = ({ commit }, { side, collapsed }) => { } }; -export const toggleRightPanelCollapsed = ( - { dispatch, state }, - e = undefined, -) => { +export const toggleRightPanelCollapsed = ({ dispatch, state }, e = undefined) => { if (e) { $(e.currentTarget) .tooltip('hide') @@ -77,7 +74,7 @@ export const createTempEntry = ( } worker.addEventListener('message', ({ data }) => { - const { file } = data; + const { file, parentPath } = data; worker.terminate(); @@ -93,6 +90,10 @@ export const createTempEntry = ( dispatch('setFileActive', file.path); } + if (parentPath && !state.entries[parentPath].opened) { + commit(types.TOGGLE_TREE_OPEN, parentPath); + } + resolve(file); }); @@ -122,6 +123,8 @@ export const scrollToTab = () => { }; export const stageAllChanges = ({ state, commit }) => { + commit(types.SET_LAST_COMMIT_MSG, ''); + state.changedFiles.forEach(file => commit(types.STAGE_CHANGE, file.path)); }; @@ -137,7 +140,39 @@ export const updateDelayViewerUpdated = ({ commit }, delay) => { commit(types.UPDATE_DELAY_VIEWER_CHANGE, delay); }; +export const updateActivityBarView = ({ commit }, view) => { + commit(types.UPDATE_ACTIVITY_BAR_VIEW, view); +}; + +export const setEmptyStateSvgs = ({ commit }, svgs) => { + commit(types.SET_EMPTY_STATE_SVGS, svgs); +}; + +export const setCurrentBranchId = ({ commit }, currentBranchId) => { + commit(types.SET_CURRENT_BRANCH, currentBranchId); +}; + +export const updateTempFlagForEntry = ({ commit, dispatch, state }, { file, tempFile }) => { + commit(types.UPDATE_TEMP_FLAG, { path: file.path, tempFile }); + + if (file.parentPath) { + dispatch('updateTempFlagForEntry', { file: state.entries[file.parentPath], tempFile }); + } +}; + +export const toggleFileFinder = ({ commit }, fileFindVisible) => + commit(types.TOGGLE_FILE_FINDER, fileFindVisible); + +export const burstUnusedSeal = ({ state, commit }) => { + if (state.unusedSeal) { + commit(types.BURST_UNUSED_SEAL); + } +}; + export * from './actions/tree'; export * from './actions/file'; export * from './actions/project'; export * from './actions/merge_request'; + +// prevent babel-plugin-rewire from generating an invalid default during karma tests +export default () => {}; diff --git a/app/assets/javascripts/ide/stores/actions/file.js b/app/assets/javascripts/ide/stores/actions/file.js index d782e0a84d2..3ac9b9222ca 100644 --- a/app/assets/javascripts/ide/stores/actions/file.js +++ b/app/assets/javascripts/ide/stores/actions/file.js @@ -5,6 +5,7 @@ import service from '../../services'; import * as types from '../mutation_types'; import router from '../../ide_router'; import { setPageTitle } from '../utils'; +import { viewerTypes } from '../../constants'; export const closeFile = ({ commit, state, dispatch }, file) => { const path = file.path; @@ -23,13 +24,12 @@ export const closeFile = ({ commit, state, dispatch }, file) => { const nextFileToOpen = state.openFiles[nextIndexToOpen]; if (nextFileToOpen.pending) { - dispatch('updateViewer', 'diff'); + dispatch('updateViewer', viewerTypes.diff); dispatch('openPendingTab', { file: nextFileToOpen, keyPrefix: nextFileToOpen.staged ? 'staged' : 'unstaged', }); } else { - dispatch('updateDelayViewerUpdated', true); router.push(`/project${nextFileToOpen.url}`); } } else if (!state.openFiles.length) { @@ -63,7 +63,7 @@ export const getFileData = ({ state, commit, dispatch }, { path, makeFileActive const file = state.entries[path]; commit(types.TOGGLE_LOADING, { entry: file }); return service - .getFileData(file.url) + .getFileData(`${gon.relative_url_root ? gon.relative_url_root : ''}${file.url}`) .then(res => { const pageTitle = decodeURI(normalizeHeaders(res.headers)['PAGE-TITLE']); setPageTitle(pageTitle); @@ -117,7 +117,7 @@ export const getRawFileData = ({ state, commit, dispatch }, { path, baseSha }) = }); }; -export const changeFileContent = ({ state, commit }, { path, content }) => { +export const changeFileContent = ({ commit, dispatch, state }, { path, content }) => { const file = state.entries[path]; commit(types.UPDATE_FILE_CONTENT, { path, content }); @@ -128,6 +128,8 @@ export const changeFileContent = ({ state, commit }, { path, content }) => { } else if (!file.changed && indexOfChangedFile !== -1) { commit(types.REMOVE_FILE_FROM_CHANGED, path); } + + dispatch('burstUnusedSeal', {}, { root: true }); }; export const setFileLanguage = ({ getters, commit }, { fileLanguage }) => { @@ -182,6 +184,7 @@ export const stageChange = ({ commit, state }, path) => { const stagedFile = state.stagedFiles.find(f => f.path === path); commit(types.STAGE_CHANGE, path); + commit(types.SET_LAST_COMMIT_MSG, ''); if (stagedFile) { eventHub.$emit(`editor.update.model.new.content.staged-${stagedFile.key}`, stagedFile.content); @@ -193,9 +196,7 @@ export const unstageChange = ({ commit }, path) => { }; export const openPendingTab = ({ commit, getters, dispatch, state }, { file, keyPrefix }) => { - if (getters.activeFile && getters.activeFile === file && state.viewer === 'diff') { - return false; - } + state.openFiles.forEach(f => eventHub.$emit(`editor.update.model.dispose.${f.key}`)); commit(types.ADD_PENDING_TAB, { file, keyPrefix }); diff --git a/app/assets/javascripts/ide/stores/actions/project.js b/app/assets/javascripts/ide/stores/actions/project.js index 4eb23b2ee0f..eff9bc03651 100644 --- a/app/assets/javascripts/ide/stores/actions/project.js +++ b/app/assets/javascripts/ide/stores/actions/project.js @@ -55,7 +55,6 @@ export const getBranchData = ( branch: data, }); commit(types.SET_BRANCH_WORKING_REFERENCE, { projectId, branchId, reference: id }); - commit(types.SET_CURRENT_BRANCH, branchId); resolve(data); }) .catch(() => { @@ -73,3 +72,26 @@ export const getBranchData = ( resolve(state.projects[`${projectId}`].branches[branchId]); } }); + +export const refreshLastCommitData = ( + { commit, state, dispatch }, + { projectId, branchId } = {}, +) => service + .getBranchData(projectId, branchId) + .then(({ data }) => { + commit(types.SET_BRANCH_COMMIT, { + projectId, + branchId, + commit: data.commit, + }); + }) + .catch(() => { + flash( + 'Error loading last commit.', + 'alert', + document, + null, + false, + true, + ); + }); diff --git a/app/assets/javascripts/ide/stores/getters.js b/app/assets/javascripts/ide/stores/getters.js index 8518d2f6f06..b239a605371 100644 --- a/app/assets/javascripts/ide/stores/getters.js +++ b/app/assets/javascripts/ide/stores/getters.js @@ -1,4 +1,5 @@ -import { __ } from '~/locale'; +import { getChangesCountForFiles, filePathMatches } from './utils'; +import { activityBarViews } from '../constants'; export const activeFile = state => state.openFiles.find(file => file.active) || null; @@ -30,16 +31,61 @@ export const currentMergeRequest = state => { return null; }; -// eslint-disable-next-line no-confusing-arrow -export const collapseButtonIcon = state => - state.rightPanelCollapsed ? 'angle-double-left' : 'angle-double-right'; +export const currentProject = state => state.projects[state.currentProjectId]; -export const hasChanges = state => !!state.changedFiles.length || !!state.stagedFiles.length; +export const currentTree = state => + state.trees[`${state.currentProjectId}/${state.currentBranchId}`]; -// eslint-disable-next-line no-confusing-arrow -export const collapseButtonTooltip = state => - state.rightPanelCollapsed ? __('Expand sidebar') : __('Collapse sidebar'); +export const hasChanges = state => !!state.changedFiles.length || !!state.stagedFiles.length; export const hasMergeRequest = state => !!state.currentMergeRequestId; +export const allBlobs = state => + Object.keys(state.entries) + .reduce((acc, key) => { + const entry = state.entries[key]; + + if (entry.type === 'blob') { + acc.push(entry); + } + + return acc; + }, []) + .sort((a, b) => b.lastOpenedAt - a.lastOpenedAt); + +export const getChangedFile = state => path => state.changedFiles.find(f => f.path === path); export const getStagedFile = state => path => state.stagedFiles.find(f => f.path === path); + +export const lastOpenedFile = state => + [...state.changedFiles, ...state.stagedFiles].sort((a, b) => b.lastOpenedAt - a.lastOpenedAt)[0]; + +export const isEditModeActive = state => state.currentActivityView === activityBarViews.edit; +export const isCommitModeActive = state => state.currentActivityView === activityBarViews.commit; +export const isReviewModeActive = state => state.currentActivityView === activityBarViews.review; + +export const someUncommitedChanges = state => + !!(state.changedFiles.length || state.stagedFiles.length); + +export const getChangesInFolder = state => path => { + const changedFilesCount = state.changedFiles.filter(f => filePathMatches(f, path)).length; + const stagedFilesCount = state.stagedFiles.filter( + f => filePathMatches(f, path) && !getChangedFile(state)(f.path), + ).length; + + return changedFilesCount + stagedFilesCount; +}; + +export const getUnstagedFilesCountForPath = state => path => + getChangesCountForFiles(state.changedFiles, path); + +export const getStagedFilesCountForPath = state => path => + getChangesCountForFiles(state.stagedFiles, path); + +export const lastCommit = (state, getters) => { + const branch = getters.currentProject && getters.currentProject.branches[state.currentBranchId]; + + return branch ? branch.commit : null; +}; + +// prevent babel-plugin-rewire from generating an invalid default during karma tests +export default () => {}; diff --git a/app/assets/javascripts/ide/stores/modules/commit/actions.js b/app/assets/javascripts/ide/stores/modules/commit/actions.js index b26512e213a..b85246b2502 100644 --- a/app/assets/javascripts/ide/stores/modules/commit/actions.js +++ b/app/assets/javascripts/ide/stores/modules/commit/actions.js @@ -8,6 +8,7 @@ import router from '../../../ide_router'; import service from '../../../services'; import * as types from './mutation_types'; import * as consts from './constants'; +import { activityBarViews } from '../../../constants'; import eventHub from '../../../eventhub'; export const updateCommitMessage = ({ commit }, message) => { @@ -75,7 +76,7 @@ export const checkCommitStatus = ({ rootState }) => export const updateFilesAfterCommit = ( { commit, dispatch, state, rootState, rootGetters }, - { data, branch }, + { data }, ) => { const selectedProject = rootState.projects[rootState.currentProjectId]; const lastCommit = { @@ -110,20 +111,25 @@ export const updateFilesAfterCommit = ( { root: true }, ); + commit( + rootTypes.TOGGLE_FILE_CHANGED, + { + file, + changed: false, + }, + { root: true }, + ); + + dispatch('updateTempFlagForEntry', { file, tempFile: false }, { root: true }); + eventHub.$emit(`editor.update.model.content.${file.key}`, { content: file.content, changed: !!changedFile, }); }); - - if (state.commitAction === consts.COMMIT_TO_NEW_BRANCH && rootGetters.activeFile) { - router.push( - `/project/${rootState.currentProjectId}/blob/${branch}/${rootGetters.activeFile.path}`, - ); - } }; -export const commitChanges = ({ commit, state, getters, dispatch, rootState }) => { +export const commitChanges = ({ commit, state, getters, dispatch, rootState, rootGetters }) => { const newBranch = state.commitAction !== consts.COMMIT_TO_CURRENT_BRANCH; const payload = createCommitPayload(getters.branchName, newBranch, state, rootState); const getCommitStatus = newBranch ? Promise.resolve(false) : dispatch('checkCommitStatus'); @@ -171,8 +177,44 @@ export const commitChanges = ({ commit, state, getters, dispatch, rootState }) = } commit(rootTypes.CLEAR_STAGED_CHANGES, null, { root: true }); + + setTimeout(() => { + commit(rootTypes.SET_LAST_COMMIT_MSG, '', { root: true }); + }, 5000); + }) + .then(() => { + if (rootGetters.lastOpenedFile) { + dispatch( + 'openPendingTab', + { + file: rootGetters.lastOpenedFile, + }, + { root: true }, + ) + .then(changeViewer => { + if (changeViewer) { + dispatch('updateViewer', 'diff', { root: true }); + } + }) + .catch(e => { + throw e; + }); + } else { + dispatch('updateActivityBarView', activityBarViews.edit, { root: true }); + dispatch('updateViewer', 'editor', { root: true }); + + router.push( + `/project/${rootState.currentProjectId}/blob/${getters.branchName}/${ + rootGetters.activeFile.path + }`, + ); + } }) - .then(() => dispatch('updateCommitAction', consts.COMMIT_TO_CURRENT_BRANCH)); + .then(() => dispatch('updateCommitAction', consts.COMMIT_TO_CURRENT_BRANCH)) + .then(() => dispatch('refreshLastCommitData', { + projectId: rootState.currentProjectId, + branchId: rootState.currentBranchId, + }, { root: true })); }) .catch(err => { let errMsg = __('Error committing changes. Please try again.'); @@ -185,3 +227,6 @@ export const commitChanges = ({ commit, state, getters, dispatch, rootState }) = commit(types.UPDATE_LOADING, false); }); }; + +// prevent babel-plugin-rewire from generating an invalid default during karma tests +export default () => {}; diff --git a/app/assets/javascripts/ide/stores/modules/commit/getters.js b/app/assets/javascripts/ide/stores/modules/commit/getters.js index 9c3905a0b0d..d01060201f2 100644 --- a/app/assets/javascripts/ide/stores/modules/commit/getters.js +++ b/app/assets/javascripts/ide/stores/modules/commit/getters.js @@ -27,3 +27,6 @@ export const branchName = (state, getters, rootState) => { return rootState.currentBranchId; }; + +// prevent babel-plugin-rewire from generating an invalid default during karma tests +export default () => {}; diff --git a/app/assets/javascripts/ide/stores/mutation_types.js b/app/assets/javascripts/ide/stores/mutation_types.js index f5f95b755c8..a3fb3232f1d 100644 --- a/app/assets/javascripts/ide/stores/mutation_types.js +++ b/app/assets/javascripts/ide/stores/mutation_types.js @@ -5,6 +5,7 @@ export const SET_LAST_COMMIT_MSG = 'SET_LAST_COMMIT_MSG'; export const SET_LEFT_PANEL_COLLAPSED = 'SET_LEFT_PANEL_COLLAPSED'; export const SET_RIGHT_PANEL_COLLAPSED = 'SET_RIGHT_PANEL_COLLAPSED'; export const SET_RESIZING_STATUS = 'SET_RESIZING_STATUS'; +export const SET_EMPTY_STATE_SVGS = 'SET_EMPTY_STATE_SVGS'; // Project Mutation Types export const SET_PROJECT = 'SET_PROJECT'; @@ -19,6 +20,7 @@ export const SET_MERGE_REQUEST_VERSIONS = 'SET_MERGE_REQUEST_VERSIONS'; // Branch Mutation Types export const SET_BRANCH = 'SET_BRANCH'; +export const SET_BRANCH_COMMIT = 'SET_BRANCH_COMMIT'; export const SET_BRANCH_WORKING_REFERENCE = 'SET_BRANCH_WORKING_REFERENCE'; export const TOGGLE_BRANCH_OPEN = 'TOGGLE_BRANCH_OPEN'; @@ -58,3 +60,8 @@ export const UNSTAGE_CHANGE = 'UNSTAGE_CHANGE'; export const UPDATE_FILE_AFTER_COMMIT = 'UPDATE_FILE_AFTER_COMMIT'; export const ADD_PENDING_TAB = 'ADD_PENDING_TAB'; export const REMOVE_PENDING_TAB = 'REMOVE_PENDING_TAB'; + +export const UPDATE_ACTIVITY_BAR_VIEW = 'UPDATE_ACTIVITY_BAR_VIEW'; +export const UPDATE_TEMP_FLAG = 'UPDATE_TEMP_FLAG'; +export const TOGGLE_FILE_FINDER = 'TOGGLE_FILE_FINDER'; +export const BURST_UNUSED_SEAL = 'BURST_UNUSED_SEAL'; diff --git a/app/assets/javascripts/ide/stores/mutations.js b/app/assets/javascripts/ide/stores/mutations.js index fbe342f9126..a257e2ef025 100644 --- a/app/assets/javascripts/ide/stores/mutations.js +++ b/app/assets/javascripts/ide/stores/mutations.js @@ -4,6 +4,7 @@ import mergeRequestMutation from './mutations/merge_request'; import fileMutations from './mutations/file'; import treeMutations from './mutations/tree'; import branchMutations from './mutations/branch'; +import { sortTree } from './utils'; export default { [types.SET_INITIAL_DATA](state, data) { @@ -73,7 +74,7 @@ export default { f => foundEntry.tree.find(e => e.path === f.path) === undefined, ); Object.assign(foundEntry, { - tree: foundEntry.tree.concat(tree), + tree: sortTree(foundEntry.tree.concat(tree)), }); } @@ -86,10 +87,16 @@ export default { if (!foundEntry) { Object.assign(state.trees[`${projectId}/${branchId}`], { - tree: state.trees[`${projectId}/${branchId}`].tree.concat(data.treeList), + tree: sortTree(state.trees[`${projectId}/${branchId}`].tree.concat(data.treeList)), }); } }, + [types.UPDATE_TEMP_FLAG](state, { path, tempFile }) { + Object.assign(state.entries[path], { + tempFile, + changed: tempFile, + }); + }, [types.UPDATE_VIEWER](state, viewer) { Object.assign(state, { viewer, @@ -100,6 +107,26 @@ export default { delayViewerUpdated, }); }, + [types.UPDATE_ACTIVITY_BAR_VIEW](state, currentActivityView) { + Object.assign(state, { + currentActivityView, + }); + }, + [types.SET_EMPTY_STATE_SVGS]( + state, + { emptyStateSvgPath, noChangesStateSvgPath, committedStateSvgPath }, + ) { + Object.assign(state, { + emptyStateSvgPath, + noChangesStateSvgPath, + committedStateSvgPath, + }); + }, + [types.TOGGLE_FILE_FINDER](state, fileFindVisible) { + Object.assign(state, { + fileFindVisible, + }); + }, [types.UPDATE_FILE_AFTER_COMMIT](state, { file, lastCommit }) { const changedFile = state.changedFiles.find(f => f.path === file.path); @@ -116,6 +143,11 @@ export default { }), }); }, + [types.BURST_UNUSED_SEAL](state) { + Object.assign(state, { + unusedSeal: false, + }); + }, ...projectMutations, ...mergeRequestMutation, ...fileMutations, diff --git a/app/assets/javascripts/ide/stores/mutations/branch.js b/app/assets/javascripts/ide/stores/mutations/branch.js index 2972ba5e38e..e09f88878f4 100644 --- a/app/assets/javascripts/ide/stores/mutations/branch.js +++ b/app/assets/javascripts/ide/stores/mutations/branch.js @@ -23,4 +23,9 @@ export default { workingReference: reference, }); }, + [types.SET_BRANCH_COMMIT](state, { projectId, branchId, commit }) { + Object.assign(state.projects[projectId].branches[branchId], { + commit, + }); + }, }; diff --git a/app/assets/javascripts/ide/stores/mutations/file.js b/app/assets/javascripts/ide/stores/mutations/file.js index dd7dcba8ac7..13f123b6630 100644 --- a/app/assets/javascripts/ide/stores/mutations/file.js +++ b/app/assets/javascripts/ide/stores/mutations/file.js @@ -1,9 +1,11 @@ +/* eslint-disable no-param-reassign */ import * as types from '../mutation_types'; export default { [types.SET_FILE_ACTIVE](state, { path, active }) { Object.assign(state.entries[path], { active, + lastOpenedAt: new Date().getTime(), }); if (active && !state.entries[path].pending) { @@ -168,32 +170,24 @@ export default { }); }, [types.ADD_PENDING_TAB](state, { file, keyPrefix = 'pending' }) { - const key = `${keyPrefix}-${file.key}`; - const pendingTab = state.openFiles.find(f => f.key === key && f.pending); - let openFiles = state.openFiles.map(f => Object.assign(f, { active: false, opened: false })); - - if (!pendingTab) { - const openFile = openFiles.find(f => f.path === file.path); - - openFiles = openFiles.concat(openFile ? null : file).reduce((acc, f) => { - if (!f) return acc; - - if (f.path === file.path) { - return acc.concat({ - ...f, - content: file.content, - active: true, - pending: true, - opened: true, - key, - }); - } - - return acc.concat(f); - }, []); - } - - Object.assign(state, { openFiles }); + state.entries[file.path].opened = false; + state.entries[file.path].active = false; + state.entries[file.path].lastOpenedAt = new Date().getTime(); + state.openFiles.forEach(f => + Object.assign(f, { + opened: false, + active: false, + }), + ); + state.openFiles = [ + { + ...file, + key: `${keyPrefix}-${file.key}`, + pending: true, + opened: true, + active: true, + }, + ]; }, [types.REMOVE_PENDING_TAB](state, file) { Object.assign(state, { diff --git a/app/assets/javascripts/ide/stores/state.js b/app/assets/javascripts/ide/stores/state.js index 34975ac3144..e7411f16a4f 100644 --- a/app/assets/javascripts/ide/stores/state.js +++ b/app/assets/javascripts/ide/stores/state.js @@ -1,3 +1,5 @@ +import { activityBarViews, viewerTypes } from '../constants'; + export default () => ({ currentProjectId: '', currentBranchId: '', @@ -16,6 +18,9 @@ export default () => ({ rightPanelCollapsed: false, panelResizing: false, entries: {}, - viewer: 'editor', + viewer: viewerTypes.edit, delayViewerUpdated: false, + currentActivityView: activityBarViews.edit, + unusedSeal: true, + fileFindVisible: false, }); diff --git a/app/assets/javascripts/ide/stores/utils.js b/app/assets/javascripts/ide/stores/utils.js index 8a222da14c0..bc79ff4a542 100644 --- a/app/assets/javascripts/ide/stores/utils.js +++ b/app/assets/javascripts/ide/stores/utils.js @@ -42,6 +42,8 @@ export const dataStructure = () => ({ viewMode: 'edit', previewMode: null, size: 0, + parentPath: null, + lastOpenedAt: 0, }); export const decorateData = entity => { @@ -64,6 +66,7 @@ export const decorateData = entity => { previewMode, file_lock, html, + parentPath = '', } = entity; return { @@ -87,6 +90,7 @@ export const decorateData = entity => { previewMode, file_lock, html, + parentPath, }; }; @@ -120,8 +124,8 @@ const sortTreesByTypeAndName = (a, b) => { } else if (a.type === 'blob' && b.type === 'tree') { return 1; } - if (a.name.toLowerCase() < b.name.toLowerCase()) return -1; - if (a.name.toLowerCase() > b.name.toLowerCase()) return 1; + if (a.name < b.name) return -1; + if (a.name > b.name) return 1; return 0; }; @@ -133,3 +137,9 @@ export const sortTree = sortedTree => }), ) .sort(sortTreesByTypeAndName); + +export const filePathMatches = (f, path) => + f.path.replace(new RegExp(`${f.name}$`), '').indexOf(`${path}/`) === 0; + +export const getChangesCountForFiles = (files, path) => + files.filter(f => filePathMatches(f, path)).length; diff --git a/app/assets/javascripts/ide/stores/workers/files_decorator_worker.js b/app/assets/javascripts/ide/stores/workers/files_decorator_worker.js index a1673276900..d249b05f47c 100644 --- a/app/assets/javascripts/ide/stores/workers/files_decorator_worker.js +++ b/app/assets/javascripts/ide/stores/workers/files_decorator_worker.js @@ -6,6 +6,7 @@ self.addEventListener('message', e => { const treeList = []; let file; + let parentPath; const entries = data.reduce((acc, path) => { const pathSplit = path.split('/'); const blobName = pathSplit.pop().trim(); @@ -17,6 +18,8 @@ self.addEventListener('message', e => { const foundEntry = acc[folderPath]; if (!foundEntry) { + parentPath = parentFolder ? parentFolder.path : null; + const tree = decorateData({ projectId, branchId, @@ -29,6 +32,7 @@ self.addEventListener('message', e => { tempFile, changed: tempFile, opened: tempFile, + parentPath, }); Object.assign(acc, { @@ -52,6 +56,8 @@ self.addEventListener('message', e => { if (blobName !== '') { const fileFolder = acc[pathSplit.join('/')]; + parentPath = fileFolder ? fileFolder.path : null; + file = decorateData({ projectId, branchId, @@ -66,6 +72,7 @@ self.addEventListener('message', e => { content, base64, previewMode: viewerInformationForPath(blobName), + parentPath, }); Object.assign(acc, { @@ -86,5 +93,6 @@ self.addEventListener('message', e => { entries, treeList: sortTree(treeList), file, + parentPath, }); }); diff --git a/app/assets/javascripts/issuable_form.js b/app/assets/javascripts/issuable_form.js index bb8b3d91e40..90d4e19e90b 100644 --- a/app/assets/javascripts/issuable_form.js +++ b/app/assets/javascripts/issuable_form.js @@ -30,7 +30,7 @@ export default class IssuableForm { } this.initAutosave(); - this.form.on('submit', this.handleSubmit); + this.form.on('submit:success', this.handleSubmit); this.form.on('click', '.btn-cancel', this.resetAutosave); this.initWip(); diff --git a/app/assets/javascripts/jobs/components/sidebar_details_block.vue b/app/assets/javascripts/jobs/components/sidebar_details_block.vue index 4cd44bf7a76..db19dc9b238 100644 --- a/app/assets/javascripts/jobs/components/sidebar_details_block.vue +++ b/app/assets/javascripts/jobs/components/sidebar_details_block.vue @@ -45,7 +45,7 @@ export default { return timeIntervalInWords(this.job.queued); }, runnerId() { - return `#${this.job.runner.id}`; + return `${this.job.runner.description} (#${this.job.runner.id})`; }, retryButtonClass() { let className = 'js-retry-button pull-right btn btn-retry visible-md-block visible-lg-block'; diff --git a/app/assets/javascripts/lib/utils/keycodes.js b/app/assets/javascripts/lib/utils/keycodes.js new file mode 100644 index 00000000000..5e0f9b612a2 --- /dev/null +++ b/app/assets/javascripts/lib/utils/keycodes.js @@ -0,0 +1,4 @@ +export const UP_KEY_CODE = 38; +export const DOWN_KEY_CODE = 40; +export const ENTER_KEY_CODE = 13; +export const ESC_KEY_CODE = 27; diff --git a/app/assets/javascripts/lib/utils/text_utility.js b/app/assets/javascripts/lib/utils/text_utility.js index b54ecd2d543..5e786ee6935 100644 --- a/app/assets/javascripts/lib/utils/text_utility.js +++ b/app/assets/javascripts/lib/utils/text_utility.js @@ -74,7 +74,11 @@ export function capitalizeFirstCharacter(text) { * @param {*} replace * @returns {String} */ -export const stripHtml = (string, replace = '') => string.replace(/<[^>]*>/g, replace); +export const stripHtml = (string, replace = '') => { + if (!string) return string; + + return string.replace(/<[^>]*>/g, replace); +}; /** * Converts snake_case string to camelCase diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js index 2c80baba10b..247aeb481c6 100644 --- a/app/assets/javascripts/main.js +++ b/app/assets/javascripts/main.js @@ -1,22 +1,19 @@ -/* eslint-disable import/first */ /* global $ */ import jQuery from 'jquery'; import Cookies from 'js-cookie'; import svg4everybody from 'svg4everybody'; -// expose common libraries as globals (TODO: remove these) -window.jQuery = jQuery; -window.$ = jQuery; +// bootstrap webpack, common libs, polyfills, and behaviors +import './webpack'; +import './commons'; +import './behaviors'; // lib/utils import { handleLocationHash, addSelectOnFocusBehaviour } from './lib/utils/common_utils'; import { localTimeAgo } from './lib/utils/datetime_utility'; import { getLocationHash, visitUrl } from './lib/utils/url_utility'; -// behaviors -import './behaviors/'; - // everything else import loadAwardsHandler from './awards_handler'; import bp from './breakpoints'; @@ -31,9 +28,12 @@ import initLogoAnimation from './logo'; import './milestone_select'; import './projects_dropdown'; import initBreadcrumbs from './breadcrumb'; - import initDispatcher from './dispatcher'; +// expose jQuery as global (TODO: remove these) +window.jQuery = jQuery; +window.$ = jQuery; + // inject test utilities if necessary if (process.env.NODE_ENV !== 'production' && gon && gon.test_env) { $.fx.off = true; @@ -52,10 +52,14 @@ document.addEventListener('beforeunload', () => { }); window.addEventListener('hashchange', handleLocationHash); -window.addEventListener('load', function onLoad() { - window.removeEventListener('load', onLoad, false); - handleLocationHash(); -}, false); +window.addEventListener( + 'load', + function onLoad() { + window.removeEventListener('load', onLoad, false); + handleLocationHash(); + }, + false, +); gl.lazyLoader = new LazyLoader({ scrollContainer: window, @@ -89,9 +93,7 @@ document.addEventListener('DOMContentLoaded', () => { if (bootstrapBreakpoint === 'xs') { const $rightSidebar = $('aside.right-sidebar, .layout-page'); - $rightSidebar - .removeClass('right-sidebar-expanded') - .addClass('right-sidebar-collapsed'); + $rightSidebar.removeClass('right-sidebar-expanded').addClass('right-sidebar-collapsed'); } // prevent default action for disabled buttons @@ -108,7 +110,8 @@ document.addEventListener('DOMContentLoaded', () => { addSelectOnFocusBehaviour('.js-select-on-focus'); $('.remove-row').on('ajax:success', function removeRowAjaxSuccessCallback() { - $(this).tooltip('destroy') + $(this) + .tooltip('destroy') .closest('li') .fadeOut(); }); @@ -118,7 +121,9 @@ document.addEventListener('DOMContentLoaded', () => { }); $('.js-remove-tr').on('ajax:success', function removeTRAjaxSuccessCallback() { - $(this).closest('tr').fadeOut(); + $(this) + .closest('tr') + .fadeOut(); }); // Initialize select2 selects @@ -155,7 +160,9 @@ document.addEventListener('DOMContentLoaded', () => { // Form submitter $('.trigger-submit').on('change', function triggerSubmitCallback() { - $(this).parents('form').submit(); + $(this) + .parents('form') + .submit(); }); localTimeAgo($('abbr.timeago, .js-timeago'), true); @@ -204,9 +211,15 @@ document.addEventListener('DOMContentLoaded', () => { $this.toggleClass('active'); if ($this.hasClass('active')) { - notesHolders.show().find('.hide, .content').show(); + notesHolders + .show() + .find('.hide, .content') + .show(); } else { - notesHolders.hide().find('.content').hide(); + notesHolders + .hide() + .find('.content') + .hide(); } $(document).trigger('toggle.comments'); @@ -247,9 +260,11 @@ document.addEventListener('DOMContentLoaded', () => { const flashContainer = document.querySelector('.flash-container'); if (flashContainer && flashContainer.children.length) { - flashContainer.querySelectorAll('.flash-alert, .flash-notice, .flash-success').forEach((flashEl) => { - removeFlashClickListener(flashEl); - }); + flashContainer + .querySelectorAll('.flash-alert, .flash-notice, .flash-success') + .forEach(flashEl => { + removeFlashClickListener(flashEl); + }); } initDispatcher(); diff --git a/app/assets/javascripts/milestone_select.js b/app/assets/javascripts/milestone_select.js index 7e9a50a885d..f8b3d3061f0 100644 --- a/app/assets/javascripts/milestone_select.js +++ b/app/assets/javascripts/milestone_select.js @@ -12,7 +12,8 @@ import ModalStore from './boards/stores/modal_store'; export default class MilestoneSelect { constructor(currentProject, els, options = {}) { if (currentProject !== null) { - this.currentProject = typeof currentProject === 'string' ? JSON.parse(currentProject) : currentProject; + this.currentProject = + typeof currentProject === 'string' ? JSON.parse(currentProject) : currentProject; } this.init(els, options); @@ -26,7 +27,10 @@ export default class MilestoneSelect { } $els.each((i, dropdown) => { - let milestoneLinkNoneTemplate, milestoneLinkTemplate, selectedMilestone, selectedMilestoneDefault; + let milestoneLinkNoneTemplate, + milestoneLinkTemplate, + selectedMilestone, + selectedMilestoneDefault; const $dropdown = $(dropdown); const projectId = $dropdown.data('projectId'); const milestonesUrl = $dropdown.data('milestones'); @@ -46,45 +50,47 @@ export default class MilestoneSelect { const $sidebarCollapsedValue = $block.find('.sidebar-collapsed-icon'); const $value = $block.find('.value'); const $loading = $block.find('.block-loading').fadeOut(); - selectedMilestoneDefault = (showAny ? '' : null); - selectedMilestoneDefault = (showNo && defaultNo ? 'No Milestone' : selectedMilestoneDefault); + selectedMilestoneDefault = showAny ? '' : null; + selectedMilestoneDefault = showNo && defaultNo ? 'No Milestone' : selectedMilestoneDefault; selectedMilestone = $dropdown.data('selected') || selectedMilestoneDefault; if (issueUpdateURL) { - milestoneLinkTemplate = _.template('<a href="/<%- full_path %>/milestones/<%- iid %>" class="bold has-tooltip" data-container="body" title="<%- remaining %>"><%- title %></a>'); + milestoneLinkTemplate = _.template( + '<a href="/<%- full_path %>/milestones/<%- iid %>" class="bold has-tooltip" data-container="body" title="<%- remaining %>"><%- title %></a>', + ); milestoneLinkNoneTemplate = '<span class="no-value">None</span>'; } return $dropdown.glDropdown({ showMenuAbove: showMenuAbove, - data: (term, callback) => axios.get(milestonesUrl) - .then(({ data }) => { + data: (term, callback) => + axios.get(milestonesUrl).then(({ data }) => { const extraOptions = []; if (showAny) { extraOptions.push({ - id: 0, - name: '', - title: 'Any Milestone' + id: null, + name: null, + title: 'Any Milestone', }); } if (showNo) { extraOptions.push({ id: -1, name: 'No Milestone', - title: 'No Milestone' + title: 'No Milestone', }); } if (showUpcoming) { extraOptions.push({ id: -2, name: '#upcoming', - title: 'Upcoming' + title: 'Upcoming', }); } if (showStarted) { extraOptions.push({ id: -3, name: '#started', - title: 'Started' + title: 'Started', }); } if (extraOptions.length) { @@ -106,7 +112,7 @@ export default class MilestoneSelect { `, filterable: true, search: { - fields: ['title'] + fields: ['title'], }, selectable: true, toggleLabel: (selected, el, e) => { @@ -119,7 +125,7 @@ export default class MilestoneSelect { defaultLabel: defaultLabel, fieldName: $dropdown.data('fieldName'), text: milestone => _.escape(milestone.title), - id: (milestone) => { + id: milestone => { if (!useId && !$dropdown.is('.js-issuable-form-dropdown')) { return milestone.name; } else { @@ -131,7 +137,7 @@ export default class MilestoneSelect { // display:block overrides the hide-collapse rule return $value.css('display', ''); }, - opened: (e) => { + opened: e => { const $el = $(e.currentTarget); if ($dropdown.hasClass('js-issue-board-sidebar') || options.handleClick) { selectedMilestone = $dropdown[0].dataset.selected || selectedMilestoneDefault; @@ -140,7 +146,7 @@ export default class MilestoneSelect { $(`[data-milestone-id="${_.escape(selectedMilestone)}"] > a`, $el).addClass('is-active'); }, vue: $dropdown.hasClass('js-issue-board-sidebar'), - clicked: (clickEvent) => { + clicked: clickEvent => { const { $el, e } = clickEvent; let selected = clickEvent.selectedObj; @@ -155,11 +161,14 @@ export default class MilestoneSelect { const page = $('body').attr('data-page'); const isIssueIndex = page === 'projects:issues:index'; - const isMRIndex = (page === page && page === 'projects:merge_requests:index'); - const isSelecting = (selected.name !== selectedMilestone); + const isMRIndex = page === page && page === 'projects:merge_requests:index'; + const isSelecting = selected.name !== selectedMilestone; selectedMilestone = isSelecting ? selected.name : selectedMilestoneDefault; - if ($dropdown.hasClass('js-filter-bulk-update') || $dropdown.hasClass('js-issuable-form-dropdown')) { + if ( + $dropdown.hasClass('js-filter-bulk-update') || + $dropdown.hasClass('js-issuable-form-dropdown') + ) { e.preventDefault(); return; } @@ -177,10 +186,13 @@ export default class MilestoneSelect { return $dropdown.closest('form').submit(); } else if ($dropdown.hasClass('js-issue-board-sidebar')) { if (selected.id !== -1 && isSelecting) { - gl.issueBoards.boardStoreIssueSet('milestone', new ListMilestone({ - id: selected.id, - title: selected.name - })); + gl.issueBoards.boardStoreIssueSet( + 'milestone', + new ListMilestone({ + id: selected.id, + title: selected.name, + }), + ); } else { gl.issueBoards.boardStoreIssueDelete('milestone'); } @@ -188,7 +200,8 @@ export default class MilestoneSelect { $dropdown.trigger('loading.gl.dropdown'); $loading.removeClass('hidden').fadeIn(); - gl.issueBoards.BoardsStore.detail.issue.update($dropdown.attr('data-issue-update')) + gl.issueBoards.BoardsStore.detail.issue + .update($dropdown.attr('data-issue-update')) .then(() => { $dropdown.trigger('loaded.gl.dropdown'); $loading.fadeOut(); @@ -203,7 +216,8 @@ export default class MilestoneSelect { data[abilityName].milestone_id = selected != null ? selected : null; $loading.removeClass('hidden').fadeIn(); $dropdown.trigger('loading.gl.dropdown'); - return axios.put(issueUpdateURL, data) + return axios + .put(issueUpdateURL, data) .then(({ data }) => { $dropdown.trigger('loaded.gl.dropdown'); $loading.fadeOut(); @@ -215,7 +229,10 @@ export default class MilestoneSelect { data.milestone.name = data.milestone.title; $value.html(milestoneLinkTemplate(data.milestone)); return $sidebarCollapsedValue - .attr('data-original-title', `${data.milestone.name}<br />${data.milestone.remaining}`) + .attr( + 'data-original-title', + `${data.milestone.name}<br />${data.milestone.remaining}`, + ) .find('span') .text(data.milestone.title); } else { @@ -230,7 +247,7 @@ export default class MilestoneSelect { $loading.fadeOut(); }); } - } + }, }); }); } diff --git a/app/assets/javascripts/mini_pipeline_graph_dropdown.js b/app/assets/javascripts/mini_pipeline_graph_dropdown.js index 01399de4c62..f8257b6abab 100644 --- a/app/assets/javascripts/mini_pipeline_graph_dropdown.js +++ b/app/assets/javascripts/mini_pipeline_graph_dropdown.js @@ -1,5 +1,3 @@ -/* eslint-disable no-new */ - import $ from 'jquery'; import flash from './flash'; import axios from './lib/utils/axios_utils'; @@ -62,7 +60,7 @@ export default class MiniPipelineGraph { */ renderBuildsList(stageContainer, data) { const dropdownContainer = stageContainer.parentElement.querySelector( - `${this.dropdownListSelector} .js-builds-dropdown-list`, + `${this.dropdownListSelector} .js-builds-dropdown-list ul`, ); dropdownContainer.innerHTML = data; diff --git a/app/assets/javascripts/monitoring/components/graph.vue b/app/assets/javascripts/monitoring/components/graph.vue index f93b1da4f58..de6755e0414 100644 --- a/app/assets/javascripts/monitoring/components/graph.vue +++ b/app/assets/javascripts/monitoring/components/graph.vue @@ -81,9 +81,8 @@ export default { time: new Date(), value: 0, }, - currentDataIndex: 0, currentXCoordinate: 0, - currentFlagPosition: 0, + currentCoordinates: [], showFlag: false, showFlagContent: false, timeSeries: [], @@ -273,6 +272,9 @@ export default { :line-style="path.lineStyle" :line-color="path.lineColor" :area-color="path.areaColor" + :current-coordinates="currentCoordinates[index]" + :current-time-series-index="index" + :show-dot="showFlagContent" /> <graph-deployment :deployment-data="reducedDeploymentData" @@ -298,9 +300,9 @@ export default { :show-flag-content="showFlagContent" :time-series="timeSeries" :unit-of-display="unitOfDisplay" - :current-data-index="currentDataIndex" :legend-title="legendTitle" :deployment-flag-data="deploymentFlagData" + :current-coordinates="currentCoordinates" /> </div> <graph-legend diff --git a/app/assets/javascripts/monitoring/components/graph/flag.vue b/app/assets/javascripts/monitoring/components/graph/flag.vue index b8202e25685..8a771107de8 100644 --- a/app/assets/javascripts/monitoring/components/graph/flag.vue +++ b/app/assets/javascripts/monitoring/components/graph/flag.vue @@ -47,14 +47,14 @@ export default { type: String, required: true, }, - currentDataIndex: { - type: Number, - required: true, - }, legendTitle: { type: String, required: true, }, + currentCoordinates: { + type: Array, + required: true, + }, }, computed: { formatTime() { @@ -90,10 +90,12 @@ export default { }, }, methods: { - seriesMetricValue(series) { + seriesMetricValue(seriesIndex, series) { + const indexFromCoordinates = this.currentCoordinates[seriesIndex] + ? this.currentCoordinates[seriesIndex].currentDataIndex : 0; const index = this.deploymentFlagData ? this.deploymentFlagData.seriesIndex - : this.currentDataIndex; + : indexFromCoordinates; const value = series.values[index] && series.values[index].value; if (isNaN(value)) { return '-'; @@ -128,7 +130,7 @@ export default { <h5 v-if="deploymentFlagData"> Deployed </h5> - {{ formatDate }} at + {{ formatDate }} <strong>{{ formatTime }}</strong> </div> <div @@ -163,9 +165,11 @@ export default { :key="index" > <track-line :track="series"/> - <td>{{ series.track }} {{ seriesMetricLabel(index, series) }}</td> <td> - <strong>{{ seriesMetricValue(series) }}</strong> + {{ series.track }} {{ seriesMetricLabel(index, series) }} + </td> + <td> + <strong>{{ seriesMetricValue(index, series) }}</strong> </td> </tr> </table> diff --git a/app/assets/javascripts/monitoring/components/graph/path.vue b/app/assets/javascripts/monitoring/components/graph/path.vue index 881560124a5..52f8aa2ee3f 100644 --- a/app/assets/javascripts/monitoring/components/graph/path.vue +++ b/app/assets/javascripts/monitoring/components/graph/path.vue @@ -22,6 +22,15 @@ export default { type: String, required: true, }, + currentCoordinates: { + type: Object, + required: false, + default: () => ({ currentX: 0, currentY: 0 }), + }, + showDot: { + type: Boolean, + required: true, + }, }, computed: { strokeDashArray() { @@ -33,12 +42,20 @@ export default { }; </script> <template> - <g> + <g transform="translate(-5, 20)"> + <circle + class="circle-path" + :cx="currentCoordinates.currentX" + :cy="currentCoordinates.currentY" + :fill="lineColor" + :stroke="lineColor" + r="3" + v-if="showDot" + /> <path class="metric-area" :d="generatedAreaPath" :fill="areaColor" - transform="translate(-5, 20)" /> <path class="metric-line" @@ -47,7 +64,6 @@ export default { fill="none" stroke-width="1" :stroke-dasharray="strokeDashArray" - transform="translate(-5, 20)" /> </g> </template> diff --git a/app/assets/javascripts/monitoring/components/graph/track_line.vue b/app/assets/javascripts/monitoring/components/graph/track_line.vue index 79b322e2e42..18be65fd1ef 100644 --- a/app/assets/javascripts/monitoring/components/graph/track_line.vue +++ b/app/assets/javascripts/monitoring/components/graph/track_line.vue @@ -19,16 +19,16 @@ export default { <template> <td> <svg - width="15" - height="6"> + width="16" + height="8"> <line :stroke-dasharray="stylizedLine" :stroke="track.lineColor" stroke-width="4" :x1="0" - :x2="15" - :y1="2" - :y2="2" + :x2="16" + :y1="4" + :y2="4" /> </svg> </td> diff --git a/app/assets/javascripts/monitoring/mixins/monitoring_mixins.js b/app/assets/javascripts/monitoring/mixins/monitoring_mixins.js index 6cc67ba57ee..4f23814ff3e 100644 --- a/app/assets/javascripts/monitoring/mixins/monitoring_mixins.js +++ b/app/assets/javascripts/monitoring/mixins/monitoring_mixins.js @@ -52,14 +52,22 @@ const mixins = { positionFlag() { const timeSeries = this.timeSeries[0]; const hoveredDataIndex = bisectDate(timeSeries.values, this.hoverData.hoveredDate, 1); + this.currentData = timeSeries.values[hoveredDataIndex]; - this.currentDataIndex = hoveredDataIndex; this.currentXCoordinate = Math.floor(timeSeries.timeSeriesScaleX(this.currentData.time)); - if (this.currentXCoordinate > (this.graphWidth - 200)) { - this.currentFlagPosition = this.currentXCoordinate - 103; - } else { - this.currentFlagPosition = this.currentXCoordinate; - } + + this.currentCoordinates = this.timeSeries.map((series) => { + const currentDataIndex = bisectDate(series.values, this.hoverData.hoveredDate, 1); + const currentData = series.values[currentDataIndex]; + const currentX = Math.floor(series.timeSeriesScaleX(currentData.time)); + const currentY = Math.floor(series.timeSeriesScaleY(currentData.value)); + + return { + currentX, + currentY, + currentDataIndex, + }; + }); if (this.hoverData.currentDeployXPos) { this.showFlag = false; diff --git a/app/assets/javascripts/monitoring/utils/date_time_formatters.js b/app/assets/javascripts/monitoring/utils/date_time_formatters.js index f3c9acdd93e..d88c13609dc 100644 --- a/app/assets/javascripts/monitoring/utils/date_time_formatters.js +++ b/app/assets/javascripts/monitoring/utils/date_time_formatters.js @@ -14,7 +14,7 @@ const d3 = { timeYear, }; -export const dateFormat = d3.time('%a, %b %-d'); +export const dateFormat = d3.time('%d %b %Y, '); export const timeFormat = d3.time('%-I:%M%p'); export const dateFormatWithName = d3.time('%a, %b %-d'); export const bisectDate = d3.bisector(d => d.time).left; diff --git a/app/assets/javascripts/monitoring/utils/multiple_time_series.js b/app/assets/javascripts/monitoring/utils/multiple_time_series.js index 8a93c7e6bae..4d3f1f1a7cc 100644 --- a/app/assets/javascripts/monitoring/utils/multiple_time_series.js +++ b/app/assets/javascripts/monitoring/utils/multiple_time_series.js @@ -123,6 +123,7 @@ function queryTimeSeries(query, graphWidth, graphHeight, graphHeightOffset, xDom linePath: lineFunction(timeSeries.values), areaPath: areaFunction(timeSeries.values), timeSeriesScaleX, + timeSeriesScaleY, values: timeSeries.values, max: maximumValue, average: accum / timeSeries.values.length, diff --git a/app/assets/javascripts/notes/components/comment_form.vue b/app/assets/javascripts/notes/components/comment_form.vue index 396a675b4ac..48642c4a086 100644 --- a/app/assets/javascripts/notes/components/comment_form.vue +++ b/app/assets/javascripts/notes/components/comment_form.vue @@ -99,10 +99,6 @@ export default { 'js-note-target-reopen': !this.isOpen, }; }, - supportQuickActions() { - // Disable quick actions support for Epics - return this.noteableType !== constants.EPIC_NOTEABLE_TYPE; - }, markdownDocsPath() { return this.getNotesData.markdownDocsPath; }, @@ -359,7 +355,7 @@ Please check your network connection and try again.`; name="note[note]" class="note-textarea js-vue-comment-form js-gfm-input js-autosize markdown-area js-vue-textarea" - :data-supports-quick-actions="supportQuickActions" + data-supports-quick-actions="true" aria-label="Description" v-model="note" ref="textarea" diff --git a/app/assets/javascripts/notes/components/discussion_counter.vue b/app/assets/javascripts/notes/components/discussion_counter.vue index d492d1cd001..cbe4774a360 100644 --- a/app/assets/javascripts/notes/components/discussion_counter.vue +++ b/app/assets/javascripts/notes/components/discussion_counter.vue @@ -86,7 +86,7 @@ export default { v-html="resolveSvg" ></span> </span> - <span class=".line-resolve-text"> + <span class="line-resolve-text"> {{ resolvedDiscussionCount }}/{{ discussionCount }} {{ countText }} resolved </span> </div> diff --git a/app/assets/javascripts/notes/stores/actions.js b/app/assets/javascripts/notes/stores/actions.js index 244a6980b5a..98ce070288e 100644 --- a/app/assets/javascripts/notes/stores/actions.js +++ b/app/assets/javascripts/notes/stores/actions.js @@ -315,3 +315,6 @@ export const scrollToNoteIfNeeded = (context, el) => { scrollToElement(el); } }; + +// prevent babel-plugin-rewire from generating an invalid default during karma tests +export default () => {}; diff --git a/app/assets/javascripts/notes/stores/getters.js b/app/assets/javascripts/notes/stores/getters.js index f89591a54d6..787be6f4c99 100644 --- a/app/assets/javascripts/notes/stores/getters.js +++ b/app/assets/javascripts/notes/stores/getters.js @@ -68,3 +68,6 @@ export const resolvedDiscussionCount = (state, getters) => { return Object.keys(resolvedMap).length; }; + +// prevent babel-plugin-rewire from generating an invalid default during karma tests +export default () => {}; diff --git a/app/assets/javascripts/pages/admin/users/components/delete_user_modal.vue b/app/assets/javascripts/pages/admin/users/components/delete_user_modal.vue index 0e3ac636661..9ce176744ba 100644 --- a/app/assets/javascripts/pages/admin/users/components/delete_user_modal.vue +++ b/app/assets/javascripts/pages/admin/users/components/delete_user_modal.vue @@ -52,16 +52,15 @@ text() { const keepContributionsText = s__(`AdminArea| You are about to permanently delete the user %{username}. - This will delete all of the issues, merge requests, and groups linked to them. + Issues, merge requests, and groups linked to them will be transferred to a system-wide "Ghost-user". To avoid data loss, consider using the %{strong_start}block user%{strong_end} feature instead. Once you %{strong_start}Delete user%{strong_end}, it cannot be undone or recovered.`); const deleteContributionsText = s__(`AdminArea| You are about to permanently delete the user %{username}. - Issues, merge requests, and groups linked to them will be transferred to a system-wide "Ghost-user". + This will delete all of the issues, merge requests, and groups linked to them. To avoid data loss, consider using the %{strong_start}block user%{strong_end} feature instead. Once you %{strong_start}Delete user%{strong_end}, it cannot be undone or recovered.`); - return sprintf(this.deleteContributions ? deleteContributionsText : keepContributionsText, { username: `<strong>${_.escape(this.username)}</strong>`, diff --git a/app/assets/javascripts/pages/groups/settings/ci_cd/show/index.js b/app/assets/javascripts/pages/groups/settings/ci_cd/show/index.js index 04a0d8117cc..d3b2656743d 100644 --- a/app/assets/javascripts/pages/groups/settings/ci_cd/show/index.js +++ b/app/assets/javascripts/pages/groups/settings/ci_cd/show/index.js @@ -1,6 +1,10 @@ +import initSettingsPanels from '~/settings_panels'; import AjaxVariableList from '~/ci_variable_list/ajax_variable_list'; document.addEventListener('DOMContentLoaded', () => { + // Initialize expandable settings panels + initSettingsPanels(); + const variableListEl = document.querySelector('.js-ci-variable-list-section'); // eslint-disable-next-line no-new new AjaxVariableList({ diff --git a/app/assets/javascripts/pages/ide/index.js b/app/assets/javascripts/pages/ide/index.js new file mode 100644 index 00000000000..efadf6967aa --- /dev/null +++ b/app/assets/javascripts/pages/ide/index.js @@ -0,0 +1,9 @@ +import { initIde, resetServiceWorkersPublicPath } from '~/ide/index'; + +document.addEventListener('DOMContentLoaded', () => { + const ideElement = document.getElementById('ide'); + if (ideElement) { + resetServiceWorkersPublicPath(); + initIde(ideElement); + } +}); diff --git a/app/assets/javascripts/pages/projects/clusters/gcp/login/index.js b/app/assets/javascripts/pages/projects/clusters/gcp/login/index.js new file mode 100644 index 00000000000..0c2d7d7c96a --- /dev/null +++ b/app/assets/javascripts/pages/projects/clusters/gcp/login/index.js @@ -0,0 +1,3 @@ +import gcpSignupOffer from '~/clusters/components/gcp_signup_offer'; + +gcpSignupOffer(); diff --git a/app/assets/javascripts/pages/projects/clusters/new/index.js b/app/assets/javascripts/pages/projects/clusters/new/index.js new file mode 100644 index 00000000000..0c2d7d7c96a --- /dev/null +++ b/app/assets/javascripts/pages/projects/clusters/new/index.js @@ -0,0 +1,3 @@ +import gcpSignupOffer from '~/clusters/components/gcp_signup_offer'; + +gcpSignupOffer(); diff --git a/app/assets/javascripts/pages/projects/compare/index.js b/app/assets/javascripts/pages/projects/compare/index.js index d1c78bd61db..768da8fb236 100644 --- a/app/assets/javascripts/pages/projects/compare/index.js +++ b/app/assets/javascripts/pages/projects/compare/index.js @@ -1,3 +1,3 @@ import initCompareAutocomplete from '~/compare_autocomplete'; -document.addEventListener('DOMContentLoaded', initCompareAutocomplete); +document.addEventListener('DOMContentLoaded', () => initCompareAutocomplete()); diff --git a/app/assets/javascripts/pages/projects/compare/show/index.js b/app/assets/javascripts/pages/projects/compare/show/index.js index 2b4fd3c47c0..a626ed2d30b 100644 --- a/app/assets/javascripts/pages/projects/compare/show/index.js +++ b/app/assets/javascripts/pages/projects/compare/show/index.js @@ -1,8 +1,10 @@ import Diff from '~/diff'; import initChangesDropdown from '~/init_changes_dropdown'; +import GpgBadges from '~/gpg_badges'; document.addEventListener('DOMContentLoaded', () => { new Diff(); // eslint-disable-line no-new const paddingTop = 16; initChangesDropdown(document.querySelector('.navbar-gitlab').offsetHeight - paddingTop); + GpgBadges.fetch(); }); diff --git a/app/assets/javascripts/pages/projects/merge_requests/creations/new/compare.js b/app/assets/javascripts/pages/projects/merge_requests/creations/new/compare.js new file mode 100644 index 00000000000..46f3f55a400 --- /dev/null +++ b/app/assets/javascripts/pages/projects/merge_requests/creations/new/compare.js @@ -0,0 +1,60 @@ +import $ from 'jquery'; +import { localTimeAgo } from '~/lib/utils/datetime_utility'; +import axios from '~/lib/utils/axios_utils'; +import initCompareAutocomplete from '~/compare_autocomplete'; +import initTargetProjectDropdown from './target_project_dropdown'; + +const updateCommitList = (url, $loadingIndicator, $commitList, params) => { + $loadingIndicator.show(); + $commitList.empty(); + + return axios + .get(url, { + params, + }) + .then(({ data }) => { + $loadingIndicator.hide(); + $commitList.html(data); + localTimeAgo($('.js-timeago', $commitList)); + }); +}; + +export default mrNewCompareNode => { + const { sourceBranchUrl, targetBranchUrl } = mrNewCompareNode.dataset; + initTargetProjectDropdown(); + + const updateSourceBranchCommitList = () => + updateCommitList( + sourceBranchUrl, + $(mrNewCompareNode).find('.js-source-loading'), + $(mrNewCompareNode).find('.mr_source_commit'), + { + ref: $(mrNewCompareNode) + .find("input[name='merge_request[source_branch]']") + .val(), + }, + ); + const updateTargetBranchCommitList = () => + updateCommitList( + targetBranchUrl, + $(mrNewCompareNode).find('.js-target-loading'), + $(mrNewCompareNode).find('.mr_target_commit'), + { + target_project_id: $(mrNewCompareNode) + .find("input[name='merge_request[target_project_id]']") + .val(), + ref: $(mrNewCompareNode) + .find("input[name='merge_request[target_branch]']") + .val(), + }, + ); + initCompareAutocomplete('branches', $dropdown => { + if ($dropdown.is('.js-target-branch')) { + updateTargetBranchCommitList(); + } else if ($dropdown.is('.js-source-branch')) { + updateSourceBranchCommitList(); + } + }); + updateSourceBranchCommitList(); + updateTargetBranchCommitList(); +}; diff --git a/app/assets/javascripts/pages/projects/merge_requests/creations/new/index.js b/app/assets/javascripts/pages/projects/merge_requests/creations/new/index.js index 6c9afddefac..01a0b4870c1 100644 --- a/app/assets/javascripts/pages/projects/merge_requests/creations/new/index.js +++ b/app/assets/javascripts/pages/projects/merge_requests/creations/new/index.js @@ -1,18 +1,15 @@ -import Compare from '~/compare'; import MergeRequest from '~/merge_request'; import initPipelines from '~/commit/pipelines/pipelines_bundle'; +import initCompare from './compare'; document.addEventListener('DOMContentLoaded', () => { const mrNewCompareNode = document.querySelector('.js-merge-request-new-compare'); if (mrNewCompareNode) { - new Compare({ // eslint-disable-line no-new - targetProjectUrl: mrNewCompareNode.dataset.targetProjectUrl, - sourceBranchUrl: mrNewCompareNode.dataset.sourceBranchUrl, - targetBranchUrl: mrNewCompareNode.dataset.targetBranchUrl, - }); + initCompare(mrNewCompareNode); } else { const mrNewSubmitNode = document.querySelector('.js-merge-request-new-submit'); - new MergeRequest({ // eslint-disable-line no-new + // eslint-disable-next-line no-new + new MergeRequest({ action: mrNewSubmitNode.dataset.mrSubmitAction, }); initPipelines(); diff --git a/app/assets/javascripts/pages/projects/merge_requests/creations/new/target_project_dropdown.js b/app/assets/javascripts/pages/projects/merge_requests/creations/new/target_project_dropdown.js new file mode 100644 index 00000000000..b72fe6681df --- /dev/null +++ b/app/assets/javascripts/pages/projects/merge_requests/creations/new/target_project_dropdown.js @@ -0,0 +1,22 @@ +import $ from 'jquery'; + +export default () => { + const $targetProjectDropdown = $('.js-target-project'); + $targetProjectDropdown.glDropdown({ + selectable: true, + fieldName: $targetProjectDropdown.data('fieldName'), + filterable: true, + id(obj, $el) { + return $el.data('id'); + }, + toggleLabel(obj, $el) { + return $el.text().trim(); + }, + clicked({ $el }) { + $('.mr_target_commit').empty(); + const $targetBranchDropdown = $('.js-target-branch'); + $targetBranchDropdown.data('refsUrl', $el.data('refsUrl')); + $targetBranchDropdown.data('glDropdown').clearMenu(); + }, + }); +}; diff --git a/app/assets/javascripts/pages/projects/pipelines/new/index.js b/app/assets/javascripts/pages/projects/pipelines/new/index.js index 9aa8945e268..b0b077a5e4c 100644 --- a/app/assets/javascripts/pages/projects/pipelines/new/index.js +++ b/app/assets/javascripts/pages/projects/pipelines/new/index.js @@ -1,6 +1,12 @@ import $ from 'jquery'; import NewBranchForm from '~/new_branch_form'; +import setupNativeFormVariableList from '~/ci_variable_list/native_form_variable_list'; document.addEventListener('DOMContentLoaded', () => { new NewBranchForm($('.js-new-pipeline-form')); // eslint-disable-line no-new + + setupNativeFormVariableList({ + container: $('.js-ci-variable-list-section'), + formField: 'variables_attributes', + }); }); diff --git a/app/assets/javascripts/pages/users/activity_calendar.js b/app/assets/javascripts/pages/users/activity_calendar.js index cbc2d80ee18..50d042fef29 100644 --- a/app/assets/javascripts/pages/users/activity_calendar.js +++ b/app/assets/javascripts/pages/users/activity_calendar.js @@ -188,11 +188,11 @@ export default class ActivityCalendar { }, { text: 'W', - y: 29 + this.dayYPos(2), + y: 29 + this.dayYPos(3), }, { text: 'F', - y: 29 + this.dayYPos(3), + y: 29 + this.dayYPos(5), }, ]; this.svg diff --git a/app/assets/javascripts/performance_bar/components/performance_bar_app.vue b/app/assets/javascripts/performance_bar/components/performance_bar_app.vue index 2fd1715ee79..8ffaa52d9e8 100644 --- a/app/assets/javascripts/performance_bar/components/performance_bar_app.vue +++ b/app/assets/javascripts/performance_bar/components/performance_bar_app.vue @@ -5,7 +5,6 @@ import PerformanceBarService from '../services/performance_bar_service'; import detailedMetric from './detailed_metric.vue'; import requestSelector from './request_selector.vue'; import simpleMetric from './simple_metric.vue'; -import upstreamPerformanceBar from './upstream_performance_bar.vue'; import Flash from '../../flash'; @@ -14,7 +13,6 @@ export default { detailedMetric, requestSelector, simpleMetric, - upstreamPerformanceBar, }, props: { store: { @@ -128,9 +126,6 @@ export default { {{ currentRequest.details.host.hostname }} </span> </div> - <upstream-performance-bar - v-if="initialRequest && currentRequest.details" - /> <detailed-metric v-for="metric in $options.detailedMetrics" :key="metric.metric" diff --git a/app/assets/javascripts/performance_bar/components/upstream_performance_bar.vue b/app/assets/javascripts/performance_bar/components/upstream_performance_bar.vue deleted file mode 100644 index 2b5915f381f..00000000000 --- a/app/assets/javascripts/performance_bar/components/upstream_performance_bar.vue +++ /dev/null @@ -1,20 +0,0 @@ -<script> -export default { - mounted() { - const upstreamPerformanceBar = document - .getElementById('peek-view-performance-bar') - .cloneNode(true); - - upstreamPerformanceBar.classList.remove('hidden'); - - this.$refs.wrapper.appendChild(upstreamPerformanceBar); - }, -}; -</script> -<template> - <div - id="peek-view-performance-bar-vue" - class="view" - ref="wrapper" - ></div> -</template> diff --git a/app/assets/javascripts/performance_bar/index.js b/app/assets/javascripts/performance_bar/index.js index a0ddf36a672..4a98aed7679 100644 --- a/app/assets/javascripts/performance_bar/index.js +++ b/app/assets/javascripts/performance_bar/index.js @@ -1,5 +1,3 @@ -import 'vendor/peek.performance_bar'; - import Vue from 'vue'; import performanceBarApp from './components/performance_bar_app.vue'; import PerformanceBarStore from './stores/performance_bar_store'; diff --git a/app/assets/javascripts/pipelines/components/graph/action_component.vue b/app/assets/javascripts/pipelines/components/graph/action_component.vue index 29ee73a2a6f..fd3491c7fe0 100644 --- a/app/assets/javascripts/pipelines/components/graph/action_component.vue +++ b/app/assets/javascripts/pipelines/components/graph/action_component.vue @@ -61,7 +61,7 @@ export default { methods: { onClickAction() { $(this.$el).tooltip('hide'); - eventHub.$emit('graphAction', this.link); + eventHub.$emit('postAction', this.link); this.linkRequested = this.link; this.isDisabled = true; }, diff --git a/app/assets/javascripts/pipelines/components/graph/dropdown_job_component.vue b/app/assets/javascripts/pipelines/components/graph/dropdown_job_component.vue index 43121dd38f3..4027d26098f 100644 --- a/app/assets/javascripts/pipelines/components/graph/dropdown_job_component.vue +++ b/app/assets/javascripts/pipelines/components/graph/dropdown_job_component.vue @@ -87,7 +87,8 @@ export default { data-toggle="dropdown" data-container="body" class="dropdown-menu-toggle build-content" - :title="tooltipText"> + :title="tooltipText" + > <job-name-component :name="job.name" @@ -104,7 +105,8 @@ export default { <ul> <li v-for="(item, i) in job.jobs" - :key="i"> + :key="i" + > <job-component :job="item" css-class-job-name="mini-pipeline-graph-dropdown-item" diff --git a/app/assets/javascripts/pipelines/components/graph/job_component.vue b/app/assets/javascripts/pipelines/components/graph/job_component.vue index 4fcd4b79f4a..c1f0f051b63 100644 --- a/app/assets/javascripts/pipelines/components/graph/job_component.vue +++ b/app/assets/javascripts/pipelines/components/graph/job_component.vue @@ -108,7 +108,7 @@ export default { <div v-else v-tooltip - class="js-job-component-tooltip" + class="js-job-component-tooltip non-details-job-component" :title="tooltipText" :class="cssClassJobName" data-html="true" diff --git a/app/assets/javascripts/pipelines/components/stage.vue b/app/assets/javascripts/pipelines/components/stage.vue index 32cf3dba3c3..a65485c05eb 100644 --- a/app/assets/javascripts/pipelines/components/stage.vue +++ b/app/assets/javascripts/pipelines/components/stage.vue @@ -1,135 +1,140 @@ <script> - - /** - * Renders each stage of the pipeline mini graph. - * - * Given the provided endpoint will make a request to - * fetch the dropdown data when the stage is clicked. - * - * Request is made inside this component to make it reusable between: - * 1. Pipelines main table - * 2. Pipelines table in commit and Merge request views - * 3. Merge request widget - * 4. Commit widget - */ - - import $ from 'jquery'; - import Flash from '../../flash'; - import axios from '../../lib/utils/axios_utils'; - import eventHub from '../event_hub'; - import Icon from '../../vue_shared/components/icon.vue'; - import LoadingIcon from '../../vue_shared/components/loading_icon.vue'; - import tooltip from '../../vue_shared/directives/tooltip'; - - export default { - components: { - LoadingIcon, - Icon, +/** + * Renders each stage of the pipeline mini graph. + * + * Given the provided endpoint will make a request to + * fetch the dropdown data when the stage is clicked. + * + * Request is made inside this component to make it reusable between: + * 1. Pipelines main table + * 2. Pipelines table in commit and Merge request views + * 3. Merge request widget + * 4. Commit widget + */ + +import $ from 'jquery'; +import { __ } from '../../locale'; +import Flash from '../../flash'; +import axios from '../../lib/utils/axios_utils'; +import eventHub from '../event_hub'; +import Icon from '../../vue_shared/components/icon.vue'; +import LoadingIcon from '../../vue_shared/components/loading_icon.vue'; +import JobComponent from './graph/job_component.vue'; +import tooltip from '../../vue_shared/directives/tooltip'; + +export default { + components: { + LoadingIcon, + Icon, + JobComponent, + }, + + directives: { + tooltip, + }, + + props: { + stage: { + type: Object, + required: true, }, - directives: { - tooltip, + updateDropdown: { + type: Boolean, + required: false, + default: false, }, - - props: { - stage: { - type: Object, - required: true, - }, - - updateDropdown: { - type: Boolean, - required: false, - default: false, - }, + }, + + data() { + return { + isLoading: false, + dropdownContent: '', + }; + }, + + computed: { + dropdownClass() { + return this.dropdownContent.length > 0 + ? 'js-builds-dropdown-container' + : 'js-builds-dropdown-loading'; }, - data() { - return { - isLoading: false, - dropdownContent: '', - }; + triggerButtonClass() { + return `ci-status-icon-${this.stage.status.group}`; }, - computed: { - dropdownClass() { - return this.dropdownContent.length > 0 ? 'js-builds-dropdown-container' : 'js-builds-dropdown-loading'; - }, + borderlessIcon() { + return `${this.stage.status.icon}_borderless`; + }, + }, - triggerButtonClass() { - return `ci-status-icon-${this.stage.status.group}`; - }, + watch: { + updateDropdown() { + if (this.updateDropdown && this.isDropdownOpen() && !this.isLoading) { + this.fetchJobs(); + } + }, + }, + + updated() { + if (this.dropdownContent.length > 0) { + this.stopDropdownClickPropagation(); + } + }, + + methods: { + onClickStage() { + if (!this.isDropdownOpen()) { + eventHub.$emit('clickedDropdown'); + this.isLoading = true; + this.fetchJobs(); + } + }, - borderlessIcon() { - return `${this.stage.status.icon}_borderless`; - }, + fetchJobs() { + axios + .get(this.stage.dropdown_path) + .then(({ data }) => { + this.dropdownContent = data.latest_statuses; + this.isLoading = false; + }) + .catch(() => { + this.closeDropdown(); + this.isLoading = false; + + Flash(__('Something went wrong on our end.')); + }); }, - watch: { - updateDropdown() { - if (this.updateDropdown && - this.isDropdownOpen() && - !this.isLoading) { - this.fetchJobs(); - } - }, + /** + * When the user right clicks or cmd/ctrl + click in the job name + * the dropdown should not be closed and the link should open in another tab, + * so we stop propagation of the click event inside the dropdown. + * + * Since this component is rendered multiple times per page we need to guarantee we only + * target the click event of this component. + */ + stopDropdownClickPropagation() { + $( + '.js-builds-dropdown-list button, .js-builds-dropdown-list a.mini-pipeline-graph-dropdown-item', + this.$el, + ).on('click', e => { + e.stopPropagation(); + }); }, - updated() { - if (this.dropdownContent.length > 0) { - this.stopDropdownClickPropagation(); + closeDropdown() { + if (this.isDropdownOpen()) { + $(this.$refs.dropdown).dropdown('toggle'); } }, - methods: { - onClickStage() { - if (!this.isDropdownOpen()) { - eventHub.$emit('clickedDropdown'); - this.isLoading = true; - this.fetchJobs(); - } - }, - - fetchJobs() { - axios.get(this.stage.dropdown_path) - .then(({ data }) => { - this.dropdownContent = data.html; - this.isLoading = false; - }) - .catch(() => { - this.closeDropdown(); - this.isLoading = false; - - Flash('Something went wrong on our end.'); - }); - }, - - /** - * When the user right clicks or cmd/ctrl + click in the job name - * the dropdown should not be closed and the link should open in another tab, - * so we stop propagation of the click event inside the dropdown. - * - * Since this component is rendered multiple times per page we need to guarantee we only - * target the click event of this component. - */ - stopDropdownClickPropagation() { - $(this.$el.querySelectorAll('.js-builds-dropdown-list a.mini-pipeline-graph-dropdown-item')) - .on('click', (e) => { - e.stopPropagation(); - }); - }, - - closeDropdown() { - if (this.isDropdownOpen()) { - $(this.$refs.dropdown).dropdown('toggle'); - } - }, - - isDropdownOpen() { - return this.$el.classList.contains('open'); - }, + isDropdownOpen() { + return this.$el.classList.contains('open'); }, - }; + }, +}; </script> <template> @@ -168,7 +173,6 @@ > <li - :class="dropdownClass" class="js-builds-dropdown-list scrollable-menu" > @@ -176,8 +180,16 @@ <ul v-else - v-html="dropdownContent" > + <li + v-for="job in dropdownContent" + :key="job.id" + > + <job-component + :job="job" + css-class-job-name="mini-pipeline-graph-dropdown-item" + /> + </li> </ul> </li> </ul> diff --git a/app/assets/javascripts/pipelines/pipeline_details_bundle.js b/app/assets/javascripts/pipelines/pipeline_details_bundle.js index 6584f96130b..04fe7958fe6 100644 --- a/app/assets/javascripts/pipelines/pipeline_details_bundle.js +++ b/app/assets/javascripts/pipelines/pipeline_details_bundle.js @@ -29,10 +29,10 @@ export default () => { }; }, created() { - eventHub.$on('graphAction', this.postAction); + eventHub.$on('postAction', this.postAction); }, beforeDestroy() { - eventHub.$off('graphAction', this.postAction); + eventHub.$off('postAction', this.postAction); }, methods: { postAction(action) { diff --git a/app/assets/javascripts/projects_dropdown/components/app.vue b/app/assets/javascripts/projects_dropdown/components/app.vue index 34a60dd574b..0bbd8a41753 100644 --- a/app/assets/javascripts/projects_dropdown/components/app.vue +++ b/app/assets/javascripts/projects_dropdown/components/app.vue @@ -100,9 +100,10 @@ export default { fetchSearchedProjects(searchQuery) { this.searchQuery = searchQuery; this.toggleLoader(true); - this.service.getSearchedProjects(this.searchQuery) + this.service + .getSearchedProjects(this.searchQuery) .then(res => res.json()) - .then((results) => { + .then(results => { this.toggleSearchProjectsList(true); this.store.setSearchedProjects(results); }) diff --git a/app/assets/javascripts/projects_dropdown/service/projects_service.js b/app/assets/javascripts/projects_dropdown/service/projects_service.js index 7231f520933..ed1c3deead2 100644 --- a/app/assets/javascripts/projects_dropdown/service/projects_service.js +++ b/app/assets/javascripts/projects_dropdown/service/projects_service.js @@ -50,7 +50,7 @@ export default class ProjectsService { } else { // Check if project is already present in frequents list // When found, update metadata of it. - storedFrequentProjects = JSON.parse(storedRawProjects).map((projectItem) => { + storedFrequentProjects = JSON.parse(storedRawProjects).map(projectItem => { if (projectItem.id === project.id) { matchFound = true; const diff = Math.abs(project.lastAccessedOn - projectItem.lastAccessedOn) / HOUR_IN_MS; @@ -104,13 +104,17 @@ export default class ProjectsService { return []; } - if (bp.getBreakpointSize() === 'sm' || - bp.getBreakpointSize() === 'xs') { + if (bp.getBreakpointSize() === 'sm' || bp.getBreakpointSize() === 'xs') { frequentProjectsCount = FREQUENT_PROJECTS.LIST_COUNT_MOBILE; } - const frequentProjects = storedFrequentProjects - .filter(project => project.frequency >= FREQUENT_PROJECTS.ELIGIBLE_FREQUENCY); + const frequentProjects = storedFrequentProjects.filter( + project => project.frequency >= FREQUENT_PROJECTS.ELIGIBLE_FREQUENCY, + ); + + if (!frequentProjects || frequentProjects.length === 0) { + return []; + } // Sort all frequent projects in decending order of frequency // and then by lastAccessedOn with recent most first diff --git a/app/assets/javascripts/registry/stores/actions.js b/app/assets/javascripts/registry/stores/actions.js index 795b39bb3dc..593a43c7cc1 100644 --- a/app/assets/javascripts/registry/stores/actions.js +++ b/app/assets/javascripts/registry/stores/actions.js @@ -35,3 +35,6 @@ export const deleteRegistry = ({ commit }, image) => Vue.http.delete(image.destr export const setMainEndpoint = ({ commit }, data) => commit(types.SET_MAIN_ENDPOINT, data); export const toggleLoading = ({ commit }) => commit(types.TOGGLE_MAIN_LOADING); + +// prevent babel-plugin-rewire from generating an invalid default during karma tests +export default () => {}; diff --git a/app/assets/javascripts/registry/stores/getters.js b/app/assets/javascripts/registry/stores/getters.js index 588f479c492..f4923512578 100644 --- a/app/assets/javascripts/registry/stores/getters.js +++ b/app/assets/javascripts/registry/stores/getters.js @@ -1,2 +1,5 @@ export const isLoading = state => state.isLoading; export const repos = state => state.repos; + +// prevent babel-plugin-rewire from generating an invalid default during karma tests +export default () => {}; diff --git a/app/assets/javascripts/shortcuts.js b/app/assets/javascripts/shortcuts.js index e31e067033f..99c71d6524a 100644 --- a/app/assets/javascripts/shortcuts.js +++ b/app/assets/javascripts/shortcuts.js @@ -85,6 +85,7 @@ export default class Shortcuts { if ($modal.length) { $modal.modal('toggle'); + return null; } return axios.get(gon.shortcuts_path, { diff --git a/app/assets/javascripts/sidebar/components/participants/participants.vue b/app/assets/javascripts/sidebar/components/participants/participants.vue index 6d95153af28..8f9e6761d20 100644 --- a/app/assets/javascripts/sidebar/components/participants/participants.vue +++ b/app/assets/javascripts/sidebar/components/participants/participants.vue @@ -70,6 +70,9 @@ toggleMoreParticipants() { this.isShowingMoreParticipants = !this.isShowingMoreParticipants; }, + onClickCollapsedIcon() { + this.$emit('toggleSidebar'); + }, }, }; </script> @@ -82,6 +85,7 @@ data-container="body" data-placement="left" :title="participantLabel" + @click="onClickCollapsedIcon" > <i class="fa fa-users" diff --git a/app/assets/javascripts/sidebar/components/subscriptions/sidebar_subscriptions.vue b/app/assets/javascripts/sidebar/components/subscriptions/sidebar_subscriptions.vue index 3e8cc7a6630..385717e7c1e 100644 --- a/app/assets/javascripts/sidebar/components/subscriptions/sidebar_subscriptions.vue +++ b/app/assets/javascripts/sidebar/components/subscriptions/sidebar_subscriptions.vue @@ -1,6 +1,5 @@ <script> import Store from '../../stores/sidebar_store'; -import eventHub from '../../event_hub'; import Flash from '../../../flash'; import { __ } from '../../../locale'; import subscriptions from './subscriptions.vue'; @@ -20,12 +19,6 @@ export default { store: new Store(), }; }, - created() { - eventHub.$on('toggleSubscription', this.onToggleSubscription); - }, - beforeDestroy() { - eventHub.$off('toggleSubscription', this.onToggleSubscription); - }, methods: { onToggleSubscription() { this.mediator.toggleSubscription() @@ -42,6 +35,7 @@ export default { <subscriptions :loading="store.isFetching.subscriptions" :subscribed="store.subscribed" + @toggleSubscription="onToggleSubscription" /> </div> </template> diff --git a/app/assets/javascripts/sidebar/components/subscriptions/subscriptions.vue b/app/assets/javascripts/sidebar/components/subscriptions/subscriptions.vue index d69d100a26c..f0df759ef7a 100644 --- a/app/assets/javascripts/sidebar/components/subscriptions/subscriptions.vue +++ b/app/assets/javascripts/sidebar/components/subscriptions/subscriptions.vue @@ -47,8 +47,25 @@ }, }, methods: { + /** + * We need to emit this event on both component & eventHub + * for 2 dependencies; + * + * 1. eventHub: This component is used in Issue Boards sidebar + * where component template is part of HAML + * and event listeners are tied to app's eventHub. + * 2. Component: This compone is also used in Epics in EE + * where listeners are tied to component event. + */ toggleSubscription() { + // App's eventHub event emission. eventHub.$emit('toggleSubscription', this.id); + + // Component event emission. + this.$emit('toggleSubscription', this.id); + }, + onClickCollapsedIcon() { + this.$emit('toggleSidebar'); }, }, }; @@ -56,7 +73,10 @@ <template> <div> - <div class="sidebar-collapsed-icon"> + <div + class="sidebar-collapsed-icon" + @click="onClickCollapsedIcon" + > <span v-tooltip :title="notificationTooltip" diff --git a/app/assets/javascripts/sidebar/components/time_tracking/no_tracking_pane.js b/app/assets/javascripts/sidebar/components/time_tracking/no_tracking_pane.js deleted file mode 100644 index 38da76c6771..00000000000 --- a/app/assets/javascripts/sidebar/components/time_tracking/no_tracking_pane.js +++ /dev/null @@ -1,10 +0,0 @@ -export default { - name: 'time-tracking-no-tracking-pane', - template: ` - <div class="time-tracking-no-tracking-pane"> - <span class="no-value"> - {{ __('No estimate or time spent') }} - </span> - </div> - `, -}; diff --git a/app/assets/javascripts/sidebar/components/time_tracking/no_tracking_pane.vue b/app/assets/javascripts/sidebar/components/time_tracking/no_tracking_pane.vue new file mode 100644 index 00000000000..9228184df5b --- /dev/null +++ b/app/assets/javascripts/sidebar/components/time_tracking/no_tracking_pane.vue @@ -0,0 +1,13 @@ +<script> +export default { + name: 'TimeTrackingNoTrackingPane', +}; +</script> + +<template> + <div class="time-tracking-no-tracking-pane"> + <span class="no-value"> + {{ __('No estimate or time spent') }} + </span> + </div> +</template> diff --git a/app/assets/javascripts/sidebar/components/time_tracking/sidebar_time_tracking.js b/app/assets/javascripts/sidebar/components/time_tracking/sidebar_time_tracking.vue index 5626cccc022..2e1d6e9643a 100644 --- a/app/assets/javascripts/sidebar/components/time_tracking/sidebar_time_tracking.js +++ b/app/assets/javascripts/sidebar/components/time_tracking/sidebar_time_tracking.vue @@ -1,3 +1,4 @@ +<script> import $ from 'jquery'; import _ from 'underscore'; @@ -10,14 +11,17 @@ import Mediator from '../../sidebar_mediator'; import eventHub from '../../event_hub'; export default { + components: { + IssuableTimeTracker, + }, data() { return { mediator: new Mediator(), store: new Store(), }; }, - components: { - IssuableTimeTracker, + mounted() { + this.listenForQuickActions(); }, methods: { listenForQuickActions() { @@ -41,18 +45,17 @@ export default { } }, }, - mounted() { - this.listenForQuickActions(); - }, - template: ` - <div class="block"> - <issuable-time-tracker - :time_estimate="store.timeEstimate" - :time_spent="store.totalTimeSpent" - :human_time_estimate="store.humanTimeEstimate" - :human_time_spent="store.humanTotalTimeSpent" - :rootPath="store.rootPath" - /> - </div> - `, }; +</script> + +<template> + <div class="block"> + <issuable-time-tracker + :time_estimate="store.timeEstimate" + :time_spent="store.totalTimeSpent" + :human_time_estimate="store.humanTimeEstimate" + :human_time_spent="store.humanTotalTimeSpent" + :root-path="store.rootPath" + /> + </div> +</template> diff --git a/app/assets/javascripts/sidebar/components/time_tracking/spent_only_pane.js b/app/assets/javascripts/sidebar/components/time_tracking/spent_only_pane.js deleted file mode 100644 index bf987562647..00000000000 --- a/app/assets/javascripts/sidebar/components/time_tracking/spent_only_pane.js +++ /dev/null @@ -1,15 +0,0 @@ -export default { - name: 'time-tracking-spent-only-pane', - props: { - timeSpentHumanReadable: { - type: String, - required: true, - }, - }, - template: ` - <div class="time-tracking-spend-only-pane"> - <span class="bold">Spent:</span> - {{ timeSpentHumanReadable }} - </div> - `, -}; diff --git a/app/assets/javascripts/sidebar/components/time_tracking/spent_only_pane.vue b/app/assets/javascripts/sidebar/components/time_tracking/spent_only_pane.vue new file mode 100644 index 00000000000..59cd99f8f14 --- /dev/null +++ b/app/assets/javascripts/sidebar/components/time_tracking/spent_only_pane.vue @@ -0,0 +1,18 @@ +<script> +export default { + name: 'TimeTrackingSpentOnlyPane', + props: { + timeSpentHumanReadable: { + type: String, + required: true, + }, + }, +}; +</script> + +<template> + <div class="time-tracking-spend-only-pane"> + <span class="bold">Spent:</span> + {{ timeSpentHumanReadable }} + </div> +</template> diff --git a/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue b/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue index 71dca498b3d..8f5d0bee107 100644 --- a/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue +++ b/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue @@ -1,8 +1,8 @@ <script> import TimeTrackingHelpState from './help_state.vue'; import TimeTrackingCollapsedState from './collapsed_state.vue'; -import timeTrackingSpentOnlyPane from './spent_only_pane'; -import timeTrackingNoTrackingPane from './no_tracking_pane'; +import TimeTrackingSpentOnlyPane from './spent_only_pane.vue'; +import TimeTrackingNoTrackingPane from './no_tracking_pane.vue'; import TimeTrackingEstimateOnlyPane from './estimate_only_pane.vue'; import TimeTrackingComparisonPane from './comparison_pane.vue'; @@ -13,8 +13,8 @@ export default { components: { TimeTrackingCollapsedState, TimeTrackingEstimateOnlyPane, - 'time-tracking-spent-only-pane': timeTrackingSpentOnlyPane, - 'time-tracking-no-tracking-pane': timeTrackingNoTrackingPane, + TimeTrackingSpentOnlyPane, + TimeTrackingNoTrackingPane, TimeTrackingComparisonPane, TimeTrackingHelpState, }, diff --git a/app/assets/javascripts/sidebar/lib/sidebar_move_issue.js b/app/assets/javascripts/sidebar/lib/sidebar_move_issue.js index 1eadebc7004..b267422cd97 100644 --- a/app/assets/javascripts/sidebar/lib/sidebar_move_issue.js +++ b/app/assets/javascripts/sidebar/lib/sidebar_move_issue.js @@ -1,4 +1,5 @@ import $ from 'jquery'; +import _ from 'underscore'; function isValidProjectId(id) { return id > 0; @@ -43,7 +44,7 @@ class SidebarMoveIssue { renderRow: project => ` <li> <a href="#" class="js-move-issue-dropdown-item"> - ${project.name_with_namespace} + ${_.escape(project.name_with_namespace)} </a> </li> `, diff --git a/app/assets/javascripts/sidebar/mount_sidebar.js b/app/assets/javascripts/sidebar/mount_sidebar.js index 26eb4cffba3..3086e7d0fc9 100644 --- a/app/assets/javascripts/sidebar/mount_sidebar.js +++ b/app/assets/javascripts/sidebar/mount_sidebar.js @@ -1,6 +1,6 @@ import $ from 'jquery'; import Vue from 'vue'; -import SidebarTimeTracking from './components/time_tracking/sidebar_time_tracking'; +import SidebarTimeTracking from './components/time_tracking/sidebar_time_tracking.vue'; import SidebarAssignees from './components/assignees/sidebar_assignees.vue'; import ConfidentialIssueSidebar from './components/confidential/confidential_issue_sidebar.vue'; import SidebarMoveIssue from './lib/sidebar_move_issue'; diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_failed_to_merge.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_failed_to_merge.vue index 602b68ea572..7d366c495f0 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_failed_to_merge.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_failed_to_merge.vue @@ -1,66 +1,70 @@ <script> - import { n__ } from '~/locale'; - import statusIcon from '../mr_widget_status_icon.vue'; - import eventHub from '../../event_hub'; +import { n__ } from '~/locale'; +import statusIcon from '../mr_widget_status_icon.vue'; +import eventHub from '../../event_hub'; - export default { - name: 'MRWidgetFailedToMerge', +export default { + name: 'MRWidgetFailedToMerge', - components: { - statusIcon, - }, + components: { + statusIcon, + }, - props: { - mr: { - type: Object, - required: true, - default: () => ({}), - }, + props: { + mr: { + type: Object, + required: true, + default: () => ({}), }, + }, - data() { - return { - timer: 10, - isRefreshing: false, - }; - }, + data() { + return { + timer: 10, + isRefreshing: false, + intervalId: null, + }; + }, - computed: { - timerText() { - return n__( - 'Refreshing in a second to show the updated status...', - 'Refreshing in %d seconds to show the updated status...', - this.timer, - ); - }, + computed: { + timerText() { + return n__( + 'Refreshing in a second to show the updated status...', + 'Refreshing in %d seconds to show the updated status...', + this.timer, + ); }, + }, - mounted() { - setInterval(() => { - this.updateTimer(); - }, 1000); - }, + mounted() { + this.intervalId = setInterval(this.updateTimer, 1000); + }, - created() { - eventHub.$emit('DisablePolling'); - }, + created() { + eventHub.$emit('DisablePolling'); + }, - methods: { - refresh() { - this.isRefreshing = true; - eventHub.$emit('MRWidgetUpdateRequested'); - eventHub.$emit('EnablePolling'); - }, - updateTimer() { - this.timer = this.timer - 1; + beforeDestroy() { + if (this.intervalId) { + clearInterval(this.intervalId); + } + }, - if (this.timer === 0) { - this.refresh(); - } - }, + methods: { + refresh() { + this.isRefreshing = true; + eventHub.$emit('MRWidgetUpdateRequested'); + eventHub.$emit('EnablePolling'); }, + updateTimer() { + this.timer = this.timer - 1; - }; + if (this.timer === 0) { + this.refresh(); + } + }, + }, +}; </script> <template> <div class="mr-widget-body media"> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.vue index c1618bc6ea0..3e36a3a10f9 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.vue @@ -3,6 +3,7 @@ import tooltip from '~/vue_shared/directives/tooltip'; import loadingIcon from '~/vue_shared/components/loading_icon.vue'; import { s__, __ } from '~/locale'; + import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; import mrWidgetAuthorTime from '../../components/mr_widget_author_time.vue'; import statusIcon from '../mr_widget_status_icon.vue'; import eventHub from '../../event_hub'; @@ -16,6 +17,7 @@ mrWidgetAuthorTime, loadingIcon, statusIcon, + ClipboardButton, }, props: { mr: { @@ -162,6 +164,18 @@ <span class="label-branch"> <a :href="mr.targetBranchPath">{{ mr.targetBranch }}</a> </span> + with + <a + :href="mr.mergeCommitPath" + class="commit-sha js-mr-merged-commit-sha" + > + {{ mr.shortMergeCommitSha }} + </a> + <clipboard-button + :title="__('Copy commit SHA to clipboard')" + :text="mr.shortMergeCommitSha" + css-class="btn-default btn-transparent btn-clipboard js-mr-merged-copy-sha" + /> </p> <p v-if="mr.sourceBranchRemoved"> {{ s__("mrWidget|The source branch has been removed") }} diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_wip.js b/app/assets/javascripts/vue_merge_request_widget/components/states/work_in_progress.vue index 44e1a616a19..fe2608e8212 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_wip.js +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/work_in_progress.vue @@ -1,25 +1,26 @@ +<script> import $ from 'jquery'; import statusIcon from '../mr_widget_status_icon.vue'; import tooltip from '../../../vue_shared/directives/tooltip'; import eventHub from '../../event_hub'; export default { - name: 'MRWidgetWIP', - props: { - mr: { type: Object, required: true }, - service: { type: Object, required: true }, + name: 'WorkInProgress', + components: { + statusIcon, }, directives: { tooltip, }, + props: { + mr: { type: Object, required: true }, + service: { type: Object, required: true }, + }, data() { return { isMakingRequest: false, }; }, - components: { - statusIcon, - }, methods: { removeWIP() { this.isMakingRequest = true; @@ -36,32 +37,40 @@ export default { }); }, }, - template: ` - <div class="mr-widget-body media"> - <status-icon status="warning" :show-disabled-button="Boolean(mr.removeWIPPath)" /> - <div class="media-body space-children"> - <span class="bold"> - This is a Work in Progress - <i - v-tooltip - class="fa fa-question-circle" - title="When this merge request is ready, remove the WIP: prefix from the title to allow it to be merged" - aria-label="When this merge request is ready, remove the WIP: prefix from the title to allow it to be merged"> - </i> - </span> - <button - v-if="mr.removeWIPPath" - @click="removeWIP" - :disabled="isMakingRequest" - type="button" - class="btn btn-default btn-xs js-remove-wip"> - <i - v-if="isMakingRequest" - class="fa fa-spinner fa-spin" - aria-hidden="true" /> - Resolve WIP status - </button> - </div> - </div> - `, }; +</script> + +<template> + <div class="mr-widget-body media"> + <status-icon + status="warning" + :show-disabled-button="Boolean(mr.removeWIPPath)" + /> + <div class="media-body space-children"> + <span class="bold"> + This is a Work in Progress + <i + v-tooltip + class="fa fa-question-circle" + title="When this merge request is ready, + remove the WIP: prefix from the title to allow it to be merged" + aria-label="When this merge request is ready, + remove the WIP: prefix from the title to allow it to be merged"> + </i> + </span> + <button + v-if="mr.removeWIPPath" + @click="removeWIP" + :disabled="isMakingRequest" + type="button" + class="btn btn-default btn-xs js-remove-wip"> + <i + v-if="isMakingRequest" + class="fa fa-spinner fa-spin" + aria-hidden="true"> + </i> + Resolve WIP status + </button> + </div> + </div> +</template> diff --git a/app/assets/javascripts/vue_merge_request_widget/dependencies.js b/app/assets/javascripts/vue_merge_request_widget/dependencies.js index 3b5c973e4a0..7f5f28091da 100644 --- a/app/assets/javascripts/vue_merge_request_widget/dependencies.js +++ b/app/assets/javascripts/vue_merge_request_widget/dependencies.js @@ -21,7 +21,7 @@ export { default as MergedState } from './components/states/mr_widget_merged.vue export { default as FailedToMerge } from './components/states/mr_widget_failed_to_merge.vue'; export { default as ClosedState } from './components/states/mr_widget_closed.vue'; export { default as MergingState } from './components/states/mr_widget_merging.vue'; -export { default as WipState } from './components/states/mr_widget_wip'; +export { default as WorkInProgressState } from './components/states/work_in_progress.vue'; export { default as ArchivedState } from './components/states/mr_widget_archived.vue'; export { default as ConflictsState } from './components/states/mr_widget_conflicts.vue'; export { default as NothingToMergeState } from './components/states/nothing_to_merge.vue'; diff --git a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js index 0be5d9e5a55..345f9ac1b4b 100644 --- a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js +++ b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js @@ -12,7 +12,7 @@ import { ClosedState, MergingState, RebaseState, - WipState, + WorkInProgressState, ArchivedState, ConflictsState, NothingToMergeState, @@ -220,7 +220,7 @@ export default { 'mr-widget-closed': ClosedState, 'mr-widget-merging': MergingState, 'mr-widget-failed-to-merge': FailedToMerge, - 'mr-widget-wip': WipState, + 'mr-widget-wip': WorkInProgressState, 'mr-widget-archived': ArchivedState, 'mr-widget-conflicts': ConflictsState, 'mr-widget-nothing-to-merge': NothingToMergeState, diff --git a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js index a47ca9fae86..83b7b054e6f 100644 --- a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js +++ b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js @@ -20,6 +20,7 @@ export default class MergeRequestStore { this.sourceBranch = data.source_branch; this.mergeStatus = data.merge_status; this.commitMessage = data.merge_commit_message; + this.shortMergeCommitSha = data.short_merge_commit_sha; this.commitMessageWithDescription = data.merge_commit_message_with_description; this.commitsCount = data.commits_count; this.divergedCommitsCount = data.diverged_commits_count; @@ -65,6 +66,7 @@ export default class MergeRequestStore { this.createIssueToResolveDiscussionsPath = data.create_issue_to_resolve_discussions_path; this.mergeCheckPath = data.merge_check_path; this.mergeActionsContentPath = data.commit_change_content_path; + this.mergeCommitPath = data.merge_commit_path; this.isRemovingSourceBranch = this.isRemovingSourceBranch || false; this.isOpen = data.state === 'opened'; this.hasMergeableDiscussionsState = data.mergeable_discussions_state === false; diff --git a/app/assets/javascripts/vue_shared/components/file_icon.vue b/app/assets/javascripts/vue_shared/components/file_icon.vue index ee1c3498748..be2755452e2 100644 --- a/app/assets/javascripts/vue_shared/components/file_icon.vue +++ b/app/assets/javascripts/vue_shared/components/file_icon.vue @@ -1,9 +1,9 @@ <script> - import getIconForFile from './file_icon/file_icon_map'; - import loadingIcon from '../../vue_shared/components/loading_icon.vue'; - import icon from '../../vue_shared/components/icon.vue'; +import getIconForFile from './file_icon/file_icon_map'; +import loadingIcon from '../../vue_shared/components/loading_icon.vue'; +import icon from '../../vue_shared/components/icon.vue'; - /* This is a re-usable vue component for rendering a svg sprite +/* This is a re-usable vue component for rendering a svg sprite icon Sample configuration: @@ -15,60 +15,60 @@ /> */ - export default { - components: { - loadingIcon, - icon, +export default { + components: { + loadingIcon, + icon, + }, + props: { + fileName: { + type: String, + required: true, }, - props: { - fileName: { - type: String, - required: true, - }, - folder: { - type: Boolean, - required: false, - default: false, - }, + folder: { + type: Boolean, + required: false, + default: false, + }, - opened: { - type: Boolean, - required: false, - default: false, - }, + opened: { + type: Boolean, + required: false, + default: false, + }, - loading: { - type: Boolean, - required: false, - default: false, - }, + loading: { + type: Boolean, + required: false, + default: false, + }, - size: { - type: Number, - required: false, - default: 16, - }, + size: { + type: Number, + required: false, + default: 16, + }, - cssClasses: { - type: String, - required: false, - default: '', - }, + cssClasses: { + type: String, + required: false, + default: '', + }, + }, + computed: { + spriteHref() { + const iconName = getIconForFile(this.fileName) || 'file'; + return `${gon.sprite_file_icons}#${iconName}`; + }, + folderIconName() { + return this.opened ? 'folder-open' : 'folder'; }, - computed: { - spriteHref() { - const iconName = getIconForFile(this.fileName) || 'file'; - return `${gon.sprite_file_icons}#${iconName}`; - }, - folderIconName() { - return this.opened ? 'folder-open' : 'folder'; - }, - iconSizeClass() { - return this.size ? `s${this.size}` : ''; - }, + iconSizeClass() { + return this.size ? `s${this.size}` : ''; }, - }; + }, +}; </script> <template> <span> @@ -82,6 +82,7 @@ v-if="!loading && folder" :name="folderIconName" :size="size" + css-classes="folder-icon" /> <loading-icon v-if="loading" diff --git a/app/assets/javascripts/vue_shared/components/icon.vue b/app/assets/javascripts/vue_shared/components/icon.vue index 1a0df49bc29..c42c4a1fbe7 100644 --- a/app/assets/javascripts/vue_shared/components/icon.vue +++ b/app/assets/javascripts/vue_shared/components/icon.vue @@ -65,6 +65,9 @@ export default { spriteHref() { return `${gon.sprite_icons}#${this.name}`; }, + iconTestClass() { + return `ic-${this.name}`; + }, iconSizeClass() { return this.size ? `s${this.size}` : ''; }, @@ -74,7 +77,7 @@ export default { <template> <svg - :class="[iconSizeClass, cssClasses]" + :class="[iconSizeClass, iconTestClass, cssClasses]" :width="width" :height="height" :x="x" diff --git a/app/assets/javascripts/vue_shared/components/identicon.vue b/app/assets/javascripts/vue_shared/components/identicon.vue index 0a30f467b08..23010f40f26 100644 --- a/app/assets/javascripts/vue_shared/components/identicon.vue +++ b/app/assets/javascripts/vue_shared/components/identicon.vue @@ -17,7 +17,7 @@ export default { }, computed: { /** - * This method is based on app/helpers/application_helper.rb#project_identicon + * This method is based on app/helpers/avatars_helper.rb#project_identicon */ identiconStyles() { const allowedColors = [ diff --git a/app/assets/javascripts/vue_shared/components/navigation_tabs.vue b/app/assets/javascripts/vue_shared/components/navigation_tabs.vue index b33a0101dbf..92d187e24bf 100644 --- a/app/assets/javascripts/vue_shared/components/navigation_tabs.vue +++ b/app/assets/javascripts/vue_shared/components/navigation_tabs.vue @@ -1,53 +1,53 @@ <script> - import $ from 'jquery'; +import $ from 'jquery'; - /** - * Given an array of tabs, renders non linked bootstrap tabs. - * When a tab is clicked it will trigger an event and provide the clicked scope. - * - * This component is used in apps that handle the API call. - * If you only need to change the URL this component should not be used. - * - * @example - * <navigation-tabs - * :tabs="[ - * { - * name: String, - * scope: String, - * count: Number || Undefined, - * isActive: Boolean, - * }, - * ]" - * @onChangeTab="onChangeTab" - * /> - */ - export default { - name: 'NavigationTabs', - props: { - tabs: { - type: Array, - required: true, - }, - scope: { - type: String, - required: false, - default: '', - }, +/** + * Given an array of tabs, renders non linked bootstrap tabs. + * When a tab is clicked it will trigger an event and provide the clicked scope. + * + * This component is used in apps that handle the API call. + * If you only need to change the URL this component should not be used. + * + * @example + * <navigation-tabs + * :tabs="[ + * { + * name: String, + * scope: String, + * count: Number || Undefined || Null, + * isActive: Boolean, + * }, + * ]" + * @onChangeTab="onChangeTab" + * /> + */ +export default { + name: 'NavigationTabs', + props: { + tabs: { + type: Array, + required: true, }, - mounted() { - $(document).trigger('init.scrolling-tabs'); + scope: { + type: String, + required: false, + default: '', + }, + }, + mounted() { + $(document).trigger('init.scrolling-tabs'); + }, + methods: { + shouldRenderBadge(count) { + // 0 is valid in a badge, but evaluates to false, we need to check for undefined or null + return !(count === undefined || count === null); }, - methods: { - shouldRenderBadge(count) { - // 0 is valid in a badge, but evaluates to false, we need to check for undefined - return count !== undefined; - }, - onTabClick(tab) { - this.$emit('onChangeTab', tab.scope); - }, + onTabClick(tab) { + this.$emit('onChangeTab', tab.scope); }, - }; + }, +}; </script> <template> <ul class="nav-links scrolling-tabs separator"> diff --git a/app/assets/javascripts/vue_shared/models/label.js b/app/assets/javascripts/vue_shared/models/label.js index 70b9efe0c68..d29c7fe973a 100644 --- a/app/assets/javascripts/vue_shared/models/label.js +++ b/app/assets/javascripts/vue_shared/models/label.js @@ -1,4 +1,4 @@ -class ListLabel { +export default class ListLabel { constructor(obj) { this.id = obj.id; this.title = obj.title; diff --git a/app/assets/stylesheets/emoji_sprites.scss b/app/assets/stylesheets/emoji_sprites.scss new file mode 100644 index 00000000000..8f6134c474b --- /dev/null +++ b/app/assets/stylesheets/emoji_sprites.scss @@ -0,0 +1,5403 @@ +// Automatic Prettier Formatting for this big file +// scss-lint:disable EmptyLineBetweenBlocks +.emoji-zzz { + background-position: 0 0; +} +.emoji-1234 { + background-position: -20px 0; +} +.emoji-1F627 { + background-position: 0 -20px; +} +.emoji-8ball { + background-position: -20px -20px; +} +.emoji-a { + background-position: -40px 0; +} +.emoji-ab { + background-position: -40px -20px; +} +.emoji-abc { + background-position: 0 -40px; +} +.emoji-abcd { + background-position: -20px -40px; +} +.emoji-accept { + background-position: -40px -40px; +} +.emoji-aerial_tramway { + background-position: -60px 0; +} +.emoji-airplane { + background-position: -60px -20px; +} +.emoji-airplane_arriving { + background-position: -60px -40px; +} +.emoji-airplane_departure { + background-position: 0 -60px; +} +.emoji-airplane_small { + background-position: -20px -60px; +} +.emoji-alarm_clock { + background-position: -40px -60px; +} +.emoji-alembic { + background-position: -60px -60px; +} +.emoji-alien { + background-position: -80px 0; +} +.emoji-ambulance { + background-position: -80px -20px; +} +.emoji-amphora { + background-position: -80px -40px; +} +.emoji-anchor { + background-position: -80px -60px; +} +.emoji-angel { + background-position: 0 -80px; +} +.emoji-angel_tone1 { + background-position: -20px -80px; +} +.emoji-angel_tone2 { + background-position: -40px -80px; +} +.emoji-angel_tone3 { + background-position: -60px -80px; +} +.emoji-angel_tone4 { + background-position: -80px -80px; +} +.emoji-angel_tone5 { + background-position: -100px 0; +} +.emoji-anger { + background-position: -100px -20px; +} +.emoji-anger_right { + background-position: -100px -40px; +} +.emoji-angry { + background-position: -100px -60px; +} +.emoji-ant { + background-position: -100px -80px; +} +.emoji-apple { + background-position: 0 -100px; +} +.emoji-aquarius { + background-position: -20px -100px; +} +.emoji-aries { + background-position: -40px -100px; +} +.emoji-arrow_backward { + background-position: -60px -100px; +} +.emoji-arrow_double_down { + background-position: -80px -100px; +} +.emoji-arrow_double_up { + background-position: -100px -100px; +} +.emoji-arrow_down { + background-position: -120px 0; +} +.emoji-arrow_down_small { + background-position: -120px -20px; +} +.emoji-arrow_forward { + background-position: -120px -40px; +} +.emoji-arrow_heading_down { + background-position: -120px -60px; +} +.emoji-arrow_heading_up { + background-position: -120px -80px; +} +.emoji-arrow_left { + background-position: -120px -100px; +} +.emoji-arrow_lower_left { + background-position: 0 -120px; +} +.emoji-arrow_lower_right { + background-position: -20px -120px; +} +.emoji-arrow_right { + background-position: -40px -120px; +} +.emoji-arrow_right_hook { + background-position: -60px -120px; +} +.emoji-arrow_up { + background-position: -80px -120px; +} +.emoji-arrow_up_down { + background-position: -100px -120px; +} +.emoji-arrow_up_small { + background-position: -120px -120px; +} +.emoji-arrow_upper_left { + background-position: -140px 0; +} +.emoji-arrow_upper_right { + background-position: -140px -20px; +} +.emoji-arrows_clockwise { + background-position: -140px -40px; +} +.emoji-arrows_counterclockwise { + background-position: -140px -60px; +} +.emoji-art { + background-position: -140px -80px; +} +.emoji-articulated_lorry { + background-position: -140px -100px; +} +.emoji-asterisk { + background-position: -140px -120px; +} +.emoji-astonished { + background-position: 0 -140px; +} +.emoji-athletic_shoe { + background-position: -20px -140px; +} +.emoji-atm { + background-position: -40px -140px; +} +.emoji-atom { + background-position: -60px -140px; +} +.emoji-avocado { + background-position: -80px -140px; +} +.emoji-b { + background-position: -100px -140px; +} +.emoji-baby { + background-position: -120px -140px; +} +.emoji-baby_bottle { + background-position: -140px -140px; +} +.emoji-baby_chick { + background-position: -160px 0; +} +.emoji-baby_symbol { + background-position: -160px -20px; +} +.emoji-baby_tone1 { + background-position: -160px -40px; +} +.emoji-baby_tone2 { + background-position: -160px -60px; +} +.emoji-baby_tone3 { + background-position: -160px -80px; +} +.emoji-baby_tone4 { + background-position: -160px -100px; +} +.emoji-baby_tone5 { + background-position: -160px -120px; +} +.emoji-back { + background-position: -160px -140px; +} +.emoji-bacon { + background-position: 0 -160px; +} +.emoji-badminton { + background-position: -20px -160px; +} +.emoji-baggage_claim { + background-position: -40px -160px; +} +.emoji-balloon { + background-position: -60px -160px; +} +.emoji-ballot_box { + background-position: -80px -160px; +} +.emoji-ballot_box_with_check { + background-position: -100px -160px; +} +.emoji-bamboo { + background-position: -120px -160px; +} +.emoji-banana { + background-position: -140px -160px; +} +.emoji-bangbang { + background-position: -160px -160px; +} +.emoji-bank { + background-position: -180px 0; +} +.emoji-bar_chart { + background-position: -180px -20px; +} +.emoji-barber { + background-position: -180px -40px; +} +.emoji-baseball { + background-position: -180px -60px; +} +.emoji-basketball { + background-position: -180px -80px; +} +.emoji-basketball_player { + background-position: -180px -100px; +} +.emoji-basketball_player_tone1 { + background-position: -180px -120px; +} +.emoji-basketball_player_tone2 { + background-position: -180px -140px; +} +.emoji-basketball_player_tone3 { + background-position: -180px -160px; +} +.emoji-basketball_player_tone4 { + background-position: 0 -180px; +} +.emoji-basketball_player_tone5 { + background-position: -20px -180px; +} +.emoji-bat { + background-position: -40px -180px; +} +.emoji-bath { + background-position: -60px -180px; +} +.emoji-bath_tone1 { + background-position: -80px -180px; +} +.emoji-bath_tone2 { + background-position: -100px -180px; +} +.emoji-bath_tone3 { + background-position: -120px -180px; +} +.emoji-bath_tone4 { + background-position: -140px -180px; +} +.emoji-bath_tone5 { + background-position: -160px -180px; +} +.emoji-bathtub { + background-position: -180px -180px; +} +.emoji-battery { + background-position: -200px 0; +} +.emoji-beach { + background-position: -200px -20px; +} +.emoji-beach_umbrella { + background-position: -200px -40px; +} +.emoji-bear { + background-position: -200px -60px; +} +.emoji-bed { + background-position: -200px -80px; +} +.emoji-bee { + background-position: -200px -100px; +} +.emoji-beer { + background-position: -200px -120px; +} +.emoji-beers { + background-position: -200px -140px; +} +.emoji-beetle { + background-position: -200px -160px; +} +.emoji-beginner { + background-position: -200px -180px; +} +.emoji-bell { + background-position: 0 -200px; +} +.emoji-bellhop { + background-position: -20px -200px; +} +.emoji-bento { + background-position: -40px -200px; +} +.emoji-bicyclist { + background-position: -60px -200px; +} +.emoji-bicyclist_tone1 { + background-position: -80px -200px; +} +.emoji-bicyclist_tone2 { + background-position: -100px -200px; +} +.emoji-bicyclist_tone3 { + background-position: -120px -200px; +} +.emoji-bicyclist_tone4 { + background-position: -140px -200px; +} +.emoji-bicyclist_tone5 { + background-position: -160px -200px; +} +.emoji-bike { + background-position: -180px -200px; +} +.emoji-bikini { + background-position: -200px -200px; +} +.emoji-biohazard { + background-position: -220px 0; +} +.emoji-bird { + background-position: -220px -20px; +} +.emoji-birthday { + background-position: -220px -40px; +} +.emoji-black_circle { + background-position: -220px -60px; +} +.emoji-black_heart { + background-position: -220px -80px; +} +.emoji-black_joker { + background-position: -220px -100px; +} +.emoji-black_large_square { + background-position: -220px -120px; +} +.emoji-black_medium_small_square { + background-position: -220px -140px; +} +.emoji-black_medium_square { + background-position: -220px -160px; +} +.emoji-black_nib { + background-position: -220px -180px; +} +.emoji-black_small_square { + background-position: -220px -200px; +} +.emoji-black_square_button { + background-position: 0 -220px; +} +.emoji-blossom { + background-position: -20px -220px; +} +.emoji-blowfish { + background-position: -40px -220px; +} +.emoji-blue_book { + background-position: -60px -220px; +} +.emoji-blue_car { + background-position: -80px -220px; +} +.emoji-blue_heart { + background-position: -100px -220px; +} +.emoji-blush { + background-position: -120px -220px; +} +.emoji-boar { + background-position: -140px -220px; +} +.emoji-bomb { + background-position: -160px -220px; +} +.emoji-book { + background-position: -180px -220px; +} +.emoji-bookmark { + background-position: -200px -220px; +} +.emoji-bookmark_tabs { + background-position: -220px -220px; +} +.emoji-books { + background-position: -240px 0; +} +.emoji-boom { + background-position: -240px -20px; +} +.emoji-boot { + background-position: -240px -40px; +} +.emoji-bouquet { + background-position: -240px -60px; +} +.emoji-bow { + background-position: -240px -80px; +} +.emoji-bow_and_arrow { + background-position: -240px -100px; +} +.emoji-bow_tone1 { + background-position: -240px -120px; +} +.emoji-bow_tone2 { + background-position: -240px -140px; +} +.emoji-bow_tone3 { + background-position: -240px -160px; +} +.emoji-bow_tone4 { + background-position: -240px -180px; +} +.emoji-bow_tone5 { + background-position: -240px -200px; +} +.emoji-bowling { + background-position: -240px -220px; +} +.emoji-boxing_glove { + background-position: 0 -240px; +} +.emoji-boy { + background-position: -20px -240px; +} +.emoji-boy_tone1 { + background-position: -40px -240px; +} +.emoji-boy_tone2 { + background-position: -60px -240px; +} +.emoji-boy_tone3 { + background-position: -80px -240px; +} +.emoji-boy_tone4 { + background-position: -100px -240px; +} +.emoji-boy_tone5 { + background-position: -120px -240px; +} +.emoji-bread { + background-position: -140px -240px; +} +.emoji-bride_with_veil { + background-position: -160px -240px; +} +.emoji-bride_with_veil_tone1 { + background-position: -180px -240px; +} +.emoji-bride_with_veil_tone2 { + background-position: -200px -240px; +} +.emoji-bride_with_veil_tone3 { + background-position: -220px -240px; +} +.emoji-bride_with_veil_tone4 { + background-position: -240px -240px; +} +.emoji-bride_with_veil_tone5 { + background-position: -260px 0; +} +.emoji-bridge_at_night { + background-position: -260px -20px; +} +.emoji-briefcase { + background-position: -260px -40px; +} +.emoji-broken_heart { + background-position: -260px -60px; +} +.emoji-bug { + background-position: -260px -80px; +} +.emoji-bulb { + background-position: -260px -100px; +} +.emoji-bullettrain_front { + background-position: -260px -120px; +} +.emoji-bullettrain_side { + background-position: -260px -140px; +} +.emoji-burrito { + background-position: -260px -160px; +} +.emoji-bus { + background-position: -260px -180px; +} +.emoji-busstop { + background-position: -260px -200px; +} +.emoji-bust_in_silhouette { + background-position: -260px -220px; +} +.emoji-busts_in_silhouette { + background-position: -260px -240px; +} +.emoji-butterfly { + background-position: 0 -260px; +} +.emoji-cactus { + background-position: -20px -260px; +} +.emoji-cake { + background-position: -40px -260px; +} +.emoji-calendar { + background-position: -60px -260px; +} +.emoji-calendar_spiral { + background-position: -80px -260px; +} +.emoji-call_me { + background-position: -100px -260px; +} +.emoji-call_me_tone1 { + background-position: -120px -260px; +} +.emoji-call_me_tone2 { + background-position: -140px -260px; +} +.emoji-call_me_tone3 { + background-position: -160px -260px; +} +.emoji-call_me_tone4 { + background-position: -180px -260px; +} +.emoji-call_me_tone5 { + background-position: -200px -260px; +} +.emoji-calling { + background-position: -220px -260px; +} +.emoji-camel { + background-position: -240px -260px; +} +.emoji-camera { + background-position: -260px -260px; +} +.emoji-camera_with_flash { + background-position: -280px 0; +} +.emoji-camping { + background-position: -280px -20px; +} +.emoji-cancer { + background-position: -280px -40px; +} +.emoji-candle { + background-position: -280px -60px; +} +.emoji-candy { + background-position: -280px -80px; +} +.emoji-canoe { + background-position: -280px -100px; +} +.emoji-capital_abcd { + background-position: -280px -120px; +} +.emoji-capricorn { + background-position: -280px -140px; +} +.emoji-card_box { + background-position: -280px -160px; +} +.emoji-card_index { + background-position: -280px -180px; +} +.emoji-carousel_horse { + background-position: -280px -200px; +} +.emoji-carrot { + background-position: -280px -220px; +} +.emoji-cartwheel { + background-position: -280px -240px; +} +.emoji-cartwheel_tone1 { + background-position: -280px -260px; +} +.emoji-cartwheel_tone2 { + background-position: 0 -280px; +} +.emoji-cartwheel_tone3 { + background-position: -20px -280px; +} +.emoji-cartwheel_tone4 { + background-position: -40px -280px; +} +.emoji-cartwheel_tone5 { + background-position: -60px -280px; +} +.emoji-cat { + background-position: -80px -280px; +} +.emoji-cat2 { + background-position: -100px -280px; +} +.emoji-cd { + background-position: -120px -280px; +} +.emoji-chains { + background-position: -140px -280px; +} +.emoji-champagne { + background-position: -160px -280px; +} +.emoji-champagne_glass { + background-position: -180px -280px; +} +.emoji-chart { + background-position: -200px -280px; +} +.emoji-chart_with_downwards_trend { + background-position: -220px -280px; +} +.emoji-chart_with_upwards_trend { + background-position: -240px -280px; +} +.emoji-checkered_flag { + background-position: -260px -280px; +} +.emoji-cheese { + background-position: -280px -280px; +} +.emoji-cherries { + background-position: -300px 0; +} +.emoji-cherry_blossom { + background-position: -300px -20px; +} +.emoji-chestnut { + background-position: -300px -40px; +} +.emoji-chicken { + background-position: -300px -60px; +} +.emoji-children_crossing { + background-position: -300px -80px; +} +.emoji-chipmunk { + background-position: -300px -100px; +} +.emoji-chocolate_bar { + background-position: -300px -120px; +} +.emoji-christmas_tree { + background-position: -300px -140px; +} +.emoji-church { + background-position: -300px -160px; +} +.emoji-cinema { + background-position: -300px -180px; +} +.emoji-circus_tent { + background-position: -300px -200px; +} +.emoji-city_dusk { + background-position: -300px -220px; +} +.emoji-city_sunset { + background-position: -300px -240px; +} +.emoji-cityscape { + background-position: -300px -260px; +} +.emoji-cl { + background-position: -300px -280px; +} +.emoji-clap { + background-position: 0 -300px; +} +.emoji-clap_tone1 { + background-position: -20px -300px; +} +.emoji-clap_tone2 { + background-position: -40px -300px; +} +.emoji-clap_tone3 { + background-position: -60px -300px; +} +.emoji-clap_tone4 { + background-position: -80px -300px; +} +.emoji-clap_tone5 { + background-position: -100px -300px; +} +.emoji-clapper { + background-position: -120px -300px; +} +.emoji-classical_building { + background-position: -140px -300px; +} +.emoji-clipboard { + background-position: -160px -300px; +} +.emoji-clock { + background-position: -180px -300px; +} +.emoji-clock1 { + background-position: -200px -300px; +} +.emoji-clock10 { + background-position: -220px -300px; +} +.emoji-clock1030 { + background-position: -240px -300px; +} +.emoji-clock11 { + background-position: -260px -300px; +} +.emoji-clock1130 { + background-position: -280px -300px; +} +.emoji-clock12 { + background-position: -300px -300px; +} +.emoji-clock1230 { + background-position: -320px 0; +} +.emoji-clock130 { + background-position: -320px -20px; +} +.emoji-clock2 { + background-position: -320px -40px; +} +.emoji-clock230 { + background-position: -320px -60px; +} +.emoji-clock3 { + background-position: -320px -80px; +} +.emoji-clock330 { + background-position: -320px -100px; +} +.emoji-clock4 { + background-position: -320px -120px; +} +.emoji-clock430 { + background-position: -320px -140px; +} +.emoji-clock5 { + background-position: -320px -160px; +} +.emoji-clock530 { + background-position: -320px -180px; +} +.emoji-clock6 { + background-position: -320px -200px; +} +.emoji-clock630 { + background-position: -320px -220px; +} +.emoji-clock7 { + background-position: -320px -240px; +} +.emoji-clock730 { + background-position: -320px -260px; +} +.emoji-clock8 { + background-position: -320px -280px; +} +.emoji-clock830 { + background-position: -320px -300px; +} +.emoji-clock9 { + background-position: 0 -320px; +} +.emoji-clock930 { + background-position: -20px -320px; +} +.emoji-closed_book { + background-position: -40px -320px; +} +.emoji-closed_lock_with_key { + background-position: -60px -320px; +} +.emoji-closed_umbrella { + background-position: -80px -320px; +} +.emoji-cloud { + background-position: -100px -320px; +} +.emoji-cloud_lightning { + background-position: -120px -320px; +} +.emoji-cloud_rain { + background-position: -140px -320px; +} +.emoji-cloud_snow { + background-position: -160px -320px; +} +.emoji-cloud_tornado { + background-position: -180px -320px; +} +.emoji-clown { + background-position: -200px -320px; +} +.emoji-clubs { + background-position: -220px -320px; +} +.emoji-cocktail { + background-position: -240px -320px; +} +.emoji-coffee { + background-position: -260px -320px; +} +.emoji-coffin { + background-position: -280px -320px; +} +.emoji-cold_sweat { + background-position: -300px -320px; +} +.emoji-comet { + background-position: -320px -320px; +} +.emoji-compression { + background-position: -340px 0; +} +.emoji-computer { + background-position: -340px -20px; +} +.emoji-confetti_ball { + background-position: -340px -40px; +} +.emoji-confounded { + background-position: -340px -60px; +} +.emoji-confused { + background-position: -340px -80px; +} +.emoji-congratulations { + background-position: -340px -100px; +} +.emoji-construction { + background-position: -340px -120px; +} +.emoji-construction_site { + background-position: -340px -140px; +} +.emoji-construction_worker { + background-position: -340px -160px; +} +.emoji-construction_worker_tone1 { + background-position: -340px -180px; +} +.emoji-construction_worker_tone2 { + background-position: -340px -200px; +} +.emoji-construction_worker_tone3 { + background-position: -340px -220px; +} +.emoji-construction_worker_tone4 { + background-position: -340px -240px; +} +.emoji-construction_worker_tone5 { + background-position: -340px -260px; +} +.emoji-control_knobs { + background-position: -340px -280px; +} +.emoji-convenience_store { + background-position: -340px -300px; +} +.emoji-cookie { + background-position: -340px -320px; +} +.emoji-cooking { + background-position: 0 -340px; +} +.emoji-cool { + background-position: -20px -340px; +} +.emoji-cop { + background-position: -40px -340px; +} +.emoji-cop_tone1 { + background-position: -60px -340px; +} +.emoji-cop_tone2 { + background-position: -80px -340px; +} +.emoji-cop_tone3 { + background-position: -100px -340px; +} +.emoji-cop_tone4 { + background-position: -120px -340px; +} +.emoji-cop_tone5 { + background-position: -140px -340px; +} +.emoji-copyright { + background-position: -160px -340px; +} +.emoji-corn { + background-position: -180px -340px; +} +.emoji-couch { + background-position: -200px -340px; +} +.emoji-couple { + background-position: -220px -340px; +} +.emoji-couple_mm { + background-position: -240px -340px; +} +.emoji-couple_with_heart { + background-position: -260px -340px; +} +.emoji-couple_ww { + background-position: -280px -340px; +} +.emoji-couplekiss { + background-position: -300px -340px; +} +.emoji-cow { + background-position: -320px -340px; +} +.emoji-cow2 { + background-position: -340px -340px; +} +.emoji-cowboy { + background-position: -360px 0; +} +.emoji-crab { + background-position: -360px -20px; +} +.emoji-crayon { + background-position: -360px -40px; +} +.emoji-credit_card { + background-position: -360px -60px; +} +.emoji-crescent_moon { + background-position: -360px -80px; +} +.emoji-cricket { + background-position: -360px -100px; +} +.emoji-crocodile { + background-position: -360px -120px; +} +.emoji-croissant { + background-position: -360px -140px; +} +.emoji-cross { + background-position: -360px -160px; +} +.emoji-crossed_flags { + background-position: -360px -180px; +} +.emoji-crossed_swords { + background-position: -360px -200px; +} +.emoji-crown { + background-position: -360px -220px; +} +.emoji-cruise_ship { + background-position: -360px -240px; +} +.emoji-cry { + background-position: -360px -260px; +} +.emoji-crying_cat_face { + background-position: -360px -280px; +} +.emoji-crystal_ball { + background-position: -360px -300px; +} +.emoji-cucumber { + background-position: -360px -320px; +} +.emoji-cupid { + background-position: -360px -340px; +} +.emoji-curly_loop { + background-position: 0 -360px; +} +.emoji-currency_exchange { + background-position: -20px -360px; +} +.emoji-curry { + background-position: -40px -360px; +} +.emoji-custard { + background-position: -60px -360px; +} +.emoji-customs { + background-position: -80px -360px; +} +.emoji-cyclone { + background-position: -100px -360px; +} +.emoji-dagger { + background-position: -120px -360px; +} +.emoji-dancer { + background-position: -140px -360px; +} +.emoji-dancer_tone1 { + background-position: -160px -360px; +} +.emoji-dancer_tone2 { + background-position: -180px -360px; +} +.emoji-dancer_tone3 { + background-position: -200px -360px; +} +.emoji-dancer_tone4 { + background-position: -220px -360px; +} +.emoji-dancer_tone5 { + background-position: -240px -360px; +} +.emoji-dancers { + background-position: -260px -360px; +} +.emoji-dango { + background-position: -280px -360px; +} +.emoji-dark_sunglasses { + background-position: -300px -360px; +} +.emoji-dart { + background-position: -320px -360px; +} +.emoji-dash { + background-position: -340px -360px; +} +.emoji-date { + background-position: -360px -360px; +} +.emoji-deciduous_tree { + background-position: -380px 0; +} +.emoji-deer { + background-position: -380px -20px; +} +.emoji-department_store { + background-position: -380px -40px; +} +.emoji-desert { + background-position: -380px -60px; +} +.emoji-desktop { + background-position: -380px -80px; +} +.emoji-diamond_shape_with_a_dot_inside { + background-position: -380px -100px; +} +.emoji-diamonds { + background-position: -380px -120px; +} +.emoji-disappointed { + background-position: -380px -140px; +} +.emoji-disappointed_relieved { + background-position: -380px -160px; +} +.emoji-dividers { + background-position: -380px -180px; +} +.emoji-dizzy { + background-position: -380px -200px; +} +.emoji-dizzy_face { + background-position: -380px -220px; +} +.emoji-do_not_litter { + background-position: -380px -240px; +} +.emoji-dog { + background-position: -380px -260px; +} +.emoji-dog2 { + background-position: -380px -280px; +} +.emoji-dollar { + background-position: -380px -300px; +} +.emoji-dolls { + background-position: -380px -320px; +} +.emoji-dolphin { + background-position: -380px -340px; +} +.emoji-door { + background-position: -380px -360px; +} +.emoji-doughnut { + background-position: 0 -380px; +} +.emoji-dove { + background-position: -20px -380px; +} +.emoji-dragon { + background-position: -40px -380px; +} +.emoji-dragon_face { + background-position: -60px -380px; +} +.emoji-dress { + background-position: -80px -380px; +} +.emoji-dromedary_camel { + background-position: -100px -380px; +} +.emoji-drooling_face { + background-position: -120px -380px; +} +.emoji-droplet { + background-position: -140px -380px; +} +.emoji-drum { + background-position: -160px -380px; +} +.emoji-duck { + background-position: -180px -380px; +} +.emoji-dvd { + background-position: -200px -380px; +} +.emoji-e-mail { + background-position: -220px -380px; +} +.emoji-eagle { + background-position: -240px -380px; +} +.emoji-ear { + background-position: -260px -380px; +} +.emoji-ear_of_rice { + background-position: -280px -380px; +} +.emoji-ear_tone1 { + background-position: -300px -380px; +} +.emoji-ear_tone2 { + background-position: -320px -380px; +} +.emoji-ear_tone3 { + background-position: -340px -380px; +} +.emoji-ear_tone4 { + background-position: -360px -380px; +} +.emoji-ear_tone5 { + background-position: -380px -380px; +} +.emoji-earth_africa { + background-position: -400px 0; +} +.emoji-earth_americas { + background-position: -400px -20px; +} +.emoji-earth_asia { + background-position: -400px -40px; +} +.emoji-egg { + background-position: -400px -60px; +} +.emoji-eggplant { + background-position: -400px -80px; +} +.emoji-eight { + background-position: -400px -100px; +} +.emoji-eight_pointed_black_star { + background-position: -400px -120px; +} +.emoji-eight_spoked_asterisk { + background-position: -400px -140px; +} +.emoji-eject { + background-position: -400px -160px; +} +.emoji-electric_plug { + background-position: -400px -180px; +} +.emoji-elephant { + background-position: -400px -200px; +} +.emoji-end { + background-position: -400px -220px; +} +.emoji-envelope { + background-position: -400px -240px; +} +.emoji-envelope_with_arrow { + background-position: -400px -260px; +} +.emoji-euro { + background-position: -400px -280px; +} +.emoji-european_castle { + background-position: -400px -300px; +} +.emoji-european_post_office { + background-position: -400px -320px; +} +.emoji-evergreen_tree { + background-position: -400px -340px; +} +.emoji-exclamation { + background-position: -400px -360px; +} +.emoji-expressionless { + background-position: -400px -380px; +} +.emoji-eye { + background-position: 0 -400px; +} +.emoji-eye_in_speech_bubble { + background-position: -20px -400px; +} +.emoji-eyeglasses { + background-position: -40px -400px; +} +.emoji-eyes { + background-position: -60px -400px; +} +.emoji-face_palm { + background-position: -80px -400px; +} +.emoji-face_palm_tone1 { + background-position: -100px -400px; +} +.emoji-face_palm_tone2 { + background-position: -120px -400px; +} +.emoji-face_palm_tone3 { + background-position: -140px -400px; +} +.emoji-face_palm_tone4 { + background-position: -160px -400px; +} +.emoji-face_palm_tone5 { + background-position: -180px -400px; +} +.emoji-factory { + background-position: -200px -400px; +} +.emoji-fallen_leaf { + background-position: -220px -400px; +} +.emoji-family { + background-position: -240px -400px; +} +.emoji-family_mmb { + background-position: -260px -400px; +} +.emoji-family_mmbb { + background-position: -280px -400px; +} +.emoji-family_mmg { + background-position: -300px -400px; +} +.emoji-family_mmgb { + background-position: -320px -400px; +} +.emoji-family_mmgg { + background-position: -340px -400px; +} +.emoji-family_mwbb { + background-position: -360px -400px; +} +.emoji-family_mwg { + background-position: -380px -400px; +} +.emoji-family_mwgb { + background-position: -400px -400px; +} +.emoji-family_mwgg { + background-position: -420px 0; +} +.emoji-family_wwb { + background-position: -420px -20px; +} +.emoji-family_wwbb { + background-position: -420px -40px; +} +.emoji-family_wwg { + background-position: -420px -60px; +} +.emoji-family_wwgb { + background-position: -420px -80px; +} +.emoji-family_wwgg { + background-position: -420px -100px; +} +.emoji-fast_forward { + background-position: -420px -120px; +} +.emoji-fax { + background-position: -420px -140px; +} +.emoji-fearful { + background-position: -420px -160px; +} +.emoji-feet { + background-position: -420px -180px; +} +.emoji-fencer { + background-position: -420px -200px; +} +.emoji-ferris_wheel { + background-position: -420px -220px; +} +.emoji-ferry { + background-position: -420px -240px; +} +.emoji-field_hockey { + background-position: -420px -260px; +} +.emoji-file_cabinet { + background-position: -420px -280px; +} +.emoji-file_folder { + background-position: -420px -300px; +} +.emoji-film_frames { + background-position: -420px -320px; +} +.emoji-fingers_crossed { + background-position: -420px -340px; +} +.emoji-fingers_crossed_tone1 { + background-position: -420px -360px; +} +.emoji-fingers_crossed_tone2 { + background-position: -420px -380px; +} +.emoji-fingers_crossed_tone3 { + background-position: -420px -400px; +} +.emoji-fingers_crossed_tone4 { + background-position: 0 -420px; +} +.emoji-fingers_crossed_tone5 { + background-position: -20px -420px; +} +.emoji-fire { + background-position: -40px -420px; +} +.emoji-fire_engine { + background-position: -60px -420px; +} +.emoji-fireworks { + background-position: -80px -420px; +} +.emoji-first_place { + background-position: -100px -420px; +} +.emoji-first_quarter_moon { + background-position: -120px -420px; +} +.emoji-first_quarter_moon_with_face { + background-position: -140px -420px; +} +.emoji-fish { + background-position: -160px -420px; +} +.emoji-fish_cake { + background-position: -180px -420px; +} +.emoji-fishing_pole_and_fish { + background-position: -200px -420px; +} +.emoji-fist { + background-position: -220px -420px; +} +.emoji-fist_tone1 { + background-position: -240px -420px; +} +.emoji-fist_tone2 { + background-position: -260px -420px; +} +.emoji-fist_tone3 { + background-position: -280px -420px; +} +.emoji-fist_tone4 { + background-position: -300px -420px; +} +.emoji-fist_tone5 { + background-position: -320px -420px; +} +.emoji-five { + background-position: -340px -420px; +} +.emoji-flag_ac { + background-position: -360px -420px; +} +.emoji-flag_ad { + background-position: -380px -420px; +} +.emoji-flag_ae { + background-position: -400px -420px; +} +.emoji-flag_af { + background-position: -420px -420px; +} +.emoji-flag_ag { + background-position: -440px 0; +} +.emoji-flag_ai { + background-position: -440px -20px; +} +.emoji-flag_al { + background-position: -440px -40px; +} +.emoji-flag_am { + background-position: -440px -60px; +} +.emoji-flag_ao { + background-position: -440px -80px; +} +.emoji-flag_aq { + background-position: -440px -100px; +} +.emoji-flag_ar { + background-position: -440px -120px; +} +.emoji-flag_as { + background-position: -440px -140px; +} +.emoji-flag_at { + background-position: -440px -160px; +} +.emoji-flag_au { + background-position: -440px -180px; +} +.emoji-flag_aw { + background-position: -440px -200px; +} +.emoji-flag_ax { + background-position: -440px -220px; +} +.emoji-flag_az { + background-position: -440px -240px; +} +.emoji-flag_ba { + background-position: -440px -260px; +} +.emoji-flag_bb { + background-position: -440px -280px; +} +.emoji-flag_bd { + background-position: -440px -300px; +} +.emoji-flag_be { + background-position: -440px -320px; +} +.emoji-flag_bf { + background-position: -440px -340px; +} +.emoji-flag_bg { + background-position: -440px -360px; +} +.emoji-flag_bh { + background-position: -440px -380px; +} +.emoji-flag_bi { + background-position: -440px -400px; +} +.emoji-flag_bj { + background-position: -440px -420px; +} +.emoji-flag_bl { + background-position: 0 -440px; +} +.emoji-flag_black { + background-position: -20px -440px; +} +.emoji-flag_bm { + background-position: -40px -440px; +} +.emoji-flag_bn { + background-position: -60px -440px; +} +.emoji-flag_bo { + background-position: -80px -440px; +} +.emoji-flag_bq { + background-position: -100px -440px; +} +.emoji-flag_br { + background-position: -120px -440px; +} +.emoji-flag_bs { + background-position: -140px -440px; +} +.emoji-flag_bt { + background-position: -160px -440px; +} +.emoji-flag_bv { + background-position: -180px -440px; +} +.emoji-flag_bw { + background-position: -200px -440px; +} +.emoji-flag_by { + background-position: -220px -440px; +} +.emoji-flag_bz { + background-position: -240px -440px; +} +.emoji-flag_ca { + background-position: -260px -440px; +} +.emoji-flag_cc { + background-position: -280px -440px; +} +.emoji-flag_cd { + background-position: -300px -440px; +} +.emoji-flag_cf { + background-position: -320px -440px; +} +.emoji-flag_cg { + background-position: -340px -440px; +} +.emoji-flag_ch { + background-position: -360px -440px; +} +.emoji-flag_ci { + background-position: -380px -440px; +} +.emoji-flag_ck { + background-position: -400px -440px; +} +.emoji-flag_cl { + background-position: -420px -440px; +} +.emoji-flag_cm { + background-position: -440px -440px; +} +.emoji-flag_cn { + background-position: -460px 0; +} +.emoji-flag_co { + background-position: -460px -20px; +} +.emoji-flag_cp { + background-position: -460px -40px; +} +.emoji-flag_cr { + background-position: -460px -60px; +} +.emoji-flag_cu { + background-position: -460px -80px; +} +.emoji-flag_cv { + background-position: -460px -100px; +} +.emoji-flag_cw { + background-position: -460px -120px; +} +.emoji-flag_cx { + background-position: -460px -140px; +} +.emoji-flag_cy { + background-position: -460px -160px; +} +.emoji-flag_cz { + background-position: -460px -180px; +} +.emoji-flag_de { + background-position: -460px -200px; +} +.emoji-flag_dg { + background-position: -460px -220px; +} +.emoji-flag_dj { + background-position: -460px -240px; +} +.emoji-flag_dk { + background-position: -460px -260px; +} +.emoji-flag_dm { + background-position: -460px -280px; +} +.emoji-flag_do { + background-position: -460px -300px; +} +.emoji-flag_dz { + background-position: -460px -320px; +} +.emoji-flag_ea { + background-position: -460px -340px; +} +.emoji-flag_ec { + background-position: -460px -360px; +} +.emoji-flag_ee { + background-position: -460px -380px; +} +.emoji-flag_eg { + background-position: -460px -400px; +} +.emoji-flag_eh { + background-position: -460px -420px; +} +.emoji-flag_er { + background-position: -460px -440px; +} +.emoji-flag_es { + background-position: 0 -460px; +} +.emoji-flag_et { + background-position: -20px -460px; +} +.emoji-flag_eu { + background-position: -40px -460px; +} +.emoji-flag_fi { + background-position: -60px -460px; +} +.emoji-flag_fj { + background-position: -80px -460px; +} +.emoji-flag_fk { + background-position: -100px -460px; +} +.emoji-flag_fm { + background-position: -120px -460px; +} +.emoji-flag_fo { + background-position: -140px -460px; +} +.emoji-flag_fr { + background-position: -160px -460px; +} +.emoji-flag_ga { + background-position: -180px -460px; +} +.emoji-flag_gb { + background-position: -200px -460px; +} +.emoji-flag_gd { + background-position: -220px -460px; +} +.emoji-flag_ge { + background-position: -240px -460px; +} +.emoji-flag_gf { + background-position: -260px -460px; +} +.emoji-flag_gg { + background-position: -280px -460px; +} +.emoji-flag_gh { + background-position: -300px -460px; +} +.emoji-flag_gi { + background-position: -320px -460px; +} +.emoji-flag_gl { + background-position: -340px -460px; +} +.emoji-flag_gm { + background-position: -360px -460px; +} +.emoji-flag_gn { + background-position: -380px -460px; +} +.emoji-flag_gp { + background-position: -400px -460px; +} +.emoji-flag_gq { + background-position: -420px -460px; +} +.emoji-flag_gr { + background-position: -440px -460px; +} +.emoji-flag_gs { + background-position: -460px -460px; +} +.emoji-flag_gt { + background-position: -480px 0; +} +.emoji-flag_gu { + background-position: -480px -20px; +} +.emoji-flag_gw { + background-position: -480px -40px; +} +.emoji-flag_gy { + background-position: -480px -60px; +} +.emoji-flag_hk { + background-position: -480px -80px; +} +.emoji-flag_hm { + background-position: -480px -100px; +} +.emoji-flag_hn { + background-position: -480px -120px; +} +.emoji-flag_hr { + background-position: -480px -140px; +} +.emoji-flag_ht { + background-position: -480px -160px; +} +.emoji-flag_hu { + background-position: -480px -180px; +} +.emoji-flag_ic { + background-position: -480px -200px; +} +.emoji-flag_id { + background-position: -480px -220px; +} +.emoji-flag_ie { + background-position: -480px -240px; +} +.emoji-flag_il { + background-position: -480px -260px; +} +.emoji-flag_im { + background-position: -480px -280px; +} +.emoji-flag_in { + background-position: -480px -300px; +} +.emoji-flag_io { + background-position: -480px -320px; +} +.emoji-flag_iq { + background-position: -480px -340px; +} +.emoji-flag_ir { + background-position: -480px -360px; +} +.emoji-flag_is { + background-position: -480px -380px; +} +.emoji-flag_it { + background-position: -480px -400px; +} +.emoji-flag_je { + background-position: -480px -420px; +} +.emoji-flag_jm { + background-position: -480px -440px; +} +.emoji-flag_jo { + background-position: -480px -460px; +} +.emoji-flag_jp { + background-position: 0 -480px; +} +.emoji-flag_ke { + background-position: -20px -480px; +} +.emoji-flag_kg { + background-position: -40px -480px; +} +.emoji-flag_kh { + background-position: -60px -480px; +} +.emoji-flag_ki { + background-position: -80px -480px; +} +.emoji-flag_km { + background-position: -100px -480px; +} +.emoji-flag_kn { + background-position: -120px -480px; +} +.emoji-flag_kp { + background-position: -140px -480px; +} +.emoji-flag_kr { + background-position: -160px -480px; +} +.emoji-flag_kw { + background-position: -180px -480px; +} +.emoji-flag_ky { + background-position: -200px -480px; +} +.emoji-flag_kz { + background-position: -220px -480px; +} +.emoji-flag_la { + background-position: -240px -480px; +} +.emoji-flag_lb { + background-position: -260px -480px; +} +.emoji-flag_lc { + background-position: -280px -480px; +} +.emoji-flag_li { + background-position: -300px -480px; +} +.emoji-flag_lk { + background-position: -320px -480px; +} +.emoji-flag_lr { + background-position: -340px -480px; +} +.emoji-flag_ls { + background-position: -360px -480px; +} +.emoji-flag_lt { + background-position: -380px -480px; +} +.emoji-flag_lu { + background-position: -400px -480px; +} +.emoji-flag_lv { + background-position: -420px -480px; +} +.emoji-flag_ly { + background-position: -440px -480px; +} +.emoji-flag_ma { + background-position: -460px -480px; +} +.emoji-flag_mc { + background-position: -480px -480px; +} +.emoji-flag_md { + background-position: -500px 0; +} +.emoji-flag_me { + background-position: -500px -20px; +} +.emoji-flag_mf { + background-position: -500px -40px; +} +.emoji-flag_mg { + background-position: -500px -60px; +} +.emoji-flag_mh { + background-position: -500px -80px; +} +.emoji-flag_mk { + background-position: -500px -100px; +} +.emoji-flag_ml { + background-position: -500px -120px; +} +.emoji-flag_mm { + background-position: -500px -140px; +} +.emoji-flag_mn { + background-position: -500px -160px; +} +.emoji-flag_mo { + background-position: -500px -180px; +} +.emoji-flag_mp { + background-position: -500px -200px; +} +.emoji-flag_mq { + background-position: -500px -220px; +} +.emoji-flag_mr { + background-position: -500px -240px; +} +.emoji-flag_ms { + background-position: -500px -260px; +} +.emoji-flag_mt { + background-position: -500px -280px; +} +.emoji-flag_mu { + background-position: -500px -300px; +} +.emoji-flag_mv { + background-position: -500px -320px; +} +.emoji-flag_mw { + background-position: -500px -340px; +} +.emoji-flag_mx { + background-position: -500px -360px; +} +.emoji-flag_my { + background-position: -500px -380px; +} +.emoji-flag_mz { + background-position: -500px -400px; +} +.emoji-flag_na { + background-position: -500px -420px; +} +.emoji-flag_nc { + background-position: -500px -440px; +} +.emoji-flag_ne { + background-position: -500px -460px; +} +.emoji-flag_nf { + background-position: -500px -480px; +} +.emoji-flag_ng { + background-position: 0 -500px; +} +.emoji-flag_ni { + background-position: -20px -500px; +} +.emoji-flag_nl { + background-position: -40px -500px; +} +.emoji-flag_no { + background-position: -60px -500px; +} +.emoji-flag_np { + background-position: -80px -500px; +} +.emoji-flag_nr { + background-position: -100px -500px; +} +.emoji-flag_nu { + background-position: -120px -500px; +} +.emoji-flag_nz { + background-position: -140px -500px; +} +.emoji-flag_om { + background-position: -160px -500px; +} +.emoji-flag_pa { + background-position: -180px -500px; +} +.emoji-flag_pe { + background-position: -200px -500px; +} +.emoji-flag_pf { + background-position: -220px -500px; +} +.emoji-flag_pg { + background-position: -240px -500px; +} +.emoji-flag_ph { + background-position: -260px -500px; +} +.emoji-flag_pk { + background-position: -280px -500px; +} +.emoji-flag_pl { + background-position: -300px -500px; +} +.emoji-flag_pm { + background-position: -320px -500px; +} +.emoji-flag_pn { + background-position: -340px -500px; +} +.emoji-flag_pr { + background-position: -360px -500px; +} +.emoji-flag_ps { + background-position: -380px -500px; +} +.emoji-flag_pt { + background-position: -400px -500px; +} +.emoji-flag_pw { + background-position: -420px -500px; +} +.emoji-flag_py { + background-position: -440px -500px; +} +.emoji-flag_qa { + background-position: -460px -500px; +} +.emoji-flag_re { + background-position: -480px -500px; +} +.emoji-flag_ro { + background-position: -500px -500px; +} +.emoji-flag_rs { + background-position: -520px 0; +} +.emoji-flag_ru { + background-position: -520px -20px; +} +.emoji-flag_rw { + background-position: -520px -40px; +} +.emoji-flag_sa { + background-position: -520px -60px; +} +.emoji-flag_sb { + background-position: -520px -80px; +} +.emoji-flag_sc { + background-position: -520px -100px; +} +.emoji-flag_sd { + background-position: -520px -120px; +} +.emoji-flag_se { + background-position: -520px -140px; +} +.emoji-flag_sg { + background-position: -520px -160px; +} +.emoji-flag_sh { + background-position: -520px -180px; +} +.emoji-flag_si { + background-position: -520px -200px; +} +.emoji-flag_sj { + background-position: -520px -220px; +} +.emoji-flag_sk { + background-position: -520px -240px; +} +.emoji-flag_sl { + background-position: -520px -260px; +} +.emoji-flag_sm { + background-position: -520px -280px; +} +.emoji-flag_sn { + background-position: -520px -300px; +} +.emoji-flag_so { + background-position: -520px -320px; +} +.emoji-flag_sr { + background-position: -520px -340px; +} +.emoji-flag_ss { + background-position: -520px -360px; +} +.emoji-flag_st { + background-position: -520px -380px; +} +.emoji-flag_sv { + background-position: -520px -400px; +} +.emoji-flag_sx { + background-position: -520px -420px; +} +.emoji-flag_sy { + background-position: -520px -440px; +} +.emoji-flag_sz { + background-position: -520px -460px; +} +.emoji-flag_ta { + background-position: -520px -480px; +} +.emoji-flag_tc { + background-position: -520px -500px; +} +.emoji-flag_td { + background-position: 0 -520px; +} +.emoji-flag_tf { + background-position: -20px -520px; +} +.emoji-flag_tg { + background-position: -40px -520px; +} +.emoji-flag_th { + background-position: -60px -520px; +} +.emoji-flag_tj { + background-position: -80px -520px; +} +.emoji-flag_tk { + background-position: -100px -520px; +} +.emoji-flag_tl { + background-position: -120px -520px; +} +.emoji-flag_tm { + background-position: -140px -520px; +} +.emoji-flag_tn { + background-position: -160px -520px; +} +.emoji-flag_to { + background-position: -180px -520px; +} +.emoji-flag_tr { + background-position: -200px -520px; +} +.emoji-flag_tt { + background-position: -220px -520px; +} +.emoji-flag_tv { + background-position: -240px -520px; +} +.emoji-flag_tw { + background-position: -260px -520px; +} +.emoji-flag_tz { + background-position: -280px -520px; +} +.emoji-flag_ua { + background-position: -300px -520px; +} +.emoji-flag_ug { + background-position: -320px -520px; +} +.emoji-flag_um { + background-position: -340px -520px; +} +.emoji-flag_us { + background-position: -360px -520px; +} +.emoji-flag_uy { + background-position: -380px -520px; +} +.emoji-flag_uz { + background-position: -400px -520px; +} +.emoji-flag_va { + background-position: -420px -520px; +} +.emoji-flag_vc { + background-position: -440px -520px; +} +.emoji-flag_ve { + background-position: -460px -520px; +} +.emoji-flag_vg { + background-position: -480px -520px; +} +.emoji-flag_vi { + background-position: -500px -520px; +} +.emoji-flag_vn { + background-position: -520px -520px; +} +.emoji-flag_vu { + background-position: -540px 0; +} +.emoji-flag_wf { + background-position: -540px -20px; +} +.emoji-flag_white { + background-position: -540px -40px; +} +.emoji-flag_ws { + background-position: -540px -60px; +} +.emoji-flag_xk { + background-position: -540px -80px; +} +.emoji-flag_ye { + background-position: -540px -100px; +} +.emoji-flag_yt { + background-position: -540px -120px; +} +.emoji-flag_za { + background-position: -540px -140px; +} +.emoji-flag_zm { + background-position: -540px -160px; +} +.emoji-flag_zw { + background-position: -540px -180px; +} +.emoji-flags { + background-position: -540px -200px; +} +.emoji-flashlight { + background-position: -540px -220px; +} +.emoji-fleur-de-lis { + background-position: -540px -240px; +} +.emoji-floppy_disk { + background-position: -540px -260px; +} +.emoji-flower_playing_cards { + background-position: -540px -280px; +} +.emoji-flushed { + background-position: -540px -300px; +} +.emoji-fog { + background-position: -540px -320px; +} +.emoji-foggy { + background-position: -540px -340px; +} +.emoji-football { + background-position: -540px -360px; +} +.emoji-footprints { + background-position: -540px -380px; +} +.emoji-fork_and_knife { + background-position: -540px -400px; +} +.emoji-fork_knife_plate { + background-position: -540px -420px; +} +.emoji-fountain { + background-position: -540px -440px; +} +.emoji-four { + background-position: -540px -460px; +} +.emoji-four_leaf_clover { + background-position: -540px -480px; +} +.emoji-fox { + background-position: -540px -500px; +} +.emoji-frame_photo { + background-position: -540px -520px; +} +.emoji-free { + background-position: 0 -540px; +} +.emoji-french_bread { + background-position: -20px -540px; +} +.emoji-fried_shrimp { + background-position: -40px -540px; +} +.emoji-fries { + background-position: -60px -540px; +} +.emoji-frog { + background-position: -80px -540px; +} +.emoji-frowning { + background-position: -100px -540px; +} +.emoji-frowning2 { + background-position: -120px -540px; +} +.emoji-fuelpump { + background-position: -140px -540px; +} +.emoji-full_moon { + background-position: -160px -540px; +} +.emoji-full_moon_with_face { + background-position: -180px -540px; +} +.emoji-game_die { + background-position: -200px -540px; +} +.emoji-gay_pride_flag { + background-position: -220px -540px; +} +.emoji-gear { + background-position: -240px -540px; +} +.emoji-gem { + background-position: -260px -540px; +} +.emoji-gemini { + background-position: -280px -540px; +} +.emoji-ghost { + background-position: -300px -540px; +} +.emoji-gift { + background-position: -320px -540px; +} +.emoji-gift_heart { + background-position: -340px -540px; +} +.emoji-girl { + background-position: -360px -540px; +} +.emoji-girl_tone1 { + background-position: -380px -540px; +} +.emoji-girl_tone2 { + background-position: -400px -540px; +} +.emoji-girl_tone3 { + background-position: -420px -540px; +} +.emoji-girl_tone4 { + background-position: -440px -540px; +} +.emoji-girl_tone5 { + background-position: -460px -540px; +} +.emoji-globe_with_meridians { + background-position: -480px -540px; +} +.emoji-goal { + background-position: -500px -540px; +} +.emoji-goat { + background-position: -520px -540px; +} +.emoji-golf { + background-position: -540px -540px; +} +.emoji-golfer { + background-position: -560px 0; +} +.emoji-gorilla { + background-position: -560px -20px; +} +.emoji-grapes { + background-position: -560px -40px; +} +.emoji-green_apple { + background-position: -560px -60px; +} +.emoji-green_book { + background-position: -560px -80px; +} +.emoji-green_heart { + background-position: -560px -100px; +} +.emoji-grey_exclamation { + background-position: -560px -120px; +} +.emoji-grey_question { + background-position: -560px -140px; +} +.emoji-grimacing { + background-position: -560px -160px; +} +.emoji-grin { + background-position: -560px -180px; +} +.emoji-grinning { + background-position: -560px -200px; +} +.emoji-guardsman { + background-position: -560px -220px; +} +.emoji-guardsman_tone1 { + background-position: -560px -240px; +} +.emoji-guardsman_tone2 { + background-position: -560px -260px; +} +.emoji-guardsman_tone3 { + background-position: -560px -280px; +} +.emoji-guardsman_tone4 { + background-position: -560px -300px; +} +.emoji-guardsman_tone5 { + background-position: -560px -320px; +} +.emoji-guitar { + background-position: -560px -340px; +} +.emoji-gun { + background-position: -560px -360px; +} +.emoji-haircut { + background-position: -560px -380px; +} +.emoji-haircut_tone1 { + background-position: -560px -400px; +} +.emoji-haircut_tone2 { + background-position: -560px -420px; +} +.emoji-haircut_tone3 { + background-position: -560px -440px; +} +.emoji-haircut_tone4 { + background-position: -560px -460px; +} +.emoji-haircut_tone5 { + background-position: -560px -480px; +} +.emoji-hamburger { + background-position: -560px -500px; +} +.emoji-hammer { + background-position: -560px -520px; +} +.emoji-hammer_pick { + background-position: -560px -540px; +} +.emoji-hamster { + background-position: 0 -560px; +} +.emoji-hand_splayed { + background-position: -20px -560px; +} +.emoji-hand_splayed_tone1 { + background-position: -40px -560px; +} +.emoji-hand_splayed_tone2 { + background-position: -60px -560px; +} +.emoji-hand_splayed_tone3 { + background-position: -80px -560px; +} +.emoji-hand_splayed_tone4 { + background-position: -100px -560px; +} +.emoji-hand_splayed_tone5 { + background-position: -120px -560px; +} +.emoji-handbag { + background-position: -140px -560px; +} +.emoji-handball { + background-position: -160px -560px; +} +.emoji-handball_tone1 { + background-position: -180px -560px; +} +.emoji-handball_tone2 { + background-position: -200px -560px; +} +.emoji-handball_tone3 { + background-position: -220px -560px; +} +.emoji-handball_tone4 { + background-position: -240px -560px; +} +.emoji-handball_tone5 { + background-position: -260px -560px; +} +.emoji-handshake { + background-position: -280px -560px; +} +.emoji-handshake_tone1 { + background-position: -300px -560px; +} +.emoji-handshake_tone2 { + background-position: -320px -560px; +} +.emoji-handshake_tone3 { + background-position: -340px -560px; +} +.emoji-handshake_tone4 { + background-position: -360px -560px; +} +.emoji-handshake_tone5 { + background-position: -380px -560px; +} +.emoji-hash { + background-position: -400px -560px; +} +.emoji-hatched_chick { + background-position: -420px -560px; +} +.emoji-hatching_chick { + background-position: -440px -560px; +} +.emoji-head_bandage { + background-position: -460px -560px; +} +.emoji-headphones { + background-position: -480px -560px; +} +.emoji-hear_no_evil { + background-position: -500px -560px; +} +.emoji-heart { + background-position: -520px -560px; +} +.emoji-heart_decoration { + background-position: -540px -560px; +} +.emoji-heart_exclamation { + background-position: -560px -560px; +} +.emoji-heart_eyes { + background-position: -580px 0; +} +.emoji-heart_eyes_cat { + background-position: -580px -20px; +} +.emoji-heartbeat { + background-position: -580px -40px; +} +.emoji-heartpulse { + background-position: -580px -60px; +} +.emoji-hearts { + background-position: -580px -80px; +} +.emoji-heavy_check_mark { + background-position: -580px -100px; +} +.emoji-heavy_division_sign { + background-position: -580px -120px; +} +.emoji-heavy_dollar_sign { + background-position: -580px -140px; +} +.emoji-heavy_minus_sign { + background-position: -580px -160px; +} +.emoji-heavy_multiplication_x { + background-position: -580px -180px; +} +.emoji-heavy_plus_sign { + background-position: -580px -200px; +} +.emoji-helicopter { + background-position: -580px -220px; +} +.emoji-helmet_with_cross { + background-position: -580px -240px; +} +.emoji-herb { + background-position: -580px -260px; +} +.emoji-hibiscus { + background-position: -580px -280px; +} +.emoji-high_brightness { + background-position: -580px -300px; +} +.emoji-high_heel { + background-position: -580px -320px; +} +.emoji-hockey { + background-position: -580px -340px; +} +.emoji-hole { + background-position: -580px -360px; +} +.emoji-homes { + background-position: -580px -380px; +} +.emoji-honey_pot { + background-position: -580px -400px; +} +.emoji-horse { + background-position: -580px -420px; +} +.emoji-horse_racing { + background-position: -580px -440px; +} +.emoji-horse_racing_tone1 { + background-position: -580px -460px; +} +.emoji-horse_racing_tone2 { + background-position: -580px -480px; +} +.emoji-horse_racing_tone3 { + background-position: -580px -500px; +} +.emoji-horse_racing_tone4 { + background-position: -580px -520px; +} +.emoji-horse_racing_tone5 { + background-position: -580px -540px; +} +.emoji-hospital { + background-position: -580px -560px; +} +.emoji-hot_pepper { + background-position: 0 -580px; +} +.emoji-hotdog { + background-position: -20px -580px; +} +.emoji-hotel { + background-position: -40px -580px; +} +.emoji-hotsprings { + background-position: -60px -580px; +} +.emoji-hourglass { + background-position: -80px -580px; +} +.emoji-hourglass_flowing_sand { + background-position: -100px -580px; +} +.emoji-house { + background-position: -120px -580px; +} +.emoji-house_abandoned { + background-position: -140px -580px; +} +.emoji-house_with_garden { + background-position: -160px -580px; +} +.emoji-hugging { + background-position: -180px -580px; +} +.emoji-hushed { + background-position: -200px -580px; +} +.emoji-ice_cream { + background-position: -220px -580px; +} +.emoji-ice_skate { + background-position: -240px -580px; +} +.emoji-icecream { + background-position: -260px -580px; +} +.emoji-id { + background-position: -280px -580px; +} +.emoji-ideograph_advantage { + background-position: -300px -580px; +} +.emoji-imp { + background-position: -320px -580px; +} +.emoji-inbox_tray { + background-position: -340px -580px; +} +.emoji-incoming_envelope { + background-position: -360px -580px; +} +.emoji-information_desk_person { + background-position: -380px -580px; +} +.emoji-information_desk_person_tone1 { + background-position: -400px -580px; +} +.emoji-information_desk_person_tone2 { + background-position: -420px -580px; +} +.emoji-information_desk_person_tone3 { + background-position: -440px -580px; +} +.emoji-information_desk_person_tone4 { + background-position: -460px -580px; +} +.emoji-information_desk_person_tone5 { + background-position: -480px -580px; +} +.emoji-information_source { + background-position: -500px -580px; +} +.emoji-innocent { + background-position: -520px -580px; +} +.emoji-interrobang { + background-position: -540px -580px; +} +.emoji-iphone { + background-position: -560px -580px; +} +.emoji-island { + background-position: -580px -580px; +} +.emoji-izakaya_lantern { + background-position: -600px 0; +} +.emoji-jack_o_lantern { + background-position: -600px -20px; +} +.emoji-japan { + background-position: -600px -40px; +} +.emoji-japanese_castle { + background-position: -600px -60px; +} +.emoji-japanese_goblin { + background-position: -600px -80px; +} +.emoji-japanese_ogre { + background-position: -600px -100px; +} +.emoji-jeans { + background-position: -600px -120px; +} +.emoji-joy { + background-position: -600px -140px; +} +.emoji-joy_cat { + background-position: -600px -160px; +} +.emoji-joystick { + background-position: -600px -180px; +} +.emoji-juggling { + background-position: -600px -200px; +} +.emoji-juggling_tone1 { + background-position: -600px -220px; +} +.emoji-juggling_tone2 { + background-position: -600px -240px; +} +.emoji-juggling_tone3 { + background-position: -600px -260px; +} +.emoji-juggling_tone4 { + background-position: -600px -280px; +} +.emoji-juggling_tone5 { + background-position: -600px -300px; +} +.emoji-kaaba { + background-position: -600px -320px; +} +.emoji-key { + background-position: -600px -340px; +} +.emoji-key2 { + background-position: -600px -360px; +} +.emoji-keyboard { + background-position: -600px -380px; +} +.emoji-kimono { + background-position: -600px -400px; +} +.emoji-kiss { + background-position: -600px -420px; +} +.emoji-kiss_mm { + background-position: -600px -440px; +} +.emoji-kiss_ww { + background-position: -600px -460px; +} +.emoji-kissing { + background-position: -600px -480px; +} +.emoji-kissing_cat { + background-position: -600px -500px; +} +.emoji-kissing_closed_eyes { + background-position: -600px -520px; +} +.emoji-kissing_heart { + background-position: -600px -540px; +} +.emoji-kissing_smiling_eyes { + background-position: -600px -560px; +} +.emoji-kiwi { + background-position: -600px -580px; +} +.emoji-knife { + background-position: 0 -600px; +} +.emoji-koala { + background-position: -20px -600px; +} +.emoji-koko { + background-position: -40px -600px; +} +.emoji-label { + background-position: -60px -600px; +} +.emoji-large_blue_circle { + background-position: -80px -600px; +} +.emoji-large_blue_diamond { + background-position: -100px -600px; +} +.emoji-large_orange_diamond { + background-position: -120px -600px; +} +.emoji-last_quarter_moon { + background-position: -140px -600px; +} +.emoji-last_quarter_moon_with_face { + background-position: -160px -600px; +} +.emoji-laughing { + background-position: -180px -600px; +} +.emoji-leaves { + background-position: -200px -600px; +} +.emoji-ledger { + background-position: -220px -600px; +} +.emoji-left_facing_fist { + background-position: -240px -600px; +} +.emoji-left_facing_fist_tone1 { + background-position: -260px -600px; +} +.emoji-left_facing_fist_tone2 { + background-position: -280px -600px; +} +.emoji-left_facing_fist_tone3 { + background-position: -300px -600px; +} +.emoji-left_facing_fist_tone4 { + background-position: -320px -600px; +} +.emoji-left_facing_fist_tone5 { + background-position: -340px -600px; +} +.emoji-left_luggage { + background-position: -360px -600px; +} +.emoji-left_right_arrow { + background-position: -380px -600px; +} +.emoji-leftwards_arrow_with_hook { + background-position: -400px -600px; +} +.emoji-lemon { + background-position: -420px -600px; +} +.emoji-leo { + background-position: -440px -600px; +} +.emoji-leopard { + background-position: -460px -600px; +} +.emoji-level_slider { + background-position: -480px -600px; +} +.emoji-levitate { + background-position: -500px -600px; +} +.emoji-libra { + background-position: -520px -600px; +} +.emoji-lifter { + background-position: -540px -600px; +} +.emoji-lifter_tone1 { + background-position: -560px -600px; +} +.emoji-lifter_tone2 { + background-position: -580px -600px; +} +.emoji-lifter_tone3 { + background-position: -600px -600px; +} +.emoji-lifter_tone4 { + background-position: -620px 0; +} +.emoji-lifter_tone5 { + background-position: -620px -20px; +} +.emoji-light_rail { + background-position: -620px -40px; +} +.emoji-link { + background-position: -620px -60px; +} +.emoji-lion_face { + background-position: -620px -80px; +} +.emoji-lips { + background-position: -620px -100px; +} +.emoji-lipstick { + background-position: -620px -120px; +} +.emoji-lizard { + background-position: -620px -140px; +} +.emoji-lock { + background-position: -620px -160px; +} +.emoji-lock_with_ink_pen { + background-position: -620px -180px; +} +.emoji-lollipop { + background-position: -620px -200px; +} +.emoji-loop { + background-position: -620px -220px; +} +.emoji-loud_sound { + background-position: -620px -240px; +} +.emoji-loudspeaker { + background-position: -620px -260px; +} +.emoji-love_hotel { + background-position: -620px -280px; +} +.emoji-love_letter { + background-position: -620px -300px; +} +.emoji-low_brightness { + background-position: -620px -320px; +} +.emoji-lying_face { + background-position: -620px -340px; +} +.emoji-m { + background-position: -620px -360px; +} +.emoji-mag { + background-position: -620px -380px; +} +.emoji-mag_right { + background-position: -620px -400px; +} +.emoji-mahjong { + background-position: -620px -420px; +} +.emoji-mailbox { + background-position: -620px -440px; +} +.emoji-mailbox_closed { + background-position: -620px -460px; +} +.emoji-mailbox_with_mail { + background-position: -620px -480px; +} +.emoji-mailbox_with_no_mail { + background-position: -620px -500px; +} +.emoji-man { + background-position: -620px -520px; +} +.emoji-man_dancing { + background-position: -620px -540px; +} +.emoji-man_dancing_tone1 { + background-position: -620px -560px; +} +.emoji-man_dancing_tone2 { + background-position: -620px -580px; +} +.emoji-man_dancing_tone3 { + background-position: -620px -600px; +} +.emoji-man_dancing_tone4 { + background-position: 0 -620px; +} +.emoji-man_dancing_tone5 { + background-position: -20px -620px; +} +.emoji-man_in_tuxedo { + background-position: -40px -620px; +} +.emoji-man_in_tuxedo_tone1 { + background-position: -60px -620px; +} +.emoji-man_in_tuxedo_tone2 { + background-position: -80px -620px; +} +.emoji-man_in_tuxedo_tone3 { + background-position: -100px -620px; +} +.emoji-man_in_tuxedo_tone4 { + background-position: -120px -620px; +} +.emoji-man_in_tuxedo_tone5 { + background-position: -140px -620px; +} +.emoji-man_tone1 { + background-position: -160px -620px; +} +.emoji-man_tone2 { + background-position: -180px -620px; +} +.emoji-man_tone3 { + background-position: -200px -620px; +} +.emoji-man_tone4 { + background-position: -220px -620px; +} +.emoji-man_tone5 { + background-position: -240px -620px; +} +.emoji-man_with_gua_pi_mao { + background-position: -260px -620px; +} +.emoji-man_with_gua_pi_mao_tone1 { + background-position: -280px -620px; +} +.emoji-man_with_gua_pi_mao_tone2 { + background-position: -300px -620px; +} +.emoji-man_with_gua_pi_mao_tone3 { + background-position: -320px -620px; +} +.emoji-man_with_gua_pi_mao_tone4 { + background-position: -340px -620px; +} +.emoji-man_with_gua_pi_mao_tone5 { + background-position: -360px -620px; +} +.emoji-man_with_turban { + background-position: -380px -620px; +} +.emoji-man_with_turban_tone1 { + background-position: -400px -620px; +} +.emoji-man_with_turban_tone2 { + background-position: -420px -620px; +} +.emoji-man_with_turban_tone3 { + background-position: -440px -620px; +} +.emoji-man_with_turban_tone4 { + background-position: -460px -620px; +} +.emoji-man_with_turban_tone5 { + background-position: -480px -620px; +} +.emoji-mans_shoe { + background-position: -500px -620px; +} +.emoji-map { + background-position: -520px -620px; +} +.emoji-maple_leaf { + background-position: -540px -620px; +} +.emoji-martial_arts_uniform { + background-position: -560px -620px; +} +.emoji-mask { + background-position: -580px -620px; +} +.emoji-massage { + background-position: -600px -620px; +} +.emoji-massage_tone1 { + background-position: -620px -620px; +} +.emoji-massage_tone2 { + background-position: -640px 0; +} +.emoji-massage_tone3 { + background-position: -640px -20px; +} +.emoji-massage_tone4 { + background-position: -640px -40px; +} +.emoji-massage_tone5 { + background-position: -640px -60px; +} +.emoji-meat_on_bone { + background-position: -640px -80px; +} +.emoji-medal { + background-position: -640px -100px; +} +.emoji-mega { + background-position: -640px -120px; +} +.emoji-melon { + background-position: -640px -140px; +} +.emoji-menorah { + background-position: -640px -160px; +} +.emoji-mens { + background-position: -640px -180px; +} +.emoji-metal { + background-position: -640px -200px; +} +.emoji-metal_tone1 { + background-position: -640px -220px; +} +.emoji-metal_tone2 { + background-position: -640px -240px; +} +.emoji-metal_tone3 { + background-position: -640px -260px; +} +.emoji-metal_tone4 { + background-position: -640px -280px; +} +.emoji-metal_tone5 { + background-position: -640px -300px; +} +.emoji-metro { + background-position: -640px -320px; +} +.emoji-microphone { + background-position: -640px -340px; +} +.emoji-microphone2 { + background-position: -640px -360px; +} +.emoji-microscope { + background-position: -640px -380px; +} +.emoji-middle_finger { + background-position: -640px -400px; +} +.emoji-middle_finger_tone1 { + background-position: -640px -420px; +} +.emoji-middle_finger_tone2 { + background-position: -640px -440px; +} +.emoji-middle_finger_tone3 { + background-position: -640px -460px; +} +.emoji-middle_finger_tone4 { + background-position: -640px -480px; +} +.emoji-middle_finger_tone5 { + background-position: -640px -500px; +} +.emoji-military_medal { + background-position: -640px -520px; +} +.emoji-milk { + background-position: -640px -540px; +} +.emoji-milky_way { + background-position: -640px -560px; +} +.emoji-minibus { + background-position: -640px -580px; +} +.emoji-minidisc { + background-position: -640px -600px; +} +.emoji-mobile_phone_off { + background-position: -640px -620px; +} +.emoji-money_mouth { + background-position: 0 -640px; +} +.emoji-money_with_wings { + background-position: -20px -640px; +} +.emoji-moneybag { + background-position: -40px -640px; +} +.emoji-monkey { + background-position: -60px -640px; +} +.emoji-monkey_face { + background-position: -80px -640px; +} +.emoji-monorail { + background-position: -100px -640px; +} +.emoji-mortar_board { + background-position: -120px -640px; +} +.emoji-mosque { + background-position: -140px -640px; +} +.emoji-motor_scooter { + background-position: -160px -640px; +} +.emoji-motorboat { + background-position: -180px -640px; +} +.emoji-motorcycle { + background-position: -200px -640px; +} +.emoji-motorway { + background-position: -220px -640px; +} +.emoji-mount_fuji { + background-position: -240px -640px; +} +.emoji-mountain { + background-position: -260px -640px; +} +.emoji-mountain_bicyclist { + background-position: -280px -640px; +} +.emoji-mountain_bicyclist_tone1 { + background-position: -300px -640px; +} +.emoji-mountain_bicyclist_tone2 { + background-position: -320px -640px; +} +.emoji-mountain_bicyclist_tone3 { + background-position: -340px -640px; +} +.emoji-mountain_bicyclist_tone4 { + background-position: -360px -640px; +} +.emoji-mountain_bicyclist_tone5 { + background-position: -380px -640px; +} +.emoji-mountain_cableway { + background-position: -400px -640px; +} +.emoji-mountain_railway { + background-position: -420px -640px; +} +.emoji-mountain_snow { + background-position: -440px -640px; +} +.emoji-mouse { + background-position: -460px -640px; +} +.emoji-mouse2 { + background-position: -480px -640px; +} +.emoji-mouse_three_button { + background-position: -500px -640px; +} +.emoji-movie_camera { + background-position: -520px -640px; +} +.emoji-moyai { + background-position: -540px -640px; +} +.emoji-mrs_claus { + background-position: -560px -640px; +} +.emoji-mrs_claus_tone1 { + background-position: -580px -640px; +} +.emoji-mrs_claus_tone2 { + background-position: -600px -640px; +} +.emoji-mrs_claus_tone3 { + background-position: -620px -640px; +} +.emoji-mrs_claus_tone4 { + background-position: -640px -640px; +} +.emoji-mrs_claus_tone5 { + background-position: -660px 0; +} +.emoji-muscle { + background-position: -660px -20px; +} +.emoji-muscle_tone1 { + background-position: -660px -40px; +} +.emoji-muscle_tone2 { + background-position: -660px -60px; +} +.emoji-muscle_tone3 { + background-position: -660px -80px; +} +.emoji-muscle_tone4 { + background-position: -660px -100px; +} +.emoji-muscle_tone5 { + background-position: -660px -120px; +} +.emoji-mushroom { + background-position: -660px -140px; +} +.emoji-musical_keyboard { + background-position: -660px -160px; +} +.emoji-musical_note { + background-position: -660px -180px; +} +.emoji-musical_score { + background-position: -660px -200px; +} +.emoji-mute { + background-position: -660px -220px; +} +.emoji-nail_care { + background-position: -660px -240px; +} +.emoji-nail_care_tone1 { + background-position: -660px -260px; +} +.emoji-nail_care_tone2 { + background-position: -660px -280px; +} +.emoji-nail_care_tone3 { + background-position: -660px -300px; +} +.emoji-nail_care_tone4 { + background-position: -660px -320px; +} +.emoji-nail_care_tone5 { + background-position: -660px -340px; +} +.emoji-name_badge { + background-position: -660px -360px; +} +.emoji-nauseated_face { + background-position: -660px -380px; +} +.emoji-necktie { + background-position: -660px -400px; +} +.emoji-negative_squared_cross_mark { + background-position: -660px -420px; +} +.emoji-nerd { + background-position: -660px -440px; +} +.emoji-neutral_face { + background-position: -660px -460px; +} +.emoji-new { + background-position: -660px -480px; +} +.emoji-new_moon { + background-position: -660px -500px; +} +.emoji-new_moon_with_face { + background-position: -660px -520px; +} +.emoji-newspaper { + background-position: -660px -540px; +} +.emoji-newspaper2 { + background-position: -660px -560px; +} +.emoji-ng { + background-position: -660px -580px; +} +.emoji-night_with_stars { + background-position: -660px -600px; +} +.emoji-nine { + background-position: -660px -620px; +} +.emoji-no_bell { + background-position: -660px -640px; +} +.emoji-no_bicycles { + background-position: 0 -660px; +} +.emoji-no_entry { + background-position: -20px -660px; +} +.emoji-no_entry_sign { + background-position: -40px -660px; +} +.emoji-no_good { + background-position: -60px -660px; +} +.emoji-no_good_tone1 { + background-position: -80px -660px; +} +.emoji-no_good_tone2 { + background-position: -100px -660px; +} +.emoji-no_good_tone3 { + background-position: -120px -660px; +} +.emoji-no_good_tone4 { + background-position: -140px -660px; +} +.emoji-no_good_tone5 { + background-position: -160px -660px; +} +.emoji-no_mobile_phones { + background-position: -180px -660px; +} +.emoji-no_mouth { + background-position: -200px -660px; +} +.emoji-no_pedestrians { + background-position: -220px -660px; +} +.emoji-no_smoking { + background-position: -240px -660px; +} +.emoji-non-potable_water { + background-position: -260px -660px; +} +.emoji-nose { + background-position: -280px -660px; +} +.emoji-nose_tone1 { + background-position: -300px -660px; +} +.emoji-nose_tone2 { + background-position: -320px -660px; +} +.emoji-nose_tone3 { + background-position: -340px -660px; +} +.emoji-nose_tone4 { + background-position: -360px -660px; +} +.emoji-nose_tone5 { + background-position: -380px -660px; +} +.emoji-notebook { + background-position: -400px -660px; +} +.emoji-notebook_with_decorative_cover { + background-position: -420px -660px; +} +.emoji-notepad_spiral { + background-position: -440px -660px; +} +.emoji-notes { + background-position: -460px -660px; +} +.emoji-nut_and_bolt { + background-position: -480px -660px; +} +.emoji-o { + background-position: -500px -660px; +} +.emoji-o2 { + background-position: -520px -660px; +} +.emoji-ocean { + background-position: -540px -660px; +} +.emoji-octagonal_sign { + background-position: -560px -660px; +} +.emoji-octopus { + background-position: -580px -660px; +} +.emoji-oden { + background-position: -600px -660px; +} +.emoji-office { + background-position: -620px -660px; +} +.emoji-oil { + background-position: -640px -660px; +} +.emoji-ok { + background-position: -660px -660px; +} +.emoji-ok_hand { + background-position: -680px 0; +} +.emoji-ok_hand_tone1 { + background-position: -680px -20px; +} +.emoji-ok_hand_tone2 { + background-position: -680px -40px; +} +.emoji-ok_hand_tone3 { + background-position: -680px -60px; +} +.emoji-ok_hand_tone4 { + background-position: -680px -80px; +} +.emoji-ok_hand_tone5 { + background-position: -680px -100px; +} +.emoji-ok_woman { + background-position: -680px -120px; +} +.emoji-ok_woman_tone1 { + background-position: -680px -140px; +} +.emoji-ok_woman_tone2 { + background-position: -680px -160px; +} +.emoji-ok_woman_tone3 { + background-position: -680px -180px; +} +.emoji-ok_woman_tone4 { + background-position: -680px -200px; +} +.emoji-ok_woman_tone5 { + background-position: -680px -220px; +} +.emoji-older_man { + background-position: -680px -240px; +} +.emoji-older_man_tone1 { + background-position: -680px -260px; +} +.emoji-older_man_tone2 { + background-position: -680px -280px; +} +.emoji-older_man_tone3 { + background-position: -680px -300px; +} +.emoji-older_man_tone4 { + background-position: -680px -320px; +} +.emoji-older_man_tone5 { + background-position: -680px -340px; +} +.emoji-older_woman { + background-position: -680px -360px; +} +.emoji-older_woman_tone1 { + background-position: -680px -380px; +} +.emoji-older_woman_tone2 { + background-position: -680px -400px; +} +.emoji-older_woman_tone3 { + background-position: -680px -420px; +} +.emoji-older_woman_tone4 { + background-position: -680px -440px; +} +.emoji-older_woman_tone5 { + background-position: -680px -460px; +} +.emoji-om_symbol { + background-position: -680px -480px; +} +.emoji-on { + background-position: -680px -500px; +} +.emoji-oncoming_automobile { + background-position: -680px -520px; +} +.emoji-oncoming_bus { + background-position: -680px -540px; +} +.emoji-oncoming_police_car { + background-position: -680px -560px; +} +.emoji-oncoming_taxi { + background-position: -680px -580px; +} +.emoji-one { + background-position: -680px -600px; +} +.emoji-open_file_folder { + background-position: -680px -620px; +} +.emoji-open_hands { + background-position: -680px -640px; +} +.emoji-open_hands_tone1 { + background-position: -680px -660px; +} +.emoji-open_hands_tone2 { + background-position: 0 -680px; +} +.emoji-open_hands_tone3 { + background-position: -20px -680px; +} +.emoji-open_hands_tone4 { + background-position: -40px -680px; +} +.emoji-open_hands_tone5 { + background-position: -60px -680px; +} +.emoji-open_mouth { + background-position: -80px -680px; +} +.emoji-ophiuchus { + background-position: -100px -680px; +} +.emoji-orange_book { + background-position: -120px -680px; +} +.emoji-orthodox_cross { + background-position: -140px -680px; +} +.emoji-outbox_tray { + background-position: -160px -680px; +} +.emoji-owl { + background-position: -180px -680px; +} +.emoji-ox { + background-position: -200px -680px; +} +.emoji-package { + background-position: -220px -680px; +} +.emoji-page_facing_up { + background-position: -240px -680px; +} +.emoji-page_with_curl { + background-position: -260px -680px; +} +.emoji-pager { + background-position: -280px -680px; +} +.emoji-paintbrush { + background-position: -300px -680px; +} +.emoji-palm_tree { + background-position: -320px -680px; +} +.emoji-pancakes { + background-position: -340px -680px; +} +.emoji-panda_face { + background-position: -360px -680px; +} +.emoji-paperclip { + background-position: -380px -680px; +} +.emoji-paperclips { + background-position: -400px -680px; +} +.emoji-park { + background-position: -420px -680px; +} +.emoji-parking { + background-position: -440px -680px; +} +.emoji-part_alternation_mark { + background-position: -460px -680px; +} +.emoji-partly_sunny { + background-position: -480px -680px; +} +.emoji-passport_control { + background-position: -500px -680px; +} +.emoji-pause_button { + background-position: -520px -680px; +} +.emoji-peace { + background-position: -540px -680px; +} +.emoji-peach { + background-position: -560px -680px; +} +.emoji-peanuts { + background-position: -580px -680px; +} +.emoji-pear { + background-position: -600px -680px; +} +.emoji-pen_ballpoint { + background-position: -620px -680px; +} +.emoji-pen_fountain { + background-position: -640px -680px; +} +.emoji-pencil { + background-position: -660px -680px; +} +.emoji-pencil2 { + background-position: -680px -680px; +} +.emoji-penguin { + background-position: -700px 0; +} +.emoji-pensive { + background-position: -700px -20px; +} +.emoji-performing_arts { + background-position: -700px -40px; +} +.emoji-persevere { + background-position: -700px -60px; +} +.emoji-person_frowning { + background-position: -700px -80px; +} +.emoji-person_frowning_tone1 { + background-position: -700px -100px; +} +.emoji-person_frowning_tone2 { + background-position: -700px -120px; +} +.emoji-person_frowning_tone3 { + background-position: -700px -140px; +} +.emoji-person_frowning_tone4 { + background-position: -700px -160px; +} +.emoji-person_frowning_tone5 { + background-position: -700px -180px; +} +.emoji-person_with_blond_hair { + background-position: -700px -200px; +} +.emoji-person_with_blond_hair_tone1 { + background-position: -700px -220px; +} +.emoji-person_with_blond_hair_tone2 { + background-position: -700px -240px; +} +.emoji-person_with_blond_hair_tone3 { + background-position: -700px -260px; +} +.emoji-person_with_blond_hair_tone4 { + background-position: -700px -280px; +} +.emoji-person_with_blond_hair_tone5 { + background-position: -700px -300px; +} +.emoji-person_with_pouting_face { + background-position: -700px -320px; +} +.emoji-person_with_pouting_face_tone1 { + background-position: -700px -340px; +} +.emoji-person_with_pouting_face_tone2 { + background-position: -700px -360px; +} +.emoji-person_with_pouting_face_tone3 { + background-position: -700px -380px; +} +.emoji-person_with_pouting_face_tone4 { + background-position: -700px -400px; +} +.emoji-person_with_pouting_face_tone5 { + background-position: -700px -420px; +} +.emoji-pick { + background-position: -700px -440px; +} +.emoji-pig { + background-position: -700px -460px; +} +.emoji-pig2 { + background-position: -700px -480px; +} +.emoji-pig_nose { + background-position: -700px -500px; +} +.emoji-pill { + background-position: -700px -520px; +} +.emoji-pineapple { + background-position: -700px -540px; +} +.emoji-ping_pong { + background-position: -700px -560px; +} +.emoji-pisces { + background-position: -700px -580px; +} +.emoji-pizza { + background-position: -700px -600px; +} +.emoji-place_of_worship { + background-position: -700px -620px; +} +.emoji-play_pause { + background-position: -700px -640px; +} +.emoji-point_down { + background-position: -700px -660px; +} +.emoji-point_down_tone1 { + background-position: -700px -680px; +} +.emoji-point_down_tone2 { + background-position: 0 -700px; +} +.emoji-point_down_tone3 { + background-position: -20px -700px; +} +.emoji-point_down_tone4 { + background-position: -40px -700px; +} +.emoji-point_down_tone5 { + background-position: -60px -700px; +} +.emoji-point_left { + background-position: -80px -700px; +} +.emoji-point_left_tone1 { + background-position: -100px -700px; +} +.emoji-point_left_tone2 { + background-position: -120px -700px; +} +.emoji-point_left_tone3 { + background-position: -140px -700px; +} +.emoji-point_left_tone4 { + background-position: -160px -700px; +} +.emoji-point_left_tone5 { + background-position: -180px -700px; +} +.emoji-point_right { + background-position: -200px -700px; +} +.emoji-point_right_tone1 { + background-position: -220px -700px; +} +.emoji-point_right_tone2 { + background-position: -240px -700px; +} +.emoji-point_right_tone3 { + background-position: -260px -700px; +} +.emoji-point_right_tone4 { + background-position: -280px -700px; +} +.emoji-point_right_tone5 { + background-position: -300px -700px; +} +.emoji-point_up { + background-position: -320px -700px; +} +.emoji-point_up_2 { + background-position: -340px -700px; +} +.emoji-point_up_2_tone1 { + background-position: -360px -700px; +} +.emoji-point_up_2_tone2 { + background-position: -380px -700px; +} +.emoji-point_up_2_tone3 { + background-position: -400px -700px; +} +.emoji-point_up_2_tone4 { + background-position: -420px -700px; +} +.emoji-point_up_2_tone5 { + background-position: -440px -700px; +} +.emoji-point_up_tone1 { + background-position: -460px -700px; +} +.emoji-point_up_tone2 { + background-position: -480px -700px; +} +.emoji-point_up_tone3 { + background-position: -500px -700px; +} +.emoji-point_up_tone4 { + background-position: -520px -700px; +} +.emoji-point_up_tone5 { + background-position: -540px -700px; +} +.emoji-police_car { + background-position: -560px -700px; +} +.emoji-poodle { + background-position: -580px -700px; +} +.emoji-poop { + background-position: -600px -700px; +} +.emoji-popcorn { + background-position: -620px -700px; +} +.emoji-post_office { + background-position: -640px -700px; +} +.emoji-postal_horn { + background-position: -660px -700px; +} +.emoji-postbox { + background-position: -680px -700px; +} +.emoji-potable_water { + background-position: -700px -700px; +} +.emoji-potato { + background-position: -720px 0; +} +.emoji-pouch { + background-position: -720px -20px; +} +.emoji-poultry_leg { + background-position: -720px -40px; +} +.emoji-pound { + background-position: -720px -60px; +} +.emoji-pouting_cat { + background-position: -720px -80px; +} +.emoji-pray { + background-position: -720px -100px; +} +.emoji-pray_tone1 { + background-position: -720px -120px; +} +.emoji-pray_tone2 { + background-position: -720px -140px; +} +.emoji-pray_tone3 { + background-position: -720px -160px; +} +.emoji-pray_tone4 { + background-position: -720px -180px; +} +.emoji-pray_tone5 { + background-position: -720px -200px; +} +.emoji-prayer_beads { + background-position: -720px -220px; +} +.emoji-pregnant_woman { + background-position: -720px -240px; +} +.emoji-pregnant_woman_tone1 { + background-position: -720px -260px; +} +.emoji-pregnant_woman_tone2 { + background-position: -720px -280px; +} +.emoji-pregnant_woman_tone3 { + background-position: -720px -300px; +} +.emoji-pregnant_woman_tone4 { + background-position: -720px -320px; +} +.emoji-pregnant_woman_tone5 { + background-position: -720px -340px; +} +.emoji-prince { + background-position: -720px -360px; +} +.emoji-prince_tone1 { + background-position: -720px -380px; +} +.emoji-prince_tone2 { + background-position: -720px -400px; +} +.emoji-prince_tone3 { + background-position: -720px -420px; +} +.emoji-prince_tone4 { + background-position: -720px -440px; +} +.emoji-prince_tone5 { + background-position: -720px -460px; +} +.emoji-princess { + background-position: -720px -480px; +} +.emoji-princess_tone1 { + background-position: -720px -500px; +} +.emoji-princess_tone2 { + background-position: -720px -520px; +} +.emoji-princess_tone3 { + background-position: -720px -540px; +} +.emoji-princess_tone4 { + background-position: -720px -560px; +} +.emoji-princess_tone5 { + background-position: -720px -580px; +} +.emoji-printer { + background-position: -720px -600px; +} +.emoji-projector { + background-position: -720px -620px; +} +.emoji-punch { + background-position: -720px -640px; +} +.emoji-punch_tone1 { + background-position: -720px -660px; +} +.emoji-punch_tone2 { + background-position: -720px -680px; +} +.emoji-punch_tone3 { + background-position: -720px -700px; +} +.emoji-punch_tone4 { + background-position: 0 -720px; +} +.emoji-punch_tone5 { + background-position: -20px -720px; +} +.emoji-purple_heart { + background-position: -40px -720px; +} +.emoji-purse { + background-position: -60px -720px; +} +.emoji-pushpin { + background-position: -80px -720px; +} +.emoji-put_litter_in_its_place { + background-position: -100px -720px; +} +.emoji-question { + background-position: -120px -720px; +} +.emoji-rabbit { + background-position: -140px -720px; +} +.emoji-rabbit2 { + background-position: -160px -720px; +} +.emoji-race_car { + background-position: -180px -720px; +} +.emoji-racehorse { + background-position: -200px -720px; +} +.emoji-radio { + background-position: -220px -720px; +} +.emoji-radio_button { + background-position: -240px -720px; +} +.emoji-radioactive { + background-position: -260px -720px; +} +.emoji-rage { + background-position: -280px -720px; +} +.emoji-railway_car { + background-position: -300px -720px; +} +.emoji-railway_track { + background-position: -320px -720px; +} +.emoji-rainbow { + background-position: -340px -720px; +} +.emoji-raised_back_of_hand { + background-position: -360px -720px; +} +.emoji-raised_back_of_hand_tone1 { + background-position: -380px -720px; +} +.emoji-raised_back_of_hand_tone2 { + background-position: -400px -720px; +} +.emoji-raised_back_of_hand_tone3 { + background-position: -420px -720px; +} +.emoji-raised_back_of_hand_tone4 { + background-position: -440px -720px; +} +.emoji-raised_back_of_hand_tone5 { + background-position: -460px -720px; +} +.emoji-raised_hand { + background-position: -480px -720px; +} +.emoji-raised_hand_tone1 { + background-position: -500px -720px; +} +.emoji-raised_hand_tone2 { + background-position: -520px -720px; +} +.emoji-raised_hand_tone3 { + background-position: -540px -720px; +} +.emoji-raised_hand_tone4 { + background-position: -560px -720px; +} +.emoji-raised_hand_tone5 { + background-position: -580px -720px; +} +.emoji-raised_hands { + background-position: -600px -720px; +} +.emoji-raised_hands_tone1 { + background-position: -620px -720px; +} +.emoji-raised_hands_tone2 { + background-position: -640px -720px; +} +.emoji-raised_hands_tone3 { + background-position: -660px -720px; +} +.emoji-raised_hands_tone4 { + background-position: -680px -720px; +} +.emoji-raised_hands_tone5 { + background-position: -700px -720px; +} +.emoji-raising_hand { + background-position: -720px -720px; +} +.emoji-raising_hand_tone1 { + background-position: -740px 0; +} +.emoji-raising_hand_tone2 { + background-position: -740px -20px; +} +.emoji-raising_hand_tone3 { + background-position: -740px -40px; +} +.emoji-raising_hand_tone4 { + background-position: -740px -60px; +} +.emoji-raising_hand_tone5 { + background-position: -740px -80px; +} +.emoji-ram { + background-position: -740px -100px; +} +.emoji-ramen { + background-position: -740px -120px; +} +.emoji-rat { + background-position: -740px -140px; +} +.emoji-record_button { + background-position: -740px -160px; +} +.emoji-recycle { + background-position: -740px -180px; +} +.emoji-red_car { + background-position: -740px -200px; +} +.emoji-red_circle { + background-position: -740px -220px; +} +.emoji-registered { + background-position: -740px -240px; +} +.emoji-relaxed { + background-position: -740px -260px; +} +.emoji-relieved { + background-position: -740px -280px; +} +.emoji-reminder_ribbon { + background-position: -740px -300px; +} +.emoji-repeat { + background-position: -740px -320px; +} +.emoji-repeat_one { + background-position: -740px -340px; +} +.emoji-restroom { + background-position: -740px -360px; +} +.emoji-revolving_hearts { + background-position: -740px -380px; +} +.emoji-rewind { + background-position: -740px -400px; +} +.emoji-rhino { + background-position: -740px -420px; +} +.emoji-ribbon { + background-position: -740px -440px; +} +.emoji-rice { + background-position: -740px -460px; +} +.emoji-rice_ball { + background-position: -740px -480px; +} +.emoji-rice_cracker { + background-position: -740px -500px; +} +.emoji-rice_scene { + background-position: -740px -520px; +} +.emoji-right_facing_fist { + background-position: -740px -540px; +} +.emoji-right_facing_fist_tone1 { + background-position: -740px -560px; +} +.emoji-right_facing_fist_tone2 { + background-position: -740px -580px; +} +.emoji-right_facing_fist_tone3 { + background-position: -740px -600px; +} +.emoji-right_facing_fist_tone4 { + background-position: -740px -620px; +} +.emoji-right_facing_fist_tone5 { + background-position: -740px -640px; +} +.emoji-ring { + background-position: -740px -660px; +} +.emoji-robot { + background-position: -740px -680px; +} +.emoji-rocket { + background-position: -740px -700px; +} +.emoji-rofl { + background-position: -740px -720px; +} +.emoji-roller_coaster { + background-position: 0 -740px; +} +.emoji-rolling_eyes { + background-position: -20px -740px; +} +.emoji-rooster { + background-position: -40px -740px; +} +.emoji-rose { + background-position: -60px -740px; +} +.emoji-rosette { + background-position: -80px -740px; +} +.emoji-rotating_light { + background-position: -100px -740px; +} +.emoji-round_pushpin { + background-position: -120px -740px; +} +.emoji-rowboat { + background-position: -140px -740px; +} +.emoji-rowboat_tone1 { + background-position: -160px -740px; +} +.emoji-rowboat_tone2 { + background-position: -180px -740px; +} +.emoji-rowboat_tone3 { + background-position: -200px -740px; +} +.emoji-rowboat_tone4 { + background-position: -220px -740px; +} +.emoji-rowboat_tone5 { + background-position: -240px -740px; +} +.emoji-rugby_football { + background-position: -260px -740px; +} +.emoji-runner { + background-position: -280px -740px; +} +.emoji-runner_tone1 { + background-position: -300px -740px; +} +.emoji-runner_tone2 { + background-position: -320px -740px; +} +.emoji-runner_tone3 { + background-position: -340px -740px; +} +.emoji-runner_tone4 { + background-position: -360px -740px; +} +.emoji-runner_tone5 { + background-position: -380px -740px; +} +.emoji-running_shirt_with_sash { + background-position: -400px -740px; +} +.emoji-sa { + background-position: -420px -740px; +} +.emoji-sagittarius { + background-position: -440px -740px; +} +.emoji-sailboat { + background-position: -460px -740px; +} +.emoji-sake { + background-position: -480px -740px; +} +.emoji-salad { + background-position: -500px -740px; +} +.emoji-sandal { + background-position: -520px -740px; +} +.emoji-santa { + background-position: -540px -740px; +} +.emoji-santa_tone1 { + background-position: -560px -740px; +} +.emoji-santa_tone2 { + background-position: -580px -740px; +} +.emoji-santa_tone3 { + background-position: -600px -740px; +} +.emoji-santa_tone4 { + background-position: -620px -740px; +} +.emoji-santa_tone5 { + background-position: -640px -740px; +} +.emoji-satellite { + background-position: -660px -740px; +} +.emoji-satellite_orbital { + background-position: -680px -740px; +} +.emoji-saxophone { + background-position: -700px -740px; +} +.emoji-scales { + background-position: -720px -740px; +} +.emoji-school { + background-position: -740px -740px; +} +.emoji-school_satchel { + background-position: -760px 0; +} +.emoji-scissors { + background-position: -760px -20px; +} +.emoji-scooter { + background-position: -760px -40px; +} +.emoji-scorpion { + background-position: -760px -60px; +} +.emoji-scorpius { + background-position: -760px -80px; +} +.emoji-scream { + background-position: -760px -100px; +} +.emoji-scream_cat { + background-position: -760px -120px; +} +.emoji-scroll { + background-position: -760px -140px; +} +.emoji-seat { + background-position: -760px -160px; +} +.emoji-second_place { + background-position: -760px -180px; +} +.emoji-secret { + background-position: -760px -200px; +} +.emoji-see_no_evil { + background-position: -760px -220px; +} +.emoji-seedling { + background-position: -760px -240px; +} +.emoji-selfie { + background-position: -760px -260px; +} +.emoji-selfie_tone1 { + background-position: -760px -280px; +} +.emoji-selfie_tone2 { + background-position: -760px -300px; +} +.emoji-selfie_tone3 { + background-position: -760px -320px; +} +.emoji-selfie_tone4 { + background-position: -760px -340px; +} +.emoji-selfie_tone5 { + background-position: -760px -360px; +} +.emoji-seven { + background-position: -760px -380px; +} +.emoji-shallow_pan_of_food { + background-position: -760px -400px; +} +.emoji-shamrock { + background-position: -760px -420px; +} +.emoji-shark { + background-position: -760px -440px; +} +.emoji-shaved_ice { + background-position: -760px -460px; +} +.emoji-sheep { + background-position: -760px -480px; +} +.emoji-shell { + background-position: -760px -500px; +} +.emoji-shield { + background-position: -760px -520px; +} +.emoji-shinto_shrine { + background-position: -760px -540px; +} +.emoji-ship { + background-position: -760px -560px; +} +.emoji-shirt { + background-position: -760px -580px; +} +.emoji-shopping_bags { + background-position: -760px -600px; +} +.emoji-shopping_cart { + background-position: -760px -620px; +} +.emoji-shower { + background-position: -760px -640px; +} +.emoji-shrimp { + background-position: -760px -660px; +} +.emoji-shrug { + background-position: -760px -680px; +} +.emoji-shrug_tone1 { + background-position: -760px -700px; +} +.emoji-shrug_tone2 { + background-position: -760px -720px; +} +.emoji-shrug_tone3 { + background-position: -760px -740px; +} +.emoji-shrug_tone4 { + background-position: 0 -760px; +} +.emoji-shrug_tone5 { + background-position: -20px -760px; +} +.emoji-signal_strength { + background-position: -40px -760px; +} +.emoji-six { + background-position: -60px -760px; +} +.emoji-six_pointed_star { + background-position: -80px -760px; +} +.emoji-ski { + background-position: -100px -760px; +} +.emoji-skier { + background-position: -120px -760px; +} +.emoji-skull { + background-position: -140px -760px; +} +.emoji-skull_crossbones { + background-position: -160px -760px; +} +.emoji-sleeping { + background-position: -180px -760px; +} +.emoji-sleeping_accommodation { + background-position: -200px -760px; +} +.emoji-sleepy { + background-position: -220px -760px; +} +.emoji-slight_frown { + background-position: -240px -760px; +} +.emoji-slight_smile { + background-position: -260px -760px; +} +.emoji-slot_machine { + background-position: -280px -760px; +} +.emoji-small_blue_diamond { + background-position: -300px -760px; +} +.emoji-small_orange_diamond { + background-position: -320px -760px; +} +.emoji-small_red_triangle { + background-position: -340px -760px; +} +.emoji-small_red_triangle_down { + background-position: -360px -760px; +} +.emoji-smile { + background-position: -380px -760px; +} +.emoji-smile_cat { + background-position: -400px -760px; +} +.emoji-smiley { + background-position: -420px -760px; +} +.emoji-smiley_cat { + background-position: -440px -760px; +} +.emoji-smiling_imp { + background-position: -460px -760px; +} +.emoji-smirk { + background-position: -480px -760px; +} +.emoji-smirk_cat { + background-position: -500px -760px; +} +.emoji-smoking { + background-position: -520px -760px; +} +.emoji-snail { + background-position: -540px -760px; +} +.emoji-snake { + background-position: -560px -760px; +} +.emoji-sneezing_face { + background-position: -580px -760px; +} +.emoji-snowboarder { + background-position: -600px -760px; +} +.emoji-snowflake { + background-position: -620px -760px; +} +.emoji-snowman { + background-position: -640px -760px; +} +.emoji-snowman2 { + background-position: -660px -760px; +} +.emoji-sob { + background-position: -680px -760px; +} +.emoji-soccer { + background-position: -700px -760px; +} +.emoji-soon { + background-position: -720px -760px; +} +.emoji-sos { + background-position: -740px -760px; +} +.emoji-sound { + background-position: -760px -760px; +} +.emoji-space_invader { + background-position: -780px 0; +} +.emoji-spades { + background-position: -780px -20px; +} +.emoji-spaghetti { + background-position: -780px -40px; +} +.emoji-sparkle { + background-position: -780px -60px; +} +.emoji-sparkler { + background-position: -780px -80px; +} +.emoji-sparkles { + background-position: -780px -100px; +} +.emoji-sparkling_heart { + background-position: -780px -120px; +} +.emoji-speak_no_evil { + background-position: -780px -140px; +} +.emoji-speaker { + background-position: -780px -160px; +} +.emoji-speaking_head { + background-position: -780px -180px; +} +.emoji-speech_balloon { + background-position: -780px -200px; +} +.emoji-speech_left { + background-position: -780px -220px; +} +.emoji-speedboat { + background-position: -780px -240px; +} +.emoji-spider { + background-position: -780px -260px; +} +.emoji-spider_web { + background-position: -780px -280px; +} +.emoji-spoon { + background-position: -780px -300px; +} +.emoji-spy { + background-position: -780px -320px; +} +.emoji-spy_tone1 { + background-position: -780px -340px; +} +.emoji-spy_tone2 { + background-position: -780px -360px; +} +.emoji-spy_tone3 { + background-position: -780px -380px; +} +.emoji-spy_tone4 { + background-position: -780px -400px; +} +.emoji-spy_tone5 { + background-position: -780px -420px; +} +.emoji-squid { + background-position: -780px -440px; +} +.emoji-stadium { + background-position: -780px -460px; +} +.emoji-star { + background-position: -780px -480px; +} +.emoji-star2 { + background-position: -780px -500px; +} +.emoji-star_and_crescent { + background-position: -780px -520px; +} +.emoji-star_of_david { + background-position: -780px -540px; +} +.emoji-stars { + background-position: -780px -560px; +} +.emoji-station { + background-position: -780px -580px; +} +.emoji-statue_of_liberty { + background-position: -780px -600px; +} +.emoji-steam_locomotive { + background-position: -780px -620px; +} +.emoji-stew { + background-position: -780px -640px; +} +.emoji-stop_button { + background-position: -780px -660px; +} +.emoji-stopwatch { + background-position: -780px -680px; +} +.emoji-straight_ruler { + background-position: -780px -700px; +} +.emoji-strawberry { + background-position: -780px -720px; +} +.emoji-stuck_out_tongue { + background-position: -780px -740px; +} +.emoji-stuck_out_tongue_closed_eyes { + background-position: -780px -760px; +} +.emoji-stuck_out_tongue_winking_eye { + background-position: 0 -780px; +} +.emoji-stuffed_flatbread { + background-position: -20px -780px; +} +.emoji-sun_with_face { + background-position: -40px -780px; +} +.emoji-sunflower { + background-position: -60px -780px; +} +.emoji-sunglasses { + background-position: -80px -780px; +} +.emoji-sunny { + background-position: -100px -780px; +} +.emoji-sunrise { + background-position: -120px -780px; +} +.emoji-sunrise_over_mountains { + background-position: -140px -780px; +} +.emoji-surfer { + background-position: -160px -780px; +} +.emoji-surfer_tone1 { + background-position: -180px -780px; +} +.emoji-surfer_tone2 { + background-position: -200px -780px; +} +.emoji-surfer_tone3 { + background-position: -220px -780px; +} +.emoji-surfer_tone4 { + background-position: -240px -780px; +} +.emoji-surfer_tone5 { + background-position: -260px -780px; +} +.emoji-sushi { + background-position: -280px -780px; +} +.emoji-suspension_railway { + background-position: -300px -780px; +} +.emoji-sweat { + background-position: -320px -780px; +} +.emoji-sweat_drops { + background-position: -340px -780px; +} +.emoji-sweat_smile { + background-position: -360px -780px; +} +.emoji-sweet_potato { + background-position: -380px -780px; +} +.emoji-swimmer { + background-position: -400px -780px; +} +.emoji-swimmer_tone1 { + background-position: -420px -780px; +} +.emoji-swimmer_tone2 { + background-position: -440px -780px; +} +.emoji-swimmer_tone3 { + background-position: -460px -780px; +} +.emoji-swimmer_tone4 { + background-position: -480px -780px; +} +.emoji-swimmer_tone5 { + background-position: -500px -780px; +} +.emoji-symbols { + background-position: -520px -780px; +} +.emoji-synagogue { + background-position: -540px -780px; +} +.emoji-syringe { + background-position: -560px -780px; +} +.emoji-taco { + background-position: -580px -780px; +} +.emoji-tada { + background-position: -600px -780px; +} +.emoji-tanabata_tree { + background-position: -620px -780px; +} +.emoji-tangerine { + background-position: -640px -780px; +} +.emoji-taurus { + background-position: -660px -780px; +} +.emoji-taxi { + background-position: -680px -780px; +} +.emoji-tea { + background-position: -700px -780px; +} +.emoji-telephone { + background-position: -720px -780px; +} +.emoji-telephone_receiver { + background-position: -740px -780px; +} +.emoji-telescope { + background-position: -760px -780px; +} +.emoji-ten { + background-position: -780px -780px; +} +.emoji-tennis { + background-position: -800px 0; +} +.emoji-tent { + background-position: -800px -20px; +} +.emoji-thermometer { + background-position: -800px -40px; +} +.emoji-thermometer_face { + background-position: -800px -60px; +} +.emoji-thinking { + background-position: -800px -80px; +} +.emoji-third_place { + background-position: -800px -100px; +} +.emoji-thought_balloon { + background-position: -800px -120px; +} +.emoji-three { + background-position: -800px -140px; +} +.emoji-thumbsdown { + background-position: -800px -160px; +} +.emoji-thumbsdown_tone1 { + background-position: -800px -180px; +} +.emoji-thumbsdown_tone2 { + background-position: -800px -200px; +} +.emoji-thumbsdown_tone3 { + background-position: -800px -220px; +} +.emoji-thumbsdown_tone4 { + background-position: -800px -240px; +} +.emoji-thumbsdown_tone5 { + background-position: -800px -260px; +} +.emoji-thumbsup { + background-position: -800px -280px; +} +.emoji-thumbsup_tone1 { + background-position: -800px -300px; +} +.emoji-thumbsup_tone2 { + background-position: -800px -320px; +} +.emoji-thumbsup_tone3 { + background-position: -800px -340px; +} +.emoji-thumbsup_tone4 { + background-position: -800px -360px; +} +.emoji-thumbsup_tone5 { + background-position: -800px -380px; +} +.emoji-thunder_cloud_rain { + background-position: -800px -400px; +} +.emoji-ticket { + background-position: -800px -420px; +} +.emoji-tickets { + background-position: -800px -440px; +} +.emoji-tiger { + background-position: -800px -460px; +} +.emoji-tiger2 { + background-position: -800px -480px; +} +.emoji-timer { + background-position: -800px -500px; +} +.emoji-tired_face { + background-position: -800px -520px; +} +.emoji-tm { + background-position: -800px -540px; +} +.emoji-toilet { + background-position: -800px -560px; +} +.emoji-tokyo_tower { + background-position: -800px -580px; +} +.emoji-tomato { + background-position: -800px -600px; +} +.emoji-tone1 { + background-position: -800px -620px; +} +.emoji-tone2 { + background-position: -800px -640px; +} +.emoji-tone3 { + background-position: -800px -660px; +} +.emoji-tone4 { + background-position: -800px -680px; +} +.emoji-tone5 { + background-position: -800px -700px; +} +.emoji-tongue { + background-position: -800px -720px; +} +.emoji-tools { + background-position: -800px -740px; +} +.emoji-top { + background-position: -800px -760px; +} +.emoji-tophat { + background-position: -800px -780px; +} +.emoji-track_next { + background-position: 0 -800px; +} +.emoji-track_previous { + background-position: -20px -800px; +} +.emoji-trackball { + background-position: -40px -800px; +} +.emoji-tractor { + background-position: -60px -800px; +} +.emoji-traffic_light { + background-position: -80px -800px; +} +.emoji-train { + background-position: -100px -800px; +} +.emoji-train2 { + background-position: -120px -800px; +} +.emoji-tram { + background-position: -140px -800px; +} +.emoji-triangular_flag_on_post { + background-position: -160px -800px; +} +.emoji-triangular_ruler { + background-position: -180px -800px; +} +.emoji-trident { + background-position: -200px -800px; +} +.emoji-triumph { + background-position: -220px -800px; +} +.emoji-trolleybus { + background-position: -240px -800px; +} +.emoji-trophy { + background-position: -260px -800px; +} +.emoji-tropical_drink { + background-position: -280px -800px; +} +.emoji-tropical_fish { + background-position: -300px -800px; +} +.emoji-truck { + background-position: -320px -800px; +} +.emoji-trumpet { + background-position: -340px -800px; +} +.emoji-tulip { + background-position: -360px -800px; +} +.emoji-tumbler_glass { + background-position: -380px -800px; +} +.emoji-turkey { + background-position: -400px -800px; +} +.emoji-turtle { + background-position: -420px -800px; +} +.emoji-tv { + background-position: -440px -800px; +} +.emoji-twisted_rightwards_arrows { + background-position: -460px -800px; +} +.emoji-two { + background-position: -480px -800px; +} +.emoji-two_hearts { + background-position: -500px -800px; +} +.emoji-two_men_holding_hands { + background-position: -520px -800px; +} +.emoji-two_women_holding_hands { + background-position: -540px -800px; +} +.emoji-u5272 { + background-position: -560px -800px; +} +.emoji-u5408 { + background-position: -580px -800px; +} +.emoji-u55b6 { + background-position: -600px -800px; +} +.emoji-u6307 { + background-position: -620px -800px; +} +.emoji-u6708 { + background-position: -640px -800px; +} +.emoji-u6709 { + background-position: -660px -800px; +} +.emoji-u6e80 { + background-position: -680px -800px; +} +.emoji-u7121 { + background-position: -700px -800px; +} +.emoji-u7533 { + background-position: -720px -800px; +} +.emoji-u7981 { + background-position: -740px -800px; +} +.emoji-u7a7a { + background-position: -760px -800px; +} +.emoji-umbrella { + background-position: -780px -800px; +} +.emoji-umbrella2 { + background-position: -800px -800px; +} +.emoji-unamused { + background-position: -820px 0; +} +.emoji-underage { + background-position: -820px -20px; +} +.emoji-unicorn { + background-position: -820px -40px; +} +.emoji-unlock { + background-position: -820px -60px; +} +.emoji-up { + background-position: -820px -80px; +} +.emoji-upside_down { + background-position: -820px -100px; +} +.emoji-urn { + background-position: -820px -120px; +} +.emoji-v { + background-position: -820px -140px; +} +.emoji-v_tone1 { + background-position: -820px -160px; +} +.emoji-v_tone2 { + background-position: -820px -180px; +} +.emoji-v_tone3 { + background-position: -820px -200px; +} +.emoji-v_tone4 { + background-position: -820px -220px; +} +.emoji-v_tone5 { + background-position: -820px -240px; +} +.emoji-vertical_traffic_light { + background-position: -820px -260px; +} +.emoji-vhs { + background-position: -820px -280px; +} +.emoji-vibration_mode { + background-position: -820px -300px; +} +.emoji-video_camera { + background-position: -820px -320px; +} +.emoji-video_game { + background-position: -820px -340px; +} +.emoji-violin { + background-position: -820px -360px; +} +.emoji-virgo { + background-position: -820px -380px; +} +.emoji-volcano { + background-position: -820px -400px; +} +.emoji-volleyball { + background-position: -820px -420px; +} +.emoji-vs { + background-position: -820px -440px; +} +.emoji-vulcan { + background-position: -820px -460px; +} +.emoji-vulcan_tone1 { + background-position: -820px -480px; +} +.emoji-vulcan_tone2 { + background-position: -820px -500px; +} +.emoji-vulcan_tone3 { + background-position: -820px -520px; +} +.emoji-vulcan_tone4 { + background-position: -820px -540px; +} +.emoji-vulcan_tone5 { + background-position: -820px -560px; +} +.emoji-walking { + background-position: -820px -580px; +} +.emoji-walking_tone1 { + background-position: -820px -600px; +} +.emoji-walking_tone2 { + background-position: -820px -620px; +} +.emoji-walking_tone3 { + background-position: -820px -640px; +} +.emoji-walking_tone4 { + background-position: -820px -660px; +} +.emoji-walking_tone5 { + background-position: -820px -680px; +} +.emoji-waning_crescent_moon { + background-position: -820px -700px; +} +.emoji-waning_gibbous_moon { + background-position: -820px -720px; +} +.emoji-warning { + background-position: -820px -740px; +} +.emoji-wastebasket { + background-position: -820px -760px; +} +.emoji-watch { + background-position: -820px -780px; +} +.emoji-water_buffalo { + background-position: -820px -800px; +} +.emoji-water_polo { + background-position: 0 -820px; +} +.emoji-water_polo_tone1 { + background-position: -20px -820px; +} +.emoji-water_polo_tone2 { + background-position: -40px -820px; +} +.emoji-water_polo_tone3 { + background-position: -60px -820px; +} +.emoji-water_polo_tone4 { + background-position: -80px -820px; +} +.emoji-water_polo_tone5 { + background-position: -100px -820px; +} +.emoji-watermelon { + background-position: -120px -820px; +} +.emoji-wave { + background-position: -140px -820px; +} +.emoji-wave_tone1 { + background-position: -160px -820px; +} +.emoji-wave_tone2 { + background-position: -180px -820px; +} +.emoji-wave_tone3 { + background-position: -200px -820px; +} +.emoji-wave_tone4 { + background-position: -220px -820px; +} +.emoji-wave_tone5 { + background-position: -240px -820px; +} +.emoji-wavy_dash { + background-position: -260px -820px; +} +.emoji-waxing_crescent_moon { + background-position: -280px -820px; +} +.emoji-waxing_gibbous_moon { + background-position: -300px -820px; +} +.emoji-wc { + background-position: -320px -820px; +} +.emoji-weary { + background-position: -340px -820px; +} +.emoji-wedding { + background-position: -360px -820px; +} +.emoji-whale { + background-position: -380px -820px; +} +.emoji-whale2 { + background-position: -400px -820px; +} +.emoji-wheel_of_dharma { + background-position: -420px -820px; +} +.emoji-wheelchair { + background-position: -440px -820px; +} +.emoji-white_check_mark { + background-position: -460px -820px; +} +.emoji-white_circle { + background-position: -480px -820px; +} +.emoji-white_flower { + background-position: -500px -820px; +} +.emoji-white_large_square { + background-position: -520px -820px; +} +.emoji-white_medium_small_square { + background-position: -540px -820px; +} +.emoji-white_medium_square { + background-position: -560px -820px; +} +.emoji-white_small_square { + background-position: -580px -820px; +} +.emoji-white_square_button { + background-position: -600px -820px; +} +.emoji-white_sun_cloud { + background-position: -620px -820px; +} +.emoji-white_sun_rain_cloud { + background-position: -640px -820px; +} +.emoji-white_sun_small_cloud { + background-position: -660px -820px; +} +.emoji-wilted_rose { + background-position: -680px -820px; +} +.emoji-wind_blowing_face { + background-position: -700px -820px; +} +.emoji-wind_chime { + background-position: -720px -820px; +} +.emoji-wine_glass { + background-position: -740px -820px; +} +.emoji-wink { + background-position: -760px -820px; +} +.emoji-wolf { + background-position: -780px -820px; +} +.emoji-woman { + background-position: -800px -820px; +} +.emoji-woman_tone1 { + background-position: -820px -820px; +} +.emoji-woman_tone2 { + background-position: -840px 0; +} +.emoji-woman_tone3 { + background-position: -840px -20px; +} +.emoji-woman_tone4 { + background-position: -840px -40px; +} +.emoji-woman_tone5 { + background-position: -840px -60px; +} +.emoji-womans_clothes { + background-position: -840px -80px; +} +.emoji-womans_hat { + background-position: -840px -100px; +} +.emoji-womens { + background-position: -840px -120px; +} +.emoji-worried { + background-position: -840px -140px; +} +.emoji-wrench { + background-position: -840px -160px; +} +.emoji-wrestlers { + background-position: -840px -180px; +} +.emoji-wrestlers_tone1 { + background-position: -840px -200px; +} +.emoji-wrestlers_tone2 { + background-position: -840px -220px; +} +.emoji-wrestlers_tone3 { + background-position: -840px -240px; +} +.emoji-wrestlers_tone4 { + background-position: -840px -260px; +} +.emoji-wrestlers_tone5 { + background-position: -840px -280px; +} +.emoji-writing_hand { + background-position: -840px -300px; +} +.emoji-writing_hand_tone1 { + background-position: -840px -320px; +} +.emoji-writing_hand_tone2 { + background-position: -840px -340px; +} +.emoji-writing_hand_tone3 { + background-position: -840px -360px; +} +.emoji-writing_hand_tone4 { + background-position: -840px -380px; +} +.emoji-writing_hand_tone5 { + background-position: -840px -400px; +} +.emoji-x { + background-position: -840px -420px; +} +.emoji-yellow_heart { + background-position: -840px -440px; +} +.emoji-yen { + background-position: -840px -460px; +} +.emoji-yin_yang { + background-position: -840px -480px; +} +.emoji-yum { + background-position: -840px -500px; +} +.emoji-zap { + background-position: -840px -520px; +} +.emoji-zero { + background-position: -840px -540px; +} +.emoji-zipper_mouth { + background-position: -840px -560px; +} +.emoji-100 { + background-position: -840px -580px; +} + +.emoji-icon { + background-image: image-url('emoji.png'); + background-repeat: no-repeat; + color: transparent; + text-indent: -99em; + height: 20px; + width: 20px; + + @media only screen and (-webkit-min-device-pixel-ratio: 2), + only screen and (min--moz-device-pixel-ratio: 2), + only screen and (-o-min-device-pixel-ratio: 2/1), + only screen and (min-device-pixel-ratio: 2), + only screen and (min-resolution: 192dpi), + only screen and (min-resolution: 2dppx) { + background-image: image-url('emoji@2x.png'); + background-size: 860px 840px; + } +} diff --git a/app/assets/stylesheets/framework.scss b/app/assets/stylesheets/framework.scss index 2fccfa4011c..9bd35183d8a 100644 --- a/app/assets/stylesheets/framework.scss +++ b/app/assets/stylesheets/framework.scss @@ -1,64 +1,64 @@ -@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'; +@import 'framework/terms'; diff --git a/app/assets/stylesheets/framework/blocks.scss b/app/assets/stylesheets/framework/blocks.scss index c5c7afe25be..c60f65e7a85 100644 --- a/app/assets/stylesheets/framework/blocks.scss +++ b/app/assets/stylesheets/framework/blocks.scss @@ -46,7 +46,7 @@ } &.middle-block { - margin-top: 0; + margin-top: $gl-padding-24; margin-bottom: 0; } @@ -61,7 +61,7 @@ } &.footer-block { - margin-top: 0; + margin-top: $gl-padding-24; border-bottom: 0; margin-bottom: -$gl-padding; } diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss index f4f5926e198..cd9d60b96d3 100644 --- a/app/assets/stylesheets/framework/buttons.scss +++ b/app/assets/stylesheets/framework/buttons.scss @@ -106,10 +106,6 @@ @include btn-color($red-500, $red-600, $red-600, $red-700, $red-700, $red-800, $white-light); } -@mixin btn-gray { - @include btn-color($gray-light, $border-gray-normal, $gray-normal, $border-gray-normal, $gray-dark, $border-gray-dark, $gl-text-color); -} - @mixin btn-white { @include btn-color($white-light, $border-color, $white-normal, $border-white-normal, $white-dark, $border-gray-dark, $gl-text-color); } @@ -183,10 +179,6 @@ } } - &.btn-gray { - @include btn-gray; - } - &.btn-info, &.btn-primary, &.btn-register { diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss index d0dda50a835..2faea55a5f5 100644 --- a/app/assets/stylesheets/framework/common.scss +++ b/app/assets/stylesheets/framework/common.scss @@ -452,6 +452,7 @@ img.emoji { /** COMMON CLASSES **/ .prepend-top-0 { margin-top: 0; } +.prepend-top-2 { margin-top: 2px; } .prepend-top-5 { margin-top: 5px; } .prepend-top-8 { margin-top: $grid-size; } .prepend-top-10 { margin-top: 10px; } @@ -472,6 +473,7 @@ img.emoji { .append-right-20 { margin-right: 20px; } .append-bottom-0 { margin-bottom: 0; } .append-bottom-5 { margin-bottom: 5px; } +.append-bottom-8 { margin-bottom: $grid-size; } .append-bottom-10 { margin-bottom: 10px; } .append-bottom-15 { margin-bottom: 15px; } .append-bottom-20 { margin-bottom: 20px; } diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss index cc5fac6816d..664aade7375 100644 --- a/app/assets/stylesheets/framework/dropdowns.scss +++ b/app/assets/stylesheets/framework/dropdowns.scss @@ -43,7 +43,7 @@ border-color: $gray-darkest; } - [data-toggle="dropdown"] { + [data-toggle='dropdown'] { outline: 0; } } @@ -172,7 +172,11 @@ color: $brand-danger; } - &:hover, + &.disable-hover { + text-decoration: none; + } + + &:not(.disable-hover):hover, &:active, &:focus, &.is-focused { @@ -508,17 +512,16 @@ } &.is-indeterminate::before { - content: "\f068"; + content: '\f068'; } &.is-active::before { - content: "\f00c"; + content: '\f00c'; } } } } - .dropdown-title { position: relative; padding: 2px 25px 10px; @@ -724,7 +727,6 @@ } } - .dropdown-menu-due-date { .dropdown-content { max-height: 230px; @@ -854,9 +856,13 @@ header.header-content .dropdown-menu.projects-dropdown-menu { } .projects-list-frequent-container, - .projects-list-search-container, { + .projects-list-search-container { padding: 8px 0; overflow-y: auto; + + li.section-empty.section-failure { + color: $callout-danger-color; + } } .section-header, @@ -867,13 +873,6 @@ header.header-content .dropdown-menu.projects-dropdown-menu { font-size: $gl-font-size; } - .projects-list-frequent-container, - .projects-list-search-container { - li.section-empty.section-failure { - color: $callout-danger-color; - } - } - .search-input-container { position: relative; padding: 4px $gl-padding; @@ -905,8 +904,7 @@ header.header-content .dropdown-menu.projects-dropdown-menu { } .projects-list-item-container { - .project-item-avatar-container - .project-item-metadata-container { + .project-item-avatar-container .project-item-metadata-container { float: left; } diff --git a/app/assets/stylesheets/framework/emoji_sprites.scss b/app/assets/stylesheets/framework/emoji_sprites.scss deleted file mode 100644 index 0174e17b660..00000000000 --- a/app/assets/stylesheets/framework/emoji_sprites.scss +++ /dev/null @@ -1,1813 +0,0 @@ -.emoji-zzz { background-position: 0 0; } -.emoji-1234 { background-position: -20px 0; } -.emoji-1F627 { background-position: 0 -20px; } -.emoji-8ball { background-position: -20px -20px; } -.emoji-a { background-position: -40px 0; } -.emoji-ab { background-position: -40px -20px; } -.emoji-abc { background-position: 0 -40px; } -.emoji-abcd { background-position: -20px -40px; } -.emoji-accept { background-position: -40px -40px; } -.emoji-aerial_tramway { background-position: -60px 0; } -.emoji-airplane { background-position: -60px -20px; } -.emoji-airplane_arriving { background-position: -60px -40px; } -.emoji-airplane_departure { background-position: 0 -60px; } -.emoji-airplane_small { background-position: -20px -60px; } -.emoji-alarm_clock { background-position: -40px -60px; } -.emoji-alembic { background-position: -60px -60px; } -.emoji-alien { background-position: -80px 0; } -.emoji-ambulance { background-position: -80px -20px; } -.emoji-amphora { background-position: -80px -40px; } -.emoji-anchor { background-position: -80px -60px; } -.emoji-angel { background-position: 0 -80px; } -.emoji-angel_tone1 { background-position: -20px -80px; } -.emoji-angel_tone2 { background-position: -40px -80px; } -.emoji-angel_tone3 { background-position: -60px -80px; } -.emoji-angel_tone4 { background-position: -80px -80px; } -.emoji-angel_tone5 { background-position: -100px 0; } -.emoji-anger { background-position: -100px -20px; } -.emoji-anger_right { background-position: -100px -40px; } -.emoji-angry { background-position: -100px -60px; } -.emoji-ant { background-position: -100px -80px; } -.emoji-apple { background-position: 0 -100px; } -.emoji-aquarius { background-position: -20px -100px; } -.emoji-aries { background-position: -40px -100px; } -.emoji-arrow_backward { background-position: -60px -100px; } -.emoji-arrow_double_down { background-position: -80px -100px; } -.emoji-arrow_double_up { background-position: -100px -100px; } -.emoji-arrow_down { background-position: -120px 0; } -.emoji-arrow_down_small { background-position: -120px -20px; } -.emoji-arrow_forward { background-position: -120px -40px; } -.emoji-arrow_heading_down { background-position: -120px -60px; } -.emoji-arrow_heading_up { background-position: -120px -80px; } -.emoji-arrow_left { background-position: -120px -100px; } -.emoji-arrow_lower_left { background-position: 0 -120px; } -.emoji-arrow_lower_right { background-position: -20px -120px; } -.emoji-arrow_right { background-position: -40px -120px; } -.emoji-arrow_right_hook { background-position: -60px -120px; } -.emoji-arrow_up { background-position: -80px -120px; } -.emoji-arrow_up_down { background-position: -100px -120px; } -.emoji-arrow_up_small { background-position: -120px -120px; } -.emoji-arrow_upper_left { background-position: -140px 0; } -.emoji-arrow_upper_right { background-position: -140px -20px; } -.emoji-arrows_clockwise { background-position: -140px -40px; } -.emoji-arrows_counterclockwise { background-position: -140px -60px; } -.emoji-art { background-position: -140px -80px; } -.emoji-articulated_lorry { background-position: -140px -100px; } -.emoji-asterisk { background-position: -140px -120px; } -.emoji-astonished { background-position: 0 -140px; } -.emoji-athletic_shoe { background-position: -20px -140px; } -.emoji-atm { background-position: -40px -140px; } -.emoji-atom { background-position: -60px -140px; } -.emoji-avocado { background-position: -80px -140px; } -.emoji-b { background-position: -100px -140px; } -.emoji-baby { background-position: -120px -140px; } -.emoji-baby_bottle { background-position: -140px -140px; } -.emoji-baby_chick { background-position: -160px 0; } -.emoji-baby_symbol { background-position: -160px -20px; } -.emoji-baby_tone1 { background-position: -160px -40px; } -.emoji-baby_tone2 { background-position: -160px -60px; } -.emoji-baby_tone3 { background-position: -160px -80px; } -.emoji-baby_tone4 { background-position: -160px -100px; } -.emoji-baby_tone5 { background-position: -160px -120px; } -.emoji-back { background-position: -160px -140px; } -.emoji-bacon { background-position: 0 -160px; } -.emoji-badminton { background-position: -20px -160px; } -.emoji-baggage_claim { background-position: -40px -160px; } -.emoji-balloon { background-position: -60px -160px; } -.emoji-ballot_box { background-position: -80px -160px; } -.emoji-ballot_box_with_check { background-position: -100px -160px; } -.emoji-bamboo { background-position: -120px -160px; } -.emoji-banana { background-position: -140px -160px; } -.emoji-bangbang { background-position: -160px -160px; } -.emoji-bank { background-position: -180px 0; } -.emoji-bar_chart { background-position: -180px -20px; } -.emoji-barber { background-position: -180px -40px; } -.emoji-baseball { background-position: -180px -60px; } -.emoji-basketball { background-position: -180px -80px; } -.emoji-basketball_player { background-position: -180px -100px; } -.emoji-basketball_player_tone1 { background-position: -180px -120px; } -.emoji-basketball_player_tone2 { background-position: -180px -140px; } -.emoji-basketball_player_tone3 { background-position: -180px -160px; } -.emoji-basketball_player_tone4 { background-position: 0 -180px; } -.emoji-basketball_player_tone5 { background-position: -20px -180px; } -.emoji-bat { background-position: -40px -180px; } -.emoji-bath { background-position: -60px -180px; } -.emoji-bath_tone1 { background-position: -80px -180px; } -.emoji-bath_tone2 { background-position: -100px -180px; } -.emoji-bath_tone3 { background-position: -120px -180px; } -.emoji-bath_tone4 { background-position: -140px -180px; } -.emoji-bath_tone5 { background-position: -160px -180px; } -.emoji-bathtub { background-position: -180px -180px; } -.emoji-battery { background-position: -200px 0; } -.emoji-beach { background-position: -200px -20px; } -.emoji-beach_umbrella { background-position: -200px -40px; } -.emoji-bear { background-position: -200px -60px; } -.emoji-bed { background-position: -200px -80px; } -.emoji-bee { background-position: -200px -100px; } -.emoji-beer { background-position: -200px -120px; } -.emoji-beers { background-position: -200px -140px; } -.emoji-beetle { background-position: -200px -160px; } -.emoji-beginner { background-position: -200px -180px; } -.emoji-bell { background-position: 0 -200px; } -.emoji-bellhop { background-position: -20px -200px; } -.emoji-bento { background-position: -40px -200px; } -.emoji-bicyclist { background-position: -60px -200px; } -.emoji-bicyclist_tone1 { background-position: -80px -200px; } -.emoji-bicyclist_tone2 { background-position: -100px -200px; } -.emoji-bicyclist_tone3 { background-position: -120px -200px; } -.emoji-bicyclist_tone4 { background-position: -140px -200px; } -.emoji-bicyclist_tone5 { background-position: -160px -200px; } -.emoji-bike { background-position: -180px -200px; } -.emoji-bikini { background-position: -200px -200px; } -.emoji-biohazard { background-position: -220px 0; } -.emoji-bird { background-position: -220px -20px; } -.emoji-birthday { background-position: -220px -40px; } -.emoji-black_circle { background-position: -220px -60px; } -.emoji-black_heart { background-position: -220px -80px; } -.emoji-black_joker { background-position: -220px -100px; } -.emoji-black_large_square { background-position: -220px -120px; } -.emoji-black_medium_small_square { background-position: -220px -140px; } -.emoji-black_medium_square { background-position: -220px -160px; } -.emoji-black_nib { background-position: -220px -180px; } -.emoji-black_small_square { background-position: -220px -200px; } -.emoji-black_square_button { background-position: 0 -220px; } -.emoji-blossom { background-position: -20px -220px; } -.emoji-blowfish { background-position: -40px -220px; } -.emoji-blue_book { background-position: -60px -220px; } -.emoji-blue_car { background-position: -80px -220px; } -.emoji-blue_heart { background-position: -100px -220px; } -.emoji-blush { background-position: -120px -220px; } -.emoji-boar { background-position: -140px -220px; } -.emoji-bomb { background-position: -160px -220px; } -.emoji-book { background-position: -180px -220px; } -.emoji-bookmark { background-position: -200px -220px; } -.emoji-bookmark_tabs { background-position: -220px -220px; } -.emoji-books { background-position: -240px 0; } -.emoji-boom { background-position: -240px -20px; } -.emoji-boot { background-position: -240px -40px; } -.emoji-bouquet { background-position: -240px -60px; } -.emoji-bow { background-position: -240px -80px; } -.emoji-bow_and_arrow { background-position: -240px -100px; } -.emoji-bow_tone1 { background-position: -240px -120px; } -.emoji-bow_tone2 { background-position: -240px -140px; } -.emoji-bow_tone3 { background-position: -240px -160px; } -.emoji-bow_tone4 { background-position: -240px -180px; } -.emoji-bow_tone5 { background-position: -240px -200px; } -.emoji-bowling { background-position: -240px -220px; } -.emoji-boxing_glove { background-position: 0 -240px; } -.emoji-boy { background-position: -20px -240px; } -.emoji-boy_tone1 { background-position: -40px -240px; } -.emoji-boy_tone2 { background-position: -60px -240px; } -.emoji-boy_tone3 { background-position: -80px -240px; } -.emoji-boy_tone4 { background-position: -100px -240px; } -.emoji-boy_tone5 { background-position: -120px -240px; } -.emoji-bread { background-position: -140px -240px; } -.emoji-bride_with_veil { background-position: -160px -240px; } -.emoji-bride_with_veil_tone1 { background-position: -180px -240px; } -.emoji-bride_with_veil_tone2 { background-position: -200px -240px; } -.emoji-bride_with_veil_tone3 { background-position: -220px -240px; } -.emoji-bride_with_veil_tone4 { background-position: -240px -240px; } -.emoji-bride_with_veil_tone5 { background-position: -260px 0; } -.emoji-bridge_at_night { background-position: -260px -20px; } -.emoji-briefcase { background-position: -260px -40px; } -.emoji-broken_heart { background-position: -260px -60px; } -.emoji-bug { background-position: -260px -80px; } -.emoji-bulb { background-position: -260px -100px; } -.emoji-bullettrain_front { background-position: -260px -120px; } -.emoji-bullettrain_side { background-position: -260px -140px; } -.emoji-burrito { background-position: -260px -160px; } -.emoji-bus { background-position: -260px -180px; } -.emoji-busstop { background-position: -260px -200px; } -.emoji-bust_in_silhouette { background-position: -260px -220px; } -.emoji-busts_in_silhouette { background-position: -260px -240px; } -.emoji-butterfly { background-position: 0 -260px; } -.emoji-cactus { background-position: -20px -260px; } -.emoji-cake { background-position: -40px -260px; } -.emoji-calendar { background-position: -60px -260px; } -.emoji-calendar_spiral { background-position: -80px -260px; } -.emoji-call_me { background-position: -100px -260px; } -.emoji-call_me_tone1 { background-position: -120px -260px; } -.emoji-call_me_tone2 { background-position: -140px -260px; } -.emoji-call_me_tone3 { background-position: -160px -260px; } -.emoji-call_me_tone4 { background-position: -180px -260px; } -.emoji-call_me_tone5 { background-position: -200px -260px; } -.emoji-calling { background-position: -220px -260px; } -.emoji-camel { background-position: -240px -260px; } -.emoji-camera { background-position: -260px -260px; } -.emoji-camera_with_flash { background-position: -280px 0; } -.emoji-camping { background-position: -280px -20px; } -.emoji-cancer { background-position: -280px -40px; } -.emoji-candle { background-position: -280px -60px; } -.emoji-candy { background-position: -280px -80px; } -.emoji-canoe { background-position: -280px -100px; } -.emoji-capital_abcd { background-position: -280px -120px; } -.emoji-capricorn { background-position: -280px -140px; } -.emoji-card_box { background-position: -280px -160px; } -.emoji-card_index { background-position: -280px -180px; } -.emoji-carousel_horse { background-position: -280px -200px; } -.emoji-carrot { background-position: -280px -220px; } -.emoji-cartwheel { background-position: -280px -240px; } -.emoji-cartwheel_tone1 { background-position: -280px -260px; } -.emoji-cartwheel_tone2 { background-position: 0 -280px; } -.emoji-cartwheel_tone3 { background-position: -20px -280px; } -.emoji-cartwheel_tone4 { background-position: -40px -280px; } -.emoji-cartwheel_tone5 { background-position: -60px -280px; } -.emoji-cat { background-position: -80px -280px; } -.emoji-cat2 { background-position: -100px -280px; } -.emoji-cd { background-position: -120px -280px; } -.emoji-chains { background-position: -140px -280px; } -.emoji-champagne { background-position: -160px -280px; } -.emoji-champagne_glass { background-position: -180px -280px; } -.emoji-chart { background-position: -200px -280px; } -.emoji-chart_with_downwards_trend { background-position: -220px -280px; } -.emoji-chart_with_upwards_trend { background-position: -240px -280px; } -.emoji-checkered_flag { background-position: -260px -280px; } -.emoji-cheese { background-position: -280px -280px; } -.emoji-cherries { background-position: -300px 0; } -.emoji-cherry_blossom { background-position: -300px -20px; } -.emoji-chestnut { background-position: -300px -40px; } -.emoji-chicken { background-position: -300px -60px; } -.emoji-children_crossing { background-position: -300px -80px; } -.emoji-chipmunk { background-position: -300px -100px; } -.emoji-chocolate_bar { background-position: -300px -120px; } -.emoji-christmas_tree { background-position: -300px -140px; } -.emoji-church { background-position: -300px -160px; } -.emoji-cinema { background-position: -300px -180px; } -.emoji-circus_tent { background-position: -300px -200px; } -.emoji-city_dusk { background-position: -300px -220px; } -.emoji-city_sunset { background-position: -300px -240px; } -.emoji-cityscape { background-position: -300px -260px; } -.emoji-cl { background-position: -300px -280px; } -.emoji-clap { background-position: 0 -300px; } -.emoji-clap_tone1 { background-position: -20px -300px; } -.emoji-clap_tone2 { background-position: -40px -300px; } -.emoji-clap_tone3 { background-position: -60px -300px; } -.emoji-clap_tone4 { background-position: -80px -300px; } -.emoji-clap_tone5 { background-position: -100px -300px; } -.emoji-clapper { background-position: -120px -300px; } -.emoji-classical_building { background-position: -140px -300px; } -.emoji-clipboard { background-position: -160px -300px; } -.emoji-clock { background-position: -180px -300px; } -.emoji-clock1 { background-position: -200px -300px; } -.emoji-clock10 { background-position: -220px -300px; } -.emoji-clock1030 { background-position: -240px -300px; } -.emoji-clock11 { background-position: -260px -300px; } -.emoji-clock1130 { background-position: -280px -300px; } -.emoji-clock12 { background-position: -300px -300px; } -.emoji-clock1230 { background-position: -320px 0; } -.emoji-clock130 { background-position: -320px -20px; } -.emoji-clock2 { background-position: -320px -40px; } -.emoji-clock230 { background-position: -320px -60px; } -.emoji-clock3 { background-position: -320px -80px; } -.emoji-clock330 { background-position: -320px -100px; } -.emoji-clock4 { background-position: -320px -120px; } -.emoji-clock430 { background-position: -320px -140px; } -.emoji-clock5 { background-position: -320px -160px; } -.emoji-clock530 { background-position: -320px -180px; } -.emoji-clock6 { background-position: -320px -200px; } -.emoji-clock630 { background-position: -320px -220px; } -.emoji-clock7 { background-position: -320px -240px; } -.emoji-clock730 { background-position: -320px -260px; } -.emoji-clock8 { background-position: -320px -280px; } -.emoji-clock830 { background-position: -320px -300px; } -.emoji-clock9 { background-position: 0 -320px; } -.emoji-clock930 { background-position: -20px -320px; } -.emoji-closed_book { background-position: -40px -320px; } -.emoji-closed_lock_with_key { background-position: -60px -320px; } -.emoji-closed_umbrella { background-position: -80px -320px; } -.emoji-cloud { background-position: -100px -320px; } -.emoji-cloud_lightning { background-position: -120px -320px; } -.emoji-cloud_rain { background-position: -140px -320px; } -.emoji-cloud_snow { background-position: -160px -320px; } -.emoji-cloud_tornado { background-position: -180px -320px; } -.emoji-clown { background-position: -200px -320px; } -.emoji-clubs { background-position: -220px -320px; } -.emoji-cocktail { background-position: -240px -320px; } -.emoji-coffee { background-position: -260px -320px; } -.emoji-coffin { background-position: -280px -320px; } -.emoji-cold_sweat { background-position: -300px -320px; } -.emoji-comet { background-position: -320px -320px; } -.emoji-compression { background-position: -340px 0; } -.emoji-computer { background-position: -340px -20px; } -.emoji-confetti_ball { background-position: -340px -40px; } -.emoji-confounded { background-position: -340px -60px; } -.emoji-confused { background-position: -340px -80px; } -.emoji-congratulations { background-position: -340px -100px; } -.emoji-construction { background-position: -340px -120px; } -.emoji-construction_site { background-position: -340px -140px; } -.emoji-construction_worker { background-position: -340px -160px; } -.emoji-construction_worker_tone1 { background-position: -340px -180px; } -.emoji-construction_worker_tone2 { background-position: -340px -200px; } -.emoji-construction_worker_tone3 { background-position: -340px -220px; } -.emoji-construction_worker_tone4 { background-position: -340px -240px; } -.emoji-construction_worker_tone5 { background-position: -340px -260px; } -.emoji-control_knobs { background-position: -340px -280px; } -.emoji-convenience_store { background-position: -340px -300px; } -.emoji-cookie { background-position: -340px -320px; } -.emoji-cooking { background-position: 0 -340px; } -.emoji-cool { background-position: -20px -340px; } -.emoji-cop { background-position: -40px -340px; } -.emoji-cop_tone1 { background-position: -60px -340px; } -.emoji-cop_tone2 { background-position: -80px -340px; } -.emoji-cop_tone3 { background-position: -100px -340px; } -.emoji-cop_tone4 { background-position: -120px -340px; } -.emoji-cop_tone5 { background-position: -140px -340px; } -.emoji-copyright { background-position: -160px -340px; } -.emoji-corn { background-position: -180px -340px; } -.emoji-couch { background-position: -200px -340px; } -.emoji-couple { background-position: -220px -340px; } -.emoji-couple_mm { background-position: -240px -340px; } -.emoji-couple_with_heart { background-position: -260px -340px; } -.emoji-couple_ww { background-position: -280px -340px; } -.emoji-couplekiss { background-position: -300px -340px; } -.emoji-cow { background-position: -320px -340px; } -.emoji-cow2 { background-position: -340px -340px; } -.emoji-cowboy { background-position: -360px 0; } -.emoji-crab { background-position: -360px -20px; } -.emoji-crayon { background-position: -360px -40px; } -.emoji-credit_card { background-position: -360px -60px; } -.emoji-crescent_moon { background-position: -360px -80px; } -.emoji-cricket { background-position: -360px -100px; } -.emoji-crocodile { background-position: -360px -120px; } -.emoji-croissant { background-position: -360px -140px; } -.emoji-cross { background-position: -360px -160px; } -.emoji-crossed_flags { background-position: -360px -180px; } -.emoji-crossed_swords { background-position: -360px -200px; } -.emoji-crown { background-position: -360px -220px; } -.emoji-cruise_ship { background-position: -360px -240px; } -.emoji-cry { background-position: -360px -260px; } -.emoji-crying_cat_face { background-position: -360px -280px; } -.emoji-crystal_ball { background-position: -360px -300px; } -.emoji-cucumber { background-position: -360px -320px; } -.emoji-cupid { background-position: -360px -340px; } -.emoji-curly_loop { background-position: 0 -360px; } -.emoji-currency_exchange { background-position: -20px -360px; } -.emoji-curry { background-position: -40px -360px; } -.emoji-custard { background-position: -60px -360px; } -.emoji-customs { background-position: -80px -360px; } -.emoji-cyclone { background-position: -100px -360px; } -.emoji-dagger { background-position: -120px -360px; } -.emoji-dancer { background-position: -140px -360px; } -.emoji-dancer_tone1 { background-position: -160px -360px; } -.emoji-dancer_tone2 { background-position: -180px -360px; } -.emoji-dancer_tone3 { background-position: -200px -360px; } -.emoji-dancer_tone4 { background-position: -220px -360px; } -.emoji-dancer_tone5 { background-position: -240px -360px; } -.emoji-dancers { background-position: -260px -360px; } -.emoji-dango { background-position: -280px -360px; } -.emoji-dark_sunglasses { background-position: -300px -360px; } -.emoji-dart { background-position: -320px -360px; } -.emoji-dash { background-position: -340px -360px; } -.emoji-date { background-position: -360px -360px; } -.emoji-deciduous_tree { background-position: -380px 0; } -.emoji-deer { background-position: -380px -20px; } -.emoji-department_store { background-position: -380px -40px; } -.emoji-desert { background-position: -380px -60px; } -.emoji-desktop { background-position: -380px -80px; } -.emoji-diamond_shape_with_a_dot_inside { background-position: -380px -100px; } -.emoji-diamonds { background-position: -380px -120px; } -.emoji-disappointed { background-position: -380px -140px; } -.emoji-disappointed_relieved { background-position: -380px -160px; } -.emoji-dividers { background-position: -380px -180px; } -.emoji-dizzy { background-position: -380px -200px; } -.emoji-dizzy_face { background-position: -380px -220px; } -.emoji-do_not_litter { background-position: -380px -240px; } -.emoji-dog { background-position: -380px -260px; } -.emoji-dog2 { background-position: -380px -280px; } -.emoji-dollar { background-position: -380px -300px; } -.emoji-dolls { background-position: -380px -320px; } -.emoji-dolphin { background-position: -380px -340px; } -.emoji-door { background-position: -380px -360px; } -.emoji-doughnut { background-position: 0 -380px; } -.emoji-dove { background-position: -20px -380px; } -.emoji-dragon { background-position: -40px -380px; } -.emoji-dragon_face { background-position: -60px -380px; } -.emoji-dress { background-position: -80px -380px; } -.emoji-dromedary_camel { background-position: -100px -380px; } -.emoji-drooling_face { background-position: -120px -380px; } -.emoji-droplet { background-position: -140px -380px; } -.emoji-drum { background-position: -160px -380px; } -.emoji-duck { background-position: -180px -380px; } -.emoji-dvd { background-position: -200px -380px; } -.emoji-e-mail { background-position: -220px -380px; } -.emoji-eagle { background-position: -240px -380px; } -.emoji-ear { background-position: -260px -380px; } -.emoji-ear_of_rice { background-position: -280px -380px; } -.emoji-ear_tone1 { background-position: -300px -380px; } -.emoji-ear_tone2 { background-position: -320px -380px; } -.emoji-ear_tone3 { background-position: -340px -380px; } -.emoji-ear_tone4 { background-position: -360px -380px; } -.emoji-ear_tone5 { background-position: -380px -380px; } -.emoji-earth_africa { background-position: -400px 0; } -.emoji-earth_americas { background-position: -400px -20px; } -.emoji-earth_asia { background-position: -400px -40px; } -.emoji-egg { background-position: -400px -60px; } -.emoji-eggplant { background-position: -400px -80px; } -.emoji-eight { background-position: -400px -100px; } -.emoji-eight_pointed_black_star { background-position: -400px -120px; } -.emoji-eight_spoked_asterisk { background-position: -400px -140px; } -.emoji-eject { background-position: -400px -160px; } -.emoji-electric_plug { background-position: -400px -180px; } -.emoji-elephant { background-position: -400px -200px; } -.emoji-end { background-position: -400px -220px; } -.emoji-envelope { background-position: -400px -240px; } -.emoji-envelope_with_arrow { background-position: -400px -260px; } -.emoji-euro { background-position: -400px -280px; } -.emoji-european_castle { background-position: -400px -300px; } -.emoji-european_post_office { background-position: -400px -320px; } -.emoji-evergreen_tree { background-position: -400px -340px; } -.emoji-exclamation { background-position: -400px -360px; } -.emoji-expressionless { background-position: -400px -380px; } -.emoji-eye { background-position: 0 -400px; } -.emoji-eye_in_speech_bubble { background-position: -20px -400px; } -.emoji-eyeglasses { background-position: -40px -400px; } -.emoji-eyes { background-position: -60px -400px; } -.emoji-face_palm { background-position: -80px -400px; } -.emoji-face_palm_tone1 { background-position: -100px -400px; } -.emoji-face_palm_tone2 { background-position: -120px -400px; } -.emoji-face_palm_tone3 { background-position: -140px -400px; } -.emoji-face_palm_tone4 { background-position: -160px -400px; } -.emoji-face_palm_tone5 { background-position: -180px -400px; } -.emoji-factory { background-position: -200px -400px; } -.emoji-fallen_leaf { background-position: -220px -400px; } -.emoji-family { background-position: -240px -400px; } -.emoji-family_mmb { background-position: -260px -400px; } -.emoji-family_mmbb { background-position: -280px -400px; } -.emoji-family_mmg { background-position: -300px -400px; } -.emoji-family_mmgb { background-position: -320px -400px; } -.emoji-family_mmgg { background-position: -340px -400px; } -.emoji-family_mwbb { background-position: -360px -400px; } -.emoji-family_mwg { background-position: -380px -400px; } -.emoji-family_mwgb { background-position: -400px -400px; } -.emoji-family_mwgg { background-position: -420px 0; } -.emoji-family_wwb { background-position: -420px -20px; } -.emoji-family_wwbb { background-position: -420px -40px; } -.emoji-family_wwg { background-position: -420px -60px; } -.emoji-family_wwgb { background-position: -420px -80px; } -.emoji-family_wwgg { background-position: -420px -100px; } -.emoji-fast_forward { background-position: -420px -120px; } -.emoji-fax { background-position: -420px -140px; } -.emoji-fearful { background-position: -420px -160px; } -.emoji-feet { background-position: -420px -180px; } -.emoji-fencer { background-position: -420px -200px; } -.emoji-ferris_wheel { background-position: -420px -220px; } -.emoji-ferry { background-position: -420px -240px; } -.emoji-field_hockey { background-position: -420px -260px; } -.emoji-file_cabinet { background-position: -420px -280px; } -.emoji-file_folder { background-position: -420px -300px; } -.emoji-film_frames { background-position: -420px -320px; } -.emoji-fingers_crossed { background-position: -420px -340px; } -.emoji-fingers_crossed_tone1 { background-position: -420px -360px; } -.emoji-fingers_crossed_tone2 { background-position: -420px -380px; } -.emoji-fingers_crossed_tone3 { background-position: -420px -400px; } -.emoji-fingers_crossed_tone4 { background-position: 0 -420px; } -.emoji-fingers_crossed_tone5 { background-position: -20px -420px; } -.emoji-fire { background-position: -40px -420px; } -.emoji-fire_engine { background-position: -60px -420px; } -.emoji-fireworks { background-position: -80px -420px; } -.emoji-first_place { background-position: -100px -420px; } -.emoji-first_quarter_moon { background-position: -120px -420px; } -.emoji-first_quarter_moon_with_face { background-position: -140px -420px; } -.emoji-fish { background-position: -160px -420px; } -.emoji-fish_cake { background-position: -180px -420px; } -.emoji-fishing_pole_and_fish { background-position: -200px -420px; } -.emoji-fist { background-position: -220px -420px; } -.emoji-fist_tone1 { background-position: -240px -420px; } -.emoji-fist_tone2 { background-position: -260px -420px; } -.emoji-fist_tone3 { background-position: -280px -420px; } -.emoji-fist_tone4 { background-position: -300px -420px; } -.emoji-fist_tone5 { background-position: -320px -420px; } -.emoji-five { background-position: -340px -420px; } -.emoji-flag_ac { background-position: -360px -420px; } -.emoji-flag_ad { background-position: -380px -420px; } -.emoji-flag_ae { background-position: -400px -420px; } -.emoji-flag_af { background-position: -420px -420px; } -.emoji-flag_ag { background-position: -440px 0; } -.emoji-flag_ai { background-position: -440px -20px; } -.emoji-flag_al { background-position: -440px -40px; } -.emoji-flag_am { background-position: -440px -60px; } -.emoji-flag_ao { background-position: -440px -80px; } -.emoji-flag_aq { background-position: -440px -100px; } -.emoji-flag_ar { background-position: -440px -120px; } -.emoji-flag_as { background-position: -440px -140px; } -.emoji-flag_at { background-position: -440px -160px; } -.emoji-flag_au { background-position: -440px -180px; } -.emoji-flag_aw { background-position: -440px -200px; } -.emoji-flag_ax { background-position: -440px -220px; } -.emoji-flag_az { background-position: -440px -240px; } -.emoji-flag_ba { background-position: -440px -260px; } -.emoji-flag_bb { background-position: -440px -280px; } -.emoji-flag_bd { background-position: -440px -300px; } -.emoji-flag_be { background-position: -440px -320px; } -.emoji-flag_bf { background-position: -440px -340px; } -.emoji-flag_bg { background-position: -440px -360px; } -.emoji-flag_bh { background-position: -440px -380px; } -.emoji-flag_bi { background-position: -440px -400px; } -.emoji-flag_bj { background-position: -440px -420px; } -.emoji-flag_bl { background-position: 0 -440px; } -.emoji-flag_black { background-position: -20px -440px; } -.emoji-flag_bm { background-position: -40px -440px; } -.emoji-flag_bn { background-position: -60px -440px; } -.emoji-flag_bo { background-position: -80px -440px; } -.emoji-flag_bq { background-position: -100px -440px; } -.emoji-flag_br { background-position: -120px -440px; } -.emoji-flag_bs { background-position: -140px -440px; } -.emoji-flag_bt { background-position: -160px -440px; } -.emoji-flag_bv { background-position: -180px -440px; } -.emoji-flag_bw { background-position: -200px -440px; } -.emoji-flag_by { background-position: -220px -440px; } -.emoji-flag_bz { background-position: -240px -440px; } -.emoji-flag_ca { background-position: -260px -440px; } -.emoji-flag_cc { background-position: -280px -440px; } -.emoji-flag_cd { background-position: -300px -440px; } -.emoji-flag_cf { background-position: -320px -440px; } -.emoji-flag_cg { background-position: -340px -440px; } -.emoji-flag_ch { background-position: -360px -440px; } -.emoji-flag_ci { background-position: -380px -440px; } -.emoji-flag_ck { background-position: -400px -440px; } -.emoji-flag_cl { background-position: -420px -440px; } -.emoji-flag_cm { background-position: -440px -440px; } -.emoji-flag_cn { background-position: -460px 0; } -.emoji-flag_co { background-position: -460px -20px; } -.emoji-flag_cp { background-position: -460px -40px; } -.emoji-flag_cr { background-position: -460px -60px; } -.emoji-flag_cu { background-position: -460px -80px; } -.emoji-flag_cv { background-position: -460px -100px; } -.emoji-flag_cw { background-position: -460px -120px; } -.emoji-flag_cx { background-position: -460px -140px; } -.emoji-flag_cy { background-position: -460px -160px; } -.emoji-flag_cz { background-position: -460px -180px; } -.emoji-flag_de { background-position: -460px -200px; } -.emoji-flag_dg { background-position: -460px -220px; } -.emoji-flag_dj { background-position: -460px -240px; } -.emoji-flag_dk { background-position: -460px -260px; } -.emoji-flag_dm { background-position: -460px -280px; } -.emoji-flag_do { background-position: -460px -300px; } -.emoji-flag_dz { background-position: -460px -320px; } -.emoji-flag_ea { background-position: -460px -340px; } -.emoji-flag_ec { background-position: -460px -360px; } -.emoji-flag_ee { background-position: -460px -380px; } -.emoji-flag_eg { background-position: -460px -400px; } -.emoji-flag_eh { background-position: -460px -420px; } -.emoji-flag_er { background-position: -460px -440px; } -.emoji-flag_es { background-position: 0 -460px; } -.emoji-flag_et { background-position: -20px -460px; } -.emoji-flag_eu { background-position: -40px -460px; } -.emoji-flag_fi { background-position: -60px -460px; } -.emoji-flag_fj { background-position: -80px -460px; } -.emoji-flag_fk { background-position: -100px -460px; } -.emoji-flag_fm { background-position: -120px -460px; } -.emoji-flag_fo { background-position: -140px -460px; } -.emoji-flag_fr { background-position: -160px -460px; } -.emoji-flag_ga { background-position: -180px -460px; } -.emoji-flag_gb { background-position: -200px -460px; } -.emoji-flag_gd { background-position: -220px -460px; } -.emoji-flag_ge { background-position: -240px -460px; } -.emoji-flag_gf { background-position: -260px -460px; } -.emoji-flag_gg { background-position: -280px -460px; } -.emoji-flag_gh { background-position: -300px -460px; } -.emoji-flag_gi { background-position: -320px -460px; } -.emoji-flag_gl { background-position: -340px -460px; } -.emoji-flag_gm { background-position: -360px -460px; } -.emoji-flag_gn { background-position: -380px -460px; } -.emoji-flag_gp { background-position: -400px -460px; } -.emoji-flag_gq { background-position: -420px -460px; } -.emoji-flag_gr { background-position: -440px -460px; } -.emoji-flag_gs { background-position: -460px -460px; } -.emoji-flag_gt { background-position: -480px 0; } -.emoji-flag_gu { background-position: -480px -20px; } -.emoji-flag_gw { background-position: -480px -40px; } -.emoji-flag_gy { background-position: -480px -60px; } -.emoji-flag_hk { background-position: -480px -80px; } -.emoji-flag_hm { background-position: -480px -100px; } -.emoji-flag_hn { background-position: -480px -120px; } -.emoji-flag_hr { background-position: -480px -140px; } -.emoji-flag_ht { background-position: -480px -160px; } -.emoji-flag_hu { background-position: -480px -180px; } -.emoji-flag_ic { background-position: -480px -200px; } -.emoji-flag_id { background-position: -480px -220px; } -.emoji-flag_ie { background-position: -480px -240px; } -.emoji-flag_il { background-position: -480px -260px; } -.emoji-flag_im { background-position: -480px -280px; } -.emoji-flag_in { background-position: -480px -300px; } -.emoji-flag_io { background-position: -480px -320px; } -.emoji-flag_iq { background-position: -480px -340px; } -.emoji-flag_ir { background-position: -480px -360px; } -.emoji-flag_is { background-position: -480px -380px; } -.emoji-flag_it { background-position: -480px -400px; } -.emoji-flag_je { background-position: -480px -420px; } -.emoji-flag_jm { background-position: -480px -440px; } -.emoji-flag_jo { background-position: -480px -460px; } -.emoji-flag_jp { background-position: 0 -480px; } -.emoji-flag_ke { background-position: -20px -480px; } -.emoji-flag_kg { background-position: -40px -480px; } -.emoji-flag_kh { background-position: -60px -480px; } -.emoji-flag_ki { background-position: -80px -480px; } -.emoji-flag_km { background-position: -100px -480px; } -.emoji-flag_kn { background-position: -120px -480px; } -.emoji-flag_kp { background-position: -140px -480px; } -.emoji-flag_kr { background-position: -160px -480px; } -.emoji-flag_kw { background-position: -180px -480px; } -.emoji-flag_ky { background-position: -200px -480px; } -.emoji-flag_kz { background-position: -220px -480px; } -.emoji-flag_la { background-position: -240px -480px; } -.emoji-flag_lb { background-position: -260px -480px; } -.emoji-flag_lc { background-position: -280px -480px; } -.emoji-flag_li { background-position: -300px -480px; } -.emoji-flag_lk { background-position: -320px -480px; } -.emoji-flag_lr { background-position: -340px -480px; } -.emoji-flag_ls { background-position: -360px -480px; } -.emoji-flag_lt { background-position: -380px -480px; } -.emoji-flag_lu { background-position: -400px -480px; } -.emoji-flag_lv { background-position: -420px -480px; } -.emoji-flag_ly { background-position: -440px -480px; } -.emoji-flag_ma { background-position: -460px -480px; } -.emoji-flag_mc { background-position: -480px -480px; } -.emoji-flag_md { background-position: -500px 0; } -.emoji-flag_me { background-position: -500px -20px; } -.emoji-flag_mf { background-position: -500px -40px; } -.emoji-flag_mg { background-position: -500px -60px; } -.emoji-flag_mh { background-position: -500px -80px; } -.emoji-flag_mk { background-position: -500px -100px; } -.emoji-flag_ml { background-position: -500px -120px; } -.emoji-flag_mm { background-position: -500px -140px; } -.emoji-flag_mn { background-position: -500px -160px; } -.emoji-flag_mo { background-position: -500px -180px; } -.emoji-flag_mp { background-position: -500px -200px; } -.emoji-flag_mq { background-position: -500px -220px; } -.emoji-flag_mr { background-position: -500px -240px; } -.emoji-flag_ms { background-position: -500px -260px; } -.emoji-flag_mt { background-position: -500px -280px; } -.emoji-flag_mu { background-position: -500px -300px; } -.emoji-flag_mv { background-position: -500px -320px; } -.emoji-flag_mw { background-position: -500px -340px; } -.emoji-flag_mx { background-position: -500px -360px; } -.emoji-flag_my { background-position: -500px -380px; } -.emoji-flag_mz { background-position: -500px -400px; } -.emoji-flag_na { background-position: -500px -420px; } -.emoji-flag_nc { background-position: -500px -440px; } -.emoji-flag_ne { background-position: -500px -460px; } -.emoji-flag_nf { background-position: -500px -480px; } -.emoji-flag_ng { background-position: 0 -500px; } -.emoji-flag_ni { background-position: -20px -500px; } -.emoji-flag_nl { background-position: -40px -500px; } -.emoji-flag_no { background-position: -60px -500px; } -.emoji-flag_np { background-position: -80px -500px; } -.emoji-flag_nr { background-position: -100px -500px; } -.emoji-flag_nu { background-position: -120px -500px; } -.emoji-flag_nz { background-position: -140px -500px; } -.emoji-flag_om { background-position: -160px -500px; } -.emoji-flag_pa { background-position: -180px -500px; } -.emoji-flag_pe { background-position: -200px -500px; } -.emoji-flag_pf { background-position: -220px -500px; } -.emoji-flag_pg { background-position: -240px -500px; } -.emoji-flag_ph { background-position: -260px -500px; } -.emoji-flag_pk { background-position: -280px -500px; } -.emoji-flag_pl { background-position: -300px -500px; } -.emoji-flag_pm { background-position: -320px -500px; } -.emoji-flag_pn { background-position: -340px -500px; } -.emoji-flag_pr { background-position: -360px -500px; } -.emoji-flag_ps { background-position: -380px -500px; } -.emoji-flag_pt { background-position: -400px -500px; } -.emoji-flag_pw { background-position: -420px -500px; } -.emoji-flag_py { background-position: -440px -500px; } -.emoji-flag_qa { background-position: -460px -500px; } -.emoji-flag_re { background-position: -480px -500px; } -.emoji-flag_ro { background-position: -500px -500px; } -.emoji-flag_rs { background-position: -520px 0; } -.emoji-flag_ru { background-position: -520px -20px; } -.emoji-flag_rw { background-position: -520px -40px; } -.emoji-flag_sa { background-position: -520px -60px; } -.emoji-flag_sb { background-position: -520px -80px; } -.emoji-flag_sc { background-position: -520px -100px; } -.emoji-flag_sd { background-position: -520px -120px; } -.emoji-flag_se { background-position: -520px -140px; } -.emoji-flag_sg { background-position: -520px -160px; } -.emoji-flag_sh { background-position: -520px -180px; } -.emoji-flag_si { background-position: -520px -200px; } -.emoji-flag_sj { background-position: -520px -220px; } -.emoji-flag_sk { background-position: -520px -240px; } -.emoji-flag_sl { background-position: -520px -260px; } -.emoji-flag_sm { background-position: -520px -280px; } -.emoji-flag_sn { background-position: -520px -300px; } -.emoji-flag_so { background-position: -520px -320px; } -.emoji-flag_sr { background-position: -520px -340px; } -.emoji-flag_ss { background-position: -520px -360px; } -.emoji-flag_st { background-position: -520px -380px; } -.emoji-flag_sv { background-position: -520px -400px; } -.emoji-flag_sx { background-position: -520px -420px; } -.emoji-flag_sy { background-position: -520px -440px; } -.emoji-flag_sz { background-position: -520px -460px; } -.emoji-flag_ta { background-position: -520px -480px; } -.emoji-flag_tc { background-position: -520px -500px; } -.emoji-flag_td { background-position: 0 -520px; } -.emoji-flag_tf { background-position: -20px -520px; } -.emoji-flag_tg { background-position: -40px -520px; } -.emoji-flag_th { background-position: -60px -520px; } -.emoji-flag_tj { background-position: -80px -520px; } -.emoji-flag_tk { background-position: -100px -520px; } -.emoji-flag_tl { background-position: -120px -520px; } -.emoji-flag_tm { background-position: -140px -520px; } -.emoji-flag_tn { background-position: -160px -520px; } -.emoji-flag_to { background-position: -180px -520px; } -.emoji-flag_tr { background-position: -200px -520px; } -.emoji-flag_tt { background-position: -220px -520px; } -.emoji-flag_tv { background-position: -240px -520px; } -.emoji-flag_tw { background-position: -260px -520px; } -.emoji-flag_tz { background-position: -280px -520px; } -.emoji-flag_ua { background-position: -300px -520px; } -.emoji-flag_ug { background-position: -320px -520px; } -.emoji-flag_um { background-position: -340px -520px; } -.emoji-flag_us { background-position: -360px -520px; } -.emoji-flag_uy { background-position: -380px -520px; } -.emoji-flag_uz { background-position: -400px -520px; } -.emoji-flag_va { background-position: -420px -520px; } -.emoji-flag_vc { background-position: -440px -520px; } -.emoji-flag_ve { background-position: -460px -520px; } -.emoji-flag_vg { background-position: -480px -520px; } -.emoji-flag_vi { background-position: -500px -520px; } -.emoji-flag_vn { background-position: -520px -520px; } -.emoji-flag_vu { background-position: -540px 0; } -.emoji-flag_wf { background-position: -540px -20px; } -.emoji-flag_white { background-position: -540px -40px; } -.emoji-flag_ws { background-position: -540px -60px; } -.emoji-flag_xk { background-position: -540px -80px; } -.emoji-flag_ye { background-position: -540px -100px; } -.emoji-flag_yt { background-position: -540px -120px; } -.emoji-flag_za { background-position: -540px -140px; } -.emoji-flag_zm { background-position: -540px -160px; } -.emoji-flag_zw { background-position: -540px -180px; } -.emoji-flags { background-position: -540px -200px; } -.emoji-flashlight { background-position: -540px -220px; } -.emoji-fleur-de-lis { background-position: -540px -240px; } -.emoji-floppy_disk { background-position: -540px -260px; } -.emoji-flower_playing_cards { background-position: -540px -280px; } -.emoji-flushed { background-position: -540px -300px; } -.emoji-fog { background-position: -540px -320px; } -.emoji-foggy { background-position: -540px -340px; } -.emoji-football { background-position: -540px -360px; } -.emoji-footprints { background-position: -540px -380px; } -.emoji-fork_and_knife { background-position: -540px -400px; } -.emoji-fork_knife_plate { background-position: -540px -420px; } -.emoji-fountain { background-position: -540px -440px; } -.emoji-four { background-position: -540px -460px; } -.emoji-four_leaf_clover { background-position: -540px -480px; } -.emoji-fox { background-position: -540px -500px; } -.emoji-frame_photo { background-position: -540px -520px; } -.emoji-free { background-position: 0 -540px; } -.emoji-french_bread { background-position: -20px -540px; } -.emoji-fried_shrimp { background-position: -40px -540px; } -.emoji-fries { background-position: -60px -540px; } -.emoji-frog { background-position: -80px -540px; } -.emoji-frowning { background-position: -100px -540px; } -.emoji-frowning2 { background-position: -120px -540px; } -.emoji-fuelpump { background-position: -140px -540px; } -.emoji-full_moon { background-position: -160px -540px; } -.emoji-full_moon_with_face { background-position: -180px -540px; } -.emoji-game_die { background-position: -200px -540px; } -.emoji-gay_pride_flag { background-position: -220px -540px; } -.emoji-gear { background-position: -240px -540px; } -.emoji-gem { background-position: -260px -540px; } -.emoji-gemini { background-position: -280px -540px; } -.emoji-ghost { background-position: -300px -540px; } -.emoji-gift { background-position: -320px -540px; } -.emoji-gift_heart { background-position: -340px -540px; } -.emoji-girl { background-position: -360px -540px; } -.emoji-girl_tone1 { background-position: -380px -540px; } -.emoji-girl_tone2 { background-position: -400px -540px; } -.emoji-girl_tone3 { background-position: -420px -540px; } -.emoji-girl_tone4 { background-position: -440px -540px; } -.emoji-girl_tone5 { background-position: -460px -540px; } -.emoji-globe_with_meridians { background-position: -480px -540px; } -.emoji-goal { background-position: -500px -540px; } -.emoji-goat { background-position: -520px -540px; } -.emoji-golf { background-position: -540px -540px; } -.emoji-golfer { background-position: -560px 0; } -.emoji-gorilla { background-position: -560px -20px; } -.emoji-grapes { background-position: -560px -40px; } -.emoji-green_apple { background-position: -560px -60px; } -.emoji-green_book { background-position: -560px -80px; } -.emoji-green_heart { background-position: -560px -100px; } -.emoji-grey_exclamation { background-position: -560px -120px; } -.emoji-grey_question { background-position: -560px -140px; } -.emoji-grimacing { background-position: -560px -160px; } -.emoji-grin { background-position: -560px -180px; } -.emoji-grinning { background-position: -560px -200px; } -.emoji-guardsman { background-position: -560px -220px; } -.emoji-guardsman_tone1 { background-position: -560px -240px; } -.emoji-guardsman_tone2 { background-position: -560px -260px; } -.emoji-guardsman_tone3 { background-position: -560px -280px; } -.emoji-guardsman_tone4 { background-position: -560px -300px; } -.emoji-guardsman_tone5 { background-position: -560px -320px; } -.emoji-guitar { background-position: -560px -340px; } -.emoji-gun { background-position: -560px -360px; } -.emoji-haircut { background-position: -560px -380px; } -.emoji-haircut_tone1 { background-position: -560px -400px; } -.emoji-haircut_tone2 { background-position: -560px -420px; } -.emoji-haircut_tone3 { background-position: -560px -440px; } -.emoji-haircut_tone4 { background-position: -560px -460px; } -.emoji-haircut_tone5 { background-position: -560px -480px; } -.emoji-hamburger { background-position: -560px -500px; } -.emoji-hammer { background-position: -560px -520px; } -.emoji-hammer_pick { background-position: -560px -540px; } -.emoji-hamster { background-position: 0 -560px; } -.emoji-hand_splayed { background-position: -20px -560px; } -.emoji-hand_splayed_tone1 { background-position: -40px -560px; } -.emoji-hand_splayed_tone2 { background-position: -60px -560px; } -.emoji-hand_splayed_tone3 { background-position: -80px -560px; } -.emoji-hand_splayed_tone4 { background-position: -100px -560px; } -.emoji-hand_splayed_tone5 { background-position: -120px -560px; } -.emoji-handbag { background-position: -140px -560px; } -.emoji-handball { background-position: -160px -560px; } -.emoji-handball_tone1 { background-position: -180px -560px; } -.emoji-handball_tone2 { background-position: -200px -560px; } -.emoji-handball_tone3 { background-position: -220px -560px; } -.emoji-handball_tone4 { background-position: -240px -560px; } -.emoji-handball_tone5 { background-position: -260px -560px; } -.emoji-handshake { background-position: -280px -560px; } -.emoji-handshake_tone1 { background-position: -300px -560px; } -.emoji-handshake_tone2 { background-position: -320px -560px; } -.emoji-handshake_tone3 { background-position: -340px -560px; } -.emoji-handshake_tone4 { background-position: -360px -560px; } -.emoji-handshake_tone5 { background-position: -380px -560px; } -.emoji-hash { background-position: -400px -560px; } -.emoji-hatched_chick { background-position: -420px -560px; } -.emoji-hatching_chick { background-position: -440px -560px; } -.emoji-head_bandage { background-position: -460px -560px; } -.emoji-headphones { background-position: -480px -560px; } -.emoji-hear_no_evil { background-position: -500px -560px; } -.emoji-heart { background-position: -520px -560px; } -.emoji-heart_decoration { background-position: -540px -560px; } -.emoji-heart_exclamation { background-position: -560px -560px; } -.emoji-heart_eyes { background-position: -580px 0; } -.emoji-heart_eyes_cat { background-position: -580px -20px; } -.emoji-heartbeat { background-position: -580px -40px; } -.emoji-heartpulse { background-position: -580px -60px; } -.emoji-hearts { background-position: -580px -80px; } -.emoji-heavy_check_mark { background-position: -580px -100px; } -.emoji-heavy_division_sign { background-position: -580px -120px; } -.emoji-heavy_dollar_sign { background-position: -580px -140px; } -.emoji-heavy_minus_sign { background-position: -580px -160px; } -.emoji-heavy_multiplication_x { background-position: -580px -180px; } -.emoji-heavy_plus_sign { background-position: -580px -200px; } -.emoji-helicopter { background-position: -580px -220px; } -.emoji-helmet_with_cross { background-position: -580px -240px; } -.emoji-herb { background-position: -580px -260px; } -.emoji-hibiscus { background-position: -580px -280px; } -.emoji-high_brightness { background-position: -580px -300px; } -.emoji-high_heel { background-position: -580px -320px; } -.emoji-hockey { background-position: -580px -340px; } -.emoji-hole { background-position: -580px -360px; } -.emoji-homes { background-position: -580px -380px; } -.emoji-honey_pot { background-position: -580px -400px; } -.emoji-horse { background-position: -580px -420px; } -.emoji-horse_racing { background-position: -580px -440px; } -.emoji-horse_racing_tone1 { background-position: -580px -460px; } -.emoji-horse_racing_tone2 { background-position: -580px -480px; } -.emoji-horse_racing_tone3 { background-position: -580px -500px; } -.emoji-horse_racing_tone4 { background-position: -580px -520px; } -.emoji-horse_racing_tone5 { background-position: -580px -540px; } -.emoji-hospital { background-position: -580px -560px; } -.emoji-hot_pepper { background-position: 0 -580px; } -.emoji-hotdog { background-position: -20px -580px; } -.emoji-hotel { background-position: -40px -580px; } -.emoji-hotsprings { background-position: -60px -580px; } -.emoji-hourglass { background-position: -80px -580px; } -.emoji-hourglass_flowing_sand { background-position: -100px -580px; } -.emoji-house { background-position: -120px -580px; } -.emoji-house_abandoned { background-position: -140px -580px; } -.emoji-house_with_garden { background-position: -160px -580px; } -.emoji-hugging { background-position: -180px -580px; } -.emoji-hushed { background-position: -200px -580px; } -.emoji-ice_cream { background-position: -220px -580px; } -.emoji-ice_skate { background-position: -240px -580px; } -.emoji-icecream { background-position: -260px -580px; } -.emoji-id { background-position: -280px -580px; } -.emoji-ideograph_advantage { background-position: -300px -580px; } -.emoji-imp { background-position: -320px -580px; } -.emoji-inbox_tray { background-position: -340px -580px; } -.emoji-incoming_envelope { background-position: -360px -580px; } -.emoji-information_desk_person { background-position: -380px -580px; } -.emoji-information_desk_person_tone1 { background-position: -400px -580px; } -.emoji-information_desk_person_tone2 { background-position: -420px -580px; } -.emoji-information_desk_person_tone3 { background-position: -440px -580px; } -.emoji-information_desk_person_tone4 { background-position: -460px -580px; } -.emoji-information_desk_person_tone5 { background-position: -480px -580px; } -.emoji-information_source { background-position: -500px -580px; } -.emoji-innocent { background-position: -520px -580px; } -.emoji-interrobang { background-position: -540px -580px; } -.emoji-iphone { background-position: -560px -580px; } -.emoji-island { background-position: -580px -580px; } -.emoji-izakaya_lantern { background-position: -600px 0; } -.emoji-jack_o_lantern { background-position: -600px -20px; } -.emoji-japan { background-position: -600px -40px; } -.emoji-japanese_castle { background-position: -600px -60px; } -.emoji-japanese_goblin { background-position: -600px -80px; } -.emoji-japanese_ogre { background-position: -600px -100px; } -.emoji-jeans { background-position: -600px -120px; } -.emoji-joy { background-position: -600px -140px; } -.emoji-joy_cat { background-position: -600px -160px; } -.emoji-joystick { background-position: -600px -180px; } -.emoji-juggling { background-position: -600px -200px; } -.emoji-juggling_tone1 { background-position: -600px -220px; } -.emoji-juggling_tone2 { background-position: -600px -240px; } -.emoji-juggling_tone3 { background-position: -600px -260px; } -.emoji-juggling_tone4 { background-position: -600px -280px; } -.emoji-juggling_tone5 { background-position: -600px -300px; } -.emoji-kaaba { background-position: -600px -320px; } -.emoji-key { background-position: -600px -340px; } -.emoji-key2 { background-position: -600px -360px; } -.emoji-keyboard { background-position: -600px -380px; } -.emoji-kimono { background-position: -600px -400px; } -.emoji-kiss { background-position: -600px -420px; } -.emoji-kiss_mm { background-position: -600px -440px; } -.emoji-kiss_ww { background-position: -600px -460px; } -.emoji-kissing { background-position: -600px -480px; } -.emoji-kissing_cat { background-position: -600px -500px; } -.emoji-kissing_closed_eyes { background-position: -600px -520px; } -.emoji-kissing_heart { background-position: -600px -540px; } -.emoji-kissing_smiling_eyes { background-position: -600px -560px; } -.emoji-kiwi { background-position: -600px -580px; } -.emoji-knife { background-position: 0 -600px; } -.emoji-koala { background-position: -20px -600px; } -.emoji-koko { background-position: -40px -600px; } -.emoji-label { background-position: -60px -600px; } -.emoji-large_blue_circle { background-position: -80px -600px; } -.emoji-large_blue_diamond { background-position: -100px -600px; } -.emoji-large_orange_diamond { background-position: -120px -600px; } -.emoji-last_quarter_moon { background-position: -140px -600px; } -.emoji-last_quarter_moon_with_face { background-position: -160px -600px; } -.emoji-laughing { background-position: -180px -600px; } -.emoji-leaves { background-position: -200px -600px; } -.emoji-ledger { background-position: -220px -600px; } -.emoji-left_facing_fist { background-position: -240px -600px; } -.emoji-left_facing_fist_tone1 { background-position: -260px -600px; } -.emoji-left_facing_fist_tone2 { background-position: -280px -600px; } -.emoji-left_facing_fist_tone3 { background-position: -300px -600px; } -.emoji-left_facing_fist_tone4 { background-position: -320px -600px; } -.emoji-left_facing_fist_tone5 { background-position: -340px -600px; } -.emoji-left_luggage { background-position: -360px -600px; } -.emoji-left_right_arrow { background-position: -380px -600px; } -.emoji-leftwards_arrow_with_hook { background-position: -400px -600px; } -.emoji-lemon { background-position: -420px -600px; } -.emoji-leo { background-position: -440px -600px; } -.emoji-leopard { background-position: -460px -600px; } -.emoji-level_slider { background-position: -480px -600px; } -.emoji-levitate { background-position: -500px -600px; } -.emoji-libra { background-position: -520px -600px; } -.emoji-lifter { background-position: -540px -600px; } -.emoji-lifter_tone1 { background-position: -560px -600px; } -.emoji-lifter_tone2 { background-position: -580px -600px; } -.emoji-lifter_tone3 { background-position: -600px -600px; } -.emoji-lifter_tone4 { background-position: -620px 0; } -.emoji-lifter_tone5 { background-position: -620px -20px; } -.emoji-light_rail { background-position: -620px -40px; } -.emoji-link { background-position: -620px -60px; } -.emoji-lion_face { background-position: -620px -80px; } -.emoji-lips { background-position: -620px -100px; } -.emoji-lipstick { background-position: -620px -120px; } -.emoji-lizard { background-position: -620px -140px; } -.emoji-lock { background-position: -620px -160px; } -.emoji-lock_with_ink_pen { background-position: -620px -180px; } -.emoji-lollipop { background-position: -620px -200px; } -.emoji-loop { background-position: -620px -220px; } -.emoji-loud_sound { background-position: -620px -240px; } -.emoji-loudspeaker { background-position: -620px -260px; } -.emoji-love_hotel { background-position: -620px -280px; } -.emoji-love_letter { background-position: -620px -300px; } -.emoji-low_brightness { background-position: -620px -320px; } -.emoji-lying_face { background-position: -620px -340px; } -.emoji-m { background-position: -620px -360px; } -.emoji-mag { background-position: -620px -380px; } -.emoji-mag_right { background-position: -620px -400px; } -.emoji-mahjong { background-position: -620px -420px; } -.emoji-mailbox { background-position: -620px -440px; } -.emoji-mailbox_closed { background-position: -620px -460px; } -.emoji-mailbox_with_mail { background-position: -620px -480px; } -.emoji-mailbox_with_no_mail { background-position: -620px -500px; } -.emoji-man { background-position: -620px -520px; } -.emoji-man_dancing { background-position: -620px -540px; } -.emoji-man_dancing_tone1 { background-position: -620px -560px; } -.emoji-man_dancing_tone2 { background-position: -620px -580px; } -.emoji-man_dancing_tone3 { background-position: -620px -600px; } -.emoji-man_dancing_tone4 { background-position: 0 -620px; } -.emoji-man_dancing_tone5 { background-position: -20px -620px; } -.emoji-man_in_tuxedo { background-position: -40px -620px; } -.emoji-man_in_tuxedo_tone1 { background-position: -60px -620px; } -.emoji-man_in_tuxedo_tone2 { background-position: -80px -620px; } -.emoji-man_in_tuxedo_tone3 { background-position: -100px -620px; } -.emoji-man_in_tuxedo_tone4 { background-position: -120px -620px; } -.emoji-man_in_tuxedo_tone5 { background-position: -140px -620px; } -.emoji-man_tone1 { background-position: -160px -620px; } -.emoji-man_tone2 { background-position: -180px -620px; } -.emoji-man_tone3 { background-position: -200px -620px; } -.emoji-man_tone4 { background-position: -220px -620px; } -.emoji-man_tone5 { background-position: -240px -620px; } -.emoji-man_with_gua_pi_mao { background-position: -260px -620px; } -.emoji-man_with_gua_pi_mao_tone1 { background-position: -280px -620px; } -.emoji-man_with_gua_pi_mao_tone2 { background-position: -300px -620px; } -.emoji-man_with_gua_pi_mao_tone3 { background-position: -320px -620px; } -.emoji-man_with_gua_pi_mao_tone4 { background-position: -340px -620px; } -.emoji-man_with_gua_pi_mao_tone5 { background-position: -360px -620px; } -.emoji-man_with_turban { background-position: -380px -620px; } -.emoji-man_with_turban_tone1 { background-position: -400px -620px; } -.emoji-man_with_turban_tone2 { background-position: -420px -620px; } -.emoji-man_with_turban_tone3 { background-position: -440px -620px; } -.emoji-man_with_turban_tone4 { background-position: -460px -620px; } -.emoji-man_with_turban_tone5 { background-position: -480px -620px; } -.emoji-mans_shoe { background-position: -500px -620px; } -.emoji-map { background-position: -520px -620px; } -.emoji-maple_leaf { background-position: -540px -620px; } -.emoji-martial_arts_uniform { background-position: -560px -620px; } -.emoji-mask { background-position: -580px -620px; } -.emoji-massage { background-position: -600px -620px; } -.emoji-massage_tone1 { background-position: -620px -620px; } -.emoji-massage_tone2 { background-position: -640px 0; } -.emoji-massage_tone3 { background-position: -640px -20px; } -.emoji-massage_tone4 { background-position: -640px -40px; } -.emoji-massage_tone5 { background-position: -640px -60px; } -.emoji-meat_on_bone { background-position: -640px -80px; } -.emoji-medal { background-position: -640px -100px; } -.emoji-mega { background-position: -640px -120px; } -.emoji-melon { background-position: -640px -140px; } -.emoji-menorah { background-position: -640px -160px; } -.emoji-mens { background-position: -640px -180px; } -.emoji-metal { background-position: -640px -200px; } -.emoji-metal_tone1 { background-position: -640px -220px; } -.emoji-metal_tone2 { background-position: -640px -240px; } -.emoji-metal_tone3 { background-position: -640px -260px; } -.emoji-metal_tone4 { background-position: -640px -280px; } -.emoji-metal_tone5 { background-position: -640px -300px; } -.emoji-metro { background-position: -640px -320px; } -.emoji-microphone { background-position: -640px -340px; } -.emoji-microphone2 { background-position: -640px -360px; } -.emoji-microscope { background-position: -640px -380px; } -.emoji-middle_finger { background-position: -640px -400px; } -.emoji-middle_finger_tone1 { background-position: -640px -420px; } -.emoji-middle_finger_tone2 { background-position: -640px -440px; } -.emoji-middle_finger_tone3 { background-position: -640px -460px; } -.emoji-middle_finger_tone4 { background-position: -640px -480px; } -.emoji-middle_finger_tone5 { background-position: -640px -500px; } -.emoji-military_medal { background-position: -640px -520px; } -.emoji-milk { background-position: -640px -540px; } -.emoji-milky_way { background-position: -640px -560px; } -.emoji-minibus { background-position: -640px -580px; } -.emoji-minidisc { background-position: -640px -600px; } -.emoji-mobile_phone_off { background-position: -640px -620px; } -.emoji-money_mouth { background-position: 0 -640px; } -.emoji-money_with_wings { background-position: -20px -640px; } -.emoji-moneybag { background-position: -40px -640px; } -.emoji-monkey { background-position: -60px -640px; } -.emoji-monkey_face { background-position: -80px -640px; } -.emoji-monorail { background-position: -100px -640px; } -.emoji-mortar_board { background-position: -120px -640px; } -.emoji-mosque { background-position: -140px -640px; } -.emoji-motor_scooter { background-position: -160px -640px; } -.emoji-motorboat { background-position: -180px -640px; } -.emoji-motorcycle { background-position: -200px -640px; } -.emoji-motorway { background-position: -220px -640px; } -.emoji-mount_fuji { background-position: -240px -640px; } -.emoji-mountain { background-position: -260px -640px; } -.emoji-mountain_bicyclist { background-position: -280px -640px; } -.emoji-mountain_bicyclist_tone1 { background-position: -300px -640px; } -.emoji-mountain_bicyclist_tone2 { background-position: -320px -640px; } -.emoji-mountain_bicyclist_tone3 { background-position: -340px -640px; } -.emoji-mountain_bicyclist_tone4 { background-position: -360px -640px; } -.emoji-mountain_bicyclist_tone5 { background-position: -380px -640px; } -.emoji-mountain_cableway { background-position: -400px -640px; } -.emoji-mountain_railway { background-position: -420px -640px; } -.emoji-mountain_snow { background-position: -440px -640px; } -.emoji-mouse { background-position: -460px -640px; } -.emoji-mouse2 { background-position: -480px -640px; } -.emoji-mouse_three_button { background-position: -500px -640px; } -.emoji-movie_camera { background-position: -520px -640px; } -.emoji-moyai { background-position: -540px -640px; } -.emoji-mrs_claus { background-position: -560px -640px; } -.emoji-mrs_claus_tone1 { background-position: -580px -640px; } -.emoji-mrs_claus_tone2 { background-position: -600px -640px; } -.emoji-mrs_claus_tone3 { background-position: -620px -640px; } -.emoji-mrs_claus_tone4 { background-position: -640px -640px; } -.emoji-mrs_claus_tone5 { background-position: -660px 0; } -.emoji-muscle { background-position: -660px -20px; } -.emoji-muscle_tone1 { background-position: -660px -40px; } -.emoji-muscle_tone2 { background-position: -660px -60px; } -.emoji-muscle_tone3 { background-position: -660px -80px; } -.emoji-muscle_tone4 { background-position: -660px -100px; } -.emoji-muscle_tone5 { background-position: -660px -120px; } -.emoji-mushroom { background-position: -660px -140px; } -.emoji-musical_keyboard { background-position: -660px -160px; } -.emoji-musical_note { background-position: -660px -180px; } -.emoji-musical_score { background-position: -660px -200px; } -.emoji-mute { background-position: -660px -220px; } -.emoji-nail_care { background-position: -660px -240px; } -.emoji-nail_care_tone1 { background-position: -660px -260px; } -.emoji-nail_care_tone2 { background-position: -660px -280px; } -.emoji-nail_care_tone3 { background-position: -660px -300px; } -.emoji-nail_care_tone4 { background-position: -660px -320px; } -.emoji-nail_care_tone5 { background-position: -660px -340px; } -.emoji-name_badge { background-position: -660px -360px; } -.emoji-nauseated_face { background-position: -660px -380px; } -.emoji-necktie { background-position: -660px -400px; } -.emoji-negative_squared_cross_mark { background-position: -660px -420px; } -.emoji-nerd { background-position: -660px -440px; } -.emoji-neutral_face { background-position: -660px -460px; } -.emoji-new { background-position: -660px -480px; } -.emoji-new_moon { background-position: -660px -500px; } -.emoji-new_moon_with_face { background-position: -660px -520px; } -.emoji-newspaper { background-position: -660px -540px; } -.emoji-newspaper2 { background-position: -660px -560px; } -.emoji-ng { background-position: -660px -580px; } -.emoji-night_with_stars { background-position: -660px -600px; } -.emoji-nine { background-position: -660px -620px; } -.emoji-no_bell { background-position: -660px -640px; } -.emoji-no_bicycles { background-position: 0 -660px; } -.emoji-no_entry { background-position: -20px -660px; } -.emoji-no_entry_sign { background-position: -40px -660px; } -.emoji-no_good { background-position: -60px -660px; } -.emoji-no_good_tone1 { background-position: -80px -660px; } -.emoji-no_good_tone2 { background-position: -100px -660px; } -.emoji-no_good_tone3 { background-position: -120px -660px; } -.emoji-no_good_tone4 { background-position: -140px -660px; } -.emoji-no_good_tone5 { background-position: -160px -660px; } -.emoji-no_mobile_phones { background-position: -180px -660px; } -.emoji-no_mouth { background-position: -200px -660px; } -.emoji-no_pedestrians { background-position: -220px -660px; } -.emoji-no_smoking { background-position: -240px -660px; } -.emoji-non-potable_water { background-position: -260px -660px; } -.emoji-nose { background-position: -280px -660px; } -.emoji-nose_tone1 { background-position: -300px -660px; } -.emoji-nose_tone2 { background-position: -320px -660px; } -.emoji-nose_tone3 { background-position: -340px -660px; } -.emoji-nose_tone4 { background-position: -360px -660px; } -.emoji-nose_tone5 { background-position: -380px -660px; } -.emoji-notebook { background-position: -400px -660px; } -.emoji-notebook_with_decorative_cover { background-position: -420px -660px; } -.emoji-notepad_spiral { background-position: -440px -660px; } -.emoji-notes { background-position: -460px -660px; } -.emoji-nut_and_bolt { background-position: -480px -660px; } -.emoji-o { background-position: -500px -660px; } -.emoji-o2 { background-position: -520px -660px; } -.emoji-ocean { background-position: -540px -660px; } -.emoji-octagonal_sign { background-position: -560px -660px; } -.emoji-octopus { background-position: -580px -660px; } -.emoji-oden { background-position: -600px -660px; } -.emoji-office { background-position: -620px -660px; } -.emoji-oil { background-position: -640px -660px; } -.emoji-ok { background-position: -660px -660px; } -.emoji-ok_hand { background-position: -680px 0; } -.emoji-ok_hand_tone1 { background-position: -680px -20px; } -.emoji-ok_hand_tone2 { background-position: -680px -40px; } -.emoji-ok_hand_tone3 { background-position: -680px -60px; } -.emoji-ok_hand_tone4 { background-position: -680px -80px; } -.emoji-ok_hand_tone5 { background-position: -680px -100px; } -.emoji-ok_woman { background-position: -680px -120px; } -.emoji-ok_woman_tone1 { background-position: -680px -140px; } -.emoji-ok_woman_tone2 { background-position: -680px -160px; } -.emoji-ok_woman_tone3 { background-position: -680px -180px; } -.emoji-ok_woman_tone4 { background-position: -680px -200px; } -.emoji-ok_woman_tone5 { background-position: -680px -220px; } -.emoji-older_man { background-position: -680px -240px; } -.emoji-older_man_tone1 { background-position: -680px -260px; } -.emoji-older_man_tone2 { background-position: -680px -280px; } -.emoji-older_man_tone3 { background-position: -680px -300px; } -.emoji-older_man_tone4 { background-position: -680px -320px; } -.emoji-older_man_tone5 { background-position: -680px -340px; } -.emoji-older_woman { background-position: -680px -360px; } -.emoji-older_woman_tone1 { background-position: -680px -380px; } -.emoji-older_woman_tone2 { background-position: -680px -400px; } -.emoji-older_woman_tone3 { background-position: -680px -420px; } -.emoji-older_woman_tone4 { background-position: -680px -440px; } -.emoji-older_woman_tone5 { background-position: -680px -460px; } -.emoji-om_symbol { background-position: -680px -480px; } -.emoji-on { background-position: -680px -500px; } -.emoji-oncoming_automobile { background-position: -680px -520px; } -.emoji-oncoming_bus { background-position: -680px -540px; } -.emoji-oncoming_police_car { background-position: -680px -560px; } -.emoji-oncoming_taxi { background-position: -680px -580px; } -.emoji-one { background-position: -680px -600px; } -.emoji-open_file_folder { background-position: -680px -620px; } -.emoji-open_hands { background-position: -680px -640px; } -.emoji-open_hands_tone1 { background-position: -680px -660px; } -.emoji-open_hands_tone2 { background-position: 0 -680px; } -.emoji-open_hands_tone3 { background-position: -20px -680px; } -.emoji-open_hands_tone4 { background-position: -40px -680px; } -.emoji-open_hands_tone5 { background-position: -60px -680px; } -.emoji-open_mouth { background-position: -80px -680px; } -.emoji-ophiuchus { background-position: -100px -680px; } -.emoji-orange_book { background-position: -120px -680px; } -.emoji-orthodox_cross { background-position: -140px -680px; } -.emoji-outbox_tray { background-position: -160px -680px; } -.emoji-owl { background-position: -180px -680px; } -.emoji-ox { background-position: -200px -680px; } -.emoji-package { background-position: -220px -680px; } -.emoji-page_facing_up { background-position: -240px -680px; } -.emoji-page_with_curl { background-position: -260px -680px; } -.emoji-pager { background-position: -280px -680px; } -.emoji-paintbrush { background-position: -300px -680px; } -.emoji-palm_tree { background-position: -320px -680px; } -.emoji-pancakes { background-position: -340px -680px; } -.emoji-panda_face { background-position: -360px -680px; } -.emoji-paperclip { background-position: -380px -680px; } -.emoji-paperclips { background-position: -400px -680px; } -.emoji-park { background-position: -420px -680px; } -.emoji-parking { background-position: -440px -680px; } -.emoji-part_alternation_mark { background-position: -460px -680px; } -.emoji-partly_sunny { background-position: -480px -680px; } -.emoji-passport_control { background-position: -500px -680px; } -.emoji-pause_button { background-position: -520px -680px; } -.emoji-peace { background-position: -540px -680px; } -.emoji-peach { background-position: -560px -680px; } -.emoji-peanuts { background-position: -580px -680px; } -.emoji-pear { background-position: -600px -680px; } -.emoji-pen_ballpoint { background-position: -620px -680px; } -.emoji-pen_fountain { background-position: -640px -680px; } -.emoji-pencil { background-position: -660px -680px; } -.emoji-pencil2 { background-position: -680px -680px; } -.emoji-penguin { background-position: -700px 0; } -.emoji-pensive { background-position: -700px -20px; } -.emoji-performing_arts { background-position: -700px -40px; } -.emoji-persevere { background-position: -700px -60px; } -.emoji-person_frowning { background-position: -700px -80px; } -.emoji-person_frowning_tone1 { background-position: -700px -100px; } -.emoji-person_frowning_tone2 { background-position: -700px -120px; } -.emoji-person_frowning_tone3 { background-position: -700px -140px; } -.emoji-person_frowning_tone4 { background-position: -700px -160px; } -.emoji-person_frowning_tone5 { background-position: -700px -180px; } -.emoji-person_with_blond_hair { background-position: -700px -200px; } -.emoji-person_with_blond_hair_tone1 { background-position: -700px -220px; } -.emoji-person_with_blond_hair_tone2 { background-position: -700px -240px; } -.emoji-person_with_blond_hair_tone3 { background-position: -700px -260px; } -.emoji-person_with_blond_hair_tone4 { background-position: -700px -280px; } -.emoji-person_with_blond_hair_tone5 { background-position: -700px -300px; } -.emoji-person_with_pouting_face { background-position: -700px -320px; } -.emoji-person_with_pouting_face_tone1 { background-position: -700px -340px; } -.emoji-person_with_pouting_face_tone2 { background-position: -700px -360px; } -.emoji-person_with_pouting_face_tone3 { background-position: -700px -380px; } -.emoji-person_with_pouting_face_tone4 { background-position: -700px -400px; } -.emoji-person_with_pouting_face_tone5 { background-position: -700px -420px; } -.emoji-pick { background-position: -700px -440px; } -.emoji-pig { background-position: -700px -460px; } -.emoji-pig2 { background-position: -700px -480px; } -.emoji-pig_nose { background-position: -700px -500px; } -.emoji-pill { background-position: -700px -520px; } -.emoji-pineapple { background-position: -700px -540px; } -.emoji-ping_pong { background-position: -700px -560px; } -.emoji-pisces { background-position: -700px -580px; } -.emoji-pizza { background-position: -700px -600px; } -.emoji-place_of_worship { background-position: -700px -620px; } -.emoji-play_pause { background-position: -700px -640px; } -.emoji-point_down { background-position: -700px -660px; } -.emoji-point_down_tone1 { background-position: -700px -680px; } -.emoji-point_down_tone2 { background-position: 0 -700px; } -.emoji-point_down_tone3 { background-position: -20px -700px; } -.emoji-point_down_tone4 { background-position: -40px -700px; } -.emoji-point_down_tone5 { background-position: -60px -700px; } -.emoji-point_left { background-position: -80px -700px; } -.emoji-point_left_tone1 { background-position: -100px -700px; } -.emoji-point_left_tone2 { background-position: -120px -700px; } -.emoji-point_left_tone3 { background-position: -140px -700px; } -.emoji-point_left_tone4 { background-position: -160px -700px; } -.emoji-point_left_tone5 { background-position: -180px -700px; } -.emoji-point_right { background-position: -200px -700px; } -.emoji-point_right_tone1 { background-position: -220px -700px; } -.emoji-point_right_tone2 { background-position: -240px -700px; } -.emoji-point_right_tone3 { background-position: -260px -700px; } -.emoji-point_right_tone4 { background-position: -280px -700px; } -.emoji-point_right_tone5 { background-position: -300px -700px; } -.emoji-point_up { background-position: -320px -700px; } -.emoji-point_up_2 { background-position: -340px -700px; } -.emoji-point_up_2_tone1 { background-position: -360px -700px; } -.emoji-point_up_2_tone2 { background-position: -380px -700px; } -.emoji-point_up_2_tone3 { background-position: -400px -700px; } -.emoji-point_up_2_tone4 { background-position: -420px -700px; } -.emoji-point_up_2_tone5 { background-position: -440px -700px; } -.emoji-point_up_tone1 { background-position: -460px -700px; } -.emoji-point_up_tone2 { background-position: -480px -700px; } -.emoji-point_up_tone3 { background-position: -500px -700px; } -.emoji-point_up_tone4 { background-position: -520px -700px; } -.emoji-point_up_tone5 { background-position: -540px -700px; } -.emoji-police_car { background-position: -560px -700px; } -.emoji-poodle { background-position: -580px -700px; } -.emoji-poop { background-position: -600px -700px; } -.emoji-popcorn { background-position: -620px -700px; } -.emoji-post_office { background-position: -640px -700px; } -.emoji-postal_horn { background-position: -660px -700px; } -.emoji-postbox { background-position: -680px -700px; } -.emoji-potable_water { background-position: -700px -700px; } -.emoji-potato { background-position: -720px 0; } -.emoji-pouch { background-position: -720px -20px; } -.emoji-poultry_leg { background-position: -720px -40px; } -.emoji-pound { background-position: -720px -60px; } -.emoji-pouting_cat { background-position: -720px -80px; } -.emoji-pray { background-position: -720px -100px; } -.emoji-pray_tone1 { background-position: -720px -120px; } -.emoji-pray_tone2 { background-position: -720px -140px; } -.emoji-pray_tone3 { background-position: -720px -160px; } -.emoji-pray_tone4 { background-position: -720px -180px; } -.emoji-pray_tone5 { background-position: -720px -200px; } -.emoji-prayer_beads { background-position: -720px -220px; } -.emoji-pregnant_woman { background-position: -720px -240px; } -.emoji-pregnant_woman_tone1 { background-position: -720px -260px; } -.emoji-pregnant_woman_tone2 { background-position: -720px -280px; } -.emoji-pregnant_woman_tone3 { background-position: -720px -300px; } -.emoji-pregnant_woman_tone4 { background-position: -720px -320px; } -.emoji-pregnant_woman_tone5 { background-position: -720px -340px; } -.emoji-prince { background-position: -720px -360px; } -.emoji-prince_tone1 { background-position: -720px -380px; } -.emoji-prince_tone2 { background-position: -720px -400px; } -.emoji-prince_tone3 { background-position: -720px -420px; } -.emoji-prince_tone4 { background-position: -720px -440px; } -.emoji-prince_tone5 { background-position: -720px -460px; } -.emoji-princess { background-position: -720px -480px; } -.emoji-princess_tone1 { background-position: -720px -500px; } -.emoji-princess_tone2 { background-position: -720px -520px; } -.emoji-princess_tone3 { background-position: -720px -540px; } -.emoji-princess_tone4 { background-position: -720px -560px; } -.emoji-princess_tone5 { background-position: -720px -580px; } -.emoji-printer { background-position: -720px -600px; } -.emoji-projector { background-position: -720px -620px; } -.emoji-punch { background-position: -720px -640px; } -.emoji-punch_tone1 { background-position: -720px -660px; } -.emoji-punch_tone2 { background-position: -720px -680px; } -.emoji-punch_tone3 { background-position: -720px -700px; } -.emoji-punch_tone4 { background-position: 0 -720px; } -.emoji-punch_tone5 { background-position: -20px -720px; } -.emoji-purple_heart { background-position: -40px -720px; } -.emoji-purse { background-position: -60px -720px; } -.emoji-pushpin { background-position: -80px -720px; } -.emoji-put_litter_in_its_place { background-position: -100px -720px; } -.emoji-question { background-position: -120px -720px; } -.emoji-rabbit { background-position: -140px -720px; } -.emoji-rabbit2 { background-position: -160px -720px; } -.emoji-race_car { background-position: -180px -720px; } -.emoji-racehorse { background-position: -200px -720px; } -.emoji-radio { background-position: -220px -720px; } -.emoji-radio_button { background-position: -240px -720px; } -.emoji-radioactive { background-position: -260px -720px; } -.emoji-rage { background-position: -280px -720px; } -.emoji-railway_car { background-position: -300px -720px; } -.emoji-railway_track { background-position: -320px -720px; } -.emoji-rainbow { background-position: -340px -720px; } -.emoji-raised_back_of_hand { background-position: -360px -720px; } -.emoji-raised_back_of_hand_tone1 { background-position: -380px -720px; } -.emoji-raised_back_of_hand_tone2 { background-position: -400px -720px; } -.emoji-raised_back_of_hand_tone3 { background-position: -420px -720px; } -.emoji-raised_back_of_hand_tone4 { background-position: -440px -720px; } -.emoji-raised_back_of_hand_tone5 { background-position: -460px -720px; } -.emoji-raised_hand { background-position: -480px -720px; } -.emoji-raised_hand_tone1 { background-position: -500px -720px; } -.emoji-raised_hand_tone2 { background-position: -520px -720px; } -.emoji-raised_hand_tone3 { background-position: -540px -720px; } -.emoji-raised_hand_tone4 { background-position: -560px -720px; } -.emoji-raised_hand_tone5 { background-position: -580px -720px; } -.emoji-raised_hands { background-position: -600px -720px; } -.emoji-raised_hands_tone1 { background-position: -620px -720px; } -.emoji-raised_hands_tone2 { background-position: -640px -720px; } -.emoji-raised_hands_tone3 { background-position: -660px -720px; } -.emoji-raised_hands_tone4 { background-position: -680px -720px; } -.emoji-raised_hands_tone5 { background-position: -700px -720px; } -.emoji-raising_hand { background-position: -720px -720px; } -.emoji-raising_hand_tone1 { background-position: -740px 0; } -.emoji-raising_hand_tone2 { background-position: -740px -20px; } -.emoji-raising_hand_tone3 { background-position: -740px -40px; } -.emoji-raising_hand_tone4 { background-position: -740px -60px; } -.emoji-raising_hand_tone5 { background-position: -740px -80px; } -.emoji-ram { background-position: -740px -100px; } -.emoji-ramen { background-position: -740px -120px; } -.emoji-rat { background-position: -740px -140px; } -.emoji-record_button { background-position: -740px -160px; } -.emoji-recycle { background-position: -740px -180px; } -.emoji-red_car { background-position: -740px -200px; } -.emoji-red_circle { background-position: -740px -220px; } -.emoji-registered { background-position: -740px -240px; } -.emoji-relaxed { background-position: -740px -260px; } -.emoji-relieved { background-position: -740px -280px; } -.emoji-reminder_ribbon { background-position: -740px -300px; } -.emoji-repeat { background-position: -740px -320px; } -.emoji-repeat_one { background-position: -740px -340px; } -.emoji-restroom { background-position: -740px -360px; } -.emoji-revolving_hearts { background-position: -740px -380px; } -.emoji-rewind { background-position: -740px -400px; } -.emoji-rhino { background-position: -740px -420px; } -.emoji-ribbon { background-position: -740px -440px; } -.emoji-rice { background-position: -740px -460px; } -.emoji-rice_ball { background-position: -740px -480px; } -.emoji-rice_cracker { background-position: -740px -500px; } -.emoji-rice_scene { background-position: -740px -520px; } -.emoji-right_facing_fist { background-position: -740px -540px; } -.emoji-right_facing_fist_tone1 { background-position: -740px -560px; } -.emoji-right_facing_fist_tone2 { background-position: -740px -580px; } -.emoji-right_facing_fist_tone3 { background-position: -740px -600px; } -.emoji-right_facing_fist_tone4 { background-position: -740px -620px; } -.emoji-right_facing_fist_tone5 { background-position: -740px -640px; } -.emoji-ring { background-position: -740px -660px; } -.emoji-robot { background-position: -740px -680px; } -.emoji-rocket { background-position: -740px -700px; } -.emoji-rofl { background-position: -740px -720px; } -.emoji-roller_coaster { background-position: 0 -740px; } -.emoji-rolling_eyes { background-position: -20px -740px; } -.emoji-rooster { background-position: -40px -740px; } -.emoji-rose { background-position: -60px -740px; } -.emoji-rosette { background-position: -80px -740px; } -.emoji-rotating_light { background-position: -100px -740px; } -.emoji-round_pushpin { background-position: -120px -740px; } -.emoji-rowboat { background-position: -140px -740px; } -.emoji-rowboat_tone1 { background-position: -160px -740px; } -.emoji-rowboat_tone2 { background-position: -180px -740px; } -.emoji-rowboat_tone3 { background-position: -200px -740px; } -.emoji-rowboat_tone4 { background-position: -220px -740px; } -.emoji-rowboat_tone5 { background-position: -240px -740px; } -.emoji-rugby_football { background-position: -260px -740px; } -.emoji-runner { background-position: -280px -740px; } -.emoji-runner_tone1 { background-position: -300px -740px; } -.emoji-runner_tone2 { background-position: -320px -740px; } -.emoji-runner_tone3 { background-position: -340px -740px; } -.emoji-runner_tone4 { background-position: -360px -740px; } -.emoji-runner_tone5 { background-position: -380px -740px; } -.emoji-running_shirt_with_sash { background-position: -400px -740px; } -.emoji-sa { background-position: -420px -740px; } -.emoji-sagittarius { background-position: -440px -740px; } -.emoji-sailboat { background-position: -460px -740px; } -.emoji-sake { background-position: -480px -740px; } -.emoji-salad { background-position: -500px -740px; } -.emoji-sandal { background-position: -520px -740px; } -.emoji-santa { background-position: -540px -740px; } -.emoji-santa_tone1 { background-position: -560px -740px; } -.emoji-santa_tone2 { background-position: -580px -740px; } -.emoji-santa_tone3 { background-position: -600px -740px; } -.emoji-santa_tone4 { background-position: -620px -740px; } -.emoji-santa_tone5 { background-position: -640px -740px; } -.emoji-satellite { background-position: -660px -740px; } -.emoji-satellite_orbital { background-position: -680px -740px; } -.emoji-saxophone { background-position: -700px -740px; } -.emoji-scales { background-position: -720px -740px; } -.emoji-school { background-position: -740px -740px; } -.emoji-school_satchel { background-position: -760px 0; } -.emoji-scissors { background-position: -760px -20px; } -.emoji-scooter { background-position: -760px -40px; } -.emoji-scorpion { background-position: -760px -60px; } -.emoji-scorpius { background-position: -760px -80px; } -.emoji-scream { background-position: -760px -100px; } -.emoji-scream_cat { background-position: -760px -120px; } -.emoji-scroll { background-position: -760px -140px; } -.emoji-seat { background-position: -760px -160px; } -.emoji-second_place { background-position: -760px -180px; } -.emoji-secret { background-position: -760px -200px; } -.emoji-see_no_evil { background-position: -760px -220px; } -.emoji-seedling { background-position: -760px -240px; } -.emoji-selfie { background-position: -760px -260px; } -.emoji-selfie_tone1 { background-position: -760px -280px; } -.emoji-selfie_tone2 { background-position: -760px -300px; } -.emoji-selfie_tone3 { background-position: -760px -320px; } -.emoji-selfie_tone4 { background-position: -760px -340px; } -.emoji-selfie_tone5 { background-position: -760px -360px; } -.emoji-seven { background-position: -760px -380px; } -.emoji-shallow_pan_of_food { background-position: -760px -400px; } -.emoji-shamrock { background-position: -760px -420px; } -.emoji-shark { background-position: -760px -440px; } -.emoji-shaved_ice { background-position: -760px -460px; } -.emoji-sheep { background-position: -760px -480px; } -.emoji-shell { background-position: -760px -500px; } -.emoji-shield { background-position: -760px -520px; } -.emoji-shinto_shrine { background-position: -760px -540px; } -.emoji-ship { background-position: -760px -560px; } -.emoji-shirt { background-position: -760px -580px; } -.emoji-shopping_bags { background-position: -760px -600px; } -.emoji-shopping_cart { background-position: -760px -620px; } -.emoji-shower { background-position: -760px -640px; } -.emoji-shrimp { background-position: -760px -660px; } -.emoji-shrug { background-position: -760px -680px; } -.emoji-shrug_tone1 { background-position: -760px -700px; } -.emoji-shrug_tone2 { background-position: -760px -720px; } -.emoji-shrug_tone3 { background-position: -760px -740px; } -.emoji-shrug_tone4 { background-position: 0 -760px; } -.emoji-shrug_tone5 { background-position: -20px -760px; } -.emoji-signal_strength { background-position: -40px -760px; } -.emoji-six { background-position: -60px -760px; } -.emoji-six_pointed_star { background-position: -80px -760px; } -.emoji-ski { background-position: -100px -760px; } -.emoji-skier { background-position: -120px -760px; } -.emoji-skull { background-position: -140px -760px; } -.emoji-skull_crossbones { background-position: -160px -760px; } -.emoji-sleeping { background-position: -180px -760px; } -.emoji-sleeping_accommodation { background-position: -200px -760px; } -.emoji-sleepy { background-position: -220px -760px; } -.emoji-slight_frown { background-position: -240px -760px; } -.emoji-slight_smile { background-position: -260px -760px; } -.emoji-slot_machine { background-position: -280px -760px; } -.emoji-small_blue_diamond { background-position: -300px -760px; } -.emoji-small_orange_diamond { background-position: -320px -760px; } -.emoji-small_red_triangle { background-position: -340px -760px; } -.emoji-small_red_triangle_down { background-position: -360px -760px; } -.emoji-smile { background-position: -380px -760px; } -.emoji-smile_cat { background-position: -400px -760px; } -.emoji-smiley { background-position: -420px -760px; } -.emoji-smiley_cat { background-position: -440px -760px; } -.emoji-smiling_imp { background-position: -460px -760px; } -.emoji-smirk { background-position: -480px -760px; } -.emoji-smirk_cat { background-position: -500px -760px; } -.emoji-smoking { background-position: -520px -760px; } -.emoji-snail { background-position: -540px -760px; } -.emoji-snake { background-position: -560px -760px; } -.emoji-sneezing_face { background-position: -580px -760px; } -.emoji-snowboarder { background-position: -600px -760px; } -.emoji-snowflake { background-position: -620px -760px; } -.emoji-snowman { background-position: -640px -760px; } -.emoji-snowman2 { background-position: -660px -760px; } -.emoji-sob { background-position: -680px -760px; } -.emoji-soccer { background-position: -700px -760px; } -.emoji-soon { background-position: -720px -760px; } -.emoji-sos { background-position: -740px -760px; } -.emoji-sound { background-position: -760px -760px; } -.emoji-space_invader { background-position: -780px 0; } -.emoji-spades { background-position: -780px -20px; } -.emoji-spaghetti { background-position: -780px -40px; } -.emoji-sparkle { background-position: -780px -60px; } -.emoji-sparkler { background-position: -780px -80px; } -.emoji-sparkles { background-position: -780px -100px; } -.emoji-sparkling_heart { background-position: -780px -120px; } -.emoji-speak_no_evil { background-position: -780px -140px; } -.emoji-speaker { background-position: -780px -160px; } -.emoji-speaking_head { background-position: -780px -180px; } -.emoji-speech_balloon { background-position: -780px -200px; } -.emoji-speech_left { background-position: -780px -220px; } -.emoji-speedboat { background-position: -780px -240px; } -.emoji-spider { background-position: -780px -260px; } -.emoji-spider_web { background-position: -780px -280px; } -.emoji-spoon { background-position: -780px -300px; } -.emoji-spy { background-position: -780px -320px; } -.emoji-spy_tone1 { background-position: -780px -340px; } -.emoji-spy_tone2 { background-position: -780px -360px; } -.emoji-spy_tone3 { background-position: -780px -380px; } -.emoji-spy_tone4 { background-position: -780px -400px; } -.emoji-spy_tone5 { background-position: -780px -420px; } -.emoji-squid { background-position: -780px -440px; } -.emoji-stadium { background-position: -780px -460px; } -.emoji-star { background-position: -780px -480px; } -.emoji-star2 { background-position: -780px -500px; } -.emoji-star_and_crescent { background-position: -780px -520px; } -.emoji-star_of_david { background-position: -780px -540px; } -.emoji-stars { background-position: -780px -560px; } -.emoji-station { background-position: -780px -580px; } -.emoji-statue_of_liberty { background-position: -780px -600px; } -.emoji-steam_locomotive { background-position: -780px -620px; } -.emoji-stew { background-position: -780px -640px; } -.emoji-stop_button { background-position: -780px -660px; } -.emoji-stopwatch { background-position: -780px -680px; } -.emoji-straight_ruler { background-position: -780px -700px; } -.emoji-strawberry { background-position: -780px -720px; } -.emoji-stuck_out_tongue { background-position: -780px -740px; } -.emoji-stuck_out_tongue_closed_eyes { background-position: -780px -760px; } -.emoji-stuck_out_tongue_winking_eye { background-position: 0 -780px; } -.emoji-stuffed_flatbread { background-position: -20px -780px; } -.emoji-sun_with_face { background-position: -40px -780px; } -.emoji-sunflower { background-position: -60px -780px; } -.emoji-sunglasses { background-position: -80px -780px; } -.emoji-sunny { background-position: -100px -780px; } -.emoji-sunrise { background-position: -120px -780px; } -.emoji-sunrise_over_mountains { background-position: -140px -780px; } -.emoji-surfer { background-position: -160px -780px; } -.emoji-surfer_tone1 { background-position: -180px -780px; } -.emoji-surfer_tone2 { background-position: -200px -780px; } -.emoji-surfer_tone3 { background-position: -220px -780px; } -.emoji-surfer_tone4 { background-position: -240px -780px; } -.emoji-surfer_tone5 { background-position: -260px -780px; } -.emoji-sushi { background-position: -280px -780px; } -.emoji-suspension_railway { background-position: -300px -780px; } -.emoji-sweat { background-position: -320px -780px; } -.emoji-sweat_drops { background-position: -340px -780px; } -.emoji-sweat_smile { background-position: -360px -780px; } -.emoji-sweet_potato { background-position: -380px -780px; } -.emoji-swimmer { background-position: -400px -780px; } -.emoji-swimmer_tone1 { background-position: -420px -780px; } -.emoji-swimmer_tone2 { background-position: -440px -780px; } -.emoji-swimmer_tone3 { background-position: -460px -780px; } -.emoji-swimmer_tone4 { background-position: -480px -780px; } -.emoji-swimmer_tone5 { background-position: -500px -780px; } -.emoji-symbols { background-position: -520px -780px; } -.emoji-synagogue { background-position: -540px -780px; } -.emoji-syringe { background-position: -560px -780px; } -.emoji-taco { background-position: -580px -780px; } -.emoji-tada { background-position: -600px -780px; } -.emoji-tanabata_tree { background-position: -620px -780px; } -.emoji-tangerine { background-position: -640px -780px; } -.emoji-taurus { background-position: -660px -780px; } -.emoji-taxi { background-position: -680px -780px; } -.emoji-tea { background-position: -700px -780px; } -.emoji-telephone { background-position: -720px -780px; } -.emoji-telephone_receiver { background-position: -740px -780px; } -.emoji-telescope { background-position: -760px -780px; } -.emoji-ten { background-position: -780px -780px; } -.emoji-tennis { background-position: -800px 0; } -.emoji-tent { background-position: -800px -20px; } -.emoji-thermometer { background-position: -800px -40px; } -.emoji-thermometer_face { background-position: -800px -60px; } -.emoji-thinking { background-position: -800px -80px; } -.emoji-third_place { background-position: -800px -100px; } -.emoji-thought_balloon { background-position: -800px -120px; } -.emoji-three { background-position: -800px -140px; } -.emoji-thumbsdown { background-position: -800px -160px; } -.emoji-thumbsdown_tone1 { background-position: -800px -180px; } -.emoji-thumbsdown_tone2 { background-position: -800px -200px; } -.emoji-thumbsdown_tone3 { background-position: -800px -220px; } -.emoji-thumbsdown_tone4 { background-position: -800px -240px; } -.emoji-thumbsdown_tone5 { background-position: -800px -260px; } -.emoji-thumbsup { background-position: -800px -280px; } -.emoji-thumbsup_tone1 { background-position: -800px -300px; } -.emoji-thumbsup_tone2 { background-position: -800px -320px; } -.emoji-thumbsup_tone3 { background-position: -800px -340px; } -.emoji-thumbsup_tone4 { background-position: -800px -360px; } -.emoji-thumbsup_tone5 { background-position: -800px -380px; } -.emoji-thunder_cloud_rain { background-position: -800px -400px; } -.emoji-ticket { background-position: -800px -420px; } -.emoji-tickets { background-position: -800px -440px; } -.emoji-tiger { background-position: -800px -460px; } -.emoji-tiger2 { background-position: -800px -480px; } -.emoji-timer { background-position: -800px -500px; } -.emoji-tired_face { background-position: -800px -520px; } -.emoji-tm { background-position: -800px -540px; } -.emoji-toilet { background-position: -800px -560px; } -.emoji-tokyo_tower { background-position: -800px -580px; } -.emoji-tomato { background-position: -800px -600px; } -.emoji-tone1 { background-position: -800px -620px; } -.emoji-tone2 { background-position: -800px -640px; } -.emoji-tone3 { background-position: -800px -660px; } -.emoji-tone4 { background-position: -800px -680px; } -.emoji-tone5 { background-position: -800px -700px; } -.emoji-tongue { background-position: -800px -720px; } -.emoji-tools { background-position: -800px -740px; } -.emoji-top { background-position: -800px -760px; } -.emoji-tophat { background-position: -800px -780px; } -.emoji-track_next { background-position: 0 -800px; } -.emoji-track_previous { background-position: -20px -800px; } -.emoji-trackball { background-position: -40px -800px; } -.emoji-tractor { background-position: -60px -800px; } -.emoji-traffic_light { background-position: -80px -800px; } -.emoji-train { background-position: -100px -800px; } -.emoji-train2 { background-position: -120px -800px; } -.emoji-tram { background-position: -140px -800px; } -.emoji-triangular_flag_on_post { background-position: -160px -800px; } -.emoji-triangular_ruler { background-position: -180px -800px; } -.emoji-trident { background-position: -200px -800px; } -.emoji-triumph { background-position: -220px -800px; } -.emoji-trolleybus { background-position: -240px -800px; } -.emoji-trophy { background-position: -260px -800px; } -.emoji-tropical_drink { background-position: -280px -800px; } -.emoji-tropical_fish { background-position: -300px -800px; } -.emoji-truck { background-position: -320px -800px; } -.emoji-trumpet { background-position: -340px -800px; } -.emoji-tulip { background-position: -360px -800px; } -.emoji-tumbler_glass { background-position: -380px -800px; } -.emoji-turkey { background-position: -400px -800px; } -.emoji-turtle { background-position: -420px -800px; } -.emoji-tv { background-position: -440px -800px; } -.emoji-twisted_rightwards_arrows { background-position: -460px -800px; } -.emoji-two { background-position: -480px -800px; } -.emoji-two_hearts { background-position: -500px -800px; } -.emoji-two_men_holding_hands { background-position: -520px -800px; } -.emoji-two_women_holding_hands { background-position: -540px -800px; } -.emoji-u5272 { background-position: -560px -800px; } -.emoji-u5408 { background-position: -580px -800px; } -.emoji-u55b6 { background-position: -600px -800px; } -.emoji-u6307 { background-position: -620px -800px; } -.emoji-u6708 { background-position: -640px -800px; } -.emoji-u6709 { background-position: -660px -800px; } -.emoji-u6e80 { background-position: -680px -800px; } -.emoji-u7121 { background-position: -700px -800px; } -.emoji-u7533 { background-position: -720px -800px; } -.emoji-u7981 { background-position: -740px -800px; } -.emoji-u7a7a { background-position: -760px -800px; } -.emoji-umbrella { background-position: -780px -800px; } -.emoji-umbrella2 { background-position: -800px -800px; } -.emoji-unamused { background-position: -820px 0; } -.emoji-underage { background-position: -820px -20px; } -.emoji-unicorn { background-position: -820px -40px; } -.emoji-unlock { background-position: -820px -60px; } -.emoji-up { background-position: -820px -80px; } -.emoji-upside_down { background-position: -820px -100px; } -.emoji-urn { background-position: -820px -120px; } -.emoji-v { background-position: -820px -140px; } -.emoji-v_tone1 { background-position: -820px -160px; } -.emoji-v_tone2 { background-position: -820px -180px; } -.emoji-v_tone3 { background-position: -820px -200px; } -.emoji-v_tone4 { background-position: -820px -220px; } -.emoji-v_tone5 { background-position: -820px -240px; } -.emoji-vertical_traffic_light { background-position: -820px -260px; } -.emoji-vhs { background-position: -820px -280px; } -.emoji-vibration_mode { background-position: -820px -300px; } -.emoji-video_camera { background-position: -820px -320px; } -.emoji-video_game { background-position: -820px -340px; } -.emoji-violin { background-position: -820px -360px; } -.emoji-virgo { background-position: -820px -380px; } -.emoji-volcano { background-position: -820px -400px; } -.emoji-volleyball { background-position: -820px -420px; } -.emoji-vs { background-position: -820px -440px; } -.emoji-vulcan { background-position: -820px -460px; } -.emoji-vulcan_tone1 { background-position: -820px -480px; } -.emoji-vulcan_tone2 { background-position: -820px -500px; } -.emoji-vulcan_tone3 { background-position: -820px -520px; } -.emoji-vulcan_tone4 { background-position: -820px -540px; } -.emoji-vulcan_tone5 { background-position: -820px -560px; } -.emoji-walking { background-position: -820px -580px; } -.emoji-walking_tone1 { background-position: -820px -600px; } -.emoji-walking_tone2 { background-position: -820px -620px; } -.emoji-walking_tone3 { background-position: -820px -640px; } -.emoji-walking_tone4 { background-position: -820px -660px; } -.emoji-walking_tone5 { background-position: -820px -680px; } -.emoji-waning_crescent_moon { background-position: -820px -700px; } -.emoji-waning_gibbous_moon { background-position: -820px -720px; } -.emoji-warning { background-position: -820px -740px; } -.emoji-wastebasket { background-position: -820px -760px; } -.emoji-watch { background-position: -820px -780px; } -.emoji-water_buffalo { background-position: -820px -800px; } -.emoji-water_polo { background-position: 0 -820px; } -.emoji-water_polo_tone1 { background-position: -20px -820px; } -.emoji-water_polo_tone2 { background-position: -40px -820px; } -.emoji-water_polo_tone3 { background-position: -60px -820px; } -.emoji-water_polo_tone4 { background-position: -80px -820px; } -.emoji-water_polo_tone5 { background-position: -100px -820px; } -.emoji-watermelon { background-position: -120px -820px; } -.emoji-wave { background-position: -140px -820px; } -.emoji-wave_tone1 { background-position: -160px -820px; } -.emoji-wave_tone2 { background-position: -180px -820px; } -.emoji-wave_tone3 { background-position: -200px -820px; } -.emoji-wave_tone4 { background-position: -220px -820px; } -.emoji-wave_tone5 { background-position: -240px -820px; } -.emoji-wavy_dash { background-position: -260px -820px; } -.emoji-waxing_crescent_moon { background-position: -280px -820px; } -.emoji-waxing_gibbous_moon { background-position: -300px -820px; } -.emoji-wc { background-position: -320px -820px; } -.emoji-weary { background-position: -340px -820px; } -.emoji-wedding { background-position: -360px -820px; } -.emoji-whale { background-position: -380px -820px; } -.emoji-whale2 { background-position: -400px -820px; } -.emoji-wheel_of_dharma { background-position: -420px -820px; } -.emoji-wheelchair { background-position: -440px -820px; } -.emoji-white_check_mark { background-position: -460px -820px; } -.emoji-white_circle { background-position: -480px -820px; } -.emoji-white_flower { background-position: -500px -820px; } -.emoji-white_large_square { background-position: -520px -820px; } -.emoji-white_medium_small_square { background-position: -540px -820px; } -.emoji-white_medium_square { background-position: -560px -820px; } -.emoji-white_small_square { background-position: -580px -820px; } -.emoji-white_square_button { background-position: -600px -820px; } -.emoji-white_sun_cloud { background-position: -620px -820px; } -.emoji-white_sun_rain_cloud { background-position: -640px -820px; } -.emoji-white_sun_small_cloud { background-position: -660px -820px; } -.emoji-wilted_rose { background-position: -680px -820px; } -.emoji-wind_blowing_face { background-position: -700px -820px; } -.emoji-wind_chime { background-position: -720px -820px; } -.emoji-wine_glass { background-position: -740px -820px; } -.emoji-wink { background-position: -760px -820px; } -.emoji-wolf { background-position: -780px -820px; } -.emoji-woman { background-position: -800px -820px; } -.emoji-woman_tone1 { background-position: -820px -820px; } -.emoji-woman_tone2 { background-position: -840px 0; } -.emoji-woman_tone3 { background-position: -840px -20px; } -.emoji-woman_tone4 { background-position: -840px -40px; } -.emoji-woman_tone5 { background-position: -840px -60px; } -.emoji-womans_clothes { background-position: -840px -80px; } -.emoji-womans_hat { background-position: -840px -100px; } -.emoji-womens { background-position: -840px -120px; } -.emoji-worried { background-position: -840px -140px; } -.emoji-wrench { background-position: -840px -160px; } -.emoji-wrestlers { background-position: -840px -180px; } -.emoji-wrestlers_tone1 { background-position: -840px -200px; } -.emoji-wrestlers_tone2 { background-position: -840px -220px; } -.emoji-wrestlers_tone3 { background-position: -840px -240px; } -.emoji-wrestlers_tone4 { background-position: -840px -260px; } -.emoji-wrestlers_tone5 { background-position: -840px -280px; } -.emoji-writing_hand { background-position: -840px -300px; } -.emoji-writing_hand_tone1 { background-position: -840px -320px; } -.emoji-writing_hand_tone2 { background-position: -840px -340px; } -.emoji-writing_hand_tone3 { background-position: -840px -360px; } -.emoji-writing_hand_tone4 { background-position: -840px -380px; } -.emoji-writing_hand_tone5 { background-position: -840px -400px; } -.emoji-x { background-position: -840px -420px; } -.emoji-yellow_heart { background-position: -840px -440px; } -.emoji-yen { background-position: -840px -460px; } -.emoji-yin_yang { background-position: -840px -480px; } -.emoji-yum { background-position: -840px -500px; } -.emoji-zap { background-position: -840px -520px; } -.emoji-zero { background-position: -840px -540px; } -.emoji-zipper_mouth { background-position: -840px -560px; } -.emoji-100 { background-position: -840px -580px; } - -.emoji-icon { - background-image: image-url('emoji.png'); - background-repeat: no-repeat; - color: transparent; - text-indent: -99em; - height: 20px; - width: 20px; - - @media only screen and (-webkit-min-device-pixel-ratio: 2), - only screen and (min--moz-device-pixel-ratio: 2), - only screen and (-o-min-device-pixel-ratio: 2/1), - only screen and (min-device-pixel-ratio: 2), - only screen and (min-resolution: 192dpi), - only screen and (min-resolution: 2dppx) { - background-image: image-url('emoji@2x.png'); - background-size: 860px 840px; - } -} diff --git a/app/assets/stylesheets/framework/gitlab_theme.scss b/app/assets/stylesheets/framework/gitlab_theme.scss index 05cb0196ced..0bbd6eb27c1 100644 --- a/app/assets/stylesheets/framework/gitlab_theme.scss +++ b/app/assets/stylesheets/framework/gitlab_theme.scss @@ -177,25 +177,6 @@ } } - // Web IDE - .ide-sidebar-link { - color: $color-200; - background-color: $color-700; - - &:hover, - &:focus { - background-color: $color-500; - } - - &:active { - background: $color-800; - } - } - - .branch-container { - border-left-color: $color-700; - } - .branch-header-title { color: $color-700; } @@ -203,6 +184,13 @@ .ide-file-list .file.file-active { color: $color-700; } + + .ide-sidebar-link { + &.active { + color: $color-700; + box-shadow: inset 3px 0 $color-700; + } + } } body { @@ -343,9 +331,5 @@ body { .sidebar-top-level-items > li.active .badge { color: $theme-gray-900; } - - .ide-sidebar-link { - color: $white-light; - } } } diff --git a/app/assets/stylesheets/framework/images.scss b/app/assets/stylesheets/framework/images.scss index 62a0fba3da3..ab3cceceae9 100644 --- a/app/assets/stylesheets/framework/images.scss +++ b/app/assets/stylesheets/framework/images.scss @@ -39,35 +39,10 @@ svg { fill: currentColor; - &.s8 { - @include svg-size(8px); - } - - &.s12 { - @include svg-size(12px); - } - - &.s16 { - @include svg-size(16px); - } - - &.s18 { - @include svg-size(18px); - } - - &.s24 { - @include svg-size(24px); - } - - &.s32 { - @include svg-size(32px); - } - - &.s48 { - @include svg-size(48px); - } - - &.s72 { - @include svg-size(72px); + $svg-sizes: 8 12 16 18 24 32 48 72; + @each $svg-size in $svg-sizes { + &.s#{$svg-size} { + @include svg-size(#{$svg-size}px); + } } } diff --git a/app/assets/stylesheets/framework/markdown_area.scss b/app/assets/stylesheets/framework/markdown_area.scss index 938f5f49c09..7b5d1c2cf8b 100644 --- a/app/assets/stylesheets/framework/markdown_area.scss +++ b/app/assets/stylesheets/framework/markdown_area.scss @@ -107,6 +107,16 @@ padding-top: 10px; } +.referenced-commands { + background: $blue-50; + padding: $gl-padding-8 $gl-padding; + border-radius: $border-radius-default; + + p { + margin: 0; + } +} + .md-preview-holder { min-height: 167px; padding: 10px 0; diff --git a/app/assets/stylesheets/framework/mobile.scss b/app/assets/stylesheets/framework/mobile.scss index 8604e753c18..9e03bb98b8e 100644 --- a/app/assets/stylesheets/framework/mobile.scss +++ b/app/assets/stylesheets/framework/mobile.scss @@ -40,10 +40,6 @@ .project-home-panel { padding-left: 0 !important; - .project-avatar { - display: block; - } - .project-repo-buttons, .git-clone-holder { display: none; diff --git a/app/assets/stylesheets/framework/secondary_navigation_elements.scss b/app/assets/stylesheets/framework/secondary_navigation_elements.scss index 17c31d6b184..66dbe403385 100644 --- a/app/assets/stylesheets/framework/secondary_navigation_elements.scss +++ b/app/assets/stylesheets/framework/secondary_navigation_elements.scss @@ -241,8 +241,6 @@ } .scrolling-tabs-container { - position: relative; - .merge-request-tabs-container & { overflow: hidden; } @@ -272,8 +270,6 @@ } .inner-page-scroll-tabs { - position: relative; - .fade-right { @include fade(left, $white-light); right: 0; diff --git a/app/assets/stylesheets/framework/terms.scss b/app/assets/stylesheets/framework/terms.scss new file mode 100644 index 00000000000..16293d32dfa --- /dev/null +++ b/app/assets/stylesheets/framework/terms.scss @@ -0,0 +1,59 @@ +.terms { + .with-performance-bar & { + margin-top: 0; + } + + .alert-wrapper { + min-height: $header-height + $gl-padding; + } + + .content { + padding-top: $gl-padding; + } + + .panel { + .panel-heading { + display: -webkit-flex; + display: flex; + align-items: center; + justify-content: space-between; + + .title { + display: flex; + align-items: center; + + .logo-text { + width: 55px; + height: 24px; + display: flex; + flex-direction: column; + justify-content: center; + } + } + + .navbar-collapse { + padding-right: 0; + } + + .nav li a { + color: $theme-gray-700; + } + } + + .panel-content { + padding: $gl-padding; + + *:first-child { + margin-top: 0; + } + + *:last-child { + margin-bottom: 0; + } + } + + .footer-block { + margin: 0; + } + } +} diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index 8c44ebc85ef..b5505538541 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -212,6 +212,7 @@ $tooltip-font-size: 12px; /* * Padding */ +$gl-padding-24: 24px; $gl-padding: 16px; $gl-padding-8: 8px; $gl-padding-4: 4px; @@ -229,6 +230,7 @@ $row-hover: $blue-50; $row-hover-border: $blue-200; $progress-color: #c0392b; $header-height: 40px; +$ide-statusbar-height: 27px; $fixed-layout-width: 1280px; $limited-layout-width: 990px; $limited-layout-width-sm: 790px; @@ -263,6 +265,7 @@ $performance-bar-height: 35px; $flash-height: 52px; $context-header-height: 60px; $breadcrumb-min-height: 48px; +$gcp-signup-offer-icon-max-width: 125px; /* * Common component specific colors @@ -332,11 +335,10 @@ $diff-jagged-border-gradient-color: darken($white-normal, 8%); /* * Fonts */ -$monospace_font: 'Menlo', 'DejaVu Sans Mono', 'Liberation Mono', 'Consolas', - 'Ubuntu Mono', 'Courier New', 'andale mono', 'lucida console', monospace; -$regular_font: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, - Oxygen-Sans, Ubuntu, Cantarell, 'Helvetica Neue', sans-serif, - 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; +$monospace_font: 'Menlo', 'DejaVu Sans Mono', 'Liberation Mono', 'Consolas', 'Ubuntu Mono', + 'Courier New', 'andale mono', 'lucida console', monospace; +$regular_font: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen-Sans, Ubuntu, Cantarell, + 'Helvetica Neue', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; /* * Dropdowns @@ -464,11 +466,9 @@ $issue-boards-card-shadow: rgba(186, 186, 186, 0.5); */ $issue-boards-filter-height: 68px; $issue-boards-breadcrumbs-height-xs: 63px; -$issue-board-list-difference-xs: $header-height + - $issue-boards-breadcrumbs-height-xs; +$issue-board-list-difference-xs: $header-height + $issue-boards-breadcrumbs-height-xs; $issue-board-list-difference-sm: $header-height + $breadcrumb-min-height; -$issue-board-list-difference-md: $issue-board-list-difference-sm + - $issue-boards-filter-height; +$issue-board-list-difference-md: $issue-board-list-difference-sm + $issue-boards-filter-height; /* * Avatar @@ -689,6 +689,8 @@ $stage-hover-bg: $gray-darker; $ci-action-icon-size: 22px; $pipeline-dropdown-line-height: 20px; $pipeline-dropdown-status-icon-size: 18px; +$ci-action-dropdown-button-size: 24px; +$ci-action-dropdown-svg-size: 12px; /* CI variable lists diff --git a/app/assets/stylesheets/framework/wells.scss b/app/assets/stylesheets/framework/wells.scss index 2f3a80daa90..3fa7a260017 100644 --- a/app/assets/stylesheets/framework/wells.scss +++ b/app/assets/stylesheets/framework/wells.scss @@ -19,6 +19,7 @@ .fork-svg { margin-right: 4px; + vertical-align: bottom; } } diff --git a/app/assets/stylesheets/pages/boards.scss b/app/assets/stylesheets/pages/boards.scss index 318d3ddaece..681242f8d85 100644 --- a/app/assets/stylesheets/pages/boards.scss +++ b/app/assets/stylesheets/pages/boards.scss @@ -317,6 +317,7 @@ a { color: $gl-text-color; word-wrap: break-word; + word-break: break-word; margin-right: 2px; } } @@ -462,6 +463,7 @@ .issuable-header-text { padding-right: 35px; + word-break: break-word; > strong { font-weight: $gl-font-weight-bold; diff --git a/app/assets/stylesheets/pages/clusters.scss b/app/assets/stylesheets/pages/clusters.scss index 7b8ee026357..3fd13078131 100644 --- a/app/assets/stylesheets/pages/clusters.scss +++ b/app/assets/stylesheets/pages/clusters.scss @@ -26,3 +26,51 @@ margin-right: 0; } } + +.gcp-signup-offer { + background-color: $blue-50; + border: 1px solid $blue-300; + border-radius: $border-radius-default; + + // TODO: To be superceded by cssLab + &.alert { + padding: 24px 16px; + + &-dismissable { + padding-right: 32px; + + .close { + top: -8px; + right: -16px; + color: $blue-500; + opacity: 1; + } + } + } + + .gcp-logo { + margin-bottom: $gl-padding; + text-align: center; + } + + img { + max-width: $gcp-signup-offer-icon-max-width; + } + + a:not(.btn) { + color: $gl-link-color; + font-weight: normal; + text-decoration: none; + } + + @media (min-width: $screen-sm-min) { + > div { + display: flex; + align-items: center; + } + + .gcp-logo { + margin: 0; + } + } +} diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss index e9384d41e00..944996159d7 100644 --- a/app/assets/stylesheets/pages/commits.scss +++ b/app/assets/stylesheets/pages/commits.scss @@ -2,7 +2,6 @@ background: none; border: 0; padding: 0; - margin-top: 10px; word-break: normal; white-space: pre-wrap; } @@ -21,10 +20,6 @@ margin: 0; color: $gl-text-color; } - - .commit-description { - margin-top: 15px; - } } .commit-hash-full { @@ -70,7 +65,7 @@ } .branch-info .commit-icon { - margin-right: 3px; + margin-right: 8px; svg { top: 3px; @@ -178,7 +173,7 @@ .commit-detail { display: flex; justify-content: space-between; - align-items: center; + align-items: start; flex-grow: 1; } @@ -268,20 +263,16 @@ .commit-row-description { font-size: 14px; - padding: 10px 15px; - margin: 10px 0; - background: $gray-light; + padding: 0 0 0 $gl-padding-8; + border: 0; display: none; white-space: pre-wrap; word-break: normal; - - pre { - border: 0; - background: inherit; - padding: 0; - margin: 0; - white-space: pre-wrap; - } + color: $gl-text-color-secondary; + background: none; + font-family: inherit; + border-left: 2px solid $theme-gray-300; + border-radius: unset; a { color: $gl-text-color; diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index 11052be40a8..70ce5de6a6c 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -44,6 +44,12 @@ } } + .note-text { + table { + font-family: $font-family-sans-serif; + } + } + table { width: 100%; font-family: $monospace_font; diff --git a/app/assets/stylesheets/pages/environments.scss b/app/assets/stylesheets/pages/environments.scss index 3a300086fa3..1f406cc1c2d 100644 --- a/app/assets/stylesheets/pages/environments.scss +++ b/app/assets/stylesheets/pages/environments.scss @@ -283,28 +283,59 @@ } &.popover { + padding: 0; + border: 1px solid $border-color; + &.left { left: auto; right: 0; margin-right: 10px; + + > .arrow { + right: -16px; + border-left-color: $border-color; + } + + > .arrow::after { + border-left-color: $theme-gray-50; + } } &.right { left: 0; right: auto; margin-left: 10px; + + > .arrow { + left: -16px; + border-right-color: $border-color; + } + + > .arrow::after { + border-right-color: $theme-gray-50; + } } > .arrow { - top: 40px; + top: 16px; + margin-top: -8px; + border-width: 8px; } > .popover-title, > .popover-content { - padding: 5px 8px; + padding: 8px; font-size: 12px; white-space: nowrap; } + + > .popover-title { + background-color: $theme-gray-50; + } + } + + strong { + font-weight: 600; } } @@ -317,7 +348,7 @@ vertical-align: middle; + td { - padding-left: 5px; + padding-left: 8px; vertical-align: top; } } diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss index b0852adb459..d81236c5883 100644 --- a/app/assets/stylesheets/pages/labels.scss +++ b/app/assets/stylesheets/pages/labels.scss @@ -314,6 +314,10 @@ display: inline-flex; vertical-align: top; + &:hover .color-label { + text-decoration: underline; + } + .label { vertical-align: inherit; font-size: $label-font-size; diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index 66db4917178..3581dd36a10 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -156,10 +156,6 @@ .dropdown-menu { z-index: 300; } - - .ci-action-icon-wrapper { - line-height: 16px; - } } .mini-pipeline-graph-dropdown-toggle { diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index 81e98f358a8..6d5c6cb136f 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -772,7 +772,3 @@ ul.notes { height: auto; } } - -.line-resolve-text { - vertical-align: middle; -} diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 3a8ec779c14..02803e7b040 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -22,7 +22,6 @@ } .ci-table { - .label { margin-bottom: 3px; } @@ -123,7 +122,6 @@ } .branch-commit { - .ref-name { font-weight: $gl-font-weight-bold; max-width: 100px; @@ -481,43 +479,6 @@ @extend .build-content:hover; } - .ci-action-icon-container { - position: absolute; - right: 5px; - top: 5px; - - // Action Icons in big pipeline-graph nodes - &.ci-action-icon-wrapper { - height: 30px; - width: 30px; - background: $white-light; - border: 1px solid $border-color; - border-radius: 100%; - display: block; - - &:hover { - background-color: $stage-hover-bg; - border: 1px solid $dropdown-toggle-active-border-color; - - svg { - fill: $gl-text-color; - } - } - - svg { - fill: $gl-text-color-secondary; - position: relative; - top: -1px; - } - - &.play { - svg { - left: 2px; - } - } - } - } - .ci-status-icon svg { height: 20px; width: 20px; @@ -548,7 +509,6 @@ border: 1px solid $dropdown-toggle-active-border-color; } - // Connect first build in each stage with right horizontal line &:first-child { &::after { @@ -602,6 +562,43 @@ } } } + + .ci-action-icon-container { + position: absolute; + right: 5px; + top: 5px; + + // Action Icons in big pipeline-graph nodes + &.ci-action-icon-wrapper { + height: 30px; + width: 30px; + background: $white-light; + border: 1px solid $border-color; + border-radius: 100%; + display: block; + + &:hover { + background-color: $stage-hover-bg; + border: 1px solid $dropdown-toggle-active-border-color; + + svg { + fill: $gl-text-color; + } + } + + svg { + fill: $gl-text-color-secondary; + position: relative; + top: -1px; + } + + &.play { + svg { + left: 2px; + } + } + } + } } // Triggers the dropdown in the big pipeline graph @@ -710,93 +707,77 @@ button.mini-pipeline-graph-dropdown-toggle { } } -// dropdown content for big and mini pipeline +/** + Action icons inside dropdowns: + - mini graph in pipelines table + - dropdown in big graph + - mini graph in MR widget pipeline + - mini graph in Commit widget pipeline +*/ .big-pipeline-graph-dropdown-menu, .mini-pipeline-graph-dropdown-menu { width: 240px; max-width: 240px; - .scrollable-menu { + // override dropdown.scss + &.dropdown-menu li button, + &.dropdown-menu li a.ci-action-icon-container { padding: 0; - max-height: 245px; - overflow: auto; + text-align: center; } - li { - position: relative; + .ci-action-icon-container { + position: absolute; + right: 8px; + top: 8px; - // ensure .mini-pipeline-graph-dropdown-item has hover style when action-icon is hovered - &:hover > .mini-pipeline-graph-dropdown-item, - &:hover > .ci-job-component > .mini-pipeline-graph-dropdown-item { - @extend .mini-pipeline-graph-dropdown-item:hover; - } + &.ci-action-icon-wrapper { + height: $ci-action-dropdown-button-size; + width: $ci-action-dropdown-button-size; - // Action icon on the right - a.ci-action-icon-wrapper { - border-radius: 50%; + background: $white-light; border: 1px solid $border-color; - width: $ci-action-icon-size; - height: $ci-action-icon-size; - padding: 2px 0 0 5px; - font-size: 12px; - background-color: $white-light; - position: absolute; - top: 50%; - right: $gl-padding; - margin-top: -#{$ci-action-icon-size / 2}; + border-radius: 50%; + display: block; - &:hover, - &:focus { + &:hover { background-color: $stage-hover-bg; border: 1px solid $dropdown-toggle-active-border-color; + + svg { + fill: $gl-text-color; + } } svg { + width: $ci-action-dropdown-svg-size; + height: $ci-action-dropdown-svg-size; fill: $gl-text-color-secondary; - width: #{$ci-action-icon-size - 6}; - height: #{$ci-action-icon-size - 6}; - left: -3px; position: relative; - top: -1px; - - &.icon-action-stop, - &.icon-action-cancel { - width: 12px; - height: 12px; - top: 1px; - left: -1px; - } - - &.icon-action-play { - width: 11px; - height: 11px; - top: 1px; - left: 1px; - } - - &.icon-action-retry { - width: 16px; - height: 16px; - top: 0; - left: -3px; - } + top: 0; + vertical-align: initial; } + } + } - &:hover svg, - &:focus svg { - fill: $gl-text-color; - } + // SVGs in the commit widget and mr widget + a.ci-action-icon-container.ci-action-icon-wrapper svg { + top: 2px; + } - &.icon-action-retry, - &.icon-action-play { - svg { - width: #{$ci-action-icon-size - 6}; - height: #{$ci-action-icon-size - 6}; - left: 8px; - } - } + .scrollable-menu { + padding: 0; + max-height: 245px; + overflow: auto; + } + li { + position: relative; + // ensure .mini-pipeline-graph-dropdown-item has hover style when action-icon is hovered + &:hover > .mini-pipeline-graph-dropdown-item, + &:hover > .ci-job-component > .mini-pipeline-graph-dropdown-item { + @extend .mini-pipeline-graph-dropdown-item:hover; } // link to the build @@ -808,6 +789,11 @@ button.mini-pipeline-graph-dropdown-toggle { line-height: $line-height-base; white-space: nowrap; + // Match dropdown.scss for all `a` tags + &.non-details-job-component { + padding: 8px 16px; + } + .ci-job-name-component { align-items: center; display: flex; @@ -939,7 +925,7 @@ button.mini-pipeline-graph-dropdown-toggle { &.dropdown-menu { transform: translate(-80%, 0); - @media(min-width: $screen-md-min) { + @media (min-width: $screen-md-min) { transform: translate(-50%, 0); right: auto; left: 50%; diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index d7d343b088a..dd0cb2c2613 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -205,7 +205,6 @@ .project-repo-buttons, .group-buttons { .btn { - @include btn-gray; padding: 3px 10px; &:last-child { @@ -294,7 +293,7 @@ } .count { - @include btn-gray; + @include btn-white; display: inline-block; background: $white-light; border-radius: 2px; @@ -354,30 +353,48 @@ min-width: 200px; } -.deploy-key-content { - @media (min-width: $screen-sm-min) { - float: left; +.deploy-keys { + .scrolling-tabs-container { + position: relative; + } +} - &:last-child { - float: right; +.deploy-key { + // Ensure that the fingerprint does not overflow on small screens + .fingerprint { + word-break: break-all; + white-space: normal; + } + + .deploy-project-label, + .key-created-at { + svg { + vertical-align: text-top; } } -} -.deploy-key-projects { - @media (min-width: $screen-sm-min) { - line-height: 42px; + .btn svg { + vertical-align: top; + } + + .key-created-at { + line-height: unset; } } -a.deploy-project-label { - padding: 5px; - margin-right: 5px; - color: $gl-text-color; - background-color: $row-hover; +.deploy-project-list { + margin-bottom: -$gl-padding-4; - &:hover { - color: $gl-link-color; + a.deploy-project-label { + margin-right: $gl-padding-4; + margin-bottom: $gl-padding-4; + color: $gl-text-color-secondary; + background-color: $theme-gray-100; + line-height: $gl-btn-line-height; + + &:hover { + color: $gl-link-color; + } } } diff --git a/app/assets/stylesheets/pages/repo.scss b/app/assets/stylesheets/pages/repo.scss index 450ef7d6b7e..00457717f00 100644 --- a/app/assets/stylesheets/pages/repo.scss +++ b/app/assets/stylesheets/pages/repo.scss @@ -17,11 +17,13 @@ } .ide-view { + position: relative; display: flex; height: calc(100vh - #{$header-height}); margin-top: 0; border-top: 1px solid $white-dark; border-bottom: 1px solid $white-dark; + padding-bottom: $ide-statusbar-height; &.is-collapsed { .ide-file-list { @@ -54,6 +56,7 @@ white-space: nowrap; text-overflow: ellipsis; max-width: inherit; + line-height: 22px; svg { vertical-align: middle; @@ -66,6 +69,11 @@ } } + .ide-file-icon-holder { + display: flex; + align-items: center; + } + .ide-file-changed-icon { margin-left: auto; @@ -76,7 +84,6 @@ .ide-new-btn { display: none; - margin-bottom: -4px; margin-right: -8px; } @@ -89,10 +96,8 @@ } } - &.folder { - svg { - fill: $gl-text-color-secondary; - } + .folder-icon { + fill: $gl-text-color-secondary; } } @@ -110,20 +115,13 @@ .file-col-commit-message { display: flex; overflow: visible; + align-items: center; 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 { @@ -150,69 +148,56 @@ } 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; - } + align-items: center; + padding: $grid-size $gl-padding; + background-color: $gray-normal; + border-right: 1px solid $white-dark; + border-bottom: 1px solid $white-dark; - .btn { - margin-top: auto; - margin-bottom: auto; + &.active { + background-color: $white-light; + border-bottom-color: $white-light; } } } .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; + @include str-truncated(141px); 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; + margin-left: $grid-size; background: none; border: 0; border-radius: $border-radius-default; color: $theme-gray-900; - transform: translateY(-50%); svg { position: relative; top: -1px; } - &:hover { + .ide-file-changed-icon { + display: block; + position: relative; + top: 1px; + right: -2px; + } + + &:not([disabled]):hover { background-color: $theme-gray-200; } - &:focus { + &:not([disabled]):focus { background-color: $blue-500; color: $white-light; outline: 0; @@ -243,6 +228,17 @@ display: none; } + .is-readonly, + .editor.original { + .view-lines { + cursor: default; + } + + .cursors-layer { + display: none; + } + } + .monaco-diff-editor.vs { .editor.modified { box-shadow: none; @@ -301,15 +297,12 @@ .margin-view-overlays .delete-sign { opacity: 0.4; } - - .cursors-layer { - display: none; - } } } .multi-file-editor-holder { height: 100%; + min-height: 0; } .preview-container { @@ -375,6 +368,7 @@ .ide-btn-group { padding: $gl-padding-4 $gl-vert-padding; + line-height: 24px; } .ide-status-bar { @@ -382,7 +376,13 @@ padding: $gl-bar-padding $gl-padding; background: $white-light; display: flex; - justify-content: flex-end; + justify-content: space-between; + height: $ide-statusbar-height; + + position: absolute; + bottom: 0; + left: 0; + width: 100%; > div + div { padding-left: $gl-padding; @@ -393,6 +393,14 @@ } } +.ide-status-file { + text-align: right; + + .ide-status-branch + &, + &:first-child { + margin-left: auto; + } +} // Not great, but this is to deal with our current output .multi-file-preview-holder { height: 100%; @@ -428,27 +436,35 @@ .multi-file-commit-panel { display: flex; position: relative; - flex-direction: column; width: 340px; padding: 0; background-color: $gray-light; - padding-right: 3px; + padding-right: 1px; + + .context-header { + width: auto; + margin-right: 0; + + a:hover, + a:focus { + text-decoration: none; + } + } .projects-sidebar { + min-height: 0; display: flex; flex-direction: column; - height: 100%; - - .context-header { - width: auto; - margin-right: 0; - } + flex: 1; } .multi-file-commit-panel-inner { + position: relative; display: flex; flex-direction: column; height: 100%; + min-width: 0; + width: 100%; } .multi-file-commit-panel-inner-scroll { @@ -456,68 +472,10 @@ 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; - margin-bottom: $gl-bar-padding; - } - - .branch-header { - background: $white-dark; - display: flex; - } - - .branch-header-title { - flex: 1; - padding: $grid-size $gl-padding; - 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; + background-color: $white-light; + border-left: 1px solid $white-dark; border-top: 1px solid $white-dark; - - svg { - vertical-align: middle; - } - } -} - -.multi-file-context-bar-icon { - padding: 10px; - - svg { - margin-right: 10px; - float: left; + border-top-left-radius: $border-radius-small; } } @@ -543,13 +501,13 @@ align-items: center; margin-bottom: 0; border-bottom: 1px solid $white-dark; - padding: $gl-btn-padding 0; + padding: $gl-btn-padding $gl-padding; } .multi-file-commit-panel-header-title { display: flex; flex: 1; - padding-left: $grid-size; + align-items: center; svg { margin-right: $gl-btn-padding; @@ -565,7 +523,7 @@ .multi-file-commit-list { flex: 1; overflow: auto; - padding: $gl-padding 0; + padding: $gl-padding; min-height: 60px; } @@ -597,14 +555,14 @@ } } -.multi-file-additions, -.multi-file-additions-solid { - fill: $green-500; +.multi-file-addition, +.multi-file-addition-solid { + color: $green-500; } .multi-file-modified, .multi-file-modified-solid { - fill: $orange-500; + color: $orange-500; } .multi-file-commit-list-collapsed { @@ -660,12 +618,24 @@ } .multi-file-commit-form { + position: relative; padding: $gl-padding; + background-color: $white-light; border-top: 1px solid $white-dark; + border-left: 1px solid $white-dark; + transition: all 0.3s ease; .btn { font-size: $gl-font-size; } + + .multi-file-commit-panel-success-message { + top: 0; + } +} + +.multi-file-commit-panel-bottom { + position: relative; } .dirty-diff { @@ -801,7 +771,7 @@ position: absolute; top: 0; bottom: 0; - width: 3px; + width: 1px; background-color: $white-dark; &.dragright { @@ -815,42 +785,40 @@ .ide-commit-list-container { display: flex; + flex: 1; flex-direction: column; width: 100%; - padding: 0 16px; + min-height: 140px; - &:not(.is-collapsed) { - flex: 1; - min-height: 140px; - } - - &.is-collapsed { - .multi-file-commit-panel-header { - margin-left: -$gl-padding; - margin-right: -$gl-padding; - - svg { - margin-left: auto; - margin-right: auto; - } - - .multi-file-commit-panel-collapse-btn { - margin-right: auto; - margin-left: auto; - border-left: 0; - } - } + &.is-first { + border-bottom: 1px solid $white-dark; } } .ide-staged-action-btn { margin-left: auto; - color: $gl-link-color; + line-height: 22px; +} + +.ide-commit-file-count { + min-width: 22px; + margin-left: auto; + background-color: $gray-light; + border-radius: $border-radius-default; + border: 1px solid $white-dark; + line-height: 20px; + text-align: center; } .ide-commit-radios { label { font-weight: normal; + + &.is-disabled { + .ide-radio-label { + text-decoration: line-through; + } + } } .help-block { @@ -863,17 +831,78 @@ margin-left: 25px; } -.ide-external-links { - p { - margin: 0; - } -} - .ide-sidebar-link { - padding: $gl-padding-8 $gl-padding; display: flex; align-items: center; - font-weight: $gl-font-weight-bold; + position: relative; + height: 60px; + width: 100%; + padding: 0 $gl-padding; + color: $gl-text-color-secondary; + background-color: transparent; + border: 0; + border-top: 1px solid transparent; + border-bottom: 1px solid transparent; + outline: 0; + + svg { + margin: 0 auto; + } + + &:hover { + color: $gl-text-color; + background-color: $theme-gray-100; + } + + &:focus { + color: $gl-text-color; + background-color: $theme-gray-200; + } + + &.active { + // extend width over border of sidebar section + width: calc(100% + 1px); + padding-right: $gl-padding + 1px; + background-color: $white-light; + border-top-color: $white-dark; + border-bottom-color: $white-dark; + + &::after { + content: ''; + position: absolute; + right: -1px; + top: 0; + bottom: 0; + width: 1px; + background: $white-light; + } + } +} + +.ide-activity-bar { + position: relative; + flex: 0 0 60px; + z-index: 1; +} + +.ide-file-finder-overlay { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 100; +} + +.ide-file-finder { + top: 10px; + left: 50%; + transform: translateX(-50%); + + .highlighted { + color: $blue-500; + font-weight: $gl-font-weight-bold; + } } .ide-commit-message-field { @@ -946,3 +975,121 @@ background: transparent; resize: none; } + +.ide-tree-header { + display: flex; + align-items: center; + padding: 10px 0; + margin-left: 10px; + margin-right: 10px; + border-bottom: 1px solid $white-dark; + + .ide-new-btn { + margin-left: auto; + } +} + +.ide-sidebar-branch-title { + font-weight: $gl-font-weight-normal; + + svg { + position: relative; + top: 3px; + margin-top: -1px; + } +} + +.commit-form-compact { + .btn { + margin-bottom: 8px; + } + + p { + margin-bottom: 0; + } +} + +.commit-form-slide-up-enter-active, +.commit-form-slide-up-leave-active { + position: absolute; + top: 16px; + left: 16px; + right: 16px; + transition: all 0.3s ease; +} + +.is-full .commit-form-slide-up-enter, +.is-compact .commit-form-slide-up-leave-to { + transform: translateY(100%); +} + +.is-full .commit-form-slide-up-enter-to, +.is-compact .commit-form-slide-up-leave { + transform: translateY(0); +} + +.commit-form-slide-up-enter, +.commit-form-slide-up-leave-to { + opacity: 0; +} + +.ide-review-header { + flex-direction: column; + align-items: flex-start; + + .dropdown { + margin-left: auto; + } + + a { + color: $gl-link-color; + } +} + +.ide-review-sub-header { + color: $gl-text-color-secondary; +} + +.ide-tree-changes { + display: flex; + align-items: center; + font-size: 12px; +} + +.ide-new-modal-label { + line-height: 34px; +} + +.multi-file-commit-panel-success-message { + position: absolute; + top: 61px; + left: 1px; + bottom: 0; + right: 0; + z-index: 10; + background: $white-light; + overflow: auto; + display: flex; + flex-direction: column; + justify-content: center; +} + +.ide-review-button-holder { + display: flex; + width: 100%; + align-items: center; +} + +.ide-context-header { + .avatar { + flex: 0 0 40px; + } +} + +.ide-sidebar-project-title { + min-width: 0; + + .sidebar-context-title { + white-space: nowrap; + } +} diff --git a/app/assets/stylesheets/pages/repo.scss.orig b/app/assets/stylesheets/pages/repo.scss.orig deleted file mode 100644 index 57b995adb64..00000000000 --- a/app/assets/stylesheets/pages/repo.scss.orig +++ /dev/null @@ -1,786 +0,0 @@ -.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; - } -} diff --git a/app/assets/stylesheets/performance_bar.scss b/app/assets/stylesheets/performance_bar.scss index 45ae94abaff..06ef58531d7 100644 --- a/app/assets/stylesheets/performance_bar.scss +++ b/app/assets/stylesheets/performance_bar.scss @@ -1,5 +1,4 @@ @import 'framework/variables'; -@import 'peek/views/performance_bar'; @import 'peek/views/rblineprof'; #js-peek { |