Compare commits
31 Commits
Author | SHA1 | Date | |
---|---|---|---|
73e7f64860 | |||
e17e1650d5 | |||
3ecb63d500 | |||
41ee7e90ef | |||
c70bba727e | |||
747248454d | |||
59386241b4 | |||
c70b9b0dd1 | |||
2ee00ed919 | |||
cbfc271da5 | |||
d45b492837 | |||
ed54c145b7 | |||
64ed9a6044 | |||
75267abd91 | |||
ba9a3992b7 | |||
a74c32ed6d | |||
c5f9812acc | |||
bb0d6853e5 | |||
8c9fe168d8 | |||
6c874c01b7 | |||
5bc84b621c | |||
dd421eedf5 | |||
570d8a73cc | |||
a95df42843 | |||
4ecbb30a1b | |||
96b40b9c49 | |||
c32eebdd46 | |||
5b17287555 | |||
fb01257c8b | |||
53470f8788 | |||
89b86936f6 |
33
CHANGELOG.md
33
CHANGELOG.md
@ -1,3 +1,36 @@
|
|||||||
|
### **4.2.6** (2021-11-08)
|
||||||
|
|
||||||
|
- Fix deepClone so we can overwrite with undefined in when we mock kcContext
|
||||||
|
|
||||||
|
### **4.2.5** (2021-11-07)
|
||||||
|
|
||||||
|
- Better debugging experience with user profile
|
||||||
|
|
||||||
|
### **4.2.4** (2021-11-01)
|
||||||
|
|
||||||
|
- Better autoComplete typings
|
||||||
|
|
||||||
|
### **4.2.3** (2021-11-01)
|
||||||
|
|
||||||
|
- Make it more easy to understand that error in the log are expected
|
||||||
|
|
||||||
|
### **4.2.2** (2021-10-27)
|
||||||
|
|
||||||
|
- Replace 'path' by 'browserify-path' #47
|
||||||
|
|
||||||
|
### **4.2.1** (2021-10-26)
|
||||||
|
|
||||||
|
- useFormValidationSlice: update when params have changed
|
||||||
|
- Explains that the password can't be validated
|
||||||
|
|
||||||
|
## **4.2.0** (2021-10-26)
|
||||||
|
|
||||||
|
- Export types definitions for Attribue and Validator
|
||||||
|
|
||||||
|
## **4.1.0** (2021-10-26)
|
||||||
|
|
||||||
|
- Document what's new in v4
|
||||||
|
|
||||||
# **4.0.0** (2021-10-26)
|
# **4.0.0** (2021-10-26)
|
||||||
|
|
||||||
- fix RegisterUserProfile password confirmation field
|
- fix RegisterUserProfile password confirmation field
|
||||||
|
99
README.md
99
README.md
@ -20,30 +20,10 @@
|
|||||||
<img src="https://user-images.githubusercontent.com/6702424/110260457-a1c3d380-7fac-11eb-853a-80459b65626b.png">
|
<img src="https://user-images.githubusercontent.com/6702424/110260457-a1c3d380-7fac-11eb-853a-80459b65626b.png">
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
**NEW in v3**
|
**NEW in v4**
|
||||||
|
|
||||||
No breaking changes except that `@emotion/react`, [`tss-react`](https://www.npmjs.com/package/tss-react) and [`powerhooks`](https://www.npmjs.com/package/powerhooks) are now `peerDependencies` instead of being just dependencies.
|
- Out of the box [frontend form validation](#user-profile-and-frontend-form-validation) 🥳
|
||||||
It's important to avoid problem when using `keycloakify` alongside [`mui`](https://mui.com) and
|
- Improvements (and breaking changes in `import { useKcMessage } from "keycloakify"`.
|
||||||
[when passing params from the app to the login page](https://github.com/InseeFrLab/keycloakify#implement-context-persistence-optional).
|
|
||||||
|
|
||||||
**NEW in v2.5**
|
|
||||||
|
|
||||||
- User Profile ([`register-user-profile.ftl`](https://github.com/InseeFrLab/keycloakify/blob/main/src/lib/components/RegisterUserProfile.tsx))
|
|
||||||
is now supported! 🎉
|
|
||||||
It enables to [define, from the admin console](https://user-images.githubusercontent.com/6702424/136872461-1f5b64ef-d2ef-4c6b-bb8d-07d4729552b3.png),
|
|
||||||
what information you want to collect on your users in the register page and to validate inputs
|
|
||||||
[**on the frontend**, in realtime](https://github.com/InseeFrLab/keycloakify/blob/6dca6a93d8cfe634ee4d8574ad0c091641220092/src/lib/getKcContext/KcContextBase.ts#L225-L261)!
|
|
||||||
NOTE: User profile is only available in Keycloak 15 and it's a beta feature that
|
|
||||||
[needs to be enabled when launching keycloak](https://github.com/InseeFrLab/keycloakify/blob/59f106bf9e210b63b190826da2bf5f75fc8b7644/src/bin/build-keycloak-theme/build-keycloak-theme.ts#L116-L117) and [enabled in the console](https://user-images.githubusercontent.com/6702424/136874428-b071d614-c7f7-440d-9b2e-670faadc0871.png).
|
|
||||||
- Feature [Use advanced message](https://github.com/InseeFrLab/keycloakify/blob/59f106bf9e210b63b190826da2bf5f75fc8b7644/src/lib/i18n/useKcMessage.tsx#L53-L66)
|
|
||||||
and [`messagesPerFields`](https://github.com/InseeFrLab/keycloakify/blob/59f106bf9e210b63b190826da2bf5f75fc8b7644/src/lib/getKcContext/KcContextBase.ts#L70-L75) (implementation [here](https://github.com/InseeFrLab/keycloakify/blob/59f106bf9e210b63b190826da2bf5f75fc8b7644/src/bin/build-keycloak-theme/generateFtl/common.ftl#L130-L189))
|
|
||||||
- Test container now uses Keycloak version `15.0.2`.
|
|
||||||
|
|
||||||
**NEW in v2**
|
|
||||||
|
|
||||||
- It's now possible to implement custom `.ftl` pages.
|
|
||||||
- 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`).
|
|
||||||
|
|
||||||
# Motivations
|
# Motivations
|
||||||
|
|
||||||
@ -85,6 +65,7 @@ If you already have a Keycloak custom theme, it can be easily ported to Keycloak
|
|||||||
- [Advanced pages configuration](#advanced-pages-configuration)
|
- [Advanced pages configuration](#advanced-pages-configuration)
|
||||||
- [Hot reload](#hot-reload)
|
- [Hot reload](#hot-reload)
|
||||||
- [Enable loading in a blink of an eye of login pages ⚡ (--external-assets)](#enable-loading-in-a-blink-of-an-eye-of-login-pages----external-assets)
|
- [Enable loading in a blink of an eye of login pages ⚡ (--external-assets)](#enable-loading-in-a-blink-of-an-eye-of-login-pages----external-assets)
|
||||||
|
- [User profile and frontend form validation](#user-profile-and-frontend-form-validation)
|
||||||
- [Support for Terms and conditions](#support-for-terms-and-conditions)
|
- [Support for Terms and conditions](#support-for-terms-and-conditions)
|
||||||
- [Some pages still have the default theme. Why?](#some-pages-still-have-the-default-theme-why)
|
- [Some pages still have the default theme. Why?](#some-pages-still-have-the-default-theme-why)
|
||||||
- [GitHub Actions](#github-actions)
|
- [GitHub Actions](#github-actions)
|
||||||
@ -98,6 +79,11 @@ If you already have a Keycloak custom theme, it can be easily ported to Keycloak
|
|||||||
- [About the errors related to `objectToJson` in Keycloak logs.](#about-the-errors-related-to-objecttojson-in-keycloak-logs)
|
- [About the errors related to `objectToJson` in Keycloak logs.](#about-the-errors-related-to-objecttojson-in-keycloak-logs)
|
||||||
- [Adding custom message (to `i18n/useKcMessage.tsx`)](#adding-custom-message-to-i18nusekcmessagetsx)
|
- [Adding custom message (to `i18n/useKcMessage.tsx`)](#adding-custom-message-to-i18nusekcmessagetsx)
|
||||||
- [Email domain whitelist](#email-domain-whitelist)
|
- [Email domain whitelist](#email-domain-whitelist)
|
||||||
|
- [Changelog highlights](#changelog-highlights)
|
||||||
|
- [v4](#v4)
|
||||||
|
- [v3](#v3)
|
||||||
|
- [v2.5](#v25)
|
||||||
|
- [v2](#v2)
|
||||||
|
|
||||||
# Requirements
|
# Requirements
|
||||||
|
|
||||||
@ -313,6 +299,32 @@ performance boost if you jump through those hoops:
|
|||||||
|
|
||||||
Checkout a complete setup [here](https://github.com/garronej/keycloakify-demo-app#about-keycloakify)
|
Checkout a complete setup [here](https://github.com/garronej/keycloakify-demo-app#about-keycloakify)
|
||||||
|
|
||||||
|
# User profile and frontend form validation
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="https://github.com/InseeFrLab/keycloakify/releases/download/v0.0.1/keycloakify_fontend_validation.mp4">
|
||||||
|
<img src="https://user-images.githubusercontent.com/6702424/138880146-6fef3280-c4a5-46d2-bbb3-8b9598c057a5.gif">
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
User Profile is a Keycloak feature that enables to
|
||||||
|
[define, from the admin console](https://user-images.githubusercontent.com/6702424/136872461-1f5b64ef-d2ef-4c6b-bb8d-07d4729552b3.png),
|
||||||
|
what information you want to collect on your users in the register page and to validate inputs
|
||||||
|
[**on the frontend**, in realtime](https://github.com/InseeFrLab/keycloakify/blob/6dca6a93d8cfe634ee4d8574ad0c091641220092/src/lib/getKcContext/KcContextBase.ts#L225-L261)!
|
||||||
|
|
||||||
|
NOTE: User profile is only available in Keycloak 15 and it's a beta feature that
|
||||||
|
[needs to be enabled when launching keycloak](https://github.com/InseeFrLab/keycloakify/blob/59f106bf9e210b63b190826da2bf5f75fc8b7644/src/bin/build-keycloak-theme/build-keycloak-theme.ts#L116-L117)
|
||||||
|
and [enabled in the console](https://user-images.githubusercontent.com/6702424/136874428-b071d614-c7f7-440d-9b2e-670faadc0871.png).
|
||||||
|
|
||||||
|
Keycloakify, in [`register-user-profile.ftl`](https://github.com/InseeFrLab/keycloakify/blob/main/src/lib/components/RegisterUserProfile.tsx),
|
||||||
|
provides frontend validation out of the box.
|
||||||
|
|
||||||
|
For implementing your own `register-user-profile.ftl` page, you can use [`import { useFormValidationSlice } from "keycloakify";`](https://github.com/InseeFrLab/keycloakify/blob/main/src/lib/useFormValidationSlice.tsx).
|
||||||
|
Find usage example [`here`](https://github.com/InseeFrLab/keycloakify/blob/d3a07edfcb3739e30032dc96fc2a55944dfc3387/src/lib/components/RegisterUserProfile.tsx#L79-L112).
|
||||||
|
|
||||||
|
As for right now [it's not possible to define a pattern for the password](https://keycloak.discourse.group/t/make-password-policies-available-to-freemarker/11632)
|
||||||
|
from the admin console. You can however pass validators for it to the `useFormValidationSlice` function.
|
||||||
|
|
||||||
# Support for Terms and conditions
|
# Support for Terms and conditions
|
||||||
|
|
||||||
[Many organizations have a requirement that when a new user logs in for the first time, they need to agree to the terms and conditions of the website.](https://www.keycloak.org/docs/4.8/server_admin/#terms-and-conditions).
|
[Many organizations have a requirement that when a new user logs in for the first time, they need to agree to the terms and conditions of the website.](https://www.keycloak.org/docs/4.8/server_admin/#terms-and-conditions).
|
||||||
@ -432,18 +444,20 @@ The logs of your keycloak server will always show this kind of errors every time
|
|||||||
|
|
||||||
```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_please_ignore_errors" at line 70, column 21]
|
||||||
- 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_please_ignore_errors" at line 36, column 5]
|
||||||
- Reached through: @objectToJson object=value depth=(dep... [in template "login.ftl" in macro "objectToJson" at line 81, column 27]
|
- Reached through: @objectToJson_please_ignore_errors object=value depth=(dep... [in template "login.ftl" in macro "objectToJson_please_ignore_errors" at line 81, column 27]
|
||||||
- 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_please_ignore_errors" at line 36, column 5]
|
||||||
- Reached through: @objectToJson object=(.data_model) de... [in template "login.ftl" at line 163, column 43]
|
- Reached through: @objectToJson_please_ignore_errors object=(.data_model) de... [in template "login.ftl" at line 163, column 43]
|
||||||
```
|
```
|
||||||
|
|
||||||
Theses are expected and can be safely ignored.
|
Theses are expected to show up in the log.
|
||||||
|
Unfortunately, there is nothing I know of that can be done to avoid them or even mute them.
|
||||||
|
They can be, however, 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)
|
||||||
without making assumptions on the `.data_model` we have to do things that throws.
|
without making assumptions on the `.data_model` we have to do things that throws.
|
||||||
It's all-right though because every statement that can fail is inside an `<#attempt><#recorver>` block but it results in errors being printed to the logs.
|
It's all-right because every statement that can fail is inside an `<#attempt><#recorver>` block but it results in errors being printed to the logs.
|
||||||
|
|
||||||
# Adding custom message (to `i18n/useKcMessage.tsx`)
|
# Adding custom message (to `i18n/useKcMessage.tsx`)
|
||||||
|
|
||||||
@ -455,3 +469,28 @@ This approach is a bit hacky as it doesn't provide type safety but it works.
|
|||||||
|
|
||||||
If you want to restrict the emails domain that can register, you can use [this plugin](https://github.com/micedre/keycloak-mail-whitelisting)
|
If you want to restrict the emails domain that can register, you can use [this plugin](https://github.com/micedre/keycloak-mail-whitelisting)
|
||||||
and `kcRegisterContext["authorizedMailDomains"]` to validate on.
|
and `kcRegisterContext["authorizedMailDomains"]` to validate on.
|
||||||
|
|
||||||
|
# Changelog highlights
|
||||||
|
|
||||||
|
## v4
|
||||||
|
|
||||||
|
- Out of the box [frontend form validation](#user-profile-and-frontend-form-validation) 🥳
|
||||||
|
- Improvements (and breaking changes in `import { useKcMessage } from "keycloakify"`.
|
||||||
|
|
||||||
|
## v3
|
||||||
|
|
||||||
|
No breaking changes except that `@emotion/react`, [`tss-react`](https://www.npmjs.com/package/tss-react) and [`powerhooks`](https://www.npmjs.com/package/powerhooks) are now `peerDependencies` instead of being just dependencies.
|
||||||
|
It's important to avoid problem when using `keycloakify` alongside [`mui`](https://mui.com) and
|
||||||
|
[when passing params from the app to the login page](https://github.com/InseeFrLab/keycloakify#implement-context-persistence-optional).
|
||||||
|
|
||||||
|
## v2.5
|
||||||
|
|
||||||
|
- Feature [Use advanced message](https://github.com/InseeFrLab/keycloakify/blob/59f106bf9e210b63b190826da2bf5f75fc8b7644/src/lib/i18n/useKcMessage.tsx#L53-L66)
|
||||||
|
and [`messagesPerFields`](https://github.com/InseeFrLab/keycloakify/blob/59f106bf9e210b63b190826da2bf5f75fc8b7644/src/lib/getKcContext/KcContextBase.ts#L70-L75) (implementation [here](https://github.com/InseeFrLab/keycloakify/blob/59f106bf9e210b63b190826da2bf5f75fc8b7644/src/bin/build-keycloak-theme/generateFtl/common.ftl#L130-L189))
|
||||||
|
- Test container now uses Keycloak version `15.0.2`.
|
||||||
|
|
||||||
|
## v2
|
||||||
|
|
||||||
|
- It's now possible to implement custom `.ftl` pages.
|
||||||
|
- 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`).
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "keycloakify",
|
"name": "keycloakify",
|
||||||
"version": "4.0.0",
|
"version": "4.2.6",
|
||||||
"description": "Keycloak theme generator for Reacts app",
|
"description": "Keycloak theme generator for Reacts app",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@ -56,7 +56,7 @@
|
|||||||
"homepage": "https://github.com/garronej/keycloakify",
|
"homepage": "https://github.com/garronej/keycloakify",
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@emotion/react": "^11.4.1",
|
"@emotion/react": "^11.4.1",
|
||||||
"powerhooks": "^0.11.0",
|
"powerhooks": "^0.10.0",
|
||||||
"react": "^16.8.0 || ^17.0.0",
|
"react": "^16.8.0 || ^17.0.0",
|
||||||
"tss-react": "^1.1.0"
|
"tss-react": "^1.1.0"
|
||||||
},
|
},
|
||||||
@ -79,7 +79,7 @@
|
|||||||
"cheerio": "^1.0.0-rc.5",
|
"cheerio": "^1.0.0-rc.5",
|
||||||
"evt": "2.0.0-beta.38",
|
"evt": "2.0.0-beta.38",
|
||||||
"minimal-polyfills": "^2.2.1",
|
"minimal-polyfills": "^2.2.1",
|
||||||
"path": "^0.12.7",
|
"path-browserify": "^1.0.1",
|
||||||
"react-markdown": "^5.0.3",
|
"react-markdown": "^5.0.3",
|
||||||
"scripting-tools": "^0.19.13",
|
"scripting-tools": "^0.19.13",
|
||||||
"tsafe": "^0.8.1"
|
"tsafe": "^0.8.1"
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<script>const _=
|
<script>const _=
|
||||||
<#macro objectToJson object depth>
|
<#macro objectToJson_please_ignore_errors object depth>
|
||||||
<@compress>
|
<@compress>
|
||||||
|
|
||||||
<#local isHash = false>
|
<#local isHash = false>
|
||||||
@ -45,7 +45,7 @@
|
|||||||
<#continue>
|
<#continue>
|
||||||
</#if>
|
</#if>
|
||||||
|
|
||||||
"${key}": <@objectToJson object=value depth=depth+1/>,
|
"${key}": <@objectToJson_please_ignore_errors object=value depth=depth+1/>,
|
||||||
|
|
||||||
</#list>
|
</#list>
|
||||||
|
|
||||||
@ -102,7 +102,7 @@
|
|||||||
|
|
||||||
<#list object as item>
|
<#list object as item>
|
||||||
|
|
||||||
<@objectToJson object=item depth=depth+1/>,
|
<@objectToJson_please_ignore_errors object=item depth=depth+1/>,
|
||||||
|
|
||||||
</#list>
|
</#list>
|
||||||
|
|
||||||
@ -194,7 +194,7 @@
|
|||||||
Object.deepAssign(
|
Object.deepAssign(
|
||||||
out,
|
out,
|
||||||
//Removing all the undefined
|
//Removing all the undefined
|
||||||
JSON.parse(JSON.stringify(<@objectToJson object=.data_model depth=0 />))
|
JSON.parse(JSON.stringify(<@objectToJson_please_ignore_errors object=.data_model depth=0 />))
|
||||||
);
|
);
|
||||||
|
|
||||||
Object.deepAssign(
|
Object.deepAssign(
|
||||||
|
@ -149,15 +149,6 @@ const UserProfileFormFields = memo(({ kcContext, onIsFormSubmittableValueChange,
|
|||||||
</div>
|
</div>
|
||||||
<div className={cx(props.kcInputWrapperClass)}>
|
<div className={cx(props.kcInputWrapperClass)}>
|
||||||
<input
|
<input
|
||||||
autoComplete={(() => {
|
|
||||||
switch (attribute.name) {
|
|
||||||
case "password-confirm":
|
|
||||||
case "password":
|
|
||||||
return "new-password";
|
|
||||||
default:
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
})()}
|
|
||||||
type={(() => {
|
type={(() => {
|
||||||
switch (attribute.name) {
|
switch (attribute.name) {
|
||||||
case "password-confirm":
|
case "password-confirm":
|
||||||
@ -174,11 +165,7 @@ const UserProfileFormFields = memo(({ kcContext, onIsFormSubmittableValueChange,
|
|||||||
className={cx(props.kcInputClass)}
|
className={cx(props.kcInputClass)}
|
||||||
aria-invalid={displayableErrors.length !== 0}
|
aria-invalid={displayableErrors.length !== 0}
|
||||||
disabled={attribute.readOnly}
|
disabled={attribute.readOnly}
|
||||||
{...(attribute.autocomplete === undefined
|
autoComplete={attribute.autocomplete}
|
||||||
? {}
|
|
||||||
: {
|
|
||||||
"autoComplete": attribute.autocomplete,
|
|
||||||
})}
|
|
||||||
onBlur={onBlurFactory(attribute.name)}
|
onBlur={onBlurFactory(attribute.name)}
|
||||||
/>
|
/>
|
||||||
{displayableErrors.length !== 0 && (
|
{displayableErrors.length !== 0 && (
|
||||||
|
@ -217,10 +217,64 @@ export type Attribute = {
|
|||||||
groupDisplayHeader?: string;
|
groupDisplayHeader?: string;
|
||||||
groupDisplayDescription?: string;
|
groupDisplayDescription?: string;
|
||||||
readOnly: boolean;
|
readOnly: boolean;
|
||||||
autocomplete?: string;
|
|
||||||
validators: Validators;
|
validators: Validators;
|
||||||
annotations: Record<string, string>;
|
annotations: Record<string, string>;
|
||||||
groupAnnotations: Record<string, string>;
|
groupAnnotations: Record<string, string>;
|
||||||
|
autocomplete?:
|
||||||
|
| "on"
|
||||||
|
| "off"
|
||||||
|
| "name"
|
||||||
|
| "honorific-prefix"
|
||||||
|
| "given-name"
|
||||||
|
| "additional-name"
|
||||||
|
| "family-name"
|
||||||
|
| "honorific-suffix"
|
||||||
|
| "nickname"
|
||||||
|
| "email"
|
||||||
|
| "username"
|
||||||
|
| "new-password"
|
||||||
|
| "current-password"
|
||||||
|
| "one-time-code"
|
||||||
|
| "organization-title"
|
||||||
|
| "organization"
|
||||||
|
| "street-address"
|
||||||
|
| "address-line1"
|
||||||
|
| "address-line2"
|
||||||
|
| "address-line3"
|
||||||
|
| "address-level4"
|
||||||
|
| "address-level3"
|
||||||
|
| "address-level2"
|
||||||
|
| "address-level1"
|
||||||
|
| "country"
|
||||||
|
| "country-name"
|
||||||
|
| "postal-code"
|
||||||
|
| "cc-name"
|
||||||
|
| "cc-given-name"
|
||||||
|
| "cc-additional-name"
|
||||||
|
| "cc-family-name"
|
||||||
|
| "cc-number"
|
||||||
|
| "cc-exp"
|
||||||
|
| "cc-exp-month"
|
||||||
|
| "cc-exp-year"
|
||||||
|
| "cc-csc"
|
||||||
|
| "cc-type"
|
||||||
|
| "transaction-currency"
|
||||||
|
| "transaction-amount"
|
||||||
|
| "language"
|
||||||
|
| "bday"
|
||||||
|
| "bday-day"
|
||||||
|
| "bday-month"
|
||||||
|
| "bday-year"
|
||||||
|
| "sex"
|
||||||
|
| "tel"
|
||||||
|
| "tel-country-code"
|
||||||
|
| "tel-national"
|
||||||
|
| "tel-area-code"
|
||||||
|
| "tel-local"
|
||||||
|
| "tel-extension"
|
||||||
|
| "impp"
|
||||||
|
| "url"
|
||||||
|
| "photo";
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Validators = Partial<{
|
export type Validators = Partial<{
|
||||||
|
@ -1,13 +1,12 @@
|
|||||||
import type { KcContextBase } from "./KcContextBase";
|
import type { KcContextBase, Attribute } from "./KcContextBase";
|
||||||
import { kcContextMocks, kcContextCommonMock } from "./kcContextMocks";
|
import { kcContextMocks, kcContextCommonMock } from "./kcContextMocks";
|
||||||
import { ftlValuesGlobalName } from "../../bin/build-keycloak-theme/ftlValuesGlobalName";
|
|
||||||
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";
|
||||||
|
import { id } from "tsafe/id";
|
||||||
export type ExtendsKcContextBase<KcContextExtended extends { pageId: string }> = [KcContextExtended] extends [never]
|
import { exclude } from "tsafe/exclude";
|
||||||
? KcContextBase
|
import { assert } from "tsafe/assert";
|
||||||
: AndByDiscriminatingKey<"pageId", KcContextExtended & KcContextBase.Common, KcContextBase>;
|
import type { ExtendsKcContextBase } from "./getKcContextFromWindow";
|
||||||
|
import { getKcContextFromWindow } from "./getKcContextFromWindow";
|
||||||
|
|
||||||
export function getKcContext<KcContextExtended extends { pageId: string } = never>(params?: {
|
export function getKcContext<KcContextExtended extends { pageId: string } = never>(params?: {
|
||||||
mockPageId?: ExtendsKcContextBase<KcContextExtended>["pageId"];
|
mockPageId?: ExtendsKcContextBase<KcContextExtended>["pageId"];
|
||||||
@ -44,12 +43,55 @@ export function getKcContext<KcContextExtended extends { pageId: string } = neve
|
|||||||
"target": kcContext,
|
"target": kcContext,
|
||||||
"source": partialKcContextCustomMock,
|
"source": partialKcContextCustomMock,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (partialKcContextCustomMock.pageId === "register-user-profile.ftl") {
|
||||||
|
assert(kcContextDefaultMock?.pageId === "register-user-profile.ftl");
|
||||||
|
|
||||||
|
const { attributes } = kcContextDefaultMock.profile;
|
||||||
|
|
||||||
|
id<KcContextBase.RegisterUserProfile>(kcContext).profile.attributes = [];
|
||||||
|
id<KcContextBase.RegisterUserProfile>(kcContext).profile.attributesByName = {};
|
||||||
|
|
||||||
|
const partialAttributes = [
|
||||||
|
...((partialKcContextCustomMock as DeepPartial<KcContextBase.RegisterUserProfile>).profile?.attributes ?? []),
|
||||||
|
].filter(exclude(undefined));
|
||||||
|
|
||||||
|
attributes.forEach(attribute => {
|
||||||
|
const partialAttribute = partialAttributes.find(({ name }) => name === attribute.name);
|
||||||
|
|
||||||
|
const augmentedAttribute: Attribute = {} as any;
|
||||||
|
|
||||||
|
deepAssign({
|
||||||
|
"target": augmentedAttribute,
|
||||||
|
"source": attribute,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (partialAttribute !== undefined) {
|
||||||
|
partialAttributes.splice(partialAttributes.indexOf(partialAttribute), 1);
|
||||||
|
|
||||||
|
deepAssign({
|
||||||
|
"target": augmentedAttribute,
|
||||||
|
"source": partialAttribute,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
id<KcContextBase.RegisterUserProfile>(kcContext).profile.attributes.push(augmentedAttribute);
|
||||||
|
id<KcContextBase.RegisterUserProfile>(kcContext).profile.attributesByName[augmentedAttribute.name] = augmentedAttribute;
|
||||||
|
});
|
||||||
|
|
||||||
|
partialAttributes.forEach(partialAttribute => {
|
||||||
|
const { name } = partialAttribute;
|
||||||
|
|
||||||
|
assert(name !== undefined, "If you define a mock attribute it must have at least a name");
|
||||||
|
|
||||||
|
id<KcContextBase.RegisterUserProfile>(kcContext).profile.attributes.push(partialAttribute as any);
|
||||||
|
id<KcContextBase.RegisterUserProfile>(kcContext).profile.attributesByName[name] = partialAttribute as any;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return { kcContext };
|
return { kcContext };
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return { "kcContext": getKcContextFromWindow<KcContextExtended>() };
|
||||||
"kcContext": typeof window === "undefined" ? undefined : (window as any)[ftlValuesGlobalName],
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
11
src/lib/getKcContext/getKcContextFromWindow.ts
Normal file
11
src/lib/getKcContext/getKcContextFromWindow.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import type { KcContextBase } from "./KcContextBase";
|
||||||
|
import type { AndByDiscriminatingKey } from "../tools/AndByDiscriminatingKey";
|
||||||
|
import { ftlValuesGlobalName } from "../../bin/build-keycloak-theme/ftlValuesGlobalName";
|
||||||
|
|
||||||
|
export type ExtendsKcContextBase<KcContextExtended extends { pageId: string }> = [KcContextExtended] extends [never]
|
||||||
|
? KcContextBase
|
||||||
|
: AndByDiscriminatingKey<"pageId", KcContextExtended & KcContextBase.Common, KcContextBase>;
|
||||||
|
|
||||||
|
export function getKcContextFromWindow<KcContextExtended extends { pageId: string } = never>(): ExtendsKcContextBase<KcContextExtended> | undefined {
|
||||||
|
return typeof window === "undefined" ? undefined : (window as any)[ftlValuesGlobalName];
|
||||||
|
}
|
@ -1,2 +1,3 @@
|
|||||||
export type { KcContextBase } from "./KcContextBase";
|
export type { KcContextBase, Attribute, Validators } from "./KcContextBase";
|
||||||
|
export type { ExtendsKcContextBase } from "./getKcContextFromWindow";
|
||||||
export { getKcContext } from "./getKcContext";
|
export { getKcContext } from "./getKcContext";
|
||||||
|
@ -278,29 +278,6 @@ export const kcContextMocks: KcContextBase[] = [
|
|||||||
"readOnly": false,
|
"readOnly": false,
|
||||||
"name": "lastName",
|
"name": "lastName",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"validators": {
|
|
||||||
"length": {
|
|
||||||
"ignore.empty.value": true,
|
|
||||||
"min": "3",
|
|
||||||
"max": "9",
|
|
||||||
},
|
|
||||||
"up-immutable-attribute": {},
|
|
||||||
"up-attribute-required-by-metadata-value": {},
|
|
||||||
"email": {
|
|
||||||
"ignore.empty.value": true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"displayName": "${foo}",
|
|
||||||
"annotations": {
|
|
||||||
"this_is_second_key": "this_is_second_value",
|
|
||||||
"this_is_first_key": "this_is_first_value",
|
|
||||||
},
|
|
||||||
"required": true,
|
|
||||||
"groupAnnotations": {},
|
|
||||||
"readOnly": false,
|
|
||||||
"name": "foo",
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { createUseGlobalState } from "powerhooks/useGlobalState";
|
import { createUseGlobalState } from "powerhooks/useGlobalState";
|
||||||
import { getKcContext } from "../getKcContext";
|
import { getKcContextFromWindow } from "../getKcContext/getKcContextFromWindow";
|
||||||
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";
|
||||||
@ -8,7 +8,7 @@ import { KcLanguageTag } from "./KcLanguageTag";
|
|||||||
const wrap = createUseGlobalState(
|
const wrap = createUseGlobalState(
|
||||||
"kcLanguageTag",
|
"kcLanguageTag",
|
||||||
() => {
|
() => {
|
||||||
const { kcContext } = getKcContext();
|
const kcContext = getKcContextFromWindow();
|
||||||
|
|
||||||
const languageLike = kcContext?.locale?.current ?? (typeof navigator === "undefined" ? undefined : navigator.language);
|
const languageLike = kcContext?.locale?.current ?? (typeof navigator === "undefined" ? undefined : navigator.language);
|
||||||
|
|
||||||
|
@ -14,5 +14,6 @@ export * from "./components/Error";
|
|||||||
export * from "./components/LoginResetPassword";
|
export * from "./components/LoginResetPassword";
|
||||||
export * from "./components/LoginVerifyEmail";
|
export * from "./components/LoginVerifyEmail";
|
||||||
export * from "./keycloakJsAdapter";
|
export * from "./keycloakJsAdapter";
|
||||||
|
export * from "./useFormValidationSlice";
|
||||||
|
|
||||||
export * from "./tools/assert";
|
export * from "./tools/assert";
|
||||||
|
@ -1,9 +1,12 @@
|
|||||||
import { assert } from "tsafe/assert";
|
import { assert } from "tsafe/assert";
|
||||||
import { is } from "tsafe/is";
|
import { is } from "tsafe/is";
|
||||||
|
import { deepClone } from "./deepClone";
|
||||||
|
|
||||||
//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(params: { target: Record<string, unknown>; source: Record<string, unknown> }) {
|
export function deepAssign(params: { target: Record<string, unknown>; source: Record<string, unknown> }) {
|
||||||
const { target, source } = params;
|
const { target } = params;
|
||||||
|
|
||||||
|
const source = deepClone(params.source);
|
||||||
|
|
||||||
Object.keys(source).forEach(key => {
|
Object.keys(source).forEach(key => {
|
||||||
var dereferencedSource = source[key];
|
var dereferencedSource = source[key];
|
||||||
|
@ -1,3 +1,17 @@
|
|||||||
export function deepClone<T>(arg: T): T {
|
import "minimal-polyfills/Object.fromEntries";
|
||||||
return JSON.parse(JSON.stringify(arg));
|
|
||||||
|
export function deepClone<T>(o: T): T {
|
||||||
|
if (!(o instanceof Object)) {
|
||||||
|
return o;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof o === "function") {
|
||||||
|
return o;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (o instanceof Array) {
|
||||||
|
return o.map(deepClone) as any;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Object.fromEntries(Object.entries(o).map(([key, value]) => [key, deepClone(value)])) as any;
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,6 @@ import { useKcMessage } from "./i18n/useKcMessage";
|
|||||||
import { useConstCallback } from "powerhooks/useConstCallback";
|
import { useConstCallback } from "powerhooks/useConstCallback";
|
||||||
import { id } from "tsafe/id";
|
import { id } from "tsafe/id";
|
||||||
import type { MessageKey } from "./i18n/useKcMessage";
|
import type { MessageKey } from "./i18n/useKcMessage";
|
||||||
import { useConst } from "powerhooks/useConst";
|
|
||||||
import { emailRegexp } from "./tools/emailRegExp";
|
import { emailRegexp } from "./tools/emailRegExp";
|
||||||
|
|
||||||
export type KcContextLike = {
|
export type KcContextLike = {
|
||||||
@ -278,6 +277,7 @@ export function useFormValidationSlice(params: {
|
|||||||
passwordRequired: boolean;
|
passwordRequired: boolean;
|
||||||
realm: { registrationEmailAsUsername: boolean };
|
realm: { registrationEmailAsUsername: boolean };
|
||||||
};
|
};
|
||||||
|
/** NOTE: Try to avoid passing a new ref every render for better performances. */
|
||||||
passwordValidators?: Validators;
|
passwordValidators?: Validators;
|
||||||
}) {
|
}) {
|
||||||
const {
|
const {
|
||||||
@ -290,49 +290,53 @@ export function useFormValidationSlice(params: {
|
|||||||
},
|
},
|
||||||
} = params;
|
} = params;
|
||||||
|
|
||||||
const attributesWithPassword = useConst(() =>
|
const attributesWithPassword = useMemo(
|
||||||
!kcContext.passwordRequired
|
() =>
|
||||||
? kcContext.profile.attributes
|
!kcContext.passwordRequired
|
||||||
: (() => {
|
? kcContext.profile.attributes
|
||||||
const name = kcContext.realm.registrationEmailAsUsername ? "email" : "username";
|
: (() => {
|
||||||
|
const name = kcContext.realm.registrationEmailAsUsername ? "email" : "username";
|
||||||
|
|
||||||
return kcContext.profile.attributes.reduce<Attribute[]>(
|
return kcContext.profile.attributes.reduce<Attribute[]>(
|
||||||
(prev, curr) => [
|
(prev, curr) => [
|
||||||
...prev,
|
...prev,
|
||||||
...(curr.name !== name
|
...(curr.name !== name
|
||||||
? [curr]
|
? [curr]
|
||||||
: [
|
: [
|
||||||
curr,
|
curr,
|
||||||
id<Attribute>({
|
id<Attribute>({
|
||||||
"name": "password",
|
"name": "password",
|
||||||
"displayName": id<`\${${MessageKey}}`>("${password}"),
|
"displayName": id<`\${${MessageKey}}`>("${password}"),
|
||||||
"required": true,
|
"required": true,
|
||||||
"readOnly": false,
|
"readOnly": false,
|
||||||
"validators": passwordValidators,
|
"validators": passwordValidators,
|
||||||
"annotations": {},
|
"annotations": {},
|
||||||
"groupAnnotations": {},
|
"groupAnnotations": {},
|
||||||
}),
|
"autocomplete": "new-password",
|
||||||
id<Attribute>({
|
}),
|
||||||
"name": "password-confirm",
|
id<Attribute>({
|
||||||
"displayName": id<`\${${MessageKey}}`>("${passwordConfirm}"),
|
"name": "password-confirm",
|
||||||
"required": true,
|
"displayName": id<`\${${MessageKey}}`>("${passwordConfirm}"),
|
||||||
"readOnly": false,
|
"required": true,
|
||||||
"validators": {
|
"readOnly": false,
|
||||||
"_compareToOther": {
|
"validators": {
|
||||||
"name": "password",
|
"_compareToOther": {
|
||||||
"ignore.empty.value": true,
|
"name": "password",
|
||||||
"shouldBe": "equal",
|
"ignore.empty.value": true,
|
||||||
"error-message": id<`\${${MessageKey}}`>("${invalidPasswordConfirmMessage}"),
|
"shouldBe": "equal",
|
||||||
|
"error-message": id<`\${${MessageKey}}`>("${invalidPasswordConfirmMessage}"),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
"annotations": {},
|
||||||
"annotations": {},
|
"groupAnnotations": {},
|
||||||
"groupAnnotations": {},
|
"autocomplete": "new-password",
|
||||||
}),
|
}),
|
||||||
]),
|
]),
|
||||||
],
|
],
|
||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
})(),
|
})(),
|
||||||
|
[kcContext, passwordValidators],
|
||||||
);
|
);
|
||||||
|
|
||||||
const { getErrors } = useGetErrors({
|
const { getErrors } = useGetErrors({
|
||||||
@ -344,27 +348,29 @@ export function useFormValidationSlice(params: {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const initialInternalState = useConst(() =>
|
const initialInternalState = useMemo(
|
||||||
Object.fromEntries(
|
() =>
|
||||||
attributesWithPassword
|
Object.fromEntries(
|
||||||
.map(attribute => ({
|
attributesWithPassword
|
||||||
attribute,
|
.map(attribute => ({
|
||||||
"errors": getErrors({
|
attribute,
|
||||||
"name": attribute.name,
|
"errors": getErrors({
|
||||||
"fieldValueByAttributeName": Object.fromEntries(
|
"name": attribute.name,
|
||||||
attributesWithPassword.map(({ name, value }) => [name, { "value": value ?? "" }]),
|
"fieldValueByAttributeName": Object.fromEntries(
|
||||||
),
|
attributesWithPassword.map(({ name, value }) => [name, { "value": value ?? "" }]),
|
||||||
}),
|
),
|
||||||
}))
|
}),
|
||||||
.map(({ attribute, errors }) => [
|
}))
|
||||||
attribute.name,
|
.map(({ attribute, errors }) => [
|
||||||
{
|
attribute.name,
|
||||||
"value": attribute.value ?? "",
|
{
|
||||||
errors,
|
"value": attribute.value ?? "",
|
||||||
"doDisplayPotentialErrorMessages": errors.length !== 0,
|
errors,
|
||||||
},
|
"doDisplayPotentialErrorMessages": errors.length !== 0,
|
||||||
]),
|
},
|
||||||
),
|
]),
|
||||||
|
),
|
||||||
|
[attributesWithPassword],
|
||||||
);
|
);
|
||||||
|
|
||||||
type InternalState = typeof initialInternalState;
|
type InternalState = typeof initialInternalState;
|
||||||
@ -421,7 +427,7 @@ export function useFormValidationSlice(params: {
|
|||||||
errors.length === 0 && (value !== "" || !attributesWithPassword.find(attribute => attribute.name === name)!.required),
|
errors.length === 0 && (value !== "" || !attributesWithPassword.find(attribute => attribute.name === name)!.required),
|
||||||
),
|
),
|
||||||
}),
|
}),
|
||||||
[formValidationInternalState],
|
[formValidationInternalState, attributesWithPassword],
|
||||||
);
|
);
|
||||||
|
|
||||||
return { formValidationState, formValidationReducer, attributesWithPassword };
|
return { formValidationState, formValidationReducer, attributesWithPassword };
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
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";
|
||||||
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";
|
||||||
|
30
yarn.lock
30
yarn.lock
@ -771,11 +771,6 @@ inherits@2, inherits@^2.0.1, inherits@~2.0.1, inherits@~2.0.3:
|
|||||||
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
|
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
|
||||||
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
|
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
|
||||||
|
|
||||||
inherits@2.0.3:
|
|
||||||
version "2.0.3"
|
|
||||||
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
|
|
||||||
integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=
|
|
||||||
|
|
||||||
inline-style-parser@0.1.1:
|
inline-style-parser@0.1.1:
|
||||||
version "0.1.1"
|
version "0.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/inline-style-parser/-/inline-style-parser-0.1.1.tgz#ec8a3b429274e9c0a1f1c4ffa9453a7fef72cea1"
|
resolved "https://registry.yarnpkg.com/inline-style-parser/-/inline-style-parser-0.1.1.tgz#ec8a3b429274e9c0a1f1c4ffa9453a7fef72cea1"
|
||||||
@ -1174,6 +1169,11 @@ parse5@^6.0.1:
|
|||||||
resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b"
|
resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b"
|
||||||
integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==
|
integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==
|
||||||
|
|
||||||
|
path-browserify@^1.0.1:
|
||||||
|
version "1.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-1.0.1.tgz#d98454a9c3753d5790860f16f68867b9e46be1fd"
|
||||||
|
integrity sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==
|
||||||
|
|
||||||
path-exists@^4.0.0:
|
path-exists@^4.0.0:
|
||||||
version "4.0.0"
|
version "4.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3"
|
resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3"
|
||||||
@ -1194,14 +1194,6 @@ path-type@^4.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b"
|
resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b"
|
||||||
integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==
|
integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==
|
||||||
|
|
||||||
path@^0.12.7:
|
|
||||||
version "0.12.7"
|
|
||||||
resolved "https://registry.yarnpkg.com/path/-/path-0.12.7.tgz#d4dc2a506c4ce2197eb481ebfcd5b36c0140b10f"
|
|
||||||
integrity sha1-1NwqUGxM4hl+tIHr/NWzbAFAsQ8=
|
|
||||||
dependencies:
|
|
||||||
process "^0.11.1"
|
|
||||||
util "^0.10.3"
|
|
||||||
|
|
||||||
picomatch@^2.2.3:
|
picomatch@^2.2.3:
|
||||||
version "2.3.0"
|
version "2.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972"
|
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972"
|
||||||
@ -1241,11 +1233,6 @@ process-nextick-args@~2.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2"
|
resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2"
|
||||||
integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==
|
integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==
|
||||||
|
|
||||||
process@^0.11.1:
|
|
||||||
version "0.11.10"
|
|
||||||
resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182"
|
|
||||||
integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI=
|
|
||||||
|
|
||||||
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"
|
||||||
@ -1677,13 +1664,6 @@ util-deprecate@~1.0.1:
|
|||||||
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"
|
||||||
integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=
|
integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=
|
||||||
|
|
||||||
util@^0.10.3:
|
|
||||||
version "0.10.4"
|
|
||||||
resolved "https://registry.yarnpkg.com/util/-/util-0.10.4.tgz#3aa0125bfe668a4672de58857d3ace27ecb76901"
|
|
||||||
integrity sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==
|
|
||||||
dependencies:
|
|
||||||
inherits "2.0.3"
|
|
||||||
|
|
||||||
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"
|
||||||
|
Reference in New Issue
Block a user