mirror of
https://github.com/vector-im/element-web.git
synced 2026-05-04 19:56:45 +02:00
Initial stable release of the Module API
Primarily to get away from semver treating every update as breaking in the 0.x.y series.
This commit is contained in:
parent
afcf4593bc
commit
d7736db1af
@ -5,6 +5,7 @@
|
||||
```ts
|
||||
|
||||
import { ModuleApi } from '@matrix-org/react-sdk-module-api';
|
||||
import { Root } from 'react-dom/client';
|
||||
import { RuntimeModule } from '@matrix-org/react-sdk-module-api';
|
||||
|
||||
// @alpha @deprecated (undocumented)
|
||||
@ -18,8 +19,10 @@ export interface AliasCustomisations {
|
||||
//
|
||||
// @public
|
||||
export interface Api extends LegacyModuleApiExtension, LegacyCustomisationsApiExtension {
|
||||
// (undocumented)
|
||||
config: ConfigApi;
|
||||
readonly config: ConfigApi;
|
||||
createRoot(element: Element): Root;
|
||||
readonly i18n: I18nApi;
|
||||
readonly rootNode: HTMLElement;
|
||||
}
|
||||
|
||||
// @alpha @deprecated (undocumented)
|
||||
@ -60,6 +63,13 @@ export interface DirectoryCustomisations {
|
||||
requireCanonicalAliasAccessToPublish?(): boolean;
|
||||
}
|
||||
|
||||
// @public
|
||||
export interface I18nApi {
|
||||
get language(): string;
|
||||
register(translations: Partial<Translations>): void;
|
||||
translate(key: keyof Translations, variables?: Variables): string;
|
||||
}
|
||||
|
||||
// @alpha @deprecated (undocumented)
|
||||
export type LegacyCustomisations<T extends object> = (customisations: T) => void;
|
||||
|
||||
@ -179,6 +189,11 @@ export interface RoomListCustomisations<Room> {
|
||||
// @alpha @deprecated (undocumented)
|
||||
export type RuntimeModuleConstructor = new (api: ModuleApi) => RuntimeModule;
|
||||
|
||||
// @public
|
||||
export type Translations = Record<string, {
|
||||
[ietfLanguageTag: string]: string;
|
||||
}>;
|
||||
|
||||
// @alpha @deprecated (undocumented)
|
||||
export interface UserIdentifierCustomisations {
|
||||
getDisplayUserIdentifier(userId: string, opts: {
|
||||
@ -187,6 +202,12 @@ export interface UserIdentifierCustomisations {
|
||||
}): string | null;
|
||||
}
|
||||
|
||||
// @public
|
||||
export type Variables = {
|
||||
count?: number;
|
||||
[key: string]: number | string | undefined;
|
||||
};
|
||||
|
||||
// @alpha @deprecated (undocumented)
|
||||
export interface WidgetPermissionsCustomisations<Widget, Capability> {
|
||||
preapproveCapabilities?(widget: Widget, requestedCapabilities: Set<Capability>): Promise<Set<Capability>>;
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@element-hq/element-web-module-api",
|
||||
"type": "module",
|
||||
"version": "0.1.5",
|
||||
"version": "1.0.0",
|
||||
"description": "Module API surface for element-web",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@ -26,7 +26,8 @@
|
||||
],
|
||||
"scripts": {
|
||||
"prepare": "vite build && api-extractor run",
|
||||
"lint": "tsc --noEmit",
|
||||
"lint:types": "tsc --noEmit",
|
||||
"lint:codestyle": "echo 'handled by lint:eslint'",
|
||||
"test": "vitest --coverage"
|
||||
},
|
||||
"devDependencies": {
|
||||
@ -34,6 +35,7 @@
|
||||
"@microsoft/api-extractor": "^7.49.1",
|
||||
"@types/node": "^22.10.7",
|
||||
"@types/react": "^19",
|
||||
"@types/react-dom": "^19.0.4",
|
||||
"@types/semver": "^7.5.8",
|
||||
"@vitest/coverage-v8": "^3.0.4",
|
||||
"matrix-web-i18n": "^3.3.0",
|
||||
|
||||
28
packages/element-web-module-api/src/api/config.ts
Normal file
28
packages/element-web-module-api/src/api/config.ts
Normal file
@ -0,0 +1,28 @@
|
||||
/*
|
||||
Copyright 2025 New Vector Ltd.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
/**
|
||||
* The configuration for the application.
|
||||
* Should be extended via declaration merging.
|
||||
* @public
|
||||
*/
|
||||
export interface Config {
|
||||
// The branding name of the application
|
||||
brand: string;
|
||||
// Other config options are available but not specified in the types as that would make it difficult to change for element-web
|
||||
// they are accessible at runtime all the same, see list at https://github.com/element-hq/element-web/blob/develop/docs/config.md
|
||||
}
|
||||
|
||||
/**
|
||||
* API for accessing the configuration.
|
||||
* @public
|
||||
*/
|
||||
export interface ConfigApi {
|
||||
get(): Config;
|
||||
get<K extends keyof Config>(key: K): Config[K];
|
||||
get<K extends keyof Config = never>(key?: K): Config | Config[K];
|
||||
}
|
||||
52
packages/element-web-module-api/src/api/i18n.ts
Normal file
52
packages/element-web-module-api/src/api/i18n.ts
Normal file
@ -0,0 +1,52 @@
|
||||
/*
|
||||
Copyright 2025 New Vector Ltd.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
/**
|
||||
* The translations for the module.
|
||||
* @public
|
||||
*/
|
||||
export type Translations = Record<
|
||||
string,
|
||||
{
|
||||
[ietfLanguageTag: string]: string;
|
||||
}
|
||||
>;
|
||||
|
||||
/**
|
||||
* Variables to interpolate into a translation.
|
||||
* @public
|
||||
*/
|
||||
export type Variables = {
|
||||
/**
|
||||
* The number of items to count for pluralised translations
|
||||
*/
|
||||
count?: number;
|
||||
[key: string]: number | string | undefined;
|
||||
};
|
||||
|
||||
/**
|
||||
* The API for interacting with translations.
|
||||
* @public
|
||||
*/
|
||||
export interface I18nApi {
|
||||
/**
|
||||
* Read the current language of the user in IETF Language Tag format
|
||||
*/
|
||||
get language(): string;
|
||||
|
||||
/**
|
||||
* Register translations for the module, may override app's existing translations
|
||||
*/
|
||||
register(translations: Partial<Translations>): void;
|
||||
|
||||
/**
|
||||
* Perform a translation, with optional variables
|
||||
* @param key - The key to translate
|
||||
* @param variables - Optional variables to interpolate into the translation
|
||||
*/
|
||||
translate(key: keyof Translations, variables?: Variables): string;
|
||||
}
|
||||
22
packages/element-web-module-api/src/api/index.test.ts
Normal file
22
packages/element-web-module-api/src/api/index.test.ts
Normal file
@ -0,0 +1,22 @@
|
||||
/*
|
||||
Copyright 2025 New Vector Ltd.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { expect, test } from "vitest";
|
||||
|
||||
import { Api, isModule } from ".";
|
||||
|
||||
const TestModule = {
|
||||
default: class TestModule {
|
||||
public static moduleApiVersion = "1.0.0";
|
||||
public constructor(private readonly api: Api) {}
|
||||
public async load(): Promise<void> {}
|
||||
},
|
||||
};
|
||||
|
||||
test("isModule correctly identifies valid modules", () => {
|
||||
expect(isModule(TestModule)).toBe(true);
|
||||
});
|
||||
96
packages/element-web-module-api/src/api/index.ts
Normal file
96
packages/element-web-module-api/src/api/index.ts
Normal file
@ -0,0 +1,96 @@
|
||||
/*
|
||||
Copyright 2025 New Vector Ltd.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import type { Root } from "react-dom/client";
|
||||
import { LegacyModuleApiExtension } from "./legacy-modules";
|
||||
import { LegacyCustomisationsApiExtension } from "./legacy-customisations";
|
||||
import { ConfigApi } from "./config";
|
||||
import { I18nApi } from "./i18n";
|
||||
|
||||
/**
|
||||
* Module interface for modules to implement.
|
||||
* @public
|
||||
*/
|
||||
export interface Module {
|
||||
load(): Promise<void>;
|
||||
}
|
||||
|
||||
const moduleSignature: Record<keyof Module, Type> = {
|
||||
load: "function",
|
||||
};
|
||||
|
||||
/**
|
||||
* Module interface for modules to export as the default export.
|
||||
* @public
|
||||
*/
|
||||
export interface ModuleFactory {
|
||||
readonly moduleApiVersion: string;
|
||||
new (api: Api): Module;
|
||||
readonly prototype: Module;
|
||||
}
|
||||
|
||||
const moduleFactorySignature: Record<keyof ModuleFactory, Type> = {
|
||||
moduleApiVersion: "string",
|
||||
prototype: "object",
|
||||
};
|
||||
|
||||
export interface ModuleExport {
|
||||
default: ModuleFactory;
|
||||
}
|
||||
|
||||
const moduleExportSignature: Record<keyof ModuleExport, Type> = {
|
||||
default: "function",
|
||||
};
|
||||
|
||||
type Type = "function" | "string" | "number" | "boolean" | "object";
|
||||
|
||||
function isInterface<T>(obj: unknown, type: "object" | "function", keys: Record<keyof T, Type>): obj is T {
|
||||
if (obj === null || typeof obj !== type) return false;
|
||||
for (const key in keys) {
|
||||
if (typeof (obj as Record<keyof T, unknown>)[key] !== keys[key]) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
export function isModule(module: unknown): module is ModuleExport {
|
||||
return (
|
||||
isInterface(module, "object", moduleExportSignature) &&
|
||||
isInterface(module.default, "function", moduleFactorySignature) &&
|
||||
isInterface(module.default.prototype, "object", moduleSignature)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* The API for modules to interact with the application.
|
||||
* @public
|
||||
*/
|
||||
export interface Api extends LegacyModuleApiExtension, LegacyCustomisationsApiExtension {
|
||||
/**
|
||||
* The API to read config.json values.
|
||||
* Keys should be scoped to the module in reverse domain name notation.
|
||||
* @public
|
||||
*/
|
||||
readonly config: ConfigApi;
|
||||
/**
|
||||
* The internationalisation API.
|
||||
* @public
|
||||
*/
|
||||
readonly i18n: I18nApi;
|
||||
/**
|
||||
* The root node the main application is rendered to.
|
||||
* Intended for rendering sibling React trees.
|
||||
* @public
|
||||
*/
|
||||
readonly rootNode: HTMLElement;
|
||||
/**
|
||||
* Create a ReactDOM root for rendering React components.
|
||||
* Exposed to allow modules to avoid needing to bundle their own ReactDOM.
|
||||
* @param element - the element to render use as the root.
|
||||
* @public
|
||||
*/
|
||||
createRoot(element: Element): Root;
|
||||
}
|
||||
259
packages/element-web-module-api/src/api/legacy-customisations.ts
Normal file
259
packages/element-web-module-api/src/api/legacy-customisations.ts
Normal file
@ -0,0 +1,259 @@
|
||||
/*
|
||||
Copyright 2025 New Vector Ltd.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
/**
|
||||
* The types here suck but these customisations are deprecated and will be removed soon.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @alpha
|
||||
* @deprecated in favour of the new Module API
|
||||
*/
|
||||
export interface AliasCustomisations {
|
||||
// E.g. prefer one of the aliases over another
|
||||
getDisplayAliasForAliasSet?(canonicalAlias: string | null, altAliases: string[]): string | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @alpha
|
||||
* @deprecated in favour of the new Module API
|
||||
*/
|
||||
export interface ChatExportCustomisations<ExportFormat, ExportType> {
|
||||
/**
|
||||
* Force parameters in room chat export fields returned here are forced
|
||||
* and not allowed to be edited in the chat export form
|
||||
*/
|
||||
getForceChatExportParameters(): {
|
||||
format?: ExportFormat;
|
||||
range?: ExportType;
|
||||
// must be < 10**8
|
||||
// only used when range is 'LastNMessages'
|
||||
// default is 100
|
||||
numberOfMessages?: number;
|
||||
includeAttachments?: boolean;
|
||||
// maximum size of exported archive
|
||||
// must be > 0 and < 8000
|
||||
sizeMb?: number;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @alpha
|
||||
* @deprecated in favour of the new Module API
|
||||
*/
|
||||
export interface ComponentVisibilityCustomisations {
|
||||
/**
|
||||
* Determines whether or not the active MatrixClient user should be able to use
|
||||
* the given UI component. If shown, the user might still not be able to use the
|
||||
* component depending on their contextual permissions. For example, invite options
|
||||
* might be shown to the user but they won't have permission to invite users to
|
||||
* the current room: the button will appear disabled.
|
||||
* @param component - The component to check visibility for.
|
||||
* @returns True (default) if the user is able to see the component, false otherwise.
|
||||
*/
|
||||
shouldShowComponent?(
|
||||
component:
|
||||
| "UIComponent.sendInvites"
|
||||
| "UIComponent.roomCreation"
|
||||
| "UIComponent.spaceCreation"
|
||||
| "UIComponent.exploreRooms"
|
||||
| "UIComponent.addIntegrations"
|
||||
| "UIComponent.filterContainer"
|
||||
| "UIComponent.roomOptionsMenu",
|
||||
): boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* @alpha
|
||||
* @deprecated in favour of the new Module API
|
||||
*/
|
||||
export interface DirectoryCustomisations {
|
||||
requireCanonicalAliasAccessToPublish?(): boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* @alpha
|
||||
* @deprecated in favour of the new Module API
|
||||
*/
|
||||
export interface LifecycleCustomisations {
|
||||
onLoggedOutAndStorageCleared?(): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* @alpha
|
||||
* @deprecated in favour of the new Module API
|
||||
*/
|
||||
export interface Media {
|
||||
readonly isEncrypted: boolean;
|
||||
readonly srcMxc: string;
|
||||
readonly thumbnailMxc: string | null | undefined;
|
||||
readonly hasThumbnail: boolean;
|
||||
readonly srcHttp: string | null;
|
||||
readonly thumbnailHttp: string | null;
|
||||
getThumbnailHttp(width: number, height: number, mode?: "scale" | "crop"): string | null;
|
||||
getThumbnailOfSourceHttp(width: number, height: number, mode?: "scale" | "crop"): string | null;
|
||||
getSquareThumbnailHttp(dim: number): string | null;
|
||||
downloadSource(): Promise<Response>;
|
||||
}
|
||||
|
||||
/**
|
||||
* @alpha
|
||||
* @deprecated in favour of the new Module API
|
||||
*/
|
||||
export interface MediaContructable<PreparedMedia> {
|
||||
new (prepared: PreparedMedia): Media;
|
||||
}
|
||||
|
||||
/**
|
||||
* @alpha
|
||||
* @deprecated in favour of the new Module API
|
||||
*/
|
||||
export interface MediaCustomisations<Content, Client, PreparedMedia> {
|
||||
readonly Media: MediaContructable<PreparedMedia>;
|
||||
mediaFromContent(content: Content, client?: Client): Media;
|
||||
mediaFromMxc(mxc?: string, client?: Client): Media;
|
||||
}
|
||||
|
||||
/**
|
||||
* @alpha
|
||||
* @deprecated in favour of the new Module API
|
||||
*/
|
||||
export interface RoomListCustomisations<Room> {
|
||||
/**
|
||||
* Determines if a room is visible in the room list or not. By default,
|
||||
* all rooms are visible. Where special handling is performed by Element,
|
||||
* those rooms will not be able to override their visibility in the room
|
||||
* list - Element will make the decision without calling this function.
|
||||
*
|
||||
* This function should be as fast as possible to avoid slowing down the
|
||||
* client.
|
||||
* @param room - The room to check the visibility of.
|
||||
* @returns True if the room should be visible, false otherwise.
|
||||
*/
|
||||
isRoomVisible?(room: Room): boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* @alpha
|
||||
* @deprecated in favour of the new Module API
|
||||
*/
|
||||
export interface UserIdentifierCustomisations {
|
||||
/**
|
||||
* Customise display of the user identifier
|
||||
* hide userId for guests, display 3pid
|
||||
*
|
||||
* Set withDisplayName to true when user identifier will be displayed alongside user name
|
||||
*/
|
||||
getDisplayUserIdentifier(userId: string, opts: { roomId?: string; withDisplayName?: boolean }): string | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @alpha
|
||||
* @deprecated in favour of the new Module API
|
||||
*/
|
||||
export interface WidgetPermissionsCustomisations<Widget, Capability> {
|
||||
/**
|
||||
* Approves the widget for capabilities that it requested, if any can be
|
||||
* approved. Typically this will be used to give certain widgets capabilities
|
||||
* without having to prompt the user to approve them. This cannot reject
|
||||
* capabilities that Element will be automatically granting, such as the
|
||||
* ability for Jitsi widgets to stay on screen - those will be approved
|
||||
* regardless.
|
||||
* @param widget - The widget to approve capabilities for.
|
||||
* @param requestedCapabilities - The capabilities the widget requested.
|
||||
* @returns Resolves to the capabilities that are approved for use
|
||||
* by the widget. If none are approved, this should return an empty Set.
|
||||
*/
|
||||
preapproveCapabilities?(widget: Widget, requestedCapabilities: Set<Capability>): Promise<Set<Capability>>;
|
||||
}
|
||||
|
||||
/**
|
||||
* @alpha
|
||||
* @deprecated in favour of the new Module API
|
||||
*/
|
||||
export interface WidgetVariablesCustomisations {
|
||||
/**
|
||||
* Provides a partial set of the variables needed to render any widget. If
|
||||
* variables are missing or not provided then they will be filled with the
|
||||
* application-determined defaults.
|
||||
*
|
||||
* This will not be called until after isReady() resolves.
|
||||
* @returns The variables.
|
||||
*/
|
||||
provideVariables?(): {
|
||||
currentUserId: string;
|
||||
userDisplayName?: string;
|
||||
userHttpAvatarUrl?: string;
|
||||
clientId?: string;
|
||||
clientTheme?: string;
|
||||
clientLanguage?: string;
|
||||
deviceId?: string;
|
||||
baseUrl?: string;
|
||||
};
|
||||
/**
|
||||
* Resolves to whether or not the customisation point is ready for variables
|
||||
* to be provided. This will block widgets being rendered.
|
||||
* If not provided, the app will assume that the customisation is always ready.
|
||||
* @returns a promise which resolves when ready.
|
||||
*/
|
||||
isReady?(): Promise<void>;
|
||||
}
|
||||
|
||||
/**
|
||||
* @alpha
|
||||
* @deprecated in favour of the new Module API
|
||||
*/
|
||||
export type LegacyCustomisations<T extends object> = (customisations: T) => void;
|
||||
|
||||
/**
|
||||
* @alpha
|
||||
* @deprecated in favour of the new Module API
|
||||
*/
|
||||
export interface LegacyCustomisationsApiExtension {
|
||||
/**
|
||||
* @deprecated in favour of the new Module API
|
||||
*/
|
||||
readonly _registerLegacyAliasCustomisations: LegacyCustomisations<AliasCustomisations>;
|
||||
/**
|
||||
* @deprecated in favour of the new Module API
|
||||
*/
|
||||
readonly _registerLegacyChatExportCustomisations: LegacyCustomisations<ChatExportCustomisations<never, never>>;
|
||||
/**
|
||||
* @deprecated in favour of the new Module API
|
||||
*/
|
||||
readonly _registerLegacyComponentVisibilityCustomisations: LegacyCustomisations<ComponentVisibilityCustomisations>;
|
||||
/**
|
||||
* @deprecated in favour of the new Module API
|
||||
*/
|
||||
readonly _registerLegacyDirectoryCustomisations: LegacyCustomisations<DirectoryCustomisations>;
|
||||
/**
|
||||
* @deprecated in favour of the new Module API
|
||||
*/
|
||||
readonly _registerLegacyLifecycleCustomisations: LegacyCustomisations<LifecycleCustomisations>;
|
||||
/**
|
||||
* @deprecated in favour of the new Module API
|
||||
*/
|
||||
readonly _registerLegacyMediaCustomisations: LegacyCustomisations<MediaCustomisations<never, never, never>>;
|
||||
/**
|
||||
* @deprecated in favour of the new Module API
|
||||
*/
|
||||
readonly _registerLegacyRoomListCustomisations: LegacyCustomisations<RoomListCustomisations<never>>;
|
||||
/**
|
||||
* @deprecated in favour of the new Module API
|
||||
*/
|
||||
readonly _registerLegacyUserIdentifierCustomisations: LegacyCustomisations<UserIdentifierCustomisations>;
|
||||
/**
|
||||
* @deprecated in favour of the new Module API
|
||||
*/
|
||||
readonly _registerLegacyWidgetPermissionsCustomisations: LegacyCustomisations<
|
||||
WidgetPermissionsCustomisations<never, never>
|
||||
>;
|
||||
/**
|
||||
* @deprecated in favour of the new Module API
|
||||
*/
|
||||
readonly _registerLegacyWidgetVariablesCustomisations: LegacyCustomisations<WidgetVariablesCustomisations>;
|
||||
}
|
||||
30
packages/element-web-module-api/src/api/legacy-modules.ts
Normal file
30
packages/element-web-module-api/src/api/legacy-modules.ts
Normal file
@ -0,0 +1,30 @@
|
||||
/*
|
||||
Copyright 2025 New Vector Ltd.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore -- optional interface, will gracefully degrade to `any` if `react-sdk-module-api` isn't installed
|
||||
import type { ModuleApi, RuntimeModule } from "@matrix-org/react-sdk-module-api";
|
||||
|
||||
/**
|
||||
* @alpha
|
||||
* @deprecated in favour of the new module API
|
||||
*/
|
||||
export type RuntimeModuleConstructor = new (api: ModuleApi) => RuntimeModule;
|
||||
|
||||
/**
|
||||
* @alpha
|
||||
* @deprecated in favour of the new module API
|
||||
*/
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
export interface LegacyModuleApiExtension {
|
||||
/**
|
||||
* Register a legacy module based on \@matrix-org/react-sdk-module-api
|
||||
* @param LegacyModule - the module class to register
|
||||
* @deprecated provided only as a transition path for legacy modules
|
||||
*/
|
||||
_registerLegacyModule(LegacyModule: RuntimeModuleConstructor): Promise<void>;
|
||||
}
|
||||
@ -6,6 +6,8 @@ Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
export { ModuleLoader, ModuleIncompatibleError } from "./loader";
|
||||
export type { Api, Module, ModuleFactory, Config, ConfigApi } from "./api";
|
||||
export type * from "./legacy-modules";
|
||||
export type * from "./legacy-customisations";
|
||||
export type { Api, Module, ModuleFactory } from "./api";
|
||||
export type { Config, ConfigApi } from "./api/config";
|
||||
export type { I18nApi, Variables, Translations } from "./api/i18n";
|
||||
export type * from "./api/legacy-modules";
|
||||
export type * from "./api/legacy-customisations";
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user