Compare commits
16 Commits
Author | SHA1 | Date | |
---|---|---|---|
79a580b4a5 | |||
994f1f8d3d | |||
a2ea81b3b8 | |||
a0461e3ef0 | |||
5357626317 | |||
552c95c59e | |||
50590697ca | |||
e261736fa3 | |||
263f55fdd3 | |||
2b7f8a24a3 | |||
dc4eac1a04 | |||
53a427d190 | |||
ae969f91ac | |||
c83319d6f3 | |||
329b4cb0fb | |||
533f5992d1 |
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "keycloakify",
|
||||
"version": "11.3.10",
|
||||
"version": "11.3.17",
|
||||
"description": "Framework to create custom Keycloak UIs",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -16,7 +16,7 @@ import { startRebuildOnSrcChange } from "./shared/startRebuildOnSrcChange";
|
||||
.filter(
|
||||
basename =>
|
||||
basename.includes("starter") &&
|
||||
basename.includes("angular") &&
|
||||
basename.includes("keycloakify") &&
|
||||
fs.statSync(pathJoin(parentDirPath, basename)).isDirectory()
|
||||
);
|
||||
|
||||
|
@ -16,10 +16,20 @@ import { assert, Equals } from "tsafe/assert";
|
||||
import type { BuildContext } from "./shared/buildContext";
|
||||
import chalk from "chalk";
|
||||
import { runFormat } from "./tools/runFormat";
|
||||
import { maybeDelegateCommandToCustomHandler } from "./shared/customHandler_delegate";
|
||||
|
||||
export async function command(params: { buildContext: BuildContext }) {
|
||||
const { buildContext } = params;
|
||||
|
||||
const { hasBeenHandled } = maybeDelegateCommandToCustomHandler({
|
||||
commandName: "add-story",
|
||||
buildContext
|
||||
});
|
||||
|
||||
if (hasBeenHandled) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(chalk.cyan("Theme type:"));
|
||||
|
||||
const themeType = await (async () => {
|
||||
|
@ -4,6 +4,7 @@ import { join as pathJoin } from "path";
|
||||
import { existsAsync } from "./tools/fs.existsAsync";
|
||||
import { maybeDelegateCommandToCustomHandler } from "./shared/customHandler_delegate";
|
||||
import { runFormat } from "./tools/runFormat";
|
||||
import * as crypto from "crypto";
|
||||
|
||||
export async function command(params: { buildContext: BuildContext }) {
|
||||
const { buildContext } = params;
|
||||
@ -19,86 +20,106 @@ export async function command(params: { buildContext: BuildContext }) {
|
||||
|
||||
const filePath = pathJoin(buildContext.themeSrcDirPath, `kc.gen.tsx`);
|
||||
|
||||
const currentContent = (await existsAsync(filePath))
|
||||
? await fs.readFile(filePath)
|
||||
: undefined;
|
||||
|
||||
const hasLoginTheme = buildContext.implementedThemeTypes.login.isImplemented;
|
||||
const hasAccountTheme = buildContext.implementedThemeTypes.account.isImplemented;
|
||||
|
||||
const newContent = Buffer.from(
|
||||
[
|
||||
``,
|
||||
`// This file is auto-generated by Keycloakify, do not modify it manually.`,
|
||||
``,
|
||||
`import { lazy, Suspense, type ReactNode } from "react";`,
|
||||
``,
|
||||
`export type ThemeName = ${buildContext.themeNames.map(themeName => `"${themeName}"`).join(" | ")};`,
|
||||
``,
|
||||
`export const themeNames: ThemeName[] = [${buildContext.themeNames.map(themeName => `"${themeName}"`).join(", ")}];`,
|
||||
``,
|
||||
`export type KcEnvName = ${buildContext.environmentVariables.length === 0 ? "never" : buildContext.environmentVariables.map(({ name }) => `"${name}"`).join(" | ")};`,
|
||||
``,
|
||||
`export const kcEnvNames: KcEnvName[] = [${buildContext.environmentVariables.map(({ name }) => `"${name}"`).join(", ")}];`,
|
||||
``,
|
||||
`export const kcEnvDefaults: Record<KcEnvName, string> = ${JSON.stringify(
|
||||
Object.fromEntries(
|
||||
buildContext.environmentVariables.map(
|
||||
({ name, default: defaultValue }) => [name, defaultValue]
|
||||
)
|
||||
),
|
||||
null,
|
||||
2
|
||||
)};`,
|
||||
``,
|
||||
`export type KcContext =`,
|
||||
hasLoginTheme && ` | import("./login/KcContext").KcContext`,
|
||||
hasAccountTheme && ` | import("./account/KcContext").KcContext`,
|
||||
` ;`,
|
||||
``,
|
||||
`declare global {`,
|
||||
` interface Window {`,
|
||||
` kcContext?: KcContext;`,
|
||||
` }`,
|
||||
`}`,
|
||||
``,
|
||||
hasLoginTheme &&
|
||||
`export const KcLoginPage = lazy(() => import("./login/KcPage"));`,
|
||||
hasAccountTheme &&
|
||||
`export const KcAccountPage = lazy(() => import("./account/KcPage"));`,
|
||||
``,
|
||||
`export function KcPage(`,
|
||||
` props: {`,
|
||||
` kcContext: KcContext;`,
|
||||
` fallback?: ReactNode;`,
|
||||
` }`,
|
||||
`) {`,
|
||||
` const { kcContext, fallback } = props;`,
|
||||
` return (`,
|
||||
` <Suspense fallback={fallback}>`,
|
||||
` {(() => {`,
|
||||
` switch (kcContext.themeType) {`,
|
||||
hasLoginTheme &&
|
||||
` case "login": return <KcLoginPage kcContext={kcContext} />;`,
|
||||
hasAccountTheme &&
|
||||
` case "account": return <KcAccountPage kcContext={kcContext} />;`,
|
||||
` }`,
|
||||
` })()}`,
|
||||
` </Suspense>`,
|
||||
` );`,
|
||||
`}`,
|
||||
``
|
||||
]
|
||||
.filter(item => typeof item === "string")
|
||||
.join("\n"),
|
||||
"utf8"
|
||||
);
|
||||
const newContent = [
|
||||
``,
|
||||
`/* eslint-disable */`,
|
||||
``,
|
||||
`// @ts-nocheck`,
|
||||
``,
|
||||
`// noinspection JSUnusedGlobalSymbols`,
|
||||
``,
|
||||
`import { lazy, Suspense, type ReactNode } from "react";`,
|
||||
``,
|
||||
`export type ThemeName = ${buildContext.themeNames.map(themeName => `"${themeName}"`).join(" | ")};`,
|
||||
``,
|
||||
`export const themeNames: ThemeName[] = [${buildContext.themeNames.map(themeName => `"${themeName}"`).join(", ")}];`,
|
||||
``,
|
||||
`export type KcEnvName = ${buildContext.environmentVariables.length === 0 ? "never" : buildContext.environmentVariables.map(({ name }) => `"${name}"`).join(" | ")};`,
|
||||
``,
|
||||
`export const kcEnvNames: KcEnvName[] = [${buildContext.environmentVariables.map(({ name }) => `"${name}"`).join(", ")}];`,
|
||||
``,
|
||||
`export const kcEnvDefaults: Record<KcEnvName, string> = ${JSON.stringify(
|
||||
Object.fromEntries(
|
||||
buildContext.environmentVariables.map(
|
||||
({ name, default: defaultValue }) => [name, defaultValue]
|
||||
)
|
||||
),
|
||||
null,
|
||||
2
|
||||
)};`,
|
||||
``,
|
||||
`type KcContext =`,
|
||||
hasLoginTheme && ` | import("./login/KcContext").KcContext`,
|
||||
hasAccountTheme && ` | import("./account/KcContext").KcContext`,
|
||||
` ;`,
|
||||
``,
|
||||
`declare global {`,
|
||||
` interface Window {`,
|
||||
` kcContext?: KcContext;`,
|
||||
` }`,
|
||||
`}`,
|
||||
``,
|
||||
hasLoginTheme &&
|
||||
`export const KcLoginPage = lazy(() => import("./login/KcPage"));`,
|
||||
hasAccountTheme &&
|
||||
`export const KcAccountPage = lazy(() => import("./account/KcPage"));`,
|
||||
``,
|
||||
`export function KcPage(`,
|
||||
` props: {`,
|
||||
` kcContext: KcContext;`,
|
||||
` fallback?: ReactNode;`,
|
||||
` }`,
|
||||
`) {`,
|
||||
` const { kcContext, fallback } = props;`,
|
||||
` return (`,
|
||||
` <Suspense fallback={fallback}>`,
|
||||
` {(() => {`,
|
||||
` switch (kcContext.themeType) {`,
|
||||
hasLoginTheme &&
|
||||
` case "login": return <KcLoginPage kcContext={kcContext} />;`,
|
||||
hasAccountTheme &&
|
||||
` case "account": return <KcAccountPage kcContext={kcContext} />;`,
|
||||
` }`,
|
||||
` })()}`,
|
||||
` </Suspense>`,
|
||||
` );`,
|
||||
`}`,
|
||||
``
|
||||
]
|
||||
.filter(item => typeof item === "string")
|
||||
.join("\n");
|
||||
|
||||
const hash = crypto.createHash("sha256").update(newContent).digest("hex");
|
||||
|
||||
skip_if_no_changes: {
|
||||
if (!(await existsAsync(filePath))) {
|
||||
break skip_if_no_changes;
|
||||
}
|
||||
|
||||
const currentContent = (await fs.readFile(filePath)).toString("utf8");
|
||||
|
||||
if (!currentContent.includes(hash)) {
|
||||
break skip_if_no_changes;
|
||||
}
|
||||
|
||||
if (currentContent !== undefined && currentContent.equals(newContent)) {
|
||||
return;
|
||||
}
|
||||
|
||||
await fs.writeFile(filePath, newContent);
|
||||
await fs.writeFile(
|
||||
filePath,
|
||||
Buffer.from(
|
||||
[
|
||||
`// This file is auto-generated by the \`update-kc-gen\` command. Do not edit it manually.`,
|
||||
`// Hash: ${hash}`,
|
||||
``,
|
||||
newContent
|
||||
].join("\n"),
|
||||
"utf8"
|
||||
)
|
||||
);
|
||||
|
||||
runFormat({ packageJsonFilePath: buildContext.packageJsonFilePath });
|
||||
|
||||
|
@ -102,7 +102,7 @@ export declare namespace KcContext {
|
||||
showTryAnotherWayLink?: boolean;
|
||||
attemptedUsername?: string;
|
||||
};
|
||||
scripts: string[];
|
||||
scripts?: string[];
|
||||
message?: {
|
||||
type: "success" | "warning" | "error" | "info";
|
||||
summary: string;
|
||||
|
@ -10,7 +10,7 @@ export type KcContextLike = {
|
||||
resourcesPath: string;
|
||||
ssoLoginInOtherTabsUrl: string;
|
||||
};
|
||||
scripts: string[];
|
||||
scripts?: string[];
|
||||
};
|
||||
|
||||
assert<keyof KcContextLike extends keyof KcContext ? true : false>();
|
||||
@ -45,10 +45,12 @@ export function useInitialize(params: {
|
||||
type: "module",
|
||||
src: `${url.resourcesPath}/js/menu-button-links.js`
|
||||
},
|
||||
...scripts.map(src => ({
|
||||
type: "text/javascript" as const,
|
||||
src
|
||||
})),
|
||||
...(scripts === undefined
|
||||
? []
|
||||
: scripts.map(src => ({
|
||||
type: "text/javascript" as const,
|
||||
src
|
||||
}))),
|
||||
{
|
||||
type: "module",
|
||||
textContent: `
|
||||
|
@ -16,6 +16,9 @@ import type { GenericI18n_noJsx } from "./GenericI18n_noJsx";
|
||||
|
||||
export type KcContextLike = {
|
||||
themeName: string;
|
||||
realm: {
|
||||
internationalizationEnabled: boolean;
|
||||
};
|
||||
locale?: {
|
||||
currentLanguageTag: string;
|
||||
supported: { languageTag: string; url: string; label: string }[];
|
||||
@ -91,14 +94,16 @@ export function createGetI18n<
|
||||
return cachedResult;
|
||||
}
|
||||
|
||||
const kcContextLocale = params.kcContext.realm.internationalizationEnabled ? params.kcContext.locale : undefined;
|
||||
|
||||
{
|
||||
const currentLanguageTag = kcContext.locale?.currentLanguageTag ?? FALLBACK_LANGUAGE_TAG;
|
||||
const currentLanguageTag = kcContextLocale?.currentLanguageTag ?? FALLBACK_LANGUAGE_TAG;
|
||||
const html = document.querySelector("html");
|
||||
assert(html !== null);
|
||||
html.lang = currentLanguageTag;
|
||||
|
||||
const isRtl = (() => {
|
||||
const { rtl } = kcContext.locale ?? {};
|
||||
const { rtl } = kcContextLocale ?? {};
|
||||
|
||||
if (rtl !== undefined) {
|
||||
return rtl;
|
||||
@ -154,11 +159,11 @@ export function createGetI18n<
|
||||
}
|
||||
|
||||
from_server: {
|
||||
if (kcContext.locale === undefined) {
|
||||
if (kcContextLocale === undefined) {
|
||||
break from_server;
|
||||
}
|
||||
|
||||
const supportedEntry = kcContext.locale.supported.find(entry => entry.languageTag === languageTag);
|
||||
const supportedEntry = kcContextLocale.supported.find(entry => entry.languageTag === languageTag);
|
||||
|
||||
if (supportedEntry === undefined) {
|
||||
break from_server;
|
||||
@ -180,7 +185,7 @@ export function createGetI18n<
|
||||
};
|
||||
|
||||
const currentLanguage: I18n["currentLanguage"] = (() => {
|
||||
const languageTag = id<string>(kcContext.locale?.currentLanguageTag ?? FALLBACK_LANGUAGE_TAG) as LanguageTag;
|
||||
const languageTag = id<string>(kcContextLocale?.currentLanguageTag ?? FALLBACK_LANGUAGE_TAG) as LanguageTag;
|
||||
|
||||
return {
|
||||
languageTag,
|
||||
@ -191,8 +196,8 @@ export function createGetI18n<
|
||||
const enabledLanguages: I18n["enabledLanguages"] = (() => {
|
||||
const enabledLanguages: I18n["enabledLanguages"] = [];
|
||||
|
||||
if (kcContext.locale !== undefined) {
|
||||
for (const entry of kcContext.locale.supported ?? []) {
|
||||
if (kcContextLocale !== undefined) {
|
||||
for (const entry of kcContextLocale.supported ?? []) {
|
||||
const languageTag = id<string>(entry.languageTag) as LanguageTag;
|
||||
|
||||
enabledLanguages.push({
|
||||
|
@ -92,7 +92,8 @@ export function createUseI18n<
|
||||
|
||||
const styleElement = document.createElement("style");
|
||||
styleElement.attributes.setNamedItem(document.createAttribute(attributeName));
|
||||
(styleElement.textContent = `[data-kc-msg] { display: inline-block; }`), document.head.prepend(styleElement);
|
||||
styleElement.textContent = `[data-kc-msg] { display: inline-block; }`;
|
||||
document.head.prepend(styleElement);
|
||||
}
|
||||
|
||||
const { getI18n } = createGetI18n({ extraLanguageTranslations, messagesByLanguageTag_themeDefined });
|
||||
|
@ -70,9 +70,9 @@ export default function LoginRecoveryAuthnCodeConfig(props: PageProps<Extract<Kc
|
||||
type="checkbox"
|
||||
id="kcRecoveryCodesConfirmationCheck"
|
||||
name="kcRecoveryCodesConfirmationCheck"
|
||||
onChange={function () {
|
||||
//@ts-expect-error: This is code from the original theme, we trust it.
|
||||
document.getElementById("saveRecoveryAuthnCodesBtn").disabled = !this.checked;
|
||||
onChange={event => {
|
||||
//@ts-expect-error: This is inherited from the original code
|
||||
document.getElementById("saveRecoveryAuthnCodesBtn").disabled = !event.target.checked;
|
||||
}}
|
||||
/>
|
||||
<label htmlFor="kcRecoveryCodesConfirmationCheck">{msg("recovery-codes-confirmation-message")}</label>
|
||||
|
@ -16,6 +16,7 @@ import {
|
||||
} from "../bin/shared/buildContext";
|
||||
import MagicString from "magic-string";
|
||||
import { command as updateKcGenCommand } from "../bin/update-kc-gen";
|
||||
import { replaceAll } from "../bin/tools/String.prototype.replaceAll";
|
||||
|
||||
export namespace keycloakify {
|
||||
export type Params = BuildOptions & {
|
||||
@ -130,6 +131,8 @@ export function keycloakify(params: keycloakify.Params) {
|
||||
await updateKcGenCommand({ buildContext });
|
||||
},
|
||||
transform: (code, id) => {
|
||||
id = replaceAll(id, "/", pathSep);
|
||||
|
||||
assert(command !== undefined);
|
||||
assert(shouldGenerateSourcemap !== undefined);
|
||||
|
||||
|
Reference in New Issue
Block a user