From b04b3d1de783ea70ca81c5f80c86d656ee6d82ea Mon Sep 17 00:00:00 2001
From: Simon Knox <psimyn@gmail.com>
Date: Tue, 24 Oct 2017 16:48:41 +0300
Subject: [PATCH] apply changes for JS and CSS from gitlab-ee!2912

https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/2912/
---
 .../boards/filtered_search_boards.js          |  9 +-
 .../javascripts/boards/stores/boards_store.js |  8 +-
 .../filtered_search/dropdown_utils.js         | 10 ++
 .../filtered_search_manager.js                | 10 +-
 .../filtered_search_visual_tokens.js          | 15 +--
 app/assets/javascripts/labels_select.js       | 13 ++-
 app/assets/javascripts/milestone_select.js    | 17 +++-
 app/assets/javascripts/users_select.js        |  7 +-
 .../vue_shared/components/loading_icon.vue    |  8 +-
 .../vue_shared/components/popup_dialog.vue    | 93 +++++++++++++++----
 app/assets/stylesheets/framework/common.scss  |  3 +
 .../stylesheets/framework/dropdowns.scss      |  1 +
 app/assets/stylesheets/framework/modal.scss   |  8 ++
 .../framework/tw_bootstrap_variables.scss     | 33 +++++++
 app/assets/stylesheets/pages/repo.scss        | 13 ---
 15 files changed, 183 insertions(+), 65 deletions(-)

