diff options
-rw-r--r-- | app/assets/javascripts/right_sidebar.js | 47 | ||||
-rw-r--r-- | app/assets/stylesheets/framework/buttons.scss | 10 | ||||
-rw-r--r-- | app/assets/stylesheets/pages/issuable.scss | 31 | ||||
-rw-r--r-- | app/assets/stylesheets/pages/todos.scss | 10 | ||||
-rw-r--r-- | app/helpers/issuables_helper.rb | 15 | ||||
-rw-r--r-- | app/views/layouts/header/_default.html.haml | 2 | ||||
-rw-r--r-- | app/views/shared/issuable/_sidebar.html.haml | 11 | ||||
-rw-r--r-- | app/views/shared/issuable/_sidebar_todo.html.haml | 15 | ||||
-rw-r--r-- | changelogs/unreleased/create-collapsed-todo-button.yml | 5 | ||||
-rw-r--r-- | spec/javascripts/collapsed_sidebar_todo_spec.js | 123 | ||||
-rw-r--r-- | spec/javascripts/right_sidebar_spec.js | 2 |
11 files changed, 237 insertions, 34 deletions
diff --git a/app/assets/javascripts/right_sidebar.js b/app/assets/javascripts/right_sidebar.js index 64a68d56962..f3cd41fb4db 100644 --- a/app/assets/javascripts/right_sidebar.js +++ b/app/assets/javascripts/right_sidebar.js @@ -56,14 +56,15 @@ import Cookies from 'js-cookie'; Sidebar.prototype.toggleTodo = function(e) { var $btnText, $this, $todoLoading, ajaxType, url; $this = $(e.currentTarget); - $todoLoading = $('.js-issuable-todo-loading'); - $btnText = $('.js-issuable-todo-text', $this); ajaxType = $this.attr('data-delete-path') ? 'DELETE' : 'POST'; if ($this.attr('data-delete-path')) { url = "" + ($this.attr('data-delete-path')); } else { url = "" + ($this.data('url')); } + + $this.tooltip('hide'); + return $.ajax({ url: url, type: ajaxType, @@ -74,34 +75,44 @@ import Cookies from 'js-cookie'; }, beforeSend: (function(_this) { return function() { - return _this.beforeTodoSend($this, $todoLoading); + $('.js-issuable-todo').disable() + .addClass('is-loading'); }; })(this) }).done((function(_this) { return function(data) { - return _this.todoUpdateDone(data, $this, $btnText, $todoLoading); + return _this.todoUpdateDone(data); }; })(this)); }; - Sidebar.prototype.beforeTodoSend = function($btn, $todoLoading) { - $btn.disable(); - return $todoLoading.removeClass('hidden'); - }; + Sidebar.prototype.todoUpdateDone = function(data) { + const deletePath = data.delete_path ? data.delete_path : null; + const attrPrefix = deletePath ? 'mark' : 'todo'; + const $todoBtns = $('.js-issuable-todo'); - Sidebar.prototype.todoUpdateDone = function(data, $btn, $btnText, $todoLoading) { $(document).trigger('todo:toggle', data.count); - $btn.enable(); - $todoLoading.addClass('hidden'); + $todoBtns.each((i, el) => { + const $el = $(el); + const $elText = $el.find('.js-issuable-todo-inner'); - if (data.delete_path != null) { - $btn.attr('aria-label', $btn.data('mark-text')).attr('data-delete-path', data.delete_path); - return $btnText.text($btn.data('mark-text')); - } else { - $btn.attr('aria-label', $btn.data('todo-text')).removeAttr('data-delete-path'); - return $btnText.text($btn.data('todo-text')); - } + $el.removeClass('is-loading') + .enable() + .attr('aria-label', $el.data(`${attrPrefix}-text`)) + .attr('data-delete-path', deletePath) + .attr('title', $el.data(`${attrPrefix}-text`)); + + if ($el.hasClass('has-tooltip')) { + $el.tooltip('fixTitle'); + } + + if ($el.data(`${attrPrefix}-icon`)) { + $elText.html($el.data(`${attrPrefix}-icon`)); + } else { + $elText.text($el.data(`${attrPrefix}-text`)); + } + }); }; Sidebar.prototype.sidebarDropdownLoading = function(e) { diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss index 50849e95541..4369ae78bde 100644 --- a/app/assets/stylesheets/framework/buttons.scss +++ b/app/assets/stylesheets/framework/buttons.scss @@ -362,3 +362,13 @@ width: 100%; } } + +.btn-blank { + padding: 0; + background: transparent; + border: 0; + + &:focus { + outline: 0; + } +} diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index c1a9bc4be28..e84a05e3e9e 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -243,6 +243,10 @@ font-size: 13px; font-weight: normal; } + + .hide-expanded { + display: none; + } } &.right-sidebar-collapsed { @@ -282,10 +286,11 @@ display: block; width: 100%; text-align: center; - padding-bottom: 10px; + margin-bottom: 10px; color: $issuable-sidebar-color; - &:hover { + &:hover, + &:hover .todo-undone { color: $gl-text-color; } @@ -294,6 +299,10 @@ margin-top: 0; } + .todo-undone { + color: $gl-link-color; + } + .author { display: none; } @@ -582,3 +591,21 @@ opacity: 0; } } + +.issuable-todo-btn { + .fa-spinner { + display: none; + } + + &.is-loading { + .fa-spinner { + display: inline-block; + } + + &.sidebar-collapsed-icon { + .issuable-todo-inner { + display: none; + } + } + } +} diff --git a/app/assets/stylesheets/pages/todos.scss b/app/assets/stylesheets/pages/todos.scss index b071d7f18cd..9c325227fa1 100644 --- a/app/assets/stylesheets/pages/todos.scss +++ b/app/assets/stylesheets/pages/todos.scss @@ -7,17 +7,17 @@ li { .badge.todos-pending-count { position: inherit; - top: -6px; + top: -10px; margin-top: -5px; font-weight: normal; background: $todo-alert-blue; - margin-left: -17px; + margin-left: -13px; font-size: 11px; color: $white-light; - padding: 3px; + padding: 4px; padding-top: 1px; - padding-bottom: 1px; - border-radius: 3px; + padding-bottom: 2px; + border-radius: 7px; } } } diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb index a777db2826b..749b6245edc 100644 --- a/app/helpers/issuables_helper.rb +++ b/app/helpers/issuables_helper.rb @@ -253,4 +253,19 @@ module IssuablesHelper def selected_template(issuable) params[:issuable_template] if issuable_templates(issuable).include?(params[:issuable_template]) end + + def issuable_todo_button_data(issuable, todo, is_collapsed) + { + todo_text: "Add todo", + mark_text: "Mark done", + todo_icon: (is_collapsed ? icon('plus-square') : nil), + mark_icon: (is_collapsed ? icon('check-square', class: 'todo-undone') : nil), + issuable_id: issuable.id, + issuable_type: issuable.class.name.underscore, + url: namespace_project_todos_path(@project.namespace, @project), + delete_path: (dashboard_todo_path(todo) if todo), + placement: (is_collapsed ? 'left' : nil), + container: (is_collapsed ? 'body' : nil) + } + end end diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml index 7bf4bc70f7c..afa6eec2fc7 100644 --- a/app/views/layouts/header/_default.html.haml +++ b/app/views/layouts/header/_default.html.haml @@ -40,7 +40,7 @@ = icon('wrench fw') %li = link_to dashboard_todos_path, title: 'Todos', aria: { label: "Todos" }, class: 'shortcuts-todos', data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do - = icon('bell fw') + = icon('check-square fw') %span.badge.todos-pending-count{ class: ("hidden" if todos_pending_count == 0) } = todos_count_format(todos_pending_count) - if current_user.can_create_project? diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml index f19f362f514..92d2d93a732 100644 --- a/app/views/shared/issuable/_sidebar.html.haml +++ b/app/views/shared/issuable/_sidebar.html.haml @@ -13,15 +13,12 @@ %a.gutter-toggle.pull-right.js-sidebar-toggle{ role: "button", href: "#", "aria-label" => "Toggle sidebar" } = sidebar_gutter_toggle_icon - if current_user - %button.btn.btn-default.issuable-header-btn.pull-right.js-issuable-todo{ type: "button", "aria-label" => (todo.nil? ? "Add todo" : "Mark done"), data: { todo_text: "Add todo", mark_text: "Mark done", issuable_id: issuable.id, issuable_type: issuable.class.name.underscore, url: namespace_project_todos_path(@project.namespace, @project), delete_path: (dashboard_todo_path(todo) if todo) } } - %span.js-issuable-todo-text - - if todo - Mark done - - else - Add todo - = icon('spin spinner', class: 'hidden js-issuable-todo-loading', 'aria-hidden': 'true') + = render "shared/issuable/sidebar_todo", todo: todo, issuable: issuable = form_for [@project.namespace.becomes(Namespace), @project, issuable], remote: true, format: :json, html: { class: 'issuable-context-form inline-update js-issuable-update' } do |f| + - if current_user + .block.todo.hide-expanded + = render "shared/issuable/sidebar_todo", todo: todo, issuable: issuable, is_collapsed: true .block.assignee .sidebar-collapsed-icon.sidebar-collapsed-user{ data: { toggle: "tooltip", placement: "left", container: "body" }, title: (issuable.assignee.name if issuable.assignee) } - if issuable.assignee diff --git a/app/views/shared/issuable/_sidebar_todo.html.haml b/app/views/shared/issuable/_sidebar_todo.html.haml new file mode 100644 index 00000000000..574e2958ae8 --- /dev/null +++ b/app/views/shared/issuable/_sidebar_todo.html.haml @@ -0,0 +1,15 @@ +- is_collapsed = local_assigns.fetch(:is_collapsed, false) +- mark_content = is_collapsed ? icon('check-square', class: 'todo-undone') : 'Mark done' +- todo_content = is_collapsed ? icon('plus-square') : 'Add todo' + +%button.issuable-todo-btn.js-issuable-todo{ type: 'button', + class: (is_collapsed ? 'btn-blank sidebar-collapsed-icon dont-change-state has-tooltip' : 'btn btn-default issuable-header-btn pull-right'), + title: (todo.nil? ? 'Add todo' : 'Mark done'), + 'aria-label' => (todo.nil? ? 'Add todo' : 'Mark done'), + data: issuable_todo_button_data(issuable, todo, is_collapsed) } + %span.issuable-todo-inner.js-issuable-todo-inner< + - if todo + = mark_content + - else + = todo_content + = icon('spin spinner', 'aria-hidden': 'true') diff --git a/changelogs/unreleased/create-collapsed-todo-button.yml b/changelogs/unreleased/create-collapsed-todo-button.yml new file mode 100644 index 00000000000..6da6c070bf7 --- /dev/null +++ b/changelogs/unreleased/create-collapsed-todo-button.yml @@ -0,0 +1,5 @@ +--- +title: adds todo functionality to closed issuable sidebar and changes todo bell icon + to check-square +merge_request: +author: diff --git a/spec/javascripts/collapsed_sidebar_todo_spec.js b/spec/javascripts/collapsed_sidebar_todo_spec.js new file mode 100644 index 00000000000..974815fe939 --- /dev/null +++ b/spec/javascripts/collapsed_sidebar_todo_spec.js @@ -0,0 +1,123 @@ +/* global Sidebar */ +/* eslint-disable no-new */ +import _ from 'underscore'; +import '~/right_sidebar'; + +describe('Issuable right sidebar collapsed todo toggle', () => { + const fixtureName = 'issues/open-issue.html.raw'; + const jsonFixtureName = 'todos/todos.json'; + + preloadFixtures(fixtureName); + preloadFixtures(jsonFixtureName); + + beforeEach(() => { + const todoData = getJSONFixture(jsonFixtureName); + new Sidebar(); + loadFixtures(fixtureName); + + document.querySelector('.js-right-sidebar') + .classList.toggle('right-sidebar-expanded'); + document.querySelector('.js-right-sidebar') + .classList.toggle('right-sidebar-collapsed'); + + spyOn(jQuery, 'ajax').and.callFake((res) => { + const d = $.Deferred(); + const response = _.clone(todoData); + + if (res.type === 'DELETE') { + delete response.delete_path; + } + + d.resolve(response); + return d.promise(); + }); + }); + + it('shows add todo button', () => { + expect( + document.querySelector('.js-issuable-todo.sidebar-collapsed-icon'), + ).not.toBeNull(); + + expect( + document.querySelector('.js-issuable-todo.sidebar-collapsed-icon .fa-plus-square'), + ).not.toBeNull(); + + expect( + document.querySelector('.js-issuable-todo.sidebar-collapsed-icon .todo-undone'), + ).toBeNull(); + }); + + it('sets default tooltip title', () => { + expect( + document.querySelector('.js-issuable-todo.sidebar-collapsed-icon').getAttribute('title'), + ).toBe('Add todo'); + }); + + it('toggle todo state', () => { + document.querySelector('.js-issuable-todo.sidebar-collapsed-icon').click(); + + expect( + document.querySelector('.js-issuable-todo.sidebar-collapsed-icon .todo-undone'), + ).not.toBeNull(); + + expect( + document.querySelector('.js-issuable-todo.sidebar-collapsed-icon .fa-check-square'), + ).not.toBeNull(); + }); + + it('toggle todo state of expanded todo toggle', () => { + document.querySelector('.js-issuable-todo.sidebar-collapsed-icon').click(); + + expect( + document.querySelector('.issuable-sidebar-header .js-issuable-todo').textContent.trim(), + ).toBe('Mark done'); + }); + + it('toggles todo button tooltip', () => { + document.querySelector('.js-issuable-todo.sidebar-collapsed-icon').click(); + + expect( + document.querySelector('.js-issuable-todo.sidebar-collapsed-icon').getAttribute('data-original-title'), + ).toBe('Mark done'); + }); + + it('marks todo as done', () => { + document.querySelector('.js-issuable-todo.sidebar-collapsed-icon').click(); + + expect( + document.querySelector('.js-issuable-todo.sidebar-collapsed-icon .todo-undone'), + ).not.toBeNull(); + + document.querySelector('.js-issuable-todo.sidebar-collapsed-icon').click(); + + expect( + document.querySelector('.js-issuable-todo.sidebar-collapsed-icon .todo-undone'), + ).toBeNull(); + + expect( + document.querySelector('.issuable-sidebar-header .js-issuable-todo').textContent.trim(), + ).toBe('Add todo'); + }); + + it('updates aria-label to mark done', () => { + document.querySelector('.js-issuable-todo.sidebar-collapsed-icon').click(); + + expect( + document.querySelector('.js-issuable-todo.sidebar-collapsed-icon').getAttribute('aria-label'), + ).toBe('Mark done'); + }); + + it('updates aria-label to add todo', () => { + document.querySelector('.js-issuable-todo.sidebar-collapsed-icon').click(); + + expect( + document.querySelector('.js-issuable-todo.sidebar-collapsed-icon').getAttribute('aria-label'), + ).toBe('Mark done'); + + document.querySelector('.js-issuable-todo.sidebar-collapsed-icon').click(); + + expect( + document.querySelector('.js-issuable-todo.sidebar-collapsed-icon').getAttribute('aria-label'), + ).toBe('Add todo'); + }); +}); diff --git a/spec/javascripts/right_sidebar_spec.js b/spec/javascripts/right_sidebar_spec.js index 464b54c62de..f2072a6f350 100644 --- a/spec/javascripts/right_sidebar_spec.js +++ b/spec/javascripts/right_sidebar_spec.js @@ -74,7 +74,7 @@ import '~/right_sidebar'; var todoToggleSpy = spyOnEvent(document, 'todo:toggle'); - $('.js-issuable-todo').click(); + $('.issuable-sidebar-header .js-issuable-todo').click(); expect(todoToggleSpy.calls.count()).toEqual(1); }); |