Compare commits
25 Commits
Author | SHA1 | Date | |
---|---|---|---|
ad275e4c34 | |||
060b9fe0de | |||
17b24d14ed | |||
2d278b0680 | |||
fb5975e4f1 | |||
24fccaf513 | |||
293953aa1b | |||
1049e312f9 | |||
a2db250600 | |||
cf7fe8c337 | |||
f5350097bf | |||
1cb5dd461b | |||
845599a5e8 | |||
0cc02c292f | |||
1919702326 | |||
0c0052e1cd | |||
78622770ec | |||
7b86727394 | |||
0965f8648e | |||
98974b4367 | |||
597bcadd9e | |||
4d9aabcb91 | |||
1606c2884d | |||
12f69b593f | |||
1ca45f90d0 |
32
CHANGELOG.md
32
CHANGELOG.md
@ -1,3 +1,35 @@
|
|||||||
|
### **0.3.8** (2021-03-22)
|
||||||
|
|
||||||
|
- Make standalone mode the default
|
||||||
|
|
||||||
|
### **0.3.7** (2021-03-22)
|
||||||
|
|
||||||
|
- (test) external asset mode by default
|
||||||
|
|
||||||
|
### **0.3.6** (2021-03-22)
|
||||||
|
|
||||||
|
- Fix previous release
|
||||||
|
|
||||||
|
### **0.3.5** (2021-03-22)
|
||||||
|
|
||||||
|
- support homepage with urlPath
|
||||||
|
|
||||||
|
### **0.3.4** (2021-03-22)
|
||||||
|
|
||||||
|
- Bugfix: Import assets from CSS
|
||||||
|
|
||||||
|
### **0.3.3** (2021-03-22)
|
||||||
|
|
||||||
|
- Fix submit not receving correct text
|
||||||
|
|
||||||
|
### **0.3.2** (2021-03-21)
|
||||||
|
|
||||||
|
- Fix broken previous release
|
||||||
|
|
||||||
|
### **0.3.1** (2021-03-21)
|
||||||
|
|
||||||
|
- kcHeaderClass can be updated after initial mount
|
||||||
|
|
||||||
## **0.3.0** (2021-03-20)
|
## **0.3.0** (2021-03-20)
|
||||||
|
|
||||||
- Bump version
|
- Bump version
|
||||||
|
71
README.md
71
README.md
@ -45,12 +45,11 @@ Tested with the following Keycloak versions:
|
|||||||
- [Just changing the look](#just-changing-the-look)
|
- [Just changing the look](#just-changing-the-look)
|
||||||
- [Changing the look **and** feel](#changing-the-look-and-feel)
|
- [Changing the look **and** feel](#changing-the-look-and-feel)
|
||||||
- [Hot reload](#hot-reload)
|
- [Hot reload](#hot-reload)
|
||||||
- [Implement context persistance (optional)](#implement-context-persistance-optional)
|
|
||||||
- [GitHub Actions](#github-actions)
|
- [GitHub Actions](#github-actions)
|
||||||
- [REQUIREMENTS](#requirements)
|
- [REQUIREMENTS](#requirements)
|
||||||
- [API Reference](#api-reference)
|
- [API Reference](#api-reference)
|
||||||
- [The build tool](#the-build-tool)
|
- [The build tool](#the-build-tool)
|
||||||
- [The fronted lib ( imported into your react app )](#the-fronted-lib--imported-into-your-react-app-)
|
- [Implement context persistance (optional)](#implement-context-persistance-optional)
|
||||||
|
|
||||||
# How to use
|
# How to use
|
||||||
## Setting up the build tool
|
## Setting up the build tool
|
||||||
@ -152,6 +151,37 @@ Checkout [this concrete example](https://github.com/garronej/keycloakify-demo-ap
|
|||||||
|
|
||||||
*NOTE: keycloak-react-theming was renamed keycloakify since this video was recorded*
|
*NOTE: keycloak-react-theming was renamed keycloakify since this video was recorded*
|
||||||
[](https://youtu.be/xTz0Rj7i2v8)
|
[](https://youtu.be/xTz0Rj7i2v8)
|
||||||
|
|
||||||
|
# GitHub Actions
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
[Here is a demo repo](https://github.com/garronej/keycloakify-demo-app) to show how to automate
|
||||||
|
the building and publishing of the theme (the .jar file).
|
||||||
|
|
||||||
|
# REQUIREMENTS
|
||||||
|
|
||||||
|
This tools assumes you are bundling your app with Webpack (tested with 4.44.2) .
|
||||||
|
It assumes there is a `build/` directory at the root of your react project directory containing a `index.html` file
|
||||||
|
and a `static/` directory generated by webpack.
|
||||||
|
|
||||||
|
**All this is defaults with [`create-react-app`](https://create-react-app.dev)** (tested with 4.0.3=)
|
||||||
|
|
||||||
|
- For building the theme: `mvn` (Maven) must be installed
|
||||||
|
- For development, (testing the theme in a local container ): `rm`, `mkdir`, `wget`, `unzip` are assumed to be available
|
||||||
|
and `docker` up and running.
|
||||||
|
|
||||||
|
NOTE: This build tool has only be tested on MacOS.
|
||||||
|
|
||||||
|
# API Reference
|
||||||
|
|
||||||
|
## The build tool
|
||||||
|
|
||||||
|
Part of the lib that runs with node, at build time.
|
||||||
|
|
||||||
|
- `npx build-keycloak-theme`: Builds the theme, the CWD is assumed to be the root of your react project.
|
||||||
|
- `npx download-sample-keycloak-themes`: Downloads the keycloak default themes (for development purposes)
|
||||||
|
|
||||||
# Implement context persistance (optional)
|
# Implement context persistance (optional)
|
||||||
|
|
||||||
If, before logging in, a user has selected a specific language
|
If, before logging in, a user has selected a specific language
|
||||||
@ -210,40 +240,3 @@ keycloakInstance.init({
|
|||||||
If you really want to go the extra miles and avoid having the white
|
If you really want to go the extra miles and avoid having the white
|
||||||
flash of the blank html before the js bundle have been evaluated
|
flash of the blank html before the js bundle have been evaluated
|
||||||
[here is a snippet](https://github.com/InseeFrLab/onyxia-ui/blob/a77eb502870cfe6878edd0d956c646d28746d053/public/index.html#L5-L54) that you can place in your `public/index.html` if you are using `powerhooks/useGlobalState`.
|
[here is a snippet](https://github.com/InseeFrLab/onyxia-ui/blob/a77eb502870cfe6878edd0d956c646d28746d053/public/index.html#L5-L54) that you can place in your `public/index.html` if you are using `powerhooks/useGlobalState`.
|
||||||
|
|
||||||
# GitHub Actions
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
[Here is a demo repo](https://github.com/garronej/keycloakify-demo-app) to show how to automate
|
|
||||||
the building and publishing of the theme (the .jar file).
|
|
||||||
|
|
||||||
# REQUIREMENTS
|
|
||||||
|
|
||||||
This tools assumes you are bundling your app with Webpack (tested with 4.44.2) .
|
|
||||||
It assumes there is a `build/` directory at the root of your react project directory containing a `index.html` file
|
|
||||||
and a `static/` directory generated by webpack.
|
|
||||||
|
|
||||||
**All this is defaults with [`create-react-app`](https://create-react-app.dev)** (tested with 4.0.3=)
|
|
||||||
|
|
||||||
- For building the theme: `mvn` (Maven) must be installed
|
|
||||||
- For development, (testing the theme in a local container ): `rm`, `mkdir`, `wget`, `unzip` are assumed to be available
|
|
||||||
and `docker` up and running.
|
|
||||||
|
|
||||||
NOTE: This build tool has only be tested on MacOS.
|
|
||||||
|
|
||||||
# API Reference
|
|
||||||
|
|
||||||
## The build tool
|
|
||||||
|
|
||||||
Part of the lib that runs with node, at build time.
|
|
||||||
|
|
||||||
- `npx build-keycloak-theme`: Builds the theme, the CWD is assumed to be the root of your react project.
|
|
||||||
- `npx download-sample-keycloak-themes`: Downloads the keycloak default themes (for development purposes)
|
|
||||||
|
|
||||||
## The fronted lib ( imported into your react app )
|
|
||||||
|
|
||||||
Part of the lib that you import in your react project and runs on the browser.
|
|
||||||
|
|
||||||
**TODO**
|
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "keycloakify",
|
"name": "keycloakify",
|
||||||
"version": "0.3.0",
|
"version": "0.3.8",
|
||||||
"description": "Keycloak theme generator for Reacts app",
|
"description": "Keycloak theme generator for Reacts app",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
@ -11,9 +11,9 @@ import { objectKeys } from "evt/tools/typeSafety/objectKeys";
|
|||||||
|
|
||||||
export const pageIds = ["login.ftl", "register.ftl", "info.ftl", "error.ftl", "login-reset-password.ftl", "login-verify-email.ftl"] as const;
|
export const pageIds = ["login.ftl", "register.ftl", "info.ftl", "error.ftl", "login-reset-password.ftl", "login-verify-email.ftl"] as const;
|
||||||
|
|
||||||
export type PageId = typeof pageIds[number];
|
export type PageId = typeof pageIds[number];
|
||||||
|
|
||||||
function loadAdjacentFile(fileBasename: string){
|
function loadAdjacentFile(fileBasename: string) {
|
||||||
return fs.readFileSync(pathJoin(__dirname, fileBasename))
|
return fs.readFileSync(pathJoin(__dirname, fileBasename))
|
||||||
.toString("utf8");
|
.toString("utf8");
|
||||||
};
|
};
|
||||||
@ -29,15 +29,25 @@ function loadFtlFile(ftlFileBasename: PageId | "template.ftl") {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type Mode = {
|
||||||
|
type: "standalone";
|
||||||
|
urlPathname: string;
|
||||||
|
} | {
|
||||||
|
type: "external assets";
|
||||||
|
urlPathname: string;
|
||||||
|
urlOrigin: string;
|
||||||
|
}
|
||||||
|
|
||||||
export function generateFtlFilesCodeFactory(
|
export function generateFtlFilesCodeFactory(
|
||||||
params: {
|
params: {
|
||||||
ftlValuesGlobalName: string;
|
ftlValuesGlobalName: string;
|
||||||
cssGlobalsToDefine: Record<string, string>;
|
cssGlobalsToDefine: Record<string, string>;
|
||||||
indexHtmlCode: string;
|
indexHtmlCode: string;
|
||||||
|
mode: Mode;
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
|
|
||||||
const { ftlValuesGlobalName, cssGlobalsToDefine, indexHtmlCode } = params;
|
const { ftlValuesGlobalName, cssGlobalsToDefine, indexHtmlCode, mode } = params;
|
||||||
|
|
||||||
const $ = cheerio.load(indexHtmlCode);
|
const $ = cheerio.load(indexHtmlCode);
|
||||||
|
|
||||||
@ -45,7 +55,8 @@ export function generateFtlFilesCodeFactory(
|
|||||||
|
|
||||||
const { fixedJsCode } = replaceImportFromStaticInJsCode({
|
const { fixedJsCode } = replaceImportFromStaticInJsCode({
|
||||||
ftlValuesGlobalName,
|
ftlValuesGlobalName,
|
||||||
"jsCode": $(element).html()!
|
"jsCode": $(element).html()!,
|
||||||
|
mode
|
||||||
});
|
});
|
||||||
|
|
||||||
$(element).text(fixedJsCode);
|
$(element).text(fixedJsCode);
|
||||||
@ -60,11 +71,28 @@ export function generateFtlFilesCodeFactory(
|
|||||||
|
|
||||||
const href = $(element).attr(attrName);
|
const href = $(element).attr(attrName);
|
||||||
|
|
||||||
if (!href?.startsWith("/")) {
|
if (href === undefined) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$(element).attr(attrName, "${url.resourcesPath}/build" + href);
|
switch (mode.type) {
|
||||||
|
case "external assets":
|
||||||
|
$(element).attr(
|
||||||
|
attrName,
|
||||||
|
href.replace(/^\//, `${mode.urlOrigin}/`)
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case "standalone":
|
||||||
|
$(element).attr(
|
||||||
|
attrName,
|
||||||
|
href.replace(
|
||||||
|
new RegExp(`^${mode.urlPathname.replace(/\//g, "\\/")}`),
|
||||||
|
"${url.resourcesPath}/build/"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@ -89,9 +117,10 @@ export function generateFtlFilesCodeFactory(
|
|||||||
...(Object.keys(cssGlobalsToDefine).length === 0 ? [] : [
|
...(Object.keys(cssGlobalsToDefine).length === 0 ? [] : [
|
||||||
'',
|
'',
|
||||||
'<style>',
|
'<style>',
|
||||||
generateCssCodeToDefineGlobals(
|
generateCssCodeToDefineGlobals({
|
||||||
{ cssGlobalsToDefine }
|
cssGlobalsToDefine,
|
||||||
).cssCodeToPrependInHead,
|
"urlPathname": mode.urlPathname
|
||||||
|
}).cssCodeToPrependInHead,
|
||||||
'</style>',
|
'</style>',
|
||||||
''
|
''
|
||||||
]),
|
]),
|
||||||
|
@ -6,23 +6,25 @@ import {
|
|||||||
replaceImportFromStaticInCssCode,
|
replaceImportFromStaticInCssCode,
|
||||||
replaceImportFromStaticInJsCode
|
replaceImportFromStaticInJsCode
|
||||||
} from "./replaceImportFromStatic";
|
} from "./replaceImportFromStatic";
|
||||||
import { generateFtlFilesCodeFactory, pageIds } from "./generateFtl";
|
import { generateFtlFilesCodeFactory, pageIds, Mode } from "./generateFtl";
|
||||||
import { builtinThemesUrl } from "../install-builtin-keycloak-themes";
|
import { builtinThemesUrl } from "../install-builtin-keycloak-themes";
|
||||||
import { downloadAndUnzip } from "../tools/downloadAndUnzip";
|
import { downloadAndUnzip } from "../tools/downloadAndUnzip";
|
||||||
import * as child_process from "child_process";
|
import * as child_process from "child_process";
|
||||||
import { ftlValuesGlobalName } from "./ftlValuesGlobalName";
|
import { ftlValuesGlobalName } from "./ftlValuesGlobalName";
|
||||||
import { resourcesCommonPath, resourcesPath, subDirOfPublicDirBasename } from "../../lib/kcContextMocks/urlResourcesPath";
|
import { resourcesCommonPath, resourcesPath, subDirOfPublicDirBasename } from "../../lib/kcContextMocks/urlResourcesPath";
|
||||||
import { isInside } from "../tools/isInside";
|
import { isInside } from "../tools/isInside";
|
||||||
|
|
||||||
|
|
||||||
export function generateKeycloakThemeResources(
|
export function generateKeycloakThemeResources(
|
||||||
params: {
|
params: {
|
||||||
themeName: string;
|
themeName: string;
|
||||||
reactAppBuildDirPath: string;
|
reactAppBuildDirPath: string;
|
||||||
keycloakThemeBuildingDirPath: string;
|
keycloakThemeBuildingDirPath: string;
|
||||||
|
mode: Mode;
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
|
|
||||||
const { themeName, reactAppBuildDirPath, keycloakThemeBuildingDirPath } = params;
|
const { themeName, reactAppBuildDirPath, keycloakThemeBuildingDirPath, mode } = params;
|
||||||
|
|
||||||
const themeDirPath = pathJoin(keycloakThemeBuildingDirPath, "src", "main", "resources", "theme", themeName, "login");
|
const themeDirPath = pathJoin(keycloakThemeBuildingDirPath, "src", "main", "resources", "theme", themeName, "login");
|
||||||
|
|
||||||
@ -43,30 +45,34 @@ export function generateKeycloakThemeResources(
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (mode.type === "standalone") {
|
||||||
|
|
||||||
if (/\.css?$/i.test(filePath)) {
|
if (/\.css?$/i.test(filePath)) {
|
||||||
|
|
||||||
const { cssGlobalsToDefine, fixedCssCode } = replaceImportFromStaticInCssCode(
|
const { cssGlobalsToDefine, fixedCssCode } = replaceImportFromStaticInCssCode(
|
||||||
{ "cssCode": sourceCode.toString("utf8") }
|
{ "cssCode": sourceCode.toString("utf8") }
|
||||||
);
|
);
|
||||||
|
|
||||||
allCssGlobalsToDefine = {
|
allCssGlobalsToDefine = {
|
||||||
...allCssGlobalsToDefine,
|
...allCssGlobalsToDefine,
|
||||||
...cssGlobalsToDefine
|
...cssGlobalsToDefine
|
||||||
};
|
};
|
||||||
|
|
||||||
return { "modifiedSourceCode": Buffer.from(fixedCssCode, "utf8") };
|
return { "modifiedSourceCode": Buffer.from(fixedCssCode, "utf8") };
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (/\.js?$/i.test(filePath)) {
|
if (/\.js?$/i.test(filePath)) {
|
||||||
|
|
||||||
const { fixedJsCode } = replaceImportFromStaticInJsCode({
|
const { fixedJsCode } = replaceImportFromStaticInJsCode({
|
||||||
"jsCode": sourceCode.toString("utf8"),
|
"jsCode": sourceCode.toString("utf8"),
|
||||||
ftlValuesGlobalName
|
ftlValuesGlobalName,
|
||||||
});
|
mode
|
||||||
|
});
|
||||||
|
|
||||||
return { "modifiedSourceCode": Buffer.from(fixedJsCode, "utf8") };
|
return { "modifiedSourceCode": Buffer.from(fixedJsCode, "utf8") };
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,7 +86,8 @@ export function generateKeycloakThemeResources(
|
|||||||
ftlValuesGlobalName,
|
ftlValuesGlobalName,
|
||||||
"indexHtmlCode": fs.readFileSync(
|
"indexHtmlCode": fs.readFileSync(
|
||||||
pathJoin(reactAppBuildDirPath, "index.html")
|
pathJoin(reactAppBuildDirPath, "index.html")
|
||||||
).toString("utf8")
|
).toString("utf8"),
|
||||||
|
mode
|
||||||
});
|
});
|
||||||
|
|
||||||
pageIds.forEach(pageId => {
|
pageIds.forEach(pageId => {
|
||||||
|
@ -6,10 +6,13 @@ import type { ParsedPackageJson } from "./generateJavaStackFiles";
|
|||||||
import { join as pathJoin, relative as pathRelative, basename as pathBasename } from "path";
|
import { join as pathJoin, relative as pathRelative, basename as pathBasename } from "path";
|
||||||
import * as child_process from "child_process";
|
import * as child_process from "child_process";
|
||||||
import { generateDebugFiles, containerLaunchScriptBasename } from "./generateDebugFiles";
|
import { generateDebugFiles, containerLaunchScriptBasename } from "./generateDebugFiles";
|
||||||
|
import { URL } from "url";
|
||||||
|
|
||||||
|
|
||||||
const reactProjectDirPath = process.cwd();
|
const reactProjectDirPath = process.cwd();
|
||||||
|
|
||||||
|
const doUseExternalAssets = process.argv[2]?.toLowerCase() === "--external-assets";
|
||||||
|
|
||||||
const parsedPackageJson: ParsedPackageJson = require(pathJoin(reactProjectDirPath, "package.json"));
|
const parsedPackageJson: ParsedPackageJson = require(pathJoin(reactProjectDirPath, "package.json"));
|
||||||
|
|
||||||
export const keycloakThemeBuildingDirPath = pathJoin(reactProjectDirPath, "build_keycloak");
|
export const keycloakThemeBuildingDirPath = pathJoin(reactProjectDirPath, "build_keycloak");
|
||||||
@ -22,7 +25,51 @@ if (require.main === module) {
|
|||||||
generateKeycloakThemeResources({
|
generateKeycloakThemeResources({
|
||||||
keycloakThemeBuildingDirPath,
|
keycloakThemeBuildingDirPath,
|
||||||
"reactAppBuildDirPath": pathJoin(reactProjectDirPath, "build"),
|
"reactAppBuildDirPath": pathJoin(reactProjectDirPath, "build"),
|
||||||
"themeName": parsedPackageJson.name
|
"themeName": parsedPackageJson.name,
|
||||||
|
"mode": (() => {
|
||||||
|
|
||||||
|
|
||||||
|
const url = (() => {
|
||||||
|
|
||||||
|
const { homepage } = parsedPackageJson;
|
||||||
|
|
||||||
|
return homepage === undefined ?
|
||||||
|
undefined :
|
||||||
|
new URL(homepage);
|
||||||
|
|
||||||
|
})();
|
||||||
|
|
||||||
|
const urlPathname =
|
||||||
|
url === undefined ?
|
||||||
|
"/" :
|
||||||
|
url.pathname.replace(/([^/])$/, "$1/");
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return !doUseExternalAssets ?
|
||||||
|
{
|
||||||
|
"type": "standalone",
|
||||||
|
urlPathname
|
||||||
|
} as const
|
||||||
|
:
|
||||||
|
{
|
||||||
|
"type": "external assets",
|
||||||
|
urlPathname,
|
||||||
|
"urlOrigin": (() => {
|
||||||
|
|
||||||
|
if (url === undefined) {
|
||||||
|
console.error("ERROR: You must specify 'homepage' in your package.json");
|
||||||
|
process.exit(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return url.origin;
|
||||||
|
|
||||||
|
})()
|
||||||
|
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
})()
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const { jarFilePath } = generateJavaStackFiles({
|
const { jarFilePath } = generateJavaStackFiles({
|
||||||
|
@ -1,19 +1,39 @@
|
|||||||
|
|
||||||
import * as crypto from "crypto";
|
import * as crypto from "crypto";
|
||||||
|
|
||||||
|
type Mode = {
|
||||||
|
type: "standalone";
|
||||||
|
} | {
|
||||||
|
type: "external assets";
|
||||||
|
urlOrigin: string;
|
||||||
|
urlPathname: string;
|
||||||
|
}
|
||||||
|
|
||||||
export function replaceImportFromStaticInJsCode(
|
export function replaceImportFromStaticInJsCode(
|
||||||
params: {
|
params: {
|
||||||
ftlValuesGlobalName: string;
|
ftlValuesGlobalName: string;
|
||||||
jsCode: string;
|
jsCode: string;
|
||||||
|
mode: Mode;
|
||||||
}
|
}
|
||||||
): { fixedJsCode: string; } {
|
): { fixedJsCode: string; } {
|
||||||
|
|
||||||
const { jsCode, ftlValuesGlobalName } = params;
|
const { jsCode, ftlValuesGlobalName, mode } = params;
|
||||||
|
|
||||||
|
const fixedJsCode = (() => {
|
||||||
|
switch (mode.type) {
|
||||||
|
case "standalone":
|
||||||
|
return jsCode!.replace(
|
||||||
|
/[a-z]+\.[a-z]+\+"static\//g,
|
||||||
|
`window.${ftlValuesGlobalName}.url.resourcesPath + "/build/static/`
|
||||||
|
);
|
||||||
|
case "external assets":
|
||||||
|
return jsCode!.replace(
|
||||||
|
/[a-z]+\.[a-z]+\+"static\//g,
|
||||||
|
`"${mode.urlOrigin}${mode.urlPathname}static/`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
const fixedJsCode = jsCode!.replace(
|
|
||||||
/"static\//g,
|
|
||||||
`window.${ftlValuesGlobalName}.url.resourcesPath.replace(/^\\//,"") + "/build/static/`
|
|
||||||
);
|
|
||||||
|
|
||||||
return { fixedJsCode };
|
return { fixedJsCode };
|
||||||
|
|
||||||
@ -32,7 +52,7 @@ export function replaceImportFromStaticInCssCode(
|
|||||||
|
|
||||||
const cssGlobalsToDefine: Record<string, string> = {};
|
const cssGlobalsToDefine: Record<string, string> = {};
|
||||||
|
|
||||||
new Set(cssCode.match(/(url\(\/[^)]+\))/g) ?? [])
|
new Set(cssCode.match(/url\(\/[^)]+\)[^;}]*/g) ?? [])
|
||||||
.forEach(match =>
|
.forEach(match =>
|
||||||
cssGlobalsToDefine[
|
cssGlobalsToDefine[
|
||||||
"url" + crypto
|
"url" + crypto
|
||||||
@ -60,12 +80,13 @@ export function replaceImportFromStaticInCssCode(
|
|||||||
export function generateCssCodeToDefineGlobals(
|
export function generateCssCodeToDefineGlobals(
|
||||||
params: {
|
params: {
|
||||||
cssGlobalsToDefine: Record<string, string>;
|
cssGlobalsToDefine: Record<string, string>;
|
||||||
|
urlPathname: string;
|
||||||
}
|
}
|
||||||
): {
|
): {
|
||||||
cssCodeToPrependInHead: string;
|
cssCodeToPrependInHead: string;
|
||||||
} {
|
} {
|
||||||
|
|
||||||
const { cssGlobalsToDefine } = params;
|
const { cssGlobalsToDefine, urlPathname } = params;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"cssCodeToPrependInHead": [
|
"cssCodeToPrependInHead": [
|
||||||
@ -73,12 +94,8 @@ export function generateCssCodeToDefineGlobals(
|
|||||||
...Object.keys(cssGlobalsToDefine)
|
...Object.keys(cssGlobalsToDefine)
|
||||||
.map(cssVariableName => [
|
.map(cssVariableName => [
|
||||||
`--${cssVariableName}:`,
|
`--${cssVariableName}:`,
|
||||||
[
|
cssGlobalsToDefine[cssVariableName]
|
||||||
"url(",
|
.replace(new RegExp(`url\\(${urlPathname.replace(/\//g, "\\/")}`, "g"), "url(${url.resourcesPath}/build/")
|
||||||
"${url.resourcesPath}/build" +
|
|
||||||
cssGlobalsToDefine[cssVariableName].match(/^url\(([^)]+)\)$/)![1],
|
|
||||||
")"
|
|
||||||
].join("")
|
|
||||||
].join(" "))
|
].join(" "))
|
||||||
.map(line => ` ${line};`),
|
.map(line => ` ${line};`),
|
||||||
"}"
|
"}"
|
||||||
|
@ -66,7 +66,7 @@ export const LoginResetPassword = memo(({ kcContext, ...props }: { kcContext: Kc
|
|||||||
props.kcButtonBlockClass, props.kcButtonLargeClass
|
props.kcButtonBlockClass, props.kcButtonLargeClass
|
||||||
)}
|
)}
|
||||||
type="submit"
|
type="submit"
|
||||||
defaultValue={msgStr("doSubmit")}
|
value={msgStr("doSubmit")}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -113,7 +113,7 @@ export const Register = memo(({ kcContext, ...props }: { kcContext: KcContext.Re
|
|||||||
|
|
||||||
<div id="kc-form-buttons" className={cx(props.kcFormButtonsClass)}>
|
<div id="kc-form-buttons" className={cx(props.kcFormButtonsClass)}>
|
||||||
<input className={cx(props.kcButtonClass, props.kcButtonPrimaryClass, props.kcButtonBlockClass, props.kcButtonLargeClass)} type="submit"
|
<input className={cx(props.kcButtonClass, props.kcButtonPrimaryClass, props.kcButtonBlockClass, props.kcButtonLargeClass)} type="submit"
|
||||||
defaultValue={msgStr("doRegister")} />
|
value={msgStr("doRegister")} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form >
|
</form >
|
||||||
|
@ -25,8 +25,7 @@ export type TemplateProps = {
|
|||||||
showUsernameNode?: ReactNode;
|
showUsernameNode?: ReactNode;
|
||||||
formNode: ReactNode;
|
formNode: ReactNode;
|
||||||
infoNode?: ReactNode;
|
infoNode?: ReactNode;
|
||||||
} & { kcContext: KcContext.Template; } & KcTemplateProps;
|
} & { kcContext: KcContext.Template; } & KcTemplateProps;
|
||||||
|
|
||||||
|
|
||||||
export const Template = memo((props: TemplateProps) => {
|
export const Template = memo((props: TemplateProps) => {
|
||||||
|
|
||||||
@ -60,34 +59,35 @@ export const Template = memo((props: TemplateProps) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
realm, locale, auth,
|
realm, locale, auth,
|
||||||
url, message, isAppInitiatedAction
|
url, message, isAppInitiatedAction
|
||||||
}= kcContext;
|
} = kcContext;
|
||||||
|
|
||||||
useEffect(()=>{
|
useEffect(() => {
|
||||||
|
|
||||||
if( !realm.internationalizationEnabled ){
|
if (!realm.internationalizationEnabled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
assert( locale !== undefined );
|
assert(locale !== undefined);
|
||||||
|
|
||||||
if( kcLanguageTag === getBestMatchAmongKcLanguageTag(locale.current) ){
|
if (kcLanguageTag === getBestMatchAmongKcLanguageTag(locale.current)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
window.location.href =
|
window.location.href =
|
||||||
locale.supported.find(({ languageTag }) => languageTag === kcLanguageTag)!.url;
|
locale.supported.find(({ languageTag }) => languageTag === kcLanguageTag)!.url;
|
||||||
|
|
||||||
},[kcLanguageTag]);
|
}, [kcLanguageTag]);
|
||||||
|
|
||||||
const [isExtraCssLoaded, setExtraCssLoaded] = useReducer(() => true, false);
|
const [isExtraCssLoaded, setExtraCssLoaded] = useReducer(() => true, false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
||||||
let isUnmounted = false;
|
let isUnmounted = false;
|
||||||
|
const cleanups: (() => void)[] = [];
|
||||||
|
|
||||||
const toArr = (x: string | readonly string[] | undefined) =>
|
const toArr = (x: string | readonly string[] | undefined) =>
|
||||||
typeof x === "string" ? x.split(" ") : x ?? [];
|
typeof x === "string" ? x.split(" ") : x ?? [];
|
||||||
|
|
||||||
Promise.all(
|
Promise.all(
|
||||||
@ -116,15 +116,27 @@ export const Template = memo((props: TemplateProps) => {
|
|||||||
|
|
||||||
if (props.kcHtmlClass !== undefined) {
|
if (props.kcHtmlClass !== undefined) {
|
||||||
|
|
||||||
document.getElementsByTagName("html")[0]
|
const htmlClassList =
|
||||||
.classList
|
document.getElementsByTagName("html")[0]
|
||||||
.add(...cx(props.kcHtmlClass).split(" "));
|
.classList;
|
||||||
|
|
||||||
|
const tokens = cx(props.kcHtmlClass).split(" ")
|
||||||
|
|
||||||
|
htmlClassList.add(...tokens);
|
||||||
|
|
||||||
|
cleanups.push(() => htmlClassList.remove(...tokens));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return () => { isUnmounted = true; };
|
return () => {
|
||||||
|
|
||||||
}, []);
|
isUnmounted = true;
|
||||||
|
|
||||||
|
cleanups.forEach(f => f());
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
}, [props.kcHtmlClass]);
|
||||||
|
|
||||||
if (!isExtraCssLoaded) {
|
if (!isExtraCssLoaded) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
|
|
||||||
import { join as pathJoin } from "path";
|
import { join as pathJoin } from "path";
|
||||||
import { generateKeycloakThemeResources } from "../bin/build-keycloak-theme/generateKeycloakThemeResources";
|
import { generateKeycloakThemeResources } from "../bin/build-keycloak-theme/generateKeycloakThemeResources";
|
||||||
import {
|
import {
|
||||||
setupSampleReactProject,
|
setupSampleReactProject,
|
||||||
sampleReactProjectDirPath
|
sampleReactProjectDirPath
|
||||||
} from "./setupSampleReactProject";
|
} from "./setupSampleReactProject";
|
||||||
@ -9,8 +9,12 @@ import {
|
|||||||
setupSampleReactProject();
|
setupSampleReactProject();
|
||||||
|
|
||||||
generateKeycloakThemeResources({
|
generateKeycloakThemeResources({
|
||||||
"themeName": "onyxia-ui",
|
"themeName": "keycloakify-demo-app",
|
||||||
"reactAppBuildDirPath": pathJoin(sampleReactProjectDirPath, "build"),
|
"reactAppBuildDirPath": pathJoin(sampleReactProjectDirPath, "build"),
|
||||||
"keycloakThemeBuildingDirPath": pathJoin(sampleReactProjectDirPath, "build_keycloak_theme")
|
"keycloakThemeBuildingDirPath": pathJoin(sampleReactProjectDirPath, "build_keycloak_theme"),
|
||||||
|
"mode": {
|
||||||
|
"type": "standalone",
|
||||||
|
"urlPathname": "/keycloakify-demo-app/"
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -19,7 +19,8 @@ const { fixedJsCode } = replaceImportFromStaticInJsCode({
|
|||||||
3: "0664cdc0"
|
3: "0664cdc0"
|
||||||
}[e] + ".chunk.js"
|
}[e] + ".chunk.js"
|
||||||
}
|
}
|
||||||
`
|
`,
|
||||||
|
"mode": { "type": "standalone" }
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log({ fixedJsCode });
|
console.log({ fixedJsCode });
|
||||||
@ -45,6 +46,6 @@ const { fixedCssCode, cssGlobalsToDefine } = replaceImportFromStaticInCssCode({
|
|||||||
console.log({ fixedCssCode, cssGlobalsToDefine });
|
console.log({ fixedCssCode, cssGlobalsToDefine });
|
||||||
|
|
||||||
|
|
||||||
const { cssCodeToPrependInHead } = generateCssCodeToDefineGlobals({ cssGlobalsToDefine });
|
const { cssCodeToPrependInHead } = generateCssCodeToDefineGlobals({ cssGlobalsToDefine, "urlPathname": "/" });
|
||||||
|
|
||||||
console.log({ cssCodeToPrependInHead });
|
console.log({ cssCodeToPrependInHead });
|
Reference in New Issue
Block a user