Remove eslint and run prettier (changelog ignore)

This commit is contained in:
garronej
2021-10-11 21:35:40 +02:00
parent 9f8218efb7
commit 305ce9e44d
76 changed files with 27255 additions and 22419 deletions

View File

@ -1,7 +0,0 @@
node_modules/
dist/
CHANGELOG.md
.yarn_home/
src/test/apps/
src/test/types/
src/tools/types/

View File

@ -1,16 +0,0 @@
module.exports = {
"root": true,
"parser": "@typescript-eslint/parser",
"plugins": [
"@typescript-eslint",
],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"prettier",
],
"rules": {
"no-extra-boolean-cast": "off",
"@typescript-eslint/explicit-module-boundary-types": "off",
},
};

View File

@ -9,14 +9,13 @@ on:
jobs: jobs:
test_lint: test_formatting:
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: ${{ !github.event.created && github.repository != 'garronej/ts_ci' }}
steps: steps:
- uses: actions/checkout@v2.3.4 - uses: actions/checkout@v2.3.4
- uses: actions/setup-node@v2.1.3 - uses: actions/setup-node@v2.1.3
- uses: bahmutov/npm-install@v1 - uses: bahmutov/npm-install@v1
- name: If this step fails run 'npm run lint' and 'npm run format' then commit again. - name: If this step fails run 'yarn format' then commit again.
run: | run: |
PACKAGE_MANAGER=npm PACKAGE_MANAGER=npm
if [ -f "./yarn.lock" ]; then if [ -f "./yarn.lock" ]; then
@ -27,7 +26,7 @@ jobs:
test: test:
runs-on: macos-10.15 runs-on: macos-10.15
needs: test_lint needs: test_formatting
strategy: strategy:
matrix: matrix:
node: [ '15', '14', '13' ] node: [ '15', '14', '13' ]

View File

@ -21,9 +21,11 @@
</p> </p>
**NEW in v2** **NEW in v2**
- It's now possible to implement custom `.ftl` pages. - It's now possible to implement custom `.ftl` pages.
- Support for Keycloak plugins that introduce non standard ftl values. - Support for Keycloak plugins that introduce non standard ftl values.
(Like for example [this plugin](https://github.com/micedre/keycloak-mail-whitelisting) that define `authorizedMailDomains` in `register.ftl`). (Like for example [this plugin](https://github.com/micedre/keycloak-mail-whitelisting) that define `authorizedMailDomains` in `register.ftl`).
# Motivations # Motivations
Keycloak provides [theme support](https://www.keycloak.org/docs/latest/server_development/#_themes) for web pages. This allows customizing the look and feel of end-user facing pages so they can be integrated with your applications. Keycloak provides [theme support](https://www.keycloak.org/docs/latest/server_development/#_themes) for web pages. This allows customizing the look and feel of end-user facing pages so they can be integrated with your applications.
@ -55,7 +57,6 @@ If you already have a Keycloak custom theme, it can be easily ported to Keycloak
--- ---
- [Motivations](#motivations) - [Motivations](#motivations)
- [Requirements](#requirements) - [Requirements](#requirements)
- [My framework doesnt seem to be supported, what can I do?](#my-framework-doesnt-seem-to-be-supported-what-can-i-do) - [My framework doesnt seem to be supported, what can I do?](#my-framework-doesnt-seem-to-be-supported-what-can-i-do)
@ -81,6 +82,7 @@ If you already have a Keycloak custom theme, it can be easily ported to Keycloak
# Requirements # Requirements
Tested with the following Keycloak versions: Tested with the following Keycloak versions:
- [11.0.3](https://hub.docker.com/layers/jboss/keycloak/11.0.3/images/sha256-4438f1e51c1369371cb807dffa526e1208086b3ebb9cab009830a178de949782?context=explore) - [11.0.3](https://hub.docker.com/layers/jboss/keycloak/11.0.3/images/sha256-4438f1e51c1369371cb807dffa526e1208086b3ebb9cab009830a178de949782?context=explore)
- [12.0.4](https://hub.docker.com/layers/jboss/keycloak/12.0.4/images/sha256-67e0c88e69bd0c7aef972c40bdeb558a974013a28b3668ca790ed63a04d70584?context=explore) - [12.0.4](https://hub.docker.com/layers/jboss/keycloak/12.0.4/images/sha256-67e0c88e69bd0c7aef972c40bdeb558a974013a28b3668ca790ed63a04d70584?context=explore)
- Tests ongoing with [14.0.0](https://hub.docker.com/layers/jboss/keycloak/14.0.0/images/sha256-ca713e87ad163da71ab329010de2464a41ff030a25ae0aef15c1c290252f3d7f?context=explore) - Tests ongoing with [14.0.0](https://hub.docker.com/layers/jboss/keycloak/14.0.0/images/sha256-ca713e87ad163da71ab329010de2464a41ff030a25ae0aef15c1c290252f3d7f?context=explore)
@ -109,7 +111,9 @@ Find specific instructions about how to get started [**here**](https://github.co
To share your styles between your main app and your login pages you will need to externalize your design system by making it a To share your styles between your main app and your login pages you will need to externalize your design system by making it a
separate module. Checkout [ts_ci](https://github.com/garronej/ts_ci), it can help with that. separate module. Checkout [ts_ci](https://github.com/garronej/ts_ci), it can help with that.
# How to use # How to use
## Setting up the build tool ## Setting up the build tool
```bash ```bash
@ -117,6 +121,7 @@ yarn add keycloakify
``` ```
[`package.json`](https://github.com/garronej/keycloakify-demo-app/blob/main/package.json) [`package.json`](https://github.com/garronej/keycloakify-demo-app/blob/main/package.json)
```json ```json
"scripts": { "scripts": {
"keycloak": "yarn build && build-keycloak-theme", "keycloak": "yarn build && build-keycloak-theme",
@ -138,6 +143,7 @@ If you have created a new React project specifically to create a Keycloak theme
your index should look something like: your index should look something like:
`src/index.tsx` `src/index.tsx`
```tsx ```tsx
import { App } from "./<wherever>/App"; import { App } from "./<wherever>/App";
import { import {
@ -167,14 +173,11 @@ If you share a unique project for your app and the Keycloak theme, your index sh
more like this: more like this:
`src/index.tsx` `src/index.tsx`
```tsx ```tsx
import { App } from "./<wherever>/App"; import { App } from "./<wherever>/App";
import { import { KcApp, defaultKcProps, getKcContext } from "keycloakify";
KcApp, import { css } from "tss-react/@emotion/css";
defaultKcProps,
getKcContext
} from "keycloakify";
import { css } from "tss-react/@emotion/css";
const { kcContext } = getKcContext(); const { kcContext } = getKcContext();
@ -183,20 +186,21 @@ const myClassName = css({ "color": "red" });
reactDom.render( reactDom.render(
// Unless the app is currently being served by Keycloak // Unless the app is currently being served by Keycloak
// kcContext is undefined. // kcContext is undefined.
kcContext !== undefined ? kcContext !== undefined ? (
<KcApp <KcApp
kcContext={kcContext} kcContext={kcContext}
{...{ {...{
...defaultKcProps, ...defaultKcProps,
"kcHeaderWrapperClass": myClassName "kcHeaderWrapperClass": myClassName,
}} }}
/> : />
<App />, // Your actual app ) : (
document.getElementById("root") <App />
), // Your actual app
document.getElementById("root"),
); );
``` ```
<p align="center"> <p align="center">
<i>result:</i></br> <i>result:</i></br>
<img src="https://user-images.githubusercontent.com/6702424/114326299-6892fc00-9b34-11eb-8d75-85696e55458f.png"> <img src="https://user-images.githubusercontent.com/6702424/114326299-6892fc00-9b34-11eb-8d75-85696e55458f.png">
@ -222,6 +226,7 @@ If you prefer a real life example you can checkout [onyxia-web's source](https:/
The web app is in production [here](https://datalab.sspcloud.fr). The web app is in production [here](https://datalab.sspcloud.fr).
Main takeaways are: Main takeaways are:
- You must declare your custom pages in the package.json. [example](https://github.com/garronej/keycloakify-demo-app/blob/4eb2a9f63e9823e653b2d439495bda55e5ecc134/package.json#L17-L22) - You must declare your custom pages in the package.json. [example](https://github.com/garronej/keycloakify-demo-app/blob/4eb2a9f63e9823e653b2d439495bda55e5ecc134/package.json#L17-L22)
- (TS only) You must declare theses page in the type argument of the getter - (TS only) You must declare theses page in the type argument of the getter
function for the `kcContext` in order to have the correct typings. [example](https://github.com/garronej/keycloakify-demo-app/blob/4eb2a9f63e9823e653b2d439495bda55e5ecc134/src/KcApp/kcContext.ts#L16-L21) function for the `kcContext` in order to have the correct typings. [example](https://github.com/garronej/keycloakify-demo-app/blob/4eb2a9f63e9823e653b2d439495bda55e5ecc134/src/KcApp/kcContext.ts#L16-L21)
@ -233,11 +238,13 @@ Main takeaways are:
to debug the page outside of keycloak. [example](https://github.com/garronej/keycloakify-demo-app/blob/4eb2a9f63e9823e653b2d439495bda55e5ecc134/src/KcApp/kcContext.ts#L28-L43) to debug the page outside of keycloak. [example](https://github.com/garronej/keycloakify-demo-app/blob/4eb2a9f63e9823e653b2d439495bda55e5ecc134/src/KcApp/kcContext.ts#L28-L43)
WARNING: If you chose to go this way use: WARNING: If you chose to go this way use:
```json ```json
"dependencies": { "dependencies": {
"keycloakify": "~X.Y.Z" "keycloakify": "~X.Y.Z"
} }
``` ```
in your `package.json` instead of `^X.Y.Z`. A minor update of Keycloakify might break your app. in your `package.json` instead of `^X.Y.Z`. A minor update of Keycloakify might break your app.
### Hot reload ### Hot reload
@ -308,14 +315,14 @@ If you need to customize pages that are not supported yet or if you need to impl
[Here is a demo repo](https://github.com/garronej/keycloakify-demo-app) to show how to automate [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). the building and publishing of the theme (the .jar file).
# Limitations # Limitations
## `process.env.PUBLIC_URL` not supported. ## `process.env.PUBLIC_URL` not supported.
You won't be able to [import things from your public directory **in your JavaScript code**](https://create-react-app.dev/docs/using-the-public-folder/#adding-assets-outside-of-the-module-system). You won't be able to [import things from your public directory **in your JavaScript code**](https://create-react-app.dev/docs/using-the-public-folder/#adding-assets-outside-of-the-module-system).
(This isn't recommended anyway). (This isn't recommended anyway).
## `@font-face` importing fonts from the `src/` dir ## `@font-face` importing fonts from the `src/` dir
If you are building the theme with [--external-assets](#enable-loading-in-a-blink-of-a-eye-of-login-pages-) If you are building the theme with [--external-assets](#enable-loading-in-a-blink-of-a-eye-of-login-pages-)
@ -368,23 +375,24 @@ Note that the states are automatically restored on the other side by `powerhooks
```typescript ```typescript
import keycloak_js from "keycloak-js"; import keycloak_js from "keycloak-js";
import { injectGlobalStatesInSearchParams } from "powerhooks/useGlobalState"; import { injectGlobalStatesInSearchParams } from "powerhooks/useGlobalState";
import { createKeycloakAdapter } from "keycloakify"; import { createKeycloakAdapter } from "keycloakify";
//... //...
const keycloakInstance = keycloak_js({ const keycloakInstance = keycloak_js({
"url": "http://keycloak-server/auth", "url": "http://keycloak-server/auth",
"realm": "myrealm", "realm": "myrealm",
"clientId": "myapp" "clientId": "myapp",
}); });
keycloakInstance.init({ keycloakInstance.init({
"onLoad": 'check-sso', "onLoad": "check-sso",
"silentCheckSsoRedirectUri": window.location.origin + "/silent-check-sso.html", "silentCheckSsoRedirectUri":
window.location.origin + "/silent-check-sso.html",
"adapter": createKeycloakAdapter({ "adapter": createKeycloakAdapter({
"transformUrlBeforeRedirect": injectGlobalStatesInSearchParams, "transformUrlBeforeRedirect": injectGlobalStatesInSearchParams,
keycloakInstance keycloakInstance,
}) }),
}); });
//... //...
@ -396,12 +404,13 @@ flash of the blank html before the js bundle have been evaluated
# Kickstart video # Kickstart video
*NOTE: keycloak-react-theming was renamed keycloakify since this video was recorded* _NOTE: keycloak-react-theming was renamed keycloakify since this video was recorded_
[![kickstart_video](https://user-images.githubusercontent.com/6702424/108877866-f146ee80-75ff-11eb-8120-003b3c5f6dd8.png)](https://youtu.be/xTz0Rj7i2v8) [![kickstart_video](https://user-images.githubusercontent.com/6702424/108877866-f146ee80-75ff-11eb-8120-003b3c5f6dd8.png)](https://youtu.be/xTz0Rj7i2v8)
# About the errors related to `objectToJson` in Keycloak logs. # About the errors related to `objectToJson` in Keycloak logs.
The logs of your keycloak server will always show this kind of errors every time a client request a page: The logs of your keycloak server will always show this kind of errors every time a client request a page:
```log ```log
FTL stack trace ("~" means nesting-related): FTL stack trace ("~" means nesting-related):
- Failed at: #local value = object[key] [in template "login.ftl" in macro "objectToJson" at line 70, column 21] - Failed at: #local value = object[key] [in template "login.ftl" in macro "objectToJson" at line 70, column 21]
@ -410,6 +419,7 @@ FTL stack trace ("~" means nesting-related):
- Reached through: @compress [in template "login.ftl" in macro "objectToJson" at line 36, column 5] - Reached through: @compress [in template "login.ftl" in macro "objectToJson" at line 36, column 5]
- Reached through: @objectToJson object=(.data_model) de... [in template "login.ftl" at line 163, column 43] - Reached through: @objectToJson object=(.data_model) de... [in template "login.ftl" at line 163, column 43]
``` ```
Theses are expected and can be safely ignored. Theses are expected and can be safely ignored.
To [converts the `.ftl` values into a JavaScript object](https://github.com/InseeFrLab/keycloakify/blob/main/src/bin/build-keycloak-theme/generateFtl/common.ftl) To [converts the `.ftl` values into a JavaScript object](https://github.com/InseeFrLab/keycloakify/blob/main/src/bin/build-keycloak-theme/generateFtl/common.ftl)

View File

@ -16,8 +16,6 @@
"copy-files": "copyfiles -u 1 src/**/*.ftl src/**/*.xml src/**/*.js dist/", "copy-files": "copyfiles -u 1 src/**/*.ftl src/**/*.xml src/**/*.js dist/",
"generate-messages": "node dist/bin/generate-i18n-messages.js", "generate-messages": "node dist/bin/generate-i18n-messages.js",
"link_in_test_app": "node dist/bin/link_in_test_app.js", "link_in_test_app": "node dist/bin/link_in_test_app.js",
"lint:check": "eslint . --ext .ts,.tsx",
"lint": "yarn lint:check --fix",
"_format": "prettier '**/*.{ts,tsx,json,md}'", "_format": "prettier '**/*.{ts,tsx,json,md}'",
"format": "yarn _format --write", "format": "yarn _format --write",
"format:check": "yarn _format --list-different", "format:check": "yarn _format --list-different",
@ -28,9 +26,6 @@
"download-builtin-keycloak-theme": "dist/bin/download-builtin-keycloak-theme.js" "download-builtin-keycloak-theme": "dist/bin/download-builtin-keycloak-theme.js"
}, },
"lint-staged": { "lint-staged": {
"*.{ts,tsx}": [
"eslint --fix"
],
"*.{ts,tsx,json,md}": [ "*.{ts,tsx,json,md}": [
"prettier --write" "prettier --write"
] ]
@ -68,12 +63,9 @@
"react": "^17.0.1", "react": "^17.0.1",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"typescript": "^4.2.3", "typescript": "^4.2.3",
"@typescript-eslint/eslint-plugin": "^4.24.0",
"@typescript-eslint/parser": "^4.24.0",
"eslint": "^7.26.0",
"eslint-config-prettier": "^8.3.0",
"husky": "^4.3.8", "husky": "^4.3.8",
"lint-staged": "^11.0.0" "lint-staged": "^11.0.0",
"prettier": "^2.3.0"
}, },
"dependencies": { "dependencies": {
"@emotion/react": "^11.4.1", "@emotion/react": "^11.4.1",

View File

@ -1,5 +1,3 @@
export const keycloakVersions = ["11.0.3", "15.0.2"] as const; export const keycloakVersions = ["11.0.3", "15.0.2"] as const;
export type KeycloakVersion = typeof keycloakVersions[number]; export type KeycloakVersion = typeof keycloakVersions[number];

View File

@ -1,8 +1,15 @@
import { generateKeycloakThemeResources } from "./generateKeycloakThemeResources"; import { generateKeycloakThemeResources } from "./generateKeycloakThemeResources";
import { generateJavaStackFiles } from "./generateJavaStackFiles"; import { generateJavaStackFiles } 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"; import { URL } from "url";
type ParsedPackageJson = { type ParsedPackageJson = {
@ -13,21 +20,34 @@ type ParsedPackageJson = {
const reactProjectDirPath = process.cwd(); const reactProjectDirPath = process.cwd();
const doUseExternalAssets = process.argv[2]?.toLowerCase() === "--external-assets"; 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",
);
function sanitizeThemeName(name: string) { function sanitizeThemeName(name: string) {
return name.replace(/^@(.*)/, '$1').split('/').join('-'); return name
.replace(/^@(.*)/, "$1")
.split("/")
.join("-");
} }
export function main() { export function main() {
console.log("🔏 Building the keycloak theme...⌚"); console.log("🔏 Building the keycloak theme...⌚");
const extraPagesId: string[] = (parsedPackageJson as any)["keycloakify"]?.["extraPages"] ?? []; const extraPagesId: string[] =
const extraThemeProperties: string[] = (parsedPackageJson as any)["keycloakify"]?.["extraThemeProperties"] ?? []; (parsedPackageJson as any)["keycloakify"]?.["extraPages"] ?? [];
const extraThemeProperties: string[] =
(parsedPackageJson as any)["keycloakify"]?.["extraThemeProperties"] ??
[];
const themeName = sanitizeThemeName(parsedPackageJson.name); const themeName = sanitizeThemeName(parsedPackageJson.name);
generateKeycloakThemeResources({ generateKeycloakThemeResources({
@ -35,105 +55,111 @@ export function main() {
"reactAppBuildDirPath": pathJoin(reactProjectDirPath, "build"), "reactAppBuildDirPath": pathJoin(reactProjectDirPath, "build"),
themeName, themeName,
...(() => { ...(() => {
const url = (() => { const url = (() => {
const { homepage } = parsedPackageJson; const { homepage } = parsedPackageJson;
return homepage === undefined ? return homepage === undefined ? undefined : new URL(homepage);
undefined :
new URL(homepage);
})(); })();
return { return {
"urlPathname": "urlPathname":
url === undefined ? url === undefined
"/" : ? "/"
url.pathname.replace(/([^/])$/, "$1/"), : url.pathname.replace(/([^/])$/, "$1/"),
"urlOrigin": !doUseExternalAssets ? undefined : (() => { "urlOrigin": !doUseExternalAssets
? undefined
: (() => {
if (url === undefined) { if (url === undefined) {
console.error("ERROR: You must specify 'homepage' in your package.json"); console.error(
"ERROR: You must specify 'homepage' in your package.json",
);
process.exit(-1); process.exit(-1);
} }
return url.origin; return url.origin;
})(),
})()
}; };
})(), })(),
extraPagesId, extraPagesId,
extraThemeProperties, extraThemeProperties,
//We have to leave it at that otherwise we break our default theme. //We have to leave it at that otherwise we break our default theme.
//Problem is that we can't guarantee that the the old resources common //Problem is that we can't guarantee that the the old resources common
//will still be available on the newer keycloak version. //will still be available on the newer keycloak version.
"keycloakVersion": "11.0.3" "keycloakVersion": "11.0.3",
}); });
const { jarFilePath } = generateJavaStackFiles({ const { jarFilePath } = generateJavaStackFiles({
version: parsedPackageJson.version, version: parsedPackageJson.version,
themeName, themeName,
homepage: parsedPackageJson.homepage, homepage: parsedPackageJson.homepage,
keycloakThemeBuildingDirPath keycloakThemeBuildingDirPath,
}); });
child_process.execSync( child_process.execSync("mvn package", {
"mvn package", "cwd": keycloakThemeBuildingDirPath,
{ "cwd": keycloakThemeBuildingDirPath } });
);
generateDebugFiles({ generateDebugFiles({
keycloakThemeBuildingDirPath, keycloakThemeBuildingDirPath,
themeName, themeName,
"keycloakVersion": "15.0.2" "keycloakVersion": "15.0.2",
}); });
console.log([ console.log(
'', [
`✅ Your keycloak theme has been generated and bundled into ./${pathRelative(reactProjectDirPath, jarFilePath)} 🚀`, "",
`✅ Your keycloak theme has been generated and bundled into ./${pathRelative(
reactProjectDirPath,
jarFilePath,
)} 🚀`,
`It is to be placed in "/opt/jboss/keycloak/standalone/deployments" in the container running a jboss/keycloak Docker image.`, `It is to be placed in "/opt/jboss/keycloak/standalone/deployments" in the container running a jboss/keycloak Docker image.`,
'', "",
'Using Helm (https://github.com/codecentric/helm-charts), edit to reflect:', "Using Helm (https://github.com/codecentric/helm-charts), edit to reflect:",
'', "",
'value.yaml: ', "value.yaml: ",
' extraInitContainers: |', " extraInitContainers: |",
' - name: realm-ext-provider', " - name: realm-ext-provider",
' image: curlimages/curl', " image: curlimages/curl",
' imagePullPolicy: IfNotPresent', " imagePullPolicy: IfNotPresent",
' command:', " command:",
' - sh', " - sh",
' args:', " args:",
' - -c', " - -c",
` - curl -L -f -S -o /extensions/${pathBasename(jarFilePath)} https://AN.URL.FOR/${pathBasename(jarFilePath)}`, ` - curl -L -f -S -o /extensions/${pathBasename(
' volumeMounts:', jarFilePath,
' - name: extensions', )} https://AN.URL.FOR/${pathBasename(jarFilePath)}`,
' mountPath: /extensions', " volumeMounts:",
' ', " - name: extensions",
' extraVolumeMounts: |', " mountPath: /extensions",
' - name: extensions', " ",
' mountPath: /opt/jboss/keycloak/standalone/deployments', " extraVolumeMounts: |",
' extraEnv: |', " - name: extensions",
' - name: KEYCLOAK_USER', " mountPath: /opt/jboss/keycloak/standalone/deployments",
' value: admin', " extraEnv: |",
' - name: KEYCLOAK_PASSWORD', " - name: KEYCLOAK_USER",
' value: xxxxxxxxx', " value: admin",
' - name: JAVA_OPTS', " - name: KEYCLOAK_PASSWORD",
' value: -Dkeycloak.profile=preview', " value: xxxxxxxxx",
'', " - name: JAVA_OPTS",
'', " value: -Dkeycloak.profile=preview",
'To test your theme locally, with hot reloading, you can spin up a Keycloak container image with the theme loaded by running:', "",
'', "",
`👉 $ ./${pathRelative(reactProjectDirPath, pathJoin(keycloakThemeBuildingDirPath, containerLaunchScriptBasename))} 👈`, "To test your theme locally, with hot reloading, you can spin up a Keycloak container image with the theme loaded by running:",
'', "",
`👉 $ ./${pathRelative(
reactProjectDirPath,
pathJoin(
keycloakThemeBuildingDirPath,
containerLaunchScriptBasename,
),
)} 👈`,
"",
'To enable the theme within keycloak log into the admin console ( 👉 http://localhost:8080 username: admin, password: admin 👈), create a realm (called "myrealm" for example),', 'To enable the theme within keycloak log into the admin console ( 👉 http://localhost:8080 username: admin, password: admin 👈), create a realm (called "myrealm" for example),',
`go to your realm settings, click on the theme tab then select ${themeName}.`, `go to your realm settings, click on the theme tab then select ${themeName}.`,
`More details: https://www.keycloak.org/getting-started/getting-started-docker`, `More details: https://www.keycloak.org/getting-started/getting-started-docker`,
'', "",
'Once your container is up and configured 👉 http://localhost:8080/auth/realms/myrealm/account 👈', "Once your container is up and configured 👉 http://localhost:8080/auth/realms/myrealm/account 👈",
'', "",
].join("\n")); ].join("\n"),
);
} }

View File

@ -1,2 +1 @@
export const ftlValuesGlobalName = "kcContext"; export const ftlValuesGlobalName = "kcContext";

View File

@ -1,18 +1,15 @@
import * as fs from "fs"; import * as fs from "fs";
import { join as pathJoin, dirname as pathDirname, } from "path"; import { join as pathJoin, dirname as pathDirname } from "path";
export const containerLaunchScriptBasename = "start_keycloak_testing_container.sh"; export const containerLaunchScriptBasename =
"start_keycloak_testing_container.sh";
/** Files for being able to run a hot reload keycloak container */ /** Files for being able to run a hot reload keycloak container */
export function generateDebugFiles( export function generateDebugFiles(params: {
params: {
keycloakVersion: "11.0.3" | "15.0.2"; keycloakVersion: "11.0.3" | "15.0.2";
themeName: string; themeName: string;
keycloakThemeBuildingDirPath: string; keycloakThemeBuildingDirPath: string;
} }) {
) {
const { themeName, keycloakThemeBuildingDirPath, keycloakVersion } = params; const { themeName, keycloakThemeBuildingDirPath, keycloakVersion } = params;
fs.writeFileSync( fs.writeFileSync(
@ -29,8 +26,8 @@ export function generateDebugFiles(
"", "",
'ENTRYPOINT [ "/opt/jboss/tools/docker-entrypoint.sh" ]', 'ENTRYPOINT [ "/opt/jboss/tools/docker-entrypoint.sh" ]',
].join("\n"), ].join("\n"),
"utf8" "utf8",
) ),
); );
const dockerImage = `${themeName}/keycloak-hot-reload`; const dockerImage = `${themeName}/keycloak-hot-reload`;
@ -54,42 +51,53 @@ export function generateDebugFiles(
" -e KEYCLOAK_USER=admin \\", " -e KEYCLOAK_USER=admin \\",
" -e KEYCLOAK_PASSWORD=admin \\", " -e KEYCLOAK_PASSWORD=admin \\",
" -e JAVA_OPTS=-Dkeycloak.profile=preview \\", " -e JAVA_OPTS=-Dkeycloak.profile=preview \\",
` -v ${pathJoin(keycloakThemeBuildingDirPath, "src", "main", "resources", "theme", themeName) ` -v ${pathJoin(
}:/opt/jboss/keycloak/themes/${themeName}:rw \\`, keycloakThemeBuildingDirPath,
"src",
"main",
"resources",
"theme",
themeName,
)}:/opt/jboss/keycloak/themes/${themeName}:rw \\`,
` -it ${dockerImage}:latest`, ` -it ${dockerImage}:latest`,
"" "",
].join("\n"), ].join("\n"),
"utf8" "utf8",
), ),
{ "mode": 0o755 } { "mode": 0o755 },
); );
const standaloneHaFilePath = pathJoin(keycloakThemeBuildingDirPath, "configuration", `standalone-ha.xml`); const standaloneHaFilePath = pathJoin(
keycloakThemeBuildingDirPath,
"configuration",
`standalone-ha.xml`,
);
try { fs.mkdirSync(pathDirname(standaloneHaFilePath)); } catch { } try {
fs.mkdirSync(pathDirname(standaloneHaFilePath));
} catch {}
fs.writeFileSync( fs.writeFileSync(
standaloneHaFilePath, standaloneHaFilePath,
fs.readFileSync( fs
pathJoin( .readFileSync(
__dirname, pathJoin(__dirname, `standalone-ha_${keycloakVersion}.xml`),
`standalone-ha_${keycloakVersion}.xml`
)
) )
.toString("utf8") .toString("utf8")
.replace( .replace(
new RegExp([ new RegExp(
[
"<staticMaxAge>2592000</staticMaxAge>", "<staticMaxAge>2592000</staticMaxAge>",
"<cacheThemes>true</cacheThemes>", "<cacheThemes>true</cacheThemes>",
"<cacheTemplates>true</cacheTemplates>" "<cacheTemplates>true</cacheTemplates>",
].join("\\s*"), "g" ].join("\\s*"),
"g",
), ),
[ [
"<staticMaxAge>-1</staticMaxAge>", "<staticMaxAge>-1</staticMaxAge>",
"<cacheThemes>false</cacheThemes>", "<cacheThemes>false</cacheThemes>",
"<cacheTemplates>false</cacheTemplates>" "<cacheTemplates>false</cacheTemplates>",
].join("\n") ].join("\n"),
) ),
); );
} }

View File

@ -2,7 +2,7 @@ import cheerio from "cheerio";
import { import {
replaceImportsFromStaticInJsCode, replaceImportsFromStaticInJsCode,
replaceImportsInInlineCssCode, replaceImportsInInlineCssCode,
generateCssCodeToDefineGlobals generateCssCodeToDefineGlobals,
} from "../replaceImportFromStatic"; } from "../replaceImportFromStatic";
import fs from "fs"; import fs from "fs";
import { join as pathJoin } from "path"; import { join as pathJoin } from "path";
@ -10,62 +10,62 @@ import { objectKeys } from "tsafe/objectKeys";
import { ftlValuesGlobalName } from "../ftlValuesGlobalName"; import { ftlValuesGlobalName } from "../ftlValuesGlobalName";
export const pageIds = [ export const pageIds = [
"login.ftl", "register.ftl", "register-user-profile.ftl", "login.ftl",
"info.ftl", "error.ftl", "login-reset-password.ftl", "register.ftl",
"login-verify-email.ftl", "terms.ftl", "login-otp.ftl", "register-user-profile.ftl",
"login-update-profile.ftl", "login-idp-link-confirm.ftl" "info.ftl",
"error.ftl",
"login-reset-password.ftl",
"login-verify-email.ftl",
"terms.ftl",
"login-otp.ftl",
"login-update-profile.ftl",
"login-idp-link-confirm.ftl",
] as const; ] 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"); }
};
export function generateFtlFilesCodeFactory(params: {
export function generateFtlFilesCodeFactory(
params: {
cssGlobalsToDefine: Record<string, string>; cssGlobalsToDefine: Record<string, string>;
indexHtmlCode: string; indexHtmlCode: string;
urlPathname: string; urlPathname: string;
urlOrigin: undefined | string; urlOrigin: undefined | string;
} }) {
) { const { cssGlobalsToDefine, indexHtmlCode, urlPathname, urlOrigin } =
params;
const { cssGlobalsToDefine, indexHtmlCode, urlPathname, urlOrigin } = params;
const $ = cheerio.load(indexHtmlCode); const $ = cheerio.load(indexHtmlCode);
$("script:not([src])").each((...[, element]) => { $("script:not([src])").each((...[, element]) => {
const { fixedJsCode } = replaceImportsFromStaticInJsCode({ const { fixedJsCode } = replaceImportsFromStaticInJsCode({
"jsCode": $(element).html()!, "jsCode": $(element).html()!,
urlOrigin urlOrigin,
}); });
$(element).text(fixedJsCode); $(element).text(fixedJsCode);
}); });
$("style").each((...[, element]) => { $("style").each((...[, element]) => {
const { fixedCssCode } = replaceImportsInInlineCssCode({ const { fixedCssCode } = replaceImportsInInlineCssCode({
"cssCode": $(element).html()!, "cssCode": $(element).html()!,
"urlPathname": params.urlPathname, "urlPathname": params.urlPathname,
urlOrigin urlOrigin,
}); });
$(element).text(fixedCssCode); $(element).text(fixedCssCode);
}); });
([ (
[
["link", "href"], ["link", "href"],
["script", "src"], ["script", "src"],
] as const).forEach(([selector, attrName]) => ] as const
).forEach(([selector, attrName]) =>
$(selector).each((...[, element]) => { $(selector).each((...[, element]) => {
const href = $(element).attr(attrName); const href = $(element).attr(attrName);
if (href === undefined) { if (href === undefined) {
@ -74,94 +74,90 @@ export function generateFtlFilesCodeFactory(
$(element).attr( $(element).attr(
attrName, attrName,
urlOrigin !== undefined ? urlOrigin !== undefined
href.replace(/^\//, `${urlOrigin}/`) : ? href.replace(/^\//, `${urlOrigin}/`)
href.replace( : href.replace(
new RegExp(`^${urlPathname.replace(/\//g, "\\/")}`), new RegExp(`^${urlPathname.replace(/\//g, "\\/")}`),
"${url.resourcesPath}/build/" "${url.resourcesPath}/build/",
) ),
); );
}),
})
); );
//FTL is no valid html, we can't insert with cheerio, we put placeholder for injecting later. //FTL is no valid html, we can't insert with cheerio, we put placeholder for injecting later.
const ftlPlaceholders = { const ftlPlaceholders = {
'{ "x": "vIdLqMeOed9sdLdIdOxdK0d" }': loadAdjacentFile("common.ftl") '{ "x": "vIdLqMeOed9sdLdIdOxdK0d" }': loadAdjacentFile(
.match(/^<script>const _=((?:.|\n)+)<\/script>[\n]?$/)![1], "common.ftl",
'<!-- xIdLqMeOedErIdLsPdNdI9dSlxI -->': ).match(/^<script>const _=((?:.|\n)+)<\/script>[\n]?$/)![1],
[ "<!-- xIdLqMeOedErIdLsPdNdI9dSlxI -->": [
'<#if scripts??>', "<#if scripts??>",
' <#list scripts as script>', " <#list scripts as script>",
' <script src="${script}" type="text/javascript"></script>', ' <script src="${script}" type="text/javascript"></script>',
' </#list>', " </#list>",
'</#if>' "</#if>",
].join("\n") ].join("\n"),
}; };
const pageSpecificCodePlaceholder = "<!-- dIddLqMeOedErIdLsPdNdI9dSl42sw -->"; const pageSpecificCodePlaceholder =
"<!-- dIddLqMeOedErIdLsPdNdI9dSl42sw -->";
$("head").prepend( $("head").prepend(
[ [
...(Object.keys(cssGlobalsToDefine).length === 0 ? [] : [ ...(Object.keys(cssGlobalsToDefine).length === 0
'', ? []
'<style>', : [
"",
"<style>",
generateCssCodeToDefineGlobals({ generateCssCodeToDefineGlobals({
cssGlobalsToDefine, cssGlobalsToDefine,
urlPathname urlPathname,
}).cssCodeToPrependInHead, }).cssCodeToPrependInHead,
'</style>', "</style>",
'' "",
]), ]),
"<script>", "<script>",
loadAdjacentFile("Object.deepAssign.js"), loadAdjacentFile("Object.deepAssign.js"),
"</script>", "</script>",
'<script>', "<script>",
` window.${ftlValuesGlobalName}= Object.assign(`, ` window.${ftlValuesGlobalName}= Object.assign(`,
` {},`, ` {},`,
` ${objectKeys(ftlPlaceholders)[0]}`, ` ${objectKeys(ftlPlaceholders)[0]}`,
' );', " );",
'</script>', "</script>",
'', "",
pageSpecificCodePlaceholder, pageSpecificCodePlaceholder,
'', "",
objectKeys(ftlPlaceholders)[1] objectKeys(ftlPlaceholders)[1],
].join("\n"), ].join("\n"),
); );
const partiallyFixedIndexHtmlCode = $.html(); const partiallyFixedIndexHtmlCode = $.html();
function generateFtlFilesCode( function generateFtlFilesCode(params: { pageId: string }): {
params: { ftlCode: string;
pageId: string; } {
}
): { ftlCode: string; } {
const { pageId } = params; const { pageId } = params;
const $ = cheerio.load(partiallyFixedIndexHtmlCode); const $ = cheerio.load(partiallyFixedIndexHtmlCode);
let ftlCode = $.html() let ftlCode = $.html().replace(
.replace(
pageSpecificCodePlaceholder, pageSpecificCodePlaceholder,
[ [
'<script>', "<script>",
` Object.deepAssign(`, ` Object.deepAssign(`,
` window.${ftlValuesGlobalName},`, ` window.${ftlValuesGlobalName},`,
` { "pageId": "${pageId}" }`, ` { "pageId": "${pageId}" }`,
' );', " );",
'</script>' "</script>",
].join("\n") ].join("\n"),
); );
objectKeys(ftlPlaceholders) objectKeys(ftlPlaceholders).forEach(
.forEach(id => ftlCode = ftlCode.replace(id, ftlPlaceholders[id])); id => (ftlCode = ftlCode.replace(id, ftlPlaceholders[id])),
);
return { ftlCode }; return { ftlCode };
} }
return { generateFtlFilesCode }; return { generateFtlFilesCode };
} }

View File

@ -1,39 +1,33 @@
import * as url from "url"; import * as url from "url";
import * as fs from "fs"; import * as fs from "fs";
import { join as pathJoin, dirname as pathDirname } from "path"; import { join as pathJoin, dirname as pathDirname } from "path";
export function generateJavaStackFiles(params: {
export function generateJavaStackFiles(
params: {
version: string; version: string;
themeName: string; themeName: string;
homepage?: string; homepage?: string;
keycloakThemeBuildingDirPath: string; keycloakThemeBuildingDirPath: string;
} }): { jarFilePath: string } {
): { jarFilePath: string; } { const { themeName, version, homepage, keycloakThemeBuildingDirPath } =
params;
const {
themeName,
version,
homepage,
keycloakThemeBuildingDirPath
} = params;
{ {
const { pomFileCode } = (function generatePomFileCode(): {
const { pomFileCode } = (function generatePomFileCode(): { pomFileCode: string; } { pomFileCode: string;
} {
const groupId = (() => { const groupId = (() => {
const fallbackGroupId = `there.was.no.homepage.field.in.the.package.json.${themeName}`; const fallbackGroupId = `there.was.no.homepage.field.in.the.package.json.${themeName}`;
return (!homepage ? return (
fallbackGroupId : (!homepage
url.parse(homepage).host?.replace(/:[0-9]+$/,"")?.split(".").reverse().join(".") ?? fallbackGroupId ? fallbackGroupId
) + ".keycloak"; : url
.parse(homepage)
.host?.replace(/:[0-9]+$/, "")
?.split(".")
.reverse()
.join(".") ?? fallbackGroupId) + ".keycloak"
);
})(); })();
const artefactId = `${themeName}-keycloak-theme`; const artefactId = `${themeName}-keycloak-theme`;
@ -49,51 +43,57 @@ export function generateJavaStackFiles(
` <version>${version}</version>`, ` <version>${version}</version>`,
` <name>${artefactId}</name>`, ` <name>${artefactId}</name>`,
` <description />`, ` <description />`,
`</project>` `</project>`,
].join("\n"); ].join("\n");
return { pomFileCode }; return { pomFileCode };
})(); })();
fs.writeFileSync( fs.writeFileSync(
pathJoin(keycloakThemeBuildingDirPath, "pom.xml"), pathJoin(keycloakThemeBuildingDirPath, "pom.xml"),
Buffer.from(pomFileCode, "utf8") Buffer.from(pomFileCode, "utf8"),
); );
} }
{ {
const themeManifestFilePath = pathJoin( const themeManifestFilePath = pathJoin(
keycloakThemeBuildingDirPath, "src", "main", keycloakThemeBuildingDirPath,
"resources", "META-INF", "keycloak-themes.json" "src",
"main",
"resources",
"META-INF",
"keycloak-themes.json",
); );
try { try {
fs.mkdirSync(pathDirname(themeManifestFilePath)); fs.mkdirSync(pathDirname(themeManifestFilePath));
} catch {} } catch {}
fs.writeFileSync( fs.writeFileSync(
themeManifestFilePath, themeManifestFilePath,
Buffer.from( Buffer.from(
JSON.stringify({ JSON.stringify(
{
"themes": [ "themes": [
{ {
"name": themeName, "name": themeName,
"types": ["login"] "types": ["login"],
} },
] ],
}, null, 2), },
"utf8" null,
) 2,
),
"utf8",
),
); );
} }
return { "jarFilePath": pathJoin(keycloakThemeBuildingDirPath, "target", `${themeName}-${version}.jar`) }; return {
"jarFilePath": pathJoin(
keycloakThemeBuildingDirPath,
"target",
`${themeName}-${version}.jar`,
),
};
} }

View File

@ -1,20 +1,21 @@
import { transformCodebase } from "../tools/transformCodebase"; import { transformCodebase } from "../tools/transformCodebase";
import * as fs from "fs"; import * as fs from "fs";
import { join as pathJoin } from "path"; import { join as pathJoin } from "path";
import { import {
replaceImportsInCssCode, replaceImportsInCssCode,
replaceImportsFromStaticInJsCode replaceImportsFromStaticInJsCode,
} from "./replaceImportFromStatic"; } from "./replaceImportFromStatic";
import { generateFtlFilesCodeFactory, pageIds } from "./generateFtl"; import { generateFtlFilesCodeFactory, pageIds } from "./generateFtl";
import { downloadBuiltinKeycloakTheme } from "../download-builtin-keycloak-theme"; import { downloadBuiltinKeycloakTheme } from "../download-builtin-keycloak-theme";
import * as child_process from "child_process"; import * as child_process from "child_process";
import { resourcesCommonPath, resourcesPath, subDirOfPublicDirBasename } from "../../lib/getKcContext/kcContextMocks/urlResourcesPath"; import {
resourcesCommonPath,
resourcesPath,
subDirOfPublicDirBasename,
} from "../../lib/getKcContext/kcContextMocks/urlResourcesPath";
import { isInside } from "../tools/isInside"; import { isInside } from "../tools/isInside";
export function generateKeycloakThemeResources(params: {
export function generateKeycloakThemeResources(
params: {
themeName: string; themeName: string;
reactAppBuildDirPath: string; reactAppBuildDirPath: string;
keycloakThemeBuildingDirPath: string; keycloakThemeBuildingDirPath: string;
@ -23,156 +24,174 @@ export function generateKeycloakThemeResources(
urlOrigin: undefined | string; urlOrigin: undefined | string;
extraPagesId: string[]; extraPagesId: string[];
extraThemeProperties: string[]; extraThemeProperties: string[];
keycloakVersion: "11.0.3" | "15.0.2" keycloakVersion: "11.0.3" | "15.0.2";
} }) {
) {
const { const {
themeName, reactAppBuildDirPath, keycloakThemeBuildingDirPath, themeName,
urlPathname, urlOrigin, extraPagesId, extraThemeProperties, reactAppBuildDirPath,
keycloakVersion keycloakThemeBuildingDirPath,
urlPathname,
urlOrigin,
extraPagesId,
extraThemeProperties,
keycloakVersion,
} = params; } = params;
const themeDirPath = pathJoin(keycloakThemeBuildingDirPath, "src", "main", "resources", "theme", themeName, "login"); const themeDirPath = pathJoin(
keycloakThemeBuildingDirPath,
"src",
"main",
"resources",
"theme",
themeName,
"login",
);
let allCssGlobalsToDefine: Record<string, string> = {}; let allCssGlobalsToDefine: Record<string, string> = {};
transformCodebase({ transformCodebase({
"destDirPath": "destDirPath":
urlOrigin === undefined ? urlOrigin === undefined
pathJoin(themeDirPath, "resources", "build") : ? pathJoin(themeDirPath, "resources", "build")
reactAppBuildDirPath, : reactAppBuildDirPath,
"srcDirPath": reactAppBuildDirPath, "srcDirPath": reactAppBuildDirPath,
"transformSourceCode": ({ filePath, sourceCode }) => { "transformSourceCode": ({ filePath, sourceCode }) => {
//NOTE: Prevent cycles, excludes the folder we generated for debug in public/ //NOTE: Prevent cycles, excludes the folder we generated for debug in public/
if ( if (
urlOrigin === undefined && urlOrigin === undefined &&
isInside({ isInside({
"dirPath": pathJoin(reactAppBuildDirPath, subDirOfPublicDirBasename), "dirPath": pathJoin(
filePath reactAppBuildDirPath,
subDirOfPublicDirBasename,
),
filePath,
}) })
) { ) {
return undefined; return undefined;
} }
if (urlOrigin === undefined && /\.css?$/i.test(filePath)) { if (urlOrigin === undefined && /\.css?$/i.test(filePath)) {
const { cssGlobalsToDefine, fixedCssCode } =
const { cssGlobalsToDefine, fixedCssCode } = replaceImportsInCssCode( replaceImportsInCssCode({
{ "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 } = replaceImportsFromStaticInJsCode({ const { fixedJsCode } = replaceImportsFromStaticInJsCode({
"jsCode": sourceCode.toString("utf8"), "jsCode": sourceCode.toString("utf8"),
urlOrigin urlOrigin,
}); });
return { "modifiedSourceCode": Buffer.from(fixedJsCode, "utf8") }; return {
"modifiedSourceCode": Buffer.from(fixedJsCode, "utf8"),
};
} }
return urlOrigin === undefined ? return urlOrigin === undefined
{ "modifiedSourceCode": sourceCode } : ? { "modifiedSourceCode": sourceCode }
undefined; : undefined;
},
}
}); });
const { generateFtlFilesCode } = generateFtlFilesCodeFactory({ const { generateFtlFilesCode } = generateFtlFilesCodeFactory({
"cssGlobalsToDefine": allCssGlobalsToDefine, "cssGlobalsToDefine": allCssGlobalsToDefine,
"indexHtmlCode": fs.readFileSync( "indexHtmlCode": fs
pathJoin(reactAppBuildDirPath, "index.html") .readFileSync(pathJoin(reactAppBuildDirPath, "index.html"))
).toString("utf8"), .toString("utf8"),
urlPathname, urlPathname,
urlOrigin urlOrigin,
}); });
[...pageIds, ...extraPagesId].forEach(pageId => { [...pageIds, ...extraPagesId].forEach(pageId => {
const { ftlCode } = generateFtlFilesCode({ pageId }); const { ftlCode } = generateFtlFilesCode({ pageId });
fs.mkdirSync(themeDirPath, { "recursive": true }); fs.mkdirSync(themeDirPath, { "recursive": true });
fs.writeFileSync( fs.writeFileSync(
pathJoin(themeDirPath, pageId), pathJoin(themeDirPath, pageId),
Buffer.from(ftlCode, "utf8") Buffer.from(ftlCode, "utf8"),
); );
}); });
{ {
const tmpDirPath = pathJoin(themeDirPath, "..", "tmp_xxKdLpdIdLd"); const tmpDirPath = pathJoin(themeDirPath, "..", "tmp_xxKdLpdIdLd");
downloadBuiltinKeycloakTheme({ downloadBuiltinKeycloakTheme({
keycloakVersion, keycloakVersion,
"destDirPath": tmpDirPath "destDirPath": tmpDirPath,
}); });
const themeResourcesDirPath = pathJoin(themeDirPath, "resources"); const themeResourcesDirPath = pathJoin(themeDirPath, "resources");
transformCodebase({ transformCodebase({
"srcDirPath": pathJoin(tmpDirPath, "keycloak", "login", "resources"), "srcDirPath": pathJoin(
"destDirPath": themeResourcesDirPath tmpDirPath,
"keycloak",
"login",
"resources",
),
"destDirPath": themeResourcesDirPath,
}); });
const reactAppPublicDirPath = pathJoin(reactAppBuildDirPath, "..", "public"); const reactAppPublicDirPath = pathJoin(
reactAppBuildDirPath,
"..",
"public",
);
transformCodebase({ transformCodebase({
"srcDirPath": themeResourcesDirPath, "srcDirPath": themeResourcesDirPath,
"destDirPath": pathJoin( "destDirPath": pathJoin(reactAppPublicDirPath, resourcesPath),
reactAppPublicDirPath,
resourcesPath
)
}); });
transformCodebase({ transformCodebase({
"srcDirPath": pathJoin(tmpDirPath, "keycloak", "common", "resources"), "srcDirPath": pathJoin(
"destDirPath": pathJoin( tmpDirPath,
reactAppPublicDirPath, "keycloak",
resourcesCommonPath "common",
) "resources",
),
"destDirPath": pathJoin(reactAppPublicDirPath, resourcesCommonPath),
}); });
const keycloakResourcesWithinPublicDirPath = const keycloakResourcesWithinPublicDirPath = pathJoin(
pathJoin(reactAppPublicDirPath, subDirOfPublicDirBasename); reactAppPublicDirPath,
subDirOfPublicDirBasename,
);
fs.writeFileSync( fs.writeFileSync(
pathJoin(keycloakResourcesWithinPublicDirPath, "README.txt"), pathJoin(keycloakResourcesWithinPublicDirPath, "README.txt"),
Buffer.from([ Buffer.from(
[
"This is just a test folder that helps develop", "This is just a test folder that helps develop",
"the login and register page without having to yarn build" "the login and register page without having to yarn build",
].join(" ")) ].join(" "),
),
); );
fs.writeFileSync( fs.writeFileSync(
pathJoin(keycloakResourcesWithinPublicDirPath, ".gitignore"), pathJoin(keycloakResourcesWithinPublicDirPath, ".gitignore"),
Buffer.from("*", "utf8") Buffer.from("*", "utf8"),
); );
child_process.execSync(`rm -r ${tmpDirPath}`); child_process.execSync(`rm -r ${tmpDirPath}`);
} }
fs.writeFileSync( fs.writeFileSync(
pathJoin(themeDirPath, "theme.properties"), pathJoin(themeDirPath, "theme.properties"),
Buffer.from( Buffer.from(
"parent=keycloak".concat("\n\n", extraThemeProperties.join("\n\n")), "parent=keycloak".concat("\n\n", extraThemeProperties.join("\n\n")),
"utf8" "utf8",
) ),
); );
} }

View File

@ -4,7 +4,5 @@ export * from "./build-keycloak-theme";
import { main } from "./build-keycloak-theme"; import { main } from "./build-keycloak-theme";
if (require.main === module) { if (require.main === module) {
main(); main();
} }

View File

@ -1,14 +1,10 @@
import * as crypto from "crypto"; import * as crypto from "crypto";
import { ftlValuesGlobalName } from "./ftlValuesGlobalName"; import { ftlValuesGlobalName } from "./ftlValuesGlobalName";
export function replaceImportsFromStaticInJsCode( export function replaceImportsFromStaticInJsCode(params: {
params: {
jsCode: string; jsCode: string;
urlOrigin: undefined | string; urlOrigin: undefined | string;
} }): { fixedJsCode: string } {
): { fixedJsCode: string; } {
/* /*
NOTE: NOTE:
@ -23,72 +19,63 @@ export function replaceImportsFromStaticInJsCode(
const { jsCode, urlOrigin } = params; const { jsCode, urlOrigin } = params;
const fixedJsCode = const fixedJsCode = jsCode
jsCode .replace(/([a-z]+\.[a-z]+)\+"static\//g, (...[, group]) =>
.replace( urlOrigin === undefined
/([a-z]+\.[a-z]+)\+"static\//g, ? `window.${ftlValuesGlobalName}.url.resourcesPath + "/build/static/`
(...[, group]) => : `("${ftlValuesGlobalName}" in window ? "${urlOrigin}" : "") + ${group} + "static/`,
urlOrigin === undefined ?
`window.${ftlValuesGlobalName}.url.resourcesPath + "/build/static/` :
`("${ftlValuesGlobalName}" in window ? "${urlOrigin}" : "") + ${group} + "static/`
) )
.replace( .replace(
/".chunk.css",([a-z])+=([a-z]+\.[a-z]+)\+([a-z]+),/, /".chunk.css",([a-z])+=([a-z]+\.[a-z]+)\+([a-z]+),/,
(...[, group1, group2, group3]) => (...[, group1, group2, group3]) =>
urlOrigin === undefined ? urlOrigin === undefined
`".chunk.css",${group1} = window.${ftlValuesGlobalName}.url.resourcesPath + "/build/" + ${group3},` : ? `".chunk.css",${group1} = window.${ftlValuesGlobalName}.url.resourcesPath + "/build/" + ${group3},`
`".chunk.css",${group1} = ("${ftlValuesGlobalName}" in window ? "${urlOrigin}" : "") + ${group2} + ${group3},` : `".chunk.css",${group1} = ("${ftlValuesGlobalName}" in window ? "${urlOrigin}" : "") + ${group2} + ${group3},`,
); );
return { fixedJsCode }; return { fixedJsCode };
} }
export function replaceImportsInInlineCssCode( export function replaceImportsInInlineCssCode(params: {
params: {
cssCode: string; cssCode: string;
urlPathname: string; urlPathname: string;
urlOrigin: undefined | string; urlOrigin: undefined | string;
} }): { fixedCssCode: string } {
): { fixedCssCode: string; } {
const { cssCode, urlPathname, urlOrigin } = params; const { cssCode, urlPathname, urlOrigin } = params;
const fixedCssCode = cssCode.replace( const fixedCssCode = cssCode.replace(
urlPathname === "/" ? urlPathname === "/"
/url\(\/([^/][^)]+)\)/g : ? /url\(\/([^/][^)]+)\)/g
new RegExp(`url\\(${urlPathname}([^)]+)\\)`, "g"), : new RegExp(`url\\(${urlPathname}([^)]+)\\)`, "g"),
(...[, group]) => `url(${urlOrigin === undefined ? (...[, group]) =>
"${url.resourcesPath}/build/" + group : `url(${
params.urlOrigin + urlPathname + group})` urlOrigin === undefined
? "${url.resourcesPath}/build/" + group
: params.urlOrigin + urlPathname + group
})`,
); );
return { fixedCssCode }; return { fixedCssCode };
} }
export function replaceImportsInCssCode( export function replaceImportsInCssCode(params: { cssCode: string }): {
params: {
cssCode: string;
}
): {
fixedCssCode: string; fixedCssCode: string;
cssGlobalsToDefine: Record<string, string>; cssGlobalsToDefine: Record<string, string>;
} { } {
const { cssCode } = params; const { cssCode } = params;
const cssGlobalsToDefine: Record<string, string> = {}; const cssGlobalsToDefine: Record<string, string> = {};
new Set(cssCode.match(/url\(\/[^/][^)]+\)[^;}]*/g) ?? []) new Set(cssCode.match(/url\(\/[^/][^)]+\)[^;}]*/g) ?? []).forEach(
.forEach(match => match =>
cssGlobalsToDefine[ (cssGlobalsToDefine[
"url" + crypto "url" +
crypto
.createHash("sha256") .createHash("sha256")
.update(match) .update(match)
.digest("hex") .digest("hex")
.substring(0, 15) .substring(0, 15)
] = match ] = match),
); );
let fixedCssCode = cssCode; let fixedCssCode = cssCode;
@ -96,41 +83,40 @@ export function replaceImportsInCssCode(
Object.keys(cssGlobalsToDefine).forEach( Object.keys(cssGlobalsToDefine).forEach(
cssVariableName => cssVariableName =>
//NOTE: split/join pattern ~ replace all //NOTE: split/join pattern ~ replace all
fixedCssCode = (fixedCssCode = fixedCssCode
fixedCssCode.split(cssGlobalsToDefine[cssVariableName]) .split(cssGlobalsToDefine[cssVariableName])
.join(`var(--${cssVariableName})`) .join(`var(--${cssVariableName})`)),
); );
return { fixedCssCode, cssGlobalsToDefine }; return { fixedCssCode, cssGlobalsToDefine };
} }
export function generateCssCodeToDefineGlobals( export function generateCssCodeToDefineGlobals(params: {
params: {
cssGlobalsToDefine: Record<string, string>; cssGlobalsToDefine: Record<string, string>;
urlPathname: string; urlPathname: string;
} }): {
): {
cssCodeToPrependInHead: string; cssCodeToPrependInHead: string;
} { } {
const { cssGlobalsToDefine, urlPathname } = params; const { cssGlobalsToDefine, urlPathname } = params;
return { return {
"cssCodeToPrependInHead": [ "cssCodeToPrependInHead": [
":root {", ":root {",
...Object.keys(cssGlobalsToDefine) ...Object.keys(cssGlobalsToDefine)
.map(cssVariableName => [ .map(cssVariableName =>
[
`--${cssVariableName}:`, `--${cssVariableName}:`,
cssGlobalsToDefine[cssVariableName] cssGlobalsToDefine[cssVariableName].replace(
.replace(new RegExp(`url\\(${urlPathname.replace(/\//g, "\\/")}`, "g"), "url(${url.resourcesPath}/build/") new RegExp(
].join(" ")) `url\\(${urlPathname.replace(/\//g, "\\/")}`,
"g",
),
"url(${url.resourcesPath}/build/",
),
].join(" "),
)
.map(line => ` ${line};`), .map(line => ` ${line};`),
"}" "}",
].join("\n") ].join("\n"),
}; };
} }

View File

@ -2,52 +2,49 @@
import { keycloakThemeBuildingDirPath } from "./build-keycloak-theme"; import { keycloakThemeBuildingDirPath } from "./build-keycloak-theme";
import { join as pathJoin } from "path"; import { join as pathJoin } from "path";
import { downloadAndUnzip } from "./tools/downloadAndUnzip" import { downloadAndUnzip } from "./tools/downloadAndUnzip";
import type { KeycloakVersion } from "./KeycloakVersion"; import type { KeycloakVersion } from "./KeycloakVersion";
export function downloadBuiltinKeycloakTheme( export function downloadBuiltinKeycloakTheme(params: {
params: {
keycloakVersion: KeycloakVersion; keycloakVersion: KeycloakVersion;
destDirPath: string; destDirPath: string;
} }) {
) {
const { keycloakVersion, destDirPath } = params; const { keycloakVersion, destDirPath } = params;
for (const ext of ["", "-community"]) { for (const ext of ["", "-community"]) {
downloadAndUnzip({ downloadAndUnzip({
"destDirPath": destDirPath, "destDirPath": destDirPath,
"url": `https://github.com/keycloak/keycloak/archive/refs/tags/${keycloakVersion}.zip`, "url": `https://github.com/keycloak/keycloak/archive/refs/tags/${keycloakVersion}.zip`,
"pathOfDirToExtractInArchive": `keycloak-${keycloakVersion}/themes/src/main/resources${ext}/theme` "pathOfDirToExtractInArchive": `keycloak-${keycloakVersion}/themes/src/main/resources${ext}/theme`,
}); });
} }
} }
if (require.main === module) { if (require.main === module) {
const keycloakVersion = (() => { const keycloakVersion = (() => {
const keycloakVersion = process.argv[2] as KeycloakVersion | undefined;
const keycloakVersion = process.argv[2] as (KeycloakVersion | undefined);
if (keycloakVersion === undefined) { if (keycloakVersion === undefined) {
return "15.0.2"; return "15.0.2";
} }
return keycloakVersion; return keycloakVersion;
})(); })();
const destDirPath = pathJoin(keycloakThemeBuildingDirPath, "src", "main", "resources", "theme"); const destDirPath = pathJoin(
keycloakThemeBuildingDirPath,
"src",
"main",
"resources",
"theme",
);
console.log(`Downloading builtins theme of Keycloak ${keycloakVersion} here ${destDirPath}`); console.log(
`Downloading builtins theme of Keycloak ${keycloakVersion} here ${destDirPath}`,
);
downloadBuiltinKeycloakTheme({ downloadBuiltinKeycloakTheme({
keycloakVersion, keycloakVersion,
destDirPath destDirPath,
}); });
} }

View File

@ -11,7 +11,6 @@ import { keycloakVersions } from "./KeycloakVersion";
const propertiesParser = require("properties-parser"); const propertiesParser = require("properties-parser");
for (const keycloakVersion of keycloakVersions) { for (const keycloakVersion of keycloakVersions) {
console.log({ keycloakVersion }); console.log({ keycloakVersion });
const tmpDirPath = pathJoin(getProjectRoot(), "tmp_xImOef9dOd44"); const tmpDirPath = pathJoin(getProjectRoot(), "tmp_xImOef9dOd44");
@ -20,20 +19,21 @@ for (const keycloakVersion of keycloakVersions) {
downloadBuiltinKeycloakTheme({ downloadBuiltinKeycloakTheme({
keycloakVersion, keycloakVersion,
"destDirPath": tmpDirPath "destDirPath": tmpDirPath,
}); });
type Dictionary = { [idiomId: string]: string }; type Dictionary = { [idiomId: string]: string };
const record: { [typeOfPage: string]: { [language: string]: Dictionary } } = {}; const record: { [typeOfPage: string]: { [language: string]: Dictionary } } =
{};
{ {
const baseThemeDirPath = pathJoin(tmpDirPath, "base"); const baseThemeDirPath = pathJoin(tmpDirPath, "base");
crawl(baseThemeDirPath).forEach(filePath => { crawl(baseThemeDirPath).forEach(filePath => {
const match = filePath.match(
const match = filePath.match(/^([^/]+)\/messages\/messages_([^.]+)\.properties$/); /^([^/]+)\/messages\/messages_([^.]+)\.properties$/,
);
if (match === null) { if (match === null) {
return; return;
@ -45,43 +45,58 @@ for (const keycloakVersion of keycloakVersions) {
Object.fromEntries( Object.fromEntries(
Object.entries( Object.entries(
propertiesParser.parse( propertiesParser.parse(
fs.readFileSync( fs
pathJoin(baseThemeDirPath, filePath) .readFileSync(
pathJoin(baseThemeDirPath, filePath),
) )
.toString("utf8") .toString("utf8"),
) ),
).map(([key, value]: any) => [key, value.replace(/''/g, "'")]) ).map(([key, value]: any) => [
key,
value.replace(/''/g, "'"),
]),
); );
}); });
} }
rm_r(tmpDirPath); rm_r(tmpDirPath);
const targetDirPath = pathJoin(getProjectRoot(), "src", "lib", "i18n", "generated_kcMessages", keycloakVersion); const targetDirPath = pathJoin(
getProjectRoot(),
"src",
"lib",
"i18n",
"generated_kcMessages",
keycloakVersion,
);
fs.mkdirSync(targetDirPath, { "recursive": true }); fs.mkdirSync(targetDirPath, { "recursive": true });
Object.keys(record).forEach(pageType => { Object.keys(record).forEach(pageType => {
const filePath = pathJoin(targetDirPath, `${pageType}.ts`); const filePath = pathJoin(targetDirPath, `${pageType}.ts`);
fs.writeFileSync( fs.writeFileSync(
filePath, filePath,
Buffer.from( Buffer.from(
[ [
`//This code was automatically generated by running ${pathRelative(getProjectRoot(), __filename)}`, `//This code was automatically generated by running ${pathRelative(
'//PLEASE DO NOT EDIT MANUALLY', getProjectRoot(),
'', __filename,
'/* spell-checker: disable */', )}`,
`export const kcMessages= ${JSON.stringify(record[pageType], null, 2)};`, "//PLEASE DO NOT EDIT MANUALLY",
'/* spell-checker: enable */' "",
].join("\n"), "utf8") "/* spell-checker: disable */",
`export const kcMessages= ${JSON.stringify(
record[pageType],
null,
2,
)};`,
"/* spell-checker: enable */",
].join("\n"),
"utf8",
),
); );
console.log(`${filePath} wrote`); console.log(`${filePath} wrote`);
}); });
} }

View File

@ -1,4 +1,3 @@
import { execSync } from "child_process"; import { execSync } from "child_process";
import { join as pathJoin, relative as pathRelative } from "path"; import { join as pathJoin, relative as pathRelative } from "path";
import * as fs from "fs"; import * as fs from "fs";
@ -12,7 +11,9 @@ fs.writeFileSync(
(() => { (() => {
const packageJsonParsed = JSON.parse( const packageJsonParsed = JSON.parse(
fs fs
.readFileSync(pathJoin(keycloakifyDirPath, "package.json")) .readFileSync(
pathJoin(keycloakifyDirPath, "package.json"),
)
.toString("utf8"), .toString("utf8"),
); );
@ -31,7 +32,13 @@ fs.writeFileSync(
const commonThirdPartyDeps = (() => { const commonThirdPartyDeps = (() => {
const namespaceModuleNames = ["@emotion"]; const namespaceModuleNames = ["@emotion"];
const standaloneModuleNames = ["react", "@types/react", "powerhooks", "tss-react", "evt"]; const standaloneModuleNames = [
"react",
"@types/react",
"powerhooks",
"tss-react",
"evt",
];
return [ return [
...namespaceModuleNames ...namespaceModuleNames
@ -69,7 +76,9 @@ const execYarnLink = (params: { targetModuleName?: string; cwd: string }) => {
...(targetModuleName !== undefined ? [targetModuleName] : []), ...(targetModuleName !== undefined ? [targetModuleName] : []),
].join(" "); ].join(" ");
console.log(`$ cd ${pathRelative(keycloakifyDirPath, cwd) || "."} && ${cmd}`); console.log(
`$ cd ${pathRelative(keycloakifyDirPath, cwd) || "."} && ${cmd}`,
);
execSync(cmd, { execSync(cmd, {
cwd, cwd,

View File

@ -3,35 +3,25 @@ import * as path from "path";
/** List all files in a given directory return paths relative to the dir_path */ /** List all files in a given directory return paths relative to the dir_path */
export const crawl = (() => { export const crawl = (() => {
const crawlRec = (dir_path: string, paths: string[]) => { const crawlRec = (dir_path: string, paths: string[]) => {
for (const file_name of fs.readdirSync(dir_path)) { for (const file_name of fs.readdirSync(dir_path)) {
const file_path = path.join(dir_path, file_name); const file_path = path.join(dir_path, file_name);
if (fs.lstatSync(file_path).isDirectory()) { if (fs.lstatSync(file_path).isDirectory()) {
crawlRec(file_path, paths); crawlRec(file_path, paths);
continue; continue;
} }
paths.push(file_path); paths.push(file_path);
} }
}; };
return function crawl(dir_path: string): string[] { return function crawl(dir_path: string): string[] {
const paths: string[] = []; const paths: string[] = [];
crawlRec(dir_path, paths); crawlRec(dir_path, paths);
return paths.map(file_path => path.relative(dir_path, file_path)); return paths.map(file_path => path.relative(dir_path, file_path));
};
}
})(); })();

View File

@ -1,4 +1,3 @@
import { basename as pathBasename, join as pathJoin } from "path"; import { basename as pathBasename, join as pathJoin } from "path";
import { execSync } from "child_process"; import { execSync } from "child_process";
import fs from "fs"; import fs from "fs";
@ -6,14 +5,11 @@ import { transformCodebase } from "../tools/transformCodebase";
import { rm_rf, rm, rm_r } from "./rm"; import { rm_rf, rm, rm_r } from "./rm";
/** assert url ends with .zip */ /** assert url ends with .zip */
export function downloadAndUnzip( export function downloadAndUnzip(params: {
params: {
url: string; url: string;
destDirPath: string; destDirPath: string;
pathOfDirToExtractInArchive?: string; pathOfDirToExtractInArchive?: string;
} }) {
) {
const { url, destDirPath, pathOfDirToExtractInArchive } = params; const { url, destDirPath, pathOfDirToExtractInArchive } = params;
const tmpDirPath = pathJoin(destDirPath, "..", "tmp_xxKdOxnEdx"); const tmpDirPath = pathJoin(destDirPath, "..", "tmp_xxKdOxnEdx");
@ -25,23 +21,23 @@ export function downloadAndUnzip(
execSync(`wget ${url}`, { "cwd": tmpDirPath }); execSync(`wget ${url}`, { "cwd": tmpDirPath });
execSync( execSync(
`unzip ${pathBasename(url) `unzip ${pathBasename(url)}${
}${pathOfDirToExtractInArchive === undefined ? pathOfDirToExtractInArchive === undefined
"" : ` "${pathOfDirToExtractInArchive}/*"` ? ""
: ` "${pathOfDirToExtractInArchive}/*"`
}`, }`,
{ "cwd": tmpDirPath } { "cwd": tmpDirPath },
); );
rm(pathBasename(url), { "cwd": tmpDirPath }); rm(pathBasename(url), { "cwd": tmpDirPath });
transformCodebase({ transformCodebase({
"srcDirPath": pathOfDirToExtractInArchive === undefined ? "srcDirPath":
tmpDirPath : pathOfDirToExtractInArchive === undefined
pathJoin(tmpDirPath, pathOfDirToExtractInArchive) ? tmpDirPath
, : pathJoin(tmpDirPath, pathOfDirToExtractInArchive),
destDirPath, destDirPath,
}); });
rm_r(tmpDirPath); rm_r(tmpDirPath);
} }

View File

@ -1,8 +1,11 @@
import { getProjectRoot } from "./getProjectRoot";
import { join as pathJoin } from "path";
import child_process from "child_process";
import { getProjectRoot } from "./getProjectRoot"; Object.entries<string>(
import { join as pathJoin } from "path"; require(pathJoin(getProjectRoot(), "package.json"))["bin"],
import child_process from "child_process"; ).forEach(([, scriptPath]) =>
child_process.execSync(`chmod +x ${scriptPath}`, {
Object.entries<string>(require(pathJoin(getProjectRoot(), "package.json"))["bin"]) "cwd": getProjectRoot(),
.forEach(([, scriptPath]) => child_process.execSync(`chmod +x ${scriptPath}`, { "cwd": getProjectRoot() })); }),
);

View File

@ -1,14 +1,7 @@
import { relative as pathRelative } from "path"; import { relative as pathRelative } from "path";
export function isInside( export function isInside(params: { dirPath: string; filePath: string }) {
params: {
dirPath: string;
filePath: string;
}
) {
const { dirPath, filePath } = params; const { dirPath, filePath } = params;
return !pathRelative(dirPath, filePath).startsWith(".."); return !pathRelative(dirPath, filePath).startsWith("..");
} }

View File

@ -1,23 +1,19 @@
import { execSync } from "child_process"; import { execSync } from "child_process";
function rmInternal( function rmInternal(params: {
params: {
pathToRemove: string; pathToRemove: string;
args: string | undefined; args: string | undefined;
cwd: string | undefined; cwd: string | undefined;
} }) {
) {
const { pathToRemove, args, cwd } = params; const { pathToRemove, args, cwd } = params;
execSync( execSync(
`rm ${args ? `-${args} ` : ""}${pathToRemove.replace(/\ /g, "\\ ")}`, `rm ${args ? `-${args} ` : ""}${pathToRemove.replace(/ /g, "\\ ")}`,
cwd !== undefined ? { cwd } : undefined cwd !== undefined ? { cwd } : undefined,
); );
} }
export function rm(pathToRemove: string, options?: { cwd: string; }) { export function rm(pathToRemove: string, options?: { cwd: string }) {
rmInternal({ rmInternal({
pathToRemove, pathToRemove,
"args": undefined, "args": undefined,
@ -25,7 +21,7 @@ export function rm(pathToRemove: string, options?: { cwd: string; }) {
}); });
} }
export function rm_r(pathToRemove: string, options?: { cwd: string; }) { export function rm_r(pathToRemove: string, options?: { cwd: string }) {
rmInternal({ rmInternal({
pathToRemove, pathToRemove,
"args": "r", "args": "r",
@ -33,7 +29,7 @@ export function rm_r(pathToRemove: string, options?: { cwd: string; }) {
}); });
} }
export function rm_rf(pathToRemove: string, options?: { cwd: string; }) { export function rm_rf(pathToRemove: string, options?: { cwd: string }) {
rmInternal({ rmInternal({
pathToRemove, pathToRemove,
"args": "rf", "args": "rf",

View File

@ -1,69 +1,56 @@
import * as fs from "fs"; import * as fs from "fs";
import * as path from "path"; import * as path from "path";
import { crawl } from "./crawl"; import { crawl } from "./crawl";
import { id } from "tsafe/id"; import { id } from "tsafe/id";
type TransformSourceCode = type TransformSourceCode = (params: {
(params: {
sourceCode: Buffer; sourceCode: Buffer;
filePath: string; filePath: string;
}) => { }) =>
| {
modifiedSourceCode: Buffer; modifiedSourceCode: Buffer;
newFileName?: string; newFileName?: string;
} | undefined; }
| undefined;
/** Apply a transformation function to every file of directory */ /** Apply a transformation function to every file of directory */
export function transformCodebase( export function transformCodebase(params: {
params: {
srcDirPath: string; srcDirPath: string;
destDirPath: string; destDirPath: string;
transformSourceCode?: TransformSourceCode; transformSourceCode?: TransformSourceCode;
} }) {
) {
const { const {
srcDirPath, srcDirPath,
destDirPath, destDirPath,
transformSourceCode = id<TransformSourceCode>(({ sourceCode }) => ({ "modifiedSourceCode": sourceCode })) transformSourceCode = id<TransformSourceCode>(({ sourceCode }) => ({
"modifiedSourceCode": sourceCode,
})),
} = params; } = params;
for (const file_relative_path of crawl(srcDirPath)) { for (const file_relative_path of crawl(srcDirPath)) {
const filePath = path.join(srcDirPath, file_relative_path); const filePath = path.join(srcDirPath, file_relative_path);
const transformSourceCodeResult = transformSourceCode({ const transformSourceCodeResult = transformSourceCode({
"sourceCode": fs.readFileSync(filePath), "sourceCode": fs.readFileSync(filePath),
"filePath": path.join(srcDirPath, file_relative_path) "filePath": path.join(srcDirPath, file_relative_path),
}); });
if (transformSourceCodeResult === undefined) { if (transformSourceCodeResult === undefined) {
continue; continue;
} }
fs.mkdirSync( fs.mkdirSync(path.dirname(path.join(destDirPath, file_relative_path)), {
path.dirname( "recursive": true,
path.join( });
destDirPath,
file_relative_path
)
),
{ "recursive": true }
);
const { newFileName, modifiedSourceCode } = transformSourceCodeResult; const { newFileName, modifiedSourceCode } = transformSourceCodeResult;
fs.writeFileSync( fs.writeFileSync(
path.join( path.join(
path.dirname(path.join(destDirPath, file_relative_path)), path.dirname(path.join(destDirPath, file_relative_path)),
newFileName ?? path.basename(file_relative_path) newFileName ?? path.basename(file_relative_path),
), ),
modifiedSourceCode modifiedSourceCode,
); );
} }
} }

View File

@ -4,8 +4,8 @@ import type { KcProps } from "./KcProps";
import type { KcContextBase } from "../getKcContext/KcContextBase"; import type { KcContextBase } from "../getKcContext/KcContextBase";
import { useKcMessage } from "../i18n/useKcMessage"; import { useKcMessage } from "../i18n/useKcMessage";
export const Error = memo(({ kcContext, ...props }: { kcContext: KcContextBase.Error; } & KcProps) => { export const Error = memo(
({ kcContext, ...props }: { kcContext: KcContextBase.Error } & KcProps) => {
const { msg } = useKcMessage(); const { msg } = useKcMessage();
const { message, client } = kcContext; const { message, client } = kcContext;
@ -19,18 +19,16 @@ export const Error = memo(({ kcContext, ...props }: { kcContext: KcContextBase.E
formNode={ formNode={
<div id="kc-error-message"> <div id="kc-error-message">
<p className="instruction">{message.summary}</p> <p className="instruction">{message.summary}</p>
{ {client !== undefined && client.baseUrl !== undefined && (
client !== undefined && client.baseUrl !== undefined &&
<p> <p>
<a id="backToApplication" href={client.baseUrl}> <a id="backToApplication" href={client.baseUrl}>
{msg("backToApplication")} {msg("backToApplication")}
</a> </a>
</p> </p>
} )}
</div> </div>
} }
/> />
); );
}); },
);

View File

@ -1,4 +1,3 @@
import { memo } from "react"; import { memo } from "react";
import { Template } from "./Template"; import { Template } from "./Template";
import type { KcProps } from "./KcProps"; import type { KcProps } from "./KcProps";
@ -6,8 +5,8 @@ import { assert } from "../tools/assert";
import type { KcContextBase } from "../getKcContext/KcContextBase"; import type { KcContextBase } from "../getKcContext/KcContextBase";
import { useKcMessage } from "../i18n/useKcMessage"; import { useKcMessage } from "../i18n/useKcMessage";
export const Info = memo(({ kcContext, ...props }: { kcContext: KcContextBase.Info; } & KcProps) => { export const Info = memo(
({ kcContext, ...props }: { kcContext: KcContextBase.Info } & KcProps) => {
const { msg } = useKcMessage(); const { msg } = useKcMessage();
assert(kcContext.message !== undefined); assert(kcContext.message !== undefined);
@ -19,7 +18,7 @@ export const Info = memo(({ kcContext, ...props }: { kcContext: KcContextBase.In
skipLink, skipLink,
pageRedirectUri, pageRedirectUri,
actionUri, actionUri,
client client,
} = kcContext; } = kcContext;
return ( return (
@ -28,46 +27,53 @@ export const Info = memo(({ kcContext, ...props }: { kcContext: KcContextBase.In
doFetchDefaultThemeResources={true} doFetchDefaultThemeResources={true}
displayMessage={false} displayMessage={false}
headerNode={ headerNode={
messageHeader !== undefined ? messageHeader !== undefined ? (
<>{messageHeader}</> <>{messageHeader}</>
: ) : (
<>{message.summary}</> <>{message.summary}</>
)
} }
formNode={ formNode={
<div id="kc-info-message"> <div id="kc-info-message">
<p className="instruction">{message.summary} <p className="instruction">
{message.summary}
{ {requiredActions !== undefined && (
requiredActions !== undefined &&
<b> <b>
{ {requiredActions
requiredActions .map(requiredAction =>
.map(requiredAction => msg(`requiredAction.${requiredAction}` as const)) msg(
.join(",") `requiredAction.${requiredAction}` as const,
} ),
)
.join(",")}
</b> </b>
)}
}
</p> </p>
{ {!skipLink && pageRedirectUri !== undefined ? (
!skipLink && <p>
pageRedirectUri !== undefined ? <a href={pageRedirectUri}>
<p><a href={pageRedirectUri}>{(msg("backToApplication"))}</a></p> {msg("backToApplication")}
: </a>
actionUri !== undefined ? </p>
<p><a href={actionUri}>{msg("proceedWithAction")}</a></p> ) : actionUri !== undefined ? (
: <p>
client.baseUrl !== undefined && <a href={actionUri}>
<p><a href={client.baseUrl}>{msg("backToApplication")}</a></p> {msg("proceedWithAction")}
} </a>
</p>
) : (
client.baseUrl !== undefined && (
<p>
<a href={client.baseUrl}>
{msg("backToApplication")}
</a>
</p>
)
)}
</div> </div>
} }
/> />
); );
}); },
);

View File

@ -1,4 +1,3 @@
import { memo } from "react"; import { memo } from "react";
import type { KcContextBase } from "../getKcContext/KcContextBase"; import type { KcContextBase } from "../getKcContext/KcContextBase";
import type { KcProps } from "./KcProps"; import type { KcProps } from "./KcProps";
@ -14,18 +13,31 @@ import { LoginOtp } from "./LoginOtp";
import { LoginUpdateProfile } from "./LoginUpdateProfile"; import { LoginUpdateProfile } from "./LoginUpdateProfile";
import { LoginIdpLinkConfirm } from "./LoginIdpLinkConfirm"; import { LoginIdpLinkConfirm } from "./LoginIdpLinkConfirm";
export const KcApp = memo(({ kcContext, ...props }: { kcContext: KcContextBase; } & KcProps) => { export const KcApp = memo(
({ kcContext, ...props }: { kcContext: KcContextBase } & KcProps) => {
switch (kcContext.pageId) { switch (kcContext.pageId) {
case "login.ftl": return <Login {...{ kcContext, ...props }} />; case "login.ftl":
case "register.ftl": return <Register {...{ kcContext, ...props }} />; return <Login {...{ kcContext, ...props }} />;
case "register-user-profile.ftl": return <RegisterUserProfile {...{ kcContext, ...props }} />; case "register.ftl":
case "info.ftl": return <Info {...{ kcContext, ...props }} />; return <Register {...{ kcContext, ...props }} />;
case "error.ftl": return <Error {...{ kcContext, ...props }} />; case "register-user-profile.ftl":
case "login-reset-password.ftl": return <LoginResetPassword {...{ kcContext, ...props }} />; return <RegisterUserProfile {...{ kcContext, ...props }} />;
case "login-verify-email.ftl": return <LoginVerifyEmail {...{ kcContext, ...props }} />; case "info.ftl":
case "terms.ftl": return <Terms {...{ kcContext, ...props }} />; return <Info {...{ kcContext, ...props }} />;
case "login-otp.ftl": return <LoginOtp {...{ kcContext, ...props }} />; case "error.ftl":
case "login-update-profile.ftl": return <LoginUpdateProfile {...{ kcContext, ...props }} />; return <Error {...{ kcContext, ...props }} />;
case "login-idp-link-confirm.ftl": return <LoginIdpLinkConfirm {...{ kcContext, ...props }} />; case "login-reset-password.ftl":
return <LoginResetPassword {...{ kcContext, ...props }} />;
case "login-verify-email.ftl":
return <LoginVerifyEmail {...{ kcContext, ...props }} />;
case "terms.ftl":
return <Terms {...{ kcContext, ...props }} />;
case "login-otp.ftl":
return <LoginOtp {...{ kcContext, ...props }} />;
case "login-update-profile.ftl":
return <LoginUpdateProfile {...{ kcContext, ...props }} />;
case "login-idp-link-confirm.ftl":
return <LoginIdpLinkConfirm {...{ kcContext, ...props }} />;
} }
}); },
);

View File

@ -1,39 +1,39 @@
import { allPropertiesValuesToUndefined } from "../tools/allPropertiesValuesToUndefined"; import { allPropertiesValuesToUndefined } from "../tools/allPropertiesValuesToUndefined";
import { assert } from "tsafe/assert"; import { assert } from "tsafe/assert";
/** Class names can be provided as an array or separated by whitespace */ /** Class names can be provided as an array or separated by whitespace */
export type KcPropsGeneric<CssClasses extends string> = { [key in CssClasses]: readonly string[] | string | undefined; }; export type KcPropsGeneric<CssClasses extends string> = {
[key in CssClasses]: readonly string[] | string | undefined;
};
export type KcTemplateClassKey = export type KcTemplateClassKey =
"stylesCommon" | | "stylesCommon"
"styles" | | "styles"
"scripts" | | "scripts"
"kcHtmlClass" | | "kcHtmlClass"
"kcLoginClass" | | "kcLoginClass"
"kcHeaderClass" | | "kcHeaderClass"
"kcHeaderWrapperClass" | | "kcHeaderWrapperClass"
"kcFormCardClass" | | "kcFormCardClass"
"kcFormCardAccountClass" | | "kcFormCardAccountClass"
"kcFormHeaderClass" | | "kcFormHeaderClass"
"kcLocaleWrapperClass" | | "kcLocaleWrapperClass"
"kcContentWrapperClass" | | "kcContentWrapperClass"
"kcLabelWrapperClass" | | "kcLabelWrapperClass"
"kcContentWrapperClass" | | "kcContentWrapperClass"
"kcLabelWrapperClass" | | "kcLabelWrapperClass"
"kcFormGroupClass" | | "kcFormGroupClass"
"kcResetFlowIcon" | | "kcResetFlowIcon"
"kcResetFlowIcon" | | "kcResetFlowIcon"
"kcFeedbackSuccessIcon" | | "kcFeedbackSuccessIcon"
"kcFeedbackWarningIcon" | | "kcFeedbackWarningIcon"
"kcFeedbackErrorIcon" | | "kcFeedbackErrorIcon"
"kcFeedbackInfoIcon" | | "kcFeedbackInfoIcon"
"kcContentWrapperClass" | | "kcContentWrapperClass"
"kcFormSocialAccountContentClass" | | "kcFormSocialAccountContentClass"
"kcFormSocialAccountClass" | | "kcFormSocialAccountClass"
"kcSignUpClass" | | "kcSignUpClass"
"kcInfoAreaWrapperClass" | "kcInfoAreaWrapperClass";
;
export type KcTemplateProps = KcPropsGeneric<KcTemplateClassKey>; export type KcTemplateProps = KcPropsGeneric<KcTemplateClassKey>;
@ -41,7 +41,7 @@ export const defaultKcTemplateProps = {
"stylesCommon": [ "stylesCommon": [
"node_modules/patternfly/dist/css/patternfly.min.css", "node_modules/patternfly/dist/css/patternfly.min.css",
"node_modules/patternfly/dist/css/patternfly-additions.min.css", "node_modules/patternfly/dist/css/patternfly-additions.min.css",
"lib/zocial/zocial.css" "lib/zocial/zocial.css",
], ],
"styles": ["css/login.css"], "styles": ["css/login.css"],
"scripts": [], "scripts": [],
@ -64,69 +64,69 @@ export const defaultKcTemplateProps = {
"kcFormGroupClass": ["form-group"], "kcFormGroupClass": ["form-group"],
"kcLabelWrapperClass": ["col-xs-12", "col-sm-12", "col-md-12", "col-lg-12"], "kcLabelWrapperClass": ["col-xs-12", "col-sm-12", "col-md-12", "col-lg-12"],
"kcSignUpClass": ["login-pf-signup"], "kcSignUpClass": ["login-pf-signup"],
"kcInfoAreaWrapperClass": [] "kcInfoAreaWrapperClass": [],
} as const; } as const;
assert<typeof defaultKcTemplateProps extends KcTemplateProps ? true : false>(); assert<typeof defaultKcTemplateProps extends KcTemplateProps ? true : false>();
/** Tu use if you don't want any default */ /** Tu use if you don't want any default */
export const allClearKcTemplateProps = export const allClearKcTemplateProps = allPropertiesValuesToUndefined(
allPropertiesValuesToUndefined(defaultKcTemplateProps); defaultKcTemplateProps,
);
assert<typeof allClearKcTemplateProps extends KcTemplateProps ? true : false>(); assert<typeof allClearKcTemplateProps extends KcTemplateProps ? true : false>();
export type KcProps = KcPropsGeneric< export type KcProps = KcPropsGeneric<
KcTemplateClassKey | | KcTemplateClassKey
"kcLogoLink" | | "kcLogoLink"
"kcLogoClass" | | "kcLogoClass"
"kcContainerClass" | | "kcContainerClass"
"kcContentClass" | | "kcContentClass"
"kcFeedbackAreaClass" | | "kcFeedbackAreaClass"
"kcLocaleClass" | | "kcLocaleClass"
"kcAlertIconClasserror" | | "kcAlertIconClasserror"
"kcFormAreaClass" | | "kcFormAreaClass"
"kcFormSocialAccountListClass" | | "kcFormSocialAccountListClass"
"kcFormSocialAccountDoubleListClass" | | "kcFormSocialAccountDoubleListClass"
"kcFormSocialAccountListLinkClass" | | "kcFormSocialAccountListLinkClass"
"kcWebAuthnKeyIcon" | | "kcWebAuthnKeyIcon"
"kcFormClass" | | "kcFormClass"
"kcFormGroupErrorClass" | | "kcFormGroupErrorClass"
"kcLabelClass" | | "kcLabelClass"
"kcInputClass" | | "kcInputClass"
"kcInputErrorMessageClass" | | "kcInputErrorMessageClass"
"kcInputWrapperClass" | | "kcInputWrapperClass"
"kcFormOptionsClass" | | "kcFormOptionsClass"
"kcFormButtonsClass" | | "kcFormButtonsClass"
"kcFormSettingClass" | | "kcFormSettingClass"
"kcTextareaClass" | | "kcTextareaClass"
"kcInfoAreaClass" | | "kcInfoAreaClass"
"kcFormGroupHeader" | | "kcFormGroupHeader"
"kcButtonClass" | | "kcButtonClass"
"kcButtonPrimaryClass" | | "kcButtonPrimaryClass"
"kcButtonDefaultClass" | | "kcButtonDefaultClass"
"kcButtonLargeClass" | | "kcButtonLargeClass"
"kcButtonBlockClass" | | "kcButtonBlockClass"
"kcInputLargeClass" | | "kcInputLargeClass"
"kcSrOnlyClass" | | "kcSrOnlyClass"
"kcSelectAuthListClass" | | "kcSelectAuthListClass"
"kcSelectAuthListItemClass" | | "kcSelectAuthListItemClass"
"kcSelectAuthListItemInfoClass" | | "kcSelectAuthListItemInfoClass"
"kcSelectAuthListItemLeftClass" | | "kcSelectAuthListItemLeftClass"
"kcSelectAuthListItemBodyClass" | | "kcSelectAuthListItemBodyClass"
"kcSelectAuthListItemDescriptionClass" | | "kcSelectAuthListItemDescriptionClass"
"kcSelectAuthListItemHeadingClass" | | "kcSelectAuthListItemHeadingClass"
"kcSelectAuthListItemHelpTextClass" | | "kcSelectAuthListItemHelpTextClass"
"kcAuthenticatorDefaultClass" | | "kcAuthenticatorDefaultClass"
"kcAuthenticatorPasswordClass" | | "kcAuthenticatorPasswordClass"
"kcAuthenticatorOTPClass" | | "kcAuthenticatorOTPClass"
"kcAuthenticatorWebAuthnClass" | | "kcAuthenticatorWebAuthnClass"
"kcAuthenticatorWebAuthnPasswordlessClass" | | "kcAuthenticatorWebAuthnPasswordlessClass"
"kcSelectOTPListClass" | | "kcSelectOTPListClass"
"kcSelectOTPListItemClass" | | "kcSelectOTPListItemClass"
"kcAuthenticatorOtpCircleClass" | | "kcAuthenticatorOtpCircleClass"
"kcSelectOTPItemHeadingClass" | | "kcSelectOTPItemHeadingClass"
"kcFormOptionsWrapperClass" | "kcFormOptionsWrapperClass"
>; >;
export const defaultKcProps = { export const defaultKcProps = {
@ -134,13 +134,31 @@ export const defaultKcProps = {
"kcLogoLink": "http://www.keycloak.org", "kcLogoLink": "http://www.keycloak.org",
"kcLogoClass": "login-pf-brand", "kcLogoClass": "login-pf-brand",
"kcContainerClass": "container-fluid", "kcContainerClass": "container-fluid",
"kcContentClass": ["col-sm-8", "col-sm-offset-2", "col-md-6", "col-md-offset-3", "col-lg-6", "col-lg-offset-3"], "kcContentClass": [
"col-sm-8",
"col-sm-offset-2",
"col-md-6",
"col-md-offset-3",
"col-lg-6",
"col-lg-offset-3",
],
"kcFeedbackAreaClass": ["col-md-12"], "kcFeedbackAreaClass": ["col-md-12"],
"kcLocaleClass": ["col-xs-12", "col-sm-1"], "kcLocaleClass": ["col-xs-12", "col-sm-1"],
"kcAlertIconClasserror": ["pficon", "pficon-error-circle-o"], "kcAlertIconClasserror": ["pficon", "pficon-error-circle-o"],
"kcFormAreaClass": ["col-sm-10", "col-sm-offset-1", "col-md-8", "col-md-offset-2", "col-lg-8", "col-lg-offset-2"], "kcFormAreaClass": [
"kcFormSocialAccountListClass": ["login-pf-social", "list-unstyled", "login-pf-social-all"], "col-sm-10",
"col-sm-offset-1",
"col-md-8",
"col-md-offset-2",
"col-lg-8",
"col-lg-offset-2",
],
"kcFormSocialAccountListClass": [
"login-pf-social",
"list-unstyled",
"login-pf-social-all",
],
"kcFormSocialAccountDoubleListClass": ["login-pf-social-double-col"], "kcFormSocialAccountDoubleListClass": ["login-pf-social-double-col"],
"kcFormSocialAccountListLinkClass": ["login-pf-social-link"], "kcFormSocialAccountListLinkClass": ["login-pf-social-link"],
"kcWebAuthnKeyIcon": ["pficon", "pficon-key"], "kcWebAuthnKeyIcon": ["pficon", "pficon-key"],
@ -149,14 +167,25 @@ export const defaultKcProps = {
"kcFormGroupErrorClass": ["has-error"], "kcFormGroupErrorClass": ["has-error"],
"kcLabelClass": ["control-label"], "kcLabelClass": ["control-label"],
"kcInputClass": ["form-control"], "kcInputClass": ["form-control"],
"kcInputErrorMessageClass": ["pf-c-form__helper-text", "pf-m-error", "required", "kc-feedback-text"], "kcInputErrorMessageClass": [
"pf-c-form__helper-text",
"pf-m-error",
"required",
"kc-feedback-text",
],
"kcInputWrapperClass": ["col-xs-12", "col-sm-12", "col-md-12", "col-lg-12"], "kcInputWrapperClass": ["col-xs-12", "col-sm-12", "col-md-12", "col-lg-12"],
"kcFormOptionsClass": ["col-xs-12", "col-sm-12", "col-md-12", "col-lg-12"], "kcFormOptionsClass": ["col-xs-12", "col-sm-12", "col-md-12", "col-lg-12"],
"kcFormButtonsClass": ["col-xs-12", "col-sm-12", "col-md-12", "col-lg-12"], "kcFormButtonsClass": ["col-xs-12", "col-sm-12", "col-md-12", "col-lg-12"],
"kcFormSettingClass": ["login-pf-settings"], "kcFormSettingClass": ["login-pf-settings"],
"kcTextareaClass": ["form-control"], "kcTextareaClass": ["form-control"],
"kcInfoAreaClass": ["col-xs-12", "col-sm-4", "col-md-4", "col-lg-5", "details"], "kcInfoAreaClass": [
"col-xs-12",
"col-sm-4",
"col-md-4",
"col-lg-5",
"details",
],
// user-profile grouping // user-profile grouping
"kcFormGroupHeader": ["pf-c-form__group"], "kcFormGroupHeader": ["pf-c-form__group"],
@ -191,21 +220,28 @@ export const defaultKcProps = {
"kcAuthenticatorPasswordClass": ["fa", "fa-unlock list-view-pf-icon-lg"], "kcAuthenticatorPasswordClass": ["fa", "fa-unlock list-view-pf-icon-lg"],
"kcAuthenticatorOTPClass": ["fa", "fa-mobile", "list-view-pf-icon-lg"], "kcAuthenticatorOTPClass": ["fa", "fa-mobile", "list-view-pf-icon-lg"],
"kcAuthenticatorWebAuthnClass": ["fa", "fa-key", "list-view-pf-icon-lg"], "kcAuthenticatorWebAuthnClass": ["fa", "fa-key", "list-view-pf-icon-lg"],
"kcAuthenticatorWebAuthnPasswordlessClass": ["fa", "fa-key", "list-view-pf-icon-lg"], "kcAuthenticatorWebAuthnPasswordlessClass": [
"fa",
"fa-key",
"list-view-pf-icon-lg",
],
//css classes for the OTP Login Form //css classes for the OTP Login Form
"kcSelectOTPListClass": ["card-pf", "card-pf-view", "card-pf-view-select", "card-pf-view-single-select"], "kcSelectOTPListClass": [
"card-pf",
"card-pf-view",
"card-pf-view-select",
"card-pf-view-single-select",
],
"kcSelectOTPListItemClass": ["card-pf-body", "card-pf-top-element"], "kcSelectOTPListItemClass": ["card-pf-body", "card-pf-top-element"],
"kcAuthenticatorOtpCircleClass": ["fa", "fa-mobile", "card-pf-icon-circle"], "kcAuthenticatorOtpCircleClass": ["fa", "fa-mobile", "card-pf-icon-circle"],
"kcSelectOTPItemHeadingClass": ["card-pf-title", "text-center"], "kcSelectOTPItemHeadingClass": ["card-pf-title", "text-center"],
"kcFormOptionsWrapperClass": [] "kcFormOptionsWrapperClass": [],
} as const; } as const;
assert<typeof defaultKcProps extends KcProps ? true : false>(); assert<typeof defaultKcProps extends KcProps ? true : false>();
/** Tu use if you don't want any default */ /** Tu use if you don't want any default */
export const allClearKcProps = export const allClearKcProps = allPropertiesValuesToUndefined(defaultKcProps);
allPropertiesValuesToUndefined(defaultKcProps);
assert<typeof allClearKcProps extends KcProps ? true : false>(); assert<typeof allClearKcProps extends KcProps ? true : false>();

View File

@ -1,4 +1,3 @@
import { useState, memo } from "react"; import { useState, memo } from "react";
import { Template } from "./Template"; import { Template } from "./Template";
import type { KcProps } from "./KcProps"; import type { KcProps } from "./KcProps";
@ -7,22 +6,27 @@ import { useKcMessage } from "../i18n/useKcMessage";
import { useCssAndCx } from "tss-react"; import { useCssAndCx } from "tss-react";
import { useConstCallback } from "powerhooks/useConstCallback"; import { useConstCallback } from "powerhooks/useConstCallback";
export const Login = memo(({ kcContext, ...props }: { kcContext: KcContextBase.Login; } & KcProps) => { export const Login = memo(
({ kcContext, ...props }: { kcContext: KcContextBase.Login } & KcProps) => {
const { const {
social, realm, url, social,
usernameEditDisabled, login, realm,
auth, registrationDisabled url,
usernameEditDisabled,
login,
auth,
registrationDisabled,
} = kcContext; } = kcContext;
const { msg, msgStr } = useKcMessage(); const { msg, msgStr } = useKcMessage();
const { cx } = useCssAndCx(); const { cx } = useCssAndCx();
const [isLoginButtonDisabled, setIsLoginButtonDisabled] = useState(false); const [isLoginButtonDisabled, setIsLoginButtonDisabled] =
useState(false);
const onSubmit = useConstCallback(() => const onSubmit = useConstCallback(
(setIsLoginButtonDisabled(true), true) () => (setIsLoginButtonDisabled(true), true),
); );
return ( return (
@ -35,112 +39,197 @@ export const Login = memo(({ kcContext, ...props }: { kcContext: KcContextBase.L
formNode={ formNode={
<div <div
id="kc-form" id="kc-form"
className={cx(realm.password && social.providers !== undefined && props.kcContentWrapperClass)} className={cx(
realm.password &&
social.providers !== undefined &&
props.kcContentWrapperClass,
)}
> >
<div <div
id="kc-form-wrapper" id="kc-form-wrapper"
className={cx(realm.password && social.providers && [props.kcFormSocialAccountContentClass, props.kcFormSocialAccountClass])} className={cx(
>
{
realm.password && realm.password &&
( social.providers && [
<form id="kc-form-login" onSubmit={onSubmit} action={url.loginAction} method="post"> props.kcFormSocialAccountContentClass,
props.kcFormSocialAccountClass,
],
)}
>
{realm.password && (
<form
id="kc-form-login"
onSubmit={onSubmit}
action={url.loginAction}
method="post"
>
<div className={cx(props.kcFormGroupClass)}> <div className={cx(props.kcFormGroupClass)}>
<label htmlFor="username" className={cx(props.kcLabelClass)}> <label
{ htmlFor="username"
!realm.loginWithEmailAllowed ? className={cx(props.kcLabelClass)}
msg("username") >
: {!realm.loginWithEmailAllowed
( ? msg("username")
!realm.registrationEmailAsUsername ? : !realm.registrationEmailAsUsername
msg("usernameOrEmail") : ? msg("usernameOrEmail")
msg("email") : msg("email")}
)
}
</label> </label>
<input <input
tabIndex={1} tabIndex={1}
id="username" id="username"
className={cx(props.kcInputClass)} className={cx(props.kcInputClass)}
name="username" name="username"
defaultValue={login.username ?? ''} defaultValue={login.username ?? ""}
type="text" type="text"
{...(usernameEditDisabled ? { "disabled": true } : { "autoFocus": true, "autoComplete": "off" })} {...(usernameEditDisabled
? { "disabled": true }
: {
"autoFocus": true,
"autoComplete": "off",
})}
/> />
</div> </div>
<div className={cx(props.kcFormGroupClass)}> <div className={cx(props.kcFormGroupClass)}>
<label htmlFor="password" className={cx(props.kcLabelClass)}> <label
htmlFor="password"
className={cx(props.kcLabelClass)}
>
{msg("password")} {msg("password")}
</label> </label>
<input tabIndex={2} id="password" className={cx(props.kcInputClass)} name="password" type="password" autoComplete="off" /> <input
tabIndex={2}
id="password"
className={cx(props.kcInputClass)}
name="password"
type="password"
autoComplete="off"
/>
</div> </div>
<div className={cx(props.kcFormGroupClass, props.kcFormSettingClass)}> <div
className={cx(
props.kcFormGroupClass,
props.kcFormSettingClass,
)}
>
<div id="kc-form-options"> <div id="kc-form-options">
{ {realm.rememberMe &&
( !usernameEditDisabled && (
realm.rememberMe &&
!usernameEditDisabled
) &&
<div className="checkbox"> <div className="checkbox">
<label> <label>
<input tabIndex={3} id="rememberMe" name="rememberMe" type="checkbox" {...(login.rememberMe ? { "checked": true } : {})} /> <input
tabIndex={3}
id="rememberMe"
name="rememberMe"
type="checkbox"
{...(login.rememberMe
? {
"checked":
true,
}
: {})}
/>
{msg("rememberMe")} {msg("rememberMe")}
</label> </label>
</div> </div>
} )}
</div> </div>
<div className={cx(props.kcFormOptionsWrapperClass)}> <div
{ className={cx(
realm.resetPasswordAllowed && props.kcFormOptionsWrapperClass,
)}
>
{realm.resetPasswordAllowed && (
<span> <span>
<a tabIndex={5} href={url.loginResetCredentialsUrl}>{msg("doForgotPassword")}</a> <a
</span> tabIndex={5}
href={
url.loginResetCredentialsUrl
} }
>
{msg(
"doForgotPassword",
)}
</a>
</span>
)}
</div> </div>
</div> </div>
<div id="kc-form-buttons" className={cx(props.kcFormGroupClass)}> <div
id="kc-form-buttons"
className={cx(props.kcFormGroupClass)}
>
<input <input
type="hidden" type="hidden"
id="id-hidden-input" id="id-hidden-input"
name="credentialId" name="credentialId"
{...(auth?.selectedCredential !== undefined ? { "value": auth.selectedCredential } : {})} {...(auth?.selectedCredential !==
undefined
? {
"value":
auth.selectedCredential,
}
: {})}
/> />
<input <input
tabIndex={4} tabIndex={4}
className={cx(props.kcButtonClass, props.kcButtonPrimaryClass, props.kcButtonBlockClass, props.kcButtonLargeClass)} name="login" id="kc-login" type="submit" className={cx(
props.kcButtonClass,
props.kcButtonPrimaryClass,
props.kcButtonBlockClass,
props.kcButtonLargeClass,
)}
name="login"
id="kc-login"
type="submit"
value={msgStr("doLogIn")} value={msgStr("doLogIn")}
disabled={isLoginButtonDisabled} disabled={isLoginButtonDisabled}
/> />
</div> </div>
</form> </form>
) )}
}
</div> </div>
{ {realm.password && social.providers !== undefined && (
(realm.password && social.providers !== undefined) && <div
<div id="kc-social-providers" className={cx(props.kcFormSocialAccountContentClass, props.kcFormSocialAccountClass)}> id="kc-social-providers"
<ul className={cx(props.kcFormSocialAccountListClass, social.providers.length > 4 && props.kcFormSocialAccountDoubleListClass)}> className={cx(
{ props.kcFormSocialAccountContentClass,
social.providers.map(p => props.kcFormSocialAccountClass,
<li key={p.providerId} className={cx(props.kcFormSocialAccountListLinkClass)}> )}
<a href={p.loginUrl} id={`zocial-${p.alias}`} className={cx("zocial", p.providerId)}> >
<ul
className={cx(
props.kcFormSocialAccountListClass,
social.providers.length > 4 &&
props.kcFormSocialAccountDoubleListClass,
)}
>
{social.providers.map(p => (
<li
key={p.providerId}
className={cx(
props.kcFormSocialAccountListLinkClass,
)}
>
<a
href={p.loginUrl}
id={`zocial-${p.alias}`}
className={cx(
"zocial",
p.providerId,
)}
>
<span>{p.displayName}</span> <span>{p.displayName}</span>
</a> </a>
</li> </li>
) ))}
}
</ul> </ul>
</div> </div>
} )}
</div> </div>
} }
infoNode={ infoNode={
(
realm.password && realm.password &&
realm.registrationAllowed && realm.registrationAllowed &&
!registrationDisabled !registrationDisabled && (
) &&
<div id="kc-registration"> <div id="kc-registration">
<span> <span>
{msg("noAccount")} {msg("noAccount")}
@ -149,9 +238,9 @@ export const Login = memo(({ kcContext, ...props }: { kcContext: KcContextBase.L
</a> </a>
</span> </span>
</div> </div>
)
} }
/> />
); );
}); },
);

View File

@ -1,4 +1,3 @@
import { memo } from "react"; import { memo } from "react";
import { Template } from "./Template"; import { Template } from "./Template";
import type { KcProps } from "./KcProps"; import type { KcProps } from "./KcProps";
@ -6,8 +5,11 @@ import type { KcContextBase } from "../getKcContext/KcContextBase";
import { useKcMessage } from "../i18n/useKcMessage"; import { useKcMessage } from "../i18n/useKcMessage";
import { useCssAndCx } from "tss-react"; import { useCssAndCx } from "tss-react";
export const LoginIdpLinkConfirm = memo(({ kcContext, ...props }: { kcContext: KcContextBase.LoginIdpLinkConfirm; } & KcProps) => { export const LoginIdpLinkConfirm = memo(
({
kcContext,
...props
}: { kcContext: KcContextBase.LoginIdpLinkConfirm } & KcProps) => {
const { url, idpAlias } = kcContext; const { url, idpAlias } = kcContext;
const { msg } = useKcMessage(); const { msg } = useKcMessage();
@ -20,7 +22,11 @@ export const LoginIdpLinkConfirm = memo(({ kcContext, ...props }: { kcContext: K
doFetchDefaultThemeResources={true} doFetchDefaultThemeResources={true}
headerNode={msg("confirmLinkIdpTitle")} headerNode={msg("confirmLinkIdpTitle")}
formNode={ formNode={
<form id="kc-register-form" action={url.loginAction} method="post"> <form
id="kc-register-form"
action={url.loginAction}
method="post"
>
<div className={cx(props.kcFormGroupClass)}> <div className={cx(props.kcFormGroupClass)}>
<button <button
type="submit" type="submit"
@ -28,7 +34,7 @@ export const LoginIdpLinkConfirm = memo(({ kcContext, ...props }: { kcContext: K
props.kcButtonClass, props.kcButtonClass,
props.kcButtonDefaultClass, props.kcButtonDefaultClass,
props.kcButtonBlockClass, props.kcButtonBlockClass,
props.kcButtonLargeClass props.kcButtonLargeClass,
)} )}
name="submitAction" name="submitAction"
id="updateProfile" id="updateProfile"
@ -42,7 +48,7 @@ export const LoginIdpLinkConfirm = memo(({ kcContext, ...props }: { kcContext: K
props.kcButtonClass, props.kcButtonClass,
props.kcButtonDefaultClass, props.kcButtonDefaultClass,
props.kcButtonBlockClass, props.kcButtonBlockClass,
props.kcButtonLargeClass props.kcButtonLargeClass,
)} )}
name="submitAction" name="submitAction"
id="linkAccount" id="linkAccount"
@ -55,7 +61,5 @@ export const LoginIdpLinkConfirm = memo(({ kcContext, ...props }: { kcContext: K
} }
/> />
); );
},
}); );

View File

@ -1,5 +1,3 @@
import { useEffect, memo } from "react"; import { useEffect, memo } from "react";
import { Template } from "./Template"; import { Template } from "./Template";
import type { KcProps } from "./KcProps"; import type { KcProps } from "./KcProps";
@ -9,38 +7,36 @@ import { appendHead } from "../tools/appendHead";
import { join as pathJoin } from "path"; import { join as pathJoin } from "path";
import { useCssAndCx } from "tss-react"; import { useCssAndCx } from "tss-react";
export const LoginOtp = memo(({ kcContext, ...props }: { kcContext: KcContextBase.LoginOtp; } & KcProps) => { export const LoginOtp = memo(
({
kcContext,
...props
}: { kcContext: KcContextBase.LoginOtp } & KcProps) => {
const { otpLogin, url } = kcContext; const { otpLogin, url } = kcContext;
const { cx } = useCssAndCx(); const { cx } = useCssAndCx();
const { msg, msgStr } = useKcMessage(); const { msg, msgStr } = useKcMessage();
useEffect( useEffect(() => {
() => {
let isCleanedUp = false; let isCleanedUp = false;
appendHead({ appendHead({
"type": "javascript", "type": "javascript",
"src": pathJoin( "src": pathJoin(
kcContext.url.resourcesCommonPath, kcContext.url.resourcesCommonPath,
"node_modules/jquery/dist/jquery.min.js" "node_modules/jquery/dist/jquery.min.js",
) ),
}).then(() => { }).then(() => {
if (isCleanedUp) return; if (isCleanedUp) return;
evaluateInlineScript(); evaluateInlineScript();
}); });
return () => { isCleanedUp = true }; return () => {
isCleanedUp = true;
}, };
[] }, []);
);
return ( return (
<Template <Template
@ -48,36 +44,59 @@ export const LoginOtp = memo(({ kcContext, ...props }: { kcContext: KcContextBas
doFetchDefaultThemeResources={true} doFetchDefaultThemeResources={true}
headerNode={msg("doLogIn")} headerNode={msg("doLogIn")}
formNode={ formNode={
<form <form
id="kc-otp-login-form" id="kc-otp-login-form"
className={cx(props.kcFormClass)} className={cx(props.kcFormClass)}
action={url.loginAction} action={url.loginAction}
method="post" method="post"
> >
{ {otpLogin.userOtpCredentials.length > 1 && (
otpLogin.userOtpCredentials.length > 1 &&
<div className={cx(props.kcFormGroupClass)}> <div className={cx(props.kcFormGroupClass)}>
<div className={cx(props.kcInputWrapperClass)}> <div className={cx(props.kcInputWrapperClass)}>
{otpLogin.userOtpCredentials.map(
otpCredential => (
<div
key={otpCredential.id}
className={cx(
props.kcSelectOTPListClass,
)}
>
<input
type="hidden"
value="${otpCredential.id}"
/>
<div
className={cx(
props.kcSelectOTPListItemClass,
)}
>
<span
className={cx(
props.kcAuthenticatorOtpCircleClass,
)}
/>
<h2
className={cx(
props.kcSelectOTPItemHeadingClass,
)}
>
{ {
otpLogin.userOtpCredentials.map(otpCredential => otpCredential.userLabel
<div key={otpCredential.id} className={cx(props.kcSelectOTPListClass)}> }
<input type="hidden" value="${otpCredential.id}" />
<div className={cx(props.kcSelectOTPListItemClass)}>
<span className={cx(props.kcAuthenticatorOtpCircleClass)} />
<h2 className={cx(props.kcSelectOTPItemHeadingClass)}>
{otpCredential.userLabel}
</h2> </h2>
</div> </div>
</div> </div>
) ),
} )}
</div> </div>
</div> </div>
} )}
<div className={cx(props.kcFormGroupClass)}> <div className={cx(props.kcFormGroupClass)}>
<div className={cx(props.kcLabelWrapperClass)}> <div className={cx(props.kcLabelWrapperClass)}>
<label htmlFor="otp" className={cx(props.kcLabelClass)}> <label
htmlFor="otp"
className={cx(props.kcLabelClass)}
>
{msg("loginOtpOneTime")} {msg("loginOtpOneTime")}
</label> </label>
</div> </div>
@ -95,17 +114,27 @@ export const LoginOtp = memo(({ kcContext, ...props }: { kcContext: KcContextBas
</div> </div>
<div className={cx(props.kcFormGroupClass)}> <div className={cx(props.kcFormGroupClass)}>
<div id="kc-form-options" className={cx(props.kcFormOptionsClass)}> <div
<div className={cx(props.kcFormOptionsWrapperClass)} /> id="kc-form-options"
className={cx(props.kcFormOptionsClass)}
>
<div
className={cx(
props.kcFormOptionsWrapperClass,
)}
/>
</div> </div>
<div id="kc-form-buttons" className={cx(props.kcFormButtonsClass)}> <div
id="kc-form-buttons"
className={cx(props.kcFormButtonsClass)}
>
<input <input
className={cx( className={cx(
props.kcButtonClass, props.kcButtonClass,
props.kcButtonPrimaryClass, props.kcButtonPrimaryClass,
props.kcButtonBlockClass, props.kcButtonBlockClass,
props.kcButtonLargeClass props.kcButtonLargeClass,
)} )}
name="login" name="login"
id="kc-login" id="kc-login"
@ -115,34 +144,32 @@ export const LoginOtp = memo(({ kcContext, ...props }: { kcContext: KcContextBas
</div> </div>
</div> </div>
</form> </form>
} }
/> />
); );
}); },
);
declare const $: any; declare const $: any;
function evaluateInlineScript() { function evaluateInlineScript() {
$(document).ready(function () { $(document).ready(function () {
// Card Single Select // Card Single Select
$('.card-pf-view-single-select').click(function (this: any) { $(".card-pf-view-single-select").click(function (this: any) {
if ($(this).hasClass('active')) { $(this).removeClass('active'); $(this).children().removeAttr('name'); } if ($(this).hasClass("active")) {
else { $(this).removeClass("active");
$('.card-pf-view-single-select').removeClass('active'); $(this).children().removeAttr("name");
$('.card-pf-view-single-select').children().removeAttr('name'); } else {
$(this).addClass('active'); $(this).children().attr('name', 'selectedCredentialId'); $(".card-pf-view-single-select").removeClass("active");
$(".card-pf-view-single-select").children().removeAttr("name");
$(this).addClass("active");
$(this).children().attr("name", "selectedCredentialId");
} }
}); });
var defaultCred = $('.card-pf-view-single-select')[0]; var defaultCred = $(".card-pf-view-single-select")[0];
if (defaultCred) { if (defaultCred) {
defaultCred.click(); defaultCred.click();
} }
}); });
} }

View File

@ -1,4 +1,3 @@
import { memo } from "react"; import { memo } from "react";
import { Template } from "./Template"; import { Template } from "./Template";
import type { KcProps } from "./KcProps"; import type { KcProps } from "./KcProps";
@ -6,13 +5,12 @@ import type { KcContextBase } from "../getKcContext/KcContextBase";
import { useKcMessage } from "../i18n/useKcMessage"; import { useKcMessage } from "../i18n/useKcMessage";
import { useCssAndCx } from "tss-react"; import { useCssAndCx } from "tss-react";
export const LoginResetPassword = memo(({ kcContext, ...props }: { kcContext: KcContextBase.LoginResetPassword; } & KcProps) => { export const LoginResetPassword = memo(
({
const { kcContext,
url, ...props
realm, }: { kcContext: KcContextBase.LoginResetPassword } & KcProps) => {
auth const { url, realm, auth } = kcContext;
} = kcContext;
const { msg, msgStr } = useKcMessage(); const { msg, msgStr } = useKcMessage();
@ -25,18 +23,23 @@ export const LoginResetPassword = memo(({ kcContext, ...props }: { kcContext: Kc
displayMessage={false} displayMessage={false}
headerNode={msg("emailForgotTitle")} headerNode={msg("emailForgotTitle")}
formNode={ formNode={
<form id="kc-reset-password-form" className={cx(props.kcFormClass)} action={url.loginAction} method="post"> <form
id="kc-reset-password-form"
className={cx(props.kcFormClass)}
action={url.loginAction}
method="post"
>
<div className={cx(props.kcFormGroupClass)}> <div className={cx(props.kcFormGroupClass)}>
<div className={cx(props.kcLabelWrapperClass)}> <div className={cx(props.kcLabelWrapperClass)}>
<label htmlFor="username" className={cx(props.kcLabelClass)}> <label
{ htmlFor="username"
!realm.loginWithEmailAllowed ? className={cx(props.kcLabelClass)}
msg("username") >
: {!realm.loginWithEmailAllowed
!realm.registrationEmailAsUsername ? ? msg("username")
msg("usernameOrEmail") : : !realm.registrationEmailAsUsername
msg("email") ? msg("usernameOrEmail")
} : msg("email")}
</label> </label>
</div> </div>
<div className={cx(props.kcInputWrapperClass)}> <div className={cx(props.kcInputWrapperClass)}>
@ -47,26 +50,46 @@ export const LoginResetPassword = memo(({ kcContext, ...props }: { kcContext: Kc
className={cx(props.kcInputClass)} className={cx(props.kcInputClass)}
autoFocus autoFocus
defaultValue={ defaultValue={
auth !== undefined && auth.showUsername ? auth !== undefined && auth.showUsername
auth.attemptedUsername : undefined ? auth.attemptedUsername
: undefined
} }
/> />
</div> </div>
</div> </div>
<div className={cx(props.kcFormGroupClass, props.kcFormSettingClass)}> <div
<div id="kc-form-options" className={cx(props.kcFormOptionsClass)}> className={cx(
<div className={cx(props.kcFormOptionsWrapperClass)}> props.kcFormGroupClass,
props.kcFormSettingClass,
)}
>
<div
id="kc-form-options"
className={cx(props.kcFormOptionsClass)}
>
<div
className={cx(
props.kcFormOptionsWrapperClass,
)}
>
<span> <span>
<a href={url.loginUrl}>{msg("backToLogin")}</a> <a href={url.loginUrl}>
{msg("backToLogin")}
</a>
</span> </span>
</div> </div>
</div> </div>
<div id="kc-form-buttons" className={cx(props.kcFormButtonsClass)}> <div
id="kc-form-buttons"
className={cx(props.kcFormButtonsClass)}
>
<input <input
className={cx( className={cx(
props.kcButtonClass, props.kcButtonPrimaryClass, props.kcButtonClass,
props.kcButtonBlockClass, props.kcButtonLargeClass props.kcButtonPrimaryClass,
props.kcButtonBlockClass,
props.kcButtonLargeClass,
)} )}
type="submit" type="submit"
value={msgStr("doSubmit")} value={msgStr("doSubmit")}
@ -78,6 +101,5 @@ export const LoginResetPassword = memo(({ kcContext, ...props }: { kcContext: Kc
infoNode={msg("emailInstruction")} infoNode={msg("emailInstruction")}
/> />
); );
}); },
);

View File

@ -5,8 +5,11 @@ import type { KcContextBase } from "../getKcContext/KcContextBase";
import { useKcMessage } from "../i18n/useKcMessage"; import { useKcMessage } from "../i18n/useKcMessage";
import { useCssAndCx } from "tss-react"; import { useCssAndCx } from "tss-react";
export const LoginUpdateProfile = memo(({ kcContext, ...props }: { kcContext: KcContextBase.LoginUpdateProfile; } & KcProps) => { export const LoginUpdateProfile = memo(
({
kcContext,
...props
}: { kcContext: KcContextBase.LoginUpdateProfile } & KcProps) => {
const { cx } = useCssAndCx(); const { cx } = useCssAndCx();
const { msg, msgStr } = useKcMessage(); const { msg, msgStr } = useKcMessage();
@ -19,11 +22,27 @@ export const LoginUpdateProfile = memo(({ kcContext, ...props }: { kcContext: Kc
doFetchDefaultThemeResources={true} doFetchDefaultThemeResources={true}
headerNode={msg("loginProfileTitle")} headerNode={msg("loginProfileTitle")}
formNode={ formNode={
<form id="kc-update-profile-form" className={cx(props.kcFormClass)} action={url.loginAction} method="post"> <form
{user.editUsernameAllowed && id="kc-update-profile-form"
<div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists("username", props.kcFormGroupErrorClass))}> className={cx(props.kcFormClass)}
action={url.loginAction}
method="post"
>
{user.editUsernameAllowed && (
<div
className={cx(
props.kcFormGroupClass,
messagesPerField.printIfExists(
"username",
props.kcFormGroupErrorClass,
),
)}
>
<div className={cx(props.kcLabelWrapperClass)}> <div className={cx(props.kcLabelWrapperClass)}>
<label htmlFor="username" className={cx(props.kcLabelClass)}> <label
htmlFor="username"
className={cx(props.kcLabelClass)}
>
{msg("username")} {msg("username")}
</label> </label>
</div> </div>
@ -37,11 +56,22 @@ export const LoginUpdateProfile = memo(({ kcContext, ...props }: { kcContext: Kc
/> />
</div> </div>
</div> </div>
} )}
<div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists("email", props.kcFormGroupErrorClass))}> <div
className={cx(
props.kcFormGroupClass,
messagesPerField.printIfExists(
"email",
props.kcFormGroupErrorClass,
),
)}
>
<div className={cx(props.kcLabelWrapperClass)}> <div className={cx(props.kcLabelWrapperClass)}>
<label htmlFor="email" className={cx(props.kcLabelClass)}> <label
htmlFor="email"
className={cx(props.kcLabelClass)}
>
{msg("email")} {msg("email")}
</label> </label>
</div> </div>
@ -56,9 +86,20 @@ export const LoginUpdateProfile = memo(({ kcContext, ...props }: { kcContext: Kc
</div> </div>
</div> </div>
<div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists("firstName", props.kcFormGroupErrorClass))}> <div
className={cx(
props.kcFormGroupClass,
messagesPerField.printIfExists(
"firstName",
props.kcFormGroupErrorClass,
),
)}
>
<div className={cx(props.kcLabelWrapperClass)}> <div className={cx(props.kcLabelWrapperClass)}>
<label htmlFor="firstName" className={cx(props.kcLabelClass)}> <label
htmlFor="firstName"
className={cx(props.kcLabelClass)}
>
{msg("firstName")} {msg("firstName")}
</label> </label>
</div> </div>
@ -73,9 +114,20 @@ export const LoginUpdateProfile = memo(({ kcContext, ...props }: { kcContext: Kc
</div> </div>
</div> </div>
<div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists("lastName", props.kcFormGroupErrorClass))}> <div
className={cx(
props.kcFormGroupClass,
messagesPerField.printIfExists(
"lastName",
props.kcFormGroupErrorClass,
),
)}
>
<div className={cx(props.kcLabelWrapperClass)}> <div className={cx(props.kcLabelWrapperClass)}>
<label htmlFor="lastName" className={cx(props.kcLabelClass)}> <label
htmlFor="lastName"
className={cx(props.kcLabelClass)}
>
{msg("lastName")} {msg("lastName")}
</label> </label>
</div> </div>
@ -91,21 +143,38 @@ export const LoginUpdateProfile = memo(({ kcContext, ...props }: { kcContext: Kc
</div> </div>
<div className={cx(props.kcFormGroupClass)}> <div className={cx(props.kcFormGroupClass)}>
<div id="kc-form-options" className={cx(props.kcFormOptionsClass)}> <div
<div className={cx(props.kcFormOptionsWrapperClass)} /> id="kc-form-options"
className={cx(props.kcFormOptionsClass)}
>
<div
className={cx(
props.kcFormOptionsWrapperClass,
)}
/>
</div> </div>
<div id="kc-form-buttons" className={cx(props.kcFormButtonsClass)}> <div
{ id="kc-form-buttons"
isAppInitiatedAction ? className={cx(props.kcFormButtonsClass)}
>
{isAppInitiatedAction ? (
<> <>
<input <input
className={cx(props.kcButtonClass, props.kcButtonPrimaryClass, props.kcButtonLargeClass)} className={cx(
props.kcButtonClass,
props.kcButtonPrimaryClass,
props.kcButtonLargeClass,
)}
type="submit" type="submit"
defaultValue={msgStr("doSubmit")} defaultValue={msgStr("doSubmit")}
/> />
<button <button
className={cx(props.kcButtonClass, props.kcButtonDefaultClass, props.kcButtonLargeClass)} className={cx(
props.kcButtonClass,
props.kcButtonDefaultClass,
props.kcButtonLargeClass,
)}
type="submit" type="submit"
name="cancel-aia" name="cancel-aia"
value="true" value="true"
@ -113,21 +182,23 @@ export const LoginUpdateProfile = memo(({ kcContext, ...props }: { kcContext: Kc
{msg("doCancel")} {msg("doCancel")}
</button> </button>
</> </>
: ) : (
<input <input
className={cx(props.kcButtonClass, props.kcButtonPrimaryClass, props.kcButtonBlockClass, props.kcButtonLargeClass)} className={cx(
props.kcButtonClass,
props.kcButtonPrimaryClass,
props.kcButtonBlockClass,
props.kcButtonLargeClass,
)}
type="submit" type="submit"
defaultValue={msgStr("doSubmit")} defaultValue={msgStr("doSubmit")}
/> />
} )}
</div> </div>
</div> </div>
</form> </form>
} }
/> />
); );
},
}); );

View File

@ -1,17 +1,17 @@
import { memo } from "react"; import { memo } from "react";
import { Template } from "./Template"; import { Template } from "./Template";
import type { KcProps } from "./KcProps"; import type { KcProps } from "./KcProps";
import type { KcContextBase } from "../getKcContext/KcContextBase"; import type { KcContextBase } from "../getKcContext/KcContextBase";
import { useKcMessage } from "../i18n/useKcMessage"; import { useKcMessage } from "../i18n/useKcMessage";
export const LoginVerifyEmail = memo(({ kcContext, ...props }: { kcContext: KcContextBase.LoginVerifyEmail; } & KcProps) => { export const LoginVerifyEmail = memo(
({
kcContext,
...props
}: { kcContext: KcContextBase.LoginVerifyEmail } & KcProps) => {
const { msg } = useKcMessage(); const { msg } = useKcMessage();
const { const { url } = kcContext;
url
} = kcContext;
return ( return (
<Template <Template
@ -33,7 +33,5 @@ export const LoginVerifyEmail = memo(({ kcContext, ...props }: { kcContext: KcCo
} }
/> />
); );
},
}); );

View File

@ -5,8 +5,11 @@ import type { KcContextBase } from "../getKcContext/KcContextBase";
import { useKcMessage } from "../i18n/useKcMessage"; import { useKcMessage } from "../i18n/useKcMessage";
import { useCssAndCx } from "tss-react"; import { useCssAndCx } from "tss-react";
export const Register = memo(({ kcContext, ...props }: { kcContext: KcContextBase.Register; } & KcProps) => { export const Register = memo(
({
kcContext,
...props
}: { kcContext: KcContextBase.Register } & KcProps) => {
const { const {
url, url,
messagesPerField, messagesPerField,
@ -14,7 +17,7 @@ export const Register = memo(({ kcContext, ...props }: { kcContext: KcContextBas
realm, realm,
passwordRequired, passwordRequired,
recaptchaRequired, recaptchaRequired,
recaptchaSiteKey recaptchaSiteKey,
} = kcContext; } = kcContext;
const { msg, msgStr } = useKcMessage(); const { msg, msgStr } = useKcMessage();
@ -27,100 +30,254 @@ export const Register = memo(({ kcContext, ...props }: { kcContext: KcContextBas
doFetchDefaultThemeResources={true} doFetchDefaultThemeResources={true}
headerNode={msg("registerTitle")} headerNode={msg("registerTitle")}
formNode={ formNode={
<form id="kc-register-form" className={cx(props.kcFormClass)} action={url.registrationAction} method="post"> <form
id="kc-register-form"
<div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists("firstName", props.kcFormGroupErrorClass))}> className={cx(props.kcFormClass)}
action={url.registrationAction}
method="post"
>
<div
className={cx(
props.kcFormGroupClass,
messagesPerField.printIfExists(
"firstName",
props.kcFormGroupErrorClass,
),
)}
>
<div className={cx(props.kcLabelWrapperClass)}> <div className={cx(props.kcLabelWrapperClass)}>
<label htmlFor="firstName" className={cx(props.kcLabelClass)}>{msg("firstName")}</label> <label
htmlFor="firstName"
className={cx(props.kcLabelClass)}
>
{msg("firstName")}
</label>
</div> </div>
<div className={cx(props.kcInputWrapperClass)}> <div className={cx(props.kcInputWrapperClass)}>
<input type="text" id="firstName" className={cx(props.kcInputClass)} name="firstName" <input
defaultValue={register.formData.firstName ?? ""} type="text"
/> id="firstName"
</div> className={cx(props.kcInputClass)}
</div> name="firstName"
defaultValue={
<div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists("lastName", props.kcFormGroupErrorClass))}> register.formData.firstName ?? ""
<div className={cx(props.kcLabelWrapperClass)}>
<label htmlFor="lastName" className={cx(props.kcLabelClass)}>{msg("lastName")}</label>
</div>
<div className={cx(props.kcInputWrapperClass)}>
<input type="text" id="lastName" className={cx(props.kcInputClass)} name="lastName"
defaultValue={register.formData.lastName ?? ""}
/>
</div>
</div>
<div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists('email', props.kcFormGroupErrorClass))}>
<div className={cx(props.kcLabelWrapperClass)}>
<label htmlFor="email" className={cx(props.kcLabelClass)}>{msg("email")}</label>
</div>
<div className={cx(props.kcInputWrapperClass)}>
<input type="text" id="email" className={cx(props.kcInputClass)} name="email"
defaultValue={register.formData.email ?? ""} autoComplete="email"
/>
</div>
</div>
{
!realm.registrationEmailAsUsername &&
<div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists('username', props.kcFormGroupErrorClass))}>
<div className={cx(props.kcLabelWrapperClass)}>
<label htmlFor="username" className={cx(props.kcLabelClass)}>{msg("username")}</label>
</div>
<div className={cx(props.kcInputWrapperClass)}>
<input type="text" id="username" className={cx(props.kcInputClass)} name="username"
defaultValue={register.formData.username ?? ""} autoComplete="username" />
</div>
</div >
} }
{ />
passwordRequired && </div>
</div>
<div
className={cx(
props.kcFormGroupClass,
messagesPerField.printIfExists(
"lastName",
props.kcFormGroupErrorClass,
),
)}
>
<div className={cx(props.kcLabelWrapperClass)}>
<label
htmlFor="lastName"
className={cx(props.kcLabelClass)}
>
{msg("lastName")}
</label>
</div>
<div className={cx(props.kcInputWrapperClass)}>
<input
type="text"
id="lastName"
className={cx(props.kcInputClass)}
name="lastName"
defaultValue={
register.formData.lastName ?? ""
}
/>
</div>
</div>
<div
className={cx(
props.kcFormGroupClass,
messagesPerField.printIfExists(
"email",
props.kcFormGroupErrorClass,
),
)}
>
<div className={cx(props.kcLabelWrapperClass)}>
<label
htmlFor="email"
className={cx(props.kcLabelClass)}
>
{msg("email")}
</label>
</div>
<div className={cx(props.kcInputWrapperClass)}>
<input
type="text"
id="email"
className={cx(props.kcInputClass)}
name="email"
defaultValue={register.formData.email ?? ""}
autoComplete="email"
/>
</div>
</div>
{!realm.registrationEmailAsUsername && (
<div
className={cx(
props.kcFormGroupClass,
messagesPerField.printIfExists(
"username",
props.kcFormGroupErrorClass,
),
)}
>
<div className={cx(props.kcLabelWrapperClass)}>
<label
htmlFor="username"
className={cx(props.kcLabelClass)}
>
{msg("username")}
</label>
</div>
<div className={cx(props.kcInputWrapperClass)}>
<input
type="text"
id="username"
className={cx(props.kcInputClass)}
name="username"
defaultValue={
register.formData.username ?? ""
}
autoComplete="username"
/>
</div>
</div>
)}
{passwordRequired && (
<> <>
<div
<div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists("password", props.kcFormGroupErrorClass))}> className={cx(
<div className={cx(props.kcLabelWrapperClass)}> props.kcFormGroupClass,
<label htmlFor="password" className={cx(props.kcLabelClass)}>{msg("password")}</label> messagesPerField.printIfExists(
"password",
props.kcFormGroupErrorClass,
),
)}
>
<div
className={cx(
props.kcLabelWrapperClass,
)}
>
<label
htmlFor="password"
className={cx(props.kcLabelClass)}
>
{msg("password")}
</label>
</div> </div>
<div className={cx(props.kcInputWrapperClass)}> <div
<input type="password" id="password" className={cx(props.kcInputClass)} name="password" autoComplete="new-password" /> className={cx(
props.kcInputWrapperClass,
)}
>
<input
type="password"
id="password"
className={cx(props.kcInputClass)}
name="password"
autoComplete="new-password"
/>
</div> </div>
</div> </div>
<div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists("password-confirm", props.kcFormGroupErrorClass))}> <div
<div className={cx(props.kcLabelWrapperClass)}> className={cx(
<label htmlFor="password-confirm" className={cx(props.kcLabelClass)}>{msg("passwordConfirm")}</label> props.kcFormGroupClass,
messagesPerField.printIfExists(
"password-confirm",
props.kcFormGroupErrorClass,
),
)}
>
<div
className={cx(
props.kcLabelWrapperClass,
)}
>
<label
htmlFor="password-confirm"
className={cx(props.kcLabelClass)}
>
{msg("passwordConfirm")}
</label>
</div> </div>
<div className={cx(props.kcInputWrapperClass)}> <div
<input type="password" id="password-confirm" className={cx(props.kcInputClass)} name="password-confirm" /> className={cx(
props.kcInputWrapperClass,
)}
>
<input
type="password"
id="password-confirm"
className={cx(props.kcInputClass)}
name="password-confirm"
/>
</div> </div>
</div> </div>
</> </>
)}
} {recaptchaRequired && (
{
recaptchaRequired &&
<div className="form-group"> <div className="form-group">
<div className={cx(props.kcInputWrapperClass)}> <div className={cx(props.kcInputWrapperClass)}>
<div className="g-recaptcha" data-size="compact" data-sitekey={recaptchaSiteKey}></div> <div
className="g-recaptcha"
data-size="compact"
data-sitekey={recaptchaSiteKey}
></div>
</div> </div>
</div> </div>
} )}
<div className={cx(props.kcFormGroupClass)}> <div className={cx(props.kcFormGroupClass)}>
<div id="kc-form-options" className={cx(props.kcFormOptionsClass)}> <div
<div className={cx(props.kcFormOptionsWrapperClass)}> id="kc-form-options"
<span><a href={url.loginUrl}>{msg("backToLogin")}</a></span> className={cx(props.kcFormOptionsClass)}
>
<div
className={cx(
props.kcFormOptionsWrapperClass,
)}
>
<span>
<a href={url.loginUrl}>
{msg("backToLogin")}
</a>
</span>
</div> </div>
</div> </div>
<div id="kc-form-buttons" className={cx(props.kcFormButtonsClass)}> <div
<input className={cx(props.kcButtonClass, props.kcButtonPrimaryClass, props.kcButtonBlockClass, props.kcButtonLargeClass)} type="submit" id="kc-form-buttons"
value={msgStr("doRegister")} /> className={cx(props.kcFormButtonsClass)}
>
<input
className={cx(
props.kcButtonClass,
props.kcButtonPrimaryClass,
props.kcButtonBlockClass,
props.kcButtonLargeClass,
)}
type="submit"
value={msgStr("doRegister")}
/>
</div> </div>
</div> </div>
</form> </form>
} }
/> />
); );
}); },
);

View File

@ -1,4 +1,3 @@
import { memo, Fragment } from "react"; import { memo, Fragment } from "react";
import { Template } from "./Template"; import { Template } from "./Template";
import type { KcProps } from "./KcProps"; import type { KcProps } from "./KcProps";
@ -7,15 +6,18 @@ import { useKcMessage } from "../i18n/useKcMessage";
import { useCssAndCx } from "tss-react"; import { useCssAndCx } from "tss-react";
import type { ReactComponent } from "../tools/ReactComponent"; import type { ReactComponent } from "../tools/ReactComponent";
export const RegisterUserProfile = memo(({ kcContext, ...props }: { kcContext: KcContextBase.RegisterUserProfile; } & KcProps) => { export const RegisterUserProfile = memo(
({
kcContext,
...props
}: { kcContext: KcContextBase.RegisterUserProfile } & KcProps) => {
const { const {
url, url,
messagesPerField, messagesPerField,
realm, realm,
passwordRequired, passwordRequired,
recaptchaRequired, recaptchaRequired,
recaptchaSiteKey recaptchaSiteKey,
} = kcContext; } = kcContext;
const { msg, msgStr } = useKcMessage(); const { msg, msgStr } = useKcMessage();
@ -41,74 +43,170 @@ export const RegisterUserProfile = memo(({ kcContext, ...props }: { kcContext: K
{...props} {...props}
AfterField={({ attribute }) => AfterField={({ attribute }) =>
/*render password fields just under the username or email (if used as username)*/ /*render password fields just under the username or email (if used as username)*/
passwordRequired && (attribute.name == "username" || (attribute.name == "email" && realm.registrationEmailAsUsername)) && (passwordRequired &&
(attribute.name == "username" ||
(attribute.name == "email" &&
realm.registrationEmailAsUsername)) && (
<> <>
<div className={cx(props.kcFormGroupClass)}> <div
<div className={cx(props.kcLabelWrapperClass)}> className={cx(
<label htmlFor="password" className={cx(props.kcLabelClass)}> props.kcFormGroupClass,
)}
>
<div
className={cx(
props.kcLabelWrapperClass,
)}
>
<label
htmlFor="password"
className={cx(
props.kcLabelClass,
)}
>
{msg("password")} {msg("password")}
</label> * </label>{" "}
*
</div> </div>
<div className={cx(props.kcInputWrapperClass)}> <div
<input type="password" id="password" className={cx(props.kcInputClass)} name="password" className={cx(
props.kcInputWrapperClass,
)}
>
<input
type="password"
id="password"
className={cx(
props.kcInputClass,
)}
name="password"
autoComplete="new-password" autoComplete="new-password"
aria-invalid={ aria-invalid={
messagesPerField.existsError("password") || messagesPerField.existsError(
messagesPerField.existsError("password-confirm") "password",
) ||
messagesPerField.existsError(
"password-confirm",
)
} }
/> />
{ {messagesPerField.existsError(
messagesPerField.existsError("password") && "password",
<span id="input-error-password" className={cx(props.kcInputErrorMessageClass)} aria-live="polite"> ) && (
{messagesPerField.get('password')} <span
id="input-error-password"
className={cx(
props.kcInputErrorMessageClass,
)}
aria-live="polite"
>
{messagesPerField.get(
"password",
)}
</span> </span>
} )}
</div> </div>
</div> </div>
<div className={cx(props.kcFormGroupClass)}> <div
<div className={cx(props.kcLabelWrapperClass)}> className={cx(
<label htmlFor="password-confirm" props.kcFormGroupClass,
className={cx(props.kcLabelClass)}> )}
>
<div
className={cx(
props.kcLabelWrapperClass,
)}
>
<label
htmlFor="password-confirm"
className={cx(
props.kcLabelClass,
)}
>
{msg("passwordConfirm")} {msg("passwordConfirm")}
</label> * </label>{" "}
*
</div> </div>
<div className={cx(props.kcInputWrapperClass)}> <div
<input type="password" id="password-confirm" className={cx(props.kcInputClass)} className={cx(
props.kcInputWrapperClass,
)}
>
<input
type="password"
id="password-confirm"
className={cx(
props.kcInputClass,
)}
name="password-confirm" name="password-confirm"
aria-invalid={messagesPerField.existsError("password-confirm")} aria-invalid={messagesPerField.existsError(
"password-confirm",
)}
/> />
{ {messagesPerField.existsError(
messagesPerField.existsError("password-confirm") && "password-confirm",
<span id="input-error-password-confirm" className={cx(props.kcInputErrorMessageClass)} aria-live="polite"> ) && (
{messagesPerField.get('password-confirm')} <span
id="input-error-password-confirm"
className={cx(
props.kcInputErrorMessageClass,
)}
aria-live="polite"
>
{messagesPerField.get(
"password-confirm",
)}
</span> </span>
} )}
</div> </div>
</div> </div>
</> || null </>
)) ||
null
} }
/> />
{ {recaptchaRequired && (
recaptchaRequired &&
<div className="form-group"> <div className="form-group">
<div className={cx(props.kcInputWrapperClass)}> <div className={cx(props.kcInputWrapperClass)}>
<div className="g-recaptcha" data-size="compact" data-sitekey={recaptchaSiteKey} /> <div
className="g-recaptcha"
data-size="compact"
data-sitekey={recaptchaSiteKey}
/>
</div> </div>
</div> </div>
} )}
<div className={cx(props.kcFormGroupClass)}> <div className={cx(props.kcFormGroupClass)}>
<div id="kc-form-options" className={cx(props.kcFormOptionsClass)}> <div
<div className={cx(props.kcFormOptionsWrapperClass)}> id="kc-form-options"
<span><a href={url.loginUrl}>{msg("backToLogin")}</a></span> className={cx(props.kcFormOptionsClass)}
>
<div
className={cx(
props.kcFormOptionsWrapperClass,
)}
>
<span>
<a href={url.loginUrl}>
{msg("backToLogin")}
</a>
</span>
</div> </div>
</div> </div>
<div id="kc-form-buttons" className={cx(props.kcFormButtonsClass)}> <div
id="kc-form-buttons"
className={cx(props.kcFormButtonsClass)}
>
<input <input
className={cx(props.kcButtonClass, props.kcButtonPrimaryClass, props.kcButtonBlockClass, props.kcButtonLargeClass)} className={cx(
type="submit" value={msgStr("doRegister")} props.kcButtonClass,
props.kcButtonPrimaryClass,
props.kcButtonBlockClass,
props.kcButtonLargeClass,
)}
type="submit"
value={msgStr("doRegister")}
/> />
</div> </div>
</div> </div>
@ -116,24 +214,24 @@ export const RegisterUserProfile = memo(({ kcContext, ...props }: { kcContext: K
} }
/> />
); );
}); },
);
const UserProfileFormFields = memo( const UserProfileFormFields = memo(
( ({
{
kcContext, kcContext,
BeforeField = () => null, BeforeField = () => null,
AfterField = () => null, AfterField = () => null,
...props ...props
}: }: { kcContext: KcContextBase.RegisterUserProfile } & KcProps &
{ kcContext: KcContextBase.RegisterUserProfile; } & Partial<
KcProps & Record<
Partial<Record<
"BeforeField" | "AfterField", "BeforeField" | "AfterField",
ReactComponent<{ attribute: KcContextBase.RegisterUserProfile["profile"]["attributes"][number]; }> ReactComponent<{
>> attribute: KcContextBase.RegisterUserProfile["profile"]["attributes"][number];
) => { }>
>
>) => {
const { messagesPerField } = kcContext; const { messagesPerField } = kcContext;
const { cx } = useCssAndCx(); const { cx } = useCssAndCx();
@ -145,11 +243,10 @@ const UserProfileFormFields = memo(
return ( return (
<> <>
{kcContext.profile.attributes.map((attribute, i) => { {kcContext.profile.attributes.map((attribute, i) => {
const { const {
group = "", group = "",
groupDisplayHeader = "", groupDisplayHeader = "",
groupDisplayDescription = "" groupDisplayDescription = "",
} = attribute; } = attribute;
if (group === currentGroup) return null; if (group === currentGroup) return null;
@ -158,27 +255,46 @@ const UserProfileFormFields = memo(
return ( return (
<Fragment key={i}> <Fragment key={i}>
{group !== "" && {group !== "" && (
<div className={cx(props.kcFormGroupClass)}> <div className={cx(props.kcFormGroupClass)}>
<div className={cx(props.kcContentWrapperClass)}> <div
className={cx(
props.kcContentWrapperClass,
)}
>
<label <label
id={`header-${group}`} id={`header-${group}`}
className={cx(props.kcFormGroupHeader)} className={cx(
props.kcFormGroupHeader,
)}
> >
{groupDisplayHeader !== "" && advancedMsg(groupDisplayHeader) || currentGroup} {(groupDisplayHeader !== "" &&
advancedMsg(
groupDisplayHeader,
)) ||
currentGroup}
</label> </label>
</div> </div>
{groupDisplayDescription !== "" && {groupDisplayDescription !== "" && (
<div className={cx(props.kcLabelWrapperClass)}> <div
className={cx(
props.kcLabelWrapperClass,
)}
>
<label <label
id={`description-${group}`} id={`description-${group}`}
className={`${cx(props.kcLabelClass)}`} className={`${cx(
props.kcLabelClass,
)}`}
> >
{advancedMsg(groupDisplayDescription) ?? ""} {advancedMsg(
groupDisplayDescription,
) ?? ""}
</label> </label>
</div> </div>
} )}
</div>} </div>
)}
<BeforeField attribute={attribute} /> <BeforeField attribute={attribute} />
<div className={cx(props.kcFormGroupClass)}> <div className={cx(props.kcFormGroupClass)}>
<div className={cx(props.kcLabelWrapperClass)}> <div className={cx(props.kcLabelWrapperClass)}>
@ -186,7 +302,9 @@ const UserProfileFormFields = memo(
htmlFor={attribute.name} htmlFor={attribute.name}
className={cx(props.kcLabelClass)} className={cx(props.kcLabelClass)}
> >
{advancedMsg(attribute.displayName ?? "")} {advancedMsg(
attribute.displayName ?? "",
)}
</label> </label>
{attribute.required && <>*</>} {attribute.required && <>*</>}
</div> </div>
@ -197,22 +315,33 @@ const UserProfileFormFields = memo(
name={attribute.name} name={attribute.name}
value={attribute.value ?? ""} value={attribute.value ?? ""}
className={cx(props.kcInputClass)} className={cx(props.kcInputClass)}
aria-invalid={messagesPerField.existsError(attribute.name)} aria-invalid={messagesPerField.existsError(
attribute.name,
)}
disabled={attribute.readOnly} disabled={attribute.readOnly}
{...(attribute.autocomplete === undefined ? {} : { {...(attribute.autocomplete ===
"autoComplete": attribute.autocomplete undefined
? {}
: {
"autoComplete":
attribute.autocomplete,
})} })}
/> />
{ {kcContext.messagesPerField.existsError(
kcContext.messagesPerField.existsError(attribute.name) && attribute.name,
) && (
<span <span
id={`input-error-${attribute.name}`} id={`input-error-${attribute.name}`}
className={cx(props.kcInputErrorMessageClass)} className={cx(
props.kcInputErrorMessageClass,
)}
aria-live="polite" aria-live="polite"
> >
{messagesPerField.get(attribute.name)} {messagesPerField.get(
attribute.name,
)}
</span> </span>
} )}
</div> </div>
</div> </div>
<AfterField attribute={attribute} /> <AfterField attribute={attribute} />
@ -221,6 +350,5 @@ const UserProfileFormFields = memo(
})} })}
</> </>
); );
},
}
); );

View File

@ -1,4 +1,3 @@
import { useReducer, useEffect, memo } from "react"; import { useReducer, useEffect, memo } from "react";
import type { ReactNode } from "react"; import type { ReactNode } from "react";
import { useKcMessage } from "../i18n/useKcMessage"; import { useKcMessage } from "../i18n/useKcMessage";
@ -29,10 +28,9 @@ export type TemplateProps = {
* to avoid pulling the default theme assets. * to avoid pulling the default theme assets.
*/ */
doFetchDefaultThemeResources: boolean; doFetchDefaultThemeResources: boolean;
} & { kcContext: KcContextBase; } & KcTemplateProps; } & { kcContext: KcContextBase } & KcTemplateProps;
export const Template = memo((props: TemplateProps) => { export const Template = memo((props: TemplateProps) => {
const { const {
displayInfo = false, displayInfo = false,
displayMessage = true, displayMessage = true,
@ -44,34 +42,34 @@ export const Template = memo((props: TemplateProps) => {
formNode, formNode,
infoNode = null, infoNode = null,
kcContext, kcContext,
doFetchDefaultThemeResources doFetchDefaultThemeResources,
} = props; } = props;
const { cx } = useCssAndCx(); const { cx } = useCssAndCx();
useEffect(() => { console.log("Rendering this page with react using keycloakify") }, []); useEffect(() => {
console.log("Rendering this page with react using keycloakify");
}, []);
const { msg } = useKcMessage(); const { msg } = useKcMessage();
const { kcLanguageTag, setKcLanguageTag } = useKcLanguageTag(); const { kcLanguageTag, setKcLanguageTag } = useKcLanguageTag();
const onChangeLanguageClickFactory = useCallbackFactory( const onChangeLanguageClickFactory = useCallbackFactory(
([languageTag]: [KcLanguageTag]) => ([languageTag]: [KcLanguageTag]) => setKcLanguageTag(languageTag),
setKcLanguageTag(languageTag)
); );
const onTryAnotherWayClick = useConstCallback(() => const onTryAnotherWayClick = useConstCallback(
(document.forms["kc-select-try-another-way-form" as never].submit(), false) () => (
document.forms["kc-select-try-another-way-form" as never].submit(),
false
),
); );
const { const { realm, locale, auth, url, message, isAppInitiatedAction } =
realm, locale, auth, kcContext;
url, message, isAppInitiatedAction
} = kcContext;
useEffect(() => { useEffect(() => {
if (!realm.internationalizationEnabled) { if (!realm.internationalizationEnabled) {
return; return;
} }
@ -82,15 +80,14 @@ export const Template = memo((props: TemplateProps) => {
return; return;
} }
window.location.href = window.location.href = locale.supported.find(
locale.supported.find(({ languageTag }) => languageTag === kcLanguageTag)!.url; ({ languageTag }) => languageTag === kcLanguageTag,
)!.url;
}, [kcLanguageTag]); }, [kcLanguageTag]);
const [isExtraCssLoaded, setExtraCssLoaded] = useReducer(() => true, false); const [isExtraCssLoaded, setExtraCssLoaded] = useReducer(() => true, false);
useEffect(() => { useEffect(() => {
if (!doFetchDefaultThemeResources) { if (!doFetchDefaultThemeResources) {
setExtraCssLoaded(); setExtraCssLoaded();
return; return;
@ -104,50 +101,49 @@ export const Template = memo((props: TemplateProps) => {
Promise.all( Promise.all(
[ [
...toArr(props.stylesCommon).map(relativePath => pathJoin(url.resourcesCommonPath, relativePath)), ...toArr(props.stylesCommon).map(relativePath =>
...toArr(props.styles).map(relativePath => pathJoin(url.resourcesPath, relativePath)) pathJoin(url.resourcesCommonPath, relativePath),
].map(href => appendHead({ ),
...toArr(props.styles).map(relativePath =>
pathJoin(url.resourcesPath, relativePath),
),
].map(href =>
appendHead({
"type": "css", "type": "css",
href href,
}))).then(() => { }),
),
).then(() => {
if (isUnmounted) { if (isUnmounted) {
return; return;
} }
setExtraCssLoaded(); setExtraCssLoaded();
}); });
toArr(props.scripts).forEach( toArr(props.scripts).forEach(relativePath =>
relativePath => appendHead({ appendHead({
"type": "javascript", "type": "javascript",
"src": pathJoin(url.resourcesPath, relativePath) "src": pathJoin(url.resourcesPath, relativePath),
}) }),
); );
if (props.kcHtmlClass !== undefined) { if (props.kcHtmlClass !== undefined) {
const htmlClassList = const htmlClassList =
document.getElementsByTagName("html")[0] document.getElementsByTagName("html")[0].classList;
.classList;
const tokens = cx(props.kcHtmlClass).split(" ") const tokens = cx(props.kcHtmlClass).split(" ");
htmlClassList.add(...tokens); htmlClassList.add(...tokens);
cleanups.push(() => htmlClassList.remove(...tokens)); cleanups.push(() => htmlClassList.remove(...tokens));
} }
return () => { return () => {
isUnmounted = true; isUnmounted = true;
cleanups.forEach(f => f()); cleanups.forEach(f => f());
}; };
}, [props.kcHtmlClass]); }, [props.kcHtmlClass]);
if (!isExtraCssLoaded) { if (!isExtraCssLoaded) {
@ -156,57 +152,77 @@ export const Template = memo((props: TemplateProps) => {
return ( return (
<div className={cx(props.kcLoginClass)}> <div className={cx(props.kcLoginClass)}>
<div id="kc-header" className={cx(props.kcHeaderClass)}> <div id="kc-header" className={cx(props.kcHeaderClass)}>
<div id="kc-header-wrapper" className={cx(props.kcHeaderWrapperClass)}> <div
id="kc-header-wrapper"
className={cx(props.kcHeaderWrapperClass)}
>
{msg("loginTitleHtml", realm.displayNameHtml)} {msg("loginTitleHtml", realm.displayNameHtml)}
</div> </div>
</div> </div>
<div className={cx(props.kcFormCardClass, displayWide && props.kcFormCardAccountClass)}> <div
className={cx(
props.kcFormCardClass,
displayWide && props.kcFormCardAccountClass,
)}
>
<header className={cx(props.kcFormHeaderClass)}> <header className={cx(props.kcFormHeaderClass)}>
{ {realm.internationalizationEnabled &&
(
realm.internationalizationEnabled &&
(assert(locale !== undefined), true) && (assert(locale !== undefined), true) &&
locale.supported.length > 1 locale.supported.length > 1 && (
) &&
<div id="kc-locale"> <div id="kc-locale">
<div id="kc-locale-wrapper" className={cx(props.kcLocaleWrapperClass)}> <div
<div className="kc-dropdown" id="kc-locale-dropdown"> id="kc-locale-wrapper"
className={cx(props.kcLocaleWrapperClass)}
>
<div
className="kc-dropdown"
id="kc-locale-dropdown"
>
<a href="#" id="kc-current-locale-link"> <a href="#" id="kc-current-locale-link">
{getKcLanguageTagLabel(kcLanguageTag)} {getKcLanguageTagLabel(
kcLanguageTag,
)}
</a> </a>
<ul> <ul>
{ {locale.supported.map(
locale.supported.map( ({ languageTag }) => (
({ languageTag }) => <li
<li key={languageTag} className="kc-dropdown-item"> key={languageTag}
<a href="#" onClick={onChangeLanguageClickFactory(languageTag)}> className="kc-dropdown-item"
{getKcLanguageTagLabel(languageTag)} >
<a
href="#"
onClick={onChangeLanguageClickFactory(
languageTag,
)}
>
{getKcLanguageTagLabel(
languageTag,
)}
</a> </a>
</li> </li>
) ),
} )}
</ul> </ul>
</div> </div>
</div> </div>
</div> </div>
)}
} {!(
{
!(
auth !== undefined && auth !== undefined &&
auth.showUsername && auth.showUsername &&
!auth.showResetCredentials !auth.showResetCredentials
) ? ) ? (
( displayRequiredFields ? (
displayRequiredFields ?
(
<div className={cx(props.kcContentWrapperClass)}> <div className={cx(props.kcContentWrapperClass)}>
<div className={cx(props.kcLabelWrapperClass, "subtitle")}> <div
className={cx(
props.kcLabelWrapperClass,
"subtitle",
)}
>
<span className="subtitle"> <span className="subtitle">
<span className="required">*</span> <span className="required">*</span>
{msg("requiredFields")} {msg("requiredFields")}
@ -216,29 +232,42 @@ export const Template = memo((props: TemplateProps) => {
<h1 id="kc-page-title">{headerNode}</h1> <h1 id="kc-page-title">{headerNode}</h1>
</div> </div>
</div> </div>
)
:
(
<h1 id="kc-page-title">{headerNode}</h1>
)
) : ( ) : (
displayRequiredFields ? ( <h1 id="kc-page-title">{headerNode}</h1>
)
) : displayRequiredFields ? (
<div className={cx(props.kcContentWrapperClass)}> <div className={cx(props.kcContentWrapperClass)}>
<div className={cx(props.kcLabelWrapperClass, "subtitle")}> <div
<span className="subtitle"><span className="required">*</span> {msg("requiredFields")}</span> className={cx(
props.kcLabelWrapperClass,
"subtitle",
)}
>
<span className="subtitle">
<span className="required">*</span>{" "}
{msg("requiredFields")}
</span>
</div> </div>
<div className="col-md-10"> <div className="col-md-10">
{showUsernameNode} {showUsernameNode}
<div className={cx(props.kcFormGroupClass)}> <div className={cx(props.kcFormGroupClass)}>
<div id="kc-username"> <div id="kc-username">
<label id="kc-attempted-username">{auth?.attemptedUsername}</label> <label id="kc-attempted-username">
<a id="reset-login" href={url.loginRestartFlowUrl}> {auth?.attemptedUsername}
</label>
<a
id="reset-login"
href={url.loginRestartFlowUrl}
>
<div className="kc-login-tooltip"> <div className="kc-login-tooltip">
<i className={cx(props.kcResetFlowIcon)}></i> <i
<span className="kc-tooltip-text">{msg("restartLoginTooltip")}</span> className={cx(
props.kcResetFlowIcon,
)}
></i>
<span className="kc-tooltip-text">
{msg("restartLoginTooltip")}
</span>
</div> </div>
</a> </a>
</div> </div>
@ -250,69 +279,133 @@ export const Template = memo((props: TemplateProps) => {
{showUsernameNode} {showUsernameNode}
<div className={cx(props.kcFormGroupClass)}> <div className={cx(props.kcFormGroupClass)}>
<div id="kc-username"> <div id="kc-username">
<label id="kc-attempted-username">{auth?.attemptedUsername}</label> <label id="kc-attempted-username">
<a id="reset-login" href={url.loginRestartFlowUrl}> {auth?.attemptedUsername}
</label>
<a
id="reset-login"
href={url.loginRestartFlowUrl}
>
<div className="kc-login-tooltip"> <div className="kc-login-tooltip">
<i className={cx(props.kcResetFlowIcon)}></i> <i
<span className="kc-tooltip-text">{msg("restartLoginTooltip")}</span> className={cx(
props.kcResetFlowIcon,
)}
></i>
<span className="kc-tooltip-text">
{msg("restartLoginTooltip")}
</span>
</div> </div>
</a> </a>
</div> </div>
</div> </div>
</> </>
) )}
)
}
</header> </header>
<div id="kc-content"> <div id="kc-content">
<div id="kc-content-wrapper"> <div id="kc-content-wrapper">
{/* App-initiated actions should not see warning messages about the need to complete the action during login. */} {/* App-initiated actions should not see warning messages about the need to complete the action during login. */}
{ {displayMessage &&
(
displayMessage &&
message !== undefined && message !== undefined &&
( (message.type !== "warning" ||
message.type !== "warning" || !isAppInitiatedAction) && (
!isAppInitiatedAction <div
) className={cx(
) && "alert",
<div className={cx("alert", `alert-${message.type}`)}> `alert-${message.type}`,
{message.type === "success" && <span className={cx(props.kcFeedbackSuccessIcon)}></span>} )}
{message.type === "warning" && <span className={cx(props.kcFeedbackWarningIcon)}></span>} >
{message.type === "error" && <span className={cx(props.kcFeedbackErrorIcon)}></span>} {message.type === "success" && (
{message.type === "info" && <span className={cx(props.kcFeedbackInfoIcon)}></span>} <span
className={cx(
props.kcFeedbackSuccessIcon,
)}
></span>
)}
{message.type === "warning" && (
<span
className={cx(
props.kcFeedbackWarningIcon,
)}
></span>
)}
{message.type === "error" && (
<span
className={cx(
props.kcFeedbackErrorIcon,
)}
></span>
)}
{message.type === "info" && (
<span
className={cx(
props.kcFeedbackInfoIcon,
)}
></span>
)}
<span <span
className="kc-feedback-text" className="kc-feedback-text"
dangerouslySetInnerHTML={{ "__html": message.summary }} dangerouslySetInnerHTML={{
"__html": message.summary,
}}
/> />
</div> </div>
} )}
{formNode} {formNode}
{ {auth !== undefined &&
(
auth !== undefined &&
auth.showTryAnotherWayLink && auth.showTryAnotherWayLink &&
showAnotherWayIfPresent showAnotherWayIfPresent && (
) && <form
id="kc-select-try-another-way-form"
<form id="kc-select-try-another-way-form" action={url.loginAction} method="post" className={cx(displayWide && props.kcContentWrapperClass)} > action={url.loginAction}
<div className={cx(displayWide && [props.kcFormSocialAccountContentClass, props.kcFormSocialAccountClass])} > method="post"
<div className={cx(props.kcFormGroupClass)}> className={cx(
<input type="hidden" name="tryAnotherWay" value="on" /> displayWide &&
<a href="#" id="try-another-way" onClick={onTryAnotherWayClick}>{msg("doTryAnotherWay")}</a> props.kcContentWrapperClass,
)}
>
<div
className={cx(
displayWide && [
props.kcFormSocialAccountContentClass,
props.kcFormSocialAccountClass,
],
)}
>
<div
className={cx(
props.kcFormGroupClass,
)}
>
<input
type="hidden"
name="tryAnotherWay"
value="on"
/>
<a
href="#"
id="try-another-way"
onClick={onTryAnotherWayClick}
>
{msg("doTryAnotherWay")}
</a>
</div> </div>
</div> </div>
</form> </form>
} )}
{ {displayInfo && (
displayInfo && <div
id="kc-info"
<div id="kc-info" className={cx(props.kcSignUpClass)}> className={cx(props.kcSignUpClass)}
<div id="kc-info-wrapper" className={cx(props.kcInfoAreaWrapperClass)}> >
<div
id="kc-info-wrapper"
className={cx(props.kcInfoAreaWrapperClass)}
>
{infoNode} {infoNode}
</div> </div>
</div> </div>
} )}
</div> </div>
</div> </div>
</div> </div>

View File

@ -5,8 +5,8 @@ import type { KcContextBase } from "../getKcContext/KcContextBase";
import { useKcMessage } from "../i18n/useKcMessage"; import { useKcMessage } from "../i18n/useKcMessage";
import { useCssAndCx } from "tss-react"; import { useCssAndCx } from "tss-react";
export const Terms = memo(({ kcContext, ...props }: { kcContext: KcContextBase.Terms; } & KcProps) => { export const Terms = memo(
({ kcContext, ...props }: { kcContext: KcContextBase.Terms } & KcProps) => {
const { msg, msgStr } = useKcMessage(); const { msg, msgStr } = useKcMessage();
const { cx } = useCssAndCx(); const { cx } = useCssAndCx();
@ -21,17 +21,19 @@ export const Terms = memo(({ kcContext, ...props }: { kcContext: KcContextBase.T
headerNode={msg("termsTitle")} headerNode={msg("termsTitle")}
formNode={ formNode={
<> <>
<div id="kc-terms-text"> <div id="kc-terms-text">{msg("termsText")}</div>
{msg("termsText")} <form
</div> className="form-actions"
<form className="form-actions" action={url.loginAction} method="POST"> action={url.loginAction}
method="POST"
>
<input <input
className={cx( className={cx(
props.kcButtonClass, props.kcButtonClass,
props.kcButtonClass, props.kcButtonClass,
props.kcButtonClass, props.kcButtonClass,
props.kcButtonPrimaryClass, props.kcButtonPrimaryClass,
props.kcButtonLargeClass props.kcButtonLargeClass,
)} )}
name="accept" name="accept"
id="kc-accept" id="kc-accept"
@ -42,7 +44,7 @@ export const Terms = memo(({ kcContext, ...props }: { kcContext: KcContextBase.T
className={cx( className={cx(
props.kcButtonClass, props.kcButtonClass,
props.kcButtonDefaultClass, props.kcButtonDefaultClass,
props.kcButtonLargeClass props.kcButtonLargeClass,
)} )}
name="cancel" name="cancel"
id="kc-decline" id="kc-decline"
@ -55,6 +57,5 @@ export const Terms = memo(({ kcContext, ...props }: { kcContext: KcContextBase.T
} }
/> />
); );
}); },
);

View File

@ -1,4 +1,3 @@
import type { PageId } from "../../bin/build-keycloak-theme/generateFtl"; import type { PageId } from "../../bin/build-keycloak-theme/generateFtl";
import type { KcLanguageTag } from "../i18n/KcLanguageTag"; import type { KcLanguageTag } from "../i18n/KcLanguageTag";
import { assert } from "tsafe/assert"; import { assert } from "tsafe/assert";
@ -6,21 +5,29 @@ import type { Equals } from "tsafe";
import type { MessageKey } from "../i18n/useKcMessage"; import type { MessageKey } from "../i18n/useKcMessage";
import type { LanguageLabel } from "../i18n/KcLanguageTag"; import type { LanguageLabel } from "../i18n/KcLanguageTag";
type ExtractAfterStartingWith<Prefix extends string, StrEnum> = type ExtractAfterStartingWith<
StrEnum extends `${Prefix}${infer U}` ? U : never; Prefix extends string,
StrEnum,
> = StrEnum extends `${Prefix}${infer U}` ? U : never;
/** Take theses type definition with a grain of salt. /** Take theses type definition with a grain of salt.
* Some values might be undefined on some pages. * Some values might be undefined on some pages.
* (ex: url.loginAction is undefined on error.ftl) * (ex: url.loginAction is undefined on error.ftl)
*/ */
export type KcContextBase = export type KcContextBase =
KcContextBase.Login | KcContextBase.Register | KcContextBase.RegisterUserProfile | KcContextBase.Info | | KcContextBase.Login
KcContextBase.Error | KcContextBase.LoginResetPassword | KcContextBase.LoginVerifyEmail | | KcContextBase.Register
KcContextBase.Terms | KcContextBase.LoginOtp | KcContextBase.LoginUpdateProfile | | KcContextBase.RegisterUserProfile
KcContextBase.LoginIdpLinkConfirm; | KcContextBase.Info
| KcContextBase.Error
| KcContextBase.LoginResetPassword
| KcContextBase.LoginVerifyEmail
| KcContextBase.Terms
| KcContextBase.LoginOtp
| KcContextBase.LoginUpdateProfile
| KcContextBase.LoginIdpLinkConfirm;
export declare namespace KcContextBase { export declare namespace KcContextBase {
export type Common = { export type Common = {
url: { url: {
loginAction: string; loginAction: string;
@ -46,7 +53,7 @@ export declare namespace KcContextBase {
//label: LanguageLabel; //label: LanguageLabel;
}[]; }[];
current: LanguageLabel; current: LanguageLabel;
}, };
auth?: { auth?: {
showUsername: boolean; showUsername: boolean;
showResetCredentials: boolean; showResetCredentials: boolean;
@ -61,7 +68,7 @@ export declare namespace KcContextBase {
client: { client: {
clientId: string; clientId: string;
name?: string; name?: string;
} };
isAppInitiatedAction: boolean; isAppInitiatedAction: boolean;
messagesPerField: { messagesPerField: {
printIfExists: <T>(fieldName: string, x: T) => T | undefined; printIfExists: <T>(fieldName: string, x: T) => T | undefined;
@ -100,7 +107,7 @@ export declare namespace KcContextBase {
alias: string; alias: string;
providerId: string; providerId: string;
displayName: string; displayName: string;
}[] }[];
}; };
}; };
@ -118,7 +125,7 @@ export declare namespace KcContextBase {
alias: string; alias: string;
providerId: string; providerId: string;
displayName: string; displayName: string;
}[] }[];
}; };
}; };
@ -131,10 +138,9 @@ export declare namespace KcContextBase {
lastName?: string; lastName?: string;
email?: string; email?: string;
username?: string; username?: string;
}
}; };
}; };
};
export type RegisterUserProfile = RegisterCommon & { export type RegisterUserProfile = RegisterCommon & {
pageId: "register-user-profile.ftl"; pageId: "register-user-profile.ftl";
@ -142,27 +148,29 @@ export declare namespace KcContextBase {
context: "REGISTRATION_PROFILE"; context: "REGISTRATION_PROFILE";
attributes: Attribute[]; attributes: Attribute[];
attributesByName: Record<string, Attribute>; attributesByName: Record<string, Attribute>;
}
}; };
};
export type Info = Common & { export type Info = Common & {
pageId: "info.ftl"; pageId: "info.ftl";
messageHeader?: string; messageHeader?: string;
requiredActions?: ExtractAfterStartingWith<"requiredAction.", MessageKey>[]; requiredActions?: ExtractAfterStartingWith<
"requiredAction.",
MessageKey
>[];
skipLink: boolean; skipLink: boolean;
pageRedirectUri?: string; pageRedirectUri?: string;
actionUri?: string; actionUri?: string;
client: { client: {
baseUrl?: string; baseUrl?: string;
} };
}; };
export type Error = Common & { export type Error = Common & {
pageId: "error.ftl"; pageId: "error.ftl";
client?: { client?: {
baseUrl?: string; baseUrl?: string;
}, };
message: NonNullable<Common["message"]>; message: NonNullable<Common["message"]>;
}; };
@ -170,7 +178,7 @@ export declare namespace KcContextBase {
pageId: "login-reset-password.ftl"; pageId: "login-reset-password.ftl";
realm: { realm: {
loginWithEmailAllowed: boolean; loginWithEmailAllowed: boolean;
} };
}; };
export type LoginVerifyEmail = Common & { export type LoginVerifyEmail = Common & {
@ -184,8 +192,8 @@ export declare namespace KcContextBase {
export type LoginOtp = Common & { export type LoginOtp = Common & {
pageId: "login-otp.ftl"; pageId: "login-otp.ftl";
otpLogin: { otpLogin: {
userOtpCredentials: { id: string; userLabel: string; }[]; userOtpCredentials: { id: string; userLabel: string }[];
} };
}; };
export type LoginUpdateProfile = Common & { export type LoginUpdateProfile = Common & {
@ -203,7 +211,6 @@ export declare namespace KcContextBase {
pageId: "login-idp-link-confirm.ftl"; pageId: "login-idp-link-confirm.ftl";
idpAlias: string; idpAlias: string;
}; };
} }
export type Attribute = { export type Attribute = {
@ -218,7 +225,7 @@ export type Attribute = {
autocomplete?: string; autocomplete?: string;
validators: Validators; validators: Validators;
annotations: Record<string, string>; annotations: Record<string, string>;
groupAnnotations: Record<string, string> groupAnnotations: Record<string, string>;
}; };
export type Validators = Partial<{ export type Validators = Partial<{
@ -226,42 +233,40 @@ export type Validators = Partial<{
double: Validators.DoIgnoreEmpty & Validators.Range; double: Validators.DoIgnoreEmpty & Validators.Range;
integer: Validators.DoIgnoreEmpty & Validators.Range; integer: Validators.DoIgnoreEmpty & Validators.Range;
email: Validators.DoIgnoreEmpty; email: Validators.DoIgnoreEmpty;
'up-immutable-attribute': {}; "up-immutable-attribute": {};
'up-attribute-required-by-metadata-value': {}; "up-attribute-required-by-metadata-value": {};
'up-username-has-value': {}; "up-username-has-value": {};
'up-duplicate-username': {}; "up-duplicate-username": {};
'up-username-mutation': {}; "up-username-mutation": {};
'up-email-exists-as-username': {}; "up-email-exists-as-username": {};
'up-blank-attribute-value': Validators.ErrorMessage & { "up-blank-attribute-value": Validators.ErrorMessage & {
'fail-on-null': boolean; "fail-on-null": boolean;
}; };
'up-duplicate-email': {}; "up-duplicate-email": {};
'local-date': Validators.DoIgnoreEmpty; "local-date": Validators.DoIgnoreEmpty;
pattern: Validators.DoIgnoreEmpty & Validators.ErrorMessage & { pattern: string; }; pattern: Validators.DoIgnoreEmpty &
'person-name-prohibited-characters': Validators.DoIgnoreEmpty & Validators.ErrorMessage; Validators.ErrorMessage & { pattern: string };
"person-name-prohibited-characters": Validators.DoIgnoreEmpty &
Validators.ErrorMessage;
uri: Validators.DoIgnoreEmpty; uri: Validators.DoIgnoreEmpty;
'username-prohibited-characters': Validators.DoIgnoreEmpty & Validators.ErrorMessage; "username-prohibited-characters": Validators.DoIgnoreEmpty &
Validators.ErrorMessage;
}>; }>;
export declare namespace Validators { export declare namespace Validators {
export type DoIgnoreEmpty = { export type DoIgnoreEmpty = {
'ignore.empty.value'?: boolean; "ignore.empty.value"?: boolean;
}; };
export type ErrorMessage = { export type ErrorMessage = {
'error-message'?: string; "error-message"?: string;
} };
export type Range = { export type Range = {
/** "0", "1", "2"... yeah I know, don't tell me */ /** "0", "1", "2"... yeah I know, don't tell me */
min?: string; min?: string;
max?: string; max?: string;
}; };
} }
assert<Equals<KcContextBase["pageId"], PageId>>(); assert<Equals<KcContextBase["pageId"], PageId>>();

View File

@ -1,4 +1,3 @@
import type { KcContextBase } from "./KcContextBase"; import type { KcContextBase } from "./KcContextBase";
import { kcContextMocks, kcContextCommonMock } from "./kcContextMocks"; import { kcContextMocks, kcContextCommonMock } from "./kcContextMocks";
import { ftlValuesGlobalName } from "../../bin/build-keycloak-theme/ftlValuesGlobalName"; import { ftlValuesGlobalName } from "../../bin/build-keycloak-theme/ftlValuesGlobalName";
@ -6,81 +5,71 @@ import type { AndByDiscriminatingKey } from "../tools/AndByDiscriminatingKey";
import type { DeepPartial } from "../tools/DeepPartial"; import type { DeepPartial } from "../tools/DeepPartial";
import { deepAssign } from "../tools/deepAssign"; import { deepAssign } from "../tools/deepAssign";
export type ExtendsKcContextBase<KcContextExtended extends { pageId: string }> =
export type ExtendsKcContextBase< [KcContextExtended] extends [never]
KcContextExtended extends { pageId: string; } ? KcContextBase
> = : AndByDiscriminatingKey<
[KcContextExtended] extends [never] ?
KcContextBase :
AndByDiscriminatingKey<
"pageId", "pageId",
KcContextExtended & KcContextBase.Common, KcContextExtended & KcContextBase.Common,
KcContextBase KcContextBase
>; >;
export function getKcContext<KcContextExtended extends { pageId: string; } = never>( export function getKcContext<
params?: { KcContextExtended extends { pageId: string } = never,
>(params?: {
mockPageId?: ExtendsKcContextBase<KcContextExtended>["pageId"]; mockPageId?: ExtendsKcContextBase<KcContextExtended>["pageId"];
mockData?: readonly DeepPartial<ExtendsKcContextBase<KcContextExtended>>[]; mockData?: readonly DeepPartial<ExtendsKcContextBase<KcContextExtended>>[];
} }): { kcContext: ExtendsKcContextBase<KcContextExtended> | undefined } {
): { kcContext: ExtendsKcContextBase<KcContextExtended> | undefined; } { const { mockPageId, mockData } = params ?? {};
const {
mockPageId,
mockData
} = params ?? {};
if (mockPageId !== undefined) { if (mockPageId !== undefined) {
//TODO maybe trow if no mock fo custom page //TODO maybe trow if no mock fo custom page
const kcContextDefaultMock = kcContextMocks.find(({ pageId }) => pageId === mockPageId); const kcContextDefaultMock = kcContextMocks.find(
({ pageId }) => pageId === mockPageId,
);
const partialKcContextCustomMock = mockData?.find(({ pageId }) => pageId === mockPageId); const partialKcContextCustomMock = mockData?.find(
({ pageId }) => pageId === mockPageId,
);
if ( if (
kcContextDefaultMock === undefined && kcContextDefaultMock === undefined &&
partialKcContextCustomMock === undefined partialKcContextCustomMock === undefined
) { ) {
console.warn(
console.warn([ [
`WARNING: You declared the non build in page ${mockPageId} but you didn't `, `WARNING: You declared the non build in page ${mockPageId} but you didn't `,
`provide mock data needed to debug the page outside of Keycloak as you are trying to do now.`, `provide mock data needed to debug the page outside of Keycloak as you are trying to do now.`,
`Please check the documentation of the getKcContext function` `Please check the documentation of the getKcContext function`,
].join("\n")); ].join("\n"),
);
} }
const kcContext: any = {}; const kcContext: any = {};
deepAssign({ deepAssign({
"target": kcContext, "target": kcContext,
"source": kcContextDefaultMock !== undefined ? "source":
kcContextDefaultMock : kcContextDefaultMock !== undefined
{ "pageId": mockPageId, ...kcContextCommonMock, } ? kcContextDefaultMock
: { "pageId": mockPageId, ...kcContextCommonMock },
}); });
if (partialKcContextCustomMock !== undefined) { if (partialKcContextCustomMock !== undefined) {
deepAssign({ deepAssign({
"target": kcContext, "target": kcContext,
"source": partialKcContextCustomMock "source": partialKcContextCustomMock,
}); });
} }
return { kcContext }; return { kcContext };
} }
return { return {
"kcContext": "kcContext":
typeof window === "undefined" ? typeof window === "undefined"
undefined : ? undefined
(window as any)[ftlValuesGlobalName] : (window as any)[ftlValuesGlobalName],
}; };
} }

View File

@ -1,2 +1,2 @@
export type { KcContextBase } from "./KcContextBase"; export type { KcContextBase } from "./KcContextBase";
export { getKcContext } from "./getKcContext"; export { getKcContext } from "./getKcContext";

View File

@ -1,4 +1,3 @@
import "minimal-polyfills/Object.fromEntries"; import "minimal-polyfills/Object.fromEntries";
import type { KcContextBase, Attribute } from "../KcContextBase"; import type { KcContextBase, Attribute } from "../KcContextBase";
import { getEvtKcLanguage } from "../../i18n/useKcLanguageTag"; import { getEvtKcLanguage } from "../../i18n/useKcLanguageTag";
@ -15,8 +14,10 @@ export const kcContextCommonMock: KcContextBase.Common = {
"loginAction": "#", "loginAction": "#",
"resourcesPath": pathJoin(PUBLIC_URL, resourcesPath), "resourcesPath": pathJoin(PUBLIC_URL, resourcesPath),
"resourcesCommonPath": pathJoin(PUBLIC_URL, resourcesCommonPath), "resourcesCommonPath": pathJoin(PUBLIC_URL, resourcesCommonPath),
"loginRestartFlowUrl": "/auth/realms/myrealm/login-actions/restart?client_id=account&tab_id=HoAx28ja4xg", "loginRestartFlowUrl":
"loginUrl": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg", "/auth/realms/myrealm/login-actions/restart?client_id=account&tab_id=HoAx28ja4xg",
"loginUrl":
"/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg",
}, },
"realm": { "realm": {
"displayName": "myrealm", "displayName": "myrealm",
@ -28,116 +29,113 @@ export const kcContextCommonMock: KcContextBase.Common = {
"printIfExists": (...[, x]) => x, "printIfExists": (...[, x]) => x,
"existsError": () => true, "existsError": () => true,
"get": key => `Fake error for ${key}`, "get": key => `Fake error for ${key}`,
"exists": () => true "exists": () => true,
}, },
"locale": { "locale": {
"supported": [ "supported": [
{ {
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=de", "url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=de",
"languageTag": "de" "languageTag": "de",
}, },
{ {
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=no", "url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=no",
"languageTag": "no" "languageTag": "no",
}, },
{ {
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=ru", "url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=ru",
"languageTag": "ru" "languageTag": "ru",
}, },
{ {
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=sv", "url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=sv",
"languageTag": "sv" "languageTag": "sv",
}, },
{ {
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=pt-BR", "url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=pt-BR",
"languageTag": "pt-BR" "languageTag": "pt-BR",
}, },
{ {
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=lt", "url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=lt",
"languageTag": "lt" "languageTag": "lt",
}, },
{ {
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=en", "url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=en",
"languageTag": "en" "languageTag": "en",
}, },
{ {
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=it", "url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=it",
"languageTag": "it" "languageTag": "it",
}, },
{ {
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=fr", "url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=fr",
"languageTag": "fr" "languageTag": "fr",
}, },
{ {
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=zh-CN", "url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=zh-CN",
"languageTag": "zh-CN" "languageTag": "zh-CN",
}, },
{ {
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=es", "url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=es",
"languageTag": "es" "languageTag": "es",
}, },
{ {
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=cs", "url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=cs",
"languageTag": "cs" "languageTag": "cs",
}, },
{ {
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=ja", "url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=ja",
"languageTag": "ja" "languageTag": "ja",
}, },
{ {
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=sk", "url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=sk",
"languageTag": "sk" "languageTag": "sk",
}, },
{ {
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=pl", "url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=pl",
"languageTag": "pl" "languageTag": "pl",
}, },
{ {
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=ca", "url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=ca",
"languageTag": "ca" "languageTag": "ca",
}, },
{ {
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=nl", "url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=nl",
"languageTag": "nl" "languageTag": "nl",
}, },
{ {
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=tr", "url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=tr",
"languageTag": "tr" "languageTag": "tr",
} },
], ],
//"current": null as any //"current": null as any
"current": "English" "current": "English",
}, },
"auth": { "auth": {
"showUsername": false, "showUsername": false,
"showResetCredentials": false, "showResetCredentials": false,
"showTryAnotherWayLink": false "showTryAnotherWayLink": false,
}, },
"client": { "client": {
"clientId": "myApp" "clientId": "myApp",
}, },
"scripts": [], "scripts": [],
"message": { "message": {
"type": "success", "type": "success",
"summary": "This is a test message" "summary": "This is a test message",
}, },
"isAppInitiatedAction": false, "isAppInitiatedAction": false,
}; };
Object.defineProperty(kcContextCommonMock.locale!, "current", {
Object.defineProperty(
kcContextCommonMock.locale!,
"current",
{
"get": () => getKcLanguageTagLabel(getEvtKcLanguage().state), "get": () => getKcLanguageTagLabel(getEvtKcLanguage().state),
"enumerable": true "enumerable": true,
} });
);
const loginUrl = { const loginUrl = {
...kcContextCommonMock.url, ...kcContextCommonMock.url,
"loginResetCredentialsUrl": "/auth/realms/myrealm/login-actions/reset-credentials?client_id=account&tab_id=HoAx28ja4xg", "loginResetCredentialsUrl":
"registrationUrl": "/auth/realms/myrealm/login-actions/registration?client_id=account&tab_id=HoAx28ja4xg" "/auth/realms/myrealm/login-actions/reset-credentials?client_id=account&tab_id=HoAx28ja4xg",
"registrationUrl":
"/auth/realms/myrealm/login-actions/registration?client_id=account&tab_id=HoAx28ja4xg",
}; };
export const kcContextMocks: KcContextBase[] = [ export const kcContextMocks: KcContextBase[] = [
@ -151,33 +149,32 @@ export const kcContextMocks: KcContextBase[] = [
"rememberMe": true, "rememberMe": true,
"password": true, "password": true,
"resetPasswordAllowed": true, "resetPasswordAllowed": true,
"registrationAllowed": true "registrationAllowed": true,
}, },
"auth": kcContextCommonMock.auth!, "auth": kcContextCommonMock.auth!,
"social": { "social": {
"displayInfo": true "displayInfo": true,
}, },
"usernameEditDisabled": false, "usernameEditDisabled": false,
"login": { "login": {
"rememberMe": false "rememberMe": false,
}, },
"registrationDisabled": false, "registrationDisabled": false,
}), }),
...(() => { ...(() => {
const registerCommon: KcContextBase.RegisterCommon = { const registerCommon: KcContextBase.RegisterCommon = {
...kcContextCommonMock, ...kcContextCommonMock,
"url": { "url": {
...loginUrl, ...loginUrl,
"registrationAction": "http://localhost:8080/auth/realms/myrealm/login-actions/registration?session_code=gwZdUeO7pbYpFTRxiIxRg_QtzMbtFTKrNu6XW_f8asM&execution=12146ce0-b139-4bbd-b25b-0eccfee6577e&client_id=account&tab_id=uS8lYfebLa0" "registrationAction":
"http://localhost:8080/auth/realms/myrealm/login-actions/registration?session_code=gwZdUeO7pbYpFTRxiIxRg_QtzMbtFTKrNu6XW_f8asM&execution=12146ce0-b139-4bbd-b25b-0eccfee6577e&client_id=account&tab_id=uS8lYfebLa0",
}, },
"scripts": [], "scripts": [],
"isAppInitiatedAction": false, "isAppInitiatedAction": false,
"passwordRequired": true, "passwordRequired": true,
"recaptchaRequired": false, "recaptchaRequired": false,
"social": { "social": {
"displayInfo": true "displayInfo": true,
}, },
}; };
@ -186,177 +183,143 @@ export const kcContextMocks: KcContextBase[] = [
"pageId": "register.ftl", "pageId": "register.ftl",
...registerCommon, ...registerCommon,
"register": { "register": {
"formData": {} "formData": {},
}, },
}), }),
id<KcContextBase.RegisterUserProfile>({ id<KcContextBase.RegisterUserProfile>({
"pageId": "register-user-profile.ftl", "pageId": "register-user-profile.ftl",
...registerCommon, ...registerCommon,
"profile": { "profile": {
"context": "REGISTRATION_PROFILE" as const, "context": "REGISTRATION_PROFILE" as const,
...(() => { ...(() => {
const attributes: Attribute[] = [ const attributes: Attribute[] = [
{ {
"validators": { "validators": {
"username-prohibited-characters": { "username-prohibited-characters": {
"ignore.empty.value": true "ignore.empty.value": true,
},
"up-username-has-value": {
}, },
"up-username-has-value": {},
"length": { "length": {
"ignore.empty.value": true, "ignore.empty.value": true,
"min": "3", "min": "3",
"max": "255" "max": "255",
}, },
"up-duplicate-username": { "up-duplicate-username": {},
"up-username-mutation": {},
},
"up-username-mutation": {
}
}, },
"displayName": "${username}", "displayName": "${username}",
"annotations": { "annotations": {},
},
"required": true, "required": true,
"groupAnnotations": { "groupAnnotations": {},
},
"autocomplete": "username", "autocomplete": "username",
"readOnly": false, "readOnly": false,
"name": "username" "name": "username",
}, },
{ {
"validators": { "validators": {
"up-email-exists-as-username": { "up-email-exists-as-username": {},
},
"length": { "length": {
"max": "255", "max": "255",
"ignore.empty.value": true "ignore.empty.value": true,
}, },
"up-blank-attribute-value": { "up-blank-attribute-value": {
"error-message": "missingEmailMessage", "error-message": "missingEmailMessage",
"fail-on-null": false "fail-on-null": false,
},
"up-duplicate-email": {
}, },
"up-duplicate-email": {},
"email": { "email": {
"ignore.empty.value": true "ignore.empty.value": true,
} },
}, },
"displayName": "${email}", "displayName": "${email}",
"annotations": { "annotations": {},
},
"required": true, "required": true,
"groupAnnotations": { "groupAnnotations": {},
},
"autocomplete": "email", "autocomplete": "email",
"readOnly": false, "readOnly": false,
"name": "email" "name": "email",
}, },
{ {
"validators": { "validators": {
"length": { "length": {
"max": "255", "max": "255",
"ignore.empty.value": true "ignore.empty.value": true,
}, },
"person-name-prohibited-characters": { "person-name-prohibited-characters": {
"ignore.empty.value": true "ignore.empty.value": true,
},
"up-immutable-attribute": {
},
"up-attribute-required-by-metadata-value": {
}
},
"displayName": "${firstName}",
"annotations": {
},
"required": true,
"groupAnnotations": {
},
"readOnly": false,
"name": "firstName"
},
{
"validators": {
"length": {
"max": "255",
"ignore.empty.value": true
},
"person-name-prohibited-characters": {
"ignore.empty.value": true
}, },
"up-immutable-attribute": {}, "up-immutable-attribute": {},
"up-attribute-required-by-metadata-value": {} "up-attribute-required-by-metadata-value":
{},
},
"displayName": "${firstName}",
"annotations": {},
"required": true,
"groupAnnotations": {},
"readOnly": false,
"name": "firstName",
},
{
"validators": {
"length": {
"max": "255",
"ignore.empty.value": true,
},
"person-name-prohibited-characters": {
"ignore.empty.value": true,
},
"up-immutable-attribute": {},
"up-attribute-required-by-metadata-value":
{},
}, },
"displayName": "${lastName}", "displayName": "${lastName}",
"annotations": { "annotations": {},
},
"required": true, "required": true,
"groupAnnotations": { "groupAnnotations": {},
},
"readOnly": false, "readOnly": false,
"name": "lastName" "name": "lastName",
}, },
{ {
"validators": { "validators": {
"length": { "length": {
"ignore.empty.value": true, "ignore.empty.value": true,
"min": "3", "min": "3",
"max": "9" "max": "9",
}, },
"up-immutable-attribute": {}, "up-immutable-attribute": {},
"up-attribute-required-by-metadata-value": {}, "up-attribute-required-by-metadata-value":
{},
"email": { "email": {
"ignore.empty.value": true "ignore.empty.value": true,
} },
}, },
"displayName": "${foo}", "displayName": "${foo}",
"annotations": { "annotations": {
"this_is_second_key": "this_is_second_value", "this_is_second_key":
"this_is_first_key": "this_is_first_value" "this_is_second_value",
"this_is_first_key": "this_is_first_value",
}, },
"required": true, "required": true,
"groupAnnotations": { "groupAnnotations": {},
},
"readOnly": false, "readOnly": false,
"name": "foo" "name": "foo",
} },
]; ];
return { return {
attributes, attributes,
"attributesByName": Object.fromEntries( "attributesByName": Object.fromEntries(
attributes.map(attribute => [attribute.name, attribute]) attributes.map(attribute => [
) as any attribute.name,
attribute,
]),
) as any,
} as any; } as any;
})(),
},
})() }),
}
})
]; ];
})(), })(),
id<KcContextBase.Info>({ id<KcContextBase.Info>({
...kcContextCommonMock, ...kcContextCommonMock,
@ -367,39 +330,36 @@ export const kcContextMocks: KcContextBase[] = [
"actionUri": "#", "actionUri": "#",
"client": { "client": {
"clientId": "myApp", "clientId": "myApp",
"baseUrl": "#" "baseUrl": "#",
} },
}), }),
id<KcContextBase.Error>({ id<KcContextBase.Error>({
...kcContextCommonMock, ...kcContextCommonMock,
"pageId": "error.ftl", "pageId": "error.ftl",
"client": { "client": {
"clientId": "myApp", "clientId": "myApp",
"baseUrl": "#" "baseUrl": "#",
}, },
"message": { "message": {
"type": "error", "type": "error",
"summary": "This is the error message" "summary": "This is the error message",
} },
}), }),
id<KcContextBase.LoginResetPassword>({ id<KcContextBase.LoginResetPassword>({
...kcContextCommonMock, ...kcContextCommonMock,
"pageId": "login-reset-password.ftl", "pageId": "login-reset-password.ftl",
"realm": { "realm": {
...kcContextCommonMock.realm, ...kcContextCommonMock.realm,
"loginWithEmailAllowed": false "loginWithEmailAllowed": false,
} },
}), }),
id<KcContextBase.LoginVerifyEmail>({ id<KcContextBase.LoginVerifyEmail>({
...kcContextCommonMock, ...kcContextCommonMock,
"pageId": "login-verify-email.ftl" "pageId": "login-verify-email.ftl",
}), }),
id<KcContextBase.Terms>({ id<KcContextBase.Terms>({
...kcContextCommonMock, ...kcContextCommonMock,
"pageId": "terms.ftl" "pageId": "terms.ftl",
}), }),
id<KcContextBase.LoginOtp>({ id<KcContextBase.LoginOtp>({
...kcContextCommonMock, ...kcContextCommonMock,
@ -408,15 +368,14 @@ export const kcContextMocks: KcContextBase[] = [
"userOtpCredentials": [ "userOtpCredentials": [
{ {
"id": "id1", "id": "id1",
"userLabel": "label1" "userLabel": "label1",
}, },
{ {
"id": "id2", "id": "id2",
"userLabel": "label2" "userLabel": "label2",
} },
] ],
} },
}), }),
id<KcContextBase.LoginUpdateProfile>({ id<KcContextBase.LoginUpdateProfile>({
...kcContextCommonMock, ...kcContextCommonMock,
@ -426,12 +385,12 @@ export const kcContextMocks: KcContextBase[] = [
"username": "anUsername", "username": "anUsername",
"email": "foo@example.com", "email": "foo@example.com",
"firstName": "aFirstName", "firstName": "aFirstName",
"lastName": "aLastName" "lastName": "aLastName",
} },
}), }),
id<KcContextBase.LoginIdpLinkConfirm>({ id<KcContextBase.LoginIdpLinkConfirm>({
...kcContextCommonMock, ...kcContextCommonMock,
"pageId": "login-idp-link-confirm.ftl", "pageId": "login-idp-link-confirm.ftl",
"idpAlias": "FranceConnect" "idpAlias": "FranceConnect",
}) }),
]; ];

View File

@ -1,6 +1,8 @@
import { join as pathJoin } from "path"; import { join as pathJoin } from "path";
export const subDirOfPublicDirBasename = "keycloak_static"; export const subDirOfPublicDirBasename = "keycloak_static";
export const resourcesPath = pathJoin(subDirOfPublicDirBasename, "/resources"); export const resourcesPath = pathJoin(subDirOfPublicDirBasename, "/resources");
export const resourcesCommonPath = pathJoin(subDirOfPublicDirBasename, "/resources_common"); export const resourcesCommonPath = pathJoin(
subDirOfPublicDirBasename,
"/resources_common",
);

View File

@ -1,4 +1,3 @@
import { objectKeys } from "tsafe/objectKeys"; import { objectKeys } from "tsafe/objectKeys";
import { kcMessages } from "./kcMessages/login"; import { kcMessages } from "./kcMessages/login";
@ -6,40 +5,75 @@ export type KcLanguageTag = keyof typeof kcMessages;
export type LanguageLabel = export type LanguageLabel =
/* spell-checker: disable */ /* spell-checker: disable */
"Deutsch" | "Norsk" | "Русский" | "Svenska" | "Português (Brasil)" | "Lietuvių" | | "Deutsch"
"English" | "Italiano" | "Français" | "中文简体" | "Español" | "Čeština" | "日本語" | | "Norsk"
"Slovenčina" | "Polski" | "Català" | "Nederlands" | "Türkçe" | "Dansk" | "Magyar"; | "Русский"
| "Svenska"
| "Português (Brasil)"
| "Lietuvių"
| "English"
| "Italiano"
| "Français"
| "中文简体"
| "Español"
| "Čeština"
| "日本語"
| "Slovenčina"
| "Polski"
| "Català"
| "Nederlands"
| "Türkçe"
| "Dansk"
| "Magyar";
/* spell-checker: enable */ /* spell-checker: enable */
export function getKcLanguageTagLabel(language: KcLanguageTag): LanguageLabel { export function getKcLanguageTagLabel(language: KcLanguageTag): LanguageLabel {
switch (language) { switch (language) {
/* spell-checker: disable */ /* spell-checker: disable */
case "es": return "Español"; case "es":
case "it": return "Italiano"; return "Español";
case "fr": return "Français"; case "it":
case "ca": return "Català"; return "Italiano";
case "en": return "English"; case "fr":
case "de": return "Deutsch"; return "Français";
case "no": return "Norsk"; case "ca":
case "pt-BR": return "Português (Brasil)"; return "Català";
case "ru": return "Русский"; case "en":
case "sk": return "Slovenčina"; return "English";
case "ja": return "日本語"; case "de":
case "pl": return "Polski"; return "Deutsch";
case "zh-CN": return "中文简体" case "no":
case "sv": return "Svenska"; return "Norsk";
case "lt": return "Lietuvių"; case "pt-BR":
case "cs": return "Čeština"; return "Português (Brasil)";
case "nl": return "Nederlands"; case "ru":
case "tr": return "Türkçe"; return "Русский";
case "da": return "Dansk"; case "sk":
case "hu": return "Magyar"; return "Slovenčina";
case "ja":
return "日本語";
case "pl":
return "Polski";
case "zh-CN":
return "中文简体";
case "sv":
return "Svenska";
case "lt":
return "Lietuvių";
case "cs":
return "Čeština";
case "nl":
return "Nederlands";
case "tr":
return "Türkçe";
case "da":
return "Dansk";
case "hu":
return "Magyar";
/* spell-checker: enable */ /* spell-checker: enable */
} }
return language; return language;
} }
const availableLanguages = objectKeys(kcMessages); const availableLanguages = objectKeys(kcMessages);
@ -51,14 +85,15 @@ const availableLanguages = objectKeys(kcMessages);
* If still no matches "en" is returned. * If still no matches "en" is returned.
*/ */
export function getBestMatchAmongKcLanguageTag( export function getBestMatchAmongKcLanguageTag(
languageLike: string languageLike: string,
): KcLanguageTag { ): KcLanguageTag {
const iso2LanguageLike = languageLike.split("-")[0].toLowerCase(); const iso2LanguageLike = languageLike.split("-")[0].toLowerCase();
const kcLanguageTag = availableLanguages.find(language => const kcLanguageTag = availableLanguages.find(
language =>
language.toLowerCase().includes(iso2LanguageLike) || language.toLowerCase().includes(iso2LanguageLike) ||
getKcLanguageTagLabel(language).toLocaleLowerCase() === languageLike.toLocaleLowerCase() getKcLanguageTagLabel(language).toLocaleLowerCase() ===
languageLike.toLocaleLowerCase(),
); );
if (kcLanguageTag !== undefined) { if (kcLanguageTag !== undefined) {
@ -71,4 +106,3 @@ export function getBestMatchAmongKcLanguageTag(
return "en"; return "en";
} }

File diff suppressed because it is too large Load Diff

View File

@ -4,241 +4,435 @@
/* spell-checker: disable */ /* spell-checker: disable */
export const kcMessages = { export const kcMessages = {
"ca": { "ca": {
"invalidPasswordHistoryMessage": "Contrasenya incorrecta: no pot ser igual a cap de les últimes {0} contrasenyes.", "invalidPasswordHistoryMessage":
"invalidPasswordMinDigitsMessage": "Contraseña incorrecta: debe contener al menos {0} caracteres numéricos.", "Contrasenya incorrecta: no pot ser igual a cap de les últimes {0} contrasenyes.",
"invalidPasswordMinLengthMessage": "Contrasenya incorrecta: longitud mínima {0}.", "invalidPasswordMinDigitsMessage":
"invalidPasswordMinLowerCaseCharsMessage": "Contrasenya incorrecta: ha de contenir almenys {0} lletres minúscules.", "Contraseña incorrecta: debe contener al menos {0} caracteres numéricos.",
"invalidPasswordMinSpecialCharsMessage": "Contrasenya incorrecta: ha de contenir almenys {0} caràcters especials.", "invalidPasswordMinLengthMessage":
"invalidPasswordMinUpperCaseCharsMessage": "Contrasenya incorrecta: ha de contenir almenys {0} lletres majúscules.", "Contrasenya incorrecta: longitud mínima {0}.",
"invalidPasswordNotUsernameMessage": "Contrasenya incorrecta: no pot ser igual al nom d'usuari.", "invalidPasswordMinLowerCaseCharsMessage":
"invalidPasswordRegexPatternMessage": "Contrasenya incorrecta: no compleix l'expressió regular." "Contrasenya incorrecta: ha de contenir almenys {0} lletres minúscules.",
"invalidPasswordMinSpecialCharsMessage":
"Contrasenya incorrecta: ha de contenir almenys {0} caràcters especials.",
"invalidPasswordMinUpperCaseCharsMessage":
"Contrasenya incorrecta: ha de contenir almenys {0} lletres majúscules.",
"invalidPasswordNotUsernameMessage":
"Contrasenya incorrecta: no pot ser igual al nom d'usuari.",
"invalidPasswordRegexPatternMessage":
"Contrasenya incorrecta: no compleix l'expressió regular.",
}, },
"de": { "de": {
"invalidPasswordMinLengthMessage": "Ungültiges Passwort: muss mindestens {0} Zeichen beinhalten.", "invalidPasswordMinLengthMessage":
"invalidPasswordMinLowerCaseCharsMessage": "Ungültiges Passwort: muss mindestens {0} Kleinbuchstaben beinhalten.", "Ungültiges Passwort: muss mindestens {0} Zeichen beinhalten.",
"invalidPasswordMinDigitsMessage": "Ungültiges Passwort: muss mindestens {0} Ziffern beinhalten.", "invalidPasswordMinLowerCaseCharsMessage":
"invalidPasswordMinUpperCaseCharsMessage": "Ungültiges Passwort: muss mindestens {0} Großbuchstaben beinhalten.", "Ungültiges Passwort: muss mindestens {0} Kleinbuchstaben beinhalten.",
"invalidPasswordMinSpecialCharsMessage": "Ungültiges Passwort: muss mindestens {0} Sonderzeichen beinhalten.", "invalidPasswordMinDigitsMessage":
"invalidPasswordNotUsernameMessage": "Ungültiges Passwort: darf nicht identisch mit dem Benutzernamen sein.", "Ungültiges Passwort: muss mindestens {0} Ziffern beinhalten.",
"invalidPasswordRegexPatternMessage": "Ungültiges Passwort: stimmt nicht mit Regex-Muster überein.", "invalidPasswordMinUpperCaseCharsMessage":
"invalidPasswordHistoryMessage": "Ungültiges Passwort: darf nicht identisch mit einem der letzten {0} Passwörter sein.", "Ungültiges Passwort: muss mindestens {0} Großbuchstaben beinhalten.",
"invalidPasswordBlacklistedMessage": "Ungültiges Passwort: Passwort ist zu bekannt und auf der schwarzen Liste.", "invalidPasswordMinSpecialCharsMessage":
"invalidPasswordGenericMessage": "Ungültiges Passwort: neues Passwort erfüllt die Passwort-Anforderungen nicht." "Ungültiges Passwort: muss mindestens {0} Sonderzeichen beinhalten.",
"invalidPasswordNotUsernameMessage":
"Ungültiges Passwort: darf nicht identisch mit dem Benutzernamen sein.",
"invalidPasswordRegexPatternMessage":
"Ungültiges Passwort: stimmt nicht mit Regex-Muster überein.",
"invalidPasswordHistoryMessage":
"Ungültiges Passwort: darf nicht identisch mit einem der letzten {0} Passwörter sein.",
"invalidPasswordBlacklistedMessage":
"Ungültiges Passwort: Passwort ist zu bekannt und auf der schwarzen Liste.",
"invalidPasswordGenericMessage":
"Ungültiges Passwort: neues Passwort erfüllt die Passwort-Anforderungen nicht.",
}, },
"en": { "en": {
"invalidPasswordMinLengthMessage": "Invalid password: minimum length {0}.", "invalidPasswordMinLengthMessage":
"invalidPasswordMinLowerCaseCharsMessage": "Invalid password: must contain at least {0} lower case characters.", "Invalid password: minimum length {0}.",
"invalidPasswordMinDigitsMessage": "Invalid password: must contain at least {0} numerical digits.", "invalidPasswordMinLowerCaseCharsMessage":
"invalidPasswordMinUpperCaseCharsMessage": "Invalid password: must contain at least {0} upper case characters.", "Invalid password: must contain at least {0} lower case characters.",
"invalidPasswordMinSpecialCharsMessage": "Invalid password: must contain at least {0} special characters.", "invalidPasswordMinDigitsMessage":
"invalidPasswordNotUsernameMessage": "Invalid password: must not be equal to the username.", "Invalid password: must contain at least {0} numerical digits.",
"invalidPasswordRegexPatternMessage": "Invalid password: fails to match regex pattern(s).", "invalidPasswordMinUpperCaseCharsMessage":
"invalidPasswordHistoryMessage": "Invalid password: must not be equal to any of last {0} passwords.", "Invalid password: must contain at least {0} upper case characters.",
"invalidPasswordBlacklistedMessage": "Invalid password: password is blacklisted.", "invalidPasswordMinSpecialCharsMessage":
"invalidPasswordGenericMessage": "Invalid password: new password does not match password policies.", "Invalid password: must contain at least {0} special characters.",
"ldapErrorInvalidCustomFilter": "Custom configured LDAP filter does not start with \"(\" or does not end with \")\".", "invalidPasswordNotUsernameMessage":
"ldapErrorConnectionTimeoutNotNumber": "Connection Timeout must be a number", "Invalid password: must not be equal to the username.",
"invalidPasswordRegexPatternMessage":
"Invalid password: fails to match regex pattern(s).",
"invalidPasswordHistoryMessage":
"Invalid password: must not be equal to any of last {0} passwords.",
"invalidPasswordBlacklistedMessage":
"Invalid password: password is blacklisted.",
"invalidPasswordGenericMessage":
"Invalid password: new password does not match password policies.",
"ldapErrorInvalidCustomFilter":
'Custom configured LDAP filter does not start with "(" or does not end with ")".',
"ldapErrorConnectionTimeoutNotNumber":
"Connection Timeout must be a number",
"ldapErrorReadTimeoutNotNumber": "Read Timeout must be a number", "ldapErrorReadTimeoutNotNumber": "Read Timeout must be a number",
"ldapErrorMissingClientId": "Client ID needs to be provided in config when Realm Roles Mapping is not used.", "ldapErrorMissingClientId":
"ldapErrorCantPreserveGroupInheritanceWithUIDMembershipType": "Not possible to preserve group inheritance and use UID membership type together.", "Client ID needs to be provided in config when Realm Roles Mapping is not used.",
"ldapErrorCantWriteOnlyForReadOnlyLdap": "Can not set write only when LDAP provider mode is not WRITABLE", "ldapErrorCantPreserveGroupInheritanceWithUIDMembershipType":
"ldapErrorCantWriteOnlyAndReadOnly": "Can not set write-only and read-only together", "Not possible to preserve group inheritance and use UID membership type together.",
"ldapErrorCantEnableStartTlsAndConnectionPooling": "Can not enable both StartTLS and connection pooling.", "ldapErrorCantWriteOnlyForReadOnlyLdap":
"ldapErrorCantEnableUnsyncedAndImportOff": "Can not disable Importing users when LDAP provider mode is UNSYNCED", "Can not set write only when LDAP provider mode is not WRITABLE",
"ldapErrorMissingGroupsPathGroup": "Groups path group does not exist - please create the group on specified path first", "ldapErrorCantWriteOnlyAndReadOnly":
"clientRedirectURIsFragmentError": "Redirect URIs must not contain an URI fragment", "Can not set write-only and read-only together",
"clientRootURLFragmentError": "Root URL must not contain an URL fragment", "ldapErrorCantEnableStartTlsAndConnectionPooling":
"Can not enable both StartTLS and connection pooling.",
"ldapErrorCantEnableUnsyncedAndImportOff":
"Can not disable Importing users when LDAP provider mode is UNSYNCED",
"ldapErrorMissingGroupsPathGroup":
"Groups path group does not exist - please create the group on specified path first",
"clientRedirectURIsFragmentError":
"Redirect URIs must not contain an URI fragment",
"clientRootURLFragmentError":
"Root URL must not contain an URL fragment",
"clientRootURLIllegalSchemeError": "Root URL uses an illegal scheme", "clientRootURLIllegalSchemeError": "Root URL uses an illegal scheme",
"clientBaseURLIllegalSchemeError": "Base URL uses an illegal scheme", "clientBaseURLIllegalSchemeError": "Base URL uses an illegal scheme",
"clientRedirectURIsIllegalSchemeError": "A redirect URI uses an illegal scheme", "clientRedirectURIsIllegalSchemeError":
"A redirect URI uses an illegal scheme",
"clientBaseURLInvalid": "Base URL is not a valid URL", "clientBaseURLInvalid": "Base URL is not a valid URL",
"clientRootURLInvalid": "Root URL is not a valid URL", "clientRootURLInvalid": "Root URL is not a valid URL",
"clientRedirectURIsInvalid": "A redirect URI is not a valid URI", "clientRedirectURIsInvalid": "A redirect URI is not a valid URI",
"pairwiseMalformedClientRedirectURI": "Client contained an invalid redirect URI.", "pairwiseMalformedClientRedirectURI":
"pairwiseClientRedirectURIsMissingHost": "Client redirect URIs must contain a valid host component.", "Client contained an invalid redirect URI.",
"pairwiseClientRedirectURIsMultipleHosts": "Without a configured Sector Identifier URI, client redirect URIs must not contain multiple host components.", "pairwiseClientRedirectURIsMissingHost":
"pairwiseMalformedSectorIdentifierURI": "Malformed Sector Identifier URI.", "Client redirect URIs must contain a valid host component.",
"pairwiseFailedToGetRedirectURIs": "Failed to get redirect URIs from the Sector Identifier URI.", "pairwiseClientRedirectURIsMultipleHosts":
"pairwiseRedirectURIsMismatch": "Client redirect URIs does not match redirect URIs fetched from the Sector Identifier URI." "Without a configured Sector Identifier URI, client redirect URIs must not contain multiple host components.",
"pairwiseMalformedSectorIdentifierURI":
"Malformed Sector Identifier URI.",
"pairwiseFailedToGetRedirectURIs":
"Failed to get redirect URIs from the Sector Identifier URI.",
"pairwiseRedirectURIsMismatch":
"Client redirect URIs does not match redirect URIs fetched from the Sector Identifier URI.",
}, },
"es": { "es": {
"invalidPasswordMinLengthMessage": "Contraseña incorrecta: longitud mínima {0}.", "invalidPasswordMinLengthMessage":
"invalidPasswordMinLowerCaseCharsMessage": "Contraseña incorrecta: debe contener al menos {0} letras minúsculas.", "Contraseña incorrecta: longitud mínima {0}.",
"invalidPasswordMinDigitsMessage": "Contraseña incorrecta: debe contener al menos {0} caracteres numéricos.", "invalidPasswordMinLowerCaseCharsMessage":
"invalidPasswordMinUpperCaseCharsMessage": "Contraseña incorrecta: debe contener al menos {0} letras mayúsculas.", "Contraseña incorrecta: debe contener al menos {0} letras minúsculas.",
"invalidPasswordMinSpecialCharsMessage": "Contraseña incorrecta: debe contener al menos {0} caracteres especiales.", "invalidPasswordMinDigitsMessage":
"invalidPasswordNotUsernameMessage": "Contraseña incorrecta: no puede ser igual al nombre de usuario.", "Contraseña incorrecta: debe contener al menos {0} caracteres numéricos.",
"invalidPasswordRegexPatternMessage": "Contraseña incorrecta: no cumple la expresión regular.", "invalidPasswordMinUpperCaseCharsMessage":
"invalidPasswordHistoryMessage": "Contraseña incorrecta: no puede ser igual a ninguna de las últimas {0} contrasas." "Contraseña incorrecta: debe contener al menos {0} letras mayúsculas.",
"invalidPasswordMinSpecialCharsMessage":
"Contraseña incorrecta: debe contener al menos {0} caracteres especiales.",
"invalidPasswordNotUsernameMessage":
"Contraseña incorrecta: no puede ser igual al nombre de usuario.",
"invalidPasswordRegexPatternMessage":
"Contraseña incorrecta: no cumple la expresión regular.",
"invalidPasswordHistoryMessage":
"Contraseña incorrecta: no puede ser igual a ninguna de las últimas {0} contraseñas.",
}, },
"fr": { "fr": {
"invalidPasswordMinLengthMessage": "Mot de passe invalide : longueur minimale requise de {0}.", "invalidPasswordMinLengthMessage":
"invalidPasswordMinLowerCaseCharsMessage": "Mot de passe invalide : doit contenir au moins {0} lettre(s) en minuscule.", "Mot de passe invalide : longueur minimale requise de {0}.",
"invalidPasswordMinDigitsMessage": "Mot de passe invalide : doit contenir au moins {0} chiffre(s).", "invalidPasswordMinLowerCaseCharsMessage":
"invalidPasswordMinUpperCaseCharsMessage": "Mot de passe invalide : doit contenir au moins {0} lettre(s) en majuscule.", "Mot de passe invalide : doit contenir au moins {0} lettre(s) en minuscule.",
"invalidPasswordMinSpecialCharsMessage": "Mot de passe invalide : doit contenir au moins {0} caractère(s) spéciaux.", "invalidPasswordMinDigitsMessage":
"invalidPasswordNotUsernameMessage": "Mot de passe invalide : ne doit pas être identique au nom d'utilisateur.", "Mot de passe invalide : doit contenir au moins {0} chiffre(s).",
"invalidPasswordRegexPatternMessage": "Mot de passe invalide : ne valide pas l'expression rationnelle.", "invalidPasswordMinUpperCaseCharsMessage":
"invalidPasswordHistoryMessage": "Mot de passe invalide : ne doit pas être égal aux {0} derniers mot de passe." "Mot de passe invalide : doit contenir au moins {0} lettre(s) en majuscule.",
"invalidPasswordMinSpecialCharsMessage":
"Mot de passe invalide : doit contenir au moins {0} caractère(s) spéciaux.",
"invalidPasswordNotUsernameMessage":
"Mot de passe invalide : ne doit pas être identique au nom d'utilisateur.",
"invalidPasswordRegexPatternMessage":
"Mot de passe invalide : ne valide pas l'expression rationnelle.",
"invalidPasswordHistoryMessage":
"Mot de passe invalide : ne doit pas être égal aux {0} derniers mot de passe.",
}, },
"it": {}, "it": {},
"ja": { "ja": {
"invalidPasswordMinLengthMessage": "無効なパスワード: 最小{0}の長さが必要です。", "invalidPasswordMinLengthMessage":
"invalidPasswordMinLowerCaseCharsMessage": "無効なパスワード: 少なくとも{0}文字の小文字を含む必要があります。", "無効なパスワード: 最小{0}の長さが必要です。",
"invalidPasswordMinDigitsMessage": "無効なパスワード: 少なくとも{0}文字の数字を含む必要があります。", "invalidPasswordMinLowerCaseCharsMessage":
"invalidPasswordMinUpperCaseCharsMessage": "無効なパスワード: 少なくとも{0}文字の文字を含む必要があります。", "無効なパスワード: 少なくとも{0}文字の文字を含む必要があります。",
"invalidPasswordMinSpecialCharsMessage": "無効なパスワード: 少なくとも{0}文字の特殊文字を含む必要があります。", "invalidPasswordMinDigitsMessage":
"invalidPasswordNotUsernameMessage": "無効なパスワード: ユーザー名と同じパスワードは禁止されています。", "無効なパスワード: 少なくとも{0}文字の数字を含む必要があります。",
"invalidPasswordRegexPatternMessage": "無効なパスワード: 正規表現パターンと一致しません。", "invalidPasswordMinUpperCaseCharsMessage":
"invalidPasswordHistoryMessage": "無効なパスワード: 最近の{0}パスワードのいずれかと同じパスワードは禁止されています。", "無効なパスワード: 少なくとも{0}文字の大文字を含む必要があります。",
"invalidPasswordBlacklistedMessage": "無効なパスワード: パスワードがブラックリストに含まれています。", "invalidPasswordMinSpecialCharsMessage":
"invalidPasswordGenericMessage": "無効なパスワード: 新しいパスワードはパスワード・ポリシーと一致しません。", "無効なパスワード: 少なくとも{0}文字の特殊文字を含む必要があります。",
"ldapErrorInvalidCustomFilter": "LDAPフィルターのカスタム設定が、「(」から開始または「)」で終了となっていません。", "invalidPasswordNotUsernameMessage":
"ldapErrorConnectionTimeoutNotNumber": "接続タイムアウトは数字でなければなりません", "無効なパスワード: ユーザー名と同じパスワードは禁止されています。",
"ldapErrorReadTimeoutNotNumber": "読み取りタイムアウトは数字でなければなりません", "invalidPasswordRegexPatternMessage":
"ldapErrorMissingClientId": "レルムロール・マッピングを使用しない場合は、クライアントIDは設定内で提供される必要があります。", "無効なパスワード: 正規表現パターンと一致しません。",
"ldapErrorCantPreserveGroupInheritanceWithUIDMembershipType": "グループの継承を維持することと、UIDメンバーシップ・タイプを使用することは同時にできません。", "invalidPasswordHistoryMessage":
"ldapErrorCantWriteOnlyForReadOnlyLdap": "LDAPプロバイダー・モードがWRITABLEではない場合は、write onlyを設定することはできません。", "無効なパスワード: 最近の{0}パスワードのいずれかと同じパスワードは禁止されています。",
"ldapErrorCantWriteOnlyAndReadOnly": "write-onlyとread-onlyを一緒に設定することはできません。", "invalidPasswordBlacklistedMessage":
"ldapErrorCantEnableStartTlsAndConnectionPooling": "StartTLSと接続プーリングの両方を有効にできません。", "無効なパスワード: パスワードがブラックリストに含まれています。",
"clientRedirectURIsFragmentError": "リダイレクトURIにURIフラグメントを含めることはできません。", "invalidPasswordGenericMessage":
"clientRootURLFragmentError": "ルートURLにURLフラグメントを含めることはできません。", "無効なパスワード: 新しいパスワードはパスワード・ポリシーと一致しません。",
"pairwiseMalformedClientRedirectURI": "クライアントに無効なリダイレクトURIが含まれていました。", "ldapErrorInvalidCustomFilter":
"pairwiseClientRedirectURIsMissingHost": "クライアントのリダイレクトURIには有効なホスト・コンポーネントが含まれている必要があります。", "LDAPフィルターのカスタム設定が、「(」から開始または「)」で終了となっていません。",
"pairwiseClientRedirectURIsMultipleHosts": "設定されたセレクター識別子URIがない場合は、クライアントのリダイレクトURIは複数のホスト・コンポーネントを含むことはできません。", "ldapErrorConnectionTimeoutNotNumber":
"pairwiseMalformedSectorIdentifierURI": "不正なセレクター識別子URIです。", "接続タイムアウトは数字でなければなりません",
"pairwiseFailedToGetRedirectURIs": "セクター識別子URIからリダイレクトURIを取得できませんでした。", "ldapErrorReadTimeoutNotNumber":
"pairwiseRedirectURIsMismatch": "クライアントのリダイレクトURIは、セクター識別子URIからフェッチされたリダイレクトURIと一致しません" "読み取りタイムアウトは数字でなければなりません",
"ldapErrorMissingClientId":
"レルムロール・マッピングを使用しない場合は、クライアントIDは設定内で提供される必要があります。",
"ldapErrorCantPreserveGroupInheritanceWithUIDMembershipType":
"グループの継承を維持することと、UIDメンバーシップ・タイプを使用することは同時にできません。",
"ldapErrorCantWriteOnlyForReadOnlyLdap":
"LDAPプロバイダー・モードがWRITABLEではない場合は、write onlyを設定することはできません。",
"ldapErrorCantWriteOnlyAndReadOnly":
"write-onlyとread-onlyを一緒に設定することはできません。",
"ldapErrorCantEnableStartTlsAndConnectionPooling":
"StartTLSと接続プーリングの両方を有効にできません。",
"clientRedirectURIsFragmentError":
"リダイレクトURIにURIフラグメントを含めることはできません。",
"clientRootURLFragmentError":
"ルートURLにURLフラグメントを含めることはできません。",
"pairwiseMalformedClientRedirectURI":
"クライアントに無効なリダイレクトURIが含まれていました。",
"pairwiseClientRedirectURIsMissingHost":
"クライアントのリダイレクトURIには有効なホスト・コンポーネントが含まれている必要があります。",
"pairwiseClientRedirectURIsMultipleHosts":
"設定されたセレクター識別子URIがない場合は、クライアントのリダイレクトURIは複数のホスト・コンポーネントを含むことはできません。",
"pairwiseMalformedSectorIdentifierURI":
"不正なセレクター識別子URIです。",
"pairwiseFailedToGetRedirectURIs":
"セクター識別子URIからリダイレクトURIを取得できませんでした。",
"pairwiseRedirectURIsMismatch":
"クライアントのリダイレクトURIは、セクター識別子URIからフェッチされたリダイレクトURIと一致しません。",
}, },
"lt": { "lt": {
"invalidPasswordMinLengthMessage": "Per trumpas slaptažodis: mažiausias ilgis {0}.", "invalidPasswordMinLengthMessage":
"invalidPasswordMinLowerCaseCharsMessage": "Neteisingas slaptažodis: privaloma įvesti {0} mažąją raidę.", "Per trumpas slaptažodis: mažiausias ilgis {0}.",
"invalidPasswordMinDigitsMessage": "Neteisingas slaptažodis: privaloma įvesti {0} skaitmenį.", "invalidPasswordMinLowerCaseCharsMessage":
"invalidPasswordMinUpperCaseCharsMessage": "Neteisingas slaptažodis: privaloma įvesti {0} didžiąją raidę.", "Neteisingas slaptažodis: privaloma įvesti {0} mažąją raidę.",
"invalidPasswordMinSpecialCharsMessage": "Neteisingas slaptažodis: privaloma įvesti {0} specialų simbolį.", "invalidPasswordMinDigitsMessage":
"invalidPasswordNotUsernameMessage": "Neteisingas slaptažodis: slaptažodis negali sutapti su naudotojo vardu.", "Neteisingas slaptažodis: privaloma įvesti {0} skaitmenį.",
"invalidPasswordRegexPatternMessage": "Neteisingas slaptažodis: slaptažodis netenkina regex taisyklės(ių).", "invalidPasswordMinUpperCaseCharsMessage":
"invalidPasswordHistoryMessage": "Neteisingas slaptažodis: slaptažodis negali sutapti su prieš tai buvusiais {0} slaptažodžiais.", "Neteisingas slaptažodis: privaloma įvesti {0} didžiąją raidę.",
"ldapErrorInvalidCustomFilter": "Sukonfigūruotas LDAP filtras neprasideda \"(\" ir nesibaigia \")\" simboliais.", "invalidPasswordMinSpecialCharsMessage":
"ldapErrorMissingClientId": "Privaloma nurodyti kliento ID kai srities rolių susiejimas nėra nenaudojamas.", "Neteisingas slaptažodis: privaloma įvesti {0} specialų simbolį.",
"ldapErrorCantPreserveGroupInheritanceWithUIDMembershipType": "Grupių paveldėjimo ir UID narystės tipas kartu negali būti naudojami.", "invalidPasswordNotUsernameMessage":
"ldapErrorCantWriteOnlyForReadOnlyLdap": "Negalima nustatyti rašymo rėžimo kuomet LDAP teikėjo rėžimas ne WRITABLE", "Neteisingas slaptažodis: slaptažodis negali sutapti su naudotojo vardu.",
"ldapErrorCantWriteOnlyAndReadOnly": "Negalima nustatyti tik rašyti ir tik skaityti kartu", "invalidPasswordRegexPatternMessage":
"clientRedirectURIsFragmentError": "Nurodykite URI fragmentą, kurio negali būti peradresuojamuose URI adresuose", "Neteisingas slaptažodis: slaptažodis netenkina regex taisyklės(ių).",
"clientRootURLFragmentError": "Nurodykite URL fragmentą, kurio negali būti šakniniame URL adrese", "invalidPasswordHistoryMessage":
"pairwiseMalformedClientRedirectURI": "Klientas pateikė neteisingą nukreipimo nuorodą.", "Neteisingas slaptažodis: slaptažodis negali sutapti su prieš tai buvusiais {0} slaptažodžiais.",
"pairwiseClientRedirectURIsMissingHost": "Kliento nukreipimo nuorodos privalo būti nurodytos su serverio vardo komponentu.", "ldapErrorInvalidCustomFilter":
"pairwiseClientRedirectURIsMultipleHosts": "Kuomet nesukonfigūruotas sektoriaus identifikatoriaus URL, kliento nukreipimo nuorodos privalo talpinti ne daugiau kaip vieną skirtingą serverio vardo komponentą.", 'Sukonfigūruotas LDAP filtras neprasideda "(" ir nesibaigia ")" simboliais.',
"pairwiseMalformedSectorIdentifierURI": "Neteisinga sektoriaus identifikatoriaus URI.", "ldapErrorMissingClientId":
"pairwiseFailedToGetRedirectURIs": "Nepavyko gauti nukreipimo nuorodų iš sektoriaus identifikatoriaus URI.", "Privaloma nurodyti kliento ID kai srities rolių susiejimas nėra nenaudojamas.",
"pairwiseRedirectURIsMismatch": "Kliento nukreipimo nuoroda neatitinka nukreipimo nuorodų iš sektoriaus identifikatoriaus URI." "ldapErrorCantPreserveGroupInheritanceWithUIDMembershipType":
"Grupių paveldėjimo ir UID narystės tipas kartu negali būti naudojami.",
"ldapErrorCantWriteOnlyForReadOnlyLdap":
"Negalima nustatyti rašymo rėžimo kuomet LDAP teikėjo rėžimas ne WRITABLE",
"ldapErrorCantWriteOnlyAndReadOnly":
"Negalima nustatyti tik rašyti ir tik skaityti kartu",
"clientRedirectURIsFragmentError":
"Nurodykite URI fragmentą, kurio negali būti peradresuojamuose URI adresuose",
"clientRootURLFragmentError":
"Nurodykite URL fragmentą, kurio negali būti šakniniame URL adrese",
"pairwiseMalformedClientRedirectURI":
"Klientas pateikė neteisingą nukreipimo nuorodą.",
"pairwiseClientRedirectURIsMissingHost":
"Kliento nukreipimo nuorodos privalo būti nurodytos su serverio vardo komponentu.",
"pairwiseClientRedirectURIsMultipleHosts":
"Kuomet nesukonfigūruotas sektoriaus identifikatoriaus URL, kliento nukreipimo nuorodos privalo talpinti ne daugiau kaip vieną skirtingą serverio vardo komponentą.",
"pairwiseMalformedSectorIdentifierURI":
"Neteisinga sektoriaus identifikatoriaus URI.",
"pairwiseFailedToGetRedirectURIs":
"Nepavyko gauti nukreipimo nuorodų iš sektoriaus identifikatoriaus URI.",
"pairwiseRedirectURIsMismatch":
"Kliento nukreipimo nuoroda neatitinka nukreipimo nuorodų iš sektoriaus identifikatoriaus URI.",
}, },
"nl": { "nl": {
"invalidPasswordMinLengthMessage": "Ongeldig wachtwoord: de minimale lengte is {0} karakters.", "invalidPasswordMinLengthMessage":
"invalidPasswordMinLowerCaseCharsMessage": "Ongeldig wachtwoord: het moet minstens {0} kleine letters bevatten.", "Ongeldig wachtwoord: de minimale lengte is {0} karakters.",
"invalidPasswordMinDigitsMessage": "Ongeldig wachtwoord: het moet minstens {0} getallen bevatten.", "invalidPasswordMinLowerCaseCharsMessage":
"invalidPasswordMinUpperCaseCharsMessage": "Ongeldig wachtwoord: het moet minstens {0} hoofdletters bevatten.", "Ongeldig wachtwoord: het moet minstens {0} kleine letters bevatten.",
"invalidPasswordMinSpecialCharsMessage": "Ongeldig wachtwoord: het moet minstens {0} speciale karakters bevatten.", "invalidPasswordMinDigitsMessage":
"invalidPasswordNotUsernameMessage": "Ongeldig wachtwoord: het mag niet overeenkomen met de gebruikersnaam.", "Ongeldig wachtwoord: het moet minstens {0} getallen bevatten.",
"invalidPasswordRegexPatternMessage": "Ongeldig wachtwoord: het voldoet niet aan het door de beheerder ingestelde patroon.", "invalidPasswordMinUpperCaseCharsMessage":
"invalidPasswordHistoryMessage": "Ongeldig wachtwoord: het mag niet overeen komen met een van de laatste {0} wachtwoorden.", "Ongeldig wachtwoord: het moet minstens {0} hoofdletters bevatten.",
"invalidPasswordGenericMessage": "Ongeldig wachtwoord: het nieuwe wachtwoord voldoet niet aan het wachtwoordbeleid.", "invalidPasswordMinSpecialCharsMessage":
"ldapErrorInvalidCustomFilter": "LDAP filter met aangepaste configuratie start niet met \"(\" of eindigt niet met \")\".", "Ongeldig wachtwoord: het moet minstens {0} speciale karakters bevatten.",
"ldapErrorConnectionTimeoutNotNumber": "Verbindingstimeout moet een getal zijn", "invalidPasswordNotUsernameMessage":
"Ongeldig wachtwoord: het mag niet overeenkomen met de gebruikersnaam.",
"invalidPasswordRegexPatternMessage":
"Ongeldig wachtwoord: het voldoet niet aan het door de beheerder ingestelde patroon.",
"invalidPasswordHistoryMessage":
"Ongeldig wachtwoord: het mag niet overeen komen met een van de laatste {0} wachtwoorden.",
"invalidPasswordGenericMessage":
"Ongeldig wachtwoord: het nieuwe wachtwoord voldoet niet aan het wachtwoordbeleid.",
"ldapErrorInvalidCustomFilter":
'LDAP filter met aangepaste configuratie start niet met "(" of eindigt niet met ")".',
"ldapErrorConnectionTimeoutNotNumber":
"Verbindingstimeout moet een getal zijn",
"ldapErrorReadTimeoutNotNumber": "Lees-timeout moet een getal zijn", "ldapErrorReadTimeoutNotNumber": "Lees-timeout moet een getal zijn",
"ldapErrorMissingClientId": "Client ID moet ingesteld zijn als Realm Roles Mapping niet gebruikt wordt.", "ldapErrorMissingClientId":
"ldapErrorCantPreserveGroupInheritanceWithUIDMembershipType": "Kan groepsovererving niet behouden bij UID-lidmaatschapstype.", "Client ID moet ingesteld zijn als Realm Roles Mapping niet gebruikt wordt.",
"ldapErrorCantWriteOnlyForReadOnlyLdap": "Alleen-schrijven niet mogelijk als LDAP provider mode niet WRITABLE is", "ldapErrorCantPreserveGroupInheritanceWithUIDMembershipType":
"ldapErrorCantWriteOnlyAndReadOnly": "Alleen-schrijven en alleen-lezen mogen niet tegelijk ingesteld zijn", "Kan groepsovererving niet behouden bij UID-lidmaatschapstype.",
"clientRedirectURIsFragmentError": "Redirect URIs mogen geen URI fragment bevatten", "ldapErrorCantWriteOnlyForReadOnlyLdap":
"Alleen-schrijven niet mogelijk als LDAP provider mode niet WRITABLE is",
"ldapErrorCantWriteOnlyAndReadOnly":
"Alleen-schrijven en alleen-lezen mogen niet tegelijk ingesteld zijn",
"clientRedirectURIsFragmentError":
"Redirect URIs mogen geen URI fragment bevatten",
"clientRootURLFragmentError": "Root URL mag geen URL fragment bevatten", "clientRootURLFragmentError": "Root URL mag geen URL fragment bevatten",
"pairwiseMalformedClientRedirectURI": "Client heeft een ongeldige redirect URI.", "pairwiseMalformedClientRedirectURI":
"pairwiseClientRedirectURIsMissingHost": "Client redirect URIs moeten een geldige host-component bevatten.", "Client heeft een ongeldige redirect URI.",
"pairwiseClientRedirectURIsMultipleHosts": "Zonder een geconfigureerde Sector Identifier URI mogen client redirect URIs niet meerdere host componenten hebben.", "pairwiseClientRedirectURIsMissingHost":
"pairwiseMalformedSectorIdentifierURI": "Onjuist notatie in Sector Identifier URI.", "Client redirect URIs moeten een geldige host-component bevatten.",
"pairwiseFailedToGetRedirectURIs": "Kon geen redirect URIs verkrijgen van de Sector Identifier URI.", "pairwiseClientRedirectURIsMultipleHosts":
"pairwiseRedirectURIsMismatch": "Client redirect URIs komen niet overeen met redict URIs ontvangen van de Sector Identifier URI." "Zonder een geconfigureerde Sector Identifier URI mogen client redirect URIs niet meerdere host componenten hebben.",
"pairwiseMalformedSectorIdentifierURI":
"Onjuist notatie in Sector Identifier URI.",
"pairwiseFailedToGetRedirectURIs":
"Kon geen redirect URIs verkrijgen van de Sector Identifier URI.",
"pairwiseRedirectURIsMismatch":
"Client redirect URIs komen niet overeen met redict URIs ontvangen van de Sector Identifier URI.",
}, },
"no": { "no": {
"invalidPasswordMinLengthMessage": "Ugyldig passord: minimum lengde {0}.", "invalidPasswordMinLengthMessage":
"invalidPasswordMinLowerCaseCharsMessage": "Ugyldig passord: må inneholde minst {0} små bokstaver.", "Ugyldig passord: minimum lengde {0}.",
"invalidPasswordMinDigitsMessage": "Ugyldig passord: må inneholde minst {0} sifre.", "invalidPasswordMinLowerCaseCharsMessage":
"invalidPasswordMinUpperCaseCharsMessage": "Ugyldig passord: må inneholde minst {0} store bokstaver.", "Ugyldig passord: må inneholde minst {0} s bokstaver.",
"invalidPasswordMinSpecialCharsMessage": "Ugyldig passord: må inneholde minst {0} spesialtegn.", "invalidPasswordMinDigitsMessage":
"invalidPasswordNotUsernameMessage": "Ugyldig passord: kan ikke være likt brukernavn.", "Ugyldig passord: må inneholde minst {0} sifre.",
"invalidPasswordRegexPatternMessage": "Ugyldig passord: tilfredsstiller ikke kravene for passord-mønster.", "invalidPasswordMinUpperCaseCharsMessage":
"invalidPasswordHistoryMessage": "Ugyldig passord: kan ikke være likt noen av de {0} foregående passordene.", "Ugyldig passord: må inneholde minst {0} store bokstaver.",
"ldapErrorInvalidCustomFilter": "Tilpasset konfigurasjon av LDAP-filter starter ikke med \"(\" eller slutter ikke med \")\".", "invalidPasswordMinSpecialCharsMessage":
"ldapErrorMissingClientId": "KlientID må være tilgjengelig i config når sikkerhetsdomenerollemapping ikke brukes.", "Ugyldig passord: må inneholde minst {0} spesialtegn.",
"ldapErrorCantPreserveGroupInheritanceWithUIDMembershipType": "Ikke mulig å bevare gruppearv og samtidig bruke UID medlemskapstype.", "invalidPasswordNotUsernameMessage":
"ldapErrorCantWriteOnlyForReadOnlyLdap": "Kan ikke sette write-only når LDAP leverandør-modus ikke er WRITABLE", "Ugyldig passord: kan ikke være likt brukernavn.",
"ldapErrorCantWriteOnlyAndReadOnly": "Kan ikke sette både write-only og read-only" "invalidPasswordRegexPatternMessage":
"Ugyldig passord: tilfredsstiller ikke kravene for passord-mønster.",
"invalidPasswordHistoryMessage":
"Ugyldig passord: kan ikke være likt noen av de {0} foregående passordene.",
"ldapErrorInvalidCustomFilter":
'Tilpasset konfigurasjon av LDAP-filter starter ikke med "(" eller slutter ikke med ")".',
"ldapErrorMissingClientId":
"KlientID må være tilgjengelig i config når sikkerhetsdomenerollemapping ikke brukes.",
"ldapErrorCantPreserveGroupInheritanceWithUIDMembershipType":
"Ikke mulig å bevare gruppearv og samtidig bruke UID medlemskapstype.",
"ldapErrorCantWriteOnlyForReadOnlyLdap":
"Kan ikke sette write-only når LDAP leverandør-modus ikke er WRITABLE",
"ldapErrorCantWriteOnlyAndReadOnly":
"Kan ikke sette både write-only og read-only",
}, },
"pl": {}, "pl": {},
"pt-BR": { "pt-BR": {
"invalidPasswordMinLengthMessage": "Senha inválida: deve conter ao menos {0} caracteres.", "invalidPasswordMinLengthMessage":
"invalidPasswordMinLowerCaseCharsMessage": "Senha inválida: deve conter ao menos {0} caracteres minúsculos.", "Senha inválida: deve conter ao menos {0} caracteres.",
"invalidPasswordMinDigitsMessage": "Senha inválida: deve conter ao menos {0} digitos numéricos.", "invalidPasswordMinLowerCaseCharsMessage":
"invalidPasswordMinUpperCaseCharsMessage": "Senha inválida: deve conter ao menos {0} caracteres maiúsculos.", "Senha inválida: deve conter ao menos {0} caracteres minúsculos.",
"invalidPasswordMinSpecialCharsMessage": "Senha inválida: deve conter ao menos {0} caracteres especiais.", "invalidPasswordMinDigitsMessage":
"invalidPasswordNotUsernameMessage": "Senha inválida: não deve ser igual ao nome de usuário.", "Senha inválida: deve conter ao menos {0} digitos numéricos.",
"invalidPasswordRegexPatternMessage": "Senha inválida: falha ao passar por padrões.", "invalidPasswordMinUpperCaseCharsMessage":
"invalidPasswordHistoryMessage": "Senha inválida: não deve ser igual às últimas {0} senhas.", "Senha inválida: deve conter ao menos {0} caracteres maiúsculos.",
"ldapErrorInvalidCustomFilter": "Filtro LDAP não inicia com \"(\" ou não termina com \")\".", "invalidPasswordMinSpecialCharsMessage":
"ldapErrorMissingClientId": "ID do cliente precisa ser definido na configuração quando mapeamentos de Roles do Realm não é utilizado.", "Senha inválida: deve conter ao menos {0} caracteres especiais.",
"ldapErrorCantPreserveGroupInheritanceWithUIDMembershipType": "Não é possível preservar herança de grupos e usar tipo de associação de UID ao mesmo tempo.", "invalidPasswordNotUsernameMessage":
"ldapErrorCantWriteOnlyForReadOnlyLdap": "Não é possível definir modo de somente escrita quando o provedor LDAP não suporta escrita", "Senha inválida: não deve ser igual ao nome de usuário.",
"ldapErrorCantWriteOnlyAndReadOnly": "Não é possível definir somente escrita e somente leitura ao mesmo tempo", "invalidPasswordRegexPatternMessage":
"clientRedirectURIsFragmentError": "URIs de redirecionamento não podem conter fragmentos", "Senha inválida: falha ao passar por padrões.",
"clientRootURLFragmentError": "URL raiz não pode conter fragmentos" "invalidPasswordHistoryMessage":
"Senha inválida: não deve ser igual às últimas {0} senhas.",
"ldapErrorInvalidCustomFilter":
'Filtro LDAP não inicia com "(" ou não termina com ")".',
"ldapErrorMissingClientId":
"ID do cliente precisa ser definido na configuração quando mapeamentos de Roles do Realm não é utilizado.",
"ldapErrorCantPreserveGroupInheritanceWithUIDMembershipType":
"Não é possível preservar herança de grupos e usar tipo de associação de UID ao mesmo tempo.",
"ldapErrorCantWriteOnlyForReadOnlyLdap":
"Não é possível definir modo de somente escrita quando o provedor LDAP não suporta escrita",
"ldapErrorCantWriteOnlyAndReadOnly":
"Não é possível definir somente escrita e somente leitura ao mesmo tempo",
"clientRedirectURIsFragmentError":
"URIs de redirecionamento não podem conter fragmentos",
"clientRootURLFragmentError": "URL raiz não pode conter fragmentos",
}, },
"ru": { "ru": {
"invalidPasswordMinLengthMessage": "Некорректный пароль: длина пароля должна быть не менее {0} символов(а).", "invalidPasswordMinLengthMessage":
"invalidPasswordMinDigitsMessage": "Некорректный пароль: должен содержать не менее {0} цифр(ы).", "Некорректный пароль: длина пароля должна быть не менее {0} символов(а).",
"invalidPasswordMinLowerCaseCharsMessage": "Некорректный пароль: пароль должен содержать не менее {0} символов(а) в нижнем регистре.", "invalidPasswordMinDigitsMessage":
"invalidPasswordMinUpperCaseCharsMessage": "Некорректный пароль: пароль должен содержать не менее {0} символов(а) в верхнем регистре.", "Некорректный пароль: должен содержать не менее {0} цифр(ы).",
"invalidPasswordMinSpecialCharsMessage": "Некорректный пароль: пароль должен содержать не менее {0} спецсимволов(а).", "invalidPasswordMinLowerCaseCharsMessage":
"invalidPasswordNotUsernameMessage": "Некорректный пароль: пароль не должен совпадать с именем пользователя.", "Некорректный пароль: пароль должен содержать не менее {0} символов(а) в нижнем регистре.",
"invalidPasswordRegexPatternMessage": "Некорректный пароль: пароль не прошел проверку по регулярному выражению.", "invalidPasswordMinUpperCaseCharsMessage":
"invalidPasswordHistoryMessage": "Некорректный пароль: пароль не должен совпадать с последним(и) {0} паролем(ями).", "Некорректный пароль: пароль должен содержать не менее {0} символов(а) в верхнем регистре.",
"invalidPasswordGenericMessage": "Некорректный пароль: новый пароль не соответствует правилам пароля.", "invalidPasswordMinSpecialCharsMessage":
"ldapErrorInvalidCustomFilter": "Сконфигурированный пользователем фильтр LDAP не должен начинаться с \"(\" или заканчиваться на \")\".", "Некорректный пароль: пароль должен содержать не менее {0} спецсимволов(а).",
"ldapErrorMissingClientId": "Client ID должен быть настроен в конфигурации, если не используется сопоставление ролей в realm.", "invalidPasswordNotUsernameMessage":
"ldapErrorCantPreserveGroupInheritanceWithUIDMembershipType": "Не удалось унаследовать группу и использовать членство UID типа вместе.", "Некорректный пароль: пароль не должен совпадать с именем пользователя.",
"ldapErrorCantWriteOnlyForReadOnlyLdap": "Невозможно установить режим \"только на запись\", когда LDAP провайдер не в режиме WRITABLE", "invalidPasswordRegexPatternMessage":
"ldapErrorCantWriteOnlyAndReadOnly": "Невозможно одновременно установить режимы \"только на чтение\" и \"только на запись\"", "Некорректный пароль: пароль не прошел проверку по регулярному выражению.",
"clientRedirectURIsFragmentError": "URI перенаправления не должен содержать фрагмент URI", "invalidPasswordHistoryMessage":
"clientRootURLFragmentError": "Корневой URL не должен содержать фрагмент URL ", "Некорректный пароль: пароль не должен совпадать с последним(и) {0} паролем(ями).",
"pairwiseMalformedClientRedirectURI": "Клиент содержит некорректный URI перенаправления.", "invalidPasswordGenericMessage":
"pairwiseClientRedirectURIsMissingHost": "URI перенаправления клиента должен содержать корректный компонент хоста.", "Некорректный пароль: новый пароль не соответствует правилам пароля.",
"pairwiseClientRedirectURIsMultipleHosts": "Без конфигурации по части идентификатора URI, URI перенаправления клиента не может содержать несколько компонентов хоста.", "ldapErrorInvalidCustomFilter":
"pairwiseMalformedSectorIdentifierURI": "Искаженная часть идентификатора URI.", 'Сконфигурированный пользователем фильтр LDAP не должен начинаться с "(" или заканчиваться на ")".',
"pairwiseFailedToGetRedirectURIs": "Не удалось получить идентификаторы URI перенаправления из части идентификатора URI.", "ldapErrorMissingClientId":
"pairwiseRedirectURIsMismatch": "Клиент URI переадресации не соответствует URI переадресации, полученной из части идентификатора URI." "Client ID должен быть настроен в конфигурации, если не используется сопоставление ролей в realm.",
"ldapErrorCantPreserveGroupInheritanceWithUIDMembershipType":
"Не удалось унаследовать группу и использовать членство UID типа вместе.",
"ldapErrorCantWriteOnlyForReadOnlyLdap":
'Невозможно установить режим "только на запись", когда LDAP провайдер не в режиме WRITABLE',
"ldapErrorCantWriteOnlyAndReadOnly":
'Невозможно одновременно установить режимы "только на чтение" и "только на запись"',
"clientRedirectURIsFragmentError":
"URI перенаправления не должен содержать фрагмент URI",
"clientRootURLFragmentError":
"Корневой URL не должен содержать фрагмент URL ",
"pairwiseMalformedClientRedirectURI":
"Клиент содержит некорректный URI перенаправления.",
"pairwiseClientRedirectURIsMissingHost":
"URI перенаправления клиента должен содержать корректный компонент хоста.",
"pairwiseClientRedirectURIsMultipleHosts":
"Без конфигурации по части идентификатора URI, URI перенаправления клиента не может содержать несколько компонентов хоста.",
"pairwiseMalformedSectorIdentifierURI":
"Искаженная часть идентификатора URI.",
"pairwiseFailedToGetRedirectURIs":
"Не удалось получить идентификаторы URI перенаправления из части идентификатора URI.",
"pairwiseRedirectURIsMismatch":
"Клиент URI переадресации не соответствует URI переадресации, полученной из части идентификатора URI.",
}, },
"zh-CN": { "zh-CN": {
"invalidPasswordMinLengthMessage": "无效的密码:最短长度 {0}.", "invalidPasswordMinLengthMessage": "无效的密码:最短长度 {0}.",
"invalidPasswordMinLowerCaseCharsMessage": "无效的密码:至少包含 {0} 小写字母", "invalidPasswordMinLowerCaseCharsMessage":
"无效的密码:至少包含 {0} 小写字母",
"invalidPasswordMinDigitsMessage": "无效的密码:至少包含 {0} 个数字", "invalidPasswordMinDigitsMessage": "无效的密码:至少包含 {0} 个数字",
"invalidPasswordMinUpperCaseCharsMessage": "无效的密码:最短长度 {0} 大写字母", "invalidPasswordMinUpperCaseCharsMessage":
"invalidPasswordMinSpecialCharsMessage": "无效的密码:最短长度 {0} 特殊字符", "无效的密码:最短长度 {0} 大写字母",
"invalidPasswordMinSpecialCharsMessage":
"无效的密码:最短长度 {0} 特殊字符",
"invalidPasswordNotUsernameMessage": "无效的密码: 不可以与用户名相同", "invalidPasswordNotUsernameMessage": "无效的密码: 不可以与用户名相同",
"invalidPasswordRegexPatternMessage": "无效的密码: 无法与正则表达式匹配", "invalidPasswordRegexPatternMessage":
"invalidPasswordHistoryMessage": "无效的密码:不能与最后使用的 {0} 个密码相同", "无效的密码: 无法与正则表达式匹配",
"ldapErrorInvalidCustomFilter": "定制的 LDAP过滤器不是以 \"(\" 开头或以 \")\"结尾.", "invalidPasswordHistoryMessage":
"ldapErrorConnectionTimeoutNotNumber": "Connection Timeout 必须是个数字", "无效的密码:不能与最后使用的 {0} 个密码相同",
"ldapErrorMissingClientId": "当域角色映射未启用时,客户端 ID 需要指定。", "ldapErrorInvalidCustomFilter":
"ldapErrorCantPreserveGroupInheritanceWithUIDMembershipType": "无法在使用UID成员类型的同时维护组继承属性。", '定制的 LDAP过滤器不是以 "(" 开头或以 ")"结尾.',
"ldapErrorCantWriteOnlyForReadOnlyLdap": "当LDAP提供方不是可写模式时无法设置只写", "ldapErrorConnectionTimeoutNotNumber":
"Connection Timeout 必须是个数字",
"ldapErrorMissingClientId":
"当域角色映射未启用时,客户端 ID 需要指定。",
"ldapErrorCantPreserveGroupInheritanceWithUIDMembershipType":
"无法在使用UID成员类型的同时维护组继承属性。",
"ldapErrorCantWriteOnlyForReadOnlyLdap":
"当LDAP提供方不是可写模式时无法设置只写",
"ldapErrorCantWriteOnlyAndReadOnly": "无法同时设置只读和只写", "ldapErrorCantWriteOnlyAndReadOnly": "无法同时设置只读和只写",
"clientRedirectURIsFragmentError": "重定向URL不应包含URI片段", "clientRedirectURIsFragmentError": "重定向URL不应包含URI片段",
"clientRootURLFragmentError": "根URL 不应包含 URL 片段", "clientRootURLFragmentError": "根URL 不应包含 URL 片段",
"pairwiseMalformedClientRedirectURI": "客户端包含一个无效的重定向URL", "pairwiseMalformedClientRedirectURI": "客户端包含一个无效的重定向URL",
"pairwiseClientRedirectURIsMissingHost": "客户端重定向URL需要有一个有效的主机", "pairwiseClientRedirectURIsMissingHost":
"pairwiseClientRedirectURIsMultipleHosts": "Without a configured Sector Identifier URI, client redirect URIs must not contain multiple host components.", "客户端重定向URL需要有一个有效的主机",
"pairwiseMalformedSectorIdentifierURI": "Malformed Sector Identifier URI.", "pairwiseClientRedirectURIsMultipleHosts":
"Without a configured Sector Identifier URI, client redirect URIs must not contain multiple host components.",
"pairwiseMalformedSectorIdentifierURI":
"Malformed Sector Identifier URI.",
"pairwiseFailedToGetRedirectURIs": "无法从服务器获得重定向URL", "pairwiseFailedToGetRedirectURIs": "无法从服务器获得重定向URL",
"pairwiseRedirectURIsMismatch": "客户端的重定向URI与服务器端获取的URI配置不匹配。" "pairwiseRedirectURIsMismatch":
} "客户端的重定向URI与服务器端获取的URI配置不匹配。",
},
}; };
/* spell-checker: enable */ /* spell-checker: enable */

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -4,78 +4,135 @@
/* spell-checker: disable */ /* spell-checker: disable */
export const kcMessages = { export const kcMessages = {
"ca": { "ca": {
"invalidPasswordHistoryMessage": "Contrasenya incorrecta: no pot ser igual a cap de les últimes {0} contrasenyes.", "invalidPasswordHistoryMessage":
"invalidPasswordMinDigitsMessage": "Contraseña incorrecta: debe contener al menos {0} caracteres numéricos.", "Contrasenya incorrecta: no pot ser igual a cap de les últimes {0} contrasenyes.",
"invalidPasswordMinLengthMessage": "Contrasenya incorrecta: longitud mínima {0}.", "invalidPasswordMinDigitsMessage":
"invalidPasswordMinLowerCaseCharsMessage": "Contrasenya incorrecta: ha de contenir almenys {0} lletres minúscules.", "Contraseña incorrecta: debe contener al menos {0} caracteres numéricos.",
"invalidPasswordMinSpecialCharsMessage": "Contrasenya incorrecta: ha de contenir almenys {0} caràcters especials.", "invalidPasswordMinLengthMessage":
"invalidPasswordMinUpperCaseCharsMessage": "Contrasenya incorrecta: ha de contenir almenys {0} lletres majúscules.", "Contrasenya incorrecta: longitud mínima {0}.",
"invalidPasswordNotUsernameMessage": "Contrasenya incorrecta: no pot ser igual al nom d'usuari.", "invalidPasswordMinLowerCaseCharsMessage":
"invalidPasswordRegexPatternMessage": "Contrasenya incorrecta: no compleix l'expressió regular." "Contrasenya incorrecta: ha de contenir almenys {0} lletres minúscules.",
"invalidPasswordMinSpecialCharsMessage":
"Contrasenya incorrecta: ha de contenir almenys {0} caràcters especials.",
"invalidPasswordMinUpperCaseCharsMessage":
"Contrasenya incorrecta: ha de contenir almenys {0} lletres majúscules.",
"invalidPasswordNotUsernameMessage":
"Contrasenya incorrecta: no pot ser igual al nom d'usuari.",
"invalidPasswordRegexPatternMessage":
"Contrasenya incorrecta: no compleix l'expressió regular.",
}, },
"de": { "de": {
"invalidPasswordMinLengthMessage": "Ungültiges Passwort: muss mindestens {0} Zeichen beinhalten.", "invalidPasswordMinLengthMessage":
"invalidPasswordMinLowerCaseCharsMessage": "Ungültiges Passwort: muss mindestens {0} Kleinbuchstaben beinhalten.", "Ungültiges Passwort: muss mindestens {0} Zeichen beinhalten.",
"invalidPasswordMinDigitsMessage": "Ungültiges Passwort: muss mindestens {0} Ziffern beinhalten.", "invalidPasswordMinLowerCaseCharsMessage":
"invalidPasswordMinUpperCaseCharsMessage": "Ungültiges Passwort: muss mindestens {0} Großbuchstaben beinhalten.", "Ungültiges Passwort: muss mindestens {0} Kleinbuchstaben beinhalten.",
"invalidPasswordMinSpecialCharsMessage": "Ungültiges Passwort: muss mindestens {0} Sonderzeichen beinhalten.", "invalidPasswordMinDigitsMessage":
"invalidPasswordNotUsernameMessage": "Ungültiges Passwort: darf nicht identisch mit dem Benutzernamen sein.", "Ungültiges Passwort: muss mindestens {0} Ziffern beinhalten.",
"invalidPasswordNotEmailMessage": "Ungültiges Passwort: darf nicht identisch mit der E-Mail-Adresse sein.", "invalidPasswordMinUpperCaseCharsMessage":
"invalidPasswordRegexPatternMessage": "Ungültiges Passwort: stimmt nicht mit Regex-Muster überein.", "Ungültiges Passwort: muss mindestens {0} Großbuchstaben beinhalten.",
"invalidPasswordHistoryMessage": "Ungültiges Passwort: darf nicht identisch mit einem der letzten {0} Passwörter sein.", "invalidPasswordMinSpecialCharsMessage":
"invalidPasswordBlacklistedMessage": "Ungültiges Passwort: Passwort ist zu bekannt und auf der schwarzen Liste.", "Ungültiges Passwort: muss mindestens {0} Sonderzeichen beinhalten.",
"invalidPasswordGenericMessage": "Ungültiges Passwort: neues Passwort erfüllt die Passwort-Anforderungen nicht." "invalidPasswordNotUsernameMessage":
"Ungültiges Passwort: darf nicht identisch mit dem Benutzernamen sein.",
"invalidPasswordNotEmailMessage":
"Ungültiges Passwort: darf nicht identisch mit der E-Mail-Adresse sein.",
"invalidPasswordRegexPatternMessage":
"Ungültiges Passwort: stimmt nicht mit Regex-Muster überein.",
"invalidPasswordHistoryMessage":
"Ungültiges Passwort: darf nicht identisch mit einem der letzten {0} Passwörter sein.",
"invalidPasswordBlacklistedMessage":
"Ungültiges Passwort: Passwort ist zu bekannt und auf der schwarzen Liste.",
"invalidPasswordGenericMessage":
"Ungültiges Passwort: neues Passwort erfüllt die Passwort-Anforderungen nicht.",
}, },
"en": { "en": {
"invalidPasswordMinLengthMessage": "Invalid password: minimum length {0}.", "invalidPasswordMinLengthMessage":
"invalidPasswordMaxLengthMessage": "Invalid password: maximum length {0}.", "Invalid password: minimum length {0}.",
"invalidPasswordMinLowerCaseCharsMessage": "Invalid password: must contain at least {0} lower case characters.", "invalidPasswordMaxLengthMessage":
"invalidPasswordMinDigitsMessage": "Invalid password: must contain at least {0} numerical digits.", "Invalid password: maximum length {0}.",
"invalidPasswordMinUpperCaseCharsMessage": "Invalid password: must contain at least {0} upper case characters.", "invalidPasswordMinLowerCaseCharsMessage":
"invalidPasswordMinSpecialCharsMessage": "Invalid password: must contain at least {0} special characters.", "Invalid password: must contain at least {0} lower case characters.",
"invalidPasswordNotUsernameMessage": "Invalid password: must not be equal to the username.", "invalidPasswordMinDigitsMessage":
"invalidPasswordNotEmailMessage": "Invalid password: must not be equal to the email.", "Invalid password: must contain at least {0} numerical digits.",
"invalidPasswordRegexPatternMessage": "Invalid password: fails to match regex pattern(s).", "invalidPasswordMinUpperCaseCharsMessage":
"invalidPasswordHistoryMessage": "Invalid password: must not be equal to any of last {0} passwords.", "Invalid password: must contain at least {0} upper case characters.",
"invalidPasswordBlacklistedMessage": "Invalid password: password is blacklisted.", "invalidPasswordMinSpecialCharsMessage":
"invalidPasswordGenericMessage": "Invalid password: new password does not match password policies.", "Invalid password: must contain at least {0} special characters.",
"ldapErrorInvalidCustomFilter": "Custom configured LDAP filter does not start with \"(\" or does not end with \")\".", "invalidPasswordNotUsernameMessage":
"ldapErrorConnectionTimeoutNotNumber": "Connection Timeout must be a number", "Invalid password: must not be equal to the username.",
"invalidPasswordNotEmailMessage":
"Invalid password: must not be equal to the email.",
"invalidPasswordRegexPatternMessage":
"Invalid password: fails to match regex pattern(s).",
"invalidPasswordHistoryMessage":
"Invalid password: must not be equal to any of last {0} passwords.",
"invalidPasswordBlacklistedMessage":
"Invalid password: password is blacklisted.",
"invalidPasswordGenericMessage":
"Invalid password: new password does not match password policies.",
"ldapErrorInvalidCustomFilter":
'Custom configured LDAP filter does not start with "(" or does not end with ")".',
"ldapErrorConnectionTimeoutNotNumber":
"Connection Timeout must be a number",
"ldapErrorReadTimeoutNotNumber": "Read Timeout must be a number", "ldapErrorReadTimeoutNotNumber": "Read Timeout must be a number",
"ldapErrorMissingClientId": "Client ID needs to be provided in config when Realm Roles Mapping is not used.", "ldapErrorMissingClientId":
"ldapErrorCantPreserveGroupInheritanceWithUIDMembershipType": "Not possible to preserve group inheritance and use UID membership type together.", "Client ID needs to be provided in config when Realm Roles Mapping is not used.",
"ldapErrorCantWriteOnlyForReadOnlyLdap": "Can not set write only when LDAP provider mode is not WRITABLE", "ldapErrorCantPreserveGroupInheritanceWithUIDMembershipType":
"ldapErrorCantWriteOnlyAndReadOnly": "Can not set write-only and read-only together", "Not possible to preserve group inheritance and use UID membership type together.",
"ldapErrorCantEnableStartTlsAndConnectionPooling": "Can not enable both StartTLS and connection pooling.", "ldapErrorCantWriteOnlyForReadOnlyLdap":
"ldapErrorCantEnableUnsyncedAndImportOff": "Can not disable Importing users when LDAP provider mode is UNSYNCED", "Can not set write only when LDAP provider mode is not WRITABLE",
"ldapErrorMissingGroupsPathGroup": "Groups path group does not exist - please create the group on specified path first", "ldapErrorCantWriteOnlyAndReadOnly":
"clientRedirectURIsFragmentError": "Redirect URIs must not contain an URI fragment", "Can not set write-only and read-only together",
"clientRootURLFragmentError": "Root URL must not contain an URL fragment", "ldapErrorCantEnableStartTlsAndConnectionPooling":
"Can not enable both StartTLS and connection pooling.",
"ldapErrorCantEnableUnsyncedAndImportOff":
"Can not disable Importing users when LDAP provider mode is UNSYNCED",
"ldapErrorMissingGroupsPathGroup":
"Groups path group does not exist - please create the group on specified path first",
"clientRedirectURIsFragmentError":
"Redirect URIs must not contain an URI fragment",
"clientRootURLFragmentError":
"Root URL must not contain an URL fragment",
"clientRootURLIllegalSchemeError": "Root URL uses an illegal scheme", "clientRootURLIllegalSchemeError": "Root URL uses an illegal scheme",
"clientBaseURLIllegalSchemeError": "Base URL uses an illegal scheme", "clientBaseURLIllegalSchemeError": "Base URL uses an illegal scheme",
"backchannelLogoutUrlIllegalSchemeError": "Backchannel logout URL uses an illegal scheme", "backchannelLogoutUrlIllegalSchemeError":
"clientRedirectURIsIllegalSchemeError": "A redirect URI uses an illegal scheme", "Backchannel logout URL uses an illegal scheme",
"clientRedirectURIsIllegalSchemeError":
"A redirect URI uses an illegal scheme",
"clientBaseURLInvalid": "Base URL is not a valid URL", "clientBaseURLInvalid": "Base URL is not a valid URL",
"clientRootURLInvalid": "Root URL is not a valid URL", "clientRootURLInvalid": "Root URL is not a valid URL",
"clientRedirectURIsInvalid": "A redirect URI is not a valid URI", "clientRedirectURIsInvalid": "A redirect URI is not a valid URI",
"backchannelLogoutUrlIsInvalid": "Backchannel logout URL is not a valid URL", "backchannelLogoutUrlIsInvalid":
"pairwiseMalformedClientRedirectURI": "Client contained an invalid redirect URI.", "Backchannel logout URL is not a valid URL",
"pairwiseClientRedirectURIsMissingHost": "Client redirect URIs must contain a valid host component.", "pairwiseMalformedClientRedirectURI":
"pairwiseClientRedirectURIsMultipleHosts": "Without a configured Sector Identifier URI, client redirect URIs must not contain multiple host components.", "Client contained an invalid redirect URI.",
"pairwiseMalformedSectorIdentifierURI": "Malformed Sector Identifier URI.", "pairwiseClientRedirectURIsMissingHost":
"pairwiseFailedToGetRedirectURIs": "Failed to get redirect URIs from the Sector Identifier URI.", "Client redirect URIs must contain a valid host component.",
"pairwiseRedirectURIsMismatch": "Client redirect URIs does not match redirect URIs fetched from the Sector Identifier URI.", "pairwiseClientRedirectURIsMultipleHosts":
"Without a configured Sector Identifier URI, client redirect URIs must not contain multiple host components.",
"pairwiseMalformedSectorIdentifierURI":
"Malformed Sector Identifier URI.",
"pairwiseFailedToGetRedirectURIs":
"Failed to get redirect URIs from the Sector Identifier URI.",
"pairwiseRedirectURIsMismatch":
"Client redirect URIs does not match redirect URIs fetched from the Sector Identifier URI.",
"error-invalid-value": "Invalid value.", "error-invalid-value": "Invalid value.",
"error-invalid-blank": "Please specify value.", "error-invalid-blank": "Please specify value.",
"error-empty": "Please specify value.", "error-empty": "Please specify value.",
"error-invalid-length": "Attribute {0} must have a length between {1} and {2}.", "error-invalid-length":
"error-invalid-length-too-short": "Attribute {0} must have minimal length of {1}.", "Attribute {0} must have a length between {1} and {2}.",
"error-invalid-length-too-long": "Attribute {0} must have maximal length of {2}.", "error-invalid-length-too-short":
"Attribute {0} must have minimal length of {1}.",
"error-invalid-length-too-long":
"Attribute {0} must have maximal length of {2}.",
"error-invalid-email": "Invalid email address.", "error-invalid-email": "Invalid email address.",
"error-invalid-number": "Invalid number.", "error-invalid-number": "Invalid number.",
"error-number-out-of-range": "Attribute {0} must be a number between {1} and {2}.", "error-number-out-of-range":
"error-number-out-of-range-too-small": "Attribute {0} must have minimal value of {1}.", "Attribute {0} must be a number between {1} and {2}.",
"error-number-out-of-range-too-big": "Attribute {0} must have maximal value of {2}.", "error-number-out-of-range-too-small":
"Attribute {0} must have minimal value of {1}.",
"error-number-out-of-range-too-big":
"Attribute {0} must have maximal value of {2}.",
"error-pattern-no-match": "Invalid value.", "error-pattern-no-match": "Invalid value.",
"error-invalid-uri": "Invalid URL.", "error-invalid-uri": "Invalid URL.",
"error-invalid-uri-scheme": "Invalid URL scheme.", "error-invalid-uri-scheme": "Invalid URL scheme.",
@ -84,186 +141,335 @@ export const kcMessages= {
"error-invalid-date": "Attribute {0} is invalid date.", "error-invalid-date": "Attribute {0} is invalid date.",
"error-user-attribute-read-only": "Attribute {0} is read only.", "error-user-attribute-read-only": "Attribute {0} is read only.",
"error-username-invalid-character": "{0} contains invalid character.", "error-username-invalid-character": "{0} contains invalid character.",
"error-person-name-invalid-character": "{0} contains invalid character." "error-person-name-invalid-character":
"{0} contains invalid character.",
}, },
"es": { "es": {
"invalidPasswordMinLengthMessage": "Contraseña incorrecta: longitud mínima {0}.", "invalidPasswordMinLengthMessage":
"invalidPasswordMinLowerCaseCharsMessage": "Contraseña incorrecta: debe contener al menos {0} letras minúsculas.", "Contraseña incorrecta: longitud mínima {0}.",
"invalidPasswordMinDigitsMessage": "Contraseña incorrecta: debe contener al menos {0} caracteres numéricos.", "invalidPasswordMinLowerCaseCharsMessage":
"invalidPasswordMinUpperCaseCharsMessage": "Contraseña incorrecta: debe contener al menos {0} letras mayúsculas.", "Contraseña incorrecta: debe contener al menos {0} letras minúsculas.",
"invalidPasswordMinSpecialCharsMessage": "Contraseña incorrecta: debe contener al menos {0} caracteres especiales.", "invalidPasswordMinDigitsMessage":
"invalidPasswordNotUsernameMessage": "Contraseña incorrecta: no puede ser igual al nombre de usuario.", "Contraseña incorrecta: debe contener al menos {0} caracteres numéricos.",
"invalidPasswordRegexPatternMessage": "Contraseña incorrecta: no cumple la expresión regular.", "invalidPasswordMinUpperCaseCharsMessage":
"invalidPasswordHistoryMessage": "Contraseña incorrecta: no puede ser igual a ninguna de las últimas {0} contrasas." "Contraseña incorrecta: debe contener al menos {0} letras mayúsculas.",
"invalidPasswordMinSpecialCharsMessage":
"Contraseña incorrecta: debe contener al menos {0} caracteres especiales.",
"invalidPasswordNotUsernameMessage":
"Contraseña incorrecta: no puede ser igual al nombre de usuario.",
"invalidPasswordRegexPatternMessage":
"Contraseña incorrecta: no cumple la expresión regular.",
"invalidPasswordHistoryMessage":
"Contraseña incorrecta: no puede ser igual a ninguna de las últimas {0} contraseñas.",
}, },
"fr": { "fr": {
"invalidPasswordMinLengthMessage": "Mot de passe invalide : longueur minimale requise de {0}.", "invalidPasswordMinLengthMessage":
"invalidPasswordMinLowerCaseCharsMessage": "Mot de passe invalide : doit contenir au moins {0} lettre(s) en minuscule.", "Mot de passe invalide : longueur minimale requise de {0}.",
"invalidPasswordMinDigitsMessage": "Mot de passe invalide : doit contenir au moins {0} chiffre(s).", "invalidPasswordMinLowerCaseCharsMessage":
"invalidPasswordMinUpperCaseCharsMessage": "Mot de passe invalide : doit contenir au moins {0} lettre(s) en majuscule.", "Mot de passe invalide : doit contenir au moins {0} lettre(s) en minuscule.",
"invalidPasswordMinSpecialCharsMessage": "Mot de passe invalide : doit contenir au moins {0} caractère(s) spéciaux.", "invalidPasswordMinDigitsMessage":
"invalidPasswordNotUsernameMessage": "Mot de passe invalide : ne doit pas être identique au nom d'utilisateur.", "Mot de passe invalide : doit contenir au moins {0} chiffre(s).",
"invalidPasswordRegexPatternMessage": "Mot de passe invalide : ne valide pas l'expression rationnelle.", "invalidPasswordMinUpperCaseCharsMessage":
"invalidPasswordHistoryMessage": "Mot de passe invalide : ne doit pas être égal aux {0} derniers mot de passe." "Mot de passe invalide : doit contenir au moins {0} lettre(s) en majuscule.",
"invalidPasswordMinSpecialCharsMessage":
"Mot de passe invalide : doit contenir au moins {0} caractère(s) spéciaux.",
"invalidPasswordNotUsernameMessage":
"Mot de passe invalide : ne doit pas être identique au nom d'utilisateur.",
"invalidPasswordRegexPatternMessage":
"Mot de passe invalide : ne valide pas l'expression rationnelle.",
"invalidPasswordHistoryMessage":
"Mot de passe invalide : ne doit pas être égal aux {0} derniers mot de passe.",
}, },
"it": {}, "it": {},
"ja": { "ja": {
"invalidPasswordMinLengthMessage": "無効なパスワード: 最小{0}の長さが必要です。", "invalidPasswordMinLengthMessage":
"invalidPasswordMinLowerCaseCharsMessage": "無効なパスワード: 少なくとも{0}文字の小文字を含む必要があります。", "無効なパスワード: 最小{0}の長さが必要です。",
"invalidPasswordMinDigitsMessage": "無効なパスワード: 少なくとも{0}文字の数字を含む必要があります。", "invalidPasswordMinLowerCaseCharsMessage":
"invalidPasswordMinUpperCaseCharsMessage": "無効なパスワード: 少なくとも{0}文字の文字を含む必要があります。", "無効なパスワード: 少なくとも{0}文字の文字を含む必要があります。",
"invalidPasswordMinSpecialCharsMessage": "無効なパスワード: 少なくとも{0}文字の特殊文字を含む必要があります。", "invalidPasswordMinDigitsMessage":
"invalidPasswordNotUsernameMessage": "無効なパスワード: ユーザー名と同じパスワードは禁止されています。", "無効なパスワード: 少なくとも{0}文字の数字を含む必要があります。",
"invalidPasswordRegexPatternMessage": "無効なパスワード: 正規表現パターンと一致しません。", "invalidPasswordMinUpperCaseCharsMessage":
"invalidPasswordHistoryMessage": "無効なパスワード: 最近の{0}パスワードのいずれかと同じパスワードは禁止されています。", "無効なパスワード: 少なくとも{0}文字の大文字を含む必要があります。",
"invalidPasswordBlacklistedMessage": "無効なパスワード: パスワードがブラックリストに含まれています。", "invalidPasswordMinSpecialCharsMessage":
"invalidPasswordGenericMessage": "無効なパスワード: 新しいパスワードはパスワード・ポリシーと一致しません。", "無効なパスワード: 少なくとも{0}文字の特殊文字を含む必要があります。",
"ldapErrorInvalidCustomFilter": "LDAPフィルターのカスタム設定が、「(」から開始または「)」で終了となっていません。", "invalidPasswordNotUsernameMessage":
"ldapErrorConnectionTimeoutNotNumber": "接続タイムアウトは数字でなければなりません", "無効なパスワード: ユーザー名と同じパスワードは禁止されています。",
"ldapErrorReadTimeoutNotNumber": "読み取りタイムアウトは数字でなければなりません", "invalidPasswordRegexPatternMessage":
"ldapErrorMissingClientId": "レルムロール・マッピングを使用しない場合は、クライアントIDは設定内で提供される必要があります。", "無効なパスワード: 正規表現パターンと一致しません。",
"ldapErrorCantPreserveGroupInheritanceWithUIDMembershipType": "グループの継承を維持することと、UIDメンバーシップ・タイプを使用することは同時にできません。", "invalidPasswordHistoryMessage":
"ldapErrorCantWriteOnlyForReadOnlyLdap": "LDAPプロバイダー・モードがWRITABLEではない場合は、write onlyを設定することはできません。", "無効なパスワード: 最近の{0}パスワードのいずれかと同じパスワードは禁止されています。",
"ldapErrorCantWriteOnlyAndReadOnly": "write-onlyとread-onlyを一緒に設定することはできません。", "invalidPasswordBlacklistedMessage":
"ldapErrorCantEnableStartTlsAndConnectionPooling": "StartTLSと接続プーリングの両方を有効にできません。", "無効なパスワード: パスワードがブラックリストに含まれています。",
"clientRedirectURIsFragmentError": "リダイレクトURIにURIフラグメントを含めることはできません。", "invalidPasswordGenericMessage":
"clientRootURLFragmentError": "ルートURLにURLフラグメントを含めることはできません。", "無効なパスワード: 新しいパスワードはパスワード・ポリシーと一致しません。",
"pairwiseMalformedClientRedirectURI": "クライアントに無効なリダイレクトURIが含まれていました。", "ldapErrorInvalidCustomFilter":
"pairwiseClientRedirectURIsMissingHost": "クライアントのリダイレクトURIには有効なホスト・コンポーネントが含まれている必要があります。", "LDAPフィルターのカスタム設定が、「(」から開始または「)」で終了となっていません。",
"pairwiseClientRedirectURIsMultipleHosts": "設定されたセレクター識別子URIがない場合は、クライアントのリダイレクトURIは複数のホスト・コンポーネントを含むことはできません。", "ldapErrorConnectionTimeoutNotNumber":
"pairwiseMalformedSectorIdentifierURI": "不正なセレクター識別子URIです。", "接続タイムアウトは数字でなければなりません",
"pairwiseFailedToGetRedirectURIs": "セクター識別子URIからリダイレクトURIを取得できませんでした。", "ldapErrorReadTimeoutNotNumber":
"pairwiseRedirectURIsMismatch": "クライアントのリダイレクトURIは、セクター識別子URIからフェッチされたリダイレクトURIと一致しません" "読み取りタイムアウトは数字でなければなりません",
"ldapErrorMissingClientId":
"レルムロール・マッピングを使用しない場合は、クライアントIDは設定内で提供される必要があります。",
"ldapErrorCantPreserveGroupInheritanceWithUIDMembershipType":
"グループの継承を維持することと、UIDメンバーシップ・タイプを使用することは同時にできません。",
"ldapErrorCantWriteOnlyForReadOnlyLdap":
"LDAPプロバイダー・モードがWRITABLEではない場合は、write onlyを設定することはできません。",
"ldapErrorCantWriteOnlyAndReadOnly":
"write-onlyとread-onlyを一緒に設定することはできません。",
"ldapErrorCantEnableStartTlsAndConnectionPooling":
"StartTLSと接続プーリングの両方を有効にできません。",
"clientRedirectURIsFragmentError":
"リダイレクトURIにURIフラグメントを含めることはできません。",
"clientRootURLFragmentError":
"ルートURLにURLフラグメントを含めることはできません。",
"pairwiseMalformedClientRedirectURI":
"クライアントに無効なリダイレクトURIが含まれていました。",
"pairwiseClientRedirectURIsMissingHost":
"クライアントのリダイレクトURIには有効なホスト・コンポーネントが含まれている必要があります。",
"pairwiseClientRedirectURIsMultipleHosts":
"設定されたセレクター識別子URIがない場合は、クライアントのリダイレクトURIは複数のホスト・コンポーネントを含むことはできません。",
"pairwiseMalformedSectorIdentifierURI":
"不正なセレクター識別子URIです。",
"pairwiseFailedToGetRedirectURIs":
"セクター識別子URIからリダイレクトURIを取得できませんでした。",
"pairwiseRedirectURIsMismatch":
"クライアントのリダイレクトURIは、セクター識別子URIからフェッチされたリダイレクトURIと一致しません。",
}, },
"lt": { "lt": {
"invalidPasswordMinLengthMessage": "Per trumpas slaptažodis: mažiausias ilgis {0}.", "invalidPasswordMinLengthMessage":
"invalidPasswordMinLowerCaseCharsMessage": "Neteisingas slaptažodis: privaloma įvesti {0} mažąją raidę.", "Per trumpas slaptažodis: mažiausias ilgis {0}.",
"invalidPasswordMinDigitsMessage": "Neteisingas slaptažodis: privaloma įvesti {0} skaitmenį.", "invalidPasswordMinLowerCaseCharsMessage":
"invalidPasswordMinUpperCaseCharsMessage": "Neteisingas slaptažodis: privaloma įvesti {0} didžiąją raidę.", "Neteisingas slaptažodis: privaloma įvesti {0} mažąją raidę.",
"invalidPasswordMinSpecialCharsMessage": "Neteisingas slaptažodis: privaloma įvesti {0} specialų simbolį.", "invalidPasswordMinDigitsMessage":
"invalidPasswordNotUsernameMessage": "Neteisingas slaptažodis: slaptažodis negali sutapti su naudotojo vardu.", "Neteisingas slaptažodis: privaloma įvesti {0} skaitmenį.",
"invalidPasswordRegexPatternMessage": "Neteisingas slaptažodis: slaptažodis netenkina regex taisyklės(ių).", "invalidPasswordMinUpperCaseCharsMessage":
"invalidPasswordHistoryMessage": "Neteisingas slaptažodis: slaptažodis negali sutapti su prieš tai buvusiais {0} slaptažodžiais.", "Neteisingas slaptažodis: privaloma įvesti {0} didžiąją raidę.",
"ldapErrorInvalidCustomFilter": "Sukonfigūruotas LDAP filtras neprasideda \"(\" ir nesibaigia \")\" simboliais.", "invalidPasswordMinSpecialCharsMessage":
"ldapErrorMissingClientId": "Privaloma nurodyti kliento ID kai srities rolių susiejimas nėra nenaudojamas.", "Neteisingas slaptažodis: privaloma įvesti {0} specialų simbolį.",
"ldapErrorCantPreserveGroupInheritanceWithUIDMembershipType": "Grupių paveldėjimo ir UID narystės tipas kartu negali būti naudojami.", "invalidPasswordNotUsernameMessage":
"ldapErrorCantWriteOnlyForReadOnlyLdap": "Negalima nustatyti rašymo rėžimo kuomet LDAP teikėjo rėžimas ne WRITABLE", "Neteisingas slaptažodis: slaptažodis negali sutapti su naudotojo vardu.",
"ldapErrorCantWriteOnlyAndReadOnly": "Negalima nustatyti tik rašyti ir tik skaityti kartu", "invalidPasswordRegexPatternMessage":
"clientRedirectURIsFragmentError": "Nurodykite URI fragmentą, kurio negali būti peradresuojamuose URI adresuose", "Neteisingas slaptažodis: slaptažodis netenkina regex taisyklės(ių).",
"clientRootURLFragmentError": "Nurodykite URL fragmentą, kurio negali būti šakniniame URL adrese", "invalidPasswordHistoryMessage":
"pairwiseMalformedClientRedirectURI": "Klientas pateikė neteisingą nukreipimo nuorodą.", "Neteisingas slaptažodis: slaptažodis negali sutapti su prieš tai buvusiais {0} slaptažodžiais.",
"pairwiseClientRedirectURIsMissingHost": "Kliento nukreipimo nuorodos privalo būti nurodytos su serverio vardo komponentu.", "ldapErrorInvalidCustomFilter":
"pairwiseClientRedirectURIsMultipleHosts": "Kuomet nesukonfigūruotas sektoriaus identifikatoriaus URL, kliento nukreipimo nuorodos privalo talpinti ne daugiau kaip vieną skirtingą serverio vardo komponentą.", 'Sukonfigūruotas LDAP filtras neprasideda "(" ir nesibaigia ")" simboliais.',
"pairwiseMalformedSectorIdentifierURI": "Neteisinga sektoriaus identifikatoriaus URI.", "ldapErrorMissingClientId":
"pairwiseFailedToGetRedirectURIs": "Nepavyko gauti nukreipimo nuorodų iš sektoriaus identifikatoriaus URI.", "Privaloma nurodyti kliento ID kai srities rolių susiejimas nėra nenaudojamas.",
"pairwiseRedirectURIsMismatch": "Kliento nukreipimo nuoroda neatitinka nukreipimo nuorodų iš sektoriaus identifikatoriaus URI." "ldapErrorCantPreserveGroupInheritanceWithUIDMembershipType":
"Grupių paveldėjimo ir UID narystės tipas kartu negali būti naudojami.",
"ldapErrorCantWriteOnlyForReadOnlyLdap":
"Negalima nustatyti rašymo rėžimo kuomet LDAP teikėjo rėžimas ne WRITABLE",
"ldapErrorCantWriteOnlyAndReadOnly":
"Negalima nustatyti tik rašyti ir tik skaityti kartu",
"clientRedirectURIsFragmentError":
"Nurodykite URI fragmentą, kurio negali būti peradresuojamuose URI adresuose",
"clientRootURLFragmentError":
"Nurodykite URL fragmentą, kurio negali būti šakniniame URL adrese",
"pairwiseMalformedClientRedirectURI":
"Klientas pateikė neteisingą nukreipimo nuorodą.",
"pairwiseClientRedirectURIsMissingHost":
"Kliento nukreipimo nuorodos privalo būti nurodytos su serverio vardo komponentu.",
"pairwiseClientRedirectURIsMultipleHosts":
"Kuomet nesukonfigūruotas sektoriaus identifikatoriaus URL, kliento nukreipimo nuorodos privalo talpinti ne daugiau kaip vieną skirtingą serverio vardo komponentą.",
"pairwiseMalformedSectorIdentifierURI":
"Neteisinga sektoriaus identifikatoriaus URI.",
"pairwiseFailedToGetRedirectURIs":
"Nepavyko gauti nukreipimo nuorodų iš sektoriaus identifikatoriaus URI.",
"pairwiseRedirectURIsMismatch":
"Kliento nukreipimo nuoroda neatitinka nukreipimo nuorodų iš sektoriaus identifikatoriaus URI.",
}, },
"nl": { "nl": {
"invalidPasswordMinLengthMessage": "Ongeldig wachtwoord: de minimale lengte is {0} karakters.", "invalidPasswordMinLengthMessage":
"invalidPasswordMinLowerCaseCharsMessage": "Ongeldig wachtwoord: het moet minstens {0} kleine letters bevatten.", "Ongeldig wachtwoord: de minimale lengte is {0} karakters.",
"invalidPasswordMinDigitsMessage": "Ongeldig wachtwoord: het moet minstens {0} getallen bevatten.", "invalidPasswordMinLowerCaseCharsMessage":
"invalidPasswordMinUpperCaseCharsMessage": "Ongeldig wachtwoord: het moet minstens {0} hoofdletters bevatten.", "Ongeldig wachtwoord: het moet minstens {0} kleine letters bevatten.",
"invalidPasswordMinSpecialCharsMessage": "Ongeldig wachtwoord: het moet minstens {0} speciale karakters bevatten.", "invalidPasswordMinDigitsMessage":
"invalidPasswordNotUsernameMessage": "Ongeldig wachtwoord: het mag niet overeenkomen met de gebruikersnaam.", "Ongeldig wachtwoord: het moet minstens {0} getallen bevatten.",
"invalidPasswordRegexPatternMessage": "Ongeldig wachtwoord: het voldoet niet aan het door de beheerder ingestelde patroon.", "invalidPasswordMinUpperCaseCharsMessage":
"invalidPasswordHistoryMessage": "Ongeldig wachtwoord: het mag niet overeen komen met een van de laatste {0} wachtwoorden.", "Ongeldig wachtwoord: het moet minstens {0} hoofdletters bevatten.",
"invalidPasswordGenericMessage": "Ongeldig wachtwoord: het nieuwe wachtwoord voldoet niet aan het wachtwoordbeleid.", "invalidPasswordMinSpecialCharsMessage":
"ldapErrorInvalidCustomFilter": "LDAP filter met aangepaste configuratie start niet met \"(\" of eindigt niet met \")\".", "Ongeldig wachtwoord: het moet minstens {0} speciale karakters bevatten.",
"ldapErrorConnectionTimeoutNotNumber": "Verbindingstimeout moet een getal zijn", "invalidPasswordNotUsernameMessage":
"Ongeldig wachtwoord: het mag niet overeenkomen met de gebruikersnaam.",
"invalidPasswordRegexPatternMessage":
"Ongeldig wachtwoord: het voldoet niet aan het door de beheerder ingestelde patroon.",
"invalidPasswordHistoryMessage":
"Ongeldig wachtwoord: het mag niet overeen komen met een van de laatste {0} wachtwoorden.",
"invalidPasswordGenericMessage":
"Ongeldig wachtwoord: het nieuwe wachtwoord voldoet niet aan het wachtwoordbeleid.",
"ldapErrorInvalidCustomFilter":
'LDAP filter met aangepaste configuratie start niet met "(" of eindigt niet met ")".',
"ldapErrorConnectionTimeoutNotNumber":
"Verbindingstimeout moet een getal zijn",
"ldapErrorReadTimeoutNotNumber": "Lees-timeout moet een getal zijn", "ldapErrorReadTimeoutNotNumber": "Lees-timeout moet een getal zijn",
"ldapErrorMissingClientId": "Client ID moet ingesteld zijn als Realm Roles Mapping niet gebruikt wordt.", "ldapErrorMissingClientId":
"ldapErrorCantPreserveGroupInheritanceWithUIDMembershipType": "Kan groepsovererving niet behouden bij UID-lidmaatschapstype.", "Client ID moet ingesteld zijn als Realm Roles Mapping niet gebruikt wordt.",
"ldapErrorCantWriteOnlyForReadOnlyLdap": "Alleen-schrijven niet mogelijk als LDAP provider mode niet WRITABLE is", "ldapErrorCantPreserveGroupInheritanceWithUIDMembershipType":
"ldapErrorCantWriteOnlyAndReadOnly": "Alleen-schrijven en alleen-lezen mogen niet tegelijk ingesteld zijn", "Kan groepsovererving niet behouden bij UID-lidmaatschapstype.",
"clientRedirectURIsFragmentError": "Redirect URIs mogen geen URI fragment bevatten", "ldapErrorCantWriteOnlyForReadOnlyLdap":
"Alleen-schrijven niet mogelijk als LDAP provider mode niet WRITABLE is",
"ldapErrorCantWriteOnlyAndReadOnly":
"Alleen-schrijven en alleen-lezen mogen niet tegelijk ingesteld zijn",
"clientRedirectURIsFragmentError":
"Redirect URIs mogen geen URI fragment bevatten",
"clientRootURLFragmentError": "Root URL mag geen URL fragment bevatten", "clientRootURLFragmentError": "Root URL mag geen URL fragment bevatten",
"pairwiseMalformedClientRedirectURI": "Client heeft een ongeldige redirect URI.", "pairwiseMalformedClientRedirectURI":
"pairwiseClientRedirectURIsMissingHost": "Client redirect URIs moeten een geldige host-component bevatten.", "Client heeft een ongeldige redirect URI.",
"pairwiseClientRedirectURIsMultipleHosts": "Zonder een geconfigureerde Sector Identifier URI mogen client redirect URIs niet meerdere host componenten hebben.", "pairwiseClientRedirectURIsMissingHost":
"pairwiseMalformedSectorIdentifierURI": "Onjuist notatie in Sector Identifier URI.", "Client redirect URIs moeten een geldige host-component bevatten.",
"pairwiseFailedToGetRedirectURIs": "Kon geen redirect URIs verkrijgen van de Sector Identifier URI.", "pairwiseClientRedirectURIsMultipleHosts":
"pairwiseRedirectURIsMismatch": "Client redirect URIs komen niet overeen met redict URIs ontvangen van de Sector Identifier URI." "Zonder een geconfigureerde Sector Identifier URI mogen client redirect URIs niet meerdere host componenten hebben.",
"pairwiseMalformedSectorIdentifierURI":
"Onjuist notatie in Sector Identifier URI.",
"pairwiseFailedToGetRedirectURIs":
"Kon geen redirect URIs verkrijgen van de Sector Identifier URI.",
"pairwiseRedirectURIsMismatch":
"Client redirect URIs komen niet overeen met redict URIs ontvangen van de Sector Identifier URI.",
}, },
"no": { "no": {
"invalidPasswordMinLengthMessage": "Ugyldig passord: minimum lengde {0}.", "invalidPasswordMinLengthMessage":
"invalidPasswordMinLowerCaseCharsMessage": "Ugyldig passord: må inneholde minst {0} små bokstaver.", "Ugyldig passord: minimum lengde {0}.",
"invalidPasswordMinDigitsMessage": "Ugyldig passord: må inneholde minst {0} sifre.", "invalidPasswordMinLowerCaseCharsMessage":
"invalidPasswordMinUpperCaseCharsMessage": "Ugyldig passord: må inneholde minst {0} store bokstaver.", "Ugyldig passord: må inneholde minst {0} s bokstaver.",
"invalidPasswordMinSpecialCharsMessage": "Ugyldig passord: må inneholde minst {0} spesialtegn.", "invalidPasswordMinDigitsMessage":
"invalidPasswordNotUsernameMessage": "Ugyldig passord: kan ikke være likt brukernavn.", "Ugyldig passord: må inneholde minst {0} sifre.",
"invalidPasswordRegexPatternMessage": "Ugyldig passord: tilfredsstiller ikke kravene for passord-mønster.", "invalidPasswordMinUpperCaseCharsMessage":
"invalidPasswordHistoryMessage": "Ugyldig passord: kan ikke være likt noen av de {0} foregående passordene.", "Ugyldig passord: må inneholde minst {0} store bokstaver.",
"ldapErrorInvalidCustomFilter": "Tilpasset konfigurasjon av LDAP-filter starter ikke med \"(\" eller slutter ikke med \")\".", "invalidPasswordMinSpecialCharsMessage":
"ldapErrorMissingClientId": "KlientID må være tilgjengelig i config når sikkerhetsdomenerollemapping ikke brukes.", "Ugyldig passord: må inneholde minst {0} spesialtegn.",
"ldapErrorCantPreserveGroupInheritanceWithUIDMembershipType": "Ikke mulig å bevare gruppearv og samtidig bruke UID medlemskapstype.", "invalidPasswordNotUsernameMessage":
"ldapErrorCantWriteOnlyForReadOnlyLdap": "Kan ikke sette write-only når LDAP leverandør-modus ikke er WRITABLE", "Ugyldig passord: kan ikke være likt brukernavn.",
"ldapErrorCantWriteOnlyAndReadOnly": "Kan ikke sette både write-only og read-only" "invalidPasswordRegexPatternMessage":
"Ugyldig passord: tilfredsstiller ikke kravene for passord-mønster.",
"invalidPasswordHistoryMessage":
"Ugyldig passord: kan ikke være likt noen av de {0} foregående passordene.",
"ldapErrorInvalidCustomFilter":
'Tilpasset konfigurasjon av LDAP-filter starter ikke med "(" eller slutter ikke med ")".',
"ldapErrorMissingClientId":
"KlientID må være tilgjengelig i config når sikkerhetsdomenerollemapping ikke brukes.",
"ldapErrorCantPreserveGroupInheritanceWithUIDMembershipType":
"Ikke mulig å bevare gruppearv og samtidig bruke UID medlemskapstype.",
"ldapErrorCantWriteOnlyForReadOnlyLdap":
"Kan ikke sette write-only når LDAP leverandør-modus ikke er WRITABLE",
"ldapErrorCantWriteOnlyAndReadOnly":
"Kan ikke sette både write-only og read-only",
}, },
"pl": {}, "pl": {},
"pt-BR": { "pt-BR": {
"invalidPasswordMinLengthMessage": "Senha inválida: deve conter ao menos {0} caracteres.", "invalidPasswordMinLengthMessage":
"invalidPasswordMinLowerCaseCharsMessage": "Senha inválida: deve conter ao menos {0} caracteres minúsculos.", "Senha inválida: deve conter ao menos {0} caracteres.",
"invalidPasswordMinDigitsMessage": "Senha inválida: deve conter ao menos {0} digitos numéricos.", "invalidPasswordMinLowerCaseCharsMessage":
"invalidPasswordMinUpperCaseCharsMessage": "Senha inválida: deve conter ao menos {0} caracteres maiúsculos.", "Senha inválida: deve conter ao menos {0} caracteres minúsculos.",
"invalidPasswordMinSpecialCharsMessage": "Senha inválida: deve conter ao menos {0} caracteres especiais.", "invalidPasswordMinDigitsMessage":
"invalidPasswordNotUsernameMessage": "Senha inválida: não deve ser igual ao nome de usuário.", "Senha inválida: deve conter ao menos {0} digitos numéricos.",
"invalidPasswordRegexPatternMessage": "Senha inválida: falha ao passar por padrões.", "invalidPasswordMinUpperCaseCharsMessage":
"invalidPasswordHistoryMessage": "Senha inválida: não deve ser igual às últimas {0} senhas.", "Senha inválida: deve conter ao menos {0} caracteres maiúsculos.",
"ldapErrorInvalidCustomFilter": "Filtro LDAP não inicia com \"(\" ou não termina com \")\".", "invalidPasswordMinSpecialCharsMessage":
"ldapErrorMissingClientId": "ID do cliente precisa ser definido na configuração quando mapeamentos de Roles do Realm não é utilizado.", "Senha inválida: deve conter ao menos {0} caracteres especiais.",
"ldapErrorCantPreserveGroupInheritanceWithUIDMembershipType": "Não é possível preservar herança de grupos e usar tipo de associação de UID ao mesmo tempo.", "invalidPasswordNotUsernameMessage":
"ldapErrorCantWriteOnlyForReadOnlyLdap": "Não é possível definir modo de somente escrita quando o provedor LDAP não suporta escrita", "Senha inválida: não deve ser igual ao nome de usuário.",
"ldapErrorCantWriteOnlyAndReadOnly": "Não é possível definir somente escrita e somente leitura ao mesmo tempo", "invalidPasswordRegexPatternMessage":
"clientRedirectURIsFragmentError": "URIs de redirecionamento não podem conter fragmentos", "Senha inválida: falha ao passar por padrões.",
"clientRootURLFragmentError": "URL raiz não pode conter fragmentos" "invalidPasswordHistoryMessage":
"Senha inválida: não deve ser igual às últimas {0} senhas.",
"ldapErrorInvalidCustomFilter":
'Filtro LDAP não inicia com "(" ou não termina com ")".',
"ldapErrorMissingClientId":
"ID do cliente precisa ser definido na configuração quando mapeamentos de Roles do Realm não é utilizado.",
"ldapErrorCantPreserveGroupInheritanceWithUIDMembershipType":
"Não é possível preservar herança de grupos e usar tipo de associação de UID ao mesmo tempo.",
"ldapErrorCantWriteOnlyForReadOnlyLdap":
"Não é possível definir modo de somente escrita quando o provedor LDAP não suporta escrita",
"ldapErrorCantWriteOnlyAndReadOnly":
"Não é possível definir somente escrita e somente leitura ao mesmo tempo",
"clientRedirectURIsFragmentError":
"URIs de redirecionamento não podem conter fragmentos",
"clientRootURLFragmentError": "URL raiz não pode conter fragmentos",
}, },
"ru": { "ru": {
"invalidPasswordMinLengthMessage": "Некорректный пароль: длина пароля должна быть не менее {0} символов(а).", "invalidPasswordMinLengthMessage":
"invalidPasswordMinDigitsMessage": "Некорректный пароль: должен содержать не менее {0} цифр(ы).", "Некорректный пароль: длина пароля должна быть не менее {0} символов(а).",
"invalidPasswordMinLowerCaseCharsMessage": "Некорректный пароль: пароль должен содержать не менее {0} символов(а) в нижнем регистре.", "invalidPasswordMinDigitsMessage":
"invalidPasswordMinUpperCaseCharsMessage": "Некорректный пароль: пароль должен содержать не менее {0} символов(а) в верхнем регистре.", "Некорректный пароль: должен содержать не менее {0} цифр(ы).",
"invalidPasswordMinSpecialCharsMessage": "Некорректный пароль: пароль должен содержать не менее {0} спецсимволов(а).", "invalidPasswordMinLowerCaseCharsMessage":
"invalidPasswordNotUsernameMessage": "Некорректный пароль: пароль не должен совпадать с именем пользователя.", "Некорректный пароль: пароль должен содержать не менее {0} символов(а) в нижнем регистре.",
"invalidPasswordRegexPatternMessage": "Некорректный пароль: пароль не прошел проверку по регулярному выражению.", "invalidPasswordMinUpperCaseCharsMessage":
"invalidPasswordHistoryMessage": "Некорректный пароль: пароль не должен совпадать с последним(и) {0} паролем(ями).", "Некорректный пароль: пароль должен содержать не менее {0} символов(а) в верхнем регистре.",
"invalidPasswordGenericMessage": "Некорректный пароль: новый пароль не соответствует правилам пароля.", "invalidPasswordMinSpecialCharsMessage":
"ldapErrorInvalidCustomFilter": "Сконфигурированный пользователем фильтр LDAP не должен начинаться с \"(\" или заканчиваться на \")\".", "Некорректный пароль: пароль должен содержать не менее {0} спецсимволов(а).",
"ldapErrorMissingClientId": "Client ID должен быть настроен в конфигурации, если не используется сопоставление ролей в realm.", "invalidPasswordNotUsernameMessage":
"ldapErrorCantPreserveGroupInheritanceWithUIDMembershipType": "Не удалось унаследовать группу и использовать членство UID типа вместе.", "Некорректный пароль: пароль не должен совпадать с именем пользователя.",
"ldapErrorCantWriteOnlyForReadOnlyLdap": "Невозможно установить режим \"только на запись\", когда LDAP провайдер не в режиме WRITABLE", "invalidPasswordRegexPatternMessage":
"ldapErrorCantWriteOnlyAndReadOnly": "Невозможно одновременно установить режимы \"только на чтение\" и \"только на запись\"", "Некорректный пароль: пароль не прошел проверку по регулярному выражению.",
"clientRedirectURIsFragmentError": "URI перенаправления не должен содержать фрагмент URI", "invalidPasswordHistoryMessage":
"clientRootURLFragmentError": "Корневой URL не должен содержать фрагмент URL ", "Некорректный пароль: пароль не должен совпадать с последним(и) {0} паролем(ями).",
"pairwiseMalformedClientRedirectURI": "Клиент содержит некорректный URI перенаправления.", "invalidPasswordGenericMessage":
"pairwiseClientRedirectURIsMissingHost": "URI перенаправления клиента должен содержать корректный компонент хоста.", "Некорректный пароль: новый пароль не соответствует правилам пароля.",
"pairwiseClientRedirectURIsMultipleHosts": "Без конфигурации по части идентификатора URI, URI перенаправления клиента не может содержать несколько компонентов хоста.", "ldapErrorInvalidCustomFilter":
"pairwiseMalformedSectorIdentifierURI": "Искаженная часть идентификатора URI.", 'Сконфигурированный пользователем фильтр LDAP не должен начинаться с "(" или заканчиваться на ")".',
"pairwiseFailedToGetRedirectURIs": "Не удалось получить идентификаторы URI перенаправления из части идентификатора URI.", "ldapErrorMissingClientId":
"pairwiseRedirectURIsMismatch": "Клиент URI переадресации не соответствует URI переадресации, полученной из части идентификатора URI." "Client ID должен быть настроен в конфигурации, если не используется сопоставление ролей в realm.",
"ldapErrorCantPreserveGroupInheritanceWithUIDMembershipType":
"Не удалось унаследовать группу и использовать членство UID типа вместе.",
"ldapErrorCantWriteOnlyForReadOnlyLdap":
'Невозможно установить режим "только на запись", когда LDAP провайдер не в режиме WRITABLE',
"ldapErrorCantWriteOnlyAndReadOnly":
'Невозможно одновременно установить режимы "только на чтение" и "только на запись"',
"clientRedirectURIsFragmentError":
"URI перенаправления не должен содержать фрагмент URI",
"clientRootURLFragmentError":
"Корневой URL не должен содержать фрагмент URL ",
"pairwiseMalformedClientRedirectURI":
"Клиент содержит некорректный URI перенаправления.",
"pairwiseClientRedirectURIsMissingHost":
"URI перенаправления клиента должен содержать корректный компонент хоста.",
"pairwiseClientRedirectURIsMultipleHosts":
"Без конфигурации по части идентификатора URI, URI перенаправления клиента не может содержать несколько компонентов хоста.",
"pairwiseMalformedSectorIdentifierURI":
"Искаженная часть идентификатора URI.",
"pairwiseFailedToGetRedirectURIs":
"Не удалось получить идентификаторы URI перенаправления из части идентификатора URI.",
"pairwiseRedirectURIsMismatch":
"Клиент URI переадресации не соответствует URI переадресации, полученной из части идентификатора URI.",
}, },
"zh-CN": { "zh-CN": {
"invalidPasswordMinLengthMessage": "无效的密码:最短长度 {0}.", "invalidPasswordMinLengthMessage": "无效的密码:最短长度 {0}.",
"invalidPasswordMinLowerCaseCharsMessage": "无效的密码:至少包含 {0} 小写字母", "invalidPasswordMinLowerCaseCharsMessage":
"无效的密码:至少包含 {0} 小写字母",
"invalidPasswordMinDigitsMessage": "无效的密码:至少包含 {0} 个数字", "invalidPasswordMinDigitsMessage": "无效的密码:至少包含 {0} 个数字",
"invalidPasswordMinUpperCaseCharsMessage": "无效的密码:最短长度 {0} 大写字母", "invalidPasswordMinUpperCaseCharsMessage":
"invalidPasswordMinSpecialCharsMessage": "无效的密码:最短长度 {0} 特殊字符", "无效的密码:最短长度 {0} 大写字母",
"invalidPasswordMinSpecialCharsMessage":
"无效的密码:最短长度 {0} 特殊字符",
"invalidPasswordNotUsernameMessage": "无效的密码: 不可以与用户名相同", "invalidPasswordNotUsernameMessage": "无效的密码: 不可以与用户名相同",
"invalidPasswordRegexPatternMessage": "无效的密码: 无法与正则表达式匹配", "invalidPasswordRegexPatternMessage":
"invalidPasswordHistoryMessage": "无效的密码:不能与最后使用的 {0} 个密码相同", "无效的密码: 无法与正则表达式匹配",
"ldapErrorInvalidCustomFilter": "定制的 LDAP过滤器不是以 \"(\" 开头或以 \")\"结尾.", "invalidPasswordHistoryMessage":
"ldapErrorConnectionTimeoutNotNumber": "Connection Timeout 必须是个数字", "无效的密码:不能与最后使用的 {0} 个密码相同",
"ldapErrorMissingClientId": "当域角色映射未启用时,客户端 ID 需要指定。", "ldapErrorInvalidCustomFilter":
"ldapErrorCantPreserveGroupInheritanceWithUIDMembershipType": "无法在使用UID成员类型的同时维护组继承属性。", '定制的 LDAP过滤器不是以 "(" 开头或以 ")"结尾.',
"ldapErrorCantWriteOnlyForReadOnlyLdap": "当LDAP提供方不是可写模式时无法设置只写", "ldapErrorConnectionTimeoutNotNumber":
"Connection Timeout 必须是个数字",
"ldapErrorMissingClientId":
"当域角色映射未启用时,客户端 ID 需要指定。",
"ldapErrorCantPreserveGroupInheritanceWithUIDMembershipType":
"无法在使用UID成员类型的同时维护组继承属性。",
"ldapErrorCantWriteOnlyForReadOnlyLdap":
"当LDAP提供方不是可写模式时无法设置只写",
"ldapErrorCantWriteOnlyAndReadOnly": "无法同时设置只读和只写", "ldapErrorCantWriteOnlyAndReadOnly": "无法同时设置只读和只写",
"clientRedirectURIsFragmentError": "重定向URL不应包含URI片段", "clientRedirectURIsFragmentError": "重定向URL不应包含URI片段",
"clientRootURLFragmentError": "根URL 不应包含 URL 片段", "clientRootURLFragmentError": "根URL 不应包含 URL 片段",
"pairwiseMalformedClientRedirectURI": "客户端包含一个无效的重定向URL", "pairwiseMalformedClientRedirectURI": "客户端包含一个无效的重定向URL",
"pairwiseClientRedirectURIsMissingHost": "客户端重定向URL需要有一个有效的主机", "pairwiseClientRedirectURIsMissingHost":
"pairwiseClientRedirectURIsMultipleHosts": "Without a configured Sector Identifier URI, client redirect URIs must not contain multiple host components.", "客户端重定向URL需要有一个有效的主机",
"pairwiseMalformedSectorIdentifierURI": "Malformed Sector Identifier URI.", "pairwiseClientRedirectURIsMultipleHosts":
"Without a configured Sector Identifier URI, client redirect URIs must not contain multiple host components.",
"pairwiseMalformedSectorIdentifierURI":
"Malformed Sector Identifier URI.",
"pairwiseFailedToGetRedirectURIs": "无法从服务器获得重定向URL", "pairwiseFailedToGetRedirectURIs": "无法从服务器获得重定向URL",
"pairwiseRedirectURIsMismatch": "客户端的重定向URI与服务器端获取的URI配置不匹配。" "pairwiseRedirectURIsMismatch":
} "客户端的重定向URI与服务器端获取的URI配置不匹配。",
},
}; };
/* spell-checker: enable */ /* spell-checker: enable */

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,3 @@
import { kcMessages } from "../generated_kcMessages/15.0.2/login"; import { kcMessages } from "../generated_kcMessages/15.0.2/login";
import { Evt } from "evt"; import { Evt } from "evt";
import { objectKeys } from "tsafe/objectKeys"; import { objectKeys } from "tsafe/objectKeys";
@ -11,8 +10,8 @@ export const evtTermsUpdated = Evt.asNonPostable(Evt.create<void>());
kcMessages[kcLanguage], kcMessages[kcLanguage],
key, key,
(() => { (() => {
let value =
let value = key === "termsText" ? "⏳" : kcMessages[kcLanguage][key]; key === "termsText" ? "⏳" : kcMessages[kcLanguage][key];
return { return {
"enumerable": true, "enumerable": true,
@ -20,14 +19,11 @@ export const evtTermsUpdated = Evt.asNonPostable(Evt.create<void>());
"set": (newValue: string) => { "set": (newValue: string) => {
value = newValue; value = newValue;
Evt.asPostable(evtTermsUpdated).post(); Evt.asPostable(evtTermsUpdated).post();
} },
}; };
})(),
),
})() ),
)
)
); );
export { kcMessages }; export { kcMessages };

View File

@ -1,35 +1,26 @@
import { createUseGlobalState } from "powerhooks/useGlobalState"; import { createUseGlobalState } from "powerhooks/useGlobalState";
import { getKcContext } from "../getKcContext"; import { getKcContext } from "../getKcContext";
import { getBestMatchAmongKcLanguageTag } from "./KcLanguageTag"; import { getBestMatchAmongKcLanguageTag } from "./KcLanguageTag";
import type { StatefulEvt } from "powerhooks"; import type { StatefulEvt } from "powerhooks";
import { KcLanguageTag } from "./KcLanguageTag"; import { KcLanguageTag } from "./KcLanguageTag";
//export const { useKcLanguageTag, evtKcLanguageTag } = createUseGlobalState( //export const { useKcLanguageTag, evtKcLanguageTag } = createUseGlobalState(
const wrap = createUseGlobalState( const wrap = createUseGlobalState(
"kcLanguageTag", "kcLanguageTag",
() => { () => {
const { kcContext } = getKcContext(); const { kcContext } = getKcContext();
const languageLike = const languageLike =
kcContext?.locale?.current ?? kcContext?.locale?.current ??
( (typeof navigator === "undefined" ? undefined : navigator.language);
typeof navigator === "undefined" ?
undefined :
navigator.language
);
if (languageLike === undefined) { if (languageLike === undefined) {
return "en"; return "en";
} }
return getBestMatchAmongKcLanguageTag(languageLike); return getBestMatchAmongKcLanguageTag(languageLike);
}, },
{ "persistance": "localStorage" } { "persistance": "localStorage" },
); );
export const { useKcLanguageTag } = wrap; export const { useKcLanguageTag } = wrap;
@ -37,6 +28,3 @@ export const { useKcLanguageTag } = wrap;
export function getEvtKcLanguage(): StatefulEvt<KcLanguageTag> { export function getEvtKcLanguage(): StatefulEvt<KcLanguageTag> {
return wrap.evtKcLanguageTag; return wrap.evtKcLanguageTag;
} }

View File

@ -1,4 +1,3 @@
import { useCallback, useReducer } from "react"; import { useCallback, useReducer } from "react";
import { useKcLanguageTag } from "./useKcLanguageTag"; import { useKcLanguageTag } from "./useKcLanguageTag";
import { kcMessages, evtTermsUpdated } from "./kcMessages/login"; import { kcMessages, evtTermsUpdated } from "./kcMessages/login";
@ -17,45 +16,52 @@ export type MessageKey = keyof typeof kcMessages["en"];
* during login so we can retrieve the "local" field of the JWT encoded accessToken. * during login so we can retrieve the "local" field of the JWT encoded accessToken.
*/ */
export function useKcMessage() { export function useKcMessage() {
const { kcLanguageTag } = useKcLanguageTag(); const { kcLanguageTag } = useKcLanguageTag();
const [trigger, forceUpdate] = useReducer((counter: number) => counter + 1, 0); const [trigger, forceUpdate] = useReducer(
(counter: number) => counter + 1,
0,
);
useEvt(ctx => evtTermsUpdated.attach(ctx, forceUpdate), []); useEvt(ctx => evtTermsUpdated.attach(ctx, forceUpdate), []);
const msgStr = useCallback( const msgStr = useCallback(
(key: MessageKey, ...args: (string | undefined)[]): string => { (key: MessageKey, ...args: (string | undefined)[]): string => {
let str: string =
let str: string = kcMessages[kcLanguageTag as any as "en"][key] ?? kcMessages["en"][key]; kcMessages[kcLanguageTag as any as "en"][key] ??
kcMessages["en"][key];
args.forEach((arg, i) => { args.forEach((arg, i) => {
if (arg === undefined) { if (arg === undefined) {
return; return;
} }
str = str.replace(new RegExp(`\\{${i}\\}`, "g"), arg); str = str.replace(new RegExp(`\\{${i}\\}`, "g"), arg);
}); });
return str; return str;
}, },
[kcLanguageTag, trigger] [kcLanguageTag, trigger],
); );
const msg = useCallback<(...args: Parameters<typeof msgStr>) => JSX.Element>( const msg = useCallback<
(key, ...args) => (...args: Parameters<typeof msgStr>) => JSX.Element
<ReactMarkdown allowDangerousHtml renderers={key === "termsText" ? undefined : { "paragraph": "span" }}> >(
(key, ...args) => (
<ReactMarkdown
allowDangerousHtml
renderers={
key === "termsText" ? undefined : { "paragraph": "span" }
}
>
{msgStr(key, ...args)} {msgStr(key, ...args)}
</ReactMarkdown>, </ReactMarkdown>
[msgStr] ),
[msgStr],
); );
const advancedMsg = useCallback( const advancedMsg = useCallback(
(key: string): string | undefined => { (key: string): string | undefined => {
const match = key.match(/^\$\{([^{]+)\}$/); const match = key.match(/^\$\{([^{]+)\}$/);
if (match === null) { if (match === null) {
@ -63,14 +69,14 @@ export function useKcMessage() {
} }
return ( return (
id<Record<string, string | undefined>>(kcMessages[kcLanguageTag])[key] ?? id<Record<string, string | undefined>>(
kcMessages[kcLanguageTag],
)[key] ??
id<Record<string, string | undefined>>(kcMessages["en"])[key] id<Record<string, string | undefined>>(kcMessages["en"])[key]
); );
}, },
[msgStr] [msgStr],
); );
return { msg, msgStr, advancedMsg }; return { msg, msgStr, advancedMsg };
} }

View File

@ -16,4 +16,3 @@ export * from "./components/LoginVerifyEmail";
export * from "./keycloakJsAdapter"; export * from "./keycloakJsAdapter";
export * from "./tools/assert"; export * from "./tools/assert";

View File

@ -1,18 +1,22 @@
export declare namespace keycloak_js { export declare namespace keycloak_js {
export type KeycloakPromiseCallback<T> = (result: T) => void; export type KeycloakPromiseCallback<T> = (result: T) => void;
export class KeycloakPromise<TSuccess, TError> extends Promise<TSuccess> { export class KeycloakPromise<TSuccess, TError> extends Promise<TSuccess> {
success(callback: KeycloakPromiseCallback<TSuccess>): KeycloakPromise<TSuccess, TError>; success(
error(callback: KeycloakPromiseCallback<TError>): KeycloakPromise<TSuccess, TError>; callback: KeycloakPromiseCallback<TSuccess>,
): KeycloakPromise<TSuccess, TError>;
error(
callback: KeycloakPromiseCallback<TError>,
): KeycloakPromise<TSuccess, TError>;
} }
export interface KeycloakAdapter { export interface KeycloakAdapter {
login(options?: KeycloakLoginOptions): KeycloakPromise<void, void>; login(options?: KeycloakLoginOptions): KeycloakPromise<void, void>;
logout(options?: KeycloakLogoutOptions): KeycloakPromise<void, void>; logout(options?: KeycloakLogoutOptions): KeycloakPromise<void, void>;
register(options?: KeycloakLoginOptions): KeycloakPromise<void, void>; register(options?: KeycloakLoginOptions): KeycloakPromise<void, void>;
accountManagement(): KeycloakPromise<void, void>; accountManagement(): KeycloakPromise<void, void>;
redirectUri(options: { redirectUri: string; }, encodeHash: boolean): string; redirectUri(
options: { redirectUri: string },
encodeHash: boolean,
): string;
} }
export interface KeycloakLogoutOptions { export interface KeycloakLogoutOptions {
redirectUri?: string; redirectUri?: string;
@ -20,7 +24,7 @@ export declare namespace keycloak_js {
export interface KeycloakLoginOptions { export interface KeycloakLoginOptions {
scope?: string; scope?: string;
redirectUri?: string; redirectUri?: string;
prompt?: 'none' | 'login'; prompt?: "none" | "login";
action?: string; action?: string;
maxAge?: number; maxAge?: number;
loginHint?: string; loginHint?: string;
@ -30,15 +34,12 @@ export declare namespace keycloak_js {
} }
export type KeycloakInstance = Record< export type KeycloakInstance = Record<
"createLoginUrl" | "createLoginUrl" | "createLogoutUrl" | "createRegisterUrl",
"createLogoutUrl" |
"createRegisterUrl",
(options: KeycloakLoginOptions | undefined) => string (options: KeycloakLoginOptions | undefined) => string
> & { > & {
createAccountUrl(): string; createAccountUrl(): string;
redirectUri?: string; redirectUri?: string;
} };
} }
/** /**
@ -47,56 +48,45 @@ export declare namespace keycloak_js {
* Our use case for it is to pass over the login screen the states of useGlobalState * Our use case for it is to pass over the login screen the states of useGlobalState
* namely isDarkModeEnabled, lgn... * namely isDarkModeEnabled, lgn...
*/ */
export function createKeycloakAdapter( export function createKeycloakAdapter(params: {
params: {
keycloakInstance: keycloak_js.KeycloakInstance; keycloakInstance: keycloak_js.KeycloakInstance;
transformUrlBeforeRedirect(url: string): string; transformUrlBeforeRedirect(url: string): string;
} }): keycloak_js.KeycloakAdapter {
): keycloak_js.KeycloakAdapter {
const { keycloakInstance, transformUrlBeforeRedirect } = params; const { keycloakInstance, transformUrlBeforeRedirect } = params;
const neverResolvingPromise: keycloak_js.KeycloakPromise<void, void> = Object.defineProperties( const neverResolvingPromise: keycloak_js.KeycloakPromise<void, void> =
new Promise(() => { }), Object.defineProperties(new Promise(() => {}), {
{
"success": { "value": () => {} }, "success": { "value": () => {} },
"error": { "value": () => { } } "error": { "value": () => {} },
} }) as any;
) as any;
return { return {
"login": options => { "login": options => {
window.location.href= window.location.href = transformUrlBeforeRedirect(
transformUrlBeforeRedirect( keycloakInstance.createLoginUrl(options),
keycloakInstance.createLoginUrl(
options
)
); );
return neverResolvingPromise; return neverResolvingPromise;
}, },
"logout": options => { "logout": options => {
window.location.replace( window.location.replace(
transformUrlBeforeRedirect( transformUrlBeforeRedirect(
keycloakInstance.createLogoutUrl( keycloakInstance.createLogoutUrl(options),
options ),
)
)
); );
return neverResolvingPromise; return neverResolvingPromise;
}, },
"register": options => { "register": options => {
window.location.href = window.location.href = transformUrlBeforeRedirect(
transformUrlBeforeRedirect( keycloakInstance.createRegisterUrl(options),
keycloakInstance.createRegisterUrl(
options
)
); );
return neverResolvingPromise; return neverResolvingPromise;
}, },
"accountManagement": () => { "accountManagement": () => {
var accountUrl = transformUrlBeforeRedirect(keycloakInstance.createAccountUrl()); var accountUrl = transformUrlBeforeRedirect(
if (typeof accountUrl !== 'undefined') { keycloakInstance.createAccountUrl(),
);
if (typeof accountUrl !== "undefined") {
window.location.href = accountUrl; window.location.href = accountUrl;
} else { } else {
throw new Error("Not supported by the OIDC server"); throw new Error("Not supported by the OIDC server");
@ -111,8 +101,6 @@ export function createKeycloakAdapter(
} else { } else {
return window.location.href; return window.location.href;
} }
} },
}; };
} }

View File

@ -1,35 +1,27 @@
export type AndByDiscriminatingKey< export type AndByDiscriminatingKey<
DiscriminatingKey extends string, DiscriminatingKey extends string,
U1 extends Record<DiscriminatingKey, string>, U1 extends Record<DiscriminatingKey, string>,
U2 extends Record<DiscriminatingKey, string> U2 extends Record<DiscriminatingKey, string>,
> = > = AndByDiscriminatingKey.Tf1<DiscriminatingKey, U1, U1, U2>;
AndByDiscriminatingKey.Tf1<DiscriminatingKey, U1, U1, U2>;
export declare namespace AndByDiscriminatingKey { export declare namespace AndByDiscriminatingKey {
export type Tf1< export type Tf1<
DiscriminatingKey extends string, DiscriminatingKey extends string,
U1, U1,
U1Again extends Record<DiscriminatingKey, string>, U1Again extends Record<DiscriminatingKey, string>,
U2 extends Record<DiscriminatingKey, string> U2 extends Record<DiscriminatingKey, string>,
> = > = U1 extends Pick<U2, DiscriminatingKey>
U1 extends Pick<U2, DiscriminatingKey> ? ? Tf2<DiscriminatingKey, U1, U2, U1Again>
Tf2<DiscriminatingKey, U1, U2, U1Again> : : U1;
U1;
export type Tf2< export type Tf2<
DiscriminatingKey extends string, DiscriminatingKey extends string,
SingletonU1 extends Record<DiscriminatingKey, string>, SingletonU1 extends Record<DiscriminatingKey, string>,
U2, U2,
U1 extends Record<DiscriminatingKey, string> U1 extends Record<DiscriminatingKey, string>,
> = > = U2 extends Pick<SingletonU1, DiscriminatingKey>
U2 extends Pick<SingletonU1, DiscriminatingKey> ? ? U2 & SingletonU1
U2 & SingletonU1 : : U2 extends Pick<U1, DiscriminatingKey>
U2 extends Pick<U1, DiscriminatingKey> ? ? never
never : : U2;
U2;
} }

View File

@ -1,4 +1,3 @@
export type DeepPartial<T> = { export type DeepPartial<T> = {
[P in keyof T]?: DeepPartial<T[P]>; [P in keyof T]?: DeepPartial<T[P]>;
}; };

View File

@ -1,4 +1,4 @@
/* eslint-disable @typescript-eslint/ban-types */
import type { FC, ComponentClass } from "react"; import type { FC, ComponentClass } from "react";
export type ReactComponent<Props extends Record<string, unknown> = {}> = export type ReactComponent<Props extends Record<string, unknown> = {}> =

View File

@ -1,10 +1,9 @@
import "minimal-polyfills/Object.fromEntries"; import "minimal-polyfills/Object.fromEntries";
export function allPropertiesValuesToUndefined<T extends Record<string, unknown>>(obj: T): Record<keyof T, undefined> { export function allPropertiesValuesToUndefined<
T extends Record<string, unknown>,
>(obj: T): Record<keyof T, undefined> {
return Object.fromEntries( return Object.fromEntries(
Object.entries(obj) Object.entries(obj).map(([key]) => [key, undefined]),
.map(([key]) => [key, undefined])
) as any; ) as any;
} }

View File

@ -1,23 +1,25 @@
import { Deferred } from "evt/tools/Deferred"; import { Deferred } from "evt/tools/Deferred";
export function appendHead( export function appendHead(
params: { params:
| {
type: "css"; type: "css";
href: string; href: string;
} | { }
| {
type: "javascript"; type: "javascript";
src: string; src: string;
} },
) { ) {
const htmlElement = document.createElement( const htmlElement = document.createElement(
(() => { (() => {
switch (params.type) { switch (params.type) {
case "css": return "link"; case "css":
case "javascript": return "script"; return "link";
case "javascript":
return "script";
} }
})() })(),
); );
const dLoaded = new Deferred<void>(); const dLoaded = new Deferred<void>();
@ -28,22 +30,23 @@ export function appendHead(
htmlElement, htmlElement,
(() => { (() => {
switch (params.type) { switch (params.type) {
case "css": return { case "css":
return {
"href": params.href, "href": params.href,
"type": "text/css", "type": "text/css",
"rel": "stylesheet", "rel": "stylesheet",
"media": "screen,print" "media": "screen,print",
}; };
case "javascript": return { case "javascript":
return {
"src": params.src, "src": params.src,
"type": "text/javascript", "type": "text/javascript",
}; };
} }
})() })(),
); );
document.getElementsByTagName("head")[0].appendChild(htmlElement); document.getElementsByTagName("head")[0].appendChild(htmlElement);
return dLoaded.pr; return dLoaded.pr;
} }

View File

@ -1,2 +1 @@
export { assert } from "tsafe/assert";
export { assert } from "tsafe/assert";

View File

@ -1,15 +1,11 @@
import { assert } from "tsafe/assert"; import { assert } from "tsafe/assert";
import { is } from "tsafe/is"; import { is } from "tsafe/is";
//Warning: Be mindful that because of array this is not idempotent. //Warning: Be mindful that because of array this is not idempotent.
export function deepAssign( export function deepAssign(params: {
params: {
target: Record<string, unknown>; target: Record<string, unknown>;
source: Record<string, unknown>; source: Record<string, unknown>;
} }) {
) {
const { target, source } = params; const { target, source } = params;
Object.keys(source).forEach(key => { Object.keys(source).forEach(key => {
@ -19,17 +15,12 @@ export function deepAssign(
target[key] === undefined || target[key] === undefined ||
!(dereferencedSource instanceof Object) !(dereferencedSource instanceof Object)
) { ) {
Object.defineProperty(target, key, {
Object.defineProperty(
target,
key,
{
"enumerable": true, "enumerable": true,
"writable": true, "writable": true,
"configurable": true, "configurable": true,
"value": dereferencedSource "value": dereferencedSource,
} });
);
return; return;
} }
@ -37,7 +28,6 @@ export function deepAssign(
const dereferencedTarget = target[key]; const dereferencedTarget = target[key];
if (dereferencedSource instanceof Array) { if (dereferencedSource instanceof Array) {
assert(is<unknown[]>(dereferencedTarget)); assert(is<unknown[]>(dereferencedTarget));
assert(is<unknown[]>(dereferencedSource)); assert(is<unknown[]>(dereferencedSource));
@ -51,9 +41,7 @@ export function deepAssign(
deepAssign({ deepAssign({
"target": dereferencedTarget, "target": dereferencedTarget,
"source": dereferencedSource "source": dereferencedSource,
}); });
}); });
} }

View File

@ -1,4 +1,3 @@
export function deepClone<T>(arg: T): T { export function deepClone<T>(arg: T): T {
return JSON.parse(JSON.stringify(arg)); return JSON.parse(JSON.stringify(arg));
} }

View File

@ -1,9 +1,8 @@
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";
setupSampleReactProject(); setupSampleReactProject();
@ -11,11 +10,13 @@ setupSampleReactProject();
generateKeycloakThemeResources({ generateKeycloakThemeResources({
"themeName": "keycloakify-demo-app", "themeName": "keycloakify-demo-app",
"reactAppBuildDirPath": pathJoin(sampleReactProjectDirPath, "build"), "reactAppBuildDirPath": pathJoin(sampleReactProjectDirPath, "build"),
"keycloakThemeBuildingDirPath": pathJoin(sampleReactProjectDirPath, "build_keycloak_theme"), "keycloakThemeBuildingDirPath": pathJoin(
sampleReactProjectDirPath,
"build_keycloak_theme",
),
"urlPathname": "/keycloakify-demo-app/", "urlPathname": "/keycloakify-demo-app/",
"urlOrigin": undefined, "urlOrigin": undefined,
"extraPagesId": ["my-custom-page.ftl"], "extraPagesId": ["my-custom-page.ftl"],
"extraThemeProperties": ["env=test"], "extraThemeProperties": ["env=test"],
"keycloakVersion": "11.0.3" "keycloakVersion": "11.0.3",
}); });

View File

@ -1,8 +1,6 @@
import { import {
setupSampleReactProject, setupSampleReactProject,
sampleReactProjectDirPath sampleReactProjectDirPath,
} from "./setupSampleReactProject"; } from "./setupSampleReactProject";
import * as st from "scripting-tools"; import * as st from "scripting-tools";
import { join as pathJoin } from "path"; import { join as pathJoin } from "path";
@ -15,10 +13,10 @@ const binDirPath= pathJoin(getProjectRoot(), "dist", "bin");
st.execSyncTrace( st.execSyncTrace(
//`node ${pathJoin(binDirPath, "build-keycloak-theme")} --external-assets`, //`node ${pathJoin(binDirPath, "build-keycloak-theme")} --external-assets`,
`node ${pathJoin(binDirPath, "build-keycloak-theme")}`, `node ${pathJoin(binDirPath, "build-keycloak-theme")}`,
{ "cwd": sampleReactProjectDirPath } { "cwd": sampleReactProjectDirPath },
); );
st.execSyncTrace( st.execSyncTrace(
`node ${pathJoin(binDirPath, "download-builtin-keycloak-theme")}`, `node ${pathJoin(binDirPath, "download-builtin-keycloak-theme")}`,
{ "cwd": sampleReactProjectDirPath } { "cwd": sampleReactProjectDirPath },
); );

View File

@ -1,8 +1,7 @@
import {
import { 
replaceImportsFromStaticInJsCode, replaceImportsFromStaticInJsCode,
replaceImportsInCssCode, replaceImportsInCssCode,
generateCssCodeToDefineGlobals generateCssCodeToDefineGlobals,
} from "../../bin/build-keycloak-theme/replaceImportFromStatic"; } from "../../bin/build-keycloak-theme/replaceImportFromStatic";
const { fixedJsCode } = replaceImportsFromStaticInJsCode({ const { fixedJsCode } = replaceImportsFromStaticInJsCode({
@ -19,7 +18,7 @@ const { fixedJsCode } = replaceImportsFromStaticInJsCode({
}[e] + ".chunk.js" }[e] + ".chunk.js"
} }
`, `,
"urlOrigin": undefined "urlOrigin": undefined,
}); });
const { fixedJsCode: fixedJsCodeExternal } = replaceImportsFromStaticInJsCode({ const { fixedJsCode: fixedJsCodeExternal } = replaceImportsFromStaticInJsCode({
@ -36,10 +35,10 @@ const { fixedJsCode: fixedJsCodeExternal } = replaceImportsFromStaticInJsCode({
}[e] + ".chunk.js" }[e] + ".chunk.js"
} }
`, `,
"urlOrigin": "https://www.example.com" "urlOrigin": "https://www.example.com",
}); });
console.log({ fixedJsCode, fixedJsCodeExternal }); console.log({ fixedJsCode, fixedJsCodeExternal });
const { fixedCssCode, cssGlobalsToDefine } = replaceImportsInCssCode({ const { fixedCssCode, cssGlobalsToDefine } = replaceImportsInCssCode({
"cssCode": ` "cssCode": `
@ -55,13 +54,14 @@ const { fixedCssCode, cssGlobalsToDefine } = replaceImportsInCssCode({
.my-div { .my-div {
background-image: url(/static/media/something.svg); background-image: url(/static/media/something.svg);
} }
` `,
}); });
console.log({ fixedCssCode, cssGlobalsToDefine }); console.log({ fixedCssCode, cssGlobalsToDefine });
const { cssCodeToPrependInHead } = generateCssCodeToDefineGlobals({
cssGlobalsToDefine,
"urlPathname": "/",
});
const { cssCodeToPrependInHead } = generateCssCodeToDefineGlobals({ cssGlobalsToDefine, "urlPathname": "/" }); console.log({ cssCodeToPrependInHead });
console.log({ cssCodeToPrependInHead });

View File

@ -1,14 +1,15 @@
import { getProjectRoot } from "../../bin/tools/getProjectRoot"; import { getProjectRoot } from "../../bin/tools/getProjectRoot";
import { join as pathJoin } from "path"; import { join as pathJoin } from "path";
import { downloadAndUnzip } from "../../bin/tools/downloadAndUnzip"; import { downloadAndUnzip } from "../../bin/tools/downloadAndUnzip";
export const sampleReactProjectDirPath = pathJoin(getProjectRoot(), "sample_react_project"); export const sampleReactProjectDirPath = pathJoin(
getProjectRoot(),
"sample_react_project",
);
export function setupSampleReactProject() { export function setupSampleReactProject() {
downloadAndUnzip({ downloadAndUnzip({
"url": "https://github.com/garronej/keycloakify/releases/download/v0.0.1/sample_build_dir_and_package_json.zip", "url": "https://github.com/garronej/keycloakify/releases/download/v0.0.1/sample_build_dir_and_package_json.zip",
"destDirPath": sampleReactProjectDirPath "destDirPath": sampleReactProjectDirPath,
}); });
} }

View File

@ -1,21 +1,22 @@
import { getKcContext } from "../../lib/getKcContext"; import { getKcContext } from "../../lib/getKcContext";
import type { KcContextBase } from "../../lib/getKcContext"; import type { KcContextBase } from "../../lib/getKcContext";
import type { ExtendsKcContextBase } from "../../lib/getKcContext/getKcContext"; import type { ExtendsKcContextBase } from "../../lib/getKcContext/getKcContext";
import { same } from "evt/tools/inDepth"; import { same } from "evt/tools/inDepth";
import { assert } from "tsafe/assert"; import { assert } from "tsafe/assert";
import type { Equals } from "tsafe"; import type { Equals } from "tsafe";
import { kcContextMocks, kcContextCommonMock } from "../../lib/getKcContext/kcContextMocks"; import {
kcContextMocks,
kcContextCommonMock,
} from "../../lib/getKcContext/kcContextMocks";
import { deepClone } from "../../lib/tools/deepClone"; import { deepClone } from "../../lib/tools/deepClone";
{ {
const authorizedMailDomains = [ const authorizedMailDomains = [
"example.com", "example.com",
"another-example.com", "another-example.com",
"*.yet-another-example.com", "*.yet-another-example.com",
"*.example.com", "*.example.com",
"hello-world.com" "hello-world.com",
]; ];
const displayName = "this is an overwritten common value"; const displayName = "this is an overwritten common value";
@ -23,25 +24,26 @@ import { deepClone } from "../../lib/tools/deepClone";
const aNonStandardValue1 = "a non standard value 1"; const aNonStandardValue1 = "a non standard value 1";
const aNonStandardValue2 = "a non standard value 2"; const aNonStandardValue2 = "a non standard value 2";
type KcContextExtended = { type KcContextExtended =
| {
pageId: "register.ftl"; pageId: "register.ftl";
authorizedMailDomains: string[]; authorizedMailDomains: string[];
} | { }
| {
pageId: "info.ftl"; pageId: "info.ftl";
aNonStandardValue1: string; aNonStandardValue1: string;
} | { }
| {
pageId: "my-extra-page-1.ftl"; pageId: "my-extra-page-1.ftl";
} | { }
| {
pageId: "my-extra-page-2.ftl"; pageId: "my-extra-page-2.ftl";
aNonStandardValue2: string; aNonStandardValue2: string;
}; };
const getKcContextProxy = ( const getKcContextProxy = (params: {
params: {
mockPageId: ExtendsKcContextBase<KcContextExtended>["pageId"]; mockPageId: ExtendsKcContextBase<KcContextExtended>["pageId"];
} }) => {
) => {
const { mockPageId } = params; const { mockPageId } = params;
const { kcContext } = getKcContext<KcContextExtended>({ const { kcContext } = getKcContext<KcContextExtended>({
@ -49,29 +51,27 @@ import { deepClone } from "../../lib/tools/deepClone";
"mockData": [ "mockData": [
{ {
"pageId": "login.ftl", "pageId": "login.ftl",
"realm": { displayName } "realm": { displayName },
}, },
{ {
"pageId": "info.ftl", "pageId": "info.ftl",
aNonStandardValue1 aNonStandardValue1,
}, },
{ {
"pageId": "register.ftl", "pageId": "register.ftl",
authorizedMailDomains authorizedMailDomains,
}, },
{ {
"pageId": "my-extra-page-2.ftl", "pageId": "my-extra-page-2.ftl",
aNonStandardValue2 aNonStandardValue2,
} },
] ],
}); });
return { kcContext }; return { kcContext };
}; };
{ {
const pageId = "login.ftl"; const pageId = "login.ftl";
const { kcContext } = getKcContextProxy({ "mockPageId": pageId }); const { kcContext } = getKcContextProxy({ "mockPageId": pageId });
@ -80,22 +80,25 @@ import { deepClone } from "../../lib/tools/deepClone";
assert<Equals<typeof kcContext, KcContextBase.Login>>(); assert<Equals<typeof kcContext, KcContextBase.Login>>();
assert(same( assert(
same(
//NOTE: deepClone for printIfExists or other functions... //NOTE: deepClone for printIfExists or other functions...
deepClone(kcContext), deepClone(kcContext),
(() => { (() => {
const mock = deepClone(
const mock = deepClone(kcContextMocks.find(({ pageId: pageId_i }) => pageId_i === pageId)!); kcContextMocks.find(
({ pageId: pageId_i }) => pageId_i === pageId,
)!,
);
mock.realm.displayName = displayName; mock.realm.displayName = displayName;
return mock; return mock;
})(),
})() ),
)); );
console.log(`PASS ${pageId}`); console.log(`PASS ${pageId}`);
} }
{ {
@ -106,23 +109,34 @@ import { deepClone } from "../../lib/tools/deepClone";
assert(kcContext?.pageId === pageId); assert(kcContext?.pageId === pageId);
//NOTE: I don't understand the need to add: pageId: typeof pageId; ... //NOTE: I don't understand the need to add: pageId: typeof pageId; ...
assert<Equals<typeof kcContext, KcContextBase.Info & { pageId: typeof pageId; aNonStandardValue1: string; }>>(); assert<
Equals<
typeof kcContext,
KcContextBase.Info & {
pageId: typeof pageId;
aNonStandardValue1: string;
}
>
>();
assert(same( assert(
same(
deepClone(kcContext), deepClone(kcContext),
(() => { (() => {
const mock = deepClone(
const mock = deepClone(kcContextMocks.find(({ pageId: pageId_i }) => pageId_i === pageId)!); kcContextMocks.find(
({ pageId: pageId_i }) => pageId_i === pageId,
)!,
);
Object.assign(mock, { aNonStandardValue1 }); Object.assign(mock, { aNonStandardValue1 });
return mock; return mock;
})(),
})() ),
)); );
console.log(`PASS ${pageId}`); console.log(`PASS ${pageId}`);
} }
{ {
@ -133,108 +147,128 @@ import { deepClone } from "../../lib/tools/deepClone";
assert(kcContext?.pageId === pageId); assert(kcContext?.pageId === pageId);
//NOTE: I don't understand the need to add: pageId: typeof pageId; ... //NOTE: I don't understand the need to add: pageId: typeof pageId; ...
assert<Equals<typeof kcContext, KcContextBase.Register & { pageId: typeof pageId; authorizedMailDomains: string[]; }>>(); assert<
Equals<
typeof kcContext,
KcContextBase.Register & {
pageId: typeof pageId;
authorizedMailDomains: string[];
}
>
>();
assert(same( assert(
same(
deepClone(kcContext), deepClone(kcContext),
(() => { (() => {
const mock = deepClone(
const mock = deepClone(kcContextMocks.find(({ pageId: pageId_i }) => pageId_i === pageId)!); kcContextMocks.find(
({ pageId: pageId_i }) => pageId_i === pageId,
)!,
);
Object.assign(mock, { authorizedMailDomains }); Object.assign(mock, { authorizedMailDomains });
return mock; return mock;
})(),
})() ),
)); );
console.log(`PASS ${pageId}`); console.log(`PASS ${pageId}`);
} }
{ {
const pageId = "my-extra-page-2.ftl"; const pageId = "my-extra-page-2.ftl";
const { kcContext } = getKcContextProxy({ "mockPageId": pageId }); const { kcContext } = getKcContextProxy({ "mockPageId": pageId });
assert(kcContext?.pageId === pageId); assert(kcContext?.pageId === pageId);
assert<Equals<typeof kcContext, KcContextBase.Common & { pageId: typeof pageId; aNonStandardValue2: string; }>>(); assert<
Equals<
typeof kcContext,
KcContextBase.Common & {
pageId: typeof pageId;
aNonStandardValue2: string;
}
>
>();
kcContext.aNonStandardValue2; kcContext.aNonStandardValue2;
assert(same( assert(
same(
deepClone(kcContext), deepClone(kcContext),
(() => { (() => {
const mock = deepClone(kcContextCommonMock); const mock = deepClone(kcContextCommonMock);
Object.assign(mock, { pageId, aNonStandardValue2 }); Object.assign(mock, { pageId, aNonStandardValue2 });
return mock; return mock;
})(),
})() ),
)); );
console.log(`PASS ${pageId}`); console.log(`PASS ${pageId}`);
} }
{ {
const pageId = "my-extra-page-1.ftl"; const pageId = "my-extra-page-1.ftl";
console.log("We expect a warning here =>"); console.log("We expect a warning here =>");
const { kcContext } = getKcContextProxy({ "mockPageId": pageId }); const { kcContext } = getKcContextProxy({ "mockPageId": pageId });
assert(kcContext?.pageId === pageId); assert(kcContext?.pageId === pageId);
assert<Equals<typeof kcContext, KcContextBase.Common & { pageId: typeof pageId; }>>(); assert<
Equals<
typeof kcContext,
KcContextBase.Common & { pageId: typeof pageId }
>
>();
assert(same( assert(
same(
deepClone(kcContext), deepClone(kcContext),
(() => { (() => {
const mock = deepClone(kcContextCommonMock); const mock = deepClone(kcContextCommonMock);
Object.assign(mock, { pageId }); Object.assign(mock, { pageId });
return mock; return mock;
})(),
})() ),
)); );
console.log(`PASS ${pageId}`); console.log(`PASS ${pageId}`);
} }
} }
{ {
const pageId = "login.ftl"; const pageId = "login.ftl";
const { kcContext } = getKcContext({ const { kcContext } = getKcContext({
"mockPageId": pageId "mockPageId": pageId,
}); });
assert<Equals<typeof kcContext, KcContextBase | undefined>>(); assert<Equals<typeof kcContext, KcContextBase | undefined>>();
assert(same( assert(
same(
deepClone(kcContext), deepClone(kcContext),
deepClone(kcContextMocks.find(({ pageId: pageId_i }) => pageId_i === pageId)!) deepClone(
)); kcContextMocks.find(
({ pageId: pageId_i }) => pageId_i === pageId,
)!,
),
),
);
console.log("PASS no extension"); console.log("PASS no extension");
} }
{ {
const { kcContext } = getKcContext(); const { kcContext } = getKcContext();
assert<Equals<typeof kcContext, KcContextBase | undefined>>(); assert<Equals<typeof kcContext, KcContextBase | undefined>>();
@ -242,8 +276,4 @@ import { deepClone } from "../../lib/tools/deepClone";
assert(kcContext === undefined); assert(kcContext === undefined);
console.log("PASS no extension, no mock"); console.log("PASS no extension, no mock");
} }

View File

@ -1,2 +1 @@
import "./getKcContext"; import "./getKcContext";

View File

@ -1,32 +1,30 @@
import { AndByDiscriminatingKey } from "../../../lib/tools/AndByDiscriminatingKey"; import { AndByDiscriminatingKey } from "../../../lib/tools/AndByDiscriminatingKey";
import { assert } from "tsafe/assert"; import { assert } from "tsafe/assert";
import type { Equals } from "tsafe"; import type { Equals } from "tsafe";
type Base = type Base =
{ pageId: "a"; onlyA: string; } | | { pageId: "a"; onlyA: string }
{ pageId: "b"; onlyB: string; } | | { pageId: "b"; onlyB: string }
{ pageId: "only base"; onlyBase: string; }; | { pageId: "only base"; onlyBase: string };
type Extension = type Extension =
{ pageId: "a"; onlyExtA: string; } | | { pageId: "a"; onlyExtA: string }
{ pageId: "b"; onlyExtB: string; } | | { pageId: "b"; onlyExtB: string }
{ pageId: "only ext"; onlyExt: string; }; | { pageId: "only ext"; onlyExt: string };
type Got = AndByDiscriminatingKey<"pageId", Extension, Base>; type Got = AndByDiscriminatingKey<"pageId", Extension, Base>;
type Expected = type Expected =
{ pageId: "a"; onlyA: string; onlyExtA: string; } | | { pageId: "a"; onlyA: string; onlyExtA: string }
{ pageId: "b"; onlyB: string; onlyExtB: string; } | | { pageId: "b"; onlyB: string; onlyExtB: string }
{ pageId: "only base"; onlyBase: string; } | | { pageId: "only base"; onlyBase: string }
{ pageId: "only ext"; onlyExt: string; }; | { pageId: "only ext"; onlyExt: string };
assert<Equals<Got, Expected>>(); assert<Equals<Got, Expected>>();
const x: Got = null as any; const x: Got = null as any;
if (x.pageId === "a") { if (x.pageId === "a") {
x.onlyA; x.onlyA;
x.onlyExtA; x.onlyExtA;
@ -38,12 +36,9 @@ if (x.pageId === "a") {
//@ts-expect-error //@ts-expect-error
x.onlyExt; x.onlyExt;
} }
if (x.pageId === "b") { if (x.pageId === "b") {
x.onlyB; x.onlyB;
x.onlyExtB; x.onlyExtB;
@ -55,11 +50,9 @@ if (x.pageId === "b") {
//@ts-expect-error //@ts-expect-error
x.onlyExt; x.onlyExt;
} }
if (x.pageId === "only base") { if (x.pageId === "only base") {
x.onlyBase; x.onlyBase;
//@ts-expect-error //@ts-expect-error
@ -70,14 +63,11 @@ if (x.pageId === "only base") {
//@ts-expect-error //@ts-expect-error
x.onlyExt; x.onlyExt;
} }
if (x.pageId === "only ext") { if (x.pageId === "only ext") {
x.onlyExt; x.onlyExt;
//@ts-expect-error //@ts-expect-error
x.onlyA; x.onlyA;
@ -86,6 +76,4 @@ if (x.pageId === "only ext") {
//@ts-expect-error //@ts-expect-error
x.onlyBase; x.onlyBase;
} }

644
yarn.lock
View File

@ -2,13 +2,6 @@
# yarn lockfile v1 # yarn lockfile v1
"@babel/code-frame@7.12.11":
version "7.12.11"
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.12.11.tgz#f4ad435aa263db935b8f10f2c552d23fb716a63f"
integrity sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==
dependencies:
"@babel/highlight" "^7.10.4"
"@babel/code-frame@^7.0.0": "@babel/code-frame@^7.0.0":
version "7.15.8" version "7.15.8"
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.15.8.tgz#45990c47adadb00c03677baa89221f7cc23d2503" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.15.8.tgz#45990c47adadb00c03677baa89221f7cc23d2503"
@ -21,7 +14,7 @@
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz#220df993bfe904a4a6b02ab4f3385a5ebf6e2389" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz#220df993bfe904a4a6b02ab4f3385a5ebf6e2389"
integrity sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w== integrity sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==
"@babel/highlight@^7.10.4", "@babel/highlight@^7.14.5": "@babel/highlight@^7.14.5":
version "7.14.5" version "7.14.5"
resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.14.5.tgz#6861a52f03966405001f6aa534a01a24d99e8cd9" resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.14.5.tgz#6861a52f03966405001f6aa534a01a24d99e8cd9"
integrity sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg== integrity sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==
@ -112,61 +105,6 @@
resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz#8eed982e2ee6f7f4e44c253e12962980791efd46" resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz#8eed982e2ee6f7f4e44c253e12962980791efd46"
integrity sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA== integrity sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA==
"@eslint/eslintrc@^0.4.3":
version "0.4.3"
resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.4.3.tgz#9e42981ef035beb3dd49add17acb96e8ff6f394c"
integrity sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==
dependencies:
ajv "^6.12.4"
debug "^4.1.1"
espree "^7.3.0"
globals "^13.9.0"
ignore "^4.0.6"
import-fresh "^3.2.1"
js-yaml "^3.13.1"
minimatch "^3.0.4"
strip-json-comments "^3.1.1"
"@humanwhocodes/config-array@^0.5.0":
version "0.5.0"
resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.5.0.tgz#1407967d4c6eecd7388f83acf1eaf4d0c6e58ef9"
integrity sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==
dependencies:
"@humanwhocodes/object-schema" "^1.2.0"
debug "^4.1.1"
minimatch "^3.0.4"
"@humanwhocodes/object-schema@^1.2.0":
version "1.2.0"
resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.0.tgz#87de7af9c231826fdd68ac7258f77c429e0e5fcf"
integrity sha512-wdppn25U8z/2yiaT6YGquE6X8sSv7hNMWSXYSSU1jGv/yd6XqjXgTDJ8KP4NgjTXfJ3GbRjeeb8RTV7a/VpM+w==
"@nodelib/fs.scandir@2.1.5":
version "2.1.5"
resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5"
integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==
dependencies:
"@nodelib/fs.stat" "2.0.5"
run-parallel "^1.1.9"
"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2":
version "2.0.5"
resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b"
integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==
"@nodelib/fs.walk@^1.2.3":
version "1.2.8"
resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a"
integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==
dependencies:
"@nodelib/fs.scandir" "2.1.5"
fastq "^1.6.0"
"@types/json-schema@^7.0.7":
version "7.0.9"
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.9.tgz#97edc9037ea0c38585320b28964dde3b39e4660d"
integrity sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==
"@types/mdast@^3.0.0", "@types/mdast@^3.0.3": "@types/mdast@^3.0.0", "@types/mdast@^3.0.3":
version "3.0.10" version "3.0.10"
resolved "https://registry.yarnpkg.com/@types/mdast/-/mdast-3.0.10.tgz#4724244a82a4598884cbbe9bcfd73dff927ee8af" resolved "https://registry.yarnpkg.com/@types/mdast/-/mdast-3.0.10.tgz#4724244a82a4598884cbbe9bcfd73dff927ee8af"
@ -208,86 +146,6 @@
resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.6.tgz#250a7b16c3b91f672a24552ec64678eeb1d3a08d" resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.6.tgz#250a7b16c3b91f672a24552ec64678eeb1d3a08d"
integrity sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ== integrity sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==
"@typescript-eslint/eslint-plugin@^4.24.0":
version "4.33.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.33.0.tgz#c24dc7c8069c7706bc40d99f6fa87edcb2005276"
integrity sha512-aINiAxGVdOl1eJyVjaWn/YcVAq4Gi/Yo35qHGCnqbWVz61g39D0h23veY/MA0rFFGfxK7TySg2uwDeNv+JgVpg==
dependencies:
"@typescript-eslint/experimental-utils" "4.33.0"
"@typescript-eslint/scope-manager" "4.33.0"
debug "^4.3.1"
functional-red-black-tree "^1.0.1"
ignore "^5.1.8"
regexpp "^3.1.0"
semver "^7.3.5"
tsutils "^3.21.0"
"@typescript-eslint/experimental-utils@4.33.0":
version "4.33.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-4.33.0.tgz#6f2a786a4209fa2222989e9380b5331b2810f7fd"
integrity sha512-zeQjOoES5JFjTnAhI5QY7ZviczMzDptls15GFsI6jyUOq0kOf9+WonkhtlIhh0RgHRnqj5gdNxW5j1EvAyYg6Q==
dependencies:
"@types/json-schema" "^7.0.7"
"@typescript-eslint/scope-manager" "4.33.0"
"@typescript-eslint/types" "4.33.0"
"@typescript-eslint/typescript-estree" "4.33.0"
eslint-scope "^5.1.1"
eslint-utils "^3.0.0"
"@typescript-eslint/parser@^4.24.0":
version "4.33.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-4.33.0.tgz#dfe797570d9694e560528d18eecad86c8c744899"
integrity sha512-ZohdsbXadjGBSK0/r+d87X0SBmKzOq4/S5nzK6SBgJspFo9/CUDJ7hjayuze+JK7CZQLDMroqytp7pOcFKTxZA==
dependencies:
"@typescript-eslint/scope-manager" "4.33.0"
"@typescript-eslint/types" "4.33.0"
"@typescript-eslint/typescript-estree" "4.33.0"
debug "^4.3.1"
"@typescript-eslint/scope-manager@4.33.0":
version "4.33.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.33.0.tgz#d38e49280d983e8772e29121cf8c6e9221f280a3"
integrity sha512-5IfJHpgTsTZuONKbODctL4kKuQje/bzBRkwHE8UOZ4f89Zeddg+EGZs8PD8NcN4LdM3ygHWYB3ukPAYjvl/qbQ==
dependencies:
"@typescript-eslint/types" "4.33.0"
"@typescript-eslint/visitor-keys" "4.33.0"
"@typescript-eslint/types@4.33.0":
version "4.33.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.33.0.tgz#a1e59036a3b53ae8430ceebf2a919dc7f9af6d72"
integrity sha512-zKp7CjQzLQImXEpLt2BUw1tvOMPfNoTAfb8l51evhYbOEEzdWyQNmHWWGPR6hwKJDAi+1VXSBmnhL9kyVTTOuQ==
"@typescript-eslint/typescript-estree@4.33.0":
version "4.33.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.33.0.tgz#0dfb51c2908f68c5c08d82aefeaf166a17c24609"
integrity sha512-rkWRY1MPFzjwnEVHsxGemDzqqddw2QbTJlICPD9p9I9LfsO8fdmfQPOX3uKfUaGRDFJbfrtm/sXhVXN4E+bzCA==
dependencies:
"@typescript-eslint/types" "4.33.0"
"@typescript-eslint/visitor-keys" "4.33.0"
debug "^4.3.1"
globby "^11.0.3"
is-glob "^4.0.1"
semver "^7.3.5"
tsutils "^3.21.0"
"@typescript-eslint/visitor-keys@4.33.0":
version "4.33.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.33.0.tgz#2a22f77a41604289b7a186586e9ec48ca92ef1dd"
integrity sha512-uqi/2aSz9g2ftcHWf8uLPJA70rUv6yuMW5Bohw+bwcuzaxQIHaKFZCKGoGXIrc9vkTJ3+0txM73K0Hq3d5wgIg==
dependencies:
"@typescript-eslint/types" "4.33.0"
eslint-visitor-keys "^2.0.0"
acorn-jsx@^5.3.1:
version "5.3.2"
resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937"
integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==
acorn@^7.4.0:
version "7.4.1"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa"
integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==
aggregate-error@^3.0.0: aggregate-error@^3.0.0:
version "3.1.0" version "3.1.0"
resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a" resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a"
@ -296,26 +154,6 @@ aggregate-error@^3.0.0:
clean-stack "^2.0.0" clean-stack "^2.0.0"
indent-string "^4.0.0" indent-string "^4.0.0"
ajv@^6.10.0, ajv@^6.12.4:
version "6.12.6"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4"
integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==
dependencies:
fast-deep-equal "^3.1.1"
fast-json-stable-stringify "^2.0.0"
json-schema-traverse "^0.4.1"
uri-js "^4.2.2"
ajv@^8.0.1:
version "8.6.3"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.6.3.tgz#11a66527761dc3e9a3845ea775d2d3c0414e8764"
integrity sha512-SMJOdDP6LqTkD0Uq8qLi+gMwSt0imXLSV080qFVwJCpH9U6Mb+SUGHAXM0KNbcBPguytWyvFxcHgMLe2D2XSpw==
dependencies:
fast-deep-equal "^3.1.1"
json-schema-traverse "^1.0.0"
require-from-string "^2.0.2"
uri-js "^4.2.2"
ansi-colors@^4.1.1: ansi-colors@^4.1.1:
version "4.1.1" version "4.1.1"
resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348"
@ -347,18 +185,6 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0:
dependencies: dependencies:
color-convert "^2.0.1" color-convert "^2.0.1"
argparse@^1.0.7:
version "1.0.10"
resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911"
integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==
dependencies:
sprintf-js "~1.0.2"
array-union@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d"
integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==
astral-regex@^2.0.0: astral-regex@^2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31" resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31"
@ -567,7 +393,7 @@ cosmiconfig@^7.0.0, cosmiconfig@^7.0.1:
path-type "^4.0.0" path-type "^4.0.0"
yaml "^1.10.0" yaml "^1.10.0"
cross-spawn@^7.0.2, cross-spawn@^7.0.3: cross-spawn@^7.0.3:
version "7.0.3" version "7.0.3"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==
@ -605,32 +431,13 @@ d@1, d@^1.0.1:
es5-ext "^0.10.50" es5-ext "^0.10.50"
type "^1.0.1" type "^1.0.1"
debug@^4.0.0, debug@^4.0.1, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2: debug@^4.0.0, debug@^4.3.2:
version "4.3.2" version "4.3.2"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b"
integrity sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw== integrity sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==
dependencies: dependencies:
ms "2.1.2" ms "2.1.2"
deep-is@^0.1.3:
version "0.1.4"
resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831"
integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==
dir-glob@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f"
integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==
dependencies:
path-type "^4.0.0"
doctrine@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961"
integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==
dependencies:
esutils "^2.0.2"
dom-serializer@^1.0.1, dom-serializer@^1.3.2: dom-serializer@^1.0.1, dom-serializer@^1.3.2:
version "1.3.2" version "1.3.2"
resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-1.3.2.tgz#6206437d32ceefaec7161803230c7a20bc1b4d91" resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-1.3.2.tgz#6206437d32ceefaec7161803230c7a20bc1b4d91"
@ -673,7 +480,7 @@ emoji-regex@^8.0.0:
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==
enquirer@^2.3.5, enquirer@^2.3.6: enquirer@^2.3.6:
version "2.3.6" version "2.3.6"
resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d" resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d"
integrity sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg== integrity sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==
@ -743,137 +550,6 @@ escape-string-regexp@^1.0.5:
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=
escape-string-regexp@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34"
integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==
eslint-config-prettier@^8.3.0:
version "8.3.0"
resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-8.3.0.tgz#f7471b20b6fe8a9a9254cc684454202886a2dd7a"
integrity sha512-BgZuLUSeKzvlL/VUjx/Yb787VQ26RU3gGjA3iiFvdsp/2bMfVIWUVP7tjxtjS0e+HP409cPlPvNkQloz8C91ew==
eslint-scope@^5.1.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c"
integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==
dependencies:
esrecurse "^4.3.0"
estraverse "^4.1.1"
eslint-utils@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-2.1.0.tgz#d2de5e03424e707dc10c74068ddedae708741b27"
integrity sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==
dependencies:
eslint-visitor-keys "^1.1.0"
eslint-utils@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-3.0.0.tgz#8aebaface7345bb33559db0a1f13a1d2d48c3672"
integrity sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==
dependencies:
eslint-visitor-keys "^2.0.0"
eslint-visitor-keys@^1.1.0, eslint-visitor-keys@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e"
integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==
eslint-visitor-keys@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303"
integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==
eslint@^7.26.0:
version "7.32.0"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.32.0.tgz#c6d328a14be3fb08c8d1d21e12c02fdb7a2a812d"
integrity sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==
dependencies:
"@babel/code-frame" "7.12.11"
"@eslint/eslintrc" "^0.4.3"
"@humanwhocodes/config-array" "^0.5.0"
ajv "^6.10.0"
chalk "^4.0.0"
cross-spawn "^7.0.2"
debug "^4.0.1"
doctrine "^3.0.0"
enquirer "^2.3.5"
escape-string-regexp "^4.0.0"
eslint-scope "^5.1.1"
eslint-utils "^2.1.0"
eslint-visitor-keys "^2.0.0"
espree "^7.3.1"
esquery "^1.4.0"
esutils "^2.0.2"
fast-deep-equal "^3.1.3"
file-entry-cache "^6.0.1"
functional-red-black-tree "^1.0.1"
glob-parent "^5.1.2"
globals "^13.6.0"
ignore "^4.0.6"
import-fresh "^3.0.0"
imurmurhash "^0.1.4"
is-glob "^4.0.0"
js-yaml "^3.13.1"
json-stable-stringify-without-jsonify "^1.0.1"
levn "^0.4.1"
lodash.merge "^4.6.2"
minimatch "^3.0.4"
natural-compare "^1.4.0"
optionator "^0.9.1"
progress "^2.0.0"
regexpp "^3.1.0"
semver "^7.2.1"
strip-ansi "^6.0.0"
strip-json-comments "^3.1.0"
table "^6.0.9"
text-table "^0.2.0"
v8-compile-cache "^2.0.3"
espree@^7.3.0, espree@^7.3.1:
version "7.3.1"
resolved "https://registry.yarnpkg.com/espree/-/espree-7.3.1.tgz#f2df330b752c6f55019f8bd89b7660039c1bbbb6"
integrity sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==
dependencies:
acorn "^7.4.0"
acorn-jsx "^5.3.1"
eslint-visitor-keys "^1.3.0"
esprima@^4.0.0:
version "4.0.1"
resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71"
integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==
esquery@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.4.0.tgz#2148ffc38b82e8c7057dfed48425b3e61f0f24a5"
integrity sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==
dependencies:
estraverse "^5.1.0"
esrecurse@^4.3.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921"
integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==
dependencies:
estraverse "^5.2.0"
estraverse@^4.1.1:
version "4.3.0"
resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d"
integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==
estraverse@^5.1.0, estraverse@^5.2.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.2.0.tgz#307df42547e6cc7324d3cf03c155d5cdb8c53880"
integrity sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==
esutils@^2.0.2:
version "2.0.3"
resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64"
integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==
event-emitter@^0.3.5: event-emitter@^0.3.5:
version "0.3.5" version "0.3.5"
resolved "https://registry.yarnpkg.com/event-emitter/-/event-emitter-0.3.5.tgz#df8c69eef1647923c7157b9ce83840610b02cc39" resolved "https://registry.yarnpkg.com/event-emitter/-/event-emitter-0.3.5.tgz#df8c69eef1647923c7157b9ce83840610b02cc39"
@ -918,46 +594,6 @@ extend@^3.0.0:
resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa"
integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==
fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
version "3.1.3"
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
fast-glob@^3.1.1:
version "3.2.7"
resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.7.tgz#fd6cb7a2d7e9aa7a7846111e85a196d6b2f766a1"
integrity sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q==
dependencies:
"@nodelib/fs.stat" "^2.0.2"
"@nodelib/fs.walk" "^1.2.3"
glob-parent "^5.1.2"
merge2 "^1.3.0"
micromatch "^4.0.4"
fast-json-stable-stringify@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633"
integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==
fast-levenshtein@^2.0.6:
version "2.0.6"
resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=
fastq@^1.6.0:
version "1.13.0"
resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.13.0.tgz#616760f88a7526bdfc596b7cab8c18938c36b98c"
integrity sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==
dependencies:
reusify "^1.0.4"
file-entry-cache@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027"
integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==
dependencies:
flat-cache "^3.0.4"
fill-range@^7.0.1: fill-range@^7.0.1:
version "7.0.1" version "7.0.1"
resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40"
@ -980,29 +616,11 @@ find-versions@^4.0.0:
dependencies: dependencies:
semver-regex "^3.1.2" semver-regex "^3.1.2"
flat-cache@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11"
integrity sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==
dependencies:
flatted "^3.1.0"
rimraf "^3.0.2"
flatted@^3.1.0:
version "3.2.2"
resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.2.tgz#64bfed5cb68fe3ca78b3eb214ad97b63bedce561"
integrity sha512-JaTY/wtrcSyvXJl4IMFHPKyFur1sE9AUqc0QnhOaJ0CxHtAoIV8pYDzeEfAaNEtGkOfq4gr3LBFmdXW5mOQFnA==
fs.realpath@^1.0.0: fs.realpath@^1.0.0:
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8=
functional-red-black-tree@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327"
integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=
get-caller-file@^2.0.5: get-caller-file@^2.0.5:
version "2.0.5" version "2.0.5"
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
@ -1018,13 +636,6 @@ get-stream@^6.0.0:
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7"
integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==
glob-parent@^5.1.2:
version "5.1.2"
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==
dependencies:
is-glob "^4.0.1"
glob@^7.0.5, glob@^7.1.3: glob@^7.0.5, glob@^7.1.3:
version "7.2.0" version "7.2.0"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023"
@ -1037,25 +648,6 @@ glob@^7.0.5, glob@^7.1.3:
once "^1.3.0" once "^1.3.0"
path-is-absolute "^1.0.0" path-is-absolute "^1.0.0"
globals@^13.6.0, globals@^13.9.0:
version "13.11.0"
resolved "https://registry.yarnpkg.com/globals/-/globals-13.11.0.tgz#40ef678da117fe7bd2e28f1fab24951bd0255be7"
integrity sha512-08/xrJ7wQjK9kkkRoI3OFUBbLx4f+6x3SGwcPvQ0QH6goFDrOU2oyAWrmh3dJezu65buo+HBMzAMQy6rovVC3g==
dependencies:
type-fest "^0.20.2"
globby@^11.0.3:
version "11.0.4"
resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.4.tgz#2cbaff77c2f2a62e71e9b2813a67b97a3a3001a5"
integrity sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg==
dependencies:
array-union "^2.1.0"
dir-glob "^3.0.1"
fast-glob "^3.1.1"
ignore "^5.1.4"
merge2 "^1.3.0"
slash "^3.0.0"
has-flag@^3.0.0: has-flag@^3.0.0:
version "3.0.0" version "3.0.0"
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
@ -1153,17 +745,7 @@ husky@^4.3.8:
slash "^3.0.0" slash "^3.0.0"
which-pm-runs "^1.0.0" which-pm-runs "^1.0.0"
ignore@^4.0.6: import-fresh@^3.2.1:
version "4.0.6"
resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc"
integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==
ignore@^5.1.4, ignore@^5.1.8:
version "5.1.8"
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.8.tgz#f150a8b50a34289b33e22f5889abd4d8016f0e57"
integrity sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==
import-fresh@^3.0.0, import-fresh@^3.2.1:
version "3.3.0" version "3.3.0"
resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b"
integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==
@ -1171,11 +753,6 @@ import-fresh@^3.0.0, import-fresh@^3.2.1:
parent-module "^1.0.0" parent-module "^1.0.0"
resolve-from "^4.0.0" resolve-from "^4.0.0"
imurmurhash@^0.1.4:
version "0.1.4"
resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
integrity sha1-khi5srkoojixPcT7a21XbyMUU+o=
indent-string@^4.0.0: indent-string@^4.0.0:
version "4.0.0" version "4.0.0"
resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251"
@ -1232,23 +809,11 @@ is-decimal@^1.0.0:
resolved "https://registry.yarnpkg.com/is-decimal/-/is-decimal-1.0.4.tgz#65a3a5958a1c5b63a706e1b333d7cd9f630d3fa5" resolved "https://registry.yarnpkg.com/is-decimal/-/is-decimal-1.0.4.tgz#65a3a5958a1c5b63a706e1b333d7cd9f630d3fa5"
integrity sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw== integrity sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==
is-extglob@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=
is-fullwidth-code-point@^3.0.0: is-fullwidth-code-point@^3.0.0:
version "3.0.0" version "3.0.0"
resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d"
integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==
is-glob@^4.0.0, is-glob@^4.0.1:
version "4.0.3"
resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084"
integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==
dependencies:
is-extglob "^2.1.1"
is-hexadecimal@^1.0.0: is-hexadecimal@^1.0.0:
version "1.0.4" version "1.0.4"
resolved "https://registry.yarnpkg.com/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz#cc35c97588da4bd49a8eedd6bc4082d44dcb23a7" resolved "https://registry.yarnpkg.com/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz#cc35c97588da4bd49a8eedd6bc4082d44dcb23a7"
@ -1304,42 +869,11 @@ isexe@^2.0.0:
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
js-yaml@^3.13.1:
version "3.14.1"
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537"
integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==
dependencies:
argparse "^1.0.7"
esprima "^4.0.0"
json-parse-even-better-errors@^2.3.0: json-parse-even-better-errors@^2.3.0:
version "2.3.1" version "2.3.1"
resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d"
integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==
json-schema-traverse@^0.4.1:
version "0.4.1"
resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660"
integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==
json-schema-traverse@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2"
integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==
json-stable-stringify-without-jsonify@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651"
integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=
levn@^0.4.1:
version "0.4.1"
resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade"
integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==
dependencies:
prelude-ls "^1.2.1"
type-check "~0.4.0"
lines-and-columns@^1.1.6: lines-and-columns@^1.1.6:
version "1.1.6" version "1.1.6"
resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00" resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00"
@ -1390,21 +924,6 @@ lodash.camelcase@^4.3.0:
resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6"
integrity sha1-soqmKIorn8ZRA1x3EfZathkDMaY= integrity sha1-soqmKIorn8ZRA1x3EfZathkDMaY=
lodash.clonedeep@^4.5.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef"
integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=
lodash.merge@^4.6.2:
version "4.6.2"
resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==
lodash.truncate@^4.4.2:
version "4.4.2"
resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193"
integrity sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=
log-update@^4.0.0: log-update@^4.0.0:
version "4.0.0" version "4.0.0"
resolved "https://registry.yarnpkg.com/log-update/-/log-update-4.0.0.tgz#589ecd352471f2a1c0c570287543a64dfd20e0a1" resolved "https://registry.yarnpkg.com/log-update/-/log-update-4.0.0.tgz#589ecd352471f2a1c0c570287543a64dfd20e0a1"
@ -1422,13 +941,6 @@ loose-envify@^1.1.0, loose-envify@^1.4.0:
dependencies: dependencies:
js-tokens "^3.0.0 || ^4.0.0" js-tokens "^3.0.0 || ^4.0.0"
lru-cache@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94"
integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==
dependencies:
yallist "^4.0.0"
lru-queue@^0.1.0: lru-queue@^0.1.0:
version "0.1.0" version "0.1.0"
resolved "https://registry.yarnpkg.com/lru-queue/-/lru-queue-0.1.0.tgz#2738bd9f0d3cf4f84490c5736c48699ac632cda3" resolved "https://registry.yarnpkg.com/lru-queue/-/lru-queue-0.1.0.tgz#2738bd9f0d3cf4f84490c5736c48699ac632cda3"
@ -1478,11 +990,6 @@ merge-stream@^2.0.0:
resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60"
integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==
merge2@^1.3.0:
version "1.4.1"
resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae"
integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==
micromark@~2.11.0: micromark@~2.11.0:
version "2.11.4" version "2.11.4"
resolved "https://registry.yarnpkg.com/micromark/-/micromark-2.11.4.tgz#d13436138eea826383e822449c9a5c50ee44665a" resolved "https://registry.yarnpkg.com/micromark/-/micromark-2.11.4.tgz#d13436138eea826383e822449c9a5c50ee44665a"
@ -1539,11 +1046,6 @@ multipipe@^1.0.2:
duplexer2 "^0.1.2" duplexer2 "^0.1.2"
object-assign "^4.1.0" object-assign "^4.1.0"
natural-compare@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=
next-tick@1, next-tick@^1.1.0: next-tick@1, next-tick@^1.1.0:
version "1.1.0" version "1.1.0"
resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.1.0.tgz#1836ee30ad56d67ef281b22bd199f709449b35eb" resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.1.0.tgz#1836ee30ad56d67ef281b22bd199f709449b35eb"
@ -1610,18 +1112,6 @@ opencollective-postinstall@^2.0.2:
resolved "https://registry.yarnpkg.com/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz#7a0fff978f6dbfa4d006238fbac98ed4198c3259" resolved "https://registry.yarnpkg.com/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz#7a0fff978f6dbfa4d006238fbac98ed4198c3259"
integrity sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q== integrity sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q==
optionator@^0.9.1:
version "0.9.1"
resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499"
integrity sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==
dependencies:
deep-is "^0.1.3"
fast-levenshtein "^2.0.6"
levn "^0.4.1"
prelude-ls "^1.2.1"
type-check "^0.4.0"
word-wrap "^1.2.3"
p-limit@^3.0.2: p-limit@^3.0.2:
version "3.1.0" version "3.1.0"
resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b"
@ -1741,10 +1231,10 @@ powerhooks@^0.9.3:
resize-observer-polyfill "^1.5.1" resize-observer-polyfill "^1.5.1"
tsafe "^0.4.1" tsafe "^0.4.1"
prelude-ls@^1.2.1: prettier@^2.3.0:
version "1.2.1" version "2.4.1"
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.4.1.tgz#671e11c89c14a4cfc876ce564106c4a6726c9f5c"
integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== integrity sha512-9fbDAXSBcc6Bs1mZrDYb3XKzDLm4EXXL9sC1LqKP5rZkT6KRr/rf9amVUcODVXgguK/isJz0d0hP72WeaKWsvA==
process-nextick-args@~2.0.0: process-nextick-args@~2.0.0:
version "2.0.1" version "2.0.1"
@ -1756,11 +1246,6 @@ process@^0.11.1:
resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182"
integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI= integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI=
progress@^2.0.0:
version "2.0.3"
resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8"
integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==
prop-types@^15.7.2: prop-types@^15.7.2:
version "15.7.2" version "15.7.2"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
@ -1777,16 +1262,6 @@ properties-parser@^0.3.1:
dependencies: dependencies:
string.prototype.codepointat "^0.2.0" string.prototype.codepointat "^0.2.0"
punycode@^2.1.0:
version "2.1.1"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
queue-microtask@^1.2.2:
version "1.2.3"
resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==
ramda@^0.27.1: ramda@^0.27.1:
version "0.27.1" version "0.27.1"
resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.27.1.tgz#66fc2df3ef873874ffc2da6aa8984658abacf5c9" resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.27.1.tgz#66fc2df3ef873874ffc2da6aa8984658abacf5c9"
@ -1854,11 +1329,6 @@ regenerator-runtime@^0.13.4:
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52"
integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA== integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==
regexpp@^3.1.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2"
integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==
remark-parse@^9.0.0: remark-parse@^9.0.0:
version "9.0.0" version "9.0.0"
resolved "https://registry.yarnpkg.com/remark-parse/-/remark-parse-9.0.0.tgz#4d20a299665880e4f4af5d90b7c7b8a935853640" resolved "https://registry.yarnpkg.com/remark-parse/-/remark-parse-9.0.0.tgz#4d20a299665880e4f4af5d90b7c7b8a935853640"
@ -1871,11 +1341,6 @@ require-directory@^2.1.1:
resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I=
require-from-string@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909"
integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==
resize-observer-polyfill@^1.5.1: resize-observer-polyfill@^1.5.1:
version "1.5.1" version "1.5.1"
resolved "https://registry.yarnpkg.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz#0e9020dd3d21024458d4ebd27e23e40269810464" resolved "https://registry.yarnpkg.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz#0e9020dd3d21024458d4ebd27e23e40269810464"
@ -1894,11 +1359,6 @@ restore-cursor@^3.1.0:
onetime "^5.1.0" onetime "^5.1.0"
signal-exit "^3.0.2" signal-exit "^3.0.2"
reusify@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76"
integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==
rimraf@^3.0.2: rimraf@^3.0.2:
version "3.0.2" version "3.0.2"
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a"
@ -1913,13 +1373,6 @@ run-exclusive@^2.2.14:
dependencies: dependencies:
minimal-polyfills "^2.1.5" minimal-polyfills "^2.1.5"
run-parallel@^1.1.9:
version "1.2.0"
resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee"
integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==
dependencies:
queue-microtask "^1.2.2"
rxjs@^6.6.7: rxjs@^6.6.7:
version "6.6.7" version "6.6.7"
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.7.tgz#90ac018acabf491bf65044235d5863c4dab804c9" resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.7.tgz#90ac018acabf491bf65044235d5863c4dab804c9"
@ -1947,13 +1400,6 @@ semver-regex@^3.1.2:
resolved "https://registry.yarnpkg.com/semver-regex/-/semver-regex-3.1.3.tgz#b2bcc6f97f63269f286994e297e229b6245d0dc3" resolved "https://registry.yarnpkg.com/semver-regex/-/semver-regex-3.1.3.tgz#b2bcc6f97f63269f286994e297e229b6245d0dc3"
integrity sha512-Aqi54Mk9uYTjVexLnR67rTyBusmwd04cLkHy9hNvk3+G3nT2Oyg7E0l4XVbOaNwIvQ3hHeYxGcyEy+mKreyBFQ== integrity sha512-Aqi54Mk9uYTjVexLnR67rTyBusmwd04cLkHy9hNvk3+G3nT2Oyg7E0l4XVbOaNwIvQ3hHeYxGcyEy+mKreyBFQ==
semver@^7.2.1, semver@^7.3.5:
version "7.3.5"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7"
integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==
dependencies:
lru-cache "^6.0.0"
shebang-command@^2.0.0: shebang-command@^2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea"
@ -1994,17 +1440,12 @@ slice-ansi@^4.0.0:
astral-regex "^2.0.0" astral-regex "^2.0.0"
is-fullwidth-code-point "^3.0.0" is-fullwidth-code-point "^3.0.0"
sprintf-js@~1.0.2:
version "1.0.3"
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=
string-argv@0.3.1: string-argv@0.3.1:
version "0.3.1" version "0.3.1"
resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.3.1.tgz#95e2fbec0427ae19184935f816d74aaa4c5c19da" resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.3.1.tgz#95e2fbec0427ae19184935f816d74aaa4c5c19da"
integrity sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg== integrity sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==
string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: string-width@^4.1.0, string-width@^4.2.0:
version "4.2.3" version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@ -2051,11 +1492,6 @@ strip-final-newline@^2.0.0:
resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad"
integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==
strip-json-comments@^3.1.0, strip-json-comments@^3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006"
integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==
style-to-js@1.1.0: style-to-js@1.1.0:
version "1.1.0" version "1.1.0"
resolved "https://registry.yarnpkg.com/style-to-js/-/style-to-js-1.1.0.tgz#631cbb20fce204019b3aa1fcb5b69d951ceac4ac" resolved "https://registry.yarnpkg.com/style-to-js/-/style-to-js-1.1.0.tgz#631cbb20fce204019b3aa1fcb5b69d951ceac4ac"
@ -2096,23 +1532,6 @@ supports-color@^7.1.0:
dependencies: dependencies:
has-flag "^4.0.0" has-flag "^4.0.0"
table@^6.0.9:
version "6.7.2"
resolved "https://registry.yarnpkg.com/table/-/table-6.7.2.tgz#a8d39b9f5966693ca8b0feba270a78722cbaf3b0"
integrity sha512-UFZK67uvyNivLeQbVtkiUs8Uuuxv24aSL4/Vil2PJVtMgU8Lx0CYkP12uCGa3kjyQzOSgV1+z9Wkb82fCGsO0g==
dependencies:
ajv "^8.0.1"
lodash.clonedeep "^4.5.0"
lodash.truncate "^4.4.2"
slice-ansi "^4.0.0"
string-width "^4.2.3"
strip-ansi "^6.0.1"
text-table@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=
through2@^2.0.1: through2@^2.0.1:
version "2.0.5" version "2.0.5"
resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd" resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd"
@ -2164,7 +1583,7 @@ tsafe@^0.8.1:
resolved "https://registry.yarnpkg.com/tsafe/-/tsafe-0.8.1.tgz#9af7e1540bc04313a82d60c98056a5017c8b086b" resolved "https://registry.yarnpkg.com/tsafe/-/tsafe-0.8.1.tgz#9af7e1540bc04313a82d60c98056a5017c8b086b"
integrity sha512-EfPjxQHzndQAV/uh0SMGP26Wg3dCuaw8dRv2VPEuGHen5qzg2oqsMvZw2wkQFkiMisZq2fm95m5lheimW2Fpvg== integrity sha512-EfPjxQHzndQAV/uh0SMGP26Wg3dCuaw8dRv2VPEuGHen5qzg2oqsMvZw2wkQFkiMisZq2fm95m5lheimW2Fpvg==
tslib@^1.8.1, tslib@^1.9.0: tslib@^1.9.0:
version "1.14.1" version "1.14.1"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
@ -2185,25 +1604,6 @@ tss-react@^1.0.0:
"@emotion/utils" "^1.0.0" "@emotion/utils" "^1.0.0"
html-react-parser "^1.2.7" html-react-parser "^1.2.7"
tsutils@^3.21.0:
version "3.21.0"
resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623"
integrity sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==
dependencies:
tslib "^1.8.1"
type-check@^0.4.0, type-check@~0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1"
integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==
dependencies:
prelude-ls "^1.2.1"
type-fest@^0.20.2:
version "0.20.2"
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4"
integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==
type-fest@^0.21.3: type-fest@^0.21.3:
version "0.21.3" version "0.21.3"
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37"
@ -2275,13 +1675,6 @@ untildify@^4.0.0:
resolved "https://registry.yarnpkg.com/untildify/-/untildify-4.0.0.tgz#2bc947b953652487e4600949fb091e3ae8cd919b" resolved "https://registry.yarnpkg.com/untildify/-/untildify-4.0.0.tgz#2bc947b953652487e4600949fb091e3ae8cd919b"
integrity sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw== integrity sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==
uri-js@^4.2.2:
version "4.4.1"
resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e"
integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==
dependencies:
punycode "^2.1.0"
util-deprecate@~1.0.1: util-deprecate@~1.0.1:
version "1.0.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
@ -2294,11 +1687,6 @@ util@^0.10.3:
dependencies: dependencies:
inherits "2.0.3" inherits "2.0.3"
v8-compile-cache@^2.0.3:
version "2.3.0"
resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee"
integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==
vfile-message@^2.0.0: vfile-message@^2.0.0:
version "2.0.4" version "2.0.4"
resolved "https://registry.yarnpkg.com/vfile-message/-/vfile-message-2.0.4.tgz#5b43b88171d409eae58477d13f23dd41d52c371a" resolved "https://registry.yarnpkg.com/vfile-message/-/vfile-message-2.0.4.tgz#5b43b88171d409eae58477d13f23dd41d52c371a"
@ -2329,11 +1717,6 @@ which@^2.0.1:
dependencies: dependencies:
isexe "^2.0.0" isexe "^2.0.0"
word-wrap@^1.2.3:
version "1.2.3"
resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c"
integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==
wrap-ansi@^6.2.0: wrap-ansi@^6.2.0:
version "6.2.0" version "6.2.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53"
@ -2374,11 +1757,6 @@ y18n@^5.0.5:
resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55"
integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==
yallist@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
yaml@^1.10.0: yaml@^1.10.0:
version "1.10.2" version "1.10.2"
resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b"