diff --git a/Gemfile b/Gemfile
index de1d063606d..51732cc96cb 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 7ca8bb789f9..7458df2a887 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/app/components/members/index_page_header_component.rb b/app/components/members/index_page_header_component.rb
index 18bd0019258..c53909b330a 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,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
diff --git a/app/components/notifications/index_page_header_component.rb b/app/components/notifications/index_page_header_component.rb
index dcb1f9fd0d8..d82bd0be5a2 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"), 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
diff --git a/app/components/projects/index_page_header_component.rb b/app/components/projects/index_page_header_component.rb
index 2e6fff5186a..43474279bc1 100644
--- a/app/components/projects/index_page_header_component.rb
+++ b/app/components/projects/index_page_header_component.rb
@@ -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 }
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/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..766b00ecdba 100644
--- a/app/views/activities/index.html.erb
+++ b/app/views/activities/index.html.erb
@@ -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
%>
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/homescreen/index.html.erb b/app/views/homescreen/index.html.erb
index 0354aee8f66..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([{ 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") %>
-
+
<%= op_icon(link[:icon]) %>
<%= title %>
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/news/edit.html.erb b/app/views/news/edit.html.erb
index 49127a8417e..eff11a9e565 100644
--- a/app/views/news/edit.html.erb
+++ b/app/views/news/edit.html.erb
@@ -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
%>
diff --git a/app/views/news/index.html.erb b/app/views/news/index.html.erb
index 300d7425694..9f391574cc6 100644
--- a/app/views/news/index.html.erb
+++ b/app/views/news/index.html.erb
@@ -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
%>
diff --git a/app/views/news/show.html.erb b/app/views/news/show.html.erb
index 8dd081cbb25..9db18882e4a 100644
--- a/app/views/news/show.html.erb
+++ b/app/views/news/show.html.erb
@@ -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(
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/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/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/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..1a4bc3afc8b
--- /dev/null
+++ b/lib/open_project/patches/primer_page_header_breadcrumb.rb
@@ -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
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.
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/boards/app/views/boards/boards/index.html.erb b/modules/boards/app/views/boards/boards/index.html.erb
index 75e4440e778..5a5a2e0a026 100644
--- a/modules/boards/app/views/boards/boards/index.html.erb
+++ b/modules/boards/app/views/boards/boards/index.html.erb
@@ -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
%>
diff --git a/modules/boards/app/views/boards/boards/new.html.erb b/modules/boards/app/views/boards/boards/new.html.erb
index c9a52fad09d..a8c21c9c24b 100644
--- a/modules/boards/app/views/boards/boards/new.html.erb
+++ b/modules/boards/app/views/boards/boards/new.html.erb
@@ -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
%>
diff --git a/modules/calendar/app/views/calendar/calendars/index.html.erb b/modules/calendar/app/views/calendar/calendars/index.html.erb
index cf97456f90d..f59568c776d 100644
--- a/modules/calendar/app/views/calendar/calendars/index.html.erb
+++ b/modules/calendar/app/views/calendar/calendars/index.html.erb
@@ -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
%>
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/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/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..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
[
- 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..d223b8370d1 100644
--- a/modules/meeting/app/components/meetings/index_page_header_component.rb
+++ b/modules/meeting/app/components/meetings/index_page_header_component.rb
@@ -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
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..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,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
diff --git a/modules/meeting/app/views/recurring_meetings/new.html.erb b/modules/meeting/app/views/recurring_meetings/new.html.erb
index 0784a8ebd36..fe589a50de1 100644
--- a/modules/meeting/app/views/recurring_meetings/new.html.erb
+++ b/modules/meeting/app/views/recurring_meetings/new.html.erb
@@ -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
%>
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/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 e9b137d3ff8..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
@@ -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
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]
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 %>
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..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,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
%>
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
%>
diff --git a/spec/features/a11y/breadcrumb_spec.rb b/spec/features/a11y/breadcrumb_spec.rb
new file mode 100644
index 00000000000..f7dbff981cc
--- /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 (#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