Commit a9847ede authored by Annabel Dunstone Gray's avatar Annabel Dunstone Gray

Merge branch 'experimental-breadcrumbs' into 'master'

New navigation breadcrumbs

Closes #34029

See merge request !12346
parents a6c02ce6 ec396fd9
import Cookies from 'js-cookie';
import _ from 'underscore'; import _ from 'underscore';
export default class GroupName { export default class GroupName {
constructor() { constructor() {
this.titleContainer = document.querySelector('.title-container'); this.titleContainer = document.querySelector('.js-title-container');
this.title = document.querySelector('.title'); this.title = this.titleContainer.querySelector('.title');
this.titleWidth = this.title.offsetWidth; this.titleWidth = this.title.offsetWidth;
this.groupTitle = document.querySelector('.group-title'); this.groupTitle = this.titleContainer.querySelector('.group-title');
this.groups = document.querySelectorAll('.group-path'); this.groups = this.titleContainer.querySelectorAll('.group-path');
this.toggle = null; this.toggle = null;
this.isHidden = false; this.isHidden = false;
this.init(); this.init();
...@@ -33,11 +33,20 @@ export default class GroupName { ...@@ -33,11 +33,20 @@ export default class GroupName {
createToggle() { createToggle() {
this.toggle = document.createElement('button'); this.toggle = document.createElement('button');
this.toggle.setAttribute('type', 'button');
this.toggle.className = 'text-expander group-name-toggle'; this.toggle.className = 'text-expander group-name-toggle';
this.toggle.setAttribute('aria-label', 'Toggle full path'); this.toggle.setAttribute('aria-label', 'Toggle full path');
this.toggle.innerHTML = '...'; if (Cookies.get('new_nav') === 'true') {
this.toggle.innerHTML = '<i class="fa fa-ellipsis-h" aria-hidden="true"></i>';
} else {
this.toggle.innerHTML = '...';
}
this.toggle.addEventListener('click', this.toggleGroups.bind(this)); this.toggle.addEventListener('click', this.toggleGroups.bind(this));
this.titleContainer.insertBefore(this.toggle, this.title); if (Cookies.get('new_nav') === 'true') {
this.title.insertBefore(this.toggle, this.groupTitle);
} else {
this.titleContainer.insertBefore(this.toggle, this.title);
}
this.toggleGroups(); this.toggleGroups();
} }
......
...@@ -264,3 +264,127 @@ header.navbar-gitlab-new { ...@@ -264,3 +264,127 @@ header.navbar-gitlab-new {
} }
} }
} }
.breadcrumbs {
display: flex;
min-height: 60px;
padding-top: $gl-padding-top;
padding-bottom: $gl-padding-top;
color: $gl-text-color;
border-bottom: 1px solid $border-color;
.dropdown-toggle-caret {
position: relative;
top: -1px;
padding: 0 5px;
color: rgba($black, .65);
font-size: 10px;
line-height: 1;
background: none;
border: 0;
&:focus {
outline: 0;
}
}
}
.breadcrumbs-container {
display: flex;
width: 100%;
position: relative;
.dropdown-menu-projects {
margin-top: -$gl-padding;
margin-left: $gl-padding;
}
}
.breadcrumbs-links {
flex: 1;
align-self: center;
color: $black-transparent;
a {
color: rgba($black, .65);
&:not(:first-child),
&.group-path {
margin-left: 4px;
}
&:not(:last-of-type),
&.group-path {
margin-right: 3px;
}
}
.title {
white-space: nowrap;
> a {
&:last-of-type {
font-weight: 600;
}
}
}
.avatar-tile {
margin-right: 5px;
border: 1px solid $border-color;
border-radius: 50%;
vertical-align: sub;
&.identicon {
float: left;
width: 16px;
height: 16px;
margin-top: 2px;
font-size: 10px;
}
}
.text-expander {
margin-left: 4px;
margin-right: 4px;
> i {
position: relative;
top: 1px;
}
}
}
.breadcrumbs-extra {
flex: 0 0 auto;
margin-left: auto;
}
.breadcrumbs-sub-title {
margin: 2px 0 0;
font-size: 16px;
font-weight: normal;
ul {
margin: 0;
}
li {
display: inline-block;
&:not(:last-child) {
&::after {
content: "/";
margin: 0 2px 0 5px;
}
}
&:last-child a {
font-weight: 600;
}
}
a {
color: $gl-text-color;
}
}
...@@ -16,11 +16,12 @@ module GroupsHelper ...@@ -16,11 +16,12 @@ module GroupsHelper
full_title = '' full_title = ''
group.ancestors.reverse.each do |parent| group.ancestors.reverse.each do |parent|
full_title += link_to(simple_sanitize(parent.name), group_path(parent), class: 'group-path hidable') full_title += group_title_link(parent, hidable: true)
full_title += '<span class="hidable"> / </span>'.html_safe full_title += '<span class="hidable"> / </span>'.html_safe
end end
full_title += link_to(simple_sanitize(group.name), group_path(group), class: 'group-path') full_title += group_title_link(group)
full_title += ' &middot; '.html_safe + link_to(simple_sanitize(name), url, class: 'group-path') if name full_title += ' &middot; '.html_safe + link_to(simple_sanitize(name), url, class: 'group-path') if name
content_tag :span, class: 'group-title' do content_tag :span, class: 'group-title' do
...@@ -56,4 +57,20 @@ module GroupsHelper ...@@ -56,4 +57,20 @@ module GroupsHelper
def group_issues(group) def group_issues(group)
IssuesFinder.new(current_user, group_id: group.id).execute IssuesFinder.new(current_user, group_id: group.id).execute
end end
private
def group_title_link(group, hidable: false)
link_to(group_path(group), class: "group-path #{'hidable' if hidable}") do
output =
if show_new_nav?
image_tag(group_icon(group), class: "avatar-tile", width: 16, height: 16)
else
""
end
output << simple_sanitize(group.name)
output.html_safe
end
end
end end
...@@ -58,7 +58,17 @@ module ProjectsHelper ...@@ -58,7 +58,17 @@ module ProjectsHelper
link_to(simple_sanitize(owner.name), user_path(owner)) link_to(simple_sanitize(owner.name), user_path(owner))
end end
project_link = link_to simple_sanitize(project.name), project_path(project), { class: "project-item-select-holder" } project_link = link_to project_path(project), { class: "project-item-select-holder" } do
output =
if show_new_nav?
project_icon(project, alt: project.name, class: 'avatar-tile', width: 16, height: 16)
else
""
end
output << simple_sanitize(project.name)
output.html_safe
end
if current_user if current_user
project_link << button_tag(type: 'button', class: 'dropdown-toggle-caret js-projects-dropdown-toggle', aria: { label: 'Toggle switch project dropdown' }, data: { target: '.js-dropdown-menu-projects', toggle: 'dropdown', order_by: 'last_activity_at' }) do project_link << button_tag(type: 'button', class: 'dropdown-toggle-caret js-projects-dropdown-toggle', aria: { label: 'Toggle switch project dropdown' }, data: { target: '.js-dropdown-menu-projects', toggle: 'dropdown', order_by: 'last_activity_at' }) do
......
- @hide_top_links = true
- @no_container = true - @no_container = true
= content_for :meta_tags do = content_for :meta_tags do
......
- @hide_top_links = true
- page_title "Groups" - page_title "Groups"
- header_title "Groups", dashboard_groups_path - header_title "Groups", dashboard_groups_path
= render 'dashboard/groups_head' = render 'dashboard/groups_head'
......
- @hide_top_links = true
- page_title 'Milestones' - page_title 'Milestones'
- header_title 'Milestones', dashboard_milestones_path - header_title 'Milestones', dashboard_milestones_path
......
- @no_container = true - @no_container = true
- @hide_top_links = true
- @breadcrumb_title = "Projects"
= content_for :meta_tags do = content_for :meta_tags do
= auto_discovery_link_tag(:atom, dashboard_projects_url(rss_url_options), title: "All activity") = auto_discovery_link_tag(:atom, dashboard_projects_url(rss_url_options), title: "All activity")
......
- @hide_top_links = true
- page_title "Snippets" - page_title "Snippets"
- header_title "Snippets", dashboard_snippets_path - header_title "Snippets", dashboard_snippets_path
......
...@@ -14,6 +14,8 @@ ...@@ -14,6 +14,8 @@
= render "layouts/broadcast" = render "layouts/broadcast"
= render "layouts/flash" = render "layouts/flash"
= yield :flash_message = yield :flash_message
- if show_new_nav?
= render "layouts/nav/breadcrumbs"
%div{ class: "#{(container_class unless @no_container)} #{@content_class}" } %div{ class: "#{(container_class unless @no_container)} #{@content_class}" }
.content{ id: "content-body" } .content{ id: "content-body" }
= yield = yield
...@@ -17,7 +17,7 @@ ...@@ -17,7 +17,7 @@
= link_to root_path, class: 'home', title: 'Dashboard', id: 'logo' do = link_to root_path, class: 'home', title: 'Dashboard', id: 'logo' do
= brand_header_logo = brand_header_logo
.title-container .title-container.js-title-container
%h1.title{ class: ('initializing' if @has_group_title) }= title %h1.title{ class: ('initializing' if @has_group_title) }= title
.navbar-collapse.collapse .navbar-collapse.collapse
......
...@@ -83,8 +83,6 @@ ...@@ -83,8 +83,6 @@
= icon('ellipsis-v', class: 'js-navbar-toggle-right') = icon('ellipsis-v', class: 'js-navbar-toggle-right')
= icon('times', class: 'js-navbar-toggle-left', style: 'display: none;') = icon('times', class: 'js-navbar-toggle-left', style: 'display: none;')
= yield :header_content
= render 'shared/outdated_browser' = render 'shared/outdated_browser'
- if @project && !@project.empty_repo? - if @project && !@project.empty_repo?
......
- breadcrumb_title = @breadcrumb_title || controller.controller_name.humanize
- hide_top_links = @hide_top_links || false
%nav.breadcrumbs{ role: "navigation" }
.breadcrumbs-container{ class: container_class }
.breadcrumbs-links.js-title-container
- unless hide_top_links
.title
= link_to "GitLab", root_path
\/
= header_title
%h2.breadcrumbs-sub-title
%ul.list-unstyled
- if content_for?(:sub_title_before)
= yield :sub_title_before
%li= link_to breadcrumb_title, request.path
- if content_for?(:breadcrumbs_extra)
.breadcrumbs-extra.hidden-xs= yield :breadcrumbs_extra
= yield :header_content
...@@ -2,6 +2,10 @@ ...@@ -2,6 +2,10 @@
- @content_class = "issue-boards-content" - @content_class = "issue-boards-content"
- page_title "Boards" - page_title "Boards"
- if show_new_nav?
- content_for :sub_title_before do
%li= link_to "Issues", namespace_project_issues_path(@project.namespace, @project)
- content_for :page_specific_javascripts do - content_for :page_specific_javascripts do
= webpack_bundle_tag 'common_vue' = webpack_bundle_tag 'common_vue'
= webpack_bundle_tag 'filtered_search' = webpack_bundle_tag 'filtered_search'
......
= link_to params.merge(rss_url_options), class: 'btn btn-default append-right-10 has-tooltip', title: 'Subscribe' do
= icon('rss')
- if @can_bulk_update
= button_tag "Edit Issues", class: "btn btn-default append-right-10 js-bulk-update-toggle"
= link_to "New issue", new_namespace_project_issue_path(@project.namespace,
@project,
issue: { assignee_id: issues_finder.assignee.try(:id),
milestone_id: issues_finder.milestones.first.try(:id) }),
class: "btn btn-new",
title: "New issue",
id: "new_issue_link"
...@@ -13,23 +13,16 @@ ...@@ -13,23 +13,16 @@
= content_for :meta_tags do = content_for :meta_tags do
= auto_discovery_link_tag(:atom, params.merge(rss_url_options), title: "#{@project.name} issues") = auto_discovery_link_tag(:atom, params.merge(rss_url_options), title: "#{@project.name} issues")
- if show_new_nav?
- content_for :breadcrumbs_extra do
= render "projects/issues/nav_btns"
- if project_issues(@project).exists? - if project_issues(@project).exists?
%div{ class: (container_class) } %div{ class: (container_class) }
.top-area .top-area
= render 'shared/issuable/nav', type: :issues = render 'shared/issuable/nav', type: :issues
.nav-controls .nav-controls{ class: ("visible-xs" if show_new_nav?) }
= link_to params.merge(rss_url_options), class: 'btn append-right-10 has-tooltip', title: 'Subscribe' do = render "projects/issues/nav_btns"
= icon('rss')
- if @can_bulk_update
= button_tag "Edit Issues", class: "btn btn-default js-bulk-update-toggle"
= link_to new_namespace_project_issue_path(@project.namespace,
@project,
issue: { assignee_id: issues_finder.assignee.try(:id),
milestone_id: issues_finder.milestones.first.try(:id) }),
class: "btn btn-new",
title: "New issue",
id: "new_issue_link" do
New issue
= render 'shared/issuable/search_bar', type: :issues = render 'shared/issuable/search_bar', type: :issues
- if @can_bulk_update - if @can_bulk_update
......
- if @can_bulk_update
= button_tag "Edit Merge Requests", class: "btn js-bulk-update-toggle"
- if merge_project
= link_to new_merge_request_path, class: "btn btn-new", title: "New merge request" do
New merge request
- @no_container = true - @no_container = true
- @can_bulk_update = can?(current_user, :admin_merge_request, @project) - @can_bulk_update = can?(current_user, :admin_merge_request, @project)
- merge_project = can?(current_user, :create_merge_request, @project) ? @project : (current_user && current_user.fork_of(@project))
- new_merge_request_path = namespace_project_new_merge_request_path(merge_project.namespace, merge_project) if merge_project
- page_title "Merge Requests" - page_title "Merge Requests"
- unless @project.default_issues_tracker? - unless @project.default_issues_tracker?
...@@ -10,22 +12,18 @@ ...@@ -10,22 +12,18 @@
= webpack_bundle_tag 'common_vue' = webpack_bundle_tag 'common_vue'
= webpack_bundle_tag 'filtered_search' = webpack_bundle_tag 'filtered_search'
- if show_new_nav?
- content_for :breadcrumbs_extra do
= render "projects/merge_requests/nav_btns", merge_project: merge_project, new_merge_request_path: new_merge_request_path
= render 'projects/last_push' = render 'projects/last_push'
- merge_project = can?(current_user, :create_merge_request, @project) ? @project : (current_user && current_user.fork_of(@project))
- new_merge_request_path = namespace_project_new_merge_request_path(merge_project.namespace, merge_project) if merge_project
- if @project.merge_requests.exists? - if @project.merge_requests.exists?
%div{ class: container_class } %div{ class: container_class }
.top-area .top-area
= render 'shared/issuable/nav', type: :merge_requests = render 'shared/issuable/nav', type: :merge_requests
.nav-controls .nav-controls{ class: ("visible-xs" if show_new_nav?) }
- if @can_bulk_update = render "projects/merge_requests/nav_btns", merge_project: merge_project, new_merge_request_path: new_merge_request_path
= button_tag "Edit Merge Requests", class: "btn js-bulk-update-toggle"
- if merge_project
= link_to new_merge_request_path, class: "btn btn-new", title: "New merge request" do
New merge request
= render 'shared/issuable/search_bar', type: :merge_requests = render 'shared/issuable/search_bar', type: :merge_requests
......
...@@ -91,7 +91,7 @@ describe GroupsHelper do ...@@ -91,7 +91,7 @@ describe GroupsHelper do
let!(:very_deep_nested_group) { create(:group, parent: deep_nested_group) } let!(:very_deep_nested_group) { create(:group, parent: deep_nested_group) }
it 'outputs the groups in the correct order' do it 'outputs the groups in the correct order' do
expect(group_title(very_deep_nested_group)).to match(/>#{group.name}<\/a>.*>#{nested_group.name}<\/a>.*>#{deep_nested_group.name}<\/a>/) expect(helper.group_title(very_deep_nested_group)).to match(/>#{group.name}<\/a>.*>#{nested_group.name}<\/a>.*>#{deep_nested_group.name}<\/a>/)
end end
end end
end end
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