summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/abuse_reports.js63
-rw-r--r--app/assets/javascripts/activities.js51
-rw-r--r--app/assets/javascripts/admin.js120
-rw-r--r--app/assets/javascripts/api.js290
-rw-r--r--app/assets/javascripts/aside.js45
-rw-r--r--app/assets/javascripts/autosave.js109
-rw-r--r--app/assets/javascripts/awards_handler.js40
-rw-r--r--app/assets/javascripts/behaviors/gl_emoji.js206
-rw-r--r--app/assets/javascripts/behaviors/gl_emoji/is_emoji_unicode_supported.js121
-rw-r--r--app/assets/javascripts/behaviors/gl_emoji/spread_string.js2
-rw-r--r--app/assets/javascripts/behaviors/gl_emoji/unicode_support_map.js33
-rw-r--r--app/assets/javascripts/behaviors/quick_submit.js2
-rw-r--r--app/assets/javascripts/behaviors/requires_input.js2
-rw-r--r--app/assets/javascripts/blob/blob_file_dropzone.js2
-rw-r--r--app/assets/javascripts/blob/blob_line_permalink_updater.js35
-rw-r--r--app/assets/javascripts/blob/create_branch_dropdown.js88
-rw-r--r--app/assets/javascripts/blob/target_branch_dropdown.js152
-rw-r--r--app/assets/javascripts/breakpoints.js108
-rw-r--r--app/assets/javascripts/broadcast_message.js61
-rw-r--r--app/assets/javascripts/build.js530
-rw-r--r--app/assets/javascripts/build_artifacts.js43
-rw-r--r--app/assets/javascripts/ci_lint_editor.js27
-rw-r--r--app/assets/javascripts/commit.js18
-rw-r--r--app/assets/javascripts/commits.js118
-rw-r--r--app/assets/javascripts/commons/bootstrap.js8
-rw-r--r--app/assets/javascripts/commons/index.js1
-rw-r--r--app/assets/javascripts/commons/polyfills.js10
-rw-r--r--app/assets/javascripts/commons/polyfills/custom_event.js9
-rw-r--r--app/assets/javascripts/commons/polyfills/element.js20
-rw-r--r--app/assets/javascripts/compare.js165
-rw-r--r--app/assets/javascripts/compare_autocomplete.js120
-rw-r--r--app/assets/javascripts/confirm_danger_modal.js57
-rw-r--r--app/assets/javascripts/copy_as_gfm.js637
-rw-r--r--app/assets/javascripts/copy_to_clipboard.js93
-rw-r--r--app/assets/javascripts/create_label.js207
-rw-r--r--app/assets/javascripts/diff.js206
-rw-r--r--app/assets/javascripts/diff_notes/components/diff_note_avatars.js155
-rw-r--r--app/assets/javascripts/diff_notes/components/new_issue_for_discussion.js29
-rw-r--r--app/assets/javascripts/diff_notes/components/resolve_btn.js16
-rw-r--r--app/assets/javascripts/diff_notes/diff_notes_bundle.js13
-rw-r--r--app/assets/javascripts/diff_notes/icons/collapse_icon.svg1
-rw-r--r--app/assets/javascripts/diff_notes/models/discussion.js4
-rw-r--r--app/assets/javascripts/diff_notes/models/note.js13
-rw-r--r--app/assets/javascripts/diff_notes/stores/comments.js6
-rw-r--r--app/assets/javascripts/dispatcher.js49
-rw-r--r--app/assets/javascripts/droplab/droplab_ajax.js3
-rw-r--r--app/assets/javascripts/droplab/droplab_ajax_filter.js3
-rw-r--r--app/assets/javascripts/dropzone_input.js406
-rw-r--r--app/assets/javascripts/due_date_select.js337
-rw-r--r--app/assets/javascripts/environments/components/environment.js76
-rw-r--r--app/assets/javascripts/environments/components/environment_actions.js56
-rw-r--r--app/assets/javascripts/environments/components/environment_external_url.js14
-rw-r--r--app/assets/javascripts/environments/components/environment_item.js34
-rw-r--r--app/assets/javascripts/environments/components/environment_rollback.js49
-rw-r--r--app/assets/javascripts/environments/components/environment_stop.js50
-rw-r--r--app/assets/javascripts/environments/components/environment_terminal_button.js9
-rw-r--r--app/assets/javascripts/environments/components/environments_table.js16
-rw-r--r--app/assets/javascripts/environments/environments_bundle.js2
-rw-r--r--app/assets/javascripts/environments/event_hub.js3
-rw-r--r--app/assets/javascripts/environments/folder/environments_folder_bundle.js2
-rw-r--r--app/assets/javascripts/environments/folder/environments_folder_view.js19
-rw-r--r--app/assets/javascripts/environments/services/environments_service.js15
-rw-r--r--app/assets/javascripts/environments/stores/environments_store.js7
-rw-r--r--app/assets/javascripts/extensions/array.js28
-rw-r--r--app/assets/javascripts/extensions/custom_event.js12
-rw-r--r--app/assets/javascripts/extensions/element.js20
-rw-r--r--app/assets/javascripts/extensions/jquery.js16
-rw-r--r--app/assets/javascripts/extensions/object.js26
-rw-r--r--app/assets/javascripts/extensions/string.js2
-rw-r--r--app/assets/javascripts/files_comment_button.js221
-rw-r--r--app/assets/javascripts/filterable_list.js1
-rw-r--r--app/assets/javascripts/filtered_search/dropdown_utils.js54
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js6
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_manager.js14
-rw-r--r--app/assets/javascripts/flash.js73
-rw-r--r--app/assets/javascripts/gfm_auto_complete.js726
-rw-r--r--app/assets/javascripts/gl_dropdown.js1488
-rw-r--r--app/assets/javascripts/gl_field_error.js288
-rw-r--r--app/assets/javascripts/gl_field_errors.js69
-rw-r--r--app/assets/javascripts/gl_form.js144
-rw-r--r--app/assets/javascripts/group_avatar.js35
-rw-r--r--app/assets/javascripts/group_label_subscription.js97
-rw-r--r--app/assets/javascripts/groups_select.js124
-rw-r--r--app/assets/javascripts/header.js15
-rw-r--r--app/assets/javascripts/issue.js226
-rw-r--r--app/assets/javascripts/lib/utils/url_utility.js7
-rw-r--r--app/assets/javascripts/line_highlighter.js12
-rw-r--r--app/assets/javascripts/main.js668
-rw-r--r--app/assets/javascripts/merge_request_widget.js5
-rw-r--r--app/assets/javascripts/mini_pipeline_graph_dropdown.js155
-rw-r--r--app/assets/javascripts/monitoring/prometheus_graph.js6
-rw-r--r--app/assets/javascripts/new_commit_form.js12
-rw-r--r--app/assets/javascripts/notes.js115
-rw-r--r--app/assets/javascripts/pager.js8
-rw-r--r--app/assets/javascripts/project_select.js4
-rw-r--r--app/assets/javascripts/search.js2
-rw-r--r--app/assets/javascripts/todos.js244
-rw-r--r--app/assets/javascripts/vue_pipelines_index/stage.js2
-rw-r--r--app/assets/javascripts/vue_shared/vue_resource_interceptor.js4
-rw-r--r--app/assets/stylesheets/framework/dropdowns.scss26
-rw-r--r--app/assets/stylesheets/framework/highlight.scss9
-rw-r--r--app/assets/stylesheets/framework/nav.scss2
-rw-r--r--app/assets/stylesheets/pages/diff.scss104
-rw-r--r--app/assets/stylesheets/pages/environments.scss8
-rw-r--r--app/assets/stylesheets/pages/merge_requests.scss4
-rw-r--r--app/assets/stylesheets/pages/note_form.scss21
-rw-r--r--app/assets/stylesheets/pages/notes.scss31
-rw-r--r--app/assets/stylesheets/pages/projects.scss3
-rw-r--r--app/controllers/admin/users_controller.rb17
-rw-r--r--app/controllers/application_controller.rb4
-rw-r--r--app/controllers/autocomplete_controller.rb3
-rw-r--r--app/controllers/concerns/authenticates_with_two_factor.rb7
-rw-r--r--app/controllers/dashboard/milestones_controller.rb1
-rw-r--r--app/controllers/groups/milestones_controller.rb1
-rw-r--r--app/controllers/groups_controller.rb2
-rw-r--r--app/controllers/projects/blob_controller.rb7
-rw-r--r--app/controllers/projects/branches_controller.rb13
-rw-r--r--app/controllers/projects/issues_controller.rb37
-rw-r--r--app/controllers/projects/raw_controller.rb2
-rw-r--r--app/controllers/projects/services_controller.rb3
-rw-r--r--app/controllers/projects/settings/members_controller.rb37
-rw-r--r--app/controllers/projects/tags_controller.rb22
-rw-r--r--app/controllers/projects/wikis_controller.rb2
-rw-r--r--app/finders/group_members_finder.rb2
-rw-r--r--app/finders/members_finder.rb40
-rw-r--r--app/helpers/ci_status_helper.rb2
-rw-r--r--app/helpers/issuables_helper.rb19
-rw-r--r--app/helpers/issues_helper.rb14
-rw-r--r--app/helpers/preferences_helper.rb2
-rw-r--r--app/models/ability.rb7
-rw-r--r--app/models/blob.rb8
-rw-r--r--app/models/ci/pipeline.rb2
-rw-r--r--app/models/ci/stage.rb6
-rw-r--r--app/models/concerns/issuable.rb2
-rw-r--r--app/models/concerns/relative_positioning.rb90
-rw-r--r--app/models/global_milestone.rb22
-rw-r--r--app/models/guest.rb2
-rw-r--r--app/models/project_services/issue_tracker_service.rb11
-rw-r--r--app/models/user.rb37
-rw-r--r--app/policies/base_policy.rb9
-rw-r--r--app/policies/global_policy.rb7
-rw-r--r--app/services/concerns/issues/resolve_discussions.rb32
-rw-r--r--app/services/issues/base_service.rb8
-rw-r--r--app/services/issues/build_service.rb34
-rw-r--r--app/services/issues/create_service.rb20
-rw-r--r--app/services/notification_service.rb2
-rw-r--r--app/services/tags/destroy_service.rb2
-rw-r--r--app/validators/namespace_validator.rb3
-rw-r--r--app/views/admin/users/_head.html.haml4
-rw-r--r--app/views/dashboard/issues.atom.builder2
-rw-r--r--app/views/dashboard/issues.html.haml4
-rw-r--r--app/views/dashboard/milestones/index.html.haml10
-rw-r--r--app/views/dashboard/projects/starred.html.haml2
-rw-r--r--app/views/dashboard/todos/_todo.html.haml5
-rw-r--r--app/views/devise/sessions/two_factor.html.haml2
-rw-r--r--app/views/devise/shared/_signup_box.html.haml2
-rw-r--r--app/views/discussions/_diff_discussion.html.haml2
-rw-r--r--app/views/discussions/_new_issue_for_all_discussions.html.haml6
-rw-r--r--app/views/discussions/_new_issue_for_discussion.html.haml8
-rw-r--r--app/views/discussions/_notes.html.haml4
-rw-r--r--app/views/events/_event.atom.builder2
-rw-r--r--app/views/groups/issues.atom.builder2
-rw-r--r--app/views/groups/milestones/index.html.haml2
-rw-r--r--app/views/help/ui.html.haml2
-rw-r--r--app/views/issues/_issue.atom.builder2
-rw-r--r--app/views/layouts/nav/_project.html.haml29
-rw-r--r--app/views/layouts/nav/_project_settings.html.haml24
-rw-r--r--app/views/profiles/two_factor_auths/show.html.haml3
-rw-r--r--app/views/projects/_activity.html.haml1
-rw-r--r--app/views/projects/activity.html.haml1
-rw-r--r--app/views/projects/blob/_actions.html.haml2
-rw-r--r--app/views/projects/blob/_blob.html.haml2
-rw-r--r--app/views/projects/commit/_change.html.haml2
-rw-r--r--app/views/projects/commit/_commit_box.html.haml5
-rw-r--r--app/views/projects/deploy_keys/_index.html.haml2
-rw-r--r--app/views/projects/deployments/_actions.haml5
-rw-r--r--app/views/projects/deployments/_deployment.html.haml2
-rw-r--r--app/views/projects/diffs/_line.html.haml20
-rw-r--r--app/views/projects/diffs/_parallel_view.html.haml21
-rw-r--r--app/views/projects/edit.html.haml1
-rw-r--r--app/views/projects/environments/show.html.haml2
-rw-r--r--app/views/projects/issues/index.atom.builder2
-rw-r--r--app/views/projects/issues/verify.html.haml3
-rw-r--r--app/views/projects/merge_requests/_show.html.haml1
-rw-r--r--app/views/projects/merge_requests/widget/open/_unresolved_discussions.html.haml2
-rw-r--r--app/views/projects/milestones/index.html.haml10
-rw-r--r--app/views/projects/notes/_note.html.haml29
-rw-r--r--app/views/projects/pages/show.html.haml2
-rw-r--r--app/views/projects/settings/_head.html.haml33
-rw-r--r--app/views/projects/settings/ci_cd/show.html.haml1
-rw-r--r--app/views/projects/settings/integrations/show.html.haml1
-rw-r--r--app/views/projects/settings/members/show.html.haml1
-rw-r--r--app/views/projects/settings/repository/show.html.haml1
-rw-r--r--app/views/projects/tags/destroy.js.haml4
-rw-r--r--app/views/shared/_branch_switcher.html.haml8
-rw-r--r--app/views/shared/_issuable_meta_data.html.haml2
-rw-r--r--app/views/shared/_milestones_filter.html.haml12
-rw-r--r--app/views/shared/_new_commit_form.html.haml2
-rw-r--r--app/views/shared/icons/_collapse.svg.erb1
-rw-r--r--app/views/shared/icons/_icon_mr_issue.svg1
-rw-r--r--app/views/shared/issuable/_form.html.haml54
-rw-r--r--app/views/shared/issuable/_search_bar.html.haml34
-rw-r--r--app/views/shared/milestones/_issuables.html.haml2
-rw-r--r--app/views/shared/projects/_project.html.haml2
-rw-r--r--app/views/shared/projects/blob/_branch_page_create.html.haml8
-rw-r--r--app/views/shared/projects/blob/_branch_page_default.html.haml10
-rw-r--r--app/workers/authorized_projects_worker.rb2
207 files changed, 6177 insertions, 5098 deletions
diff --git a/app/assets/javascripts/abuse_reports.js b/app/assets/javascripts/abuse_reports.js
index 8a260aae1b1..346de4ad11e 100644
--- a/app/assets/javascripts/abuse_reports.js
+++ b/app/assets/javascripts/abuse_reports.js
@@ -1,40 +1,37 @@
-/* eslint-disable no-param-reassign */
+const MAX_MESSAGE_LENGTH = 500;
+const MESSAGE_CELL_SELECTOR = '.abuse-reports .message';
-((global) => {
- const MAX_MESSAGE_LENGTH = 500;
- const MESSAGE_CELL_SELECTOR = '.abuse-reports .message';
-
- class AbuseReports {
- constructor() {
- $(MESSAGE_CELL_SELECTOR).each(this.truncateLongMessage);
- $(document)
- .off('click', MESSAGE_CELL_SELECTOR)
- .on('click', MESSAGE_CELL_SELECTOR, this.toggleMessageTruncation);
- }
+class AbuseReports {
+ constructor() {
+ $(MESSAGE_CELL_SELECTOR).each(this.truncateLongMessage);
+ $(document)
+ .off('click', MESSAGE_CELL_SELECTOR)
+ .on('click', MESSAGE_CELL_SELECTOR, this.toggleMessageTruncation);
+ }
- truncateLongMessage() {
- const $messageCellElement = $(this);
- const reportMessage = $messageCellElement.text();
- if (reportMessage.length > MAX_MESSAGE_LENGTH) {
- $messageCellElement.data('original-message', reportMessage);
- $messageCellElement.data('message-truncated', 'true');
- $messageCellElement.text(global.text.truncate(reportMessage, MAX_MESSAGE_LENGTH));
- }
+ truncateLongMessage() {
+ const $messageCellElement = $(this);
+ const reportMessage = $messageCellElement.text();
+ if (reportMessage.length > MAX_MESSAGE_LENGTH) {
+ $messageCellElement.data('original-message', reportMessage);
+ $messageCellElement.data('message-truncated', 'true');
+ $messageCellElement.text(window.gl.text.truncate(reportMessage, MAX_MESSAGE_LENGTH));
}
+ }
- toggleMessageTruncation() {
- const $messageCellElement = $(this);
- const originalMessage = $messageCellElement.data('original-message');
- if (!originalMessage) return;
- if ($messageCellElement.data('message-truncated') === 'true') {
- $messageCellElement.data('message-truncated', 'false');
- $messageCellElement.text(originalMessage);
- } else {
- $messageCellElement.data('message-truncated', 'true');
- $messageCellElement.text(`${originalMessage.substr(0, (MAX_MESSAGE_LENGTH - 3))}...`);
- }
+ toggleMessageTruncation() {
+ const $messageCellElement = $(this);
+ const originalMessage = $messageCellElement.data('original-message');
+ if (!originalMessage) return;
+ if ($messageCellElement.data('message-truncated') === 'true') {
+ $messageCellElement.data('message-truncated', 'false');
+ $messageCellElement.text(originalMessage);
+ } else {
+ $messageCellElement.data('message-truncated', 'true');
+ $messageCellElement.text(`${originalMessage.substr(0, (MAX_MESSAGE_LENGTH - 3))}...`);
}
}
+}
- global.AbuseReports = AbuseReports;
-})(window.gl || (window.gl = {}));
+window.gl = window.gl || {};
+window.gl.AbuseReports = AbuseReports;
diff --git a/app/assets/javascripts/activities.js b/app/assets/javascripts/activities.js
index 648cb4d5d85..aebda7780e1 100644
--- a/app/assets/javascripts/activities.js
+++ b/app/assets/javascripts/activities.js
@@ -2,36 +2,35 @@
/* global Pager */
/* global Cookies */
-((global) => {
- class Activities {
- constructor() {
- Pager.init(20, true, false, this.updateTooltips);
- $('.event-filter-link').on('click', (e) => {
- e.preventDefault();
- this.toggleFilter(e.currentTarget);
- this.reloadActivities();
- });
- }
+class Activities {
+ constructor() {
+ Pager.init(20, true, false, this.updateTooltips);
+ $('.event-filter-link').on('click', (e) => {
+ e.preventDefault();
+ this.toggleFilter(e.currentTarget);
+ this.reloadActivities();
+ });
+ }
- updateTooltips() {
- gl.utils.localTimeAgo($('.js-timeago', '.content_list'));
- }
+ updateTooltips() {
+ gl.utils.localTimeAgo($('.js-timeago', '.content_list'));
+ }
- reloadActivities() {
- $('.content_list').html('');
- Pager.init(20, true, false, this.updateTooltips);
- }
+ reloadActivities() {
+ $('.content_list').html('');
+ Pager.init(20, true, false, this.updateTooltips);
+ }
- toggleFilter(sender) {
- const $sender = $(sender);
- const filter = $sender.attr('id').split('_')[0];
+ toggleFilter(sender) {
+ const $sender = $(sender);
+ const filter = $sender.attr('id').split('_')[0];
- $('.event-filter .active').removeClass('active');
- Cookies.set('event_filter', filter);
+ $('.event-filter .active').removeClass('active');
+ Cookies.set('event_filter', filter);
- $sender.closest('li').toggleClass('active');
- }
+ $sender.closest('li').toggleClass('active');
}
+}
- global.Activities = Activities;
-})(window.gl || (window.gl = {}));
+window.gl = window.gl || {};
+window.gl.Activities = Activities;
diff --git a/app/assets/javascripts/admin.js b/app/assets/javascripts/admin.js
index aaed74d6073..34669dd13d6 100644
--- a/app/assets/javascripts/admin.js
+++ b/app/assets/javascripts/admin.js
@@ -1,64 +1,62 @@
/* 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() {
- function Admin() {
- var modal, showBlacklistType;
- $('input#user_force_random_password').on('change', function(elem) {
- var elems;
- elems = $('#user_password, #user_password_confirmation');
- if ($(this).attr('checked')) {
- return elems.val('').attr('disabled', true);
- } else {
- return elems.removeAttr('disabled');
- }
- });
- $('body').on('click', '.js-toggle-colors-link', function(e) {
- e.preventDefault();
- return $('.js-toggle-colors-container').toggle();
- });
- $('.log-tabs a').click(function(e) {
- e.preventDefault();
- return $(this).tab('show');
- });
- $('.log-bottom').click(function(e) {
- var visible_log;
- e.preventDefault();
- visible_log = $(".file-content:visible");
- return visible_log.animate({
- scrollTop: visible_log.find('ol').height()
- }, "fast");
- });
- modal = $('.change-owner-holder');
- $('.change-owner-link').bind("click", function(e) {
- e.preventDefault();
- $(this).hide();
- return modal.show();
- });
- $('.change-owner-cancel-link').bind("click", function(e) {
- e.preventDefault();
- modal.hide();
- return $('.change-owner-link').show();
- });
- $('li.project_member').bind('ajax:success', function() {
- return gl.utils.refreshCurrentPage();
- });
- $('li.group_member').bind('ajax:success', function() {
- return gl.utils.refreshCurrentPage();
- });
- showBlacklistType = function() {
- if ($("input[name='blacklist_type']:checked").val() === 'file') {
- $('.blacklist-file').show();
- return $('.blacklist-raw').hide();
- } else {
- $('.blacklist-file').hide();
- return $('.blacklist-raw').show();
- }
- };
- $("input[name='blacklist_type']").click(showBlacklistType);
- showBlacklistType();
- }
+window.Admin = (function() {
+ function Admin() {
+ var modal, showBlacklistType;
+ $('input#user_force_random_password').on('change', function(elem) {
+ var elems;
+ elems = $('#user_password, #user_password_confirmation');
+ if ($(this).attr('checked')) {
+ return elems.val('').attr('disabled', true);
+ } else {
+ return elems.removeAttr('disabled');
+ }
+ });
+ $('body').on('click', '.js-toggle-colors-link', function(e) {
+ e.preventDefault();
+ return $('.js-toggle-colors-container').toggle();
+ });
+ $('.log-tabs a').click(function(e) {
+ e.preventDefault();
+ return $(this).tab('show');
+ });
+ $('.log-bottom').click(function(e) {
+ var visible_log;
+ e.preventDefault();
+ visible_log = $(".file-content:visible");
+ return visible_log.animate({
+ scrollTop: visible_log.find('ol').height()
+ }, "fast");
+ });
+ modal = $('.change-owner-holder');
+ $('.change-owner-link').bind("click", function(e) {
+ e.preventDefault();
+ $(this).hide();
+ return modal.show();
+ });
+ $('.change-owner-cancel-link').bind("click", function(e) {
+ e.preventDefault();
+ modal.hide();
+ return $('.change-owner-link').show();
+ });
+ $('li.project_member').bind('ajax:success', function() {
+ return gl.utils.refreshCurrentPage();
+ });
+ $('li.group_member').bind('ajax:success', function() {
+ return gl.utils.refreshCurrentPage();
+ });
+ showBlacklistType = function() {
+ if ($("input[name='blacklist_type']:checked").val() === 'file') {
+ $('.blacklist-file').show();
+ return $('.blacklist-raw').hide();
+ } else {
+ $('.blacklist-file').hide();
+ return $('.blacklist-raw').show();
+ }
+ };
+ $("input[name='blacklist_type']").click(showBlacklistType);
+ showBlacklistType();
+ }
- return Admin;
- })();
-}).call(window);
+ return Admin;
+})();
diff --git a/app/assets/javascripts/api.js b/app/assets/javascripts/api.js
index 86e0ad89431..e5f36c84987 100644
--- a/app/assets/javascripts/api.js
+++ b/app/assets/javascripts/api.js
@@ -1,150 +1,148 @@
/* 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 = {
- groupsPath: "/api/:version/groups.json",
- groupPath: "/api/:version/groups/:id.json",
- namespacesPath: "/api/:version/namespaces.json",
- groupProjectsPath: "/api/:version/groups/:id/projects.json",
- projectsPath: "/api/:version/projects.json?simple=true",
- labelsPath: "/:namespace_path/:project_path/labels",
- licensePath: "/api/:version/templates/licenses/:key",
- gitignorePath: "/api/:version/templates/gitignores/:key",
- gitlabCiYmlPath: "/api/:version/templates/gitlab_ci_ymls/:key",
- dockerfilePath: "/api/:version/templates/dockerfiles/:key",
- issuableTemplatePath: "/:namespace_path/:project_path/templates/:type/:key",
- group: function(group_id, callback) {
- var url = Api.buildUrl(Api.groupPath)
- .replace(':id', group_id);
- return $.ajax({
- url: url,
- dataType: "json"
- }).done(function(group) {
- return callback(group);
- });
- },
- // Return groups list. Filtered by query
- groups: function(query, options, callback) {
- var url = Api.buildUrl(Api.groupsPath);
- return $.ajax({
- url: url,
- data: $.extend({
- search: query,
- per_page: 20
- }, options),
- dataType: "json"
- }).done(function(groups) {
- return callback(groups);
- });
- },
- // Return namespaces list. Filtered by query
- namespaces: function(query, callback) {
- var url = Api.buildUrl(Api.namespacesPath);
- return $.ajax({
- url: url,
- data: {
- search: query,
- per_page: 20
- },
- dataType: "json"
- }).done(function(namespaces) {
- return callback(namespaces);
- });
- },
- // Return projects list. Filtered by query
- projects: function(query, order, callback) {
- var url = Api.buildUrl(Api.projectsPath);
- return $.ajax({
- url: url,
- data: {
- search: query,
- order_by: order,
- per_page: 20
- },
- dataType: "json"
- }).done(function(projects) {
- return callback(projects);
- });
- },
- newLabel: function(namespace_path, project_path, data, callback) {
- var url = Api.buildUrl(Api.labelsPath)
- .replace(':namespace_path', namespace_path)
- .replace(':project_path', project_path);
- return $.ajax({
- url: url,
- type: "POST",
- data: { 'label': data },
- dataType: "json"
- }).done(function(label) {
- return callback(label);
- }).error(function(message) {
- return callback(message.responseJSON);
- });
- },
- // Return group projects list. Filtered by query
- groupProjects: function(group_id, query, callback) {
- var url = Api.buildUrl(Api.groupProjectsPath)
- .replace(':id', group_id);
- return $.ajax({
- url: url,
- data: {
- search: query,
- per_page: 20
- },
- dataType: "json"
- }).done(function(projects) {
- return callback(projects);
- });
- },
- // Return text for a specific license
- licenseText: function(key, data, callback) {
- var url = Api.buildUrl(Api.licensePath)
- .replace(':key', key);
- return $.ajax({
- url: url,
- data: data
- }).done(function(license) {
- return callback(license);
- });
- },
- gitignoreText: function(key, callback) {
- var url = Api.buildUrl(Api.gitignorePath)
- .replace(':key', key);
- return $.get(url, function(gitignore) {
- return callback(gitignore);
- });
- },
- gitlabCiYml: function(key, callback) {
- var url = Api.buildUrl(Api.gitlabCiYmlPath)
- .replace(':key', key);
- return $.get(url, function(file) {
- return callback(file);
- });
- },
- dockerfileYml: function(key, callback) {
- var url = Api.buildUrl(Api.dockerfilePath).replace(':key', key);
- $.get(url, callback);
- },
- issueTemplate: function(namespacePath, projectPath, key, type, callback) {
- var url = Api.buildUrl(Api.issuableTemplatePath)
- .replace(':key', key)
- .replace(':type', type)
- .replace(':project_path', projectPath)
- .replace(':namespace_path', namespacePath);
- $.ajax({
- url: url,
- dataType: 'json'
- }).done(function(file) {
- callback(null, file);
- }).error(callback);
- },
- buildUrl: function(url) {
- if (gon.relative_url_root != null) {
- url = gon.relative_url_root + url;
- }
- return url.replace(':version', gon.api_version);
+var Api = {
+ groupsPath: "/api/:version/groups.json",
+ groupPath: "/api/:version/groups/:id.json",
+ namespacesPath: "/api/:version/namespaces.json",
+ groupProjectsPath: "/api/:version/groups/:id/projects.json",
+ projectsPath: "/api/:version/projects.json?simple=true",
+ labelsPath: "/:namespace_path/:project_path/labels",
+ licensePath: "/api/:version/templates/licenses/:key",
+ gitignorePath: "/api/:version/templates/gitignores/:key",
+ gitlabCiYmlPath: "/api/:version/templates/gitlab_ci_ymls/:key",
+ dockerfilePath: "/api/:version/templates/dockerfiles/:key",
+ issuableTemplatePath: "/:namespace_path/:project_path/templates/:type/:key",
+ group: function(group_id, callback) {
+ var url = Api.buildUrl(Api.groupPath)
+ .replace(':id', group_id);
+ return $.ajax({
+ url: url,
+ dataType: "json"
+ }).done(function(group) {
+ return callback(group);
+ });
+ },
+ // Return groups list. Filtered by query
+ groups: function(query, options, callback) {
+ var url = Api.buildUrl(Api.groupsPath);
+ return $.ajax({
+ url: url,
+ data: $.extend({
+ search: query,
+ per_page: 20
+ }, options),
+ dataType: "json"
+ }).done(function(groups) {
+ return callback(groups);
+ });
+ },
+ // Return namespaces list. Filtered by query
+ namespaces: function(query, callback) {
+ var url = Api.buildUrl(Api.namespacesPath);
+ return $.ajax({
+ url: url,
+ data: {
+ search: query,
+ per_page: 20
+ },
+ dataType: "json"
+ }).done(function(namespaces) {
+ return callback(namespaces);
+ });
+ },
+ // Return projects list. Filtered by query
+ projects: function(query, options, callback) {
+ var url = Api.buildUrl(Api.projectsPath);
+ return $.ajax({
+ url: url,
+ data: $.extend({
+ search: query,
+ per_page: 20,
+ membership: true
+ }, options),
+ dataType: "json"
+ }).done(function(projects) {
+ return callback(projects);
+ });
+ },
+ newLabel: function(namespace_path, project_path, data, callback) {
+ var url = Api.buildUrl(Api.labelsPath)
+ .replace(':namespace_path', namespace_path)
+ .replace(':project_path', project_path);
+ return $.ajax({
+ url: url,
+ type: "POST",
+ data: { 'label': data },
+ dataType: "json"
+ }).done(function(label) {
+ return callback(label);
+ }).error(function(message) {
+ return callback(message.responseJSON);
+ });
+ },
+ // Return group projects list. Filtered by query
+ groupProjects: function(group_id, query, callback) {
+ var url = Api.buildUrl(Api.groupProjectsPath)
+ .replace(':id', group_id);
+ return $.ajax({
+ url: url,
+ data: {
+ search: query,
+ per_page: 20
+ },
+ dataType: "json"
+ }).done(function(projects) {
+ return callback(projects);
+ });
+ },
+ // Return text for a specific license
+ licenseText: function(key, data, callback) {
+ var url = Api.buildUrl(Api.licensePath)
+ .replace(':key', key);
+ return $.ajax({
+ url: url,
+ data: data
+ }).done(function(license) {
+ return callback(license);
+ });
+ },
+ gitignoreText: function(key, callback) {
+ var url = Api.buildUrl(Api.gitignorePath)
+ .replace(':key', key);
+ return $.get(url, function(gitignore) {
+ return callback(gitignore);
+ });
+ },
+ gitlabCiYml: function(key, callback) {
+ var url = Api.buildUrl(Api.gitlabCiYmlPath)
+ .replace(':key', key);
+ return $.get(url, function(file) {
+ return callback(file);
+ });
+ },
+ dockerfileYml: function(key, callback) {
+ var url = Api.buildUrl(Api.dockerfilePath).replace(':key', key);
+ $.get(url, callback);
+ },
+ issueTemplate: function(namespacePath, projectPath, key, type, callback) {
+ var url = Api.buildUrl(Api.issuableTemplatePath)
+ .replace(':key', key)
+ .replace(':type', type)
+ .replace(':project_path', projectPath)
+ .replace(':namespace_path', namespacePath);
+ $.ajax({
+ url: url,
+ dataType: 'json'
+ }).done(function(file) {
+ callback(null, file);
+ }).error(callback);
+ },
+ buildUrl: function(url) {
+ if (gon.relative_url_root != null) {
+ url = gon.relative_url_root + url;
}
- };
+ return url.replace(':version', gon.api_version);
+ }
+};
- window.Api = Api;
-}).call(window);
+window.Api = Api;
diff --git a/app/assets/javascripts/aside.js b/app/assets/javascripts/aside.js
index 448e6e2cc78..88756884d16 100644
--- a/app/assets/javascripts/aside.js
+++ b/app/assets/javascripts/aside.js
@@ -1,25 +1,24 @@
/* 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() {
- $(document).off("click", "a.show-aside");
- $(document).on("click", 'a.show-aside', function(e) {
- var btn, icon;
- e.preventDefault();
- btn = $(e.currentTarget);
- icon = btn.find('i');
- if (icon.hasClass('fa-angle-left')) {
- btn.parent().find('section').hide();
- btn.parent().find('aside').fadeIn();
- return icon.removeClass('fa-angle-left').addClass('fa-angle-right');
- } else {
- btn.parent().find('aside').hide();
- btn.parent().find('section').fadeIn();
- return icon.removeClass('fa-angle-right').addClass('fa-angle-left');
- }
- });
- }
- return Aside;
- })();
-}).call(window);
+window.Aside = (function() {
+ function Aside() {
+ $(document).off("click", "a.show-aside");
+ $(document).on("click", 'a.show-aside', function(e) {
+ var btn, icon;
+ e.preventDefault();
+ btn = $(e.currentTarget);
+ icon = btn.find('i');
+ if (icon.hasClass('fa-angle-left')) {
+ btn.parent().find('section').hide();
+ btn.parent().find('aside').fadeIn();
+ return icon.removeClass('fa-angle-left').addClass('fa-angle-right');
+ } else {
+ btn.parent().find('aside').hide();
+ btn.parent().find('section').fadeIn();
+ return icon.removeClass('fa-angle-right').addClass('fa-angle-left');
+ }
+ });
+ }
+
+ return Aside;
+})();
diff --git a/app/assets/javascripts/autosave.js b/app/assets/javascripts/autosave.js
index e55405135fb..8630b18a73f 100644
--- a/app/assets/javascripts/autosave.js
+++ b/app/assets/javascripts/autosave.js
@@ -1,62 +1,61 @@
/* 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) {
- this.field = field;
- if (key.join != null) {
- key = key.join("/");
- }
- this.key = "autosave/" + key;
- this.field.data("autosave", this);
- this.restore();
- this.field.on("input", (function(_this) {
- return function() {
- return _this.save();
- };
- })(this));
- }
- Autosave.prototype.restore = function() {
- var e, text;
- if (window.localStorage == null) {
- return;
- }
- try {
- text = window.localStorage.getItem(this.key);
- } catch (error) {
- e = error;
- return;
- }
- if ((text != null ? text.length : void 0) > 0) {
- this.field.val(text);
- }
- return this.field.trigger("input");
- };
+window.Autosave = (function() {
+ function Autosave(field, key) {
+ this.field = field;
+ if (key.join != null) {
+ key = key.join("/");
+ }
+ this.key = "autosave/" + key;
+ this.field.data("autosave", this);
+ this.restore();
+ this.field.on("input", (function(_this) {
+ return function() {
+ return _this.save();
+ };
+ })(this));
+ }
- Autosave.prototype.save = function() {
- var text;
- if (window.localStorage == null) {
- return;
- }
- text = this.field.val();
- if ((text != null ? text.length : void 0) > 0) {
- try {
- return window.localStorage.setItem(this.key, text);
- } catch (error) {}
- } else {
- return this.reset();
- }
- };
+ Autosave.prototype.restore = function() {
+ var e, text;
+ if (window.localStorage == null) {
+ return;
+ }
+ try {
+ text = window.localStorage.getItem(this.key);
+ } catch (error) {
+ e = error;
+ return;
+ }
+ if ((text != null ? text.length : void 0) > 0) {
+ this.field.val(text);
+ }
+ return this.field.trigger("input");
+ };
- Autosave.prototype.reset = function() {
- if (window.localStorage == null) {
- return;
- }
+ Autosave.prototype.save = function() {
+ var text;
+ if (window.localStorage == null) {
+ return;
+ }
+ text = this.field.val();
+ if ((text != null ? text.length : void 0) > 0) {
try {
- return window.localStorage.removeItem(this.key);
+ return window.localStorage.setItem(this.key, text);
} catch (error) {}
- };
+ } else {
+ return this.reset();
+ }
+ };
+
+ Autosave.prototype.reset = function() {
+ if (window.localStorage == null) {
+ return;
+ }
+ try {
+ return window.localStorage.removeItem(this.key);
+ } catch (error) {}
+ };
- return Autosave;
- })();
-}).call(window);
+ return Autosave;
+})();
diff --git a/app/assets/javascripts/awards_handler.js b/app/assets/javascripts/awards_handler.js
index 4667980a960..8a077f0081a 100644
--- a/app/assets/javascripts/awards_handler.js
+++ b/app/assets/javascripts/awards_handler.js
@@ -1,10 +1,8 @@
/* global Cookies */
-const emojiMap = require('emoji-map');
-const emojiAliases = require('emoji-aliases');
-const glEmoji = require('./behaviors/gl_emoji');
-
-const glEmojiTag = glEmoji.glEmojiTag;
+import emojiMap from 'emojis/digests.json';
+import emojiAliases from 'emojis/aliases.json';
+import { glEmojiTag } from './behaviors/gl_emoji';
const animationEndEventString = 'animationend webkitAnimationEnd MSAnimationEnd oAnimationEnd';
const requestAnimationFrame = window.requestAnimationFrame ||
@@ -47,12 +45,12 @@ function buildCategoryMap() {
});
}
-function renderCategory(name, emojiList) {
+function renderCategory(name, emojiList, opts = {}) {
return `
<h5 class="emoji-menu-title">
${name}
</h5>
- <ul class="clearfix emoji-menu-list">
+ <ul class="clearfix emoji-menu-list ${opts.menuListClass}">
${emojiList.map(emojiName => `
<li class="emoji-menu-list-item">
<button class="emoji-menu-btn text-center js-emoji-btn" type="button">
@@ -142,9 +140,6 @@ AwardsHandler.prototype.showEmojiMenu = function showEmojiMenu($addBtn) {
const $createdMenu = $('.emoji-menu');
$addBtn.removeClass('is-loading');
this.positionMenu($createdMenu, $addBtn);
- if (!this.frequentEmojiBlockRendered) {
- this.renderFrequentlyUsedBlock();
- }
return setTimeout(() => {
$createdMenu.addClass('is-visible');
$('#emoji_search').focus();
@@ -167,11 +162,21 @@ AwardsHandler.prototype.createEmojiMenu = function createEmojiMenu(callback) {
const emojisInCategory = categoryMap[categoryNameKey];
const firstCategory = renderCategory(categoryLabelMap[categoryNameKey], emojisInCategory);
+ // Render the frequently used
+ const frequentlyUsedEmojis = this.getFrequentlyUsedEmojis();
+ let frequentlyUsedCatgegory = '';
+ if (frequentlyUsedEmojis.length > 0) {
+ frequentlyUsedCatgegory = renderCategory('Frequently used', frequentlyUsedEmojis, {
+ menuListClass: 'frequent-emojis',
+ });
+ }
+
const emojiMenuMarkup = `
<div class="emoji-menu">
<input type="text" name="emoji_search" id="emoji_search" value="" class="emoji-search search-input form-control" placeholder="Search emoji" />
<div class="emoji-menu-content">
+ ${frequentlyUsedCatgegory}
${firstCategory}
</div>
</div>
@@ -459,19 +464,6 @@ AwardsHandler.prototype.getFrequentlyUsedEmojis = function getFrequentlyUsedEmoj
return _.compact(_.uniq(frequentlyUsedEmojis));
};
-AwardsHandler.prototype.renderFrequentlyUsedBlock = function renderFrequentlyUsedBlock() {
- if (Cookies.get('frequently_used_emojis')) {
- const frequentlyUsedEmojis = this.getFrequentlyUsedEmojis();
- const ul = $('<ul class="clearfix emoji-menu-list frequent-emojis">');
- for (let i = 0, len = frequentlyUsedEmojis.length; i < len; i += 1) {
- const emoji = frequentlyUsedEmojis[i];
- $(`.emoji-menu-content [data-name="${emoji}"]`).closest('li').clone().appendTo(ul);
- }
- $('.emoji-menu-content').prepend(ul).prepend($('<h5>').text('Frequently used'));
- }
- this.frequentEmojiBlockRendered = true;
-};
-
AwardsHandler.prototype.setupSearch = function setupSearch() {
this.registerEventListener('on', $('input.emoji-search'), 'input', (e) => {
const term = $(e.target).val().trim();
@@ -515,4 +507,4 @@ AwardsHandler.prototype.destroy = function destroy() {
$('.emoji-menu').remove();
};
-module.exports = AwardsHandler;
+export default AwardsHandler;
diff --git a/app/assets/javascripts/behaviors/gl_emoji.js b/app/assets/javascripts/behaviors/gl_emoji.js
index d1d98c3919f..59741cc9b1a 100644
--- a/app/assets/javascripts/behaviors/gl_emoji.js
+++ b/app/assets/javascripts/behaviors/gl_emoji.js
@@ -1,11 +1,13 @@
-const installCustomElements = require('document-register-element');
-const emojiMap = require('emoji-map');
-const emojiAliases = require('emoji-aliases');
-const generatedUnicodeSupportMap = require('./gl_emoji/unicode_support_map');
-const spreadString = require('./gl_emoji/spread_string');
+import installCustomElements from 'document-register-element';
+import emojiMap from 'emojis/digests.json';
+import emojiAliases from 'emojis/aliases.json';
+import { getUnicodeSupportMap } from './gl_emoji/unicode_support_map';
+import { isEmojiUnicodeSupported } from './gl_emoji/is_emoji_unicode_supported';
installCustomElements(window);
+const generatedUnicodeSupportMap = getUnicodeSupportMap();
+
function emojiImageTag(name, src) {
return `<img class="emoji" title=":${name}:" alt=":${name}:" src="${src}" width="20" height="20" align="absmiddle" />`;
}
@@ -55,163 +57,49 @@ function glEmojiTag(inputName, options) {
`;
}
-// On Windows, flags render as two-letter country codes, see http://emojipedia.org/flags/
-const flagACodePoint = 127462; // parseInt('1F1E6', 16)
-const flagZCodePoint = 127487; // parseInt('1F1FF', 16)
-function isFlagEmoji(emojiUnicode) {
- const cp = emojiUnicode.codePointAt(0);
- // Length 4 because flags are made of 2 characters which are surrogate pairs
- return emojiUnicode.length === 4 && cp >= flagACodePoint && cp <= flagZCodePoint;
-}
-
-// Chrome <57 renders keycaps oddly
-// See https://bugs.chromium.org/p/chromium/issues/detail?id=632294
-// Same issue on Windows also fixed in Chrome 57, http://i.imgur.com/rQF7woO.png
-function isKeycapEmoji(emojiUnicode) {
- return emojiUnicode.length === 3 && emojiUnicode[2] === '\u20E3';
-}
-
-// Check for a skin tone variation emoji which aren't always supported
-const tone1 = 127995;// parseInt('1F3FB', 16)
-const tone5 = 127999;// parseInt('1F3FF', 16)
-function isSkinToneComboEmoji(emojiUnicode) {
- return emojiUnicode.length > 2 && spreadString(emojiUnicode).some((char) => {
- const cp = char.codePointAt(0);
- return cp >= tone1 && cp <= tone5;
- });
-}
-
-// macOS supports most skin tone emoji's but
-// doesn't support the skin tone versions of horse racing
-const horseRacingCodePoint = 127943;// parseInt('1F3C7', 16)
-function isHorceRacingSkinToneComboEmoji(emojiUnicode) {
- return spreadString(emojiUnicode)[0].codePointAt(0) === horseRacingCodePoint &&
- isSkinToneComboEmoji(emojiUnicode);
-}
-
-// Check for `family_*`, `kiss_*`, `couple_*`
-// For ex. Windows 8.1 Firefox 51.0.1, doesn't support these
-const zwj = 8205; // parseInt('200D', 16)
-const personStartCodePoint = 128102; // parseInt('1F466', 16)
-const personEndCodePoint = 128105; // parseInt('1F469', 16)
-function isPersonZwjEmoji(emojiUnicode) {
- let hasPersonEmoji = false;
- let hasZwj = false;
- spreadString(emojiUnicode).forEach((character) => {
- const cp = character.codePointAt(0);
- if (cp === zwj) {
- hasZwj = true;
- } else if (cp >= personStartCodePoint && cp <= personEndCodePoint) {
- hasPersonEmoji = true;
+function installGlEmojiElement() {
+ const GlEmojiElementProto = Object.create(HTMLElement.prototype);
+ GlEmojiElementProto.createdCallback = function createdCallback() {
+ const emojiUnicode = this.textContent.trim();
+ const {
+ name,
+ unicodeVersion,
+ fallbackSrc,
+ fallbackSpriteClass,
+ } = this.dataset;
+
+ const isEmojiUnicode = this.childNodes && Array.prototype.every.call(
+ this.childNodes,
+ childNode => childNode.nodeType === 3,
+ );
+ const hasImageFallback = fallbackSrc && fallbackSrc.length > 0;
+ const hasCssSpriteFalback = fallbackSpriteClass && fallbackSpriteClass.length > 0;
+
+ if (
+ isEmojiUnicode &&
+ !isEmojiUnicodeSupported(generatedUnicodeSupportMap, emojiUnicode, unicodeVersion)
+ ) {
+ // CSS sprite fallback takes precedence over image fallback
+ if (hasCssSpriteFalback) {
+ // IE 11 doesn't like adding multiple at once :(
+ this.classList.add('emoji-icon');
+ this.classList.add(fallbackSpriteClass);
+ } else if (hasImageFallback) {
+ this.innerHTML = emojiImageTag(name, fallbackSrc);
+ } else {
+ const src = assembleFallbackImageSrc(name);
+ this.innerHTML = emojiImageTag(name, src);
+ }
}
- });
-
- return hasPersonEmoji && hasZwj;
-}
-
-// Helper so we don't have to run `isFlagEmoji` twice
-// in `isEmojiUnicodeSupported` logic
-function checkFlagEmojiSupport(unicodeSupportMap, emojiUnicode) {
- const isFlagResult = isFlagEmoji(emojiUnicode);
- return (
- (unicodeSupportMap.flag && isFlagResult) ||
- !isFlagResult
- );
-}
-
-// Helper so we don't have to run `isSkinToneComboEmoji` twice
-// in `isEmojiUnicodeSupported` logic
-function checkSkinToneModifierSupport(unicodeSupportMap, emojiUnicode) {
- const isSkinToneResult = isSkinToneComboEmoji(emojiUnicode);
- return (
- (unicodeSupportMap.skinToneModifier && isSkinToneResult) ||
- !isSkinToneResult
- );
-}
-
-// Helper func so we don't have to run `isHorceRacingSkinToneComboEmoji` twice
-// in `isEmojiUnicodeSupported` logic
-function checkHorseRacingSkinToneComboEmojiSupport(unicodeSupportMap, emojiUnicode) {
- const isHorseRacingSkinToneResult = isHorceRacingSkinToneComboEmoji(emojiUnicode);
- return (
- (unicodeSupportMap.horseRacing && isHorseRacingSkinToneResult) ||
- !isHorseRacingSkinToneResult
- );
-}
-
-// Helper so we don't have to run `isPersonZwjEmoji` twice
-// in `isEmojiUnicodeSupported` logic
-function checkPersonEmojiSupport(unicodeSupportMap, emojiUnicode) {
- const isPersonZwjResult = isPersonZwjEmoji(emojiUnicode);
- return (
- (unicodeSupportMap.personZwj && isPersonZwjResult) ||
- !isPersonZwjResult
- );
-}
-
-// Takes in a support map and determines whether
-// the given unicode emoji is supported on the platform.
-//
-// Combines all the edge case tests into a one-stop shop method
-function isEmojiUnicodeSupported(unicodeSupportMap = {}, emojiUnicode, unicodeVersion) {
- const isOlderThanChrome57 = unicodeSupportMap.meta && unicodeSupportMap.meta.isChrome &&
- unicodeSupportMap.meta.chromeVersion < 57;
+ };
- // For comments about each scenario, see the comments above each individual respective function
- return unicodeSupportMap[unicodeVersion] &&
- !(isOlderThanChrome57 && isKeycapEmoji(emojiUnicode)) &&
- checkFlagEmojiSupport(unicodeSupportMap, emojiUnicode) &&
- checkSkinToneModifierSupport(unicodeSupportMap, emojiUnicode) &&
- checkHorseRacingSkinToneComboEmojiSupport(unicodeSupportMap, emojiUnicode) &&
- checkPersonEmojiSupport(unicodeSupportMap, emojiUnicode);
+ document.registerElement('gl-emoji', {
+ prototype: GlEmojiElementProto,
+ });
}
-const GlEmojiElementProto = Object.create(HTMLElement.prototype);
-GlEmojiElementProto.createdCallback = function createdCallback() {
- const emojiUnicode = this.textContent.trim();
- const {
- name,
- unicodeVersion,
- fallbackSrc,
- fallbackSpriteClass,
- } = this.dataset;
-
- const isEmojiUnicode = this.childNodes && Array.prototype.every.call(
- this.childNodes,
- childNode => childNode.nodeType === 3,
- );
- const hasImageFallback = fallbackSrc && fallbackSrc.length > 0;
- const hasCssSpriteFalback = fallbackSpriteClass && fallbackSpriteClass.length > 0;
-
- if (
- isEmojiUnicode &&
- !isEmojiUnicodeSupported(generatedUnicodeSupportMap, emojiUnicode, unicodeVersion)
- ) {
- // CSS sprite fallback takes precedence over image fallback
- if (hasCssSpriteFalback) {
- // IE 11 doesn't like adding multiple at once :(
- this.classList.add('emoji-icon');
- this.classList.add(fallbackSpriteClass);
- } else if (hasImageFallback) {
- this.innerHTML = emojiImageTag(name, fallbackSrc);
- } else {
- const src = assembleFallbackImageSrc(name);
- this.innerHTML = emojiImageTag(name, src);
- }
- }
-};
-
-document.registerElement('gl-emoji', {
- prototype: GlEmojiElementProto,
-});
-
-module.exports = {
- emojiImageTag,
+export {
+ installGlEmojiElement,
glEmojiTag,
- isEmojiUnicodeSupported,
- isFlagEmoji,
- isKeycapEmoji,
- isSkinToneComboEmoji,
- isHorceRacingSkinToneComboEmoji,
- isPersonZwjEmoji,
+ emojiImageTag,
};
diff --git a/app/assets/javascripts/behaviors/gl_emoji/is_emoji_unicode_supported.js b/app/assets/javascripts/behaviors/gl_emoji/is_emoji_unicode_supported.js
new file mode 100644
index 00000000000..5e3c45f7e92
--- /dev/null
+++ b/app/assets/javascripts/behaviors/gl_emoji/is_emoji_unicode_supported.js
@@ -0,0 +1,121 @@
+import spreadString from './spread_string';
+
+// On Windows, flags render as two-letter country codes, see http://emojipedia.org/flags/
+const flagACodePoint = 127462; // parseInt('1F1E6', 16)
+const flagZCodePoint = 127487; // parseInt('1F1FF', 16)
+function isFlagEmoji(emojiUnicode) {
+ const cp = emojiUnicode.codePointAt(0);
+ // Length 4 because flags are made of 2 characters which are surrogate pairs
+ return emojiUnicode.length === 4 && cp >= flagACodePoint && cp <= flagZCodePoint;
+}
+
+// Chrome <57 renders keycaps oddly
+// See https://bugs.chromium.org/p/chromium/issues/detail?id=632294
+// Same issue on Windows also fixed in Chrome 57, http://i.imgur.com/rQF7woO.png
+function isKeycapEmoji(emojiUnicode) {
+ return emojiUnicode.length === 3 && emojiUnicode[2] === '\u20E3';
+}
+
+// Check for a skin tone variation emoji which aren't always supported
+const tone1 = 127995;// parseInt('1F3FB', 16)
+const tone5 = 127999;// parseInt('1F3FF', 16)
+function isSkinToneComboEmoji(emojiUnicode) {
+ return emojiUnicode.length > 2 && spreadString(emojiUnicode).some((char) => {
+ const cp = char.codePointAt(0);
+ return cp >= tone1 && cp <= tone5;
+ });
+}
+
+// macOS supports most skin tone emoji's but
+// doesn't support the skin tone versions of horse racing
+const horseRacingCodePoint = 127943;// parseInt('1F3C7', 16)
+function isHorceRacingSkinToneComboEmoji(emojiUnicode) {
+ return spreadString(emojiUnicode)[0].codePointAt(0) === horseRacingCodePoint &&
+ isSkinToneComboEmoji(emojiUnicode);
+}
+
+// Check for `family_*`, `kiss_*`, `couple_*`
+// For ex. Windows 8.1 Firefox 51.0.1, doesn't support these
+const zwj = 8205; // parseInt('200D', 16)
+const personStartCodePoint = 128102; // parseInt('1F466', 16)
+const personEndCodePoint = 128105; // parseInt('1F469', 16)
+function isPersonZwjEmoji(emojiUnicode) {
+ let hasPersonEmoji = false;
+ let hasZwj = false;
+ spreadString(emojiUnicode).forEach((character) => {
+ const cp = character.codePointAt(0);
+ if (cp === zwj) {
+ hasZwj = true;
+ } else if (cp >= personStartCodePoint && cp <= personEndCodePoint) {
+ hasPersonEmoji = true;
+ }
+ });
+
+ return hasPersonEmoji && hasZwj;
+}
+
+// Helper so we don't have to run `isFlagEmoji` twice
+// in `isEmojiUnicodeSupported` logic
+function checkFlagEmojiSupport(unicodeSupportMap, emojiUnicode) {
+ const isFlagResult = isFlagEmoji(emojiUnicode);
+ return (
+ (unicodeSupportMap.flag && isFlagResult) ||
+ !isFlagResult
+ );
+}
+
+// Helper so we don't have to run `isSkinToneComboEmoji` twice
+// in `isEmojiUnicodeSupported` logic
+function checkSkinToneModifierSupport(unicodeSupportMap, emojiUnicode) {
+ const isSkinToneResult = isSkinToneComboEmoji(emojiUnicode);
+ return (
+ (unicodeSupportMap.skinToneModifier && isSkinToneResult) ||
+ !isSkinToneResult
+ );
+}
+
+// Helper func so we don't have to run `isHorceRacingSkinToneComboEmoji` twice
+// in `isEmojiUnicodeSupported` logic
+function checkHorseRacingSkinToneComboEmojiSupport(unicodeSupportMap, emojiUnicode) {
+ const isHorseRacingSkinToneResult = isHorceRacingSkinToneComboEmoji(emojiUnicode);
+ return (
+ (unicodeSupportMap.horseRacing && isHorseRacingSkinToneResult) ||
+ !isHorseRacingSkinToneResult
+ );
+}
+
+// Helper so we don't have to run `isPersonZwjEmoji` twice
+// in `isEmojiUnicodeSupported` logic
+function checkPersonEmojiSupport(unicodeSupportMap, emojiUnicode) {
+ const isPersonZwjResult = isPersonZwjEmoji(emojiUnicode);
+ return (
+ (unicodeSupportMap.personZwj && isPersonZwjResult) ||
+ !isPersonZwjResult
+ );
+}
+
+// Takes in a support map and determines whether
+// the given unicode emoji is supported on the platform.
+//
+// Combines all the edge case tests into a one-stop shop method
+function isEmojiUnicodeSupported(unicodeSupportMap = {}, emojiUnicode, unicodeVersion) {
+ const isOlderThanChrome57 = unicodeSupportMap.meta && unicodeSupportMap.meta.isChrome &&
+ unicodeSupportMap.meta.chromeVersion < 57;
+
+ // For comments about each scenario, see the comments above each individual respective function
+ return unicodeSupportMap[unicodeVersion] &&
+ !(isOlderThanChrome57 && isKeycapEmoji(emojiUnicode)) &&
+ checkFlagEmojiSupport(unicodeSupportMap, emojiUnicode) &&
+ checkSkinToneModifierSupport(unicodeSupportMap, emojiUnicode) &&
+ checkHorseRacingSkinToneComboEmojiSupport(unicodeSupportMap, emojiUnicode) &&
+ checkPersonEmojiSupport(unicodeSupportMap, emojiUnicode);
+}
+
+export {
+ isEmojiUnicodeSupported,
+ isFlagEmoji,
+ isKeycapEmoji,
+ isSkinToneComboEmoji,
+ isHorceRacingSkinToneComboEmoji,
+ isPersonZwjEmoji,
+};
diff --git a/app/assets/javascripts/behaviors/gl_emoji/spread_string.js b/app/assets/javascripts/behaviors/gl_emoji/spread_string.js
index 2380349c4fa..327764ec6e9 100644
--- a/app/assets/javascripts/behaviors/gl_emoji/spread_string.js
+++ b/app/assets/javascripts/behaviors/gl_emoji/spread_string.js
@@ -47,4 +47,4 @@ function spreadString(str) {
return arr;
}
-module.exports = spreadString;
+export default spreadString;
diff --git a/app/assets/javascripts/behaviors/gl_emoji/unicode_support_map.js b/app/assets/javascripts/behaviors/gl_emoji/unicode_support_map.js
index f31716d4c07..aa522e20c36 100644
--- a/app/assets/javascripts/behaviors/gl_emoji/unicode_support_map.js
+++ b/app/assets/javascripts/behaviors/gl_emoji/unicode_support_map.js
@@ -68,7 +68,7 @@ const chromeVersion = chromeMatches && chromeMatches[1] && parseInt(chromeMatche
// See 32px, https://i.imgur.com/htY6Zym.png
// See 16px, https://i.imgur.com/FPPsIF8.png
const fontSize = 16;
-function testUnicodeSupportMap(testMap) {
+function generateUnicodeSupportMap(testMap) {
const testMapKeys = Object.keys(testMap);
const numTestEntries = testMapKeys
.reduce((list, testKey) => list.concat(testMap[testKey]), []).length;
@@ -138,17 +138,24 @@ function testUnicodeSupportMap(testMap) {
return resultMap;
}
-let unicodeSupportMap;
-const userAgentFromCache = window.localStorage.getItem('gl-emoji-user-agent');
-try {
- unicodeSupportMap = JSON.parse(window.localStorage.getItem('gl-emoji-unicode-support-map'));
-} catch (err) {
- // swallow
-}
-if (!unicodeSupportMap || userAgentFromCache !== navigator.userAgent) {
- unicodeSupportMap = testUnicodeSupportMap(unicodeSupportTestMap);
- window.localStorage.setItem('gl-emoji-user-agent', navigator.userAgent);
- window.localStorage.setItem('gl-emoji-unicode-support-map', JSON.stringify(unicodeSupportMap));
+function getUnicodeSupportMap() {
+ let unicodeSupportMap;
+ const userAgentFromCache = window.localStorage.getItem('gl-emoji-user-agent');
+ try {
+ unicodeSupportMap = JSON.parse(window.localStorage.getItem('gl-emoji-unicode-support-map'));
+ } catch (err) {
+ // swallow
+ }
+ if (!unicodeSupportMap || userAgentFromCache !== navigator.userAgent) {
+ unicodeSupportMap = generateUnicodeSupportMap(unicodeSupportTestMap);
+ window.localStorage.setItem('gl-emoji-user-agent', navigator.userAgent);
+ window.localStorage.setItem('gl-emoji-unicode-support-map', JSON.stringify(unicodeSupportMap));
+ }
+
+ return unicodeSupportMap;
}
-module.exports = unicodeSupportMap;
+export {
+ getUnicodeSupportMap,
+ generateUnicodeSupportMap,
+};
diff --git a/app/assets/javascripts/behaviors/quick_submit.js b/app/assets/javascripts/behaviors/quick_submit.js
index a7e68ae5cb9..626f3503c91 100644
--- a/app/assets/javascripts/behaviors/quick_submit.js
+++ b/app/assets/javascripts/behaviors/quick_submit.js
@@ -6,7 +6,7 @@
// "Meta+Enter" (Mac) or "Ctrl+Enter" (Linux/Windows) key combination, the form
// is submitted.
//
-require('../extensions/jquery');
+import '../commons/bootstrap';
//
// ### Example Markup
diff --git a/app/assets/javascripts/behaviors/requires_input.js b/app/assets/javascripts/behaviors/requires_input.js
index 6b21695d082..eb7143f5b1a 100644
--- a/app/assets/javascripts/behaviors/requires_input.js
+++ b/app/assets/javascripts/behaviors/requires_input.js
@@ -4,7 +4,7 @@
// 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');
+import '../commons/bootstrap';
//
// ### Example Markup
diff --git a/app/assets/javascripts/blob/blob_file_dropzone.js b/app/assets/javascripts/blob/blob_file_dropzone.js
index 5f14ff40eee..8f6bf162d6e 100644
--- a/app/assets/javascripts/blob/blob_file_dropzone.js
+++ b/app/assets/javascripts/blob/blob_file_dropzone.js
@@ -36,7 +36,7 @@
this.removeFile(file);
});
return this.on('sending', function(file, xhr, formData) {
- formData.append('target_branch', form.find('.js-target-branch').val());
+ formData.append('target_branch', form.find('input[name="target_branch"]').val());
formData.append('create_merge_request', form.find('.js-create-merge-request').val());
formData.append('commit_message', form.find('.js-commit-message').val());
});
diff --git a/app/assets/javascripts/blob/blob_line_permalink_updater.js b/app/assets/javascripts/blob/blob_line_permalink_updater.js
new file mode 100644
index 00000000000..c8f68860fbd
--- /dev/null
+++ b/app/assets/javascripts/blob/blob_line_permalink_updater.js
@@ -0,0 +1,35 @@
+const lineNumberRe = /^L[0-9]+/;
+
+const updateLineNumbersOnBlobPermalinks = (linksToUpdate) => {
+ const hash = gl.utils.getLocationHash();
+ if (hash && lineNumberRe.test(hash)) {
+ const hashUrlString = `#${hash}`;
+
+ [].concat(Array.prototype.slice.call(linksToUpdate)).forEach((permalinkButton) => {
+ const baseHref = permalinkButton.getAttribute('data-original-href') || (() => {
+ const href = permalinkButton.getAttribute('href');
+ permalinkButton.setAttribute('data-original-href', href);
+ return href;
+ })();
+ permalinkButton.setAttribute('href', `${baseHref}${hashUrlString}`);
+ });
+ }
+};
+
+function BlobLinePermalinkUpdater(blobContentHolder, lineNumberSelector, elementsToUpdate) {
+ const updateBlameAndBlobPermalinkCb = () => {
+ // Wait for the hash to update from the LineHighlighter callback
+ setTimeout(() => {
+ updateLineNumbersOnBlobPermalinks(elementsToUpdate);
+ }, 0);
+ };
+
+ blobContentHolder.addEventListener('click', (e) => {
+ if (e.target.matches(lineNumberSelector)) {
+ updateBlameAndBlobPermalinkCb();
+ }
+ });
+ updateBlameAndBlobPermalinkCb();
+}
+
+export default BlobLinePermalinkUpdater;
diff --git a/app/assets/javascripts/blob/create_branch_dropdown.js b/app/assets/javascripts/blob/create_branch_dropdown.js
new file mode 100644
index 00000000000..95517f51b1c
--- /dev/null
+++ b/app/assets/javascripts/blob/create_branch_dropdown.js
@@ -0,0 +1,88 @@
+class CreateBranchDropdown {
+ constructor(el, targetBranchDropdown) {
+ this.targetBranchDropdown = targetBranchDropdown;
+ this.el = el;
+ this.dropdownBack = this.el.closest('.dropdown').querySelector('.dropdown-menu-back');
+ this.cancelButton = this.el.querySelector('.js-cancel-branch-btn');
+ this.newBranchField = this.el.querySelector('#new_branch_name');
+ this.newBranchCreateButton = this.el.querySelector('.js-new-branch-btn');
+
+ this.newBranchCreateButton.setAttribute('disabled', '');
+
+ this.addBindings();
+ this.cleanupWrapper = this.cleanup.bind(this);
+ document.addEventListener('beforeunload', this.cleanupWrapper);
+ }
+
+ cleanup() {
+ this.cleanBindings();
+ document.removeEventListener('beforeunload', this.cleanupWrapper);
+ }
+
+ cleanBindings() {
+ this.newBranchField.removeEventListener('keyup', this.enableBranchCreateButtonWrapper);
+ this.newBranchField.removeEventListener('change', this.enableBranchCreateButtonWrapper);
+ this.newBranchField.removeEventListener('keydown', this.handleNewBranchKeydownWrapper);
+ this.dropdownBack.removeEventListener('click', this.resetFormWrapper);
+ this.cancelButton.removeEventListener('click', this.handleCancelClickWrapper);
+ this.newBranchCreateButton.removeEventListener('click', this.createBranchWrapper);
+ }
+
+ addBindings() {
+ this.enableBranchCreateButtonWrapper = this.enableBranchCreateButton.bind(this);
+ this.handleNewBranchKeydownWrapper = this.handleNewBranchKeydown.bind(this);
+ this.resetFormWrapper = this.resetForm.bind(this);
+ this.handleCancelClickWrapper = this.handleCancelClick.bind(this);
+ this.createBranchWrapper = this.createBranch.bind(this);
+
+ this.newBranchField.addEventListener('keyup', this.enableBranchCreateButtonWrapper);
+ this.newBranchField.addEventListener('change', this.enableBranchCreateButtonWrapper);
+ this.newBranchField.addEventListener('keydown', this.handleNewBranchKeydownWrapper);
+ this.dropdownBack.addEventListener('click', this.resetFormWrapper);
+ this.cancelButton.addEventListener('click', this.handleCancelClickWrapper);
+ this.newBranchCreateButton.addEventListener('click', this.createBranchWrapper);
+ }
+
+ handleCancelClick(e) {
+ e.preventDefault();
+ e.stopPropagation();
+
+ this.resetForm();
+ this.dropdownBack.click();
+ }
+
+ handleNewBranchKeydown(e) {
+ const keyCode = e.which;
+ const ENTER_KEYCODE = 13;
+ if (keyCode === ENTER_KEYCODE) {
+ this.createBranch(e);
+ }
+ }
+
+ enableBranchCreateButton() {
+ if (this.newBranchField.value !== '') {
+ this.newBranchCreateButton.removeAttribute('disabled');
+ } else {
+ this.newBranchCreateButton.setAttribute('disabled', '');
+ }
+ }
+
+ resetForm() {
+ this.newBranchField.value = '';
+ this.enableBranchCreateButtonWrapper();
+ }
+
+ createBranch(e) {
+ e.preventDefault();
+
+ if (this.newBranchCreateButton.getAttribute('disabled') === '') {
+ return;
+ }
+ const newBranchName = this.newBranchField.value;
+ this.targetBranchDropdown.setNewBranch(newBranchName);
+ this.resetForm();
+ }
+}
+
+window.gl = window.gl || {};
+gl.CreateBranchDropdown = CreateBranchDropdown;
diff --git a/app/assets/javascripts/blob/target_branch_dropdown.js b/app/assets/javascripts/blob/target_branch_dropdown.js
new file mode 100644
index 00000000000..216f069ef71
--- /dev/null
+++ b/app/assets/javascripts/blob/target_branch_dropdown.js
@@ -0,0 +1,152 @@
+/* eslint-disable class-methods-use-this */
+const SELECT_ITEM_MSG = 'Select';
+
+class TargetBranchDropDown {
+ constructor(dropdown) {
+ this.dropdown = dropdown;
+ this.$dropdown = $(dropdown);
+ this.fieldName = this.dropdown.getAttribute('data-field-name');
+ this.form = this.dropdown.closest('form');
+ this.createDropdown();
+ }
+
+ static bootstrap() {
+ const dropdowns = document.querySelectorAll('.js-project-branches-dropdown');
+ [].forEach.call(dropdowns, dropdown => new TargetBranchDropDown(dropdown));
+ }
+
+ createDropdown() {
+ const self = this;
+ this.$dropdown.glDropdown({
+ selectable: true,
+ filterable: true,
+ search: {
+ fields: ['title'],
+ },
+ data: (term, callback) => $.ajax({
+ url: self.dropdown.getAttribute('data-refs-url'),
+ data: {
+ ref: self.dropdown.getAttribute('data-ref'),
+ show_all: true,
+ },
+ dataType: 'json',
+ }).done(refs => callback(self.dropdownData(refs))),
+ toggleLabel(item, el) {
+ if (el.is('.is-active')) {
+ return item.text;
+ }
+ return SELECT_ITEM_MSG;
+ },
+ clicked(item, el, e) {
+ e.preventDefault();
+ self.onClick.call(self);
+ },
+ fieldName: self.fieldName,
+ });
+ return new gl.CreateBranchDropdown(this.form.querySelector('.dropdown-new-branch'), this);
+ }
+
+ onClick() {
+ this.enableSubmit();
+ this.$dropdown.trigger('change.branch');
+ }
+
+ enableSubmit() {
+ const submitBtn = this.form.querySelector('[type="submit"]');
+ if (this.branchInput && this.branchInput.value) {
+ submitBtn.removeAttribute('disabled');
+ } else {
+ submitBtn.setAttribute('disabled', '');
+ }
+ }
+
+ dropdownData(refs) {
+ const branchList = this.dropdownItems(refs);
+ this.cachedRefs = refs;
+ this.addDefaultBranch(branchList);
+ this.addNewBranch(branchList);
+ return { Branches: branchList };
+ }
+
+ dropdownItems(refs) {
+ return refs.map(this.dropdownItem);
+ }
+
+ dropdownItem(ref) {
+ return { id: ref, text: ref, title: ref };
+ }
+
+ addDefaultBranch(branchList) {
+ // when no branch is selected do nothing
+ if (!this.branchInput) {
+ return;
+ }
+
+ const branchInputVal = this.branchInput.value;
+ const currentBranchIndex = this.searchBranch(branchList, branchInputVal);
+
+ if (currentBranchIndex === -1) {
+ this.unshiftBranch(branchList, this.dropdownItem(branchInputVal));
+ }
+ }
+
+ addNewBranch(branchList) {
+ if (this.newBranch) {
+ this.unshiftBranch(branchList, this.newBranch);
+ }
+ }
+
+ searchBranch(branchList, branchName) {
+ return _.findIndex(branchList, el => branchName === el.id);
+ }
+
+ unshiftBranch(branchList, branch) {
+ const branchIndex = this.searchBranch(branchList, branch.id);
+
+ if (branchIndex === -1) {
+ branchList.unshift(branch);
+ }
+ }
+
+ setNewBranch(newBranchName) {
+ this.newBranch = this.dropdownItem(newBranchName);
+ this.refreshData();
+ this.selectBranch(this.searchBranch(this.glDropdown.fullData.Branches, newBranchName));
+ }
+
+ refreshData() {
+ this.glDropdown.fullData = this.dropdownData(this.cachedRefs);
+ this.clearFilter();
+ }
+
+ clearFilter() {
+ // apply an empty filter in order to refresh the data
+ this.glDropdown.filter.filter('');
+ this.dropdown.closest('.dropdown').querySelector('.dropdown-page-one .dropdown-input-field').value = '';
+ }
+
+ selectBranch(index) {
+ const branch = this.dropdown.closest('.dropdown').querySelectorAll('li a')[index];
+
+ if (!branch.classList.contains('is-active')) {
+ branch.click();
+ } else {
+ this.closeDropdown();
+ }
+ }
+
+ closeDropdown() {
+ this.dropdown.closest('.dropdown').querySelector('.dropdown-menu-close').click();
+ }
+
+ get branchInput() {
+ return this.form.querySelector(`input[name="${this.fieldName}"]`);
+ }
+
+ get glDropdown() {
+ return this.$dropdown.data('glDropdown');
+ }
+}
+
+window.gl = window.gl || {};
+gl.TargetBranchDropDown = TargetBranchDropDown;
diff --git a/app/assets/javascripts/breakpoints.js b/app/assets/javascripts/breakpoints.js
index 22e93328548..2c1f988d987 100644
--- a/app/assets/javascripts/breakpoints.js
+++ b/app/assets/javascripts/breakpoints.js
@@ -1,72 +1,66 @@
/* 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() {
- var BreakpointInstance, instance;
+var Breakpoints = (function() {
+ var BreakpointInstance, instance;
- function Breakpoints() {}
+ function Breakpoints() {}
- instance = null;
+ instance = null;
- BreakpointInstance = (function() {
- var BREAKPOINTS;
+ BreakpointInstance = (function() {
+ var BREAKPOINTS;
- BREAKPOINTS = ["xs", "sm", "md", "lg"];
+ BREAKPOINTS = ["xs", "sm", "md", "lg"];
- function BreakpointInstance() {
- this.setup();
- }
-
- BreakpointInstance.prototype.setup = function() {
- var allDeviceSelector, els;
- allDeviceSelector = BREAKPOINTS.map(function(breakpoint) {
- return ".device-" + breakpoint;
- });
- if ($(allDeviceSelector.join(",")).length) {
- return;
- }
- // Create all the elements
- els = $.map(BREAKPOINTS, function(breakpoint) {
- return "<div class='device-" + breakpoint + " visible-" + breakpoint + "'></div>";
- });
- return $("body").append(els.join(''));
- };
+ function BreakpointInstance() {
+ this.setup();
+ }
- BreakpointInstance.prototype.visibleDevice = function() {
- var allDeviceSelector;
- allDeviceSelector = BREAKPOINTS.map(function(breakpoint) {
- return ".device-" + breakpoint;
- });
- return $(allDeviceSelector.join(",")).filter(":visible");
- };
-
- 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();
- }
- $visibleDevice = this.visibleDevice();
- return $visibleDevice.attr("class").split("visible-")[1];
- };
+ BreakpointInstance.prototype.setup = function() {
+ var allDeviceSelector, els;
+ allDeviceSelector = BREAKPOINTS.map(function(breakpoint) {
+ return ".device-" + breakpoint;
+ });
+ if ($(allDeviceSelector.join(",")).length) {
+ return;
+ }
+ // Create all the elements
+ els = $.map(BREAKPOINTS, function(breakpoint) {
+ return "<div class='device-" + breakpoint + " visible-" + breakpoint + "'></div>";
+ });
+ return $("body").append(els.join(''));
+ };
- return BreakpointInstance;
- })();
+ BreakpointInstance.prototype.visibleDevice = function() {
+ var allDeviceSelector;
+ allDeviceSelector = BREAKPOINTS.map(function(breakpoint) {
+ return ".device-" + breakpoint;
+ });
+ return $(allDeviceSelector.join(",")).filter(":visible");
+ };
- Breakpoints.get = function() {
- return instance != null ? instance : instance = new BreakpointInstance;
+ 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();
+ }
+ $visibleDevice = this.visibleDevice();
+ return $visibleDevice.attr("class").split("visible-")[1];
};
- return Breakpoints;
+ return BreakpointInstance;
})();
- $((function(_this) {
- return function() {
- return _this.bp = Breakpoints.get();
- };
- })(this));
+ Breakpoints.get = function() {
+ return instance != null ? instance : instance = new BreakpointInstance;
+ };
+
+ return Breakpoints;
+})();
+
+$(() => { window.bp = Breakpoints.get(); });
- window.Breakpoints = Breakpoints;
-}).call(window);
+window.Breakpoints = Breakpoints;
diff --git a/app/assets/javascripts/broadcast_message.js b/app/assets/javascripts/broadcast_message.js
index e8531c43b4b..f73e489e7b2 100644
--- a/app/assets/javascripts/broadcast_message.js
+++ b/app/assets/javascripts/broadcast_message.js
@@ -1,34 +1,33 @@
/* 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;
- $('input#broadcast_message_color').on('input', function() {
- var previewColor;
- previewColor = $(this).val();
- return $('div.broadcast-message-preview').css('background-color', previewColor);
- });
- $('input#broadcast_message_font').on('input', function() {
- var previewColor;
- previewColor = $(this).val();
- return $('div.broadcast-message-preview').css('color', previewColor);
- });
- previewPath = $('textarea#broadcast_message_message').data('preview-path');
- return $('textarea#broadcast_message_message').on('input', function() {
- var message;
- message = $(this).val();
- if (message === '') {
- return $('.js-broadcast-message-preview').text("Your message here");
- } else {
- return $.ajax({
- url: previewPath,
- type: "POST",
- data: {
- broadcast_message: {
- message: message
- }
+
+$(function() {
+ var previewPath;
+ $('input#broadcast_message_color').on('input', function() {
+ var previewColor;
+ previewColor = $(this).val();
+ return $('div.broadcast-message-preview').css('background-color', previewColor);
+ });
+ $('input#broadcast_message_font').on('input', function() {
+ var previewColor;
+ previewColor = $(this).val();
+ return $('div.broadcast-message-preview').css('color', previewColor);
+ });
+ previewPath = $('textarea#broadcast_message_message').data('preview-path');
+ return $('textarea#broadcast_message_message').on('input', function() {
+ var message;
+ message = $(this).val();
+ if (message === '') {
+ return $('.js-broadcast-message-preview').text("Your message here");
+ } else {
+ return $.ajax({
+ url: previewPath,
+ type: "POST",
+ data: {
+ broadcast_message: {
+ message: message
}
- });
- }
- });
+ }
+ });
+ }
});
-}).call(window);
+});
diff --git a/app/assets/javascripts/build.js b/app/assets/javascripts/build.js
index 6e6e9b18686..6efd26ccc37 100644
--- a/app/assets/javascripts/build.js
+++ b/app/assets/javascripts/build.js
@@ -1,285 +1,283 @@
/* 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 */
-(function() {
- 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.timeout = null;
-
- Build.state = null;
-
- function Build(options) {
- options = options || $('.js-build-options').data();
- this.pageUrl = options.pageUrl;
- this.buildUrl = options.buildUrl;
- this.buildStatus = options.buildStatus;
- this.state = options.logState;
- 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');
-
- clearTimeout(Build.timeout);
- // Init breakpoint checker
- this.bp = Breakpoints.get();
-
- this.initSidebar();
- this.$buildScroll = $('#js-build-scroll');
-
- this.populateJobs(this.buildStage);
- this.updateStageDropdownText(this.buildStage);
- this.sidebarOnResize();
-
- 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();
- if ($('#build-trace').length) {
- this.getInitialBuildTrace();
- this.initScrollButtonAffix();
- }
- this.invokeBuildTrace();
+var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; };
+var AUTO_SCROLL_OFFSET = 75;
+var DOWN_BUILD_TRACE = '#down-build-trace';
+
+window.Build = (function() {
+ Build.timeout = null;
+
+ Build.state = null;
+
+ function Build(options) {
+ options = options || $('.js-build-options').data();
+ this.pageUrl = options.pageUrl;
+ this.buildUrl = options.buildUrl;
+ this.buildStatus = options.buildStatus;
+ this.state = options.logState;
+ 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');
+
+ clearTimeout(Build.timeout);
+ // Init breakpoint checker
+ this.bp = Breakpoints.get();
+
+ this.initSidebar();
+ this.$buildScroll = $('#js-build-scroll');
+
+ this.populateJobs(this.buildStage);
+ this.updateStageDropdownText(this.buildStage);
+ this.sidebarOnResize();
+
+ 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();
+ if ($('#build-trace').length) {
+ this.getInitialBuildTrace();
+ this.initScrollButtonAffix();
}
-
- Build.prototype.initSidebar = function() {
- this.$sidebar = $('.js-build-sidebar');
- this.$sidebar.niceScroll();
- this.$document.off('click', '.js-sidebar-build-toggle').on('click', '.js-sidebar-build-toggle', this.toggleSidebar);
- };
-
- Build.prototype.location = function() {
- return window.location.href.split("#")[0];
- };
-
- Build.prototype.invokeBuildTrace = function() {
- var continueRefreshStatuses = ['running', 'pending'];
- // Continue to update build trace when build is running or pending
- if (continueRefreshStatuses.indexOf(this.buildStatus) !== -1) {
- // Check for new build output if user still watching build page
- // Only valid for runnig build when output changes during time
- Build.timeout = setTimeout((function(_this) {
- return function() {
- if (_this.location() === _this.pageUrl) {
- return _this.getBuildTrace();
- }
- };
- })(this), 4000);
- }
- };
-
- Build.prototype.getInitialBuildTrace = function() {
- 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());
+ this.invokeBuildTrace();
+ }
+
+ Build.prototype.initSidebar = function() {
+ this.$sidebar = $('.js-build-sidebar');
+ this.$sidebar.niceScroll();
+ this.$document.off('click', '.js-sidebar-build-toggle').on('click', '.js-sidebar-build-toggle', this.toggleSidebar);
+ };
+
+ Build.prototype.location = function() {
+ return window.location.href.split("#")[0];
+ };
+
+ Build.prototype.invokeBuildTrace = function() {
+ var continueRefreshStatuses = ['running', 'pending'];
+ // Continue to update build trace when build is running or pending
+ if (continueRefreshStatuses.indexOf(this.buildStatus) !== -1) {
+ // Check for new build output if user still watching build page
+ // Only valid for runnig build when output changes during time
+ Build.timeout = setTimeout((function(_this) {
+ return function() {
+ if (_this.location() === _this.pageUrl) {
+ return _this.getBuildTrace();
}
- if (removeRefreshStatuses.indexOf(buildData.status) !== -1) {
- this.$buildRefreshAnimation.remove();
- return this.initScrollMonitor();
+ };
+ })(this), 4000);
+ }
+ };
+
+ Build.prototype.getInitialBuildTrace = function() {
+ 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) !== -1) {
+ this.$buildRefreshAnimation.remove();
+ return this.initScrollMonitor();
+ }
+ }.bind(this)
+ });
+ };
+
+ Build.prototype.getBuildTrace = function() {
+ return $.ajax({
+ url: this.pageUrl + "/trace.json?state=" + (encodeURIComponent(this.state)),
+ dataType: "json",
+ success: (function(_this) {
+ return function(log) {
+ var pageUrl;
+
+ if (log.state) {
+ _this.state = log.state;
}
- }.bind(this)
- });
- };
-
- Build.prototype.getBuildTrace = function() {
- return $.ajax({
- url: this.pageUrl + "/trace.json?state=" + (encodeURIComponent(this.state)),
- dataType: "json",
- success: (function(_this) {
- return function(log) {
- var pageUrl;
-
- if (log.state) {
- _this.state = log.state;
+ _this.invokeBuildTrace();
+ if (log.status === "running") {
+ if (log.append) {
+ $('.js-build-output').append(log.html);
+ } else {
+ $('.js-build-output').html(log.html);
}
- _this.invokeBuildTrace();
- if (log.status === "running") {
- if (log.append) {
- $('.js-build-output').append(log.html);
- } else {
- $('.js-build-output').html(log.html);
- }
- return _this.checkAutoscroll();
- } else if (log.status !== _this.buildStatus) {
- pageUrl = _this.pageUrl;
- if (_this.$autoScrollStatus.data('state') === 'enabled') {
- pageUrl += DOWN_BUILD_TRACE;
- }
-
- return gl.utils.visitUrl(pageUrl);
+ return _this.checkAutoscroll();
+ } else if (log.status !== _this.buildStatus) {
+ pageUrl = _this.pageUrl;
+ if (_this.$autoScrollStatus.data('state') === 'enabled') {
+ pageUrl += DOWN_BUILD_TRACE;
}
- };
- })(this)
- });
- };
-
- Build.prototype.checkAutoscroll = function() {
- 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() {
- // 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
+ return gl.utils.visitUrl(pageUrl);
+ }
+ };
+ })(this)
+ });
+ };
+
+ Build.prototype.checkAutoscroll = function() {
+ if (this.$autoScrollStatus.data("state") === "enabled") {
+ return $("html,body").scrollTop(this.$buildTrace.height());
+ }
- this.$scrollTopBtn.hide();
+ // 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() {
+ // 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 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
+ } 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))) {
- // Build Log height is small
+ }
+ } 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.hide();
+ this.$scrollTopBtn.hide();
+ this.$scrollBottomBtn.show();
- // Hide Autoscroll Status Indicator
- this.$autoScrollContainer.hide();
- this.$autoScrollStatusText.removeClass('animate');
- }
+ 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
- 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() {
- var bootstrapBreakpoint;
- bootstrapBreakpoint = this.bp.getBreakpointSize();
- return bootstrapBreakpoint === 'xs' || bootstrapBreakpoint === 'sm';
- };
-
- Build.prototype.toggleSidebar = function(shouldHide) {
- var shouldShow = typeof shouldHide === 'boolean' ? !shouldHide : undefined;
- this.$buildScroll.toggleClass('sidebar-expanded', shouldShow)
- .toggleClass('sidebar-collapsed', shouldHide);
- this.$sidebar.toggleClass('right-sidebar-expanded', shouldShow)
- .toggleClass('right-sidebar-collapsed', shouldHide);
- };
-
- Build.prototype.sidebarOnResize = function() {
- this.toggleSidebar(this.shouldHideSidebarForViewport());
- };
-
- Build.prototype.sidebarOnClick = function() {
- if (this.shouldHideSidebarForViewport()) this.toggleSidebar();
- };
-
- Build.prototype.updateArtifactRemoveDate = function() {
- var $date, date;
- $date = $('.js-artifacts-remove');
- if ($date.length) {
- date = $date.text();
- return $date.text(gl.utils.timeFor(new Date(date.replace(/([0-9]+)-([0-9]+)-([0-9]+)/g, '$1/$2/$3')), ' '));
- }
- };
-
- Build.prototype.populateJobs = function(stage) {
- $('.build-job').hide();
- $('.build-job[data-stage="' + stage + '"]').show();
- };
-
- Build.prototype.updateStageDropdownText = function(stage) {
- $('.stage-selection').text(stage);
- };
-
- Build.prototype.updateDropdown = function(e) {
- e.preventDefault();
- var stage = e.currentTarget.text;
- this.updateStageDropdownText(stage);
- this.populateJobs(stage);
- };
-
- Build.prototype.stepTrace = function(e) {
- var $currentTarget;
- e.preventDefault();
- $currentTarget = $(e.currentTarget);
- $.scrollTo($currentTarget.attr('href'), {
- offset: 0
- });
- };
-
- return Build;
- })();
-}).call(window);
+ 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() {
+ var bootstrapBreakpoint;
+ bootstrapBreakpoint = this.bp.getBreakpointSize();
+ return bootstrapBreakpoint === 'xs' || bootstrapBreakpoint === 'sm';
+ };
+
+ Build.prototype.toggleSidebar = function(shouldHide) {
+ var shouldShow = typeof shouldHide === 'boolean' ? !shouldHide : undefined;
+ this.$buildScroll.toggleClass('sidebar-expanded', shouldShow)
+ .toggleClass('sidebar-collapsed', shouldHide);
+ this.$sidebar.toggleClass('right-sidebar-expanded', shouldShow)
+ .toggleClass('right-sidebar-collapsed', shouldHide);
+ };
+
+ Build.prototype.sidebarOnResize = function() {
+ this.toggleSidebar(this.shouldHideSidebarForViewport());
+ };
+
+ Build.prototype.sidebarOnClick = function() {
+ if (this.shouldHideSidebarForViewport()) this.toggleSidebar();
+ };
+
+ Build.prototype.updateArtifactRemoveDate = function() {
+ var $date, date;
+ $date = $('.js-artifacts-remove');
+ if ($date.length) {
+ date = $date.text();
+ return $date.text(gl.utils.timeFor(new Date(date.replace(/([0-9]+)-([0-9]+)-([0-9]+)/g, '$1/$2/$3')), ' '));
+ }
+ };
+
+ Build.prototype.populateJobs = function(stage) {
+ $('.build-job').hide();
+ $('.build-job[data-stage="' + stage + '"]').show();
+ };
+
+ Build.prototype.updateStageDropdownText = function(stage) {
+ $('.stage-selection').text(stage);
+ };
+
+ Build.prototype.updateDropdown = function(e) {
+ e.preventDefault();
+ var stage = e.currentTarget.text;
+ this.updateStageDropdownText(stage);
+ this.populateJobs(stage);
+ };
+
+ Build.prototype.stepTrace = function(e) {
+ var $currentTarget;
+ e.preventDefault();
+ $currentTarget = $(e.currentTarget);
+ $.scrollTo($currentTarget.attr('href'), {
+ offset: 0
+ });
+ };
+
+ return Build;
+})();
diff --git a/app/assets/javascripts/build_artifacts.js b/app/assets/javascripts/build_artifacts.js
index cae9a0ffca4..bd479700fd3 100644
--- a/app/assets/javascripts/build_artifacts.js
+++ b/app/assets/javascripts/build_artifacts.js
@@ -1,26 +1,25 @@
/* 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() {
- this.disablePropagation();
- this.setupEntryClick();
- }
- BuildArtifacts.prototype.disablePropagation = function() {
- $('.top-block').on('click', '.download', function(e) {
- return e.stopPropagation();
- });
- return $('.tree-holder').on('click', 'tr[data-link] a', function(e) {
- return e.stopImmediatePropagation();
- });
- };
+window.BuildArtifacts = (function() {
+ function BuildArtifacts() {
+ this.disablePropagation();
+ this.setupEntryClick();
+ }
- BuildArtifacts.prototype.setupEntryClick = function() {
- return $('.tree-holder').on('click', 'tr[data-link]', function(e) {
- return window.location = this.dataset.link;
- });
- };
+ BuildArtifacts.prototype.disablePropagation = function() {
+ $('.top-block').on('click', '.download', function(e) {
+ return e.stopPropagation();
+ });
+ return $('.tree-holder').on('click', 'tr[data-link] a', function(e) {
+ return e.stopImmediatePropagation();
+ });
+ };
- return BuildArtifacts;
- })();
-}).call(window);
+ BuildArtifacts.prototype.setupEntryClick = function() {
+ return $('.tree-holder').on('click', 'tr[data-link]', function(e) {
+ return window.location = this.dataset.link;
+ });
+ };
+
+ return BuildArtifacts;
+})();
diff --git a/app/assets/javascripts/ci_lint_editor.js b/app/assets/javascripts/ci_lint_editor.js
index 56ffaa765a8..dd4a08a2f31 100644
--- a/app/assets/javascripts/ci_lint_editor.js
+++ b/app/assets/javascripts/ci_lint_editor.js
@@ -1,18 +1,17 @@
-(() => {
- window.gl = window.gl || {};
- class CILintEditor {
- constructor() {
- this.editor = window.ace.edit('ci-editor');
- this.textarea = document.querySelector('#content');
+window.gl = window.gl || {};
- this.editor.getSession().setMode('ace/mode/yaml');
- this.editor.on('input', () => {
- const content = this.editor.getSession().getValue();
- this.textarea.value = content;
- });
- }
+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;
-})();
+gl.CILintEditor = CILintEditor;
diff --git a/app/assets/javascripts/commit.js b/app/assets/javascripts/commit.js
index 566b322eb49..5f637524e30 100644
--- a/app/assets/javascripts/commit.js
+++ b/app/assets/javascripts/commit.js
@@ -1,14 +1,12 @@
/* eslint-disable func-names, space-before-function-paren, wrap-iife */
/* global CommitFile */
-(function() {
- this.Commit = (function() {
- function Commit() {
- $('.files .diff-file').each(function() {
- return new CommitFile(this);
- });
- }
+window.Commit = (function() {
+ function Commit() {
+ $('.files .diff-file').each(function() {
+ return new CommitFile(this);
+ });
+ }
- return Commit;
- })();
-}).call(window);
+ return Commit;
+})();
diff --git a/app/assets/javascripts/commits.js b/app/assets/javascripts/commits.js
index ccd895f3bf4..e3f9eaaf39c 100644
--- a/app/assets/javascripts/commits.js
+++ b/app/assets/javascripts/commits.js
@@ -1,68 +1,66 @@
/* 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() {
- var CommitsList = {};
+window.CommitsList = (function() {
+ var CommitsList = {};
- CommitsList.timer = null;
+ CommitsList.timer = null;
- CommitsList.init = function(limit) {
- $("body").on("click", ".day-commits-table li.commit", function(e) {
- if (e.target.nodeName !== "A") {
- location.href = $(this).attr("url");
- e.stopPropagation();
- return false;
- }
- });
- Pager.init(limit, false, false, function() {
- gl.utils.localTimeAgo($('.js-timeago'));
- });
- this.content = $("#commits-list");
- this.searchField = $("#commits-search");
- this.lastSearch = this.searchField.val();
- return this.initSearch();
- };
+ CommitsList.init = function(limit) {
+ $("body").on("click", ".day-commits-table li.commit", function(e) {
+ if (e.target.nodeName !== "A") {
+ location.href = $(this).attr("url");
+ e.stopPropagation();
+ return false;
+ }
+ });
+ Pager.init(limit, false, false, function() {
+ gl.utils.localTimeAgo($('.js-timeago'));
+ });
+ this.content = $("#commits-list");
+ this.searchField = $("#commits-search");
+ this.lastSearch = this.searchField.val();
+ return this.initSearch();
+ };
- CommitsList.initSearch = function() {
- this.timer = null;
- return this.searchField.keyup((function(_this) {
- return function() {
- clearTimeout(_this.timer);
- return _this.timer = setTimeout(_this.filterResults, 500);
- };
- })(this));
- };
+ CommitsList.initSearch = function() {
+ this.timer = null;
+ return this.searchField.keyup((function(_this) {
+ return function() {
+ clearTimeout(_this.timer);
+ return _this.timer = setTimeout(_this.filterResults, 500);
+ };
+ })(this));
+ };
- CommitsList.filterResults = function() {
- 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({
- type: "GET",
- url: form.attr("action"),
- data: form.serialize(),
- complete: function() {
- 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"
- });
- };
+ CommitsList.filterResults = function() {
+ 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({
+ type: "GET",
+ url: form.attr("action"),
+ data: form.serialize(),
+ complete: function() {
+ 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(window);
+ return CommitsList;
+})();
diff --git a/app/assets/javascripts/commons/bootstrap.js b/app/assets/javascripts/commons/bootstrap.js
index db0cbfd87c3..36bfe457be9 100644
--- a/app/assets/javascripts/commons/bootstrap.js
+++ b/app/assets/javascripts/commons/bootstrap.js
@@ -1,4 +1,4 @@
-import 'jquery';
+import $ from 'jquery';
// bootstrap jQuery plugins
import 'bootstrap-sass/assets/javascripts/bootstrap/affix';
@@ -8,3 +8,9 @@ import 'bootstrap-sass/assets/javascripts/bootstrap/modal';
import 'bootstrap-sass/assets/javascripts/bootstrap/tab';
import 'bootstrap-sass/assets/javascripts/bootstrap/transition';
import 'bootstrap-sass/assets/javascripts/bootstrap/tooltip';
+
+// custom jQuery functions
+$.fn.extend({
+ disable() { return $(this).attr('disabled', 'disabled').addClass('disabled'); },
+ enable() { return $(this).removeAttr('disabled').removeClass('disabled'); },
+});
diff --git a/app/assets/javascripts/commons/index.js b/app/assets/javascripts/commons/index.js
index 72ede1d621a..7063f59d446 100644
--- a/app/assets/javascripts/commons/index.js
+++ b/app/assets/javascripts/commons/index.js
@@ -1,2 +1,3 @@
+import './polyfills';
import './jquery';
import './bootstrap';
diff --git a/app/assets/javascripts/commons/polyfills.js b/app/assets/javascripts/commons/polyfills.js
new file mode 100644
index 00000000000..fbd0db64ca7
--- /dev/null
+++ b/app/assets/javascripts/commons/polyfills.js
@@ -0,0 +1,10 @@
+// ECMAScript polyfills
+import 'core-js/fn/array/find';
+import 'core-js/fn/object/assign';
+import 'core-js/fn/promise';
+import 'core-js/fn/string/code-point-at';
+import 'core-js/fn/string/from-code-point';
+
+// Browser polyfills
+import './polyfills/custom_event';
+import './polyfills/element';
diff --git a/app/assets/javascripts/commons/polyfills/custom_event.js b/app/assets/javascripts/commons/polyfills/custom_event.js
new file mode 100644
index 00000000000..aea61b82d03
--- /dev/null
+++ b/app/assets/javascripts/commons/polyfills/custom_event.js
@@ -0,0 +1,9 @@
+if (typeof window.CustomEvent !== 'function') {
+ window.CustomEvent = function CustomEvent(event, params) {
+ const evt = document.createEvent('CustomEvent');
+ const evtParams = params || { bubbles: false, cancelable: false, detail: undefined };
+ evt.initCustomEvent(event, evtParams.bubbles, evtParams.cancelable, evtParams.detail);
+ return evt;
+ };
+ window.CustomEvent.prototype = Event;
+}
diff --git a/app/assets/javascripts/commons/polyfills/element.js b/app/assets/javascripts/commons/polyfills/element.js
new file mode 100644
index 00000000000..9a1f73bf2ac
--- /dev/null
+++ b/app/assets/javascripts/commons/polyfills/element.js
@@ -0,0 +1,20 @@
+Element.prototype.closest = Element.prototype.closest ||
+ function closest(selector, selectedElement = this) {
+ if (!selectedElement) return null;
+ return selectedElement.matches(selector) ?
+ selectedElement :
+ Element.prototype.closest(selector, selectedElement.parentElement);
+ };
+
+Element.prototype.matches = Element.prototype.matches ||
+ Element.prototype.matchesSelector ||
+ Element.prototype.mozMatchesSelector ||
+ Element.prototype.msMatchesSelector ||
+ Element.prototype.oMatchesSelector ||
+ Element.prototype.webkitMatchesSelector ||
+ function matches(selector) {
+ const elms = (this.document || this.ownerDocument).querySelectorAll(selector);
+ let i = elms.length - 1;
+ while (i >= 0 && elms.item(i) !== this) { i -= 1; }
+ return i > -1;
+ };
diff --git a/app/assets/javascripts/compare.js b/app/assets/javascripts/compare.js
index 15df105d4cc..9e5dbd64a7e 100644
--- a/app/assets/javascripts/compare.js
+++ b/app/assets/javascripts/compare.js
@@ -1,91 +1,90 @@
/* 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) {
- this.opts = opts;
- this.source_loading = $(".js-source-loading");
- this.target_loading = $(".js-target-loading");
- $('.js-compare-dropdown').each((function(_this) {
- return function(i, dropdown) {
- var $dropdown;
- $dropdown = $(dropdown);
- return $dropdown.glDropdown({
- selectable: true,
- fieldName: $dropdown.data('field-name'),
- filterable: true,
- id: function(obj, $el) {
- return $el.data('id');
- },
- toggleLabel: function(obj, $el) {
- return $el.text().trim();
- },
- clicked: function(e, el) {
- if ($dropdown.is('.js-target-branch')) {
- return _this.getTargetHtml();
- } else if ($dropdown.is('.js-source-branch')) {
- return _this.getSourceHtml();
- } else if ($dropdown.is('.js-target-project')) {
- return _this.getTargetProject();
- }
+
+window.Compare = (function() {
+ function Compare(opts) {
+ this.opts = opts;
+ this.source_loading = $(".js-source-loading");
+ this.target_loading = $(".js-target-loading");
+ $('.js-compare-dropdown').each((function(_this) {
+ return function(i, dropdown) {
+ var $dropdown;
+ $dropdown = $(dropdown);
+ return $dropdown.glDropdown({
+ selectable: true,
+ fieldName: $dropdown.data('field-name'),
+ filterable: true,
+ id: function(obj, $el) {
+ return $el.data('id');
+ },
+ toggleLabel: function(obj, $el) {
+ return $el.text().trim();
+ },
+ clicked: function(e, el) {
+ if ($dropdown.is('.js-target-branch')) {
+ return _this.getTargetHtml();
+ } else if ($dropdown.is('.js-source-branch')) {
+ return _this.getSourceHtml();
+ } else if ($dropdown.is('.js-target-project')) {
+ return _this.getTargetProject();
}
- });
- };
- })(this));
- this.initialState();
- }
+ }
+ });
+ };
+ })(this));
+ this.initialState();
+ }
- Compare.prototype.initialState = function() {
- this.getSourceHtml();
- return this.getTargetHtml();
- };
+ Compare.prototype.initialState = function() {
+ this.getSourceHtml();
+ return this.getTargetHtml();
+ };
- Compare.prototype.getTargetProject = function() {
- return $.ajax({
- url: this.opts.targetProjectUrl,
- data: {
- target_project_id: $("input[name='merge_request[target_project_id]']").val()
- },
- beforeSend: function() {
- return $('.mr_target_commit').empty();
- },
- success: function(html) {
- return $('.js-target-branch-dropdown .dropdown-content').html(html);
- }
- });
- };
+ Compare.prototype.getTargetProject = function() {
+ return $.ajax({
+ url: this.opts.targetProjectUrl,
+ data: {
+ target_project_id: $("input[name='merge_request[target_project_id]']").val()
+ },
+ beforeSend: function() {
+ return $('.mr_target_commit').empty();
+ },
+ success: function(html) {
+ return $('.js-target-branch-dropdown .dropdown-content').html(html);
+ }
+ });
+ };
- Compare.prototype.getSourceHtml = function() {
- return this.sendAjax(this.opts.sourceBranchUrl, this.source_loading, '.mr_source_commit', {
- ref: $("input[name='merge_request[source_branch]']").val()
- });
- };
+ Compare.prototype.getSourceHtml = function() {
+ return this.sendAjax(this.opts.sourceBranchUrl, this.source_loading, '.mr_source_commit', {
+ ref: $("input[name='merge_request[source_branch]']").val()
+ });
+ };
- Compare.prototype.getTargetHtml = function() {
- return this.sendAjax(this.opts.targetBranchUrl, this.target_loading, '.mr_target_commit', {
- target_project_id: $("input[name='merge_request[target_project_id]']").val(),
- ref: $("input[name='merge_request[target_branch]']").val()
- });
- };
+ Compare.prototype.getTargetHtml = function() {
+ return this.sendAjax(this.opts.targetBranchUrl, this.target_loading, '.mr_target_commit', {
+ target_project_id: $("input[name='merge_request[target_project_id]']").val(),
+ ref: $("input[name='merge_request[target_branch]']").val()
+ });
+ };
- Compare.prototype.sendAjax = function(url, loading, target, data) {
- var $target;
- $target = $(target);
- return $.ajax({
- url: url,
- data: data,
- beforeSend: function() {
- loading.show();
- return $target.empty();
- },
- success: function(html) {
- loading.hide();
- $target.html(html);
- var className = '.' + $target[0].className.replace(' ', '.');
- gl.utils.localTimeAgo($('.js-timeago', className));
- }
- });
- };
+ Compare.prototype.sendAjax = function(url, loading, target, data) {
+ var $target;
+ $target = $(target);
+ return $.ajax({
+ url: url,
+ data: data,
+ beforeSend: function() {
+ loading.show();
+ return $target.empty();
+ },
+ success: function(html) {
+ loading.hide();
+ $target.html(html);
+ var className = '.' + $target[0].className.replace(' ', '.');
+ gl.utils.localTimeAgo($('.js-timeago', className));
+ }
+ });
+ };
- return Compare;
- })();
-}).call(window);
+ return Compare;
+})();
diff --git a/app/assets/javascripts/compare_autocomplete.js b/app/assets/javascripts/compare_autocomplete.js
index 1eca973e069..d91bfb1ccbd 100644
--- a/app/assets/javascripts/compare_autocomplete.js
+++ b/app/assets/javascripts/compare_autocomplete.js
@@ -1,69 +1,67 @@
/* 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() {
- function CompareAutocomplete() {
- this.initDropdown();
- }
+window.CompareAutocomplete = (function() {
+ function CompareAutocomplete() {
+ this.initDropdown();
+ }
- CompareAutocomplete.prototype.initDropdown = function() {
- return $('.js-compare-dropdown').each(function() {
- var $dropdown, selected;
- $dropdown = $(this);
- selected = $dropdown.data('selected');
- const $dropdownContainer = $dropdown.closest('.dropdown');
- const $fieldInput = $(`input[name="${$dropdown.data('field-name')}"]`, $dropdownContainer);
- const $filterInput = $('input[type="search"]', $dropdownContainer);
- $dropdown.glDropdown({
- data: function(term, callback) {
- return $.ajax({
- url: $dropdown.data('refs-url'),
- data: {
- ref: $dropdown.data('ref')
- }
- }).done(function(refs) {
- return callback(refs);
- });
- },
- selectable: true,
- filterable: true,
- filterByText: true,
- fieldName: $dropdown.data('field-name'),
- filterInput: 'input[type="search"]',
- renderRow: function(ref) {
- var link;
- if (ref.header != null) {
- return $('<li />').addClass('dropdown-header').text(ref.header);
- } else {
- link = $('<a />').attr('href', '#').addClass(ref === selected ? 'is-active' : '').text(ref).attr('data-ref', escape(ref));
- return $('<li />').append(link);
+ CompareAutocomplete.prototype.initDropdown = function() {
+ return $('.js-compare-dropdown').each(function() {
+ var $dropdown, selected;
+ $dropdown = $(this);
+ selected = $dropdown.data('selected');
+ const $dropdownContainer = $dropdown.closest('.dropdown');
+ const $fieldInput = $(`input[name="${$dropdown.data('field-name')}"]`, $dropdownContainer);
+ const $filterInput = $('input[type="search"]', $dropdownContainer);
+ $dropdown.glDropdown({
+ data: function(term, callback) {
+ return $.ajax({
+ url: $dropdown.data('refs-url'),
+ data: {
+ ref: $dropdown.data('ref')
}
- },
- id: function(obj, $el) {
- return $el.attr('data-ref');
- },
- toggleLabel: function(obj, $el) {
- return $el.text().trim();
+ }).done(function(refs) {
+ return callback(refs);
+ });
+ },
+ selectable: true,
+ filterable: true,
+ filterByText: true,
+ fieldName: $dropdown.data('field-name'),
+ filterInput: 'input[type="search"]',
+ renderRow: function(ref) {
+ var link;
+ if (ref.header != null) {
+ return $('<li />').addClass('dropdown-header').text(ref.header);
+ } else {
+ link = $('<a />').attr('href', '#').addClass(ref === selected ? 'is-active' : '').text(ref).attr('data-ref', escape(ref));
+ return $('<li />').append(link);
}
- });
- $filterInput.on('keyup', (e) => {
- const keyCode = e.keyCode || e.which;
- if (keyCode !== 13) return;
- const text = $filterInput.val();
- $fieldInput.val(text);
- $('.dropdown-toggle-text', $dropdown).text(text);
- $dropdownContainer.removeClass('open');
- });
+ },
+ id: function(obj, $el) {
+ return $el.attr('data-ref');
+ },
+ toggleLabel: function(obj, $el) {
+ return $el.text().trim();
+ }
+ });
+ $filterInput.on('keyup', (e) => {
+ const keyCode = e.keyCode || e.which;
+ if (keyCode !== 13) return;
+ const text = $filterInput.val();
+ $fieldInput.val(text);
+ $('.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');
- }
- });
+ $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(window);
+ return CompareAutocomplete;
+})();
diff --git a/app/assets/javascripts/confirm_danger_modal.js b/app/assets/javascripts/confirm_danger_modal.js
index a1c1b721228..b375b61202e 100644
--- a/app/assets/javascripts/confirm_danger_modal.js
+++ b/app/assets/javascripts/confirm_danger_modal.js
@@ -1,31 +1,30 @@
/* 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) {
- var project_path, submit;
- this.form = form;
- $('.js-confirm-text').text(text || '');
- $('.js-confirm-danger-input').val('');
- $('#modal-confirm-danger').modal('show');
- project_path = $('.js-confirm-danger-match').text();
- submit = $('.js-confirm-danger-submit');
- submit.disable();
- $('.js-confirm-danger-input').off('input');
- $('.js-confirm-danger-input').on('input', function() {
- if (gl.utils.rstrip($(this).val()) === project_path) {
- return submit.enable();
- } else {
- return submit.disable();
- }
- });
- $('.js-confirm-danger-submit').off('click');
- $('.js-confirm-danger-submit').on('click', (function(_this) {
- return function() {
- return _this.form.submit();
- };
- })(this));
- }
- return ConfirmDangerModal;
- })();
-}).call(window);
+window.ConfirmDangerModal = (function() {
+ function ConfirmDangerModal(form, text) {
+ var project_path, submit;
+ this.form = form;
+ $('.js-confirm-text').text(text || '');
+ $('.js-confirm-danger-input').val('');
+ $('#modal-confirm-danger').modal('show');
+ project_path = $('.js-confirm-danger-match').text();
+ submit = $('.js-confirm-danger-submit');
+ submit.disable();
+ $('.js-confirm-danger-input').off('input');
+ $('.js-confirm-danger-input').on('input', function() {
+ if (gl.utils.rstrip($(this).val()) === project_path) {
+ return submit.enable();
+ } else {
+ return submit.disable();
+ }
+ });
+ $('.js-confirm-danger-submit').off('click');
+ $('.js-confirm-danger-submit').on('click', (function(_this) {
+ return function() {
+ return _this.form.submit();
+ };
+ })(this));
+ }
+
+ return ConfirmDangerModal;
+})();
diff --git a/app/assets/javascripts/copy_as_gfm.js b/app/assets/javascripts/copy_as_gfm.js
index 8883c339335..0fb7bde1fd6 100644
--- a/app/assets/javascripts/copy_as_gfm.js
+++ b/app/assets/javascripts/copy_as_gfm.js
@@ -1,364 +1,361 @@
/* 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: {
- '.tooltip'(el, text) {
- return '';
- },
- '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');
- },
- 'gl-emoji'(el, text) {
- return `:${el.getAttribute('data-name')}:`;
- },
- },
- 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: {
- 'a[name]:not([href]):empty'(el, text) {
- return el.outerHTML;
- },
- '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 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: {
+ '.tooltip'(el, text) {
+ return '';
+ },
+ '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');
+ },
+ 'gl-emoji'(el, text) {
+ return `:${el.getAttribute('data-name')}:`;
+ },
+ },
+ 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: {
+ 'a[name]:not([href]):empty'(el, text) {
+ return el.outerHTML;
+ },
+ '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, summary, details'(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}`;
- });
+ return ` ${line}`;
+ }).join('\n');
+ }
- return `<dl>\n${lines.join('\n')}\n</dl>`;
- },
- 'sub, dt, dd, kbd, q, samp, var, ruby, rt, rp, abbr, summary, details'(el, text) {
- const tag = el.nodeName.toLowerCase();
- return `<${tag}>${text}</${tag}>`;
- },
+ 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 ' ';
},
- SyntaxHighlightFilter: {
- 'pre.code.highlight'(el, t) {
- const text = t.trim();
+ 'code'(el, text) {
+ let backtickCount = 1;
+ const backtickMatch = text.match(/`+/);
+ if (backtickMatch) {
+ backtickCount = backtickMatch[0].length + 1;
+ }
- let lang = el.getAttribute('lang');
- if (lang === 'plaintext') {
- lang = '';
- }
+ const backticks = Array(backtickCount + 1).join('`');
+ const spaceOrNoSpace = backtickCount > 1 ? ' ' : '';
- // 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 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;
- return ` ${line}`;
- }).join('\n');
- }
+ const theadText = CopyAsGFM.nodeToGFM(theadEl);
+ const tbodyText = CopyAsGFM.nodeToGFM(tbodyEl);
- 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;
+ 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;
}
- 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;
+ chars = Math.max(chars, 3);
- // If the documentFragment contains more than just Markdown, don't copy as GFM.
- if (documentFragment.querySelector('.md, .wiki')) return;
+ const middle = Array(chars + 1).join('-');
- e.preventDefault();
- clipboardData.setData('text/plain', documentFragment.textContent);
+ return before + middle + after;
+ });
- const gfm = CopyAsGFM.nodeToGFM(documentFragment);
- clipboardData.setData('text/x-gfm', gfm);
- }
+ return `${text}|${cells.join('|')}|`;
+ },
+ 'tr'(el, text) {
+ const cells = _.map(el.querySelectorAll('td, th'), cell => CopyAsGFM.nodeToGFM(cell).trim());
+ return `| ${cells.join(' | ')} |`;
+ },
+ },
+};
- handlePaste(e) {
- const clipboardData = e.originalEvent.clipboardData;
- if (!clipboardData) return;
+class CopyAsGFM {
+ constructor() {
+ $(document).on('copy', '.md, .wiki', this.handleCopy);
+ $(document).on('paste', '.js-gfm-input', this.handlePaste);
+ }
- const gfm = clipboardData.getData('text/x-gfm');
- if (!gfm) return;
+ handleCopy(e) {
+ const clipboardData = e.originalEvent.clipboardData;
+ if (!clipboardData) return;
- e.preventDefault();
+ const documentFragment = window.gl.utils.getSelectedFragment();
+ if (!documentFragment) return;
- window.gl.utils.insertText(e.target, gfm);
- }
+ // If the documentFragment contains more than just Markdown, don't copy as GFM.
+ if (documentFragment.querySelector('.md, .wiki')) return;
- static nodeToGFM(node) {
- if (node.nodeType === Node.TEXT_NODE) {
- return node.textContent;
- }
+ e.preventDefault();
+ clipboardData.setData('text/plain', documentFragment.textContent);
- const text = this.innerGFM(node);
+ const gfm = CopyAsGFM.nodeToGFM(documentFragment);
+ clipboardData.setData('text/x-gfm', gfm);
+ }
- if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
- return text;
- }
+ handlePaste(e) {
+ const clipboardData = e.originalEvent.clipboardData;
+ if (!clipboardData) return;
- for (const filter in gfmRules) {
- const rules = gfmRules[filter];
+ const gfm = clipboardData.getData('text/x-gfm');
+ if (!gfm) return;
- for (const selector in rules) {
- const func = rules[selector];
+ e.preventDefault();
- if (!window.gl.utils.nodeMatchesSelector(node, selector)) continue;
+ window.gl.utils.insertText(e.target, gfm);
+ }
- const result = func(node, text);
- if (result === false) continue;
+ static nodeToGFM(node) {
+ if (node.nodeType === Node.TEXT_NODE) {
+ return node.textContent;
+ }
- return result;
- }
- }
+ const text = this.innerGFM(node);
+ if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
return text;
}
- static innerGFM(parentNode) {
- const nodes = parentNode.childNodes;
+ for (const filter in gfmRules) {
+ const rules = gfmRules[filter];
- const clonedParentNode = parentNode.cloneNode(true);
- const clonedNodes = Array.prototype.slice.call(clonedParentNode.childNodes, 0);
+ for (const selector in rules) {
+ const func = rules[selector];
- for (let i = 0; i < nodes.length; i += 1) {
- const node = nodes[i];
- const clonedNode = clonedNodes[i];
+ if (!window.gl.utils.nodeMatchesSelector(node, selector)) continue;
- const text = this.nodeToGFM(node);
+ const result = func(node, text);
+ if (result === false) continue;
- // `clonedNode.replaceWith(text)` is not yet widely supported
- clonedNode.parentNode.replaceChild(document.createTextNode(text), clonedNode);
+ return result;
}
+ }
+
+ return text;
+ }
- return clonedParentNode.innerText || clonedParentNode.textContent;
+ 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;
+window.gl = window.gl || {};
+window.gl.CopyAsGFM = CopyAsGFM;
- new CopyAsGFM();
-})();
+new CopyAsGFM();
diff --git a/app/assets/javascripts/copy_to_clipboard.js b/app/assets/javascripts/copy_to_clipboard.js
index 615f485e18a..6dbec50b890 100644
--- a/app/assets/javascripts/copy_to_clipboard.js
+++ b/app/assets/javascripts/copy_to_clipboard.js
@@ -1,49 +1,46 @@
/* 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 */
-
-window.Clipboard = require('vendor/clipboard');
-
-(function() {
- var genericError, genericSuccess, showTooltip;
-
- genericSuccess = function(e) {
- showTooltip(e.trigger, 'Copied');
- // Clear the selection and blur the trigger so it loses its border
- e.clearSelection();
- return $(e.trigger).blur();
- };
-
- // Safari doesn't support `execCommand`, so instead we inform the user to
- // copy manually.
- //
- // See http://clipboardjs.com/#browser-support
- genericError = function(e) {
- var key;
- if (/Mac/i.test(navigator.userAgent)) {
- key = '&#8984;'; // Command
- } else {
- key = 'Ctrl';
- }
- return showTooltip(e.trigger, "Press " + key + "-C to copy");
- };
-
- showTooltip = function(target, title) {
- var $target = $(target);
- var originalTitle = $target.data('original-title');
-
- $target
- .attr('title', 'Copied')
- .tooltip('fixTitle')
- .tooltip('show')
- .attr('title', originalTitle)
- .tooltip('fixTitle');
- };
-
- $(function() {
- var clipboard;
-
- clipboard = new Clipboard('[data-clipboard-target], [data-clipboard-text]');
- clipboard.on('success', genericSuccess);
- return clipboard.on('error', genericError);
- });
-}).call(window);
+
+import Clipboard from 'vendor/clipboard';
+
+var genericError, genericSuccess, showTooltip;
+
+genericSuccess = function(e) {
+ showTooltip(e.trigger, 'Copied');
+ // Clear the selection and blur the trigger so it loses its border
+ e.clearSelection();
+ return $(e.trigger).blur();
+};
+
+// Safari doesn't support `execCommand`, so instead we inform the user to
+// copy manually.
+//
+// See http://clipboardjs.com/#browser-support
+genericError = function(e) {
+ var key;
+ if (/Mac/i.test(navigator.userAgent)) {
+ key = '&#8984;'; // Command
+ } else {
+ key = 'Ctrl';
+ }
+ return showTooltip(e.trigger, "Press " + key + "-C to copy");
+};
+
+showTooltip = function(target, title) {
+ var $target = $(target);
+ var originalTitle = $target.data('original-title');
+
+ $target
+ .attr('title', 'Copied')
+ .tooltip('fixTitle')
+ .tooltip('show')
+ .attr('title', originalTitle)
+ .tooltip('fixTitle');
+};
+
+$(function() {
+ var clipboard;
+
+ clipboard = new Clipboard('[data-clipboard-target], [data-clipboard-text]');
+ clipboard.on('success', genericSuccess);
+ return clipboard.on('error', genericError);
+});
diff --git a/app/assets/javascripts/create_label.js b/app/assets/javascripts/create_label.js
index 85384d98126..121d64db789 100644
--- a/app/assets/javascripts/create_label.js
+++ b/app/assets/javascripts/create_label.js
@@ -1,132 +1,127 @@
/* eslint-disable func-names, space-before-function-paren, prefer-arrow-callback, comma-dangle, prefer-template, quotes, no-param-reassign, wrap-iife, max-len */
/* global Api */
-(function (w) {
- class CreateLabelDropdown {
- constructor ($el, namespacePath, projectPath) {
- this.$el = $el;
- this.namespacePath = namespacePath;
- this.projectPath = projectPath;
- this.$dropdownBack = $('.dropdown-menu-back', this.$el.closest('.dropdown'));
- this.$cancelButton = $('.js-cancel-label-btn', this.$el);
- this.$newLabelField = $('#new_label_name', this.$el);
- this.$newColorField = $('#new_label_color', this.$el);
- this.$colorPreview = $('.js-dropdown-label-color-preview', this.$el);
- this.$newLabelError = $('.js-label-error', this.$el);
- this.$newLabelCreateButton = $('.js-new-label-btn', this.$el);
- this.$colorSuggestions = $('.suggest-colors-dropdown a', this.$el);
-
- this.$newLabelError.hide();
- this.$newLabelCreateButton.disable();
+class CreateLabelDropdown {
+ constructor ($el, namespacePath, projectPath) {
+ this.$el = $el;
+ this.namespacePath = namespacePath;
+ this.projectPath = projectPath;
+ this.$dropdownBack = $('.dropdown-menu-back', this.$el.closest('.dropdown'));
+ this.$cancelButton = $('.js-cancel-label-btn', this.$el);
+ this.$newLabelField = $('#new_label_name', this.$el);
+ this.$newColorField = $('#new_label_color', this.$el);
+ this.$colorPreview = $('.js-dropdown-label-color-preview', this.$el);
+ this.$newLabelError = $('.js-label-error', this.$el);
+ this.$newLabelCreateButton = $('.js-new-label-btn', this.$el);
+ this.$colorSuggestions = $('.suggest-colors-dropdown a', this.$el);
+
+ this.$newLabelError.hide();
+ this.$newLabelCreateButton.disable();
+
+ this.cleanBinding();
+ this.addBinding();
+ }
- this.cleanBinding();
- this.addBinding();
- }
+ cleanBinding () {
+ this.$colorSuggestions.off('click');
+ this.$newLabelField.off('keyup change');
+ this.$newColorField.off('keyup change');
+ this.$dropdownBack.off('click');
+ this.$cancelButton.off('click');
+ this.$newLabelCreateButton.off('click');
+ }
- cleanBinding () {
- this.$colorSuggestions.off('click');
- this.$newLabelField.off('keyup change');
- this.$newColorField.off('keyup change');
- this.$dropdownBack.off('click');
- this.$cancelButton.off('click');
- this.$newLabelCreateButton.off('click');
- }
+ addBinding () {
+ const self = this;
- addBinding () {
- const self = this;
+ this.$colorSuggestions.on('click', function (e) {
+ const $this = $(this);
+ self.addColorValue(e, $this);
+ });
- this.$colorSuggestions.on('click', function (e) {
- const $this = $(this);
- self.addColorValue(e, $this);
- });
+ this.$newLabelField.on('keyup change', this.enableLabelCreateButton.bind(this));
+ this.$newColorField.on('keyup change', this.enableLabelCreateButton.bind(this));
- this.$newLabelField.on('keyup change', this.enableLabelCreateButton.bind(this));
- this.$newColorField.on('keyup change', this.enableLabelCreateButton.bind(this));
+ this.$dropdownBack.on('click', this.resetForm.bind(this));
- this.$dropdownBack.on('click', this.resetForm.bind(this));
+ this.$cancelButton.on('click', function(e) {
+ e.preventDefault();
+ e.stopPropagation();
- this.$cancelButton.on('click', function(e) {
- e.preventDefault();
- e.stopPropagation();
+ self.resetForm();
+ self.$dropdownBack.trigger('click');
+ });
- self.resetForm();
- self.$dropdownBack.trigger('click');
- });
+ this.$newLabelCreateButton.on('click', this.saveLabel.bind(this));
+ }
- this.$newLabelCreateButton.on('click', this.saveLabel.bind(this));
- }
+ addColorValue (e, $this) {
+ e.preventDefault();
+ e.stopPropagation();
- addColorValue (e, $this) {
- e.preventDefault();
- e.stopPropagation();
+ this.$newColorField.val($this.data('color')).trigger('change');
+ this.$colorPreview
+ .css('background-color', $this.data('color'))
+ .parent()
+ .addClass('is-active');
+ }
- this.$newColorField.val($this.data('color')).trigger('change');
- this.$colorPreview
- .css('background-color', $this.data('color'))
- .parent()
- .addClass('is-active');
+ enableLabelCreateButton () {
+ if (this.$newLabelField.val() !== '' && this.$newColorField.val() !== '') {
+ this.$newLabelError.hide();
+ this.$newLabelCreateButton.enable();
+ } else {
+ this.$newLabelCreateButton.disable();
}
+ }
- enableLabelCreateButton () {
- if (this.$newLabelField.val() !== '' && this.$newColorField.val() !== '') {
- this.$newLabelError.hide();
- this.$newLabelCreateButton.enable();
- } else {
- this.$newLabelCreateButton.disable();
- }
- }
+ resetForm () {
+ this.$newLabelField
+ .val('')
+ .trigger('change');
- resetForm () {
- this.$newLabelField
- .val('')
- .trigger('change');
+ this.$newColorField
+ .val('')
+ .trigger('change');
- this.$newColorField
- .val('')
- .trigger('change');
+ this.$colorPreview
+ .css('background-color', '')
+ .parent()
+ .removeClass('is-active');
+ }
- this.$colorPreview
- .css('background-color', '')
- .parent()
- .removeClass('is-active');
- }
+ saveLabel (e) {
+ e.preventDefault();
+ e.stopPropagation();
- saveLabel (e) {
- e.preventDefault();
- e.stopPropagation();
+ Api.newLabel(this.namespacePath, this.projectPath, {
+ title: this.$newLabelField.val(),
+ color: this.$newColorField.val()
+ }, (label) => {
+ this.$newLabelCreateButton.enable();
- Api.newLabel(this.namespacePath, this.projectPath, {
- title: this.$newLabelField.val(),
- color: this.$newColorField.val()
- }, (label) => {
- this.$newLabelCreateButton.enable();
-
- if (label.message) {
- let errors;
-
- if (typeof label.message === 'string') {
- errors = label.message;
- } else {
- errors = Object.keys(label.message).map(key =>
- `${gl.text.humanize(key)} ${label.message[key].join(', ')}`
- ).join("<br/>");
- }
-
- this.$newLabelError
- .html(errors)
- .show();
- } else {
- this.$dropdownBack.trigger('click');
+ if (label.message) {
+ let errors;
- $(document).trigger('created.label', label);
+ if (typeof label.message === 'string') {
+ errors = label.message;
+ } else {
+ errors = Object.keys(label.message).map(key =>
+ `${gl.text.humanize(key)} ${label.message[key].join(', ')}`
+ ).join("<br/>");
}
- });
- }
- }
- if (!w.gl) {
- w.gl = {};
+ this.$newLabelError
+ .html(errors)
+ .show();
+ } else {
+ this.$dropdownBack.trigger('click');
+
+ $(document).trigger('created.label', label);
+ }
+ });
}
+}
- gl.CreateLabelDropdown = CreateLabelDropdown;
-})(window);
+window.gl = window.gl || {};
+gl.CreateLabelDropdown = CreateLabelDropdown;
diff --git a/app/assets/javascripts/diff.js b/app/assets/javascripts/diff.js
index 6829e8aeaea..cfa60325fcc 100644
--- a/app/assets/javascripts/diff.js
+++ b/app/assets/javascripts/diff.js
@@ -2,129 +2,127 @@
require('./lib/utils/url_utility');
-(() => {
- const UNFOLD_COUNT = 20;
- let isBound = false;
+const UNFOLD_COUNT = 20;
+let isBound = false;
- class Diff {
- constructor() {
- const $diffFile = $('.files .diff-file');
- $diffFile.singleFileDiff();
- $diffFile.filesCommentButton();
+class Diff {
+ constructor() {
+ const $diffFile = $('.files .diff-file');
+ $diffFile.singleFileDiff();
+ $diffFile.filesCommentButton();
- $diffFile.each((index, file) => new gl.ImageFile(file));
+ $diffFile.each((index, file) => new gl.ImageFile(file));
- if (this.diffViewType() === 'parallel') {
- $('.content-wrapper .container-fluid').removeClass('container-limited');
- }
-
- if (!isBound) {
- $(document)
- .on('click', '.js-unfold', this.handleClickUnfold.bind(this))
- .on('click', '.diff-line-num a', this.handleClickLineNum.bind(this));
- isBound = true;
- }
+ if (this.diffViewType() === 'parallel') {
+ $('.content-wrapper .container-fluid').removeClass('container-limited');
+ }
- if (gl.utils.getLocationHash()) {
- this.highlightSelectedLine();
- }
+ if (!isBound) {
+ $(document)
+ .on('click', '.js-unfold', this.handleClickUnfold.bind(this))
+ .on('click', '.diff-line-num a', this.handleClickLineNum.bind(this));
+ isBound = true;
+ }
- this.openAnchoredDiff();
+ if (gl.utils.getLocationHash()) {
+ this.highlightSelectedLine();
}
- handleClickUnfold(e) {
- const $target = $(e.target);
- // current babel config relies on iterators implementation, so we cannot simply do:
- // const [oldLineNumber, newLineNumber] = this.lineNumbers($target.parent());
- const ref = this.lineNumbers($target.parent());
- const oldLineNumber = ref[0];
- const newLineNumber = ref[1];
- const offset = newLineNumber - oldLineNumber;
- const bottom = $target.hasClass('js-unfold-bottom');
- let since;
- let to;
- let unfold = true;
-
- if (bottom) {
- const lineNumber = newLineNumber + 1;
- since = lineNumber;
- to = lineNumber + UNFOLD_COUNT;
- } else {
- const lineNumber = newLineNumber - 1;
- since = lineNumber - UNFOLD_COUNT;
- to = lineNumber;
-
- // make sure we aren't loading more than we need
- const prevNewLine = this.lineNumbers($target.parent().prev())[1];
- if (since <= prevNewLine + 1) {
- since = prevNewLine + 1;
- unfold = false;
- }
+ this.openAnchoredDiff();
+ }
+
+ handleClickUnfold(e) {
+ const $target = $(e.target);
+ // current babel config relies on iterators implementation, so we cannot simply do:
+ // const [oldLineNumber, newLineNumber] = this.lineNumbers($target.parent());
+ const ref = this.lineNumbers($target.parent());
+ const oldLineNumber = ref[0];
+ const newLineNumber = ref[1];
+ const offset = newLineNumber - oldLineNumber;
+ const bottom = $target.hasClass('js-unfold-bottom');
+ let since;
+ let to;
+ let unfold = true;
+
+ if (bottom) {
+ const lineNumber = newLineNumber + 1;
+ since = lineNumber;
+ to = lineNumber + UNFOLD_COUNT;
+ } else {
+ const lineNumber = newLineNumber - 1;
+ since = lineNumber - UNFOLD_COUNT;
+ to = lineNumber;
+
+ // make sure we aren't loading more than we need
+ const prevNewLine = this.lineNumbers($target.parent().prev())[1];
+ if (since <= prevNewLine + 1) {
+ since = prevNewLine + 1;
+ unfold = false;
}
+ }
- const file = $target.parents('.diff-file');
- const link = file.data('blob-diff-path');
- const view = file.data('view');
+ const file = $target.parents('.diff-file');
+ const link = file.data('blob-diff-path');
+ const view = file.data('view');
- const params = { since, to, bottom, offset, unfold, view };
- $.get(link, params, response => $target.parent().replaceWith(response));
- }
+ const params = { since, to, bottom, offset, unfold, view };
+ $.get(link, params, response => $target.parent().replaceWith(response));
+ }
- 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) {
- const clickTarget = $('.js-file-title, .click-to-expand', diffFile);
- diffFile.data('singleFileDiff').toggleDiff(clickTarget, () => {
- this.highlightSelectedLine();
- if (cb) cb();
- });
- } else if (cb) {
- cb();
- }
- }
+ openAnchoredDiff(cb) {
+ const locationHash = gl.utils.getLocationHash();
+ const anchoredDiff = locationHash && locationHash.split('_')[0];
- handleClickLineNum(e) {
- const hash = $(e.currentTarget).attr('href');
- e.preventDefault();
- if (window.history.pushState) {
- window.history.pushState(null, null, hash);
- } else {
- window.location.hash = hash;
- }
- this.highlightSelectedLine();
+ if (!anchoredDiff) return;
+
+ const diffTitle = $(`#${anchoredDiff}`);
+ const diffFile = diffTitle.closest('.diff-file');
+ const nothingHereBlock = $('.nothing-here-block:visible', diffFile);
+ if (nothingHereBlock.length) {
+ const clickTarget = $('.js-file-title, .click-to-expand', diffFile);
+ diffFile.data('singleFileDiff').toggleDiff(clickTarget, () => {
+ this.highlightSelectedLine();
+ if (cb) cb();
+ });
+ } else if (cb) {
+ cb();
}
+ }
- diffViewType() {
- return $('.inline-parallel-buttons a.active').data('view-type');
+ handleClickLineNum(e) {
+ const hash = $(e.currentTarget).attr('href');
+ e.preventDefault();
+ if (window.history.pushState) {
+ window.history.pushState(null, null, hash);
+ } else {
+ window.location.hash = hash;
}
+ this.highlightSelectedLine();
+ }
- lineNumbers(line) {
- if (!line.children().length) {
- return [0, 0];
- }
- return line.find('.diff-line-num').map((i, elm) => parseInt($(elm).data('linenumber'), 10));
+ diffViewType() {
+ return $('.inline-parallel-buttons a.active').data('view-type');
+ }
+
+ lineNumbers(line) {
+ if (!line.children().length) {
+ return [0, 0];
}
+ return line.find('.diff-line-num').map((i, elm) => parseInt($(elm).data('linenumber'), 10));
+ }
- highlightSelectedLine() {
- const hash = gl.utils.getLocationHash();
- const $diffFiles = $('.diff-file');
- $diffFiles.find('.hll').removeClass('hll');
+ highlightSelectedLine() {
+ const hash = gl.utils.getLocationHash();
+ const $diffFiles = $('.diff-file');
+ $diffFiles.find('.hll').removeClass('hll');
- if (hash) {
- $diffFiles
- .find(`tr#${hash}:not(.match) td, td#${hash}, td[data-line-code="${hash}"]`)
- .addClass('hll');
- }
+ if (hash) {
+ $diffFiles
+ .find(`tr#${hash}:not(.match) td, td#${hash}, td[data-line-code="${hash}"]`)
+ .addClass('hll');
}
}
+}
- window.gl = window.gl || {};
- window.gl.Diff = Diff;
-})();
+window.gl = window.gl || {};
+window.gl.Diff = Diff;
diff --git a/app/assets/javascripts/diff_notes/components/diff_note_avatars.js b/app/assets/javascripts/diff_notes/components/diff_note_avatars.js
new file mode 100644
index 00000000000..788daa96b3d
--- /dev/null
+++ b/app/assets/javascripts/diff_notes/components/diff_note_avatars.js
@@ -0,0 +1,155 @@
+/* global CommentsStore Cookies notes */
+import Vue from 'vue';
+import collapseIcon from '../icons/collapse_icon.svg';
+
+(() => {
+ const DiffNoteAvatars = Vue.extend({
+ props: ['discussionId'],
+ data() {
+ return {
+ isVisible: false,
+ lineType: '',
+ storeState: CommentsStore.state,
+ shownAvatars: 3,
+ collapseIcon,
+ };
+ },
+ template: `
+ <div class="diff-comment-avatar-holders"
+ v-show="notesCount !== 0">
+ <div v-if="!isVisible">
+ <img v-for="note in notesSubset"
+ class="avatar diff-comment-avatar has-tooltip js-diff-comment-avatar"
+ width="19"
+ height="19"
+ role="button"
+ data-container="body"
+ data-placement="top"
+ :data-line-type="lineType"
+ :title="note.authorName + ': ' + note.noteTruncated"
+ :src="note.authorAvatar"
+ @click="clickedAvatar($event)" />
+ <span v-if="notesCount > shownAvatars"
+ class="diff-comments-more-count has-tooltip js-diff-comment-avatar"
+ data-container="body"
+ data-placement="top"
+ ref="extraComments"
+ role="button"
+ :data-line-type="lineType"
+ :title="extraNotesTitle"
+ @click="clickedAvatar($event)">{{ moreText }}</span>
+ </div>
+ <button class="diff-notes-collapse js-diff-comment-avatar"
+ type="button"
+ aria-label="Show comments"
+ :data-line-type="lineType"
+ @click="clickedAvatar($event)"
+ v-if="isVisible"
+ v-html="collapseIcon">
+ </button>
+ </div>
+ `,
+ mounted() {
+ this.$nextTick(() => {
+ this.addNoCommentClass();
+ this.setDiscussionVisible();
+
+ this.lineType = $(this.$el).closest('.diff-line-num').hasClass('old_line') ? 'old' : 'new';
+ });
+
+ $(document).on('toggle.comments', () => {
+ this.$nextTick(() => {
+ this.setDiscussionVisible();
+ });
+ });
+ },
+ destroyed() {
+ $(document).off('toggle.comments');
+ },
+ watch: {
+ storeState: {
+ handler() {
+ this.$nextTick(() => {
+ $('.has-tooltip', this.$el).tooltip('fixTitle');
+
+ // We need to add/remove a class to an element that is outside the Vue instance
+ this.addNoCommentClass();
+ });
+ },
+ deep: true,
+ },
+ },
+ computed: {
+ notesSubset() {
+ let notes = [];
+
+ if (this.discussion) {
+ notes = Object.keys(this.discussion.notes)
+ .slice(0, this.shownAvatars)
+ .map(noteId => this.discussion.notes[noteId]);
+ }
+
+ return notes;
+ },
+ extraNotesTitle() {
+ if (this.discussion) {
+ const extra = this.discussion.notesCount() - this.shownAvatars;
+
+ return `${extra} more comment${extra > 1 ? 's' : ''}`;
+ }
+
+ return '';
+ },
+ discussion() {
+ return this.storeState[this.discussionId];
+ },
+ notesCount() {
+ if (this.discussion) {
+ return this.discussion.notesCount();
+ }
+
+ return 0;
+ },
+ moreText() {
+ const plusSign = this.notesCount < 100 ? '+' : '';
+
+ return `${plusSign}${this.notesCount - this.shownAvatars}`;
+ },
+ },
+ methods: {
+ clickedAvatar(e) {
+ notes.addDiffNote(e);
+
+ // Toggle the active state of the toggle all button
+ this.toggleDiscussionsToggleState();
+
+ this.$nextTick(() => {
+ this.setDiscussionVisible();
+
+ $('.has-tooltip', this.$el).tooltip('fixTitle');
+ $('.has-tooltip', this.$el).tooltip('hide');
+ });
+ },
+ addNoCommentClass() {
+ const notesCount = this.notesCount;
+
+ $(this.$el).closest('.js-avatar-container')
+ .toggleClass('js-no-comment-btn', notesCount > 0)
+ .nextUntil('.js-avatar-container')
+ .toggleClass('js-no-comment-btn', notesCount > 0);
+ },
+ toggleDiscussionsToggleState() {
+ const $notesHolders = $(this.$el).closest('.code').find('.notes_holder');
+ const $visibleNotesHolders = $notesHolders.filter(':visible');
+ const $toggleDiffCommentsBtn = $(this.$el).closest('.diff-file').find('.js-toggle-diff-comments');
+
+ $toggleDiffCommentsBtn.toggleClass('active', $notesHolders.length === $visibleNotesHolders.length);
+ },
+ setDiscussionVisible() {
+ this.isVisible = $(`.diffs .notes[data-discussion-id="${this.discussion.id}"]`).is(':visible');
+ },
+ },
+ });
+
+ Vue.component('diff-note-avatars', DiffNoteAvatars);
+})();
diff --git a/app/assets/javascripts/diff_notes/components/new_issue_for_discussion.js b/app/assets/javascripts/diff_notes/components/new_issue_for_discussion.js
new file mode 100644
index 00000000000..e86bef47172
--- /dev/null
+++ b/app/assets/javascripts/diff_notes/components/new_issue_for_discussion.js
@@ -0,0 +1,29 @@
+/* global Vue */
+/* global CommentsStore */
+
+(() => {
+ const NewIssueForDiscussion = Vue.extend({
+ props: {
+ discussionId: {
+ type: String,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ discussions: CommentsStore.state,
+ };
+ },
+ computed: {
+ discussion() {
+ return this.discussions[this.discussionId];
+ },
+ showButton() {
+ if (this.discussion) return !this.discussion.isResolved();
+ return false;
+ },
+ },
+ });
+
+ Vue.component('new-issue-for-discussion-btn', NewIssueForDiscussion);
+})();
diff --git a/app/assets/javascripts/diff_notes/components/resolve_btn.js b/app/assets/javascripts/diff_notes/components/resolve_btn.js
index d1873d6c7a2..fbd980f0fce 100644
--- a/app/assets/javascripts/diff_notes/components/resolve_btn.js
+++ b/app/assets/javascripts/diff_notes/components/resolve_btn.js
@@ -11,7 +11,10 @@ const Vue = require('vue');
discussionId: String,
resolved: Boolean,
canResolve: Boolean,
- resolvedBy: String
+ resolvedBy: String,
+ authorName: String,
+ authorAvatar: String,
+ noteTruncated: String,
},
data: function () {
return {
@@ -98,7 +101,16 @@ const Vue = require('vue');
CommentsStore.delete(this.discussionId, this.noteId);
},
created: function () {
- CommentsStore.create(this.discussionId, this.noteId, this.canResolve, this.resolved, this.resolvedBy);
+ CommentsStore.create({
+ discussionId: this.discussionId,
+ noteId: this.noteId,
+ canResolve: this.canResolve,
+ resolved: this.resolved,
+ resolvedBy: this.resolvedBy,
+ authorName: this.authorName,
+ authorAvatar: this.authorAvatar,
+ noteTruncated: this.noteTruncated,
+ });
this.note = this.discussion.getNote(this.noteId);
}
diff --git a/app/assets/javascripts/diff_notes/diff_notes_bundle.js b/app/assets/javascripts/diff_notes/diff_notes_bundle.js
index cadf8b96b87..4f6b86a917c 100644
--- a/app/assets/javascripts/diff_notes/diff_notes_bundle.js
+++ b/app/assets/javascripts/diff_notes/diff_notes_bundle.js
@@ -13,10 +13,12 @@ require('./components/jump_to_discussion');
require('./components/resolve_btn');
require('./components/resolve_count');
require('./components/resolve_discussion_btn');
+require('./components/diff_note_avatars');
+require('./components/new_issue_for_discussion');
$(() => {
const projectPath = document.querySelector('.merge-request').dataset.projectPath;
- const COMPONENT_SELECTOR = 'resolve-btn, resolve-discussion-btn, jump-to-discussion, comment-and-resolve-btn';
+ const COMPONENT_SELECTOR = 'resolve-btn, resolve-discussion-btn, jump-to-discussion, comment-and-resolve-btn, new-issue-for-discussion-btn';
window.gl = window.gl || {};
window.gl.diffNoteApps = {};
@@ -24,6 +26,15 @@ $(() => {
window.ResolveService = new gl.DiffNotesResolveServiceClass(projectPath);
gl.diffNotesCompileComponents = () => {
+ $('diff-note-avatars').each(function () {
+ const tmp = Vue.extend({
+ template: $(this).get(0).outerHTML
+ });
+ const tmpApp = new tmp().$mount();
+
+ $(this).replaceWith(tmpApp.$el);
+ });
+
const $components = $(COMPONENT_SELECTOR).filter(function () {
return $(this).closest('resolve-count').length !== 1;
});
diff --git a/app/assets/javascripts/diff_notes/icons/collapse_icon.svg b/app/assets/javascripts/diff_notes/icons/collapse_icon.svg
new file mode 100644
index 00000000000..bd4b393cfaa
--- /dev/null
+++ b/app/assets/javascripts/diff_notes/icons/collapse_icon.svg
@@ -0,0 +1 @@
+<svg width="11" height="11" viewBox="0 0 9 13"><path d="M2.57568253,6.49866948 C2.50548852,6.57199715 2.44637866,6.59708255 2.39835118,6.57392645 C2.3503237,6.55077034 2.32631032,6.48902165 2.32631032,6.38867852 L2.32631032,-2.13272614 C2.32631032,-2.23306927 2.3503237,-2.29481796 2.39835118,-2.31797406 C2.44637866,-2.34113017 2.50548852,-2.31604477 2.57568253,-2.24271709 L6.51022184,1.86747129 C6.53977721,1.8983461 6.56379059,1.93500939 6.5822627,1.97746225 L6.5822627,2.27849013 C6.56379059,2.31708364 6.53977721,2.35374693 6.51022184,2.38848109 L2.57568253,6.49866948 Z" transform="translate(4.454287, 2.127976) rotate(90.000000) translate(-4.454287, -2.127976) "></path><path d="M3.74312342,2.09553332 C3.74312342,1.99519019 3.77821989,1.9083561 3.8484139,1.83502843 C3.91860791,1.76170075 4.00173115,1.72503747 4.09778611,1.72503747 L4.80711151,1.72503747 C4.90316647,1.72503747 4.98628971,1.76170075 5.05648372,1.83502843 C5.12667773,1.9083561 5.16177421,1.99519019 5.16177421,2.09553332 L5.16177421,10.2464421 C5.16177421,10.3467853 5.12667773,10.4336194 5.05648372,10.506947 C4.98628971,10.5802747 4.90316647,10.616938 4.80711151,10.616938 L4.09778611,10.616938 C4.00173115,10.616938 3.91860791,10.5802747 3.8484139,10.506947 C3.77821989,10.4336194 3.74312342,10.3467853 3.74312342,10.2464421 L3.74312342,2.09553332 Z" transform="translate(4.452449, 6.170988) rotate(-90.000000) translate(-4.452449, -6.170988) "></path><path d="M2.57568253,14.6236695 C2.50548852,14.6969971 2.44637866,14.7220826 2.39835118,14.6989264 C2.3503237,14.6757703 2.32631032,14.6140216 2.32631032,14.5136785 L2.32631032,5.99227386 C2.32631032,5.89193073 2.3503237,5.83018204 2.39835118,5.80702594 C2.44637866,5.78386983 2.50548852,5.80895523 2.57568253,5.88228291 L6.51022184,9.99247129 C6.53977721,10.0233461 6.56379059,10.0600094 6.5822627,10.1024622 L6.5822627,10.4034901 C6.56379059,10.4420836 6.53977721,10.4787469 6.51022184,10.5134811 L2.57568253,14.6236695 Z" transform="translate(4.454287, 10.252976) scale(1, -1) rotate(90.000000) translate(-4.454287, -10.252976) "></path></svg>
diff --git a/app/assets/javascripts/diff_notes/models/discussion.js b/app/assets/javascripts/diff_notes/models/discussion.js
index fa518ba4d33..dce1a9b58bd 100644
--- a/app/assets/javascripts/diff_notes/models/discussion.js
+++ b/app/assets/javascripts/diff_notes/models/discussion.js
@@ -10,8 +10,8 @@ class DiscussionModel {
this.canResolve = false;
}
- createNote (noteId, canResolve, resolved, resolved_by) {
- Vue.set(this.notes, noteId, new NoteModel(this.id, noteId, canResolve, resolved, resolved_by));
+ createNote (noteObj) {
+ Vue.set(this.notes, noteObj.noteId, new NoteModel(this.id, noteObj));
}
deleteNote (noteId) {
diff --git a/app/assets/javascripts/diff_notes/models/note.js b/app/assets/javascripts/diff_notes/models/note.js
index f3a7cba5ef6..04465aa507e 100644
--- a/app/assets/javascripts/diff_notes/models/note.js
+++ b/app/assets/javascripts/diff_notes/models/note.js
@@ -1,12 +1,15 @@
/* eslint-disable camelcase, no-unused-vars */
class NoteModel {
- constructor(discussionId, noteId, canResolve, resolved, resolved_by) {
+ constructor(discussionId, noteObj) {
this.discussionId = discussionId;
- this.id = noteId;
- this.canResolve = canResolve;
- this.resolved = resolved;
- this.resolved_by = resolved_by;
+ this.id = noteObj.noteId;
+ this.canResolve = noteObj.canResolve;
+ this.resolved = noteObj.resolved;
+ this.resolved_by = noteObj.resolvedBy;
+ this.authorName = noteObj.authorName;
+ this.authorAvatar = noteObj.authorAvatar;
+ this.noteTruncated = noteObj.noteTruncated;
}
}
diff --git a/app/assets/javascripts/diff_notes/stores/comments.js b/app/assets/javascripts/diff_notes/stores/comments.js
index c80d979b977..69c4d7a8434 100644
--- a/app/assets/javascripts/diff_notes/stores/comments.js
+++ b/app/assets/javascripts/diff_notes/stores/comments.js
@@ -21,10 +21,10 @@
return discussion;
},
- create: function (discussionId, noteId, canResolve, resolved, resolved_by) {
- const discussion = this.createDiscussion(discussionId);
+ create: function (noteObj) {
+ const discussion = this.createDiscussion(noteObj.discussionId);
- discussion.createNote(noteId, canResolve, resolved, resolved_by);
+ discussion.createNote(noteObj);
},
update: function (discussionId, noteId, resolved, resolved_by) {
const discussion = this.state[discussionId];
diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js
index 546bdc9c8d7..6d8174e199e 100644
--- a/app/assets/javascripts/dispatcher.js
+++ b/app/assets/javascripts/dispatcher.js
@@ -5,7 +5,6 @@ import PrometheusGraph from './monitoring/prometheus_graph'; // TODO: Maybe Make
/* global ShortcutsNavigation */
/* global Build */
/* global Issuable */
-/* global Issue */
/* global ShortcutsIssuable */
/* global ZenMode */
/* global Milestone */
@@ -35,10 +34,13 @@ import PrometheusGraph from './monitoring/prometheus_graph'; // TODO: Maybe Make
/* global ProjectShow */
/* global Labels */
/* global Shortcuts */
+import Issue from './issue';
import BindInOut from './behaviors/bind_in_out';
import GroupsList from './groups_list';
import ProjectsList from './projects_list';
+import MiniPipelineGraph from './mini_pipeline_graph_dropdown';
+import BlobLinePermalinkUpdater from './blob/blob_line_permalink_updater';
const ShortcutsBlob = require('./shortcuts_blob');
const UserCallout = require('./user_callout');
@@ -58,13 +60,32 @@ const UserCallout = require('./user_callout');
}
Dispatcher.prototype.initPageScripts = function() {
- var page, path, shortcut_handler;
+ var page, path, shortcut_handler, fileBlobPermalinkUrlElement, fileBlobPermalinkUrl;
page = $('body').attr('data-page');
if (!page) {
return false;
}
path = page.split(':');
shortcut_handler = null;
+
+ function initBlob() {
+ new LineHighlighter();
+
+ new BlobLinePermalinkUpdater(
+ document.querySelector('#blob-content-holder'),
+ '.diff-line-num[data-line-number]',
+ document.querySelectorAll('.js-data-file-blob-permalink-url, .js-blob-blame-link'),
+ );
+
+ shortcut_handler = new ShortcutsNavigation();
+ fileBlobPermalinkUrlElement = document.querySelector('.js-data-file-blob-permalink-url');
+ fileBlobPermalinkUrl = fileBlobPermalinkUrlElement && fileBlobPermalinkUrlElement.getAttribute('href');
+ new ShortcutsBlob({
+ skipResetBindings: true,
+ fileBlobPermalinkUrl,
+ });
+ }
+
switch (page) {
case 'sessions:new':
new UsernameValidator();
@@ -181,7 +202,7 @@ const UserCallout = require('./user_callout');
shortcut_handler = new ShortcutsNavigation();
break;
case 'projects:commit:pipelines':
- new gl.MiniPipelineGraph({
+ new MiniPipelineGraph({
container: '.js-pipeline-table',
}).bindEvents();
break;
@@ -244,20 +265,26 @@ const UserCallout = require('./user_callout');
case 'projects:tree:show':
shortcut_handler = new ShortcutsNavigation();
new TreeView();
+ gl.TargetBranchDropDown.bootstrap();
break;
case 'projects:find_file:show':
shortcut_handler = true;
break;
+ case 'projects:blob:new':
+ gl.TargetBranchDropDown.bootstrap();
+ break;
+ case 'projects:blob:create':
+ gl.TargetBranchDropDown.bootstrap();
+ break;
case 'projects:blob:show':
+ gl.TargetBranchDropDown.bootstrap();
+ initBlob();
+ break;
+ case 'projects:blob:edit':
+ gl.TargetBranchDropDown.bootstrap();
+ break;
case 'projects:blame:show':
- new LineHighlighter();
- shortcut_handler = new ShortcutsNavigation();
- const fileBlobPermalinkUrlElement = document.querySelector('.js-data-file-blob-permalink-url');
- const fileBlobPermalinkUrl = fileBlobPermalinkUrlElement && fileBlobPermalinkUrlElement.getAttribute('href');
- new ShortcutsBlob({
- skipResetBindings: true,
- fileBlobPermalinkUrl,
- });
+ initBlob();
break;
case 'groups:labels:new':
case 'groups:labels:edit':
diff --git a/app/assets/javascripts/droplab/droplab_ajax.js b/app/assets/javascripts/droplab/droplab_ajax.js
index f61be741b4a..020f8b4ac65 100644
--- a/app/assets/javascripts/droplab/droplab_ajax.js
+++ b/app/assets/javascripts/droplab/droplab_ajax.js
@@ -74,6 +74,9 @@ require('../window')(function(w){
this._loadUrlData(config.endpoint)
.then(function(d) {
self._loadData(d, config, self);
+ }, function(xhrError) {
+ // TODO: properly handle errors due to XHR cancellation
+ return;
}).catch(function(e) {
throw new droplabAjaxException(e.message || e);
});
diff --git a/app/assets/javascripts/droplab/droplab_ajax_filter.js b/app/assets/javascripts/droplab/droplab_ajax_filter.js
index b63d73066cb..05eba7aef56 100644
--- a/app/assets/javascripts/droplab/droplab_ajax_filter.js
+++ b/app/assets/javascripts/droplab/droplab_ajax_filter.js
@@ -82,6 +82,9 @@ require('../window')(function(w){
this._loadUrlData(url)
.then(function(data) {
self._loadData(data, config, self);
+ }, function(xhrError) {
+ // TODO: properly handle errors due to XHR cancellation
+ return;
});
}
},
diff --git a/app/assets/javascripts/dropzone_input.js b/app/assets/javascripts/dropzone_input.js
index 646f836aff0..f2963a5eb19 100644
--- a/app/assets/javascripts/dropzone_input.js
+++ b/app/assets/javascripts/dropzone_input.js
@@ -3,218 +3,216 @@
require('./preview_markdown');
-(function() {
- this.DropzoneInput = (function() {
- function DropzoneInput(form) {
- var $mdArea, alertAttr, alertClass, appendToTextArea, btnAlert, child, closeAlertMessage, closeSpinner, divAlert, divHover, divSpinner, dropzone, form_dropzone, form_textarea, getFilename, handlePaste, iconPaperclip, iconSpinner, insertToTextArea, isImage, max_file_size, pasteText, project_uploads_path, showError, showSpinner, uploadFile, uploadProgress;
- Dropzone.autoDiscover = false;
- alertClass = "alert alert-danger alert-dismissable div-dropzone-alert";
- alertAttr = "class=\"close\" data-dismiss=\"alert\"" + "aria-hidden=\"true\"";
- divHover = "<div class=\"div-dropzone-hover\"></div>";
- divSpinner = "<div class=\"div-dropzone-spinner\"></div>";
- divAlert = "<div class=\"" + alertClass + "\"></div>";
- iconPaperclip = "<i class=\"fa fa-paperclip div-dropzone-icon\"></i>";
- iconSpinner = "<i class=\"fa fa-spinner fa-spin div-dropzone-icon\"></i>";
- uploadProgress = $("<div class=\"div-dropzone-progress\"></div>");
- btnAlert = "<button type=\"button\"" + alertAttr + ">&times;</button>";
- project_uploads_path = window.project_uploads_path || null;
- max_file_size = gon.max_file_size || 10;
- form_textarea = $(form).find(".js-gfm-input");
- form_textarea.wrap("<div class=\"div-dropzone\"></div>");
- form_textarea.on('paste', (function(_this) {
- return function(event) {
- return handlePaste(event);
- };
- })(this));
- $mdArea = $(form_textarea).closest('.md-area');
- $(form).setupMarkdownPreview();
- form_dropzone = $(form).find('.div-dropzone');
- form_dropzone.parent().addClass("div-dropzone-wrapper");
- form_dropzone.append(divHover);
- form_dropzone.find(".div-dropzone-hover").append(iconPaperclip);
- form_dropzone.append(divSpinner);
- form_dropzone.find(".div-dropzone-spinner").append(iconSpinner);
- form_dropzone.find(".div-dropzone-spinner").append(uploadProgress);
- form_dropzone.find(".div-dropzone-spinner").css({
- "opacity": 0,
- "display": "none"
- });
- dropzone = form_dropzone.dropzone({
- url: project_uploads_path,
- dictDefaultMessage: "",
- clickable: true,
- paramName: "file",
- maxFilesize: max_file_size,
- uploadMultiple: false,
- headers: {
- "X-CSRF-Token": $("meta[name=\"csrf-token\"]").attr("content")
- },
- previewContainer: false,
- processing: function() {
- return $(".div-dropzone-alert").alert("close");
- },
- dragover: function() {
- $mdArea.addClass('is-dropzone-hover');
- form.find(".div-dropzone-hover").css("opacity", 0.7);
- },
- dragleave: function() {
- $mdArea.removeClass('is-dropzone-hover');
- form.find(".div-dropzone-hover").css("opacity", 0);
- },
- drop: function() {
- $mdArea.removeClass('is-dropzone-hover');
- form.find(".div-dropzone-hover").css("opacity", 0);
- form_textarea.focus();
- },
- success: function(header, response) {
- pasteText(response.link.markdown);
- },
- error: function(temp) {
- var checkIfMsgExists, errorAlert;
- errorAlert = $(form).find('.error-alert');
- checkIfMsgExists = errorAlert.children().length;
- if (checkIfMsgExists === 0) {
- errorAlert.append(divAlert);
- $(".div-dropzone-alert").append(btnAlert + "Attaching the file failed.");
- }
- },
- totaluploadprogress: function(totalUploadProgress) {
- uploadProgress.text(Math.round(totalUploadProgress) + "%");
- },
- sending: function() {
- form_dropzone.find(".div-dropzone-spinner").css({
- "opacity": 0.7,
- "display": "inherit"
- });
- },
- queuecomplete: function() {
- uploadProgress.text("");
- $(".dz-preview").remove();
- $(".markdown-area").trigger("input");
- $(".div-dropzone-spinner").css({
- "opacity": 0,
- "display": "none"
- });
- }
- });
- child = $(dropzone[0]).children("textarea");
- handlePaste = function(event) {
- var filename, image, pasteEvent, text;
- pasteEvent = event.originalEvent;
- if (pasteEvent.clipboardData && pasteEvent.clipboardData.items) {
- image = isImage(pasteEvent);
- if (image) {
- event.preventDefault();
- filename = getFilename(pasteEvent) || "image.png";
- text = "{{" + filename + "}}";
- pasteText(text);
- return uploadFile(image.getAsFile(), filename);
- }
- }
+window.DropzoneInput = (function() {
+ function DropzoneInput(form) {
+ var $mdArea, alertAttr, alertClass, appendToTextArea, btnAlert, child, closeAlertMessage, closeSpinner, divAlert, divHover, divSpinner, dropzone, form_dropzone, form_textarea, getFilename, handlePaste, iconPaperclip, iconSpinner, insertToTextArea, isImage, max_file_size, pasteText, project_uploads_path, showError, showSpinner, uploadFile, uploadProgress;
+ Dropzone.autoDiscover = false;
+ alertClass = "alert alert-danger alert-dismissable div-dropzone-alert";
+ alertAttr = "class=\"close\" data-dismiss=\"alert\"" + "aria-hidden=\"true\"";
+ divHover = "<div class=\"div-dropzone-hover\"></div>";
+ divSpinner = "<div class=\"div-dropzone-spinner\"></div>";
+ divAlert = "<div class=\"" + alertClass + "\"></div>";
+ iconPaperclip = "<i class=\"fa fa-paperclip div-dropzone-icon\"></i>";
+ iconSpinner = "<i class=\"fa fa-spinner fa-spin div-dropzone-icon\"></i>";
+ uploadProgress = $("<div class=\"div-dropzone-progress\"></div>");
+ btnAlert = "<button type=\"button\"" + alertAttr + ">&times;</button>";
+ project_uploads_path = window.project_uploads_path || null;
+ max_file_size = gon.max_file_size || 10;
+ form_textarea = $(form).find(".js-gfm-input");
+ form_textarea.wrap("<div class=\"div-dropzone\"></div>");
+ form_textarea.on('paste', (function(_this) {
+ return function(event) {
+ return handlePaste(event);
};
- isImage = function(data) {
- var i, item;
- i = 0;
- while (i < data.clipboardData.items.length) {
- item = data.clipboardData.items[i];
- if (item.type.indexOf("image") !== -1) {
- return item;
- }
- i += 1;
- }
- return false;
- };
- pasteText = function(text) {
- var afterSelection, beforeSelection, caretEnd, caretStart, textEnd;
- var formattedText = text + "\n\n";
- caretStart = $(child)[0].selectionStart;
- caretEnd = $(child)[0].selectionEnd;
- textEnd = $(child).val().length;
- beforeSelection = $(child).val().substring(0, caretStart);
- afterSelection = $(child).val().substring(caretEnd, textEnd);
- $(child).val(beforeSelection + formattedText + afterSelection);
- child.get(0).setSelectionRange(caretStart + formattedText.length, caretEnd + formattedText.length);
- return form_textarea.trigger("input");
- };
- getFilename = function(e) {
- var value;
- if (window.clipboardData && window.clipboardData.getData) {
- value = window.clipboardData.getData("Text");
- } else if (e.clipboardData && e.clipboardData.getData) {
- value = e.clipboardData.getData("text/plain");
+ })(this));
+ $mdArea = $(form_textarea).closest('.md-area');
+ $(form).setupMarkdownPreview();
+ form_dropzone = $(form).find('.div-dropzone');
+ form_dropzone.parent().addClass("div-dropzone-wrapper");
+ form_dropzone.append(divHover);
+ form_dropzone.find(".div-dropzone-hover").append(iconPaperclip);
+ form_dropzone.append(divSpinner);
+ form_dropzone.find(".div-dropzone-spinner").append(iconSpinner);
+ form_dropzone.find(".div-dropzone-spinner").append(uploadProgress);
+ form_dropzone.find(".div-dropzone-spinner").css({
+ "opacity": 0,
+ "display": "none"
+ });
+ dropzone = form_dropzone.dropzone({
+ url: project_uploads_path,
+ dictDefaultMessage: "",
+ clickable: true,
+ paramName: "file",
+ maxFilesize: max_file_size,
+ uploadMultiple: false,
+ headers: {
+ "X-CSRF-Token": $("meta[name=\"csrf-token\"]").attr("content")
+ },
+ previewContainer: false,
+ processing: function() {
+ return $(".div-dropzone-alert").alert("close");
+ },
+ dragover: function() {
+ $mdArea.addClass('is-dropzone-hover');
+ form.find(".div-dropzone-hover").css("opacity", 0.7);
+ },
+ dragleave: function() {
+ $mdArea.removeClass('is-dropzone-hover');
+ form.find(".div-dropzone-hover").css("opacity", 0);
+ },
+ drop: function() {
+ $mdArea.removeClass('is-dropzone-hover');
+ form.find(".div-dropzone-hover").css("opacity", 0);
+ form_textarea.focus();
+ },
+ success: function(header, response) {
+ pasteText(response.link.markdown);
+ },
+ error: function(temp) {
+ var checkIfMsgExists, errorAlert;
+ errorAlert = $(form).find('.error-alert');
+ checkIfMsgExists = errorAlert.children().length;
+ if (checkIfMsgExists === 0) {
+ errorAlert.append(divAlert);
+ $(".div-dropzone-alert").append(btnAlert + "Attaching the file failed.");
}
- value = value.split("\r");
- return value.first();
- };
- uploadFile = function(item, filename) {
- var formData;
- formData = new FormData();
- formData.append("file", item, filename);
- return $.ajax({
- url: project_uploads_path,
- type: "POST",
- data: formData,
- dataType: "json",
- processData: false,
- contentType: false,
- headers: {
- "X-CSRF-Token": $("meta[name=\"csrf-token\"]").attr("content")
- },
- beforeSend: function() {
- showSpinner();
- return closeAlertMessage();
- },
- success: function(e, textStatus, response) {
- return insertToTextArea(filename, response.responseJSON.link.markdown);
- },
- error: function(response) {
- return showError(response.responseJSON.message);
- },
- complete: function() {
- return closeSpinner();
- }
- });
- };
- insertToTextArea = function(filename, url) {
- return $(child).val(function(index, val) {
- return val.replace("{{" + filename + "}}", url + "\n");
- });
- };
- appendToTextArea = function(url) {
- return $(child).val(function(index, val) {
- return val + url + "\n";
- });
- };
- showSpinner = function(e) {
- return form.find(".div-dropzone-spinner").css({
+ },
+ totaluploadprogress: function(totalUploadProgress) {
+ uploadProgress.text(Math.round(totalUploadProgress) + "%");
+ },
+ sending: function() {
+ form_dropzone.find(".div-dropzone-spinner").css({
"opacity": 0.7,
"display": "inherit"
});
- };
- closeSpinner = function() {
- return form.find(".div-dropzone-spinner").css({
+ },
+ queuecomplete: function() {
+ uploadProgress.text("");
+ $(".dz-preview").remove();
+ $(".markdown-area").trigger("input");
+ $(".div-dropzone-spinner").css({
"opacity": 0,
"display": "none"
});
- };
- showError = function(message) {
- var checkIfMsgExists, errorAlert;
- errorAlert = $(form).find('.error-alert');
- checkIfMsgExists = errorAlert.children().length;
- if (checkIfMsgExists === 0) {
- errorAlert.append(divAlert);
- return $(".div-dropzone-alert").append(btnAlert + message);
+ }
+ });
+ child = $(dropzone[0]).children("textarea");
+ handlePaste = function(event) {
+ var filename, image, pasteEvent, text;
+ pasteEvent = event.originalEvent;
+ if (pasteEvent.clipboardData && pasteEvent.clipboardData.items) {
+ image = isImage(pasteEvent);
+ if (image) {
+ event.preventDefault();
+ filename = getFilename(pasteEvent) || "image.png";
+ text = "{{" + filename + "}}";
+ pasteText(text);
+ return uploadFile(image.getAsFile(), filename);
}
- };
- closeAlertMessage = function() {
- return form.find(".div-dropzone-alert").alert("close");
- };
- form.find(".markdown-selector").click(function(e) {
- e.preventDefault();
- $(this).closest('.gfm-form').find('.div-dropzone').click();
+ }
+ };
+ isImage = function(data) {
+ var i, item;
+ i = 0;
+ while (i < data.clipboardData.items.length) {
+ item = data.clipboardData.items[i];
+ if (item.type.indexOf("image") !== -1) {
+ return item;
+ }
+ i += 1;
+ }
+ return false;
+ };
+ pasteText = function(text) {
+ var afterSelection, beforeSelection, caretEnd, caretStart, textEnd;
+ var formattedText = text + "\n\n";
+ caretStart = $(child)[0].selectionStart;
+ caretEnd = $(child)[0].selectionEnd;
+ textEnd = $(child).val().length;
+ beforeSelection = $(child).val().substring(0, caretStart);
+ afterSelection = $(child).val().substring(caretEnd, textEnd);
+ $(child).val(beforeSelection + formattedText + afterSelection);
+ child.get(0).setSelectionRange(caretStart + formattedText.length, caretEnd + formattedText.length);
+ return form_textarea.trigger("input");
+ };
+ getFilename = function(e) {
+ var value;
+ if (window.clipboardData && window.clipboardData.getData) {
+ value = window.clipboardData.getData("Text");
+ } else if (e.clipboardData && e.clipboardData.getData) {
+ value = e.clipboardData.getData("text/plain");
+ }
+ value = value.split("\r");
+ return value.first();
+ };
+ uploadFile = function(item, filename) {
+ var formData;
+ formData = new FormData();
+ formData.append("file", item, filename);
+ return $.ajax({
+ url: project_uploads_path,
+ type: "POST",
+ data: formData,
+ dataType: "json",
+ processData: false,
+ contentType: false,
+ headers: {
+ "X-CSRF-Token": $("meta[name=\"csrf-token\"]").attr("content")
+ },
+ beforeSend: function() {
+ showSpinner();
+ return closeAlertMessage();
+ },
+ success: function(e, textStatus, response) {
+ return insertToTextArea(filename, response.responseJSON.link.markdown);
+ },
+ error: function(response) {
+ return showError(response.responseJSON.message);
+ },
+ complete: function() {
+ return closeSpinner();
+ }
+ });
+ };
+ insertToTextArea = function(filename, url) {
+ return $(child).val(function(index, val) {
+ return val.replace("{{" + filename + "}}", url + "\n");
+ });
+ };
+ appendToTextArea = function(url) {
+ return $(child).val(function(index, val) {
+ return val + url + "\n";
+ });
+ };
+ showSpinner = function(e) {
+ return form.find(".div-dropzone-spinner").css({
+ "opacity": 0.7,
+ "display": "inherit"
+ });
+ };
+ closeSpinner = function() {
+ return form.find(".div-dropzone-spinner").css({
+ "opacity": 0,
+ "display": "none"
});
- }
+ };
+ showError = function(message) {
+ var checkIfMsgExists, errorAlert;
+ errorAlert = $(form).find('.error-alert');
+ checkIfMsgExists = errorAlert.children().length;
+ if (checkIfMsgExists === 0) {
+ errorAlert.append(divAlert);
+ return $(".div-dropzone-alert").append(btnAlert + message);
+ }
+ };
+ closeAlertMessage = function() {
+ return form.find(".div-dropzone-alert").alert("close");
+ };
+ form.find(".markdown-selector").click(function(e) {
+ e.preventDefault();
+ $(this).closest('.gfm-form').find('.div-dropzone').click();
+ });
+ }
- return DropzoneInput;
- })();
-}).call(window);
+ return DropzoneInput;
+})();
diff --git a/app/assets/javascripts/due_date_select.js b/app/assets/javascripts/due_date_select.js
index 9169fcd7328..fdbb4644971 100644
--- a/app/assets/javascripts/due_date_select.js
+++ b/app/assets/javascripts/due_date_select.js
@@ -2,203 +2,202 @@
/* global dateFormat */
/* global Pikaday */
-(function(global) {
- class DueDateSelect {
- constructor({ $dropdown, $loading } = {}) {
- const $dropdownParent = $dropdown.closest('.dropdown');
- const $block = $dropdown.closest('.block');
- this.$loading = $loading;
- this.$dropdown = $dropdown;
- this.$dropdownParent = $dropdownParent;
- this.$datePicker = $dropdownParent.find('.js-due-date-calendar');
- this.$block = $block;
- this.$selectbox = $dropdown.closest('.selectbox');
- this.$value = $block.find('.value');
- this.$valueContent = $block.find('.value-content');
- 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.rawSelectedDate = null;
- this.displayedDate = null;
- this.datePayload = null;
-
- this.initGlDropdown();
- this.initRemoveDueDate();
- this.initDatePicker();
- }
-
- initGlDropdown() {
- this.$dropdown.glDropdown({
- opened: () => {
- const calendar = this.$datePicker.data('pikaday');
- calendar.show();
- },
- hidden: () => {
- this.$selectbox.hide();
- this.$value.css('display', '');
- }
- });
- }
-
- initDatePicker() {
- const $dueDateInput = $(`input[name='${this.fieldName}']`);
-
- const calendar = new Pikaday({
- field: $dueDateInput.get(0),
- theme: 'gitlab-theme',
- format: 'yyyy-mm-dd',
- onSelect: (dateText) => {
- const formattedDate = dateFormat(new Date(dateText), 'yyyy-mm-dd');
-
- $dueDateInput.val(formattedDate);
+class DueDateSelect {
+ constructor({ $dropdown, $loading } = {}) {
+ const $dropdownParent = $dropdown.closest('.dropdown');
+ const $block = $dropdown.closest('.block');
+ this.$loading = $loading;
+ this.$dropdown = $dropdown;
+ this.$dropdownParent = $dropdownParent;
+ this.$datePicker = $dropdownParent.find('.js-due-date-calendar');
+ this.$block = $block;
+ this.$selectbox = $dropdown.closest('.selectbox');
+ this.$value = $block.find('.value');
+ this.$valueContent = $block.find('.value-content');
+ 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.rawSelectedDate = null;
+ this.displayedDate = null;
+ this.datePayload = null;
+
+ this.initGlDropdown();
+ this.initRemoveDueDate();
+ this.initDatePicker();
+ }
- if (this.$dropdown.hasClass('js-issue-boards-due-date')) {
- gl.issueBoards.BoardsStore.detail.issue.dueDate = $dueDateInput.val();
- this.updateIssueBoardIssue();
- } else {
- this.saveDueDate(true);
- }
- }
- });
+ initGlDropdown() {
+ this.$dropdown.glDropdown({
+ opened: () => {
+ const calendar = this.$datePicker.data('pikaday');
+ calendar.show();
+ },
+ hidden: () => {
+ this.$selectbox.hide();
+ this.$value.css('display', '');
+ }
+ });
+ }
- calendar.setDate(new Date($dueDateInput.val()));
- this.$datePicker.append(calendar.el);
- this.$datePicker.data('pikaday', calendar);
- }
+ initDatePicker() {
+ const $dueDateInput = $(`input[name='${this.fieldName}']`);
- initRemoveDueDate() {
- this.$block.on('click', '.js-remove-due-date', (e) => {
- const calendar = this.$datePicker.data('pikaday');
- e.preventDefault();
+ const calendar = new Pikaday({
+ field: $dueDateInput.get(0),
+ theme: 'gitlab-theme',
+ format: 'yyyy-mm-dd',
+ onSelect: (dateText) => {
+ const formattedDate = dateFormat(new Date(dateText), 'yyyy-mm-dd');
- calendar.setDate(null);
+ $dueDateInput.val(formattedDate);
if (this.$dropdown.hasClass('js-issue-boards-due-date')) {
- gl.issueBoards.BoardsStore.detail.issue.dueDate = '';
+ gl.issueBoards.BoardsStore.detail.issue.dueDate = $dueDateInput.val();
this.updateIssueBoardIssue();
} else {
- $("input[name='" + this.fieldName + "']").val('');
- return this.saveDueDate(false);
+ this.saveDueDate(true);
}
- });
- }
+ }
+ });
- saveDueDate(isDropdown) {
- this.parseSelectedDate();
- this.prepSelectedDate();
- this.submitSelectedDate(isDropdown);
- }
+ calendar.setDate(new Date($dueDateInput.val()));
+ this.$datePicker.append(calendar.el);
+ this.$datePicker.data('pikaday', calendar);
+ }
+
+ initRemoveDueDate() {
+ this.$block.on('click', '.js-remove-due-date', (e) => {
+ const calendar = this.$datePicker.data('pikaday');
+ e.preventDefault();
- parseSelectedDate() {
- this.rawSelectedDate = $(`input[name='${this.fieldName}']`).val();
+ calendar.setDate(null);
- if (this.rawSelectedDate.length) {
- // 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 = dateFormat(dateObj, 'mmm d, yyyy');
+ if (this.$dropdown.hasClass('js-issue-boards-due-date')) {
+ gl.issueBoards.BoardsStore.detail.issue.dueDate = '';
+ this.updateIssueBoardIssue();
} else {
- this.displayedDate = 'No due date';
+ $("input[name='" + this.fieldName + "']").val('');
+ return this.saveDueDate(false);
}
- }
+ });
+ }
- prepSelectedDate() {
- const datePayload = {};
- datePayload[this.abilityName] = {};
- datePayload[this.abilityName].due_date = this.rawSelectedDate;
- this.datePayload = datePayload;
- }
+ saveDueDate(isDropdown) {
+ this.parseSelectedDate();
+ this.prepSelectedDate();
+ this.submitSelectedDate(isDropdown);
+ }
- updateIssueBoardIssue () {
- this.$loading.fadeIn();
- this.$dropdown.trigger('loading.gl.dropdown');
- this.$selectbox.hide();
- this.$value.css('display', '');
+ parseSelectedDate() {
+ this.rawSelectedDate = $(`input[name='${this.fieldName}']`).val();
- gl.issueBoards.BoardsStore.detail.issue.update(this.$dropdown.attr('data-issue-update'))
- .then(() => {
- this.$loading.fadeOut();
- });
+ if (this.rawSelectedDate.length) {
+ // 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 = dateFormat(dateObj, 'mmm d, yyyy');
+ } else {
+ this.displayedDate = 'No due date';
}
+ }
+
+ prepSelectedDate() {
+ const datePayload = {};
+ datePayload[this.abilityName] = {};
+ datePayload[this.abilityName].due_date = this.rawSelectedDate;
+ this.datePayload = datePayload;
+ }
+
+ updateIssueBoardIssue () {
+ this.$loading.fadeIn();
+ this.$dropdown.trigger('loading.gl.dropdown');
+ this.$selectbox.hide();
+ this.$value.css('display', '');
+
+ gl.issueBoards.BoardsStore.detail.issue.update(this.$dropdown.attr('data-issue-update'))
+ .then(() => {
+ this.$loading.fadeOut();
+ });
+ }
+
+ submitSelectedDate(isDropdown) {
+ return $.ajax({
+ type: 'PUT',
+ url: this.issueUpdateURL,
+ data: this.datePayload,
+ dataType: 'json',
+ beforeSend: () => {
+ const selectedDateValue = this.datePayload[this.abilityName].due_date;
+ const displayedDateStyle = this.displayedDate !== 'No due date' ? 'bold' : 'no-value';
+
+ this.$loading.fadeIn();
- submitSelectedDate(isDropdown) {
- return $.ajax({
- type: 'PUT',
- url: this.issueUpdateURL,
- data: this.datePayload,
- dataType: 'json',
- beforeSend: () => {
- const selectedDateValue = this.datePayload[this.abilityName].due_date;
- const displayedDateStyle = this.displayedDate !== 'No due date' ? 'bold' : 'no-value';
-
- this.$loading.fadeIn();
-
- if (isDropdown) {
- this.$dropdown.trigger('loading.gl.dropdown');
- this.$selectbox.hide();
- }
-
- this.$value.css('display', '');
- this.$valueContent.html(`<span class='${displayedDateStyle}'>${this.displayedDate}</span>`);
- this.$sidebarValue.html(this.displayedDate);
-
- return selectedDateValue.length ?
- $('.js-remove-due-date-holder').removeClass('hidden') :
- $('.js-remove-due-date-holder').addClass('hidden');
- }
- }).done((data) => {
if (isDropdown) {
- this.$dropdown.trigger('loaded.gl.dropdown');
- this.$dropdown.dropdown('toggle');
+ this.$dropdown.trigger('loading.gl.dropdown');
+ this.$selectbox.hide();
}
- return this.$loading.fadeOut();
- });
- }
+
+ this.$value.css('display', '');
+ this.$valueContent.html(`<span class='${displayedDateStyle}'>${this.displayedDate}</span>`);
+ this.$sidebarValue.html(this.displayedDate);
+
+ return selectedDateValue.length ?
+ $('.js-remove-due-date-holder').removeClass('hidden') :
+ $('.js-remove-due-date-holder').addClass('hidden');
+ }
+ }).done((data) => {
+ if (isDropdown) {
+ this.$dropdown.trigger('loaded.gl.dropdown');
+ this.$dropdown.dropdown('toggle');
+ }
+ return this.$loading.fadeOut();
+ });
}
+}
- class DueDateSelectors {
- constructor() {
- this.initMilestoneDatePicker();
- this.initIssuableSelect();
- }
+class DueDateSelectors {
+ constructor() {
+ this.initMilestoneDatePicker();
+ this.initIssuableSelect();
+ }
- initMilestoneDatePicker() {
- $('.datepicker').each(function() {
- const $datePicker = $(this);
- const calendar = new Pikaday({
- field: $datePicker.get(0),
- theme: 'gitlab-theme',
- format: 'yyyy-mm-dd',
- onSelect(dateText) {
- $datePicker.val(dateFormat(new Date(dateText), 'yyyy-mm-dd'));
- }
- });
- calendar.setDate(new Date($datePicker.val()));
-
- $datePicker.data('pikaday', calendar);
+ initMilestoneDatePicker() {
+ $('.datepicker').each(function() {
+ const $datePicker = $(this);
+ const calendar = new Pikaday({
+ field: $datePicker.get(0),
+ theme: 'gitlab-theme',
+ format: 'yyyy-mm-dd',
+ onSelect(dateText) {
+ $datePicker.val(dateFormat(new Date(dateText), 'yyyy-mm-dd'));
+ }
});
+ calendar.setDate(new Date($datePicker.val()));
- $('.js-clear-due-date,.js-clear-start-date').on('click', (e) => {
- e.preventDefault();
- const calendar = $(e.target).siblings('.datepicker').data('pikaday');
- calendar.setDate(null);
- });
- }
+ $datePicker.data('pikaday', calendar);
+ });
- initIssuableSelect() {
- const $loading = $('.js-issuable-update .due_date').find('.block-loading').hide();
+ $('.js-clear-due-date,.js-clear-start-date').on('click', (e) => {
+ e.preventDefault();
+ const calendar = $(e.target).siblings('.datepicker').data('pikaday');
+ calendar.setDate(null);
+ });
+ }
+
+ initIssuableSelect() {
+ const $loading = $('.js-issuable-update .due_date').find('.block-loading').hide();
- $('.js-due-date-select').each((i, dropdown) => {
- const $dropdown = $(dropdown);
- new DueDateSelect({
- $dropdown,
- $loading
- });
+ $('.js-due-date-select').each((i, dropdown) => {
+ const $dropdown = $(dropdown);
+ new DueDateSelect({
+ $dropdown,
+ $loading
});
- }
+ });
}
+}
- global.DueDateSelectors = DueDateSelectors;
-})(window.gl || (window.gl = {}));
+window.gl = window.gl || {};
+window.gl.DueDateSelectors = DueDateSelectors;
diff --git a/app/assets/javascripts/environments/components/environment.js b/app/assets/javascripts/environments/components/environment.js
index 2cb48dde628..0923ce6b550 100644
--- a/app/assets/javascripts/environments/components/environment.js
+++ b/app/assets/javascripts/environments/components/environment.js
@@ -1,16 +1,17 @@
/* eslint-disable no-param-reassign, no-new */
/* global Flash */
+import EnvironmentsService from '../services/environments_service';
+import EnvironmentTable from './environments_table';
+import EnvironmentsStore from '../stores/environments_store';
+import eventHub from '../event_hub';
const Vue = window.Vue = require('vue');
window.Vue.use(require('vue-resource'));
-const EnvironmentsService = require('../services/environments_service');
-const EnvironmentTable = require('./environments_table');
-const EnvironmentsStore = require('../stores/environments_store');
require('../../vue_shared/components/table_pagination');
require('../../lib/utils/common_utils');
require('../../vue_shared/vue_resource_interceptor');
-module.exports = Vue.component('environment-component', {
+export default Vue.component('environment-component', {
components: {
'environment-table': EnvironmentTable,
@@ -66,33 +67,15 @@ module.exports = Vue.component('environment-component', {
* Toggles loading property.
*/
created() {
- const scope = gl.utils.getParameterByName('scope') || this.visibility;
- const pageNumber = gl.utils.getParameterByName('page') || this.pageNumber;
-
- const endpoint = `${this.endpoint}?scope=${scope}&page=${pageNumber}`;
-
- const service = new EnvironmentsService(endpoint);
-
- this.isLoading = true;
-
- return service.get()
- .then(resp => ({
- headers: resp.headers,
- body: resp.json(),
- }))
- .then((response) => {
- this.store.storeAvailableCount(response.body.available_count);
- this.store.storeStoppedCount(response.body.stopped_count);
- this.store.storeEnvironments(response.body.environments);
- this.store.setPagination(response.headers);
- })
- .then(() => {
- this.isLoading = false;
- })
- .catch(() => {
- this.isLoading = false;
- new Flash('An error occurred while fetching the environments.', 'alert');
- });
+ this.service = new EnvironmentsService(this.endpoint);
+
+ this.fetchEnvironments();
+
+ eventHub.$on('refreshEnvironments', this.fetchEnvironments);
+ },
+
+ beforeDestroyed() {
+ eventHub.$off('refreshEnvironments');
},
methods: {
@@ -112,6 +95,32 @@ module.exports = Vue.component('environment-component', {
gl.utils.visitUrl(param);
return param;
},
+
+ fetchEnvironments() {
+ const scope = gl.utils.getParameterByName('scope') || this.visibility;
+ const pageNumber = gl.utils.getParameterByName('page') || this.pageNumber;
+
+ this.isLoading = true;
+
+ return this.service.get(scope, pageNumber)
+ .then(resp => ({
+ headers: resp.headers,
+ body: resp.json(),
+ }))
+ .then((response) => {
+ this.store.storeAvailableCount(response.body.available_count);
+ this.store.storeStoppedCount(response.body.stopped_count);
+ this.store.storeEnvironments(response.body.environments);
+ this.store.setPagination(response.headers);
+ })
+ .then(() => {
+ this.isLoading = false;
+ })
+ .catch(() => {
+ this.isLoading = false;
+ new Flash('An error occurred while fetching the environments.');
+ });
+ },
},
template: `
@@ -144,7 +153,7 @@ module.exports = Vue.component('environment-component', {
<div class="content-list environments-container">
<div class="environments-list-loading text-center" v-if="isLoading">
- <i class="fa fa-spinner fa-spin"></i>
+ <i class="fa fa-spinner fa-spin" aria-hidden="true"></i>
</div>
<div class="blank-state blank-state-no-icon"
@@ -173,7 +182,8 @@ module.exports = Vue.component('environment-component', {
<environment-table
:environments="state.environments"
:can-create-deployment="canCreateDeploymentParsed"
- :can-read-environment="canReadEnvironmentParsed"/>
+ :can-read-environment="canReadEnvironmentParsed"
+ :service="service"/>
</div>
<table-pagination v-if="state.paginationInformation && state.paginationInformation.totalPages > 1"
diff --git a/app/assets/javascripts/environments/components/environment_actions.js b/app/assets/javascripts/environments/components/environment_actions.js
index 15e3f8823d2..455a8819549 100644
--- a/app/assets/javascripts/environments/components/environment_actions.js
+++ b/app/assets/javascripts/environments/components/environment_actions.js
@@ -1,41 +1,71 @@
-const Vue = require('vue');
-const playIconSvg = require('icons/_icon_play.svg');
+/* global Flash */
+/* eslint-disable no-new */
-module.exports = Vue.component('actions-component', {
+import playIconSvg from 'icons/_icon_play.svg';
+import eventHub from '../event_hub';
+
+export default {
props: {
actions: {
type: Array,
required: false,
default: () => [],
},
+
+ service: {
+ type: Object,
+ required: true,
+ },
},
data() {
- return { playIconSvg };
+ return {
+ playIconSvg,
+ isLoading: false,
+ };
+ },
+
+ methods: {
+ onClickAction(endpoint) {
+ this.isLoading = true;
+
+ this.service.postAction(endpoint)
+ .then(() => {
+ this.isLoading = false;
+ eventHub.$emit('refreshEnvironments');
+ })
+ .catch(() => {
+ this.isLoading = false;
+ new Flash('An error occured while making the request.');
+ });
+ },
},
template: `
<div class="btn-group" role="group">
- <button class="dropdown btn btn-default dropdown-new" data-toggle="dropdown">
+ <button
+ class="dropdown btn btn-default dropdown-new js-dropdown-play-icon-container"
+ data-toggle="dropdown"
+ :disabled="isLoading">
<span>
- <span class="js-dropdown-play-icon-container" v-html="playIconSvg"></span>
- <i class="fa fa-caret-down"></i>
+ <span v-html="playIconSvg"></span>
+ <i class="fa fa-caret-down" aria-hidden="true"></i>
+ <i v-if="isLoading" class="fa fa-spinner fa-spin" aria-hidden="true"></i>
</span>
<ul class="dropdown-menu dropdown-menu-align-right">
<li v-for="action in actions">
- <a :href="action.play_path"
- data-method="post"
- rel="nofollow"
- class="js-manual-action-link">
+ <button
+ @click="onClickAction(action.play_path)"
+ class="js-manual-action-link no-btn">
${playIconSvg}
<span>
{{action.name}}
</span>
- </a>
+ </button>
</li>
</ul>
</button>
</div>
`,
-});
+};
diff --git a/app/assets/javascripts/environments/components/environment_external_url.js b/app/assets/javascripts/environments/components/environment_external_url.js
index 2599bba3c59..a554998f52c 100644
--- a/app/assets/javascripts/environments/components/environment_external_url.js
+++ b/app/assets/javascripts/environments/components/environment_external_url.js
@@ -1,9 +1,7 @@
/**
* Renders the external url link in environments table.
*/
-const Vue = require('vue');
-
-module.exports = Vue.component('external-url-component', {
+export default {
props: {
externalUrl: {
type: String,
@@ -12,8 +10,12 @@ module.exports = Vue.component('external-url-component', {
},
template: `
- <a class="btn external_url" :href="externalUrl" target="_blank">
- <i class="fa fa-external-link"></i>
+ <a
+ class="btn external_url"
+ :href="externalUrl"
+ target="_blank"
+ title="Environment external URL">
+ <i class="fa fa-external-link" aria-hidden="true"></i>
</a>
`,
-});
+};
diff --git a/app/assets/javascripts/environments/components/environment_item.js b/app/assets/javascripts/environments/components/environment_item.js
index 7f4e070b229..93919d41c60 100644
--- a/app/assets/javascripts/environments/components/environment_item.js
+++ b/app/assets/javascripts/environments/components/environment_item.js
@@ -1,13 +1,11 @@
-const Vue = require('vue');
-const Timeago = require('timeago.js');
-
-require('../../lib/utils/text_utility');
-require('../../vue_shared/components/commit');
-const ActionsComponent = require('./environment_actions');
-const ExternalUrlComponent = require('./environment_external_url');
-const StopComponent = require('./environment_stop');
-const RollbackComponent = require('./environment_rollback');
-const TerminalButtonComponent = require('./environment_terminal_button');
+import Timeago from 'timeago.js';
+import ActionsComponent from './environment_actions';
+import ExternalUrlComponent from './environment_external_url';
+import StopComponent from './environment_stop';
+import RollbackComponent from './environment_rollback';
+import TerminalButtonComponent from './environment_terminal_button';
+import '../../lib/utils/text_utility';
+import '../../vue_shared/components/commit';
/**
* Envrionment Item Component
@@ -17,7 +15,7 @@ const TerminalButtonComponent = require('./environment_terminal_button');
const timeagoInstance = new Timeago();
-module.exports = Vue.component('environment-item', {
+export default {
components: {
'commit-component': gl.CommitComponent,
@@ -46,6 +44,11 @@ module.exports = Vue.component('environment-item', {
required: false,
default: false,
},
+
+ service: {
+ type: Object,
+ required: true,
+ },
},
computed: {
@@ -489,22 +492,25 @@ module.exports = Vue.component('environment-item', {
<td class="environments-actions">
<div v-if="!model.isFolder" class="btn-group pull-right" role="group">
<actions-component v-if="hasManualActions && canCreateDeployment"
+ :service="service"
:actions="manualActions"/>
<external-url-component v-if="externalURL && canReadEnvironment"
:external-url="externalURL"/>
<stop-component v-if="hasStopAction && canCreateDeployment"
- :stop-url="model.stop_path"/>
+ :stop-url="model.stop_path"
+ :service="service"/>
<terminal-button-component v-if="model && model.terminal_path"
:terminal-path="model.terminal_path"/>
<rollback-component v-if="canRetry && canCreateDeployment"
:is-last-deployment="isLastDeployment"
- :retry-url="retryUrl"/>
+ :retry-url="retryUrl"
+ :service="service"/>
</div>
</td>
</tr>
`,
-});
+};
diff --git a/app/assets/javascripts/environments/components/environment_rollback.js b/app/assets/javascripts/environments/components/environment_rollback.js
index daf126eb4e8..baa15d9e5b5 100644
--- a/app/assets/javascripts/environments/components/environment_rollback.js
+++ b/app/assets/javascripts/environments/components/environment_rollback.js
@@ -1,10 +1,14 @@
+/* global Flash */
+/* eslint-disable no-new */
/**
* Renders Rollback or Re deploy button in environments table depending
- * of the provided property `isLastDeployment`
+ * of the provided property `isLastDeployment`.
+ *
+ * Makes a post request when the button is clicked.
*/
-const Vue = require('vue');
+import eventHub from '../event_hub';
-module.exports = Vue.component('rollback-component', {
+export default {
props: {
retryUrl: {
type: String,
@@ -15,16 +19,49 @@ module.exports = Vue.component('rollback-component', {
type: Boolean,
default: true,
},
+
+ service: {
+ type: Object,
+ required: true,
+ },
+ },
+
+ data() {
+ return {
+ isLoading: false,
+ };
+ },
+
+ methods: {
+ onClick() {
+ this.isLoading = true;
+
+ this.service.postAction(this.retryUrl)
+ .then(() => {
+ this.isLoading = false;
+ eventHub.$emit('refreshEnvironments');
+ })
+ .catch(() => {
+ this.isLoading = false;
+ new Flash('An error occured while making the request.');
+ });
+ },
},
template: `
- <a class="btn" :href="retryUrl" data-method="post" rel="nofollow">
+ <button type="button"
+ class="btn"
+ @click="onClick"
+ :disabled="isLoading">
+
<span v-if="isLastDeployment">
Re-deploy
</span>
<span v-else>
Rollback
</span>
- </a>
+
+ <i v-if="isLoading" class="fa fa-spinner fa-spin" aria-hidden="true"></i>
+ </button>
`,
-});
+};
diff --git a/app/assets/javascripts/environments/components/environment_stop.js b/app/assets/javascripts/environments/components/environment_stop.js
index 96983a19568..5404d647745 100644
--- a/app/assets/javascripts/environments/components/environment_stop.js
+++ b/app/assets/javascripts/environments/components/environment_stop.js
@@ -1,24 +1,56 @@
+/* global Flash */
+/* eslint-disable no-new, no-alert */
/**
* Renders the stop "button" that allows stop an environment.
* Used in environments table.
*/
-const Vue = require('vue');
+import eventHub from '../event_hub';
-module.exports = Vue.component('stop-component', {
+export default {
props: {
stopUrl: {
type: String,
default: '',
},
+
+ service: {
+ type: Object,
+ required: true,
+ },
+ },
+
+ data() {
+ return {
+ isLoading: false,
+ };
+ },
+
+ methods: {
+ onClick() {
+ if (confirm('Are you sure you want to stop this environment?')) {
+ this.isLoading = true;
+
+ this.service.postAction(this.retryUrl)
+ .then(() => {
+ this.isLoading = false;
+ eventHub.$emit('refreshEnvironments');
+ })
+ .catch(() => {
+ this.isLoading = false;
+ new Flash('An error occured while making the request.', 'alert');
+ });
+ }
+ },
},
template: `
- <a class="btn stop-env-link"
- :href="stopUrl"
- data-confirm="Are you sure you want to stop this environment?"
- data-method="post"
- rel="nofollow">
+ <button type="button"
+ class="btn stop-env-link"
+ @click="onClick"
+ :disabled="isLoading"
+ title="Stop Environment">
<i class="fa fa-stop stop-env-icon" aria-hidden="true"></i>
- </a>
+ <i v-if="isLoading" class="fa fa-spinner fa-spin" aria-hidden="true"></i>
+ </button>
`,
-});
+};
diff --git a/app/assets/javascripts/environments/components/environment_terminal_button.js b/app/assets/javascripts/environments/components/environment_terminal_button.js
index e86607e78f4..66a71faa02f 100644
--- a/app/assets/javascripts/environments/components/environment_terminal_button.js
+++ b/app/assets/javascripts/environments/components/environment_terminal_button.js
@@ -2,13 +2,13 @@
* Renders a terminal button to open a web terminal.
* Used in environments table.
*/
-const Vue = require('vue');
-const terminalIconSvg = require('icons/_icon_terminal.svg');
+import terminalIconSvg from 'icons/_icon_terminal.svg';
-module.exports = Vue.component('terminal-button-component', {
+export default {
props: {
terminalPath: {
type: String,
+ required: false,
default: '',
},
},
@@ -19,8 +19,9 @@ module.exports = Vue.component('terminal-button-component', {
template: `
<a class="btn terminal-button"
+ title="Open web terminal"
:href="terminalPath">
${terminalIconSvg}
</a>
`,
-});
+};
diff --git a/app/assets/javascripts/environments/components/environments_table.js b/app/assets/javascripts/environments/components/environments_table.js
index 4088d63be80..5f07b612b91 100644
--- a/app/assets/javascripts/environments/components/environments_table.js
+++ b/app/assets/javascripts/environments/components/environments_table.js
@@ -1,11 +1,9 @@
/**
* Render environments table.
*/
-const Vue = require('vue');
-const EnvironmentItem = require('./environment_item');
-
-module.exports = Vue.component('environment-table-component', {
+import EnvironmentItem from './environment_item';
+export default {
components: {
'environment-item': EnvironmentItem,
},
@@ -28,6 +26,11 @@ module.exports = Vue.component('environment-table-component', {
required: false,
default: false,
},
+
+ service: {
+ type: Object,
+ required: true,
+ },
},
template: `
@@ -48,9 +51,10 @@ module.exports = Vue.component('environment-table-component', {
<tr is="environment-item"
:model="model"
:can-create-deployment="canCreateDeployment"
- :can-read-environment="canReadEnvironment"></tr>
+ :can-read-environment="canReadEnvironment"
+ :service="service"></tr>
</template>
</tbody>
</table>
`,
-});
+};
diff --git a/app/assets/javascripts/environments/environments_bundle.js b/app/assets/javascripts/environments/environments_bundle.js
index 7bbba91bc10..8d963b335cf 100644
--- a/app/assets/javascripts/environments/environments_bundle.js
+++ b/app/assets/javascripts/environments/environments_bundle.js
@@ -1,4 +1,4 @@
-const EnvironmentsComponent = require('./components/environment');
+import EnvironmentsComponent from './components/environment';
$(() => {
window.gl = window.gl || {};
diff --git a/app/assets/javascripts/environments/event_hub.js b/app/assets/javascripts/environments/event_hub.js
new file mode 100644
index 00000000000..0948c2e5352
--- /dev/null
+++ b/app/assets/javascripts/environments/event_hub.js
@@ -0,0 +1,3 @@
+import Vue from 'vue';
+
+export default new Vue();
diff --git a/app/assets/javascripts/environments/folder/environments_folder_bundle.js b/app/assets/javascripts/environments/folder/environments_folder_bundle.js
index d2ca465351a..f939eccf246 100644
--- a/app/assets/javascripts/environments/folder/environments_folder_bundle.js
+++ b/app/assets/javascripts/environments/folder/environments_folder_bundle.js
@@ -1,4 +1,4 @@
-const EnvironmentsFolderComponent = require('./environments_folder_view');
+import EnvironmentsFolderComponent from './environments_folder_view';
$(() => {
window.gl = window.gl || {};
diff --git a/app/assets/javascripts/environments/folder/environments_folder_view.js b/app/assets/javascripts/environments/folder/environments_folder_view.js
index 2a9d0492d7a..7abcf6dbbea 100644
--- a/app/assets/javascripts/environments/folder/environments_folder_view.js
+++ b/app/assets/javascripts/environments/folder/environments_folder_view.js
@@ -1,16 +1,16 @@
/* eslint-disable no-param-reassign, no-new */
/* global Flash */
+import EnvironmentsService from '../services/environments_service';
+import EnvironmentTable from '../components/environments_table';
+import EnvironmentsStore from '../stores/environments_store';
const Vue = window.Vue = require('vue');
window.Vue.use(require('vue-resource'));
-const EnvironmentsService = require('../services/environments_service');
-const EnvironmentTable = require('../components/environments_table');
-const EnvironmentsStore = require('../stores/environments_store');
require('../../vue_shared/components/table_pagination');
require('../../lib/utils/common_utils');
require('../../vue_shared/vue_resource_interceptor');
-module.exports = Vue.component('environment-folder-view', {
+export default Vue.component('environment-folder-view', {
components: {
'environment-table': EnvironmentTable,
@@ -88,11 +88,11 @@ module.exports = Vue.component('environment-folder-view', {
const endpoint = `${this.endpoint}?scope=${scope}&page=${pageNumber}`;
- const service = new EnvironmentsService(endpoint);
+ this.service = new EnvironmentsService(endpoint);
this.isLoading = true;
- return service.get()
+ return this.service.get()
.then(resp => ({
headers: resp.headers,
body: resp.json(),
@@ -168,13 +168,12 @@ module.exports = Vue.component('environment-folder-view', {
:can-read-environment="canReadEnvironmentParsed"
:play-icon-svg="playIconSvg"
:terminal-icon-svg="terminalIconSvg"
- :commit-icon-svg="commitIconSvg">
- </environment-table>
+ :commit-icon-svg="commitIconSvg"
+ :service="service"/>
<table-pagination v-if="state.paginationInformation && state.paginationInformation.totalPages > 1"
:change="changePage"
- :pageInfo="state.paginationInformation">
- </table-pagination>
+ :pageInfo="state.paginationInformation"/>
</div>
</div>
</div>
diff --git a/app/assets/javascripts/environments/services/environments_service.js b/app/assets/javascripts/environments/services/environments_service.js
index effc6c4c838..76296c83d11 100644
--- a/app/assets/javascripts/environments/services/environments_service.js
+++ b/app/assets/javascripts/environments/services/environments_service.js
@@ -1,13 +1,16 @@
-const Vue = require('vue');
+/* eslint-disable class-methods-use-this */
+import Vue from 'vue';
-class EnvironmentsService {
+export default class EnvironmentsService {
constructor(endpoint) {
this.environments = Vue.resource(endpoint);
}
- get() {
- return this.environments.get();
+ get(scope, page) {
+ return this.environments.get({ scope, page });
}
-}
-module.exports = EnvironmentsService;
+ postAction(endpoint) {
+ return Vue.http.post(endpoint, {}, { emulateJSON: true });
+ }
+}
diff --git a/app/assets/javascripts/environments/stores/environments_store.js b/app/assets/javascripts/environments/stores/environments_store.js
index 15cd9bde08e..d3fe3872c56 100644
--- a/app/assets/javascripts/environments/stores/environments_store.js
+++ b/app/assets/javascripts/environments/stores/environments_store.js
@@ -1,11 +1,12 @@
-require('~/lib/utils/common_utils');
+import '~/lib/utils/common_utils';
+
/**
* Environments Store.
*
* Stores received environments, count of stopped environments and count of
* available environments.
*/
-class EnvironmentsStore {
+export default class EnvironmentsStore {
constructor() {
this.state = {};
this.state.environments = [];
@@ -86,5 +87,3 @@ class EnvironmentsStore {
return count;
}
}
-
-module.exports = EnvironmentsStore;
diff --git a/app/assets/javascripts/extensions/array.js b/app/assets/javascripts/extensions/array.js
index f8256a8d26d..027222f804d 100644
--- a/app/assets/javascripts/extensions/array.js
+++ b/app/assets/javascripts/extensions/array.js
@@ -1,27 +1,11 @@
-/* eslint-disable no-extend-native, func-names, space-before-function-paren, space-infix-ops, strict, max-len */
+// TODO: remove this
-'use strict';
-
-Array.prototype.first = function() {
+// eslint-disable-next-line no-extend-native
+Array.prototype.first = function first() {
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');
- if (typeof predicate !== 'function') throw new TypeError('predicate must be a function');
-
- const list = Object(this);
- const thisArg = args[1];
- let value = {};
-
- for (let i = 0; i < list.length; i += 1) {
- value = list[i];
- if (predicate.call(thisArg, value, i, list)) return value;
- }
-
- return undefined;
+// eslint-disable-next-line no-extend-native
+Array.prototype.last = function last() {
+ return this[this.length - 1];
};
diff --git a/app/assets/javascripts/extensions/custom_event.js b/app/assets/javascripts/extensions/custom_event.js
deleted file mode 100644
index abedae4c1c7..00000000000
--- a/app/assets/javascripts/extensions/custom_event.js
+++ /dev/null
@@ -1,12 +0,0 @@
-/* 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 b/app/assets/javascripts/extensions/element.js
deleted file mode 100644
index 90ab79305a7..00000000000
--- a/app/assets/javascripts/extensions/element.js
+++ /dev/null
@@ -1,20 +0,0 @@
-/* global Element */
-/* eslint-disable consistent-return, max-len, no-empty, func-names */
-
-Element.prototype.closest = Element.prototype.closest || function closest(selector, selectedElement = this) {
- if (!selectedElement) return;
- return selectedElement.matches(selector) ? selectedElement : Element.prototype.closest(selector, selectedElement.parentElement);
-};
-
-Element.prototype.matches = Element.prototype.matches ||
- Element.prototype.matchesSelector ||
- Element.prototype.mozMatchesSelector ||
- Element.prototype.msMatchesSelector ||
- Element.prototype.oMatchesSelector ||
- Element.prototype.webkitMatchesSelector ||
- function (s) {
- const matches = (this.document || this.ownerDocument).querySelectorAll(s);
- 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
deleted file mode 100644
index 1a489b859e8..00000000000
--- a/app/assets/javascripts/extensions/jquery.js
+++ /dev/null
@@ -1,16 +0,0 @@
-/* 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({
- disable: function() {
- return $(this).attr('disabled', 'disabled').addClass('disabled');
- }
- });
-
- // Enable an element and remove the 'disabled' Bootstrap class
- $.fn.extend({
- enable: function() {
- return $(this).removeAttr('disabled').removeClass('disabled');
- }
- });
-}).call(window);
diff --git a/app/assets/javascripts/extensions/object.js b/app/assets/javascripts/extensions/object.js
deleted file mode 100644
index 70a2d765abd..00000000000
--- a/app/assets/javascripts/extensions/object.js
+++ /dev/null
@@ -1,26 +0,0 @@
-/* eslint-disable no-restricted-syntax */
-
-// Adapted from https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/assign#Polyfill
-if (typeof Object.assign !== 'function') {
- Object.assign = function assign(target, ...args) {
- if (target == null) { // TypeError if undefined or null
- throw new TypeError('Cannot convert undefined or null to object');
- }
-
- const to = Object(target);
-
- for (let index = 0; index < args.length; index += 1) {
- const nextSource = args[index];
-
- if (nextSource != null) { // Skip over if undefined or null
- for (const nextKey in nextSource) {
- // Avoid bugs when hasOwnProperty is shadowed
- if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
- to[nextKey] = nextSource[nextKey];
- }
- }
- }
- }
- return to;
- };
-}
diff --git a/app/assets/javascripts/extensions/string.js b/app/assets/javascripts/extensions/string.js
deleted file mode 100644
index fe23be0bbc1..00000000000
--- a/app/assets/javascripts/extensions/string.js
+++ /dev/null
@@ -1,2 +0,0 @@
-require('string.prototype.codepointat');
-require('string.fromcodepoint');
diff --git a/app/assets/javascripts/files_comment_button.js b/app/assets/javascripts/files_comment_button.js
index 6d86888dcb8..3f041172ff3 100644
--- a/app/assets/javascripts/files_comment_button.js
+++ b/app/assets/javascripts/files_comment_button.js
@@ -2,139 +2,140 @@
/* global FilesCommentButton */
/* global notes */
-(function() {
- let $commentButtonTemplate;
- var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; };
+let $commentButtonTemplate;
+var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; };
- this.FilesCommentButton = (function() {
- var COMMENT_BUTTON_CLASS, EMPTY_CELL_CLASS, LINE_COLUMN_CLASSES, LINE_CONTENT_CLASS, LINE_HOLDER_CLASS, LINE_NUMBER_CLASS, OLD_LINE_CLASS, TEXT_FILE_SELECTOR, UNFOLDABLE_LINE_CLASS;
+window.FilesCommentButton = (function() {
+ var COMMENT_BUTTON_CLASS, EMPTY_CELL_CLASS, LINE_COLUMN_CLASSES, LINE_CONTENT_CLASS, LINE_HOLDER_CLASS, LINE_NUMBER_CLASS, OLD_LINE_CLASS, TEXT_FILE_SELECTOR, UNFOLDABLE_LINE_CLASS;
- COMMENT_BUTTON_CLASS = '.add-diff-note';
+ COMMENT_BUTTON_CLASS = '.add-diff-note';
- LINE_HOLDER_CLASS = '.line_holder';
+ LINE_HOLDER_CLASS = '.line_holder';
- LINE_NUMBER_CLASS = 'diff-line-num';
+ LINE_NUMBER_CLASS = 'diff-line-num';
- LINE_CONTENT_CLASS = 'line_content';
+ LINE_CONTENT_CLASS = 'line_content';
- UNFOLDABLE_LINE_CLASS = 'js-unfold';
+ UNFOLDABLE_LINE_CLASS = 'js-unfold';
- EMPTY_CELL_CLASS = 'empty-cell';
+ EMPTY_CELL_CLASS = 'empty-cell';
- OLD_LINE_CLASS = 'old_line';
+ OLD_LINE_CLASS = 'old_line';
- LINE_COLUMN_CLASSES = "." + LINE_NUMBER_CLASS + ", .line_content";
+ LINE_COLUMN_CLASSES = "." + LINE_NUMBER_CLASS + ", .line_content";
- TEXT_FILE_SELECTOR = '.text-file';
+ TEXT_FILE_SELECTOR = '.text-file';
- function FilesCommentButton(filesContainerElement) {
- this.render = bind(this.render, this);
- this.hideButton = bind(this.hideButton, this);
- this.isParallelView = notes.isParallelView();
- filesContainerElement.on('mouseover', LINE_COLUMN_CLASSES, this.render)
- .on('mouseleave', LINE_COLUMN_CLASSES, this.hideButton);
+ function FilesCommentButton(filesContainerElement) {
+ this.render = bind(this.render, this);
+ this.hideButton = bind(this.hideButton, this);
+ this.isParallelView = notes.isParallelView();
+ filesContainerElement.on('mouseover', LINE_COLUMN_CLASSES, this.render)
+ .on('mouseleave', LINE_COLUMN_CLASSES, this.hideButton);
+ }
+
+ FilesCommentButton.prototype.render = function(e) {
+ var $currentTarget, buttonParentElement, lineContentElement, textFileElement, $button;
+ $currentTarget = $(e.currentTarget);
+
+ if ($currentTarget.hasClass('js-no-comment-btn')) return;
+
+ lineContentElement = this.getLineContent($currentTarget);
+ buttonParentElement = this.getButtonParent($currentTarget);
+
+ if (!this.validateButtonParent(buttonParentElement) || !this.validateLineContent(lineContentElement)) return;
+
+ $button = $(COMMENT_BUTTON_CLASS, buttonParentElement);
+ buttonParentElement.addClass('is-over')
+ .nextUntil(`.${LINE_CONTENT_CLASS}`).addClass('is-over');
+
+ if ($button.length) {
+ return;
}
- FilesCommentButton.prototype.render = function(e) {
- var $currentTarget, buttonParentElement, lineContentElement, textFileElement, $button;
- $currentTarget = $(e.currentTarget);
- lineContentElement = this.getLineContent($currentTarget);
- buttonParentElement = this.getButtonParent($currentTarget);
+ textFileElement = this.getTextFileElement($currentTarget);
+ buttonParentElement.append(this.buildButton({
+ noteableType: textFileElement.attr('data-noteable-type'),
+ noteableID: textFileElement.attr('data-noteable-id'),
+ commitID: textFileElement.attr('data-commit-id'),
+ noteType: lineContentElement.attr('data-note-type'),
+ position: lineContentElement.attr('data-position'),
+ lineType: lineContentElement.attr('data-line-type'),
+ discussionID: lineContentElement.attr('data-discussion-id'),
+ lineCode: lineContentElement.attr('data-line-code')
+ }));
+ };
- if (!this.validateButtonParent(buttonParentElement) || !this.validateLineContent(lineContentElement)) return;
+ FilesCommentButton.prototype.hideButton = function(e) {
+ var $currentTarget = $(e.currentTarget);
+ var buttonParentElement = this.getButtonParent($currentTarget);
- $button = $(COMMENT_BUTTON_CLASS, buttonParentElement);
- buttonParentElement.addClass('is-over')
- .nextUntil(`.${LINE_CONTENT_CLASS}`).addClass('is-over');
+ buttonParentElement.removeClass('is-over')
+ .nextUntil(`.${LINE_CONTENT_CLASS}`).removeClass('is-over');
+ };
- if ($button.length) {
- return;
- }
+ FilesCommentButton.prototype.buildButton = function(buttonAttributes) {
+ return $commentButtonTemplate.clone().attr({
+ 'data-noteable-type': buttonAttributes.noteableType,
+ 'data-noteable-id': buttonAttributes.noteableID,
+ 'data-commit-id': buttonAttributes.commitID,
+ 'data-note-type': buttonAttributes.noteType,
+ 'data-line-code': buttonAttributes.lineCode,
+ 'data-position': buttonAttributes.position,
+ 'data-discussion-id': buttonAttributes.discussionID,
+ 'data-line-type': buttonAttributes.lineType
+ });
+ };
+
+ FilesCommentButton.prototype.getTextFileElement = function(hoveredElement) {
+ return hoveredElement.closest(TEXT_FILE_SELECTOR);
+ };
- textFileElement = this.getTextFileElement($currentTarget);
- buttonParentElement.append(this.buildButton({
- noteableType: textFileElement.attr('data-noteable-type'),
- noteableID: textFileElement.attr('data-noteable-id'),
- commitID: textFileElement.attr('data-commit-id'),
- noteType: lineContentElement.attr('data-note-type'),
- position: lineContentElement.attr('data-position'),
- lineType: lineContentElement.attr('data-line-type'),
- discussionID: lineContentElement.attr('data-discussion-id'),
- lineCode: lineContentElement.attr('data-line-code')
- }));
- };
-
- FilesCommentButton.prototype.hideButton = function(e) {
- var $currentTarget = $(e.currentTarget);
- var buttonParentElement = this.getButtonParent($currentTarget);
-
- buttonParentElement.removeClass('is-over')
- .nextUntil(`.${LINE_CONTENT_CLASS}`).removeClass('is-over');
- };
-
- FilesCommentButton.prototype.buildButton = function(buttonAttributes) {
- return $commentButtonTemplate.clone().attr({
- 'data-noteable-type': buttonAttributes.noteableType,
- 'data-noteable-id': buttonAttributes.noteableID,
- 'data-commit-id': buttonAttributes.commitID,
- 'data-note-type': buttonAttributes.noteType,
- 'data-line-code': buttonAttributes.lineCode,
- 'data-position': buttonAttributes.position,
- 'data-discussion-id': buttonAttributes.discussionID,
- 'data-line-type': buttonAttributes.lineType
- });
- };
-
- FilesCommentButton.prototype.getTextFileElement = function(hoveredElement) {
- return hoveredElement.closest(TEXT_FILE_SELECTOR);
- };
-
- FilesCommentButton.prototype.getLineContent = function(hoveredElement) {
- if (hoveredElement.hasClass(LINE_CONTENT_CLASS)) {
+ FilesCommentButton.prototype.getLineContent = function(hoveredElement) {
+ if (hoveredElement.hasClass(LINE_CONTENT_CLASS)) {
+ return hoveredElement;
+ }
+ if (!this.isParallelView) {
+ return $(hoveredElement).closest(LINE_HOLDER_CLASS).find("." + LINE_CONTENT_CLASS);
+ } else {
+ return $(hoveredElement).next("." + LINE_CONTENT_CLASS);
+ }
+ };
+
+ FilesCommentButton.prototype.getButtonParent = function(hoveredElement) {
+ if (!this.isParallelView) {
+ if (hoveredElement.hasClass(OLD_LINE_CLASS)) {
return hoveredElement;
}
- if (!this.isParallelView) {
- return $(hoveredElement).closest(LINE_HOLDER_CLASS).find("." + LINE_CONTENT_CLASS);
- } else {
- return $(hoveredElement).next("." + LINE_CONTENT_CLASS);
- }
- };
-
- FilesCommentButton.prototype.getButtonParent = function(hoveredElement) {
- if (!this.isParallelView) {
- if (hoveredElement.hasClass(OLD_LINE_CLASS)) {
- return hoveredElement;
- }
- return hoveredElement.parent().find("." + OLD_LINE_CLASS);
- } else {
- if (hoveredElement.hasClass(LINE_NUMBER_CLASS)) {
- return hoveredElement;
- }
- return $(hoveredElement).prev("." + LINE_NUMBER_CLASS);
+ return hoveredElement.parent().find("." + OLD_LINE_CLASS);
+ } else {
+ if (hoveredElement.hasClass(LINE_NUMBER_CLASS)) {
+ return hoveredElement;
}
- };
+ return $(hoveredElement).prev("." + LINE_NUMBER_CLASS);
+ }
+ };
- FilesCommentButton.prototype.validateButtonParent = function(buttonParentElement) {
- return !buttonParentElement.hasClass(EMPTY_CELL_CLASS) && !buttonParentElement.hasClass(UNFOLDABLE_LINE_CLASS);
- };
+ FilesCommentButton.prototype.validateButtonParent = function(buttonParentElement) {
+ return !buttonParentElement.hasClass(EMPTY_CELL_CLASS) && !buttonParentElement.hasClass(UNFOLDABLE_LINE_CLASS);
+ };
- FilesCommentButton.prototype.validateLineContent = function(lineContentElement) {
- return lineContentElement.attr('data-discussion-id') && lineContentElement.attr('data-discussion-id') !== '';
- };
+ FilesCommentButton.prototype.validateLineContent = function(lineContentElement) {
+ return lineContentElement.attr('data-discussion-id') && lineContentElement.attr('data-discussion-id') !== '';
+ };
- return FilesCommentButton;
- })();
+ return FilesCommentButton;
+})();
- $.fn.filesCommentButton = function() {
- $commentButtonTemplate = $('<button name="button" type="submit" class="add-diff-note js-add-diff-note-button" title="Add a comment to this line"><i class="fa fa-comment-o"></i></button>');
+$.fn.filesCommentButton = function() {
+ $commentButtonTemplate = $('<button name="button" type="submit" class="add-diff-note js-add-diff-note-button" title="Add a comment to this line"><i class="fa fa-comment-o"></i></button>');
- if (!(this && (this.parent().data('can-create-note') != null))) {
- return;
+ if (!(this && (this.parent().data('can-create-note') != null))) {
+ return;
+ }
+ return this.each(function() {
+ if (!$.data(this, 'filesCommentButton')) {
+ return $.data(this, 'filesCommentButton', new FilesCommentButton($(this)));
}
- return this.each(function() {
- if (!$.data(this, 'filesCommentButton')) {
- return $.data(this, 'filesCommentButton', new FilesCommentButton($(this)));
- }
- });
- };
-}).call(window);
+ });
+};
diff --git a/app/assets/javascripts/filterable_list.js b/app/assets/javascripts/filterable_list.js
index 47a40e28461..aaaeb9bddb1 100644
--- a/app/assets/javascripts/filterable_list.js
+++ b/app/assets/javascripts/filterable_list.js
@@ -2,6 +2,7 @@
* Makes search request for content when user types a value in the search input.
* Updates the html content of the page with the received one.
*/
+
export default class FilterableList {
constructor(form, filter, holder) {
this.filterForm = form;
diff --git a/app/assets/javascripts/filtered_search/dropdown_utils.js b/app/assets/javascripts/filtered_search/dropdown_utils.js
index b52081df646..a5a6b56a0d3 100644
--- a/app/assets/javascripts/filtered_search/dropdown_utils.js
+++ b/app/assets/javascripts/filtered_search/dropdown_utils.js
@@ -80,30 +80,48 @@
}
// Determines the full search query (visual tokens + input)
- static getSearchQuery() {
- const tokensContainer = document.querySelector('.tokens-container');
+ static getSearchQuery(untilInput = false) {
+ const tokens = [].slice.call(document.querySelectorAll('.tokens-container li'));
const values = [];
- [].forEach.call(tokensContainer.querySelectorAll('.js-visual-token'), (token) => {
- const name = token.querySelector('.name');
- const value = token.querySelector('.value');
- const symbol = value && value.dataset.symbol ? value.dataset.symbol : '';
- let valueText = '';
+ if (untilInput) {
+ const inputIndex = _.findIndex(tokens, t => t.classList.contains('input-token'));
+ // Add one to include input-token to the tokens array
+ tokens.splice(inputIndex + 1);
+ }
- if (value && value.innerText) {
- valueText = value.innerText;
- }
-
- if (token.className.indexOf('filtered-search-token') !== -1) {
- values.push(`${name.innerText.toLowerCase()}:${symbol}${valueText}`);
- } else {
- values.push(name.innerText);
+ tokens.forEach((token) => {
+ if (token.classList.contains('js-visual-token')) {
+ const name = token.querySelector('.name');
+ const value = token.querySelector('.value');
+ const symbol = value && value.dataset.symbol ? value.dataset.symbol : '';
+ let valueText = '';
+
+ if (value && value.innerText) {
+ valueText = value.innerText;
+ }
+
+ if (token.className.indexOf('filtered-search-token') !== -1) {
+ values.push(`${name.innerText.toLowerCase()}:${symbol}${valueText}`);
+ } else {
+ values.push(name.innerText);
+ }
+ } else if (token.classList.contains('input-token')) {
+ const { isLastVisualTokenValid } =
+ gl.FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
+
+ const input = document.querySelector('.filtered-search');
+ const inputValue = input && input.value;
+
+ if (isLastVisualTokenValid) {
+ values.push(inputValue);
+ } else {
+ const previous = values.pop();
+ values.push(`${previous}${inputValue}`);
+ }
}
});
- const input = document.querySelector('.filtered-search');
- values.push(input && input.value);
-
return values.join(' ');
}
diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js
index 608c65c78a4..d37c812c1f7 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js
+++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js
@@ -139,7 +139,7 @@
}
setDropdown() {
- const query = gl.DropdownUtils.getSearchQuery();
+ const query = gl.DropdownUtils.getSearchQuery(true);
const { lastToken, searchToken } = this.tokenizer.processTokens(query);
if (this.currentDropdown) {
@@ -162,6 +162,10 @@
}
resetDropdowns() {
+ if (!this.currentDropdown) {
+ return;
+ }
+
// Force current dropdown to hide
this.mapping[this.currentDropdown].reference.hideDropdown();
diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js b/app/assets/javascripts/filtered_search/filtered_search_manager.js
index 3478f1130a5..f885932bd91 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_manager.js
+++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js
@@ -38,7 +38,8 @@
this.editTokenWrapper = this.editToken.bind(this);
this.tokenChange = this.tokenChange.bind(this);
- this.filteredSearchInput.form.addEventListener('submit', this.handleFormSubmit);
+ this.filteredSearchInputForm = this.filteredSearchInput.form;
+ this.filteredSearchInputForm.addEventListener('submit', this.handleFormSubmit);
this.filteredSearchInput.addEventListener('input', this.setDropdownWrapper);
this.filteredSearchInput.addEventListener('input', this.toggleClearSearchButtonWrapper);
this.filteredSearchInput.addEventListener('input', this.handleInputPlaceholderWrapper);
@@ -56,7 +57,7 @@
}
unbindEvents() {
- this.filteredSearchInput.form.removeEventListener('submit', this.handleFormSubmit);
+ this.filteredSearchInputForm.removeEventListener('submit', this.handleFormSubmit);
this.filteredSearchInput.removeEventListener('input', this.setDropdownWrapper);
this.filteredSearchInput.removeEventListener('input', this.toggleClearSearchButtonWrapper);
this.filteredSearchInput.removeEventListener('input', this.handleInputPlaceholderWrapper);
@@ -378,10 +379,13 @@
tokenChange() {
const dropdown = this.dropdownManager.mapping[this.dropdownManager.currentDropdown];
- const currentDropdownRef = dropdown.reference;
- this.setDropdownWrapper();
- currentDropdownRef.dispatchInputEvent();
+ if (dropdown) {
+ const currentDropdownRef = dropdown.reference;
+
+ this.setDropdownWrapper();
+ currentDropdownRef.dispatchInputEvent();
+ }
}
}
diff --git a/app/assets/javascripts/flash.js b/app/assets/javascripts/flash.js
index 730104b89f9..eec30624ff2 100644
--- a/app/assets/javascripts/flash.js
+++ b/app/assets/javascripts/flash.js
@@ -1,42 +1,41 @@
/* 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;
- hideFlash = function() {
- return $(this).fadeOut();
- };
+window.Flash = (function() {
+ var hideFlash;
- function Flash(message, type, parent) {
- var flash, textDiv;
- if (type == null) {
- type = 'alert';
- }
- if (parent == null) {
- parent = null;
- }
- if (parent) {
- this.flashContainer = parent.find('.flash-container');
- } else {
- this.flashContainer = $('.flash-container-page');
- }
- this.flashContainer.html('');
- flash = $('<div/>', {
- "class": "flash-" + type
- });
- flash.on('click', hideFlash);
- textDiv = $('<div/>', {
- "class": 'flash-text',
- text: message
- });
- textDiv.appendTo(flash);
- if (this.flashContainer.parent().hasClass('content-wrapper')) {
- textDiv.addClass('container-fluid container-limited');
- }
- flash.appendTo(this.flashContainer);
- this.flashContainer.show();
+ hideFlash = function() {
+ return $(this).fadeOut();
+ };
+
+ function Flash(message, type, parent) {
+ var flash, textDiv;
+ if (type == null) {
+ type = 'alert';
+ }
+ if (parent == null) {
+ parent = null;
+ }
+ if (parent) {
+ this.flashContainer = parent.find('.flash-container');
+ } else {
+ this.flashContainer = $('.flash-container-page');
+ }
+ this.flashContainer.html('');
+ flash = $('<div/>', {
+ "class": "flash-" + type
+ });
+ flash.on('click', hideFlash);
+ textDiv = $('<div/>', {
+ "class": 'flash-text',
+ text: message
+ });
+ textDiv.appendTo(flash);
+ if (this.flashContainer.parent().hasClass('content-wrapper')) {
+ textDiv.addClass('container-fluid container-limited');
}
+ flash.appendTo(this.flashContainer);
+ this.flashContainer.show();
+ }
- return Flash;
- })();
-}).call(window);
+ return Flash;
+})();
diff --git a/app/assets/javascripts/gfm_auto_complete.js b/app/assets/javascripts/gfm_auto_complete.js
index 1bc04a5ad96..9ac4c49d697 100644
--- a/app/assets/javascripts/gfm_auto_complete.js
+++ b/app/assets/javascripts/gfm_auto_complete.js
@@ -1,396 +1,390 @@
/* 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 */
-const emojiMap = require('emoji-map');
-const emojiAliases = require('emoji-aliases');
-const glEmoji = require('./behaviors/gl_emoji');
-
-const glEmojiTag = glEmoji.glEmojiTag;
+import emojiMap from 'emojis/digests.json';
+import emojiAliases from 'emojis/aliases.json';
+import { glEmojiTag } from '~/behaviors/gl_emoji';
// Creates the variables for setting up GFM auto-completion
-(function() {
- if (window.gl == null) {
- window.gl = {};
- }
+window.gl = window.gl || {};
- function sanitize(str) {
- return str.replace(/<(?:.|\n)*?>/gm, '');
- }
+function sanitize(str) {
+ return str.replace(/<(?:.|\n)*?>/gm, '');
+}
- window.gl.GfmAutoComplete = {
- dataSources: {},
- defaultLoadingData: ['loading'],
- cachedData: {},
- isLoadingData: {},
- atTypeMap: {
- ':': 'emojis',
- '@': 'members',
- '#': 'issues',
- '!': 'mergeRequests',
- '~': 'labels',
- '%': 'milestones',
- '/': 'commands'
- },
- // Emoji
- Emoji: {
- templateFunction: function(name) {
- return `<li>
- ${name} ${glEmojiTag(name)}
- </li>
- `;
+window.gl.GfmAutoComplete = {
+ dataSources: {},
+ defaultLoadingData: ['loading'],
+ cachedData: {},
+ isLoadingData: {},
+ atTypeMap: {
+ ':': 'emojis',
+ '@': 'members',
+ '#': 'issues',
+ '!': 'mergeRequests',
+ '~': 'labels',
+ '%': 'milestones',
+ '/': 'commands'
+ },
+ // Emoji
+ Emoji: {
+ templateFunction: function(name) {
+ return `<li>
+ ${name} ${glEmojiTag(name)}
+ </li>
+ `;
+ }
+ },
+ // Team Members
+ Members: {
+ template: '<li>${avatarTag} ${username} <small>${title}</small></li>'
+ },
+ Labels: {
+ template: '<li><span class="dropdown-label-box" style="background: ${color}"></span> ${title}</li>'
+ },
+ // Issues and MergeRequests
+ Issues: {
+ template: '<li><small>${id}</small> ${title}</li>'
+ },
+ // Milestones
+ Milestones: {
+ template: '<li>${title}</li>'
+ },
+ Loading: {
+ template: '<li style="pointer-events: none;"><i class="fa fa-refresh fa-spin"></i> Loading...</li>'
+ },
+ 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);
},
- // Team Members
- Members: {
- template: '<li>${avatarTag} ${username} <small>${title}</small></li>'
- },
- Labels: {
- template: '<li><span class="dropdown-label-box" style="background: ${color}"></span> ${title}</li>'
- },
- // Issues and MergeRequests
- Issues: {
- template: '<li><small>${id}</small> ${title}</li>'
- },
- // Milestones
- Milestones: {
- template: '<li>${title}</li>'
+ filter: function(query, data, searchKey) {
+ if (gl.GfmAutoComplete.isLoading(data)) {
+ gl.GfmAutoComplete.fetchData(this.$inputor, this.at);
+ return data;
+ } else {
+ return $.fn.atwho["default"].callbacks.filter(query, data, searchKey);
+ }
},
- Loading: {
- template: '<li style="pointer-events: none;"><i class="fa fa-refresh fa-spin"></i> Loading...</li>'
+ beforeInsert: function(value) {
+ if (value && !this.setting.skipSpecialCharacterTest) {
+ var withoutAt = value.substring(1);
+ if (withoutAt && /[^\w\d]/.test(withoutAt)) value = value.charAt() + '"' + withoutAt + '"';
+ }
+ return value;
},
- 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.fetchData(this.$inputor, this.at);
- return data;
- } else {
- return $.fn.atwho["default"].callbacks.filter(query, data, searchKey);
- }
- },
- beforeInsert: function(value) {
- if (value && !this.setting.skipSpecialCharacterTest) {
- var withoutAt = value.substring(1);
- if (withoutAt && /[^\w\d]/.test(withoutAt)) value = value.charAt() + '"' + withoutAt + '"';
- }
- return value;
- },
- matcher: function (flag, subtext) {
- // 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, 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, "\\$&");
+ matcher: function (flag, subtext) {
+ // 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, 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");
+ _a = decodeURI("%C3%80");
+ _y = decodeURI("%C3%BF");
- regexp = new RegExp("^(?:\\B|[^a-zA-Z0-9_" + atSymbolsWithoutBar + "]|\\s)" + flag + "(?!" + atSymbolsWithBar + ")((?:[A-Za-z" + _a + "-" + _y + "0-9_\'\.\+\-]|[^\\x00-\\x7a])*)$", '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);
+ match = regexp.exec(subtext);
- if (match) {
- return match[1];
- } else {
- return null;
- }
+ if (match) {
+ return match[1];
+ } else {
+ return null;
}
- },
- setup: function(input) {
- // Add GFM auto-completion to all input fields, that accept GFM input.
- this.input = input || $('.js-gfm-input');
- this.setupLifecycle();
- },
- setupLifecycle() {
- this.input.each((i, input) => {
- const $input = $(input);
- $input.off('focus.setupAtWho').on('focus.setupAtWho', this.setupAtWho.bind(this, $input));
- // This triggers at.js again
- // Needed for slash commands with suffixes (ex: /label ~)
- $input.on('inserted-commands.atwho', $input.trigger.bind($input, 'keyup'));
- });
- },
- setupAtWho: function($input) {
- // Emoji
- $input.atwho({
- at: ':',
- displayTpl: function(value) {
- return value && value.name ? this.Emoji.templateFunction(value.name) : this.Loading.template;
- }.bind(this),
- insertTpl: ':${name}:',
- skipSpecialCharacterTest: true,
- data: this.defaultLoadingData,
- callbacks: {
- sorter: this.DefaultOptions.sorter,
- beforeInsert: this.DefaultOptions.beforeInsert,
- filter: this.DefaultOptions.filter
- }
- });
- // Team Members
- $input.atwho({
- at: '@',
- displayTpl: function(value) {
- return value.username != null ? this.Members.template : this.Loading.template;
- }.bind(this),
- insertTpl: '${atwho-at}${username}',
- searchKey: 'search',
- alwaysHighlightFirst: true,
- skipSpecialCharacterTest: true,
- data: this.defaultLoadingData,
- callbacks: {
- sorter: this.DefaultOptions.sorter,
- filter: this.DefaultOptions.filter,
- beforeInsert: this.DefaultOptions.beforeInsert,
- matcher: this.DefaultOptions.matcher,
- beforeSave: function(members) {
- return $.map(members, function(m) {
- let title = '';
- if (m.username == null) {
- return m;
- }
- title = m.name;
- if (m.count) {
- title += " (" + m.count + ")";
- }
+ }
+ },
+ setup: function(input) {
+ // Add GFM auto-completion to all input fields, that accept GFM input.
+ this.input = input || $('.js-gfm-input');
+ this.setupLifecycle();
+ },
+ setupLifecycle() {
+ this.input.each((i, input) => {
+ const $input = $(input);
+ $input.off('focus.setupAtWho').on('focus.setupAtWho', this.setupAtWho.bind(this, $input));
+ // This triggers at.js again
+ // Needed for slash commands with suffixes (ex: /label ~)
+ $input.on('inserted-commands.atwho', $input.trigger.bind($input, 'keyup'));
+ });
+ },
+ setupAtWho: function($input) {
+ // Emoji
+ $input.atwho({
+ at: ':',
+ displayTpl: function(value) {
+ return value && value.name ? this.Emoji.templateFunction(value.name) : this.Loading.template;
+ }.bind(this),
+ insertTpl: ':${name}:',
+ skipSpecialCharacterTest: true,
+ data: this.defaultLoadingData,
+ callbacks: {
+ sorter: this.DefaultOptions.sorter,
+ beforeInsert: this.DefaultOptions.beforeInsert,
+ filter: this.DefaultOptions.filter
+ }
+ });
+ // Team Members
+ $input.atwho({
+ at: '@',
+ displayTpl: function(value) {
+ return value.username != null ? this.Members.template : this.Loading.template;
+ }.bind(this),
+ insertTpl: '${atwho-at}${username}',
+ searchKey: 'search',
+ alwaysHighlightFirst: true,
+ skipSpecialCharacterTest: true,
+ data: this.defaultLoadingData,
+ callbacks: {
+ sorter: this.DefaultOptions.sorter,
+ filter: this.DefaultOptions.filter,
+ beforeInsert: this.DefaultOptions.beforeInsert,
+ matcher: this.DefaultOptions.matcher,
+ beforeSave: function(members) {
+ return $.map(members, function(m) {
+ let title = '';
+ if (m.username == null) {
+ return m;
+ }
+ title = m.name;
+ if (m.count) {
+ title += " (" + m.count + ")";
+ }
- const autoCompleteAvatar = m.avatar_url || m.username.charAt(0).toUpperCase();
- const imgAvatar = `<img src="${m.avatar_url}" alt="${m.username}" class="avatar avatar-inline center s26"/>`;
- const txtAvatar = `<div class="avatar center avatar-inline s26">${autoCompleteAvatar}</div>`;
+ const autoCompleteAvatar = m.avatar_url || m.username.charAt(0).toUpperCase();
+ const imgAvatar = `<img src="${m.avatar_url}" alt="${m.username}" class="avatar avatar-inline center s26"/>`;
+ const txtAvatar = `<div class="avatar center avatar-inline s26">${autoCompleteAvatar}</div>`;
- return {
- username: m.username,
- avatarTag: autoCompleteAvatar.length === 1 ? txtAvatar : imgAvatar,
- title: sanitize(title),
- search: sanitize(m.username + " " + m.name)
- };
- });
- }
+ return {
+ username: m.username,
+ avatarTag: autoCompleteAvatar.length === 1 ? txtAvatar : imgAvatar,
+ title: sanitize(title),
+ search: sanitize(m.username + " " + m.name)
+ };
+ });
}
- });
- $input.atwho({
- at: '#',
- alias: 'issues',
- searchKey: 'search',
- displayTpl: function(value) {
- return value.title != null ? this.Issues.template : this.Loading.template;
- }.bind(this),
- data: this.defaultLoadingData,
- insertTpl: '${atwho-at}${id}',
- callbacks: {
- sorter: this.DefaultOptions.sorter,
- filter: this.DefaultOptions.filter,
- beforeInsert: this.DefaultOptions.beforeInsert,
- matcher: this.DefaultOptions.matcher,
- beforeSave: function(issues) {
- return $.map(issues, function(i) {
- if (i.title == null) {
- return i;
- }
- return {
- id: i.iid,
- title: sanitize(i.title),
- search: i.iid + " " + i.title
- };
- });
- }
+ }
+ });
+ $input.atwho({
+ at: '#',
+ alias: 'issues',
+ searchKey: 'search',
+ displayTpl: function(value) {
+ return value.title != null ? this.Issues.template : this.Loading.template;
+ }.bind(this),
+ data: this.defaultLoadingData,
+ insertTpl: '${atwho-at}${id}',
+ callbacks: {
+ sorter: this.DefaultOptions.sorter,
+ filter: this.DefaultOptions.filter,
+ beforeInsert: this.DefaultOptions.beforeInsert,
+ matcher: this.DefaultOptions.matcher,
+ beforeSave: function(issues) {
+ return $.map(issues, function(i) {
+ if (i.title == null) {
+ return i;
+ }
+ return {
+ id: i.iid,
+ title: sanitize(i.title),
+ search: i.iid + " " + i.title
+ };
+ });
}
- });
- $input.atwho({
- at: '%',
- alias: 'milestones',
- searchKey: 'search',
- insertTpl: '${atwho-at}${title}',
- displayTpl: function(value) {
- return value.title != null ? this.Milestones.template : this.Loading.template;
- }.bind(this),
- data: this.defaultLoadingData,
- callbacks: {
- matcher: this.DefaultOptions.matcher,
- sorter: this.DefaultOptions.sorter,
- beforeInsert: this.DefaultOptions.beforeInsert,
- filter: this.DefaultOptions.filter,
- beforeSave: function(milestones) {
- return $.map(milestones, function(m) {
- if (m.title == null) {
- return m;
- }
- return {
- id: m.iid,
- title: sanitize(m.title),
- search: "" + m.title
- };
- });
- }
+ }
+ });
+ $input.atwho({
+ at: '%',
+ alias: 'milestones',
+ searchKey: 'search',
+ insertTpl: '${atwho-at}${title}',
+ displayTpl: function(value) {
+ return value.title != null ? this.Milestones.template : this.Loading.template;
+ }.bind(this),
+ data: this.defaultLoadingData,
+ callbacks: {
+ matcher: this.DefaultOptions.matcher,
+ sorter: this.DefaultOptions.sorter,
+ beforeInsert: this.DefaultOptions.beforeInsert,
+ filter: this.DefaultOptions.filter,
+ beforeSave: function(milestones) {
+ return $.map(milestones, function(m) {
+ if (m.title == null) {
+ return m;
+ }
+ return {
+ id: m.iid,
+ title: sanitize(m.title),
+ search: "" + m.title
+ };
+ });
}
- });
- $input.atwho({
- at: '!',
- alias: 'mergerequests',
- searchKey: 'search',
- displayTpl: function(value) {
- return value.title != null ? this.Issues.template : this.Loading.template;
- }.bind(this),
- data: this.defaultLoadingData,
- insertTpl: '${atwho-at}${id}',
- callbacks: {
- sorter: this.DefaultOptions.sorter,
- filter: this.DefaultOptions.filter,
- beforeInsert: this.DefaultOptions.beforeInsert,
- matcher: this.DefaultOptions.matcher,
- beforeSave: function(merges) {
- return $.map(merges, function(m) {
- if (m.title == null) {
- return m;
- }
- return {
- id: m.iid,
- title: sanitize(m.title),
- search: m.iid + " " + m.title
- };
- });
- }
+ }
+ });
+ $input.atwho({
+ at: '!',
+ alias: 'mergerequests',
+ searchKey: 'search',
+ displayTpl: function(value) {
+ return value.title != null ? this.Issues.template : this.Loading.template;
+ }.bind(this),
+ data: this.defaultLoadingData,
+ insertTpl: '${atwho-at}${id}',
+ callbacks: {
+ sorter: this.DefaultOptions.sorter,
+ filter: this.DefaultOptions.filter,
+ beforeInsert: this.DefaultOptions.beforeInsert,
+ matcher: this.DefaultOptions.matcher,
+ beforeSave: function(merges) {
+ return $.map(merges, function(m) {
+ if (m.title == null) {
+ return m;
+ }
+ return {
+ id: m.iid,
+ title: sanitize(m.title),
+ search: m.iid + " " + m.title
+ };
+ });
}
- });
- $input.atwho({
- at: '~',
- alias: 'labels',
- searchKey: 'search',
- data: this.defaultLoadingData,
- displayTpl: function(value) {
- return this.isLoading(value) ? this.Loading.template : this.Labels.template;
- }.bind(this),
- insertTpl: '${atwho-at}${title}',
- callbacks: {
- matcher: this.DefaultOptions.matcher,
- beforeInsert: this.DefaultOptions.beforeInsert,
- filter: this.DefaultOptions.filter,
- sorter: this.DefaultOptions.sorter,
- beforeSave: function(merges) {
- if (gl.GfmAutoComplete.isLoading(merges)) return merges;
- var sanitizeLabelTitle;
- sanitizeLabelTitle = function(title) {
- if (/[\w\?&]+\s+[\w\?&]+/g.test(title)) {
- return "\"" + (sanitize(title)) + "\"";
- } else {
- return sanitize(title);
- }
+ }
+ });
+ $input.atwho({
+ at: '~',
+ alias: 'labels',
+ searchKey: 'search',
+ data: this.defaultLoadingData,
+ displayTpl: function(value) {
+ return this.isLoading(value) ? this.Loading.template : this.Labels.template;
+ }.bind(this),
+ insertTpl: '${atwho-at}${title}',
+ callbacks: {
+ matcher: this.DefaultOptions.matcher,
+ beforeInsert: this.DefaultOptions.beforeInsert,
+ filter: this.DefaultOptions.filter,
+ sorter: this.DefaultOptions.sorter,
+ beforeSave: function(merges) {
+ if (gl.GfmAutoComplete.isLoading(merges)) return merges;
+ var sanitizeLabelTitle;
+ sanitizeLabelTitle = function(title) {
+ if (/[\w\?&]+\s+[\w\?&]+/g.test(title)) {
+ return "\"" + (sanitize(title)) + "\"";
+ } else {
+ return sanitize(title);
+ }
+ };
+ return $.map(merges, function(m) {
+ return {
+ title: sanitize(m.title),
+ color: m.color,
+ search: "" + m.title
};
- return $.map(merges, function(m) {
- return {
- title: sanitize(m.title),
- color: m.color,
- search: "" + m.title
- };
- });
- }
+ });
}
- });
- // We don't instantiate the slash commands autocomplete for note and issue/MR edit forms
- $input.filter('[data-supports-slash-commands="true"]').atwho({
- at: '/',
- alias: 'commands',
- searchKey: 'search',
- skipSpecialCharacterTest: true,
- data: this.defaultLoadingData,
- displayTpl: function(value) {
- if (this.isLoading(value)) return this.Loading.template;
- var tpl = '<li>/${name}';
- if (value.aliases.length > 0) {
- tpl += ' <small>(or /<%- aliases.join(", /") %>)</small>';
- }
- if (value.params.length > 0) {
- tpl += ' <small><%- params.join(" ") %></small>';
- }
- if (value.description !== '') {
- tpl += '<small class="description"><i><%- description %></i></small>';
+ }
+ });
+ // We don't instantiate the slash commands autocomplete for note and issue/MR edit forms
+ $input.filter('[data-supports-slash-commands="true"]').atwho({
+ at: '/',
+ alias: 'commands',
+ searchKey: 'search',
+ skipSpecialCharacterTest: true,
+ data: this.defaultLoadingData,
+ displayTpl: function(value) {
+ if (this.isLoading(value)) return this.Loading.template;
+ var tpl = '<li>/${name}';
+ if (value.aliases.length > 0) {
+ tpl += ' <small>(or /<%- aliases.join(", /") %>)</small>';
+ }
+ if (value.params.length > 0) {
+ tpl += ' <small><%- params.join(" ") %></small>';
+ }
+ if (value.description !== '') {
+ tpl += '<small class="description"><i><%- description %></i></small>';
+ }
+ tpl += '</li>';
+ return _.template(tpl)(value);
+ }.bind(this),
+ insertTpl: function(value) {
+ var tpl = "/${name} ";
+ var reference_prefix = null;
+ if (value.params.length > 0) {
+ reference_prefix = value.params[0][0];
+ if (/^[@%~]/.test(reference_prefix)) {
+ tpl += '<%- reference_prefix %>';
}
- tpl += '</li>';
- return _.template(tpl)(value);
- }.bind(this),
- insertTpl: function(value) {
- var tpl = "/${name} ";
- var reference_prefix = null;
- if (value.params.length > 0) {
- reference_prefix = value.params[0][0];
- if (/^[@%~]/.test(reference_prefix)) {
- tpl += '<%- reference_prefix %>';
+ }
+ return _.template(tpl)({ reference_prefix: reference_prefix });
+ },
+ suffix: '',
+ callbacks: {
+ sorter: this.DefaultOptions.sorter,
+ filter: this.DefaultOptions.filter,
+ beforeInsert: this.DefaultOptions.beforeInsert,
+ beforeSave: function(commands) {
+ if (gl.GfmAutoComplete.isLoading(commands)) return commands;
+ return $.map(commands, function(c) {
+ var search = c.name;
+ if (c.aliases.length > 0) {
+ search = search + " " + c.aliases.join(" ");
}
- }
- return _.template(tpl)({ reference_prefix: reference_prefix });
+ return {
+ name: c.name,
+ aliases: c.aliases,
+ params: c.params,
+ description: c.description,
+ search: search
+ };
+ });
},
- suffix: '',
- callbacks: {
- sorter: this.DefaultOptions.sorter,
- filter: this.DefaultOptions.filter,
- beforeInsert: this.DefaultOptions.beforeInsert,
- beforeSave: function(commands) {
- if (gl.GfmAutoComplete.isLoading(commands)) return commands;
- return $.map(commands, function(c) {
- var search = c.name;
- if (c.aliases.length > 0) {
- search = search + " " + c.aliases.join(" ");
- }
- return {
- name: c.name,
- aliases: c.aliases,
- params: c.params,
- description: c.description,
- search: search
- };
- });
- },
- matcher: function(flag, subtext, should_startWithSpace, acceptSpaceBar) {
- var regexp = /(?:^|\n)\/([A-Za-z_]*)$/gi;
- var match = regexp.exec(subtext);
- if (match) {
- return match[1];
- } else {
- return null;
- }
+ matcher: function(flag, subtext, should_startWithSpace, acceptSpaceBar) {
+ var regexp = /(?:^|\n)\/([A-Za-z_]*)$/gi;
+ var match = regexp.exec(subtext);
+ if (match) {
+ return match[1];
+ } else {
+ return null;
}
}
- });
- return;
- },
- fetchData: function($input, at) {
- if (this.isLoadingData[at]) return;
- this.isLoadingData[at] = true;
- if (this.cachedData[at]) {
- this.loadData($input, at, this.cachedData[at]);
- } else if (this.atTypeMap[at] === 'emojis') {
- this.loadData($input, at, Object.keys(emojiMap).concat(Object.keys(emojiAliases)));
- } else {
- $.getJSON(this.dataSources[this.atTypeMap[at]], (data) => {
- this.loadData($input, at, data);
- }).fail(() => { this.isLoadingData[at] = false; });
- }
- },
- loadData: function($input, at, data) {
- this.isLoadingData[at] = false;
- this.cachedData[at] = data;
- $input.atwho('load', at, data);
- // This trigger at.js again
- // otherwise we would be stuck with loading until the user types
- return $input.trigger('keyup');
- },
- isLoading(data) {
- var dataToInspect = data;
- if (data && data.length > 0) {
- dataToInspect = data[0];
}
-
- var loadingState = this.defaultLoadingData[0];
- return dataToInspect &&
- (dataToInspect === loadingState || dataToInspect.name === loadingState);
+ });
+ return;
+ },
+ fetchData: function($input, at) {
+ if (this.isLoadingData[at]) return;
+ this.isLoadingData[at] = true;
+ if (this.cachedData[at]) {
+ this.loadData($input, at, this.cachedData[at]);
+ } else if (this.atTypeMap[at] === 'emojis') {
+ this.loadData($input, at, Object.keys(emojiMap).concat(Object.keys(emojiAliases)));
+ } else {
+ $.getJSON(this.dataSources[this.atTypeMap[at]], (data) => {
+ this.loadData($input, at, data);
+ }).fail(() => { this.isLoadingData[at] = false; });
+ }
+ },
+ loadData: function($input, at, data) {
+ this.isLoadingData[at] = false;
+ this.cachedData[at] = data;
+ $input.atwho('load', at, data);
+ // This trigger at.js again
+ // otherwise we would be stuck with loading until the user types
+ return $input.trigger('keyup');
+ },
+ isLoading(data) {
+ var dataToInspect = data;
+ if (data && data.length > 0) {
+ dataToInspect = data[0];
}
- };
-}).call(window);
+
+ var loadingState = this.defaultLoadingData[0];
+ return dataToInspect &&
+ (dataToInspect === loadingState || dataToInspect.name === loadingState);
+ }
+};
diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js
index 9e6ed06054b..a03f1202a6d 100644
--- a/app/assets/javascripts/gl_dropdown.js
+++ b/app/assets/javascripts/gl_dropdown.js
@@ -1,850 +1,848 @@
/* 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 */
-(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 += 1) { if (i in this && this[i] === item) return i; } return -1; };
-
- GitLabDropdownFilter = (function() {
- var ARROW_KEY_CODES, BLUR_KEYCODES, HAS_VALUE_CLASS;
-
- BLUR_KEYCODES = [27, 40];
-
- ARROW_KEY_CODES = [38, 40];
-
- HAS_VALUE_CLASS = "has-value";
-
- function GitLabDropdownFilter(input, options) {
- var $clearButton, $inputContainer, ref, timeout;
- this.input = input;
- this.options = options;
- this.filterInputBlur = (ref = this.options.filterInputBlur) != null ? ref : true;
- $inputContainer = this.input.parent();
- $clearButton = $inputContainer.find('.js-dropdown-input-clear');
- $clearButton.on('click', (function(_this) {
- // Clear click
- return function(e) {
+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 += 1) { if (i in this && this[i] === item) return i; } return -1; };
+
+GitLabDropdownFilter = (function() {
+ var ARROW_KEY_CODES, BLUR_KEYCODES, HAS_VALUE_CLASS;
+
+ BLUR_KEYCODES = [27, 40];
+
+ ARROW_KEY_CODES = [38, 40];
+
+ HAS_VALUE_CLASS = "has-value";
+
+ function GitLabDropdownFilter(input, options) {
+ var $clearButton, $inputContainer, ref, timeout;
+ this.input = input;
+ this.options = options;
+ this.filterInputBlur = (ref = this.options.filterInputBlur) != null ? ref : true;
+ $inputContainer = this.input.parent();
+ $clearButton = $inputContainer.find('.js-dropdown-input-clear');
+ $clearButton.on('click', (function(_this) {
+ // Clear click
+ return function(e) {
+ e.preventDefault();
+ e.stopPropagation();
+ return _this.input.val('').trigger('input').focus();
+ };
+ })(this));
+ // Key events
+ timeout = "";
+ this.input
+ .on('keydown', function (e) {
+ var keyCode = e.which;
+ if (keyCode === 13 && !options.elIsInput) {
e.preventDefault();
- e.stopPropagation();
- return _this.input.val('').trigger('input').focus();
- };
- })(this));
- // Key events
- timeout = "";
- this.input
- .on('keydown', function (e) {
- var keyCode = e.which;
- if (keyCode === 13 && !options.elIsInput) {
- e.preventDefault();
- }
- })
- .on('input', function() {
- if (this.input.val() !== "" && !$inputContainer.hasClass(HAS_VALUE_CLASS)) {
- $inputContainer.addClass(HAS_VALUE_CLASS);
- } else if (this.input.val() === "" && $inputContainer.hasClass(HAS_VALUE_CLASS)) {
- $inputContainer.removeClass(HAS_VALUE_CLASS);
- }
- // Only filter asynchronously only if option remote is set
- if (this.options.remote) {
- clearTimeout(timeout);
- return timeout = setTimeout(function() {
- $inputContainer.parent().addClass('is-loading');
-
- return this.options.query(this.input.val(), function(data) {
- $inputContainer.parent().removeClass('is-loading');
- return this.options.callback(data);
- }.bind(this));
- }.bind(this), 250);
- } else {
- return this.filter(this.input.val());
- }
- }.bind(this));
- }
+ }
+ })
+ .on('input', function() {
+ if (this.input.val() !== "" && !$inputContainer.hasClass(HAS_VALUE_CLASS)) {
+ $inputContainer.addClass(HAS_VALUE_CLASS);
+ } else if (this.input.val() === "" && $inputContainer.hasClass(HAS_VALUE_CLASS)) {
+ $inputContainer.removeClass(HAS_VALUE_CLASS);
+ }
+ // Only filter asynchronously only if option remote is set
+ if (this.options.remote) {
+ clearTimeout(timeout);
+ return timeout = setTimeout(function() {
+ $inputContainer.parent().addClass('is-loading');
+
+ return this.options.query(this.input.val(), function(data) {
+ $inputContainer.parent().removeClass('is-loading');
+ return this.options.callback(data);
+ }.bind(this));
+ }.bind(this), 250);
+ } else {
+ return this.filter(this.input.val());
+ }
+ }.bind(this));
+ }
- GitLabDropdownFilter.prototype.shouldBlur = function(keyCode) {
- return BLUR_KEYCODES.indexOf(keyCode) !== -1;
- };
+ GitLabDropdownFilter.prototype.shouldBlur = function(keyCode) {
+ return BLUR_KEYCODES.indexOf(keyCode) !== -1;
+ };
- GitLabDropdownFilter.prototype.filter = function(search_text) {
- var data, elements, group, key, results, tmp;
- if (this.options.onFilter) {
- this.options.onFilter(search_text);
- }
- data = this.options.data();
- if ((data != null) && !this.options.filterByText) {
- results = data;
- if (search_text !== '') {
- // When data is an array of objects therefore [object Array] e.g.
- // [
- // { prop: 'foo' },
- // { prop: 'baz' }
- // ]
- if (_.isArray(data)) {
- results = fuzzaldrinPlus.filter(data, search_text, {
- key: this.options.keys
- });
- } else {
- // If data is grouped therefore an [object Object]. e.g.
- // {
- // groupName1: [
- // { prop: 'foo' },
- // { prop: 'baz' }
- // ],
- // groupName2: [
- // { prop: 'abc' },
- // { prop: 'def' }
- // ]
- // }
- if (gl.utils.isObject(data)) {
- results = {};
- for (key in data) {
- group = data[key];
- tmp = fuzzaldrinPlus.filter(group, search_text, {
- key: this.options.keys
+ GitLabDropdownFilter.prototype.filter = function(search_text) {
+ var data, elements, group, key, results, tmp;
+ if (this.options.onFilter) {
+ this.options.onFilter(search_text);
+ }
+ data = this.options.data();
+ if ((data != null) && !this.options.filterByText) {
+ results = data;
+ if (search_text !== '') {
+ // When data is an array of objects therefore [object Array] e.g.
+ // [
+ // { prop: 'foo' },
+ // { prop: 'baz' }
+ // ]
+ if (_.isArray(data)) {
+ results = fuzzaldrinPlus.filter(data, search_text, {
+ key: this.options.keys
+ });
+ } else {
+ // If data is grouped therefore an [object Object]. e.g.
+ // {
+ // groupName1: [
+ // { prop: 'foo' },
+ // { prop: 'baz' }
+ // ],
+ // groupName2: [
+ // { prop: 'abc' },
+ // { prop: 'def' }
+ // ]
+ // }
+ if (gl.utils.isObject(data)) {
+ results = {};
+ for (key in data) {
+ group = data[key];
+ tmp = fuzzaldrinPlus.filter(group, search_text, {
+ key: this.options.keys
+ });
+ if (tmp.length) {
+ results[key] = tmp.map(function(item) {
+ return item;
});
- if (tmp.length) {
- results[key] = tmp.map(function(item) {
- return item;
- });
- }
}
}
}
}
- return this.options.callback(results);
- } else {
- elements = this.options.elements();
- if (search_text) {
- return elements.each(function() {
- var $el, matches;
- $el = $(this);
- matches = fuzzaldrinPlus.match($el.text().trim(), search_text);
- if (!$el.is('.dropdown-header')) {
- if (matches.length) {
- return $el.show().removeClass('option-hidden');
- } else {
- return $el.hide().addClass('option-hidden');
- }
+ }
+ return this.options.callback(results);
+ } else {
+ elements = this.options.elements();
+ if (search_text) {
+ return elements.each(function() {
+ var $el, matches;
+ $el = $(this);
+ matches = fuzzaldrinPlus.match($el.text().trim(), search_text);
+ if (!$el.is('.dropdown-header')) {
+ if (matches.length) {
+ return $el.show().removeClass('option-hidden');
+ } else {
+ return $el.hide().addClass('option-hidden');
}
- });
- } else {
- return elements.show().removeClass('option-hidden');
- }
+ }
+ });
+ } else {
+ return elements.show().removeClass('option-hidden');
}
- };
-
- return GitLabDropdownFilter;
- })();
+ }
+ };
- GitLabDropdownRemote = (function() {
- function GitLabDropdownRemote(dataEndpoint, options) {
- this.dataEndpoint = dataEndpoint;
- this.options = options;
+ return GitLabDropdownFilter;
+})();
+
+GitLabDropdownRemote = (function() {
+ function GitLabDropdownRemote(dataEndpoint, options) {
+ this.dataEndpoint = dataEndpoint;
+ this.options = options;
+ }
+
+ GitLabDropdownRemote.prototype.execute = function() {
+ if (typeof this.dataEndpoint === "string") {
+ return this.fetchData();
+ } else if (typeof this.dataEndpoint === "function") {
+ if (this.options.beforeSend) {
+ this.options.beforeSend();
+ }
+ return this.dataEndpoint("", (function(_this) {
+ // Fetch the data by calling the data funcfion
+ return function(data) {
+ if (_this.options.success) {
+ _this.options.success(data);
+ }
+ if (_this.options.beforeSend) {
+ return _this.options.beforeSend();
+ }
+ };
+ })(this));
}
+ };
- GitLabDropdownRemote.prototype.execute = function() {
- if (typeof this.dataEndpoint === "string") {
- return this.fetchData();
- } else if (typeof this.dataEndpoint === "function") {
- if (this.options.beforeSend) {
- this.options.beforeSend();
- }
- return this.dataEndpoint("", (function(_this) {
- // Fetch the data by calling the data funcfion
- return function(data) {
- if (_this.options.success) {
- _this.options.success(data);
- }
- if (_this.options.beforeSend) {
- return _this.options.beforeSend();
- }
- };
- })(this));
- }
- };
+ GitLabDropdownRemote.prototype.fetchData = function() {
+ return $.ajax({
+ url: this.dataEndpoint,
+ dataType: this.options.dataType,
+ beforeSend: (function(_this) {
+ return function() {
+ if (_this.options.beforeSend) {
+ return _this.options.beforeSend();
+ }
+ };
+ })(this),
+ success: (function(_this) {
+ return function(data) {
+ if (_this.options.success) {
+ return _this.options.success(data);
+ }
+ };
+ })(this)
+ });
+ // Fetch the data through ajax if the data is a string
+ };
- GitLabDropdownRemote.prototype.fetchData = function() {
- return $.ajax({
- url: this.dataEndpoint,
- dataType: this.options.dataType,
- beforeSend: (function(_this) {
- return function() {
- if (_this.options.beforeSend) {
- return _this.options.beforeSend();
- }
- };
- })(this),
- success: (function(_this) {
- return function(data) {
- if (_this.options.success) {
- return _this.options.success(data);
- }
- };
- })(this)
- });
- // Fetch the data through ajax if the data is a string
- };
+ return GitLabDropdownRemote;
+})();
- return GitLabDropdownRemote;
- })();
+GitLabDropdown = (function() {
+ var ACTIVE_CLASS, FILTER_INPUT, INDETERMINATE_CLASS, LOADING_CLASS, PAGE_TWO_CLASS, NON_SELECTABLE_CLASSES, SELECTABLE_CLASSES, CURSOR_SELECT_SCROLL_PADDING, currentIndex;
- GitLabDropdown = (function() {
- var ACTIVE_CLASS, FILTER_INPUT, INDETERMINATE_CLASS, LOADING_CLASS, PAGE_TWO_CLASS, NON_SELECTABLE_CLASSES, SELECTABLE_CLASSES, CURSOR_SELECT_SCROLL_PADDING, currentIndex;
+ LOADING_CLASS = "is-loading";
- LOADING_CLASS = "is-loading";
+ PAGE_TWO_CLASS = "is-page-two";
- PAGE_TWO_CLASS = "is-page-two";
+ ACTIVE_CLASS = "is-active";
- ACTIVE_CLASS = "is-active";
+ INDETERMINATE_CLASS = "is-indeterminate";
- INDETERMINATE_CLASS = "is-indeterminate";
+ currentIndex = -1;
- currentIndex = -1;
+ NON_SELECTABLE_CLASSES = '.divider, .separator, .dropdown-header, .dropdown-menu-empty-link';
- NON_SELECTABLE_CLASSES = '.divider, .separator, .dropdown-header, .dropdown-menu-empty-link';
-
- SELECTABLE_CLASSES = ".dropdown-content li:not(" + NON_SELECTABLE_CLASSES + ", .option-hidden)";
-
- CURSOR_SELECT_SCROLL_PADDING = 5;
-
- FILTER_INPUT = '.dropdown-input .dropdown-input-field';
-
- function GitLabDropdown(el1, options) {
- var searchFields, selector, self;
- this.el = el1;
- this.options = options;
- this.updateLabel = bind(this.updateLabel, this);
- this.hidden = bind(this.hidden, this);
- this.opened = bind(this.opened, this);
- this.shouldPropagate = bind(this.shouldPropagate, this);
- self = this;
- selector = $(this.el).data("target");
- 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.filterInputBlur = this.options.filterInputBlur != null
- ? this.options.filterInputBlur
- : true;
- // If no input is passed create a default one
- self = this;
- // If selector was passed
- if (_.isString(this.filterInput)) {
- this.filterInput = this.getElement(this.filterInput);
- }
- searchFields = this.options.search ? this.options.search.fields : [];
- if (this.options.data) {
- // If we provided data
- // data could be an array of objects or a group of arrays
- if (_.isObject(this.options.data) && !_.isFunction(this.options.data)) {
- this.fullData = this.options.data;
- currentIndex = -1;
- this.parseData(this.options.data);
- this.focusTextInput();
- } else {
- this.remote = new GitLabDropdownRemote(this.options.data, {
- dataType: this.options.dataType,
- beforeSend: this.toggleLoading.bind(this),
- success: (function(_this) {
- return function(data) {
- _this.fullData = data;
- _this.parseData(_this.fullData);
- _this.focusTextInput();
- if (_this.options.filterable && _this.filter && _this.filter.input && _this.filter.input.val() && _this.filter.input.val().trim() !== '') {
- return _this.filter.input.trigger('input');
- }
- };
- // Remote data
- })(this)
- });
- }
- }
- // Init filterable
- if (this.options.filterable) {
- this.filter = new GitLabDropdownFilter(this.filterInput, {
- elIsInput: $(this.el).is('input'),
- filterInputBlur: this.filterInputBlur,
- filterByText: this.options.filterByText,
- onFilter: this.options.onFilter,
- remote: this.options.filterRemote,
- query: this.options.data,
- keys: searchFields,
- elements: (function(_this) {
- return function() {
- selector = '.dropdown-content li:not(' + NON_SELECTABLE_CLASSES + ')';
- if (_this.dropdown.find('.dropdown-toggle-page').length) {
- selector = ".dropdown-page-one " + selector;
- }
- return $(selector);
- };
- })(this),
- data: (function(_this) {
- return function() {
- return _this.fullData;
- };
- })(this),
- callback: (function(_this) {
+ SELECTABLE_CLASSES = ".dropdown-content li:not(" + NON_SELECTABLE_CLASSES + ", .option-hidden)";
+
+ CURSOR_SELECT_SCROLL_PADDING = 5;
+
+ FILTER_INPUT = '.dropdown-input .dropdown-input-field';
+
+ function GitLabDropdown(el1, options) {
+ var searchFields, selector, self;
+ this.el = el1;
+ this.options = options;
+ this.updateLabel = bind(this.updateLabel, this);
+ this.hidden = bind(this.hidden, this);
+ this.opened = bind(this.opened, this);
+ this.shouldPropagate = bind(this.shouldPropagate, this);
+ self = this;
+ selector = $(this.el).data("target");
+ 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.filterInputBlur = this.options.filterInputBlur != null
+ ? this.options.filterInputBlur
+ : true;
+ // If no input is passed create a default one
+ self = this;
+ // If selector was passed
+ if (_.isString(this.filterInput)) {
+ this.filterInput = this.getElement(this.filterInput);
+ }
+ searchFields = this.options.search ? this.options.search.fields : [];
+ if (this.options.data) {
+ // If we provided data
+ // data could be an array of objects or a group of arrays
+ if (_.isObject(this.options.data) && !_.isFunction(this.options.data)) {
+ this.fullData = this.options.data;
+ currentIndex = -1;
+ this.parseData(this.options.data);
+ this.focusTextInput();
+ } else {
+ this.remote = new GitLabDropdownRemote(this.options.data, {
+ dataType: this.options.dataType,
+ beforeSend: this.toggleLoading.bind(this),
+ success: (function(_this) {
return function(data) {
- _this.parseData(data);
- if (_this.filterInput.val() !== '') {
- selector = SELECTABLE_CLASSES;
- if (_this.dropdown.find('.dropdown-toggle-page').length) {
- selector = ".dropdown-page-one " + selector;
- }
- if ($(_this.el).is('input')) {
- currentIndex = -1;
- } else {
- $(selector, _this.dropdown).first().find('a').addClass('is-focused');
- currentIndex = 0;
- }
+ _this.fullData = data;
+ _this.parseData(_this.fullData);
+ _this.focusTextInput();
+ if (_this.options.filterable && _this.filter && _this.filter.input && _this.filter.input.val() && _this.filter.input.val().trim() !== '') {
+ return _this.filter.input.trigger('input');
}
};
+ // Remote data
})(this)
});
}
- // Event listeners
- this.dropdown.on("shown.bs.dropdown", this.opened);
- this.dropdown.on("hidden.bs.dropdown", this.hidden);
- $(this.el).on("update.label", this.updateLabel);
- this.dropdown.on("click", ".dropdown-menu, .dropdown-menu-close", this.shouldPropagate);
- this.dropdown.on('keyup', (function(_this) {
- return function(e) {
- // Escape key
- if (e.which === 27) {
- return $('.dropdown-menu-close', _this.dropdown).trigger('click');
- }
- };
- })(this));
- this.dropdown.on('blur', 'a', (function(_this) {
- return function(e) {
- var $dropdownMenu, $relatedTarget;
- if (e.relatedTarget != null) {
- $relatedTarget = $(e.relatedTarget);
- $dropdownMenu = $relatedTarget.closest('.dropdown-menu');
- if ($dropdownMenu.length === 0) {
- return _this.dropdown.removeClass('open');
+ }
+ // Init filterable
+ if (this.options.filterable) {
+ this.filter = new GitLabDropdownFilter(this.filterInput, {
+ elIsInput: $(this.el).is('input'),
+ filterInputBlur: this.filterInputBlur,
+ filterByText: this.options.filterByText,
+ onFilter: this.options.onFilter,
+ remote: this.options.filterRemote,
+ query: this.options.data,
+ keys: searchFields,
+ elements: (function(_this) {
+ return function() {
+ selector = '.dropdown-content li:not(' + NON_SELECTABLE_CLASSES + ')';
+ if (_this.dropdown.find('.dropdown-toggle-page').length) {
+ selector = ".dropdown-page-one " + selector;
+ }
+ return $(selector);
+ };
+ })(this),
+ data: (function(_this) {
+ return function() {
+ return _this.fullData;
+ };
+ })(this),
+ callback: (function(_this) {
+ return function(data) {
+ _this.parseData(data);
+ if (_this.filterInput.val() !== '') {
+ selector = SELECTABLE_CLASSES;
+ if (_this.dropdown.find('.dropdown-toggle-page').length) {
+ selector = ".dropdown-page-one " + selector;
+ }
+ if ($(_this.el).is('input')) {
+ currentIndex = -1;
+ } else {
+ $(selector, _this.dropdown).first().find('a').addClass('is-focused');
+ currentIndex = 0;
+ }
}
+ };
+ })(this)
+ });
+ }
+ // Event listeners
+ this.dropdown.on("shown.bs.dropdown", this.opened);
+ this.dropdown.on("hidden.bs.dropdown", this.hidden);
+ $(this.el).on("update.label", this.updateLabel);
+ this.dropdown.on("click", ".dropdown-menu, .dropdown-menu-close", this.shouldPropagate);
+ this.dropdown.on('keyup', (function(_this) {
+ return function(e) {
+ // Escape key
+ if (e.which === 27) {
+ return $('.dropdown-menu-close', _this.dropdown).trigger('click');
+ }
+ };
+ })(this));
+ this.dropdown.on('blur', 'a', (function(_this) {
+ return function(e) {
+ var $dropdownMenu, $relatedTarget;
+ if (e.relatedTarget != null) {
+ $relatedTarget = $(e.relatedTarget);
+ $dropdownMenu = $relatedTarget.closest('.dropdown-menu');
+ if ($dropdownMenu.length === 0) {
+ return _this.dropdown.removeClass('open');
}
+ }
+ };
+ })(this));
+ if (this.dropdown.find(".dropdown-toggle-page").length) {
+ this.dropdown.find(".dropdown-toggle-page, .dropdown-menu-back").on("click", (function(_this) {
+ return function(e) {
+ e.preventDefault();
+ e.stopPropagation();
+ return _this.togglePage();
};
})(this));
+ }
+ if (this.options.selectable) {
+ selector = ".dropdown-content a";
if (this.dropdown.find(".dropdown-toggle-page").length) {
- this.dropdown.find(".dropdown-toggle-page, .dropdown-menu-back").on("click", (function(_this) {
- return function(e) {
- e.preventDefault();
- e.stopPropagation();
- return _this.togglePage();
- };
- })(this));
- }
- if (this.options.selectable) {
- selector = ".dropdown-content a";
- if (this.dropdown.find(".dropdown-toggle-page").length) {
- selector = ".dropdown-page-one .dropdown-content a";
+ selector = ".dropdown-page-one .dropdown-content a";
+ }
+ this.dropdown.on("click", selector, function(e) {
+ var $el, selected, selectedObj, isMarking;
+ $el = $(this);
+ selected = self.rowClicked($el);
+ selectedObj = selected ? selected[0] : null;
+ isMarking = selected ? selected[1] : null;
+ if (self.options.clicked) {
+ self.options.clicked(selectedObj, $el, e, isMarking);
}
- this.dropdown.on("click", selector, function(e) {
- var $el, selected, selectedObj, isMarking;
- $el = $(this);
- selected = self.rowClicked($el);
- selectedObj = selected ? selected[0] : null;
- isMarking = selected ? selected[1] : null;
- if (self.options.clicked) {
- self.options.clicked(selectedObj, $el, e, isMarking);
- }
- // Update label right after all modifications in dropdown has been done
- if (self.options.toggleLabel) {
- self.updateLabel(selectedObj, $el, self);
- }
+ // Update label right after all modifications in dropdown has been done
+ if (self.options.toggleLabel) {
+ self.updateLabel(selectedObj, $el, self);
+ }
- $el.trigger('blur');
- });
- }
+ $el.trigger('blur');
+ });
}
+ }
- // Finds an element inside wrapper element
- GitLabDropdown.prototype.getElement = function(selector) {
- return this.dropdown.find(selector);
- };
+ // Finds an element inside wrapper element
+ GitLabDropdown.prototype.getElement = function(selector) {
+ return this.dropdown.find(selector);
+ };
- GitLabDropdown.prototype.toggleLoading = function() {
- return $('.dropdown-menu', this.dropdown).toggleClass(LOADING_CLASS);
- };
+ GitLabDropdown.prototype.toggleLoading = function() {
+ return $('.dropdown-menu', this.dropdown).toggleClass(LOADING_CLASS);
+ };
- GitLabDropdown.prototype.togglePage = function() {
- var menu;
- menu = $('.dropdown-menu', this.dropdown);
- if (menu.hasClass(PAGE_TWO_CLASS)) {
- if (this.remote) {
- this.remote.execute();
- }
- }
- menu.toggleClass(PAGE_TWO_CLASS);
- // Focus first visible input on active page
- return this.dropdown.find('[class^="dropdown-page-"]:visible :text:visible:first').focus();
- };
-
- GitLabDropdown.prototype.parseData = function(data) {
- var full_html, groupData, html, name;
- this.renderedData = data;
- if (this.options.filterable && data.length === 0) {
- // render no matching results
- html = [this.noResults()];
- } else {
- // Handle array groups
- if (gl.utils.isObject(data)) {
- html = [];
- for (name in data) {
- groupData = data[name];
- html.push(this.renderItem({
- header: name
- // Add header for each group
- }, name));
- this.renderData(groupData, name).map(function(item) {
- return html.push(item);
- });
- }
- } else {
- // Render each row
- html = this.renderData(data);
- }
- }
- // Render the full menu
- full_html = this.renderMenu(html);
- return this.appendMenu(full_html);
- };
-
- GitLabDropdown.prototype.renderData = function(data, group) {
- if (group == null) {
- group = false;
+ GitLabDropdown.prototype.togglePage = function() {
+ var menu;
+ menu = $('.dropdown-menu', this.dropdown);
+ if (menu.hasClass(PAGE_TWO_CLASS)) {
+ if (this.remote) {
+ this.remote.execute();
}
- return data.map((function(_this) {
- return function(obj, index) {
- return _this.renderItem(obj, group, index);
- };
- })(this));
- };
-
- GitLabDropdown.prototype.shouldPropagate = function(e) {
- var $target;
- if (this.options.multiSelect) {
- $target = $(e.target);
- if ($target && !$target.hasClass('dropdown-menu-close') &&
- !$target.hasClass('dropdown-menu-close-icon') &&
- !$target.data('is-link')) {
- e.stopPropagation();
- return false;
- } else {
- return true;
+ }
+ menu.toggleClass(PAGE_TWO_CLASS);
+ // Focus first visible input on active page
+ return this.dropdown.find('[class^="dropdown-page-"]:visible :text:visible:first').focus();
+ };
+
+ GitLabDropdown.prototype.parseData = function(data) {
+ var full_html, groupData, html, name;
+ this.renderedData = data;
+ if (this.options.filterable && data.length === 0) {
+ // render no matching results
+ html = [this.noResults()];
+ } else {
+ // Handle array groups
+ if (gl.utils.isObject(data)) {
+ html = [];
+ for (name in data) {
+ groupData = data[name];
+ html.push(this.renderItem({
+ header: name
+ // Add header for each group
+ }, name));
+ this.renderData(groupData, name).map(function(item) {
+ return html.push(item);
+ });
}
+ } else {
+ // Render each row
+ html = this.renderData(data);
}
- };
+ }
+ // Render the full menu
+ full_html = this.renderMenu(html);
+ return this.appendMenu(full_html);
+ };
- GitLabDropdown.prototype.opened = function(e) {
- var contentHtml;
- this.resetRows();
- this.addArrowKeyEvent();
+ GitLabDropdown.prototype.renderData = function(data, group) {
+ if (group == null) {
+ group = false;
+ }
+ return data.map((function(_this) {
+ return function(obj, index) {
+ return _this.renderItem(obj, group, index);
+ };
+ })(this));
+ };
- // Makes indeterminate items effective
- if (this.fullData && this.dropdown.find('.dropdown-menu-toggle').hasClass('js-filter-bulk-update')) {
- this.parseData(this.fullData);
- }
- contentHtml = $('.dropdown-content', this.dropdown).html();
- if (this.remote && contentHtml === "") {
- this.remote.execute();
+ GitLabDropdown.prototype.shouldPropagate = function(e) {
+ var $target;
+ if (this.options.multiSelect) {
+ $target = $(e.target);
+ if ($target && !$target.hasClass('dropdown-menu-close') &&
+ !$target.hasClass('dropdown-menu-close-icon') &&
+ !$target.data('is-link')) {
+ e.stopPropagation();
+ return false;
} else {
- this.focusTextInput();
+ return true;
}
+ }
+ };
- if (this.options.showMenuAbove) {
- this.positionMenuAbove();
- }
+ GitLabDropdown.prototype.opened = function(e) {
+ var contentHtml;
+ this.resetRows();
+ this.addArrowKeyEvent();
- if (this.options.opened) {
- this.options.opened.call(this, e);
- }
+ // Makes indeterminate items effective
+ if (this.fullData && this.dropdown.find('.dropdown-menu-toggle').hasClass('js-filter-bulk-update')) {
+ this.parseData(this.fullData);
+ }
+ contentHtml = $('.dropdown-content', this.dropdown).html();
+ if (this.remote && contentHtml === "") {
+ this.remote.execute();
+ } else {
+ this.focusTextInput();
+ }
+
+ if (this.options.showMenuAbove) {
+ this.positionMenuAbove();
+ }
- return this.dropdown.trigger('shown.gl.dropdown');
- };
+ if (this.options.opened) {
+ this.options.opened.call(this, e);
+ }
- GitLabDropdown.prototype.positionMenuAbove = function() {
- var $button = $(this.el);
- var $menu = this.dropdown.find('.dropdown-menu');
+ return this.dropdown.trigger('shown.gl.dropdown');
+ };
- $menu.css('top', ($button.height() + $menu.height()) * -1);
- };
+ GitLabDropdown.prototype.positionMenuAbove = function() {
+ var $button = $(this.el);
+ var $menu = this.dropdown.find('.dropdown-menu');
- GitLabDropdown.prototype.hidden = function(e) {
- var $input;
- this.resetRows();
- this.removeArrayKeyEvent();
- $input = this.dropdown.find(".dropdown-input-field");
- if (this.options.filterable) {
- $input.blur();
- }
- if (this.dropdown.find(".dropdown-toggle-page").length) {
- $('.dropdown-menu', this.dropdown).removeClass(PAGE_TWO_CLASS);
- }
- if (this.options.hidden) {
- this.options.hidden.call(this, e);
- }
- return this.dropdown.trigger('hidden.gl.dropdown');
- };
+ $menu.css('top', ($button.height() + $menu.height()) * -1);
+ };
- // Render the full menu
- GitLabDropdown.prototype.renderMenu = function(html) {
- if (this.options.renderMenu) {
- return this.options.renderMenu(html);
- } else {
- var ul = document.createElement('ul');
+ GitLabDropdown.prototype.hidden = function(e) {
+ var $input;
+ this.resetRows();
+ this.removeArrayKeyEvent();
+ $input = this.dropdown.find(".dropdown-input-field");
+ if (this.options.filterable) {
+ $input.blur();
+ }
+ if (this.dropdown.find(".dropdown-toggle-page").length) {
+ $('.dropdown-menu', this.dropdown).removeClass(PAGE_TWO_CLASS);
+ }
+ if (this.options.hidden) {
+ this.options.hidden.call(this, e);
+ }
+ return this.dropdown.trigger('hidden.gl.dropdown');
+ };
- for (var i = 0; i < html.length; i += 1) {
- var el = html[i];
+ // Render the full menu
+ GitLabDropdown.prototype.renderMenu = function(html) {
+ if (this.options.renderMenu) {
+ return this.options.renderMenu(html);
+ } else {
+ var ul = document.createElement('ul');
- if (el instanceof jQuery) {
- el = el.get(0);
- }
+ for (var i = 0; i < html.length; i += 1) {
+ var el = html[i];
- if (typeof el === 'string') {
- ul.innerHTML += el;
- } else {
- ul.appendChild(el);
- }
+ if (el instanceof jQuery) {
+ el = el.get(0);
}
- return ul;
+ if (typeof el === 'string') {
+ ul.innerHTML += el;
+ } else {
+ ul.appendChild(el);
+ }
}
- };
- // Append the menu into the dropdown
- GitLabDropdown.prototype.appendMenu = function(html) {
- return this.clearMenu().append(html);
- };
+ return ul;
+ }
+ };
+
+ // 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";
- }
+ 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();
- };
+ return $(selector, this.dropdown).empty();
+ };
- GitLabDropdown.prototype.renderItem = function(data, group, index) {
- var field, fieldName, html, selected, text, url, value;
- if (group == null) {
- group = false;
+ GitLabDropdown.prototype.renderItem = function(data, group, index) {
+ var field, fieldName, html, selected, text, url, value;
+ if (group == null) {
+ group = false;
+ }
+ if (index == null) {
+ // Render the row
+ index = false;
+ }
+ html = document.createElement('li');
+ if (data === 'divider' || data === 'separator') {
+ html.className = data;
+ return html;
+ }
+ // Header
+ if (data.header != null) {
+ html.className = 'dropdown-header';
+ html.innerHTML = data.header;
+ return html;
+ }
+ if (this.options.renderRow) {
+ // Call the render function
+ html = this.options.renderRow.call(this.options, data, this);
+ } else {
+ if (!selected) {
+ value = this.options.id ? this.options.id(data) : data.id;
+ fieldName = this.options.fieldName;
+
+ if (value) { value = value.toString().replace(/'/g, '\\\''); }
+
+ field = this.dropdown.parent().find("input[name='" + fieldName + "'][value='" + value + "']");
+ if (field.length) {
+ selected = true;
+ }
}
- if (index == null) {
- // Render the row
- index = false;
+ // Set URL
+ if (this.options.url != null) {
+ url = this.options.url(data);
+ } else {
+ url = data.url != null ? data.url : '#';
}
- html = document.createElement('li');
- if (data === 'divider' || data === 'separator') {
- html.className = data;
- return html;
+ // Set Text
+ if (this.options.text != null) {
+ text = this.options.text(data);
+ } else {
+ text = data.text != null ? data.text : '';
}
- // Header
- if (data.header != null) {
- html.className = 'dropdown-header';
- html.innerHTML = data.header;
- return html;
+ if (this.highlight) {
+ text = this.highlightTextMatches(text, this.filterInput.val());
}
- if (this.options.renderRow) {
- // Call the render function
- html = this.options.renderRow.call(this.options, data, this);
- } else {
- if (!selected) {
- value = this.options.id ? this.options.id(data) : data.id;
- fieldName = this.options.fieldName;
-
- if (value) { value = value.toString().replace(/'/g, '\\\''); }
-
- field = this.dropdown.parent().find("input[name='" + fieldName + "'][value='" + value + "']");
- if (field.length) {
- selected = true;
- }
- }
- // Set URL
- if (this.options.url != null) {
- url = this.options.url(data);
- } else {
- url = data.url != null ? data.url : '#';
- }
- // Set Text
- if (this.options.text != null) {
- text = this.options.text(data);
- } else {
- text = data.text != null ? data.text : '';
- }
- if (this.highlight) {
- text = this.highlightTextMatches(text, this.filterInput.val());
- }
- // Create the list item & the link
- var link = document.createElement('a');
-
- link.href = url;
- link.innerHTML = text;
+ // Create the list item & the link
+ var link = document.createElement('a');
- if (selected) {
- link.className = 'is-active';
- }
-
- if (group) {
- link.dataset.group = group;
- link.dataset.index = index;
- }
+ link.href = url;
+ link.innerHTML = text;
- html.appendChild(link);
+ if (selected) {
+ link.className = 'is-active';
}
- return html;
- };
-
- GitLabDropdown.prototype.highlightTextMatches = function(text, term) {
- var occurrences;
- occurrences = fuzzaldrinPlus.match(text, term);
- return text.split('').map(function(character, i) {
- if (indexOf.call(occurrences, i) !== -1) {
- return "<b>" + character + "</b>";
- } else {
- return character;
- }
- }).join('');
- };
-
- GitLabDropdown.prototype.noResults = function() {
- var html;
- return html = "<li class='dropdown-menu-empty-link'> <a href='#' class='is-focused'> No matching results. </a> </li>";
- };
-
- GitLabDropdown.prototype.rowClicked = function(el) {
- var field, fieldName, groupName, isInput, selectedIndex, selectedObject, value, isMarking;
-
- fieldName = this.options.fieldName;
- isInput = $(this.el).is('input');
- if (this.renderedData) {
- groupName = el.data('group');
- if (groupName) {
- selectedIndex = el.data('index');
- selectedObject = this.renderedData[groupName][selectedIndex];
- } else {
- selectedIndex = el.closest('li').index();
- selectedObject = this.renderedData[selectedIndex];
- }
+
+ if (group) {
+ link.dataset.group = group;
+ link.dataset.index = index;
}
- if (this.options.vue) {
- if (el.hasClass(ACTIVE_CLASS)) {
- el.removeClass(ACTIVE_CLASS);
- } else {
- el.addClass(ACTIVE_CLASS);
- }
+ html.appendChild(link);
+ }
+ return html;
+ };
- return [selectedObject];
+ GitLabDropdown.prototype.highlightTextMatches = function(text, term) {
+ var occurrences;
+ occurrences = fuzzaldrinPlus.match(text, term);
+ return text.split('').map(function(character, i) {
+ if (indexOf.call(occurrences, i) !== -1) {
+ return "<b>" + character + "</b>";
+ } else {
+ return character;
}
+ }).join('');
+ };
- field = [];
- value = this.options.id
- ? this.options.id(selectedObject, el)
- : selectedObject.id;
- if (isInput) {
- field = $(this.el);
- } else if (value) {
- field = this.dropdown.parent().find("input[name='" + fieldName + "'][value='" + value.toString().replace(/'/g, '\\\'') + "']");
- }
+ GitLabDropdown.prototype.noResults = function() {
+ var html;
+ return html = "<li class='dropdown-menu-empty-link'> <a href='#' class='is-focused'> No matching results. </a> </li>";
+ };
+
+ GitLabDropdown.prototype.rowClicked = function(el) {
+ var field, fieldName, groupName, isInput, selectedIndex, selectedObject, value, isMarking;
- if (this.options.isSelectable && !this.options.isSelectable(selectedObject, el)) {
- return;
+ fieldName = this.options.fieldName;
+ isInput = $(this.el).is('input');
+ if (this.renderedData) {
+ groupName = el.data('group');
+ if (groupName) {
+ selectedIndex = el.data('index');
+ selectedObject = this.renderedData[groupName][selectedIndex];
+ } else {
+ selectedIndex = el.closest('li').index();
+ selectedObject = this.renderedData[selectedIndex];
}
+ }
+ if (this.options.vue) {
if (el.hasClass(ACTIVE_CLASS)) {
- isMarking = false;
el.removeClass(ACTIVE_CLASS);
- if (field && field.length) {
- 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) {
- this.clearField(field, isInput);
- }
- if ((!field || !field.length) && fieldName) {
- this.addInput(fieldName, value, selectedObject);
- }
} else {
- isMarking = true;
- if (!this.options.multiSelect || el.hasClass('dropdown-clear-active')) {
- this.dropdown.find("." + ACTIVE_CLASS).removeClass(ACTIVE_CLASS);
- if (!isInput) {
- this.dropdown.parent().find("input[name='" + fieldName + "']").remove();
- }
- }
- if (field && field.length && value == null) {
- this.clearField(field, isInput);
- }
- // Toggle active class for the tick mark
el.addClass(ACTIVE_CLASS);
- if (value != null) {
- if ((!field || !field.length) && fieldName) {
- this.addInput(fieldName, value, selectedObject);
- } else if (field && field.length) {
- field.val(value).trigger('change');
- }
- }
}
- return [selectedObject, isMarking];
- };
+ return [selectedObject];
+ }
- GitLabDropdown.prototype.focusTextInput = function() {
- if (this.options.filterable) { this.filterInput.focus(); }
- };
+ field = [];
+ value = this.options.id
+ ? this.options.id(selectedObject, el)
+ : selectedObject.id;
+ if (isInput) {
+ field = $(this.el);
+ } else if (value) {
+ field = this.dropdown.parent().find("input[name='" + fieldName + "'][value='" + value.toString().replace(/'/g, '\\\'') + "']");
+ }
- GitLabDropdown.prototype.addInput = function(fieldName, value, selectedObject) {
- var $input;
- // Create hidden input for form
- $input = $('<input>').attr('type', 'hidden').attr('name', fieldName).val(value);
- if (this.options.inputId != null) {
- $input.attr('id', this.options.inputId);
- }
- return this.dropdown.before($input);
- };
-
- GitLabDropdown.prototype.selectRowAtIndex = function(index) {
- var $el, selector;
- // If we pass an option index
- if (typeof index !== "undefined") {
- selector = SELECTABLE_CLASSES + ":eq(" + index + ") a";
- } else {
- selector = ".dropdown-content .is-focused";
+ if (this.options.isSelectable && !this.options.isSelectable(selectedObject, el)) {
+ return;
+ }
+
+ if (el.hasClass(ACTIVE_CLASS)) {
+ isMarking = false;
+ el.removeClass(ACTIVE_CLASS);
+ if (field && field.length) {
+ 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) {
+ this.clearField(field, isInput);
+ }
+ if ((!field || !field.length) && fieldName) {
+ this.addInput(fieldName, value, selectedObject);
+ }
+ } else {
+ isMarking = true;
+ if (!this.options.multiSelect || el.hasClass('dropdown-clear-active')) {
+ this.dropdown.find("." + ACTIVE_CLASS).removeClass(ACTIVE_CLASS);
+ if (!isInput) {
+ this.dropdown.parent().find("input[name='" + fieldName + "']").remove();
+ }
}
- if (this.dropdown.find(".dropdown-toggle-page").length) {
- selector = ".dropdown-page-one " + selector;
+ if (field && field.length && value == null) {
+ this.clearField(field, isInput);
}
- // simulate a click on the first link
- $el = $(selector, this.dropdown);
- if ($el.length) {
- var href = $el.attr('href');
- if (href && href !== '#') {
- gl.utils.visitUrl(href);
- } else {
- $el.first().trigger('click');
+ // Toggle active class for the tick mark
+ el.addClass(ACTIVE_CLASS);
+ if (value != null) {
+ if ((!field || !field.length) && fieldName) {
+ this.addInput(fieldName, value, selectedObject);
+ } else if (field && field.length) {
+ field.val(value).trigger('change');
}
}
- };
+ }
- GitLabDropdown.prototype.addArrowKeyEvent = function() {
- var $input, ARROW_KEY_CODES, selector;
- ARROW_KEY_CODES = [38, 40];
- $input = this.dropdown.find(".dropdown-input-field");
- selector = SELECTABLE_CLASSES;
- if (this.dropdown.find(".dropdown-toggle-page").length) {
- selector = ".dropdown-page-one " + selector;
+ return [selectedObject, isMarking];
+ };
+
+ GitLabDropdown.prototype.focusTextInput = function() {
+ if (this.options.filterable) { this.filterInput.focus(); }
+ };
+
+ GitLabDropdown.prototype.addInput = function(fieldName, value, selectedObject) {
+ var $input;
+ // Create hidden input for form
+ $input = $('<input>').attr('type', 'hidden').attr('name', fieldName).val(value);
+ if (this.options.inputId != null) {
+ $input.attr('id', this.options.inputId);
+ }
+ return this.dropdown.before($input);
+ };
+
+ GitLabDropdown.prototype.selectRowAtIndex = function(index) {
+ var $el, selector;
+ // If we pass an option index
+ if (typeof index !== "undefined") {
+ selector = SELECTABLE_CLASSES + ":eq(" + index + ") a";
+ } else {
+ selector = ".dropdown-content .is-focused";
+ }
+ if (this.dropdown.find(".dropdown-toggle-page").length) {
+ selector = ".dropdown-page-one " + selector;
+ }
+ // simulate a click on the first link
+ $el = $(selector, this.dropdown);
+ if ($el.length) {
+ var href = $el.attr('href');
+ if (href && href !== '#') {
+ gl.utils.visitUrl(href);
+ } else {
+ $el.first().trigger('click');
}
- return $('body').on('keydown', (function(_this) {
- return function(e) {
- var $listItems, PREV_INDEX, currentKeyCode;
- currentKeyCode = e.which;
- if (ARROW_KEY_CODES.indexOf(currentKeyCode) !== -1) {
- e.preventDefault();
- e.stopImmediatePropagation();
- PREV_INDEX = currentIndex;
- $listItems = $(selector, _this.dropdown);
- // if @options.filterable
- // $input.blur()
- if (currentKeyCode === 40) {
- // Move down
- if (currentIndex < ($listItems.length - 1)) {
- currentIndex += 1;
- }
- } else if (currentKeyCode === 38) {
- // Move up
- if (currentIndex > 0) {
- currentIndex -= 1;
- }
+ }
+ };
+
+ GitLabDropdown.prototype.addArrowKeyEvent = function() {
+ var $input, ARROW_KEY_CODES, selector;
+ ARROW_KEY_CODES = [38, 40];
+ $input = this.dropdown.find(".dropdown-input-field");
+ selector = SELECTABLE_CLASSES;
+ if (this.dropdown.find(".dropdown-toggle-page").length) {
+ selector = ".dropdown-page-one " + selector;
+ }
+ return $('body').on('keydown', (function(_this) {
+ return function(e) {
+ var $listItems, PREV_INDEX, currentKeyCode;
+ currentKeyCode = e.which;
+ if (ARROW_KEY_CODES.indexOf(currentKeyCode) !== -1) {
+ e.preventDefault();
+ e.stopImmediatePropagation();
+ PREV_INDEX = currentIndex;
+ $listItems = $(selector, _this.dropdown);
+ // if @options.filterable
+ // $input.blur()
+ if (currentKeyCode === 40) {
+ // Move down
+ if (currentIndex < ($listItems.length - 1)) {
+ currentIndex += 1;
}
- if (currentIndex !== PREV_INDEX) {
- _this.highlightRowAtIndex($listItems, currentIndex);
+ } else if (currentKeyCode === 38) {
+ // Move up
+ if (currentIndex > 0) {
+ currentIndex -= 1;
}
- return false;
}
- if (currentKeyCode === 13 && currentIndex !== -1) {
- e.preventDefault();
- _this.selectRowAtIndex();
+ if (currentIndex !== PREV_INDEX) {
+ _this.highlightRowAtIndex($listItems, currentIndex);
}
- };
- })(this));
- };
-
- GitLabDropdown.prototype.removeArrayKeyEvent = function() {
- return $('body').off('keydown');
- };
-
- GitLabDropdown.prototype.resetRows = function resetRows() {
- currentIndex = -1;
- $('.is-focused', this.dropdown).removeClass('is-focused');
- };
-
- GitLabDropdown.prototype.highlightRowAtIndex = function($listItems, index) {
- var $dropdownContent, $listItem, dropdownContentBottom, dropdownContentHeight, dropdownContentTop, dropdownScrollTop, listItemBottom, listItemHeight, listItemTop;
- // Remove the class for the previously focused row
- $('.is-focused', this.dropdown).removeClass('is-focused');
- // Update the class for the row at the specific index
- $listItem = $listItems.eq(index);
- $listItem.find('a:first-child').addClass("is-focused");
- // Dropdown content scroll area
- $dropdownContent = $listItem.closest('.dropdown-content');
- dropdownScrollTop = $dropdownContent.scrollTop();
- dropdownContentHeight = $dropdownContent.outerHeight();
- dropdownContentTop = $dropdownContent.prop('offsetTop');
- dropdownContentBottom = dropdownContentTop + dropdownContentHeight;
- // Get the offset bottom of the list item
- listItemHeight = $listItem.outerHeight();
- listItemTop = $listItem.prop('offsetTop');
- listItemBottom = listItemTop + listItemHeight;
- if (!index) {
- // Scroll the dropdown content to the top
- $dropdownContent.scrollTop(0);
- } else if (index === ($listItems.length - 1)) {
- // Scroll the dropdown content to the bottom
- $dropdownContent.scrollTop($dropdownContent.prop('scrollHeight'));
- } else if (listItemBottom > (dropdownContentBottom + dropdownScrollTop)) {
- // Scroll the dropdown content down
- $dropdownContent.scrollTop(listItemBottom - dropdownContentBottom + CURSOR_SELECT_SCROLL_PADDING);
- } else if (listItemTop < (dropdownContentTop + dropdownScrollTop)) {
- // Scroll the dropdown content up
- return $dropdownContent.scrollTop(listItemTop - dropdownContentTop - CURSOR_SELECT_SCROLL_PADDING);
- }
- };
+ return false;
+ }
+ if (currentKeyCode === 13 && currentIndex !== -1) {
+ e.preventDefault();
+ _this.selectRowAtIndex();
+ }
+ };
+ })(this));
+ };
- GitLabDropdown.prototype.updateLabel = function(selected, el, instance) {
- if (selected == null) {
- selected = null;
- }
- if (el == null) {
- el = null;
- }
- if (instance == null) {
- instance = null;
- }
- return $(this.el).find(".dropdown-toggle-text").text(this.options.toggleLabel(selected, el, instance));
- };
+ GitLabDropdown.prototype.removeArrayKeyEvent = function() {
+ return $('body').off('keydown');
+ };
- GitLabDropdown.prototype.clearField = function(field, isInput) {
- return isInput ? field.val('') : field.remove();
- };
+ GitLabDropdown.prototype.resetRows = function resetRows() {
+ currentIndex = -1;
+ $('.is-focused', this.dropdown).removeClass('is-focused');
+ };
- return GitLabDropdown;
- })();
+ GitLabDropdown.prototype.highlightRowAtIndex = function($listItems, index) {
+ var $dropdownContent, $listItem, dropdownContentBottom, dropdownContentHeight, dropdownContentTop, dropdownScrollTop, listItemBottom, listItemHeight, listItemTop;
+ // Remove the class for the previously focused row
+ $('.is-focused', this.dropdown).removeClass('is-focused');
+ // Update the class for the row at the specific index
+ $listItem = $listItems.eq(index);
+ $listItem.find('a:first-child').addClass("is-focused");
+ // Dropdown content scroll area
+ $dropdownContent = $listItem.closest('.dropdown-content');
+ dropdownScrollTop = $dropdownContent.scrollTop();
+ dropdownContentHeight = $dropdownContent.outerHeight();
+ dropdownContentTop = $dropdownContent.prop('offsetTop');
+ dropdownContentBottom = dropdownContentTop + dropdownContentHeight;
+ // Get the offset bottom of the list item
+ listItemHeight = $listItem.outerHeight();
+ listItemTop = $listItem.prop('offsetTop');
+ listItemBottom = listItemTop + listItemHeight;
+ if (!index) {
+ // Scroll the dropdown content to the top
+ $dropdownContent.scrollTop(0);
+ } else if (index === ($listItems.length - 1)) {
+ // Scroll the dropdown content to the bottom
+ $dropdownContent.scrollTop($dropdownContent.prop('scrollHeight'));
+ } else if (listItemBottom > (dropdownContentBottom + dropdownScrollTop)) {
+ // Scroll the dropdown content down
+ $dropdownContent.scrollTop(listItemBottom - dropdownContentBottom + CURSOR_SELECT_SCROLL_PADDING);
+ } else if (listItemTop < (dropdownContentTop + dropdownScrollTop)) {
+ // Scroll the dropdown content up
+ return $dropdownContent.scrollTop(listItemTop - dropdownContentTop - CURSOR_SELECT_SCROLL_PADDING);
+ }
+ };
- $.fn.glDropdown = function(opts) {
- return this.each(function() {
- if (!$.data(this, 'glDropdown')) {
- return $.data(this, 'glDropdown', new GitLabDropdown(this, opts));
- }
- });
+ GitLabDropdown.prototype.updateLabel = function(selected, el, instance) {
+ if (selected == null) {
+ selected = null;
+ }
+ if (el == null) {
+ el = null;
+ }
+ if (instance == null) {
+ instance = null;
+ }
+ return $(this.el).find(".dropdown-toggle-text").text(this.options.toggleLabel(selected, el, instance));
+ };
+
+ GitLabDropdown.prototype.clearField = function(field, isInput) {
+ return isInput ? field.val('') : field.remove();
};
-}).call(window);
+
+ return GitLabDropdown;
+})();
+
+$.fn.glDropdown = function(opts) {
+ return this.each(function() {
+ if (!$.data(this, 'glDropdown')) {
+ return $.data(this, 'glDropdown', new GitLabDropdown(this, opts));
+ }
+ });
+};
diff --git a/app/assets/javascripts/gl_field_error.js b/app/assets/javascripts/gl_field_error.js
index f7cbecc0385..76de249ac3b 100644
--- a/app/assets/javascripts/gl_field_error.js
+++ b/app/assets/javascripts/gl_field_error.js
@@ -1,164 +1,162 @@
-/* eslint-disable no-param-reassign */
-((global) => {
- /*
- * This class overrides the browser's validation error bubbles, displaying custom
- * error messages for invalid fields instead. To begin validating any form, add the
- * class `gl-show-field-errors` to the form element, and ensure error messages are
- * declared in each inputs' `title` attribute. If no title is declared for an invalid
- * field the user attempts to submit, "This field is required." will be shown by default.
- *
- * Opt not to validate certain fields by adding the class `gl-field-error-ignore` to the input.
- *
- * Set a custom error anchor for error message to be injected after with the
- * class `gl-field-error-anchor`
- *
- * Examples:
- *
- * Basic:
- *
- * <form class='gl-show-field-errors'>
- * <input type='text' name='username' title='Username is required.'/>
- * </form>
- *
- * Ignore specific inputs (e.g. UsernameValidator):
- *
- * <form class='gl-show-field-errors'>
- * <div class="form-group>
- * <input type='text' class='gl-field-errors-ignore' pattern='[a-zA-Z0-9-_]+'/>
- * </div>
- * <div class="form-group">
- * <input type='text' name='username' title='Username is required.'/>
- * </div>
- * </form>
- *
- * Custom Error Anchor (allows error message to be injected after specified element):
- *
- * <form class='gl-show-field-errors'>
- * <div class="form-group gl-field-error-anchor">
- * <input type='text' name='username' title='Username is required.'/>
- * // Error message typically injected here
- * </div>
- * // Error message now injected here
- * </form>
- *
- * */
-
- /*
- * Regex Patterns in use:
- *
- * Only alphanumeric: : "[a-zA-Z0-9]+"
- * No special characters : "[a-zA-Z0-9-_]+",
- *
- * */
-
- const errorMessageClass = 'gl-field-error';
- const inputErrorClass = 'gl-field-error-outline';
- const errorAnchorSelector = '.gl-field-error-anchor';
- const ignoreInputSelector = '.gl-field-error-ignore';
-
- class GlFieldError {
- constructor({ input, formErrors }) {
- this.inputElement = $(input);
- this.inputDomElement = this.inputElement.get(0);
- this.form = formErrors;
- this.errorMessage = this.inputElement.attr('title') || 'This field is required.';
- this.fieldErrorElement = $(`<p class='${errorMessageClass} hide'>${this.errorMessage}</p>`);
-
- this.state = {
- valid: false,
- empty: true,
- };
-
- this.initFieldValidation();
- }
+/**
+ * This class overrides the browser's validation error bubbles, displaying custom
+ * error messages for invalid fields instead. To begin validating any form, add the
+ * class `gl-show-field-errors` to the form element, and ensure error messages are
+ * declared in each inputs' `title` attribute. If no title is declared for an invalid
+ * field the user attempts to submit, "This field is required." will be shown by default.
+ *
+ * Opt not to validate certain fields by adding the class `gl-field-error-ignore` to the input.
+ *
+ * Set a custom error anchor for error message to be injected after with the
+ * class `gl-field-error-anchor`
+ *
+ * Examples:
+ *
+ * Basic:
+ *
+ * <form class='gl-show-field-errors'>
+ * <input type='text' name='username' title='Username is required.'/>
+ * </form>
+ *
+ * Ignore specific inputs (e.g. UsernameValidator):
+ *
+ * <form class='gl-show-field-errors'>
+ * <div class="form-group>
+ * <input type='text' class='gl-field-errors-ignore' pattern='[a-zA-Z0-9-_]+'/>
+ * </div>
+ * <div class="form-group">
+ * <input type='text' name='username' title='Username is required.'/>
+ * </div>
+ * </form>
+ *
+ * Custom Error Anchor (allows error message to be injected after specified element):
+ *
+ * <form class='gl-show-field-errors'>
+ * <div class="form-group gl-field-error-anchor">
+ * <input type='text' name='username' title='Username is required.'/>
+ * // Error message typically injected here
+ * </div>
+ * // Error message now injected here
+ * </form>
+ *
+ */
+
+/**
+ * Regex Patterns in use:
+ *
+ * Only alphanumeric: : "[a-zA-Z0-9]+"
+ * No special characters : "[a-zA-Z0-9-_]+",
+ *
+ */
+
+const errorMessageClass = 'gl-field-error';
+const inputErrorClass = 'gl-field-error-outline';
+const errorAnchorSelector = '.gl-field-error-anchor';
+const ignoreInputSelector = '.gl-field-error-ignore';
+
+class GlFieldError {
+ constructor({ input, formErrors }) {
+ this.inputElement = $(input);
+ this.inputDomElement = this.inputElement.get(0);
+ this.form = formErrors;
+ this.errorMessage = this.inputElement.attr('title') || 'This field is required.';
+ this.fieldErrorElement = $(`<p class='${errorMessageClass} hide'>${this.errorMessage}</p>`);
+
+ this.state = {
+ valid: false,
+ empty: true,
+ };
+
+ this.initFieldValidation();
+ }
- initFieldValidation() {
- const customErrorAnchor = this.inputElement.parents(errorAnchorSelector);
- const errorAnchor = customErrorAnchor.length ? customErrorAnchor : this.inputElement;
+ initFieldValidation() {
+ const customErrorAnchor = this.inputElement.parents(errorAnchorSelector);
+ const errorAnchor = customErrorAnchor.length ? customErrorAnchor : this.inputElement;
- // hidden when injected into DOM
- errorAnchor.after(this.fieldErrorElement);
- this.inputElement.off('invalid').on('invalid', this.handleInvalidSubmit.bind(this));
- this.scopedSiblings = this.safelySelectSiblings();
- }
+ // hidden when injected into DOM
+ errorAnchor.after(this.fieldErrorElement);
+ this.inputElement.off('invalid').on('invalid', this.handleInvalidSubmit.bind(this));
+ this.scopedSiblings = this.safelySelectSiblings();
+ }
- safelySelectSiblings() {
- // Apply `ignoreSelector` in markup to siblings whose visibility should not be toggled
- const unignoredSiblings = this.inputElement.siblings(`p:not(${ignoreInputSelector})`);
- const parentContainer = this.inputElement.parent('.form-group');
+ safelySelectSiblings() {
+ // Apply `ignoreSelector` in markup to siblings whose visibility should not be toggled
+ const unignoredSiblings = this.inputElement.siblings(`p:not(${ignoreInputSelector})`);
+ const parentContainer = this.inputElement.parent('.form-group');
- // Only select siblings when they're scoped within a form-group with one input
- const safelyScoped = parentContainer.length && parentContainer.find('input').length === 1;
+ // Only select siblings when they're scoped within a form-group with one input
+ const safelyScoped = parentContainer.length && parentContainer.find('input').length === 1;
- return safelyScoped ? unignoredSiblings : this.fieldErrorElement;
- }
+ return safelyScoped ? unignoredSiblings : this.fieldErrorElement;
+ }
- renderValidity() {
- this.renderClear();
+ renderValidity() {
+ this.renderClear();
- if (this.state.valid) {
- this.renderValid();
- } else if (this.state.empty) {
- this.renderEmpty();
- } else if (!this.state.valid) {
- this.renderInvalid();
- }
+ if (this.state.valid) {
+ this.renderValid();
+ } else if (this.state.empty) {
+ this.renderEmpty();
+ } else if (!this.state.valid) {
+ this.renderInvalid();
}
+ }
- handleInvalidSubmit(event) {
- event.preventDefault();
- const currentValue = this.accessCurrentValue();
- this.state.valid = false;
- this.state.empty = currentValue === '';
-
- this.renderValidity();
- this.form.focusOnFirstInvalid.apply(this.form);
- // For UX, wait til after first invalid submission to check each keyup
- this.inputElement.off('keyup.fieldValidator')
- .on('keyup.fieldValidator', this.updateValidity.bind(this));
- }
+ handleInvalidSubmit(event) {
+ event.preventDefault();
+ const currentValue = this.accessCurrentValue();
+ this.state.valid = false;
+ this.state.empty = currentValue === '';
+
+ this.renderValidity();
+ this.form.focusOnFirstInvalid.apply(this.form);
+ // For UX, wait til after first invalid submission to check each keyup
+ this.inputElement.off('keyup.fieldValidator')
+ .on('keyup.fieldValidator', this.updateValidity.bind(this));
+ }
- /* Get or set current input value */
- accessCurrentValue(newVal) {
- return newVal ? this.inputElement.val(newVal) : this.inputElement.val();
- }
+ /* Get or set current input value */
+ accessCurrentValue(newVal) {
+ return newVal ? this.inputElement.val(newVal) : this.inputElement.val();
+ }
- getInputValidity() {
- return this.inputDomElement.validity.valid;
- }
+ getInputValidity() {
+ return this.inputDomElement.validity.valid;
+ }
- updateValidity() {
- const inputVal = this.accessCurrentValue();
- this.state.empty = !inputVal.length;
- this.state.valid = this.getInputValidity();
- this.renderValidity();
- }
+ updateValidity() {
+ const inputVal = this.accessCurrentValue();
+ this.state.empty = !inputVal.length;
+ this.state.valid = this.getInputValidity();
+ this.renderValidity();
+ }
- renderValid() {
- return this.renderClear();
- }
+ renderValid() {
+ return this.renderClear();
+ }
- renderEmpty() {
- return this.renderInvalid();
- }
+ renderEmpty() {
+ return this.renderInvalid();
+ }
- renderInvalid() {
- this.inputElement.addClass(inputErrorClass);
- this.scopedSiblings.hide();
- return this.fieldErrorElement.show();
- }
+ renderInvalid() {
+ this.inputElement.addClass(inputErrorClass);
+ this.scopedSiblings.hide();
+ return this.fieldErrorElement.show();
+ }
- renderClear() {
- const inputVal = this.accessCurrentValue();
- if (!inputVal.split(' ').length) {
- const trimmedInput = inputVal.trim();
- this.accessCurrentValue(trimmedInput);
- }
- this.inputElement.removeClass(inputErrorClass);
- this.scopedSiblings.hide();
- this.fieldErrorElement.hide();
+ renderClear() {
+ const inputVal = this.accessCurrentValue();
+ if (!inputVal.split(' ').length) {
+ const trimmedInput = inputVal.trim();
+ this.accessCurrentValue(trimmedInput);
}
+ this.inputElement.removeClass(inputErrorClass);
+ this.scopedSiblings.hide();
+ this.fieldErrorElement.hide();
}
+}
- global.GlFieldError = GlFieldError;
-})(window.gl || (window.gl = {}));
+window.gl = window.gl || {};
+window.gl.GlFieldError = GlFieldError;
diff --git a/app/assets/javascripts/gl_field_errors.js b/app/assets/javascripts/gl_field_errors.js
index e9add115429..636258ec555 100644
--- a/app/assets/javascripts/gl_field_errors.js
+++ b/app/assets/javascripts/gl_field_errors.js
@@ -2,47 +2,46 @@
require('./gl_field_error');
-((global) => {
- const customValidationFlag = 'gl-field-error-ignore';
-
- class GlFieldErrors {
- constructor(form) {
- this.form = $(form);
- this.state = {
- inputs: [],
- valid: false
- };
- this.initValidators();
- }
+const customValidationFlag = 'gl-field-error-ignore';
+
+class GlFieldErrors {
+ constructor(form) {
+ this.form = $(form);
+ this.state = {
+ inputs: [],
+ valid: false
+ };
+ this.initValidators();
+ }
- initValidators () {
- // register selectors here as needed
- const validateSelectors = [':text', ':password', '[type=email]']
- .map((selector) => `input${selector}`).join(',');
+ initValidators () {
+ // register selectors here as needed
+ const validateSelectors = [':text', ':password', '[type=email]']
+ .map((selector) => `input${selector}`).join(',');
- this.state.inputs = this.form.find(validateSelectors).toArray()
- .filter((input) => !input.classList.contains(customValidationFlag))
- .map((input) => new global.GlFieldError({ input, formErrors: this }));
+ this.state.inputs = this.form.find(validateSelectors).toArray()
+ .filter((input) => !input.classList.contains(customValidationFlag))
+ .map((input) => new window.gl.GlFieldError({ input, formErrors: this }));
- this.form.on('submit', this.catchInvalidFormSubmit);
- }
+ this.form.on('submit', this.catchInvalidFormSubmit);
+ }
- /* Neccessary to prevent intercept and override invalid form submit
- * because Safari & iOS quietly allow form submission when form is invalid
- * and prevents disabling of invalid submit button by application.js */
+ /* Neccessary to prevent intercept and override invalid form submit
+ * because Safari & iOS quietly allow form submission when form is invalid
+ * and prevents disabling of invalid submit button by application.js */
- catchInvalidFormSubmit (event) {
- if (!event.currentTarget.checkValidity()) {
- event.preventDefault();
- event.stopPropagation();
- }
+ catchInvalidFormSubmit (event) {
+ if (!event.currentTarget.checkValidity()) {
+ event.preventDefault();
+ event.stopPropagation();
}
+ }
- focusOnFirstInvalid () {
- const firstInvalid = this.state.inputs.filter((input) => !input.inputDomElement.validity.valid)[0];
- firstInvalid.inputElement.focus();
- }
+ focusOnFirstInvalid () {
+ const firstInvalid = this.state.inputs.filter((input) => !input.inputDomElement.validity.valid)[0];
+ firstInvalid.inputElement.focus();
}
+}
- global.GlFieldErrors = GlFieldErrors;
-})(window.gl || (window.gl = {}));
+window.gl = window.gl || {};
+window.gl.GlFieldErrors = GlFieldErrors;
diff --git a/app/assets/javascripts/gl_form.js b/app/assets/javascripts/gl_form.js
index 0b446ff364a..e7c98e16581 100644
--- a/app/assets/javascripts/gl_form.js
+++ b/app/assets/javascripts/gl_form.js
@@ -3,90 +3,88 @@
/* global DropzoneInput */
/* global autosize */
-(() => {
- const global = window.gl || (window.gl = {});
+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);
- }
+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.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.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));
+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));
+ this.textarea.off('mouseup.autosize')
+ .on('mouseup.autosize', this.destroyAutosize.bind(this));
- setTimeout(() => {
- autosize(this.textarea);
- this.textarea.css('resize', 'vertical');
- }, 0);
- };
+ setTimeout(() => {
+ autosize(this.textarea);
+ this.textarea.css('resize', 'vertical');
+ }, 0);
+};
- GLForm.prototype.setHeightData = function () {
- this.textarea.data('height', this.textarea.outerHeight());
- };
+GLForm.prototype.setHeightData = function () {
+ this.textarea.data('height', this.textarea.outerHeight());
+};
- GLForm.prototype.destroyAutosize = function () {
- const outerHeight = this.textarea.outerHeight();
+GLForm.prototype.destroyAutosize = function () {
+ const outerHeight = this.textarea.outerHeight();
- if (this.textarea.data('height') === outerHeight) return;
+ if (this.textarea.data('height') === outerHeight) return;
- autosize.destroy(this.textarea);
+ autosize.destroy(this.textarea);
- this.textarea.data('height', outerHeight);
- this.textarea.outerHeight(outerHeight);
- this.textarea.css('max-height', window.outerHeight);
- };
+ 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.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');
- });
- };
+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;
-})();
+window.gl.GLForm = GLForm;
diff --git a/app/assets/javascripts/group_avatar.js b/app/assets/javascripts/group_avatar.js
index c5cb273c5b2..f03b47b1c1d 100644
--- a/app/assets/javascripts/group_avatar.js
+++ b/app/assets/javascripts/group_avatar.js
@@ -1,20 +1,19 @@
/* 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').on("click", function() {
- var form;
- form = $(this).closest("form");
- return form.find(".js-group-avatar-input").click();
- });
- $('.js-group-avatar-input').on("change", function() {
- var filename, form;
- form = $(this).closest("form");
- filename = $(this).val().replace(/^.*[\\\/]/, '');
- return form.find(".js-avatar-filename").text(filename);
- });
- }
- return GroupAvatar;
- })();
-}).call(window);
+window.GroupAvatar = (function() {
+ function GroupAvatar() {
+ $('.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').on("change", function() {
+ var filename, form;
+ form = $(this).closest("form");
+ filename = $(this).val().replace(/^.*[\\\/]/, '');
+ return form.find(".js-avatar-filename").text(filename);
+ });
+ }
+
+ return GroupAvatar;
+})();
diff --git a/app/assets/javascripts/group_label_subscription.js b/app/assets/javascripts/group_label_subscription.js
index 15e695e81cf..7dc9ce898e8 100644
--- a/app/assets/javascripts/group_label_subscription.js
+++ b/app/assets/javascripts/group_label_subscription.js
@@ -1,53 +1,52 @@
/* eslint-disable func-names, object-shorthand, comma-dangle, wrap-iife, space-before-function-paren, no-param-reassign, max-len */
-(function(global) {
- class GroupLabelSubscription {
- constructor(container) {
- const $container = $(container);
- this.$dropdown = $container.find('.dropdown');
- this.$subscribeButtons = $container.find('.js-subscribe-button');
- this.$unsubscribeButtons = $container.find('.js-unsubscribe-button');
-
- this.$subscribeButtons.on('click', this.subscribe.bind(this));
- this.$unsubscribeButtons.on('click', this.unsubscribe.bind(this));
- }
-
- unsubscribe(event) {
- event.preventDefault();
-
- const url = this.$unsubscribeButtons.attr('data-url');
-
- $.ajax({
- type: 'POST',
- url: url
- }).done(() => {
- this.toggleSubscriptionButtons();
- this.$unsubscribeButtons.removeAttr('data-url');
- });
- }
-
- subscribe(event) {
- event.preventDefault();
-
- const $btn = $(event.currentTarget);
- const url = $btn.attr('data-url');
-
- this.$unsubscribeButtons.attr('data-url', url);
-
- $.ajax({
- type: 'POST',
- url: url
- }).done(() => {
- this.toggleSubscriptionButtons();
- });
- }
-
- toggleSubscriptionButtons() {
- this.$dropdown.toggleClass('hidden');
- this.$subscribeButtons.toggleClass('hidden');
- this.$unsubscribeButtons.toggleClass('hidden');
- }
+class GroupLabelSubscription {
+ constructor(container) {
+ const $container = $(container);
+ this.$dropdown = $container.find('.dropdown');
+ this.$subscribeButtons = $container.find('.js-subscribe-button');
+ this.$unsubscribeButtons = $container.find('.js-unsubscribe-button');
+
+ this.$subscribeButtons.on('click', this.subscribe.bind(this));
+ this.$unsubscribeButtons.on('click', this.unsubscribe.bind(this));
}
- global.GroupLabelSubscription = GroupLabelSubscription;
-})(window.gl || (window.gl = {}));
+ unsubscribe(event) {
+ event.preventDefault();
+
+ const url = this.$unsubscribeButtons.attr('data-url');
+
+ $.ajax({
+ type: 'POST',
+ url: url
+ }).done(() => {
+ this.toggleSubscriptionButtons();
+ this.$unsubscribeButtons.removeAttr('data-url');
+ });
+ }
+
+ subscribe(event) {
+ event.preventDefault();
+
+ const $btn = $(event.currentTarget);
+ const url = $btn.attr('data-url');
+
+ this.$unsubscribeButtons.attr('data-url', url);
+
+ $.ajax({
+ type: 'POST',
+ url: url
+ }).done(() => {
+ this.toggleSubscriptionButtons();
+ });
+ }
+
+ toggleSubscriptionButtons() {
+ this.$dropdown.toggleClass('hidden');
+ this.$subscribeButtons.toggleClass('hidden');
+ this.$unsubscribeButtons.toggleClass('hidden');
+ }
+}
+
+window.gl = window.gl || {};
+window.gl.GroupLabelSubscription = GroupLabelSubscription;
diff --git a/app/assets/javascripts/groups_select.js b/app/assets/javascripts/groups_select.js
index 6b937e7fa0f..e5dfa30edab 100644
--- a/app/assets/javascripts/groups_select.js
+++ b/app/assets/javascripts/groups_select.js
@@ -1,71 +1,69 @@
/* 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() {
- var slice = [].slice;
+var slice = [].slice;
- this.GroupsSelect = (function() {
- function GroupsSelect() {
- $('.ajax-groups-select').each((function(_this) {
- return function(i, select) {
- var all_available, skip_groups;
- all_available = $(select).data('all-available');
- skip_groups = $(select).data('skip-groups') || [];
- return $(select).select2({
- placeholder: "Search for a group",
- multiple: $(select).hasClass('multiselect'),
- minimumInputLength: 0,
- query: function(query) {
- var options = { all_available: all_available, skip_groups: skip_groups };
- return Api.groups(query.term, options, function(groups) {
- var data;
- data = {
- results: groups
- };
- return query.callback(data);
- });
- },
- initSelection: function(element, callback) {
- var id;
- id = $(element).val();
- if (id !== "") {
- return Api.group(id, callback);
- }
- },
- formatResult: function() {
- var args;
- args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
- return _this.formatResult.apply(_this, args);
- },
- formatSelection: function() {
- var args;
- args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
- return _this.formatSelection.apply(_this, args);
- },
- dropdownCssClass: "ajax-groups-dropdown",
- // we do not want to escape markup since we are displaying html in results
- escapeMarkup: function(m) {
- return m;
+window.GroupsSelect = (function() {
+ function GroupsSelect() {
+ $('.ajax-groups-select').each((function(_this) {
+ return function(i, select) {
+ var all_available, skip_groups;
+ all_available = $(select).data('all-available');
+ skip_groups = $(select).data('skip-groups') || [];
+ return $(select).select2({
+ placeholder: "Search for a group",
+ multiple: $(select).hasClass('multiselect'),
+ minimumInputLength: 0,
+ query: function(query) {
+ var options = { all_available: all_available, skip_groups: skip_groups };
+ return Api.groups(query.term, options, function(groups) {
+ var data;
+ data = {
+ results: groups
+ };
+ return query.callback(data);
+ });
+ },
+ initSelection: function(element, callback) {
+ var id;
+ id = $(element).val();
+ if (id !== "") {
+ return Api.group(id, callback);
}
- });
- };
- })(this));
- }
+ },
+ formatResult: function() {
+ var args;
+ args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
+ return _this.formatResult.apply(_this, args);
+ },
+ formatSelection: function() {
+ var args;
+ args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
+ return _this.formatSelection.apply(_this, args);
+ },
+ dropdownCssClass: "ajax-groups-dropdown",
+ // we do not want to escape markup since we are displaying html in results
+ escapeMarkup: function(m) {
+ return m;
+ }
+ });
+ };
+ })(this));
+ }
- GroupsSelect.prototype.formatResult = function(group) {
- var avatar;
- if (group.avatar_url) {
- avatar = group.avatar_url;
- } else {
- avatar = gon.default_avatar_url;
- }
- return "<div class='group-result'> <div class='group-name'>" + group.full_name + "</div> <div class='group-path'>" + group.full_path + "</div> </div>";
- };
+ GroupsSelect.prototype.formatResult = function(group) {
+ var avatar;
+ if (group.avatar_url) {
+ avatar = group.avatar_url;
+ } else {
+ avatar = gon.default_avatar_url;
+ }
+ 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.full_name;
- };
+ GroupsSelect.prototype.formatSelection = function(group) {
+ return group.full_name;
+ };
- return GroupsSelect;
- })();
-}).call(window);
+ return GroupsSelect;
+})();
diff --git a/app/assets/javascripts/header.js b/app/assets/javascripts/header.js
index a853c3aeb1f..34f44dad7a5 100644
--- a/app/assets/javascripts/header.js
+++ b/app/assets/javascripts/header.js
@@ -1,8 +1,7 @@
-/* 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.highCountTrim(count));
- $todoPendingCount.toggleClass('hidden', count === 0);
- });
-})();
+/* eslint-disable func-names, space-before-function-paren, prefer-arrow-callback, no-var */
+
+$(document).on('todo:toggle', function(e, count) {
+ var $todoPendingCount = $('.todos-pending-count');
+ $todoPendingCount.text(gl.text.highCountTrim(count));
+ $todoPendingCount.toggleClass('hidden', count === 0);
+});
diff --git a/app/assets/javascripts/issue.js b/app/assets/javascripts/issue.js
index 52457f70d90..ef4029a8623 100644
--- a/app/assets/javascripts/issue.js
+++ b/app/assets/javascripts/issue.js
@@ -5,131 +5,125 @@ require('./flash');
require('vendor/jquery.waitforimages');
require('./task_list');
-(function() {
- var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; };
-
- this.Issue = (function() {
- function Issue() {
- this.submitNoteForm = bind(this.submitNoteForm, this);
- if ($('a.btn-close').length) {
- this.taskList = new gl.TaskList({
- dataType: 'issue',
- fieldName: 'description',
- selector: '.detail-page-description',
- onSuccess: (result) => {
- document.querySelector('#task_status').innerText = result.task_status;
- document.querySelector('#task_status_short').innerText = result.task_status_short;
- }
- });
- this.initIssueBtnEventListeners();
- }
- this.initMergeRequests();
- this.initRelatedBranches();
- this.initCanCreateBranch();
+class Issue {
+ constructor() {
+ if ($('a.btn-close').length) {
+ this.taskList = new gl.TaskList({
+ dataType: 'issue',
+ fieldName: 'description',
+ selector: '.detail-page-description',
+ onSuccess: (result) => {
+ document.querySelector('#task_status').innerText = result.task_status;
+ document.querySelector('#task_status_short').innerText = result.task_status_short;
+ }
+ });
+ Issue.initIssueBtnEventListeners();
}
+ Issue.initMergeRequests();
+ Issue.initRelatedBranches();
+ Issue.initCanCreateBranch();
+ }
- Issue.prototype.initIssueBtnEventListeners = function() {
- var _this, issueFailMessage;
- _this = this;
- issueFailMessage = 'Unable to update this issue at this time.';
- return $('a.btn-close, a.btn-reopen').on('click', function(e) {
- var $this, isClose, shouldSubmit, url;
- e.preventDefault();
- e.stopImmediatePropagation();
- $this = $(this);
- isClose = $this.hasClass('btn-close');
- shouldSubmit = $this.hasClass('btn-comment');
- if (shouldSubmit) {
- _this.submitNoteForm($this.closest('form'));
- }
- $this.prop('disabled', true);
- url = $this.attr('href');
- return $.ajax({
- type: 'PUT',
- url: url,
- error: function(jqXHR, textStatus, errorThrown) {
- var issueStatus;
- issueStatus = isClose ? 'close' : 'open';
- return new Flash(issueFailMessage, 'alert');
- },
- success: function(data, textStatus, jqXHR) {
- if ('id' in data) {
- $(document).trigger('issuable:change');
- const currentTotal = Number($('.issue_counter').text());
- if (isClose) {
- $('a.btn-close').addClass('hidden');
- $('a.btn-reopen').removeClass('hidden');
- $('div.status-box-closed').removeClass('hidden');
- $('div.status-box-open').addClass('hidden');
- $('.issue_counter').text(currentTotal - 1);
- } else {
- $('a.btn-reopen').addClass('hidden');
- $('a.btn-close').removeClass('hidden');
- $('div.status-box-closed').addClass('hidden');
- $('div.status-box-open').removeClass('hidden');
- $('.issue_counter').text(currentTotal + 1);
- }
+ static initIssueBtnEventListeners() {
+ var issueFailMessage;
+ issueFailMessage = 'Unable to update this issue at this time.';
+ return $('a.btn-close, a.btn-reopen').on('click', function(e) {
+ var $this, isClose, shouldSubmit, url;
+ e.preventDefault();
+ e.stopImmediatePropagation();
+ $this = $(this);
+ isClose = $this.hasClass('btn-close');
+ shouldSubmit = $this.hasClass('btn-comment');
+ if (shouldSubmit) {
+ Issue.submitNoteForm($this.closest('form'));
+ }
+ $this.prop('disabled', true);
+ url = $this.attr('href');
+ return $.ajax({
+ type: 'PUT',
+ url: url,
+ error: function(jqXHR, textStatus, errorThrown) {
+ var issueStatus;
+ issueStatus = isClose ? 'close' : 'open';
+ return new Flash(issueFailMessage, 'alert');
+ },
+ success: function(data, textStatus, jqXHR) {
+ if ('id' in data) {
+ $(document).trigger('issuable:change');
+ const currentTotal = Number($('.issue_counter').text());
+ if (isClose) {
+ $('a.btn-close').addClass('hidden');
+ $('a.btn-reopen').removeClass('hidden');
+ $('div.status-box-closed').removeClass('hidden');
+ $('div.status-box-open').addClass('hidden');
+ $('.issue_counter').text(currentTotal - 1);
} else {
- new Flash(issueFailMessage, 'alert');
+ $('a.btn-reopen').addClass('hidden');
+ $('a.btn-close').removeClass('hidden');
+ $('div.status-box-closed').addClass('hidden');
+ $('div.status-box-open').removeClass('hidden');
+ $('.issue_counter').text(currentTotal + 1);
}
- return $this.prop('disabled', false);
+ } else {
+ new Flash(issueFailMessage, 'alert');
}
- });
+ return $this.prop('disabled', false);
+ }
});
- };
+ });
+ }
- Issue.prototype.submitNoteForm = function(form) {
- var noteText;
- noteText = form.find("textarea.js-note-text").val();
- if (noteText.trim().length > 0) {
- return form.submit();
- }
- };
+ static submitNoteForm(form) {
+ var noteText;
+ noteText = form.find("textarea.js-note-text").val();
+ if (noteText.trim().length > 0) {
+ return form.submit();
+ }
+ }
- Issue.prototype.initMergeRequests = function() {
- var $container;
- $container = $('#merge-requests');
- return $.getJSON($container.data('url')).error(function() {
- return new Flash('Failed to load referenced merge requests', 'alert');
- }).success(function(data) {
- if ('html' in data) {
- return $container.html(data.html);
- }
- });
- };
+ static initMergeRequests() {
+ var $container;
+ $container = $('#merge-requests');
+ return $.getJSON($container.data('url')).error(function() {
+ return new Flash('Failed to load referenced merge requests', 'alert');
+ }).success(function(data) {
+ if ('html' in data) {
+ return $container.html(data.html);
+ }
+ });
+ }
- Issue.prototype.initRelatedBranches = function() {
- var $container;
- $container = $('#related-branches');
- return $.getJSON($container.data('url')).error(function() {
- return new Flash('Failed to load related branches', 'alert');
- }).success(function(data) {
- if ('html' in data) {
- return $container.html(data.html);
- }
- });
- };
+ static initRelatedBranches() {
+ var $container;
+ $container = $('#related-branches');
+ return $.getJSON($container.data('url')).error(function() {
+ return new Flash('Failed to load related branches', 'alert');
+ }).success(function(data) {
+ if ('html' in data) {
+ return $container.html(data.html);
+ }
+ });
+ }
- Issue.prototype.initCanCreateBranch = function() {
- var $container;
- $container = $('#new-branch');
- // If the user doesn't have the required permissions the container isn't
- // rendered at all.
- if ($container.length === 0) {
- return;
+ static initCanCreateBranch() {
+ var $container;
+ $container = $('#new-branch');
+ // If the user doesn't have the required permissions the container isn't
+ // rendered at all.
+ if ($container.length === 0) {
+ return;
+ }
+ return $.getJSON($container.data('path')).error(function() {
+ $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('.available').show();
+ } else {
+ return $container.find('.unavailable').show();
}
- return $.getJSON($container.data('path')).error(function() {
- $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('.available').show();
- } else {
- return $container.find('.unavailable').show();
- }
- });
- };
+ });
+ }
+}
- return Issue;
- })();
-}).call(window);
+export default Issue;
diff --git a/app/assets/javascripts/lib/utils/url_utility.js b/app/assets/javascripts/lib/utils/url_utility.js
index 1bc81d2e4a4..09c4261b318 100644
--- a/app/assets/javascripts/lib/utils/url_utility.js
+++ b/app/assets/javascripts/lib/utils/url_utility.js
@@ -66,6 +66,13 @@
return results;
})()).join('&');
};
+ w.gl.utils.removeParams = (params) => {
+ const url = new URL(window.location.href);
+ params.forEach((param) => {
+ url.search = w.gl.utils.removeParamQueryString(url.search, param);
+ });
+ return url.href;
+ };
w.gl.utils.getLocationHash = function(url) {
var hashIndex;
if (typeof url === 'undefined') {
diff --git a/app/assets/javascripts/line_highlighter.js b/app/assets/javascripts/line_highlighter.js
index 966fcd8ec47..1821ca18053 100644
--- a/app/assets/javascripts/line_highlighter.js
+++ b/app/assets/javascripts/line_highlighter.js
@@ -67,17 +67,7 @@ require('vendor/jquery.scrollTo');
}
LineHighlighter.prototype.bindEvents = function() {
- $('#blob-content-holder').on('mousedown', 'a[data-line-number]', this.clickHandler);
- // While it may seem odd to bind to the mousedown event and then throw away
- // the click event, there is a method to our madness.
- //
- // 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.
- $('#blob-content-holder').on('click', 'a[data-line-number]', function(event) {
- event.preventDefault();
- event.stopPropagation();
- });
+ $('#blob-content-holder').on('click', 'a[data-line-number]', this.clickHandler);
};
LineHighlighter.prototype.clickHandler = function(event) {
diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js
index ae4dd64424c..81d5748191d 100644
--- a/app/assets/javascripts/main.js
+++ b/app/assets/javascripts/main.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, no-multi-spaces, import/newline-after-import */
+/* 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, import/first */
/* global bp */
/* global Cookies */
/* global Flash */
@@ -13,19 +13,12 @@ import Dropzone from 'dropzone';
import Sortable from 'vendor/Sortable';
// libraries with import side-effects
-require('mousetrap');
-require('mousetrap/plugins/pause/mousetrap-pause');
-require('vendor/fuzzaldrin-plus');
-require('es6-promise').polyfill();
+import 'mousetrap';
+import 'mousetrap/plugins/pause/mousetrap-pause';
+import 'vendor/fuzzaldrin-plus';
// extensions
-require('./extensions/string');
-require('./extensions/array');
-require('./extensions/custom_event');
-require('./extensions/element');
-require('./extensions/jquery');
-require('./extensions/object');
-require('es6-promise').polyfill();
+import './extensions/array';
// expose common libraries as globals (TODO: remove these)
window.jQuery = jQuery;
@@ -37,358 +30,355 @@ window.Dropzone = Dropzone;
window.Sortable = Sortable;
// shortcuts
-require('./shortcuts');
-require('./shortcuts_navigation');
-require('./shortcuts_dashboard_navigation');
-require('./shortcuts_issuable');
-require('./shortcuts_network');
+import './shortcuts';
+import './shortcuts_blob';
+import './shortcuts_dashboard_navigation';
+import './shortcuts_navigation';
+import './shortcuts_find_file';
+import './shortcuts_issuable';
+import './shortcuts_network';
// behaviors
-require('./behaviors/autosize');
-require('./behaviors/details_behavior');
-require('./behaviors/quick_submit');
-require('./behaviors/requires_input');
-require('./behaviors/toggler_behavior');
-require('./behaviors/bind_in_out');
+import './behaviors/autosize';
+import './behaviors/details_behavior';
+import './behaviors/quick_submit';
+import './behaviors/requires_input';
+import './behaviors/toggler_behavior';
+import './behaviors/bind_in_out';
+import { installGlEmojiElement } from './behaviors/gl_emoji';
+installGlEmojiElement();
// blob
-require('./blob/blob_ci_yaml');
-require('./blob/blob_dockerfile_selector');
-require('./blob/blob_dockerfile_selectors');
-require('./blob/blob_file_dropzone');
-require('./blob/blob_gitignore_selector');
-require('./blob/blob_gitignore_selectors');
-require('./blob/blob_license_selector');
-require('./blob/blob_license_selectors');
-require('./blob/template_selector');
+import './blob/blob_ci_yaml';
+import './blob/blob_dockerfile_selector';
+import './blob/blob_dockerfile_selectors';
+import './blob/blob_file_dropzone';
+import './blob/blob_gitignore_selector';
+import './blob/blob_gitignore_selectors';
+import './blob/blob_license_selector';
+import './blob/blob_license_selectors';
+import './blob/template_selector';
+import './blob/create_branch_dropdown';
+import './blob/target_branch_dropdown';
// templates
-require('./templates/issuable_template_selector');
-require('./templates/issuable_template_selectors');
+import './templates/issuable_template_selector';
+import './templates/issuable_template_selectors';
// commit
-require('./commit/file.js');
-require('./commit/image_file.js');
+import './commit/file';
+import './commit/image_file';
// lib/utils
-require('./lib/utils/animate');
-require('./lib/utils/bootstrap_linked_tabs');
-require('./lib/utils/common_utils');
-require('./lib/utils/datetime_utility');
-require('./lib/utils/notify');
-require('./lib/utils/pretty_time');
-require('./lib/utils/text_utility');
-require('./lib/utils/type_utility');
-require('./lib/utils/url_utility');
+import './lib/utils/animate';
+import './lib/utils/bootstrap_linked_tabs';
+import './lib/utils/common_utils';
+import './lib/utils/datetime_utility';
+import './lib/utils/notify';
+import './lib/utils/pretty_time';
+import './lib/utils/text_utility';
+import './lib/utils/type_utility';
+import './lib/utils/url_utility';
// u2f
-require('./u2f/authenticate');
-require('./u2f/error');
-require('./u2f/register');
-require('./u2f/util');
+import './u2f/authenticate';
+import './u2f/error';
+import './u2f/register';
+import './u2f/util';
// droplab
-require('./droplab/droplab');
-require('./droplab/droplab_ajax');
-require('./droplab/droplab_ajax_filter');
-require('./droplab/droplab_filter');
+import './droplab/droplab';
+import './droplab/droplab_ajax';
+import './droplab/droplab_ajax_filter';
+import './droplab/droplab_filter';
// everything else
-require('./abuse_reports');
-require('./activities');
-require('./admin');
-require('./ajax_loading_spinner');
-require('./api');
-require('./aside');
-require('./autosave');
-const AwardsHandler = require('./awards_handler');
-require('./breakpoints');
-require('./broadcast_message');
-require('./build');
-require('./build_artifacts');
-require('./build_variables');
-require('./ci_lint_editor');
-require('./commit');
-require('./commits');
-require('./compare');
-require('./compare_autocomplete');
-require('./confirm_danger_modal');
-require('./copy_as_gfm');
-require('./copy_to_clipboard');
-require('./create_label');
-require('./diff');
-require('./dispatcher');
-require('./dropzone_input');
-require('./due_date_select');
-require('./files_comment_button');
-require('./flash');
-require('./gfm_auto_complete');
-require('./gl_dropdown');
-require('./gl_field_error');
-require('./gl_field_errors');
-require('./gl_form');
-require('./group_avatar');
-require('./group_label_subscription');
-require('./groups_select');
-require('./header');
-require('./importer_status');
-require('./issuable');
-require('./issuable_context');
-require('./issuable_form');
-require('./issue');
-require('./issue_status_select');
-require('./issues_bulk_assignment');
-require('./label_manager');
-require('./labels');
-require('./labels_select');
-require('./layout_nav');
-require('./line_highlighter');
-require('./logo');
-require('./member_expiration_date');
-require('./members');
-require('./merge_request');
-require('./merge_request_tabs');
-require('./merge_request_widget');
-require('./merged_buttons');
-require('./milestone');
-require('./milestone_select');
-require('./mini_pipeline_graph_dropdown');
-require('./namespace_select');
-require('./new_branch_form');
-require('./new_commit_form');
-require('./notes');
-require('./notifications_dropdown');
-require('./notifications_form');
-require('./pager');
-require('./pipelines');
-require('./preview_markdown');
-require('./project');
-require('./project_avatar');
-require('./project_find_file');
-require('./project_fork');
-require('./project_import');
-require('./project_label_subscription');
-require('./project_new');
-require('./project_select');
-require('./project_show');
-require('./project_variables');
-require('./projects_list');
-require('./render_gfm');
-require('./render_math');
-require('./right_sidebar');
-require('./search');
-require('./search_autocomplete');
-require('./shortcuts');
-require('./shortcuts_blob');
-require('./shortcuts_dashboard_navigation');
-require('./shortcuts_find_file');
-require('./shortcuts_issuable');
-require('./shortcuts_navigation');
-require('./shortcuts_network');
-require('./signin_tabs_memoizer');
-require('./single_file_diff');
-require('./smart_interval');
-require('./snippets_list');
-require('./star');
-require('./subbable_resource');
-require('./subscription');
-require('./subscription_select');
-require('./syntax_highlight');
-require('./task_list');
-require('./todos');
-require('./tree');
-require('./user');
-require('./user_tabs');
-require('./username_validator');
-require('./users_select');
-require('./version_check_image');
-require('./visibility_select');
-require('./wikis');
-require('./zen_mode');
+import './abuse_reports';
+import './activities';
+import './admin';
+import './ajax_loading_spinner';
+import './api';
+import './aside';
+import './autosave';
+import AwardsHandler from './awards_handler';
+import './breakpoints';
+import './broadcast_message';
+import './build';
+import './build_artifacts';
+import './build_variables';
+import './ci_lint_editor';
+import './commit';
+import './commits';
+import './compare';
+import './compare_autocomplete';
+import './confirm_danger_modal';
+import './copy_as_gfm';
+import './copy_to_clipboard';
+import './create_label';
+import './diff';
+import './dispatcher';
+import './dropzone_input';
+import './due_date_select';
+import './files_comment_button';
+import './flash';
+import './gfm_auto_complete';
+import './gl_dropdown';
+import './gl_field_error';
+import './gl_field_errors';
+import './gl_form';
+import './group_avatar';
+import './group_label_subscription';
+import './groups_select';
+import './header';
+import './importer_status';
+import './issuable';
+import './issuable_context';
+import './issuable_form';
+import './issue';
+import './issue_status_select';
+import './issues_bulk_assignment';
+import './label_manager';
+import './labels';
+import './labels_select';
+import './layout_nav';
+import './line_highlighter';
+import './logo';
+import './member_expiration_date';
+import './members';
+import './merge_request';
+import './merge_request_tabs';
+import './merge_request_widget';
+import './merged_buttons';
+import './milestone';
+import './milestone_select';
+import './mini_pipeline_graph_dropdown';
+import './namespace_select';
+import './new_branch_form';
+import './new_commit_form';
+import './notes';
+import './notifications_dropdown';
+import './notifications_form';
+import './pager';
+import './pipelines';
+import './preview_markdown';
+import './project';
+import './project_avatar';
+import './project_find_file';
+import './project_fork';
+import './project_import';
+import './project_label_subscription';
+import './project_new';
+import './project_select';
+import './project_show';
+import './project_variables';
+import './projects_list';
+import './render_gfm';
+import './render_math';
+import './right_sidebar';
+import './search';
+import './search_autocomplete';
+import './signin_tabs_memoizer';
+import './single_file_diff';
+import './smart_interval';
+import './snippets_list';
+import './star';
+import './subbable_resource';
+import './subscription';
+import './subscription_select';
+import './syntax_highlight';
+import './task_list';
+import './todos';
+import './tree';
+import './user';
+import './user_tabs';
+import './username_validator';
+import './users_select';
+import './version_check_image';
+import './visibility_select';
+import './wikis';
+import './zen_mode';
-(function () {
- document.addEventListener('beforeunload', function () {
- // Unbind scroll events
- $(document).off('scroll');
- // Close any open tooltips
- $('.has-tooltip, [data-toggle="tooltip"]').tooltip('destroy');
- });
-
- window.addEventListener('hashchange', gl.utils.handleLocationHash);
- window.addEventListener('load', function onLoad() {
- window.removeEventListener('load', onLoad, false);
- gl.utils.handleLocationHash();
- }, false);
+document.addEventListener('beforeunload', function () {
+ // Unbind scroll events
+ $(document).off('scroll');
+ // Close any open tooltips
+ $('.has-tooltip, [data-toggle="tooltip"]').tooltip('destroy');
+});
- $(function () {
- var $body = $('body');
- var $document = $(document);
- var $window = $(window);
- var $sidebarGutterToggle = $('.js-sidebar-toggle');
- var $flash = $('.flash-container');
- var bootstrapBreakpoint = bp.getBreakpointSize();
- var fitSidebarForSize;
+window.addEventListener('hashchange', gl.utils.handleLocationHash);
+window.addEventListener('load', function onLoad() {
+ window.removeEventListener('load', onLoad, false);
+ gl.utils.handleLocationHash();
+}, false);
- // Set the default path for all cookies to GitLab's root directory
- Cookies.defaults.path = gon.relative_url_root || '/';
+$(function () {
+ var $body = $('body');
+ var $document = $(document);
+ var $window = $(window);
+ var $sidebarGutterToggle = $('.js-sidebar-toggle');
+ var $flash = $('.flash-container');
+ var bootstrapBreakpoint = bp.getBreakpointSize();
+ var fitSidebarForSize;
- // `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);
- }
- });
+ // Set the default path for all cookies to GitLab's root directory
+ Cookies.defaults.path = gon.relative_url_root || '/';
- // prevent default action for disabled buttons
- $('.btn').click(function(e) {
- if ($(this).hasClass('disabled')) {
- e.preventDefault();
- e.stopImmediatePropagation();
- return false;
- }
- });
+ // `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);
+ }
+ });
- $('.js-select-on-focus').on('focusin', function () {
- return $(this).select().one('mouseup', function (e) {
- return e.preventDefault();
- });
- // Click a .js-select-on-focus field, select the contents
- // Prevent a mouseup event from deselecting the input
- });
- $('.remove-row').bind('ajax:success', function () {
- $(this).tooltip('destroy')
- .closest('li')
- .fadeOut();
- });
- $('.js-remove-tr').bind('ajax:before', function () {
- return $(this).hide();
- });
- $('.js-remove-tr').bind('ajax:success', function () {
- return $(this).closest('tr').fadeOut();
- });
- $('select.select2').select2({
- width: 'resolve',
- // Initialize select2 selects
- dropdownAutoWidth: true
- });
- $('.js-select2').bind('select2-close', function () {
- return setTimeout((function () {
- $('.select2-container-active').removeClass('select2-container-active');
- return $(':focus').blur();
- }), 1);
- // Close select2 on escape
- });
- // Initialize tooltips
- $.fn.tooltip.Constructor.DEFAULTS.trigger = 'hover';
- $body.tooltip({
- selector: '.has-tooltip, [data-toggle="tooltip"]',
- placement: function (tip, el) {
- return $(el).data('placement') || 'bottom';
- }
- });
- $('.trigger-submit').on('change', function () {
- return $(this).parents('form').submit();
- // Form submitter
- });
- gl.utils.localTimeAgo($('abbr.timeago, .js-timeago'), true);
- // Flash
- if ($flash.length > 0) {
- $flash.click(function () {
- return $(this).fadeOut();
- });
- $flash.show();
+ // prevent default action for disabled buttons
+ $('.btn').click(function(e) {
+ if ($(this).hasClass('disabled')) {
+ e.preventDefault();
+ e.stopImmediatePropagation();
+ return false;
}
- // Disable form buttons while a form is submitting
- $body.on('ajax:complete, ajax:beforeSend, submit', 'form', function (e) {
- var buttons;
- buttons = $('[type="submit"]', this);
- switch (e.type) {
- case 'ajax:beforeSend':
- case 'submit':
- return buttons.disable();
- default:
- return buttons.enable();
- }
- });
- $(document).ajaxError(function (e, xhrObj) {
- var ref = xhrObj.status;
- if (xhrObj.status === 401) {
- return new Flash('You need to be logged in.', 'alert');
- } else if (ref === 404 || ref === 500) {
- return new Flash('Something went wrong on our end.', 'alert');
- }
- });
- $('.account-box').hover(function () {
- // Show/Hide the profile menu when hovering the account box
- return $(this).toggleClass('hover');
- });
- $document.on('click', '.diff-content .js-show-suppressed-diff', function () {
- var $container;
- $container = $(this).parent();
- $container.next('table').show();
- return $container.remove();
- // Commit show suppressed diff
- });
- $('.navbar-toggle').on('click', function () {
- $('.header-content .title').toggle();
- $('.header-content .header-logo').toggle();
- $('.header-content .navbar-collapse').toggle();
- return $('.navbar-toggle').toggleClass('active');
- });
- // Show/hide comments on diff
- $body.on('click', '.js-toggle-diff-comments', function (e) {
- var $this = $(this);
- var notesHolders = $this.closest('.diff-file').find('.notes_holder');
- $this.toggleClass('active');
- if ($this.hasClass('active')) {
- notesHolders.show().find('.hide').show();
- } else {
- notesHolders.hide();
- }
- $this.trigger('blur');
+ });
+
+ $('.js-select-on-focus').on('focusin', function () {
+ return $(this).select().one('mouseup', function (e) {
return e.preventDefault();
});
- $document.off('click', '.js-confirm-danger');
- $document.on('click', '.js-confirm-danger', function (e) {
- var btn = $(e.target);
- var form = btn.closest('form');
- var text = btn.data('confirm-danger-message');
- e.preventDefault();
- return new ConfirmDangerModal(form, text);
- });
- $('input[type="search"]').each(function () {
- var $this = $(this);
- $this.attr('value', $this.val());
- });
- $document.off('keyup', 'input[type="search"]').on('keyup', 'input[type="search"]', function () {
- var $this;
- $this = $(this);
- return $this.attr('value', $this.val());
- });
- $document.off('breakpoint:change').on('breakpoint:change', function (e, breakpoint) {
- var $gutterIcon;
- if (breakpoint === 'sm' || breakpoint === 'xs') {
- $gutterIcon = $sidebarGutterToggle.find('i');
- if ($gutterIcon.hasClass('fa-angle-double-right')) {
- return $sidebarGutterToggle.trigger('click');
- }
- }
+ // Click a .js-select-on-focus field, select the contents
+ // Prevent a mouseup event from deselecting the input
+ });
+ $('.remove-row').bind('ajax:success', function () {
+ $(this).tooltip('destroy')
+ .closest('li')
+ .fadeOut();
+ });
+ $('.js-remove-tr').bind('ajax:before', function () {
+ return $(this).hide();
+ });
+ $('.js-remove-tr').bind('ajax:success', function () {
+ return $(this).closest('tr').fadeOut();
+ });
+ $('select.select2').select2({
+ width: 'resolve',
+ // Initialize select2 selects
+ dropdownAutoWidth: true
+ });
+ $('.js-select2').bind('select2-close', function () {
+ return setTimeout((function () {
+ $('.select2-container-active').removeClass('select2-container-active');
+ return $(':focus').blur();
+ }), 1);
+ // Close select2 on escape
+ });
+ // Initialize tooltips
+ $.fn.tooltip.Constructor.DEFAULTS.trigger = 'hover';
+ $body.tooltip({
+ selector: '.has-tooltip, [data-toggle="tooltip"]',
+ placement: function (tip, el) {
+ return $(el).data('placement') || 'bottom';
+ }
+ });
+ $('.trigger-submit').on('change', function () {
+ return $(this).parents('form').submit();
+ // Form submitter
+ });
+ gl.utils.localTimeAgo($('abbr.timeago, .js-timeago'), true);
+ // Flash
+ if ($flash.length > 0) {
+ $flash.click(function () {
+ return $(this).fadeOut();
});
- fitSidebarForSize = function () {
- var oldBootstrapBreakpoint;
- oldBootstrapBreakpoint = bootstrapBreakpoint;
- bootstrapBreakpoint = bp.getBreakpointSize();
- if (bootstrapBreakpoint !== oldBootstrapBreakpoint) {
- return $document.trigger('breakpoint:change', [bootstrapBreakpoint]);
+ $flash.show();
+ }
+ // Disable form buttons while a form is submitting
+ $body.on('ajax:complete, ajax:beforeSend, submit', 'form', function (e) {
+ var buttons;
+ buttons = $('[type="submit"]', this);
+ switch (e.type) {
+ case 'ajax:beforeSend':
+ case 'submit':
+ return buttons.disable();
+ default:
+ return buttons.enable();
+ }
+ });
+ $(document).ajaxError(function (e, xhrObj) {
+ var ref = xhrObj.status;
+ if (xhrObj.status === 401) {
+ return new Flash('You need to be logged in.', 'alert');
+ } else if (ref === 404 || ref === 500) {
+ return new Flash('Something went wrong on our end.', 'alert');
+ }
+ });
+ $('.account-box').hover(function () {
+ // Show/Hide the profile menu when hovering the account box
+ return $(this).toggleClass('hover');
+ });
+ $document.on('click', '.diff-content .js-show-suppressed-diff', function () {
+ var $container;
+ $container = $(this).parent();
+ $container.next('table').show();
+ return $container.remove();
+ // Commit show suppressed diff
+ });
+ $('.navbar-toggle').on('click', function () {
+ $('.header-content .title').toggle();
+ $('.header-content .header-logo').toggle();
+ $('.header-content .navbar-collapse').toggle();
+ return $('.navbar-toggle').toggleClass('active');
+ });
+ // Show/hide comments on diff
+ $body.on('click', '.js-toggle-diff-comments', function (e) {
+ var $this = $(this);
+ var notesHolders = $this.closest('.diff-file').find('.notes_holder');
+ $this.toggleClass('active');
+ if ($this.hasClass('active')) {
+ notesHolders.show().find('.hide, .content').show();
+ } else {
+ notesHolders.hide().find('.content').hide();
+ }
+ $(document).trigger('toggle.comments');
+ return e.preventDefault();
+ });
+ $document.off('click', '.js-confirm-danger');
+ $document.on('click', '.js-confirm-danger', function (e) {
+ var btn = $(e.target);
+ var form = btn.closest('form');
+ var text = btn.data('confirm-danger-message');
+ e.preventDefault();
+ return new ConfirmDangerModal(form, text);
+ });
+ $('input[type="search"]').each(function () {
+ var $this = $(this);
+ $this.attr('value', $this.val());
+ });
+ $document.off('keyup', 'input[type="search"]').on('keyup', 'input[type="search"]', function () {
+ var $this;
+ $this = $(this);
+ return $this.attr('value', $this.val());
+ });
+ $document.off('breakpoint:change').on('breakpoint:change', function (e, breakpoint) {
+ var $gutterIcon;
+ if (breakpoint === 'sm' || breakpoint === 'xs') {
+ $gutterIcon = $sidebarGutterToggle.find('i');
+ if ($gutterIcon.hasClass('fa-angle-double-right')) {
+ return $sidebarGutterToggle.trigger('click');
}
- };
- $window.off('resize.app').on('resize.app', function () {
- return fitSidebarForSize();
- });
- gl.awardsHandler = new AwardsHandler();
- new Aside();
-
- gl.utils.initTimeagoTimeout();
+ }
});
-}).call(window);
+ fitSidebarForSize = function () {
+ var oldBootstrapBreakpoint;
+ oldBootstrapBreakpoint = bootstrapBreakpoint;
+ bootstrapBreakpoint = bp.getBreakpointSize();
+ if (bootstrapBreakpoint !== oldBootstrapBreakpoint) {
+ return $document.trigger('breakpoint:change', [bootstrapBreakpoint]);
+ }
+ };
+ $window.off('resize.app').on('resize.app', function () {
+ return fitSidebarForSize();
+ });
+ gl.awardsHandler = new AwardsHandler();
+ new Aside();
+
+ gl.utils.initTimeagoTimeout();
+});
diff --git a/app/assets/javascripts/merge_request_widget.js b/app/assets/javascripts/merge_request_widget.js
index 5f1bd474a0c..66cc270ab4d 100644
--- a/app/assets/javascripts/merge_request_widget.js
+++ b/app/assets/javascripts/merge_request_widget.js
@@ -3,7 +3,8 @@
/* global notifyPermissions */
/* global merge_request_widget */
-require('./smart_interval');
+import './smart_interval';
+import MiniPipelineGraph from './mini_pipeline_graph_dropdown';
((global) => {
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; };
@@ -285,7 +286,7 @@ require('./smart_interval');
};
MergeRequestWidget.prototype.initMiniPipelineGraph = function() {
- new gl.MiniPipelineGraph({
+ new MiniPipelineGraph({
container: '.js-pipeline-inline-mr-widget-graph:visible',
}).bindEvents();
};
diff --git a/app/assets/javascripts/mini_pipeline_graph_dropdown.js b/app/assets/javascripts/mini_pipeline_graph_dropdown.js
index 2145e531331..9c58c465001 100644
--- a/app/assets/javascripts/mini_pipeline_graph_dropdown.js
+++ b/app/assets/javascripts/mini_pipeline_graph_dropdown.js
@@ -15,81 +15,96 @@
* <div class="js-builds-dropdown-container dropdown-menu"></div>
* </div>
*/
-(() => {
- class MiniPipelineGraph {
- constructor(opts = {}) {
- this.container = opts.container || '';
- this.dropdownListSelector = '.js-builds-dropdown-container';
- this.getBuildsList = this.getBuildsList.bind(this);
- }
- /**
- * Adds the event listener when the dropdown is opened.
- * All dropdown events are fired at the .dropdown-menu's parent element.
- */
- bindEvents() {
- $(document).off('shown.bs.dropdown', this.container).on('shown.bs.dropdown', this.container, this.getBuildsList);
- }
+export default class MiniPipelineGraph {
+ constructor(opts = {}) {
+ this.container = opts.container || '';
+ this.dropdownListSelector = '.js-builds-dropdown-container';
+ this.getBuildsList = this.getBuildsList.bind(this);
+ }
+
+ /**
+ * Adds the event listener when the dropdown is opened.
+ * All dropdown events are fired at the .dropdown-menu's parent element.
+ */
+ bindEvents() {
+ $(document).off('shown.bs.dropdown', this.container).on('shown.bs.dropdown', this.container, this.getBuildsList);
+ }
- /**
- * For the clicked stage, renders the given data in the dropdown list.
- *
- * @param {HTMLElement} stageContainer
- * @param {Object} data
- */
- renderBuildsList(stageContainer, data) {
- const dropdownContainer = stageContainer.parentElement.querySelector(
- `${this.dropdownListSelector} .js-builds-dropdown-list`,
- );
+ /**
+ * When the user right clicks or cmd/ctrl + click in the job name
+ * the dropdown should not be closed and the link should open in another tab,
+ * so we stop propagation of the click event inside the dropdown.
+ *
+ * Since this component is rendered multiple times per page we need to guarantee we only
+ * target the click event of this component.
+ */
+ stopDropdownClickPropagation() {
+ $(document).on(
+ 'click',
+ `${this.container} .js-builds-dropdown-list a.mini-pipeline-graph-dropdown-item`,
+ (e) => {
+ e.stopPropagation();
+ },
+ );
+ }
- dropdownContainer.innerHTML = data;
- }
+ /**
+ * For the clicked stage, renders the given data in the dropdown list.
+ *
+ * @param {HTMLElement} stageContainer
+ * @param {Object} data
+ */
+ renderBuildsList(stageContainer, data) {
+ const dropdownContainer = stageContainer.parentElement.querySelector(
+ `${this.dropdownListSelector} .js-builds-dropdown-list`,
+ );
- /**
- * For the clicked stage, gets the list of builds.
- *
- * 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.relatedTarget;
- const endpoint = button.dataset.stageEndpoint;
+ dropdownContainer.innerHTML = data;
+ }
- return $.ajax({
- dataType: 'json',
- type: 'GET',
- url: endpoint,
- beforeSend: () => {
- this.renderBuildsList(button, '');
- this.toggleLoading(button);
- },
- success: (data) => {
- this.toggleLoading(button);
- this.renderBuildsList(button, data.html);
- },
- error: () => {
- this.toggleLoading(button);
- new Flash('An error occurred while fetching the builds.', 'alert');
- },
- });
- }
+ /**
+ * For the clicked stage, gets the list of builds.
+ *
+ * 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.relatedTarget;
+ const endpoint = button.dataset.stageEndpoint;
- /**
- * Toggles the visibility of the loading icon.
- *
- * @param {HTMLElement} stageContainer
- * @return {type}
- */
- toggleLoading(stageContainer) {
- stageContainer.parentElement.querySelector(
- `${this.dropdownListSelector} .js-builds-dropdown-loading`,
- ).classList.toggle('hidden');
- }
+ return $.ajax({
+ dataType: 'json',
+ type: 'GET',
+ url: endpoint,
+ beforeSend: () => {
+ this.renderBuildsList(button, '');
+ this.toggleLoading(button);
+ },
+ success: (data) => {
+ this.toggleLoading(button);
+ this.renderBuildsList(button, data.html);
+ this.stopDropdownClickPropagation();
+ },
+ error: () => {
+ this.toggleLoading(button);
+ new Flash('An error occurred while fetching the builds.', 'alert');
+ },
+ });
}
- window.gl = window.gl || {};
- window.gl.MiniPipelineGraph = MiniPipelineGraph;
-})();
+ /**
+ * Toggles the visibility of the loading icon.
+ *
+ * @param {HTMLElement} stageContainer
+ * @return {type}
+ */
+ toggleLoading(stageContainer) {
+ stageContainer.parentElement.querySelector(
+ `${this.dropdownListSelector} .js-builds-dropdown-loading`,
+ ).classList.toggle('hidden');
+ }
+}
diff --git a/app/assets/javascripts/monitoring/prometheus_graph.js b/app/assets/javascripts/monitoring/prometheus_graph.js
index 9384fe3f276..71eb746edac 100644
--- a/app/assets/javascripts/monitoring/prometheus_graph.js
+++ b/app/assets/javascripts/monitoring/prometheus_graph.js
@@ -1,9 +1,11 @@
-/* eslint-disable no-new*/
+/* eslint-disable no-new */
+/* global Flash */
+
import d3 from 'd3';
import _ from 'underscore';
import statusCodes from '~/lib/utils/http_status';
import '~/lib/utils/common_utils';
-import Flash from '~/flash';
+import '~/flash';
const prometheusGraphsContainer = '.prometheus-graph';
const metricsEndpoint = 'metrics.json';
diff --git a/app/assets/javascripts/new_commit_form.js b/app/assets/javascripts/new_commit_form.js
index 747f693726e..ad36f08840d 100644
--- a/app/assets/javascripts/new_commit_form.js
+++ b/app/assets/javascripts/new_commit_form.js
@@ -3,19 +3,23 @@
var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; };
this.NewCommitForm = (function() {
- function NewCommitForm(form) {
+ function NewCommitForm(form, targetBranchName = 'target_branch') {
+ this.form = form;
+ this.targetBranchName = targetBranchName;
this.renderDestination = bind(this.renderDestination, this);
- this.newBranch = form.find('.js-target-branch');
+ this.targetBranchDropdown = form.find('button.js-target-branch');
this.originalBranch = form.find('.js-original-branch');
this.createMergeRequest = form.find('.js-create-merge-request');
this.createMergeRequestContainer = form.find('.js-create-merge-request-container');
+ this.targetBranchDropdown.on('change.branch', this.renderDestination);
this.renderDestination();
- this.newBranch.keyup(this.renderDestination);
}
NewCommitForm.prototype.renderDestination = function() {
var different;
- different = this.newBranch.val() !== this.originalBranch.val();
+ var targetBranch = this.form.find(`input[name="${this.targetBranchName}"]`);
+
+ different = targetBranch.val() !== this.originalBranch.val();
if (different) {
this.createMergeRequestContainer.show();
if (!this.wasDifferent) {
diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js
index df7a7d2a459..47cc34e7a20 100644
--- a/app/assets/javascripts/notes.js
+++ b/app/assets/javascripts/notes.js
@@ -1,12 +1,14 @@
/* 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 Autosave */
+/* global Cookies */
/* global ResolveService */
/* global mrRefreshWidgetUrl */
require('./autosave');
window.autosize = require('vendor/autosize');
window.Dropzone = require('dropzone');
+window.Cookies = require('js-cookie');
require('./dropzone_input');
require('./gfm_auto_complete');
require('vendor/jquery.caret'); // required by jquery.atwho
@@ -42,7 +44,6 @@ require('./task_list');
this.notes_url = notes_url;
this.note_ids = note_ids;
this.last_fetched_at = last_fetched_at;
- this.view = view;
this.noteable_url = document.URL;
this.notesCountBadge || (this.notesCountBadge = $(".issuable-details").find(".notes-tab .badge"));
this.basePollingInterval = 15000;
@@ -57,6 +58,7 @@ require('./task_list');
selector: '.notes'
});
this.collapseLongCommitList();
+ this.setViewType(view);
// We are in the Merge Requests page so we need another edit form for Changes tab
if (gl.utils.getPagePath(1) === 'merge_requests') {
@@ -65,6 +67,10 @@ require('./task_list');
}
}
+ Notes.prototype.setViewType = function(view) {
+ this.view = Cookies.get('diff_view') || view;
+ };
+
Notes.prototype.addBinding = function() {
// add note to UI after creation
$(document).on("ajax:success", ".js-main-target-form", this.addNote);
@@ -302,7 +308,7 @@ require('./task_list');
};
Notes.prototype.isParallelView = function() {
- return this.view === 'parallel';
+ return Cookies.get('diff_view') === 'parallel';
};
/*
@@ -312,7 +318,7 @@ require('./task_list');
*/
Notes.prototype.renderDiscussionNote = function(note) {
- var discussionContainer, form, note_html, row;
+ var discussionContainer, form, note_html, row, lineType, diffAvatarContainer;
if (!this.isNewNote(note)) {
return;
}
@@ -322,6 +328,8 @@ require('./task_list');
form = $("#new-discussion-note-form-" + note.original_discussion_id);
}
row = form.closest("tr");
+ lineType = this.isParallelView() ? form.find('#line_type').val() : 'old';
+ diffAvatarContainer = row.prevAll('.line_holder').first().find('.js-avatar-container.' + lineType + '_line');
note_html = $(note.html);
note_html.renderGFM();
// is this the first note of discussion?
@@ -330,10 +338,26 @@ require('./task_list');
discussionContainer = $(".notes[data-discussion-id='" + note.original_discussion_id + "']");
}
if (discussionContainer.length === 0) {
- // insert the note and the reply button after the temp row
- row.after(note.diff_discussion_html);
- // remove the note (will be added again below)
- row.next().find(".note").remove();
+ if (!this.isParallelView() || row.hasClass('js-temp-notes-holder')) {
+ // insert the note and the reply button after the temp row
+ row.after(note.diff_discussion_html);
+
+ // remove the note (will be added again below)
+ row.next().find(".note").remove();
+ } else {
+ // Merge new discussion HTML in
+ var $discussion = $(note.diff_discussion_html);
+ var $notes = $discussion.find('.notes[data-discussion-id="' + note.discussion_id + '"]');
+ var contentContainerClass = '.' + $notes.closest('.notes_content')
+ .attr('class')
+ .split(' ')
+ .join('.');
+
+ // remove the note (will be added again below)
+ $notes.find('.note').remove();
+
+ row.find(contentContainerClass + ' .content').append($notes.closest('.content').children());
+ }
// Before that, the container didn't exist
discussionContainer = $(".notes[data-discussion-id='" + note.discussion_id + "']");
// Add note to 'Changes' page discussions
@@ -347,14 +371,40 @@ require('./task_list');
discussionContainer.append(note_html);
}
- if (typeof gl.diffNotesCompileComponents !== 'undefined') {
+ if (typeof gl.diffNotesCompileComponents !== 'undefined' && note.discussion_id) {
gl.diffNotesCompileComponents();
+ this.renderDiscussionAvatar(diffAvatarContainer, note);
}
gl.utils.localTimeAgo($('.js-timeago'), false);
return this.updateNotesCount(1);
};
+ Notes.prototype.getLineHolder = function(changesDiscussionContainer) {
+ return $(changesDiscussionContainer).closest('.notes_holder')
+ .prevAll('.line_holder')
+ .first()
+ .get(0);
+ };
+
+ Notes.prototype.renderDiscussionAvatar = function(diffAvatarContainer, note) {
+ var commentButton = diffAvatarContainer.find('.js-add-diff-note-button');
+ var avatarHolder = diffAvatarContainer.find('.diff-comment-avatar-holders');
+
+ if (!avatarHolder.length) {
+ avatarHolder = document.createElement('diff-note-avatars');
+ avatarHolder.setAttribute('discussion-id', note.discussion_id);
+
+ diffAvatarContainer.append(avatarHolder);
+
+ gl.diffNotesCompileComponents();
+ }
+
+ if (commentButton.length) {
+ commentButton.remove();
+ }
+ };
+
/*
Called in response the main target form has been successfully submitted.
@@ -592,9 +642,14 @@ require('./task_list');
*/
Notes.prototype.removeNote = function(e) {
- var noteId;
- noteId = $(e.currentTarget).closest(".note").attr("id");
- $(".note[id='" + noteId + "']").each((function(_this) {
+ var noteElId, noteId, dataNoteId, $note, lineHolder;
+ $note = $(e.currentTarget).closest('.note');
+ noteElId = $note.attr('id');
+ noteId = $note.attr('data-note-id');
+ lineHolder = $(e.currentTarget).closest('.notes[data-discussion-id]')
+ .closest('.notes_holder')
+ .prev('.line_holder');
+ $(".note[id='" + noteElId + "']").each((function(_this) {
// A same note appears in the "Discussion" and in the "Changes" tab, we have
// to remove all. Using $(".note[id='noteId']") ensure we get all the notes,
// where $("#noteId") would return only one.
@@ -604,17 +659,26 @@ require('./task_list');
notes = note.closest(".notes");
if (typeof gl.diffNotesCompileComponents !== 'undefined') {
- if (gl.diffNoteApps[noteId]) {
- gl.diffNoteApps[noteId].$destroy();
+ if (gl.diffNoteApps[noteElId]) {
+ gl.diffNoteApps[noteElId].$destroy();
}
}
+ note.remove();
+
// check if this is the last note for this line
- if (notes.find(".note").length === 1) {
+ if (notes.find(".note").length === 0) {
+ var notesTr = notes.closest("tr");
+
// "Discussions" tab
notes.closest(".timeline-entry").remove();
- // "Changes" tab / commit view
- notes.closest("tr").remove();
+
+ if (!_this.isParallelView() || notesTr.find('.note').length === 0) {
+ // "Changes" tab / commit view
+ notesTr.remove();
+ } else {
+ notes.closest('.content').empty();
+ }
}
return note.remove();
};
@@ -707,15 +771,16 @@ require('./task_list');
*/
Notes.prototype.addDiffNote = function(e) {
- var $link, addForm, hasNotes, lineType, newForm, nextRow, noteForm, notesContent, notesContentSelector, replyButton, row, rowCssToAdd, targetContent;
+ var $link, addForm, hasNotes, lineType, newForm, nextRow, noteForm, notesContent, notesContentSelector, replyButton, row, rowCssToAdd, targetContent, isDiffCommentAvatar;
e.preventDefault();
- $link = $(e.currentTarget);
+ $link = $(e.currentTarget || e.target);
row = $link.closest("tr");
nextRow = row.next();
hasNotes = nextRow.is(".notes_holder");
addForm = false;
notesContentSelector = ".notes_content";
rowCssToAdd = "<tr class=\"notes_holder js-temp-notes-holder\"><td class=\"notes_line\" colspan=\"2\"></td><td class=\"notes_content\"><div class=\"content\"></div></td></tr>";
+ isDiffCommentAvatar = $link.hasClass('js-diff-comment-avatar');
// In parallel view, look inside the correct left/right pane
if (this.isParallelView()) {
lineType = $link.data("lineType");
@@ -723,7 +788,9 @@ require('./task_list');
rowCssToAdd = "<tr class=\"notes_holder js-temp-notes-holder\"><td class=\"notes_line old\"></td><td class=\"notes_content parallel old\"><div class=\"content\"></div></td><td class=\"notes_line new\"></td><td class=\"notes_content parallel new\"><div class=\"content\"></div></td></tr>";
}
notesContentSelector += " .content";
- if (hasNotes) {
+ notesContent = nextRow.find(notesContentSelector);
+
+ if (hasNotes && !isDiffCommentAvatar) {
nextRow.show();
notesContent = nextRow.find(notesContentSelector);
if (notesContent.length) {
@@ -740,13 +807,21 @@ require('./task_list');
}
}
}
- } else {
+ } else if (!isDiffCommentAvatar) {
// add a notes row and insert the form
row.after(rowCssToAdd);
nextRow = row.next();
notesContent = nextRow.find(notesContentSelector);
addForm = true;
+ } else {
+ nextRow.show();
+ notesContent.toggle(!notesContent.is(':visible'));
+
+ if (!nextRow.find('.content:not(:empty)').is(':visible')) {
+ nextRow.hide();
+ }
}
+
if (addForm) {
newForm = this.formClone.clone();
newForm.appendTo(notesContent);
diff --git a/app/assets/javascripts/pager.js b/app/assets/javascripts/pager.js
index e35cf6d295e..5f6bc902cf8 100644
--- a/app/assets/javascripts/pager.js
+++ b/app/assets/javascripts/pager.js
@@ -1,11 +1,15 @@
+require('~/lib/utils/common_utils');
+require('~/lib/utils/url_utility');
+
(() => {
const ENDLESS_SCROLL_BOTTOM_PX = 400;
const ENDLESS_SCROLL_FIRE_DELAY_MS = 1000;
const Pager = {
init(limit = 0, preload = false, disable = false, callback = $.noop) {
+ this.url = $('.content_list').data('href') || gl.utils.removeParams(['limit', 'offset']);
this.limit = limit;
- this.offset = this.limit;
+ this.offset = parseInt(gl.utils.getParameterByName('offset'), 10) || this.limit;
this.disable = disable;
this.callback = callback;
this.loading = $('.loading').first();
@@ -20,7 +24,7 @@
this.loading.show();
$.ajax({
type: 'GET',
- url: $('.content_list').data('href') || window.location.href,
+ url: this.url,
data: `limit=${this.limit}&offset=${this.offset}`,
dataType: 'json',
error: () => this.loading.hide(),
diff --git a/app/assets/javascripts/project_select.js b/app/assets/javascripts/project_select.js
index f80e765ce30..3c1c1e7dceb 100644
--- a/app/assets/javascripts/project_select.js
+++ b/app/assets/javascripts/project_select.js
@@ -35,7 +35,7 @@
if (this.groupId) {
return Api.groupProjects(this.groupId, term, projectsCallback);
} else {
- return Api.projects(term, orderBy, projectsCallback);
+ return Api.projects(term, { order_by: orderBy }, projectsCallback);
}
},
url: function(project) {
@@ -84,7 +84,7 @@
if (_this.groupId) {
return Api.groupProjects(_this.groupId, query.term, projectsCallback);
} else {
- return Api.projects(query.term, _this.orderBy, projectsCallback);
+ return Api.projects(query.term, { order_by: _this.orderBy }, projectsCallback);
}
};
})(this),
diff --git a/app/assets/javascripts/search.js b/app/assets/javascripts/search.js
index e66418beeab..15f5963353a 100644
--- a/app/assets/javascripts/search.js
+++ b/app/assets/javascripts/search.js
@@ -47,7 +47,7 @@
fields: ['name']
},
data: function(term, callback) {
- return Api.projects(term, 'id', function(data) {
+ return Api.projects(term, { order_by: 'id' }, function(data) {
data.unshift({
name_with_namespace: 'Any'
});
diff --git a/app/assets/javascripts/todos.js b/app/assets/javascripts/todos.js
index e9513725d9d..caaf6484a34 100644
--- a/app/assets/javascripts/todos.js
+++ b/app/assets/javascripts/todos.js
@@ -1,146 +1,146 @@
-/* eslint-disable class-methods-use-this, no-new, func-names, no-unneeded-ternary, object-shorthand, quote-props, no-param-reassign, max-len */
+/* eslint-disable class-methods-use-this, no-unneeded-ternary, quote-props */
/* global UsersSelect */
-((global) => {
- class Todos {
- constructor() {
- this.initFilters();
- this.bindEvents();
+class Todos {
+ constructor() {
+ this.initFilters();
+ this.bindEvents();
- this.cleanupWrapper = this.cleanup.bind(this);
- document.addEventListener('beforeunload', this.cleanupWrapper);
- }
+ this.cleanupWrapper = this.cleanup.bind(this);
+ document.addEventListener('beforeunload', this.cleanupWrapper);
+ }
- cleanup() {
- this.unbindEvents();
- document.removeEventListener('beforeunload', this.cleanupWrapper);
- }
+ cleanup() {
+ this.unbindEvents();
+ document.removeEventListener('beforeunload', this.cleanupWrapper);
+ }
- unbindEvents() {
- $('.js-done-todo, .js-undo-todo').off('click', this.updateStateClickedWrapper);
- $('.js-todos-mark-all').off('click', this.allDoneClickedWrapper);
- $('.todo').off('click', this.goToTodoUrl);
- }
+ unbindEvents() {
+ $('.js-done-todo, .js-undo-todo, .js-add-todo').off('click', this.updateRowStateClickedWrapper);
+ $('.js-todos-mark-all').off('click', this.allDoneClickedWrapper);
+ $('.todo').off('click', this.goToTodoUrl);
+ }
- bindEvents() {
- this.updateStateClickedWrapper = this.updateStateClicked.bind(this);
- this.allDoneClickedWrapper = this.allDoneClicked.bind(this);
+ bindEvents() {
+ this.updateRowStateClickedWrapper = this.updateRowStateClicked.bind(this);
+ this.allDoneClickedWrapper = this.allDoneClicked.bind(this);
- $('.js-done-todo, .js-undo-todo').on('click', this.updateStateClickedWrapper);
- $('.js-todos-mark-all').on('click', this.allDoneClickedWrapper);
- $('.todo').on('click', this.goToTodoUrl);
- }
+ $('.js-done-todo, .js-undo-todo, .js-add-todo').on('click', this.updateRowStateClickedWrapper);
+ $('.js-todos-mark-all').on('click', this.allDoneClickedWrapper);
+ $('.todo').on('click', this.goToTodoUrl);
+ }
- initFilters() {
- new UsersSelect();
- this.initFilterDropdown($('.js-project-search'), 'project_id', ['text']);
- this.initFilterDropdown($('.js-type-search'), 'type');
- this.initFilterDropdown($('.js-action-search'), 'action_id');
+ initFilters() {
+ this.initFilterDropdown($('.js-project-search'), 'project_id', ['text']);
+ this.initFilterDropdown($('.js-type-search'), 'type');
+ this.initFilterDropdown($('.js-action-search'), 'action_id');
- $('form.filter-form').on('submit', function (event) {
- event.preventDefault();
- gl.utils.visitUrl(`${this.action}&${$(this).serialize()}`);
- });
- }
+ $('form.filter-form').on('submit', function applyFilters(event) {
+ event.preventDefault();
+ gl.utils.visitUrl(`${this.action}&${$(this).serialize()}`);
+ });
+ return new UsersSelect();
+ }
- initFilterDropdown($dropdown, fieldName, searchFields) {
- $dropdown.glDropdown({
- fieldName,
- selectable: true,
- filterable: searchFields ? true : false,
- search: { fields: searchFields },
- data: $dropdown.data('data'),
- clicked: function () {
- return $dropdown.closest('form.filter-form').submit();
- },
- });
- }
+ initFilterDropdown($dropdown, fieldName, searchFields) {
+ $dropdown.glDropdown({
+ fieldName,
+ selectable: true,
+ filterable: searchFields ? true : false,
+ search: { fields: searchFields },
+ data: $dropdown.data('data'),
+ clicked: () => $dropdown.closest('form.filter-form').submit(),
+ });
+ }
- updateStateClicked(e) {
- e.preventDefault();
- const target = e.target;
- target.setAttribute('disabled', '');
- target.classList.add('disabled');
- $.ajax({
- type: 'POST',
- url: target.getAttribute('href'),
- dataType: 'json',
- data: {
- '_method': target.getAttribute('data-method'),
- },
- success: (data) => {
- this.updateState(target);
- this.updateBadges(data);
- },
- });
- }
+ updateRowStateClicked(e) {
+ e.preventDefault();
+
+ const target = e.target;
+ target.setAttribute('disabled', '');
+ target.classList.add('disabled');
+ $.ajax({
+ type: 'POST',
+ url: target.getAttribute('href'),
+ dataType: 'json',
+ data: {
+ '_method': target.getAttribute('data-method'),
+ },
+ success: (data) => {
+ this.updateRowState(target);
+ return this.updateBadges(data);
+ },
+ });
+ }
- allDoneClicked(e) {
- e.preventDefault();
- const $target = $(e.currentTarget);
- $target.disable();
- $.ajax({
- type: 'POST',
- url: $target.attr('href'),
- dataType: 'json',
- data: {
- '_method': 'delete',
- },
- success: (data) => {
- $target.remove();
- $('.js-todos-all').html('<div class="nothing-here-block">You\'re all done!</div>');
- this.updateBadges(data);
- },
- });
+ allDoneClicked(e) {
+ e.preventDefault();
+ const $target = $(e.currentTarget);
+ $target.disable();
+ $.ajax({
+ type: 'POST',
+ url: $target.attr('href'),
+ dataType: 'json',
+ data: {
+ '_method': 'delete',
+ },
+ success: (data) => {
+ $target.remove();
+ $('.js-todos-all').html('<div class="nothing-here-block">You\'re all done!</div>');
+ this.updateBadges(data);
+ },
+ });
+ }
+
+ updateRowState(target) {
+ const row = target.closest('li');
+ const restoreBtn = row.querySelector('.js-undo-todo');
+ const doneBtn = row.querySelector('.js-done-todo');
+
+ target.classList.add('hidden');
+ target.removeAttribute('disabled');
+ target.classList.remove('disabled');
+
+ if (target === doneBtn) {
+ row.classList.add('done-reversible');
+ restoreBtn.classList.remove('hidden');
+ } else if (target === restoreBtn) {
+ row.classList.remove('done-reversible');
+ doneBtn.classList.remove('hidden');
+ } else {
+ row.parentNode.removeChild(row);
}
+ }
- updateState(target) {
- const row = target.closest('li');
- const restoreBtn = row.querySelector('.js-undo-todo');
- const doneBtn = row.querySelector('.js-done-todo');
+ updateBadges(data) {
+ $(document).trigger('todo:toggle', data.count);
+ document.querySelector('.todos-pending .badge').innerHTML = data.count;
+ document.querySelector('.todos-done .badge').innerHTML = data.done_count;
+ }
- target.removeAttribute('disabled');
- target.classList.remove('disabled');
- target.classList.add('hidden');
+ goToTodoUrl(e) {
+ const todoLink = this.dataset.url;
- if (target === doneBtn) {
- row.classList.add('done-reversible');
- restoreBtn.classList.remove('hidden');
- } else {
- row.classList.remove('done-reversible');
- doneBtn.classList.remove('hidden');
- }
- }
-
- updateBadges(data) {
- $(document).trigger('todo:toggle', data.count);
- $('.todos-pending .badge').text(data.count);
- $('.todos-done .badge').text(data.done_count);
+ if (!todoLink) {
+ return;
}
- goToTodoUrl(e) {
- const todoLink = this.dataset.url;
-
- if (!todoLink) {
- return;
- }
+ if (gl.utils.isMetaClick(e)) {
+ const windowTarget = '_blank';
+ const selected = e.target;
+ e.preventDefault();
- if (gl.utils.isMetaClick(e)) {
- const windowTarget = '_blank';
- const selected = e.target;
- e.preventDefault();
-
- if (selected.tagName === 'IMG') {
- const avatarUrl = selected.parentElement.getAttribute('href');
- window.open(avatarUrl, windowTarget);
- } else {
- window.open(todoLink, windowTarget);
- }
+ if (selected.tagName === 'IMG') {
+ const avatarUrl = selected.parentElement.getAttribute('href');
+ window.open(avatarUrl, windowTarget);
} else {
- gl.utils.visitUrl(todoLink);
+ window.open(todoLink, windowTarget);
}
+ } else {
+ gl.utils.visitUrl(todoLink);
}
}
+}
- global.Todos = Todos;
-})(window.gl || (window.gl = {}));
+window.gl = window.gl || {};
+gl.Todos = Todos;
diff --git a/app/assets/javascripts/vue_pipelines_index/stage.js b/app/assets/javascripts/vue_pipelines_index/stage.js
index f67ebd6a265..ae4f0b4a53b 100644
--- a/app/assets/javascripts/vue_pipelines_index/stage.js
+++ b/app/assets/javascripts/vue_pipelines_index/stage.js
@@ -69,7 +69,7 @@ import warningSvg from 'icons/_icon_status_warning_borderless.svg';
* target the click event of this component.
*/
stopDropdownClickPropagation() {
- $(this.$el.querySelectorAll('.js-builds-dropdown-list a.mini-pipeline-graph-dropdown-item')).on('click', (e) => {
+ $(this.$el).on('click', '.js-builds-dropdown-list a.mini-pipeline-graph-dropdown-item', (e) => {
e.stopPropagation();
});
},
diff --git a/app/assets/javascripts/vue_shared/vue_resource_interceptor.js b/app/assets/javascripts/vue_shared/vue_resource_interceptor.js
index d3229f9f730..4157fefddc9 100644
--- a/app/assets/javascripts/vue_shared/vue_resource_interceptor.js
+++ b/app/assets/javascripts/vue_shared/vue_resource_interceptor.js
@@ -6,10 +6,6 @@ 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--;
});
});
diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss
index 887ab481de4..186bb9ac616 100644
--- a/app/assets/stylesheets/framework/dropdowns.scss
+++ b/app/assets/stylesheets/framework/dropdowns.scss
@@ -43,7 +43,7 @@
white-space: nowrap;
&[disabled] {
- background-color: $input-bg-disabled;
+ opacity: .65;
cursor: not-allowed;
}
@@ -159,12 +159,12 @@
li {
text-align: left;
list-style: none;
- padding: 0 8px;
+ padding: 0 10px;
}
.divider {
height: 1px;
- margin: 8px;
+ margin: 6px 10px;
padding: 0;
background-color: $dropdown-divider-color;
}
@@ -181,7 +181,7 @@
display: block;
position: relative;
padding: 5px 8px;
- color: $dropdown-link-color;
+ color: $gl-text-color;
line-height: initial;
text-overflow: ellipsis;
border-radius: 2px;
@@ -218,11 +218,12 @@
}
.dropdown-header {
- color: $gl-text-color-secondary;
+ color: $gl-text-color;
font-size: 13px;
+ font-weight: 600;
line-height: 22px;
text-transform: capitalize;
- padding: 0 10px;
+ padding: 0 16px;
}
.separator + .dropdown-header {
@@ -325,14 +326,17 @@
.dropdown-menu-selectable {
a {
- padding-left: 25px;
+ padding-left: 26px;
&.is-indeterminate,
&.is-active {
+ font-weight: 600;
+ color: $gl-text-color;
+
&::before {
position: absolute;
- left: 5px;
- top: 8px;
+ left: 6px;
+ top: 6px;
font: normal normal normal 14px/1 FontAwesome;
font-size: inherit;
text-rendering: auto;
@@ -354,7 +358,7 @@
.dropdown-title {
position: relative;
- padding: 0 25px 10px;
+ padding: 2px 25px 10px;
margin: 0 10px 10px;
font-weight: 600;
line-height: 1;
@@ -384,7 +388,7 @@
right: 5px;
width: 20px;
height: 20px;
- top: -3px;
+ top: -1px;
}
.dropdown-menu-back {
diff --git a/app/assets/stylesheets/framework/highlight.scss b/app/assets/stylesheets/framework/highlight.scss
index 909a0f4afda..6d27d7568cf 100644
--- a/app/assets/stylesheets/framework/highlight.scss
+++ b/app/assets/stylesheets/framework/highlight.scss
@@ -57,8 +57,13 @@
visibility: hidden;
}
- &:hover i {
- visibility: visible;
+ &:hover,
+ &:focus {
+ outline: none;
+
+ & i {
+ visibility: visible;
+ }
}
}
}
diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss
index 674d3bb45aa..ea45aaa0253 100644
--- a/app/assets/stylesheets/framework/nav.scss
+++ b/app/assets/stylesheets/framework/nav.scss
@@ -294,7 +294,7 @@
.nav-control {
@media (max-width: $screen-sm-max) {
- margin-right: 75px;
+ margin-right: 2px;
}
}
}
diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss
index 5d0c247dea8..eab79c2a481 100644
--- a/app/assets/stylesheets/pages/diff.scss
+++ b/app/assets/stylesheets/pages/diff.scss
@@ -113,6 +113,10 @@
td.line_content.parallel {
width: 46%;
}
+
+ .add-diff-note {
+ margin-left: -55px;
+ }
}
.old_line,
@@ -490,3 +494,103 @@
}
}
}
+
+.diff-comment-avatar-holders {
+ position: absolute;
+ height: 19px;
+ width: 19px;
+ margin-left: -15px;
+
+ &:hover {
+ .diff-comment-avatar,
+ .diff-comments-more-count {
+ @for $i from 1 through 4 {
+ $x-pos: 14px;
+
+ &:nth-child(#{$i}) {
+ @if $i == 4 {
+ $x-pos: 14.5px;
+ }
+
+ transform: translateX((($i * $x-pos) - $x-pos));
+
+ &:hover {
+ transform: translateX((($i * $x-pos) - $x-pos)) scale(1.2);
+ }
+ }
+ }
+ }
+
+ .diff-comments-more-count {
+ padding-left: 2px;
+ padding-right: 2px;
+ width: auto;
+ }
+ }
+}
+
+.diff-comment-avatar,
+.diff-comments-more-count {
+ position: absolute;
+ left: 0;
+ width: 19px;
+ height: 19px;
+ margin-right: 0;
+ border-color: $white-light;
+ cursor: pointer;
+ transition: all .1s ease-out;
+
+ @for $i from 1 through 4 {
+ &:nth-child(#{$i}) {
+ z-index: (4 - $i);
+ }
+ }
+}
+
+.diff-comments-more-count {
+ width: 19px;
+ min-width: 19px;
+ padding-left: 0;
+ padding-right: 0;
+ overflow: hidden;
+}
+
+.diff-comments-more-count,
+.diff-notes-collapse {
+ background-color: $gray-darkest;
+ color: $white-light;
+ border: 1px solid $white-light;
+ border-radius: 1em;
+ font-family: $regular_font;
+ font-size: 9px;
+ line-height: 17px;
+ text-align: center;
+}
+
+.diff-notes-collapse {
+ position: relative;
+ width: 19px;
+ height: 19px;
+ padding: 0;
+ transition: transform .1s ease-out;
+
+ svg {
+ position: absolute;
+ left: 50%;
+ top: 50%;
+ margin-left: -5.5px;
+ margin-top: -5.5px;
+ }
+
+ path {
+ fill: $white-light;
+ }
+
+ &:hover {
+ transform: scale(1.2);
+ }
+
+ &:focus {
+ outline: 0;
+ }
+}
diff --git a/app/assets/stylesheets/pages/environments.scss b/app/assets/stylesheets/pages/environments.scss
index 0e2b8dba780..73a5da715f2 100644
--- a/app/assets/stylesheets/pages/environments.scss
+++ b/app/assets/stylesheets/pages/environments.scss
@@ -141,6 +141,14 @@
margin-right: 0;
}
}
+
+ .no-btn {
+ border: none;
+ background: none;
+ outline: none;
+ width: 100%;
+ text-align: left;
+ }
}
}
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index d3496e19dde..7c3172421c1 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -24,10 +24,6 @@
color: inherit;
}
- .btn-success.dropdown-toggle:disabled {
- background-color: $gl-success;
- }
-
.accept-merge-request {
&.ci-pending,
&.ci-running {
diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss
index f984b469609..c2156a5ac69 100644
--- a/app/assets/stylesheets/pages/note_form.scss
+++ b/app/assets/stylesheets/pages/note_form.scss
@@ -178,8 +178,25 @@
padding-right: 5px;
}
- &:last-child {
- padding-left: 5px;
+ }
+
+ .discussion-actions {
+ display: table;
+
+ .new-issue-for-discussion path {
+ fill: $gray-darkest;
+ }
+
+ .btn-group {
+ display: table-cell;
+
+ &:first-child {
+ padding-right: 0;
+ }
+
+ &:first-child:not(:last-child) > div {
+ border-right: 0;
+ }
}
}
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index dc79de19d48..e238f0865f6 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -384,7 +384,7 @@ ul.notes {
top: 0;
.note-action-button {
- margin-left: 10px;
+ margin-left: 8px;
}
}
@@ -400,8 +400,7 @@ ul.notes {
}
.note-action-button {
- display: inline-block;
- margin-left: 0;
+ display: inline;
line-height: 20px;
@media (min-width: $screen-sm-min) {
@@ -510,6 +509,7 @@ ul.notes {
}
.line-resolve-all-container {
+
.btn-group {
margin-left: -4px;
}
@@ -518,6 +518,27 @@ ul.notes {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
+
+ .btn.discussion-create-issue-btn {
+ margin-left: -4px;
+ border-radius: 0;
+ border-right: 0;
+
+ a {
+ padding: 0;
+ line-height: 0;
+
+ &:hover {
+ text-decoration: none;
+ border: 0;
+ }
+ }
+
+ .new-issue-for-discussion path {
+ fill: $gray-darkest;
+ }
+ }
+
}
.line-resolve-all {
@@ -540,7 +561,6 @@ ul.notes {
}
.line-resolve-btn {
- display: inline-block;
position: relative;
top: 2px;
padding: 0;
@@ -563,8 +583,9 @@ ul.notes {
}
svg {
- position: relative;
fill: $gray-darkest;
+ height: 15px;
+ width: 15px;
}
}
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index 4914933430f..efa47be9a73 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -795,7 +795,8 @@ pre.light-well {
}
.project-refs-form .dropdown-menu,
-.dropdown-menu-projects {
+.dropdown-menu-projects,
+.dropdown-menu-branches {
width: 300px;
@media (min-width: $screen-sm-min) {
diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb
index 7ffde71c3b1..24504685e48 100644
--- a/app/controllers/admin/users_controller.rb
+++ b/app/controllers/admin/users_controller.rb
@@ -29,11 +29,7 @@ class Admin::UsersController < Admin::ApplicationController
end
def impersonate
- if user.blocked?
- flash[:alert] = "You cannot impersonate a blocked user"
-
- redirect_to admin_user_path(user)
- else
+ if can?(user, :log_in)
session[:impersonator_id] = current_user.id
warden.set_user(user, scope: :user)
@@ -43,6 +39,17 @@ class Admin::UsersController < Admin::ApplicationController
flash[:alert] = "You are now impersonating #{user.username}"
redirect_to root_path
+ else
+ flash[:alert] =
+ if user.blocked?
+ "You cannot impersonate a blocked user"
+ elsif user.internal?
+ "You cannot impersonate an internal user"
+ else
+ "You cannot impersonate a user who cannot log in"
+ end
+
+ redirect_to admin_user_path(user)
end
end
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 1c66c530cd2..b7ce081a5cd 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -67,7 +67,7 @@ class ApplicationController < ActionController::Base
token_string = params[:private_token].presence || request.headers['PRIVATE-TOKEN'].presence
user = User.find_by_authentication_token(token_string) || User.find_by_personal_access_token(token_string)
- if user
+ if user && can?(user, :log_in)
# Notice we are passing store false, so the user is not
# actually stored in the session and a token is needed
# for every request. If you want the token to work as a
@@ -90,7 +90,7 @@ class ApplicationController < ActionController::Base
current_application_settings.after_sign_out_path.presence || new_user_session_path
end
- def can?(object, action, subject)
+ def can?(object, action, subject = :global)
Ability.allowed?(object, action, subject)
end
diff --git a/app/controllers/autocomplete_controller.rb b/app/controllers/autocomplete_controller.rb
index d7a45bacd35..b79ca034c5b 100644
--- a/app/controllers/autocomplete_controller.rb
+++ b/app/controllers/autocomplete_controller.rb
@@ -18,8 +18,7 @@ class AutocompleteController < ApplicationController
if params[:search].blank?
# Include current user if available to filter by "Me"
if params[:current_user].present? && current_user
- @users = @users.where.not(id: current_user.id)
- @users = [current_user, *@users]
+ @users = [current_user, *@users].uniq
end
if params[:author_id].present?
diff --git a/app/controllers/concerns/authenticates_with_two_factor.rb b/app/controllers/concerns/authenticates_with_two_factor.rb
index 4c497711fc0..ea441b1736b 100644
--- a/app/controllers/concerns/authenticates_with_two_factor.rb
+++ b/app/controllers/concerns/authenticates_with_two_factor.rb
@@ -23,7 +23,7 @@ module AuthenticatesWithTwoFactor
#
# Returns nil
def prompt_for_two_factor(user)
- return locked_user_redirect(user) if user.access_locked?
+ return locked_user_redirect(user) unless user.can?(:log_in)
session[:otp_user_id] = user.id
setup_u2f_authentication(user)
@@ -37,10 +37,9 @@ module AuthenticatesWithTwoFactor
def authenticate_with_two_factor
user = self.resource = find_user
+ return locked_user_redirect(user) unless user.can?(:log_in)
- if user.access_locked?
- locked_user_redirect(user)
- elsif user_params[:otp_attempt].present? && session[:otp_user_id]
+ if user_params[:otp_attempt].present? && session[:otp_user_id]
authenticate_with_two_factor_via_otp(user)
elsif user_params[:device_response].present? && session[:otp_user_id]
authenticate_with_two_factor_via_u2f(user)
diff --git a/app/controllers/dashboard/milestones_controller.rb b/app/controllers/dashboard/milestones_controller.rb
index 7f506db583f..df528d10f6e 100644
--- a/app/controllers/dashboard/milestones_controller.rb
+++ b/app/controllers/dashboard/milestones_controller.rb
@@ -5,6 +5,7 @@ class Dashboard::MilestonesController < Dashboard::ApplicationController
def index
respond_to do |format|
format.html do
+ @milestone_states = GlobalMilestone.states_count(@projects)
@milestones = Kaminari.paginate_array(milestones).page(params[:page])
end
format.json do
diff --git a/app/controllers/groups/milestones_controller.rb b/app/controllers/groups/milestones_controller.rb
index 0d872c86c8a..43102596201 100644
--- a/app/controllers/groups/milestones_controller.rb
+++ b/app/controllers/groups/milestones_controller.rb
@@ -6,6 +6,7 @@ class Groups::MilestonesController < Groups::ApplicationController
def index
respond_to do |format|
format.html do
+ @milestone_states = GlobalMilestone.states_count(@projects)
@milestones = Kaminari.paginate_array(milestones).page(params[:page])
end
end
diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb
index 4663b6e7fc6..05f9ee1ee90 100644
--- a/app/controllers/groups_controller.rb
+++ b/app/controllers/groups_controller.rb
@@ -118,7 +118,7 @@ class GroupsController < Groups::ApplicationController
end
def authorize_create_group!
- unless can?(current_user, :create_group, nil)
+ unless can?(current_user, :create_group)
return render_404
end
end
diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb
index 21ed0660762..52fc67d162c 100644
--- a/app/controllers/projects/blob_controller.rb
+++ b/app/controllers/projects/blob_controller.rb
@@ -23,6 +23,8 @@ class Projects::BlobController < Projects::ApplicationController
end
def create
+ update_ref
+
create_commit(Files::CreateService, success_notice: "The file has been successfully created.",
success_path: -> { namespace_project_blob_path(@project.namespace, @project, File.join(@target_branch, @file_path)) },
failure_view: :new,
@@ -87,6 +89,11 @@ class Projects::BlobController < Projects::ApplicationController
private
+ def update_ref
+ branch_exists = @repository.find_branch(@target_branch)
+ @ref = @target_branch if branch_exists
+ end
+
def blob
@blob ||= Blob.decorate(@repository.blob_at(@commit.id, @path))
diff --git a/app/controllers/projects/branches_controller.rb b/app/controllers/projects/branches_controller.rb
index c40f9b7f75f..22714d9c5a4 100644
--- a/app/controllers/projects/branches_controller.rb
+++ b/app/controllers/projects/branches_controller.rb
@@ -10,15 +10,16 @@ class Projects::BranchesController < Projects::ApplicationController
def index
@sort = params[:sort].presence || sort_value_name
@branches = BranchesFinder.new(@repository, params).execute
- @branches = Kaminari.paginate_array(@branches).page(params[:page])
- @max_commits = @branches.reduce(0) do |memo, branch|
- diverging_commit_counts = repository.diverging_commit_counts(branch)
- [memo, diverging_commit_counts[:behind], diverging_commit_counts[:ahead]].max
- end
+ @branches = Kaminari.paginate_array(@branches).page(params[:page]) unless params[:show_all].present?
respond_to do |format|
- format.html
+ format.html do
+ @max_commits = @branches.reduce(0) do |memo, branch|
+ diverging_commit_counts = repository.diverging_commit_counts(branch)
+ [memo, diverging_commit_counts[:behind], diverging_commit_counts[:ahead]].max
+ end
+ end
format.json do
render json: @branches.map(&:name)
end
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index 1151555b8fa..f2fee62ebd6 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -64,8 +64,15 @@ class Projects::IssuesController < Projects::ApplicationController
params[:issue] ||= ActionController::Parameters.new(
assignee_id: ""
)
- build_params = issue_params.merge(merge_request_for_resolving_discussions: merge_request_for_resolving_discussions)
- @issue = @noteable = Issues::BuildService.new(project, current_user, build_params).execute
+ build_params = issue_params.merge(
+ merge_request_to_resolve_discussions_of: params[:merge_request_to_resolve_discussions_of],
+ discussion_to_resolve: params[:discussion_to_resolve]
+ )
+ service = Issues::BuildService.new(project, current_user, build_params)
+
+ @issue = @noteable = service.execute
+ @merge_request_to_resolve_discussions_of = service.merge_request_to_resolve_discussions_of
+ @discussion_to_resolve = service.discussions_to_resolve.first if params[:discussion_to_resolve]
respond_with(@issue)
end
@@ -94,11 +101,21 @@ class Projects::IssuesController < Projects::ApplicationController
end
def create
- create_params = issue_params
- .merge(merge_request_for_resolving_discussions: merge_request_for_resolving_discussions)
- .merge(spammable_params)
+ create_params = issue_params.merge(spammable_params).merge(
+ merge_request_to_resolve_discussions_of: params[:merge_request_to_resolve_discussions_of],
+ discussion_to_resolve: params[:discussion_to_resolve]
+ )
+
+ service = Issues::CreateService.new(project, current_user, create_params)
+ @issue = service.execute
- @issue = Issues::CreateService.new(project, current_user, create_params).execute
+ if service.discussions_to_resolve.count(&:resolved?) > 0
+ flash[:notice] = if service.discussion_to_resolve_id
+ "Resolved 1 discussion."
+ else
+ "Resolved all discussions."
+ end
+ end
respond_to do |format|
format.html do
@@ -185,14 +202,6 @@ class Projects::IssuesController < Projects::ApplicationController
alias_method :awardable, :issue
alias_method :spammable, :issue
- def merge_request_for_resolving_discussions
- return unless merge_request_iid = params[:merge_request_for_resolving_discussions]
-
- @merge_request_for_resolving_discussions ||= MergeRequestsFinder.new(current_user, project_id: project.id).
- execute.
- find_by(iid: merge_request_iid)
- end
-
def authorize_read_issue!
return render_404 unless can?(current_user, :read_issue, @issue)
end
diff --git a/app/controllers/projects/raw_controller.rb b/app/controllers/projects/raw_controller.rb
index 10d24da16d7..c55b37ae0dd 100644
--- a/app/controllers/projects/raw_controller.rb
+++ b/app/controllers/projects/raw_controller.rb
@@ -15,7 +15,7 @@ class Projects::RawController < Projects::ApplicationController
return if cached_blob?
- if @blob.lfs_pointer?
+ if @blob.lfs_pointer? && project.lfs_enabled?
send_lfs_object
else
send_git_blob @repository, @blob
diff --git a/app/controllers/projects/services_controller.rb b/app/controllers/projects/services_controller.rb
index 17cb1d5be24..f9d798d0455 100644
--- a/app/controllers/projects/services_controller.rb
+++ b/app/controllers/projects/services_controller.rb
@@ -13,7 +13,8 @@ class Projects::ServicesController < Projects::ApplicationController
end
def update
- if @service.update_attributes(service_params[:service])
+ @service.assign_attributes(service_params[:service])
+ if @service.save(context: :manual_change)
redirect_to(
edit_namespace_project_service_path(@project.namespace, @project, @service.to_param),
notice: 'Successfully updated.'
diff --git a/app/controllers/projects/settings/members_controller.rb b/app/controllers/projects/settings/members_controller.rb
index 5735e281f66..cbfa2afa959 100644
--- a/app/controllers/projects/settings/members_controller.rb
+++ b/app/controllers/projects/settings/members_controller.rb
@@ -7,47 +7,18 @@ module Projects
@sort = params[:sort].presence || sort_value_name
@group_links = @project.project_group_links
- @project_members = @project.project_members
- @project_members = @project_members.non_invite unless can?(current_user, :admin_project, @project)
-
- group = @project.group
-
- # group links
- @group_links = @project.project_group_links.all
-
@skip_groups = @group_links.pluck(:group_id)
@skip_groups << @project.namespace_id unless @project.personal?
- if group
- # We need `.where.not(user_id: nil)` here otherwise when a group has an
- # invitee, it would make the following query return 0 rows since a NULL
- # user_id would be present in the subquery
- # See http://stackoverflow.com/questions/129077/not-in-clause-and-null-values
- group_members = MembersFinder.new(@project_members, group).execute(current_user)
- end
+ @project_members = MembersFinder.new(@project, current_user).execute
if params[:search].present?
- user_ids = @project.users.search(params[:search]).select(:id)
- @project_members = @project_members.where(user_id: user_ids)
-
- if group_members
- user_ids = group.users.search(params[:search]).select(:id)
- group_members = group_members.where(user_id: user_ids)
- end
-
- @group_links = @project.project_group_links.where(group_id: @project.invited_groups.search(params[:search]).select(:id))
+ @project_members = @project_members.joins(:user).merge(User.search(params[:search]))
+ @group_links = @group_links.where(group_id: @project.invited_groups.search(params[:search]).select(:id))
end
- wheres = ["members.id IN (#{@project_members.select(:id).to_sql})"]
- wheres << "members.id IN (#{group_members.select(:id).to_sql})" if group_members
-
- @project_members = Member.
- where(wheres.join(' OR ')).
- sort(@sort).
- page(params[:page])
-
+ @project_members = @project_members.sort(@sort).page(params[:page])
@requesters = AccessRequestsFinder.new(@project).execute(current_user)
-
@project_member = @project.project_members.new
end
end
diff --git a/app/controllers/projects/tags_controller.rb b/app/controllers/projects/tags_controller.rb
index 33379659d73..ea7e4d9f663 100644
--- a/app/controllers/projects/tags_controller.rb
+++ b/app/controllers/projects/tags_controller.rb
@@ -41,13 +41,27 @@ class Projects::TagsController < Projects::ApplicationController
end
def destroy
- Tags::DestroyService.new(project, current_user).execute(params[:id])
+ result = Tags::DestroyService.new(project, current_user).execute(params[:id])
respond_to do |format|
- format.html do
- redirect_to namespace_project_tags_path(@project.namespace, @project)
+ if result[:status] == :success
+ format.html do
+ redirect_to namespace_project_tags_path(@project.namespace, @project)
+ end
+
+ format.js
+ else
+ @error = result[:message]
+
+ format.html do
+ redirect_to namespace_project_tags_path(@project.namespace, @project),
+ alert: @error
+ end
+
+ format.js do
+ render status: :unprocessable_entity
+ end
end
- format.js
end
end
end
diff --git a/app/controllers/projects/wikis_controller.rb b/app/controllers/projects/wikis_controller.rb
index 2d8064c9878..8b6c83d4fed 100644
--- a/app/controllers/projects/wikis_controller.rb
+++ b/app/controllers/projects/wikis_controller.rb
@@ -1,5 +1,3 @@
-require 'project_wiki'
-
class Projects::WikisController < Projects::ApplicationController
before_action :authorize_read_wiki!
before_action :authorize_create_wiki!, only: [:edit, :create, :history]
diff --git a/app/finders/group_members_finder.rb b/app/finders/group_members_finder.rb
index 9f2206346ce..fce3775f40e 100644
--- a/app/finders/group_members_finder.rb
+++ b/app/finders/group_members_finder.rb
@@ -1,4 +1,4 @@
-class GroupMembersFinder < Projects::ApplicationController
+class GroupMembersFinder
def initialize(group)
@group = group
end
diff --git a/app/finders/members_finder.rb b/app/finders/members_finder.rb
index 702944404f5..af24045886e 100644
--- a/app/finders/members_finder.rb
+++ b/app/finders/members_finder.rb
@@ -1,13 +1,35 @@
-class MembersFinder < Projects::ApplicationController
- def initialize(project_members, project_group)
- @project_members = project_members
- @project_group = project_group
+class MembersFinder
+ attr_reader :project, :current_user, :group
+
+ def initialize(project, current_user)
+ @project = project
+ @current_user = current_user
+ @group = project.group
+ end
+
+ def execute
+ project_members = project.project_members
+ project_members = project_members.non_invite unless can?(current_user, :admin_project, project)
+ wheres = ["members.id IN (#{project_members.select(:id).to_sql})"]
+
+ if group
+ # We need `.where.not(user_id: nil)` here otherwise when a group has an
+ # invitee, it would make the following query return 0 rows since a NULL
+ # user_id would be present in the subquery
+ # See http://stackoverflow.com/questions/129077/not-in-clause-and-null-values
+ non_null_user_ids = project_members.where.not(user_id: nil).select(:user_id)
+
+ group_members = GroupMembersFinder.new(group).execute
+ group_members = group_members.where.not(user_id: non_null_user_ids)
+ group_members = group_members.non_invite unless can?(current_user, :admin_group, group)
+
+ wheres << "members.id IN (#{group_members.select(:id).to_sql})"
+ end
+
+ Member.where(wheres.join(' OR '))
end
- def execute(current_user)
- non_null_user_ids = @project_members.where.not(user_id: nil).select(:user_id)
- group_members = @project_group.group_members.where.not(user_id: non_null_user_ids)
- group_members = group_members.non_invite unless can?(current_user, :admin_group, @project_group)
- group_members
+ def can?(*args)
+ Ability.allowed?(*args)
end
end
diff --git a/app/helpers/ci_status_helper.rb b/app/helpers/ci_status_helper.rb
index 2c2c408b035..a7cdca9ba2e 100644
--- a/app/helpers/ci_status_helper.rb
+++ b/app/helpers/ci_status_helper.rb
@@ -15,6 +15,8 @@ module CiStatusHelper
'passed'
when 'success_with_warnings'
'passed with warnings'
+ when 'manual'
+ 'waiting for manual action'
else
status
end
diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb
index c2b399041c6..aad83731b87 100644
--- a/app/helpers/issuables_helper.rb
+++ b/app/helpers/issuables_helper.rb
@@ -1,4 +1,6 @@
module IssuablesHelper
+ include GitlabRoutingHelper
+
def sidebar_gutter_toggle_icon
sidebar_gutter_collapsed? ? icon('angle-double-left', { 'aria-hidden': 'true' }) : icon('angle-double-right', { 'aria-hidden': 'true' })
end
@@ -95,8 +97,23 @@ module IssuablesHelper
h(milestone_title.presence || default_label)
end
+ def to_url_reference(issuable)
+ case issuable
+ when Issue
+ link_to issuable.to_reference, issue_url(issuable)
+ when MergeRequest
+ link_to issuable.to_reference, merge_request_url(issuable)
+ else
+ issuable.to_reference
+ end
+ end
+
def issuable_meta(issuable, project, text)
- output = content_tag :strong, "#{text} #{issuable.to_reference}", class: "identifier"
+ output = content_tag(:strong, class: "identifier") do
+ concat("#{text} ")
+ concat(to_url_reference(issuable))
+ end
+
output << " opened #{time_ago_with_tooltip(issuable.created_at)} by ".html_safe
output << content_tag(:strong) do
author_output = link_to_member(project, issuable.author, size: 24, mobile_classes: "hidden-xs", tooltip: true)
diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb
index 4bdf07fe1ad..6978b0c89fd 100644
--- a/app/helpers/issues_helper.rb
+++ b/app/helpers/issues_helper.rb
@@ -134,6 +134,20 @@ module IssuesHelper
options_from_collection_for_select(options, 'name', 'title', params[:due_date])
end
+ def link_to_discussions_to_resolve(merge_request, single_discussion = nil)
+ link_text = merge_request.to_reference
+ link_text += " (discussion #{single_discussion.first_note.id})" if single_discussion
+
+ path = if single_discussion
+ Gitlab::UrlBuilder.build(single_discussion.first_note)
+ else
+ project = merge_request.project
+ namespace_project_merge_request_path(project.namespace, project, merge_request)
+ end
+
+ link_to link_text, path
+ end
+
# Required for Banzai::Filter::IssueReferenceFilter
module_function :url_for_issue
end
diff --git a/app/helpers/preferences_helper.rb b/app/helpers/preferences_helper.rb
index 710218082f2..243ef39ef61 100644
--- a/app/helpers/preferences_helper.rb
+++ b/app/helpers/preferences_helper.rb
@@ -36,7 +36,7 @@ module PreferencesHelper
def project_view_choices
[
['Files and Readme (default)', :files],
- ['Activity view', :activity]
+ ['Activity', :activity]
]
end
diff --git a/app/models/ability.rb b/app/models/ability.rb
index ad6c588202e..f3692a5a067 100644
--- a/app/models/ability.rb
+++ b/app/models/ability.rb
@@ -56,15 +56,16 @@ class Ability
end
end
- def allowed?(user, action, subject)
+ def allowed?(user, action, subject = :global)
allowed(user, subject).include?(action)
end
- def allowed(user, subject)
+ def allowed(user, subject = :global)
+ return BasePolicy::RuleSet.none if subject.nil?
return uncached_allowed(user, subject) unless RequestStore.active?
user_key = user ? user.id : 'anonymous'
- subject_key = subject ? "#{subject.class.name}/#{subject.id}" : 'global'
+ subject_key = subject == :global ? 'global' : "#{subject.class.name}/#{subject.id}"
key = "/ability/#{user_key}/#{subject_key}"
RequestStore[key] ||= uncached_allowed(user, subject).freeze
end
diff --git a/app/models/blob.rb b/app/models/blob.rb
index ab92e820335..1376b86fdad 100644
--- a/app/models/blob.rb
+++ b/app/models/blob.rb
@@ -54,9 +54,13 @@ class Blob < SimpleDelegator
UploaderHelper::VIDEO_EXT.include?(extname.downcase.delete('.'))
end
- def to_partial_path
+ def to_partial_path(project)
if lfs_pointer?
- 'download'
+ if project.lfs_enabled?
+ 'download'
+ else
+ 'text'
+ end
elsif image? || svg?
'image'
elsif text?
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index 67206415f7b..8a5a9aa4adb 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -144,7 +144,7 @@ module Ci
status_sql = statuses.latest.where('stage=sg.stage').status_sql
- warnings_sql = statuses.latest.select('COUNT(*) > 0')
+ warnings_sql = statuses.latest.select('COUNT(*)')
.where('stage=sg.stage').failed_but_allowed.to_sql
stages_with_statuses = CommitStatus.from(stages_query, :sg)
diff --git a/app/models/ci/stage.rb b/app/models/ci/stage.rb
index ca74c91b062..e7d6b17d445 100644
--- a/app/models/ci/stage.rb
+++ b/app/models/ci/stage.rb
@@ -46,10 +46,10 @@ module Ci
end
def has_warnings?
- if @warnings.nil?
- statuses.latest.failed_but_allowed.any?
+ if @warnings.is_a?(Integer)
+ @warnings > 0
else
- @warnings
+ statuses.latest.failed_but_allowed.any?
end
end
end
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index 37c727b5d9f..3cf4c67d7e7 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -63,6 +63,7 @@ module Issuable
scope :authored, ->(user) { where(author_id: user) }
scope :assigned_to, ->(u) { where(assignee_id: u.id)}
scope :recent, -> { reorder(id: :desc) }
+ scope :order_position_asc, -> { reorder(position: :asc) }
scope :assigned, -> { where("assignee_id IS NOT NULL") }
scope :unassigned, -> { where("assignee_id IS NULL") }
scope :of_projects, ->(ids) { where(project_id: ids) }
@@ -144,6 +145,7 @@ module Issuable
when 'downvotes_desc' then order_downvotes_desc
when 'upvotes_desc' then order_upvotes_desc
when 'priority' then order_labels_priority(excluded_labels: excluded_labels)
+ when 'position_asc' then order_position_asc
else
order_by(method)
end
diff --git a/app/models/concerns/relative_positioning.rb b/app/models/concerns/relative_positioning.rb
index 603f2dd7e5d..f1d8532a6d6 100644
--- a/app/models/concerns/relative_positioning.rb
+++ b/app/models/concerns/relative_positioning.rb
@@ -2,16 +2,14 @@ module RelativePositioning
extend ActiveSupport::Concern
MIN_POSITION = 0
+ START_POSITION = Gitlab::Database::MAX_INT_VALUE / 2
MAX_POSITION = Gitlab::Database::MAX_INT_VALUE
+ IDEAL_DISTANCE = 500
included do
after_save :save_positionable_neighbours
end
- def min_relative_position
- self.class.in_projects(project.id).minimum(:relative_position)
- end
-
def max_relative_position
self.class.in_projects(project.id).maximum(:relative_position)
end
@@ -26,7 +24,7 @@ module RelativePositioning
maximum(:relative_position)
end
- prev_pos || MIN_POSITION
+ prev_pos
end
def next_relative_position
@@ -39,55 +37,95 @@ module RelativePositioning
minimum(:relative_position)
end
- next_pos || MAX_POSITION
+ next_pos
end
def move_between(before, after)
return move_after(before) unless after
return move_before(after) unless before
+ # If there is no place to insert an issue we need to create one by moving the before issue closer
+ # to its predecessor. This process will recursively move all the predecessors until we have a place
+ if (after.relative_position - before.relative_position) < 2
+ before.move_before
+ @positionable_neighbours = [before]
+ end
+
+ self.relative_position = position_between(before.relative_position, after.relative_position)
+ end
+
+ def move_after(before = self)
pos_before = before.relative_position
+ pos_after = before.next_relative_position
+
+ if before.shift_after?
+ issue_to_move = self.class.in_projects(project.id).find_by!(relative_position: pos_after)
+ issue_to_move.move_after
+ @positionable_neighbours = [issue_to_move]
+
+ pos_after = issue_to_move.relative_position
+ end
+
+ self.relative_position = position_between(pos_before, pos_after)
+ end
+
+ def move_before(after = self)
pos_after = after.relative_position
+ pos_before = after.prev_relative_position
- if pos_after && (pos_before == pos_after)
- self.relative_position = pos_before
- before.move_before(self)
- after.move_after(self)
+ if after.shift_before?
+ issue_to_move = self.class.in_projects(project.id).find_by!(relative_position: pos_before)
+ issue_to_move.move_before
+ @positionable_neighbours = [issue_to_move]
- @positionable_neighbours = [before, after]
- else
- self.relative_position = position_between(pos_before, pos_after)
+ pos_before = issue_to_move.relative_position
end
+
+ self.relative_position = position_between(pos_before, pos_after)
end
- def move_before(after)
- self.relative_position = position_between(after.prev_relative_position, after.relative_position)
+ def move_to_end
+ self.relative_position = position_between(max_relative_position || START_POSITION, MAX_POSITION)
end
- def move_after(before)
- self.relative_position = position_between(before.relative_position, before.next_relative_position)
+ # Indicates if there is an issue that should be shifted to free the place
+ def shift_after?
+ next_pos = next_relative_position
+ next_pos && (next_pos - relative_position) == 1
end
- def move_to_end
- self.relative_position = position_between(max_relative_position, MAX_POSITION)
+ # Indicates if there is an issue that should be shifted to free the place
+ def shift_before?
+ prev_pos = prev_relative_position
+ prev_pos && (relative_position - prev_pos) == 1
end
private
# This method takes two integer values (positions) and
- # calculates some random position between them. The range is huge as
- # the maximum integer value is 2147483647. Ideally, the calculated value would be
- # exactly between those terminating values, but this will introduce possibility of a race condition
- # so two or more issues can get the same value, we want to avoid that and we also want to avoid
- # using a lock here. If we have two issues with distance more than one thousand, we are OK.
- # Given the huge range of possible values that integer can fit we shoud never face a problem.
+ # calculates the position between them. The range is huge as
+ # the maximum integer value is 2147483647. We are incrementing position by IDEAL_DISTANCE * 2 every time
+ # when we have enough space. If distance is less then IDEAL_DISTANCE we are calculating an average number
def position_between(pos_before, pos_after)
pos_before ||= MIN_POSITION
pos_after ||= MAX_POSITION
pos_before, pos_after = [pos_before, pos_after].sort
- rand(pos_before.next..pos_after.pred)
+ halfway = (pos_after + pos_before) / 2
+ distance_to_halfway = pos_after - halfway
+
+ if distance_to_halfway < IDEAL_DISTANCE
+ halfway
+ else
+ if pos_before == MIN_POSITION
+ pos_after - IDEAL_DISTANCE
+ elsif pos_after == MAX_POSITION
+ pos_before + IDEAL_DISTANCE
+ else
+ halfway
+ end
+ end
end
def save_positionable_neighbours
diff --git a/app/models/global_milestone.rb b/app/models/global_milestone.rb
index b991d78e27f..0afbca2cb32 100644
--- a/app/models/global_milestone.rb
+++ b/app/models/global_milestone.rb
@@ -28,6 +28,28 @@ class GlobalMilestone
new(title, child_milestones)
end
+ def self.states_count(projects)
+ relation = MilestonesFinder.new.execute(projects, state: 'all')
+ milestones_by_state_and_title = relation.reorder(nil).group(:state, :title).count
+
+ opened = count_by_state(milestones_by_state_and_title, 'active')
+ closed = count_by_state(milestones_by_state_and_title, 'closed')
+ all = milestones_by_state_and_title.map { |(_, title), _| title }.uniq.count
+
+ {
+ opened: opened,
+ closed: closed,
+ all: all
+ }
+ end
+
+ def self.count_by_state(milestones_by_state_and_title, state)
+ milestones_by_state_and_title.count do |(milestone_state, _), _|
+ milestone_state == state
+ end
+ end
+ private_class_method :count_by_state
+
def initialize(title, milestones)
@title = title
@name = title
diff --git a/app/models/guest.rb b/app/models/guest.rb
index 01285ca1264..df287c277a7 100644
--- a/app/models/guest.rb
+++ b/app/models/guest.rb
@@ -1,6 +1,6 @@
class Guest
class << self
- def can?(action, subject)
+ def can?(action, subject = :global)
Ability.allowed?(nil, action, subject)
end
end
diff --git a/app/models/project_services/issue_tracker_service.rb b/app/models/project_services/issue_tracker_service.rb
index 9e65fdbf9d6..50435b67eda 100644
--- a/app/models/project_services/issue_tracker_service.rb
+++ b/app/models/project_services/issue_tracker_service.rb
@@ -1,4 +1,6 @@
class IssueTrackerService < Service
+ validate :one_issue_tracker, if: :activated?, on: :manual_change
+
default_value_for :category, 'issue_tracker'
# Pattern used to extract links from comments
@@ -92,4 +94,13 @@ class IssueTrackerService < Service
def issues_tracker
Gitlab.config.issues_tracker[to_param]
end
+
+ def one_issue_tracker
+ return if template?
+ return if project.blank?
+
+ if project.services.external_issue_trackers.where.not(id: id).any?
+ errors.add(:base, 'Another issue tracker is already in use. Only one issue tracker service can be active at a time')
+ end
+ end
end
diff --git a/app/models/user.rb b/app/models/user.rb
index 76fb4cd470e..39c1281179b 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -126,7 +126,6 @@ class User < ActiveRecord::Base
validate :unique_email, if: ->(user) { user.email_changed? }
validate :owns_notification_email, if: ->(user) { user.notification_email_changed? }
validate :owns_public_email, if: ->(user) { user.public_email_changed? }
- validate :ghost_users_must_be_blocked
validates :avatar, file_size: { maximum: 200.kilobytes.to_i }
before_validation :generate_password, on: :create
@@ -350,12 +349,27 @@ class User < ActiveRecord::Base
def ghost
unique_internal(where(ghost: true), 'ghost', 'ghost%s@example.com') do |u|
u.bio = 'This is a "Ghost User", created to hold all issues authored by users that have since been deleted. This user cannot be removed.'
- u.state = :blocked
u.name = 'Ghost User'
end
end
end
+ def self.internal_attributes
+ [:ghost]
+ end
+
+ def internal?
+ self.class.internal_attributes.any? { |a| self[a] }
+ end
+
+ def self.internal
+ where(Hash[internal_attributes.zip([true] * internal_attributes.size)])
+ end
+
+ def self.non_internal
+ where(Hash[internal_attributes.zip([[false, nil]] * internal_attributes.size)])
+ end
+
#
# Instance methods
#
@@ -452,12 +466,6 @@ class User < ActiveRecord::Base
errors.add(:public_email, "is not an email you own") unless all_emails.include?(public_email)
end
- def ghost_users_must_be_blocked
- if ghost? && !blocked?
- errors.add(:ghost, 'cannot be enabled for a user who is not blocked.')
- end
- end
-
def update_emails_with_primary_email
primary_email_record = emails.find_by(email: email)
if primary_email_record
@@ -563,14 +571,14 @@ class User < ActiveRecord::Base
end
def can_create_group?
- can?(:create_group, nil)
+ can?(:create_group)
end
def can_select_namespace?
several_namespaces? || admin
end
- def can?(action, subject)
+ def can?(action, subject = :global)
Ability.allowed?(self, action, subject)
end
@@ -955,6 +963,14 @@ class User < ActiveRecord::Base
self.admin = (new_level == 'admin')
end
+ protected
+
+ # override, from Devise::Validatable
+ def password_required?
+ return false if internal?
+ super
+ end
+
private
def ci_projects_union
@@ -1055,7 +1071,6 @@ class User < ActiveRecord::Base
scope.create(
username: username,
- password: Devise.friendly_token,
email: email,
&creation_block
)
diff --git a/app/policies/base_policy.rb b/app/policies/base_policy.rb
index e07b144355a..8890409d056 100644
--- a/app/policies/base_policy.rb
+++ b/app/policies/base_policy.rb
@@ -12,6 +12,10 @@ class BasePolicy
new(Set.new, Set.new)
end
+ def self.none
+ empty.freeze
+ end
+
def can?(ability)
@can_set.include?(ability) && !@cannot_set.include?(ability)
end
@@ -49,7 +53,8 @@ class BasePolicy
end
def self.class_for(subject)
- return GlobalPolicy if subject.nil?
+ return GlobalPolicy if subject == :global
+ raise ArgumentError, 'no policy for nil' if subject.nil?
if subject.class.try(:presenter?)
subject = subject.subject
@@ -79,7 +84,7 @@ class BasePolicy
end
def abilities
- return RuleSet.empty if @user && @user.blocked?
+ return RuleSet.none if @user && @user.blocked?
return anonymous_abilities if @user.nil?
collect_rules { rules }
end
diff --git a/app/policies/global_policy.rb b/app/policies/global_policy.rb
index 3c2fbe6b56b..cb72c2b4590 100644
--- a/app/policies/global_policy.rb
+++ b/app/policies/global_policy.rb
@@ -4,5 +4,12 @@ class GlobalPolicy < BasePolicy
can! :create_group if @user.can_create_group
can! :read_users_list
+
+ unless @user.blocked? || @user.internal?
+ can! :log_in unless @user.access_locked?
+ can! :access_api
+ can! :access_git
+ can! :receive_notifications
+ end
end
end
diff --git a/app/services/concerns/issues/resolve_discussions.rb b/app/services/concerns/issues/resolve_discussions.rb
new file mode 100644
index 00000000000..297c7d696c3
--- /dev/null
+++ b/app/services/concerns/issues/resolve_discussions.rb
@@ -0,0 +1,32 @@
+module Issues
+ module ResolveDiscussions
+ attr_reader :merge_request_to_resolve_discussions_of_iid, :discussion_to_resolve_id
+
+ def filter_resolve_discussion_params
+ @merge_request_to_resolve_discussions_of_iid ||= params.delete(:merge_request_to_resolve_discussions_of)
+ @discussion_to_resolve_id ||= params.delete(:discussion_to_resolve)
+ end
+
+ def merge_request_to_resolve_discussions_of
+ return @merge_request_to_resolve_discussions_of if defined?(@merge_request_to_resolve_discussions_of)
+
+ @merge_request_to_resolve_discussions_of = MergeRequestsFinder.new(current_user, project_id: project.id).
+ execute.
+ find_by(iid: merge_request_to_resolve_discussions_of_iid)
+ end
+
+ def discussions_to_resolve
+ return [] unless merge_request_to_resolve_discussions_of
+
+ @discussions_to_resolve ||=
+ if discussion_to_resolve_id
+ discussion_or_nil = merge_request_to_resolve_discussions_of
+ .find_diff_discussion(discussion_to_resolve_id)
+ Array(discussion_or_nil)
+ else
+ merge_request_to_resolve_discussions_of
+ .resolvable_discussions
+ end
+ end
+ end
+end
diff --git a/app/services/issues/base_service.rb b/app/services/issues/base_service.rb
index 35af867a098..ee1b40db718 100644
--- a/app/services/issues/base_service.rb
+++ b/app/services/issues/base_service.rb
@@ -1,13 +1,5 @@
module Issues
class BaseService < ::IssuableBaseService
- attr_reader :merge_request_for_resolving_discussions
-
- def initialize(*args)
- super
-
- @merge_request_for_resolving_discussions ||= params.delete(:merge_request_for_resolving_discussions)
- end
-
def hook_data(issue, action)
issue_data = issue.to_hook_data(current_user)
issue_url = Gitlab::UrlBuilder.build(issue)
diff --git a/app/services/issues/build_service.rb b/app/services/issues/build_service.rb
index 7cd927d8005..77bced4bd5c 100644
--- a/app/services/issues/build_service.rb
+++ b/app/services/issues/build_service.rb
@@ -1,50 +1,56 @@
module Issues
class BuildService < Issues::BaseService
+ include ResolveDiscussions
+
def execute
+ filter_resolve_discussion_params
@issue = project.issues.new(issue_params)
end
- def issue_params_with_info_from_merge_request
- return {} unless merge_request_for_resolving_discussions
+ def issue_params_with_info_from_discussions
+ return {} unless merge_request_to_resolve_discussions_of
- { title: title_from_merge_request, description: description_from_merge_request }
+ { title: title_from_merge_request, description: description_for_discussions }
end
def title_from_merge_request
- "Follow-up from \"#{merge_request_for_resolving_discussions.title}\""
+ "Follow-up from \"#{merge_request_to_resolve_discussions_of.title}\""
end
- def description_from_merge_request
- if merge_request_for_resolving_discussions.resolvable_discussions.empty?
+ def description_for_discussions
+ if discussions_to_resolve.empty?
return "There are no unresolved discussions. "\
- "Review the conversation in #{merge_request_for_resolving_discussions.to_reference}"
+ "Review the conversation in #{merge_request_to_resolve_discussions_of.to_reference}"
end
- description = "The following discussions from #{merge_request_for_resolving_discussions.to_reference} should be addressed:"
+ description = "The following #{'discussion'.pluralize(discussions_to_resolve.size)} "\
+ "from #{merge_request_to_resolve_discussions_of.to_reference} "\
+ "should be addressed:"
+
[description, *items_for_discussions].join("\n\n")
end
def items_for_discussions
- merge_request_for_resolving_discussions.resolvable_discussions.map { |discussion| item_for_discussion(discussion) }
+ discussions_to_resolve.map { |discussion| item_for_discussion(discussion) }
end
def item_for_discussion(discussion)
- first_note = discussion.first_note_to_resolve
+ first_note = discussion.first_note_to_resolve || discussion.first_note
other_note_count = discussion.notes.size - 1
- creation_time = first_note.created_at.to_s(:medium)
note_url = Gitlab::UrlBuilder.build(first_note)
- discussion_info = "- [ ] #{first_note.author.to_reference} commented in a discussion on [#{creation_time}](#{note_url}): "
+ discussion_info = "- [ ] #{first_note.author.to_reference} commented on a [discussion](#{note_url}): "
discussion_info << " (+#{other_note_count} #{'comment'.pluralize(other_note_count)})" if other_note_count > 0
note_without_block_quotes = Banzai::Filter::BlockquoteFenceFilter.new(first_note.note).call
- quote = ">>>\n#{note_without_block_quotes}\n>>>"
+ spaces = ' ' * 4
+ quote = note_without_block_quotes.lines.map { |line| "#{spaces}> #{line}" }.join
[discussion_info, quote].join("\n\n")
end
def issue_params
- @issue_params ||= issue_params_with_info_from_merge_request.merge(whitelisted_issue_params)
+ @issue_params ||= issue_params_with_info_from_discussions.merge(whitelisted_issue_params)
end
def whitelisted_issue_params
diff --git a/app/services/issues/create_service.rb b/app/services/issues/create_service.rb
index 85b6eb3fe3d..3cf4b82b9f2 100644
--- a/app/services/issues/create_service.rb
+++ b/app/services/issues/create_service.rb
@@ -1,12 +1,13 @@
module Issues
class CreateService < Issues::BaseService
include SpamCheckService
+ include ResolveDiscussions
def execute
- filter_spam_check_params
+ @issue = BuildService.new(project, current_user, params).execute
- issue_attributes = params.merge(merge_request_for_resolving_discussions: merge_request_for_resolving_discussions)
- @issue = BuildService.new(project, current_user, issue_attributes).execute
+ filter_spam_check_params
+ filter_resolve_discussion_params
create(@issue)
end
@@ -21,17 +22,16 @@ module Issues
notification_service.new_issue(issuable, current_user)
todo_service.new_issue(issuable, current_user)
user_agent_detail_service.create
-
- if merge_request_for_resolving_discussions.try(:discussions_can_be_resolved_by?, current_user)
- resolve_discussions_in_merge_request(issuable)
- end
+ resolve_discussions_with_issue(issuable)
end
- def resolve_discussions_in_merge_request(issue)
+ def resolve_discussions_with_issue(issue)
+ return if discussions_to_resolve.empty?
+
Discussions::ResolveService.new(project, current_user,
- merge_request: merge_request_for_resolving_discussions,
+ merge_request: merge_request_to_resolve_discussions_of,
follow_up_issue: issue).
- execute(merge_request_for_resolving_discussions.resolvable_discussions)
+ execute(discussions_to_resolve)
end
private
diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb
index fbad85d310e..d12692ecc90 100644
--- a/app/services/notification_service.rb
+++ b/app/services/notification_service.rb
@@ -465,7 +465,7 @@ class NotificationService
end
users = users.to_a.compact.uniq
- users = users.reject(&:blocked?)
+ users = users.select { |u| u.can?(:receive_notifications) }
users.reject do |user|
global_notification_setting = user.global_notification_setting
diff --git a/app/services/tags/destroy_service.rb b/app/services/tags/destroy_service.rb
index 910b4f5e361..a368f4f5b61 100644
--- a/app/services/tags/destroy_service.rb
+++ b/app/services/tags/destroy_service.rb
@@ -21,6 +21,8 @@ module Tags
else
error('Failed to remove tag')
end
+ rescue GitHooksService::PreReceiveError => ex
+ error(ex.message)
end
def error(message, return_code = 400)
diff --git a/app/validators/namespace_validator.rb b/app/validators/namespace_validator.rb
index 03921db6947..77ca033e97f 100644
--- a/app/validators/namespace_validator.rb
+++ b/app/validators/namespace_validator.rb
@@ -36,7 +36,8 @@ class NamespaceValidator < ActiveModel::EachValidator
].freeze
WILDCARD_ROUTES = %w[tree commits wikis new edit create update logs_tree
- preview blob blame raw files create_dir find_file].freeze
+ preview blob blame raw files create_dir find_file
+ artifacts graphs refs badges].freeze
STRICT_RESERVED = (RESERVED + WILDCARD_ROUTES).freeze
diff --git a/app/views/admin/users/_head.html.haml b/app/views/admin/users/_head.html.haml
index d20be373564..be41c33b853 100644
--- a/app/views/admin/users/_head.html.haml
+++ b/app/views/admin/users/_head.html.haml
@@ -2,11 +2,13 @@
= @user.name
- if @user.blocked?
%span.cred (Blocked)
+ - if @user.internal?
+ %span.cred (Internal)
- if @user.admin
%span.cred (Admin)
.pull-right
- - unless @user == current_user || @user.blocked?
+ - if @user != current_user && @user.can?(:log_in)
= link_to 'Impersonate', impersonate_admin_user_path(@user), method: :post, class: "btn btn-nr btn-grouped btn-info"
= link_to edit_admin_user_path(@user), class: "btn btn-nr btn-grouped" do
%i.fa.fa-pencil-square-o
diff --git a/app/views/dashboard/issues.atom.builder b/app/views/dashboard/issues.atom.builder
index bdea1064096..06fb531b546 100644
--- a/app/views/dashboard/issues.atom.builder
+++ b/app/views/dashboard/issues.atom.builder
@@ -4,7 +4,7 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear
xml.link href: url_for(params), rel: "self", type: "application/atom+xml"
xml.link href: issues_dashboard_url, rel: "alternate", type: "text/html"
xml.id issues_dashboard_url
- xml.updated @issues.first.created_at.xmlschema if @issues.reorder(nil).any?
+ xml.updated @issues.first.updated_at.xmlschema if @issues.reorder(nil).any?
xml << render(partial: 'issues/issue', collection: @issues) if @issues.reorder(nil).any?
end
diff --git a/app/views/dashboard/issues.html.haml b/app/views/dashboard/issues.html.haml
index 9a4e423f896..10867140d4f 100644
--- a/app/views/dashboard/issues.html.haml
+++ b/app/views/dashboard/issues.html.haml
@@ -6,10 +6,8 @@
.top-area
= render 'shared/issuable/nav', type: :issues
.nav-controls
- = link_to params.merge(rss_url_options), class: 'btn' do
+ = link_to params.merge(rss_url_options), class: 'btn has-tooltip', title: 'Subscribe' do
= icon('rss')
- %span.icon-label
- Subscribe
= render 'shared/new_project_item_select', path: 'issues/new', label: "New Issue"
= render 'shared/issuable/filter', type: :issues
diff --git a/app/views/dashboard/milestones/index.html.haml b/app/views/dashboard/milestones/index.html.haml
index 917bfbd47e9..505b475f55b 100644
--- a/app/views/dashboard/milestones/index.html.haml
+++ b/app/views/dashboard/milestones/index.html.haml
@@ -1,11 +1,11 @@
-- page_title "Milestones"
-- header_title "Milestones", dashboard_milestones_path
+- page_title 'Milestones'
+- header_title 'Milestones', dashboard_milestones_path
.top-area
- = render 'shared/milestones_filter'
+ = render 'shared/milestones_filter', counts: @milestone_states
.nav-controls
- = render 'shared/new_project_item_select', path: 'milestones/new', label: "New Milestone", include_groups: true
+ = render 'shared/new_project_item_select', path: 'milestones/new', label: 'New Milestone', include_groups: true
.milestones
%ul.content-list
@@ -15,4 +15,4 @@
- else
- @milestones.each do |milestone|
= render 'milestone', milestone: milestone
- = paginate @milestones, theme: "gitlab"
+ = paginate @milestones, theme: 'gitlab'
diff --git a/app/views/dashboard/projects/starred.html.haml b/app/views/dashboard/projects/starred.html.haml
index 70705923d42..162ae153b1c 100644
--- a/app/views/dashboard/projects/starred.html.haml
+++ b/app/views/dashboard/projects/starred.html.haml
@@ -6,7 +6,7 @@
- if @last_push
= render "events/event_last_push", event: @last_push
-- if @projects.any?
+- if @projects.any? || params[:filter_projects]
= render 'projects'
- else
%h3 You don't have starred projects yet
diff --git a/app/views/dashboard/todos/_todo.html.haml b/app/views/dashboard/todos/_todo.html.haml
index a3993d5ef16..388190642aa 100644
--- a/app/views/dashboard/todos/_todo.html.haml
+++ b/app/views/dashboard/todos/_todo.html.haml
@@ -42,3 +42,8 @@
= link_to restore_dashboard_todo_path(todo), method: :patch, class: 'btn btn-loading js-undo-todo hidden' do
Undo
= icon('spinner spin')
+ - else
+ .todo-actions
+ = link_to restore_dashboard_todo_path(todo), method: :patch, class: 'btn btn-loading js-add-todo' do
+ Add todo
+ = icon('spinner spin')
diff --git a/app/views/devise/sessions/two_factor.html.haml b/app/views/devise/sessions/two_factor.html.haml
index 951f03083bf..a039756c7e2 100644
--- a/app/views/devise/sessions/two_factor.html.haml
+++ b/app/views/devise/sessions/two_factor.html.haml
@@ -1,6 +1,6 @@
- if inject_u2f_api?
- content_for :page_specific_javascripts do
- = page_specific_javascript_tag('u2f.js')
+ = page_specific_javascript_bundle_tag('u2f')
%div
= render 'devise/shared/tab_single', tab_title: 'Two-Factor Authentication'
diff --git a/app/views/devise/shared/_signup_box.html.haml b/app/views/devise/shared/_signup_box.html.haml
index 30e63d991bb..a2f6a7ab1cb 100644
--- a/app/views/devise/shared/_signup_box.html.haml
+++ b/app/views/devise/shared/_signup_box.html.haml
@@ -4,7 +4,7 @@
.devise-errors
= devise_error_messages!
.form-group
- = f.label :name
+ = f.label :name, 'Full name'
= f.text_field :name, class: "form-control top", required: true, title: "This field is required."
.username.form-group
= f.label :username
diff --git a/app/views/discussions/_diff_discussion.html.haml b/app/views/discussions/_diff_discussion.html.haml
index 2deadbeeceb..ee452add394 100644
--- a/app/views/discussions/_diff_discussion.html.haml
+++ b/app/views/discussions/_diff_discussion.html.haml
@@ -2,5 +2,5 @@
%tr.notes_holder{ class: ('hide' unless expanded) }
%td.notes_line{ colspan: 2 }
%td.notes_content
- .content
+ .content{ class: ('hide' unless expanded) }
= render "discussions/notes", discussion: discussion
diff --git a/app/views/discussions/_new_issue_for_all_discussions.html.haml b/app/views/discussions/_new_issue_for_all_discussions.html.haml
new file mode 100644
index 00000000000..ca9e0e8728a
--- /dev/null
+++ b/app/views/discussions/_new_issue_for_all_discussions.html.haml
@@ -0,0 +1,6 @@
+- if merge_request.discussions_can_be_resolved_by?(current_user) && can?(current_user, :create_issue, @project)
+ .btn-group{ role: "group", "v-if" => "unresolvedDiscussionCount > 0" }
+ .btn.btn-default.discussion-create-issue-btn.has-tooltip{ title: "Resolve all discussions in new issue",
+ "aria-label" => "Resolve all discussions in a new issue",
+ "data-container" => "body" }
+ = link_to custom_icon('icon_mr_issue'), new_namespace_project_issue_path(@project.namespace, @project, merge_request_to_resolve_discussions_of: merge_request.iid), title: "Resolve all discussions in new issue", class: 'new-issue-for-discussion'
diff --git a/app/views/discussions/_new_issue_for_discussion.html.haml b/app/views/discussions/_new_issue_for_discussion.html.haml
new file mode 100644
index 00000000000..df5546a1e32
--- /dev/null
+++ b/app/views/discussions/_new_issue_for_discussion.html.haml
@@ -0,0 +1,8 @@
+- if discussion.can_resolve?(current_user) && can?(current_user, :create_issue, @project)
+ %new-issue-for-discussion-btn{ ":discussion-id" => "'#{discussion.id}'",
+ "inline-template" => true }
+ .btn-group{ role: "group", "v-if" => "showButton" }
+ .btn.btn-default.discussion-create-issue-btn.has-tooltip{ title: "Resolve this discussion in a new issue",
+ "aria-label" => "Resolve this discussion in a new issue",
+ "data-container" => "body" }
+ = link_to custom_icon('icon_mr_issue'), new_namespace_project_issue_path(@project.namespace, @project, merge_request_to_resolve_discussions_of: merge_request.iid, discussion_to_resolve: discussion.id), title: "Resolve this discussion in a new issue", class: 'new-issue-for-discussion'
diff --git a/app/views/discussions/_notes.html.haml b/app/views/discussions/_notes.html.haml
index dfdbdf1f969..2789391819c 100644
--- a/app/views/discussions/_notes.html.haml
+++ b/app/views/discussions/_notes.html.haml
@@ -11,6 +11,8 @@
= link_to_reply_discussion(discussion, line_type)
= render "discussions/resolve_all", discussion: discussion
- if discussion.for_merge_request?
- = render "discussions/jump_to_next", discussion: discussion
+ .btn-group.discussion-actions
+ = render "discussions/new_issue_for_discussion", discussion: discussion, merge_request: discussion.noteable
+ = render "discussions/jump_to_next", discussion: discussion
- else
= link_to_reply_discussion(discussion)
diff --git a/app/views/events/_event.atom.builder b/app/views/events/_event.atom.builder
index 7890e717aa7..43a52cf3002 100644
--- a/app/views/events/_event.atom.builder
+++ b/app/views/events/_event.atom.builder
@@ -4,7 +4,7 @@ xml.entry do
xml.id "tag:#{request.host},#{event.created_at.strftime("%Y-%m-%d")}:#{event.id}"
xml.link href: event_feed_url(event)
xml.title truncate(event_feed_title(event), length: 80)
- xml.updated event.created_at.xmlschema
+ xml.updated event.updated_at.xmlschema
xml.media :thumbnail, width: "40", height: "40", url: image_url(avatar_icon(event.author_email))
xml.author do
diff --git a/app/views/groups/issues.atom.builder b/app/views/groups/issues.atom.builder
index 0cc6466d34e..469768d83f2 100644
--- a/app/views/groups/issues.atom.builder
+++ b/app/views/groups/issues.atom.builder
@@ -4,7 +4,7 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear
xml.link href: url_for(params), rel: "self", type: "application/atom+xml"
xml.link href: issues_group_url, rel: "alternate", type: "text/html"
xml.id issues_group_url
- xml.updated @issues.first.created_at.xmlschema if @issues.reorder(nil).any?
+ xml.updated @issues.first.updated_at.xmlschema if @issues.reorder(nil).any?
xml << render(partial: 'issues/issue', collection: @issues) if @issues.reorder(nil).any?
end
diff --git a/app/views/groups/milestones/index.html.haml b/app/views/groups/milestones/index.html.haml
index 644895c56a1..6893168f039 100644
--- a/app/views/groups/milestones/index.html.haml
+++ b/app/views/groups/milestones/index.html.haml
@@ -2,7 +2,7 @@
= render "groups/head_issues"
.top-area
- = render 'shared/milestones_filter'
+ = render 'shared/milestones_filter', counts: @milestone_states
.nav-controls
- if can?(current_user, :admin_milestones, @group)
diff --git a/app/views/help/ui.html.haml b/app/views/help/ui.html.haml
index 87f9b503989..1fb2c6271ad 100644
--- a/app/views/help/ui.html.haml
+++ b/app/views/help/ui.html.haml
@@ -410,7 +410,7 @@
:javascript
$('#js-project-dropdown').glDropdown({
data: function (term, callback) {
- Api.projects(term, "last_activity_at", function (data) {
+ Api.projects(term, { order_by: 'last_activity_at' }, function (data) {
callback(data);
});
},
diff --git a/app/views/issues/_issue.atom.builder b/app/views/issues/_issue.atom.builder
index 96831874144..fcd30c8c765 100644
--- a/app/views/issues/_issue.atom.builder
+++ b/app/views/issues/_issue.atom.builder
@@ -2,7 +2,7 @@ xml.entry do
xml.id namespace_project_issue_url(issue.project.namespace, issue.project, issue)
xml.link href: namespace_project_issue_url(issue.project.namespace, issue.project, issue)
xml.title truncate(issue.title, length: 80)
- xml.updated issue.created_at.xmlschema
+ xml.updated issue.updated_at.xmlschema
xml.media :thumbnail, width: "40", height: "40", url: image_url(avatar_icon(issue.author_email))
xml.author do
diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml
index f351e7feac9..299dace3406 100644
--- a/app/views/layouts/nav/_project.html.haml
+++ b/app/views/layouts/nav/_project.html.haml
@@ -1,20 +1,4 @@
-- if current_user
- .controls
- .dropdown.project-settings-dropdown
- %a.dropdown-new.btn.btn-default#project-settings-button{ href: '#', 'data-toggle' => 'dropdown' }
- = icon('cog')
- = icon('caret-down')
- %ul.dropdown-menu.dropdown-menu-align-right
- - can_edit = can?(current_user, :admin_project, @project)
-
- = render 'layouts/nav/project_settings', can_edit: can_edit
-
- - if can_edit
- %li.divider
- %li
- = link_to edit_project_path(@project) do
- Edit Project
-
+- can_edit = can?(current_user, :admin_project, @project)
.scrolling-tabs-container{ class: nav_control_class }
.fade-left
= icon('angle-left')
@@ -71,6 +55,17 @@
%span
Snippets
+ - if project_nav_tab? :settings
+ = nav_link(path: %w[projects#edit members#show integrations#show repository#show ci_cd#show pages#show]) do
+ = link_to edit_project_path(@project), title: 'Settings', class: 'shortcuts-tree' do
+ %span
+ Settings
+ - else
+ = nav_link(path: %w[members#show]) do
+ = link_to namespace_project_settings_members_path(@project.namespace, @project), title: 'Settings', class: 'shortcuts-tree' do
+ %span
+ Settings
+
-# Shortcut to Project > Activity
%li.hidden
= link_to activity_project_path(@project), title: 'Activity', class: 'shortcuts-project-activity' do
diff --git a/app/views/layouts/nav/_project_settings.html.haml b/app/views/layouts/nav/_project_settings.html.haml
deleted file mode 100644
index 6f2777d1be6..00000000000
--- a/app/views/layouts/nav/_project_settings.html.haml
+++ /dev/null
@@ -1,24 +0,0 @@
-- if project_nav_tab? :team
- = nav_link(controller: [:members, :teams]) do
- = link_to namespace_project_settings_members_path(@project.namespace, @project), title: 'Members', class: 'team-tab tab' do
- %span
- Members
-- if can_edit
- = nav_link(controller: :repository) do
- = link_to namespace_project_settings_repository_path(@project.namespace, @project), title: 'Repository' do
- %span
- Repository
- = nav_link(controller: :integrations) do
- = link_to namespace_project_settings_integrations_path(@project.namespace, @project), title: 'Integrations' do
- %span
- Integrations
-
- - if @project.feature_available?(:builds, current_user)
- = nav_link(controller: :ci_cd) do
- = link_to namespace_project_settings_ci_cd_path(@project.namespace, @project), title: 'CI/CD Pipelines' do
- %span
- CI/CD Pipelines
- = nav_link(controller: :pages) do
- = link_to namespace_project_pages_path(@project.namespace, @project), title: 'Pages', data: {placement: 'right'} do
- %span
- Pages
diff --git a/app/views/profiles/two_factor_auths/show.html.haml b/app/views/profiles/two_factor_auths/show.html.haml
index 558a1d56151..7ade5f00d47 100644
--- a/app/views/profiles/two_factor_auths/show.html.haml
+++ b/app/views/profiles/two_factor_auths/show.html.haml
@@ -4,7 +4,7 @@
- if inject_u2f_api?
- content_for :page_specific_javascripts do
- = page_specific_javascript_tag('u2f.js')
+ = page_specific_javascript_bundle_tag('u2f')
.row.prepend-top-default
.col-lg-3
@@ -96,4 +96,3 @@
:javascript
var button = "<a class='btn btn-xs btn-warning pull-right' data-method='patch' href='#{skip_profile_two_factor_auth_path}'>Configure it later</a>";
$(".flash-alert").append(button);
-
diff --git a/app/views/projects/_activity.html.haml b/app/views/projects/_activity.html.haml
index fb990dd9592..aa0cb3e1a50 100644
--- a/app/views/projects/_activity.html.haml
+++ b/app/views/projects/_activity.html.haml
@@ -1,5 +1,4 @@
- @no_container = true
-= render "projects/head"
%div{ class: container_class }
.nav-block.activity-filter-block
diff --git a/app/views/projects/activity.html.haml b/app/views/projects/activity.html.haml
index 3c0f01cbf6f..27c8e3c7fca 100644
--- a/app/views/projects/activity.html.haml
+++ b/app/views/projects/activity.html.haml
@@ -1,4 +1,5 @@
- page_title "Activity"
+= render "projects/head"
= render 'projects/last_push'
diff --git a/app/views/projects/blob/_actions.html.haml b/app/views/projects/blob/_actions.html.haml
index c44d8fcd430..14d42f7d9ec 100644
--- a/app/views/projects/blob/_actions.html.haml
+++ b/app/views/projects/blob/_actions.html.haml
@@ -12,7 +12,7 @@
class: 'btn btn-sm'
- else
= link_to 'Blame', namespace_project_blame_path(@project.namespace, @project, @id),
- class: 'btn btn-sm' unless @blob.empty?
+ class: 'btn btn-sm js-blob-blame-link' unless @blob.empty?
= link_to 'History', namespace_project_commits_path(@project.namespace, @project, @id),
class: 'btn btn-sm'
= link_to 'Permalink', namespace_project_blob_path(@project.namespace, @project,
diff --git a/app/views/projects/blob/_blob.html.haml b/app/views/projects/blob/_blob.html.haml
index 24ff74ecb3b..bf8801bb1e3 100644
--- a/app/views/projects/blob/_blob.html.haml
+++ b/app/views/projects/blob/_blob.html.haml
@@ -33,4 +33,4 @@
= number_to_human_size(blob_size(blob))
.file-actions.hidden-xs
= render "actions"
- = render blob, blob: blob
+ = render blob.to_partial_path(@project), blob: blob
diff --git a/app/views/projects/commit/_change.html.haml b/app/views/projects/commit/_change.html.haml
index 2ebd4f9069a..b5f67cae341 100644
--- a/app/views/projects/commit/_change.html.haml
+++ b/app/views/projects/commit/_change.html.haml
@@ -37,4 +37,4 @@
= commit_in_fork_help
:javascript
- new NewCommitForm($('.js-#{type}-form'))
+ new NewCommitForm($('.js-#{type}-form'), 'start_branch')
diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml
index 4d0b7a5ca85..d001e01609a 100644
--- a/app/views/projects/commit/_commit_box.html.haml
+++ b/app/views/projects/commit/_commit_box.html.haml
@@ -34,8 +34,9 @@
= revert_commit_link(@commit, namespace_project_commit_path(@project.namespace, @project, @commit.id), has_tooltip: false)
%li.clearfix
= cherry_pick_commit_link(@commit, namespace_project_commit_path(@project.namespace, @project, @commit.id), has_tooltip: false)
- %li.clearfix
- = link_to "Tag", new_namespace_project_tag_path(@project.namespace, @project, ref: @commit)
+ - if can_collaborate_with_project?
+ %li.clearfix
+ = link_to "Tag", new_namespace_project_tag_path(@project.namespace, @project, ref: @commit)
%li.divider
%li.dropdown-header
Download
diff --git a/app/views/projects/deploy_keys/_index.html.haml b/app/views/projects/deploy_keys/_index.html.haml
index 0cbe9b3275a..4cfbd9add00 100644
--- a/app/views/projects/deploy_keys/_index.html.haml
+++ b/app/views/projects/deploy_keys/_index.html.haml
@@ -3,7 +3,7 @@
%h4.prepend-top-0
Deploy Keys
%p
- Deploy keys allow read-only access to your repository. Deploy keys can be used for CI, staging or production servers. You can create a deploy key or add an existing one.
+ Deploy keys allow read-only or read-write (if enabled) access to your repository. Deploy keys can be used for CI, staging or production servers. You can create a deploy key or add an existing one.
.col-lg-9
%h5.prepend-top-0
Create a new deploy key for this project
diff --git a/app/views/projects/deployments/_actions.haml b/app/views/projects/deployments/_actions.haml
index a680b1ca017..506246f2ee6 100644
--- a/app/views/projects/deployments/_actions.haml
+++ b/app/views/projects/deployments/_actions.haml
@@ -1,9 +1,9 @@
- if can?(current_user, :create_deployment, deployment)
- actions = deployment.manual_actions
- if actions.present?
- .inline
+ .btn-group
.dropdown
- %a.dropdown-new.btn.btn-default{ type: 'button', 'data-toggle' => 'dropdown' }
+ %button.dropdown.dropdown-new.btn.btn-default{ type: 'button', 'data-toggle' => 'dropdown' }
= custom_icon('icon_play')
= icon('caret-down')
%ul.dropdown-menu.dropdown-menu-align-right
@@ -12,4 +12,3 @@
= link_to [:play, @project.namespace.becomes(Namespace), @project, action], method: :post, rel: 'nofollow' do
= custom_icon('icon_play')
%span= action.name.humanize
-
diff --git a/app/views/projects/deployments/_deployment.html.haml b/app/views/projects/deployments/_deployment.html.haml
index c468202569f..260c9023daf 100644
--- a/app/views/projects/deployments/_deployment.html.haml
+++ b/app/views/projects/deployments/_deployment.html.haml
@@ -17,6 +17,6 @@
#{time_ago_with_tooltip(deployment.created_at)}
%td.hidden-xs
- .pull-right
+ .pull-right.btn-group
= render 'projects/deployments/actions', deployment: deployment
= render 'projects/deployments/rollback', deployment: deployment
diff --git a/app/views/projects/diffs/_line.html.haml b/app/views/projects/diffs/_line.html.haml
index cd18ba2ed00..c09c7b87e24 100644
--- a/app/views/projects/diffs/_line.html.haml
+++ b/app/views/projects/diffs/_line.html.haml
@@ -1,22 +1,27 @@
- email = local_assigns.fetch(:email, false)
- plain = local_assigns.fetch(:plain, false)
+- discussions = local_assigns.fetch(:discussions, nil)
- type = line.type
- line_code = diff_file.line_code(line)
-%tr.line_holder{ plain ? { class: type} : { class: type, id: line_code } }
+- if discussions && !line.meta?
+ - discussion = discussions[line_code]
+%tr.line_holder{ class: type, id: (line_code unless plain) }
- case type
- when 'match'
= diff_match_line line.old_pos, line.new_pos, text: line.text
- - when 'nonewline'
+ - when 'old-nonewline', 'new-nonewline'
%td.old_line.diff-line-num
%td.new_line.diff-line-num
%td.line_content.match= line.text
- else
- %td.old_line.diff-line-num{ class: type, data: { linenumber: line.old_pos } }
+ %td.old_line.diff-line-num{ class: [type, ("js-avatar-container" if !plain)], data: { linenumber: line.old_pos } }
- link_text = type == "new" ? " " : line.old_pos
- if plain
= link_text
- else
%a{ href: "##{line_code}", data: { linenumber: link_text } }
+ - if discussion && discussion.resolvable? && !plain
+ %diff-note-avatars{ "discussion-id" => discussion.id }
%td.new_line.diff-line-num{ class: type, data: { linenumber: line.new_pos } }
- link_text = type == "old" ? " " : line.new_pos
- if plain
@@ -29,9 +34,6 @@
- else
= diff_line_content(line.text)
-- discussions = local_assigns.fetch(:discussions, nil)
-- if discussions && !line.meta?
- - discussion = discussions[line_code]
- - if discussion
- - discussion_expanded = local_assigns.fetch(:discussion_expanded, discussion.expanded?)
- = render "discussions/diff_discussion", discussion: discussion, expanded: discussion_expanded
+- if discussion
+ - discussion_expanded = local_assigns.fetch(:discussion_expanded, discussion.expanded?)
+ = render "discussions/diff_discussion", discussion: discussion, expanded: discussion_expanded
diff --git a/app/views/projects/diffs/_parallel_view.html.haml b/app/views/projects/diffs/_parallel_view.html.haml
index 997bf0fc560..b7346f27ddb 100644
--- a/app/views/projects/diffs/_parallel_view.html.haml
+++ b/app/views/projects/diffs/_parallel_view.html.haml
@@ -4,19 +4,24 @@
- diff_file.parallel_diff_lines.each do |line|
- left = line[:left]
- right = line[:right]
+ - last_line = right.new_pos if right
+ - unless @diff_notes_disabled
+ - discussion_left, discussion_right = parallel_diff_discussions(left, right, diff_file)
%tr.line_holder.parallel
- if left
- case left.type
- when 'match'
= diff_match_line left.old_pos, nil, text: left.text, view: :parallel
- - when 'nonewline'
+ - when 'old-nonewline', 'new-nonewline'
%td.old_line.diff-line-num
%td.line_content.match= left.text
- else
- left_line_code = diff_file.line_code(left)
- left_position = diff_file.position(left)
- %td.old_line.diff-line-num{ id: left_line_code, class: left.type, data: { linenumber: left.old_pos } }
+ %td.old_line.diff-line-num.js-avatar-container{ id: left_line_code, class: left.type, data: { linenumber: left.old_pos } }
%a{ href: "##{left_line_code}", data: { linenumber: left.old_pos } }
+ - if discussion_left && discussion_left.resolvable?
+ %diff-note-avatars{ "discussion-id" => discussion_left.id }
%td.line_content.parallel.noteable_line{ class: left.type, data: diff_view_line_data(left_line_code, left_position, 'old') }= diff_line_content(left.text)
- else
%td.old_line.diff-line-num.empty-cell
@@ -26,23 +31,23 @@
- case right.type
- when 'match'
= diff_match_line nil, right.new_pos, text: left.text, view: :parallel
- - when 'nonewline'
+ - when 'old-nonewline', 'new-nonewline'
%td.new_line.diff-line-num
%td.line_content.match= right.text
- else
- right_line_code = diff_file.line_code(right)
- right_position = diff_file.position(right)
- %td.new_line.diff-line-num{ id: right_line_code, class: right.type, data: { linenumber: right.new_pos } }
+ %td.new_line.diff-line-num.js-avatar-container{ id: right_line_code, class: right.type, data: { linenumber: right.new_pos } }
%a{ href: "##{right_line_code}", data: { linenumber: right.new_pos } }
+ - if discussion_right && discussion_right.resolvable?
+ %diff-note-avatars{ "discussion-id" => discussion_right.id }
%td.line_content.parallel.noteable_line{ class: right.type, data: diff_view_line_data(right_line_code, right_position, 'new') }= diff_line_content(right.text)
- else
%td.old_line.diff-line-num.empty-cell
%td.line_content.parallel
- - unless @diff_notes_disabled
- - discussion_left, discussion_right = parallel_diff_discussions(left, right, diff_file)
- - if discussion_left || discussion_right
- = render "discussions/parallel_diff_discussion", discussion_left: discussion_left, discussion_right: discussion_right
+ - if discussion_left || discussion_right
+ = render "discussions/parallel_diff_discussion", discussion_left: discussion_left, discussion_right: discussion_right
- if !diff_file.new_file && !diff_file.deleted_file && diff_file.diff_lines.any?
- last_line = diff_file.diff_lines.last
- if last_line.new_pos < total_lines
diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml
index 83ae9fd10ec..2802a4eca7b 100644
--- a/app/views/projects/edit.html.haml
+++ b/app/views/projects/edit.html.haml
@@ -1,3 +1,4 @@
+= render "projects/settings/head"
.project-edit-container
.row.prepend-top-default
.col-lg-3.profile-settings-sidebar
diff --git a/app/views/projects/environments/show.html.haml b/app/views/projects/environments/show.html.haml
index 29a98f23b88..f463a429f65 100644
--- a/app/views/projects/environments/show.html.haml
+++ b/app/views/projects/environments/show.html.haml
@@ -16,7 +16,7 @@
- if can?(current_user, :create_deployment, @environment) && @environment.can_stop?
= link_to 'Stop', stop_namespace_project_environment_path(@project.namespace, @project, @environment), data: { confirm: 'Are you sure you want to stop this environment?' }, class: 'btn btn-danger', method: :post
- .deployments-container
+ .environments-container
- if @deployments.blank?
.blank-state.blank-state-no-icon
%h2.blank-state-title
diff --git a/app/views/projects/issues/index.atom.builder b/app/views/projects/issues/index.atom.builder
index a0df0db77c5..4feec09bb5d 100644
--- a/app/views/projects/issues/index.atom.builder
+++ b/app/views/projects/issues/index.atom.builder
@@ -4,7 +4,7 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear
xml.link href: url_for(params), rel: "self", type: "application/atom+xml"
xml.link href: namespace_project_issues_url(@project.namespace, @project), rel: "alternate", type: "text/html"
xml.id namespace_project_issues_url(@project.namespace, @project)
- xml.updated @issues.first.created_at.xmlschema if @issues.reorder(nil).any?
+ xml.updated @issues.first.updated_at.xmlschema if @issues.reorder(nil).any?
xml << render(partial: 'issues/issue', collection: @issues) if @issues.reorder(nil).any?
end
diff --git a/app/views/projects/issues/verify.html.haml b/app/views/projects/issues/verify.html.haml
index 09aa401e44a..6da7c317f3a 100644
--- a/app/views/projects/issues/verify.html.haml
+++ b/app/views/projects/issues/verify.html.haml
@@ -1,4 +1,5 @@
- form = [@project.namespace.becomes(Namespace), @project, @issue]
= render layout: 'layouts/recaptcha_verification', locals: { spammable: @issue, form: form } do
- = hidden_field_tag(:merge_request_for_resolving_discussions, params[:merge_request_for_resolving_discussions])
+ = hidden_field_tag(:merge_request_to_resolve_discussions_of, params[:merge_request_to_resolve_discussions_of])
+ = hidden_field_tag(:discussion_to_resolve, params[:discussion_to_resolve])
diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml
index 17be0490a86..c8f097c69da 100644
--- a/app/views/projects/merge_requests/_show.html.haml
+++ b/app/views/projects/merge_requests/_show.html.haml
@@ -82,6 +82,7 @@
= render "shared/icons/icon_status_success.svg"
%span.line-resolve-text
{{ resolvedDiscussionCount }}/{{ discussionCount }} {{ resolvedCountText }} resolved
+ = render "discussions/new_issue_for_all_discussions", merge_request: @merge_request
= render "discussions/jump_to_next"
.tab-content#diff-notes-app
diff --git a/app/views/projects/merge_requests/widget/open/_unresolved_discussions.html.haml b/app/views/projects/merge_requests/widget/open/_unresolved_discussions.html.haml
index e094f97f3b6..ec9346ce89b 100644
--- a/app/views/projects/merge_requests/widget/open/_unresolved_discussions.html.haml
+++ b/app/views/projects/merge_requests/widget/open/_unresolved_discussions.html.haml
@@ -6,5 +6,5 @@
Please resolve these discussions
- if @project.issues_enabled? && can?(current_user, :create_issue, @project)
or
- = link_to "open an issue to resolve them later", new_namespace_project_issue_path(@project.namespace, @project, merge_request_for_resolving_discussions: @merge_request.iid)
+ = link_to "open an issue to resolve them later", new_namespace_project_issue_path(@project.namespace, @project, merge_request_to_resolve_discussions_of: @merge_request.iid)
to allow this merge request to be merged.
diff --git a/app/views/projects/milestones/index.html.haml b/app/views/projects/milestones/index.html.haml
index ad2bfbec915..918f5d161bb 100644
--- a/app/views/projects/milestones/index.html.haml
+++ b/app/views/projects/milestones/index.html.haml
@@ -1,14 +1,14 @@
- @no_container = true
-- page_title "Milestones"
-= render "projects/issues/head"
+- page_title 'Milestones'
+= render 'projects/issues/head'
%div{ class: container_class }
.top-area
- = render 'shared/milestones_filter'
+ = render 'shared/milestones_filter', counts: milestone_counts(@project.milestones)
.nav-controls
- if can?(current_user, :admin_milestone, @project)
- = link_to new_namespace_project_milestone_path(@project.namespace, @project), class: "btn btn-new", title: "New Milestone" do
+ = link_to new_namespace_project_milestone_path(@project.namespace, @project), class: 'btn btn-new', title: 'New Milestone' do
New Milestone
.milestones
@@ -19,4 +19,4 @@
%li
.nothing-here-block No milestones to show
- = paginate @milestones, theme: "gitlab"
+ = paginate @milestones, theme: 'gitlab'
diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml
index a73e8f345e0..5552086bc50 100644
--- a/app/views/projects/notes/_note.html.haml
+++ b/app/views/projects/notes/_note.html.haml
@@ -2,7 +2,7 @@
- return if note.cross_reference_not_visible_for?(current_user)
- note_editable = note_editable?(note)
-%li.timeline-entry{ id: dom_id(note), class: ["note", "note-row-#{note.id}", ('system-note' if note.system)], data: {author_id: note.author.id, editable: note_editable} }
+%li.timeline-entry{ id: dom_id(note), class: ["note", "note-row-#{note.id}", ('system-note' if note.system)], data: {author_id: note.author.id, editable: note_editable, note_id: note.id} }
.timeline-entry-inner
.timeline-icon
%a{ href: user_path(note.author) }
@@ -30,27 +30,30 @@
- if note.resolvable?
- can_resolve = can?(current_user, :resolve_note, note)
- %resolve-btn{ "discussion-id" => "#{note.discussion_id}",
+ %resolve-btn{ "project-path" => project_path(note.project),
+ "discussion-id" => note.discussion_id,
":note-id" => note.id,
":resolved" => note.resolved?,
":can-resolve" => can_resolve,
- "resolved-by" => "#{note.resolved_by.try(:name)}",
+ ":author-name" => "'#{j(note.author.name)}'",
+ "author-avatar" => note.author.avatar_url,
+ ":note-truncated" => "'#{truncate(note.note, length: 17)}'",
+ ":resolved-by" => "'#{j(note.resolved_by.try(:name))}'",
"v-show" => "#{can_resolve || note.resolved?}",
"inline-template" => true,
"ref" => "note_#{note.id}" }
- .note-action-button
+ %button.note-action-button.line-resolve-btn{ type: "button",
+ class: ("is-disabled" unless can_resolve),
+ ":class" => "{ 'is-active': isResolved }",
+ ":aria-label" => "buttonText",
+ "@click" => "resolve",
+ ":title" => "buttonText",
+ "v-show" => "!loading",
+ ":ref" => "'button'" }
= icon("spin spinner", "v-show" => "loading")
- %button.line-resolve-btn{ type: "button",
- class: ("is-disabled" unless can_resolve),
- ":class" => "{ 'is-active': isResolved }",
- ":aria-label" => "buttonText",
- "@click" => "resolve",
- ":title" => "buttonText",
- "v-show" => "!loading",
- ":ref" => "'button'" }
- = render "shared/icons/icon_status_success.svg"
+ = render "shared/icons/icon_status_success.svg"
- if current_user
- if note.emoji_awardable?
diff --git a/app/views/projects/pages/show.html.haml b/app/views/projects/pages/show.html.haml
index b6595269b06..259d5bd63d6 100644
--- a/app/views/projects/pages/show.html.haml
+++ b/app/views/projects/pages/show.html.haml
@@ -1,4 +1,6 @@
- page_title 'Pages'
+= render "projects/settings/head"
+
%h3.page_title
Pages
diff --git a/app/views/projects/settings/_head.html.haml b/app/views/projects/settings/_head.html.haml
new file mode 100644
index 00000000000..88bcb541dac
--- /dev/null
+++ b/app/views/projects/settings/_head.html.haml
@@ -0,0 +1,33 @@
+= content_for :sub_nav do
+ .scrolling-tabs-container.sub-nav-scroll
+ = render 'shared/nav_scroll'
+ .nav-links.sub-nav.scrolling-tabs
+ %ul{ class: container_class }
+ - can_edit = can?(current_user, :admin_project, @project)
+ - if can_edit
+ = nav_link(controller: :projects) do
+ = link_to edit_project_path(@project), title: 'General' do
+ %span
+ General
+ = nav_link(controller: :members) do
+ = link_to project_settings_members_path(@project), title: 'Members' do
+ %span
+ Members
+ - if can_edit
+ = nav_link(controller: :integrations) do
+ = link_to project_settings_integrations_path(@project), title: 'Integrations' do
+ %span
+ Integrations
+ = nav_link(controller: :repository) do
+ = link_to namespace_project_settings_repository_path(@project.namespace, @project), title: 'Repository' do
+ %span
+ Repository
+ - if @project.feature_available?(:builds, current_user)
+ = nav_link(controller: :ci_cd) do
+ = link_to namespace_project_settings_ci_cd_path(@project.namespace, @project), title: 'CI/CD Pipelines' do
+ %span
+ CI/CD Pipelines
+ = nav_link(controller: :pages) do
+ = link_to namespace_project_pages_path(@project.namespace, @project), title: 'Pages' do
+ %span
+ Pages
diff --git a/app/views/projects/settings/ci_cd/show.html.haml b/app/views/projects/settings/ci_cd/show.html.haml
index 52f5f7b81e2..e2603096014 100644
--- a/app/views/projects/settings/ci_cd/show.html.haml
+++ b/app/views/projects/settings/ci_cd/show.html.haml
@@ -1,4 +1,5 @@
- page_title "CI/CD Pipelines"
+= render "projects/settings/head"
= render 'projects/runners/index'
= render 'projects/variables/index'
diff --git a/app/views/projects/settings/integrations/show.html.haml b/app/views/projects/settings/integrations/show.html.haml
index aa38a889cdd..f69992566b5 100644
--- a/app/views/projects/settings/integrations/show.html.haml
+++ b/app/views/projects/settings/integrations/show.html.haml
@@ -1,3 +1,4 @@
- page_title 'Integrations'
+= render "projects/settings/head"
= render 'projects/hooks/index'
= render 'projects/services/index'
diff --git a/app/views/projects/settings/members/show.html.haml b/app/views/projects/settings/members/show.html.haml
index d81ed7bb609..20e1ad68244 100644
--- a/app/views/projects/settings/members/show.html.haml
+++ b/app/views/projects/settings/members/show.html.haml
@@ -1,4 +1,5 @@
- page_title "Members"
+= render "projects/settings/head"
= render "projects/project_members/index"
- if can?(current_user, :admin_project, @project)
diff --git a/app/views/projects/settings/repository/show.html.haml b/app/views/projects/settings/repository/show.html.haml
index 95d821f6135..4c02302e161 100644
--- a/app/views/projects/settings/repository/show.html.haml
+++ b/app/views/projects/settings/repository/show.html.haml
@@ -1,4 +1,5 @@
- page_title "Repository"
+= render "projects/settings/head"
= render @deploy_keys
= render "projects/protected_branches/index"
diff --git a/app/views/projects/tags/destroy.js.haml b/app/views/projects/tags/destroy.js.haml
index e4a78fadbeb..cde23e03d54 100644
--- a/app/views/projects/tags/destroy.js.haml
+++ b/app/views/projects/tags/destroy.js.haml
@@ -1,2 +1,4 @@
-- if @repository.tags.empty?
+- if @error.present?
+ new Flash('#{escape_javascript(@error)}', 'alert');
+- elsif @repository.tags.empty?
$('.tags').load(document.URL + ' .nothing-here-block').hide().fadeIn(1000)
diff --git a/app/views/shared/_branch_switcher.html.haml b/app/views/shared/_branch_switcher.html.haml
new file mode 100644
index 00000000000..7799aff6b5b
--- /dev/null
+++ b/app/views/shared/_branch_switcher.html.haml
@@ -0,0 +1,8 @@
+- dropdown_toggle_text = @target_branch || tree_edit_branch
+= hidden_field_tag 'target_branch', dropdown_toggle_text
+
+.dropdown
+ = dropdown_toggle dropdown_toggle_text, { toggle: 'dropdown', selected: dropdown_toggle_text, field_name: 'target_branch', form_id: '.js-edit-blob-form', refs_url: namespace_project_branches_path(@project.namespace, @project) }, { toggle_class: 'js-project-branches-dropdown js-target-branch' }
+ .dropdown-menu.dropdown-menu-selectable.dropdown-menu-paging.dropdown-menu-branches
+ = render partial: 'shared/projects/blob/branch_page_default'
+ = render partial: 'shared/projects/blob/branch_page_create'
diff --git a/app/views/shared/_issuable_meta_data.html.haml b/app/views/shared/_issuable_meta_data.html.haml
index 66310da5cd6..1d4fd71522d 100644
--- a/app/views/shared/_issuable_meta_data.html.haml
+++ b/app/views/shared/_issuable_meta_data.html.haml
@@ -6,7 +6,7 @@
- if issuable_mr > 0
%li
- = image_tag('icon-merge-request-unmerged', class: 'icon-merge-request-unmerged')
+ = image_tag('icon-merge-request-unmerged.svg', class: 'icon-merge-request-unmerged')
= issuable_mr
- if upvotes > 0
diff --git a/app/views/shared/_milestones_filter.html.haml b/app/views/shared/_milestones_filter.html.haml
index 704893b4d5b..57a0eaa919e 100644
--- a/app/views/shared/_milestones_filter.html.haml
+++ b/app/views/shared/_milestones_filter.html.haml
@@ -1,19 +1,13 @@
-- if @project
- - counts = milestone_counts(@project.milestones)
-
%ul.nav-links
%li{ class: milestone_class_for_state(params[:state], 'opened', true) }>
= link_to milestones_filter_path(state: 'opened') do
Open
- - if @project
- %span.badge= counts[:opened]
+ %span.badge= counts[:opened]
%li{ class: milestone_class_for_state(params[:state], 'closed') }>
= link_to milestones_filter_path(state: 'closed') do
Closed
- - if @project
- %span.badge= counts[:closed]
+ %span.badge= counts[:closed]
%li{ class: milestone_class_for_state(params[:state], 'all') }>
= link_to milestones_filter_path(state: 'all') do
All
- - if @project
- %span.badge= counts[:all]
+ %span.badge= counts[:all]
diff --git a/app/views/shared/_new_commit_form.html.haml b/app/views/shared/_new_commit_form.html.haml
index 0c8ac48bb58..3ac5e15d1c4 100644
--- a/app/views/shared/_new_commit_form.html.haml
+++ b/app/views/shared/_new_commit_form.html.haml
@@ -7,7 +7,7 @@
.form-group.branch
= label_tag 'target_branch', 'Target branch', class: 'control-label'
.col-sm-10
- = text_field_tag 'target_branch', @target_branch || tree_edit_branch, required: true, class: "form-control js-target-branch"
+ = render 'shared/branch_switcher'
.js-create-merge-request-container
.checkbox
diff --git a/app/views/shared/icons/_collapse.svg.erb b/app/views/shared/icons/_collapse.svg.erb
new file mode 100644
index 00000000000..917753fb343
--- /dev/null
+++ b/app/views/shared/icons/_collapse.svg.erb
@@ -0,0 +1 @@
+<svg width="<%= size %>" height="<%= size %>" viewBox="0 0 9 13"><path d="M2.57568253,6.49866948 C2.50548852,6.57199715 2.44637866,6.59708255 2.39835118,6.57392645 C2.3503237,6.55077034 2.32631032,6.48902165 2.32631032,6.38867852 L2.32631032,-2.13272614 C2.32631032,-2.23306927 2.3503237,-2.29481796 2.39835118,-2.31797406 C2.44637866,-2.34113017 2.50548852,-2.31604477 2.57568253,-2.24271709 L6.51022184,1.86747129 C6.53977721,1.8983461 6.56379059,1.93500939 6.5822627,1.97746225 L6.5822627,2.27849013 C6.56379059,2.31708364 6.53977721,2.35374693 6.51022184,2.38848109 L2.57568253,6.49866948 Z" transform="translate(4.454287, 2.127976) rotate(90.000000) translate(-4.454287, -2.127976) "></path><path d="M3.74312342,2.09553332 C3.74312342,1.99519019 3.77821989,1.9083561 3.8484139,1.83502843 C3.91860791,1.76170075 4.00173115,1.72503747 4.09778611,1.72503747 L4.80711151,1.72503747 C4.90316647,1.72503747 4.98628971,1.76170075 5.05648372,1.83502843 C5.12667773,1.9083561 5.16177421,1.99519019 5.16177421,2.09553332 L5.16177421,10.2464421 C5.16177421,10.3467853 5.12667773,10.4336194 5.05648372,10.506947 C4.98628971,10.5802747 4.90316647,10.616938 4.80711151,10.616938 L4.09778611,10.616938 C4.00173115,10.616938 3.91860791,10.5802747 3.8484139,10.506947 C3.77821989,10.4336194 3.74312342,10.3467853 3.74312342,10.2464421 L3.74312342,2.09553332 Z" transform="translate(4.452449, 6.170988) rotate(-90.000000) translate(-4.452449, -6.170988) "></path><path d="M2.57568253,14.6236695 C2.50548852,14.6969971 2.44637866,14.7220826 2.39835118,14.6989264 C2.3503237,14.6757703 2.32631032,14.6140216 2.32631032,14.5136785 L2.32631032,5.99227386 C2.32631032,5.89193073 2.3503237,5.83018204 2.39835118,5.80702594 C2.44637866,5.78386983 2.50548852,5.80895523 2.57568253,5.88228291 L6.51022184,9.99247129 C6.53977721,10.0233461 6.56379059,10.0600094 6.5822627,10.1024622 L6.5822627,10.4034901 C6.56379059,10.4420836 6.53977721,10.4787469 6.51022184,10.5134811 L2.57568253,14.6236695 Z" transform="translate(4.454287, 10.252976) scale(1, -1) rotate(90.000000) translate(-4.454287, -10.252976) "></path></svg>
diff --git a/app/views/shared/icons/_icon_mr_issue.svg b/app/views/shared/icons/_icon_mr_issue.svg
new file mode 100644
index 00000000000..ae219a3ded2
--- /dev/null
+++ b/app/views/shared/icons/_icon_mr_issue.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g fill-rule="evenodd"><path d="m8.411 1.012c-.136-.008-.273-.012-.411-.012-3.866 0-7 3.134-7 7 0 3.866 3.134 7 7 7 3.866 0 7-3.134 7-7 0-.138-.004-.275-.012-.411-.464.201-.964.334-1.488.386 0 .008 0 .016 0 .025 0 3.038-2.462 5.5-5.5 5.5-3.038 0-5.5-2.462-5.5-5.5 0-3.038 2.462-5.5 5.5-5.5.008 0 .016 0 .025 0 .052-.524.185-1.024.386-1.488"/><path d="m12 2h-1.01c-.54 0-.991.448-.991 1 0 .556.444 1 .991 1h1.01v1.01c0 .54.448.991 1 .991.556 0 1-.444 1-.991v-1.01h1.01c.54 0 .991-.448.991-1 0-.556-.444-1-.991-1h-1.01v-1.01c0-.54-.448-.991-1-.991-.556 0-1 .444-1 .991v1.01m-5 4.01c0-.557.444-1.01 1-1.01.552 0 1 .443 1 1.01v1.981c0 .557-.444 1.01-1 1.01-.552 0-1-.443-1-1.01v-1.981m1 5.991c.552 0 1-.448 1-1 0-.552-.448-1-1-1-.552 0-1 .448-1 1 0 .552.448 1 1 1"/></g></svg> \ No newline at end of file
diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml
index cb92b2e97a7..0b0f2c9cd1a 100644
--- a/app/views/shared/issuable/_form.html.haml
+++ b/app/views/shared/issuable/_form.html.haml
@@ -45,41 +45,47 @@
= render 'shared/issuable/form/merge_params', issuable: issuable
-- if @merge_request_for_resolving_discussions
+- if @merge_request_to_resolve_discussions_of
.form-group
.col-sm-10.col-sm-offset-2
- - if @merge_request_for_resolving_discussions.discussions_can_be_resolved_by?(current_user)
- = icon('exclamation-triangle')
- Creating this issue will mark all discussions in
- = link_to @merge_request_for_resolving_discussions.to_reference, merge_request_path(@merge_request_for_resolving_discussions)
- as resolved.
- = hidden_field_tag 'merge_request_for_resolving_discussions', @merge_request_for_resolving_discussions.iid
+ = icon('info-circle')
+ - if @merge_request_to_resolve_discussions_of.discussions_can_be_resolved_by?(current_user)
+ = hidden_field_tag 'merge_request_to_resolve_discussions_of', @merge_request_to_resolve_discussions_of.iid
+ - if @discussion_to_resolve
+ = hidden_field_tag 'discussion_to_resolve', @discussion_to_resolve.id
+ Creating this issue will resolve the discussion in
+ - else
+ Creating this issue will resolve all discussions in
+ = link_to_discussions_to_resolve(@merge_request_to_resolve_discussions_of, @discussion_to_resolve)
- else
- = icon('exclamation-triangle')
- You can't automatically mark all discussions in
- = link_to @merge_request_for_resolving_discussions.to_reference, merge_request_path(@merge_request_for_resolving_discussions)
- as resolved. Ask someone with sufficient rights to resolve the them.
+ The
+ = @discussion_to_resolve ? 'discussion' : 'discussions'
+ at
+ = link_to_discussions_to_resolve(@merge_request_to_resolve_discussions_of, @discussion_to_resolve)
+ will stay unresolved. Ask someone with permission to resolve
+ = @discussion_to_resolve ? 'it.' : 'them.'
- is_footer = !(issuable.is_a?(MergeRequest) && issuable.new_record?)
.row-content-block{ class: (is_footer ? "footer-block" : "middle-block") }
- - if issuable.new_record?
- = form.submit "Submit #{issuable.class.model_name.human.downcase}", class: 'btn btn-create'
- - else
- = form.submit 'Save changes', class: 'btn btn-save'
+ .pull-right
+ - if issuable.new_record?
+ = link_to 'Cancel', polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable.class]), class: 'btn btn-cancel'
+ - else
+ - if can?(current_user, :"destroy_#{issuable.to_ability_name}", @project)
+ = link_to 'Delete', polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable]), data: { confirm: "#{issuable.human_class_name} will be removed! Are you sure?" }, method: :delete, class: 'btn btn-danger btn-grouped'
+ = link_to 'Cancel', polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable]), class: 'btn btn-grouped btn-cancel'
+
+ %span.append-right-10
+ - if issuable.new_record?
+ = form.submit "Submit #{issuable.class.model_name.human.downcase}", class: 'btn btn-create'
+ - else
+ = form.submit 'Save changes', class: 'btn btn-save'
- if !issuable.persisted? && !issuable.project.empty_repo? && (guide_url = contribution_guide_path(issuable.project))
- .inline.prepend-left-10
+ .inline.prepend-top-10
Please review the
%strong= link_to('contribution guidelines', guide_url)
for this project.
- - if issuable.new_record?
- = link_to 'Cancel', polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable.class]), class: 'btn btn-cancel'
- - else
- .pull-right
- - if can?(current_user, :"destroy_#{issuable.to_ability_name}", @project)
- = link_to 'Delete', polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable]), data: { confirm: "#{issuable.human_class_name} will be removed! Are you sure?" },
- method: :delete, class: 'btn btn-danger btn-grouped'
- = link_to 'Cancel', polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable]), class: 'btn btn-grouped btn-cancel'
= form.hidden_field :lock_version
diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml
index d73556114d8..f1730b1791c 100644
--- a/app/views/shared/issuable/_search_bar.html.haml
+++ b/app/views/shared/issuable/_search_bar.html.haml
@@ -14,18 +14,18 @@
.scroll-container
%ul.tokens-container.list-unstyled
%li.input-token
- %input.form-control.filtered-search{ placeholder: 'Search or filter results...', 'data-id' => 'filtered-search', 'data-project-id' => @project.id, 'data-username-params' => @users.to_json(only: [:id, :username]), 'data-base-endpoint' => namespace_project_path(@project.namespace, @project) }
+ %input.form-control.filtered-search{ placeholder: 'Search or filter results...', data: { id: 'filtered-search', 'project-id' => @project.id, 'username-params' => @users.to_json(only: [:id, :username]), 'base-endpoint' => namespace_project_path(@project.namespace, @project) } }
= icon('filter')
%button.clear-search.hidden{ type: 'button' }
= icon('times')
#js-dropdown-hint.dropdown-menu.hint-dropdown
- %ul{ 'data-dropdown' => true }
- %li.filter-dropdown-item{ 'data-action' => 'submit' }
+ %ul{ data: { dropdown: true } }
+ %li.filter-dropdown-item{ data: { action: 'submit' } }
%button.btn.btn-link
= icon('search')
%span
Keep typing and press Enter
- %ul.filter-dropdown{ 'data-dynamic' => true, 'data-dropdown' => true }
+ %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
%li.filter-dropdown-item
%button.btn.btn-link
-# Encapsulate static class name `{{icon}}` inside #{} to bypass
@@ -36,50 +36,50 @@
%span.js-filter-tag.dropdown-light-content
{{tag}}
#js-dropdown-author.dropdown-menu{ data: { icon: 'pencil', hint: 'author', tag: '@author' } }
- %ul.filter-dropdown{ 'data-dynamic' => true, 'data-dropdown' => true }
+ %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
%li.filter-dropdown-item
%button.btn.btn-link.dropdown-user
- %img.avatar.avatar-inline{ 'data-src' => '{{avatar_url}}', alt: '{{name}}\'s avatar', width: '30' }
+ %img.avatar{ alt: '{{name}}\'s avatar', width: '30', data: { src: '{{avatar_url}}' } }
.dropdown-user-details
%span
{{name}}
%span.dropdown-light-content
@{{username}}
#js-dropdown-assignee.dropdown-menu{ data: { icon: 'user', hint: 'assignee', tag: '@assignee' } }
- %ul{ 'data-dropdown' => true }
- %li.filter-dropdown-item{ 'data-value' => 'none' }
+ %ul{ data: { dropdown: true } }
+ %li.filter-dropdown-item{ data: { value: 'none' } }
%button.btn.btn-link
No Assignee
%li.divider
- %ul.filter-dropdown{ 'data-dynamic' => true, 'data-dropdown' => true }
+ %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
%li.filter-dropdown-item
%button.btn.btn-link.dropdown-user
- %img.avatar.avatar-inline{ 'data-src' => '{{avatar_url}}', alt: '{{name}}\'s avatar', width: '30' }
+ %img.avatar{ alt: '{{name}}\'s avatar', width: '30', data: { src: '{{avatar_url}}' } }
.dropdown-user-details
%span
{{name}}
%span.dropdown-light-content
@{{username}}
#js-dropdown-milestone.dropdown-menu{ data: { icon: 'clock-o', hint: 'milestone', tag: '%milestone' } }
- %ul{ 'data-dropdown' => true }
- %li.filter-dropdown-item{ 'data-value' => 'none' }
+ %ul{ data: { dropdown: true } }
+ %li.filter-dropdown-item{ data: { value: 'none' } }
%button.btn.btn-link
No Milestone
- %li.filter-dropdown-item{ 'data-value' => 'upcoming' }
+ %li.filter-dropdown-item{ data: { value: 'upcoming' } }
%button.btn.btn-link
Upcoming
%li.divider
- %ul.filter-dropdown{ 'data-dynamic' => true, 'data-dropdown' => true }
+ %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
%li.filter-dropdown-item
%button.btn.btn-link.js-data-value
{{title}}
#js-dropdown-label.dropdown-menu{ data: { icon: 'tag', hint: 'label', tag: '~label' } }
- %ul{ 'data-dropdown' => true }
- %li.filter-dropdown-item{ 'data-value' => 'none' }
+ %ul{ data: { dropdown: true } }
+ %li.filter-dropdown-item{ data: { value: 'none' } }
%button.btn.btn-link
No Label
%li.divider
- %ul.filter-dropdown{ 'data-dynamic' => true, 'data-dropdown' => true }
+ %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
%li.filter-dropdown-item
%button.btn.btn-link
%span.dropdown-label-box{ style: 'background: {{color}}' }
diff --git a/app/views/shared/milestones/_issuables.html.haml b/app/views/shared/milestones/_issuables.html.haml
index a93cbd1041f..8af3bd597c5 100644
--- a/app/views/shared/milestones/_issuables.html.haml
+++ b/app/views/shared/milestones/_issuables.html.haml
@@ -13,6 +13,6 @@
- class_prefix = dom_class(issuables).pluralize
%ul{ class: "well-list #{class_prefix}-sortable-list", id: "#{class_prefix}-list-#{id}", "data-state" => id }
= render partial: 'shared/milestones/issuable',
- collection: issuables.sort_by(&:position),
+ collection: issuables.order_position_asc,
as: :issuable,
locals: { show_project_name: show_project_name, show_full_project_name: show_full_project_name }
diff --git a/app/views/shared/projects/_project.html.haml b/app/views/shared/projects/_project.html.haml
index 4a27965754d..7e9fb7bb4d3 100644
--- a/app/views/shared/projects/_project.html.haml
+++ b/app/views/shared/projects/_project.html.haml
@@ -7,7 +7,7 @@
- show_last_commit_as_description = false unless local_assigns[:show_last_commit_as_description] == true && project.commit
- css_class += " no-description" if project.description.blank? && !show_last_commit_as_description
- cache_key = [project.namespace, project, controller.controller_name, controller.action_name, current_application_settings, 'v2.3']
-- cache_key.push(project.commit.status) if project.commit.try(:status)
+- cache_key.push(project.commit&.sha, project.commit&.status)
%li.project-row{ class: css_class }
= cache(cache_key) do
diff --git a/app/views/shared/projects/blob/_branch_page_create.html.haml b/app/views/shared/projects/blob/_branch_page_create.html.haml
new file mode 100644
index 00000000000..c279a0d8846
--- /dev/null
+++ b/app/views/shared/projects/blob/_branch_page_create.html.haml
@@ -0,0 +1,8 @@
+.dropdown-page-two.dropdown-new-branch
+ = dropdown_title('Create new branch', back: true)
+ = dropdown_content do
+ %input#new_branch_name.default-dropdown-input.append-bottom-10{ type: "text", placeholder: "Name new branch" }
+ %button.btn.btn-primary.pull-left.js-new-branch-btn{ type: "button" }
+ Create
+ %button.btn.btn-default.pull-right.js-cancel-branch-btn{ type: "button" }
+ Cancel
diff --git a/app/views/shared/projects/blob/_branch_page_default.html.haml b/app/views/shared/projects/blob/_branch_page_default.html.haml
new file mode 100644
index 00000000000..9bf78d10878
--- /dev/null
+++ b/app/views/shared/projects/blob/_branch_page_default.html.haml
@@ -0,0 +1,10 @@
+.dropdown-page-one
+ = dropdown_title "Select branch"
+ = dropdown_filter "Search branches"
+ = dropdown_content
+ = dropdown_loading
+ = dropdown_footer do
+ %ul.dropdown-footer-list
+ %li
+ %a.create-new-branch.dropdown-toggle-page{ href: "#" }
+ Create new branch
diff --git a/app/workers/authorized_projects_worker.rb b/app/workers/authorized_projects_worker.rb
index 0e20df506a3..13207a8bc71 100644
--- a/app/workers/authorized_projects_worker.rb
+++ b/app/workers/authorized_projects_worker.rb
@@ -10,7 +10,7 @@ class AuthorizedProjectsWorker
end
def self.bulk_perform_async(args_list)
- Sidekiq::Client.push_bulk('class' => self, 'args' => args_list)
+ Sidekiq::Client.push_bulk('class' => self, 'queue' => sidekiq_options['queue'], 'args' => args_list)
end
def perform(user_id)