Commit 8cc47bbf authored by Dmitriy Zaporozhets's avatar Dmitriy Zaporozhets

Merge branch 'ui-improvements' into 'master'

UI improvements

* use shared partials for repeating rendering of projects, groups, snippets etc
* more consistency in way how projects, snippets and groups lists are rendered
* fix 500 error when submit project snippet without body
* remove some old and unused css
* more compact search page

See merge request !1214
parents de3b7d9c 6a889572
...@@ -19,6 +19,9 @@ v 8.0.0 (unreleased) ...@@ -19,6 +19,9 @@ v 8.0.0 (unreleased)
- Move dashboard activity to separate page - Move dashboard activity to separate page
- Improve performance of git blame - Improve performance of git blame
- Limit content width to 1200px for most of pages to improve readability on big screens - Limit content width to 1200px for most of pages to improve readability on big screens
- Fix 500 error when submit project snippet without body
- Improve search page usability
- Bring more UI consistency in way how projects, snippets and groups lists are rendered
v 7.14.1 v 7.14.1
- Improve abuse reports management from admin area - Improve abuse reports management from admin area
......
...@@ -55,7 +55,6 @@ class Dispatcher ...@@ -55,7 +55,6 @@ class Dispatcher
new Activities() new Activities()
when 'dashboard:projects:starred' when 'dashboard:projects:starred'
new Activities() new Activities()
new ProjectsList()
when 'projects:commit:show' when 'projects:commit:show'
new Commit() new Commit()
new Diff() new Diff()
...@@ -70,7 +69,6 @@ class Dispatcher ...@@ -70,7 +69,6 @@ class Dispatcher
when 'groups:show' when 'groups:show'
new Activities() new Activities()
shortcut_handler = new ShortcutsNavigation() shortcut_handler = new ShortcutsNavigation()
new ProjectsList()
when 'groups:group_members:index' when 'groups:group_members:index'
new GroupMembers() new GroupMembers()
new UsersSelect() new UsersSelect()
...@@ -96,8 +94,6 @@ class Dispatcher ...@@ -96,8 +94,6 @@ class Dispatcher
when 'users:show' when 'users:show'
new User() new User()
new Activities() new Activities()
when 'admin:users:show'
new ProjectsList()
switch path.first() switch path.first()
when 'admin' when 'admin'
......
...@@ -157,3 +157,41 @@ ...@@ -157,3 +157,41 @@
white-space: nowrap; white-space: nowrap;
max-width: $max_width; max-width: $max_width;
} }
/*
* Base mixin for lists in GitLab
*/
@mixin basic-list {
margin: 5px 0px;
padding: 0px;
list-style: none;
li {
padding: 10px 0;
border-bottom: 1px solid #EEE;
overflow: hidden;
display: block;
margin: 0px;
&:last-child {
border:none
}
&.active {
background: #f9f9f9;
a {
font-weight: bold;
}
}
&.hide {
display: none;
}
&.light {
a {
color: #777;
}
}
}
}
...@@ -375,9 +375,9 @@ table { ...@@ -375,9 +375,9 @@ table {
} }
.center-top-menu { .center-top-menu {
border-bottom: 1px solid #EEE;
list-style: none; list-style: none;
text-align: center; text-align: center;
margin-top: 5px;
padding-bottom: 15px; padding-bottom: 15px;
margin-bottom: 15px; margin-bottom: 15px;
...@@ -385,7 +385,7 @@ table { ...@@ -385,7 +385,7 @@ table {
display: inline-block; display: inline-block;
a { a {
padding: 10px; padding: 15px;
} }
&.active a { &.active a {
......
...@@ -93,28 +93,12 @@ ol, ul { ...@@ -93,28 +93,12 @@ ol, ul {
/** light list with border-bottom between li **/ /** light list with border-bottom between li **/
ul.bordered-list { ul.bordered-list {
margin: 5px 0px; @include basic-list;
padding: 0px;
li {
padding: 5px 0;
border-bottom: 1px solid #EEE;
overflow: hidden;
display: block;
margin: 0px;
&:last-child { border:none }
&.active {
background: #f9f9f9;
a { font-weight: bold; }
}
&.light {
a { color: #777; }
}
}
&.top-list { &.top-list {
li:first-child { li:first-child {
padding-top: 0; padding-top: 0;
h4, h5 { h4, h5 {
margin-top: 0; margin-top: 0;
} }
......
...@@ -162,78 +162,6 @@ ul.nav.nav-projects-tabs { ...@@ -162,78 +162,6 @@ ul.nav.nav-projects-tabs {
margin: 0px; margin: 0px;
} }
.my-projects,
.public-projects {
li {
.project-info {
margin-bottom: 10px;
overflow: hidden;
}
.access-icon {
color: #AAA;
margin-left: 10px;
i {
color: #AAA;
}
}
}
}
.public-clone {
background: #EEE;
color: #777;
padding: 6px 10px;
margin: 1px;
font-weight: normal;
}
.public-projects .repo-info {
color: #777;
a {
color: #777;
}
}
.project-side {
.project-fork-icon {
float: left;
font-size: 26px;
margin-right: 10px;
line-height: 1.5;
}
.panel {
@include border-radius(3px);
.panel-heading, .panel-footer {
font-weight: normal;
background-color: transparent;
color: #666;
border-color: #EEE;
}
.actions {
margin-top: 10px;
}
.nav-pills a {
padding: 10px;
font-weight: bold;
color: $gl-link-color;
}
.nav {
margin-bottom: 15px;
}
}
.ci-status-image {
max-height: 22px;
}
}
.transfer-project .select2-container { .transfer-project .select2-container {
min-width: 200px; min-width: 200px;
} }
...@@ -334,8 +262,15 @@ pre.light-well { ...@@ -334,8 +262,15 @@ pre.light-well {
} }
} }
.project-row { /*
* Projects list rendered on dashboard and user page
*/
.projects-list {
@include basic-list;
.project-row {
.project-full-name { .project-full-name {
@include str-truncated;
font-weight: bold; font-weight: bold;
font-size: 15px; font-size: 15px;
} }
...@@ -345,12 +280,14 @@ pre.light-well { ...@@ -345,12 +280,14 @@ pre.light-well {
font-size: 13px; font-size: 13px;
p { p {
@include str-truncated;
margin-bottom: 0; margin-bottom: 0;
color: #888; color: #888;
} }
} }
}
} }
.my-projects .project-row { .panel .projects-list li {
padding: 10px 0; padding: 10px 15px;
} }
.search-results { .search-results {
.search-result-row { .search-result-row {
border-bottom: 1px solid #EEE; border-bottom: 1px solid #DDD;
padding-bottom: 10px; padding-bottom: 15px;
margin-bottom: 10px; margin-bottom: 15px;
} }
} }
.search-holder {
max-width: 600px;
margin: 0 auto;
margin-bottom: 20px;
input {
border-color: #BBB;
font-weight: bold;
}
}
...@@ -6,3 +6,27 @@ ...@@ -6,3 +6,27 @@
.snippet-form-holder .file-holder .file-title { .snippet-form-holder .file-holder .file-title {
padding: 2px; padding: 2px;
} }
.snippet-row {
.snippet-title {
font-size: 15px;
font-weight: bold;
line-height: 20px;
margin-bottom: 2px;
.monospace {
font-weight: normal;
}
}
.snippet-info {
color: #888;
font-size: 13px;
line-height: 24px;
a {
color: #888;
}
}
}
...@@ -30,9 +30,14 @@ class Projects::SnippetsController < Projects::ApplicationController ...@@ -30,9 +30,14 @@ class Projects::SnippetsController < Projects::ApplicationController
def create def create
@snippet = CreateSnippetService.new(@project, current_user, @snippet = CreateSnippetService.new(@project, current_user,
snippet_params).execute snippet_params).execute
if @snippet.valid?
respond_with(@snippet, respond_with(@snippet,
location: namespace_project_snippet_path(@project.namespace, location: namespace_project_snippet_path(@project.namespace,
@project, @snippet)) @project, @snippet))
else
render :new
end
end end
def edit def edit
......
...@@ -2,13 +2,21 @@ class TrendingProjectsFinder ...@@ -2,13 +2,21 @@ class TrendingProjectsFinder
def execute(current_user, start_date = nil) def execute(current_user, start_date = nil)
start_date ||= Date.today - 1.month start_date ||= Date.today - 1.month
projects = projects_for(current_user)
# Determine trending projects based on comments count # Determine trending projects based on comments count
# for period of time - ex. month # for period of time - ex. month
projects.joins(:notes).where('notes.created_at > ?', start_date). trending_project_ids = Note.
select("projects.*, count(notes.id) as ncount"). select("notes.project_id, count(notes.project_id) as pcount").
group("projects.id").reorder("ncount DESC") where('notes.created_at > ?', start_date).
group("project_id").
reorder("pcount DESC").
map(&:project_id)
sql_order_ids = trending_project_ids.reverse.
map { |project_id| "id = #{project_id}" }.join(", ")
# Get list of projects that user allowed to see
projects = projects_for(current_user)
projects.where(id: trending_project_ids).reorder(sql_order_ids)
end end
private private
......
...@@ -7,7 +7,4 @@ ...@@ -7,7 +7,4 @@
= link_to new_project_path, class: 'btn btn-success' do = link_to new_project_path, class: 'btn btn-success' do
New project New project
%ul.projects-list.bordered-list.my-projects = render 'shared/projects/list', projects: @projects
- @projects.each do |project|
%li.project-row
= render partial: 'shared/project', locals: { project: project, avatar: true, stars: true }
...@@ -8,32 +8,9 @@ ...@@ -8,32 +8,9 @@
= link_to new_group_path, class: "btn btn-new btn-sm" do = link_to new_group_path, class: "btn btn-new btn-sm" do
%i.fa.fa-plus %i.fa.fa-plus
New Group New Group
.panel.panel-default %ul.bordered-list
.panel-heading
%strong Groups
(#{@group_members.count})
%ul.well-list
- @group_members.each do |group_member| - @group_members.each do |group_member|
- group = group_member.group - group = group_member.group
%li = render 'shared/groups/group', group: group, group_member: group_member
.pull-right.hidden-xs
- if can?(current_user, :admin_group, group)
= link_to edit_group_path(group), class: "btn-sm btn btn-grouped" do
%i.fa.fa-cogs
Settings
= link_to leave_group_group_members_path(group), data: { confirm: leave_group_message(group.name) }, method: :delete, class: "btn-sm btn btn-grouped", title: 'Leave this group' do
%i.fa.fa-sign-out
Leave
= image_tag group_icon(group), class: "avatar s40 avatar-tile hidden-xs"
= link_to group, class: 'group-name' do
%strong= group.name
as
%strong #{group_member.human_access}
%div.light
#{pluralize(group.projects.count, "project")}, #{pluralize(group.users.count, "user")}
= paginate @group_members = paginate @group_members
...@@ -17,8 +17,7 @@ ...@@ -17,8 +17,7 @@
= link_to new_project_path, class: 'btn btn-success' do = link_to new_project_path, class: 'btn btn-success' do
New project New project
= render 'shared/projects_list', projects: @projects, = render 'shared/projects/list', projects: @projects, projects_limit: 20
projects_limit: 20, stars: true, avatar: false
- else - else
%h3 You don't have starred projects yet %h3 You don't have starred projects yet
......
...@@ -32,17 +32,7 @@ ...@@ -32,17 +32,7 @@
%ul.bordered-list %ul.bordered-list
- @groups.each do |group| - @groups.each do |group|
%li = render 'shared/groups/group', group: group
.clearfix
%h4
= link_to group_path(id: group.path) do
= group.name
.clearfix
%p
= truncate group.description, length: 150
.clearfix
%p.light
#{pluralize(group.members.size, 'member')}, #{pluralize(group.projects.count, 'project')}
- unless @groups.present? - unless @groups.present?
.nothing-here-block No public groups .nothing-here-block No public groups
......
%ul.projects-list.bordered-list.my-projects.public-projects - if projects.any?
- projects.each do |project| .public-projects
%li.project-row = render 'shared/projects/list', projects: projects
= render partial: 'shared/project', locals: { project: project, avatar: true, stars: true } - else
- unless projects.present? .nothing-here-block
.nothing-here-block No such projects No such projects
...@@ -7,4 +7,4 @@ ...@@ -7,4 +7,4 @@
= link_to new_project_path(namespace_id: @group.id), class: 'btn btn-success' do = link_to new_project_path(namespace_id: @group.id), class: 'btn btn-success' do
New project New project
= render 'shared/projects_list', projects: @projects, projects_limit: 20 = render 'shared/projects/list', projects: @projects, projects_limit: 20
...@@ -100,7 +100,7 @@ ...@@ -100,7 +100,7 @@
- if project_nav_tab? :snippets - if project_nav_tab? :snippets
= nav_link(controller: :snippets) do = nav_link(controller: :snippets) do
= link_to namespace_project_snippets_path(@project.namespace, @project), title: 'Snippets', class: 'shortcuts-snippets', data: {placement: 'right'} do = link_to namespace_project_snippets_path(@project.namespace, @project), title: 'Snippets', class: 'shortcuts-snippets', data: {placement: 'right'} do
= icon('file-text-o fw') = icon('clipboard fw')
%span %span
Snippets Snippets
......
%li
%h4.snippet-title
= link_to reliable_snippet_path(snippet) do
= truncate(snippet.title, length: 60)
%span.cgray.monospace.tiny.pull-right
= snippet.file_name
.snippet-info
= "##{snippet.id}"
%span
by
= image_tag avatar_icon(snippet.author_email), class: "avatar avatar-inline s16"
= snippet.author_name
%span.light
#{time_ago_with_tooltip(snippet.created_at)}
...@@ -8,9 +8,8 @@ ...@@ -8,9 +8,8 @@
%p.light %p.light
Share code pastes with others out of git repository Share code pastes with others out of git repository
%hr
%ul.bordered-list %ul.bordered-list
= render partial: "projects/snippets/snippet", collection: @snippets = render partial: "shared/snippets/snippet", collection: @snippets
- if @snippets.empty? - if @snippets.empty?
%li %li
.nothing-here-block Nothing here. .nothing-here-block Nothing here.
%ul.nav.nav-pills.search-filter %ul.nav.nav-tabs.search-filter
- if @project - if @project
%li{class: ("active" if @scope == 'blobs')} %li{class: ("active" if @scope == 'blobs')}
= link_to search_filter_path(scope: 'blobs') do = link_to search_filter_path(scope: 'blobs') do
......
.dropdown.inline .dropdown.inline
%button.dropdown-toggle.btn.btn{type: 'button', 'data-toggle' => 'dropdown'} %button.dropdown-toggle.btn.btn-sm{type: 'button', 'data-toggle' => 'dropdown'}
%i.fa.fa-tags
%span.light Group: %span.light Group:
- if @group.present? - if @group.present?
%strong= @group.name %strong= @group.name
...@@ -17,8 +16,7 @@ ...@@ -17,8 +16,7 @@
= group.name = group.name
.dropdown.inline.prepend-left-10.project-filter .dropdown.inline.prepend-left-10.project-filter
%button.dropdown-toggle.btn.btn{type: 'button', 'data-toggle' => 'dropdown'} %button.dropdown-toggle.btn.btn-sm{type: 'button', 'data-toggle' => 'dropdown'}
%i.fa.fa-tags
%span.light Project: %span.light Project:
- if @project.present? - if @project.present?
%strong= @project.name_with_namespace %strong= @project.name_with_namespace
......
= form_tag search_path, method: :get, class: 'form-inline' do |f| = form_tag search_path, method: :get do |f|
= hidden_field_tag :project_id, params[:project_id] = hidden_field_tag :project_id, params[:project_id]
= hidden_field_tag :group_id, params[:group_id] = hidden_field_tag :group_id, params[:group_id]
= hidden_field_tag :snippets, params[:snippets] = hidden_field_tag :snippets, params[:snippets]
= hidden_field_tag :scope, params[:scope] = hidden_field_tag :scope, params[:scope]
.search-holder.clearfix .search-holder.clearfix
.form-group .input-group
= search_field_tag :search, params[:search], placeholder: "Search for projects, issues etc", class: "form-control search-text-input", id: "dashboard_search", autofocus: true = search_field_tag :search, params[:search], placeholder: "Search for projects, issues etc", class: "form-control search-text-input", id: "dashboard_search", autofocus: true
%span.input-group-btn
= button_tag 'Search', class: "btn btn-primary" = button_tag 'Search', class: "btn btn-primary"
- unless params[:snippets].eql? 'true' - unless params[:snippets].eql? 'true'
.pull-right %br
= render 'filter' = render 'filter'
- if @search_results.empty? - if @search_results.empty?
= render partial: "search/results/empty" = render partial: "search/results/empty"
- else - else
.light %p.light
Search results for Search results for
%code %code
= @search_term = @search_term
...@@ -11,9 +11,12 @@ ...@@ -11,9 +11,12 @@
- elsif @group - elsif @group
in group #{link_to @group.name, @group} in group #{link_to @group.name, @group}
%br
.results.prepend-top-10 .results.prepend-top-10
.search-results .search-results
- if @scope == 'projects'
.term
= render 'shared/projects/list', projects: @objects
- else
= render partial: "search/results/#{@scope.singularize}", collection: @objects = render partial: "search/results/#{@scope.singularize}", collection: @objects
= paginate @objects, theme: 'gitlab' = paginate @objects, theme: 'gitlab'
......
.search-result-row
%h4
= link_to [project.namespace.becomes(Namespace), project] do
%span.term= project.name_with_namespace
- if project.description.present?
%span.light.term= project.description
- page_title @search_term - page_title @search_term
= render 'search/form' = render 'search/form'
%hr
- if @search_term - if @search_term
= render 'search/category' = render 'search/category'
%hr
= render 'search/results' = render 'search/results'
= cache [project.namespace, project, controller.controller_name, controller.action_name] do
= link_to project_path(project), class: dom_class(project) do
- if avatar
.dash-project-avatar
= project_icon(project, alt: '', class: 'avatar project-avatar s40')
%span.str-truncated.project-full-name
%span.namespace-name
- if project.namespace
= project.namespace.human_name
\/
%span.project-name.filter-title
= project.name
- if stars
%span.pull-right.light
%i.fa.fa-star
= project.star_count
- if project.description.present?
.project-description
.str-truncated
= markdown(project.description, pipeline: :description)
- group_member = local_assigns[:group_member]
%li
- if group_member
.pull-right.hidden-xs
- if can?(current_user, :admin_group, group)
= link_to edit_group_path(group), class: "btn-sm btn btn-grouped" do
%i.fa.fa-cogs
Settings
= link_to leave_group_group_members_path(group), data: { confirm: leave_group_message(group.name) }, method: :delete, class: "btn-sm btn btn-grouped", title: 'Leave this group' do
%i.fa.fa-sign-out
Leave
= image_tag group_icon(group), class: "avatar s40 avatar-tile hidden-xs"
= link_to group, class: 'group-name' do
%strong= group.name
- if group_member
as
%strong #{group_member.human_access}
%div.light
#{pluralize(group.projects.count, "project")}, #{pluralize(group.users.count, "user")}
- projects_limit = 20 unless local_assigns[:projects_limit] - projects_limit = 20 unless local_assigns[:projects_limit]
- avatar = true unless local_assigns[:avatar] == false - avatar = true unless local_assigns[:avatar] == false
- stars = false unless local_assigns[:stars] == true - stars = true unless local_assigns[:stars] == false
%ul.well-list.projects-list
%ul.projects-list
- projects.each_with_index do |project, i| - projects.each_with_index do |project, i|
%li{class: (i >= projects_limit) ? 'project-row hide' : 'project-row'} - css_class = (i >= projects_limit) ? 'hide' : nil
= render "shared/project", project: project, avatar: avatar, stars: stars = render "shared/projects/project", project: project,
- if projects.blank? avatar: avatar, stars: stars, css_class: css_class
%li
.nothing-here-block There are no projects here.
- if projects.count > projects_limit - if projects.count > projects_limit
%li.bottom %li.bottom.center
%span.light .light
#{projects_limit} of #{pluralize(projects.count, 'project')} displayed. #{projects_limit} of #{pluralize(projects.count, 'project')} displayed.
%span
= link_to '#', class: 'js-expand' do = link_to '#', class: 'js-expand' do
Show all Show all
:coffeescript
new ProjectsList()
- avatar = true unless local_assigns[:avatar] == false
- stars = true unless local_assigns[:stars] == false
- css_class = nil unless local_assigns[:css_class]
%li.project-row{ class: css_class }
= cache [project.namespace, project, controller.controller_name, controller.action_name, 'v2'] do
= link_to project_path(project), class: dom_class(project) do
- if avatar
.dash-project-avatar
= project_icon(project, alt: '', class: 'avatar project-avatar s40')
%span.project-full-name
%span.namespace-name
- if project.namespace
= project.namespace.human_name
\/
%span.project-name.filter-title
= project.name
- if stars
%span.pull-right.light
%i.fa.fa-star
= project.star_count
- if project.description.present?
.project-description
= markdown(project.description, pipeline: :description)
%li %li.snippet-row
%h4.snippet-title .snippet-title
= link_to reliable_snippet_path(snippet) do = link_to reliable_snippet_path(snippet) do
= truncate(snippet.title, length: 60) = truncate(snippet.title, length: 60)
- if snippet.private? - if snippet.private?
%span.label.label-gray %span.label.label-gray
%i.fa.fa-lock %i.fa.fa-lock
private private
%span.cgray.monospace.tiny.pull-right %span.monospace.pull-right
= snippet.file_name = snippet.file_name
%small.pull-right.cgray %small.pull-right.cgray
...@@ -14,10 +14,8 @@ ...@@ -14,10 +14,8 @@
= link_to snippet.project.name_with_namespace, namespace_project_path(snippet.project.namespace, snippet.project) = link_to snippet.project.name_with_namespace, namespace_project_path(snippet.project.namespace, snippet.project)
.snippet-info .snippet-info
= "##{snippet.id}"
%span
by
= link_to user_snippets_path(snippet.author) do = link_to user_snippets_path(snippet.author) do
= image_tag avatar_icon(snippet.author_email), class: "avatar avatar-inline s16", alt: '' = image_tag avatar_icon(snippet.author_email), class: "avatar s24", alt: ''
= snippet.author_name = snippet.author_name
%span.light #{time_ago_with_tooltip(snippet.created_at)} authored #{time_ago_with_tooltip(snippet.created_at)}
%ul.bordered-list %ul.bordered-list
= render partial: 'snippet', collection: @snippets = render partial: 'shared/snippets/snippet', collection: @snippets
- if @snippets.empty? - if @snippets.empty?
%li %li
.nothing-here-block Nothing here. .nothing-here-block Nothing here.
......
- if local_assigns.has_key?(:contributed_projects) && contributed_projects.present? - if local_assigns.has_key?(:contributed_projects) && contributed_projects.present?
.panel.panel-default.contributed-projects .panel.panel-default.contributed-projects
.panel-heading Projects contributed to .panel-heading Projects contributed to
= render 'shared/projects_list', = render 'shared/projects/list',
projects: contributed_projects.sort_by(&:star_count).reverse, projects: contributed_projects.sort_by(&:star_count).reverse,
projects_limit: 5, stars: true, avatar: false projects_limit: 5, stars: true, avatar: false
- if local_assigns.has_key?(:projects) && projects.present? - if local_assigns.has_key?(:projects) && projects.present?
.panel.panel-default .panel.panel-default
.panel-heading Personal projects .panel-heading Personal projects
= render 'shared/projects_list', = render 'shared/projects/list',
projects: projects.sort_by(&:star_count).reverse, projects: projects.sort_by(&:star_count).reverse,
projects_limit: 10, stars: true, avatar: false projects_limit: 10, stars: true, avatar: false
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment