diff --git a/web/src/admin/applications/wizard/ContextIdentity.ts b/web/src/admin/applications/wizard/ContextIdentity.ts index 7a0b85b400a0..e821bc94c1ee 100644 --- a/web/src/admin/applications/wizard/ContextIdentity.ts +++ b/web/src/admin/applications/wizard/ContextIdentity.ts @@ -3,5 +3,5 @@ import { LocalTypeCreate } from "./steps/ProviderChoices.js"; import { createContext } from "@lit/context"; export const applicationWizardProvidersContext = createContext( - Symbol.for("ak-application-wizard-providers-context"), + Symbol("ak-application-wizard-providers-context"), ); diff --git a/web/src/common/api/middleware.ts b/web/src/common/api/middleware.ts index a43e032a7193..50f35048f111 100644 --- a/web/src/common/api/middleware.ts +++ b/web/src/common/api/middleware.ts @@ -1,5 +1,5 @@ import { AKRequestPostEvent, APIRequestInfo } from "#common/api/events"; -import { autoDetectLanguage } from "#common/ui/locale/utils"; +import { formatAcceptLanguageHeader } from "#common/ui/locale/utils"; import { getCookie } from "#common/utils"; import { ConsoleLogger, Logger } from "#logger/browser"; @@ -74,11 +74,11 @@ export class LocaleMiddleware implements Middleware, Disposable { return; } - this.#locale = event.detail.readyLocale; + this.#locale = formatAcceptLanguageHeader(event.detail.readyLocale); }; - constructor(localeHint?: string) { - this.#locale = autoDetectLanguage(localeHint); + constructor(languageTagHint: Intl.UnicodeBCP47LocaleIdentifier) { + this.#locale = formatAcceptLanguageHeader(languageTagHint); window.addEventListener(LOCALE_STATUS_EVENT, this.#localeStatusListener); } diff --git a/web/src/common/ui/locale/format.ts b/web/src/common/ui/locale/format.ts index 7fc9cfd73be8..5e2ab4460853 100644 --- a/web/src/common/ui/locale/format.ts +++ b/web/src/common/ui/locale/format.ts @@ -9,8 +9,6 @@ import { import { safeParseLocale } from "#common/ui/locale/utils"; import { msg, str } from "@lit/localize"; -import { html } from "lit"; -import { repeat } from "lit/directives/repeat.js"; /** * Safely get a minimized locale ID, with fallback for older browsers. @@ -198,34 +196,41 @@ export function formatLocaleDisplayNames( return entries.sort(createIntlCollator(activeLocaleTag, collatorOptions)); } -export function renderLocaleDisplayNames( - entries: LocaleDisplay[], - activeLocaleTag: TargetLanguageTag | null, +export function formatRelativeLocaleDisplayName( + languageTag: TargetLanguageTag, + localizedDisplayName: string, + relativeDisplayName: string, ) { - return repeat( - entries, - ([languageTag]) => languageTag, - ([languageTag, localizedDisplayName, relativeDisplayName]) => { - const pseudo = languageTag === PseudoLanguageTag; - - const same = - relativeDisplayName && - normalizeDisplayName(relativeDisplayName) === - normalizeDisplayName(localizedDisplayName); - - let localizedMessage = localizedDisplayName; - - if (!same && !pseudo) { - localizedMessage = msg(str`${relativeDisplayName} (${localizedDisplayName})`, { - id: "locale-option-localized-label", - desc: "Locale option label showing the localized language name along with the native language name in parentheses.", - }); - } + const pseudo = languageTag === PseudoLanguageTag; + + const same = + relativeDisplayName && + normalizeDisplayName(relativeDisplayName) === normalizeDisplayName(localizedDisplayName); + + if (same || pseudo) { + return localizedDisplayName; + } + + return msg(str`${relativeDisplayName} (${localizedDisplayName})`, { + id: "locale-option-localized-label", + desc: "Locale option label showing the localized language name along with the native language name in parentheses.", + }); +} + +/** + * Format the display name for the auto-detect locale option. + * + * @param detectedLocale The detected locale display, if any. + */ +export function formatAutoDetectLocaleDisplayName(detectedLocale?: LocaleDisplay | null) { + const prefix = msg("Auto-detect", { + id: "locale-auto-detect-option", + desc: "Label for the auto-detect locale option in language selection dropdown", + }); + + if (!detectedLocale) { + return prefix; + } - return html`${pseudo ? html`
` : null} - `; - }, - ); + return `${prefix} (${formatRelativeLocaleDisplayName(...detectedLocale)})`; } diff --git a/web/src/common/ui/locale/utils.ts b/web/src/common/ui/locale/utils.ts index 66c41fbe4724..0cba58aef26e 100644 --- a/web/src/common/ui/locale/utils.ts +++ b/web/src/common/ui/locale/utils.ts @@ -157,8 +157,8 @@ export function getSessionLocale(): string | null { /** * Auto-detect the best locale to use from several sources. * - * @param localeHint An optional locale code hint. - * @param fallbackLocaleCode An optional fallback locale code. + * @param languageTagHint An optional locale code hint. + * @param fallbackLanguageTag An optional fallback locale code. * @returns The best-matching supported locale code. * * @remarks @@ -172,8 +172,8 @@ export function getSessionLocale(): string | null { * 6. The source locale (English) */ export function autoDetectLanguage( - localeHint?: string, - fallbackLocaleCode?: string, + languageTagHint?: Intl.UnicodeBCP47LocaleIdentifier, + fallbackLanguageTag?: Intl.UnicodeBCP47LocaleIdentifier, ): TargetLanguageTag { let localeParam: string | null = null; @@ -188,9 +188,9 @@ export function autoDetectLanguage( const candidates = [ localeParam, sessionLocale, - localeHint, - self.navigator?.language, - fallbackLocaleCode, + languageTagHint, + ...(self.navigator?.languages || []), + fallbackLanguageTag, ].filter((item): item is string => !!item); const firstSupportedLocale = findSupportedLocale(candidates); @@ -198,8 +198,8 @@ export function autoDetectLanguage( if (!firstSupportedLocale) { console.debug(`authentik/locale: Falling back to source locale`, { SourceLanguageTag, - localeHint, - fallbackLocaleCode, + languageTagHint, + fallbackLanguageTag, candidates, }); @@ -208,3 +208,28 @@ export function autoDetectLanguage( return firstSupportedLocale; } + +/** + * Given a locale code, format it for use in an `Accept-Language` header. + */ +export function formatAcceptLanguageHeader(languageTag: Intl.UnicodeBCP47LocaleIdentifier): string { + const [preferredLanguageTag, ...languageTags] = new Set([ + languageTag, + ...(self.navigator?.languages || []), + SourceLanguageTag, + "*", + ]); + + const fallbackCount = languageTags.length; + + return [ + preferredLanguageTag, + ...languageTags.map((tag, idx) => { + const weight = ((fallbackCount - idx) / (fallbackCount + 1)).toFixed( + fallbackCount > 9 ? 2 : 1, + ); + + return `${tag};q=${weight}`; + }), + ].join(", "); +} diff --git a/web/src/components/ak-wizard/WizardContexts.ts b/web/src/components/ak-wizard/WizardContexts.ts index 2b9c2e3f7919..51ae845d8663 100644 --- a/web/src/components/ak-wizard/WizardContexts.ts +++ b/web/src/components/ak-wizard/WizardContexts.ts @@ -3,5 +3,5 @@ import type { WizardStepState } from "./types.js"; import { createContext } from "@lit/context"; export const wizardStepContext = createContext( - Symbol.for("authentik-wizard-step-labels"), + Symbol("authentik-wizard-step-labels"), ); diff --git a/web/src/elements/AuthenticatedInterface.ts b/web/src/elements/AuthenticatedInterface.ts index e6a9ded7f746..9eba16b70863 100644 --- a/web/src/elements/AuthenticatedInterface.ts +++ b/web/src/elements/AuthenticatedInterface.ts @@ -3,16 +3,18 @@ import { NotificationsContextController } from "#elements/controllers/Notificati import { SessionContextController } from "#elements/controllers/SessionContextController"; import { VersionContextController } from "#elements/controllers/VersionContextController"; import { Interface } from "#elements/Interface"; +import { LicenseContext } from "#elements/mixins/license"; import { NotificationsContext } from "#elements/mixins/notifications"; import { SessionContext } from "#elements/mixins/session"; +import { VersionContext } from "#elements/mixins/version"; export class AuthenticatedInterface extends Interface { constructor() { super(); - this.addController(new LicenseContextController(this)); + this.addController(new LicenseContextController(this), LicenseContext); this.addController(new SessionContextController(this), SessionContext); - this.addController(new VersionContextController(this)); + this.addController(new VersionContextController(this), VersionContext); this.addController(new NotificationsContextController(this), NotificationsContext); } } diff --git a/web/src/elements/Interface.ts b/web/src/elements/Interface.ts index 21eaca63189d..fcf5f176860e 100644 --- a/web/src/elements/Interface.ts +++ b/web/src/elements/Interface.ts @@ -7,7 +7,9 @@ import { ConfigContextController } from "#elements/controllers/ConfigContextCont import { ContextControllerRegistry } from "#elements/controllers/ContextControllerRegistry"; import { LocaleContextController } from "#elements/controllers/LocaleContextController"; import { ModalOrchestrationController } from "#elements/controllers/ModalOrchestrationController"; -import { ReactiveContextController } from "#elements/types"; +import { ReactiveContextController } from "#elements/controllers/ReactiveContextController"; +import { BrandingContext } from "#elements/mixins/branding"; +import { AuthentikConfigContext } from "#elements/mixins/config"; import { Context, ContextType } from "@lit/context"; import { ReactiveController } from "lit"; @@ -16,6 +18,14 @@ import { ReactiveController } from "lit"; * The base interface element for the application. */ export abstract class Interface extends AKElement { + /** + * Private map of controllers to their registry keys. + * + * This is used to track which controllers have been registered, + * and to unregister them when removed. + */ + #registryKeys = new WeakMap>>(); + constructor() { super(); @@ -24,24 +34,44 @@ export abstract class Interface extends AKElement { createUIThemeEffect(applyDocumentTheme); this.addController(new LocaleContextController(this, locale)); - this.addController(new ConfigContextController(this, config)); - this.addController(new BrandingContextController(this, brand)); + this.addController(new ConfigContextController(this, config), AuthentikConfigContext); + this.addController(new BrandingContextController(this, brand), BrandingContext); this.addController(new ModalOrchestrationController()); + + this.dataset.testId = "interface-root"; } public override addController( controller: ReactiveController, registryKey?: ContextType>, ): void { - super.addController(controller); + if (controller instanceof ReactiveContextController) { + if (!registryKey) { + throw new TypeError( + `ReactiveContextController (${controller.constructor.name}) requires a registry key.`, + ); + } - if (registryKey) { - ContextControllerRegistry.set(registryKey, controller as ReactiveContextController); + if (this.#registryKeys.has(controller)) { + throw new Error( + `Controller (${controller.constructor.name}) is already registered.`, + ); + } + + this.#registryKeys.set(controller, registryKey); + ContextControllerRegistry.set(registryKey, controller); } + super.addController(controller); } - public connectedCallback(): void { - super.connectedCallback(); - this.dataset.testId = "interface-root"; + public override removeController(controller: ReactiveController): void { + super.removeController(controller); + + const registryKey = this.#registryKeys.get(controller); + + if (registryKey) { + ContextControllerRegistry.delete(registryKey); + this.#registryKeys.delete(controller); + } } } diff --git a/web/src/elements/controllers/ContextControllerRegistry.ts b/web/src/elements/controllers/ContextControllerRegistry.ts index bccd43e9aec1..9d48d3158be4 100644 --- a/web/src/elements/controllers/ContextControllerRegistry.ts +++ b/web/src/elements/controllers/ContextControllerRegistry.ts @@ -1,5 +1,34 @@ import { type ContextControllerRegistryMap } from "#elements/types"; +/** + * Check if the environment supports Symbol-keyed WeakMaps. + * + * @see {@link https://caniuse.com/mdn-javascript_builtins_weakmap_symbol_as_keys | Can I use} + * + * @todo Re-evaluate browser coverage after 2027-01-01 + */ +function supportsSymbolKeyedWeakMap(): boolean { + const testKey = Symbol("test"); + const wm = new WeakMap(); + + try { + wm.set(testKey, "value"); + return wm.has(testKey); + } catch (_error) { + return false; + } +} + +/** + * A constructor for either WeakMap or Map, depending on environment support. + * + * @remarks + * + * A preference for `WeakMap` is optional at the moment. + * However, if we ever support short-lived context controllers, such as + */ +const ContextControllerConstructor = supportsSymbolKeyedWeakMap() ? WeakMap : Map; + /** * A registry of context controllers added to the Interface. * @@ -9,4 +38,5 @@ import { type ContextControllerRegistryMap } from "#elements/types"; * * This is exported separately to avoid circular dependencies. */ -export const ContextControllerRegistry = new WeakMap() as ContextControllerRegistryMap; +export const ContextControllerRegistry = + new ContextControllerConstructor() as ContextControllerRegistryMap; diff --git a/web/src/elements/locale/ak-locale-select.ts b/web/src/elements/locale/ak-locale-select.ts index 636a58a93128..f79c94a33e4e 100644 --- a/web/src/elements/locale/ak-locale-select.ts +++ b/web/src/elements/locale/ak-locale-select.ts @@ -1,9 +1,10 @@ import { TargetLanguageTag } from "#common/ui/locale/definitions"; -import { formatLocaleDisplayNames, renderLocaleDisplayNames } from "#common/ui/locale/format"; +import { formatLocaleDisplayNames } from "#common/ui/locale/format"; import { setSessionLocale } from "#common/ui/locale/utils"; import { AKElement } from "#elements/Base"; import Styles from "#elements/locale/ak-locale-select.css"; +import { LocaleOptions } from "#elements/locale/utils"; import { WithCapabilitiesConfig } from "#elements/mixins/capabilities"; import { WithLocale } from "#elements/mixins/locale"; @@ -159,7 +160,7 @@ export class AKLocaleSelect extends WithLocale(WithCapabilitiesConfig(AKElement) class="pf-c-form-control ak-m-capitalize" name="locale" > - ${renderLocaleDisplayNames(entries, activeLocaleTag)} + ${LocaleOptions({ entries, activeLocaleTag })} `; }); } diff --git a/web/src/elements/locale/utils.ts b/web/src/elements/locale/utils.ts new file mode 100644 index 000000000000..1636a019f07b --- /dev/null +++ b/web/src/elements/locale/utils.ts @@ -0,0 +1,36 @@ +import { PseudoLanguageTag, TargetLanguageTag } from "#common/ui/locale/definitions"; +import { formatRelativeLocaleDisplayName, LocaleDisplay } from "#common/ui/locale/format"; + +import type { LitFC, SlottedTemplateResult } from "#elements/types"; + +import { html } from "lit"; +import { repeat } from "lit/directives/repeat.js"; + +export interface LocaleOptionsProps { + entries: Iterable; + activeLocaleTag: TargetLanguageTag | null; +} + +/** + * Render locale display name options for a select element. + */ +export const LocaleOptions: LitFC = ({ entries, activeLocaleTag }) => { + return repeat( + entries, + ([languageTag]) => languageTag, + ([languageTag, localizedDisplayName, relativeDisplayName]) => { + const pseudo = languageTag === PseudoLanguageTag; + + const localizedMessage = formatRelativeLocaleDisplayName( + languageTag, + localizedDisplayName, + relativeDisplayName, + ); + + return html`${pseudo ? html`
` : null} + `; + }, + ) as SlottedTemplateResult; +}; diff --git a/web/src/elements/mixins/branding.ts b/web/src/elements/mixins/branding.ts index cd3fddeb4d6e..b33f5a33b37d 100644 --- a/web/src/elements/mixins/branding.ts +++ b/web/src/elements/mixins/branding.ts @@ -13,9 +13,7 @@ import { consume, Context, createContext } from "@lit/context"; * @see {@linkcode BrandingMixin} * @see {@linkcode WithBrandConfig} */ -export const BrandingContext = createContext( - Symbol.for("authentik-branding-context"), -); +export const BrandingContext = createContext(Symbol("authentik-branding-context")); export type BrandingContext = Context; diff --git a/web/src/elements/mixins/config.ts b/web/src/elements/mixins/config.ts index 550b9945cfd0..7f89265c3d05 100644 --- a/web/src/elements/mixins/config.ts +++ b/web/src/elements/mixins/config.ts @@ -13,7 +13,7 @@ export const kAKConfig = Symbol("kAKConfig"); * @see {@linkcode AKConfigMixin} * @see {@linkcode WithAuthentikConfig} */ -export const AuthentikConfigContext = createContext(Symbol.for("authentik-config-context")); +export const AuthentikConfigContext = createContext(Symbol("authentik-config-context")); export type AuthentikConfigContext = Context; diff --git a/web/src/elements/mixins/license.ts b/web/src/elements/mixins/license.ts index b5d125f7809f..104645b41fa0 100644 --- a/web/src/elements/mixins/license.ts +++ b/web/src/elements/mixins/license.ts @@ -4,9 +4,7 @@ import { type LicenseSummary, LicenseSummaryStatusEnum } from "@goauthentik/api" import { consume, Context, createContext } from "@lit/context"; -export const LicenseContext = createContext( - Symbol.for("authentik-license-context"), -); +export const LicenseContext = createContext(Symbol("authentik-license-context")); export type LicenseContext = Context; diff --git a/web/src/elements/mixins/locale.ts b/web/src/elements/mixins/locale.ts index 4af490be472e..099c707f2336 100644 --- a/web/src/elements/mixins/locale.ts +++ b/web/src/elements/mixins/locale.ts @@ -16,9 +16,7 @@ export const kAKLocale = Symbol("kAKLocale"); * @see {@linkcode LocaleMixin} * @see {@linkcode WithLocale} */ -export const LocaleContext = createContext( - Symbol.for("authentik-locale-context"), -); +export const LocaleContext = createContext(Symbol("authentik-locale-context")); export type LocaleContext = typeof LocaleContext; diff --git a/web/src/elements/mixins/version.ts b/web/src/elements/mixins/version.ts index 4cb707452f78..60652b8941ca 100644 --- a/web/src/elements/mixins/version.ts +++ b/web/src/elements/mixins/version.ts @@ -13,9 +13,7 @@ import { property } from "lit/decorators.js"; * @see {@linkcode WithVersion} */ -export const VersionContext = createContext( - Symbol.for("authentik-version-context"), -); +export const VersionContext = createContext(Symbol("authentik-version-context")); export type VersionContext = typeof VersionContext; diff --git a/web/src/elements/types.ts b/web/src/elements/types.ts index 23b95d04fbff..3be662a1e01e 100644 --- a/web/src/elements/types.ts +++ b/web/src/elements/types.ts @@ -100,6 +100,7 @@ export interface ContextControllerRegistryMap { key: ContextType, controller: ReactiveContextController, ): void; + delete>(key: ContextType): void; } export interface ReactiveControllerHostRegistry extends ReactiveControllerHost { diff --git a/web/src/flow/FlowExecutor.ts b/web/src/flow/FlowExecutor.ts index 8fcbd814643b..dac9c3bea1a0 100644 --- a/web/src/flow/FlowExecutor.ts +++ b/web/src/flow/FlowExecutor.ts @@ -84,20 +84,26 @@ export class FlowExecutor @property({ type: String, attribute: "slug", useDefault: true }) public flowSlug: string = window.location.pathname.split("/")[3]; - #challenge?: ChallengeTypes; + #challenge: ChallengeTypes | null = null; @property({ attribute: false }) - public set challenge(value: ChallengeTypes | undefined) { + public set challenge(value: ChallengeTypes | null) { + const previousValue = this.#challenge; + const previousTitle = previousValue?.flowInfo?.title; + const nextTitle = value?.flowInfo?.title; + this.#challenge = value; - if (value?.flowInfo?.title) { - document.title = `${value.flowInfo?.title} - ${this.brandingTitle}`; - } else { + + if (!nextTitle) { document.title = this.brandingTitle; + } else if (nextTitle !== previousTitle) { + document.title = `${nextTitle} - ${this.brandingTitle}`; } - this.requestUpdate(); + + this.requestUpdate("challenge", previousValue); } - public get challenge(): ChallengeTypes | undefined { + public get challenge(): ChallengeTypes | null { return this.#challenge; } @@ -116,7 +122,7 @@ export class FlowExecutor @property({ type: Boolean }) public inspectorAvailable?: boolean; - @property({ type: String, attribute: "data-layout", useDefault: true }) + @property({ type: String, attribute: "data-layout", useDefault: true, reflect: true }) public layout: FlowLayoutEnum = FlowExecutor.DefaultLayout; @state() @@ -158,6 +164,8 @@ export class FlowExecutor }); } + //#region Listeners + @listen(AKSessionAuthenticatedEvent) protected sessionAuthenticatedListener = () => { if (!document.hidden) { @@ -174,11 +182,7 @@ export class FlowExecutor WebsocketClient.close(); } - public async firstUpdated(): Promise { - if (this.can(CapabilitiesEnum.CanDebug)) { - this.inspectorAvailable = true; - } - + protected refresh = () => { this.loading = true; return new FlowsApi(DEFAULT_CONFIG) @@ -186,11 +190,7 @@ export class FlowExecutor flowSlug: this.flowSlug, query: window.location.search.substring(1), }) - .then((challenge: ChallengeTypes) => { - if (this.inspectorOpen) { - window.dispatchEvent(new AKFlowAdvanceEvent()); - } - + .then((challenge) => { this.challenge = challenge; if (this.challenge.flowInfo) { @@ -209,6 +209,20 @@ export class FlowExecutor .finally(() => { this.loading = false; }); + }; + + public async firstUpdated(changed: PropertyValues): Promise { + super.firstUpdated(changed); + + if (this.can(CapabilitiesEnum.CanDebug)) { + this.inspectorAvailable = true; + } + + this.refresh().then(() => { + if (this.inspectorOpen) { + window.dispatchEvent(new AKFlowAdvanceEvent()); + } + }); } // DOM post-processing has to happen after the render. diff --git a/web/src/flow/stages/base.ts b/web/src/flow/stages/base.ts index 565974a8738e..49d6fba61a15 100644 --- a/web/src/flow/stages/base.ts +++ b/web/src/flow/stages/base.ts @@ -7,6 +7,8 @@ import { FocusTarget } from "#elements/utils/focus"; import { FlowUserDetails } from "#flow/FormStatic"; +import { ConsoleLogger } from "#logger/browser"; + import { ContextualFlowInfo, CurrentBrand, ErrorDetail } from "@goauthentik/api"; import { html, LitElement, nothing, PropertyValues } from "lit"; @@ -63,6 +65,8 @@ export abstract class BaseStage< delegatesFocus: true, }; + protected logger = ConsoleLogger.prefix(`flow:${this.tagName.toLowerCase()}`); + // TODO: Should have a property but this needs some refactoring first. // @property({ attribute: false }) public host!: StageHost; @@ -135,18 +139,18 @@ export abstract class BaseStage< } } - return this.host?.submit(payload).then((successful) => { + return this.host?.submit(payload).then(async (successful) => { if (successful) { - this.onSubmitSuccess(); + await this.onSubmitSuccess?.(payload); } else { - this.onSubmitFailure(); + await this.onSubmitFailure?.(payload); } return successful; }); }; - renderNonFieldErrors() { + protected renderNonFieldErrors() { const nonFieldErrors = this.challenge?.responseErrors?.non_field_errors; if (!nonFieldErrors) { @@ -171,7 +175,7 @@ export abstract class BaseStage< `; } - renderUserInfo() { + protected renderUserInfo() { if (!this.challenge.pendingUser || !this.challenge.pendingUserAvatar) { return nothing; } @@ -186,12 +190,17 @@ export abstract class BaseStage< `; } - onSubmitSuccess(): void { - // Method that can be overridden by stages - return; - } - onSubmitFailure(): void { - // Method that can be overridden by stages - return; - } + /** + * Callback method for successful form submission. + * + * @abstract + */ + protected onSubmitSuccess?(payload: Record): void | Promise; + + /** + * Callback method for failed form submission. + * + * @abstract + */ + protected onSubmitFailure?(payload: Record): void | Promise; } diff --git a/web/src/flow/stages/identification/IdentificationStage.ts b/web/src/flow/stages/identification/IdentificationStage.ts index c017022f2c2d..c5d3a89d7328 100644 --- a/web/src/flow/stages/identification/IdentificationStage.ts +++ b/web/src/flow/stages/identification/IdentificationStage.ts @@ -307,11 +307,11 @@ export class IdentificationStage extends BaseStage< //#endregion - onSubmitSuccess(): void { + protected override onSubmitSuccess(): void { this.#form?.remove(); } - onSubmitFailure(): void { + protected override onSubmitFailure(): void { const captchaInput = this.#captchaInputRef.value; if (captchaInput) { diff --git a/web/src/flow/stages/prompt/PromptStage.ts b/web/src/flow/stages/prompt/PromptStage.ts index 6559274fddda..5f45e6acea78 100644 --- a/web/src/flow/stages/prompt/PromptStage.ts +++ b/web/src/flow/stages/prompt/PromptStage.ts @@ -1,17 +1,17 @@ import "#elements/Divider"; import "#flow/components/ak-flow-card"; -import { formatLocaleDisplayNames, renderLocaleDisplayNames } from "#common/ui/locale/format"; -import { getBestMatchLocale } from "#common/ui/locale/utils"; - import { WithCapabilitiesConfig } from "#elements/mixins/capabilities"; +import { SlottedTemplateResult } from "#elements/types"; import { AKFormErrors } from "#components/ak-field-errors"; import { AKLabel } from "#components/ak-label"; import { BaseStage } from "#flow/stages/base"; +import { LocalePrompt } from "#flow/stages/prompt/components/locale"; import { + CapabilitiesEnum, PromptChallenge, PromptChallengeResponseRequest, PromptTypeEnum, @@ -19,7 +19,7 @@ import { } from "@goauthentik/api"; import { msg } from "@lit/localize"; -import { css, CSSResult, html, nothing, TemplateResult } from "lit"; +import { css, CSSResult, html, nothing } from "lit"; import { customElement } from "lit/decorators.js"; import { unsafeHTML } from "lit/directives/unsafe-html.js"; @@ -33,9 +33,6 @@ import PFLogin from "@patternfly/patternfly/components/Login/login.css"; import PFTitle from "@patternfly/patternfly/components/Title/title.css"; import PFBase from "@patternfly/patternfly/patternfly-base.css"; -// Fixes horizontal rule
warning in select dropdowns. -/* eslint-disable lit/no-invalid-html */ - @customElement("ak-stage-prompt") export class PromptStage extends WithCapabilitiesConfig( BaseStage, @@ -59,7 +56,7 @@ export class PromptStage extends WithCapabilitiesConfig( `, ]; - renderPromptInner(prompt: StagePrompt): TemplateResult { + protected renderPromptInner(prompt: StagePrompt): SlottedTemplateResult { const fieldId = `field-${prompt.fieldKey}`; switch (prompt.type) { @@ -219,37 +216,19 @@ ${prompt.initialValue} `; })}`; case PromptTypeEnum.AkLocale: { - const entries = formatLocaleDisplayNames(this.activeLanguageTag); - - const currentLanguageTag = prompt.initialValue - ? getBestMatchLocale(prompt.initialValue) - : null; - - return html``; + return LocalePrompt({ + activeLanguageTag: this.activeLanguageTag, + prompt, + fieldId, + debug: this.can(CapabilitiesEnum.CanDebug), + }); } default: return html`

invalid type '${prompt.type}'

`; } } - renderPromptHelpText(prompt: StagePrompt) { + protected renderPromptHelpText(prompt: StagePrompt) { if (!prompt.subText) { return nothing; } @@ -257,7 +236,7 @@ ${prompt.initialValue}${unsafeHTML(prompt.subText)}

`; } - shouldRenderInWrapper(prompt: StagePrompt): boolean { + protected shouldRenderInWrapper(prompt: StagePrompt): boolean { // Special types that aren't rendered in a wrapper return !( prompt.type === PromptTypeEnum.Static || @@ -266,7 +245,7 @@ ${prompt.initialValue} @@ -303,7 +282,7 @@ ${prompt.initialValue} ${msg("Form actions")}