diff --git a/app/views/layouts/_common_head.html.erb b/app/views/layouts/_common_head.html.erb index ff5c33451036..1e895933c5f2 100644 --- a/app/views/layouts/_common_head.html.erb +++ b/app/views/layouts/_common_head.html.erb @@ -4,6 +4,7 @@ <%= appsignal_frontend_tag %> <% relative_url_root = OpenProject::Configuration['rails_relative_url_root'] || '' %> + <% if @project %> diff --git a/app/views/news/index.html.erb b/app/views/news/index.html.erb index 0ac67a4077d0..6b18570a3df2 100644 --- a/app/views/news/index.html.erb +++ b/app/views/news/index.html.erb @@ -26,6 +26,9 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. See COPYRIGHT and LICENSE files for more details. ++#%> + +<% html_title t(:label_news_plural) %> + <% managable = User.current.allowed_in_project?(:manage_news, @project) %> diff --git a/frontend/src/app/core/html/op-title.service.ts b/frontend/src/app/core/html/op-title.service.ts index c4b1f0bfb821..2d8e8cd48338 100644 --- a/frontend/src/app/core/html/op-title.service.ts +++ b/frontend/src/app/core/html/op-title.service.ts @@ -6,28 +6,29 @@ const titlePartsSeparator = ' | '; @Injectable({ providedIn: 'root' }) export class OpTitleService { constructor(private titleService:Title) { - } public get current():string { return this.titleService.getTitle(); } + public get base():string { + const appTitle = document.querySelector('meta[name=app_title]') as HTMLMetaElement; + return appTitle.content; + } + public get titleParts():string[] { return this.current.split(titlePartsSeparator); } public setFirstPart(value:string) { - const parts = this.titleParts; - parts[0] = value; - - this.titleService.setTitle(parts.join(titlePartsSeparator)); - } - - public prependFirstPart(value:string):void { - const parts = this.titleParts; - parts.unshift(value); - - this.titleService.setTitle(parts.join(titlePartsSeparator)); + if (this.current.includes(this.base) && this.current.includes(titlePartsSeparator)) { + const parts = this.titleParts; + parts[0] = value; + this.titleService.setTitle(parts.join(titlePartsSeparator)); + } else { + const newTitle = [value, this.base].join(titlePartsSeparator); + this.titleService.setTitle(newTitle); + } } } diff --git a/frontend/src/app/shared/components/grids/grid/page/grid-page.component.ts b/frontend/src/app/shared/components/grids/grid/page/grid-page.component.ts index 0267279a9176..fe6a42d70ab2 100644 --- a/frontend/src/app/shared/components/grids/grid/page/grid-page.component.ts +++ b/frontend/src/app/shared/components/grids/grid/page/grid-page.component.ts @@ -1,6 +1,5 @@ import { ChangeDetectorRef, Directive, OnDestroy, OnInit, Renderer2 } from '@angular/core'; import { I18nService } from 'core-app/core/i18n/i18n.service'; -import { Title } from '@angular/platform-browser'; import { GridInitializationService } from 'core-app/shared/components/grids/grid/initialization.service'; import { PathHelperService } from 'core-app/core/path-helper/path-helper.service'; import { GridResource } from 'core-app/features/hal/resources/grid-resource'; @@ -8,6 +7,7 @@ import { GridAddWidgetService } from 'core-app/shared/components/grids/grid/add- import { GridAreaService } from 'core-app/shared/components/grids/grid/area.service'; import { CurrentProjectService } from 'core-app/core/current-project/current-project.service'; import { ConfigurationService } from 'core-app/core/config/configuration.service'; +import { OpTitleService } from 'core-app/core/html/op-title.service'; @Directive() export abstract class GridPageComponent implements OnInit, OnDestroy { @@ -25,7 +25,7 @@ export abstract class GridPageComponent implements OnInit, OnDestroy { readonly currentProject:CurrentProjectService, readonly i18n:I18nService, readonly cdRef:ChangeDetectorRef, - readonly title:Title, + readonly title:OpTitleService, readonly addWidget:GridAddWidgetService, readonly renderer:Renderer2, readonly areas:GridAreaService, @@ -57,7 +57,7 @@ export abstract class GridPageComponent implements OnInit, OnDestroy { } private setHtmlTitle() { - this.title.setTitle(this.text.html_title); + this.title.setFirstPart(this.text.html_title); } protected abstract i18nNamespace():string; diff --git a/modules/my_page/spec/features/my/my_page_spec.rb b/modules/my_page/spec/features/my/my_page_spec.rb index 1e574feec951..88ebd892eb3c 100644 --- a/modules/my_page/spec/features/my/my_page_spec.rb +++ b/modules/my_page/spec/features/my/my_page_spec.rb @@ -54,6 +54,7 @@ let(:my_page) do Pages::My::Page.new end + let(:global_html_title) { Components::HtmlTitle.new } before do login_as user @@ -98,6 +99,8 @@ def find_area(name) end it "renders the default view, allows altering and saving" do + global_html_title.expect_first_segment "My page" + # Waits for the default view to be created my_page.expect_toast(message: "Successful update") diff --git a/spec/features/homescreen/index_spec.rb b/spec/features/homescreen/index_spec.rb index af7e97e83c7c..b4d7a6397d67 100644 --- a/spec/features/homescreen/index_spec.rb +++ b/spec/features/homescreen/index_spec.rb @@ -33,6 +33,7 @@ let(:user) { build_stubbed(:user) } let!(:project) { create(:public_project, identifier: "public-project") } let(:general_settings_page) { Pages::Admin::SystemSettings::General.new } + let(:global_html_title) { Components::HtmlTitle.new } it "is reachable by the global menu" do login_as user diff --git a/spec/features/news/creation_and_commenting_spec.rb b/spec/features/news/creation_and_commenting_spec.rb index 5d6616f8ccde..9b8959736cc8 100644 --- a/spec/features/news/creation_and_commenting_spec.rb +++ b/spec/features/news/creation_and_commenting_spec.rb @@ -37,6 +37,7 @@ build(:notification_setting, news_added: true, news_commented: true) ]) end + let(:global_html_title) { Components::HtmlTitle.new(project) } current_user do create(:user, @@ -47,6 +48,7 @@ it "allows creating new and commenting it all of which will result in notifications and mails" do visit project_news_index_path(project) + global_html_title.expect_first_segment "News" page.find_test_selector("add-news-button").click diff --git a/spec/features/notifications/notification_center/split_screen_spec.rb b/spec/features/notifications/notification_center/split_screen_spec.rb index 1749a2e354d8..36d846d0ca4a 100644 --- a/spec/features/notifications/notification_center/split_screen_spec.rb +++ b/spec/features/notifications/notification_center/split_screen_spec.rb @@ -98,7 +98,7 @@ center.click_item notification sleep 0.25 # Wait after the item has been clicked to not be interpreted as a double click center.mark_notification_as_read notification - global_html_title.expect_first_segment second_title + global_html_title.expect_first_segment "#{second_title} | Notifications" # After making all notifications as read, html title should show the base route center.mark_notification_as_read second_notification diff --git a/spec/features/work_packages/navigation_spec.rb b/spec/features/work_packages/navigation_spec.rb index 7e6240e7d7b7..2676991e3d6b 100644 --- a/spec/features/work_packages/navigation_spec.rb +++ b/spec/features/work_packages/navigation_spec.rb @@ -62,7 +62,7 @@ global_work_packages.visit! global_work_packages.expect_work_package_listed(work_package) - global_html_title.expect_first_segment "All open" + global_html_title.expect_first_segment "All open | Work Packages" # open details pane for work package @@ -70,14 +70,14 @@ split_work_package.expect_subject split_work_package.expect_current_path - global_html_title.expect_first_segment wp_title_segment + global_html_title.expect_first_segment "#{wp_title_segment} | Work Packages" # Go to full screen by double click full_work_package = global_work_packages.open_full_screen_by_doubleclick(work_package) full_work_package.expect_subject full_work_package.expect_current_path - global_html_title.expect_first_segment wp_title_segment + global_html_title.expect_first_segment "#{wp_title_segment} | Work Packages" # deep link work package details pane @@ -97,17 +97,17 @@ project_work_packages.visit! project_work_packages.expect_work_package_listed(work_package) - project_html_title.expect_first_segment "All open" + project_html_title.expect_first_segment "All open | Work Packages" # Visit query with project wp project_work_packages.visit_query query project_work_packages.expect_work_package_listed(work_package) - project_html_title.expect_first_segment "My fancy query" + project_html_title.expect_first_segment "My fancy query | Work Packages" # Go back to work packages without query page.execute_script("window.history.back()") project_work_packages.expect_work_package_listed(work_package) - project_html_title.expect_first_segment "All open" + project_html_title.expect_first_segment "All open | Work Packages" # open project work package details pane @@ -115,19 +115,19 @@ split_project_work_package.expect_subject split_project_work_package.expect_current_path - project_html_title.expect_first_segment wp_title_segment + project_html_title.expect_first_segment "#{wp_title_segment} | Work Packages" # open work package full screen by button full_work_package = split_project_work_package.switch_to_fullscreen full_work_package.expect_subject expect(page).to have_current_path project_work_package_path(project, work_package, "activity") - project_html_title.expect_first_segment wp_title_segment + project_html_title.expect_first_segment "#{wp_title_segment} | Work Packages" # Switch tabs full_work_package.switch_to_tab tab: :relations expect(page).to have_current_path project_work_package_path(project, work_package, "relations") - project_html_title.expect_first_segment wp_title_segment + project_html_title.expect_first_segment "#{wp_title_segment} | Work Packages" # Back to split screen using the button full_work_package.go_back