From 52d2fd49b5d913d40259161e44579b715b4a383c Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 31 Aug 2016 15:28:58 +0100 Subject: Updated members UI --- app/assets/javascripts/dispatcher.js | 2 +- app/assets/javascripts/project_members.js | 10 ---- app/assets/javascripts/project_members.js.es6 | 36 +++++++++++++++ app/assets/stylesheets/framework/selects.scss | 6 ++- app/assets/stylesheets/pages/members.scss | 22 +++++++++ .../project_members/_new_project_member.html.haml | 35 ++++++-------- app/views/projects/project_members/_team.html.haml | 2 +- app/views/projects/project_members/index.html.haml | 23 ++++----- app/views/shared/members/_member.html.haml | 54 ++++++++-------------- 9 files changed, 111 insertions(+), 79 deletions(-) delete mode 100644 app/assets/javascripts/project_members.js create mode 100644 app/assets/javascripts/project_members.js.es6 create mode 100644 app/assets/stylesheets/pages/members.scss (limited to 'app') diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index 99b16f7d59b..c95aaf61443 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -132,7 +132,7 @@ break; case 'projects:project_members:index': new gl.MemberExpirationDate(); - new ProjectMembers(); + new gl.ProjectMembers(); new UsersSelect(); break; case 'groups:new': diff --git a/app/assets/javascripts/project_members.js b/app/assets/javascripts/project_members.js deleted file mode 100644 index 78f7b48bc7d..00000000000 --- a/app/assets/javascripts/project_members.js +++ /dev/null @@ -1,10 +0,0 @@ -(function() { - this.ProjectMembers = (function() { - function ProjectMembers() { - $('li.project_member').bind('ajax:success', function() { - return $(this).fadeOut(); - }); - } - return ProjectMembers; - })(); -}).call(this); diff --git a/app/assets/javascripts/project_members.js.es6 b/app/assets/javascripts/project_members.js.es6 new file mode 100644 index 00000000000..74cedfd5006 --- /dev/null +++ b/app/assets/javascripts/project_members.js.es6 @@ -0,0 +1,36 @@ +((w) => { + window.gl = window.gl || {}; + + class ProjectMembers { + constructor() { + this.removeListeners(); + this.addListeners(); + } + + removeListeners() { + $('.project_member').off('ajax:success'); + $('.js-member-update-control').off('change'); + } + + addListeners() { + $('.project_member').on('ajax:success', this.removeRow); + $('.js-member-update-control').on('change', function () { + console.log($(this).val()); + }); + } + + removeRow(e) { + const $target = $(e.target); + + if ($target.hasClass('btn-remove')) { + $target.fadeOut(); + } + } + + submitForm() { + + } + } + + gl.ProjectMembers = ProjectMembers; +})(window); diff --git a/app/assets/stylesheets/framework/selects.scss b/app/assets/stylesheets/framework/selects.scss index c75dacf95d9..746ab89abd2 100644 --- a/app/assets/stylesheets/framework/selects.scss +++ b/app/assets/stylesheets/framework/selects.scss @@ -86,7 +86,7 @@ background: none; .select2-search-field input { - padding: $gl-padding / 2; + padding: 5px $gl-padding / 2; font-size: 13px; height: auto; font-family: inherit; @@ -191,6 +191,10 @@ &.input-clamp { max-width: 100%; } + + &.input-full { + width: 100%; + } } .select2-highlighted { diff --git a/app/assets/stylesheets/pages/members.scss b/app/assets/stylesheets/pages/members.scss new file mode 100644 index 00000000000..9583d7c6161 --- /dev/null +++ b/app/assets/stylesheets/pages/members.scss @@ -0,0 +1,22 @@ +.project-members-new { + > h5 { + font-weight: normal; + } +} + +.member { + .controls { + display: flex; + width: 400px; + } + + .form-horizontal { + display: flex; + flex: 1; + margin-top: 3px; + } + + .member-form-control { + width: 50%; + } +} diff --git a/app/views/projects/project_members/_new_project_member.html.haml b/app/views/projects/project_members/_new_project_member.html.haml index fa8cbf71733..c0b187fb460 100644 --- a/app/views/projects/project_members/_new_project_member.html.haml +++ b/app/views/projects/project_members/_new_project_member.html.haml @@ -1,27 +1,22 @@ -= form_for @project_member, as: :project_member, url: namespace_project_project_members_path(@project.namespace, @project), html: { class: 'form-horizontal users-project-form' } do |f| - .form-group - = f.label :user_ids, "People", class: 'control-label' - .col-sm-10 - = users_select_tag(:user_ids, multiple: true, class: 'input-large', scope: :all, email_user: true) - .help-block += form_for @project_member, as: :project_member, url: namespace_project_project_members_path(@project.namespace, @project) do |f| + .row + .col-md-4.col-lg-6 + = users_select_tag(:user_ids, multiple: true, class: "input-full", scope: :all, email_user: true) + .help-block.append-bottom-10 Search for users by name, username, or email, or invite new ones using their email address. - .form-group - = f.label :access_level, "Project Access", class: 'control-label' - .col-sm-10 - = select_tag :access_level, options_for_select(ProjectMember.access_level_roles, @project_member.access_level), class: "project-access-select select2" - .help-block - Read more about role permissions - %strong= link_to "here", help_page_path("user/permissions"), class: "vlink" + .col-md-3.col-lg-2 + = select_tag :access_level, options_for_select(ProjectMember.access_level_roles, @project_member.access_level), class: "form-control project-access-select" + .help-block.append-bottom-10 + = link_to "Read more", help_page_path("user/permissions"), class: "vlink" + about role permissions - .form-group - = f.label :expires_at, 'Access expiration date', class: 'control-label' - .col-sm-10 + .col-md-3.col-lg-2 .clearable-input - = text_field_tag :expires_at, nil, class: 'form-control js-access-expiration-date', placeholder: 'Select access expiration date' + = text_field_tag :expires_at, nil, class: 'form-control js-access-expiration-date', placeholder: 'Expiration date' %i.clear-icon.js-clear-input - .help-block + .help-block.append-bottom-10 On this date, the user(s) will automatically lose access to this project. - .form-actions - = f.submit 'Add users to project', class: "btn btn-create" + .col-md-2 + = f.submit "Add to project", class: "btn btn-create btn-block" diff --git a/app/views/projects/project_members/_team.html.haml b/app/views/projects/project_members/_team.html.haml index b0bfdd235f7..db6c1194da7 100644 --- a/app/views/projects/project_members/_team.html.haml +++ b/app/views/projects/project_members/_team.html.haml @@ -1,7 +1,7 @@ .panel.panel-default .panel-heading + Users with access to %strong #{@project.name} - project members %span.badge= members.size .controls = form_tag namespace_project_project_members_path(@project.namespace, @project), method: :get, class: 'form-inline member-search-form' do diff --git a/app/views/projects/project_members/index.html.haml b/app/views/projects/project_members/index.html.haml index 9d063b3081f..9d47e7d725c 100644 --- a/app/views/projects/project_members/index.html.haml +++ b/app/views/projects/project_members/index.html.haml @@ -1,20 +1,21 @@ - page_title "Members" .project-members-page.js-project-members-page.prepend-top-default + %h4 + Members + %hr - if can?(current_user, :admin_project_member, @project) - .panel.panel-default - .panel-heading - Add new user to project - .controls - = link_to import_namespace_project_project_members_path(@project.namespace, @project), class: "btn btn-grouped", title: "Import members from another project" do - Import members - .panel-body - %p.light - Users with access to this project are listed below. - = render "new_project_member" + .project-members-new.append-bottom-default + %h5.clearfix + Add new user to + %strong= @project.name + -# = link_to "Import", import_namespace_project_project_members_path(@project.namespace, @project), class: "btn btn-default pull-right", title: "Import members from another project" + = render "new_project_member" - = render 'shared/members/requests', membership_source: @project, requesters: @requesters + = render 'shared/members/requests', membership_source: @project, requesters: @requesters + %h5.append-bottom-default + Existing users and groups = render 'team', members: @project_members - if @group diff --git a/app/views/shared/members/_member.html.haml b/app/views/shared/members/_member.html.haml index 5f20e4bd42a..fd9b688dc20 100644 --- a/app/views/shared/members/_member.html.haml +++ b/app/views/shared/members/_member.html.haml @@ -2,27 +2,28 @@ - show_controls = local_assigns.fetch(:show_controls, true) - user = member.user -%li.js-toggle-container{ class: dom_class(member), id: dom_id(member) } +%li.member{ class: dom_class(member), id: dom_id(member) } - if show_roles .controls - %strong.control-text= member.human_access - if show_controls - - if !user && can?(current_user, action_member_permission(:admin, member), member.source) - = link_to 'Resend invite', polymorphic_path([:resend_invite, member]), - method: :post, - class: 'btn' - - - if can?(current_user, action_member_permission(:update, member), member) - = button_tag icon('pencil'), - type: 'button', - class: 'btn inline js-toggle-button', - title: 'Edit' - - - if member.request? - = link_to icon('check inverse'), polymorphic_path([:approve_access_request, member]), + - if @project.owner != user + = form_for member, remote: true, html: { class: 'form-horizontal' } do |f| + = f.select :access_level, options_for_select(member.class.access_level_roles, member.access_level), {}, class: 'form-control member-form-control append-right-5 js-member-update-control', id: "member_access_level_#{member.id}", disabled: !can?(current_user, action_member_permission(:update, member), member) + .prepend-left-5.append-right-10.clearable-input.member-form-control + = f.text_field :expires_at, class: 'form-control js-access-expiration-date js-member-update-control', placeholder: 'Expiration date', id: "member_expires_at_#{member.id}", disabled: !can?(current_user, action_member_permission(:update, member), member) + %i.clear-icon.js-clear-input + - if !user && can?(current_user, action_member_permission(:admin, member), member.source) + = link_to 'Resend invite', polymorphic_path([:resend_invite, member]), method: :post, - class: 'btn btn-success', - title: 'Grant access' + class: 'btn' + - else + Owner + + - if member.request? && can?(current_user, action_member_permission(:update, member), member) + = link_to icon('check inverse'), polymorphic_path([:approve_access_request, member]), + method: :post, + class: 'btn btn-success', + title: 'Grant access' - if can?(current_user, action_member_permission(:destroy, member), member) - if current_user == user @@ -44,7 +45,7 @@ = image_tag avatar_icon(user, 40), class: "avatar s40", alt: '' %strong = link_to user.name, user_path(user) - %span.cgray= user.username + %span.cgray= user.to_reference - if user == current_user %span.label.label-success It's you @@ -73,20 +74,3 @@ by = link_to member.created_by.name, user_path(member.created_by) = time_ago_with_tooltip(member.created_at) - - - if show_roles - .edit-member.hide.js-toggle-content - %br - = form_for member, remote: true, html: { class: 'form-horizontal' } do |f| - .form-group - = label_tag "member_access_level_#{member.id}", 'Project access', class: 'control-label' - .col-sm-10 - = f.select :access_level, options_for_select(member.class.access_level_roles, member.access_level), {}, class: 'form-control', id: "member_access_level_#{member.id}" - .form-group - = label_tag "member_expires_at_#{member.id}", 'Access expiration date', class: 'control-label' - .col-sm-10 - .clearable-input - = f.text_field :expires_at, class: 'form-control js-access-expiration-date', placeholder: 'Select access expiration date', id: "member_expires_at_#{member.id}" - %i.clear-icon.js-clear-input - .prepend-top-10 - = f.submit 'Save', class: 'btn btn-save btn-sm' -- cgit v1.2.1 From 87a0501ded0d08ae718b6f3f6feb4ac2c9c6b016 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 1 Sep 2016 11:56:21 +0100 Subject: Updates the member row when values changed --- app/assets/javascripts/member_expiration_date.js | 8 ++++++-- app/assets/javascripts/project_members.js.es6 | 5 +++-- app/views/projects/project_members/update.js.haml | 4 ++-- 3 files changed, 11 insertions(+), 6 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/member_expiration_date.js b/app/assets/javascripts/member_expiration_date.js index 1935af491f7..e1532fd9ec4 100644 --- a/app/assets/javascripts/member_expiration_date.js +++ b/app/assets/javascripts/member_expiration_date.js @@ -14,14 +14,18 @@ inputs.datepicker({ dateFormat: 'yy-mm-dd', minDate: 1, - onSelect: toggleClearInput + onSelect: function () { + $(this).trigger('change'); + toggleClearInput.call(this); + } }); inputs.next('.js-clear-input').on('click', function(event) { event.preventDefault(); var input = $(this).closest('.clearable-input').find('.js-access-expiration-date'); - input.datepicker('setDate', null); + input.datepicker('setDate', null) + .trigger('change'); toggleClearInput.call(input); }); diff --git a/app/assets/javascripts/project_members.js.es6 b/app/assets/javascripts/project_members.js.es6 index 74cedfd5006..659c57d8b6c 100644 --- a/app/assets/javascripts/project_members.js.es6 +++ b/app/assets/javascripts/project_members.js.es6 @@ -15,7 +15,8 @@ addListeners() { $('.project_member').on('ajax:success', this.removeRow); $('.js-member-update-control').on('change', function () { - console.log($(this).val()); + $(this).closest('form') + .trigger("submit.rails"); }); } @@ -28,7 +29,7 @@ } submitForm() { - + } } diff --git a/app/views/projects/project_members/update.js.haml b/app/views/projects/project_members/update.js.haml index 37e55dc72a3..91927181efb 100644 --- a/app/views/projects/project_members/update.js.haml +++ b/app/views/projects/project_members/update.js.haml @@ -1,3 +1,3 @@ :plain - $("##{dom_id(@project_member)}").replaceWith('#{escape_javascript(render('shared/members/member', member: @project_member))}'); - new gl.MemberExpirationDate(); + var $listItem = $('#{escape_javascript(render('shared/members/member', member: @project_member))}'); + $("##{dom_id(@project_member)} .list-item-name").replaceWith($listItem.find('.list-item-name')); -- cgit v1.2.1 From 4afd17b2786b5bca075ac7508979fad582c65bc9 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 1 Sep 2016 13:48:20 +0100 Subject: Included groups on project_members page --- app/assets/stylesheets/framework/lists.scss | 8 +++ app/assets/stylesheets/pages/groups.scss | 2 - app/assets/stylesheets/pages/members.scss | 9 +++ app/controllers/projects/group_links_controller.rb | 12 ++++ .../projects/project_members_controller.rb | 1 + app/views/projects/group_links/update.js.haml | 3 + .../project_members/_group_members.html.haml | 2 +- .../projects/project_members/_groups.html.haml | 9 +++ app/views/projects/project_members/_team.html.haml | 12 ---- app/views/projects/project_members/index.html.haml | 14 +++-- app/views/shared/members/_group.html.haml | 23 ++++++++ app/views/shared/members/_member.html.haml | 69 ++++++++++------------ 12 files changed, 107 insertions(+), 57 deletions(-) create mode 100644 app/views/projects/group_links/update.js.haml create mode 100644 app/views/projects/project_members/_groups.html.haml create mode 100644 app/views/shared/members/_group.html.haml (limited to 'app') diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss index 965fcc06518..dfdfe4a3c89 100644 --- a/app/assets/stylesheets/framework/lists.scss +++ b/app/assets/stylesheets/framework/lists.scss @@ -164,6 +164,14 @@ ul.content-list { } } + .member-controls { + float: none; + + @media (min-width: $screen-md-min) { + float: right; + } + } + // When dragging a list item &.ui-sortable-helper { border-bottom: none; diff --git a/app/assets/stylesheets/pages/groups.scss b/app/assets/stylesheets/pages/groups.scss index b657ca47d38..a27f7a2fd77 100644 --- a/app/assets/stylesheets/pages/groups.scss +++ b/app/assets/stylesheets/pages/groups.scss @@ -1,6 +1,4 @@ .member-search-form { - float: left; - input[type='search'] { width: 225px; vertical-align: bottom; diff --git a/app/assets/stylesheets/pages/members.scss b/app/assets/stylesheets/pages/members.scss index 9583d7c6161..4bc34ac15df 100644 --- a/app/assets/stylesheets/pages/members.scss +++ b/app/assets/stylesheets/pages/members.scss @@ -5,6 +5,15 @@ } .member { + .list-item-name { + float: none; + + @media (min-width: $screen-md-min) { + float: left; + width: 50%; + } + } + .controls { display: flex; width: 400px; diff --git a/app/controllers/projects/group_links_controller.rb b/app/controllers/projects/group_links_controller.rb index d0c4550733c..57c54bf625a 100644 --- a/app/controllers/projects/group_links_controller.rb +++ b/app/controllers/projects/group_links_controller.rb @@ -19,9 +19,21 @@ class Projects::GroupLinksController < Projects::ApplicationController redirect_to namespace_project_group_links_path(project.namespace, project) end + def update + @group_link = @project.project_group_links.find(params[:id]) + + @group_link.update_attributes(group_link_params) + end + def destroy project.project_group_links.find(params[:id]).destroy redirect_to namespace_project_group_links_path(project.namespace, project) end + + protected + + def group_link_params + params.require(:group_link).permit(:group_access, :expires_at) + end end diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb index 42a7e5a2c30..d83e95cf097 100644 --- a/app/controllers/projects/project_members_controller.rb +++ b/app/controllers/projects/project_members_controller.rb @@ -5,6 +5,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController before_action :authorize_admin_project_member!, except: [:index, :leave, :request_access] def index + @groups = @project.project_group_links.all @project_members = @project.project_members @project_members = @project_members.non_invite unless can?(current_user, :admin_project, @project) diff --git a/app/views/projects/group_links/update.js.haml b/app/views/projects/group_links/update.js.haml new file mode 100644 index 00000000000..d3a37847f58 --- /dev/null +++ b/app/views/projects/group_links/update.js.haml @@ -0,0 +1,3 @@ +:plain + var $listItem = $('#{escape_javascript(render('shared/members/group', group_link: @group_link, group: @group_link.group))}'); + $("##{dom_id(@group_link.group)} .list-item-name").replaceWith($listItem.find('.list-item-name')); diff --git a/app/views/projects/project_members/_group_members.html.haml b/app/views/projects/project_members/_group_members.html.haml index e783d8c72c5..9738f369a35 100644 --- a/app/views/projects/project_members/_group_members.html.haml +++ b/app/views/projects/project_members/_group_members.html.haml @@ -1,7 +1,7 @@ .panel.panel-default .panel-heading + Group members with access to %strong #{@group.name} - group members %span.badge= members.size - if can?(current_user, :admin_group_member, @group) .controls diff --git a/app/views/projects/project_members/_groups.html.haml b/app/views/projects/project_members/_groups.html.haml new file mode 100644 index 00000000000..79791af7963 --- /dev/null +++ b/app/views/projects/project_members/_groups.html.haml @@ -0,0 +1,9 @@ +.panel.panel-default + .panel-heading + Groups with access to + %strong #{@project.name} + %span.badge= groups.size + %ul.content-list + - @groups.each do |group_link| + - group = group_link.group + = render 'shared/members/group', group_link: group_link, group: group diff --git a/app/views/projects/project_members/_team.html.haml b/app/views/projects/project_members/_team.html.haml index db6c1194da7..6a8b28d3886 100644 --- a/app/views/projects/project_members/_team.html.haml +++ b/app/views/projects/project_members/_team.html.haml @@ -3,17 +3,5 @@ Users with access to %strong #{@project.name} %span.badge= members.size - .controls - = form_tag namespace_project_project_members_path(@project.namespace, @project), method: :get, class: 'form-inline member-search-form' do - .form-group - = search_field_tag :search, params[:search], { placeholder: 'Find existing member by name', class: 'form-control', spellcheck: false } - = button_tag class: 'btn', title: 'Search' do - = icon("search") %ul.content-list = render partial: 'shared/members/member', collection: members, as: :member - -:javascript - $('form.member-search-form').on('submit', function (event) { - event.preventDefault(); - Turbolinks.visit(this.action + '?' + $(this).serialize()); - }); diff --git a/app/views/projects/project_members/index.html.haml b/app/views/projects/project_members/index.html.haml index 9d47e7d725c..db8a060d170 100644 --- a/app/views/projects/project_members/index.html.haml +++ b/app/views/projects/project_members/index.html.haml @@ -14,12 +14,16 @@ = render 'shared/members/requests', membership_source: @project, requesters: @requesters - %h5.append-bottom-default - Existing users and groups + .append-bottom-default.clearfix + %h5.pull-left + Existing users and groups + = form_tag namespace_project_project_members_path(@project.namespace, @project), method: :get, class: 'form-inline member-search-form pull-right hidden-xs hidden-sm' do + .form-group + = search_field_tag :search, params[:search], { placeholder: 'Find existing members by name', class: 'form-control', spellcheck: false } + = icon("search") + - if @grups + = render 'groups', groups: @groups = render 'team', members: @project_members - if @group = render "group_members", members: @group_members - - - if @project_group_links.any? && @project.allowed_to_share_with_group? - = render "shared_group_members" diff --git a/app/views/shared/members/_group.html.haml b/app/views/shared/members/_group.html.haml new file mode 100644 index 00000000000..0502de5210b --- /dev/null +++ b/app/views/shared/members/_group.html.haml @@ -0,0 +1,23 @@ +- group = local_assigns[:group] +- group_link = local_assigns[:group_link] +%li.member{ class: dom_class(group), id: dom_id(group) } + %span{ class: "list-item-name" } + = image_tag group_icon(group), class: "avatar s40", alt: '' + %strong + = link_to group.name, group_path(group) + .cgray + Joined #{time_ago_with_tooltip(group.created_at)} + - if group_link.expires? + · + %span{ class: ('text-warning' if group_link.expires_soon?) } + Expires in #{distance_of_time_in_words_to_now(group_link.expires_at)} + .controls.member-controls + = form_tag namespace_project_group_link_path(@project.namespace, @project, group_link), method: :put, remote: true, class: 'form-horizontal' do + = select_tag 'group_link[group_access]', options_for_select(ProjectGroupLink.access_options, group_link.group_access), class: 'form-control member-form-control append-right-5 js-member-update-control', id: "member_access_level_#{group.id}" + .prepend-left-5.append-right-10.clearable-input.member-form-control + = text_field_tag 'group_link[expires_at]', group_link.expires_at, class: 'form-control js-access-expiration-date js-member-update-control', placeholder: 'Expiration date', id: "member_expires_at_#{group.id}" + %i.clear-icon.js-clear-input + = link_to icon('trash'), namespace_project_group_link_path(@project.namespace, @project, group_link), + remote: true, + method: :delete, + class: 'btn btn-remove' diff --git a/app/views/shared/members/_member.html.haml b/app/views/shared/members/_member.html.haml index fd9b688dc20..800badc051a 100644 --- a/app/views/shared/members/_member.html.haml +++ b/app/views/shared/members/_member.html.haml @@ -3,43 +3,6 @@ - user = member.user %li.member{ class: dom_class(member), id: dom_id(member) } - - if show_roles - .controls - - if show_controls - - if @project.owner != user - = form_for member, remote: true, html: { class: 'form-horizontal' } do |f| - = f.select :access_level, options_for_select(member.class.access_level_roles, member.access_level), {}, class: 'form-control member-form-control append-right-5 js-member-update-control', id: "member_access_level_#{member.id}", disabled: !can?(current_user, action_member_permission(:update, member), member) - .prepend-left-5.append-right-10.clearable-input.member-form-control - = f.text_field :expires_at, class: 'form-control js-access-expiration-date js-member-update-control', placeholder: 'Expiration date', id: "member_expires_at_#{member.id}", disabled: !can?(current_user, action_member_permission(:update, member), member) - %i.clear-icon.js-clear-input - - if !user && can?(current_user, action_member_permission(:admin, member), member.source) - = link_to 'Resend invite', polymorphic_path([:resend_invite, member]), - method: :post, - class: 'btn' - - else - Owner - - - if member.request? && can?(current_user, action_member_permission(:update, member), member) - = link_to icon('check inverse'), polymorphic_path([:approve_access_request, member]), - method: :post, - class: 'btn btn-success', - title: 'Grant access' - - - if can?(current_user, action_member_permission(:destroy, member), member) - - if current_user == user - = link_to icon('sign-out', text: 'Leave'), polymorphic_path([:leave, member.source, :members]), - method: :delete, - data: { confirm: leave_confirmation_message(member.source) }, - class: 'btn btn-remove' - - else - = link_to icon('trash'), member, - remote: true, - method: :delete, - data: { confirm: remove_member_message(member) }, - class: 'btn btn-remove', - title: remove_member_title(member) - - %span{ class: ("list-item-name" if show_controls) } - if user = image_tag avatar_icon(user, 40), class: "avatar s40", alt: '' @@ -74,3 +37,35 @@ by = link_to member.created_by.name, user_path(member.created_by) = time_ago_with_tooltip(member.created_at) + - if show_roles + .controls.member-controls + - if show_controls + = form_for member, remote: true, html: { class: 'form-horizontal' } do |f| + = f.select :access_level, options_for_select(member.class.access_level_roles, member.access_level), {}, class: 'form-control member-form-control append-right-5 js-member-update-control', id: "member_access_level_#{member.id}", disabled: !can?(current_user, action_member_permission(:update, member), member) + .prepend-left-5.append-right-10.clearable-input.member-form-control + = f.text_field :expires_at, class: 'form-control js-access-expiration-date js-member-update-control', placeholder: 'Expiration date', id: "member_expires_at_#{member.id}", disabled: !can?(current_user, action_member_permission(:update, member), member) + %i.clear-icon.js-clear-input + - if !user && can?(current_user, action_member_permission(:admin, member), member.source) + = link_to 'Resend invite', polymorphic_path([:resend_invite, member]), + method: :post, + class: 'btn' + + - if member.request? && can?(current_user, action_member_permission(:update, member), member) + = link_to icon('check inverse'), polymorphic_path([:approve_access_request, member]), + method: :post, + class: 'btn btn-success', + title: 'Grant access' + + - if can?(current_user, action_member_permission(:destroy, member), member) + - if current_user == user + = link_to icon('sign-out', text: 'Leave'), polymorphic_path([:leave, member.source, :members]), + method: :delete, + data: { confirm: leave_confirmation_message(member.source) }, + class: 'btn btn-remove' + - else + = link_to icon('trash'), member, + remote: true, + method: :delete, + data: { confirm: remove_member_message(member) }, + class: 'btn btn-remove', + title: remove_member_title(member) -- cgit v1.2.1 From 23993147fbf24e868d33927dc1194b60a106076d Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 1 Sep 2016 14:13:49 +0100 Subject: Fixed issue with groups not displaying --- app/controllers/projects/project_members_controller.rb | 14 -------------- app/views/projects/project_members/index.html.haml | 6 ++---- 2 files changed, 2 insertions(+), 18 deletions(-) (limited to 'app') diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb index d83e95cf097..6060ddf025b 100644 --- a/app/controllers/projects/project_members_controller.rb +++ b/app/controllers/projects/project_members_controller.rb @@ -16,20 +16,6 @@ class Projects::ProjectMembersController < Projects::ApplicationController @project_members = @project_members.order('access_level DESC') - @group = @project.group - - if @group - @group_members = @group.group_members - @group_members = @group_members.non_invite unless can?(current_user, :admin_group, @group) - - if params[:search].present? - users = @group.users.search(params[:search]).to_a - @group_members = @group_members.where(user_id: users) - end - - @group_members = @group_members.order('access_level DESC') - end - @requesters = @project.requesters if can?(current_user, :admin_project, @project) @project_member = @project.project_members.new diff --git a/app/views/projects/project_members/index.html.haml b/app/views/projects/project_members/index.html.haml index db8a060d170..42a23057ff1 100644 --- a/app/views/projects/project_members/index.html.haml +++ b/app/views/projects/project_members/index.html.haml @@ -21,9 +21,7 @@ .form-group = search_field_tag :search, params[:search], { placeholder: 'Find existing members by name', class: 'form-control', spellcheck: false } = icon("search") - - if @grups + - if @groups.size > 0 = render 'groups', groups: @groups - = render 'team', members: @project_members - - if @group - = render "group_members", members: @group_members + = render 'team', members: @project_members -- cgit v1.2.1 From e33cda96cb20f47fdde4314f6bb00e43bbf5aeb4 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 1 Sep 2016 15:27:42 +0100 Subject: Fixed group members not deleting Combine both group members & project members in project members list --- app/assets/javascripts/project_members.js.es6 | 7 ++++--- app/controllers/projects/project_members_controller.rb | 9 +++++---- app/views/projects/project_members/_team.html.haml | 6 ++++-- app/views/projects/project_members/index.html.haml | 1 + 4 files changed, 14 insertions(+), 9 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/project_members.js.es6 b/app/assets/javascripts/project_members.js.es6 index 659c57d8b6c..8b5cb17ac2d 100644 --- a/app/assets/javascripts/project_members.js.es6 +++ b/app/assets/javascripts/project_members.js.es6 @@ -8,12 +8,12 @@ } removeListeners() { - $('.project_member').off('ajax:success'); + $('.project_member, .group_member').off('ajax:success'); $('.js-member-update-control').off('change'); } addListeners() { - $('.project_member').on('ajax:success', this.removeRow); + $('.project_member, .group_member').on('ajax:success', this.removeRow); $('.js-member-update-control').on('change', function () { $(this).closest('form') .trigger("submit.rails"); @@ -24,7 +24,8 @@ const $target = $(e.target); if ($target.hasClass('btn-remove')) { - $target.fadeOut(); + console.log('a'); + $target.closest('.member').fadeOut(); } } diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb index 6060ddf025b..abb92938211 100644 --- a/app/controllers/projects/project_members_controller.rb +++ b/app/controllers/projects/project_members_controller.rb @@ -6,15 +6,16 @@ class Projects::ProjectMembersController < Projects::ApplicationController def index @groups = @project.project_group_links.all - @project_members = @project.project_members + @project_members = @project.team.members.all + @project_members_size = @project_members.size + @group_members = @project.group.group_members @project_members = @project_members.non_invite unless can?(current_user, :admin_project, @project) if params[:search].present? - users = @project.users.search(params[:search]).to_a - @project_members = @project_members.where(user_id: users) + @project_members = @project_members.search(params[:search]) end - @project_members = @project_members.order('access_level DESC') + @project_members = @project_members.page(params[:page]) @requesters = @project.requesters if can?(current_user, :admin_project, @project) diff --git a/app/views/projects/project_members/_team.html.haml b/app/views/projects/project_members/_team.html.haml index 6a8b28d3886..23c35f91b6b 100644 --- a/app/views/projects/project_members/_team.html.haml +++ b/app/views/projects/project_members/_team.html.haml @@ -2,6 +2,8 @@ .panel-heading Users with access to %strong #{@project.name} - %span.badge= members.size + %span.badge= @project_members_size %ul.content-list - = render partial: 'shared/members/member', collection: members, as: :member + - members.each do |user| + - member = @project.team.find_member(user.id) + = render 'shared/members/member', member: member diff --git a/app/views/projects/project_members/index.html.haml b/app/views/projects/project_members/index.html.haml index 42a23057ff1..85e512a75f4 100644 --- a/app/views/projects/project_members/index.html.haml +++ b/app/views/projects/project_members/index.html.haml @@ -25,3 +25,4 @@ = render 'groups', groups: @groups = render 'team', members: @project_members + = paginate @project_members, theme: "gitlab" -- cgit v1.2.1 From 843dd24bdf063bb199c841cd6a08643344ae7598 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 1 Sep 2016 16:22:53 +0100 Subject: Mobile improvements Added group name to members row Fixed saving group member --- app/assets/javascripts/project_members.js.es6 | 7 +++- app/assets/stylesheets/framework/lists.scss | 8 +++-- app/assets/stylesheets/pages/members.scss | 42 ++++++++++++++++++---- app/views/groups/group_members/update.js.haml | 4 +-- app/views/projects/project_members/_team.html.haml | 2 +- app/views/shared/members/_group.html.haml | 7 ++-- app/views/shared/members/_member.html.haml | 28 ++++++++++----- 7 files changed, 74 insertions(+), 24 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/project_members.js.es6 b/app/assets/javascripts/project_members.js.es6 index 8b5cb17ac2d..d11467cf6eb 100644 --- a/app/assets/javascripts/project_members.js.es6 +++ b/app/assets/javascripts/project_members.js.es6 @@ -1,5 +1,5 @@ ((w) => { - window.gl = window.gl || {}; + w.gl = w.gl || {}; class ProjectMembers { constructor() { @@ -10,6 +10,7 @@ removeListeners() { $('.project_member, .group_member').off('ajax:success'); $('.js-member-update-control').off('change'); + $('.js-edit-member-form').off('ajax:success'); } addListeners() { @@ -17,6 +18,10 @@ $('.js-member-update-control').on('change', function () { $(this).closest('form') .trigger("submit.rails"); + $(this).disable(); + }); + $('.js-edit-member-form').on('ajax:success', function () { + $(this).find('.js-member-update-control').enable(); }); } diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss index dfdfe4a3c89..272d37763c1 100644 --- a/app/assets/stylesheets/framework/lists.scss +++ b/app/assets/stylesheets/framework/lists.scss @@ -128,6 +128,10 @@ ul.content-list { color: $gl-dark-link-color; } + .member-group-link { + color: $blue-normal; + } + .description { p { @include str-truncated; @@ -166,8 +170,8 @@ ul.content-list { .member-controls { float: none; - - @media (min-width: $screen-md-min) { + + @media (min-width: $screen-sm-min) { float: right; } } diff --git a/app/assets/stylesheets/pages/members.scss b/app/assets/stylesheets/pages/members.scss index 4bc34ac15df..a69af862348 100644 --- a/app/assets/stylesheets/pages/members.scss +++ b/app/assets/stylesheets/pages/members.scss @@ -8,24 +8,52 @@ .list-item-name { float: none; - @media (min-width: $screen-md-min) { + @media (min-width: $screen-sm-min) { float: left; width: 50%; } } .controls { - display: flex; - width: 400px; + @media (min-width: $screen-sm-min) { + display: flex; + width: 400px; + max-width: 50%; + } } .form-horizontal { - display: flex; - flex: 1; - margin-top: 3px; + margin-top: 5px; + + @media (min-width: $screen-sm-min) { + display: flex; + flex: 1; + margin-top: 3px; + } + } + + .btn-remove { + width: 100%; + + @media (min-width: $screen-sm-min) { + width: auto; + } + } +} + +.member-form-control { + @media (max-width: $screen-xs-max) { + padding: 5px 0; + margin-left: 0; + margin-right: 0; } - .member-form-control { + @media (min-width: $screen-sm-min) { width: 50%; } } + +.member-access-text { + margin-left: auto; + line-height: 43px; +} diff --git a/app/views/groups/group_members/update.js.haml b/app/views/groups/group_members/update.js.haml index 3be7ed8432c..de8f53b6b52 100644 --- a/app/views/groups/group_members/update.js.haml +++ b/app/views/groups/group_members/update.js.haml @@ -1,3 +1,3 @@ :plain - $("##{dom_id(@group_member)}").replaceWith('#{escape_javascript(render('shared/members/member', member: @group_member))}'); - new gl.MemberExpirationDate(); + var $listItem = $('#{escape_javascript(render('shared/members/member', member: @group_member))}'); + $("##{dom_id(@group_member)} .list-item-name").replaceWith($listItem.find('.list-item-name')); diff --git a/app/views/projects/project_members/_team.html.haml b/app/views/projects/project_members/_team.html.haml index 23c35f91b6b..2af9fe0519c 100644 --- a/app/views/projects/project_members/_team.html.haml +++ b/app/views/projects/project_members/_team.html.haml @@ -6,4 +6,4 @@ %ul.content-list - members.each do |user| - member = @project.team.find_member(user.id) - = render 'shared/members/member', member: member + = render 'shared/members/member', member: member, user: user diff --git a/app/views/shared/members/_group.html.haml b/app/views/shared/members/_group.html.haml index 0502de5210b..5d54195646c 100644 --- a/app/views/shared/members/_group.html.haml +++ b/app/views/shared/members/_group.html.haml @@ -17,7 +17,10 @@ .prepend-left-5.append-right-10.clearable-input.member-form-control = text_field_tag 'group_link[expires_at]', group_link.expires_at, class: 'form-control js-access-expiration-date js-member-update-control', placeholder: 'Expiration date', id: "member_expires_at_#{group.id}" %i.clear-icon.js-clear-input - = link_to icon('trash'), namespace_project_group_link_path(@project.namespace, @project, group_link), + = link_to namespace_project_group_link_path(@project.namespace, @project, group_link), remote: true, method: :delete, - class: 'btn btn-remove' + class: 'btn btn-remove' do + %span.visible-xs-block + Delete + = icon('trash', class: 'hidden-xs') diff --git a/app/views/shared/members/_member.html.haml b/app/views/shared/members/_member.html.haml index 800badc051a..4518e84fe34 100644 --- a/app/views/shared/members/_member.html.haml +++ b/app/views/shared/members/_member.html.haml @@ -1,6 +1,6 @@ - show_roles = local_assigns.fetch(:show_roles, true) - show_controls = local_assigns.fetch(:show_controls, true) -- user = member.user +- user = local_assigns.fetch(:user, member.user) %li.member{ class: dom_class(member), id: dom_id(member) } %span{ class: ("list-item-name" if show_controls) } @@ -11,12 +11,16 @@ %span.cgray= user.to_reference - if user == current_user - %span.label.label-success It's you + %span.label.label-success.prepend-left-5 It's you - if user.blocked? %label.label.label-danger %strong Blocked + - if member.respond_to?(:group) && !@group + = link_to member.group, class: "member-group-link prepend-left-5" do + = "· #{member.group.name}" + .cgray - if member.request? Requested @@ -40,11 +44,14 @@ - if show_roles .controls.member-controls - if show_controls - = form_for member, remote: true, html: { class: 'form-horizontal' } do |f| - = f.select :access_level, options_for_select(member.class.access_level_roles, member.access_level), {}, class: 'form-control member-form-control append-right-5 js-member-update-control', id: "member_access_level_#{member.id}", disabled: !can?(current_user, action_member_permission(:update, member), member) - .prepend-left-5.append-right-10.clearable-input.member-form-control - = f.text_field :expires_at, class: 'form-control js-access-expiration-date js-member-update-control', placeholder: 'Expiration date', id: "member_expires_at_#{member.id}", disabled: !can?(current_user, action_member_permission(:update, member), member) - %i.clear-icon.js-clear-input + - if user != current_user + = form_for member, remote: true, html: { class: 'form-horizontal js-edit-member-form' } do |f| + = f.select :access_level, options_for_select(member.class.access_level_roles, member.access_level), {}, class: 'form-control member-form-control append-right-5 js-member-update-control', id: "member_access_level_#{member.id}", disabled: !can?(current_user, action_member_permission(:update, member), member) + .prepend-left-5.append-right-10.clearable-input.member-form-control + = f.text_field :expires_at, class: 'form-control js-access-expiration-date js-member-update-control', placeholder: 'Expiration date', id: "member_expires_at_#{member.id}", disabled: !can?(current_user, action_member_permission(:update, member), member) + %i.clear-icon.js-clear-input + - else + %span.member-access-text= member.human_access - if !user && can?(current_user, action_member_permission(:admin, member), member.source) = link_to 'Resend invite', polymorphic_path([:resend_invite, member]), method: :post, @@ -63,9 +70,12 @@ data: { confirm: leave_confirmation_message(member.source) }, class: 'btn btn-remove' - else - = link_to icon('trash'), member, + = link_to member, remote: true, method: :delete, data: { confirm: remove_member_message(member) }, class: 'btn btn-remove', - title: remove_member_title(member) + title: remove_member_title(member) do + %span.visible-xs-block + Delete + = icon('trash', class: 'hidden-xs') -- cgit v1.2.1 From 49a31e64b76b351c1bad91459991a69f0e0fb296 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 1 Sep 2016 17:04:46 +0100 Subject: Removed console log Hides time on mobile --- app/assets/javascripts/project_members.js.es6 | 1 - app/views/shared/members/_member.html.haml | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/project_members.js.es6 b/app/assets/javascripts/project_members.js.es6 index d11467cf6eb..f525bdbb1ea 100644 --- a/app/assets/javascripts/project_members.js.es6 +++ b/app/assets/javascripts/project_members.js.es6 @@ -29,7 +29,6 @@ const $target = $(e.target); if ($target.hasClass('btn-remove')) { - console.log('a'); $target.closest('.member').fadeOut(); } } diff --git a/app/views/shared/members/_member.html.haml b/app/views/shared/members/_member.html.haml index 4518e84fe34..ab4f1f1382b 100644 --- a/app/views/shared/members/_member.html.haml +++ b/app/views/shared/members/_member.html.haml @@ -21,7 +21,7 @@ = link_to member.group, class: "member-group-link prepend-left-5" do = "· #{member.group.name}" - .cgray + .hidden-xs.cgray - if member.request? Requested = time_ago_with_tooltip(member.requested_at) -- cgit v1.2.1 From 15a3111a6663894d952103e7395f2f56408f88ce Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 2 Sep 2016 09:04:28 +0100 Subject: Mobile spacing improvements --- app/assets/stylesheets/framework/forms.scss | 4 --- app/assets/stylesheets/pages/groups.scss | 12 -------- app/assets/stylesheets/pages/members.scss | 36 ++++++++++++++++++++++ app/views/projects/project_members/index.html.haml | 7 +++-- app/views/shared/members/_group.html.haml | 2 +- 5 files changed, 41 insertions(+), 20 deletions(-) (limited to 'app') diff --git a/app/assets/stylesheets/framework/forms.scss b/app/assets/stylesheets/framework/forms.scss index 37ff7e22ed1..d1f1a372c06 100644 --- a/app/assets/stylesheets/framework/forms.scss +++ b/app/assets/stylesheets/framework/forms.scss @@ -125,7 +125,3 @@ label { border-right: 0; } } - -.help-block { - margin-bottom: 0; -} diff --git a/app/assets/stylesheets/pages/groups.scss b/app/assets/stylesheets/pages/groups.scss index a27f7a2fd77..cc1c0249df3 100644 --- a/app/assets/stylesheets/pages/groups.scss +++ b/app/assets/stylesheets/pages/groups.scss @@ -1,15 +1,3 @@ -.member-search-form { - input[type='search'] { - width: 225px; - vertical-align: bottom; - - @media (max-width: $screen-xs-max) { - width: 100px; - vertical-align: bottom; - } - } -} - .milestone-row { @include str-truncated(90%); } diff --git a/app/assets/stylesheets/pages/members.scss b/app/assets/stylesheets/pages/members.scss index a69af862348..a7f1324f69a 100644 --- a/app/assets/stylesheets/pages/members.scss +++ b/app/assets/stylesheets/pages/members.scss @@ -57,3 +57,39 @@ margin-left: auto; line-height: 43px; } + +.member.existing-title { + @media (min-width: $screen-sm-min) { + float: left; + } +} + +.member-search-form { + position: relative; + + @media (min-width: $screen-sm-min) { + float: right; + } + + .form-control { + width: 100%; + padding-right: 35px; + + @media (min-width: $screen-sm-min) { + width: 350px; + } + } +} + +.member-search-btn { + position: absolute; + right: 0; + top: 0; + height: 35px; + padding-left: 10px; + padding-right: 10px; + color: $gray-darkest; + background: transparent; + border: 0; + outline: 0; +} diff --git a/app/views/projects/project_members/index.html.haml b/app/views/projects/project_members/index.html.haml index 85e512a75f4..abe10433387 100644 --- a/app/views/projects/project_members/index.html.haml +++ b/app/views/projects/project_members/index.html.haml @@ -15,12 +15,13 @@ = render 'shared/members/requests', membership_source: @project, requesters: @requesters .append-bottom-default.clearfix - %h5.pull-left + %h5.member.existing-title Existing users and groups - = form_tag namespace_project_project_members_path(@project.namespace, @project), method: :get, class: 'form-inline member-search-form pull-right hidden-xs hidden-sm' do + = form_tag namespace_project_project_members_path(@project.namespace, @project), method: :get, class: 'form-inline member-search-form' do .form-group = search_field_tag :search, params[:search], { placeholder: 'Find existing members by name', class: 'form-control', spellcheck: false } - = icon("search") + %button.member-search-btn{ type: "submit", "aria-label" => "Submit search" } + = icon("search") - if @groups.size > 0 = render 'groups', groups: @groups diff --git a/app/views/shared/members/_group.html.haml b/app/views/shared/members/_group.html.haml index 5d54195646c..e545aec80a9 100644 --- a/app/views/shared/members/_group.html.haml +++ b/app/views/shared/members/_group.html.haml @@ -12,7 +12,7 @@ %span{ class: ('text-warning' if group_link.expires_soon?) } Expires in #{distance_of_time_in_words_to_now(group_link.expires_at)} .controls.member-controls - = form_tag namespace_project_group_link_path(@project.namespace, @project, group_link), method: :put, remote: true, class: 'form-horizontal' do + = form_tag namespace_project_group_link_path(@project.namespace, @project, group_link), method: :put, remote: true, class: 'form-horizontal js-edit-member-form' do = select_tag 'group_link[group_access]', options_for_select(ProjectGroupLink.access_options, group_link.group_access), class: 'form-control member-form-control append-right-5 js-member-update-control', id: "member_access_level_#{group.id}" .prepend-left-5.append-right-10.clearable-input.member-form-control = text_field_tag 'group_link[expires_at]', group_link.expires_at, class: 'form-control js-access-expiration-date js-member-update-control', placeholder: 'Expiration date', id: "member_expires_at_#{group.id}" -- cgit v1.2.1 From e477ad44565dbe69e3f0200f4f4f7bebbd48cb15 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 2 Sep 2016 09:25:53 +0100 Subject: Removes row from dom when deleting Fixed spacing with buttons Disables group form when user doesnt have correct permissions --- app/assets/javascripts/project_members.js.es6 | 5 ++++- .../projects/project_members_controller.rb | 7 ++----- app/models/project_team.rb | 8 ++++---- app/views/shared/members/_group.html.haml | 21 +++++++++++---------- app/views/shared/members/_member.html.haml | 10 +++++----- app/views/shared/members/_requests.html.haml | 2 +- 6 files changed, 27 insertions(+), 26 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/project_members.js.es6 b/app/assets/javascripts/project_members.js.es6 index f525bdbb1ea..56bc98d1076 100644 --- a/app/assets/javascripts/project_members.js.es6 +++ b/app/assets/javascripts/project_members.js.es6 @@ -29,7 +29,10 @@ const $target = $(e.target); if ($target.hasClass('btn-remove')) { - $target.closest('.member').fadeOut(); + $target.closest('.member') + .fadeOut(function () { + $(this).remove(); + }); } } diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb index abb92938211..cd31653698c 100644 --- a/app/controllers/projects/project_members_controller.rb +++ b/app/controllers/projects/project_members_controller.rb @@ -5,11 +5,9 @@ class Projects::ProjectMembersController < Projects::ApplicationController before_action :authorize_admin_project_member!, except: [:index, :leave, :request_access] def index - @groups = @project.project_group_links.all - @project_members = @project.team.members.all + @groups = @project.project_group_links + @project_members = @project.team.members(can?(current_user, :admin_project, @project)) @project_members_size = @project_members.size - @group_members = @project.group.group_members - @project_members = @project_members.non_invite unless can?(current_user, :admin_project, @project) if params[:search].present? @project_members = @project_members.search(params[:search]) @@ -20,7 +18,6 @@ class Projects::ProjectMembersController < Projects::ApplicationController @requesters = @project.requesters if can?(current_user, :admin_project, @project) @project_member = @project.project_members.new - @project_group_links = @project.project_group_links end def create diff --git a/app/models/project_team.rb b/app/models/project_team.rb index ab6ea2aae36..57925a0861a 100644 --- a/app/models/project_team.rb +++ b/app/models/project_team.rb @@ -52,8 +52,8 @@ class ProjectTeam ProjectMember.truncate_team(project) end - def members - @members ||= fetch_members + def members(non_invite) + @members ||= fetch_members(nil, non_invite) end alias_method :users, :members @@ -197,7 +197,7 @@ class ProjectTeam access.each { |key, value| access[key] = [value, capped_access_level].min } end - def fetch_members(level = nil) + def fetch_members(level = nil, non_invite = false) project_members = project.members group_members = group ? group.members : [] invited_members = [] @@ -236,7 +236,7 @@ class ProjectTeam end user_ids = project_members.pluck(:user_id) - user_ids.push(*invited_members.map(&:user_id)) if invited_members.any? + user_ids.push(*invited_members.map(&:user_id)) if invited_members.any? && !non_invite user_ids.push(*group_members.pluck(:user_id)) if group User.where(id: user_ids) diff --git a/app/views/shared/members/_group.html.haml b/app/views/shared/members/_group.html.haml index e545aec80a9..19b58ef20ae 100644 --- a/app/views/shared/members/_group.html.haml +++ b/app/views/shared/members/_group.html.haml @@ -13,14 +13,15 @@ Expires in #{distance_of_time_in_words_to_now(group_link.expires_at)} .controls.member-controls = form_tag namespace_project_group_link_path(@project.namespace, @project, group_link), method: :put, remote: true, class: 'form-horizontal js-edit-member-form' do - = select_tag 'group_link[group_access]', options_for_select(ProjectGroupLink.access_options, group_link.group_access), class: 'form-control member-form-control append-right-5 js-member-update-control', id: "member_access_level_#{group.id}" - .prepend-left-5.append-right-10.clearable-input.member-form-control - = text_field_tag 'group_link[expires_at]', group_link.expires_at, class: 'form-control js-access-expiration-date js-member-update-control', placeholder: 'Expiration date', id: "member_expires_at_#{group.id}" + = select_tag 'group_link[group_access]', options_for_select(ProjectGroupLink.access_options, group_link.group_access), class: 'form-control member-form-control append-right-5 js-member-update-control', id: "member_access_level_#{group.id}", disabled: !can?(current_user, action_member_permission(:admin, group), group) + .prepend-left-5.clearable-input.member-form-control + = text_field_tag 'group_link[expires_at]', group_link.expires_at, class: 'form-control js-access-expiration-date js-member-update-control', placeholder: 'Expiration date', id: "member_expires_at_#{group.id}", disabled: !can?(current_user, action_member_permission(:admin, group), group) %i.clear-icon.js-clear-input - = link_to namespace_project_group_link_path(@project.namespace, @project, group_link), - remote: true, - method: :delete, - class: 'btn btn-remove' do - %span.visible-xs-block - Delete - = icon('trash', class: 'hidden-xs') + - if can?(current_user, action_member_permission(:admin, group), group) + = link_to namespace_project_group_link_path(@project.namespace, @project, group_link), + remote: true, + method: :delete, + class: 'btn btn-remove prepend-left-10' do + %span.visible-xs-block + Delete + = icon('trash', class: 'hidden-xs') diff --git a/app/views/shared/members/_member.html.haml b/app/views/shared/members/_member.html.haml index ab4f1f1382b..2d4853eef92 100644 --- a/app/views/shared/members/_member.html.haml +++ b/app/views/shared/members/_member.html.haml @@ -47,7 +47,7 @@ - if user != current_user = form_for member, remote: true, html: { class: 'form-horizontal js-edit-member-form' } do |f| = f.select :access_level, options_for_select(member.class.access_level_roles, member.access_level), {}, class: 'form-control member-form-control append-right-5 js-member-update-control', id: "member_access_level_#{member.id}", disabled: !can?(current_user, action_member_permission(:update, member), member) - .prepend-left-5.append-right-10.clearable-input.member-form-control + .prepend-left-5.clearable-input.member-form-control = f.text_field :expires_at, class: 'form-control js-access-expiration-date js-member-update-control', placeholder: 'Expiration date', id: "member_expires_at_#{member.id}", disabled: !can?(current_user, action_member_permission(:update, member), member) %i.clear-icon.js-clear-input - else @@ -55,12 +55,12 @@ - if !user && can?(current_user, action_member_permission(:admin, member), member.source) = link_to 'Resend invite', polymorphic_path([:resend_invite, member]), method: :post, - class: 'btn' + class: 'btn btn-default prepend-left-10' - if member.request? && can?(current_user, action_member_permission(:update, member), member) = link_to icon('check inverse'), polymorphic_path([:approve_access_request, member]), method: :post, - class: 'btn btn-success', + class: 'btn btn-success prepend-left-10', title: 'Grant access' - if can?(current_user, action_member_permission(:destroy, member), member) @@ -68,13 +68,13 @@ = link_to icon('sign-out', text: 'Leave'), polymorphic_path([:leave, member.source, :members]), method: :delete, data: { confirm: leave_confirmation_message(member.source) }, - class: 'btn btn-remove' + class: 'btn btn-remove prepend-left-10' - else = link_to member, remote: true, method: :delete, data: { confirm: remove_member_message(member) }, - class: 'btn btn-remove', + class: 'btn btn-remove prepend-left-10', title: remove_member_title(member) do %span.visible-xs-block Delete diff --git a/app/views/shared/members/_requests.html.haml b/app/views/shared/members/_requests.html.haml index 40b39e850b0..10050adfda5 100644 --- a/app/views/shared/members/_requests.html.haml +++ b/app/views/shared/members/_requests.html.haml @@ -1,8 +1,8 @@ - if requesters.any? .panel.panel-default .panel-heading + Users requesting access to %strong= membership_source.name - access requests %span.badge= requesters.size %ul.content-list = render partial: 'shared/members/member', collection: requesters, as: :member -- cgit v1.2.1 From b3d75ac5135130522f253d4b09f72a7c0a8e2f80 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 2 Sep 2016 09:28:25 +0100 Subject: Return 403 if user can't update group --- app/controllers/projects/group_links_controller.rb | 1 + 1 file changed, 1 insertion(+) (limited to 'app') diff --git a/app/controllers/projects/group_links_controller.rb b/app/controllers/projects/group_links_controller.rb index 57c54bf625a..b5e314dced3 100644 --- a/app/controllers/projects/group_links_controller.rb +++ b/app/controllers/projects/group_links_controller.rb @@ -21,6 +21,7 @@ class Projects::GroupLinksController < Projects::ApplicationController def update @group_link = @project.project_group_links.find(params[:id]) + return render_403 unless can?(current_user, action_member_permission(:admin, @group_link.group), @group_link.group) @group_link.update_attributes(group_link_params) end -- cgit v1.2.1 From cdc55db3452ca82f0dbdcdb631a1fc48abdf1f84 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 2 Sep 2016 10:13:49 +0100 Subject: Fixed members error --- app/controllers/projects/project_members_controller.rb | 2 +- app/models/project_team.rb | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'app') diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb index cd31653698c..617dd9823b9 100644 --- a/app/controllers/projects/project_members_controller.rb +++ b/app/controllers/projects/project_members_controller.rb @@ -6,7 +6,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController def index @groups = @project.project_group_links - @project_members = @project.team.members(can?(current_user, :admin_project, @project)) + @project_members = @project.team.members(!can?(current_user, :admin_project, @project)) @project_members_size = @project_members.size if params[:search].present? diff --git a/app/models/project_team.rb b/app/models/project_team.rb index 57925a0861a..a58c56288dd 100644 --- a/app/models/project_team.rb +++ b/app/models/project_team.rb @@ -52,7 +52,7 @@ class ProjectTeam ProjectMember.truncate_team(project) end - def members(non_invite) + def members(non_invite = false) @members ||= fetch_members(nil, non_invite) end alias_method :users, :members @@ -236,7 +236,7 @@ class ProjectTeam end user_ids = project_members.pluck(:user_id) - user_ids.push(*invited_members.map(&:user_id)) if invited_members.any? && !non_invite + user_ids.push(*invited_members.map(&:user_id)) if invited_members.any? && non_invite user_ids.push(*group_members.pluck(:user_id)) if group User.where(id: user_ids) -- cgit v1.2.1 From 3e19f1976f9a13fc1b13ec49b3ce31c3e114a454 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 2 Sep 2016 10:50:06 +0100 Subject: Fixed issue with invited users not showing up --- app/controllers/projects/project_members_controller.rb | 14 +++++++++++++- app/models/project_team.rb | 8 ++++---- app/views/projects/project_members/_team.html.haml | 5 ++--- 3 files changed, 19 insertions(+), 8 deletions(-) (limited to 'app') diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb index 617dd9823b9..2175a5d8dcb 100644 --- a/app/controllers/projects/project_members_controller.rb +++ b/app/controllers/projects/project_members_controller.rb @@ -6,7 +6,19 @@ class Projects::ProjectMembersController < Projects::ApplicationController def index @groups = @project.project_group_links - @project_members = @project.team.members(!can?(current_user, :admin_project, @project)) + + members = [] + project_members = @project.project_members + project_members = project_members.non_invite unless can?(current_user, :admin_project, @project) + members << project_members.pluck(:id) + + if @project.group + group_members = @project.group.group_members + group_members = group_members.non_invite unless can?(current_user, :admin_project, @project) + members << group_members.pluck(:id) + end + + @project_members = Member.where(id: members) @project_members_size = @project_members.size if params[:search].present? diff --git a/app/models/project_team.rb b/app/models/project_team.rb index a58c56288dd..ab6ea2aae36 100644 --- a/app/models/project_team.rb +++ b/app/models/project_team.rb @@ -52,8 +52,8 @@ class ProjectTeam ProjectMember.truncate_team(project) end - def members(non_invite = false) - @members ||= fetch_members(nil, non_invite) + def members + @members ||= fetch_members end alias_method :users, :members @@ -197,7 +197,7 @@ class ProjectTeam access.each { |key, value| access[key] = [value, capped_access_level].min } end - def fetch_members(level = nil, non_invite = false) + def fetch_members(level = nil) project_members = project.members group_members = group ? group.members : [] invited_members = [] @@ -236,7 +236,7 @@ class ProjectTeam end user_ids = project_members.pluck(:user_id) - user_ids.push(*invited_members.map(&:user_id)) if invited_members.any? && non_invite + user_ids.push(*invited_members.map(&:user_id)) if invited_members.any? user_ids.push(*group_members.pluck(:user_id)) if group User.where(id: user_ids) diff --git a/app/views/projects/project_members/_team.html.haml b/app/views/projects/project_members/_team.html.haml index 2af9fe0519c..867cb2b97e4 100644 --- a/app/views/projects/project_members/_team.html.haml +++ b/app/views/projects/project_members/_team.html.haml @@ -4,6 +4,5 @@ %strong #{@project.name} %span.badge= @project_members_size %ul.content-list - - members.each do |user| - - member = @project.team.find_member(user.id) - = render 'shared/members/member', member: member, user: user + - members.each do |member| + = render 'shared/members/member', member: member -- cgit v1.2.1 From 931d09f481d5e174a984c6f874e67273ba2864f0 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 2 Sep 2016 10:52:17 +0100 Subject: Fixed search --- .../projects/project_members_controller.rb | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) (limited to 'app') diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb index 2175a5d8dcb..7581833eacc 100644 --- a/app/controllers/projects/project_members_controller.rb +++ b/app/controllers/projects/project_members_controller.rb @@ -10,21 +10,30 @@ class Projects::ProjectMembersController < Projects::ApplicationController members = [] project_members = @project.project_members project_members = project_members.non_invite unless can?(current_user, :admin_project, @project) + + if params[:search].present? + users = @project.users.search(params[:search]).to_a + project_members = project_members.where(user_id: users) + end + members << project_members.pluck(:id) - if @project.group - group_members = @project.group.group_members + @group = @project.group + if @group + group_members = @group.group_members group_members = group_members.non_invite unless can?(current_user, :admin_project, @project) + + if params[:search].present? + users = @group.users.search(params[:search]).to_a + group_members = group_members.where(user_id: users) + end + members << group_members.pluck(:id) end @project_members = Member.where(id: members) @project_members_size = @project_members.size - if params[:search].present? - @project_members = @project_members.search(params[:search]) - end - @project_members = @project_members.page(params[:page]) @requesters = @project.requesters if can?(current_user, :admin_project, @project) -- cgit v1.2.1 From 999f18480511d81b1499b502cbc89a5b34e54544 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 2 Sep 2016 13:35:43 +0100 Subject: Tests update --- app/views/projects/project_members/_groups.html.haml | 2 +- app/views/projects/project_members/_new_project_member.html.haml | 2 +- app/views/shared/members/_member.html.haml | 2 ++ 3 files changed, 4 insertions(+), 2 deletions(-) (limited to 'app') diff --git a/app/views/projects/project_members/_groups.html.haml b/app/views/projects/project_members/_groups.html.haml index 79791af7963..340e4cd06b8 100644 --- a/app/views/projects/project_members/_groups.html.haml +++ b/app/views/projects/project_members/_groups.html.haml @@ -1,4 +1,4 @@ -.panel.panel-default +.panel.panel-default.project-members-groups .panel-heading Groups with access to %strong #{@project.name} diff --git a/app/views/projects/project_members/_new_project_member.html.haml b/app/views/projects/project_members/_new_project_member.html.haml index c0b187fb460..26e06a14c07 100644 --- a/app/views/projects/project_members/_new_project_member.html.haml +++ b/app/views/projects/project_members/_new_project_member.html.haml @@ -1,4 +1,4 @@ -= form_for @project_member, as: :project_member, url: namespace_project_project_members_path(@project.namespace, @project) do |f| += form_for @project_member, as: :project_member, url: namespace_project_project_members_path(@project.namespace, @project), html: { class: 'users-project-form' } do |f| .row .col-md-4.col-lg-6 = users_select_tag(:user_ids, multiple: true, class: "input-full", scope: :all, email_user: true) diff --git a/app/views/shared/members/_member.html.haml b/app/views/shared/members/_member.html.haml index 2d4853eef92..2f98eeff658 100644 --- a/app/views/shared/members/_member.html.haml +++ b/app/views/shared/members/_member.html.haml @@ -79,3 +79,5 @@ %span.visible-xs-block Delete = icon('trash', class: 'hidden-xs') + - else + %span.member-access-text= member.human_access -- cgit v1.2.1 From a56216c8bd1ef82c09c7ce39596e4b6436ebb7fa Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 2 Sep 2016 14:06:20 +0100 Subject: Added import button back in --- app/views/projects/project_members/index.html.haml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'app') diff --git a/app/views/projects/project_members/index.html.haml b/app/views/projects/project_members/index.html.haml index abe10433387..d289d75454d 100644 --- a/app/views/projects/project_members/index.html.haml +++ b/app/views/projects/project_members/index.html.haml @@ -1,15 +1,15 @@ - page_title "Members" .project-members-page.js-project-members-page.prepend-top-default - %h4 + %h4.clearfix Members + = link_to "Import", import_namespace_project_project_members_path(@project.namespace, @project), class: "btn btn-default pull-right hidden-xs", title: "Import members from another project" %hr - if can?(current_user, :admin_project_member, @project) .project-members-new.append-bottom-default %h5.clearfix Add new user to %strong= @project.name - -# = link_to "Import", import_namespace_project_project_members_path(@project.namespace, @project), class: "btn btn-default pull-right", title: "Import members from another project" = render "new_project_member" = render 'shared/members/requests', membership_source: @project, requesters: @requesters -- cgit v1.2.1 From ccf76831da422298242ce3d8d11f72ab50454c85 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 2 Sep 2016 14:19:11 +0100 Subject: Updated groups member UI to match --- app/assets/javascripts/dispatcher.js | 4 +- app/assets/javascripts/groups.js | 13 ------- app/assets/javascripts/members.js.es6 | 45 ++++++++++++++++++++++ app/assets/javascripts/project_members.js.es6 | 45 ---------------------- .../group_members/_new_group_member.html.haml | 35 ++++++++--------- app/views/groups/group_members/index.html.haml | 40 +++++++++---------- app/views/projects/project_members/index.html.haml | 2 +- 7 files changed, 81 insertions(+), 103 deletions(-) delete mode 100644 app/assets/javascripts/groups.js create mode 100644 app/assets/javascripts/members.js.es6 delete mode 100644 app/assets/javascripts/project_members.js.es6 (limited to 'app') diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index c95aaf61443..da3757d8992 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -127,12 +127,12 @@ break; case 'groups:group_members:index': new gl.MemberExpirationDate(); - new GroupMembers(); + new gl.Members(); new UsersSelect(); break; case 'projects:project_members:index': new gl.MemberExpirationDate(); - new gl.ProjectMembers(); + new gl.Members(); new UsersSelect(); break; case 'groups:new': diff --git a/app/assets/javascripts/groups.js b/app/assets/javascripts/groups.js deleted file mode 100644 index 4382dd6860f..00000000000 --- a/app/assets/javascripts/groups.js +++ /dev/null @@ -1,13 +0,0 @@ -(function() { - this.GroupMembers = (function() { - function GroupMembers() { - $('li.group_member').bind('ajax:success', function() { - return $(this).fadeOut(); - }); - } - - return GroupMembers; - - })(); - -}).call(this); diff --git a/app/assets/javascripts/members.js.es6 b/app/assets/javascripts/members.js.es6 new file mode 100644 index 00000000000..fa259520810 --- /dev/null +++ b/app/assets/javascripts/members.js.es6 @@ -0,0 +1,45 @@ +((w) => { + w.gl = w.gl || {}; + + class Members { + constructor() { + this.removeListeners(); + this.addListeners(); + } + + removeListeners() { + $('.project_member, .group_member').off('ajax:success'); + $('.js-member-update-control').off('change'); + $('.js-edit-member-form').off('ajax:success'); + } + + addListeners() { + $('.project_member, .group_member').on('ajax:success', this.removeRow); + $('.js-member-update-control').on('change', function () { + $(this).closest('form') + .trigger("submit.rails"); + $(this).disable(); + }); + $('.js-edit-member-form').on('ajax:success', function () { + $(this).find('.js-member-update-control').enable(); + }); + } + + removeRow(e) { + const $target = $(e.target); + + if ($target.hasClass('btn-remove')) { + $target.closest('.member') + .fadeOut(function () { + $(this).remove(); + }); + } + } + + submitForm() { + + } + } + + gl.Members = Members; +})(window); diff --git a/app/assets/javascripts/project_members.js.es6 b/app/assets/javascripts/project_members.js.es6 deleted file mode 100644 index 56bc98d1076..00000000000 --- a/app/assets/javascripts/project_members.js.es6 +++ /dev/null @@ -1,45 +0,0 @@ -((w) => { - w.gl = w.gl || {}; - - class ProjectMembers { - constructor() { - this.removeListeners(); - this.addListeners(); - } - - removeListeners() { - $('.project_member, .group_member').off('ajax:success'); - $('.js-member-update-control').off('change'); - $('.js-edit-member-form').off('ajax:success'); - } - - addListeners() { - $('.project_member, .group_member').on('ajax:success', this.removeRow); - $('.js-member-update-control').on('change', function () { - $(this).closest('form') - .trigger("submit.rails"); - $(this).disable(); - }); - $('.js-edit-member-form').on('ajax:success', function () { - $(this).find('.js-member-update-control').enable(); - }); - } - - removeRow(e) { - const $target = $(e.target); - - if ($target.hasClass('btn-remove')) { - $target.closest('.member') - .fadeOut(function () { - $(this).remove(); - }); - } - } - - submitForm() { - - } - } - - gl.ProjectMembers = ProjectMembers; -})(window); diff --git a/app/views/groups/group_members/_new_group_member.html.haml b/app/views/groups/group_members/_new_group_member.html.haml index 2fb3190ab11..2987befd2a4 100644 --- a/app/views/groups/group_members/_new_group_member.html.haml +++ b/app/views/groups/group_members/_new_group_member.html.haml @@ -1,27 +1,22 @@ -= form_for @group_member, url: group_group_members_path(@group), html: { class: 'form-horizontal users-group-form' } do |f| - .form-group - = f.label :user_ids, "People", class: 'control-label' - .col-sm-10 - = users_select_tag(:user_ids, multiple: true, class: 'input-large', scope: :all, email_user: true) - .help-block += form_for @group_member, url: group_group_members_path(@group), html: { class: 'users-project-form users-group-form' } do |f| + .row + .col-md-4.col-lg-6 + = users_select_tag(:user_ids, multiple: true, class: 'input-full', scope: :all, email_user: true) + .help-block.append-bottom-10 Search for users by name, username, or email, or invite new ones using their email address. - .form-group - = f.label :access_level, "Group Access", class: 'control-label' - .col-sm-10 - = select_tag :access_level, options_for_select(GroupMember.access_level_roles, @group_member.access_level), class: "project-access-select select2" - .help-block - Read more about role permissions - %strong= link_to "here", help_page_path("user/permissions"), class: "vlink" + .col-md-3.col-lg-2 + = select_tag :access_level, options_for_select(GroupMember.access_level_roles, @group_member.access_level), class: "form-control project-access-select" + .help-block.append-bottom-10 + = link_to "Read more", help_page_path("user/permissions"), class: "vlink" + about role permissions - .form-group - = f.label :expires_at, 'Access expiration date', class: 'control-label' - .col-sm-10 + .col-md-3.col-lg-2 .clearable-input - = text_field_tag :expires_at, nil, class: 'form-control js-access-expiration-date', placeholder: 'Select access expiration date' + = text_field_tag :expires_at, nil, class: 'form-control js-access-expiration-date', placeholder: 'Expiration date' %i.clear-icon.js-clear-input - .help-block + .help-block.append-bottom-10 On this date, the user(s) will automatically lose access to this group and all of its projects. - .form-actions - = f.submit 'Add users to group', class: "btn btn-create" + .col-md-2 + = f.submit 'Add to group', class: "btn btn-create btn-block" diff --git a/app/views/groups/group_members/index.html.haml b/app/views/groups/group_members/index.html.haml index f789796e942..d2c7ec2e821 100644 --- a/app/views/groups/group_members/index.html.haml +++ b/app/views/groups/group_members/index.html.haml @@ -1,35 +1,31 @@ - page_title "Members" -.group-members-page.prepend-top-default +.project-members-page.prepend-top-default + %h4 + Members + %hr - if can?(current_user, :admin_group_member, @group) - .panel.panel-default - .panel-heading - Add new user to group - .panel-body - %p.light - Members of group have access to all group projects. - .new-group-member-holder - = render "new_group_member" + .project-members-new.append-bottom-default + %h5.clearfix + Add new user to + %strong= @group.name + = render "new_group_member" = render 'shared/members/requests', membership_source: @group, requesters: @requesters + .append-bottom-default.clearfix + %h5.member.existing-title + Existing users + = form_tag group_group_members_path(@group), method: :get, class: 'form-inline member-search-form' do + .form-group + = search_field_tag :search, params[:search], { placeholder: 'Find existing members by name', class: 'form-control', spellcheck: false } + %button.member-search-btn{ type: "submit", "aria-label" => "Submit search" } + = icon("search") .panel.panel-default .panel-heading + Users with access to %strong #{@group.name} - group members %span.badge= @members.total_count - .controls - = form_tag group_group_members_path(@group), method: :get, class: 'form-inline member-search-form' do - .form-group - = search_field_tag :search, params[:search], { placeholder: 'Find existing member by name', class: 'form-control', spellcheck: false } - = button_tag class: 'btn', title: 'Search' do - = icon("search") %ul.content-list = render partial: 'shared/members/member', collection: @members, as: :member = paginate @members, theme: 'gitlab' - -:javascript - $('form.member-search-form').on('submit', function(event) { - event.preventDefault(); - Turbolinks.visit(this.action + '?' + $(this).serialize()); - }); diff --git a/app/views/projects/project_members/index.html.haml b/app/views/projects/project_members/index.html.haml index d289d75454d..a90de32bd47 100644 --- a/app/views/projects/project_members/index.html.haml +++ b/app/views/projects/project_members/index.html.haml @@ -1,6 +1,6 @@ - page_title "Members" -.project-members-page.js-project-members-page.prepend-top-default +.project-members-page.prepend-top-default %h4.clearfix Members = link_to "Import", import_namespace_project_project_members_path(@project.namespace, @project), class: "btn btn-default pull-right hidden-xs", title: "Import members from another project" -- cgit v1.2.1 From 2c3fa33ca2e75792d1027eb73e2f69fed67bc435 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 2 Sep 2016 15:10:22 +0100 Subject: Updated some specs Fixed issue with group name not showing --- .../projects/project_members_controller.rb | 19 +++++++++---------- app/views/projects/project_members/_team.html.haml | 2 +- app/views/projects/project_members/index.html.haml | 4 ++-- app/views/shared/members/_member.html.haml | 7 ++++--- 4 files changed, 16 insertions(+), 16 deletions(-) (limited to 'app') diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb index 7581833eacc..bf6ac25266a 100644 --- a/app/controllers/projects/project_members_controller.rb +++ b/app/controllers/projects/project_members_controller.rb @@ -7,7 +7,6 @@ class Projects::ProjectMembersController < Projects::ApplicationController def index @groups = @project.project_group_links - members = [] project_members = @project.project_members project_members = project_members.non_invite unless can?(current_user, :admin_project, @project) @@ -16,25 +15,25 @@ class Projects::ProjectMembersController < Projects::ApplicationController project_members = project_members.where(user_id: users) end - members << project_members.pluck(:id) + members_ids = project_members.pluck(:id) - @group = @project.group - if @group - group_members = @group.group_members + group = @project.group + if group + group_members = group.group_members group_members = group_members.non_invite unless can?(current_user, :admin_project, @project) if params[:search].present? - users = @group.users.search(params[:search]).to_a + users = group.users.search(params[:search]).to_a group_members = group_members.where(user_id: users) end - members << group_members.pluck(:id) + members_ids << group_members.pluck(:id) end - @project_members = Member.where(id: members) - @project_members_size = @project_members.size + @members = Member.where(id: members_ids.flatten) + @members_size = @members.size - @project_members = @project_members.page(params[:page]) + @members = @members.page(params[:page]) @requesters = @project.requesters if can?(current_user, :admin_project, @project) diff --git a/app/views/projects/project_members/_team.html.haml b/app/views/projects/project_members/_team.html.haml index 867cb2b97e4..d9799033e17 100644 --- a/app/views/projects/project_members/_team.html.haml +++ b/app/views/projects/project_members/_team.html.haml @@ -2,7 +2,7 @@ .panel-heading Users with access to %strong #{@project.name} - %span.badge= @project_members_size + %span.badge= @members_size %ul.content-list - members.each do |member| = render 'shared/members/member', member: member diff --git a/app/views/projects/project_members/index.html.haml b/app/views/projects/project_members/index.html.haml index a90de32bd47..80882d0c11c 100644 --- a/app/views/projects/project_members/index.html.haml +++ b/app/views/projects/project_members/index.html.haml @@ -25,5 +25,5 @@ - if @groups.size > 0 = render 'groups', groups: @groups - = render 'team', members: @project_members - = paginate @project_members, theme: "gitlab" + = render 'team', members: @members + = paginate @members, theme: "gitlab" diff --git a/app/views/shared/members/_member.html.haml b/app/views/shared/members/_member.html.haml index 2f98eeff658..6f8c3c4da2e 100644 --- a/app/views/shared/members/_member.html.haml +++ b/app/views/shared/members/_member.html.haml @@ -1,6 +1,7 @@ - show_roles = local_assigns.fetch(:show_roles, true) - show_controls = local_assigns.fetch(:show_controls, true) - user = local_assigns.fetch(:user, member.user) +- source = member.source %li.member{ class: dom_class(member), id: dom_id(member) } %span{ class: ("list-item-name" if show_controls) } @@ -17,9 +18,9 @@ %label.label.label-danger %strong Blocked - - if member.respond_to?(:group) && !@group - = link_to member.group, class: "member-group-link prepend-left-5" do - = "· #{member.group.name}" + - if source.instance_of?(Group) && !@group + = link_to source, class: "member-group-link prepend-left-5" do + = "· #{source.name}" .hidden-xs.cgray - if member.request? -- cgit v1.2.1 From 97a51817bf1e6b0504bb84b686daf7e931ded2da Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 2 Sep 2016 17:39:16 +0100 Subject: Fixed error when updating groups --- app/controllers/projects/group_links_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app') diff --git a/app/controllers/projects/group_links_controller.rb b/app/controllers/projects/group_links_controller.rb index b5e314dced3..3574ecf2811 100644 --- a/app/controllers/projects/group_links_controller.rb +++ b/app/controllers/projects/group_links_controller.rb @@ -21,7 +21,7 @@ class Projects::GroupLinksController < Projects::ApplicationController def update @group_link = @project.project_group_links.find(params[:id]) - return render_403 unless can?(current_user, action_member_permission(:admin, @group_link.group), @group_link.group) + return render_403 unless can?(current_user, :admin_group, @group_link.group) @group_link.update_attributes(group_link_params) end -- cgit v1.2.1 From e747626fad5c0e675d6a5cd5b6fcd482f10dad90 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 5 Sep 2016 09:24:47 +0100 Subject: Added test for updating groups permissions --- app/controllers/projects/group_links_controller.rb | 2 +- app/views/shared/members/_group.html.haml | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'app') diff --git a/app/controllers/projects/group_links_controller.rb b/app/controllers/projects/group_links_controller.rb index 3574ecf2811..7b4c39cdb8f 100644 --- a/app/controllers/projects/group_links_controller.rb +++ b/app/controllers/projects/group_links_controller.rb @@ -21,7 +21,7 @@ class Projects::GroupLinksController < Projects::ApplicationController def update @group_link = @project.project_group_links.find(params[:id]) - return render_403 unless can?(current_user, :admin_group, @group_link.group) + return render_403 unless can?(current_user, :admin_project_member, @project) @group_link.update_attributes(group_link_params) end diff --git a/app/views/shared/members/_group.html.haml b/app/views/shared/members/_group.html.haml index 19b58ef20ae..56d31a949ff 100644 --- a/app/views/shared/members/_group.html.haml +++ b/app/views/shared/members/_group.html.haml @@ -13,11 +13,11 @@ Expires in #{distance_of_time_in_words_to_now(group_link.expires_at)} .controls.member-controls = form_tag namespace_project_group_link_path(@project.namespace, @project, group_link), method: :put, remote: true, class: 'form-horizontal js-edit-member-form' do - = select_tag 'group_link[group_access]', options_for_select(ProjectGroupLink.access_options, group_link.group_access), class: 'form-control member-form-control append-right-5 js-member-update-control', id: "member_access_level_#{group.id}", disabled: !can?(current_user, action_member_permission(:admin, group), group) + = select_tag 'group_link[group_access]', options_for_select(ProjectGroupLink.access_options, group_link.group_access), class: 'form-control member-form-control append-right-5 js-member-update-control', id: "member_access_level_#{group.id}", disabled: !can?(current_user, :admin_project_member, @project) .prepend-left-5.clearable-input.member-form-control - = text_field_tag 'group_link[expires_at]', group_link.expires_at, class: 'form-control js-access-expiration-date js-member-update-control', placeholder: 'Expiration date', id: "member_expires_at_#{group.id}", disabled: !can?(current_user, action_member_permission(:admin, group), group) + = text_field_tag 'group_link[expires_at]', group_link.expires_at, class: 'form-control js-access-expiration-date js-member-update-control', placeholder: 'Expiration date', id: "member_expires_at_#{group.id}", disabled: !can?(current_user, :admin_project_member, @project) %i.clear-icon.js-clear-input - - if can?(current_user, action_member_permission(:admin, group), group) + - if can?(current_user, :admin_project_member, @project) = link_to namespace_project_group_link_path(@project.namespace, @project, group_link), remote: true, method: :delete, -- cgit v1.2.1 From fe71edc3336ae662997ebbad3b4c46b2a2b4927c Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 5 Sep 2016 15:11:45 +0100 Subject: JS update --- app/assets/javascripts/members.js.es6 | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/members.js.es6 b/app/assets/javascripts/members.js.es6 index fa259520810..7986987e49a 100644 --- a/app/assets/javascripts/members.js.es6 +++ b/app/assets/javascripts/members.js.es6 @@ -15,14 +15,8 @@ addListeners() { $('.project_member, .group_member').on('ajax:success', this.removeRow); - $('.js-member-update-control').on('change', function () { - $(this).closest('form') - .trigger("submit.rails"); - $(this).disable(); - }); - $('.js-edit-member-form').on('ajax:success', function () { - $(this).find('.js-member-update-control').enable(); - }); + $('.js-member-update-control').on('change', this.formSubmit); + $('.js-edit-member-form').on('ajax:success', this.formSuccess); } removeRow(e) { @@ -36,8 +30,16 @@ } } - submitForm() { + formSubmit() { + const $this = $(this); + $this.disable() + .closest('form') + .trigger("submit.rails"); + } + + formSuccess() { + $(this).find('.js-member-update-control').enable(); } } -- cgit v1.2.1 From 3354bfd8f92ba034816890f0ea89bca630405103 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 5 Sep 2016 15:45:23 +0100 Subject: CSS cleanup --- app/assets/stylesheets/framework/selects.scss | 4 ---- app/assets/stylesheets/pages/members.scss | 10 +++------- app/views/groups/group_members/_new_group_member.html.haml | 2 +- app/views/groups/group_members/index.html.haml | 2 +- .../projects/project_members/_new_project_member.html.haml | 2 +- app/views/projects/project_members/index.html.haml | 2 +- 6 files changed, 7 insertions(+), 15 deletions(-) (limited to 'app') diff --git a/app/assets/stylesheets/framework/selects.scss b/app/assets/stylesheets/framework/selects.scss index 746ab89abd2..b309e2ad9f4 100644 --- a/app/assets/stylesheets/framework/selects.scss +++ b/app/assets/stylesheets/framework/selects.scss @@ -191,10 +191,6 @@ &.input-clamp { max-width: 100%; } - - &.input-full { - width: 100%; - } } .select2-highlighted { diff --git a/app/assets/stylesheets/pages/members.scss b/app/assets/stylesheets/pages/members.scss index a7f1324f69a..72f31cb1168 100644 --- a/app/assets/stylesheets/pages/members.scss +++ b/app/assets/stylesheets/pages/members.scss @@ -1,9 +1,3 @@ -.project-members-new { - > h5 { - font-weight: normal; - } -} - .member { .list-item-name { float: none; @@ -16,6 +10,7 @@ .controls { @media (min-width: $screen-sm-min) { + display: -webkit-flex; display: flex; width: 400px; max-width: 50%; @@ -26,8 +21,9 @@ margin-top: 5px; @media (min-width: $screen-sm-min) { + display: -webkit-flex; display: flex; - flex: 1; + width: 100%; margin-top: 3px; } } diff --git a/app/views/groups/group_members/_new_group_member.html.haml b/app/views/groups/group_members/_new_group_member.html.haml index 2987befd2a4..b185b81db7f 100644 --- a/app/views/groups/group_members/_new_group_member.html.haml +++ b/app/views/groups/group_members/_new_group_member.html.haml @@ -1,7 +1,7 @@ = form_for @group_member, url: group_group_members_path(@group), html: { class: 'users-project-form users-group-form' } do |f| .row .col-md-4.col-lg-6 - = users_select_tag(:user_ids, multiple: true, class: 'input-full', scope: :all, email_user: true) + = users_select_tag(:user_ids, multiple: true, class: 'input-clamp', scope: :all, email_user: true) .help-block.append-bottom-10 Search for users by name, username, or email, or invite new ones using their email address. diff --git a/app/views/groups/group_members/index.html.haml b/app/views/groups/group_members/index.html.haml index d2c7ec2e821..ebf9aca7700 100644 --- a/app/views/groups/group_members/index.html.haml +++ b/app/views/groups/group_members/index.html.haml @@ -6,7 +6,7 @@ %hr - if can?(current_user, :admin_group_member, @group) .project-members-new.append-bottom-default - %h5.clearfix + %p.clearfix Add new user to %strong= @group.name = render "new_group_member" diff --git a/app/views/projects/project_members/_new_project_member.html.haml b/app/views/projects/project_members/_new_project_member.html.haml index 26e06a14c07..79dcd7a6ee9 100644 --- a/app/views/projects/project_members/_new_project_member.html.haml +++ b/app/views/projects/project_members/_new_project_member.html.haml @@ -1,7 +1,7 @@ = form_for @project_member, as: :project_member, url: namespace_project_project_members_path(@project.namespace, @project), html: { class: 'users-project-form' } do |f| .row .col-md-4.col-lg-6 - = users_select_tag(:user_ids, multiple: true, class: "input-full", scope: :all, email_user: true) + = users_select_tag(:user_ids, multiple: true, class: "input-clamp", scope: :all, email_user: true) .help-block.append-bottom-10 Search for users by name, username, or email, or invite new ones using their email address. diff --git a/app/views/projects/project_members/index.html.haml b/app/views/projects/project_members/index.html.haml index 80882d0c11c..86b2752cc0b 100644 --- a/app/views/projects/project_members/index.html.haml +++ b/app/views/projects/project_members/index.html.haml @@ -7,7 +7,7 @@ %hr - if can?(current_user, :admin_project_member, @project) .project-members-new.append-bottom-default - %h5.clearfix + %p.clearfix Add new user to %strong= @project.name = render "new_project_member" -- cgit v1.2.1 From 68c7b52364307e26ef7a85b80aa11242abbfa5b6 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 5 Sep 2016 17:14:42 +0100 Subject: Fixed fields not being sent --- app/assets/javascripts/members.js.es6 | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/members.js.es6 b/app/assets/javascripts/members.js.es6 index 7986987e49a..12d212ca185 100644 --- a/app/assets/javascripts/members.js.es6 +++ b/app/assets/javascripts/members.js.es6 @@ -33,9 +33,10 @@ formSubmit() { const $this = $(this); - $this.disable() - .closest('form') + $this.closest('form') .trigger("submit.rails"); + + $this.disable(); } formSuccess() { -- cgit v1.2.1 From 73c4da1780c5086543eb998d5bc9cbd632ef6576 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 6 Sep 2016 16:20:20 +0100 Subject: Fixed removing groups --- app/controllers/projects/group_links_controller.rb | 7 ++++++- app/views/shared/members/_group.html.haml | 3 ++- 2 files changed, 8 insertions(+), 2 deletions(-) (limited to 'app') diff --git a/app/controllers/projects/group_links_controller.rb b/app/controllers/projects/group_links_controller.rb index 7b4c39cdb8f..7b6f07465e0 100644 --- a/app/controllers/projects/group_links_controller.rb +++ b/app/controllers/projects/group_links_controller.rb @@ -29,7 +29,12 @@ class Projects::GroupLinksController < Projects::ApplicationController def destroy project.project_group_links.find(params[:id]).destroy - redirect_to namespace_project_group_links_path(project.namespace, project) + respond_to do |format| + format.html do + redirect_to namespace_project_group_links_path(project.namespace, project) + end + format.js { head :ok } + end end protected diff --git a/app/views/shared/members/_group.html.haml b/app/views/shared/members/_group.html.haml index 56d31a949ff..171a388b233 100644 --- a/app/views/shared/members/_group.html.haml +++ b/app/views/shared/members/_group.html.haml @@ -1,6 +1,6 @@ - group = local_assigns[:group] - group_link = local_assigns[:group_link] -%li.member{ class: dom_class(group), id: dom_id(group) } +%li.member.group_member{ id: "group_member_#{group_link.id}" } %span{ class: "list-item-name" } = image_tag group_icon(group), class: "avatar s40", alt: '' %strong @@ -21,6 +21,7 @@ = link_to namespace_project_group_link_path(@project.namespace, @project, group_link), remote: true, method: :delete, + data: { confirm: "Are you sure you want to remove #{group.name}?" }, class: 'btn btn-remove prepend-left-10' do %span.visible-xs-block Delete -- cgit v1.2.1 From 2b41db9215f322ba61113a7bef2f49da157bbd53 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 6 Sep 2016 16:48:48 +0100 Subject: Search project groups --- .../projects/project_members_controller.rb | 49 ++++++++++++++++++---- 1 file changed, 41 insertions(+), 8 deletions(-) (limited to 'app') diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb index bf6ac25266a..ac83377148a 100644 --- a/app/controllers/projects/project_members_controller.rb +++ b/app/controllers/projects/project_members_controller.rb @@ -10,23 +10,30 @@ class Projects::ProjectMembersController < Projects::ApplicationController project_members = @project.project_members project_members = project_members.non_invite unless can?(current_user, :admin_project, @project) - if params[:search].present? - users = @project.users.search(params[:search]).to_a - project_members = project_members.where(user_id: users) - end - - members_ids = project_members.pluck(:id) - group = @project.group + if group group_members = group.group_members group_members = group_members.non_invite unless can?(current_user, :admin_project, @project) + end + + if params[:search].present? + groups_id = @groups.pluck(:group_id) + groups = Group.where(id: groups_id).search(params[:search]).to_a + @groups = @project.project_group_links.where(group_id: groups) + + users = @project.users.search(params[:search]).to_a + project_members = project_members.where(user_id: users) - if params[:search].present? + if group_members users = group.users.search(params[:search]).to_a group_members = group_members.where(user_id: users) end + end + members_ids = project_members.pluck(:id) + + if group_members members_ids << group_members.pluck(:id) end @@ -48,6 +55,17 @@ class Projects::ProjectMembersController < Projects::ApplicationController current_user: current_user ) + group_ids = params[:group_ids].split(',') + groups = Group.where(id: group_ids) + + groups.each do |group| + project.project_group_links.create( + group: group, + group_access: params[:access_level], + expires_at: params[:expires_at] + ) + end + redirect_to namespace_project_project_members_path(@project.namespace, @project) end @@ -101,6 +119,21 @@ class Projects::ProjectMembersController < Projects::ApplicationController notice: notice) end + def options + users = User.all + users = users.search(params[:search]) if params[:search].present? + users = users.page(1) + + groups = Group.all + groups = groups.search(params[:search]) if params[:search].present? + groups = groups.page(1) + + render json: { + Groups: groups.as_json(only: [:id, :name], methods: [:avatar_url]), + Users: users.as_json(only: [:id, :name, :username], methods: [:avatar_url]), + } + end + protected def member_params -- cgit v1.2.1 From 401b797671b9b67ef40c4afa75acdeca83b6a6de Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 6 Sep 2016 17:11:58 +0100 Subject: Fixed bug when group_ids not present when creating --- .../projects/project_members_controller.rb | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) (limited to 'app') diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb index ac83377148a..d49598d2786 100644 --- a/app/controllers/projects/project_members_controller.rb +++ b/app/controllers/projects/project_members_controller.rb @@ -55,15 +55,17 @@ class Projects::ProjectMembersController < Projects::ApplicationController current_user: current_user ) - group_ids = params[:group_ids].split(',') - groups = Group.where(id: group_ids) - - groups.each do |group| - project.project_group_links.create( - group: group, - group_access: params[:access_level], - expires_at: params[:expires_at] - ) + if params[:group_ids].present? + group_ids = params[:group_ids].split(',') + groups = Group.where(id: group_ids) + + groups.each do |group| + project.project_group_links.create( + group: group, + group_access: params[:access_level], + expires_at: params[:expires_at] + ) + end end redirect_to namespace_project_project_members_path(@project.namespace, @project) -- cgit v1.2.1 From ecf7640b28562468880dda97ba42e5fd18c0859f Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 7 Sep 2016 10:58:52 +0100 Subject: Fixed group_links expire date not updating in view --- app/views/projects/group_links/update.js.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app') diff --git a/app/views/projects/group_links/update.js.haml b/app/views/projects/group_links/update.js.haml index d3a37847f58..231d5a79723 100644 --- a/app/views/projects/group_links/update.js.haml +++ b/app/views/projects/group_links/update.js.haml @@ -1,3 +1,3 @@ :plain var $listItem = $('#{escape_javascript(render('shared/members/group', group_link: @group_link, group: @group_link.group))}'); - $("##{dom_id(@group_link.group)} .list-item-name").replaceWith($listItem.find('.list-item-name')); + $("#group_member_#{@group_link.id} .list-item-name").replaceWith($listItem.find('.list-item-name')); -- cgit v1.2.1 From d8fee09e338006acb09c80ebcb032b6a75f3d7cd Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 7 Sep 2016 12:36:21 +0100 Subject: Fixed jQuery chaining --- app/assets/javascripts/members.js.es6 | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/members.js.es6 b/app/assets/javascripts/members.js.es6 index 12d212ca185..1b4b3f38838 100644 --- a/app/assets/javascripts/members.js.es6 +++ b/app/assets/javascripts/members.js.es6 @@ -31,12 +31,10 @@ } formSubmit() { - const $this = $(this); - - $this.closest('form') - .trigger("submit.rails"); - - $this.disable(); + $(this).closest('form') + .trigger("submit.rails") + .end() + .disable(); } formSuccess() { -- cgit v1.2.1 From 2abbb0980f061d4297aab02f914c324c7fbe073b Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 7 Sep 2016 14:26:07 +0100 Subject: Removed group members from the list for now --- .../projects/project_members_controller.rb | 25 ---------------------- 1 file changed, 25 deletions(-) (limited to 'app') diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb index d49598d2786..64cbc76da6f 100644 --- a/app/controllers/projects/project_members_controller.rb +++ b/app/controllers/projects/project_members_controller.rb @@ -10,33 +10,8 @@ class Projects::ProjectMembersController < Projects::ApplicationController project_members = @project.project_members project_members = project_members.non_invite unless can?(current_user, :admin_project, @project) - group = @project.group - - if group - group_members = group.group_members - group_members = group_members.non_invite unless can?(current_user, :admin_project, @project) - end - - if params[:search].present? - groups_id = @groups.pluck(:group_id) - groups = Group.where(id: groups_id).search(params[:search]).to_a - @groups = @project.project_group_links.where(group_id: groups) - - users = @project.users.search(params[:search]).to_a - project_members = project_members.where(user_id: users) - - if group_members - users = group.users.search(params[:search]).to_a - group_members = group_members.where(user_id: users) - end - end - members_ids = project_members.pluck(:id) - if group_members - members_ids << group_members.pluck(:id) - end - @members = Member.where(id: members_ids.flatten) @members_size = @members.size -- cgit v1.2.1 From 7cca8ffe60f4cdc7ca012cf223c6d7855b928685 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 8 Sep 2016 10:45:53 +0100 Subject: Ruby update --- app/controllers/projects/project_members_controller.rb | 14 +++++++------- app/views/projects/project_members/_team.html.haml | 2 +- app/views/projects/project_members/index.html.haml | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) (limited to 'app') diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb index 64cbc76da6f..1c49ebfb99d 100644 --- a/app/controllers/projects/project_members_controller.rb +++ b/app/controllers/projects/project_members_controller.rb @@ -7,15 +7,15 @@ class Projects::ProjectMembersController < Projects::ApplicationController def index @groups = @project.project_group_links - project_members = @project.project_members - project_members = project_members.non_invite unless can?(current_user, :admin_project, @project) + @project_members = @project.project_members + @project_members = @project_members.non_invite unless can?(current_user, :admin_project, @project) - members_ids = project_members.pluck(:id) - - @members = Member.where(id: members_ids.flatten) - @members_size = @members.size + if params[:search].present? + users = @project.users.search(params[:search]).to_a + @project_members = @project_members.where(user_id: users) + end - @members = @members.page(params[:page]) + @project_members = @project_members.page(params[:page]) @requesters = @project.requesters if can?(current_user, :admin_project, @project) diff --git a/app/views/projects/project_members/_team.html.haml b/app/views/projects/project_members/_team.html.haml index d9799033e17..ff54035cfe1 100644 --- a/app/views/projects/project_members/_team.html.haml +++ b/app/views/projects/project_members/_team.html.haml @@ -2,7 +2,7 @@ .panel-heading Users with access to %strong #{@project.name} - %span.badge= @members_size + %span.badge= @project_members.total_count %ul.content-list - members.each do |member| = render 'shared/members/member', member: member diff --git a/app/views/projects/project_members/index.html.haml b/app/views/projects/project_members/index.html.haml index 86b2752cc0b..f566748e95a 100644 --- a/app/views/projects/project_members/index.html.haml +++ b/app/views/projects/project_members/index.html.haml @@ -25,5 +25,5 @@ - if @groups.size > 0 = render 'groups', groups: @groups - = render 'team', members: @members - = paginate @members, theme: "gitlab" + = render 'team', members: @project_members + = paginate @project_members, theme: "gitlab" -- cgit v1.2.1 From b61cd8a3930f194333ea417a03d53a0ad91efa42 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 9 Sep 2016 10:45:55 +0100 Subject: Added back ordering --- app/controllers/projects/project_members_controller.rb | 1 + 1 file changed, 1 insertion(+) (limited to 'app') diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb index 1c49ebfb99d..5fd2e77a51a 100644 --- a/app/controllers/projects/project_members_controller.rb +++ b/app/controllers/projects/project_members_controller.rb @@ -15,6 +15,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController @project_members = @project_members.where(user_id: users) end + @project_members = @project_members.order('access_level DESC') @project_members = @project_members.page(params[:page]) @requesters = @project.requesters if can?(current_user, :admin_project, @project) -- cgit v1.2.1 From aac80d76c272523a1ee7c9ef751034e955dfab9e Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 9 Sep 2016 11:31:57 +0100 Subject: Group links search test fix --- app/controllers/projects/project_members_controller.rb | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'app') diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb index 5fd2e77a51a..4d27617608d 100644 --- a/app/controllers/projects/project_members_controller.rb +++ b/app/controllers/projects/project_members_controller.rb @@ -13,6 +13,10 @@ class Projects::ProjectMembersController < Projects::ApplicationController if params[:search].present? users = @project.users.search(params[:search]).to_a @project_members = @project_members.where(user_id: users) + + group_ids = @groups.pluck(:group_id) + group_ids = Group.where(id: group_ids).search(params[:search]).to_a + @groups = @project.project_group_links.where(group_id: group_ids) end @project_members = @project_members.order('access_level DESC') -- cgit v1.2.1 From b8d41220bde1b5c1f0e86a3346959fc7b760ccf5 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 12 Sep 2016 15:34:54 +0100 Subject: Admin group members UI fix --- app/views/shared/members/_member.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app') diff --git a/app/views/shared/members/_member.html.haml b/app/views/shared/members/_member.html.haml index 6f8c3c4da2e..80e52bf5637 100644 --- a/app/views/shared/members/_member.html.haml +++ b/app/views/shared/members/_member.html.haml @@ -4,7 +4,7 @@ - source = member.source %li.member{ class: dom_class(member), id: dom_id(member) } - %span{ class: ("list-item-name" if show_controls) } + %span.list-item-name - if user = image_tag avatar_icon(user, 40), class: "avatar s40", alt: '' %strong -- cgit v1.2.1 From a34c0e5490c78402b72fab7196d43352ff719cbb Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 4 Oct 2016 10:32:24 +0100 Subject: Border instead of hr --- app/assets/stylesheets/pages/members.scss | 5 +++++ app/views/projects/project_members/index.html.haml | 3 +-- 2 files changed, 6 insertions(+), 2 deletions(-) (limited to 'app') diff --git a/app/assets/stylesheets/pages/members.scss b/app/assets/stylesheets/pages/members.scss index 72f31cb1168..187151fe26c 100644 --- a/app/assets/stylesheets/pages/members.scss +++ b/app/assets/stylesheets/pages/members.scss @@ -1,3 +1,8 @@ +.project-members-title { + padding-bottom: 10px; + border-bottom: 1px solid $border-color; +} + .member { .list-item-name { float: none; diff --git a/app/views/projects/project_members/index.html.haml b/app/views/projects/project_members/index.html.haml index f566748e95a..24e5a8e4015 100644 --- a/app/views/projects/project_members/index.html.haml +++ b/app/views/projects/project_members/index.html.haml @@ -1,10 +1,9 @@ - page_title "Members" .project-members-page.prepend-top-default - %h4.clearfix + %h4.project-members-title.clearfix Members = link_to "Import", import_namespace_project_project_members_path(@project.namespace, @project), class: "btn btn-default pull-right hidden-xs", title: "Import members from another project" - %hr - if can?(current_user, :admin_project_member, @project) .project-members-new.append-bottom-default %p.clearfix -- cgit v1.2.1 From c2602aaff3f78ad12e1cc06136a7345699951454 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 4 Oct 2016 14:40:03 +0100 Subject: Updated Ruby --- app/controllers/projects/group_links_controller.rb | 2 +- app/controllers/projects/project_members_controller.rb | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) (limited to 'app') diff --git a/app/controllers/projects/group_links_controller.rb b/app/controllers/projects/group_links_controller.rb index 7b6f07465e0..2994d8c9666 100644 --- a/app/controllers/projects/group_links_controller.rb +++ b/app/controllers/projects/group_links_controller.rb @@ -1,6 +1,7 @@ class Projects::GroupLinksController < Projects::ApplicationController layout 'project_settings' before_action :authorize_admin_project! + before_action :authorize_admin_project_member!, only: [:update] def index @group_links = project.project_group_links.all @@ -21,7 +22,6 @@ class Projects::GroupLinksController < Projects::ApplicationController def update @group_link = @project.project_group_links.find(params[:id]) - return render_403 unless can?(current_user, :admin_project_member, @project) @group_link.update_attributes(group_link_params) end diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb index eb1bf445a7d..870dc8abbd4 100644 --- a/app/controllers/projects/project_members_controller.rb +++ b/app/controllers/projects/project_members_controller.rb @@ -19,8 +19,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController @groups = @project.project_group_links.where(group_id: group_ids) end - @project_members = @project_members.order('access_level DESC') - @project_members = @project_members.page(params[:page]) + @project_members = @project_members.order(access_level: :desc).page(params[:page]) @requesters = AccessRequestsFinder.new(@project).execute(current_user) @@ -40,6 +39,8 @@ class Projects::ProjectMembersController < Projects::ApplicationController groups = Group.where(id: group_ids) groups.each do |group| + next unless can?(current_user, :read_group, group) + project.project_group_links.create( group: group, group_access: params[:access_level], -- cgit v1.2.1 From 66c7f5cb46d446a1c6c89ba888efd9e5f74f876d Mon Sep 17 00:00:00 2001 From: James Lopez Date: Wed, 5 Oct 2016 14:31:33 +0200 Subject: fix empty import URL errors --- app/models/project.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app') diff --git a/app/models/project.rb b/app/models/project.rb index 507228606df..4beec36242d 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -494,7 +494,7 @@ class Project < ActiveRecord::Base end def import_url - if import_data && super + if import_data && super.present? import_url = Gitlab::UrlSanitizer.new(super, credentials: import_data.credentials) import_url.full_url else -- cgit v1.2.1 From a0eaff14124b829ccc02df951bd7cb7d3abb7708 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 5 Oct 2016 14:37:03 +0100 Subject: Updated Ruby variable name Fixed Ruby code based on review feedback --- app/controllers/projects/project_members_controller.rb | 8 +++----- app/views/projects/project_members/_groups.html.haml | 4 ++-- app/views/projects/project_members/index.html.haml | 4 ++-- 3 files changed, 7 insertions(+), 9 deletions(-) (limited to 'app') diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb index 870dc8abbd4..b2c8656d124 100644 --- a/app/controllers/projects/project_members_controller.rb +++ b/app/controllers/projects/project_members_controller.rb @@ -5,7 +5,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController before_action :authorize_admin_project_member!, except: [:index, :leave, :request_access] def index - @groups = @project.project_group_links + @group_links = @project.project_group_links @project_members = @project.project_members @project_members = @project_members.non_invite unless can?(current_user, :admin_project, @project) @@ -14,9 +14,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController users = @project.users.search(params[:search]).to_a @project_members = @project_members.where(user_id: users) - group_ids = @groups.pluck(:group_id) - group_ids = Group.where(id: group_ids).search(params[:search]).to_a - @groups = @project.project_group_links.where(group_id: group_ids) + @group_links = @project.project_group_links.where(group_id: @project.invited_groups.search(params[:search]).select(:id)) end @project_members = @project_members.order(access_level: :desc).page(params[:page]) @@ -40,7 +38,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController groups.each do |group| next unless can?(current_user, :read_group, group) - + project.project_group_links.create( group: group, group_access: params[:access_level], diff --git a/app/views/projects/project_members/_groups.html.haml b/app/views/projects/project_members/_groups.html.haml index 340e4cd06b8..11f896006da 100644 --- a/app/views/projects/project_members/_groups.html.haml +++ b/app/views/projects/project_members/_groups.html.haml @@ -2,8 +2,8 @@ .panel-heading Groups with access to %strong #{@project.name} - %span.badge= groups.size + %span.badge= group_links.size %ul.content-list - - @groups.each do |group_link| + - group_links.each do |group_link| - group = group_link.group = render 'shared/members/group', group_link: group_link, group: group diff --git a/app/views/projects/project_members/index.html.haml b/app/views/projects/project_members/index.html.haml index 24e5a8e4015..f1461444241 100644 --- a/app/views/projects/project_members/index.html.haml +++ b/app/views/projects/project_members/index.html.haml @@ -21,8 +21,8 @@ = search_field_tag :search, params[:search], { placeholder: 'Find existing members by name', class: 'form-control', spellcheck: false } %button.member-search-btn{ type: "submit", "aria-label" => "Submit search" } = icon("search") - - if @groups.size > 0 - = render 'groups', groups: @groups + - if @group_links.size > 0 + = render 'groups', group_links: @group_links = render 'team', members: @project_members = paginate @project_members, theme: "gitlab" -- cgit v1.2.1 From c65ea7a5c30ee44c859ac711f1054dab734bdb89 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 16 Sep 2016 10:45:23 +0100 Subject: Merge request tabs stick when scrolling page Closes #20548 --- app/assets/javascripts/merge_request_tabs.js | 38 +++++++++++++++++++++++ app/assets/stylesheets/pages/merge_requests.scss | 9 ++++++ app/views/projects/merge_requests/_show.html.haml | 2 +- 3 files changed, 48 insertions(+), 1 deletion(-) (limited to 'app') diff --git a/app/assets/javascripts/merge_request_tabs.js b/app/assets/javascripts/merge_request_tabs.js index bec11a198a1..f685af7d97e 100644 --- a/app/assets/javascripts/merge_request_tabs.js +++ b/app/assets/javascripts/merge_request_tabs.js @@ -68,6 +68,7 @@ this._location = location; this.bindEvents(); this.activateTab(this.opts.action); + this.initAffix(); } MergeRequestTabs.prototype.bindEvents = function() { @@ -367,6 +368,43 @@ // Only when sidebar is collapsed }; + MergeRequestTabs.prototype.initAffix = function () { + // Screen space on small screens is usually very sparse + // So we dont affix the tabs on these + if (Breakpoints.get().getBreakpointSize() === 'xs') return; + + var $tabs = $('.js-tabs-affix'), + tabsWidth = $tabs.outerWidth(), + $diffTabs = $('#diff-notes-app'), + offsetTop = $tabs.offset().top - ($('.navbar-fixed-top').height() + $('.layout-nav').height()); + + $tabs.off('affix.bs.affix affix-top.bs.affix') + .affix({ + offset: offsetTop + }).on('affix.bs.affix', function () { + $tabs.css({ + left: $tabs.offset().left, + width: tabsWidth + }); + $diffTabs.css({ + marginTop: $tabs.height() + }); + }).on('affix-top.bs.affix', function () { + $tabs.css({ + left: '', + width: '' + }); + $diffTabs.css({ + marginTop: '' + }); + }); + + // Fix bug when reloading the page already scrolling + if ($tabs.hasClass('affix')) { + $tabs.trigger('affix.bs.affix'); + } + }; + return MergeRequestTabs; })(); diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index bc8693ae467..d01aaa4be51 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -421,3 +421,12 @@ margin-bottom: 20px; } } + +.merge-request-tabs { + background-color: #fff; + + &.affix { + top: 100px; + z-index: 9; + } +} diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml index 9f34ca9ff4e..351c9d6ff91 100644 --- a/app/views/projects/merge_requests/_show.html.haml +++ b/app/views/projects/merge_requests/_show.html.haml @@ -47,7 +47,7 @@ = link_to "command line", "#modal_merge_info", class: "how_to_merge_link vlink", title: "How To Merge", "data-toggle" => "modal" - if @commits_count.nonzero? - %ul.merge-request-tabs.nav-links.no-top.no-bottom + %ul.merge-request-tabs.nav-links.no-top.no-bottom.js-tabs-affix %li.notes-tab = link_to namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: 'div#notes', action: 'notes', toggle: 'tab' } do Discussion -- cgit v1.2.1 From 97dc95b18e5a5c29daab84e0d65522b3d3158dc1 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 16 Sep 2016 14:32:43 +0100 Subject: Fixed tests --- app/assets/javascripts/merge_request_tabs.js | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/merge_request_tabs.js b/app/assets/javascripts/merge_request_tabs.js index f685af7d97e..1a214cdd00e 100644 --- a/app/assets/javascripts/merge_request_tabs.js +++ b/app/assets/javascripts/merge_request_tabs.js @@ -369,18 +369,21 @@ }; MergeRequestTabs.prototype.initAffix = function () { + var $tabs = $('.js-tabs-affix'); + // Screen space on small screens is usually very sparse // So we dont affix the tabs on these - if (Breakpoints.get().getBreakpointSize() === 'xs') return; + if (Breakpoints.get().getBreakpointSize() === 'xs' || !$tabs.length) return; - var $tabs = $('.js-tabs-affix'), - tabsWidth = $tabs.outerWidth(), - $diffTabs = $('#diff-notes-app'), - offsetTop = $tabs.offset().top - ($('.navbar-fixed-top').height() + $('.layout-nav').height()); + var tabsWidth = $tabs.outerWidth(), + $diffTabs = $('#diff-notes-app'), + offsetTop = $tabs.offset().top - ($('.navbar-fixed-top').height() + $('.layout-nav').height()); $tabs.off('affix.bs.affix affix-top.bs.affix') .affix({ - offset: offsetTop + offset: { + top: offsetTop + } }).on('affix.bs.affix', function () { $tabs.css({ left: $tabs.offset().left, -- cgit v1.2.1 From 6865c46c6649118e09e60dd29dfa060470010aa9 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 6 Oct 2016 15:41:00 +0100 Subject: Changed how collections are rendered Used variables in haml for replicated checks Fixed broken conflict --- app/controllers/projects/project_members_controller.rb | 15 --------------- app/views/projects/group_links/update.js.haml | 2 +- app/views/projects/project_members/_groups.html.haml | 4 +--- app/views/projects/project_members/_team.html.haml | 3 +-- app/views/projects/project_members/index.html.haml | 2 +- app/views/shared/members/_group.html.haml | 9 +++++---- app/views/shared/members/_member.html.haml | 10 ++++++---- 7 files changed, 15 insertions(+), 30 deletions(-) (limited to 'app') diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb index 67b41c4573a..37a86ed0523 100644 --- a/app/controllers/projects/project_members_controller.rb +++ b/app/controllers/projects/project_members_controller.rb @@ -98,21 +98,6 @@ class Projects::ProjectMembersController < Projects::ApplicationController notice: notice) end - def options - users = User.all - users = users.search(params[:search]) if params[:search].present? - users = users.page(1) - - groups = Group.all - groups = groups.search(params[:search]) if params[:search].present? - groups = groups.page(1) - - render json: { - Groups: groups.as_json(only: [:id, :name], methods: [:avatar_url]), - Users: users.as_json(only: [:id, :name, :username], methods: [:avatar_url]), - } - end - protected def member_params diff --git a/app/views/projects/group_links/update.js.haml b/app/views/projects/group_links/update.js.haml index 231d5a79723..af9a5b19060 100644 --- a/app/views/projects/group_links/update.js.haml +++ b/app/views/projects/group_links/update.js.haml @@ -1,3 +1,3 @@ :plain - var $listItem = $('#{escape_javascript(render('shared/members/group', group_link: @group_link, group: @group_link.group))}'); + var $listItem = $('#{escape_javascript(render('shared/members/group', group_link: @group_link))}'); $("#group_member_#{@group_link.id} .list-item-name").replaceWith($listItem.find('.list-item-name')); diff --git a/app/views/projects/project_members/_groups.html.haml b/app/views/projects/project_members/_groups.html.haml index 11f896006da..d7f5fa96527 100644 --- a/app/views/projects/project_members/_groups.html.haml +++ b/app/views/projects/project_members/_groups.html.haml @@ -4,6 +4,4 @@ %strong #{@project.name} %span.badge= group_links.size %ul.content-list - - group_links.each do |group_link| - - group = group_link.group - = render 'shared/members/group', group_link: group_link, group: group + = render partial: 'shared/members/group', collection: group_links, as: :group_link diff --git a/app/views/projects/project_members/_team.html.haml b/app/views/projects/project_members/_team.html.haml index ff54035cfe1..c1e894d8f40 100644 --- a/app/views/projects/project_members/_team.html.haml +++ b/app/views/projects/project_members/_team.html.haml @@ -4,5 +4,4 @@ %strong #{@project.name} %span.badge= @project_members.total_count %ul.content-list - - members.each do |member| - = render 'shared/members/member', member: member + = render partial: 'shared/members/member', collection: members, as: :member diff --git a/app/views/projects/project_members/index.html.haml b/app/views/projects/project_members/index.html.haml index f1461444241..bdeb704b6da 100644 --- a/app/views/projects/project_members/index.html.haml +++ b/app/views/projects/project_members/index.html.haml @@ -21,7 +21,7 @@ = search_field_tag :search, params[:search], { placeholder: 'Find existing members by name', class: 'form-control', spellcheck: false } %button.member-search-btn{ type: "submit", "aria-label" => "Submit search" } = icon("search") - - if @group_links.size > 0 + - if @group_links.any? = render 'groups', group_links: @group_links = render 'team', members: @project_members diff --git a/app/views/shared/members/_group.html.haml b/app/views/shared/members/_group.html.haml index 171a388b233..1c0346bbc78 100644 --- a/app/views/shared/members/_group.html.haml +++ b/app/views/shared/members/_group.html.haml @@ -1,5 +1,6 @@ -- group = local_assigns[:group] - group_link = local_assigns[:group_link] +- group = group_link.group +- can_admin_member = can?(current_user, :admin_project_member, @project) %li.member.group_member{ id: "group_member_#{group_link.id}" } %span{ class: "list-item-name" } = image_tag group_icon(group), class: "avatar s40", alt: '' @@ -13,11 +14,11 @@ Expires in #{distance_of_time_in_words_to_now(group_link.expires_at)} .controls.member-controls = form_tag namespace_project_group_link_path(@project.namespace, @project, group_link), method: :put, remote: true, class: 'form-horizontal js-edit-member-form' do - = select_tag 'group_link[group_access]', options_for_select(ProjectGroupLink.access_options, group_link.group_access), class: 'form-control member-form-control append-right-5 js-member-update-control', id: "member_access_level_#{group.id}", disabled: !can?(current_user, :admin_project_member, @project) + = select_tag 'group_link[group_access]', options_for_select(ProjectGroupLink.access_options, group_link.group_access), class: 'form-control member-form-control append-right-5 js-member-update-control', id: "member_access_level_#{group.id}", disabled: !can_admin_member .prepend-left-5.clearable-input.member-form-control - = text_field_tag 'group_link[expires_at]', group_link.expires_at, class: 'form-control js-access-expiration-date js-member-update-control', placeholder: 'Expiration date', id: "member_expires_at_#{group.id}", disabled: !can?(current_user, :admin_project_member, @project) + = text_field_tag 'group_link[expires_at]', group_link.expires_at, class: 'form-control js-access-expiration-date js-member-update-control', placeholder: 'Expiration date', id: "member_expires_at_#{group.id}", disabled: !can_admin_member %i.clear-icon.js-clear-input - - if can?(current_user, :admin_project_member, @project) + - if can_admin_member = link_to namespace_project_group_link_path(@project.namespace, @project, group_link), remote: true, method: :delete, diff --git a/app/views/shared/members/_member.html.haml b/app/views/shared/members/_member.html.haml index 80e52bf5637..432047a1c4e 100644 --- a/app/views/shared/members/_member.html.haml +++ b/app/views/shared/members/_member.html.haml @@ -2,6 +2,7 @@ - show_controls = local_assigns.fetch(:show_controls, true) - user = local_assigns.fetch(:user, member.user) - source = member.source +- can_admin_member = can?(current_user, action_member_permission(:update, member), member) %li.member{ class: dom_class(member), id: dom_id(member) } %span.list-item-name @@ -47,18 +48,19 @@ - if show_controls - if user != current_user = form_for member, remote: true, html: { class: 'form-horizontal js-edit-member-form' } do |f| - = f.select :access_level, options_for_select(member.class.access_level_roles, member.access_level), {}, class: 'form-control member-form-control append-right-5 js-member-update-control', id: "member_access_level_#{member.id}", disabled: !can?(current_user, action_member_permission(:update, member), member) + = f.select :access_level, options_for_select(member.class.access_level_roles, member.access_level), {}, class: 'form-control member-form-control append-right-5 js-member-update-control', id: "member_access_level_#{member.id}", disabled: !can_admin_member .prepend-left-5.clearable-input.member-form-control - = f.text_field :expires_at, class: 'form-control js-access-expiration-date js-member-update-control', placeholder: 'Expiration date', id: "member_expires_at_#{member.id}", disabled: !can?(current_user, action_member_permission(:update, member), member) + = f.text_field :expires_at, class: 'form-control js-access-expiration-date js-member-update-control', placeholder: 'Expiration date', id: "member_expires_at_#{member.id}", disabled: !can_admin_member %i.clear-icon.js-clear-input - else %span.member-access-text= member.human_access - - if !user && can?(current_user, action_member_permission(:admin, member), member.source) + + - if member.invite? && can?(current_user, action_member_permission(:admin, member), member.source) = link_to 'Resend invite', polymorphic_path([:resend_invite, member]), method: :post, class: 'btn btn-default prepend-left-10' - - if member.request? && can?(current_user, action_member_permission(:update, member), member) + - elsif member.request? && can_admin_member = link_to icon('check inverse'), polymorphic_path([:approve_access_request, member]), method: :post, class: 'btn btn-success prepend-left-10', -- cgit v1.2.1 From b2e5240af3d14bd67900ef194226274639537888 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 6 Oct 2016 15:42:48 +0100 Subject: Changed jQuery to be in single line Removed un-required CSS --- app/assets/javascripts/members.js.es6 | 5 +---- app/assets/stylesheets/pages/members.scss | 2 -- 2 files changed, 1 insertion(+), 6 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/members.js.es6 b/app/assets/javascripts/members.js.es6 index 1b4b3f38838..5dc6990bdb6 100644 --- a/app/assets/javascripts/members.js.es6 +++ b/app/assets/javascripts/members.js.es6 @@ -31,10 +31,7 @@ } formSubmit() { - $(this).closest('form') - .trigger("submit.rails") - .end() - .disable(); + $(this).closest('form').trigger("submit.rails").end().disable(); } formSuccess() { diff --git a/app/assets/stylesheets/pages/members.scss b/app/assets/stylesheets/pages/members.scss index 187151fe26c..ff4fd751f26 100644 --- a/app/assets/stylesheets/pages/members.scss +++ b/app/assets/stylesheets/pages/members.scss @@ -5,8 +5,6 @@ .member { .list-item-name { - float: none; - @media (min-width: $screen-sm-min) { float: left; width: 50%; -- cgit v1.2.1 From 680efd4f8d6fb3a1036edc135f5761a512f8eea7 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Wed, 24 Aug 2016 23:44:58 +0100 Subject: Added subnav to labels edit+new and milestones edit+new+show Added subnav to blame show, blob edit, builds show, commit builds, commit show, environments edit and pipelines show Added subnav to new enviro view Added sidebar top position calculation logic Added sidebar translation to follow the subnav up when the body is scrolled until a certain limit --- app/assets/javascripts/build.js | 36 ++++++++-- app/assets/stylesheets/pages/builds.scss | 6 ++ app/views/projects/blame/show.html.haml | 87 +++++++++++------------ app/views/projects/blob/edit.html.haml | 43 ++++++------ app/views/projects/builds/show.html.haml | 95 +++++++++++++------------- app/views/projects/commit/builds.html.haml | 11 +-- app/views/projects/commit/show.html.haml | 25 ++++--- app/views/projects/environments/edit.html.haml | 11 +-- app/views/projects/environments/new.html.haml | 11 +-- app/views/projects/labels/edit.html.haml | 11 +-- app/views/projects/labels/new.html.haml | 11 +-- app/views/projects/milestones/edit.html.haml | 12 ++-- app/views/projects/milestones/new.html.haml | 11 +-- app/views/projects/milestones/show.html.haml | 83 +++++++++++----------- app/views/projects/pipelines/_head.html.haml | 2 +- app/views/projects/pipelines/show.html.haml | 13 ++-- 16 files changed, 268 insertions(+), 200 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/build.js b/app/assets/javascripts/build.js index f336bfc36d6..97462a5959c 100644 --- a/app/assets/javascripts/build.js +++ b/app/assets/javascripts/build.js @@ -15,18 +15,17 @@ this.hideSidebar = bind(this.hideSidebar, this); this.toggleSidebar = bind(this.toggleSidebar, this); this.updateDropdown = bind(this.updateDropdown, this); + this.$document = $(document); clearInterval(Build.interval); // Init breakpoint checker this.bp = Breakpoints.get(); - $('.js-build-sidebar').niceScroll(); + this.initSidebar(); this.populateJobs(this.build_stage); this.updateStageDropdownText(this.build_stage); - this.hideSidebar(); - $(document).off('click', '.js-sidebar-build-toggle').on('click', '.js-sidebar-build-toggle', this.toggleSidebar); $(window).off('resize.build').on('resize.build', this.hideSidebar); - $(document).off('click', '.stage-item').on('click', '.stage-item', this.updateDropdown); + this.$document.off('click', '.stage-item').on('click', '.stage-item', this.updateDropdown); $('#js-build-scroll > a').off('click').on('click', this.stepTrace); this.updateArtifactRemoveDate(); if ($('#build-trace').length) { @@ -62,6 +61,21 @@ } } + Build.prototype.initSidebar = function() { + this.$sidebar = $('.js-build-sidebar'); + this.sidebarTranslationLimits = { + min: $('.navbar-gitlab').outerHeight() + $('.layout-nav').outerHeight() + } + this.sidebarTranslationLimits.max = this.sidebarTranslationLimits.min + $('.scrolling-tabs-container').outerHeight(); + this.$sidebar.css({ + top: this.sidebarTranslationLimits.max + }); + this.$sidebar.niceScroll(); + this.hideSidebar(); + this.$document.off('click', '.js-sidebar-build-toggle').on('click', '.js-sidebar-build-toggle', this.toggleSidebar); + this.$document.off('scroll.translateSidebar').on('scroll.translateSidebar', this.translateSidebar.bind(this)); + }; + Build.prototype.getInitialBuildTrace = function() { var removeRefreshStatuses = ['success', 'failed', 'canceled', 'skipped'] @@ -129,15 +143,23 @@ Build.prototype.toggleSidebar = function() { if (this.shouldHideSidebar()) { - return $('.js-build-sidebar').toggleClass('right-sidebar-expanded right-sidebar-collapsed'); + return this.$sidebar.toggleClass('right-sidebar-expanded right-sidebar-collapsed'); } }; + Build.prototype.translateSidebar = function(e) { + var newPosition = this.sidebarTranslationLimits.max - document.body.scrollTop; + if (newPosition < this.sidebarTranslationLimits.min) newPosition = this.sidebarTranslationLimits.min; + this.$sidebar.css({ + top: newPosition + }); + }; + Build.prototype.hideSidebar = function() { if (this.shouldHideSidebar()) { - return $('.js-build-sidebar').removeClass('right-sidebar-expanded').addClass('right-sidebar-collapsed'); + return this.$sidebar.removeClass('right-sidebar-expanded').addClass('right-sidebar-collapsed'); } else { - return $('.js-build-sidebar').removeClass('right-sidebar-collapsed').addClass('right-sidebar-expanded'); + return this.$sidebar.removeClass('right-sidebar-collapsed').addClass('right-sidebar-expanded'); } }; diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss index 194a39a8377..fcaba954615 100644 --- a/app/assets/stylesheets/pages/builds.scss +++ b/app/assets/stylesheets/pages/builds.scss @@ -233,3 +233,9 @@ right: 0; margin-top: -17px; } + +@media (min-width: $screen-md-min) { + .sub-nav.build { + width: calc(100% + #{$gutter_width}); + } +} diff --git a/app/views/projects/blame/show.html.haml b/app/views/projects/blame/show.html.haml index 5a98e258b22..dfb96305f48 100644 --- a/app/views/projects/blame/show.html.haml +++ b/app/views/projects/blame/show.html.haml @@ -1,45 +1,48 @@ +- @no_container = true - page_title "Blame", @blob.path, @ref += render "projects/commits/head" -%h3.page-title Blame view +%div{ class: container_class } + %h3.page-title Blame view -#blob-content-holder.tree-holder - .file-holder - .file-title - = blob_icon @blob.mode, @blob.name - %strong - = @path - %small= number_to_human_size @blob.size - .file-actions - = render "projects/blob/actions" - .table-responsive.file-content.blame.code.js-syntax-highlight - %table - - current_line = 1 - - @blame_groups.each do |blame_group| - %tr - %td.blame-commit - .commit - - commit = blame_group[:commit] - = author_avatar(commit, size: 36) - .commit-row-title - %strong - = link_to_gfm truncate(commit.title, length: 35), namespace_project_commit_path(@project.namespace, @project, commit.id), class: "cdark" - .pull-right - = link_to commit.short_id, namespace_project_commit_path(@project.namespace, @project, commit), class: "monospace" -   - .light - = commit_author_link(commit, avatar: false) - authored - #{time_ago_with_tooltip(commit.committed_date, skip_js: true)} - %td.line-numbers - - line_count = blame_group[:lines].count - - (current_line...(current_line + line_count)).each do |i| - %a.diff-line-num{href: "#L#{i}", id: "L#{i}", 'data-line-number' => i} - = icon("link") - = i - \ - - current_line += line_count - %td.lines - %pre.code.highlight - %code - - blame_group[:lines].each do |line| - #{line} + #blob-content-holder.tree-holder + .file-holder + .file-title + = blob_icon @blob.mode, @blob.name + %strong + = @path + %small= number_to_human_size @blob.size + .file-actions + = render "projects/blob/actions" + .table-responsive.file-content.blame.code.js-syntax-highlight + %table + - current_line = 1 + - @blame_groups.each do |blame_group| + %tr + %td.blame-commit + .commit + - commit = blame_group[:commit] + = author_avatar(commit, size: 36) + .commit-row-title + %strong + = link_to_gfm truncate(commit.title, length: 35), namespace_project_commit_path(@project.namespace, @project, commit.id), class: "cdark" + .pull-right + = link_to commit.short_id, namespace_project_commit_path(@project.namespace, @project, commit), class: "monospace" +   + .light + = commit_author_link(commit, avatar: false) + authored + #{time_ago_with_tooltip(commit.committed_date, skip_js: true)} + %td.line-numbers + - line_count = blame_group[:lines].count + - (current_line...(current_line + line_count)).each do |i| + %a.diff-line-num{href: "#L#{i}", id: "L#{i}", 'data-line-number' => i} + = icon("link") + = i + \ + - current_line += line_count + %td.lines + %pre.code.highlight + %code + - blame_group[:lines].each do |line| + #{line} diff --git a/app/views/projects/blob/edit.html.haml b/app/views/projects/blob/edit.html.haml index 680e95ac6b5..2a0352a71b7 100644 --- a/app/views/projects/blob/edit.html.haml +++ b/app/views/projects/blob/edit.html.haml @@ -1,28 +1,31 @@ +- @no_container = true - page_title "Edit", @blob.path, @ref - content_for :page_specific_javascripts do = page_specific_javascript_tag('lib/ace.js') = page_specific_javascript_tag('blob_edit/blob_edit_bundle.js') += render "projects/commits/head" -- if @conflict - .alert.alert-danger - Someone edited the file the same time you did. Please check out - = link_to "the file", namespace_project_blob_path(@project.namespace, @project, tree_join(@target_branch, @file_path)), target: "_blank" - and make sure your changes will not unintentionally remove theirs. +%div{ class: container_class } + - if @conflict + .alert.alert-danger + Someone edited the file the same time you did. Please check out + = link_to "the file", namespace_project_blob_path(@project.namespace, @project, tree_join(@target_branch, @file_path)), target: "_blank" + and make sure your changes will not unintentionally remove theirs. -.file-editor - %ul.nav-links.no-bottom.js-edit-mode - %li.active - = link_to '#editor' do - Edit File + .file-editor + %ul.nav-links.no-bottom.js-edit-mode + %li.active + = link_to '#editor' do + Edit File - %li - = link_to '#preview', 'data-preview-url' => namespace_project_preview_blob_path(@project.namespace, @project, @id) do - = editing_preview_title(@blob.name) + %li + = link_to '#preview', 'data-preview-url' => namespace_project_preview_blob_path(@project.namespace, @project, @id) do + = editing_preview_title(@blob.name) - = form_tag(namespace_project_update_blob_path(@project.namespace, @project, @id), method: :put, class: 'form-horizontal js-quick-submit js-requires-input js-edit-blob-form', data: blob_editor_paths) do - = render 'projects/blob/editor', ref: @ref, path: @path, blob_data: @blob.data - = render 'shared/new_commit_form', placeholder: "Update #{@blob.name}" - = hidden_field_tag 'last_commit_sha', @last_commit_sha - = hidden_field_tag 'content', '', id: "file-content" - = hidden_field_tag 'from_merge_request_id', params[:from_merge_request_id] - = render 'projects/commit_button', ref: @ref, cancel_path: namespace_project_blob_path(@project.namespace, @project, @id) + = form_tag(namespace_project_update_blob_path(@project.namespace, @project, @id), method: :put, class: 'form-horizontal js-quick-submit js-requires-input js-edit-blob-form', data: blob_editor_paths) do + = render 'projects/blob/editor', ref: @ref, path: @path, blob_data: @blob.data + = render 'shared/new_commit_form', placeholder: "Update #{@blob.name}" + = hidden_field_tag 'last_commit_sha', @last_commit_sha + = hidden_field_tag 'content', '', id: "file-content" + = hidden_field_tag 'from_merge_request_id', params[:from_merge_request_id] + = render 'projects/commit_button', ref: @ref, cancel_path: namespace_project_blob_path(@project.namespace, @project, @id) diff --git a/app/views/projects/builds/show.html.haml b/app/views/projects/builds/show.html.haml index e4d41288aa6..b5e8b0bf6eb 100644 --- a/app/views/projects/builds/show.html.haml +++ b/app/views/projects/builds/show.html.haml @@ -1,56 +1,59 @@ +- @no_container = true - page_title "#{@build.name} (##{@build.id})", "Builds" - trace_with_state = @build.trace_with_state - header_title project_title(@project, "Builds", project_builds_path(@project)) += render "projects/pipelines/head", build_subnav: true -.build-page - = render "header" +%div{ class: container_class } + .build-page + = render "header" - - if @build.stuck? - - unless @build.any_runners_online? - .bs-callout.bs-callout-warning - %p - - if no_runners_for_project?(@build.project) - This build is stuck, because the project doesn't have any runners online assigned to it. - - elsif @build.tags.any? - This build is stuck, because you don't have any active runners online with any of these tags assigned to them: - - @build.tags.each do |tag| - %span.label.label-primary - = tag - - else - This build is stuck, because you don't have any active runners that can run this build. + - if @build.stuck? + - unless @build.any_runners_online? + .bs-callout.bs-callout-warning + %p + - if no_runners_for_project?(@build.project) + This build is stuck, because the project doesn't have any runners online assigned to it. + - elsif @build.tags.any? + This build is stuck, because you don't have any active runners online with any of these tags assigned to them: + - @build.tags.each do |tag| + %span.label.label-primary + = tag + - else + This build is stuck, because you don't have any active runners that can run this build. - %br - Go to - = link_to namespace_project_runners_path(@build.project.namespace, @build.project) do - Runners page + %br + Go to + = link_to namespace_project_runners_path(@build.project.namespace, @build.project) do + Runners page - .prepend-top-default - - if @build.active? - .autoscroll-container - %button.btn.btn-success.btn-sm#autoscroll-button{:type => "button", :data => {:state => 'disabled'}} enable autoscroll - - if @build.erased? - .erased.alert.alert-warning - - erased_by = "by #{link_to @build.erased_by.name, user_path(@build.erased_by)}" if @build.erased_by - Build has been erased #{erased_by.html_safe} #{time_ago_with_tooltip(@build.erased_at)} - - else - #js-build-scroll.scroll-controls - = link_to '#build-trace', class: 'btn' do - %i.fa.fa-angle-up - = link_to '#down-build-trace', class: 'btn' do - %i.fa.fa-angle-down - %pre.build-trace#build-trace - %code.bash.js-build-output - = icon("refresh spin", class: "js-build-refresh") + .prepend-top-default + - if @build.active? + .autoscroll-container + %button.btn.btn-success.btn-sm#autoscroll-button{:type => "button", :data => {:state => 'disabled'}} enable autoscroll + - if @build.erased? + .erased.alert.alert-warning + - erased_by = "by #{link_to @build.erased_by.name, user_path(@build.erased_by)}" if @build.erased_by + Build has been erased #{erased_by.html_safe} #{time_ago_with_tooltip(@build.erased_at)} + - else + #js-build-scroll.scroll-controls + = link_to '#build-trace', class: 'btn' do + %i.fa.fa-angle-up + = link_to '#down-build-trace', class: 'btn' do + %i.fa.fa-angle-down + %pre.build-trace#build-trace + %code.bash.js-build-output + = icon("refresh spin", class: "js-build-refresh") - #down-build-trace + #down-build-trace -= render "sidebar" + = render "sidebar" -:javascript - new Build({ - page_url: "#{namespace_project_build_url(@project.namespace, @project, @build)}", - build_url: "#{namespace_project_build_url(@project.namespace, @project, @build, :json)}", - build_status: "#{@build.status}", - build_stage: "#{@build.stage}", - state1: "#{trace_with_state[:state]}" - }) + :javascript + new Build({ + page_url: "#{namespace_project_build_url(@project.namespace, @project, @build)}", + build_url: "#{namespace_project_build_url(@project.namespace, @project, @build, :json)}", + build_status: "#{@build.status}", + build_stage: "#{@build.stage}", + state1: "#{trace_with_state[:state]}" + }) diff --git a/app/views/projects/commit/builds.html.haml b/app/views/projects/commit/builds.html.haml index 2f051fb90e0..f9d7eac3542 100644 --- a/app/views/projects/commit/builds.html.haml +++ b/app/views/projects/commit/builds.html.haml @@ -1,7 +1,10 @@ +- @no_container = true - page_title "Builds", "#{@commit.title} (#{@commit.short_id})", "Commits" += render "projects/commits/head" -.prepend-top-default - = render "commit_box" +%div{ class: container_class } + .prepend-top-default + = render "commit_box" -= render "ci_menu" -= render "builds" + = render "ci_menu" + = render "builds" diff --git a/app/views/projects/commit/show.html.haml b/app/views/projects/commit/show.html.haml index ed44d86a687..cebf58d63df 100644 --- a/app/views/projects/commit/show.html.haml +++ b/app/views/projects/commit/show.html.haml @@ -1,14 +1,17 @@ +- @no_container = true - page_title "#{@commit.title} (#{@commit.short_id})", "Commits" - page_description @commit.description += render "projects/commits/head" -.prepend-top-default - = render "commit_box" -- if @commit.status - = render "ci_menu" -- else - %div.block-connector -= render "projects/diffs/diffs", diffs: @diffs -= render "projects/notes/notes_with_form" -- if can_collaborate_with_project? - - %w(revert cherry-pick).each do |type| - = render "projects/commit/change", type: type, commit: @commit, title: @commit.title +%div{ class: container_class } + .prepend-top-default + = render "commit_box" + - if @commit.status + = render "ci_menu" + - else + %div.block-connector + = render "projects/diffs/diffs", diffs: @diffs + = render "projects/notes/notes_with_form" + - if can_collaborate_with_project? + - %w(revert cherry-pick).each do |type| + = render "projects/commit/change", type: type, commit: @commit, title: @commit.title diff --git a/app/views/projects/environments/edit.html.haml b/app/views/projects/environments/edit.html.haml index 6d1bdb9320f..3871165763c 100644 --- a/app/views/projects/environments/edit.html.haml +++ b/app/views/projects/environments/edit.html.haml @@ -1,6 +1,9 @@ +- @no_container = true - page_title "Edit", @environment.name, "Environments" += render "projects/pipelines/head" -%h3.page-title - Edit environment -%hr -= render 'form' +%div{ class: container_class } + %h3.page-title + Edit environment + %hr + = render 'form' diff --git a/app/views/projects/environments/new.html.haml b/app/views/projects/environments/new.html.haml index e51667ade2d..24638c77cbb 100644 --- a/app/views/projects/environments/new.html.haml +++ b/app/views/projects/environments/new.html.haml @@ -1,6 +1,9 @@ +- @no_container = true - page_title 'New Environment' += render "projects/pipelines/head" -%h3.page-title - New environment -%hr -= render 'form' +%div{ class: container_class } + %h3.page-title + New environment + %hr + = render 'form' diff --git a/app/views/projects/labels/edit.html.haml b/app/views/projects/labels/edit.html.haml index 6901ba13ab7..52b187e7e58 100644 --- a/app/views/projects/labels/edit.html.haml +++ b/app/views/projects/labels/edit.html.haml @@ -1,6 +1,9 @@ +- @no_container = true - page_title "Edit", @label.name, "Labels" += render "projects/issues/head" -%h3.page-title - Edit Label -%hr -= render 'form' +%div{ class: container_class } + %h3.page-title + Edit Label + %hr + = render 'form' diff --git a/app/views/projects/labels/new.html.haml b/app/views/projects/labels/new.html.haml index 49ddf901619..a1bb66cfb6c 100644 --- a/app/views/projects/labels/new.html.haml +++ b/app/views/projects/labels/new.html.haml @@ -1,6 +1,9 @@ +- @no_container = true - page_title "New Label" += render "projects/issues/head" -%h3.page-title - New Label -%hr -= render 'form' +%div{ class: container_class } + %h3.page-title + New Label + %hr + = render 'form' diff --git a/app/views/projects/milestones/edit.html.haml b/app/views/projects/milestones/edit.html.haml index be682226ab6..11f41e75e63 100644 --- a/app/views/projects/milestones/edit.html.haml +++ b/app/views/projects/milestones/edit.html.haml @@ -1,8 +1,12 @@ +- @no_container = true - page_title "Edit", @milestone.title, "Milestones" += render "projects/issues/head" -%h3.page-title - Edit Milestone ##{@milestone.iid} +%div{ class: container_class } -%hr + %h3.page-title + Edit Milestone ##{@milestone.iid} -= render "form" + %hr + + = render "form" diff --git a/app/views/projects/milestones/new.html.haml b/app/views/projects/milestones/new.html.haml index 7f372b41698..cda093ade81 100644 --- a/app/views/projects/milestones/new.html.haml +++ b/app/views/projects/milestones/new.html.haml @@ -1,8 +1,11 @@ +- @no_container = true - page_title "New Milestone" += render "projects/issues/head" -%h3.page-title - New Milestone +%div{ class: container_class } + %h3.page-title + New Milestone -%hr + %hr -= render "form" + = render "form" diff --git a/app/views/projects/milestones/show.html.haml b/app/views/projects/milestones/show.html.haml index e62f810a521..c83818e9199 100644 --- a/app/views/projects/milestones/show.html.haml +++ b/app/views/projects/milestones/show.html.haml @@ -1,49 +1,52 @@ +- @no_container = true - page_title @milestone.title, "Milestones" - page_description @milestone.description += render "projects/issues/head" -.detail-page-header - .status-box{ class: status_box_class(@milestone) } - - if @milestone.closed? - Closed - - elsif @milestone.expired? - Past due - - else - Open - %span.identifier - Milestone ##{@milestone.iid} - - if @milestone.expires_at - %span.creator - · - = @milestone.expires_at - .pull-right - - if can?(current_user, :admin_milestone, @project) - - if @milestone.active? - = link_to 'Close Milestone', namespace_project_milestone_path(@project.namespace, @project, @milestone, milestone: {state_event: :close }), method: :put, class: "btn btn-close btn-nr btn-grouped" +%div{ class: container_class } + .detail-page-header + .status-box{ class: status_box_class(@milestone) } + - if @milestone.closed? + Closed + - elsif @milestone.expired? + Past due - else - = link_to 'Reopen Milestone', namespace_project_milestone_path(@project.namespace, @project, @milestone, milestone: {state_event: :activate }), method: :put, class: "btn btn-reopen btn-nr btn-grouped" + Open + %span.identifier + Milestone ##{@milestone.iid} + - if @milestone.expires_at + %span.creator + · + = @milestone.expires_at + .pull-right + - if can?(current_user, :admin_milestone, @project) + - if @milestone.active? + = link_to 'Close Milestone', namespace_project_milestone_path(@project.namespace, @project, @milestone, milestone: {state_event: :close }), method: :put, class: "btn btn-close btn-nr btn-grouped" + - else + = link_to 'Reopen Milestone', namespace_project_milestone_path(@project.namespace, @project, @milestone, milestone: {state_event: :activate }), method: :put, class: "btn btn-reopen btn-nr btn-grouped" - = link_to edit_namespace_project_milestone_path(@project.namespace, @project, @milestone), class: "btn btn-grouped btn-nr" do - Edit + = link_to edit_namespace_project_milestone_path(@project.namespace, @project, @milestone), class: "btn btn-grouped btn-nr" do + Edit - = link_to namespace_project_milestone_path(@project.namespace, @project, @milestone), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-grouped btn-danger" do - Delete + = link_to namespace_project_milestone_path(@project.namespace, @project, @milestone), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-grouped btn-danger" do + Delete -.detail-page-description.milestone-detail - %h2.title - = markdown_field(@milestone, :title) - %div - - if @milestone.description.present? - .description - .wiki - = preserve do - = markdown_field(@milestone, :description) + .detail-page-description.milestone-detail + %h2.title + = markdown_field(@milestone, :title) + %div + - if @milestone.description.present? + .description + .wiki + = preserve do + = markdown_field(@milestone, :description) -- if @milestone.total_items_count(current_user).zero? - .alert.alert-success.prepend-top-default - %span Assign some issues to this milestone. -- elsif @milestone.complete?(current_user) && @milestone.active? - .alert.alert-success.prepend-top-default - %span All issues for this milestone are closed. You may close this milestone now. + - if @milestone.total_items_count(current_user).zero? + .alert.alert-success.prepend-top-default + %span Assign some issues to this milestone. + - elsif @milestone.complete?(current_user) && @milestone.active? + .alert.alert-success.prepend-top-default + %span All issues for this milestone are closed. You may close this milestone now. -= render 'shared/milestones/summary', milestone: @milestone, project: @project -= render 'shared/milestones/tabs', milestone: @milestone + = render 'shared/milestones/summary', milestone: @milestone, project: @project + = render 'shared/milestones/tabs', milestone: @milestone diff --git a/app/views/projects/pipelines/_head.html.haml b/app/views/projects/pipelines/_head.html.haml index 7d421c0e740..b10dd47709f 100644 --- a/app/views/projects/pipelines/_head.html.haml +++ b/app/views/projects/pipelines/_head.html.haml @@ -1,7 +1,7 @@ = content_for :sub_nav do .scrolling-tabs-container.sub-nav-scroll = render 'shared/nav_scroll' - .nav-links.sub-nav.scrolling-tabs + .nav-links.sub-nav.scrolling-tabs{ class: ('build' if local_assigns.fetch(:build_subnav, false)) } %ul{ class: (container_class) } - if project_nav_tab? :pipelines = nav_link(controller: :pipelines) do diff --git a/app/views/projects/pipelines/show.html.haml b/app/views/projects/pipelines/show.html.haml index 75943c64276..688535ad764 100644 --- a/app/views/projects/pipelines/show.html.haml +++ b/app/views/projects/pipelines/show.html.haml @@ -1,8 +1,11 @@ +- @no_container = true - page_title "Pipeline" += render "projects/pipelines/head" -.prepend-top-default - - if @commit - = render "projects/pipelines/info" - %div.block-connector +%div{ class: container_class } + .prepend-top-default + - if @commit + = render "projects/pipelines/info" + %div.block-connector -= render "projects/commit/pipeline", pipeline: @pipeline + = render "projects/commit/pipeline", pipeline: @pipeline -- cgit v1.2.1 From 517895da4c1ca6201f952e443a579e4f2845e6e0 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 10 Oct 2016 13:13:07 +0100 Subject: Don't run affix tabs in test env This was messing up other tests --- app/views/projects/merge_requests/_show.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app') diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml index 351c9d6ff91..72ad96aa217 100644 --- a/app/views/projects/merge_requests/_show.html.haml +++ b/app/views/projects/merge_requests/_show.html.haml @@ -47,7 +47,7 @@ = link_to "command line", "#modal_merge_info", class: "how_to_merge_link vlink", title: "How To Merge", "data-toggle" => "modal" - if @commits_count.nonzero? - %ul.merge-request-tabs.nav-links.no-top.no-bottom.js-tabs-affix + %ul.merge-request-tabs.nav-links.no-top.no-bottom{ class: ("js-tabs-affix" unless ENV['RAILS_ENV'] == 'test') } %li.notes-tab = link_to namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: 'div#notes', action: 'notes', toggle: 'tab' } do Discussion -- cgit v1.2.1 From 0a79e5cd44e1d83f0cd18ffb538da25e6b2deae3 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Gray Date: Mon, 10 Oct 2016 16:21:08 -0500 Subject: Replace generic table with --- app/assets/stylesheets/pages/environments.scss | 4 ++-- app/assets/stylesheets/pages/merge_requests.scss | 4 ++-- app/assets/stylesheets/pages/pipelines.scss | 4 ++-- app/views/admin/runners/show.html.haml | 2 +- app/views/projects/builds/_table.html.haml | 2 +- app/views/projects/commit/_pipeline.html.haml | 2 +- app/views/projects/commit/_pipelines_list.haml | 2 +- app/views/projects/environments/index.html.haml | 2 +- app/views/projects/environments/show.html.haml | 2 +- app/views/projects/pipelines/index.html.haml | 2 +- 10 files changed, 13 insertions(+), 13 deletions(-) (limited to 'app') diff --git a/app/assets/stylesheets/pages/environments.scss b/app/assets/stylesheets/pages/environments.scss index 3f19e920166..820cc0fc991 100644 --- a/app/assets/stylesheets/pages/environments.scss +++ b/app/assets/stylesheets/pages/environments.scss @@ -52,13 +52,13 @@ } } -.table.builds.environments { +.table.ci-table.environments { .icon-container { width: 20px; text-align: center; } - + .branch-commit { .commit-id { margin-right: 0; diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index 043f3f3afe1..9676c2d1026 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -264,7 +264,7 @@ line-height: 31px; } -.builds { +.ci-table { .table-holder { overflow-x: auto; } @@ -357,7 +357,7 @@ } .table-holder { - .builds { + .ci-table { th { background-color: $white-light; diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index a2779704eff..bad4eeaa2cf 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -20,7 +20,7 @@ margin: 4px; } - .table.builds { + .table.ci-table { min-width: 1200px; .branch-commit { @@ -44,7 +44,7 @@ overflow: auto; } -.table.builds { +.table.ci-table { min-width: 900px; &.pipeline { diff --git a/app/views/admin/runners/show.html.haml b/app/views/admin/runners/show.html.haml index a5e82e55cc1..10fea1996aa 100644 --- a/app/views/admin/runners/show.html.haml +++ b/app/views/admin/runners/show.html.haml @@ -71,7 +71,7 @@ .col-md-6 %h4 Recent builds served by this Runner - %table.table.builds.runner-builds + %table.table.ci-table.runner-builds %thead %tr %th Build diff --git a/app/views/projects/builds/_table.html.haml b/app/views/projects/builds/_table.html.haml index f3747ba2a21..9b91ae688ae 100644 --- a/app/views/projects/builds/_table.html.haml +++ b/app/views/projects/builds/_table.html.haml @@ -5,7 +5,7 @@ .nothing-here-block No builds to show - else .table-holder - %table.table.builds + %table.table.ci-table %thead %tr %th Status diff --git a/app/views/projects/commit/_pipeline.html.haml b/app/views/projects/commit/_pipeline.html.haml index da5b9832ba5..b5e5e8db9eb 100644 --- a/app/views/projects/commit/_pipeline.html.haml +++ b/app/views/projects/commit/_pipeline.html.haml @@ -55,7 +55,7 @@ \.gitlab-ci.yml not found in this commit .table-holder.pipeline-holder - %table.table.builds.pipeline + %table.table.ci-table.pipeline %thead %tr %th Status diff --git a/app/views/projects/commit/_pipelines_list.haml b/app/views/projects/commit/_pipelines_list.haml index 998812793a2..640651e93f5 100644 --- a/app/views/projects/commit/_pipelines_list.haml +++ b/app/views/projects/commit/_pipelines_list.haml @@ -4,7 +4,7 @@ .nothing-here-block No pipelines to show - else .table-holder - %table.table.builds + %table.table.ci-table %tbody %th Status %th Pipeline diff --git a/app/views/projects/environments/index.html.haml b/app/views/projects/environments/index.html.haml index ab801409722..721ba156334 100644 --- a/app/views/projects/environments/index.html.haml +++ b/app/views/projects/environments/index.html.haml @@ -24,7 +24,7 @@ New environment - else .table-holder - %table.table.builds.environments + %table.table.ci-table.environments %tbody %th Environment %th Last Deployment diff --git a/app/views/projects/environments/show.html.haml b/app/views/projects/environments/show.html.haml index 7a8d196cf4e..90c59223a35 100644 --- a/app/views/projects/environments/show.html.haml +++ b/app/views/projects/environments/show.html.haml @@ -24,7 +24,7 @@ = link_to "Read more", help_page_path("ci/environments"), class: "btn btn-success" - else .table-holder - %table.table.builds.environments + %table.table.ci-table.environments %thead %tr %th ID diff --git a/app/views/projects/pipelines/index.html.haml b/app/views/projects/pipelines/index.html.haml index 2d1df095bfa..9eeef5f57b4 100644 --- a/app/views/projects/pipelines/index.html.haml +++ b/app/views/projects/pipelines/index.html.haml @@ -43,7 +43,7 @@ .nothing-here-block No pipelines to show - else .table-holder - %table.table.builds + %table.table.ci-table %thead %th.col-xs-1.col-sm-1 Status %th.col-xs-2.col-sm-4 Pipeline -- cgit v1.2.1 From 0012b74e01429b98d2c42f68f50dc6ce6ad2b70f Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Gray Date: Mon, 10 Oct 2016 16:31:24 -0500 Subject: Set height on build page rows --- app/assets/stylesheets/pages/pipelines.scss | 7 +++++++ app/views/projects/builds/_table.html.haml | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) (limited to 'app') diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index bad4eeaa2cf..149c29858fb 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -51,6 +51,13 @@ min-width: 650px; } + &.builds-page { + + tr { + height: 71px; + } + } + tr { th { padding: 16px 8px; diff --git a/app/views/projects/builds/_table.html.haml b/app/views/projects/builds/_table.html.haml index 9b91ae688ae..36294c89fa8 100644 --- a/app/views/projects/builds/_table.html.haml +++ b/app/views/projects/builds/_table.html.haml @@ -5,7 +5,7 @@ .nothing-here-block No builds to show - else .table-holder - %table.table.ci-table + %table.table.ci-table.builds-page %thead %tr %th Status -- cgit v1.2.1 From 64609ba84845969ef084aee3058ce2fbea582a3f Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Gray Date: Mon, 10 Oct 2016 16:43:40 -0500 Subject: Set height on MR builds rows --- app/assets/stylesheets/pages/merge_requests.scss | 6 ----- app/assets/stylesheets/pages/pipelines.scss | 30 +++++++++++++++++------- 2 files changed, 21 insertions(+), 15 deletions(-) (limited to 'app') diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index 9676c2d1026..71cf17fdb41 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -264,12 +264,6 @@ line-height: 31px; } -.ci-table { - .table-holder { - overflow-x: auto; - } -} - .panel-new-merge-request { .panel-heading { padding: 5px 10px; diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 149c29858fb..84af2a01997 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -574,19 +574,31 @@ } } -.pipelines.tab-pane { +.tab-pane { - .content-list.pipelines { - overflow: auto; - } + &.pipelines { - .stage { - max-width: 100px; - width: 100px; + .content-list.pipelines { + overflow: auto; + } + + .stage { + max-width: 100px; + width: 100px; + } + + .pipeline-actions { + min-width: initial; + } } - .pipeline-actions { - min-width: initial; + &.builds { + + .ci-table { + tr { + height: 71px; + } + } } } -- cgit v1.2.1 From 2461e10912b484c6942cd26f8fffd5c5557befa7 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Wed, 12 Oct 2016 13:44:33 +0200 Subject: Execute pipeline hooks asynchronously --- app/models/ci/pipeline.rb | 7 ++++++- app/workers/pipeline_hooks_worker.rb | 9 +++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 app/workers/pipeline_hooks_worker.rb (limited to 'app') diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 2cf9892edc5..13a58a7a308 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -3,6 +3,7 @@ module Ci extend Ci::Model include HasStatus include Importable + include AfterCommitQueue self.table_name = 'ci_commits' @@ -71,7 +72,11 @@ module Ci end after_transition do |pipeline, transition| - pipeline.execute_hooks unless transition.loopback? + next if transition.loopback? + + pipeline.run_after_commit do + PipelineHooksWorker.perform_async(id) + end end end diff --git a/app/workers/pipeline_hooks_worker.rb b/app/workers/pipeline_hooks_worker.rb new file mode 100644 index 00000000000..ab5e9f6daad --- /dev/null +++ b/app/workers/pipeline_hooks_worker.rb @@ -0,0 +1,9 @@ +class PipelineHooksWorker + include Sidekiq::Worker + sidekiq_options queue: :default + + def perform(pipeline_id) + Ci::Pipeline.find_by(id: pipeline_id) + .try(:execute_hooks) + end +end -- cgit v1.2.1 From 96ee975805dd9bbb20bd26edf2dcc7bfdc858c78 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 13 Oct 2016 10:25:38 +0100 Subject: Moved how we remove event listeners --- app/assets/javascripts/members.js.es6 | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/members.js.es6 b/app/assets/javascripts/members.js.es6 index 5dc6990bdb6..a0cd20f21e8 100644 --- a/app/assets/javascripts/members.js.es6 +++ b/app/assets/javascripts/members.js.es6 @@ -3,20 +3,13 @@ class Members { constructor() { - this.removeListeners(); this.addListeners(); } - removeListeners() { - $('.project_member, .group_member').off('ajax:success'); - $('.js-member-update-control').off('change'); - $('.js-edit-member-form').off('ajax:success'); - } - addListeners() { - $('.project_member, .group_member').on('ajax:success', this.removeRow); - $('.js-member-update-control').on('change', this.formSubmit); - $('.js-edit-member-form').on('ajax:success', this.formSuccess); + $('.project_member, .group_member').off('ajax:success').on('ajax:success', this.removeRow); + $('.js-member-update-control').off('change').on('change', this.formSubmit); + $('.js-edit-member-form').off('ajax:success').on('ajax:success', this.formSuccess); } removeRow(e) { -- cgit v1.2.1 From 9ec7aeac2362151e15e59531f347f2d7924437f8 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 13 Oct 2016 11:18:56 +0100 Subject: Tweaked position of badge in panel headings Various UI tweaks --- app/assets/stylesheets/framework/panels.scss | 5 +++++ app/assets/stylesheets/framework/selects.scss | 2 +- app/assets/stylesheets/pages/members.scss | 4 ++++ 3 files changed, 10 insertions(+), 1 deletion(-) (limited to 'app') diff --git a/app/assets/stylesheets/framework/panels.scss b/app/assets/stylesheets/framework/panels.scss index c6f30e144fd..5ba0486177f 100644 --- a/app/assets/stylesheets/framework/panels.scss +++ b/app/assets/stylesheets/framework/panels.scss @@ -13,6 +13,11 @@ .dropdown-menu-toggle { line-height: 20px; } + + .badge { + margin-top: -2px; + margin-left: 5px; + } } .panel-body { diff --git a/app/assets/stylesheets/framework/selects.scss b/app/assets/stylesheets/framework/selects.scss index b309e2ad9f4..58f9db0fb21 100644 --- a/app/assets/stylesheets/framework/selects.scss +++ b/app/assets/stylesheets/framework/selects.scss @@ -94,7 +94,7 @@ } .select2-search-choice { - margin: 8px 0 0 8px; + margin: 5px 0 0 8px; box-shadow: none; border-color: $input-border; color: $gl-text-color; diff --git a/app/assets/stylesheets/pages/members.scss b/app/assets/stylesheets/pages/members.scss index ff4fd751f26..756efa9c7fa 100644 --- a/app/assets/stylesheets/pages/members.scss +++ b/app/assets/stylesheets/pages/members.scss @@ -9,6 +9,10 @@ float: left; width: 50%; } + + strong { + font-weight: 600; + } } .controls { -- cgit v1.2.1 From fafc5a1777484ba6b1b12c5e88f3fc783fb4d839 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 13 Oct 2016 12:45:16 +0200 Subject: Perform CI build hooks asynchronously using worker --- app/models/ci/build.rb | 5 +++-- app/workers/build_hooks_worker.rb | 9 +++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 app/workers/build_hooks_worker.rb (limited to 'app') diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 5dbf66173de..b0d13c1bf06 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -1,6 +1,7 @@ module Ci class Build < CommitStatus include TokenAuthenticatable + include AfterCommitQueue belongs_to :runner, class_name: 'Ci::Runner' belongs_to :trigger_request, class_name: 'Ci::TriggerRequest' @@ -75,12 +76,12 @@ module Ci state_machine :status do after_transition pending: :running do |build| - build.execute_hooks + build.run_after_commit { BuildHooksWorker.perform_async(id) } end after_transition any => [:success, :failed, :canceled] do |build| build.update_coverage - build.execute_hooks + build.run_after_commit { BuildHooksWorker.perform_async(id) } end after_transition any => [:success] do |build| diff --git a/app/workers/build_hooks_worker.rb b/app/workers/build_hooks_worker.rb new file mode 100644 index 00000000000..e22ececb3fd --- /dev/null +++ b/app/workers/build_hooks_worker.rb @@ -0,0 +1,9 @@ +class BuildHooksWorker + include Sidekiq::Worker + sidekiq_options queue: :default + + def perform(build_id) + Ci::Build.find_by(id: build_id) + .try(:execute_hooks) + end +end -- cgit v1.2.1 From 204fdcb1abb9c76b2d4bd6260c6e5ce91529aeb8 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 13 Oct 2016 12:58:25 +0200 Subject: Add build success worker that runs asynchronously --- app/models/ci/build.rb | 21 +++++++++------------ app/workers/build_success_worker.rb | 27 +++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 12 deletions(-) create mode 100644 app/workers/build_success_worker.rb (limited to 'app') diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index b0d13c1bf06..ff70f3deb52 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -76,25 +76,22 @@ module Ci state_machine :status do after_transition pending: :running do |build| - build.run_after_commit { BuildHooksWorker.perform_async(id) } + build.run_after_commit do + BuildHooksWorker.perform_async(id) + end end after_transition any => [:success, :failed, :canceled] do |build| build.update_coverage - build.run_after_commit { BuildHooksWorker.perform_async(id) } + + build.run_after_commit do + BuildHooksWorker.perform_async(id) + end end after_transition any => [:success] do |build| - if build.environment.present? - service = CreateDeploymentService.new( - build.project, build.user, - environment: build.environment, - sha: build.sha, - ref: build.ref, - tag: build.tag, - options: build.options.to_h[:environment], - variables: build.variables) - service.execute(build) + build.run_after_commit do + BuildSuccessWorker.perform_async(id) end end end diff --git a/app/workers/build_success_worker.rb b/app/workers/build_success_worker.rb new file mode 100644 index 00000000000..d6db7642d82 --- /dev/null +++ b/app/workers/build_success_worker.rb @@ -0,0 +1,27 @@ +class BuildSuccessWorker + include Sidekiq::Worker + sidekiq_options queue: :default + + def perform(build_id) + Ci::Build.find_by(id: build_id).try do |build| + perform_deloyment(build) + end + end + + private + + def perform_deloyment(build) + return if build.environment.blank? + + service = CreateDeploymentService.new( + build.project, build.user, + environment: build.environment, + sha: build.sha, + ref: build.ref, + tag: build.tag, + options: build.options.to_h[:environment], + variables: build.variables) + + service.execute(build) + end +end -- cgit v1.2.1 From 03a8ed971154cb218b82e8be53943612de94999f Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 13 Oct 2016 13:23:23 +0200 Subject: Use trasaction to process build deployment --- app/services/create_deployment_service.rb | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) (limited to 'app') diff --git a/app/services/create_deployment_service.rb b/app/services/create_deployment_service.rb index 799ad3e1bd0..c6dc2148c11 100644 --- a/app/services/create_deployment_service.rb +++ b/app/services/create_deployment_service.rb @@ -2,25 +2,29 @@ require_relative 'base_service' class CreateDeploymentService < BaseService def execute(deployable = nil) - environment = find_or_create_environment + ActiveRecord::Base.transaction do + @deployable = deployable + @environment = prepare_environment - deployment = project.deployments.create( - environment: environment, + deploy.tap do |deployment| + deployment.update_merge_request_metrics! + end + end + end + + private + + def deploy + project.deployments.create( + environment: @environment, ref: params[:ref], tag: params[:tag], sha: params[:sha], user: current_user, - deployable: deployable - ) - - deployment.update_merge_request_metrics! - - deployment + deployable: @deployable) end - private - - def find_or_create_environment + def prepare_environment project.environments.find_or_create_by(name: expanded_name) do |environment| environment.external_url = expanded_url end -- cgit v1.2.1 From 8efa041a730515e57f127850414b3557c7af60b4 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 13 Oct 2016 14:55:44 +0200 Subject: Do not process build success if project was removed --- app/workers/build_success_worker.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'app') diff --git a/app/workers/build_success_worker.rb b/app/workers/build_success_worker.rb index d6db7642d82..5afb15f52b4 100644 --- a/app/workers/build_success_worker.rb +++ b/app/workers/build_success_worker.rb @@ -4,13 +4,15 @@ class BuildSuccessWorker def perform(build_id) Ci::Build.find_by(id: build_id).try do |build| - perform_deloyment(build) + return unless build.project + + create_deloyment(build) end end private - def perform_deloyment(build) + def create_deloyment(build) return if build.environment.blank? service = CreateDeploymentService.new( -- cgit v1.2.1 From 5fd635d18b77f56f6acd264ccaa1a357e2fa1cdd Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 13 Oct 2016 15:22:55 +0200 Subject: Update code coverage for CI build asynchronously --- app/workers/build_coverage_worker.rb | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 app/workers/build_coverage_worker.rb (limited to 'app') diff --git a/app/workers/build_coverage_worker.rb b/app/workers/build_coverage_worker.rb new file mode 100644 index 00000000000..0680645a8db --- /dev/null +++ b/app/workers/build_coverage_worker.rb @@ -0,0 +1,9 @@ +class BuildCoverageWorker + include Sidekiq::Worker + sidekiq_options queue: :default + + def perform(build_id) + Ci::Build.find_by(id: build_id) + .try(:update_coverage) + end +end -- cgit v1.2.1 From 4e9342599b472a28928a95fd43efc4df851f8e33 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 13 Oct 2016 15:24:33 +0200 Subject: Fix typo in build success worker for deployment --- app/workers/build_success_worker.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'app') diff --git a/app/workers/build_success_worker.rb b/app/workers/build_success_worker.rb index 5afb15f52b4..a9dc34166a1 100644 --- a/app/workers/build_success_worker.rb +++ b/app/workers/build_success_worker.rb @@ -6,13 +6,13 @@ class BuildSuccessWorker Ci::Build.find_by(id: build_id).try do |build| return unless build.project - create_deloyment(build) + create_deployment(build) end end private - def create_deloyment(build) + def create_deployment(build) return if build.environment.blank? service = CreateDeploymentService.new( -- cgit v1.2.1 From ae851edc3c76fd7b81de2e3d48ec8a531f3609b3 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Mon, 10 Oct 2016 03:19:55 +0100 Subject: Added no-template functionality Added tests --- .../javascripts/templates/issuable_template_selector.js.es6 | 8 +++++++- app/views/shared/issuable/_form.html.haml | 2 ++ 2 files changed, 9 insertions(+), 1 deletion(-) (limited to 'app') diff --git a/app/assets/javascripts/templates/issuable_template_selector.js.es6 b/app/assets/javascripts/templates/issuable_template_selector.js.es6 index 2ecf3b18975..bd4e3c3d00d 100644 --- a/app/assets/javascripts/templates/issuable_template_selector.js.es6 +++ b/app/assets/javascripts/templates/issuable_template_selector.js.es6 @@ -16,7 +16,13 @@ if (initialQuery.name) this.requestFile(initialQuery); $('.reset-template', this.dropdown.parent()).on('click', () => { - if (this.currentTemplate) this.setInputValueToTemplateContent(false); + this.setInputValueToTemplateContent(); + }); + + $('.no-template', this.dropdown.parent()).on('click', () => { + this.currentTemplate = ''; + this.setInputValueToTemplateContent(); + $('.dropdown-toggle-text', this.dropdown).text('Choose a template'); }); } diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml index c3f4e10c954..16834c3db29 100644 --- a/app/views/shared/issuable/_form.html.haml +++ b/app/views/shared/issuable/_form.html.haml @@ -23,6 +23,8 @@ data: { data: issuable_template_names, field_name: 'issuable_template', selected: selected_template(issuable), project_path: ref_project.path, namespace_path: ref_project.namespace.path } } ) do %ul.dropdown-footer-list %li + %a.no-template + No template %a.reset-template Reset template %div{ class: issuable_template_names.any? ? 'col-sm-7 col-lg-8' : 'col-sm-10' } -- cgit v1.2.1 From 8751491b8db471dc661daa19bc82a9dbd58e4aae Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Thu, 13 Oct 2016 13:58:31 -0500 Subject: Improve tabbing usability for sign in page --- app/assets/stylesheets/pages/login.scss | 10 ++++++++++ app/views/devise/sessions/_new_base.html.haml | 4 ++-- 2 files changed, 12 insertions(+), 2 deletions(-) (limited to 'app') diff --git a/app/assets/stylesheets/pages/login.scss b/app/assets/stylesheets/pages/login.scss index a5ca509163d..a08b033dff9 100644 --- a/app/assets/stylesheets/pages/login.scss +++ b/app/assets/stylesheets/pages/login.scss @@ -48,6 +48,16 @@ margin: 0 0 10px; } + .new_user { + position: relative; + padding-bottom: 35px; + } + + .sign-in { + position: absolute; + bottom: 0; + } + .login-footer { margin-top: 10px; diff --git a/app/views/devise/sessions/_new_base.html.haml b/app/views/devise/sessions/_new_base.html.haml index 9f5520603cd..781fd1b32a6 100644 --- a/app/views/devise/sessions/_new_base.html.haml +++ b/app/views/devise/sessions/_new_base.html.haml @@ -1,6 +1,8 @@ = form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| = f.text_field :login, class: "form-control top", placeholder: "Username or Email", autofocus: "autofocus", autocapitalize: "off", autocorrect: "off" = f.password_field :password, class: "form-control bottom", placeholder: "Password" + .sign-in + = f.submit "Sign in", class: "btn btn-save" - if devise_mapping.rememberable? .remember-me.checkbox %label{for: "user_remember_me"} @@ -8,5 +10,3 @@ %span Remember me .pull-right = link_to "Forgot your password?", new_password_path(resource_name) - %div - = f.submit "Sign in", class: "btn btn-save" -- cgit v1.2.1 From 6c7d3a0ee4c4c2b4e0a38fe92cd364c888eda907 Mon Sep 17 00:00:00 2001 From: blackst0ne Date: Fri, 14 Oct 2016 09:40:40 +1100 Subject: Remove '/u' prefix form username from Account page --- app/views/profiles/accounts/show.html.haml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'app') diff --git a/app/views/profiles/accounts/show.html.haml b/app/views/profiles/accounts/show.html.haml index c80f22457b4..8ee643f3bcc 100644 --- a/app/views/profiles/accounts/show.html.haml +++ b/app/views/profiles/accounts/show.html.haml @@ -86,11 +86,11 @@ = f.label :username, "Path", class: "label-light" .input-group .input-group-addon - = "#{root_url}u/" + = "#{root_url}" = f.text_field :username, required: true, class: 'form-control' .help-block Current path: - = "#{root_url}u/#{current_user.username}" + = "#{root_url}#{current_user.username}" .prepend-top-default = f.button class: "btn btn-warning", type: "submit" do = icon "spinner spin", class: "hidden loading-username" -- cgit v1.2.1 From 86d59657fc8ba0f96b8f957b1298a34144e060f9 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 6 Sep 2016 11:55:26 +0100 Subject: Increased performance of GL dropdown renderItem - Fixes an issue where `renderItem` is called several times even when not required - Increased performance when rendering dropdown items Closes #21110 --- app/assets/javascripts/gl_dropdown.js | 63 +++++++++++++++++------------------ 1 file changed, 31 insertions(+), 32 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js index e034ca68645..c0cb70cb427 100644 --- a/app/assets/javascripts/gl_dropdown.js +++ b/app/assets/javascripts/gl_dropdown.js @@ -37,12 +37,9 @@ e.preventDefault() } }) - .on('keyup', function(e) { + .on('input', function(e) { var keyCode; keyCode = e.which; - if (ARROW_KEY_CODES.indexOf(keyCode) >= 0) { - return; - } if (this.input.val() !== "" && !$inputContainer.hasClass(HAS_VALUE_CLASS)) { $inputContainer.addClass(HAS_VALUE_CLASS); } else if (this.input.val() === "" && $inputContainer.hasClass(HAS_VALUE_CLASS)) { @@ -500,14 +497,17 @@ // Render the full menu GitLabDropdown.prototype.renderMenu = function(html) { - var menu_html; - menu_html = ""; if (this.options.renderMenu) { - menu_html = this.options.renderMenu(html); + return this.options.renderMenu(html); } else { - menu_html = $('
    ').append(html); + var ul = document.createElement('ul'); + + for (var i = 0; i < html.length; i++) { + ul.appendChild(html[i]); + } + + return ul; } - return menu_html; }; // Append the menu into the dropdown @@ -521,7 +521,7 @@ }; GitLabDropdown.prototype.renderItem = function(data, group, index) { - var cssClass, field, fieldName, groupAttrs, html, selected, text, url, value; + var field, fieldName, html, selected, text, url, value; if (group == null) { group = false; } @@ -529,18 +529,16 @@ // Render the row index = false; } - html = ""; - // Divider - if (data === "divider") { - return "
  • "; - } - // Separator is a full-width divider - if (data === "separator") { - return "
  • "; + html = document.createElement('li'); + if (data === 'divider' || data === 'separator') { + html.className = data; + return html; } // Header if (data.header != null) { - return _.template('')({ header: data.header }); + html.className = 'dropdown-header'; + html.innerHTML = data.header; + return html; } if (this.options.renderRow) { // Call the render function @@ -567,24 +565,25 @@ } else { text = data.text != null ? data.text : ''; } - cssClass = ""; - if (selected) { - cssClass = "is-active"; - } if (this.highlight) { text = this.highlightTextMatches(text, this.filterInput.val()); } + // Create the list item & the link + var link = document.createElement('a'); + + link.href = url; + link.innerHTML = text; + + if (selected) { + link.className = 'is-active'; + } + if (group) { - groupAttrs = 'data-group=' + group + ' data-index=' + index; - } else { - groupAttrs = ''; + link.dataset.group = group; + link.dataset.index = index; } - html = _.template('
  • class="<%- cssClass %>"><%= text %>
  • ')({ - url: url, - groupAttrs: groupAttrs, - cssClass: cssClass, - text: text - }); + + html.appendChild(link); } return html; }; -- cgit v1.2.1 From ef5a76f68a89b9bee9070800ceb19753d0c602fe Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 6 Sep 2016 17:31:08 +0100 Subject: Fixed rendering of HTML strings --- app/assets/javascripts/gl_dropdown.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'app') diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js index c0cb70cb427..06b31b9b24b 100644 --- a/app/assets/javascripts/gl_dropdown.js +++ b/app/assets/javascripts/gl_dropdown.js @@ -503,7 +503,13 @@ var ul = document.createElement('ul'); for (var i = 0; i < html.length; i++) { - ul.appendChild(html[i]); + var el = html[i]; + + if (typeof el === 'string') { + ul.innerHTML += el; + } else { + ul.appendChild(el); + } } return ul; -- cgit v1.2.1 From ba4e6376e1a6284286d2af46a9f5f1afa6382fd0 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 7 Sep 2016 09:29:37 +0100 Subject: Fixed appending jQuery elements --- app/assets/javascripts/gl_dropdown.js | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'app') diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js index 06b31b9b24b..e94e7cd8be8 100644 --- a/app/assets/javascripts/gl_dropdown.js +++ b/app/assets/javascripts/gl_dropdown.js @@ -505,6 +505,10 @@ for (var i = 0; i < html.length; i++) { var el = html[i]; + if (el instanceof jQuery) { + el = el.get(0); + } + if (typeof el === 'string') { ul.innerHTML += el; } else { -- cgit v1.2.1 From e8901b20e9c07ae2654e267e5e88c0aa3476835d Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 7 Sep 2016 11:25:59 +0100 Subject: Changed trigger keyup to input --- app/assets/javascripts/gl_dropdown.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js index e94e7cd8be8..b7a70fab74e 100644 --- a/app/assets/javascripts/gl_dropdown.js +++ b/app/assets/javascripts/gl_dropdown.js @@ -25,7 +25,7 @@ return function(e) { e.preventDefault(); e.stopPropagation(); - return _this.input.val('').trigger('keyup').focus(); + return _this.input.val('').trigger('input').focus(); }; })(this)); // Key events @@ -38,8 +38,6 @@ } }) .on('input', function(e) { - var keyCode; - keyCode = e.which; if (this.input.val() !== "" && !$inputContainer.hasClass(HAS_VALUE_CLASS)) { $inputContainer.addClass(HAS_VALUE_CLASS); } else if (this.input.val() === "" && $inputContainer.hasClass(HAS_VALUE_CLASS)) { @@ -252,7 +250,7 @@ _this.fullData = data; _this.parseData(_this.fullData); if (_this.options.filterable && _this.filter && _this.filter.input) { - return _this.filter.input.trigger('keyup'); + return _this.filter.input.trigger('input'); } }; // Remote data @@ -484,7 +482,7 @@ // Triggering 'keyup' will re-render the dropdown which is not always required // specially if we want to keep the state of the dropdown needed for bulk-assignment if (!this.options.persistWhenHide) { - $input.trigger("keyup"); + $input.trigger("input"); } if (this.dropdown.find(".dropdown-toggle-page").length) { $('.dropdown-menu', this.dropdown).removeClass(PAGE_TWO_CLASS); -- cgit v1.2.1 From 88996deba5a1d8b29f03856b295b33caa7290981 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 7 Sep 2016 12:32:30 +0100 Subject: Fixed keycode undefined --- app/assets/javascripts/gl_dropdown.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js index b7a70fab74e..fe798509420 100644 --- a/app/assets/javascripts/gl_dropdown.js +++ b/app/assets/javascripts/gl_dropdown.js @@ -37,7 +37,7 @@ e.preventDefault() } }) - .on('input', function(e) { + .on('input', function() { if (this.input.val() !== "" && !$inputContainer.hasClass(HAS_VALUE_CLASS)) { $inputContainer.addClass(HAS_VALUE_CLASS); } else if (this.input.val() === "" && $inputContainer.hasClass(HAS_VALUE_CLASS)) { @@ -50,10 +50,6 @@ if (this.options.remote) { clearTimeout(timeout); return timeout = setTimeout(function() { - var blurField = this.shouldBlur(keyCode); - if (blurField && this.filterInputBlur) { - this.input.blur(); - } return this.options.query(this.input.val(), function(data) { return this.options.callback(data); }.bind(this)); -- cgit v1.2.1 From 3726dc4bb73647123ebef5c97f401f5537d3ae16 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 14 Oct 2016 10:19:16 +0200 Subject: Check if project exists before creating deployment --- app/services/create_deployment_service.rb | 6 ++++++ app/workers/build_success_worker.rb | 2 -- 2 files changed, 6 insertions(+), 2 deletions(-) (limited to 'app') diff --git a/app/services/create_deployment_service.rb b/app/services/create_deployment_service.rb index c6dc2148c11..ff9a8310a8c 100644 --- a/app/services/create_deployment_service.rb +++ b/app/services/create_deployment_service.rb @@ -2,6 +2,8 @@ require_relative 'base_service' class CreateDeploymentService < BaseService def execute(deployable = nil) + return unless executable? + ActiveRecord::Base.transaction do @deployable = deployable @environment = prepare_environment @@ -14,6 +16,10 @@ class CreateDeploymentService < BaseService private + def executable? + project && name.present? + end + def deploy project.deployments.create( environment: @environment, diff --git a/app/workers/build_success_worker.rb b/app/workers/build_success_worker.rb index a9dc34166a1..500d357ce31 100644 --- a/app/workers/build_success_worker.rb +++ b/app/workers/build_success_worker.rb @@ -4,8 +4,6 @@ class BuildSuccessWorker def perform(build_id) Ci::Build.find_by(id: build_id).try do |build| - return unless build.project - create_deployment(build) end end -- cgit v1.2.1 From ebddb5f329778ebf96d78c31e2ac93707a455864 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 14 Oct 2016 09:34:53 +0100 Subject: Fixed undefined keycode build error --- app/assets/javascripts/gl_dropdown.js | 3 --- 1 file changed, 3 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js index fe798509420..53762f2965c 100644 --- a/app/assets/javascripts/gl_dropdown.js +++ b/app/assets/javascripts/gl_dropdown.js @@ -43,9 +43,6 @@ } else if (this.input.val() === "" && $inputContainer.hasClass(HAS_VALUE_CLASS)) { $inputContainer.removeClass(HAS_VALUE_CLASS); } - if (keyCode === 13 && !options.elIsInput) { - return false; - } // Only filter asynchronously only if option remote is set if (this.options.remote) { clearTimeout(timeout); -- cgit v1.2.1 From b8003aa012843fac1745c94788b993c36570fbd9 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 14 Oct 2016 11:47:54 +0200 Subject: Calculate build coverage asynchronously --- app/models/ci/build.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'app') diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index ff70f3deb52..23119c5ed61 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -82,9 +82,8 @@ module Ci end after_transition any => [:success, :failed, :canceled] do |build| - build.update_coverage - build.run_after_commit do + BuildCoverageWorker.perform_async(id) BuildHooksWorker.perform_async(id) end end -- cgit v1.2.1 From 270434f7372dcb4d412f3f62068f69d8eac212e8 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 13 Oct 2016 08:34:35 +0100 Subject: Loads GFM once for per page #22827 --- app/views/layouts/application.html.haml | 1 + app/views/projects/_zen.html.haml | 4 +--- 2 files changed, 2 insertions(+), 3 deletions(-) (limited to 'app') diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index 15a94ac23c5..6c2285fa2b6 100644 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -11,3 +11,4 @@ = render 'layouts/page', sidebar: sidebar, nav: nav = yield :scripts_body + = render "layouts/init_auto_complete" if @gfm_form diff --git a/app/views/projects/_zen.html.haml b/app/views/projects/_zen.html.haml index cb97181b9e1..0c8241053e7 100644 --- a/app/views/projects/_zen.html.haml +++ b/app/views/projects/_zen.html.haml @@ -1,3 +1,4 @@ +- @gfm_form = true - supports_slash_commands = local_assigns.fetch(:supports_slash_commands, false) .zen-backdrop - classes << ' js-gfm-input js-autosize markdown-area' @@ -7,6 +8,3 @@ = text_area_tag attr, nil, class: classes, placeholder: placeholder %a.zen-control.zen-control-leave.js-zen-leave{ href: "#" } = icon('compress') - -- content_for :scripts_body do - = render "layouts/init_auto_complete" if current_user && (@target_project || @project) -- cgit v1.2.1 From eb4a42e0cef5e89b1d01cfa2a401908ffd09874c Mon Sep 17 00:00:00 2001 From: blackst0ne Date: Fri, 14 Oct 2016 21:26:43 +1100 Subject: Remove changelog item and interpolation --- app/views/profiles/accounts/show.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app') diff --git a/app/views/profiles/accounts/show.html.haml b/app/views/profiles/accounts/show.html.haml index 8ee643f3bcc..e2e974ba072 100644 --- a/app/views/profiles/accounts/show.html.haml +++ b/app/views/profiles/accounts/show.html.haml @@ -86,7 +86,7 @@ = f.label :username, "Path", class: "label-light" .input-group .input-group-addon - = "#{root_url}" + = root_url = f.text_field :username, required: true, class: 'form-control' .help-block Current path: -- cgit v1.2.1 From 5904793ad8ba88d3dfc9c973bcffd1d426db5a33 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 14 Oct 2016 12:53:51 +0200 Subject: Add build finished worker that creates a workflow --- app/models/ci/build.rb | 3 +-- app/workers/build_finished_worker.rb | 12 ++++++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) create mode 100644 app/workers/build_finished_worker.rb (limited to 'app') diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 23119c5ed61..87475119b23 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -83,8 +83,7 @@ module Ci after_transition any => [:success, :failed, :canceled] do |build| build.run_after_commit do - BuildCoverageWorker.perform_async(id) - BuildHooksWorker.perform_async(id) + BuildFinishedWorker.perform_async(id) end end diff --git a/app/workers/build_finished_worker.rb b/app/workers/build_finished_worker.rb new file mode 100644 index 00000000000..f1267ab92fc --- /dev/null +++ b/app/workers/build_finished_worker.rb @@ -0,0 +1,12 @@ +class BuildFinishedWorker + include Sidekiq::Worker + + def perform(build_id) + Ci::Build.find_by(id: build_id).try do |build| + build.with_lock do + BuildCoverageWorker.new.perform(build.id) + BuildHooksWorker.new.perform(build.id) + end + end + end +end -- cgit v1.2.1 From c736ffdfb043df34a89273639297cfc86d2d5f88 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 14 Oct 2016 16:04:23 +0300 Subject: Validate user id for users select autcomplete Single user autcomplete should be used only for existing users with digital ID provided. Now js code puts any input into generating user URL which can lead to 500 error because routing like this does not exists: GET "/autocomplete/users/whatever@example.com.json". Signed-off-by: Dmitriy Zaporozhets --- app/assets/javascripts/users_select.js | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'app') diff --git a/app/assets/javascripts/users_select.js b/app/assets/javascripts/users_select.js index 6aa0e1cd2b6..3020b7cc239 100644 --- a/app/assets/javascripts/users_select.js +++ b/app/assets/javascripts/users_select.js @@ -325,6 +325,10 @@ }; UsersSelect.prototype.user = function(user_id, callback) { + if(!/^\d+$/.test(user_id)) { + return false; + } + var url; url = this.buildUrl(this.userPath); url = url.replace(':id', user_id); -- cgit v1.2.1 From 9ec3c32f98f9548b4aa756c8fba74159b12492a7 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Fri, 14 Oct 2016 13:39:20 +0100 Subject: Improved specifity of toggleable file headers --- app/assets/stylesheets/framework/files.scss | 9 --------- app/assets/stylesheets/pages/diff.scss | 13 +++++++++++++ app/assets/stylesheets/pages/notes.scss | 8 ++++++++ 3 files changed, 21 insertions(+), 9 deletions(-) (limited to 'app') diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss index 81520500594..76a3c083697 100644 --- a/app/assets/stylesheets/framework/files.scss +++ b/app/assets/stylesheets/framework/files.scss @@ -26,15 +26,6 @@ padding: 10px $gl-padding; word-wrap: break-word; border-radius: 3px 3px 0 0; - cursor: pointer; - - &:hover { - background-color: $dark-background-color; - } - - .diff-toggle-caret { - padding-right: 6px; - } &.file-title-clear { padding-left: 0; diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index b8ef76cc74e..84094b0cb1b 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -33,6 +33,19 @@ font-size: smaller; } } + + .file-title { + cursor: pointer; + + &:hover { + background-color: $dark-background-color; + } + + .diff-toggle-caret { + padding-right: 6px; + } + } + .diff-content { overflow: auto; overflow-y: hidden; diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index d399f84a2ff..efeea96373f 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -147,6 +147,14 @@ ul.notes { // Diff code in discussion view .discussion-body .diff-file { + .file-title { + cursor: default; + + &:hover { + background-color: $gray-light; + } + } + .diff-header > span { margin-right: 10px; } -- cgit v1.2.1 From 308769f82b19a142f443fb101a01a60c2b19757f Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Fri, 14 Oct 2016 17:48:28 +0200 Subject: Remove unecessary lock --- app/workers/build_finished_worker.rb | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'app') diff --git a/app/workers/build_finished_worker.rb b/app/workers/build_finished_worker.rb index f1267ab92fc..e7286b77ac5 100644 --- a/app/workers/build_finished_worker.rb +++ b/app/workers/build_finished_worker.rb @@ -3,10 +3,8 @@ class BuildFinishedWorker def perform(build_id) Ci::Build.find_by(id: build_id).try do |build| - build.with_lock do - BuildCoverageWorker.new.perform(build.id) - BuildHooksWorker.new.perform(build.id) - end + BuildCoverageWorker.new.perform(build.id) + BuildHooksWorker.new.perform(build.id) end end end -- cgit v1.2.1 From b9f351e8798529f09316c0d8ab77a01c6a18f7bf Mon Sep 17 00:00:00 2001 From: "Luke \"Jared\" Bennett" Date: Fri, 14 Oct 2016 17:12:50 +0000 Subject: Rename pipeline.js.es6 to pipelines.js.es6 --- app/assets/javascripts/pipeline.js.es6 | 40 --------------------------------- app/assets/javascripts/pipelines.js.es6 | 40 +++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 40 deletions(-) delete mode 100644 app/assets/javascripts/pipeline.js.es6 create mode 100644 app/assets/javascripts/pipelines.js.es6 (limited to 'app') diff --git a/app/assets/javascripts/pipeline.js.es6 b/app/assets/javascripts/pipeline.js.es6 deleted file mode 100644 index 6bf63ee6979..00000000000 --- a/app/assets/javascripts/pipeline.js.es6 +++ /dev/null @@ -1,40 +0,0 @@ -((global) => { - - class Pipelines { - constructor() { - $(document).off('click', '.toggle-pipeline-btn').on('click', '.toggle-pipeline-btn', this.toggleGraph); - this.addMarginToBuildColumns(); - } - - toggleGraph() { - const $pipelineBtn = $(this).closest('.toggle-pipeline-btn'); - const $pipelineGraph = $(this).closest('.row-content-block').next('.pipeline-graph'); - const $btnText = $(this).find('.toggle-btn-text'); - const graphCollapsed = $pipelineGraph.hasClass('graph-collapsed'); - - $($pipelineBtn).add($pipelineGraph).toggleClass('graph-collapsed'); - - - graphCollapsed ? $btnText.text('Expand') : $btnText.text('Hide') - } - - addMarginToBuildColumns() { - const $secondChildBuildNode = $('.build:nth-child(2)'); - if ($secondChildBuildNode.length) { - const $firstChildBuildNode = $secondChildBuildNode.prev('.build'); - const $multiBuildColumn = $secondChildBuildNode.closest('.stage-column'); - const $previousColumn = $multiBuildColumn.prev('.stage-column'); - $multiBuildColumn.addClass('left-margin'); - $firstChildBuildNode.addClass('left-connector'); - $previousColumn.each(function() { - $this = $(this); - if ($('.build', $this).length === 1) $this.addClass('no-margin'); - }); - } - $('.pipeline-graph').removeClass('hidden'); - } - } - - global.Pipelines = Pipelines; - -})(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/pipelines.js.es6 b/app/assets/javascripts/pipelines.js.es6 new file mode 100644 index 00000000000..6bf63ee6979 --- /dev/null +++ b/app/assets/javascripts/pipelines.js.es6 @@ -0,0 +1,40 @@ +((global) => { + + class Pipelines { + constructor() { + $(document).off('click', '.toggle-pipeline-btn').on('click', '.toggle-pipeline-btn', this.toggleGraph); + this.addMarginToBuildColumns(); + } + + toggleGraph() { + const $pipelineBtn = $(this).closest('.toggle-pipeline-btn'); + const $pipelineGraph = $(this).closest('.row-content-block').next('.pipeline-graph'); + const $btnText = $(this).find('.toggle-btn-text'); + const graphCollapsed = $pipelineGraph.hasClass('graph-collapsed'); + + $($pipelineBtn).add($pipelineGraph).toggleClass('graph-collapsed'); + + + graphCollapsed ? $btnText.text('Expand') : $btnText.text('Hide') + } + + addMarginToBuildColumns() { + const $secondChildBuildNode = $('.build:nth-child(2)'); + if ($secondChildBuildNode.length) { + const $firstChildBuildNode = $secondChildBuildNode.prev('.build'); + const $multiBuildColumn = $secondChildBuildNode.closest('.stage-column'); + const $previousColumn = $multiBuildColumn.prev('.stage-column'); + $multiBuildColumn.addClass('left-margin'); + $firstChildBuildNode.addClass('left-connector'); + $previousColumn.each(function() { + $this = $(this); + if ($('.build', $this).length === 1) $this.addClass('no-margin'); + }); + } + $('.pipeline-graph').removeClass('hidden'); + } + } + + global.Pipelines = Pipelines; + +})(window.gl || (window.gl = {})); -- cgit v1.2.1 From e88aa25f42c394785d4c176046f10c815250c8b1 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Fri, 14 Oct 2016 15:39:08 -0500 Subject: Enforce TrailingSemicolon and EmptyLineBetweenBlocks in scss-lint --- app/assets/stylesheets/behaviors.scss | 1 + app/assets/stylesheets/framework/blocks.scss | 2 + app/assets/stylesheets/framework/buttons.scss | 6 +- app/assets/stylesheets/framework/callout.scss | 5 + app/assets/stylesheets/framework/common.scss | 39 +-- app/assets/stylesheets/framework/dropdowns.scss | 2 + app/assets/stylesheets/framework/files.scss | 9 + app/assets/stylesheets/framework/forms.scss | 4 +- app/assets/stylesheets/framework/gitlab-theme.scss | 2 +- app/assets/stylesheets/framework/header.scss | 1 + app/assets/stylesheets/framework/lists.scss | 1 + app/assets/stylesheets/framework/logo.scss | 7 + app/assets/stylesheets/framework/mixins.scss | 1 + app/assets/stylesheets/framework/nav.scss | 1 + app/assets/stylesheets/framework/selects.scss | 4 + app/assets/stylesheets/framework/timeline.scss | 1 + app/assets/stylesheets/framework/tw_bootstrap.scss | 10 + app/assets/stylesheets/framework/typography.scss | 1 + app/assets/stylesheets/highlight/dark.scss | 128 ++++----- app/assets/stylesheets/highlight/monokai.scss | 118 ++++----- .../stylesheets/highlight/solarized_dark.scss | 136 +++++----- .../stylesheets/highlight/solarized_light.scss | 136 +++++----- app/assets/stylesheets/highlight/white.scss | 2 +- .../stylesheets/mailers/repository_push_email.scss | 2 +- app/assets/stylesheets/notify.scss | 10 +- app/assets/stylesheets/pages/admin.scss | 12 +- app/assets/stylesheets/pages/builds.scss | 2 + app/assets/stylesheets/pages/commit.scss | 6 + app/assets/stylesheets/pages/commits.scss | 1 + app/assets/stylesheets/pages/dashboard.scss | 2 + app/assets/stylesheets/pages/diff.scss | 24 ++ app/assets/stylesheets/pages/editor.scss | 5 + app/assets/stylesheets/pages/events.scss | 8 +- app/assets/stylesheets/pages/groups.scss | 1 + app/assets/stylesheets/pages/help.scss | 8 +- app/assets/stylesheets/pages/issuable.scss | 8 +- app/assets/stylesheets/pages/issues.scss | 1 + app/assets/stylesheets/pages/labels.scss | 1 + app/assets/stylesheets/pages/lint.scss | 1 + app/assets/stylesheets/pages/login.scss | 2 +- app/assets/stylesheets/pages/merge_conflicts.scss | 2 + app/assets/stylesheets/pages/merge_requests.scss | 4 + app/assets/stylesheets/pages/milestone.scss | 1 + app/assets/stylesheets/pages/note_form.scss | 2 + app/assets/stylesheets/pages/notes.scss | 2 + app/assets/stylesheets/pages/pipelines.scss | 4 +- app/assets/stylesheets/pages/profile.scss | 1 + app/assets/stylesheets/pages/projects.scss | 18 ++ app/assets/stylesheets/pages/runners.scss | 1 + app/assets/stylesheets/pages/status.scss | 2 + app/assets/stylesheets/pages/tree.scss | 1 + app/assets/stylesheets/pages/xterm.scss | 290 ++++++++++++++++++++- 52 files changed, 743 insertions(+), 296 deletions(-) (limited to 'app') diff --git a/app/assets/stylesheets/behaviors.scss b/app/assets/stylesheets/behaviors.scss index 897bc49e7df..e3ca7f6373a 100644 --- a/app/assets/stylesheets/behaviors.scss +++ b/app/assets/stylesheets/behaviors.scss @@ -5,6 +5,7 @@ display: none; &.hide { display: block; } } + &.open .content { display: block; &.hide { display: none; } diff --git a/app/assets/stylesheets/framework/blocks.scss b/app/assets/stylesheets/framework/blocks.scss index 8002e56724b..df2e2ea8d2c 100644 --- a/app/assets/stylesheets/framework/blocks.scss +++ b/app/assets/stylesheets/framework/blocks.scss @@ -19,6 +19,7 @@ &.diff-collapsed { padding: 5px; + .click-to-expand { cursor: pointer; } @@ -203,6 +204,7 @@ } } } + &.user-cover-block { padding: 24px 0 0; } diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss index a7c8d782e9b..7c0ed72dbc5 100644 --- a/app/assets/stylesheets/framework/buttons.scss +++ b/app/assets/stylesheets/framework/buttons.scss @@ -25,7 +25,7 @@ &:focus { background-color: $hover-background; color: $hover-text; - border-color: $hover-border;; + border-color: $hover-border; } } @@ -240,6 +240,7 @@ width: 100%; margin: 0; margin-bottom: 15px; + &.btn { padding: 6px 0; } @@ -321,6 +322,7 @@ .btn-build { margin-left: 10px; + i { color: $gl-icon-color; } @@ -328,6 +330,7 @@ .clone-dropdown-btn a { color: $dropdown-link-color; + &:hover { text-decoration: none; } @@ -337,6 +340,7 @@ background-color: $background-color !important; border: 1px solid lightgrey; cursor: default; + &:active { -moz-box-shadow: inset 0 0 0 white; -webkit-box-shadow: inset 0 0 0 white; diff --git a/app/assets/stylesheets/framework/callout.scss b/app/assets/stylesheets/framework/callout.scss index da7bab74a32..f3b6ad88ad6 100644 --- a/app/assets/stylesheets/framework/callout.scss +++ b/app/assets/stylesheets/framework/callout.scss @@ -13,10 +13,12 @@ color: $text-color; background: $background-color; } + .bs-callout h4 { margin-top: 0; margin-bottom: 5px; } + .bs-callout p:last-child { margin-bottom: 0; } @@ -27,16 +29,19 @@ border-color: #eed3d7; color: #b94a48; } + .bs-callout-warning { background-color: #faf8f0; border-color: #faebcc; color: #8a6d3b; } + .bs-callout-info { background-color: #f4f8fa; border-color: #bce8f1; color: #34789a; } + .bs-callout-success { background-color: #dff0d8; border-color: #5ca64d; diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss index 5957dce89bc..81e4e264560 100644 --- a/app/assets/stylesheets/framework/common.scss +++ b/app/assets/stylesheets/framework/common.scss @@ -1,31 +1,31 @@ /** COLORS **/ .cgray { color: $gl-gray; } -.clgray { color: #bbb } +.clgray { color: #bbb; } .cred { color: $gl-text-red; } .cgreen { color: $gl-text-green; } -.cdark { color: #444 } +.cdark { color: #444; } /** COMMON CLASSES **/ .prepend-top-0 { margin-top: 0; } .prepend-top-5 { margin-top: 5px; } -.prepend-top-10 { margin-top: 10px } +.prepend-top-10 { margin-top: 10px; } .prepend-top-default { margin-top: $gl-padding !important; } -.prepend-top-20 { margin-top: 20px } -.prepend-left-5 { margin-left: 5px } -.prepend-left-10 { margin-left: 10px } +.prepend-top-20 { margin-top: 20px; } +.prepend-left-5 { margin-left: 5px; } +.prepend-left-10 { margin-left: 10px; } .prepend-left-default { margin-left: $gl-padding; } -.prepend-left-20 { margin-left: 20px } -.append-right-5 { margin-right: 5px } -.append-right-10 { margin-right: 10px } +.prepend-left-20 { margin-left: 20px; } +.append-right-5 { margin-right: 5px; } +.append-right-10 { margin-right: 10px; } .append-right-default { margin-right: $gl-padding; } -.append-right-20 { margin-right: 20px } -.append-bottom-0 { margin-bottom: 0 } -.append-bottom-10 { margin-bottom: 10px } -.append-bottom-15 { margin-bottom: 15px } -.append-bottom-20 { margin-bottom: 20px } +.append-right-20 { margin-right: 20px; } +.append-bottom-0 { margin-bottom: 0; } +.append-bottom-10 { margin-bottom: 10px; } +.append-bottom-15 { margin-bottom: 15px; } +.append-bottom-20 { margin-bottom: 20px; } .append-bottom-default { margin-bottom: $gl-padding; } -.inline { display: inline-block } -.center { text-align: center } +.inline { display: inline-block; } +.center { text-align: center; } .underlined-link { text-decoration: underline; } .hint { font-style: italic; color: #999; } @@ -97,6 +97,7 @@ span.update-author { color: #999; font-weight: normal; font-style: italic; + strong { font-weight: bold; font-style: normal; @@ -128,7 +129,7 @@ p.time { // Fix issue with notes & lists creating a bunch of bottom borders. li.note { - img { max-width: 100% } + img { max-width: 100%; } .note-title { li { border-bottom: none !important; @@ -172,6 +173,7 @@ li.note { @extend .col-md-6; text-align: left; margin-top: 40px; + pre { background: white; border: none; @@ -197,6 +199,7 @@ li.note { background: #c67; color: #fff; font-weight: bold; + a { color: #fff; text-decoration: underline; @@ -227,6 +230,7 @@ li.note { &.milestone-closed { background: $gray-light; } + .progress { margin-bottom: 0; margin-top: 4px; @@ -286,6 +290,7 @@ table { .footer-links { margin-bottom: 20px; + a { margin-right: 15px; } diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss index baa95711329..a839371a6f2 100644 --- a/app/assets/stylesheets/framework/dropdowns.scss +++ b/app/assets/stylesheets/framework/dropdowns.scss @@ -12,6 +12,7 @@ .dropdown-menu, .dropdown-menu-nav { display: block; + @media (max-width: $screen-xs-max) { width: 100%; } @@ -48,6 +49,7 @@ margin-top: -6px; color: $dropdown-toggle-icon-color; font-size: 10px; + &.fa-spinner { font-size: 16px; margin-top: -8px; diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss index 76a3c083697..13c1bbf0359 100644 --- a/app/assets/stylesheets/framework/files.scss +++ b/app/assets/stylesheets/framework/files.scss @@ -57,6 +57,7 @@ margin-top: -3px; } } + .file-content { background: #fff; @@ -96,22 +97,27 @@ border: none; margin: 0; } + tr { border-bottom: 1px solid #eee; } + td { &:first-child { border-left: none; } + &:last-child { border-right: none; } } + td.blame-commit { padding: 0 10px; min-width: 400px; background: $gray-light; } + td.line-numbers { float: none; border-left: 1px solid #ddd; @@ -121,6 +127,7 @@ margin-right: 0; } } + td.lines { padding: 0; } @@ -137,8 +144,10 @@ border-left: 1px solid $border-color; margin-bottom: 0; background: white; + li { color: #888; + p { margin: 0; color: #333; diff --git a/app/assets/stylesheets/framework/forms.scss b/app/assets/stylesheets/framework/forms.scss index 3d01179f074..311e3fa1a35 100644 --- a/app/assets/stylesheets/framework/forms.scss +++ b/app/assets/stylesheets/framework/forms.scss @@ -9,7 +9,7 @@ input { input[type='text'].danger { background: #f2dede!important; border-color: #d66; - text-shadow: 0 1px 1px #fff + text-shadow: 0 1px 1px #fff; } .datetime-controls { @@ -117,9 +117,11 @@ label { display: table-cell; width: 200px !important; } + .input-group-addon { background-color: #f7f8fa; } + .input-group-addon:not(:first-child):not(:last-child) { border-left: 0; border-right: 0; diff --git a/app/assets/stylesheets/framework/gitlab-theme.scss b/app/assets/stylesheets/framework/gitlab-theme.scss index 3673b81f183..fe834f4e2f6 100644 --- a/app/assets/stylesheets/framework/gitlab-theme.scss +++ b/app/assets/stylesheets/framework/gitlab-theme.scss @@ -62,7 +62,7 @@ } i { - color: $white-light + color: $white-light; } path, diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss index 9823abdde1f..3a4fdd0da22 100644 --- a/app/assets/stylesheets/framework/header.scss +++ b/app/assets/stylesheets/framework/header.scss @@ -168,6 +168,7 @@ header { a { color: $gl-text-color; + &:hover { text-decoration: underline; } diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss index 9114425cfdd..4b2627c1b87 100644 --- a/app/assets/stylesheets/framework/lists.scss +++ b/app/assets/stylesheets/framework/lists.scss @@ -60,6 +60,7 @@ padding-top: 1px; margin: 0; color: $gray-dark; + img { position: relative; top: 3px; diff --git a/app/assets/stylesheets/framework/logo.scss b/app/assets/stylesheets/framework/logo.scss index c214eabcad7..a90e45bb5f4 100644 --- a/app/assets/stylesheets/framework/logo.scss +++ b/app/assets/stylesheets/framework/logo.scss @@ -37,6 +37,7 @@ 0%, 10%, 100% { fill: lighten($tanuki-yellow, 25%); } + 90% { fill: $tanuki-yellow; } @@ -48,6 +49,7 @@ 10%, 80% { fill: $tanuki-orange; } + 20%, 90% { fill: lighten($tanuki-orange, 25%); } @@ -59,6 +61,7 @@ 10%, 80% { fill: $tanuki-red; } + 20%, 90% { fill: lighten($tanuki-red, 25%); } @@ -70,6 +73,7 @@ 20%, 70% { fill: $tanuki-red; } + 30%, 80% { fill: lighten($tanuki-red, 25%); } @@ -81,6 +85,7 @@ 30%, 60% { fill: $tanuki-orange; } + 40%, 70% { fill: lighten($tanuki-orange, 25%); } @@ -92,6 +97,7 @@ 30%, 60% { fill: $tanuki-red; } + 40%, 70% { fill: lighten($tanuki-red, 25%); } @@ -103,6 +109,7 @@ 40% { fill: $tanuki-yellow; } + 60% { fill: lighten($tanuki-yellow, 25%); } diff --git a/app/assets/stylesheets/framework/mixins.scss b/app/assets/stylesheets/framework/mixins.scss index 7fabf27a558..f84ca36d10f 100644 --- a/app/assets/stylesheets/framework/mixins.scss +++ b/app/assets/stylesheets/framework/mixins.scss @@ -34,6 +34,7 @@ &.active { background: $gray-light; + a { font-weight: 600; } diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss index ea43f4afc37..899db045b74 100644 --- a/app/assets/stylesheets/framework/nav.scss +++ b/app/assets/stylesheets/framework/nav.scss @@ -210,6 +210,7 @@ @media (max-width: $screen-xs-max) { padding-bottom: 0; width: 100%; + .btn, form, .dropdown, .dropdown-menu-toggle, .form-control { margin: 0 0 10px; display: block; diff --git a/app/assets/stylesheets/framework/selects.scss b/app/assets/stylesheets/framework/selects.scss index bf9208f83f3..e0708c65695 100644 --- a/app/assets/stylesheets/framework/selects.scss +++ b/app/assets/stylesheets/framework/selects.scss @@ -137,6 +137,7 @@ .select2-results { max-height: 350px; + .select2-highlighted { background: $gl-primary; } @@ -212,9 +213,11 @@ .group-image { float: left; } + .group-name { font-weight: bold; } + .group-path { color: #999; } @@ -239,6 +242,7 @@ color: #aaa; font-weight: normal; } + .namespace-path { margin-left: 10px; font-weight: bolder; diff --git a/app/assets/stylesheets/framework/timeline.scss b/app/assets/stylesheets/framework/timeline.scss index 0b0bd80c326..eb63a9f214b 100644 --- a/app/assets/stylesheets/framework/timeline.scss +++ b/app/assets/stylesheets/framework/timeline.scss @@ -48,6 +48,7 @@ &:before { background: none; } + .timeline-entry .timeline-entry-inner { .timeline-icon { display: none; diff --git a/app/assets/stylesheets/framework/tw_bootstrap.scss b/app/assets/stylesheets/framework/tw_bootstrap.scss index e3154657c54..f4106641269 100644 --- a/app/assets/stylesheets/framework/tw_bootstrap.scss +++ b/app/assets/stylesheets/framework/tw_bootstrap.scss @@ -48,31 +48,40 @@ .clearfix { @include clearfix(); } + .center-block { @include center-block(); } + .pull-right { float: right !important; } + .pull-left { float: left !important; } + .hide { display: none; } + .show { display: block !important; } + .invisible { visibility: hidden; } + .text-hide { @include text-hide(); } + .hidden { display: none !important; visibility: hidden !important; } + .affix { position: fixed; } @@ -146,6 +155,7 @@ padding: 6px 15px; font-size: 13px; font-weight: normal; + a { color: #777; } diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss index d099a884f54..8df0067fac1 100644 --- a/app/assets/stylesheets/framework/typography.scss +++ b/app/assets/stylesheets/framework/typography.scss @@ -106,6 +106,7 @@ @extend .table-bordered; margin: 12px 0; color: #5c5d5e; + th { background: #f8fafc; } diff --git a/app/assets/stylesheets/highlight/dark.scss b/app/assets/stylesheets/highlight/dark.scss index 16ffbe57a99..a3acee299e3 100644 --- a/app/assets/stylesheets/highlight/dark.scss +++ b/app/assets/stylesheets/highlight/dark.scss @@ -55,68 +55,68 @@ color: #000 !important; } - .hll { background-color: #373b41 } - .c { color: #969896 } /* Comment */ - .err { color: #c66 } /* Error */ - .k { color: #b294bb } /* Keyword */ - .l { color: #de935f } /* Literal */ - .n { color: #c5c8c6 } /* Name */ - .o { color: #8abeb7 } /* Operator */ - .p { color: #c5c8c6 } /* Punctuation */ - .cm { color: #969896 } /* Comment.Multiline */ - .cp { color: #969896 } /* Comment.Preproc */ - .c1 { color: #969896 } /* Comment.Single */ - .cs { color: #969896 } /* Comment.Special */ - .gd { color: #c66 } /* Generic.Deleted */ - .ge { font-style: italic } /* Generic.Emph */ - .gh { color: #c5c8c6; font-weight: bold } /* Generic.Heading */ - .gi { color: #b5bd68 } /* Generic.Inserted */ - .gp { color: #969896; font-weight: bold } /* Generic.Prompt */ - .gs { font-weight: bold } /* Generic.Strong */ - .gu { color: #8abeb7; font-weight: bold } /* Generic.Subheading */ - .kc { color: #b294bb } /* Keyword.Constant */ - .kd { color: #b294bb } /* Keyword.Declaration */ - .kn { color: #8abeb7 } /* Keyword.Namespace */ - .kp { color: #b294bb } /* Keyword.Pseudo */ - .kr { color: #b294bb } /* Keyword.Reserved */ - .kt { color: #f0c674 } /* Keyword.Type */ - .ld { color: #b5bd68 } /* Literal.Date */ - .m { color: #de935f } /* Literal.Number */ - .s { color: #b5bd68 } /* Literal.String */ - .na { color: #81a2be } /* Name.Attribute */ - .nb { color: #c5c8c6 } /* Name.Builtin */ - .nc { color: #f0c674 } /* Name.Class */ - .no { color: #c66 } /* Name.Constant */ - .nd { color: #8abeb7 } /* Name.Decorator */ - .ni { color: #c5c8c6 } /* Name.Entity */ - .ne { color: #c66 } /* Name.Exception */ - .nf { color: #81a2be } /* Name.Function */ - .nl { color: #c5c8c6 } /* Name.Label */ - .nn { color: #f0c674 } /* Name.Namespace */ - .nx { color: #81a2be } /* Name.Other */ - .py { color: #c5c8c6 } /* Name.Property */ - .nt { color: #8abeb7 } /* Name.Tag */ - .nv { color: #c66 } /* Name.Variable */ - .ow { color: #8abeb7 } /* Operator.Word */ - .w { color: #c5c8c6 } /* Text.Whitespace */ - .mf { color: #de935f } /* Literal.Number.Float */ - .mh { color: #de935f } /* Literal.Number.Hex */ - .mi { color: #de935f } /* Literal.Number.Integer */ - .mo { color: #de935f } /* Literal.Number.Oct */ - .sb { color: #b5bd68 } /* Literal.String.Backtick */ - .sc { color: #c5c8c6 } /* Literal.String.Char */ - .sd { color: #969896 } /* Literal.String.Doc */ - .s2 { color: #b5bd68 } /* Literal.String.Double */ - .se { color: #de935f } /* Literal.String.Escape */ - .sh { color: #b5bd68 } /* Literal.String.Heredoc */ - .si { color: #de935f } /* Literal.String.Interpol */ - .sx { color: #b5bd68 } /* Literal.String.Other */ - .sr { color: #b5bd68 } /* Literal.String.Regex */ - .s1 { color: #b5bd68 } /* Literal.String.Single */ - .ss { color: #b5bd68 } /* Literal.String.Symbol */ - .bp { color: #c5c8c6 } /* Name.Builtin.Pseudo */ - .vc { color: #c66 } /* Name.Variable.Class */ - .vg { color: #c66 } /* Name.Variable.Global */ - .vi { color: #c66 } /* Name.Variable.Instance */ - .il { color: #de935f } /* Literal.Number.Integer.Long */ + .hll { background-color: #373b41; } + .c { color: #969896; } /* Comment */ + .err { color: #c66; } /* Error */ + .k { color: #b294bb; } /* Keyword */ + .l { color: #de935f; } /* Literal */ + .n { color: #c5c8c6; } /* Name */ + .o { color: #8abeb7; } /* Operator */ + .p { color: #c5c8c6; } /* Punctuation */ + .cm { color: #969896; } /* Comment.Multiline */ + .cp { color: #969896; } /* Comment.Preproc */ + .c1 { color: #969896; } /* Comment.Single */ + .cs { color: #969896; } /* Comment.Special */ + .gd { color: #c66; } /* Generic.Deleted */ + .ge { font-style: italic; } /* Generic.Emph */ + .gh { color: #c5c8c6; font-weight: bold; } /* Generic.Heading */ + .gi { color: #b5bd68; } /* Generic.Inserted */ + .gp { color: #969896; font-weight: bold; } /* Generic.Prompt */ + .gs { font-weight: bold; } /* Generic.Strong */ + .gu { color: #8abeb7; font-weight: bold; } /* Generic.Subheading */ + .kc { color: #b294bb; } /* Keyword.Constant */ + .kd { color: #b294bb; } /* Keyword.Declaration */ + .kn { color: #8abeb7; } /* Keyword.Namespace */ + .kp { color: #b294bb; } /* Keyword.Pseudo */ + .kr { color: #b294bb; } /* Keyword.Reserved */ + .kt { color: #f0c674; } /* Keyword.Type */ + .ld { color: #b5bd68; } /* Literal.Date */ + .m { color: #de935f; } /* Literal.Number */ + .s { color: #b5bd68; } /* Literal.String */ + .na { color: #81a2be; } /* Name.Attribute */ + .nb { color: #c5c8c6; } /* Name.Builtin */ + .nc { color: #f0c674; } /* Name.Class */ + .no { color: #c66; } /* Name.Constant */ + .nd { color: #8abeb7; } /* Name.Decorator */ + .ni { color: #c5c8c6; } /* Name.Entity */ + .ne { color: #c66; } /* Name.Exception */ + .nf { color: #81a2be; } /* Name.Function */ + .nl { color: #c5c8c6; } /* Name.Label */ + .nn { color: #f0c674; } /* Name.Namespace */ + .nx { color: #81a2be; } /* Name.Other */ + .py { color: #c5c8c6; } /* Name.Property */ + .nt { color: #8abeb7; } /* Name.Tag */ + .nv { color: #c66; } /* Name.Variable */ + .ow { color: #8abeb7; } /* Operator.Word */ + .w { color: #c5c8c6; } /* Text.Whitespace */ + .mf { color: #de935f; } /* Literal.Number.Float */ + .mh { color: #de935f; } /* Literal.Number.Hex */ + .mi { color: #de935f; } /* Literal.Number.Integer */ + .mo { color: #de935f; } /* Literal.Number.Oct */ + .sb { color: #b5bd68; } /* Literal.String.Backtick */ + .sc { color: #c5c8c6; } /* Literal.String.Char */ + .sd { color: #969896; } /* Literal.String.Doc */ + .s2 { color: #b5bd68; } /* Literal.String.Double */ + .se { color: #de935f; } /* Literal.String.Escape */ + .sh { color: #b5bd68; } /* Literal.String.Heredoc */ + .si { color: #de935f; } /* Literal.String.Interpol */ + .sx { color: #b5bd68; } /* Literal.String.Other */ + .sr { color: #b5bd68; } /* Literal.String.Regex */ + .s1 { color: #b5bd68; } /* Literal.String.Single */ + .ss { color: #b5bd68; } /* Literal.String.Symbol */ + .bp { color: #c5c8c6; } /* Name.Builtin.Pseudo */ + .vc { color: #c66; } /* Name.Variable.Class */ + .vg { color: #c66; } /* Name.Variable.Global */ + .vi { color: #c66; } /* Name.Variable.Instance */ + .il { color: #de935f; } /* Literal.Number.Integer.Long */ } diff --git a/app/assets/stylesheets/highlight/monokai.scss b/app/assets/stylesheets/highlight/monokai.scss index 7de920e074b..e9228c94db9 100644 --- a/app/assets/stylesheets/highlight/monokai.scss +++ b/app/assets/stylesheets/highlight/monokai.scss @@ -55,65 +55,65 @@ color: #000 !important; } - .hll { background-color: #49483e } - .c { color: #75715e } /* Comment */ - .err { color: #960050; background-color: #1e0010 } /* Error */ - .k { color: #66d9ef } /* Keyword */ - .l { color: #ae81ff } /* Literal */ - .n { color: #f8f8f2 } /* Name */ - .o { color: #f92672 } /* Operator */ - .p { color: #f8f8f2 } /* Punctuation */ - .cm { color: #75715e } /* Comment.Multiline */ - .cp { color: #75715e } /* Comment.Preproc */ - .c1 { color: #75715e } /* Comment.Single */ - .cs { color: #75715e } /* Comment.Special */ - .ge { font-style: italic } /* Generic.Emph */ - .gs { font-weight: bold } /* Generic.Strong */ - .kc { color: #66d9ef } /* Keyword.Constant */ - .kd { color: #66d9ef } /* Keyword.Declaration */ - .kn { color: #f92672 } /* Keyword.Namespace */ - .kp { color: #66d9ef } /* Keyword.Pseudo */ - .kr { color: #66d9ef } /* Keyword.Reserved */ - .kt { color: #66d9ef } /* Keyword.Type */ - .ld { color: #e6db74 } /* Literal.Date */ - .m { color: #ae81ff } /* Literal.Number */ - .s { color: #e6db74 } /* Literal.String */ - .na { color: #a6e22e } /* Name.Attribute */ - .nb { color: #f8f8f2 } /* Name.Builtin */ - .nc { color: #a6e22e } /* Name.Class */ - .no { color: #66d9ef } /* Name.Constant */ - .nd { color: #a6e22e } /* Name.Decorator */ - .ni { color: #f8f8f2 } /* Name.Entity */ - .ne { color: #a6e22e } /* Name.Exception */ - .nf { color: #a6e22e } /* Name.Function */ - .nl { color: #f8f8f2 } /* Name.Label */ - .nn { color: #f8f8f2 } /* Name.Namespace */ - .nx { color: #a6e22e } /* Name.Other */ - .py { color: #f8f8f2 } /* Name.Property */ - .nt { color: #f92672 } /* Name.Tag */ - .nv { color: #f8f8f2 } /* Name.Variable */ - .ow { color: #f92672 } /* Operator.Word */ - .w { color: #f8f8f2 } /* Text.Whitespace */ - .mf { color: #ae81ff } /* Literal.Number.Float */ - .mh { color: #ae81ff } /* Literal.Number.Hex */ - .mi { color: #ae81ff } /* Literal.Number.Integer */ - .mo { color: #ae81ff } /* Literal.Number.Oct */ - .sb { color: #e6db74 } /* Literal.String.Backtick */ - .sc { color: #e6db74 } /* Literal.String.Char */ - .sd { color: #e6db74 } /* Literal.String.Doc */ - .s2 { color: #e6db74 } /* Literal.String.Double */ - .se { color: #ae81ff } /* Literal.String.Escape */ - .sh { color: #e6db74 } /* Literal.String.Heredoc */ - .si { color: #e6db74 } /* Literal.String.Interpol */ - .sx { color: #e6db74 } /* Literal.String.Other */ - .sr { color: #e6db74 } /* Literal.String.Regex */ - .s1 { color: #e6db74 } /* Literal.String.Single */ - .ss { color: #e6db74 } /* Literal.String.Symbol */ - .bp { color: #f8f8f2 } /* Name.Builtin.Pseudo */ - .vc { color: #f8f8f2 } /* Name.Variable.Class */ - .vg { color: #f8f8f2 } /* Name.Variable.Global */ - .vi { color: #f8f8f2 } /* Name.Variable.Instance */ - .il { color: #ae81ff } /* Literal.Number.Integer.Long */ + .hll { background-color: #49483e; } + .c { color: #75715e; } /* Comment */ + .err { color: #960050; background-color: #1e0010; } /* Error */ + .k { color: #66d9ef; } /* Keyword */ + .l { color: #ae81ff; } /* Literal */ + .n { color: #f8f8f2; } /* Name */ + .o { color: #f92672; } /* Operator */ + .p { color: #f8f8f2; } /* Punctuation */ + .cm { color: #75715e; } /* Comment.Multiline */ + .cp { color: #75715e; } /* Comment.Preproc */ + .c1 { color: #75715e; } /* Comment.Single */ + .cs { color: #75715e; } /* Comment.Special */ + .ge { font-style: italic; } /* Generic.Emph */ + .gs { font-weight: bold; } /* Generic.Strong */ + .kc { color: #66d9ef; } /* Keyword.Constant */ + .kd { color: #66d9ef; } /* Keyword.Declaration */ + .kn { color: #f92672; } /* Keyword.Namespace */ + .kp { color: #66d9ef; } /* Keyword.Pseudo */ + .kr { color: #66d9ef; } /* Keyword.Reserved */ + .kt { color: #66d9ef; } /* Keyword.Type */ + .ld { color: #e6db74; } /* Literal.Date */ + .m { color: #ae81ff; } /* Literal.Number */ + .s { color: #e6db74; } /* Literal.String */ + .na { color: #a6e22e; } /* Name.Attribute */ + .nb { color: #f8f8f2; } /* Name.Builtin */ + .nc { color: #a6e22e; } /* Name.Class */ + .no { color: #66d9ef; } /* Name.Constant */ + .nd { color: #a6e22e; } /* Name.Decorator */ + .ni { color: #f8f8f2; } /* Name.Entity */ + .ne { color: #a6e22e; } /* Name.Exception */ + .nf { color: #a6e22e; } /* Name.Function */ + .nl { color: #f8f8f2; } /* Name.Label */ + .nn { color: #f8f8f2; } /* Name.Namespace */ + .nx { color: #a6e22e; } /* Name.Other */ + .py { color: #f8f8f2; } /* Name.Property */ + .nt { color: #f92672; } /* Name.Tag */ + .nv { color: #f8f8f2; } /* Name.Variable */ + .ow { color: #f92672; } /* Operator.Word */ + .w { color: #f8f8f2; } /* Text.Whitespace */ + .mf { color: #ae81ff; } /* Literal.Number.Float */ + .mh { color: #ae81ff; } /* Literal.Number.Hex */ + .mi { color: #ae81ff; } /* Literal.Number.Integer */ + .mo { color: #ae81ff; } /* Literal.Number.Oct */ + .sb { color: #e6db74; } /* Literal.String.Backtick */ + .sc { color: #e6db74; } /* Literal.String.Char */ + .sd { color: #e6db74; } /* Literal.String.Doc */ + .s2 { color: #e6db74; } /* Literal.String.Double */ + .se { color: #ae81ff; } /* Literal.String.Escape */ + .sh { color: #e6db74; } /* Literal.String.Heredoc */ + .si { color: #e6db74; } /* Literal.String.Interpol */ + .sx { color: #e6db74; } /* Literal.String.Other */ + .sr { color: #e6db74; } /* Literal.String.Regex */ + .s1 { color: #e6db74; } /* Literal.String.Single */ + .ss { color: #e6db74; } /* Literal.String.Symbol */ + .bp { color: #f8f8f2; } /* Name.Builtin.Pseudo */ + .vc { color: #f8f8f2; } /* Name.Variable.Class */ + .vg { color: #f8f8f2; } /* Name.Variable.Global */ + .vi { color: #f8f8f2; } /* Name.Variable.Instance */ + .il { color: #ae81ff; } /* Literal.Number.Integer.Long */ .gu { color: #75715e; } /* Generic.Subheading & Diff Unified/Comment? */ .gd { color: #f92672; } /* Generic.Deleted & Diff Deleted */ .gi { color: #a6e22e; } /* Generic.Inserted & Diff Inserted */ diff --git a/app/assets/stylesheets/highlight/solarized_dark.scss b/app/assets/stylesheets/highlight/solarized_dark.scss index b11499c71ee..c3c7773b9e2 100644 --- a/app/assets/stylesheets/highlight/solarized_dark.scss +++ b/app/assets/stylesheets/highlight/solarized_dark.scss @@ -72,72 +72,72 @@ green #859900 operators, other keywords */ - .c { color: #586e75 } /* Comment */ - .err { color: #93a1a1 } /* Error */ - .g { color: #93a1a1 } /* Generic */ - .k { color: #859900 } /* Keyword */ - .l { color: #93a1a1 } /* Literal */ - .n { color: #93a1a1 } /* Name */ - .o { color: #859900 } /* Operator */ - .x { color: #cb4b16 } /* Other */ - .p { color: #93a1a1 } /* Punctuation */ - .cm { color: #586e75 } /* Comment.Multiline */ - .cp { color: #859900 } /* Comment.Preproc */ - .c1 { color: #586e75 } /* Comment.Single */ - .cs { color: #859900 } /* Comment.Special */ - .gd { color: #2aa198 } /* Generic.Deleted */ - .ge { color: #93a1a1; font-style: italic } /* Generic.Emph */ - .gr { color: #dc322f } /* Generic.Error */ - .gh { color: #cb4b16 } /* Generic.Heading */ - .gi { color: #859900 } /* Generic.Inserted */ - .go { color: #93a1a1 } /* Generic.Output */ - .gp { color: #93a1a1 } /* Generic.Prompt */ - .gs { color: #93a1a1; font-weight: bold } /* Generic.Strong */ - .gu { color: #cb4b16 } /* Generic.Subheading */ - .gt { color: #93a1a1 } /* Generic.Traceback */ - .kc { color: #cb4b16 } /* Keyword.Constant */ - .kd { color: #268bd2 } /* Keyword.Declaration */ - .kn { color: #859900 } /* Keyword.Namespace */ - .kp { color: #859900 } /* Keyword.Pseudo */ - .kr { color: #268bd2 } /* Keyword.Reserved */ - .kt { color: #dc322f } /* Keyword.Type */ - .ld { color: #93a1a1 } /* Literal.Date */ - .m { color: #2aa198 } /* Literal.Number */ - .s { color: #2aa198 } /* Literal.String */ - .na { color: #93a1a1 } /* Name.Attribute */ - .nb { color: #b58900 } /* Name.Builtin */ - .nc { color: #268bd2 } /* Name.Class */ - .no { color: #cb4b16 } /* Name.Constant */ - .nd { color: #268bd2 } /* Name.Decorator */ - .ni { color: #cb4b16 } /* Name.Entity */ - .ne { color: #cb4b16 } /* Name.Exception */ - .nf { color: #268bd2 } /* Name.Function */ - .nl { color: #93a1a1 } /* Name.Label */ - .nn { color: #93a1a1 } /* Name.Namespace */ - .nx { color: #93a1a1 } /* Name.Other */ - .py { color: #93a1a1 } /* Name.Property */ - .nt { color: #268bd2 } /* Name.Tag */ - .nv { color: #268bd2 } /* Name.Variable */ - .ow { color: #859900 } /* Operator.Word */ - .w { color: #93a1a1 } /* Text.Whitespace */ - .mf { color: #2aa198 } /* Literal.Number.Float */ - .mh { color: #2aa198 } /* Literal.Number.Hex */ - .mi { color: #2aa198 } /* Literal.Number.Integer */ - .mo { color: #2aa198 } /* Literal.Number.Oct */ - .sb { color: #586e75 } /* Literal.String.Backtick */ - .sc { color: #2aa198 } /* Literal.String.Char */ - .sd { color: #93a1a1 } /* Literal.String.Doc */ - .s2 { color: #2aa198 } /* Literal.String.Double */ - .se { color: #cb4b16 } /* Literal.String.Escape */ - .sh { color: #93a1a1 } /* Literal.String.Heredoc */ - .si { color: #2aa198 } /* Literal.String.Interpol */ - .sx { color: #2aa198 } /* Literal.String.Other */ - .sr { color: #dc322f } /* Literal.String.Regex */ - .s1 { color: #2aa198 } /* Literal.String.Single */ - .ss { color: #2aa198 } /* Literal.String.Symbol */ - .bp { color: #268bd2 } /* Name.Builtin.Pseudo */ - .vc { color: #268bd2 } /* Name.Variable.Class */ - .vg { color: #268bd2 } /* Name.Variable.Global */ - .vi { color: #268bd2 } /* Name.Variable.Instance */ - .il { color: #2aa198 } /* Literal.Number.Integer.Long */ + .c { color: #586e75; } /* Comment */ + .err { color: #93a1a1; } /* Error */ + .g { color: #93a1a1; } /* Generic */ + .k { color: #859900; } /* Keyword */ + .l { color: #93a1a1; } /* Literal */ + .n { color: #93a1a1; } /* Name */ + .o { color: #859900; } /* Operator */ + .x { color: #cb4b16; } /* Other */ + .p { color: #93a1a1; } /* Punctuation */ + .cm { color: #586e75; } /* Comment.Multiline */ + .cp { color: #859900; } /* Comment.Preproc */ + .c1 { color: #586e75; } /* Comment.Single */ + .cs { color: #859900; } /* Comment.Special */ + .gd { color: #2aa198; } /* Generic.Deleted */ + .ge { color: #93a1a1; font-style: italic; } /* Generic.Emph */ + .gr { color: #dc322f; } /* Generic.Error */ + .gh { color: #cb4b16; } /* Generic.Heading */ + .gi { color: #859900; } /* Generic.Inserted */ + .go { color: #93a1a1; } /* Generic.Output */ + .gp { color: #93a1a1; } /* Generic.Prompt */ + .gs { color: #93a1a1; font-weight: bold; } /* Generic.Strong */ + .gu { color: #cb4b16; } /* Generic.Subheading */ + .gt { color: #93a1a1; } /* Generic.Traceback */ + .kc { color: #cb4b16; } /* Keyword.Constant */ + .kd { color: #268bd2; } /* Keyword.Declaration */ + .kn { color: #859900; } /* Keyword.Namespace */ + .kp { color: #859900; } /* Keyword.Pseudo */ + .kr { color: #268bd2; } /* Keyword.Reserved */ + .kt { color: #dc322f; } /* Keyword.Type */ + .ld { color: #93a1a1; } /* Literal.Date */ + .m { color: #2aa198; } /* Literal.Number */ + .s { color: #2aa198; } /* Literal.String */ + .na { color: #93a1a1; } /* Name.Attribute */ + .nb { color: #b58900; } /* Name.Builtin */ + .nc { color: #268bd2; } /* Name.Class */ + .no { color: #cb4b16; } /* Name.Constant */ + .nd { color: #268bd2; } /* Name.Decorator */ + .ni { color: #cb4b16; } /* Name.Entity */ + .ne { color: #cb4b16; } /* Name.Exception */ + .nf { color: #268bd2; } /* Name.Function */ + .nl { color: #93a1a1; } /* Name.Label */ + .nn { color: #93a1a1; } /* Name.Namespace */ + .nx { color: #93a1a1; } /* Name.Other */ + .py { color: #93a1a1; } /* Name.Property */ + .nt { color: #268bd2; } /* Name.Tag */ + .nv { color: #268bd2; } /* Name.Variable */ + .ow { color: #859900; } /* Operator.Word */ + .w { color: #93a1a1; } /* Text.Whitespace */ + .mf { color: #2aa198; } /* Literal.Number.Float */ + .mh { color: #2aa198; } /* Literal.Number.Hex */ + .mi { color: #2aa198; } /* Literal.Number.Integer */ + .mo { color: #2aa198; } /* Literal.Number.Oct */ + .sb { color: #586e75; } /* Literal.String.Backtick */ + .sc { color: #2aa198; } /* Literal.String.Char */ + .sd { color: #93a1a1; } /* Literal.String.Doc */ + .s2 { color: #2aa198; } /* Literal.String.Double */ + .se { color: #cb4b16; } /* Literal.String.Escape */ + .sh { color: #93a1a1; } /* Literal.String.Heredoc */ + .si { color: #2aa198; } /* Literal.String.Interpol */ + .sx { color: #2aa198; } /* Literal.String.Other */ + .sr { color: #dc322f; } /* Literal.String.Regex */ + .s1 { color: #2aa198; } /* Literal.String.Single */ + .ss { color: #2aa198; } /* Literal.String.Symbol */ + .bp { color: #268bd2; } /* Name.Builtin.Pseudo */ + .vc { color: #268bd2; } /* Name.Variable.Class */ + .vg { color: #268bd2; } /* Name.Variable.Global */ + .vi { color: #268bd2; } /* Name.Variable.Instance */ + .il { color: #2aa198; } /* Literal.Number.Integer.Long */ } diff --git a/app/assets/stylesheets/highlight/solarized_light.scss b/app/assets/stylesheets/highlight/solarized_light.scss index 657bb5e3cd9..5956a28cafe 100644 --- a/app/assets/stylesheets/highlight/solarized_light.scss +++ b/app/assets/stylesheets/highlight/solarized_light.scss @@ -78,72 +78,72 @@ green #859900 operators, other keywords */ - .c { color: #93a1a1 } /* Comment */ - .err { color: #586e75 } /* Error */ - .g { color: #586e75 } /* Generic */ - .k { color: #859900 } /* Keyword */ - .l { color: #586e75 } /* Literal */ - .n { color: #586e75 } /* Name */ - .o { color: #859900 } /* Operator */ - .x { color: #cb4b16 } /* Other */ - .p { color: #586e75 } /* Punctuation */ - .cm { color: #93a1a1 } /* Comment.Multiline */ - .cp { color: #859900 } /* Comment.Preproc */ - .c1 { color: #93a1a1 } /* Comment.Single */ - .cs { color: #859900 } /* Comment.Special */ - .gd { color: #2aa198 } /* Generic.Deleted */ - .ge { color: #586e75; font-style: italic } /* Generic.Emph */ - .gr { color: #dc322f } /* Generic.Error */ - .gh { color: #cb4b16 } /* Generic.Heading */ - .gi { color: #859900 } /* Generic.Inserted */ - .go { color: #586e75 } /* Generic.Output */ - .gp { color: #586e75 } /* Generic.Prompt */ - .gs { color: #586e75; font-weight: bold } /* Generic.Strong */ - .gu { color: #cb4b16 } /* Generic.Subheading */ - .gt { color: #586e75 } /* Generic.Traceback */ - .kc { color: #cb4b16 } /* Keyword.Constant */ - .kd { color: #268bd2 } /* Keyword.Declaration */ - .kn { color: #859900 } /* Keyword.Namespace */ - .kp { color: #859900 } /* Keyword.Pseudo */ - .kr { color: #268bd2 } /* Keyword.Reserved */ - .kt { color: #dc322f } /* Keyword.Type */ - .ld { color: #586e75 } /* Literal.Date */ - .m { color: #2aa198 } /* Literal.Number */ - .s { color: #2aa198 } /* Literal.String */ - .na { color: #586e75 } /* Name.Attribute */ - .nb { color: #b58900 } /* Name.Builtin */ - .nc { color: #268bd2 } /* Name.Class */ - .no { color: #cb4b16 } /* Name.Constant */ - .nd { color: #268bd2 } /* Name.Decorator */ - .ni { color: #cb4b16 } /* Name.Entity */ - .ne { color: #cb4b16 } /* Name.Exception */ - .nf { color: #268bd2 } /* Name.Function */ - .nl { color: #586e75 } /* Name.Label */ - .nn { color: #586e75 } /* Name.Namespace */ - .nx { color: #586e75 } /* Name.Other */ - .py { color: #586e75 } /* Name.Property */ - .nt { color: #268bd2 } /* Name.Tag */ - .nv { color: #268bd2 } /* Name.Variable */ - .ow { color: #859900 } /* Operator.Word */ - .w { color: #586e75 } /* Text.Whitespace */ - .mf { color: #2aa198 } /* Literal.Number.Float */ - .mh { color: #2aa198 } /* Literal.Number.Hex */ - .mi { color: #2aa198 } /* Literal.Number.Integer */ - .mo { color: #2aa198 } /* Literal.Number.Oct */ - .sb { color: #93a1a1 } /* Literal.String.Backtick */ - .sc { color: #2aa198 } /* Literal.String.Char */ - .sd { color: #586e75 } /* Literal.String.Doc */ - .s2 { color: #2aa198 } /* Literal.String.Double */ - .se { color: #cb4b16 } /* Literal.String.Escape */ - .sh { color: #586e75 } /* Literal.String.Heredoc */ - .si { color: #2aa198 } /* Literal.String.Interpol */ - .sx { color: #2aa198 } /* Literal.String.Other */ - .sr { color: #dc322f } /* Literal.String.Regex */ - .s1 { color: #2aa198 } /* Literal.String.Single */ - .ss { color: #2aa198 } /* Literal.String.Symbol */ - .bp { color: #268bd2 } /* Name.Builtin.Pseudo */ - .vc { color: #268bd2 } /* Name.Variable.Class */ - .vg { color: #268bd2 } /* Name.Variable.Global */ - .vi { color: #268bd2 } /* Name.Variable.Instance */ - .il { color: #2aa198 } /* Literal.Number.Integer.Long */ + .c { color: #93a1a1; } /* Comment */ + .err { color: #586e75; } /* Error */ + .g { color: #586e75; } /* Generic */ + .k { color: #859900; } /* Keyword */ + .l { color: #586e75; } /* Literal */ + .n { color: #586e75; } /* Name */ + .o { color: #859900; } /* Operator */ + .x { color: #cb4b16; } /* Other */ + .p { color: #586e75; } /* Punctuation */ + .cm { color: #93a1a1; } /* Comment.Multiline */ + .cp { color: #859900; } /* Comment.Preproc */ + .c1 { color: #93a1a1; } /* Comment.Single */ + .cs { color: #859900; } /* Comment.Special */ + .gd { color: #2aa198; } /* Generic.Deleted */ + .ge { color: #586e75; font-style: italic; } /* Generic.Emph */ + .gr { color: #dc322f; } /* Generic.Error */ + .gh { color: #cb4b16; } /* Generic.Heading */ + .gi { color: #859900; } /* Generic.Inserted */ + .go { color: #586e75; } /* Generic.Output */ + .gp { color: #586e75; } /* Generic.Prompt */ + .gs { color: #586e75; font-weight: bold; } /* Generic.Strong */ + .gu { color: #cb4b16; } /* Generic.Subheading */ + .gt { color: #586e75; } /* Generic.Traceback */ + .kc { color: #cb4b16; } /* Keyword.Constant */ + .kd { color: #268bd2; } /* Keyword.Declaration */ + .kn { color: #859900; } /* Keyword.Namespace */ + .kp { color: #859900; } /* Keyword.Pseudo */ + .kr { color: #268bd2; } /* Keyword.Reserved */ + .kt { color: #dc322f; } /* Keyword.Type */ + .ld { color: #586e75; } /* Literal.Date */ + .m { color: #2aa198; } /* Literal.Number */ + .s { color: #2aa198; } /* Literal.String */ + .na { color: #586e75; } /* Name.Attribute */ + .nb { color: #b58900; } /* Name.Builtin */ + .nc { color: #268bd2; } /* Name.Class */ + .no { color: #cb4b16; } /* Name.Constant */ + .nd { color: #268bd2; } /* Name.Decorator */ + .ni { color: #cb4b16; } /* Name.Entity */ + .ne { color: #cb4b16; } /* Name.Exception */ + .nf { color: #268bd2; } /* Name.Function */ + .nl { color: #586e75; } /* Name.Label */ + .nn { color: #586e75; } /* Name.Namespace */ + .nx { color: #586e75; } /* Name.Other */ + .py { color: #586e75; } /* Name.Property */ + .nt { color: #268bd2; } /* Name.Tag */ + .nv { color: #268bd2; } /* Name.Variable */ + .ow { color: #859900; } /* Operator.Word */ + .w { color: #586e75; } /* Text.Whitespace */ + .mf { color: #2aa198; } /* Literal.Number.Float */ + .mh { color: #2aa198; } /* Literal.Number.Hex */ + .mi { color: #2aa198; } /* Literal.Number.Integer */ + .mo { color: #2aa198; } /* Literal.Number.Oct */ + .sb { color: #93a1a1; } /* Literal.String.Backtick */ + .sc { color: #2aa198; } /* Literal.String.Char */ + .sd { color: #586e75; } /* Literal.String.Doc */ + .s2 { color: #2aa198; } /* Literal.String.Double */ + .se { color: #cb4b16; } /* Literal.String.Escape */ + .sh { color: #586e75; } /* Literal.String.Heredoc */ + .si { color: #2aa198; } /* Literal.String.Interpol */ + .sx { color: #2aa198; } /* Literal.String.Other */ + .sr { color: #dc322f; } /* Literal.String.Regex */ + .s1 { color: #2aa198; } /* Literal.String.Single */ + .ss { color: #2aa198; } /* Literal.String.Symbol */ + .bp { color: #268bd2; } /* Name.Builtin.Pseudo */ + .vc { color: #268bd2; } /* Name.Variable.Class */ + .vg { color: #268bd2; } /* Name.Variable.Global */ + .vi { color: #268bd2; } /* Name.Variable.Instance */ + .il { color: #2aa198; } /* Literal.Number.Integer.Long */ } diff --git a/app/assets/stylesheets/highlight/white.scss b/app/assets/stylesheets/highlight/white.scss index 36a80a916b2..6f31a5235c0 100644 --- a/app/assets/stylesheets/highlight/white.scss +++ b/app/assets/stylesheets/highlight/white.scss @@ -86,7 +86,7 @@ background-color: #fafe3d !important; } - .hll { background-color: #f8f8f8 } + .hll { background-color: #f8f8f8; } .c { color: #998; font-style: italic; } .err { color: #a61717; background-color: #e3d2d2; } .k { font-weight: bold; } diff --git a/app/assets/stylesheets/mailers/repository_push_email.scss b/app/assets/stylesheets/mailers/repository_push_email.scss index 5bfe9bcb443..8d1a6020ca4 100644 --- a/app/assets/stylesheets/mailers/repository_push_email.scss +++ b/app/assets/stylesheets/mailers/repository_push_email.scss @@ -78,7 +78,7 @@ span.highlight_word { background-color: #fafe3d !important; } -.hll { background-color: #f8f8f8 } +.hll { background-color: #f8f8f8; } .c { color: #998; font-style: italic; } .err { color: #a61717; background-color: #e3d2d2; } .k { font-weight: bold; } diff --git a/app/assets/stylesheets/notify.scss b/app/assets/stylesheets/notify.scss index fc12964872d..ced8c4a9907 100644 --- a/app/assets/stylesheets/notify.scss +++ b/app/assets/stylesheets/notify.scss @@ -2,22 +2,28 @@ img { max-width: 100%; height: auto; } + p.details { font-style: italic; - color: #777 + color: #777; } + .footer > p { font-size: small; - color: #777 + color: #777; } + pre.commit-message { white-space: pre-wrap; } + .file-stats > a { text-decoration: none; + > .new-file { color: #090; } + > .deleted-file { color: #b00; } diff --git a/app/assets/stylesheets/pages/admin.scss b/app/assets/stylesheets/pages/admin.scss index 8f71381f5c4..140d589024b 100644 --- a/app/assets/stylesheets/pages/admin.scss +++ b/app/assets/stylesheets/pages/admin.scss @@ -22,7 +22,7 @@ .admin-filter form { .select2-container { - width: 100% + width: 100%; } .controls { @@ -31,7 +31,7 @@ .form-actions { padding-left: 130px; - background: #fff + background: #fff; } .visibility-levels { @@ -106,26 +106,33 @@ .table { table-layout: fixed; } + .subheading { padding-bottom: $gl-padding; } + .message { word-wrap: break-word; } + .btn { white-space: normal; padding: $gl-btn-padding; } + th { width: 15%; + &.wide { width: 55%; } } + @media (max-width: $screen-sm-max) { th { width: 100%; } + td { width: 100%; float: left; @@ -137,6 +144,7 @@ margin-left: $btn-side-margin; margin-top: 3px; } + span { font-size: 19px; } diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss index fcaba954615..2fbf0cf34bf 100644 --- a/app/assets/stylesheets/pages/builds.scss +++ b/app/assets/stylesheets/pages/builds.scss @@ -137,6 +137,7 @@ .retry-link { color: $gl-link-color; + &:hover { text-decoration: underline; } @@ -218,6 +219,7 @@ .build-detail-row { margin-bottom: 5px; + &:last-of-type { margin-bottom: 0; } diff --git a/app/assets/stylesheets/pages/commit.scss b/app/assets/stylesheets/pages/commit.scss index 53ec0002afe..264e7e01a34 100644 --- a/app/assets/stylesheets/pages/commit.scss +++ b/app/assets/stylesheets/pages/commit.scss @@ -51,6 +51,7 @@ margin-left: 4px; } } + .commit-committer-link, .commit-author-link { color: $gl-gray; @@ -108,21 +109,25 @@ line-height: 20px; } } + .new-file { a { color: $gl-text-green; } } + .renamed-file { a { color: $gl-text-orange; } } + .deleted-file { a { color: $gl-text-red; } } + .edit-file { a { color: $gl-text-color; @@ -158,6 +163,7 @@ position: absolute; z-index: 1; } + > textarea { background-color: rgba(0, 0, 0, 0.0); font-family: inherit; diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss index dc57a837155..2b5621e20d6 100644 --- a/app/assets/stylesheets/pages/commits.scss +++ b/app/assets/stylesheets/pages/commits.scss @@ -161,6 +161,7 @@ .branch-commit { color: $gl-gray; + .commit-id, .commit-row-message { color: $gl-gray; } diff --git a/app/assets/stylesheets/pages/dashboard.scss b/app/assets/stylesheets/pages/dashboard.scss index 42928ee279c..76225ed8d06 100644 --- a/app/assets/stylesheets/pages/dashboard.scss +++ b/app/assets/stylesheets/pages/dashboard.scss @@ -5,6 +5,7 @@ background: $background-color; border-top-left-radius: 0; } + border-top-left-radius: 0; } } @@ -17,6 +18,7 @@ float: left; @extend .col-md-2; } + .btn { margin-left: 5px; float: left; diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index 84094b0cb1b..bdc82a8f0f5 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -136,15 +136,18 @@ max-width: 50px; width: 35px; @include user-select(none); + a { float: left; width: 35px; font-weight: normal; + &:hover { text-decoration: underline; } } } + .line_content { display: block; margin: 0; @@ -164,10 +167,12 @@ white-space: pre-wrap; } } + .image { background: #ddd; text-align: center; padding: 30px; + .wrap { display: inline-block; } @@ -176,6 +181,7 @@ display: inline-block; background-color: #fff; line-height: 0; + img { border: 1px solid #fff; background-image: linear-gradient(45deg, #e5e5e5 25%, transparent 25%, transparent 75%, #e5e5e5 75%, #e5e5e5 100%), @@ -184,6 +190,7 @@ background-position: 0 0, 5px 5px; max-width: 100%; } + &.deleted { border: 1px solid $deleted; } @@ -192,6 +199,7 @@ border: 1px solid $added; } } + .image-info { font-size: 12px; margin: 5px 0 0; @@ -206,6 +214,7 @@ margin: auto; position: relative; } + .swipe-wrap { overflow: hidden; border-left: 1px solid #999; @@ -214,10 +223,12 @@ top: 13px; right: 7px; } + .frame { top: 0; right: 0; position: absolute; + &.deleted { margin: 0; display: block; @@ -225,6 +236,7 @@ right: 7px; } } + .swipe-bar { display: block; height: 100%; @@ -232,14 +244,17 @@ z-index: 100; position: absolute; cursor: pointer; + &:hover { .top-handle { background-position: -15px 3px; } + .bottom-handle { background-position: -15px -11px; } } + .top-handle { display: block; height: 14px; @@ -248,6 +263,7 @@ top: 0; background: image-url('swipemode_sprites.gif') 0 3px no-repeat; } + .bottom-handle { display: block; height: 14px; @@ -265,12 +281,14 @@ margin: auto; position: relative; } + .frame.added, .frame.deleted { position: absolute; display: block; top: 0; left: 0; } + .controls { display: block; height: 14px; @@ -324,6 +342,7 @@ } //.view.onion-skin } + .view-modes { padding: 10px; text-align: center; @@ -341,19 +360,24 @@ border-left: 1px solid #c1c1c1; padding: 0 12px 0 16px; cursor: pointer; + &:first-child { border-left: none; } + &:hover { text-decoration: underline; } + &.active { &:hover { text-decoration: none; } + cursor: default; color: #333; } + &.disabled { display: none; } diff --git a/app/assets/stylesheets/pages/editor.scss b/app/assets/stylesheets/pages/editor.scss index fcc5f32c738..029dabd2138 100644 --- a/app/assets/stylesheets/pages/editor.scss +++ b/app/assets/stylesheets/pages/editor.scss @@ -15,6 +15,7 @@ .cancel-btn { color: #b94a48; + &:hover { color: #b94a48; } @@ -70,16 +71,20 @@ .soft-wrap-toggle { margin: 0 $btn-side-margin; + .soft-wrap { display: block; } + .no-wrap { display: none; } + &.soft-wrap-active { .soft-wrap { display: none; } + .no-wrap { display: block; } diff --git a/app/assets/stylesheets/pages/events.scss b/app/assets/stylesheets/pages/events.scss index 789d6237df8..5d9a76dac05 100644 --- a/app/assets/stylesheets/pages/events.scss +++ b/app/assets/stylesheets/pages/events.scss @@ -78,6 +78,7 @@ margin-bottom: 0; } } + .event-note-icon { color: #777; float: left; @@ -86,6 +87,7 @@ margin-right: 5px; } } + .event_icon { position: relative; float: right; @@ -95,12 +97,13 @@ background: $gray-light; margin-left: 10px; top: -6px; + img { width: 20px; } } - &:last-child { border: none } + &:last-child { border: none; } .event_commits { li { @@ -109,6 +112,7 @@ padding: 3px; padding-left: 0; border: none; + .commit-row-title { font-size: $gl-font-size; } @@ -117,6 +121,7 @@ &.commits-stat { display: block; padding: 0 3px 0 0; + &:hover { background: none; } @@ -158,6 +163,7 @@ overflow: visible; max-width: 100%; } + .avatar { display: none; } diff --git a/app/assets/stylesheets/pages/groups.scss b/app/assets/stylesheets/pages/groups.scss index edc9592f564..ee2a398f031 100644 --- a/app/assets/stylesheets/pages/groups.scss +++ b/app/assets/stylesheets/pages/groups.scss @@ -34,6 +34,7 @@ .group-right-buttons { position: absolute; right: 16px; + .btn { @include btn-gray; padding: 3px 10px; diff --git a/app/assets/stylesheets/pages/help.scss b/app/assets/stylesheets/pages/help.scss index 00ab42bec5c..a48b4c65db8 100644 --- a/app/assets/stylesheets/pages/help.scss +++ b/app/assets/stylesheets/pages/help.scss @@ -23,28 +23,28 @@ color: #555; tbody:first-child tr:first-child { - padding-top: 0 + padding-top: 0; } th { padding-top: 15px; line-height: 1.5; color: #333; - text-align: left + text-align: left; } td { padding-top: 3px; padding-bottom: 3px; vertical-align: top; - line-height: 20px + line-height: 20px; } .shortcut { padding-right: 10px; color: #999; text-align: right; - white-space: nowrap + white-space: nowrap; } .key { diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index 41079b6eeb5..230b927a17d 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -27,6 +27,7 @@ margin-right: 5px; margin-bottom: 5px; display: inline-block; + .color-label { padding: 6px 10px; } @@ -128,7 +129,7 @@ } .selectbox { - display: none + display: none; } .btn-clipboard { @@ -199,7 +200,7 @@ display: none; /* Small devices (tablets, 768px and up) */ @media (min-width: $screen-sm-min) { - display: block + display: block; } width: $sidebar_collapsed_width; @@ -276,7 +277,7 @@ } &.btn-primary { - @extend .btn-primary + @extend .btn-primary; } } @@ -400,6 +401,7 @@ .js-issuable-selector { width: 100%; } + @media (max-width: $screen-sm-max) { margin-bottom: $gl-padding; } diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss index 3ac34cbc829..623da67a239 100644 --- a/app/assets/stylesheets/pages/issues.scss +++ b/app/assets/stylesheets/pages/issues.scss @@ -37,6 +37,7 @@ ul.related-merge-requests > li { display: -ms-flexbox; display: -webkit-flex; display: flex; + .merge-request-id { flex-shrink: 0; } diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss index 701c29a3986..9bac6d46355 100644 --- a/app/assets/stylesheets/pages/labels.scss +++ b/app/assets/stylesheets/pages/labels.scss @@ -1,5 +1,6 @@ .suggest-colors { margin-top: 5px; + a { border-radius: 4px; width: 30px; diff --git a/app/assets/stylesheets/pages/lint.scss b/app/assets/stylesheets/pages/lint.scss index 6926448519e..8290519dc25 100644 --- a/app/assets/stylesheets/pages/lint.scss +++ b/app/assets/stylesheets/pages/lint.scss @@ -3,6 +3,7 @@ font-size: 19px; color: red; } + .correct-syntax { font-size: 19px; color: #47a447; diff --git a/app/assets/stylesheets/pages/login.scss b/app/assets/stylesheets/pages/login.scss index a08b033dff9..47d112dbbe3 100644 --- a/app/assets/stylesheets/pages/login.scss +++ b/app/assets/stylesheets/pages/login.scss @@ -68,7 +68,7 @@ a.forgot { float: right; - padding-top: 6px + padding-top: 6px; } .nav .active a { diff --git a/app/assets/stylesheets/pages/merge_conflicts.scss b/app/assets/stylesheets/pages/merge_conflicts.scss index 5ec660799e3..49013d7cac9 100644 --- a/app/assets/stylesheets/pages/merge_conflicts.scss +++ b/app/assets/stylesheets/pages/merge_conflicts.scss @@ -131,6 +131,7 @@ $colors: ( } } } + &.head { background-color: map-get($colors, #{$color}_header_head_neutral); border-color: map-get($colors, #{$color}_header_head_neutral); @@ -174,6 +175,7 @@ $colors: ( background-color: map-get($colors, #{$color}_line_not_chosen); } } + &.head { background-color: map-get($colors, #{$color}_line_head_neutral); diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index 0ccb0ccfd14..afc4e517fde 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -10,6 +10,7 @@ form { margin-bottom: 0; + .clearfix { margin-bottom: 0; } @@ -46,6 +47,7 @@ &.right { float: right; + a { color: $gl-gray; } @@ -192,6 +194,7 @@ padding-top: 2px; padding-bottom: 2px; list-style: none; + &:hover { background: none; } @@ -215,6 +218,7 @@ padding-top: 20px; padding-bottom: 10px; } + svg { width: 230px; } diff --git a/app/assets/stylesheets/pages/milestone.scss b/app/assets/stylesheets/pages/milestone.scss index 8c2ba3ed58c..dd6d1783667 100644 --- a/app/assets/stylesheets/pages/milestone.scss +++ b/app/assets/stylesheets/pages/milestone.scss @@ -59,6 +59,7 @@ color: $gl-placeholder-color; margin-right: 5px; } + .avatar { float: none; } diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss index bd875b9823f..17f28959414 100644 --- a/app/assets/stylesheets/pages/note_form.scss +++ b/app/assets/stylesheets/pages/note_form.scss @@ -11,6 +11,7 @@ filter: alpha(opacity=100); } } + .diff-file, .discussion { .new-note { @@ -194,6 +195,7 @@ min-height: 140px; max-height: 500px; } + .note-form-actions { background: transparent; } diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index efeea96373f..fffcdc812a7 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -158,6 +158,7 @@ ul.notes { .diff-header > span { margin-right: 10px; } + .line_content { white-space: pre-wrap; } @@ -353,6 +354,7 @@ ul.notes { width: 32px; // "hide" it by default display: none; + &:hover { background: $gl-info; color: #fff; diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 29dd03bfd60..7b71876b822 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -183,7 +183,7 @@ &::after { content: ''; width: 8px; - position: absolute;; + position: absolute; right: -7px; bottom: 8px; border-bottom: 2px solid $border-color; @@ -360,6 +360,7 @@ &:hover { background-color: $gray-lighter; + .dropdown-menu-toggle { background-color: transparent; } @@ -543,6 +544,7 @@ height: 29px; top: -9px; } + .curve { display: block; } diff --git a/app/assets/stylesheets/pages/profile.scss b/app/assets/stylesheets/pages/profile.scss index c7eac5cf4b9..ed80d2beec2 100644 --- a/app/assets/stylesheets/pages/profile.scss +++ b/app/assets/stylesheets/pages/profile.scss @@ -243,6 +243,7 @@ .btn { -webkit-flex-grow: 1; flex-grow: 1; + &:first-child { margin-left: 0; } diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index 530fb0c0d05..d30f02340b9 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -17,34 +17,43 @@ &.features .control-label { font-weight: normal; } + .form-group { margin-bottom: 5px; } + &> .form-group { padding-left: 0; } } + .help-block { margin-bottom: 10px; } + .project-path { padding-right: 0; + .form-control { border-radius: $border-radius-base; } } + .input-group > div { &:last-child { padding-right: 0; } } + @media (max-width: $screen-xs-max) { .input-group > div { margin-bottom: 14px; + &:last-child { margin-bottom: 0; } } + fieldset > .form-group:first-child { padding-right: 0; } @@ -56,6 +65,7 @@ border-radius: 3px; border: 1px solid #e5e5e5; } + &+ .select2 a { border-top-left-radius: 0; border-bottom-left-radius: 0; @@ -201,6 +211,7 @@ pointer-events: none; } } + .count { @include btn-gray; display: inline-block; @@ -361,10 +372,12 @@ a.deploy-project-label { margin: $gl-padding; text-align: center; width: 169px; + &:hover, &.forked { background-color: $row-hover; border-color: $row-hover-border; } + .no-avatar { width: 100px; height: 100px; @@ -372,17 +385,20 @@ a.deploy-project-label { border: 1px solid $gray-dark; margin: 0 auto; border-radius: 50%; + i { font-size: 100px; color: $gray-dark; } } + a { display: block; width: 100%; height: 100%; padding-top: $gl-padding; color: $gl-gray; + .caption { min-height: 30px; padding: $gl-padding 0; @@ -644,6 +660,7 @@ pre.light-well { .clone-options { display: table-cell; + a.btn { width: 100%; } @@ -832,6 +849,7 @@ pre.light-well { .form-control { min-width: 100px; } + .select2-choice { border-top-right-radius: 0; border-bottom-right-radius: 0; diff --git a/app/assets/stylesheets/pages/runners.scss b/app/assets/stylesheets/pages/runners.scss index eec22c5dc96..7b3878c91df 100644 --- a/app/assets/stylesheets/pages/runners.scss +++ b/app/assets/stylesheets/pages/runners.scss @@ -6,6 +6,7 @@ &.runner-state-shared { background: #32b186; } + &.runner-state-specific { background: #3498db; } diff --git a/app/assets/stylesheets/pages/status.scss b/app/assets/stylesheets/pages/status.scss index c05f3d5ff32..f1d53c7b8bc 100644 --- a/app/assets/stylesheets/pages/status.scss +++ b/app/assets/stylesheets/pages/status.scss @@ -65,6 +65,7 @@ .ci-status-icon-success { color: $gl-success; } + .ci-status-icon-failed { color: $gl-danger; } @@ -77,6 +78,7 @@ .ci-status-icon-running { color: $blue-normal; } + .ci-status-icon-canceled, .ci-status-icon-disabled, .ci-status-icon-not-found, diff --git a/app/assets/stylesheets/pages/tree.scss b/app/assets/stylesheets/pages/tree.scss index 41ad10f07bd..99c0f6362d0 100644 --- a/app/assets/stylesheets/pages/tree.scss +++ b/app/assets/stylesheets/pages/tree.scss @@ -5,6 +5,7 @@ .file-finder { width: 50%; + .file-finder-input { width: 95%; display: inline-block; diff --git a/app/assets/stylesheets/pages/xterm.scss b/app/assets/stylesheets/pages/xterm.scss index c9846103762..3fa7fa3d7e3 100644 --- a/app/assets/stylesheets/pages/xterm.scss +++ b/app/assets/stylesheets/pages/xterm.scss @@ -23,15 +23,19 @@ .term-bold { font-weight: bold; } + .term-italic { font-style: italic; } + .term-conceal { visibility: hidden; } + .term-underline { text-decoration: underline; } + .term-cross { text-decoration: line-through; } @@ -39,48 +43,63 @@ .term-fg-black { color: $black; } + .term-fg-red { color: $red; } + .term-fg-green { color: $green; } + .term-fg-yellow { color: $yellow; } + .term-fg-blue { color: $blue; } + .term-fg-magenta { color: $magenta; } + .term-fg-cyan { color: $cyan; } + .term-fg-white { color: $white; } + .term-fg-l-black { color: $l-black; } + .term-fg-l-red { color: $l-red; } + .term-fg-l-green { color: $l-green; } + .term-fg-l-yellow { color: $l-yellow; } + .term-fg-l-blue { color: $l-blue; } + .term-fg-l-magenta { color: $l-magenta; } + .term-fg-l-cyan { color: $l-cyan; } + .term-fg-l-white { color: $l-white; } @@ -88,818 +107,1087 @@ .term-bg-black { background-color: $black; } + .term-bg-red { background-color: $red; } + .term-bg-green { background-color: $green; } + .term-bg-yellow { background-color: $yellow; } + .term-bg-blue { background-color: $blue; } + .term-bg-magenta { background-color: $magenta; } + .term-bg-cyan { background-color: $cyan; } + .term-bg-white { background-color: $white; } + .term-bg-l-black { background-color: $l-black; } + .term-bg-l-red { background-color: $l-red; } + .term-bg-l-green { background-color: $l-green; } + .term-bg-l-yellow { background-color: $l-yellow; } + .term-bg-l-blue { background-color: $l-blue; } + .term-bg-l-magenta { background-color: $l-magenta; } + .term-bg-l-cyan { background-color: $l-cyan; } + .term-bg-l-white { background-color: $l-white; } - .xterm-fg-0 { color: #000; } + .xterm-fg-1 { color: #800000; } + .xterm-fg-2 { color: #008000; } + .xterm-fg-3 { color: #808000; } + .xterm-fg-4 { color: #000080; } + .xterm-fg-5 { color: #800080; } + .xterm-fg-6 { color: #008080; } + .xterm-fg-7 { color: #c0c0c0; } + .xterm-fg-8 { color: #808080; } + .xterm-fg-9 { color: #f00; } + .xterm-fg-10 { color: #0f0; } + .xterm-fg-11 { color: #ff0; } + .xterm-fg-12 { color: #00f; } + .xterm-fg-13 { color: #f0f; } + .xterm-fg-14 { color: #0ff; } + .xterm-fg-15 { color: #fff; } + .xterm-fg-16 { color: #000; } + .xterm-fg-17 { color: #00005f; } + .xterm-fg-18 { color: #000087; } + .xterm-fg-19 { color: #0000af; } + .xterm-fg-20 { color: #0000d7; } + .xterm-fg-21 { color: #00f; } + .xterm-fg-22 { color: #005f00; } + .xterm-fg-23 { color: #005f5f; } + .xterm-fg-24 { color: #005f87; } + .xterm-fg-25 { color: #005faf; } + .xterm-fg-26 { color: #005fd7; } + .xterm-fg-27 { color: #005fff; } + .xterm-fg-28 { color: #008700; } + .xterm-fg-29 { color: #00875f; } + .xterm-fg-30 { color: #008787; } + .xterm-fg-31 { color: #0087af; } + .xterm-fg-32 { color: #0087d7; } + .xterm-fg-33 { color: #0087ff; } + .xterm-fg-34 { color: #00af00; } + .xterm-fg-35 { color: #00af5f; } + .xterm-fg-36 { color: #00af87; } + .xterm-fg-37 { color: #00afaf; } + .xterm-fg-38 { color: #00afd7; } + .xterm-fg-39 { color: #00afff; } + .xterm-fg-40 { color: #00d700; } + .xterm-fg-41 { color: #00d75f; } + .xterm-fg-42 { color: #00d787; } + .xterm-fg-43 { color: #00d7af; } + .xterm-fg-44 { color: #00d7d7; } + .xterm-fg-45 { color: #00d7ff; } + .xterm-fg-46 { color: #0f0; } + .xterm-fg-47 { color: #00ff5f; } + .xterm-fg-48 { color: #00ff87; } + .xterm-fg-49 { color: #00ffaf; } + .xterm-fg-50 { color: #00ffd7; } + .xterm-fg-51 { color: #0ff; } + .xterm-fg-52 { color: #5f0000; } + .xterm-fg-53 { color: #5f005f; } + .xterm-fg-54 { color: #5f0087; } + .xterm-fg-55 { color: #5f00af; } + .xterm-fg-56 { color: #5f00d7; } + .xterm-fg-57 { color: #5f00ff; } + .xterm-fg-58 { color: #5f5f00; } + .xterm-fg-59 { color: #5f5f5f; } + .xterm-fg-60 { color: #5f5f87; } + .xterm-fg-61 { color: #5f5faf; } + .xterm-fg-62 { color: #5f5fd7; } + .xterm-fg-63 { color: #5f5fff; } + .xterm-fg-64 { color: #5f8700; } + .xterm-fg-65 { color: #5f875f; } + .xterm-fg-66 { color: #5f8787; } + .xterm-fg-67 { color: #5f87af; } + .xterm-fg-68 { color: #5f87d7; } + .xterm-fg-69 { color: #5f87ff; } + .xterm-fg-70 { color: #5faf00; } + .xterm-fg-71 { color: #5faf5f; } + .xterm-fg-72 { color: #5faf87; } + .xterm-fg-73 { color: #5fafaf; } + .xterm-fg-74 { color: #5fafd7; } + .xterm-fg-75 { color: #5fafff; } + .xterm-fg-76 { color: #5fd700; } + .xterm-fg-77 { color: #5fd75f; } + .xterm-fg-78 { color: #5fd787; } + .xterm-fg-79 { color: #5fd7af; } + .xterm-fg-80 { color: #5fd7d7; } + .xterm-fg-81 { color: #5fd7ff; } + .xterm-fg-82 { color: #5fff00; } + .xterm-fg-83 { color: #5fff5f; } + .xterm-fg-84 { color: #5fff87; } + .xterm-fg-85 { color: #5fffaf; } + .xterm-fg-86 { color: #5fffd7; } + .xterm-fg-87 { color: #5fffff; } + .xterm-fg-88 { color: #870000; } + .xterm-fg-89 { color: #87005f; } + .xterm-fg-90 { color: #870087; } + .xterm-fg-91 { color: #8700af; } + .xterm-fg-92 { color: #8700d7; } + .xterm-fg-93 { color: #8700ff; } + .xterm-fg-94 { color: #875f00; } + .xterm-fg-95 { color: #875f5f; } + .xterm-fg-96 { color: #875f87; } + .xterm-fg-97 { color: #875faf; } + .xterm-fg-98 { color: #875fd7; } + .xterm-fg-99 { color: #875fff; } + .xterm-fg-100 { color: #878700; } + .xterm-fg-101 { color: #87875f; } + .xterm-fg-102 { color: #878787; } + .xterm-fg-103 { color: #8787af; } + .xterm-fg-104 { color: #8787d7; } + .xterm-fg-105 { color: #8787ff; } + .xterm-fg-106 { color: #87af00; } + .xterm-fg-107 { color: #87af5f; } + .xterm-fg-108 { color: #87af87; } + .xterm-fg-109 { color: #87afaf; } + .xterm-fg-110 { color: #87afd7; } + .xterm-fg-111 { color: #87afff; } + .xterm-fg-112 { color: #87d700; } + .xterm-fg-113 { color: #87d75f; } + .xterm-fg-114 { color: #87d787; } + .xterm-fg-115 { color: #87d7af; } + .xterm-fg-116 { color: #87d7d7; } + .xterm-fg-117 { color: #87d7ff; } + .xterm-fg-118 { color: #87ff00; } + .xterm-fg-119 { color: #87ff5f; } + .xterm-fg-120 { color: #87ff87; } + .xterm-fg-121 { color: #87ffaf; } + .xterm-fg-122 { color: #87ffd7; } + .xterm-fg-123 { color: #87ffff; } + .xterm-fg-124 { color: #af0000; } + .xterm-fg-125 { color: #af005f; } + .xterm-fg-126 { color: #af0087; } + .xterm-fg-127 { color: #af00af; } + .xterm-fg-128 { color: #af00d7; } + .xterm-fg-129 { color: #af00ff; } + .xterm-fg-130 { color: #af5f00; } + .xterm-fg-131 { color: #af5f5f; } + .xterm-fg-132 { color: #af5f87; } + .xterm-fg-133 { color: #af5faf; } + .xterm-fg-134 { color: #af5fd7; } + .xterm-fg-135 { color: #af5fff; } + .xterm-fg-136 { color: #af8700; } + .xterm-fg-137 { color: #af875f; } + .xterm-fg-138 { color: #af8787; } + .xterm-fg-139 { color: #af87af; } + .xterm-fg-140 { color: #af87d7; } + .xterm-fg-141 { color: #af87ff; } + .xterm-fg-142 { color: #afaf00; } + .xterm-fg-143 { color: #afaf5f; } + .xterm-fg-144 { color: #afaf87; } + .xterm-fg-145 { color: #afafaf; } + .xterm-fg-146 { color: #afafd7; } + .xterm-fg-147 { color: #afafff; } + .xterm-fg-148 { color: #afd700; } + .xterm-fg-149 { color: #afd75f; } + .xterm-fg-150 { color: #afd787; } + .xterm-fg-151 { color: #afd7af; } + .xterm-fg-152 { color: #afd7d7; } + .xterm-fg-153 { color: #afd7ff; } + .xterm-fg-154 { color: #afff00; } + .xterm-fg-155 { color: #afff5f; } + .xterm-fg-156 { color: #afff87; } + .xterm-fg-157 { color: #afffaf; } + .xterm-fg-158 { color: #afffd7; } + .xterm-fg-159 { color: #afffff; } + .xterm-fg-160 { color: #d70000; } + .xterm-fg-161 { color: #d7005f; } + .xterm-fg-162 { color: #d70087; } + .xterm-fg-163 { color: #d700af; } + .xterm-fg-164 { color: #d700d7; } + .xterm-fg-165 { color: #d700ff; } + .xterm-fg-166 { color: #d75f00; } + .xterm-fg-167 { color: #d75f5f; } + .xterm-fg-168 { color: #d75f87; } + .xterm-fg-169 { color: #d75faf; } + .xterm-fg-170 { color: #d75fd7; } + .xterm-fg-171 { color: #d75fff; } + .xterm-fg-172 { color: #d78700; } + .xterm-fg-173 { color: #d7875f; } + .xterm-fg-174 { color: #d78787; } + .xterm-fg-175 { color: #d787af; } + .xterm-fg-176 { color: #d787d7; } + .xterm-fg-177 { color: #d787ff; } + .xterm-fg-178 { color: #d7af00; } + .xterm-fg-179 { color: #d7af5f; } + .xterm-fg-180 { color: #d7af87; } + .xterm-fg-181 { color: #d7afaf; } + .xterm-fg-182 { color: #d7afd7; } + .xterm-fg-183 { color: #d7afff; } + .xterm-fg-184 { color: #d7d700; } + .xterm-fg-185 { color: #d7d75f; } + .xterm-fg-186 { color: #d7d787; } + .xterm-fg-187 { color: #d7d7af; } + .xterm-fg-188 { color: #d7d7d7; } + .xterm-fg-189 { color: #d7d7ff; } + .xterm-fg-190 { color: #d7ff00; } + .xterm-fg-191 { color: #d7ff5f; } + .xterm-fg-192 { color: #d7ff87; } + .xterm-fg-193 { color: #d7ffaf; } + .xterm-fg-194 { color: #d7ffd7; } + .xterm-fg-195 { color: #d7ffff; } + .xterm-fg-196 { color: #f00; } + .xterm-fg-197 { color: #ff005f; } + .xterm-fg-198 { color: #ff0087; } + .xterm-fg-199 { color: #ff00af; } + .xterm-fg-200 { color: #ff00d7; } + .xterm-fg-201 { color: #f0f; } + .xterm-fg-202 { color: #ff5f00; } + .xterm-fg-203 { color: #ff5f5f; } + .xterm-fg-204 { color: #ff5f87; } + .xterm-fg-205 { color: #ff5faf; } + .xterm-fg-206 { color: #ff5fd7; } + .xterm-fg-207 { color: #ff5fff; } + .xterm-fg-208 { color: #ff8700; } + .xterm-fg-209 { color: #ff875f; } + .xterm-fg-210 { color: #ff8787; } + .xterm-fg-211 { color: #ff87af; } + .xterm-fg-212 { color: #ff87d7; } + .xterm-fg-213 { color: #ff87ff; } + .xterm-fg-214 { color: #ffaf00; } + .xterm-fg-215 { color: #ffaf5f; } + .xterm-fg-216 { color: #ffaf87; } + .xterm-fg-217 { color: #ffafaf; } + .xterm-fg-218 { color: #ffafd7; } + .xterm-fg-219 { color: #ffafff; } + .xterm-fg-220 { color: #ffd700; } + .xterm-fg-221 { color: #ffd75f; } + .xterm-fg-222 { color: #ffd787; } + .xterm-fg-223 { color: #ffd7af; } + .xterm-fg-224 { color: #ffd7d7; } + .xterm-fg-225 { color: #ffd7ff; } + .xterm-fg-226 { color: #ff0; } + .xterm-fg-227 { color: #ffff5f; } + .xterm-fg-228 { color: #ffff87; } + .xterm-fg-229 { color: #ffffaf; } + .xterm-fg-230 { color: #ffffd7; } + .xterm-fg-231 { color: #fff; } + .xterm-fg-232 { color: #080808; } + .xterm-fg-233 { color: #121212; } + .xterm-fg-234 { color: #1c1c1c; } + .xterm-fg-235 { color: #262626; } + .xterm-fg-236 { color: #303030; } + .xterm-fg-237 { color: #3a3a3a; } + .xterm-fg-238 { color: #444; } + .xterm-fg-239 { color: #4e4e4e; } + .xterm-fg-240 { color: #585858; } + .xterm-fg-241 { color: #626262; } + .xterm-fg-242 { color: #6c6c6c; } + .xterm-fg-243 { color: #767676; } + .xterm-fg-244 { color: #808080; } + .xterm-fg-245 { color: #8a8a8a; } + .xterm-fg-246 { color: #949494; } + .xterm-fg-247 { color: #9e9e9e; } + .xterm-fg-248 { color: #a8a8a8; } + .xterm-fg-249 { color: #b2b2b2; } + .xterm-fg-250 { color: #bcbcbc; } + .xterm-fg-251 { color: #c6c6c6; } + .xterm-fg-252 { color: #d0d0d0; } + .xterm-fg-253 { color: #dadada; } + .xterm-fg-254 { color: #e4e4e4; } + .xterm-fg-255 { color: #eee; } -- cgit v1.2.1 From 2b9a25bd5a69c3c6a5bb24bb67838a4d354204e1 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Sat, 15 Oct 2016 00:11:19 +0300 Subject: Handle unmatched routing with not_found method We need this to prevent routing error when user access URL like /123 when there is no resource located under such name Signed-off-by: Dmitriy Zaporozhets --- app/controllers/application_controller.rb | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'app') diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index b3455e04c29..705824502eb 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -45,6 +45,10 @@ class ApplicationController < ActionController::Base redirect_to request.referer.present? ? :back : default, options end + def not_found + render_404 + end + protected # This filter handles both private tokens and personal access tokens -- cgit v1.2.1 From 1dd826d4aad2ce6c195bad24b458b1967b74db1d Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Fri, 9 Sep 2016 14:21:00 +0200 Subject: Make UX upgrades to SignIn/Register views. - Tab between register and sign in forms - Add individual input validation error messages - Validate username - Update many styles for all login-box forms --- app/assets/javascripts/dispatcher.js | 11 ++ app/assets/javascripts/gl_field_errors.js.es6 | 100 ++++++++++++++++ app/assets/javascripts/username_validator.js.es6 | 131 +++++++++++++++++++++ app/assets/stylesheets/framework/buttons.scss | 3 +- app/assets/stylesheets/framework/forms.scss | 13 ++- app/assets/stylesheets/pages/login.scss | 138 +++++++++++++++++++++-- app/controllers/users_controller.rb | 6 +- app/views/admin/appearances/preview.html.haml | 17 +-- app/views/devise/confirmations/new.html.haml | 12 +- app/views/devise/passwords/edit.html.haml | 20 ++-- app/views/devise/passwords/new.html.haml | 10 +- app/views/devise/sessions/_new_base.html.haml | 10 +- app/views/devise/sessions/_new_crowd.html.haml | 10 +- app/views/devise/sessions/_new_ldap.html.haml | 10 +- app/views/devise/sessions/new.html.haml | 32 +++--- app/views/devise/sessions/two_factor.html.haml | 17 ++- app/views/devise/shared/_omniauth_box.html.haml | 15 +-- app/views/devise/shared/_sign_in_link.html.haml | 1 - app/views/devise/shared/_signin_box.html.haml | 37 ++---- app/views/devise/shared/_signup_box.html.haml | 36 +++--- app/views/devise/shared/_tab_single.html.haml | 4 + app/views/devise/shared/_tabs_ldap.html.haml | 10 ++ app/views/devise/shared/_tabs_normal.html.haml | 5 + app/views/devise/unlocks/new.html.haml | 10 +- app/views/layouts/devise.html.haml | 59 +++++----- app/views/u2f/_authenticate.html.haml | 2 +- 26 files changed, 557 insertions(+), 162 deletions(-) create mode 100644 app/assets/javascripts/gl_field_errors.js.es6 create mode 100644 app/assets/javascripts/username_validator.js.es6 create mode 100644 app/views/devise/shared/_tab_single.html.haml create mode 100644 app/views/devise/shared/_tabs_ldap.html.haml create mode 100644 app/views/devise/shared/_tabs_normal.html.haml (limited to 'app') diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index 858621218f8..fb6e82cd37c 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -8,6 +8,7 @@ Dispatcher = (function() { function Dispatcher() { this.initSearch(); + this.initFieldErrors(); this.initPageScripts(); } @@ -20,6 +21,10 @@ path = page.split(':'); shortcut_handler = null; switch (page) { + case 'sessions:new': + case 'sessions:create': + new UsernameValidator(); + break; case 'projects:boards:show': case 'projects:boards:index': shortcut_handler = new ShortcutsNavigation(); @@ -291,6 +296,12 @@ } }; + Dispatcher.prototype.initFieldErrors = function() { + $('form.show-gl-field-errors').each(function(i, form) { + new gl.GlFieldErrors(form); + }); + }; + return Dispatcher; })(); diff --git a/app/assets/javascripts/gl_field_errors.js.es6 b/app/assets/javascripts/gl_field_errors.js.es6 new file mode 100644 index 00000000000..42a2ddeeafe --- /dev/null +++ b/app/assets/javascripts/gl_field_errors.js.es6 @@ -0,0 +1,100 @@ +((global) => { + /* + * This class overrides the browser's validation error bubbles, displaying custom + * error messages for invalid fields instead. To begin validating any form, add the + * class `show-gl-field-errors` to the form element, and ensure error messages are + * declared in each inputs' title attribute. + * + * Example: + * + *
    + * + *
    + * + * */ + + const fieldErrorClass = 'gl-field-error'; + const fieldErrorSelector = `.${fieldErrorClass}`; + const inputErrorClass = 'gl-field-error-outline'; + + class GlFieldErrors { + constructor(form) { + this.form = $(form); + this.initValidators(); + } + + initValidators () { + this.inputs = this.form.find(':input:not([type=hidden])').toArray(); + this.inputs.forEach((input) => { + $(input).off('invalid').on('invalid', this.handleInvalidInput.bind(this)); + }); + this.form.on('submit', this.catchInvalidFormSubmit); + } + + /* Neccessary because Safari & iOS quietly allow form submission when form is invalid */ + catchInvalidFormSubmit (event) { + if (!event.currentTarget.checkValidity()) { + event.preventDefault(); + // Prevents disabling of invalid submit button by application.js + event.stopPropagation(); + } + } + + handleInvalidInput (event) { + event.preventDefault(); + this.updateFieldValidityState(event); + + const $input = $(event.currentTarget); + + // For UX, wait til after first invalid submission to check each keyup + $input.off('keyup.field_validator') + .on('keyup.field_validator', this.updateFieldValidityState.bind(this)); + + } + + displayFieldValidity (target, isValid) { + const $input = $(target).removeClass(inputErrorClass); + const $existingError = $input.siblings(fieldErrorSelector); + const alreadyInvalid = !!$existingError.length; + const implicitErrorMessage = $input.attr('title'); + const $errorToDisplay = alreadyInvalid ? $existingError.detach() : $(`

    ${implicitErrorMessage}

    `); + + if (!isValid) { + $input.after($errorToDisplay); + $input.addClass(inputErrorClass); + } + + this.updateFieldSiblings($errorToDisplay, isValid); + } + + updateFieldSiblings($target, isValid) { + const siblings = $target.siblings(`p${fieldErrorSelector}`); + return isValid ? siblings.show() : siblings.hide(); + } + + checkFieldValidity(target) { + return target.validity.valid; + } + + updateFieldValidityState(event) { + const target = event.currentTarget; + const isKeyup = event.type === 'keyup'; + const isValid = this.checkFieldValidity(target); + + this.displayFieldValidity(target, isValid); + + // prevent changing focus while user is typing. + if (!isKeyup) { + this.focusOnFirstInvalid.apply(this); + } + } + + focusOnFirstInvalid () { + const firstInvalid = this.inputs.find((input) => !input.validity.valid); + $(firstInvalid).focus(); + } + } + + global.GlFieldErrors = GlFieldErrors; + +})(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/username_validator.js.es6 b/app/assets/javascripts/username_validator.js.es6 new file mode 100644 index 00000000000..f8be356af04 --- /dev/null +++ b/app/assets/javascripts/username_validator.js.es6 @@ -0,0 +1,131 @@ +((global) => { + const debounceTimeoutDuration = 1000; + const inputErrorClass = 'gl-field-error-outline'; + const inputSuccessClass = 'gl-field-success-outline'; + const messageErrorSelector = '.username .validation-error'; + const messageSuccessSelector = '.username .validation-success'; + const messagePendingSelector = '.username .validation-pending'; + + class UsernameValidator { + constructor() { + this.inputElement = $('#new_user_username'); + this.inputDomElement = this.inputElement.get(0); + + this.available = false; + this.valid = false; + this.pending = false; + this.fresh = true; + this.empty = true; + + const debounceTimeout = _.debounce((username) => { + this.validateUsername(username); + }, debounceTimeoutDuration); + + this.inputElement.on('keyup.username_check', () => { + const username = this.inputElement.val(); + + this.valid = this.inputDomElement.validity.valid; + this.fresh = false; + this.empty = !username.length; + + if (this.valid) { + return debounceTimeout(username); + } + + this.renderState(); + }); + + // Override generic field validation + this.inputElement.on('invalid', this.handleInvalidInput.bind(this)); + } + + renderState() { + // Clear all state + this.clearFieldValidationState(); + + if (this.valid && this.available) { + return this.setSuccessState(); + } + + if (this.empty) { + return this.clearFieldValidationState(); + } + + if (this.pending) { + return this.setPendingState(); + } + + if (!this.available) { + return this.setUnavailableState(); + } + + if (!this.valid) { + return this.setInvalidState(); + } + } + + handleInvalidInput(event) { + event.preventDefault(); + event.stopPropagation(); + } + + validateUsername(username) { + if (this.valid) { + this.pending = true; + this.available = false; + this.renderState(); + return $.ajax({ + type: 'GET', + url: `/u/${username}/exists`, + dataType: 'json', + success: (res) => this.updateValidationState(res.exists) + }); + } + } + + updateValidationState(usernameTaken) { + if (usernameTaken) { + this.valid = false; + this.available = false; + } else { + this.available = true; + } + this.pending = false; + this.renderState(); + } + + clearFieldValidationState() { + this.inputElement.siblings('p').hide(); + this.inputElement.removeClass(inputErrorClass); + this.inputElement.removeClass(inputSuccessClass); + } + + setUnavailableState() { + const $usernameErrorMessage = this.inputElement.siblings(messageErrorSelector); + this.inputElement.addClass(inputErrorClass).removeClass(inputSuccessClass); + $usernameErrorMessage.show(); + } + + setSuccessState() { + const $usernameSuccessMessage = this.inputElement.siblings(messageSuccessSelector); + this.inputElement.addClass(inputSuccessClass).removeClass(inputErrorClass); + $usernameSuccessMessage.show(); + } + + setPendingState(show) { + const $usernamePendingMessage = $(messagePendingSelector); + if (this.pending) { + $usernamePendingMessage.show(); + } else { + $usernamePendingMessage.hide(); + } + } + + setInvalidState() { + this.inputElement.addClass(inputErrorClass).removeClass(inputSuccessClass); + $(`.gl-field-error`).show(); + } + } + + global.UsernameValidator = UsernameValidator; +})(window); diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss index 7c0ed72dbc5..e6656c2d69a 100644 --- a/app/assets/stylesheets/framework/buttons.scss +++ b/app/assets/stylesheets/framework/buttons.scss @@ -152,7 +152,8 @@ @include btn-blue-medium; } - &.btn-info { + &.btn-info, + &.btn-register { @include btn-blue; } diff --git a/app/assets/stylesheets/framework/forms.scss b/app/assets/stylesheets/framework/forms.scss index 311e3fa1a35..761c07384f4 100644 --- a/app/assets/stylesheets/framework/forms.scss +++ b/app/assets/stylesheets/framework/forms.scss @@ -73,8 +73,8 @@ label { } .form-control { - box-shadow: none; - border-radius: 3px; + @include box-shadow(none); + border-radius: 2px; padding: $gl-vert-padding $gl-input-padding; } @@ -127,3 +127,12 @@ label { border-right: 0; } } + +.help-block { + margin-bottom: 0; +} + +.gl-field-error { + color: $red-normal; +} + diff --git a/app/assets/stylesheets/pages/login.scss b/app/assets/stylesheets/pages/login.scss index 47d112dbbe3..06b90fbefab 100644 --- a/app/assets/stylesheets/pages/login.scss +++ b/app/assets/stylesheets/pages/login.scss @@ -17,6 +17,7 @@ line-height: 1.5; p { + font-size: 18px; color: #888; } @@ -36,10 +37,13 @@ } } + p { + font-size: 13px; + } .login-box { - background: #fafafa; - border-radius: 10px; - box-shadow: 0 0 2px #ccc; + box-shadow: 0 0 0 1px $border-color; + border-bottom-right-radius: 2px; + border-bottom-left-radius: 2px; padding: 15px; .login-heading h3 { @@ -74,7 +78,6 @@ .nav .active a { background: transparent; } - } .form-control { font-size: 14px; @@ -92,18 +95,109 @@ border-top: 0; margin-bottom: 20px; } + } + + // Styles the glowing border of focused input for username async validation + .login-body { + font-size: 13px; + + + input + p { + margin-top: 5px; + } + + .gl-field-success-outline { + border: 1px solid $green-normal; + + &:focus { + box-shadow: 0 0 0 1px $green-normal inset, 0 0 4px 0 $green-normal; + border: 0 none; + } + } + + .gl-field-error-outline { + border: 1px solid $red-normal; + + &:focus { + opacity: .6; + box-shadow: 0 0 0 1px $red-normal inset, 0 0 4px 0 $red-normal; + border: 0 none; + } + } + + .username .validation-success, + .gl-field-success-message { + color: $green-normal; + } + + .username .validation-error, + .gl-field-error-message { + color: $red-normal; + } + + .gl-field-hint { + color: $gl-text-color; + } + + } + + .new-session-tabs { // Are these being applied to other login-related screens? They need to be. + display: flex; + box-shadow: 0 0 0 1px $border-color; + border-top-right-radius: 2px; + border-top-left-radius: 2px; + + li { + flex: 1; + text-align: center; &.middle { border-top: 0; margin-bottom: 0; border-radius: 0; + &:last-of-type { + border-left: 1px solid $border-color; + } + + &:not(.active) { + background-color: $gray-light; + } + + a { + width: 100%; + font-size: 18px; + &:hover { + border: 1px solid transparent; + } + } + + &.active { + border-bottom: 1px solid $border-color; + + a { + border: none; + border-bottom: 2px solid $link-underline-blue; + color: $black; + + &:hover { + border-bottom: 2px solid $link-underline-blue; + } + } + } } + } + + .form-control { &:active, &:focus { background-color: #fff; } } + label { + font-weight: normal; + } + .devise-errors { h2 { margin-top: 0; @@ -111,14 +205,6 @@ color: #a00; } } - - .remember-me { - margin-top: -10px; - - label { - font-weight: normal; - } - } } @media (max-width: $screen-xs-max) { @@ -137,3 +223,31 @@ height: 32px; } } + +.devise-layout-html { + margin: 0; + padding: 0; + height: 100%; +} + +// Fixes footer container to bottom of viewport +.devise-layout-html body { + // offset height of fixed header + 1 to avoid scroll + height: calc(100% - 51px); + margin: 0; + padding: 0; + + .page-wrap { + min-height: 100%; + position: relative; + } + + .footer-container, hr.footer-fixed { + position: absolute; + bottom: 0; + left: 0; + right: 0; + height: 40px; + background: $white-light; + } +} diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 838ecc837e4..30f0118254a 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -1,6 +1,6 @@ class UsersController < ApplicationController skip_before_action :authenticate_user! - before_action :user + before_action :user, except: [:exists] before_action :authorize_read_user!, only: [:show] def show @@ -85,6 +85,10 @@ class UsersController < ApplicationController render 'calendar_activities', layout: false end + def exists + render json: { exists: !User.find_by_username(params[:username]).nil? } + end + private def authorize_read_user! diff --git a/app/views/admin/appearances/preview.html.haml b/app/views/admin/appearances/preview.html.haml index 6c51639b840..0d35702c634 100644 --- a/app/views/admin/appearances/preview.html.haml +++ b/app/views/admin/appearances/preview.html.haml @@ -1,9 +1,12 @@ -- page_title "Preview | Appearance" += render 'devise/shared/tab_single', { :tab_title => 'Sign in preview' } .login-box - .login-heading - %h3 Existing user? Sign in - %form - = text_field_tag :login, nil, class: "form-control top", placeholder: "Username or Email" - = password_field_tag :password, nil, class: "form-control bottom", placeholder: "Password" - = button_tag "Sign in", class: "btn-create btn" + %form.show-gl-field-errors + .form-group + = label_tag :login + = text_field_tag :login, nil, class: "form-control top", title: 'Please provide your username or email address.' + .form-group + = label_tag :password + = password_field_tag :password, nil, class: "form-control bottom", title: 'This field is required.' + .form-group + = button_tag "Sign in", class: "btn-create btn" diff --git a/app/views/devise/confirmations/new.html.haml b/app/views/devise/confirmations/new.html.haml index 970ba147111..443a316c6e2 100644 --- a/app/views/devise/confirmations/new.html.haml +++ b/app/views/devise/confirmations/new.html.haml @@ -1,14 +1,14 @@ += render 'devise/shared/tab_single', { :tab_title => 'Resend confirmation instructions' } .login-box - .login-heading - %h3 Resend confirmation instructions .login-body - = form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post }) do |f| + = form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post, class: 'show-gl-field-errors' }) do |f| .devise-errors = devise_error_messages! - .clearfix.append-bottom-20 - = f.email_field :email, placeholder: 'Email', class: "form-control", required: true + .form-group + = f.label :email + = f.email_field :email, class: "form-control", required: true, title: 'Please provide a valid email address.' .clearfix - = f.submit "Resend confirmation instructions", class: 'btn btn-success' + = f.submit "Resend", class: 'btn btn-success' .clearfix.prepend-top-20 = render 'devise/shared/sign_in_link' diff --git a/app/views/devise/passwords/edit.html.haml b/app/views/devise/passwords/edit.html.haml index 56048e99c17..9c533ef9916 100644 --- a/app/views/devise/passwords/edit.html.haml +++ b/app/views/devise/passwords/edit.html.haml @@ -1,19 +1,21 @@ += render 'devise/shared/tab_single', { :tab_title => 'Change your password' } .login-box - .login-heading - %h3 Change your password .login-body - = form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :put }) do |f| + = form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :put, class: 'show-gl-field-errors' }) do |f| .devise-errors = devise_error_messages! = f.hidden_field :reset_password_token - %div - = f.password_field :password, class: "form-control top", placeholder: "New password", required: true - %div - = f.password_field :password_confirmation, class: "form-control bottom", placeholder: "Confirm new password", required: true + .form-group + = f.label 'New password', for: :password + = f.password_field :password, class: "form-control top", required: true, title: 'This field is required' + .form-group + = f.label 'Confirm new password', for: :password_confirmation + = f.password_field :password_confirmation, class: "form-control bottom", title: 'This field is required', required: true .clearfix = f.submit "Change your password", class: "btn btn-primary" .clearfix.prepend-top-20 %p - = link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name) - = render 'devise/shared/sign_in_link' + %span.light Didn't receive a confirmation email? + = link_to "Request a new one", new_confirmation_path(resource_name) += render 'devise/shared/sign_in_link' diff --git a/app/views/devise/passwords/new.html.haml b/app/views/devise/passwords/new.html.haml index 535e85869e5..91b46a12ac0 100644 --- a/app/views/devise/passwords/new.html.haml +++ b/app/views/devise/passwords/new.html.haml @@ -1,12 +1,12 @@ += render 'devise/shared/tab_single', { :tab_title => 'Reset Password' } .login-box - .login-heading - %h3 Reset password .login-body - = form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :post }) do |f| + = form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :post, class: 'show-gl-field-errors' }) do |f| .devise-errors = devise_error_messages! - .clearfix.append-bottom-20 - = f.email_field :email, placeholder: "Email", class: "form-control", required: true, value: params[:user_email], autofocus: true + .form-group + = f.label :email + = f.email_field :email, class: "form-control", required: true, value: params[:user_email], autofocus: true, title: 'Please provide a valid email address.' .clearfix = f.submit "Reset password", class: "btn-primary btn" diff --git a/app/views/devise/sessions/_new_base.html.haml b/app/views/devise/sessions/_new_base.html.haml index 781fd1b32a6..cfb1b964d76 100644 --- a/app/views/devise/sessions/_new_base.html.haml +++ b/app/views/devise/sessions/_new_base.html.haml @@ -1,6 +1,10 @@ -= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| - = f.text_field :login, class: "form-control top", placeholder: "Username or Email", autofocus: "autofocus", autocapitalize: "off", autocorrect: "off" - = f.password_field :password, class: "form-control bottom", placeholder: "Password" += form_for(resource, as: resource_name, url: session_path(resource_name), html: { class: 'new_user show-gl-field-errors', 'aria-live' => 'assertive'}) do |f| + %div.form-group + = f.label "Username or email", for: :login + = f.text_field :login, class: "form-control top", autofocus: "autofocus", autocapitalize: "off", autocorrect: "off", required: true, title: "This field is required." + %div.form-group + = f.label :password + = f.password_field :password, class: "form-control bottom", required: true, title: "This field is required." .sign-in = f.submit "Sign in", class: "btn btn-save" - if devise_mapping.rememberable? diff --git a/app/views/devise/sessions/_new_crowd.html.haml b/app/views/devise/sessions/_new_crowd.html.haml index b7d3acac2b1..5a192c63c7c 100644 --- a/app/views/devise/sessions/_new_crowd.html.haml +++ b/app/views/devise/sessions/_new_crowd.html.haml @@ -1,6 +1,10 @@ -= form_tag(omniauth_authorize_path(:user, :crowd), id: 'new_crowd_user' ) do - = text_field_tag :username, nil, {class: "form-control top", placeholder: "Username", autofocus: "autofocus"} - = password_field_tag :password, nil, {class: "form-control bottom", placeholder: "Password"} += form_tag(omniauth_authorize_path(:user, :crowd), id: 'new_crowd_user' class: 'show-gl-field-errors') do + .form-group + = label_tag 'Username or email', for: :username + = text_field_tag :username, nil, {class: "form-control top", title: "This field is required", autofocus: "autofocus", required: true } + .form-group + = label_tag :password + = password_field_tag :password, nil, { class: "form-control bottom", title: "This field is required.", required: true } - if devise_mapping.rememberable? .remember-me.checkbox %label{for: "remember_me"} diff --git a/app/views/devise/sessions/_new_ldap.html.haml b/app/views/devise/sessions/_new_ldap.html.haml index 2ef383960f4..b26efbb4535 100644 --- a/app/views/devise/sessions/_new_ldap.html.haml +++ b/app/views/devise/sessions/_new_ldap.html.haml @@ -1,6 +1,10 @@ -= form_tag(omniauth_callback_path(:user, server['provider_name']), id: 'new_ldap_user') do - = text_field_tag :username, nil, {class: "form-control top", placeholder: "#{server['label']} Login", autofocus: "autofocus"} - = password_field_tag :password, nil, {class: "form-control bottom", placeholder: "Password"} += form_tag(omniauth_callback_path(:user, server['provider_name']), id: 'new_ldap_user', class: "show-gl-field-errors") do + .form-group + = label_tag "#{server['label']} Login", for: :username + = text_field_tag :username, nil, {class: "form-control top", title: "This field is required.", autofocus: "autofocus", required: true } + .form-group + = label_tag :password + = password_field_tag :password, nil, { class: "form-control bottom", title: "This field is required.", required: true } - if devise_mapping.rememberable? .remember-me.checkbox %label{for: "remember_me"} diff --git a/app/views/devise/sessions/new.html.haml b/app/views/devise/sessions/new.html.haml index 28194506acc..2fb05b9456b 100644 --- a/app/views/devise/sessions/new.html.haml +++ b/app/views/devise/sessions/new.html.haml @@ -1,19 +1,23 @@ - page_title "Sign in" %div - - if signin_enabled? || ldap_enabled? || crowd_enabled? - = render 'devise/shared/signin_box' + - if form_based_providers.any? + = render 'devise/shared/tabs_ldap' + - else + = render 'devise/shared/tabs_normal' + .tab-content + - if signin_enabled? || ldap_enabled? || crowd_enabled? + = render 'devise/shared/signin_box' - -# Omniauth fits between signin/ldap signin and signup and does not have a surrounding box - - if omniauth_enabled? && devise_mapping.omniauthable? && button_based_providers_enabled? - .clearfix.prepend-top-20 - = render 'devise/shared/omniauth_box' - - -# Signup only makes sense if you can also sign-in - - if signin_enabled? && signup_enabled? - .prepend-top-20 + -# Signup only makes sense if you can also sign-in + - if signin_enabled? && signup_enabled? = render 'devise/shared/signup_box' - -# Show a message if none of the mechanisms above are enabled - - if !signin_enabled? && !ldap_enabled? && !(omniauth_enabled? && devise_mapping.omniauthable?) - %div - No authentication methods configured. + - if omniauth_enabled? && devise_mapping.omniauthable? && button_based_providers_enabled? + .clearfix + = render 'devise/shared/omniauth_box' + + -# Show a message if none of the mechanisms above are enabled + - if !signin_enabled? && !ldap_enabled? && !(omniauth_enabled? && devise_mapping.omniauthable?) + %div + No authentication methods configured. + diff --git a/app/views/devise/sessions/two_factor.html.haml b/app/views/devise/sessions/two_factor.html.haml index e623f7cff88..56074c057d7 100644 --- a/app/views/devise/sessions/two_factor.html.haml +++ b/app/views/devise/sessions/two_factor.html.haml @@ -3,20 +3,19 @@ = page_specific_javascript_tag('u2f.js') %div + = render 'devise/shared/tab_single', { :tab_title => 'Two-Factor Authentication' } .login-box - .login-heading - %h3 Two-Factor Authentication .login-body - if @user.two_factor_otp_enabled? - %h5 Authenticate via Two-Factor App - = form_for(resource, as: resource_name, url: session_path(resource_name), method: :post) do |f| + = form_for(resource, as: resource_name, url: session_path(resource_name), method: :post, html: { class: 'edit_user show-gl-field-errors' }) do |f| - resource_params = params[resource_name].presence || params = f.hidden_field :remember_me, value: resource_params.fetch(:remember_me, 0) - = f.text_field :otp_attempt, class: 'form-control', placeholder: 'Two-Factor Authentication code', required: true, autofocus: true, autocomplete: 'off' - %p.help-block.hint Enter the code from the two-factor app on your mobile device. If you've lost your device, you may enter one of your recovery codes. - .prepend-top-20 - = f.submit "Verify code", class: "btn btn-save" + .form-group + = f.label 'Two-Factor Authentication code', name: :otp_attempt + = f.text_field :otp_attempt, class: 'form-control', required: true, autofocus: true, autocomplete: 'off', title: 'This field is required.' + %p.help-block.hint Enter the code from the two-factor app on your mobile device. If you've lost your device, you may enter one of your recovery codes. + .prepend-top-20 + = f.submit "Verify code", class: "btn btn-save" - if @user.two_factor_u2f_enabled? - %hr = render "u2f/authenticate", locals: { params: params, resource: resource, resource_name: resource_name } diff --git a/app/views/devise/shared/_omniauth_box.html.haml b/app/views/devise/shared/_omniauth_box.html.haml index 2e7da2747d0..d5b6db48a29 100644 --- a/app/views/devise/shared/_omniauth_box.html.haml +++ b/app/views/devise/shared/_omniauth_box.html.haml @@ -1,8 +1,9 @@ -%p - %span.light - Sign in with   - - providers = enabled_button_based_providers - - providers.each do |provider| +%div.login-box + %p %span.light - - has_icon = provider_has_icon?(provider) - = link_to provider_image_tag(provider), omniauth_authorize_path(:user, provider), method: :post, class: (has_icon ? 'oauth-image-link' : 'btn'), "data-no-turbolink" => "true" + Sign in with   + - providers = enabled_button_based_providers + - providers.each do |provider| + %span.light + - has_icon = provider_has_icon?(provider) + = link_to provider_image_tag(provider), omniauth_authorize_path(:user, provider), method: :post, class: (has_icon ? 'oauth-image-link' : 'btn'), "data-no-turbolink" => "true" diff --git a/app/views/devise/shared/_sign_in_link.html.haml b/app/views/devise/shared/_sign_in_link.html.haml index fafc4b82f53..289bf40f3de 100644 --- a/app/views/devise/shared/_sign_in_link.html.haml +++ b/app/views/devise/shared/_sign_in_link.html.haml @@ -1,5 +1,4 @@ %p %span.light Already have login and password? - %strong = link_to "Sign in", new_session_path(resource_name) diff --git a/app/views/devise/shared/_signin_box.html.haml b/app/views/devise/shared/_signin_box.html.haml index 2c15e2c4891..810dd5ab687 100644 --- a/app/views/devise/shared/_signin_box.html.haml +++ b/app/views/devise/shared/_signin_box.html.haml @@ -1,32 +1,15 @@ -.login-box - - if signup_enabled? - .login-heading - %h3 Existing user? Sign in - - else - .login-heading - %h3 Sign in +#login-pane.login-box{ role: 'tabpanel', class: 'tab-pane active' } .login-body - if form_based_providers.any? - %ul.nav-links - - if crowd_enabled? - %li.active - = link_to "Crowd", "#tab-crowd", 'data-toggle' => 'tab' - - @ldap_servers.each_with_index do |server, i| - %li{class: (:active if i.zero? && !crowd_enabled?)} - = link_to server['label'], "#tab-#{server['provider_name']}", 'data-toggle' => 'tab' - - if signin_enabled? - %li - = link_to 'Standard', '#tab-signin', 'data-toggle' => 'tab' - .tab-content - - if crowd_enabled? - %div.tab-pane.active{id: "tab-crowd"} - = render 'devise/sessions/new_crowd' - - @ldap_servers.each_with_index do |server, i| - %div.tab-pane{id: "tab-#{server['provider_name']}", class: (:active if i.zero? && !crowd_enabled?)} - = render 'devise/sessions/new_ldap', server: server - - if signin_enabled? - %div#tab-signin.tab-pane - = render 'devise/sessions/new_base' + - if crowd_enabled? + %div.tab-pane.active{id: "tab-crowd"} + = render 'devise/sessions/new_crowd' + - @ldap_servers.each_with_index do |server, i| + %div.tab-pane{id: "tab-#{server['provider_name']}", class: (:active if i.zero? && !crowd_enabled?)} + = render 'devise/sessions/new_ldap', server: server + - if signin_enabled? + %div#tab-signin.tab-pane + = render 'devise/sessions/new_base' - elsif signin_enabled? = render 'devise/sessions/new_base' diff --git a/app/views/devise/shared/_signup_box.html.haml b/app/views/devise/shared/_signup_box.html.haml index 905a8dbcd84..c43a6aa3e49 100644 --- a/app/views/devise/shared/_signup_box.html.haml +++ b/app/views/devise/shared/_signup_box.html.haml @@ -1,28 +1,30 @@ -.login-box - - if signin_enabled? - .login-heading - %h3 New user? Create an account - - else - .login-heading - %h3 Create an account +#register-pane.login-box{ role: 'tabpanel', class: 'tab-pane' } .login-body - = form_for(resource, as: "new_#{resource_name}", url: registration_path(resource_name)) do |f| + = form_for(resource, as: "new_#{resource_name}", url: registration_path(resource_name), html: { class: "new_new_user show-gl-field-errors", "aria-live" => "assertive" }) do |f| .devise-errors = devise_error_messages! - %div - = f.text_field :name, class: "form-control top", placeholder: "Name", required: true - %div - = f.text_field :username, class: "form-control middle", placeholder: "Username", required: true - %div - = f.email_field :email, class: "form-control middle", placeholder: "Email", required: true + %div.form-group + = f.label :name + = f.text_field :name, class: "form-control top", required: true, title: "This field is required." + %div.username.form-group + = f.label :username + = f.text_field :username, class: "form-control middle", pattern: "[a-zA-Z0-9]+", required: true + %p.gl-field-error.hide Please create a username with only alphanumeric characters. + %p.validation-error.hide Username is already taken. + %p.validation-success.hide Username is available. + %p.validation-pending.hide Checking username availability... + %div.form-group + = f.label :email + = f.email_field :email, class: "form-control middle", required: true, title: "Please provide a valid email address." .form-group.append-bottom-20#password-strength - = f.password_field :password, class: "form-control bottom", placeholder: "Password - minimum length #{@minimum_password_length} characters", required: true, pattern: ".{#{@minimum_password_length},}", title: "Minimum length is #{@minimum_password_length} characters" + = f.label :password + = f.password_field :password, class: "form-control bottom", required: true, pattern: ".{#{@minimum_password_length},}", title: "Minimum length is #{@minimum_password_length} characters." + %p.gl-field-hint Minimum length is #{@minimum_password_length} characters %div - if current_application_settings.recaptcha_enabled = recaptcha_tags %div - = f.submit "Sign up", class: "btn-create btn" - + = f.submit "Register", class: "btn-register btn" .clearfix.prepend-top-20 %p %span.light Didn't receive a confirmation email? diff --git a/app/views/devise/shared/_tab_single.html.haml b/app/views/devise/shared/_tab_single.html.haml new file mode 100644 index 00000000000..8590c43d54d --- /dev/null +++ b/app/views/devise/shared/_tab_single.html.haml @@ -0,0 +1,4 @@ +// = render 'devise/shared/tab_single', :tab_title => 'Tab Title' +%ul.nav-links.nav-tabs.new-session-tabs.single-tab + %li.active + = link_to tab_title, '#', disabled: true diff --git a/app/views/devise/shared/_tabs_ldap.html.haml b/app/views/devise/shared/_tabs_ldap.html.haml new file mode 100644 index 00000000000..e276e91433a --- /dev/null +++ b/app/views/devise/shared/_tabs_ldap.html.haml @@ -0,0 +1,10 @@ +%ul.new-session-tabs.nav-links.nav-tabs + - if crowd_enabled? + %li.active + = link_to "Crowd", "#tab-crowd", 'data-toggle' => 'tab' + - @ldap_servers.each_with_index do |server, i| + %li{class: (:active if i.zero? && !crowd_enabled?)} + = link_to server['label'], "#tab-#{server['provider_name']}", 'data-toggle' => 'tab' + - if signin_enabled? + %li + = link_to 'Standard', '#tab-signin', 'data-toggle' => 'tab' diff --git a/app/views/devise/shared/_tabs_normal.html.haml b/app/views/devise/shared/_tabs_normal.html.haml new file mode 100644 index 00000000000..48abd6519d6 --- /dev/null +++ b/app/views/devise/shared/_tabs_normal.html.haml @@ -0,0 +1,5 @@ +%ul.nav-links.new-session-tabs.nav-tabs{ role: 'tablist'} + %li.active{ role: 'presentation' } + %a{ href: '#login-pane', data: {'toggle':'tab'}, role: 'tab'} Sign in + %li{ role: 'presentation'} + %a{ href: '#register-pane', data: {'toggle':'tab'}, role: 'tab'} Register diff --git a/app/views/devise/unlocks/new.html.haml b/app/views/devise/unlocks/new.html.haml index 49c087c0646..0036f3b98e5 100644 --- a/app/views/devise/unlocks/new.html.haml +++ b/app/views/devise/unlocks/new.html.haml @@ -1,12 +1,12 @@ += render 'devise/shared/tab_single', { :tab_title => 'Resend unlock instructions' } .login-box - .login-heading - %h3 Resend unlock email .login-body - = form_for(resource, as: resource_name, url: unlock_path(resource_name), html: { method: :post }) do |f| + = form_for(resource, as: resource_name, url: unlock_path(resource_name), html: { method: :post, class: 'show-gl-field-errors' }) do |f| .devise-errors = devise_error_messages! - .clearfix.append-bottom-20 - = f.email_field :email, class: 'form-control', placeholder: 'Email', autofocus: 'autofocus', autocapitalize: 'off', autocorrect: 'off' + .form-group.append-bottom-20 + = f.label :email + = f.email_field :email, class: 'form-control', autofocus: 'autofocus', autocapitalize: 'off', autocorrect: 'off', title: 'Please provide a valid email address.' .clearfix = f.submit 'Resend unlock instructions', class: 'btn btn-success' diff --git a/app/views/layouts/devise.html.haml b/app/views/layouts/devise.html.haml index a9a384bd5f3..825e540cb0c 100644 --- a/app/views/layouts/devise.html.haml +++ b/app/views/layouts/devise.html.haml @@ -1,36 +1,37 @@ !!! 5 -%html{ lang: "en"} +%html{ lang: "en", class: "devise-layout-html"} = render "layouts/head" - %body.ui_charcoal.login-page.application.navless - = Gon::Base.render_data - = render "layouts/header/empty" - = render "layouts/broadcast" - .container.navless-container - .content - = render "layouts/flash" - .row - .col-sm-5.pull-right - = yield - .col-sm-7.brand-holder.pull-left - %h1 - = brand_title - - if brand_item - = brand_image - = brand_text - - else - %h3 Open source software to collaborate on code + %body{ class: "ui_charcoal login-page application navless", data: {page: body_data_page}} + .page-wrap + = Gon::Base.render_data + = render "layouts/header/empty" + = render "layouts/broadcast" + .container.navless-container + .content + = render "layouts/flash" + .row + .col-sm-5.pull-right.new-session-forms-container + = yield + .col-sm-7.brand-holder.pull-left + %h1 + = brand_title + - if brand_item + = brand_image + = brand_text + - else + %h3 Open source software to collaborate on code - %p - Manage git repositories with fine grained access controls that keep your code secure. - Perform code reviews and enhance collaboration with merge requests. - Each project can also have an issue tracker and a wiki. + %p + Manage git repositories with fine grained access controls that keep your code secure. + Perform code reviews and enhance collaboration with merge requests. + Each project can also have an issue tracker and a wiki. - if current_application_settings.sign_in_text.present? = markdown_field(current_application_settings, :sign_in_text) - %hr - .container - .footer-links - = link_to "Explore", explore_root_path - = link_to "Help", help_path - = link_to "About GitLab", "https://about.gitlab.com/" + %hr.footer-fixed + .container.footer-container + .footer-links + = link_to "Explore", explore_root_path + = link_to "Help", help_path + = link_to "About GitLab", "https://about.gitlab.com/" diff --git a/app/views/u2f/_authenticate.html.haml b/app/views/u2f/_authenticate.html.haml index 9657101ace5..232ca26c1af 100644 --- a/app/views/u2f/_authenticate.html.haml +++ b/app/views/u2f/_authenticate.html.haml @@ -6,7 +6,7 @@ %script#js-authenticate-u2f-setup{ type: "text/template" } %div %p Insert your security key (if you haven't already), and press the button below. - %a.btn.btn-info#js-login-u2f-device{ href: 'javascript:void(0)' } Login Via U2F Device + %a.btn.btn-info#js-login-u2f-device{ href: 'javascript:void(0)' } Sign in via U2F device %script#js-authenticate-u2f-in-progress{ type: "text/template" } %p Trying to communicate with your device. Plug it in (if you haven't already) and press the button on the device now. -- cgit v1.2.1 From d2bad46efee65688ab56d9d7c850775f1339f491 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Fri, 23 Sep 2016 06:47:53 +0200 Subject: Add padding to fixed footer, to more quickly support scrolling. --- app/assets/stylesheets/pages/login.scss | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'app') diff --git a/app/assets/stylesheets/pages/login.scss b/app/assets/stylesheets/pages/login.scss index 06b90fbefab..9cc1bf90122 100644 --- a/app/assets/stylesheets/pages/login.scss +++ b/app/assets/stylesheets/pages/login.scss @@ -250,4 +250,8 @@ height: 40px; background: $white-light; } + + .navless-container { + padding: 65px; // height of footer + bottom padding of email confirmation link + } } -- cgit v1.2.1 From 768cd071afa41acc5b9c91958ac280e1d6870dae Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Fri, 23 Sep 2016 07:25:47 +0200 Subject: Clean up username_validator private vars and members. --- app/assets/javascripts/username_validator.js.es6 | 91 ++++++++++++------------ 1 file changed, 47 insertions(+), 44 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/username_validator.js.es6 b/app/assets/javascripts/username_validator.js.es6 index f8be356af04..a22f598b753 100644 --- a/app/assets/javascripts/username_validator.js.es6 +++ b/app/assets/javascripts/username_validator.js.es6 @@ -1,34 +1,34 @@ ((global) => { const debounceTimeoutDuration = 1000; - const inputErrorClass = 'gl-field-error-outline'; - const inputSuccessClass = 'gl-field-success-outline'; - const messageErrorSelector = '.username .validation-error'; - const messageSuccessSelector = '.username .validation-success'; - const messagePendingSelector = '.username .validation-pending'; + const invalidInputClass = 'gl-field-error-outline'; + const successInputClass = 'gl-field-success-outline'; + const unavailableMessageSelector = '.username .validation-error'; + const successMessageSelector = '.username .validation-success'; + const pendingMessageSelector = '.username .validation-pending'; + const invalidMessageSelector = '.username .gl-field-error'; class UsernameValidator { constructor() { this.inputElement = $('#new_user_username'); this.inputDomElement = this.inputElement.get(0); - - this.available = false; - this.valid = false; - this.pending = false; - this.fresh = true; - this.empty = true; + this.state = { + available: false, + valid: false, + pending: false, + empty: true + }; const debounceTimeout = _.debounce((username) => { - this.validateUsername(username); + this.state.validateUsername(username); }, debounceTimeoutDuration); this.inputElement.on('keyup.username_check', () => { const username = this.inputElement.val(); - this.valid = this.inputDomElement.validity.valid; - this.fresh = false; - this.empty = !username.length; + this.state.valid = this.inputDomElement.validity.valid; + this.state.empty = !username.length; - if (this.valid) { + if (this.state.valid) { return debounceTimeout(username); } @@ -36,43 +36,43 @@ }); // Override generic field validation - this.inputElement.on('invalid', this.handleInvalidInput.bind(this)); + this.inputElement.on('invalid', this.interceptInvalid.bind(this)); } renderState() { // Clear all state this.clearFieldValidationState(); - if (this.valid && this.available) { + if (this.state.valid && this.state.available) { return this.setSuccessState(); } - if (this.empty) { + if (this.state.empty) { return this.clearFieldValidationState(); } - if (this.pending) { + if (this.state.pending) { return this.setPendingState(); } - if (!this.available) { + if (!this.state.available) { return this.setUnavailableState(); } - if (!this.valid) { + if (!this.state.valid) { return this.setInvalidState(); } } - handleInvalidInput(event) { + interceptInvalid(event) { event.preventDefault(); event.stopPropagation(); } validateUsername(username) { - if (this.valid) { - this.pending = true; - this.available = false; + if (this.state.valid) { + this.state.pending = true; + this.state.available = false; this.renderState(); return $.ajax({ type: 'GET', @@ -83,38 +83,40 @@ } } - updateValidationState(usernameTaken) { + setAvailabilityState(usernameTaken) { if (usernameTaken) { - this.valid = false; - this.available = false; + this.state.valid = false; + this.state.available = false; } else { - this.available = true; + this.state.available = true; } - this.pending = false; + this.state.pending = false; this.renderState(); } clearFieldValidationState() { - this.inputElement.siblings('p').hide(); - this.inputElement.removeClass(inputErrorClass); - this.inputElement.removeClass(inputSuccessClass); + // TODO: Double check if this is valid chaining + const $input = this.inputElement + .siblings('p').hide().end() + .removeClass(invalidInputClass); + removeClass(successInputClass); } setUnavailableState() { - const $usernameErrorMessage = this.inputElement.siblings(messageErrorSelector); - this.inputElement.addClass(inputErrorClass).removeClass(inputSuccessClass); - $usernameErrorMessage.show(); + const $usernameUnavailableMessage = this.inputElement.siblings(unavailableMessageSelector); + this.inputElement.addClass(invalidInputClass).removeClass(successInputClass); + $usernameUnavailableMessage.show(); } setSuccessState() { - const $usernameSuccessMessage = this.inputElement.siblings(messageSuccessSelector); - this.inputElement.addClass(inputSuccessClass).removeClass(inputErrorClass); + const $usernameSuccessMessage = this.inputElement.siblings(successMessageSelector); + this.inputElement.addClass(successInputClass).removeClass(invalidInputClass); $usernameSuccessMessage.show(); } - setPendingState(show) { - const $usernamePendingMessage = $(messagePendingSelector); - if (this.pending) { + setPendingState() { + const $usernamePendingMessage = $(pendingMessageSelector); + if (this.state.pending) { $usernamePendingMessage.show(); } else { $usernamePendingMessage.hide(); @@ -122,8 +124,9 @@ } setInvalidState() { - this.inputElement.addClass(inputErrorClass).removeClass(inputSuccessClass); - $(`.gl-field-error`).show(); + const $inputErrorMessage = $(invalidMessageSelector); + this.inputElement.addClass(invalidInputClass).removeClass(successInputClass); + $inputErrorMessage.show(); } } -- cgit v1.2.1 From a449b9b8a171c659c7b3ce37f685624e3e079192 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Fri, 23 Sep 2016 08:31:04 +0200 Subject: Refactor gl field errors for simpler state management. --- app/assets/javascripts/gl_field_errors.js.es6 | 134 +++++++++++++++++--------- 1 file changed, 89 insertions(+), 45 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/gl_field_errors.js.es6 b/app/assets/javascripts/gl_field_errors.js.es6 index 42a2ddeeafe..f4c09dd407d 100644 --- a/app/assets/javascripts/gl_field_errors.js.es6 +++ b/app/assets/javascripts/gl_field_errors.js.es6 @@ -13,79 +13,123 @@ * * */ - const fieldErrorClass = 'gl-field-error'; - const fieldErrorSelector = `.${fieldErrorClass}`; + const errorMessageClass = 'gl-field-error'; const inputErrorClass = 'gl-field-error-outline'; - class GlFieldErrors { - constructor(form) { - this.form = $(form); - this.initValidators(); + class GlFieldError { + constructor({ input, form }) { + this.inputElement = $(input); + this.inputDomElement = this.inputElement.get(0); + this.form = form; + this.errorMessage = this.inputElement.attr('title') || 'This field is required.'; + this.fieldErrorElement = $(`

    ${ this.errorMessage }

    `); + + this.state = { + valid: false, + empty: true + }; + + this.initFieldValidation(); } - initValidators () { - this.inputs = this.form.find(':input:not([type=hidden])').toArray(); - this.inputs.forEach((input) => { - $(input).off('invalid').on('invalid', this.handleInvalidInput.bind(this)); - }); - this.form.on('submit', this.catchInvalidFormSubmit); + initFieldValidation() { + // hidden when injected into DOM + $input.after(this.fieldErrorElement); + this.inputElement.off('invalid').on('invalid', this.handleInvalidInput.bind(this)); } - /* Neccessary because Safari & iOS quietly allow form submission when form is invalid */ - catchInvalidFormSubmit (event) { - if (!event.currentTarget.checkValidity()) { - event.preventDefault(); - // Prevents disabling of invalid submit button by application.js - event.stopPropagation(); + renderValidity() { + this.setClearState(); + + if (this.state.valid) { + this.setValidState(); + } + + if (this.state.empty) { + this.setEmptyState(); + } + + if (!this.state.valid) { + this.setInvalidState(); } + + this.form.focusOnFirstInvalid.apply(this); } - handleInvalidInput (event) { + handleInvalidInput(event) { event.preventDefault(); - this.updateFieldValidityState(event); - const $input = $(event.currentTarget); + this.state.valid = true; + this.state.empty = false; + + this.renderValidity(); // For UX, wait til after first invalid submission to check each keyup - $input.off('keyup.field_validator') - .on('keyup.field_validator', this.updateFieldValidityState.bind(this)); + this.inputElement.off('keyup.field_validator') + .on('keyup.field_validator', this.updateValidityState.bind(this)); } - displayFieldValidity (target, isValid) { - const $input = $(target).removeClass(inputErrorClass); - const $existingError = $input.siblings(fieldErrorSelector); - const alreadyInvalid = !!$existingError.length; - const implicitErrorMessage = $input.attr('title'); - const $errorToDisplay = alreadyInvalid ? $existingError.detach() : $(`

    ${implicitErrorMessage}

    `); + getInputValidity() { + return this.inputDomElement.validity.valid; + } - if (!isValid) { - $input.after($errorToDisplay); - $input.addClass(inputErrorClass); - } + updateValidityState() { + const inputVal = this.inputElement.val(); + this.state.empty = !!inputVal.length; + this.state.valid = this.getInputValidity; - this.updateFieldSiblings($errorToDisplay, isValid); + this.renderValidity(); } - updateFieldSiblings($target, isValid) { - const siblings = $target.siblings(`p${fieldErrorSelector}`); - return isValid ? siblings.show() : siblings.hide(); + setValidState() { + return this.setClearState(); + } + + setEmptyState() { + return this.setClearState(); + } + + setInvalidState() { + $input.addClass(inputErrorClass); + return this.$fieldErrorElement.show(); + } + + setClearState() { + $input.removeClass(inputErrorClass); + return this.fieldErrorElement.hide(); } checkFieldValidity(target) { return target.validity.valid; } + } - updateFieldValidityState(event) { - const target = event.currentTarget; - const isKeyup = event.type === 'keyup'; - const isValid = this.checkFieldValidity(target); + class GlFieldErrors { + constructor(form) { + this.form = $(form); + this.initValidators(); + } - this.displayFieldValidity(target, isValid); + initValidators () { + // select all non-hidden inputs in form + const form = this.form; + + this.inputs = this.form.find(':input:not([type=hidden])') + .toArray() + .map((input) => new GlFieldError({ input, form })); + + this.form.on('submit', this.catchInvalidFormSubmit); + } - // prevent changing focus while user is typing. - if (!isKeyup) { - this.focusOnFirstInvalid.apply(this); + /* Neccessary to prevent intercept and override invalid form submit + * because Safari & iOS quietly allow form submission when form is invalid + * and prevents disabling of invalid submit button by application.js */ + + catchInvalidFormSubmit (event) { + if (!event.currentTarget.checkValidity()) { + event.preventDefault(); + event.stopPropagation(); } } -- cgit v1.2.1 From 00bfb645e16b24ce929211bf5080aa3083f8543b Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Fri, 23 Sep 2016 10:29:21 +0200 Subject: Fix errors, get validation running for signup box and sign in. --- app/assets/javascripts/gl_field_errors.js.es6 | 52 ++++++++++++++---------- app/assets/javascripts/username_validator.js.es6 | 12 +++--- app/views/devise/shared/_signup_box.html.haml | 2 +- 3 files changed, 38 insertions(+), 28 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/gl_field_errors.js.es6 b/app/assets/javascripts/gl_field_errors.js.es6 index f4c09dd407d..e1de7f78efc 100644 --- a/app/assets/javascripts/gl_field_errors.js.es6 +++ b/app/assets/javascripts/gl_field_errors.js.es6 @@ -17,10 +17,10 @@ const inputErrorClass = 'gl-field-error-outline'; class GlFieldError { - constructor({ input, form }) { + constructor({ input, formErrors }) { this.inputElement = $(input); this.inputDomElement = this.inputElement.get(0); - this.form = form; + this.form = formErrors; this.errorMessage = this.inputElement.attr('title') || 'This field is required.'; this.fieldErrorElement = $(`

    ${ this.errorMessage }

    `); @@ -34,7 +34,7 @@ initFieldValidation() { // hidden when injected into DOM - $input.after(this.fieldErrorElement); + this.inputElement.after(this.fieldErrorElement); this.inputElement.off('invalid').on('invalid', this.handleInvalidInput.bind(this)); } @@ -42,24 +42,24 @@ this.setClearState(); if (this.state.valid) { - this.setValidState(); + return this.setValidState(); } if (this.state.empty) { - this.setEmptyState(); + return this.setEmptyState(); } if (!this.state.valid) { - this.setInvalidState(); + return this.setInvalidState(); } - this.form.focusOnFirstInvalid.apply(this); + this.form.focusOnFirstInvalid.apply(this.form); } handleInvalidInput(event) { event.preventDefault(); - this.state.valid = true; + this.state.valid = false; this.state.empty = false; this.renderValidity(); @@ -77,8 +77,7 @@ updateValidityState() { const inputVal = this.inputElement.val(); this.state.empty = !!inputVal.length; - this.state.valid = this.getInputValidity; - + this.state.valid = this.getInputValidity(); this.renderValidity(); } @@ -87,17 +86,24 @@ } setEmptyState() { - return this.setClearState(); + return this.setInvalidState(); } setInvalidState() { - $input.addClass(inputErrorClass); - return this.$fieldErrorElement.show(); + this.inputElement.addClass(inputErrorClass); + this.inputElement.siblings('p').hide(); + return this.fieldErrorElement.show(); } setClearState() { - $input.removeClass(inputErrorClass); - return this.fieldErrorElement.hide(); + const inputVal = this.inputElement.val(); + if (!inputVal.split(' ').length) { + const trimmedInput = this.inputElement.val().trim(); + this.inputElement.val(trimmedInput); + } + this.inputElement.removeClass(inputErrorClass); + this.inputElement.siblings('p').hide(); + this.fieldErrorElement.hide(); } checkFieldValidity(target) { @@ -105,19 +111,23 @@ } } + const customValidationFlag = 'no-gl-field-errors'; + class GlFieldErrors { constructor(form) { this.form = $(form); + this.state = { + inputs: [], + valid: false + }; this.initValidators(); } initValidators () { // select all non-hidden inputs in form - const form = this.form; - - this.inputs = this.form.find(':input:not([type=hidden])') - .toArray() - .map((input) => new GlFieldError({ input, form })); + this.state.inputs = this.form.find(':input:not([type=hidden])').toArray() + .filter((input) => !input.classList.contains(customValidationFlag)) + .map((input) => new GlFieldError({ input, formErrors: this })); this.form.on('submit', this.catchInvalidFormSubmit); } @@ -134,7 +144,7 @@ } focusOnFirstInvalid () { - const firstInvalid = this.inputs.find((input) => !input.validity.valid); + const firstInvalid = this.state.inputs.find((input) => !input.inputDomElement.validity.valid); $(firstInvalid).focus(); } } diff --git a/app/assets/javascripts/username_validator.js.es6 b/app/assets/javascripts/username_validator.js.es6 index a22f598b753..b19fb9b4771 100644 --- a/app/assets/javascripts/username_validator.js.es6 +++ b/app/assets/javascripts/username_validator.js.es6 @@ -19,7 +19,7 @@ }; const debounceTimeout = _.debounce((username) => { - this.state.validateUsername(username); + this.validateUsername(username); }, debounceTimeoutDuration); this.inputElement.on('keyup.username_check', () => { @@ -78,7 +78,7 @@ type: 'GET', url: `/u/${username}/exists`, dataType: 'json', - success: (res) => this.updateValidationState(res.exists) + success: (res) => this.setAvailabilityState(res.exists) }); } } @@ -96,10 +96,10 @@ clearFieldValidationState() { // TODO: Double check if this is valid chaining - const $input = this.inputElement - .siblings('p').hide().end() - .removeClass(invalidInputClass); - removeClass(successInputClass); + this.inputElement.siblings('p').hide(); + + this.inputElement.removeClass(invalidInputClass) + .removeClass(successInputClass); } setUnavailableState() { diff --git a/app/views/devise/shared/_signup_box.html.haml b/app/views/devise/shared/_signup_box.html.haml index c43a6aa3e49..7382042cc50 100644 --- a/app/views/devise/shared/_signup_box.html.haml +++ b/app/views/devise/shared/_signup_box.html.haml @@ -8,7 +8,7 @@ = f.text_field :name, class: "form-control top", required: true, title: "This field is required." %div.username.form-group = f.label :username - = f.text_field :username, class: "form-control middle", pattern: "[a-zA-Z0-9]+", required: true + = f.text_field :username, class: "form-control middle no-gl-field-error", pattern: "[a-zA-Z0-9]+", required: true %p.gl-field-error.hide Please create a username with only alphanumeric characters. %p.validation-error.hide Username is already taken. %p.validation-success.hide Username is available. -- cgit v1.2.1 From 74bfba72263ae28a1a9a9fd53984e5d11a125fa7 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Fri, 23 Sep 2016 11:25:30 +0200 Subject: Include correct validation error with username invalid. --- app/views/devise/shared/_signup_box.html.haml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'app') diff --git a/app/views/devise/shared/_signup_box.html.haml b/app/views/devise/shared/_signup_box.html.haml index 7382042cc50..fb7ee2dbd3b 100644 --- a/app/views/devise/shared/_signup_box.html.haml +++ b/app/views/devise/shared/_signup_box.html.haml @@ -8,8 +8,7 @@ = f.text_field :name, class: "form-control top", required: true, title: "This field is required." %div.username.form-group = f.label :username - = f.text_field :username, class: "form-control middle no-gl-field-error", pattern: "[a-zA-Z0-9]+", required: true - %p.gl-field-error.hide Please create a username with only alphanumeric characters. + = f.text_field :username, class: "form-control middle no-gl-field-error", pattern: "[a-zA-Z0-9]+", required: true, title: 'Please create a username with only alphanumeric characters.' %p.validation-error.hide Username is already taken. %p.validation-success.hide Username is available. %p.validation-pending.hide Checking username availability... -- cgit v1.2.1 From bd0bf40763ed7acd43d24413f4b4a421f0076242 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Fri, 23 Sep 2016 13:30:26 +0200 Subject: Make style fixes, make all submit buttons full-width btn-block. --- app/assets/stylesheets/pages/login.scss | 5 +++++ app/views/devise/sessions/_new_base.html.haml | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) (limited to 'app') diff --git a/app/assets/stylesheets/pages/login.scss b/app/assets/stylesheets/pages/login.scss index 9cc1bf90122..f680fd68d70 100644 --- a/app/assets/stylesheets/pages/login.scss +++ b/app/assets/stylesheets/pages/login.scss @@ -198,6 +198,11 @@ font-weight: normal; } + input[type="submit"] { + @extend .btn-block; + margin-bottom: 0px; + } + .devise-errors { h2 { margin-top: 0; diff --git a/app/views/devise/sessions/_new_base.html.haml b/app/views/devise/sessions/_new_base.html.haml index cfb1b964d76..d0cd418236a 100644 --- a/app/views/devise/sessions/_new_base.html.haml +++ b/app/views/devise/sessions/_new_base.html.haml @@ -5,8 +5,6 @@ %div.form-group = f.label :password = f.password_field :password, class: "form-control bottom", required: true, title: "This field is required." - .sign-in - = f.submit "Sign in", class: "btn btn-save" - if devise_mapping.rememberable? .remember-me.checkbox %label{for: "user_remember_me"} @@ -14,3 +12,5 @@ %span Remember me .pull-right = link_to "Forgot your password?", new_password_path(resource_name) + %div.prepend-top-20 + = f.submit "Sign in", class: "btn btn-save" -- cgit v1.2.1 From 2c7a1af66f45e475b5aa7b8b91e7f8c85134a762 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Fri, 23 Sep 2016 13:38:39 +0200 Subject: Add submit button contain with custom margin. --- app/assets/stylesheets/pages/login.scss | 4 ++++ app/views/devise/sessions/_new_base.html.haml | 2 +- app/views/devise/shared/_signup_box.html.haml | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) (limited to 'app') diff --git a/app/assets/stylesheets/pages/login.scss b/app/assets/stylesheets/pages/login.scss index f680fd68d70..b235eb845b0 100644 --- a/app/assets/stylesheets/pages/login.scss +++ b/app/assets/stylesheets/pages/login.scss @@ -198,6 +198,10 @@ font-weight: normal; } + .submit-container { + margin-top: 16px; + } + input[type="submit"] { @extend .btn-block; margin-bottom: 0px; diff --git a/app/views/devise/sessions/_new_base.html.haml b/app/views/devise/sessions/_new_base.html.haml index d0cd418236a..a96b579c593 100644 --- a/app/views/devise/sessions/_new_base.html.haml +++ b/app/views/devise/sessions/_new_base.html.haml @@ -12,5 +12,5 @@ %span Remember me .pull-right = link_to "Forgot your password?", new_password_path(resource_name) - %div.prepend-top-20 + %div.submit-container = f.submit "Sign in", class: "btn btn-save" diff --git a/app/views/devise/shared/_signup_box.html.haml b/app/views/devise/shared/_signup_box.html.haml index fb7ee2dbd3b..d0bbcf3115e 100644 --- a/app/views/devise/shared/_signup_box.html.haml +++ b/app/views/devise/shared/_signup_box.html.haml @@ -24,7 +24,7 @@ = recaptcha_tags %div = f.submit "Register", class: "btn-register btn" -.clearfix.prepend-top-20 +.clearfix.submit-container %p %span.light Didn't receive a confirmation email? = succeed '.' do -- cgit v1.2.1 From 49688d399a3b3c7d3ec50ae0bb728ff0d25671b0 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Tue, 27 Sep 2016 14:06:18 +0200 Subject: Add third box shadow to validation focus, for depth. --- app/assets/stylesheets/pages/login.scss | 90 ++++++++++++++------------------- 1 file changed, 37 insertions(+), 53 deletions(-) (limited to 'app') diff --git a/app/assets/stylesheets/pages/login.scss b/app/assets/stylesheets/pages/login.scss index b235eb845b0..f1d15417705 100644 --- a/app/assets/stylesheets/pages/login.scss +++ b/app/assets/stylesheets/pages/login.scss @@ -40,7 +40,7 @@ p { font-size: 13px; } - .login-box { + .login-box, .omniauth-container { box-shadow: 0 0 0 1px $border-color; border-bottom-right-radius: 2px; border-bottom-left-radius: 2px; @@ -79,69 +79,57 @@ background: transparent; } - .form-control { - font-size: 14px; - padding: 10px 8px; - width: 100%; - height: auto; - - &.top { - border-radius: 5px 5px 0 0; - margin-bottom: 0; - } + // Styles the glowing border of focused input for username async validation + .login-body { + font-size: 13px; - &.bottom { - border-radius: 0 0 5px 5px; - border-top: 0; - margin-bottom: 20px; - } - } - // Styles the glowing border of focused input for username async validation - .login-body { - font-size: 13px; + input + p { + margin-top: 5px; + } + .gl-field-success-outline { + border: 1px solid $green-normal; - input + p { - margin-top: 5px; - } + &:focus { + box-shadow: 0 0 0 1px $green-normal inset, 0 1px 1px rgba(0, 0, 0, 0.075) inset, 0 0 4px 0 $green-normal; + border: 0 none; + } + } - .gl-field-success-outline { - border: 1px solid $green-normal; + .gl-field-error-outline { + border: 1px solid $red-normal; - &:focus { - box-shadow: 0 0 0 1px $green-normal inset, 0 0 4px 0 $green-normal; - border: 0 none; + &:focus { + box-shadow: 0 0 0 1px $red-normal inset, 0 1px 1px rgba(0, 0, 0, 0.075) inset, 0 0 4px 0 rgba(210, 40, 82, 0.6); + border: 0 none; + } } - } - .gl-field-error-outline { - border: 1px solid $red-normal; + .username .validation-success, + .gl-field-success-message { + color: $green-normal; + } - &:focus { - opacity: .6; - box-shadow: 0 0 0 1px $red-normal inset, 0 0 4px 0 $red-normal; - border: 0 none; + .username .validation-error, + .gl-field-error-message { + color: $red-normal; } - } - .username .validation-success, - .gl-field-success-message { - color: $green-normal; - } + .gl-field-hint { + color: $gl-text-color; + } - .username .validation-error, - .gl-field-error-message { - color: $red-normal; } + } - .gl-field-hint { - color: $gl-text-color; + .omniauth-container { + p { + margin: 0; } - } - - .new-session-tabs { // Are these being applied to other login-related screens? They need to be. + .new-session-tabs { + display: -webkit-flex; display: flex; box-shadow: 0 0 0 1px $border-color; border-top-right-radius: 2px; @@ -151,10 +139,6 @@ flex: 1; text-align: center; - &.middle { - border-top: 0; - margin-bottom: 0; - border-radius: 0; &:last-of-type { border-left: 1px solid $border-color; } @@ -204,7 +188,7 @@ input[type="submit"] { @extend .btn-block; - margin-bottom: 0px; + margin-bottom: 0; } .devise-errors { -- cgit v1.2.1 From 80cbc9838eae8836a9bf85bac1dca7e965d1d77d Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Tue, 27 Sep 2016 17:07:51 +0200 Subject: Fix omniauth box styling. --- app/views/devise/sessions/new.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app') diff --git a/app/views/devise/sessions/new.html.haml b/app/views/devise/sessions/new.html.haml index 2fb05b9456b..dbdbc9689aa 100644 --- a/app/views/devise/sessions/new.html.haml +++ b/app/views/devise/sessions/new.html.haml @@ -13,7 +13,7 @@ = render 'devise/shared/signup_box' - if omniauth_enabled? && devise_mapping.omniauthable? && button_based_providers_enabled? - .clearfix + .clearfix.omniauth-box = render 'devise/shared/omniauth_box' -# Show a message if none of the mechanisms above are enabled -- cgit v1.2.1 From 503dcacaa42bc5870a87b579009c53c991b03c4e Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Wed, 28 Sep 2016 12:59:58 +0200 Subject: Properly implement focus on first invalid. --- app/assets/javascripts/gl_field_errors.js.es6 | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/gl_field_errors.js.es6 b/app/assets/javascripts/gl_field_errors.js.es6 index e1de7f78efc..91c25047f7b 100644 --- a/app/assets/javascripts/gl_field_errors.js.es6 +++ b/app/assets/javascripts/gl_field_errors.js.es6 @@ -53,17 +53,16 @@ return this.setInvalidState(); } - this.form.focusOnFirstInvalid.apply(this.form); } handleInvalidInput(event) { event.preventDefault(); - + const currentValue = this.inputElement.val(); this.state.valid = false; - this.state.empty = false; + this.state.empty = currentValue === ''; this.renderValidity(); - + this.form.focusOnFirstInvalid.apply(this.form); // For UX, wait til after first invalid submission to check each keyup this.inputElement.off('keyup.field_validator') .on('keyup.field_validator', this.updateValidityState.bind(this)); @@ -76,7 +75,7 @@ updateValidityState() { const inputVal = this.inputElement.val(); - this.state.empty = !!inputVal.length; + this.state.empty = !inputVal.length; this.state.valid = this.getInputValidity(); this.renderValidity(); } @@ -105,10 +104,6 @@ this.inputElement.siblings('p').hide(); this.fieldErrorElement.hide(); } - - checkFieldValidity(target) { - return target.validity.valid; - } } const customValidationFlag = 'no-gl-field-errors'; @@ -144,8 +139,8 @@ } focusOnFirstInvalid () { - const firstInvalid = this.state.inputs.find((input) => !input.inputDomElement.validity.valid); - $(firstInvalid).focus(); + const firstInvalid = this.state.inputs.filter((input) => !input.inputDomElement.validity.valid)[0]; + firstInvalid.inputElement.focus(); } } -- cgit v1.2.1 From 673c23dd7ebef7d6c370a0d0d5c024c1c042261c Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Wed, 28 Sep 2016 13:26:37 +0200 Subject: Unbreak all the tests relying on one login-box. --- app/views/devise/sessions/new.html.haml | 2 +- app/views/devise/shared/_omniauth_box.html.haml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'app') diff --git a/app/views/devise/sessions/new.html.haml b/app/views/devise/sessions/new.html.haml index dbdbc9689aa..2fb05b9456b 100644 --- a/app/views/devise/sessions/new.html.haml +++ b/app/views/devise/sessions/new.html.haml @@ -13,7 +13,7 @@ = render 'devise/shared/signup_box' - if omniauth_enabled? && devise_mapping.omniauthable? && button_based_providers_enabled? - .clearfix.omniauth-box + .clearfix = render 'devise/shared/omniauth_box' -# Show a message if none of the mechanisms above are enabled diff --git a/app/views/devise/shared/_omniauth_box.html.haml b/app/views/devise/shared/_omniauth_box.html.haml index d5b6db48a29..8908b64cdac 100644 --- a/app/views/devise/shared/_omniauth_box.html.haml +++ b/app/views/devise/shared/_omniauth_box.html.haml @@ -1,4 +1,4 @@ -%div.login-box +%div.omniauth-container %p %span.light Sign in with   -- cgit v1.2.1 From 445711675ca198660dcaee014cd4229b8eb266ab Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Wed, 28 Sep 2016 17:13:42 +0200 Subject: Fix nesting on sessions/new. --- app/views/devise/sessions/new.html.haml | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) (limited to 'app') diff --git a/app/views/devise/sessions/new.html.haml b/app/views/devise/sessions/new.html.haml index 2fb05b9456b..fa8e7979461 100644 --- a/app/views/devise/sessions/new.html.haml +++ b/app/views/devise/sessions/new.html.haml @@ -12,12 +12,11 @@ - if signin_enabled? && signup_enabled? = render 'devise/shared/signup_box' - - if omniauth_enabled? && devise_mapping.omniauthable? && button_based_providers_enabled? - .clearfix - = render 'devise/shared/omniauth_box' - - -# Show a message if none of the mechanisms above are enabled - - if !signin_enabled? && !ldap_enabled? && !(omniauth_enabled? && devise_mapping.omniauthable?) - %div - No authentication methods configured. + -# Show a message if none of the mechanisms above are enabled + - if !signin_enabled? && !ldap_enabled? && !(omniauth_enabled? && devise_mapping.omniauthable?) + %div + No authentication methods configured. + - if omniauth_enabled? && devise_mapping.omniauthable? && button_based_providers_enabled? + .clearfix + = render 'devise/shared/omniauth_box' -- cgit v1.2.1 From c2766614ddc04b8df54fe8649d1dbe516f532987 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Thu, 29 Sep 2016 11:58:43 +0200 Subject: Fix syntax error -- missing comma in _new_crowd. --- app/views/devise/sessions/_new_crowd.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app') diff --git a/app/views/devise/sessions/_new_crowd.html.haml b/app/views/devise/sessions/_new_crowd.html.haml index 5a192c63c7c..e82a08cdb0c 100644 --- a/app/views/devise/sessions/_new_crowd.html.haml +++ b/app/views/devise/sessions/_new_crowd.html.haml @@ -1,4 +1,4 @@ -= form_tag(omniauth_authorize_path(:user, :crowd), id: 'new_crowd_user' class: 'show-gl-field-errors') do += form_tag(omniauth_authorize_path(:user, :crowd), id: 'new_crowd_user', class: 'show-gl-field-errors') do .form-group = label_tag 'Username or email', for: :username = text_field_tag :username, nil, {class: "form-control top", title: "This field is required", autofocus: "autofocus", required: true } -- cgit v1.2.1 From 2486078d5b93aaaf5bb0f60e580f65fa3da0271a Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Fri, 30 Sep 2016 10:45:12 +0200 Subject: Prevent unneccessary loading of UsernameValidator. --- app/assets/javascripts/dispatcher.js | 1 - 1 file changed, 1 deletion(-) (limited to 'app') diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index fb6e82cd37c..956e6085bfb 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -22,7 +22,6 @@ shortcut_handler = null; switch (page) { case 'sessions:new': - case 'sessions:create': new UsernameValidator(); break; case 'projects:boards:show': -- cgit v1.2.1 From cbd68e5bd10b8cc8b933a8bfffd446a433628ddc Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Fri, 30 Sep 2016 10:45:53 +0200 Subject: Remove superfluous comment. --- app/assets/javascripts/username_validator.js.es6 | 1 - 1 file changed, 1 deletion(-) (limited to 'app') diff --git a/app/assets/javascripts/username_validator.js.es6 b/app/assets/javascripts/username_validator.js.es6 index b19fb9b4771..2517f778365 100644 --- a/app/assets/javascripts/username_validator.js.es6 +++ b/app/assets/javascripts/username_validator.js.es6 @@ -95,7 +95,6 @@ } clearFieldValidationState() { - // TODO: Double check if this is valid chaining this.inputElement.siblings('p').hide(); this.inputElement.removeClass(invalidInputClass) -- cgit v1.2.1 From 80864a1939e495ca5e85ba3c0c3ea869d40abda4 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Wed, 5 Oct 2016 14:28:17 +0200 Subject: Safely scope siblings of validated input. --- app/assets/javascripts/gl_field_errors.js.es6 | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/gl_field_errors.js.es6 b/app/assets/javascripts/gl_field_errors.js.es6 index 91c25047f7b..e54a10a1901 100644 --- a/app/assets/javascripts/gl_field_errors.js.es6 +++ b/app/assets/javascripts/gl_field_errors.js.es6 @@ -36,6 +36,19 @@ // hidden when injected into DOM this.inputElement.after(this.fieldErrorElement); this.inputElement.off('invalid').on('invalid', this.handleInvalidInput.bind(this)); + this.scopedSiblings = this.safelySelectSiblings(); + } + + safelySelectSiblings() { + // Apply `ignoreSelector` in markup to siblings whose visibility should not be toggled with input validity + const ignoreSelector = '.validation-ignore'; + const unignoredSiblings = this.inputElement.siblings(`p:not(${ignoreSelector})`); + const parentContainer = this.inputElement.parent('.form-group'); + + // Only select siblings when they're scoped within a form-group with one input + const safelyScoped = parentContainer.length && parentContainer.find('input').length === 1; + + return safelyScoped ? unignoredSiblings : this.fieldErrorElement; } renderValidity() { @@ -90,7 +103,7 @@ setInvalidState() { this.inputElement.addClass(inputErrorClass); - this.inputElement.siblings('p').hide(); + this.scopedSiblings.hide(); return this.fieldErrorElement.show(); } @@ -101,7 +114,7 @@ this.inputElement.val(trimmedInput); } this.inputElement.removeClass(inputErrorClass); - this.inputElement.siblings('p').hide(); + this.scopedSiblings.hide(); this.fieldErrorElement.hide(); } } -- cgit v1.2.1 From cdf232752a4c2bfc6ebb57bcca3b33bcf8755b40 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Wed, 5 Oct 2016 14:28:52 +0200 Subject: Add accessor for current val for convenience. --- app/assets/javascripts/gl_field_errors.js.es6 | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/gl_field_errors.js.es6 b/app/assets/javascripts/gl_field_errors.js.es6 index e54a10a1901..86aea25fbbb 100644 --- a/app/assets/javascripts/gl_field_errors.js.es6 +++ b/app/assets/javascripts/gl_field_errors.js.es6 @@ -68,9 +68,13 @@ } + accessCurrentVal(newVal) { + return newVal ? this.inputElement.val(newVal) : this.inputElement.val(); + } + handleInvalidInput(event) { event.preventDefault(); - const currentValue = this.inputElement.val(); + const currentValue = this.accessCurrentVal(); this.state.valid = false; this.state.empty = currentValue === ''; @@ -87,7 +91,7 @@ } updateValidityState() { - const inputVal = this.inputElement.val(); + const inputVal = this.accessCurrentVal(); this.state.empty = !inputVal.length; this.state.valid = this.getInputValidity(); this.renderValidity(); @@ -108,10 +112,10 @@ } setClearState() { - const inputVal = this.inputElement.val(); + const inputVal = this.accessCurrentVal(); if (!inputVal.split(' ').length) { - const trimmedInput = this.inputElement.val().trim(); - this.inputElement.val(trimmedInput); + const trimmedInput = inputVal.trim(); + this.accessCurrentVal(trimmedInput); } this.inputElement.removeClass(inputErrorClass); this.scopedSiblings.hide(); -- cgit v1.2.1 From 349caec3088905cf4dc740b8809f9fd6fbdeeb0e Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Fri, 7 Oct 2016 11:35:23 +0200 Subject: Stringify username before passing to ActiveRecord. --- app/controllers/users_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app') diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 30f0118254a..7a2bd83f22f 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -86,7 +86,7 @@ class UsersController < ApplicationController end def exists - render json: { exists: !User.find_by_username(params[:username]).nil? } + render json: { exists: User.where(username: params[:username].to_s).any? } end private -- cgit v1.2.1 From 3c536bcc9d89e6d28d5a9bfb3ac8d1191ad03b88 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Fri, 7 Oct 2016 11:51:02 +0200 Subject: Improve method naming in gl_field_errors. --- app/assets/javascripts/gl_field_errors.js.es6 | 43 ++++++++++++++------------- 1 file changed, 22 insertions(+), 21 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/gl_field_errors.js.es6 b/app/assets/javascripts/gl_field_errors.js.es6 index 86aea25fbbb..bd8f3c52234 100644 --- a/app/assets/javascripts/gl_field_errors.js.es6 +++ b/app/assets/javascripts/gl_field_errors.js.es6 @@ -35,7 +35,7 @@ initFieldValidation() { // hidden when injected into DOM this.inputElement.after(this.fieldErrorElement); - this.inputElement.off('invalid').on('invalid', this.handleInvalidInput.bind(this)); + this.inputElement.off('invalid').on('invalid', this.handleInvalidSubmit.bind(this)); this.scopedSiblings = this.safelySelectSiblings(); } @@ -52,29 +52,25 @@ } renderValidity() { - this.setClearState(); + this.renderClear(); if (this.state.valid) { - return this.setValidState(); + return this.renderValid(); } if (this.state.empty) { - return this.setEmptyState(); + return this.renderEmpty(); } if (!this.state.valid) { - return this.setInvalidState(); + return this.renderInvalid(); } } - accessCurrentVal(newVal) { - return newVal ? this.inputElement.val(newVal) : this.inputElement.val(); - } - - handleInvalidInput(event) { + handleInvalidSubmit(event) { event.preventDefault(); - const currentValue = this.accessCurrentVal(); + const currentValue = this.accessCurrentValue(); this.state.valid = false; this.state.empty = currentValue === ''; @@ -86,36 +82,41 @@ } + /* Get or set current input value */ + accessCurrentValue(newVal) { + return newVal ? this.inputElement.val(newVal) : this.inputElement.val(); + } + getInputValidity() { return this.inputDomElement.validity.valid; } - updateValidityState() { - const inputVal = this.accessCurrentVal(); + updateValidity() { + const inputVal = this.accessCurrentValue(); this.state.empty = !inputVal.length; this.state.valid = this.getInputValidity(); this.renderValidity(); } - setValidState() { - return this.setClearState(); + renderValid() { + return this.renderClear(); } - setEmptyState() { - return this.setInvalidState(); + renderEmpty() { + return this.renderInvalid(); } - setInvalidState() { + renderInvalid() { this.inputElement.addClass(inputErrorClass); this.scopedSiblings.hide(); return this.fieldErrorElement.show(); } - setClearState() { - const inputVal = this.accessCurrentVal(); + renderClear() { + const inputVal = this.accessCurrentValue(); if (!inputVal.split(' ').length) { const trimmedInput = inputVal.trim(); - this.accessCurrentVal(trimmedInput); + this.accessCurrentValue(trimmedInput); } this.inputElement.removeClass(inputErrorClass); this.scopedSiblings.hide(); -- cgit v1.2.1 From 0f57245f0c81fe00ecedbb39c1e93fc48ef55099 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Fri, 7 Oct 2016 11:57:49 +0200 Subject: Use native dom in dispatcher field error init. --- app/assets/javascripts/dispatcher.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index 956e6085bfb..846884ea793 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -296,9 +296,10 @@ }; Dispatcher.prototype.initFieldErrors = function() { - $('form.show-gl-field-errors').each(function(i, form) { - new gl.GlFieldErrors(form); - }); + return document.querySelectorAll('.show-gl-field-errors') + .forEach(function(form) { + new gl.GlFieldErrors(form); + }); }; return Dispatcher; -- cgit v1.2.1 From bad1feb23c63a86f8f4103e4b75ec590fc90da4f Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Fri, 7 Oct 2016 21:45:55 +0200 Subject: Convert field error NodeList to an Array. --- app/assets/javascripts/dispatcher.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index 846884ea793..6452ae675b4 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -296,8 +296,8 @@ }; Dispatcher.prototype.initFieldErrors = function() { - return document.querySelectorAll('.show-gl-field-errors') - .forEach(function(form) { + var flaggedForms = document.querySelectorAll('.show-gl-field-errors'); + return [...taggedForms].forEach(function(form) { new gl.GlFieldErrors(form); }); }; -- cgit v1.2.1 From f141cdb3dddd16c729a828838e907473132a3a18 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Fri, 7 Oct 2016 21:50:35 +0200 Subject: Fixup invalid refs. --- app/assets/javascripts/dispatcher.js | 2 +- app/assets/javascripts/gl_field_errors.js.es6 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index 6452ae675b4..abc077a404e 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -297,7 +297,7 @@ Dispatcher.prototype.initFieldErrors = function() { var flaggedForms = document.querySelectorAll('.show-gl-field-errors'); - return [...taggedForms].forEach(function(form) { + return [...flaggedForms].forEach(function(form) { new gl.GlFieldErrors(form); }); }; diff --git a/app/assets/javascripts/gl_field_errors.js.es6 b/app/assets/javascripts/gl_field_errors.js.es6 index bd8f3c52234..8657e7b4abf 100644 --- a/app/assets/javascripts/gl_field_errors.js.es6 +++ b/app/assets/javascripts/gl_field_errors.js.es6 @@ -78,7 +78,7 @@ this.form.focusOnFirstInvalid.apply(this.form); // For UX, wait til after first invalid submission to check each keyup this.inputElement.off('keyup.field_validator') - .on('keyup.field_validator', this.updateValidityState.bind(this)); + .on('keyup.field_validator', this.updateValidity.bind(this)); } -- cgit v1.2.1 From 23fd1f1630f4977ecc452ede086eaebfd836b978 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Mon, 10 Oct 2016 16:34:57 +0200 Subject: Use Ruby 1.9 syntax in tab_single def and usage. --- app/views/admin/appearances/preview.html.haml | 2 +- app/views/devise/confirmations/new.html.haml | 2 +- app/views/devise/passwords/edit.html.haml | 2 +- app/views/devise/passwords/new.html.haml | 2 +- app/views/devise/sessions/two_factor.html.haml | 2 +- app/views/devise/shared/_tab_single.html.haml | 3 +-- app/views/devise/unlocks/new.html.haml | 2 +- 7 files changed, 7 insertions(+), 8 deletions(-) (limited to 'app') diff --git a/app/views/admin/appearances/preview.html.haml b/app/views/admin/appearances/preview.html.haml index 0d35702c634..acbe17036f7 100644 --- a/app/views/admin/appearances/preview.html.haml +++ b/app/views/admin/appearances/preview.html.haml @@ -1,4 +1,4 @@ -= render 'devise/shared/tab_single', { :tab_title => 'Sign in preview' } += render 'devise/shared/tab_single', tab_title: 'Sign in preview' .login-box %form.show-gl-field-errors .form-group diff --git a/app/views/devise/confirmations/new.html.haml b/app/views/devise/confirmations/new.html.haml index 443a316c6e2..5d25dd398d6 100644 --- a/app/views/devise/confirmations/new.html.haml +++ b/app/views/devise/confirmations/new.html.haml @@ -1,4 +1,4 @@ -= render 'devise/shared/tab_single', { :tab_title => 'Resend confirmation instructions' } += render 'devise/shared/tab_single', tab_title: 'Resend confirmation instructions' .login-box .login-body = form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post, class: 'show-gl-field-errors' }) do |f| diff --git a/app/views/devise/passwords/edit.html.haml b/app/views/devise/passwords/edit.html.haml index 9c533ef9916..b518fae7c95 100644 --- a/app/views/devise/passwords/edit.html.haml +++ b/app/views/devise/passwords/edit.html.haml @@ -1,4 +1,4 @@ -= render 'devise/shared/tab_single', { :tab_title => 'Change your password' } += render 'devise/shared/tab_single', tab_title:'Change your password' .login-box .login-body = form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :put, class: 'show-gl-field-errors' }) do |f| diff --git a/app/views/devise/passwords/new.html.haml b/app/views/devise/passwords/new.html.haml index 91b46a12ac0..1fcfd06419a 100644 --- a/app/views/devise/passwords/new.html.haml +++ b/app/views/devise/passwords/new.html.haml @@ -1,4 +1,4 @@ -= render 'devise/shared/tab_single', { :tab_title => 'Reset Password' } += render 'devise/shared/tab_single', tab_title: 'Reset Password' .login-box .login-body = form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :post, class: 'show-gl-field-errors' }) do |f| diff --git a/app/views/devise/sessions/two_factor.html.haml b/app/views/devise/sessions/two_factor.html.haml index 56074c057d7..0e865b807c1 100644 --- a/app/views/devise/sessions/two_factor.html.haml +++ b/app/views/devise/sessions/two_factor.html.haml @@ -3,7 +3,7 @@ = page_specific_javascript_tag('u2f.js') %div - = render 'devise/shared/tab_single', { :tab_title => 'Two-Factor Authentication' } + = render 'devise/shared/tab_single', tab_title: 'Two-Factor Authentication' .login-box .login-body - if @user.two_factor_otp_enabled? diff --git a/app/views/devise/shared/_tab_single.html.haml b/app/views/devise/shared/_tab_single.html.haml index 8590c43d54d..f943d25e41a 100644 --- a/app/views/devise/shared/_tab_single.html.haml +++ b/app/views/devise/shared/_tab_single.html.haml @@ -1,4 +1,3 @@ -// = render 'devise/shared/tab_single', :tab_title => 'Tab Title' %ul.nav-links.nav-tabs.new-session-tabs.single-tab %li.active - = link_to tab_title, '#', disabled: true + %a= tab_title diff --git a/app/views/devise/unlocks/new.html.haml b/app/views/devise/unlocks/new.html.haml index 0036f3b98e5..49b2f77111f 100644 --- a/app/views/devise/unlocks/new.html.haml +++ b/app/views/devise/unlocks/new.html.haml @@ -1,4 +1,4 @@ -= render 'devise/shared/tab_single', { :tab_title => 'Resend unlock instructions' } += render 'devise/shared/tab_single', tab_title: 'Resend unlock instructions' .login-box .login-body = form_for(resource, as: resource_name, url: unlock_path(resource_name), html: { method: :post, class: 'show-gl-field-errors' }) do |f| -- cgit v1.2.1 From 85db5ba847ffc8f4b71ad9fe0ba22ab42f90b3e4 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Mon, 10 Oct 2016 16:35:27 +0200 Subject: Clean up layouts/devise.html. --- app/views/layouts/devise.html.haml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'app') diff --git a/app/views/layouts/devise.html.haml b/app/views/layouts/devise.html.haml index 825e540cb0c..6922f1e153f 100644 --- a/app/views/layouts/devise.html.haml +++ b/app/views/layouts/devise.html.haml @@ -1,7 +1,7 @@ !!! 5 %html{ lang: "en", class: "devise-layout-html"} = render "layouts/head" - %body{ class: "ui_charcoal login-page application navless", data: {page: body_data_page}} + %body.ui_charcoal.login-page.application.navless{ data: { page: body_data_page }} .page-wrap = Gon::Base.render_data = render "layouts/header/empty" @@ -22,7 +22,7 @@ %h3 Open source software to collaborate on code %p - Manage git repositories with fine grained access controls that keep your code secure. + Manage Git repositories with fine-grained access controls that keep your code secure. Perform code reviews and enhance collaboration with merge requests. Each project can also have an issue tracker and a wiki. -- cgit v1.2.1 From 5439bd9f952c7e7ee9fa10613655a9761396e1f9 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Mon, 10 Oct 2016 16:54:55 +0200 Subject: Attempt to fix username validation ruby. --- app/controllers/users_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app') diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 7a2bd83f22f..1aa22995692 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -86,7 +86,7 @@ class UsersController < ApplicationController end def exists - render json: { exists: User.where(username: params[:username].to_s).any? } + render json: { exists: !Namespace.where(name: params[:username]).nil? } end private -- cgit v1.2.1 From 568a405ce43834d5b4da3ff81f1e8a6972db802b Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Tue, 11 Oct 2016 18:28:00 +0200 Subject: Add exists to users routes and fix endpoint. --- app/controllers/users_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app') diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 1aa22995692..6a881b271d7 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -86,7 +86,7 @@ class UsersController < ApplicationController end def exists - render json: { exists: !Namespace.where(name: params[:username]).nil? } + render json: { exists: Namespace.where(path: params[:username].downcase).any? } end private -- cgit v1.2.1 From eb866dfe05c82b2a45da69d924bcbe15b151a054 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Wed, 12 Oct 2016 18:34:38 +0200 Subject: Convert dispatcher to es6. --- app/assets/javascripts/dispatcher.js | 309 ------------------------------- app/assets/javascripts/dispatcher.js.es6 | 309 +++++++++++++++++++++++++++++++ 2 files changed, 309 insertions(+), 309 deletions(-) delete mode 100644 app/assets/javascripts/dispatcher.js create mode 100644 app/assets/javascripts/dispatcher.js.es6 (limited to 'app') diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js deleted file mode 100644 index abc077a404e..00000000000 --- a/app/assets/javascripts/dispatcher.js +++ /dev/null @@ -1,309 +0,0 @@ -(function() { - var Dispatcher; - - $(function() { - return new Dispatcher(); - }); - - Dispatcher = (function() { - function Dispatcher() { - this.initSearch(); - this.initFieldErrors(); - this.initPageScripts(); - } - - Dispatcher.prototype.initPageScripts = function() { - var page, path, shortcut_handler; - page = $('body').attr('data-page'); - if (!page) { - return false; - } - path = page.split(':'); - shortcut_handler = null; - switch (page) { - case 'sessions:new': - new UsernameValidator(); - break; - case 'projects:boards:show': - case 'projects:boards:index': - shortcut_handler = new ShortcutsNavigation(); - break; - case 'projects:merge_requests:index': - case 'projects:issues:index': - Issuable.init(); - new gl.IssuableBulkActions(); - shortcut_handler = new ShortcutsNavigation(); - break; - case 'projects:issues:show': - new Issue(); - shortcut_handler = new ShortcutsIssuable(); - new ZenMode(); - break; - case 'projects:milestones:show': - case 'groups:milestones:show': - case 'dashboard:milestones:show': - new Milestone(); - break; - case 'dashboard:todos:index': - new gl.Todos(); - break; - case 'projects:milestones:new': - case 'projects:milestones:edit': - new ZenMode(); - new DueDateSelect(); - new GLForm($('.milestone-form')); - break; - case 'groups:milestones:new': - new ZenMode(); - break; - case 'projects:compare:show': - new Diff(); - break; - case 'projects:issues:new': - case 'projects:issues:edit': - shortcut_handler = new ShortcutsNavigation(); - new GLForm($('.issue-form')); - new IssuableForm($('.issue-form')); - new LabelsSelect(); - new MilestoneSelect(); - new gl.IssuableTemplateSelectors(); - break; - case 'projects:merge_requests:new': - case 'projects:merge_requests:edit': - new Diff(); - shortcut_handler = new ShortcutsNavigation(); - new GLForm($('.merge-request-form')); - new IssuableForm($('.merge-request-form')); - new LabelsSelect(); - new MilestoneSelect(); - new gl.IssuableTemplateSelectors(); - break; - case 'projects:tags:new': - new ZenMode(); - new GLForm($('.tag-form')); - break; - case 'projects:releases:edit': - new ZenMode(); - new GLForm($('.release-form')); - break; - case 'projects:merge_requests:show': - new Diff(); - shortcut_handler = new ShortcutsIssuable(true); - new ZenMode(); - new MergedButtons(); - break; - case 'projects:merge_requests:commits': - case 'projects:merge_requests:builds': - new MergedButtons(); - break; - case "projects:merge_requests:diffs": - new Diff(); - new ZenMode(); - new MergedButtons(); - break; - case "projects:merge_requests:conflicts": - window.mcui = new MergeConflictResolver() - break; - case 'projects:merge_requests:index': - shortcut_handler = new ShortcutsNavigation(); - Issuable.init(); - break; - case 'dashboard:activity': - new Activities(); - break; - case 'dashboard:projects:starred': - new Activities(); - break; - case 'projects:commit:show': - new Commit(); - new Diff(); - new ZenMode(); - shortcut_handler = new ShortcutsNavigation(); - break; - case 'projects:commits:show': - case 'projects:activity': - shortcut_handler = new ShortcutsNavigation(); - break; - case 'projects:show': - shortcut_handler = new ShortcutsNavigation(); - new NotificationsForm(); - if ($('#tree-slider').length) { - new TreeView(); - } - break; - case 'projects:pipelines:show': - new gl.Pipelines(); - break; - case 'groups:activity': - new Activities(); - break; - case 'groups:show': - shortcut_handler = new ShortcutsNavigation(); - new NotificationsForm(); - new NotificationsDropdown(); - break; - case 'groups:group_members:index': - new gl.MemberExpirationDate(); - new gl.Members(); - new UsersSelect(); - break; - case 'projects:project_members:index': - new gl.MemberExpirationDate(); - new gl.Members(); - new UsersSelect(); - break; - case 'groups:new': - case 'groups:edit': - case 'admin:groups:edit': - case 'admin:groups:new': - new GroupAvatar(); - break; - case 'projects:tree:show': - shortcut_handler = new ShortcutsNavigation(); - new TreeView(); - break; - case 'projects:find_file:show': - shortcut_handler = true; - break; - case 'projects:blob:show': - case 'projects:blame:show': - new LineHighlighter(); - shortcut_handler = new ShortcutsNavigation(); - new ShortcutsBlob(true); - break; - case 'projects:labels:new': - case 'projects:labels:edit': - new Labels(); - break; - case 'projects:labels:index': - if ($('.prioritized-labels').length) { - new gl.LabelManager(); - } - break; - case 'projects:network:show': - // Ensure we don't create a particular shortcut handler here. This is - // already created, where the network graph is created. - shortcut_handler = true; - break; - case 'projects:forks:new': - new ProjectFork(); - break; - case 'projects:artifacts:browse': - new BuildArtifacts(); - break; - case 'projects:group_links:index': - new gl.MemberExpirationDate(); - new GroupsSelect(); - break; - case 'search:show': - new Search(); - break; - case 'projects:protected_branches:index': - new gl.ProtectedBranchCreate(); - new gl.ProtectedBranchEditList(); - break; - case 'projects:cycle_analytics:show': - new gl.CycleAnalytics(); - break; - } - switch (path.first()) { - case 'admin': - new Admin(); - switch (path[1]) { - case 'groups': - new UsersSelect(); - break; - case 'projects': - new NamespaceSelects(); - break; - case 'labels': - switch (path[2]) { - case 'new': - case 'edit': - new Labels(); - } - case 'abuse_reports': - new gl.AbuseReports(); - break; - } - break; - case 'dashboard': - case 'root': - shortcut_handler = new ShortcutsDashboardNavigation(); - break; - case 'profiles': - new NotificationsForm(); - new NotificationsDropdown(); - break; - case 'projects': - new Project(); - new ProjectAvatar(); - switch (path[1]) { - case 'compare': - new CompareAutocomplete(); - break; - case 'edit': - shortcut_handler = new ShortcutsNavigation(); - new ProjectNew(); - break; - case 'new': - new ProjectNew(); - break; - case 'show': - new Star(); - new ProjectNew(); - new ProjectShow(); - new NotificationsDropdown(); - break; - case 'wikis': - new Wikis(); - shortcut_handler = new ShortcutsNavigation(); - new ZenMode(); - new GLForm($('.wiki-form')); - break; - case 'snippets': - shortcut_handler = new ShortcutsNavigation(); - if (path[2] === 'show') { - new ZenMode(); - } - break; - case 'labels': - case 'graphs': - case 'compare': - case 'pipelines': - case 'forks': - case 'milestones': - case 'project_members': - case 'deploy_keys': - case 'builds': - case 'hooks': - case 'services': - case 'protected_branches': - shortcut_handler = new ShortcutsNavigation(); - } - } - // If we haven't installed a custom shortcut handler, install the default one - if (!shortcut_handler) { - return new Shortcuts(); - } - }; - - Dispatcher.prototype.initSearch = function() { - // Only when search form is present - if ($('.search').length) { - return new gl.SearchAutocomplete(); - } - }; - - Dispatcher.prototype.initFieldErrors = function() { - var flaggedForms = document.querySelectorAll('.show-gl-field-errors'); - return [...flaggedForms].forEach(function(form) { - new gl.GlFieldErrors(form); - }); - }; - - return Dispatcher; - - })(); - -}).call(this); diff --git a/app/assets/javascripts/dispatcher.js.es6 b/app/assets/javascripts/dispatcher.js.es6 new file mode 100644 index 00000000000..abc077a404e --- /dev/null +++ b/app/assets/javascripts/dispatcher.js.es6 @@ -0,0 +1,309 @@ +(function() { + var Dispatcher; + + $(function() { + return new Dispatcher(); + }); + + Dispatcher = (function() { + function Dispatcher() { + this.initSearch(); + this.initFieldErrors(); + this.initPageScripts(); + } + + Dispatcher.prototype.initPageScripts = function() { + var page, path, shortcut_handler; + page = $('body').attr('data-page'); + if (!page) { + return false; + } + path = page.split(':'); + shortcut_handler = null; + switch (page) { + case 'sessions:new': + new UsernameValidator(); + break; + case 'projects:boards:show': + case 'projects:boards:index': + shortcut_handler = new ShortcutsNavigation(); + break; + case 'projects:merge_requests:index': + case 'projects:issues:index': + Issuable.init(); + new gl.IssuableBulkActions(); + shortcut_handler = new ShortcutsNavigation(); + break; + case 'projects:issues:show': + new Issue(); + shortcut_handler = new ShortcutsIssuable(); + new ZenMode(); + break; + case 'projects:milestones:show': + case 'groups:milestones:show': + case 'dashboard:milestones:show': + new Milestone(); + break; + case 'dashboard:todos:index': + new gl.Todos(); + break; + case 'projects:milestones:new': + case 'projects:milestones:edit': + new ZenMode(); + new DueDateSelect(); + new GLForm($('.milestone-form')); + break; + case 'groups:milestones:new': + new ZenMode(); + break; + case 'projects:compare:show': + new Diff(); + break; + case 'projects:issues:new': + case 'projects:issues:edit': + shortcut_handler = new ShortcutsNavigation(); + new GLForm($('.issue-form')); + new IssuableForm($('.issue-form')); + new LabelsSelect(); + new MilestoneSelect(); + new gl.IssuableTemplateSelectors(); + break; + case 'projects:merge_requests:new': + case 'projects:merge_requests:edit': + new Diff(); + shortcut_handler = new ShortcutsNavigation(); + new GLForm($('.merge-request-form')); + new IssuableForm($('.merge-request-form')); + new LabelsSelect(); + new MilestoneSelect(); + new gl.IssuableTemplateSelectors(); + break; + case 'projects:tags:new': + new ZenMode(); + new GLForm($('.tag-form')); + break; + case 'projects:releases:edit': + new ZenMode(); + new GLForm($('.release-form')); + break; + case 'projects:merge_requests:show': + new Diff(); + shortcut_handler = new ShortcutsIssuable(true); + new ZenMode(); + new MergedButtons(); + break; + case 'projects:merge_requests:commits': + case 'projects:merge_requests:builds': + new MergedButtons(); + break; + case "projects:merge_requests:diffs": + new Diff(); + new ZenMode(); + new MergedButtons(); + break; + case "projects:merge_requests:conflicts": + window.mcui = new MergeConflictResolver() + break; + case 'projects:merge_requests:index': + shortcut_handler = new ShortcutsNavigation(); + Issuable.init(); + break; + case 'dashboard:activity': + new Activities(); + break; + case 'dashboard:projects:starred': + new Activities(); + break; + case 'projects:commit:show': + new Commit(); + new Diff(); + new ZenMode(); + shortcut_handler = new ShortcutsNavigation(); + break; + case 'projects:commits:show': + case 'projects:activity': + shortcut_handler = new ShortcutsNavigation(); + break; + case 'projects:show': + shortcut_handler = new ShortcutsNavigation(); + new NotificationsForm(); + if ($('#tree-slider').length) { + new TreeView(); + } + break; + case 'projects:pipelines:show': + new gl.Pipelines(); + break; + case 'groups:activity': + new Activities(); + break; + case 'groups:show': + shortcut_handler = new ShortcutsNavigation(); + new NotificationsForm(); + new NotificationsDropdown(); + break; + case 'groups:group_members:index': + new gl.MemberExpirationDate(); + new gl.Members(); + new UsersSelect(); + break; + case 'projects:project_members:index': + new gl.MemberExpirationDate(); + new gl.Members(); + new UsersSelect(); + break; + case 'groups:new': + case 'groups:edit': + case 'admin:groups:edit': + case 'admin:groups:new': + new GroupAvatar(); + break; + case 'projects:tree:show': + shortcut_handler = new ShortcutsNavigation(); + new TreeView(); + break; + case 'projects:find_file:show': + shortcut_handler = true; + break; + case 'projects:blob:show': + case 'projects:blame:show': + new LineHighlighter(); + shortcut_handler = new ShortcutsNavigation(); + new ShortcutsBlob(true); + break; + case 'projects:labels:new': + case 'projects:labels:edit': + new Labels(); + break; + case 'projects:labels:index': + if ($('.prioritized-labels').length) { + new gl.LabelManager(); + } + break; + case 'projects:network:show': + // Ensure we don't create a particular shortcut handler here. This is + // already created, where the network graph is created. + shortcut_handler = true; + break; + case 'projects:forks:new': + new ProjectFork(); + break; + case 'projects:artifacts:browse': + new BuildArtifacts(); + break; + case 'projects:group_links:index': + new gl.MemberExpirationDate(); + new GroupsSelect(); + break; + case 'search:show': + new Search(); + break; + case 'projects:protected_branches:index': + new gl.ProtectedBranchCreate(); + new gl.ProtectedBranchEditList(); + break; + case 'projects:cycle_analytics:show': + new gl.CycleAnalytics(); + break; + } + switch (path.first()) { + case 'admin': + new Admin(); + switch (path[1]) { + case 'groups': + new UsersSelect(); + break; + case 'projects': + new NamespaceSelects(); + break; + case 'labels': + switch (path[2]) { + case 'new': + case 'edit': + new Labels(); + } + case 'abuse_reports': + new gl.AbuseReports(); + break; + } + break; + case 'dashboard': + case 'root': + shortcut_handler = new ShortcutsDashboardNavigation(); + break; + case 'profiles': + new NotificationsForm(); + new NotificationsDropdown(); + break; + case 'projects': + new Project(); + new ProjectAvatar(); + switch (path[1]) { + case 'compare': + new CompareAutocomplete(); + break; + case 'edit': + shortcut_handler = new ShortcutsNavigation(); + new ProjectNew(); + break; + case 'new': + new ProjectNew(); + break; + case 'show': + new Star(); + new ProjectNew(); + new ProjectShow(); + new NotificationsDropdown(); + break; + case 'wikis': + new Wikis(); + shortcut_handler = new ShortcutsNavigation(); + new ZenMode(); + new GLForm($('.wiki-form')); + break; + case 'snippets': + shortcut_handler = new ShortcutsNavigation(); + if (path[2] === 'show') { + new ZenMode(); + } + break; + case 'labels': + case 'graphs': + case 'compare': + case 'pipelines': + case 'forks': + case 'milestones': + case 'project_members': + case 'deploy_keys': + case 'builds': + case 'hooks': + case 'services': + case 'protected_branches': + shortcut_handler = new ShortcutsNavigation(); + } + } + // If we haven't installed a custom shortcut handler, install the default one + if (!shortcut_handler) { + return new Shortcuts(); + } + }; + + Dispatcher.prototype.initSearch = function() { + // Only when search form is present + if ($('.search').length) { + return new gl.SearchAutocomplete(); + } + }; + + Dispatcher.prototype.initFieldErrors = function() { + var flaggedForms = document.querySelectorAll('.show-gl-field-errors'); + return [...flaggedForms].forEach(function(form) { + new gl.GlFieldErrors(form); + }); + }; + + return Dispatcher; + + })(); + +}).call(this); -- cgit v1.2.1 From 716406bc71fc3866588ccca8c132060d4f33e53e Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Wed, 12 Oct 2016 18:47:31 +0200 Subject: Back off the array spreading, bc poltergeist freaks out. --- app/assets/javascripts/dispatcher.js.es6 | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/dispatcher.js.es6 b/app/assets/javascripts/dispatcher.js.es6 index abc077a404e..f3957ed374b 100644 --- a/app/assets/javascripts/dispatcher.js.es6 +++ b/app/assets/javascripts/dispatcher.js.es6 @@ -296,10 +296,9 @@ }; Dispatcher.prototype.initFieldErrors = function() { - var flaggedForms = document.querySelectorAll('.show-gl-field-errors'); - return [...flaggedForms].forEach(function(form) { - new gl.GlFieldErrors(form); - }); + $('.show-gl-field-errors').each((i, form) => { + new gl.GlFieldErrors(form); + }); }; return Dispatcher; -- cgit v1.2.1 From 75962a2ddd320fa03fc18d04861b42342c91a978 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Thu, 13 Oct 2016 17:50:35 +0200 Subject: Clean up stray Sign up ref. --- app/views/projects/notes/_notes_with_form.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app') diff --git a/app/views/projects/notes/_notes_with_form.html.haml b/app/views/projects/notes/_notes_with_form.html.haml index 8352eba7446..00b62a595ff 100644 --- a/app/views/projects/notes/_notes_with_form.html.haml +++ b/app/views/projects/notes/_notes_with_form.html.haml @@ -14,7 +14,7 @@ .disabled-comment.text-center .disabled-comment-text.inline Please - = link_to "sign up", new_session_path(:user, redirect_to_referer: 'yes') + = link_to "register", new_session_path(:user, redirect_to_referer: 'yes') or = link_to "sign in", new_session_path(:user, redirect_to_referer: 'yes') to post a comment -- cgit v1.2.1 From 391e109c813542d5f32953088e91a6ea82dfdcfc Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Sat, 15 Oct 2016 09:05:37 +0200 Subject: Revert "Improve tabbing usability for sign in page" This reverts commit 8751491b8db471dc661daa19bc82a9dbd58e4aae. --- app/assets/stylesheets/pages/login.scss | 10 ---------- 1 file changed, 10 deletions(-) (limited to 'app') diff --git a/app/assets/stylesheets/pages/login.scss b/app/assets/stylesheets/pages/login.scss index f1d15417705..e210dc4c0b8 100644 --- a/app/assets/stylesheets/pages/login.scss +++ b/app/assets/stylesheets/pages/login.scss @@ -52,16 +52,6 @@ margin: 0 0 10px; } - .new_user { - position: relative; - padding-bottom: 35px; - } - - .sign-in { - position: absolute; - bottom: 0; - } - .login-footer { margin-top: 10px; -- cgit v1.2.1 From fa25f61d474849a7a1ac893af9896b3227c33404 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Sat, 15 Oct 2016 20:31:40 -0700 Subject: Fix Hash syntax to work for both Ruby 2.1 and 2.3 --- app/views/devise/shared/_tabs_normal.html.haml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'app') diff --git a/app/views/devise/shared/_tabs_normal.html.haml b/app/views/devise/shared/_tabs_normal.html.haml index 48abd6519d6..79b1d447a92 100644 --- a/app/views/devise/shared/_tabs_normal.html.haml +++ b/app/views/devise/shared/_tabs_normal.html.haml @@ -1,5 +1,5 @@ %ul.nav-links.new-session-tabs.nav-tabs{ role: 'tablist'} %li.active{ role: 'presentation' } - %a{ href: '#login-pane', data: {'toggle':'tab'}, role: 'tab'} Sign in + %a{ href: '#login-pane', data: { toggle: 'tab' }, role: 'tab'} Sign in %li{ role: 'presentation'} - %a{ href: '#register-pane', data: {'toggle':'tab'}, role: 'tab'} Register + %a{ href: '#register-pane', data: { toggle: 'tab' }, role: 'tab'} Register -- cgit v1.2.1 From 4cea4d25270f604fb4b9335442657168b294e633 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Sat, 15 Oct 2016 20:40:25 -0700 Subject: Fix broken SCSS linter errors due to missing newlines --- app/assets/stylesheets/pages/login.scss | 3 +++ 1 file changed, 3 insertions(+) (limited to 'app') diff --git a/app/assets/stylesheets/pages/login.scss b/app/assets/stylesheets/pages/login.scss index e210dc4c0b8..4c0c0839fe2 100644 --- a/app/assets/stylesheets/pages/login.scss +++ b/app/assets/stylesheets/pages/login.scss @@ -40,6 +40,7 @@ p { font-size: 13px; } + .login-box, .omniauth-container { box-shadow: 0 0 0 1px $border-color; border-bottom-right-radius: 2px; @@ -118,6 +119,7 @@ margin: 0; } } + .new-session-tabs { display: -webkit-flex; display: flex; @@ -140,6 +142,7 @@ a { width: 100%; font-size: 18px; + &:hover { border: 1px solid transparent; } -- cgit v1.2.1 From 9449fe1744f4ce1265c42c73b977d4e138260d69 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Sun, 16 Oct 2016 15:03:44 +0300 Subject: Fix 500 error when creating mileston from group page Signed-off-by: Dmitriy Zaporozhets --- app/views/layouts/_init_auto_complete.html.haml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'app') diff --git a/app/views/layouts/_init_auto_complete.html.haml b/app/views/layouts/_init_auto_complete.html.haml index 67ff4b272b9..e138ebab018 100644 --- a/app/views/layouts/_init_auto_complete.html.haml +++ b/app/views/layouts/_init_auto_complete.html.haml @@ -1,7 +1,8 @@ - project = @target_project || @project - noteable_type = @noteable.class if @noteable.present? -:javascript - GitLab.GfmAutoComplete.dataSource = "#{autocomplete_sources_namespace_project_path(project.namespace, project, type: noteable_type, type_id: params[:id])}" - GitLab.GfmAutoComplete.cachedData = undefined; - GitLab.GfmAutoComplete.setup(); +- if project + :javascript + GitLab.GfmAutoComplete.dataSource = "#{autocomplete_sources_namespace_project_path(project.namespace, project, type: noteable_type, type_id: params[:id])}" + GitLab.GfmAutoComplete.cachedData = undefined; + GitLab.GfmAutoComplete.setup(); -- cgit v1.2.1 From 93e464f454ec997743048e34db5c848b4146d452 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Sun, 16 Oct 2016 03:30:31 +0100 Subject: Added logic to handle a revision input that does not exist in the menu --- app/assets/javascripts/compare_autocomplete.js | 52 ------------------ app/assets/javascripts/compare_autocomplete.js.es6 | 63 ++++++++++++++++++++++ app/views/projects/compare/_ref_dropdown.html.haml | 4 +- 3 files changed, 65 insertions(+), 54 deletions(-) delete mode 100644 app/assets/javascripts/compare_autocomplete.js create mode 100644 app/assets/javascripts/compare_autocomplete.js.es6 (limited to 'app') diff --git a/app/assets/javascripts/compare_autocomplete.js b/app/assets/javascripts/compare_autocomplete.js deleted file mode 100644 index 294d2c9052c..00000000000 --- a/app/assets/javascripts/compare_autocomplete.js +++ /dev/null @@ -1,52 +0,0 @@ -(function() { - this.CompareAutocomplete = (function() { - function CompareAutocomplete() { - this.initDropdown(); - } - - CompareAutocomplete.prototype.initDropdown = function() { - return $('.js-compare-dropdown').each(function() { - var $dropdown, selected; - $dropdown = $(this); - selected = $dropdown.data('selected'); - return $dropdown.glDropdown({ - data: function(term, callback) { - return $.ajax({ - url: $dropdown.data('refs-url'), - data: { - ref: $dropdown.data('ref') - } - }).done(function(refs) { - return callback(refs); - }); - }, - selectable: true, - filterable: true, - filterByText: true, - toggleLabel: true, - fieldName: $dropdown.data('field-name'), - filterInput: 'input[type="search"]', - renderRow: function(ref) { - var link; - if (ref.header != null) { - return $('
  • ').addClass('dropdown-header').text(ref.header); - } else { - link = $('').attr('href', '#').addClass(ref === selected ? 'is-active' : '').text(ref).attr('data-ref', escape(ref)); - return $('
  • ').append(link); - } - }, - id: function(obj, $el) { - return $el.attr('data-ref'); - }, - toggleLabel: function(obj, $el) { - return $el.text().trim(); - } - }); - }); - }; - - return CompareAutocomplete; - - })(); - -}).call(this); diff --git a/app/assets/javascripts/compare_autocomplete.js.es6 b/app/assets/javascripts/compare_autocomplete.js.es6 new file mode 100644 index 00000000000..9a2082d97e0 --- /dev/null +++ b/app/assets/javascripts/compare_autocomplete.js.es6 @@ -0,0 +1,63 @@ +(function() { + this.CompareAutocomplete = (function() { + function CompareAutocomplete() { + this.initDropdown(); + } + + CompareAutocomplete.prototype.initDropdown = function() { + return $('.js-compare-dropdown').each(function() { + var $dropdown, selected; + $dropdown = $(this); + selected = $dropdown.data('selected'); + const $dropdownContainer = $dropdown.closest('.dropdown'); + const $fieldInput = $(`input[name="${$dropdown.data('field-name')}"]`, $dropdownContainer); + const $filterInput = $('input[type="search"]', $dropdownContainer); + $dropdown.glDropdown({ + data: function(term, callback) { + return $.ajax({ + url: $dropdown.data('refs-url'), + data: { + ref: $dropdown.data('ref') + } + }).done(function(refs) { + return callback(refs); + }); + }, + selectable: true, + filterable: true, + filterByText: true, + toggleLabel: true, + fieldName: $dropdown.data('field-name'), + filterInput: 'input[type="search"]', + renderRow: function(ref) { + var link; + if (ref.header != null) { + return $('
  • ').addClass('dropdown-header').text(ref.header); + } else { + link = $('').attr('href', '#').addClass(ref === selected ? 'is-active' : '').text(ref).attr('data-ref', escape(ref)); + return $('
  • ').append(link); + } + }, + id: function(obj, $el) { + return $el.attr('data-ref'); + }, + toggleLabel: function(obj, $el) { + return $el.text().trim(); + } + }); + $filterInput.on('keyup', (e) => { + const keyCode = e.keyCode || e.which; + if (keyCode !== 13) return; + const text = $filterInput.val(); + $fieldInput.val(text); + $('.dropdown-toggle-text', $dropdown).text(text); + $dropdownContainer.removeClass('open'); + }); + }); + }; + + return CompareAutocomplete; + + })(); + +}).call(this); diff --git a/app/views/projects/compare/_ref_dropdown.html.haml b/app/views/projects/compare/_ref_dropdown.html.haml index 27d928c87a0..05fb37cdc0f 100644 --- a/app/views/projects/compare/_ref_dropdown.html.haml +++ b/app/views/projects/compare/_ref_dropdown.html.haml @@ -1,5 +1,5 @@ .dropdown-menu.dropdown-menu-selectable - = dropdown_title "Select branch/tag" - = dropdown_filter "Filter by branch/tag" + = dropdown_title "Select Git revision" + = dropdown_filter "Filter by Git revision" = dropdown_content = dropdown_loading -- cgit v1.2.1