@ -158,6 +158,34 @@
|
|||||||
"contributions": [
|
"contributions": [
|
||||||
"code"
|
"code"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "zavoloklom",
|
||||||
|
"name": "Sergey Kupletsky",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/4151869?v=4",
|
||||||
|
"profile": "https://github.com/zavoloklom",
|
||||||
|
"contributions": [
|
||||||
|
"test",
|
||||||
|
"code"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "rome-user",
|
||||||
|
"name": "rome-user",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/114131048?v=4",
|
||||||
|
"profile": "https://github.com/rome-user",
|
||||||
|
"contributions": [
|
||||||
|
"code"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "celinepelletier",
|
||||||
|
"name": "Céline Pelletier",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/82821620?v=4",
|
||||||
|
"profile": "https://github.com/celinepelletier",
|
||||||
|
"contributions": [
|
||||||
|
"code"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"contributorsPerLine": 7,
|
"contributorsPerLine": 7,
|
||||||
@ -165,5 +193,6 @@
|
|||||||
"repoType": "github",
|
"repoType": "github",
|
||||||
"repoHost": "https://github.com",
|
"repoHost": "https://github.com",
|
||||||
"projectName": "keycloakify",
|
"projectName": "keycloakify",
|
||||||
"projectOwner": "keycloakify"
|
"projectOwner": "keycloakify",
|
||||||
|
"commitType": "docs"
|
||||||
}
|
}
|
||||||
|
1
.github/workflows/ci.yaml
vendored
1
.github/workflows/ci.yaml
vendored
@ -3,6 +3,7 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
|
- v*
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
|
11
README.md
11
README.md
@ -47,12 +47,12 @@
|
|||||||
|
|
||||||
> 📣 🛑 Account themes generated by Keycloakify are not currently compatible with Keycloak 22.
|
> 📣 🛑 Account themes generated by Keycloakify are not currently compatible with Keycloak 22.
|
||||||
> We are working on a solution. [Follow progress](https://github.com/keycloakify/keycloakify/issues/389).
|
> We are working on a solution. [Follow progress](https://github.com/keycloakify/keycloakify/issues/389).
|
||||||
> Login and email themes are not affected.
|
> **Login and email themes are not affected**.
|
||||||
> UPDATE: [The PR](https://github.com/keycloak/keycloak/pull/22317) that should future proof Keycloakify account themes has been greenlighted
|
> UPDATE: [The PR](https://github.com/keycloak/keycloak/pull/22317) that should future proof Keycloakify account themes has been
|
||||||
> by the Keycloak team. Resolution is only a matter of time.
|
> merged into Keycloak! 🥳 Credit to @xgp. We are now waiting for a new Keycloak release to be published.
|
||||||
|
|
||||||
Keycloakify is fully compatible with Keycloak, starting from version 11 and is anticipated to maintain compatibility with all future versions.
|
Keycloakify is fully compatible with Keycloak, starting from version 11 and is anticipated to maintain compatibility with all future versions.
|
||||||
You can update your Keycloak, your Keycloakify generated theme won't break.
|
You can update your Keycloak, your Keycloakify generated theme won't break. (Well except for Keycloak 22's Account theme obviously but this was hopefully a one time debacle)
|
||||||
To understand the basis of my confidence in this, you can [visit this discussion thread where I've explained in detail](https://github.com/keycloakify/keycloakify/discussions/346#discussioncomment-5889791).
|
To understand the basis of my confidence in this, you can [visit this discussion thread where I've explained in detail](https://github.com/keycloakify/keycloakify/discussions/346#discussioncomment-5889791).
|
||||||
|
|
||||||
## Sponsor 👼
|
## Sponsor 👼
|
||||||
@ -116,6 +116,9 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
|
|||||||
<td align="center" valign="top" width="14.28%"><a href="https://www.gravitysoftware.be"><img src="https://avatars.githubusercontent.com/u/1140574?v=4?s=100" width="100px;" alt="Thomas Silvestre"/><br /><sub><b>Thomas Silvestre</b></sub></a><br /><a href="https://github.com/keycloakify/keycloakify/commits?author=thosil" title="Code">💻</a></td>
|
<td align="center" valign="top" width="14.28%"><a href="https://www.gravitysoftware.be"><img src="https://avatars.githubusercontent.com/u/1140574?v=4?s=100" width="100px;" alt="Thomas Silvestre"/><br /><sub><b>Thomas Silvestre</b></sub></a><br /><a href="https://github.com/keycloakify/keycloakify/commits?author=thosil" title="Code">💻</a></td>
|
||||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/satanshiro"><img src="https://avatars.githubusercontent.com/u/38865738?v=4?s=100" width="100px;" alt="satanshiro"/><br /><sub><b>satanshiro</b></sub></a><br /><a href="https://github.com/keycloakify/keycloakify/commits?author=satanshiro" title="Code">💻</a></td>
|
<td align="center" valign="top" width="14.28%"><a href="https://github.com/satanshiro"><img src="https://avatars.githubusercontent.com/u/38865738?v=4?s=100" width="100px;" alt="satanshiro"/><br /><sub><b>satanshiro</b></sub></a><br /><a href="https://github.com/keycloakify/keycloakify/commits?author=satanshiro" title="Code">💻</a></td>
|
||||||
<td align="center" valign="top" width="14.28%"><a href="https://poelhekke.dev"><img src="https://avatars.githubusercontent.com/u/1632377?v=4?s=100" width="100px;" alt="Koen Poelhekke"/><br /><sub><b>Koen Poelhekke</b></sub></a><br /><a href="https://github.com/keycloakify/keycloakify/commits?author=kpoelhekke" title="Code">💻</a></td>
|
<td align="center" valign="top" width="14.28%"><a href="https://poelhekke.dev"><img src="https://avatars.githubusercontent.com/u/1632377?v=4?s=100" width="100px;" alt="Koen Poelhekke"/><br /><sub><b>Koen Poelhekke</b></sub></a><br /><a href="https://github.com/keycloakify/keycloakify/commits?author=kpoelhekke" title="Code">💻</a></td>
|
||||||
|
<td align="center" valign="top" width="14.28%"><a href="https://github.com/zavoloklom"><img src="https://avatars.githubusercontent.com/u/4151869?v=4?s=100" width="100px;" alt="Sergey Kupletsky"/><br /><sub><b>Sergey Kupletsky</b></sub></a><br /><a href="https://github.com/keycloakify/keycloakify/commits?author=zavoloklom" title="Tests">⚠️</a> <a href="https://github.com/keycloakify/keycloakify/commits?author=zavoloklom" title="Code">💻</a></td>
|
||||||
|
<td align="center" valign="top" width="14.28%"><a href="https://github.com/rome-user"><img src="https://avatars.githubusercontent.com/u/114131048?v=4?s=100" width="100px;" alt="rome-user"/><br /><sub><b>rome-user</b></sub></a><br /><a href="https://github.com/keycloakify/keycloakify/commits?author=rome-user" title="Code">💻</a></td>
|
||||||
|
<td align="center" valign="top" width="14.28%"><a href="https://github.com/celinepelletier"><img src="https://avatars.githubusercontent.com/u/82821620?v=4?s=100" width="100px;" alt="Céline Pelletier"/><br /><sub><b>Céline Pelletier</b></sub></a><br /><a href="https://github.com/keycloakify/keycloakify/commits?author=celinepelletier" title="Code">💻</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
@ -7,6 +7,7 @@ export type KcContext = KcContext.Password | KcContext.Account;
|
|||||||
|
|
||||||
export declare namespace KcContext {
|
export declare namespace KcContext {
|
||||||
export type Common = {
|
export type Common = {
|
||||||
|
themeVersion: string;
|
||||||
keycloakifyVersion: string;
|
keycloakifyVersion: string;
|
||||||
themeType: "account";
|
themeType: "account";
|
||||||
themeName: string;
|
themeName: string;
|
||||||
|
@ -9,6 +9,7 @@ const PUBLIC_URL = (typeof process !== "object" ? undefined : process.env?.["PUB
|
|||||||
const resourcesPath = pathJoin(PUBLIC_URL, keycloak_resources, "account", "resources");
|
const resourcesPath = pathJoin(PUBLIC_URL, keycloak_resources, "account", "resources");
|
||||||
|
|
||||||
export const kcContextCommonMock: KcContext.Common = {
|
export const kcContextCommonMock: KcContext.Common = {
|
||||||
|
"themeVersion": "0.0.0",
|
||||||
"keycloakifyVersion": "0.0.0",
|
"keycloakifyVersion": "0.0.0",
|
||||||
"themeType": "account",
|
"themeType": "account",
|
||||||
"themeName": "my-theme-name",
|
"themeName": "my-theme-name",
|
||||||
|
@ -51,7 +51,7 @@ export default function Account(props: PageProps<Extract<KcContext, { pageId: "a
|
|||||||
id="username"
|
id="username"
|
||||||
name="username"
|
name="username"
|
||||||
disabled={!realm.editUsernameAllowed}
|
disabled={!realm.editUsernameAllowed}
|
||||||
value={account.username ?? ""}
|
defaultValue={account.username ?? ""}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -66,7 +66,7 @@ export default function Account(props: PageProps<Extract<KcContext, { pageId: "a
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="col-sm-10 col-md-10">
|
<div className="col-sm-10 col-md-10">
|
||||||
<input type="text" className="form-control" id="email" name="email" autoFocus value={account.email ?? ""} />
|
<input type="text" className="form-control" id="email" name="email" autoFocus defaultValue={account.email ?? ""} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -79,7 +79,7 @@ export default function Account(props: PageProps<Extract<KcContext, { pageId: "a
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="col-sm-10 col-md-10">
|
<div className="col-sm-10 col-md-10">
|
||||||
<input type="text" className="form-control" id="firstName" name="firstName" value={account.firstName ?? ""} />
|
<input type="text" className="form-control" id="firstName" name="firstName" defaultValue={account.firstName ?? ""} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -92,7 +92,7 @@ export default function Account(props: PageProps<Extract<KcContext, { pageId: "a
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="col-sm-10 col-md-10">
|
<div className="col-sm-10 col-md-10">
|
||||||
<input type="text" className="form-control" id="lastName" name="lastName" value={account.lastName ?? ""} />
|
<input type="text" className="form-control" id="lastName" name="lastName" defaultValue={account.lastName ?? ""} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ import { crawl } from "./tools/crawl";
|
|||||||
import { join as pathJoin } from "path";
|
import { join as pathJoin } from "path";
|
||||||
import { themeTypes } from "./constants";
|
import { themeTypes } from "./constants";
|
||||||
|
|
||||||
const themeSrcDirBasename = "keycloak-theme";
|
const themeSrcDirBasenames = ["keycloak-theme", "keycloak_theme"];
|
||||||
|
|
||||||
/** Can't catch error, if the directory isn't found, this function will just exit the process with an error message. */
|
/** Can't catch error, if the directory isn't found, this function will just exit the process with an error message. */
|
||||||
export function getThemeSrcDirPath(params: { reactAppRootDirPath: string }) {
|
export function getThemeSrcDirPath(params: { reactAppRootDirPath: string }) {
|
||||||
@ -14,13 +14,13 @@ export function getThemeSrcDirPath(params: { reactAppRootDirPath: string }) {
|
|||||||
|
|
||||||
const themeSrcDirPath: string | undefined = crawl({ "dirPath": srcDirPath, "returnedPathsType": "relative to dirPath" })
|
const themeSrcDirPath: string | undefined = crawl({ "dirPath": srcDirPath, "returnedPathsType": "relative to dirPath" })
|
||||||
.map(fileRelativePath => {
|
.map(fileRelativePath => {
|
||||||
|
for (const themeSrcDirBasename of themeSrcDirBasenames) {
|
||||||
const split = fileRelativePath.split(themeSrcDirBasename);
|
const split = fileRelativePath.split(themeSrcDirBasename);
|
||||||
|
if (split.length === 2) {
|
||||||
if (split.length !== 2) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return pathJoin(srcDirPath, split[0] + themeSrcDirBasename);
|
return pathJoin(srcDirPath, split[0] + themeSrcDirBasename);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
})
|
})
|
||||||
.filter(exclude(undefined))[0];
|
.filter(exclude(undefined))[0];
|
||||||
|
|
||||||
@ -38,7 +38,7 @@ export function getThemeSrcDirPath(params: { reactAppRootDirPath: string }) {
|
|||||||
console.error(
|
console.error(
|
||||||
[
|
[
|
||||||
"Can't locate your theme source directory. It should be either: ",
|
"Can't locate your theme source directory. It should be either: ",
|
||||||
"src/ or src/keycloak-theme.",
|
"src/ or src/keycloak-theme or src/keycloak_theme.",
|
||||||
"Example in the starter: https://github.com/keycloakify/keycloakify-starter/tree/main/src/keycloak-theme"
|
"Example in the starter: https://github.com/keycloakify/keycloakify-starter/tree/main/src/keycloak-theme"
|
||||||
].join("\n")
|
].join("\n")
|
||||||
);
|
);
|
||||||
|
@ -478,6 +478,12 @@
|
|||||||
"error.ftl" == pageId &&
|
"error.ftl" == pageId &&
|
||||||
are_same_path(path, ["realm"]) &&
|
are_same_path(path, ["realm"]) &&
|
||||||
!["name", "displayName", "displayNameHtml", "internationalizationEnabled", "registrationEmailAsUsername" ]?seq_contains(key)
|
!["name", "displayName", "displayNameHtml", "internationalizationEnabled", "registrationEmailAsUsername" ]?seq_contains(key)
|
||||||
|
) || (
|
||||||
|
"applications.ftl" == pageId &&
|
||||||
|
are_same_path(path, ["applications", "applications", "*", "client", "realm"])
|
||||||
|
) || (
|
||||||
|
"applications.ftl" == pageId &&
|
||||||
|
"masterAdminClient" == key
|
||||||
)
|
)
|
||||||
>
|
>
|
||||||
<#local out_seq += ["/*If you need '" + key + "' on " + pageId + ", please submit an issue to the Keycloakify repo*/"]>
|
<#local out_seq += ["/*If you need '" + key + "' on " + pageId + ", please submit an issue to the Keycloakify repo*/"]>
|
||||||
|
@ -121,6 +121,27 @@ export function generateFtlFilesCodeFactory(params: {
|
|||||||
].join("\n")
|
].join("\n")
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Remove part of the document marked as ignored.
|
||||||
|
{
|
||||||
|
const startTags = $('meta[name="keycloakify-ignore-start"]');
|
||||||
|
|
||||||
|
startTags.each((...[, startTag]) => {
|
||||||
|
const $startTag = $(startTag);
|
||||||
|
const $endTag = $startTag.nextAll('meta[name="keycloakify-ignore-end"]').first();
|
||||||
|
|
||||||
|
if ($endTag.length) {
|
||||||
|
let currentNode = $startTag.next();
|
||||||
|
while (currentNode.length && !currentNode.is($endTag)) {
|
||||||
|
currentNode.remove();
|
||||||
|
currentNode = $startTag.next();
|
||||||
|
}
|
||||||
|
|
||||||
|
$startTag.remove();
|
||||||
|
$endTag.remove();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const partiallyFixedIndexHtmlCode = $.html();
|
const partiallyFixedIndexHtmlCode = $.html();
|
||||||
|
|
||||||
function generateFtlFilesCode(params: { pageId: string }): {
|
function generateFtlFilesCode(params: { pageId: string }): {
|
||||||
|
@ -10,6 +10,8 @@ export const loginThemePageIds = [
|
|||||||
"login-reset-password.ftl",
|
"login-reset-password.ftl",
|
||||||
"login-verify-email.ftl",
|
"login-verify-email.ftl",
|
||||||
"terms.ftl",
|
"terms.ftl",
|
||||||
|
"login-oauth2-device-verify-user-code.ftl",
|
||||||
|
"login-oauth-grant.ftl",
|
||||||
"login-otp.ftl",
|
"login-otp.ftl",
|
||||||
"login-update-profile.ftl",
|
"login-update-profile.ftl",
|
||||||
"login-update-password.ftl",
|
"login-update-password.ftl",
|
||||||
|
@ -1,17 +1,15 @@
|
|||||||
import { crawl } from "../../tools/crawl";
|
import { crawl } from "../../tools/crawl";
|
||||||
import { join as pathJoin } from "path";
|
import { join as pathJoin, sep as pathSep } from "path";
|
||||||
import * as fs from "fs";
|
import * as fs from "fs";
|
||||||
import type { ThemeType } from "../../constants";
|
import type { ThemeType } from "../../constants";
|
||||||
|
|
||||||
/** Assumes the theme type exists */
|
/** Assumes the theme type exists */
|
||||||
export function readStaticResourcesUsage(params: { keycloakifySrcDirPath: string; themeSrcDirPath: string; themeType: ThemeType }): {
|
export function readStaticResourcesUsage(params: { keycloakifySrcDirPath: string; themeSrcDirPath: string; themeType: ThemeType }): {
|
||||||
resourcesCommonFilePaths: string[];
|
resourcesCommonFilePaths: string[];
|
||||||
resourcesFilePaths: string[];
|
|
||||||
} {
|
} {
|
||||||
const { keycloakifySrcDirPath, themeSrcDirPath, themeType } = params;
|
const { keycloakifySrcDirPath, themeSrcDirPath, themeType } = params;
|
||||||
|
|
||||||
const resourcesCommonFilePaths = new Set<string>();
|
const resourcesCommonFilePaths = new Set<string>();
|
||||||
const resourcesFilePaths = new Set<string>();
|
|
||||||
|
|
||||||
for (const srcDirPath of [pathJoin(keycloakifySrcDirPath, themeType), pathJoin(themeSrcDirPath, themeType)]) {
|
for (const srcDirPath of [pathJoin(keycloakifySrcDirPath, themeType), pathJoin(themeSrcDirPath, themeType)]) {
|
||||||
const filePaths = crawl({ "dirPath": srcDirPath, "returnedPathsType": "absolute" }).filter(filePath => /\.(ts|tsx|js|jsx)$/.test(filePath));
|
const filePaths = crawl({ "dirPath": srcDirPath, "returnedPathsType": "absolute" }).filter(filePath => /\.(ts|tsx|js|jsx)$/.test(filePath));
|
||||||
@ -26,58 +24,53 @@ export function readStaticResourcesUsage(params: { keycloakifySrcDirPath: string
|
|||||||
const wrap = readPaths({ rawSourceFile });
|
const wrap = readPaths({ rawSourceFile });
|
||||||
|
|
||||||
wrap.resourcesCommonFilePaths.forEach(filePath => resourcesCommonFilePaths.add(filePath));
|
wrap.resourcesCommonFilePaths.forEach(filePath => resourcesCommonFilePaths.add(filePath));
|
||||||
wrap.resourcesFilePaths.forEach(filePath => resourcesFilePaths.add(filePath));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"resourcesCommonFilePaths": Array.from(resourcesCommonFilePaths),
|
"resourcesCommonFilePaths": Array.from(resourcesCommonFilePaths)
|
||||||
"resourcesFilePaths": Array.from(resourcesFilePaths)
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Exported for testing purpose */
|
/** Exported for testing purpose */
|
||||||
export function readPaths(params: { rawSourceFile: string }): {
|
export function readPaths(params: { rawSourceFile: string }): {
|
||||||
resourcesCommonFilePaths: string[];
|
resourcesCommonFilePaths: string[];
|
||||||
resourcesFilePaths: string[];
|
|
||||||
} {
|
} {
|
||||||
const { rawSourceFile } = params;
|
const { rawSourceFile } = params;
|
||||||
|
|
||||||
const resourcesCommonFilePaths = new Set<string>();
|
const resourcesCommonFilePaths = new Set<string>();
|
||||||
const resourcesFilePaths = new Set<string>();
|
|
||||||
|
|
||||||
for (const isCommon of [true, false]) {
|
|
||||||
const set = isCommon ? resourcesCommonFilePaths : resourcesFilePaths;
|
|
||||||
|
|
||||||
{
|
{
|
||||||
const regexp = new RegExp(`resources${isCommon ? "Common" : ""}Path\\s*}([^\`]+)\``, "g");
|
const regexp = new RegExp(`resourcesCommonPath\\s*}([^\`]+)\``, "g");
|
||||||
|
|
||||||
const matches = [...rawSourceFile.matchAll(regexp)];
|
const matches = [...rawSourceFile.matchAll(regexp)];
|
||||||
|
|
||||||
for (const match of matches) {
|
for (const match of matches) {
|
||||||
const filePath = match[1];
|
const filePath = match[1];
|
||||||
|
|
||||||
set.add(filePath);
|
resourcesCommonFilePaths.add(filePath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
const regexp = new RegExp(`resources${isCommon ? "Common" : ""}Path\\s*[+,]\\s*["']([^"'\`]+)["'\`]`, "g");
|
const regexp = new RegExp(`resourcesCommonPath\\s*[+,]\\s*["']([^"'\`]+)["'\`]`, "g");
|
||||||
|
|
||||||
const matches = [...rawSourceFile.matchAll(regexp)];
|
const matches = [...rawSourceFile.matchAll(regexp)];
|
||||||
|
|
||||||
for (const match of matches) {
|
for (const match of matches) {
|
||||||
const filePath = match[1];
|
const filePath = match[1];
|
||||||
|
|
||||||
set.add(filePath);
|
resourcesCommonFilePaths.add(filePath);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const removePrefixSlash = (filePath: string) => (filePath.startsWith("/") ? filePath.slice(1) : filePath);
|
const normalizePath = (filePath: string) => {
|
||||||
|
filePath = filePath.startsWith("/") ? filePath.slice(1) : filePath;
|
||||||
|
filePath = filePath.replace(/\//g, pathSep);
|
||||||
|
return filePath;
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"resourcesCommonFilePaths": Array.from(resourcesCommonFilePaths).map(removePrefixSlash),
|
"resourcesCommonFilePaths": Array.from(resourcesCommonFilePaths).map(normalizePath)
|
||||||
"resourcesFilePaths": Array.from(resourcesFilePaths).map(removePrefixSlash)
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -16,18 +16,25 @@ export function replaceImportsFromStaticInJsCode(params: { jsCode: string }): {
|
|||||||
const { jsCode } = params;
|
const { jsCode } = params;
|
||||||
|
|
||||||
const getReplaceArgs = (language: "js" | "css"): Parameters<typeof String.prototype.replace> => [
|
const getReplaceArgs = (language: "js" | "css"): Parameters<typeof String.prototype.replace> => [
|
||||||
new RegExp(`([a-zA-Z_]+)\\.([a-zA-Z]+)=function\\(([a-zA-Z]+)\\){return"static\\/${language}\\/"`, "g"),
|
new RegExp(`([a-zA-Z_]+)\\.([a-zA-Z]+)=(function\\(([a-z]+)\\){return|([a-z]+)=>)"static\\/${language}\\/"`, "g"),
|
||||||
(...[, n, u, e]) => `
|
(...[, n, u, matchedFunction, eForFunction]) => {
|
||||||
|
const isArrowFunction = matchedFunction.includes("=>");
|
||||||
|
const e = isArrowFunction ? matchedFunction.replace("=>", "").trim() : eForFunction;
|
||||||
|
|
||||||
|
return `
|
||||||
${n}[(function(){
|
${n}[(function(){
|
||||||
var pd= Object.getOwnPropertyDescriptor(${n}, "p");
|
var pd = Object.getOwnPropertyDescriptor(${n}, "p");
|
||||||
if( pd === undefined || pd.configurable ){
|
if( pd === undefined || pd.configurable ){
|
||||||
Object.defineProperty(${n}, "p", {
|
Object.defineProperty(${n}, "p", {
|
||||||
get: function() { return window.${ftlValuesGlobalName}.url.resourcesPath; },
|
get: function() { return window.${ftlValuesGlobalName}.url.resourcesPath; },
|
||||||
set: function (){}
|
set: function() {}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return "${u}";
|
return "${u}";
|
||||||
})()] = function(${e}) { return "${true ? "/build/" : ""}static/${language}/"`
|
})()] = ${isArrowFunction ? `${e} =>` : `function(${e}) { return `} "/build/static/${language}/"`
|
||||||
|
.replace(/\s+/g, " ")
|
||||||
|
.trim();
|
||||||
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
const fixedJsCode = jsCode
|
const fixedJsCode = jsCode
|
||||||
|
@ -23,25 +23,22 @@ export function usePrepareTemplate(params: {
|
|||||||
const removeArray: (() => void)[] = [];
|
const removeArray: (() => void)[] = [];
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
const prLoadedArray: Promise<void>[] = [];
|
for (const style of [...styles].reverse()) {
|
||||||
|
|
||||||
styles.reverse().forEach(href => {
|
|
||||||
const { prLoaded, remove } = headInsert({
|
const { prLoaded, remove } = headInsert({
|
||||||
"type": "css",
|
"type": "css",
|
||||||
"position": "prepend",
|
"position": "prepend",
|
||||||
href
|
"href": style
|
||||||
});
|
});
|
||||||
|
|
||||||
removeArray.push(remove);
|
removeArray.push(remove);
|
||||||
|
|
||||||
prLoadedArray.push(prLoaded);
|
// TODO: Find a way to do that in parallel (without breaking the order)
|
||||||
});
|
await prLoaded;
|
||||||
|
|
||||||
await Promise.all(prLoadedArray);
|
|
||||||
|
|
||||||
if (isUnmounted) {
|
if (isUnmounted) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
setReady();
|
setReady();
|
||||||
})();
|
})();
|
||||||
|
@ -12,6 +12,8 @@ const Error = lazy(() => import("keycloakify/login/pages/Error"));
|
|||||||
const LoginResetPassword = lazy(() => import("keycloakify/login/pages/LoginResetPassword"));
|
const LoginResetPassword = lazy(() => import("keycloakify/login/pages/LoginResetPassword"));
|
||||||
const LoginVerifyEmail = lazy(() => import("keycloakify/login/pages/LoginVerifyEmail"));
|
const LoginVerifyEmail = lazy(() => import("keycloakify/login/pages/LoginVerifyEmail"));
|
||||||
const Terms = lazy(() => import("keycloakify/login/pages/Terms"));
|
const Terms = lazy(() => import("keycloakify/login/pages/Terms"));
|
||||||
|
const LoginDeviceVerifyUserCode = lazy(() => import("keycloakify/login/pages/LoginDeviceVerifyUserCode"));
|
||||||
|
const LoginOauthGrant = lazy(() => import("keycloakify/login/pages/LoginOauthGrant"));
|
||||||
const LoginOtp = lazy(() => import("keycloakify/login/pages/LoginOtp"));
|
const LoginOtp = lazy(() => import("keycloakify/login/pages/LoginOtp"));
|
||||||
const LoginPassword = lazy(() => import("keycloakify/login/pages/LoginPassword"));
|
const LoginPassword = lazy(() => import("keycloakify/login/pages/LoginPassword"));
|
||||||
const LoginUsername = lazy(() => import("keycloakify/login/pages/LoginUsername"));
|
const LoginUsername = lazy(() => import("keycloakify/login/pages/LoginUsername"));
|
||||||
@ -52,6 +54,10 @@ export default function Fallback(props: PageProps<KcContext, I18n>) {
|
|||||||
return <LoginVerifyEmail kcContext={kcContext} {...rest} />;
|
return <LoginVerifyEmail kcContext={kcContext} {...rest} />;
|
||||||
case "terms.ftl":
|
case "terms.ftl":
|
||||||
return <Terms kcContext={kcContext} {...rest} />;
|
return <Terms kcContext={kcContext} {...rest} />;
|
||||||
|
case "login-oauth2-device-verify-user-code.ftl":
|
||||||
|
return <LoginDeviceVerifyUserCode kcContext={kcContext} {...rest} />;
|
||||||
|
case "login-oauth-grant.ftl":
|
||||||
|
return <LoginOauthGrant kcContext={kcContext} {...rest} />;
|
||||||
case "login-otp.ftl":
|
case "login-otp.ftl":
|
||||||
return <LoginOtp kcContext={kcContext} {...rest} />;
|
return <LoginOtp kcContext={kcContext} {...rest} />;
|
||||||
case "login-username.ftl":
|
case "login-username.ftl":
|
||||||
|
@ -94,4 +94,5 @@ export type ClassKey =
|
|||||||
| "kcSelectOTPListItemClass"
|
| "kcSelectOTPListItemClass"
|
||||||
| "kcAuthenticatorOtpCircleClass"
|
| "kcAuthenticatorOtpCircleClass"
|
||||||
| "kcSelectOTPItemHeadingClass"
|
| "kcSelectOTPItemHeadingClass"
|
||||||
| "kcFormOptionsWrapperClass";
|
| "kcFormOptionsWrapperClass"
|
||||||
|
| "kcFormButtonsWrapperClass";
|
||||||
|
@ -19,6 +19,8 @@ export type KcContext =
|
|||||||
| KcContext.LoginResetPassword
|
| KcContext.LoginResetPassword
|
||||||
| KcContext.LoginVerifyEmail
|
| KcContext.LoginVerifyEmail
|
||||||
| KcContext.Terms
|
| KcContext.Terms
|
||||||
|
| KcContext.LoginDeviceVerifyUserCode
|
||||||
|
| KcContext.LoginOauthGrant
|
||||||
| KcContext.LoginOtp
|
| KcContext.LoginOtp
|
||||||
| KcContext.LoginUsername
|
| KcContext.LoginUsername
|
||||||
| KcContext.WebauthnAuthenticate
|
| KcContext.WebauthnAuthenticate
|
||||||
@ -38,6 +40,7 @@ export type KcContext =
|
|||||||
|
|
||||||
export declare namespace KcContext {
|
export declare namespace KcContext {
|
||||||
export type Common = {
|
export type Common = {
|
||||||
|
themeVersion: string;
|
||||||
keycloakifyVersion: string;
|
keycloakifyVersion: string;
|
||||||
themeType: "login";
|
themeType: "login";
|
||||||
themeName: string;
|
themeName: string;
|
||||||
@ -242,6 +245,27 @@ export declare namespace KcContext {
|
|||||||
pageId: "terms.ftl";
|
pageId: "terms.ftl";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type LoginDeviceVerifyUserCode = Common & {
|
||||||
|
pageId: "login-oauth2-device-verify-user-code.ftl";
|
||||||
|
url: {
|
||||||
|
oauth2DeviceVerificationAction: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export type LoginOauthGrant = Common & {
|
||||||
|
pageId: "login-oauth-grant.ftl";
|
||||||
|
oauth: {
|
||||||
|
code: string;
|
||||||
|
client: string;
|
||||||
|
clientScopesRequested: {
|
||||||
|
consentScreenText: string;
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
url: {
|
||||||
|
oauthAction: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export type LoginOtp = Common & {
|
export type LoginOtp = Common & {
|
||||||
pageId: "login-otp.ftl";
|
pageId: "login-otp.ftl";
|
||||||
otpLogin: {
|
otpLogin: {
|
||||||
|
@ -105,6 +105,7 @@ const PUBLIC_URL = (typeof process !== "object" ? undefined : process.env?.["PUB
|
|||||||
const resourcesPath = pathJoin(PUBLIC_URL, keycloak_resources, "login", "resources");
|
const resourcesPath = pathJoin(PUBLIC_URL, keycloak_resources, "login", "resources");
|
||||||
|
|
||||||
export const kcContextCommonMock: KcContext.Common = {
|
export const kcContextCommonMock: KcContext.Common = {
|
||||||
|
"themeVersion": "0.0.0",
|
||||||
"keycloakifyVersion": "0.0.0",
|
"keycloakifyVersion": "0.0.0",
|
||||||
"themeType": "login",
|
"themeType": "login",
|
||||||
"themeName": "my-theme-name",
|
"themeName": "my-theme-name",
|
||||||
@ -242,7 +243,9 @@ export const kcContextCommonMock: KcContext.Common = {
|
|||||||
const loginUrl = {
|
const loginUrl = {
|
||||||
...kcContextCommonMock.url,
|
...kcContextCommonMock.url,
|
||||||
"loginResetCredentialsUrl": "/auth/realms/myrealm/login-actions/reset-credentials?client_id=account&tab_id=HoAx28ja4xg",
|
"loginResetCredentialsUrl": "/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"
|
"registrationUrl": "/auth/realms/myrealm/login-actions/registration?client_id=account&tab_id=HoAx28ja4xg",
|
||||||
|
"oauth2DeviceVerificationAction": "/auth/realms/myrealm/device",
|
||||||
|
"oauthAction": "/auth/realms/myrealm/login-actions/consent?client_id=account&tab_id=HoAx28ja4xg"
|
||||||
};
|
};
|
||||||
|
|
||||||
export const kcContextMocks = [
|
export const kcContextMocks = [
|
||||||
@ -346,6 +349,25 @@ export const kcContextMocks = [
|
|||||||
...kcContextCommonMock,
|
...kcContextCommonMock,
|
||||||
"pageId": "terms.ftl"
|
"pageId": "terms.ftl"
|
||||||
}),
|
}),
|
||||||
|
id<KcContext.LoginDeviceVerifyUserCode>({
|
||||||
|
...kcContextCommonMock,
|
||||||
|
"pageId": "login-oauth2-device-verify-user-code.ftl",
|
||||||
|
url: loginUrl
|
||||||
|
}),
|
||||||
|
id<KcContext.LoginOauthGrant>({
|
||||||
|
...kcContextCommonMock,
|
||||||
|
"pageId": "login-oauth-grant.ftl",
|
||||||
|
oauth: {
|
||||||
|
code: "5-1N4CIzfi1aprIQjmylI-9e3spLCWW9i5d-GDcs-Sw",
|
||||||
|
clientScopesRequested: [
|
||||||
|
{ consentScreenText: "${profileScopeConsentText}" },
|
||||||
|
{ consentScreenText: "${rolesScopeConsentText}" },
|
||||||
|
{ consentScreenText: "${emailScopeConsentText}" }
|
||||||
|
],
|
||||||
|
client: "account"
|
||||||
|
},
|
||||||
|
url: loginUrl
|
||||||
|
}),
|
||||||
id<KcContext.LoginOtp>({
|
id<KcContext.LoginOtp>({
|
||||||
...kcContextCommonMock,
|
...kcContextCommonMock,
|
||||||
"pageId": "login-otp.ftl",
|
"pageId": "login-otp.ftl",
|
||||||
|
@ -45,6 +45,7 @@ export const { useGetClassName } = createUseClassName<ClassKey>({
|
|||||||
"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",
|
||||||
|
"kcFormButtonsWrapperClass": undefined,
|
||||||
"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",
|
||||||
|
@ -8,7 +8,10 @@ export default function Info(props: PageProps<Extract<KcContext, { pageId: "info
|
|||||||
|
|
||||||
const { msgStr, msg } = i18n;
|
const { msgStr, msg } = i18n;
|
||||||
|
|
||||||
assert(kcContext.message !== undefined);
|
assert(
|
||||||
|
kcContext.message !== undefined,
|
||||||
|
"No message in kcContext.message, there will always be a message in production context, add it in your mock"
|
||||||
|
);
|
||||||
|
|
||||||
const { messageHeader, message, requiredActions, skipLink, pageRedirectUri, actionUri, client } = kcContext;
|
const { messageHeader, message, requiredActions, skipLink, pageRedirectUri, actionUri, client } = kcContext;
|
||||||
|
|
||||||
|
@ -32,7 +32,7 @@ export default function LoginConfigTotp(props: PageProps<Extract<KcContext, { pa
|
|||||||
|
|
||||||
<ul id="kc-totp-supported-apps">
|
<ul id="kc-totp-supported-apps">
|
||||||
{totp.supportedApplications.map(app => (
|
{totp.supportedApplications.map(app => (
|
||||||
<li>{msgStr(app as MessageKey, app)}</li>
|
<li>{msg(app as MessageKey)}</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
|
68
src/login/pages/LoginDeviceVerifyUserCode.tsx
Normal file
68
src/login/pages/LoginDeviceVerifyUserCode.tsx
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
import { clsx } from "keycloakify/tools/clsx";
|
||||||
|
import Template from "../Template";
|
||||||
|
import { I18n } from "../i18n";
|
||||||
|
import { KcContext } from "../kcContext";
|
||||||
|
import { useGetClassName } from "../lib/useGetClassName";
|
||||||
|
import { PageProps } from "./PageProps";
|
||||||
|
|
||||||
|
export default function LoginOauthGrant(props: PageProps<Extract<KcContext, { pageId: "login-oauth2-device-verify-user-code.ftl" }>, I18n>) {
|
||||||
|
const { kcContext, i18n, doUseDefaultCss, classes } = props;
|
||||||
|
const { url } = kcContext;
|
||||||
|
|
||||||
|
const { msg, msgStr } = i18n;
|
||||||
|
|
||||||
|
const { getClassName } = useGetClassName({
|
||||||
|
doUseDefaultCss,
|
||||||
|
classes
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Template {...{ kcContext, i18n, doUseDefaultCss, classes }} headerNode={msg("oauth2DeviceVerificationTitle")}>
|
||||||
|
<form
|
||||||
|
id="kc-user-verify-device-user-code-form"
|
||||||
|
className={getClassName("kcFormClass")}
|
||||||
|
action={url.oauth2DeviceVerificationAction}
|
||||||
|
method="post"
|
||||||
|
>
|
||||||
|
<div className={getClassName("kcFormGroupClass")}>
|
||||||
|
<div className={getClassName("kcLabelWrapperClass")}>
|
||||||
|
<label htmlFor="device-user-code" className={getClassName("kcLabelClass")}>
|
||||||
|
{msg("verifyOAuth2DeviceUserCode")}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={getClassName("kcInputWrapperClass")}>
|
||||||
|
<input
|
||||||
|
id="device-user-code"
|
||||||
|
name="device_user_code"
|
||||||
|
autoComplete="off"
|
||||||
|
type="text"
|
||||||
|
className={getClassName("kcInputClass")}
|
||||||
|
autoFocus
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={getClassName("kcFormGroupClass")}>
|
||||||
|
<div id="kc-form-options" className={getClassName("kcFormOptionsClass")}>
|
||||||
|
<div className={getClassName("kcFormOptionsWrapperClass")}></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="kc-form-buttons" className={getClassName("kcFormButtonsClass")}>
|
||||||
|
<div className={getClassName("kcFormButtonsWrapperClass")}>
|
||||||
|
<input
|
||||||
|
className={clsx(
|
||||||
|
getClassName("kcButtonClass"),
|
||||||
|
getClassName("kcButtonPrimaryClass"),
|
||||||
|
getClassName("kcButtonLargeClass")
|
||||||
|
)}
|
||||||
|
type="submit"
|
||||||
|
value={msgStr("doSubmit")}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</Template>
|
||||||
|
);
|
||||||
|
}
|
73
src/login/pages/LoginOauthGrant.tsx
Normal file
73
src/login/pages/LoginOauthGrant.tsx
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
import { clsx } from "keycloakify/tools/clsx";
|
||||||
|
import { PageProps } from "./PageProps";
|
||||||
|
import { KcContext } from "../kcContext";
|
||||||
|
import { I18n } from "../i18n";
|
||||||
|
import Template from "../Template";
|
||||||
|
import { useGetClassName } from "keycloakify/login/lib/useGetClassName";
|
||||||
|
|
||||||
|
export default function LoginOauthGrant(props: PageProps<Extract<KcContext, { pageId: "login-oauth-grant.ftl" }>, I18n>) {
|
||||||
|
const { kcContext, i18n, doUseDefaultCss, classes } = props;
|
||||||
|
const { url, oauth, client } = kcContext;
|
||||||
|
|
||||||
|
const { msg, msgStr, advancedMsg, advancedMsgStr } = i18n;
|
||||||
|
|
||||||
|
const { getClassName } = useGetClassName({
|
||||||
|
doUseDefaultCss,
|
||||||
|
classes
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Template
|
||||||
|
{...{ kcContext, i18n, doUseDefaultCss, classes }}
|
||||||
|
headerNode={msg("oauthGrantTitle", client.name ? advancedMsgStr(client.name) : client.clientId)}
|
||||||
|
>
|
||||||
|
<div id="kc-oauth" className="content-area">
|
||||||
|
<h3>{msg("oauthGrantRequest")}</h3>
|
||||||
|
<ul>
|
||||||
|
{oauth.clientScopesRequested.map(clientScope => (
|
||||||
|
<li key={clientScope.consentScreenText}>
|
||||||
|
<span>{advancedMsg(clientScope.consentScreenText)}</span>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<form className="form-actions" action={url.oauthAction} method="POST">
|
||||||
|
<input type="hidden" name="code" value={oauth.code} />
|
||||||
|
<div className={getClassName("kcFormGroupClass")}>
|
||||||
|
<div id="kc-form-options">
|
||||||
|
<div className={getClassName("kcFormOptionsWrapperClass")}></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="kc-form-buttons">
|
||||||
|
<div className={getClassName("kcFormButtonsWrapperClass")}>
|
||||||
|
<input
|
||||||
|
className={clsx(
|
||||||
|
getClassName("kcButtonClass"),
|
||||||
|
getClassName("kcButtonPrimaryClass"),
|
||||||
|
getClassName("kcButtonLargeClass")
|
||||||
|
)}
|
||||||
|
name="accept"
|
||||||
|
id="kc-login"
|
||||||
|
type="submit"
|
||||||
|
value={msgStr("doYes")}
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
className={clsx(
|
||||||
|
getClassName("kcButtonClass"),
|
||||||
|
getClassName("kcButtonDefaultClass"),
|
||||||
|
getClassName("kcButtonLargeClass")
|
||||||
|
)}
|
||||||
|
name="cancel"
|
||||||
|
id="kc-cancel"
|
||||||
|
type="submit"
|
||||||
|
value={msgStr("doNo")}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<div className="clearfix"></div>
|
||||||
|
</div>
|
||||||
|
</Template>
|
||||||
|
);
|
||||||
|
}
|
@ -21,4 +21,13 @@ const meta: ComponentMeta<any> = {
|
|||||||
|
|
||||||
export default meta;
|
export default meta;
|
||||||
|
|
||||||
export const Default = () => <PageStory />;
|
export const Default = () => (
|
||||||
|
<PageStory
|
||||||
|
kcContext={{
|
||||||
|
message: {
|
||||||
|
summary: "This is the server message",
|
||||||
|
type: "info"
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
24
stories/login/pages/LoginDeviceVerifyUserCode.stories.tsx
Normal file
24
stories/login/pages/LoginDeviceVerifyUserCode.stories.tsx
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import React from "react";
|
||||||
|
import type { ComponentMeta } from "@storybook/react";
|
||||||
|
import { createPageStory } from "../createPageStory";
|
||||||
|
|
||||||
|
const pageId = "login-oauth2-device-verify-user-code.ftl";
|
||||||
|
|
||||||
|
const { PageStory } = createPageStory({ pageId });
|
||||||
|
|
||||||
|
const meta: ComponentMeta<any> = {
|
||||||
|
title: `login/${pageId}`,
|
||||||
|
component: PageStory,
|
||||||
|
parameters: {
|
||||||
|
viewMode: "story",
|
||||||
|
previewTabs: {
|
||||||
|
"storybook/docs/panel": {
|
||||||
|
"hidden": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
|
||||||
|
export const Default = () => <PageStory />;
|
24
stories/login/pages/LoginOauthGrant.stories.tsx
Normal file
24
stories/login/pages/LoginOauthGrant.stories.tsx
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import React from "react";
|
||||||
|
import type { ComponentMeta } from "@storybook/react";
|
||||||
|
import { createPageStory } from "../createPageStory";
|
||||||
|
|
||||||
|
const pageId = "login-oauth-grant.ftl";
|
||||||
|
|
||||||
|
const { PageStory } = createPageStory({ pageId });
|
||||||
|
|
||||||
|
const meta: ComponentMeta<any> = {
|
||||||
|
title: `login/${pageId}`,
|
||||||
|
component: PageStory,
|
||||||
|
parameters: {
|
||||||
|
viewMode: "story",
|
||||||
|
previewTabs: {
|
||||||
|
"storybook/docs/panel": {
|
||||||
|
"hidden": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
|
||||||
|
export const Default = () => <PageStory />;
|
@ -9,8 +9,7 @@ describe("Ensure it's able to extract used Keycloak resources", () => {
|
|||||||
"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",
|
||||||
"node_modules/jquery/dist/jquery.min.js"
|
"node_modules/jquery/dist/jquery.min.js"
|
||||||
],
|
]
|
||||||
"resourcesFilePaths": ["css/login.css"]
|
|
||||||
};
|
};
|
||||||
|
|
||||||
it("works with coding style n°1", () => {
|
it("works with coding style n°1", () => {
|
||||||
|
@ -32,6 +32,10 @@ describe("bin/js-transforms", () => {
|
|||||||
908:"67c9ed2c"
|
908:"67c9ed2c"
|
||||||
}[e]+".chunk.css"
|
}[e]+".chunk.css"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
n.u=e=>"static/js/"+e+"."+{69:"4f205f87",128:"49264537",453:"b2fed72e",482:"f0106901"}[e]+".chunk.js"
|
||||||
|
|
||||||
|
t.miniCssF=e=>"static/css/"+e+"."+{164:"dcfd7749",908:"67c9ed2c"}[e]+".chunk.css"
|
||||||
`;
|
`;
|
||||||
it("transforms standalone code properly", () => {
|
it("transforms standalone code properly", () => {
|
||||||
const { fixedJsCode } = replaceImportsFromStaticInJsCode({
|
const { fixedJsCode } = replaceImportsFromStaticInJsCode({
|
||||||
@ -52,11 +56,11 @@ describe("bin/js-transforms", () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
__webpack_require__[(function (){
|
__webpack_require__[(function (){
|
||||||
var pd= Object.getOwnPropertyDescriptor(__webpack_require__, "p");
|
var pd = Object.getOwnPropertyDescriptor(__webpack_require__, "p");
|
||||||
if( pd === undefined || pd.configurable ){
|
if( pd === undefined || pd.configurable ){
|
||||||
Object.defineProperty(__webpack_require__, "p", {
|
Object.defineProperty(__webpack_require__, "p", {
|
||||||
get: function() { return window.kcContext.url.resourcesPath; },
|
get: function() { return window.kcContext.url.resourcesPath; },
|
||||||
set: function (){}
|
set: function() {}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return "u";
|
return "u";
|
||||||
@ -69,11 +73,11 @@ describe("bin/js-transforms", () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
t[(function (){
|
t[(function (){
|
||||||
var pd= Object.getOwnPropertyDescriptor(t, "p");
|
var pd = Object.getOwnPropertyDescriptor(t, "p");
|
||||||
if( pd === undefined || pd.configurable ){
|
if( pd === undefined || pd.configurable ){
|
||||||
Object.defineProperty(t, "p", {
|
Object.defineProperty(t, "p", {
|
||||||
get: function() { return window.kcContext.url.resourcesPath; },
|
get: function() { return window.kcContext.url.resourcesPath; },
|
||||||
set: function (){}
|
set: function() {}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return "miniCssF";
|
return "miniCssF";
|
||||||
@ -84,6 +88,27 @@ describe("bin/js-transforms", () => {
|
|||||||
} [e] + ".chunk.css"
|
} [e] + ".chunk.css"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
n[(function(){
|
||||||
|
var pd = Object.getOwnPropertyDescriptor(n, "p");
|
||||||
|
if( pd === undefined || pd.configurable ){
|
||||||
|
Object.defineProperty(n, "p", {
|
||||||
|
get: function() { return window.kcContext.url.resourcesPath; },
|
||||||
|
set: function() {}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return "u";
|
||||||
|
})()] = e => "/build/static/js/"+e+"."+{69:"4f205f87",128:"49264537",453:"b2fed72e",482:"f0106901"}[e]+".chunk.js"
|
||||||
|
|
||||||
|
t[(function(){
|
||||||
|
var pd = Object.getOwnPropertyDescriptor(t, "p");
|
||||||
|
if( pd === undefined || pd.configurable ){
|
||||||
|
Object.defineProperty(t, "p", {
|
||||||
|
get: function() { return window.kcContext.url.resourcesPath; },
|
||||||
|
set: function() {}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return "miniCssF";
|
||||||
|
})()] = e => "/build/static/css/"+e+"."+{164:"dcfd7749",908:"67c9ed2c"}[e]+".chunk.css"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
expect(isSameCode(fixedJsCode, fixedJsCodeExpected)).toBe(true);
|
expect(isSameCode(fixedJsCode, fixedJsCodeExpected)).toBe(true);
|
||||||
|
Reference in New Issue
Block a user