From 3ffcaba20b422bc5d316ddc4d2312e412d8118f6 Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Mon, 23 Jun 2025 12:42:43 +0100 Subject: [PATCH] Rewrite favicon code to support modules. --- src/BasePlatform.ts | 13 ++++-------- src/favicon.ts | 44 ++++++++++++++++++++++++++++----------- src/modules/Api.ts | 2 ++ src/modules/faviconApi.ts | 36 ++++++++++++++++++++++++++++++++ 4 files changed, 74 insertions(+), 21 deletions(-) create mode 100644 src/modules/faviconApi.ts diff --git a/src/BasePlatform.ts b/src/BasePlatform.ts index 87635a421c..7f585ed720 100644 --- a/src/BasePlatform.ts +++ b/src/BasePlatform.ts @@ -494,15 +494,10 @@ export default abstract class BasePlatform { } private updateFavicon(): void { - let bgColor = "#d00"; - let notif: string | number = this.notificationCount; - - if (this.errorDidOccur) { - notif = notif || "×"; - bgColor = "#f00"; - } - - this.favicon.badge(notif, { bgColor }); + this.favicon.badge({ + notificationCount: this.notificationCount, + errorDidOccur: this.errorDidOccur + }); } /** diff --git a/src/favicon.ts b/src/favicon.ts index e109c73460..bdaf4aafab 100644 --- a/src/favicon.ts +++ b/src/favicon.ts @@ -17,6 +17,8 @@ interface IParams { isUp: boolean; isLeft: boolean; } +import { FaviconRenderFunction, FaviconRenderOptions } from "@element-hq/element-web-module-api"; +import moduleApi from "./modules/Api.ts" const defaults: IParams = { bgColor: "#d00", @@ -184,9 +186,9 @@ export default class Favicon { this.readyCb?.(); } - private setIcon(canvas: HTMLCanvasElement): void { + private setIcon(src: string): void { setTimeout(() => { - this.setIconSrc(canvas.toDataURL("image/png")); + this.setIconSrc(src); }, 0); } @@ -209,21 +211,39 @@ export default class Favicon { } } - public badge(content: number | string, opts?: Partial): void { - if (!this.isReady) { - this.readyCb = (): void => { - this.badge(content, opts); - }; - return; + /** + * Default badge renderer, may be overridden by a module. + * @param opts Notification rendering options. + * @returns A data URL for the favicon. + */ + private renderBadge: FaviconRenderFunction = ({notificationCount, errorDidOccur}) => { + let bgColor = "#d00"; + let notif: string | number = notificationCount; + + if (errorDidOccur) { + notif = notif || "×"; + bgColor = "#f00"; } - if (typeof content === "string" || content > 0) { - this.circle(content, opts); + if (errorDidOccur || notificationCount > 0) { + this.circle(notif, {...this.params, bgColor }); } else { this.reset(); } - this.setIcon(this.canvas); + return this.canvas.toDataURL("image/png"); + } + + public badge(opts: FaviconRenderOptions): void { + if (!this.isReady) { + this.readyCb = (): void => { + this.badge(opts); + }; + return; + } + + const badgeUrl = moduleApi.faviconApi.renderFavicon(opts) || this.renderBadge(opts); + this.setIcon(badgeUrl); } private static getLinks(): HTMLLinkElement[] { @@ -237,7 +257,7 @@ export default class Favicon { return icons; } - private static getIcons(): HTMLLinkElement[] { + public static getIcons(): HTMLLinkElement[] { // get favicon link elements let elms = Favicon.getLinks(); if (elms.length === 0) { diff --git a/src/modules/Api.ts b/src/modules/Api.ts index db7dd80334..8945e0d3c0 100644 --- a/src/modules/Api.ts +++ b/src/modules/Api.ts @@ -22,6 +22,7 @@ import { WidgetVariableCustomisations } from "../customisations/WidgetVariables. import { ConfigApi } from "./ConfigApi.ts"; import { I18nApi } from "./I18nApi.ts"; import { CustomComponentsApi } from "./customComponentApi.ts"; +import { FaviconApi } from "./faviconApi.ts"; const legacyCustomisationsFactory = (baseCustomisations: T) => { let used = false; @@ -61,6 +62,7 @@ class ModuleApi implements Api { public readonly i18n = new I18nApi(); public readonly customComponents = new CustomComponentsApi(); public readonly rootNode = document.getElementById("matrixchat")!; + public readonly faviconApi = new FaviconApi(); public createRoot(element: Element): Root { return createRoot(element); diff --git a/src/modules/faviconApi.ts b/src/modules/faviconApi.ts new file mode 100644 index 0000000000..809f79acc0 --- /dev/null +++ b/src/modules/faviconApi.ts @@ -0,0 +1,36 @@ +/* +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 { + FaviconApi as IFaviconApi, + FaviconRenderFunction, + FaviconRenderOptions +} from "@element-hq/element-web-module-api"; + + +export class FaviconApi implements IFaviconApi { + private registeredFunction?: FaviconRenderFunction; + + public registerRenderer( + func: FaviconRenderFunction + ): void { + if (this.registeredFunction) { + throw Error('A custom favicon rendering function has already been registered'); + } + this.registeredFunction = func; + } + + /** + * Returns a URL to a rendered favicon if a module has generated one, otherwise + * this returns undefined. + * @param opts Options to pass to the render function. + * @returns A URL string, or undefined. + */ + public renderFavicon(opts: FaviconRenderOptions): string|undefined { + return this.registeredFunction?.(opts); + } +}