Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 33 additions & 8 deletions apps/web/src/components/structures/MatrixChat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,9 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
// What to focus on next component update, if anything
private focusNext: FocusNextType;
private subTitleStatus: string;
private notificationCount = 0;
private hasActivity = false;
private errorDidOccur = false;
private prevWindowWidth: number;

private readonly loggedInView = createRef<LoggedInViewType>();
Expand Down Expand Up @@ -2081,17 +2084,36 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
}

private setPageSubtitle(subtitle = ""): void {
if (this.state.currentRoomId) {
const client = MatrixClientPeg.get();
const room = client?.getRoom(this.state.currentRoomId);
if (room) {
subtitle = `${this.subTitleStatus} | ${room.name} ${subtitle}`;
const brand = SdkConfig.get().brand;
const room = this.state.currentRoomId
? MatrixClientPeg.get()?.getRoom(this.state.currentRoomId)
: undefined;

const customTitle = ModuleApi.instance.brand.renderTitle({
brand,
notificationCount: this.notificationCount || undefined,
hasActivity: this.hasActivity || undefined,
statusText: this.errorDidOccur ? _t("common|offline") : undefined,
roomId: this.state.currentRoomId ?? undefined,
roomName: room?.name,
});

if (customTitle !== undefined) {
if (document.title !== customTitle) {
document.title = customTitle;
}
return;
}

const elementSuffix = brand !== "Element" ? " | Element" : "";
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this would make Nightly be Element Nightly | Element, no?


if (room) {
subtitle = `${this.subTitleStatus} | ${room.name} ${subtitle}`;
} else {
subtitle = `${this.subTitleStatus} ${subtitle}`;
}

const title = `${SdkConfig.get().brand} ${subtitle}`;
const title = `${brand}${elementSuffix} ${subtitle}`;

if (document.title !== title) {
document.title = title;
Expand All @@ -2107,12 +2129,15 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
}

this.subTitleStatus = "";
if (state === SyncState.Error) {
this.errorDidOccur = state === SyncState.Error;
if (this.errorDidOccur) {
this.subTitleStatus += `[${_t("common|offline")}] `;
}
this.notificationCount = numUnreadRooms;
this.hasActivity = notificationState.level >= NotificationLevel.Activity;
if (numUnreadRooms > 0) {
this.subTitleStatus += `[${numUnreadRooms}]`;
} else if (notificationState.level >= NotificationLevel.Activity) {
} else if (this.hasActivity) {
this.subTitleStatus += `*`;
}

Expand Down
2 changes: 2 additions & 0 deletions apps/web/src/modules/Api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import { StoresApi } from "./StoresApi.ts";
import { WidgetLifecycleApi } from "./WidgetLifecycleApi.ts";
import { WidgetApi } from "./WidgetApi.ts";
import { CustomisationsApi } from "./customisationsApi.ts";
import { ElementWebBrandApi } from "./BrandApi.ts";

const legacyCustomisationsFactory = <T extends object>(baseCustomisations: T) => {
let used = false;
Expand Down Expand Up @@ -87,6 +88,7 @@ export class ModuleApi implements Api {
public readonly i18n = new I18nApi();
public readonly customComponents = new CustomComponentsApi();
public readonly customisations = new CustomisationsApi();
public readonly brand = new ElementWebBrandApi();
public readonly extras = new ElementWebExtrasApi();
public readonly builtins = new ElementWebBuiltinsApi();
public readonly widgetLifecycle = new WidgetLifecycleApi();
Expand Down
27 changes: 27 additions & 0 deletions apps/web/src/modules/BrandApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
Copyright 2026 Element Creations Ltd.

SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/

import { type BrandApi, type TitleRenderFunction, type TitleRenderOptions } from "@element-hq/element-web-module-api";

export class ElementWebBrandApi implements BrandApi {
private titleRenderer: TitleRenderFunction | undefined;

public registerTitleRenderer(renderFunction: TitleRenderFunction): void {
if (this.titleRenderer) {
throw new Error("A title renderer has already been registered by another module");
}
this.titleRenderer = renderFunction;
}

/**
* Render the window title using the registered renderer, or return undefined
* to fall back to the default title logic.
*/
public renderTitle(opts: TitleRenderOptions): string | undefined {
return this.titleRenderer?.(opts);
}
}
Loading