From 7c59f45d10a791e790f55821ca501d9cdeddbabf Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 4 Oct 2016 15:27:02 +0100 Subject: [PATCH 01/25] Added markup for sidebar --- .../javascripts/boards/boards_bundle.js.es6 | 4 +- .../boards/components/board_card.js.es6 | 4 ++ .../boards/components/board_sidebar.js.es6 | 25 ++++++++++++ .../boards/stores/boards_store.js.es6 | 4 +- .../boards/components/_card.html.haml | 3 +- .../boards/components/_sidebar.html.haml | 40 +++++++++++++++++++ app/views/projects/boards/show.html.haml | 10 +++-- 7 files changed, 83 insertions(+), 7 deletions(-) create mode 100644 app/assets/javascripts/boards/components/board_sidebar.js.es6 create mode 100644 app/views/projects/boards/components/_sidebar.html.haml diff --git a/app/assets/javascripts/boards/boards_bundle.js.es6 b/app/assets/javascripts/boards/boards_bundle.js.es6 index 91c12570e09..b3a2386bbf3 100644 --- a/app/assets/javascripts/boards/boards_bundle.js.es6 +++ b/app/assets/javascripts/boards/boards_bundle.js.es6 @@ -6,6 +6,7 @@ //= require_tree ./services //= require_tree ./mixins //= require ./components/board +//= require ./components/board_sidebar //= require ./components/new_list_dropdown //= require ./vue_resource_interceptor @@ -22,7 +23,8 @@ $(() => { gl.IssueBoardsApp = new Vue({ el: $boardApp, components: { - 'board': gl.issueBoards.Board + 'board': gl.issueBoards.Board, + 'board-sidebar': gl.issueBoards.BoardSidebar }, data: { state: Store.state, diff --git a/app/assets/javascripts/boards/components/board_card.js.es6 b/app/assets/javascripts/boards/components/board_card.js.es6 index 4a7cfeaeab2..1bd0b19b6da 100644 --- a/app/assets/javascripts/boards/components/board_card.js.es6 +++ b/app/assets/javascripts/boards/components/board_card.js.es6 @@ -37,6 +37,10 @@ $('.labels-filter .dropdown-toggle-text').text(labelToggleText); Store.updateFiltersUrl(); + }, + showIssue () { + Store.state.detailIssue = this.issue; + console.log(Store.state.detailIssue); } } }); diff --git a/app/assets/javascripts/boards/components/board_sidebar.js.es6 b/app/assets/javascripts/boards/components/board_sidebar.js.es6 new file mode 100644 index 00000000000..e26c8209b69 --- /dev/null +++ b/app/assets/javascripts/boards/components/board_sidebar.js.es6 @@ -0,0 +1,25 @@ +(() => { + const Store = gl.issueBoards.BoardsStore; + + window.gl = window.gl || {}; + window.gl.issueBoards = window.gl.issueBoards || {}; + + gl.issueBoards.BoardSidebar = Vue.extend({ + data() { + return { + issue: Store.state.detailIssue + }; + }, + ready: function () { + console.log(this.issue); + }, + watch: { + issue: { + handler () { + console.log('a'); + }, + deep: true + } + } + }); +})(); diff --git a/app/assets/javascripts/boards/stores/boards_store.js.es6 b/app/assets/javascripts/boards/stores/boards_store.js.es6 index bd07ee0c161..126e3f49fc8 100644 --- a/app/assets/javascripts/boards/stores/boards_store.js.es6 +++ b/app/assets/javascripts/boards/stores/boards_store.js.es6 @@ -4,7 +4,9 @@ gl.issueBoards.BoardsStore = { disabled: false, - state: {}, + state: { + detailIssue: {} + }, moving: { issue: {}, list: {} diff --git a/app/views/projects/boards/components/_card.html.haml b/app/views/projects/boards/components/_card.html.haml index d8f16022407..71f64d7827c 100644 --- a/app/views/projects/boards/components/_card.html.haml +++ b/app/views/projects/boards/components/_card.html.haml @@ -8,7 +8,8 @@ ":disabled" => "disabled", "track-by" => "id" } %li.card{ ":class" => "{ 'user-can-drag': !disabled && issue.id, 'is-disabled': disabled || !issue.id }", - ":index" => "index" } + ":index" => "index", + "@click" => "showIssue" } %h4.card-title = icon("eye-slash", class: "confidential-icon", "v-if" => "issue.confidential") %a{ ":href" => "issueLinkBase + '/' + issue.id", diff --git a/app/views/projects/boards/components/_sidebar.html.haml b/app/views/projects/boards/components/_sidebar.html.haml new file mode 100644 index 00000000000..6cec833d6dc --- /dev/null +++ b/app/views/projects/boards/components/_sidebar.html.haml @@ -0,0 +1,40 @@ +%board-sidebar{ "inline-template" => true } + %aside.right-sidebar.right-sidebar-expanded{ "v-if" => "showSidebar" } + .issuable-sidebar + .block.issuable-sidebar-header + %span.issuable-header-text.hide-collapsed.pull-left + %strong Test + %br/ + %span #13 + %a.gutter-toggle.pull-right.js-sidebar-toggle{ role: "button", href: "#", aria: { label: "Toggle sidebar" } } + = icon("times") + .block.assignee + .title.hide-collapsed + Assignee + = icon("spinner spin", class: "block-loading") + = link_to "Edit", "#", class: "edit-link pull-right" + .value.hide-collapsed + %span.assign-yourself.no-value + No assignee + \- + %a.js-assign-yourself{ href: "#" } + assign yourself + .block.milestone + .title.hide-collapsed + Milestone + = icon("spinner spin", class: "block-loading") + .value.hide-collapsed + %span.no-value + None + .block.due_date + .title.hide-collapsed + Due date + = icon("spinner spin", class: "block-loading") + .value.hide-collapsed + %span.no-value No due date + .block.labels + .title.hide-collapsed + Labels + = icon("spinner spin", class: "block-loading") + .value.issuable-show-labels.hide-collapsed + %span.no-value None diff --git a/app/views/projects/boards/show.html.haml b/app/views/projects/boards/show.html.haml index edbbd3f3d2a..10b495363a6 100644 --- a/app/views/projects/boards/show.html.haml +++ b/app/views/projects/boards/show.html.haml @@ -10,10 +10,12 @@ = render 'shared/issuable/filter', type: :boards -.boards-list#board-app{ "v-cloak" => true, +#board-app{ "v-cloak" => true, "data-endpoint" => "#{namespace_project_board_path(@project.namespace, @project)}", "data-disabled" => "#{!can?(current_user, :admin_list, @project)}", "data-issue-link-base" => "#{namespace_project_issues_path(@project.namespace, @project)}" } - .boards-app-loading.text-center{ "v-if" => "loading" } - = icon("spinner spin") - = render "projects/boards/components/board" + .boards-list + .boards-app-loading.text-center{ "v-if" => "loading" } + = icon("spinner spin") + = render "projects/boards/components/board" + = render "projects/boards/components/sidebar" From 6cece3f49eb8777929b8a18a4fea1b8249bbb9df Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 5 Oct 2016 08:49:40 +0100 Subject: [PATCH 02/25] Show clicked issue data in the sidebar --- .../boards/components/board_card.js.es6 | 3 +- .../boards/components/board_sidebar.js.es6 | 29 ++++++++++-- .../javascripts/boards/models/issue.js.es6 | 5 ++ .../boards/models/milestone.js.es6 | 6 +++ .../boards/stores/boards_store.js.es6 | 5 +- app/assets/javascripts/right_sidebar.js | 11 ++++- .../projects/boards/issues_controller.rb | 2 +- .../boards/components/_sidebar.html.haml | 47 ++++++------------- .../components/sidebar/_assignee.html.haml | 22 +++++++++ .../components/sidebar/_due_date.html.haml | 16 +++++++ .../components/sidebar/_labels.html.haml | 18 +++++++ .../components/sidebar/_milestone.html.haml | 16 +++++++ .../sidebar/_notifications.html.haml | 11 +++++ 13 files changed, 147 insertions(+), 44 deletions(-) create mode 100644 app/assets/javascripts/boards/models/milestone.js.es6 create mode 100644 app/views/projects/boards/components/sidebar/_assignee.html.haml create mode 100644 app/views/projects/boards/components/sidebar/_due_date.html.haml create mode 100644 app/views/projects/boards/components/sidebar/_labels.html.haml create mode 100644 app/views/projects/boards/components/sidebar/_milestone.html.haml create mode 100644 app/views/projects/boards/components/sidebar/_notifications.html.haml diff --git a/app/assets/javascripts/boards/components/board_card.js.es6 b/app/assets/javascripts/boards/components/board_card.js.es6 index 1bd0b19b6da..e61943de5a9 100644 --- a/app/assets/javascripts/boards/components/board_card.js.es6 +++ b/app/assets/javascripts/boards/components/board_card.js.es6 @@ -39,8 +39,7 @@ Store.updateFiltersUrl(); }, showIssue () { - Store.state.detailIssue = this.issue; - console.log(Store.state.detailIssue); + Store.detail.issue = this.issue; } } }); diff --git a/app/assets/javascripts/boards/components/board_sidebar.js.es6 b/app/assets/javascripts/boards/components/board_sidebar.js.es6 index e26c8209b69..c267b4f0817 100644 --- a/app/assets/javascripts/boards/components/board_sidebar.js.es6 +++ b/app/assets/javascripts/boards/components/board_sidebar.js.es6 @@ -7,18 +7,37 @@ gl.issueBoards.BoardSidebar = Vue.extend({ data() { return { - issue: Store.state.detailIssue + detail: Store.detail, + issue: {} }; }, - ready: function () { - console.log(this.issue); + computed: { + showSidebar () { + return Object.keys(this.issue).length; + } }, watch: { - issue: { + detail: { handler () { - console.log('a'); + this.issue = this.detail.issue; }, deep: true + }, + issue () { + if (this.showSidebar) { + this.$nextTick(() => { + new IssuableContext(); + new MilestoneSelect(); + new Sidebar(); + }); + } else { + $('.right-sidebar').getNiceScroll().remove(); + } + } + }, + methods: { + closeSidebar () { + this.detail.issue = {}; } } }); diff --git a/app/assets/javascripts/boards/models/issue.js.es6 b/app/assets/javascripts/boards/models/issue.js.es6 index eb082103de9..a8c28e27f41 100644 --- a/app/assets/javascripts/boards/models/issue.js.es6 +++ b/app/assets/javascripts/boards/models/issue.js.es6 @@ -3,12 +3,17 @@ class ListIssue { this.id = obj.iid; this.title = obj.title; this.confidential = obj.confidential; + this.dueDate = obj.due_date; this.labels = []; if (obj.assignee) { this.assignee = new ListUser(obj.assignee); } + if (obj.milestone) { + this.milestone = new ListMilestone(obj.milestone); + } + obj.labels.forEach((label) => { this.labels.push(new ListLabel(label)); }); diff --git a/app/assets/javascripts/boards/models/milestone.js.es6 b/app/assets/javascripts/boards/models/milestone.js.es6 new file mode 100644 index 00000000000..577adf11265 --- /dev/null +++ b/app/assets/javascripts/boards/models/milestone.js.es6 @@ -0,0 +1,6 @@ +class ListMilestone { + constructor (obj) { + this.id = obj.id; + this.title = obj.title; + } +} diff --git a/app/assets/javascripts/boards/stores/boards_store.js.es6 b/app/assets/javascripts/boards/stores/boards_store.js.es6 index 126e3f49fc8..a6c5739ee41 100644 --- a/app/assets/javascripts/boards/stores/boards_store.js.es6 +++ b/app/assets/javascripts/boards/stores/boards_store.js.es6 @@ -4,8 +4,9 @@ gl.issueBoards.BoardsStore = { disabled: false, - state: { - detailIssue: {} + state: {}, + detail: { + issue: {} }, moving: { issue: {}, diff --git a/app/assets/javascripts/right_sidebar.js b/app/assets/javascripts/right_sidebar.js index e3d5f413c77..221460f32e7 100644 --- a/app/assets/javascripts/right_sidebar.js +++ b/app/assets/javascripts/right_sidebar.js @@ -5,15 +5,24 @@ function Sidebar(currentUser) { this.toggleTodo = bind(this.toggleTodo, this); this.sidebar = $('aside'); + this.removeListeners(); this.addEventListeners(); } + Sidebar.prototype.removeListeners = function () { + this.sidebar.off('click', '.sidebar-collapsed-icon'); + $('.dropdown').off('hidden.gl.dropdown'); + $('.dropdown').off('loading.gl.dropdown'); + $('.dropdown').off('loaded.gl.dropdown'); + $(document).off('click', '.js-sidebar-toggle'); + } + Sidebar.prototype.addEventListeners = function() { this.sidebar.on('click', '.sidebar-collapsed-icon', this, this.sidebarCollapseClicked); $('.dropdown').on('hidden.gl.dropdown', this, this.onSidebarDropdownHidden); $('.dropdown').on('loading.gl.dropdown', this.sidebarDropdownLoading); $('.dropdown').on('loaded.gl.dropdown', this.sidebarDropdownLoaded); - $(document).off('click', '.js-sidebar-toggle').on('click', '.js-sidebar-toggle', function(e, triggered) { + $(document).on('click', '.js-sidebar-toggle', function(e, triggered) { var $allGutterToggleIcons, $this, $thisIcon; e.preventDefault(); $this = $(this); diff --git a/app/controllers/projects/boards/issues_controller.rb b/app/controllers/projects/boards/issues_controller.rb index 095af6c35eb..b5a56d11d32 100644 --- a/app/controllers/projects/boards/issues_controller.rb +++ b/app/controllers/projects/boards/issues_controller.rb @@ -73,7 +73,7 @@ module Projects def serialize_as_json(resource) resource.as_json( - only: [:iid, :title, :confidential], + only: [:iid, :title, :confidential, :due_date], include: { assignee: { only: [:id, :name, :username], methods: [:avatar_url] }, labels: { only: [:id, :title, :description, :color, :priority], methods: [:text_color] } diff --git a/app/views/projects/boards/components/_sidebar.html.haml b/app/views/projects/boards/components/_sidebar.html.haml index 6cec833d6dc..b03b8384220 100644 --- a/app/views/projects/boards/components/_sidebar.html.haml +++ b/app/views/projects/boards/components/_sidebar.html.haml @@ -3,38 +3,19 @@ .issuable-sidebar .block.issuable-sidebar-header %span.issuable-header-text.hide-collapsed.pull-left - %strong Test + %strong + {{ issue.title }} %br/ - %span #13 - %a.gutter-toggle.pull-right.js-sidebar-toggle{ role: "button", href: "#", aria: { label: "Toggle sidebar" } } + %span + = precede "#" do + {{ issue.id }} + %a.gutter-toggle.pull-right{ role: "button", + href: "#", + "@click" => "closeSidebar", + aria: { label: "Toggle sidebar" } } = icon("times") - .block.assignee - .title.hide-collapsed - Assignee - = icon("spinner spin", class: "block-loading") - = link_to "Edit", "#", class: "edit-link pull-right" - .value.hide-collapsed - %span.assign-yourself.no-value - No assignee - \- - %a.js-assign-yourself{ href: "#" } - assign yourself - .block.milestone - .title.hide-collapsed - Milestone - = icon("spinner spin", class: "block-loading") - .value.hide-collapsed - %span.no-value - None - .block.due_date - .title.hide-collapsed - Due date - = icon("spinner spin", class: "block-loading") - .value.hide-collapsed - %span.no-value No due date - .block.labels - .title.hide-collapsed - Labels - = icon("spinner spin", class: "block-loading") - .value.issuable-show-labels.hide-collapsed - %span.no-value None + = render "projects/boards/components/sidebar/assignee" + = render "projects/boards/components/sidebar/milestone" + = render "projects/boards/components/sidebar/due_date" + = render "projects/boards/components/sidebar/labels" + = render "projects/boards/components/sidebar/notifications" diff --git a/app/views/projects/boards/components/sidebar/_assignee.html.haml b/app/views/projects/boards/components/sidebar/_assignee.html.haml new file mode 100644 index 00000000000..93780fc929e --- /dev/null +++ b/app/views/projects/boards/components/sidebar/_assignee.html.haml @@ -0,0 +1,22 @@ +.block.assignee + .title.hide-collapsed + Assignee + = icon("spinner spin", class: "block-loading") + - if can?(current_user, :admin_issue, @project) + = link_to "Edit", "#", class: "edit-link pull-right" + .value.hide-collapsed + %span.assign-yourself.no-value{ "v-if" => "!issue.assignee" } + No assignee + - if can?(current_user, :admin_issue, @project) + \- + %a.js-assign-yourself{ href: "#" } + assign yourself + %a.author_link.bold{ href: "", + "v-if" => "issue.assignee" } + %img.avatar.avatar-inline.s32{ ":src" => "issue.assignee.avatar", + width: "32" } + %span.author + {{ issue.assignee.name }} + %span.username + = precede "@" do + {{ issue.assignee.username }} diff --git a/app/views/projects/boards/components/sidebar/_due_date.html.haml b/app/views/projects/boards/components/sidebar/_due_date.html.haml new file mode 100644 index 00000000000..9861523824a --- /dev/null +++ b/app/views/projects/boards/components/sidebar/_due_date.html.haml @@ -0,0 +1,16 @@ +.block.due_date + .title.hide-collapsed + Due date + = icon("spinner spin", class: "block-loading") + - if can?(current_user, :admin_issue, @project) + = link_to "Edit", "#", class: "edit-link pull-right" + .value.hide-collapsed + .value-content + %span.no-value{ "v-if" => "!issue.dueDate" } + No due date + %span.bold{ "v-if" => "issue.dueDate" } + {{ issue.dueDate }} + %span.no-value.js-remove-due-date-holder{ "v-if" => "issue.dueDate" } + \- + %a.js-remove-due-date{ href: "#", role: "button" } + remove due date diff --git a/app/views/projects/boards/components/sidebar/_labels.html.haml b/app/views/projects/boards/components/sidebar/_labels.html.haml new file mode 100644 index 00000000000..b135b134a96 --- /dev/null +++ b/app/views/projects/boards/components/sidebar/_labels.html.haml @@ -0,0 +1,18 @@ +.block.labels + .title.hide-collapsed + Labels + = icon("spinner spin", class: "block-loading") + - if can?(current_user, :admin_issue, @project) + = link_to "Edit", "#", class: "edit-link pull-right" + .value.issuable-show-labels.hide-collapsed + %span.no-value{ "v-if" => "issue.labels.length === 0" } + None + %a{ href: "#", + "v-for" => "label in issue.labels" } + %span.label.color-label.has-tooltip{ ":style" => "{ backgroundColor: label.color, color: label.textColor }" } + {{ label.title }} + .selectbox.hide-collapsed + %input{ type: "hidden", + name: "issue[label_names][]", + "v-for" => "label in issue.labels", + ":value" => "label.id" } diff --git a/app/views/projects/boards/components/sidebar/_milestone.html.haml b/app/views/projects/boards/components/sidebar/_milestone.html.haml new file mode 100644 index 00000000000..14f521f57a4 --- /dev/null +++ b/app/views/projects/boards/components/sidebar/_milestone.html.haml @@ -0,0 +1,16 @@ +.block.milestone + .title.hide-collapsed + Milestone + = icon("spinner spin", class: "block-loading") + - if can?(current_user, :admin_issue, @project) + = link_to "Edit", "#", class: "edit-link pull-right" + .value + %span.no-value{ "v-if" => "!issue.milestone" } + None + %span.bold.has-tooltip{ "v-if" => "issue.milestone" } + {{ issue.milestone.title }} + .selectbox + .dropdown + %button.dropdown-menu-toggle + Milestone + -# = dropdown_tag('Milestone', options: { title: 'Assign milestone', toggle_class: 'js-milestone-select js-extra-options', filter: true, dropdown_class: 'dropdown-menu-selectable', placeholder: 'Search milestones', data: { show_no: true, field_name: "issue[milestone_id]", project_id: @project.id, milestones: namespace_project_milestones_path(@project.namespace, @project, :json), ability_name: "issue", use_id: true }}) diff --git a/app/views/projects/boards/components/sidebar/_notifications.html.haml b/app/views/projects/boards/components/sidebar/_notifications.html.haml new file mode 100644 index 00000000000..e28be47fd8e --- /dev/null +++ b/app/views/projects/boards/components/sidebar/_notifications.html.haml @@ -0,0 +1,11 @@ +- if current_user + .block.light.subscription{ data: { url: '' } } + .title.hide-collapsed + Notifications + %button.btn.btn-block.btn-default.js-subscribe-button.issuable-subscribe-button.hide-collapsed{ type: "button" } + Unsubscribe + .subscription-status.hide-collapsed{ data: { status: '' } } + .unsubscribed{class: ( 'hidden' if true )} + You're not receiving notifications from this thread. + .subscribed{class: ( 'hidden' unless true )} + You're receiving notifications because you're subscribed to this thread. From 6b3e3aeb9e6b78ade960a4fad1da906fa023cd5e Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 5 Oct 2016 12:20:59 +0100 Subject: [PATCH 03/25] Sidebar details update when changing Need to get working the subscription Styling updates --- .../javascripts/boards/boards_bundle.js.es6 | 1 + .../boards/components/board_sidebar.js.es6 | 8 +++- .../boards/filters/due_date_filters.js.es6 | 4 ++ .../javascripts/boards/models/issue.js.es6 | 14 ++++++ .../boards/services/board_service.js.es6 | 2 - app/assets/javascripts/due_date_select.js | 29 ++++++++++-- app/assets/javascripts/labels_select.js | 28 +++++++++++- app/assets/javascripts/milestone_select.js | 20 ++++++++- app/assets/javascripts/subscription.js | 19 +++++--- app/assets/javascripts/users_select.js | 44 +++++++++++++++++-- app/assets/stylesheets/pages/boards.scss | 15 +++++++ .../boards/components/_sidebar.html.haml | 16 ++++--- .../components/sidebar/_assignee.html.haml | 18 ++++++++ .../components/sidebar/_due_date.html.haml | 21 +++++++-- .../components/sidebar/_labels.html.haml | 26 ++++++++--- .../components/sidebar/_milestone.html.haml | 24 +++++++--- .../sidebar/_notifications.html.haml | 12 ++--- app/views/projects/boards/show.html.haml | 2 +- 18 files changed, 254 insertions(+), 49 deletions(-) create mode 100644 app/assets/javascripts/boards/filters/due_date_filters.js.es6 diff --git a/app/assets/javascripts/boards/boards_bundle.js.es6 b/app/assets/javascripts/boards/boards_bundle.js.es6 index b3a2386bbf3..c5dd51c39d7 100644 --- a/app/assets/javascripts/boards/boards_bundle.js.es6 +++ b/app/assets/javascripts/boards/boards_bundle.js.es6 @@ -5,6 +5,7 @@ //= require_tree ./stores //= require_tree ./services //= require_tree ./mixins +//= require_tree ./filters //= require ./components/board //= require ./components/board_sidebar //= require ./components/new_list_dropdown diff --git a/app/assets/javascripts/boards/components/board_sidebar.js.es6 b/app/assets/javascripts/boards/components/board_sidebar.js.es6 index c267b4f0817..12f69f93279 100644 --- a/app/assets/javascripts/boards/components/board_sidebar.js.es6 +++ b/app/assets/javascripts/boards/components/board_sidebar.js.es6 @@ -5,6 +5,9 @@ window.gl.issueBoards = window.gl.issueBoards || {}; gl.issueBoards.BoardSidebar = Vue.extend({ + props: { + currentUser: Object + }, data() { return { detail: Store.detail, @@ -26,9 +29,12 @@ issue () { if (this.showSidebar) { this.$nextTick(() => { - new IssuableContext(); + new IssuableContext(this.currentUser); new MilestoneSelect(); + new DueDateSelect(); + new LabelsSelect(); new Sidebar(); + new Subscription('.subscription') }); } else { $('.right-sidebar').getNiceScroll().remove(); diff --git a/app/assets/javascripts/boards/filters/due_date_filters.js.es6 b/app/assets/javascripts/boards/filters/due_date_filters.js.es6 new file mode 100644 index 00000000000..5c1519986c1 --- /dev/null +++ b/app/assets/javascripts/boards/filters/due_date_filters.js.es6 @@ -0,0 +1,4 @@ +Vue.filter('due-date', (value) => { + const date = new Date(value.replace(new RegExp('-', 'g'), ',')); + return $.datepicker.formatDate('M d, yy', date); +}); diff --git a/app/assets/javascripts/boards/models/issue.js.es6 b/app/assets/javascripts/boards/models/issue.js.es6 index a8c28e27f41..fac753dadb5 100644 --- a/app/assets/javascripts/boards/models/issue.js.es6 +++ b/app/assets/javascripts/boards/models/issue.js.es6 @@ -4,6 +4,7 @@ class ListIssue { this.title = obj.title; this.confidential = obj.confidential; this.dueDate = obj.due_date; + this.subscribed = true; this.labels = []; if (obj.assignee) { @@ -46,4 +47,17 @@ class ListIssue { getLists () { return gl.issueBoards.BoardsStore.state.lists.filter( list => list.findIssue(this.id) ); } + + update (url) { + const data = { + issue: { + milestone_id: this.milestone ? this.milestone.id : null, + due_date: this.dueDate, + assignee_id: this.assignee ? this.assignee.id : null, + label_ids: this.labels.map((label) => label.id ) + } + }; + + return Vue.http.patch(url, data); + } } diff --git a/app/assets/javascripts/boards/services/board_service.js.es6 b/app/assets/javascripts/boards/services/board_service.js.es6 index 2b825c3949f..b7a9ea16204 100644 --- a/app/assets/javascripts/boards/services/board_service.js.es6 +++ b/app/assets/javascripts/boards/services/board_service.js.es6 @@ -1,7 +1,5 @@ class BoardService { constructor (root) { - Vue.http.options.root = root; - this.lists = Vue.resource(`${root}/lists{/id}`, {}, { generate: { method: 'POST', diff --git a/app/assets/javascripts/due_date_select.js b/app/assets/javascripts/due_date_select.js index bf68b7e3a9b..aad81892311 100644 --- a/app/assets/javascripts/due_date_select.js +++ b/app/assets/javascripts/due_date_select.js @@ -38,6 +38,19 @@ return $value.css('display', ''); } }); + + var updateIssueBoardIssue = function () { + $dropdown.trigger('loading.gl.dropdown'); + $selectbox.hide(); + $value.css('display', ''); + $loading.fadeIn(); + + gl.issueBoards.BoardsStore.detail.issue.update(issueUpdateURL) + .then(function () { + $loading.fadeOut(); + }); + } + addDueDate = function(isDropdown) { var data, date, mediumDate, value; // Create the post date @@ -83,15 +96,25 @@ }; $block.on('click', '.js-remove-due-date', function(e) { e.preventDefault(); - $("input[name='" + fieldName + "']").val(''); - return addDueDate(false); + if ($dropdown.hasClass('js-issue-boards-due-date')) { + gl.issueBoards.BoardsStore.detail.issue.dueDate = ''; + updateIssueBoardIssue(); + } else { + $("input[name='" + fieldName + "']").val(''); + return addDueDate(false); + } }); return $datePicker.datepicker({ dateFormat: 'yy-mm-dd', defaultDate: $("input[name='" + fieldName + "']").val(), altField: "input[name='" + fieldName + "']", onSelect: function() { - return addDueDate(true); + if ($dropdown.hasClass('js-issue-boards-due-date')) { + gl.issueBoards.BoardsStore.detail.issue.dueDate = $("input[name='" + fieldName + "']").val(); + updateIssueBoardIssue(); + } else { + return addDueDate(true); + } } }); }); diff --git a/app/assets/javascripts/labels_select.js b/app/assets/javascripts/labels_select.js index e356872624a..6e5d3dff9eb 100644 --- a/app/assets/javascripts/labels_select.js +++ b/app/assets/javascripts/labels_select.js @@ -22,7 +22,7 @@ abilityName = $dropdown.data('ability-name'); $selectbox = $dropdown.closest('.selectbox'); $block = $selectbox.closest('.block'); - $form = $dropdown.closest('form'); + $form = $dropdown.closest('.js-issuable-update'); $sidebarCollapsedValue = $block.find('.sidebar-collapsed-icon span'); $sidebarLabelTooltip = $block.find('.js-sidebar-labels-tooltip'); $value = $block.find('.value'); @@ -334,7 +334,7 @@ page = $('body').data('page'); isIssueIndex = page === 'projects:issues:index'; isMRIndex = page === 'projects:merge_requests:index'; - if (page === 'projects:boards:show') { + if (page === 'projects:boards:show' && !$dropdown.hasClass('js-issue-boards-label')) { if (label.isAny) { gl.issueBoards.BoardsStore.state.filters['label_name'] = []; } @@ -362,6 +362,30 @@ else if ($dropdown.hasClass('js-filter-submit')) { return $dropdown.closest('form').submit(); } + else if ($dropdown.hasClass('js-issue-boards-label')) { + if ($el.hasClass('is-active')) { + gl.issueBoards.BoardsStore.detail.issue.labels.push(new ListLabel({ + id: label.id, + title: label.title, + color: label.color[0], + textColor: '#fff' + })); + } + else { + var labels = gl.issueBoards.BoardsStore.detail.issue.labels; + labels = labels.filter(function (selectedLabel) { + return selectedLabel.id !== label.id; + }); + gl.issueBoards.BoardsStore.detail.issue.labels = labels; + } + + $loading.fadeIn(); + + gl.issueBoards.BoardsStore.detail.issue.update(issueUpdateURL) + .then(function () { + $loading.fadeOut(); + }); + } else { if ($dropdown.hasClass('js-multiselect')) { diff --git a/app/assets/javascripts/milestone_select.js b/app/assets/javascripts/milestone_select.js index 26cc6eb0e96..3004cfee40c 100644 --- a/app/assets/javascripts/milestone_select.js +++ b/app/assets/javascripts/milestone_select.js @@ -110,7 +110,7 @@ e.preventDefault(); return; } - if (page === 'projects:boards:show') { + if (page === 'projects:boards:show' && !$dropdown.hasClass('js-issue-board-sidebar')) { gl.issueBoards.BoardsStore.state.filters[$dropdown.data('field-name')] = selected.name; gl.issueBoards.BoardsStore.updateFiltersUrl(); e.preventDefault(); @@ -123,6 +123,24 @@ return Issuable.filterResults($dropdown.closest('form')); } else if ($dropdown.hasClass('js-filter-submit')) { return $dropdown.closest('form').submit(); + } else if ($dropdown.hasClass('js-issue-board-sidebar')) { + if (selected.id !== -1) { + Vue.set(gl.issueBoards.BoardsStore.detail.issue, 'milestone', new ListMilestone({ + id: selected.id, + title: selected.name + })); + } else { + Vue.delete(gl.issueBoards.BoardsStore.detail.issue, 'milestone'); + } + + $dropdown.trigger('loading.gl.dropdown'); + $loading.fadeIn(); + + gl.issueBoards.BoardsStore.detail.issue.update(issueUpdateURL) + .then(function () { + $dropdown.trigger('loaded.gl.dropdown'); + $loading.fadeOut(); + }); } else { selected = $selectbox.find('input[type="hidden"]').val(); data = {}; diff --git a/app/assets/javascripts/subscription.js b/app/assets/javascripts/subscription.js index 5e3c5983d75..bfef9532d2b 100644 --- a/app/assets/javascripts/subscription.js +++ b/app/assets/javascripts/subscription.js @@ -22,13 +22,18 @@ return function() { var status; btn.removeClass('disabled'); - status = current_status === 'subscribed' ? 'unsubscribed' : 'subscribed'; - _this.subscription_status.attr('data-status', status); - action = status === 'subscribed' ? 'Unsubscribe' : 'Subscribe'; - btn.find('span').text(action); - _this.subscription_status.find('>div').toggleClass('hidden'); - if (btn.attr('data-original-title')) { - return btn.tooltip('hide').attr('data-original-title', action).tooltip('fixTitle'); + + if ($('body').data('page') === 'projects:boards:show') { + Vue.set(gl.issueBoards.BoardsStore.detail.issue, 'subscribed', !gl.issueBoards.BoardsStore.detail.issue.subscribed); + } else { + status = current_status === 'subscribed' ? 'unsubscribed' : 'subscribed'; + _this.subscription_status.attr('data-status', status); + action = status === 'subscribed' ? 'Unsubscribe' : 'Subscribe'; + btn.find('span').text(action); + _this.subscription_status.find('>div').toggleClass('hidden'); + if (btn.attr('data-original-title')) { + return btn.tooltip('hide').attr('data-original-title', action).tooltip('fixTitle'); + } } }; })(this)); diff --git a/app/assets/javascripts/users_select.js b/app/assets/javascripts/users_select.js index bcabda3ceb2..d3868f392ea 100644 --- a/app/assets/javascripts/users_select.js +++ b/app/assets/javascripts/users_select.js @@ -9,7 +9,11 @@ this.usersPath = "/autocomplete/users.json"; this.userPath = "/autocomplete/users/:id.json"; if (currentUser != null) { - this.currentUser = JSON.parse(currentUser); + if (typeof currentUser === 'object') { + this.currentUser = currentUser; + } else { + this.currentUser = JSON.parse(currentUser); + } } $('.js-user-search').each((function(_this) { return function(i, dropdown) { @@ -32,9 +36,30 @@ $value = $block.find('.value'); $collapsedSidebar = $block.find('.sidebar-collapsed-user'); $loading = $block.find('.block-loading').fadeOut(); + + var updateIssueBoardsIssue = function () { + $loading.fadeIn(); + gl.issueBoards.BoardsStore.detail.issue.update(issueURL) + .then(function () { + $loading.fadeOut(); + }); + }; + $block.on('click', '.js-assign-yourself', function(e) { e.preventDefault(); - return assignTo(_this.currentUser.id); + + if ($dropdown.hasClass('js-issue-board-assignee')) { + Vue.set(gl.issueBoards.BoardsStore.detail.issue, 'assignee', new ListUser({ + id: _this.currentUser.id, + username: _this.currentUser.username, + name: _this.currentUser.name, + avatar_url: _this.currentUser.avatar_url + })); + + updateIssueBoardsIssue(); + } else { + return assignTo(_this.currentUser.id); + } }); assignTo = function(selected) { var data; @@ -160,7 +185,7 @@ selectedId = user.id; return; } - if (page === 'projects:boards:show') { + if (page === 'projects:boards:show' && !$dropdown.hasClass('js-issue-board-assignee')) { selectedId = user.id; gl.issueBoards.BoardsStore.state.filters[$dropdown.data('field-name')] = user.id; gl.issueBoards.BoardsStore.updateFiltersUrl(); @@ -170,6 +195,19 @@ return Issuable.filterResults($dropdown.closest('form')); } else if ($dropdown.hasClass('js-filter-submit')) { return $dropdown.closest('form').submit(); + } else if ($dropdown.hasClass('js-issue-board-assignee')) { + if (user.id) { + Vue.set(gl.issueBoards.BoardsStore.detail.issue, 'assignee', new ListUser({ + id: user.id, + username: user.username, + name: user.name, + avatar_url: user.avatar_url + })); + } else { + Vue.delete(gl.issueBoards.BoardsStore.detail.issue, 'assignee'); + } + + updateIssueBoardsIssue(); } else { selected = $dropdown.closest('.selectbox').find("input[name='" + ($dropdown.data('field-name')) + "']").val(); return assignTo(selected); diff --git a/app/assets/stylesheets/pages/boards.scss b/app/assets/stylesheets/pages/boards.scss index 6e81c12aa55..6eb2cb93baa 100644 --- a/app/assets/stylesheets/pages/boards.scss +++ b/app/assets/stylesheets/pages/boards.scss @@ -46,6 +46,15 @@ lex .page-with-sidebar { padding-bottom: 0; } + + .issues-filters { + position: relative; + z-index: 999999; + } +} + +.boards-app { + position: relative; } .boards-app-loading { @@ -265,3 +274,9 @@ lex border-width: 1px 0 1px 1px; } } + +.right-sidebar.issue-boards-sidebar { + position: absolute; + top: 0; + bottom: 0; +} diff --git a/app/views/projects/boards/components/_sidebar.html.haml b/app/views/projects/boards/components/_sidebar.html.haml index b03b8384220..b4a757a40a6 100644 --- a/app/views/projects/boards/components/_sidebar.html.haml +++ b/app/views/projects/boards/components/_sidebar.html.haml @@ -1,5 +1,6 @@ -%board-sidebar{ "inline-template" => true } - %aside.right-sidebar.right-sidebar-expanded{ "v-if" => "showSidebar" } +%board-sidebar{ "inline-template" => true, + ":current-user" => "#{current_user.to_json(only: [:username, :id, :name], methods: [:avatar_url]) if current_user}" } + %aside.right-sidebar.right-sidebar-expanded.issue-boards-sidebar{ "v-if" => "showSidebar" } .issuable-sidebar .block.issuable-sidebar-header %span.issuable-header-text.hide-collapsed.pull-left @@ -14,8 +15,9 @@ "@click" => "closeSidebar", aria: { label: "Toggle sidebar" } } = icon("times") - = render "projects/boards/components/sidebar/assignee" - = render "projects/boards/components/sidebar/milestone" - = render "projects/boards/components/sidebar/due_date" - = render "projects/boards/components/sidebar/labels" - = render "projects/boards/components/sidebar/notifications" + .js-issuable-update + = render "projects/boards/components/sidebar/assignee" + = render "projects/boards/components/sidebar/milestone" + = render "projects/boards/components/sidebar/due_date" + = render "projects/boards/components/sidebar/labels" + = render "projects/boards/components/sidebar/notifications" diff --git a/app/views/projects/boards/components/sidebar/_assignee.html.haml b/app/views/projects/boards/components/sidebar/_assignee.html.haml index 93780fc929e..35343e65097 100644 --- a/app/views/projects/boards/components/sidebar/_assignee.html.haml +++ b/app/views/projects/boards/components/sidebar/_assignee.html.haml @@ -20,3 +20,21 @@ %span.username = precede "@" do {{ issue.assignee.username }} + - if can?(current_user, :admin_issue, @project) + .selectbox.hide-collapsed + %input{ type: "hidden", + name: "issue[assignee_id]", + id: "issue_assignee_id", + ":value" => "issue.assignee.id", + "v-if" => "issue.assignee" } + .dropdown + %button.dropdown-menu-toggle.js-user-search.js-author-search.js-issue-board-assignee{ data: { toggle: "dropdown", field_name: "issue[assignee_id]", first_user: (current_user.username if current_user), current_user: "true", project_id: @project.id, field_name: "issue[assignee_id]", null_user: "true" }, + ":data-issuable-id" => "issue.id", + ":data-issue-update" => "'/root/issue-boards/issues/' + issue.id + '.json'" } + Select assignee + = icon("chevron-down") + .dropdown-menu.dropdown-menu-user.dropdown-menu-selectable.dropdown-menu-author + = dropdown_title("Assign to") + = dropdown_filter("Search users") + = dropdown_content + = dropdown_loading diff --git a/app/views/projects/boards/components/sidebar/_due_date.html.haml b/app/views/projects/boards/components/sidebar/_due_date.html.haml index 9861523824a..c9fb1378274 100644 --- a/app/views/projects/boards/components/sidebar/_due_date.html.haml +++ b/app/views/projects/boards/components/sidebar/_due_date.html.haml @@ -1,16 +1,31 @@ .block.due_date - .title.hide-collapsed + .title Due date = icon("spinner spin", class: "block-loading") - if can?(current_user, :admin_issue, @project) = link_to "Edit", "#", class: "edit-link pull-right" - .value.hide-collapsed + .value .value-content %span.no-value{ "v-if" => "!issue.dueDate" } No due date %span.bold{ "v-if" => "issue.dueDate" } - {{ issue.dueDate }} + {{ issue.dueDate | due-date }} %span.no-value.js-remove-due-date-holder{ "v-if" => "issue.dueDate" } \- %a.js-remove-due-date{ href: "#", role: "button" } remove due date + - if can?(current_user, :admin_issue, @project) + .selectbox + %input{ type: "hidden", + name: "issue[due_date]", + ":value" => "issue.dueDate" } + .dropdown + %button.dropdown-menu-toggle.js-due-date-select.js-issue-boards-due-date{ type: 'button', + data: { toggle: 'dropdown', field_name: "issue[due_date]", ability_name: "issue" }, + ":data-issue-update" => "'/root/issue-boards/issues/' + issue.id + '.json'" } + %span.dropdown-toggle-text Due date + = icon('chevron-down') + .dropdown-menu.dropdown-menu-due-date + = dropdown_title('Due date') + = dropdown_content do + .js-due-date-calendar diff --git a/app/views/projects/boards/components/sidebar/_labels.html.haml b/app/views/projects/boards/components/sidebar/_labels.html.haml index b135b134a96..71dd93db7d5 100644 --- a/app/views/projects/boards/components/sidebar/_labels.html.haml +++ b/app/views/projects/boards/components/sidebar/_labels.html.haml @@ -1,18 +1,30 @@ .block.labels - .title.hide-collapsed + .title Labels = icon("spinner spin", class: "block-loading") - if can?(current_user, :admin_issue, @project) = link_to "Edit", "#", class: "edit-link pull-right" - .value.issuable-show-labels.hide-collapsed + .value.issuable-show-labels %span.no-value{ "v-if" => "issue.labels.length === 0" } None %a{ href: "#", "v-for" => "label in issue.labels" } %span.label.color-label.has-tooltip{ ":style" => "{ backgroundColor: label.color, color: label.textColor }" } {{ label.title }} - .selectbox.hide-collapsed - %input{ type: "hidden", - name: "issue[label_names][]", - "v-for" => "label in issue.labels", - ":value" => "label.id" } + - if can?(current_user, :admin_issue, @project) + .selectbox + %input{ type: "hidden", + name: "issue[label_names][]", + "v-for" => "label in issue.labels", + ":value" => "label.id" } + .dropdown + %button.dropdown-menu-toggle.js-label-select.js-multiselect.js-issue-boards-label{ type: "button", + data: { toggle: "dropdown", field_name: "issue[label_names][]", show_no: "true", show_any: "true", project_id: @project.id, labels: namespace_project_labels_path(@project.namespace, @project, :json) }, + ":data-issue-update" => "'/root/issue-boards/issues/' + issue.id + '.json'" } + %span.dropdown-toggle-text + Label + = icon('chevron-down') + .dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable + = render partial: "shared/issuable/label_page_default" + - if can? current_user, :admin_label, @project and @project + = render partial: "shared/issuable/label_page_create" diff --git a/app/views/projects/boards/components/sidebar/_milestone.html.haml b/app/views/projects/boards/components/sidebar/_milestone.html.haml index 14f521f57a4..fb88700cb3a 100644 --- a/app/views/projects/boards/components/sidebar/_milestone.html.haml +++ b/app/views/projects/boards/components/sidebar/_milestone.html.haml @@ -1,5 +1,5 @@ .block.milestone - .title.hide-collapsed + .title Milestone = icon("spinner spin", class: "block-loading") - if can?(current_user, :admin_issue, @project) @@ -9,8 +9,20 @@ None %span.bold.has-tooltip{ "v-if" => "issue.milestone" } {{ issue.milestone.title }} - .selectbox - .dropdown - %button.dropdown-menu-toggle - Milestone - -# = dropdown_tag('Milestone', options: { title: 'Assign milestone', toggle_class: 'js-milestone-select js-extra-options', filter: true, dropdown_class: 'dropdown-menu-selectable', placeholder: 'Search milestones', data: { show_no: true, field_name: "issue[milestone_id]", project_id: @project.id, milestones: namespace_project_milestones_path(@project.namespace, @project, :json), ability_name: "issue", use_id: true }}) + - if can?(current_user, :admin_issue, @project) + .selectbox + %input{ type: "hidden", + ":value" => "issue.milestone.id", + name: "issue[milestone_id]", + "v-if" => "issue.milestone" } + .dropdown + %button.dropdown-menu-toggle.js-milestone-select.js-issue-board-sidebar{ data: { toggle: "dropdown", show_no: "true", field_name: "issue[milestone_id]", project_id: @project.id, milestones: namespace_project_milestones_path(@project.namespace, @project, :json), ability_name: "issue", use_id: "true" }, + ":data-issuable-id" => "issue.id", + ":data-issue-update" => "'/root/issue-boards/issues/' + issue.id + '.json'" } + Milestone + = icon("chevron-down") + .dropdown-menu.dropdown-select.dropdown-menu-selectable + = dropdown_title("Assignee milestone") + = dropdown_filter("Search milestones") + = dropdown_content + = dropdown_loading diff --git a/app/views/projects/boards/components/sidebar/_notifications.html.haml b/app/views/projects/boards/components/sidebar/_notifications.html.haml index e28be47fd8e..21c9563e9db 100644 --- a/app/views/projects/boards/components/sidebar/_notifications.html.haml +++ b/app/views/projects/boards/components/sidebar/_notifications.html.haml @@ -1,11 +1,11 @@ - if current_user - .block.light.subscription{ data: { url: '' } } - .title.hide-collapsed + .block.light.subscription{ ":data-url" => "'#{namespace_project_issues_path(@project.namespace, @project)}/' + issue.id + '/toggle_subscription'" } + .title Notifications %button.btn.btn-block.btn-default.js-subscribe-button.issuable-subscribe-button.hide-collapsed{ type: "button" } - Unsubscribe - .subscription-status.hide-collapsed{ data: { status: '' } } - .unsubscribed{class: ( 'hidden' if true )} + {{ issue.subscribed ? 'Unsubscribe' : 'Subscribe' }} + .subscription-status{ ":data-status" => "issue.subscribed ? 'subscribed' : 'unsubscribed'" } + .unsubscribed{ "v-show" => "!issue.subscribed" } You're not receiving notifications from this thread. - .subscribed{class: ( 'hidden' unless true )} + .subscribed{ "v-show" => "issue.subscribed" } You're receiving notifications because you're subscribed to this thread. diff --git a/app/views/projects/boards/show.html.haml b/app/views/projects/boards/show.html.haml index 10b495363a6..432390eb03f 100644 --- a/app/views/projects/boards/show.html.haml +++ b/app/views/projects/boards/show.html.haml @@ -10,7 +10,7 @@ = render 'shared/issuable/filter', type: :boards -#board-app{ "v-cloak" => true, +#board-app.boards-app{ "v-cloak" => true, "data-endpoint" => "#{namespace_project_board_path(@project.namespace, @project)}", "data-disabled" => "#{!can?(current_user, :admin_list, @project)}", "data-issue-link-base" => "#{namespace_project_issues_path(@project.namespace, @project)}" } From 05e8404b700decf7ebff2e5a4e3c9cec6cc609f4 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 5 Oct 2016 16:17:03 +0100 Subject: [PATCH 04/25] Fixed issue with dragging opening the issue sidebar Added indicator when issue detail is visible --- .../boards/components/board_card.js.es6 | 28 ++++++++++++++++++- app/assets/stylesheets/pages/boards.scss | 4 +++ .../boards/components/_card.html.haml | 6 ++-- 3 files changed, 35 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/boards/components/board_card.js.es6 b/app/assets/javascripts/boards/components/board_card.js.es6 index e61943de5a9..17bcbc1d54f 100644 --- a/app/assets/javascripts/boards/components/board_card.js.es6 +++ b/app/assets/javascripts/boards/components/board_card.js.es6 @@ -12,6 +12,21 @@ disabled: Boolean, index: Number }, + data () { + return { + showDetail: false, + detailIssue: Store.detail + }; + }, + computed: { + issueDetailVisible () { + if (this.detailIssue.issue && this.detailIssue.issue.id === this.issue.id) { + return true; + } else { + return false; + } + } + }, methods: { filterByLabel (label, e) { let labelToggleText = label.title; @@ -38,8 +53,19 @@ Store.updateFiltersUrl(); }, + mouseDown () { + this.showDetail = true; + }, + mouseMove () { + if (this.showDetail) { + this.showDetail = false; + } + }, showIssue () { - Store.detail.issue = this.issue; + if (this.showDetail) { + this.showDetail = false; + Vue.set(Store.detail, 'issue', this.issue); + } } } }); diff --git a/app/assets/stylesheets/pages/boards.scss b/app/assets/stylesheets/pages/boards.scss index 6eb2cb93baa..f57e9a37bc3 100644 --- a/app/assets/stylesheets/pages/boards.scss +++ b/app/assets/stylesheets/pages/boards.scss @@ -194,6 +194,10 @@ lex margin-bottom: 5px; } + &.is-active { + background-color: $row-hover; + } + .label { border: 0; outline: 0; diff --git a/app/views/projects/boards/components/_card.html.haml b/app/views/projects/boards/components/_card.html.haml index 71f64d7827c..d466165816f 100644 --- a/app/views/projects/boards/components/_card.html.haml +++ b/app/views/projects/boards/components/_card.html.haml @@ -7,9 +7,11 @@ ":issue-link-base" => "issueLinkBase", ":disabled" => "disabled", "track-by" => "id" } - %li.card{ ":class" => "{ 'user-can-drag': !disabled && issue.id, 'is-disabled': disabled || !issue.id }", + %li.card{ ":class" => "{ 'user-can-drag': !disabled && issue.id, 'is-disabled': disabled || !issue.id, 'is-active': issueDetailVisible }", ":index" => "index", - "@click" => "showIssue" } + "@mousedown" => "mouseDown", + "@mouseMove" => "mouseMove", + "@mouseup" => "showIssue" } %h4.card-title = icon("eye-slash", class: "confidential-icon", "v-if" => "issue.confidential") %a{ ":href" => "issueLinkBase + '/' + issue.id", From 5f84c4e240a87b8e40a3391501d93022c3258b75 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 5 Oct 2016 16:22:36 +0100 Subject: [PATCH 05/25] Hides sidebar when clicking same issue --- app/assets/javascripts/boards/components/board_card.js.es6 | 7 ++++++- .../boards/mixins/sortable_default_options.js.es6 | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/boards/components/board_card.js.es6 b/app/assets/javascripts/boards/components/board_card.js.es6 index 17bcbc1d54f..27c78e7f02e 100644 --- a/app/assets/javascripts/boards/components/board_card.js.es6 +++ b/app/assets/javascripts/boards/components/board_card.js.es6 @@ -64,7 +64,12 @@ showIssue () { if (this.showDetail) { this.showDetail = false; - Vue.set(Store.detail, 'issue', this.issue); + + if (Store.detail.issue && Store.detail.issue.id === this.issue.id) { + Store.detail.issue = {}; + } else { + Store.detail.issue = this.issue; + } } } } diff --git a/app/assets/javascripts/boards/mixins/sortable_default_options.js.es6 b/app/assets/javascripts/boards/mixins/sortable_default_options.js.es6 index f629d45c587..bd9ba7d5118 100644 --- a/app/assets/javascripts/boards/mixins/sortable_default_options.js.es6 +++ b/app/assets/javascripts/boards/mixins/sortable_default_options.js.es6 @@ -22,7 +22,7 @@ fallbackOnBody: true, ghostClass: 'is-ghost', filter: '.has-tooltip, .btn', - delay: gl.issueBoards.touchEnabled ? 100 : 0, + delay: gl.issueBoards.touchEnabled ? 100 : 50, scrollSensitivity: gl.issueBoards.touchEnabled ? 60 : 100, scrollSpeed: 20, onStart: gl.issueBoards.onStart, From 81e7490d3af1418d63276527be5be7149ca60ba7 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 5 Oct 2016 16:23:45 +0100 Subject: [PATCH 06/25] Correct the issue update path --- .../projects/boards/components/sidebar/_assignee.html.haml | 2 +- .../projects/boards/components/sidebar/_due_date.html.haml | 2 +- app/views/projects/boards/components/sidebar/_labels.html.haml | 2 +- .../projects/boards/components/sidebar/_milestone.html.haml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/views/projects/boards/components/sidebar/_assignee.html.haml b/app/views/projects/boards/components/sidebar/_assignee.html.haml index 35343e65097..57039237d0b 100644 --- a/app/views/projects/boards/components/sidebar/_assignee.html.haml +++ b/app/views/projects/boards/components/sidebar/_assignee.html.haml @@ -30,7 +30,7 @@ .dropdown %button.dropdown-menu-toggle.js-user-search.js-author-search.js-issue-board-assignee{ data: { toggle: "dropdown", field_name: "issue[assignee_id]", first_user: (current_user.username if current_user), current_user: "true", project_id: @project.id, field_name: "issue[assignee_id]", null_user: "true" }, ":data-issuable-id" => "issue.id", - ":data-issue-update" => "'/root/issue-boards/issues/' + issue.id + '.json'" } + ":data-issue-update" => "'#{namespace_project_issues_path(@project.namespace, @project)}/' + issue.id + '.json'" } Select assignee = icon("chevron-down") .dropdown-menu.dropdown-menu-user.dropdown-menu-selectable.dropdown-menu-author diff --git a/app/views/projects/boards/components/sidebar/_due_date.html.haml b/app/views/projects/boards/components/sidebar/_due_date.html.haml index c9fb1378274..91b4a572ee4 100644 --- a/app/views/projects/boards/components/sidebar/_due_date.html.haml +++ b/app/views/projects/boards/components/sidebar/_due_date.html.haml @@ -22,7 +22,7 @@ .dropdown %button.dropdown-menu-toggle.js-due-date-select.js-issue-boards-due-date{ type: 'button', data: { toggle: 'dropdown', field_name: "issue[due_date]", ability_name: "issue" }, - ":data-issue-update" => "'/root/issue-boards/issues/' + issue.id + '.json'" } + ":data-issue-update" => "'#{namespace_project_issues_path(@project.namespace, @project)}/' + issue.id + '.json'" } %span.dropdown-toggle-text Due date = icon('chevron-down') .dropdown-menu.dropdown-menu-due-date diff --git a/app/views/projects/boards/components/sidebar/_labels.html.haml b/app/views/projects/boards/components/sidebar/_labels.html.haml index 71dd93db7d5..865bfa6d1da 100644 --- a/app/views/projects/boards/components/sidebar/_labels.html.haml +++ b/app/views/projects/boards/components/sidebar/_labels.html.haml @@ -20,7 +20,7 @@ .dropdown %button.dropdown-menu-toggle.js-label-select.js-multiselect.js-issue-boards-label{ type: "button", data: { toggle: "dropdown", field_name: "issue[label_names][]", show_no: "true", show_any: "true", project_id: @project.id, labels: namespace_project_labels_path(@project.namespace, @project, :json) }, - ":data-issue-update" => "'/root/issue-boards/issues/' + issue.id + '.json'" } + ":data-issue-update" => "'#{namespace_project_issues_path(@project.namespace, @project)}/' + issue.id + '.json'" } %span.dropdown-toggle-text Label = icon('chevron-down') diff --git a/app/views/projects/boards/components/sidebar/_milestone.html.haml b/app/views/projects/boards/components/sidebar/_milestone.html.haml index fb88700cb3a..ec1354509f2 100644 --- a/app/views/projects/boards/components/sidebar/_milestone.html.haml +++ b/app/views/projects/boards/components/sidebar/_milestone.html.haml @@ -18,7 +18,7 @@ .dropdown %button.dropdown-menu-toggle.js-milestone-select.js-issue-board-sidebar{ data: { toggle: "dropdown", show_no: "true", field_name: "issue[milestone_id]", project_id: @project.id, milestones: namespace_project_milestones_path(@project.namespace, @project, :json), ability_name: "issue", use_id: "true" }, ":data-issuable-id" => "issue.id", - ":data-issue-update" => "'/root/issue-boards/issues/' + issue.id + '.json'" } + ":data-issue-update" => "'#{namespace_project_issues_path(@project.namespace, @project)}/' + issue.id + '.json'" } Milestone = icon("chevron-down") .dropdown-menu.dropdown-select.dropdown-menu-selectable From 45fd2d38844afafd4ec144eb4dc388b7a76b04ad Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 5 Oct 2016 16:59:47 +0100 Subject: [PATCH 07/25] Styling updates to sidebar to match design --- app/assets/stylesheets/pages/boards.scss | 38 +++++++++++++++++-- .../boards/components/_sidebar.html.haml | 2 +- 2 files changed, 35 insertions(+), 5 deletions(-) diff --git a/app/assets/stylesheets/pages/boards.scss b/app/assets/stylesheets/pages/boards.scss index f57e9a37bc3..0807d583207 100644 --- a/app/assets/stylesheets/pages/boards.scss +++ b/app/assets/stylesheets/pages/boards.scss @@ -279,8 +279,38 @@ lex } } -.right-sidebar.issue-boards-sidebar { - position: absolute; - top: 0; - bottom: 0; +.issue-boards-sidebar { + &.right-sidebar { + position: absolute; + top: 0; + bottom: 0; + } + + .issuable-sidebar-header { + position: relative; + } + + .gutter-toggle { + position: absolute; + top: 0; + bottom: 10px; + right: 0; + width: 22px; + color: $gray-darkest; + + .fa { + position: absolute; + top: 50%; + margin-top: (-15px / 2); + } + } + + .issuable-header-text { + width: 100%; + padding-right: 35px; + + > strong { + font-weight: 600; + } + } } diff --git a/app/views/projects/boards/components/_sidebar.html.haml b/app/views/projects/boards/components/_sidebar.html.haml index b4a757a40a6..cf70ad4927a 100644 --- a/app/views/projects/boards/components/_sidebar.html.haml +++ b/app/views/projects/boards/components/_sidebar.html.haml @@ -13,7 +13,7 @@ %a.gutter-toggle.pull-right{ role: "button", href: "#", "@click" => "closeSidebar", - aria: { label: "Toggle sidebar" } } + "aria-label" => "Toggle sidebar" } = icon("times") .js-issuable-update = render "projects/boards/components/sidebar/assignee" From e6fa8a3d10fe34e9ee17f3122139f0b8b1b0a3f9 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 6 Oct 2016 09:03:10 +0100 Subject: [PATCH 08/25] Fixed sidebar dropdowns to work with Vue --- app/assets/javascripts/boards/models/issue.js.es6 | 4 ++++ app/assets/javascripts/gl_dropdown.js | 11 +++++++++++ app/assets/javascripts/labels_select.js | 5 +++-- app/assets/javascripts/milestone_select.js | 1 + app/assets/javascripts/users_select.js | 5 +++-- .../boards/components/sidebar/_assignee.html.haml | 2 +- .../boards/components/sidebar/_labels.html.haml | 2 +- 7 files changed, 24 insertions(+), 6 deletions(-) diff --git a/app/assets/javascripts/boards/models/issue.js.es6 b/app/assets/javascripts/boards/models/issue.js.es6 index fac753dadb5..ca013aa92b5 100644 --- a/app/assets/javascripts/boards/models/issue.js.es6 +++ b/app/assets/javascripts/boards/models/issue.js.es6 @@ -58,6 +58,10 @@ class ListIssue { } }; + if (!data.issue.label_ids.length) { + data.issue.label_ids = ['']; + } + return Vue.http.patch(url, data); } } diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js index d4403375643..834eaef6fff 100644 --- a/app/assets/javascripts/gl_dropdown.js +++ b/app/assets/javascripts/gl_dropdown.js @@ -620,6 +620,17 @@ selectedObject = this.renderedData[selectedIndex]; } } + + if (this.options.vue) { + if (el.hasClass(ACTIVE_CLASS)) { + el.removeClass(ACTIVE_CLASS); + } else { + el.addClass(ACTIVE_CLASS); + } + + return selectedObject; + } + field = []; value = this.options.id ? this.options.id(selectedObject, el) : selectedObject.id; if (isInput) { diff --git a/app/assets/javascripts/labels_select.js b/app/assets/javascripts/labels_select.js index 6e5d3dff9eb..03e6136509a 100644 --- a/app/assets/javascripts/labels_select.js +++ b/app/assets/javascripts/labels_select.js @@ -317,6 +317,7 @@ } }, multiSelect: $dropdown.hasClass('js-multiselect'), + vue: $dropdown.hasClass('js-issue-board-sidebar'), clicked: function(label, $el, e) { var isIssueIndex, isMRIndex, page; _this.enableBulkLabelDropdown(); @@ -334,7 +335,7 @@ page = $('body').data('page'); isIssueIndex = page === 'projects:issues:index'; isMRIndex = page === 'projects:merge_requests:index'; - if (page === 'projects:boards:show' && !$dropdown.hasClass('js-issue-boards-label')) { + if (page === 'projects:boards:show' && !$dropdown.hasClass('js-issue-board-sidebar')) { if (label.isAny) { gl.issueBoards.BoardsStore.state.filters['label_name'] = []; } @@ -362,7 +363,7 @@ else if ($dropdown.hasClass('js-filter-submit')) { return $dropdown.closest('form').submit(); } - else if ($dropdown.hasClass('js-issue-boards-label')) { + else if ($dropdown.hasClass('js-issue-board-sidebar')) { if ($el.hasClass('is-active')) { gl.issueBoards.BoardsStore.detail.issue.labels.push(new ListLabel({ id: label.id, diff --git a/app/assets/javascripts/milestone_select.js b/app/assets/javascripts/milestone_select.js index 3004cfee40c..8dffadd88e2 100644 --- a/app/assets/javascripts/milestone_select.js +++ b/app/assets/javascripts/milestone_select.js @@ -101,6 +101,7 @@ // display:block overrides the hide-collapse rule return $value.css('display', ''); }, + vue: $dropdown.hasClass('js-issue-board-sidebar'), clicked: function(selected, $el, e) { var data, isIssueIndex, isMRIndex, page; page = $('body').data('page'); diff --git a/app/assets/javascripts/users_select.js b/app/assets/javascripts/users_select.js index d3868f392ea..67a71f6c381 100644 --- a/app/assets/javascripts/users_select.js +++ b/app/assets/javascripts/users_select.js @@ -175,6 +175,7 @@ // display:block overrides the hide-collapse rule return $value.css('display', ''); }, + vue: $dropdown.hasClass('js-issue-board-sidebar'), clicked: function(user, $el, e) { var isIssueIndex, isMRIndex, page, selected; page = $('body').data('page'); @@ -185,7 +186,7 @@ selectedId = user.id; return; } - if (page === 'projects:boards:show' && !$dropdown.hasClass('js-issue-board-assignee')) { + if (page === 'projects:boards:show' && !$dropdown.hasClass('js-issue-board-sidebar')) { selectedId = user.id; gl.issueBoards.BoardsStore.state.filters[$dropdown.data('field-name')] = user.id; gl.issueBoards.BoardsStore.updateFiltersUrl(); @@ -195,7 +196,7 @@ return Issuable.filterResults($dropdown.closest('form')); } else if ($dropdown.hasClass('js-filter-submit')) { return $dropdown.closest('form').submit(); - } else if ($dropdown.hasClass('js-issue-board-assignee')) { + } else if ($dropdown.hasClass('js-issue-board-sidebar')) { if (user.id) { Vue.set(gl.issueBoards.BoardsStore.detail.issue, 'assignee', new ListUser({ id: user.id, diff --git a/app/views/projects/boards/components/sidebar/_assignee.html.haml b/app/views/projects/boards/components/sidebar/_assignee.html.haml index 57039237d0b..92f2a931668 100644 --- a/app/views/projects/boards/components/sidebar/_assignee.html.haml +++ b/app/views/projects/boards/components/sidebar/_assignee.html.haml @@ -28,7 +28,7 @@ ":value" => "issue.assignee.id", "v-if" => "issue.assignee" } .dropdown - %button.dropdown-menu-toggle.js-user-search.js-author-search.js-issue-board-assignee{ data: { toggle: "dropdown", field_name: "issue[assignee_id]", first_user: (current_user.username if current_user), current_user: "true", project_id: @project.id, field_name: "issue[assignee_id]", null_user: "true" }, + %button.dropdown-menu-toggle.js-user-search.js-author-search.js-issue-board-sidebar{ data: { toggle: "dropdown", field_name: "issue[assignee_id]", first_user: (current_user.username if current_user), current_user: "true", project_id: @project.id, field_name: "issue[assignee_id]", null_user: "true" }, ":data-issuable-id" => "issue.id", ":data-issue-update" => "'#{namespace_project_issues_path(@project.namespace, @project)}/' + issue.id + '.json'" } Select assignee diff --git a/app/views/projects/boards/components/sidebar/_labels.html.haml b/app/views/projects/boards/components/sidebar/_labels.html.haml index 865bfa6d1da..0e2ea74ec41 100644 --- a/app/views/projects/boards/components/sidebar/_labels.html.haml +++ b/app/views/projects/boards/components/sidebar/_labels.html.haml @@ -18,7 +18,7 @@ "v-for" => "label in issue.labels", ":value" => "label.id" } .dropdown - %button.dropdown-menu-toggle.js-label-select.js-multiselect.js-issue-boards-label{ type: "button", + %button.dropdown-menu-toggle.js-label-select.js-multiselect.js-issue-board-sidebar{ type: "button", data: { toggle: "dropdown", field_name: "issue[label_names][]", show_no: "true", show_any: "true", project_id: @project.id, labels: namespace_project_labels_path(@project.namespace, @project, :json) }, ":data-issue-update" => "'#{namespace_project_issues_path(@project.namespace, @project)}/' + issue.id + '.json'" } %span.dropdown-toggle-text From 18607d6c88e820b927d90ca1d247a23a61be8c99 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 6 Oct 2016 14:36:44 +0100 Subject: [PATCH 09/25] Added tests --- .../boards/filters/due_date_filters.js.es6 | 2 +- app/assets/javascripts/users_select.js | 2 +- .../components/sidebar/_assignee.html.haml | 2 +- spec/features/boards/sidebar_spec.rb | 297 ++++++++++++++++++ 4 files changed, 300 insertions(+), 3 deletions(-) create mode 100644 spec/features/boards/sidebar_spec.rb diff --git a/app/assets/javascripts/boards/filters/due_date_filters.js.es6 b/app/assets/javascripts/boards/filters/due_date_filters.js.es6 index 5c1519986c1..50ef1911022 100644 --- a/app/assets/javascripts/boards/filters/due_date_filters.js.es6 +++ b/app/assets/javascripts/boards/filters/due_date_filters.js.es6 @@ -1,4 +1,4 @@ Vue.filter('due-date', (value) => { - const date = new Date(value.replace(new RegExp('-', 'g'), ',')); + const date = new Date(value); return $.datepicker.formatDate('M d, yy', date); }); diff --git a/app/assets/javascripts/users_select.js b/app/assets/javascripts/users_select.js index 67a71f6c381..82c75c614b1 100644 --- a/app/assets/javascripts/users_select.js +++ b/app/assets/javascripts/users_select.js @@ -48,7 +48,7 @@ $block.on('click', '.js-assign-yourself', function(e) { e.preventDefault(); - if ($dropdown.hasClass('js-issue-board-assignee')) { + if ($dropdown.hasClass('js-issue-board-sidebar')) { Vue.set(gl.issueBoards.BoardsStore.detail.issue, 'assignee', new ListUser({ id: _this.currentUser.id, username: _this.currentUser.username, diff --git a/app/views/projects/boards/components/sidebar/_assignee.html.haml b/app/views/projects/boards/components/sidebar/_assignee.html.haml index 92f2a931668..4307e8e7626 100644 --- a/app/views/projects/boards/components/sidebar/_assignee.html.haml +++ b/app/views/projects/boards/components/sidebar/_assignee.html.haml @@ -28,7 +28,7 @@ ":value" => "issue.assignee.id", "v-if" => "issue.assignee" } .dropdown - %button.dropdown-menu-toggle.js-user-search.js-author-search.js-issue-board-sidebar{ data: { toggle: "dropdown", field_name: "issue[assignee_id]", first_user: (current_user.username if current_user), current_user: "true", project_id: @project.id, field_name: "issue[assignee_id]", null_user: "true" }, + %button.dropdown-menu-toggle.js-user-search.js-author-search.js-issue-board-sidebar{ data: { toggle: "dropdown", field_name: "issue[assignee_id]", first_user: (current_user.username if current_user), current_user: "true", project_id: @project.id, null_user: "true" }, ":data-issuable-id" => "issue.id", ":data-issue-update" => "'#{namespace_project_issues_path(@project.namespace, @project)}/' + issue.id + '.json'" } Select assignee diff --git a/spec/features/boards/sidebar_spec.rb b/spec/features/boards/sidebar_spec.rb new file mode 100644 index 00000000000..2e754287f3a --- /dev/null +++ b/spec/features/boards/sidebar_spec.rb @@ -0,0 +1,297 @@ +require 'rails_helper' + +describe 'Issue Boards', feature: true, js: true do + include WaitForAjax + include WaitForVueResource + + let(:project) { create(:project_with_board, :public) } + let(:user) { create(:user) } + let!(:label) { create(:label, project: project) } + let!(:label2) { create(:label, project: project) } + let!(:milestone) { create(:milestone, project: project) } + let!(:issue2) { create(:labeled_issue, project: project, assignee: user, milestone: milestone, labels: [label]) } + let!(:issue) { create(:issue, project: project) } + + before do + project.team << [user, :master] + + login_as(user) + + visit namespace_project_board_path(project.namespace, project) + wait_for_vue_resource + end + + it 'shows sidebar when clicking issue' do + page.within(first('.board')) do + first('.card').click + end + + expect(page).to have_selector('.issue-boards-sidebar') + end + + it 'closes sidebar when clicking issue' do + page.within(first('.board')) do + first('.card').click + end + + expect(page).to have_selector('.issue-boards-sidebar') + + page.within(first('.board')) do + first('.card').click + end + + expect(page).not_to have_selector('.issue-boards-sidebar') + end + + it 'closes sidebar when clicking close button' do + page.within(first('.board')) do + first('.card').click + end + + expect(page).to have_selector('.issue-boards-sidebar') + + find('.gutter-toggle').click + + expect(page).not_to have_selector('.issue-boards-sidebar') + end + + it 'shows issue details when sidebar is open' do + page.within(first('.board')) do + first('.card').click + end + + page.within('.issue-boards-sidebar') do + expect(page).to have_content(issue.title) + expect(page).to have_content(issue.to_reference) + end + end + + context 'assignee' do + it 'updates the issues assignee' do + page.within(first('.board')) do + first('.card').click + end + + page.within('.assignee') do + click_link 'Edit' + + wait_for_ajax + + page.within('.dropdown-menu-user') do + click_link user.name + + wait_for_vue_resource + end + + expect(page).to have_content(user.name) + end + + page.within(first('.board')) do + page.within(first('.card')) do + expect(page).to have_selector('.avatar') + end + end + end + + it 'removes the assignee' do + page.within(first('.board')) do + find('.card:nth-child(2)').click + end + + page.within('.assignee') do + click_link 'Edit' + + wait_for_ajax + + page.within('.dropdown-menu-user') do + click_link 'Unassigned' + + wait_for_vue_resource + end + + expect(page).to have_content('No assignee') + end + + page.within(first('.board')) do + page.within(find('.card:nth-child(2)')) do + expect(page).not_to have_selector('.avatar') + end + end + end + + it 'assignees to current user' do + page.within(first('.board')) do + first('.card').click + end + + page.within('.assignee') do + click_link 'assign yourself' + + wait_for_vue_resource + + expect(page).to have_content(user.name) + end + + page.within(first('.board')) do + page.within(first('.card')) do + expect(page).to have_selector('.avatar') + end + end + end + end + + context 'milestone' do + it 'adds a milestone' do + page.within(first('.board')) do + first('.card').click + end + + page.within('.milestone') do + click_link 'Edit' + + wait_for_ajax + + click_link milestone.title + + wait_for_vue_resource + + page.within('.value') do + expect(page).to have_content(milestone.title) + end + end + end + + it 'removes a milestone' do + page.within(first('.board')) do + find('.card:nth-child(2)').click + end + + page.within('.milestone') do + click_link 'Edit' + + wait_for_ajax + + click_link "No Milestone" + + wait_for_vue_resource + + page.within('.value') do + expect(page).not_to have_content(milestone.title) + end + end + end + end + + context 'due date' do + it 'updates due date' do + page.within(first('.board')) do + first('.card').click + end + + page.within('.due_date') do + click_link 'Edit' + + click_link Date.today.day + + wait_for_vue_resource + + expect(page).to have_content(Date.today.to_s(:medium)) + end + end + end + + context 'labels' do + it 'adds a single label' do + page.within(first('.board')) do + first('.card').click + end + + page.within('.labels') do + click_link 'Edit' + + wait_for_ajax + + click_link label.title + + wait_for_vue_resource + + find('.dropdown-menu-close-icon').click + + page.within('.value') do + expect(page).to have_selector('.label', count: 1) + expect(page).to have_content(label.title) + end + end + + page.within(first('.board')) do + page.within(first('.card')) do + expect(page).to have_selector('.label', count: 1) + expect(page).to have_content(label.title) + end + end + end + + it 'adds a multiple labels' do + page.within(first('.board')) do + first('.card').click + end + + page.within('.labels') do + click_link 'Edit' + + wait_for_ajax + + click_link label.title + click_link label2.title + + wait_for_vue_resource + + find('.dropdown-menu-close-icon').click + + page.within('.value') do + expect(page).to have_selector('.label', count: 2) + expect(page).to have_content(label.title) + expect(page).to have_content(label2.title) + end + end + + page.within(first('.board')) do + page.within(first('.card')) do + expect(page).to have_selector('.label', count: 2) + expect(page).to have_content(label.title) + expect(page).to have_content(label2.title) + end + end + end + + it 'removes a label' do + page.within(first('.board')) do + find('.card:nth-child(2)').click + end + + page.within('.labels') do + click_link 'Edit' + + wait_for_ajax + + click_link label.title + + wait_for_vue_resource + + find('.dropdown-menu-close-icon').click + + page.within('.value') do + expect(page).to have_selector('.label', count: 0) + expect(page).not_to have_content(label.title) + end + end + + page.within(first('.board')) do + page.within(find('.card:nth-child(2)')) do + expect(page).not_to have_selector('.label', count: 1) + expect(page).not_to have_content(label.title) + end + end + end + end +end From 0c286d54737f685caf7ec21320d43d8069f2001d Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Thu, 6 Oct 2016 11:51:38 -0300 Subject: [PATCH 10/25] Fix JSON Schema that validates data returned by board issues endpoint --- spec/controllers/projects/boards/issues_controller_spec.rb | 2 +- spec/fixtures/api/schemas/issue.json | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/spec/controllers/projects/boards/issues_controller_spec.rb b/spec/controllers/projects/boards/issues_controller_spec.rb index 566658b508d..0eebb5a4624 100644 --- a/spec/controllers/projects/boards/issues_controller_spec.rb +++ b/spec/controllers/projects/boards/issues_controller_spec.rb @@ -21,7 +21,7 @@ describe Projects::Boards::IssuesController do it 'returns issues that have the list label applied' do johndoe = create(:user, avatar: fixture_file_upload(File.join(Rails.root, 'spec/fixtures/dk.png'))) create(:labeled_issue, project: project, labels: [planning]) - create(:labeled_issue, project: project, labels: [development]) + create(:labeled_issue, project: project, labels: [development], due_date: Date.tomorrow) create(:labeled_issue, project: project, labels: [development], assignee: johndoe) list_issues user: user, list_id: list2 diff --git a/spec/fixtures/api/schemas/issue.json b/spec/fixtures/api/schemas/issue.json index 532ebb9640e..6de6618e475 100644 --- a/spec/fixtures/api/schemas/issue.json +++ b/spec/fixtures/api/schemas/issue.json @@ -9,6 +9,7 @@ "iid": { "type": "integer" }, "title": { "type": "string" }, "confidential": { "type": "boolean" }, + "due_date": { "type": ["date", "null"] }, "labels": { "type": "array", "items": { From 1aff95c76844adb880e7f935deab8af5e797fb51 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 6 Oct 2016 16:14:12 +0100 Subject: [PATCH 11/25] Fix issue when clicking links inside issue showing sidebar --- app/assets/javascripts/boards/components/board_card.js.es6 | 6 +++++- app/views/projects/boards/components/_card.html.haml | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/boards/components/board_card.js.es6 b/app/assets/javascripts/boards/components/board_card.js.es6 index 27c78e7f02e..bcb1b60b978 100644 --- a/app/assets/javascripts/boards/components/board_card.js.es6 +++ b/app/assets/javascripts/boards/components/board_card.js.es6 @@ -61,7 +61,11 @@ this.showDetail = false; } }, - showIssue () { + showIssue (e) { + const targetTagName = e.target.tagName.toLowerCase(); + + if (targetTagName === 'a' || targetTagName === 'button') return; + if (this.showDetail) { this.showDetail = false; diff --git a/app/views/projects/boards/components/_card.html.haml b/app/views/projects/boards/components/_card.html.haml index d466165816f..5cfd42485c5 100644 --- a/app/views/projects/boards/components/_card.html.haml +++ b/app/views/projects/boards/components/_card.html.haml @@ -11,7 +11,7 @@ ":index" => "index", "@mousedown" => "mouseDown", "@mouseMove" => "mouseMove", - "@mouseup" => "showIssue" } + "@mouseup" => "showIssue($event)" } %h4.card-title = icon("eye-slash", class: "confidential-icon", "v-if" => "issue.confidential") %a{ ":href" => "issueLinkBase + '/' + issue.id", From 7d20a91b2ecf0af89b3a6d3a5d4d8621114687ec Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 7 Oct 2016 09:24:57 +0100 Subject: [PATCH 12/25] Restore subscribe status in JSON --- app/controllers/projects/boards/issues_controller.rb | 7 +++++-- app/models/issue.rb | 6 ++++++ spec/controllers/projects/boards/issues_controller_spec.rb | 2 ++ spec/fixtures/api/schemas/issue.json | 3 ++- 4 files changed, 15 insertions(+), 3 deletions(-) diff --git a/app/controllers/projects/boards/issues_controller.rb b/app/controllers/projects/boards/issues_controller.rb index b5a56d11d32..fbb06c0ffba 100644 --- a/app/controllers/projects/boards/issues_controller.rb +++ b/app/controllers/projects/boards/issues_controller.rb @@ -76,8 +76,11 @@ module Projects only: [:iid, :title, :confidential, :due_date], include: { assignee: { only: [:id, :name, :username], methods: [:avatar_url] }, - labels: { only: [:id, :title, :description, :color, :priority], methods: [:text_color] } - }) + labels: { only: [:id, :title, :description, :color, :priority], methods: [:text_color] }, + milestone: { only: [:id, :title] } + }, + user: current_user + ) end end end diff --git a/app/models/issue.rb b/app/models/issue.rb index abd58e0454a..89794290520 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -274,4 +274,10 @@ class Issue < ActiveRecord::Base def check_for_spam? project.public? end + + def as_json(options = {}) + super(options).tap do |json| + json[:subscribed] = subscribed?(options[:user]) if options.has_key?(:user) + end + end end diff --git a/spec/controllers/projects/boards/issues_controller_spec.rb b/spec/controllers/projects/boards/issues_controller_spec.rb index 0eebb5a4624..75f6e7f54e2 100644 --- a/spec/controllers/projects/boards/issues_controller_spec.rb +++ b/spec/controllers/projects/boards/issues_controller_spec.rb @@ -20,9 +20,11 @@ describe Projects::Boards::IssuesController do context 'with valid list id' do it 'returns issues that have the list label applied' do johndoe = create(:user, avatar: fixture_file_upload(File.join(Rails.root, 'spec/fixtures/dk.png'))) + issue = create(:labeled_issue, project: project, labels: [planning]) create(:labeled_issue, project: project, labels: [planning]) create(:labeled_issue, project: project, labels: [development], due_date: Date.tomorrow) create(:labeled_issue, project: project, labels: [development], assignee: johndoe) + issue.subscribe(johndoe) list_issues user: user, list_id: list2 diff --git a/spec/fixtures/api/schemas/issue.json b/spec/fixtures/api/schemas/issue.json index 6de6618e475..77f2bcee1f3 100644 --- a/spec/fixtures/api/schemas/issue.json +++ b/spec/fixtures/api/schemas/issue.json @@ -43,7 +43,8 @@ "name": { "type": "string" }, "username": { "type": "string" }, "avatar_url": { "type": "uri" } - } + }, + "subscribed": { "type": ["boolean", "null"] } }, "additionalProperties": false } From e4571614983ca6ba7ef7c4ea8c4bc5bbd992ce32 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 7 Oct 2016 09:38:35 +0100 Subject: [PATCH 13/25] Make the subscribe button work correctly --- .../javascripts/boards/components/board_new_issue.js.es6 | 7 ++++++- app/assets/javascripts/boards/models/issue.js.es6 | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/boards/components/board_new_issue.js.es6 b/app/assets/javascripts/boards/components/board_new_issue.js.es6 index a4fad422eca..b31adfdb8da 100644 --- a/app/assets/javascripts/boards/components/board_new_issue.js.es6 +++ b/app/assets/javascripts/boards/components/board_new_issue.js.es6 @@ -1,4 +1,6 @@ (() => { + const Store = gl.issueBoards.BoardsStore; + window.gl = window.gl || {}; gl.issueBoards.BoardNewIssue = Vue.extend({ @@ -27,13 +29,16 @@ const labels = this.list.label ? [this.list.label] : []; const issue = new ListIssue({ title: this.title, - labels + labels, + subscribed: true }); this.list.newIssue(issue) .then((data) => { // Need this because our jQuery very kindly disables buttons on ALL form submissions $(this.$els.submitButton).enable(); + + Store.detail.issue = issue; }) .catch(() => { // Need this because our jQuery very kindly disables buttons on ALL form submissions diff --git a/app/assets/javascripts/boards/models/issue.js.es6 b/app/assets/javascripts/boards/models/issue.js.es6 index ca013aa92b5..9704274b886 100644 --- a/app/assets/javascripts/boards/models/issue.js.es6 +++ b/app/assets/javascripts/boards/models/issue.js.es6 @@ -4,7 +4,7 @@ class ListIssue { this.title = obj.title; this.confidential = obj.confidential; this.dueDate = obj.due_date; - this.subscribed = true; + this.subscribed = obj.subscribed; this.labels = []; if (obj.assignee) { From b602023e95f130e4b24cf2be1817f4a22444bb5b Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 7 Oct 2016 09:55:12 +0100 Subject: [PATCH 14/25] Added tests for showing sidebar when new issue is saved --- spec/features/boards/new_issue_spec.rb | 15 +++++++++++++++ spec/features/boards/sidebar_spec.rb | 14 ++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/spec/features/boards/new_issue_spec.rb b/spec/features/boards/new_issue_spec.rb index c046e6b8d79..c776c977416 100644 --- a/spec/features/boards/new_issue_spec.rb +++ b/spec/features/boards/new_issue_spec.rb @@ -65,6 +65,21 @@ describe 'Issue Boards new issue', feature: true, js: true do expect(page).to have_content('1') end end + + it 'shows sidebar when creating new issue' do + page.within(first('.board')) do + find('.board-issue-count-holder .btn').click + end + + page.within(first('.board-new-issue-form')) do + find('.form-control').set('bug') + click_button 'Submit issue' + end + + wait_for_vue_resource + + expect(page).to have_selector('.issue-boards-sidebar') + end end context 'unauthorized user' do diff --git a/spec/features/boards/sidebar_spec.rb b/spec/features/boards/sidebar_spec.rb index 2e754287f3a..905d720705d 100644 --- a/spec/features/boards/sidebar_spec.rb +++ b/spec/features/boards/sidebar_spec.rb @@ -294,4 +294,18 @@ describe 'Issue Boards', feature: true, js: true do end end end + + context 'subscription' do + it 'changes issue subscription' do + page.within(first('.board')) do + first('.card').click + end + + page.within('.subscription') do + click_button 'Subscribe' + + expect(page).to have_content("You're receiving notifications because you're subscribed to this thread.") + end + end + end end From 306e55261e055081b08c7774eaa9e741c4f5515a Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 7 Oct 2016 11:12:02 +0100 Subject: [PATCH 15/25] Fixed filter specs --- app/assets/javascripts/labels_select.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/labels_select.js b/app/assets/javascripts/labels_select.js index 03e6136509a..19eb0ab4294 100644 --- a/app/assets/javascripts/labels_select.js +++ b/app/assets/javascripts/labels_select.js @@ -22,7 +22,7 @@ abilityName = $dropdown.data('ability-name'); $selectbox = $dropdown.closest('.selectbox'); $block = $selectbox.closest('.block'); - $form = $dropdown.closest('.js-issuable-update'); + $form = $dropdown.closest('form, .js-issuable-update'); $sidebarCollapsedValue = $block.find('.sidebar-collapsed-icon span'); $sidebarLabelTooltip = $block.find('.js-sidebar-labels-tooltip'); $value = $block.find('.value'); From e4176f4ec42fa8e391b088d62deef051cdafaf23 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 7 Oct 2016 11:20:22 +0100 Subject: [PATCH 16/25] Fixed some JS styling --- app/assets/javascripts/boards/components/board_sidebar.js.es6 | 2 +- app/assets/javascripts/boards/models/issue.js.es6 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/boards/components/board_sidebar.js.es6 b/app/assets/javascripts/boards/components/board_sidebar.js.es6 index 12f69f93279..8d217f0f573 100644 --- a/app/assets/javascripts/boards/components/board_sidebar.js.es6 +++ b/app/assets/javascripts/boards/components/board_sidebar.js.es6 @@ -34,7 +34,7 @@ new DueDateSelect(); new LabelsSelect(); new Sidebar(); - new Subscription('.subscription') + new Subscription('.subscription'); }); } else { $('.right-sidebar').getNiceScroll().remove(); diff --git a/app/assets/javascripts/boards/models/issue.js.es6 b/app/assets/javascripts/boards/models/issue.js.es6 index 9704274b886..03fd6c05d87 100644 --- a/app/assets/javascripts/boards/models/issue.js.es6 +++ b/app/assets/javascripts/boards/models/issue.js.es6 @@ -54,7 +54,7 @@ class ListIssue { milestone_id: this.milestone ? this.milestone.id : null, due_date: this.dueDate, assignee_id: this.assignee ? this.assignee.id : null, - label_ids: this.labels.map((label) => label.id ) + label_ids: this.labels.map( (label) => label.id ) } }; From a0f6526f02ac855027d7dfe763aab4b2a9978dca Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 19 Oct 2016 10:26:28 +0100 Subject: [PATCH 17/25] Tests update --- spec/features/boards/sidebar_spec.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/spec/features/boards/sidebar_spec.rb b/spec/features/boards/sidebar_spec.rb index 905d720705d..f160052a844 100644 --- a/spec/features/boards/sidebar_spec.rb +++ b/spec/features/boards/sidebar_spec.rb @@ -4,7 +4,8 @@ describe 'Issue Boards', feature: true, js: true do include WaitForAjax include WaitForVueResource - let(:project) { create(:project_with_board, :public) } + let(:project) { create(:empty_project, :public) } + let(:board) { create(:board, project: project) } let(:user) { create(:user) } let!(:label) { create(:label, project: project) } let!(:label2) { create(:label, project: project) } @@ -17,7 +18,7 @@ describe 'Issue Boards', feature: true, js: true do login_as(user) - visit namespace_project_board_path(project.namespace, project) + visit namespace_project_board_path(project.namespace, project, board) wait_for_vue_resource end From cd5e83b6d6da3bddbc44334a1bcdbac287b35fb4 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 19 Oct 2016 22:26:51 +0100 Subject: [PATCH 18/25] Scroll board into view when clicking issue Changed return statement instead of if Delete objects after issue is closed --- .../javascripts/boards/boards_bundle.js.es6 | 8 ++++++- .../boards/components/board.js.es6 | 21 +++++++++++++++++++ .../boards/components/board_card.js.es6 | 6 +----- .../boards/components/board_sidebar.js.es6 | 19 +++++++++++------ app/assets/stylesheets/pages/boards.scss | 4 ++++ .../boards/components/_sidebar.html.haml | 2 +- app/views/projects/boards/index.html.haml | 2 +- app/views/projects/boards/show.html.haml | 2 +- 8 files changed, 49 insertions(+), 15 deletions(-) diff --git a/app/assets/javascripts/boards/boards_bundle.js.es6 b/app/assets/javascripts/boards/boards_bundle.js.es6 index 0cecdc4f50a..56f91503017 100644 --- a/app/assets/javascripts/boards/boards_bundle.js.es6 +++ b/app/assets/javascripts/boards/boards_bundle.js.es6 @@ -33,9 +33,15 @@ $(() => { endpoint: $boardApp.dataset.endpoint, boardId: $boardApp.dataset.boardId, disabled: $boardApp.dataset.disabled === 'true', - issueLinkBase: $boardApp.dataset.issueLinkBase + issueLinkBase: $boardApp.dataset.issueLinkBase, + detailIssue: Store.detail }, init: Store.create.bind(Store), + computed: { + detailIssueVisible () { + return Object.keys(this.detailIssue.issue).length; + } + }, created () { gl.boardService = new BoardService(this.endpoint, this.boardId); }, diff --git a/app/assets/javascripts/boards/components/board.js.es6 b/app/assets/javascripts/boards/components/board.js.es6 index cacb36a897f..500213a3a43 100644 --- a/app/assets/javascripts/boards/components/board.js.es6 +++ b/app/assets/javascripts/boards/components/board.js.es6 @@ -21,6 +21,7 @@ }, data () { return { + detailIssue: Store.detail, filters: Store.state.filters, showIssueForm: false }; @@ -32,6 +33,26 @@ this.list.getIssues(true); }, deep: true + }, + detailIssue: { + handler () { + if (!Object.keys(this.detailIssue.issue).length) return; + + const issue = this.list.findIssue(this.detailIssue.issue.id); + + if (issue) { + const boardsList = document.querySelectorAll('.boards-list')[0]; + const right = (this.$el.offsetLeft + this.$el.offsetWidth) - boardsList.offsetWidth; + const left = boardsList.scrollLeft - this.$el.offsetLeft; + + if (right - boardsList.scrollLeft > 0) { + boardsList.scrollLeft = right; + } else if (left > 0) { + boardsList.scrollLeft = this.$el.offsetLeft; + } + } + }, + deep: true } }, methods: { diff --git a/app/assets/javascripts/boards/components/board_card.js.es6 b/app/assets/javascripts/boards/components/board_card.js.es6 index bcb1b60b978..f743d72f936 100644 --- a/app/assets/javascripts/boards/components/board_card.js.es6 +++ b/app/assets/javascripts/boards/components/board_card.js.es6 @@ -20,11 +20,7 @@ }, computed: { issueDetailVisible () { - if (this.detailIssue.issue && this.detailIssue.issue.id === this.issue.id) { - return true; - } else { - return false; - } + return this.detailIssue.issue && this.detailIssue.issue.id === this.issue.id; } }, methods: { diff --git a/app/assets/javascripts/boards/components/board_sidebar.js.es6 b/app/assets/javascripts/boards/components/board_sidebar.js.es6 index 1c002a54bed..35d97531439 100644 --- a/app/assets/javascripts/boards/components/board_sidebar.js.es6 +++ b/app/assets/javascripts/boards/components/board_sidebar.js.es6 @@ -29,15 +29,22 @@ issue () { if (this.showSidebar) { this.$nextTick(() => { - new IssuableContext(this.currentUser); - new MilestoneSelect(); - new gl.DueDateSelectors(); - new LabelsSelect(); - new Sidebar(); - new Subscription('.subscription'); + this.issuableContext = new IssuableContext(this.currentUser); + this.milestoneSelect = new MilestoneSelect(); + this.dueDateSelect = new gl.DueDateSelectors(); + this.labelsSelect = new LabelsSelect(); + this.sidebar = new Sidebar(); + this.subscription = new Subscription('.subscription'); }); } else { $('.right-sidebar').getNiceScroll().remove(); + + delete this.issuableContext; + delete this.milestoneSelect; + delete this.dueDateSelect; + delete this.labelsSelect; + delete this.sidebar; + delete this.subscription; } } }, diff --git a/app/assets/stylesheets/pages/boards.scss b/app/assets/stylesheets/pages/boards.scss index 0807d583207..8e972020234 100644 --- a/app/assets/stylesheets/pages/boards.scss +++ b/app/assets/stylesheets/pages/boards.scss @@ -76,6 +76,10 @@ lex height: 475px; // Needed for PhantomJS height: calc(100vh - 220px); min-height: 475px; + + &.is-compact { + width: calc(100% - 290px); + } } } diff --git a/app/views/projects/boards/components/_sidebar.html.haml b/app/views/projects/boards/components/_sidebar.html.haml index cf70ad4927a..7907fdfa810 100644 --- a/app/views/projects/boards/components/_sidebar.html.haml +++ b/app/views/projects/boards/components/_sidebar.html.haml @@ -12,7 +12,7 @@ {{ issue.id }} %a.gutter-toggle.pull-right{ role: "button", href: "#", - "@click" => "closeSidebar", + "@click.prevent" => "closeSidebar", "aria-label" => "Toggle sidebar" } = icon("times") .js-issuable-update diff --git a/app/views/projects/boards/index.html.haml b/app/views/projects/boards/index.html.haml index 45c2e0ee2da..29c9a43a0c1 100644 --- a/app/views/projects/boards/index.html.haml +++ b/app/views/projects/boards/index.html.haml @@ -11,7 +11,7 @@ = render 'shared/issuable/filter', type: :boards #board-app.boards-app{ "v-cloak" => true, data: board_data } - .boards-list + .boards-list{ ":class" => "{ 'is-compact': detailIssueVisible }" } .boards-app-loading.text-center{ "v-if" => "loading" } = icon("spinner spin") = render "projects/boards/components/board" diff --git a/app/views/projects/boards/show.html.haml b/app/views/projects/boards/show.html.haml index 45c2e0ee2da..29c9a43a0c1 100644 --- a/app/views/projects/boards/show.html.haml +++ b/app/views/projects/boards/show.html.haml @@ -11,7 +11,7 @@ = render 'shared/issuable/filter', type: :boards #board-app.boards-app{ "v-cloak" => true, data: board_data } - .boards-list + .boards-list{ ":class" => "{ 'is-compact': detailIssueVisible }" } .boards-app-loading.text-center{ "v-if" => "loading" } = icon("spinner spin") = render "projects/boards/components/board" From 344154e9068945af2874d6cb253457fb90fef3f3 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 19 Oct 2016 22:39:57 +0100 Subject: [PATCH 19/25] Updated close sidebar icon --- app/assets/stylesheets/pages/boards.scss | 10 ++++++++-- .../projects/boards/components/_sidebar.html.haml | 2 +- app/views/shared/icons/_icon_close.svg | 1 + 3 files changed, 10 insertions(+), 3 deletions(-) create mode 100644 app/views/shared/icons/_icon_close.svg diff --git a/app/assets/stylesheets/pages/boards.scss b/app/assets/stylesheets/pages/boards.scss index 8e972020234..641f35f1609 100644 --- a/app/assets/stylesheets/pages/boards.scss +++ b/app/assets/stylesheets/pages/boards.scss @@ -302,10 +302,16 @@ lex width: 22px; color: $gray-darkest; - .fa { + svg { position: absolute; top: 50%; - margin-top: (-15px / 2); + margin-top: (-11px / 2); + } + + &:hover { + path { + fill: $gray-darkest; + } } } diff --git a/app/views/projects/boards/components/_sidebar.html.haml b/app/views/projects/boards/components/_sidebar.html.haml index 7907fdfa810..9616ee5b795 100644 --- a/app/views/projects/boards/components/_sidebar.html.haml +++ b/app/views/projects/boards/components/_sidebar.html.haml @@ -14,7 +14,7 @@ href: "#", "@click.prevent" => "closeSidebar", "aria-label" => "Toggle sidebar" } - = icon("times") + = custom_icon("icon_close", size: 15) .js-issuable-update = render "projects/boards/components/sidebar/assignee" = render "projects/boards/components/sidebar/milestone" diff --git a/app/views/shared/icons/_icon_close.svg b/app/views/shared/icons/_icon_close.svg new file mode 100644 index 00000000000..9d62012518b --- /dev/null +++ b/app/views/shared/icons/_icon_close.svg @@ -0,0 +1 @@ + \ No newline at end of file From 29645f06e7889fe85bb155abaa0f361f8680311b Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 20 Oct 2016 08:54:33 +0100 Subject: [PATCH 20/25] Hides/shows the boards sidebar Rather than constructing & then deconstructing, we know just hide & show the sidebar. This is done so we dont' have a memory leak on the frontend with objects getting created. --- .../boards/components/board_sidebar.js.es6 | 24 ++++++--------- app/assets/javascripts/due_date_select.js.es6 | 29 +++++++++++++++++-- app/assets/javascripts/labels_select.js | 2 +- app/assets/javascripts/milestone_select.js | 2 +- app/assets/javascripts/subscription.js | 15 ++++++---- app/assets/javascripts/users_select.js | 2 +- .../boards/components/_sidebar.html.haml | 2 +- .../components/sidebar/_due_date.html.haml | 9 +++--- .../components/sidebar/_labels.html.haml | 2 +- 9 files changed, 55 insertions(+), 32 deletions(-) diff --git a/app/assets/javascripts/boards/components/board_sidebar.js.es6 b/app/assets/javascripts/boards/components/board_sidebar.js.es6 index 35d97531439..b20890df622 100644 --- a/app/assets/javascripts/boards/components/board_sidebar.js.es6 +++ b/app/assets/javascripts/boards/components/board_sidebar.js.es6 @@ -29,22 +29,8 @@ issue () { if (this.showSidebar) { this.$nextTick(() => { - this.issuableContext = new IssuableContext(this.currentUser); - this.milestoneSelect = new MilestoneSelect(); - this.dueDateSelect = new gl.DueDateSelectors(); - this.labelsSelect = new LabelsSelect(); - this.sidebar = new Sidebar(); - this.subscription = new Subscription('.subscription'); + $(".right-sidebar").getNiceScroll(0).doScrollTop(0, 0); }); - } else { - $('.right-sidebar').getNiceScroll().remove(); - - delete this.issuableContext; - delete this.milestoneSelect; - delete this.dueDateSelect; - delete this.labelsSelect; - delete this.sidebar; - delete this.subscription; } } }, @@ -52,6 +38,14 @@ closeSidebar () { this.detail.issue = {}; } + }, + ready () { + new IssuableContext(this.currentUser); + new MilestoneSelect(); + new gl.DueDateSelectors(); + new LabelsSelect(); + new Sidebar(); + new Subscription('.subscription'); } }); })(); diff --git a/app/assets/javascripts/due_date_select.js.es6 b/app/assets/javascripts/due_date_select.js.es6 index 41925fcc8e3..4f7c1092d05 100644 --- a/app/assets/javascripts/due_date_select.js.es6 +++ b/app/assets/javascripts/due_date_select.js.es6 @@ -41,7 +41,12 @@ defaultDate: $("input[name='" + this.fieldName + "']").val(), altField: "input[name='" + this.fieldName + "']", onSelect: () => { - return this.saveDueDate(true); + if (this.$dropdown.hasClass('js-issue-boards-due-date')) { + gl.issueBoards.BoardsStore.detail.issue.dueDate = $("input[name='" + this.fieldName + "']").val(); + this.updateIssueBoardIssue(); + } else { + return this.saveDueDate(true); + } } }); } @@ -49,8 +54,14 @@ initRemoveDueDate() { this.$block.on('click', '.js-remove-due-date', (e) => { e.preventDefault(); - $("input[name='" + this.fieldName + "']").val(''); - return this.saveDueDate(false); + + if (this.$dropdown.hasClass('js-issue-boards-due-date')) { + gl.issueBoards.BoardsStore.detail.issue.dueDate = ''; + this.updateIssueBoardIssue(); + } else { + $("input[name='" + this.fieldName + "']").val(''); + return this.saveDueDate(false); + } }); } @@ -83,6 +94,18 @@ 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', diff --git a/app/assets/javascripts/labels_select.js b/app/assets/javascripts/labels_select.js index c532737626c..4e29ab71bd4 100644 --- a/app/assets/javascripts/labels_select.js +++ b/app/assets/javascripts/labels_select.js @@ -382,7 +382,7 @@ $loading.fadeIn(); - gl.issueBoards.BoardsStore.detail.issue.update(issueUpdateURL) + gl.issueBoards.BoardsStore.detail.issue.update($dropdown.attr('data-issue-update')) .then(function () { $loading.fadeOut(); }); diff --git a/app/assets/javascripts/milestone_select.js b/app/assets/javascripts/milestone_select.js index bed1d52c989..95b5b934c81 100644 --- a/app/assets/javascripts/milestone_select.js +++ b/app/assets/javascripts/milestone_select.js @@ -137,7 +137,7 @@ $dropdown.trigger('loading.gl.dropdown'); $loading.fadeIn(); - gl.issueBoards.BoardsStore.detail.issue.update(issueUpdateURL) + gl.issueBoards.BoardsStore.detail.issue.update($dropdown.attr('data-issue-update')) .then(function () { $dropdown.trigger('loaded.gl.dropdown'); $loading.fadeOut(); diff --git a/app/assets/javascripts/subscription.js b/app/assets/javascripts/subscription.js index bfef9532d2b..bfa3663bca3 100644 --- a/app/assets/javascripts/subscription.js +++ b/app/assets/javascripts/subscription.js @@ -5,10 +5,10 @@ function Subscription(container) { this.toggleSubscription = bind(this.toggleSubscription, this); var $container; - $container = $(container); - this.url = $container.attr('data-url'); - this.subscribe_button = $container.find('.js-subscribe-button'); - this.subscription_status = $container.find('.subscription-status'); + this.$container = $(container); + this.url = this.$container.attr('data-url'); + this.subscribe_button = this.$container.find('.js-subscribe-button'); + this.subscription_status = this.$container.find('.subscription-status'); this.subscribe_button.unbind('click').click(this.toggleSubscription); } @@ -18,12 +18,17 @@ action = btn.find('span').text(); current_status = this.subscription_status.attr('data-status'); btn.addClass('disabled'); + + if ($('html').hasClass('issue-boards-page')) { + this.url = this.$container.attr('data-url'); + } + return $.post(this.url, (function(_this) { return function() { var status; btn.removeClass('disabled'); - if ($('body').data('page') === 'projects:boards:show') { + if ($('html').hasClass('issue-boards-page')) { Vue.set(gl.issueBoards.BoardsStore.detail.issue, 'subscribed', !gl.issueBoards.BoardsStore.detail.issue.subscribed); } else { status = current_status === 'subscribed' ? 'unsubscribed' : 'subscribed'; diff --git a/app/assets/javascripts/users_select.js b/app/assets/javascripts/users_select.js index 6626d730e87..da6a59bcf97 100644 --- a/app/assets/javascripts/users_select.js +++ b/app/assets/javascripts/users_select.js @@ -39,7 +39,7 @@ var updateIssueBoardsIssue = function () { $loading.fadeIn(); - gl.issueBoards.BoardsStore.detail.issue.update(issueURL) + gl.issueBoards.BoardsStore.detail.issue.update($dropdown.attr('data-issue-update')) .then(function () { $loading.fadeOut(); }); diff --git a/app/views/projects/boards/components/_sidebar.html.haml b/app/views/projects/boards/components/_sidebar.html.haml index 9616ee5b795..f0c0c6953e0 100644 --- a/app/views/projects/boards/components/_sidebar.html.haml +++ b/app/views/projects/boards/components/_sidebar.html.haml @@ -1,6 +1,6 @@ %board-sidebar{ "inline-template" => true, ":current-user" => "#{current_user.to_json(only: [:username, :id, :name], methods: [:avatar_url]) if current_user}" } - %aside.right-sidebar.right-sidebar-expanded.issue-boards-sidebar{ "v-if" => "showSidebar" } + %aside.right-sidebar.right-sidebar-expanded.issue-boards-sidebar{ "v-show" => "showSidebar" } .issuable-sidebar .block.issuable-sidebar-header %span.issuable-header-text.hide-collapsed.pull-left diff --git a/app/views/projects/boards/components/sidebar/_due_date.html.haml b/app/views/projects/boards/components/sidebar/_due_date.html.haml index 91b4a572ee4..c7da1d0d4ac 100644 --- a/app/views/projects/boards/components/sidebar/_due_date.html.haml +++ b/app/views/projects/boards/components/sidebar/_due_date.html.haml @@ -10,10 +10,11 @@ No due date %span.bold{ "v-if" => "issue.dueDate" } {{ issue.dueDate | due-date }} - %span.no-value.js-remove-due-date-holder{ "v-if" => "issue.dueDate" } - \- - %a.js-remove-due-date{ href: "#", role: "button" } - remove due date + - if can?(current_user, :admin_issue, @project) + %span.no-value.js-remove-due-date-holder{ "v-if" => "issue.dueDate" } + \- + %a.js-remove-due-date{ href: "#", role: "button" } + remove due date - if can?(current_user, :admin_issue, @project) .selectbox %input{ type: "hidden", diff --git a/app/views/projects/boards/components/sidebar/_labels.html.haml b/app/views/projects/boards/components/sidebar/_labels.html.haml index 0e2ea74ec41..a6d2c3c300c 100644 --- a/app/views/projects/boards/components/sidebar/_labels.html.haml +++ b/app/views/projects/boards/components/sidebar/_labels.html.haml @@ -5,7 +5,7 @@ - if can?(current_user, :admin_issue, @project) = link_to "Edit", "#", class: "edit-link pull-right" .value.issuable-show-labels - %span.no-value{ "v-if" => "issue.labels.length === 0" } + %span.no-value{ "v-if" => "issue.labels && issue.labels.length === 0" } None %a{ href: "#", "v-for" => "label in issue.labels" } From 033988cf32da4d7a0ecdffac6e5b8e1b88a264f4 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 20 Oct 2016 08:57:21 +0100 Subject: [PATCH 21/25] Added button types --- .../projects/boards/components/sidebar/_assignee.html.haml | 2 +- .../projects/boards/components/sidebar/_milestone.html.haml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/projects/boards/components/sidebar/_assignee.html.haml b/app/views/projects/boards/components/sidebar/_assignee.html.haml index 4307e8e7626..b90db421af0 100644 --- a/app/views/projects/boards/components/sidebar/_assignee.html.haml +++ b/app/views/projects/boards/components/sidebar/_assignee.html.haml @@ -28,7 +28,7 @@ ":value" => "issue.assignee.id", "v-if" => "issue.assignee" } .dropdown - %button.dropdown-menu-toggle.js-user-search.js-author-search.js-issue-board-sidebar{ data: { toggle: "dropdown", field_name: "issue[assignee_id]", first_user: (current_user.username if current_user), current_user: "true", project_id: @project.id, null_user: "true" }, + %button.dropdown-menu-toggle.js-user-search.js-author-search.js-issue-board-sidebar{ type: "button", data: { toggle: "dropdown", field_name: "issue[assignee_id]", first_user: (current_user.username if current_user), current_user: "true", project_id: @project.id, null_user: "true" }, ":data-issuable-id" => "issue.id", ":data-issue-update" => "'#{namespace_project_issues_path(@project.namespace, @project)}/' + issue.id + '.json'" } Select assignee diff --git a/app/views/projects/boards/components/sidebar/_milestone.html.haml b/app/views/projects/boards/components/sidebar/_milestone.html.haml index ec1354509f2..3cd20d1c0f7 100644 --- a/app/views/projects/boards/components/sidebar/_milestone.html.haml +++ b/app/views/projects/boards/components/sidebar/_milestone.html.haml @@ -16,7 +16,7 @@ name: "issue[milestone_id]", "v-if" => "issue.milestone" } .dropdown - %button.dropdown-menu-toggle.js-milestone-select.js-issue-board-sidebar{ data: { toggle: "dropdown", show_no: "true", field_name: "issue[milestone_id]", project_id: @project.id, milestones: namespace_project_milestones_path(@project.namespace, @project, :json), ability_name: "issue", use_id: "true" }, + %button.dropdown-menu-toggle.js-milestone-select.js-issue-board-sidebar{ type: "button", data: { toggle: "dropdown", show_no: "true", field_name: "issue[milestone_id]", project_id: @project.id, milestones: namespace_project_milestones_path(@project.namespace, @project, :json), ability_name: "issue", use_id: "true" }, ":data-issuable-id" => "issue.id", ":data-issue-update" => "'#{namespace_project_issues_path(@project.namespace, @project)}/' + issue.id + '.json'" } Milestone From 5f276f353c08966c82431cb2a85ce743f779e111 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 20 Oct 2016 14:32:24 +0100 Subject: [PATCH 22/25] Alignment of toggle button --- app/assets/stylesheets/pages/boards.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/stylesheets/pages/boards.scss b/app/assets/stylesheets/pages/boards.scss index 3dda2042cb3..83f899164a9 100644 --- a/app/assets/stylesheets/pages/boards.scss +++ b/app/assets/stylesheets/pages/boards.scss @@ -296,7 +296,7 @@ .gutter-toggle { position: absolute; top: 0; - bottom: 10px; + bottom: 15px; right: 0; width: 22px; color: $gray-darkest; From 079d146278f1f95d748d3533b6eb9a108482bbc8 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 25 Oct 2016 13:16:22 +0100 Subject: [PATCH 23/25] Fixed users profile link in sidebar Fixed new labels not being created Fixed scrolling issues --- app/assets/javascripts/boards/components/board_sidebar.js.es6 | 3 ++- app/assets/javascripts/due_date_select.js.es6 | 2 +- .../projects/boards/components/sidebar/_assignee.html.haml | 2 +- app/views/projects/boards/components/sidebar/_labels.html.haml | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/app/assets/javascripts/boards/components/board_sidebar.js.es6 b/app/assets/javascripts/boards/components/board_sidebar.js.es6 index b20890df622..e83e69247d9 100644 --- a/app/assets/javascripts/boards/components/board_sidebar.js.es6 +++ b/app/assets/javascripts/boards/components/board_sidebar.js.es6 @@ -29,7 +29,8 @@ issue () { if (this.showSidebar) { this.$nextTick(() => { - $(".right-sidebar").getNiceScroll(0).doScrollTop(0, 0); + $('.right-sidebar').getNiceScroll(0).doScrollTop(0, 0); + $('.right-sidebar').getNiceScroll().resize(); }); } } diff --git a/app/assets/javascripts/due_date_select.js.es6 b/app/assets/javascripts/due_date_select.js.es6 index 4f7c1092d05..27a7b95e281 100644 --- a/app/assets/javascripts/due_date_select.js.es6 +++ b/app/assets/javascripts/due_date_select.js.es6 @@ -42,7 +42,7 @@ altField: "input[name='" + this.fieldName + "']", onSelect: () => { if (this.$dropdown.hasClass('js-issue-boards-due-date')) { - gl.issueBoards.BoardsStore.detail.issue.dueDate = $("input[name='" + this.fieldName + "']").val(); + gl.issueBoards.BoardsStore.detail.issue.dueDate = $(`input[name='${this.fieldName}']`).val(); this.updateIssueBoardIssue(); } else { return this.saveDueDate(true); diff --git a/app/views/projects/boards/components/sidebar/_assignee.html.haml b/app/views/projects/boards/components/sidebar/_assignee.html.haml index b90db421af0..604e13858d1 100644 --- a/app/views/projects/boards/components/sidebar/_assignee.html.haml +++ b/app/views/projects/boards/components/sidebar/_assignee.html.haml @@ -11,7 +11,7 @@ \- %a.js-assign-yourself{ href: "#" } assign yourself - %a.author_link.bold{ href: "", + %a.author_link.bold{ ":href" => "'#{root_url}' + issue.assignee.username", "v-if" => "issue.assignee" } %img.avatar.avatar-inline.s32{ ":src" => "issue.assignee.avatar", width: "32" } diff --git a/app/views/projects/boards/components/sidebar/_labels.html.haml b/app/views/projects/boards/components/sidebar/_labels.html.haml index a6d2c3c300c..ce68e5e1998 100644 --- a/app/views/projects/boards/components/sidebar/_labels.html.haml +++ b/app/views/projects/boards/components/sidebar/_labels.html.haml @@ -19,7 +19,7 @@ ":value" => "label.id" } .dropdown %button.dropdown-menu-toggle.js-label-select.js-multiselect.js-issue-board-sidebar{ type: "button", - data: { toggle: "dropdown", field_name: "issue[label_names][]", show_no: "true", show_any: "true", project_id: @project.id, labels: namespace_project_labels_path(@project.namespace, @project, :json) }, + data: { toggle: "dropdown", field_name: "issue[label_names][]", show_no: "true", show_any: "true", project_id: @project.id, labels: namespace_project_labels_path(@project.namespace, @project, :json), namespace_path: @project.try(:namespace).try(:path), project_path: @project.try(:path) }, ":data-issue-update" => "'#{namespace_project_issues_path(@project.namespace, @project)}/' + issue.id + '.json'" } %span.dropdown-toggle-text Label From 19367b77776153696c4eda2b5d546e660357e965 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 25 Oct 2016 14:53:48 +0100 Subject: [PATCH 24/25] Fixed height of sidebar causing scrolling issues --- app/assets/stylesheets/pages/boards.scss | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/app/assets/stylesheets/pages/boards.scss b/app/assets/stylesheets/pages/boards.scss index 83f899164a9..4d358f96c92 100644 --- a/app/assets/stylesheets/pages/boards.scss +++ b/app/assets/stylesheets/pages/boards.scss @@ -284,9 +284,12 @@ .issue-boards-sidebar { &.right-sidebar { - position: absolute; - top: 0; + top: 153px; bottom: 0; + + @media (min-width: $screen-sm-min) { + top: 220px; + } } .issuable-sidebar-header { From 13f170fc5d182da78c3d0a7a0885628f59420ea0 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 25 Oct 2016 17:29:00 +0100 Subject: [PATCH 25/25] Moved avatar infront of labels --- app/assets/stylesheets/pages/boards.scss | 4 ++++ app/views/projects/boards/components/_card.html.haml | 10 +++++----- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/app/assets/stylesheets/pages/boards.scss b/app/assets/stylesheets/pages/boards.scss index 4d358f96c92..ef6833c9845 100644 --- a/app/assets/stylesheets/pages/boards.scss +++ b/app/assets/stylesheets/pages/boards.scss @@ -229,6 +229,10 @@ margin-right: 5px; font-size: (14px / $issue-boards-font-size) * 1em; } + + .avatar { + margin-left: 0; + } } .card-number { diff --git a/app/views/projects/boards/components/_card.html.haml b/app/views/projects/boards/components/_card.html.haml index 5cfd42485c5..22c1dfa84fc 100644 --- a/app/views/projects/boards/components/_card.html.haml +++ b/app/views/projects/boards/components/_card.html.haml @@ -21,6 +21,11 @@ %span.card-number{ "v-if" => "issue.id" } = precede '#' do {{ issue.id }} + %a.has-tooltip{ ":href" => "'/' + issue.assignee.username", + ":title" => "'Assigned to ' + issue.assignee.name", + "v-if" => "issue.assignee", + data: { container: 'body' } } + %img.avatar.avatar-inline.s20{ ":src" => "issue.assignee.avatar", width: 20, height: 20 } %button.label.color-label.has-tooltip{ "v-for" => "label in issue.labels", type: "button", "v-if" => "(!list.label || label.id !== list.label.id)", @@ -29,8 +34,3 @@ ":title" => "label.description", data: { container: 'body' } } {{ label.title }} - %a.has-tooltip{ ":href" => "'/' + issue.assignee.username", - ":title" => "'Assigned to ' + issue.assignee.name", - "v-if" => "issue.assignee", - data: { container: 'body' } } - %img.avatar.avatar-inline.s20{ ":src" => "issue.assignee.avatar", width: 20, height: 20 }