Sidebar details update when changing

Need to get working the subscription
Styling updates
This commit is contained in:
Phil Hughes 2016-10-05 12:20:59 +01:00
parent 6cece3f49e
commit 6b3e3aeb9e
18 changed files with 254 additions and 49 deletions

View file

@ -5,6 +5,7 @@
//= require_tree ./stores //= require_tree ./stores
//= require_tree ./services //= require_tree ./services
//= require_tree ./mixins //= require_tree ./mixins
//= require_tree ./filters
//= require ./components/board //= require ./components/board
//= require ./components/board_sidebar //= require ./components/board_sidebar
//= require ./components/new_list_dropdown //= require ./components/new_list_dropdown

View file

@ -5,6 +5,9 @@
window.gl.issueBoards = window.gl.issueBoards || {}; window.gl.issueBoards = window.gl.issueBoards || {};
gl.issueBoards.BoardSidebar = Vue.extend({ gl.issueBoards.BoardSidebar = Vue.extend({
props: {
currentUser: Object
},
data() { data() {
return { return {
detail: Store.detail, detail: Store.detail,
@ -26,9 +29,12 @@
issue () { issue () {
if (this.showSidebar) { if (this.showSidebar) {
this.$nextTick(() => { this.$nextTick(() => {
new IssuableContext(); new IssuableContext(this.currentUser);
new MilestoneSelect(); new MilestoneSelect();
new DueDateSelect();
new LabelsSelect();
new Sidebar(); new Sidebar();
new Subscription('.subscription')
}); });
} else { } else {
$('.right-sidebar').getNiceScroll().remove(); $('.right-sidebar').getNiceScroll().remove();

View file

@ -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);
});

View file

@ -4,6 +4,7 @@ class ListIssue {
this.title = obj.title; this.title = obj.title;
this.confidential = obj.confidential; this.confidential = obj.confidential;
this.dueDate = obj.due_date; this.dueDate = obj.due_date;
this.subscribed = true;
this.labels = []; this.labels = [];
if (obj.assignee) { if (obj.assignee) {
@ -46,4 +47,17 @@ class ListIssue {
getLists () { getLists () {
return gl.issueBoards.BoardsStore.state.lists.filter( list => list.findIssue(this.id) ); return gl.issueBoards.BoardsStore.state.lists.filter( list => list.findIssue(this.id) );
} }
update (url) {
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);
}
} }

View file

@ -1,7 +1,5 @@
class BoardService { class BoardService {
constructor (root) { constructor (root) {
Vue.http.options.root = root;
this.lists = Vue.resource(`${root}/lists{/id}`, {}, { this.lists = Vue.resource(`${root}/lists{/id}`, {}, {
generate: { generate: {
method: 'POST', method: 'POST',

View file

@ -38,6 +38,19 @@
return $value.css('display', ''); 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) { addDueDate = function(isDropdown) {
var data, date, mediumDate, value; var data, date, mediumDate, value;
// Create the post date // Create the post date
@ -83,16 +96,26 @@
}; };
$block.on('click', '.js-remove-due-date', function(e) { $block.on('click', '.js-remove-due-date', function(e) {
e.preventDefault(); e.preventDefault();
if ($dropdown.hasClass('js-issue-boards-due-date')) {
gl.issueBoards.BoardsStore.detail.issue.dueDate = '';
updateIssueBoardIssue();
} else {
$("input[name='" + fieldName + "']").val(''); $("input[name='" + fieldName + "']").val('');
return addDueDate(false); return addDueDate(false);
}
}); });
return $datePicker.datepicker({ return $datePicker.datepicker({
dateFormat: 'yy-mm-dd', dateFormat: 'yy-mm-dd',
defaultDate: $("input[name='" + fieldName + "']").val(), defaultDate: $("input[name='" + fieldName + "']").val(),
altField: "input[name='" + fieldName + "']", altField: "input[name='" + fieldName + "']",
onSelect: function() { onSelect: function() {
if ($dropdown.hasClass('js-issue-boards-due-date')) {
gl.issueBoards.BoardsStore.detail.issue.dueDate = $("input[name='" + fieldName + "']").val();
updateIssueBoardIssue();
} else {
return addDueDate(true); return addDueDate(true);
} }
}
}); });
}); });
$(document).off('click', '.ui-datepicker-header a').on('click', '.ui-datepicker-header a', function(e) { $(document).off('click', '.ui-datepicker-header a').on('click', '.ui-datepicker-header a', function(e) {

View file

@ -22,7 +22,7 @@
abilityName = $dropdown.data('ability-name'); abilityName = $dropdown.data('ability-name');
$selectbox = $dropdown.closest('.selectbox'); $selectbox = $dropdown.closest('.selectbox');
$block = $selectbox.closest('.block'); $block = $selectbox.closest('.block');
$form = $dropdown.closest('form'); $form = $dropdown.closest('.js-issuable-update');
$sidebarCollapsedValue = $block.find('.sidebar-collapsed-icon span'); $sidebarCollapsedValue = $block.find('.sidebar-collapsed-icon span');
$sidebarLabelTooltip = $block.find('.js-sidebar-labels-tooltip'); $sidebarLabelTooltip = $block.find('.js-sidebar-labels-tooltip');
$value = $block.find('.value'); $value = $block.find('.value');
@ -334,7 +334,7 @@
page = $('body').data('page'); page = $('body').data('page');
isIssueIndex = page === 'projects:issues:index'; isIssueIndex = page === 'projects:issues:index';
isMRIndex = page === 'projects:merge_requests: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) { if (label.isAny) {
gl.issueBoards.BoardsStore.state.filters['label_name'] = []; gl.issueBoards.BoardsStore.state.filters['label_name'] = [];
} }
@ -362,6 +362,30 @@
else if ($dropdown.hasClass('js-filter-submit')) { else if ($dropdown.hasClass('js-filter-submit')) {
return $dropdown.closest('form').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 { else {
if ($dropdown.hasClass('js-multiselect')) { if ($dropdown.hasClass('js-multiselect')) {

View file

@ -110,7 +110,7 @@
e.preventDefault(); e.preventDefault();
return; 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.state.filters[$dropdown.data('field-name')] = selected.name;
gl.issueBoards.BoardsStore.updateFiltersUrl(); gl.issueBoards.BoardsStore.updateFiltersUrl();
e.preventDefault(); e.preventDefault();
@ -123,6 +123,24 @@
return Issuable.filterResults($dropdown.closest('form')); return Issuable.filterResults($dropdown.closest('form'));
} else if ($dropdown.hasClass('js-filter-submit')) { } else if ($dropdown.hasClass('js-filter-submit')) {
return $dropdown.closest('form').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 { } else {
selected = $selectbox.find('input[type="hidden"]').val(); selected = $selectbox.find('input[type="hidden"]').val();
data = {}; data = {};

View file

@ -22,6 +22,10 @@
return function() { return function() {
var status; var status;
btn.removeClass('disabled'); btn.removeClass('disabled');
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'; status = current_status === 'subscribed' ? 'unsubscribed' : 'subscribed';
_this.subscription_status.attr('data-status', status); _this.subscription_status.attr('data-status', status);
action = status === 'subscribed' ? 'Unsubscribe' : 'Subscribe'; action = status === 'subscribed' ? 'Unsubscribe' : 'Subscribe';
@ -30,6 +34,7 @@
if (btn.attr('data-original-title')) { if (btn.attr('data-original-title')) {
return btn.tooltip('hide').attr('data-original-title', action).tooltip('fixTitle'); return btn.tooltip('hide').attr('data-original-title', action).tooltip('fixTitle');
} }
}
}; };
})(this)); })(this));
}; };

View file

@ -9,8 +9,12 @@
this.usersPath = "/autocomplete/users.json"; this.usersPath = "/autocomplete/users.json";
this.userPath = "/autocomplete/users/:id.json"; this.userPath = "/autocomplete/users/:id.json";
if (currentUser != null) { if (currentUser != null) {
if (typeof currentUser === 'object') {
this.currentUser = currentUser;
} else {
this.currentUser = JSON.parse(currentUser); this.currentUser = JSON.parse(currentUser);
} }
}
$('.js-user-search').each((function(_this) { $('.js-user-search').each((function(_this) {
return function(i, dropdown) { return function(i, dropdown) {
var options = {}; var options = {};
@ -32,9 +36,30 @@
$value = $block.find('.value'); $value = $block.find('.value');
$collapsedSidebar = $block.find('.sidebar-collapsed-user'); $collapsedSidebar = $block.find('.sidebar-collapsed-user');
$loading = $block.find('.block-loading').fadeOut(); $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) { $block.on('click', '.js-assign-yourself', function(e) {
e.preventDefault(); e.preventDefault();
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); return assignTo(_this.currentUser.id);
}
}); });
assignTo = function(selected) { assignTo = function(selected) {
var data; var data;
@ -160,7 +185,7 @@
selectedId = user.id; selectedId = user.id;
return; return;
} }
if (page === 'projects:boards:show') { if (page === 'projects:boards:show' && !$dropdown.hasClass('js-issue-board-assignee')) {
selectedId = user.id; selectedId = user.id;
gl.issueBoards.BoardsStore.state.filters[$dropdown.data('field-name')] = user.id; gl.issueBoards.BoardsStore.state.filters[$dropdown.data('field-name')] = user.id;
gl.issueBoards.BoardsStore.updateFiltersUrl(); gl.issueBoards.BoardsStore.updateFiltersUrl();
@ -170,6 +195,19 @@
return Issuable.filterResults($dropdown.closest('form')); return Issuable.filterResults($dropdown.closest('form'));
} else if ($dropdown.hasClass('js-filter-submit')) { } else if ($dropdown.hasClass('js-filter-submit')) {
return $dropdown.closest('form').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 { } else {
selected = $dropdown.closest('.selectbox').find("input[name='" + ($dropdown.data('field-name')) + "']").val(); selected = $dropdown.closest('.selectbox').find("input[name='" + ($dropdown.data('field-name')) + "']").val();
return assignTo(selected); return assignTo(selected);

View file

@ -46,6 +46,15 @@ lex
.page-with-sidebar { .page-with-sidebar {
padding-bottom: 0; padding-bottom: 0;
} }
.issues-filters {
position: relative;
z-index: 999999;
}
}
.boards-app {
position: relative;
} }
.boards-app-loading { .boards-app-loading {
@ -265,3 +274,9 @@ lex
border-width: 1px 0 1px 1px; border-width: 1px 0 1px 1px;
} }
} }
.right-sidebar.issue-boards-sidebar {
position: absolute;
top: 0;
bottom: 0;
}

View file

@ -1,5 +1,6 @@
%board-sidebar{ "inline-template" => true } %board-sidebar{ "inline-template" => true,
%aside.right-sidebar.right-sidebar-expanded{ "v-if" => "showSidebar" } ":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 .issuable-sidebar
.block.issuable-sidebar-header .block.issuable-sidebar-header
%span.issuable-header-text.hide-collapsed.pull-left %span.issuable-header-text.hide-collapsed.pull-left
@ -14,6 +15,7 @@
"@click" => "closeSidebar", "@click" => "closeSidebar",
aria: { label: "Toggle sidebar" } } aria: { label: "Toggle sidebar" } }
= icon("times") = icon("times")
.js-issuable-update
= render "projects/boards/components/sidebar/assignee" = render "projects/boards/components/sidebar/assignee"
= render "projects/boards/components/sidebar/milestone" = render "projects/boards/components/sidebar/milestone"
= render "projects/boards/components/sidebar/due_date" = render "projects/boards/components/sidebar/due_date"

View file

@ -20,3 +20,21 @@
%span.username %span.username
= precede "@" do = precede "@" do
{{ issue.assignee.username }} {{ 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

View file

@ -1,16 +1,31 @@
.block.due_date .block.due_date
.title.hide-collapsed .title
Due date Due date
= icon("spinner spin", class: "block-loading") = icon("spinner spin", class: "block-loading")
- if can?(current_user, :admin_issue, @project) - if can?(current_user, :admin_issue, @project)
= link_to "Edit", "#", class: "edit-link pull-right" = link_to "Edit", "#", class: "edit-link pull-right"
.value.hide-collapsed .value
.value-content .value-content
%span.no-value{ "v-if" => "!issue.dueDate" } %span.no-value{ "v-if" => "!issue.dueDate" }
No due date No due date
%span.bold{ "v-if" => "issue.dueDate" } %span.bold{ "v-if" => "issue.dueDate" }
{{ issue.dueDate }} {{ issue.dueDate | due-date }}
%span.no-value.js-remove-due-date-holder{ "v-if" => "issue.dueDate" } %span.no-value.js-remove-due-date-holder{ "v-if" => "issue.dueDate" }
\- \-
%a.js-remove-due-date{ href: "#", role: "button" } %a.js-remove-due-date{ href: "#", role: "button" }
remove due date 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

View file

@ -1,18 +1,30 @@
.block.labels .block.labels
.title.hide-collapsed .title
Labels Labels
= icon("spinner spin", class: "block-loading") = icon("spinner spin", class: "block-loading")
- if can?(current_user, :admin_issue, @project) - if can?(current_user, :admin_issue, @project)
= link_to "Edit", "#", class: "edit-link pull-right" = 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" } %span.no-value{ "v-if" => "issue.labels.length === 0" }
None None
%a{ href: "#", %a{ href: "#",
"v-for" => "label in issue.labels" } "v-for" => "label in issue.labels" }
%span.label.color-label.has-tooltip{ ":style" => "{ backgroundColor: label.color, color: label.textColor }" } %span.label.color-label.has-tooltip{ ":style" => "{ backgroundColor: label.color, color: label.textColor }" }
{{ label.title }} {{ label.title }}
.selectbox.hide-collapsed - if can?(current_user, :admin_issue, @project)
.selectbox
%input{ type: "hidden", %input{ type: "hidden",
name: "issue[label_names][]", name: "issue[label_names][]",
"v-for" => "label in issue.labels", "v-for" => "label in issue.labels",
":value" => "label.id" } ":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"

View file

@ -1,5 +1,5 @@
.block.milestone .block.milestone
.title.hide-collapsed .title
Milestone Milestone
= icon("spinner spin", class: "block-loading") = icon("spinner spin", class: "block-loading")
- if can?(current_user, :admin_issue, @project) - if can?(current_user, :admin_issue, @project)
@ -9,8 +9,20 @@
None None
%span.bold.has-tooltip{ "v-if" => "issue.milestone" } %span.bold.has-tooltip{ "v-if" => "issue.milestone" }
{{ issue.milestone.title }} {{ issue.milestone.title }}
- if can?(current_user, :admin_issue, @project)
.selectbox .selectbox
%input{ type: "hidden",
":value" => "issue.milestone.id",
name: "issue[milestone_id]",
"v-if" => "issue.milestone" }
.dropdown .dropdown
%button.dropdown-menu-toggle %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 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 }}) = icon("chevron-down")
.dropdown-menu.dropdown-select.dropdown-menu-selectable
= dropdown_title("Assignee milestone")
= dropdown_filter("Search milestones")
= dropdown_content
= dropdown_loading

View file

@ -1,11 +1,11 @@
- if current_user - if current_user
.block.light.subscription{ data: { url: '' } } .block.light.subscription{ ":data-url" => "'#{namespace_project_issues_path(@project.namespace, @project)}/' + issue.id + '/toggle_subscription'" }
.title.hide-collapsed .title
Notifications Notifications
%button.btn.btn-block.btn-default.js-subscribe-button.issuable-subscribe-button.hide-collapsed{ type: "button" } %button.btn.btn-block.btn-default.js-subscribe-button.issuable-subscribe-button.hide-collapsed{ type: "button" }
Unsubscribe {{ issue.subscribed ? 'Unsubscribe' : 'Subscribe' }}
.subscription-status.hide-collapsed{ data: { status: '' } } .subscription-status{ ":data-status" => "issue.subscribed ? 'subscribed' : 'unsubscribed'" }
.unsubscribed{class: ( 'hidden' if true )} .unsubscribed{ "v-show" => "!issue.subscribed" }
You're not receiving notifications from this thread. 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. You're receiving notifications because you're subscribed to this thread.

View file

@ -10,7 +10,7 @@
= render 'shared/issuable/filter', type: :boards = 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-endpoint" => "#{namespace_project_board_path(@project.namespace, @project)}",
"data-disabled" => "#{!can?(current_user, :admin_list, @project)}", "data-disabled" => "#{!can?(current_user, :admin_list, @project)}",
"data-issue-link-base" => "#{namespace_project_issues_path(@project.namespace, @project)}" } "data-issue-link-base" => "#{namespace_project_issues_path(@project.namespace, @project)}" }