diff --git a/app/assets/javascripts/boards/filtered_search_boards.js b/app/assets/javascripts/boards/filtered_search_boards.js
index 3f083655f95..184665f395c 100644
--- a/app/assets/javascripts/boards/filtered_search_boards.js
+++ b/app/assets/javascripts/boards/filtered_search_boards.js
@@ -11,7 +11,8 @@ export default class FilteredSearchBoards extends gl.FilteredSearchManager {
     // Issue boards is slightly different, we handle all the requests async
     // instead or reloading the page, we just re-fire the list ajax requests
     this.isHandledAsync = true;
-    this.cantEdit = cantEdit;
+    this.cantEdit = cantEdit.filter(i => typeof i === 'string');
+    this.cantEditWithValue = cantEdit.filter(i => typeof i === 'object');
   }
 
   updateObject(path) {
@@ -42,7 +43,9 @@ export default class FilteredSearchBoards extends gl.FilteredSearchManager {
     this.filteredSearchInput.dispatchEvent(new Event('input'));
   }
 
-  canEdit(tokenName) {
-    return this.cantEdit.indexOf(tokenName) === -1;
+  canEdit(tokenName, tokenValue) {
+    if (this.cantEdit.includes(tokenName)) return false;
+    return this.cantEditWithValue.findIndex(token => token.name === tokenName &&
+      token.value === tokenValue) === -1;
   }
 }
diff --git a/app/assets/javascripts/boards/stores/boards_store.js b/app/assets/javascripts/boards/stores/boards_store.js
index ea82958e80d..798d7e0d147 100644
--- a/app/assets/javascripts/boards/stores/boards_store.js
+++ b/app/assets/javascripts/boards/stores/boards_store.js
@@ -14,16 +14,18 @@ gl.issueBoards.BoardsStore = {
   },
   state: {},
   detail: {
-    issue: {}
+    issue: {},
   },
   moving: {
     issue: {},
-    list: {}
+    list: {},
   },
   create () {
     this.state.lists = [];
     this.filter.path = getUrlParamsArray().join('&');
-    this.detail = { issue: {} };
+    this.detail = {
+      issue: {},
+    };
   },
   addList (listObj, defaultAvatar) {
     const list = new List(listObj, defaultAvatar);
diff --git a/app/assets/javascripts/filtered_search/dropdown_utils.js b/app/assets/javascripts/filtered_search/dropdown_utils.js
index 8d711e3213c..cf8a9b0402b 100644
--- a/app/assets/javascripts/filtered_search/dropdown_utils.js
+++ b/app/assets/javascripts/filtered_search/dropdown_utils.js
@@ -147,6 +147,16 @@ class DropdownUtils {
     return dataValue !== null;
   }
 
+  static getVisualTokenValues(visualToken) {
+    const tokenName = visualToken && visualToken.querySelector('.name').textContent.trim();
+    let tokenValue = visualToken && visualToken.querySelector('.value') && visualToken.querySelector('.value').textContent.trim();
+    if (tokenName === 'label' && tokenValue) {
+      // remove leading symbol and wrapping quotes
+      tokenValue = tokenValue.replace(/^~("|')?(.*)/, '$2').replace(/("|')$/, '');
+    }
+    return { tokenName, tokenValue };
+  }
+
   // Determines the full search query (visual tokens + input)
   static getSearchQuery(untilInput = false) {
     const container = FilteredSearchContainer.container;
diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js b/app/assets/javascripts/filtered_search/filtered_search_manager.js
index 7b233842d5a..69c57f923b6 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_manager.js
+++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js
@@ -185,8 +185,8 @@ class FilteredSearchManager {
     if (e.keyCode === 8 || e.keyCode === 46) {
       const { lastVisualToken } = gl.FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
 
-      const sanitizedTokenName = lastVisualToken && lastVisualToken.querySelector('.name').textContent.trim();
-      const canEdit = sanitizedTokenName && this.canEdit && this.canEdit(sanitizedTokenName);
+      const { tokenName, tokenValue } = gl.DropdownUtils.getVisualTokenValues(lastVisualToken);
+      const canEdit = tokenName && this.canEdit && this.canEdit(tokenName, tokenValue);
       if (this.filteredSearchInput.value === '' && lastVisualToken && canEdit) {
         this.filteredSearchInput.value = gl.FilteredSearchVisualTokens.getLastTokenPartial();
         gl.FilteredSearchVisualTokens.removeLastTokenPartial();
@@ -336,8 +336,8 @@ class FilteredSearchManager {
       let canClearToken = t.classList.contains('js-visual-token');
 
       if (canClearToken) {
-        const tokenKey = t.querySelector('.name').textContent.trim();
-        canClearToken = this.canEdit && this.canEdit(tokenKey);
+        const { tokenName, tokenValue } = gl.DropdownUtils.getVisualTokenValues(t);
+        canClearToken = this.canEdit && this.canEdit(tokenName, tokenValue);
       }
 
       if (canClearToken) {
@@ -469,7 +469,7 @@ class FilteredSearchManager {
           }
 
           hasFilteredSearch = true;
-          const canEdit = this.canEdit && this.canEdit(sanitizedKey);
+          const canEdit = this.canEdit && this.canEdit(sanitizedKey, sanitizedValue);
           gl.FilteredSearchVisualTokens.addFilterVisualToken(
             sanitizedKey,
             `${symbol}${quotationsToUse}${sanitizedValue}${quotationsToUse}`,
diff --git a/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js b/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js
index d2f92929b8a..6139e81fe6d 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js
+++ b/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js
@@ -38,21 +38,14 @@ class FilteredSearchVisualTokens {
   }
 
   static createVisualTokenElementHTML(canEdit = true) {
-    let removeTokenMarkup = '';
-    if (canEdit) {
-      removeTokenMarkup = `
-        <div class="remove-token" role="button">
-          <i class="fa fa-close"></i>
-        </div>
-      `;
-    }
-
     return `
-      <div class="selectable" role="button">
+      <div class="${canEdit ? 'selectable' : 'hidden'}" role="button">
         <div class="name"></div>
         <div class="value-container">
           <div class="value"></div>
-          ${removeTokenMarkup}
+          <div class="remove-token" role="button">
+            <i class="fa fa-close"></i>
+          </div>
         </div>
       </div>
     `;
diff --git a/app/assets/javascripts/labels_select.js b/app/assets/javascripts/labels_select.js
index 84602cf9207..1e52963b1dd 100644
--- a/app/assets/javascripts/labels_select.js
+++ b/app/assets/javascripts/labels_select.js
@@ -8,7 +8,7 @@ import CreateLabelDropdown from './create_label';
 
 (function() {
   this.LabelsSelect = (function() {
-    function LabelsSelect(els) {
+    function LabelsSelect(els, options = {}) {
       var _this, $els;
       _this = this;
 
@@ -58,6 +58,7 @@ import CreateLabelDropdown from './create_label';
           labelHTMLTemplate = _.template('<% _.each(labels, function(label){ %> <a href="<%- ["",issueURLSplit[1], issueURLSplit[2],""].join("/") %>issues?label_name[]=<%- encodeURIComponent(label.title) %>"> <span class="label has-tooltip color-label" title="<%- label.description %>" style="background-color: <%- label.color %>; color: <%- label.text_color %>;"> <%- label.title %> </span> </a> <% }); %>');
           labelNoneHTMLTemplate = '<span class="no-value">None</span>';
         }
+        const handleClick = options.handleClick;
 
         $sidebarLabelTooltip.tooltip();
 
@@ -316,9 +317,9 @@ import CreateLabelDropdown from './create_label';
           },
           multiSelect: $dropdown.hasClass('js-multiselect'),
           vue: $dropdown.hasClass('js-issue-board-sidebar'),
-          clicked: function(options) {
-            const { $el, e, isMarking } = options;
-            const label = options.selectedObj;
+          clicked: function(clickEvent) {
+            const { $el, e, isMarking } = clickEvent;
+            const label = clickEvent.selectedObj;
 
             var isIssueIndex, isMRIndex, page, boardsModel;
             var fadeOutLoader = () => {
@@ -391,6 +392,10 @@ import CreateLabelDropdown from './create_label';
                 .then(fadeOutLoader)
                 .catch(fadeOutLoader);
             }
+            else if (handleClick) {
+              e.preventDefault();
+              handleClick(label);
+            }
             else {
               if ($dropdown.hasClass('js-multiselect')) {
 
diff --git a/app/assets/javascripts/milestone_select.js b/app/assets/javascripts/milestone_select.js
index e7d5325a509..74e5a4f1cea 100644
--- a/app/assets/javascripts/milestone_select.js
+++ b/app/assets/javascripts/milestone_select.js
@@ -5,7 +5,7 @@ import _ from 'underscore';
 
 (function() {
   this.MilestoneSelect = (function() {
-    function MilestoneSelect(currentProject, els) {
+    function MilestoneSelect(currentProject, els, options = {}) {
       var _this, $els;
       if (currentProject != null) {
         _this = this;
@@ -136,19 +136,26 @@ import _ from 'underscore';
           },
           opened: function(e) {
             const $el = $(e.currentTarget);
-            if ($dropdown.hasClass('js-issue-board-sidebar')) {
+            if ($dropdown.hasClass('js-issue-board-sidebar') || options.handleClick) {
               selectedMilestone = $dropdown[0].dataset.selected || selectedMilestoneDefault;
             }
             $('a.is-active', $el).removeClass('is-active');
             $(`[data-milestone-id="${selectedMilestone}"] > a`, $el).addClass('is-active');
           },
           vue: $dropdown.hasClass('js-issue-board-sidebar'),
-          clicked: function(options) {
-            const { $el, e } = options;
-            let selected = options.selectedObj;
+          clicked: function(clickEvent) {
+            const { $el, e } = clickEvent;
+            let selected = clickEvent.selectedObj;
 
             var data, isIssueIndex, isMRIndex, isSelecting, page, boardsStore;
             if (!selected) return;
+
+            if (options.handleClick) {
+              e.preventDefault();
+              options.handleClick(selected);
+              return;
+            }
+
             page = $('body').attr('data-page');
             isIssueIndex = page === 'projects:issues:index';
             isMRIndex = (page === page && page === 'projects:merge_requests:index');
diff --git a/app/assets/javascripts/users_select.js b/app/assets/javascripts/users_select.js
index a0883b32593..759cc9925f4 100644
--- a/app/assets/javascripts/users_select.js
+++ b/app/assets/javascripts/users_select.js
@@ -6,7 +6,7 @@ import _ from 'underscore';
 // TODO: remove eventHub hack after code splitting refactor
 window.emitSidebarEvent = window.emitSidebarEvent || $.noop;
 
-function UsersSelect(currentUser, els) {
+function UsersSelect(currentUser, els, options = {}) {
   var $els;
   this.users = this.users.bind(this);
   this.user = this.user.bind(this);
@@ -20,6 +20,8 @@ function UsersSelect(currentUser, els) {
     }
   }
 
+  const { handleClick } = options;
+
   $els = $(els);
 
   if (!els) {
@@ -442,6 +444,9 @@ function UsersSelect(currentUser, els) {
           }
           if ($el.closest('.add-issues-modal').length) {
             gl.issueBoards.ModalStore.store.filter[$dropdown.data('field-name')] = user.id;
+          } else if (handleClick) {
+            e.preventDefault();
+            handleClick(user, isMarking);
           } else if ($dropdown.hasClass('js-filter-submit') && (isIssueIndex || isMRIndex)) {
             return Issuable.filterResults($dropdown.closest('form'));
           } else if ($dropdown.hasClass('js-filter-submit')) {
diff --git a/app/assets/javascripts/vue_shared/components/loading_icon.vue b/app/assets/javascripts/vue_shared/components/loading_icon.vue
index 15581d5c2a0..494fe4468d9 100644
--- a/app/assets/javascripts/vue_shared/components/loading_icon.vue
+++ b/app/assets/javascripts/vue_shared/components/loading_icon.vue
@@ -18,6 +18,12 @@
         required: false,
         default: false,
       },
+
+      class: {
+        type: String,
+        required: false,
+        default: '',
+      },
     },
 
     computed: {
@@ -25,7 +31,7 @@
         return this.inline ? 'span' : 'div';
       },
       cssClass() {
-        return `fa-${this.size}x`;
+        return `fa-${this.size}x ${this.class}`.trim();
       },
     },
   };
diff --git a/app/assets/javascripts/vue_shared/components/popup_dialog.vue b/app/assets/javascripts/vue_shared/components/popup_dialog.vue
index 9e8c10bdc1a..d326277d1d6 100644
--- a/app/assets/javascripts/vue_shared/components/popup_dialog.vue
+++ b/app/assets/javascripts/vue_shared/components/popup_dialog.vue
@@ -5,17 +5,30 @@ export default {
   props: {
     title: {
       type: String,
-      required: true,
+      required: false,
     },
     text: {
       type: String,
       required: false,
+<<<<<<< HEAD
+=======
+    },
+    hideFooter: {
+      type: Boolean,
+      required: false,
+      default: false,
+>>>>>>> e8a46294c0... apply changes for JS and CSS from gitlab-ee!2912
     },
     kind: {
       type: String,
       required: false,
       default: 'primary',
     },
+    modalDialogClass: {
+      type: String,
+      required: false,
+      default: '',
+    },
     closeKind: {
       type: String,
       required: false,
@@ -30,6 +43,11 @@ export default {
       type: String,
       required: true,
     },
+    submitDisabled: {
+      type: Boolean,
+      required: false,
+      default: false,
+    },
   },
 
   computed: {
@@ -57,26 +75,60 @@ export default {
 </script>
 
 <template>
-<div
-  class="modal popup-dialog"
-  role="dialog"
-  tabindex="-1">
-  <div class="modal-dialog" role="document">
-    <div class="modal-content">
-      <div class="modal-header">
-        <button type="button"
-          class="close"
-          @click="close"
-          aria-label="Close">
-          <span aria-hidden="true">&times;</span>
-        </button>
-        <h4 class="modal-title">{{this.title}}</h4>
-      </div>
-      <div class="modal-body">
-        <slot name="body" :text="text">
-          <p>{{text}}</p>
+<div class="modal-open">
+  <div
+    class="modal popup-dialog"
+    role="dialog"
+    tabindex="-1"
+  >
+    <div
+      :class="modalDialogClass"
+      class="modal-dialog"
+      role="document"
+    >
+      <div class="modal-content">
+        <div class="modal-header">
+          <slot name="header">
+            <h4 class="modal-title pull-left">
+              {{this.title}}
+            </h4>
+            <button
+              type="button"
+              class="close pull-right"
+              @click="close"
+              aria-label="Close"
+            >
+              <span aria-hidden="true">&times;</span>
+            </button>
+          </slot>
+        </div>
+        <div class="modal-body">
+          <slot name="body" :text="text">
+            <p>{{this.text}}</p>
+          </slot>
+        </div>
+        <slot name="footer">
+          <div class="modal-footer" v-if="!hideFooter">
+            <button
+              type="button"
+              class="btn btn-default pull-right"
+              @click="close"
+            >
+              Cancel
+            </button>
+            <button
+              type="button"
+              class="btn pull-left"
+              :disabled="submitDisabled"
+              :class="btnKindClass"
+              @click="emitSubmit(true)"
+            >
+              {{primaryButtonLabel}}
+            </button>
+          </div>
         </slot>
       </div>
+<<<<<<< HEAD
       <div class="modal-footer">
         <button
           type="button"
@@ -93,7 +145,10 @@ export default {
           {{ primaryButtonLabel }}
         </button>
       </div>
+=======
+>>>>>>> e8a46294c0... apply changes for JS and CSS from gitlab-ee!2912
     </div>
   </div>
+  <div class="modal-backdrop fade in" />
 </div>
 </template>
diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss
index 96f9dda26c4..1cfd7ef01a8 100644
--- a/app/assets/stylesheets/framework/common.scss
+++ b/app/assets/stylesheets/framework/common.scss
@@ -4,6 +4,9 @@
 .cred { color: $common-red; }
 .cgreen { color: $common-green; }
 .cdark { color: $common-gray-dark; }
+.text-secondary {
+  color: $gl-text-color-secondary;
+}
 
 /** COMMON CLASSES **/
 .prepend-top-0 { margin-top: 0; }
diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss
index 1aa53b8f8cf..3ff124496d2 100644
--- a/app/assets/stylesheets/framework/dropdowns.scss
+++ b/app/assets/stylesheets/framework/dropdowns.scss
@@ -37,6 +37,7 @@
   .dropdown-menu-nav {
     @include set-visible;
     display: block;
+    min-height: 40px;
 
     @media (max-width: $screen-xs-max) {
       width: 100%;
diff --git a/app/assets/stylesheets/framework/modal.scss b/app/assets/stylesheets/framework/modal.scss
index 1cebd02df48..d218fb6d702 100644
--- a/app/assets/stylesheets/framework/modal.scss
+++ b/app/assets/stylesheets/framework/modal.scss
@@ -42,3 +42,11 @@ body.modal-open {
     width: 98%;
   }
 }
+
+.modal.popup-dialog {
+  display: block;
+}
+
+.modal-body {
+  background-color: $modal-body-bg;
+}
diff --git a/app/assets/stylesheets/framework/tw_bootstrap_variables.scss b/app/assets/stylesheets/framework/tw_bootstrap_variables.scss
index 3ea77eb7a43..a23131e0818 100644
--- a/app/assets/stylesheets/framework/tw_bootstrap_variables.scss
+++ b/app/assets/stylesheets/framework/tw_bootstrap_variables.scss
@@ -164,3 +164,36 @@ $pre-border-color: $border-color;
 $table-bg-accent: $gray-light;
 
 $zindex-popover: 900;
+
+//== Modals
+//
+//##
+
+//** Padding applied to the modal body
+$modal-inner-padding: $gl-padding;
+
+//** Padding applied to the modal title
+$modal-title-padding: $gl-padding;
+//** Modal title line-height
+// $modal-title-line-height:     $line-height-base
+
+//** Background color of modal content area
+$modal-content-bg: $gray-light;
+$modal-body-bg: $white-light;
+//** Modal content border color
+// $modal-content-border-color:                   rgba(0,0,0,.2)
+//** Modal content border color **for IE8**
+// $modal-content-fallback-border-color:          #999
+
+//** Modal backdrop background color
+// $modal-backdrop-bg:           #000
+//** Modal backdrop opacity
+// $modal-backdrop-opacity:      .5
+//** Modal header border color
+// $modal-header-border-color:   #e5e5e5
+//** Modal footer border color
+// $modal-footer-border-color:   $modal-header-border-color
+
+// $modal-lg:                    900px
+// $modal-md:                    600px
+// $modal-sm:                    300px
diff --git a/app/assets/stylesheets/pages/repo.scss b/app/assets/stylesheets/pages/repo.scss
index 6a363b1710e..e8c7f8a8fc0 100644
--- a/app/assets/stylesheets/pages/repo.scss
+++ b/app/assets/stylesheets/pages/repo.scss
@@ -7,19 +7,6 @@
   background: $black-transparent;
 }
 
-.modal.popup-dialog {
-  display: block;
-  background-color: $black-transparent;
-  z-index: 2100;
-
-  @media (min-width: $screen-md-min) {
-    .modal-dialog {
-      width: 600px;
-      margin: 30px auto;
-    }
-  }
-}
-
 .project-refs-form,
 .project-refs-target-form {
   display: inline-block;
-- 
2.30.9