From c02163bf55fcc7bce0b44d399d6c12ad7574eda3 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 14 Oct 2025 15:16:06 +0100 Subject: [PATCH 1/7] Additions to module API --- src/PosthogTrackers.ts | 6 ++-- src/components/structures/LoggedInView.tsx | 38 +++++++++++++++------- 2 files changed, 29 insertions(+), 15 deletions(-) diff --git a/src/PosthogTrackers.ts b/src/PosthogTrackers.ts index 5eddeecb2ae..ae67b5f378e 100644 --- a/src/PosthogTrackers.ts +++ b/src/PosthogTrackers.ts @@ -31,7 +31,7 @@ const notLoggedInMap: Record, ScreenName> = { [Views.LOCK_STOLEN]: "SessionLockStolen", }; -const loggedInPageTypeMap: Record = { +const loggedInPageTypeMap: Record = { [PageType.HomePage]: "Home", [PageType.RoomView]: "Room", [PageType.UserView]: "User", @@ -48,10 +48,10 @@ export default class PosthogTrackers { } private view: Views = Views.LOADING; - private pageType?: PageType; + private pageType?: PageType | string; private override?: ScreenName; - public trackPageChange(view: Views, pageType: PageType | undefined, durationMs: number): void { + public trackPageChange(view: Views, pageType: PageType | string | undefined, durationMs: number): void { this.view = view; this.pageType = pageType; if (this.override) return; diff --git a/src/components/structures/LoggedInView.tsx b/src/components/structures/LoggedInView.tsx index 907e40dede1..493bfdee505 100644 --- a/src/components/structures/LoggedInView.tsx +++ b/src/components/structures/LoggedInView.tsx @@ -69,6 +69,7 @@ import { type ConfigOptions } from "../../SdkConfig"; import { MatrixClientContextProvider } from "./MatrixClientContextProvider"; import { Landmark, LandmarkNavigation } from "../../accessibility/LandmarkNavigation"; import { SDKContext } from "../../contexts/SDKContext.ts"; +import ModuleApi from "../../modules/Api.ts"; // We need to fetch each pinned message individually (if we don't already have it) // so each pinned message may trigger a request. Limit the number per room for sanity. @@ -679,6 +680,10 @@ class LoggedInView extends React.Component { public render(): React.ReactNode { let pageElement; + const moduleRenderer = this.props.page_type + ? ModuleApi.navigation.locationRenderers.get(this.props.page_type) + : undefined; + switch (this.props.page_type) { case PageTypes.RoomView: pageElement = ( @@ -705,6 +710,13 @@ class LoggedInView extends React.Component { ); } break; + default: { + if (moduleRenderer) { + pageElement = moduleRenderer(); + } else { + console.warn(`Couldn't render page type "${this.props.page_type}"`); + } + } } const wrapperClasses = classNames({ @@ -746,20 +758,22 @@ class LoggedInView extends React.Component { )} {!useNewRoomList && } -
- -
+ {!moduleRenderer && ( +
+ +
+ )} - + {!moduleRenderer && )
{pageElement}
From d23223c836c9772d500ebd2f57e0360210387970 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 14 Oct 2025 15:24:42 +0100 Subject: [PATCH 2/7] right kind of bracket --- src/components/structures/LoggedInView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/structures/LoggedInView.tsx b/src/components/structures/LoggedInView.tsx index 493bfdee505..009ef704b2e 100644 --- a/src/components/structures/LoggedInView.tsx +++ b/src/components/structures/LoggedInView.tsx @@ -773,7 +773,7 @@ class LoggedInView extends React.Component { )} - {!moduleRenderer && ) + {!moduleRenderer && }
{pageElement}
From 31410fe4ef0b9b159d2d3453a20c703a49dbe905 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 14 Oct 2025 15:31:42 +0100 Subject: [PATCH 3/7] Add actual module api bits --- src/modules/Api.ts | 2 ++ src/modules/Navigation.ts | 8 +++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/modules/Api.ts b/src/modules/Api.ts index 1f72784bd66..66aed735832 100644 --- a/src/modules/Api.ts +++ b/src/modules/Api.ts @@ -26,6 +26,7 @@ import { WatchableProfile } from "./Profile.ts"; import { NavigationApi } from "./Navigation.ts"; import { openDialog } from "./Dialog.tsx"; import { overwriteAccountAuth } from "./Auth.ts"; +import { ElementWebExtrasApi } from "./ExtrasApi.ts"; const legacyCustomisationsFactory = (baseCustomisations: T) => { let used = false; @@ -69,6 +70,7 @@ class ModuleApi implements Api { public readonly config = new ConfigApi(); public readonly i18n = new I18nApi(); public readonly customComponents = new CustomComponentsApi(); + public readonly extras = new ElementWebExtrasApi(); public readonly rootNode = document.getElementById("matrixchat")!; public createRoot(element: Element): Root { diff --git a/src/modules/Navigation.ts b/src/modules/Navigation.ts index 0e7724727d3..52bdb5aee9b 100644 --- a/src/modules/Navigation.ts +++ b/src/modules/Navigation.ts @@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com Please see LICENSE files in the repository root for full details. */ -import { type NavigationApi as INavigationApi } from "@element-hq/element-web-module-api"; +import { type LocationRenderFunction, type NavigationApi as INavigationApi } from "@element-hq/element-web-module-api"; import { navigateToPermalink } from "../utils/permalinks/navigator.ts"; import { parsePermalink } from "../utils/permalinks/Permalinks.ts"; @@ -14,6 +14,8 @@ import { Action } from "../dispatcher/actions.ts"; import type { ViewRoomPayload } from "../dispatcher/payloads/ViewRoomPayload.ts"; export class NavigationApi implements INavigationApi { + public locationRenderers = new Map(); + public async toMatrixToLink(link: string, join = false): Promise { navigateToPermalink(link); @@ -38,4 +40,8 @@ export class NavigationApi implements INavigationApi { } } } + + public registerLocationRenderer(path: string, renderer: LocationRenderFunction): void { + this.locationRenderers.set(path, renderer); + } } From 494ba59aa1274458b18204483364b329fc54ec54 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 14 Oct 2025 15:34:37 +0100 Subject: [PATCH 4/7] and new files --- src/modules/BuiltinsApi.ts | 16 ++++++++++++++++ src/modules/ExtrasApi.ts | 16 ++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 src/modules/BuiltinsApi.ts create mode 100644 src/modules/ExtrasApi.ts diff --git a/src/modules/BuiltinsApi.ts b/src/modules/BuiltinsApi.ts new file mode 100644 index 00000000000..20bd6343e60 --- /dev/null +++ b/src/modules/BuiltinsApi.ts @@ -0,0 +1,16 @@ +/* +Copyright 2025 New Vector 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 RoomViewProps, type BuiltinsApi } from "@element-hq/element-web-module-api"; + +import { RoomView } from "../components/structures/RoomView"; + +export class ElementWebBuiltinsApi implements BuiltinsApi { + public getRoomViewComponent(): React.ComponentType { + return RoomView; + } +} diff --git a/src/modules/ExtrasApi.ts b/src/modules/ExtrasApi.ts new file mode 100644 index 00000000000..fb98e132808 --- /dev/null +++ b/src/modules/ExtrasApi.ts @@ -0,0 +1,16 @@ +/* +Copyright 2025 New Vector 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 SpacePanelItemRenderFunction, type ExtrasApi } from "@element-hq/element-web-module-api"; + +export class ElementWebExtrasApi implements ExtrasApi { + public spacePanelItems: SpacePanelItemRenderFunction[] = []; + + public addSpacePanelItem(renderer: SpacePanelItemRenderFunction): void { + this.spacePanelItems.push(renderer); + } +} From a79febf6d8660ce8bf44bf5af62f895c366eee66 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 14 Oct 2025 15:48:54 +0100 Subject: [PATCH 5/7] Add items from the module api --- src/components/structures/MatrixChat.tsx | 11 ++++++++--- src/components/structures/RoomView.tsx | 3 ++- src/components/views/spaces/SpacePanel.tsx | 2 ++ 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index a4df7a5fe9e..ea4cc36f8d4 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -140,6 +140,7 @@ import { ShareFormat, type SharePayload } from "../../dispatcher/payloads/ShareP import Markdown from "../../Markdown"; import { sanitizeHtmlParams } from "../../Linkify"; import { isOnlyAdmin } from "../../utils/membership"; +import ModuleApi from "../../modules/Api.ts"; // legacy export export { default as Views } from "../../Views"; @@ -175,9 +176,11 @@ interface IProps { interface IState { // the master view we are showing. view: Views; - // What the LoggedInView would be showing if visible + // What the LoggedInView would be showing if visible. + // A member of the enum for standard pages or a string for those provided by + // a module. // eslint-disable-next-line camelcase - page_type?: PageType; + page_type?: PageType | string; // The ID of the room we're viewing. This is either populated directly // in the case where we view a room by ID or by RoomView when it resolves // what ID an alias points at. @@ -1922,7 +1925,9 @@ export default class MatrixChat extends React.PureComponent { subAction: params?.action, }); } else { - logger.info(`Ignoring showScreen for '${screen}'`); + if (ModuleApi.navigation.locationRenderers.get(screen)) { + this.setState({ page_type: screen }); + } } } diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index 10647ee86b5..8f0870c06aa 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -44,6 +44,7 @@ import { type CallState, type MatrixCall } from "matrix-js-sdk/src/webrtc/call"; import { debounce, throttle } from "lodash"; import { CryptoEvent } from "matrix-js-sdk/src/crypto-api"; import { type ViewRoomOpts } from "@matrix-org/react-sdk-module-api/lib/lifecycles/RoomViewLifecycle"; +import { type RoomViewProps } from "@element-hq/element-web-module-api"; import shouldHideEvent from "../../shouldHideEvent"; import { _t } from "../../languageHandler"; @@ -147,7 +148,7 @@ if (DEBUG) { debuglog = logger.log.bind(console); } -interface IRoomProps { +interface IRoomProps extends RoomViewProps { threepidInvite?: IThreepidInvite; oobData?: IOOBData; diff --git a/src/components/views/spaces/SpacePanel.tsx b/src/components/views/spaces/SpacePanel.tsx index 984b33bb12e..39b246c6980 100644 --- a/src/components/views/spaces/SpacePanel.tsx +++ b/src/components/views/spaces/SpacePanel.tsx @@ -69,6 +69,7 @@ import AccessibleButton from "../elements/AccessibleButton"; import { Landmark, LandmarkNavigation } from "../../../accessibility/LandmarkNavigation"; import { KeyboardShortcut } from "../settings/KeyboardShortcut"; import { ReleaseAnnouncement } from "../../structures/ReleaseAnnouncement"; +import ModuleApi from "../../../modules/Api.ts"; const useSpaces = (): [Room[], MetaSpace[], Room[], SpaceKey] => { const invites = useEventEmitterState(SpaceStore.instance, UPDATE_INVITED_SPACES, () => { @@ -341,6 +342,7 @@ const InnerSpacePanel = React.memo( ))} {children} + {ModuleApi.extras.spacePanelItems.map((renderer) => renderer({ isPanelCollapsed }))} {shouldShowComponent(UIComponent.CreateSpaces) && ( )} From 898475ab5a801c23bdace48219523243d6786997 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 14 Oct 2025 16:04:38 +0100 Subject: [PATCH 6/7] CSS --- res/css/structures/_SpacePanel.pcss | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/res/css/structures/_SpacePanel.pcss b/res/css/structures/_SpacePanel.pcss index 64044c4c5c1..1aea3ae23bf 100644 --- a/res/css/structures/_SpacePanel.pcss +++ b/res/css/structures/_SpacePanel.pcss @@ -211,6 +211,10 @@ Please see LICENSE files in the repository root for full details. } } + &.mx_SpaceButton_withIcon .mx_SpaceButton_icon { + background-color: $panel-actions; + } + &.mx_SpaceButton_home .mx_SpaceButton_icon::before { mask-image: url("@vector-im/compound-design-tokens/icons/home-solid.svg"); } From b07362717bad75874ac70c2b5ba11cae2f08e649 Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 15 Oct 2025 12:01:51 +0100 Subject: [PATCH 7/7] Add soacetreelevel change --- src/components/views/spaces/SpaceTreeLevel.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/components/views/spaces/SpaceTreeLevel.tsx b/src/components/views/spaces/SpaceTreeLevel.tsx index d03ac8a1e20..f2c41c367f6 100644 --- a/src/components/views/spaces/SpaceTreeLevel.tsx +++ b/src/components/views/spaces/SpaceTreeLevel.tsx @@ -52,6 +52,7 @@ type ButtonProps = Omit< className?: string; selected?: boolean; label: string; + icon?: JSX.Element; contextMenuTooltip?: string; notificationState?: NotificationState; isNarrow?: boolean; @@ -65,6 +66,7 @@ export const SpaceButton = ({ space, spaceKey: _spaceKey, className, + icon, selected, label, contextMenuTooltip, @@ -84,7 +86,7 @@ export const SpaceButton = ({ let avatar = (
-
+
{icon}
); if (space) { @@ -143,6 +145,7 @@ export const SpaceButton = ({ mx_SpaceButton_active: selected, mx_SpaceButton_hasMenuOpen: menuDisplayed, mx_SpaceButton_narrow: isNarrow, + mx_SpaceButton_withIcon: Boolean(icon), })} aria-label={label} title={!isNarrow || menuDisplayed ? undefined : label}