summaryrefslogtreecommitdiff
path: root/app/assets
diff options
context:
space:
mode:
Diffstat (limited to 'app/assets')
-rw-r--r--app/assets/javascripts/gl_dropdown.js8
-rw-r--r--app/assets/javascripts/issuable_form.js52
-rw-r--r--app/assets/javascripts/issue_show/components/app.vue21
-rw-r--r--app/assets/javascripts/issue_show/components/fields/project_move.vue83
-rw-r--r--app/assets/javascripts/issue_show/components/form.vue14
-rw-r--r--app/assets/javascripts/issue_show/index.js2
-rw-r--r--app/assets/javascripts/issue_show/stores/index.js1
-rw-r--r--app/assets/javascripts/right_sidebar.js9
-rw-r--r--app/assets/javascripts/sidebar/components/assignees/assignee_title.js2
-rw-r--r--app/assets/javascripts/sidebar/lib/sidebar_move_issue.js85
-rw-r--r--app/assets/javascripts/sidebar/services/sidebar_service.js20
-rw-r--r--app/assets/javascripts/sidebar/sidebar_bundle.js7
-rw-r--r--app/assets/javascripts/sidebar/sidebar_mediator.js29
-rw-r--r--app/assets/javascripts/sidebar/stores/sidebar_store.js10
-rw-r--r--app/assets/stylesheets/framework/dropdowns.scss7
-rw-r--r--app/assets/stylesheets/pages/issuable.scss20
16 files changed, 185 insertions, 185 deletions
diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js
index b62acfcd445..d65bbc0d808 100644
--- a/app/assets/javascripts/gl_dropdown.js
+++ b/app/assets/javascripts/gl_dropdown.js
@@ -486,7 +486,7 @@ GitLabDropdown = (function() {
GitLabDropdown.prototype.shouldPropagate = function(e) {
var $target;
- if (this.options.multiSelect) {
+ if (this.options.multiSelect || this.options.shouldPropagate === false) {
$target = $(e.target);
if ($target && !$target.hasClass('dropdown-menu-close') &&
!$target.hasClass('dropdown-menu-close-icon') &&
@@ -546,10 +546,10 @@ GitLabDropdown = (function() {
};
GitLabDropdown.prototype.positionMenuAbove = function() {
- var $button = $(this.el);
var $menu = this.dropdown.find('.dropdown-menu');
- $menu.css('top', ($button.height() + $menu.height()) * -1);
+ $menu.css('top', 'initial');
+ $menu.css('bottom', '100%');
};
GitLabDropdown.prototype.hidden = function(e) {
@@ -698,7 +698,7 @@ GitLabDropdown = (function() {
GitLabDropdown.prototype.noResults = function() {
var html;
- return html = "<li class='dropdown-menu-empty-link'> <a href='#' class='is-focused'> No matching results. </a> </li>";
+ return html = '<li class="dropdown-menu-empty-link"><a href="#" class="is-focused">No matching results</a></li>';
};
GitLabDropdown.prototype.rowClicked = function(el) {
diff --git a/app/assets/javascripts/issuable_form.js b/app/assets/javascripts/issuable_form.js
index 3f848e0859b..470c39c6f76 100644
--- a/app/assets/javascripts/issuable_form.js
+++ b/app/assets/javascripts/issuable_form.js
@@ -10,8 +10,6 @@ import ZenMode from './zen_mode';
(function() {
this.IssuableForm = (function() {
- IssuableForm.prototype.issueMoveConfirmMsg = 'Are you sure you want to move this issue to another project?';
-
IssuableForm.prototype.wipRegex = /^\s*(\[WIP\]\s*|WIP:\s*|WIP\s+)+\s*/i;
function IssuableForm(form) {
@@ -26,7 +24,6 @@ import ZenMode from './zen_mode';
new ZenMode();
this.titleField = this.form.find("input[name*='[title]']");
this.descriptionField = this.form.find("textarea[name*='[description]']");
- this.issueMoveField = this.form.find("#move_to_project_id");
if (!(this.titleField.length && this.descriptionField.length)) {
return;
}
@@ -34,7 +31,6 @@ import ZenMode from './zen_mode';
this.form.on("submit", this.handleSubmit);
this.form.on("click", ".btn-cancel", this.resetAutosave);
this.initWip();
- this.initMoveDropdown();
$issuableDueDate = $('#issuable-due-date');
if ($issuableDueDate.length) {
calendar = new Pikaday({
@@ -56,12 +52,6 @@ import ZenMode from './zen_mode';
};
IssuableForm.prototype.handleSubmit = function() {
- var fieldId = (this.issueMoveField != null) ? this.issueMoveField.val() : null;
- if ((parseInt(fieldId, 10) || 0) > 0) {
- if (!confirm(this.issueMoveConfirmMsg)) {
- return false;
- }
- }
return this.resetAutosave();
};
@@ -113,48 +103,6 @@ import ZenMode from './zen_mode';
return this.titleField.val("WIP: " + (this.titleField.val()));
};
- IssuableForm.prototype.initMoveDropdown = function() {
- var $moveDropdown, pageSize;
- $moveDropdown = $('.js-move-dropdown');
- if ($moveDropdown.length) {
- pageSize = $moveDropdown.data('page-size');
- return $('.js-move-dropdown').select2({
- ajax: {
- url: $moveDropdown.data('projects-url'),
- quietMillis: 125,
- data: function(term, page, context) {
- return {
- search: term,
- offset_id: context
- };
- },
- results: function(data) {
- var context,
- more;
-
- if (data.length >= pageSize)
- more = true;
-
- if (data[data.length - 1])
- context = data[data.length - 1].id;
-
- return {
- results: data,
- more: more,
- context: context
- };
- }
- },
- formatResult: function(project) {
- return project.name_with_namespace;
- },
- formatSelection: function(project) {
- return project.name_with_namespace;
- }
- });
- }
- };
-
return IssuableForm;
})();
}).call(window);
diff --git a/app/assets/javascripts/issue_show/components/app.vue b/app/assets/javascripts/issue_show/components/app.vue
index eaaafd4c149..e115ee40219 100644
--- a/app/assets/javascripts/issue_show/components/app.vue
+++ b/app/assets/javascripts/issue_show/components/app.vue
@@ -17,10 +17,6 @@ export default {
required: true,
type: String,
},
- canMove: {
- required: true,
- type: Boolean,
- },
canUpdate: {
required: true,
type: Boolean,
@@ -96,10 +92,6 @@ export default {
type: String,
required: true,
},
- projectsAutocompletePath: {
- type: String,
- required: true,
- },
},
data() {
const store = new Store({
@@ -142,7 +134,6 @@ export default {
confidential: this.isConfidential,
description: this.state.descriptionText,
lockedWarningVisible: false,
- move_to_project_id: 0,
updateLoading: false,
});
}
@@ -151,16 +142,6 @@ export default {
this.showForm = false;
},
updateIssuable() {
- const canPostUpdate = this.store.formState.move_to_project_id !== 0 ?
- confirm('Are you sure you want to move this issue to another project?') : true; // eslint-disable-line no-alert
-
- if (!canPostUpdate) {
- this.store.setFormState({
- updateLoading: false,
- });
- return;
- }
-
this.service.updateIssuable(this.store.formState)
.then(res => res.json())
.then((data) => {
@@ -239,14 +220,12 @@ export default {
<form-component
v-if="canUpdate && showForm"
:form-state="formState"
- :can-move="canMove"
:can-destroy="canDestroy"
:issuable-templates="issuableTemplates"
:markdown-docs-path="markdownDocsPath"
:markdown-preview-path="markdownPreviewPath"
:project-path="projectPath"
:project-namespace="projectNamespace"
- :projects-autocomplete-path="projectsAutocompletePath"
/>
<div v-else>
<title-component
diff --git a/app/assets/javascripts/issue_show/components/fields/project_move.vue b/app/assets/javascripts/issue_show/components/fields/project_move.vue
deleted file mode 100644
index e514bebc5f6..00000000000
--- a/app/assets/javascripts/issue_show/components/fields/project_move.vue
+++ /dev/null
@@ -1,83 +0,0 @@
-<script>
- import tooltip from '../../../vue_shared/directives/tooltip';
-
- export default {
- directives: {
- tooltip,
- },
- props: {
- formState: {
- type: Object,
- required: true,
- },
- projectsAutocompletePath: {
- type: String,
- required: true,
- },
- },
- mounted() {
- const $moveDropdown = $(this.$refs['move-dropdown']);
-
- $moveDropdown.select2({
- ajax: {
- url: this.projectsAutocompletePath,
- quietMillis: 125,
- data(term, page, context) {
- return {
- search: term,
- offset_id: context,
- };
- },
- results(data) {
- const more = data.length >= 50;
- const context = data[data.length - 1] ? data[data.length - 1].id : null;
-
- return {
- results: data,
- more,
- context,
- };
- },
- },
- formatResult(project) {
- return project.name_with_namespace;
- },
- formatSelection(project) {
- return project.name_with_namespace;
- },
- })
- .on('change', (e) => {
- this.formState.move_to_project_id = parseInt(e.target.value, 10);
- });
- },
- beforeDestroy() {
- $(this.$refs['move-dropdown']).select2('destroy');
- },
- };
-</script>
-
-<template>
- <fieldset>
- <label
- for="issuable-move"
- class="sr-only">
- Move
- </label>
- <div class="issuable-form-select-holder append-right-5">
- <input
- ref="move-dropdown"
- type="hidden"
- id="issuable-move"
- data-placeholder="Move to a different project" />
- </div>
- <span
- v-tooltip
- data-placement="auto top"
- title="Moving an issue will copy the discussion to a different project and close it here. All participants will be notified of the new location.">
- <i
- class="fa fa-question-circle"
- aria-hidden="true">
- </i>
- </span>
- </fieldset>
-</template>
diff --git a/app/assets/javascripts/issue_show/components/form.vue b/app/assets/javascripts/issue_show/components/form.vue
index d9b53bc55cf..6a2dd502fe2 100644
--- a/app/assets/javascripts/issue_show/components/form.vue
+++ b/app/assets/javascripts/issue_show/components/form.vue
@@ -4,15 +4,10 @@
import descriptionField from './fields/description.vue';
import editActions from './edit_actions.vue';
import descriptionTemplate from './fields/description_template.vue';
- import projectMove from './fields/project_move.vue';
import confidentialCheckbox from './fields/confidential_checkbox.vue';
export default {
props: {
- canMove: {
- type: Boolean,
- required: true,
- },
canDestroy: {
type: Boolean,
required: true,
@@ -42,10 +37,6 @@
type: String,
required: true,
},
- projectsAutocompletePath: {
- type: String,
- required: true,
- },
},
components: {
lockedWarning,
@@ -53,7 +44,6 @@
descriptionField,
descriptionTemplate,
editActions,
- projectMove,
confidentialCheckbox,
},
computed: {
@@ -93,10 +83,6 @@
:markdown-docs-path="markdownDocsPath" />
<confidential-checkbox
:form-state="formState" />
- <project-move
- v-if="canMove"
- :form-state="formState"
- :projects-autocomplete-path="projectsAutocompletePath" />
<edit-actions
:form-state="formState"
:can-destroy="canDestroy" />
diff --git a/app/assets/javascripts/issue_show/index.js b/app/assets/javascripts/issue_show/index.js
index 60b69b300fd..8053ef57e6c 100644
--- a/app/assets/javascripts/issue_show/index.js
+++ b/app/assets/javascripts/issue_show/index.js
@@ -28,7 +28,6 @@ document.addEventListener('DOMContentLoaded', () => {
props: {
canUpdate: this.canUpdate,
canDestroy: this.canDestroy,
- canMove: this.canMove,
endpoint: this.endpoint,
issuableRef: this.issuableRef,
initialTitleHtml: this.initialTitleHtml,
@@ -41,7 +40,6 @@ document.addEventListener('DOMContentLoaded', () => {
markdownDocsPath: this.markdownDocsPath,
projectPath: this.projectPath,
projectNamespace: this.projectNamespace,
- projectsAutocompletePath: this.projectsAutocompletePath,
updatedAt: this.updatedAt,
updatedByName: this.updatedByName,
updatedByPath: this.updatedByPath,
diff --git a/app/assets/javascripts/issue_show/stores/index.js b/app/assets/javascripts/issue_show/stores/index.js
index 0c8bd6f1cc3..f4639e9ed2a 100644
--- a/app/assets/javascripts/issue_show/stores/index.js
+++ b/app/assets/javascripts/issue_show/stores/index.js
@@ -6,7 +6,6 @@ export default class Store {
confidential: false,
description: '',
lockedWarningVisible: false,
- move_to_project_id: 0,
updateLoading: false,
};
}
diff --git a/app/assets/javascripts/right_sidebar.js b/app/assets/javascripts/right_sidebar.js
index fa958d75fa4..4c87d46c96e 100644
--- a/app/assets/javascripts/right_sidebar.js
+++ b/app/assets/javascripts/right_sidebar.js
@@ -157,11 +157,16 @@ import SidebarHeightManager from './sidebar_height_manager';
Sidebar.prototype.openDropdown = function(blockOrName) {
var $block;
$block = _.isString(blockOrName) ? this.getBlock(blockOrName) : blockOrName;
- $block.find('.edit-link').trigger('click');
if (!this.isOpen()) {
this.setCollapseAfterUpdate($block);
- return this.toggleSidebar('open');
+ this.toggleSidebar('open');
}
+
+ // Wait for the sidebar to trigger('click') open
+ // so it doesn't cause our dropdown to close preemptively
+ setTimeout(() => {
+ $block.find('.js-sidebar-dropdown-toggle').trigger('click');
+ });
};
Sidebar.prototype.setCollapseAfterUpdate = function($block) {
diff --git a/app/assets/javascripts/sidebar/components/assignees/assignee_title.js b/app/assets/javascripts/sidebar/components/assignees/assignee_title.js
index 5a6e47e566e..77f070d48cc 100644
--- a/app/assets/javascripts/sidebar/components/assignees/assignee_title.js
+++ b/app/assets/javascripts/sidebar/components/assignees/assignee_title.js
@@ -36,7 +36,7 @@ export default {
/>
<a
v-if="editable"
- class="edit-link pull-right"
+ class="js-sidebar-dropdown-toggle edit-link pull-right"
href="#"
>
Edit
diff --git a/app/assets/javascripts/sidebar/lib/sidebar_move_issue.js b/app/assets/javascripts/sidebar/lib/sidebar_move_issue.js
new file mode 100644
index 00000000000..1c15a1b877a
--- /dev/null
+++ b/app/assets/javascripts/sidebar/lib/sidebar_move_issue.js
@@ -0,0 +1,85 @@
+/* global Flash */
+
+function isValidProjectId(id) {
+ return id > 0;
+}
+
+class SidebarMoveIssue {
+ constructor(mediator, dropdownToggle, confirmButton) {
+ this.mediator = mediator;
+
+ this.$dropdownToggle = $(dropdownToggle);
+ this.$confirmButton = $(confirmButton);
+
+ this.onConfirmClickedWrapper = this.onConfirmClicked.bind(this);
+ }
+
+ init() {
+ this.initDropdown();
+ this.addEventListeners();
+ }
+
+ destroy() {
+ this.removeEventListeners();
+ }
+
+ initDropdown() {
+ this.$dropdownToggle.glDropdown({
+ search: {
+ fields: ['name_with_namespace'],
+ },
+ showMenuAbove: true,
+ selectable: true,
+ filterable: true,
+ filterRemote: true,
+ multiSelect: false,
+ // Keep the dropdown open after selecting an option
+ shouldPropagate: false,
+ data: (searchTerm, callback) => {
+ this.mediator.fetchAutocompleteProjects(searchTerm)
+ .then(callback)
+ .catch(() => new Flash('An error occured while fetching projects autocomplete.'));
+ },
+ renderRow: project => `
+ <li>
+ <a href="#" class="js-move-issue-dropdown-item">
+ ${project.name_with_namespace}
+ </a>
+ </li>
+ `,
+ clicked: (options) => {
+ const project = options.selectedObj;
+ const selectedProjectId = options.isMarking ? project.id : 0;
+ this.mediator.setMoveToProjectId(selectedProjectId);
+
+ this.$confirmButton.attr('disabled', !isValidProjectId(selectedProjectId));
+ },
+ });
+ }
+
+ addEventListeners() {
+ this.$confirmButton.on('click', this.onConfirmClickedWrapper);
+ }
+
+ removeEventListeners() {
+ this.$confirmButton.off('click', this.onConfirmClickedWrapper);
+ }
+
+ onConfirmClicked() {
+ if (isValidProjectId(this.mediator.store.moveToProjectId)) {
+ this.$confirmButton
+ .disable()
+ .addClass('is-loading');
+
+ this.mediator.moveIssue()
+ .catch(() => {
+ Flash('An error occured while moving the issue.');
+ this.$confirmButton
+ .enable()
+ .removeClass('is-loading');
+ });
+ }
+ }
+}
+
+export default SidebarMoveIssue;
diff --git a/app/assets/javascripts/sidebar/services/sidebar_service.js b/app/assets/javascripts/sidebar/services/sidebar_service.js
index 5a82d01dc41..604648407a4 100644
--- a/app/assets/javascripts/sidebar/services/sidebar_service.js
+++ b/app/assets/javascripts/sidebar/services/sidebar_service.js
@@ -4,9 +4,11 @@ import VueResource from 'vue-resource';
Vue.use(VueResource);
export default class SidebarService {
- constructor(endpoint) {
+ constructor(endpointMap) {
if (!SidebarService.singleton) {
- this.endpoint = endpoint;
+ this.endpoint = endpointMap.endpoint;
+ this.moveIssueEndpoint = endpointMap.moveIssueEndpoint;
+ this.projectsAutocompleteEndpoint = endpointMap.projectsAutocompleteEndpoint;
SidebarService.singleton = this;
}
@@ -25,4 +27,18 @@ export default class SidebarService {
emulateJSON: true,
});
}
+
+ getProjectsAutocomplete(searchTerm) {
+ return Vue.http.get(this.projectsAutocompleteEndpoint, {
+ params: {
+ search: searchTerm,
+ },
+ });
+ }
+
+ moveIssue(moveToProjectId) {
+ return Vue.http.post(this.moveIssueEndpoint, {
+ move_to_project_id: moveToProjectId,
+ });
+ }
}
diff --git a/app/assets/javascripts/sidebar/sidebar_bundle.js b/app/assets/javascripts/sidebar/sidebar_bundle.js
index 9edded3ead6..3d8972050a9 100644
--- a/app/assets/javascripts/sidebar/sidebar_bundle.js
+++ b/app/assets/javascripts/sidebar/sidebar_bundle.js
@@ -2,6 +2,7 @@ import Vue from 'vue';
import sidebarTimeTracking from './components/time_tracking/sidebar_time_tracking';
import sidebarAssignees from './components/assignees/sidebar_assignees';
import confidential from './components/confidential/confidential_issue_sidebar.vue';
+import SidebarMoveIssue from './lib/sidebar_move_issue';
import Mediator from './sidebar_mediator';
@@ -31,6 +32,12 @@ function domContentLoaded() {
service: mediator.service,
},
}).$mount(confidentialEl);
+
+ new SidebarMoveIssue(
+ mediator,
+ $('.js-move-issue'),
+ $('.js-move-issue-confirmation-button'),
+ ).init();
}
new Vue(sidebarTimeTracking).$mount('#issuable-time-tracker');
diff --git a/app/assets/javascripts/sidebar/sidebar_mediator.js b/app/assets/javascripts/sidebar/sidebar_mediator.js
index 721e92221cf..e38a8db4cc5 100644
--- a/app/assets/javascripts/sidebar/sidebar_mediator.js
+++ b/app/assets/javascripts/sidebar/sidebar_mediator.js
@@ -7,7 +7,11 @@ export default class SidebarMediator {
constructor(options) {
if (!SidebarMediator.singleton) {
this.store = new Store(options);
- this.service = new Service(options.endpoint);
+ this.service = new Service({
+ endpoint: options.endpoint,
+ moveIssueEndpoint: options.moveIssueEndpoint,
+ projectsAutocompleteEndpoint: options.projectsAutocompleteEndpoint,
+ });
SidebarMediator.singleton = this;
}
@@ -26,6 +30,10 @@ export default class SidebarMediator {
return this.service.update(field, selected.length === 0 ? [0] : selected);
}
+ setMoveToProjectId(projectId) {
+ this.store.setMoveToProjectId(projectId);
+ }
+
fetch() {
this.service.get()
.then(response => response.json())
@@ -35,4 +43,23 @@ export default class SidebarMediator {
})
.catch(() => new Flash('Error occured when fetching sidebar data'));
}
+
+ fetchAutocompleteProjects(searchTerm) {
+ return this.service.getProjectsAutocomplete(searchTerm)
+ .then(response => response.json())
+ .then((data) => {
+ this.store.setAutocompleteProjects(data);
+ return this.store.autocompleteProjects;
+ });
+ }
+
+ moveIssue() {
+ return this.service.moveIssue(this.store.moveToProjectId)
+ .then(response => response.json())
+ .then((data) => {
+ if (location.pathname !== data.web_url) {
+ gl.utils.visitUrl(data.web_url);
+ }
+ });
+ }
}
diff --git a/app/assets/javascripts/sidebar/stores/sidebar_store.js b/app/assets/javascripts/sidebar/stores/sidebar_store.js
index 3356dd0191f..cc04a2a3fcf 100644
--- a/app/assets/javascripts/sidebar/stores/sidebar_store.js
+++ b/app/assets/javascripts/sidebar/stores/sidebar_store.js
@@ -13,6 +13,8 @@ export default class SidebarStore {
this.isFetching = {
assignees: true,
};
+ this.autocompleteProjects = [];
+ this.moveToProjectId = 0;
SidebarStore.singleton = this;
}
@@ -53,4 +55,12 @@ export default class SidebarStore {
removeAllAssignees() {
this.assignees = [];
}
+
+ setAutocompleteProjects(projects) {
+ this.autocompleteProjects = projects;
+ }
+
+ setMoveToProjectId(moveToProjectId) {
+ this.moveToProjectId = moveToProjectId;
+ }
}
diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss
index 110b171676a..487b3148b14 100644
--- a/app/assets/stylesheets/framework/dropdowns.scss
+++ b/app/assets/stylesheets/framework/dropdowns.scss
@@ -193,7 +193,7 @@
min-width: 240px;
max-width: 500px;
margin-top: 2px;
- margin-bottom: 0;
+ margin-bottom: 2px;
font-size: 14px;
font-weight: $gl-font-weight-normal;
padding: 8px 0;
@@ -622,6 +622,11 @@
border-top: 1px solid $dropdown-divider-color;
}
+.dropdown-footer-content {
+ padding-left: 10px;
+ padding-right: 10px;
+}
+
.dropdown-due-date-footer {
padding-top: 0;
margin-left: 10px;
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index 4b0b238a767..6523376ccc3 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -473,7 +473,7 @@
padding-top: 6px;
}
- .open .dropdown-menu {
+ .dropdown-menu {
width: 100%;
}
}
@@ -486,6 +486,24 @@
}
}
+.sidebar-move-issue-dropdown {
+ @include new-style-dropdown;
+}
+
+.sidebar-move-issue-confirmation-button {
+ width: 100%;
+
+ &.is-loading {
+ .sidebar-move-issue-confirmation-loading-icon {
+ display: inline-block;
+ }
+ }
+}
+
+.sidebar-move-issue-confirmation-loading-icon {
+ display: none;
+}
+
.detail-page-description {
padding: 16px 0;