implement BrandApi and it's titleRenderer

This commit is contained in:
David Langley 2026-04-09 20:47:35 +01:00
parent e53a148da2
commit 883fb622e6
3 changed files with 62 additions and 8 deletions

View File

@ -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>();
@ -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" : "";
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;
@ -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 += `*`;
}

View File

@ -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;
@ -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();

View File

@ -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);
}
}