diff options
Diffstat (limited to 'app/assets')
348 files changed, 9909 insertions, 2931 deletions
diff --git a/app/assets/images/auth_buttons/authentiq_64.png b/app/assets/images/auth_buttons/authentiq_64.png Binary files differnew file mode 100644 index 00000000000..81767bbcc54 --- /dev/null +++ b/app/assets/images/auth_buttons/authentiq_64.png diff --git a/app/assets/javascripts/admin.js b/app/assets/javascripts/admin.js index 5a7d823e84c..424dc719c78 100644 --- a/app/assets/javascripts/admin.js +++ b/app/assets/javascripts/admin.js @@ -1,5 +1,4 @@ -/* eslint-disable func-names, space-before-function-paren, wrap-iife, one-var, no-var, one-var-declaration-per-line, no-unused-vars, no-else-return, prefer-arrow-callback, camelcase, quotes, comma-dangle, padded-blocks, max-len */ -/* global Turbolinks */ +/* eslint-disable func-names, space-before-function-paren, wrap-iife, one-var, no-var, one-var-declaration-per-line, no-unused-vars, no-else-return, prefer-arrow-callback, camelcase, quotes, comma-dangle, max-len */ (function() { this.Admin = (function() { @@ -42,10 +41,10 @@ return $('.change-owner-link').show(); }); $('li.project_member').bind('ajax:success', function() { - return Turbolinks.visit(location.href); + return gl.utils.refreshCurrentPage(); }); $('li.group_member').bind('ajax:success', function() { - return Turbolinks.visit(location.href); + return gl.utils.refreshCurrentPage(); }); showBlacklistType = function() { if ($("input[name='blacklist_type']:checked").val() === 'file') { @@ -61,7 +60,5 @@ } return Admin; - })(); - }).call(this); diff --git a/app/assets/javascripts/api.js b/app/assets/javascripts/api.js index f60f27d1210..b4a8c827d7f 100644 --- a/app/assets/javascripts/api.js +++ b/app/assets/javascripts/api.js @@ -1,4 +1,4 @@ -/* eslint-disable func-names, space-before-function-paren, quotes, object-shorthand, camelcase, no-var, comma-dangle, prefer-arrow-callback, indent, object-curly-spacing, quote-props, no-param-reassign, padded-blocks, max-len */ +/* eslint-disable func-names, space-before-function-paren, quotes, object-shorthand, camelcase, no-var, comma-dangle, prefer-arrow-callback, quote-props, no-param-reassign, max-len */ (function() { var Api = { @@ -29,9 +29,9 @@ return $.ajax({ url: url, data: $.extend({ - search: query, - per_page: 20 - }, options), + search: query, + per_page: 20 + }, options), dataType: "json" }).done(function(groups) { return callback(groups); @@ -73,7 +73,7 @@ return $.ajax({ url: url, type: "POST", - data: {'label': data}, + data: { 'label': data }, dataType: "json" }).done(function(label) { return callback(label); diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 043c6a11c4f..ea3f13bd00f 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -1,4 +1,4 @@ -/* eslint-disable func-names, space-before-function-paren, no-var, quotes, consistent-return, prefer-arrow-callback, comma-dangle, object-shorthand, no-new, max-len */ +/* eslint-disable func-names, space-before-function-paren, no-var, quotes, consistent-return, prefer-arrow-callback, comma-dangle, object-shorthand, no-new, max-len, no-multi-spaces, import/newline-after-import */ /* global bp */ /* global Cookies */ /* global Flash */ @@ -6,64 +6,60 @@ /* global AwardsHandler */ /* global Aside */ -// This is a manifest file that'll be compiled into including all the files listed below. -// Add new JavaScript code in separate files in this directory and they'll automatically -// be included in the compiled file accessible from http://example.com/assets/application.js -// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the -// the compiled file. -// -/*= require jquery2 */ -/*= require jquery-ui/autocomplete */ -/*= require jquery-ui/datepicker */ -/*= require jquery-ui/draggable */ -/*= require jquery-ui/effect-highlight */ -/*= require jquery-ui/sortable */ -/*= require jquery_ujs */ -/*= require jquery.endless-scroll */ -/*= require jquery.highlight */ -/*= require jquery.waitforimages */ -/*= require jquery.atwho */ -/*= require jquery.scrollTo */ -/*= require jquery.turbolinks */ -/*= require js.cookie */ -/*= require turbolinks */ -/*= require autosave */ -/*= require bootstrap/affix */ -/*= require bootstrap/alert */ -/*= require bootstrap/button */ -/*= require bootstrap/collapse */ -/*= require bootstrap/dropdown */ -/*= require bootstrap/modal */ -/*= require bootstrap/scrollspy */ -/*= require bootstrap/tab */ -/*= require bootstrap/transition */ -/*= require bootstrap/tooltip */ -/*= require bootstrap/popover */ -/*= require select2 */ -/*= require underscore */ -/*= require dropzone */ -/*= require mousetrap */ -/*= require mousetrap/pause */ -/*= require shortcuts */ -/*= require shortcuts_navigation */ -/*= require shortcuts_dashboard_navigation */ -/*= require shortcuts_issuable */ -/*= require shortcuts_network */ -/*= require jquery.nicescroll */ -/*= require date.format */ -/*= require_directory ./behaviors */ -/*= require_directory ./blob */ -/*= require_directory ./templates */ -/*= require_directory ./commit */ -/*= require_directory ./extensions */ -/*= require_directory ./lib/utils */ -/*= require_directory ./u2f */ -/*= require_directory . */ -/*= require fuzzaldrin-plus */ -/*= require es6-promise.auto */ +function requireAll(context) { return context.keys().map(context); } + +window.$ = window.jQuery = require('jquery'); +require('jquery-ui/ui/autocomplete'); +require('jquery-ui/ui/datepicker'); +require('jquery-ui/ui/draggable'); +require('jquery-ui/ui/effect-highlight'); +require('jquery-ui/ui/sortable'); +require('jquery-ujs'); +require('vendor/jquery.endless-scroll'); +require('vendor/jquery.highlight'); +require('vendor/jquery.waitforimages'); +require('vendor/jquery.caret'); +require('vendor/jquery.atwho'); +require('vendor/jquery.scrollTo'); +window.Cookies = require('vendor/js.cookie'); +require('./autosave'); +require('bootstrap/js/affix'); +require('bootstrap/js/alert'); +require('bootstrap/js/button'); +require('bootstrap/js/collapse'); +require('bootstrap/js/dropdown'); +require('bootstrap/js/modal'); +require('bootstrap/js/scrollspy'); +require('bootstrap/js/tab'); +require('bootstrap/js/transition'); +require('bootstrap/js/tooltip'); +require('bootstrap/js/popover'); +require('select2/select2.js'); +window._ = require('underscore'); +window.Dropzone = require('dropzone'); +require('mousetrap'); +require('mousetrap/plugins/pause/mousetrap-pause'); +require('./shortcuts'); +require('./shortcuts_navigation'); +require('./shortcuts_dashboard_navigation'); +require('./shortcuts_issuable'); +require('./shortcuts_network'); +require('vendor/jquery.nicescroll'); +requireAll(require.context('./behaviors', false, /^\.\/.*\.(js|es6)$/)); +requireAll(require.context('./blob', false, /^\.\/.*\.(js|es6)$/)); +requireAll(require.context('./templates', false, /^\.\/.*\.(js|es6)$/)); +requireAll(require.context('./commit', false, /^\.\/.*\.(js|es6)$/)); +requireAll(require.context('./extensions', false, /^\.\/.*\.(js|es6)$/)); +requireAll(require.context('./lib/utils', false, /^\.\/.*\.(js|es6)$/)); +requireAll(require.context('./u2f', false, /^\.\/.*\.(js|es6)$/)); +requireAll(require.context('./droplab', false, /^\.\/.*\.(js|es6)$/)); +requireAll(require.context('.', false, /^\.\/(?!application\.js).*\.(js|es6)$/)); +require('vendor/fuzzaldrin-plus'); +window.ES6Promise = require('vendor/es6-promise.auto'); +window.ES6Promise.polyfill(); (function () { - document.addEventListener('page:fetch', function () { + document.addEventListener('beforeunload', function () { // Unbind scroll events $(document).off('scroll'); // Close any open tooltips @@ -83,12 +79,19 @@ var $sidebarGutterToggle = $('.js-sidebar-toggle'); var $flash = $('.flash-container'); var bootstrapBreakpoint = bp.getBreakpointSize(); - var checkInitialSidebarSize; var fitSidebarForSize; // Set the default path for all cookies to GitLab's root directory Cookies.defaults.path = gon.relative_url_root || '/'; + // `hashchange` is not triggered when link target is already in window.location + $body.on('click', 'a[href^="#"]', function() { + var href = this.getAttribute('href'); + if (href.substr(1) === gl.utils.getLocationHash()) { + setTimeout(gl.utils.handleLocationHash, 1); + } + }); + // prevent default action for disabled buttons $('.btn').click(function(e) { if ($(this).hasClass('disabled')) { @@ -237,20 +240,14 @@ return $document.trigger('breakpoint:change', [bootstrapBreakpoint]); } }; - checkInitialSidebarSize = function () { - bootstrapBreakpoint = bp.getBreakpointSize(); - if (bootstrapBreakpoint === 'xs' || 'sm') { - return $document.trigger('breakpoint:change', [bootstrapBreakpoint]); - } - }; $window.off('resize.app').on('resize.app', function () { return fitSidebarForSize(); }); gl.awardsHandler = new AwardsHandler(); - checkInitialSidebarSize(); new Aside(); - // bind sidebar events new gl.Sidebar(); + + gl.utils.initTimeagoTimeout(); }); }).call(this); diff --git a/app/assets/javascripts/aside.js b/app/assets/javascripts/aside.js index 9417afc2ea7..8438de6cdf1 100644 --- a/app/assets/javascripts/aside.js +++ b/app/assets/javascripts/aside.js @@ -1,4 +1,4 @@ -/* eslint-disable func-names, space-before-function-paren, wrap-iife, quotes, prefer-arrow-callback, no-var, one-var, one-var-declaration-per-line, no-else-return, padded-blocks, max-len */ +/* eslint-disable func-names, space-before-function-paren, wrap-iife, quotes, prefer-arrow-callback, no-var, one-var, one-var-declaration-per-line, no-else-return, max-len */ (function() { this.Aside = (function() { function Aside() { @@ -21,7 +21,5 @@ } return Aside; - })(); - }).call(this); diff --git a/app/assets/javascripts/autosave.js b/app/assets/javascripts/autosave.js index f45dbe4cbf2..b16a2c0f73a 100644 --- a/app/assets/javascripts/autosave.js +++ b/app/assets/javascripts/autosave.js @@ -1,4 +1,4 @@ -/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-param-reassign, quotes, prefer-template, no-var, one-var, no-unused-vars, one-var-declaration-per-line, no-void, consistent-return, no-empty, padded-blocks, max-len */ +/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-param-reassign, quotes, prefer-template, no-var, one-var, no-unused-vars, one-var-declaration-per-line, no-void, consistent-return, no-empty, max-len */ (function() { this.Autosave = (function() { function Autosave(field, key) { @@ -58,7 +58,5 @@ }; return Autosave; - })(); - }).call(this); diff --git a/app/assets/javascripts/awards_handler.js b/app/assets/javascripts/awards_handler.js index 107a7978a87..9d776b74965 100644 --- a/app/assets/javascripts/awards_handler.js +++ b/app/assets/javascripts/awards_handler.js @@ -1,11 +1,13 @@ -/* eslint-disable func-names, space-before-function-paren, wrap-iife, max-len, no-var, spaced-comment, prefer-arrow-callback, consistent-return, one-var, one-var-declaration-per-line, no-unused-vars, no-else-return, prefer-template, quotes, comma-dangle, no-param-reassign, no-void, radix, keyword-spacing, space-before-blocks, brace-style, no-underscore-dangle, no-plusplus, no-return-assign, camelcase, padded-blocks */ +/* eslint-disable func-names, space-before-function-paren, wrap-iife, max-len, no-var, prefer-arrow-callback, consistent-return, one-var, one-var-declaration-per-line, no-unused-vars, no-else-return, prefer-template, quotes, comma-dangle, no-param-reassign, no-void, brace-style, no-underscore-dangle, no-return-assign, camelcase */ /* global Cookies */ +var emojiAliases = require('emoji-aliases'); + (function() { this.AwardsHandler = (function() { - var FROM_SENTENCE_REGEX = /(?:, and | and |, )/; //For separating lists produced by ruby's Array#toSentence + var FROM_SENTENCE_REGEX = /(?:, and | and |, )/; // For separating lists produced by ruby's Array#toSentence function AwardsHandler() { - this.aliases = gl.emojiAliases(); + this.aliases = emojiAliases; $(document).off('click', '.js-add-award').on('click', '.js-add-award', (function(_this) { return function(e) { e.stopPropagation(); @@ -134,7 +136,7 @@ return this.decrementCounter($emojiButton, emoji); } else { counter = $emojiButton.find('.js-counter'); - counter.text(parseInt(counter.text()) + 1); + counter.text(parseInt(counter.text(), 10) + 1); $emojiButton.addClass('active'); this.addYouToUserList(votesBlock, emoji); return this.animateEmoji($emojiButton); @@ -211,10 +213,10 @@ }; AwardsHandler.prototype.toSentence = function(list) { - if(list.length <= 2){ + if (list.length <= 2) { return list.join(' and '); } - else{ + else { return list.slice(0, -1).join(', ') + ', and ' + list[list.length - 1]; } }; @@ -339,7 +341,7 @@ if (Cookies.get('frequently_used_emojis')) { frequentlyUsedEmojis = this.getFrequentlyUsedEmojis(); ul = $("<ul class='clearfix emoji-menu-list frequent-emojis'>"); - for (i = 0, len = frequentlyUsedEmojis.length; i < len; i++) { + for (i = 0, len = frequentlyUsedEmojis.length; i < len; i += 1) { emoji = frequentlyUsedEmojis[i]; $(".emoji-menu-content [data-emoji='" + emoji + "']").closest('li').clone().appendTo(ul); } @@ -374,7 +376,5 @@ }; return AwardsHandler; - })(); - }).call(this); diff --git a/app/assets/javascripts/behaviors/autosize.js b/app/assets/javascripts/behaviors/autosize.js index c62a4c5a456..a489523b802 100644 --- a/app/assets/javascripts/behaviors/autosize.js +++ b/app/assets/javascripts/behaviors/autosize.js @@ -1,8 +1,7 @@ -/* eslint-disable func-names, space-before-function-paren, prefer-arrow-callback, no-var, consistent-return, padded-blocks, max-len */ +/* eslint-disable func-names, space-before-function-paren, prefer-arrow-callback, no-var, consistent-return, max-len */ /* global autosize */ -/*= require jquery.ba-resize */ -/*= require autosize */ +var autosize = require('vendor/autosize'); (function() { $(function() { @@ -26,5 +25,4 @@ autosize.update($fields); return $fields.css('resize', 'vertical'); }); - }).call(this); diff --git a/app/assets/javascripts/behaviors/details_behavior.js b/app/assets/javascripts/behaviors/details_behavior.js index 3998ee9a0a0..6af8f593872 100644 --- a/app/assets/javascripts/behaviors/details_behavior.js +++ b/app/assets/javascripts/behaviors/details_behavior.js @@ -1,4 +1,4 @@ -/* eslint-disable func-names, space-before-function-paren, prefer-arrow-callback, quotes, no-var, vars-on-top, padded-blocks, max-len */ +/* eslint-disable func-names, space-before-function-paren, prefer-arrow-callback, quotes, no-var, vars-on-top, max-len */ (function() { $(function() { $("body").on("click", ".js-details-target", function() { @@ -23,5 +23,4 @@ return e.preventDefault(); }); }); - }).call(this); diff --git a/app/assets/javascripts/behaviors/quick_submit.js b/app/assets/javascripts/behaviors/quick_submit.js index 586f941a6e3..7747306688c 100644 --- a/app/assets/javascripts/behaviors/quick_submit.js +++ b/app/assets/javascripts/behaviors/quick_submit.js @@ -1,4 +1,4 @@ -/* eslint-disable func-names, space-before-function-paren, one-var, no-var, one-var-declaration-per-line, prefer-arrow-callback, camelcase, consistent-return, quotes, object-shorthand, comma-dangle, padded-blocks, max-len */ +/* eslint-disable func-names, space-before-function-paren, one-var, no-var, one-var-declaration-per-line, prefer-arrow-callback, camelcase, consistent-return, quotes, object-shorthand, comma-dangle, max-len */ // Quick Submit behavior // @@ -6,7 +6,7 @@ // "Meta+Enter" (Mac) or "Ctrl+Enter" (Linux/Windows) key combination, the form // is submitted. // -/*= require extensions/jquery */ +require('../extensions/jquery'); // // ### Example Markup @@ -74,5 +74,4 @@ return $this.tooltip('hide'); }); }); - }).call(this); diff --git a/app/assets/javascripts/behaviors/requires_input.js b/app/assets/javascripts/behaviors/requires_input.js index 72362988b2e..6276933e93e 100644 --- a/app/assets/javascripts/behaviors/requires_input.js +++ b/app/assets/javascripts/behaviors/requires_input.js @@ -1,10 +1,10 @@ -/* eslint-disable func-names, space-before-function-paren, one-var, no-var, one-var-declaration-per-line, quotes, prefer-template, prefer-arrow-callback, no-else-return, consistent-return, padded-blocks, max-len */ +/* eslint-disable func-names, space-before-function-paren, one-var, no-var, one-var-declaration-per-line, quotes, prefer-template, prefer-arrow-callback, no-else-return, consistent-return, max-len */ // Requires Input behavior // // When called on a form with input fields with the `required` attribute, the // form's submit button will be disabled until all required fields have values. // -/*= require extensions/jquery */ +require('../extensions/jquery'); // // ### Example Markup @@ -59,5 +59,4 @@ return hideOrShowHelpBlock($form); }); }); - }).call(this); diff --git a/app/assets/javascripts/behaviors/toggler_behavior.js b/app/assets/javascripts/behaviors/toggler_behavior.js index 6a49715590c..a7181904ac9 100644 --- a/app/assets/javascripts/behaviors/toggler_behavior.js +++ b/app/assets/javascripts/behaviors/toggler_behavior.js @@ -1,6 +1,19 @@ /* eslint-disable wrap-iife, func-names, space-before-function-paren, prefer-arrow-callback, vars-on-top, no-var, max-len */ (function(w) { $(function() { + var toggleContainer = function(container, /* optional */toggleState) { + var $container = $(container); + + $container + .find('.js-toggle-button .fa') + .toggleClass('fa-chevron-up', toggleState) + .toggleClass('fa-chevron-down', toggleState !== undefined ? !toggleState : undefined); + + $container + .find('.js-toggle-content') + .toggle(toggleState); + }; + // Toggle button. Show/hide content inside parent container. // Button does not change visibility. If button has icon - it changes chevron style. // @@ -10,14 +23,7 @@ // $('body').on('click', '.js-toggle-button', function(e) { e.preventDefault(); - $(this) - .find('.fa') - .toggleClass('fa-chevron-down fa-chevron-up') - .end() - .closest('.js-toggle-container') - .find('.js-toggle-content') - .toggle() - ; + toggleContainer($(this).closest('.js-toggle-container')); }); // If we're accessing a permalink, ensure it is not inside a @@ -26,8 +32,8 @@ var anchor = hash && document.getElementById(hash); var container = anchor && $(anchor).closest('.js-toggle-container'); - if (container && container.find('.js-toggle-content').is(':hidden')) { - container.find('.js-toggle-button').trigger('click'); + if (container) { + toggleContainer(container, true); anchor.scrollIntoView(); } }); diff --git a/app/assets/javascripts/blob/blob_ci_yaml.js.es6 b/app/assets/javascripts/blob/blob_ci_yaml.js.es6 index 57bd13eecf8..ec1c018424d 100644 --- a/app/assets/javascripts/blob/blob_ci_yaml.js.es6 +++ b/app/assets/javascripts/blob/blob_ci_yaml.js.es6 @@ -1,9 +1,9 @@ -/* eslint-disable padded-blocks, no-param-reassign, comma-dangle */ +/* eslint-disable no-param-reassign, comma-dangle */ /* global Api */ -/*= require blob/template_selector */ -((global) => { +require('./template_selector'); +((global) => { class BlobCiYamlSelector extends gl.TemplateSelector { requestFile(query) { return Api.gitlabCiYml(query.name, this.requestFileSuccess.bind(this)); @@ -39,5 +39,4 @@ } global.BlobCiYamlSelectors = BlobCiYamlSelectors; - })(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/blob/blob_dockerfile_selector.js.es6 b/app/assets/javascripts/blob/blob_dockerfile_selector.js.es6 index bdf95017613..d4f60cc6ecd 100644 --- a/app/assets/javascripts/blob/blob_dockerfile_selector.js.es6 +++ b/app/assets/javascripts/blob/blob_dockerfile_selector.js.es6 @@ -1,5 +1,6 @@ /* global Api */ -/*= require blob/template_selector */ + +require('./template_selector'); (() => { const global = window.gl || (window.gl = {}); diff --git a/app/assets/javascripts/blob/blob_file_dropzone.js b/app/assets/javascripts/blob/blob_file_dropzone.js index eab686c45c3..04bfe363929 100644 --- a/app/assets/javascripts/blob/blob_file_dropzone.js +++ b/app/assets/javascripts/blob/blob_file_dropzone.js @@ -1,4 +1,4 @@ -/* eslint-disable func-names, space-before-function-paren, wrap-iife, one-var, no-var, one-var-declaration-per-line, camelcase, object-shorthand, quotes, comma-dangle, prefer-arrow-callback, no-unused-vars, prefer-template, no-useless-escape, no-alert, padded-blocks, max-len */ +/* eslint-disable func-names, space-before-function-paren, wrap-iife, one-var, no-var, one-var-declaration-per-line, camelcase, object-shorthand, quotes, comma-dangle, prefer-arrow-callback, no-unused-vars, prefer-template, no-useless-escape, no-alert, max-len */ /* global Dropzone */ (function() { @@ -62,7 +62,5 @@ } return BlobFileDropzone; - })(); - }).call(this); diff --git a/app/assets/javascripts/blob/blob_gitignore_selector.js b/app/assets/javascripts/blob/blob_gitignore_selector.js index 15563e429a0..1d0bcf6471f 100644 --- a/app/assets/javascripts/blob/blob_gitignore_selector.js +++ b/app/assets/javascripts/blob/blob_gitignore_selector.js @@ -1,7 +1,7 @@ -/* eslint-disable func-names, space-before-function-paren, max-len, one-var, no-var, no-restricted-syntax, vars-on-top, no-use-before-define, no-param-reassign, new-cap, no-underscore-dangle, wrap-iife, prefer-rest-params, padded-blocks */ +/* eslint-disable func-names, space-before-function-paren, max-len, one-var, no-var, no-restricted-syntax, vars-on-top, no-use-before-define, no-param-reassign, new-cap, no-underscore-dangle, wrap-iife, prefer-rest-params */ /* global Api */ -/*= require blob/template_selector */ +require('./template_selector'); (function() { var 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; }, @@ -19,7 +19,5 @@ }; return BlobGitignoreSelector; - })(gl.TemplateSelector); - }).call(this); diff --git a/app/assets/javascripts/blob/blob_gitignore_selectors.js b/app/assets/javascripts/blob/blob_gitignore_selectors.js index d7f95093688..8236457f0f1 100644 --- a/app/assets/javascripts/blob/blob_gitignore_selectors.js +++ b/app/assets/javascripts/blob/blob_gitignore_selectors.js @@ -1,4 +1,4 @@ -/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-unused-expressions, no-cond-assign, no-sequences, comma-dangle, padded-blocks, max-len */ +/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-unused-expressions, no-cond-assign, no-sequences, comma-dangle, max-len */ /* global BlobGitignoreSelector */ (function() { @@ -22,7 +22,5 @@ } return BlobGitignoreSelectors; - })(); - }).call(this); diff --git a/app/assets/javascripts/blob/blob_license_selector.js b/app/assets/javascripts/blob/blob_license_selector.js index d9c6f65a083..1d5672d4c48 100644 --- a/app/assets/javascripts/blob/blob_license_selector.js +++ b/app/assets/javascripts/blob/blob_license_selector.js @@ -1,7 +1,7 @@ -/* eslint-disable func-names, space-before-function-paren, max-len, one-var, no-var, no-restricted-syntax, vars-on-top, no-use-before-define, no-param-reassign, new-cap, no-underscore-dangle, wrap-iife, prefer-rest-params, comma-dangle, padded-blocks */ +/* eslint-disable func-names, space-before-function-paren, max-len, one-var, no-var, no-restricted-syntax, vars-on-top, no-use-before-define, no-param-reassign, new-cap, no-underscore-dangle, wrap-iife, prefer-rest-params, comma-dangle */ /* global Api */ -/*= require blob/template_selector */ +require('./template_selector'); (function() { var 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; }, @@ -24,7 +24,5 @@ }; return BlobLicenseSelector; - })(gl.TemplateSelector); - }).call(this); diff --git a/app/assets/javascripts/blob/blob_license_selectors.js.es6 b/app/assets/javascripts/blob/blob_license_selectors.js.es6 index 268640681d4..c5067b0feae 100644 --- a/app/assets/javascripts/blob/blob_license_selectors.js.es6 +++ b/app/assets/javascripts/blob/blob_license_selectors.js.es6 @@ -1,4 +1,4 @@ -/* eslint-disable no-unused-vars, no-param-reassign, padded-blocks */ +/* eslint-disable no-unused-vars, no-param-reassign */ /* global BlobLicenseSelector */ ((global) => { @@ -20,5 +20,4 @@ } global.BlobLicenseSelectors = BlobLicenseSelectors; - })(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/blob/template_selector.js.es6 b/app/assets/javascripts/blob/template_selector.js.es6 index 7a1ee9998c8..7e03ec3b391 100644 --- a/app/assets/javascripts/blob/template_selector.js.es6 +++ b/app/assets/javascripts/blob/template_selector.js.es6 @@ -1,101 +1,101 @@ -/* eslint-disable indent, comma-dangle, object-shorthand, func-names, space-before-function-paren, arrow-parens, no-unused-vars, class-methods-use-this, no-var, consistent-return, prefer-const, no-param-reassign, space-in-parens, max-len */ +/* eslint-disable comma-dangle, object-shorthand, func-names, space-before-function-paren, arrow-parens, no-unused-vars, class-methods-use-this, no-var, consistent-return, no-param-reassign, max-len */ ((global) => { - class TemplateSelector { - constructor({ dropdown, data, pattern, wrapper, editor, fileEndpoint, $input } = {}) { - this.onClick = this.onClick.bind(this); - this.dropdown = dropdown; - this.data = data; - this.pattern = pattern; - this.wrapper = wrapper; - this.editor = editor; - this.fileEndpoint = fileEndpoint; - this.$input = $input || $('#file_name'); - this.dropdownIcon = $('.fa-chevron-down', this.dropdown); - this.buildDropdown(); - this.bindEvents(); - this.onFilenameUpdate(); + class TemplateSelector { + constructor({ dropdown, data, pattern, wrapper, editor, fileEndpoint, $input } = {}) { + this.onClick = this.onClick.bind(this); + this.dropdown = dropdown; + this.data = data; + this.pattern = pattern; + this.wrapper = wrapper; + this.editor = editor; + this.fileEndpoint = fileEndpoint; + this.$input = $input || $('#file_name'); + this.dropdownIcon = $('.fa-chevron-down', this.dropdown); + this.buildDropdown(); + this.bindEvents(); + this.onFilenameUpdate(); - this.autosizeUpdateEvent = document.createEvent('Event'); - this.autosizeUpdateEvent.initEvent('autosize:update', true, false); - } + this.autosizeUpdateEvent = document.createEvent('Event'); + this.autosizeUpdateEvent.initEvent('autosize:update', true, false); + } - buildDropdown() { - return this.dropdown.glDropdown({ - data: this.data, - filterable: true, - selectable: true, - toggleLabel: this.toggleLabel, - search: { - fields: ['name'] - }, - clicked: this.onClick, - text: function(item) { - return item.name; - } - }); - } + buildDropdown() { + return this.dropdown.glDropdown({ + data: this.data, + filterable: true, + selectable: true, + toggleLabel: this.toggleLabel, + search: { + fields: ['name'] + }, + clicked: this.onClick, + text: function(item) { + return item.name; + } + }); + } - bindEvents() { - return this.$input.on('keyup blur', (e) => this.onFilenameUpdate()); - } + bindEvents() { + return this.$input.on('keyup blur', (e) => this.onFilenameUpdate()); + } - toggleLabel(item) { - return item.name; - } + toggleLabel(item) { + return item.name; + } - onFilenameUpdate() { - var filenameMatches; - if (!this.$input.length) { - return; - } - filenameMatches = this.pattern.test(this.$input.val().trim()); - if (!filenameMatches) { - this.wrapper.addClass('hidden'); - return; - } - return this.wrapper.removeClass('hidden'); + onFilenameUpdate() { + var filenameMatches; + if (!this.$input.length) { + return; } - - onClick(item, el, e) { - e.preventDefault(); - return this.requestFile(item); + filenameMatches = this.pattern.test(this.$input.val().trim()); + if (!filenameMatches) { + this.wrapper.addClass('hidden'); + return; } + return this.wrapper.removeClass('hidden'); + } - requestFile(item) { - // This `requestFile` method is an abstract method that should - // be added by all subclasses. - } + onClick(item, el, e) { + e.preventDefault(); + return this.requestFile(item); + } - // To be implemented on the extending class - // e.g. - // Api.gitignoreText item.name, @requestFileSuccess.bind(@) - requestFileSuccess(file, { skipFocus } = {}) { - if (!file) return; + requestFile(item) { + // This `requestFile` method is an abstract method that should + // be added by all subclasses. + } - const oldValue = this.editor.getValue(); - let newValue = file.content; + // To be implemented on the extending class + // e.g. + // Api.gitignoreText item.name, @requestFileSuccess.bind(@) + requestFileSuccess(file, { skipFocus } = {}) { + if (!file) return; - this.editor.setValue(newValue, 1); - if (!skipFocus) this.editor.focus(); + const oldValue = this.editor.getValue(); + const newValue = file.content; - if (this.editor instanceof jQuery) { - this.editor.get(0).dispatchEvent(this.autosizeUpdateEvent); - } - } + this.editor.setValue(newValue, 1); + if (!skipFocus) this.editor.focus(); - startLoadingSpinner() { - this.dropdownIcon - .addClass('fa-spinner fa-spin') - .removeClass('fa-chevron-down'); + if (this.editor instanceof jQuery) { + this.editor.get(0).dispatchEvent(this.autosizeUpdateEvent); } + } - stopLoadingSpinner() { - this.dropdownIcon - .addClass('fa-chevron-down') - .removeClass('fa-spinner fa-spin'); - } + startLoadingSpinner() { + this.dropdownIcon + .addClass('fa-spinner fa-spin') + .removeClass('fa-chevron-down'); + } + + stopLoadingSpinner() { + this.dropdownIcon + .addClass('fa-chevron-down') + .removeClass('fa-spinner fa-spin'); } + } - global.TemplateSelector = TemplateSelector; - })(window.gl || ( window.gl = {})); + global.TemplateSelector = TemplateSelector; +})(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/blob_edit/blob_edit_bundle.js b/app/assets/javascripts/blob_edit/blob_edit_bundle.js index 8c40e36a80a..9e0754819fa 100644 --- a/app/assets/javascripts/blob_edit/blob_edit_bundle.js +++ b/app/assets/javascripts/blob_edit/blob_edit_bundle.js @@ -1,8 +1,8 @@ -/* eslint-disable func-names, space-before-function-paren, prefer-arrow-callback, no-var, quotes, vars-on-top, no-unused-vars, no-new, padded-blocks, max-len */ +/* eslint-disable func-names, space-before-function-paren, prefer-arrow-callback, no-var, quotes, vars-on-top, no-unused-vars, no-new, max-len */ /* global EditBlob */ /* global NewCommitForm */ -/*= require_tree . */ +require('./edit_blob'); (function() { $(function() { @@ -12,5 +12,4 @@ var blob = new EditBlob(url, $('.js-edit-blob-form').data('blob-language')); new NewCommitForm($('.js-edit-blob-form')); }); - }).call(this); diff --git a/app/assets/javascripts/blob_edit/edit_blob.js b/app/assets/javascripts/blob_edit/edit_blob.js index fa43ff611cc..079445e8278 100644 --- a/app/assets/javascripts/blob_edit/edit_blob.js +++ b/app/assets/javascripts/blob_edit/edit_blob.js @@ -1,9 +1,9 @@ -/* eslint-disable func-names, space-before-function-paren, no-var, space-before-blocks, prefer-rest-params, wrap-iife, camelcase, no-param-reassign, quotes, prefer-template, no-new, comma-dangle, one-var, one-var-declaration-per-line, prefer-arrow-callback, no-else-return, no-unused-vars, padded-blocks, max-len */ +/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, camelcase, no-param-reassign, quotes, prefer-template, no-new, comma-dangle, one-var, one-var-declaration-per-line, prefer-arrow-callback, no-else-return, no-unused-vars, max-len */ /* global ace */ /* global BlobGitignoreSelectors */ (function() { - var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; + var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; }; this.EditBlob = (function() { function EditBlob(assets_path, ace_mode) { @@ -84,7 +84,5 @@ }; return EditBlob; - })(); - }).call(this); diff --git a/app/assets/javascripts/boards/boards_bundle.js.es6 b/app/assets/javascripts/boards/boards_bundle.js.es6 index ab2343c72fc..c345fb6ce14 100644 --- a/app/assets/javascripts/boards/boards_bundle.js.es6 +++ b/app/assets/javascripts/boards/boards_bundle.js.es6 @@ -1,23 +1,27 @@ -/* eslint-disable one-var, indent, quote-props, comma-dangle, space-before-function-paren */ +/* eslint-disable one-var, quote-props, comma-dangle, space-before-function-paren, import/newline-after-import, no-multi-spaces, max-len */ /* global Vue */ /* global BoardService */ -//= require vue -//= require vue-resource -//= require Sortable -//= require_tree ./models -//= require_tree ./stores -//= require_tree ./services -//= require_tree ./mixins -//= require_tree ./filters -//= require ./components/board -//= require ./components/board_sidebar -//= require ./components/new_list_dropdown -//= require ./vue_resource_interceptor +function requireAll(context) { return context.keys().map(context); } + +window.Vue = require('vue'); +window.Vue.use(require('vue-resource')); +window.Sortable = require('vendor/Sortable'); +requireAll(require.context('./models', true, /^\.\/.*\.(js|es6)$/)); +requireAll(require.context('./stores', true, /^\.\/.*\.(js|es6)$/)); +requireAll(require.context('./services', true, /^\.\/.*\.(js|es6)$/)); +requireAll(require.context('./mixins', true, /^\.\/.*\.(js|es6)$/)); +requireAll(require.context('./filters', true, /^\.\/.*\.(js|es6)$/)); +require('./components/board'); +require('./components/board_sidebar'); +require('./components/new_list_dropdown'); +require('./components/modal/index'); +require('../vue_shared/vue_resource_interceptor'); $(() => { - const $boardApp = document.getElementById('board-app'), - Store = gl.issueBoards.BoardsStore; + const $boardApp = document.getElementById('board-app'); + const Store = gl.issueBoards.BoardsStore; + const ModalStore = gl.issueBoards.ModalStore; window.gl = window.gl || {}; @@ -31,7 +35,8 @@ $(() => { el: $boardApp, components: { 'board': gl.issueBoards.Board, - 'board-sidebar': gl.issueBoards.BoardSidebar + 'board-sidebar': gl.issueBoards.BoardSidebar, + 'board-add-issues-modal': gl.issueBoards.IssuesModal, }, data: { state: Store.state, @@ -40,6 +45,8 @@ $(() => { boardId: $boardApp.dataset.boardId, disabled: $boardApp.dataset.disabled === 'true', issueLinkBase: $boardApp.dataset.issueLinkBase, + rootPath: $boardApp.dataset.rootPath, + bulkUpdatePath: $boardApp.dataset.bulkUpdatePath, detailIssue: Store.detail }, computed: { @@ -48,7 +55,7 @@ $(() => { }, }, created () { - gl.boardService = new BoardService(this.endpoint, this.boardId); + gl.boardService = new BoardService(this.endpoint, this.bulkUpdatePath, this.boardId); }, mounted () { Store.disabled = this.disabled; @@ -59,8 +66,6 @@ $(() => { if (list.type === 'done') { list.position = Infinity; - } else if (list.type === 'backlog') { - list.position = -1; } }); @@ -73,7 +78,7 @@ $(() => { }); gl.IssueBoardsSearch = new Vue({ - el: '#js-boards-seach', + el: document.getElementById('js-boards-search'), data: { filters: Store.state.filters }, @@ -81,4 +86,27 @@ $(() => { gl.issueBoards.newListDropdownInit(); } }); + + gl.IssueBoardsModalAddBtn = new Vue({ + mixins: [gl.issueBoards.ModalMixins], + el: document.getElementById('js-add-issues-btn'), + data: { + modal: ModalStore.store, + store: Store.state, + }, + computed: { + disabled() { + return Store.shouldAddBlankState(); + }, + }, + template: ` + <button + class="btn btn-create pull-right prepend-left-10 has-tooltip" + type="button" + :disabled="disabled" + @click="toggleModal(true)"> + Add issues + </button> + `, + }); }); diff --git a/app/assets/javascripts/boards/components/board.js.es6 b/app/assets/javascripts/boards/components/board.js.es6 index d1fb0ec48e0..18324de18b3 100644 --- a/app/assets/javascripts/boards/components/board.js.es6 +++ b/app/assets/javascripts/boards/components/board.js.es6 @@ -1,10 +1,10 @@ -/* eslint-disable comma-dangle, space-before-function-paren, one-var, indent, radix */ +/* eslint-disable comma-dangle, space-before-function-paren, one-var */ /* global Vue */ /* global Sortable */ -//= require ./board_blank_state -//= require ./board_delete -//= require ./board_list +require('./board_blank_state'); +require('./board_delete'); +require('./board_list'); (() => { const Store = gl.issueBoards.BoardsStore; @@ -22,7 +22,8 @@ props: { list: Object, disabled: Boolean, - issueLinkBase: String + issueLinkBase: String, + rootPath: String, }, data () { return { @@ -45,14 +46,28 @@ const issue = this.list.findIssue(this.detailIssue.issue.id); if (issue) { + const offsetLeft = this.$el.offsetLeft; const boardsList = document.querySelectorAll('.boards-list')[0]; - const right = (this.$el.offsetLeft + this.$el.offsetWidth) - boardsList.offsetWidth; - const left = boardsList.scrollLeft - this.$el.offsetLeft; + const left = boardsList.scrollLeft - offsetLeft; + let right = (offsetLeft + this.$el.offsetWidth); + + if (window.innerWidth > 768 && boardsList.classList.contains('is-compact')) { + // -290 here because width of boardsList is animating so therefore + // getting the width here is incorrect + // 290 is the width of the sidebar + right -= (boardsList.offsetWidth - 290); + } else { + right -= boardsList.offsetWidth; + } if (right - boardsList.scrollLeft > 0) { - boardsList.scrollLeft = right; + $(boardsList).animate({ + scrollLeft: right + }, this.sortableOptions.animation); } else if (left > 0) { - boardsList.scrollLeft = this.$el.offsetLeft; + $(boardsList).animate({ + scrollLeft: offsetLeft + }, this.sortableOptions.animation); } } }, @@ -65,7 +80,7 @@ } }, mounted () { - const options = gl.issueBoards.getBoardSortableDefaultOptions({ + this.sortableOptions = gl.issueBoards.getBoardSortableDefaultOptions({ disabled: this.disabled, group: 'boards', draggable: '.is-draggable', @@ -74,8 +89,8 @@ gl.issueBoards.onEnd(); if (e.newIndex !== undefined && e.oldIndex !== e.newIndex) { - const order = this.sortable.toArray(), - list = Store.findList('id', parseInt(e.item.dataset.id)); + const order = this.sortable.toArray(); + const list = Store.findList('id', parseInt(e.item.dataset.id, 10)); this.$nextTick(() => { Store.moveList(list, order); @@ -84,7 +99,7 @@ } }); - this.sortable = Sortable.create(this.$el.parentNode, options); + this.sortable = Sortable.create(this.$el.parentNode, this.sortableOptions); }, }); })(); diff --git a/app/assets/javascripts/boards/components/board_blank_state.js.es6 b/app/assets/javascripts/boards/components/board_blank_state.js.es6 index 0a47a22fad2..d76314c1892 100644 --- a/app/assets/javascripts/boards/components/board_blank_state.js.es6 +++ b/app/assets/javascripts/boards/components/board_blank_state.js.es6 @@ -1,4 +1,4 @@ -/* eslint-disable space-before-function-paren, comma-dangle, semi */ +/* eslint-disable space-before-function-paren, comma-dangle */ /* global Vue */ /* global ListLabel */ @@ -15,7 +15,7 @@ new ListLabel({ title: 'To Do', color: '#F0AD4E' }), new ListLabel({ title: 'Doing', color: '#5CB85C' }) ] - } + }; }, methods: { addDefaultLists () { diff --git a/app/assets/javascripts/boards/components/board_card.js.es6 b/app/assets/javascripts/boards/components/board_card.js.es6 index 5fc50280811..0ea66bd027c 100644 --- a/app/assets/javascripts/boards/components/board_card.js.es6 +++ b/app/assets/javascripts/boards/components/board_card.js.es6 @@ -1,6 +1,8 @@ /* eslint-disable comma-dangle, space-before-function-paren, dot-notation */ /* global Vue */ +require('./issue_card_inner'); + (() => { const Store = gl.issueBoards.BoardsStore; @@ -9,12 +11,16 @@ gl.issueBoards.BoardCard = Vue.extend({ template: '#js-board-list-card', + components: { + 'issue-card-inner': gl.issueBoards.IssueCardInner, + }, props: { list: Object, issue: Object, issueLinkBase: String, disabled: Boolean, - index: Number + index: Number, + rootPath: String, }, data () { return { @@ -28,31 +34,6 @@ } }, methods: { - filterByLabel (label, e) { - let labelToggleText = label.title; - const labelIndex = Store.state.filters['label_name'].indexOf(label.title); - $(e.target).tooltip('hide'); - - if (labelIndex === -1) { - Store.state.filters['label_name'].push(label.title); - $('.labels-filter').prepend(`<input type="hidden" name="label_name[]" value="${label.title}" />`); - } else { - Store.state.filters['label_name'].splice(labelIndex, 1); - labelToggleText = Store.state.filters['label_name'][0]; - $(`.labels-filter input[name="label_name[]"][value="${label.title}"]`).remove(); - } - - const selectedLabels = Store.state.filters['label_name']; - if (selectedLabels.length === 0) { - labelToggleText = 'Label'; - } else if (selectedLabels.length > 1) { - labelToggleText = `${selectedLabels[0]} + ${selectedLabels.length - 1} more`; - } - - $('.labels-filter .dropdown-toggle-text').text(labelToggleText); - - Store.updateFiltersUrl(); - }, mouseDown () { this.showDetail = true; }, @@ -71,6 +52,7 @@ Store.detail.issue = {}; } else { Store.detail.issue = this.issue; + Store.detail.list = this.list; } } } diff --git a/app/assets/javascripts/boards/components/board_list.js.es6 b/app/assets/javascripts/boards/components/board_list.js.es6 index 6711930622b..60b0a30af3f 100644 --- a/app/assets/javascripts/boards/components/board_list.js.es6 +++ b/app/assets/javascripts/boards/components/board_list.js.es6 @@ -1,9 +1,9 @@ -/* eslint-disable comma-dangle, space-before-function-paren, max-len, no-plusplus */ +/* eslint-disable comma-dangle, space-before-function-paren, max-len */ /* global Vue */ /* global Sortable */ -//= require ./board_card -//= require ./board_new_issue +require('./board_card'); +require('./board_new_issue'); (() => { const Store = gl.issueBoards.BoardsStore; @@ -23,6 +23,7 @@ issues: Array, loading: Boolean, issueLinkBase: String, + rootPath: String, }, data () { return { @@ -43,7 +44,7 @@ issues () { this.$nextTick(() => { if (this.scrollHeight() <= this.listHeight() && this.list.issuesSize > this.list.issues.length) { - this.list.page++; + this.list.page += 1; this.list.getIssues(false); } diff --git a/app/assets/javascripts/boards/components/board_new_issue.js.es6 b/app/assets/javascripts/boards/components/board_new_issue.js.es6 index 2386d3a613c..b5c14a198ba 100644 --- a/app/assets/javascripts/boards/components/board_new_issue.js.es6 +++ b/app/assets/javascripts/boards/components/board_new_issue.js.es6 @@ -37,6 +37,7 @@ $(this.$refs.submitButton).enable(); Store.detail.issue = issue; + Store.detail.list = this.list; }) .catch(() => { // Need this because our jQuery very kindly disables buttons on ALL form submissions diff --git a/app/assets/javascripts/boards/components/board_sidebar.js.es6 b/app/assets/javascripts/boards/components/board_sidebar.js.es6 index 02459722bbf..dfc6eed785c 100644 --- a/app/assets/javascripts/boards/components/board_sidebar.js.es6 +++ b/app/assets/javascripts/boards/components/board_sidebar.js.es6 @@ -5,6 +5,8 @@ /* global LabelsSelect */ /* global Sidebar */ +require('./sidebar/remove_issue'); + (() => { const Store = gl.issueBoards.BoardsStore; @@ -18,7 +20,8 @@ data() { return { detail: Store.detail, - issue: {} + issue: {}, + list: {}, }; }, computed: { @@ -29,7 +32,14 @@ watch: { detail: { handler () { + if (this.issue.id !== this.detail.issue.id) { + $('.js-issue-board-sidebar', this.$el).each((i, el) => { + $(el).data('glDropdown').clearMenu(); + }); + } + this.issue = this.detail.issue; + this.list = this.detail.list; }, deep: true }, @@ -54,6 +64,9 @@ new LabelsSelect(); new Sidebar(); gl.Subscription.bindAll('.subscription'); - } + }, + components: { + removeBtn: gl.issueBoards.RemoveIssueBtn, + }, }); })(); diff --git a/app/assets/javascripts/boards/components/issue_card_inner.js.es6 b/app/assets/javascripts/boards/components/issue_card_inner.js.es6 new file mode 100644 index 00000000000..22a8b971ff8 --- /dev/null +++ b/app/assets/javascripts/boards/components/issue_card_inner.js.es6 @@ -0,0 +1,111 @@ +/* global Vue */ +(() => { + const Store = gl.issueBoards.BoardsStore; + + window.gl = window.gl || {}; + window.gl.issueBoards = window.gl.issueBoards || {}; + + gl.issueBoards.IssueCardInner = Vue.extend({ + props: { + issue: { + type: Object, + required: true, + }, + issueLinkBase: { + type: String, + required: true, + }, + list: { + type: Object, + required: false, + }, + rootPath: { + type: String, + required: true, + }, + }, + methods: { + showLabel(label) { + if (!this.list) return true; + + return !this.list.label || label.id !== this.list.label.id; + }, + filterByLabel(label, e) { + let labelToggleText = label.title; + const labelIndex = Store.state.filters.label_name.indexOf(label.title); + $(e.currentTarget).tooltip('hide'); + + if (labelIndex === -1) { + Store.state.filters.label_name.push(label.title); + $('.labels-filter').prepend(`<input type="hidden" name="label_name[]" value="${label.title}" />`); + } else { + Store.state.filters.label_name.splice(labelIndex, 1); + labelToggleText = Store.state.filters.label_name[0]; + $(`.labels-filter input[name="label_name[]"][value="${label.title}"]`).remove(); + } + + const selectedLabels = Store.state.filters.label_name; + if (selectedLabels.length === 0) { + labelToggleText = 'Label'; + } else if (selectedLabels.length > 1) { + labelToggleText = `${selectedLabels[0]} + ${selectedLabels.length - 1} more`; + } + + $('.labels-filter .dropdown-toggle-text').text(labelToggleText); + + Store.updateFiltersUrl(); + }, + labelStyle(label) { + return { + backgroundColor: label.color, + color: label.textColor, + }; + }, + }, + template: ` + <div> + <h4 class="card-title"> + <i + class="fa fa-eye-slash confidential-icon" + v-if="issue.confidential"></i> + <a + :href="issueLinkBase + '/' + issue.id" + :title="issue.title"> + {{ issue.title }} + </a> + </h4> + <div class="card-footer"> + <span + class="card-number" + v-if="issue.id"> + #{{ issue.id }} + </span> + <a + class="card-assignee has-tooltip" + :href="rootPath + issue.assignee.username" + :title="'Assigned to ' + issue.assignee.name" + v-if="issue.assignee" + data-container="body"> + <img + class="avatar avatar-inline s20" + :src="issue.assignee.avatar" + width="20" + height="20" + :alt="'Avatar for ' + issue.assignee.name" /> + </a> + <button + class="label color-label has-tooltip" + v-for="label in issue.labels" + type="button" + v-if="showLabel(label)" + @click="filterByLabel(label, $event)" + :style="labelStyle(label)" + :title="label.description" + data-container="body"> + {{ label.title }} + </button> + </div> + </div> + `, + }); +})(); diff --git a/app/assets/javascripts/boards/components/modal/empty_state.js.es6 b/app/assets/javascripts/boards/components/modal/empty_state.js.es6 new file mode 100644 index 00000000000..9538f5b69e9 --- /dev/null +++ b/app/assets/javascripts/boards/components/modal/empty_state.js.es6 @@ -0,0 +1,70 @@ +/* global Vue */ +(() => { + const ModalStore = gl.issueBoards.ModalStore; + + gl.issueBoards.ModalEmptyState = Vue.extend({ + mixins: [gl.issueBoards.ModalMixins], + data() { + return ModalStore.store; + }, + props: { + image: { + type: String, + required: true, + }, + newIssuePath: { + type: String, + required: true, + }, + }, + computed: { + contents() { + const obj = { + title: 'You haven\'t added any issues to your project yet', + content: ` + An issue can be a bug, a todo or a feature request that needs to be + discussed in a project. Besides, issues are searchable and filterable. + `, + }; + + if (this.activeTab === 'selected') { + obj.title = 'You haven\'t selected any issues yet'; + obj.content = ` + Go back to <strong>All issues</strong> and select some issues + to add to your board. + `; + } + + return obj; + }, + }, + template: ` + <section class="empty-state"> + <div class="row"> + <div class="col-xs-12 col-sm-6 col-sm-push-6"> + <aside class="svg-content" v-html="image"></aside> + </div> + <div class="col-xs-12 col-sm-6 col-sm-pull-6"> + <div class="text-content"> + <h4>{{ contents.title }}</h4> + <p v-html="contents.content"></p> + <a + :href="newIssuePath" + class="btn btn-success btn-inverted" + v-if="activeTab === 'all'"> + New issue + </a> + <button + type="button" + class="btn btn-default" + @click="changeTab('all')" + v-if="activeTab === 'selected'"> + All issues + </button> + </div> + </div> + </div> + </section> + `, + }); +})(); diff --git a/app/assets/javascripts/boards/components/modal/filters.js.es6 b/app/assets/javascripts/boards/components/modal/filters.js.es6 new file mode 100644 index 00000000000..6de06811d94 --- /dev/null +++ b/app/assets/javascripts/boards/components/modal/filters.js.es6 @@ -0,0 +1,49 @@ +/* global Vue */ +const userFilter = require('./filters/user'); +const milestoneFilter = require('./filters/milestone'); +const labelFilter = require('./filters/label'); + +module.exports = Vue.extend({ + name: 'modal-filters', + props: { + projectId: { + type: Number, + required: true, + }, + milestonePath: { + type: String, + required: true, + }, + labelPath: { + type: String, + required: true, + }, + }, + destroyed() { + gl.issueBoards.ModalStore.setDefaultFilter(); + }, + components: { + userFilter, + milestoneFilter, + labelFilter, + }, + template: ` + <div class="modal-filters"> + <user-filter + dropdown-class-name="dropdown-menu-author" + toggle-class-name="js-user-search js-author-search" + toggle-label="Author" + field-name="author_id" + :project-id="projectId"></user-filter> + <user-filter + dropdown-class-name="dropdown-menu-author" + toggle-class-name="js-assignee-search" + toggle-label="Assignee" + field-name="assignee_id" + :null-user="true" + :project-id="projectId"></user-filter> + <milestone-filter :milestone-path="milestonePath"></milestone-filter> + <label-filter :label-path="labelPath"></label-filter> + </div> + `, +}); diff --git a/app/assets/javascripts/boards/components/modal/filters/label.js.es6 b/app/assets/javascripts/boards/components/modal/filters/label.js.es6 new file mode 100644 index 00000000000..4fc8f72a145 --- /dev/null +++ b/app/assets/javascripts/boards/components/modal/filters/label.js.es6 @@ -0,0 +1,54 @@ +/* eslint-disable no-new */ +/* global Vue */ +/* global LabelsSelect */ +module.exports = Vue.extend({ + name: 'filter-label', + props: { + labelPath: { + type: String, + required: true, + }, + }, + mounted() { + new LabelsSelect(this.$refs.dropdown); + }, + template: ` + <div class="dropdown"> + <button + class="dropdown-menu-toggle js-label-select js-multiselect js-extra-options" + type="button" + data-toggle="dropdown" + data-show-any="true" + data-show-no="true" + :data-labels="labelPath" + ref="dropdown"> + <span class="dropdown-toggle-text"> + Label + </span> + <i class="fa fa-chevron-down"></i> + </button> + <div class="dropdown-menu dropdown-select dropdown-menu-paging dropdown-menu-labels dropdown-menu-selectable"> + <div class="dropdown-title"> + Filter by label + <button + class="dropdown-title-button dropdown-menu-close" + aria-label="Close" + type="button"> + <i class="fa fa-times dropdown-menu-close-icon"></i> + </button> + </div> + <div class="dropdown-input"> + <input + type="search" + class="dropdown-input-field" + placeholder="Search" + autocomplete="off" /> + <i class="fa fa-search dropdown-input-search"></i> + <i role="button" class="fa fa-times dropdown-input-clear js-dropdown-input-clear"></i> + </div> + <div class="dropdown-content"></div> + <div class="dropdown-loading"><i class="fa fa-spinner fa-spin"></i></div> + </div> + </div> + `, +}); diff --git a/app/assets/javascripts/boards/components/modal/filters/milestone.js.es6 b/app/assets/javascripts/boards/components/modal/filters/milestone.js.es6 new file mode 100644 index 00000000000..d555599d300 --- /dev/null +++ b/app/assets/javascripts/boards/components/modal/filters/milestone.js.es6 @@ -0,0 +1,55 @@ +/* eslint-disable no-new */ +/* global Vue */ +/* global MilestoneSelect */ +module.exports = Vue.extend({ + name: 'filter-milestone', + props: { + milestonePath: { + type: String, + required: true, + }, + }, + mounted() { + new MilestoneSelect(null, this.$refs.dropdown); + }, + template: ` + <div class="dropdown"> + <button + class="dropdown-menu-toggle js-milestone-select" + type="button" + data-toggle="dropdown" + data-show-any="true" + data-show-upcoming="true" + data-field-name="milestone_title" + :data-milestones="milestonePath" + ref="dropdown"> + <span class="dropdown-toggle-text"> + Milestone + </span> + <i class="fa fa-chevron-down"></i> + </button> + <div class="dropdown-menu dropdown-select dropdown-menu-selectable dropdown-menu-milestone"> + <div class="dropdown-title"> + <span>Filter by milestone</span> + <button + class="dropdown-title-button dropdown-menu-close" + aria-label="Close" + type="button"> + <i class="fa fa-times dropdown-menu-close-icon"></i> + </button> + </div> + <div class="dropdown-input"> + <input + type="search" + class="dropdown-input-field" + placeholder="Search milestones" + autocomplete="off" /> + <i class="fa fa-search dropdown-input-search"></i> + <i role="button" class="fa fa-times dropdown-input-clear js-dropdown-input-clear"></i> + </div> + <div class="dropdown-content"></div> + <div class="dropdown-loading"><i class="fa fa-spinner fa-spin"></i></div> + </div> + </div> + `, +}); diff --git a/app/assets/javascripts/boards/components/modal/filters/user.js.es6 b/app/assets/javascripts/boards/components/modal/filters/user.js.es6 new file mode 100644 index 00000000000..8523028c29c --- /dev/null +++ b/app/assets/javascripts/boards/components/modal/filters/user.js.es6 @@ -0,0 +1,96 @@ +/* eslint-disable no-new */ +/* global Vue */ +/* global UsersSelect */ +module.exports = Vue.extend({ + name: 'filter-user', + props: { + toggleClassName: { + type: String, + required: true, + }, + dropdownClassName: { + type: String, + required: false, + default: '', + }, + toggleLabel: { + type: String, + required: true, + }, + fieldName: { + type: String, + required: true, + }, + nullUser: { + type: Boolean, + required: false, + default: false, + }, + projectId: { + type: Number, + required: true, + }, + }, + mounted() { + new UsersSelect(null, this.$refs.dropdown); + }, + computed: { + currentUsername() { + return gon.current_username; + }, + dropdownTitle() { + return `Filter by ${this.toggleLabel.toLowerCase()}`; + }, + inputPlaceholder() { + return `Search ${this.toggleLabel.toLowerCase()}`; + }, + }, + template: ` + <div class="dropdown"> + <button + class="dropdown-menu-toggle js-user-search" + :class="toggleClassName" + type="button" + data-toggle="dropdown" + data-current-user="true" + :data-any-user="'Any ' + toggleLabel" + :data-null-user="nullUser" + :data-field-name="fieldName" + :data-project-id="projectId" + :data-first-user="currentUsername" + ref="dropdown"> + <span class="dropdown-toggle-text"> + {{ toggleLabel }} + </span> + <i class="fa fa-chevron-down"></i> + </button> + <div + class="dropdown-menu dropdown-select dropdown-menu-user dropdown-menu-selectable" + :class="dropdownClassName"> + <div class="dropdown-title"> + {{ dropdownTitle }} + <button + class="dropdown-title-button dropdown-menu-close" + aria-label="Close" + type="button"> + <i class="fa fa-times dropdown-menu-close-icon"></i> + </button> + </div> + <div class="dropdown-input"> + <input + type="search" + class="dropdown-input-field" + autocomplete="off" + :placeholder="inputPlaceholder" /> + <i class="fa fa-search dropdown-input-search"></i> + <i + role="button" + class="fa fa-times dropdown-input-clear js-dropdown-input-clear"> + </i> + </div> + <div class="dropdown-content"></div> + <div class="dropdown-loading"><i class="fa fa-spinner fa-spin"></i></div> + </div> + </div> + `, +}); diff --git a/app/assets/javascripts/boards/components/modal/footer.js.es6 b/app/assets/javascripts/boards/components/modal/footer.js.es6 new file mode 100644 index 00000000000..1cbc422c961 --- /dev/null +++ b/app/assets/javascripts/boards/components/modal/footer.js.es6 @@ -0,0 +1,83 @@ +/* eslint-disable no-new */ +/* global Vue */ +/* global Flash */ + +require('./lists_dropdown'); + +(() => { + const ModalStore = gl.issueBoards.ModalStore; + + gl.issueBoards.ModalFooter = Vue.extend({ + mixins: [gl.issueBoards.ModalMixins], + data() { + return { + modal: ModalStore.store, + state: gl.issueBoards.BoardsStore.state, + }; + }, + computed: { + submitDisabled() { + return !ModalStore.selectedCount(); + }, + submitText() { + const count = ModalStore.selectedCount(); + + return `Add ${count > 0 ? count : ''} ${gl.text.pluralize('issue', count)}`; + }, + }, + methods: { + addIssues() { + const list = this.modal.selectedList || this.state.lists[0]; + const selectedIssues = ModalStore.getSelectedIssues(); + const issueIds = selectedIssues.map(issue => issue.globalId); + + // Post the data to the backend + gl.boardService.bulkUpdate(issueIds, { + add_label_ids: [list.label.id], + }).catch(() => { + new Flash('Failed to update issues, please try again.', 'alert'); + + selectedIssues.forEach((issue) => { + list.removeIssue(issue); + list.issuesSize -= 1; + }); + }); + + // Add the issues on the frontend + selectedIssues.forEach((issue) => { + list.addIssue(issue); + list.issuesSize += 1; + }); + + this.toggleModal(false); + }, + }, + components: { + 'lists-dropdown': gl.issueBoards.ModalFooterListsDropdown, + }, + template: ` + <footer + class="form-actions add-issues-footer"> + <div class="pull-left"> + <button + class="btn btn-success" + type="button" + :disabled="submitDisabled" + @click="addIssues"> + {{ submitText }} + </button> + <span class="inline add-issues-footer-to-list"> + to list + </span> + <lists-dropdown></lists-dropdown> + </div> + <button + class="btn btn-default pull-right" + type="button" + @click="toggleModal(false)"> + Cancel + </button> + </footer> + `, + }); +})(); diff --git a/app/assets/javascripts/boards/components/modal/header.js.es6 b/app/assets/javascripts/boards/components/modal/header.js.es6 new file mode 100644 index 00000000000..70c088f9054 --- /dev/null +++ b/app/assets/javascripts/boards/components/modal/header.js.es6 @@ -0,0 +1,90 @@ +/* global Vue */ +require('./tabs'); +const modalFilters = require('./filters'); + +(() => { + const ModalStore = gl.issueBoards.ModalStore; + + gl.issueBoards.ModalHeader = Vue.extend({ + mixins: [gl.issueBoards.ModalMixins], + props: { + projectId: { + type: Number, + required: true, + }, + milestonePath: { + type: String, + required: true, + }, + labelPath: { + type: String, + required: true, + }, + }, + data() { + return ModalStore.store; + }, + computed: { + selectAllText() { + if (ModalStore.selectedCount() !== this.issues.length || this.issues.length === 0) { + return 'Select all'; + } + + return 'Deselect all'; + }, + showSearch() { + return this.activeTab === 'all' && !this.loading && this.issuesCount > 0; + }, + }, + methods: { + toggleAll() { + this.$refs.selectAllBtn.blur(); + + ModalStore.toggleAll(); + }, + }, + components: { + 'modal-tabs': gl.issueBoards.ModalTabs, + modalFilters, + }, + template: ` + <div> + <header class="add-issues-header form-actions"> + <h2> + Add issues + <button + type="button" + class="close" + data-dismiss="modal" + aria-label="Close" + @click="toggleModal(false)"> + <span aria-hidden="true">×</span> + </button> + </h2> + </header> + <modal-tabs v-if="!loading && issuesCount > 0"></modal-tabs> + <div + class="add-issues-search append-bottom-10" + v-if="showSearch"> + <modal-filters + :project-id="projectId" + :milestone-path="milestonePath" + :label-path="labelPath"> + </modal-filters> + <input + placeholder="Search issues..." + class="form-control" + type="search" + v-model="searchTerm" /> + <button + type="button" + class="btn btn-success btn-inverted prepend-left-10" + ref="selectAllBtn" + @click="toggleAll"> + {{ selectAllText }} + </button> + </div> + </div> + `, + }); +})(); diff --git a/app/assets/javascripts/boards/components/modal/index.js.es6 b/app/assets/javascripts/boards/components/modal/index.js.es6 new file mode 100644 index 00000000000..f290cd13763 --- /dev/null +++ b/app/assets/javascripts/boards/components/modal/index.js.es6 @@ -0,0 +1,163 @@ +/* global Vue */ +/* global ListIssue */ + +require('./header'); +require('./list'); +require('./footer'); +require('./empty_state'); + +(() => { + const ModalStore = gl.issueBoards.ModalStore; + + gl.issueBoards.IssuesModal = Vue.extend({ + props: { + blankStateImage: { + type: String, + required: true, + }, + newIssuePath: { + type: String, + required: true, + }, + issueLinkBase: { + type: String, + required: true, + }, + rootPath: { + type: String, + required: true, + }, + projectId: { + type: Number, + required: true, + }, + milestonePath: { + type: String, + required: true, + }, + labelPath: { + type: String, + required: true, + }, + }, + data() { + return ModalStore.store; + }, + watch: { + page() { + this.loadIssues(); + }, + searchTerm() { + this.searchOperation(); + }, + showAddIssuesModal() { + if (this.showAddIssuesModal && !this.issues.length) { + this.loading = true; + + this.loadIssues() + .then(() => { + this.loading = false; + }); + } else if (!this.showAddIssuesModal) { + this.issues = []; + this.selectedIssues = []; + this.issuesCount = false; + } + }, + filter: { + handler() { + this.loadIssues(true); + }, + deep: true, + }, + }, + methods: { + searchOperation: _.debounce(function searchOperationDebounce() { + this.loadIssues(true); + }, 500), + loadIssues(clearIssues = false) { + if (!this.showAddIssuesModal) return false; + + const queryData = Object.assign({}, this.filter, { + search: this.searchTerm, + page: this.page, + per: this.perPage, + }); + + return gl.boardService.getBacklog(queryData).then((res) => { + const data = res.json(); + + if (clearIssues) { + this.issues = []; + } + + data.issues.forEach((issueObj) => { + const issue = new ListIssue(issueObj); + const foundSelectedIssue = ModalStore.findSelectedIssue(issue); + issue.selected = !!foundSelectedIssue; + + this.issues.push(issue); + }); + + this.loadingNewPage = false; + + if (!this.issuesCount) { + this.issuesCount = data.size; + } + }); + }, + }, + computed: { + showList() { + if (this.activeTab === 'selected') { + return this.selectedIssues.length > 0; + } + + return this.issuesCount > 0; + }, + showEmptyState() { + if (!this.loading && this.issuesCount === 0) { + return true; + } + + return this.activeTab === 'selected' && this.selectedIssues.length === 0; + }, + }, + components: { + 'modal-header': gl.issueBoards.ModalHeader, + 'modal-list': gl.issueBoards.ModalList, + 'modal-footer': gl.issueBoards.ModalFooter, + 'empty-state': gl.issueBoards.ModalEmptyState, + }, + template: ` + <div + class="add-issues-modal" + v-if="showAddIssuesModal"> + <div class="add-issues-container"> + <modal-header + :project-id="projectId" + :milestone-path="milestonePath" + :label-path="labelPath"> + </modal-header> + <modal-list + :image="blankStateImage" + :issue-link-base="issueLinkBase" + :root-path="rootPath" + v-if="!loading && showList"></modal-list> + <empty-state + v-if="showEmptyState" + :image="blankStateImage" + :new-issue-path="newIssuePath"></empty-state> + <section + class="add-issues-list text-center" + v-if="loading"> + <div class="add-issues-list-loading"> + <i class="fa fa-spinner fa-spin"></i> + </div> + </section> + <modal-footer></modal-footer> + </div> + </div> + `, + }); +})(); diff --git a/app/assets/javascripts/boards/components/modal/list.js.es6 b/app/assets/javascripts/boards/components/modal/list.js.es6 new file mode 100644 index 00000000000..3730c1ecaeb --- /dev/null +++ b/app/assets/javascripts/boards/components/modal/list.js.es6 @@ -0,0 +1,159 @@ +/* global Vue */ +/* global ListIssue */ +/* global bp */ +(() => { + const ModalStore = gl.issueBoards.ModalStore; + + gl.issueBoards.ModalList = Vue.extend({ + props: { + issueLinkBase: { + type: String, + required: true, + }, + rootPath: { + type: String, + required: true, + }, + image: { + type: String, + required: true, + }, + }, + data() { + return ModalStore.store; + }, + watch: { + activeTab() { + if (this.activeTab === 'all') { + ModalStore.purgeUnselectedIssues(); + } + }, + }, + computed: { + loopIssues() { + if (this.activeTab === 'all') { + return this.issues; + } + + return this.selectedIssues; + }, + groupedIssues() { + const groups = []; + this.loopIssues.forEach((issue, i) => { + const index = i % this.columns; + + if (!groups[index]) { + groups.push([]); + } + + groups[index].push(issue); + }); + + return groups; + }, + }, + methods: { + scrollHandler() { + const currentPage = Math.floor(this.issues.length / this.perPage); + + if ((this.scrollTop() > this.scrollHeight() - 100) && !this.loadingNewPage + && currentPage === this.page) { + this.loadingNewPage = true; + this.page += 1; + } + }, + toggleIssue(e, issue) { + if (e.target.tagName !== 'A') { + ModalStore.toggleIssue(issue); + } + }, + listHeight() { + return this.$refs.list.getBoundingClientRect().height; + }, + scrollHeight() { + return this.$refs.list.scrollHeight; + }, + scrollTop() { + return this.$refs.list.scrollTop + this.listHeight(); + }, + showIssue(issue) { + if (this.activeTab === 'all') return true; + + const index = ModalStore.selectedIssueIndex(issue); + + return index !== -1; + }, + setColumnCount() { + const breakpoint = bp.getBreakpointSize(); + + if (breakpoint === 'lg' || breakpoint === 'md') { + this.columns = 3; + } else if (breakpoint === 'sm') { + this.columns = 2; + } else { + this.columns = 1; + } + }, + }, + mounted() { + this.scrollHandlerWrapper = this.scrollHandler.bind(this); + this.setColumnCountWrapper = this.setColumnCount.bind(this); + this.setColumnCount(); + + this.$refs.list.addEventListener('scroll', this.scrollHandlerWrapper); + window.addEventListener('resize', this.setColumnCountWrapper); + }, + beforeDestroy() { + this.$refs.list.removeEventListener('scroll', this.scrollHandlerWrapper); + window.removeEventListener('resize', this.setColumnCountWrapper); + }, + components: { + 'issue-card-inner': gl.issueBoards.IssueCardInner, + }, + template: ` + <section + class="add-issues-list add-issues-list-columns" + ref="list"> + <div + class="empty-state add-issues-empty-state-filter text-center" + v-if="issuesCount > 0 && issues.length === 0"> + <div + class="svg-content" + v-html="image"> + </div> + <div class="text-content"> + <h4> + There are no issues to show. + </h4> + </div> + </div> + <div + v-for="group in groupedIssues" + class="add-issues-list-column"> + <div + v-for="issue in group" + v-if="showIssue(issue)" + class="card-parent"> + <div + class="card" + :class="{ 'is-active': issue.selected }" + @click="toggleIssue($event, issue)"> + <issue-card-inner + :issue="issue" + :issue-link-base="issueLinkBase" + :root-path="rootPath"> + </issue-card-inner> + <span + :aria-label="'Issue #' + issue.id + ' selected'" + aria-checked="true" + v-if="issue.selected" + class="issue-card-selected text-center"> + <i class="fa fa-check"></i> + </span> + </div> + </div> + </div> + </section> + `, + }); +})(); diff --git a/app/assets/javascripts/boards/components/modal/lists_dropdown.js.es6 b/app/assets/javascripts/boards/components/modal/lists_dropdown.js.es6 new file mode 100644 index 00000000000..3c05120a2da --- /dev/null +++ b/app/assets/javascripts/boards/components/modal/lists_dropdown.js.es6 @@ -0,0 +1,56 @@ +/* global Vue */ +(() => { + const ModalStore = gl.issueBoards.ModalStore; + + gl.issueBoards.ModalFooterListsDropdown = Vue.extend({ + data() { + return { + modal: ModalStore.store, + state: gl.issueBoards.BoardsStore.state, + }; + }, + computed: { + selected() { + return this.modal.selectedList || this.state.lists[0]; + }, + }, + destroyed() { + this.modal.selectedList = null; + }, + template: ` + <div class="dropdown inline"> + <button + class="dropdown-menu-toggle" + type="button" + data-toggle="dropdown" + aria-expanded="false"> + <span + class="dropdown-label-box" + :style="{ backgroundColor: selected.label.color }"> + </span> + {{ selected.title }} + <i class="fa fa-chevron-down"></i> + </button> + <div class="dropdown-menu dropdown-menu-selectable dropdown-menu-drop-up"> + <ul> + <li + v-for="list in state.lists" + v-if="list.type == 'label'"> + <a + href="#" + role="button" + :class="{ 'is-active': list.id == selected.id }" + @click.prevent="modal.selectedList = list"> + <span + class="dropdown-label-box" + :style="{ backgroundColor: list.label.color }"> + </span> + {{ list.title }} + </a> + </li> + </ul> + </div> + </div> + `, + }); +})(); diff --git a/app/assets/javascripts/boards/components/modal/tabs.js.es6 b/app/assets/javascripts/boards/components/modal/tabs.js.es6 new file mode 100644 index 00000000000..e8cb43f3503 --- /dev/null +++ b/app/assets/javascripts/boards/components/modal/tabs.js.es6 @@ -0,0 +1,47 @@ +/* global Vue */ +(() => { + const ModalStore = gl.issueBoards.ModalStore; + + gl.issueBoards.ModalTabs = Vue.extend({ + mixins: [gl.issueBoards.ModalMixins], + data() { + return ModalStore.store; + }, + computed: { + selectedCount() { + return ModalStore.selectedCount(); + }, + }, + destroyed() { + this.activeTab = 'all'; + }, + template: ` + <div class="top-area prepend-top-10 append-bottom-10"> + <ul class="nav-links issues-state-filters"> + <li :class="{ 'active': activeTab == 'all' }"> + <a + href="#" + role="button" + @click.prevent="changeTab('all')"> + All issues + <span class="badge"> + {{ issuesCount }} + </span> + </a> + </li> + <li :class="{ 'active': activeTab == 'selected' }"> + <a + href="#" + role="button" + @click.prevent="changeTab('selected')"> + Selected issues + <span class="badge"> + {{ selectedCount }} + </span> + </a> + </li> + </ul> + </div> + `, + }); +})(); diff --git a/app/assets/javascripts/boards/components/new_list_dropdown.js.es6 b/app/assets/javascripts/boards/components/new_list_dropdown.js.es6 index 3f5cf8420a8..556826a9148 100644 --- a/app/assets/javascripts/boards/components/new_list_dropdown.js.es6 +++ b/app/assets/javascripts/boards/components/new_list_dropdown.js.es6 @@ -1,4 +1,4 @@ -/* eslint-disable comma-dangle, func-names, no-new, space-before-function-paren, one-var, indent */ +/* eslint-disable comma-dangle, func-names, no-new, space-before-function-paren, one-var */ (() => { window.gl = window.gl || {}; @@ -32,17 +32,17 @@ }); }, renderRow (label) { - const active = Store.findList('title', label.title), - $li = $('<li />'), - $a = $('<a />', { - class: (active ? `is-active js-board-list-${active.id}` : ''), - text: label.title, - href: '#' - }), - $labelColor = $('<span />', { - class: 'dropdown-label-box', - style: `background-color: ${label.color}` - }); + const active = Store.findList('title', label.title); + const $li = $('<li />'); + const $a = $('<a />', { + class: (active ? `is-active js-board-list-${active.id}` : ''), + text: label.title, + href: '#' + }); + const $labelColor = $('<span />', { + class: 'dropdown-label-box', + style: `background-color: ${label.color}` + }); return $li.append($a.prepend($labelColor)); }, diff --git a/app/assets/javascripts/boards/components/sidebar/remove_issue.js.es6 b/app/assets/javascripts/boards/components/sidebar/remove_issue.js.es6 new file mode 100644 index 00000000000..e74935e1cb0 --- /dev/null +++ b/app/assets/javascripts/boards/components/sidebar/remove_issue.js.es6 @@ -0,0 +1,59 @@ +/* eslint-disable no-new */ +/* global Vue */ +/* global Flash */ +(() => { + const Store = gl.issueBoards.BoardsStore; + + window.gl = window.gl || {}; + window.gl.issueBoards = window.gl.issueBoards || {}; + + gl.issueBoards.RemoveIssueBtn = Vue.extend({ + props: { + issue: { + type: Object, + required: true, + }, + list: { + type: Object, + required: true, + }, + }, + methods: { + removeIssue() { + const issue = this.issue; + const lists = issue.getLists(); + const labelIds = lists.map(list => list.label.id); + + // Post the remove data + gl.boardService.bulkUpdate([issue.globalId], { + remove_label_ids: labelIds, + }).catch(() => { + new Flash('Failed to remove issue from board, please try again.', 'alert'); + + lists.forEach((list) => { + list.addIssue(issue); + }); + }); + + // Remove from the frontend store + lists.forEach((list) => { + list.removeIssue(issue); + }); + + Store.detail.issue = {}; + }, + }, + template: ` + <div + class="block list" + v-if="list.type !== 'done'"> + <button + class="btn btn-default btn-block" + type="button" + @click="removeIssue"> + Remove from board + </button> + </div> + `, + }); +})(); diff --git a/app/assets/javascripts/boards/mixins/modal_mixins.js.es6 b/app/assets/javascripts/boards/mixins/modal_mixins.js.es6 new file mode 100644 index 00000000000..d378b7d4baf --- /dev/null +++ b/app/assets/javascripts/boards/mixins/modal_mixins.js.es6 @@ -0,0 +1,14 @@ +(() => { + const ModalStore = gl.issueBoards.ModalStore; + + gl.issueBoards.ModalMixins = { + methods: { + toggleModal(toggle) { + ModalStore.store.showAddIssuesModal = toggle; + }, + changeTab(tab) { + ModalStore.store.activeTab = tab; + }, + }, + }; +})(); diff --git a/app/assets/javascripts/boards/mixins/sortable_default_options.js.es6 b/app/assets/javascripts/boards/mixins/sortable_default_options.js.es6 index a5e62ed775d..b6c6d17274f 100644 --- a/app/assets/javascripts/boards/mixins/sortable_default_options.js.es6 +++ b/app/assets/javascripts/boards/mixins/sortable_default_options.js.es6 @@ -1,4 +1,4 @@ -/* eslint-disable no-unused-vars, no-mixed-operators, prefer-const, comma-dangle, semi */ +/* eslint-disable no-unused-vars, no-mixed-operators, comma-dangle */ /* global DocumentTouch */ ((w) => { @@ -19,7 +19,8 @@ gl.issueBoards.touchEnabled = ('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch; gl.issueBoards.getBoardSortableDefaultOptions = (obj) => { - let defaultSortOptions = { + const defaultSortOptions = { + animation: 200, forceFallback: true, fallbackClass: 'is-dragging', fallbackOnBody: true, @@ -30,7 +31,7 @@ scrollSpeed: 20, onStart: gl.issueBoards.onStart, onEnd: gl.issueBoards.onEnd - } + }; Object.keys(obj).forEach((key) => { defaultSortOptions[key] = obj[key]; }); return defaultSortOptions; diff --git a/app/assets/javascripts/boards/models/issue.js.es6 b/app/assets/javascripts/boards/models/issue.js.es6 index 1199e022ff1..2d0a295ae4d 100644 --- a/app/assets/javascripts/boards/models/issue.js.es6 +++ b/app/assets/javascripts/boards/models/issue.js.es6 @@ -1,4 +1,4 @@ -/* eslint-disable no-unused-vars, space-before-function-paren, arrow-body-style, space-in-parens, arrow-parens, comma-dangle, max-len */ +/* eslint-disable no-unused-vars, space-before-function-paren, arrow-body-style, arrow-parens, comma-dangle, max-len */ /* global Vue */ /* global ListLabel */ /* global ListMilestone */ @@ -6,12 +6,15 @@ class ListIssue { constructor (obj) { + this.globalId = obj.id; this.id = obj.iid; this.title = obj.title; this.confidential = obj.confidential; this.dueDate = obj.due_date; this.subscribed = obj.subscribed; this.labels = []; + this.selected = false; + this.assignee = false; if (obj.assignee) { this.assignee = new ListUser(obj.assignee); @@ -37,12 +40,12 @@ class ListIssue { } findLabel (findLabel) { - return this.labels.filter( label => label.title === findLabel.title )[0]; + return this.labels.filter(label => label.title === findLabel.title)[0]; } removeLabel (removeLabel) { if (removeLabel) { - this.labels = this.labels.filter( label => removeLabel.title !== label.title ); + this.labels = this.labels.filter(label => removeLabel.title !== label.title); } } @@ -51,7 +54,7 @@ class ListIssue { } getLists () { - return gl.issueBoards.BoardsStore.state.lists.filter( list => list.findIssue(this.id) ); + return gl.issueBoards.BoardsStore.state.lists.filter(list => list.findIssue(this.id)); } update (url) { @@ -60,7 +63,7 @@ class ListIssue { milestone_id: this.milestone ? this.milestone.id : null, due_date: this.dueDate, assignee_id: this.assignee ? this.assignee.id : null, - label_ids: this.labels.map( (label) => label.id ) + label_ids: this.labels.map((label) => label.id) } }; @@ -71,3 +74,5 @@ class ListIssue { return Vue.http.patch(url, data); } } + +window.ListIssue = ListIssue; diff --git a/app/assets/javascripts/boards/models/label.js.es6 b/app/assets/javascripts/boards/models/label.js.es6 index 8f20a1bbec7..9af88d167d6 100644 --- a/app/assets/javascripts/boards/models/label.js.es6 +++ b/app/assets/javascripts/boards/models/label.js.es6 @@ -10,3 +10,5 @@ class ListLabel { this.priority = (obj.priority !== null) ? obj.priority : Infinity; } } + +window.ListLabel = ListLabel; diff --git a/app/assets/javascripts/boards/models/list.js.es6 b/app/assets/javascripts/boards/models/list.js.es6 index a8d38c16485..5152be56b66 100644 --- a/app/assets/javascripts/boards/models/list.js.es6 +++ b/app/assets/javascripts/boards/models/list.js.es6 @@ -1,4 +1,4 @@ -/* eslint-disable space-before-function-paren, no-underscore-dangle, class-methods-use-this, consistent-return, no-plusplus, prefer-const, space-in-parens, no-shadow, no-param-reassign, max-len, no-unused-vars */ +/* eslint-disable space-before-function-paren, no-underscore-dangle, class-methods-use-this, consistent-return, no-shadow, no-param-reassign, max-len, no-unused-vars */ /* global ListIssue */ /* global ListLabel */ @@ -9,7 +9,7 @@ class List { this.position = obj.position; this.title = obj.title; this.type = obj.list_type; - this.preset = ['backlog', 'done', 'blank'].indexOf(this.type) > -1; + this.preset = ['done', 'blank'].indexOf(this.type) > -1; this.filters = gl.issueBoards.BoardsStore.state.filters; this.page = 1; this.loading = true; @@ -58,7 +58,7 @@ class List { nextPage () { if (this.issuesSize > this.issues.length) { - this.page++; + this.page += 1; return this.getIssues(false); } @@ -66,12 +66,12 @@ class List { getIssues (emptyIssues = true) { const filters = this.filters; - let data = { page: this.page }; + const data = { page: this.page }; Object.keys(filters).forEach((key) => { data[key] = filters[key]; }); if (this.label) { - data.label_name = data.label_name.filter( label => label !== this.label.title ); + data.label_name = data.label_name.filter(label => label !== this.label.title); } if (emptyIssues) { @@ -94,7 +94,7 @@ class List { newIssue (issue) { this.addIssue(issue); - this.issuesSize++; + this.issuesSize += 1; return gl.boardService.newIssue(this.id, issue) .then((resp) => { @@ -122,7 +122,7 @@ class List { } if (listFrom) { - this.issuesSize++; + this.issuesSize += 1; gl.boardService.moveIssue(issue.id, listFrom.id, this.id) .then(() => { listFrom.getIssues(false); @@ -132,7 +132,7 @@ class List { } findIssue (id) { - return this.issues.filter( issue => issue.id === id )[0]; + return this.issues.filter(issue => issue.id === id)[0]; } removeIssue (removeIssue) { @@ -140,7 +140,7 @@ class List { const matchesRemove = removeIssue.id === issue.id; if (matchesRemove) { - this.issuesSize--; + this.issuesSize -= 1; issue.removeLabel(this.label); } @@ -148,3 +148,5 @@ class List { }); } } + +window.List = List; diff --git a/app/assets/javascripts/boards/models/milestone.js.es6 b/app/assets/javascripts/boards/models/milestone.js.es6 index 9c173c1b70b..c867b06d320 100644 --- a/app/assets/javascripts/boards/models/milestone.js.es6 +++ b/app/assets/javascripts/boards/models/milestone.js.es6 @@ -6,3 +6,5 @@ class ListMilestone { this.title = obj.title; } } + +window.ListMilestone = ListMilestone; diff --git a/app/assets/javascripts/boards/models/user.js.es6 b/app/assets/javascripts/boards/models/user.js.es6 index a8a3892e2ad..8e9de4d4cbb 100644 --- a/app/assets/javascripts/boards/models/user.js.es6 +++ b/app/assets/javascripts/boards/models/user.js.es6 @@ -8,3 +8,5 @@ class ListUser { this.avatar = user.avatar_url; } } + +window.ListUser = ListUser; diff --git a/app/assets/javascripts/boards/services/board_service.js.es6 b/app/assets/javascripts/boards/services/board_service.js.es6 index 189a8703198..065e90518df 100644 --- a/app/assets/javascripts/boards/services/board_service.js.es6 +++ b/app/assets/javascripts/boards/services/board_service.js.es6 @@ -1,8 +1,14 @@ -/* eslint-disable space-before-function-paren, comma-dangle, no-param-reassign, camelcase, prefer-const, no-extra-semi, max-len, no-unused-vars */ +/* eslint-disable space-before-function-paren, comma-dangle, no-param-reassign, camelcase, max-len, no-unused-vars */ /* global Vue */ class BoardService { - constructor (root, boardId) { + constructor (root, bulkUpdatePath, boardId) { + this.boards = Vue.resource(`${root}{/id}.json`, {}, { + issues: { + method: 'GET', + url: `${root}/${boardId}/issues.json` + } + }); this.lists = Vue.resource(`${root}/${boardId}/lists{/id}`, {}, { generate: { method: 'POST', @@ -10,7 +16,12 @@ class BoardService { } }); this.issue = Vue.resource(`${root}/${boardId}/issues{/id}`, {}); - this.issues = Vue.resource(`${root}/${boardId}/lists{/id}/issues`, {}); + this.issues = Vue.resource(`${root}/${boardId}/lists{/id}/issues`, {}, { + bulkUpdate: { + method: 'POST', + url: bulkUpdatePath, + }, + }); Vue.http.interceptors.push((request, next) => { request.headers['X-CSRF-Token'] = $.rails.csrfToken(); @@ -47,7 +58,7 @@ class BoardService { } getIssuesForList (id, filter = {}) { - let data = { id }; + const data = { id }; Object.keys(filter).forEach((key) => { data[key] = filter[key]; }); return this.issues.get(data); @@ -65,4 +76,20 @@ class BoardService { issue }); } -}; + + getBacklog(data) { + return this.boards.issues(data); + } + + bulkUpdate(issueIds, extraData = {}) { + const data = { + update: Object.assign(extraData, { + issuable_ids: issueIds.join(','), + }), + }; + + return this.issues.bulkUpdate(data); + } +} + +window.BoardService = BoardService; diff --git a/app/assets/javascripts/boards/stores/boards_store.js.es6 b/app/assets/javascripts/boards/stores/boards_store.js.es6 index e7a14ea5bca..50842ecbaaa 100644 --- a/app/assets/javascripts/boards/stores/boards_store.js.es6 +++ b/app/assets/javascripts/boards/stores/boards_store.js.es6 @@ -1,4 +1,4 @@ -/* eslint-disable comma-dangle, space-before-function-paren, one-var, indent, space-in-parens, no-shadow, radix, dot-notation, semi, max-len */ +/* eslint-disable comma-dangle, space-before-function-paren, one-var, no-shadow, dot-notation, max-len */ /* global Cookies */ /* global List */ @@ -33,16 +33,11 @@ return list; }, new (listObj) { - const list = this.addList(listObj), - backlogList = this.findList('type', 'backlog', 'backlog'); + const list = this.addList(listObj); list .save() .then(() => { - // Remove any new issues from the backlog - // as they will be visible in the new list - list.issues.forEach(backlogList.removeIssue.bind(backlogList)); - this.state.lists = _.sortBy(this.state.lists, 'position'); }); this.removeBlankState(); @@ -52,7 +47,7 @@ }, shouldAddBlankState () { // Decide whether to add the blank state - return !(this.state.lists.filter( list => list.type !== 'backlog' && list.type !== 'done' )[0]); + return !(this.state.lists.filter(list => list.type !== 'done')[0]); }, addBlankState () { if (!this.shouldAddBlankState() || this.welcomeIsHidden() || this.disabled) return; @@ -82,30 +77,30 @@ if (!list) return; - this.state.lists = this.state.lists.filter( list => list.id !== id ); + this.state.lists = this.state.lists.filter(list => list.id !== id); }, moveList (listFrom, orderLists) { orderLists.forEach((id, i) => { - const list = this.findList('id', parseInt(id)); + const list = this.findList('id', parseInt(id, 10)); list.position = i; }); listFrom.update(); }, moveIssueToList (listFrom, listTo, issue, newIndex) { - const issueTo = listTo.findIssue(issue.id), - issueLists = issue.getLists(), - listLabels = issueLists.map( listIssue => listIssue.label ); + const issueTo = listTo.findIssue(issue.id); + const issueLists = issue.getLists(); + const listLabels = issueLists.map(listIssue => listIssue.label); // Add to new lists issues if it doesn't already exist if (!issueTo) { listTo.addIssue(issue, listFrom, newIndex); } - if (listTo.type === 'done' && listFrom.type !== 'backlog') { + if (listTo.type === 'done') { issueLists.forEach((list) => { list.removeIssue(issue); - }) + }); issue.removeLabels(listLabels); } else { listFrom.removeIssue(issue); diff --git a/app/assets/javascripts/boards/stores/modal_store.js.es6 b/app/assets/javascripts/boards/stores/modal_store.js.es6 new file mode 100644 index 00000000000..15fc6c79e8d --- /dev/null +++ b/app/assets/javascripts/boards/stores/modal_store.js.es6 @@ -0,0 +1,107 @@ +(() => { + window.gl = window.gl || {}; + window.gl.issueBoards = window.gl.issueBoards || {}; + + class ModalStore { + constructor() { + this.store = { + columns: 3, + issues: [], + issuesCount: false, + selectedIssues: [], + showAddIssuesModal: false, + activeTab: 'all', + selectedList: null, + searchTerm: '', + loading: false, + loadingNewPage: false, + page: 1, + perPage: 50, + }; + + this.setDefaultFilter(); + } + + setDefaultFilter() { + this.store.filter = { + author_id: '', + assignee_id: '', + milestone_title: '', + label_name: [], + }; + } + + selectedCount() { + return this.getSelectedIssues().length; + } + + toggleIssue(issueObj) { + const issue = issueObj; + const selected = issue.selected; + + issue.selected = !selected; + + if (!selected) { + this.addSelectedIssue(issue); + } else { + this.removeSelectedIssue(issue); + } + } + + toggleAll() { + const select = this.selectedCount() !== this.store.issues.length; + + this.store.issues.forEach((issue) => { + const issueUpdate = issue; + + if (issueUpdate.selected !== select) { + issueUpdate.selected = select; + + if (select) { + this.addSelectedIssue(issue); + } else { + this.removeSelectedIssue(issue); + } + } + }); + } + + getSelectedIssues() { + return this.store.selectedIssues.filter(issue => issue.selected); + } + + addSelectedIssue(issue) { + const index = this.selectedIssueIndex(issue); + + if (index === -1) { + this.store.selectedIssues.push(issue); + } + } + + removeSelectedIssue(issue, forcePurge = false) { + if (this.store.activeTab === 'all' || forcePurge) { + this.store.selectedIssues = this.store.selectedIssues + .filter(fIssue => fIssue.id !== issue.id); + } + } + + purgeUnselectedIssues() { + this.store.selectedIssues.forEach((issue) => { + if (!issue.selected) { + this.removeSelectedIssue(issue, true); + } + }); + } + + selectedIssueIndex(issue) { + return this.store.selectedIssues.indexOf(issue); + } + + findSelectedIssue(issue) { + return this.store.selectedIssues + .filter(filteredIssue => filteredIssue.id === issue.id)[0]; + } + } + + gl.issueBoards.ModalStore = new ModalStore(); +})(); diff --git a/app/assets/javascripts/boards/test_utils/simulate_drag.js b/app/assets/javascripts/boards/test_utils/simulate_drag.js index 01e09ec482e..f05780167bf 100644 --- a/app/assets/javascripts/boards/test_utils/simulate_drag.js +++ b/app/assets/javascripts/boards/test_utils/simulate_drag.js @@ -1,120 +1,119 @@ -/* eslint-disable wrap-iife, func-names, strict, indent, no-tabs, no-var, vars-on-top, no-param-reassign, object-shorthand, no-shadow, comma-dangle, prefer-template, consistent-return, no-mixed-operators, no-unused-vars, object-curly-spacing, no-unused-expressions, prefer-arrow-callback, max-len */ +/* eslint-disable wrap-iife, func-names, strict, no-var, vars-on-top, no-param-reassign, object-shorthand, no-shadow, comma-dangle, prefer-template, consistent-return, no-mixed-operators, no-unused-vars, no-unused-expressions, prefer-arrow-callback, max-len */ (function () { - 'use strict'; - - function simulateEvent(el, type, options) { - var event; - if (!el) return; - var ownerDocument = el.ownerDocument; - - options = options || {}; - - if (/^mouse/.test(type)) { - event = ownerDocument.createEvent('MouseEvents'); - event.initMouseEvent(type, true, true, ownerDocument.defaultView, - options.button, options.screenX, options.screenY, options.clientX, options.clientY, - options.ctrlKey, options.altKey, options.shiftKey, options.metaKey, options.button, el); - } else { - event = ownerDocument.createEvent('CustomEvent'); - - event.initCustomEvent(type, true, true, ownerDocument.defaultView, - options.button, options.screenX, options.screenY, options.clientX, options.clientY, - options.ctrlKey, options.altKey, options.shiftKey, options.metaKey, options.button, el); - - event.dataTransfer = { - data: {}, - - setData: function (type, val) { - this.data[type] = val; - }, - - getData: function (type) { - return this.data[type]; - } - }; - } - - if (el.dispatchEvent) { - el.dispatchEvent(event); - } else if (el.fireEvent) { - el.fireEvent('on' + type, event); - } - - return event; - } - - function getTraget(target) { - var el = typeof target.el === 'string' ? document.getElementById(target.el.substr(1)) : target.el; - var children = el.children; - - return ( - children[target.index] || - children[target.index === 'first' ? 0 : -1] || - children[target.index === 'last' ? children.length - 1 : -1] - ); - } - - function getRect(el) { - var rect = el.getBoundingClientRect(); - var width = rect.right - rect.left; - var height = rect.bottom - rect.top; - - return { - x: rect.left, - y: rect.top, - cx: rect.left + width / 2, - cy: rect.top + height / 2, - w: width, - h: height, - hw: width / 2, - wh: height / 2 - }; - } - - function simulateDrag(options, callback) { - options.to.el = options.to.el || options.from.el; - - var fromEl = getTraget(options.from); - var toEl = getTraget(options.to); + 'use strict'; + + function simulateEvent(el, type, options) { + var event; + if (!el) return; + var ownerDocument = el.ownerDocument; + + options = options || {}; + + if (/^mouse/.test(type)) { + event = ownerDocument.createEvent('MouseEvents'); + event.initMouseEvent(type, true, true, ownerDocument.defaultView, + options.button, options.screenX, options.screenY, options.clientX, options.clientY, + options.ctrlKey, options.altKey, options.shiftKey, options.metaKey, options.button, el); + } else { + event = ownerDocument.createEvent('CustomEvent'); + + event.initCustomEvent(type, true, true, ownerDocument.defaultView, + options.button, options.screenX, options.screenY, options.clientX, options.clientY, + options.ctrlKey, options.altKey, options.shiftKey, options.metaKey, options.button, el); + + event.dataTransfer = { + data: {}, + + setData: function (type, val) { + this.data[type] = val; + }, + + getData: function (type) { + return this.data[type]; + } + }; + } + + if (el.dispatchEvent) { + el.dispatchEvent(event); + } else if (el.fireEvent) { + el.fireEvent('on' + type, event); + } + + return event; + } + + function getTraget(target) { + var el = typeof target.el === 'string' ? document.getElementById(target.el.substr(1)) : target.el; + var children = el.children; + + return ( + children[target.index] || + children[target.index === 'first' ? 0 : -1] || + children[target.index === 'last' ? children.length - 1 : -1] + ); + } + + function getRect(el) { + var rect = el.getBoundingClientRect(); + var width = rect.right - rect.left; + var height = rect.bottom - rect.top; + + return { + x: rect.left, + y: rect.top, + cx: rect.left + width / 2, + cy: rect.top + height / 2, + w: width, + h: height, + hw: width / 2, + wh: height / 2 + }; + } + + function simulateDrag(options, callback) { + options.to.el = options.to.el || options.from.el; + + var fromEl = getTraget(options.from); + var toEl = getTraget(options.to); var scrollable = options.scrollable; - var fromRect = getRect(fromEl); - var toRect = getRect(toEl); - - var startTime = new Date().getTime(); - var duration = options.duration || 1000; - simulateEvent(fromEl, 'mousedown', {button: 0}); - options.ontap && options.ontap(); - window.SIMULATE_DRAG_ACTIVE = 1; - - var dragInterval = setInterval(function loop() { - var progress = (new Date().getTime() - startTime) / duration; - var x = (fromRect.cx + (toRect.cx - fromRect.cx) * progress) - scrollable.scrollLeft; - var y = (fromRect.cy + (toRect.cy - fromRect.cy) * progress) - scrollable.scrollTop; - var overEl = fromEl.ownerDocument.elementFromPoint(x, y); - - simulateEvent(overEl, 'mousemove', { - clientX: x, - clientY: y - }); - - if (progress >= 1) { - options.ondragend && options.ondragend(); - simulateEvent(toEl, 'mouseup'); - clearInterval(dragInterval); - window.SIMULATE_DRAG_ACTIVE = 0; - } - }, 100); - - return { - target: fromEl, - fromList: fromEl.parentNode, - toList: toEl.parentNode - }; - } - - - // Export - window.simulateEvent = simulateEvent; - window.simulateDrag = simulateDrag; + var fromRect = getRect(fromEl); + var toRect = getRect(toEl); + + var startTime = new Date().getTime(); + var duration = options.duration || 1000; + simulateEvent(fromEl, 'mousedown', { button: 0 }); + options.ontap && options.ontap(); + window.SIMULATE_DRAG_ACTIVE = 1; + + var dragInterval = setInterval(function loop() { + var progress = (new Date().getTime() - startTime) / duration; + var x = (fromRect.cx + (toRect.cx - fromRect.cx) * progress) - scrollable.scrollLeft; + var y = (fromRect.cy + (toRect.cy - fromRect.cy) * progress) - scrollable.scrollTop; + var overEl = fromEl.ownerDocument.elementFromPoint(x, y); + + simulateEvent(overEl, 'mousemove', { + clientX: x, + clientY: y + }); + + if (progress >= 1) { + options.ondragend && options.ondragend(); + simulateEvent(toEl, 'mouseup'); + clearInterval(dragInterval); + window.SIMULATE_DRAG_ACTIVE = 0; + } + }, 100); + + return { + target: fromEl, + fromList: fromEl.parentNode, + toList: toEl.parentNode + }; + } + + // Export + window.simulateEvent = simulateEvent; + window.simulateDrag = simulateDrag; })(); diff --git a/app/assets/javascripts/boards/vue_resource_interceptor.js.es6 b/app/assets/javascripts/boards/vue_resource_interceptor.js.es6 deleted file mode 100644 index 3723a2039f9..00000000000 --- a/app/assets/javascripts/boards/vue_resource_interceptor.js.es6 +++ /dev/null @@ -1,10 +0,0 @@ -/* eslint-disable func-names, prefer-arrow-callback, no-unused-vars, no-plusplus */ -/* global Vue */ - -Vue.http.interceptors.push((request, next) => { - Vue.activeResources = Vue.activeResources ? Vue.activeResources + 1 : 1; - - next(function (response) { - Vue.activeResources--; - }); -}); diff --git a/app/assets/javascripts/breakpoints.js b/app/assets/javascripts/breakpoints.js index a7e72430141..f8dac1ff56e 100644 --- a/app/assets/javascripts/breakpoints.js +++ b/app/assets/javascripts/breakpoints.js @@ -1,4 +1,4 @@ -/* eslint-disable func-names, space-before-function-paren, wrap-iife, one-var, no-var, one-var-declaration-per-line, quotes, no-shadow, prefer-arrow-callback, prefer-template, consistent-return, padded-blocks, no-return-assign, new-parens, no-param-reassign, max-len */ +/* eslint-disable func-names, space-before-function-paren, wrap-iife, one-var, no-var, one-var-declaration-per-line, quotes, no-shadow, prefer-arrow-callback, prefer-template, consistent-return, no-return-assign, new-parens, no-param-reassign, max-len */ (function() { var Breakpoints = (function() { @@ -43,6 +43,7 @@ BreakpointInstance.prototype.getBreakpointSize = function() { var $visibleDevice; $visibleDevice = this.visibleDevice; + // TODO: Consider refactoring in light of turbolinks removal. // the page refreshed via turbolinks if (!$visibleDevice().length) { this.setup(); @@ -52,7 +53,6 @@ }; return BreakpointInstance; - })(); Breakpoints.get = function() { @@ -60,7 +60,6 @@ }; return Breakpoints; - })(); $((function(_this) { diff --git a/app/assets/javascripts/broadcast_message.js b/app/assets/javascripts/broadcast_message.js index 30432dae278..dbdadc73c3f 100644 --- a/app/assets/javascripts/broadcast_message.js +++ b/app/assets/javascripts/broadcast_message.js @@ -1,4 +1,4 @@ -/* eslint-disable func-names, space-before-function-paren, prefer-arrow-callback, no-var, quotes, no-else-return, object-shorthand, comma-dangle, padded-blocks, max-len */ +/* eslint-disable func-names, space-before-function-paren, prefer-arrow-callback, no-var, quotes, no-else-return, object-shorthand, comma-dangle, max-len */ (function() { $(function() { var previewPath; @@ -31,5 +31,4 @@ } }); }); - }).call(this); diff --git a/app/assets/javascripts/build.js b/app/assets/javascripts/build.js index 824febe3fd3..0152be88b48 100644 --- a/app/assets/javascripts/build.js +++ b/app/assets/javascripts/build.js @@ -1,9 +1,10 @@ -/* eslint-disable func-names, space-before-function-paren, no-var, space-before-blocks, prefer-rest-params, wrap-iife, no-use-before-define, no-param-reassign, quotes, yoda, no-else-return, consistent-return, comma-dangle, semi, object-shorthand, prefer-template, one-var, one-var-declaration-per-line, no-unused-vars, max-len, vars-on-top, padded-blocks */ +/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, no-use-before-define, no-param-reassign, quotes, yoda, no-else-return, consistent-return, comma-dangle, object-shorthand, prefer-template, one-var, one-var-declaration-per-line, no-unused-vars, max-len, vars-on-top */ /* global Breakpoints */ -/* global Turbolinks */ (function() { - var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; + var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; }; + var AUTO_SCROLL_OFFSET = 75; + var DOWN_BUILD_TRACE = '#down-build-trace'; this.Build = (function() { Build.interval = null; @@ -19,6 +20,17 @@ this.buildStage = options.buildStage; this.updateDropdown = bind(this.updateDropdown, this); this.$document = $(document); + this.$body = $('body'); + this.$buildTrace = $('#build-trace'); + this.$autoScrollContainer = $('.autoscroll-container'); + this.$autoScrollStatus = $('#autoscroll-status'); + this.$autoScrollStatusText = this.$autoScrollStatus.find('.status-text'); + this.$upBuildTrace = $('#up-build-trace'); + this.$downBuildTrace = $(DOWN_BUILD_TRACE); + this.$scrollTopBtn = $('#scroll-top'); + this.$scrollBottomBtn = $('#scroll-bottom'); + this.$buildRefreshAnimation = $('.js-build-refresh'); + clearInterval(Build.interval); // Init breakpoint checker this.bp = Breakpoints.get(); @@ -32,6 +44,7 @@ this.$document.off('click', '.js-sidebar-build-toggle').on('click', '.js-sidebar-build-toggle', this.sidebarOnClick.bind(this)); this.$document.off('click', '.stage-item').on('click', '.stage-item', this.updateDropdown); + this.$document.on('scroll', this.initScrollMonitor.bind(this)); $(window).off('resize.build').on('resize.build', this.sidebarOnResize.bind(this)); $('a', this.$buildScroll).off('click.stepTrace').on('click.stepTrace', this.stepTrace); this.updateArtifactRemoveDate(); @@ -40,18 +53,6 @@ this.initScrollButtonAffix(); } if (this.buildStatus === "running" || this.buildStatus === "pending") { - // Bind autoscroll button to follow build output - $('#autoscroll-button').on('click', function() { - var state; - state = $(this).data("state"); - if ("enabled" === state) { - $(this).data("state", "disabled"); - return $(this).text("Enable autoscroll"); - } else { - $(this).data("state", "enabled"); - return $(this).text("Disable autoscroll"); - } - }); Build.interval = setInterval((function(_this) { // Check for new build output if user still watching build page // Only valid for runnig build when output changes during time @@ -68,7 +69,7 @@ this.$sidebar = $('.js-build-sidebar'); this.sidebarTranslationLimits = { min: $('.navbar-gitlab').outerHeight() + $('.layout-nav').outerHeight() - } + }; this.sidebarTranslationLimits.max = this.sidebarTranslationLimits.min + $('.scrolling-tabs-container').outerHeight(); this.$sidebar.css({ top: this.sidebarTranslationLimits.max @@ -83,17 +84,21 @@ }; Build.prototype.getInitialBuildTrace = function() { - var removeRefreshStatuses = ['success', 'failed', 'canceled', 'skipped'] + var removeRefreshStatuses = ['success', 'failed', 'canceled', 'skipped']; return $.ajax({ url: this.buildUrl, dataType: 'json', success: function(buildData) { $('.js-build-output').html(buildData.trace_html); + if (window.location.hash === DOWN_BUILD_TRACE) { + $("html,body").scrollTop(this.$buildTrace.height()); + } if (removeRefreshStatuses.indexOf(buildData.status) >= 0) { - return $('.js-build-refresh').remove(); + this.$buildRefreshAnimation.remove(); + return this.initScrollMonitor(); } - } + }.bind(this) }); }; @@ -103,6 +108,8 @@ dataType: "json", success: (function(_this) { return function(log) { + var pageUrl; + if (log.state) { _this.state = log.state; } @@ -114,7 +121,12 @@ } return _this.checkAutoscroll(); } else if (log.status !== _this.buildStatus) { - return Turbolinks.visit(_this.pageUrl); + pageUrl = _this.pageUrl; + if (_this.$autoScrollStatus.data('state') === 'enabled') { + pageUrl += DOWN_BUILD_TRACE; + } + + return gl.utils.visitUrl(pageUrl); } }; })(this) @@ -122,22 +134,95 @@ }; Build.prototype.checkAutoscroll = function() { - if ("enabled" === $("#autoscroll-button").data("state")) { - return $("html,body").scrollTop($("#build-trace").height()); + if (this.$autoScrollStatus.data("state") === "enabled") { + return $("html,body").scrollTop(this.$buildTrace.height()); + } + + // Handle a situation where user started new build + // but never scrolled a page + if (!this.$scrollTopBtn.is(':visible') && + !this.$scrollBottomBtn.is(':visible') && + !gl.utils.isInViewport(this.$downBuildTrace.get(0))) { + this.$scrollBottomBtn.show(); } }; Build.prototype.initScrollButtonAffix = function() { - var $body, $buildTrace; - $body = $('body'); - $buildTrace = $('#build-trace'); - return this.$buildScroll.affix({ - offset: { - bottom: function() { - return $body.outerHeight() - ($buildTrace.outerHeight() + $buildTrace.offset().top); - } + // Hide everything initially + this.$scrollTopBtn.hide(); + this.$scrollBottomBtn.hide(); + this.$autoScrollContainer.hide(); + }; + + // Page scroll listener to detect if user has scrolling page + // and handle following cases + // 1) User is at Top of Build Log; + // - Hide Top Arrow button + // - Show Bottom Arrow button + // - Disable Autoscroll and hide indicator (when build is running) + // 2) User is at Bottom of Build Log; + // - Show Top Arrow button + // - Hide Bottom Arrow button + // - Enable Autoscroll and show indicator (when build is running) + // 3) User is somewhere in middle of Build Log; + // - Show Top Arrow button + // - Show Bottom Arrow button + // - Disable Autoscroll and hide indicator (when build is running) + Build.prototype.initScrollMonitor = function() { + if (!gl.utils.isInViewport(this.$upBuildTrace.get(0)) && !gl.utils.isInViewport(this.$downBuildTrace.get(0))) { + // User is somewhere in middle of Build Log + + this.$scrollTopBtn.show(); + + if (this.buildStatus === 'success' || this.buildStatus === 'failed') { // Check if Build is completed + this.$scrollBottomBtn.show(); + } else if (this.$buildRefreshAnimation.is(':visible') && !gl.utils.isInViewport(this.$buildRefreshAnimation.get(0))) { + this.$scrollBottomBtn.show(); + } else { + this.$scrollBottomBtn.hide(); } - }); + + // Hide Autoscroll Status Indicator + if (this.$scrollBottomBtn.is(':visible')) { + this.$autoScrollContainer.hide(); + this.$autoScrollStatusText.removeClass('animate'); + } else { + this.$autoScrollContainer.css({ top: this.$body.outerHeight() - AUTO_SCROLL_OFFSET }).show(); + this.$autoScrollStatusText.addClass('animate'); + } + } else if (gl.utils.isInViewport(this.$upBuildTrace.get(0)) && !gl.utils.isInViewport(this.$downBuildTrace.get(0))) { + // User is at Top of Build Log + + this.$scrollTopBtn.hide(); + this.$scrollBottomBtn.show(); + + this.$autoScrollContainer.hide(); + this.$autoScrollStatusText.removeClass('animate'); + } else if ((!gl.utils.isInViewport(this.$upBuildTrace.get(0)) && gl.utils.isInViewport(this.$downBuildTrace.get(0))) || + (this.$buildRefreshAnimation.is(':visible') && gl.utils.isInViewport(this.$buildRefreshAnimation.get(0)))) { + // User is at Bottom of Build Log + + this.$scrollTopBtn.show(); + this.$scrollBottomBtn.hide(); + + // Show and Reposition Autoscroll Status Indicator + this.$autoScrollContainer.css({ top: this.$body.outerHeight() - AUTO_SCROLL_OFFSET }).show(); + this.$autoScrollStatusText.addClass('animate'); + } else if (gl.utils.isInViewport(this.$upBuildTrace.get(0)) && gl.utils.isInViewport(this.$downBuildTrace.get(0))) { + // Build Log height is small + + this.$scrollTopBtn.hide(); + this.$scrollBottomBtn.hide(); + + // Hide Autoscroll Status Indicator + this.$autoScrollContainer.hide(); + this.$autoScrollStatusText.removeClass('animate'); + } + + if (this.buildStatus === "running" || this.buildStatus === "pending") { + // Check if Refresh Animation is in Viewport and enable Autoscroll, disable otherwise. + this.$autoScrollStatus.data("state", gl.utils.isInViewport(this.$buildRefreshAnimation.get(0)) ? 'enabled' : 'disabled'); + } }; Build.prototype.shouldHideSidebarForViewport = function() { @@ -205,7 +290,5 @@ }; return Build; - })(); - }).call(this); diff --git a/app/assets/javascripts/build_artifacts.js b/app/assets/javascripts/build_artifacts.js index c423a548a30..083448552b6 100644 --- a/app/assets/javascripts/build_artifacts.js +++ b/app/assets/javascripts/build_artifacts.js @@ -1,4 +1,4 @@ -/* eslint-disable func-names, space-before-function-paren, wrap-iife, prefer-arrow-callback, no-unused-vars, no-return-assign, padded-blocks, max-len */ +/* eslint-disable func-names, space-before-function-paren, wrap-iife, prefer-arrow-callback, no-unused-vars, no-return-assign, max-len */ (function() { this.BuildArtifacts = (function() { function BuildArtifacts() { @@ -22,7 +22,5 @@ }; return BuildArtifacts; - })(); - }).call(this); diff --git a/app/assets/javascripts/build_variables.js.es6 b/app/assets/javascripts/build_variables.js.es6 index 993424d422f..99082b412e2 100644 --- a/app/assets/javascripts/build_variables.js.es6 +++ b/app/assets/javascripts/build_variables.js.es6 @@ -1,7 +1,7 @@ -/* eslint-disable func-names, prefer-arrow-callback, space-before-blocks, space-before-function-paren, comma-spacing, max-len */ +/* eslint-disable func-names, prefer-arrow-callback, space-before-function-paren */ -$(function(){ - $('.reveal-variables').off('click').on('click',function(){ +$(function() { + $('.reveal-variables').off('click').on('click', function() { $('.js-build').toggle().niceScroll(); $(this).hide(); }); diff --git a/app/assets/javascripts/ci_lint_editor.js.es6 b/app/assets/javascripts/ci_lint_editor.js.es6 new file mode 100644 index 00000000000..56ffaa765a8 --- /dev/null +++ b/app/assets/javascripts/ci_lint_editor.js.es6 @@ -0,0 +1,18 @@ +(() => { + window.gl = window.gl || {}; + + class CILintEditor { + constructor() { + this.editor = window.ace.edit('ci-editor'); + this.textarea = document.querySelector('#content'); + + this.editor.getSession().setMode('ace/mode/yaml'); + this.editor.on('input', () => { + const content = this.editor.getSession().getValue(); + this.textarea.value = content; + }); + } + } + + gl.CILintEditor = CILintEditor; +})(); diff --git a/app/assets/javascripts/commit.js b/app/assets/javascripts/commit.js index 67b33a4d7ee..c656ae4e241 100644 --- a/app/assets/javascripts/commit.js +++ b/app/assets/javascripts/commit.js @@ -1,4 +1,4 @@ -/* eslint-disable func-names, space-before-function-paren, wrap-iife, padded-blocks */ +/* eslint-disable func-names, space-before-function-paren, wrap-iife */ /* global CommitFile */ (function() { @@ -10,7 +10,5 @@ } return Commit; - })(); - }).call(this); diff --git a/app/assets/javascripts/commit/file.js b/app/assets/javascripts/commit/file.js index 27512312c7c..184b4561d2e 100644 --- a/app/assets/javascripts/commit/file.js +++ b/app/assets/javascripts/commit/file.js @@ -1,4 +1,4 @@ -/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-new, padded-blocks */ +/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-new */ /* global ImageFile */ (function() { @@ -10,7 +10,5 @@ } return CommitFile; - })(); - }).call(this); diff --git a/app/assets/javascripts/commit/image_file.js b/app/assets/javascripts/commit/image_file.js index fd8910e916f..f09a6b1e676 100644 --- a/app/assets/javascripts/commit/image_file.js +++ b/app/assets/javascripts/commit/image_file.js @@ -1,4 +1,4 @@ -/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-use-before-define, prefer-arrow-callback, no-else-return, consistent-return, prefer-template, quotes, one-var, one-var-declaration-per-line, no-unused-vars, no-return-assign, comma-dangle, quote-props, no-unused-expressions, no-sequences, object-shorthand, padded-blocks, max-len */ +/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-use-before-define, prefer-arrow-callback, no-else-return, consistent-return, prefer-template, quotes, one-var, one-var-declaration-per-line, no-unused-vars, no-return-assign, comma-dangle, quote-props, no-unused-expressions, no-sequences, object-shorthand, max-len */ (function() { gl.ImageFile = (function() { var prepareFrames; @@ -172,7 +172,5 @@ }; return ImageFile; - })(); - }).call(this); diff --git a/app/assets/javascripts/commit/pipelines/pipelines_bundle.js.es6 b/app/assets/javascripts/commit/pipelines/pipelines_bundle.js.es6 new file mode 100644 index 00000000000..fbfec7743c7 --- /dev/null +++ b/app/assets/javascripts/commit/pipelines/pipelines_bundle.js.es6 @@ -0,0 +1,26 @@ +/* eslint-disable no-new, no-param-reassign */ +/* global Vue, CommitsPipelineStore, PipelinesService, Flash */ + +window.Vue = require('vue'); +require('./pipelines_table'); +/** + * Commits View > Pipelines Tab > Pipelines Table. + * Merge Request View > Pipelines Tab > Pipelines Table. + * + * Renders Pipelines table in pipelines tab in the commits show view. + * Renders Pipelines table in pipelines tab in the merge request show view. + */ + +$(() => { + window.gl = window.gl || {}; + gl.commits = gl.commits || {}; + gl.commits.pipelines = gl.commits.pipelines || {}; + + if (gl.commits.PipelinesTableBundle) { + gl.commits.PipelinesTableBundle.$destroy(true); + } + + gl.commits.pipelines.PipelinesTableBundle = new gl.commits.pipelines.PipelinesTableView({ + el: document.querySelector('#commit-pipeline-table-view'), + }); +}); diff --git a/app/assets/javascripts/commit/pipelines/pipelines_service.js.es6 b/app/assets/javascripts/commit/pipelines/pipelines_service.js.es6 new file mode 100644 index 00000000000..483b414126a --- /dev/null +++ b/app/assets/javascripts/commit/pipelines/pipelines_service.js.es6 @@ -0,0 +1,29 @@ +/* globals Vue */ +/* eslint-disable no-unused-vars, no-param-reassign */ + +/** + * Pipelines service. + * + * Used to fetch the data used to render the pipelines table. + * Uses Vue.Resource + */ +class PipelinesService { + constructor(endpoint) { + this.pipelines = Vue.resource(endpoint); + } + + /** + * Given the root param provided when the class is initialized, will + * make a GET request. + * + * @return {Promise} + */ + all() { + return this.pipelines.get(); + } +} + +window.gl = window.gl || {}; +gl.commits = gl.commits || {}; +gl.commits.pipelines = gl.commits.pipelines || {}; +gl.commits.pipelines.PipelinesService = PipelinesService; diff --git a/app/assets/javascripts/commit/pipelines/pipelines_store.js.es6 b/app/assets/javascripts/commit/pipelines/pipelines_store.js.es6 new file mode 100644 index 00000000000..11a3449d99a --- /dev/null +++ b/app/assets/javascripts/commit/pipelines/pipelines_store.js.es6 @@ -0,0 +1,50 @@ +/* eslint-disable no-underscore-dangle*/ +/** + * Pipelines' Store for commits view. + * + * Used to store the Pipelines rendered in the commit view in the pipelines table. + */ + +class PipelinesStore { + constructor() { + this.state = {}; + this.state.pipelines = []; + } + + storePipelines(pipelines = []) { + this.state.pipelines = pipelines; + + return pipelines; + } + + /** + * Once the data is received we will start the time ago loops. + * + * Everytime a request is made like retry or cancel a pipeline, every 10 seconds we + * update the time to show how long as passed. + * + */ + startTimeAgoLoops() { + const startTimeLoops = () => { + this.timeLoopInterval = setInterval(function timeloopInterval() { + this.$children[0].$children.reduce((acc, component) => { + const timeAgoComponent = component.$children.filter(el => el.$options._componentTag === 'time-ago')[0]; + acc.push(timeAgoComponent); + return acc; + }, []).forEach(e => e.changeTime()); + }, 10000); + }; + + startTimeLoops(); + + const removeIntervals = () => clearInterval(this.timeLoopInterval); + const startIntervals = () => startTimeLoops(); + + gl.VueRealtimeListener(removeIntervals, startIntervals); + } +} + +window.gl = window.gl || {}; +gl.commits = gl.commits || {}; +gl.commits.pipelines = gl.commits.pipelines || {}; +gl.commits.pipelines.PipelinesStore = PipelinesStore; diff --git a/app/assets/javascripts/commit/pipelines/pipelines_table.js.es6 b/app/assets/javascripts/commit/pipelines/pipelines_table.js.es6 new file mode 100644 index 00000000000..ce0dbd4d56b --- /dev/null +++ b/app/assets/javascripts/commit/pipelines/pipelines_table.js.es6 @@ -0,0 +1,107 @@ +/* eslint-disable no-new, no-param-reassign */ +/* global Vue, CommitsPipelineStore, PipelinesService, Flash */ + +window.Vue = require('vue'); +window.Vue.use(require('vue-resource')); +require('../../lib/utils/common_utils'); +require('../../vue_shared/vue_resource_interceptor'); +require('../../vue_shared/components/pipelines_table'); +require('../../vue_realtime_listener/index'); +require('./pipelines_service'); +require('./pipelines_store'); + +/** + * + * Uses `pipelines-table-component` to render Pipelines table with an API call. + * Endpoint is provided in HTML and passed as `endpoint`. + * We need a store to store the received environemnts. + * We need a service to communicate with the server. + * + * Necessary SVG in the table are provided as props. This should be refactored + * as soon as we have Webpack and can load them directly into JS files. + */ + +(() => { + window.gl = window.gl || {}; + gl.commits = gl.commits || {}; + gl.commits.pipelines = gl.commits.pipelines || {}; + + gl.commits.pipelines.PipelinesTableView = Vue.component('pipelines-table', { + + components: { + 'pipelines-table-component': gl.pipelines.PipelinesTableComponent, + }, + + /** + * Accesses the DOM to provide the needed data. + * Returns the necessary props to render `pipelines-table-component` component. + * + * @return {Object} + */ + data() { + const pipelinesTableData = document.querySelector('#commit-pipeline-table-view').dataset; + const svgsData = document.querySelector('.pipeline-svgs').dataset; + const store = new gl.commits.pipelines.PipelinesStore(); + + // Transform svgs DOMStringMap to a plain Object. + const svgsObject = gl.utils.DOMStringMapToObject(svgsData); + + return { + endpoint: pipelinesTableData.endpoint, + svgs: svgsObject, + store, + state: store.state, + isLoading: false, + }; + }, + + /** + * When the component is created the service to fetch the data will be + * initialized with the correct endpoint. + * + * A request to fetch the pipelines will be made. + * In case of a successfull response we will store the data in the provided + * store, in case of a failed response we need to warn the user. + * + */ + created() { + const pipelinesService = new gl.commits.pipelines.PipelinesService(this.endpoint); + + this.isLoading = true; + return pipelinesService.all() + .then(response => response.json()) + .then((json) => { + this.store.storePipelines(json); + this.store.startTimeAgoLoops.call(this, Vue); + this.isLoading = false; + }) + .catch(() => { + this.isLoading = false; + new Flash('An error occurred while fetching the pipelines, please reload the page again.', 'alert'); + }); + }, + + template: ` + <div> + <div class="pipelines realtime-loading" v-if="isLoading"> + <i class="fa fa-spinner fa-spin"></i> + </div> + + <div class="blank-state blank-state-no-icon" + v-if="!isLoading && state.pipelines.length === 0"> + <h2 class="blank-state-title js-blank-state-title"> + No pipelines to show + </h2> + </div> + + <div class="table-holder pipelines" + v-if="!isLoading && state.pipelines.length > 0"> + <pipelines-table-component + :pipelines="state.pipelines" + :svgs="svgs"> + </pipelines-table-component> + </div> + </div> + `, + }); +})(); diff --git a/app/assets/javascripts/commits.js b/app/assets/javascripts/commits.js index 24a6e4ff0e9..c6fdfbcaa10 100644 --- a/app/assets/javascripts/commits.js +++ b/app/assets/javascripts/commits.js @@ -1,9 +1,9 @@ -/* eslint-disable func-names, space-before-function-paren, wrap-iife, quotes, consistent-return, no-return-assign, no-param-reassign, one-var, no-var, one-var-declaration-per-line, no-unused-vars, prefer-template, object-shorthand, comma-dangle, padded-blocks, max-len, prefer-arrow-callback */ +/* eslint-disable func-names, space-before-function-paren, wrap-iife, quotes, consistent-return, no-return-assign, no-param-reassign, one-var, no-var, one-var-declaration-per-line, no-unused-vars, prefer-template, object-shorthand, comma-dangle, max-len, prefer-arrow-callback */ /* global Pager */ (function() { this.CommitsList = (function() { - function CommitsList() {} + var CommitsList = {}; CommitsList.timer = null; @@ -20,6 +20,7 @@ }); this.content = $("#commits-list"); this.searchField = $("#commits-search"); + this.lastSearch = this.searchField.val(); return this.initSearch(); }; @@ -37,6 +38,7 @@ var commitsUrl, form, search; form = $(".commits-search-form"); search = CommitsList.searchField.val(); + if (search === CommitsList.lastSearch) return; commitsUrl = form.attr("action") + '?' + form.serialize(); CommitsList.content.fadeTo('fast', 0.5); return $.ajax({ @@ -47,18 +49,20 @@ return CommitsList.content.fadeTo('fast', 1.0); }, success: function(data) { + CommitsList.lastSearch = search; CommitsList.content.html(data.html); return history.replaceState({ page: commitsUrl // Change url so if user reload a page - search results are saved }, document.title, commitsUrl); }, + error: function() { + CommitsList.lastSearch = null; + }, dataType: "json" }); }; return CommitsList; - })(); - }).call(this); diff --git a/app/assets/javascripts/compare.js b/app/assets/javascripts/compare.js index d4243baadb5..9591df70e9c 100644 --- a/app/assets/javascripts/compare.js +++ b/app/assets/javascripts/compare.js @@ -1,4 +1,4 @@ -/* eslint-disable func-names, space-before-function-paren, wrap-iife, quotes, no-var, object-shorthand, consistent-return, no-unused-vars, comma-dangle, vars-on-top, prefer-template, padded-blocks, max-len */ +/* eslint-disable func-names, space-before-function-paren, wrap-iife, quotes, no-var, object-shorthand, consistent-return, no-unused-vars, comma-dangle, vars-on-top, prefer-template, max-len */ (function() { this.Compare = (function() { function Compare(opts) { @@ -87,7 +87,5 @@ }; return Compare; - })(); - }).call(this); diff --git a/app/assets/javascripts/compare_autocomplete.js.es6 b/app/assets/javascripts/compare_autocomplete.js.es6 index 45c974b2b68..3587431ab69 100644 --- a/app/assets/javascripts/compare_autocomplete.js.es6 +++ b/app/assets/javascripts/compare_autocomplete.js.es6 @@ -1,4 +1,4 @@ -/* eslint-disable func-names, space-before-function-paren, one-var, no-var, one-var-declaration-per-line, object-shorthand, comma-dangle, prefer-arrow-callback, no-else-return, newline-per-chained-call, no-dupe-keys, wrap-iife, padded-blocks, max-len */ +/* eslint-disable func-names, space-before-function-paren, one-var, no-var, one-var-declaration-per-line, object-shorthand, comma-dangle, prefer-arrow-callback, no-else-return, newline-per-chained-call, wrap-iife, max-len */ (function() { this.CompareAutocomplete = (function() { @@ -28,7 +28,6 @@ selectable: true, filterable: true, filterByText: true, - toggleLabel: true, fieldName: $dropdown.data('field-name'), filterInput: 'input[type="search"]', renderRow: function(ref) { @@ -55,11 +54,16 @@ $('.dropdown-toggle-text', $dropdown).text(text); $dropdownContainer.removeClass('open'); }); + + $dropdownContainer.on('click', '.dropdown-content a', (e) => { + $dropdown.prop('title', e.target.text.replace(/_+?/g, '-')); + if ($dropdown.hasClass('has-tooltip')) { + $dropdown.tooltip('fixTitle'); + } + }); }); }; return CompareAutocomplete; - })(); - }).call(this); diff --git a/app/assets/javascripts/confirm_danger_modal.js b/app/assets/javascripts/confirm_danger_modal.js index 686a48486f3..35d98492012 100644 --- a/app/assets/javascripts/confirm_danger_modal.js +++ b/app/assets/javascripts/confirm_danger_modal.js @@ -1,4 +1,4 @@ -/* eslint-disable func-names, space-before-function-paren, wrap-iife, one-var, no-var, camelcase, one-var-declaration-per-line, no-else-return, padded-blocks, max-len */ +/* eslint-disable func-names, space-before-function-paren, wrap-iife, one-var, no-var, camelcase, one-var-declaration-per-line, no-else-return, max-len */ (function() { this.ConfirmDangerModal = (function() { function ConfirmDangerModal(form, text) { @@ -27,7 +27,5 @@ } return ConfirmDangerModal; - })(); - }).call(this); diff --git a/app/assets/javascripts/copy_as_gfm.js.es6 b/app/assets/javascripts/copy_as_gfm.js.es6 new file mode 100644 index 00000000000..2bfe57b4100 --- /dev/null +++ b/app/assets/javascripts/copy_as_gfm.js.es6 @@ -0,0 +1,355 @@ +/* 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 */ +/* jshint esversion: 6 */ + +require('./lib/utils/common_utils'); + +(() => { + const gfmRules = { + // The filters referenced in lib/banzai/pipeline/gfm_pipeline.rb convert + // GitLab Flavored Markdown (GFM) to HTML. + // These handlers consequently convert that same HTML to GFM to be copied to the clipboard. + // Every filter in lib/banzai/pipeline/gfm_pipeline.rb that generates HTML + // from GFM should have a handler here, in reverse order. + // The GFM-to-HTML-to-GFM cycle is tested in spec/features/copy_as_gfm_spec.rb. + InlineDiffFilter: { + 'span.idiff.addition'(el, text) { + return `{+${text}+}`; + }, + 'span.idiff.deletion'(el, text) { + return `{-${text}-}`; + }, + }, + TaskListFilter: { + 'input[type=checkbox].task-list-item-checkbox'(el, text) { + return `[${el.checked ? 'x' : ' '}]`; + }, + }, + ReferenceFilter: { + 'a.gfm:not([data-link=true])'(el, text) { + return el.dataset.original || text; + }, + }, + AutolinkFilter: { + 'a'(el, text) { + // Fallback on the regular MarkdownFilter's `a` handler. + if (text !== el.getAttribute('href')) return false; + + return text; + }, + }, + TableOfContentsFilter: { + 'ul.section-nav'(el, text) { + return '[[_TOC_]]'; + }, + }, + EmojiFilter: { + 'img.emoji'(el, text) { + return el.getAttribute('alt'); + }, + }, + ImageLinkFilter: { + 'a.no-attachment-icon'(el, text) { + return text; + }, + }, + VideoLinkFilter: { + '.video-container'(el, text) { + const videoEl = el.querySelector('video'); + if (!videoEl) return false; + + return CopyAsGFM.nodeToGFM(videoEl); + }, + 'video'(el, text) { + return `![${el.dataset.title}](${el.getAttribute('src')})`; + }, + }, + MathFilter: { + 'pre.code.math[data-math-style=display]'(el, text) { + return `\`\`\`math\n${text.trim()}\n\`\`\``; + }, + 'code.code.math[data-math-style=inline]'(el, text) { + return `$\`${text}\`$`; + }, + 'span.katex-display span.katex-mathml'(el, text) { + const mathAnnotation = el.querySelector('annotation[encoding="application/x-tex"]'); + if (!mathAnnotation) return false; + + return `\`\`\`math\n${CopyAsGFM.nodeToGFM(mathAnnotation)}\n\`\`\``; + }, + 'span.katex-mathml'(el, text) { + const mathAnnotation = el.querySelector('annotation[encoding="application/x-tex"]'); + if (!mathAnnotation) return false; + + return `$\`${CopyAsGFM.nodeToGFM(mathAnnotation)}\`$`; + }, + 'span.katex-html'(el, text) { + // We don't want to include the content of this element in the copied text. + return ''; + }, + 'annotation[encoding="application/x-tex"]'(el, text) { + return text.trim(); + }, + }, + SanitizationFilter: { + 'dl'(el, text) { + let lines = text.trim().split('\n'); + // Add two spaces to the front of subsequent list items lines, + // or leave the line entirely blank. + lines = lines.map((l) => { + const line = l.trim(); + if (line.length === 0) return ''; + + return ` ${line}`; + }); + + return `<dl>\n${lines.join('\n')}\n</dl>`; + }, + 'sub, dt, dd, kbd, q, samp, var, ruby, rt, rp, abbr'(el, text) { + const tag = el.nodeName.toLowerCase(); + return `<${tag}>${text}</${tag}>`; + }, + }, + SyntaxHighlightFilter: { + 'pre.code.highlight'(el, t) { + const text = t.trim(); + + let lang = el.getAttribute('lang'); + if (lang === 'plaintext') { + lang = ''; + } + + // Prefixes lines with 4 spaces if the code contains triple backticks + if (lang === '' && text.match(/^```/gm)) { + return text.split('\n').map((l) => { + const line = l.trim(); + if (line.length === 0) return ''; + + return ` ${line}`; + }).join('\n'); + } + + return `\`\`\`${lang}\n${text}\n\`\`\``; + }, + 'pre > code'(el, text) { + // Don't wrap code blocks in `` + return text; + }, + }, + MarkdownFilter: { + 'br'(el, text) { + // Two spaces at the end of a line are turned into a BR + return ' '; + }, + 'code'(el, text) { + let backtickCount = 1; + const backtickMatch = text.match(/`+/); + if (backtickMatch) { + backtickCount = backtickMatch[0].length + 1; + } + + const backticks = Array(backtickCount + 1).join('`'); + const spaceOrNoSpace = backtickCount > 1 ? ' ' : ''; + + return backticks + spaceOrNoSpace + text + spaceOrNoSpace + backticks; + }, + 'blockquote'(el, text) { + return text.trim().split('\n').map(s => `> ${s}`.trim()).join('\n'); + }, + 'img'(el, text) { + return `![${el.getAttribute('alt')}](${el.getAttribute('src')})`; + }, + 'a.anchor'(el, text) { + // Don't render a Markdown link for the anchor link inside a heading + return text; + }, + 'a'(el, text) { + return `[${text}](${el.getAttribute('href')})`; + }, + 'li'(el, text) { + const lines = text.trim().split('\n'); + const firstLine = `- ${lines.shift()}`; + // Add four spaces to the front of subsequent list items lines, + // or leave the line entirely blank. + const nextLines = lines.map((s) => { + if (s.trim().length === 0) return ''; + + return ` ${s}`; + }); + + return `${firstLine}\n${nextLines.join('\n')}`; + }, + 'ul'(el, text) { + return text; + }, + 'ol'(el, text) { + // LIs get a `- ` prefix by default, which we replace by `1. ` for ordered lists. + return text.replace(/^- /mg, '1. '); + }, + 'h1'(el, text) { + return `# ${text.trim()}`; + }, + 'h2'(el, text) { + return `## ${text.trim()}`; + }, + 'h3'(el, text) { + return `### ${text.trim()}`; + }, + 'h4'(el, text) { + return `#### ${text.trim()}`; + }, + 'h5'(el, text) { + return `##### ${text.trim()}`; + }, + 'h6'(el, text) { + return `###### ${text.trim()}`; + }, + 'strong'(el, text) { + return `**${text}**`; + }, + 'em'(el, text) { + return `_${text}_`; + }, + 'del'(el, text) { + return `~~${text}~~`; + }, + 'sup'(el, text) { + return `^${text}`; + }, + 'hr'(el, text) { + return '-----'; + }, + 'table'(el, text) { + const theadEl = el.querySelector('thead'); + const tbodyEl = el.querySelector('tbody'); + if (!theadEl || !tbodyEl) return false; + + const theadText = CopyAsGFM.nodeToGFM(theadEl); + const tbodyText = CopyAsGFM.nodeToGFM(tbodyEl); + + return theadText + tbodyText; + }, + 'thead'(el, text) { + const cells = _.map(el.querySelectorAll('th'), (cell) => { + let chars = CopyAsGFM.nodeToGFM(cell).trim().length + 2; + + let before = ''; + let after = ''; + switch (cell.style.textAlign) { + case 'center': + before = ':'; + after = ':'; + chars -= 2; + break; + case 'right': + after = ':'; + chars -= 1; + break; + default: + break; + } + + chars = Math.max(chars, 3); + + const middle = Array(chars + 1).join('-'); + + return before + middle + after; + }); + + return `${text}|${cells.join('|')}|`; + }, + 'tr'(el, text) { + const cells = _.map(el.querySelectorAll('td, th'), cell => CopyAsGFM.nodeToGFM(cell).trim()); + return `| ${cells.join(' | ')} |`; + }, + }, + }; + + class CopyAsGFM { + constructor() { + $(document).on('copy', '.md, .wiki', this.handleCopy); + $(document).on('paste', '.js-gfm-input', this.handlePaste); + } + + handleCopy(e) { + const clipboardData = e.originalEvent.clipboardData; + if (!clipboardData) return; + + const documentFragment = window.gl.utils.getSelectedFragment(); + if (!documentFragment) return; + + // If the documentFragment contains more than just Markdown, don't copy as GFM. + if (documentFragment.querySelector('.md, .wiki')) return; + + e.preventDefault(); + clipboardData.setData('text/plain', documentFragment.textContent); + + const gfm = CopyAsGFM.nodeToGFM(documentFragment); + clipboardData.setData('text/x-gfm', gfm); + } + + handlePaste(e) { + const clipboardData = e.originalEvent.clipboardData; + if (!clipboardData) return; + + const gfm = clipboardData.getData('text/x-gfm'); + if (!gfm) return; + + e.preventDefault(); + + window.gl.utils.insertText(e.target, gfm); + } + + static nodeToGFM(node) { + if (node.nodeType === Node.TEXT_NODE) { + return node.textContent; + } + + const text = this.innerGFM(node); + + if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) { + return text; + } + + for (const filter in gfmRules) { + const rules = gfmRules[filter]; + + for (const selector in rules) { + const func = rules[selector]; + + if (!window.gl.utils.nodeMatchesSelector(node, selector)) continue; + + const result = func(node, text); + if (result === false) continue; + + return result; + } + } + + return text; + } + + static innerGFM(parentNode) { + const nodes = parentNode.childNodes; + + const clonedParentNode = parentNode.cloneNode(true); + const clonedNodes = Array.prototype.slice.call(clonedParentNode.childNodes, 0); + + for (let i = 0; i < nodes.length; i += 1) { + const node = nodes[i]; + const clonedNode = clonedNodes[i]; + + const text = this.nodeToGFM(node); + + // `clonedNode.replaceWith(text)` is not yet widely supported + clonedNode.parentNode.replaceChild(document.createTextNode(text), clonedNode); + } + + return clonedParentNode.innerText || clonedParentNode.textContent; + } + } + + window.gl = window.gl || {}; + window.gl.CopyAsGFM = CopyAsGFM; + + new CopyAsGFM(); +})(); diff --git a/app/assets/javascripts/copy_to_clipboard.js b/app/assets/javascripts/copy_to_clipboard.js index 6a13f38588d..0029c59e550 100644 --- a/app/assets/javascripts/copy_to_clipboard.js +++ b/app/assets/javascripts/copy_to_clipboard.js @@ -1,7 +1,7 @@ -/* eslint-disable func-names, space-before-function-paren, one-var, no-var, one-var-declaration-per-line, prefer-template, quotes, no-unused-vars, prefer-arrow-callback, padded-blocks, max-len */ +/* eslint-disable func-names, space-before-function-paren, one-var, no-var, one-var-declaration-per-line, prefer-template, quotes, no-unused-vars, prefer-arrow-callback, max-len */ /* global Clipboard */ -/*= require clipboard */ +window.Clipboard = require('vendor/clipboard'); (function() { var genericError, genericSuccess, showTooltip; @@ -46,5 +46,4 @@ clipboard.on('success', genericSuccess); return clipboard.on('error', genericError); }); - }).call(this); diff --git a/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js.es6 b/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js.es6 index 2f810a69758..c41c57c1dcd 100644 --- a/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js.es6 +++ b/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js.es6 @@ -2,9 +2,12 @@ /* global Cookies */ /* global Flash */ -//= require vue -//= require_tree ./svg -//= require_tree . +window.Vue = require('vue'); +window.Cookies = require('vendor/js.cookie'); + +function requireAll(context) { return context.keys().map(context); } +requireAll(require.context('./svg', false, /^\.\/.*\.(js|es6)$/)); +requireAll(require.context('.', true, /^\.\/(?!cycle_analytics_bundle).*\.(js|es6)$/)); $(() => { const OVERVIEW_DIALOG_COOKIE = 'cycle_analytics_help_dismissed'; diff --git a/app/assets/javascripts/diff.js.es6 b/app/assets/javascripts/diff.js.es6 index 9cf33e62958..c39e30fb7e0 100644 --- a/app/assets/javascripts/diff.js.es6 +++ b/app/assets/javascripts/diff.js.es6 @@ -1,7 +1,10 @@ /* eslint-disable class-methods-use-this */ +require('./lib/utils/url_utility'); + (() => { const UNFOLD_COUNT = 20; + let isBound = false; class Diff { constructor() { @@ -15,12 +18,14 @@ $('.content-wrapper .container-fluid').removeClass('container-limited'); } - $(document) - .off('click', '.js-unfold, .diff-line-num a') - .on('click', '.js-unfold', this.handleClickUnfold.bind(this)) - .on('click', '.diff-line-num a', this.handleClickLineNum.bind(this)); + if (!isBound) { + $(document) + .on('click', '.js-unfold', this.handleClickUnfold.bind(this)) + .on('click', '.diff-line-num a', this.handleClickLineNum.bind(this)); + isBound = true; + } - this.highlighSelectedLine(); + this.openAnchoredDiff(); } handleClickUnfold(e) { @@ -61,13 +66,22 @@ $.get(link, params, response => $target.parent().replaceWith(response)); } - openAnchoredDiff(anchoredDiff, cb) { - const diffTitle = $(`#file-path-${anchoredDiff}`); + openAnchoredDiff(cb) { + const locationHash = gl.utils.getLocationHash(); + const anchoredDiff = locationHash && locationHash.split('_')[0]; + + if (!anchoredDiff) return; + + const diffTitle = $(`#${anchoredDiff}`); const diffFile = diffTitle.closest('.diff-file'); const nothingHereBlock = $('.nothing-here-block:visible', diffFile); if (nothingHereBlock.length) { - diffFile.singleFileDiff(true, cb); - } else { + const clickTarget = $('.file-title, .click-to-expand', diffFile); + diffFile.data('singleFileDiff').toggleDiff(clickTarget, () => { + this.highlighSelectedLine(); + if (cb) cb(); + }); + } else if (cb) { cb(); } } @@ -95,11 +109,11 @@ } highlighSelectedLine() { + const hash = gl.utils.getLocationHash(); const $diffFiles = $('.diff-file'); $diffFiles.find('.hll').removeClass('hll'); - if (window.location.hash !== '') { - const hash = window.location.hash.replace('#', ''); + if (hash) { $diffFiles .find(`tr#${hash}:not(.match) td, td#${hash}, td[data-line-code="${hash}"]`) .addClass('hll'); diff --git a/app/assets/javascripts/diff_notes/components/comment_resolve_btn.js.es6 b/app/assets/javascripts/diff_notes/components/comment_resolve_btn.js.es6 index c59d3996fab..2514459e65e 100644 --- a/app/assets/javascripts/diff_notes/components/comment_resolve_btn.js.es6 +++ b/app/assets/javascripts/diff_notes/components/comment_resolve_btn.js.es6 @@ -1,4 +1,4 @@ -/* eslint-disable comma-dangle, object-shorthand, func-names, no-else-return, quotes, no-lonely-if, semi, max-len */ +/* eslint-disable comma-dangle, object-shorthand, func-names, no-else-return, quotes, no-lonely-if, max-len */ /* global Vue */ /* global CommentsStore */ @@ -10,7 +10,7 @@ data() { return { textareaIsEmpty: true - } + }; }, computed: { discussion: function () { diff --git a/app/assets/javascripts/diff_notes/components/jump_to_discussion.js.es6 b/app/assets/javascripts/diff_notes/components/jump_to_discussion.js.es6 index f47867fc3b0..c3898873eaa 100644 --- a/app/assets/javascripts/diff_notes/components/jump_to_discussion.js.es6 +++ b/app/assets/javascripts/diff_notes/components/jump_to_discussion.js.es6 @@ -1,4 +1,4 @@ -/* eslint-disable comma-dangle, object-shorthand, func-names, no-else-return, guard-for-in, no-restricted-syntax, one-var, indent, space-before-function-paren, no-plusplus, no-lonely-if, no-continue, brace-style, max-len, quotes, semi */ +/* eslint-disable comma-dangle, object-shorthand, func-names, no-else-return, guard-for-in, no-restricted-syntax, one-var, space-before-function-paren, no-lonely-if, no-continue, brace-style, max-len, quotes */ /* global Vue */ /* global DiscussionMixins */ /* global CommentsStore */ @@ -46,13 +46,13 @@ }, methods: { jumpToNextUnresolvedDiscussion: function () { - let discussionsSelector, - discussionIdsInScope, - firstUnresolvedDiscussionId, - nextUnresolvedDiscussionId, - activeTab = window.mrTabs.currentAction, - hasDiscussionsToJumpTo = true, - jumpToFirstDiscussion = !this.discussionId; + let discussionsSelector; + let discussionIdsInScope; + let firstUnresolvedDiscussionId; + let nextUnresolvedDiscussionId; + let activeTab = window.mrTabs.currentAction; + let hasDiscussionsToJumpTo = true; + let jumpToFirstDiscussion = !this.discussionId; const discussionIdsForElements = function(elements) { return elements.map(function() { @@ -68,11 +68,11 @@ let unresolvedDiscussionCount = 0; - for (let i = 0; i < discussionIdsInScope.length; i++) { + for (let i = 0; i < discussionIdsInScope.length; i += 1) { const discussionId = discussionIdsInScope[i]; const discussion = discussions[discussionId]; if (discussion && !discussion.isResolved()) { - unresolvedDiscussionCount++; + unresolvedDiscussionCount += 1; } } @@ -109,7 +109,7 @@ } let currentDiscussionFound = false; - for (let i = 0; i < discussionIdsInScope.length; i++) { + for (let i = 0; i < discussionIdsInScope.length; i += 1) { const discussionId = discussionIdsInScope[i]; const discussion = discussions[discussionId]; @@ -156,7 +156,7 @@ // If the next discussion is closed, toggle it open. if ($target.find('.js-toggle-content').is(':hidden')) { - $target.find('.js-toggle-button i').trigger('click') + $target.find('.js-toggle-button i').trigger('click'); } } else if (activeTab === 'diffs') { // Resolved discussions are hidden in the diffs tab by default. @@ -170,7 +170,7 @@ // If we are on the diffs tab, we don't scroll to the discussion itself, but to // 4 diff lines above it: the line the discussion was in response to + 3 context let prevEl; - for (let i = 0; i < 4; i++) { + for (let i = 0; i < 4; i += 1) { prevEl = $target.prev(); // If the discussion doesn't have 4 lines above it, we'll have to do with fewer. diff --git a/app/assets/javascripts/diff_notes/components/resolve_btn.js.es6 b/app/assets/javascripts/diff_notes/components/resolve_btn.js.es6 index 88a19fc6e1d..5852b8bbdb7 100644 --- a/app/assets/javascripts/diff_notes/components/resolve_btn.js.es6 +++ b/app/assets/javascripts/diff_notes/components/resolve_btn.js.es6 @@ -59,9 +59,11 @@ }, methods: { updateTooltip: function () { - $(this.$refs.button) - .tooltip('hide') - .tooltip('fixTitle'); + this.$nextTick(() => { + $(this.$refs.button) + .tooltip('hide') + .tooltip('fixTitle'); + }); }, resolve: function () { if (!this.canResolve) return; @@ -90,7 +92,7 @@ new Flash('An error occurred when trying to resolve a comment. Please try again.', 'alert'); } - this.$nextTick(this.updateTooltip); + this.updateTooltip(); }); } }, diff --git a/app/assets/javascripts/diff_notes/diff_notes_bundle.js.es6 b/app/assets/javascripts/diff_notes/diff_notes_bundle.js.es6 index 840b5aa922e..f0edfb8aaf1 100644 --- a/app/assets/javascripts/diff_notes/diff_notes_bundle.js.es6 +++ b/app/assets/javascripts/diff_notes/diff_notes_bundle.js.es6 @@ -1,14 +1,13 @@ -/* eslint-disable func-names, comma-dangle, new-cap, no-new */ +/* eslint-disable func-names, comma-dangle, new-cap, no-new, import/newline-after-import, no-multi-spaces, max-len */ /* global Vue */ /* global ResolveCount */ -//= require vue -//= require vue-resource -//= require_directory ./models -//= require_directory ./stores -//= require_directory ./services -//= require_directory ./mixins -//= require_directory ./components +function requireAll(context) { return context.keys().map(context); } +requireAll(require.context('./models', false, /^\.\/.*\.(js|es6)$/)); +requireAll(require.context('./stores', false, /^\.\/.*\.(js|es6)$/)); +requireAll(require.context('./services', false, /^\.\/.*\.(js|es6)$/)); +requireAll(require.context('./mixins', false, /^\.\/.*\.(js|es6)$/)); +requireAll(require.context('./components', false, /^\.\/.*\.(js|es6)$/)); $(() => { const COMPONENT_SELECTOR = 'resolve-btn, resolve-discussion-btn, jump-to-discussion, comment-and-resolve-btn'; diff --git a/app/assets/javascripts/diff_notes/mixins/discussion.js.es6 b/app/assets/javascripts/diff_notes/mixins/discussion.js.es6 index a9ea18bf82b..3c08c222f46 100644 --- a/app/assets/javascripts/diff_notes/mixins/discussion.js.es6 +++ b/app/assets/javascripts/diff_notes/mixins/discussion.js.es6 @@ -1,4 +1,4 @@ -/* eslint-disable object-shorthand, func-names, guard-for-in, no-restricted-syntax, comma-dangle, no-plusplus, no-param-reassign, max-len */ +/* eslint-disable object-shorthand, func-names, guard-for-in, no-restricted-syntax, comma-dangle, no-param-reassign, max-len */ ((w) => { w.DiscussionMixins = { @@ -13,7 +13,7 @@ const discussion = this.discussions[discussionId]; if (discussion.isResolved()) { - resolvedCount++; + resolvedCount += 1; } } @@ -26,7 +26,7 @@ const discussion = this.discussions[discussionId]; if (!discussion.isResolved()) { - unresolvedCount++; + unresolvedCount += 1; } } diff --git a/app/assets/javascripts/diff_notes/models/discussion.js.es6 b/app/assets/javascripts/diff_notes/models/discussion.js.es6 index efcd46680a7..fa518ba4d33 100644 --- a/app/assets/javascripts/diff_notes/models/discussion.js.es6 +++ b/app/assets/javascripts/diff_notes/models/discussion.js.es6 @@ -92,3 +92,5 @@ class DiscussionModel { return false; } } + +window.DiscussionModel = DiscussionModel; diff --git a/app/assets/javascripts/diff_notes/models/note.js.es6 b/app/assets/javascripts/diff_notes/models/note.js.es6 index e3bce1d2038..f3a7cba5ef6 100644 --- a/app/assets/javascripts/diff_notes/models/note.js.es6 +++ b/app/assets/javascripts/diff_notes/models/note.js.es6 @@ -9,3 +9,5 @@ class NoteModel { this.resolved_by = resolved_by; } } + +window.NoteModel = NoteModel; diff --git a/app/assets/javascripts/diff_notes/services/resolve.js.es6 b/app/assets/javascripts/diff_notes/services/resolve.js.es6 index 78c74985f78..a52c476352d 100644 --- a/app/assets/javascripts/diff_notes/services/resolve.js.es6 +++ b/app/assets/javascripts/diff_notes/services/resolve.js.es6 @@ -1,4 +1,4 @@ -/* eslint-disable class-methods-use-this, one-var, indent, camelcase, no-new, comma-dangle, semi, no-param-reassign, max-len */ +/* eslint-disable class-methods-use-this, one-var, camelcase, no-new, comma-dangle, no-param-reassign, max-len */ /* global Vue */ /* global Flash */ /* global CommentsStore */ @@ -32,8 +32,8 @@ } toggleResolveForDiscussion(projectPath, mergeRequestId, discussionId) { - const discussion = CommentsStore.state[discussionId], - isResolved = discussion.isResolved(); + const discussion = CommentsStore.state[discussionId]; + const isResolved = discussion.isResolved(); let promise; if (isResolved) { @@ -59,7 +59,7 @@ } else { new Flash('An error occurred when trying to resolve a discussion. Please try again.', 'alert'); } - }) + }); } resolveAll(projectPath, mergeRequestId, discussionId) { diff --git a/app/assets/javascripts/diff_notes/stores/comments.js.es6 b/app/assets/javascripts/diff_notes/stores/comments.js.es6 index 1a7abbc6f75..c80d979b977 100644 --- a/app/assets/javascripts/diff_notes/stores/comments.js.es6 +++ b/app/assets/javascripts/diff_notes/stores/comments.js.es6 @@ -1,4 +1,4 @@ -/* eslint-disable object-shorthand, func-names, camelcase, prefer-const, no-restricted-syntax, guard-for-in, comma-dangle, max-len, no-param-reassign */ +/* eslint-disable object-shorthand, func-names, camelcase, no-restricted-syntax, guard-for-in, comma-dangle, max-len, no-param-reassign */ /* global Vue */ /* global DiscussionModel */ @@ -41,7 +41,7 @@ } }, unresolvedDiscussionIds: function () { - let ids = []; + const ids = []; for (const discussionId in this.state) { const discussion = this.state[discussionId]; diff --git a/app/assets/javascripts/dispatcher.js.es6 b/app/assets/javascripts/dispatcher.js.es6 index 752f35e6356..f8efca76b13 100644 --- a/app/assets/javascripts/dispatcher.js.es6 +++ b/app/assets/javascripts/dispatcher.js.es6 @@ -1,4 +1,4 @@ -/* eslint-disable func-names, space-before-function-paren, no-var, prefer-arrow-callback, wrap-iife, no-shadow, consistent-return, one-var, one-var-declaration-per-line, camelcase, default-case, no-new, quotes, no-duplicate-case, no-case-declarations, no-fallthrough, max-len, padded-blocks */ +/* eslint-disable func-names, space-before-function-paren, no-var, prefer-arrow-callback, wrap-iife, no-shadow, consistent-return, one-var, one-var-declaration-per-line, camelcase, default-case, no-new, quotes, no-duplicate-case, no-case-declarations, no-fallthrough, max-len */ /* global UsernameValidator */ /* global ActiveTabMemoizer */ /* global ShortcutsNavigation */ @@ -8,7 +8,6 @@ /* global ShortcutsIssuable */ /* global ZenMode */ /* global Milestone */ -/* global GLForm */ /* global IssuableForm */ /* global LabelsSelect */ /* global MilestoneSelect */ @@ -20,7 +19,6 @@ /* global UsersSelect */ /* global GroupAvatar */ /* global LineHighlighter */ -/* global ShortcutsBlob */ /* global ProjectFork */ /* global BuildArtifacts */ /* global GroupsSelect */ @@ -37,6 +35,8 @@ /* global Labels */ /* global Shortcuts */ +const ShortcutsBlob = require('./shortcuts_blob'); + (function() { var Dispatcher; @@ -73,6 +73,9 @@ break; case 'projects:merge_requests:index': case 'projects:issues:index': + if (gl.FilteredSearchManager) { + new gl.FilteredSearchManager(); + } Issuable.init(); new gl.IssuableBulkActions({ prefixId: page === 'projects:merge_requests:index' ? 'merge_request_' : 'issue_', @@ -96,7 +99,7 @@ case 'projects:milestones:edit': new ZenMode(); new gl.DueDateSelectors(); - new GLForm($('.milestone-form')); + new gl.GLForm($('.milestone-form')); break; case 'groups:milestones:new': new ZenMode(); @@ -107,7 +110,7 @@ case 'projects:issues:new': case 'projects:issues:edit': shortcut_handler = new ShortcutsNavigation(); - new GLForm($('.issue-form')); + new gl.GLForm($('.issue-form')); new IssuableForm($('.issue-form')); new LabelsSelect(); new MilestoneSelect(); @@ -117,7 +120,7 @@ case 'projects:merge_requests:edit': new gl.Diff(); shortcut_handler = new ShortcutsNavigation(); - new GLForm($('.merge-request-form')); + new gl.GLForm($('.merge-request-form')); new IssuableForm($('.merge-request-form')); new LabelsSelect(); new MilestoneSelect(); @@ -125,11 +128,11 @@ break; case 'projects:tags:new': new ZenMode(); - new GLForm($('.tag-form')); + new gl.GLForm($('.tag-form')); break; case 'projects:releases:edit': new ZenMode(); - new GLForm($('.release-form')); + new gl.GLForm($('.release-form')); break; case 'projects:merge_requests:show': new gl.Diff(); @@ -138,14 +141,8 @@ new MergedButtons(); break; case 'projects:merge_requests:commits': - case 'projects:merge_requests:builds': new MergedButtons(); break; - case 'projects:merge_requests:pipelines': - new gl.MiniPipelineGraph({ - container: '.js-pipeline-table', - }); - break; case "projects:merge_requests:diffs": new gl.Diff(); new ZenMode(); @@ -166,10 +163,7 @@ case 'projects:commit:pipelines': new gl.MiniPipelineGraph({ container: '.js-pipeline-table', - }); - break; - case 'projects:commit:builds': - new gl.Pipelines(); + }).bindEvents(); break; case 'projects:commits:show': case 'projects:activity': @@ -182,11 +176,6 @@ new TreeView(); } break; - case 'projects:pipelines:index': - new gl.MiniPipelineGraph({ - container: '.js-pipeline-table', - }); - break; case 'projects:pipelines:builds': case 'projects:pipelines:show': const { controllerAction } = document.querySelector('.js-pipeline-container').dataset; @@ -213,7 +202,9 @@ new gl.Members(); new UsersSelect(); break; - case 'projects:project_members:index': + case 'projects:members:show': + new gl.MemberExpirationDate('.js-access-expiration-date-groups'); + new GroupsSelect(); new gl.MemberExpirationDate(); new gl.Members(); new UsersSelect(); @@ -235,7 +226,12 @@ case 'projects:blame:show': new LineHighlighter(); shortcut_handler = new ShortcutsNavigation(); - new ShortcutsBlob(true); + const fileBlobPermalinkUrlElement = document.querySelector('.js-data-file-blob-permalink-url'); + const fileBlobPermalinkUrl = fileBlobPermalinkUrlElement && fileBlobPermalinkUrlElement.getAttribute('href'); + new ShortcutsBlob({ + skipResetBindings: true, + fileBlobPermalinkUrl, + }); break; case 'groups:labels:new': case 'groups:labels:edit': @@ -259,9 +255,8 @@ case 'projects:artifacts:browse': new BuildArtifacts(); break; - case 'projects:group_links:index': - new gl.MemberExpirationDate(); - new GroupsSelect(); + case 'help:index': + gl.VersionCheckImage.bindErrorEvent($('img.js-version-status-badge')); break; case 'search:show': new Search(); @@ -270,11 +265,26 @@ new gl.ProtectedBranchCreate(); new gl.ProtectedBranchEditList(); break; - case 'projects:variables:index': + case 'projects:ci_cd:show': new gl.ProjectVariables(); break; + case 'ci:lints:create': + case 'ci:lints:show': + new gl.CILintEditor(); + break; } switch (path.first()) { + case 'sessions': + case 'omniauth_callbacks': + if (!gon.u2f) break; + gl.u2fAuthenticate = new gl.U2FAuthenticate( + $('#js-authenticate-u2f'), + '#js-login-u2f-form', + gon.u2f, + document.querySelector('#js-login-2fa-device'), + document.querySelector('.js-2fa-form'), + ); + gl.u2fAuthenticate.start(); case 'admin': new Admin(); switch (path[1]) { @@ -327,7 +337,7 @@ new gl.Wikis(); shortcut_handler = new ShortcutsNavigation(); new ZenMode(); - new GLForm($('.wiki-form')); + new gl.GLForm($('.wiki-form')); break; case 'snippets': shortcut_handler = new ShortcutsNavigation(); @@ -352,7 +362,7 @@ } // If we haven't installed a custom shortcut handler, install the default one if (!shortcut_handler) { - return new Shortcuts(); + new Shortcuts(); } }; @@ -370,7 +380,5 @@ }; return Dispatcher; - })(); - }).call(this); diff --git a/app/assets/javascripts/droplab/droplab.js b/app/assets/javascripts/droplab/droplab.js new file mode 100644 index 00000000000..8b14191395b --- /dev/null +++ b/app/assets/javascripts/droplab/droplab.js @@ -0,0 +1,741 @@ +/* eslint-disable */ +// Determine where to place this +if (typeof Object.assign != 'function') { + Object.assign = function (target, varArgs) { // .length of function is 2 + 'use strict'; + if (target == null) { // TypeError if undefined or null + throw new TypeError('Cannot convert undefined or null to object'); + } + + var to = Object(target); + + for (var index = 1; index < arguments.length; index++) { + var nextSource = arguments[index]; + + if (nextSource != null) { // Skip over if undefined or null + for (var nextKey in nextSource) { + // Avoid bugs when hasOwnProperty is shadowed + if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) { + to[nextKey] = nextSource[nextKey]; + } + } + } + } + return to; + }; +} + +(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.droplab = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){ +var DATA_TRIGGER = 'data-dropdown-trigger'; +var DATA_DROPDOWN = 'data-dropdown'; + +module.exports = { + DATA_TRIGGER: DATA_TRIGGER, + DATA_DROPDOWN: DATA_DROPDOWN, +} + +},{}],2:[function(require,module,exports){ +// Custom event support for IE +if ( typeof CustomEvent === "function" ) { + module.exports = CustomEvent; +} else { + require('./window')(function(w){ + var CustomEvent = function ( event, params ) { + params = params || { bubbles: false, cancelable: false, detail: undefined }; + var evt = document.createEvent( 'CustomEvent' ); + evt.initCustomEvent( event, params.bubbles, params.cancelable, params.detail ); + return evt; + } + CustomEvent.prototype = w.Event.prototype; + + w.CustomEvent = CustomEvent; + }); + module.exports = CustomEvent; +} + +},{"./window":11}],3:[function(require,module,exports){ +var CustomEvent = require('./custom_event_polyfill'); +var utils = require('./utils'); + +var DropDown = function(list) { + this.currentIndex = 0; + this.hidden = true; + this.list = list; + this.items = []; + this.getItems(); + this.initTemplateString(); + this.addEvents(); + this.initialState = list.innerHTML; +}; + +Object.assign(DropDown.prototype, { + getItems: function() { + this.items = [].slice.call(this.list.querySelectorAll('li')); + return this.items; + }, + + initTemplateString: function() { + var items = this.items || this.getItems(); + + var templateString = ''; + if(items.length > 0) { + templateString = items[items.length - 1].outerHTML; + } + this.templateString = templateString; + return this.templateString; + }, + + clickEvent: function(e) { + // climb up the tree to find the LI + var selected = utils.closest(e.target, 'LI'); + + if(selected) { + e.preventDefault(); + this.hide(); + var listEvent = new CustomEvent('click.dl', { + detail: { + list: this, + selected: selected, + data: e.target.dataset, + }, + }); + this.list.dispatchEvent(listEvent); + } + }, + + addEvents: function() { + this.clickWrapper = this.clickEvent.bind(this); + // event delegation. + this.list.addEventListener('click', this.clickWrapper); + }, + + toggle: function() { + if(this.hidden) { + this.show(); + } else { + this.hide(); + } + }, + + setData: function(data) { + this.data = data; + this.render(data); + }, + + addData: function(data) { + this.data = (this.data || []).concat(data); + this.render(this.data); + }, + + // call render manually on data; + render: function(data){ + // debugger + // empty the list first + var templateString = this.templateString; + var newChildren = []; + var toAppend; + + newChildren = (data ||[]).map(function(dat){ + var html = utils.t(templateString, dat); + var template = document.createElement('div'); + template.innerHTML = html; + + // Help set the image src template + var imageTags = template.querySelectorAll('img[data-src]'); + // debugger + for(var i = 0; i < imageTags.length; i++) { + var imageTag = imageTags[i]; + imageTag.src = imageTag.getAttribute('data-src'); + imageTag.removeAttribute('data-src'); + } + + if(dat.hasOwnProperty('droplab_hidden') && dat.droplab_hidden){ + template.firstChild.style.display = 'none' + }else{ + template.firstChild.style.display = 'block'; + } + return template.firstChild.outerHTML; + }); + toAppend = this.list.querySelector('ul[data-dynamic]'); + if(toAppend) { + toAppend.innerHTML = newChildren.join(''); + } else { + this.list.innerHTML = newChildren.join(''); + } + }, + + show: function() { + if (this.hidden) { + // debugger + this.list.style.display = 'block'; + this.currentIndex = 0; + this.hidden = false; + } + }, + + hide: function() { + if (!this.hidden) { + // debugger + this.list.style.display = 'none'; + this.currentIndex = 0; + this.hidden = true; + } + }, + + destroy: function() { + this.hide(); + this.list.removeEventListener('click', this.clickWrapper); + } +}); + +module.exports = DropDown; + +},{"./custom_event_polyfill":2,"./utils":10}],4:[function(require,module,exports){ +require('./window')(function(w){ + module.exports = function(deps) { + deps = deps || {}; + var window = deps.window || w; + var document = deps.document || window.document; + var CustomEvent = deps.CustomEvent || require('./custom_event_polyfill'); + var HookButton = deps.HookButton || require('./hook_button'); + var HookInput = deps.HookInput || require('./hook_input'); + var utils = deps.utils || require('./utils'); + var DATA_TRIGGER = require('./constants').DATA_TRIGGER; + + var DropLab = function(hook){ + if (!(this instanceof DropLab)) return new DropLab(hook); + this.ready = false; + this.hooks = []; + this.queuedData = []; + this.config = {}; + this.loadWrapper; + if(typeof hook !== 'undefined'){ + this.addHook(hook); + } + }; + + + Object.assign(DropLab.prototype, { + load: function() { + this.loadWrapper(); + }, + + loadWrapper: function(){ + var dropdownTriggers = [].slice.apply(document.querySelectorAll('['+DATA_TRIGGER+']')); + this.addHooks(dropdownTriggers).init(); + }, + + addData: function () { + var args = [].slice.apply(arguments); + this.applyArgs(args, '_addData'); + }, + + setData: function() { + var args = [].slice.apply(arguments); + this.applyArgs(args, '_setData'); + }, + + destroy: function() { + for(var i = 0; i < this.hooks.length; i++) { + this.hooks[i].destroy(); + } + this.hooks = []; + this.removeEvents(); + }, + + applyArgs: function(args, methodName) { + if(this.ready) { + this[methodName].apply(this, args); + } else { + this.queuedData = this.queuedData || []; + this.queuedData.push(args); + } + }, + + _addData: function(trigger, data) { + this._processData(trigger, data, 'addData'); + }, + + _setData: function(trigger, data) { + this._processData(trigger, data, 'setData'); + }, + + _processData: function(trigger, data, methodName) { + for(var i = 0; i < this.hooks.length; i++) { + var hook = this.hooks[i]; + if(hook.trigger.dataset.hasOwnProperty('id')) { + if(hook.trigger.dataset.id === trigger) { + hook.list[methodName](data); + } + } + } + }, + + addEvents: function() { + var self = this; + this.windowClickedWrapper = function(e){ + var thisTag = e.target; + if(thisTag.tagName !== 'UL'){ + // climb up the tree to find the UL + thisTag = utils.closest(thisTag, 'UL'); + } + if(utils.isDropDownParts(thisTag)){ return } + if(utils.isDropDownParts(e.target)){ return } + for(var i = 0; i < self.hooks.length; i++) { + self.hooks[i].list.hide(); + } + }.bind(this); + document.addEventListener('click', this.windowClickedWrapper); + }, + + removeEvents: function(){ + w.removeEventListener('click', this.windowClickedWrapper); + w.removeEventListener('load', this.loadWrapper); + }, + + changeHookList: function(trigger, list, plugins, config) { + trigger = document.querySelector('[data-id="'+trigger+'"]'); + // list = document.querySelector(list); + this.hooks.every(function(hook, i) { + if(hook.trigger === trigger) { + hook.destroy(); + this.hooks.splice(i, 1); + this.addHook(trigger, list, plugins, config); + return false; + } + return true + }.bind(this)); + }, + + addHook: function(hook, list, plugins, config) { + if(!(hook instanceof HTMLElement) && typeof hook === 'string'){ + hook = document.querySelector(hook); + } + if(!list){ + list = document.querySelector(hook.dataset[utils.toDataCamelCase(DATA_TRIGGER)]); + } + + if(hook) { + if(hook.tagName === 'A' || hook.tagName === 'BUTTON') { + this.hooks.push(new HookButton(hook, list, plugins, config)); + } else if(hook.tagName === 'INPUT') { + this.hooks.push(new HookInput(hook, list, plugins, config)); + } + } + return this; + }, + + addHooks: function(hooks, plugins, config) { + for(var i = 0; i < hooks.length; i++) { + var hook = hooks[i]; + this.addHook(hook, null, plugins, config); + } + return this; + }, + + setConfig: function(obj){ + this.config = obj; + }, + + init: function () { + this.addEvents(); + var readyEvent = new CustomEvent('ready.dl', { + detail: { + dropdown: this, + }, + }); + window.dispatchEvent(readyEvent); + this.ready = true; + for(var i = 0; i < this.queuedData.length; i++) { + this.addData.apply(this, this.queuedData[i]); + } + this.queuedData = []; + return this; + }, + }); + + return DropLab; + }; +}); + +},{"./constants":1,"./custom_event_polyfill":2,"./hook_button":6,"./hook_input":7,"./utils":10,"./window":11}],5:[function(require,module,exports){ +var DropDown = require('./dropdown'); + +var Hook = function(trigger, list, plugins, config){ + this.trigger = trigger; + this.list = new DropDown(list); + this.type = 'Hook'; + this.event = 'click'; + this.plugins = plugins || []; + this.config = config || {}; + this.id = trigger.dataset.id; +}; + +Object.assign(Hook.prototype, { + + addEvents: function(){}, + + constructor: Hook, +}); + +module.exports = Hook; + +},{"./dropdown":3}],6:[function(require,module,exports){ +var CustomEvent = require('./custom_event_polyfill'); +var Hook = require('./hook'); + +var HookButton = function(trigger, list, plugins, config) { + Hook.call(this, trigger, list, plugins, config); + this.type = 'button'; + this.event = 'click'; + this.addEvents(); + this.addPlugins(); +}; + +HookButton.prototype = Object.create(Hook.prototype); + +Object.assign(HookButton.prototype, { + addPlugins: function() { + for(var i = 0; i < this.plugins.length; i++) { + this.plugins[i].init(this); + } + }, + + clicked: function(e){ + var buttonEvent = new CustomEvent('click.dl', { + detail: { + hook: this, + }, + bubbles: true, + cancelable: true + }); + this.list.show(); + e.target.dispatchEvent(buttonEvent); + }, + + addEvents: function(){ + this.clickedWrapper = this.clicked.bind(this); + this.trigger.addEventListener('click', this.clickedWrapper); + }, + + removeEvents: function(){ + this.trigger.removeEventListener('click', this.clickedWrapper); + }, + + restoreInitialState: function() { + this.list.list.innerHTML = this.list.initialState; + }, + + removePlugins: function() { + for(var i = 0; i < this.plugins.length; i++) { + this.plugins[i].destroy(); + } + }, + + destroy: function() { + this.restoreInitialState(); + this.removeEvents(); + this.removePlugins(); + }, + + + constructor: HookButton, +}); + + +module.exports = HookButton; + +},{"./custom_event_polyfill":2,"./hook":5}],7:[function(require,module,exports){ +var CustomEvent = require('./custom_event_polyfill'); +var Hook = require('./hook'); + +var HookInput = function(trigger, list, plugins, config) { + Hook.call(this, trigger, list, plugins, config); + this.type = 'input'; + this.event = 'input'; + this.addPlugins(); + this.addEvents(); +}; + +Object.assign(HookInput.prototype, { + addPlugins: function() { + var self = this; + for(var i = 0; i < this.plugins.length; i++) { + this.plugins[i].init(self); + } + }, + + addEvents: function(){ + var self = this; + + this.mousedown = function mousedown(e) { + if(self.hasRemovedEvents) return; + + var mouseEvent = new CustomEvent('mousedown.dl', { + detail: { + hook: self, + text: e.target.value, + }, + bubbles: true, + cancelable: true + }); + e.target.dispatchEvent(mouseEvent); + } + + this.input = function input(e) { + if(self.hasRemovedEvents) return; + + self.list.show(); + + var inputEvent = new CustomEvent('input.dl', { + detail: { + hook: self, + text: e.target.value, + }, + bubbles: true, + cancelable: true + }); + e.target.dispatchEvent(inputEvent); + } + + this.keyup = function keyup(e) { + if(self.hasRemovedEvents) return; + + keyEvent(e, 'keyup.dl'); + } + + this.keydown = function keydown(e) { + if(self.hasRemovedEvents) return; + + keyEvent(e, 'keydown.dl'); + } + + function keyEvent(e, keyEventName){ + self.list.show(); + + var keyEvent = new CustomEvent(keyEventName, { + detail: { + hook: self, + text: e.target.value, + which: e.which, + key: e.key, + }, + bubbles: true, + cancelable: true + }); + e.target.dispatchEvent(keyEvent); + } + + this.events = this.events || {}; + this.events.mousedown = this.mousedown; + this.events.input = this.input; + this.events.keyup = this.keyup; + this.events.keydown = this.keydown; + this.trigger.addEventListener('mousedown', this.mousedown); + this.trigger.addEventListener('input', this.input); + this.trigger.addEventListener('keyup', this.keyup); + this.trigger.addEventListener('keydown', this.keydown); + }, + + removeEvents: function() { + this.hasRemovedEvents = true; + this.trigger.removeEventListener('mousedown', this.mousedown); + this.trigger.removeEventListener('input', this.input); + this.trigger.removeEventListener('keyup', this.keyup); + this.trigger.removeEventListener('keydown', this.keydown); + }, + + restoreInitialState: function() { + this.list.list.innerHTML = this.list.initialState; + }, + + removePlugins: function() { + for(var i = 0; i < this.plugins.length; i++) { + this.plugins[i].destroy(); + } + }, + + destroy: function() { + this.restoreInitialState(); + this.removeEvents(); + this.removePlugins(); + this.list.destroy(); + } +}); + +module.exports = HookInput; + +},{"./custom_event_polyfill":2,"./hook":5}],8:[function(require,module,exports){ +var DropLab = require('./droplab')(); +var DATA_TRIGGER = require('./constants').DATA_TRIGGER; +var keyboard = require('./keyboard')(); +var setup = function() { + window.DropLab = DropLab; +}; + + +module.exports = setup(); + +},{"./constants":1,"./droplab":4,"./keyboard":9}],9:[function(require,module,exports){ +require('./window')(function(w){ + module.exports = function(){ + var currentKey; + var currentFocus; + var isUpArrow = false; + var isDownArrow = false; + var removeHighlight = function removeHighlight(list) { + var listItems = Array.prototype.slice.call(list.list.querySelectorAll('li:not(.divider)'), 0); + var listItemsTmp = []; + for(var i = 0; i < listItems.length; i++) { + var listItem = listItems[i]; + listItem.classList.remove('dropdown-active'); + + if (listItem.style.display !== 'none') { + listItemsTmp.push(listItem); + } + } + return listItemsTmp; + }; + + var setMenuForArrows = function setMenuForArrows(list) { + var listItems = removeHighlight(list); + if(list.currentIndex>0){ + if(!listItems[list.currentIndex-1]){ + list.currentIndex = list.currentIndex-1; + } + + if (listItems[list.currentIndex-1]) { + var el = listItems[list.currentIndex-1]; + var filterDropdownEl = el.closest('.filter-dropdown'); + el.classList.add('dropdown-active'); + + if (filterDropdownEl) { + var filterDropdownBottom = filterDropdownEl.offsetHeight; + var elOffsetTop = el.offsetTop - 30; + + if (elOffsetTop > filterDropdownBottom) { + filterDropdownEl.scrollTop = elOffsetTop - filterDropdownBottom; + } + } + } + } + }; + + var mousedown = function mousedown(e) { + var list = e.detail.hook.list; + removeHighlight(list); + list.show(); + list.currentIndex = 0; + isUpArrow = false; + isDownArrow = false; + }; + var selectItem = function selectItem(list) { + var listItems = removeHighlight(list); + var currentItem = listItems[list.currentIndex-1]; + var listEvent = new CustomEvent('click.dl', { + detail: { + list: list, + selected: currentItem, + data: currentItem.dataset, + }, + }); + list.list.dispatchEvent(listEvent); + list.hide(); + } + + var keydown = function keydown(e){ + var typedOn = e.target; + var list = e.detail.hook.list; + var currentIndex = list.currentIndex; + isUpArrow = false; + isDownArrow = false; + + if(e.detail.which){ + currentKey = e.detail.which; + if(currentKey === 13){ + selectItem(e.detail.hook.list); + return; + } + if(currentKey === 38) { + isUpArrow = true; + } + if(currentKey === 40) { + isDownArrow = true; + } + } else if(e.detail.key) { + currentKey = e.detail.key; + if(currentKey === 'Enter'){ + selectItem(e.detail.hook.list); + return; + } + if(currentKey === 'ArrowUp') { + isUpArrow = true; + } + if(currentKey === 'ArrowDown') { + isDownArrow = true; + } + } + if(isUpArrow){ currentIndex--; } + if(isDownArrow){ currentIndex++; } + if(currentIndex < 0){ currentIndex = 0; } + list.currentIndex = currentIndex; + setMenuForArrows(e.detail.hook.list); + }; + + w.addEventListener('mousedown.dl', mousedown); + w.addEventListener('keydown.dl', keydown); + }; +}); +},{"./window":11}],10:[function(require,module,exports){ +var DATA_TRIGGER = require('./constants').DATA_TRIGGER; +var DATA_DROPDOWN = require('./constants').DATA_DROPDOWN; + +var toDataCamelCase = function(attr){ + return this.camelize(attr.split('-').slice(1).join(' ')); +}; + +// the tiniest damn templating I can do +var t = function(s,d){ + for(var p in d) + s=s.replace(new RegExp('{{'+p+'}}','g'), d[p]); + return s; +}; + +var camelize = function(str) { + return str.replace(/(?:^\w|[A-Z]|\b\w)/g, function(letter, index) { + return index == 0 ? letter.toLowerCase() : letter.toUpperCase(); + }).replace(/\s+/g, ''); +}; + +var closest = function(thisTag, stopTag) { + while(thisTag && thisTag.tagName !== stopTag && thisTag.tagName !== 'HTML'){ + thisTag = thisTag.parentNode; + } + return thisTag; +}; + +var isDropDownParts = function(target) { + if(!target || target.tagName === 'HTML') { return false; } + return ( + target.hasAttribute(DATA_TRIGGER) || + target.hasAttribute(DATA_DROPDOWN) + ); +}; + +module.exports = { + toDataCamelCase: toDataCamelCase, + t: t, + camelize: camelize, + closest: closest, + isDropDownParts: isDropDownParts, +}; + +},{"./constants":1}],11:[function(require,module,exports){ +module.exports = function(callback) { + return (function() { + callback(this); + }).call(null); +}; + +},{}]},{},[8])(8) +}); diff --git a/app/assets/javascripts/droplab/droplab_ajax.js b/app/assets/javascripts/droplab/droplab_ajax.js new file mode 100644 index 00000000000..5cdf11c6a2c --- /dev/null +++ b/app/assets/javascripts/droplab/droplab_ajax.js @@ -0,0 +1,96 @@ +/* eslint-disable */ +(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g=(g.droplab||(g.droplab = {}));g=(g.ajax||(g.ajax = {}));g=(g.datasource||(g.datasource = {}));g.js = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){ +/* global droplab */ + +require('../window')(function(w){ + function droplabAjaxException(message) { + this.message = message; + } + + w.droplabAjax = { + _loadUrlData: function _loadUrlData(url) { + var self = this; + return new Promise(function(resolve, reject) { + var xhr = new XMLHttpRequest; + xhr.open('GET', url, true); + xhr.onreadystatechange = function () { + if(xhr.readyState === XMLHttpRequest.DONE) { + if (xhr.status === 200) { + var data = JSON.parse(xhr.responseText); + self.cache[url] = data; + return resolve(data); + } else { + return reject([xhr.responseText, xhr.status]); + } + } + }; + xhr.send(); + }); + }, + + _loadData: function _loadData(data, config, self) { + if (config.loadingTemplate) { + var dataLoadingTemplate = self.hook.list.list.querySelector('[data-loading-template]'); + + if (dataLoadingTemplate) { + dataLoadingTemplate.outerHTML = self.listTemplate; + } + } + + self.hook.list[config.method].call(self.hook.list, data); + }, + + init: function init(hook) { + var self = this; + self.cache = self.cache || {}; + var config = hook.config.droplabAjax; + this.hook = hook; + + if (!config || !config.endpoint || !config.method) { + return; + } + + if (config.method !== 'setData' && config.method !== 'addData') { + return; + } + + if (config.loadingTemplate) { + var dynamicList = hook.list.list.querySelector('[data-dynamic]'); + + var loadingTemplate = document.createElement('div'); + loadingTemplate.innerHTML = config.loadingTemplate; + loadingTemplate.setAttribute('data-loading-template', ''); + + this.listTemplate = dynamicList.outerHTML; + dynamicList.outerHTML = loadingTemplate.outerHTML; + } + + if (self.cache[config.endpoint]) { + self._loadData(self.cache[config.endpoint], config, self); + } else { + this._loadUrlData(config.endpoint) + .then(function(d) { + self._loadData(d, config, self); + }).catch(function(e) { + throw new droplabAjaxException(e.message || e); + }); + } + }, + + destroy: function() { + var dynamicList = this.hook.list.list.querySelector('[data-dynamic]'); + if (this.listTemplate && dynamicList) { + dynamicList.outerHTML = this.listTemplate; + } + } + }; +}); +},{"../window":2}],2:[function(require,module,exports){ +module.exports = function(callback) { + return (function() { + callback(this); + }).call(null); +}; + +},{}]},{},[1])(1) +}); diff --git a/app/assets/javascripts/droplab/droplab_ajax_filter.js b/app/assets/javascripts/droplab/droplab_ajax_filter.js new file mode 100644 index 00000000000..b63d73066cb --- /dev/null +++ b/app/assets/javascripts/droplab/droplab_ajax_filter.js @@ -0,0 +1,161 @@ +/* eslint-disable */ +(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g=(g.droplab||(g.droplab = {}));g=(g.ajax||(g.ajax = {}));g=(g.datasource||(g.datasource = {}));g.js = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){ +/* global droplab */ + +require('../window')(function(w){ + w.droplabAjaxFilter = { + init: function(hook) { + this.destroyed = false; + this.hook = hook; + this.notLoading(); + + this.debounceTriggerWrapper = this.debounceTrigger.bind(this); + this.hook.trigger.addEventListener('keydown.dl', this.debounceTriggerWrapper); + this.hook.trigger.addEventListener('focus', this.debounceTriggerWrapper); + this.trigger(true); + }, + + notLoading: function notLoading() { + this.loading = false; + }, + + debounceTrigger: function debounceTrigger(e) { + var NON_CHARACTER_KEYS = [16, 17, 18, 20, 37, 38, 39, 40, 91, 93]; + var invalidKeyPressed = NON_CHARACTER_KEYS.indexOf(e.detail.which || e.detail.keyCode) > -1; + var focusEvent = e.type === 'focus'; + + if (invalidKeyPressed || this.loading) { + return; + } + + if (this.timeout) { + clearTimeout(this.timeout); + } + + this.timeout = setTimeout(this.trigger.bind(this, focusEvent), 200); + }, + + trigger: function trigger(getEntireList) { + var config = this.hook.config.droplabAjaxFilter; + var searchValue = this.trigger.value; + + if (!config || !config.endpoint || !config.searchKey) { + return; + } + + if (config.searchValueFunction) { + searchValue = config.searchValueFunction(); + } + + if (config.loadingTemplate && this.hook.list.data === undefined || + this.hook.list.data.length === 0) { + var dynamicList = this.hook.list.list.querySelector('[data-dynamic]'); + + var loadingTemplate = document.createElement('div'); + loadingTemplate.innerHTML = config.loadingTemplate; + loadingTemplate.setAttribute('data-loading-template', true); + + this.listTemplate = dynamicList.outerHTML; + dynamicList.outerHTML = loadingTemplate.outerHTML; + } + + if (getEntireList) { + searchValue = ''; + } + + if (config.searchKey === searchValue) { + return this.list.show(); + } + + this.loading = true; + + var params = config.params || {}; + params[config.searchKey] = searchValue; + var self = this; + self.cache = self.cache || {}; + var url = config.endpoint + this.buildParams(params); + var urlCachedData = self.cache[url]; + + if (urlCachedData) { + self._loadData(urlCachedData, config, self); + } else { + this._loadUrlData(url) + .then(function(data) { + self._loadData(data, config, self); + }); + } + }, + + _loadUrlData: function _loadUrlData(url) { + var self = this; + return new Promise(function(resolve, reject) { + var xhr = new XMLHttpRequest; + xhr.open('GET', url, true); + xhr.onreadystatechange = function () { + if(xhr.readyState === XMLHttpRequest.DONE) { + if (xhr.status === 200) { + var data = JSON.parse(xhr.responseText); + self.cache[url] = data; + return resolve(data); + } else { + return reject([xhr.responseText, xhr.status]); + } + } + }; + xhr.send(); + }); + }, + + _loadData: function _loadData(data, config, self) { + if (config.loadingTemplate && self.hook.list.data === undefined || + self.hook.list.data.length === 0) { + const dataLoadingTemplate = self.hook.list.list.querySelector('[data-loading-template]'); + + if (dataLoadingTemplate) { + dataLoadingTemplate.outerHTML = self.listTemplate; + } + } + + if (!self.destroyed) { + var hookListChildren = self.hook.list.list.children; + var onlyDynamicList = hookListChildren.length === 1 && hookListChildren[0].hasAttribute('data-dynamic'); + + if (onlyDynamicList && data.length === 0) { + self.hook.list.hide(); + } + + self.hook.list.setData.call(self.hook.list, data); + } + self.notLoading(); + self.hook.list.currentIndex = 0; + }, + + buildParams: function(params) { + if (!params) return ''; + var paramsArray = Object.keys(params).map(function(param) { + return param + '=' + (params[param] || ''); + }); + return '?' + paramsArray.join('&'); + }, + + destroy: function destroy() { + if (this.timeout) { + clearTimeout(this.timeout); + } + + this.destroyed = true; + + this.hook.trigger.removeEventListener('keydown.dl', this.debounceTriggerWrapper); + this.hook.trigger.removeEventListener('focus', this.debounceTriggerWrapper); + } + }; +}); +},{"../window":2}],2:[function(require,module,exports){ +module.exports = function(callback) { + return (function() { + callback(this); + }).call(null); +}; + +},{}]},{},[1])(1) +}); diff --git a/app/assets/javascripts/droplab/droplab_filter.js b/app/assets/javascripts/droplab/droplab_filter.js new file mode 100644 index 00000000000..9b40a3f20a4 --- /dev/null +++ b/app/assets/javascripts/droplab/droplab_filter.js @@ -0,0 +1,74 @@ +/* eslint-disable */ +(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g=(g.droplab||(g.droplab = {}));g=(g.filter||(g.filter = {}));g.js = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){ +/* global droplab */ + +require('../window')(function(w){ + w.droplabFilter = { + + keydownWrapper: function(e){ + var hiddenCount = 0; + var dataHiddenCount = 0; + var list = e.detail.hook.list; + var data = list.data; + var value = e.detail.hook.trigger.value.toLowerCase(); + var config = e.detail.hook.config.droplabFilter; + var matches = []; + var filterFunction; + // will only work on dynamically set data + if(!data){ + return; + } + + if (config && config.filterFunction && typeof config.filterFunction === 'function') { + filterFunction = config.filterFunction; + } else { + filterFunction = function(o){ + // cheap string search + o.droplab_hidden = o[config.template].toLowerCase().indexOf(value) === -1; + return o; + }; + } + + dataHiddenCount = data.filter(function(o) { + return !o.droplab_hidden; + }).length; + + matches = data.map(function(o) { + return filterFunction(o, value); + }); + + hiddenCount = matches.filter(function(o) { + return !o.droplab_hidden; + }).length; + + if (dataHiddenCount !== hiddenCount) { + list.render(matches); + list.currentIndex = 0; + } + }, + + init: function init(hookInput) { + var config = hookInput.config.droplabFilter; + + if (!config || (!config.template && !config.filterFunction)) { + return; + } + + this.hookInput = hookInput; + this.hookInput.trigger.addEventListener('keyup.dl', this.keydownWrapper); + }, + + destroy: function destroy(){ + this.hookInput.trigger.removeEventListener('keyup.dl', this.keydownWrapper); + } + }; +}); +},{"../window":2}],2:[function(require,module,exports){ +module.exports = function(callback) { + return (function() { + callback(this); + }).call(null); +}; + +},{}]},{},[1])(1) +}); diff --git a/app/assets/javascripts/dropzone_input.js b/app/assets/javascripts/dropzone_input.js index 56cb39be642..a510eebae1a 100644 --- a/app/assets/javascripts/dropzone_input.js +++ b/app/assets/javascripts/dropzone_input.js @@ -1,7 +1,7 @@ -/* 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, no-plusplus, prefer-arrow-callback, padded-blocks */ +/* 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 */ -/*= require preview_markdown */ +require('./preview_markdown'); (function() { this.DropzoneInput = (function() { @@ -120,7 +120,7 @@ if (item.type.indexOf("image") !== -1) { return item; } - i++; + i += 1; } return false; }; @@ -215,7 +215,5 @@ } return DropzoneInput; - })(); - }).call(this); diff --git a/app/assets/javascripts/due_date_select.js.es6 b/app/assets/javascripts/due_date_select.js.es6 index 2b7d57d86c6..d81d4cf8425 100644 --- a/app/assets/javascripts/due_date_select.js.es6 +++ b/app/assets/javascripts/due_date_select.js.es6 @@ -1,4 +1,4 @@ -/* eslint-disable wrap-iife, func-names, space-before-function-paren, comma-dangle, prefer-template, consistent-return, class-methods-use-this, arrow-body-style, prefer-const, padded-blocks, no-unused-vars, no-underscore-dangle, no-new, max-len, semi, no-sequences, no-unused-expressions, no-param-reassign */ +/* eslint-disable wrap-iife, func-names, space-before-function-paren, comma-dangle, prefer-template, consistent-return, class-methods-use-this, arrow-body-style, no-unused-vars, no-underscore-dangle, no-new, max-len, no-sequences, no-unused-expressions, no-param-reassign */ (function(global) { class DueDateSelect { @@ -16,7 +16,7 @@ this.$sidebarValue = $('.js-due-date-sidebar-value', $block); this.fieldName = $dropdown.data('field-name'), this.abilityName = $dropdown.data('ability-name'), - this.issueUpdateURL = $dropdown.data('issue-update') + this.issueUpdateURL = $dropdown.data('issue-update'); this.rawSelectedDate = null; this.displayedDate = null; @@ -80,9 +80,12 @@ } parseSelectedDate() { - this.rawSelectedDate = $("input[name='" + this.fieldName + "']").val(); + this.rawSelectedDate = $(`input[name='${this.fieldName}']`).val(); + if (this.rawSelectedDate.length) { - let dateObj = new Date(this.rawSelectedDate); + // Construct Date object manually to avoid buggy dateString support within Date constructor + const dateArray = this.rawSelectedDate.split('-').map(v => parseInt(v, 10)); + const dateObj = new Date(dateArray[0], dateArray[1] - 1, dateArray[2]); this.displayedDate = $.datepicker.formatDate('M d, yy', dateObj); } else { this.displayedDate = 'No due date'; @@ -132,7 +135,6 @@ return selectedDateValue.length ? $('.js-remove-due-date-holder').removeClass('hidden') : $('.js-remove-due-date-holder').addClass('hidden'); - } }).done((data) => { if (isDropdown) { @@ -176,5 +178,4 @@ } global.DueDateSelectors = DueDateSelectors; - })(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/environments/components/environment.js.es6 b/app/assets/javascripts/environments/components/environment.js.es6 index facd653fd72..91553bda4dc 100644 --- a/app/assets/javascripts/environments/components/environment.js.es6 +++ b/app/assets/javascripts/environments/components/environment.js.es6 @@ -1,51 +1,17 @@ -/* eslint-disable no-param-reassign */ +/* eslint-disable no-param-reassign, no-new */ /* global Vue */ /* global EnvironmentsService */ +/* global Flash */ -//= require vue -//= require vue-resource -//= require_tree ../services/ -//= require ./environment_item +window.Vue = require('vue'); +window.Vue.use(require('vue-resource')); +require('../services/environments_service'); +require('./environment_item'); (() => { window.gl = window.gl || {}; - /** - * Given the visibility prop provided by the url query parameter and which - * changes according to the active tab we need to filter which environments - * should be visible. - * - * The environments array is a recursive tree structure and we need to filter - * both root level environments and children environments. - * - * In order to acomplish that, both `filterState` and `filterEnvironmentsByState` - * functions work together. - * The first one works as the filter that verifies if the given environment matches - * the given state. - * The second guarantees both root level and children elements are filtered as well. - */ - - const filterState = state => environment => environment.state === state && environment; - /** - * Given the filter function and the array of environments will return only - * the environments that match the state provided to the filter function. - * - * @param {Function} fn - * @param {Array} array - * @return {Array} - */ - const filterEnvironmentsByState = (fn, arr) => arr.map((item) => { - if (item.children) { - const filteredChildren = filterEnvironmentsByState(fn, item.children).filter(Boolean); - if (filteredChildren.length) { - item.children = filteredChildren; - return item; - } - } - return fn(item); - }).filter(Boolean); - - window.gl.environmentsList.EnvironmentsComponent = Vue.component('environment-component', { + gl.environmentsList.EnvironmentsComponent = Vue.component('environment-component', { props: { store: { type: Object, @@ -55,7 +21,7 @@ }, components: { - 'environment-item': window.gl.environmentsList.EnvironmentItem, + 'environment-item': gl.environmentsList.EnvironmentItem, }, data() { @@ -81,10 +47,6 @@ }, computed: { - filteredEnvironments() { - return filterEnvironmentsByState(filterState(this.visibility), this.state.environments); - }, - scope() { return this.$options.getQueryParameter('scope'); }, @@ -111,7 +73,7 @@ const scope = this.$options.getQueryParameter('scope'); if (scope) { - this.visibility = scope; + this.store.storeVisibility(scope); } this.isLoading = true; @@ -121,6 +83,10 @@ .then((json) => { this.store.storeEnvironments(json); this.isLoading = false; + }) + .catch(() => { + this.isLoading = false; + new Flash('An error occurred while fetching the environments.', 'alert'); }); }, @@ -188,7 +154,7 @@ <div class="blank-state blank-state-no-icon" v-if="!isLoading && state.environments.length === 0"> - <h2 class="blank-state-title"> + <h2 class="blank-state-title js-blank-state-title"> You don't have any environments right now. </h2> <p class="blank-state-text"> @@ -202,26 +168,26 @@ <a v-if="canCreateEnvironmentParsed" :href="newEnvironmentPath" - class="btn btn-create"> + class="btn btn-create js-new-environment-button"> New Environment </a> </div> <div class="table-holder" - v-if="!isLoading && state.environments.length > 0"> + v-if="!isLoading && state.filteredEnvironments.length > 0"> <table class="table ci-table environments"> <thead> <tr> <th class="environments-name">Environment</th> <th class="environments-deploy">Last deployment</th> - <th class="environments-build">Build</th> + <th class="environments-build">Job</th> <th class="environments-commit">Commit</th> - <th class="environments-date"></th> + <th class="environments-date">Updated</th> <th class="hidden-xs environments-actions"></th> </tr> </thead> <tbody> - <template v-for="model in filteredEnvironments" + <template v-for="model in state.filteredEnvironments" v-bind:model="model"> <tr diff --git a/app/assets/javascripts/environments/components/environment_actions.js.es6 b/app/assets/javascripts/environments/components/environment_actions.js.es6 index 7c743705d51..ed1c78945db 100644 --- a/app/assets/javascripts/environments/components/environment_actions.js.es6 +++ b/app/assets/javascripts/environments/components/environment_actions.js.es6 @@ -1,11 +1,12 @@ -/*= require vue */ /* global Vue */ +window.Vue = require('vue'); + (() => { window.gl = window.gl || {}; window.gl.environmentsList = window.gl.environmentsList || {}; - window.gl.environmentsList.ActionsComponent = Vue.component('actions-component', { + gl.environmentsList.ActionsComponent = Vue.component('actions-component', { props: { actions: { type: Array, diff --git a/app/assets/javascripts/environments/components/environment_external_url.js.es6 b/app/assets/javascripts/environments/components/environment_external_url.js.es6 index aed65b33c04..28cc0022d17 100644 --- a/app/assets/javascripts/environments/components/environment_external_url.js.es6 +++ b/app/assets/javascripts/environments/components/environment_external_url.js.es6 @@ -1,11 +1,12 @@ -/*= require vue */ /* global Vue */ +window.Vue = require('vue'); + (() => { window.gl = window.gl || {}; window.gl.environmentsList = window.gl.environmentsList || {}; - window.gl.environmentsList.ExternalUrlComponent = Vue.component('external-url-component', { + gl.environmentsList.ExternalUrlComponent = Vue.component('external-url-component', { props: { externalUrl: { type: String, diff --git a/app/assets/javascripts/environments/components/environment_item.js.es6 b/app/assets/javascripts/environments/components/environment_item.js.es6 index b26a40aa268..33a99231315 100644 --- a/app/assets/javascripts/environments/components/environment_item.js.es6 +++ b/app/assets/javascripts/environments/components/environment_item.js.es6 @@ -1,14 +1,15 @@ /* global Vue */ /* global timeago */ -/*= require timeago */ -/*= require lib/utils/text_utility */ -/*= require vue_common_component/commit */ -/*= require ./environment_actions */ -/*= require ./environment_external_url */ -/*= require ./environment_stop */ -/*= require ./environment_rollback */ -/*= require ./environment_terminal_button */ +window.Vue = require('vue'); +window.timeago = require('vendor/timeago'); +require('../../lib/utils/text_utility'); +require('../../vue_shared/components/commit'); +require('./environment_actions'); +require('./environment_external_url'); +require('./environment_stop'); +require('./environment_rollback'); +require('./environment_terminal_button'); (() => { /** @@ -29,12 +30,12 @@ gl.environmentsList.EnvironmentItem = Vue.component('environment-item', { components: { - 'commit-component': window.gl.CommitComponent, - 'actions-component': window.gl.environmentsList.ActionsComponent, - 'external-url-component': window.gl.environmentsList.ExternalUrlComponent, - 'stop-component': window.gl.environmentsList.StopComponent, - 'rollback-component': window.gl.environmentsList.RollbackComponent, - 'terminal-button-component': window.gl.environmentsList.TerminalButtonComponent, + 'commit-component': gl.CommitComponent, + 'actions-component': gl.environmentsList.ActionsComponent, + 'external-url-component': gl.environmentsList.ExternalUrlComponent, + 'stop-component': gl.environmentsList.StopComponent, + 'rollback-component': gl.environmentsList.RollbackComponent, + 'terminal-button-component': gl.environmentsList.TerminalButtonComponent, }, props: { @@ -146,12 +147,12 @@ }, /** - * Returns the value of the `stoppable?` key provided in the response. + * Returns the value of the `stop_action?` key provided in the response. * * @returns {Boolean} */ - isStoppable() { - return this.model['stoppable?']; + hasStopAction() { + return this.model['stop_action?']; }, /** @@ -183,7 +184,7 @@ * @returns {String} */ createdDate() { - return window.gl.environmentsList.timeagoInstance.format( + return gl.environmentsList.timeagoInstance.format( this.model.last_deployment.deployable.created_at, ); }, @@ -507,7 +508,7 @@ </external-url-component> </div> - <div v-if="isStoppable && canCreateDeployment" + <div v-if="hasStopAction && canCreateDeployment" class="inline js-stop-component-container"> <stop-component :stop-url="model.stop_path"> diff --git a/app/assets/javascripts/environments/components/environment_rollback.js.es6 b/app/assets/javascripts/environments/components/environment_rollback.js.es6 index 6d4e8fad604..5938340a128 100644 --- a/app/assets/javascripts/environments/components/environment_rollback.js.es6 +++ b/app/assets/javascripts/environments/components/environment_rollback.js.es6 @@ -1,11 +1,12 @@ -/*= require vue */ /* global Vue */ +window.Vue = require('vue'); + (() => { window.gl = window.gl || {}; window.gl.environmentsList = window.gl.environmentsList || {}; - window.gl.environmentsList.RollbackComponent = Vue.component('rollback-component', { + gl.environmentsList.RollbackComponent = Vue.component('rollback-component', { props: { retryUrl: { type: String, diff --git a/app/assets/javascripts/environments/components/environment_stop.js.es6 b/app/assets/javascripts/environments/components/environment_stop.js.es6 index 7292f924e5c..be9526989a0 100644 --- a/app/assets/javascripts/environments/components/environment_stop.js.es6 +++ b/app/assets/javascripts/environments/components/environment_stop.js.es6 @@ -1,11 +1,12 @@ -/*= require vue */ /* global Vue */ +window.Vue = require('vue'); + (() => { window.gl = window.gl || {}; window.gl.environmentsList = window.gl.environmentsList || {}; - window.gl.environmentsList.StopComponent = Vue.component('stop-component', { + gl.environmentsList.StopComponent = Vue.component('stop-component', { props: { stopUrl: { type: String, diff --git a/app/assets/javascripts/environments/components/environment_terminal_button.js.es6 b/app/assets/javascripts/environments/components/environment_terminal_button.js.es6 index 25e6ac7f3c9..a3ad063f7cb 100644 --- a/app/assets/javascripts/environments/components/environment_terminal_button.js.es6 +++ b/app/assets/javascripts/environments/components/environment_terminal_button.js.es6 @@ -1,11 +1,12 @@ -/*= require vue */ /* global Vue */ +window.Vue = require('vue'); + (() => { window.gl = window.gl || {}; window.gl.environmentsList = window.gl.environmentsList || {}; - window.gl.environmentsList.TerminalButtonComponent = Vue.component('terminal-button-component', { + gl.environmentsList.TerminalButtonComponent = Vue.component('terminal-button-component', { props: { terminalPath: { type: String, diff --git a/app/assets/javascripts/environments/environments_bundle.js.es6 b/app/assets/javascripts/environments/environments_bundle.js.es6 index 20eee7976ec..05c59d92fd4 100644 --- a/app/assets/javascripts/environments/environments_bundle.js.es6 +++ b/app/assets/javascripts/environments/environments_bundle.js.es6 @@ -1,21 +1,22 @@ -//= require vue -//= require_tree ./stores/ -//= require ./components/environment -//= require ./vue_resource_interceptor - +window.Vue = require('vue'); +require('./stores/environments_store'); +require('./components/environment'); +require('../vue_shared/vue_resource_interceptor'); $(() => { window.gl = window.gl || {}; - if (window.gl.EnvironmentsListApp) { - window.gl.EnvironmentsListApp.$destroy(true); + if (gl.EnvironmentsListApp) { + gl.EnvironmentsListApp.$destroy(true); } - const Store = window.gl.environmentsList.EnvironmentsStore; + const Store = gl.environmentsList.EnvironmentsStore; - window.gl.EnvironmentsListApp = new window.gl.environmentsList.EnvironmentsComponent({ + gl.EnvironmentsListApp = new gl.environmentsList.EnvironmentsComponent({ el: document.querySelector('#environments-list-view'), + propsData: { store: Store.create(), }, + }); }); diff --git a/app/assets/javascripts/environments/services/environments_service.js.es6 b/app/assets/javascripts/environments/services/environments_service.js.es6 index 15ec7b76c3d..fab8d977f58 100644 --- a/app/assets/javascripts/environments/services/environments_service.js.es6 +++ b/app/assets/javascripts/environments/services/environments_service.js.es6 @@ -1,5 +1,6 @@ /* globals Vue */ /* eslint-disable no-unused-vars, no-param-reassign */ + class EnvironmentsService { constructor(root) { @@ -20,3 +21,5 @@ class EnvironmentsService { return this.environments.get(); } } + +window.EnvironmentsService = EnvironmentsService; diff --git a/app/assets/javascripts/environments/stores/environments_store.js.es6 b/app/assets/javascripts/environments/stores/environments_store.js.es6 index 0204a903ab5..9b4090100da 100644 --- a/app/assets/javascripts/environments/stores/environments_store.js.es6 +++ b/app/assets/javascripts/environments/stores/environments_store.js.es6 @@ -10,6 +10,8 @@ this.state.environments = []; this.state.stoppedCounter = 0; this.state.availableCounter = 0; + this.state.visibility = 'available'; + this.state.filteredEnvironments = []; return this; }, @@ -59,7 +61,7 @@ if (occurs.length) { acc[acc.indexOf(occurs[0])].children.push(environment); - acc[acc.indexOf(occurs[0])].children.sort(this.sortByName); + acc[acc.indexOf(occurs[0])].children.slice().sort(this.sortByName); } else { acc.push({ name: environment.environment_type, @@ -73,13 +75,70 @@ } return acc; - }, []).sort(this.sortByName); + }, []).slice().sort(this.sortByName); this.state.environments = environmentsTree; + this.filterEnvironmentsByVisibility(this.state.environments); + return environmentsTree; }, + storeVisibility(visibility) { + this.state.visibility = visibility; + }, + /** + * Given the visibility prop provided by the url query parameter and which + * changes according to the active tab we need to filter which environments + * should be visible. + * + * The environments array is a recursive tree structure and we need to filter + * both root level environments and children environments. + * + * In order to acomplish that, both `filterState` and `filterEnvironmentsByVisibility` + * functions work together. + * The first one works as the filter that verifies if the given environment matches + * the given state. + * The second guarantees both root level and children elements are filtered as well. + * + * Given array of environments will return only + * the environments that match the state stored. + * + * @param {Array} array + * @return {Array} + */ + filterEnvironmentsByVisibility(arr) { + const filteredEnvironments = arr.map((item) => { + if (item.children) { + const filteredChildren = this.filterEnvironmentsByVisibility( + item.children, + ).filter(Boolean); + + if (filteredChildren.length) { + item.children = filteredChildren; + return item; + } + } + + return this.filterState(this.state.visibility, item); + }).filter(Boolean); + + this.state.filteredEnvironments = filteredEnvironments; + return filteredEnvironments; + }, + + /** + * Given the state and the environment, + * returns only if the environment state matches the one provided. + * + * @param {String} state + * @param {Object} environment + * @return {Object} + */ + filterState(state, environment) { + return environment.state === state && environment; + }, + /** * Toggles folder open property given the environment type. * diff --git a/app/assets/javascripts/environments/vue_resource_interceptor.js.es6 b/app/assets/javascripts/environments/vue_resource_interceptor.js.es6 deleted file mode 100644 index 406bdbc1c7d..00000000000 --- a/app/assets/javascripts/environments/vue_resource_interceptor.js.es6 +++ /dev/null @@ -1,12 +0,0 @@ -/* global Vue */ -Vue.http.interceptors.push((request, next) => { - Vue.activeResources = Vue.activeResources ? Vue.activeResources + 1 : 1; - - next((response) => { - if (typeof response.data === 'string') { - response.data = JSON.parse(response.data); // eslint-disable-line - } - - Vue.activeResources--; // eslint-disable-line - }); -}); diff --git a/app/assets/javascripts/extensions/array.js.es6 b/app/assets/javascripts/extensions/array.js.es6 index 717566a4715..f8256a8d26d 100644 --- a/app/assets/javascripts/extensions/array.js.es6 +++ b/app/assets/javascripts/extensions/array.js.es6 @@ -1,11 +1,14 @@ -/* eslint-disable no-extend-native, func-names, space-before-function-paren, semi, space-infix-ops, max-len */ +/* eslint-disable no-extend-native, func-names, space-before-function-paren, space-infix-ops, strict, max-len */ + +'use strict'; + Array.prototype.first = function() { return this[0]; -} +}; Array.prototype.last = function() { return this[this.length-1]; -} +}; Array.prototype.find = Array.prototype.find || function(predicate, ...args) { if (!this) throw new TypeError('Array.prototype.find called on null or undefined'); diff --git a/app/assets/javascripts/extensions/custom_event.js.es6 b/app/assets/javascripts/extensions/custom_event.js.es6 new file mode 100644 index 00000000000..abedae4c1c7 --- /dev/null +++ b/app/assets/javascripts/extensions/custom_event.js.es6 @@ -0,0 +1,12 @@ +/* global CustomEvent */ +/* eslint-disable no-global-assign */ + +// Custom event support for IE +CustomEvent = function CustomEvent(event, parameters) { + const params = parameters || { bubbles: false, cancelable: false, detail: undefined }; + const evt = document.createEvent('CustomEvent'); + evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail); + return evt; +}; + +CustomEvent.prototype = window.Event.prototype; diff --git a/app/assets/javascripts/extensions/element.js.es6 b/app/assets/javascripts/extensions/element.js.es6 index 3f12ad9ff9f..90ab79305a7 100644 --- a/app/assets/javascripts/extensions/element.js.es6 +++ b/app/assets/javascripts/extensions/element.js.es6 @@ -1,5 +1,5 @@ /* global Element */ -/* eslint-disable consistent-return, max-len, no-empty, no-plusplus, func-names */ +/* eslint-disable consistent-return, max-len, no-empty, func-names */ Element.prototype.closest = Element.prototype.closest || function closest(selector, selectedElement = this) { if (!selectedElement) return; @@ -14,7 +14,7 @@ Element.prototype.matches = Element.prototype.matches || Element.prototype.webkitMatchesSelector || function (s) { const matches = (this.document || this.ownerDocument).querySelectorAll(s); - let i = matches.length; - while (--i >= 0 && matches.item(i) !== this) {} + let i = matches.length - 1; + while (i >= 0 && matches.item(i) !== this) { i -= 1; } return i > -1; }; diff --git a/app/assets/javascripts/extensions/jquery.js b/app/assets/javascripts/extensions/jquery.js index cdedc865d1b..d3b58b2707a 100644 --- a/app/assets/javascripts/extensions/jquery.js +++ b/app/assets/javascripts/extensions/jquery.js @@ -1,4 +1,4 @@ -/* eslint-disable func-names, space-before-function-paren, object-shorthand, comma-dangle, padded-blocks, max-len */ +/* eslint-disable func-names, space-before-function-paren, object-shorthand, comma-dangle, max-len */ // Disable an element and add the 'disabled' Bootstrap class (function() { $.fn.extend({ @@ -13,5 +13,4 @@ return $(this).removeAttr('disabled').removeClass('disabled'); } }); - }).call(this); diff --git a/app/assets/javascripts/files_comment_button.js b/app/assets/javascripts/files_comment_button.js index 785f2869970..895a872568d 100644 --- a/app/assets/javascripts/files_comment_button.js +++ b/app/assets/javascripts/files_comment_button.js @@ -1,8 +1,8 @@ -/* eslint-disable func-names, space-before-function-paren, no-var, space-before-blocks, prefer-rest-params, wrap-iife, max-len, one-var, one-var-declaration-per-line, quotes, prefer-template, newline-per-chained-call, comma-dangle, new-cap, no-else-return, padded-blocks, consistent-return */ +/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, max-len, one-var, one-var-declaration-per-line, quotes, prefer-template, newline-per-chained-call, comma-dangle, new-cap, no-else-return, consistent-return */ /* global FilesCommentButton */ (function() { - var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; + var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; }; this.FilesCommentButton = (function() { var COMMENT_BUTTON_CLASS, COMMENT_BUTTON_TEMPLATE, DEBOUNCE_TIMEOUT_DURATION, EMPTY_CELL_CLASS, LINE_COLUMN_CLASSES, LINE_CONTENT_CLASS, LINE_HOLDER_CLASS, LINE_NUMBER_CLASS, OLD_LINE_CLASS, TEXT_FILE_SELECTOR, UNFOLDABLE_LINE_CLASS; @@ -132,7 +132,6 @@ }; return FilesCommentButton; - })(); $.fn.filesCommentButton = function() { @@ -145,5 +144,4 @@ } }); }; - }).call(this); diff --git a/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 b/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 new file mode 100644 index 00000000000..572c221929a --- /dev/null +++ b/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 @@ -0,0 +1,69 @@ +require('./filtered_search_dropdown'); + +/* global droplabFilter */ + +(() => { + class DropdownHint extends gl.FilteredSearchDropdown { + constructor(droplab, dropdown, input, filter) { + super(droplab, dropdown, input, filter); + this.config = { + droplabFilter: { + template: 'hint', + filterFunction: gl.DropdownUtils.filterHint.bind(null, input), + }, + }; + } + + itemClicked(e) { + const { selected } = e.detail; + + if (selected.tagName === 'LI') { + if (selected.hasAttribute('data-value')) { + this.dismissDropdown(); + } else if (selected.getAttribute('data-action') === 'submit') { + this.dismissDropdown(); + this.dispatchFormSubmitEvent(); + } else { + const token = selected.querySelector('.js-filter-hint').innerText.trim(); + const tag = selected.querySelector('.js-filter-tag').innerText.trim(); + + if (tag.length) { + gl.FilteredSearchDropdownManager.addWordToInput(token.replace(':', '')); + } + this.dismissDropdown(); + this.dispatchInputEvent(); + } + } + } + + renderContent() { + const dropdownData = [{ + icon: 'fa-pencil', + hint: 'author:', + tag: '<@author>', + }, { + icon: 'fa-user', + hint: 'assignee:', + tag: '<@assignee>', + }, { + icon: 'fa-clock-o', + hint: 'milestone:', + tag: '<%milestone>', + }, { + icon: 'fa-tag', + hint: 'label:', + tag: '<~label>', + }]; + + this.droplab.changeHookList(this.hookId, this.dropdown, [droplabFilter], this.config); + this.droplab.setData(this.hookId, dropdownData); + } + + init() { + this.droplab.addHook(this.input, this.dropdown, [droplabFilter], this.config).init(); + } + } + + window.gl = window.gl || {}; + gl.DropdownHint = DropdownHint; +})(); diff --git a/app/assets/javascripts/filtered_search/dropdown_non_user.js.es6 b/app/assets/javascripts/filtered_search/dropdown_non_user.js.es6 new file mode 100644 index 00000000000..b3dc3e502c5 --- /dev/null +++ b/app/assets/javascripts/filtered_search/dropdown_non_user.js.es6 @@ -0,0 +1,44 @@ +require('./filtered_search_dropdown'); + +/* global droplabAjax */ +/* global droplabFilter */ + +(() => { + class DropdownNonUser extends gl.FilteredSearchDropdown { + constructor(droplab, dropdown, input, filter, endpoint, symbol) { + super(droplab, dropdown, input, filter); + this.symbol = symbol; + this.config = { + droplabAjax: { + endpoint, + method: 'setData', + loadingTemplate: this.loadingTemplate, + }, + droplabFilter: { + filterFunction: gl.DropdownUtils.filterWithSymbol.bind(null, this.symbol, input), + }, + }; + } + + itemClicked(e) { + super.itemClicked(e, (selected) => { + const title = selected.querySelector('.js-data-value').innerText.trim(); + return `${this.symbol}${gl.DropdownUtils.getEscapedText(title)}`; + }); + } + + renderContent(forceShowList = false) { + this.droplab + .changeHookList(this.hookId, this.dropdown, [droplabAjax, droplabFilter], this.config); + super.renderContent(forceShowList); + } + + init() { + this.droplab + .addHook(this.input, this.dropdown, [droplabAjax, droplabFilter], this.config).init(); + } + } + + window.gl = window.gl || {}; + gl.DropdownNonUser = DropdownNonUser; +})(); diff --git a/app/assets/javascripts/filtered_search/dropdown_user.js.es6 b/app/assets/javascripts/filtered_search/dropdown_user.js.es6 new file mode 100644 index 00000000000..7e9c6f74aa5 --- /dev/null +++ b/app/assets/javascripts/filtered_search/dropdown_user.js.es6 @@ -0,0 +1,60 @@ +require('./filtered_search_dropdown'); + +/* global droplabAjaxFilter */ + +(() => { + class DropdownUser extends gl.FilteredSearchDropdown { + constructor(droplab, dropdown, input, filter) { + super(droplab, dropdown, input, filter); + this.config = { + droplabAjaxFilter: { + endpoint: `${gon.relative_url_root || ''}/autocomplete/users.json`, + searchKey: 'search', + params: { + per_page: 20, + active: true, + project_id: this.getProjectId(), + current_user: true, + }, + searchValueFunction: this.getSearchInput.bind(this), + loadingTemplate: this.loadingTemplate, + }, + }; + } + + itemClicked(e) { + super.itemClicked(e, + selected => selected.querySelector('.dropdown-light-content').innerText.trim()); + } + + renderContent(forceShowList = false) { + this.droplab.changeHookList(this.hookId, this.dropdown, [droplabAjaxFilter], this.config); + super.renderContent(forceShowList); + } + + getProjectId() { + return this.input.getAttribute('data-project-id'); + } + + getSearchInput() { + const query = gl.DropdownUtils.getSearchInput(this.input); + const { lastToken } = gl.FilteredSearchTokenizer.processTokens(query); + let value = lastToken.value || ''; + + // Removes the first character if it is a quotation so that we can search + // with multiple words + if (value[0] === '"' || value[0] === '\'') { + value = value.slice(1); + } + + return value; + } + + init() { + this.droplab.addHook(this.input, this.dropdown, [droplabAjaxFilter], this.config).init(); + } + } + + window.gl = window.gl || {}; + gl.DropdownUser = DropdownUser; +})(); diff --git a/app/assets/javascripts/filtered_search/dropdown_utils.js.es6 b/app/assets/javascripts/filtered_search/dropdown_utils.js.es6 new file mode 100644 index 00000000000..de3fa116717 --- /dev/null +++ b/app/assets/javascripts/filtered_search/dropdown_utils.js.es6 @@ -0,0 +1,126 @@ +(() => { + class DropdownUtils { + static getEscapedText(text) { + let escapedText = text; + const hasSpace = text.indexOf(' ') !== -1; + const hasDoubleQuote = text.indexOf('"') !== -1; + + // Encapsulate value with quotes if it has spaces + // Known side effect: values's with both single and double quotes + // won't escape properly + if (hasSpace) { + if (hasDoubleQuote) { + escapedText = `'${text}'`; + } else { + // Encapsulate singleQuotes or if it hasSpace + escapedText = `"${text}"`; + } + } + + return escapedText; + } + + static filterWithSymbol(filterSymbol, input, item) { + const updatedItem = item; + const query = gl.DropdownUtils.getSearchInput(input); + const { lastToken, searchToken } = gl.FilteredSearchTokenizer.processTokens(query); + + if (lastToken !== searchToken) { + const title = updatedItem.title.toLowerCase(); + let value = lastToken.value.toLowerCase(); + + // Removes the first character if it is a quotation so that we can search + // with multiple words + if ((value[0] === '"' || value[0] === '\'') && title.indexOf(' ') !== -1) { + value = value.slice(1); + } + + // Eg. filterSymbol = ~ for labels + const matchWithoutSymbol = lastToken.symbol === filterSymbol && title.indexOf(value) !== -1; + const match = title.indexOf(`${lastToken.symbol}${value}`) !== -1; + + updatedItem.droplab_hidden = !match && !matchWithoutSymbol; + } else { + updatedItem.droplab_hidden = false; + } + + return updatedItem; + } + + static filterHint(input, item) { + const updatedItem = item; + const query = gl.DropdownUtils.getSearchInput(input); + let { lastToken } = gl.FilteredSearchTokenizer.processTokens(query); + lastToken = lastToken.key || lastToken || ''; + + if (!lastToken || query.split('').last() === ' ') { + updatedItem.droplab_hidden = false; + } else if (lastToken) { + const split = lastToken.split(':'); + const tokenName = split[0].split(' ').last(); + + const match = updatedItem.hint.indexOf(tokenName.toLowerCase()) === -1; + updatedItem.droplab_hidden = tokenName ? match : false; + } + + return updatedItem; + } + + static setDataValueIfSelected(filter, selected) { + const dataValue = selected.getAttribute('data-value'); + + if (dataValue) { + gl.FilteredSearchDropdownManager.addWordToInput(filter, dataValue); + } + + // Return boolean based on whether it was set + return dataValue !== null; + } + + static getSearchInput(filteredSearchInput) { + const inputValue = filteredSearchInput.value; + const { right } = gl.DropdownUtils.getInputSelectionPosition(filteredSearchInput); + + return inputValue.slice(0, right); + } + + static getInputSelectionPosition(input) { + const selectionStart = input.selectionStart; + let inputValue = input.value; + // Replace all spaces inside quote marks with underscores + // (will continue to match entire string until an end quote is found if any) + // This helps with matching the beginning & end of a token:key + inputValue = inputValue.replace(/(('[^']*'{0,1})|("[^"]*"{0,1})|:\s+)/g, str => str.replace(/\s/g, '_')); + + // Get the right position for the word selected + // Regex matches first space + let right = inputValue.slice(selectionStart).search(/\s/); + + if (right >= 0) { + right += selectionStart; + } else if (right < 0) { + right = inputValue.length; + } + + // Get the left position for the word selected + // Regex matches last non-whitespace character + let left = inputValue.slice(0, right).search(/\S+$/); + + if (selectionStart === 0) { + left = 0; + } else if (selectionStart === inputValue.length && left < 0) { + left = inputValue.length; + } else if (left < 0) { + left = selectionStart; + } + + return { + left, + right, + }; + } + } + + window.gl = window.gl || {}; + gl.DropdownUtils = DropdownUtils; +})(); diff --git a/app/assets/javascripts/filtered_search/filtered_search_bundle.js b/app/assets/javascripts/filtered_search/filtered_search_bundle.js new file mode 100644 index 00000000000..392f1835966 --- /dev/null +++ b/app/assets/javascripts/filtered_search/filtered_search_bundle.js @@ -0,0 +1,3 @@ +function requireAll(context) { return context.keys().map(context); } + +requireAll(require.context('./', true, /^\.\/(?!filtered_search_bundle).*\.(js|es6)$/)); diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 new file mode 100644 index 00000000000..859d6515531 --- /dev/null +++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 @@ -0,0 +1,112 @@ +(() => { + const DATA_DROPDOWN_TRIGGER = 'data-dropdown-trigger'; + + class FilteredSearchDropdown { + constructor(droplab, dropdown, input, filter) { + this.droplab = droplab; + this.hookId = input.getAttribute('data-id'); + this.input = input; + this.filter = filter; + this.dropdown = dropdown; + this.loadingTemplate = `<div class="filter-dropdown-loading"> + <i class="fa fa-spinner fa-spin"></i> + </div>`; + this.bindEvents(); + } + + bindEvents() { + this.itemClickedWrapper = this.itemClicked.bind(this); + this.dropdown.addEventListener('click.dl', this.itemClickedWrapper); + } + + unbindEvents() { + this.dropdown.removeEventListener('click.dl', this.itemClickedWrapper); + } + + getCurrentHook() { + return this.droplab.hooks.filter(h => h.id === this.hookId)[0] || null; + } + + itemClicked(e, getValueFunction) { + const { selected } = e.detail; + + if (selected.tagName === 'LI' && selected.innerHTML) { + const dataValueSet = gl.DropdownUtils.setDataValueIfSelected(this.filter, selected); + + if (!dataValueSet) { + const value = getValueFunction(selected); + gl.FilteredSearchDropdownManager.addWordToInput(this.filter, value); + } + + this.dismissDropdown(); + this.dispatchInputEvent(); + } + } + + setAsDropdown() { + this.input.setAttribute(DATA_DROPDOWN_TRIGGER, `#${this.dropdown.id}`); + } + + setOffset(offset = 0) { + this.dropdown.style.left = `${offset}px`; + } + + renderContent(forceShowList = false) { + if (forceShowList && this.getCurrentHook().list.hidden) { + this.getCurrentHook().list.show(); + } + } + + render(forceRenderContent = false, forceShowList = false) { + this.setAsDropdown(); + + const currentHook = this.getCurrentHook(); + const firstTimeInitialized = currentHook === null; + + if (firstTimeInitialized || forceRenderContent) { + this.renderContent(forceShowList); + } else if (currentHook.list.list.id !== this.dropdown.id) { + this.renderContent(forceShowList); + } + } + + dismissDropdown() { + // Focusing on the input will dismiss dropdown + // (default droplab functionality) + this.input.focus(); + } + + dispatchInputEvent() { + // Propogate input change to FilteredSearchDropdownManager + // so that it can determine which dropdowns to open + this.input.dispatchEvent(new CustomEvent('input', { + bubbles: true, + cancelable: true, + })); + } + + dispatchFormSubmitEvent() { + // dispatchEvent() is necessary as form.submit() does not + // trigger event handlers + this.input.form.dispatchEvent(new Event('submit')); + } + + hideDropdown() { + this.getCurrentHook().list.hide(); + } + + resetFilters() { + const hook = this.getCurrentHook(); + const data = hook.list.data; + const results = data.map((o) => { + const updated = o; + updated.droplab_hidden = false; + return updated; + }); + hook.list.render(results); + } + } + + window.gl = window.gl || {}; + gl.FilteredSearchDropdown = FilteredSearchDropdown; +})(); diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js.es6 new file mode 100644 index 00000000000..547989a6ff5 --- /dev/null +++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js.es6 @@ -0,0 +1,207 @@ +/* global DropLab */ + +(() => { + class FilteredSearchDropdownManager { + constructor() { + this.tokenizer = gl.FilteredSearchTokenizer; + this.filteredSearchInput = document.querySelector('.filtered-search'); + + this.setupMapping(); + + this.cleanupWrapper = this.cleanup.bind(this); + document.addEventListener('beforeunload', this.cleanupWrapper); + } + + cleanup() { + if (this.droplab) { + this.droplab.destroy(); + this.droplab = null; + } + + this.setupMapping(); + + document.removeEventListener('beforeunload', this.cleanupWrapper); + } + + setupMapping() { + this.mapping = { + author: { + reference: null, + gl: 'DropdownUser', + element: document.querySelector('#js-dropdown-author'), + }, + assignee: { + reference: null, + gl: 'DropdownUser', + element: document.querySelector('#js-dropdown-assignee'), + }, + milestone: { + reference: null, + gl: 'DropdownNonUser', + extraArguments: ['milestones.json', '%'], + element: document.querySelector('#js-dropdown-milestone'), + }, + label: { + reference: null, + gl: 'DropdownNonUser', + extraArguments: ['labels.json', '~'], + element: document.querySelector('#js-dropdown-label'), + }, + hint: { + reference: null, + gl: 'DropdownHint', + element: document.querySelector('#js-dropdown-hint'), + }, + }; + } + + static addWordToInput(tokenName, tokenValue = '') { + const input = document.querySelector('.filtered-search'); + const inputValue = input.value; + const word = `${tokenName}:${tokenValue}`; + + // Get the string to replace + let newCaretPosition = input.selectionStart; + const { left, right } = gl.DropdownUtils.getInputSelectionPosition(input); + + input.value = `${inputValue.substr(0, left)}${word}${inputValue.substr(right)}`; + + // If we have added a tokenValue at the end of the input, + // add a space and set selection to the end + if (right >= inputValue.length && tokenValue !== '') { + input.value += ' '; + newCaretPosition = input.value.length; + } + + gl.FilteredSearchDropdownManager.updateInputCaretPosition(newCaretPosition, input); + } + + static updateInputCaretPosition(selectionStart, input) { + // Reset the position + // Sometimes can end up at end of input + input.setSelectionRange(selectionStart, selectionStart); + + const { right } = gl.DropdownUtils.getInputSelectionPosition(input); + + input.setSelectionRange(right, right); + } + + updateCurrentDropdownOffset() { + this.updateDropdownOffset(this.currentDropdown); + } + + updateDropdownOffset(key) { + if (!this.font) { + this.font = window.getComputedStyle(this.filteredSearchInput).font; + } + + const input = this.filteredSearchInput; + const inputText = input.value.slice(0, input.selectionStart); + const filterIconPadding = 27; + let offset = gl.text.getTextWidth(inputText, this.font) + filterIconPadding; + + const currentDropdownWidth = this.mapping[key].element.clientWidth === 0 ? 200 : + this.mapping[key].element.clientWidth; + const offsetMaxWidth = this.filteredSearchInput.clientWidth - currentDropdownWidth; + + if (offsetMaxWidth < offset) { + offset = offsetMaxWidth; + } + + this.mapping[key].reference.setOffset(offset); + } + + load(key, firstLoad = false) { + const mappingKey = this.mapping[key]; + const glClass = mappingKey.gl; + const element = mappingKey.element; + let forceShowList = false; + + if (!mappingKey.reference) { + const dl = this.droplab; + const defaultArguments = [null, dl, element, this.filteredSearchInput, key]; + const glArguments = defaultArguments.concat(mappingKey.extraArguments || []); + + // Passing glArguments to `new gl[glClass](<arguments>)` + mappingKey.reference = new (Function.prototype.bind.apply(gl[glClass], glArguments))(); + } + + if (firstLoad) { + mappingKey.reference.init(); + } + + if (this.currentDropdown === 'hint') { + // Force the dropdown to show if it was clicked from the hint dropdown + forceShowList = true; + } + + this.updateDropdownOffset(key); + mappingKey.reference.render(firstLoad, forceShowList); + + this.currentDropdown = key; + } + + loadDropdown(dropdownName = '') { + let firstLoad = false; + + if (!this.droplab) { + firstLoad = true; + this.droplab = new DropLab(); + } + + const match = gl.FilteredSearchTokenKeys.searchByKey(dropdownName.toLowerCase()); + const shouldOpenFilterDropdown = match && this.currentDropdown !== match.key + && this.mapping[match.key]; + const shouldOpenHintDropdown = !match && this.currentDropdown !== 'hint'; + + if (shouldOpenFilterDropdown || shouldOpenHintDropdown) { + const key = match && match.key ? match.key : 'hint'; + this.load(key, firstLoad); + } + } + + setDropdown() { + const { lastToken, searchToken } = this.tokenizer + .processTokens(gl.DropdownUtils.getSearchInput(this.filteredSearchInput)); + + if (this.currentDropdown) { + this.updateCurrentDropdownOffset(); + } + + if (lastToken === searchToken && lastToken !== null) { + // Token is not fully initialized yet because it has no value + // Eg. token = 'label:' + + const split = lastToken.split(':'); + const dropdownName = split[0].split(' ').last(); + this.loadDropdown(split.length > 1 ? dropdownName : ''); + } else if (lastToken) { + // Token has been initialized into an object because it has a value + this.loadDropdown(lastToken.key); + } else { + this.loadDropdown('hint'); + } + } + + resetDropdowns() { + // Force current dropdown to hide + this.mapping[this.currentDropdown].reference.hideDropdown(); + + // Re-Load dropdown + this.setDropdown(); + + // Reset filters for current dropdown + this.mapping[this.currentDropdown].reference.resetFilters(); + + // Reposition dropdown so that it is aligned with cursor + this.updateDropdownOffset(this.currentDropdown); + } + + destroyDroplab() { + this.droplab.destroy(); + } + } + + window.gl = window.gl || {}; + gl.FilteredSearchDropdownManager = FilteredSearchDropdownManager; +})(); diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 new file mode 100644 index 00000000000..4e02ab7c8c1 --- /dev/null +++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 @@ -0,0 +1,230 @@ +(() => { + class FilteredSearchManager { + constructor() { + this.filteredSearchInput = document.querySelector('.filtered-search'); + this.clearSearchButton = document.querySelector('.clear-search'); + + if (this.filteredSearchInput) { + this.tokenizer = gl.FilteredSearchTokenizer; + this.dropdownManager = new gl.FilteredSearchDropdownManager(); + + this.bindEvents(); + this.loadSearchParamsFromURL(); + this.dropdownManager.setDropdown(); + + this.cleanupWrapper = this.cleanup.bind(this); + document.addEventListener('beforeunload', this.cleanupWrapper); + } + } + + cleanup() { + this.unbindEvents(); + document.removeEventListener('beforeunload', this.cleanupWrapper); + } + + bindEvents() { + this.handleFormSubmit = this.handleFormSubmit.bind(this); + this.setDropdownWrapper = this.dropdownManager.setDropdown.bind(this.dropdownManager); + this.toggleClearSearchButtonWrapper = this.toggleClearSearchButton.bind(this); + this.checkForEnterWrapper = this.checkForEnter.bind(this); + this.clearSearchWrapper = this.clearSearch.bind(this); + this.checkForBackspaceWrapper = this.checkForBackspace.bind(this); + this.tokenChange = this.tokenChange.bind(this); + + this.filteredSearchInput.form.addEventListener('submit', this.handleFormSubmit); + this.filteredSearchInput.addEventListener('input', this.setDropdownWrapper); + this.filteredSearchInput.addEventListener('input', this.toggleClearSearchButtonWrapper); + this.filteredSearchInput.addEventListener('keydown', this.checkForEnterWrapper); + this.filteredSearchInput.addEventListener('keyup', this.checkForBackspaceWrapper); + this.filteredSearchInput.addEventListener('click', this.tokenChange); + this.filteredSearchInput.addEventListener('keyup', this.tokenChange); + this.clearSearchButton.addEventListener('click', this.clearSearchWrapper); + } + + unbindEvents() { + this.filteredSearchInput.form.removeEventListener('submit', this.handleFormSubmit); + this.filteredSearchInput.removeEventListener('input', this.setDropdownWrapper); + this.filteredSearchInput.removeEventListener('input', this.toggleClearSearchButtonWrapper); + this.filteredSearchInput.removeEventListener('keydown', this.checkForEnterWrapper); + this.filteredSearchInput.removeEventListener('keyup', this.checkForBackspaceWrapper); + this.filteredSearchInput.removeEventListener('click', this.tokenChange); + this.filteredSearchInput.removeEventListener('keyup', this.tokenChange); + this.clearSearchButton.removeEventListener('click', this.clearSearchWrapper); + } + + checkForBackspace(e) { + // 8 = Backspace Key + // 46 = Delete Key + if (e.keyCode === 8 || e.keyCode === 46) { + // Reposition dropdown so that it is aligned with cursor + this.dropdownManager.updateCurrentDropdownOffset(); + } + } + + checkForEnter(e) { + if (e.keyCode === 38 || e.keyCode === 40) { + const selectionStart = this.filteredSearchInput.selectionStart; + + e.preventDefault(); + this.filteredSearchInput.setSelectionRange(selectionStart, selectionStart); + } + + if (e.keyCode === 13) { + const dropdown = this.dropdownManager.mapping[this.dropdownManager.currentDropdown]; + const dropdownEl = dropdown.element; + const activeElements = dropdownEl.querySelectorAll('.dropdown-active'); + + e.preventDefault(); + + if (!activeElements.length) { + // Prevent droplab from opening dropdown + this.dropdownManager.destroyDroplab(); + + this.search(); + } + } + } + + toggleClearSearchButton(e) { + if (e.target.value) { + this.clearSearchButton.classList.remove('hidden'); + } else { + this.clearSearchButton.classList.add('hidden'); + } + } + + clearSearch(e) { + e.preventDefault(); + + this.filteredSearchInput.value = ''; + this.clearSearchButton.classList.add('hidden'); + + this.dropdownManager.resetDropdowns(); + } + + handleFormSubmit(e) { + e.preventDefault(); + this.search(); + } + + loadSearchParamsFromURL() { + const params = gl.utils.getUrlParamsArray(); + const usernameParams = this.getUsernameParams(); + const inputValues = []; + + params.forEach((p) => { + const split = p.split('='); + const keyParam = decodeURIComponent(split[0]); + const value = split[1]; + + // Check if it matches edge conditions listed in gl.FilteredSearchTokenKeys + const condition = gl.FilteredSearchTokenKeys.searchByConditionUrl(p); + + if (condition) { + inputValues.push(`${condition.tokenKey}:${condition.value}`); + } else { + // Sanitize value since URL converts spaces into + + // Replace before decode so that we know what was originally + versus the encoded + + const sanitizedValue = value ? decodeURIComponent(value.replace(/\+/g, ' ')) : value; + const match = gl.FilteredSearchTokenKeys.searchByKeyParam(keyParam); + + if (match) { + const indexOf = keyParam.indexOf('_'); + const sanitizedKey = indexOf !== -1 ? keyParam.slice(0, keyParam.indexOf('_')) : keyParam; + const symbol = match.symbol; + let quotationsToUse = ''; + + if (sanitizedValue.indexOf(' ') !== -1) { + // Prefer ", but use ' if required + quotationsToUse = sanitizedValue.indexOf('"') === -1 ? '"' : '\''; + } + + inputValues.push(`${sanitizedKey}:${symbol}${quotationsToUse}${sanitizedValue}${quotationsToUse}`); + } else if (!match && keyParam === 'assignee_id') { + const id = parseInt(value, 10); + if (usernameParams[id]) { + inputValues.push(`assignee:@${usernameParams[id]}`); + } + } else if (!match && keyParam === 'author_id') { + const id = parseInt(value, 10); + if (usernameParams[id]) { + inputValues.push(`author:@${usernameParams[id]}`); + } + } else if (!match && keyParam === 'search') { + inputValues.push(sanitizedValue); + } + } + }); + + // Trim the last space value + this.filteredSearchInput.value = inputValues.join(' '); + + if (inputValues.length > 0) { + this.clearSearchButton.classList.remove('hidden'); + } + } + + search() { + const paths = []; + const { tokens, searchToken } = this.tokenizer.processTokens(this.filteredSearchInput.value); + const currentState = gl.utils.getParameterByName('state') || 'opened'; + paths.push(`state=${currentState}`); + + tokens.forEach((token) => { + const condition = gl.FilteredSearchTokenKeys + .searchByConditionKeyValue(token.key, token.value.toLowerCase()); + const { param } = gl.FilteredSearchTokenKeys.searchByKey(token.key); + const keyParam = param ? `${token.key}_${param}` : token.key; + let tokenPath = ''; + + if (condition) { + tokenPath = condition.url; + } else { + let tokenValue = token.value; + + if ((tokenValue[0] === '\'' && tokenValue[tokenValue.length - 1] === '\'') || + (tokenValue[0] === '"' && tokenValue[tokenValue.length - 1] === '"')) { + tokenValue = tokenValue.slice(1, tokenValue.length - 1); + } + + tokenPath = `${keyParam}=${encodeURIComponent(tokenValue)}`; + } + + paths.push(tokenPath); + }); + + if (searchToken) { + const sanitized = searchToken.split(' ').map(t => encodeURIComponent(t)).join('+'); + paths.push(`search=${sanitized}`); + } + + const parameterizedUrl = `?scope=all&utf8=✓&${paths.join('&')}`; + + gl.utils.visitUrl(parameterizedUrl); + } + + getUsernameParams() { + const usernamesById = {}; + try { + const attribute = this.filteredSearchInput.getAttribute('data-username-params'); + JSON.parse(attribute).forEach((user) => { + usernamesById[user.id] = user.username; + }); + } catch (e) { + // do nothing + } + return usernamesById; + } + + tokenChange() { + const dropdown = this.dropdownManager.mapping[this.dropdownManager.currentDropdown]; + const currentDropdownRef = dropdown.reference; + + this.setDropdownWrapper(); + currentDropdownRef.dispatchInputEvent(); + } + } + + window.gl = window.gl || {}; + gl.FilteredSearchManager = FilteredSearchManager; +})(); diff --git a/app/assets/javascripts/filtered_search/filtered_search_token_keys.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_token_keys.js.es6 new file mode 100644 index 00000000000..e6b53cd4b55 --- /dev/null +++ b/app/assets/javascripts/filtered_search/filtered_search_token_keys.js.es6 @@ -0,0 +1,96 @@ +(() => { + const tokenKeys = [{ + key: 'author', + type: 'string', + param: 'username', + symbol: '@', + }, { + key: 'assignee', + type: 'string', + param: 'username', + symbol: '@', + }, { + key: 'milestone', + type: 'string', + param: 'title', + symbol: '%', + }, { + key: 'label', + type: 'array', + param: 'name[]', + symbol: '~', + }]; + + const alternativeTokenKeys = [{ + key: 'label', + type: 'string', + param: 'name', + symbol: '~', + }]; + + const tokenKeysWithAlternative = tokenKeys.concat(alternativeTokenKeys); + + const conditions = [{ + url: 'assignee_id=0', + tokenKey: 'assignee', + value: 'none', + }, { + url: 'milestone_title=No+Milestone', + tokenKey: 'milestone', + value: 'none', + }, { + url: 'milestone_title=%23upcoming', + tokenKey: 'milestone', + value: 'upcoming', + }, { + url: 'label_name[]=No+Label', + tokenKey: 'label', + value: 'none', + }]; + + class FilteredSearchTokenKeys { + static get() { + return tokenKeys; + } + + static getAlternatives() { + return alternativeTokenKeys; + } + + static getConditions() { + return conditions; + } + + static searchByKey(key) { + return tokenKeys.find(tokenKey => tokenKey.key === key) || null; + } + + static searchBySymbol(symbol) { + return tokenKeys.find(tokenKey => tokenKey.symbol === symbol) || null; + } + + static searchByKeyParam(keyParam) { + return tokenKeysWithAlternative.find((tokenKey) => { + let tokenKeyParam = tokenKey.key; + + if (tokenKey.param) { + tokenKeyParam += `_${tokenKey.param}`; + } + + return keyParam === tokenKeyParam; + }) || null; + } + + static searchByConditionUrl(url) { + return conditions.find(condition => condition.url === url) || null; + } + + static searchByConditionKeyValue(key, value) { + return conditions + .find(condition => condition.tokenKey === key && condition.value === value) || null; + } + } + + window.gl = window.gl || {}; + gl.FilteredSearchTokenKeys = FilteredSearchTokenKeys; +})(); diff --git a/app/assets/javascripts/filtered_search/filtered_search_tokenizer.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_tokenizer.js.es6 new file mode 100644 index 00000000000..cf53845a48b --- /dev/null +++ b/app/assets/javascripts/filtered_search/filtered_search_tokenizer.js.es6 @@ -0,0 +1,45 @@ +(() => { + class FilteredSearchTokenizer { + static processTokens(input) { + // Regex extracts `(token):(symbol)(value)` + // Values that start with a double quote must end in a double quote (same for single) + const tokenRegex = /(\w+):([~%@]?)(?:('[^']*'{0,1})|("[^"]*"{0,1})|(\S+))/g; + const tokens = []; + let lastToken = null; + const searchToken = input.replace(tokenRegex, (match, key, symbol, v1, v2, v3) => { + let tokenValue = v1 || v2 || v3; + let tokenSymbol = symbol; + + if (tokenValue === '~' || tokenValue === '%' || tokenValue === '@') { + tokenSymbol = tokenValue; + tokenValue = ''; + } + + tokens.push({ + key, + value: tokenValue || '', + symbol: tokenSymbol || '', + }); + return ''; + }).replace(/\s{2,}/g, ' ').trim() || ''; + + if (tokens.length > 0) { + const last = tokens[tokens.length - 1]; + const lastString = `${last.key}:${last.symbol}${last.value}`; + lastToken = input.lastIndexOf(lastString) === + input.length - lastString.length ? last : searchToken; + } else { + lastToken = searchToken; + } + + return { + tokens, + lastToken, + searchToken, + }; + } + } + + window.gl = window.gl || {}; + gl.FilteredSearchTokenizer = FilteredSearchTokenizer; +})(); diff --git a/app/assets/javascripts/flash.js b/app/assets/javascripts/flash.js index 804d7d9c4ab..249fe23d4cb 100644 --- a/app/assets/javascripts/flash.js +++ b/app/assets/javascripts/flash.js @@ -1,4 +1,4 @@ -/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, one-var, one-var-declaration-per-line, no-param-reassign, quotes, quote-props, prefer-template, comma-dangle, padded-blocks, max-len */ +/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, one-var, one-var-declaration-per-line, no-param-reassign, quotes, quote-props, prefer-template, comma-dangle, max-len */ (function() { this.Flash = (function() { var hideFlash; @@ -38,7 +38,5 @@ } return Flash; - })(); - }).call(this); diff --git a/app/assets/javascripts/gfm_auto_complete.js.es6 b/app/assets/javascripts/gfm_auto_complete.js.es6 index 17d03c87bf5..7f1f2a5d278 100644 --- a/app/assets/javascripts/gfm_auto_complete.js.es6 +++ b/app/assets/javascripts/gfm_auto_complete.js.es6 @@ -1,4 +1,4 @@ -/* eslint-disable func-names, space-before-function-paren, no-template-curly-in-string, comma-dangle, object-shorthand, quotes, dot-notation, no-else-return, one-var, no-var, no-underscore-dangle, one-var-declaration-per-line, no-param-reassign, no-useless-escape, prefer-template, consistent-return, wrap-iife, prefer-arrow-callback, camelcase, no-unused-vars, no-useless-return, padded-blocks, vars-on-top, indent, no-extra-semi, no-multi-spaces, semi, max-len */ +/* eslint-disable func-names, space-before-function-paren, no-template-curly-in-string, comma-dangle, object-shorthand, quotes, dot-notation, no-else-return, one-var, no-var, no-underscore-dangle, one-var-declaration-per-line, no-param-reassign, no-useless-escape, prefer-template, consistent-return, wrap-iife, prefer-arrow-callback, camelcase, no-unused-vars, no-useless-return, vars-on-top, max-len */ // Creates the variables for setting up GFM auto-completion (function() { @@ -48,18 +48,18 @@ }, DefaultOptions: { sorter: function(query, items, searchKey) { + this.setting.highlightFirst = this.setting.alwaysHighlightFirst || query.length > 0; if (gl.GfmAutoComplete.isLoading(items)) { + this.setting.highlightFirst = false; return items; } return $.fn.atwho["default"].callbacks.sorter(query, items, searchKey); }, filter: function(query, data, searchKey) { if (gl.GfmAutoComplete.isLoading(data)) { - gl.GfmAutoComplete.togglePreventSelection.call(this, true); gl.GfmAutoComplete.fetchData(this.$inputor, this.at); return data; } else { - gl.GfmAutoComplete.togglePreventSelection.call(this, false); return $.fn.atwho["default"].callbacks.filter(query, data, searchKey); } }, @@ -74,20 +74,21 @@ // The below is taken from At.js source // Tweaked to commands to start without a space only if char before is a non-word character // https://github.com/ichord/At.js - var _a, _y, regexp, match, atSymbols; - atSymbols = Object.keys(this.app.controllers).join('|'); - subtext = subtext.split(' ').pop(); + var _a, _y, regexp, match, atSymbolsWithBar, atSymbolsWithoutBar; + atSymbolsWithBar = Object.keys(this.app.controllers).join('|'); + atSymbolsWithoutBar = Object.keys(this.app.controllers).join(''); + subtext = subtext.split(/\s+/g).pop(); flag = flag.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); _a = decodeURI("%C3%80"); _y = decodeURI("%C3%BF"); - regexp = new RegExp("(?:\\B|\\W|\\s)" + flag + "(?![" + atSymbols + "])([A-Za-z" + _a + "-" + _y + "0-9_\'\.\+\-]*)$", 'gi'); + regexp = new RegExp("^(?:\\B|[^a-zA-Z0-9_" + atSymbolsWithoutBar + "]|\\s)" + flag + "(?![" + atSymbolsWithBar + "])(([A-Za-z" + _a + "-" + _y + "0-9_\'\.\+\-]|[^\\x00-\\x7a])*)$", 'gi'); match = regexp.exec(subtext); if (match) { - return match[2] || match[1]; + return (match[1] || match[1] === "") ? match[1] : match[2]; } else { return null; } @@ -112,7 +113,6 @@ return value.path != null ? this.Emoji.template : this.Loading.template; }.bind(this), insertTpl: ':${name}:', - startWithSpace: false, skipSpecialCharacterTest: true, data: this.defaultLoadingData, callbacks: { @@ -129,7 +129,6 @@ }.bind(this), insertTpl: '${atwho-at}${username}', searchKey: 'search', - startWithSpace: false, alwaysHighlightFirst: true, skipSpecialCharacterTest: true, data: this.defaultLoadingData, @@ -155,7 +154,7 @@ return { username: m.username, - avatarTag: autoCompleteAvatar.length === 1 ? txtAvatar : imgAvatar, + avatarTag: autoCompleteAvatar.length === 1 ? txtAvatar : imgAvatar, title: sanitize(title), search: sanitize(m.username + " " + m.name) }; @@ -172,7 +171,6 @@ }.bind(this), data: this.defaultLoadingData, insertTpl: '${atwho-at}${id}', - startWithSpace: false, callbacks: { sorter: this.DefaultOptions.sorter, filter: this.DefaultOptions.filter, @@ -200,7 +198,6 @@ displayTpl: function(value) { return value.title != null ? this.Milestones.template : this.Loading.template; }.bind(this), - startWithSpace: false, data: this.defaultLoadingData, callbacks: { matcher: this.DefaultOptions.matcher, @@ -225,7 +222,6 @@ at: '!', alias: 'mergerequests', searchKey: 'search', - startWithSpace: false, displayTpl: function(value) { return value.title != null ? this.Issues.template : this.Loading.template; }.bind(this), @@ -259,12 +255,11 @@ return this.isLoading(value) ? this.Loading.template : this.Labels.template; }.bind(this), insertTpl: '${atwho-at}${title}', - startWithSpace: false, callbacks: { matcher: this.DefaultOptions.matcher, - sorter: this.DefaultOptions.sorter, beforeInsert: this.DefaultOptions.beforeInsert, filter: this.DefaultOptions.filter, + sorter: this.DefaultOptions.sorter, beforeSave: function(merges) { if (gl.GfmAutoComplete.isLoading(merges)) return merges; var sanitizeLabelTitle; @@ -340,7 +335,7 @@ }); }, matcher: function(flag, subtext, should_startWithSpace, acceptSpaceBar) { - var regexp = /(?:^|\n)\/([A-Za-z_]*)$/gi + var regexp = /(?:^|\n)\/([A-Za-z_]*)$/gi; var match = regexp.exec(subtext); if (match) { return match[1]; @@ -372,21 +367,14 @@ return $input.trigger('keyup'); }, isLoading(data) { - if (!data) return false; - if (Array.isArray(data)) data = data[0]; - return data === this.defaultLoadingData[0] || data.name === this.defaultLoadingData[0]; - }, - togglePreventSelection(isPrevented = !!this.setting.tabSelectsMatch) { - this.setting.tabSelectsMatch = !isPrevented; - this.setting.spaceSelectsMatch = !isPrevented; - const eventListenerAction = `${isPrevented ? 'add' : 'remove'}EventListener`; - this.$inputor[0][eventListenerAction]('keydown', gl.GfmAutoComplete.preventSpaceTabEnter); - }, - preventSpaceTabEnter(e) { - const key = e.which || e.keyCode; - const preventables = [9, 13, 32]; - if (preventables.indexOf(key) > -1) e.preventDefault(); + var dataToInspect = data; + if (data && data.length > 0) { + dataToInspect = data[0]; + } + + var loadingState = this.defaultLoadingData[0]; + return dataToInspect && + (dataToInspect === loadingState || dataToInspect.name === loadingState); } }; - }).call(this); diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js index bb516b3d2df..d9101b55c7f 100644 --- a/app/assets/javascripts/gl_dropdown.js +++ b/app/assets/javascripts/gl_dropdown.js @@ -1,11 +1,10 @@ -/* eslint-disable func-names, space-before-function-paren, no-var, one-var, one-var-declaration-per-line, space-before-blocks, prefer-rest-params, max-len, vars-on-top, no-plusplus, wrap-iife, no-unused-vars, quotes, no-shadow, no-cond-assign, prefer-arrow-callback, semi, no-return-assign, no-else-return, camelcase, comma-dangle, no-lonely-if, guard-for-in, no-restricted-syntax, consistent-return, padded-blocks, prefer-template, no-param-reassign, no-loop-func, no-extra-semi, keyword-spacing, no-mixed-operators */ +/* 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 */ -/* global Turbolinks */ (function() { var GitLabDropdown, GitLabDropdownFilter, GitLabDropdownRemote, - bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, - indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; + bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; }, + indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i += 1) { if (i in this && this[i] === item) return i; } return -1; }; GitLabDropdownFilter = (function() { var ARROW_KEY_CODES, BLUR_KEYCODES, HAS_VALUE_CLASS; @@ -37,7 +36,7 @@ .on('keydown', function (e) { var keyCode = e.which; if (keyCode === 13 && !options.elIsInput) { - e.preventDefault() + e.preventDefault(); } }) .on('input', function() { @@ -133,7 +132,6 @@ }; return GitLabDropdownFilter; - })(); GitLabDropdownRemote = (function() { @@ -186,7 +184,6 @@ }; return GitLabDropdownRemote; - })(); GitLabDropdown = (function() { @@ -206,7 +203,7 @@ SELECTABLE_CLASSES = ".dropdown-content li:not(" + NON_SELECTABLE_CLASSES + ", .option-hidden)"; - CURSOR_SELECT_SCROLL_PADDING = 5 + CURSOR_SELECT_SCROLL_PADDING = 5; FILTER_INPUT = '.dropdown-input .dropdown-input-field'; @@ -223,7 +220,7 @@ this.dropdown = selector != null ? $(selector) : $(this.el).parent(); // Set Defaults this.filterInput = this.options.filterInput || this.getElement(FILTER_INPUT); - this.highlight = !!this.options.highlight + this.highlight = !!this.options.highlight; this.filterInputBlur = this.options.filterInputBlur != null ? this.options.filterInputBlur : true; @@ -251,7 +248,7 @@ _this.fullData = data; _this.parseData(_this.fullData); _this.focusTextInput(); - if (_this.options.filterable && _this.filter && _this.filter.input && _this.filter.input.val().trim() !== '') { + if (_this.options.filterable && _this.filter && _this.filter.input && _this.filter.input.val() && _this.filter.input.val().trim() !== '') { return _this.filter.input.trigger('input'); } }; @@ -476,7 +473,7 @@ this.removeArrayKeyEvent(); $input = this.dropdown.find(".dropdown-input-field"); if (this.options.filterable) { - $input.blur().val(""); + $input.blur(); } if (this.dropdown.find(".dropdown-toggle-page").length) { $('.dropdown-menu', this.dropdown).removeClass(PAGE_TWO_CLASS); @@ -494,7 +491,7 @@ } else { var ul = document.createElement('ul'); - for (var i = 0; i < html.length; i++) { + for (var i = 0; i < html.length; i += 1) { var el = html[i]; if (el instanceof jQuery) { @@ -514,12 +511,17 @@ // Append the menu into the dropdown GitLabDropdown.prototype.appendMenu = function(html) { + return this.clearMenu().append(html); + }; + + GitLabDropdown.prototype.clearMenu = function() { var selector; selector = '.dropdown-content'; if (this.dropdown.find(".dropdown-toggle-page").length) { selector = ".dropdown-page-one .dropdown-content"; } - return $(selector, this.dropdown).empty().append(html); + + return $(selector, this.dropdown).empty(); }; GitLabDropdown.prototype.renderItem = function(data, group, index) { @@ -550,7 +552,7 @@ value = this.options.id ? this.options.id(data) : data.id; fieldName = this.options.fieldName; - if (value) { value = value.toString().replace(/'/g, '\\\'') }; + if (value) { value = value.toString().replace(/'/g, '\\\''); } field = this.dropdown.parent().find("input[name='" + fieldName + "'][value='" + value + "']"); if (field.length) { @@ -641,7 +643,7 @@ : selectedObject.id; if (isInput) { field = $(this.el); - } else if(value) { + } else if (value) { field = this.dropdown.parent().find("input[name='" + fieldName + "'][value='" + value.toString().replace(/'/g, '\\\'') + "']"); } @@ -653,18 +655,14 @@ isMarking = false; el.removeClass(ACTIVE_CLASS); if (field && field.length) { - if (isInput) { - field.val(''); - } else { - field.remove(); - } + this.clearField(field, isInput); } } else if (el.hasClass(INDETERMINATE_CLASS)) { isMarking = true; el.addClass(ACTIVE_CLASS); el.removeClass(INDETERMINATE_CLASS); if (field && field.length && value == null) { - field.remove(); + this.clearField(field, isInput); } if ((!field || !field.length) && fieldName) { this.addInput(fieldName, value, selectedObject); @@ -678,7 +676,7 @@ } } if (field && field.length && value == null) { - field.remove(); + this.clearField(field, isInput); } // Toggle active class for the tick mark el.addClass(ACTIVE_CLASS); @@ -695,8 +693,8 @@ }; GitLabDropdown.prototype.focusTextInput = function() { - if (this.options.filterable) { this.filterInput.focus() } - } + if (this.options.filterable) { this.filterInput.focus(); } + }; GitLabDropdown.prototype.addInput = function(fieldName, value, selectedObject) { var $input; @@ -724,7 +722,7 @@ if ($el.length) { var href = $el.attr('href'); if (href && href !== '#') { - Turbolinks.visit(href); + gl.utils.visitUrl(href); } else { $el.first().trigger('click'); } @@ -802,7 +800,7 @@ listItemBottom = listItemTop + listItemHeight; if (!index) { // Scroll the dropdown content to the top - $dropdownContent.scrollTop(0) + $dropdownContent.scrollTop(0); } else if (index === ($listItems.length - 1)) { // Scroll the dropdown content to the bottom $dropdownContent.scrollTop($dropdownContent.prop('scrollHeight')); @@ -828,8 +826,11 @@ return $(this.el).find(".dropdown-toggle-text").text(this.options.toggleLabel(selected, el, instance)); }; - return GitLabDropdown; + GitLabDropdown.prototype.clearField = function(field, isInput) { + return isInput ? field.val('') : field.remove(); + }; + return GitLabDropdown; })(); $.fn.glDropdown = function(opts) { @@ -839,5 +840,4 @@ } }); }; - }).call(this); diff --git a/app/assets/javascripts/gl_field_errors.js.es6 b/app/assets/javascripts/gl_field_errors.js.es6 index 63f9cafa8d0..e9add115429 100644 --- a/app/assets/javascripts/gl_field_errors.js.es6 +++ b/app/assets/javascripts/gl_field_errors.js.es6 @@ -1,6 +1,6 @@ -/* eslint-disable comma-dangle, class-methods-use-this, max-len, space-before-function-paren, arrow-parens, no-param-reassign, padded-blocks */ +/* eslint-disable comma-dangle, class-methods-use-this, max-len, space-before-function-paren, arrow-parens, no-param-reassign */ -//= require gl_field_error +require('./gl_field_error'); ((global) => { const customValidationFlag = 'gl-field-error-ignore'; @@ -45,5 +45,4 @@ } global.GlFieldErrors = GlFieldErrors; - })(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/gl_form.js b/app/assets/javascripts/gl_form.js deleted file mode 100644 index 7dc2d13e5d8..00000000000 --- a/app/assets/javascripts/gl_form.js +++ /dev/null @@ -1,64 +0,0 @@ -/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-new, padded-blocks, max-len */ -/* global GitLab */ -/* global DropzoneInput */ -/* global autosize */ - -(function() { - this.GLForm = (function() { - function GLForm(form) { - this.form = form; - this.textarea = this.form.find('textarea.js-gfm-input'); - // Before we start, we should clean up any previous data for this form - this.destroy(); - // Setup the form - this.setupForm(); - this.form.data('gl-form', this); - } - - GLForm.prototype.destroy = function() { - // Clean form listeners - this.clearEventListeners(); - return this.form.data('gl-form', null); - }; - - GLForm.prototype.setupForm = function() { - var isNewForm; - isNewForm = this.form.is(':not(.gfm-form)'); - this.form.removeClass('js-new-note-form'); - if (isNewForm) { - this.form.find('.div-dropzone').remove(); - this.form.addClass('gfm-form'); - // remove notify commit author checkbox for non-commit notes - gl.utils.disableButtonIfEmptyField(this.form.find('.js-note-text'), this.form.find('.js-comment-button')); - gl.GfmAutoComplete.setup(this.form.find('.js-gfm-input')); - new DropzoneInput(this.form); - autosize(this.textarea); - // form and textarea event listeners - this.addEventListeners(); - gl.text.init(this.form); - } - // hide discard button - this.form.find('.js-note-discard').hide(); - return this.form.show(); - }; - - GLForm.prototype.clearEventListeners = function() { - this.textarea.off('focus'); - this.textarea.off('blur'); - return gl.text.removeListeners(this.form); - }; - - GLForm.prototype.addEventListeners = function() { - this.textarea.on('focus', function() { - return $(this).closest('.md-area').addClass('is-focused'); - }); - return this.textarea.on('blur', function() { - return $(this).closest('.md-area').removeClass('is-focused'); - }); - }; - - return GLForm; - - })(); - -}).call(this); diff --git a/app/assets/javascripts/gl_form.js.es6 b/app/assets/javascripts/gl_form.js.es6 new file mode 100644 index 00000000000..0b446ff364a --- /dev/null +++ b/app/assets/javascripts/gl_form.js.es6 @@ -0,0 +1,92 @@ +/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-new, max-len */ +/* global GitLab */ +/* global DropzoneInput */ +/* global autosize */ + +(() => { + const global = window.gl || (window.gl = {}); + + function GLForm(form) { + this.form = form; + this.textarea = this.form.find('textarea.js-gfm-input'); + // Before we start, we should clean up any previous data for this form + this.destroy(); + // Setup the form + this.setupForm(); + this.form.data('gl-form', this); + } + + GLForm.prototype.destroy = function() { + // Clean form listeners + this.clearEventListeners(); + return this.form.data('gl-form', null); + }; + + GLForm.prototype.setupForm = function() { + var isNewForm; + isNewForm = this.form.is(':not(.gfm-form)'); + this.form.removeClass('js-new-note-form'); + if (isNewForm) { + this.form.find('.div-dropzone').remove(); + this.form.addClass('gfm-form'); + // remove notify commit author checkbox for non-commit notes + gl.utils.disableButtonIfEmptyField(this.form.find('.js-note-text'), this.form.find('.js-comment-button')); + gl.GfmAutoComplete.setup(this.form.find('.js-gfm-input')); + new DropzoneInput(this.form); + autosize(this.textarea); + // form and textarea event listeners + this.addEventListeners(); + } + gl.text.init(this.form); + // hide discard button + this.form.find('.js-note-discard').hide(); + this.form.show(); + if (this.isAutosizeable) this.setupAutosize(); + }; + + GLForm.prototype.setupAutosize = function () { + this.textarea.off('autosize:resized') + .on('autosize:resized', this.setHeightData.bind(this)); + + this.textarea.off('mouseup.autosize') + .on('mouseup.autosize', this.destroyAutosize.bind(this)); + + setTimeout(() => { + autosize(this.textarea); + this.textarea.css('resize', 'vertical'); + }, 0); + }; + + GLForm.prototype.setHeightData = function () { + this.textarea.data('height', this.textarea.outerHeight()); + }; + + GLForm.prototype.destroyAutosize = function () { + const outerHeight = this.textarea.outerHeight(); + + if (this.textarea.data('height') === outerHeight) return; + + autosize.destroy(this.textarea); + + this.textarea.data('height', outerHeight); + this.textarea.outerHeight(outerHeight); + this.textarea.css('max-height', window.outerHeight); + }; + + GLForm.prototype.clearEventListeners = function() { + this.textarea.off('focus'); + this.textarea.off('blur'); + return gl.text.removeListeners(this.form); + }; + + GLForm.prototype.addEventListeners = function() { + this.textarea.on('focus', function() { + return $(this).closest('.md-area').addClass('is-focused'); + }); + return this.textarea.on('blur', function() { + return $(this).closest('.md-area').removeClass('is-focused'); + }); + }; + + global.GLForm = GLForm; +})(); diff --git a/app/assets/javascripts/graphs/graphs_bundle.js b/app/assets/javascripts/graphs/graphs_bundle.js index 32c26349da0..4f7777aa5bc 100644 --- a/app/assets/javascripts/graphs/graphs_bundle.js +++ b/app/assets/javascripts/graphs/graphs_bundle.js @@ -1,12 +1,3 @@ -/* eslint-disable func-names, space-before-function-paren */ -// This is a manifest file that'll be compiled into including all the files listed below. -// Add new JavaScript code in separate files in this directory and they'll automatically -// be included in the compiled file accessible from http://example.com/assets/application.js -// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the -// the compiled file. -// -/*= require_tree . */ - -(function() { - -}).call(this); +// require everything else in this directory +function requireAll(context) { return context.keys().map(context); } +requireAll(require.context('.', false, /^\.\/(?!graphs_bundle).*\.(js|es6)$/)); diff --git a/app/assets/javascripts/graphs/stat_graph.js b/app/assets/javascripts/graphs/stat_graph.js index 3273bf3a263..2e6da5750de 100644 --- a/app/assets/javascripts/graphs/stat_graph.js +++ b/app/assets/javascripts/graphs/stat_graph.js @@ -1,4 +1,4 @@ -/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-return-assign, padded-blocks, max-len */ +/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-return-assign, max-len */ (function() { this.StatGraph = (function() { function StatGraph() {} @@ -14,7 +14,5 @@ }; return StatGraph; - })(); - }).call(this); diff --git a/app/assets/javascripts/graphs/stat_graph_contributors.js b/app/assets/javascripts/graphs/stat_graph_contributors.js index 2d08a7c6ac3..d06a1a5dae4 100644 --- a/app/assets/javascripts/graphs/stat_graph_contributors.js +++ b/app/assets/javascripts/graphs/stat_graph_contributors.js @@ -1,11 +1,11 @@ -/* 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, padded-blocks */ +/* 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 */ /* global ContributorsGraph */ /* global ContributorsAuthorGraph */ /* global ContributorsMasterGraph */ /* global ContributorsStatGraphUtil */ /* global d3 */ -/*= require d3 */ +window.d3 = require('d3'); (function() { this.ContributorsStatGraph = (function() { @@ -112,7 +112,5 @@ }; return ContributorsStatGraph; - })(); - }).call(this); diff --git a/app/assets/javascripts/graphs/stat_graph_contributors_graph.js b/app/assets/javascripts/graphs/stat_graph_contributors_graph.js index 9c5e9381e52..241249fae63 100644 --- a/app/assets/javascripts/graphs/stat_graph_contributors_graph.js +++ b/app/assets/javascripts/graphs/stat_graph_contributors_graph.js @@ -1,11 +1,11 @@ -/* eslint-disable func-names, space-before-function-paren, one-var, no-var, space-before-blocks, 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, padded-blocks, newline-per-chained-call, no-else-return */ +/* eslint-disable func-names, space-before-function-paren, one-var, 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 */ /* global d3 */ /* global ContributorsGraph */ -/*= require d3 */ +window.d3 = require('d3'); (function() { - var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, + var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; }, 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; }, hasProp = {}.hasOwnProperty; @@ -91,7 +91,6 @@ }; return ContributorsGraph; - })(); this.ContributorsMasterGraph = (function(superClass) { @@ -196,7 +195,6 @@ }; return ContributorsMasterGraph; - })(ContributorsGraph); this.ContributorsAuthorGraph = (function(superClass) { @@ -274,7 +272,5 @@ }; return ContributorsAuthorGraph; - })(ContributorsGraph); - }).call(this); diff --git a/app/assets/javascripts/graphs/stat_graph_contributors_util.js b/app/assets/javascripts/graphs/stat_graph_contributors_util.js index 1982f4af939..29c3163328f 100644 --- a/app/assets/javascripts/graphs/stat_graph_contributors_util.js +++ b/app/assets/javascripts/graphs/stat_graph_contributors_util.js @@ -1,4 +1,4 @@ -/* eslint-disable func-names, space-before-function-paren, object-shorthand, no-var, one-var, camelcase, one-var-declaration-per-line, no-plusplus, 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, padded-blocks, max-len */ +/* 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 */ (function() { window.ContributorsStatGraphUtil = { parse_log: function(log) { @@ -6,7 +6,7 @@ total = {}; by_author = {}; by_email = {}; - for (i = 0, len = log.length; i < len; i++) { + for (i = 0, len = log.length; i < len; i += 1) { entry = log[i]; if (total[entry.date] == null) { this.add_date(entry.date, total); @@ -135,5 +135,4 @@ } } }; - }).call(this); diff --git a/app/assets/javascripts/group_avatar.js b/app/assets/javascripts/group_avatar.js index 17a76168a79..10dfd05fe3c 100644 --- a/app/assets/javascripts/group_avatar.js +++ b/app/assets/javascripts/group_avatar.js @@ -1,13 +1,13 @@ -/* eslint-disable func-names, space-before-function-paren, wrap-iife, quotes, no-var, one-var, one-var-declaration-per-line, no-useless-escape, padded-blocks, max-len */ +/* eslint-disable func-names, space-before-function-paren, wrap-iife, quotes, no-var, one-var, one-var-declaration-per-line, no-useless-escape, max-len */ (function() { this.GroupAvatar = (function() { function GroupAvatar() { - $('.js-choose-group-avatar-button').bind("click", function() { + $('.js-choose-group-avatar-button').on("click", function() { var form; form = $(this).closest("form"); return form.find(".js-group-avatar-input").click(); }); - $('.js-group-avatar-input').bind("change", function() { + $('.js-group-avatar-input').on("change", function() { var filename, form; form = $(this).closest("form"); filename = $(this).val().replace(/^.*[\\\/]/, ''); @@ -16,7 +16,5 @@ } return GroupAvatar; - })(); - }).call(this); diff --git a/app/assets/javascripts/group_label_subscription.js.es6 b/app/assets/javascripts/group_label_subscription.js.es6 index 8e10e424412..15e695e81cf 100644 --- a/app/assets/javascripts/group_label_subscription.js.es6 +++ b/app/assets/javascripts/group_label_subscription.js.es6 @@ -1,4 +1,4 @@ -/* eslint-disable func-names, object-shorthand, comma-dangle, wrap-iife, space-before-function-paren, no-param-reassign, padded-blocks, max-len */ +/* eslint-disable func-names, object-shorthand, comma-dangle, wrap-iife, space-before-function-paren, no-param-reassign, max-len */ (function(global) { class GroupLabelSubscription { @@ -50,5 +50,4 @@ } global.GroupLabelSubscription = GroupLabelSubscription; - })(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/groups_select.js b/app/assets/javascripts/groups_select.js index 99700e7562a..bc88dc2d092 100644 --- a/app/assets/javascripts/groups_select.js +++ b/app/assets/javascripts/groups_select.js @@ -1,4 +1,4 @@ -/* eslint-disable func-names, space-before-function-paren, no-var, wrap-iife, one-var, camelcase, one-var-declaration-per-line, quotes, object-shorthand, prefer-arrow-callback, comma-dangle, consistent-return, yoda, prefer-rest-params, prefer-spread, no-unused-vars, prefer-template, padded-blocks, max-len */ +/* eslint-disable func-names, space-before-function-paren, no-var, wrap-iife, one-var, camelcase, one-var-declaration-per-line, quotes, object-shorthand, prefer-arrow-callback, comma-dangle, consistent-return, yoda, prefer-rest-params, prefer-spread, no-unused-vars, prefer-template, max-len */ /* global Api */ (function() { @@ -59,15 +59,13 @@ } else { avatar = gon.default_avatar_url; } - return "<div class='group-result'> <div class='group-name'>" + group.name + "</div> <div class='group-path'>" + group.path + "</div> </div>"; + return "<div class='group-result'> <div class='group-name'>" + group.full_name + "</div> <div class='group-path'>" + group.full_path + "</div> </div>"; }; GroupsSelect.prototype.formatSelection = function(group) { - return group.name; + return group.full_name; }; return GroupsSelect; - })(); - }).call(this); diff --git a/app/assets/javascripts/header.js b/app/assets/javascripts/header.js index c7cbf9ca44b..fa85f9a6c86 100644 --- a/app/assets/javascripts/header.js +++ b/app/assets/javascripts/header.js @@ -1,10 +1,8 @@ -/* eslint-disable wrap-iife, func-names, space-before-function-paren, padded-blocks, prefer-arrow-callback, no-var, max-len */ +/* eslint-disable wrap-iife, func-names, space-before-function-paren, prefer-arrow-callback, no-var, max-len */ (function() { - $(document).on('todo:toggle', function(e, count) { var $todoPendingCount = $('.todos-pending-count'); $todoPendingCount.text(gl.text.addDelimiter(count)); $todoPendingCount.toggleClass('hidden', count === 0); }); - })(); diff --git a/app/assets/javascripts/importer_status.js b/app/assets/javascripts/importer_status.js index fa795be07ed..9390136d3d8 100644 --- a/app/assets/javascripts/importer_status.js +++ b/app/assets/javascripts/importer_status.js @@ -1,4 +1,4 @@ -/* eslint-disable func-names, space-before-function-paren, wrap-iife, camelcase, no-var, one-var, one-var-declaration-per-line, prefer-template, quotes, object-shorthand, comma-dangle, no-unused-vars, prefer-arrow-callback, no-else-return, padded-blocks, vars-on-top, no-new, max-len */ +/* eslint-disable func-names, space-before-function-paren, wrap-iife, camelcase, no-var, one-var, one-var-declaration-per-line, prefer-template, quotes, object-shorthand, comma-dangle, no-unused-vars, prefer-arrow-callback, no-else-return, vars-on-top, no-new, max-len */ (function() { window.ImporterStatus = (function() { @@ -68,7 +68,6 @@ }; return ImporterStatus; - })(); $(function() { diff --git a/app/assets/javascripts/issuable.js.es6 b/app/assets/javascripts/issuable.js.es6 index 1c10a7445bb..8df86f68218 100644 --- a/app/assets/javascripts/issuable.js.es6 +++ b/app/assets/javascripts/issuable.js.es6 @@ -1,13 +1,12 @@ -/* eslint-disable 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, prefer-const, padded-blocks, wrap-iife, max-len */ +/* 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 Issuable */ -/* global Turbolinks */ -(function() { +((global) => { var issuable_created; issuable_created = false; - this.Issuable = { + global.Issuable = { init: function() { Issuable.initTemplates(); Issuable.initSearch(); @@ -34,7 +33,6 @@ e.preventDefault(); debouncedExecSearch(e); }); - }, initSearchState: function($searchInput) { const currentSearchVal = $searchInput.val(); @@ -111,12 +109,16 @@ filterResults: (function(_this) { return function(form) { var formAction, formData, issuesUrl; - formData = form.serialize(); + formData = form.serializeArray(); + formData = formData.filter(function(data) { + return data.value !== ''; + }); + formData = $.param(formData); formAction = form.attr('action'); issuesUrl = formAction; issuesUrl += "" + (formAction.indexOf('?') < 0 ? '?' : '&'); issuesUrl += formData; - return Turbolinks.visit(issuesUrl); + return gl.utils.visitUrl(issuesUrl); }; })(this), initResetFilters: function() { @@ -127,7 +129,7 @@ const baseIssuesUrl = target.href; $form.attr('action', baseIssuesUrl); - Turbolinks.visit(baseIssuesUrl); + gl.utils.visitUrl(baseIssuesUrl); }); }, initChecks: function() { @@ -148,7 +150,7 @@ this.issuableBulkActions.setOriginalDropdownData(); if ($checkedIssues.length > 0) { - let ids = $.map($checkedIssues, function(value) { + const ids = $.map($checkedIssues, function(value) { return $(value).data('id'); }); $updateIssuesIds.val(ids); @@ -183,5 +185,4 @@ }); } }; - -}).call(this); +})(window); diff --git a/app/assets/javascripts/issuable/issuable_bundle.js.es6 b/app/assets/javascripts/issuable/issuable_bundle.js.es6 new file mode 100644 index 00000000000..e927cc0077c --- /dev/null +++ b/app/assets/javascripts/issuable/issuable_bundle.js.es6 @@ -0,0 +1 @@ +require('./time_tracking/time_tracking_bundle'); diff --git a/app/assets/javascripts/issuable/time_tracking/components/collapsed_state.js.es6 b/app/assets/javascripts/issuable/time_tracking/components/collapsed_state.js.es6 new file mode 100644 index 00000000000..bf27fbac5d7 --- /dev/null +++ b/app/assets/javascripts/issuable/time_tracking/components/collapsed_state.js.es6 @@ -0,0 +1,41 @@ +/* global Vue */ +require('../../../lib/utils/pretty_time'); + +(() => { + Vue.component('time-tracking-collapsed-state', { + name: 'time-tracking-collapsed-state', + props: [ + 'showComparisonState', + 'showSpentOnlyState', + 'showEstimateOnlyState', + 'showNoTimeTrackingState', + 'timeSpentHumanReadable', + 'timeEstimateHumanReadable', + 'stopwatchSvg', + ], + methods: { + abbreviateTime(timeStr) { + return gl.utils.prettyTime.abbreviateTime(timeStr); + }, + }, + template: ` + <div class='sidebar-collapsed-icon'> + <div v-html='stopwatchSvg'></div> + <div class='time-tracking-collapsed-summary'> + <div class='compare' v-if='showComparisonState'> + <span>{{ abbreviateTime(timeSpentHumanReadable) }} / {{ abbreviateTime(timeEstimateHumanReadable) }}</span> + </div> + <div class='estimate-only' v-if='showEstimateOnlyState'> + <span class='bold'>-- / {{ abbreviateTime(timeEstimateHumanReadable) }}</span> + </div> + <div class='spend-only' v-if='showSpentOnlyState'> + <span class='bold'>{{ abbreviateTime(timeSpentHumanReadable) }} / --</span> + </div> + <div class='no-tracking' v-if='showNoTimeTrackingState'> + <span class='no-value'>None</span> + </div> + </div> + </div> + `, + }); +})(); diff --git a/app/assets/javascripts/issuable/time_tracking/components/comparison_pane.js.es6 b/app/assets/javascripts/issuable/time_tracking/components/comparison_pane.js.es6 new file mode 100644 index 00000000000..750468c679b --- /dev/null +++ b/app/assets/javascripts/issuable/time_tracking/components/comparison_pane.js.es6 @@ -0,0 +1,69 @@ +/* global Vue */ +require('../../../lib/utils/pretty_time'); + +(() => { + const prettyTime = gl.utils.prettyTime; + + Vue.component('time-tracking-comparison-pane', { + name: 'time-tracking-comparison-pane', + props: [ + 'timeSpent', + 'timeEstimate', + 'timeSpentHumanReadable', + 'timeEstimateHumanReadable', + ], + computed: { + parsedRemaining() { + const diffSeconds = this.timeEstimate - this.timeSpent; + return prettyTime.parseSeconds(diffSeconds); + }, + timeRemainingHumanReadable() { + return prettyTime.stringifyTime(this.parsedRemaining); + }, + timeRemainingTooltip() { + const prefix = this.timeRemainingMinutes < 0 ? 'Over by' : 'Time remaining:'; + return `${prefix} ${this.timeRemainingHumanReadable}`; + }, + /* Diff values for comparison meter */ + timeRemainingMinutes() { + return this.timeEstimate - this.timeSpent; + }, + timeRemainingPercent() { + return `${Math.floor((this.timeSpent / this.timeEstimate) * 100)}%`; + }, + timeRemainingStatusClass() { + return this.timeEstimate >= this.timeSpent ? 'within_estimate' : 'over_estimate'; + }, + /* Parsed time values */ + parsedEstimate() { + return prettyTime.parseSeconds(this.timeEstimate); + }, + parsedSpent() { + return prettyTime.parseSeconds(this.timeSpent); + }, + }, + template: ` + <div class='time-tracking-comparison-pane'> + <div class='compare-meter' data-toggle='tooltip' data-placement='top' role='timeRemainingDisplay' + :aria-valuenow='timeRemainingTooltip' + :title='timeRemainingTooltip' + :data-original-title='timeRemainingTooltip' + :class='timeRemainingStatusClass'> + <div class='meter-container' role='timeSpentPercent' :aria-valuenow='timeRemainingPercent'> + <div :style='{ width: timeRemainingPercent }' class='meter-fill'></div> + </div> + <div class='compare-display-container'> + <div class='compare-display pull-left'> + <span class='compare-label'>Spent</span> + <span class='compare-value spent'>{{ timeSpentHumanReadable }}</span> + </div> + <div class='compare-display estimated pull-right'> + <span class='compare-label'>Est</span> + <span class='compare-value'>{{ timeEstimateHumanReadable }}</span> + </div> + </div> + </div> + </div> + `, + }); +})(); diff --git a/app/assets/javascripts/issuable/time_tracking/components/estimate_only_pane.js.es6 b/app/assets/javascripts/issuable/time_tracking/components/estimate_only_pane.js.es6 new file mode 100644 index 00000000000..309e9f2f9ef --- /dev/null +++ b/app/assets/javascripts/issuable/time_tracking/components/estimate_only_pane.js.es6 @@ -0,0 +1,13 @@ +/* global Vue */ +(() => { + Vue.component('time-tracking-estimate-only-pane', { + name: 'time-tracking-estimate-only-pane', + props: ['timeEstimateHumanReadable'], + template: ` + <div class='time-tracking-estimate-only-pane'> + <span class='bold'>Estimated:</span> + {{ timeEstimateHumanReadable }} + </div> + `, + }); +})(); diff --git a/app/assets/javascripts/issuable/time_tracking/components/help_state.js.es6 b/app/assets/javascripts/issuable/time_tracking/components/help_state.js.es6 new file mode 100644 index 00000000000..d7ced6d7151 --- /dev/null +++ b/app/assets/javascripts/issuable/time_tracking/components/help_state.js.es6 @@ -0,0 +1,24 @@ +/* global Vue */ +(() => { + Vue.component('time-tracking-help-state', { + name: 'time-tracking-help-state', + props: ['docsUrl'], + template: ` + <div class='time-tracking-help-state'> + <div class='time-tracking-info'> + <h4>Track time with slash commands</h4> + <p>Slash commands can be used in the issues description and comment boxes.</p> + <p> + <code>/estimate</code> + will update the estimated time with the latest command. + </p> + <p> + <code>/spend</code> + will update the sum of the time spent. + </p> + <a class='btn btn-default learn-more-button' :href='docsUrl'>Learn more</a> + </div> + </div> + `, + }); +})(); diff --git a/app/assets/javascripts/issuable/time_tracking/components/no_tracking_pane.js.es6 b/app/assets/javascripts/issuable/time_tracking/components/no_tracking_pane.js.es6 new file mode 100644 index 00000000000..1d2ca643b5b --- /dev/null +++ b/app/assets/javascripts/issuable/time_tracking/components/no_tracking_pane.js.es6 @@ -0,0 +1,11 @@ +/* global Vue */ +(() => { + Vue.component('time-tracking-no-tracking-pane', { + name: 'time-tracking-no-tracking-pane', + template: ` + <div class='time-tracking-no-tracking-pane'> + <span class='no-value'>No estimate or time spent</span> + </div> + `, + }); +})(); diff --git a/app/assets/javascripts/issuable/time_tracking/components/spent_only_pane.js.es6 b/app/assets/javascripts/issuable/time_tracking/components/spent_only_pane.js.es6 new file mode 100644 index 00000000000..ed283fec3c3 --- /dev/null +++ b/app/assets/javascripts/issuable/time_tracking/components/spent_only_pane.js.es6 @@ -0,0 +1,13 @@ +/* global Vue */ +(() => { + Vue.component('time-tracking-spent-only-pane', { + name: 'time-tracking-spent-only-pane', + props: ['timeSpentHumanReadable'], + template: ` + <div class='time-tracking-spend-only-pane'> + <span class='bold'>Spent:</span> + {{ timeSpentHumanReadable }} + </div> + `, + }); +})(); diff --git a/app/assets/javascripts/issuable/time_tracking/components/time_tracker.js.es6 b/app/assets/javascripts/issuable/time_tracking/components/time_tracker.js.es6 new file mode 100644 index 00000000000..e38f7852b1c --- /dev/null +++ b/app/assets/javascripts/issuable/time_tracking/components/time_tracker.js.es6 @@ -0,0 +1,119 @@ +/* global Vue */ + +require('./help_state'); +require('./collapsed_state'); +require('./spent_only_pane'); +require('./no_tracking_pane'); +require('./estimate_only_pane'); +require('./comparison_pane'); + +(() => { + Vue.component('issuable-time-tracker', { + name: 'issuable-time-tracker', + props: [ + 'time_estimate', + 'time_spent', + 'human_time_estimate', + 'human_time_spent', + 'stopwatchSvg', + 'docsUrl', + ], + data() { + return { + showHelp: false, + }; + }, + computed: { + timeSpent() { + return this.time_spent; + }, + timeEstimate() { + return this.time_estimate; + }, + timeEstimateHumanReadable() { + return this.human_time_estimate; + }, + timeSpentHumanReadable() { + return this.human_time_spent; + }, + hasTimeSpent() { + return !!this.timeSpent; + }, + hasTimeEstimate() { + return !!this.timeEstimate; + }, + showComparisonState() { + return this.hasTimeEstimate && this.hasTimeSpent; + }, + showEstimateOnlyState() { + return this.hasTimeEstimate && !this.hasTimeSpent; + }, + showSpentOnlyState() { + return this.hasTimeSpent && !this.hasTimeEstimate; + }, + showNoTimeTrackingState() { + return !this.hasTimeEstimate && !this.hasTimeSpent; + }, + showHelpState() { + return !!this.showHelp; + }, + }, + methods: { + toggleHelpState(show) { + this.showHelp = show; + }, + }, + template: ` + <div class='time_tracker time-tracking-component-wrap' v-cloak> + <time-tracking-collapsed-state + :show-comparison-state='showComparisonState' + :show-help-state='showHelpState' + :show-spent-only-state='showSpentOnlyState' + :show-estimate-only-state='showEstimateOnlyState' + :time-spent-human-readable='timeSpentHumanReadable' + :time-estimate-human-readable='timeEstimateHumanReadable' + :stopwatch-svg='stopwatchSvg'> + </time-tracking-collapsed-state> + <div class='title hide-collapsed'> + Time tracking + <div class='help-button pull-right' + v-if='!showHelpState' + @click='toggleHelpState(true)'> + <i class='fa fa-question-circle'></i> + </div> + <div class='close-help-button pull-right' + v-if='showHelpState' + @click='toggleHelpState(false)'> + <i class='fa fa-close'></i> + </div> + </div> + <div class='time-tracking-content hide-collapsed'> + <time-tracking-estimate-only-pane + v-if='showEstimateOnlyState' + :time-estimate-human-readable='timeEstimateHumanReadable'> + </time-tracking-estimate-only-pane> + <time-tracking-spent-only-pane + v-if='showSpentOnlyState' + :time-spent-human-readable='timeSpentHumanReadable'> + </time-tracking-spent-only-pane> + <time-tracking-no-tracking-pane + v-if='showNoTimeTrackingState'> + </time-tracking-no-tracking-pane> + <time-tracking-comparison-pane + v-if='showComparisonState' + :time-estimate='timeEstimate' + :time-spent='timeSpent' + :time-spent-human-readable='timeSpentHumanReadable' + :time-estimate-human-readable='timeEstimateHumanReadable'> + </time-tracking-comparison-pane> + <transition name='help-state-toggle'> + <time-tracking-help-state + v-if='showHelpState' + :docs-url='docsUrl'> + </time-tracking-help-state> + </transition> + </div> + </div> + `, + }); +})(); diff --git a/app/assets/javascripts/issuable/time_tracking/time_tracking_bundle.js.es6 b/app/assets/javascripts/issuable/time_tracking/time_tracking_bundle.js.es6 new file mode 100644 index 00000000000..1ca01d3bdb9 --- /dev/null +++ b/app/assets/javascripts/issuable/time_tracking/time_tracking_bundle.js.es6 @@ -0,0 +1,62 @@ +/* global Vue */ + +require('./components/time_tracker'); +require('../../smart_interval'); +require('../../subbable_resource'); + +(() => { + /* This Vue instance represents what will become the parent instance for the + * sidebar. It will be responsible for managing `issuable` state and propagating + * changes to sidebar components. We will want to create a separate service to + * interface with the server at that point. + */ + + class IssuableTimeTracking { + constructor(issuableJSON) { + const parsedIssuable = JSON.parse(issuableJSON); + return this.initComponent(parsedIssuable); + } + + initComponent(parsedIssuable) { + this.parentInstance = new Vue({ + el: '#issuable-time-tracker', + data: { + issuable: parsedIssuable, + }, + methods: { + fetchIssuable() { + return gl.IssuableResource.get.call(gl.IssuableResource, { + type: 'GET', + url: gl.IssuableResource.endpoint, + }); + }, + updateState(data) { + this.issuable = data; + }, + subscribeToUpdates() { + gl.IssuableResource.subscribe(data => this.updateState(data)); + }, + listenForSlashCommands() { + $(document).on('ajax:success', '.gfm-form', (e, data) => { + const subscribedCommands = ['spend_time', 'time_estimate']; + const changedCommands = data.commands_changes; + + if (changedCommands && _.intersection(subscribedCommands, changedCommands).length) { + this.fetchIssuable(); + } + }); + }, + }, + created() { + this.fetchIssuable(); + }, + mounted() { + this.subscribeToUpdates(); + this.listenForSlashCommands(); + }, + }); + } + } + + gl.IssuableTimeTracking = IssuableTimeTracking; +})(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/issuable_context.js b/app/assets/javascripts/issuable_context.js index 4aaad111082..c77fbb6a1c7 100644 --- a/app/assets/javascripts/issuable_context.js +++ b/app/assets/javascripts/issuable_context.js @@ -1,5 +1,7 @@ -/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-new, comma-dangle, quotes, prefer-arrow-callback, consistent-return, one-var, no-var, one-var-declaration-per-line, no-underscore-dangle, padded-blocks, max-len */ +/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-new, comma-dangle, quotes, prefer-arrow-callback, consistent-return, one-var, no-var, one-var-declaration-per-line, no-underscore-dangle, max-len */ /* global UsersSelect */ +/* global Cookies */ +/* global bp */ (function() { this.IssuableContext = (function() { @@ -37,6 +39,13 @@ }, 0); } }); + window.addEventListener('beforeunload', function() { + // collapsed_gutter cookie hides the sidebar + var bpBreakpoint = bp.getBreakpointSize(); + if (bpBreakpoint === 'xs' || bpBreakpoint === 'sm') { + Cookies.set('collapsed_gutter', true); + } + }); $(".right-sidebar").niceScroll(); } @@ -66,7 +75,5 @@ }; return IssuableContext; - })(); - }).call(this); diff --git a/app/assets/javascripts/issuable_form.js b/app/assets/javascripts/issuable_form.js index 1c4086517fe..293b856dc4d 100644 --- a/app/assets/javascripts/issuable_form.js +++ b/app/assets/javascripts/issuable_form.js @@ -1,11 +1,11 @@ -/* eslint-disable func-names, space-before-function-paren, no-var, space-before-blocks, prefer-rest-params, wrap-iife, no-use-before-define, no-useless-escape, no-new, quotes, object-shorthand, no-unused-vars, comma-dangle, radix, no-alert, consistent-return, no-else-return, prefer-template, one-var, one-var-declaration-per-line, curly, padded-blocks, max-len */ +/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, no-use-before-define, no-useless-escape, no-new, quotes, object-shorthand, no-unused-vars, comma-dangle, no-alert, consistent-return, no-else-return, prefer-template, one-var, one-var-declaration-per-line, curly, max-len */ /* global GitLab */ /* global UsersSelect */ /* global ZenMode */ /* global Autosave */ (function() { - var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; + var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; }; this.IssuableForm = (function() { IssuableForm.prototype.issueMoveConfirmMsg = 'Are you sure you want to move this issue to another project?'; @@ -51,7 +51,7 @@ IssuableForm.prototype.handleSubmit = function() { var fieldId = (this.issueMoveField != null) ? this.issueMoveField.val() : null; - if ((parseInt(fieldId) || 0) > 0) { + if ((parseInt(fieldId, 10) || 0) > 0) { if (!confirm(this.issueMoveConfirmMsg)) { return false; } @@ -150,7 +150,5 @@ }; return IssuableForm; - })(); - }).call(this); diff --git a/app/assets/javascripts/issue.js b/app/assets/javascripts/issue.js index 63b70d4be17..6c08b1b8e61 100644 --- a/app/assets/javascripts/issue.js +++ b/app/assets/javascripts/issue.js @@ -1,12 +1,12 @@ -/* eslint-disable func-names, space-before-function-paren, no-var, space-before-blocks, prefer-rest-params, wrap-iife, one-var, no-underscore-dangle, one-var-declaration-per-line, object-shorthand, no-unused-vars, no-new, comma-dangle, consistent-return, quotes, dot-notation, quote-props, prefer-arrow-callback, padded-blocks, max-len */ +/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, one-var, no-underscore-dangle, one-var-declaration-per-line, object-shorthand, no-unused-vars, no-new, comma-dangle, consistent-return, quotes, dot-notation, quote-props, prefer-arrow-callback, max-len */ /* global Flash */ -/*= require flash */ -/*= require jquery.waitforimages */ -/*= require task_list */ +require('./flash'); +require('vendor/jquery.waitforimages'); +require('vendor/task_list'); (function() { - var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; + var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; }; this.Issue = (function() { function Issue() { @@ -139,22 +139,17 @@ return; } return $.getJSON($container.data('path')).error(function() { - $container.find('.checking').hide(); $container.find('.unavailable').show(); return new Flash('Failed to check if a new branch can be created.', 'alert'); }).success(function(data) { if (data.can_create_branch) { - $container.find('.checking').hide(); $container.find('.available').show(); } else { - $container.find('.checking').hide(); return $container.find('.unavailable').show(); } }); }; return Issue; - })(); - }).call(this); diff --git a/app/assets/javascripts/issue_status_select.js b/app/assets/javascripts/issue_status_select.js index b39d8274e13..1d6eff11403 100644 --- a/app/assets/javascripts/issue_status_select.js +++ b/app/assets/javascripts/issue_status_select.js @@ -1,4 +1,4 @@ -/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, quotes, object-shorthand, no-unused-vars, no-shadow, one-var, one-var-declaration-per-line, comma-dangle, padded-blocks, max-len */ +/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, quotes, object-shorthand, no-unused-vars, no-shadow, one-var, one-var-declaration-per-line, comma-dangle, max-len */ (function() { this.IssueStatusSelect = (function() { function IssueStatusSelect() { @@ -30,7 +30,5 @@ } return IssueStatusSelect; - })(); - }).call(this); diff --git a/app/assets/javascripts/issues_bulk_assignment.js.es6 b/app/assets/javascripts/issues_bulk_assignment.js.es6 index 52fd5d71b18..e0ebd36a65c 100644 --- a/app/assets/javascripts/issues_bulk_assignment.js.es6 +++ b/app/assets/javascripts/issues_bulk_assignment.js.es6 @@ -1,9 +1,8 @@ -/* eslint-disable comma-dangle, quotes, consistent-return, func-names, array-callback-return, space-before-function-paren, prefer-arrow-callback, radix, max-len, padded-blocks, no-unused-expressions, no-sequences, no-underscore-dangle, no-unused-vars, no-param-reassign */ +/* 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 Issuable */ /* global Flash */ ((global) => { - class IssuableBulkActions { constructor({ container, form, issues, prefixId } = {}) { this.prefixId = prefixId || 'issue_'; @@ -62,7 +61,6 @@ return labels; } - /** * Will return only labels that were marked previously and the user has unmarked * @return {Array} Label IDs @@ -81,7 +79,6 @@ return result; } - /** * Simple form serialization, it will return just what we need * Returns key/value pairs from form data @@ -163,5 +160,4 @@ } global.IssuableBulkActions = IssuableBulkActions; - })(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/label_manager.js.es6 b/app/assets/javascripts/label_manager.js.es6 index 33c5e35324d..2a50b72c8aa 100644 --- a/app/assets/javascripts/label_manager.js.es6 +++ b/app/assets/javascripts/label_manager.js.es6 @@ -1,14 +1,14 @@ -/* eslint-disable comma-dangle, class-methods-use-this, no-underscore-dangle, no-param-reassign, no-unused-vars, consistent-return, func-names, space-before-function-paren, padded-blocks, max-len */ +/* eslint-disable comma-dangle, class-methods-use-this, no-underscore-dangle, no-param-reassign, no-unused-vars, consistent-return, func-names, space-before-function-paren, max-len */ /* global Flash */ ((global) => { - class LabelManager { constructor({ togglePriorityButton, prioritizedLabels, otherLabels } = {}) { this.togglePriorityButton = togglePriorityButton || $('.js-toggle-priority'); this.prioritizedLabels = prioritizedLabels || $('.js-prioritized-labels'); this.otherLabels = otherLabels || $('.js-other-labels'); this.errorMessage = 'Unable to update label prioritization at this time'; + this.emptyState = document.querySelector('#js-priority-labels-empty-state'); this.prioritizedLabels.sortable({ items: 'li', placeholder: 'list-placeholder', @@ -30,7 +30,12 @@ const action = $btn.parents('.js-prioritized-labels').length ? 'remove' : 'add'; const $tooltip = $(`#${$btn.find('.has-tooltip:visible').attr('aria-describedby')}`); $tooltip.tooltip('destroy'); - return _this.toggleLabelPriority($label, action); + _this.toggleLabelPriority($label, action); + _this.toggleEmptyState($label, $btn, action); + } + + toggleEmptyState($label, $btn, action) { + this.emptyState.classList.toggle('hidden', !!this.prioritizedLabels[0].querySelector(':scope > li')); } toggleLabelPriority($label, action, persistState) { @@ -104,5 +109,4 @@ } gl.LabelManager = LabelManager; - })(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/labels.js b/app/assets/javascripts/labels.js index 10de13c9a8a..40ad6fc348e 100644 --- a/app/assets/javascripts/labels.js +++ b/app/assets/javascripts/labels.js @@ -1,6 +1,6 @@ -/* eslint-disable func-names, space-before-function-paren, no-var, space-before-blocks, prefer-rest-params, wrap-iife, vars-on-top, no-unused-vars, padded-blocks, max-len */ +/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, vars-on-top, no-unused-vars, max-len */ (function() { - var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; + var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; }; this.Labels = (function() { function Labels() { @@ -42,7 +42,5 @@ }; return Labels; - })(); - }).call(this); diff --git a/app/assets/javascripts/labels_select.js b/app/assets/javascripts/labels_select.js index ec2fc87bece..e4cf9057e6d 100644 --- a/app/assets/javascripts/labels_select.js +++ b/app/assets/javascripts/labels_select.js @@ -1,13 +1,20 @@ -/* 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, semi, dot-notation, no-empty, no-return-assign, camelcase, prefer-spread, padded-blocks */ +/* 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 */ (function() { this.LabelsSelect = (function() { - function LabelsSelect() { - var _this; + function LabelsSelect(els) { + var _this, $els; _this = this; - $('.js-label-select').each(function(i, dropdown) { + + $els = $(els); + + if (!els) { + $els = $('.js-label-select'); + } + + $els.each(function(i, dropdown) { var $block, $colorPreview, $dropdown, $form, $loading, $selectbox, $sidebarCollapsedValue, $value, abilityName, defaultLabel, enableLabelCreateButton, issueURLSplit, issueUpdateURL, labelHTMLTemplate, labelNoneHTMLTemplate, labelUrl, namespacePath, projectPath, saveLabelData, selectedLabel, showAny, showNo, $sidebarLabelTooltip, initialSelected, $toggleText, fieldName, useId, propertyName, showMenuAbove, $container, $dropdownContainer; $dropdown = $(dropdown); $dropdownContainer = $dropdown.closest('.labels-filter'); @@ -324,7 +331,7 @@ multiSelect: $dropdown.hasClass('js-multiselect'), vue: $dropdown.hasClass('js-issue-board-sidebar'), clicked: function(label, $el, e, isMarking) { - var isIssueIndex, isMRIndex, page; + var isIssueIndex, isMRIndex, page, boardsModel; page = $('body').data('page'); isIssueIndex = page === 'projects:issues:index'; @@ -333,31 +340,44 @@ if ($dropdown.parent().find('.is-active:not(.dropdown-clear-active)').length) { $dropdown.parent() .find('.dropdown-clear-active') - .removeClass('is-active') + .removeClass('is-active'); } - if ($dropdown.hasClass('js-filter-bulk-update') || $dropdown.hasClass('js-issuable-form-dropdown')) { + if ($dropdown.hasClass('js-issuable-form-dropdown')) { + return; + } + + if ($dropdown.hasClass('js-filter-bulk-update')) { _this.enableBulkLabelDropdown(); _this.setDropdownData($dropdown, isMarking, this.id(label)); return; } - if ($('html').hasClass('issue-boards-page') && !$dropdown.hasClass('js-issue-board-sidebar')) { + if ($('html').hasClass('issue-boards-page') && !$dropdown.hasClass('js-issue-board-sidebar') && + !$dropdown.closest('.add-issues-modal').length) { + boardsModel = gl.issueBoards.BoardsStore.state.filters; + } else if ($dropdown.closest('.add-issues-modal').length) { + boardsModel = gl.issueBoards.ModalStore.store.filter; + } + + if (boardsModel) { if (label.isAny) { - gl.issueBoards.BoardsStore.state.filters['label_name'] = []; + boardsModel['label_name'] = []; } else if ($el.hasClass('is-active')) { - gl.issueBoards.BoardsStore.state.filters['label_name'].push(label.title); + boardsModel['label_name'].push(label.title); } else { - var filters = gl.issueBoards.BoardsStore.state.filters['label_name']; + var filters = boardsModel['label_name']; filters = filters.filter(function (filteredLabel) { return filteredLabel !== label.title; }); - gl.issueBoards.BoardsStore.state.filters['label_name'] = filters; + boardsModel['label_name'] = filters; } - gl.issueBoards.BoardsStore.updateFiltersUrl(); + if (!$dropdown.closest('.add-issues-modal').length) { + gl.issueBoards.BoardsStore.updateFiltersUrl(); + } e.preventDefault(); return; } @@ -484,5 +504,4 @@ return LabelsSelect; })(); - }).call(this); diff --git a/app/assets/javascripts/layout_nav.js b/app/assets/javascripts/layout_nav.js index 2b700539c2b..1c0ea317c1a 100644 --- a/app/assets/javascripts/layout_nav.js +++ b/app/assets/javascripts/layout_nav.js @@ -1,4 +1,5 @@ -/* eslint-disable func-names, space-before-function-paren, no-var, prefer-arrow-callback, no-unused-vars, one-var, one-var-declaration-per-line, indent, vars-on-top, padded-blocks, max-len */ +/* eslint-disable func-names, space-before-function-paren, no-var, prefer-arrow-callback, no-unused-vars, one-var, one-var-declaration-per-line, vars-on-top, max-len */ + (function() { var hideEndFade; @@ -27,10 +28,10 @@ }); $scrollingTabs.each(function () { - var $this = $(this), - scrollingTabWidth = $this.width(), - $active = $this.find('.active'), - activeWidth = $active.width(); + var $this = $(this); + var scrollingTabWidth = $this.width(); + var $active = $this.find('.active'); + var activeWidth = $active.width(); if ($active.length) { var offset = $active.offset().left + activeWidth; @@ -43,5 +44,4 @@ } }); }); - }).call(this); diff --git a/app/assets/javascripts/lib/ace.js b/app/assets/javascripts/lib/ace.js index 4cdf99cae72..9cdc0309503 100644 --- a/app/assets/javascripts/lib/ace.js +++ b/app/assets/javascripts/lib/ace.js @@ -1,2 +1,3 @@ -/*= require ace-rails-ap */ +/*= require ace/ace */ /*= require ace/ext-searchbox */ +/*= require ./ace/ace_config_paths */ diff --git a/app/assets/javascripts/lib/ace/ace_config_paths.js.erb b/app/assets/javascripts/lib/ace/ace_config_paths.js.erb new file mode 100644 index 00000000000..976769ba84a --- /dev/null +++ b/app/assets/javascripts/lib/ace/ace_config_paths.js.erb @@ -0,0 +1,34 @@ +<% +ace_gem_path = Bundler.rubygems.find_name('ace-rails-ap').first.full_gem_path +ace_workers = Dir[ace_gem_path + '/vendor/assets/javascripts/ace/worker-*.js'].sort.map do |file| + File.basename(file, '.js').sub(/^worker-/, '') +end +ace_modes = Dir[ace_gem_path + '/vendor/assets/javascripts/ace/mode-*.js'].sort.map do |file| + File.basename(file, '.js').sub(/^mode-/, '') +end +%> +// Lazy-load configuration when ace.edit is called +(function() { + var basePath; + var ace = window.ace; + var edit = ace.edit; + ace.edit = function() { + window.gon = window.gon || {}; + basePath = (window.gon.relative_url_root || '').replace(/\/$/, '') + '/assets/ace'; + ace.config.set('basePath', basePath); + + // configure paths for all worker modules +<% ace_workers.each do |worker| %> + ace.config.setModuleUrl('ace/mode/<%= worker %>_worker', basePath + '/<%= File.basename(asset_path("ace/worker-#{worker}.js")) %>'); +<% end %> + + // configure paths for all mode modules +<% ace_modes.each do |mode| %> + ace.config.setModuleUrl('ace/mode/<%= mode %>', basePath + '/<%= File.basename(asset_path("ace/mode-#{mode}.js")) %>'); +<% end %> + + // restore original method + ace.edit = edit; + return ace.edit.apply(ace, arguments); + }; +})(); diff --git a/app/assets/javascripts/lib/chart.js b/app/assets/javascripts/lib/chart.js index d8ad5aaeffe..9b011d89e93 100644 --- a/app/assets/javascripts/lib/chart.js +++ b/app/assets/javascripts/lib/chart.js @@ -1,7 +1,3 @@ /* eslint-disable func-names, space-before-function-paren */ -/*= require Chart */ - -(function() { - -}).call(this); +window.Chart = require('vendor/Chart'); diff --git a/app/assets/javascripts/lib/d3.js b/app/assets/javascripts/lib/d3.js index 57e7986576c..a9dd32edbed 100644 --- a/app/assets/javascripts/lib/d3.js +++ b/app/assets/javascripts/lib/d3.js @@ -1,7 +1,3 @@ /* eslint-disable func-names, space-before-function-paren */ -/*= require d3 */ - -(function() { - -}).call(this); +window.d3 = require('d3'); diff --git a/app/assets/javascripts/lib/utils/animate.js b/app/assets/javascripts/lib/utils/animate.js index 83957af94f3..ce090a2e4fd 100644 --- a/app/assets/javascripts/lib/utils/animate.js +++ b/app/assets/javascripts/lib/utils/animate.js @@ -1,4 +1,4 @@ -/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-param-reassign, no-void, prefer-template, no-var, new-cap, prefer-arrow-callback, consistent-return, padded-blocks, max-len */ +/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-param-reassign, no-void, prefer-template, no-var, new-cap, prefer-arrow-callback, consistent-return, max-len */ (function() { (function(w) { if (w.gl == null) { @@ -46,5 +46,4 @@ return dfd.promise(); }; })(window); - }).call(this); diff --git a/app/assets/javascripts/lib/utils/bootstrap_linked_tabs.js.es6 b/app/assets/javascripts/lib/utils/bootstrap_linked_tabs.js.es6 index e810ee85bd3..2955bda1a36 100644 --- a/app/assets/javascripts/lib/utils/bootstrap_linked_tabs.js.es6 +++ b/app/assets/javascripts/lib/utils/bootstrap_linked_tabs.js.es6 @@ -95,7 +95,6 @@ const newState = `${copySource}${this.currentLocation.search}${this.currentLocation.hash}`; history.replaceState({ - turbolinks: true, url: newState, }, document.title, newState); return newState; diff --git a/app/assets/javascripts/lib/utils/common_utils.js b/app/assets/javascripts/lib/utils/common_utils.js deleted file mode 100644 index 8fa80502d92..00000000000 --- a/app/assets/javascripts/lib/utils/common_utils.js +++ /dev/null @@ -1,119 +0,0 @@ -/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-unused-expressions, no-param-reassign, no-else-return, quotes, object-shorthand, comma-dangle, camelcase, one-var, vars-on-top, one-var-declaration-per-line, no-return-assign, consistent-return, padded-blocks, max-len, prefer-template */ -(function() { - (function(w) { - var base; - w.gl || (w.gl = {}); - (base = w.gl).utils || (base.utils = {}); - w.gl.utils.isInGroupsPage = function() { - return gl.utils.getPagePath() === 'groups'; - }; - w.gl.utils.isInProjectPage = function() { - return gl.utils.getPagePath() === 'projects'; - }; - w.gl.utils.getProjectSlug = function() { - if (this.isInProjectPage()) { - return $('body').data('project'); - } else { - return null; - } - }; - w.gl.utils.getGroupSlug = function() { - if (this.isInGroupsPage()) { - return $('body').data('group'); - } else { - return null; - } - }; - - w.gl.utils.ajaxGet = function(url) { - return $.ajax({ - type: "GET", - url: url, - dataType: "script" - }); - }; - - w.gl.utils.extractLast = function(term) { - return this.split(term).pop(); - }; - - w.gl.utils.rstrip = function rstrip(val) { - if (val) { - return val.replace(/\s+$/, ''); - } else { - return val; - } - }; - - w.gl.utils.disableButtonIfEmptyField = function(field_selector, button_selector, event_name) { - event_name = event_name || 'input'; - var closest_submit, field, that; - that = this; - field = $(field_selector); - closest_submit = field.closest('form').find(button_selector); - if (this.rstrip(field.val()) === "") { - closest_submit.disable(); - } - return field.on(event_name, function() { - if (that.rstrip($(this).val()) === "") { - return closest_submit.disable(); - } else { - return closest_submit.enable(); - } - }); - }; - - // automatically adjust scroll position for hash urls taking the height of the navbar into account - // https://github.com/twitter/bootstrap/issues/1768 - w.gl.utils.handleLocationHash = function() { - var hash = w.gl.utils.getLocationHash(); - if (!hash) return; - - var navbar = document.querySelector('.navbar-gitlab'); - var subnav = document.querySelector('.layout-nav'); - var fixedTabs = document.querySelector('.js-tabs-affix'); - - var adjustment = 0; - if (navbar) adjustment -= navbar.offsetHeight; - if (subnav) adjustment -= subnav.offsetHeight; - - // scroll to user-generated markdown anchor if we cannot find a match - if (document.getElementById(hash) === null) { - var target = document.getElementById('user-content-' + hash); - if (target && target.scrollIntoView) { - target.scrollIntoView(true); - window.scrollBy(0, adjustment); - } - } else { - // only adjust for fixedTabs when not targeting user-generated content - if (fixedTabs) { - adjustment -= fixedTabs.offsetHeight; - } - window.scrollBy(0, adjustment); - } - }; - - gl.utils.getPagePath = function() { - return $('body').data('page').split(':')[0]; - }; - - gl.utils.parseUrl = function (url) { - var parser = document.createElement('a'); - parser.href = url; - return parser; - }; - - gl.utils.parseUrlPathname = function (url) { - var parsedUrl = gl.utils.parseUrl(url); - // parsedUrl.pathname will return an absolute path for Firefox and a relative path for IE11 - // We have to make sure we always have an absolute path. - return parsedUrl.pathname.charAt(0) === '/' ? parsedUrl.pathname : '/' + parsedUrl.pathname; - }; - - gl.utils.isMetaKey = function(e) { - return e.metaKey || e.ctrlKey || e.altKey || e.shiftKey; - }; - - })(window); - -}).call(this); diff --git a/app/assets/javascripts/lib/utils/common_utils.js.es6 b/app/assets/javascripts/lib/utils/common_utils.js.es6 new file mode 100644 index 00000000000..5becf688652 --- /dev/null +++ b/app/assets/javascripts/lib/utils/common_utils.js.es6 @@ -0,0 +1,256 @@ +/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-unused-expressions, no-param-reassign, no-else-return, quotes, object-shorthand, comma-dangle, camelcase, one-var, vars-on-top, one-var-declaration-per-line, no-return-assign, consistent-return, max-len, prefer-template */ +(function() { + (function(w) { + var base; + w.gl || (w.gl = {}); + (base = w.gl).utils || (base.utils = {}); + w.gl.utils.isInGroupsPage = function() { + return gl.utils.getPagePath() === 'groups'; + }; + w.gl.utils.isInProjectPage = function() { + return gl.utils.getPagePath() === 'projects'; + }; + w.gl.utils.getProjectSlug = function() { + if (this.isInProjectPage()) { + return $('body').data('project'); + } else { + return null; + } + }; + w.gl.utils.getGroupSlug = function() { + if (this.isInGroupsPage()) { + return $('body').data('group'); + } else { + return null; + } + }; + + w.gl.utils.ajaxGet = function(url) { + return $.ajax({ + type: "GET", + url: url, + dataType: "script" + }); + }; + + w.gl.utils.extractLast = function(term) { + return this.split(term).pop(); + }; + + w.gl.utils.rstrip = function rstrip(val) { + if (val) { + return val.replace(/\s+$/, ''); + } else { + return val; + } + }; + + w.gl.utils.disableButtonIfEmptyField = function(field_selector, button_selector, event_name) { + event_name = event_name || 'input'; + var closest_submit, field, that; + that = this; + field = $(field_selector); + closest_submit = field.closest('form').find(button_selector); + if (this.rstrip(field.val()) === "") { + closest_submit.disable(); + } + return field.on(event_name, function() { + if (that.rstrip($(this).val()) === "") { + return closest_submit.disable(); + } else { + return closest_submit.enable(); + } + }); + }; + + // automatically adjust scroll position for hash urls taking the height of the navbar into account + // https://github.com/twitter/bootstrap/issues/1768 + w.gl.utils.handleLocationHash = function() { + var hash = w.gl.utils.getLocationHash(); + if (!hash) return; + + // This is required to handle non-unicode characters in hash + hash = decodeURIComponent(hash); + + var navbar = document.querySelector('.navbar-gitlab'); + var subnav = document.querySelector('.layout-nav'); + var fixedTabs = document.querySelector('.js-tabs-affix'); + + var adjustment = 0; + if (navbar) adjustment -= navbar.offsetHeight; + if (subnav) adjustment -= subnav.offsetHeight; + + // scroll to user-generated markdown anchor if we cannot find a match + if (document.getElementById(hash) === null) { + var target = document.getElementById('user-content-' + hash); + if (target && target.scrollIntoView) { + target.scrollIntoView(true); + window.scrollBy(0, adjustment); + } + } else { + // only adjust for fixedTabs when not targeting user-generated content + if (fixedTabs) { + adjustment -= fixedTabs.offsetHeight; + } + window.scrollBy(0, adjustment); + } + }; + + // Check if element scrolled into viewport from above or below + // Courtesy http://stackoverflow.com/a/7557433/414749 + w.gl.utils.isInViewport = function(el) { + var rect = el.getBoundingClientRect(); + + return ( + rect.top >= 0 && + rect.left >= 0 && + rect.bottom <= window.innerHeight && + rect.right <= window.innerWidth + ); + }; + + gl.utils.getPagePath = function(index) { + index = index || 0; + return $('body').data('page').split(':')[index]; + }; + + gl.utils.parseUrl = function (url) { + var parser = document.createElement('a'); + parser.href = url; + return parser; + }; + + gl.utils.parseUrlPathname = function (url) { + var parsedUrl = gl.utils.parseUrl(url); + // parsedUrl.pathname will return an absolute path for Firefox and a relative path for IE11 + // We have to make sure we always have an absolute path. + return parsedUrl.pathname.charAt(0) === '/' ? parsedUrl.pathname : '/' + parsedUrl.pathname; + }; + + gl.utils.getUrlParamsArray = function () { + // We can trust that each param has one & since values containing & will be encoded + // Remove the first character of search as it is always ? + return window.location.search.slice(1).split('&'); + }; + + gl.utils.isMetaKey = function(e) { + return e.metaKey || e.ctrlKey || e.altKey || e.shiftKey; + }; + + gl.utils.isMetaClick = function(e) { + // Identify following special clicks + // 1) Cmd + Click on Mac (e.metaKey) + // 2) Ctrl + Click on PC (e.ctrlKey) + // 3) Middle-click or Mouse Wheel Click (e.which is 2) + return e.metaKey || e.ctrlKey || e.which === 2; + }; + + gl.utils.scrollToElement = function($el) { + var top = $el.offset().top; + gl.navBarHeight = gl.navBarHeight || $('.navbar-gitlab').height(); + gl.navLinksHeight = gl.navLinksHeight || $('.nav-links').height(); + gl.mrTabsHeight = gl.mrTabsHeight || $('.merge-request-tabs').height(); + + return $('body, html').animate({ + scrollTop: top - (gl.navBarHeight + gl.navLinksHeight + gl.mrTabsHeight) + }, 200); + }; + + /** + this will take in the `name` of the param you want to parse in the url + if the name does not exist this function will return `null` + otherwise it will return the value of the param key provided + */ + w.gl.utils.getParameterByName = (name) => { + const url = window.location.href; + name = name.replace(/[[\]]/g, '\\$&'); + const regex = new RegExp(`[?&]${name}(=([^&#]*)|&|#|$)`); + const results = regex.exec(url); + if (!results) return null; + if (!results[2]) return ''; + return decodeURIComponent(results[2].replace(/\+/g, ' ')); + }; + + w.gl.utils.getSelectedFragment = () => { + const selection = window.getSelection(); + if (selection.rangeCount === 0) return null; + const documentFragment = selection.getRangeAt(0).cloneContents(); + if (documentFragment.textContent.length === 0) return null; + + return documentFragment; + }; + + w.gl.utils.insertText = (target, text) => { + // Firefox doesn't support `document.execCommand('insertText', false, text)` on textareas + + const selectionStart = target.selectionStart; + const selectionEnd = target.selectionEnd; + const value = target.value; + + const textBefore = value.substring(0, selectionStart); + const textAfter = value.substring(selectionEnd, value.length); + const newText = textBefore + text + textAfter; + + target.value = newText; + target.selectionStart = target.selectionEnd = selectionStart + text.length; + + // Trigger autosave + $(target).trigger('input'); + + // Trigger autosize + var event = document.createEvent('Event'); + event.initEvent('autosize:update', true, false); + target.dispatchEvent(event); + }; + + w.gl.utils.nodeMatchesSelector = (node, selector) => { + const matches = Element.prototype.matches || + Element.prototype.matchesSelector || + Element.prototype.mozMatchesSelector || + Element.prototype.msMatchesSelector || + Element.prototype.oMatchesSelector || + Element.prototype.webkitMatchesSelector; + + if (matches) { + return matches.call(node, selector); + } + + // IE11 doesn't support `node.matches(selector)` + + let parentNode = node.parentNode; + if (!parentNode) { + parentNode = document.createElement('div'); + node = node.cloneNode(true); + parentNode.appendChild(node); + } + + const matchingNodes = parentNode.querySelectorAll(selector); + return Array.prototype.indexOf.call(matchingNodes, node) !== -1; + }; + + /** + this will take in the headers from an API response and normalize them + this way we don't run into production issues when nginx gives us lowercased header keys + */ + w.gl.utils.normalizeHeaders = (headers) => { + const upperCaseHeaders = {}; + + Object.keys(headers).forEach((e) => { + upperCaseHeaders[e.toUpperCase()] = headers[e]; + }); + + return upperCaseHeaders; + }; + + /** + * Transforms a DOMStringMap into a plain object. + * + * @param {DOMStringMap} DOMStringMapObject + * @returns {Object} + */ + w.gl.utils.DOMStringMapToObject = DOMStringMapObject => Object.keys(DOMStringMapObject).reduce((acc, element) => { + acc[element] = DOMStringMapObject[element]; + return acc; + }, {}); + })(window); +}).call(this); diff --git a/app/assets/javascripts/lib/utils/custom_event_polyfill.js.es6 b/app/assets/javascripts/lib/utils/custom_event_polyfill.js.es6 deleted file mode 100644 index 5ae978010c9..00000000000 --- a/app/assets/javascripts/lib/utils/custom_event_polyfill.js.es6 +++ /dev/null @@ -1,12 +0,0 @@ -/** - * CustomEvent support for IE - */ -if (typeof window.CustomEvent !== 'function') { - window.CustomEvent = function CustomEvent(e, params) { - const options = params || { bubbles: false, cancelable: false, detail: undefined }; - const evt = document.createEvent('CustomEvent'); - evt.initCustomEvent(e, options.bubbles, options.cancelable, options.detail); - return evt; - }; - window.CustomEvent.prototype = window.Event.prototype; -} diff --git a/app/assets/javascripts/lib/utils/datetime_utility.js b/app/assets/javascripts/lib/utils/datetime_utility.js deleted file mode 100644 index e8e502694d6..00000000000 --- a/app/assets/javascripts/lib/utils/datetime_utility.js +++ /dev/null @@ -1,103 +0,0 @@ -/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-param-reassign, no-cond-assign, comma-dangle, no-unused-expressions, prefer-template, padded-blocks, max-len */ -/* global timeago */ -/* global dateFormat */ - -/*= require timeago */ -/*= require date.format */ - -(function() { - (function(w) { - var base; - if (w.gl == null) { - w.gl = {}; - } - if ((base = w.gl).utils == null) { - base.utils = {}; - } - w.gl.utils.days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']; - - w.gl.utils.formatDate = function(datetime) { - return dateFormat(datetime, 'mmm d, yyyy h:MMtt Z'); - }; - - w.gl.utils.getDayName = function(date) { - return this.days[date.getDay()]; - }; - - w.gl.utils.localTimeAgo = function($timeagoEls, setTimeago) { - if (setTimeago == null) { - setTimeago = true; - } - - $timeagoEls.filter(':not([data-timeago-rendered])').each(function() { - var $el = $(this); - $el.attr('title', gl.utils.formatDate($el.attr('datetime'))); - - if (setTimeago) { - // Recreate with custom template - $el.tooltip({ - template: '<div class="tooltip local-timeago" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>' - }); - } - - $el.attr('data-timeago-rendered', true); - gl.utils.renderTimeago($el); - }); - }; - - w.gl.utils.getTimeago = function() { - var locale = function(number, index) { - return [ - ['less than a minute ago', 'a while'], - ['less than a minute ago', 'in %s seconds'], - ['about a minute ago', 'in 1 minute'], - ['%s minutes ago', 'in %s minutes'], - ['about an hour ago', 'in 1 hour'], - ['about %s hours ago', 'in %s hours'], - ['a day ago', 'in 1 day'], - ['%s days ago', 'in %s days'], - ['a week ago', 'in 1 week'], - ['%s weeks ago', 'in %s weeks'], - ['a month ago', 'in 1 month'], - ['%s months ago', 'in %s months'], - ['a year ago', 'in 1 year'], - ['%s years ago', 'in %s years'] - ][index]; - }; - - timeago.register('gl_en', locale); - return timeago(); - }; - - w.gl.utils.timeFor = function(time, suffix, expiredLabel) { - var timefor; - if (!time) { - return ''; - } - suffix || (suffix = 'remaining'); - expiredLabel || (expiredLabel = 'Past due'); - timefor = gl.utils.getTimeago().format(time).replace('in', ''); - if (timefor.indexOf('ago') > -1) { - timefor = expiredLabel; - } else { - timefor = timefor.trim() + ' ' + suffix; - } - return timefor; - }; - - w.gl.utils.renderTimeago = function($element) { - var timeagoInstance = gl.utils.getTimeago(); - timeagoInstance.render($element, 'gl_en'); - }; - - w.gl.utils.getDayDifference = function(a, b) { - var millisecondsPerDay = 1000 * 60 * 60 * 24; - var date1 = Date.UTC(a.getFullYear(), a.getMonth(), a.getDate()); - var date2 = Date.UTC(b.getFullYear(), b.getMonth(), b.getDate()); - - return Math.floor((date2 - date1) / millisecondsPerDay); - }; - - })(window); - -}).call(this); diff --git a/app/assets/javascripts/lib/utils/datetime_utility.js.es6 b/app/assets/javascripts/lib/utils/datetime_utility.js.es6 new file mode 100644 index 00000000000..56300926188 --- /dev/null +++ b/app/assets/javascripts/lib/utils/datetime_utility.js.es6 @@ -0,0 +1,126 @@ +/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-param-reassign, no-cond-assign, comma-dangle, no-unused-expressions, prefer-template, max-len */ +/* global timeago */ +/* global dateFormat */ + +window.timeago = require('vendor/timeago'); +window.dateFormat = require('vendor/date.format'); + +(function() { + (function(w) { + var base; + var timeagoInstance; + + if (w.gl == null) { + w.gl = {}; + } + if ((base = w.gl).utils == null) { + base.utils = {}; + } + w.gl.utils.days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']; + + w.gl.utils.formatDate = function(datetime) { + return dateFormat(datetime, 'mmm d, yyyy h:MMtt Z'); + }; + + w.gl.utils.getDayName = function(date) { + return this.days[date.getDay()]; + }; + + w.gl.utils.localTimeAgo = function($timeagoEls, setTimeago = true) { + $timeagoEls.each((i, el) => { + el.setAttribute('title', gl.utils.formatDate(el.getAttribute('datetime'))); + + if (setTimeago) { + // Recreate with custom template + $(el).tooltip({ + template: '<div class="tooltip local-timeago" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>' + }); + } + + el.classList.add('js-timeago-render'); + }); + + gl.utils.renderTimeago($timeagoEls); + }; + + w.gl.utils.getTimeago = function() { + var locale; + + if (!timeagoInstance) { + locale = function(number, index) { + return [ + ['less than a minute ago', 'a while'], + ['less than a minute ago', 'in %s seconds'], + ['about a minute ago', 'in 1 minute'], + ['%s minutes ago', 'in %s minutes'], + ['about an hour ago', 'in 1 hour'], + ['about %s hours ago', 'in %s hours'], + ['a day ago', 'in 1 day'], + ['%s days ago', 'in %s days'], + ['a week ago', 'in 1 week'], + ['%s weeks ago', 'in %s weeks'], + ['a month ago', 'in 1 month'], + ['%s months ago', 'in %s months'], + ['a year ago', 'in 1 year'], + ['%s years ago', 'in %s years'] + ][index]; + }; + + timeago.register('gl_en', locale); + timeagoInstance = timeago(); + } + + return timeagoInstance; + }; + + w.gl.utils.timeFor = function(time, suffix, expiredLabel) { + var timefor; + if (!time) { + return ''; + } + suffix || (suffix = 'remaining'); + expiredLabel || (expiredLabel = 'Past due'); + timefor = gl.utils.getTimeago().format(time).replace('in', ''); + if (timefor.indexOf('ago') > -1) { + timefor = expiredLabel; + } else { + timefor = timefor.trim() + ' ' + suffix; + } + return timefor; + }; + + w.gl.utils.cachedTimeagoElements = []; + w.gl.utils.renderTimeago = function($els) { + if (!$els && !w.gl.utils.cachedTimeagoElements.length) { + w.gl.utils.cachedTimeagoElements = [].slice.call(document.querySelectorAll('.js-timeago-render')); + } else if ($els) { + w.gl.utils.cachedTimeagoElements = w.gl.utils.cachedTimeagoElements.concat($els.toArray()); + } + + w.gl.utils.cachedTimeagoElements.forEach(gl.utils.updateTimeagoText); + }; + + w.gl.utils.updateTimeagoText = function(el) { + const timeago = gl.utils.getTimeago(); + const formattedDate = timeago.format(el.getAttribute('datetime'), 'gl_en'); + + if (el.textContent !== formattedDate) { + el.textContent = formattedDate; + } + }; + + w.gl.utils.initTimeagoTimeout = function() { + gl.utils.renderTimeago(); + + gl.utils.timeagoTimeout = setTimeout(gl.utils.initTimeagoTimeout, 1000); + }; + + w.gl.utils.getDayDifference = function(a, b) { + var millisecondsPerDay = 1000 * 60 * 60 * 24; + var date1 = Date.UTC(a.getFullYear(), a.getMonth(), a.getDate()); + var date2 = Date.UTC(b.getFullYear(), b.getMonth(), b.getDate()); + + return Math.floor((date2 - date1) / millisecondsPerDay); + }; + })(window); +}).call(this); diff --git a/app/assets/javascripts/lib/utils/emoji_aliases.js.erb b/app/assets/javascripts/lib/utils/emoji_aliases.js.erb deleted file mode 100644 index aeb86c9fa5b..00000000000 --- a/app/assets/javascripts/lib/utils/emoji_aliases.js.erb +++ /dev/null @@ -1,6 +0,0 @@ -(function() { - gl.emojiAliases = function() { - return JSON.parse('<%= Gitlab::AwardEmoji.aliases.to_json %>'); - }; - -}).call(this); diff --git a/app/assets/javascripts/lib/utils/notify.js b/app/assets/javascripts/lib/utils/notify.js index 3c9ad0e67c8..6d5979603b9 100644 --- a/app/assets/javascripts/lib/utils/notify.js +++ b/app/assets/javascripts/lib/utils/notify.js @@ -1,4 +1,4 @@ -/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, one-var, one-var-declaration-per-line, consistent-return, prefer-arrow-callback, no-return-assign, object-shorthand, comma-dangle, no-param-reassign, padded-blocks, max-len */ +/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, one-var, one-var-declaration-per-line, consistent-return, prefer-arrow-callback, no-return-assign, object-shorthand, comma-dangle, no-param-reassign, max-len */ (function() { (function(w) { @@ -44,5 +44,4 @@ w.notify = notifyMe; return w.notifyPermissions = notifyPermissions; })(window); - }).call(this); diff --git a/app/assets/javascripts/lib/utils/pretty_time.js.es6 b/app/assets/javascripts/lib/utils/pretty_time.js.es6 index ccaf447eb0b..ae397212e55 100644 --- a/app/assets/javascripts/lib/utils/pretty_time.js.es6 +++ b/app/assets/javascripts/lib/utils/pretty_time.js.es6 @@ -4,13 +4,13 @@ * stringifyTime condensed or non-condensed, abbreviateTimelengths) * */ - class PrettyTime { - + const utils = window.gl.utils = gl.utils || {}; + const prettyTime = utils.prettyTime = { /* * Accepts seconds and returns a timeObject { weeks: #, days: #, hours: #, minutes: # } * Seconds can be negative or positive, zero or non-zero. */ - static parseSeconds(seconds) { + parseSeconds(seconds) { const DAYS_PER_WEEK = 5; const HOURS_PER_DAY = 8; const MINUTES_PER_HOUR = 60; @@ -24,7 +24,7 @@ minutes: 1, }; - let unorderedMinutes = PrettyTime.secondsToMinutes(seconds); + let unorderedMinutes = prettyTime.secondsToMinutes(seconds); return _.mapObject(timePeriodConstraints, (minutesPerPeriod) => { const periodCount = Math.floor(unorderedMinutes / minutesPerPeriod); @@ -33,35 +33,33 @@ return periodCount; }); - } + }, /* * Accepts a timeObject and returns a condensed string representation of it * (e.g. '1w 2d 3h 1m' or '1h 30m'). Zero value units are not included. */ - static stringifyTime(timeObject) { + stringifyTime(timeObject) { const reducedTime = _.reduce(timeObject, (memo, unitValue, unitName) => { const isNonZero = !!unitValue; return isNonZero ? `${memo} ${unitValue}${unitName.charAt(0)}` : memo; }, '').trim(); return reducedTime.length ? reducedTime : '0m'; - } + }, /* * Accepts a time string of any size (e.g. '1w 2d 3h 5m' or '1w 2d') and returns * the first non-zero unit/value pair. */ - static abbreviateTime(timeStr) { + abbreviateTime(timeStr) { return timeStr.split(' ') .filter(unitStr => unitStr.charAt(0) !== '0')[0]; - } + }, - static secondsToMinutes(seconds) { + secondsToMinutes(seconds) { return Math.abs(seconds / 60); - } - } - - gl.PrettyTime = PrettyTime; + }, + }; })(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/lib/utils/text_utility.js b/app/assets/javascripts/lib/utils/text_utility.js index ac44b81ee22..d9370db0cf2 100644 --- a/app/assets/javascripts/lib/utils/text_utility.js +++ b/app/assets/javascripts/lib/utils/text_utility.js @@ -1,4 +1,5 @@ -/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-param-reassign, no-cond-assign, quotes, semi, one-var, one-var-declaration-per-line, operator-assignment, no-else-return, prefer-template, prefer-arrow-callback, no-empty, max-len, consistent-return, no-unused-vars, no-return-assign, padded-blocks, max-len */ +/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-param-reassign, no-cond-assign, quotes, one-var, one-var-declaration-per-line, operator-assignment, no-else-return, prefer-template, prefer-arrow-callback, no-empty, max-len, consistent-return, no-unused-vars, no-return-assign, max-len */ + (function() { (function(w) { var base; @@ -10,13 +11,28 @@ } gl.text.addDelimiter = function(text) { return text ? text.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",") : text; - } + }; gl.text.randomString = function() { return Math.random().toString(36).substring(7); }; gl.text.replaceRange = function(s, start, end, substitute) { return s.substring(0, start) + substitute + s.substring(end); }; + gl.text.getTextWidth = function(text, font) { + /** + * Uses canvas.measureText to compute and return the width of the given text of given font in pixels. + * + * @param {String} text The text to be rendered. + * @param {String} font The css font descriptor that text is to be rendered with (e.g. "bold 14px verdana"). + * + * @see http://stackoverflow.com/questions/118241/calculate-text-width-with-javascript/21015393#21015393 + */ + // re-use canvas object for better performance + var canvas = gl.text.getTextWidth.canvas || (gl.text.getTextWidth.canvas = document.createElement('canvas')); + var context = canvas.getContext('2d'); + context.font = font; + return context.measureText(text).width; + }; gl.text.selectedText = function(text, textarea) { return text.substring(textarea.selectionStart, textarea.selectionEnd); }; @@ -44,9 +60,25 @@ } }; gl.text.insertText = function(textArea, text, tag, blockTag, selected, wrap) { - var insertText, inserted, selectedSplit, startChar; + var insertText, inserted, selectedSplit, startChar, removedLastNewLine, removedFirstNewLine; + removedLastNewLine = false; + removedFirstNewLine = false; + + // Remove the first newline + if (selected.indexOf('\n') === 0) { + removedFirstNewLine = true; + selected = selected.replace(/\n+/, ''); + } + + // Remove the last newline + if (textArea.selectionEnd - textArea.selectionStart > selected.replace(/\n$/, '').length) { + removedLastNewLine = true; + selected = selected.replace(/\n$/, ''); + } + selectedSplit = selected.split('\n'); startChar = !wrap && textArea.selectionStart > 0 ? '\n' : ''; + if (selectedSplit.length > 1 && (!wrap || (blockTag != null))) { if (blockTag != null) { insertText = this.blockTagText(text, textArea, blockTag, selected); @@ -62,6 +94,15 @@ } else { insertText = "" + startChar + tag + selected + (wrap ? tag : ' '); } + + if (removedFirstNewLine) { + insertText = '\n' + insertText; + } + + if (removedLastNewLine) { + insertText += '\n'; + } + if (document.queryCommandSupported('insertText')) { inserted = document.execCommand('insertText', false, insertText); } @@ -74,9 +115,9 @@ document.execCommand("ms-endUndoUnit"); } catch (error) {} } - return this.moveCursor(textArea, tag, wrap); + return this.moveCursor(textArea, tag, wrap, removedLastNewLine); }; - gl.text.moveCursor = function(textArea, tag, wrapped) { + gl.text.moveCursor = function(textArea, tag, wrapped, removedLastNewLine) { var pos; if (!textArea.setSelectionRange) { return; @@ -87,6 +128,11 @@ } else { pos = textArea.selectionStart; } + + if (removedLastNewLine) { + pos -= 1; + } + return textArea.setSelectionRange(pos, pos); } }; @@ -114,10 +160,12 @@ }; gl.text.humanize = function(string) { return string.charAt(0).toUpperCase() + string.replace(/_/g, ' ').slice(1); - } + }; + gl.text.pluralize = function(str, count) { + return str + (count > 1 || count === 0 ? 's' : ''); + }; return gl.text.truncate = function(string, maxLength) { return string.substr(0, (maxLength - 3)) + '...'; }; })(window); - }).call(this); diff --git a/app/assets/javascripts/lib/utils/type_utility.js b/app/assets/javascripts/lib/utils/type_utility.js index 961859dfb5b..6d813d61601 100644 --- a/app/assets/javascripts/lib/utils/type_utility.js +++ b/app/assets/javascripts/lib/utils/type_utility.js @@ -1,4 +1,4 @@ -/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-param-reassign, no-cond-assign, no-return-assign, padded-blocks, max-len */ +/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-param-reassign, no-cond-assign, no-return-assign, max-len */ (function() { (function(w) { var base; @@ -12,5 +12,4 @@ return (obj != null) && (obj.constructor === Object); }; })(window); - }).call(this); diff --git a/app/assets/javascripts/lib/utils/url_utility.js b/app/assets/javascripts/lib/utils/url_utility.js.es6 index 6872186cd7f..a1558b371f0 100644 --- a/app/assets/javascripts/lib/utils/url_utility.js +++ b/app/assets/javascripts/lib/utils/url_utility.js.es6 @@ -1,4 +1,4 @@ -/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-param-reassign, no-cond-assign, one-var, one-var-declaration-per-line, no-void, no-plusplus, guard-for-in, no-restricted-syntax, prefer-template, quotes, padded-blocks, max-len */ +/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-param-reassign, no-cond-assign, one-var, one-var-declaration-per-line, no-void, guard-for-in, no-restricted-syntax, prefer-template, quotes, max-len */ (function() { (function(w) { var base; @@ -22,7 +22,7 @@ if (sParameterName[0] === sParam) { values.push(sParameterName[1].replace(/\+/g, ' ')); } - i++; + i += 1; } return values; }; @@ -57,7 +57,7 @@ return ((function() { var j, len, results; results = []; - for (j = 0, len = urlVariables.length; j < len; j++) { + for (j = 0, len = urlVariables.length; j < len; j += 1) { variables = urlVariables[j]; if (variables.indexOf(param) === -1) { results.push(variables); @@ -76,6 +76,11 @@ hashIndex = url.indexOf('#'); return hashIndex === -1 ? null : url.substring(hashIndex + 1); }; - })(window); + w.gl.utils.refreshCurrentPage = () => gl.utils.visitUrl(document.location.href); + + w.gl.utils.visitUrl = (url) => { + document.location.href = url; + }; + })(window); }).call(this); diff --git a/app/assets/javascripts/lib/vue_resource.js.es6 b/app/assets/javascripts/lib/vue_resource.js.es6 new file mode 100644 index 00000000000..49babdea2e1 --- /dev/null +++ b/app/assets/javascripts/lib/vue_resource.js.es6 @@ -0,0 +1,2 @@ +window.Vue = require('vue'); +window.Vue.use(require('vue-resource')); diff --git a/app/assets/javascripts/line_highlighter.js b/app/assets/javascripts/line_highlighter.js index 9af89b79f84..d7137ec63e4 100644 --- a/app/assets/javascripts/line_highlighter.js +++ b/app/assets/javascripts/line_highlighter.js @@ -1,10 +1,10 @@ -/* eslint-disable func-names, space-before-function-paren, no-var, space-before-blocks, prefer-rest-params, wrap-iife, no-use-before-define, no-underscore-dangle, no-param-reassign, prefer-template, quotes, comma-dangle, prefer-arrow-callback, consistent-return, one-var, one-var-declaration-per-line, spaced-comment, radix, no-else-return, max-len, no-plusplus, padded-blocks */ +/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, no-use-before-define, no-underscore-dangle, no-param-reassign, prefer-template, quotes, comma-dangle, prefer-arrow-callback, consistent-return, one-var, one-var-declaration-per-line, no-else-return, max-len */ // LineHighlighter // // Handles single- and multi-line selection and highlight for blob views. // -/*= require jquery.scrollTo */ +require('vendor/jquery.scrollTo'); // // ### Example Markup @@ -31,7 +31,7 @@ // </div> // (function() { - var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; + var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; }; this.LineHighlighter = (function() { // CSS class applied to highlighted lines @@ -74,8 +74,9 @@ // If not done this way, the line number anchor will sometimes keep its // active state even when the event is cancelled, resulting in an ugly border // around the link and/or a persisted underline text decoration. - return $('#blob-content-holder').on('click', 'a[data-line-number]', function(event) { - return event.preventDefault(); + $('#blob-content-holder').on('click', 'a[data-line-number]', function(event) { + event.preventDefault(); + event.stopPropagation(); }); }; @@ -119,11 +120,11 @@ // Returns an Array LineHighlighter.prototype.hashToRange = function(hash) { var first, last, matches; - //?L(\d+)(?:-(\d+))?$/) + // ?L(\d+)(?:-(\d+))?$/) matches = hash.match(/^#?L(\d+)(?:-(\d+))?$/); if (matches && matches.length) { - first = parseInt(matches[1]); - last = matches[2] ? parseInt(matches[2]) : null; + first = parseInt(matches[1], 10); + last = matches[2] ? parseInt(matches[2], 10) : null; return [first, last]; } else { return [null, null]; @@ -144,7 +145,7 @@ var i, lineNumber, ref, ref1, results; if (range[1]) { results = []; - for (lineNumber = i = ref = range[0], ref1 = range[1]; ref <= ref1 ? i <= ref1 : i >= ref1; lineNumber = ref <= ref1 ? ++i : --i) { + for (lineNumber = i = ref = range[0], ref1 = range[1]; ref <= ref1 ? i <= ref1 : i >= ref1; lineNumber = ref <= ref1 ? (i += 1) : (i -= 1)) { results.push(this.highlightLine(lineNumber)); } return results; @@ -170,7 +171,6 @@ // This method is stubbed in tests. LineHighlighter.prototype.__setLocationHash__ = function(value) { return history.pushState({ - turbolinks: false, url: value // We're using pushState instead of assigning location.hash directly to // prevent the page from scrolling on the hashchange event @@ -178,7 +178,5 @@ }; return LineHighlighter; - })(); - }).call(this); diff --git a/app/assets/javascripts/logo.js b/app/assets/javascripts/logo.js index 0ae6df311bb..1b0d0768db8 100644 --- a/app/assets/javascripts/logo.js +++ b/app/assets/javascripts/logo.js @@ -1,15 +1,7 @@ -/* eslint-disable func-names, space-before-function-paren, prefer-arrow-callback, padded-blocks */ -/* global Turbolinks */ +/* eslint-disable func-names, space-before-function-paren, prefer-arrow-callback */ (function() { - Turbolinks.enableProgressBar(); - - $(document).on('page:fetch', function() { + window.addEventListener('beforeunload', function() { $('.tanuki-logo').addClass('animate'); }); - - $(document).on('page:change', function() { - $('.tanuki-logo').removeClass('animate'); - }); - }).call(this); diff --git a/app/assets/javascripts/member_expiration_date.js b/app/assets/javascripts/member_expiration_date.js.es6 index 7741cd29793..bf6c0ec2798 100644 --- a/app/assets/javascripts/member_expiration_date.js +++ b/app/assets/javascripts/member_expiration_date.js.es6 @@ -1,30 +1,29 @@ -/* eslint-disable func-names, space-before-function-paren, vars-on-top, no-var, object-shorthand, comma-dangle, max-len */ -(function() { +(() => { // Add datepickers to all `js-access-expiration-date` elements. If those elements are // children of an element with the `clearable-input` class, and have a sibling // `js-clear-input` element, then show that element when there is a value in the // datepicker, and make clicking on that element clear the field. // - gl.MemberExpirationDate = function() { + window.gl = window.gl || {}; + gl.MemberExpirationDate = (selector = '.js-access-expiration-date') => { function toggleClearInput() { $(this).closest('.clearable-input').toggleClass('has-value', $(this).val() !== ''); } - - var inputs = $('.js-access-expiration-date'); + const inputs = $(selector); inputs.datepicker({ dateFormat: 'yy-mm-dd', minDate: 1, - onSelect: function () { + onSelect: function onSelect() { $(this).trigger('change'); toggleClearInput.call(this); - } + }, }); - inputs.next('.js-clear-input').on('click', function(event) { + inputs.next('.js-clear-input').on('click', function clicked(event) { event.preventDefault(); - var input = $(this).closest('.clearable-input').find('.js-access-expiration-date'); + const input = $(this).closest('.clearable-input').find(selector); input.datepicker('setDate', null) .trigger('change'); toggleClearInput.call(input); diff --git a/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js.es6 b/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js.es6 index f95b079c972..c7e78fed8fe 100644 --- a/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js.es6 +++ b/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js.es6 @@ -1,10 +1,9 @@ -/* eslint-disable comma-dangle, quote-props, no-useless-computed-key, object-shorthand, prefer-const, no-new, padded-blocks, no-param-reassign, semi, max-len */ +/* eslint-disable comma-dangle, quote-props, no-useless-computed-key, object-shorthand, no-new, no-param-reassign, max-len */ /* global Vue */ /* global ace */ /* global Flash */ ((global) => { - global.mergeConflicts = global.mergeConflicts || {}; global.mergeConflicts.diffFileEditor = Vue.extend({ @@ -19,7 +18,7 @@ loading: false, fileLoaded: false, originalContent: '', - } + }; }, computed: { classObject() { @@ -51,8 +50,8 @@ $.get(this.file.content_path) .done((file) => { - let content = this.$el.querySelector('pre'); - let fileContent = document.createTextNode(file.content); + const content = this.$el.querySelector('pre'); + const fileContent = document.createTextNode(file.content); content.textContent = fileContent.textContent; @@ -94,5 +93,4 @@ } } }); - })(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/merge_conflicts/components/inline_conflict_lines.js.es6 b/app/assets/javascripts/merge_conflicts/components/inline_conflict_lines.js.es6 index 74544b7d0c7..240c8f98932 100644 --- a/app/assets/javascripts/merge_conflicts/components/inline_conflict_lines.js.es6 +++ b/app/assets/javascripts/merge_conflicts/components/inline_conflict_lines.js.es6 @@ -1,8 +1,7 @@ -/* eslint-disable padded-blocks, no-param-reassign, comma-dangle */ +/* eslint-disable no-param-reassign, comma-dangle */ /* global Vue */ ((global) => { - global.mergeConflicts = global.mergeConflicts || {}; global.mergeConflicts.inlineConflictLines = Vue.extend({ @@ -11,5 +10,4 @@ }, mixins: [global.mergeConflicts.utils, global.mergeConflicts.actions], }); - })(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/merge_conflicts/components/parallel_conflict_lines.js.es6 b/app/assets/javascripts/merge_conflicts/components/parallel_conflict_lines.js.es6 index 78c00c31c16..97753c50b60 100644 --- a/app/assets/javascripts/merge_conflicts/components/parallel_conflict_lines.js.es6 +++ b/app/assets/javascripts/merge_conflicts/components/parallel_conflict_lines.js.es6 @@ -1,8 +1,7 @@ -/* eslint-disable padded-blocks, no-param-reassign, comma-dangle */ +/* eslint-disable no-param-reassign, comma-dangle */ /* global Vue */ ((global) => { - global.mergeConflicts = global.mergeConflicts || {}; global.mergeConflicts.parallelConflictLines = Vue.extend({ @@ -26,5 +25,4 @@ </table> `, }); - })(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/merge_conflicts/merge_conflict_service.js.es6 b/app/assets/javascripts/merge_conflicts/merge_conflict_service.js.es6 index 8df3170edac..c012b77e0bf 100644 --- a/app/assets/javascripts/merge_conflicts/merge_conflict_service.js.es6 +++ b/app/assets/javascripts/merge_conflicts/merge_conflict_service.js.es6 @@ -1,4 +1,4 @@ -/* eslint-disable no-param-reassign, comma-dangle, no-extra-semi, padded-blocks */ +/* eslint-disable no-param-reassign, comma-dangle */ ((global) => { global.mergeConflicts = global.mergeConflicts || {}; @@ -25,8 +25,7 @@ method: 'POST' }); } - }; + } global.mergeConflicts.mergeConflictsService = mergeConflictsService; - })(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/merge_conflicts/merge_conflict_store.js.es6 b/app/assets/javascripts/merge_conflicts/merge_conflict_store.js.es6 index 53b44007510..74587df22c5 100644 --- a/app/assets/javascripts/merge_conflicts/merge_conflict_store.js.es6 +++ b/app/assets/javascripts/merge_conflicts/merge_conflict_store.js.es6 @@ -1,4 +1,4 @@ -/* eslint-disable comma-dangle, object-shorthand, no-dupe-keys, no-param-reassign, no-plusplus, camelcase, prefer-const, no-nested-ternary, no-continue, semi, func-call-spacing, no-spaced-func, padded-blocks, max-len */ +/* eslint-disable comma-dangle, object-shorthand, no-param-reassign, camelcase, no-nested-ternary, no-continue, max-len */ /* global Cookies */ /* global Vue */ @@ -40,7 +40,6 @@ commitMessage: data.commit_message, sourceBranch: data.source_branch, targetBranch: data.target_branch, - commitMessage: data.commit_message, shortCommitSha: data.commit_sha.slice(0, 7), }; }, @@ -89,7 +88,7 @@ this.decorateLineForInlineView(line, id, conflict); file.inlineLines.push(line); - }) + }); if (conflict) { file.inlineLines.push(this.getOriginHeaderLine(id)); @@ -121,7 +120,7 @@ } else { const lineType = type || 'context'; - linesObj.left.push (this.getLineForParallelView(line, id, lineType)); + linesObj.left.push(this.getLineForParallelView(line, id, lineType)); linesObj.right.push(this.getLineForParallelView(line, id, lineType, true)); } }); @@ -129,7 +128,7 @@ this.checkLineLengths(linesObj); }); - for (let i = 0, len = linesObj.left.length; i < len; i++) { + for (let i = 0, len = linesObj.left.length; i < len; i += 1) { file.parallelLines.push([ linesObj.right[i], linesObj.left[i] @@ -162,11 +161,11 @@ if (file.type === CONFLICT_TYPES.TEXT) { file.sections.forEach((section) => { if (section.conflict) { - count++; + count += 1; } }); } else { - count++; + count += 1; } }); @@ -252,17 +251,17 @@ }, checkLineLengths(linesObj) { - let { left, right } = linesObj; + const { left, right } = linesObj; if (left.length !== right.length) { if (left.length > right.length) { const diff = left.length - right.length; - for (let i = 0; i < diff; i++) { + for (let i = 0; i < diff; i += 1) { right.push({ lineType: 'emptyLine', richText: '' }); } } else { const diff = right.length - left.length; - for (let i = 0; i < diff; i++) { + for (let i = 0; i < diff; i += 1) { left.push({ lineType: 'emptyLine', richText: '' }); } } @@ -316,32 +315,31 @@ const hasCommitMessage = $.trim(this.state.conflictsData.commitMessage).length; let unresolved = 0; - for (let i = 0, l = files.length; i < l; i++) { - let file = files[i]; + for (let i = 0, l = files.length; i < l; i += 1) { + const file = files[i]; if (file.resolveMode === INTERACTIVE_RESOLVE_MODE) { let numberConflicts = 0; - let resolvedConflicts = Object.keys(file.resolutionData).length + const resolvedConflicts = Object.keys(file.resolutionData).length; // We only check for conflicts type 'text' // since conflicts `text_editor` can´t be resolved in interactive mode if (file.type === CONFLICT_TYPES.TEXT) { - for (let j = 0, k = file.sections.length; j < k; j++) { + for (let j = 0, k = file.sections.length; j < k; j += 1) { if (file.sections[j].conflict) { - numberConflicts++; + numberConflicts += 1; } } if (resolvedConflicts !== numberConflicts) { - unresolved++; + unresolved += 1; } } } else if (file.resolveMode === EDIT_RESOLVE_MODE) { - // Unlikely to happen since switching to Edit mode saves content automatically. // Checking anyway in case the save strategy changes in the future if (!file.content) { - unresolved++; + unresolved += 1; continue; } } @@ -366,15 +364,12 @@ }; this.state.conflictsData.files.forEach((file) => { - let addFile; - - addFile = { + const addFile = { old_path: file.old_path, new_path: file.new_path }; if (file.type === CONFLICT_TYPES.TEXT) { - // Submit only one data for type of editing if (file.resolveMode === INTERACTIVE_RESOLVE_MODE) { addFile.sections = file.resolutionData; @@ -435,5 +430,4 @@ return this.state.conflictsData.files.some(f => f.type === CONFLICT_TYPES.TEXT); } }; - })(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js.es6 b/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js.es6 index 83520702f9b..653e52fb6bf 100644 --- a/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js.es6 +++ b/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js.es6 @@ -1,15 +1,15 @@ -/* eslint-disable new-cap, comma-dangle, no-new, semi */ +/* eslint-disable new-cap, comma-dangle, no-new */ /* global Vue */ /* global Flash */ -//= require vue -//= require ./merge_conflict_store -//= require ./merge_conflict_service -//= require ./mixins/line_conflict_utils -//= require ./mixins/line_conflict_actions -//= require ./components/diff_file_editor -//= require ./components/inline_conflict_lines -//= require ./components/parallel_conflict_lines +window.Vue = require('vue'); +require('./merge_conflict_store'); +require('./merge_conflict_service'); +require('./mixins/line_conflict_utils'); +require('./mixins/line_conflict_actions'); +require('./components/diff_file_editor'); +require('./components/inline_conflict_lines'); +require('./components/parallel_conflict_lines'); $(() => { const INTERACTIVE_RESOLVE_MODE = 'interactive'; @@ -29,10 +29,10 @@ $(() => { 'parallel-conflict-lines': gl.mergeConflicts.parallelConflictLines }, computed: { - conflictsCountText() { return mergeConflictsStore.getConflictsCountText() }, - readyToCommit() { return mergeConflictsStore.isReadyToCommit() }, - commitButtonText() { return mergeConflictsStore.getCommitButtonText() }, - showDiffViewTypeSwitcher() { return mergeConflictsStore.fileTextTypePresent() } + conflictsCountText() { return mergeConflictsStore.getConflictsCountText(); }, + readyToCommit() { return mergeConflictsStore.isReadyToCommit(); }, + commitButtonText() { return mergeConflictsStore.getCommitButtonText(); }, + showDiffViewTypeSwitcher() { return mergeConflictsStore.fileTextTypePresent(); } }, created() { mergeConflictsService @@ -88,5 +88,5 @@ $(() => { }); } } - }) + }); }); diff --git a/app/assets/javascripts/merge_conflicts/mixins/line_conflict_actions.js.es6 b/app/assets/javascripts/merge_conflicts/mixins/line_conflict_actions.js.es6 index e89b35d5407..53e000d7e9e 100644 --- a/app/assets/javascripts/merge_conflicts/mixins/line_conflict_actions.js.es6 +++ b/app/assets/javascripts/merge_conflicts/mixins/line_conflict_actions.js.es6 @@ -1,4 +1,4 @@ -/* eslint-disable no-param-reassign, comma-dangle, padded-blocks */ +/* eslint-disable no-param-reassign, comma-dangle */ ((global) => { global.mergeConflicts = global.mergeConflicts || {}; @@ -10,5 +10,4 @@ } } }; - })(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/merge_conflicts/mixins/line_conflict_utils.js.es6 b/app/assets/javascripts/merge_conflicts/mixins/line_conflict_utils.js.es6 index a4aca85d460..0f475f62ee6 100644 --- a/app/assets/javascripts/merge_conflicts/mixins/line_conflict_utils.js.es6 +++ b/app/assets/javascripts/merge_conflicts/mixins/line_conflict_utils.js.es6 @@ -1,4 +1,4 @@ -/* eslint-disable no-param-reassign, quote-props, comma-dangle, padded-blocks */ +/* eslint-disable no-param-reassign, quote-props, comma-dangle */ ((global) => { global.mergeConflicts = global.mergeConflicts || {}; @@ -16,5 +16,4 @@ } } }; - })(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/merge_request.js b/app/assets/javascripts/merge_request.js index 244c2f6746c..8762ec35b80 100644 --- a/app/assets/javascripts/merge_request.js +++ b/app/assets/javascripts/merge_request.js @@ -1,12 +1,12 @@ -/* eslint-disable func-names, space-before-function-paren, no-var, space-before-blocks, prefer-rest-params, wrap-iife, quotes, no-underscore-dangle, one-var, one-var-declaration-per-line, consistent-return, dot-notation, quote-props, comma-dangle, object-shorthand, padded-blocks, max-len, prefer-arrow-callback */ +/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, quotes, no-underscore-dangle, one-var, one-var-declaration-per-line, consistent-return, dot-notation, quote-props, comma-dangle, object-shorthand, max-len, prefer-arrow-callback */ /* global MergeRequestTabs */ -/*= require jquery.waitforimages */ -/*= require task_list */ -/*= require merge_request_tabs */ +require('vendor/jquery.waitforimages'); +require('vendor/task_list'); +require('./merge_request_tabs'); (function() { - var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; + var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; }; this.MergeRequest = (function() { function MergeRequest(opts) { @@ -110,9 +110,8 @@ }; MergeRequest.prototype.initCommitMessageListeners = function() { - var textarea = $('textarea.js-commit-message'); - - $('a.js-with-description-link').on('click', function(e) { + $(document).on('click', 'a.js-with-description-link', function(e) { + var textarea = $('textarea.js-commit-message'); e.preventDefault(); textarea.val(textarea.data('messageWithDescription')); @@ -120,7 +119,8 @@ $('p.js-without-description-hint').show(); }); - $('a.js-without-description-link').on('click', function(e) { + $(document).on('click', 'a.js-without-description-link', function(e) { + var textarea = $('textarea.js-commit-message'); e.preventDefault(); textarea.val(textarea.data('messageWithoutDescription')); @@ -130,7 +130,5 @@ }; return MergeRequest; - })(); - }).call(this); diff --git a/app/assets/javascripts/merge_request_tabs.js.es6 b/app/assets/javascripts/merge_request_tabs.js.es6 index 3ec0f1fd613..af1ba9ecaf3 100644 --- a/app/assets/javascripts/merge_request_tabs.js.es6 +++ b/app/assets/javascripts/merge_request_tabs.js.es6 @@ -1,11 +1,11 @@ /* eslint-disable no-new, class-methods-use-this */ /* global Breakpoints */ /* global Cookies */ -/* global DiffNotesApp */ /* global Flash */ -/*= require js.cookie */ -/*= require breakpoints */ +require('./breakpoints'); +window.Cookies = require('vendor/js.cookie'); +require('./flash'); /* eslint-disable max-len */ // MergeRequestTabs @@ -59,16 +59,12 @@ class MergeRequestTabs { - constructor({ action, setUrl, buildsLoaded, stubLocation } = {}) { + constructor({ action, setUrl, stubLocation } = {}) { this.diffsLoaded = false; - this.buildsLoaded = false; - this.pipelinesLoaded = false; this.commitsLoaded = false; this.fixedLayoutPref = null; this.setUrl = setUrl !== undefined ? setUrl : true; - this.buildsLoaded = buildsLoaded || false; - this.setCurrentAction = this.setCurrentAction.bind(this); this.tabShown = this.tabShown.bind(this); this.showTab = this.showTab.bind(this); @@ -86,12 +82,18 @@ $(document) .on('shown.bs.tab', '.merge-request-tabs a[data-toggle="tab"]', this.tabShown) .on('click', '.js-show-tab', this.showTab); + + $('.merge-request-tabs a[data-toggle="tab"]') + .on('click', this.clickTab); } unbindEvents() { $(document) .off('shown.bs.tab', '.merge-request-tabs a[data-toggle="tab"]', this.tabShown) .off('click', '.js-show-tab', this.showTab); + + $('.merge-request-tabs a[data-toggle="tab"]') + .off('click', this.clickTab); } showTab(e) { @@ -99,6 +101,14 @@ this.activateTab($(e.target).data('action')); } + clickTab(e) { + if (e.target && gl.utils.isMetaClick(e)) { + const targetLink = e.target.getAttribute('href'); + e.stopImmediatePropagation(); + window.open(targetLink, '_blank'); + } + } + tabShown(e) { const $target = $(e.target); const action = $target.data('action'); @@ -119,14 +129,6 @@ $.scrollTo('.merge-request-details .merge-request-tabs', { offset: -navBarHeight, }); - } else if (action === 'builds') { - this.loadBuilds($target.attr('href')); - this.expandView(); - this.resetViewContainer(); - } else if (action === 'pipelines') { - this.loadPipelines($target.attr('href')); - this.expandView(); - this.resetViewContainer(); } else { this.expandView(); this.resetViewContainer(); @@ -180,8 +182,8 @@ setCurrentAction(action) { this.currentAction = action === 'show' ? 'notes' : action; - // Remove a trailing '/commits' '/diffs' '/builds' '/pipelines' '/new' '/new/diffs' - let newState = location.pathname.replace(/\/(commits|diffs|builds|pipelines|new|new\/diffs)(\.html)?\/?$/, ''); + // Remove a trailing '/commits' '/diffs' '/pipelines' '/new' '/new/diffs' + let newState = location.pathname.replace(/\/(commits|diffs|pipelines|new|new\/diffs)(\.html)?\/?$/, ''); // Append the new action if we're on a tab other than 'notes' if (this.currentAction !== 'notes') { @@ -191,12 +193,13 @@ // Ensure parameters and hash come along for the ride newState += location.search + location.hash; + // TODO: Consider refactoring in light of turbolinks removal. + // Replace the current history state with the new one without breaking // Turbolinks' history. // // See https://github.com/rails/turbolinks/issues/363 window.history.replaceState({ - turbolinks: true, url: newState, }, document.title, newState); @@ -244,44 +247,8 @@ } this.diffsLoaded = true; - const diffPage = new gl.Diff(); - - const locationHash = gl.utils.getLocationHash(); - const anchoredDiff = locationHash && locationHash.split('_')[0]; - if (anchoredDiff) { - diffPage.openAnchoredDiff(anchoredDiff, () => this.scrollToElement('#diffs')); - } - }, - }); - } - - loadBuilds(source) { - if (this.buildsLoaded) { - return; - } - this.ajaxGet({ - url: `${source}.json`, - success: (data) => { - document.querySelector('div#builds').innerHTML = data.html; - gl.utils.localTimeAgo($('.js-timeago', 'div#builds')); - this.buildsLoaded = true; - new gl.Pipelines(); - this.scrollToElement('#builds'); - }, - }); - } - - loadPipelines(source) { - if (this.pipelinesLoaded) { - return; - } - this.ajaxGet({ - url: `${source}.json`, - success: (data) => { - $('#pipelines').html(data.html); - gl.utils.localTimeAgo($('.js-timeago', '#pipelines')); - this.pipelinesLoaded = true; - this.scrollToElement('#pipelines'); + new gl.Diff(); + this.scrollToElement('#diffs'); }, }); } diff --git a/app/assets/javascripts/merge_request_widget.js.es6 b/app/assets/javascripts/merge_request_widget.js.es6 index e47047c4cca..e5d2d706fc7 100644 --- a/app/assets/javascripts/merge_request_widget.js.es6 +++ b/app/assets/javascripts/merge_request_widget.js.es6 @@ -1,11 +1,12 @@ -/* eslint-disable max-len, no-var, func-names, space-before-function-paren, vars-on-top, no-plusplus, comma-dangle, no-return-assign, consistent-return, no-param-reassign, one-var, one-var-declaration-per-line, quotes, prefer-template, no-else-return, prefer-arrow-callback, no-unused-vars, no-underscore-dangle, no-shadow, no-mixed-operators, template-curly-spacing, camelcase, default-case, wrap-iife, semi, padded-blocks */ +/* eslint-disable max-len, no-var, func-names, space-before-function-paren, vars-on-top, comma-dangle, no-return-assign, consistent-return, no-param-reassign, one-var, one-var-declaration-per-line, quotes, prefer-template, no-else-return, prefer-arrow-callback, no-unused-vars, no-underscore-dangle, no-shadow, no-mixed-operators, camelcase, default-case, wrap-iife */ /* global notify */ /* global notifyPermissions */ /* global merge_request_widget */ -/* global Turbolinks */ + +require('./smart_interval'); ((global) => { - var indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; + var indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i += 1) { if (i in this && this[i] === item) return i; } return -1; }; const DEPLOYMENT_TEMPLATE = `<div class="mr-widget-heading" id="<%- id %>"> <div class="ci_widget ci-success"> @@ -50,6 +51,8 @@ this.getCIStatus(false); this.retrieveSuccessIcon(); + this.initMiniPipelineGraph(); + this.ciStatusInterval = new global.SmartInterval({ callback: this.getCIStatus.bind(this, true), startingInterval: 10000, @@ -65,17 +68,18 @@ incrementByFactorOf: 15000, immediateExecution: true, }); + notifyPermissions(); } MergeRequestWidget.prototype.clearEventListeners = function() { - return $(document).off('page:change.merge_request'); + return $(document).off('DOMContentLoaded'); }; MergeRequestWidget.prototype.addEventListeners = function() { var allowedPages; - allowedPages = ['show', 'commits', 'builds', 'pipelines', 'changes']; - $(document).on('page:change.merge_request', (function(_this) { + allowedPages = ['show', 'commits', 'pipelines', 'changes']; + $(document).on('DOMContentLoaded', (function(_this) { return function() { var page; page = $('body').data('page').split(':').last(); @@ -90,7 +94,7 @@ const $ciSuccessIcon = $('.js-success-icon'); this.$ciSuccessIcon = $ciSuccessIcon.html(); $ciSuccessIcon.remove(); - } + }; MergeRequestWidget.prototype.mergeInProgress = function(deleteSourceBranch) { if (deleteSourceBranch == null) { @@ -126,7 +130,9 @@ MergeRequestWidget.prototype.getMergeStatus = function() { return $.get(this.opts.merge_check_url, function(data) { - return $('.mr-state-widget').replaceWith(data); + var $html = $(data); + $('.mr-widget-body').replaceWith($html.find('.mr-widget-body')); + $('.mr-widget-footer').replaceWith($html.find('.mr-widget-footer')); }); }; @@ -152,12 +158,22 @@ return; } if (data.environments && data.environments.length) _this.renderEnvironments(data.environments); - if (data.status !== _this.opts.ci_status && (data.status != null)) { + if (data.status !== _this.opts.ci_status || + data.sha !== _this.opts.ci_sha || + data.pipeline !== _this.opts.ci_pipeline) { _this.opts.ci_status = data.status; _this.showCIStatus(data.status); if (data.coverage) { _this.showCICoverage(data.coverage); } + if (data.pipeline) { + _this.opts.ci_pipeline = data.pipeline; + _this.updatePipelineUrls(data.pipeline); + } + if (data.sha) { + _this.opts.ci_sha = data.sha; + _this.updateCommitUrls(data.sha); + } if (showNotification) { status = _this.ciLabelForStatus(data.status); if (status === "preparing") { @@ -173,7 +189,6 @@ message = message.replace('{{title}}', data.title); notify(title, message, _this.opts.gitlab_icon, function() { this.close(); - return Turbolinks.visit(_this.opts.builds_path); }); } } @@ -188,9 +203,9 @@ }; MergeRequestWidget.prototype.renderEnvironments = function(environments) { - for (let i = 0; i < environments.length; i++) { + for (let i = 0; i < environments.length; i += 1) { const environment = environments[i]; - if ($(`.mr-state-widget #${ environment.id }`).length) return; + if ($(`.mr-state-widget #${environment.id}`).length) return; const $template = $(DEPLOYMENT_TEMPLATE); if (!environment.external_url || !environment.external_url_formatted) $('.js-environment-link', $template).remove(); @@ -206,7 +221,7 @@ } environment.ci_success_icon = this.$ciSuccessIcon; const templateString = _.unescape($template[0].outerHTML); - const template = _.template(templateString)(environment) + const template = _.template(templateString)(environment); this.$widgetBody.before(template); } }; @@ -224,17 +239,20 @@ case "failed": case "canceled": case "not_found": - return this.setMergeButtonClass('btn-danger'); + this.setMergeButtonClass('btn-danger'); + break; case "running": - return this.setMergeButtonClass('btn-info'); + this.setMergeButtonClass('btn-info'); + break; case "success": case "success_with_warnings": - return this.setMergeButtonClass('btn-create'); + this.setMergeButtonClass('btn-create'); } } else { $('.ci_widget.ci-error').show(); - return this.setMergeButtonClass('btn-danger'); + this.setMergeButtonClass('btn-danger'); } + this.initMiniPipelineGraph(); }; MergeRequestWidget.prototype.showCICoverage = function(coverage) { @@ -247,8 +265,22 @@ return $('.js-merge-button,.accept-action .dropdown-toggle').removeClass('btn-danger btn-info btn-create').addClass(css_class); }; - return MergeRequestWidget; + MergeRequestWidget.prototype.updatePipelineUrls = function(id) { + const pipelineUrl = this.opts.pipeline_path; + $('.pipeline').text(`#${id}`).attr('href', [pipelineUrl, id].join('/')); + }; - })(); + MergeRequestWidget.prototype.updateCommitUrls = function(id) { + const commitsUrl = this.opts.commits_path; + $('.js-commit-link').text(`#${id}`).attr('href', [commitsUrl, id].join('/')); + }; + MergeRequestWidget.prototype.initMiniPipelineGraph = function() { + new gl.MiniPipelineGraph({ + container: '.js-pipeline-inline-mr-widget-graph:visible', + }).bindEvents(); + }; + + return MergeRequestWidget; + })(); })(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/merge_request_widget/ci_bundle.js.es6 b/app/assets/javascripts/merge_request_widget/ci_bundle.js.es6 new file mode 100644 index 00000000000..5840916846b --- /dev/null +++ b/app/assets/javascripts/merge_request_widget/ci_bundle.js.es6 @@ -0,0 +1,53 @@ +/* global merge_request_widget */ + +(() => { + $(() => { + /* TODO: This needs a better home, or should be refactored. It was previously contained + * in a script tag in app/views/projects/merge_requests/widget/open/_accept.html.haml, + * but Vue chokes on script tags and prevents their execution. So it was moved here + * temporarily. + * */ + + $(document) + .off('ajax:send', '.accept-mr-form') + .on('ajax:send', '.accept-mr-form', () => { + $('.accept-mr-form :input').disable(); + }); + + $(document) + .off('click', '.accept_merge_request') + .on('click', '.accept_merge_request', () => { + $('.js-merge-button').html('<i class="fa fa-spinner fa-spin"></i> Merge in progress'); + }); + + $(document) + .off('click', '.merge_when_build_succeeds') + .on('click', '.merge_when_build_succeeds', () => { + $('#merge_when_build_succeeds').val('1'); + }); + + $(document) + .off('click', '.js-merge-dropdown a') + .on('click', '.js-merge-dropdown a', (e) => { + e.preventDefault(); + $(e.target).closest('form').submit(); + }); + if ($('.rebase-in-progress').length) { + merge_request_widget.rebaseInProgress(); + } else if ($('.rebase-mr-form').length) { + $(document) + .off('ajax:send', '.rebase-mr-form') + .on('ajax:send', '.rebase-mr-form', () => { + $('.rebase-mr-form :input').disable(); + }); + + $(document) + .off('click', '.js-rebase-button') + .on('click', '.js-rebase-button', () => { + $('.js-rebase-button').html("<i class='fa fa-spinner fa-spin'></i> Rebase in progress"); + }); + } else { + setTimeout(() => merge_request_widget.getMergeStatus(), 200); + } + }); +})(); diff --git a/app/assets/javascripts/merged_buttons.js b/app/assets/javascripts/merged_buttons.js index 9f8af46c715..527cdc9b698 100644 --- a/app/assets/javascripts/merged_buttons.js +++ b/app/assets/javascripts/merged_buttons.js @@ -1,7 +1,7 @@ -/* eslint-disable func-names, space-before-function-paren, no-var, space-before-blocks, prefer-rest-params, wrap-iife, padded-blocks, max-len */ +/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, max-len */ (function() { - var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; + var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; }; this.MergedButtons = (function() { function MergedButtons() { @@ -41,7 +41,5 @@ }; return MergedButtons; - })(); - }).call(this); diff --git a/app/assets/javascripts/milestone.js b/app/assets/javascripts/milestone.js index 42152362e60..7ce1259e015 100644 --- a/app/assets/javascripts/milestone.js +++ b/app/assets/javascripts/milestone.js @@ -1,4 +1,4 @@ -/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-use-before-define, camelcase, quotes, object-shorthand, no-shadow, no-unused-vars, comma-dangle, no-var, prefer-template, no-underscore-dangle, consistent-return, one-var, one-var-declaration-per-line, default-case, prefer-arrow-callback, padded-blocks, max-len */ +/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-use-before-define, camelcase, quotes, object-shorthand, no-shadow, no-unused-vars, comma-dangle, no-var, prefer-template, no-underscore-dangle, consistent-return, one-var, one-var-declaration-per-line, default-case, prefer-arrow-callback, max-len */ /* global Flash */ (function() { @@ -193,7 +193,5 @@ }; return Milestone; - })(); - }).call(this); diff --git a/app/assets/javascripts/milestone_select.js b/app/assets/javascripts/milestone_select.js index 28054b78249..2f08aa7fe8b 100644 --- a/app/assets/javascripts/milestone_select.js +++ b/app/assets/javascripts/milestone_select.js @@ -1,17 +1,24 @@ -/* 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, padded-blocks */ +/* 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 Vue */ /* global Issuable */ /* global ListMilestone */ (function() { this.MilestoneSelect = (function() { - function MilestoneSelect(currentProject) { - var _this; + function MilestoneSelect(currentProject, els) { + var _this, $els; if (currentProject != null) { _this = this; this.currentProject = JSON.parse(currentProject); } - $('.js-milestone-select').each(function(i, dropdown) { + + $els = $(els); + + if (!els) { + $els = $('.js-milestone-select'); + } + + $els.each(function(i, dropdown) { var $block, $dropdown, $loading, $selectbox, $sidebarCollapsedValue, $value, abilityName, collapsedSidebarLabelTemplate, defaultLabel, issuableId, issueUpdateURL, milestoneLinkNoneTemplate, milestoneLinkTemplate, milestonesUrl, projectId, selectedMilestone, showAny, showNo, showUpcoming, useId, showMenuAbove; $dropdown = $(dropdown); projectId = $dropdown.data('project-id'); @@ -108,7 +115,7 @@ }, vue: $dropdown.hasClass('js-issue-board-sidebar'), clicked: function(selected, $el, e) { - var data, isIssueIndex, isMRIndex, page; + var data, isIssueIndex, isMRIndex, page, boardsStore; page = $('body').data('page'); isIssueIndex = page === 'projects:issues:index'; isMRIndex = (page === page && page === 'projects:merge_requests:index'); @@ -116,9 +123,19 @@ e.preventDefault(); return; } - if ($('html').hasClass('issue-boards-page') && !$dropdown.hasClass('js-issue-board-sidebar')) { - gl.issueBoards.BoardsStore.state.filters[$dropdown.data('field-name')] = selected.name; - gl.issueBoards.BoardsStore.updateFiltersUrl(); + + if ($('html').hasClass('issue-boards-page') && !$dropdown.hasClass('js-issue-board-sidebar') && + !$dropdown.closest('.add-issues-modal').length) { + boardsStore = gl.issueBoards.BoardsStore.state.filters; + } else if ($dropdown.closest('.add-issues-modal').length) { + boardsStore = gl.issueBoards.ModalStore.store.filter; + } + + if (boardsStore) { + boardsStore[$dropdown.data('field-name')] = selected.name; + if (!$dropdown.closest('.add-issues-modal').length) { + gl.issueBoards.BoardsStore.updateFiltersUrl(); + } e.preventDefault(); } else if ($dropdown.hasClass('js-filter-submit') && (isIssueIndex || isMRIndex)) { if (selected.name != null) { @@ -181,7 +198,5 @@ } return MilestoneSelect; - })(); - }).call(this); diff --git a/app/assets/javascripts/mini_pipeline_graph_dropdown.js.es6 b/app/assets/javascripts/mini_pipeline_graph_dropdown.js.es6 index 90b3366f14b..4becbc32681 100644 --- a/app/assets/javascripts/mini_pipeline_graph_dropdown.js.es6 +++ b/app/assets/javascripts/mini_pipeline_graph_dropdown.js.es6 @@ -10,9 +10,9 @@ * The container should be the table element. * * The stage icon clicked needs to have the following HTML structure: - * <div> - * <button class="dropdown js-builds-dropdown-button"></button> - * <div class="js-builds-dropdown-container"></div> + * <div class="dropdown"> + * <button class="dropdown js-builds-dropdown-button" data-toggle="dropdown"></button> + * <div class="js-builds-dropdown-container dropdown-menu"></div> * </div> */ (() => { @@ -21,18 +21,14 @@ this.container = opts.container || ''; this.dropdownListSelector = '.js-builds-dropdown-container'; this.getBuildsList = this.getBuildsList.bind(this); - - this.bindEvents(); } /** - * Adds and removes the event listener. + * Adds the event listener when the dropdown is opened. + * All dropdown events are fired at the .dropdown-menu's parent element. */ bindEvents() { - const dropdownButtonSelector = 'button.js-builds-dropdown-button'; - - $(this.container).off('click', dropdownButtonSelector, this.getBuildsList) - .on('click', dropdownButtonSelector, this.getBuildsList); + $(this.container).on('shown.bs.dropdown', this.getBuildsList); } /** @@ -52,11 +48,14 @@ /** * For the clicked stage, gets the list of builds. * - * @param {Object} e + * All dropdown events have a relatedTarget property, + * whose value is the toggling anchor element. + * + * @param {Object} e bootstrap dropdown event * @return {Promise} */ getBuildsList(e) { - const button = e.currentTarget; + const button = e.relatedTarget; const endpoint = button.dataset.stageEndpoint; return $.ajax({ diff --git a/app/assets/javascripts/namespace_select.js b/app/assets/javascripts/namespace_select.js index 6633f2c2709..514556ade0b 100644 --- a/app/assets/javascripts/namespace_select.js +++ b/app/assets/javascripts/namespace_select.js @@ -1,8 +1,8 @@ -/* eslint-disable func-names, space-before-function-paren, no-var, space-before-blocks, prefer-rest-params, wrap-iife, one-var, vars-on-top, one-var-declaration-per-line, comma-dangle, object-shorthand, no-else-return, prefer-template, quotes, prefer-arrow-callback, padded-blocks, no-param-reassign, no-cond-assign, max-len */ +/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, one-var, vars-on-top, one-var-declaration-per-line, comma-dangle, object-shorthand, no-else-return, prefer-template, quotes, prefer-arrow-callback, no-param-reassign, no-cond-assign, max-len */ /* global Api */ (function() { - var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; + var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; }; window.NamespaceSelect = (function() { function NamespaceSelect(opts) { @@ -63,7 +63,6 @@ }; return NamespaceSelect; - })(); window.NamespaceSelects = (function() { @@ -83,7 +82,5 @@ } return NamespaceSelects; - })(); - }).call(this); diff --git a/app/assets/javascripts/network/branch_graph.js b/app/assets/javascripts/network/branch_graph.js index 64b19a54893..a7ccd03b60c 100644 --- a/app/assets/javascripts/network/branch_graph.js +++ b/app/assets/javascripts/network/branch_graph.js @@ -1,8 +1,8 @@ -/* eslint-disable func-names, space-before-function-paren, no-var, space-before-blocks, prefer-rest-params, wrap-iife, quotes, comma-dangle, one-var, one-var-declaration-per-line, no-mixed-operators, new-cap, no-plusplus, no-loop-func, no-floating-decimal, consistent-return, no-unused-vars, prefer-template, prefer-arrow-callback, camelcase, max-len, padded-blocks */ +/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, quotes, comma-dangle, one-var, one-var-declaration-per-line, no-mixed-operators, new-cap, no-loop-func, no-floating-decimal, consistent-return, no-unused-vars, prefer-template, prefer-arrow-callback, camelcase, max-len */ /* global Raphael */ (function() { - var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; + var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; }; this.BranchGraph = (function() { function BranchGraph(element1, options1) { @@ -53,7 +53,7 @@ this.top = this.r.set(); this.barHeight = Math.max(this.graphHeight, this.unitTime * this.days.length + 320); ref = this.commits; - for (j = 0, len = ref.length; j < len; j++) { + for (j = 0, len = ref.length; j < len; j += 1) { c = ref[j]; if (c.id in this.parents) { c.isParent = true; @@ -68,7 +68,7 @@ var c, j, len, p, ref, results; ref = this.commits; results = []; - for (j = 0, len = ref.length; j < len; j++) { + for (j = 0, len = ref.length; j < len; j += 1) { c = ref[j]; this.mtime = Math.max(this.mtime, c.time); this.mspace = Math.max(this.mspace, c.space); @@ -76,7 +76,7 @@ var l, len1, ref1, results1; ref1 = c.parents; results1 = []; - for (l = 0, len1 = ref1.length; l < len1; l++) { + for (l = 0, len1 = ref1.length; l < len1; l += 1) { p = ref1[l]; this.parents[p[0]] = true; results1.push(this.mspace = Math.max(this.mspace, p[1])); @@ -96,7 +96,7 @@ // Skipping a few colors in the spectrum to get more contrast between colors Raphael.getColor(); Raphael.getColor(); - results.push(k++); + results.push(k += 1); } return results; }; @@ -113,7 +113,7 @@ fill: "#444" }); ref = this.days; - for (mm = j = 0, len = ref.length; j < len; mm = ++j) { + for (mm = j = 0, len = ref.length; j < len; mm = (j += 1)) { day = ref[mm]; if (cuday !== day[0] || cumonth !== day[1]) { // Dates @@ -286,7 +286,7 @@ r = this.r; ref = commit.parents; results = []; - for (i = j = 0, len = ref.length; j < len; i = ++j) { + for (i = j = 0, len = ref.length; j < len; i = (j += 1)) { parent = ref[i]; parentCommit = this.preparedCommits[parent[0]]; parentY = this.offsetY + this.unitTime * parentCommit.time; @@ -346,7 +346,6 @@ }; return BranchGraph; - })(); Raphael.prototype.commitTooltip = function(x, y, commit) { @@ -356,7 +355,7 @@ icon = this.image(gon.relative_url_root + commit.author.icon, x, y, 20, 20); nameText = this.text(x + 25, y + 10, commit.author.name); idText = this.text(x, y + 35, commit.id); - messageText = this.text(x, y + 50, commit.message); + messageText = this.text(x, y + 50, commit.message.replace(/\r?\n/g, " \n ")); textSet = this.set(icon, nameText, idText, messageText).attr({ "text-anchor": "start", font: "12px Monaco, monospace" @@ -368,6 +367,7 @@ idText.attr({ fill: "#AAA" }); + messageText.node.style["white-space"] = "pre"; this.textWrap(messageText, boxWidth - 50); rect = this.rect(x - 10, y - 10, boxWidth, 100, 4).attr({ fill: "#FFF", @@ -398,23 +398,27 @@ words = content.split(" "); x = 0; s = []; - for (j = 0, len = words.length; j < len; j++) { + for (j = 0, len = words.length; j < len; j += 1) { word = words[j]; if (x + (word.length * letterWidth) > width) { s.push("\n"); x = 0; } - x += word.length * letterWidth; - s.push(word + " "); + if (word === "\n") { + s.push("\n"); + x = 0; + } else { + s.push(word + " "); + x += word.length * letterWidth; + } } t.attr({ - text: s.join("") + text: s.join("").trim() }); b = t.getBBox(); - h = Math.abs(b.y2) - Math.abs(b.y) + 1; + h = Math.abs(b.y2) + 1; return t.attr({ - y: b.y + h + y: h }); }; - }).call(this); diff --git a/app/assets/javascripts/network/network.js b/app/assets/javascripts/network/network.js index 2367d2497b2..37bf6436fd1 100644 --- a/app/assets/javascripts/network/network.js +++ b/app/assets/javascripts/network/network.js @@ -1,4 +1,4 @@ -/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, quotes, quote-props, prefer-template, comma-dangle, padded-blocks, max-len */ +/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, quotes, quote-props, prefer-template, comma-dangle, max-len */ /* global BranchGraph */ (function() { @@ -16,7 +16,5 @@ } return Network; - })(); - }).call(this); diff --git a/app/assets/javascripts/network/network_bundle.js b/app/assets/javascripts/network/network_bundle.js index 17833d3419a..b4491354472 100644 --- a/app/assets/javascripts/network/network_bundle.js +++ b/app/assets/javascripts/network/network_bundle.js @@ -1,14 +1,10 @@ -/* eslint-disable func-names, space-before-function-paren, prefer-arrow-callback, quotes, no-var, vars-on-top, camelcase, comma-dangle, consistent-return, padded-blocks, max-len */ +/* eslint-disable func-names, space-before-function-paren, prefer-arrow-callback, quotes, no-var, vars-on-top, camelcase, comma-dangle, consistent-return, max-len */ /* global Network */ /* global ShortcutsNetwork */ -// This is a manifest file that'll be compiled into including all the files listed below. -// Add new JavaScript code in separate files in this directory and they'll automatically -// be included in the compiled file accessible from http://example.com/assets/application.js -// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the -// the compiled file. -// -/*= require_tree . */ +// require everything else in this directory +function requireAll(context) { return context.keys().map(context); } +requireAll(require.context('.', false, /^\.\/(?!network_bundle).*\.(js|es6)$/)); (function() { $(function() { @@ -23,5 +19,4 @@ }); return new ShortcutsNetwork(network_graph.branch_graph); }); - }).call(this); diff --git a/app/assets/javascripts/new_branch_form.js b/app/assets/javascripts/new_branch_form.js index 29a323dd4c6..7f763c13b50 100644 --- a/app/assets/javascripts/new_branch_form.js +++ b/app/assets/javascripts/new_branch_form.js @@ -1,7 +1,7 @@ -/* eslint-disable func-names, space-before-function-paren, no-var, one-var, space-before-blocks, prefer-rest-params, max-len, vars-on-top, no-plusplus, wrap-iife, consistent-return, comma-dangle, one-var-declaration-per-line, quotes, no-return-assign, prefer-arrow-callback, prefer-template, no-shadow, no-else-return, padded-blocks, max-len */ +/* eslint-disable func-names, space-before-function-paren, no-var, one-var, prefer-rest-params, max-len, vars-on-top, wrap-iife, consistent-return, comma-dangle, one-var-declaration-per-line, quotes, no-return-assign, prefer-arrow-callback, prefer-template, no-shadow, no-else-return, max-len */ (function() { - var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, - indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; + var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; }, + indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i += 1) { if (i in this && this[i] === item) return i; } return -1; }; this.NewBranchForm = (function() { function NewBranchForm(form, availableRefs) { @@ -99,7 +99,5 @@ }; return NewBranchForm; - })(); - }).call(this); diff --git a/app/assets/javascripts/new_commit_form.js b/app/assets/javascripts/new_commit_form.js index 8fb8f3e4a5f..41eea78a3e6 100644 --- a/app/assets/javascripts/new_commit_form.js +++ b/app/assets/javascripts/new_commit_form.js @@ -1,6 +1,6 @@ -/* eslint-disable func-names, space-before-function-paren, no-var, space-before-blocks, prefer-rest-params, wrap-iife, no-return-assign, padded-blocks, max-len */ +/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, no-return-assign, max-len */ (function() { - var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; + var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; }; this.NewCommitForm = (function() { function NewCommitForm(form) { @@ -29,7 +29,5 @@ }; return NewCommitForm; - })(); - }).call(this); diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js index a8b9a352870..d108da29af7 100644 --- a/app/assets/javascripts/notes.js +++ b/app/assets/javascripts/notes.js @@ -1,19 +1,20 @@ -/* eslint-disable no-restricted-properties, func-names, space-before-function-paren, no-var, space-before-blocks, prefer-rest-params, wrap-iife, no-use-before-define, camelcase, no-unused-expressions, quotes, max-len, one-var, one-var-declaration-per-line, default-case, prefer-template, consistent-return, no-alert, no-return-assign, no-param-reassign, prefer-arrow-callback, no-else-return, comma-dangle, no-new, brace-style, no-lonely-if, vars-on-top, no-unused-vars, semi, indent, no-sequences, no-shadow, newline-per-chained-call, no-useless-escape, radix, padded-blocks */ +/* eslint-disable no-restricted-properties, func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, no-use-before-define, camelcase, no-unused-expressions, quotes, max-len, one-var, one-var-declaration-per-line, default-case, prefer-template, consistent-return, no-alert, no-return-assign, no-param-reassign, prefer-arrow-callback, no-else-return, comma-dangle, no-new, brace-style, no-lonely-if, vars-on-top, no-unused-vars, no-sequences, no-shadow, newline-per-chained-call, no-useless-escape */ /* global Flash */ -/* global GLForm */ /* global Autosave */ /* global ResolveService */ +/* global mrRefreshWidgetUrl */ -/*= require autosave */ -/*= require autosize */ -/*= require dropzone */ -/*= require dropzone_input */ -/*= require gfm_auto_complete */ -/*= require jquery.atwho */ -/*= require task_list */ +require('./autosave'); +window.autosize = require('vendor/autosize'); +window.Dropzone = require('dropzone'); +require('./dropzone_input'); +require('./gfm_auto_complete'); +require('vendor/jquery.caret'); // required by jquery.atwho +require('vendor/jquery.atwho'); +require('vendor/task_list'); (function() { - var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; + var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; }; this.Notes = (function() { const MAX_VISIBLE_COMMIT_LIST_COUNT = 3; @@ -52,6 +53,12 @@ this.setupMainTargetNoteForm(); this.initTaskList(); this.collapseLongCommitList(); + + // We are in the Merge Requests page so we need another edit form for Changes tab + if (gl.utils.getPagePath(1) === 'merge_requests') { + $('.note-edit-form').clone() + .addClass('mr-note-edit-form').insertAfter('.note-edit-form'); + } } Notes.prototype.addBinding = function() { @@ -63,7 +70,7 @@ // change note in UI after update $(document).on("ajax:success", "form.edit-note", this.updateNote); // Edit note link - $(document).on("click", ".js-note-edit", this.showEditForm); + $(document).on("click", ".js-note-edit", this.showEditForm.bind(this)); $(document).on("click", ".note-edit-cancel", this.cancelEdit); // Reopen and close actions for Issue/MR combined with note form submit $(document).on("click", ".js-comment-button", this.updateCloseButton); @@ -213,7 +220,6 @@ })(this)); }; - /* Increase @pollingInterval up to 120 seconds on every function call, if `shouldReset` has a truthy value, 'null' or 'undefined' the variable @@ -237,6 +243,15 @@ return this.initRefresh(); }; + Notes.prototype.handleCreateChanges = function(note) { + if (typeof note === 'undefined') { + return; + } + + if (note.commands_changes && note.commands_changes.indexOf('merge') !== -1) { + $.get(mrRefreshWidgetUrl); + } + }; /* Render note in main comments area. @@ -277,7 +292,6 @@ } }; - /* Check if note does not exists on page */ @@ -290,7 +304,6 @@ return this.view === 'parallel'; }; - /* Render note in discussion area. @@ -341,7 +354,6 @@ return this.updateNotesCount(1); }; - /* Called in response the main target form has been successfully submitted. @@ -373,7 +385,6 @@ return form.find(".js-note-text").trigger("input"); }; - /* Shows the main form and does some setup on it. @@ -398,7 +409,6 @@ return this.parentTimeline = form.parents('.timeline'); }; - /* General note form setup. @@ -410,12 +420,11 @@ Notes.prototype.setupNoteForm = function(form) { var textarea; - new GLForm(form); + new gl.GLForm(form); textarea = form.find(".js-note-text"); return new Autosave(textarea, ["Note", form.find("#note_noteable_type").val(), form.find("#note_noteable_id").val(), form.find("#note_commit_id").val(), form.find("#note_type").val(), form.find("#note_line_code").val(), form.find("#note_position").val()]); }; - /* Called in response to the new note form being submitted @@ -423,6 +432,7 @@ */ Notes.prototype.addNote = function(xhr, note, status) { + this.handleCreateChanges(note); return this.renderNote(note); }; @@ -430,7 +440,6 @@ return new Flash('Your comment could not be submitted! Please check your network connection and try again.', 'alert', this.parentTimeline); }; - /* Called in response to the new note form being submitted @@ -455,7 +464,6 @@ this.removeDiscussionNoteForm($form); }; - /* Called in response to the edit note form being submitted @@ -466,6 +474,7 @@ var $html, $note_li; // Convert returned HTML to a jQuery object so we can modify it further $html = $(note.html); + this.revertNoteEditForm(); gl.utils.localTimeAgo($('.js-timeago', $html)); $html.renderGFM(); $html.find('.js-task-list-container').taskList('enable'); @@ -479,51 +488,56 @@ } }; + Notes.prototype.checkContentToAllowEditing = function($el) { + var initialContent = $el.find('.original-note-content').text().trim(); + var currentContent = $el.find('.note-textarea').val(); + var isAllowed = true; + + if (currentContent === initialContent) { + this.removeNoteEditForm($el); + } + else { + var $buttons = $el.find('.note-form-actions'); + var isWidgetVisible = gl.utils.isInViewport($el.get(0)); + + if (!isWidgetVisible) { + gl.utils.scrollToElement($el); + } + + $el.find('.js-edit-warning').show(); + isAllowed = false; + } + + return isAllowed; + }; /* Called in response to clicking the edit note link Replaces the note text with the note edit form Adds a data attribute to the form with the original content of the note for cancellations - */ - + */ Notes.prototype.showEditForm = function(e, scrollTo, myLastNote) { - var $noteText, done, form, note; e.preventDefault(); - note = $(this).closest(".note"); - note.addClass("is-editting"); - form = note.find(".note-edit-form"); - form.addClass('current-note-edit-form'); - // Show the attachment delete link - note.find(".js-note-attachment-delete").show(); - done = function($noteText) { - var noteTextVal; - // Neat little trick to put the cursor at the end - noteTextVal = $noteText.val(); - // Store the original note text in a data attribute to retrieve if a user cancels edit. - form.find('form.edit-note').data('original-note', noteTextVal); - return $noteText.val('').val(noteTextVal); - }; - new GLForm(form); - if ((scrollTo != null) && (myLastNote != null)) { - // scroll to the bottom - // so the open of the last element doesn't make a jump - $('html, body').scrollTop($(document).height()); - return $('html, body').animate({ - scrollTop: myLastNote.offset().top - 150 - }, 500, function() { - var $noteText; - $noteText = form.find(".js-note-text"); - $noteText.focus(); - return done($noteText); - }); - } else { - $noteText = form.find('.js-note-text'); - $noteText.focus(); - return done($noteText); + + var $target = $(e.target); + var $editForm = $(this.getEditFormSelector($target)); + var $note = $target.closest('.note'); + var $currentlyEditing = $('.note.is-editting:visible'); + + if ($currentlyEditing.length) { + var isEditAllowed = this.checkContentToAllowEditing($currentlyEditing); + + if (!isEditAllowed) { + return; + } } - }; + $note.find('.js-note-attachment-delete').show(); + $editForm.addClass('current-note-edit-form'); + $note.addClass('is-editting'); + this.putEditFormInPlace($target); + }; /* Called in response to clicking the edit note link @@ -532,22 +546,43 @@ */ Notes.prototype.cancelEdit = function(e) { - var note; e.preventDefault(); - note = $(e.target).closest('.note'); + var $target = $(e.target); + var note = $target.closest('.note'); + note.find('.js-edit-warning').hide(); + this.revertNoteEditForm($target); return this.removeNoteEditForm(note); }; + Notes.prototype.revertNoteEditForm = function($target) { + $target = $target || $('.note.is-editting:visible'); + var selector = this.getEditFormSelector($target); + var $editForm = $(selector); + + $editForm.insertBefore('.notes-form'); + $editForm.find('.js-comment-button').enable(); + $editForm.find('.js-edit-warning').hide(); + }; + + Notes.prototype.getEditFormSelector = function($el) { + var selector = '.note-edit-form:not(.mr-note-edit-form)'; + + if ($el.parents('#diffs').length) { + selector = '.note-edit-form.mr-note-edit-form'; + } + + return selector; + }; + Notes.prototype.removeNoteEditForm = function(note) { - var form; - form = note.find(".current-note-edit-form"); - note.removeClass("is-editting"); - form.removeClass("current-note-edit-form"); + var form = note.find('.current-note-edit-form'); + note.removeClass('is-editting'); + form.removeClass('current-note-edit-form'); + form.find('.js-edit-warning').hide(); // Replace markdown textarea text with original note text. - return form.find(".js-note-text").val(form.find('form.edit-note').data('original-note')); + return form.find('.js-note-text').val(form.find('form.edit-note').data('original-note')); }; - /* Called in response to deleting a note of any kind. @@ -587,7 +622,6 @@ return this.updateNotesCount(-1); }; - /* Called in response to clicking the delete attachment link @@ -604,7 +638,6 @@ return note.find(".current-note-edit-form").remove(); }; - /* Called when clicking on the "reply" button for a diff line. @@ -624,7 +657,6 @@ return this.setupDiscussionNoteForm(replyLink, form); }; - /* Shows the diff or discussion form and does some setup on it. @@ -666,7 +698,6 @@ .addClass("discussion-form js-discussion-note-form"); }; - /* Called when clicking on the "add a comment" button on the side of a diff line. @@ -723,7 +754,6 @@ } }; - /* Called in response to "cancel" on a diff note form. @@ -757,7 +787,6 @@ return this.removeDiscussionNoteForm(form); }; - /* Called after an attachment file has been selected. @@ -772,7 +801,6 @@ return form.find(".js-attachment-filename").text(filename); }; - /* Called when the tab visibility changes */ @@ -837,19 +865,50 @@ Notes.prototype.initTaskList = function() { this.enableTaskList(); - return $(document).on('tasklist:changed', '.note .js-task-list-container', this.updateTaskList); + return $(document).on('tasklist:changed', '.note .js-task-list-container', this.updateTaskList.bind(this)); }; Notes.prototype.enableTaskList = function() { return $('.note .js-task-list-container').taskList('enable'); }; - Notes.prototype.updateTaskList = function() { - return $('form', this).submit(); + Notes.prototype.putEditFormInPlace = function($el) { + var $editForm = $(this.getEditFormSelector($el)); + var $note = $el.closest('.note'); + + $editForm.insertAfter($note.find('.note-text')); + + var $originalContentEl = $note.find('.original-note-content'); + var originalContent = $originalContentEl.text().trim(); + var postUrl = $originalContentEl.data('post-url'); + var targetId = $originalContentEl.data('target-id'); + var targetType = $originalContentEl.data('target-type'); + + new gl.GLForm($editForm.find('form')); + + $editForm.find('form') + .attr('action', postUrl) + .attr('data-remote', 'true'); + $editForm.find('.js-form-target-id').val(targetId); + $editForm.find('.js-form-target-type').val(targetType); + $editForm.find('.js-note-text').focus().val(originalContent); + $editForm.find('.js-md-write-button').trigger('click'); + $editForm.find('.referenced-users').hide(); + }; + + Notes.prototype.updateTaskList = function(e) { + var $target = $(e.target); + var $list = $target.closest('.js-task-list-container'); + var $editForm = $(this.getEditFormSelector($target)); + var $note = $list.closest('.note'); + + this.putEditFormInPlace($list); + $editForm.find('#note_note').val($note.find('.original-task-list').val()); + $('form', $list).submit(); }; Notes.prototype.updateNotesCount = function(updateCount) { - return this.notesCountBadge.text(parseInt(this.notesCountBadge.text()) + updateCount); + return this.notesCountBadge.text(parseInt(this.notesCountBadge.text(), 10) + updateCount); }; Notes.prototype.resolveDiscussion = function() { @@ -894,7 +953,5 @@ }; return Notes; - })(); - }).call(this); diff --git a/app/assets/javascripts/notifications_dropdown.js b/app/assets/javascripts/notifications_dropdown.js index 5d0d594073d..926dc35fee8 100644 --- a/app/assets/javascripts/notifications_dropdown.js +++ b/app/assets/javascripts/notifications_dropdown.js @@ -1,4 +1,4 @@ -/* eslint-disable func-names, space-before-function-paren, wrap-iife, one-var, no-var, one-var-declaration-per-line, no-unused-vars, consistent-return, prefer-arrow-callback, no-else-return, padded-blocks, max-len */ +/* eslint-disable func-names, space-before-function-paren, wrap-iife, one-var, no-var, one-var-declaration-per-line, no-unused-vars, consistent-return, prefer-arrow-callback, no-else-return, max-len */ /* global Flash */ (function() { @@ -27,7 +27,5 @@ } return NotificationsDropdown; - })(); - }).call(this); diff --git a/app/assets/javascripts/notifications_form.js b/app/assets/javascripts/notifications_form.js index 2034f9a748a..c3d7cc0adfb 100644 --- a/app/assets/javascripts/notifications_form.js +++ b/app/assets/javascripts/notifications_form.js @@ -1,6 +1,6 @@ -/* eslint-disable func-names, space-before-function-paren, no-var, space-before-blocks, prefer-rest-params, wrap-iife, one-var, one-var-declaration-per-line, newline-per-chained-call, comma-dangle, consistent-return, prefer-arrow-callback, padded-blocks, max-len */ +/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, one-var, one-var-declaration-per-line, newline-per-chained-call, comma-dangle, consistent-return, prefer-arrow-callback, max-len */ (function() { - var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; + var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; }; this.NotificationsForm = (function() { function NotificationsForm() { @@ -53,7 +53,5 @@ }; return NotificationsForm; - })(); - }).call(this); diff --git a/app/assets/javascripts/pipelines.js.es6 b/app/assets/javascripts/pipelines.js.es6 index 0b09ad113a3..9203abefbbc 100644 --- a/app/assets/javascripts/pipelines.js.es6 +++ b/app/assets/javascripts/pipelines.js.es6 @@ -1,12 +1,10 @@ -/* eslint-disable no-new, guard-for-in, no-restricted-syntax, no-continue, padded-blocks, no-param-reassign, max-len */ +/* eslint-disable no-new, guard-for-in, no-restricted-syntax, no-continue, no-param-reassign, max-len */ -//= require lib/utils/bootstrap_linked_tabs +require('./lib/utils/bootstrap_linked_tabs'); ((global) => { - class Pipelines { constructor(options = {}) { - if (options.initTabs && options.tabsOptions) { new global.LinkedTabs(options.tabsOptions); } @@ -37,5 +35,4 @@ } global.Pipelines = Pipelines; - })(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/preview_markdown.js b/app/assets/javascripts/preview_markdown.js index 1e261cd49c2..07eea98e737 100644 --- a/app/assets/javascripts/preview_markdown.js +++ b/app/assets/javascripts/preview_markdown.js @@ -1,14 +1,18 @@ -/* eslint-disable func-names, space-before-function-paren, no-var, one-var, one-var-declaration-per-line, wrap-iife, no-else-return, consistent-return, object-shorthand, comma-dangle, no-param-reassign, padded-blocks, camelcase, prefer-arrow-callback, max-len */ +/* eslint-disable func-names, no-var, object-shorthand, comma-dangle, prefer-arrow-callback */ // MarkdownPreview // // Handles toggling the "Write" and "Preview" tab clicks, rendering the preview, // and showing a warning when more than `x` users are referenced. // -(function() { - var lastTextareaPreviewed, markdownPreview, previewButtonSelector, writeButtonSelector; - - window.MarkdownPreview = (function() { +(function () { + var lastTextareaPreviewed; + var lastTextareaHeight = null; + var markdownPreview; + var previewButtonSelector; + var writeButtonSelector; + + window.MarkdownPreview = (function () { function MarkdownPreview() {} // Minimum number of users referenced before triggering a warning @@ -16,73 +20,71 @@ MarkdownPreview.prototype.ajaxCache = {}; - MarkdownPreview.prototype.showPreview = function(form) { - var mdText, preview; - preview = form.find('.js-md-preview'); - mdText = form.find('textarea.markdown-area').val(); + MarkdownPreview.prototype.showPreview = function ($form) { + var mdText; + var preview = $form.find('.js-md-preview'); + if (preview.hasClass('md-preview-loading')) { + return; + } + mdText = $form.find('textarea.markdown-area').val(); + if (mdText.trim().length === 0) { preview.text('Nothing to preview.'); - return this.hideReferencedUsers(form); + this.hideReferencedUsers($form); } else { - preview.text('Loading...'); - return this.renderMarkdown(mdText, (function(_this) { - return function(response) { - preview.html(response.body); - preview.renderGFM(); - return _this.renderReferencedUsers(response.references.users, form); - }; - })(this)); + preview.addClass('md-preview-loading').text('Loading...'); + this.fetchMarkdownPreview(mdText, (function (response) { + preview.removeClass('md-preview-loading').html(response.body); + preview.renderGFM(); + this.renderReferencedUsers(response.references.users, $form); + }).bind(this)); } }; - MarkdownPreview.prototype.renderMarkdown = function(text, success) { + MarkdownPreview.prototype.fetchMarkdownPreview = function (text, success) { if (!window.preview_markdown_path) { return; } if (text === this.ajaxCache.text) { - return success(this.ajaxCache.response); + success(this.ajaxCache.response); + return; } - return $.ajax({ + $.ajax({ type: 'POST', url: window.preview_markdown_path, data: { text: text }, dataType: 'json', - success: (function(_this) { - return function(response) { - _this.ajaxCache = { - text: text, - response: response - }; - return success(response); + success: (function (response) { + this.ajaxCache = { + text: text, + response: response }; - })(this) + success(response); + }).bind(this) }); }; - MarkdownPreview.prototype.hideReferencedUsers = function(form) { - var referencedUsers; - referencedUsers = form.find('.referenced-users'); - return referencedUsers.hide(); + MarkdownPreview.prototype.hideReferencedUsers = function ($form) { + $form.find('.referenced-users').hide(); }; - MarkdownPreview.prototype.renderReferencedUsers = function(users, form) { + MarkdownPreview.prototype.renderReferencedUsers = function (users, $form) { var referencedUsers; - referencedUsers = form.find('.referenced-users'); + referencedUsers = $form.find('.referenced-users'); if (referencedUsers.length) { if (users.length >= this.referenceThreshold) { referencedUsers.show(); - return referencedUsers.find('.js-referenced-users-count').text(users.length); + referencedUsers.find('.js-referenced-users-count').text(users.length); } else { - return referencedUsers.hide(); + referencedUsers.hide(); } } }; return MarkdownPreview; - - })(); + }()); markdownPreview = new window.MarkdownPreview(); @@ -92,71 +94,75 @@ lastTextareaPreviewed = null; - $.fn.setupMarkdownPreview = function() { - var $form, form_textarea; - $form = $(this); - form_textarea = $form.find('textarea.markdown-area'); - form_textarea.on('input', function() { - return markdownPreview.hideReferencedUsers($form); - }); - return form_textarea.on('blur', function() { - return markdownPreview.showPreview($form); + $.fn.setupMarkdownPreview = function () { + var $form = $(this); + $form.find('textarea.markdown-area').on('input', function () { + markdownPreview.hideReferencedUsers($form); }); }; - $(document).on('markdown-preview:show', function(e, $form) { + $(document).on('markdown-preview:show', function (e, $form) { if (!$form) { return; } + lastTextareaPreviewed = $form.find('textarea.markdown-area'); + lastTextareaHeight = lastTextareaPreviewed.height(); + // toggle tabs $form.find(writeButtonSelector).parent().removeClass('active'); $form.find(previewButtonSelector).parent().addClass('active'); + // toggle content $form.find('.md-write-holder').hide(); $form.find('.md-preview-holder').show(); - return markdownPreview.showPreview($form); + markdownPreview.showPreview($form); }); - $(document).on('markdown-preview:hide', function(e, $form) { + $(document).on('markdown-preview:hide', function (e, $form) { if (!$form) { return; } lastTextareaPreviewed = null; + + if (lastTextareaHeight) { + $form.find('textarea.markdown-area').height(lastTextareaHeight); + } + // toggle tabs $form.find(writeButtonSelector).parent().addClass('active'); $form.find(previewButtonSelector).parent().removeClass('active'); + // toggle content $form.find('.md-write-holder').show(); $form.find('textarea.markdown-area').focus(); - return $form.find('.md-preview-holder').hide(); + $form.find('.md-preview-holder').hide(); }); - $(document).on('markdown-preview:toggle', function(e, keyboardEvent) { + $(document).on('markdown-preview:toggle', function (e, keyboardEvent) { var $target; $target = $(keyboardEvent.target); if ($target.is('textarea.markdown-area')) { $(document).triggerHandler('markdown-preview:show', [$target.closest('form')]); - return keyboardEvent.preventDefault(); + keyboardEvent.preventDefault(); } else if (lastTextareaPreviewed) { $target = lastTextareaPreviewed; $(document).triggerHandler('markdown-preview:hide', [$target.closest('form')]); - return keyboardEvent.preventDefault(); + keyboardEvent.preventDefault(); } }); - $(document).on('click', previewButtonSelector, function(e) { + $(document).on('click', previewButtonSelector, function (e) { var $form; e.preventDefault(); $form = $(this).closest('form'); - return $(document).triggerHandler('markdown-preview:show', [$form]); + $(document).triggerHandler('markdown-preview:show', [$form]); }); - $(document).on('click', writeButtonSelector, function(e) { + $(document).on('click', writeButtonSelector, function (e) { var $form; e.preventDefault(); $form = $(this).closest('form'); - return $(document).triggerHandler('markdown-preview:hide', [$form]); + $(document).triggerHandler('markdown-preview:hide', [$form]); }); - -}).call(this); +}()); diff --git a/app/assets/javascripts/profile/gl_crop.js.es6 b/app/assets/javascripts/profile/gl_crop.js.es6 index b4b6da41f63..42e9847af91 100644 --- a/app/assets/javascripts/profile/gl_crop.js.es6 +++ b/app/assets/javascripts/profile/gl_crop.js.es6 @@ -1,14 +1,12 @@ -/* eslint-disable no-useless-escape, max-len, padded-blocks, 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, no-plusplus, new-parens, semi */ +/* 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 */ ((global) => { - // Matches everything but the file name const FILENAMEREGEX = /^.*[\\\/]/; class GitLabCrop { constructor(input, { filename, previewImage, modalCrop, pickImageEl, uploadImageBtn, modalCropImg, exportWidth = 200, exportHeight = 200, cropBoxWidth = 200, cropBoxHeight = 200 } = {}) { - this.onUploadImageBtnClick = this.onUploadImageBtnClick.bind(this); this.onModalHide = this.onModalHide.bind(this); this.onModalShow = this.onModalShow.bind(this); @@ -136,7 +134,7 @@ var array, binary, i, k, len, v; binary = atob(dataURL.split(',')[1]); array = []; - for (k = i = 0, len = binary.length; i < len; k = ++i) { + for (k = i = 0, len = binary.length; i < len; k = (i += 1)) { v = binary[k]; array.push(binary.charCodeAt(k)); } @@ -169,6 +167,5 @@ return this.each(function() { return $(this).data('glcrop', new GitLabCrop(this, opts)); }); - } - + }; })(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/profile/profile.js.es6 b/app/assets/javascripts/profile/profile.js.es6 index eb2fe3477a2..5aec9c813fe 100644 --- a/app/assets/javascripts/profile/profile.js.es6 +++ b/app/assets/javascripts/profile/profile.js.es6 @@ -1,8 +1,7 @@ -/* eslint-disable comma-dangle, no-unused-vars, class-methods-use-this, quotes, consistent-return, func-names, prefer-arrow-callback, space-before-function-paren, max-len, padded-blocks */ +/* eslint-disable comma-dangle, no-unused-vars, class-methods-use-this, quotes, consistent-return, func-names, prefer-arrow-callback, space-before-function-paren, max-len */ /* global Flash */ ((global) => { - class Profile { constructor({ form } = {}) { this.onSubmitForm = this.onSubmitForm.bind(this); @@ -37,19 +36,17 @@ } onSubmitForm(e) { + e.preventDefault(); return this.saveForm(); } beforeUpdateUsername() { - $('.loading-username').show(); - $(this).find('.update-success').hide(); - return $(this).find('.update-failed').hide(); + $('.loading-username', this).removeClass('hidden'); } afterUpdateUsername() { - $('.loading-username').hide(); - $(this).find('.btn-save').enable(); - return $(this).find('.loading-gif').hide(); + $('.loading-username', this).addClass('hidden'); + $('button[type=submit]', this).enable(); } onUpdateNotifs(e, data) { @@ -98,5 +95,4 @@ return new Profile(); } }); - })(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/profile/profile_bundle.js b/app/assets/javascripts/profile/profile_bundle.js index f50802bdf2e..d7f3c9fd37e 100644 --- a/app/assets/javascripts/profile/profile_bundle.js +++ b/app/assets/javascripts/profile/profile_bundle.js @@ -1,7 +1,3 @@ -/* eslint-disable func-names, space-before-function-paren */ - -/*= require_tree . */ - -(function() { - -}).call(this); +// require everything else in this directory +function requireAll(context) { return context.keys().map(context); } +requireAll(require.context('.', false, /^\.\/(?!profile_bundle).*\.(js|es6)$/)); diff --git a/app/assets/javascripts/project.js b/app/assets/javascripts/project.js index fcf3a4af956..71719917d0c 100644 --- a/app/assets/javascripts/project.js +++ b/app/assets/javascripts/project.js @@ -1,6 +1,5 @@ -/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, quotes, consistent-return, no-new, prefer-arrow-callback, no-return-assign, one-var, one-var-declaration-per-line, object-shorthand, comma-dangle, no-else-return, newline-per-chained-call, no-shadow, semi, vars-on-top, indent, prefer-template, padded-blocks, max-len */ +/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, quotes, consistent-return, no-new, prefer-arrow-callback, no-return-assign, one-var, one-var-declaration-per-line, object-shorthand, comma-dangle, no-else-return, newline-per-chained-call, no-shadow, vars-on-top, prefer-template, max-len */ /* global Cookies */ -/* global Turbolinks */ /* global ProjectSelect */ (function() { @@ -58,6 +57,11 @@ }; Project.prototype.initRefSwitcher = function() { + var refListItem = document.createElement('li'); + var refLink = document.createElement('a'); + + refLink.href = '#'; + return $('.js-project-refs-dropdown').each(function() { var $dropdown, selected; $dropdown = $(this); @@ -67,7 +71,8 @@ return $.ajax({ url: $dropdown.data('refs-url'), data: { - ref: $dropdown.data('ref') + ref: $dropdown.data('ref'), + search: term }, dataType: "json" }).done(function(refs) { @@ -76,16 +81,29 @@ }, selectable: true, filterable: true, + filterRemote: true, filterByText: true, fieldName: $dropdown.data('field-name'), renderRow: function(ref) { - var link; + var li = refListItem.cloneNode(false); + if (ref.header != null) { - return $('<li />').addClass('dropdown-header').text(ref.header); + li.className = 'dropdown-header'; + li.textContent = ref.header; } else { - link = $('<a />').attr('href', '#').addClass(ref === selected ? 'is-active' : '').text(ref).attr('data-ref', ref); - return $('<li />').append(link); + var link = refLink.cloneNode(false); + + if (ref === selected) { + link.className = 'is-active'; + } + + link.textContent = ref; + link.dataset.ref = ref; + + li.appendChild(link); } + + return li; }, id: function(obj, $el) { return $el.attr('data-ref'); @@ -94,12 +112,12 @@ return $el.text().trim(); }, clicked: function(selected, $el, e) { - e.preventDefault() + e.preventDefault(); if ($('input[name="ref"]').length) { - var $form = $dropdown.closest('form'), - action = $form.attr('action'), - divider = action.indexOf('?') < 0 ? '?' : '&'; - Turbolinks.visit(action + '' + divider + '' + $form.serialize()); + var $form = $dropdown.closest('form'); + var action = $form.attr('action'); + var divider = action.indexOf('?') < 0 ? '?' : '&'; + gl.utils.visitUrl(action + '' + divider + '' + $form.serialize()); } } }); @@ -107,7 +125,5 @@ }; return Project; - })(); - }).call(this); diff --git a/app/assets/javascripts/project_avatar.js b/app/assets/javascripts/project_avatar.js index 84f28ede4bf..a6d3ba9eb86 100644 --- a/app/assets/javascripts/project_avatar.js +++ b/app/assets/javascripts/project_avatar.js @@ -1,4 +1,4 @@ -/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, one-var, one-var-declaration-per-line, no-useless-escape, padded-blocks, max-len */ +/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, one-var, one-var-declaration-per-line, no-useless-escape, max-len */ (function() { this.ProjectAvatar = (function() { function ProjectAvatar() { @@ -16,7 +16,5 @@ } return ProjectAvatar; - })(); - }).call(this); diff --git a/app/assets/javascripts/project_find_file.js b/app/assets/javascripts/project_find_file.js index 1bd232314d0..04fe84683f3 100644 --- a/app/assets/javascripts/project_find_file.js +++ b/app/assets/javascripts/project_find_file.js @@ -1,8 +1,8 @@ -/* eslint-disable func-names, space-before-function-paren, no-var, space-before-blocks, prefer-rest-params, wrap-iife, quotes, consistent-return, one-var, one-var-declaration-per-line, no-cond-assign, max-len, object-shorthand, no-param-reassign, comma-dangle, no-plusplus, prefer-template, no-unused-vars, no-return-assign, padded-blocks */ +/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, quotes, consistent-return, one-var, one-var-declaration-per-line, no-cond-assign, max-len, object-shorthand, no-param-reassign, comma-dangle, prefer-template, no-unused-vars, no-return-assign */ /* global fuzzaldrinPlus */ (function() { - var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; + var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; }; this.ProjectFindFile = (function() { var highlighter; @@ -71,7 +71,7 @@ var blobItemUrl, filePath, html, i, j, len, matches, results; this.element.find(".tree-table > tbody").empty(); results = []; - for (i = j = 0, len = filePaths.length; j < len; i = ++j) { + for (i = j = 0, len = filePaths.length; j < len; i = (j += 1)) { filePath = filePaths[i]; if (i === 20) { break; @@ -92,7 +92,7 @@ lastIndex = 0; highlightText = ""; matchedChars = []; - for (j = 0, len = matches.length; j < len; j++) { + for (j = 0, len = matches.length; j < len; j += 1) { matchIndex = matches[j]; unmatched = text.substring(lastIndex, matchIndex); if (unmatched) { @@ -167,7 +167,5 @@ }; return ProjectFindFile; - })(); - }).call(this); diff --git a/app/assets/javascripts/project_fork.js b/app/assets/javascripts/project_fork.js index 4aedc9a2330..208f25a0e33 100644 --- a/app/assets/javascripts/project_fork.js +++ b/app/assets/javascripts/project_fork.js @@ -1,4 +1,4 @@ -/* eslint-disable func-names, space-before-function-paren, wrap-iife, prefer-arrow-callback, padded-blocks, max-len */ +/* eslint-disable func-names, space-before-function-paren, wrap-iife, prefer-arrow-callback, max-len */ (function() { this.ProjectFork = (function() { function ProjectFork() { @@ -9,7 +9,5 @@ } return ProjectFork; - })(); - }).call(this); diff --git a/app/assets/javascripts/project_import.js b/app/assets/javascripts/project_import.js index 02dafcfb865..d7943959238 100644 --- a/app/assets/javascripts/project_import.js +++ b/app/assets/javascripts/project_import.js @@ -1,16 +1,13 @@ -/* eslint-disable func-names, space-before-function-paren, wrap-iife, prefer-arrow-callback, padded-blocks, max-len */ -/* global Turbolinks */ +/* eslint-disable func-names, space-before-function-paren, wrap-iife, prefer-arrow-callback, max-len */ (function() { this.ProjectImport = (function() { function ProjectImport() { setTimeout(function() { - return Turbolinks.visit(location.href); + return gl.utils.visitUrl(location.href); }, 5000); } return ProjectImport; - })(); - }).call(this); diff --git a/app/assets/javascripts/project_label_subscription.js.es6 b/app/assets/javascripts/project_label_subscription.js.es6 index b8d6a198996..8365f7118d5 100644 --- a/app/assets/javascripts/project_label_subscription.js.es6 +++ b/app/assets/javascripts/project_label_subscription.js.es6 @@ -1,4 +1,4 @@ -/* eslint-disable wrap-iife, func-names, space-before-function-paren, object-shorthand, comma-dangle, one-var, one-var-declaration-per-line, no-restricted-syntax, prefer-const, max-len, no-param-reassign, padded-blocks */ +/* eslint-disable wrap-iife, func-names, space-before-function-paren, object-shorthand, comma-dangle, one-var, one-var-declaration-per-line, no-restricted-syntax, max-len, no-param-reassign */ (function(global) { class ProjectLabelSubscription { @@ -38,8 +38,8 @@ this.$buttons.attr('data-status', newStatus); this.$buttons.find('> span').text(newAction); - for (let button of this.$buttons) { - let $button = $(button); + for (const button of this.$buttons) { + const $button = $(button); if ($button.attr('data-original-title')) { $button.tooltip('hide').attr('data-original-title', newAction).tooltip('fixTitle'); @@ -50,5 +50,4 @@ } global.ProjectLabelSubscription = ProjectLabelSubscription; - })(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/project_new.js b/app/assets/javascripts/project_new.js index 7fc611d0dad..3aa6f6771ce 100644 --- a/app/assets/javascripts/project_new.js +++ b/app/assets/javascripts/project_new.js @@ -1,6 +1,7 @@ -/* eslint-disable func-names, space-before-function-paren, no-var, space-before-blocks, prefer-rest-params, wrap-iife, no-unused-vars, one-var, indent, no-underscore-dangle, prefer-template, no-else-return, prefer-arrow-callback, radix, padded-blocks, max-len */ +/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, no-unused-vars, one-var, no-underscore-dangle, prefer-template, no-else-return, prefer-arrow-callback, max-len */ + (function() { - var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; + var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; }; this.ProjectNew = (function() { function ProjectNew() { @@ -14,18 +15,29 @@ return $('.save-project-loader').show(); }; })(this)); + + this.initVisibilitySelect(); + this.toggleSettings(); this.toggleSettingsOnclick(); this.toggleRepoVisibility(); } + ProjectNew.prototype.initVisibilitySelect = function() { + const visibilityContainer = document.querySelector('.js-visibility-select'); + if (!visibilityContainer) return; + const visibilitySelect = new gl.VisibilitySelect(visibilityContainer); + visibilitySelect.init(); + }; + ProjectNew.prototype.toggleSettings = function() { var self = this; this.$selects.each(function () { - var $select = $(this), - className = $select.data('field').replace(/_/g, '-') - .replace('access-level', 'feature'); + var $select = $(this); + var className = $select.data('field') + .replace(/_/g, '-') + .replace('access-level', 'feature'); self._showOrHide($select, '.' + className); }); }; @@ -45,9 +57,9 @@ }; ProjectNew.prototype.toggleRepoVisibility = function () { - var $repoAccessLevel = $('.js-repo-access-level select'), - containerRegistry = document.querySelectorAll('.js-container-registry')[0], - containerRegistryCheckbox = document.getElementById('project_container_registry_enabled'); + var $repoAccessLevel = $('.js-repo-access-level select'); + var containerRegistry = document.querySelectorAll('.js-container-registry')[0]; + var containerRegistryCheckbox = document.getElementById('project_container_registry_enabled'); this.$repoSelects.find("option[value='" + $repoAccessLevel.val() + "']") .nextAll() @@ -55,11 +67,11 @@ $repoAccessLevel.off('change') .on('change', function () { - var selectedVal = parseInt($repoAccessLevel.val()); + var selectedVal = parseInt($repoAccessLevel.val(), 10); this.$repoSelects.each(function () { - var $this = $(this), - repoSelectVal = parseInt($this.val()); + var $this = $(this); + var repoSelectVal = parseInt($this.val(), 10); $this.find('option').show(); @@ -88,7 +100,5 @@ }; return ProjectNew; - })(); - }).call(this); diff --git a/app/assets/javascripts/project_select.js b/app/assets/javascripts/project_select.js index 650996700ba..7b5e9953598 100644 --- a/app/assets/javascripts/project_select.js +++ b/app/assets/javascripts/project_select.js @@ -1,4 +1,4 @@ -/* eslint-disable func-names, space-before-function-paren, wrap-iife, prefer-arrow-callback, no-var, comma-dangle, object-shorthand, one-var, one-var-declaration-per-line, no-else-return, quotes, padded-blocks, max-len */ +/* eslint-disable func-names, space-before-function-paren, wrap-iife, prefer-arrow-callback, no-var, comma-dangle, object-shorthand, one-var, one-var-declaration-per-line, no-else-return, quotes, max-len */ /* global Api */ (function() { @@ -15,6 +15,7 @@ }, data: function(term, callback) { var finalCallback, projectsCallback; + var orderBy = $dropdown.data('order-by'); finalCallback = function(projects) { return callback(projects); }; @@ -34,7 +35,7 @@ if (this.groupId) { return Api.groupProjects(this.groupId, term, projectsCallback); } else { - return Api.projects(term, this.orderBy, projectsCallback); + return Api.projects(term, orderBy, projectsCallback); } }, url: function(project) { @@ -99,7 +100,5 @@ } return ProjectSelect; - })(); - }).call(this); diff --git a/app/assets/javascripts/project_show.js b/app/assets/javascripts/project_show.js index eaf4c03d573..aad130cf267 100644 --- a/app/assets/javascripts/project_show.js +++ b/app/assets/javascripts/project_show.js @@ -1,12 +1,11 @@ -/* eslint-disable func-names, space-before-function-paren, wrap-iife, padded-blocks */ +/* eslint-disable func-names, space-before-function-paren, wrap-iife */ + (function() { this.ProjectShow = (function() { function ProjectShow() {} return ProjectShow; - })(); - }).call(this); // I kept class for future diff --git a/app/assets/javascripts/projects_list.js b/app/assets/javascripts/projects_list.js index 4548dc68fe1..69a11dfaf39 100644 --- a/app/assets/javascripts/projects_list.js +++ b/app/assets/javascripts/projects_list.js @@ -1,4 +1,4 @@ -/* eslint-disable func-names, space-before-function-paren, object-shorthand, quotes, no-var, one-var, one-var-declaration-per-line, prefer-arrow-callback, consistent-return, no-unused-vars, camelcase, prefer-template, comma-dangle, padded-blocks, max-len */ +/* eslint-disable func-names, space-before-function-paren, object-shorthand, quotes, no-var, one-var, one-var-declaration-per-line, prefer-arrow-callback, consistent-return, no-unused-vars, camelcase, prefer-template, comma-dangle, max-len */ (function() { window.ProjectsList = { @@ -47,5 +47,4 @@ }); } }; - }).call(this); diff --git a/app/assets/javascripts/protected_branches/protected_branch_access_dropdown.js.es6 b/app/assets/javascripts/protected_branches/protected_branch_access_dropdown.js.es6 index 4aef1c84b56..e7fff57ff45 100644 --- a/app/assets/javascripts/protected_branches/protected_branch_access_dropdown.js.es6 +++ b/app/assets/javascripts/protected_branches/protected_branch_access_dropdown.js.es6 @@ -1,7 +1,7 @@ -/* eslint-disable arrow-parens, no-param-reassign, no-irregular-whitespace, object-shorthand, no-else-return, comma-dangle, semi, padded-blocks, max-len */ +/* eslint-disable arrow-parens, no-param-reassign, object-shorthand, no-else-return, comma-dangle, max-len */ (global => { - global.gl = global.gl || {}; + global.gl = global.gl || {}; gl.ProtectedBranchAccessDropdown = class { constructor(options) { @@ -25,6 +25,5 @@ } }); } - } - + }; })(window); diff --git a/app/assets/javascripts/protected_branches/protected_branch_create.js.es6 b/app/assets/javascripts/protected_branches/protected_branch_create.js.es6 index f26fba979a4..57ea2f52814 100644 --- a/app/assets/javascripts/protected_branches/protected_branch_create.js.es6 +++ b/app/assets/javascripts/protected_branches/protected_branch_create.js.es6 @@ -1,8 +1,8 @@ -/* eslint-disable no-new, arrow-parens, no-param-reassign, no-irregular-whitespace, comma-dangle, padded-blocks, semi, max-len */ +/* eslint-disable no-new, arrow-parens, no-param-reassign, comma-dangle, max-len */ /* global ProtectedBranchDropdown */ (global => { - global.gl = global.gl || {}; + global.gl = global.gl || {}; gl.ProtectedBranchCreate = class { constructor() { @@ -44,7 +44,6 @@ // This will run after clicked callback onSelect() { - // Enable submit button const $branchInput = this.$wrap.find('input[name="protected_branch[name]"]'); const $allowedToMergeInput = this.$wrap.find('input[name="protected_branch[merge_access_levels_attributes][0][access_level]"]'); @@ -52,6 +51,5 @@ this.$form.find('input[type="submit"]').attr('disabled', !($branchInput.val() && $allowedToMergeInput.length && $allowedToPushInput.length)); } - } - + }; })(window); diff --git a/app/assets/javascripts/protected_branches/protected_branch_dropdown.js.es6 b/app/assets/javascripts/protected_branches/protected_branch_dropdown.js.es6 index 08264ad3d2f..5cf28aa7a73 100644 --- a/app/assets/javascripts/protected_branches/protected_branch_dropdown.js.es6 +++ b/app/assets/javascripts/protected_branches/protected_branch_dropdown.js.es6 @@ -49,7 +49,7 @@ class ProtectedBranchDropdown { onClickCreateWildcard() { // Refresh the dropdown's data, which ends up calling `getProtectedBranches` this.$dropdown.data('glDropdown').remote.execute(); - this.$dropdown.data('glDropdown').selectRowAtIndex(0); + this.$dropdown.data('glDropdown').selectRowAtIndex(); } getProtectedBranches(term, callback) { @@ -76,3 +76,5 @@ class ProtectedBranchDropdown { this.$dropdownFooter.toggleClass('hidden', !branchName); } } + +window.ProtectedBranchDropdown = ProtectedBranchDropdown; diff --git a/app/assets/javascripts/protected_branches/protected_branch_edit.js.es6 b/app/assets/javascripts/protected_branches/protected_branch_edit.js.es6 index 4ff2fa5a80f..149e511451e 100644 --- a/app/assets/javascripts/protected_branches/protected_branch_edit.js.es6 +++ b/app/assets/javascripts/protected_branches/protected_branch_edit.js.es6 @@ -1,8 +1,8 @@ -/* eslint-disable no-new, arrow-parens, no-param-reassign, no-irregular-whitespace, padded-blocks, comma-dangle, no-trailing-spaces, semi, max-len */ +/* eslint-disable no-new, arrow-parens, no-param-reassign, comma-dangle, max-len */ /* global Flash */ (global => { - global.gl = global.gl || {}; + global.gl = global.gl || {}; gl.ProtectedBranchEdit = class { constructor(options) { @@ -14,7 +14,6 @@ } buildDropdowns() { - // Allowed to merge dropdown new gl.ProtectedBranchAccessDropdown({ $dropdown: this.$allowedToMergeDropdown, @@ -63,6 +62,5 @@ } }); } - } - + }; })(window); diff --git a/app/assets/javascripts/protected_branches/protected_branch_edit_list.js.es6 b/app/assets/javascripts/protected_branches/protected_branch_edit_list.js.es6 index b6972ef2e16..336fa6c57a7 100644 --- a/app/assets/javascripts/protected_branches/protected_branch_edit_list.js.es6 +++ b/app/assets/javascripts/protected_branches/protected_branch_edit_list.js.es6 @@ -1,10 +1,10 @@ -/* eslint-disable arrow-parens, no-param-reassign, no-irregular-whitespace, no-new, comma-dangle, semi, padded-blocks, max-len */ +/* eslint-disable arrow-parens, no-param-reassign, no-new, comma-dangle */ (global => { - global.gl = global.gl || {}; + global.gl = global.gl || {}; gl.ProtectedBranchEditList = class { - constructor() { + constructor() { this.$wrap = $('.protected-branches-list'); // Build edit forms @@ -14,6 +14,5 @@ }); }); } - } - + }; })(window); diff --git a/app/assets/javascripts/protected_branches/protected_branches_bundle.js b/app/assets/javascripts/protected_branches/protected_branches_bundle.js index 15b3affd469..ffb66caf5f4 100644 --- a/app/assets/javascripts/protected_branches/protected_branches_bundle.js +++ b/app/assets/javascripts/protected_branches/protected_branches_bundle.js @@ -1 +1,3 @@ -/*= require_tree . */ +// require everything else in this directory +function requireAll(context) { return context.keys().map(context); } +requireAll(require.context('.', false, /^\.\/(?!protected_branches_bundle).*\.(js|es6)$/)); diff --git a/app/assets/javascripts/render_gfm.js b/app/assets/javascripts/render_gfm.js index bbb2f186655..bdbad93ad04 100644 --- a/app/assets/javascripts/render_gfm.js +++ b/app/assets/javascripts/render_gfm.js @@ -1,4 +1,4 @@ -/* eslint-disable func-names, space-before-function-paren, consistent-return, no-var, no-undef, no-else-return, prefer-arrow-callback, padded-blocks, max-len */ +/* eslint-disable func-names, space-before-function-paren, consistent-return, no-var, no-undef, no-else-return, prefer-arrow-callback, max-len */ // Render Gitlab flavoured Markdown // // Delegates to syntax highlight and render math @@ -9,8 +9,7 @@ this.find('.js-render-math').renderMath(); }; - $(document).on('ready page:load', function() { + $(document).on('ready load', function() { return $('body').renderGFM(); }); - }).call(this); diff --git a/app/assets/javascripts/render_math.js b/app/assets/javascripts/render_math.js index 209e7a8661d..6cef449babf 100644 --- a/app/assets/javascripts/render_math.js +++ b/app/assets/javascripts/render_math.js @@ -1,4 +1,4 @@ -/* eslint-disable func-names, space-before-function-paren, consistent-return, no-var, no-undef, no-else-return, prefer-arrow-callback, padded-blocks, max-len, no-console */ +/* eslint-disable func-names, space-before-function-paren, consistent-return, no-var, no-undef, no-else-return, prefer-arrow-callback, max-len, no-console */ // Renders math using KaTeX in any element with the // `js-render-math` class // @@ -51,5 +51,4 @@ }); } }; - }).call(this); diff --git a/app/assets/javascripts/right_sidebar.js b/app/assets/javascripts/right_sidebar.js index b1e844b7302..76a0f993ea0 100644 --- a/app/assets/javascripts/right_sidebar.js +++ b/app/assets/javascripts/right_sidebar.js @@ -1,8 +1,8 @@ -/* eslint-disable func-names, space-before-function-paren, no-var, space-before-blocks, prefer-rest-params, wrap-iife, no-unused-vars, semi, consistent-return, one-var, one-var-declaration-per-line, quotes, prefer-template, object-shorthand, comma-dangle, no-else-return, no-param-reassign, padded-blocks, max-len */ +/* 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 */ /* global Cookies */ (function() { - var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; + var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; }; this.Sidebar = (function() { function Sidebar(currentUser) { @@ -18,7 +18,7 @@ $('.dropdown').off('loading.gl.dropdown'); $('.dropdown').off('loaded.gl.dropdown'); $(document).off('click', '.js-sidebar-toggle'); - } + }; Sidebar.prototype.addEventListeners = function() { this.sidebar.on('click', '.sidebar-collapsed-icon', this, this.sidebarCollapseClicked); @@ -200,7 +200,5 @@ }; return Sidebar; - })(); - }).call(this); diff --git a/app/assets/javascripts/search.js b/app/assets/javascripts/search.js index 4b6ebadeac7..b1c0dc37b4d 100644 --- a/app/assets/javascripts/search.js +++ b/app/assets/javascripts/search.js @@ -1,4 +1,4 @@ -/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, one-var, one-var-declaration-per-line, object-shorthand, prefer-arrow-callback, comma-dangle, prefer-template, quotes, no-else-return, padded-blocks, max-len */ +/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, one-var, one-var-declaration-per-line, object-shorthand, prefer-arrow-callback, comma-dangle, prefer-template, quotes, no-else-return, max-len */ /* global Api */ (function() { @@ -12,10 +12,13 @@ selectable: true, filterable: true, fieldName: 'group_id', + search: { + fields: ['full_name'] + }, data: function(term, callback) { return Api.groups(term, {}, function(data) { data.unshift({ - name: 'Any' + full_name: 'Any' }); data.splice(1, 0, 'divider'); return callback(data); @@ -25,10 +28,10 @@ return obj.id; }, text: function(obj) { - return obj.name; + return obj.full_name; }, toggleLabel: function(obj) { - return ($groupDropdown.data('default-label')) + " " + obj.name; + return ($groupDropdown.data('default-label')) + " " + obj.full_name; }, clicked: (function(_this) { return function() { @@ -40,6 +43,9 @@ selectable: true, filterable: true, fieldName: 'project_id', + search: { + fields: ['name'] + }, data: function(term, callback) { return Api.projects(term, 'id', function(data) { data.unshift({ @@ -90,7 +96,5 @@ }; return Search; - })(); - }).call(this); diff --git a/app/assets/javascripts/search_autocomplete.js.es6 b/app/assets/javascripts/search_autocomplete.js.es6 index 437f5dbbf7d..6250e75d407 100644 --- a/app/assets/javascripts/search_autocomplete.js.es6 +++ b/app/assets/javascripts/search_autocomplete.js.es6 @@ -1,7 +1,6 @@ -/* eslint-disable comma-dangle, no-return-assign, one-var, no-var, no-underscore-dangle, one-var-declaration-per-line, no-unused-vars, no-cond-assign, consistent-return, object-shorthand, prefer-arrow-callback, func-names, space-before-function-paren, no-plusplus, prefer-template, quotes, class-methods-use-this, no-unused-expressions, no-sequences, wrap-iife, no-lonely-if, no-else-return, no-param-reassign, vars-on-top, padded-blocks, no-extra-semi, indent, max-len */ +/* eslint-disable comma-dangle, no-return-assign, one-var, no-var, no-underscore-dangle, one-var-declaration-per-line, no-unused-vars, no-cond-assign, consistent-return, object-shorthand, prefer-arrow-callback, func-names, space-before-function-paren, prefer-template, quotes, class-methods-use-this, no-unused-expressions, no-sequences, wrap-iife, no-lonely-if, no-else-return, no-param-reassign, vars-on-top, max-len */ ((global) => { - const KEYCODE = { ESCAPE: 27, BACKSPACE: 8, @@ -70,12 +69,17 @@ search: { fields: ['text'] }, + id: this.getSearchText, data: this.getData.bind(this), selectable: true, clicked: this.onClick.bind(this) }); } + getSearchText(selectedObject, el) { + return selectedObject.id ? selectedObject.text : ''; + } + getData(term, callback) { var _this, contents, jqXHR; _this = this; @@ -105,7 +109,7 @@ data = []; // List results firstCategory = true; - for (i = 0, len = response.length; i < len; i++) { + for (i = 0, len = response.length; i < len; i += 1) { suggestion = response[i]; // Add group header before list each group if (lastCategory !== suggestion.category) { @@ -142,8 +146,9 @@ } getCategoryContents() { - var dashboardOptions, groupOptions, issuesPath, items, mrPath, name, options, projectOptions, userId, utils; + var dashboardOptions, groupOptions, issuesPath, items, mrPath, name, options, projectOptions, userId, userName, utils; userId = gon.current_user_id; + userName = gon.current_username; utils = gl.utils, projectOptions = gl.projectOptions, groupOptions = gl.groupOptions, dashboardOptions = gl.dashboardOptions; if (utils.isInGroupsPage() && groupOptions) { options = groupOptions[utils.getGroupSlug()]; @@ -158,10 +163,10 @@ header: "" + name }, { text: 'Issues assigned to me', - url: issuesPath + "/?assignee_id=" + userId + url: issuesPath + "/?assignee_username=" + userName }, { text: "Issues I've created", - url: issuesPath + "/?author_id=" + userId + url: issuesPath + "/?author_username=" + userName }, 'separator', { text: 'Merge requests assigned to me', url: mrPath + "/?assignee_id=" + userId @@ -215,7 +220,7 @@ this.dropdown.addClass('open').trigger('shown.bs.dropdown'); return this.searchInput.removeClass('disabled'); } - }; + } // Saves last length of the entered text onSearchInputKeyDown() { @@ -279,12 +284,12 @@ return this.searchInput.val(); } - onClearInputClick(e) { + onClearInputClick(e) { e.preventDefault(); return this.searchInput.val('').focus(); } - onSearchInputBlur(e) { + onSearchInputBlur(e) { this.isFocused = false; this.wrap.removeClass('search-active'); // If input is blank then restore state @@ -304,12 +309,12 @@ hasLocationBadge() { return this.wrap.is('.has-location-badge'); - }; + } restoreOriginalState() { var i, input, inputs, len; inputs = Object.keys(this.originalState); - for (i = 0, len = inputs.length; i < len; i++) { + for (i = 0, len = inputs.length; i < len; i += 1) { input = inputs[i]; this.getElement("#" + input).val(this.originalState[input]); } @@ -330,7 +335,7 @@ var i, input, inputs, len, results; inputs = Object.keys(this.originalState); results = []; - for (i = 0, len = inputs.length; i < len; i++) { + for (i = 0, len = inputs.length; i < len; i += 1) { input = inputs[i]; // _location isnt a input if (input === '_location') { @@ -360,11 +365,11 @@ var html; html = "<ul> <li><a class='dropdown-menu-empty-link is-focused'>Loading...</a></li> </ul>"; return this.dropdownContent.html(html); - }; + } onClick(item, $el, e) { if (location.pathname.indexOf(item.url) !== -1) { - e.preventDefault(); + if (!e.metaKey) e.preventDefault(); if (!this.badgePresent) { if (item.category === 'Projects') { this.projectInputEl.val(item.id); @@ -383,8 +388,7 @@ this.disableAutocomplete(); return this.searchInput.val('').focus(); } - }; - + } } global.SearchAutocomplete = SearchAutocomplete; @@ -425,5 +429,4 @@ }; } }); - })(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/shortcuts.js b/app/assets/javascripts/shortcuts.js index 5ea00f408f4..c6d9b007ad1 100644 --- a/app/assets/javascripts/shortcuts.js +++ b/app/assets/javascripts/shortcuts.js @@ -1,10 +1,9 @@ -/* eslint-disable func-names, space-before-function-paren, no-var, space-before-blocks, prefer-rest-params, wrap-iife, quotes, prefer-arrow-callback, consistent-return, object-shorthand, no-unused-vars, one-var, one-var-declaration-per-line, no-plusplus, no-else-return, comma-dangle, padded-blocks, max-len */ +/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, quotes, prefer-arrow-callback, consistent-return, object-shorthand, no-unused-vars, one-var, one-var-declaration-per-line, no-else-return, comma-dangle, max-len */ /* global Mousetrap */ -/* global Turbolinks */ /* global findFileURL */ (function() { - var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; + var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; }; this.Shortcuts = (function() { function Shortcuts(skipResetBindings) { @@ -23,7 +22,7 @@ Mousetrap.bind(['ctrl+shift+p', 'command+shift+p'], this.toggleMarkdownPreview); if (typeof findFileURL !== "undefined" && findFileURL !== null) { Mousetrap.bind('t', function() { - return Turbolinks.visit(findFileURL); + return gl.utils.visitUrl(findFileURL); }); } } @@ -51,7 +50,7 @@ var i, l, len, results; if (location && location.length > 0) { results = []; - for (i = 0, len = location.length; i < len; i++) { + for (i = 0, len = location.length; i < len; i += 1) { l = location[i]; results.push($(l).show()); } @@ -78,7 +77,6 @@ }; return Shortcuts; - })(); $(document).on('click.more_help', '.js-more-help-button', function(e) { @@ -99,5 +97,4 @@ } }; })(); - }).call(this); diff --git a/app/assets/javascripts/shortcuts_blob.js b/app/assets/javascripts/shortcuts_blob.js deleted file mode 100644 index c26903038b4..00000000000 --- a/app/assets/javascripts/shortcuts_blob.js +++ /dev/null @@ -1,31 +0,0 @@ -/* eslint-disable func-names, space-before-function-paren, max-len, one-var, no-var, no-restricted-syntax, vars-on-top, no-use-before-define, no-param-reassign, new-cap, no-underscore-dangle, wrap-iife, consistent-return, padded-blocks */ -/* global Shortcuts */ -/* global Mousetrap */ - -/*= require shortcuts */ - -(function() { - var 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; }, - hasProp = {}.hasOwnProperty; - - this.ShortcutsBlob = (function(superClass) { - extend(ShortcutsBlob, superClass); - - function ShortcutsBlob(skipResetBindings) { - ShortcutsBlob.__super__.constructor.call(this, skipResetBindings); - Mousetrap.bind('y', ShortcutsBlob.copyToClipboard); - } - - ShortcutsBlob.copyToClipboard = function() { - var clipboardButton; - clipboardButton = $('.btn-clipboard'); - if (clipboardButton) { - return clipboardButton.click(); - } - }; - - return ShortcutsBlob; - - })(Shortcuts); - -}).call(this); diff --git a/app/assets/javascripts/shortcuts_blob.js.es6 b/app/assets/javascripts/shortcuts_blob.js.es6 new file mode 100644 index 00000000000..bfe90aef71e --- /dev/null +++ b/app/assets/javascripts/shortcuts_blob.js.es6 @@ -0,0 +1,29 @@ +/* global Mousetrap */ +/* global Shortcuts */ + +require('./shortcuts'); + +const defaults = { + skipResetBindings: false, + fileBlobPermalinkUrl: null, +}; + +class ShortcutsBlob extends Shortcuts { + constructor(opts) { + const options = Object.assign({}, defaults, opts); + super(options.skipResetBindings); + this.options = options; + + Mousetrap.bind('y', this.moveToFilePermalink.bind(this)); + } + + moveToFilePermalink() { + if (this.options.fileBlobPermalinkUrl) { + const hash = gl.utils.getLocationHash(); + const hashUrlString = hash ? `#${hash}` : ''; + gl.utils.visitUrl(`${this.options.fileBlobPermalinkUrl}${hashUrlString}`); + } + } +} + +module.exports = ShortcutsBlob; diff --git a/app/assets/javascripts/shortcuts_dashboard_navigation.js b/app/assets/javascripts/shortcuts_dashboard_navigation.js index 4549742bbcb..7378b322426 100644 --- a/app/assets/javascripts/shortcuts_dashboard_navigation.js +++ b/app/assets/javascripts/shortcuts_dashboard_navigation.js @@ -1,8 +1,8 @@ -/* eslint-disable func-names, space-before-function-paren, max-len, one-var, no-var, no-restricted-syntax, vars-on-top, no-use-before-define, no-param-reassign, new-cap, no-underscore-dangle, wrap-iife, prefer-arrow-callback, consistent-return, no-return-assign, padded-blocks */ +/* eslint-disable func-names, space-before-function-paren, max-len, one-var, no-var, no-restricted-syntax, vars-on-top, no-use-before-define, no-param-reassign, new-cap, no-underscore-dangle, wrap-iife, prefer-arrow-callback, consistent-return, no-return-assign */ /* global Mousetrap */ /* global Shortcuts */ -/*= require shortcuts */ +require('./shortcuts'); (function() { var 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; }, @@ -36,7 +36,5 @@ }; return ShortcutsDashboardNavigation; - })(Shortcuts); - }).call(this); diff --git a/app/assets/javascripts/shortcuts_find_file.js b/app/assets/javascripts/shortcuts_find_file.js index 3a81380eef0..36e379d634d 100644 --- a/app/assets/javascripts/shortcuts_find_file.js +++ b/app/assets/javascripts/shortcuts_find_file.js @@ -1,8 +1,8 @@ -/* eslint-disable func-names, space-before-function-paren, max-len, one-var, no-var, no-restricted-syntax, vars-on-top, no-use-before-define, no-param-reassign, new-cap, no-underscore-dangle, wrap-iife, padded-blocks */ +/* eslint-disable func-names, space-before-function-paren, max-len, one-var, no-var, no-restricted-syntax, vars-on-top, no-use-before-define, no-param-reassign, new-cap, no-underscore-dangle, wrap-iife */ /* global Mousetrap */ /* global ShortcutsNavigation */ -/*= require shortcuts_navigation */ +require('./shortcuts_navigation'); (function() { var 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; }, @@ -34,7 +34,5 @@ } return ShortcutsFindFile; - })(ShortcutsNavigation); - }).call(this); diff --git a/app/assets/javascripts/shortcuts_issuable.js b/app/assets/javascripts/shortcuts_issuable.js index b892fbc3393..b841abb754d 100644 --- a/app/assets/javascripts/shortcuts_issuable.js +++ b/app/assets/javascripts/shortcuts_issuable.js @@ -1,11 +1,10 @@ -/* eslint-disable func-names, space-before-function-paren, max-len, no-var, one-var, no-restricted-syntax, vars-on-top, no-use-before-define, no-param-reassign, new-cap, no-underscore-dangle, wrap-iife, one-var-declaration-per-line, quotes, prefer-arrow-callback, consistent-return, prefer-template, no-mixed-operators, padded-blocks */ +/* eslint-disable func-names, space-before-function-paren, max-len, no-var, one-var, no-restricted-syntax, vars-on-top, no-use-before-define, no-param-reassign, new-cap, no-underscore-dangle, wrap-iife, one-var-declaration-per-line, quotes, prefer-arrow-callback, consistent-return, prefer-template, no-mixed-operators */ /* global Mousetrap */ -/* global Turbolinks */ /* global ShortcutsNavigation */ /* global sidebar */ -/*= require mousetrap */ -/*= require shortcuts_navigation */ +require('mousetrap'); +require('./shortcuts_navigation'); (function() { var 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; }, @@ -39,35 +38,48 @@ } ShortcutsIssuable.prototype.replyWithSelectedText = function() { - var quote, replyField, selected, separator; - if (window.getSelection) { - selected = window.getSelection().toString(); - replyField = $('.js-main-target-form #note_note'); - if (selected.trim() === "") { - return; - } - // Put a '>' character before each non-empty line in the selection - quote = _.map(selected.split("\n"), function(val) { - if (val.trim() !== '') { - return "> " + val + "\n"; - } - }); - // If replyField already has some content, add a newline before our quote - separator = replyField.val().trim() !== "" && "\n" || ''; - replyField.val(function(_, current) { - return current + separator + quote.join('') + "\n"; - }); - // Trigger autosave for the added text - replyField.trigger('input'); - // Focus the input field - return replyField.focus(); + var quote, documentFragment, selected, separator; + var replyField = $('.js-main-target-form #note_note'); + + documentFragment = window.gl.utils.getSelectedFragment(); + if (!documentFragment) { + replyField.focus(); + return; + } + + // If the documentFragment contains more than just Markdown, don't copy as GFM. + if (documentFragment.querySelector('.md, .wiki')) return; + + selected = window.gl.CopyAsGFM.nodeToGFM(documentFragment); + + if (selected.trim() === "") { + return; } + quote = _.map(selected.split("\n"), function(val) { + return ("> " + val).trim() + "\n"; + }); + // If replyField already has some content, add a newline before our quote + separator = replyField.val().trim() !== "" && "\n\n" || ''; + replyField.val(function(_, current) { + return current + separator + quote.join('') + "\n"; + }); + + // Trigger autosave + replyField.trigger('input'); + + // Trigger autosize + var event = document.createEvent('Event'); + event.initEvent('autosize:update', true, false); + replyField.get(0).dispatchEvent(event); + + // Focus the input field + return replyField.focus(); }; ShortcutsIssuable.prototype.editIssue = function() { var $editBtn; $editBtn = $('.issuable-edit'); - return Turbolinks.visit($editBtn.attr('href')); + return gl.utils.visitUrl($editBtn.attr('href')); }; ShortcutsIssuable.prototype.openSidebarDropdown = function(name) { @@ -76,7 +88,5 @@ }; return ShortcutsIssuable; - })(ShortcutsNavigation); - }).call(this); diff --git a/app/assets/javascripts/shortcuts_navigation.js b/app/assets/javascripts/shortcuts_navigation.js index 0776d0a9b67..cb5f2c53ea6 100644 --- a/app/assets/javascripts/shortcuts_navigation.js +++ b/app/assets/javascripts/shortcuts_navigation.js @@ -1,8 +1,8 @@ -/* eslint-disable func-names, space-before-function-paren, max-len, no-var, one-var, no-restricted-syntax, vars-on-top, no-use-before-define, no-param-reassign, new-cap, no-underscore-dangle, wrap-iife, prefer-arrow-callback, consistent-return, no-return-assign, padded-blocks */ +/* eslint-disable func-names, space-before-function-paren, max-len, no-var, one-var, no-restricted-syntax, vars-on-top, no-use-before-define, no-param-reassign, new-cap, no-underscore-dangle, wrap-iife, prefer-arrow-callback, consistent-return, no-return-assign */ /* global Mousetrap */ /* global Shortcuts */ -/*= require shortcuts */ +require('./shortcuts'); (function() { var 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; }, @@ -64,7 +64,5 @@ }; return ShortcutsNavigation; - })(Shortcuts); - }).call(this); diff --git a/app/assets/javascripts/shortcuts_network.js b/app/assets/javascripts/shortcuts_network.js index ecc3fab84c3..651957f5325 100644 --- a/app/assets/javascripts/shortcuts_network.js +++ b/app/assets/javascripts/shortcuts_network.js @@ -1,8 +1,8 @@ -/* eslint-disable func-names, space-before-function-paren, max-len, no-var, one-var, no-restricted-syntax, vars-on-top, no-use-before-define, no-param-reassign, new-cap, no-underscore-dangle, wrap-iife, padded-blocks, max-len */ +/* eslint-disable func-names, space-before-function-paren, max-len, no-var, one-var, no-restricted-syntax, vars-on-top, no-use-before-define, no-param-reassign, new-cap, no-underscore-dangle, wrap-iife, max-len */ /* global Mousetrap */ /* global ShortcutsNavigation */ -/*= require shortcuts_navigation */ +require('./shortcuts_navigation'); (function() { var 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; }, @@ -24,7 +24,5 @@ } return ShortcutsNetwork; - })(ShortcutsNavigation); - }).call(this); diff --git a/app/assets/javascripts/sidebar.js.es6 b/app/assets/javascripts/sidebar.js.es6 index 9790a44972d..cbb2ae9f1bd 100644 --- a/app/assets/javascripts/sidebar.js.es6 +++ b/app/assets/javascripts/sidebar.js.es6 @@ -1,9 +1,7 @@ -/* eslint-disable arrow-parens, class-methods-use-this, no-param-reassign, padded-blocks */ +/* eslint-disable arrow-parens, class-methods-use-this, no-param-reassign */ /* global Cookies */ -((global) => { - let singleton; - +(() => { const pinnedStateCookie = 'pin_nav'; const sidebarBreakpoint = 1024; @@ -23,11 +21,12 @@ class Sidebar { constructor() { - if (!singleton) { - singleton = this; - singleton.init(); + if (!Sidebar.singleton) { + Sidebar.singleton = this; + Sidebar.singleton.init(); } - return singleton; + + return Sidebar.singleton; } init() { @@ -39,8 +38,8 @@ $(document) .on('click', sidebarToggleSelector, () => this.toggleSidebar()) .on('click', pinnedToggleSelector, () => this.togglePinnedState()) - .on('click', 'html, body', (e) => this.handleClickEvent(e)) - .on('page:change', () => this.renderState()) + .on('click', 'html, body, a, button', (e) => this.handleClickEvent(e)) + .on('DOMContentLoaded', () => this.renderState()) .on('todo:toggle', (e, count) => this.updateTodoCount(count)); this.renderState(); } @@ -88,11 +87,12 @@ $pinnedToggle.attr('title', tooltipText).tooltip('fixTitle').tooltip(tooltipState); if (this.isExpanded) { - setTimeout(() => $(sidebarContentSelector).niceScroll().updateScrollBar(), 200); + const sidebarContent = $(sidebarContentSelector); + setTimeout(() => { sidebarContent.niceScroll().updateScrollBar(); }, 200); } } } - global.Sidebar = Sidebar; - -})(window.gl || (window.gl = {})); + window.gl = window.gl || {}; + gl.Sidebar = Sidebar; +})(); diff --git a/app/assets/javascripts/single_file_diff.js b/app/assets/javascripts/single_file_diff.js index ac8603ccd10..5b20c63384c 100644 --- a/app/assets/javascripts/single_file_diff.js +++ b/app/assets/javascripts/single_file_diff.js @@ -1,7 +1,7 @@ -/* eslint-disable func-names, space-before-function-paren, no-var, space-before-blocks, prefer-rest-params, wrap-iife, one-var, one-var-declaration-per-line, consistent-return, no-param-reassign, padded-blocks, max-len */ +/* eslint-disable func-names, prefer-arrow-callback, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, one-var, one-var-declaration-per-line, consistent-return, no-param-reassign, max-len */ (function() { - var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; + var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; }; window.SingleFileDiff = (function() { var COLLAPSED_HTML, ERROR_HTML, LOADING_HTML, WRAPPER; @@ -14,8 +14,7 @@ COLLAPSED_HTML = '<div class="nothing-here-block diff-collapsed">This diff is collapsed. <a class="click-to-expand">Click to expand it.</a></div>'; - function SingleFileDiff(file, forceLoad, cb) { - var clickTarget; + function SingleFileDiff(file) { this.file = file; this.toggleDiff = bind(this.toggleDiff, this); this.content = $('.diff-content', this.file); @@ -33,14 +32,13 @@ this.content.after(this.collapsedContent); this.$toggleIcon.addClass('fa-caret-down'); } - clickTarget = $('.file-title, .click-to-expand', this.file).on('click', this.toggleDiff); - if (forceLoad) { - this.toggleDiff({ target: clickTarget }, cb); - } + + $('.file-title, .click-to-expand', this.file).on('click', (function (e) { + this.toggleDiff($(e.target)); + }).bind(this)); } - SingleFileDiff.prototype.toggleDiff = function(e, cb) { - var $target = $(e.target); + SingleFileDiff.prototype.toggleDiff = function($target, cb) { if (!$target.hasClass('file-title') && !$target.hasClass('click-to-expand') && !$target.hasClass('diff-toggle-caret')) return; this.isOpen = !this.isOpen; if (!this.isOpen && !this.hasError) { @@ -88,15 +86,13 @@ }; return SingleFileDiff; - })(); - $.fn.singleFileDiff = function(forceLoad, cb) { + $.fn.singleFileDiff = function() { return this.each(function() { - if (!$.data(this, 'singleFileDiff') || forceLoad) { - return $.data(this, 'singleFileDiff', new window.SingleFileDiff(this, forceLoad, cb)); + if (!$.data(this, 'singleFileDiff')) { + return $.data(this, 'singleFileDiff', new window.SingleFileDiff(this)); } }); }; - }).call(this); diff --git a/app/assets/javascripts/smart_interval.js.es6 b/app/assets/javascripts/smart_interval.js.es6 index 40f67637c7c..d1bdc353be2 100644 --- a/app/assets/javascripts/smart_interval.js.es6 +++ b/app/assets/javascripts/smart_interval.js.es6 @@ -89,7 +89,7 @@ destroy() { this.cancel(); document.removeEventListener('visibilitychange', this.handleVisibilityChange); - $(document).off('visibilitychange').off('page:before-unload'); + $(document).off('visibilitychange').off('beforeunload'); } /* private */ @@ -111,8 +111,9 @@ } initPageUnloadHandling() { + // TODO: Consider refactoring in light of turbolinks removal. // prevent interval continuing after page change, when kept in cache by Turbolinks - $(document).on('page:before-unload', () => this.cancel()); + $(document).on('beforeunload', () => this.cancel()); } handleVisibilityChange(e) { diff --git a/app/assets/javascripts/snippet/snippet_bundle.js b/app/assets/javascripts/snippet/snippet_bundle.js index 18512d179b3..64f9065be42 100644 --- a/app/assets/javascripts/snippet/snippet_bundle.js +++ b/app/assets/javascripts/snippet/snippet_bundle.js @@ -1,15 +1,16 @@ -/* eslint-disable func-names, space-before-function-paren, prefer-arrow-callback, no-var, quotes, semi, padded-blocks, max-len */ +/* eslint-disable func-names, space-before-function-paren, prefer-arrow-callback, no-var, quotes, max-len */ /* global ace */ -/*= require_tree . */ +// require everything else in this directory +function requireAll(context) { return context.keys().map(context); } +requireAll(require.context('.', false, /^\.\/(?!snippet_bundle).*\.(js|es6)$/)); (function() { $(function() { - var editor = ace.edit("editor") + var editor = ace.edit("editor"); $(".snippet-form-holder form").on('submit', function() { $(".snippet-file-content").val(editor.getValue()); }); }); - }).call(this); diff --git a/app/assets/javascripts/snippets_list.js.es6 b/app/assets/javascripts/snippets_list.js.es6 index 6f913326a3a..2128007113f 100644 --- a/app/assets/javascripts/snippets_list.js.es6 +++ b/app/assets/javascripts/snippets_list.js.es6 @@ -1,4 +1,4 @@ -/* eslint-disable arrow-parens, no-param-reassign, space-before-function-paren, func-names, no-var, semi, max-len */ +/* eslint-disable arrow-parens, no-param-reassign, space-before-function-paren, func-names, no-var, max-len */ (global => { global.gl = global.gl || {}; @@ -9,5 +9,5 @@ $holder.find('.pagination').on('ajax:success', (e, data) => { $holder.replaceWith(data.html); }); - } + }; })(window); diff --git a/app/assets/javascripts/star.js b/app/assets/javascripts/star.js index f1fc526bf2e..531fd0e9c32 100644 --- a/app/assets/javascripts/star.js +++ b/app/assets/javascripts/star.js @@ -1,4 +1,4 @@ -/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-unused-vars, one-var, no-var, one-var-declaration-per-line, prefer-arrow-callback, no-new, padded-blocks, max-len */ +/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-unused-vars, one-var, no-var, one-var-declaration-per-line, prefer-arrow-callback, no-new, max-len */ /* global Flash */ (function() { @@ -26,7 +26,5 @@ } return Star; - })(); - }).call(this); diff --git a/app/assets/javascripts/subbable_resource.js.es6 b/app/assets/javascripts/subbable_resource.js.es6 index 932120157a3..d8191605128 100644 --- a/app/assets/javascripts/subbable_resource.js.es6 +++ b/app/assets/javascripts/subbable_resource.js.es6 @@ -1,6 +1,3 @@ -//= require vue -//= require vue-resource - (() => { /* * SubbableResource can be extended to provide a pubsub-style service for one-off REST diff --git a/app/assets/javascripts/subscription_select.js b/app/assets/javascripts/subscription_select.js index 185d20775d0..187356f0bf9 100644 --- a/app/assets/javascripts/subscription_select.js +++ b/app/assets/javascripts/subscription_select.js @@ -1,4 +1,4 @@ -/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, quotes, object-shorthand, no-unused-vars, no-shadow, one-var, one-var-declaration-per-line, comma-dangle, padded-blocks, max-len */ +/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, quotes, object-shorthand, no-unused-vars, no-shadow, one-var, one-var-declaration-per-line, comma-dangle, max-len */ (function() { this.SubscriptionSelect = (function() { function SubscriptionSelect() { @@ -30,7 +30,5 @@ } return SubscriptionSelect; - })(); - }).call(this); diff --git a/app/assets/javascripts/syntax_highlight.js b/app/assets/javascripts/syntax_highlight.js index 5d0fa62c50a..115716bff6a 100644 --- a/app/assets/javascripts/syntax_highlight.js +++ b/app/assets/javascripts/syntax_highlight.js @@ -1,4 +1,4 @@ -/* eslint-disable func-names, space-before-function-paren, consistent-return, no-var, no-else-return, prefer-arrow-callback, padded-blocks, max-len */ +/* eslint-disable func-names, space-before-function-paren, consistent-return, no-var, no-else-return, prefer-arrow-callback, max-len */ // Syntax Highlighter // @@ -10,7 +10,6 @@ // <div class="js-syntax-highlight"></div> // (function() { - $.fn.syntaxHighlight = function() { var $children; @@ -25,5 +24,4 @@ } } }; - }).call(this); diff --git a/app/assets/javascripts/templates/issuable_template_selector.js.es6 b/app/assets/javascripts/templates/issuable_template_selector.js.es6 index d2b152045b4..e9e9aafd71a 100644 --- a/app/assets/javascripts/templates/issuable_template_selector.js.es6 +++ b/app/assets/javascripts/templates/issuable_template_selector.js.es6 @@ -1,7 +1,7 @@ -/* eslint-disable prefer-const, comma-dangle, max-len, no-useless-return, object-curly-spacing, no-param-reassign, max-len */ +/* eslint-disable comma-dangle, max-len, no-useless-return, no-param-reassign, max-len */ /* global Api */ -/*= require ../blob/template_selector */ +require('../blob/template_selector'); ((global) => { class IssuableTemplateSelector extends gl.TemplateSelector { @@ -12,7 +12,7 @@ this.issuableType = this.wrapper.data('issuable-type'); this.titleInput = $(`#${this.issuableType}_title`); - let initialQuery = { + const initialQuery = { name: this.dropdown.data('selected') }; @@ -23,7 +23,7 @@ }); $('.no-template', this.dropdown.parent()).on('click', () => { - this.currentTemplate = ''; + this.currentTemplate.content = ''; this.setInputValueToTemplateContent(); $('.dropdown-toggle-text', this.dropdown).text('Choose a template'); }); @@ -47,10 +47,10 @@ // If the title has not yet been set, focus the title input and // skip focusing the description input by setting `true` as the // `skipFocus` option to `requestFileSuccess`. - this.requestFileSuccess(this.currentTemplate, {skipFocus: true}); + this.requestFileSuccess(this.currentTemplate, { skipFocus: true }); this.titleInput.focus(); } else { - this.requestFileSuccess(this.currentTemplate, {skipFocus: false}); + this.requestFileSuccess(this.currentTemplate, { skipFocus: false }); } return; } diff --git a/app/assets/javascripts/templates/issuable_template_selectors.js.es6 b/app/assets/javascripts/templates/issuable_template_selectors.js.es6 index 7310b9de074..97f6d37364d 100644 --- a/app/assets/javascripts/templates/issuable_template_selectors.js.es6 +++ b/app/assets/javascripts/templates/issuable_template_selectors.js.es6 @@ -1,4 +1,4 @@ -/* eslint-disable no-new, comma-dangle, class-methods-use-this, prefer-const, no-param-reassign */ +/* eslint-disable no-new, comma-dangle, class-methods-use-this, no-param-reassign */ ((global) => { class IssuableTemplateSelectors { @@ -19,7 +19,7 @@ } initEditor() { - let editor = $('.markdown-area'); + const editor = $('.markdown-area'); // Proxy ace-editor's .setValue to jQuery's .val editor.setValue = editor.val; editor.getValue = editor.val; diff --git a/app/assets/javascripts/terminal/terminal_bundle.js.es6 b/app/assets/javascripts/terminal/terminal_bundle.js.es6 index ded7ee6e9fe..13cf3a10a38 100644 --- a/app/assets/javascripts/terminal/terminal_bundle.js.es6 +++ b/app/assets/javascripts/terminal/terminal_bundle.js.es6 @@ -1,5 +1,7 @@ -//= require xterm/xterm.js -//= require xterm/fit.js -//= require ./terminal.js +require('vendor/xterm/encoding-indexes.js'); +require('vendor/xterm/encoding.js'); +window.Terminal = require('vendor/xterm/xterm.js'); +require('vendor/xterm/fit.js'); +require('./terminal.js'); $(() => new gl.Terminal({ selector: '#terminal' })); diff --git a/app/assets/javascripts/todos.js.es6 b/app/assets/javascripts/todos.js.es6 index d8713600030..b07e62a8c30 100644 --- a/app/assets/javascripts/todos.js.es6 +++ b/app/assets/javascripts/todos.js.es6 @@ -1,9 +1,7 @@ -/* eslint-disable padded-blocks, class-methods-use-this, no-new, func-names, prefer-template, no-unneeded-ternary, object-shorthand, space-before-function-paren, comma-dangle, quote-props, consistent-return, no-else-return, semi, no-param-reassign, max-len */ +/* eslint-disable class-methods-use-this, no-new, func-names, prefer-template, no-unneeded-ternary, object-shorthand, space-before-function-paren, comma-dangle, quote-props, consistent-return, no-else-return, no-param-reassign, max-len */ /* global UsersSelect */ -/* global Turbolinks */ ((global) => { - class Todos { constructor({ el } = {}) { this.allDoneClicked = this.allDoneClicked.bind(this); @@ -35,7 +33,7 @@ $('form.filter-form').on('submit', function (event) { event.preventDefault(); - Turbolinks.visit(this.action + '&' + $(this).serialize()); + gl.utils.visitUrl(this.action + '&' + $(this).serialize()); }); } @@ -49,7 +47,7 @@ clicked: function() { return $dropdown.closest('form.filter-form').submit(); } - }) + }); } doneClicked(e) { @@ -86,7 +84,7 @@ }, success: (data) => { $target.remove(); - $('.prepend-top-default').html('<div class="nothing-here-block">You\'re all done!</div>'); + $('.js-todos-all').html('<div class="nothing-here-block">You\'re all done!</div>'); return this.updateBadges(data); } }); @@ -143,21 +141,33 @@ }; url = gl.utils.mergeUrlParams(pageParams, url); } - return Turbolinks.visit(url); + return gl.utils.visitUrl(url); } } goToTodoUrl(e) { - const todoLink = $(this).data('url'); + const todoLink = this.dataset.url; + let targetLink = e.target.getAttribute('href'); + + if (e.target.tagName === 'IMG') { // See if clicked target was Avatar + targetLink = e.target.parentElement.getAttribute('href'); // Parent of Avatar is link + } + if (!todoLink) { return; } - // Allow Meta-Click or Mouse3-click to open in a new tab - if (e.metaKey || e.which === 2) { + + if (gl.utils.isMetaClick(e)) { e.preventDefault(); - return window.open(todoLink, '_blank'); + // Meta-Click on username leads to different URL than todoLink. + // Turbolinks can resolve that URL, but window.open requires URL manually. + if (targetLink !== todoLink) { + return window.open(targetLink, '_blank'); + } else { + return window.open(todoLink, '_blank'); + } } else { - return Turbolinks.visit(todoLink); + return gl.utils.visitUrl(todoLink); } } } diff --git a/app/assets/javascripts/tree.js b/app/assets/javascripts/tree.js index f48a7ee0f55..b1b35fdbd6c 100644 --- a/app/assets/javascripts/tree.js +++ b/app/assets/javascripts/tree.js @@ -1,5 +1,5 @@ -/* eslint-disable func-names, space-before-function-paren, wrap-iife, max-len, quotes, consistent-return, no-var, one-var, one-var-declaration-per-line, no-else-return, prefer-arrow-callback, padded-blocks, max-len */ -/* global Turbolinks */ +/* eslint-disable func-names, space-before-function-paren, wrap-iife, max-len, quotes, consistent-return, no-var, one-var, one-var-declaration-per-line, no-else-return, prefer-arrow-callback, max-len */ + (function() { this.TreeView = (function() { function TreeView() { @@ -15,7 +15,7 @@ e.preventDefault(); return window.open(path, '_blank'); } else { - return Turbolinks.visit(path); + return gl.utils.visitUrl(path); } } }); @@ -57,14 +57,12 @@ } else if (e.which === 13) { path = $('.tree-item.selected .tree-item-file-name a').attr('href'); if (path) { - return Turbolinks.visit(path); + return gl.utils.visitUrl(path); } } }); }; return TreeView; - })(); - }).call(this); diff --git a/app/assets/javascripts/u2f/authenticate.js b/app/assets/javascripts/u2f/authenticate.js.es6 index d2aa3c7a841..500b78fc5d8 100644 --- a/app/assets/javascripts/u2f/authenticate.js +++ b/app/assets/javascripts/u2f/authenticate.js.es6 @@ -1,4 +1,4 @@ -/* eslint-disable func-names, space-before-function-paren, no-var, space-before-blocks, prefer-rest-params, wrap-iife, prefer-arrow-callback, no-else-return, quotes, quote-props, comma-dangle, one-var, one-var-declaration-per-line, padded-blocks, max-len */ +/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, prefer-arrow-callback, no-else-return, quotes, quote-props, comma-dangle, one-var, one-var-declaration-per-line, max-len */ /* global u2f */ /* global U2FError */ /* global U2FUtil */ @@ -8,21 +8,26 @@ // State Flow #1: setup -> in_progress -> authenticated -> POST to server // State Flow #2: setup -> in_progress -> error -> setup (function() { - var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; + const global = window.gl || (window.gl = {}); - this.U2FAuthenticate = (function() { - function U2FAuthenticate(container, u2fParams) { + var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; }; + + global.U2FAuthenticate = (function() { + function U2FAuthenticate(container, form, u2fParams, fallbackButton, fallbackUI) { this.container = container; this.renderNotSupported = bind(this.renderNotSupported, this); this.renderAuthenticated = bind(this.renderAuthenticated, this); this.renderError = bind(this.renderError, this); this.renderInProgress = bind(this.renderInProgress, this); - this.renderSetup = bind(this.renderSetup, this); this.renderTemplate = bind(this.renderTemplate, this); this.authenticate = bind(this.authenticate, this); this.start = bind(this.start, this); this.appId = u2fParams.app_id; this.challenge = u2fParams.challenge; + this.form = form; + this.fallbackButton = fallbackButton; + this.fallbackUI = fallbackUI; + if (this.fallbackButton) this.fallbackButton.addEventListener('click', this.switchToFallbackUI.bind(this)); this.signRequests = u2fParams.sign_requests.map(function(request) { // The U2F Javascript API v1.1 requires a single challenge, with // _no challenges per-request_. The U2F Javascript API v1.0 requires a @@ -41,7 +46,7 @@ U2FAuthenticate.prototype.start = function() { if (U2FUtil.isU2FSupported()) { - return this.renderSetup(); + return this.renderInProgress(); } else { return this.renderNotSupported(); } @@ -52,7 +57,7 @@ return function(response) { var error; if (response.errorCode) { - error = new U2FError(response.errorCode); + error = new U2FError(response.errorCode, 'authenticate'); return _this.renderError(error); } else { return _this.renderAuthenticated(JSON.stringify(response)); @@ -77,11 +82,6 @@ return this.container.html(template(params)); }; - U2FAuthenticate.prototype.renderSetup = function() { - this.renderTemplate('setup'); - return this.container.find('#js-login-u2f-device').on('click', this.renderInProgress); - }; - U2FAuthenticate.prototype.renderInProgress = function() { this.renderTemplate('inProgress'); return this.authenticate(); @@ -89,24 +89,30 @@ U2FAuthenticate.prototype.renderError = function(error) { this.renderTemplate('error', { - error_message: error.message() + error_message: error.message(), + error_code: error.errorCode }); - return this.container.find('#js-u2f-try-again').on('click', this.renderSetup); + return this.container.find('#js-u2f-try-again').on('click', this.renderInProgress); }; U2FAuthenticate.prototype.renderAuthenticated = function(deviceResponse) { this.renderTemplate('authenticated'); - // Prefer to do this instead of interpolating using Underscore templates - // because of JSON escaping issues. - return this.container.find("#js-device-response").val(deviceResponse); + const container = this.container[0]; + container.querySelector('#js-device-response').value = deviceResponse; + container.querySelector(this.form).submit(); + this.fallbackButton.classList.add('hidden'); }; U2FAuthenticate.prototype.renderNotSupported = function() { return this.renderTemplate('notSupported'); }; - return U2FAuthenticate; + U2FAuthenticate.prototype.switchToFallbackUI = function() { + this.fallbackButton.classList.add('hidden'); + this.container[0].classList.add('hidden'); + this.fallbackUI.classList.remove('hidden'); + }; + return U2FAuthenticate; })(); - -}).call(this); +})(); diff --git a/app/assets/javascripts/u2f/error.js b/app/assets/javascripts/u2f/error.js index 69f98c9c0ad..86b459e1866 100644 --- a/app/assets/javascripts/u2f/error.js +++ b/app/assets/javascripts/u2f/error.js @@ -1,30 +1,27 @@ -/* eslint-disable func-names, space-before-function-paren, no-var, space-before-blocks, prefer-rest-params, wrap-iife, no-console, quotes, prefer-template, padded-blocks, max-len */ +/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, no-console, quotes, prefer-template, max-len */ /* global u2f */ (function() { - var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; + var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; }; this.U2FError = (function() { - function U2FError(errorCode) { + function U2FError(errorCode, u2fFlowType) { this.errorCode = errorCode; this.message = bind(this.message, this); this.httpsDisabled = window.location.protocol !== 'https:'; - console.error("U2F Error Code: " + this.errorCode); + this.u2fFlowType = u2fFlowType; } U2FError.prototype.message = function() { - switch (false) { - case !(this.errorCode === u2f.ErrorCodes.BAD_REQUEST && this.httpsDisabled): - return "U2F only works with HTTPS-enabled websites. Contact your administrator for more details."; - case this.errorCode !== u2f.ErrorCodes.DEVICE_INELIGIBLE: - return "This device has already been registered with us."; - default: - return "There was a problem communicating with your device."; + if (this.errorCode === u2f.ErrorCodes.BAD_REQUEST && this.httpsDisabled) { + return 'U2F only works with HTTPS-enabled websites. Contact your administrator for more details.'; + } else if (this.errorCode === u2f.ErrorCodes.DEVICE_INELIGIBLE) { + if (this.u2fFlowType === 'authenticate') return 'This device has not been registered with us.'; + if (this.u2fFlowType === 'register') return 'This device has already been registered with us.'; } + return "There was a problem communicating with your device."; }; return U2FError; - })(); - }).call(this); diff --git a/app/assets/javascripts/u2f/register.js b/app/assets/javascripts/u2f/register.js index 4f5d68f546b..69d1ff3a39e 100644 --- a/app/assets/javascripts/u2f/register.js +++ b/app/assets/javascripts/u2f/register.js @@ -1,4 +1,4 @@ -/* eslint-disable func-names, space-before-function-paren, no-var, space-before-blocks, prefer-rest-params, wrap-iife, no-else-return, quotes, quote-props, comma-dangle, one-var, one-var-declaration-per-line, padded-blocks, max-len */ +/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, no-else-return, quotes, quote-props, comma-dangle, one-var, one-var-declaration-per-line, max-len */ /* global u2f */ /* global U2FError */ /* global U2FUtil */ @@ -8,7 +8,7 @@ // State Flow #1: setup -> in_progress -> registered -> POST to server // State Flow #2: setup -> in_progress -> error -> setup (function() { - var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; + var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; }; this.U2FRegister = (function() { function U2FRegister(container, u2fParams) { @@ -39,7 +39,7 @@ return function(response) { var error; if (response.errorCode) { - error = new U2FError(response.errorCode); + error = new U2FError(response.errorCode, 'register'); return _this.renderError(error); } else { return _this.renderRegistered(JSON.stringify(response)); @@ -76,7 +76,8 @@ U2FRegister.prototype.renderError = function(error) { this.renderTemplate('error', { - error_message: error.message() + error_message: error.message(), + error_code: error.errorCode }); return this.container.find('#js-u2f-try-again').on('click', this.renderSetup); }; @@ -93,7 +94,5 @@ }; return U2FRegister; - })(); - }).call(this); diff --git a/app/assets/javascripts/u2f/util.js b/app/assets/javascripts/u2f/util.js index eedd3bcd5a1..34e88220b12 100644 --- a/app/assets/javascripts/u2f/util.js +++ b/app/assets/javascripts/u2f/util.js @@ -1,4 +1,4 @@ -/* eslint-disable func-names, space-before-function-paren, wrap-iife, padded-blocks */ +/* eslint-disable func-names, space-before-function-paren, wrap-iife */ (function() { this.U2FUtil = (function() { function U2FUtil() {} @@ -8,7 +8,5 @@ }; return U2FUtil; - })(); - }).call(this); diff --git a/app/assets/javascripts/user.js.es6 b/app/assets/javascripts/user.js.es6 index 0a2db7c05fe..059e6c628b3 100644 --- a/app/assets/javascripts/user.js.es6 +++ b/app/assets/javascripts/user.js.es6 @@ -1,4 +1,4 @@ -/* eslint-disable class-methods-use-this, comma-dangle, arrow-parens, no-param-reassign, semi */ +/* eslint-disable class-methods-use-this, comma-dangle, arrow-parens, no-param-reassign */ /* global Cookies */ ((global) => { @@ -30,5 +30,5 @@ $(this).parents('.project-limit-message').remove(); }); } - } + }; })(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/user_tabs.js.es6 b/app/assets/javascripts/user_tabs.js.es6 index b9c23b51b4d..465618e3d53 100644 --- a/app/assets/javascripts/user_tabs.js.es6 +++ b/app/assets/javascripts/user_tabs.js.es6 @@ -1,4 +1,4 @@ -/* eslint-disable max-len, space-before-function-paren, no-underscore-dangle, array-bracket-spacing, consistent-return, comma-dangle, no-unused-vars, dot-notation, no-new, no-return-assign, camelcase, semi, no-param-reassign */ +/* 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 */ /* UserTabs @@ -107,7 +107,7 @@ content on the Users#show page. this.loadActivities(source); } - const loadableActions = [ 'groups', 'contributed', 'projects', 'snippets' ]; + const loadableActions = ['groups', 'contributed', 'projects', 'snippets']; if (loadableActions.indexOf(action) > -1) { return this.loadTab(source, action); } @@ -145,11 +145,10 @@ content on the Users#show page. } setCurrentAction(source, action) { - let new_state = source + let new_state = source; new_state = new_state.replace(/\/+$/, ''); new_state += this._location.search + this._location.hash; history.replaceState({ - turbolinks: true, url: new_state }, document.title, new_state); return new_state; diff --git a/app/assets/javascripts/users/calendar.js b/app/assets/javascripts/users/calendar.js index 578be7c3590..6e40dfdf3d8 100644 --- a/app/assets/javascripts/users/calendar.js +++ b/app/assets/javascripts/users/calendar.js @@ -1,9 +1,8 @@ -/* eslint-disable func-names, space-before-function-paren, no-var, space-before-blocks, prefer-rest-params, wrap-iife, camelcase, vars-on-top, semi, keyword-spacing, no-plusplus, 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, padded-blocks, max-len */ +/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, 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 */ /* global d3 */ -/* global dateFormat */ (function() { - var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; + var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; }; this.Calendar = (function() { function Calendar(timestamps, calendar_activities_path) { @@ -20,7 +19,7 @@ this.timestampsTmp = []; var group = 0; - var today = new Date() + var today = new Date(); today.setHours(0, 0, 0, 0, 0); var oneYearAgo = new Date(today); @@ -28,18 +27,18 @@ var days = gl.utils.getDayDifference(oneYearAgo, today); - for(var i = 0; i <= days; i++) { + for (var i = 0; i <= days; i += 1) { var date = new Date(oneYearAgo); date.setDate(date.getDate() + i); var day = date.getDay(); - var count = timestamps[dateFormat(date, 'yyyy-mm-dd')]; + var count = timestamps[date.format('yyyy-mm-dd')]; // Create a new group array if this is the first day of the week // or if is first object if ((day === 0 && i !== 0) || i === 0) { this.timestampsTmp.push([]); - group++; + group += 1; } var innerArray = this.timestampsTmp[group - 1]; @@ -74,7 +73,7 @@ } return extraWidthPadding; - } + }; Calendar.prototype.renderSvg = function(group) { var width = (group + 1) * this.daySizeWithSpace + this.getExtraWidthPadding(group); @@ -122,7 +121,7 @@ if (stamp.count > 0) { contribText = stamp.count + " contribution" + (stamp.count > 1 ? 's' : ''); } - dateText = dateFormat(date, 'mmm d, yyyy'); + 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) { @@ -158,7 +157,7 @@ }; Calendar.prototype.renderMonths = function() { - return this.svg.append('g').selectAll('text').data(this.months).enter().append('text').attr('x', function(date) { + 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) { @@ -221,7 +220,5 @@ }; return Calendar; - })(); - }).call(this); diff --git a/app/assets/javascripts/users/users_bundle.js b/app/assets/javascripts/users/users_bundle.js index f50802bdf2e..4cad60a59b1 100644 --- a/app/assets/javascripts/users/users_bundle.js +++ b/app/assets/javascripts/users/users_bundle.js @@ -1,7 +1,3 @@ -/* eslint-disable func-names, space-before-function-paren */ - -/*= require_tree . */ - -(function() { - -}).call(this); +// require everything else in this directory +function requireAll(context) { return context.keys().map(context); } +requireAll(require.context('.', false, /^\.\/(?!users_bundle).*\.(js|es6)$/)); diff --git a/app/assets/javascripts/users_select.js b/app/assets/javascripts/users_select.js index d4b5e03aa35..d4b24d13299 100644 --- a/app/assets/javascripts/users_select.js +++ b/app/assets/javascripts/users_select.js @@ -1,14 +1,15 @@ -/* eslint-disable func-names, space-before-function-paren, one-var, no-var, space-before-blocks, 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-plusplus, no-else-return, no-self-compare, prefer-template, no-unused-expressions, no-lonely-if, yoda, prefer-spread, no-void, camelcase, keyword-spacing, no-param-reassign, padded-blocks */ +/* 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 Vue */ /* global Issuable */ /* global ListUser */ (function() { - var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, + var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; }, slice = [].slice; this.UsersSelect = (function() { - function UsersSelect(currentUser) { + function UsersSelect(currentUser, els) { + var $els; this.users = bind(this.users, this); this.user = bind(this.user, this); this.usersPath = "/autocomplete/users.json"; @@ -20,7 +21,14 @@ this.currentUser = JSON.parse(currentUser); } } - $('.js-user-search').each((function(_this) { + + $els = $(els); + + if (!els) { + $els = $('.js-user-search'); + } + + $els.each((function(_this) { return function(i, dropdown) { var options = {}; var $block, $collapsedSidebar, $dropdown, $loading, $selectbox, $value, abilityName, assignTo, assigneeTemplate, collapsedAssigneeTemplate, defaultLabel, firstUser, issueURL, selectedId, showAnyUser, showNullUser, showMenuAbove; @@ -116,7 +124,7 @@ showDivider = 0; if (firstUser) { // Move current user to the front of the list - for (index = j = 0, len = users.length; j < len; index = ++j) { + for (index = j = 0, len = users.length; j < len; index = (j += 1)) { obj = users[index]; if (obj.username === firstUser) { users.splice(index, 1); @@ -193,7 +201,9 @@ selectedId = user.id; return; } - if ($('html').hasClass('issue-boards-page') && !$dropdown.hasClass('js-issue-board-sidebar')) { + if ($el.closest('.add-issues-modal').length) { + gl.issueBoards.ModalStore.store.filter[$dropdown.data('field-name')] = user.id; + } else if ($('html').hasClass('issue-boards-page') && !$dropdown.hasClass('js-issue-board-sidebar')) { selectedId = user.id; gl.issueBoards.BoardsStore.state.filters[$dropdown.data('field-name')] = user.id; gl.issueBoards.BoardsStore.updateFiltersUrl(); @@ -278,7 +288,7 @@ if (firstUser) { // Move current user to the front of the list ref = data.results; - for (index = j = 0, len = ref.length; j < len; index = ++j) { + for (index = j = 0, len = ref.length; j < len; index = (j += 1)) { obj = ref[index]; if (obj.username === firstUser) { data.results.splice(index, 1); @@ -371,7 +381,7 @@ }; UsersSelect.prototype.user = function(user_id, callback) { - if(!/^\d+$/.test(user_id)) { + if (!/^\d+$/.test(user_id)) { return false; } @@ -421,7 +431,5 @@ }; return UsersSelect; - })(); - }).call(this); diff --git a/app/assets/javascripts/version_check_image.js.es6 b/app/assets/javascripts/version_check_image.js.es6 new file mode 100644 index 00000000000..1fa2b5ac399 --- /dev/null +++ b/app/assets/javascripts/version_check_image.js.es6 @@ -0,0 +1,10 @@ +(() => { + class VersionCheckImage { + static bindErrorEvent(imageElement) { + imageElement.off('error').on('error', () => imageElement.hide()); + } + } + + window.gl = window.gl || {}; + gl.VersionCheckImage = VersionCheckImage; +})(); diff --git a/app/assets/javascripts/visibility_select.js.es6 b/app/assets/javascripts/visibility_select.js.es6 new file mode 100644 index 00000000000..f712d7ba930 --- /dev/null +++ b/app/assets/javascripts/visibility_select.js.es6 @@ -0,0 +1,27 @@ +(() => { + const gl = window.gl || (window.gl = {}); + + class VisibilitySelect { + constructor(container) { + if (!container) throw new Error('VisibilitySelect requires a container element as argument 1'); + this.container = container; + this.helpBlock = this.container.querySelector('.help-block'); + this.select = this.container.querySelector('select'); + } + + init() { + if (this.select) { + this.updateHelpText(); + this.select.addEventListener('change', this.updateHelpText.bind(this)); + } else { + this.helpBlock.textContent = this.container.querySelector('.js-locked').dataset.helpBlock; + } + } + + updateHelpText() { + this.helpBlock.textContent = this.select.querySelector('option:checked').dataset.description; + } + } + + gl.VisibilitySelect = VisibilitySelect; +})(); diff --git a/app/assets/javascripts/vue_pipelines_index/index.js.es6 b/app/assets/javascripts/vue_pipelines_index/index.js.es6 new file mode 100644 index 00000000000..e7432afb56e --- /dev/null +++ b/app/assets/javascripts/vue_pipelines_index/index.js.es6 @@ -0,0 +1,36 @@ +/* eslint-disable no-param-reassign */ +/* global Vue, VueResource, gl */ +window.Vue = require('vue'); +window.Vue.use(require('vue-resource')); +require('../lib/utils/common_utils'); +require('../vue_shared/vue_resource_interceptor'); +require('./pipelines'); + +$(() => new Vue({ + el: document.querySelector('.vue-pipelines-index'), + + data() { + const project = document.querySelector('.pipelines'); + const svgs = document.querySelector('.pipeline-svgs').dataset; + + // Transform svgs DOMStringMap to a plain Object. + const svgsObject = gl.utils.DOMStringMapToObject(svgs); + + return { + scope: project.dataset.url, + store: new gl.PipelineStore(), + svgs: svgsObject, + }; + }, + components: { + 'vue-pipelines': gl.VuePipelines, + }, + template: ` + <vue-pipelines + :scope='scope' + :store='store' + :svgs='svgs' + > + </vue-pipelines> + `, +})); diff --git a/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6 new file mode 100644 index 00000000000..8106934e864 --- /dev/null +++ b/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6 @@ -0,0 +1,104 @@ +/* global Vue, Flash, gl */ +/* eslint-disable no-param-reassign */ + +((gl) => { + gl.VuePipelineActions = Vue.extend({ + props: ['pipeline', 'svgs'], + computed: { + actions() { + return this.pipeline.details.manual_actions.length > 0; + }, + artifacts() { + return this.pipeline.details.artifacts.length > 0; + }, + }, + methods: { + download(name) { + return `Download ${name} artifacts`; + }, + }, + template: ` + <td class="pipeline-actions hidden-xs"> + <div class="controls pull-right"> + <div class="btn-group inline"> + <div class="btn-group"> + <button + v-if='actions' + class="dropdown-toggle btn btn-default has-tooltip js-pipeline-dropdown-manual-actions" + data-toggle="dropdown" + title="Manual job" + data-placement="top" + aria-label="Manual job" + > + <span v-html='svgs.iconPlay' aria-hidden="true"></span> + <i class="fa fa-caret-down" aria-hidden="true"></i> + </button> + <ul class="dropdown-menu dropdown-menu-align-right"> + <li v-for='action in pipeline.details.manual_actions'> + <a + rel="nofollow" + data-method="post" + :href='action.path' + > + <span v-html='svgs.iconPlay' aria-hidden="true"></span> + <span>{{action.name}}</span> + </a> + </li> + </ul> + </div> + <div class="btn-group"> + <button + v-if='artifacts' + class="dropdown-toggle btn btn-default build-artifacts has-tooltip js-pipeline-dropdown-download" + title="Artifacts" + data-placement="top" + data-toggle="dropdown" + aria-label="Artifacts" + > + <i class="fa fa-download" aria-hidden="true"></i> + <i class="fa fa-caret-down" aria-hidden="true"></i> + </button> + <ul class="dropdown-menu dropdown-menu-align-right"> + <li v-for='artifact in pipeline.details.artifacts'> + <a + rel="nofollow" + :href='artifact.path' + > + <i class="fa fa-download" aria-hidden="true"></i> + <span>{{download(artifact.name)}}</span> + </a> + </li> + </ul> + </div> + </div> + <div class="cancel-retry-btns inline"> + <a + v-if='pipeline.flags.retryable' + class="btn has-tooltip" + title="Retry" + rel="nofollow" + data-method="post" + data-placement="top" + data-toggle="dropdown" + :href='pipeline.retry_path' + aria-label="Retry"> + <i class="fa fa-repeat" aria-hidden="true"></i> + </a> + <a + v-if='pipeline.flags.cancelable' + class="btn btn-remove has-tooltip" + title="Cancel" + rel="nofollow" + data-method="post" + data-placement="top" + data-toggle="dropdown" + :href='pipeline.cancel_path' + aria-label="Cancel"> + <i class="fa fa-remove" aria-hidden="true"></i> + </a> + </div> + </div> + </td> + `, + }); +})(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/vue_pipelines_index/pipeline_url.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipeline_url.js.es6 new file mode 100644 index 00000000000..ae5649f0519 --- /dev/null +++ b/app/assets/javascripts/vue_pipelines_index/pipeline_url.js.es6 @@ -0,0 +1,63 @@ +/* global Vue, gl */ +/* eslint-disable no-param-reassign */ + +((gl) => { + gl.VuePipelineUrl = Vue.extend({ + props: [ + 'pipeline', + ], + computed: { + user() { + return !!this.pipeline.user; + }, + }, + template: ` + <td> + <a :href='pipeline.path'> + <span class="pipeline-id">#{{pipeline.id}}</span> + </a> + <span>by</span> + <a + v-if='user' + :href='pipeline.user.web_url' + > + <img + v-if='user' + class="avatar has-tooltip s20 " + :title='pipeline.user.name' + data-container="body" + :src='pipeline.user.avatar_url' + > + </a> + <span + v-if='!user' + class="api monospace" + > + API + </span> + <span + v-if='pipeline.flags.latest' + class="label label-success has-tooltip" + title="Latest pipeline for this branch" + data-original-title="Latest pipeline for this branch" + > + latest + </span> + <span + v-if='pipeline.flags.yaml_errors' + class="label label-danger has-tooltip" + :title='pipeline.yaml_errors' + :data-original-title='pipeline.yaml_errors' + > + yaml invalid + </span> + <span + v-if='pipeline.flags.stuck' + class="label label-warning" + > + stuck + </span> + </td> + `, + }); +})(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 new file mode 100644 index 00000000000..e47dc6935d6 --- /dev/null +++ b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 @@ -0,0 +1,73 @@ +/* global Vue, gl */ +/* eslint-disable no-param-reassign */ + +window.Vue = require('vue'); +require('../vue_shared/components/table_pagination'); +require('./store'); +require('../vue_shared/components/pipelines_table'); + +((gl) => { + gl.VuePipelines = Vue.extend({ + + components: { + 'gl-pagination': gl.VueGlPagination, + 'pipelines-table-component': gl.pipelines.PipelinesTableComponent, + }, + + data() { + return { + pipelines: [], + timeLoopInterval: '', + intervalId: '', + apiScope: 'all', + pageInfo: {}, + pagenum: 1, + count: { all: 0, running_or_pending: 0 }, + pageRequest: false, + }; + }, + props: ['scope', 'store', 'svgs'], + created() { + const pagenum = gl.utils.getParameterByName('p'); + const scope = gl.utils.getParameterByName('scope'); + if (pagenum) this.pagenum = pagenum; + if (scope) this.apiScope = scope; + this.store.fetchDataLoop.call(this, Vue, this.pagenum, this.scope, this.apiScope); + }, + methods: { + change(pagenum, apiScope) { + gl.utils.visitUrl(`?scope=${apiScope}&p=${pagenum}`); + }, + }, + template: ` + <div> + <div class="pipelines realtime-loading" v-if='pageRequest'> + <i class="fa fa-spinner fa-spin"></i> + </div> + + <div class="blank-state blank-state-no-icon" + v-if="!pageRequest && pipelines.length === 0"> + <h2 class="blank-state-title js-blank-state-title"> + No pipelines to show + </h2> + </div> + + <div class="table-holder" v-if='!pageRequest && pipelines.length'> + <pipelines-table-component + :pipelines='pipelines' + :svgs='svgs'> + </pipelines-table-component> + </div> + + <gl-pagination + v-if='!pageRequest && pipelines.length && pageInfo.total > pageInfo.perPage' + :pagenum='pagenum' + :change='change' + :count='count.all' + :pageInfo='pageInfo' + > + </gl-pagination> + </div> + `, + }); +})(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/vue_pipelines_index/stage.js.es6 b/app/assets/javascripts/vue_pipelines_index/stage.js.es6 new file mode 100644 index 00000000000..8cc417a9966 --- /dev/null +++ b/app/assets/javascripts/vue_pipelines_index/stage.js.es6 @@ -0,0 +1,103 @@ +/* global Vue, Flash, gl */ +/* eslint-disable no-param-reassign */ + +((gl) => { + gl.VueStage = Vue.extend({ + data() { + return { + builds: '', + spinner: '<span class="fa fa-spinner fa-spin"></span>', + }; + }, + props: { + stage: { + type: Object, + required: true, + }, + svgs: { + type: Object, + required: true, + }, + match: { + type: Function, + required: true, + }, + }, + methods: { + fetchBuilds(e) { + const areaExpanded = e.currentTarget.attributes['aria-expanded']; + + if (areaExpanded && (areaExpanded.textContent === 'true')) return null; + + return this.$http.get(this.stage.dropdown_path) + .then((response) => { + this.builds = JSON.parse(response.body).html; + }, () => { + const flash = new Flash('Something went wrong on our end.'); + return flash; + }); + }, + keepGraph(e) { + const { target } = e; + + if (target.className.indexOf('js-ci-action-icon') >= 0) return null; + + if ( + target.parentElement && + (target.parentElement.className.indexOf('js-ci-action-icon') >= 0) + ) return null; + + return e.stopPropagation(); + }, + }, + computed: { + buildsOrSpinner() { + return this.builds ? this.builds : this.spinner; + }, + dropdownClass() { + if (this.builds) return 'js-builds-dropdown-container'; + return 'js-builds-dropdown-loading builds-dropdown-loading'; + }, + buildStatus() { + return `Build: ${this.stage.status.label}`; + }, + tooltip() { + return `has-tooltip ci-status-icon ci-status-icon-${this.stage.status.group}`; + }, + svg() { + const { icon } = this.stage.status; + const stageIcon = icon.replace(/icon/i, 'stage_icon'); + return this.svgs[this.match(stageIcon)]; + }, + triggerButtonClass() { + return `mini-pipeline-graph-dropdown-toggle has-tooltip js-builds-dropdown-button ci-status-icon-${this.stage.status.group}`; + }, + }, + template: ` + <div> + <button + @click='fetchBuilds($event)' + :class="triggerButtonClass" + :title='stage.title' + data-placement="top" + data-toggle="dropdown" + type="button" + :aria-label='stage.title' + > + <span v-html="svg" aria-hidden="true"></span> + <i class="fa fa-caret-down" aria-hidden="true"></i> + </button> + <ul class="dropdown-menu mini-pipeline-graph-dropdown-menu js-builds-dropdown-container"> + <div class="arrow-up" aria-hidden="true"></div> + <div + @click='keepGraph($event)' + :class="dropdownClass" + class="js-builds-dropdown-list scrollable-menu" + v-html="buildsOrSpinner" + > + </div> + </ul> + </div> + `, + }); +})(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/vue_pipelines_index/status.js.es6 b/app/assets/javascripts/vue_pipelines_index/status.js.es6 new file mode 100644 index 00000000000..05175082704 --- /dev/null +++ b/app/assets/javascripts/vue_pipelines_index/status.js.es6 @@ -0,0 +1,34 @@ +/* global Vue, gl */ +/* eslint-disable no-param-reassign */ + +((gl) => { + gl.VueStatusScope = Vue.extend({ + props: [ + 'pipeline', 'svgs', 'match', + ], + computed: { + cssClasses() { + const cssObject = { 'ci-status': true }; + cssObject[`ci-${this.pipeline.details.status.group}`] = true; + return cssObject; + }, + svg() { + return this.svgs[this.match(this.pipeline.details.status.icon)]; + }, + detailsPath() { + const { status } = this.pipeline.details; + return status.has_details ? status.details_path : false; + }, + }, + template: ` + <td class="commit-link"> + <a + :class='cssClasses' + :href='detailsPath' + v-html='svg + pipeline.details.status.text' + > + </a> + </td> + `, + }); +})(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/vue_pipelines_index/store.js.es6 b/app/assets/javascripts/vue_pipelines_index/store.js.es6 new file mode 100644 index 00000000000..0ee21f00fdc --- /dev/null +++ b/app/assets/javascripts/vue_pipelines_index/store.js.es6 @@ -0,0 +1,68 @@ +/* global gl, Flash */ +/* eslint-disable no-param-reassign, no-underscore-dangle */ +require('../vue_realtime_listener'); + +((gl) => { + const pageValues = (headers) => { + const normalized = gl.utils.normalizeHeaders(headers); + + const paginationInfo = { + perPage: +normalized['X-PER-PAGE'], + page: +normalized['X-PAGE'], + total: +normalized['X-TOTAL'], + totalPages: +normalized['X-TOTAL-PAGES'], + nextPage: +normalized['X-NEXT-PAGE'], + previousPage: +normalized['X-PREV-PAGE'], + }; + + return paginationInfo; + }; + + gl.PipelineStore = class { + fetchDataLoop(Vue, pageNum, url, apiScope) { + this.pageRequest = true; + const updatePipelineNums = (count) => { + const { all } = count; + const running = count.running_or_pending; + document.querySelector('.js-totalbuilds-count').innerHTML = all; + document.querySelector('.js-running-count').innerHTML = running; + }; + + const goFetch = () => + this.$http.get(`${url}?scope=${apiScope}&page=${pageNum}`) + .then((response) => { + const pageInfo = pageValues(response.headers); + this.pageInfo = Object.assign({}, this.pageInfo, pageInfo); + + const res = JSON.parse(response.body); + this.count = Object.assign({}, this.count, res.count); + this.pipelines = Object.assign([], this.pipelines, res.pipelines); + + updatePipelineNums(this.count); + this.pageRequest = false; + }, () => { + this.pageRequest = false; + return new Flash('An error occurred while fetching the pipelines, please reload the page again.'); + }); + + goFetch(); + + const startTimeLoops = () => { + this.timeLoopInterval = setInterval(() => { + this.$children[0].$children.reduce((acc, component) => { + const timeAgoComponent = component.$children.filter(el => el.$options._componentTag === 'time-ago')[0]; + acc.push(timeAgoComponent); + return acc; + }, []).forEach(e => e.changeTime()); + }, 10000); + }; + + startTimeLoops(); + + const removeIntervals = () => clearInterval(this.timeLoopInterval); + const startIntervals = () => startTimeLoops(); + + gl.VueRealtimeListener(removeIntervals, startIntervals); + } + }; +})(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/vue_pipelines_index/time_ago.js.es6 b/app/assets/javascripts/vue_pipelines_index/time_ago.js.es6 new file mode 100644 index 00000000000..3598da11573 --- /dev/null +++ b/app/assets/javascripts/vue_pipelines_index/time_ago.js.es6 @@ -0,0 +1,76 @@ +/* global Vue, gl */ +/* eslint-disable no-param-reassign */ + +window.Vue = require('vue'); +require('../lib/utils/datetime_utility'); + +((gl) => { + gl.VueTimeAgo = Vue.extend({ + data() { + return { + currentTime: new Date(), + }; + }, + props: ['pipeline', 'svgs'], + computed: { + timeAgo() { + return gl.utils.getTimeago(); + }, + localTimeFinished() { + return gl.utils.formatDate(this.pipeline.details.finished_at); + }, + timeStopped() { + const changeTime = this.currentTime; + const options = { + weekday: 'long', + year: 'numeric', + month: 'short', + day: 'numeric', + }; + options.timeZoneName = 'short'; + const finished = this.pipeline.details.finished_at; + if (!finished && changeTime) return false; + return ({ words: this.timeAgo.format(finished) }); + }, + duration() { + const { duration } = this.pipeline.details; + const date = new Date(duration * 1000); + + let hh = date.getUTCHours(); + let mm = date.getUTCMinutes(); + let ss = date.getSeconds(); + + if (hh < 10) hh = `0${hh}`; + if (mm < 10) mm = `0${mm}`; + if (ss < 10) ss = `0${ss}`; + + if (duration !== null) return `${hh}:${mm}:${ss}`; + return false; + }, + }, + methods: { + changeTime() { + this.currentTime = new Date(); + }, + }, + template: ` + <td> + <p class="duration" v-if='duration'> + <span v-html='svgs.iconTimer'></span> + {{duration}} + </p> + <p class="finished-at" v-if='timeStopped'> + <i class="fa fa-calendar"></i> + <time + data-toggle="tooltip" + data-placement="top" + data-container="body" + :data-original-title='localTimeFinished' + > + {{timeStopped.words}} + </time> + </p> + </td> + `, + }); +})(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/vue_realtime_listener/index.js.es6 b/app/assets/javascripts/vue_realtime_listener/index.js.es6 new file mode 100644 index 00000000000..95564152cce --- /dev/null +++ b/app/assets/javascripts/vue_realtime_listener/index.js.es6 @@ -0,0 +1,18 @@ +/* eslint-disable no-param-reassign */ + +((gl) => { + gl.VueRealtimeListener = (removeIntervals, startIntervals) => { + const removeAll = () => { + removeIntervals(); + window.removeEventListener('beforeunload', removeIntervals); + window.removeEventListener('focus', startIntervals); + window.removeEventListener('blur', removeIntervals); + document.removeEventListener('beforeunload', removeAll); + }; + + window.addEventListener('beforeunload', removeIntervals); + window.addEventListener('focus', startIntervals); + window.addEventListener('blur', removeIntervals); + document.addEventListener('beforeunload', removeAll); + }; +})(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/vue_common_component/commit.js.es6 b/app/assets/javascripts/vue_shared/components/commit.js.es6 index 62a22e39a3b..7f7c18ddeb1 100644 --- a/app/assets/javascripts/vue_common_component/commit.js.es6 +++ b/app/assets/javascripts/vue_shared/components/commit.js.es6 @@ -1,5 +1,5 @@ -/*= require vue */ /* global Vue */ + (() => { window.gl = window.gl || {}; diff --git a/app/assets/javascripts/vue_shared/components/pipelines_table.js.es6 b/app/assets/javascripts/vue_shared/components/pipelines_table.js.es6 new file mode 100644 index 00000000000..4bdaef31ee9 --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/pipelines_table.js.es6 @@ -0,0 +1,61 @@ +/* eslint-disable no-param-reassign */ +/* global Vue */ + +require('./pipelines_table_row'); +/** + * Pipelines Table Component. + * + * Given an array of objects, renders a table. + */ + +(() => { + window.gl = window.gl || {}; + gl.pipelines = gl.pipelines || {}; + + gl.pipelines.PipelinesTableComponent = Vue.component('pipelines-table-component', { + + props: { + pipelines: { + type: Array, + required: true, + default: () => ([]), + }, + + /** + * TODO: Remove this when we have webpack. + */ + svgs: { + type: Object, + required: true, + default: () => ({}), + }, + }, + + components: { + 'pipelines-table-row-component': gl.pipelines.PipelinesTableRowComponent, + }, + + template: ` + <table class="table ci-table"> + <thead> + <tr> + <th class="js-pipeline-status pipeline-status">Status</th> + <th class="js-pipeline-info pipeline-info">Pipeline</th> + <th class="js-pipeline-commit pipeline-commit">Commit</th> + <th class="js-pipeline-stages pipeline-stages">Stages</th> + <th class="js-pipeline-date pipeline-date"></th> + <th class="js-pipeline-actions pipeline-actions hidden-xs"></th> + </tr> + </thead> + <tbody> + <template v-for="model in pipelines" + v-bind:model="model"> + <tr is="pipelines-table-row-component" + :pipeline="model" + :svgs="svgs"></tr> + </template> + </tbody> + </table> + `, + }); +})(); diff --git a/app/assets/javascripts/vue_shared/components/pipelines_table_row.js.es6 b/app/assets/javascripts/vue_shared/components/pipelines_table_row.js.es6 new file mode 100644 index 00000000000..c819f0dd7cd --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/pipelines_table_row.js.es6 @@ -0,0 +1,234 @@ +/* eslint-disable no-param-reassign */ +/* global Vue */ + +require('../../vue_pipelines_index/status'); +require('../../vue_pipelines_index/pipeline_url'); +require('../../vue_pipelines_index/stage'); +require('../../vue_pipelines_index/pipeline_actions'); +require('../../vue_pipelines_index/time_ago'); +require('./commit'); +/** + * Pipeline table row. + * + * Given the received object renders a table row in the pipelines' table. + */ +(() => { + window.gl = window.gl || {}; + gl.pipelines = gl.pipelines || {}; + + gl.pipelines.PipelinesTableRowComponent = Vue.component('pipelines-table-row-component', { + + props: { + pipeline: { + type: Object, + required: true, + default: () => ({}), + }, + + /** + * TODO: Remove this when we have webpack; + */ + svgs: { + type: Object, + required: true, + default: () => ({}), + }, + }, + + components: { + 'commit-component': gl.CommitComponent, + 'pipeline-actions': gl.VuePipelineActions, + 'dropdown-stage': gl.VueStage, + 'pipeline-url': gl.VuePipelineUrl, + 'status-scope': gl.VueStatusScope, + 'time-ago': gl.VueTimeAgo, + }, + + computed: { + /** + * If provided, returns the commit tag. + * Needed to render the commit component column. + * + * This field needs a lot of verification, because of different possible cases: + * + * 1. person who is an author of a commit might be a GitLab user + * 2. if person who is an author of a commit is a GitLab user he/she can have a GitLab avatar + * 3. If GitLab user does not have avatar he/she might have a Gravatar + * 4. If committer is not a GitLab User he/she can have a Gravatar + * 5. We do not have consistent API object in this case + * 6. We should improve API and the code + * + * @returns {Object|Undefined} + */ + commitAuthor() { + let commitAuthorInformation; + + // 1. person who is an author of a commit might be a GitLab user + if (this.pipeline && + this.pipeline.commit && + this.pipeline.commit.author) { + // 2. if person who is an author of a commit is a GitLab user + // he/she can have a GitLab avatar + if (this.pipeline.commit.author.avatar_url) { + commitAuthorInformation = this.pipeline.commit.author; + + // 3. If GitLab user does not have avatar he/she might have a Gravatar + } else if (this.pipeline.commit.author_gravatar_url) { + commitAuthorInformation = Object.assign({}, this.pipeline.commit.author, { + avatar_url: this.pipeline.commit.author_gravatar_url, + }); + } + } + + // 4. If committer is not a GitLab User he/she can have a Gravatar + if (this.pipeline && + this.pipeline.commit) { + commitAuthorInformation = { + avatar_url: this.pipeline.commit.author_gravatar_url, + web_url: `mailto:${this.pipeline.commit.author_email}`, + username: this.pipeline.commit.author_name, + }; + } + + return commitAuthorInformation; + }, + + /** + * If provided, returns the commit tag. + * Needed to render the commit component column. + * + * @returns {String|Undefined} + */ + commitTag() { + if (this.pipeline.ref && + this.pipeline.ref.tag) { + return this.pipeline.ref.tag; + } + return undefined; + }, + + /** + * If provided, returns the commit ref. + * Needed to render the commit component column. + * + * Matched `url` prop sent in the API to `path` prop needed + * in the commit component. + * + * @returns {Object|Undefined} + */ + commitRef() { + if (this.pipeline.ref) { + return Object.keys(this.pipeline.ref).reduce((accumulator, prop) => { + if (prop === 'url') { + accumulator.path = this.pipeline.ref[prop]; + } else { + accumulator[prop] = this.pipeline.ref[prop]; + } + return accumulator; + }, {}); + } + + return undefined; + }, + + /** + * If provided, returns the commit url. + * Needed to render the commit component column. + * + * @returns {String|Undefined} + */ + commitUrl() { + if (this.pipeline.commit && + this.pipeline.commit.commit_path) { + return this.pipeline.commit.commit_path; + } + return undefined; + }, + + /** + * If provided, returns the commit short sha. + * Needed to render the commit component column. + * + * @returns {String|Undefined} + */ + commitShortSha() { + if (this.pipeline.commit && + this.pipeline.commit.short_id) { + return this.pipeline.commit.short_id; + } + return undefined; + }, + + /** + * If provided, returns the commit title. + * Needed to render the commit component column. + * + * @returns {String|Undefined} + */ + commitTitle() { + if (this.pipeline.commit && + this.pipeline.commit.title) { + return this.pipeline.commit.title; + } + return undefined; + }, + }, + + methods: { + /** + * FIXME: This should not be in this component but in the components that + * need this function. + * + * Used to render SVGs in the following components: + * - status-scope + * - dropdown-stage + * + * @param {String} string + * @return {String} + */ + match(string) { + return string.replace(/_([a-z])/g, (m, w) => w.toUpperCase()); + }, + }, + + template: ` + <tr class="commit"> + <status-scope + :pipeline="pipeline" + :svgs="svgs" + :match="match"> + </status-scope> + + <pipeline-url :pipeline="pipeline"></pipeline-url> + + <td> + <commit-component + :tag="commitTag" + :commit-ref="commitRef" + :commit-url="commitUrl" + :short-sha="commitShortSha" + :title="commitTitle" + :author="commitAuthor" + :commit-icon-svg="svgs.commitIconSvg"> + </commit-component> + </td> + + <td class="stage-cell"> + <div class="stage-container dropdown js-mini-pipeline-graph" + v-if="pipeline.details.stages.length > 0" + v-for="stage in pipeline.details.stages"> + <dropdown-stage + :stage="stage" + :svgs="svgs" + :match="match"> + </dropdown-stage> + </div> + </td> + + <time-ago :pipeline="pipeline" :svgs="svgs"></time-ago> + + <pipeline-actions :pipeline="pipeline" :svgs="svgs"></pipeline-actions> + </tr> + `, + }); +})(); diff --git a/app/assets/javascripts/vue_shared/components/table_pagination.js.es6 b/app/assets/javascripts/vue_shared/components/table_pagination.js.es6 new file mode 100644 index 00000000000..67c6cb73761 --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/table_pagination.js.es6 @@ -0,0 +1,152 @@ +/* global Vue, gl */ +/* eslint-disable no-param-reassign, no-plusplus */ + +window.Vue = require('vue'); + +((gl) => { + const PAGINATION_UI_BUTTON_LIMIT = 4; + const UI_LIMIT = 6; + const SPREAD = '...'; + const PREV = 'Prev'; + const NEXT = 'Next'; + const FIRST = '<< First'; + const LAST = 'Last >>'; + + gl.VueGlPagination = Vue.extend({ + props: { + + // TODO: Consider refactoring in light of turbolinks removal. + + /** + This function will take the information given by the pagination component + And make a new Turbolinks call + + Here is an example `change` method: + + change(pagenum, apiScope) { + gl.utils.visitUrl(`?scope=${apiScope}&p=${pagenum}`); + }, + */ + + change: { + type: Function, + required: true, + }, + + /** + pageInfo will come from the headers of the API call + in the `.then` clause of the VueResource API call + there should be a function that contructs the pageInfo for this component + + This is an example: + + const pageInfo = headers => ({ + perPage: +headers['X-Per-Page'], + page: +headers['X-Page'], + total: +headers['X-Total'], + totalPages: +headers['X-Total-Pages'], + nextPage: +headers['X-Next-Page'], + previousPage: +headers['X-Prev-Page'], + }); + */ + + pageInfo: { + type: Object, + required: true, + }, + }, + methods: { + changePage(e) { + let apiScope = gl.utils.getParameterByName('scope'); + + if (!apiScope) apiScope = 'all'; + + const text = e.target.innerText; + const { totalPages, nextPage, previousPage } = this.pageInfo; + + switch (text) { + case SPREAD: + break; + case LAST: + this.change(totalPages, apiScope); + break; + case NEXT: + this.change(nextPage, apiScope); + break; + case PREV: + this.change(previousPage, apiScope); + break; + case FIRST: + this.change(1, apiScope); + break; + default: + this.change(+text, apiScope); + break; + } + }, + }, + computed: { + prev() { + return this.pageInfo.previousPage; + }, + next() { + return this.pageInfo.nextPage; + }, + getItems() { + const total = this.pageInfo.totalPages; + const page = this.pageInfo.page; + const items = []; + + if (page > 1) items.push({ title: FIRST }); + + if (page > 1) { + items.push({ title: PREV, prev: true }); + } else { + items.push({ title: PREV, disabled: true, prev: true }); + } + + if (page > UI_LIMIT) items.push({ title: SPREAD, separator: true }); + + const start = Math.max(page - PAGINATION_UI_BUTTON_LIMIT, 1); + const end = Math.min(page + PAGINATION_UI_BUTTON_LIMIT, total); + + for (let i = start; i <= end; i++) { + const isActive = i === page; + items.push({ title: i, active: isActive, page: true }); + } + + if (total - page > PAGINATION_UI_BUTTON_LIMIT) { + items.push({ title: SPREAD, separator: true, page: true }); + } + + if (page === total) { + items.push({ title: NEXT, disabled: true, next: true }); + } else if (total - page >= 1) { + items.push({ title: NEXT, next: true }); + } + + if (total - page >= 1) items.push({ title: LAST, last: true }); + + return items; + }, + }, + template: ` + <div class="gl-pagination"> + <ul class="pagination clearfix"> + <li v-for='item in getItems' + :class='{ + page: item.page, + prev: item.prev, + next: item.next, + separator: item.separator, + active: item.active, + disabled: item.disabled + }' + > + <a @click="changePage($event)">{{item.title}}</a> + </li> + </ul> + </div> + `, + }); +})(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/vue_shared/vue_resource_interceptor.js.es6 b/app/assets/javascripts/vue_shared/vue_resource_interceptor.js.es6 new file mode 100644 index 00000000000..d3229f9f730 --- /dev/null +++ b/app/assets/javascripts/vue_shared/vue_resource_interceptor.js.es6 @@ -0,0 +1,23 @@ +/* eslint-disable func-names, prefer-arrow-callback, no-unused-vars, +no-param-reassign, no-plusplus */ +/* global Vue */ + +Vue.http.interceptors.push((request, next) => { + Vue.activeResources = Vue.activeResources ? Vue.activeResources + 1 : 1; + + next((response) => { + if (typeof response.data === 'string') { + response.data = JSON.parse(response.data); + } + + Vue.activeResources--; + }); +}); + +Vue.http.interceptors.push((request, next) => { + // needed in order to not break the tests. + if ($.rails) { + request.headers['X-CSRF-Token'] = $.rails.csrfToken(); + } + next(); +}); diff --git a/app/assets/javascripts/wikis.js.es6 b/app/assets/javascripts/wikis.js.es6 index ecff5fd5bf4..ef99b2e92f0 100644 --- a/app/assets/javascripts/wikis.js.es6 +++ b/app/assets/javascripts/wikis.js.es6 @@ -1,9 +1,9 @@ /* eslint-disable no-param-reassign */ /* global Breakpoints */ -/*= require latinise */ -/*= require breakpoints */ -/*= require jquery.nicescroll */ +require('vendor/latinise'); +require('./breakpoints'); +require('vendor/jquery.nicescroll'); ((global) => { const dasherize = str => str.replace(/[_\s]+/g, '-'); diff --git a/app/assets/javascripts/zen_mode.js b/app/assets/javascripts/zen_mode.js index e09b59dd5aa..d9261cda1b1 100644 --- a/app/assets/javascripts/zen_mode.js +++ b/app/assets/javascripts/zen_mode.js @@ -1,4 +1,4 @@ -/* eslint-disable func-names, space-before-function-paren, wrap-iife, prefer-arrow-callback, no-unused-vars, consistent-return, camelcase, comma-dangle, padded-blocks, max-len */ +/* eslint-disable func-names, space-before-function-paren, wrap-iife, prefer-arrow-callback, no-unused-vars, consistent-return, camelcase, comma-dangle, max-len */ /* global Dropzone */ /* global Mousetrap */ @@ -6,11 +6,11 @@ // /*= provides zen_mode:enter */ /*= provides zen_mode:leave */ -// -/*= require jquery.scrollTo */ -/*= require dropzone */ -/*= require mousetrap */ -/*= require mousetrap/pause */ + +require('vendor/jquery.scrollTo'); +window.Dropzone = require('dropzone'); +require('mousetrap'); +require('mousetrap/plugins/pause/mousetrap-pause'); // // ### Events @@ -93,7 +93,5 @@ }; return ZenMode; - })(); - }).call(this); diff --git a/app/assets/stylesheets/framework.scss b/app/assets/stylesheets/framework.scss index 40bc0579393..08f203a1bf6 100644 --- a/app/assets/stylesheets/framework.scss +++ b/app/assets/stylesheets/framework.scss @@ -9,6 +9,7 @@ @import "framework/asciidoctor.scss"; @import "framework/blocks.scss"; @import "framework/buttons.scss"; +@import "framework/badges.scss"; @import "framework/calendar.scss"; @import "framework/callout.scss"; @import "framework/common.scss"; @@ -30,7 +31,6 @@ @import "framework/modal.scss"; @import "framework/nav.scss"; @import "framework/pagination.scss"; -@import "framework/progress.scss"; @import "framework/panels.scss"; @import "framework/selects.scss"; @import "framework/sidebar.scss"; diff --git a/app/assets/stylesheets/framework/animations.scss b/app/assets/stylesheets/framework/animations.scss index f1d36efb3de..0a26b4c6a8c 100644 --- a/app/assets/stylesheets/framework/animations.scss +++ b/app/assets/stylesheets/framework/animations.scss @@ -50,3 +50,100 @@ .pulse { @include webkit-prefix(animation-name, pulse); } + +/* +* General hover animations +*/ + + +// Sass multiple transitions mixin | https://gist.github.com/tobiasahlin/7a421fb9306a4f518aab +// Usage: @include transition(width, height 0.3s ease-in-out); +// Output: -webkit-transition(width 0.2s, height 0.3s ease-in-out); +// transition(width 0.2s, height 0.3s ease-in-out); +// +// Pass in any number of transitions +@mixin transition($transitions...) { + $unfoldedTransitions: (); + @each $transition in $transitions { + $unfoldedTransitions: append($unfoldedTransitions, unfoldTransition($transition), comma); + } + + transition: $unfoldedTransitions; +} + +@mixin disableAllAnimation { + /*CSS transitions*/ + -o-transition-property: none !important; + -moz-transition-property: none !important; + -ms-transition-property: none !important; + -webkit-transition-property: none !important; + transition-property: none !important; + /*CSS transforms*/ + -o-transform: none !important; + -moz-transform: none !important; + -ms-transform: none !important; + -webkit-transform: none !important; + transform: none !important; + /*CSS animations*/ + -webkit-animation: none !important; + -moz-animation: none !important; + -o-animation: none !important; + -ms-animation: none !important; + animation: none !important; +} + +@function unfoldTransition ($transition) { + // Default values + $property: all; + $duration: $general-hover-transition-duration; + $easing: $general-hover-transition-curve; // Browser default is ease, which is what we want + $delay: null; // Browser default is 0, which is what we want + $defaultProperties: ($property, $duration, $easing, $delay); + + // Grab transition properties if they exist + $unfoldedTransition: (); + @for $i from 1 through length($defaultProperties) { + $p: null; + @if $i <= length($transition) { + $p: nth($transition, $i); + } @else { + $p: nth($defaultProperties, $i); + } + $unfoldedTransition: append($unfoldedTransition, $p); + } + + @return $unfoldedTransition; +} + +.btn, +.side-nav-toggle { + @include transition(background-color, border-color, color, box-shadow); +} + +.dropdown-menu-toggle, +.avatar-circle, +.header-user-avatar { + @include transition(border-color); +} + +.note-action-button .link-highlight, +.toolbar-btn, +.dropdown-toggle-caret, +.fa:not(.fa-bell) { + @include transition(color); +} + +a { + @include transition(background-color, color, border); +} + +.stage-nav-item { + @include transition(background-color, box-shadow); +} + +.nav-sidebar a, +.dropdown-menu a, +.dropdown-menu button, +.dropdown-menu-nav a { + transition: none; +} diff --git a/app/assets/stylesheets/framework/avatar.scss b/app/assets/stylesheets/framework/avatar.scss index 48827578d94..1d59700543c 100644 --- a/app/assets/stylesheets/framework/avatar.scss +++ b/app/assets/stylesheets/framework/avatar.scss @@ -37,6 +37,8 @@ display: inline-block; margin-left: 4px; margin-bottom: 2px; + flex-shrink: 0; + -webkit-flex-shrink: 0; &.s16 { margin-right: 4px; } &.s24 { margin-right: 4px; } @@ -52,6 +54,10 @@ border-radius: 0; border: none; } + + &:not([href]):hover { + border-color: rgba($avatar-border, .2); + } } .identicon { diff --git a/app/assets/stylesheets/framework/awards.scss b/app/assets/stylesheets/framework/awards.scss index 9fc9bcebc44..49907417e26 100644 --- a/app/assets/stylesheets/framework/awards.scss +++ b/app/assets/stylesheets/framework/awards.scss @@ -97,8 +97,20 @@ padding: 5px 6px; outline: 0; - &:hover, + &.disabled { + cursor: default; + + &:hover, + &:focus, + &:active { + background-color: $white-light; + border-color: $border-color; + box-shadow: none; + } + } + &.active, + &:hover, &:active { background-color: $row-hover; border-color: $row-hover-border; diff --git a/app/assets/stylesheets/framework/badges.scss b/app/assets/stylesheets/framework/badges.scss new file mode 100644 index 00000000000..47a8f44c709 --- /dev/null +++ b/app/assets/stylesheets/framework/badges.scss @@ -0,0 +1,6 @@ +.badge { + font-weight: normal; + background-color: $badge-bg; + color: $badge-color; + vertical-align: baseline; +} diff --git a/app/assets/stylesheets/framework/blocks.scss b/app/assets/stylesheets/framework/blocks.scss index 9f02749f5ab..0f9213b98e3 100644 --- a/app/assets/stylesheets/framework/blocks.scss +++ b/app/assets/stylesheets/framework/blocks.scss @@ -1,15 +1,15 @@ .centered-light-block { text-align: center; - color: $gl-gray; + color: $gl-text-color; margin: 20px; } .nothing-here-block { text-align: center; padding: 20px; - color: $gl-gray; + color: $gl-text-color; font-weight: normal; - font-size: 16px; + font-size: 14px; line-height: 36px; &.diff-collapsed { @@ -29,7 +29,7 @@ margin-bottom: 0; border-top: 1px solid $white-dark; border-bottom: 1px solid $white-dark; - color: $gl-gray; + color: $gl-text-color; &.oneline-block { line-height: 42px; @@ -82,7 +82,12 @@ } .block-controls { - float: right; + display: -webkit-flex; + display: flex; + -webkit-justify-content: flex-end; + justify-content: flex-end; + -webkit-flex: 1; + flex: 1; .control { float: left; @@ -135,11 +140,11 @@ } .cover-title { - color: $gl-header-color; + color: $gl-text-color; font-size: 23px; h1 { - color: $gl-gray-dark; + color: $gl-text-color; margin-bottom: 6px; font-size: 23px; } @@ -153,7 +158,7 @@ p { padding: 0 $gl-padding; - color: $gl-text-color-dark; + color: $gl-text-color; } } @@ -211,7 +216,7 @@ display: inline; font-weight: normal; font-size: 24px; - color: $gl-title-color; + color: $gl-text-color; } } } @@ -273,6 +278,10 @@ display: inline-block; } + .btn { + margin: $btn-side-margin $btn-side-margin 0 0; + } + @media(max-width: $screen-xs-max) { margin-top: 50px; text-align: center; @@ -281,4 +290,15 @@ width: 100%; } } + + @media(min-width: $screen-xs-max) { + &.labels .text-content { + margin-top: 70px; + } + } +} + +.flex-container-block { + display: -webkit-flex; + display: flex; } diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss index a11f1cd7735..cda46223492 100644 --- a/app/assets/stylesheets/framework/buttons.scss +++ b/app/assets/stylesheets/framework/buttons.scss @@ -88,7 +88,7 @@ } @mixin btn-gray { - @include btn-color($gray-light, $border-gray-normal, $gray-normal, $border-gray-normal, $gray-dark, $border-gray-dark, $gl-gray-dark); + @include btn-color($gray-light, $border-gray-normal, $gray-normal, $border-gray-normal, $gray-dark, $border-gray-dark, $gl-text-color); } @mixin btn-white { @@ -242,7 +242,7 @@ } .btn-transparent { - color: $gl-gray-light; + color: $gl-text-color-secondary; background-color: transparent; border: 0; @@ -324,21 +324,17 @@ &:focus { cursor: text; box-shadow: none; - border-color: $border-color; + border-color: lighten($dropdown-input-focus-border, 20%); color: $gray-darkest; background-color: $gray-light; } } -.btn-file-option { - background: linear-gradient(180deg, $white-light 25%, $gray-light 100%); -} - .btn-build { margin-left: 10px; i { - color: $gl-gray-light; + color: $gl-text-color-secondary; } } diff --git a/app/assets/stylesheets/framework/calendar.scss b/app/assets/stylesheets/framework/calendar.scss index ef921a8c6a9..1d2d1bfc0d7 100644 --- a/app/assets/stylesheets/framework/calendar.scss +++ b/app/assets/stylesheets/framework/calendar.scss @@ -1,6 +1,7 @@ .calender-block { padding-left: 0; padding-right: 0; + direction: rtl; @media (min-width: $screen-sm-min) and (max-width: $screen-md-max) { overflow-x: scroll; diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss index 251e43d2edd..a4b38723bbd 100644 --- a/app/assets/stylesheets/framework/common.scss +++ b/app/assets/stylesheets/framework/common.scss @@ -26,6 +26,7 @@ .append-bottom-default { margin-bottom: $gl-padding; } .inline { display: inline-block; } .center { text-align: center; } +.vertical-align-middle { vertical-align: middle; } .underlined-link { text-decoration: underline; } .hint { font-style: italic; color: $hint-color; } @@ -57,16 +58,33 @@ pre { border-radius: 0; color: $well-pre-color; } + + &.wrap { + word-break: break-word; + white-space: pre-wrap; + } } hr { margin: $gl-padding 0; + border-top: 1px solid darken($gray-normal, 8%); } .str-truncated { @include str-truncated; } +.block-truncated { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + + > div, + .str-truncated { + display: inline; + } +} + .item-title { font-weight: 600; } /** FLASH message **/ @@ -235,6 +253,8 @@ li.note { .progress { margin-bottom: 0; margin-top: 4px; + box-shadow: none; + background-color: $border-gray-light; } } @@ -394,7 +414,7 @@ table { padding: 0 10px; clip: auto; text-decoration: none; - color: $gl-title-color; + color: $gl-text-color; background: $gray-light; z-index: 1; } diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss index 889366d6ddf..ca5861bf3e6 100644 --- a/app/assets/stylesheets/framework/dropdowns.scss +++ b/app/assets/stylesheets/framework/dropdowns.scss @@ -125,7 +125,8 @@ top: 100%; left: 0; z-index: 9; - width: 240px; + max-width: 280px; + min-width: 240px; margin-top: 2px; margin-bottom: 0; font-size: 14px; @@ -201,7 +202,7 @@ } .icon-play { - fill: $gl-gray-light; + fill: $gl-text-color-secondary; margin-right: 6px; height: 12px; width: 11px; @@ -209,7 +210,7 @@ } .dropdown-header { - color: $gl-gray-light; + color: $gl-text-color-secondary; font-size: 13px; line-height: 22px; padding: 0 10px; @@ -222,10 +223,15 @@ .unclickable { cursor: not-allowed; padding: 5px 8px; - color: $gl-gray-light; + color: $gl-text-color-secondary; } } +.dropdown-menu-drop-up { + top: auto; + bottom: 100%; +} + .dropdown-menu-large { width: 340px; } @@ -592,7 +598,7 @@ } .ui-datepicker-title { - color: $gl-gray; + color: $gl-text-color; font-size: 14px; line-height: 1; font-weight: normal; @@ -614,17 +620,17 @@ .dropdown-menu-inner-title { display: block; - color: $gl-title-color; + color: $gl-text-color; font-weight: 600; } .dropdown-menu-inner-content { display: block; - color: $gl-gray-light; + color: $gl-text-color-secondary; } .dropdown-toggle-text { &.is-default { - color: $gl-gray-light; + color: $gl-text-color-secondary; } } diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss index 88ed0a4a17e..c51912b4ac4 100644 --- a/app/assets/stylesheets/framework/files.scss +++ b/app/assets/stylesheets/framework/files.scss @@ -182,3 +182,52 @@ span.idiff { border-bottom-right-radius: 2px; } } + +.file-stats { + ul { + list-style: none; + margin: 0; + padding: 10px 0; + + li { + padding: 3px 0; + line-height: 20px; + } + } + + .new-file { + a { + color: $gl-text-green; + } + } + + .renamed-file { + a { + color: $gl-text-orange; + } + } + + .deleted-file { + a { + color: $gl-text-red; + } + } + + .edit-file { + a { + color: $gl-text-color; + } + } + + a { + text-decoration: none; + + .new-file { + color: $notify-new-file; + } + + .deleted-file { + color: $notify-deleted-file; + } + } +} diff --git a/app/assets/stylesheets/framework/filters.scss b/app/assets/stylesheets/framework/filters.scss index 19827943385..e3da467a27c 100644 --- a/app/assets/stylesheets/framework/filters.scss +++ b/app/assets/stylesheets/framework/filters.scss @@ -23,3 +23,129 @@ } } +.filtered-search-container { + display: -webkit-flex; + display: flex; +} + +.filtered-search-input-container { + display: -webkit-flex; + display: flex; + position: relative; + width: 100%; + + .form-control { + padding-left: 25px; + padding-right: 25px; + + &:focus ~ .fa-filter { + color: $common-gray-dark; + } + } + + .fa-filter { + position: absolute; + top: 10px; + left: 10px; + color: $gray-darkest; + } + + .fa-times { + right: 10px; + color: $gray-darkest; + } + + .clear-search { + width: 35px; + background-color: transparent; + border: none; + position: absolute; + right: 0; + height: 100%; + outline: none; + + &:hover .fa-times { + color: $common-gray-dark; + } + } +} + +.dropdown-menu .filter-dropdown-item { + padding: 0; +} + +.filter-dropdown { + max-height: 215px; + overflow: auto; +} + +%filter-dropdown-item-btn-hover { + background-color: $dropdown-hover-color; + color: $white-light; + text-decoration: none; + + .avatar { + border-color: $white-light; + } +} + +.filter-dropdown-item { + .btn { + border: none; + width: 100%; + text-align: left; + padding: 8px 16px; + text-overflow: ellipsis; + overflow: hidden; + border-radius: 0; + + .fa { + width: 15px; + } + + .dropdown-label-box { + border-color: $white-light; + border-style: solid; + border-width: 1px; + width: 17px; + height: 17px; + } + + &:hover, + &:focus { + @extend %filter-dropdown-item-btn-hover; + } + } + + .dropdown-light-content { + font-size: 14px; + font-weight: 400; + } + + .dropdown-user { + display: -webkit-flex; + display: flex; + } + + .dropdown-user-details { + display: -webkit-flex; + display: flex; + -webkit-flex-direction: column; + flex-direction: column; + + &> span { + white-space: normal; + word-break: break-all; + } + } +} + +.filter-dropdown-item.dropdown-active { + .btn { + @extend %filter-dropdown-item-btn-hover; + } +} + +.filter-dropdown-loading { + padding: 8px 16px; +} diff --git a/app/assets/stylesheets/framework/forms.scss b/app/assets/stylesheets/framework/forms.scss index 8726a69867b..25d6fbe465a 100644 --- a/app/assets/stylesheets/framework/forms.scss +++ b/app/assets/stylesheets/framework/forms.scss @@ -153,7 +153,7 @@ label { } .form-control::-webkit-input-placeholder { - color: $gl-gray-light; + color: $gl-text-color-secondary; } .input-group { diff --git a/app/assets/stylesheets/framework/gitlab-theme.scss b/app/assets/stylesheets/framework/gitlab-theme.scss index 5cd242af91d..d6566dc4ec9 100644 --- a/app/assets/stylesheets/framework/gitlab-theme.scss +++ b/app/assets/stylesheets/framework/gitlab-theme.scss @@ -80,29 +80,32 @@ } } + .about-gitlab { + color: $color-light; + } } } } -$theme-charcoal: #3d454d; -$theme-charcoal-light: #485157; -$theme-charcoal-dark: #383f45; -$theme-charcoal-text: #b9bbbe; +$theme-charcoal-light: #b9bbbe; +$theme-charcoal: #485157; +$theme-charcoal-dark: #3d454d; +$theme-charcoal-darker: #383f45; $theme-blue-light: #becde9; $theme-blue: #2980b9; $theme-blue-dark: #1970a9; $theme-blue-darker: #096099; -$theme-graphite-lighter: #ccc; -$theme-graphite-light: #777; -$theme-graphite: #666; -$theme-graphite-dark: #555; +$theme-graphite-light: #ccc; +$theme-graphite: #777; +$theme-graphite-dark: #666; +$theme-graphite-darker: #555; -$theme-gray-light: #979797; -$theme-gray: #373737; -$theme-gray-dark: #272727; -$theme-gray-darker: #222; +$theme-black-light: #979797; +$theme-black: #373737; +$theme-black-dark: #272727; +$theme-black-darker: #222; $theme-green-light: #adc; $theme-green: #019875; @@ -120,15 +123,15 @@ body { } &.ui_charcoal { - @include gitlab-theme($theme-charcoal-text, $theme-charcoal-light, $theme-charcoal, $theme-charcoal-dark); + @include gitlab-theme($theme-charcoal-light, $theme-charcoal, $theme-charcoal-dark, $theme-charcoal-darker); } &.ui_graphite { - @include gitlab-theme($theme-graphite-lighter, $theme-graphite-light, $theme-graphite, $theme-graphite-dark); + @include gitlab-theme($theme-graphite-light, $theme-graphite, $theme-graphite-dark, $theme-graphite-darker); } - &.ui_gray { - @include gitlab-theme($theme-gray-light, $theme-gray, $theme-gray-dark, $theme-gray-darker); + &.ui_black { + @include gitlab-theme($theme-black-light, $theme-black, $theme-black-dark, $theme-black-darker); } &.ui_green { diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss index 971940773f7..2a01bc4d44d 100644 --- a/app/assets/stylesheets/framework/header.scss +++ b/app/assets/stylesheets/framework/header.scss @@ -45,7 +45,7 @@ header { padding: 0; .nav > li > a { - color: $gl-gray-light; + color: $gl-text-color-secondary; font-size: 18px; padding: 0; margin: ($header-height - 28) / 2 0; @@ -57,13 +57,21 @@ header { &.header-user-dropdown-toggle { margin-left: 14px; + + &:hover, + &:focus, + &:active { + .header-user-avatar { + border-color: rgba($avatar-border, .2); + } + } } &:hover, &:focus, &:active { background-color: $gray-light; - color: darken($gl-gray-light, 30%); + color: $gl-text-color; .todos-pending-count { background: darken($todo-alert-blue, 10%); @@ -88,7 +96,7 @@ header { } &.active { - color: $gl-gray-light; + color: $gl-text-color-secondary; } } } @@ -104,6 +112,7 @@ header { &:hover { background-color: $white-normal; + color: $gl-header-nav-hover-color; } } } @@ -180,6 +189,7 @@ header { &:hover { text-decoration: underline; + color: $gl-header-nav-hover-color; } } @@ -198,7 +208,7 @@ header { cursor: pointer; &:hover { - color: darken($color: $gl-text-color, $amount: 30%); + color: $gl-header-nav-hover-color; } } @@ -271,4 +281,5 @@ header { float: left; margin-right: 5px; border-radius: 50%; + border: 1px solid $avatar-border; } diff --git a/app/assets/stylesheets/framework/icons.scss b/app/assets/stylesheets/framework/icons.scss index 8624a25c052..db8d231a82a 100644 --- a/app/assets/stylesheets/framework/icons.scss +++ b/app/assets/stylesheets/framework/icons.scss @@ -15,6 +15,7 @@ } .ci-status-icon-pending, +.ci-status-icon-failed_with_warnings, .ci-status-icon-success_with_warnings { color: $gl-warning; @@ -34,10 +35,10 @@ .ci-status-icon-canceled, .ci-status-icon-disabled, .ci-status-icon-not-found { - color: $gl-gray; + color: $gl-text-color; svg { - fill: $gl-gray; + fill: $gl-text-color; } } @@ -57,3 +58,9 @@ fill: $gl-text-color; } } + +.icon-link { + &:hover { + text-decoration: none; + } +} diff --git a/app/assets/stylesheets/framework/issue_box.scss b/app/assets/stylesheets/framework/issue_box.scss index 298913108ee..46632f15f35 100644 --- a/app/assets/stylesheets/framework/issue_box.scss +++ b/app/assets/stylesheets/framework/issue_box.scss @@ -41,6 +41,6 @@ } &.status-box-upcoming { - background: $gl-gray-light; + background: $gl-text-color-secondary; } } diff --git a/app/assets/stylesheets/framework/layout.scss b/app/assets/stylesheets/framework/layout.scss index 59fae61a44f..29d55c44699 100644 --- a/app/assets/stylesheets/framework/layout.scss +++ b/app/assets/stylesheets/framework/layout.scss @@ -33,10 +33,27 @@ body { } .alert-wrapper { - margin-bottom: $gl-padding; - .alert { margin-bottom: 0; + + &:last-child { + margin-bottom: $gl-padding; + } + } + + .alert-link-group { + float: right; + } + + /* Center alert text and alert action links on smaller screens */ + @media (max-width: $screen-sm-max) { + .alert { + text-align: center; + } + + .alert-link-group { + float: none; + } } /* Stripe the background colors so that adjacent alert-warnings are distinct from one another */ diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss index e96cd671e34..2bfdb9f9601 100644 --- a/app/assets/stylesheets/framework/lists.scss +++ b/app/assets/stylesheets/framework/lists.scss @@ -128,7 +128,7 @@ ul.content-list { } a { - color: $gl-dark-link-color; + color: $gl-text-color; } .member-group-link { @@ -155,7 +155,8 @@ ul.content-list { } > .btn, - > .btn-group { + > .btn-group, + > .dropdown.inline { margin-right: $gl-padding-top; display: inline-block; margin-top: 3px; @@ -163,6 +164,10 @@ ul.content-list { &:last-child { margin-right: 0; + + @media(max-width: $screen-xs-max) { + margin: 0 auto; + } } } @@ -199,6 +204,7 @@ ul.content-list { display: -webkit-flex; display: -ms-flexbox; display: flex; + align-items: center; white-space: nowrap; } @@ -208,6 +214,11 @@ ul.content-list { padding-right: 8px; } + .row-fixed-content { + flex: 0 0 auto; + margin-left: auto; + } + .row-title { font-weight: 600; } @@ -224,22 +235,53 @@ ul.content-list { } .label-default { - color: $gl-gray-light; + color: $gl-text-color-secondary; } } -.panel > .content-list > li { - padding: $gl-padding-top $gl-padding; +// Table list +.table-list { + display: table; + width: 100%; + + .table-list-row { + display: table-row; + } + + .table-list-cell { + display: table-cell; + vertical-align: top; + padding: 10px 16px; + border-bottom: 1px solid $gray-darker; + + &.avatar-cell { + width: 36px; + padding-right: 0; + + img { + margin-right: 0; + } + } + } - &.commit { - @media (min-width: $screen-sm-min) { - padding-left: 46px + $gl-padding; + &.table-wide { + .table-list-cell { + &:last-of-type { + padding-right: 0; + } + + &:first-of-type { + padding-left: 0; + } } } } +.panel > .content-list > li { + padding: $gl-padding-top $gl-padding; +} + ul.controls { - padding-top: 1px; float: right; list-style: none; @@ -265,3 +307,7 @@ ul.controls { } } } + +ul.indent-list { + padding: 10px 0 0 30px; +} diff --git a/app/assets/stylesheets/framework/markdown_area.scss b/app/assets/stylesheets/framework/markdown_area.scss index e30d81d09f0..d4758d90352 100644 --- a/app/assets/stylesheets/framework/markdown_area.scss +++ b/app/assets/stylesheets/framework/markdown_area.scss @@ -73,7 +73,7 @@ } .referenced-users { - color: $gl-header-color; + color: $gl-text-color; padding-top: 10px; } @@ -135,7 +135,7 @@ .toolbar-btn { float: left; padding: 0 5px; - color: $gl-gray-light; + color: $gl-text-color-secondary; background: transparent; border: 0; outline: 0; @@ -159,6 +159,7 @@ .cur { .avatar { border: 1px solid $white-light; + @include disableAllAnimation; } } } diff --git a/app/assets/stylesheets/framework/mixins.scss b/app/assets/stylesheets/framework/mixins.scss index 4f2ac77f228..1acd06122a3 100644 --- a/app/assets/stylesheets/framework/mixins.scss +++ b/app/assets/stylesheets/framework/mixins.scss @@ -46,7 +46,7 @@ &.light { a { - color: $gl-gray; + color: $gl-text-color; } } } diff --git a/app/assets/stylesheets/framework/mobile.scss b/app/assets/stylesheets/framework/mobile.scss index 7eb9962ba33..8e2c56a8488 100644 --- a/app/assets/stylesheets/framework/mobile.scss +++ b/app/assets/stylesheets/framework/mobile.scss @@ -23,21 +23,21 @@ margin-right: 0; } - .issues-details-filters, + .issues-details-filters:not(.filtered-search-block), .dash-projects-filters, .check-all-holder { display: none; } - .rss-btn { + .issues-holder .issue-check { display: none; } - .project-home-links { + .rss-btn { display: none; } - .project-avatar { + .project-home-links { display: none; } diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss index e4affbb1be1..fd081c2d7e1 100644 --- a/app/assets/stylesheets/framework/nav.scss +++ b/app/assets/stylesheets/framework/nav.scss @@ -51,7 +51,7 @@ margin-bottom: -1px; font-size: 14px; line-height: 28px; - color: $gl-gray-light; + color: $gl-text-color-secondary; border-bottom: 2px solid transparent; &:hover, @@ -76,13 +76,6 @@ color: $black; } } - - .badge { - font-weight: normal; - background-color: $nav-badge-bg; - color: $gl-gray-light; - vertical-align: baseline; - } } &.sub-nav { @@ -108,7 +101,7 @@ &:hover, &:active, &:focus { - border-bottom: none; + border-color: transparent; } } } @@ -123,8 +116,8 @@ padding-top: 16px; padding-bottom: 11px; display: inline-block; - width: 50%; line-height: 28px; + white-space: normal; /* Small devices (phones, tablets, 768px and lower) */ @media (max-width: $screen-xs-max) { @@ -165,30 +158,24 @@ } .nav-controls { - width: 50%; display: inline-block; float: right; text-align: right; padding: 11px 0; margin-bottom: 0; - > .dropdown { - margin-right: $gl-padding-top; - display: inline-block; - vertical-align: top; - - &:last-child { - margin-right: 0; - } - } - - > .btn { + > .btn, + > .btn-container, + > .dropdown, + > input, + > form { margin-right: $gl-padding-top; display: inline-block; vertical-align: top; &:last-child { margin-right: 0; + float: right; } } @@ -196,19 +183,21 @@ float: none; } - > form { - display: inline-block; - } - .icon-label { display: none; } - input { + .btn, + .dropdown, + .dropdown-toggle, + input, + form { height: 35px; + } + + input { display: inline-block; position: relative; - margin-right: $gl-padding-top; /* Medium devices (desktops, 992px and up) */ @media (min-width: $screen-md-min) { width: 200px; } @@ -232,6 +221,7 @@ .btn, form, .dropdown, + .dropdown-toggle, .dropdown-menu-toggle, .form-control { margin: 0 0 10px; @@ -270,6 +260,10 @@ .nav-text, .nav-controls { width: auto; + + @media (max-width: $screen-xs-max) { + width: 100%; + } } } @@ -282,6 +276,10 @@ padding: 17px 0; } } + + pre { + width: 100%; + } } .layout-nav { @@ -296,16 +294,18 @@ .container-fluid { position: relative; + + .nav-control { + @media (max-width: $screen-sm-max) { + margin-right: 75px; + } + } } .controls { float: right; padding: 7px 0 0; - @media (max-width: $screen-sm-max) { - display: none; - } - i { color: $layout-link-gray; } @@ -317,7 +317,7 @@ .fa-caret-down { margin-left: 5px; - color: $gl-gray-light; + color: $gl-text-color-secondary; } .dropdown { @@ -363,6 +363,7 @@ .fade-left { @include fade(right, $gray-light); left: -5px; + text-align: center; .fa { left: -7px; @@ -435,3 +436,40 @@ } } } + +@media (max-width: $screen-xs-max) { + .top-area { + flex-flow: row wrap; + + .nav-controls { + $controls-margin: $btn-xs-side-margin - 2px; + flex: 0 0 100%; + + &.controls-flex { + display: flex; + flex-flow: row wrap; + align-items: center; + justify-content: center; + padding: 0 0 $gl-padding-top; + } + + .controls-item, + .controls-item-full, + .controls-item:last-child { + flex: 1 1 35%; + display: block; + width: 100%; + margin: $controls-margin; + + .btn, + .dropdown { + margin: 0; + } + } + + .controls-item-full { + flex: 1 1 100%; + } + } + } +} diff --git a/app/assets/stylesheets/framework/page-header.scss b/app/assets/stylesheets/framework/page-header.scss index 625bea96aaa..5f4211147f3 100644 --- a/app/assets/stylesheets/framework/page-header.scss +++ b/app/assets/stylesheets/framework/page-header.scss @@ -14,7 +14,7 @@ .header-action-buttons { i { - color: $gl-gray-light; + color: $gl-text-color-secondary; font-size: 13px; margin-right: 3px; } @@ -42,14 +42,10 @@ .commit-committer-link, .commit-author-link { - color: $gl-gray; + color: $gl-text-color; font-weight: bold; } - .fa-clipboard { - color: $dropdown-title-btn-color; - } - .commit-info { &.branches { margin-left: 8px; diff --git a/app/assets/stylesheets/framework/pagination.scss b/app/assets/stylesheets/framework/pagination.scss index b37c1d0d670..c3ec9db0f07 100644 --- a/app/assets/stylesheets/framework/pagination.scss +++ b/app/assets/stylesheets/framework/pagination.scss @@ -6,8 +6,22 @@ .pagination { padding: 0; + + a { + cursor: pointer; + } + + .separator, + .separator:hover { + a { + cursor: default; + background-color: $gray-light; + padding: $gl-vert-padding; + } + } } + .gap, .gap:hover { background-color: $gray-light; diff --git a/app/assets/stylesheets/framework/panels.scss b/app/assets/stylesheets/framework/panels.scss index 9d8d08dff88..efe93724013 100644 --- a/app/assets/stylesheets/framework/panels.scss +++ b/app/assets/stylesheets/framework/panels.scss @@ -48,3 +48,11 @@ line-height: inherit; } } + +.panel-default { + .table-list-row:last-child { + .table-list-cell { + border-bottom: 0; + } + } +} diff --git a/app/assets/stylesheets/framework/progress.scss b/app/assets/stylesheets/framework/progress.scss deleted file mode 100644 index e9800bd24b5..00000000000 --- a/app/assets/stylesheets/framework/progress.scss +++ /dev/null @@ -1,5 +0,0 @@ -html.turbolinks-progress-bar::before { - background-color: $progress-color!important; - height: 2px!important; - box-shadow: 0 0 10px $progress-color, 0 0 5px $progress-color; -} diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss index 46a06cd7eab..f0b03710c79 100644 --- a/app/assets/stylesheets/framework/sidebar.scss +++ b/app/assets/stylesheets/framework/sidebar.scss @@ -101,6 +101,17 @@ padding: 0 8px; border-radius: 6px; } + + .about-gitlab { + padding: 7px $gl-sidebar-padding; + font-size: $gl-font-size; + line-height: 24px; + display: block; + text-decoration: none; + font-weight: normal; + position: absolute; + bottom: 10px; + } } .sidebar-action-buttons { @@ -172,7 +183,9 @@ &.right-sidebar-expanded { .line-resolve-all-container { - display: none; + @media (min-width: $sidebar-breakpoint) { + display: none; + } } } } @@ -223,9 +236,13 @@ header.header-sidebar-pinned { @media (min-width: $screen-md-min) { padding-right: $gutter_width; - .merge-request-tabs-holder.affix { + &:not(.with-overlay) .merge-request-tabs-holder.affix { right: $gutter_width; } + + &.with-overlay .merge-request-tabs-holder.affix { + right: $sidebar_collapsed_width; + } } &.with-overlay { diff --git a/app/assets/stylesheets/framework/timeline.scss b/app/assets/stylesheets/framework/timeline.scss index 6078505807e..ff185cd8767 100644 --- a/app/assets/stylesheets/framework/timeline.scss +++ b/app/assets/stylesheets/framework/timeline.scss @@ -7,7 +7,7 @@ .timeline-entry { padding: $gl-padding $gl-btn-padding 11px; border-color: $white-normal; - color: $gl-gray; + color: $gl-text-color; border-bottom: 1px solid $border-white-light; &:target { @@ -32,7 +32,7 @@ .system-note { .note-text { - color: $gl-gray !important; + color: $gl-text-color !important; } } diff --git a/app/assets/stylesheets/framework/tw_bootstrap.scss b/app/assets/stylesheets/framework/tw_bootstrap.scss index 718dbbfea27..ea2d26dd5a0 100644 --- a/app/assets/stylesheets/framework/tw_bootstrap.scss +++ b/app/assets/stylesheets/framework/tw_bootstrap.scss @@ -33,7 +33,7 @@ @import "bootstrap/labels"; @import "bootstrap/badges"; @import "bootstrap/alerts"; -// @import "bootstrap/progress-bars"; +@import "bootstrap/progress-bars"; @import "bootstrap/list-group"; @import "bootstrap/wells"; @import "bootstrap/close"; @@ -91,14 +91,14 @@ // Labels .label { padding: 4px 5px; - font-size: 13px; + font-size: 12px; font-style: normal; font-weight: normal; display: inline-block; &.label-gray { background-color: $label-gray-bg; - color: $gl-gray; + color: $gl-text-color; text-shadow: none; } @@ -162,6 +162,10 @@ } } } + + &.panel-without-border { + border: 0; + } } .panel-succes .panel-heading, diff --git a/app/assets/stylesheets/framework/tw_bootstrap_variables.scss b/app/assets/stylesheets/framework/tw_bootstrap_variables.scss index 876adf7f712..0fc89d5976a 100644 --- a/app/assets/stylesheets/framework/tw_bootstrap_variables.scss +++ b/app/assets/stylesheets/framework/tw_bootstrap_variables.scss @@ -65,11 +65,11 @@ $legend-color: $text-color; // //## -$pagination-color: $gl-gray; +$pagination-color: $gl-text-color; $pagination-bg: $white-light; $pagination-border: $border-color; -$pagination-hover-color: $gl-gray; +$pagination-hover-color: $gl-text-color; $pagination-hover-bg: $row-hover; $pagination-hover-border: $border-color; @@ -121,6 +121,9 @@ $panel-default-heading-bg: $gray-light; $panel-footer-bg: $gray-light; $panel-inner-border: $border-color; +$badge-bg: $badge-bg; +$badge-color: $badge-color; + //== Wells // //## @@ -154,7 +157,7 @@ $nav-link-padding: 13px $gl-padding; // //## $pre-bg: $gray-light !default; -$pre-color: $gl-gray !default; +$pre-color: $gl-text-color !default; $pre-border-color: $border-color; $table-bg-accent: $gray-light; diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss index d906d26bba9..54958973f15 100644 --- a/app/assets/stylesheets/framework/typography.scss +++ b/app/assets/stylesheets/framework/typography.scss @@ -1,5 +1,5 @@ @mixin md-typography { - color: $md-text-color; + color: $gl-text-color; word-wrap: break-word; a { @@ -10,7 +10,7 @@ max-width: 100%; } - *:first-child { + *:first-child:not(.katex-display) { margin-top: 0; } @@ -50,14 +50,14 @@ margin: 16px 0 10px; padding: 0 0 0.3em; border-bottom: 1px solid $white-dark; - color: $gl-gray-dark; + color: $gl-text-color; } h2 { font-size: 1.5em; font-weight: 600; margin: 16px 0 10px; - color: $gl-gray-dark; + color: $gl-text-color; } h3 { @@ -100,7 +100,7 @@ } p { - color: $gl-text-color-dark; + color: $gl-text-color; margin: 6px 0 0; } @@ -108,7 +108,7 @@ @extend .table; @extend .table-bordered; margin: 12px 0; - color: $gl-text-color-dark; + color: $gl-text-color; th { background: $label-gray-bg; @@ -230,7 +230,7 @@ h3, h4, h5, h6 { - color: $gl-title-color; + color: $gl-text-color; font-weight: 600; } @@ -292,7 +292,7 @@ h2, h3, h4 { small { - color: $gl-gray; + color: $gl-text-color; } } diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index 460c5d995be..7809d4866f1 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -5,7 +5,7 @@ $sidebar_collapsed_width: 62px; $sidebar_width: 220px; $gutter_collapsed_width: 62px; $gutter_width: 290px; -$gutter_inner_width: 258px; +$gutter_inner_width: 250px; $sidebar-transition-duration: .15s; $sidebar-breakpoint: 1024px; @@ -56,6 +56,7 @@ $black-transparent: rgba(0, 0, 0, 0.3); $border-white-light: darken($white-light, $darken-border-factor); $border-white-normal: darken($white-normal, $darken-border-factor); +$border-gray-light: darken($gray-light, $darken-border-factor); $border-gray-normal: darken($gray-normal, $darken-border-factor); $border-gray-dark: darken($white-normal, $darken-border-factor); @@ -85,6 +86,7 @@ $warning-message-border: #f0e2bb; */ $border-color: #e5e5e5; $focus-border-color: #3aabf0; +$sidebar-collapsed-icon-color: #999; $well-expand-item: #e8f2f7; $well-inner-border: #eef0f2; $well-light-border: #f1f1f1; @@ -94,29 +96,26 @@ $well-light-text-color: #5b6169; * Text */ $gl-font-size: 14px; -$gl-title-color: #333; -$gl-text-color: #5c5c5c; -$gl-text-color-dark: #5c5d5e; -$gl-text-color-light: #8c8c8c; +$gl-text-color: rgba(0, 0, 0, .85); +$gl-text-color-secondary: rgba(0, 0, 0, .55); +$gl-text-color-disabled: rgba(0, 0, 0, .35); $gl-text-green: #4a2; $gl-text-red: #d12f19; $gl-text-orange: #d90; $gl-link-color: #3777b0; -$gl-diff-text-color: #555; -$gl-dark-link-color: #333; -$gl-gray-light: #8f8f8f; $gl-grayish-blue: #7f8fa4; $gl-gray: $gl-text-color; $gl-gray-dark: #313236; $gl-header-color: #4c4e54; +$gl-header-nav-hover-color: #434343; /* * Lists */ $list-font-size: $gl-font-size; -$list-title-color: $gl-title-color; +$list-title-color: $gl-text-color; $list-text-color: $gl-text-color; -$list-text-disabled-color: #888; +$list-text-disabled-color: $gl-text-color-disabled; $list-border-light: #eee; $list-border: rgba(0, 0, 0, 0.05); $list-text-height: 42px; @@ -127,15 +126,14 @@ $list-warning-row-color: #8a6d3b; /* * Markdown */ -$md-text-color: $gl-text-color; $md-link-color: $gl-link-color; $md-area-border: #ddd; /* * Code */ -$code_font_size: 13px; -$code_line_height: 1.5; +$code_font_size: 12px; +$code_line_height: 1.6; /* * Padding @@ -168,9 +166,7 @@ $btn-side-margin: 10px; $btn-sm-side-margin: 7px; $btn-xs-side-margin: 5px; $issue-status-expired: #cea61b; -$issuable-sidebar-color: #999; -$issuable-avatar-hover-border: #999; -$issuable-clipboard-color: #999; +$issuable-sidebar-color: $gl-text-color-secondary; $show-aside-bg: #eee; $show-aside-color: #777; $show-aside-shadow: #ddd; @@ -182,6 +178,9 @@ $count-arrow-border: #dce0e5; $save-project-loader-color: #555; $divergence-graph-bar-bg: #ccc; $divergence-graph-separator-bg: #ccc; +$general-hover-transition-duration: 100ms; +$general-hover-transition-curve: linear; + /* * Common component specific colors @@ -274,10 +273,22 @@ $dropdown-toggle-active-border-color: darken($border-color, 14%); /* +* Filtered Search +*/ +$dropdown-hover-color: #3b86ff; + +/* * Buttons */ $btn-active-gray: #ececec; $btn-active-gray-light: e4e7ed; +$btn-white-active: #848484; + +/* +* Badges +*/ +$badge-bg: #eee; +$badge-color: $gl-text-color-secondary; /* * Award emoji @@ -296,8 +307,8 @@ $location-icon-color: #e7e9ed; /* * Notes */ -$notes-light-color: #8e8e8e; -$notes-role-color: #8e8e8e; +$notes-light-color: $gl-text-color-secondary; +$notes-role-color: $gl-text-color-secondary; $note-disabled-comment-color: #b2b2b2; $note-targe3-outside: #fffff0; $note-targe3-inside: #ffffd3; @@ -322,7 +333,7 @@ $calendar-user-contrib-text: #959494; $cycle-analytics-box-padding: 30px; $cycle-analytics-box-text-color: #8c8c8c; $cycle-analytics-big-font: 19px; -$cycle-analytics-dark-text: $gl-title-color; +$cycle-analytics-dark-text: $gl-text-color; $cycle-analytics-light-gray: #bfbfbf; $cycle-analytics-dismiss-icon-color: #b2b2b2; @@ -368,14 +379,13 @@ $callout-success-color: #3c763d; /* * Commit Page */ -$commit-committer-color: #999; $commit-max-width-marker-color: rgba(0, 0, 0, 0.0); $commit-message-text-area-bg: rgba(0, 0, 0, 0.0); /* * Common */ -$common-gray: $gl-gray; +$common-gray: $gl-text-color; $common-gray-light: #bbb; $common-gray-dark: #444; $common-red: $gl-text-red; @@ -426,6 +436,7 @@ $help-shortcut-header-color: #333; */ $issues-today-bg: #f3fff2; $issues-today-border: #e1e8d5; +$compare-display-color: #888; /* * jQuery UI @@ -481,9 +492,9 @@ $project-network-controls-color: #888; */ $runner-state-shared-bg: #32b186; $runner-state-specific-bg: #3498db; -$runner-status-online-color: green; -$runner-status-offline-color: gray; -$runner-status-paused-color: red; +$runner-status-online-color: $green-normal; +$runner-status-offline-color: $gray-darkest; +$runner-status-paused-color: $red-normal; /* Stat Graph @@ -530,3 +541,4 @@ Pipeline Graph */ $stage-hover-bg: #eaf3fc; $stage-hover-border: #d1e7fc; +$action-icon-color: #d6d6d6;
\ No newline at end of file diff --git a/app/assets/stylesheets/framework/wells.scss b/app/assets/stylesheets/framework/wells.scss index f9c850ebdc8..32eb750180f 100644 --- a/app/assets/stylesheets/framework/wells.scss +++ b/app/assets/stylesheets/framework/wells.scss @@ -1,6 +1,6 @@ .info-well { background: $gray-light; - color: $gl-gray; + color: $gl-text-color; border: 1px solid $border-color; border-radius: $border-radius-default; diff --git a/app/assets/stylesheets/framework/zen.scss b/app/assets/stylesheets/framework/zen.scss index 84b639fabf5..97ade638db6 100644 --- a/app/assets/stylesheets/framework/zen.scss +++ b/app/assets/stylesheets/framework/zen.scss @@ -40,7 +40,7 @@ } .zen-control-full { - color: $gl-gray-light; + color: $gl-text-color-secondary; &:hover { color: $gl-link-color; diff --git a/app/assets/stylesheets/highlight/dark.scss b/app/assets/stylesheets/highlight/dark.scss index cb923166b25..6f2e746d4b0 100644 --- a/app/assets/stylesheets/highlight/dark.scss +++ b/app/assets/stylesheets/highlight/dark.scss @@ -13,6 +13,8 @@ $dark-main-bg: #1d1f21; $dark-main-color: #1d1f21; $dark-line-color: #c5c8c6; $dark-line-num-color: rgba(255, 255, 255, 0.3); +$dark-line-num-color-new: #627165; +$dark-line-num-color-old: #806565; $dark-diff-not-empty-bg: #557; $dark-highlight-bg: #ffe792; $dark-highlight-color: $black; @@ -89,7 +91,6 @@ $dark-il: #de935f; .diff-line-num, .diff-line-num a { - color: $dark-main-color; color: $dark-line-num-color; } @@ -121,11 +122,21 @@ $dark-il: #de935f; .diff-line-num.new, .line_content.new { @include diff_background($dark-new-bg, $dark-new-idiff, $dark-border); + + &::before, + a { + color: $dark-line-num-color-new; + } } .diff-line-num.old, .line_content.old { @include diff_background($dark-old-bg, $dark-old-idiff, $dark-border); + + &::before, + a { + color: $dark-line-num-color-old; + } } .line_content.match { diff --git a/app/assets/stylesheets/highlight/monokai.scss b/app/assets/stylesheets/highlight/monokai.scss index d8510baad8a..2144a5f7466 100644 --- a/app/assets/stylesheets/highlight/monokai.scss +++ b/app/assets/stylesheets/highlight/monokai.scss @@ -7,6 +7,8 @@ $monokai-bg: #272822; $monokai-border: #555; $monokai-text-color: #f8f8f2; $monokai-line-num-color: rgba(255, 255, 255, 0.3); +$monokai-line-num-color-new: #707565; +$monokai-line-num-color-old: #7e736f; $monokai-line-empty-bg: #49483e; $monokai-line-empty-border: darken($monokai-line-empty-bg, 15%); $monokai-diff-border: #808080; @@ -120,11 +122,21 @@ $monokai-gi: #a6e22e; .diff-line-num.new, .line_content.new { @include diff_background($monokai-new-bg, $monokai-new-idiff, $monokai-diff-border); + + &::before, + a { + color: $monokai-line-num-color-new; + } } .diff-line-num.old, .line_content.old { @include diff_background($monokai-old-bg, $monokai-old-idiff, $monokai-diff-border); + + &::before, + a { + color: $monokai-line-num-color-old; + } } .line_content.match { diff --git a/app/assets/stylesheets/highlight/solarized_dark.scss b/app/assets/stylesheets/highlight/solarized_dark.scss index 874aecb5e16..2cb1d18f12f 100644 --- a/app/assets/stylesheets/highlight/solarized_dark.scss +++ b/app/assets/stylesheets/highlight/solarized_dark.scss @@ -13,6 +13,8 @@ $solarized-dark-pre-color: #93a1a1; $solarized-dark-pre-border: #113b46; $solarized-dark-line-bg: #002b36; $solarized-dark-line-color: rgba(255, 255, 255, 0.3); +$solarized-dark-line-color-new: #5a766c; +$solarized-dark-line-color-old: #7a6c71; $solarized-dark-highlight: #094554; $solarized-dark-hll-bg: #174652; $solarized-dark-c: #586e75; @@ -124,11 +126,21 @@ $solarized-dark-il: #2aa198; .diff-line-num.new, .line_content.new { @include diff_background($solarized-dark-new-bg, $solarized-dark-new-idiff, $solarized-dark-border); + + &::before, + a { + color: $solarized-dark-line-color-new; + } } .diff-line-num.old, .line_content.old { @include diff_background($solarized-dark-old-bg, $solarized-dark-old-idiff, $solarized-dark-border); + + &::before, + a { + color: $solarized-dark-line-color-old; + } } .line_content.match { diff --git a/app/assets/stylesheets/highlight/solarized_light.scss b/app/assets/stylesheets/highlight/solarized_light.scss index 499a1c108b8..b72c4326730 100644 --- a/app/assets/stylesheets/highlight/solarized_light.scss +++ b/app/assets/stylesheets/highlight/solarized_light.scss @@ -13,6 +13,9 @@ $solarized-light-pre-bg: #002b36; $solarized-light-pre-bg: #fdf6e3; $solarized-light-pre-color: #586e75; $solarized-light-line-bg: #fdf6e3; +$solarized-light-line-color: rgba(0, 0, 0, 0.3); +$solarized-light-line-color-new: #a1a080; +$solarized-light-line-color-old: #ad9186; $solarized-light-highlight: #eee8d5; $solarized-light-hll-bg: #ddd8c5; $solarized-light-c: #93a1a1; @@ -98,7 +101,7 @@ $solarized-light-il: #2aa198; .diff-line-num, .diff-line-num a { - color: $black-transparent; + color: $solarized-light-line-color; } // Code itself @@ -130,11 +133,21 @@ $solarized-light-il: #2aa198; .line_content.new { @include diff_background($solarized-light-new-bg, $solarized-light-new-idiff, $solarized-light-border); + + &::before, + a { + color: $solarized-light-line-color-new; + } } .diff-line-num.old, .line_content.old { @include diff_background($solarized-light-old-bg, $solarized-light-old-idiff, $solarized-light-border); + + &::before, + a { + color: $solarized-light-line-color-old; + } } .line_content.match { diff --git a/app/assets/stylesheets/highlight/white.scss b/app/assets/stylesheets/highlight/white.scss index 54a5664a874..398fbfd3b18 100644 --- a/app/assets/stylesheets/highlight/white.scss +++ b/app/assets/stylesheets/highlight/white.scss @@ -3,7 +3,7 @@ /* * White Syntax Colors */ -$white-code-color: #333; +$white-code-color: $gl-text-color; $white-highlight: #fafe3d; $white-pre-hll-bg: #f8eec7; $white-hll-bg: #f8f8f8; @@ -108,11 +108,19 @@ $white-gc-bg: #eaf2f5; &.old { background-color: $line-number-old; border-color: $line-removed-dark; + + a { + color: scale-color($line-number-old,$red: -30%, $green: -30%, $blue: -30%); + } } &.new { background-color: $line-number-new; border-color: $line-added-dark; + + a { + color: scale-color($line-number-new,$red: -30%, $green: -30%, $blue: -30%); + } } &.hll:not(.empty-cell) { @@ -125,6 +133,10 @@ $white-gc-bg: #eaf2f5; &.old { background-color: $line-removed; + &::before { + color: scale-color($line-number-old,$red: -30%, $green: -30%, $blue: -30%); + } + span.idiff { background-color: $line-removed-dark; } @@ -133,6 +145,10 @@ $white-gc-bg: #eaf2f5; &.new { background-color: $line-added; + &::before { + color: scale-color($line-number-new,$red: -30%, $green: -30%, $blue: -30%); + } + span.idiff { background-color: $line-added-dark; } diff --git a/app/assets/stylesheets/notify.scss b/app/assets/stylesheets/notify.scss index ddc382362f7..a81e5eb5ebf 100644 --- a/app/assets/stylesheets/notify.scss +++ b/app/assets/stylesheets/notify.scss @@ -18,15 +18,3 @@ p.details { pre.commit-message { white-space: pre-wrap; } - -.file-stats > a { - text-decoration: none; - - > .new-file { - color: $notify-new-file; - } - - > .deleted-file { - color: $notify-deleted-file; - } -} diff --git a/app/assets/stylesheets/pages/boards.scss b/app/assets/stylesheets/pages/boards.scss index c735f104c20..b362cc758cc 100644 --- a/app/assets/stylesheets/pages/boards.scss +++ b/app/assets/stylesheets/pages/boards.scss @@ -31,7 +31,7 @@ .dropdown-content { max-height: 150px; - } + } } .issue-board-dropdown-content { @@ -74,6 +74,7 @@ height: 475px; // Needed for PhantomJS height: calc(100vh - 220px); min-height: 475px; + transition: width .2s; &.is-compact { width: calc(100% - 290px); @@ -109,6 +110,12 @@ &.has-border { border-top: 3px solid; + margin-top: -1px; + margin-right: -1px; + margin-left: -1px; + padding-top: 1px; + padding-right: 1px; + padding-left: 1px; .board-title { padding-top: ($gl-padding - 3px); @@ -243,7 +250,7 @@ } .issue-boards-search { - width: 290px; + width: 395px; .form-control { display: inline-block; @@ -253,7 +260,7 @@ .board-list-count { padding: 10px 0; - color: $gl-gray-light; + color: $gl-text-color-secondary; font-size: 13px; > .fa { @@ -332,3 +339,186 @@ } } } + +.right-sidebar.right-sidebar-expanded { + &.boards-sidebar-slide-enter-active, + &.boards-sidebar-slide-leave-active { + transition: width .2s, + padding .2s; + } + + &.boards-sidebar-slide-enter, + &.boards-sidebar-slide-leave-active { + width: 0; + padding-left: 0; + padding-right: 0; + } +} + +.add-issues-modal { + display: -webkit-flex; + display: flex; + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + background-color: rgba($black, .3); + z-index: 9999; +} + +.add-issues-container { + display: -webkit-flex; + display: flex; + -webkit-flex-direction: column; + flex-direction: column; + width: 90vw; + height: 85vh; + max-width: 1100px; + min-height: 500px; + margin: auto; + padding: 25px 15px 0; + background-color: $white-light; + border-radius: $border-radius-default; + box-shadow: 0 2px 12px rgba($black, .5); + + .empty-state { + display: -webkit-flex; + display: flex; + -webkit-flex: 1; + flex: 1; + margin-top: 0; + + &.add-issues-empty-state-filter { + -webkit-flex-direction: column; + flex-direction: column; + -webkit-justify-content: center; + justify-content: center; + } + + > .row { + width: 100%; + margin: auto 0; + } + + .svg-content { + margin-top: -40px; + } + } +} + +.add-issues-header { + margin: -25px -15px -5px; + border-top: 0; + border-bottom: 1px solid $border-color; + border-top-right-radius: $border-radius-default; + border-top-left-radius: $border-radius-default; + + > h2 { + margin: 0; + font-size: 18px; + } +} + +.add-issues-search { + display: -webkit-flex; + display: flex; + + .form-control { + margin-left: auto; + + @media (min-width: $screen-sm-min) { + max-width: 200px; + } + } +} + +.add-issues-list-column { + width: 100%; + + @media (min-width: $screen-sm-min) { + width: 50%; + } + + @media (min-width: $screen-md-min) { + width: (100% / 3); + } +} + +.add-issues-list { + display: -webkit-flex; + display: flex; + -webkit-flex: 1; + flex: 1; + padding-top: 3px; + margin-left: -$gl-vert-padding; + margin-right: -$gl-vert-padding; + overflow-y: scroll; + + .card-parent { + padding: 0 5px 5px; + } + + .card { + border: 1px solid $border-gray-dark; + box-shadow: 0 1px 2px rgba($issue-boards-card-shadow, .3); + cursor: pointer; + } +} + +.add-issues-list-loading { + -webkit-align-self: center; + align-self: center; + width: 100%; + padding-left: $gl-vert-padding; + padding-right: $gl-vert-padding; + font-size: 35px; +} + +.add-issues-footer { + margin: auto -15px 0; + padding-left: 15px; + padding-right: 15px; + border-bottom-right-radius: $border-radius-default; + border-bottom-left-radius: $border-radius-default; +} + +.add-issues-footer-to-list { + padding-left: $gl-vert-padding; + padding-right: $gl-vert-padding; + line-height: 34px; +} + +.issue-card-selected { + position: absolute; + right: -3px; + top: -3px; + width: 17px; + background-color: $blue-light; + color: $white-light; + border: 1px solid $border-blue-light; + font-size: 9px; + line-height: 15px; + border-radius: 50%; +} + +.modal-filters { + display: flex; + + > .dropdown { + display: none; + margin-right: 10px; + + @media (min-width: $screen-sm-min) { + display: block; + } + } + + .dropdown-menu-toggle { + width: 100px; + + @media (min-width: $screen-md-min) { + width: 140px; + } + } +} diff --git a/app/assets/stylesheets/pages/branches.scss b/app/assets/stylesheets/pages/branches.scss new file mode 100644 index 00000000000..3e2fa8ca88d --- /dev/null +++ b/app/assets/stylesheets/pages/branches.scss @@ -0,0 +1,55 @@ +.divergence-graph { + padding: 12px 12px 0 0; + float: right; + + .graph-side { + position: relative; + width: 80px; + height: 22px; + padding: 5px 0 13px; + float: left; + + .bar { + position: absolute; + height: 4px; + background-color: $divergence-graph-bar-bg; + } + + .bar-behind { + right: 0; + border-radius: 3px 0 0 3px; + } + + .bar-ahead { + left: 0; + border-radius: 0 3px 3px 0; + } + + .count { + padding-top: 6px; + padding-bottom: 0; + font-size: 12px; + color: $gl-text-color; + display: block; + } + + .count-behind { + padding-right: 4px; + text-align: right; + } + + .count-ahead { + padding-left: 4px; + text-align: left; + } + } + + .graph-separator { + position: relative; + width: 1px; + height: 18px; + margin: 5px 0 0; + float: left; + background-color: $divergence-graph-separator-bg; + } +} diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss index 66f7e7f97c8..fd101d43b5b 100644 --- a/app/assets/stylesheets/pages/builds.scss +++ b/app/assets/stylesheets/pages/builds.scss @@ -1,3 +1,34 @@ +@keyframes fade-out-status { + 0%, 50% { opacity: 1; } + 100% { opacity: 0; } +} + +@keyframes blinking-dots { + 0% { + background-color: rgba($white-light, 1); + box-shadow: 12px 0 0 0 rgba($white-light,0.2), + 24px 0 0 0 rgba($white-light,0.2); + } + + 25% { + background-color: rgba($white-light, 0.4); + box-shadow: 12px 0 0 0 rgba($white-light,2), + 24px 0 0 0 rgba($white-light,0.2); + } + + 75% { + background-color: rgba($white-light, 0.4); + box-shadow: 12px 0 0 0 rgba($white-light,0.2), + 24px 0 0 0 rgba($white-light,1); + } + + 100% { + background-color: rgba($white-light, 1); + box-shadow: 12px 0 0 0 rgba($white-light,0.2), + 24px 0 0 0 rgba($white-light,0.2); + } +} + .build-page { pre.trace { background: $builds-trace-bg; @@ -14,45 +45,99 @@ } } - .scroll-controls { - .scroll-step { - width: 31px; - margin: 0 0 0 auto; + .environment-information { + background-color: $gray-light; + border: 1px solid $border-color; + padding: 12px $gl-padding; + border-radius: $border-radius-default; + + svg { + position: relative; + top: 1px; + margin-right: 5px; + } + } +} + +.scroll-controls { + height: 100%; + + .scroll-step { + width: 31px; + margin: 0 0 0 auto; + } + + .scroll-link, + .autoscroll-container { + right: 25px; + z-index: 1; + } + + .scroll-link { + position: fixed; + display: block; + margin-bottom: 10px; + + &.scroll-top .gitlab-icon-scroll-up-hover, + &.scroll-top:hover .gitlab-icon-scroll-up, + &.scroll-bottom .gitlab-icon-scroll-down-hover, + &.scroll-bottom:hover .gitlab-icon-scroll-down { + display: none; } - &.affix-bottom { - position: absolute; - right: 25px; + &.scroll-top:hover .gitlab-icon-scroll-up-hover, + &.scroll-bottom:hover .gitlab-icon-scroll-down-hover { + display: inline-block; } - &.affix { - right: 25px; - bottom: 15px; - z-index: 1; + &.scroll-top { + top: 110px; } - &.sidebar-expanded { - right: #{$gutter_width + ($gl-padding * 2)}; + &.scroll-bottom { + bottom: -2px; } + } - a { - display: block; - margin-bottom: 10px; + .autoscroll-container { + position: absolute; + } + + &.sidebar-expanded { + + .scroll-link, + .autoscroll-container { + right: ($gutter_width + ($gl-padding * 2)); } } +} - .environment-information { - background-color: $gray-light; - border: 1px solid $border-color; - padding: 12px $gl-padding; - border-radius: $border-radius-default; +.status-message { + display: inline-block; + color: $white-light; - svg { - position: relative; - top: 1px; - margin-right: 5px; + .status-icon { + display: inline-block; + width: 16px; + height: 33px; + } + + .status-text { + float: left; + opacity: 0; + margin-right: 10px; + font-weight: normal; + line-height: 1.8; + transition: opacity 1s ease-out; + + &.animate { + animation: fade-out-status 2s ease; } } + + &:hover .status-text { + opacity: 1; + } } .build-header { @@ -75,7 +160,7 @@ flex: 1; a { - color: $gl-gray; + color: $gl-text-color; &:hover { color: $gl-link-color; @@ -109,6 +194,15 @@ .bash { display: block; } + + .build-loader-animation { + position: relative; + width: 6px; + height: 6px; + margin: auto auto 12px 2px; + border-radius: 50%; + animation: blinking-dots 1s linear infinite; + } } .right-sidebar.build-sidebar { @@ -248,6 +342,12 @@ } } +.build-sidebar { + .container-fluid.container-limited { + max-width: 100%; + } +} + .build-detail-row { margin-bottom: 5px; @@ -257,7 +357,7 @@ } .build-light-text { - color: $gl-gray-light; + color: $gl-text-color-secondary; } .build-gutter-toggle { diff --git a/app/assets/stylesheets/pages/ci_projects.scss b/app/assets/stylesheets/pages/ci_projects.scss index d1cd1e5d848..90643832390 100644 --- a/app/assets/stylesheets/pages/ci_projects.scss +++ b/app/assets/stylesheets/pages/ci_projects.scss @@ -18,7 +18,7 @@ } td { - color: $gl-gray; + color: $gl-text-color; vertical-align: middle !important; a { diff --git a/app/assets/stylesheets/pages/commit.scss b/app/assets/stylesheets/pages/commit.scss deleted file mode 100644 index bf656d0e28e..00000000000 --- a/app/assets/stylesheets/pages/commit.scss +++ /dev/null @@ -1,132 +0,0 @@ -.commit-title { - display: block; -} - -.commit-author, -.commit-committer { - display: block; - color: $commit-committer-color; - font-weight: normal; - font-style: italic; -} - -.commit-author strong, -.commit-committer strong { - font-weight: bold; - font-style: normal; -} - -.commit-description { - background: none; - border: none; - margin: 0; - padding: 0; - margin-top: 10px; - word-break: normal; - white-space: pre-wrap; -} - -.js-details-expand { - &:hover { - text-decoration: none; - } -} - -.ci-status-link { - svg { - overflow: visible; - } -} - -.commit-box { - border-top: 1px solid $border-color; - padding: $gl-padding 0; - - .commit-title { - margin: 0; - font-size: 23px; - color: $gl-gray-dark; - } - - .commit-description { - margin-top: 15px; - } -} - -.commit-hash-full { - @media (max-width: $screen-sm-max) { - width: 80px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - display: inline-block; - vertical-align: bottom; - } -} - -.file-stats { - ul { - list-style: none; - margin: 0; - padding: 10px 0; - - li { - padding: 3px 0; - line-height: 20px; - } - } - - .new-file { - a { - color: $gl-text-green; - } - } - - .renamed-file { - a { - color: $gl-text-orange; - } - } - - .deleted-file { - a { - color: $gl-text-red; - } - } - - .edit-file { - a { - color: $gl-text-color; - } - } -} - -/* - * Commit message textarea for web editor and - * custom merge request message - */ -.commit-message-container { - background-color: $body-bg; - position: relative; - font-family: $monospace_font; - $left: 12px; - overflow: hidden; // See https://gitlab.com/gitlab-org/gitlab-ce/issues/13987 - .max-width-marker { - width: 72ch; - color: $commit-max-width-marker-color; - font-family: inherit; - left: $left; - height: 100%; - border-right: 1px solid mix($input-border, $white-light); - position: absolute; - z-index: 1; - } - - > textarea { - background-color: $commit-message-text-area-bg; - font-family: inherit; - padding-left: $left; - position: relative; - z-index: 2; - } -} diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss index e76e1a73b25..c3d45d708c1 100644 --- a/app/assets/stylesheets/pages/commits.scss +++ b/app/assets/stylesheets/pages/commits.scss @@ -1,6 +1,75 @@ +.commit-description { + background: none; + border: none; + padding: 0; + margin-top: 10px; + word-break: normal; + white-space: pre-wrap; +} + +.js-details-expand { + &:hover { + text-decoration: none; + } +} + +.commit-box { + border-top: 1px solid $border-color; + padding: $gl-padding 0; + + .commit-title { + margin: 0; + color: $gl-text-color; + } + + .commit-description { + margin-top: 15px; + } +} + +.commit-hash-full { + @media (max-width: $screen-sm-max) { + width: 80px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + display: inline-block; + vertical-align: bottom; + } +} + +/* + * Commit message textarea for web editor and + * custom merge request message + */ +.commit-message-container { + background-color: $body-bg; + position: relative; + font-family: $monospace_font; + $left: 12px; + overflow: hidden; // See https://gitlab.com/gitlab-org/gitlab-ce/issues/13987 + .max-width-marker { + width: 72ch; + color: $commit-max-width-marker-color; + font-family: inherit; + left: $left; + height: 100%; + border-right: 1px solid mix($input-border, $white-light); + position: absolute; + z-index: 1; + } + + textarea { + background-color: $commit-message-text-area-bg; + font-family: inherit; + padding-left: $left; + position: relative; + z-index: 2; + } +} + + .commits-compare-switch { - @include btn-default; - @include btn-white; float: left; margin-right: 9px; } @@ -8,7 +77,6 @@ .commit-header { padding: 5px 10px; background-color: $gray-light; - border-top: 1px solid $gray-darker; border-bottom: 1px solid $gray-darker; font-size: 14px; @@ -18,8 +86,6 @@ } .commit-row-title { - line-height: 1.35; - .notes_count { float: right; margin-right: 10px; @@ -30,15 +96,14 @@ } .commit-row-message { - color: $gl-dark-link-color; + color: $gl-text-color; } - } .text-expander { display: inline-block; background: $gray-light; - color: $gl-gray-light; + color: $gl-text-color-secondary; padding: 0 5px; cursor: pointer; border: 1px solid $border-gray-dark; @@ -54,9 +119,8 @@ .commit-actions { @media (min-width: $screen-sm-min) { - float: right; - margin-left: $gl-padding; - margin-top: 2px; + width: 300px; + text-align: right; font-size: 0; } @@ -86,38 +150,15 @@ .commit, .generic_commit_status { - padding: 10px 0; - position: relative; - - @media (min-width: $screen-sm-min) { - padding-left: 46px; - } - - &:not(:last-child) { - border-bottom: 1px solid $gray-darker; - } a, button { - color: $gl-dark-link-color; + color: $gl-text-color; vertical-align: baseline; } - .avatar { - margin-left: -46px; - } - - .item-title { - display: inline-block; - - @media (min-width: $screen-sm-min) { - max-width: 70%; - } - } - .commit-row-description { font-size: 14px; - border-left: 1px solid $white-normal; padding: 10px 15px; margin: 10px 0; background: $gray-light; @@ -134,20 +175,7 @@ } a { - color: $gl-dark-link-color; - } - } - - .commit-row-info { - color: $gl-gray; - line-height: 1.35; - - a { - color: $gl-gray; - } - - .avatar { - margin-right: 8px; + color: $gl-text-color; } } @@ -164,7 +192,7 @@ } .branch-commit { - color: $gl-gray; + color: $gl-text-color; .commit-icon { text-align: center; @@ -174,7 +202,7 @@ height: 14px; width: 14px; vertical-align: middle; - fill: $gl-gray-light; + fill: $gl-text-color-secondary; } } @@ -183,62 +211,6 @@ } .commit-row-message { - color: $gl-gray; - } -} - -.divergence-graph { - padding: 12px 12px 0 0; - float: right; - - .graph-side { - position: relative; - width: 80px; - height: 22px; - padding: 5px 0 13px; - float: left; - - .bar { - position: absolute; - height: 4px; - background-color: $divergence-graph-bar-bg; - } - - .bar-behind { - right: 0; - border-radius: 3px 0 0 3px; - } - - .bar-ahead { - left: 0; - border-radius: 0 3px 3px 0; - } - - .count { - padding-top: 6px; - padding-bottom: 0; - font-size: 12px; - color: $gl-title-color; - display: block; - } - - .count-behind { - padding-right: 4px; - text-align: right; - } - - .count-ahead { - padding-left: 4px; - text-align: left; - } - } - - .graph-separator { - position: relative; - width: 1px; - height: 18px; - margin: 5px 0 0; - float: left; - background-color: $divergence-graph-separator-bg; + color: $gl-text-color; } } diff --git a/app/assets/stylesheets/pages/cycle_analytics.scss b/app/assets/stylesheets/pages/cycle_analytics.scss index 5aef31724e1..cda069e6c0e 100644 --- a/app/assets/stylesheets/pages/cycle_analytics.scss +++ b/app/assets/stylesheets/pages/cycle_analytics.scss @@ -20,15 +20,19 @@ .fa { color: $cycle-analytics-light-gray; + + &:hover { + color: $gl-text-color; + } } .stage-header { - width: 28%; + width: 26%; padding-left: $gl-padding; } .median-header { - width: 12%; + width: 14%; } .event-header { @@ -111,14 +115,14 @@ line-height: 19px; font-size: 14px; font-weight: 600; - color: $gl-title-color; + color: $gl-text-color; } &.text { color: $layout-link-gray; &.value-col { - color: $gl-title-color; + color: $gl-text-color; } } } @@ -141,7 +145,7 @@ .dismiss-icon { position: absolute; - right: $cycle-analytics-dismiss-icon-color; + right: $cycle-analytics-box-padding; cursor: pointer; color: $cycle-analytics-dismiss-icon-color; } @@ -215,7 +219,6 @@ border-bottom: 1px solid transparent; border-right: 1px solid $border-color; background-color: $gray-light; - cursor: default; &.active { background-color: transparent; @@ -247,11 +250,11 @@ float: left; &.stage-name { - width: 70%; + width: 65%; } &.stage-median { - width: 30%; + width: 35%; } } @@ -261,7 +264,7 @@ .stage-empty, .not-available { - color: $gl-text-color-light; + color: $gl-text-color-secondary; } } } @@ -328,7 +331,7 @@ @include text-overflow(); a { - color: $gl-dark-link-color; + color: $gl-text-color; } } } @@ -356,7 +359,7 @@ .issue-link, .commit-author-link, .issue-author-link { - color: $gl-dark-link-color; + color: $gl-text-color; } // Custom CSS for components @@ -397,11 +400,11 @@ } .item-build-name { - color: $gl-title-color; + color: $gl-text-color; } .pipeline-id { - color: $gl-title-color; + color: $gl-text-color; padding: 0 3px 0 0; } @@ -424,7 +427,7 @@ } .fa { - color: $gl-text-color-light; + color: $gl-text-color-secondary; font-size: $code_font_size; } } @@ -436,7 +439,7 @@ width: 75%; margin: 0 auto; padding-top: 130px; - color: $gl-text-color-light; + color: $gl-text-color-secondary; h4 { color: $gl-text-color; diff --git a/app/assets/stylesheets/pages/deploy_keys.scss b/app/assets/stylesheets/pages/deploy_keys.scss new file mode 100644 index 00000000000..2fafe052106 --- /dev/null +++ b/app/assets/stylesheets/pages/deploy_keys.scss @@ -0,0 +1,13 @@ +.deploy-keys-list { + width: 100%; + overflow: auto; + + table { + border: 1px solid $table-border-color; + } +} + +.deploy-keys-title { + padding-bottom: 2px; + line-height: 2; +} diff --git a/app/assets/stylesheets/pages/detail_page.scss b/app/assets/stylesheets/pages/detail_page.scss index 9b28df1afc5..46fd19c93f9 100644 --- a/app/assets/stylesheets/pages/detail_page.scss +++ b/app/assets/stylesheets/pages/detail_page.scss @@ -1,15 +1,15 @@ .detail-page-header { padding: $gl-padding-top 0; border-bottom: 1px solid $border-color; - color: $gl-text-color-dark; + color: $gl-text-color; line-height: 34px; .author { - color: $gl-text-color-dark; + color: $gl-text-color; } .identifier { - color: $gl-text-color-dark; + color: $gl-text-color; } .issue_created_ago, @@ -22,7 +22,7 @@ .title { margin: 0 0 16px; font-size: 2em; - color: $gl-gray-dark; + color: $gl-text-color; padding: 0 0 0.3em; border-bottom: 1px solid $white-dark; } diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index f30795fd2c2..96ba7c40634 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -14,7 +14,7 @@ background: $gray-light; border-bottom: 1px solid $border-color; padding: 10px 16px; - color: $gl-diff-text-color; + color: $gl-text-color; z-index: 10; border-radius: 3px 3px 0 0; @@ -50,7 +50,7 @@ overflow: auto; overflow-y: hidden; background: $white-light; - color: $gl-title-color; + color: $gl-text-color; border-radius: 0 0 3px 3px; .unfold { @@ -380,7 +380,7 @@ } cursor: default; - color: $gl-title-color; + color: $gl-text-color; } &.disabled { diff --git a/app/assets/stylesheets/pages/environments.scss b/app/assets/stylesheets/pages/environments.scss index 3d60426de01..778ef01430e 100644 --- a/app/assets/stylesheets/pages/environments.scss +++ b/app/assets/stylesheets/pages/environments.scss @@ -72,25 +72,25 @@ .external-url, .dropdown-new { - color: $gl-gray-light; + color: $gl-text-color-secondary; } .dropdown-menu { .fa { margin-right: 6px; - color: $gl-gray-light; + color: $gl-text-color-secondary; } } .build-link, .branch-name { - color: $gl-dark-link-color; + color: $gl-text-color; } .stop-env-link, .external-url { - color: $gl-gray-light; + color: $gl-text-color-secondary; .stop-env-icon { font-size: 14px; @@ -101,7 +101,7 @@ .build-column { .build-link { - color: $gl-dark-link-color; + color: $gl-text-color; } .avatar { @@ -121,13 +121,6 @@ .folder-name { cursor: pointer; - - .badge { - font-weight: normal; - background-color: $gray-darker; - color: $gl-gray-light; - vertical-align: baseline; - } } } @@ -142,4 +135,4 @@ margin-right: 0; } } -} +}
\ No newline at end of file diff --git a/app/assets/stylesheets/pages/events.scss b/app/assets/stylesheets/pages/events.scss index 98925c2d0cb..b989d72ce1c 100644 --- a/app/assets/stylesheets/pages/events.scss +++ b/app/assets/stylesheets/pages/events.scss @@ -21,7 +21,7 @@ } a { - color: $gl-dark-link-color; + color: $gl-text-color; } .avatar { diff --git a/app/assets/stylesheets/pages/groups.scss b/app/assets/stylesheets/pages/groups.scss index 16bff5f1e03..d377526e655 100644 --- a/app/assets/stylesheets/pages/groups.scss +++ b/app/assets/stylesheets/pages/groups.scss @@ -13,7 +13,7 @@ .stats { float: right; line-height: $list-text-height; - color: $gl-gray; + color: $gl-text-color; span { margin-right: 15px; diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index 4fac0cfb0ba..9174976c4c6 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -1,9 +1,13 @@ // Limit MR description for side-by-side diff view +.fixed-width-container { + max-width: $limited-layout-width - ($gl-padding * 2); + margin-left: auto; + margin-right: auto; +} + .limit-container-width { .detail-page-header { - max-width: calc(#{$limited-layout-width} - (#{$gl-padding} * 2)); - margin-left: auto; - margin-right: auto; + @extend .fixed-width-container; } .issuable-details { @@ -11,9 +15,7 @@ .mr-source-target, .mr-state-widget, .merge-manually { - max-width: calc(#{$limited-layout-width} - (#{$gl-padding} * 2)); - margin-left: auto; - margin-right: auto; + @extend .fixed-width-container; } .merge-request-tabs-holder { @@ -24,23 +26,19 @@ border: 0; } } + } + } - .container-fluid { - padding-left: 0; - padding-right: 0; - max-width: calc(#{$limited-layout-width} - (#{$gl-padding} * 2)); - margin-left: auto; - margin-right: auto; - } + .merge-request-details { + .emoji-list-container { + @extend .fixed-width-container; } } .diffs { .mr-version-controls, .files-changed { - max-width: calc(#{$limited-layout-width} - (#{$gl-padding} * 2)); - margin-left: auto; - margin-right: auto; + @extend .fixed-width-container; } } } @@ -52,6 +50,12 @@ } } + .title { + padding: 0; + margin: 0; + border-bottom: none; + } + // Border around images in issue and MR descriptions. .description img:not(.emoji) { border: 1px solid $white-normal; @@ -144,10 +148,10 @@ } .edit-link { - color: $gl-gray; + color: $gl-text-color; - &:hover { - color: $md-link-color; + &:not([href]):hover { + color: rgba($avatar-border, .2); } } } @@ -180,7 +184,7 @@ } .btn-clipboard:hover { - color: $gl-gray; + color: $gl-text-color; } } @@ -189,7 +193,7 @@ top: $header-height; bottom: 0; right: 0; - z-index: 10; + z-index: 8; transition: width .3s; background: $gray-light; padding: 10px 20px; @@ -215,7 +219,7 @@ } .no-value { - color: $gl-gray-light; + color: $gl-text-color-secondary; } .sidebar-collapsed-icon { @@ -283,7 +287,7 @@ color: $issuable-sidebar-color; &:hover { - color: $gl-gray; + color: $gl-text-color; } span { @@ -296,16 +300,16 @@ } .avatar:hover { - border-color: $issuable-avatar-hover-border; + border-color: $issuable-sidebar-color; } .btn-clipboard { border: none; - color: $issuable-clipboard-color; + color: $issuable-sidebar-color; &:hover { background: transparent; - color: $gl-gray; + color: $gl-text-color; } } } @@ -324,6 +328,10 @@ &:hover { color: $md-link-color; text-decoration: none; + + .avatar { + border-color: rgba($avatar-border, .2); + } } } @@ -365,6 +373,10 @@ display: inline-block; padding: 5px; + &:nth-of-type(7n) { + padding-right: 0; + } + .author_link { display: block; } @@ -379,7 +391,7 @@ margin-left: 5px; a { - color: $gl-gray-light; + color: $gl-text-color-secondary; } } @@ -434,6 +446,7 @@ .issuable-meta { display: inline-block; line-height: 18px; + font-size: 14px; } .js-issuable-selector-wrap { @@ -460,3 +473,102 @@ } } } + +.time_tracker { + padding-bottom: 0; + border-bottom: 0; + + + .sidebar-collapsed-icon { + + > .stopwatch-svg { + display: inline-block; + } + + svg { + width: 16px; + height: 16px; + fill: $sidebar-collapsed-icon-color; + } + + &:hover svg { + fill: $gl-text-color; + } + } + + .help-button, + .close-help-button { + cursor: pointer; + } + + .compare-meter { + &.within_estimate { + .meter-fill { + background: $gl-primary; + } + } + + &.over_estimate { + .meter-fill { + background: $red-light; + } + + .time-remaining, + .compare-value.spent { + color: $red-light; + } + } + } + + .meter-container { + background: $border-gray-light; + border-radius: 3px; + + .meter-fill { + max-width: 100%; + height: 5px; + border-radius: 3px; + background: $gl-primary; + } + } + + .compare-display-container { + display: flex; + justify-content: space-between; + margin-top: 5px; + + .compare-display { + font-size: 13px; + color: $compare-display-color; + + .compare-value { + color: $gl-text-color; + } + } + } + + .time-tracking-help-state { + background: $white-light; + margin: 16px -20px 0; + padding: 16px 20px; + border-top: 1px solid $border-gray-light; + border-bottom: 1px solid $border-gray-light; + + a:hover { + color: $btn-white-active; + } + } + + .help-state-toggle-enter-active { + transition: all .8s ease; + } + + .help-state-toggle-leave-active { + transition: all .5s ease; + } + + .help-state-toggle-enter, + .help-state-toggle-leave-active { + opacity: 0; + } +} diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss index 8734a3b1598..1e605337f09 100644 --- a/app/assets/stylesheets/pages/issues.scss +++ b/app/assets/stylesheets/pages/issues.scss @@ -148,3 +148,7 @@ ul.related-merge-requests > li { border: 1px solid $border-gray-normal; } } + +.recaptcha { + margin-bottom: 30px; +} diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss index d129eb12a45..762b95a657c 100644 --- a/app/assets/stylesheets/pages/labels.scss +++ b/app/assets/stylesheets/pages/labels.scss @@ -117,7 +117,7 @@ .manage-labels-list { .btn-action { - color: $gl-dark-link-color; + color: $gl-text-color; .fa { font-size: 18px; @@ -203,6 +203,10 @@ z-index: 3; border-radius: $label-border-radius; padding: 6px 10px 6px 9px; + + &:hover { + box-shadow: inset 0 0 0 80px $label-remove-border; + } } .btn { @@ -255,3 +259,8 @@ } } } + +.label-link { + display: inline-block; + vertical-align: text-top; +} diff --git a/app/assets/stylesheets/pages/lint.scss b/app/assets/stylesheets/pages/lint.scss index a7c80dce424..68b6c5ecbd4 100644 --- a/app/assets/stylesheets/pages/lint.scss +++ b/app/assets/stylesheets/pages/lint.scss @@ -9,3 +9,13 @@ color: $lint-correct-color; } } + +.ci-linter { + .ci-editor { + height: 400px; + } + + .ci-template pre { + white-space: pre-wrap; + } +} diff --git a/app/assets/stylesheets/pages/login.scss b/app/assets/stylesheets/pages/login.scss index dd27a06fcd2..71ed5b1361a 100644 --- a/app/assets/stylesheets/pages/login.scss +++ b/app/assets/stylesheets/pages/login.scss @@ -17,14 +17,19 @@ line-height: 1.5; p { - font-size: 18px; + font-size: 16px; color: $login-brand-holder-color; } h1:first-child { font-weight: normal; - margin-bottom: 30px; + margin-bottom: 0.68em; margin-top: 0; + font-size: 34px; + } + + h3 { + font-size: 22px; } img { @@ -105,19 +110,19 @@ li { flex: 1; text-align: center; + border-left: 1px solid $border-color; &:first-of-type { + border-left: none; border-top-left-radius: $border-radius-default; } &:last-of-type { - border-left: 1px solid $border-color; border-top-right-radius: $border-radius-default; } &:not(.active) { background-color: $gray-light; - border-left: 1px solid $border-color; } a { diff --git a/app/assets/stylesheets/pages/members.scss b/app/assets/stylesheets/pages/members.scss index 36ee5d17211..be7193bae04 100644 --- a/app/assets/stylesheets/pages/members.scss +++ b/app/assets/stylesheets/pages/members.scss @@ -25,7 +25,7 @@ } .form-horizontal { - margin-top: 5px; + margin-top: 20px; @media (min-width: $screen-sm-min) { display: -webkit-flex; @@ -98,6 +98,10 @@ padding-right: 35px; @media (min-width: $screen-sm-min) { + width: 250px; + } + + @media (min-width: $screen-md-min) { width: 350px; } diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index e779e65eca3..b01d8d695d6 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -4,7 +4,7 @@ */ .mr-state-widget { background: $gray-light; - color: $gl-gray; + color: $gl-text-color; border: 1px solid $border-color; border-radius: 2px; @@ -21,6 +21,14 @@ display: inline-block; float: left; + .btn-success.dropdown-toggle .fa { + color: inherit; + } + + .btn-success.dropdown-toggle:disabled { + background-color: $gl-success; + } + .accept_merge_request { &.ci-pending, &.ci-running { @@ -48,21 +56,34 @@ &.right { float: right; padding-right: 0; + } - a { - color: $gl-gray; - } + .modify-merge-commit-link { + color: $gl-text-color; } - .remove_source_checkbox { + .merge-param-checkbox { margin: 0; } + + a .fa-question-circle { + color: $gl-text-color-secondary; + + &:hover, + &:focus { + color: $link-hover-color; + } + } } } .ci_widget { border-bottom: 1px solid $well-inner-border; - color: $gl-gray; + color: $gl-text-color; + display: -webkit-flex; + display: flex; + -webkit-align-items: center; + align-items: center; svg { margin-right: 4px; @@ -71,12 +92,20 @@ overflow: visible; } + &> span { + padding-right: 4px; + } + &.ci-success_with_warnings { i { color: $gl-warning; } } + + @media (max-width: $screen-xs-max) { + flex-wrap: wrap; + } } .mr-widget-body, @@ -85,8 +114,39 @@ padding: $gl-padding; } + .mr-widget-pipeline-graph { + flex-shrink: 0; + + .dropdown-menu { + margin-top: 11px; + } + + .ci-action-icon-wrapper { + line-height: 16px; + } + + @media (max-width: $screen-xs-max) { + order: 1; + margin-top: $gl-padding-top; + border-radius: 3px; + background-color: $white-light; + border: 1px solid $gray-darker; + width: 100%; + text-align: center; + + .dropdown-menu { + margin-left: -97.5px; + } + + .arrow-up::before, + .arrow-up::after, { + margin-left: 97.5px; + } + } + } + .normal { - color: $gl-text-color-dark; + color: $gl-text-color; } .js-deployment-link { @@ -96,9 +156,9 @@ .mr-widget-body { h4 { font-weight: 600; - font-size: 17px; + font-size: 16px; margin: 5px 0; - color: $gl-gray-dark; + color: $gl-text-color; &.has-conflicts .fa-exclamation-triangle { color: $gl-warning; @@ -182,7 +242,7 @@ } .label-branch { - color: $gl-gray-dark; + color: $gl-text-color; font-family: $monospace_font; font-weight: bold; overflow: hidden; @@ -302,10 +362,6 @@ left: 0; top: 2px; } - - .commit-row-info { - line-height: 20px; - } } .btn-clipboard { @@ -359,7 +415,7 @@ th { background-color: $white-light; - color: $gl-gray-light; + color: $gl-text-color-secondary; } } } @@ -421,10 +477,30 @@ left: 0; z-index: 10; transition: right .15s; + + @media (max-width: $screen-xs-max) { + right: 0; + } + + .merge-request-tabs-container { + padding-left: $gl-padding; + padding-right: $gl-padding; + } + } +} + +.limit-container-width { + .merge-request-tabs-container { + max-width: $limited-layout-width; + margin-left: auto; + margin-right: auto; } +} - &:not(.affix) .container-fluid { - padding-left: 0; - padding-right: 0; +.limit-container-width:not(.container-limited) { + .merge-request-tabs-holder:not(.affix) { + .merge-request-tabs-container { + max-width: $limited-layout-width - ($gl-padding * 2); + } } } diff --git a/app/assets/stylesheets/pages/milestone.scss b/app/assets/stylesheets/pages/milestone.scss index 77c523d7310..686b64cdd24 100644 --- a/app/assets/stylesheets/pages/milestone.scss +++ b/app/assets/stylesheets/pages/milestone.scss @@ -25,12 +25,6 @@ } .issuable-row { - .color-label { - border-radius: 2px; - padding: 3px !important; - margin-right: 7px; - } - span a { color: $gl-text-color; word-wrap: break-word; @@ -108,13 +102,17 @@ margin-top: 7px; .issuable-number { - color: $gl-gray-light; + color: $gl-text-color-secondary; margin-right: 5px; } .avatar { float: none; } + + > a:not(:last-of-type) { + margin-right: 5px; + } } } diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss index 074abec7692..f984b469609 100644 --- a/app/assets/stylesheets/pages/note_form.scss +++ b/app/assets/stylesheets/pages/note_form.scss @@ -27,6 +27,7 @@ .new-note, .note-edit-form { .note-form-actions { + position: relative; margin-top: $gl-padding; } @@ -44,7 +45,7 @@ .note-textarea { display: block; padding: 10px 0; - color: $gl-gray; + color: $gl-text-color; font-family: $regular_font; border: 0; @@ -109,7 +110,7 @@ margin: auto; margin-top: 0; text-align: center; - font-size: 13px; + font-size: 12px; @media (max-width: $screen-sm-max) { // On smaller devices the warning becomes the fourth item in the list, @@ -204,7 +205,7 @@ .comment-toolbar { padding-top: $gl-padding-top; - color: $gl-gray-light; + color: $gl-text-color-secondary; border-top: 1px solid $border-color; } @@ -265,3 +266,18 @@ } } } + +.note-edit-warning.settings-message { + display: none; + padding: 5px 10px; + position: absolute; + left: 127px; + top: 2px; + + @media (max-width: $screen-xs-max) { + position: relative; + top: 0; + left: 0; + margin-bottom: 10px; + } +} diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index 106c5d4d390..f310cc72da0 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -43,7 +43,7 @@ ul.notes { } .system-note-message { - display: inline-block; + display: inline; &::first-letter { text-transform: lowercase; @@ -55,7 +55,7 @@ ul.notes { } p { - display: inline-block; + display: inline; margin: 0; &::first-letter { @@ -195,10 +195,10 @@ ul.notes { } .note-body { - overflow: auto; + overflow-x: auto; + overflow-y: hidden; .note-text { - overflow: auto; word-wrap: break-word; @include md-typography; // Reset ul style types since we're nested inside a ul already @@ -345,7 +345,7 @@ ul.notes { } .author_link { - color: $gl-gray; + color: $gl-text-color; } } @@ -353,6 +353,14 @@ ul.notes { font-size: 14px; } +.note-headline-light { + display: inline; + + @media (max-width: $screen-xs-min) { + display: block; + } +} + .note-headline-light, .discussion-headline-light { color: $notes-light-color; @@ -448,11 +456,6 @@ ul.notes { border-radius: $border-radius-base; } -.diff-file .note .note-actions { - right: 0; - top: 0; -} - /** * Line note button on the side of diffs @@ -464,7 +467,7 @@ ul.notes { } .add-diff-note { - margin-top: -4px; + margin-top: -8px; border-radius: 40px; background: $white-light; padding: 4px; @@ -512,7 +515,6 @@ ul.notes { .line-resolve-all-container { .btn-group { - margin-top: -1px; margin-left: -4px; } @@ -523,8 +525,9 @@ ul.notes { } .line-resolve-all { + vertical-align: middle; display: inline-block; - padding: 5px 10px; + padding: 6px 10px; background-color: $gray-light; border: 1px solid $border-color; border-radius: $border-radius-default; @@ -532,18 +535,14 @@ ul.notes { &.has-next-btn { border-top-right-radius: 0; border-bottom-right-radius: 0; + border-right: 0; } .line-resolve-btn { - vertical-align: middle; margin-right: 5px; } } -.line-resolve-text { - vertical-align: middle; -} - .line-resolve-btn { display: inline-block; position: relative; @@ -562,18 +561,14 @@ ul.notes { &.is-active { color: $gl-text-green; - svg path { + svg { fill: $gl-text-green; } } svg { position: relative; - color: $gray-darkest; - - path { - fill: $gray-darkest; - } + fill: $gray-darkest; } } @@ -586,3 +581,17 @@ ul.notes { } } } + +// Merge request notes in diffs +.diff-file { + // Diff is side by side + .notes_content.parallel .note-header .note-headline-light { + display: block; + position: relative; + } + // Diff is inline + .notes_content .note-header .note-headline-light { + display: inline-block; + position: relative; + } +} diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 621b780ce4d..974100bdff0 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -1,4 +1,9 @@ .pipelines { + .realtime-loading { + font-size: 40px; + text-align: center; + } + .stage { max-width: 90px; width: 90px; @@ -24,6 +29,10 @@ min-width: 1200px; table-layout: fixed; + .label { + margin-bottom: 3px; + } + .pipeline-id { color: $black; } @@ -35,8 +44,8 @@ .pipeline-info, .pipeline-commit, - .pipeline-actions, - .pipeline-stages { + .pipeline-stages, + .pipeline-actions { width: 20%; } } @@ -52,6 +61,10 @@ } } +.content-list.pipelines .table-holder { + min-height: 300px; +} + .pipeline-holder { width: 100%; overflow: auto; @@ -80,6 +93,10 @@ td { padding: 10px 8px; } + + .commit-link { + padding: 9px 8px 10px; + } } tbody { @@ -123,7 +140,7 @@ height: 14px; width: 14px; vertical-align: middle; - fill: $gl-gray-light; + fill: $gl-text-color-secondary; } .fa { @@ -166,44 +183,11 @@ } } - .stage-cell { - font-size: 0; - - svg { - height: 18px; - width: 18px; - position: relative; - z-index: 2; - vertical-align: middle; - overflow: visible; - } - - .stage-container { - display: inline-block; - position: relative; - margin-right: 6px; - - .tooltip { - white-space: nowrap; - } - - &:not(:last-child) { - &::after { - content: ''; - width: 8px; - position: absolute; - right: -7px; - bottom: 9px; - border-bottom: 2px solid $border-color; - } - } - } - } - .duration, .finished-at { - color: $gl-gray-light; + color: $gl-text-color-secondary; margin: 4px 0; + white-space: nowrap; .fa { font-size: 12px; @@ -223,7 +207,7 @@ .btn { margin: 0; - color: $gl-gray-light; + color: $gl-text-color-secondary; } .cancel-retry-btns { @@ -236,10 +220,10 @@ .dropdown-toggle, .dropdown-menu { - color: $gl-gray-light; + color: $gl-text-color-secondary; .fa { - color: $gl-gray-light; + color: $gl-text-color-secondary; font-size: 14px; } @@ -268,12 +252,16 @@ } } } + + .tooltip { + white-space: nowrap; + } } .build-link { a { - color: $gl-dark-link-color; + color: $gl-text-color; } } @@ -282,6 +270,48 @@ } } +.stage-cell { + font-size: 0; + padding: 10px 4px; + + > .stage-container > div > button > span > svg, + > .stage-container > button > svg { + height: 22px; + width: 22px; + position: absolute; + top: -1px; + left: -1px; + z-index: 2; + overflow: visible; + } + + .stage-container { + display: inline-block; + position: relative; + height: 22px; + margin: 3px 6px 3px 0; + + .tooltip { + white-space: nowrap; + } + + .tooltip-inner { + padding: 3px 4px; + } + + &:not(:last-child) { + &::after { + content: ''; + width: 7px; + position: absolute; + right: -7px; + top: 10px; + border-bottom: 2px solid $border-color; + } + } + } +} + .admin-builds-table { .ci-table td:last-child { min-width: 120px; @@ -327,17 +357,18 @@ width: 100%; background-color: $gray-light; padding: $gl-padding; - overflow: auto; white-space: nowrap; transition: max-height 0.3s, padding 0.3s; + overflow: auto; - ul { + .stage-column-list, + .builds-container > ul { padding: 0; } a { text-decoration: none; - color: $gl-text-color-light; + color: $gl-text-color-secondary; } svg { @@ -461,13 +492,50 @@ width: 186px; margin-bottom: 10px; white-space: normal; - color: $gl-text-color-light; + color: $gl-text-color-secondary; + + // Action Icons in big pipeline-graph nodes + > .ci-action-icon-container .ci-action-icon-wrapper { + height: 30px; + width: 30px; + background: $white-light; + border: 1px solid $border-color; + border-radius: 100%; + display: block; + + &:hover { + background-color: $stage-hover-bg; + border: 1px solid $stage-hover-bg; + } + + svg { + fill: $border-color; + position: relative; + left: -1px; + top: -1px; + } + + &:hover svg { + fill: $gl-text-color; + } + } + + > .ci-action-icon-container { + position: absolute; + right: 5px; + top: 5px; + } + + .ci-status-icon svg { + height: 20px; + width: 20px; + } .dropdown-menu-toggle { background-color: transparent; border: none; padding: 0; - color: $gl-text-color-light; + color: $gl-text-color-secondary; &:focus { outline: none; @@ -497,21 +565,6 @@ } } - > .ci-action-icon-container { - position: absolute; - right: 4px; - top: 5px; - } - - .ci-status-icon { - position: relative; - top: 1px; - } - - .ci-status-icon svg { - height: 20px; - width: 20px; - } .arrow { &::before, @@ -596,44 +649,14 @@ } } +// Triggers the dropdown in the big pipeline graph .dropdown-counter-badge { - float: right; color: $border-color; font-weight: 100; font-size: 15px; - margin-right: 2px; -} - -.grouped-pipeline-dropdown { - padding: 0; - width: 191px; - left: auto; - right: -195px; - top: -4px; - box-shadow: 0 1px 5px $black-transparent; - - a { - display: inline-block; - - &:hover { - background-color: $stage-hover-bg; - } - } - - ul { - max-height: 245px; - overflow: auto; - margin: 5px 0; - - li { - padding-top: 2px; - margin: 0 5px; - padding-left: 0; - padding-bottom: 0; - margin-bottom: 0; - line-height: 1.2; - } - } + position: absolute; + right: 13px; + top: 8px; } .ci-status-text { @@ -644,117 +667,248 @@ vertical-align: bottom; display: inline-block; position: relative; - font-weight: 100; + font-weight: normal; } -// Action Icons -.ci-action-icon-container .ci-action-icon-wrapper { - i { - color: $border-color; - border-radius: 100%; - border: 1px solid $border-color; - padding: 5px 6px; - font-size: 13px; - background: $white-light; - height: 30px; - width: 30px; +// Dropdown button in mini pipeline graph +.mini-pipeline-graph-dropdown-toggle { + border-radius: 100px; + background-color: $white-light; + border-width: 1px; + border-style: solid; + width: 22px; + height: 22px; + margin: 0; + padding: 0; + transition: all 0.2s linear; + position: relative; - &::before { - position: relative; - top: 3px; - left: 3px; + > .fa.fa-caret-down { + position: absolute; + left: 20px; + top: 5px; + display: inline-block; + visibility: hidden; + opacity: 0; + color: inherit; + font-size: 12px; + transition: visibility 0.1s, opacity 0.1s linear; + } + + &:active, + &:focus, + &:hover { + outline: none; + width: 35px; + + .fa.fa-caret-down { + visibility: visible; + opacity: 1; } + } - &:hover { - color: $gl-text-color; - background-color: $stage-hover-bg; - border: 1px solid $stage-hover-bg; + // Dropdown button animation in mini pipeline graph + &.ci-status-icon-success { + border-color: $gl-success; + color: $gl-success; + + &:hover, + &:focus, + &:active { + background-color: rgba($gl-success, 0.1); + border-color: $gl-success; } } - .ci-play-icon { - padding: 5px 5px 5px 7px; + &.ci-status-icon-failed { + border-color: $gl-danger; + color: $gl-danger; + + &:hover, + &:focus, + &:active { + background-color: rgba($gl-danger, 0.1); + border-color: $gl-danger; + } } -} -.dropdown-build { - color: $gl-text-color-light; + &.ci-status-icon-pending, + &.ci-status-icon-success_with_warnings { + border-color: $gl-warning; + color: $gl-warning; - .ci-action-icon-container { - padding: 0; - font-size: 11px; - float: right; - margin-top: 4px; - display: inline-block; - position: relative; + &:hover, + &:focus, + &:active { + background-color: rgba($gl-warning, 0.1); + border-color: $gl-warning; + } + } + + &.ci-status-icon-running { + border-color: $blue-normal; + color: $blue-normal; - i { - font-size: 11px; - margin-top: 0; + &:hover, + &:focus, + &:active { + background-color: rgba($blue-normal, 0.1); + border-color: $blue-normal; } } - &:hover { - background-color: $stage-hover-bg; - border-radius: 3px; + &.ci-status-icon-canceled, + &.ci-status-icon-disabled, + &.ci-status-icon-not-found, + &.ci-status-icon-manual { + border-color: $gl-text-color; color: $gl-text-color; + + &:hover, + &:focus, + &:active { + background-color: rgba($gl-text-color, 0.1); + border-color: $gl-text-color; + } } - .ci-action-icon-container { - i { - width: 25px; - height: 25px; + &.ci-status-icon-created, + &.ci-status-icon-skipped { + border-color: $gray-darkest; + color: $gray-darkest; - &::before { - top: 1px; - left: 1px; - } + &:hover, + &:focus, + &:active { + background-color: rgba($gray-darkest, 0.1); + border-color: $gray-darkest; } } +} - .stage { - max-width: 100px; - width: 100px; - } +// dropdown content for big and mini pipeline +.big-pipeline-graph-dropdown-menu, +.mini-pipeline-graph-dropdown-menu { + width: 195px; + max-width: 195px; - .ci-status-icon svg { - height: 18px; - width: 18px; + li { + padding: 2px 3px; } - .ci-status-text { - max-width: 95px; + .scrollable-menu { + max-height: 245px; + overflow: auto; } -} -/** - * Builds dropdown in mini pipeline - */ -.mini-pipeline-graph { - .builds-dropdown { - background-color: transparent; - border: none; - padding: 0; - color: $gl-text-color-light; - border: none; - margin: 0; + // Loading icon + .builds-dropdown-loading { + margin: 0 auto; + width: 20px; + } + + // Action icon on the right + a.ci-action-icon-wrapper { + color: $action-icon-color; + border: 1px solid $action-icon-color; + border-radius: 20px; + width: 22px; + height: 22px; + padding: 2px 0 0 5px; + cursor: pointer; + float: right; + margin: -26px 9px 0 0; + font-size: 12px; + background-color: $white-light; + + &:hover, + &:focus { + background-color: $stage-hover-bg; + border: 1px solid transparent; + } + + svg { + width: 22px; + height: 22px; + left: -6px; + position: relative; + top: -3px; + fill: $action-icon-color; + } + + &:hover svg, + &:focus svg { + fill: $gl-text-color; + } } - .builds-dropdown-loading { - margin: 10px auto; - width: 18px; + // link to the build + .mini-pipeline-graph-dropdown-item { + padding: 3px 7px 4px; + clear: both; + font-weight: normal; + line-height: 1.428571429; + white-space: nowrap; + margin: 0 5px; + border-radius: 3px; + + // build name + .ci-build-text { + font-weight: 200; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + width: 90px; + color: $gl-text-color-secondary; + margin-left: 2px; + display: inline-block; + top: 1px; + vertical-align: text-bottom; + position: relative; + } + + // status icon on the left + .ci-status-icon { + top: 3px; + position: relative; + + > svg { + overflow: visible; + width: 18px; + height: 18px; + } + } + + &:hover, + &:focus { + outline: none; + text-decoration: none; + color: $gl-text-color; + background-color: $stage-hover-bg; + } } +} - .grouped-pipeline-dropdown { - right: -172px; - top: 23px; - min-height: 50px; +// Dropdown in the big pipeline graph +.big-pipeline-graph-dropdown-menu { + width: 195px; + min-width: 195px; + left: auto; + right: -195px; + top: -4px; + box-shadow: 0 1px 5px $black-transparent; - a { - color: $gl-text-color-light; + .mini-pipeline-graph-dropdown-item { + .ci-status-icon { + top: -1px; } } +} +/** + * Top arrow in the dropdown in the mini pipeline graph + */ +.mini-pipeline-graph-dropdown-menu { .arrow-up { &::before, &::after { @@ -783,94 +937,8 @@ } /** - * Icons in mini pipeline graph + * Terminal */ -.mini-pipeline-graph-icon-container .ci-status-icon { - display: inline-block; - border: 1px solid; - border-radius: 20px; - margin-right: 1px; - width: 20px; - height: 20px; - position: relative; - z-index: 2; - transition: all 0.2s cubic-bezier(0.25, 0, 1, 1); - - svg { - top: -1px; - } -} - -.builds-dropdown { - &:focus { - outline: none; - margin-right: -8px; - - .ci-status-icon { - width: 28px; - padding: 0 8px 0 0; - transition: width 0.2s cubic-bezier(0.25, 0, 1, 1); - - + .dropdown-caret { - display: inline-block; - } - } - } - - &:focus, - &:active { - .ci-status-icon-success { - background-color: rgba($gl-success, .1); - } - - .ci-status-icon-failed { - background-color: rgba($gl-danger, .1); - } - - .ci-status-icon-pending, - .ci-status-icon-success_with_warnings { - background-color: rgba($gl-warning, .1); - } - - .ci-status-icon-running { - background-color: rgba($blue-normal, .1); - } - - .ci-status-icon-canceled, - .ci-status-icon-disabled, - .ci-status-icon-not-found { - background-color: rgba($gl-gray, .1); - } - - .ci-status-icon-created, - .ci-status-icon-skipped { - background-color: rgba($gray-darkest, .1); - } - } - - .mini-pipeline-graph-icon-container { - .ci-status-icon:hover, - .ci-status-icon:focus { - width: 28px; - padding: 0 8px 0 0; - - + .dropdown-caret { - display: inline-block; - } - } - - .dropdown-caret { - font-size: 11px; - position: relative; - top: 3px; - left: -11px; - margin-right: -6px; - display: none; - z-index: 2; - } - } -} - .terminal-icon { margin-left: 3px; } diff --git a/app/assets/stylesheets/pages/profile.scss b/app/assets/stylesheets/pages/profile.scss index 8b1976bd925..722b3006f7c 100644 --- a/app/assets/stylesheets/pages/profile.scss +++ b/app/assets/stylesheets/pages/profile.scss @@ -216,8 +216,8 @@ } } -.user-profile { +.user-profile { .cover-controls a { margin-left: 5px; } @@ -231,8 +231,11 @@ } } - @media (max-width: $screen-xs-max) { + .user-profile-nav { + font-size: 0; + } + @media (max-width: $screen-xs-max) { .cover-block { padding-top: 20px; } @@ -253,6 +256,12 @@ } } } + + .user-profile-nav { + a { + margin-right: 0; + } + } } } @@ -271,4 +280,4 @@ table.u2f-registrations { .scopes-list { padding-left: 18px; } -}
\ No newline at end of file +} diff --git a/app/assets/stylesheets/pages/profiles/preferences.scss b/app/assets/stylesheets/pages/profiles/preferences.scss index f8da0983b77..100ace41f2a 100644 --- a/app/assets/stylesheets/pages/profiles/preferences.scss +++ b/app/assets/stylesheets/pages/profiles/preferences.scss @@ -22,8 +22,8 @@ background: $theme-graphite; } - &.ui_gray { - background: $theme-gray; + &.ui_black { + background: $theme-black; } &.ui_green { diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index e16a553bcda..8b59c20cb65 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -9,24 +9,24 @@ .new_project, .edit-project { - fieldset { - - &.features { + .sharing-and-permissions { + .header { + padding-top: $gl-vert-padding; + } - .label-light { - margin-bottom: 0; - } + .label-light { + margin-bottom: 0; + } - .help-block { - margin-top: 0; - } + .help-block { + margin-top: 0; } .form-group { margin-bottom: 5px; } - &> .form-group { + & > .form-group { padding-left: 0; } } @@ -73,7 +73,7 @@ border: 1px solid $border-color; } - &+ .select2 a { + & + .select2 a { border-top-left-radius: 0; border-bottom-left-radius: 0; } @@ -292,7 +292,7 @@ .option-title { font-weight: normal; display: inline-block; - color: $gl-gray-dark; + color: $gl-text-color; } .option-descr { @@ -331,7 +331,7 @@ a.deploy-project-label { padding: 5px; margin-right: 5px; - color: $gl-gray; + color: $gl-text-color; background-color: $row-hover; &:hover { @@ -372,7 +372,7 @@ a.deploy-project-label { } a { - color: $gl-dark-link-color; + color: $gl-text-color; } .dropdown-menu { @@ -426,7 +426,7 @@ a.deploy-project-label { width: 100%; height: 100%; padding-top: $gl-padding; - color: $gl-gray; + color: $gl-text-color; .caption { min-height: 30px; @@ -523,7 +523,7 @@ a.deploy-project-label { &:hover, &:focus { - color: darken($notes-light-color, 15%); + color: $gl-text-color; } } @@ -552,7 +552,7 @@ pre.light-well { margin: 0 7px 7px; h5 { - color: $gl-text-color-dark; + color: $gl-text-color; } .light-well { @@ -587,11 +587,21 @@ pre.light-well { .project-full-name { @include str-truncated; + + @media (max-width: $screen-xs-max) { + max-width: 50%; + } } .controls { line-height: $list-text-height; + .badge { + @media (max-width: $screen-xs-max) { + display: none; + } + } + a:hover { text-decoration: none; } @@ -605,6 +615,12 @@ pre.light-well { top: 2px; } } + + .description p { + @media (max-width: $screen-xs-max) { + max-width: 50%; + } + } } .bottom { @@ -618,7 +634,6 @@ pre.light-well { margin: 0; } - .activity-filter-block { .controls { padding-bottom: 7px; @@ -627,6 +642,12 @@ pre.light-well { } } +.commits-search-form { + .input-short { + min-width: 200px; + } +} + .project-last-commit { @media (min-width: $screen-sm-min) { margin-top: $gl-padding; @@ -657,7 +678,7 @@ pre.light-well { } .commit-row-message { - color: $gl-gray; + color: $gl-text-color; } .commit_short_id { @@ -745,7 +766,7 @@ pre.light-well { .protected-branches-list { a { - color: $gl-gray; + color: $gl-text-color; &:hover { color: $gl-link-color; @@ -805,7 +826,31 @@ pre.light-well { .compare-form-group { .dropdown-menu { - width: 300px; + width: 100%; + + @media (min-width: $screen-sm-min) { + width: 300px; + } + } + + + .compare-ellipsis { + width: 100%; + vertical-align: middle; + text-align: center; + margin-top: -20px; + + @media (min-width: $screen-sm-min) { + margin-top: 0; + width: auto; + } + } + + .inline-input-group { + width: 100%; + + @media (min-width: $screen-sm-min) { + width: 250px; + } } } @@ -860,10 +905,18 @@ pre.light-well { } } -.project-feature-nested { +.project-feature { + padding-top: 10px; + @media (min-width: $screen-sm-min) { padding-left: 45px; } + + &.nested { + @media (min-width: $screen-sm-min) { + padding-left: 90px; + } + } } .project-repo-select { @@ -876,7 +929,51 @@ pre.light-well { .variables-table { table-layout: fixed; + &.table-responsive { + border: none; + } + .variable-key { - width: 30%; + width: 300px; + max-width: 300px; + overflow: hidden; + word-wrap: break-word; + + // override bootstrap + white-space: normal!important; + + @media (max-width: $screen-sm-max) { + width: 150px; + max-width: 150px; + } + } + + .variable-value { + @media(max-width: $screen-xs-max) { + width: 150px; + max-width: 150px; + overflow: hidden; + word-wrap: break-word; + } + } +} + +.services-installation-info .row { + margin-bottom: 10px; +} + +.service-installation { + padding: 32px; + margin: 32px; + border-radius: 3px; + background-color: $white-light; + + h3 { + margin-top: 0; + } + + hr { + margin: 32px 0; + border-color: $border-color; } } diff --git a/app/assets/stylesheets/pages/search.scss b/app/assets/stylesheets/pages/search.scss index cedd4cb2987..88ea92c5afb 100644 --- a/app/assets/stylesheets/pages/search.scss +++ b/app/assets/stylesheets/pages/search.scss @@ -14,6 +14,19 @@ } } +.search form:hover, +.file-finder-input:hover, +.issuable-search-form:hover, +.search-text-input:hover, +.form-control:hover { + border-color: lighten($dropdown-input-focus-border, 20%); + box-shadow: 0 0 4px lighten($search-input-focus-shadow-color, 20%); +} + +input[type="checkbox"]:hover { + box-shadow: 0 0 2px 2px lighten($search-input-focus-shadow-color, 20%), 0 0 0 1px lighten($search-input-focus-shadow-color, 20%); +} + .search { margin-right: 10px; margin-left: 10px; diff --git a/app/assets/stylesheets/pages/settings.scss b/app/assets/stylesheets/pages/settings.scss index ddee2c95247..a28a87ed4f8 100644 --- a/app/assets/stylesheets/pages/settings.scss +++ b/app/assets/stylesheets/pages/settings.scss @@ -1,5 +1,5 @@ .settings-list-icon { - color: $gl-gray-light; + color: $gl-text-color-secondary; font-size: $settings-icon-size; line-height: 42px; } diff --git a/app/assets/stylesheets/pages/status.scss b/app/assets/stylesheets/pages/status.scss index a810ed32327..6f31d4ed789 100644 --- a/app/assets/stylesheets/pages/status.scss +++ b/app/assets/stylesheets/pages/status.scss @@ -1,6 +1,6 @@ .container-fluid { .ci-status { - padding: 2px 7px; + padding: 2px 7px 4px; margin-right: 10px; border: 1px solid $gray-darker; white-space: nowrap; @@ -19,7 +19,8 @@ overflow: visible; } - &.ci-failed { + &.ci-failed, + &.ci-failed_with_warnings { color: $gl-danger; border-color: $gl-danger; @@ -61,15 +62,15 @@ &.ci-canceled, &.ci-disabled { - color: $gl-gray; - border-color: $gl-gray; + color: $gl-text-color; + border-color: $gl-text-color; &:not(span):hover { - background-color: rgba($gl-gray, .07); + background-color: rgba($gl-text-color, .07); } svg { - fill: $gl-gray; + fill: $gl-text-color; } } @@ -101,15 +102,15 @@ &.ci-created, &.ci-skipped { - color: $gl-gray-light; - border-color: $gl-gray-light; + color: $gl-text-color-secondary; + border-color: $gl-text-color-secondary; &:not(span):hover { - background-color: rgba($gl-gray-light, .07); + background-color: rgba($gl-text-color-secondary, .07); } svg { - fill: $gl-gray-light; + fill: $gl-text-color-secondary; } } @@ -135,3 +136,9 @@ left: 5px; } } + +.ci-status-link { + svg { + overflow: visible; + } +} diff --git a/app/assets/stylesheets/pages/todos.scss b/app/assets/stylesheets/pages/todos.scss index 508b30f3947..0d5604aae69 100644 --- a/app/assets/stylesheets/pages/todos.scss +++ b/app/assets/stylesheets/pages/todos.scss @@ -76,6 +76,10 @@ font-size: 14px; } + .action-name { + font-weight: normal; + } + .todo-body { .todo-note { word-wrap: break-word; @@ -90,7 +94,7 @@ } p { - color: $gl-text-color-dark; + color: $gl-text-color; } } diff --git a/app/assets/stylesheets/pages/tree.scss b/app/assets/stylesheets/pages/tree.scss index cad4e149845..948921efc0b 100644 --- a/app/assets/stylesheets/pages/tree.scss +++ b/app/assets/stylesheets/pages/tree.scss @@ -32,6 +32,10 @@ .last-commit { @include str-truncated(506px); + .fa-angle-right { + margin-left: 5px; + } + @media (min-width: $screen-md-min) and (max-width: $screen-md-max) { @include str-truncated(450px); } @@ -78,7 +82,7 @@ i, a { - color: $gl-dark-link-color; + color: $gl-text-color; } img { @@ -104,21 +108,21 @@ padding-right: 8px; .commit-author-name { - color: $gl-gray; + color: $gl-text-color; } } .tree-time-ago { min-width: 135px; - color: $gl-gray-light; + color: $gl-text-color-secondary; } .tree-commit { max-width: 320px; - color: $gl-gray-light; + color: $gl-text-color-secondary; .tree-commit-link { - color: $gl-gray-light; + color: $gl-text-color-secondary; &:hover { text-decoration: underline; @@ -134,21 +138,18 @@ .blob-commit-info { list-style: none; - padding: $gl-padding; background: $gray-light; + padding: 6px 0; border: 1px solid $border-color; border-bottom: none; margin: 0; - .commit { - padding-top: 0; - padding-bottom: 0; + .table-list-cell { + border-bottom: none; + } - .commit-row-title { - .commit-row-message { - font-weight: normal; - } - } + .commit-actions { + width: 200px; } } diff --git a/app/assets/stylesheets/pages/wiki.scss b/app/assets/stylesheets/pages/wiki.scss index b9f81533150..d5783e14b21 100644 --- a/app/assets/stylesheets/pages/wiki.scss +++ b/app/assets/stylesheets/pages/wiki.scss @@ -15,7 +15,7 @@ } .wiki-last-edit-by { - color: $gl-gray-light; + color: $gl-text-color-secondary; strong { color: $gl-text-color; @@ -24,7 +24,7 @@ .light { font-weight: normal; - color: $gl-gray-light; + color: $gl-text-color-secondary; } .git-access-header { |