Skip to content

Commit 2814247

Browse files
committed
Replace with module API.
1 parent ee03527 commit 2814247

File tree

5 files changed

+76
-64
lines changed

5 files changed

+76
-64
lines changed

docs/config.md

-13
Original file line numberDiff line numberDiff line change
@@ -212,19 +212,6 @@ Starting with `branding`, the following subproperties are available:
212212
3. `auth_footer_links`: A list of links to add to the footer during login, registration, etc. Each entry must have a `text` and
213213
`url` property.
214214

215-
4. `title_template`: A template string that can be used to configure the title of the application when not viewing a room.
216-
5. `title_template_in_room`: A template string that can be used to configure the title of the application when viewing a room
217-
218-
#### `title_template` vars
219-
220-
- `$brand` The name of the web app, as configured by the `brand` config value.
221-
- `$room_name` The friendly name of a room. Only applicable to `title_template_in_room`.
222-
- `$status` The client's status, repesented as.
223-
- The notification count, when at least one room is unread.
224-
- "\*" when no rooms are unread, but notifications are not muted.
225-
- "Offline", when the client is offline.
226-
- "", when the client isn't logged in or notifications are muted.
227-
228215
`embedded_pages` can be configured as such:
229216

230217
1. `welcome_url`: A URL to an HTML page to show as a welcome page (landing on `#/welcome`). When not specified, the default

playwright/e2e/branding/title.spec.ts

