Compare commits
45 Commits
Author | SHA1 | Date | |
---|---|---|---|
3186d33ad7 | |||
b7fb8f98ae | |||
6e666b6ee2 | |||
4c66c9877a | |||
e8fce5f796 | |||
481131d58b | |||
0a4e71e149 | |||
96f5139421 | |||
1a326bf7e4 | |||
e1afc1cf7a | |||
bb007ddce5 | |||
b5dd0317c7 | |||
3c54541a73 | |||
2657f01135 | |||
7223409eb1 | |||
c41eae63e7 | |||
c8b85c43aa | |||
e918788c3f | |||
b53f4f997c | |||
481d93ebc4 | |||
1ce666f136 | |||
49a8e702bc | |||
5d59e652d7 | |||
02af8c7311 | |||
fadf4e867c | |||
0839859fef | |||
c122b48e35 | |||
cebb297bbf | |||
2e31b796f7 | |||
e0a61b51cb | |||
46e50e622b | |||
7cfa1df0b2 | |||
8a63648339 | |||
bb6b026720 | |||
2a13b314dc | |||
4506b3f6d4 | |||
804abef0de | |||
7e932b920e | |||
46fdfbc507 | |||
a4ff8607c5 | |||
7fe4eeda57 | |||
9f25cddaa4 | |||
eb64fe60d0 | |||
36f404e17d | |||
5398590939 |
@ -168,6 +168,24 @@
|
||||
"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,
|
||||
|
2
.github/workflows/ci.yaml
vendored
2
.github/workflows/ci.yaml
vendored
@ -3,7 +3,7 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- v*
|
||||
- v8
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
|
10
README.md
10
README.md
@ -47,12 +47,12 @@
|
||||
|
||||
> 📣 🛑 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).
|
||||
> 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
|
||||
> by the Keycloak team. Resolution is only a matter of time.
|
||||
> **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
|
||||
> 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.
|
||||
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).
|
||||
|
||||
## Sponsor 👼
|
||||
@ -117,6 +117,8 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
|
||||
<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://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>
|
||||
</tbody>
|
||||
</table>
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "keycloakify",
|
||||
"version": "8.1.2",
|
||||
"version": "8.4.4",
|
||||
"description": "Create Keycloak themes using React",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@ -112,7 +112,7 @@
|
||||
"@babel/parser": "^7.22.7",
|
||||
"@babel/types": "^7.22.5",
|
||||
"@octokit/rest": "^18.12.0",
|
||||
"cheerio": "^1.0.0-rc.5",
|
||||
"cheerio": "1.0.0-rc.5",
|
||||
"cli-select": "^1.1.2",
|
||||
"evt": "^2.4.18",
|
||||
"make-fetch-happen": "^11.0.3",
|
||||
|
@ -6,6 +6,7 @@ export type KcContext = KcContext.Password | KcContext.Account;
|
||||
|
||||
export declare namespace KcContext {
|
||||
export type Common = {
|
||||
themeVersion: string;
|
||||
keycloakifyVersion: string;
|
||||
themeType: "account";
|
||||
themeName: string;
|
||||
|
@ -7,6 +7,7 @@ import type { KcContext } from "./KcContext";
|
||||
const PUBLIC_URL = (typeof process !== "object" ? undefined : process.env?.["PUBLIC_URL"]) || "/";
|
||||
|
||||
export const kcContextCommonMock: KcContext.Common = {
|
||||
"themeVersion": "0.0.0",
|
||||
"keycloakifyVersion": "0.0.0",
|
||||
"themeType": "account",
|
||||
"themeName": "my-theme-name",
|
||||
|
@ -51,7 +51,7 @@ export default function Account(props: PageProps<Extract<KcContext, { pageId: "a
|
||||
id="username"
|
||||
name="username"
|
||||
disabled={!realm.editUsernameAllowed}
|
||||
value={account.username ?? ""}
|
||||
defaultValue={account.username ?? ""}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -66,7 +66,7 @@ export default function Account(props: PageProps<Extract<KcContext, { pageId: "a
|
||||
</div>
|
||||
|
||||
<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>
|
||||
|
||||
@ -79,7 +79,7 @@ export default function Account(props: PageProps<Extract<KcContext, { pageId: "a
|
||||
</div>
|
||||
|
||||
<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>
|
||||
|
||||
@ -92,7 +92,7 @@ export default function Account(props: PageProps<Extract<KcContext, { pageId: "a
|
||||
</div>
|
||||
|
||||
<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>
|
||||
|
||||
|
@ -4,7 +4,7 @@ import { crawl } from "./tools/crawl";
|
||||
import { join as pathJoin } from "path";
|
||||
import { themeTypes } from "./keycloakify/generateFtl";
|
||||
|
||||
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. */
|
||||
export function getThemeSrcDirPath(params: { projectDirPath: string }) {
|
||||
@ -14,13 +14,13 @@ export function getThemeSrcDirPath(params: { projectDirPath: string }) {
|
||||
|
||||
const themeSrcDirPath: string | undefined = crawl({ "dirPath": srcDirPath, "returnedPathsType": "relative to dirPath" })
|
||||
.map(fileRelativePath => {
|
||||
const split = fileRelativePath.split(themeSrcDirBasename);
|
||||
|
||||
if (split.length !== 2) {
|
||||
return undefined;
|
||||
for (const themeSrcDirBasename of themeSrcDirBasenames) {
|
||||
const split = fileRelativePath.split(themeSrcDirBasename);
|
||||
if (split.length === 2) {
|
||||
return pathJoin(srcDirPath, split[0] + themeSrcDirBasename);
|
||||
}
|
||||
}
|
||||
|
||||
return pathJoin(srcDirPath, split[0] + themeSrcDirBasename);
|
||||
return undefined;
|
||||
})
|
||||
.filter(exclude(undefined))[0];
|
||||
|
||||
@ -38,7 +38,7 @@ export function getThemeSrcDirPath(params: { projectDirPath: string }) {
|
||||
console.error(
|
||||
[
|
||||
"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"
|
||||
].join("\n")
|
||||
);
|
||||
|
@ -408,6 +408,14 @@
|
||||
out["themeName"] = "KEYCLOAKIFY_THEME_NAME_cXxKd3xEer";
|
||||
out["pageId"] = "${pageId}";
|
||||
|
||||
try {
|
||||
|
||||
out["url"]["resourcesCommonPath"] = out["url"]["resourcesPath"] + "/" + "RESOURCES_COMMON_cLsLsMrtDkpVv";
|
||||
|
||||
} catch(error) {
|
||||
|
||||
}
|
||||
|
||||
return out;
|
||||
|
||||
})()
|
||||
@ -423,7 +431,7 @@
|
||||
<#if isHash>
|
||||
|
||||
<#if path?size gt 10>
|
||||
<#return "ABORT: Too many recursive calls">
|
||||
<#return "ABORT: Too many recursive calls, path: " + path?join(".")>
|
||||
</#if>
|
||||
|
||||
<#local keys = "">
|
||||
@ -455,9 +463,10 @@
|
||||
<#-- https://github.com/keycloakify/keycloakify/issues/91#issue-1212319466 (reports with error.ftl and Kc18) -->
|
||||
<#-- https://github.com/keycloakify/keycloakify/issues/109#issuecomment-1134610163 -->
|
||||
<#-- https://github.com/keycloakify/keycloakify/issues/357 -->
|
||||
<#-- https://github.com/keycloakify/keycloakify/discussions/406#discussioncomment-7514787 -->
|
||||
key == "loginAction" &&
|
||||
are_same_path(path, ["url"]) &&
|
||||
["saml-post-form.ftl", "error.ftl", "info.ftl", "login-oauth-grant.ftl", "logout-confirm.ftl"]?seq_contains(pageId) &&
|
||||
["saml-post-form.ftl", "error.ftl", "info.ftl", "login-oauth-grant.ftl", "logout-confirm.ftl", "login-oauth2-device-verify-user-code.ftl"]?seq_contains(pageId) &&
|
||||
!(auth?has_content && auth.showTryAnotherWayLink())
|
||||
) || (
|
||||
<#-- https://github.com/keycloakify/keycloakify/issues/362 -->
|
||||
@ -478,20 +487,35 @@
|
||||
"error.ftl" == pageId &&
|
||||
are_same_path(path, ["realm"]) &&
|
||||
!["name", "displayName", "displayNameHtml", "internationalizationEnabled", "registrationEmailAsUsername" ]?seq_contains(key)
|
||||
) || (
|
||||
"applications.ftl" == pageId &&
|
||||
is_subpath(path, ["applications", "applications"]) &&
|
||||
(
|
||||
key == "realm" ||
|
||||
key == "container"
|
||||
)
|
||||
) || (
|
||||
are_same_path(path, ["user"]) &&
|
||||
key == "delegateForUpdate"
|
||||
)
|
||||
>
|
||||
<#local out_seq += ["/*If you need '" + key + "' on " + pageId + ", please submit an issue to the Keycloakify repo*/"]>
|
||||
<#local out_seq += ["/*If you need '" + path?join(".") + "." + key + "' on " + pageId + ", please submit an issue to the Keycloakify repo*/"]>
|
||||
<#continue>
|
||||
</#if>
|
||||
|
||||
<#if pageId == "register.ftl" && key == "attemptedUsername" && are_same_path(path, ["auth"])>
|
||||
<#-- https://github.com/keycloakify/keycloakify/discussions/406 -->
|
||||
<#if (
|
||||
["register.ftl", "info.ftl", "login.ftl", "login-update-password.ftl", "login-oauth2-device-verify-user-code.ftl"]?seq_contains(pageId) &&
|
||||
key == "attemptedUsername" && are_same_path(path, ["auth"])
|
||||
)>
|
||||
<#attempt>
|
||||
<#-- https://github.com/keycloak/keycloak/blob/3a2bf0c04bcde185e497aaa32d0bb7ab7520cf4a/themes/src/main/resources/theme/base/login/template.ftl#L63 -->
|
||||
<#-- https://github.com/keycloakify/keycloakify/discussions/406 -->
|
||||
<#if !(auth?has_content && auth.showUsername() && !auth.showResetCredentials())>
|
||||
<#local out_seq += ["/*If you need '" + key + "' on " + pageId + ", please submit an issue to the Keycloakify repo*/"]>
|
||||
<#continue>
|
||||
</#if>
|
||||
<#recover>
|
||||
<#local out_seq += ["/*Testing if attemptedUsername should be skipped throwed an exception */"]>
|
||||
</#attempt>
|
||||
</#if>
|
||||
|
||||
@ -644,9 +668,9 @@
|
||||
<#return "ABORT: Couldn't convert into string non hash, non method, non boolean, non enumerable object">
|
||||
|
||||
</#function>
|
||||
<#function are_same_path path searchedPath>
|
||||
<#function is_subpath path searchedPath>
|
||||
|
||||
<#if path?size != searchedPath?size>
|
||||
<#if path?size < searchedPath?size>
|
||||
<#return false>
|
||||
</#if>
|
||||
|
||||
@ -654,8 +678,14 @@
|
||||
|
||||
<#list path as property>
|
||||
|
||||
<#if i == searchedPath?size >
|
||||
<#continue>
|
||||
</#if>
|
||||
|
||||
<#local searchedProperty=searchedPath[i]>
|
||||
|
||||
<#local i+= 1>
|
||||
|
||||
<#if searchedProperty?is_string && searchedProperty == "*">
|
||||
<#continue>
|
||||
</#if>
|
||||
@ -672,11 +702,13 @@
|
||||
<#return false>
|
||||
</#if>
|
||||
|
||||
<#local i+= 1>
|
||||
|
||||
</#list>
|
||||
|
||||
<#return true>
|
||||
|
||||
</#function>
|
||||
|
||||
<#function are_same_path path searchedPath>
|
||||
<#return path?size == searchedPath?size && is_subpath(path, searchedPath)>
|
||||
</#function>
|
||||
</script>
|
@ -124,6 +124,27 @@ export function generateFtlFilesCodeFactory(params: {
|
||||
].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();
|
||||
|
||||
function generateFtlFilesCode(params: { pageId: string }): {
|
||||
|
@ -10,6 +10,8 @@ export const loginThemePageIds = [
|
||||
"login-reset-password.ftl",
|
||||
"login-verify-email.ftl",
|
||||
"terms.ftl",
|
||||
"login-oauth2-device-verify-user-code.ftl",
|
||||
"login-oauth-grant.ftl",
|
||||
"login-otp.ftl",
|
||||
"login-update-profile.ftl",
|
||||
"login-update-password.ftl",
|
||||
|
@ -20,7 +20,6 @@ export async function downloadKeycloakStaticResources(
|
||||
keycloakVersion: string;
|
||||
usedResources: {
|
||||
resourcesCommonFilePaths: string[];
|
||||
resourcesFilePaths: string[];
|
||||
} | undefined
|
||||
}
|
||||
) {
|
||||
|
@ -1,17 +1,15 @@
|
||||
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 type { ThemeType } from "../generateFtl";
|
||||
|
||||
/** Assumes the theme type exists */
|
||||
export function readStaticResourcesUsage(params: { keycloakifySrcDirPath: string; themeSrcDirPath: string; themeType: ThemeType }): {
|
||||
resourcesCommonFilePaths: string[];
|
||||
resourcesFilePaths: string[];
|
||||
} {
|
||||
const { keycloakifySrcDirPath, themeSrcDirPath, themeType } = params;
|
||||
|
||||
const resourcesCommonFilePaths = new Set<string>();
|
||||
const resourcesFilePaths = new Set<string>();
|
||||
|
||||
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));
|
||||
@ -26,58 +24,53 @@ export function readStaticResourcesUsage(params: { keycloakifySrcDirPath: string
|
||||
const wrap = readPaths({ rawSourceFile });
|
||||
|
||||
wrap.resourcesCommonFilePaths.forEach(filePath => resourcesCommonFilePaths.add(filePath));
|
||||
wrap.resourcesFilePaths.forEach(filePath => resourcesFilePaths.add(filePath));
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
"resourcesCommonFilePaths": Array.from(resourcesCommonFilePaths),
|
||||
"resourcesFilePaths": Array.from(resourcesFilePaths)
|
||||
"resourcesCommonFilePaths": Array.from(resourcesCommonFilePaths)
|
||||
};
|
||||
}
|
||||
|
||||
/** Exported for testing purpose */
|
||||
export function readPaths(params: { rawSourceFile: string }): {
|
||||
resourcesCommonFilePaths: string[];
|
||||
resourcesFilePaths: string[];
|
||||
} {
|
||||
const { rawSourceFile } = params;
|
||||
|
||||
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(`resourcesCommonPath\\s*}([^\`]+)\``, "g");
|
||||
|
||||
{
|
||||
const regexp = new RegExp(`resources${isCommon ? "Common" : ""}Path\\s*}([^\`]+)\``, "g");
|
||||
const matches = [...rawSourceFile.matchAll(regexp)];
|
||||
|
||||
const matches = [...rawSourceFile.matchAll(regexp)];
|
||||
for (const match of matches) {
|
||||
const filePath = match[1];
|
||||
|
||||
for (const match of matches) {
|
||||
const filePath = match[1];
|
||||
|
||||
set.add(filePath);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
const regexp = new RegExp(`resources${isCommon ? "Common" : ""}Path\\s*[+,]\\s*["']([^"'\`]+)["'\`]`, "g");
|
||||
|
||||
const matches = [...rawSourceFile.matchAll(regexp)];
|
||||
|
||||
for (const match of matches) {
|
||||
const filePath = match[1];
|
||||
|
||||
set.add(filePath);
|
||||
}
|
||||
resourcesCommonFilePaths.add(filePath);
|
||||
}
|
||||
}
|
||||
|
||||
const removePrefixSlash = (filePath: string) => (filePath.startsWith("/") ? filePath.slice(1) : filePath);
|
||||
{
|
||||
const regexp = new RegExp(`resourcesCommonPath\\s*[+,]\\s*["']([^"'\`]+)["'\`]`, "g");
|
||||
|
||||
const matches = [...rawSourceFile.matchAll(regexp)];
|
||||
|
||||
for (const match of matches) {
|
||||
const filePath = match[1];
|
||||
|
||||
resourcesCommonFilePaths.add(filePath);
|
||||
}
|
||||
}
|
||||
|
||||
const normalizePath = (filePath: string) => {
|
||||
filePath = filePath.startsWith("/") ? filePath.slice(1) : filePath;
|
||||
filePath = filePath.replace(/\//g, pathSep);
|
||||
return filePath;
|
||||
};
|
||||
|
||||
return {
|
||||
"resourcesCommonFilePaths": Array.from(resourcesCommonFilePaths).map(removePrefixSlash),
|
||||
"resourcesFilePaths": Array.from(resourcesFilePaths).map(removePrefixSlash)
|
||||
"resourcesCommonFilePaths": Array.from(resourcesCommonFilePaths).map(normalizePath)
|
||||
};
|
||||
}
|
||||
|
@ -23,24 +23,21 @@ export function usePrepareTemplate(params: {
|
||||
const removeArray: (() => void)[] = [];
|
||||
|
||||
(async () => {
|
||||
const prLoadedArray: Promise<void>[] = [];
|
||||
|
||||
styles.reverse().forEach(href => {
|
||||
for (const style of [...styles].reverse()) {
|
||||
const { prLoaded, remove } = headInsert({
|
||||
"type": "css",
|
||||
"position": "prepend",
|
||||
href
|
||||
"href": style
|
||||
});
|
||||
|
||||
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) {
|
||||
return;
|
||||
if (isUnmounted) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
setReady();
|
||||
|
@ -12,6 +12,8 @@ const Error = lazy(() => import("keycloakify/login/pages/Error"));
|
||||
const LoginResetPassword = lazy(() => import("keycloakify/login/pages/LoginResetPassword"));
|
||||
const LoginVerifyEmail = lazy(() => import("keycloakify/login/pages/LoginVerifyEmail"));
|
||||
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 LoginPassword = lazy(() => import("keycloakify/login/pages/LoginPassword"));
|
||||
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} />;
|
||||
case "terms.ftl":
|
||||
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":
|
||||
return <LoginOtp kcContext={kcContext} {...rest} />;
|
||||
case "login-username.ftl":
|
||||
|
@ -94,4 +94,5 @@ export type ClassKey =
|
||||
| "kcSelectOTPListItemClass"
|
||||
| "kcAuthenticatorOtpCircleClass"
|
||||
| "kcSelectOTPItemHeadingClass"
|
||||
| "kcFormOptionsWrapperClass";
|
||||
| "kcFormOptionsWrapperClass"
|
||||
| "kcFormButtonsWrapperClass";
|
||||
|
@ -18,6 +18,8 @@ export type KcContext =
|
||||
| KcContext.LoginResetPassword
|
||||
| KcContext.LoginVerifyEmail
|
||||
| KcContext.Terms
|
||||
| KcContext.LoginDeviceVerifyUserCode
|
||||
| KcContext.LoginOauthGrant
|
||||
| KcContext.LoginOtp
|
||||
| KcContext.LoginUsername
|
||||
| KcContext.WebauthnAuthenticate
|
||||
@ -37,6 +39,7 @@ export type KcContext =
|
||||
|
||||
export declare namespace KcContext {
|
||||
export type Common = {
|
||||
themeVersion: string;
|
||||
keycloakifyVersion: string;
|
||||
themeType: "login";
|
||||
themeName: string;
|
||||
@ -241,6 +244,27 @@ export declare namespace KcContext {
|
||||
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 & {
|
||||
pageId: "login-otp.ftl";
|
||||
otpLogin: {
|
||||
|
@ -103,6 +103,7 @@ const attributes: Attribute[] = [
|
||||
const attributesByName = Object.fromEntries(attributes.map(attribute => [attribute.name, attribute])) as any;
|
||||
|
||||
export const kcContextCommonMock: KcContext.Common = {
|
||||
"themeVersion": "0.0.0",
|
||||
"keycloakifyVersion": "0.0.0",
|
||||
"themeType": "login",
|
||||
"themeName": "my-theme-name",
|
||||
@ -240,7 +241,9 @@ export const kcContextCommonMock: KcContext.Common = {
|
||||
const loginUrl = {
|
||||
...kcContextCommonMock.url,
|
||||
"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 = [
|
||||
@ -344,6 +347,25 @@ export const kcContextMocks = [
|
||||
...kcContextCommonMock,
|
||||
"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>({
|
||||
...kcContextCommonMock,
|
||||
"pageId": "login-otp.ftl",
|
||||
|
@ -45,6 +45,7 @@ export const { useGetClassName } = createUseClassName<ClassKey>({
|
||||
"kcInputClass": "form-control",
|
||||
"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",
|
||||
"kcFormButtonsWrapperClass": undefined,
|
||||
"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",
|
||||
"kcFormSettingClass": "login-pf-settings",
|
||||
|
@ -8,7 +8,10 @@ export default function Info(props: PageProps<Extract<KcContext, { pageId: "info
|
||||
|
||||
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;
|
||||
|
||||
|
@ -32,7 +32,7 @@ export default function LoginConfigTotp(props: PageProps<Extract<KcContext, { pa
|
||||
|
||||
<ul id="kc-totp-supported-apps">
|
||||
{totp.supportedApplications.map(app => (
|
||||
<li>{msgStr(app as MessageKey, app)}</li>
|
||||
<li>{msg(app as MessageKey)}</li>
|
||||
))}
|
||||
</ul>
|
||||
</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 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",
|
||||
"lib/zocial/zocial.css",
|
||||
"node_modules/jquery/dist/jquery.min.js"
|
||||
],
|
||||
"resourcesFilePaths": ["css/login.css"]
|
||||
]
|
||||
};
|
||||
|
||||
it("works with coding style n°1", () => {
|
||||
|
112
yarn.lock
112
yarn.lock
@ -4559,30 +4559,29 @@ check-error@^1.0.2:
|
||||
resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82"
|
||||
integrity sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==
|
||||
|
||||
cheerio-select@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/cheerio-select/-/cheerio-select-2.1.0.tgz#4d8673286b8126ca2a8e42740d5e3c4884ae21b4"
|
||||
integrity sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==
|
||||
cheerio-select-tmp@^0.1.0:
|
||||
version "0.1.1"
|
||||
resolved "https://registry.yarnpkg.com/cheerio-select-tmp/-/cheerio-select-tmp-0.1.1.tgz#55bbef02a4771710195ad736d5e346763ca4e646"
|
||||
integrity sha512-YYs5JvbpU19VYJyj+F7oYrIE2BOll1/hRU7rEy/5+v9BzkSo3bK81iAeeQEMI92vRIxz677m72UmJUiVwwgjfQ==
|
||||
dependencies:
|
||||
boolbase "^1.0.0"
|
||||
css-select "^5.1.0"
|
||||
css-what "^6.1.0"
|
||||
domelementtype "^2.3.0"
|
||||
domhandler "^5.0.3"
|
||||
domutils "^3.0.1"
|
||||
css-select "^3.1.2"
|
||||
css-what "^4.0.0"
|
||||
domelementtype "^2.1.0"
|
||||
domhandler "^4.0.0"
|
||||
domutils "^2.4.4"
|
||||
|
||||
cheerio@^1.0.0-rc.5:
|
||||
version "1.0.0-rc.12"
|
||||
resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-1.0.0-rc.12.tgz#788bf7466506b1c6bf5fae51d24a2c4d62e47683"
|
||||
integrity sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==
|
||||
cheerio@1.0.0-rc.5:
|
||||
version "1.0.0-rc.5"
|
||||
resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-1.0.0-rc.5.tgz#88907e1828674e8f9fee375188b27dadd4f0fa2f"
|
||||
integrity sha512-yoqps/VCaZgN4pfXtenwHROTp8NG6/Hlt4Jpz2FEP0ZJQ+ZUkVDd0hAPDNKhj3nakpfPt/CNs57yEtxD1bXQiw==
|
||||
dependencies:
|
||||
cheerio-select "^2.1.0"
|
||||
dom-serializer "^2.0.0"
|
||||
domhandler "^5.0.3"
|
||||
domutils "^3.0.1"
|
||||
htmlparser2 "^8.0.1"
|
||||
parse5 "^7.0.0"
|
||||
parse5-htmlparser2-tree-adapter "^7.0.0"
|
||||
cheerio-select-tmp "^0.1.0"
|
||||
dom-serializer "~1.2.0"
|
||||
domhandler "^4.0.0"
|
||||
entities "~2.1.0"
|
||||
htmlparser2 "^6.0.0"
|
||||
parse5 "^6.0.0"
|
||||
parse5-htmlparser2-tree-adapter "^6.0.0"
|
||||
|
||||
chokidar@^2.1.8:
|
||||
version "2.1.8"
|
||||
@ -5164,6 +5163,17 @@ css-loader@^5.0.1:
|
||||
schema-utils "^3.0.0"
|
||||
semver "^7.3.5"
|
||||
|
||||
css-select@^3.1.2:
|
||||
version "3.1.2"
|
||||
resolved "https://registry.yarnpkg.com/css-select/-/css-select-3.1.2.tgz#d52cbdc6fee379fba97fb0d3925abbd18af2d9d8"
|
||||
integrity sha512-qmss1EihSuBNWNNhHjxzxSfJoFBM/lERB/Q4EnsJQQC62R2evJDW481091oAdOr9uh46/0n4nrg0It5cAnj1RA==
|
||||
dependencies:
|
||||
boolbase "^1.0.0"
|
||||
css-what "^4.0.0"
|
||||
domhandler "^4.0.0"
|
||||
domutils "^2.4.3"
|
||||
nth-check "^2.0.0"
|
||||
|
||||
css-select@^4.1.3:
|
||||
version "4.3.0"
|
||||
resolved "https://registry.yarnpkg.com/css-select/-/css-select-4.3.0.tgz#db7129b2846662fd8628cfc496abb2b59e41529b"
|
||||
@ -5175,18 +5185,12 @@ css-select@^4.1.3:
|
||||
domutils "^2.8.0"
|
||||
nth-check "^2.0.1"
|
||||
|
||||
css-select@^5.1.0:
|
||||
version "5.1.0"
|
||||
resolved "https://registry.yarnpkg.com/css-select/-/css-select-5.1.0.tgz#b8ebd6554c3637ccc76688804ad3f6a6fdaea8a6"
|
||||
integrity sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==
|
||||
dependencies:
|
||||
boolbase "^1.0.0"
|
||||
css-what "^6.1.0"
|
||||
domhandler "^5.0.2"
|
||||
domutils "^3.0.1"
|
||||
nth-check "^2.0.1"
|
||||
css-what@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/css-what/-/css-what-4.0.0.tgz#35e73761cab2eeb3d3661126b23d7aa0e8432233"
|
||||
integrity sha512-teijzG7kwYfNVsUh2H/YN62xW3KK9YhXEgSlbxMlcyjPNvdKJqFx5lrwlJgoFP1ZHlB89iGDlo/JyshKeRhv5A==
|
||||
|
||||
css-what@^6.0.1, css-what@^6.1.0:
|
||||
css-what@^6.0.1:
|
||||
version "6.1.0"
|
||||
resolved "https://registry.yarnpkg.com/css-what/-/css-what-6.1.0.tgz#fb5effcf76f1ddea2c81bdfaa4de44e79bac70f4"
|
||||
integrity sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==
|
||||
@ -5471,6 +5475,15 @@ dom-serializer@^2.0.0:
|
||||
domhandler "^5.0.2"
|
||||
entities "^4.2.0"
|
||||
|
||||
dom-serializer@~1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-1.2.0.tgz#3433d9136aeb3c627981daa385fc7f32d27c48f1"
|
||||
integrity sha512-n6kZFH/KlCrqs/1GHMOd5i2fd/beQHuehKdWvNNffbGHTr/almdhuVvTVFb3V7fglz+nC50fFusu3lY33h12pA==
|
||||
dependencies:
|
||||
domelementtype "^2.0.1"
|
||||
domhandler "^4.0.0"
|
||||
entities "^2.0.0"
|
||||
|
||||
dom-walk@^0.1.0:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/dom-walk/-/dom-walk-0.1.2.tgz#0c548bef048f4d1f2a97249002236060daa3fd84"
|
||||
@ -5481,7 +5494,7 @@ domain-browser@^1.1.1:
|
||||
resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda"
|
||||
integrity sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==
|
||||
|
||||
domelementtype@^2.0.1, domelementtype@^2.2.0, domelementtype@^2.3.0:
|
||||
domelementtype@^2.0.1, domelementtype@^2.1.0, domelementtype@^2.2.0, domelementtype@^2.3.0:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.3.0.tgz#5c45e8e869952626331d7aab326d01daf65d589d"
|
||||
integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==
|
||||
@ -5500,7 +5513,7 @@ domhandler@^5.0, domhandler@^5.0.1, domhandler@^5.0.2, domhandler@^5.0.3:
|
||||
dependencies:
|
||||
domelementtype "^2.3.0"
|
||||
|
||||
domutils@^2.5.2, domutils@^2.8.0:
|
||||
domutils@^2.4.3, domutils@^2.4.4, domutils@^2.5.2, domutils@^2.8.0:
|
||||
version "2.8.0"
|
||||
resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.8.0.tgz#4437def5db6e2d1f5d6ee859bd95ca7d02048135"
|
||||
integrity sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==
|
||||
@ -5658,6 +5671,11 @@ entities@^4.2.0, entities@^4.4.0:
|
||||
resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48"
|
||||
integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==
|
||||
|
||||
entities@~2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/entities/-/entities-2.1.0.tgz#992d3129cf7df6870b96c57858c249a120f8b8b5"
|
||||
integrity sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==
|
||||
|
||||
err-code@^2.0.2:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/err-code/-/err-code-2.0.3.tgz#23c2f3b756ffdfc608d30e27c9a941024807e7f9"
|
||||
@ -6987,7 +7005,7 @@ html-webpack-plugin@^5.0.0:
|
||||
pretty-error "^4.0.0"
|
||||
tapable "^2.0.0"
|
||||
|
||||
htmlparser2@^6.1.0:
|
||||
htmlparser2@^6.0.0, htmlparser2@^6.1.0:
|
||||
version "6.1.0"
|
||||
resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-6.1.0.tgz#c4d762b6c3371a05dbe65e94ae43a9f845fb8fb7"
|
||||
integrity sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==
|
||||
@ -6997,7 +7015,7 @@ htmlparser2@^6.1.0:
|
||||
domutils "^2.5.2"
|
||||
entities "^2.0.0"
|
||||
|
||||
htmlparser2@^8.0, htmlparser2@^8.0.1:
|
||||
htmlparser2@^8.0:
|
||||
version "8.0.2"
|
||||
resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-8.0.2.tgz#f002151705b383e62433b5cf466f5b716edaec21"
|
||||
integrity sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==
|
||||
@ -8823,7 +8841,7 @@ npmlog@^5.0.1:
|
||||
gauge "^3.0.0"
|
||||
set-blocking "^2.0.0"
|
||||
|
||||
nth-check@^2.0.1:
|
||||
nth-check@^2.0.0, nth-check@^2.0.1:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.1.1.tgz#c9eab428effce36cd6b92c924bdb000ef1f1ed1d"
|
||||
integrity sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==
|
||||
@ -9176,26 +9194,18 @@ parse-json@^5.0.0:
|
||||
json-parse-even-better-errors "^2.3.0"
|
||||
lines-and-columns "^1.1.6"
|
||||
|
||||
parse5-htmlparser2-tree-adapter@^7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.0.0.tgz#23c2cc233bcf09bb7beba8b8a69d46b08c62c2f1"
|
||||
integrity sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==
|
||||
parse5-htmlparser2-tree-adapter@^6.0.0:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz#2cdf9ad823321140370d4dbf5d3e92c7c8ddc6e6"
|
||||
integrity sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==
|
||||
dependencies:
|
||||
domhandler "^5.0.2"
|
||||
parse5 "^7.0.0"
|
||||
parse5 "^6.0.1"
|
||||
|
||||
parse5@^6.0.0:
|
||||
parse5@^6.0.0, parse5@^6.0.1:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b"
|
||||
integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==
|
||||
|
||||
parse5@^7.0.0:
|
||||
version "7.1.2"
|
||||
resolved "https://registry.yarnpkg.com/parse5/-/parse5-7.1.2.tgz#0736bebbfd77793823240a23b7fc5e010b7f8e32"
|
||||
integrity sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==
|
||||
dependencies:
|
||||
entities "^4.4.0"
|
||||
|
||||
parseurl@~1.3.2, parseurl@~1.3.3:
|
||||
version "1.3.3"
|
||||
resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4"
|
||||
|
Reference in New Issue
Block a user