From 8d46e97468c0888e21cf1edc7aa0e7cd69d21b78 Mon Sep 17 00:00:00 2001 From: Henriette Darge Date: Wed, 2 Jul 2025 15:14:24 +0200 Subject: [PATCH 1/9] Create a OP specific patch for the PageHeader to always prepend the organization name as the first element in the breadcrumb --- .../index_page_header_component.rb | 3 +- .../projects/index_page_header_component.rb | 1 - app/views/account/lost_password.html.erb | 3 +- app/views/account/password_recovery.html.erb | 3 +- app/views/activities/index.html.erb | 3 +- app/views/homescreen/index.html.erb | 2 +- app/views/news/edit.html.erb | 3 +- app/views/news/index.html.erb | 3 +- app/views/news/show.html.erb | 3 +- app/views/projects/new.html.erb | 2 +- .../pages/viewer/ifc-viewer-page.component.ts | 1 + .../board-partitioned-page.component.ts | 1 + .../wp-calendar-page.component.ts | 1 + .../page/team-planner-page.component.ts | 1 + .../wp-view-page/wp-view-page.component.ts | 15 +++---- .../patches/primer_page_header_breadcrumb.rb | 41 +++++++++++++++++++ .../app/views/boards/boards/index.html.erb | 3 +- .../app/views/boards/boards/new.html.erb | 3 +- .../views/calendar/calendars/index.html.erb | 3 +- .../app/views/calendar/calendars/new.html.erb | 3 +- .../time_tracking/header_component.html.erb | 1 - .../app/views/hourly_rates/edit.html.erb | 2 +- .../app/views/hourly_rates/show.html.erb | 2 +- .../components/meetings/header_component.rb | 10 +---- .../meetings/index_page_header_component.rb | 10 +---- .../show_page_header_component.rb | 10 +---- .../app/views/recurring_meetings/new.html.erb | 6 +-- .../my_page/app/views/my/page/show.html.erb | 1 - .../index_page_header_component.rb | 10 +---- .../team_planner/team_planner/new.html.erb | 3 +- .../team_planner/overview.html.erb | 3 +- 31 files changed, 75 insertions(+), 81 deletions(-) create mode 100644 lib/open_project/patches/primer_page_header_breadcrumb.rb diff --git a/app/components/notifications/index_page_header_component.rb b/app/components/notifications/index_page_header_component.rb index dcb1f9fd0d8..6d6eb0f7c16 100644 --- a/app/components/notifications/index_page_header_component.rb +++ b/app/components/notifications/index_page_header_component.rb @@ -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") }, current_breadcrumb_element] end diff --git a/app/components/projects/index_page_header_component.rb b/app/components/projects/index_page_header_component.rb index 2e6fff5186a..b0a202216b1 100644 --- a/app/components/projects/index_page_header_component.rb +++ b/app/components/projects/index_page_header_component.rb @@ -112,7 +112,6 @@ class Projects::IndexPageHeaderComponent < ApplicationComponent def breadcrumb_items [ - { href: home_path, text: helpers.organization_name }, { href: projects_path, text: t(:label_project_plural) }, current_breadcrumb_element ] diff --git a/app/views/account/lost_password.html.erb b/app/views/account/lost_password.html.erb index 62ae0cb2b1d..c97b94f35dd 100644 --- a/app/views/account/lost_password.html.erb +++ b/app/views/account/lost_password.html.erb @@ -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 %> diff --git a/app/views/account/password_recovery.html.erb b/app/views/account/password_recovery.html.erb index 33d456d67ae..5356ff1b5bc 100644 --- a/app/views/account/password_recovery.html.erb +++ b/app/views/account/password_recovery.html.erb @@ -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 %> diff --git a/app/views/activities/index.html.erb b/app/views/activities/index.html.erb index bfe068e77f1..2ae2301c320 100644 --- a/app/views/activities/index.html.erb +++ b/app/views/activities/index.html.erb @@ -34,8 +34,7 @@ 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), + [*([href: project_overview_path(@project.id), text: @project.name] if @project), t(:label_activity)] ) end diff --git a/app/views/homescreen/index.html.erb b/app/views/homescreen/index.html.erb index 0354aee8f66..8e25fc37da4 100644 --- a/app/views/homescreen/index.html.erb +++ b/app/views/homescreen/index.html.erb @@ -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)]) end %> diff --git a/app/views/news/edit.html.erb b/app/views/news/edit.html.erb index 49127a8417e..f3d59f71b21 100644 --- a/app/views/news/edit.html.erb +++ b/app/views/news/edit.html.erb @@ -32,8 +32,7 @@ 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_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] ) diff --git a/app/views/news/index.html.erb b/app/views/news/index.html.erb index 300d7425694..5fe57f6f2df 100644 --- a/app/views/news/index.html.erb +++ b/app/views/news/index.html.erb @@ -37,8 +37,7 @@ 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), + [*([href: project_overview_path(@project.id), text: @project.name] if @project), t(:label_news_plural)] ) end diff --git a/app/views/news/show.html.erb b/app/views/news/show.html.erb index 8dd081cbb25..4dfd8b1b224 100644 --- a/app/views/news/show.html.erb +++ b/app/views/news/show.html.erb @@ -31,8 +31,7 @@ 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_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] ) diff --git a/app/views/projects/new.html.erb b/app/views/projects/new.html.erb index d8f9fc8e599..eedf76c1ad1 100644 --- a/app/views/projects/new.html.erb +++ b/app/views/projects/new.html.erb @@ -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 %> diff --git a/frontend/src/app/features/bim/ifc_models/pages/viewer/ifc-viewer-page.component.ts b/frontend/src/app/features/bim/ifc_models/pages/viewer/ifc-viewer-page.component.ts index 1350f10fea0..b0d76df9b5a 100644 --- a/frontend/src/app/features/bim/ifc_models/pages/viewer/ifc-viewer-page.component.ts +++ b/frontend/src/app/features/bim/ifc_models/pages/viewer/ifc-viewer-page.component.ts @@ -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?? '', diff --git a/frontend/src/app/features/boards/board/board-partitioned-page/board-partitioned-page.component.ts b/frontend/src/app/features/boards/board/board-partitioned-page/board-partitioned-page.component.ts index 41eba276009..9fb2abe638d 100644 --- a/frontend/src/app/features/boards/board/board-partitioned-page/board-partitioned-page.component.ts +++ b/frontend/src/app/features/boards/board/board-partitioned-page/board-partitioned-page.component.ts @@ -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?? '', diff --git a/frontend/src/app/features/calendar/wp-calendar-page/wp-calendar-page.component.ts b/frontend/src/app/features/calendar/wp-calendar-page/wp-calendar-page.component.ts index 7ce31c841d9..c749d8366e0 100644 --- a/frontend/src/app/features/calendar/wp-calendar-page/wp-calendar-page.component.ts +++ b/frontend/src/app/features/calendar/wp-calendar-page/wp-calendar-page.component.ts @@ -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?? '', diff --git a/frontend/src/app/features/team-planner/team-planner/page/team-planner-page.component.ts b/frontend/src/app/features/team-planner/team-planner/page/team-planner-page.component.ts index 2988cba02c9..d9ed51f5849 100644 --- a/frontend/src/app/features/team-planner/team-planner/page/team-planner-page.component.ts +++ b/frontend/src/app/features/team-planner/team-planner/page/team-planner-page.component.ts @@ -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?? '', diff --git a/frontend/src/app/features/work-packages/routing/wp-view-page/wp-view-page.component.ts b/frontend/src/app/features/work-packages/routing/wp-view-page/wp-view-page.component.ts index 627128a1bd8..4f7758a01ee 100644 --- a/frontend/src/app/features/work-packages/routing/wp-view-page/wp-view-page.component.ts +++ b/frontend/src/app/features/work-packages/routing/wp-view-page/wp-view-page.component.ts @@ -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); } diff --git a/lib/open_project/patches/primer_page_header_breadcrumb.rb b/lib/open_project/patches/primer_page_header_breadcrumb.rb new file mode 100644 index 00000000000..af6717b1260 --- /dev/null +++ b/lib/open_project/patches/primer_page_header_breadcrumb.rb @@ -0,0 +1,41 @@ +#-- 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, **) + super([{ href: home_path, text: helpers.organization_name}] + breadcrumbs, **) + end + end + end +end + +OpenProject::Patches.patch_gem_version "openproject-primer_view_components", "0.70.2" do + Primer::OpenProject::PageHeader.prepend OpenProject::Patches::PrimerPageHeaderBreadcrumb +end diff --git a/modules/boards/app/views/boards/boards/index.html.erb b/modules/boards/app/views/boards/boards/index.html.erb index 75e4440e778..524bb0cccfd 100644 --- a/modules/boards/app/views/boards/boards/index.html.erb +++ b/modules/boards/app/views/boards/boards/index.html.erb @@ -33,8 +33,7 @@ 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), + [*([href: project_overview_path(@project.id), text: @project.name] if @project), t("boards.label_boards")] ) end diff --git a/modules/boards/app/views/boards/boards/new.html.erb b/modules/boards/app/views/boards/boards/new.html.erb index c9a52fad09d..da90c86177b 100644 --- a/modules/boards/app/views/boards/boards/new.html.erb +++ b/modules/boards/app/views/boards/boards/new.html.erb @@ -33,8 +33,7 @@ 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_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")] ) diff --git a/modules/calendar/app/views/calendar/calendars/index.html.erb b/modules/calendar/app/views/calendar/calendars/index.html.erb index cf97456f90d..4d41239814d 100644 --- a/modules/calendar/app/views/calendar/calendars/index.html.erb +++ b/modules/calendar/app/views/calendar/calendars/index.html.erb @@ -32,8 +32,7 @@ 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), + [*([href: project_overview_path(@project.id), text: @project.name] if @project), t(:label_calendar_plural)] ) end diff --git a/modules/calendar/app/views/calendar/calendars/new.html.erb b/modules/calendar/app/views/calendar/calendars/new.html.erb index fea58db439e..0de4281096f 100644 --- a/modules/calendar/app/views/calendar/calendars/new.html.erb +++ b/modules/calendar/app/views/calendar/calendars/new.html.erb @@ -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 diff --git a/modules/costs/app/components/my/time_tracking/header_component.html.erb b/modules/costs/app/components/my/time_tracking/header_component.html.erb index b1b6f725b5c..d0ee993b5c9 100644 --- a/modules/costs/app/components/my/time_tracking/header_component.html.erb +++ b/modules/costs/app/components/my/time_tracking/header_component.html.erb @@ -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 } ] ) diff --git a/modules/costs/app/views/hourly_rates/edit.html.erb b/modules/costs/app/views/hourly_rates/edit.html.erb index fd359ff0146..2c230e9fd15 100644 --- a/modules/costs/app/views/hourly_rates/edit.html.erb +++ b/modules/costs/app/views/hourly_rates/edit.html.erb @@ -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 %> diff --git a/modules/costs/app/views/hourly_rates/show.html.erb b/modules/costs/app/views/hourly_rates/show.html.erb index 2f8816bbffa..54c88ddcddd 100644 --- a/modules/costs/app/views/hourly_rates/show.html.erb +++ b/modules/costs/app/views/hourly_rates/show.html.erb @@ -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 %> diff --git a/modules/meeting/app/components/meetings/header_component.rb b/modules/meeting/app/components/meetings/header_component.rb index ea42f3d24d8..aeb1534878f 100644 --- a/modules/meeting/app/components/meetings/header_component.rb +++ b/modules/meeting/app/components/meetings/header_component.rb @@ -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") diff --git a/modules/meeting/app/components/meetings/index_page_header_component.rb b/modules/meeting/app/components/meetings/index_page_header_component.rb index 186f0dd4cd0..d61229b89b3 100644 --- a/modules/meeting/app/components/meetings/index_page_header_component.rb +++ b/modules/meeting/app/components/meetings/index_page_header_component.rb @@ -46,20 +46,12 @@ module Meetings end def breadcrumb_items - [parent_element, + [*([{ 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) }, 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 - end - def current_breadcrumb_element if section_present? helpers.nested_breadcrumb_element(current_section.header, page_title) diff --git a/modules/meeting/app/components/recurring_meetings/show_page_header_component.rb b/modules/meeting/app/components/recurring_meetings/show_page_header_component.rb index dc7fea60b79..3636394d294 100644 --- a/modules/meeting/app/components/recurring_meetings/show_page_header_component.rb +++ b/modules/meeting/app/components/recurring_meetings/show_page_header_component.rb @@ -83,18 +83,10 @@ module RecurringMeetings end 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) }, 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 - end end end diff --git a/modules/meeting/app/views/recurring_meetings/new.html.erb b/modules/meeting/app/views/recurring_meetings/new.html.erb index 0784a8ebd36..b17a5736112 100644 --- a/modules/meeting/app/views/recurring_meetings/new.html.erb +++ b/modules/meeting/app/views/recurring_meetings/new.html.erb @@ -31,11 +31,7 @@ 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_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)] diff --git a/modules/my_page/app/views/my/page/show.html.erb b/modules/my_page/app/views/my/page/show.html.erb index 3eb27c87c3d..3da4d4759f5 100644 --- a/modules/my_page/app/views/my/page/show.html.erb +++ b/modules/my_page/app/views/my/page/show.html.erb @@ -5,7 +5,6 @@ header.with_title { t("my_page.label") } header.with_breadcrumbs( [ - { href: home_path, text: organization_name }, t("my_page.label") ] ) diff --git a/modules/reporting/app/components/cost_reports/index_page_header_component.rb b/modules/reporting/app/components/cost_reports/index_page_header_component.rb index e9b137d3ff8..aeddab9323a 100644 --- a/modules/reporting/app/components/cost_reports/index_page_header_component.rb +++ b/modules/reporting/app/components/cost_reports/index_page_header_component.rb @@ -44,20 +44,12 @@ module CostReports end def breadcrumb_items - [parent_element, + [*([{ href: project_overview_path(@project.id), text: @project.name }] if @project.present?), { 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 - end - def current_breadcrumb_element return I18n.t(:label_new_report) unless @query.persisted? diff --git a/modules/team_planner/app/views/team_planner/team_planner/new.html.erb b/modules/team_planner/app/views/team_planner/team_planner/new.html.erb index 1c0454d3bae..e54051de008 100644 --- a/modules/team_planner/app/views/team_planner/team_planner/new.html.erb +++ b/modules/team_planner/app/views/team_planner/team_planner/new.html.erb @@ -32,8 +32,7 @@ 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), + [*([href: project_overview_path(@project.id), text: @project.name] if @project), t("team_planner.label_new_team_planner")] ) end diff --git a/modules/team_planner/app/views/team_planner/team_planner/overview.html.erb b/modules/team_planner/app/views/team_planner/team_planner/overview.html.erb index e8ca63d351c..0df7db59d66 100644 --- a/modules/team_planner/app/views/team_planner/team_planner/overview.html.erb +++ b/modules/team_planner/app/views/team_planner/team_planner/overview.html.erb @@ -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 %> From 44f5e054cd2e6b66a9c01c0d2a28a464096f8c86 Mon Sep 17 00:00:00 2001 From: Henriette Darge Date: Fri, 4 Jul 2025 14:53:54 +0200 Subject: [PATCH 2/9] Cleanup code --- app/views/activities/index.html.erb | 6 ++++-- app/views/news/edit.html.erb | 8 +++++--- app/views/news/index.html.erb | 6 ++++-- app/views/news/show.html.erb | 8 +++++--- .../patches/primer_page_header_breadcrumb.rb | 4 +++- modules/boards/app/views/boards/boards/index.html.erb | 6 ++++-- modules/boards/app/views/boards/boards/new.html.erb | 8 +++++--- .../app/views/calendar/calendars/index.html.erb | 6 ++++-- .../app/components/meetings/header_component.rb | 2 +- .../components/meetings/index_page_header_component.rb | 10 ++++++---- .../recurring_meetings/show_page_header_component.rb | 10 ++++++---- .../meeting/app/views/recurring_meetings/new.html.erb | 10 ++++++---- .../cost_reports/index_page_header_component.rb | 10 ++++++---- .../app/views/team_planner/team_planner/new.html.erb | 6 ++++-- 14 files changed, 63 insertions(+), 37 deletions(-) diff --git a/app/views/activities/index.html.erb b/app/views/activities/index.html.erb index 2ae2301c320..766b00ecdba 100644 --- a/app/views/activities/index.html.erb +++ b/app/views/activities/index.html.erb @@ -34,8 +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: 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 %> diff --git a/app/views/news/edit.html.erb b/app/views/news/edit.html.erb index f3d59f71b21..eff11a9e565 100644 --- a/app/views/news/edit.html.erb +++ b/app/views/news/edit.html.erb @@ -32,9 +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: 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 %> diff --git a/app/views/news/index.html.erb b/app/views/news/index.html.erb index 5fe57f6f2df..9f391574cc6 100644 --- a/app/views/news/index.html.erb +++ b/app/views/news/index.html.erb @@ -37,8 +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: 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 %> diff --git a/app/views/news/show.html.erb b/app/views/news/show.html.erb index 4dfd8b1b224..9db18882e4a 100644 --- a/app/views/news/show.html.erb +++ b/app/views/news/show.html.erb @@ -31,9 +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: 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( diff --git a/lib/open_project/patches/primer_page_header_breadcrumb.rb b/lib/open_project/patches/primer_page_header_breadcrumb.rb index af6717b1260..5e3e220c4d5 100644 --- a/lib/open_project/patches/primer_page_header_breadcrumb.rb +++ b/lib/open_project/patches/primer_page_header_breadcrumb.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + #-- copyright # OpenProject is an open source project management software. # Copyright (C) the OpenProject GmbH @@ -30,7 +32,7 @@ module OpenProject module Patches module PrimerPageHeaderBreadcrumb def with_breadcrumbs(breadcrumbs, **) - super([{ href: home_path, text: helpers.organization_name}] + breadcrumbs, **) + super([{ href: home_path, text: helpers.organization_name }] + breadcrumbs, **) end end end diff --git a/modules/boards/app/views/boards/boards/index.html.erb b/modules/boards/app/views/boards/boards/index.html.erb index 524bb0cccfd..5a5a2e0a026 100644 --- a/modules/boards/app/views/boards/boards/index.html.erb +++ b/modules/boards/app/views/boards/boards/index.html.erb @@ -33,8 +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: 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 %> diff --git a/modules/boards/app/views/boards/boards/new.html.erb b/modules/boards/app/views/boards/boards/new.html.erb index da90c86177b..a8c21c9c24b 100644 --- a/modules/boards/app/views/boards/boards/new.html.erb +++ b/modules/boards/app/views/boards/boards/new.html.erb @@ -33,9 +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: 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 %> diff --git a/modules/calendar/app/views/calendar/calendars/index.html.erb b/modules/calendar/app/views/calendar/calendars/index.html.erb index 4d41239814d..f59568c776d 100644 --- a/modules/calendar/app/views/calendar/calendars/index.html.erb +++ b/modules/calendar/app/views/calendar/calendars/index.html.erb @@ -32,8 +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: 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 %> diff --git a/modules/meeting/app/components/meetings/header_component.rb b/modules/meeting/app/components/meetings/header_component.rb index aeb1534878f..5d6fa8d81ad 100644 --- a/modules/meeting/app/components/meetings/header_component.rb +++ b/modules/meeting/app/components/meetings/header_component.rb @@ -75,7 +75,7 @@ module Meetings def breadcrumb_items [ - *([{ href: project_overview_path(@project.id), text: @project.name }] if @project.present?), + ({ 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, diff --git a/modules/meeting/app/components/meetings/index_page_header_component.rb b/modules/meeting/app/components/meetings/index_page_header_component.rb index d61229b89b3..931a75ab07a 100644 --- a/modules/meeting/app/components/meetings/index_page_header_component.rb +++ b/modules/meeting/app/components/meetings/index_page_header_component.rb @@ -46,10 +46,12 @@ module Meetings end def breadcrumb_items - [*([{ 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) }, - current_breadcrumb_element] + [ + ({ 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) }, + current_breadcrumb_element + ].compact end def current_breadcrumb_element diff --git a/modules/meeting/app/components/recurring_meetings/show_page_header_component.rb b/modules/meeting/app/components/recurring_meetings/show_page_header_component.rb index 3636394d294..5813be8ffc1 100644 --- a/modules/meeting/app/components/recurring_meetings/show_page_header_component.rb +++ b/modules/meeting/app/components/recurring_meetings/show_page_header_component.rb @@ -83,10 +83,12 @@ module RecurringMeetings end def breadcrumb_items - [*([{ 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)] + [ + ({ 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 diff --git a/modules/meeting/app/views/recurring_meetings/new.html.erb b/modules/meeting/app/views/recurring_meetings/new.html.erb index b17a5736112..fe589a50de1 100644 --- a/modules/meeting/app/views/recurring_meetings/new.html.erb +++ b/modules/meeting/app/views/recurring_meetings/new.html.erb @@ -31,10 +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( - [*([{ 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)] + [ + ({ 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 %> diff --git a/modules/reporting/app/components/cost_reports/index_page_header_component.rb b/modules/reporting/app/components/cost_reports/index_page_header_component.rb index aeddab9323a..232a18dab84 100644 --- a/modules/reporting/app/components/cost_reports/index_page_header_component.rb +++ b/modules/reporting/app/components/cost_reports/index_page_header_component.rb @@ -44,10 +44,12 @@ module CostReports end def breadcrumb_items - [*([{ href: project_overview_path(@project.id), text: @project.name }] if @project.present?), - { href: url_for({ controller: "cost_reports", action: :index, project_id: @project }), - text: I18n.t(:cost_reports_title) }, - current_breadcrumb_element] + [ + ({ href: project_overview_path(@project.id), text: @project.name } if @project.present?), + { href: url_for({ controller: "cost_reports", action: :index, project_id: @project }), + text: I18n.t(:cost_reports_title) }, + current_breadcrumb_element + ].compact end def current_breadcrumb_element diff --git a/modules/team_planner/app/views/team_planner/team_planner/new.html.erb b/modules/team_planner/app/views/team_planner/team_planner/new.html.erb index e54051de008..36d5d6429b7 100644 --- a/modules/team_planner/app/views/team_planner/team_planner/new.html.erb +++ b/modules/team_planner/app/views/team_planner/team_planner/new.html.erb @@ -32,8 +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: 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 %> From a62ee876d7894a19f263efd87f42a2d0f1821cb4 Mon Sep 17 00:00:00 2001 From: Henriette Darge Date: Thu, 3 Jul 2025 15:48:30 +0200 Subject: [PATCH 3/9] Add a test for the breadcrumb behaviour --- spec/features/a11y/breadcrumb_spec.rb | 81 +++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 spec/features/a11y/breadcrumb_spec.rb diff --git a/spec/features/a11y/breadcrumb_spec.rb b/spec/features/a11y/breadcrumb_spec.rb new file mode 100644 index 00000000000..acd96abb747 --- /dev/null +++ b/spec/features/a11y/breadcrumb_spec.rb @@ -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", :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 project_path(project) + + 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" + + expect(page).to have_css ".PageHeader-parentLink", href: "/", text: "OpenProject" + end + end + end + + context "when being on an non-index page" do + it "does show the index page as mobile back link" do + visit project_path(project, { 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" + + expect(page).to have_css ".PageHeader-parentLink", href: "/projects", text: "Projects" + end + 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 From 9f9a74e4ef1930da9b6e77122cdd76e38f2580ee Mon Sep 17 00:00:00 2001 From: Henriette Darge Date: Fri, 4 Jul 2025 15:26:39 +0200 Subject: [PATCH 4/9] Exclude index pages from items used to generate the mobile back button --- .../members/index_page_header_component.rb | 13 +++++++++++-- .../notifications/index_page_header_component.rb | 6 +++++- .../projects/index_page_header_component.rb | 7 ++++++- .../settings/index_page_header_component.html.erb | 2 +- .../index_header_component.rb | 2 +- app/views/admin/index.html.erb | 2 +- .../settings/aggregation_settings/show.html.erb | 2 +- app/views/admin/settings/api_settings/show.html.erb | 2 +- .../settings/authentication_settings/show.html.erb | 2 +- .../admin/settings/general_settings/show.html.erb | 2 +- .../admin/settings/users_settings/show.html.erb | 2 +- .../settings/work_packages_general/show.html.erb | 2 +- .../working_days_and_hours_settings/show.html.erb | 2 +- app/views/my/account.html.erb | 2 +- app/views/wiki/index.html.erb | 2 +- .../app/views/admin/costs_settings/show.html.erb | 2 +- .../meetings/index_page_header_component.rb | 6 +++++- .../app/views/overviews/overviews/show.html.erb | 2 +- .../cost_reports/index_page_header_component.rb | 6 +----- .../views/storages/admin/storages/index.html.erb | 2 +- 20 files changed, 43 insertions(+), 25 deletions(-) diff --git a/app/components/members/index_page_header_component.rb b/app/components/members/index_page_header_component.rb index 18bd0019258..d13a3ef4498 100644 --- a/app/components/members/index_page_header_component.rb +++ b/app/components/members/index_page_header_component.rb @@ -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,6 +74,7 @@ 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| @@ -85,6 +86,14 @@ class Members::IndexPageHeaderComponent < ApplicationComponent 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 diff --git a/app/components/notifications/index_page_header_component.rb b/app/components/notifications/index_page_header_component.rb index 6d6eb0f7c16..d82bd0be5a2 100644 --- a/app/components/notifications/index_page_header_component.rb +++ b/app/components/notifications/index_page_header_component.rb @@ -46,7 +46,7 @@ module Notifications end def breadcrumb_items - [{ 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 @@ -73,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 diff --git a/app/components/projects/index_page_header_component.rb b/app/components/projects/index_page_header_component.rb index b0a202216b1..43474279bc1 100644 --- a/app/components/projects/index_page_header_component.rb +++ b/app/components/projects/index_page_header_component.rb @@ -112,7 +112,7 @@ class Projects::IndexPageHeaderComponent < ApplicationComponent def breadcrumb_items [ - { 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 @@ -135,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 } diff --git a/app/components/projects/settings/index_page_header_component.html.erb b/app/components/projects/settings/index_page_header_component.html.erb index 508c6416192..57ffb6b2482 100644 --- a/app/components/projects/settings/index_page_header_component.html.erb +++ b/app/components/projects/settings/index_page_header_component.html.erb @@ -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) ] ) diff --git a/app/components/settings/project_phase_definitions/index_header_component.rb b/app/components/settings/project_phase_definitions/index_header_component.rb index 2e20391173c..f0ffdd1984e 100644 --- a/app/components/settings/project_phase_definitions/index_header_component.rb +++ b/app/components/settings/project_phase_definitions/index_header_component.rb @@ -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 diff --git a/app/views/admin/index.html.erb b/app/views/admin/index.html.erb index 3333f06634a..b3f7956e902 100644 --- a/app/views/admin/index.html.erb +++ b/app/views/admin/index.html.erb @@ -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 diff --git a/app/views/admin/settings/aggregation_settings/show.html.erb b/app/views/admin/settings/aggregation_settings/show.html.erb index 279ee7ce5ce..14308e9ff81 100644 --- a/app/views/admin/settings/aggregation_settings/show.html.erb +++ b/app/views/admin/settings/aggregation_settings/show.html.erb @@ -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 diff --git a/app/views/admin/settings/api_settings/show.html.erb b/app/views/admin/settings/api_settings/show.html.erb index e118f8e9fe3..1eeeba35ca0 100644 --- a/app/views/admin/settings/api_settings/show.html.erb +++ b/app/views/admin/settings/api_settings/show.html.erb @@ -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 diff --git a/app/views/admin/settings/authentication_settings/show.html.erb b/app/views/admin/settings/authentication_settings/show.html.erb index d99a6730281..2b33e0ad750 100644 --- a/app/views/admin/settings/authentication_settings/show.html.erb +++ b/app/views/admin/settings/authentication_settings/show.html.erb @@ -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) diff --git a/app/views/admin/settings/general_settings/show.html.erb b/app/views/admin/settings/general_settings/show.html.erb index ed36df515ca..a17939c97de 100644 --- a/app/views/admin/settings/general_settings/show.html.erb +++ b/app/views/admin/settings/general_settings/show.html.erb @@ -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 diff --git a/app/views/admin/settings/users_settings/show.html.erb b/app/views/admin/settings/users_settings/show.html.erb index 7ca9d92401d..fece879f2ed 100644 --- a/app/views/admin/settings/users_settings/show.html.erb +++ b/app/views/admin/settings/users_settings/show.html.erb @@ -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 diff --git a/app/views/admin/settings/work_packages_general/show.html.erb b/app/views/admin/settings/work_packages_general/show.html.erb index 88b6713f3c4..b662cea990c 100644 --- a/app/views/admin/settings/work_packages_general/show.html.erb +++ b/app/views/admin/settings/work_packages_general/show.html.erb @@ -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 diff --git a/app/views/admin/settings/working_days_and_hours_settings/show.html.erb b/app/views/admin/settings/working_days_and_hours_settings/show.html.erb index 410b88269bc..ddebe53fef9 100644 --- a/app/views/admin/settings/working_days_and_hours_settings/show.html.erb +++ b/app/views/admin/settings/working_days_and_hours_settings/show.html.erb @@ -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 diff --git a/app/views/my/account.html.erb b/app/views/my/account.html.erb index 442d0ce72e2..fb93aaf021e 100644 --- a/app/views/my/account.html.erb +++ b/app/views/my/account.html.erb @@ -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 diff --git a/app/views/wiki/index.html.erb b/app/views/wiki/index.html.erb index c84bc9b26f0..5aa4aa5d36b 100644 --- a/app/views/wiki/index.html.erb +++ b/app/views/wiki/index.html.erb @@ -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) ] ) diff --git a/modules/costs/app/views/admin/costs_settings/show.html.erb b/modules/costs/app/views/admin/costs_settings/show.html.erb index f8472a3b89a..c3408c3e163 100644 --- a/modules/costs/app/views/admin/costs_settings/show.html.erb +++ b/modules/costs/app/views/admin/costs_settings/show.html.erb @@ -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) ] ) diff --git a/modules/meeting/app/components/meetings/index_page_header_component.rb b/modules/meeting/app/components/meetings/index_page_header_component.rb index 931a75ab07a..d223b8370d1 100644 --- a/modules/meeting/app/components/meetings/index_page_header_component.rb +++ b/modules/meeting/app/components/meetings/index_page_header_component.rb @@ -49,7 +49,7 @@ module Meetings [ ({ 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) }, + text: I18n.t(:label_meeting_plural), skip_for_mobile: first_menu_item? }, current_breadcrumb_element ].compact end @@ -81,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 diff --git a/modules/overviews/app/views/overviews/overviews/show.html.erb b/modules/overviews/app/views/overviews/overviews/show.html.erb index 1460a8e5842..c51821e4eee 100644 --- a/modules/overviews/app/views/overviews/overviews/show.html.erb +++ b/modules/overviews/app/views/overviews/overviews/show.html.erb @@ -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") ] ) diff --git a/modules/reporting/app/components/cost_reports/index_page_header_component.rb b/modules/reporting/app/components/cost_reports/index_page_header_component.rb index 232a18dab84..7b6fc9e582f 100644 --- a/modules/reporting/app/components/cost_reports/index_page_header_component.rb +++ b/modules/reporting/app/components/cost_reports/index_page_header_component.rb @@ -39,15 +39,11 @@ module CostReports @user = User.current end - def page_title - I18n.t(:label_meeting_plural) - end - def breadcrumb_items [ ({ href: project_overview_path(@project.id), text: @project.name } if @project.present?), { href: url_for({ controller: "cost_reports", action: :index, project_id: @project }), - text: I18n.t(:cost_reports_title) }, + text: I18n.t(:cost_reports_title), skip_for_mobile: current_section && current_section.header.present? }, current_breadcrumb_element ].compact end diff --git a/modules/storages/app/views/storages/admin/storages/index.html.erb b/modules/storages/app/views/storages/admin/storages/index.html.erb index 24da22ddbb9..e0cd0848a6b 100644 --- a/modules/storages/app/views/storages/admin/storages/index.html.erb +++ b/modules/storages/app/views/storages/admin/storages/index.html.erb @@ -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 %> From b09b89d3a8eac9ec4642a3004963da72df11dc80 Mon Sep 17 00:00:00 2001 From: Henriette Darge Date: Fri, 4 Jul 2025 15:37:35 +0200 Subject: [PATCH 5/9] Document new breadcrumb behavior --- lookbook/docs/components/page-header.md.erb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lookbook/docs/components/page-header.md.erb b/lookbook/docs/components/page-header.md.erb index a3614304ab8..a36b1f4d146 100644 --- a/lookbook/docs/components/page-header.md.erb +++ b/lookbook/docs/components/page-header.md.erb @@ -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. From 10abcbcb3ab12a35ef14b37855c45474a30aba31 Mon Sep 17 00:00:00 2001 From: Henriette Darge Date: Mon, 7 Jul 2025 09:14:15 +0200 Subject: [PATCH 6/9] Fix some breadcrumbs for better mobile behaviour --- app/components/members/index_page_header_component.rb | 1 + .../auth_saml/app/views/saml/providers/index.html.erb | 3 ++- .../cost_reports/index_page_header_component.rb | 9 +++++++-- modules/reporting/config/routes.rb | 2 +- 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/app/components/members/index_page_header_component.rb b/app/components/members/index_page_header_component.rb index d13a3ef4498..c53909b330a 100644 --- a/app/components/members/index_page_header_component.rb +++ b/app/components/members/index_page_header_component.rb @@ -81,6 +81,7 @@ class Members::IndexPageHeaderComponent < ApplicationComponent 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 diff --git a/modules/auth_saml/app/views/saml/providers/index.html.erb b/modules/auth_saml/app/views/saml/providers/index.html.erb index dc3e7ef4c82..d886e517c8e 100644 --- a/modules/auth_saml/app/views/saml/providers/index.html.erb +++ b/modules/auth_saml/app/views/saml/providers/index.html.erb @@ -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) %> diff --git a/modules/reporting/app/components/cost_reports/index_page_header_component.rb b/modules/reporting/app/components/cost_reports/index_page_header_component.rb index 7b6fc9e582f..1bcbe184c50 100644 --- a/modules/reporting/app/components/cost_reports/index_page_header_component.rb +++ b/modules/reporting/app/components/cost_reports/index_page_header_component.rb @@ -42,8 +42,9 @@ module CostReports def breadcrumb_items [ ({ href: project_overview_path(@project.id), text: @project.name } if @project.present?), - { href: url_for({ controller: "cost_reports", action: :index, project_id: @project }), - text: I18n.t(:cost_reports_title), skip_for_mobile: current_section && current_section.header.present? }, + { href: module_path, + text: I18n.t(:cost_reports_title), + skip_for_mobile: !current_section || current_section.header.blank? }, current_breadcrumb_element ].compact end @@ -69,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 diff --git a/modules/reporting/config/routes.rb b/modules/reporting/config/routes.rb index 9baf11ce1c8..9e4df2f9f55 100644 --- a/modules/reporting/config/routes.rb +++ b/modules/reporting/config/routes.rb @@ -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] From 446be5e9f8d9872b300e964a86b6c8385bf567e4 Mon Sep 17 00:00:00 2001 From: Henriette Darge Date: Mon, 7 Jul 2025 09:14:44 +0200 Subject: [PATCH 7/9] Bump primer to 0.70.3 --- Gemfile | 2 +- Gemfile.lock | 6 ++-- frontend/package-lock.json | 28 +++++++++---------- frontend/package.json | 4 +-- .../patches/primer_page_header_breadcrumb.rb | 2 +- 5 files changed, 21 insertions(+), 21 deletions(-) diff --git a/Gemfile b/Gemfile index 51912bf05a4..4aafb36ba58 100644 --- a/Gemfile +++ b/Gemfile @@ -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" diff --git a/Gemfile.lock b/Gemfile.lock index 95522f7485c..31cdfead08d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -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) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index f4e6b245331..9c6645cf29d 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -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", diff --git a/frontend/package.json b/frontend/package.json index e3d38173bd7..daaf4394c7a 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -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", diff --git a/lib/open_project/patches/primer_page_header_breadcrumb.rb b/lib/open_project/patches/primer_page_header_breadcrumb.rb index 5e3e220c4d5..5d06368178b 100644 --- a/lib/open_project/patches/primer_page_header_breadcrumb.rb +++ b/lib/open_project/patches/primer_page_header_breadcrumb.rb @@ -38,6 +38,6 @@ module OpenProject end end -OpenProject::Patches.patch_gem_version "openproject-primer_view_components", "0.70.2" do +OpenProject::Patches.patch_gem_version "openproject-primer_view_components", "0.70.3" do Primer::OpenProject::PageHeader.prepend OpenProject::Patches::PrimerPageHeaderBreadcrumb end From fa888b72645e41c5e03d3deca62cb991966d2d8c Mon Sep 17 00:00:00 2001 From: Henriette Darge Date: Mon, 7 Jul 2025 09:48:08 +0200 Subject: [PATCH 8/9] Hide mobile breadcrumb on home page --- app/views/homescreen/index.html.erb | 4 ++-- lib/open_project/patches/primer_page_header_breadcrumb.rb | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/app/views/homescreen/index.html.erb b/app/views/homescreen/index.html.erb index 8e25fc37da4..3d90bccce51 100644 --- a/app/views/homescreen/index.html.erb +++ b/app/views/homescreen/index.html.erb @@ -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([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") %> - + <%= op_icon(link[:icon]) %> <%= title %> diff --git a/lib/open_project/patches/primer_page_header_breadcrumb.rb b/lib/open_project/patches/primer_page_header_breadcrumb.rb index 5d06368178b..1a4bc3afc8b 100644 --- a/lib/open_project/patches/primer_page_header_breadcrumb.rb +++ b/lib/open_project/patches/primer_page_header_breadcrumb.rb @@ -31,8 +31,10 @@ module OpenProject module Patches module PrimerPageHeaderBreadcrumb - def with_breadcrumbs(breadcrumbs, **) - super([{ href: home_path, text: helpers.organization_name }] + breadcrumbs, **) + 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 From 73d3ec67cfd8026430b5686d7aadd78b453a3910 Mon Sep 17 00:00:00 2001 From: Henriette Darge Date: Tue, 8 Jul 2025 09:58:44 +0200 Subject: [PATCH 9/9] Fix breadcrumb spec --- spec/features/a11y/breadcrumb_spec.rb | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/spec/features/a11y/breadcrumb_spec.rb b/spec/features/a11y/breadcrumb_spec.rb index acd96abb747..f7dbff981cc 100644 --- a/spec/features/a11y/breadcrumb_spec.rb +++ b/spec/features/a11y/breadcrumb_spec.rb @@ -30,7 +30,7 @@ require "spec_helper" -RSpec.describe "Breadcrumbs", :js do +RSpec.describe "Breadcrumbs (#63777)", :js do let(:user) { create(:admin) } let(:project) { create(:project) } @@ -40,29 +40,29 @@ RSpec.describe "Breadcrumbs", :js do 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 project_path(project) + 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" - - expect(page).to have_css ".PageHeader-parentLink", 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 project_path(project, { query_id: "my" }) + 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" - - expect(page).to have_css ".PageHeader-parentLink", href: "/projects", text: "Projects" end + + expect(page).to have_link href: "/projects", text: "Projects", class: "PageHeader-parentLink", visible: :hidden end end