-4
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,6 @@ test.describe("Test with custom branding", () => {
2828
test.use({
2929
config: {
3030
brand: "TestBrand",
31-
branding: {
32-
title_template: "TestingApp $ignoredParameter $brand $status $ignoredParameter",
33-
title_template_in_room: "TestingApp $brand $status $room_name $ignoredParameter",
34-
},
3531
},
3632
});
3733
test("Shows custom branding when showing the home page", async ({ pageWithCredentials: page }) => {

src/IConfigOptions.ts

-2
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,6 @@ export interface IConfigOptions {
5050
welcome_background_url?: string | string[]; // chosen at random if array
5151
auth_header_logo_url?: string;
5252
auth_footer_links?: { text: string; url: string }[];
53-
title_template?: string;
54-
title_template_in_room?: string;
5553
};
5654

5755
force_verification?: boolean; // if true, users must verify new logins

src/components/structures/MatrixChat.tsx

+52-45
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ import { ConfirmSessionLockTheftView } from "./auth/ConfirmSessionLockTheftView"
133133
import { LoginSplashView } from "./auth/LoginSplashView";
134134
import { cleanUpDraftsIfRequired } from "../../DraftCleaner";
135135
import { InitialCryptoSetupStore } from "../../stores/InitialCryptoSetupStore";
136+
import { AppTitleContext } from "@matrix-org/react-sdk-module-api/lib/lifecycles/BrandingExtensions";
136137

137138
// legacy export
138139
export { default as Views } from "../../Views";
@@ -225,18 +226,16 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
225226
private tokenLogin?: boolean;
226227
// What to focus on next component update, if anything
227228
private focusNext: FocusNextType;
228-
private subTitleStatus: string;
229229
private prevWindowWidth: number;
230230

231-
private readonly titleTemplate: string;
232-
private readonly titleTemplateInRoom: string;
233-
234231
private readonly loggedInView = createRef<LoggedInViewType>();
235232
private dispatcherRef?: string;
236233
private themeWatcher?: ThemeWatcher;
237234
private fontWatcher?: FontWatcher;
238235
private readonly stores: SdkContextClass;
239236

237+
private subtitleContext?: {unreadNotificationCount: number, userNotificationLevel: NotificationLevel, syncState: SyncState};
238+
240239
public constructor(props: IProps) {
241240
super(props);
242241
this.stores = SdkContextClass.instance;
@@ -280,13 +279,6 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
280279
}
281280

282281
this.prevWindowWidth = UIStore.instance.windowWidth || 1000;
283-
284-
// object field used for tracking the status info appended to the title tag.
285-
// we don't do it as react state as i'm scared about triggering needless react refreshes.
286-
this.subTitleStatus = "";
287-
288-
this.titleTemplate = props.config.branding?.title_template ?? "$brand $status";
289-
this.titleTemplateInRoom = props.config.branding?.title_template_in_room ?? "$brand $status | $room_name";
290282
}
291283

292284
/**
@@ -1112,7 +1104,6 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
11121104
}
11131105
this.setStateForNewView({
11141106
view: Views.WELCOME,
1115-
currentRoomId: null,
11161107
});
11171108
this.notifyNewScreen("welcome");
11181109
ThemeController.isLogin = true;
@@ -1122,7 +1113,6 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
11221113
private viewLogin(otherState?: any): void {
11231114
this.setStateForNewView({
11241115
view: Views.LOGIN,
1125-
currentRoomId: null,
11261116
...otherState,
11271117
});
11281118
this.notifyNewScreen("login");
@@ -1490,7 +1480,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
14901480
collapseLhs: false,
14911481
currentRoomId: null,
14921482
});
1493-
this.subTitleStatus = "";
1483+
this.subtitleContext = undefined;
14941484
this.setPageSubtitle();
14951485
this.stores.onLoggedOut();
14961486
}
@@ -1506,7 +1496,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
15061496
collapseLhs: false,
15071497
currentRoomId: null,
15081498
});
1509-
this.subTitleStatus = "";
1499+
this.subtitleContext = undefined;
15101500
this.setPageSubtitle();
15111501
}
15121502

@@ -1958,33 +1948,56 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
19581948
}
19591949

19601950
private setPageSubtitle(): void {
1961-
const params: {
1962-
$brand: string;
1963-
$status: string;
1964-
$room_name: string | undefined;
1965-
} = {
1966-
$brand: SdkConfig.get().brand,
1967-
$status: this.subTitleStatus,
1968-
$room_name: undefined,
1951+
const extraContext = this.subtitleContext;
1952+
let context: AppTitleContext = {
1953+
brand: SdkConfig.get().brand,
1954+
syncError: extraContext?.syncState === SyncState.Error,
19691955
};
19701956

1971-
if (this.state.currentRoomId) {
1972-
const client = MatrixClientPeg.get();
1973-
const room = client?.getRoom(this.state.currentRoomId);
1974-
if (room) {
1975-
params.$room_name = room.name;
1957+
if (extraContext) {
1958+
if (this.state.currentRoomId) {
1959+
const client = MatrixClientPeg.get();
1960+
const room = client?.getRoom(this.state.currentRoomId);
1961+
context = {
1962+
...context,
1963+
roomId: this.state.currentRoomId,
1964+
roomName: room?.name,
1965+
notificationsMuted: extraContext.userNotificationLevel < NotificationLevel.Activity,
1966+
unreadNotificationCount: extraContext.unreadNotificationCount,
1967+
};
19761968
}
19771969
}
19781970

1979-
const titleTemplate = params.$room_name ? this.titleTemplateInRoom : this.titleTemplate;
1971+
const moduleTitle = ModuleRunner.instance.extensions.branding?.getAppTitle(context);
1972+
if (moduleTitle) {
1973+
if (document.title !== moduleTitle) {
1974+
document.title = moduleTitle;
1975+
}
1976+
return;
1977+
}
19801978

1981-
const title = Object.entries(params).reduce(
1982-
(title: string, [key, value]) => title.replaceAll(key, (value ?? "").replaceAll("$", "$_DLR$")),
1983-
titleTemplate,
1984-
);
1979+
let subtitle = "";
1980+
if (context?.syncError) {
1981+
subtitle += `[${_t("common|offline")}] `;
1982+
}
1983+
if ('unreadNotificationCount' in context && context.unreadNotificationCount > 0) {
1984+
subtitle += `[${context.unreadNotificationCount}]`;
1985+
} else if ('notificationsMuted' in context && !context.notificationsMuted) {
1986+
subtitle += `*`;
1987+
}
1988+
1989+
if ('roomId' in context && context.roomId) {
1990+
if (context.roomName) {
1991+
subtitle = `${subtitle} | ${context.roomName}`;
1992+
}
1993+
} else {
1994+
subtitle = subtitle;
1995+
}
1996+
1997+
const title = `${SdkConfig.get().brand} ${subtitle}`;
19851998

19861999
if (document.title !== title) {
1987-
document.title = title.replaceAll("$_DLR$", "$");
2000+
document.title = title;
19882001
}
19892002
}
19902003

@@ -1995,17 +2008,11 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
19952008
PlatformPeg.get()!.setErrorStatus(state === SyncState.Error);
19962009
PlatformPeg.get()!.setNotificationCount(numUnreadRooms);
19972010
}
1998-
1999-
this.subTitleStatus = "";
2000-
if (state === SyncState.Error) {
2001-
this.subTitleStatus += `[${_t("common|offline")}] `;
2002-
}
2003-
if (numUnreadRooms > 0) {
2004-
this.subTitleStatus += `[${numUnreadRooms}]`;
2005-
} else if (notificationState.level >= NotificationLevel.Activity) {
2006-
this.subTitleStatus += `*`;
2007-
}
2008-
2011+
this.subtitleContext = {
2012+
syncState: state,
2013+
userNotificationLevel: notificationState.level,
2014+
unreadNotificationCount: numUnreadRooms,
2015+
};
20092016
this.setPageSubtitle();
20102017
};
20112018

src/modules/ModuleRunner.ts

+24
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ import {
1717
DefaultExperimentalExtensions,
1818
ProvideExperimentalExtensions,
1919
} from "@matrix-org/react-sdk-module-api/lib/lifecycles/ExperimentalExtensions";
20+
import {
21+
ProvideBrandingExtensions,
22+
} from "@matrix-org/react-sdk-module-api/lib/lifecycles/BrandingExtensions";
23+
2024

2125
import { AppModule } from "./AppModule";
2226
import { ModuleFactory } from "./ModuleFactory";
@@ -30,6 +34,7 @@ class ExtensionsManager {
3034
// Private backing fields for extensions
3135
private cryptoSetupExtension: ProvideCryptoSetupExtensions;
3236
private experimentalExtension: ProvideExperimentalExtensions;
37+
private brandingExtension?: ProvideBrandingExtensions;
3338

3439
/** `true` if `cryptoSetupExtension` is the default implementation; `false` if it is implemented by a module. */
3540
private hasDefaultCryptoSetupExtension = true;
@@ -67,6 +72,15 @@ class ExtensionsManager {
6772
return this.experimentalExtension;
6873
}
6974

75+
/**
76+
* Provides branding extension.
77+
*
78+
* @returns The registered extension. If no module provides this extension, undefined is returned..
79+
*/
80+
public get branding(): ProvideBrandingExtensions|undefined {
81+
return this.brandingExtension;
82+
}
83+
7084
/**
7185
* Add any extensions provided by the module.
7286
*
@@ -100,6 +114,16 @@ class ExtensionsManager {
100114
);
101115
}
102116
}
117+
118+
if (runtimeModule.extensions?.branding) {
119+
if (!this.brandingExtension) {
120+
this.brandingExtension = runtimeModule.extensions?.branding;
121+
} else {
122+
throw new Error(
123+
`adding experimental branding implementation from module ${runtimeModule.moduleName} but an implementation was already provided.`,
124+
);
125+
}
126+
}
103127
}
104128
}
105129

0 commit comments

Comments
 (0)