diff options
Diffstat (limited to 'app/assets')
122 files changed, 1699 insertions, 468 deletions
diff --git a/app/assets/javascripts/ajax_loading_spinner.js b/app/assets/javascripts/ajax_loading_spinner.js index 38a8317dbd7..8f5e2e545ec 100644 --- a/app/assets/javascripts/ajax_loading_spinner.js +++ b/app/assets/javascripts/ajax_loading_spinner.js @@ -10,7 +10,7 @@ class AjaxLoadingSpinner { e.target.setAttribute('disabled', ''); const iconElement = e.target.querySelector('i'); // get first fa- icon - const originalIcon = iconElement.className.match(/(fa-)([^\s]+)/g).first(); + const originalIcon = iconElement.className.match(/(fa-)([^\s]+)/g)[0]; iconElement.dataset.icon = originalIcon; AjaxLoadingSpinner.toggleLoadingIcon(iconElement); $(e.target).off('ajax:beforeSend', AjaxLoadingSpinner.ajaxBeforeSend); diff --git a/app/assets/javascripts/awards_handler.js b/app/assets/javascripts/awards_handler.js index 18cd04b176a..097f79a250a 100644 --- a/app/assets/javascripts/awards_handler.js +++ b/app/assets/javascripts/awards_handler.js @@ -1,6 +1,6 @@ /* eslint-disable class-methods-use-this */ /* global Flash */ - +import _ from 'underscore'; import Cookies from 'js-cookie'; const animationEndEventString = 'animationend webkitAnimationEnd MSAnimationEnd oAnimationEnd'; diff --git a/app/assets/javascripts/behaviors/requires_input.js b/app/assets/javascripts/behaviors/requires_input.js index b20d108aa25..035a7e5c431 100644 --- a/app/assets/javascripts/behaviors/requires_input.js +++ b/app/assets/javascripts/behaviors/requires_input.js @@ -1,3 +1,4 @@ +import _ from 'underscore'; import '../commons/bootstrap'; // Requires Input behavior @@ -48,7 +49,9 @@ function hideOrShowHelpBlock(form) { $(() => { const $form = $('form.js-requires-input'); - $form.requiresInput(); - hideOrShowHelpBlock($form); - $('.select2.js-select-namespace').change(() => hideOrShowHelpBlock($form)); + if ($form) { + $form.requiresInput(); + hideOrShowHelpBlock($form); + $('.select2.js-select-namespace').change(() => hideOrShowHelpBlock($form)); + } }); diff --git a/app/assets/javascripts/behaviors/toggler_behavior.js b/app/assets/javascripts/behaviors/toggler_behavior.js index 77e92ff8caf..b70b0a9bbf8 100644 --- a/app/assets/javascripts/behaviors/toggler_behavior.js +++ b/app/assets/javascripts/behaviors/toggler_behavior.js @@ -1,4 +1,3 @@ - // Toggle button. Show/hide content inside parent container. // Button does not change visibility. If button has icon - it changes chevron style. // diff --git a/app/assets/javascripts/blob_edit/blob_bundle.js b/app/assets/javascripts/blob_edit/blob_bundle.js index 1c64ccf536f..b5500ac116f 100644 --- a/app/assets/javascripts/blob_edit/blob_bundle.js +++ b/app/assets/javascripts/blob_edit/blob_bundle.js @@ -8,6 +8,7 @@ import BlobFileDropzone from '../blob/blob_file_dropzone'; $(() => { const editBlobForm = $('.js-edit-blob-form'); const uploadBlobForm = $('.js-upload-blob-form'); + const deleteBlobForm = $('.js-delete-blob-form'); if (editBlobForm.length) { const urlRoot = editBlobForm.data('relative-url-root'); @@ -30,4 +31,8 @@ $(() => { '.btn-upload-file', ); } + + if (deleteBlobForm.length) { + new NewCommitForm(deleteBlobForm); + } }); diff --git a/app/assets/javascripts/boards/boards_bundle.js b/app/assets/javascripts/boards/boards_bundle.js index 88b054b76e6..89c14180149 100644 --- a/app/assets/javascripts/boards/boards_bundle.js +++ b/app/assets/javascripts/boards/boards_bundle.js @@ -2,6 +2,7 @@ /* global BoardService */ /* global Flash */ +import _ from 'underscore'; import Vue from 'vue'; import VueResource from 'vue-resource'; import FilteredSearchBoards from './filtered_search_boards'; diff --git a/app/assets/javascripts/boards/components/board_blank_state.js b/app/assets/javascripts/boards/components/board_blank_state.js index e7f16899362..edfe7c326db 100644 --- a/app/assets/javascripts/boards/components/board_blank_state.js +++ b/app/assets/javascripts/boards/components/board_blank_state.js @@ -1,5 +1,6 @@ /* global ListLabel */ +import _ from 'underscore'; import Cookies from 'js-cookie'; const Store = gl.issueBoards.BoardsStore; diff --git a/app/assets/javascripts/boards/components/modal/index.js b/app/assets/javascripts/boards/components/modal/index.js index 1d36519c75c..96af69e7a36 100644 --- a/app/assets/javascripts/boards/components/modal/index.js +++ b/app/assets/javascripts/boards/components/modal/index.js @@ -1,8 +1,8 @@ /* global ListIssue */ import Vue from 'vue'; -import queryData from '../../utils/query_data'; -import loadingIcon from '../../../vue_shared/components/loading_icon.vue'; +import queryData from '~/boards/utils/query_data'; +import loadingIcon from '~/vue_shared/components/loading_icon.vue'; import './header'; import './list'; import './footer'; diff --git a/app/assets/javascripts/boards/components/new_list_dropdown.js b/app/assets/javascripts/boards/components/new_list_dropdown.js index f29b6caa1ac..72bb9e10fbc 100644 --- a/app/assets/javascripts/boards/components/new_list_dropdown.js +++ b/app/assets/javascripts/boards/components/new_list_dropdown.js @@ -1,5 +1,6 @@ /* eslint-disable comma-dangle, func-names, no-new, space-before-function-paren, one-var, promise/catch-or-return */ +import _ from 'underscore'; window.gl = window.gl || {}; window.gl.issueBoards = window.gl.issueBoards || {}; diff --git a/app/assets/javascripts/boards/stores/boards_store.js b/app/assets/javascripts/boards/stores/boards_store.js index 1e12d4ca415..43928e602d6 100644 --- a/app/assets/javascripts/boards/stores/boards_store.js +++ b/app/assets/javascripts/boards/stores/boards_store.js @@ -1,6 +1,6 @@ /* eslint-disable comma-dangle, space-before-function-paren, one-var, no-shadow, dot-notation, max-len */ /* global List */ - +import _ from 'underscore'; import Cookies from 'js-cookie'; window.gl = window.gl || {}; diff --git a/app/assets/javascripts/build.js b/app/assets/javascripts/build.js index 1dfa064acfd..b3d3bbcf84f 100644 --- a/app/assets/javascripts/build.js +++ b/app/assets/javascripts/build.js @@ -64,7 +64,7 @@ window.Build = (function () { $(window) .off('scroll') .on('scroll', () => { - const contentHeight = this.$buildTraceOutput.prop('scrollHeight'); + const contentHeight = this.$buildTraceOutput.height(); if (contentHeight > this.windowSize) { // means the user did not scroll, the content was updated. this.windowSize = contentHeight; @@ -105,16 +105,17 @@ window.Build = (function () { }; Build.prototype.canScroll = function () { - return document.body.scrollHeight > window.innerHeight; + return $(document).height() > $(window).height(); }; Build.prototype.toggleScroll = function () { - const currentPosition = document.body.scrollTop; - const windowHeight = window.innerHeight; + const currentPosition = $(document).scrollTop(); + const scrollHeight = $(document).height(); + const windowHeight = $(window).height(); if (this.canScroll()) { if (currentPosition > 0 && - (document.body.scrollHeight - currentPosition !== windowHeight)) { + (scrollHeight - currentPosition !== windowHeight)) { // User is in the middle of the log this.toggleDisableButton(this.$scrollTopBtn, false); @@ -124,7 +125,7 @@ window.Build = (function () { this.toggleDisableButton(this.$scrollTopBtn, true); this.toggleDisableButton(this.$scrollBottomBtn, false); - } else if (document.body.scrollHeight - currentPosition === windowHeight) { + } else if (scrollHeight - currentPosition === windowHeight) { // User is at the bottom of the build log. this.toggleDisableButton(this.$scrollTopBtn, false); @@ -137,7 +138,7 @@ window.Build = (function () { }; Build.prototype.scrollDown = function () { - document.body.scrollTop = document.body.scrollHeight; + $(document).scrollTop($(document).height()); }; Build.prototype.scrollToBottom = function () { @@ -147,7 +148,7 @@ window.Build = (function () { }; Build.prototype.scrollToTop = function () { - document.body.scrollTop = 0; + $(document).scrollTop(0); this.hasBeenScrolled = true; this.toggleScroll(); }; @@ -178,7 +179,7 @@ window.Build = (function () { this.state = log.state; } - this.windowSize = this.$buildTraceOutput.prop('scrollHeight'); + this.windowSize = this.$buildTraceOutput.height(); if (log.append) { this.$buildTraceOutput.append(log.html); diff --git a/app/assets/javascripts/commons/bootstrap.js b/app/assets/javascripts/commons/bootstrap.js index 607d3d88df0..4b589b66dc3 100644 --- a/app/assets/javascripts/commons/bootstrap.js +++ b/app/assets/javascripts/commons/bootstrap.js @@ -9,6 +9,8 @@ import 'bootstrap-sass/assets/javascripts/bootstrap/modal'; import 'bootstrap-sass/assets/javascripts/bootstrap/tab'; import 'bootstrap-sass/assets/javascripts/bootstrap/transition'; import 'bootstrap-sass/assets/javascripts/bootstrap/tooltip'; +import 'bootstrap-sass/assets/javascripts/bootstrap/popover'; +import 'bootstrap-sass/assets/javascripts/bootstrap/button'; // custom jQuery functions $.fn.extend({ diff --git a/app/assets/javascripts/commons/index.js b/app/assets/javascripts/commons/index.js index 7063f59d446..6db8b3afbef 100644 --- a/app/assets/javascripts/commons/index.js +++ b/app/assets/javascripts/commons/index.js @@ -1,3 +1,4 @@ +import 'underscore'; import './polyfills'; import './jquery'; import './bootstrap'; diff --git a/app/assets/javascripts/copy_as_gfm.js b/app/assets/javascripts/copy_as_gfm.js index 54257531284..13ba4a57293 100644 --- a/app/assets/javascripts/copy_as_gfm.js +++ b/app/assets/javascripts/copy_as_gfm.js @@ -1,5 +1,5 @@ /* eslint-disable class-methods-use-this, object-shorthand, no-unused-vars, no-use-before-define, no-new, max-len, no-restricted-syntax, guard-for-in, no-continue */ - +import _ from 'underscore'; import './lib/utils/common_utils'; import { placeholderImage } from './lazy_loader'; diff --git a/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js b/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js index 44791a93936..6583e471a48 100644 --- a/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js +++ b/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js @@ -92,7 +92,7 @@ $(() => { }); }, selectDefaultStage() { - const stage = this.state.stages.first(); + const stage = this.state.stages[0]; this.selectStage(stage); }, selectStage(stage) { diff --git a/app/assets/javascripts/diff_notes/components/jump_to_discussion.js b/app/assets/javascripts/diff_notes/components/jump_to_discussion.js index 37ddca29e71..298f737a2bc 100644 --- a/app/assets/javascripts/diff_notes/components/jump_to_discussion.js +++ b/app/assets/javascripts/diff_notes/components/jump_to_discussion.js @@ -94,7 +94,7 @@ const JumpToDiscussion = Vue.extend({ hasDiscussionsToJumpTo = false; } } - } else if (activeTab !== 'notes') { + } else if (activeTab !== 'show') { // If we are on the commits or builds tabs, // there are no discussions to jump to. hasDiscussionsToJumpTo = false; @@ -103,12 +103,12 @@ const JumpToDiscussion = Vue.extend({ if (!hasDiscussionsToJumpTo) { // If there are no discussions to jump to on the current page, // switch to the notes tab and jump to the first disucssion there. - window.mrTabs.activateTab('notes'); - activeTab = 'notes'; + window.mrTabs.activateTab('show'); + activeTab = 'show'; jumpToFirstDiscussion = true; } - if (activeTab === 'notes') { + if (activeTab === 'show') { discussionsSelector = '.discussion[data-discussion-id]'; discussionIdsInScope = discussionIdsForElements($(discussionsSelector)); } @@ -156,7 +156,7 @@ const JumpToDiscussion = Vue.extend({ let $target = $(`${discussionsSelector}[data-discussion-id="${nextUnresolvedDiscussionId}"]`); - if (activeTab === 'notes') { + if (activeTab === 'show') { $target = $target.closest('.note-discussion'); // If the next discussion is closed, toggle it open. diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index ffe97c071ba..5630940f5bb 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -8,6 +8,8 @@ /* global LabelsSelect */ /* global MilestoneSelect */ /* global Commit */ +/* global CommitsList */ +/* global NewBranchForm */ /* global NotificationsForm */ /* global NotificationsDropdown */ /* global GroupAvatar */ @@ -18,13 +20,20 @@ /* global Search */ /* global Admin */ /* global NamespaceSelects */ +/* global NewCommitForm */ +/* global NewBranchForm */ /* global Project */ /* global ProjectAvatar */ +/* global MergeRequest */ +/* global Compare */ /* global CompareAutocomplete */ +/* global ProjectFindFile */ /* global ProjectNew */ /* global ProjectShow */ +/* global ProjectImport */ /* global Labels */ /* global Shortcuts */ +/* global ShortcutsFindFile */ /* global Sidebar */ /* global ShortcutsWiki */ @@ -62,14 +71,14 @@ import initSettingsPanels from './settings_panels'; import initExperimentalFlags from './experimental_flags'; import OAuthRememberMe from './oauth_remember_me'; import PerformanceBar from './performance_bar'; +import initNotes from './init_notes'; +import initLegacyFilters from './init_legacy_filters'; +import initIssuableSidebar from './init_issuable_sidebar'; +import GpgBadges from './gpg_badges'; (function() { var Dispatcher; - $(function() { - return new Dispatcher(); - }); - Dispatcher = (function() { function Dispatcher() { this.initSearch(); @@ -126,6 +135,8 @@ import PerformanceBar from './performance_bar'; .init(); } + const filteredSearchEnabled = gl.FilteredSearchManager && document.querySelector('.filtered-search'); + switch (page) { case 'profiles:preferences:show': initExperimentalFlags(); @@ -142,7 +153,7 @@ import PerformanceBar from './performance_bar'; break; case 'projects:merge_requests:index': case 'projects:issues:index': - if (gl.FilteredSearchManager && document.querySelector('.filtered-search')) { + if (filteredSearchEnabled) { const filteredSearchManager = new gl.FilteredSearchManager(page === 'projects:issues:index' ? 'issues' : 'merge_requests'); filteredSearchManager.setup(); } @@ -156,6 +167,8 @@ import PerformanceBar from './performance_bar'; new Issue(); shortcut_handler = new ShortcutsIssuable(); new ZenMode(); + initIssuableSidebar(); + initNotes(); break; case 'dashboard:milestones:index': new ProjectSelect(); @@ -166,9 +179,17 @@ import PerformanceBar from './performance_bar'; new Milestone(); new Sidebar(); break; - case 'groups:issues': + case 'dashboard:issues': + case 'dashboard:merge_requests': case 'groups:merge_requests': - new UsersSelect(); + new ProjectSelect(); + initLegacyFilters(); + break; + case 'groups:issues': + if (filteredSearchEnabled) { + const filteredSearchManager = new gl.FilteredSearchManager('issues'); + filteredSearchManager.setup(); + } new ProjectSelect(); break; case 'dashboard:todos:index': @@ -184,7 +205,6 @@ import PerformanceBar from './performance_bar'; break; case 'explore:groups:index': new GroupsList(); - const landingElement = document.querySelector('.js-explore-groups-landing'); if (!landingElement) break; const exploreGroupsLanding = new Landing( @@ -207,6 +227,10 @@ import PerformanceBar from './performance_bar'; case 'projects:compare:show': new gl.Diff(); break; + case 'projects:branches:new': + case 'projects:branches:create': + new NewBranchForm($('.js-create-branch-form'), JSON.parse(document.getElementById('availableRefs').innerHTML)); + break; case 'projects:branches:index': gl.AjaxLoadingSpinner.init(); new DeleteModal(); @@ -221,6 +245,19 @@ import PerformanceBar from './performance_bar'; new gl.IssuableTemplateSelectors(); break; case 'projects:merge_requests:creations:new': + const mrNewCompareNode = document.querySelector('.js-merge-request-new-compare'); + if (mrNewCompareNode) { + new Compare({ + targetProjectUrl: mrNewCompareNode.dataset.targetProjectUrl, + sourceBranchUrl: mrNewCompareNode.dataset.sourceBranchUrl, + targetBranchUrl: mrNewCompareNode.dataset.targetBranchUrl, + }); + } else { + const mrNewSubmitNode = document.querySelector('.js-merge-request-new-submit'); + new MergeRequest({ + action: mrNewSubmitNode.dataset.mrSubmitAction, + }); + } case 'projects:merge_requests:creations:diffs': case 'projects:merge_requests:edit': new gl.Diff(); @@ -235,7 +272,10 @@ import PerformanceBar from './performance_bar'; case 'projects:tags:new': new ZenMode(); new gl.GLForm($('.tag-form'), true); - new RefSelectDropdown($('.js-branch-select'), window.gl.availableRefs); + new RefSelectDropdown($('.js-branch-select')); + break; + case 'projects:snippets:show': + initNotes(); break; case 'projects:snippets:new': case 'projects:snippets:edit': @@ -257,15 +297,18 @@ import PerformanceBar from './performance_bar'; new gl.Diff(); shortcut_handler = new ShortcutsIssuable(true); new ZenMode(); + + initIssuableSidebar(); + initNotes(); + + const mrShowNode = document.querySelector('.merge-request'); + window.mergeRequest = new MergeRequest({ + action: mrShowNode.dataset.mrAction, + }); break; case 'dashboard:activity': new gl.Activities(); break; - case 'dashboard:issues': - case 'dashboard:merge_requests': - new ProjectSelect(); - new UsersSelect(); - break; case 'projects:commit:show': new Commit(); new gl.Diff(); @@ -274,15 +317,24 @@ import PerformanceBar from './performance_bar'; new MiniPipelineGraph({ container: '.js-commit-pipeline-graph', }).bindEvents(); + initNotes(); + $('.commit-info.branches').load(document.querySelector('.js-commit-box').dataset.commitPath); break; case 'projects:commit:pipelines': new MiniPipelineGraph({ container: '.js-commit-pipeline-graph', }).bindEvents(); + $('.commit-info.branches').load(document.querySelector('.js-commit-box').dataset.commitPath); break; - case 'projects:commits:show': case 'projects:activity': + new gl.Activities(); + shortcut_handler = new ShortcutsNavigation(); + break; + case 'projects:commits:show': + CommitsList.init(document.querySelector('.js-project-commits-show').dataset.commitsLimit); + new gl.Activities(); shortcut_handler = new ShortcutsNavigation(); + GpgBadges.fetch(); break; case 'projects:show': shortcut_handler = new ShortcutsNavigation(); @@ -296,6 +348,14 @@ import PerformanceBar from './performance_bar'; break; case 'projects:edit': setupProjectEdit(); + // Initialize expandable settings panels + initSettingsPanels(); + break; + case 'projects:imports:show': + new ProjectImport(); + break; + case 'projects:pipelines:new': + new NewBranchForm($('.js-new-pipeline-form')); break; case 'projects:pipelines:builds': case 'projects:pipelines:failures': @@ -350,8 +410,19 @@ import PerformanceBar from './performance_bar'; shortcut_handler = new ShortcutsNavigation(); new TreeView(); new BlobViewer(); + new NewCommitForm($('.js-create-dir-form')); + $('#tree-slider').waitForImages(function() { + gl.utils.ajaxGet(document.querySelector('.js-tree-content').dataset.logsPath); + }); break; case 'projects:find_file:show': + const findElement = document.querySelector('.js-file-finder'); + const projectFindFile = new ProjectFindFile($(".file-finder-holder"), { + url: findElement.dataset.fileFindUrl, + treeUrl: findElement.dataset.findTreeUrl, + blobUrlTemplate: findElement.dataset.blobUrlTemplate, + }); + new ShortcutsFindFile(projectFindFile); shortcut_handler = true; break; case 'projects:blob:show': @@ -367,10 +438,20 @@ import PerformanceBar from './performance_bar'; case 'projects:labels:edit': new Labels(); break; + case 'groups:labels:index': case 'projects:labels:index': if ($('.prioritized-labels').length) { new gl.LabelManager(); } + $('.label-subscription').each((i, el) => { + const $el = $(el); + + if ($el.find('.dropdown-group-label').length) { + new gl.GroupLabelSubscription($el); + } else { + new gl.ProjectLabelSubscription($el); + } + }); break; case 'projects:network:show': // Ensure we don't create a particular shortcut handler here. This is @@ -415,12 +496,17 @@ import PerformanceBar from './performance_bar'; case 'snippets:show': new LineHighlighter(); new BlobViewer(); + initNotes(); break; case 'import:fogbugz:new_user_map': new UsersSelect(); break; + case 'profiles:personal_access_tokens:index': + case 'admin:impersonation_tokens:index': + new gl.DueDateSelectors(); + break; } - switch (path.first()) { + switch (path[0]) { case 'sessions': case 'omniauth_callbacks': if (!gon.u2f) break; @@ -492,6 +578,7 @@ import PerformanceBar from './performance_bar'; shortcut_handler = new ShortcutsWiki(); new ZenMode(); new gl.GLForm($('.wiki-form'), true); + new Sidebar(); break; case 'snippets': shortcut_handler = new ShortcutsNavigation(); @@ -515,6 +602,13 @@ import PerformanceBar from './performance_bar'; case 'protected_branches': shortcut_handler = new ShortcutsNavigation(); } + break; + case 'users': + const action = path[1]; + import(/* webpackChunkName: 'user_profile' */ './users') + .then(user => user.default(action)) + .catch(() => {}); + break; } // If we haven't installed a custom shortcut handler, install the default one if (!shortcut_handler) { @@ -541,4 +635,8 @@ import PerformanceBar from './performance_bar'; return Dispatcher; })(); + + $(function() { + new Dispatcher(); + }); }).call(window); diff --git a/app/assets/javascripts/droplab/plugins/ajax.js b/app/assets/javascripts/droplab/plugins/ajax.js index c0da5866139..267b53fa4f2 100644 --- a/app/assets/javascripts/droplab/plugins/ajax.js +++ b/app/assets/javascripts/droplab/plugins/ajax.js @@ -11,6 +11,16 @@ const Ajax = { if (!self.destroyed) self.hook.list[config.method].call(self.hook.list, data); }, + preprocessing: function preprocessing(config, data) { + let results = data; + + if (config.preprocessing && !data.preprocessed) { + results = config.preprocessing(data); + AjaxCache.override(config.endpoint, results); + } + + return results; + }, init: function init(hook) { var self = this; self.destroyed = false; @@ -31,7 +41,8 @@ const Ajax = { dynamicList.outerHTML = loadingTemplate.outerHTML; } - AjaxCache.retrieve(config.endpoint) + return AjaxCache.retrieve(config.endpoint) + .then(self.preprocessing.bind(null, config)) .then((data) => self._loadData(data, config, self)) .catch(config.onError); }, diff --git a/app/assets/javascripts/dropzone_input.js b/app/assets/javascripts/dropzone_input.js index 9ebbb22e807..6d19a6d9b3a 100644 --- a/app/assets/javascripts/dropzone_input.js +++ b/app/assets/javascripts/dropzone_input.js @@ -1,11 +1,10 @@ /* eslint-disable func-names, space-before-function-paren, wrap-iife, max-len, one-var, no-var, one-var-declaration-per-line, no-unused-vars, camelcase, quotes, no-useless-concat, prefer-template, quote-props, comma-dangle, object-shorthand, consistent-return, prefer-arrow-callback */ /* global Dropzone */ - +import _ from 'underscore'; import './preview_markdown'; window.DropzoneInput = (function() { function DropzoneInput(form) { - Dropzone.autoDiscover = false; const divHover = '<div class="div-dropzone-hover"></div>'; const iconPaperclip = '<i class="fa fa-paperclip div-dropzone-icon"></i>'; const $attachButton = form.find('.button-attach-file'); @@ -218,7 +217,7 @@ window.DropzoneInput = (function() { value = e.clipboardData.getData('text/plain'); } value = value.split("\r"); - return value.first(); + return value[0]; }; const showSpinner = function(e) { diff --git a/app/assets/javascripts/emoji/index.js b/app/assets/javascripts/emoji/index.js index cac35d6eed5..dc7672560ea 100644 --- a/app/assets/javascripts/emoji/index.js +++ b/app/assets/javascripts/emoji/index.js @@ -1,3 +1,4 @@ +import _ from 'underscore'; import emojiMap from 'emojis/digests.json'; import emojiAliases from 'emojis/aliases.json'; diff --git a/app/assets/javascripts/extensions/array.js b/app/assets/javascripts/extensions/array.js deleted file mode 100644 index 027222f804d..00000000000 --- a/app/assets/javascripts/extensions/array.js +++ /dev/null @@ -1,11 +0,0 @@ -// TODO: remove this - -// eslint-disable-next-line no-extend-native -Array.prototype.first = function first() { - return this[0]; -}; - -// eslint-disable-next-line no-extend-native -Array.prototype.last = function last() { - return this[this.length - 1]; -}; diff --git a/app/assets/javascripts/filterable_list.js b/app/assets/javascripts/filterable_list.js index 139206cc185..6d516a253bb 100644 --- a/app/assets/javascripts/filterable_list.js +++ b/app/assets/javascripts/filterable_list.js @@ -1,3 +1,5 @@ +import _ from 'underscore'; + /** * Makes search request for content when user types a value in the search input. * Updates the html content of the page with the received one. diff --git a/app/assets/javascripts/filtered_search/dropdown_non_user.js b/app/assets/javascripts/filtered_search/dropdown_non_user.js index 2615d626c4c..0bc4b6f22a9 100644 --- a/app/assets/javascripts/filtered_search/dropdown_non_user.js +++ b/app/assets/javascripts/filtered_search/dropdown_non_user.js @@ -6,7 +6,7 @@ import './filtered_search_dropdown'; class DropdownNonUser extends gl.FilteredSearchDropdown { constructor(options = {}) { - const { input, endpoint, symbol } = options; + const { input, endpoint, symbol, preprocessing } = options; super(options); this.symbol = symbol; this.config = { @@ -14,6 +14,7 @@ class DropdownNonUser extends gl.FilteredSearchDropdown { endpoint, method: 'setData', loadingTemplate: this.loadingTemplate, + preprocessing, onError() { /* eslint-disable no-new */ new Flash('An error occured fetching the dropdown data.'); diff --git a/app/assets/javascripts/filtered_search/dropdown_utils.js b/app/assets/javascripts/filtered_search/dropdown_utils.js index ef8fe071012..8d711e3213c 100644 --- a/app/assets/javascripts/filtered_search/dropdown_utils.js +++ b/app/assets/javascripts/filtered_search/dropdown_utils.js @@ -1,3 +1,4 @@ +import _ from 'underscore'; import FilteredSearchContainer from './container'; class DropdownUtils { @@ -50,6 +51,66 @@ class DropdownUtils { return updatedItem; } + static mergeDuplicateLabels(dataMap, newLabel) { + const updatedMap = dataMap; + const key = newLabel.title; + + const hasKeyProperty = Object.prototype.hasOwnProperty.call(updatedMap, key); + + if (!hasKeyProperty) { + updatedMap[key] = newLabel; + } else { + const existing = updatedMap[key]; + + if (!existing.multipleColors) { + existing.multipleColors = [existing.color]; + } + + existing.multipleColors.push(newLabel.color); + } + + return updatedMap; + } + + static duplicateLabelColor(labelColors) { + const colors = labelColors; + const spacing = 100 / colors.length; + + // Reduce the colors to 4 + colors.length = Math.min(colors.length, 4); + + const color = colors.map((c, i) => { + const percentFirst = Math.floor(spacing * i); + const percentSecond = Math.floor(spacing * (i + 1)); + return `${c} ${percentFirst}%, ${c} ${percentSecond}%`; + }).join(', '); + + return `linear-gradient(${color})`; + } + + static duplicateLabelPreprocessing(data) { + const results = []; + const dataMap = {}; + + data.forEach(DropdownUtils.mergeDuplicateLabels.bind(null, dataMap)); + + Object.keys(dataMap) + .forEach((key) => { + const label = dataMap[key]; + + if (label.multipleColors) { + label.color = DropdownUtils.duplicateLabelColor(label.multipleColors); + label.text_color = '#000000'; + } + + results.push(label); + }); + + results.preprocessed = true; + + return results; + } + static filterHint(config, item) { const { input, allowedKeys } = config; const updatedItem = item; @@ -62,11 +123,11 @@ class DropdownUtils { if (!allowMultiple && itemInExistingTokens) { updatedItem.droplab_hidden = true; - } else if (!lastKey || searchInput.split('').last() === ' ') { + } else if (!lastKey || _.last(searchInput.split('')) === ' ') { updatedItem.droplab_hidden = false; } else if (lastKey) { const split = lastKey.split(':'); - const tokenName = split[0].split(' ').last(); + const tokenName = _.last(split[0].split(' ')); const match = updatedItem.hint.indexOf(tokenName.toLowerCase()) === -1; updatedItem.droplab_hidden = tokenName ? match : false; diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js index 61cef435209..dd1c067df87 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js +++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js @@ -54,6 +54,7 @@ class FilteredSearchDropdownManager { extraArguments: { endpoint: `${this.baseEndpoint}/labels.json`, symbol: '~', + preprocessing: gl.DropdownUtils.duplicateLabelPreprocessing, }, element: this.container.querySelector('#js-dropdown-label'), }, @@ -166,7 +167,7 @@ class FilteredSearchDropdownManager { // Eg. token = 'label:' const split = lastToken.split(':'); - const dropdownName = split[0].split(' ').last(); + const dropdownName = _.last(split[0].split(' ')); this.loadDropdown(split.length > 1 ? dropdownName : ''); } else if (lastToken) { // Token has been initialized into an object because it has a value diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js b/app/assets/javascripts/filtered_search/filtered_search_manager.js index 7872e9e68ad..a31be2b0bc7 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_manager.js +++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js @@ -20,13 +20,13 @@ class FilteredSearchManager { allowedKeys: this.filteredSearchTokenKeys.getKeys(), }); this.searchHistoryDropdownElement = document.querySelector('.js-filtered-search-history-dropdown'); - const projectPath = this.searchHistoryDropdownElement ? - this.searchHistoryDropdownElement.dataset.projectFullPath : 'project'; + const fullPath = this.searchHistoryDropdownElement ? + this.searchHistoryDropdownElement.dataset.fullPath : 'project'; let recentSearchesPagePrefix = 'issue-recent-searches'; if (this.page === 'merge_requests') { recentSearchesPagePrefix = 'merge-request-recent-searches'; } - const recentSearchesKey = `${projectPath}-${recentSearchesPagePrefix}`; + const recentSearchesKey = `${fullPath}-${recentSearchesPagePrefix}`; this.recentSearchesService = new RecentSearchesService(recentSearchesKey); } @@ -367,7 +367,7 @@ class FilteredSearchManager { const fragments = searchToken.split(':'); if (fragments.length > 1) { const inputValues = fragments[0].split(' '); - const tokenKey = inputValues.last(); + const tokenKey = _.last(inputValues); if (inputValues.length > 1) { inputValues.pop(); diff --git a/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js b/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js index e9278140af0..243ee4d723a 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js +++ b/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js @@ -58,29 +58,54 @@ class FilteredSearchVisualTokens { `; } + static setTokenStyle(tokenContainer, backgroundColor, textColor) { + const token = tokenContainer; + + // Labels with linear gradient should not override default background color + if (backgroundColor.indexOf('linear-gradient') === -1) { + token.style.backgroundColor = backgroundColor; + } + + token.style.color = textColor; + + if (textColor === '#FFFFFF') { + const removeToken = token.querySelector('.remove-token'); + removeToken.classList.add('inverted'); + } + + return token; + } + + static preprocessLabel(labelsEndpoint, labels) { + let processed = labels; + + if (!labels.preprocessed) { + processed = gl.DropdownUtils.duplicateLabelPreprocessing(labels); + AjaxCache.override(labelsEndpoint, processed); + processed.preprocessed = true; + } + + return processed; + } + static updateLabelTokenColor(tokenValueContainer, tokenValue) { const filteredSearchInput = FilteredSearchContainer.container.querySelector('.filtered-search'); const baseEndpoint = filteredSearchInput.dataset.baseEndpoint; const labelsEndpoint = `${baseEndpoint}/labels.json`; return AjaxCache.retrieve(labelsEndpoint) - .then((labels) => { - const matchingLabel = (labels || []).find(label => `~${gl.DropdownUtils.getEscapedText(label.title)}` === tokenValue); - - if (!matchingLabel) { - return; - } + .then(FilteredSearchVisualTokens.preprocessLabel.bind(null, labelsEndpoint)) + .then((labels) => { + const matchingLabel = (labels || []).find(label => `~${gl.DropdownUtils.getEscapedText(label.title)}` === tokenValue); - const tokenValueStyle = tokenValueContainer.style; - tokenValueStyle.backgroundColor = matchingLabel.color; - tokenValueStyle.color = matchingLabel.text_color; + if (!matchingLabel) { + return; + } - if (matchingLabel.text_color === '#FFFFFF') { - const removeToken = tokenValueContainer.querySelector('.remove-token'); - removeToken.classList.add('inverted'); - } - }) - .catch(() => new Flash('An error occurred while fetching label colors.')); + FilteredSearchVisualTokens + .setTokenStyle(tokenValueContainer, matchingLabel.color, matchingLabel.text_color); + }) + .catch(() => new Flash('An error occurred while fetching label colors.')); } static updateUserTokenAppearance(tokenValueContainer, tokenValueElement, tokenValue) { diff --git a/app/assets/javascripts/fly_out_nav.js b/app/assets/javascripts/fly_out_nav.js new file mode 100644 index 00000000000..8e9a97fe207 --- /dev/null +++ b/app/assets/javascripts/fly_out_nav.js @@ -0,0 +1,51 @@ +/* global bp */ +import './breakpoints'; + +export const canShowSubItems = () => bp.getBreakpointSize() === 'md' || bp.getBreakpointSize() === 'lg'; + +export const calculateTop = (boundingRect, outerHeight) => { + const windowHeight = window.innerHeight; + const bottomOverflow = windowHeight - (boundingRect.top + outerHeight); + + return bottomOverflow < 0 ? (boundingRect.top - outerHeight) + boundingRect.height : + boundingRect.top; +}; + +export const showSubLevelItems = (el) => { + const subItems = el.querySelector('.sidebar-sub-level-items'); + + if (!subItems || !canShowSubItems()) return; + + subItems.style.display = 'block'; + el.classList.add('is-over'); + + const boundingRect = el.getBoundingClientRect(); + const top = calculateTop(boundingRect, subItems.offsetHeight); + const isAbove = top < boundingRect.top; + + subItems.style.transform = `translate3d(0, ${Math.floor(top)}px, 0)`; + + if (isAbove) { + subItems.classList.add('is-above'); + } +}; + +export const hideSubLevelItems = (el) => { + const subItems = el.querySelector('.sidebar-sub-level-items'); + + if (!subItems || !canShowSubItems()) return; + + el.classList.remove('is-over'); + subItems.style.display = 'none'; + subItems.classList.remove('is-above'); +}; + +export default () => { + const items = [...document.querySelectorAll('.sidebar-top-level-items > li:not(.active)')] + .filter(el => el.querySelector('.sidebar-sub-level-items')); + + items.forEach((el) => { + el.addEventListener('mouseenter', e => showSubLevelItems(e.currentTarget)); + el.addEventListener('mouseleave', e => hideSubLevelItems(e.currentTarget)); + }); +}; diff --git a/app/assets/javascripts/gfm_auto_complete.js b/app/assets/javascripts/gfm_auto_complete.js index 6cb9cfe1382..5c624b79d45 100644 --- a/app/assets/javascripts/gfm_auto_complete.js +++ b/app/assets/javascripts/gfm_auto_complete.js @@ -1,3 +1,4 @@ +import _ from 'underscore'; import glRegexp from './lib/utils/regexp'; import AjaxCache from './lib/utils/ajax_cache'; diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js index 3babe273100..7d11cd0b6b2 100644 --- a/app/assets/javascripts/gl_dropdown.js +++ b/app/assets/javascripts/gl_dropdown.js @@ -1,5 +1,6 @@ /* eslint-disable func-names, space-before-function-paren, no-var, one-var, one-var-declaration-per-line, prefer-rest-params, max-len, vars-on-top, wrap-iife, no-unused-vars, quotes, no-shadow, no-cond-assign, prefer-arrow-callback, no-return-assign, no-else-return, camelcase, comma-dangle, no-lonely-if, guard-for-in, no-restricted-syntax, consistent-return, prefer-template, no-param-reassign, no-loop-func, no-mixed-operators */ /* global fuzzaldrinPlus */ +import _ from 'underscore'; import { isObject } from './lib/utils/type_utility'; var GitLabDropdown, GitLabDropdownFilter, GitLabDropdownRemote; @@ -114,7 +115,7 @@ GitLabDropdownFilter = (function() { } else { elements = this.options.elements(); if (search_text) { - return elements.each(function() { + elements.each(function() { var $el, matches; $el = $(this); matches = fuzzaldrinPlus.match($el.text().trim(), search_text); @@ -127,8 +128,10 @@ GitLabDropdownFilter = (function() { } }); } else { - return elements.show().removeClass('option-hidden'); + elements.show().removeClass('option-hidden'); } + + elements.parent().find('.dropdown-menu-empty-link').toggleClass('hidden', elements.is(':visible')); } }; @@ -730,10 +733,16 @@ GitLabDropdown = (function() { GitLabDropdown.prototype.focusTextInput = function(triggerFocus = false) { if (this.options.filterable) { - $(':focus').blur(); - this.dropdown.one('transitionend', () => { - this.filterInput.focus(); + const initialScrollTop = $(window).scrollTop(); + + if (this.dropdown.is('.open')) { + this.filterInput.focus(); + } + + if ($(window).scrollTop() < initialScrollTop) { + $(window).scrollTop(initialScrollTop); + } }); if (triggerFocus) { diff --git a/app/assets/javascripts/gpg_badges.js b/app/assets/javascripts/gpg_badges.js new file mode 100644 index 00000000000..1c379e9bb67 --- /dev/null +++ b/app/assets/javascripts/gpg_badges.js @@ -0,0 +1,15 @@ +export default class GpgBadges { + static fetch() { + const form = $('.commits-search-form'); + + $.get({ + url: form.data('signatures-path'), + data: form.serialize(), + }).done((response) => { + const badges = $('.js-loading-gpg-badge'); + response.signatures.forEach((signature) => { + badges.filter(`[data-commit-sha="${signature.commit_sha}"]`).replaceWith(signature.html); + }); + }); + } +} diff --git a/app/assets/javascripts/graphs/graphs_bundle.js b/app/assets/javascripts/graphs/graphs_bundle.js index a433c7ba8f0..534bc535bb6 100644 --- a/app/assets/javascripts/graphs/graphs_bundle.js +++ b/app/assets/javascripts/graphs/graphs_bundle.js @@ -1,6 +1,4 @@ import Chart from 'vendor/Chart'; -import ContributorsStatGraph from './stat_graph_contributors'; // export to global scope window.Chart = Chart; -window.ContributorsStatGraph = ContributorsStatGraph; diff --git a/app/assets/javascripts/graphs/graphs_charts.js b/app/assets/javascripts/graphs/graphs_charts.js new file mode 100644 index 00000000000..279ffef770f --- /dev/null +++ b/app/assets/javascripts/graphs/graphs_charts.js @@ -0,0 +1,63 @@ +import Chart from 'vendor/Chart'; + +document.addEventListener('DOMContentLoaded', () => { + const projectChartData = JSON.parse(document.getElementById('projectChartData').innerHTML); + + const responsiveChart = (selector, data) => { + const options = { + scaleOverlay: true, + responsive: true, + pointHitDetectionRadius: 2, + maintainAspectRatio: false, + }; + // get selector by context + const ctx = selector.get(0).getContext('2d'); + // pointing parent container to make chart.js inherit its width + const container = $(selector).parent(); + const generateChart = () => { + selector.attr('width', $(container).width()); + if (window.innerWidth < 768) { + // Scale fonts if window width lower than 768px (iPad portrait) + options.scaleFontSize = 8; + } + return new Chart(ctx).Bar(data, options); + }; + // enabling auto-resizing + $(window).resize(generateChart); + return generateChart(); + }; + + const chartData = (keys, values) => { + const data = { + labels: keys, + datasets: [{ + fillColor: 'rgba(220,220,220,0.5)', + strokeColor: 'rgba(220,220,220,1)', + barStrokeWidth: 1, + barValueSpacing: 1, + barDatasetSpacing: 1, + data: values, + }], + }; + return data; + }; + + const hourData = chartData(projectChartData.hour.keys, projectChartData.hour.values); + responsiveChart($('#hour-chart'), hourData); + + const dayData = chartData(projectChartData.weekDays.keys, projectChartData.weekDays.values); + responsiveChart($('#weekday-chart'), dayData); + + const monthData = chartData(projectChartData.month.keys, projectChartData.month.values); + responsiveChart($('#month-chart'), monthData); + + const data = projectChartData.languages; + const ctx = $('#languages-chart').get(0).getContext('2d'); + const options = { + scaleOverlay: true, + responsive: true, + maintainAspectRatio: false, + }; + + new Chart(ctx).Pie(data, options); +}); diff --git a/app/assets/javascripts/graphs/graphs_show.js b/app/assets/javascripts/graphs/graphs_show.js new file mode 100644 index 00000000000..36bad6db3e1 --- /dev/null +++ b/app/assets/javascripts/graphs/graphs_show.js @@ -0,0 +1,21 @@ +import ContributorsStatGraph from './stat_graph_contributors'; + +document.addEventListener('DOMContentLoaded', () => { + $.ajax({ + type: 'GET', + url: document.querySelector('.js-graphs-show').dataset.projectGraphPath, + dataType: 'json', + success(data) { + const graph = new ContributorsStatGraph(); + graph.init(data); + + $('#brush_change').change(() => { + graph.change_date_header(); + graph.redraw_authors(); + }); + + $('.stat-graph').fadeIn(); + $('.loading-graph').hide(); + }, + }); +}); diff --git a/app/assets/javascripts/graphs/stat_graph_contributors.js b/app/assets/javascripts/graphs/stat_graph_contributors.js index c6be4c9e8fe..cdc4fcf6573 100644 --- a/app/assets/javascripts/graphs/stat_graph_contributors.js +++ b/app/assets/javascripts/graphs/stat_graph_contributors.js @@ -1,5 +1,6 @@ /* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, one-var, camelcase, one-var-declaration-per-line, quotes, no-param-reassign, quote-props, comma-dangle, prefer-template, max-len, no-return-assign, no-shadow */ +import _ from 'underscore'; import d3 from 'd3'; import { ContributorsGraph, ContributorsAuthorGraph, ContributorsMasterGraph } from './stat_graph_contributors_graph'; import ContributorsStatGraphUtil from './stat_graph_contributors_util'; diff --git a/app/assets/javascripts/graphs/stat_graph_contributors_graph.js b/app/assets/javascripts/graphs/stat_graph_contributors_graph.js index 0deb27e522b..f64b4638485 100644 --- a/app/assets/javascripts/graphs/stat_graph_contributors_graph.js +++ b/app/assets/javascripts/graphs/stat_graph_contributors_graph.js @@ -1,5 +1,5 @@ /* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, max-len, no-restricted-syntax, vars-on-top, no-use-before-define, no-param-reassign, new-cap, no-underscore-dangle, wrap-iife, comma-dangle, no-return-assign, prefer-arrow-callback, quotes, prefer-template, newline-per-chained-call, no-else-return, no-shadow */ - +import _ from 'underscore'; import d3 from 'd3'; const extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; diff --git a/app/assets/javascripts/graphs/stat_graph_contributors_util.js b/app/assets/javascripts/graphs/stat_graph_contributors_util.js index c583757f3f2..77135ad1f0e 100644 --- a/app/assets/javascripts/graphs/stat_graph_contributors_util.js +++ b/app/assets/javascripts/graphs/stat_graph_contributors_util.js @@ -1,4 +1,5 @@ /* eslint-disable func-names, space-before-function-paren, object-shorthand, no-var, one-var, camelcase, one-var-declaration-per-line, comma-dangle, no-param-reassign, no-return-assign, quotes, prefer-arrow-callback, wrap-iife, consistent-return, no-unused-vars, max-len, no-cond-assign, no-else-return, max-len */ +import _ from 'underscore'; export default { parse_log: function(log) { diff --git a/app/assets/javascripts/groups/components/group_identicon.vue b/app/assets/javascripts/groups/components/group_identicon.vue new file mode 100644 index 00000000000..0edd820743f --- /dev/null +++ b/app/assets/javascripts/groups/components/group_identicon.vue @@ -0,0 +1,45 @@ +<script> +export default { + props: { + entityId: { + type: Number, + required: true, + }, + entityName: { + type: String, + required: true, + }, + }, + computed: { + /** + * This method is based on app/helpers/application_helper.rb#project_identicon + */ + identiconStyles() { + const allowedColors = [ + '#FFEBEE', + '#F3E5F5', + '#E8EAF6', + '#E3F2FD', + '#E0F2F1', + '#FBE9E7', + '#EEEEEE', + ]; + + const backgroundColor = allowedColors[this.entityId % 7]; + + return `background-color: ${backgroundColor}; color: #555;`; + }, + identiconTitle() { + return this.entityName.charAt(0).toUpperCase(); + }, + }, +}; +</script> + +<template> + <div + class="avatar s40 identicon" + :style="identiconStyles"> + {{identiconTitle}} + </div> +</template> diff --git a/app/assets/javascripts/groups/components/group_item.vue b/app/assets/javascripts/groups/components/group_item.vue index b1db34b9c50..cb133cf7535 100644 --- a/app/assets/javascripts/groups/components/group_item.vue +++ b/app/assets/javascripts/groups/components/group_item.vue @@ -1,7 +1,11 @@ <script> import eventHub from '../event_hub'; +import groupIdenticon from './group_identicon.vue'; export default { + components: { + groupIdenticon, + }, props: { group: { type: Object, @@ -92,6 +96,9 @@ export default { hasGroups() { return Object.keys(this.group.subGroups).length > 0; }, + hasAvatar() { + return this.group.avatarUrl && this.group.avatarUrl.indexOf('/assets/no_group_avatar') === -1; + }, }, }; </script> @@ -194,9 +201,15 @@ export default { <a :href="group.groupPath"> <img + v-if="hasAvatar" class="avatar s40" :src="group.avatarUrl" /> + <group-identicon + v-else + :entity-id=group.id + :entity-name="group.name" + /> </a> </div> <div diff --git a/app/assets/javascripts/how_to_merge.js b/app/assets/javascripts/how_to_merge.js index f739db751a6..19f4a946f73 100644 --- a/app/assets/javascripts/how_to_merge.js +++ b/app/assets/javascripts/how_to_merge.js @@ -3,10 +3,10 @@ document.addEventListener('DOMContentLoaded', () => { modal: true, show: false, }); - $('.how_to_merge_link').bind('click', () => { + $('.how_to_merge_link').on('click', () => { modal.show(); }); - $('.modal-header .close').bind('click', () => { + $('.modal-header .close').on('click', () => { modal.hide(); }); }); diff --git a/app/assets/javascripts/init_issuable_sidebar.js b/app/assets/javascripts/init_issuable_sidebar.js new file mode 100644 index 00000000000..29e3d2ea94e --- /dev/null +++ b/app/assets/javascripts/init_issuable_sidebar.js @@ -0,0 +1,18 @@ +/* eslint-disable no-new */ +/* global MilestoneSelect */ +/* global LabelsSelect */ +/* global IssuableContext */ +/* global Sidebar */ + +export default () => { + const sidebarOptions = JSON.parse(document.querySelector('.js-sidebar-options').innerHTML); + + new MilestoneSelect({ + full_path: sidebarOptions.fullPath, + }); + new LabelsSelect(); + new IssuableContext(sidebarOptions.currentUser); + gl.Subscription.bindAll('.subscription'); + new gl.DueDateSelectors(); + window.sidebar = new Sidebar(); +}; diff --git a/app/assets/javascripts/init_legacy_filters.js b/app/assets/javascripts/init_legacy_filters.js new file mode 100644 index 00000000000..1211c2c802c --- /dev/null +++ b/app/assets/javascripts/init_legacy_filters.js @@ -0,0 +1,15 @@ +/* eslint-disable no-new */ +/* global LabelsSelect */ +/* global MilestoneSelect */ +/* global IssueStatusSelect */ +/* global SubscriptionSelect */ + +import UsersSelect from './users_select'; + +export default () => { + new UsersSelect(); + new LabelsSelect(); + new MilestoneSelect(); + new IssueStatusSelect(); + new SubscriptionSelect(); +}; diff --git a/app/assets/javascripts/init_notes.js b/app/assets/javascripts/init_notes.js new file mode 100644 index 00000000000..3a8b4360cb6 --- /dev/null +++ b/app/assets/javascripts/init_notes.js @@ -0,0 +1,14 @@ +/* global Notes */ + +export default () => { + const dataEl = document.querySelector('.js-notes-data'); + const { + notesUrl, + notesIds, + now, + diffView, + autocomplete, + } = JSON.parse(dataEl.innerHTML); + + window.notes = new Notes(notesUrl, notesIds, now, diffView, autocomplete); +}; diff --git a/app/assets/javascripts/integrations/integration_settings_form.js b/app/assets/javascripts/integrations/integration_settings_form.js index ddd3a6aab99..cf1e6a14725 100644 --- a/app/assets/javascripts/integrations/integration_settings_form.js +++ b/app/assets/javascripts/integrations/integration_settings_form.js @@ -102,7 +102,7 @@ export default class IntegrationSettingsForm { }) .done((res) => { if (res.error) { - new Flash(res.message, null, null, { + new Flash(`${res.message} ${res.service_response}`, null, null, { title: 'Save anyway', clickHandler: (e) => { e.preventDefault(); diff --git a/app/assets/javascripts/issuable_bulk_update_actions.js b/app/assets/javascripts/issuable_bulk_update_actions.js index e46c0e90255..c39ffdb2e0f 100644 --- a/app/assets/javascripts/issuable_bulk_update_actions.js +++ b/app/assets/javascripts/issuable_bulk_update_actions.js @@ -1,6 +1,7 @@ /* eslint-disable comma-dangle, quotes, consistent-return, func-names, array-callback-return, space-before-function-paren, prefer-arrow-callback, max-len, no-unused-expressions, no-sequences, no-underscore-dangle, no-unused-vars, no-param-reassign */ /* global IssuableIndex */ /* global Flash */ +import _ from 'underscore'; export default { init({ container, form, issues, prefixId } = {}) { diff --git a/app/assets/javascripts/issuable_context.js b/app/assets/javascripts/issuable_context.js index a4d7bf096ef..26392db4b5b 100644 --- a/app/assets/javascripts/issuable_context.js +++ b/app/assets/javascripts/issuable_context.js @@ -4,6 +4,8 @@ import Cookies from 'js-cookie'; import UsersSelect from './users_select'; +const PARTICIPANTS_ROW_COUNT = 7; + (function() { this.IssuableContext = (function() { function IssuableContext(currentUser) { @@ -50,11 +52,9 @@ import UsersSelect from './users_select'; } IssuableContext.prototype.initParticipants = function() { - var _this; - _this = this; $(document).on("click", ".js-participants-more", this.toggleHiddenParticipants); return $(".js-participants-author").each(function(i) { - if (i >= _this.PARTICIPANTS_ROW_COUNT) { + if (i >= PARTICIPANTS_ROW_COUNT) { return $(this).addClass("js-participants-hidden").hide(); } }); diff --git a/app/assets/javascripts/issuable_index.js b/app/assets/javascripts/issuable_index.js index 5c96646def8..ece0220c927 100644 --- a/app/assets/javascripts/issuable_index.js +++ b/app/assets/javascripts/issuable_index.js @@ -1,6 +1,6 @@ /* eslint-disable no-param-reassign, func-names, no-var, camelcase, no-unused-vars, object-shorthand, space-before-function-paren, no-return-assign, comma-dangle, consistent-return, one-var, one-var-declaration-per-line, quotes, prefer-template, prefer-arrow-callback, wrap-iife, max-len */ /* global IssuableIndex */ - +import _ from 'underscore'; import IssuableBulkUpdateSidebar from './issuable_bulk_update_sidebar'; import IssuableBulkUpdateActions from './issuable_bulk_update_actions'; diff --git a/app/assets/javascripts/labels_select.js b/app/assets/javascripts/labels_select.js index 8d7d3d73571..7d7f91227f9 100644 --- a/app/assets/javascripts/labels_select.js +++ b/app/assets/javascripts/labels_select.js @@ -1,8 +1,9 @@ /* eslint-disable no-useless-return, func-names, space-before-function-paren, wrap-iife, no-var, no-underscore-dangle, prefer-arrow-callback, max-len, one-var, no-unused-vars, one-var-declaration-per-line, prefer-template, no-new, consistent-return, object-shorthand, comma-dangle, no-shadow, no-param-reassign, brace-style, vars-on-top, quotes, no-lonely-if, no-else-return, dot-notation, no-empty, no-return-assign, camelcase, prefer-spread */ /* global Issuable */ /* global ListLabel */ - +import _ from 'underscore'; import IssuableBulkUpdateActions from './issuable_bulk_update_actions'; +import DropdownUtils from './filtered_search/dropdown_utils'; (function() { this.LabelsSelect = (function() { @@ -218,18 +219,7 @@ import IssuableBulkUpdateActions from './issuable_bulk_update_actions'; } } if (label.duplicate) { - spacing = 100 / label.color.length; - // Reduce the colors to 4 - label.color = label.color.filter(function(color, i) { - return i < 4; - }); - color = _.map(label.color, function(color, i) { - var percentFirst, percentSecond; - percentFirst = Math.floor(spacing * i); - percentSecond = Math.floor(spacing * (i + 1)); - return color + " " + percentFirst + "%," + color + " " + percentSecond + "% "; - }).join(','); - color = "linear-gradient(" + color + ")"; + color = gl.DropdownUtils.duplicateLabelColor(label.color); } else { if (label.color != null) { diff --git a/app/assets/javascripts/layout_nav.js b/app/assets/javascripts/layout_nav.js index 6186ffe20b3..5c1ba416a03 100644 --- a/app/assets/javascripts/layout_nav.js +++ b/app/assets/javascripts/layout_nav.js @@ -2,6 +2,7 @@ import _ from 'underscore'; import Cookies from 'js-cookie'; import NewNavSidebar from './new_sidebar'; +import initFlyOutNav from './fly_out_nav'; (function() { var hideEndFade; @@ -58,6 +59,8 @@ import NewNavSidebar from './new_sidebar'; if (Cookies.get('new_nav') === 'true') { const newNavSidebar = new NewNavSidebar(); newNavSidebar.bindEvents(); + + initFlyOutNav(); } $(window).on('scroll', _.throttle(applyScrollNavClass, 100)); diff --git a/app/assets/javascripts/lib/utils/ajax_cache.js b/app/assets/javascripts/lib/utils/ajax_cache.js index 7477b5a5214..629d8f44e18 100644 --- a/app/assets/javascripts/lib/utils/ajax_cache.js +++ b/app/assets/javascripts/lib/utils/ajax_cache.js @@ -6,6 +6,10 @@ class AjaxCache extends Cache { this.pendingRequests = { }; } + override(endpoint, data) { + this.internalStorage[endpoint] = data; + } + retrieve(endpoint, forceRetrieve) { if (this.hasData(endpoint) && !forceRetrieve) { return Promise.resolve(this.get(endpoint)); diff --git a/app/assets/javascripts/lib/utils/common_utils.js b/app/assets/javascripts/lib/utils/common_utils.js index 122ec138c59..e916724b666 100644 --- a/app/assets/javascripts/lib/utils/common_utils.js +++ b/app/assets/javascripts/lib/utils/common_utils.js @@ -86,8 +86,9 @@ // This is required to handle non-unicode characters in hash hash = decodeURIComponent(hash); - var fixedTabs = document.querySelector('.js-tabs-affix'); - var fixedNav = document.querySelector('.navbar-gitlab'); + const fixedTabs = document.querySelector('.js-tabs-affix'); + const fixedDiffStats = document.querySelector('.js-diff-files-changed.is-stuck'); + const fixedNav = document.querySelector('.navbar-gitlab'); var adjustment = 0; if (fixedNav) adjustment -= fixedNav.offsetHeight; @@ -104,6 +105,11 @@ if (fixedTabs) { adjustment -= fixedTabs.offsetHeight; } + + if (fixedDiffStats) { + adjustment -= fixedDiffStats.offsetHeight; + } + window.scrollBy(0, adjustment); } }; diff --git a/app/assets/javascripts/lib/utils/pretty_time.js b/app/assets/javascripts/lib/utils/pretty_time.js index ae397212e55..716aefbfcb7 100644 --- a/app/assets/javascripts/lib/utils/pretty_time.js +++ b/app/assets/javascripts/lib/utils/pretty_time.js @@ -1,3 +1,5 @@ +import _ from 'underscore'; + (() => { /* * TODO: Make these methods more configurable (e.g. parseSeconds timePeriodContstraints, diff --git a/app/assets/javascripts/lib/utils/sticky.js b/app/assets/javascripts/lib/utils/sticky.js new file mode 100644 index 00000000000..43a808b6ab3 --- /dev/null +++ b/app/assets/javascripts/lib/utils/sticky.js @@ -0,0 +1,23 @@ +export const isSticky = (el, scrollY, stickyTop) => { + const top = el.offsetTop - scrollY; + + if (top === stickyTop) { + el.classList.add('is-stuck'); + } else { + el.classList.remove('is-stuck'); + } +}; + +export default (el) => { + if (!el) return; + + const computedStyle = window.getComputedStyle(el); + + if (!/sticky/.test(computedStyle.position)) return; + + const stickyTop = parseInt(computedStyle.top, 10); + + document.addEventListener('scroll', () => isSticky(el, window.scrollY, stickyTop), { + passive: true, + }); +}; diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js index e96d51de838..42092a34c2f 100644 --- a/app/assets/javascripts/main.js +++ b/app/assets/javascripts/main.js @@ -16,9 +16,6 @@ import 'mousetrap'; import 'mousetrap/plugins/pause/mousetrap-pause'; import 'vendor/fuzzaldrin-plus'; -// extensions -import './extensions/array'; - // expose common libraries as globals (TODO: remove these) window.jQuery = jQuery; window.$ = jQuery; @@ -36,9 +33,6 @@ import './shortcuts_find_file'; import './shortcuts_issuable'; import './shortcuts_network'; -// behaviors -import './behaviors/'; - // templates import './templates/issuable_template_selector'; import './templates/issuable_template_selectors'; @@ -56,6 +50,9 @@ import './lib/utils/pretty_time'; import './lib/utils/text_utility'; import './lib/utils/url_utility'; +// behaviors +import './behaviors/'; + // u2f import './u2f/authenticate'; import './u2f/error'; @@ -86,7 +83,6 @@ import './copy_as_gfm'; import './copy_to_clipboard'; import './create_label'; import './diff'; -import './dispatcher'; import './dropzone_input'; import './due_date_select'; import './files_comment_button'; @@ -145,20 +141,25 @@ import './right_sidebar'; import './search'; import './search_autocomplete'; import './smart_interval'; -import './snippets_list'; import './star'; import './subscription'; import './subscription_select'; import './syntax_highlight'; +import './dispatcher'; + // eslint-disable-next-line global-require, import/no-commonjs if (process.env.NODE_ENV !== 'production') require('./test_utils/'); +Dropzone.autoDiscover = false; + document.addEventListener('beforeunload', function () { // Unbind scroll events $(document).off('scroll'); // Close any open tooltips $('.has-tooltip, [data-toggle="tooltip"]').tooltip('destroy'); + // Close any open popover + $('[data-toggle="popover"]').popover('destroy'); }); window.addEventListener('hashchange', gl.utils.handleLocationHash); @@ -247,6 +248,11 @@ $(function () { return $(el).data('placement') || 'bottom'; } }); + // Initialize popovers + $body.popover({ + selector: '[data-toggle="popover"]', + trigger: 'focus' + }); $('.trigger-submit').on('change', function () { return $(this).parents('form').submit(); // Form submitter @@ -347,4 +353,14 @@ $(function () { gl.utils.renderTimeago(); $(document).trigger('init.scrolling-tabs'); + + $('form.filter-form').on('submit', function (event) { + const link = document.createElement('a'); + link.href = this.action; + + const action = `${this.action}${link.search === '' ? '?' : '&'}`; + + event.preventDefault(); + gl.utils.visitUrl(`${action}${$(this).serialize()}`); + }); }); diff --git a/app/assets/javascripts/merge_conflicts/merge_conflict_store.js b/app/assets/javascripts/merge_conflicts/merge_conflict_store.js index c4e379a4a0b..8be7314ded8 100644 --- a/app/assets/javascripts/merge_conflicts/merge_conflict_store.js +++ b/app/assets/javascripts/merge_conflicts/merge_conflict_store.js @@ -175,7 +175,7 @@ import Cookies from 'js-cookie'; getConflictsCountText() { const count = this.getConflictsCount(); - const text = count ? 'conflicts' : 'conflict'; + const text = count > 1 ? 'conflicts' : 'conflict'; return `${count} ${text}`; }, diff --git a/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js b/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js index 17030c3e4d3..d74cf5328ad 100644 --- a/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js +++ b/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js @@ -2,6 +2,7 @@ /* global Flash */ import Vue from 'vue'; +import initIssuableSidebar from '../init_issuable_sidebar'; import './merge_conflict_store'; import './merge_conflict_service'; import './mixins/line_conflict_utils'; @@ -19,6 +20,8 @@ $(() => { resolveConflictsPath: conflictsEl.dataset.resolveConflictsPath }); + initIssuableSidebar(); + gl.MergeConflictsResolverApp = new Vue({ el: '#conflicts', data: mergeConflictsStore.state, diff --git a/app/assets/javascripts/merge_request_tabs.js b/app/assets/javascripts/merge_request_tabs.js index 7840f05a8ae..4ffd71d9de5 100644 --- a/app/assets/javascripts/merge_request_tabs.js +++ b/app/assets/javascripts/merge_request_tabs.js @@ -7,6 +7,7 @@ import Cookies from 'js-cookie'; import './breakpoints'; import './flash'; import BlobForkSuggestion from './blob/blob_fork_suggestion'; +import stickyMonitor from './lib/utils/sticky'; /* eslint-disable max-len */ // MergeRequestTabs @@ -266,6 +267,10 @@ import BlobForkSuggestion from './blob/blob_fork_suggestion'; const $container = $('#diffs'); $container.html(data.html); + this.initChangesDropdown(); + + stickyMonitor(document.querySelector('.js-diff-files-changed')); + if (typeof gl.diffNotesCompileComponents !== 'undefined') { gl.diffNotesCompileComponents(); } @@ -314,6 +319,13 @@ import BlobForkSuggestion from './blob/blob_fork_suggestion'; }); } + initChangesDropdown() { + $('.js-diff-stats-dropdown').glDropdown({ + filterable: true, + remoteFilter: false, + }); + } + // Show or hide the loading spinner // // status - Boolean, true to show, false to hide diff --git a/app/assets/javascripts/milestone_select.js b/app/assets/javascripts/milestone_select.js index 9d481d7c003..04579058688 100644 --- a/app/assets/javascripts/milestone_select.js +++ b/app/assets/javascripts/milestone_select.js @@ -1,6 +1,7 @@ /* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-underscore-dangle, prefer-arrow-callback, max-len, one-var, one-var-declaration-per-line, no-unused-vars, object-shorthand, comma-dangle, no-else-return, no-self-compare, consistent-return, no-param-reassign, no-shadow */ /* global Issuable */ /* global ListMilestone */ +import _ from 'underscore'; (function() { this.MilestoneSelect = (function() { @@ -8,7 +9,7 @@ var _this, $els; if (currentProject != null) { _this = this; - this.currentProject = JSON.parse(currentProject); + this.currentProject = typeof currentProject === 'string' ? JSON.parse(currentProject) : currentProject; } $els = $(els); diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js index b2c503d1656..b38a6abc8d1 100644 --- a/app/assets/javascripts/notes.js +++ b/app/assets/javascripts/notes.js @@ -11,6 +11,7 @@ newline-per-chained-call, no-useless-escape, class-methods-use-this */ /* global mrRefreshWidgetUrl */ import $ from 'jquery'; +import _ from 'underscore'; import Cookies from 'js-cookie'; import autosize from 'vendor/autosize'; import Dropzone from 'dropzone'; @@ -529,6 +530,7 @@ export default class Notes { form.find('#note_line_code').remove(); form.find('#note_position').remove(); form.find('#note_type').val(''); + form.find('#note_project_id').remove(); form.find('#in_reply_to_discussion_id').remove(); form.find('.js-comment-resolve-button').closest('comment-and-resolve-btn').remove(); this.parentTimeline = form.parents('.timeline'); @@ -556,6 +558,7 @@ export default class Notes { form.find('#note_noteable_id').val(), form.find('#note_commit_id').val(), form.find('#note_type').val(), + form.find('#note_project_id').val(), form.find('#in_reply_to_discussion_id').val(), // LegacyDiffNote @@ -848,6 +851,8 @@ export default class Notes { form.find('#in_reply_to_discussion_id').val(discussionID); } + form.find('#note_project_id').val(dataHolder.data('discussionProjectId')); + form.attr('data-line-code', dataHolder.data('lineCode')); form.find('#line_type').val(dataHolder.data('lineType')); diff --git a/app/assets/javascripts/pdf/index.vue b/app/assets/javascripts/pdf/index.vue index 4603859d7b0..b874e484d45 100644 --- a/app/assets/javascripts/pdf/index.vue +++ b/app/assets/javascripts/pdf/index.vue @@ -9,8 +9,8 @@ </template> <script> - import pdfjsLib from 'pdfjs-dist'; - import workerSrc from 'vendor/pdf.worker'; + import pdfjsLib from 'vendor/pdf'; + import workerSrc from 'vendor/pdf.worker.min'; import page from './page/index.vue'; diff --git a/app/assets/javascripts/pipeline_schedules/components/interval_pattern_input.vue b/app/assets/javascripts/pipeline_schedules/components/interval_pattern_input.vue index ce46b3fa3fa..b5d85299cf8 100644 --- a/app/assets/javascripts/pipeline_schedules/components/interval_pattern_input.vue +++ b/app/assets/javascripts/pipeline_schedules/components/interval_pattern_input.vue @@ -1,4 +1,6 @@ <script> + import _ from 'underscore'; + export default { props: { initialCronInterval: { diff --git a/app/assets/javascripts/pipelines/components/graph/graph_component.vue b/app/assets/javascripts/pipelines/components/graph/graph_component.vue index 77cbaeb43ef..66bc1d1979c 100644 --- a/app/assets/javascripts/pipelines/components/graph/graph_component.vue +++ b/app/assets/javascripts/pipelines/components/graph/graph_component.vue @@ -1,7 +1,7 @@ <script> + import loadingIcon from '~/vue_shared/components/loading_icon.vue'; + import '~/flash'; import stageColumnComponent from './stage_column_component.vue'; - import loadingIcon from '../../../vue_shared/components/loading_icon.vue'; - import '../../../flash'; export default { props: { diff --git a/app/assets/javascripts/pipelines/pipelines_charts.js b/app/assets/javascripts/pipelines/pipelines_charts.js new file mode 100644 index 00000000000..001faf4be33 --- /dev/null +++ b/app/assets/javascripts/pipelines/pipelines_charts.js @@ -0,0 +1,38 @@ +import Chart from 'vendor/Chart'; + +document.addEventListener('DOMContentLoaded', () => { + const chartData = JSON.parse(document.getElementById('pipelinesChartsData').innerHTML); + const buildChart = (chartScope) => { + const data = { + labels: chartScope.labels, + datasets: [{ + fillColor: '#7f8fa4', + strokeColor: '#7f8fa4', + pointColor: '#7f8fa4', + pointStrokeColor: '#EEE', + data: chartScope.totalValues, + }, + { + fillColor: '#44aa22', + strokeColor: '#44aa22', + pointColor: '#44aa22', + pointStrokeColor: '#fff', + data: chartScope.successValues, + }, + ], + }; + const ctx = $(`#${chartScope.scope}Chart`).get(0).getContext('2d'); + const options = { + scaleOverlay: true, + responsive: true, + maintainAspectRatio: false, + }; + if (window.innerWidth < 768) { + // Scale fonts if window width lower than 768px (iPad portrait) + options.scaleFontSize = 8; + } + new Chart(ctx).Line(data, options); + }; + + chartData.forEach(scope => buildChart(scope)); +}); diff --git a/app/assets/javascripts/pipelines/pipelines_times.js b/app/assets/javascripts/pipelines/pipelines_times.js new file mode 100644 index 00000000000..b5e7a0e53d9 --- /dev/null +++ b/app/assets/javascripts/pipelines/pipelines_times.js @@ -0,0 +1,27 @@ +import Chart from 'vendor/Chart'; + +document.addEventListener('DOMContentLoaded', () => { + const chartData = JSON.parse(document.getElementById('pipelinesTimesChartsData').innerHTML); + const data = { + labels: chartData.labels, + datasets: [{ + fillColor: 'rgba(220,220,220,0.5)', + strokeColor: 'rgba(220,220,220,1)', + barStrokeWidth: 1, + barValueSpacing: 1, + barDatasetSpacing: 1, + data: chartData.values, + }], + }; + const ctx = $('#build_timesChart').get(0).getContext('2d'); + const options = { + scaleOverlay: true, + responsive: true, + maintainAspectRatio: false, + }; + if (window.innerWidth < 768) { + // Scale fonts if window width lower than 768px (iPad portrait) + options.scaleFontSize = 8; + } + new Chart(ctx).Bar(data, options); +}); diff --git a/app/assets/javascripts/profile/gl_crop.js b/app/assets/javascripts/profile/gl_crop.js index cf1566eeb87..291ae24aa68 100644 --- a/app/assets/javascripts/profile/gl_crop.js +++ b/app/assets/javascripts/profile/gl_crop.js @@ -1,6 +1,7 @@ /* eslint-disable no-useless-escape, max-len, quotes, no-var, no-underscore-dangle, func-names, space-before-function-paren, no-unused-vars, no-return-assign, object-shorthand, one-var, one-var-declaration-per-line, comma-dangle, consistent-return, class-methods-use-this, new-parens */ -import 'vendor/cropper'; +import 'cropper'; +import _ from 'underscore'; ((global) => { // Matches everything but the file name diff --git a/app/assets/javascripts/project.js b/app/assets/javascripts/project.js index 738e710deb9..6e1744e8e72 100644 --- a/app/assets/javascripts/project.js +++ b/app/assets/javascripts/project.js @@ -6,21 +6,27 @@ import Cookies from 'js-cookie'; (function() { this.Project = (function() { function Project() { - $('ul.clone-options-dropdown a').click(function() { - var url; - if ($(this).hasClass('active')) { - return; - } - $('.active').not($(this)).removeClass('active'); - $(this).toggleClass('active'); - url = $("#project_clone").val(); - $('#project_clone').val(url); + const $cloneOptions = $('ul.clone-options-dropdown'); + const $projectCloneField = $('#project_clone'); + const $cloneBtnText = $('a.clone-dropdown-btn span'); + + const selectedCloneOption = $cloneBtnText.text().trim(); + if (selectedCloneOption.length > 0) { + $(`a:contains('${selectedCloneOption}')`, $cloneOptions).addClass('is-active'); + } + + $('a', $cloneOptions).on('click', (e) => { + const $this = $(e.currentTarget); + const url = $this.attr('href'); + + e.preventDefault(); + + $('.is-active', $cloneOptions).not($this).removeClass('is-active'); + $this.toggleClass('is-active'); + $projectCloneField.val(url); + $cloneBtnText.text($this.text()); + return $('.clone').text(url); - // Git protocol switcher - // Remove the active class for all buttons (ssh, http, kerberos if shown) - // Add the active class for the clicked button - // Update the input field - // Update the command line instructions }); // Ref switcher this.initRefSwitcher(); diff --git a/app/assets/javascripts/project_edit.js b/app/assets/javascripts/project_edit.js index d7d284b6c86..7572fec15e0 100644 --- a/app/assets/javascripts/project_edit.js +++ b/app/assets/javascripts/project_edit.js @@ -1,6 +1,6 @@ export default function setupProjectEdit() { const $transferForm = $('.js-project-transfer-form'); - const $selectNamespace = $transferForm.find('.select2'); + const $selectNamespace = $transferForm.find('select.select2'); $selectNamespace.on('change', () => { $transferForm.find(':submit').prop('disabled', !$selectNamespace.val()); diff --git a/app/assets/javascripts/projects/project_import_gitlab_project.js b/app/assets/javascripts/projects/project_import_gitlab_project.js new file mode 100644 index 00000000000..c34927499fc --- /dev/null +++ b/app/assets/javascripts/projects/project_import_gitlab_project.js @@ -0,0 +1,14 @@ +import '../lib/utils/url_utility'; + +const bindEvents = () => { + const path = gl.utils.getParameterValues('path')[0]; + + // get the path url and append it in the inputS + $('.js-path-name').val(path); +}; + +document.addEventListener('DOMContentLoaded', bindEvents); + +export default { + bindEvents, +}; diff --git a/app/assets/javascripts/projects/project_new.js b/app/assets/javascripts/projects/project_new.js new file mode 100644 index 00000000000..985521aef34 --- /dev/null +++ b/app/assets/javascripts/projects/project_new.js @@ -0,0 +1,67 @@ +let hasUserDefinedProjectPath = false; + +const deriveProjectPathFromUrl = ($projectImportUrl, $projectPath) => { + if (hasUserDefinedProjectPath) { + return; + } + + let importUrl = $projectImportUrl.val().trim(); + if (importUrl.length === 0) { + return; + } + + /* + \/?: remove trailing slash + (\.git\/?)?: remove trailing .git (with optional trailing slash) + (\?.*)?: remove query string + (#.*)?: remove fragment identifier + */ + importUrl = importUrl.replace(/\/?(\.git\/?)?(\?.*)?(#.*)?$/, ''); + + // extract everything after the last slash + const pathMatch = /\/([^/]+)$/.exec(importUrl); + if (pathMatch) { + $projectPath.val(pathMatch[1]); + } +}; + +const bindEvents = () => { + const $newProjectForm = $('#new_project'); + const $projectImportUrl = $('#project_import_url'); + const $projectPath = $('#project_path'); + + if ($newProjectForm.length !== 1) { + return; + } + + $('.how_to_import_link').on('click', (e) => { + e.preventDefault(); + $('.how_to_import_link').next('.modal').show(); + }); + + $('.modal-header .close').on('click', () => { + $('.modal').hide(); + }); + + $('.btn_import_gitlab_project').on('click', () => { + const importHref = $('a.btn_import_gitlab_project').attr('href'); + $('.btn_import_gitlab_project').attr('href', `${importHref}?namespace_id=${$('#project_namespace_id').val()}&path=${$projectPath.val()}`); + }); + + $newProjectForm.on('submit', () => { + $projectPath.val($projectPath.val().trim()); + }); + + $projectPath.on('keyup', () => { + hasUserDefinedProjectPath = $projectPath.val().trim().length > 0; + }); + + $projectImportUrl.keyup(() => deriveProjectPathFromUrl($projectImportUrl, $projectPath)); +}; + +document.addEventListener('DOMContentLoaded', bindEvents); + +export default { + bindEvents, + deriveProjectPathFromUrl, +}; diff --git a/app/assets/javascripts/protected_branches/protected_branch_dropdown.js b/app/assets/javascripts/protected_branches/protected_branch_dropdown.js index cc0b2ebe071..678882a8d2c 100644 --- a/app/assets/javascripts/protected_branches/protected_branch_dropdown.js +++ b/app/assets/javascripts/protected_branches/protected_branch_dropdown.js @@ -1,3 +1,5 @@ +import _ from 'underscore'; + export default class ProtectedBranchDropdown { /** * @param {Object} options containing diff --git a/app/assets/javascripts/protected_tags/protected_tag_dropdown.js b/app/assets/javascripts/protected_tags/protected_tag_dropdown.js index 9d045886262..a0224213aa0 100644 --- a/app/assets/javascripts/protected_tags/protected_tag_dropdown.js +++ b/app/assets/javascripts/protected_tags/protected_tag_dropdown.js @@ -1,3 +1,5 @@ +import _ from 'underscore'; + export default class ProtectedTagDropdown { /** * @param {Object} options containing diff --git a/app/assets/javascripts/ref_select_dropdown.js b/app/assets/javascripts/ref_select_dropdown.js index 215cd6fbdfd..65e4101352c 100644 --- a/app/assets/javascripts/ref_select_dropdown.js +++ b/app/assets/javascripts/ref_select_dropdown.js @@ -1,7 +1,8 @@ class RefSelectDropdown { constructor($dropdownButton, availableRefs) { + const availableRefsValue = availableRefs || JSON.parse(document.getElementById('availableRefs').innerHTML); $dropdownButton.glDropdown({ - data: availableRefs, + data: availableRefsValue, filterable: true, filterByText: true, remote: false, diff --git a/app/assets/javascripts/right_sidebar.js b/app/assets/javascripts/right_sidebar.js index d8f1fe10b26..fa958d75fa4 100644 --- a/app/assets/javascripts/right_sidebar.js +++ b/app/assets/javascripts/right_sidebar.js @@ -1,5 +1,6 @@ /* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, no-unused-vars, consistent-return, one-var, one-var-declaration-per-line, quotes, prefer-template, object-shorthand, comma-dangle, no-else-return, no-param-reassign, max-len */ +import _ from 'underscore'; import Cookies from 'js-cookie'; import SidebarHeightManager from './sidebar_height_manager'; diff --git a/app/assets/javascripts/shortcuts_issuable.js b/app/assets/javascripts/shortcuts_issuable.js index 51448252c0f..0be141eb5f9 100644 --- a/app/assets/javascripts/shortcuts_issuable.js +++ b/app/assets/javascripts/shortcuts_issuable.js @@ -3,6 +3,7 @@ /* global ShortcutsNavigation */ /* global sidebar */ +import _ from 'underscore'; import 'mousetrap'; import './shortcuts_navigation'; @@ -58,7 +59,7 @@ import './shortcuts_navigation'; }); // If replyField already has some content, add a newline before our quote separator = replyField.val().trim() !== "" && "\n\n" || ''; - replyField.val(function(_, current) { + replyField.val(function(a, current) { return current + separator + quote.join('') + "\n"; }); 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.js index 650e935b116..2d682215cf8 100644 --- a/app/assets/javascripts/sidebar/components/time_tracking/sidebar_time_tracking.js +++ b/app/assets/javascripts/sidebar/components/time_tracking/sidebar_time_tracking.js @@ -1,3 +1,5 @@ +import _ from 'underscore'; + import '~/smart_interval'; import timeTracker from './time_tracker'; diff --git a/app/assets/javascripts/sidebar/sidebar_bundle.js b/app/assets/javascripts/sidebar/sidebar_bundle.js index 2b02af87d8a..a9df66748c5 100644 --- a/app/assets/javascripts/sidebar/sidebar_bundle.js +++ b/app/assets/javascripts/sidebar/sidebar_bundle.js @@ -5,7 +5,8 @@ import sidebarAssignees from './components/assignees/sidebar_assignees'; import Mediator from './sidebar_mediator'; function domContentLoaded() { - const mediator = new Mediator(gl.sidebarOptions); + const sidebarOptions = JSON.parse(document.querySelector('.js-sidebar-options').innerHTML); + const mediator = new Mediator(sidebarOptions); mediator.fetch(); const sidebarAssigneesEl = document.querySelector('#js-vue-sidebar-assignees'); diff --git a/app/assets/javascripts/sidebar_height_manager.js b/app/assets/javascripts/sidebar_height_manager.js index 022415f22b2..df19d7305f8 100644 --- a/app/assets/javascripts/sidebar_height_manager.js +++ b/app/assets/javascripts/sidebar_height_manager.js @@ -1,3 +1,5 @@ +import _ from 'underscore'; + export default { init() { if (!this.initialized) { @@ -30,4 +32,3 @@ export default { } }, }; - diff --git a/app/assets/javascripts/snippets_list.js b/app/assets/javascripts/snippets_list.js deleted file mode 100644 index 3b6d999b1c3..00000000000 --- a/app/assets/javascripts/snippets_list.js +++ /dev/null @@ -1,9 +0,0 @@ -function SnippetsList() { - const $holder = $('.snippets-list-holder'); - - $holder.find('.pagination').on('ajax:success', (e, data) => { - $holder.replaceWith(data.html); - }); -} - -window.gl.SnippetsList = SnippetsList; diff --git a/app/assets/javascripts/todos.js b/app/assets/javascripts/todos.js index cd305631c10..a606852c22c 100644 --- a/app/assets/javascripts/todos.js +++ b/app/assets/javascripts/todos.js @@ -37,10 +37,6 @@ export default class Todos { this.initFilterDropdown($('.js-type-search'), 'type'); this.initFilterDropdown($('.js-action-search'), 'action_id'); - $('form.filter-form').on('submit', function applyFilters(event) { - event.preventDefault(); - gl.utils.visitUrl(`${this.action}&${$(this).serialize()}`); - }); return new UsersSelect(); } @@ -56,6 +52,7 @@ export default class Todos { } updateRowStateClicked(e) { + e.stopPropagation(); e.preventDefault(); const target = e.target; @@ -96,6 +93,7 @@ export default class Todos { } updateAllStateClicked(e) { + e.stopPropagation(); e.preventDefault(); const target = e.currentTarget; @@ -146,6 +144,7 @@ export default class Todos { if (gl.utils.isMetaClick(e)) { const windowTarget = '_blank'; const selected = e.target; + e.stopPropagation(); e.preventDefault(); if (selected.tagName === 'IMG') { diff --git a/app/assets/javascripts/two_factor_auth.js b/app/assets/javascripts/two_factor_auth.js new file mode 100644 index 00000000000..d26f61562a5 --- /dev/null +++ b/app/assets/javascripts/two_factor_auth.js @@ -0,0 +1,13 @@ +/* global U2FRegister */ +document.addEventListener('DOMContentLoaded', () => { + const twoFactorNode = document.querySelector('.js-two-factor-auth'); + const skippable = twoFactorNode.dataset.twoFactorSkippable === 'true'; + if (skippable) { + const button = `<a class="btn btn-xs btn-warning pull-right" data-method="patch" href="${twoFactorNode.dataset.two_factor_skip_url}">Configure it later</a>`; + const flashAlert = document.querySelector('.flash-alert .container-fluid'); + if (flashAlert) flashAlert.insertAdjacentHTML('beforeend', button); + } + + const u2fRegister = new U2FRegister($('#js-register-u2f'), gon.u2f); + u2fRegister.start(); +}); diff --git a/app/assets/javascripts/u2f/authenticate.js b/app/assets/javascripts/u2f/authenticate.js index cd5280948fd..8821b22477f 100644 --- a/app/assets/javascripts/u2f/authenticate.js +++ b/app/assets/javascripts/u2f/authenticate.js @@ -3,6 +3,8 @@ /* global U2FError */ /* global U2FUtil */ +import _ from 'underscore'; + // Authenticate U2F (universal 2nd factor) devices for users to authenticate with. // // State Flow #1: setup -> in_progress -> authenticated -> POST to server diff --git a/app/assets/javascripts/u2f/register.js b/app/assets/javascripts/u2f/register.js index 1234d17b8fd..3a2534d553b 100644 --- a/app/assets/javascripts/u2f/register.js +++ b/app/assets/javascripts/u2f/register.js @@ -3,6 +3,8 @@ /* global U2FError */ /* global U2FUtil */ +import _ from 'underscore'; + // Register U2F (universal 2nd factor) devices for users to authenticate with. // // State Flow #1: setup -> in_progress -> registered -> POST to server diff --git a/app/assets/javascripts/ui_development_kit.js b/app/assets/javascripts/ui_development_kit.js new file mode 100644 index 00000000000..f503076715c --- /dev/null +++ b/app/assets/javascripts/ui_development_kit.js @@ -0,0 +1,22 @@ +import Api from './api'; + +document.addEventListener('DOMContentLoaded', () => { + $('#js-project-dropdown').glDropdown({ + data: (term, callback) => { + Api.projects(term, { + order_by: 'last_activity_at', + }, (data) => { + callback(data); + }); + }, + text: project => (project.name_with_namespace || project.name), + selectable: true, + fieldName: 'author_id', + filterable: true, + search: { + fields: ['name_with_namespace'], + }, + id: data => data.id, + isSelected: data => (data.id === 2), + }); +}); diff --git a/app/assets/javascripts/username_validator.js b/app/assets/javascripts/username_validator.js index a348d69153c..bb34d5d2008 100644 --- a/app/assets/javascripts/username_validator.js +++ b/app/assets/javascripts/username_validator.js @@ -1,5 +1,7 @@ /* eslint-disable comma-dangle, consistent-return, class-methods-use-this, arrow-parens, no-param-reassign, max-len */ +import _ from 'underscore'; + const debounceTimeoutDuration = 1000; const invalidInputClass = 'gl-field-error-outline'; const successInputClass = 'gl-field-success-outline'; diff --git a/app/assets/javascripts/users/activity_calendar.js b/app/assets/javascripts/users/activity_calendar.js index b7f50cfd083..5e947769f8a 100644 --- a/app/assets/javascripts/users/activity_calendar.js +++ b/app/assets/javascripts/users/activity_calendar.js @@ -1,10 +1,37 @@ -/* eslint-disable func-names, space-before-function-paren, no-var, wrap-iife, camelcase, vars-on-top, object-shorthand, comma-dangle, eqeqeq, no-mixed-operators, no-return-assign, newline-per-chained-call, prefer-arrow-callback, consistent-return, one-var, one-var-declaration-per-line, prefer-template, quotes, no-unused-vars, no-else-return, max-len, class-methods-use-this */ - +import _ from 'underscore'; import d3 from 'd3'; +const LOADING_HTML = ` + <div class="text-center"> + <i class="fa fa-spinner fa-spin user-calendar-activities-loading"></i> + </div> +`; + +function getSystemDate(systemUtcOffsetSeconds) { + const date = new Date(); + const localUtcOffsetMinutes = 0 - date.getTimezoneOffset(); + const systemUtcOffsetMinutes = systemUtcOffsetSeconds / 60; + date.setMinutes((date.getMinutes() - localUtcOffsetMinutes) + systemUtcOffsetMinutes); + return date; +} + +function formatTooltipText({ date, count }) { + const dateObject = new Date(date); + const dateDayName = gl.utils.getDayName(dateObject); + const dateText = dateObject.format('mmm d, yyyy'); + + let contribText = 'No contributions'; + if (count > 0) { + contribText = `${count} contribution${count > 1 ? 's' : ''}`; + } + return `${contribText}<br />${dateDayName} ${dateText}`; +} + +const initColorKey = () => d3.scale.linear().range(['#acd5f2', '#254e77']).domain([0, 3]); + export default class ActivityCalendar { - constructor(timestamps, calendar_activities_path) { - this.calendar_activities_path = calendar_activities_path; + constructor(container, timestamps, calendarActivitiesPath, utcOffset = 0) { + this.calendarActivitiesPath = calendarActivitiesPath; this.clickDay = this.clickDay.bind(this); this.currentSelectedDate = ''; this.daySpace = 1; @@ -12,25 +39,26 @@ export default class ActivityCalendar { this.daySizeWithSpace = this.daySize + (this.daySpace * 2); this.monthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; this.months = []; + // Loop through the timestamps to create a group of objects // The group of objects will be grouped based on the day of the week they are this.timestampsTmp = []; - var group = 0; + let group = 0; - var today = new Date(); + const today = getSystemDate(utcOffset); today.setHours(0, 0, 0, 0, 0); - var oneYearAgo = new Date(today); + const oneYearAgo = new Date(today); oneYearAgo.setFullYear(today.getFullYear() - 1); - var days = gl.utils.getDayDifference(oneYearAgo, today); + const days = gl.utils.getDayDifference(oneYearAgo, today); - for (var i = 0; i <= days; i += 1) { - var date = new Date(oneYearAgo); + for (let i = 0; i <= days; i += 1) { + const date = new Date(oneYearAgo); date.setDate(date.getDate() + i); - var day = date.getDay(); - var count = timestamps[date.format('yyyy-mm-dd')]; + const day = date.getDay(); + const count = timestamps[date.format('yyyy-mm-dd')] || 0; // Create a new group array if this is the first day of the week // or if is first object @@ -39,129 +67,119 @@ export default class ActivityCalendar { group += 1; } - var innerArray = this.timestampsTmp[group - 1]; // Push to the inner array the values that will be used to render map - innerArray.push({ - count: count || 0, - date: date, - day: day - }); + const innerArray = this.timestampsTmp[group - 1]; + innerArray.push({ count, date, day }); } // Init color functions - this.colorKey = this.initColorKey(); + this.colorKey = initColorKey(); this.color = this.initColor(); + // Init the svg element - this.renderSvg(group); + this.svg = this.renderSvg(container, group); this.renderDays(); this.renderMonths(); this.renderDayTitles(); this.renderKey(); - this.initTooltips(); + + // Init tooltips + $(`${container} .js-tooltip`).tooltip({ html: true }); } // Add extra padding for the last month label if it is also the last column getExtraWidthPadding(group) { - var extraWidthPadding = 0; - var lastColMonth = this.timestampsTmp[group - 1][0].date.getMonth(); - var secondLastColMonth = this.timestampsTmp[group - 2][0].date.getMonth(); + let extraWidthPadding = 0; + const lastColMonth = this.timestampsTmp[group - 1][0].date.getMonth(); + const secondLastColMonth = this.timestampsTmp[group - 2][0].date.getMonth(); - if (lastColMonth != secondLastColMonth) { + if (lastColMonth !== secondLastColMonth) { extraWidthPadding = 3; } return extraWidthPadding; } - renderSvg(group) { - var width = (group + 1) * this.daySizeWithSpace + this.getExtraWidthPadding(group); - return this.svg = d3.select('.js-contrib-calendar').append('svg').attr('width', width).attr('height', 167).attr('class', 'contrib-calendar'); + renderSvg(container, group) { + const width = ((group + 1) * this.daySizeWithSpace) + this.getExtraWidthPadding(group); + return d3.select(container) + .append('svg') + .attr('width', width) + .attr('height', 167) + .attr('class', 'contrib-calendar'); } renderDays() { - return this.svg.selectAll('g').data(this.timestampsTmp).enter().append('g').attr('transform', (function(_this) { - return function(group, i) { - _.each(group, function(stamp, a) { - var lastMonth, lastMonthX, month, x; + this.svg.selectAll('g').data(this.timestampsTmp).enter().append('g') + .attr('transform', (group, i) => { + _.each(group, (stamp, a) => { if (a === 0 && stamp.day === 0) { - month = stamp.date.getMonth(); - x = (_this.daySizeWithSpace * i + 1) + _this.daySizeWithSpace; - lastMonth = _.last(_this.months); - if (lastMonth != null) { - lastMonthX = lastMonth.x; - } - if (lastMonth == null) { - return _this.months.push({ - month: month, - x: x - }); - } else if (month !== lastMonth.month && x - _this.daySizeWithSpace !== lastMonthX) { - return _this.months.push({ - month: month, - x: x - }); + const month = stamp.date.getMonth(); + const x = (this.daySizeWithSpace * i) + 1 + this.daySizeWithSpace; + const lastMonth = _.last(this.months); + if ( + lastMonth == null || + (month !== lastMonth.month && x - this.daySizeWithSpace !== lastMonth.x) + ) { + this.months.push({ month, x }); } } }); - return "translate(" + ((_this.daySizeWithSpace * i + 1) + _this.daySizeWithSpace) + ", 18)"; - }; - })(this)).selectAll('rect').data(function(stamp) { - return stamp; - }).enter().append('rect').attr('x', '0').attr('y', (function(_this) { - return function(stamp, i) { - return _this.daySizeWithSpace * stamp.day; - }; - })(this)).attr('width', this.daySize).attr('height', this.daySize).attr('title', (function(_this) { - return function(stamp) { - var contribText, date, dateText; - date = new Date(stamp.date); - contribText = 'No contributions'; - if (stamp.count > 0) { - contribText = stamp.count + " contribution" + (stamp.count > 1 ? 's' : ''); - } - dateText = date.format('mmm d, yyyy'); - return contribText + "<br />" + (gl.utils.getDayName(date)) + " " + dateText; - }; - })(this)).attr('class', 'user-contrib-cell js-tooltip').attr('fill', (function(_this) { - return function(stamp) { - if (stamp.count !== 0) { - return _this.color(Math.min(stamp.count, 40)); - } else { - return '#ededed'; - } - }; - })(this)).attr('data-container', 'body').on('click', this.clickDay); + return `translate(${(this.daySizeWithSpace * i) + 1 + this.daySizeWithSpace}, 18)`; + }) + .selectAll('rect') + .data(stamp => stamp) + .enter() + .append('rect') + .attr('x', '0') + .attr('y', stamp => this.daySizeWithSpace * stamp.day) + .attr('width', this.daySize) + .attr('height', this.daySize) + .attr('fill', stamp => ( + stamp.count !== 0 ? this.color(Math.min(stamp.count, 40)) : '#ededed' + )) + .attr('title', stamp => formatTooltipText(stamp)) + .attr('class', 'user-contrib-cell js-tooltip') + .attr('data-container', 'body') + .on('click', this.clickDay); } renderDayTitles() { - var days; - days = [ + const days = [ { text: 'M', - y: 29 + (this.daySizeWithSpace * 1) + y: 29 + (this.daySizeWithSpace * 1), }, { text: 'W', - y: 29 + (this.daySizeWithSpace * 3) + y: 29 + (this.daySizeWithSpace * 3), }, { text: 'F', - y: 29 + (this.daySizeWithSpace * 5) - } + y: 29 + (this.daySizeWithSpace * 5), + }, ]; - return this.svg.append('g').selectAll('text').data(days).enter().append('text').attr('text-anchor', 'middle').attr('x', 8).attr('y', function(day) { - return day.y; - }).text(function(day) { - return day.text; - }).attr('class', 'user-contrib-text'); + this.svg.append('g') + .selectAll('text') + .data(days) + .enter() + .append('text') + .attr('text-anchor', 'middle') + .attr('x', 8) + .attr('y', day => day.y) + .text(day => day.text) + .attr('class', 'user-contrib-text'); } renderMonths() { - return this.svg.append('g').attr('direction', 'ltr').selectAll('text').data(this.months).enter().append('text').attr('x', function(date) { - return date.x; - }).attr('y', 10).attr('class', 'user-contrib-text').text((function(_this) { - return function(date) { - return _this.monthNames[date.month]; - }; - })(this)); + this.svg.append('g') + .attr('direction', 'ltr') + .selectAll('text') + .data(this.months) + .enter() + .append('text') + .attr('x', date => date.x) + .attr('y', 10) + .attr('class', 'user-contrib-text') + .text(date => this.monthNames[date.month]); } renderKey() { @@ -169,7 +187,7 @@ export default class ActivityCalendar { const keyColors = ['#ededed', this.colorKey(0), this.colorKey(1), this.colorKey(2), this.colorKey(3)]; this.svg.append('g') - .attr('transform', `translate(18, ${this.daySizeWithSpace * 8 + 16})`) + .attr('transform', `translate(18, ${(this.daySizeWithSpace * 8) + 16})`) .selectAll('rect') .data(keyColors) .enter() @@ -185,43 +203,31 @@ export default class ActivityCalendar { } initColor() { - var colorRange; - colorRange = ['#ededed', this.colorKey(0), this.colorKey(1), this.colorKey(2), this.colorKey(3)]; + const colorRange = ['#ededed', this.colorKey(0), this.colorKey(1), this.colorKey(2), this.colorKey(3)]; return d3.scale.threshold().domain([0, 10, 20, 30]).range(colorRange); } - initColorKey() { - return d3.scale.linear().range(['#acd5f2', '#254e77']).domain([0, 3]); - } - clickDay(stamp) { - var formatted_date; if (this.currentSelectedDate !== stamp.date) { this.currentSelectedDate = stamp.date; - formatted_date = this.currentSelectedDate.getFullYear() + "-" + (this.currentSelectedDate.getMonth() + 1) + "-" + this.currentSelectedDate.getDate(); - return $.ajax({ - url: this.calendar_activities_path, - data: { - date: formatted_date - }, + + const date = [ + this.currentSelectedDate.getFullYear(), + this.currentSelectedDate.getMonth() + 1, + this.currentSelectedDate.getDate(), + ].join('-'); + + $.ajax({ + url: this.calendarActivitiesPath, + data: { date }, cache: false, dataType: 'html', - beforeSend: function() { - return $('.user-calendar-activities').html('<div class="text-center"><i class="fa fa-spinner fa-spin user-calendar-activities-loading"></i></div>'); - }, - success: function(data) { - return $('.user-calendar-activities').html(data); - } + beforeSend: () => $('.user-calendar-activities').html(LOADING_HTML), + success: data => $('.user-calendar-activities').html(data), }); } else { this.currentSelectedDate = ''; - return $('.user-calendar-activities').html(''); + $('.user-calendar-activities').html(''); } } - - initTooltips() { - return $('.js-contrib-calendar .js-tooltip').tooltip({ - html: true - }); - } } diff --git a/app/assets/javascripts/users/index.js b/app/assets/javascripts/users/index.js index ecd8e09161e..33a83f8dae5 100644 --- a/app/assets/javascripts/users/index.js +++ b/app/assets/javascripts/users/index.js @@ -1,7 +1,19 @@ -import ActivityCalendar from './activity_calendar'; -import User from './user'; +import Cookies from 'js-cookie'; +import UserTabs from './user_tabs'; -// use legacy exports until embedded javascript is refactored -window.Calendar = ActivityCalendar; -window.gl = window.gl || {}; -window.gl.User = User; +export default function initUserProfile(action) { + // place profile avatars to top + $('.profile-groups-avatars').tooltip({ + placement: 'top', + }); + + // eslint-disable-next-line no-new + new UserTabs({ parentEl: '.user-profile', action }); + + // hide project limit message + $('.hide-project-limit-message').on('click', (e) => { + e.preventDefault(); + Cookies.set('hide_project_limit_message', 'false'); + $(this).parents('.project-limit-message').remove(); + }); +} diff --git a/app/assets/javascripts/users/user.js b/app/assets/javascripts/users/user.js deleted file mode 100644 index 0b0a3e1afb4..00000000000 --- a/app/assets/javascripts/users/user.js +++ /dev/null @@ -1,34 +0,0 @@ -/* eslint-disable class-methods-use-this */ - -import Cookies from 'js-cookie'; -import UserTabs from './user_tabs'; - -export default class User { - constructor({ action }) { - this.action = action; - this.placeProfileAvatarsToTop(); - this.initTabs(); - this.hideProjectLimitMessage(); - } - - placeProfileAvatarsToTop() { - $('.profile-groups-avatars').tooltip({ - placement: 'top', - }); - } - - initTabs() { - return new UserTabs({ - parentEl: '.user-profile', - action: this.action, - }); - } - - hideProjectLimitMessage() { - $('.hide-project-limit-message').on('click', (e) => { - e.preventDefault(); - Cookies.set('hide_project_limit_message', 'false'); - $(this).parents('.project-limit-message').remove(); - }); - } -} diff --git a/app/assets/javascripts/users/user_tabs.js b/app/assets/javascripts/users/user_tabs.js index f8e23c8624d..1215b265e28 100644 --- a/app/assets/javascripts/users/user_tabs.js +++ b/app/assets/javascripts/users/user_tabs.js @@ -1,72 +1,76 @@ -/* eslint-disable max-len, space-before-function-paren, no-underscore-dangle, consistent-return, comma-dangle, no-unused-vars, dot-notation, no-new, no-return-assign, camelcase, no-param-reassign, class-methods-use-this */ - -/* -UserTabs - -Handles persisting and restoring the current tab selection and lazily-loading -content on the Users#show page. - -### Example Markup - - <ul class="nav-links"> - <li class="activity-tab active"> - <a data-action="activity" data-target="#activity" data-toggle="tab" href="/u/username"> - Activity - </a> - </li> - <li class="groups-tab"> - <a data-action="groups" data-target="#groups" data-toggle="tab" href="/u/username/groups"> - Groups - </a> - </li> - <li class="contributed-tab"> - <a data-action="contributed" data-target="#contributed" data-toggle="tab" href="/u/username/contributed"> - Contributed projects - </a> - </li> - <li class="projects-tab"> - <a data-action="projects" data-target="#projects" data-toggle="tab" href="/u/username/projects"> - Personal projects - </a> - </li> - <li class="snippets-tab"> - <a data-action="snippets" data-target="#snippets" data-toggle="tab" href="/u/username/snippets"> - </a> - </li> - </ul> - - <div class="tab-content"> - <div class="tab-pane" id="activity"> - Activity Content - </div> - <div class="tab-pane" id="groups"> - Groups Content - </div> - <div class="tab-pane" id="contributed"> - Contributed projects content - </div> - <div class="tab-pane" id="projects"> - Projects content - </div> - <div class="tab-pane" id="snippets"> - Snippets content - </div> +import ActivityCalendar from './activity_calendar'; + +/** + * UserTabs + * + * Handles persisting and restoring the current tab selection and lazily-loading + * content on the Users#show page. + * + * ### Example Markup + * + * <ul class="nav-links"> + * <li class="activity-tab active"> + * <a data-action="activity" data-target="#activity" data-toggle="tab" href="/u/username"> + * Activity + * </a> + * </li> + * <li class="groups-tab"> + * <a data-action="groups" data-target="#groups" data-toggle="tab" href="/u/username/groups"> + * Groups + * </a> + * </li> + * <li class="contributed-tab"> + * ... + * </li> + * <li class="projects-tab"> + * ... + * </li> + * <li class="snippets-tab"> + * ... + * </li> + * </ul> + * + * <div class="tab-content"> + * <div class="tab-pane" id="activity"> + * Activity Content + * </div> + * <div class="tab-pane" id="groups"> + * Groups Content + * </div> + * <div class="tab-pane" id="contributed"> + * Contributed projects content + * </div> + * <div class="tab-pane" id="projects"> + * Projects content + * </div> + * <div class="tab-pane" id="snippets"> + * Snippets content + * </div> + * </div> + * + * <div class="loading-status"> + * <div class="loading"> + * Loading Animation + * </div> + * </div> + */ + +const CALENDAR_TEMPLATE = ` + <div class="clearfix calendar"> + <div class="js-contrib-calendar"></div> + <div class="calendar-hint"> + Summary of issues, merge requests, push events, and comments + </div> </div> - - <div class="loading-status"> - <div class="loading"> - Loading Animation - </div> - </div> -*/ +`; export default class UserTabs { - constructor ({ defaultAction, action, parentEl }) { + constructor({ defaultAction, action, parentEl }) { this.loaded = {}; this.defaultAction = defaultAction || 'activity'; this.action = action || this.defaultAction; this.$parentEl = $(parentEl) || $(document); - this._location = window.location; + this.windowLocation = window.location; this.$parentEl.find('.nav-links a') .each((i, navLink) => { this.loaded[$(navLink).attr('data-action')] = false; @@ -82,12 +86,10 @@ export default class UserTabs { } bindEvents() { - this.changeProjectsPageWrapper = this.changeProjectsPage.bind(this); - - this.$parentEl.off('shown.bs.tab', '.nav-links a[data-toggle="tab"]') - .on('shown.bs.tab', '.nav-links a[data-toggle="tab"]', event => this.tabShown(event)); - - this.$parentEl.on('click', '.gl-pagination a', this.changeProjectsPageWrapper); + this.$parentEl + .off('shown.bs.tab', '.nav-links a[data-toggle="tab"]') + .on('shown.bs.tab', '.nav-links a[data-toggle="tab"]', event => this.tabShown(event)) + .on('click', '.gl-pagination a', event => this.changeProjectsPage(event)); } changeProjectsPage(e) { @@ -122,7 +124,7 @@ export default class UserTabs { const loadableActions = ['groups', 'contributed', 'projects', 'snippets']; if (loadableActions.indexOf(action) > -1) { - return this.loadTab(action, endpoint); + this.loadTab(action, endpoint); } } @@ -131,25 +133,44 @@ export default class UserTabs { beforeSend: () => this.toggleLoading(true), complete: () => this.toggleLoading(false), dataType: 'json', - type: 'GET', url: endpoint, success: (data) => { const tabSelector = `div#${action}`; this.$parentEl.find(tabSelector).html(data.html); this.loaded[action] = true; - return gl.utils.localTimeAgo($('.js-timeago', tabSelector)); - } + gl.utils.localTimeAgo($('.js-timeago', tabSelector)); + }, }); } loadActivities() { - if (this.loaded['activity']) { + if (this.loaded.activity) { return; } const $calendarWrap = this.$parentEl.find('.user-calendar'); - $calendarWrap.load($calendarWrap.data('href')); + const calendarPath = $calendarWrap.data('calendarPath'); + const calendarActivitiesPath = $calendarWrap.data('calendarActivitiesPath'); + const utcOffset = $calendarWrap.data('utcOffset'); + let utcFormatted = 'UTC'; + if (utcOffset !== 0) { + utcFormatted = `UTC${utcOffset > 0 ? '+' : ''}${(utcOffset / 3600)}`; + } + + $.ajax({ + dataType: 'json', + url: calendarPath, + success: (activityData) => { + $calendarWrap.html(CALENDAR_TEMPLATE); + $calendarWrap.find('.calendar-hint').append(`(Timezone: ${utcFormatted})`); + + // eslint-disable-next-line no-new + new ActivityCalendar('.js-contrib-calendar', activityData, calendarActivitiesPath, utcOffset); + }, + }); + + // eslint-disable-next-line no-new new gl.Activities(); - return this.loaded['activity'] = true; + this.loaded.activity = true; } toggleLoading(status) { @@ -158,13 +179,13 @@ export default class UserTabs { } setCurrentAction(source) { - let new_state = source; - new_state = new_state.replace(/\/+$/, ''); - new_state += this._location.search + this._location.hash; + let newState = source; + newState = newState.replace(/\/+$/, ''); + newState += this.windowLocation.search + this.windowLocation.hash; history.replaceState({ - url: new_state - }, document.title, new_state); - return new_state; + url: newState, + }, document.title, newState); + return newState; } getCurrentAction() { diff --git a/app/assets/javascripts/users_select.js b/app/assets/javascripts/users_select.js index 5728afb4c59..16ebf5916dc 100644 --- a/app/assets/javascripts/users_select.js +++ b/app/assets/javascripts/users_select.js @@ -1,6 +1,7 @@ /* eslint-disable func-names, space-before-function-paren, one-var, no-var, prefer-rest-params, wrap-iife, quotes, max-len, one-var-declaration-per-line, vars-on-top, prefer-arrow-callback, consistent-return, comma-dangle, object-shorthand, no-shadow, no-unused-vars, no-else-return, no-self-compare, prefer-template, no-unused-expressions, no-lonely-if, yoda, prefer-spread, no-void, camelcase, no-param-reassign */ /* global Issuable */ /* global emitSidebarEvent */ +import _ from 'underscore'; // TODO: remove eventHub hack after code splitting refactor window.emitSidebarEvent = window.emitSidebarEvent || $.noop; diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_deployment.js b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_deployment.js index e8e22ad93a5..744a1cd24fa 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_deployment.js +++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_deployment.js @@ -108,7 +108,8 @@ export default { </div> <mr-widget-memory-usage v-if="deployment.metrics_url" - :metricsUrl="deployment.metrics_url" + :metrics-url="deployment.metrics_url" + :metrics-monitoring-url="deployment.metrics_monitoring_url" /> </div> </div> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_memory_usage.js b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_memory_usage.js index 76cb71b6c12..534e2a88eff 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_memory_usage.js +++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_memory_usage.js @@ -7,7 +7,14 @@ import MRWidgetService from '../services/mr_widget_service'; export default { name: 'MemoryUsage', props: { - metricsUrl: { type: String, required: true }, + metricsUrl: { + type: String, + required: true, + }, + metricsMonitoringUrl: { + type: String, + required: true, + }, }, data() { return { @@ -124,7 +131,7 @@ export default { <p v-if="shouldShowMemoryGraph" class="usage-info js-usage-info"> - Memory usage <b>{{memoryChangeType}}</b> from {{memoryFrom}}MB to {{memoryTo}}MB + <a :href="metricsMonitoringUrl">Memory</a> usage <b>{{memoryChangeType}}</b> from {{memoryFrom}}MB to {{memoryTo}}MB </p> <p v-if="shouldShowLoadFailure" diff --git a/app/assets/javascripts/vue_merge_request_widget/dependencies.js b/app/assets/javascripts/vue_merge_request_widget/dependencies.js index fe5e1bbb55c..546a3f625c7 100644 --- a/app/assets/javascripts/vue_merge_request_widget/dependencies.js +++ b/app/assets/javascripts/vue_merge_request_widget/dependencies.js @@ -1,7 +1,7 @@ /** * This file is the centerpiece of an attempt to reduce potential conflicts * between the CE and EE versions of the MR widget. EE additions to the MR widget should - * be contained in the ./vue_merge_request_widget/ee directory, and should **extend** + * be contained in the ee/vue_merge_request_widget directory, and should **extend** * rather than mutate CE MR Widget code. * * This file should be the only source of conflicts between EE and CE. EE-only components should 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 72a13108404..fddafb0ddfa 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 @@ -65,7 +65,7 @@ export default class MergeRequestStore { this.mergeCheckPath = data.merge_check_path; this.mergeActionsContentPath = data.commit_change_content_path; this.isRemovingSourceBranch = this.isRemovingSourceBranch || false; - this.isOpen = data.state === 'opened' || data.state === 'reopened' || false; + this.isOpen = data.state === 'opened'; this.hasMergeableDiscussionsState = data.mergeable_discussions_state === false; this.canRemoveSourceBranch = currentUser.can_remove_source_branch || false; this.canMerge = !!data.merge_path; diff --git a/app/assets/stylesheets/framework/avatar.scss b/app/assets/stylesheets/framework/avatar.scss index 0dfa7a31d31..cb41df8a88d 100644 --- a/app/assets/stylesheets/framework/avatar.scss +++ b/app/assets/stylesheets/framework/avatar.scss @@ -88,6 +88,10 @@ overflow: hidden; display: flex; + a { + display: flex; + } + .avatar { border-radius: 0; border: none; diff --git a/app/assets/stylesheets/framework/calendar.scss b/app/assets/stylesheets/framework/calendar.scss index 0ac095f7d8f..0ded4a3b423 100644 --- a/app/assets/stylesheets/framework/calendar.scss +++ b/app/assets/stylesheets/framework/calendar.scss @@ -45,6 +45,7 @@ margin-top: -23px; float: right; font-size: 12px; + direction: ltr; } .pika-single.gitlab-theme { diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss index 5e410cbf563..02e0ba74158 100644 --- a/app/assets/stylesheets/framework/dropdowns.scss +++ b/app/assets/stylesheets/framework/dropdowns.scss @@ -148,7 +148,6 @@ padding: 5px 8px; color: $gl-text-color; line-height: initial; - text-overflow: ellipsis; border-radius: 2px; white-space: nowrap; overflow: hidden; @@ -203,11 +202,6 @@ border-radius: $border-radius-base; box-shadow: 0 2px 4px $dropdown-shadow-color; - @media (max-width: $screen-sm-min) { - width: 100%; - min-width: 180px; - } - &.dropdown-open-left { right: 0; left: auto; @@ -289,6 +283,11 @@ padding: 5px 8px; color: $gl-text-color-secondary; } + + .badge + span:not(.badge) { + // Expects up to 3 digits on the badge + margin-right: 40px; + } } .droplab-dropdown { @@ -373,7 +372,6 @@ .dropdown-menu, .dropdown-menu-nav { max-width: 280px; - width: auto; } } @@ -576,6 +574,7 @@ .dropdown-input-field, .default-dropdown-input { + display: block; width: 100%; min-height: 30px; padding: 0 7px; @@ -724,3 +723,57 @@ @include set-invisible; overflow: hidden; } + +// TODO: change global style and remove mixin +@mixin new-style-dropdown { + .dropdown-menu, + .dropdown-menu-nav { + .divider { + margin: 6px 0; + } + + li { + padding: 0 1px; + + &.dropdown-header { + padding: 8px 16px; + } + + a { + border-radius: 0; + padding: 8px 16px; + + &.is-focused, + &:hover, + &:active, + &:focus { + background-color: $gray-darker; + } + + &.is-active { + font-weight: inherit; + + &::before { + top: 16px; + } + } + } + } + + &.dropdown-menu-selectable { + li { + a { + padding: 8px 40px; + + &.is-active::before { + left: 16px; + } + } + } + } + } + + .dropdown-menu-align-right { + margin-top: 2px; + } +} diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss index c7c2684d548..8ad082f7a65 100644 --- a/app/assets/stylesheets/framework/files.scss +++ b/app/assets/stylesheets/framework/files.scss @@ -163,8 +163,18 @@ td.blame-commit { padding: 5px 10px; min-width: 400px; + max-width: 400px; background: $gray-light; border-left: 3px solid; + + .commit-row-title { + display: flex; + } + + .item-title { + flex: 1; + margin-right: 0.5em; + } } @for $i from 0 through 5 { diff --git a/app/assets/stylesheets/framework/filters.scss b/app/assets/stylesheets/framework/filters.scss index 41184907abb..ec13a86ccf7 100644 --- a/app/assets/stylesheets/framework/filters.scss +++ b/app/assets/stylesheets/framework/filters.scss @@ -393,7 +393,8 @@ @media (max-width: $screen-xs) { .filter-dropdown-container { .dropdown-toggle, - .dropdown { + .dropdown, + .dropdown-menu { width: 100%; } @@ -413,13 +414,16 @@ background-color: $dropdown-hover-color; color: $white-light; text-decoration: none; + outline: 0; .avatar { border-color: $white-light; } } -.filter-dropdown-item { +.droplab-dropdown .dropdown-menu .filter-dropdown-item { + padding: 0; + .btn { border: none; width: 100%; @@ -454,14 +458,11 @@ } .dropdown-user { - display: -webkit-flex; display: flex; } .dropdown-user-details { - display: -webkit-flex; display: flex; - -webkit-flex-direction: column; flex-direction: column; > span { diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss index 605f4284bb5..4a69c14fa7e 100644 --- a/app/assets/stylesheets/framework/header.scss +++ b/app/assets/stylesheets/framework/header.scss @@ -4,6 +4,8 @@ */ header { + @include new-style-dropdown; + transition: padding $sidebar-transition-duration; &.navbar-empty { @@ -315,13 +317,17 @@ header { } } +.with-performance-bar header.navbar-gitlab { + top: $performance-bar-height; +} + .navbar-nav { li { .badge { position: inherit; - top: -3px; + top: -8px; font-weight: normal; - margin-left: -12px; + margin-left: -11px; font-size: 11px; color: $white-light; padding: 1px 5px 2px; diff --git a/app/assets/stylesheets/framework/layout.scss b/app/assets/stylesheets/framework/layout.scss index 4a9d41b4fda..67c3287ed74 100644 --- a/app/assets/stylesheets/framework/layout.scss +++ b/app/assets/stylesheets/framework/layout.scss @@ -120,3 +120,7 @@ of the body element here, we negate cascading side effects but allow momentum sc .page-with-sidebar { -webkit-overflow-scrolling: auto; } + +.with-performance-bar .page-with-sidebar { + margin-top: $header-height + $performance-bar-height; +} diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss index 868e65a8f46..ab754f4a492 100644 --- a/app/assets/stylesheets/framework/lists.scss +++ b/app/assets/stylesheets/framework/lists.scss @@ -369,6 +369,10 @@ ul.indent-list { background-color: $row-hover; cursor: pointer; } + + .avatar-container > a { + width: 100%; + } } } diff --git a/app/assets/stylesheets/framework/markdown_area.scss b/app/assets/stylesheets/framework/markdown_area.scss index a2de4598167..fcd4c72b430 100644 --- a/app/assets/stylesheets/framework/markdown_area.scss +++ b/app/assets/stylesheets/framework/markdown_area.scss @@ -185,3 +185,28 @@ text-overflow: ellipsis; } } + +// TODO: fallback to global style +.atwho-view { + .atwho-view-ul { + padding: 8px 1px; + + li { + padding: 8px 16px; + border: 0; + + &.cur { + background-color: $gray-darker; + color: $gl-text-color; + + small { + color: inherit; + } + } + + strong { + color: $gl-text-color; + } + } + } +} diff --git a/app/assets/stylesheets/framework/mixins.scss b/app/assets/stylesheets/framework/mixins.scss index 3a98332e46c..6f91d11b369 100644 --- a/app/assets/stylesheets/framework/mixins.scss +++ b/app/assets/stylesheets/framework/mixins.scss @@ -118,3 +118,29 @@ @content; } } + +/* + * Mixin for status badges, as used for pipelines and commit signatures + */ +@mixin status-color($color-light, $color-main, $color-dark) { + color: $color-main; + border-color: $color-main; + + &:not(span):hover { + background-color: $color-light; + color: $color-dark; + border-color: $color-dark; + + svg { + fill: $color-dark; + } + } + + svg { + fill: $color-main; + } +} + +@mixin green-status-color { + @include status-color($green-50, $green-500, $green-700); +} diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss index 35b4d77a5ab..88e7ba117d5 100644 --- a/app/assets/stylesheets/framework/nav.scss +++ b/app/assets/stylesheets/framework/nav.scss @@ -347,6 +347,10 @@ } } +.with-performance-bar .layout-nav { + margin-top: $header-height + $performance-bar-height; +} + .scrolling-tabs-container { position: relative; @@ -441,6 +445,22 @@ } } +.with-performance-bar .page-with-layout-nav { + .right-sidebar { + top: ($header-height + 1) * 2 + $performance-bar-height; + } + + &.page-with-sub-nav { + .right-sidebar { + top: ($header-height + 1) * 3 + $performance-bar-height; + + &.affix { + top: $header-height + $performance-bar-height; + } + } + } +} + .nav-block { &.activities { border-bottom: 1px solid $border-color; diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss index 49b2f0e43a4..09b60ad1676 100644 --- a/app/assets/stylesheets/framework/sidebar.scss +++ b/app/assets/stylesheets/framework/sidebar.scss @@ -89,6 +89,10 @@ } } +.with-performance-bar .right-sidebar.affix { + top: $header-height + $performance-bar-height; +} + @mixin maintain-sidebar-dimensions { display: block; width: $gutter-width; diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss index befd8133be0..bf5f124d142 100644 --- a/app/assets/stylesheets/framework/typography.scss +++ b/app/assets/stylesheets/framework/typography.scss @@ -19,9 +19,9 @@ } img.js-lazy-loaded { - min-width: none; - min-height: none; - background-color: none; + min-width: inherit; + min-height: inherit; + background-color: inherit; } p a:not(.no-attachment-icon) img { diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index f25b96b87a6..78ff5961ab5 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -204,6 +204,7 @@ $divergence-graph-separator-bg: #ccc; $general-hover-transition-duration: 100ms; $general-hover-transition-curve: linear; $highlight-changes-color: rgb(235, 255, 232); +$performance-bar-height: 35px; /* diff --git a/app/assets/stylesheets/new_nav.scss b/app/assets/stylesheets/new_nav.scss index 9f3e278ebfc..795ee91af8b 100644 --- a/app/assets/stylesheets/new_nav.scss +++ b/app/assets/stylesheets/new_nav.scss @@ -21,6 +21,11 @@ header.navbar-gitlab-new { padding-right: 0; color: currentColor; + img { + height: 28px; + margin-right: 10px; + } + > a { display: flex; align-items: center; @@ -304,6 +309,29 @@ header.navbar-gitlab-new { outline: 0; } } + + // TODO: fallback to global style + .dropdown-menu { + .divider { + margin: 6px 0; + } + + li { + padding: 0 1px; + + a { + border-radius: 0; + padding: 8px 16px; + + &.is-focused, + &:hover, + &:active, + &:focus { + background-color: $gray-darker; + } + } + } + } } .breadcrumbs-container { @@ -320,6 +348,7 @@ header.navbar-gitlab-new { .breadcrumbs-links { flex: 1; + min-width: 0; align-self: center; color: $gl-text-color-quaternary; @@ -338,7 +367,7 @@ header.navbar-gitlab-new { } .title { - white-space: nowrap; + display: inline-block; > a { &:last-of-type:not(:first-child) { diff --git a/app/assets/stylesheets/new_sidebar.scss b/app/assets/stylesheets/new_sidebar.scss index ae43197a1a6..3d202183c82 100644 --- a/app/assets/stylesheets/new_sidebar.scss +++ b/app/assets/stylesheets/new_sidebar.scss @@ -118,7 +118,7 @@ $new-sidebar-width: 220px; z-index: 400; width: $new-sidebar-width; transition: left $sidebar-transition-duration; - top: 50px; + top: $header-height; bottom: 0; left: 0; overflow: auto; @@ -143,10 +143,19 @@ $new-sidebar-width: 220px; white-space: nowrap; a { - display: block; + display: flex; + align-items: center; padding: 12px 16px; color: $inactive-color; } + + svg { + fill: $inactive-color; + } + } + + .nav-item-name { + flex: 1; } li.active { @@ -156,11 +165,29 @@ $new-sidebar-width: 220px; color: $active-color; font-weight: 700; } + + svg { + fill: $active-color; + } } @media (max-width: $screen-xs-max) { left: (-$new-sidebar-width); } + + .nav-icon-container { + display: flex; + margin-right: 8px; + + svg { + height: 16px; + width: 16px; + } + } +} + +.with-performance-bar .nav-sidebar { + top: $header-height + $performance-bar-height; } .sidebar-sub-level-items { @@ -169,7 +196,7 @@ $new-sidebar-width: 220px; > li { a { - padding: 8px 16px 8px 24px; + padding: 8px 16px 8px 50px; &:hover, &:focus { @@ -193,8 +220,83 @@ $new-sidebar-width: 220px; .sidebar-top-level-items { > li { + > a { + @media (min-width: $screen-sm-min) { + margin-right: 2px; + } + + &:hover { + color: $gl-text-color; + } + } + + &:not(.active) { + > a { + margin-left: 1px; + margin-right: 3px; + } + + .sidebar-sub-level-items { + @media (min-width: $screen-sm-min) { + position: fixed; + top: 0; + left: 220px; + width: 150px; + margin-top: -1px; + padding: 8px 1px; + background-color: $white-light; + box-shadow: 2px 1px 3px $dropdown-shadow-color; + border: 1px solid $gray-darker; + border-left: 0; + border-radius: 0 3px 3px 0; + + &::before { + content: ""; + position: absolute; + top: -30px; + bottom: -30px; + left: 0; + right: -30px; + z-index: -1; + } + + &::after { + content: ""; + position: absolute; + top: 44px; + left: -30px; + right: 35px; + bottom: 0; + height: 100%; + max-height: 150px; + z-index: -1; + transform: skew(33deg); + } + + &.is-above { + margin-top: 1px; + + &::after { + top: auto; + bottom: 44px; + transform: skew(-30deg); + } + } + + a { + padding: 8px 16px; + color: $gl-text-color; + + &:hover, + &:focus { + background-color: $gray-darker; + } + } + } + } + } + .badge { - float: right; background-color: $inactive-badge-background; color: $inactive-color; } @@ -202,6 +304,10 @@ $new-sidebar-width: 220px; &.active { background: $active-background; + > a { + margin-left: 4px; + } + .badge { color: $active-color; font-weight: 600; @@ -212,14 +318,10 @@ $new-sidebar-width: 220px; } } - > a:hover { - background-color: $hover-background; - color: $hover-color; - - .badge { - background-color: $indigo-500; - color: $hover-color; - } + &:not(.active):hover > a, + > a:hover, + &.is-over > a { + background-color: $white-light; } } } @@ -260,7 +362,7 @@ $new-sidebar-width: 220px; // Make issue boards full-height now that sub-nav is gone .boards-list { - height: calc(100vh - 50px); + height: calc(100vh - #{$header-height}); @media (min-width: $screen-sm-min) { height: 475px; // Needed for PhantomJS @@ -270,6 +372,10 @@ $new-sidebar-width: 220px; } } +.with-performance-bar .boards-list { + height: calc(100vh - #{$header-height} - #{$performance-bar-height}); +} + // Change color of all horizontal tabs to match the new indigo color .nav-links li.active a { diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss index b6fc628c02b..28c99d8e57c 100644 --- a/app/assets/stylesheets/pages/builds.scss +++ b/app/assets/stylesheets/pages/builds.scss @@ -64,10 +64,10 @@ color: $gl-text-color; position: sticky; position: -webkit-sticky; - top: 50px; + top: $header-height; &.affix { - top: 50px; + top: $header-height; } // with sidebar @@ -86,6 +86,7 @@ position: absolute; right: 0; left: 0; + top: 0; } .truncated-info { @@ -171,6 +172,16 @@ } } +.with-performance-bar .build-page { + .top-bar { + top: $header-height + $performance-bar-height; + + &.affix { + top: $header-height + $performance-bar-height; + } + } +} + .build-header { .ci-header-container, .header-action-buttons { @@ -300,9 +311,7 @@ } .dropdown-menu { - right: $gl-padding; - left: $gl-padding; - width: auto; + margin-top: -$gl-padding; } svg { diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss index fd0871ec0b8..cd9f2d787c5 100644 --- a/app/assets/stylesheets/pages/commits.scss +++ b/app/assets/stylesheets/pages/commits.scss @@ -283,3 +283,63 @@ color: $gl-text-color; } } + + +.gpg-status-box { + &.valid { + @include green-status-color; + } + + &.invalid { + @include status-color($gray-dark, $gray, $common-gray-dark); + border-color: $common-gray-light; + } +} + +.gpg-popover-status { + display: flex; + align-items: center; + font-weight: normal; + line-height: 1.5; +} + +.gpg-popover-icon { + // same margin as .s32.avatar + margin-right: $btn-side-margin; + + &.valid { + svg { + border: 1px solid $brand-success; + + fill: $brand-success; + } + } + + &.invalid { + svg { + border: 1px solid $common-gray-light; + + fill: $common-gray-light; + } + } + + svg { + width: 32px; + height: 32px; + border-radius: 50%; + vertical-align: middle; + } +} + +.gpg-popover-user-link { + display: flex; + align-items: center; + margin-bottom: $gl-padding / 2; + text-decoration: none; + color: $gl-text-color; +} + +.commit .gpg-popover-help-link { + display: block; + color: $link-color; +} diff --git a/app/assets/stylesheets/pages/cycle_analytics.scss b/app/assets/stylesheets/pages/cycle_analytics.scss index eeb90759f10..6753eb08285 100644 --- a/app/assets/stylesheets/pages/cycle_analytics.scss +++ b/app/assets/stylesheets/pages/cycle_analytics.scss @@ -1,4 +1,6 @@ #cycle-analytics { + @include new-style-dropdown; + max-width: 1000px; margin: 24px auto 0; position: relative; diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index 398fd4444ea..da77346d8b2 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -395,12 +395,11 @@ background-color: transparent; border: 0; color: $gl-link-color; - transition: color 0.1s linear; + font-weight: 600; &:hover, &:focus { outline: none; - text-decoration: underline; color: $gl-link-hover-color; } } @@ -559,3 +558,68 @@ outline: 0; } } + +.diff-files-changed { + .commit-stat-summary { + @include new-style-dropdown; + z-index: -1; + + @media (min-width: $screen-sm-min) { + margin-left: -$gl-padding; + padding-left: $gl-padding; + background-color: $white-light; + } + } + + @media (min-width: $screen-sm-min) { + position: -webkit-sticky; + position: sticky; + top: 84px; + background-color: $white-light; + z-index: 190; + + + .files, + + .alert { + margin-top: 1px; + } + + &:not(.is-stuck) .diff-stats-additions-deletions-collapsed { + display: none; + } + + &.is-stuck { + padding-top: 0; + padding-bottom: 0; + border-bottom: 1px solid $white-dark; + transform: translateY(16px); + + .diff-stats-additions-deletions-expanded, + .inline-parallel-buttons { + display: none; + } + + + .files, + + .alert { + margin-top: 30px; + } + } + } +} + +.diff-file-changes { + width: 450px; + z-index: 150; + + @media (min-width: $screen-sm-min) { + left: $gl-padding; + } + + a { + padding-top: 8px; + padding-bottom: 8px; + } +} + +.diff-file-changes-path { + @include str-truncated(78%); +} diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index aa04e490649..88343bd0113 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -211,6 +211,10 @@ -webkit-overflow-scrolling: touch; } + &.affix-top .issuable-sidebar { + height: 100%; + } + &.right-sidebar-expanded { width: $gutter_width; @@ -324,9 +328,17 @@ margin-bottom: 10px; color: $issuable-sidebar-color; + svg { + fill: $issuable-sidebar-color; + } + &:hover, &:hover .todo-undone { color: $gl-text-color; + + svg { + fill: $gl-text-color; + } } span { @@ -441,6 +453,14 @@ } } +.with-performance-bar .right-sidebar { + top: $header-height + $performance-bar-height; + + .issuable-sidebar { + height: calc(100% - #{$header-height} - #{$performance-bar-height}); + } +} + .detail-page-description { padding: 16px 0; diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index 2db967547dd..a4e19094508 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -691,8 +691,10 @@ } .mr-version-controls { + position: relative; background: $gray-light; color: $gl-text-color; + z-index: 199; .mr-version-menus-container { display: -webkit-flex; @@ -759,6 +761,10 @@ } } +.with-performance-bar .merge-request-tabs-holder { + top: $header-height + $performance-bar-height; +} + .merge-request-tabs { display: flex; margin-bottom: 0; diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 9637d26e56d..d3862df20d3 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -597,7 +597,7 @@ } // Dropdown button in mini pipeline graph -.mini-pipeline-graph-dropdown-toggle { +button.mini-pipeline-graph-dropdown-toggle { border-radius: 100px; background-color: $white-light; border-width: 1px; @@ -608,6 +608,7 @@ padding: 0; transition: all 0.2s linear; position: relative; + vertical-align: middle; > .fa.fa-caret-down { position: absolute; diff --git a/app/assets/stylesheets/pages/profile.scss b/app/assets/stylesheets/pages/profile.scss index 22672614e0d..14ad06b0ac2 100644 --- a/app/assets/stylesheets/pages/profile.scss +++ b/app/assets/stylesheets/pages/profile.scss @@ -391,3 +391,26 @@ table.u2f-registrations { margin-bottom: 0; } } + +.gpg-email-badge { + display: inline; + margin-right: $gl-padding / 2; + + .gpg-email-badge-email { + display: inline; + margin-right: $gl-padding / 4; + } + + .label-verification-status { + border-width: 1px; + border-style: solid; + + &.verified { + @include green-status-color; + } + + &.unverified { + @include status-color($gray-dark, $gray, $common-gray-dark); + } + } +} diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index 2f4a8426b8b..276465488e7 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -37,7 +37,6 @@ } select { - background: transparent; transition: background 2s ease-out; &.highlight-changes { @@ -283,6 +282,8 @@ } .project-repo-buttons { + @include new-style-dropdown; + .project-action-button .dropdown-menu { max-height: 250px; overflow-y: auto; @@ -837,6 +838,7 @@ pre.light-well { background-color: transparent; border: 0; text-align: left; + text-overflow: ellipsis; } .protected-branches-list, diff --git a/app/assets/stylesheets/pages/settings.scss b/app/assets/stylesheets/pages/settings.scss index d69a8e0995c..15df51e9c69 100644 --- a/app/assets/stylesheets/pages/settings.scss +++ b/app/assets/stylesheets/pages/settings.scss @@ -54,8 +54,7 @@ .settings-content { max-height: 1px; overflow-y: scroll; - margin-right: -20px; - padding-right: 130px; + padding-right: 110px; animation: collapseMaxHeight 300ms ease-out; &.expanded { @@ -87,6 +86,23 @@ overflow: hidden; margin-top: 20px; } + + .sub-section { + margin-bottom: 32px; + padding: 16px; + border: 1px solid $border-color; + background-color: $gray-light; + } + + .bs-callout, + .checkbox:first-child, + .help-block { + margin-top: 0; + } + + .label-light { + margin-bottom: 0; + } } .settings-list-icon { diff --git a/app/assets/stylesheets/pages/status.scss b/app/assets/stylesheets/pages/status.scss index 67ad1ae60af..36f622db136 100644 --- a/app/assets/stylesheets/pages/status.scss +++ b/app/assets/stylesheets/pages/status.scss @@ -1,22 +1,3 @@ -@mixin status-color($color-light, $color-main, $color-dark) { - color: $color-main; - border-color: $color-main; - - &:not(span):hover { - background-color: $color-light; - color: $color-dark; - border-color: $color-dark; - - svg { - fill: $color-dark; - } - } - - svg { - fill: $color-main; - } -} - .ci-status { padding: 2px 7px 4px; border: 1px solid $gray-darker; @@ -41,7 +22,7 @@ } &.ci-success { - @include status-color($green-50, $green-500, $green-700); + @include green-status-color; } &.ci-canceled, diff --git a/app/assets/stylesheets/pages/tree.scss b/app/assets/stylesheets/pages/tree.scss index dc88cf3e699..44ab07a4367 100644 --- a/app/assets/stylesheets/pages/tree.scss +++ b/app/assets/stylesheets/pages/tree.scss @@ -1,4 +1,5 @@ .tree-holder { + @include new-style-dropdown; .nav-block { margin: 10px 0; diff --git a/app/assets/stylesheets/performance_bar.scss b/app/assets/stylesheets/performance_bar.scss index 2890b6b1e49..6e539e39ca1 100644 --- a/app/assets/stylesheets/performance_bar.scss +++ b/app/assets/stylesheets/performance_bar.scss @@ -3,9 +3,16 @@ @import "peek/views/rblineprof"; #peek { - height: 35px; + position: fixed; + left: 0; + top: 0; + width: 100%; + z-index: 2000; + overflow-x: hidden; + + height: $performance-bar-height; background: $black; - line-height: 35px; + line-height: $performance-bar-height; color: $perf-bar-text; &.disabled { @@ -25,7 +32,8 @@ } .wrapper { - width: 1000px; + width: 80%; + height: $performance-bar-height; margin: 0 auto; } |