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