Merge pull request #19421 from opf/bug/63777-breadcrumb-index-creates-unnecessary-navigation-buttons-on-mobile-web-2

[63777] Breadcrumb index creates unnecessary navigation buttons on mobile web
This commit is contained in:
Henriette Darge
2025-07-08 10:35:42 +02:00
committed by GitHub
55 changed files with 279 additions and 150 deletions
+1 -1
View File
@@ -422,4 +422,4 @@ end
gem "openproject-octicons", "~>19.25.0"
gem "openproject-octicons_helper", "~>19.25.0"
gem "openproject-primer_view_components", "~>0.70.2"
gem "openproject-primer_view_components", "~>0.70.3"
+3 -3
View File
@@ -861,7 +861,7 @@ GEM
actionview
openproject-octicons (= 19.25.0)
railties
openproject-primer_view_components (0.70.2)
openproject-primer_view_components (0.70.3)
actionview (>= 7.1.0)
activesupport (>= 7.1.0)
openproject-octicons (>= 19.25.0)
@@ -1453,7 +1453,7 @@ DEPENDENCIES
openproject-octicons (~> 19.25.0)
openproject-octicons_helper (~> 19.25.0)
openproject-openid_connect!
openproject-primer_view_components (~> 0.70.2)
openproject-primer_view_components (~> 0.70.3)
openproject-recaptcha!
openproject-reporting!
openproject-storages!
@@ -1827,7 +1827,7 @@ CHECKSUMS
openproject-octicons (19.25.0) sha256=16fc221375e693f0e893b1c208286f2d7719ae4dfe080c5415642b221f51f550
openproject-octicons_helper (19.25.0) sha256=9b1778a67b0015ebe84ca0471f74e31004b985a8dcaaa443f7a2ac365b0a4e2d
openproject-openid_connect (1.0.0)
openproject-primer_view_components (0.70.2) sha256=b08bc35f1edb00c583544e8757badf8914ba7e32c88a2eb187de53a6d688ba0a
openproject-primer_view_components (0.70.3) sha256=ccfa81b533f66d6fa1a7268d38e5350d33717685e6cfe175f19816544ca080e2
openproject-recaptcha (1.0.0)
openproject-reporting (1.0.0)
openproject-storages (1.0.0)
@@ -39,7 +39,7 @@ class Members::IndexPageHeaderComponent < ApplicationComponent
def breadcrumb_items
[{ href: project_overview_path(@project.id), text: @project.name },
{ href: project_members_path(@project), text: t(:label_member_plural) },
{ href: project_members_path(@project), text: I18n.t(:label_member_plural), skip_for_mobile: first_menu_item? },
current_breadcrumb_element]
end
@@ -74,17 +74,27 @@ class Members::IndexPageHeaderComponent < ApplicationComponent
def current_query
query_name = nil
query_href = nil
menu_header = nil
Members::Menu.new(project: @project, params:).menu_items.find do |section|
section.children.find do |menu_query|
if !!menu_query.selected
query_name = menu_query.title
query_href = menu_query.href
menu_header = section.header
end
end
end
{ query_name:, menu_header: }
{ query_name:, query_href:, menu_header: }
end
def first_menu_item?
if current_query.present?
return current_query[:query_href] == project_members_path(@project)
end
false
end
end
@@ -46,8 +46,7 @@ module Notifications
end
def breadcrumb_items
[{ href: home_path, text: helpers.organization_name },
{ href: notifications_path, text: I18n.t("js.notifications.title") },
[{ href: notifications_path, text: I18n.t("js.notifications.title"), skip_for_mobile: first_menu_item? },
current_breadcrumb_element]
end
@@ -74,5 +73,9 @@ module Notifications
.new(params:, current_user: User.current)
.selected_menu_item
end
def first_menu_item?
current_item&.href == notifications_path
end
end
end
@@ -112,8 +112,7 @@ class Projects::IndexPageHeaderComponent < ApplicationComponent
def breadcrumb_items
[
{ href: home_path, text: helpers.organization_name },
{ href: projects_path, text: t(:label_project_plural) },
{ href: projects_path, text: t(:label_project_plural), skip_for_mobile: first_menu_item? },
current_breadcrumb_element
]
end
@@ -136,6 +135,11 @@ class Projects::IndexPageHeaderComponent < ApplicationComponent
.selected_menu_group
end
def first_menu_item?
current_item = current_section&.children&.select { |x| x.selected == true }&.first
current_item&.title == ::ProjectQueries::Static.query(ProjectQueries::Static::DEFAULT).name
end
def header_save_action(header:, message:, label:, href:, method: nil)
header.with_action_text { message }
@@ -4,7 +4,7 @@
header.with_breadcrumbs(
[
{ href: project_overview_path(@project.id), text: @project.name },
{ href: project_settings_general_path(@project.id), text: I18n.t("label_project_settings") },
{ href: project_settings_general_path(@project.id), text: I18n.t("label_project_settings"), skip_for_mobile: true },
t(:label_information_plural)
]
)
@@ -34,7 +34,7 @@ module Settings
def breadcrumbs_items
[
{ href: admin_index_path, text: t("label_administration") },
{ href: admin_settings_project_custom_fields_path, text: t("label_project_plural") },
{ href: admin_settings_project_custom_fields_path, text: t("label_project_plural"), skip_for_mobile: true },
t("settings.project_phase_definitions.heading")
]
end
+1 -2
View File
@@ -32,8 +32,7 @@ See COPYRIGHT and LICENSE files for more details.
render Primer::OpenProject::PageHeader.new do |header|
header.with_title { t(:label_password_lost) }
header.with_breadcrumbs(
[{ href: home_path, text: organization_name },
t(:label_password_lost)]
[t(:label_password_lost)]
)
end
%>
+1 -2
View File
@@ -32,8 +32,7 @@ See COPYRIGHT and LICENSE files for more details.
render Primer::OpenProject::PageHeader.new do |header|
header.with_title { t(:label_password_lost) }
header.with_breadcrumbs(
[{ href: home_path, text: organization_name },
t(:label_password_lost)]
[t(:label_password_lost)]
)
end
%>
+4 -3
View File
@@ -34,9 +34,10 @@ See COPYRIGHT and LICENSE files for more details.
header.with_title { (@author.nil? ? t(:label_activity) : t(:label_user_activity, value: link_to_user(@author))).html_safe }
header.with_description { t(:label_date_from_to, start: format_date(@date_to - @days), end: format_date(@date_to - 1)) }
header.with_breadcrumbs(
[*([href: home_path, text: organization_name] unless @project),
*([href: project_overview_path(@project.id), text: @project.name] if @project),
t(:label_activity)]
[
({ href: project_overview_path(@project.id), text: @project.name } if @project),
t(:label_activity)
].compact
)
end
%>
+1 -1
View File
@@ -33,7 +33,7 @@ See COPYRIGHT and LICENSE files for more details.
render(Primer::OpenProject::PageHeader.new) do |header|
header.with_title { t(:label_overview) }
header.with_breadcrumbs(
[{ href: admin_index_path, text: t(:label_administration) },
[{ href: admin_index_path, text: t(:label_administration), skip_for_mobile: true },
t(:label_overview)]
)
end
@@ -34,7 +34,7 @@ See COPYRIGHT and LICENSE files for more details.
header.with_title { t(:"menus.admin.aggregation") }
header.with_breadcrumbs(
[{ href: admin_index_path, text: t(:label_administration) },
{ href: admin_settings_aggregation_path, text: t(:"menus.admin.mails_and_notifications") },
{ href: admin_settings_aggregation_path, text: t(:"menus.admin.mails_and_notifications"), skip_for_mobile: true },
t(:"menus.admin.aggregation")]
)
end
@@ -33,7 +33,7 @@ See COPYRIGHT and LICENSE files for more details.
header.with_title { t(:label_api_access_key_type) }
header.with_breadcrumbs(
[{ href: admin_index_path, text: t("label_administration") },
{ href: admin_settings_api_path, text: t("menus.admin.api_and_webhooks") },
{ href: admin_settings_api_path, text: t("menus.admin.api_and_webhooks"), skip_for_mobile: true },
t(:label_api_access_key_type)]
)
end
@@ -55,7 +55,7 @@ See COPYRIGHT and LICENSE files for more details.
header.with_title { t("authentication.login_and_registration") }
header.with_breadcrumbs(
[{ href: admin_index_path, text: t(:label_administration) },
{ href: admin_settings_authentication_path, text: t(:label_authentication) },
{ href: admin_settings_authentication_path, text: t(:label_authentication), skip_for_mobile: true },
t("authentication.login_and_registration")]
)
render_tab_header_nav(header, tabs)
@@ -34,7 +34,7 @@ See COPYRIGHT and LICENSE files for more details.
header.with_title { t(:label_general) }
header.with_breadcrumbs(
[{ href: admin_index_path, text: t("label_administration") },
{ href: admin_settings_general_path, text: t(:label_system_settings) },
{ href: admin_settings_general_path, text: t(:label_system_settings), skip_for_mobile: true },
t(:label_general)]
)
end
@@ -33,7 +33,7 @@ See COPYRIGHT and LICENSE files for more details.
header.with_title { t(:label_user_settings) }
header.with_breadcrumbs(
[{ href: admin_index_path, text: t("label_administration") },
{ href: admin_settings_users_path, text: t(:label_user_and_permission) },
{ href: admin_settings_users_path, text: t(:label_user_and_permission), skip_for_mobile: true },
t(:label_user_settings)]
)
end
@@ -34,7 +34,7 @@ See COPYRIGHT and LICENSE files for more details.
header.with_title { t(:label_general) }
header.with_breadcrumbs(
[{ href: admin_index_path, text: t("label_administration") },
{ href: admin_settings_work_packages_general_path, text: t(:label_work_package_plural) },
{ href: admin_settings_work_packages_general_path, text: t(:label_work_package_plural), skip_for_mobile: true },
t(:label_general)]
)
end
@@ -34,7 +34,7 @@ See COPYRIGHT and LICENSE files for more details.
header.with_title(test_selector: "op-working-days-admin-settings--title") { t(:label_working_days_and_hours) }
header.with_breadcrumbs(
[{ href: admin_index_path, text: t("label_administration") },
{ href: admin_settings_working_days_and_hours_path, text: t(:label_calendars_and_dates) },
{ href: admin_settings_working_days_and_hours_path, text: t(:label_calendars_and_dates), skip_for_mobile: true },
t(:label_working_days_and_hours)]
)
end
+2 -2
View File
@@ -29,7 +29,7 @@ See COPYRIGHT and LICENSE files for more details.
<%=
render(Primer::OpenProject::PageHeader.new) do |header|
header.with_title { I18n.t("label_home") }
header.with_breadcrumbs([{ href: home_path, text: organization_name }, I18n.t(:label_home)])
header.with_breadcrumbs([I18n.t(:label_home)], skip_home_on_mobile: true)
end
%>
@@ -52,7 +52,7 @@ See COPYRIGHT and LICENSE files for more details.
<% @homescreen[:links].each do |link| %>
<% title = I18n.t(link[:label], scope: "homescreen.links") %>
<a class="homescreen--links--item" href="<%= link[:url] %>" target="_blank" title="<%= title %>">
<a class="homescreen--links--item" href="<%= link[:url] %>" target="_blank" aria-label="<%= title %>">
<%= op_icon(link[:icon]) %>
<%= title %>
</a>
+1 -1
View File
@@ -33,7 +33,7 @@ See COPYRIGHT and LICENSE files for more details.
render(Primer::OpenProject::PageHeader.new) do |header|
header.with_title { t(:label_profile) }
header.with_breadcrumbs(
[{ href: my_account_path, text: t(:label_my_account) },
[{ href: my_account_path, text: t(:label_my_account), skip_for_mobile: true },
t(:label_profile)]
)
end
+5 -4
View File
@@ -32,10 +32,11 @@ See COPYRIGHT and LICENSE files for more details.
render Primer::OpenProject::PageHeader.new do |header|
header.with_title { @news.title }
header.with_breadcrumbs(
[*([href: home_path, text: organization_name] unless @project),
*([href: project_overview_path(@project.id), text: @project.name] if @project),
*([href: project_news_index_path(@project.id), text: t(:label_news_plural)] if @project),
@news.title]
[
({ href: project_overview_path(@project.id), text: @project.name } if @project),
({ href: project_news_index_path(@project.id), text: t(:label_news_plural) } if @project),
@news.title
].compact
)
end
%>
+4 -3
View File
@@ -37,9 +37,10 @@ See COPYRIGHT and LICENSE files for more details.
render Primer::OpenProject::PageHeader.new do |header|
header.with_title { t(:label_news_plural) }
header.with_breadcrumbs(
[*([href: home_path, text: organization_name] unless @project),
*([href: project_overview_path(@project.id), text: @project.name] if @project),
t(:label_news_plural)]
[
({ href: project_overview_path(@project.id), text: @project.name } if @project),
t(:label_news_plural)
].compact
)
end
%>
+5 -4
View File
@@ -31,10 +31,11 @@ See COPYRIGHT and LICENSE files for more details.
render Primer::OpenProject::PageHeader.new do |header|
header.with_title { "#{avatar(@news.author)} #{h @news.title}".html_safe }
header.with_breadcrumbs(
[*([href: home_path, text: organization_name] unless @project),
*([href: project_overview_path(@project.id), text: @project.name] if @project),
*([href: project_news_index_path(@project.id), text: t(:label_news_plural)] if @project),
@news.title]
[
({ href: project_overview_path(@project.id), text: @project.name } if @project),
({ href: project_news_index_path(@project.id), text: t(:label_news_plural) } if @project),
@news.title
].compact
)
if User.current.allowed_in_project?(:manage_news, @project)
header.with_action_button(
+1 -1
View File
@@ -30,7 +30,7 @@ See COPYRIGHT and LICENSE files for more details.
<%=
render(Primer::OpenProject::PageHeader.new) do |header|
header.with_title { t(:label_project_new) }
header.with_breadcrumbs([{ href: home_path, text: organization_name }, t(:label_project_new)])
header.with_breadcrumbs([t(:label_project_new)])
end
%>
+1 -1
View File
@@ -34,7 +34,7 @@ See COPYRIGHT and LICENSE files for more details.
header.with_breadcrumbs(
[
{ href: project_overview_path(@project.id), text: @project.name },
{ href: url_for({ controller: "/wiki", action: "index", project_id: @project.identifier, id: @related_page }), text: t("activerecord.models.wiki") },
{ href: url_for({ controller: "/wiki", action: "index", project_id: @project.identifier, id: @related_page }), text: t("activerecord.models.wiki"), skip_for_mobile: true },
t(:label_wiki_toc)
]
)
+14 -14
View File
@@ -53,12 +53,12 @@
"@ngneat/content-loader": "^7.0.0",
"@ngx-formly/core": "^6.1.4",
"@openproject/octicons-angular": "^19.25.0",
"@openproject/primer-view-components": "^0.70.2",
"@openproject/primer-view-components": "^0.70.3",
"@openproject/reactivestates": "^3.0.1",
"@primer/css": "^21.5.0",
"@primer/live-region-element": "^0.8.0",
"@primer/primitives": "^10.6.0",
"@primer/view-components": "npm:@openproject/primer-view-components@^0.70.2",
"@primer/view-components": "npm:@openproject/primer-view-components@^0.70.3",
"@stimulus-components/auto-submit": "^6.0.0",
"@types/hotwired__turbo": "^8.0.1",
"@uirouter/angular": "^13.0.0",
@@ -4907,9 +4907,9 @@
}
},
"node_modules/@openproject/primer-view-components": {
"version": "0.70.2",
"resolved": "https://registry.npmjs.org/@openproject/primer-view-components/-/primer-view-components-0.70.2.tgz",
"integrity": "sha512-h/2m1Ott9n1awZQRA26bNbex9g6NItBI1/sPDIWBVlQovy5pVJOx1LBjgv8W8VBCwKCJwWoTarK7Ln6JrNkc5A==",
"version": "0.70.3",
"resolved": "https://registry.npmjs.org/@openproject/primer-view-components/-/primer-view-components-0.70.3.tgz",
"integrity": "sha512-b/cNIpxfXWcaQzq282EqobEtqUZ6oTCrPilHRGEhJCkM4JVDZIbN9UvXZjuORGfs3s53Q1HACL1q5Q7/BTHP2Q==",
"dependencies": {
"@github/auto-check-element": "^6.0.0",
"@github/auto-complete-element": "^3.8.0",
@@ -4996,9 +4996,9 @@
},
"node_modules/@primer/view-components": {
"name": "@openproject/primer-view-components",
"version": "0.70.2",
"resolved": "https://registry.npmjs.org/@openproject/primer-view-components/-/primer-view-components-0.70.2.tgz",
"integrity": "sha512-h/2m1Ott9n1awZQRA26bNbex9g6NItBI1/sPDIWBVlQovy5pVJOx1LBjgv8W8VBCwKCJwWoTarK7Ln6JrNkc5A==",
"version": "0.70.3",
"resolved": "https://registry.npmjs.org/@openproject/primer-view-components/-/primer-view-components-0.70.3.tgz",
"integrity": "sha512-b/cNIpxfXWcaQzq282EqobEtqUZ6oTCrPilHRGEhJCkM4JVDZIbN9UvXZjuORGfs3s53Q1HACL1q5Q7/BTHP2Q==",
"dependencies": {
"@github/auto-check-element": "^6.0.0",
"@github/auto-complete-element": "^3.8.0",
@@ -28326,9 +28326,9 @@
}
},
"@openproject/primer-view-components": {
"version": "0.70.2",
"resolved": "https://registry.npmjs.org/@openproject/primer-view-components/-/primer-view-components-0.70.2.tgz",
"integrity": "sha512-h/2m1Ott9n1awZQRA26bNbex9g6NItBI1/sPDIWBVlQovy5pVJOx1LBjgv8W8VBCwKCJwWoTarK7Ln6JrNkc5A==",
"version": "0.70.3",
"resolved": "https://registry.npmjs.org/@openproject/primer-view-components/-/primer-view-components-0.70.3.tgz",
"integrity": "sha512-b/cNIpxfXWcaQzq282EqobEtqUZ6oTCrPilHRGEhJCkM4JVDZIbN9UvXZjuORGfs3s53Q1HACL1q5Q7/BTHP2Q==",
"requires": {
"@github/auto-check-element": "^6.0.0",
"@github/auto-complete-element": "^3.8.0",
@@ -28394,9 +28394,9 @@
"integrity": "sha512-732Dq7c4znkewwRkXldV0NLLppfGrDJHPXsXO2QLHwJxKLwi2icKkzcOR77vb5g0ThObZJrRqlB/4DYajIiyyQ=="
},
"@primer/view-components": {
"version": "npm:@openproject/primer-view-components@0.70.2",
"resolved": "https://registry.npmjs.org/@openproject/primer-view-components/-/primer-view-components-0.70.2.tgz",
"integrity": "sha512-h/2m1Ott9n1awZQRA26bNbex9g6NItBI1/sPDIWBVlQovy5pVJOx1LBjgv8W8VBCwKCJwWoTarK7Ln6JrNkc5A==",
"version": "npm:@openproject/primer-view-components@0.70.3",
"resolved": "https://registry.npmjs.org/@openproject/primer-view-components/-/primer-view-components-0.70.3.tgz",
"integrity": "sha512-b/cNIpxfXWcaQzq282EqobEtqUZ6oTCrPilHRGEhJCkM4JVDZIbN9UvXZjuORGfs3s53Q1HACL1q5Q7/BTHP2Q==",
"requires": {
"@github/auto-check-element": "^6.0.0",
"@github/auto-complete-element": "^3.8.0",
+2 -2
View File
@@ -105,12 +105,12 @@
"@ngneat/content-loader": "^7.0.0",
"@ngx-formly/core": "^6.1.4",
"@openproject/octicons-angular": "^19.25.0",
"@openproject/primer-view-components": "^0.70.2",
"@openproject/primer-view-components": "^0.70.3",
"@openproject/reactivestates": "^3.0.1",
"@primer/css": "^21.5.0",
"@primer/live-region-element": "^0.8.0",
"@primer/primitives": "^10.6.0",
"@primer/view-components": "npm:@openproject/primer-view-components@^0.70.2",
"@primer/view-components": "npm:@openproject/primer-view-components@^0.70.3",
"@stimulus-components/auto-submit": "^6.0.0",
"@types/hotwired__turbo": "^8.0.1",
"@uirouter/angular": "^13.0.0",
@@ -179,6 +179,7 @@ export class IFCViewerPageComponent extends PartitionedQuerySpacePageComponent i
breadcrumbItems() {
return [
{ href: this.pathHelperService.homePath(), text: this.titleService.appTitle },
{ href: this.pathHelperService.projectPath(this.currentProject.identifier as string), text: (this.currentProject.name) },
{ href: this.pathHelperService.projectBCFPath(this.currentProject.identifier as string), text: this.I18n.t('js.bcf.label_bcf') },
this.selectedTitle?? '',
@@ -200,6 +200,7 @@ export class BoardPartitionedPageComponent extends UntilDestroyedMixin {
breadcrumbItems() {
return [
{ href: this.pathHelperService.homePath(), text: this.titleService.appTitle },
{ href: this.pathHelperService.projectPath(this.currentProject.identifier as string), text: (this.currentProject.name) },
{ href: this.pathHelperService.boardsPath(this.currentProject.identifier as string), text: this.I18n.t('js.label_board_plural') },
this.selectedTitle?? '',
@@ -74,6 +74,7 @@ export class WorkPackagesCalendarPageComponent extends PartitionedQuerySpacePage
breadcrumbItems() {
return [
{ href: this.pathHelperService.homePath(), text: this.titleService.appTitle },
{ href: this.pathHelperService.projectPath(this.currentProject.identifier as string), text: (this.currentProject.name) },
{ href: this.pathHelperService.projectCalendarPath(this.currentProject.identifier as string), text: this.I18n.t('js.calendar.label_calendar_plural') },
this.selectedTitle?? '',
@@ -125,6 +125,7 @@ export class TeamPlannerPageComponent extends PartitionedQuerySpacePageComponent
breadcrumbItems() {
return [
{ href: this.pathHelperService.homePath(), text: this.titleService.appTitle },
{ href: this.pathHelperService.projectPath(this.currentProject.identifier as string), text: (this.currentProject.name) },
{ href: this.pathHelperService.projectTeamplannerPath(this.currentProject.identifier as string), text: this.I18n.t('js.team_planner.label_team_planner_plural') },
this.selectedTitle?? '',
@@ -45,6 +45,7 @@ import { of } from 'rxjs';
import { WorkPackageFoldToggleButtonComponent } from 'core-app/features/work-packages/components/wp-buttons/wp-fold-toggle-button/wp-fold-toggle-button.component';
import { OpProjectIncludeComponent } from 'core-app/shared/components/project-include/project-include.component';
import { OpBaselineModalComponent } from 'core-app/features/work-packages/components/wp-baseline/baseline-modal/baseline-modal.component';
import { BreadcrumbItem } from 'core-app/shared/components/breadcrumbs/op-breadcrumbs.component';
@Component({
selector: 'wp-view-page',
@@ -109,20 +110,20 @@ export class WorkPackageViewPageComponent extends PartitionedQuerySpacePageCompo
}
breadcrumbItems() {
const items = [];
const items:BreadcrumbItem[] = [{
href: this.pathHelperService.homePath(),
text: this.titleService.appTitle,
}];
if (this.currentProject?.identifier) {
items.push({
href: this.pathHelperService.projectPath(this.currentProject.identifier),
text: this.currentProject.name,
});
} else {
items.push({
href: this.pathHelperService.homePath(),
text: this.titleService.appTitle,
text: this.currentProject.name as string,
});
}
items.push(this.breadcrumbModuleEntry());
if (this.selectedTitle) {
items.push(this.selectedTitle);
}
@@ -0,0 +1,45 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++
module OpenProject
module Patches
module PrimerPageHeaderBreadcrumb
def with_breadcrumbs(breadcrumbs, skip_home_on_mobile: false, **)
super([{ href: home_path,
text: helpers.organization_name,
skip_for_mobile: skip_home_on_mobile }] + breadcrumbs, **)
end
end
end
end
OpenProject::Patches.patch_gem_version "openproject-primer_view_components", "0.70.3" do
Primer::OpenProject::PageHeader.prepend OpenProject::Patches::PrimerPageHeaderBreadcrumb
end
+2 -1
View File
@@ -13,7 +13,8 @@ There are minor adjustments on how the composition of this component is defined
- **Desktop:** The content of the context bar will be a [breadcrumb](https://primer.style/components/breadcrumbs).
- **Tablet:** The content of the context bar will be a [breadcrumb](https://primer.style/components/breadcrumbs).
- **Mobile:**
- The content of the breadcrumb will collapse to a single element using the parent link style.
- The content of the breadcrumb will collapse to a single element using the parent link style. The text and href for that link will be taken from the second-last item of the breadcrumb, (which should be the parent of the current page).
The only exception to that behavior are index pages, where the parent link might lead to the page itself again. To avoid these kind of loops on mobile, we can exclude certain elements from being taken as a mobile back link. An example can be found the [component previews](../../inspect/primer/open_project/page_header/skip_breadcrumb_item).
- All the PageHeader actions will merge into an [ActionMenu](https://primer.style/components/action-menu) triggered by a single [Icon button](https://primer.style/components/icon-button) in the context bar.
**[Title bar](https://primer.style/components/page-header#title-bar):** The default (medium) size will be always used keeping the capability as optionals of adding leading and trailing visuals and actions.
@@ -5,6 +5,7 @@
header.with_title { t("saml.providers.plural") }
header.with_breadcrumbs(
[{ href: admin_index_path, text: t("label_administration") },
{ href: admin_settings_authentication_path, text: t(:label_authentication) },
t("saml.providers.plural")]
)
end %>
@@ -23,5 +24,5 @@
end %>
<% end %>
<%= render EnterpriseEdition::BannerComponent.new(:sso_auth_providers, i18n_scope: "saml.providers.upsell")%>
<%= render EnterpriseEdition::BannerComponent.new(:sso_auth_providers, i18n_scope: "saml.providers.upsell") %>
<%= render ::Saml::Providers::TableComponent.new(rows: @providers) %>
@@ -33,9 +33,10 @@ See COPYRIGHT and LICENSE files for more details.
render Primer::OpenProject::PageHeader.new do |header|
header.with_title { t("boards.label_boards") }
header.with_breadcrumbs(
[*([href: home_path, text: organization_name] unless @project),
*([href: project_overview_path(@project.id), text: @project.name] if @project),
t("boards.label_boards")]
[
({ href: project_overview_path(@project.id), text: @project.name } if @project),
t("boards.label_boards")
].compact
)
end
%>
@@ -33,10 +33,11 @@ See COPYRIGHT and LICENSE files for more details.
render Primer::OpenProject::PageHeader.new do |header|
header.with_title { t("boards.label_create_new_board") }
header.with_breadcrumbs(
[*([href: home_path, text: organization_name] unless @project),
*([href: project_overview_path(@project.id), text: @project.name] if @project),
*([href: project_work_package_boards_path(@project.id), text: t("boards.label_boards")] if @project),
t("boards.label_create_new_board")]
[
({ href: project_overview_path(@project.id), text: @project.name } if @project),
({ href: project_work_package_boards_path(@project.id), text: t("boards.label_boards") } if @project),
t("boards.label_create_new_board")
].compact
)
end
%>
@@ -32,9 +32,10 @@ See COPYRIGHT and LICENSE files for more details.
render Primer::OpenProject::PageHeader.new do |header|
header.with_title { t(:label_calendar_plural) }
header.with_breadcrumbs(
[*([href: home_path, text: organization_name] unless @project),
*([href: project_overview_path(@project.id), text: @project.name] if @project),
t(:label_calendar_plural)]
[
({ href: project_overview_path(@project.id), text: @project.name } if @project),
t(:label_calendar_plural)
].compact
)
end
%>
@@ -33,8 +33,7 @@ See COPYRIGHT and LICENSE files for more details.
render Primer::OpenProject::PageHeader.new do |header|
header.with_title { t(:label_new_calendar) }
header.with_breadcrumbs(
[{ href: home_path, text: organization_name },
{ href: calendars_path, text: t(:label_calendar_plural) },
[{ href: calendars_path, text: t(:label_calendar_plural) },
t(:label_new_calendar)]
)
end
@@ -2,7 +2,6 @@
header.with_title { I18n.t(:label_my_time_tracking) }
header.with_breadcrumbs(
[
{ href: home_path, text: helpers.organization_name },
{ text: I18n.t(:label_my_time_tracking), href: my_time_tracking_path }
]
)
@@ -35,7 +35,7 @@ See COPYRIGHT and LICENSE files for more details.
header.with_breadcrumbs(
[
{ href: admin_index_path, text: t("label_administration") },
{ href: admin_costs_settings_path, text: t(:project_module_costs) },
{ href: admin_costs_settings_path, text: t(:project_module_costs), skip_for_mobile: true },
t(:label_defaults)
]
)
@@ -34,7 +34,7 @@ See COPYRIGHT and LICENSE files for more details.
[{ href: project_overview_path(@project.identifier), text: @project.name },
{ href: projects_budgets_path(@project.identifier), text: I18n.t(:label_budget_plural) }]
else
[{ href: home_path, text: organization_name }]
[]
end
%>
@@ -34,7 +34,7 @@ See COPYRIGHT and LICENSE files for more details.
[{ href: project_overview_path(@project.identifier), text: @project.name },
{ href: projects_budgets_path(@project.identifier), text: I18n.t(:label_budget_plural) }]
else
[{ href: home_path, text: organization_name }]
[]
end
%>
@@ -75,7 +75,7 @@ module Meetings
def breadcrumb_items
[
parent_element,
({ href: project_overview_path(@project.id), text: @project.name } if @project.present?),
{ href: @project.present? ? project_meetings_path(@project.id) : meetings_path,
text: I18n.t(:label_meeting_plural) },
meeting_series_element,
@@ -99,14 +99,6 @@ module Meetings
end
end
def parent_element
if @project.present?
{ href: project_overview_path(@project.id), text: @project.name }
else
{ href: home_path, text: helpers.organization_name }
end
end
def delete_label
if @series.present?
I18n.t("label_recurring_meeting_cancel")
@@ -46,18 +46,12 @@ module Meetings
end
def breadcrumb_items
[parent_element,
{ href: url_for({ controller: "meetings", action: :index, project_id: @project }),
text: I18n.t(:label_meeting_plural) },
current_breadcrumb_element]
end
def parent_element
if @project.present?
{ href: project_overview_path(@project.id), text: @project.name }
else
{ href: home_path, text: helpers.organization_name }
end
[
({ href: project_overview_path(@project.id), text: @project.name } if @project.present?),
{ href: url_for({ controller: "meetings", action: :index, project_id: @project }),
text: I18n.t(:label_meeting_plural), skip_for_mobile: first_menu_item? },
current_breadcrumb_element
].compact
end
def current_breadcrumb_element
@@ -87,5 +81,9 @@ module Meetings
.new(project: @project, params: params.merge(current_href: request.path))
.selected_menu_item
end
def first_menu_item?
current_item&.href == (@project.present? ? project_meetings_path(@project.identifier) : meetings_path)
end
end
end
@@ -83,18 +83,12 @@ module RecurringMeetings
end
def breadcrumb_items
[parent_element,
{ href: @project.present? ? project_meetings_path(@project.id) : meetings_path,
text: I18n.t(:label_meeting_plural) },
page_title(true)]
end
def parent_element
if @project.present?
{ href: project_overview_path(@project.id), text: @project.name }
else
{ href: home_path, text: I18n.t(:label_home) }
end
[
({ href: project_overview_path(@project.id), text: @project.name } if @project.present?),
{ href: @project.present? ? project_meetings_path(@project.id) : meetings_path,
text: I18n.t(:label_meeting_plural) },
page_title(true)
].compact
end
end
end
@@ -31,14 +31,12 @@ See COPYRIGHT and LICENSE files for more details.
render(Primer::OpenProject::PageHeader.new) do |header|
header.with_title { t(:label_recurring_meeting_new) }
header.with_breadcrumbs(
[if @project.present?
{ href: project_overview_path(@project.id), text: @project.name }
else
{ href: home_path, text: I18n.t(:label_home) }
end,
{ href: @project.present? ? project_recurring_meetings_path(@project.id) : recurring_meetings_path,
text: I18n.t(:label_meeting_plural) },
t(:label_recurring_meeting_new)]
[
({ href: project_overview_path(@project.id), text: @project.name } if @project.present?),
{ href: @project.present? ? project_recurring_meetings_path(@project.id) : recurring_meetings_path,
text: I18n.t(:label_meeting_plural) },
t(:label_recurring_meeting_new)
].compact
)
end
%>
@@ -5,7 +5,6 @@
header.with_title { t("my_page.label") }
header.with_breadcrumbs(
[
{ href: home_path, text: organization_name },
t("my_page.label")
]
)
@@ -8,7 +8,7 @@
header.with_title(variant: :medium) { t("overviews.label") }
header.with_breadcrumbs(
[
{ href: project_path(@project), text: @project.name },
{ href: project_path(@project), text: @project.name, skip_for_mobile: true },
t("overviews.label")
]
)
@@ -39,23 +39,14 @@ module CostReports
@user = User.current
end
def page_title
I18n.t(:label_meeting_plural)
end
def breadcrumb_items
[parent_element,
{ href: url_for({ controller: "cost_reports", action: :index, project_id: @project }),
text: I18n.t(:cost_reports_title) },
current_breadcrumb_element]
end
def parent_element
if @project.present?
{ href: project_overview_path(@project.id), text: @project.name }
else
{ href: home_path, text: helpers.organization_name }
end
[
({ href: project_overview_path(@project.id), text: @project.name } if @project.present?),
{ href: module_path,
text: I18n.t(:cost_reports_title),
skip_for_mobile: !current_section || current_section.header.blank? },
current_breadcrumb_element
].compact
end
def current_breadcrumb_element
@@ -79,5 +70,9 @@ module CostReports
def show_export_button?
@user.allowed_in_any_work_package?(:export_work_packages, in_project: @project)
end
def module_path
@project.present? ? cost_reports_path(@project) : global_cost_reports_path
end
end
end
+1 -1
View File
@@ -48,7 +48,7 @@ Rails.application.routes.draw do
resources :cost_reports, except: :create do
collection do
match :index, via: %i[get post]
match :index, via: %i[get post], as: :global
post :save_as, action: :create
get :drill_down
match :available_values, via: %i[get post]
@@ -11,7 +11,7 @@ end %>
<% header.with_title { t("external_file_storages") } %>
<% header.with_description { t("storages.page_titles.file_storages.subtitle") } %>
<% header.with_breadcrumbs([{ href: admin_index_path, text: t("label_administration") },
{ href: admin_settings_storages_path, text: t("project_module_storages") },
{ href: admin_settings_storages_path, text: t("project_module_storages"), skip_for_mobile: true },
t("external_file_storages")]) %>
<% end %>
@@ -32,9 +32,10 @@ See COPYRIGHT and LICENSE files for more details.
render(Primer::OpenProject::PageHeader.new) do |header|
header.with_title { t("team_planner.label_new_team_planner") }
header.with_breadcrumbs(
[*([href: home_path, text: organization_name] unless @project),
*([href: project_overview_path(@project.id), text: @project.name] if @project),
t("team_planner.label_new_team_planner")]
[
({ href: project_overview_path(@project.id), text: @project.name} if @project),
t("team_planner.label_new_team_planner")
].compact
)
end
%>
@@ -3,8 +3,7 @@
render Primer::OpenProject::PageHeader.new do |header|
header.with_title { t("team_planner.label_team_planner_plural") }
header.with_breadcrumbs(
[{ href: home_path, text: organization_name },
t("team_planner.label_team_planner_plural")]
[t("team_planner.label_team_planner_plural")]
)
end
%>
+81
View File
@@ -0,0 +1,81 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++
require "spec_helper"
RSpec.describe "Breadcrumbs (#63777)", :js do
let(:user) { create(:admin) }
let(:project) { create(:project) }
before do
login_as user
end
context "when being on an index page which is not the home screen" do
it "does not create a loop in the mobile back links" do
visit projects_path
within ".PageHeader-breadcrumbs" do
expect(page).to have_link href: "#", text: "Active projects", aria: { current: "page" }
expect(page).to have_link href: "/projects", text: "Projects"
expect(page).to have_link href: "/", text: "OpenProject"
end
expect(page).to have_link href: "/", text: "OpenProject", class: "PageHeader-parentLink", visible: :hidden
end
end
context "when being on an non-index page" do
it "does show the index page as mobile back link" do
visit projects_path({ query_id: "my" })
within ".PageHeader-breadcrumbs" do
expect(page).to have_link href: "#", text: "My projects", aria: { current: "page" }
expect(page).to have_link href: "/projects", text: "Projects"
expect(page).to have_link href: "/", text: "OpenProject"
end
expect(page).to have_link href: "/projects", text: "Projects", class: "PageHeader-parentLink", visible: :hidden
end
end
context "when being on the home screen" do
it "does not display a (mobile) back link" do
visit "/"
within ".PageHeader-breadcrumbs" do
expect(page).to have_link href: "#", text: "Home", aria: { current: "page" }
expect(page).to have_link href: "/", text: "OpenProject"
expect(page).to have_no_css ".PageHeader-parentLink"
end
end
end
end